From 13d68e99aa2f0253c2596cd1dc5eade343e1c521 Mon Sep 17 00:00:00 2001 From: jrheinlaender Date: Sat, 3 Nov 2012 23:07:48 +0100 Subject: [PATCH] PartDesign, 758, 766, 773, 775: several improvements, fixes and code refactoring for Pad and Pocket --- src/Mod/PartDesign/App/FeaturePad.cpp | 196 +++---------- src/Mod/PartDesign/App/FeaturePad.h | 2 +- src/Mod/PartDesign/App/FeaturePocket.cpp | 205 +++++--------- src/Mod/PartDesign/App/FeaturePocket.h | 2 +- src/Mod/PartDesign/App/FeatureSketchBased.cpp | 189 ++++++++++++- src/Mod/PartDesign/App/FeatureSketchBased.h | 35 +++ src/Mod/PartDesign/Gui/TaskPadParameters.cpp | 195 ++++++++++--- src/Mod/PartDesign/Gui/TaskPadParameters.h | 2 + src/Mod/PartDesign/Gui/TaskPadParameters.ui | 15 +- .../PartDesign/Gui/TaskPocketParameters.cpp | 258 ++++++++++++++---- src/Mod/PartDesign/Gui/TaskPocketParameters.h | 7 + .../PartDesign/Gui/TaskPocketParameters.ui | 151 +++++----- 12 files changed, 795 insertions(+), 462 deletions(-) diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index 5a9733b92..ca02bf850 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -26,7 +26,7 @@ # include # include # include -# include +# include # include # include # include @@ -58,7 +58,7 @@ Pad::Pad() Type.setEnums(TypeEnums); ADD_PROPERTY(Length,(100.0)); ADD_PROPERTY(Length2,(100.0)); - ADD_PROPERTY(FaceName,("")); + ADD_PROPERTY_TYPE(UpToFace,(0),"Pad",(App::PropertyType)(App::Prop_None),"Face where feature will end"); } short Pad::mustExecute() const @@ -66,7 +66,7 @@ short Pad::mustExecute() const if (Placement.isTouched() || Length.isTouched() || Length2.isTouched() || - FaceName.isTouched()) + UpToFace.isTouched()) return 1; return Additive::mustExecute(); } @@ -108,171 +108,57 @@ App::DocumentObjectExecReturn *Pad::execute(void) TopLoc_Location invObjLoc = this->getLocation().Inverted(); try { - // TopoDS::Face is not strictly necessary, but it will through an exception for - // invalid wires e.g. intersections or multiple separate wires - TopoDS_Shape aFace = TopoDS::Face(makeFace(wires)); - if (aFace.IsNull()) - return new App::DocumentObjectExecReturn("Creating a face from sketch failed"); + support.Move(invObjLoc); - // extrude the face to a solid + gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); + dir.Transform(invObjLoc.Transformation()); + + TopoDS_Shape sketchshape = makeFace(wires); + if (sketchshape.IsNull()) + return new App::DocumentObjectExecReturn("Pad: Creating a face from sketch failed"); + sketchshape.Move(invObjLoc); + TopoDS_Shape prism; - bool isSolid = false; // support is a solid? - bool isSolidChecked = false; // not checked yet + std::string method(Type.getValueAsString()); + if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") { + TopoDS_Face supportface = getSupportFace(); + supportface.Move(invObjLoc); - if ((std::string(Type.getValueAsString()) == "UpToLast") || - (std::string(Type.getValueAsString()) == "UpToFirst") || - (std::string(Type.getValueAsString()) == "UpToFace")) - { + // Find a valid face to extrude up to TopoDS_Face upToFace; - gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); - - if ((std::string(Type.getValueAsString()) == "UpToLast") || - (std::string(Type.getValueAsString()) == "UpToFirst")) - { - // Check for valid support object - if (support.IsNull()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: No valid support in Sketch"); - TopExp_Explorer xp (support, TopAbs_SOLID); - if (!xp.More()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Support shape is not a solid"); - isSolid = true; - isSolidChecked = true; - - TopoDS_Shape origFace = makeFace(wires); // original sketch face before moving one unit - std::vector cfaces = Part::findAllFacesCutBy(support, origFace, dir); - if (cfaces.empty()) - return new App::DocumentObjectExecReturn("No faces found in this direction"); - - // Find nearest/furthest face - std::vector::const_iterator it, it_near, it_far; - it_near = it_far = cfaces.begin(); - for (it = cfaces.begin(); it != cfaces.end(); it++) - if (it->distsq > it_far->distsq) - it_far = it; - else if (it->distsq < it_near->distsq) - it_near = it; - upToFace = (std::string(Type.getValueAsString()) == "UpToLast" ? it_far->face : it_near->face); - } else { - if (FaceName.isEmpty()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: No face selected"); - - // Get active object, this is the object that the user referenced when he clicked on the face! - App::DocumentObject* baseLink = this->getDocument()->getActiveObject(); - - if (!baseLink) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: No object linked"); - if (!baseLink->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Linked object is not a Part object"); - Part::Feature *base = static_cast(baseLink); - const Part::TopoShape& baseShape = base->Shape.getShape(); - if (baseShape._Shape.IsNull()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Cannot work on invalid shape"); - - TopoDS_Shape sub = baseShape.getSubShape(FaceName.getValue()); - if (!sub.IsNull() && sub.ShapeType() == TopAbs_FACE) - upToFace = TopoDS::Face(sub); - else - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Selection is not a face"); - - // Validate face - // TODO: This would also exclude faces that are valid but not cut by the line - // So for now we trust to the intelligence of the user when picking the face - /*std::vector cfaces = findAllFacesCutBy(upToFace, origFace, dir); - if (cfaces.empty()) - return new App::DocumentObjectExecReturn("No faces found in this direction");*/ + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.Move(invObjLoc); } + getUpToFace(upToFace, support, supportface, sketchshape, method, dir); + + // A support object is always required and we need to use BRepFeat_MakePrism + // Problem: For Pocket/UpToFirst (or an equivalent Pocket/UpToFace) the resulting shape is invalid + // because the feature does not add any material. This only happens with the "2" option, though + // Note: It might be possible to pass a shell or a compound containing multiple faces + // as the Until parameter of Perform() + BRepFeat_MakePrism PrismMaker; + PrismMaker.Init(support, sketchshape, supportface, dir, 2, 1); + PrismMaker.Perform(upToFace); - // Create semi-infinite prism from sketch in direction dir - // Hack, because the two lines commented out below do NOT work!!! - SketchVector *= 1E6; - gp_Vec vec(SketchVector.x,SketchVector.y,SketchVector.z); - vec.Transform(invObjLoc.Transformation()); - BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),vec,0,1); // very long, but finite prism - //dir.Transform(invObjLoc.Transformation()); - //BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),dir,0,0,1); if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Could not extrude the sketch!"); - - // Cut off the prism at the face we found - // Grab any point from the sketch - TopExp_Explorer exp; - exp.Init(aFace, TopAbs_VERTEX); - if (!exp.More()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Sketch without points?"); - gp_Pnt aPnt = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current())); - - // Create a halfspace from the face, extending in direction of sketch plane - BRepPrimAPI_MakeHalfSpace mkHalfSpace(upToFace, aPnt); - if (!mkHalfSpace.IsDone()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: HalfSpace creation failed"); - - // Find common material between halfspace and prism - BRepAlgoAPI_Common mkCommon(PrismMaker.Shape(), mkHalfSpace.Solid().Moved(invObjLoc)); - if (!mkCommon.IsDone()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Common creation failed"); - - prism = this->getSolid(mkCommon.Shape()); - if (prism.IsNull()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Resulting shape is not a solid"); - } else if ((std::string(Type.getValueAsString()) == "Length") || - (std::string(Type.getValueAsString()) == "TwoLengths")) { - if (std::string(Type.getValueAsString()) == "Length") { - if (Midplane.getValue()) { - // Move face by half the extrusion distance to get pad symmetric to sketch plane - gp_Trsf mov; - mov.SetTranslation(gp_Vec(SketchVector.x,SketchVector.y,SketchVector.z) * (-1.0) * L/2.0); - TopLoc_Location loc(mov); - aFace.Move(loc); - } else if (Reversed.getValue()) { // negative direction - SketchVector *= -1.0; - } - - // lengthen the vector - SketchVector *= L; - } else { - // Move face by the second length to get pad extending to both sides of sketch plane - gp_Trsf mov; - mov.SetTranslation(gp_Vec(SketchVector.x,SketchVector.y,SketchVector.z) * (-1.0) * L2); - TopLoc_Location loc(mov); - aFace.Move(loc); - - // lengthen the vector - SketchVector *= (L + L2); - } - - // create the extrusion - gp_Vec vec(SketchVector.x,SketchVector.y,SketchVector.z); - vec.Transform(invObjLoc.Transformation()); - BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),vec,0,1); // finite prism - if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Could not extrude the sketch!"); + return new App::DocumentObjectExecReturn("Pad: Up to face: Could not extrude the sketch!"); prism = PrismMaker.Shape(); } else { - return new App::DocumentObjectExecReturn("Internal error: Unknown type for Pad feature"); + generatePrism(prism, sketchshape, method, dir, L, L2, + Midplane.getValue(), Reversed.getValue()); } + if (prism.IsNull()) + return new App::DocumentObjectExecReturn("Pad: Resulting shape is empty"); + // set the additive shape property for later usage in e.g. pattern this->AddShape.setValue(prism); // if the sketch has a support fuse them to get one result object if (!support.IsNull()) { - - if (!isSolidChecked) { // we haven't checked for solid, yet - if (!support.IsNull()) { - TopExp_Explorer xp; - xp.Init(support,TopAbs_SOLID); - for (;xp.More(); xp.Next()) { - isSolid = true; - break; - } - } - - if (!isSolid) - return new App::DocumentObjectExecReturn("Support is not a solid"); - } - // Let's call algorithm computing a fuse operation: - BRepAlgoAPI_Fuse mkFuse(support.Moved(invObjLoc), prism); + BRepAlgoAPI_Fuse mkFuse(support, prism); // Let's check if the fusion has been successful if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn("Pad: Fusion with support failed"); @@ -283,12 +169,8 @@ App::DocumentObjectExecReturn *Pad::execute(void) if (solRes.IsNull()) return new App::DocumentObjectExecReturn("Pad: Resulting shape is not a solid"); this->Shape.setValue(solRes); - } - else { - TopoDS_Shape result = this->getSolid(prism); - // set the additive shape property for later usage in e.g. pattern - this->AddShape.setValue(result); - this->Shape.setValue(result); + } else { + this->Shape.setValue(prism); } return App::DocumentObject::StdReturn; diff --git a/src/Mod/PartDesign/App/FeaturePad.h b/src/Mod/PartDesign/App/FeaturePad.h index f70803df7..6bfb9e609 100644 --- a/src/Mod/PartDesign/App/FeaturePad.h +++ b/src/Mod/PartDesign/App/FeaturePad.h @@ -41,7 +41,7 @@ public: App::PropertyEnumeration Type; App::PropertyLength Length; App::PropertyLength Length2; - App::PropertyString FaceName; + App::PropertyLinkSub UpToFace; /** @name methods override feature */ //@{ diff --git a/src/Mod/PartDesign/App/FeaturePocket.cpp b/src/Mod/PartDesign/App/FeaturePocket.cpp index 639cf1058..5779e32a1 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.cpp +++ b/src/Mod/PartDesign/App/FeaturePocket.cpp @@ -27,10 +27,9 @@ # include # include # include -# include # include # include -# include +# include # include # include # include @@ -52,7 +51,7 @@ using namespace PartDesign; -const char* Pocket::TypeEnums[]= {"Length","UpToLast","UpToFirst","ThroughAll","UpToFace",NULL}; +const char* Pocket::TypeEnums[]= {"Length","ThroughAll","UpToFirst","UpToFace",NULL}; PROPERTY_SOURCE(PartDesign::Pocket, PartDesign::Subtractive) @@ -61,20 +60,26 @@ Pocket::Pocket() ADD_PROPERTY(Type,((long)0)); Type.setEnums(TypeEnums); ADD_PROPERTY(Length,(100.0)); - ADD_PROPERTY(FaceName,("")); + ADD_PROPERTY_TYPE(UpToFace,(0),"Pocket",(App::PropertyType)(App::Prop_None),"Face where feature will end"); } short Pocket::mustExecute() const { if (Placement.isTouched() || Length.isTouched() || - FaceName.isTouched()) + UpToFace.isTouched()) return 1; return Subtractive::mustExecute(); } App::DocumentObjectExecReturn *Pocket::execute(void) { + // Handle legacy features, these typically have Type set to 3 (previously NULL, now UpToFace), + // empty FaceName (because it didn't exist) and a value for Length + if (std::string(Type.getValueAsString()) == "UpToFace" && + (UpToFace.getValue() == NULL && Length.getValue() > Precision::Confusion())) + Type.setValue("Length"); + // Validate parameters double L = Length.getValue(); if ((std::string(Type.getValueAsString()) == "Length") && (L < Precision::Confusion())) @@ -97,156 +102,84 @@ App::DocumentObjectExecReturn *Pocket::execute(void) Base::Vector3d SketchVector(0,0,1); SketchOrientation.multVec(SketchVector,SketchVector); + // turn around for pockets + SketchVector *= -1; + this->positionBySketch(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); try { - TopoDS_Face aFace = TopoDS::Face(makeFace(wires)); - if (aFace.IsNull()) - return new App::DocumentObjectExecReturn("Pocket: Creating a face from sketch failed"); - // This is a trick to avoid problems with the cut operation. Sometimes a cut doesn't - // work as expected if faces are coincident. Thus, we move the face in normal direction - // but make it longer by one unit in the opposite direction. - // TODO: Isn't one unit (one millimeter) a lot, assuming someone models a really tiny solid? - // What about using 2 * Precision::Confusion() ? - gp_Trsf mov; - mov.SetTranslation(gp_Vec(SketchVector.x,SketchVector.y,SketchVector.z)); - TopLoc_Location loc(mov); - aFace.Move(loc); + support.Move(invObjLoc); - // lengthen the vector - SketchVector *= (Length.getValue()+1); + gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); + dir.Transform(invObjLoc.Transformation()); - // turn around for pockets - SketchVector *= -1; + TopoDS_Shape sketchshape = makeFace(wires); + if (sketchshape.IsNull()) + return new App::DocumentObjectExecReturn("Pocket: Creating a face from sketch failed"); + sketchshape.Move(invObjLoc); - // extrude the face to a solid - TopoDS_Shape prism; + std::string method(Type.getValueAsString()); + if (method == "UpToFirst" || method == "UpToFace") { + TopoDS_Face supportface = getSupportFace(); + supportface.Move(invObjLoc); - if ((std::string(Type.getValueAsString()) == "UpToLast") || - (std::string(Type.getValueAsString()) == "UpToFirst") || - (std::string(Type.getValueAsString()) == "UpToFace")) - { + // Find a valid face to extrude up to TopoDS_Face upToFace; - gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); - - if ((std::string(Type.getValueAsString()) == "UpToLast") || - (std::string(Type.getValueAsString()) == "UpToFirst")) - { - TopoDS_Shape origFace = makeFace(wires); // original sketch face before moving one unit - std::vector cfaces = Part::findAllFacesCutBy(support, origFace, dir); - if (cfaces.empty()) - return new App::DocumentObjectExecReturn("No faces found in this direction"); - - // Find nearest/furthest face - std::vector::const_iterator it, it_near, it_far; - it_near = it_far = cfaces.begin(); - for (it = cfaces.begin(); it != cfaces.end(); it++) - if (it->distsq > it_far->distsq) - it_far = it; - else if (it->distsq < it_near->distsq) - it_near = it; - upToFace = (std::string(Type.getValueAsString()) == "UpToLast" ? it_far->face : it_near->face); - } else { - if (FaceName.isEmpty()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: No face selected"); - - // Get active object, this is the object that the user referenced when he clicked on the face! - App::DocumentObject* baseLink = this->getDocument()->getActiveObject(); - - if (!baseLink) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: No object linked"); - if (!baseLink->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Linked object is not a Part object"); - Part::Feature *base = static_cast(baseLink); - const Part::TopoShape& baseShape = base->Shape.getShape(); - if (baseShape._Shape.IsNull()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Cannot work on invalid shape"); - - TopoDS_Shape sub = baseShape.getSubShape(FaceName.getValue()); - if (!sub.IsNull() && sub.ShapeType() == TopAbs_FACE) - upToFace = TopoDS::Face(sub); - else - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Selection is not a face"); - - // Find the origin of this face (i.e. a vertex or a edge in a sketch) - TopoDS_Shape origin = base->findOriginOf(sub); - - // Validate face - // TODO: This would also exclude faces that are valid but not cut by the line - // So for now we trust to the intelligence of the user when picking the face - /*std::vector cfaces = findAllFacesCutBy(upToFace, origFace, dir); - if (cfaces.empty()) - return new App::DocumentObjectExecReturn("No faces found in this direction");*/ + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.Move(invObjLoc); } + getUpToFace(upToFace, support, supportface, sketchshape, method, dir); + + // Special treatment because often the created stand-alone prism is invalid (empty) because + // BRepFeat_MakePrism(..., 2, 1) is buggy + BRepFeat_MakePrism PrismMaker; + PrismMaker.Init(support, sketchshape, supportface, dir, 0, 1); + PrismMaker.Perform(upToFace); - // Create semi-infinite prism from sketch in direction dir - dir.Transform(invObjLoc.Transformation()); - BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),dir,0,0,1); if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Could not extrude the sketch!"); + return new App::DocumentObjectExecReturn("Pocket: Up to face: Could not extrude the sketch!"); + TopoDS_Shape prism = PrismMaker.Shape(); - // Cut off the prism at the face we found - // Grab any point from the sketch - TopExp_Explorer exp; - exp.Init(aFace, TopAbs_VERTEX); - if (!exp.More()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Sketch without points?"); - gp_Pnt aPnt = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current())); - - // Create a halfspace from the face, extending in direction of sketch plane - BRepPrimAPI_MakeHalfSpace mkHalfSpace(upToFace, aPnt); - if (!mkHalfSpace.IsDone()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: HalfSpace creation failed"); - - // Find common material between halfspace and prism - BRepAlgoAPI_Common mkCommon(PrismMaker.Shape(), mkHalfSpace.Solid().Moved(invObjLoc)); - if (!mkCommon.IsDone()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Common creation failed"); - - prism = this->getSolid(mkCommon.Shape()); - if (prism.IsNull()) - return new App::DocumentObjectExecReturn("Cannot extrude up to face: Resulting shape is not a solid"); - } else if (std::string(Type.getValueAsString()) == "ThroughAll") { - gp_Dir dir(SketchVector.x,SketchVector.y,SketchVector.z); - dir.Transform(invObjLoc.Transformation()); - BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),dir,1,0,1); // infinite prism (in both directions!) - if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Could not extrude the sketch!"); - prism = PrismMaker.Shape(); - } else if (std::string(Type.getValueAsString()) == "Length") { - gp_Vec vec(SketchVector.x,SketchVector.y,SketchVector.z); - vec.Transform(invObjLoc.Transformation()); - BRepPrimAPI_MakePrism PrismMaker(aFace.Moved(invObjLoc),vec,0,1); // finite prism - if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Could not extrude the sketch!"); - prism = PrismMaker.Shape(); + // And the really expensive way to get the SubShape... + BRepAlgoAPI_Cut mkCut(support, prism); + if (!mkCut.IsDone()) + return new App::DocumentObjectExecReturn("Pocket: Up to face: Could not get SubShape!"); + // FIXME: In some cases this affects the Shape property: It is set to the same shape as the SubShape!!!! + this->SubShape.setValue(mkCut.Shape()); + this->Shape.setValue(prism); } else { - return new App::DocumentObjectExecReturn("Internal error: Unknown type for Pocket feature"); + TopoDS_Shape prism; + generatePrism(prism, sketchshape, method, dir, L, 0.0, + Midplane.getValue(), Reversed.getValue()); + if (prism.IsNull()) + return new App::DocumentObjectExecReturn("Pocket: Resulting shape is empty"); + + // set the subtractive shape property for later usage in e.g. pattern + this->SubShape.setValue(prism); + + // Cut the SubShape out of the support + BRepAlgoAPI_Cut mkCut(support, prism); + if (!mkCut.IsDone()) + return new App::DocumentObjectExecReturn("Pocket: Cut out of support failed"); + TopoDS_Shape result = mkCut.Shape(); + // we have to get the solids (fuse sometimes creates compounds) + TopoDS_Shape solRes = this->getSolid(result); + if (solRes.IsNull()) + return new App::DocumentObjectExecReturn("Pocket: Resulting shape is not a solid"); + this->Shape.setValue(solRes); } - this->SubShape.setValue(prism); - - // Cut out the pocket - BRepAlgoAPI_Cut mkCut(support.Moved(invObjLoc), prism); - - // Let's check if the fusion has been successful - if (!mkCut.IsDone()) - return new App::DocumentObjectExecReturn("Cut with support failed"); - - // we have to get the solids (fuse sometimes creates compounds) - TopoDS_Shape solRes = this->getSolid(mkCut.Shape()); - if (solRes.IsNull()) - return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); - - this->Shape.setValue(solRes); - return App::DocumentObject::StdReturn; - } catch (Standard_Failure) { + } + catch (Standard_Failure) { Handle_Standard_Failure e = Standard_Failure::Caught(); - if (std::string(e->GetMessageString()) == "TopoDS::Face") + if (std::string(e->GetMessageString()) == "TopoDS::Face" && + (Type.getValueAsString() == "UpToFirst" || Type.getValueAsString() == "UpToFace")) return new App::DocumentObjectExecReturn("Could not create face from sketch.\n" - "Intersecting sketch entities or multiple faces in a sketch are not allowed."); + "Intersecting sketch entities or multiple faces in a sketch are not allowed " + "for making a pocket up to a face."); else return new App::DocumentObjectExecReturn(e->GetMessageString()); } diff --git a/src/Mod/PartDesign/App/FeaturePocket.h b/src/Mod/PartDesign/App/FeaturePocket.h index f3c65bf12..1b15957ee 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.h +++ b/src/Mod/PartDesign/App/FeaturePocket.h @@ -39,7 +39,7 @@ public: App::PropertyEnumeration Type; App::PropertyLength Length; - App::PropertyString FaceName; + App::PropertyLinkSub UpToFace; /** @name methods override feature */ //@{ diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 25b978685..c09b1425b 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -31,6 +31,9 @@ # include # include # include +# include +# include +# include # include # include # include @@ -136,13 +139,50 @@ std::vector SketchBased::getSketchWires() const { return result; } -const TopoDS_Shape& SketchBased::getSupportShape() const { +// TODO: This code is taken from and duplicates code in Part2DObject::positionBySupport() +// Note: We cannot return a reference, because it will become Null. +// Not clear where, because we check for IsNull() here, but as soon as it is passed out of +// this method, it becomes null! +const TopoDS_Face SketchBased::getSupportFace() const { + const App::PropertyLinkSub& Support = static_cast(Sketch.getValue())->Support; + Part::Feature *part = static_cast(Support.getValue()); + if (!part || !part->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) + throw Base::Exception("Sketch has no support shape"); + + const std::vector &sub = Support.getSubValues(); + assert(sub.size()==1); + // get the selected sub shape (a Face) + const Part::TopoShape &shape = part->Shape.getShape(); + if (shape._Shape.IsNull()) + throw Base::Exception("Sketch support shape is empty!"); + + TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); + if (sh.IsNull()) + throw Base::Exception("Null shape in SketchBased::getSupportFace()!"); + + const TopoDS_Face face = TopoDS::Face(sh); + if (face.IsNull()) + throw Base::Exception("Null face in SketchBased::getSupportFace()!"); + + BRepAdaptor_Surface adapt(face); + if (adapt.GetType() != GeomAbs_Plane) + throw Base::Exception("No planar face in SketchBased::getSupportFace()!"); + + return face; +} + +Part::Feature* SketchBased::getSupport() const { // get the support of the Sketch if any App::DocumentObject* SupportLink = static_cast(Sketch.getValue())->Support.getValue(); Part::Feature* SupportObject = NULL; if (SupportLink && SupportLink->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) SupportObject = static_cast(SupportLink); + return SupportObject; +} + +const TopoDS_Shape& SketchBased::getSupportShape() const { + Part::Feature* SupportObject = getSupport(); if (SupportObject == NULL) throw Base::Exception("No support in Sketch!"); @@ -156,6 +196,12 @@ const TopoDS_Shape& SketchBased::getSupportShape() const { return result; } +int SketchBased::getSketchAxisCount(void) const +{ + Part::Part2DObject *sketch = static_cast(Sketch.getValue()); + return sketch->getAxisCount(); +} + void SketchBased::onChanged(const App::Property* prop) { if (prop == &Sketch) { @@ -332,10 +378,145 @@ TopoDS_Shape SketchBased::makeFace(const std::vector& w) const } } -int SketchBased::getSketchAxisCount(void) const +void SketchBased::getUpToFaceFromLinkSub(TopoDS_Face& upToFace, + const App::PropertyLinkSub& refFace) { - Part::Part2DObject *sketch = static_cast(Sketch.getValue()); - return sketch->getAxisCount(); + App::DocumentObject* ref = refFace.getValue(); + std::vector subStrings = refFace.getSubValues(); + + if (ref == NULL) + throw Base::Exception("SketchBased: Up to face: No face selected"); + if (!ref->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) + throw Base::Exception("SketchBased: Up to face: Must be face of a feature"); + Part::TopoShape baseShape = static_cast(ref)->Shape.getShape(); + + if (subStrings.empty() || subStrings[0].empty()) + throw Base::Exception("SketchBased: Up to face: No face selected"); + // TODO: Check for multiple UpToFaces? + + upToFace = TopoDS::Face(baseShape.getSubShape(subStrings[0].c_str())); + if (upToFace.IsNull()) + throw Base::Exception("SketchBased: Up to face: Failed to extract face"); +} + +void SketchBased::getUpToFace(TopoDS_Face& upToFace, + const TopoDS_Shape& support, + const TopoDS_Face& supportface, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& dir) +{ + if ((method == "UpToLast") || (method == "UpToFirst")) { + // Check for valid support object + if (support.IsNull()) + throw Base::Exception("SketchBased: Up to face: No support in Sketch!"); + + std::vector cfaces = Part::findAllFacesCutBy(support, sketchshape, dir); + if (cfaces.empty()) + throw Base::Exception("SketchBased: Up to face: No faces found in this direction"); + + // Find nearest/furthest face + std::vector::const_iterator it, it_near, it_far; + it_near = it_far = cfaces.begin(); + for (it = cfaces.begin(); it != cfaces.end(); it++) + if (it->distsq > it_far->distsq) + it_far = it; + else if (it->distsq < it_near->distsq) + it_near = it; + upToFace = (method == "UpToLast" ? it_far->face : it_near->face); + } + + // Remove the limits of the upToFace so that the extrusion works even if sketchshape is larger + // than the upToFace + bool remove_limits = false; + TopExp_Explorer Ex; + for (Ex.Init(sketchshape,TopAbs_FACE); Ex.More(); Ex.Next()) { + // Get outermost wire of sketch face + TopoDS_Face sketchface = TopoDS::Face(Ex.Current()); + TopoDS_Wire outerWire = ShapeAnalysis::OuterWire(sketchface); + if (!checkWireInsideFace(outerWire, upToFace, dir)) { + remove_limits = true; + break; + } + } + + if (remove_limits) { + // Note: Using an unlimited face every time gives unnecessary failures for concave faces + BRepAdaptor_Surface adapt(upToFace, Standard_False); + BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface()); + if (!mkFace.IsDone()) + throw Base::Exception("SketchBased: Up To Face: Failed to create unlimited face"); + upToFace = TopoDS::Face(mkFace.Shape()); + } + + // Check that the upToFace does not intersect the sketch face and + // is not parallel to the extrusion direction + BRepAdaptor_Surface adapt1(TopoDS::Face(supportface)); + BRepAdaptor_Surface adapt2(TopoDS::Face(upToFace)); + + if (adapt2.GetType() == GeomAbs_Plane) { + if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) + throw Base::Exception("SketchBased: Up to face: Must not be parallel to extrusion direction!"); + } + + BRepExtrema_DistShapeShape distSS(supportface, upToFace); + if (distSS.Value() < Precision::Confusion()) + throw Base::Exception("SketchBased: Up to face: Must not intersect sketch!"); + +} + +void SketchBased::generatePrism(TopoDS_Shape& prism, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& dir, + const double L, + const double L2, + const bool midplane, + const bool reversed) +{ + if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { + double Ltotal = L; + double Loffset = 0.; + if (method == "ThroughAll") + // "ThroughAll" is modelled as a very long, but finite prism to avoid problems with pockets + // Note: 1E6 created problems once... + Ltotal = 1E4; + + if (midplane) + Loffset = -Ltotal/2; + else if (method == "TwoLengths") { + Loffset = -L2; + Ltotal += L2; + } + + TopoDS_Shape from = sketchshape; + if (method == "TwoLengths" || midplane) { + gp_Trsf mov; + mov.SetTranslation(Loffset * gp_Vec(dir)); + TopLoc_Location loc(mov); + from = sketchshape.Moved(loc); + } else if (reversed) + Ltotal *= -1.0; + + // Its better not to use BRepFeat_MakePrism here even if we have a support because the + // resulting shape creates problems with Pocket + BRepPrimAPI_MakePrism PrismMaker(from, Ltotal*gp_Vec(dir), 0,1); // finite prism + if (!PrismMaker.IsDone()) + throw Base::Exception("SketchBased: Length: Could not extrude the sketch!"); + prism = PrismMaker.Shape(); + } else { + throw Base::Exception("SketchBased: Internal error: Unknown method for generatePrism()"); + } + +} + +const bool SketchBased::checkWireInsideFace(const TopoDS_Wire& wire, const TopoDS_Face& face, + const gp_Dir& dir) { + // Project wire onto the face (face, not surface! So limits of face apply) + // FIXME: For a user-selected upToFace, sometimes this returns a non-closed wire for no apparent reason + // Check again after introduction of "robust" reference for upToFace + BRepProj_Projection proj(wire, face, dir); + return (proj.More() && proj.Current().Closed()); } } diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index c228000da..726461ec0 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -30,6 +30,7 @@ class TopoDS_Face; class TopoDS_Wire; +class gp_Dir; namespace PartDesign { @@ -59,8 +60,13 @@ public: Part::Part2DObject* getVerifiedSketch() const; /// Returns the wires the sketch is composed of std::vector getSketchWires() const; + /// Returns the face of the sketch support (if any) + const TopoDS_Face getSupportFace() const; + /// Returns the sketch support feature or NULL + Part::Feature* getSupport() const; /// Returns the sketch support shape (if any) const TopoDS_Shape& getSupportShape() const; + /// retrieves the number of axes in the linked sketch (defined as construction lines) int getSketchAxisCount(void) const; @@ -70,6 +76,35 @@ protected: TopoDS_Shape makeFace(const std::vector&) const; TopoDS_Shape makeFace(std::list&) const; // for internal use only bool isInside(const TopoDS_Wire&, const TopoDS_Wire&) const; + + /// Extract a face from a given LinkSub + static void getUpToFaceFromLinkSub(TopoDS_Face& upToFace, + const App::PropertyLinkSub& refFace); + + /// Find a valid face to extrude up to + static void getUpToFace(TopoDS_Face& upToFace, + const TopoDS_Shape& support, + const TopoDS_Face& supportface, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& dir); + /** + * Generate a linear prism + * It will be a stand-alone solid created with BRepPrimAPI_MakePrism + */ + static void generatePrism(TopoDS_Shape& prism, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& direction, + const double L, + const double L2, + const bool midplane, + const bool reversed); + + /// Check whether the wire after projection on the face is inside the face + static const bool checkWireInsideFace(const TopoDS_Wire& wire, + const TopoDS_Face& face, + const gp_Dir& dir); }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index 5a075d344..13a3b36cc 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -25,9 +25,10 @@ #ifndef _PreComp_ # include -# include # include # include +# include +# include #endif #include "ui_TaskPadParameters.h" @@ -44,7 +45,7 @@ #include #include #include - +#include "ReferenceSelection.h" using namespace PartDesignGui; using namespace Gui; @@ -70,6 +71,8 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView,QWidget *parent) this, SLOT(onLength2Changed(double))); connect(ui->changeMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onModeChanged(int))); + connect(ui->buttonFace, SIGNAL(pressed()), + this, SLOT(onButtonFace())); connect(ui->lineFaceName, SIGNAL(textEdited(QString)), this, SLOT(onFaceName(QString))); connect(ui->checkBoxUpdateView, SIGNAL(toggled(bool)), @@ -77,6 +80,15 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView,QWidget *parent) this->groupLayout()->addWidget(proxy); + // Temporarily prevent unnecessary feature recomputes + ui->doubleSpinBox->blockSignals(true); + ui->doubleSpinBox2->blockSignals(true); + ui->checkBoxMidplane->blockSignals(true); + ui->checkBoxReversed->blockSignals(true); + ui->buttonFace->blockSignals(true); + ui->lineFaceName->blockSignals(true); + ui->changeMode->blockSignals(true); + // Get the feature data PartDesign::Pad* pcPad = static_cast(PadView->getObject()); double l = pcPad->Length.getValue(); @@ -84,7 +96,14 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView,QWidget *parent) bool reversed = pcPad->Reversed.getValue(); double l2 = pcPad->Length2.getValue(); int index = pcPad->Type.getValue(); // must extract value here, clear() kills it! - const char* upToFace = pcPad->FaceName.getValue(); + std::vector subStrings = pcPad->UpToFace.getSubValues(); + std::string upToFace; + int faceId = -1; + if (!subStrings.empty()) { + upToFace = subStrings.front(); + if (upToFace.substr(0,4) == "Face") + faceId = std::atoi(&upToFace[4]); + } // Fill data into dialog elements ui->doubleSpinBox->setMinimum(0); @@ -97,8 +116,10 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView,QWidget *parent) // According to bug #0000521 the reversed option // shouldn't be de-activated if the pad has a support face ui->checkBoxReversed->setChecked(reversed); - ui->lineFaceName->setText(pcPad->FaceName.isEmpty() ? tr("No face selected") : tr(upToFace)); - ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace)); + ui->lineFaceName->setText(faceId >= 0 ? + tr("Face") + QString::number(faceId) : + tr("No face selected")); + ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); ui->changeMode->clear(); ui->changeMode->insertItem(0, tr("Dimension")); ui->changeMode->insertItem(1, tr("To last")); @@ -108,6 +129,13 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView,QWidget *parent) ui->changeMode->setCurrentIndex(index); // activate and de-activate dialog elements as appropriate + ui->doubleSpinBox->blockSignals(false); + ui->doubleSpinBox2->blockSignals(false); + ui->checkBoxMidplane->blockSignals(false); + ui->checkBoxReversed->blockSignals(false); + ui->buttonFace->blockSignals(false); + ui->lineFaceName->blockSignals(false); + ui->changeMode->blockSignals(false); updateUI(index); } @@ -121,22 +149,31 @@ void TaskPadParameters::updateUI(int index) // yet visible. QMetaObject::invokeMethod(ui->doubleSpinBox, "setFocus", Qt::QueuedConnection); ui->checkBoxMidplane->setEnabled(true); - ui->checkBoxReversed->setEnabled(true); + // Reverse only makes sense if Midplane is not true + ui->checkBoxReversed->setEnabled(!ui->checkBoxMidplane->isChecked()); ui->doubleSpinBox2->setEnabled(false); + ui->buttonFace->setEnabled(false); ui->lineFaceName->setEnabled(false); - } else if ((index == 1) || (index == 2)) { // up to first/last + onButtonFace(false); + } else if (index == 1 || index == 2) { // up to first/last ui->doubleSpinBox->setEnabled(false); ui->checkBoxMidplane->setEnabled(false); ui->checkBoxReversed->setEnabled(false); ui->doubleSpinBox2->setEnabled(false); + ui->buttonFace->setEnabled(false); ui->lineFaceName->setEnabled(false); + onButtonFace(false); } else if (index == 3) { // up to face ui->doubleSpinBox->setEnabled(false); ui->checkBoxMidplane->setEnabled(false); ui->checkBoxReversed->setEnabled(false); ui->doubleSpinBox2->setEnabled(false); + ui->buttonFace->setEnabled(true); ui->lineFaceName->setEnabled(true); QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); + // Go into reference selection mode if no face has been selected yet + if (ui->lineFaceName->text().isEmpty()) + onButtonFace(true); } else { // two dimensions ui->doubleSpinBox->setEnabled(true); ui->doubleSpinBox->selectAll(); @@ -144,70 +181,88 @@ void TaskPadParameters::updateUI(int index) ui->checkBoxMidplane->setEnabled(false); ui->checkBoxReversed->setEnabled(false); ui->doubleSpinBox2->setEnabled(true); + ui->buttonFace->setEnabled(false); ui->lineFaceName->setEnabled(false); + onButtonFace(false); } } void TaskPadParameters::onSelectionChanged(const Gui::SelectionChanges& msg) { if (msg.Type == Gui::SelectionChanges::AddSelection) { - PartDesign::Pad* pcPad = static_cast(PadView->getObject()); - if (pcPad->Type.getValue() != 3) // ignore user selections if mode is not upToFace + // Don't allow selection in other document + if (strcmp(msg.pDocName, PadView->getObject()->getDocument()->getName()) != 0) return; if (!msg.pSubName || msg.pSubName[0] == '\0') return; - std::string element(msg.pSubName); - if (element.substr(0,4) != "Face") + std::string subName(msg.pSubName); + if (subName.substr(0,4) != "Face") return; + int faceId = std::atoi(&subName[4]); - int index=std::atoi(&element[4]); - pcPad->FaceName.setValue(element); - pcPad->getDocument()->recomputeFeature(pcPad); - ui->lineFaceName->setText(tr("Face") + QString::number(index)); - ui->lineFaceName->setProperty("FaceName", QByteArray(element.c_str())); + // Don't allow selection outside support + PartDesign::Pad* pcPad = static_cast(PadView->getObject()); + Part::Feature* support = pcPad->getSupport(); + if (support == NULL) { + // There is no support, so we can't select from it... + // Turn off reference selection mode + onButtonFace(false); + return; + } + if (strcmp(msg.pObjectName, support->getNameInDocument()) != 0) + return; + + std::vector upToFaces(1,subName); + pcPad->UpToFace.setValue(support, upToFaces); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); + ui->lineFaceName->blockSignals(true); + ui->lineFaceName->setText(tr("Face") + QString::number(faceId)); + ui->lineFaceName->setProperty("FaceName", QByteArray(subName.c_str())); + ui->lineFaceName->blockSignals(false); + // Turn off reference selection mode + onButtonFace(false); } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + ui->lineFaceName->blockSignals(true); ui->lineFaceName->setText(tr("No face selected")); ui->lineFaceName->setProperty("FaceName", QByteArray()); + ui->lineFaceName->blockSignals(false); } } -void TaskPadParameters::onUpdateView(bool on) -{ - ui->changeMode->blockSignals(!on); - ui->doubleSpinBox->blockSignals(!on); - ui->checkBoxMidplane->blockSignals(!on); - ui->checkBoxReversed->blockSignals(!on); - ui->doubleSpinBox2->blockSignals(!on); -} - void TaskPadParameters::onLengthChanged(double len) { PartDesign::Pad* pcPad = static_cast(PadView->getObject()); pcPad->Length.setValue((float)len); - pcPad->getDocument()->recomputeFeature(pcPad); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); } void TaskPadParameters::onMidplane(bool on) { PartDesign::Pad* pcPad = static_cast(PadView->getObject()); pcPad->Midplane.setValue(on); - pcPad->getDocument()->recomputeFeature(pcPad); + ui->checkBoxReversed->setEnabled(!on); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); } void TaskPadParameters::onReversed(bool on) { PartDesign::Pad* pcPad = static_cast(PadView->getObject()); pcPad->Reversed.setValue(on); - pcPad->getDocument()->recomputeFeature(pcPad); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); } void TaskPadParameters::onLength2Changed(double len) { PartDesign::Pad* pcPad = static_cast(PadView->getObject()); pcPad->Length2.setValue((float)len); - pcPad->getDocument()->recomputeFeature(pcPad); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); } void TaskPadParameters::onModeChanged(int index) @@ -215,7 +270,12 @@ void TaskPadParameters::onModeChanged(int index) PartDesign::Pad* pcPad = static_cast(PadView->getObject()); switch (index) { - case 0: pcPad->Type.setValue("Length"); break; + case 0: + pcPad->Type.setValue("Length"); + // Avoid error message + if (ui->doubleSpinBox->value() < Precision::Confusion()) + ui->doubleSpinBox->setValue(5.0); + break; case 1: pcPad->Type.setValue("UpToLast"); break; case 2: pcPad->Type.setValue("UpToFirst"); break; case 3: pcPad->Type.setValue("UpToFace"); break; @@ -224,7 +284,38 @@ void TaskPadParameters::onModeChanged(int index) updateUI(index); - pcPad->getDocument()->recomputeFeature(pcPad); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); +} + +void TaskPadParameters::onButtonFace(const bool pressed) { + PartDesign::Pad* pcPad = static_cast(PadView->getObject()); + Part::Feature* support = pcPad->getSupport(); + if (support == NULL) { + // There is no support, so we can't select from it... + return; + } + + if (pressed) { + Gui::Document* doc = Gui::Application::Instance->activeDocument(); + if (doc) { + doc->setHide(PadView->getObject()->getNameInDocument()); + doc->setShow(support->getNameInDocument()); + } + Gui::Selection().clearSelection(); + Gui::Selection().addSelectionGate + (new ReferenceSelection(support, false, true, false)); + } else { + Gui::Selection().rmvSelectionGate(); + Gui::Document* doc = Gui::Application::Instance->activeDocument(); + if (doc) { + doc->setShow(PadView->getObject()->getNameInDocument()); + doc->setHide(support->getNameInDocument()); + } + } + + // Update button if onButtonFace() is called explicitly + ui->buttonFace->setChecked(pressed); } void TaskPadParameters::onFaceName(const QString& text) @@ -239,14 +330,29 @@ void TaskPadParameters::onFaceName(const QString& text) return; } - int index = rx.cap(1).toInt(); + int faceId = rx.cap(1).toInt(); std::stringstream ss; - ss << "Face" << index; + ss << "Face" << faceId; ui->lineFaceName->setProperty("FaceName", QByteArray(ss.str().c_str())); PartDesign::Pad* pcPad = static_cast(PadView->getObject()); - pcPad->FaceName.setValue(ss.str().c_str()); - pcPad->getDocument()->recomputeFeature(pcPad); + Part::Feature* support = pcPad->getSupport(); + if (support == NULL) { + // There is no support, so we can't select from it... + return; + } + std::vector upToFaces(1,ss.str()); + pcPad->UpToFace.setValue(support, upToFaces); + if (updateView()) + pcPad->getDocument()->recomputeFeature(pcPad); +} + +void TaskPadParameters::onUpdateView(bool on) +{ + if (on) { + PartDesign::Pad* pcPad = static_cast(PadView->getObject()); + pcPad->getDocument()->recomputeFeature(pcPad); + } } double TaskPadParameters::getLength(void) const @@ -279,6 +385,11 @@ QByteArray TaskPadParameters::getFaceName(void) const return ui->lineFaceName->property("FaceName").toByteArray(); } +const bool TaskPadParameters::updateView() const +{ + return ui->checkBoxUpdateView->isChecked(); +} + TaskPadParameters::~TaskPadParameters() { delete ui; @@ -335,7 +446,17 @@ bool TaskDlgPadParameters::accept() Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Midplane = %i",name.c_str(),parameter->getMidplane()?1:0); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Length2 = %f",name.c_str(),parameter->getLength2()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Type = %u",name.c_str(),parameter->getMode()); - Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.FaceName = \"%s\"",name.c_str(),parameter->getFaceName().data()); + const char* facename = parameter->getFaceName().data(); + PartDesign::Pad* pcPad = static_cast(PadView->getObject()); + Part::Feature* support = pcPad->getSupport(); + + if (support != NULL && facename && facename[0] != '\0') { + QString buf = QString::fromUtf8("(App.ActiveDocument.%1,[\"%2\"])"); + buf = buf.arg(QString::fromUtf8(support->getNameInDocument())); + buf = buf.arg(QString::fromUtf8(facename)); + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.UpToFace = %s", name.c_str(), buf.toStdString().c_str()); + } else + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.UpToFace = None", name.c_str()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.recompute()"); if (!PadView->getObject()->isValid()) throw Base::Exception(PadView->getObject()->getStatusString()); @@ -361,7 +482,7 @@ bool TaskDlgPadParameters::reject() pcSupport = pcSketch->Support.getValue(); } - // role back the done things + // roll back the done things Gui::Command::abortCommand(); Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()"); diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.h b/src/Mod/PartDesign/Gui/TaskPadParameters.h index 672f074a3..e17a30527 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.h @@ -58,6 +58,7 @@ public: bool getReversed(void) const; bool getMidplane(void) const; QByteArray getFaceName(void) const; + const bool updateView() const; private Q_SLOTS: void onLengthChanged(double); @@ -65,6 +66,7 @@ private Q_SLOTS: void onReversed(bool); void onLength2Changed(double); void onModeChanged(int); + void onButtonFace(const bool pressed = true); void onFaceName(const QString& text); void onUpdateView(bool); diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.ui b/src/Mod/PartDesign/Gui/TaskPadParameters.ui index 277adba2d..69e85d4f8 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.ui @@ -7,7 +7,7 @@ 0 0 272 - 238 + 271 @@ -46,7 +46,7 @@ - -999999999.000000000000000 + 0.000000000000000 999999999.000000000000000 @@ -90,7 +90,7 @@ - -999999999.000000000000000 + 0.000000000000000 999999999.000000000000000 @@ -108,7 +108,7 @@ - + Face @@ -119,6 +119,13 @@ + + + + Qt::Horizontal + + + diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp index 8f4da6f4c..6fe3e2c6e 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp @@ -27,6 +27,7 @@ # include # include # include +# include #endif #include "ui_TaskPocketParameters.h" @@ -43,7 +44,7 @@ #include #include #include - +#include "ReferenceSelection.h" using namespace PartDesignGui; using namespace Gui; @@ -61,40 +62,63 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge connect(ui->doubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onLengthChanged(double))); + connect(ui->checkBoxMidplane, SIGNAL(toggled(bool)), + this, SLOT(onMidplaneChanged(bool))); connect(ui->changeMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onModeChanged(int))); + connect(ui->buttonFace, SIGNAL(pressed()), + this, SLOT(onButtonFace())); connect(ui->lineFaceName, SIGNAL(textEdited(QString)), this, SLOT(onFaceName(QString))); + connect(ui->checkBoxUpdateView, SIGNAL(toggled(bool)), + this, SLOT(onUpdateView(bool))); this->groupLayout()->addWidget(proxy); + // Temporarily prevent unnecessary feature recomputes + ui->doubleSpinBox->blockSignals(true); + ui->checkBoxMidplane->blockSignals(true); + ui->buttonFace->blockSignals(true); + ui->lineFaceName->blockSignals(true); + ui->changeMode->blockSignals(true); + + // Get the feature data PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); double l = pcPocket->Length.getValue(); + bool midplane = pcPocket->Midplane.getValue(); int index = pcPocket->Type.getValue(); // must extract value here, clear() kills it! - const char* upToFace = pcPocket->FaceName.getValue(); + std::vector subStrings = pcPocket->UpToFace.getSubValues(); + std::string upToFace; + int faceId = -1; + if (!subStrings.empty()) { + upToFace = subStrings.front(); + if (upToFace.substr(0,4) == "Face") + faceId = std::atoi(&upToFace[4]); + } + // Fill data into dialog elements + ui->doubleSpinBox->setMinimum(0); + ui->doubleSpinBox->setMaximum(INT_MAX); + ui->doubleSpinBox->setValue(l); + ui->checkBoxMidplane->setChecked(midplane); + ui->lineFaceName->setText(faceId >= 0 ? + tr("Face") + QString::number(faceId) : + tr("No face selected")); + ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); ui->changeMode->clear(); ui->changeMode->insertItem(0, tr("Dimension")); - ui->changeMode->insertItem(1, tr("To last")); + ui->changeMode->insertItem(1, tr("Through all")); ui->changeMode->insertItem(2, tr("To first")); - ui->changeMode->insertItem(3, tr("Through all")); - ui->changeMode->insertItem(4, tr("Up to face")); + ui->changeMode->insertItem(3, tr("Up to face")); ui->changeMode->setCurrentIndex(index); + ui->checkBoxMidplane->setChecked(midplane); - if (index == 0) { // Only this option requires a numeric value - ui->doubleSpinBox->setMaximum(INT_MAX); - ui->doubleSpinBox->setValue(l); - ui->doubleSpinBox->selectAll(); - QMetaObject::invokeMethod(ui->doubleSpinBox, "setFocus", Qt::QueuedConnection); - ui->lineFaceName->setEnabled(false); - } else if (index == 4) { // Only this option requires to select a face - ui->doubleSpinBox->setEnabled(false); - ui->lineFaceName->setText(pcPocket->FaceName.isEmpty() ? tr("No face selected") : tr(upToFace)); - ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace)); - } else { // Neither value nor face required - ui->doubleSpinBox->setEnabled(false); - ui->lineFaceName->setEnabled(false); - } + ui->doubleSpinBox->blockSignals(false); + ui->checkBoxMidplane->blockSignals(false); + ui->buttonFace->blockSignals(false); + ui->lineFaceName->blockSignals(false); + ui->changeMode->blockSignals(false); + updateUI(index); //// check if the sketch has support //Sketcher::SketchObject *pcSketch; @@ -108,28 +132,82 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge //} } +void TaskPocketParameters::updateUI(int index) +{ + if (index == 0) { // Only this option requires a numeric value + ui->doubleSpinBox->setEnabled(true); + ui->doubleSpinBox->selectAll(); + QMetaObject::invokeMethod(ui->doubleSpinBox, "setFocus", Qt::QueuedConnection); + ui->checkBoxMidplane->setEnabled(true); + ui->buttonFace->setEnabled(false); + ui->lineFaceName->setEnabled(false); + onButtonFace(false); + } else if (index == 1) { + ui->checkBoxMidplane->setEnabled(true); + ui->doubleSpinBox->setEnabled(false); + ui->buttonFace->setEnabled(false); + ui->lineFaceName->setEnabled(false); + onButtonFace(false); + } else if (index == 2) { // Neither value nor face required + ui->doubleSpinBox->setEnabled(false); + ui->checkBoxMidplane->setEnabled(false); + ui->buttonFace->setEnabled(false); + ui->lineFaceName->setEnabled(false); + onButtonFace(false); + } else if (index == 3) { // Only this option requires to select a face + ui->doubleSpinBox->setEnabled(false); + ui->checkBoxMidplane->setEnabled(false); + ui->buttonFace->setEnabled(true); + ui->lineFaceName->setEnabled(true); + QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); + // Go into reference selection mode if no face has been selected yet + if (ui->lineFaceName->text().isEmpty()) + onButtonFace(true); + } +} + void TaskPocketParameters::onSelectionChanged(const Gui::SelectionChanges& msg) { if (msg.Type == Gui::SelectionChanges::AddSelection) { - PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); - if (pcPocket->Type.getValue() != 4) // ignore user selections if mode is not upToFace + // Don't allow selection in other document + if (strcmp(msg.pDocName, PocketView->getObject()->getDocument()->getName()) != 0) return; if (!msg.pSubName || msg.pSubName[0] == '\0') return; - std::string element(msg.pSubName); - if (element.substr(0,4) != "Face") + std::string subName(msg.pSubName); + if (subName.substr(0,4) != "Face") return; + int faceId = std::atoi(&subName[4]); - int index=std::atoi(&element[4]); - pcPocket->FaceName.setValue(element); - pcPocket->getDocument()->recomputeFeature(pcPocket); - ui->lineFaceName->setText(tr("Face") + QString::number(index)); - ui->lineFaceName->setProperty("FaceName", QByteArray(element.c_str())); + // Don't allow selection outside of support + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + Part::Feature* support = pcPocket->getSupport(); + if (support == NULL) { + // There is no support, so we can't select from it... + // Turn off reference selection mode + onButtonFace(false); + return; + } + if (strcmp(msg.pObjectName, support->getNameInDocument()) != 0) + return; + + std::vector upToFaces(1,subName); + pcPocket->UpToFace.setValue(support, upToFaces); + if (updateView()) + pcPocket->getDocument()->recomputeFeature(pcPocket); + ui->lineFaceName->blockSignals(true); + ui->lineFaceName->setText(tr("Face") + QString::number(faceId)); + ui->lineFaceName->setProperty("FaceName", QByteArray(subName.c_str())); + ui->lineFaceName->blockSignals(false); + // Turn off reference selection mode + onButtonFace(false); } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + ui->lineFaceName->blockSignals(true); ui->lineFaceName->setText(tr("No face selected")); ui->lineFaceName->setProperty("FaceName", QByteArray()); + ui->lineFaceName->blockSignals(false); } } @@ -137,7 +215,16 @@ void TaskPocketParameters::onLengthChanged(double len) { PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); pcPocket->Length.setValue((float)len); - pcPocket->getDocument()->recomputeFeature(pcPocket); + if (updateView()) + pcPocket->getDocument()->recomputeFeature(pcPocket); +} + +void TaskPocketParameters::onMidplaneChanged(bool on) +{ + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + pcPocket->Midplane.setValue(on); + if (updateView()) + pcPocket->getDocument()->recomputeFeature(pcPocket); } void TaskPocketParameters::onModeChanged(int index) @@ -145,28 +232,68 @@ void TaskPocketParameters::onModeChanged(int index) PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); switch (index) { - case 0: pcPocket->Type.setValue("Length"); break; - case 1: pcPocket->Type.setValue("UpToLast"); break; - case 2: pcPocket->Type.setValue("UpToFirst"); break; - case 3: pcPocket->Type.setValue("ThroughAll"); break; - case 4: pcPocket->Type.setValue("UpToFace"); break; - default: pcPocket->Type.setValue("Length"); + case 0: + // Why? See below for "UpToFace" + pcPocket->Type.setValue("Length"); + if (oldLength < Precision::Confusion()) + oldLength = 5.0; + pcPocket->Length.setValue(oldLength); + ui->doubleSpinBox->setValue(oldLength); + break; + case 1: + oldLength = pcPocket->Length.getValue(); + pcPocket->Type.setValue("ThroughAll"); + break; + case 2: + oldLength = pcPocket->Length.getValue(); + pcPocket->Type.setValue("UpToFirst"); + break; + case 3: + // Because of the code at the begining of Pocket::execute() which is used to detect + // broken legacy parts, we must set the length to zero here! + oldLength = pcPocket->Length.getValue(); + pcPocket->Type.setValue("UpToFace"); + pcPocket->Length.setValue(0.0); + ui->doubleSpinBox->setValue(0.0); + break; + default: + pcPocket->Type.setValue("Length"); } - if (index == 0) { - ui->doubleSpinBox->setEnabled(true); - ui->lineFaceName->setEnabled(false); - ui->doubleSpinBox->setValue(pcPocket->Length.getValue()); - } else if (index == 4) { - ui->lineFaceName->setEnabled(true); - ui->doubleSpinBox->setEnabled(false); - ui->lineFaceName->setText(tr(pcPocket->FaceName.getValue())); + updateUI(index); + + if (updateView()) + pcPocket->getDocument()->recomputeFeature(pcPocket); +} + +void TaskPocketParameters::onButtonFace(const bool pressed) { + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + Part::Feature* support = pcPocket->getSupport(); + if (support == NULL) { + // There is no support, so we can't select from it... + return; + } + + if (pressed) { + Gui::Document* doc = Gui::Application::Instance->activeDocument(); + if (doc) { + doc->setHide(PocketView->getObject()->getNameInDocument()); + doc->setShow(support->getNameInDocument()); + } + Gui::Selection().clearSelection(); + Gui::Selection().addSelectionGate + (new ReferenceSelection(support, false, true, false)); } else { - ui->doubleSpinBox->setEnabled(false); - ui->lineFaceName->setEnabled(false); + Gui::Selection().rmvSelectionGate(); + Gui::Document* doc = Gui::Application::Instance->activeDocument(); + if (doc) { + doc->setShow(PocketView->getObject()->getNameInDocument()); + doc->setHide(support->getNameInDocument()); + } } - pcPocket->getDocument()->recomputeFeature(pcPocket); + // Update button if onButtonFace() is called explicitly + ui->buttonFace->setChecked(pressed); } void TaskPocketParameters::onFaceName(const QString& text) @@ -181,14 +308,29 @@ void TaskPocketParameters::onFaceName(const QString& text) return; } - int index = rx.cap(1).toInt(); + int faceId = rx.cap(1).toInt(); std::stringstream ss; - ss << "Face" << index; + ss << "Face" << faceId; ui->lineFaceName->setProperty("FaceName", QByteArray(ss.str().c_str())); PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); - pcPocket->FaceName.setValue(ss.str().c_str()); - pcPocket->getDocument()->recomputeFeature(pcPocket); + Part::Feature* support = pcPocket->getSupport(); + if (support == NULL) { + // There is no support, so we can't select from it... + return; + } + std::vector upToFaces(1,ss.str()); + pcPocket->UpToFace.setValue(support, upToFaces); + if (updateView()) + pcPocket->getDocument()->recomputeFeature(pcPocket); +} + +void TaskPocketParameters::onUpdateView(bool on) +{ + if (on) { + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + pcPocket->getDocument()->recomputeFeature(pcPocket); + } } double TaskPocketParameters::getLength(void) const @@ -206,6 +348,11 @@ QByteArray TaskPocketParameters::getFaceName(void) const return ui->lineFaceName->property("FaceName").toByteArray(); } +const bool TaskPocketParameters::updateView() const +{ + return ui->checkBoxUpdateView->isChecked(); +} + TaskPocketParameters::~TaskPocketParameters() { delete ui; @@ -258,7 +405,16 @@ bool TaskDlgPocketParameters::accept() //Gui::Command::openCommand("Pocket changed"); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Length = %f",name.c_str(),parameter->getLength()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Type = %u",name.c_str(),parameter->getMode()); - Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.FaceName = \"%s\"",name.c_str(),parameter->getFaceName().data()); + const char* facename = parameter->getFaceName().data(); + PartDesign::Pocket* pcPocket = static_cast(PocketView->getObject()); + Part::Feature* support = pcPocket->getSupport(); + if (support != NULL && facename && facename[0] != '\0') { + QString buf = QString::fromUtf8("(App.ActiveDocument.%1,[\"%2\"])"); + buf = buf.arg(QString::fromUtf8(support->getNameInDocument())); + buf = buf.arg(QString::fromUtf8(facename)); + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.UpToFace = %s", name.c_str(), buf.toStdString().c_str()); + } else + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.UpToFace = None", name.c_str()); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.recompute()"); Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()"); Gui::Command::commitCommand(); diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.h b/src/Mod/PartDesign/Gui/TaskPocketParameters.h index d695c7569..dbabca61f 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.h @@ -53,24 +53,31 @@ public: ~TaskPocketParameters(); double getLength(void) const; + bool getMidplane(void) const; int getMode(void) const; QByteArray getFaceName(void) const; + const bool updateView() const; private Q_SLOTS: void onLengthChanged(double); + void onMidplaneChanged(bool); void onModeChanged(int); + void onButtonFace(const bool pressed = true); void onFaceName(const QString& text); + void onUpdateView(bool); protected: void changeEvent(QEvent *e); private: void onSelectionChanged(const Gui::SelectionChanges& msg); + void updateUI(int index); private: QWidget* proxy; Ui_TaskPocketParameters* ui; ViewProviderPocket *PocketView; + double oldLength; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui index 4f313be76..5182479d2 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui @@ -7,7 +7,7 @@ 0 0 241 - 134 + 192 @@ -25,78 +25,87 @@ Form - - - - 10 - 10 - 211 - 34 - - - - - - - Type - - - - - - + + + + + - Dimension + Type - - - - - - - - - 10 - 90 - 211 - 34 - - - - - - - Face - - - - - - - - - - - - 10 - 50 - 211 - 34 - - - - - - - Length - - - - - - - - + + + + + + + Dimension + + + + + + + + + + + + Length + + + + + + + 999999999.000000000000000 + + + + + + + + + true + + + Symmetric to plane + + + + + + + + + Face + + + + + + + + + + + + Qt::Horizontal + + + + + + + Update view + + + true + + + +