From 942ebd58f00d3cf52f7a3bcefb36fab24c640c15 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sun, 7 Apr 2024 12:39:32 -0400 Subject: [PATCH] Toponaming/Part: Add deprecation comments, clean up code --- src/Mod/Part/App/PartFeature.cpp | 29 +- src/Mod/Part/App/PartFeature.h | 12 +- src/Mod/Part/App/TopoShape.h | 44 +-- src/Mod/Part/App/TopoShapeExpansion.cpp | 6 +- src/Mod/PartDesign/App/Feature.cpp | 61 ++-- src/Mod/PartDesign/App/Feature.h | 6 +- src/Mod/PartDesign/App/FeatureAddSub.cpp | 3 +- src/Mod/PartDesign/App/FeatureAddSub.h | 1 + src/Mod/PartDesign/App/FeatureExtrude.cpp | 263 ++++++++++------ src/Mod/PartDesign/App/FeatureExtrude.h | 5 +- src/Mod/PartDesign/App/FeaturePad.cpp | 8 + src/Mod/PartDesign/App/FeaturePrimitive.cpp | 67 +++- src/Mod/PartDesign/App/FeatureSketchBased.cpp | 297 ++++++++++++------ src/Mod/PartDesign/App/FeatureSketchBased.h | 21 +- src/Mod/Sketcher/App/SketchObject.cpp | 30 +- tests/src/Mod/Part/App/PartTestHelpers.cpp | 11 +- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 56 ++-- 17 files changed, 594 insertions(+), 326 deletions(-) diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index e569305e36..e46abd564c 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -1319,6 +1319,7 @@ template<> PyObject* Part::FeaturePython::getPyObject() { template class PartExport FeaturePythonT; } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector Part::findAllFacesCutBy( const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir) { @@ -1359,38 +1360,42 @@ std::vector Part::findAllFacesCutBy( return result; } -std::vector Part::findAllFacesCutBy( - const TopoShape& shape, const TopoShape& face, const gp_Dir& dir) +std::vector +Part::findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir) { // Find the centre of gravity of the face GProp_GProps props; - BRepGProp::SurfaceProperties(face.getShape(),props); + BRepGProp::SurfaceProperties(face.getShape(), props); gp_Pnt cog = props.CentreOfMass(); // create a line through the centre of gravity gp_Lin line = gce_MakeLin(cog, dir); // Find intersection of line with all faces of the shape - std::vector result; + std::vector result; BRepIntCurveSurface_Inter mkSection; // TODO: Less precision than Confusion() should be OK? - for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More(); mkSection.Next()) { + for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More(); + mkSection.Next()) { gp_Pnt iPnt = mkSection.Pnt(); double dsq = cog.SquareDistance(iPnt); - if (dsq < Precision::Confusion()) - continue; // intersection with original face + if (dsq < Precision::Confusion()) { + continue; // intersection with original face + } // Find out which side of the original face the intersection is on gce_MakeDir mkDir(cog, iPnt); - if (!mkDir.IsDone()) - continue; // some error (appears highly unlikely to happen, though...) + if (!mkDir.IsDone()) { + continue; // some error (appears highly unlikely to happen, though...) + } - if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) - continue; // wrong side of face (opposite to extrusion direction) + if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) { + continue; // wrong side of face (opposite to extrusion direction) + } - cutFaces newF; + cutTopoShapeFaces newF; newF.face = mkSection.Face(); newF.face.mapSubElement(shape); newF.distsq = dsq; diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 54bd009a69..e3259089e0 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -211,18 +211,24 @@ public: * Find all faces cut by a line through the centre of gravity of a given face * Useful for the "up to face" options to pocket or pad */ +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. struct cutFaces { TopoDS_Face face; double distsq; }; +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. PartExport std::vector findAllFacesCutBy(const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir); +struct cutTopoShapeFaces +{ + TopoShape face; + double distsq; +}; -PartExport - std::vector findAllFacesCutBy(const TopoShape& shape, - const TopoShape& face, const gp_Dir& dir); +PartExport std::vector +findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir); /** * Check for intersection between the two shapes. Only solids are guaranteed to work properly diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 74db8e5697..bc4ae8718f 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -1153,14 +1153,14 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - TopoShape &makeElementPrismUntil(const TopoShape &base, - const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& upToFace, - const gp_Dir& direction, - PrismMode mode, - Standard_Boolean checkLimits = Standard_True, - const char *op=nullptr); + TopoShape& makeElementPrismUntil(const TopoShape& base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char* op = nullptr); /** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face * @@ -1180,21 +1180,21 @@ public: * @return Return the generated new shape. The TopoShape itself is not modified. */ TopoShape makeElementPrismUntil(const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& upToFace, - const gp_Dir& direction, - PrismMode mode, - Standard_Boolean checkLimits = Standard_True, - const char *op=nullptr) const + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char* op = nullptr) const { - return TopoShape(0,Hasher).makeElementPrismUntil(*this, - profile, - supportFace, - upToFace, - direction, - mode, - checkLimits, - op); + return TopoShape(0, Hasher).makeElementPrismUntil(*this, + profile, + supportFace, + upToFace, + direction, + mode, + checkLimits, + op); } diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index ad69144905..556560ea22 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4231,7 +4231,7 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, return makeElementShape(mkPrism, base, op); } - TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, +TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, const TopoShape& profile, const TopoShape& supportFace, const TopoShape& __uptoface, @@ -4302,8 +4302,8 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, if (remove_limits) { // Note: Using an unlimited face every time gives unnecessary failures for concave // faces - TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face, - Standard_False); + TopLoc_Location loc = face.Location(); + BRepAdaptor_Surface adapt(face, Standard_False); // use the placement of the adapter, not of the upToFace loc = TopLoc_Location(adapt.Trsf()); BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index f3fe0df1aa..28d37c9c45 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -87,6 +87,7 @@ short Feature::mustExecute() const return Part::Feature::mustExecute(); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) { if (shape.IsNull()) @@ -100,33 +101,30 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) return {}; } -// TODO REMOVE THIS METHOD AND DONT TRANSFER IN? -bool Feature::allowMultiSolid() const { - auto body = getFeatureBody(); - return body && !body->SingleSolid.getValue(); -} - TopoShape Feature::getSolid(const TopoShape& shape, bool force) { - if (shape.isNull()) + if (shape.isNull()) { throw Part::NullShapeException("Null shape"); + } int count = shape.countSubShapes(TopAbs_SOLID); - if(count>1) { - if(allowMultiSolid()) { + if (count > 1) { + if (getFeatureBody()) { auto res = shape; res.fixSolidOrientation(); return res; } - throw Base::RuntimeError("Result has multiple solids.\n" - "To allow multiple solids, please set 'SingleSolid' property of the body to false"); + throw Base::RuntimeError( + "Result has multiple solids.\n" + "To allow multiple solids, please set 'SingleSolid' property of the body to false"); } - if(count) { - auto res = shape.getSubTopoShape(TopAbs_SOLID,1); + if (count) { + auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); res.fixSolidOrientation(); return res; } - if (force) + if (force) { return TopoShape(); + } return shape; } @@ -206,29 +204,40 @@ const TopoDS_Shape& Feature::getBaseShape() const { return result; } -Part::TopoShape Feature::getBaseTopoShape(bool silent) const { +Part::TopoShape Feature::getBaseTopoShape(bool silent) const +{ Part::TopoShape result; const Part::Feature* BaseObject = getBaseObject(silent); - if (!BaseObject) + if (!BaseObject) { return result; + } - if(BaseObject != BaseFeature.getValue()) { - if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) || - BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())) - { - if(silent) + if (BaseObject != BaseFeature.getValue()) { + auto body = getFeatureBody(); + if (!body) { + if (silent) { return result; + } + throw Base::RuntimeError("Missing container body"); + } + if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) + || BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())) { + if (silent) { + return result; + } throw Base::ValueError("Base shape of shape binder cannot be used"); } } result = BaseObject->Shape.getShape(); - if(!silent) { - if (result.isNull()) + if (!silent) { + if (result.isNull()) { throw Base::ValueError("Base feature's TopoShape is invalid"); - if (!result.hasSubShape(TopAbs_SOLID)) + } + if (!result.hasSubShape(TopAbs_SOLID)) { throw Base::ValueError("Base feature's shape is not a solid"); + } } return result; } @@ -261,6 +270,7 @@ gp_Pln Feature::makePlnFromPlane(const App::DocumentObject* obj) return gp_Pln(gp_Pnt(pos.x,pos.y,pos.z), gp_Dir(normal.x,normal.y,normal.z)); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) { BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); @@ -273,8 +283,9 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj) { BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); - if (!builder.IsDone()) + if (!builder.IsDone()) { throw Base::CADKernelError("Feature: Could not create shape from base plane"); + } return TopoShape(obj->getID(), nullptr, builder.Shape()); } diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index fe2352fff6..8f901f9eeb 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -94,14 +94,16 @@ protected: /** * Get a solid of the given shape. If no solid is found an exception is raised. */ + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. static TopoDS_Shape getSolid(const TopoDS_Shape&); - TopoShape getSolid(const TopoShape &, bool force = true); - static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID ); + TopoShape getSolid(const TopoShape&, bool force = true); + static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID); /// Grab any point from the given face static const gp_Pnt getPointFromFace(const TopoDS_Face& f); /// Make a shape from a base plane (convenience method) static gp_Pln makePlnFromPlane(const App::DocumentObject* obj); + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj); static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj); }; diff --git a/src/Mod/PartDesign/App/FeatureAddSub.cpp b/src/Mod/PartDesign/App/FeatureAddSub.cpp index fd67c1cb0c..c500c69ad9 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.cpp +++ b/src/Mod/PartDesign/App/FeatureAddSub.cpp @@ -64,6 +64,7 @@ short FeatureAddSub::mustExecute() const return PartDesign::Feature::mustExecute(); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) const { if (this->Refine.getValue()) { @@ -87,7 +88,7 @@ TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const { if (this->Refine.getValue()) { TopoShape shape(oldShape); -// this->fixShape(shape); + // this->fixShape(shape); // Todo: Not clear that this is required return shape.makeElementRefine(); } return oldShape; diff --git a/src/Mod/PartDesign/App/FeatureAddSub.h b/src/Mod/PartDesign/App/FeatureAddSub.h index 30f73e48ee..19f0baf578 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.h +++ b/src/Mod/PartDesign/App/FeatureAddSub.h @@ -54,6 +54,7 @@ public: protected: Type addSubType{Additive}; + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; TopoShape refineShapeIfActive(const TopoShape&) const; }; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 805d278559..1cbd35949d 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -38,9 +38,9 @@ #include #include #include +#include "Mod/Part/App/TopoShapeOpCode.h" #include "FeatureExtrude.h" -#include "Mod/Part/App/TopoShapeOpCode.h" FC_LOG_LEVEL_INIT("PartDesign", true, true) @@ -129,6 +129,7 @@ bool FeatureExtrude::hasTaperedAngle() const fabs(TaperAngle2.getValue()) > Base::toRadians(Precision::Angular()); } +// 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, @@ -262,48 +263,57 @@ void FeatureExtrude::generatePrism(TopoShape& prism, if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { double Ltotal = L; double Loffset = 0.; - if (method == "ThroughAll") + if (method == "ThroughAll") { Ltotal = getThroughAllLength(); + } if (method == "TwoLengths") { // midplane makes no sense here Ltotal += L2; - if (reversed) + if (reversed) { Loffset = -L; - else if (midplane) + } + else if (midplane) { Loffset = -0.5 * (L2 + L); - else + } + else { Loffset = -L2; - } else if (midplane) - Loffset = -Ltotal/2; + } + } + 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) + } + 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 + // 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 &) { + 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()"; + str << "FeatureExtrusion: Internal error: Unknown method '" << method + << "' for generatePrism()"; throw Base::RuntimeError(str.str()); } - } void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism, @@ -415,7 +425,7 @@ void FeatureExtrude::setupObject() ProfileBased::setupObject(); } -App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions options) +App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions options) { bool makeface = options.testFlag(ExtrudeOption::MakeFace); bool fuse = options.testFlag(ExtrudeOption::MakeFuse); @@ -426,13 +436,17 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt // Validate parameters double L = Length.getValue(); - if ((method == "Length") && (L < Precision::Confusion())) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Length too small")); + 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")); + if (std::abs(L2) < Precision::Confusion()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Second length too small")); + } } Part::Feature* obj = nullptr; @@ -440,36 +454,48 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt try { obj = getVerifiedObject(); if (makeface) { - sketchshape = getVerifiedFace(); - } else { + sketchshape = getTopoShapeVerifiedFace(); + } + else { std::vector shapes; bool hasEdges = false; auto subs = Profile.getSubValues(false); - if (subs.empty()) + if (subs.empty()) { subs.emplace_back(""); + } bool failed = false; - for (auto & sub : subs) { - if (sub.empty() && subs.size()>1) + for (auto& sub : subs) { + if (sub.empty() && subs.size() > 1) { continue; + } TopoShape shape = Part::Feature::getTopoShape(obj, sub.c_str(), true); if (shape.isNull()) { - FC_ERR(getFullName() << ": failed to get profile shape " - << obj->getFullName() << "." << sub); + FC_ERR(getFullName() + << ": failed to get profile shape " << obj->getFullName() << "." << sub); failed = true; } hasEdges = hasEdges || shape.hasSubShape(TopAbs_EDGE); shapes.push_back(shape); } - if (failed) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape")); - if (hasEdges) + if (failed) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape")); + } + if (hasEdges) { sketchshape.makeElementWires(shapes); - else - sketchshape.makeElementCompound(shapes, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } + else { + sketchshape.makeElementCompound( + shapes, + nullptr, + TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } } - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); - } catch (const Standard_Failure& e) { + } + catch (const Standard_Failure& e) { return new App::DocumentObjectExecReturn(e.GetMessageString()); } @@ -504,9 +530,11 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt double factor = fabs(dir * gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z)); // factor would be zero if vectors are orthogonal - if (factor < Precision::Confusion()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Creation failed because direction is orthogonal to sketch's normal vector")); + if (factor < Precision::Confusion()) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", + "Creation failed because direction is orthogonal to sketch's normal vector")); + } // perform the length correction if not along custom vector if (AlongSketchNormal.getValue()) { @@ -520,20 +548,22 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt dir.Transform(invTrsf); - if (sketchshape.isNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Creating a face from sketch failed")); + if (sketchshape.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); + } sketchshape.move(invObjLoc); - TopoShape prism(0,getDocument()->getStringHasher()); + TopoShape prism(0); if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") { // Note: This will return an unlimited planar face if support is a datum plane - TopoShape supportface = getSupportFace(); + TopoShape supportface = getTopoShapeSupportFace(); supportface.move(invObjLoc); - if (Reversed.getValue()) + if (Reversed.getValue()) { dir.Reverse(); + } // Find a valid face or datum plane to extrude up to TopoShape upToFace; @@ -544,13 +574,18 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt getUpToFace(upToFace, base, supportface, sketchshape, method, dir); addOffsetToFace(upToFace, dir, Offset.getValue()); - if (!supportface.hasSubShape(TopAbs_WIRE)) + if (!supportface.hasSubShape(TopAbs_WIRE)) { supportface = TopoShape(); + } if (legacyPocket) { - auto mode = base.isNull() ? TopoShape::PrismMode::None - : TopoShape::PrismMode::CutFromBase; - prism = base.makeElementPrismUntil(sketchshape, supportface, upToFace, - dir, mode, false/*CheckUpToFaceLimits.getValue()*/); + auto mode = + base.isNull() ? TopoShape::PrismMode::None : TopoShape::PrismMode::CutFromBase; + prism = base.makeElementPrismUntil(sketchshape, + supportface, + upToFace, + 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 @@ -561,59 +596,88 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt // And the really expensive way to get the SubShape... try { - TopoShape result(0,getDocument()->getStringHasher()); - if (base.isNull()) + TopoShape result(0); + if (base.isNull()) { result = prism; - else - result.makeElementCut({base,prism}); + } + else { + result.makeElementCut({base, prism}); + } 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!")); + } + catch (Standard_Failure&) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!")); } - if (getAddSubType() == Additive) + if (getAddSubType() == Additive) { prism = base.makeElementFuse(this->AddSubShape.getShape()); - else + } + else { prism = refineShapeIfActive(prism); + } this->Shape.setValue(getSolid(prism)); return App::DocumentObject::StdReturn; } - prism.makeElementPrismUntil(base, sketchshape, supportface, upToFace, - dir, TopoShape::PrismMode::None, false /*CheckUpToFaceLimits.getValue()*/); - } else { + prism.makeElementPrismUntil(base, + sketchshape, + supportface, + upToFace, + dir, + TopoShape::PrismMode::None, + false /*CheckUpToFaceLimits.getValue()*/); + } + else { Part::ExtrusionParameters params; params.dir = dir; params.solid = makeface; params.taperAngleFwd = this->TaperAngle.getValue() * M_PI / 180.0; params.taperAngleRev = this->TaperAngle2.getValue() * M_PI / 180.0; if (L2 == 0.0 && Midplane.getValue()) { - params.lengthFwd = L/2; - params.lengthRev = L/2; - if (params.taperAngleRev == 0.0) + params.lengthFwd = L / 2; + params.lengthRev = L / 2; + if (params.taperAngleRev == 0.0) { params.taperAngleRev = params.taperAngleFwd; - } else { + } + } + else { params.lengthFwd = L; params.lengthRev = L2; } if (std::fabs(params.taperAngleFwd) >= Precision::Angular() - || std::fabs(params.taperAngleRev) >= Precision::Angular() ) { + || std::fabs(params.taperAngleRev) >= Precision::Angular()) { if (fabs(params.taperAngleFwd) > M_PI * 0.5 - Precision::Angular() - || fabs(params.taperAngleRev) > M_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()) + || fabs(params.taperAngleRev) > M_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); - 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()); + 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()); + } } // set the additive shape property for later usage in e.g. pattern @@ -624,9 +688,9 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt prism.Tag = -this->getID(); // Let's call algorithm computing a fuse operation: - TopoShape result(0,getDocument()->getStringHasher()); + TopoShape result(0); try { - const char *maker; + const char* maker; switch (getAddSubType()) { case Subtractive: maker = Part::OpCodes::Cut; @@ -634,26 +698,31 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt default: maker = Part::OpCodes::Fuse; } - result.makeElementBoolean(maker, {base,prism}); - }catch(Standard_Failure &){ - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Fusion with base feature failed")); + result.makeElementBoolean(maker, {base, prism}); + } + catch (Standard_Failure&) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed")); } // we have to get the solids (fuse sometimes creates compounds) auto solRes = this->getSolid(result); // lets check if the result is a solid - if (solRes.isNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Resulting shape is not a solid")); + if (solRes.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + } solRes = refineShapeIfActive(solRes); this->Shape.setValue(getSolid(solRes)); - } else if (prism.hasSubShape(TopAbs_SOLID)) { - if (prism.countSubShapes(TopAbs_SOLID) > 1) + } + else if (prism.hasSubShape(TopAbs_SOLID)) { + if (prism.countSubShapes(TopAbs_SOLID) > 1) { prism.makeElementFuse(prism.getSubTopoShapes(TopAbs_SOLID)); + } prism = refineShapeIfActive(prism); this->Shape.setValue(getSolid(prism)); - } else { + } + else { prism = refineShapeIfActive(prism); this->Shape.setValue(prism); } @@ -664,15 +733,17 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { - if (std::string(e.GetMessageString()) == "TopoDS::Face") - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Could not create face from sketch.\n" - "Intersecting sketch entities or multiple faces in a sketch are not allowed.")); - else + if (std::string(e.GetMessageString()) == "TopoDS::Face") { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", + "Could not create face from sketch.\n" + "Intersecting sketch entities or multiple faces in a sketch are not allowed.")); + } + else { return new App::DocumentObjectExecReturn(e.GetMessageString()); + } } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } - } diff --git a/src/Mod/PartDesign/App/FeatureExtrude.h b/src/Mod/PartDesign/App/FeatureExtrude.h index 9ba2945ee8..22ed2741e6 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.h +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -68,7 +68,8 @@ protected: bool hasTaperedAngle() const; /// Options for buildExtrusion() - enum class ExtrudeOption { + enum class ExtrudeOption + { MakeFace = 1, MakeFuse = 2, LegacyPocket = 4, @@ -77,7 +78,7 @@ protected: using ExtrudeOptions = Base::Flags; - App::DocumentObjectExecReturn *buildExtrusion(ExtrudeOptions options); + App::DocumentObjectExecReturn* buildExtrusion(ExtrudeOptions options); /** * Generates an extrusion of the input sketchshape and stores it in the given \a prism diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index 3e22e81077..cab6b2f754 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -67,6 +67,13 @@ Pad::Pad() Length2.setConstraints(nullptr); } +#ifdef FC_USE_TNP_FIX + +App::DocumentObjectExecReturn* Pad::execute() +{ + return buildExtrusion(ExtrudeOption::MakeFace | ExtrudeOption::MakeFuse); +} +#else App::DocumentObjectExecReturn *Pad::execute() { double L = Length.getValue(); @@ -262,3 +269,4 @@ App::DocumentObjectExecReturn *Pad::execute() } } +#endif diff --git a/src/Mod/PartDesign/App/FeaturePrimitive.cpp b/src/Mod/PartDesign/App/FeaturePrimitive.cpp index 1e8b743a8f..47b9ffe277 100644 --- a/src/Mod/PartDesign/App/FeaturePrimitive.cpp +++ b/src/Mod/PartDesign/App/FeaturePrimitive.cpp @@ -45,6 +45,7 @@ #include "FeaturePrimitive.h" #include "FeaturePy.h" +#include "Mod/Part/App/TopoShapeOpCode.h" using namespace PartDesign; @@ -65,19 +66,34 @@ FeaturePrimitive::FeaturePrimitive() Part::AttachExtension::initExtension(this); } -App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitiveShape) +App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitive) { try { //transform the primitive in the correct coordinance FeatureAddSub::execute(); //if we have no base we just add the standard primitive shape +#ifdef FC_USE_TNP_FIX + TopoShape primitiveShape; + primitiveShape.setShape(primitive); + + TopoShape base; + try { + // if we have a base shape we need to make sure that it does not get our transformation + // to + base = getBaseTopoShape().moved(getLocation().Inverted()); + primitiveShape.Tag = -this->getID(); + } + +#else + auto primitiveShape = primitive; TopoDS_Shape base; try { //if we have a base shape we need to make sure that it does not get our transformation to BRepBuilderAPI_Transform trsf(getBaseShape(), getLocation().Transformation().Inverted(), true); base = trsf.Shape(); } +#endif catch (const Base::Exception&) { //as we use this for preview we can add it even if useless for subtractive @@ -90,14 +106,45 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri return App::DocumentObject::StdReturn; } +#ifdef FC_USE_TNP_FIX + AddSubShape.setValue(primitiveShape); + TopoShape boolOp(0); + + const char* maker; + switch (getAddSubType()) { + case Additive: + maker = Part::OpCodes::Fuse; + break; + case Subtractive: + maker = Part::OpCodes::Cut; + break; + default: + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Unknown operation type")); + } + try { + boolOp.makeElementBoolean(maker, {base, primitiveShape}); + } + catch (Standard_Failure& e) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Failed to perform boolean operation")); + } + boolOp = this->getSolid(boolOp); + // lets check if the result is a solid + if (boolOp.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + } +#else + TopoDS_Shape boolOp; if (getAddSubType() == FeatureAddSub::Additive) { BRepAlgoAPI_Fuse mkFuse(base, primitiveShape); if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Adding the primitive failed")); // we have to get the solids (fuse sometimes creates compounds) - TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape()); + boolOp = this->getSolid(mkFuse.Shape()); // lets check if the result is a solid if (boolOp.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); @@ -106,10 +153,6 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri if (solidCount > 1) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); } - - boolOp = refineShapeIfActive(boolOp); - Shape.setValue(getSolid(boolOp)); - AddSubShape.setValue(primitiveShape); } else if (getAddSubType() == FeatureAddSub::Subtractive) { @@ -117,7 +160,7 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Subtracting the primitive failed")); // we have to get the solids (fuse sometimes creates compounds) - TopoDS_Shape boolOp = this->getSolid(mkCut.Shape()); + boolOp = this->getSolid(mkCut.Shape()); // lets check if the result is a solid if (boolOp.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); @@ -126,13 +169,11 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri if (solidCount > 1) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); } - - boolOp = refineShapeIfActive(boolOp); - Shape.setValue(getSolid(boolOp)); - AddSubShape.setValue(primitiveShape); } - - +#endif + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + AddSubShape.setValue(primitiveShape); } catch (Standard_Failure& e) { diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index b89d14e5c7..1673d65e45 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -165,6 +165,29 @@ Part::Feature* ProfileBased::getVerifiedObject(bool silent) const { return static_cast(result); } +#ifdef FC_USE_TNP_FIX +TopoShape ProfileBased::getProfileShape() const +{ + TopoShape shape; + const auto& subs = Profile.getSubValues(); + auto profile = Profile.getValue(); + if (subs.empty()) { + shape = Part::Feature::getTopoShape(profile); + } + else { + std::vector shapes; + for (auto& sub : subs) { + shapes.push_back( + Part::Feature::getTopoShape(profile, sub.c_str(), /* needSubElement */ true)); + } + shape = TopoShape(shape.Tag).makeElementCompound(shapes); + } + if (shape.isNull()) { + throw Part::NullShapeException("Linked shape object is empty"); + } + return shape; +} +#else Part::TopoShape ProfileBased::getProfileShape() const { auto shape = getTopoShape(Profile.getValue()); @@ -176,7 +199,8 @@ Part::TopoShape ProfileBased::getProfileShape() const } return shape; } - +#endif +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { App::DocumentObject* result = Profile.getValue(); @@ -256,134 +280,166 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { } TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent, - bool doFit, - bool allowOpen, - const App::DocumentObject *profile, - const std::vector &_subs) const + bool doFit, + bool allowOpen, + const App::DocumentObject* profile, + const std::vector& _subs) const { auto obj = profile ? profile : Profile.getValue(); - if(!obj || !obj->getNameInDocument()) { - if(silent) + if (!obj || !obj->getNameInDocument()) { + if (silent) { return TopoShape(); + } throw Base::ValueError("No profile linked"); } - const auto &subs = profile ? _subs : Profile.getSubValues(); + const auto& subs = profile ? _subs : Profile.getSubValues(); try { TopoShape shape; - if(AllowMultiFace.getValue()) { - if (subs.empty()) + if (AllowMultiFace.getValue()) { + if (subs.empty()) { shape = Part::Feature::getTopoShape(obj); + } else { std::vector shapes; - for (auto &sub : subs) { - auto subshape = Part::Feature::getTopoShape( - obj, sub.c_str(), /*needSubElement*/true); - if (subshape.isNull()) - FC_THROWM(Base::CADKernelError, "Sub shape not found: " << - obj->getFullName() << "." << sub); + for (auto& sub : subs) { + auto subshape = + Part::Feature::getTopoShape(obj, sub.c_str(), /*needSubElement*/ true); + if (subshape.isNull()) { + FC_THROWM(Base::CADKernelError, + "Sub shape not found: " << obj->getFullName() << "." << sub); + } shapes.push_back(subshape); } shape.makeElementCompound(shapes); } - } else { - std::string sub; - if(!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { - if(!subs.empty()) - sub = subs[0]; - } - shape = Part::Feature::getTopoShape(obj,sub.c_str(),!sub.empty()); } - if(shape.isNull()) { - if (silent) + else { + std::string sub; + if (!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { + if (!subs.empty()) { + sub = subs[0]; + } + } + shape = Part::Feature::getTopoShape(obj, sub.c_str(), !sub.empty()); + } + if (shape.isNull()) { + if (silent) { return shape; + } throw Base::CADKernelError("Linked shape object is empty"); } TopoShape openshape; - if(!shape.hasSubShape(TopAbs_FACE)) { + if (!shape.hasSubShape(TopAbs_FACE)) { try { - if(!shape.hasSubShape(TopAbs_WIRE)) + if (!shape.hasSubShape(TopAbs_WIRE)) { shape = shape.makeElementWires(); - if(shape.hasSubShape(TopAbs_WIRE)) { + } + if (shape.hasSubShape(TopAbs_WIRE)) { shape.Hasher = getDocument()->getStringHasher(); if (allowOpen) { std::vector openwires; std::vector wires; - for (auto &wire : shape.getSubTopoShapes(TopAbs_WIRE)) { - if (!wire.isClosed()) + for (auto& wire : shape.getSubTopoShapes(TopAbs_WIRE)) { + if (!wire.isClosed()) { openwires.push_back(wire); - else + } + else { wires.push_back(wire); + } } if (openwires.size()) { - openshape.makeElementCompound(openwires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); - if (wires.empty()) + openshape.makeElementCompound( + openwires, + nullptr, + TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + if (wires.empty()) { shape = TopoShape(); - else - shape.makeElementCompound(wires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + } + else { + shape.makeElementCompound( + wires, + nullptr, + TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + } } } if (!shape.isNull()) { - if (AllowMultiFace.getValue()) - shape = shape.makeElementFace(); // default to use FaceMakerBullseye - else + if (AllowMultiFace.getValue()) { + shape = shape.makeElementFace(); // default to use FaceMakerBullseye + } + else { shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese"); + } } } - } catch (const Base::Exception &) { - if (silent) + } + catch (const Base::Exception&) { + if (silent) { return TopoShape(); + } throw; - } catch (const Standard_Failure &) { - if (silent) + } + catch (const Standard_Failure&) { + if (silent) { return TopoShape(); + } throw; } } int count = shape.countSubShapes(TopAbs_FACE); - if(!count && !allowOpen) { - if(silent) + if (!count && !allowOpen) { + if (silent) { return TopoShape(); + } throw Base::CADKernelError("Cannot make face from profile"); } -// if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion() -// || std::abs(InnerFit.getValue()) > Precision::Confusion())) { -// -// if (!shape.isNull()) -// shape = shape.makEOffsetFace(Fit.getValue(), -// InnerFit.getValue(), -// static_cast(FitJoin.getValue()), -// static_cast(InnerFitJoin.getValue())); -// if (!openshape.isNull()) -// openshape.makEOffset2D(Fit.getValue()); -// } + // Toponaming April 2024: This appears to be new feature, not TNP: + // if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion() + // || std::abs(InnerFit.getValue()) > Precision::Confusion())) { + // + // if (!shape.isNull()) + // shape = shape.makEOffsetFace(Fit.getValue(), + // InnerFit.getValue(), + // static_cast(FitJoin.getValue()), + // static_cast(InnerFitJoin.getValue())); + // if (!openshape.isNull()) + // openshape.makEOffset2D(Fit.getValue()); + // } if (!openshape.isNull()) { - if (shape.isNull()) + if (shape.isNull()) { shape = openshape; - else + } + else { shape.makeElementCompound({shape, openshape}); + } } - if(count>1) { - if(AllowMultiFace.getValue() -// || allowMultiSolid() - || obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) + if (count > 1) { + if (AllowMultiFace.getValue() + || obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) { return shape; + } FC_WARN("Found more than one face from profile"); } - if (!openshape.isNull()) + if (!openshape.isNull()) { return shape; - if (count) - return shape.getSubTopoShape(TopAbs_FACE,1); + } + if (count) { + return shape.getSubTopoShape(TopAbs_FACE, 1); + } return shape; - }catch (Standard_Failure &) { - if(silent) + } + catch (Standard_Failure&) { + if (silent) { return TopoShape(); + } throw; } } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector ProfileBased::getProfileWires() const { std::vector result; @@ -422,25 +478,29 @@ std::vector ProfileBased::getProfileWires() const { return result; } -std::vector ProfileBased::getTopoShapeProfileWires() const { +std::vector ProfileBased::getTopoShapeProfileWires() const +{ // shape copy is a workaround for an obscure OCC bug which leads to empty // tessellations for some faces. Making an explicit copy of the linked // shape seems to fix it. The error mostly happens when re-computing the // shape but sometimes also for the first time auto shape = getProfileShape().makeElementCopy(); - if(shape.hasSubShape(TopAbs_WIRE)) + if (shape.hasSubShape(TopAbs_WIRE)) { return shape.getSubTopoShapes(TopAbs_WIRE); + } auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE); - if(wires.empty()) + if (wires.empty()) { throw Part::NullShapeException("Linked shape object is not a wire"); + } return wires; } // Note: We cannot return a reference, because it will become Null. // Not clear where, because we check for IsNull() here, but as soon as it is passed out of // this method, it becomes null! +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. const TopoDS_Face ProfileBased::getSupportFace() const { const Part::Part2DObject* sketch = getVerifiedSketch(true); @@ -453,7 +513,8 @@ const TopoDS_Face ProfileBased::getSupportFace() const TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const { - if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { + if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace + && sketch->AttachmentSupport.getValue()) { const auto& AttachmentSupport = sketch->AttachmentSupport; App::DocumentObject* ref = AttachmentSupport.getValue(); @@ -469,20 +530,24 @@ TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const // get the selected sub shape (a Face) const Part::TopoShape& shape = part->Shape.getShape(); - if (shape.getShape().IsNull()) + if (shape.getShape().IsNull()) { throw Base::ValueError("Sketch support shape is empty!"); + } TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); - if (sh.IsNull()) + if (sh.IsNull()) { throw Base::ValueError("Null shape in SketchBased::getSupportFace()!"); + } const TopoDS_Face face = TopoDS::Face(sh); - if (face.IsNull()) + if (face.IsNull()) { throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); + } BRepAdaptor_Surface adapt(face); - if (adapt.GetType() != GeomAbs_Plane) + if (adapt.GetType() != GeomAbs_Plane) { throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); + } return face; } @@ -502,31 +567,39 @@ TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const return face; } -TopoShape ProfileBased::getTopoShapeSupportFace() const { +TopoShape ProfileBased::getTopoShapeSupportFace() const +{ TopoShape shape; const Part::Part2DObject* sketch = getVerifiedSketch(true); - if (!sketch) - shape = getVerifiedFace(); - else if (sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { - const auto &Support = sketch->AttachmentSupport; + if (!sketch) { + shape = getTopoShapeVerifiedFace(); + } + else if (sketch->MapMode.getValue() == Attacher::mmFlatFace + && sketch->AttachmentSupport.getValue()) { + const auto& Support = sketch->AttachmentSupport; App::DocumentObject* ref = Support.getValue(); shape = Part::Feature::getTopoShape( - ref, Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", true); + ref, + Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", + true); } if (!shape.isNull()) { if (shape.shapeType(true) != TopAbs_FACE) { - if (!shape.hasSubShape(TopAbs_FACE)) + if (!shape.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); + } shape = shape.getSubTopoShape(TopAbs_FACE, 1); } gp_Pln pln; - if (!shape.findPlane(pln)) + if (!shape.findPlane(pln)) { throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); + } return shape; } - if (!sketch) + if (!sketch) { throw Base::RuntimeError("No planar support"); + } return Feature::makeShapeFromPlane(sketch); } @@ -589,6 +662,26 @@ void ProfileBased::onChanged(const App::Property* prop) FeatureAddSub::onChanged(prop); } +void ProfileBased::getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace) +{ + App::DocumentObject* ref = refFace.getValue(); + + if (!ref) { + throw Base::ValueError("SketchBased: No face selected"); + } + + if (ref->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) { + upToFace = makeShapeFromPlane(ref); + return; + } + + const auto& subs = refFace.getSubValues(); + upToFace = Part::Feature::getTopoShape(ref, subs.size() ? subs[0].c_str() : nullptr, true); + if (!upToFace.hasSubShape(TopAbs_FACE)) { + throw Base::ValueError("SketchBased: Up to face: Failed to extract face"); + } +} + void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace) { @@ -621,6 +714,7 @@ void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::Property throw Base::ValueError("SketchBased: Failed to extract face"); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. void ProfileBased::getUpToFace(TopoDS_Face& upToFace, const TopoDS_Shape& support, const TopoDS_Shape& sketchshape, @@ -722,46 +816,59 @@ void ProfileBased::getUpToFace(TopoShape& upToFace, gp_Dir& dir) { if ((method == "UpToLast") || (method == "UpToFirst")) { - std::vector cfaces = Part::findAllFacesCutBy(support, sketchshape, dir); - if (cfaces.empty()) + std::vector cfaces = + Part::findAllFacesCutBy(support, sketchshape, dir); + if (cfaces.empty()) { throw Base::ValueError("SketchBased: No faces found in this direction"); + } // Find nearest/furthest face - std::vector::const_iterator it, it_near, it_far; + std::vector::const_iterator it, it_near, it_far; it_near = it_far = cfaces.begin(); - for (it = cfaces.begin(); it != cfaces.end(); it++) - if (it->distsq > it_far->distsq) + for (it = cfaces.begin(); it != cfaces.end(); it++) { + if (it->distsq > it_far->distsq) { it_far = it; - else if (it->distsq < it_near->distsq) + } + else if (it->distsq < it_near->distsq) { it_near = it; + } + } upToFace = (method == "UpToLast" ? it_far->face : it_near->face); - } else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) + } + else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) { dir = -dir; + } if (upToFace.shapeType(true) != TopAbs_FACE) { - if (!upToFace.hasSubShape(TopAbs_FACE)) + if (!upToFace.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("SketchBased: Up to face: No face found"); + } upToFace = upToFace.getSubTopoShape(TopAbs_FACE, 1); } TopoDS_Face face = TopoDS::Face(upToFace.getShape()); // Check that the upToFace does not intersect the sketch face and - // is not parallel to the extrusion direction (for simplicity, supportface is used instead of sketchshape) + // is not parallel to the extrusion direction (for simplicity, supportface is used instead of + // sketchshape) BRepAdaptor_Surface adapt1(TopoDS::Face(supportface.getShape())); BRepAdaptor_Surface adapt2(face); if (adapt2.GetType() == GeomAbs_Plane) { - if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) - throw Base::ValueError("SketchBased: Up to face: Must not be parallel to extrusion direction!"); + if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) { + throw Base::ValueError( + "SketchBased: Up to face: Must not be parallel to extrusion direction!"); + } } // We must measure from sketchshape, not supportface, here BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face); - if (distSS.Value() < Precision::Confusion()) + if (distSS.Value() < Precision::Confusion()) { throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!"); + } } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset) { // Move the face in the extrusion direction diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index be4204d334..4db8246c6c 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -97,6 +97,7 @@ public: * silently returns nullptr, otherwise throw a Base::Exception. * Default is false. */ + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape getVerifiedFace(bool silent = false) const; /** @@ -110,18 +111,20 @@ public: * @param subs: optional profile sub-object names, if not given then use 'Profile' property */ TopoShape getTopoShapeVerifiedFace(bool silent = false, - bool doFit = true, - bool allowOpen = false, - const App::DocumentObject *profile = nullptr, - const std::vector &subs = {}) const; + bool doFit = true, + bool allowOpen = false, + const App::DocumentObject* profile = nullptr, + const std::vector& subs = {}) const; /// Returns the wires the sketch is composed of + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector getProfileWires() const; std::vector getTopoShapeProfileWires() const; /// Returns the face of the sketch support (if any) const TopoDS_Face getSupportFace() const; + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoShape getTopoShapeSupportFace() const; Base::Vector3d getProfileNormal() const; @@ -147,8 +150,7 @@ protected: TopoDS_Face getSupportFace(const App::PropertyLinkSub& link) const; /// Extract a face from a given LinkSub - static void getFaceFromLinkSub(TopoDS_Face& upToFace, - const App::PropertyLinkSub& refFace); + static void getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace); /// Find a valid face to extrude up to static void getUpToFace(TopoDS_Face& upToFace, @@ -158,12 +160,9 @@ protected: const gp_Dir& dir); /// Add an offset to the face - static void addOffsetToFace(TopoDS_Face& upToFace, - const gp_Dir& dir, - double offset); + static void addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset); /// Extract a face from a given LinkSub - static void getUpToFaceFromLinkSub(TopoShape& upToFace, - const App::PropertyLinkSub& refFace); + static void getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace); /// Find a valid face to extrude up to static void getUpToFace(TopoShape& upToFace, diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 2d1e1b7641..4ebe85e5c3 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -261,6 +261,20 @@ void SketchObject::buildShape() if(GeometryFacade::getConstruction(geo)) continue; if (geo->isDerivedFrom(Part::GeomPoint::getClassTypeId())) { +#ifdef FC_USE_TNP_FIX + Part::TopoShape vertex(TopoDS::Vertex(geo->toShape())); + int idx = getVertexIndexGeoPos(i-1, Sketcher::PointPos::start); + std::string name = convertSubName(Data::IndexedName::fromConst("Vertex", idx+1), false); + vertex.setElementName(Data::IndexedName::fromConst("Vertex", 1), + Data::MappedName::fromRawData(name.c_str()),0L); + vertices.push_back(vertex); + vertices.back().copyElementMap(vertex, Part::OpCodes::Sketch); + } else { + auto indexedName = Data::IndexedName::fromConst("Edge", i); + shapes.push_back(getEdge(geo,convertSubName(indexedName, false).c_str())); + } + +#else vertices.emplace_back(TopoDS::Vertex(geo->toShape())); int idx = getVertexIndexGeoPos(i-1, PointPos::start); std::string name = convertSubName(Data::IndexedName::fromConst("Vertex", idx+1), false); @@ -269,6 +283,7 @@ void SketchObject::buildShape() } else shapes.push_back(getEdge(geo,convertSubName( Data::IndexedName::fromConst("Edge", i), false).c_str())); +#endif } // FIXME: Commented since ExternalGeometryFacade is not added @@ -280,11 +295,14 @@ void SketchObject::buildShape() // shapes.push_back(getEdge(geo, convertSubName( // Data::IndexedName::fromConst("ExternalEdge", i-1), false).c_str())); // } - if(shapes.empty() && vertices.empty()) - Shape.setValue(Part::TopoShape()); - else if (vertices.empty()) { + if(shapes.empty() && vertices.empty()) { + Shape.setValue(Part::TopoShape()); + return; + } + Part::TopoShape result(0); + if (vertices.empty()) { // Notice here we supply op code Part::OpCodes::Sketch to makEWires(). - Shape.setValue(Part::TopoShape().makeElementWires(shapes,Part::OpCodes::Sketch)); + result.makeElementWires(shapes,Part::OpCodes::Sketch); } else { std::vector results; if (!shapes.empty()) { @@ -302,8 +320,10 @@ void SketchObject::buildShape() results.push_back(wire); } results.insert(results.end(), vertices.begin(), vertices.end()); - Shape.setValue(Part::TopoShape().makeElementCompound(results, Part::OpCodes::Sketch)); + result.makeElementCompound(results, Part::OpCodes::Sketch); } + result.Tag = getID(); + Shape.setValue(result); } static const char *hasSketchMarker(const char *name) { diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index c9361b9f4b..4c65b746a5 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -160,8 +160,9 @@ bool matchStringsWithoutClause(std::string first, std::string second, const std: /** * Check to see if the elementMap in a shape contains all the names in a list - * The "Duplicate" clause in a name - ";Dnnn" can contain a random number, so we need to - * exclude those. + * There are some sections of the name that can vary due to random numbers or + * memory addresses, so we use a regex to exclude those sections while still + * validating that the name exists and is the correct type. * @param shape The Shape * @param names The vector of names * @return An assertion usable by the gtest framework @@ -178,14 +179,16 @@ testing::AssertionResult elementsMatch(const TopoShape& shape, return matchStringsWithoutClause( element.name.toString(), name, - "(;D|;:H|;K)-?[a-fA-F0-9]+(:[0-9]+)?"); + "(;D|;:H|;K)-?[a-fA-F0-9]+(:[0-9]+)?|(\\(.*?\\))?"); // ;D ;:H and ;K are the sections of an encoded name for // Duplicate, Tag and a Face name in slices. All three of these // can vary from run to run or platform to platform, as they are // based on either explicit random numbers or memory addresses. // Thus we remove the value from comparisons and just check that // they exist. The full form could be something like ;:He59:53 - // which is what we match and remove. + // which is what we match and remove. We also pull out any + // subexpressions wrapped in parens to keep the parse from + // becoming too complex. }) == elements.end()) { return testing::AssertionFailure() << mappedElementVectorToString(elements); diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 3e4516624f..b80873bf0d 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2381,38 +2381,30 @@ TEST_F(TopoShapeExpansionTest, makeElementPrism) ); } -// TODO: This code was written in Feb 2024 as part of the toponaming project, but appears to be -// unused. It is potentially useful if debugged. -// -// TEST_F(TopoShapeExpansionTest, makeElementPrismUntil) -//{ -// // Arrange -// auto [cube1, cube2] = CreateTwoCubes(); -// TopoShape cube1TS {cube1, 1L}; -// auto subFaces = cube1TS.getSubShapes(TopAbs_FACE); -// auto subTopoFaces = cube1TS.getSubTopoShapes(TopAbs_FACE); -// subTopoFaces[0].Tag = 2L; -// subTopoFaces[1].Tag = 3L; -// auto tr {gp_Trsf()}; -// auto direction = gp_Vec(gp_XYZ(0.0, 0.0, 0.25)); -// tr.SetTranslation(direction); -// auto support = subFaces[0].Moved(TopLoc_Location(tr)); -// auto upto = support.Moved(TopLoc_Location(tr)); -// // Act -// TopoShape result = cube1TS.makeElementPrismUntil(subTopoFaces[0], -// TopoShape(support, 4L), -// TopoShape(upto, 5L), -// direction, -// TopoShape::PrismMode::CutFromBase); -// auto elements = elementMap(result); -// Base::BoundBox3d bb = result.getBoundBox(); -// // Assert shape is correct -// EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, -0.5, 0.0, 1.5, 1.0, 1.0))); -// EXPECT_FLOAT_EQ(getVolume(result.getShape()), 2); -// // Assert elementMap is correct -// EXPECT_TRUE(elementsMatch(result, -// {"Edge1;:G;XTR;:H2:7,F",})); -//} +TEST_F(TopoShapeExpansionTest, makeElementPrismUntil) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape cube1TS {cube1, 1L}; + auto subTopoFaces = cube1TS.getSubTopoShapes(TopAbs_FACE); + auto direction = gp_Vec(gp_XYZ(0.0, 0.0, 1)); + // Act + TopoShape result = cube1TS.makeElementPrismUntil(subTopoFaces[4], + subTopoFaces[4], + subTopoFaces[5], + direction, + TopoShape::PrismMode::FuseWithBase); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0))); + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1); + // Assert elementMap is correct + // EXPECT_EQ(result.getElementMapSize(),26); // Todo: Sometimes too big in TNP code. Why? + EXPECT_EQ(result.countSubElements("Edge"), 12); + EXPECT_EQ(result.countSubElements("Face"), 6); + EXPECT_EQ(result.countSubElements("Vertex"), 8); +} TEST_F(TopoShapeExpansionTest, makeElementFilledFace) {