From a346c266e7604d68e29da7fd5667ae5671538591 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 25 Aug 2025 19:17:23 +0200 Subject: [PATCH] PartDesign: Extrude 2 sides (#21794) * PartDesign: extrude 2 sides * Part: OpCodes XOR * PartDesign: Remove deprecated generatePrism functions * PartDesign: Extrude : Update Sides combobox strings * Change "Sides" to "Mode" * Use OpCodes::Extrude instead of Prism. --- src/Mod/Part/App/TopoShape.h | 29 + src/Mod/Part/App/TopoShapeExpansion.cpp | 82 ++ src/Mod/Part/App/TopoShapeOpCode.h | 3 +- src/Mod/PartDesign/App/FeatureExtrude.cpp | 782 +++++++-------- src/Mod/PartDesign/App/FeatureExtrude.h | 76 +- src/Mod/PartDesign/App/FeaturePad.cpp | 27 +- src/Mod/PartDesign/App/FeaturePad.h | 2 - src/Mod/PartDesign/App/FeaturePocket.cpp | 27 +- src/Mod/PartDesign/App/FeatureSketchBased.cpp | 11 +- src/Mod/PartDesign/App/FeatureSketchBased.h | 2 + .../PartDesign/Gui/TaskExtrudeParameters.cpp | 889 ++++++++++-------- .../PartDesign/Gui/TaskExtrudeParameters.h | 141 ++- src/Mod/PartDesign/Gui/TaskPadParameters.cpp | 96 +- src/Mod/PartDesign/Gui/TaskPadParameters.h | 7 +- .../PartDesign/Gui/TaskPadPocketParameters.ui | 633 ++++++++----- .../PartDesign/Gui/TaskPocketParameters.cpp | 81 +- src/Mod/PartDesign/Gui/TaskPocketParameters.h | 10 +- .../Gui/TaskRevolutionParameters.cpp | 3 +- .../Gui/TaskSketchBasedParameters.cpp | 4 +- .../Gui/TaskSketchBasedParameters.h | 3 +- src/Mod/PartDesign/PartDesignTests/TestPad.py | 4 +- tests/src/Mod/PartDesign/App/Pad.cpp | 2 +- 22 files changed, 1656 insertions(+), 1258 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index f788c1b532..78b3c2e231 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -1422,6 +1422,35 @@ public: return TopoShape(0, Hasher).makeElementCut({*this, source}, op, tol); } + /** Make a boolean xor of this shape with an input shape + * + * @param source: the source shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for the fusion + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a self reference so that multiple operations can be carried out + * for the same shape in the same line of code. + */ + TopoShape& + makeElementXor(const std::vector& sources, const char* op = nullptr, double tol = -1.0); + /** Make a boolean xor of this shape with an input shape + * + * @param source: the source shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for the fusion + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape + makeElementXor(const TopoShape& source, const char* op = nullptr, double tol = -1.0) const + { + return TopoShape(0, Hasher).makeElementXor({*this, source}, op, tol); + } + /** Try to simplify geometry of any linear/planar subshape to line/plane * * @return Return true if the shape is modified diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index b8215827c3..405b64a97b 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4143,6 +4143,84 @@ TopoShape::makeElementCut(const std::vector& shapes, const char* op, return makeElementBoolean(Part::OpCodes::Cut, shapes, op, tol); } +TopoShape& +TopoShape::makeElementXor(const std::vector& shapes, const char* op, double tol) +{ + if (shapes.empty()) { + FC_THROWM(NullShapeException, "Null shape"); + } + + if (OCCTProgressIndicator::getAppIndicator().UserBreak()) { + FC_THROWM(Base::CADKernelError, "User aborted"); + } + + if (!op) { + op = Part::OpCodes::Xor; + } + + std::vector expandedShapes; + // Same compound expansion as Fuse + for (auto it = shapes.begin(); it != shapes.end(); ++it) { + auto& shape = *it; + if (shape.isNull()) { + FC_THROWM(NullShapeException, "Null input shape for XOR operation"); + } + if (shape.shapeType() == TopAbs_COMPOUND) { + if (expandedShapes.empty()) { + expandedShapes.insert(expandedShapes.end(), shapes.begin(), it); + } + expandCompound(shape, expandedShapes); + } + else if (!expandedShapes.empty()) { + expandedShapes.push_back(shape); + } + } + + const auto& inputs = expandedShapes.empty() ? shapes : expandedShapes; + // Note: The inputs.empty() check is now redundant because of the check at the top, + // but it's harmless to leave it. + if (inputs.empty()) { + FC_THROWM(NullShapeException, "Null shape"); + } + if (inputs.size() == 1) { + *this = inputs[0]; + if (shapes.size() == 1) { + FC_WARN("Boolean operation with only one shape input"); + } + return *this; + } + + TopoShape result = inputs[0]; + for (size_t i = 1; i < inputs.size(); ++i) { + // The final op is only applied on the very last iteration. + const char* currentOp = (i == inputs.size() - 1) ? op : nullptr; + + // Step 1: Union(A, B) - intermediate result, no op code. + TopoShape tempUnion(0, Hasher); + tempUnion.makeElementBoolean(Part::OpCodes::Fuse, {result, inputs[i]}, nullptr, tol); + + // Step 2: Common(A, B) - intermediate result, no op code. + TopoShape tempCommon(0, Hasher); + tempCommon.makeElementBoolean(Part::OpCodes::Common, {result, inputs[i]}, nullptr, tol); + + // Step 3: Compute the final result for this iteration + if (tempCommon.isNull() || tempCommon.getShape().IsNull()) { + // No intersection, XOR is the same as Union. + // We still call the boolean op to get the correct history. + result.makeElementBoolean(Part::OpCodes::Fuse, {result, inputs[i]}, currentOp, tol); + } + else { + // Final result is Cut(Union, Common). + result.makeElementBoolean(Part::OpCodes::Cut, + {tempUnion, tempCommon}, + currentOp, + tol); + } + } + + *this = result; + return *this; +} TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, const TopoShape& source, @@ -5686,6 +5764,10 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker, return *this; } + if (strcmp(maker, Part::OpCodes::Xor) == 0) { + return makeElementXor(shapes, op, tolerance); + } + bool buildShell = true; std::vector _shapes; diff --git a/src/Mod/Part/App/TopoShapeOpCode.h b/src/Mod/Part/App/TopoShapeOpCode.h index 074082bdbb..00f5b9686b 100644 --- a/src/Mod/Part/App/TopoShapeOpCode.h +++ b/src/Mod/Part/App/TopoShapeOpCode.h @@ -48,7 +48,8 @@ public: static constexpr const char *Fuse = "FUS"; static constexpr const char *Cut = "CUT"; static constexpr const char *Common = "CMN"; - static constexpr const char *Section = "SEC"; + static constexpr const char* Section = "SEC"; + static constexpr const char* Xor = "XOR"; static constexpr const char *Compound = "CMP"; static constexpr const char *Compsolid = "CSD"; static constexpr const char *Pipe = "PIP"; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 3cac3bd60f..5257551903 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -30,6 +30,7 @@ # include # include # include +# include # include # include # include @@ -49,6 +50,8 @@ FC_LOG_LEVEL_INIT("PartDesign", true, true) using namespace PartDesign; +const char* FeatureExtrude::SideTypesEnums[] = {"One side", "Two sides", "Symmetric", nullptr}; + PROPERTY_SOURCE(PartDesign::FeatureExtrude, PartDesign::ProfileBased) App::PropertyQuantityConstraint::Constraints FeatureExtrude::signedLengthConstraint = { @@ -61,7 +64,9 @@ FeatureExtrude::FeatureExtrude() = default; short FeatureExtrude::mustExecute() const { if (Placement.isTouched() || + SideType.isTouched() || Type.isTouched() || + Type2.isTouched() || Length.isTouched() || Length2.isTouched() || TaperAngle.isTouched() || @@ -71,7 +76,11 @@ short FeatureExtrude::mustExecute() const ReferenceAxis.isTouched() || AlongSketchNormal.isTouched() || Offset.isTouched() || - UpToFace.isTouched()) + Offset2.isTouched() || + UpToFace.isTouched() || + UpToFace2.isTouched() || + UpToShape.isTouched() || + UpToShape2.isTouched()) return 1; return ProfileBased::mustExecute(); } @@ -174,293 +183,115 @@ TopoShape FeatureExtrude::makeShellFromUpToShape(TopoShape shape, TopoShape sket return shape; } -// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. -void FeatureExtrude::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) +void FeatureExtrude::updateProperties() { - if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { - double Ltotal = L; - double Loffset = 0.; - if (method == "ThroughAll") - Ltotal = getThroughAllLength(); + std::string sideTypeVal = SideType.getValueAsString(); + std::string methodSide1 = Type.getValueAsString(); + std::string methodSide2 = Type2.getValueAsString(); + bool isLength1Enabled = false; + bool isTaper1Visible = false; + bool isUpToFace1Enabled = false; + bool isUpToShape1Enabled = false; + bool isOffset1Enabled = false; - if (method == "TwoLengths") { - Ltotal += L2; - if (reversed) - Loffset = -L; - else - Loffset = -L2; - } - else if (midplane) { - Loffset = -Ltotal / 2; - } - - TopoDS_Shape from = sketchshape; - if (method == "TwoLengths" || midplane) { - gp_Trsf mov; - mov.SetTranslation(Loffset * gp_Vec(direction)); - TopLoc_Location loc(mov); - from = sketchshape.Moved(loc); - } - else if (reversed) { - Ltotal *= -1.0; - } - - if (fabs(Ltotal) < Precision::Confusion()) { - if (addSubType == Type::Additive) - throw Base::ValueError("Cannot create a pad with a height of zero."); - else - throw Base::ValueError("Cannot create a pocket with a depth of zero."); - } - - // Without taper angle we create a prism because its shells are in every case no B-splines and can therefore - // be use as support for further features like Pads, Lofts etc. B-spline shells can break certain features, - // see e.g. https://forum.freecad.org/viewtopic.php?p=560785#p560785 - // It is 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(direction), Standard_False, Standard_True); // finite prism - if (!PrismMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: Length: Could not extrude the sketch!"); - prism = PrismMaker.Shape(); - } - else { - std::stringstream str; - str << "ProfileBased: Internal error: Unknown method '" - << method << "' for generatePrism()"; - throw Base::RuntimeError(str.str()); - } -} - -void FeatureExtrude::generatePrism(TopoDS_Shape& prism, - const std::string& method, - const TopoDS_Shape& baseshape, - const TopoDS_Shape& profileshape, - const TopoDS_Face& supportface, - const TopoDS_Shape& uptoface, - const gp_Dir& direction, - PrismMode Mode, - Standard_Boolean Modify) -{ - if (method == "UpToFirst" || method == "UpToFace") { - BRepFeat_MakePrism PrismMaker; - TopoDS_Shape base = baseshape; - for (TopExp_Explorer xp(profileshape, TopAbs_FACE); xp.More(); xp.Next()) { - PrismMaker.Init(base, xp.Current(), supportface, direction, Mode, Modify); - PrismMaker.Perform(uptoface); - if (!PrismMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: Up to face: Could not extrude the sketch!"); - - base = PrismMaker.Shape(); - if (Mode == PrismMode::None) - Mode = PrismMode::FuseWithBase; - } - - prism = base; - } - else if (method == "UpToLast") { - BRepFeat_MakePrism PrismMaker; - prism = baseshape; - for (TopExp_Explorer xp(profileshape, TopAbs_FACE); xp.More(); xp.Next()) { - PrismMaker.Init(baseshape, xp.Current(), supportface, direction, PrismMode::None, Modify); - - //Each face needs 2 prisms because if uptoFace is intersected twice the first one ends too soon - for (int i=0; i<2; i++){ - if (i==0){ - PrismMaker.Perform(uptoface); - }else{ - PrismMaker.Perform(uptoface, uptoface); - } - - if (!PrismMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: Up to face: Could not extrude the sketch!"); - auto onePrism = PrismMaker.Shape(); - - FCBRepAlgoAPI_Fuse fuse(prism, onePrism); - prism = fuse.Shape(); - } - } - } - else { - std::stringstream str; - str << "ProfileBased: Internal error: Unknown method '" - << method << "' for generatePrism()"; - throw Base::RuntimeError(str.str()); - } -} - -void FeatureExtrude::generatePrism(TopoShape& prism, - TopoShape sketchTopoShape, - const std::string& method, - const gp_Dir& dir, - const double L, - const double L2, - const bool midplane, - const bool reversed) -{ - auto sketchShape = sketchTopoShape.getShape(); - if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { - double Ltotal = L; - double Loffset = 0.; - if (method == "ThroughAll") { - Ltotal = getThroughAllLength(); - } - - if (method == "TwoLengths") { - Ltotal += L2; - if (reversed) { - Loffset = -L; - } - else { - Loffset = -L2; - } - } - else if (midplane) { - Loffset = -Ltotal / 2; - } - - if (method == "TwoLengths" || midplane) { - gp_Trsf mov; - mov.SetTranslation(Loffset * gp_Vec(dir)); - TopLoc_Location loc(mov); - sketchTopoShape.move(loc); - } - else if (reversed) { - Ltotal *= -1.0; - } - - // Without taper angle we create a prism because its shells are in every case no B-splines - // and can therefore be use as support for further features like Pads, Lofts etc. B-spline - // shells can break certain features, see e.g. - // https://forum.freecad.org/viewtopic.php?p=560785#p560785 It is better not to use - // BRepFeat_MakePrism here even if we have a support because the resulting shape creates - // problems with Pocket - try { - prism.makeElementPrism(sketchTopoShape, Ltotal * gp_Vec(dir)); // finite prism - } - catch (Standard_Failure&) { - throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!"); - } - } - else { - std::stringstream str; - str << "FeatureExtrusion: Internal error: Unknown method '" << method - << "' for generatePrism()"; - throw Base::RuntimeError(str.str()); - } -} - -void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism, - const TopoDS_Shape& sketchshape, - const std::string& method, - const gp_Dir& direction, - const double L, - const double L2, - const double angle, - const double angle2, - const bool midplane) -{ - std::list drafts; - bool isSolid = true; // in PD we only generate solids, while Part Extrude can also create only shells - bool isPartDesign = true; // there is an OCC bug with single-edge wires (circles) we need to treat differently for PD and Part - if (method == "ThroughAll") { - Part::ExtrusionHelper::makeDraft(sketchshape, direction, getThroughAllLength(), - 0.0, Base::toRadians(angle), 0.0, isSolid, drafts, isPartDesign); - } - else if (method == "TwoLengths") { - Part::ExtrusionHelper::makeDraft(sketchshape, direction, L, L2, - Base::toRadians(angle), Base::toRadians(angle2), isSolid, drafts, isPartDesign); - } - else if (method == "Length") { - if (midplane) { - Part::ExtrusionHelper::makeDraft(sketchshape, direction, L / 2, L / 2, - Base::toRadians(angle), Base::toRadians(angle), isSolid, drafts, isPartDesign); - } - else - Part::ExtrusionHelper::makeDraft(sketchshape, direction, L, 0.0, - Base::toRadians(angle), 0.0, isSolid, drafts, isPartDesign); - } - - if (drafts.empty()) { - throw Base::RuntimeError("Creation of tapered object failed"); - } - else if (drafts.size() == 1) { - prism = drafts.front(); - } - else { - TopoDS_Compound comp; - BRep_Builder builder; - builder.MakeCompound(comp); - for (const auto & draft : drafts) - builder.Add(comp, draft); - prism = comp; - } -} - -void FeatureExtrude::updateProperties(const std::string &method) -{ - // disable settings that are not valid on the current method - // disable everything unless we are sure we need it - bool isLengthEnabled = false; + bool isType2Enabled = false; bool isLength2Enabled = false; - bool isOffsetEnabled = false; - bool isMidplaneEnabled = false; - bool isReversedEnabled = false; - bool isUpToFaceEnabled = false; - bool isUpToShapeEnabled = false; - bool isTaperVisible = false; bool isTaper2Visible = false; - if (method == "Length") { - isLengthEnabled = true; - isTaperVisible = true; - isMidplaneEnabled = true; - isReversedEnabled = !Midplane.getValue(); + bool isUpToFace2Enabled = false; + bool isUpToShape2Enabled = false; + bool isOffset2Enabled = false; + + bool currentAlongSketchNormalEnabled = false; + + auto configureSideProperties = [&](const std::string& method, + bool& lengthEnabled, + bool& taperVisible, + bool& upToFaceEnabled, + bool& upToShapeEnabled, + bool& localAlongSketchNormal, + bool& localOffset) { + if (method == "Length") { + lengthEnabled = true; + taperVisible = true; + localAlongSketchNormal = true; + } + else if (method == "UpToFace") { + upToFaceEnabled = true; + localOffset = true; + } + else if (method == "UpToShape") { + upToShapeEnabled = true; + localOffset = true; + } + else if (method == "UpToLast" || method == "UpToFirst") { + localOffset = true; + } + else if (method == "ThroughAll") { + // No specific length/taper/offset for ThroughAll type + } + }; + + if (sideTypeVal == "One side") { + bool side1ASN = false; + configureSideProperties(methodSide1, + isLength1Enabled, + isTaper1Visible, + isUpToFace1Enabled, + isUpToShape1Enabled, + side1ASN, + isOffset1Enabled); + currentAlongSketchNormalEnabled = side1ASN; } - else if (method == "UpToLast") { - isOffsetEnabled = true; - isReversedEnabled = true; + else if (sideTypeVal == "Two sides") { + isType2Enabled = true; + + bool side1ASN = false; + configureSideProperties(methodSide1, + isLength1Enabled, + isTaper1Visible, + isUpToFace1Enabled, + isUpToShape1Enabled, + side1ASN, + isOffset1Enabled); + + bool side2ASN = false; + configureSideProperties(methodSide2, + isLength2Enabled, + isTaper2Visible, + isUpToFace2Enabled, + isUpToShape2Enabled, + side2ASN, + isOffset2Enabled); + + currentAlongSketchNormalEnabled = side1ASN || side2ASN; // Enable if either side needs it } - else if (method == "ThroughAll") { - isMidplaneEnabled = true; - isReversedEnabled = !Midplane.getValue(); - } - else if (method == "UpToFirst") { - isOffsetEnabled = true; - isReversedEnabled = true; - } - else if (method == "UpToFace") { - isOffsetEnabled = true; - isReversedEnabled = true; - isUpToFaceEnabled = true; - } - else if (method == "TwoLengths") { - isLengthEnabled = true; - isLength2Enabled = true; - isTaperVisible = true; - isTaper2Visible = true; - isReversedEnabled = true; - } - else if (method == "UpToShape") { - isReversedEnabled = true; - isUpToShapeEnabled = true; + else if (sideTypeVal == "Symmetric") { + bool symASN = false; + configureSideProperties(methodSide1, + isLength1Enabled, + isTaper1Visible, + isUpToFace1Enabled, + isUpToShape1Enabled, + symASN, + isOffset1Enabled); + currentAlongSketchNormalEnabled = symASN; } - Length.setReadOnly(!isLengthEnabled); - AlongSketchNormal.setReadOnly(!isLengthEnabled); + Length.setReadOnly(!isLength1Enabled); + TaperAngle.setReadOnly(!isTaper1Visible); + UpToFace.setReadOnly(!isUpToFace1Enabled); + UpToShape.setReadOnly(!isUpToShape1Enabled); + Offset.setReadOnly(!isOffset1Enabled); + + Type2.setReadOnly(!isType2Enabled); Length2.setReadOnly(!isLength2Enabled); - Offset.setReadOnly(!isOffsetEnabled); - TaperAngle.setReadOnly(!isTaperVisible); TaperAngle2.setReadOnly(!isTaper2Visible); - Midplane.setReadOnly(!isMidplaneEnabled); - Reversed.setReadOnly(!isReversedEnabled); - UpToFace.setReadOnly(!isUpToFaceEnabled); - UpToShape.setReadOnly(!isUpToShapeEnabled); + UpToFace2.setReadOnly(!isUpToFace2Enabled); + UpToShape2.setReadOnly(!isUpToShape2Enabled); + Offset2.setReadOnly(!isOffset2Enabled); + + AlongSketchNormal.setReadOnly(!currentAlongSketchNormalEnabled); } void FeatureExtrude::setupObject() @@ -475,23 +306,30 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt bool makeface = options.testFlag(ExtrudeOption::MakeFace); bool fuse = options.testFlag(ExtrudeOption::MakeFuse); - bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket); bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection); + std::string Sidemethod(SideType.getValueAsString()); std::string method(Type.getValueAsString()); + std::string method2(Type2.getValueAsString()); // Validate parameters double L = Length.getValue(); - if ((method == "Length") && (L < Precision::Confusion())) { - return new App::DocumentObjectExecReturn( - QT_TRANSLATE_NOOP("Exception", "Length too small")); - } - double L2 = 0; - if ((method == "TwoLengths")) { - L2 = Length2.getValue(); - if (std::abs(L2) < Precision::Confusion()) { - return new App::DocumentObjectExecReturn( - QT_TRANSLATE_NOOP("Exception", "Second length too small")); + double L2 = (Sidemethod == "Two sides" && method2 == "Length") ? Length2.getValue() : 0; + + if ((Sidemethod == "One side" && method == "Length") + || (Sidemethod == "Two sides" && method == "Length" && method2 == "Length")) { + + if (std::abs(L + L2) < Precision::Confusion()) { + if (addSubType == Type::Additive) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", + "Cannot create a pad with a total length of zero.")); + } + else { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", + "Cannot create a pocket with a total length of zero.")); + } } } @@ -598,6 +436,9 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt Direction.setValue(paddingDirection); dir.Transform(invTrsf); + if (Reversed.getValue()) { + dir.Reverse(); + } if (sketchshape.isNull()) { return new App::DocumentObjectExecReturn( @@ -605,174 +446,96 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt } sketchshape.move(invObjLoc); - TopoShape prism(0, getDocument()->getStringHasher()); + std::vector prisms; // Stores prisms, all in global CS + std::string sideTypeStr = SideType.getValueAsString(); + std::string method1 = Type.getValueAsString(); + double len1 = method1 == "ThroughAll" ? getThroughAllLength() : Length.getValue(); + double taper1 = TaperAngle.getValue(); + double offset1 = Offset.getValue(); - if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace" || method == "UpToShape") { - // Note: This will return an unlimited planar face if support is a datum plane - TopoShape supportface = getTopoShapeSupportFace(); - supportface.move(invObjLoc); + if (sideTypeStr == "One side") { + TopoShape prism1 = generateSingleExtrusionSide(sketchshape, + method1, len1, taper1, UpToFace, UpToShape, + dir, offset1, makeface, base); + prisms.push_back(prism1); + } + else if (sideTypeStr == "Symmetric") { + TopoShape prism1 = generateSingleExtrusionSide(sketchshape, + method1, len1, taper1, UpToFace, UpToShape, + dir, offset1, makeface, base); + prisms.push_back(prism1); - if (Reversed.getValue()) { - dir.Reverse(); + // Prism 2 : Make a symmetric of prism1 + Base::Vector3d base = sketchshape.getBoundBox().GetCenter(); + gp_Ax2 axe(gp_Pnt(base.x, base.y, base.z), dir); + TopoShape prism2 = prism1.makeElementMirror(axe); + prisms.push_back(prism2); + + } + else if (sideTypeStr == "Two sides") { + TopoShape prism1 = generateSingleExtrusionSide(sketchshape.makeElementCopy(), + method1, + len1, + taper1, + UpToFace, + UpToShape, + dir, + offset1, + makeface, + base); + if (!prism1.isNull() && !prism1.getShape().IsNull()) { + prisms.push_back(prism1); } - TopoShape upToShape; - int faceCount = 1; - // Find a valid shape, face or datum plane to extrude up to - if (method == "UpToFace") { - getUpToFaceFromLinkSub(upToShape, UpToFace); - upToShape.move(invObjLoc); - faceCount = 1; - } - else if (method == "UpToShape") { - faceCount = getUpToShapeFromLinkSubList(upToShape, UpToShape); - upToShape.move(invObjLoc); - if (faceCount == 0){ - // No shape selected, use the base - upToShape = base; - faceCount = 0; - } - } + // Side 2 + std::string method2 = Type2.getValueAsString(); + double len2 = method2 == "ThroughAll" ? getThroughAllLength() : Length2.getValue(); + double taper2 = TaperAngle2.getValue(); + double offset2 = Offset2.getValue(); + gp_Dir dir2 = dir; + dir2.Reverse(); - if (faceCount == 1) { - getUpToFace(upToShape, base, sketchshape, method, dir); - addOffsetToFace(upToShape, dir, Offset.getValue()); - } - else{ - if (fabs(Offset.getValue()) > Precision::Confusion()){ - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Extrude: Can only offset one face")); - } - // open the shell by removing the furthest face - upToShape = makeShellFromUpToShape(upToShape, sketchshape, dir); - } - - if (!supportface.hasSubShape(TopAbs_WIRE)) { - supportface = TopoShape(); - } - if (legacyPocket) { - auto mode = - base.isNull() ? TopoShape::PrismMode::None : TopoShape::PrismMode::CutFromBase; - prism = base.makeElementPrismUntil(sketchshape, - supportface, - upToShape, - dir, - mode, - false /*CheckUpToFaceLimits.getValue()*/); - // DO NOT assign id to the generated prism, because this prism is - // actually the final result. We obtain the subtracted shape by cut - // this prism with the original base. Assigning a minus self id here - // will mess up with preselection highlight. It is enough to re-tag - // the profile shape above. - // - // prism.Tag = -this->getID(); - - // And the really expensive way to get the SubShape... - try { - TopoShape result(0, getDocument()->getStringHasher()); - if (base.isNull()) { - result = prism; - } - else { - result.makeElementCut({base, prism}); - } - - // store shape before refinement - this->rawShape = result; - result = refineShapeIfActive(result); - this->AddSubShape.setValue(result); - } - catch (Standard_Failure&) { - return new App::DocumentObjectExecReturn( - QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!")); - } - - if (getAddSubType() == Additive) { - prism = base.makeElementFuse(this->AddSubShape.getShape()); - } - else { - - // store shape before refinement - this->rawShape = prism; - prism = refineShapeIfActive(prism); - } - - this->Shape.setValue(getSolid(prism)); - return App::DocumentObject::StdReturn; - } - try { - TopoShape _base; - if (addSubType!=FeatureAddSub::Subtractive) { - _base=base; // avoid issue #16690 - } - prism.makeElementPrismUntil(_base, - sketchshape, - supportface, - upToShape, - dir, - TopoShape::PrismMode::None, - true /*CheckUpToFaceLimits.getValue()*/); - } - catch (Base::Exception&) { - if (method == "UpToShape" && faceCount > 1){ - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( - "Exception", - "Unable to reach the selected shape, please select faces")); - } + TopoShape prism2 = generateSingleExtrusionSide(sketchshape.makeElementCopy(), + method2, + len2, + taper2, + UpToFace2, + UpToShape2, + dir2, + offset2, + makeface, + base); + if (!prism2.isNull() && !prism2.getShape().IsNull()) { + prisms.push_back(prism2); } } - else { - using std::numbers::pi; - Part::ExtrusionParameters params; - params.dir = dir; - params.solid = makeface; - params.taperAngleFwd = Base::toRadians(this->TaperAngle.getValue()); - params.taperAngleRev = Base::toRadians(this->TaperAngle2.getValue()); - if (L2 == 0.0 && Midplane.getValue()) { - params.lengthFwd = L / 2; - params.lengthRev = L / 2; - if (params.taperAngleRev == 0.0) { - params.taperAngleRev = params.taperAngleFwd; - } + // --- Combine generated prisms (all in global CS) --- + TopoShape prism(0, getDocument()->getStringHasher()); + if (prisms.empty()) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "No extrusion geometry was generated.")); + } + else if (prisms.size() == 1) { + prism = prisms[0]; + } + else { + try { + prism.makeElementXor(prisms, Part::OpCodes::Extrude); } - else { - params.lengthFwd = L; - params.lengthRev = L2; + catch (const Standard_Failure& e) { + return new App::DocumentObjectExecReturn( + std::string("Failed to xor extrusion sides (OCC): ") + e.GetMessageString()); } - if (std::fabs(params.taperAngleFwd) >= Precision::Angular() - || std::fabs(params.taperAngleRev) >= Precision::Angular()) { - if (fabs(params.taperAngleFwd) > pi * 0.5 - Precision::Angular() - || fabs(params.taperAngleRev) > pi * 0.5 - Precision::Angular()) { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( - "Exception", - "Magnitude of taper angle matches or exceeds 90 degrees")); - } - if (Reversed.getValue()) { - params.dir.Reverse(); - } - std::vector drafts; - Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts, getDocument()->getStringHasher()); - if (drafts.empty()) { - return new App::DocumentObjectExecReturn( - QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed")); - } - prism.makeElementCompound( - drafts, - nullptr, - TopoShape::SingleShapeCompoundCreationPolicy::returnShape); - } - else { - generatePrism(prism, - sketchshape, - method, - dir, - L, - L2, - Midplane.getValue(), - Reversed.getValue()); + catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn( + std::string("Failed to xor extrusion sides: ") + e.what()); } } + if (prism.isNull()) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting fused extrusion is null.")); + } + // store shape before refinement this->rawShape = prism; prism = refineShapeIfActive(prism); @@ -841,7 +604,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt } // eventually disable some settings that are not valid for the current method - updateProperties(method); + updateProperties(); return App::DocumentObject::StdReturn; } @@ -860,3 +623,134 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt return new App::DocumentObjectExecReturn(e.what()); } } + +TopoShape FeatureExtrude::generateSingleExtrusionSide(const TopoShape& sketchshape, + const std::string& method, + double length, + double taperAngleDeg, + App::PropertyLinkSub& upToFacePropHandle, + App::PropertyLinkSubList& upToShapePropHandle, + gp_Dir dir, + double offsetVal, + bool makeFace, + const TopoShape& base) +{ + TopoShape prism(0, getDocument()->getStringHasher()); + + if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace" || method == "UpToShape") { + // Note: This will return an unlimited planar face if support is a datum plane + TopoShape supportface = getTopoShapeSupportFace(); + auto invObjLoc = getLocation().Inverted(); + supportface.move(invObjLoc); + + if (!supportface.hasSubShape(TopAbs_WIRE)) { + supportface = TopoShape(); + } + + TopoShape upToShape; + int faceCount = 1; + // Find a valid shape, face or datum plane to extrude up to + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToShape, upToFacePropHandle); + upToShape.move(invObjLoc); + } + else if (method == "UpToShape") { + faceCount = getUpToShapeFromLinkSubList(upToShape, upToShapePropHandle); + upToShape.move(invObjLoc); + if (faceCount == 0) { + // No shape selected, use the base + upToShape = base; + } + } + + if (faceCount == 1) { + getUpToFace(upToShape, base, sketchshape, method, dir); + addOffsetToFace(upToShape, dir, offsetVal); + } + else { + if (fabs(offsetVal) > Precision::Confusion()) { + throw Base::RuntimeError("Extrude: Can only offset one face"); + } + // open the shell by removing the furthest face + upToShape = makeShellFromUpToShape(upToShape, sketchshape, dir); + } + + try { + TopoShape _base; + if (addSubType != FeatureAddSub::Subtractive) { + _base = base; // avoid issue #16690 + } + prism.makeElementPrismUntil(_base, + sketchshape, + supportface, + upToShape, + dir, + TopoShape::PrismMode::None, + true /*CheckUpToFaceLimits.getValue()*/); + } + catch (Base::Exception&) { + if (method == "UpToShape" && faceCount > 1) { + throw Base::RuntimeError("Extrude: Unable to reach the selected shape, please select faces"); + } + } + } + else if (method == "Length" || method == "ThroughAll") { + using std::numbers::pi; + + Part::ExtrusionParameters params; + params.taperAngleFwd = Base::toRadians(taperAngleDeg); + + if (std::fabs(params.taperAngleFwd) >= Precision::Angular() || std::fabs(params.taperAngleRev) >= Precision::Angular()) { + if (fabs(params.taperAngleFwd) > pi * 0.5 - Precision::Angular() || fabs(params.taperAngleRev) > pi * 0.5 - Precision::Angular()) { + return prism; + } + params.dir = dir; + params.solid = makeFace; + params.lengthFwd = length; + + std::vector drafts; + Part::ExtrusionHelper::makeElementDraft(params, + sketchshape, + drafts, + getDocument()->getStringHasher()); + if (drafts.empty()) { + return prism; + } + prism.makeElementCompound(drafts, + nullptr, + TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } + else { + // Without taper angle we create a prism because its shells are in every case no + // B-splines and can therefore be use as support for further features like Pads, + // Lofts etc. B-spline shells can break certain features, see e.g. + // https://forum.freecad.org/viewtopic.php?p=560785#p560785 It is better not to use + // BRepFeat_MakePrism here even if we have a support because the resulting shape + // creates problems with Pocket + try { + prism.makeElementPrism(sketchshape, length * gp_Vec(dir)); + } + catch (Standard_Failure&) { + throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!"); + } + } + } + + return prism; +} + + +void FeatureExtrude::handleChangedPropertyType(Base::XMLReader& reader, + const char* TypeName, + App::Property* prop) +{ + // property Type no longer has TwoLengths. + if (prop == &Type && strcmp(Type.getValueAsString(), "TwoLengths") == 0) { + Type.setValue("Length"); + Type2.setValue("Length"); + SideType.setValue("Two sides"); + } + else { + ProfileBased::handleChangedPropertyType(reader, TypeName, prop); + } +} diff --git a/src/Mod/PartDesign/App/FeatureExtrude.h b/src/Mod/PartDesign/App/FeatureExtrude.h index 8978834c78..62e5d7fea0 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.h +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -42,7 +42,9 @@ class PartDesignExport FeatureExtrude : public ProfileBased public: FeatureExtrude(); + App::PropertyEnumeration SideType; App::PropertyEnumeration Type; + App::PropertyEnumeration Type2; App::PropertyLength Length; App::PropertyLength Length2; App::PropertyAngle TaperAngle; @@ -51,6 +53,7 @@ public: App::PropertyVector Direction; App::PropertyBool AlongSketchNormal; App::PropertyLength Offset; + App::PropertyLength Offset2; App::PropertyLinkSub ReferenceAxis; static App::PropertyQuantityConstraint::Constraints signedLengthConstraint; @@ -67,7 +70,10 @@ public: } //@} + static const char* SideTypesEnums[]; + protected: + void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; Base::Vector3d computeDirection(const Base::Vector3d& sketchVector, bool inverse); bool hasTaperedAngle() const; @@ -92,65 +98,23 @@ protected: */ TopoShape makeShellFromUpToShape(TopoShape shape, TopoShape sketchshape, gp_Dir dir); - /** - * Generates an extrusion of the input sketchshape and stores it in the given \a prism - */ - 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); - - void generatePrism(TopoShape& prism, - TopoShape sketchshape, - const std::string& method, - const gp_Dir& direction, - const double L, - const double L2, - const bool midplane, - const bool reversed); - - // See BRepFeat_MakePrism - enum PrismMode { - CutFromBase = 0, - FuseWithBase = 1, - None = 2 - }; - - /** - * Generates an extrusion of the input profileshape - * It will be a stand-alone solid created with BRepFeat_MakePrism - */ - static void generatePrism(TopoDS_Shape& prism, - const std::string& method, - const TopoDS_Shape& baseshape, - const TopoDS_Shape& profileshape, - const TopoDS_Face& sketchface, - const TopoDS_Shape& uptoface, - const gp_Dir& direction, - PrismMode Mode, - Standard_Boolean Modify); - - /** - * Generates a tapered prism of the input sketchshape and stores it in the given \a prism - */ - void generateTaperedPrism(TopoDS_Shape& prism, - const TopoDS_Shape& sketchshape, - const std::string& method, - const gp_Dir& direction, - const double L, - const double L2, - const double angle, - const double angle2, - const bool midplane); - /** * Disables settings that are not valid for the current method */ - void updateProperties(const std::string &method); + void updateProperties(); + + TopoShape generateSingleExtrusionSide( + const TopoShape& sketchShape, // The base sketch for this side (global CS) + const std::string& method, + double length, + double taperAngleDeg, + App::PropertyLinkSub& upToFacePropHandle, // e.g., &UpToFace or &UpToFace2 + App::PropertyLinkSubList& upToShapePropHandle, // e.g., &UpToShape or &UpToShape2 + gp_Dir dir, + double offsetVal, + bool makeFace, + const TopoShape& base // The base shape for context (global CS) + ); }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index 3178b667fc..5ac69345ba 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -38,7 +38,7 @@ using namespace PartDesign; -const char* Pad::TypeEnums[]= {"Length", "UpToLast", "UpToFirst", "UpToFace", "TwoLengths", "UpToShape", nullptr}; +const char* Pad::TypeEnums[]= {"Length", "UpToLast", "UpToFirst", "UpToFace", "UpToShape", nullptr}; PROPERTY_SOURCE(PartDesign::Pad, PartDesign::FeatureExtrude) @@ -46,25 +46,34 @@ Pad::Pad() { addSubType = FeatureAddSub::Additive; - ADD_PROPERTY_TYPE(Type, (0L), "Pad", App::Prop_None, "Pad type"); + ADD_PROPERTY_TYPE(SideType, (0L), "Pad", App::Prop_None, "Type of sides definition"); + ADD_PROPERTY_TYPE(Type, (0L), "Side1", App::Prop_None, "Pad type for side 1"); + ADD_PROPERTY_TYPE(Type2, (0L), "Side2", App::Prop_None, "Pad type for side 2"); + SideType.setEnums(SideTypesEnums); Type.setEnums(TypeEnums); - ADD_PROPERTY_TYPE(Length, (10.0), "Pad", App::Prop_None, "Pad length"); - ADD_PROPERTY_TYPE(Length2, (10.0), "Pad", App::Prop_None, "Pad length in 2nd direction"); + Type2.setEnums(TypeEnums); + ADD_PROPERTY_TYPE(Length, (10.0), "Side1", App::Prop_None, "Pad length"); + ADD_PROPERTY_TYPE(Length2, (10.0), "Side2", App::Prop_None, "Pad length in 2nd direction"); ADD_PROPERTY_TYPE(UseCustomVector, (false), "Pad", App::Prop_None, "Use custom vector for pad direction"); ADD_PROPERTY_TYPE(Direction, (Base::Vector3d(1.0, 1.0, 1.0)), "Pad", App::Prop_None, "Pad direction vector"); ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Pad", App::Prop_None, "Reference axis of direction"); ADD_PROPERTY_TYPE(AlongSketchNormal, (true), "Pad", App::Prop_None, "Measure pad length along the sketch normal direction"); - ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Pad", App::Prop_None, "Face where pad will end"); - ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Pad", App::Prop_None, "Faces or shape(s) where pad will end"); - ADD_PROPERTY_TYPE(Offset, (0.0), "Pad", App::Prop_None, "Offset from face in which pad will end"); + ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Side1", App::Prop_None, "Face where pad will end"); + ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Side1", App::Prop_None, "Faces or shape(s) where pad will end"); + ADD_PROPERTY_TYPE(UpToFace2, (nullptr), "Side2", App::Prop_None, "Face where pad will end on side2"); + ADD_PROPERTY_TYPE(UpToShape2, (nullptr), "Side2", App::Prop_None, "Faces or shape(s) where pad will end on side2"); + ADD_PROPERTY_TYPE(Offset, (0.0), "Side1", App::Prop_None, "Offset from face in which pad will end"); + ADD_PROPERTY_TYPE(Offset2, (0.0), "Side2", App::Prop_None, "Offset from face in which pad will end on side 2"); Offset.setConstraints(&signedLengthConstraint); - ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Pad", App::Prop_None, "Taper angle"); + Offset2.setConstraints(&signedLengthConstraint); + ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Side1", App::Prop_None, "Taper angle"); TaperAngle.setConstraints(&floatAngle); - ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Pad", App::Prop_None, "Taper angle for 2nd direction"); + ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Side2", App::Prop_None, "Taper angle for 2nd direction"); TaperAngle2.setConstraints(&floatAngle); // Remove the constraints and keep the type to allow one to accept negative values // https://forum.freecad.org/viewtopic.php?f=3&t=52075&p=448410#p447636 + Length.setConstraints(nullptr); Length2.setConstraints(nullptr); } diff --git a/src/Mod/PartDesign/App/FeaturePad.h b/src/Mod/PartDesign/App/FeaturePad.h index f9933c1621..64456c6bcc 100644 --- a/src/Mod/PartDesign/App/FeaturePad.h +++ b/src/Mod/PartDesign/App/FeaturePad.h @@ -45,8 +45,6 @@ public: * that is cut by a line through the centre of gravite of the sketch * If Type is "UpToFirst" then extrusion will stop at the first face of the support * If Type is "UpToFace" then the extrusion will stop at FaceName in the support - * If Type is "TwoLengths" then the extrusion will extend Length in the direction away from the support - * and Length2 in the opposite direction * If Midplane is true, then the extrusion will extend for half of the length on both sides of the sketch plane * If Reversed is true then the direction of revolution will be reversed. * The created material will be fused with the sketch support (if there is one) diff --git a/src/Mod/PartDesign/App/FeaturePocket.cpp b/src/Mod/PartDesign/App/FeaturePocket.cpp index 4c5bea53b1..614b403dcc 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.cpp +++ b/src/Mod/PartDesign/App/FeaturePocket.cpp @@ -39,7 +39,7 @@ using namespace PartDesign; /* TRANSLATOR PartDesign::Pocket */ -const char* Pocket::TypeEnums[]= {"Length", "ThroughAll", "UpToFirst", "UpToFace", "TwoLengths", "UpToShape", nullptr}; +const char* Pocket::TypeEnums[]= {"Length", "ThroughAll", "UpToFirst", "UpToFace", "UpToShape", nullptr}; PROPERTY_SOURCE(PartDesign::Pocket, PartDesign::FeatureExtrude) @@ -47,25 +47,34 @@ Pocket::Pocket() { addSubType = FeatureAddSub::Subtractive; - ADD_PROPERTY_TYPE(Type, ((long)0), "Pocket", App::Prop_None, "Pocket type"); + ADD_PROPERTY_TYPE(SideType, (0L), "Pocket", App::Prop_None, "Type of sides definition"); + ADD_PROPERTY_TYPE(Type, ((long)0), "Side1", App::Prop_None, "Pocket type"); + ADD_PROPERTY_TYPE(Type2, ((long)0), "Side2", App::Prop_None, "Pocket type"); + SideType.setEnums(SideTypesEnums); Type.setEnums(TypeEnums); - ADD_PROPERTY_TYPE(Length, (5.0), "Pocket", App::Prop_None, "Pocket length"); - ADD_PROPERTY_TYPE(Length2, (5.0), "Pocket", App::Prop_None, "Pocket length in 2nd direction"); + Type2.setEnums(TypeEnums); + ADD_PROPERTY_TYPE(Length, (5.0), "Side1", App::Prop_None, "Pocket length"); + ADD_PROPERTY_TYPE(Length2, (5.0), "Side2", App::Prop_None, "Pocket length in 2nd direction"); ADD_PROPERTY_TYPE(UseCustomVector, (false), "Pocket", App::Prop_None, "Use custom vector for pocket direction"); ADD_PROPERTY_TYPE(Direction, (Base::Vector3d(1.0, 1.0, 1.0)), "Pocket", App::Prop_None, "Pocket direction vector"); ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Pocket", App::Prop_None, "Reference axis of direction"); ADD_PROPERTY_TYPE(AlongSketchNormal, (true), "Pocket", App::Prop_None, "Measure pocket length along the sketch normal direction"); - ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Pocket", App::Prop_None, "Face where pocket will end"); - ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Pocket", App::Prop_None, "Face(s) or shape(s) where pocket will end"); - ADD_PROPERTY_TYPE(Offset, (0.0), "Pocket", App::Prop_None, "Offset from face in which pocket will end"); + ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Side1", App::Prop_None, "Face where pocket will end"); + ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Side1", App::Prop_None, "Face(s) or shape(s) where pocket will end"); + ADD_PROPERTY_TYPE(UpToFace2, (nullptr), "Side2", App::Prop_None, "Face where pocket will end"); + ADD_PROPERTY_TYPE(UpToShape2, (nullptr), "Side2", App::Prop_None, "Face(s) or shape(s) where pocket will end"); + ADD_PROPERTY_TYPE(Offset, (0.0), "Side1", App::Prop_None, "Offset from face in which pocket will end"); + ADD_PROPERTY_TYPE(Offset2, (0.0), "Side2", App::Prop_None, "Offset from face in which pocket will end on side 2"); Offset.setConstraints(&signedLengthConstraint); - ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Pocket", App::Prop_None, "Taper angle"); + Offset2.setConstraints(&signedLengthConstraint); + ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Side1", App::Prop_None, "Taper angle"); TaperAngle.setConstraints(&floatAngle); - ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Pocket", App::Prop_None, "Taper angle for 2nd direction"); + ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Side2", App::Prop_None, "Taper angle for 2nd direction"); TaperAngle2.setConstraints(&floatAngle); // Remove the constraints and keep the type to allow one to accept negative values // https://forum.freecad.org/viewtopic.php?f=3&t=52075&p=448410#p447636 + Length.setConstraints(nullptr); Length2.setConstraints(nullptr); } diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index ea308e4c70..01b69d9e1c 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -74,8 +74,10 @@ ProfileBased::ProfileBased() ADD_PROPERTY_TYPE(Profile, (nullptr), "SketchBased", App::Prop_None, "Reference to sketch"); ADD_PROPERTY_TYPE(Midplane, (0), "SketchBased", App::Prop_None, "Extrude symmetric to sketch face"); ADD_PROPERTY_TYPE(Reversed, (0), "SketchBased", App::Prop_None, "Reverse extrusion direction"); - ADD_PROPERTY_TYPE(UpToFace, (nullptr), "SketchBased", (App::PropertyType)(App::Prop_None), "Face where feature will end"); - ADD_PROPERTY_TYPE(UpToShape, (nullptr), "SketchBased", (App::PropertyType)(App::Prop_None), "Shape where feature will end"); + ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Side1", (App::PropertyType)(App::Prop_None), "Face where feature will end"); + ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Side1", (App::PropertyType)(App::Prop_None), "Shape where feature will end"); + ADD_PROPERTY_TYPE(UpToFace2, (nullptr), "Side2", (App::PropertyType)(App::Prop_None), "Face where feature will end"); + ADD_PROPERTY_TYPE(UpToShape2, (nullptr), "Side2", (App::PropertyType)(App::Prop_None), "Shape where feature will end"); ADD_PROPERTY_TYPE(AllowMultiFace, (false), "SketchBased", App::Prop_None, "Allow multiple faces in profile"); } @@ -84,7 +86,10 @@ short ProfileBased::mustExecute() const if (Profile.isTouched() || Midplane.isTouched() || Reversed.isTouched() || - UpToFace.isTouched()) + UpToFace.isTouched() || + UpToFace2.isTouched() || + UpToShape.isTouched() || + UpToShape2.isTouched()) return 1; return PartDesign::FeatureAddSub::mustExecute(); } diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index dfa73cb87b..12537472fe 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -57,8 +57,10 @@ public: App::PropertyBool Midplane; /// Face to extrude up to App::PropertyLinkSub UpToFace; + App::PropertyLinkSub UpToFace2; /// Shape to extrude up to App::PropertyLinkSubList UpToShape; + App::PropertyLinkSubList UpToShape2; App::PropertyBool AllowMultiFace; diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp index 320e68d05f..a21dd25529 100644 --- a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp @@ -55,10 +55,10 @@ TaskExtrudeParameters::TaskExtrudeParameters(ViewProviderExtrude* SketchBasedVie // we need a separate container widget to add all controls to proxy = new QWidget(this); ui->setupUi(proxy); - handleLineFaceNameNo(); + handleLineFaceNameNo(ui->lineFaceName); + handleLineFaceNameNo(ui->lineFaceName2); Gui::ButtonGroup* group = new Gui::ButtonGroup(this); - group->addButton(ui->checkBoxMidplane); group->addButton(ui->checkBoxReversed); group->setExclusive(true); @@ -69,126 +69,170 @@ TaskExtrudeParameters::~TaskExtrudeParameters() = default; void TaskExtrudeParameters::setupDialog() { - // Get the feature data + auto createRemoveAction = [this]() -> QAction* { + auto action = new QAction(tr("Remove"), this); + action->setShortcut(Gui::QtTools::deleteKeySequence()); + action->setShortcutVisibleInContextMenu(true); + return action; + }; + + unselectShapeFaceAction = createRemoveAction(); + unselectShapeFaceAction2 = createRemoveAction(); + + createSideControllers(); + + // --- Global, Non-Side-Specific Setup --- auto extrude = getObject(); - Base::Quantity l = extrude->Length.getQuantityValue(); - Base::Quantity l2 = extrude->Length2.getQuantityValue(); - Base::Quantity off = extrude->Offset.getQuantityValue(); - Base::Quantity taper = extrude->TaperAngle.getQuantityValue(); - Base::Quantity taper2 = extrude->TaperAngle2.getQuantityValue(); - bool alongNormal = extrude->AlongSketchNormal.getValue(); - bool useCustom = extrude->UseCustomVector.getValue(); - - double xs = extrude->Direction.getValue().x; - double ys = extrude->Direction.getValue().y; - double zs = extrude->Direction.getValue().z; - - bool midplane = extrude->Midplane.getValue(); - bool reversed = extrude->Reversed.getValue(); - - int index = extrude->Type.getValue(); // must extract value here, clear() kills it! - App::DocumentObject* obj = extrude->UpToFace.getValue(); - std::vector subStrings = extrude->UpToFace.getSubValues(); - std::string upToFace; - int faceId = -1; - if (obj && !subStrings.empty()) { - upToFace = subStrings.front(); - if (upToFace.compare(0, 4, "Face") == 0) { - faceId = std::atoi(&upToFace[4]); - } - } - - // set decimals for the direction edits - // do this here before the edits are filled to avoid rounding mistakes int UserDecimals = Base::UnitsApi::getDecimals(); ui->XDirectionEdit->setDecimals(UserDecimals); ui->YDirectionEdit->setDecimals(UserDecimals); ui->ZDirectionEdit->setDecimals(UserDecimals); - // Fill data into dialog elements - // the direction combobox is later filled in updateUI() - ui->lengthEdit->setValue(l); - ui->lengthEdit2->setValue(l2); - ui->offsetEdit->setValue(off); - ui->taperEdit->setMinimum(extrude->TaperAngle.getMinimum()); - ui->taperEdit->setMaximum(extrude->TaperAngle.getMaximum()); - ui->taperEdit->setSingleStep(extrude->TaperAngle.getStepSize()); - ui->taperEdit->setValue(taper); - ui->taperEdit2->setMinimum(extrude->TaperAngle2.getMinimum()); - ui->taperEdit2->setMaximum(extrude->TaperAngle2.getMaximum()); - ui->taperEdit2->setSingleStep(extrude->TaperAngle2.getStepSize()); - ui->taperEdit2->setValue(taper2); + ui->checkBoxAlongDirection->setChecked(extrude->AlongSketchNormal.getValue()); + ui->checkBoxDirection->setChecked(extrude->UseCustomVector.getValue()); + onDirectionToggled(ui->checkBoxDirection->isChecked()); - ui->checkBoxAlongDirection->setChecked(alongNormal); - ui->checkBoxDirection->setChecked(useCustom); - onDirectionToggled(useCustom); + ui->XDirectionEdit->setValue(extrude->Direction.getValue().x); + ui->YDirectionEdit->setValue(extrude->Direction.getValue().y); + ui->ZDirectionEdit->setValue(extrude->Direction.getValue().z); - // disable to change the direction if not custom - if (!useCustom) { - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - ui->XDirectionEdit->setValue(xs); - ui->YDirectionEdit->setValue(ys); - ui->ZDirectionEdit->setValue(zs); + ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, "Direction.x")); + ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, "Direction.y")); + ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, "Direction.z")); - // Bind input fields to properties - ui->lengthEdit->bind(extrude->Length); - ui->lengthEdit2->bind(extrude->Length2); - ui->offsetEdit->bind(extrude->Offset); - ui->taperEdit->bind(extrude->TaperAngle); - ui->taperEdit2->bind(extrude->TaperAngle2); - ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.x"))); - ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.y"))); - ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.z"))); + ui->checkBoxReversed->setChecked(extrude->Reversed.getValue()); - ui->checkBoxMidplane->setChecked(midplane); - // According to bug #0000521 the reversed option - // shouldn't be de-activated if the pad has a support face - ui->checkBoxReversed->setChecked(reversed); + // --- Per-Side Setup using the Helper --- + setupSideDialog(m_side1); + setupSideDialog(m_side2); - // Set object labels - if (obj && PartDesign::Feature::isDatum(obj)) { - ui->lineFaceName->setText(QString::fromUtf8(obj->Label.getValue())); - ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); - } - else if (obj && faceId >= 0) { - ui->lineFaceName->setText( - QStringLiteral("%1:%2%3").arg(QString::fromUtf8(obj->Label.getValue()), - tr("Face"), - QString::number(faceId))); - ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); - } - else { - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - } - - ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); - - updateShapeName(); - updateShapeFaces(); - - translateModeList(index); - - unselectShapeFaceAction = new QAction(tr("Remove"), this); - unselectShapeFaceAction->setShortcut(Gui::QtTools::deleteKeySequence()); - - // display shortcut behind the context menu entry - unselectShapeFaceAction->setShortcutVisibleInContextMenu(true); - - ui->listWidgetReferences->addAction(unselectShapeFaceAction); - ui->listWidgetReferences->setContextMenuPolicy(Qt::ActionsContextMenu); - ui->checkBoxAllFaces->setChecked(ui->listWidgetReferences->count() == 0); + // --- Final Global UI State Setup --- + translateSidesList(extrude->SideType.getValue()); connectSlots(); this->propReferenceAxis = &(extrude->ReferenceAxis); + updateUI(Side::First); +} - // Due to signals attached after changes took took into effect we should update the UI now. - updateUI(index); +void TaskExtrudeParameters::setupSideDialog(SideController& side) +{ + // --- Get initial values from the correct side's properties --- + Base::Quantity length = side.Length->getQuantityValue(); + Base::Quantity offset = side.Offset->getQuantityValue(); + Base::Quantity taper = side.TaperAngle->getQuantityValue(); + int typeIndex = side.Type->getValue(); + + // --- Set up UI widgets with initial values --- + side.lengthEdit->setValue(length); + side.offsetEdit->setValue(offset); + side.taperEdit->setMinimum(side.TaperAngle->getMinimum()); + side.taperEdit->setMaximum(side.TaperAngle->getMaximum()); + side.taperEdit->setSingleStep(side.TaperAngle->getStepSize()); + side.taperEdit->setValue(taper); + + // --- Bind UI widgets to the correct properties --- + side.lengthEdit->bind(*side.Length); + side.offsetEdit->bind(*side.Offset); + side.taperEdit->bind(*side.TaperAngle); + + // --- Handle "Up to face" label logic --- + App::DocumentObject* faceObj = side.UpToFace->getValue(); + std::vector subStrings = side.UpToFace->getSubValues(); + std::string upToFaceName; + int faceId = -1; + if (faceObj && !subStrings.empty()) { + upToFaceName = subStrings.front(); + if (upToFaceName.rfind("Face", 0) == 0) { // starts_with + faceId = std::atoi(&upToFaceName[4]); + } + } + + if (faceObj && PartDesign::Feature::isDatum(faceObj)) { + side.lineFaceName->setText(QString::fromUtf8(faceObj->Label.getValue())); + side.lineFaceName->setProperty("FeatureName", QByteArray(faceObj->getNameInDocument())); + } + else if (faceObj && faceId >= 0) { + side.lineFaceName->setText( + QStringLiteral("%1:%2%3").arg(QString::fromUtf8(faceObj->Label.getValue()), + tr("Face"), + QString::number(faceId))); + side.lineFaceName->setProperty("FeatureName", QByteArray(faceObj->getNameInDocument())); + } + else { + side.lineFaceName->clear(); + side.lineFaceName->setProperty("FeatureName", QVariant()); + } + side.lineFaceName->setProperty("FaceName", QByteArray(upToFaceName.c_str())); + + // --- Update shape-related UI --- + updateShapeName(side.lineShapeName, *side.UpToShape); + updateShapeFaces(side.listWidgetReferences, *side.UpToShape); + + // --- Set up the mode combobox and list widget context menu --- + translateModeList(side.changeMode, typeIndex); + + side.listWidgetReferences->addAction(side.unselectShapeFaceAction); + side.listWidgetReferences->setContextMenuPolicy(Qt::ActionsContextMenu); + side.checkBoxAllFaces->setChecked(side.listWidgetReferences->count() == 0); +} + +void TaskExtrudeParameters::createSideControllers() +{ + auto extrude = getObject(); + + // --- Initialize Side 1 Controller --- + m_side1.changeMode = ui->changeMode; + m_side1.labelLength = ui->labelLength; + m_side1.labelOffset = ui->labelOffset; + m_side1.labelTaperAngle = ui->labelTaperAngle; + m_side1.lengthEdit = ui->lengthEdit; + m_side1.offsetEdit = ui->offsetEdit; + m_side1.taperEdit = ui->taperEdit; + m_side1.lineFaceName = ui->lineFaceName; + m_side1.buttonFace = ui->buttonFace; + m_side1.lineShapeName = ui->lineShapeName; + m_side1.buttonShape = ui->buttonShape; + m_side1.listWidgetReferences = ui->listWidgetReferences; + m_side1.buttonShapeFace = ui->buttonShapeFace; + m_side1.checkBoxAllFaces = ui->checkBoxAllFaces; + m_side1.upToShapeList = ui->upToShapeList; + m_side1.upToShapeFaces = ui->upToShapeFaces; + m_side1.unselectShapeFaceAction = unselectShapeFaceAction; + + m_side1.Type = &extrude->Type; + m_side1.Length = &extrude->Length; + m_side1.Offset = &extrude->Offset; + m_side1.TaperAngle = &extrude->TaperAngle; + m_side1.UpToFace = &extrude->UpToFace; + m_side1.UpToShape = &extrude->UpToShape; + + // --- Initialize Side 2 Controller --- + m_side2.changeMode = ui->changeMode2; + m_side2.labelLength = ui->labelLength2; + m_side2.labelOffset = ui->labelOffset2; + m_side2.labelTaperAngle = ui->labelTaperAngle2; + m_side2.lengthEdit = ui->lengthEdit2; + m_side2.offsetEdit = ui->offsetEdit2; + m_side2.taperEdit = ui->taperEdit2; + m_side2.lineFaceName = ui->lineFaceName2; + m_side2.buttonFace = ui->buttonFace2; + m_side2.lineShapeName = ui->lineShapeName2; + m_side2.buttonShape = ui->buttonShape2; + m_side2.listWidgetReferences = ui->listWidgetReferences2; + m_side2.buttonShapeFace = ui->buttonShapeFace2; + m_side2.checkBoxAllFaces = ui->checkBoxAllFaces2; + m_side2.upToShapeList = ui->upToShapeList2; + m_side2.upToShapeFaces = ui->upToShapeFaces2; + m_side2.unselectShapeFaceAction = unselectShapeFaceAction2; + + m_side2.Type = &extrude->Type2; + m_side2.Length = &extrude->Length2; + m_side2.Offset = &extrude->Offset2; + m_side2.TaperAngle = &extrude->TaperAngle2; + m_side2.UpToFace = &extrude->UpToFace2; + m_side2.UpToShape = &extrude->UpToShape2; } void TaskExtrudeParameters::readValuesFromHistory() @@ -199,6 +243,8 @@ void TaskExtrudeParameters::readValuesFromHistory() ui->lengthEdit2->selectNumber(); ui->offsetEdit->setToLastUsedValue(); ui->offsetEdit->selectNumber(); + ui->offsetEdit2->setToLastUsedValue(); + ui->offsetEdit2->selectNumber(); ui->taperEdit->setToLastUsedValue(); ui->taperEdit->selectNumber(); ui->taperEdit2->setToLastUsedValue(); @@ -209,17 +255,57 @@ void TaskExtrudeParameters::connectSlots() { QMetaObject::connectSlotsByName(this); + auto connectSideSlots = [this](auto& side, Side sideEnum, auto modeChangedSlot) { + connect(side.lengthEdit, + qOverload(&Gui::PrefQuantitySpinBox::valueChanged), + this, + [this, sideEnum](double val) { + onLengthChanged(val, sideEnum); + }); + connect(side.offsetEdit, + qOverload(&Gui::PrefQuantitySpinBox::valueChanged), + this, + [this, sideEnum](double val) { + onOffsetChanged(val, sideEnum); + }); + connect(side.taperEdit, + qOverload(&Gui::PrefQuantitySpinBox::valueChanged), + this, + [this, sideEnum](double val) { + onTaperChanged(val, sideEnum); + }); + connect(side.changeMode, + qOverload(&QComboBox::currentIndexChanged), + this, + modeChangedSlot); + connect(side.buttonFace, &QToolButton::toggled, this, [this, sideEnum](bool checked) { + onSelectFaceToggle(checked, sideEnum); + }); + connect(side.lineFaceName, + &QLineEdit::textEdited, + this, + [this, sideEnum](const QString& text) { + onFaceName(text, sideEnum); + }); + connect(side.checkBoxAllFaces, &QCheckBox::toggled, this, [this, sideEnum](bool checked) { + onAllFacesToggled(checked, sideEnum); + }); + connect(side.buttonShape, &QToolButton::toggled, this, [this, sideEnum](bool checked) { + onSelectShapeToggle(checked, sideEnum); + }); + connect(side.buttonShapeFace, &QToolButton::toggled, this, [this, sideEnum](bool checked) { + onSelectShapeFacesToggle(checked, sideEnum); + }); + connect(side.unselectShapeFaceAction, &QAction::triggered, this, [this, sideEnum]() { + onUnselectShapeFacesTrigger(sideEnum); + }); + }; + + // Use the lambda to connect slots for both sides + connectSideSlots(m_side1, Side::First, &TaskExtrudeParameters::onModeChanged_Side1); + connectSideSlots(m_side2, Side::Second, &TaskExtrudeParameters::onModeChanged_Side2); + // clang-format off - connect(ui->lengthEdit, qOverload(&Gui::PrefQuantitySpinBox::valueChanged), - this, &TaskExtrudeParameters::onLengthChanged); - connect(ui->lengthEdit2, qOverload(&Gui::PrefQuantitySpinBox::valueChanged), - this, &TaskExtrudeParameters::onLength2Changed); - connect(ui->offsetEdit, qOverload(&Gui::PrefQuantitySpinBox::valueChanged), - this, &TaskExtrudeParameters::onOffsetChanged); - connect(ui->taperEdit, qOverload(&Gui::PrefQuantitySpinBox::valueChanged), - this, &TaskExtrudeParameters::onTaperChanged); - connect(ui->taperEdit2, qOverload(&Gui::PrefQuantitySpinBox::valueChanged), - this, &TaskExtrudeParameters::onTaper2Changed); connect(ui->directionCB, qOverload(&QComboBox::activated), this, &TaskExtrudeParameters::onDirectionCBChanged); connect(ui->checkBoxAlongDirection, &QCheckBox::toggled, @@ -232,47 +318,45 @@ void TaskExtrudeParameters::connectSlots() this, &TaskExtrudeParameters::onYDirectionEditChanged); connect(ui->ZDirectionEdit, qOverload(&QDoubleSpinBox::valueChanged), this, &TaskExtrudeParameters::onZDirectionEditChanged); - connect(ui->checkBoxMidplane, &QCheckBox::toggled, - this, &TaskExtrudeParameters::onMidplaneChanged); connect(ui->checkBoxReversed, &QCheckBox::toggled, this, &TaskExtrudeParameters::onReversedChanged); - connect(ui->checkBoxAllFaces, &QCheckBox::toggled, - this, &TaskExtrudeParameters::onAllFacesToggled); - connect(ui->changeMode, qOverload(&QComboBox::currentIndexChanged), - this, &TaskExtrudeParameters::onModeChanged); - connect(ui->buttonFace, &QToolButton::toggled, - this, &TaskExtrudeParameters::onSelectFaceToggle); - connect(ui->buttonShape, &QToolButton::toggled, - this, &TaskExtrudeParameters::onSelectShapeToggle); - connect(ui->lineFaceName, &QLineEdit::textEdited, - this, &TaskExtrudeParameters::onFaceName); + connect(ui->sidesMode, qOverload(&QComboBox::currentIndexChanged), + this, &TaskExtrudeParameters::onSidesModeChanged); connect(ui->checkBoxUpdateView, &QCheckBox::toggled, this, &TaskExtrudeParameters::onUpdateView); - connect(ui->buttonShapeFace, &QToolButton::toggled, - this, &TaskExtrudeParameters::onSelectShapeFacesToggle); - connect(unselectShapeFaceAction, &QAction::triggered, - this, &TaskExtrudeParameters::onUnselectShapeFacesTrigger); // clang-format on } -void TaskExtrudeParameters::onSelectShapeFacesToggle(bool checked) +void TaskExtrudeParameters::onModeChanged_Side1(int index) { + onModeChanged(index, Side::First); +} + +void TaskExtrudeParameters::onModeChanged_Side2(int index) +{ + onModeChanged(index, Side::Second); +} + +void TaskExtrudeParameters::onSelectShapeFacesToggle(bool checked, Side side) +{ + auto& sideCtrl = getSideController(side); if (checked) { - setSelectionMode(SelectShapeFaces); - ui->buttonShapeFace->setText(tr("Preview")); + setSelectionMode(SelectShapeFaces, side); + sideCtrl.buttonShapeFace->setText(tr("Preview")); } else { setSelectionMode(None); - ui->buttonShapeFace->setText(tr("Select Faces")); + sideCtrl.buttonShapeFace->setText(tr("Select Faces")); } } -void PartDesignGui::TaskExtrudeParameters::onUnselectShapeFacesTrigger() +void PartDesignGui::TaskExtrudeParameters::onUnselectShapeFacesTrigger(Side side) { - auto selected = ui->listWidgetReferences->selectedItems(); - auto faces = getShapeFaces(); + auto& sideCtrl = getSideController(side); + + auto selected = sideCtrl.listWidgetReferences->selectedItems(); + auto faces = getShapeFaces(*sideCtrl.UpToShape); - auto extrude = getObject(); faces.erase(std::remove_if(faces.begin(), faces.end(), [selected](const std::string& face) { for (auto& item : selected) { @@ -284,22 +368,30 @@ void PartDesignGui::TaskExtrudeParameters::onUnselectShapeFacesTrigger() return false; })); - extrude->UpToShape.setValue(extrude->UpToShape.getValue(), faces); + sideCtrl.UpToShape->setValue(sideCtrl.UpToShape->getValue(), faces); - updateShapeFaces(); + updateShapeFaces(sideCtrl.listWidgetReferences, *sideCtrl.UpToShape); } -void TaskExtrudeParameters::setSelectionMode(SelectionMode mode) +void TaskExtrudeParameters::setSelectionMode(SelectionMode mode, Side side) { - if (selectionMode == mode) { + if (selectionMode == mode && activeSelectionSide == side) { return; } - ui->buttonShapeFace->setChecked(mode == SelectShapeFaces); - ui->buttonFace->setChecked(mode == SelectFace); - ui->buttonShape->setChecked(mode == SelectShape); + const auto updateCheckedForSide = [mode, side](Side relatedSide, + QAbstractButton* buttonFace, + QAbstractButton* buttonShape, + QAbstractButton* buttonShapeFace) { + buttonFace->setChecked(mode == SelectFace && side == relatedSide); + buttonShape->setChecked(mode == SelectShape && side == relatedSide); + buttonShapeFace->setChecked(mode == SelectShapeFaces && side == relatedSide); + }; + updateCheckedForSide(Side::First, ui->buttonFace, ui->buttonShape, ui->buttonShapeFace); + updateCheckedForSide(Side::Second, ui->buttonFace2, ui->buttonShape2, ui->buttonShapeFace2); selectionMode = mode; + activeSelectionSide = side; switch (mode) { case SelectShape: @@ -310,10 +402,13 @@ void TaskExtrudeParameters::setSelectionMode(SelectionMode mode) case SelectFace: onSelectReference(AllowSelection::FACE); break; - case SelectShapeFaces: + case SelectShapeFaces: { onSelectReference(AllowSelection::FACE); - getViewObject()->highlightShapeFaces(getShapeFaces()); + auto& sideCtrl = getSideController(activeSelectionSide); + getViewObject()->highlightShapeFaces( + getShapeFaces(*sideCtrl.UpToShape)); break; + } case SelectReferenceAxis: onSelectReference(AllowSelection::EDGE | AllowSelection::PLANAR | AllowSelection::CIRCLE); @@ -337,18 +432,19 @@ void TaskExtrudeParameters::tryRecomputeFeature() void TaskExtrudeParameters::onSelectionChanged(const Gui::SelectionChanges& msg) { + auto& sideCtrl = getSideController(activeSelectionSide); + if (msg.Type == Gui::SelectionChanges::AddSelection) { switch (selectionMode) { case SelectShape: - selectedShape(msg); + selectedShape(msg, sideCtrl); break; - case SelectShapeFaces: - selectedShapeFace(msg); + selectedShapeFace(msg, sideCtrl); break; case SelectFace: - selectedFace(msg); + selectedFace(msg, sideCtrl); break; case SelectReferenceAxis: @@ -360,8 +456,10 @@ void TaskExtrudeParameters::onSelectionChanged(const Gui::SelectionChanges& msg) break; } } - else if (msg.Type == Gui::SelectionChanges::ClrSelection && selectionMode == SelectFace) { - clearFaceName(); + else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + if (selectionMode == SelectFace) { + clearFaceName(sideCtrl.lineFaceName); + } } } @@ -381,7 +479,8 @@ void TaskExtrudeParameters::selectedReferenceAxis(const Gui::SelectionChanges& m } } -void TaskExtrudeParameters::selectedShapeFace(const Gui::SelectionChanges& msg) +void TaskExtrudeParameters::selectedShapeFace(const Gui::SelectionChanges& msg, + SideController& side) { auto extrude = getObject(); auto document = extrude->getDocument(); @@ -390,61 +489,64 @@ void TaskExtrudeParameters::selectedShapeFace(const Gui::SelectionChanges& msg) return; } - auto base = static_cast(extrude->UpToShape.getValue()); - if (!base){ + // Get the base shape from the correct side's property + auto base = static_cast(side.UpToShape->getValue()); + if (!base) { base = static_cast(extrude); } else if (strcmp(msg.pObjectName, base->getNameInDocument()) != 0) { return; } - std::vector faces = getShapeFaces(); + std::vector faces = getShapeFaces(*side.UpToShape); const std::string subName(msg.pSubName); if (subName.empty()) { return; } + // Add or remove the face from the list if (const auto positionInList = std::ranges::find(faces, subName); - positionInList != faces.end()) { // it's in the list - faces.erase(positionInList); // remove it. + positionInList != faces.end()) { + faces.erase(positionInList); // Remove it if it exists } else { - faces.push_back(subName); // not yet in the list so add it. + faces.push_back(subName); // Add it if it's new } - extrude->UpToShape.setValue(base, faces); + side.UpToShape->setValue(base, faces); - updateShapeFaces(); + updateShapeFaces(side.listWidgetReferences, *side.UpToShape); tryRecomputeFeature(); } -void PartDesignGui::TaskExtrudeParameters::selectedFace(const Gui::SelectionChanges& msg) +void PartDesignGui::TaskExtrudeParameters::selectedFace(const Gui::SelectionChanges& msg, + SideController& side) { - QString refText = onAddSelection(msg); + QString refText = onAddSelection(msg, *side.UpToFace); if (refText.length() > 0) { - QSignalBlocker block(ui->lineFaceName); + QSignalBlocker block(side.lineFaceName); - ui->lineFaceName->setText(refText); - ui->lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); - ui->lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); + side.lineFaceName->setText(refText); + side.lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); + side.lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); // Turn off reference selection mode - ui->buttonFace->setChecked(false); + side.buttonFace->setChecked(false); } else { - clearFaceName(); + clearFaceName(side.lineFaceName); } setSelectionMode(None); } -void PartDesignGui::TaskExtrudeParameters::selectedShape(const Gui::SelectionChanges& msg) +void PartDesignGui::TaskExtrudeParameters::selectedShape(const Gui::SelectionChanges& msg, + SideController& side) { - auto extrude = getObject(); - auto document = extrude->getDocument(); + auto document = getObject()->getDocument(); if (strcmp(msg.pDocName, document->getName()) != 0) { return; @@ -454,49 +556,48 @@ void PartDesignGui::TaskExtrudeParameters::selectedShape(const Gui::SelectionCha auto ref = document->getObject(msg.pObjectName); - extrude->UpToShape.setValue(ref); + side.UpToShape->setValue(ref); - ui->checkBoxAllFaces->setChecked(true); + side.checkBoxAllFaces->setChecked(true); setSelectionMode(None); - updateShapeName(); - updateShapeFaces(); + updateShapeName(side.lineShapeName, *side.UpToShape); + updateShapeFaces(side.listWidgetReferences, *side.UpToShape); tryRecomputeFeature(); } -void TaskExtrudeParameters::clearFaceName() +void TaskExtrudeParameters::clearFaceName(QLineEdit* lineEdit) { - QSignalBlocker block(ui->lineFaceName); - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); + QSignalBlocker block(lineEdit); + lineEdit->clear(); + lineEdit->setProperty("FeatureName", QVariant()); + lineEdit->setProperty("FaceName", QVariant()); } -void TaskExtrudeParameters::updateShapeName() +void TaskExtrudeParameters::updateShapeName(QLineEdit* lineEdit, App::PropertyLinkSubList& prop) { - QSignalBlocker block(ui->lineShapeName); + QSignalBlocker block(lineEdit); - auto extrude = getObject(); - auto shape = extrude->UpToShape.getValue(); + auto shape = prop.getValue(); if (shape) { - ui->lineShapeName->setText(QString::fromStdString(shape->getFullName())); + lineEdit->setText(QString::fromStdString(shape->getFullName())); } else { - ui->lineShapeName->setText({}); - ui->lineShapeName->setPlaceholderText(tr("No shape selected")); + lineEdit->setText({}); + lineEdit->setPlaceholderText(tr("No shape selected")); } } -void TaskExtrudeParameters::updateShapeFaces() +void TaskExtrudeParameters::updateShapeFaces(QListWidget* list, App::PropertyLinkSubList& prop) { - auto faces = getShapeFaces(); + auto faces = getShapeFaces(prop); - ui->listWidgetReferences->clear(); + list->clear(); for (auto& ref : faces) { - ui->listWidgetReferences->addItem(QString::fromStdString(ref)); + list->addItem(QString::fromStdString(ref)); } if (selectionMode == SelectShapeFaces) { @@ -504,12 +605,11 @@ void TaskExtrudeParameters::updateShapeFaces() } } -std::vector PartDesignGui::TaskExtrudeParameters::getShapeFaces() +std::vector PartDesignGui::TaskExtrudeParameters::getShapeFaces(App::PropertyLinkSubList& prop) { std::vector faces; - auto extrude = getObject(); - auto allRefs = extrude->UpToShape.getSubValues(); + auto allRefs = prop.getSubValues(); std::copy_if(allRefs.begin(), allRefs.end(), @@ -521,44 +621,22 @@ std::vector PartDesignGui::TaskExtrudeParameters::getShapeFaces() return faces; } -void TaskExtrudeParameters::onLengthChanged(double len) +void TaskExtrudeParameters::onLengthChanged(double len, Side side) { - if (auto extrude = getObject()) { - extrude->Length.setValue(len); - tryRecomputeFeature(); - } + getSideController(side).Length->setValue(len); + tryRecomputeFeature(); } -void TaskExtrudeParameters::onLength2Changed(double len) +void TaskExtrudeParameters::onOffsetChanged(double len, Side side) { - if (auto extrude = getObject()) { - extrude->Length2.setValue(len); - tryRecomputeFeature(); - } + getSideController(side).Offset->setValue(len); + tryRecomputeFeature(); } -void TaskExtrudeParameters::onOffsetChanged(double len) +void TaskExtrudeParameters::onTaperChanged(double angle, Side side) { - if (auto extrude = getObject()) { - extrude->Offset.setValue(len); - tryRecomputeFeature(); - } -} - -void TaskExtrudeParameters::onTaperChanged(double angle) -{ - if (auto extrude = getObject()) { - extrude->TaperAngle.setValue(angle); - tryRecomputeFeature(); - } -} - -void TaskExtrudeParameters::onTaper2Changed(double angle) -{ - if (auto extrude = getObject()) { - extrude->TaperAngle2.setValue(angle); - tryRecomputeFeature(); - } + getSideController(side).TaperAngle->setValue(angle); + tryRecomputeFeature(); } bool TaskExtrudeParameters::hasProfileFace(PartDesign::ProfileBased* profile) const @@ -669,107 +747,110 @@ void TaskExtrudeParameters::addAxisToCombo(App::DocumentObject* linkObj, } } -void TaskExtrudeParameters::setCheckboxes(Mode mode, Type type) +void TaskExtrudeParameters::updateWholeUI(Type type, Side side) { - // disable/hide everything unless we are sure we don't need it - // exception: the direction parameters are in any case visible - bool isLengthEditVisible = false; - bool isLengthEdit2Visible = false; - bool isOffsetEditVisible = false; - bool isOffsetEditEnabled = true; - bool isMidplaneEnabled = false; - bool isMidplaneVisible = false; - bool isReversedEnabled = false; - bool isFaceEditVisible = false; - bool isShapeEditVisible = false; - bool isTaperEditVisible = false; - bool isTaperEdit2Visible = false; + SidesMode sidesMode = static_cast(ui->sidesMode->currentIndex()); + Mode mode1 = static_cast(ui->changeMode->currentIndex()); + Mode mode2 = static_cast(ui->changeMode2->currentIndex()); - if (mode == Mode::Dimension) { - isLengthEditVisible = true; - ui->lengthEdit->selectNumber(); - QMetaObject::invokeMethod(ui->lengthEdit, "setFocus", Qt::QueuedConnection); - isTaperEditVisible = true; - isMidplaneVisible = true; - isMidplaneEnabled = true; - // Reverse only makes sense if Midplane is not true - isReversedEnabled = !ui->checkBoxMidplane->isChecked(); - } - else if (mode == Mode::ThroughAll && type == Type::Pocket) { - isOffsetEditVisible = true; - isOffsetEditEnabled = - false; // offset may have some meaning for through all but it doesn't work - isMidplaneEnabled = true; - isMidplaneVisible = true; - isReversedEnabled = !ui->checkBoxMidplane->isChecked(); - } - else if (mode == Mode::ToLast && type == Type::Pad) { - isOffsetEditVisible = true; - isReversedEnabled = true; - } - else if (mode == Mode::ToFirst) { - isOffsetEditVisible = true; - isReversedEnabled = true; - } - else if (mode == Mode::ToFace) { - isOffsetEditVisible = true; - isReversedEnabled = true; - isFaceEditVisible = true; - QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); - // Go into reference selection mode if no face has been selected yet - if (ui->lineFaceName->property("FeatureName").isNull()) { - ui->buttonFace->setChecked(true); + // --- Global UI visibility based on SidesMode --- + const bool isSide2GroupVisible = (sidesMode == SidesMode::TwoSides); + ui->side1Label->setVisible(isSide2GroupVisible); + ui->side2Label->setVisible(isSide2GroupVisible); + ui->line1->setVisible(isSide2GroupVisible); + ui->line2->setVisible(isSide2GroupVisible); + ui->typeLabel2->setVisible(isSide2GroupVisible); + ui->changeMode2->setVisible(isSide2GroupVisible); + + // --- Configure each side using the helper method --- + // Side 1 is always conceptually visible, and we pass whether it should receive focus. + updateSideUI(m_side1, type, mode1, true, (side == Side::First)); + // Side 2 is only visible if in TwoSides mode, and we pass whether it should receive focus. + updateSideUI(m_side2, type, mode2, isSide2GroupVisible, (side == Side::Second)); + + ui->checkBoxAlongDirection->setVisible(m_side1.lengthEdit->isVisible() + || m_side2.lengthEdit->isVisible()); + ui->checkBoxReversed->setEnabled(sidesMode != SidesMode::Symmetric || mode1 != Mode::Dimension); +} + +void TaskExtrudeParameters::updateSideUI(const SideController& s, + Type featureType, + Mode sideMode, + bool isParentVisible, + bool setFocus) +{ + // Default states for all controls for this side + bool isLengthVisible = false; + bool isOffsetVisible = false; + bool isOffsetEnabled = true; + bool isTaperVisible = false; + bool isFaceVisible = false; + bool isShapeVisible = false; + + // This logic block is a direct translation of the original 'if/else if' chain + if (sideMode == Mode::Dimension) { + isLengthVisible = true; + isTaperVisible = true; + if (setFocus) { + s.lengthEdit->selectNumber(); + QMetaObject::invokeMethod(s.lengthEdit, "setFocus", Qt::QueuedConnection); } } - else if (mode == Mode::ToShape) { - isReversedEnabled = true; - isShapeEditVisible = true; - - if (!ui->checkBoxAllFaces->isChecked()) { - ui->buttonShapeFace->setChecked(true); + else if (sideMode == Mode::ThroughAll && featureType == Type::Pocket) { + isOffsetVisible = true; + isOffsetEnabled = false; // "through all" pocket offset doesn't work + isTaperVisible = true; + } + else if (sideMode == Mode::ToLast && featureType == Type::Pad) { + isOffsetVisible = true; + } + else if (sideMode == Mode::ToFirst) { + isOffsetVisible = true; + } + else if (sideMode == Mode::ToFace) { + isOffsetVisible = true; + isFaceVisible = true; + if (setFocus) { + QMetaObject::invokeMethod(s.lineFaceName, "setFocus", Qt::QueuedConnection); + // Go into reference selection mode if no face has been selected yet + if (s.lineFaceName->property("FeatureName").isNull()) { + s.buttonFace->setChecked(true); + } } } - else if (mode == Mode::TwoDimensions) { - isLengthEditVisible = true; - isLengthEdit2Visible = true; - isTaperEditVisible = true; - isTaperEdit2Visible = true; - isReversedEnabled = true; + else if (sideMode == Mode::ToShape) { + isShapeVisible = true; + if (setFocus) { + if (!s.checkBoxAllFaces->isChecked()) { + s.buttonShapeFace->setChecked(true); + } + } } - ui->lengthEdit->setVisible(isLengthEditVisible); - ui->lengthEdit->setEnabled(isLengthEditVisible); - ui->labelLength->setVisible(isLengthEditVisible); - ui->checkBoxAlongDirection->setVisible(isLengthEditVisible); + // Apply visibility based on the logic above AND the parent visibility. + // This single 'isParentVisible' check correctly hides all of Side 2's UI at once. + const bool finalLengthVisible = isParentVisible && isLengthVisible; + s.labelLength->setVisible(finalLengthVisible); + s.lengthEdit->setVisible(finalLengthVisible); + s.lengthEdit->setEnabled(finalLengthVisible); - ui->lengthEdit2->setVisible(isLengthEdit2Visible); - ui->lengthEdit2->setEnabled(isLengthEdit2Visible); - ui->labelLength2->setVisible(isLengthEdit2Visible); + const bool finalOffsetVisible = isParentVisible && isOffsetVisible; + s.labelOffset->setVisible(finalOffsetVisible); + s.offsetEdit->setVisible(finalOffsetVisible); + s.offsetEdit->setEnabled(finalOffsetVisible && isOffsetEnabled); - ui->offsetEdit->setVisible(isOffsetEditVisible); - ui->offsetEdit->setEnabled(isOffsetEditVisible && isOffsetEditEnabled); - ui->labelOffset->setVisible(isOffsetEditVisible); + const bool finalTaperVisible = isParentVisible && isTaperVisible; + s.labelTaperAngle->setVisible(finalTaperVisible); + s.taperEdit->setVisible(finalTaperVisible); + s.taperEdit->setEnabled(finalTaperVisible); - ui->taperEdit->setVisible(isTaperEditVisible); - ui->taperEdit->setEnabled(isTaperEditVisible); - ui->labelTaperAngle->setVisible(isTaperEditVisible); - - ui->taperEdit2->setVisible(isTaperEdit2Visible); - ui->taperEdit2->setEnabled(isTaperEdit2Visible); - ui->labelTaperAngle2->setVisible(isTaperEdit2Visible); - - ui->checkBoxMidplane->setEnabled(isMidplaneEnabled); - ui->checkBoxMidplane->setVisible(isMidplaneVisible); - - ui->checkBoxReversed->setEnabled(isReversedEnabled); - - ui->buttonFace->setVisible(isFaceEditVisible); - ui->lineFaceName->setVisible(isFaceEditVisible); - if (!isFaceEditVisible) { - ui->buttonFace->setChecked(false); + s.buttonFace->setVisible(isParentVisible && isFaceVisible); + s.lineFaceName->setVisible(isParentVisible && isFaceVisible); + if (!isFaceVisible) { + s.buttonFace->setChecked(false); // Ensure button is unchecked when hidden } - ui->upToShapeList->setVisible(isShapeEditVisible); + s.upToShapeList->setVisible(isParentVisible && isShapeVisible); } void TaskExtrudeParameters::onDirectionCBChanged(int num) @@ -831,17 +912,16 @@ void TaskExtrudeParameters::onDirectionToggled(bool on) } } -void PartDesignGui::TaskExtrudeParameters::onAllFacesToggled(bool on) +void TaskExtrudeParameters::onAllFacesToggled(bool on, Side side) { - ui->upToShapeFaces->setVisible(!on); - ui->buttonShapeFace->setChecked(false); + auto& sideCtrl = getSideController(side); + sideCtrl.upToShapeFaces->setVisible(!on); + sideCtrl.buttonShapeFace->setChecked(false); if (on) { if (auto extrude = getObject()) { extrude->UpToShape.setValue(extrude->UpToShape.getValue()); - - updateShapeFaces(); - + updateShapeFaces(sideCtrl.listWidgetReferences, *sideCtrl.UpToShape); tryRecomputeFeature(); } } @@ -932,20 +1012,10 @@ void TaskExtrudeParameters::setDirectionMode(int index) } } -void TaskExtrudeParameters::onMidplaneChanged(bool on) -{ - if (auto extrude = getObject()) { - extrude->Midplane.setValue(on); - ui->checkBoxReversed->setEnabled(!on); - tryRecomputeFeature(); - } -} - void TaskExtrudeParameters::onReversedChanged(bool on) { if (auto extrude = getObject()) { extrude->Reversed.setValue(on); - ui->checkBoxMidplane->setEnabled(!on); // update the direction tryRecomputeFeature(); updateDirectionEdits(); @@ -978,63 +1048,70 @@ void TaskExtrudeParameters::getReferenceAxis(App::DocumentObject*& obj, } } -void TaskExtrudeParameters::onSelectFaceToggle(const bool checked) +void TaskExtrudeParameters::onSelectFaceToggle(const bool checked, Side side) { - if (!checked) { - handleLineFaceNameNo(); + auto& sideCtrl = getSideController(side); + if (checked) { + handleLineFaceNameClick(sideCtrl.lineFaceName); + setSelectionMode(SelectFace, side); } else { - handleLineFaceNameClick(); // sets placeholder text - setSelectionMode(SelectFace); + handleLineFaceNameNo(sideCtrl.lineFaceName); } } -void PartDesignGui::TaskExtrudeParameters::onSelectShapeToggle(const bool checked) +void TaskExtrudeParameters::onSelectShapeToggle(bool checked, Side side) { + auto& sideCtrl = getSideController(side); if (checked) { - setSelectionMode(SelectShape); - - ui->lineShapeName->setText({}); - ui->lineShapeName->setPlaceholderText(tr("Click on a shape in the model")); + setSelectionMode(SelectShape, side); + sideCtrl.lineShapeName->setText({}); + sideCtrl.lineShapeName->setPlaceholderText(tr("Click on a shape in the model")); } else { setSelectionMode(None); - updateShapeName(); + updateShapeName(sideCtrl.lineShapeName, *sideCtrl.UpToShape); } } -void TaskExtrudeParameters::onFaceName(const QString& text) +void TaskExtrudeParameters::onFaceName(const QString& text, Side side) +{ + auto& sideCtrl = getSideController(side); + changeFaceName(sideCtrl.lineFaceName, text); +} + +void TaskExtrudeParameters::changeFaceName(QLineEdit* lineEdit, const QString& text) { if (text.isEmpty()) { // if user cleared the text field then also clear the properties - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); + lineEdit->setProperty("FeatureName", QVariant()); + lineEdit->setProperty("FaceName", QVariant()); } else { // expect that the label of an object is used QStringList parts = text.split(QChar::fromLatin1(':')); QString label = parts[0]; - QVariant name = objectNameByLabel(label, ui->lineFaceName->property("FeatureName")); + QVariant name = objectNameByLabel(label, lineEdit->property("FeatureName")); if (name.isValid()) { parts[0] = name.toString(); QString uptoface = parts.join(QStringLiteral(":")); - ui->lineFaceName->setProperty("FeatureName", name); - ui->lineFaceName->setProperty("FaceName", setUpToFace(uptoface)); + lineEdit->setProperty("FeatureName", name); + lineEdit->setProperty("FaceName", setUpToFace(uptoface)); } else { - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); + lineEdit->setProperty("FeatureName", QVariant()); + lineEdit->setProperty("FaceName", QVariant()); } } } -void TaskExtrudeParameters::translateFaceName() +void TaskExtrudeParameters::translateFaceName(QLineEdit* lineEdit) { - handleLineFaceNameNo(); - QVariant featureName = ui->lineFaceName->property("FeatureName"); + handleLineFaceNameNo(lineEdit); + QVariant featureName = lineEdit->property("FeatureName"); if (featureName.isValid()) { - QStringList parts = ui->lineFaceName->text().split(QChar::fromLatin1(':')); - QByteArray upToFace = ui->lineFaceName->property("FaceName").toByteArray(); + QStringList parts = lineEdit->text().split(QChar::fromLatin1(':')); + QByteArray upToFace = lineEdit->property("FaceName").toByteArray(); int faceId = -1; bool ok = false; if (upToFace.indexOf("Face") == 0) { @@ -1042,11 +1119,11 @@ void TaskExtrudeParameters::translateFaceName() } if (ok) { - ui->lineFaceName->setText( + lineEdit->setText( QStringLiteral("%1:%2%3").arg(parts[0], tr("Face")).arg(faceId)); } else { - ui->lineFaceName->setText(parts[0]); + lineEdit->setText(parts[0]); } } } @@ -1056,6 +1133,11 @@ double TaskExtrudeParameters::getOffset() const return ui->offsetEdit->value().getValue(); } +double TaskExtrudeParameters::getOffset2() const +{ + return ui->offsetEdit2->value().getValue(); +} + bool TaskExtrudeParameters::getAlongSketchNormal() const { return ui->checkBoxAlongDirection->isChecked(); @@ -1094,21 +1176,26 @@ bool TaskExtrudeParameters::getReversed() const return ui->checkBoxReversed->isChecked(); } -bool TaskExtrudeParameters::getMidplane() const -{ - return ui->checkBoxMidplane->isChecked(); -} - int TaskExtrudeParameters::getMode() const { return ui->changeMode->currentIndex(); } -QString TaskExtrudeParameters::getFaceName() const +int TaskExtrudeParameters::getMode2() const { - QVariant featureName = ui->lineFaceName->property("FeatureName"); + return ui->changeMode2->currentIndex(); +} + +int TaskExtrudeParameters::getSidesMode() const +{ + return ui->sidesMode->currentIndex(); +} + +QString TaskExtrudeParameters::getFaceName(QLineEdit* lineEdit) const +{ + QVariant featureName = lineEdit->property("FeatureName"); if (featureName.isValid()) { - QString faceName = ui->lineFaceName->property("FaceName").toString(); + QString faceName = lineEdit->property("FaceName").toString(); return getFaceReference(featureName.toString(), faceName); } @@ -1122,6 +1209,7 @@ void TaskExtrudeParameters::changeEvent(QEvent* e) QSignalBlocker length(ui->lengthEdit); QSignalBlocker length2(ui->lengthEdit2); QSignalBlocker offset(ui->offsetEdit); + QSignalBlocker offset2(ui->offsetEdit2); QSignalBlocker taper(ui->taperEdit); QSignalBlocker taper2(ui->taperEdit2); QSignalBlocker xdir(ui->XDirectionEdit); @@ -1129,7 +1217,10 @@ void TaskExtrudeParameters::changeEvent(QEvent* e) QSignalBlocker zdir(ui->ZDirectionEdit); QSignalBlocker dir(ui->directionCB); QSignalBlocker face(ui->lineFaceName); + QSignalBlocker face2(ui->lineFaceName2); QSignalBlocker mode(ui->changeMode); + QSignalBlocker mode2(ui->changeMode2); + QSignalBlocker sidesMode(ui->sidesMode); // Save all items QStringList items; @@ -1149,9 +1240,12 @@ void TaskExtrudeParameters::changeEvent(QEvent* e) ui->directionCB->setCurrentIndex(index); // Translate mode items - translateModeList(ui->changeMode->currentIndex()); + translateModeList(ui->changeMode, ui->changeMode->currentIndex()); + translateModeList(ui->changeMode2, ui->changeMode2->currentIndex()); + translateSidesList(ui->sidesMode->currentIndex()); - translateFaceName(); + translateFaceName(ui->lineFaceName); + translateFaceName(ui->lineFaceName2); } } @@ -1161,14 +1255,24 @@ void TaskExtrudeParameters::saveHistory() ui->lengthEdit->pushToHistory(); ui->lengthEdit2->pushToHistory(); ui->offsetEdit->pushToHistory(); + ui->offsetEdit2->pushToHistory(); ui->taperEdit->pushToHistory(); ui->taperEdit2->pushToHistory(); } -void TaskExtrudeParameters::applyParameters(QString facename) +void TaskExtrudeParameters::applyParameters() { auto obj = getObject(); + QString facename = QStringLiteral("None"); + QString facename2 = QStringLiteral("None"); + if (static_cast(getMode()) == Mode::ToFace) { + facename = getFaceName(ui->lineFaceName); + } + if (static_cast(getMode2()) == Mode::ToFace) { + facename2 = getFaceName(ui->lineFaceName2); + } + ui->lengthEdit->apply(); ui->lengthEdit2->apply(); ui->taperEdit->apply(); @@ -1179,36 +1283,64 @@ void TaskExtrudeParameters::applyParameters(QString facename) << getZDirection() << ")"); FCMD_OBJ_CMD(obj, "ReferenceAxis = " << getReferenceAxis()); FCMD_OBJ_CMD(obj, "AlongSketchNormal = " << (getAlongSketchNormal() ? 1 : 0)); + FCMD_OBJ_CMD(obj, "SideType = " << getSidesMode()); FCMD_OBJ_CMD(obj, "Type = " << getMode()); + FCMD_OBJ_CMD(obj, "Type2 = " << getMode2()); FCMD_OBJ_CMD(obj, "UpToFace = " << facename.toLatin1().data()); + FCMD_OBJ_CMD(obj, "UpToFace2 = " << facename2.toLatin1().data()); FCMD_OBJ_CMD(obj, "Reversed = " << (getReversed() ? 1 : 0)); - FCMD_OBJ_CMD(obj, "Midplane = " << (getMidplane() ? 1 : 0)); FCMD_OBJ_CMD(obj, "Offset = " << getOffset()); + FCMD_OBJ_CMD(obj, "Offset2 = " << getOffset2()); } -void TaskExtrudeParameters::onModeChanged(int) +void TaskExtrudeParameters::onSidesModeChanged(int index) +{ + auto extrude = getObject(); + switch (static_cast(index)) { + case SidesMode::OneSide: + extrude->SideType.setValue("One side"); + updateUI(Side::First); + break; + case SidesMode::TwoSides: + extrude->SideType.setValue("Two sides"); + updateUI(Side::Second); + break; + case SidesMode::Symmetric: + extrude->SideType.setValue("Symmetric"); + updateUI(Side::First); + break; + } + + recomputeFeature(); +} + +void TaskExtrudeParameters::updateUI(Side) { // implement in sub-class } -void TaskExtrudeParameters::updateUI(int) +void TaskExtrudeParameters::translateModeList(QComboBox*, int) { // implement in sub-class } -void TaskExtrudeParameters::translateModeList(int) +void TaskExtrudeParameters::translateSidesList(int index) { - // implement in sub-class + ui->sidesMode->clear(); + ui->sidesMode->addItem(tr("One sided")); + ui->sidesMode->addItem(tr("Two sided")); + ui->sidesMode->addItem(tr("Symmetric")); + ui->sidesMode->setCurrentIndex(index); } -void TaskExtrudeParameters::handleLineFaceNameClick() +void TaskExtrudeParameters::handleLineFaceNameClick(QLineEdit* lineEdit) { - ui->lineFaceName->setPlaceholderText(tr("Click on a face in the model")); + lineEdit->setPlaceholderText(tr("Click on a face in the model")); } -void TaskExtrudeParameters::handleLineFaceNameNo() +void TaskExtrudeParameters::handleLineFaceNameNo(QLineEdit* lineEdit) { - ui->lineFaceName->setPlaceholderText(tr("No face selected")); + lineEdit->setPlaceholderText(tr("No face selected")); } TaskDlgExtrudeParameters::TaskDlgExtrudeParameters(PartDesignGui::ViewProviderExtrude* vp) @@ -1230,3 +1362,4 @@ bool TaskDlgExtrudeParameters::reject() } #include "moc_TaskExtrudeParameters.cpp" + diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h index 93d293ada0..905cb81fe5 100644 --- a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h @@ -26,11 +26,20 @@ #include "TaskSketchBasedParameters.h" #include "ViewProviderExtrude.h" +class QCheckBox; +class QComboBox; +class QLineEdit; +class QListWidget; +class QToolButton; class Ui_TaskPadPocketParameters; namespace App { class Property; +class PropertyLinkSubList; +} +namespace Gui { +class PrefQuantitySpinBox; } namespace PartDesign { @@ -57,13 +66,23 @@ public: Pocket }; + enum class SidesMode { + OneSide, + TwoSides, + Symmetric, + }; + + enum class Side { + First, + Second, + }; + enum class Mode { Dimension, ThroughAll, ToLast = ThroughAll, ToFirst, ToFace, - TwoDimensions, ToShape, }; @@ -84,35 +103,84 @@ public: void fillDirectionCombo(); void addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, QString itemText, bool hasSketch = true); - void applyParameters(QString facename); + void applyParameters(); - void setSelectionMode(SelectionMode mode); + void setSelectionMode(SelectionMode mode, Side side = Side::First); + +protected: + // This struct holds all pointers for one side's UI and properties + struct SideController + { + // UI Widgets + QComboBox* changeMode = nullptr; + QLabel* labelLength = nullptr; + QLabel* labelOffset = nullptr; + QLabel* labelTaperAngle = nullptr; + Gui::PrefQuantitySpinBox* lengthEdit = nullptr; + Gui::PrefQuantitySpinBox* offsetEdit = nullptr; + Gui::PrefQuantitySpinBox* taperEdit = nullptr; + QLineEdit* lineFaceName = nullptr; + QToolButton* buttonFace = nullptr; + QLineEdit* lineShapeName = nullptr; + QToolButton* buttonShape = nullptr; + QListWidget* listWidgetReferences = nullptr; + QToolButton* buttonShapeFace = nullptr; + QCheckBox* checkBoxAllFaces = nullptr; + QWidget* upToShapeList = nullptr; + QWidget* upToShapeFaces = nullptr; + QAction* unselectShapeFaceAction = nullptr; + + // Feature Properties + App::PropertyEnumeration* Type = nullptr; + App::PropertyLength* Length = nullptr; + App::PropertyLength* Offset = nullptr; + App::PropertyAngle* TaperAngle = nullptr; + App::PropertyLinkSub* UpToFace = nullptr; + App::PropertyLinkSubList* UpToShape = nullptr; + }; + + SideController m_side1; + SideController m_side2; + + SideController& getSideController(Side side) + { + return (side == Side::First) ? m_side1 : m_side2; + } protected Q_SLOTS: - void onLengthChanged(double); - void onLength2Changed(double); - void onOffsetChanged(double); - void onTaperChanged(double); - void onTaper2Changed(double); + void onSidesModeChanged(int); + virtual void onModeChanged(int index, Side side) = 0; + +private Q_SLOTS: void onDirectionCBChanged(int); void onAlongSketchNormalChanged(bool); void onDirectionToggled(bool); - void onAllFacesToggled(bool); void onXDirectionEditChanged(double); void onYDirectionEditChanged(double); void onZDirectionEditChanged(double); - void onMidplaneChanged(bool); void onReversedChanged(bool); - void onFaceName(const QString& text); - void onSelectFaceToggle(const bool checked = true); - void onSelectShapeToggle(const bool checked = true); - void onSelectShapeFacesToggle(const bool checked); - void onUnselectShapeFacesTrigger(); - virtual void onModeChanged(int); +private: + void onModeChanged_Side1(int index); + void onModeChanged_Side2(int index); + void onLengthChanged(double len, Side side); + void onOffsetChanged(double len, Side side); + void onTaperChanged(double angle, Side side); + void onSelectFaceToggle(bool checked, Side side); + + void onFaceName(const QString& text, Side side); + void onAllFacesToggled(bool checked, Side side); + void onSelectShapeToggle(bool checked, Side side); + void onSelectShapeFacesToggle(bool checked, Side side); + void onUnselectShapeFacesTrigger(Side side); protected: - void setCheckboxes(Mode mode, Type type); + void updateWholeUI(Type type, Side side); + void updateSideUI(const SideController& s, + Type featureType, + Mode sideMode, + bool isParentVisible, + bool setFocus); void setupDialog(); void readValuesFromHistory(); void changeEvent(QEvent *e) override; @@ -120,6 +188,7 @@ protected: void getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const; double getOffset() const; + double getOffset2() const; bool getAlongSketchNormal() const; bool getCustom() const; std::string getReferenceAxis() const; @@ -127,42 +196,52 @@ protected: double getYDirection() const; double getZDirection() const; bool getReversed() const; - bool getMidplane() const; int getMode() const; - QString getFaceName() const; + int getMode2() const; + int getSidesMode() const; + QString getFaceName(QLineEdit*) const; void onSelectionChanged(const Gui::SelectionChanges& msg) override; - virtual void translateModeList(int index); - virtual void updateUI(int index); + void translateSidesList(int index); + virtual void translateModeList(QComboBox* box, int index); + virtual void updateUI(Side side); void updateDirectionEdits(); void setDirectionMode(int index); - void handleLineFaceNameClick(); - void handleLineFaceNameNo(); + void handleLineFaceNameClick(QLineEdit*); + void handleLineFaceNameNo(QLineEdit*); private: + void setupSideDialog(SideController& side); + void selectedReferenceAxis(const Gui::SelectionChanges& msg); - void selectedFace(const Gui::SelectionChanges& msg); - void selectedShape(const Gui::SelectionChanges& msg); - void selectedShapeFace(const Gui::SelectionChanges& msg); + void selectedFace(const Gui::SelectionChanges& msg, SideController& side); + void selectedShape(const Gui::SelectionChanges& msg, SideController& side); + void selectedShapeFace(const Gui::SelectionChanges& msg, SideController& side); void tryRecomputeFeature(); - void translateFaceName(); + void translateFaceName(QLineEdit*); void connectSlots(); bool hasProfileFace(PartDesign::ProfileBased*) const; - void clearFaceName(); + void clearFaceName(QLineEdit*); - void updateShapeName(); - void updateShapeFaces(); + void updateShapeName(QLineEdit*, App::PropertyLinkSubList&); + void updateShapeFaces(QListWidget* list, App::PropertyLinkSubList& prop); - std::vector getShapeFaces(); + std::vector getShapeFaces(App::PropertyLinkSubList& prop); + + void changeFaceName(QLineEdit* lineEdit, const QString& text); + + void createSideControllers(); protected: QWidget* proxy; QAction* unselectShapeFaceAction; + QAction* unselectShapeFaceAction2; std::unique_ptr ui; std::vector> axesInList; SelectionMode selectionMode = None; + Side activeSelectionSide = Side::First; }; class TaskDlgExtrudeParameters : public TaskDlgSketchBasedParameters diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index 6f968cbbe6..174ca5e01b 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -39,7 +39,8 @@ using namespace Gui; TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, bool newObj) : TaskExtrudeParameters(PadView, parent, "PartDesign_Pad", tr("Pad Parameters")) { - ui->offsetEdit->setToolTip(tr("Offsets the pad from the face at which the pad will end")); + ui->offsetEdit->setToolTip(tr("Offset the pad from the face at which the pad will end on side 1")); + ui->offsetEdit2->setToolTip(tr("Offset the pad from the face at which the pad will end on side 2")); ui->checkBoxReversed->setToolTip(tr("Reverses pad direction")); // set the history path @@ -49,6 +50,8 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadLength2")); ui->offsetEdit->setEntryName(QByteArray("Offset")); ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadOffset")); + ui->offsetEdit2->setEntryName(QByteArray("Offset2")); + ui->offsetEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadOffset2")); ui->taperEdit->setEntryName(QByteArray("TaperAngle")); ui->taperEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadTaperAngle")); ui->taperEdit2->setEntryName(QByteArray("TaperAngle2")); @@ -64,71 +67,70 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, TaskPadParameters::~TaskPadParameters() = default; -void TaskPadParameters::translateModeList(int index) +void TaskPadParameters::translateModeList(QComboBox* box, int index) { - ui->changeMode->clear(); - ui->changeMode->addItem(tr("Dimension")); - ui->changeMode->addItem(tr("To last")); - ui->changeMode->addItem(tr("To first")); - ui->changeMode->addItem(tr("Up to face")); - ui->changeMode->addItem(tr("Two dimensions")); - ui->changeMode->addItem(tr("Up to shape")); - ui->changeMode->setCurrentIndex(index); + box->clear(); + box->addItem(tr("Dimension")); + box->addItem(tr("To last")); + box->addItem(tr("To first")); + box->addItem(tr("Up to face")); + box->addItem(tr("Up to shape")); + box->setCurrentIndex(index); } -void TaskPadParameters::updateUI(int index) +void TaskPadParameters::updateUI(Side side) { // update direction combobox fillDirectionCombo(); // set and enable checkboxes - setCheckboxes(static_cast(index), Type::Pad); + updateWholeUI(Type::Pad, side); } -void TaskPadParameters::onModeChanged(int index) +void TaskPadParameters::onModeChanged(int index, Side side) { - auto pcPad = getObject(); + auto& sideCtrl = getSideController(side); switch (static_cast(index)) { - case Mode::Dimension: - pcPad->Type.setValue("Length"); - // Avoid error message - if (ui->lengthEdit->value() < Base::Quantity(Precision::Confusion(), Base::Unit::Length)) - ui->lengthEdit->setValue(5.0); - break; - case Mode::ToLast: - pcPad->Type.setValue("UpToLast"); - break; - case Mode::ToFirst: - pcPad->Type.setValue("UpToFirst"); - break; - case Mode::ToFace: - // Note: ui->checkBoxReversed is purposely enabled because the selected face - // could be a circular one around the sketch - pcPad->Type.setValue("UpToFace"); - if (ui->lineFaceName->text().isEmpty()) { - ui->buttonFace->setChecked(true); - handleLineFaceNameClick(); // sets placeholder text - } - break; - case Mode::TwoDimensions: - pcPad->Type.setValue("TwoLengths"); - break; - case Mode::ToShape: - pcPad->Type.setValue("UpToShape"); - break; + case Mode::Dimension: + sideCtrl.Type->setValue("Length"); + if (side == Side::First) { + // Avoid error message + double L = sideCtrl.lengthEdit->value().getValue(); + Side otherSide = side == Side::First ? Side::Second : Side::First; + auto& sideCtrl2 = getSideController(otherSide); + double L2 = static_cast(getSidesMode()) == SidesMode::TwoSides + ? sideCtrl2.lengthEdit->value().getValue() + : 0; + if (std::abs(L + L2) < Precision::Confusion()) { + sideCtrl.lengthEdit->setValue(5.0); + } + } + break; + case Mode::ToLast: + sideCtrl.Type->setValue("UpToLast"); + break; + case Mode::ToFirst: + sideCtrl.Type->setValue("UpToFirst"); + break; + case Mode::ToFace: + sideCtrl.Type->setValue("UpToFace"); + if (sideCtrl.lineFaceName->text().isEmpty()) { + sideCtrl.buttonFace->setChecked(true); + handleLineFaceNameClick(sideCtrl.lineFaceName); // sets placeholder text + } + break; + case Mode::ToShape: + sideCtrl.Type->setValue("UpToShape"); + break; } - updateUI(index); + updateUI(side); recomputeFeature(); } void TaskPadParameters::apply() { - QString facename = QStringLiteral("None"); - if (static_cast(getMode()) == Mode::ToFace) { - facename = getFaceName(); - } - applyParameters(facename); + applyParameters(); } //************************************************************************** diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.h b/src/Mod/PartDesign/Gui/TaskPadParameters.h index 070ec23476..4f19ba628f 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.h @@ -26,6 +26,7 @@ #include "TaskExtrudeParameters.h" #include "ViewProviderPad.h" +class QComboBox; namespace App { class Property; @@ -49,9 +50,9 @@ public: void apply() override; private: - void onModeChanged(int index) override; - void translateModeList(int index) override; - void updateUI(int index) override; + void onModeChanged(int index, Side side) override; + void translateModeList(QComboBox* box, int index) override; + void updateUI(Side side) override; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui b/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui index a8e09710c3..ba6aa7a858 100644 --- a/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui @@ -17,13 +17,59 @@ + + + Mode + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Side 1 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + Type - + @@ -32,14 +78,14 @@ - + Length - + false @@ -47,36 +93,16 @@ mm - - 0.000000000000000 - - - - - 2nd length - - - - - - - false - - - mm - - - - + Offset to face - + false @@ -86,147 +112,390 @@ - - - - - - QFrame::Plain - - - 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - + + + + Angle to taper the extrusion + + + Taper angle + + + + + + + false + + + deg + + + + + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + + + + + + 0 + 22 + + + + Select Shape + + + true + + + + + + + + true + + Selects all faces of the shape + + + Select all faces + - - - - - 0 - 22 - - - - Select shape - - - true - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Toggles between selection and preview mode + + + Select + + + true + + + + + + + QAbstractItemView::ExtendedSelection + + + + - - - - - true - - - Applies length symmetrically to sketch plane - - - Select all faces - - - - - - - - 0 + + + + + + + + true - - 0 + + + + + + + 0 + 22 + - - 0 + + Select Face - - 0 + + true - - - - Toggles between selection and preview mode - - - Select - - - true - - - - - - - QAbstractItemView::ExtendedSelection - - - - - - - - - - - - - - - true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Side 2 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Type - - - - - 0 - 22 - - + + + + + - Select face - - - true + Length + + + + false + + + mm + + + + + + + Offset to face + + + + + + + false + + + mm + + + + + + + Angle to taper the extrusion + + + Taper angle + + + + + + + false + + + deg + + + + + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + + + + + + 0 + 22 + + + + Select Shape + + + true + + + + + + + + + true + + + Selects all faces of the shape + + + Select all faces + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Toggles between selection and preview mode + + + Select + + + true + + + + + + + QAbstractItemView::ExtendedSelection + + + + + + + + + + + + + + + true + + + + + + + + 0 + 22 + + + + Select Face + + + true + + + + + - - - true - - - Applies length symmetrically to sketch plane - - - Symmetric to plane + + + Qt::Horizontal @@ -410,61 +679,6 @@ measured along the specified direction - - - - - - Angle to taper the extrusion - - - Taper angle - - - - - - - false - - - deg - - - - - - - - - - - Angle to taper the extrusion - - - 2nd taper angle - - - - - - - false - - - deg - - - - - - - - - Qt::Horizontal - - - @@ -494,25 +708,6 @@ measured along the specified direction
Gui/PrefWidgets.h
- - changeMode - lengthEdit - lengthEdit2 - offsetEdit - buttonFace - lineFaceName - checkBoxMidplane - checkBoxReversed - directionCB - checkBoxDirection - XDirectionEdit - YDirectionEdit - ZDirectionEdit - checkBoxAlongDirection - taperEdit - taperEdit2 - checkBoxUpdateView - diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp index 013780c3cc..34fb370d52 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp @@ -38,9 +38,9 @@ using namespace Gui; TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidget *parent, bool newObj) : TaskExtrudeParameters(PocketView, parent, "PartDesign_Pocket", tr("Pocket Parameters")) - , oldLength(0) { - ui->offsetEdit->setToolTip(tr("Offset from face at which pocket will end")); + ui->offsetEdit->setToolTip(tr("Offset from the selected face at which the pocket will end on side 1")); + ui->offsetEdit2->setToolTip(tr("Offset from the selected face at which the pocket will end on side 2")); ui->checkBoxReversed->setToolTip(tr("Reverses pocket direction")); // set the history path @@ -50,6 +50,8 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketLength2")); ui->offsetEdit->setEntryName(QByteArray("Offset")); ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketOffset")); + ui->offsetEdit2->setEntryName(QByteArray("Offset2")); + ui->offsetEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketOffset2")); ui->taperEdit->setEntryName(QByteArray("TaperAngle")); ui->taperEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketTaperAngle")); ui->taperEdit2->setEntryName(QByteArray("TaperAngle2")); @@ -65,81 +67,74 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge TaskPocketParameters::~TaskPocketParameters() = default; -void TaskPocketParameters::translateModeList(int index) +void TaskPocketParameters::translateModeList(QComboBox* box, int index) { - ui->changeMode->clear(); - ui->changeMode->addItem(tr("Dimension")); - ui->changeMode->addItem(tr("Through all")); - ui->changeMode->addItem(tr("To first")); - ui->changeMode->addItem(tr("Up to face")); - ui->changeMode->addItem(tr("Two dimensions")); - ui->changeMode->addItem(tr("Up to shape")); - ui->changeMode->setCurrentIndex(index); + box->clear(); + box->addItem(tr("Dimension")); + box->addItem(tr("Through all")); + box->addItem(tr("To first")); + box->addItem(tr("Up to face")); + box->addItem(tr("Up to shape")); + box->setCurrentIndex(index); } -void TaskPocketParameters::updateUI(int index) +void TaskPocketParameters::updateUI(Side side) { // update direction combobox fillDirectionCombo(); // set and enable checkboxes - setCheckboxes(static_cast(index), Type::Pocket); + updateWholeUI(Type::Pocket, side); } -void TaskPocketParameters::onModeChanged(int index) +void TaskPocketParameters::onModeChanged(int index, Side side) { - auto pcPocket = getObject(); + auto& sideCtrl = getSideController(side); switch (static_cast(index)) { case Mode::Dimension: - // Why? See below for "UpToFace" - if (oldLength < Precision::Confusion()) - oldLength = 5.0; - pcPocket->Length.setValue(oldLength); - ui->lengthEdit->setValue(oldLength); - pcPocket->Type.setValue("Length"); + sideCtrl.Type->setValue("Length"); + if (side == Side::First) { + // Avoid error message + double L = sideCtrl.lengthEdit->value().getValue(); + Side otherSide = side == Side::First ? Side::Second : Side::First; + auto& sideCtrl2 = getSideController(otherSide); + double L2 = static_cast(getSidesMode()) == SidesMode::TwoSides + ? sideCtrl2.lengthEdit->value().getValue() + : 0; + if (std::abs(L + L2) < Precision::Confusion()) { + sideCtrl.lengthEdit->setValue(5.0); + } + } break; case Mode::ThroughAll: - oldLength = pcPocket->Length.getValue(); - pcPocket->Type.setValue("ThroughAll"); + sideCtrl.Type->setValue("ThroughAll"); break; case Mode::ToFirst: - oldLength = pcPocket->Length.getValue(); - pcPocket->Type.setValue("UpToFirst"); + sideCtrl.Type->setValue("UpToFirst"); break; case Mode::ToFace: // Note: ui->checkBoxReversed is purposely enabled because the selected face // could be a circular one around the sketch // Also note: Because of the code at the beginning 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->lengthEdit->setValue(0.0); - if (ui->lineFaceName->text().isEmpty()) { - ui->buttonFace->setChecked(true); - handleLineFaceNameClick(); // sets placeholder text + sideCtrl.Type->setValue("UpToFace"); + if (sideCtrl.lineFaceName->text().isEmpty()) { + sideCtrl.buttonFace->setChecked(true); + handleLineFaceNameClick(sideCtrl.lineFaceName); // sets placeholder text } break; - case Mode::TwoDimensions: - oldLength = pcPocket->Length.getValue(); - pcPocket->Type.setValue("TwoLengths"); - break; case Mode::ToShape: - pcPocket->Type.setValue("UpToShape"); + sideCtrl.Type->setValue("UpToShape"); break; } - updateUI(index); + updateUI(side); recomputeFeature(); } void TaskPocketParameters::apply() { - QString facename = QStringLiteral("None"); - if (static_cast(getMode()) == Mode::ToFace) { - facename = getFaceName(); - } - applyParameters(facename); + applyParameters(); } //************************************************************************** diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.h b/src/Mod/PartDesign/Gui/TaskPocketParameters.h index 2934c06dc1..5faf69e6f5 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.h @@ -26,6 +26,7 @@ #include "TaskExtrudeParameters.h" #include "ViewProviderPocket.h" +class QComboBox; namespace App { class Property; @@ -49,12 +50,9 @@ public: void apply() override; private: - void onModeChanged(int index) override; - void translateModeList(int index) override; - void updateUI(int index) override; - -private: - double oldLength; + void onModeChanged(int index, Side side) override; + void translateModeList(QComboBox* box, int index) override; + void updateUI(Side side) override; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp index 619155eb36..41c3cb4a46 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp @@ -385,7 +385,8 @@ void TaskRevolutionParameters::onSelectionChanged(const Gui::SelectionChanges& m if (msg.Type == Gui::SelectionChanges::AddSelection) { int mode = ui->changeMode->currentIndex(); if (selectionFace) { - QString refText = onAddSelection(msg); + auto rev = getObject(); + QString refText = onAddSelection(msg, rev->UpToFace); if (refText.length() > 0) { QSignalBlocker block(ui->lineFaceName); ui->lineFaceName->setText(refText); diff --git a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp index 1de7a06db9..4bf62859b7 100644 --- a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp @@ -58,7 +58,7 @@ TaskSketchBasedParameters::TaskSketchBasedParameters(PartDesignGui::ViewProvider this->blockSelection(true); } -const QString TaskSketchBasedParameters::onAddSelection(const Gui::SelectionChanges& msg) +const QString TaskSketchBasedParameters::onAddSelection(const Gui::SelectionChanges& msg, App::PropertyLinkSub& prop) { // Note: The validity checking has already been done in ReferenceSelection.cpp auto sketchBased = getObject(); @@ -81,7 +81,7 @@ const QString TaskSketchBasedParameters::onAddSelection(const Gui::SelectionChan } std::vector upToFaces(1,subname); - sketchBased->UpToFace.setValue(selObj, upToFaces); + prop.setValue(selObj, upToFaces); recomputeFeature(); return refStr; diff --git a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h index 7d8acf9516..0e476e3597 100644 --- a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h +++ b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h @@ -33,6 +33,7 @@ namespace App { class Property; +class PropertyLinkSubList; } namespace PartDesignGui { @@ -51,7 +52,7 @@ public: protected: void onSelectionChanged(const Gui::SelectionChanges& msg) override =0; - const QString onAddSelection(const Gui::SelectionChanges& msg); + const QString onAddSelection(const Gui::SelectionChanges& msg, App::PropertyLinkSub& prop); virtual void startReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base); virtual void finishReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base); /*! diff --git a/src/Mod/PartDesign/PartDesignTests/TestPad.py b/src/Mod/PartDesign/PartDesignTests/TestPad.py index 54c5e08a50..7ad4ffd40d 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestPad.py +++ b/src/Mod/PartDesign/PartDesignTests/TestPad.py @@ -178,7 +178,7 @@ class TestPad(unittest.TestCase): self.Pad1 = self.Doc.addObject("PartDesign::Pad", "Pad1") self.Body.addObject(self.Pad1) self.Pad1.Profile = self.PadSketch1 - self.Pad1.Type = 4 + self.Pad1.SideType = 1 self.Pad1.Length = 1.0 self.Pad1.Length2 = 2.0 self.Pad1.Reversed = 1 @@ -238,7 +238,7 @@ class TestPad(unittest.TestCase): self.Pad1 = self.Doc.addObject("PartDesign::Pad", "Pad1") self.Body.addObject(self.Pad1) self.Pad1.Profile = self.PadSketch1 - self.Pad1.Type = 5 + self.Pad1.Type = 4 self.Doc.recompute() self.assertAlmostEqual(self.Pad1.Shape.Volume, 2.58787, places=4) diff --git a/tests/src/Mod/PartDesign/App/Pad.cpp b/tests/src/Mod/PartDesign/App/Pad.cpp index 69eff9f416..82d4a8a973 100644 --- a/tests/src/Mod/PartDesign/App/Pad.cpp +++ b/tests/src/Mod/PartDesign/App/Pad.cpp @@ -76,7 +76,7 @@ TEST_F(PadTest, TestMidPlaneTwoLength) pad->Length.setValue(10.0); pad->Length2.setValue(20.0); - pad->Type.setValue("TwoLengths"); + pad->SideType.setValue("Two sides"); doc->recompute();