From 231a638fc78530b2d2d1255bf959e48c9e5a46f5 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 7 Apr 2024 11:47:13 -0400 Subject: [PATCH 1/4] TopoShape/Part: Bring in PartDesign dependencies --- src/Mod/Part/App/PartFeature.cpp | 41 ++ src/Mod/Part/App/PartFeature.h | 4 + src/Mod/Part/App/TopoShape.h | 54 ++- src/Mod/Part/App/TopoShapeExpansion.cpp | 371 +++++++++--------- src/Mod/PartDesign/App/Feature.cpp | 39 ++ src/Mod/PartDesign/App/Feature.h | 2 + src/Mod/PartDesign/App/FeatureAddSub.cpp | 10 + src/Mod/PartDesign/App/FeatureAddSub.h | 1 + src/Mod/PartDesign/App/FeatureExtrude.cpp | 327 +++++++++++++++ src/Mod/PartDesign/App/FeatureExtrude.h | 23 ++ src/Mod/PartDesign/App/FeatureSketchBased.cpp | 242 ++++++++++++ src/Mod/PartDesign/App/FeatureSketchBased.h | 37 +- 12 files changed, 935 insertions(+), 216 deletions(-) diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index a359982cb0..e569305e36 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -1359,6 +1359,47 @@ std::vector Part::findAllFacesCutBy( return result; } +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); + 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; + BRepIntCurveSurface_Inter mkSection; + // TODO: Less precision than Confusion() should be OK? + + 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 + + // 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.Value().IsOpposite(dir, Precision::Confusion())) + continue; // wrong side of face (opposite to extrusion direction) + + cutFaces newF; + newF.face = mkSection.Face(); + newF.face.mapSubElement(shape); + newF.distsq = dsq; + result.push_back(newF); + } + + return result; +} + bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& second, const bool quick, const bool touch_is_intersection) { diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 56a482d57a..54bd009a69 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -220,6 +220,10 @@ PartExport std::vector findAllFacesCutBy(const TopoDS_Shape& shape, const TopoDS_Shape& 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 * There are two modes: diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index a7f0ebe048..74db8e5697 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -1153,16 +1153,14 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - // TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be - // unused. It is potentially useful if debugged. -// 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 * @@ -1181,25 +1179,23 @@ public: * * @return Return the generated new shape. The TopoShape itself is not modified. */ - // TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be - // unused. It is potentially useful if debugged. -// 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 -// { -// return TopoShape(0,Hasher).makeElementPrismUntil(*this, -// profile, -// supportFace, -// upToFace, -// direction, -// mode, -// checkLimits, -// op); -// } + 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 + { + return TopoShape(0,Hasher).makeElementPrismUntil(*this, + profile, + supportFace, + upToFace, + direction, + mode, + checkLimits, + op); + } /* Make a shell or solid by sweeping profile wire along a spine diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index cce20f8e4a..ad69144905 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4231,192 +4231,191 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, return makeElementShape(mkPrism, base, op); } -// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be -// unused. It is potentially useful if debugged. -// TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, -// const TopoShape& profile, -// const TopoShape& supportFace, -// const TopoShape& __uptoface, -// const gp_Dir& direction, -// PrismMode Mode, -// Standard_Boolean checkLimits, -// const char* op) -//{ -// if (!op) { -// op = Part::OpCodes::Prism; -// } -// -// BRepFeat_MakePrism PrismMaker; -// -// TopoShape _uptoface(__uptoface); -// if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE -// && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { -// // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) -// // then the algorithm expects that the 'NaturalRestriction' flag is set in order -// // to work as expected. -// BRep_Builder builder; -// _uptoface = _uptoface.makeElementCopy(); -// builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); -// } -// -// TopoShape uptoface(_uptoface); -// TopoShape base(_base); -// -// if (base.isNull()) { -// Mode = PrismMode::None; -// base = profile; -// } -// -// // Check whether the face has limits or not. Unlimited faces have no wire -// // Note: Datum planes are always unlimited -// if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { -// TopoDS_Face face = TopoDS::Face(uptoface.getShape()); -// bool remove_limits = false; -// // Remove the limits of the upToFace so that the extrusion works even if profile is larger -// // than the upToFace -// for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { -// // Get outermost wire of sketch face -// TopoShape outerWire = sketchface.splitWires(); -// BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); -// if (!proj.More() || !proj.Current().Closed()) { -// remove_limits = true; -// break; -// } -// } -// -// // It must also be checked that all projected inner wires of the upToFace -// // lie outside the sketch shape. If this is not the case then the sketch -// // shape is not completely covered by the upToFace. See #0003141 -// if (!remove_limits) { -// std::vector wires; -// uptoface.splitWires(&wires); -// for (auto& w : wires) { -// BRepProj_Projection proj(TopoDS::Wire(w.getShape()), -// profile.getShape(), -// -direction); -// if (proj.More()) { -// remove_limits = true; -// break; -// } -// } -// } -// -// 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); -// // use the placement of the adapter, not of the upToFace -// loc = TopLoc_Location(adapt.Trsf()); -// BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); -// if (!mkFace.IsDone()) { -// remove_limits = false; -// } -// else { -// uptoface.setShape(located(mkFace.Shape(), loc), false); -// } -// } -// } -// -// TopoShape uptofaceCopy = uptoface; -// bool checkBase = false; -// auto retry = [&]() { -// if (!uptoface.isSame(_uptoface)) { -// // retry using the original up to face in case unnecessary failure -// // due to removing the limits -// uptoface = _uptoface; -// return true; -// } -// if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) { -// // It is unclear under exactly what condition extrude up to face -// // can fail. Either the support face or the up to face must be part -// // of the base, or maybe some thing else. -// // -// // To deal with it, we retry again by disregard the supplied base, -// // and use up to face to extrude our own base. Later on, use the -// // supplied base (i.e. _base) to calculate the final shape if the -// // mode is FuseWithBase or CutWithBase. -// checkBase = true; -// uptoface = uptofaceCopy; -// base.makeElementPrism(_uptoface, direction); -// return true; -// } -// return false; -// }; -// -// std::vector srcShapes; -// TopoShape result; -// for (;;) { -// try { -// result = base; -// -// // We do not rely on BRepFeat_MakePrism to perform fuse or cut for -// // us because of its poor support of shape history. -// auto mode = PrismMode::None; -// -// for (auto& face : profile.getSubTopoShapes( -// profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) { -// srcShapes.clear(); -// if (!profile.isNull() && !result.findShape(profile.getShape())) { -// srcShapes.push_back(profile); -// } -// if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) { -// srcShapes.push_back(supportFace); -// } -// -// // DO NOT include uptoface for element mapping. Because OCCT -// // BRepFeat_MakePrism will report all top extruded face being -// // modified by the uptoface. If there are more than one face in -// // the profile, this will cause unnecessary duplicated element -// // mapped name. And will also disrupte element history tracing -// // back to the profile sketch. -// // -// // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) -// // srcShapes.push_back(uptoface); -// -// srcShapes.push_back(result); -// -// PrismMaker.Init(result.getShape(), -// face.getShape(), -// TopoDS::Face(supportFace.getShape()), -// direction, -// mode, -// Standard_False); -// mode = PrismMode::FuseWithBase; -// -// PrismMaker.Perform(uptoface.getShape()); -// -// if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) { -// FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed"); -// } -// -// result.makeElementShape(PrismMaker, srcShapes, uptoface, op); -// } -// break; -// } -// catch (Base::Exception&) { -// if (!retry()) { -// throw; -// } -// } -// catch (Standard_Failure&) { -// if (!retry()) { -// throw; -// } -// } -// } -// -// if (!_base.isNull() && Mode != PrismMode::None) { -// if (Mode == PrismMode::FuseWithBase) { -// result.makeElementFuse({_base, result}); -// } -// else { -// result.makeElementCut({_base, result}); -// } -// } -// -// *this = result; -// return *this; -//} + TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& __uptoface, + const gp_Dir& direction, + PrismMode Mode, + Standard_Boolean checkLimits, + const char* op) +{ + if (!op) { + op = Part::OpCodes::Prism; + } + + BRepFeat_MakePrism PrismMaker; + + TopoShape _uptoface(__uptoface); + if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE + && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { + // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) + // then the algorithm expects that the 'NaturalRestriction' flag is set in order + // to work as expected. + BRep_Builder builder; + _uptoface = _uptoface.makeElementCopy(); + builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); + } + + TopoShape uptoface(_uptoface); + TopoShape base(_base); + + if (base.isNull()) { + Mode = PrismMode::None; + base = profile; + } + + // Check whether the face has limits or not. Unlimited faces have no wire + // Note: Datum planes are always unlimited + if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { + TopoDS_Face face = TopoDS::Face(uptoface.getShape()); + bool remove_limits = false; + // Remove the limits of the upToFace so that the extrusion works even if profile is larger + // than the upToFace + for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { + // Get outermost wire of sketch face + TopoShape outerWire = sketchface.splitWires(); + BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); + if (!proj.More() || !proj.Current().Closed()) { + remove_limits = true; + break; + } + } + + // It must also be checked that all projected inner wires of the upToFace + // lie outside the sketch shape. If this is not the case then the sketch + // shape is not completely covered by the upToFace. See #0003141 + if (!remove_limits) { + std::vector wires; + uptoface.splitWires(&wires); + for (auto& w : wires) { + BRepProj_Projection proj(TopoDS::Wire(w.getShape()), + profile.getShape(), + -direction); + if (proj.More()) { + remove_limits = true; + break; + } + } + } + + 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); + // use the placement of the adapter, not of the upToFace + loc = TopLoc_Location(adapt.Trsf()); + BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); + if (!mkFace.IsDone()) { + remove_limits = false; + } + else { + uptoface.setShape(located(mkFace.Shape(), loc), false); + } + } + } + + TopoShape uptofaceCopy = uptoface; + bool checkBase = false; + auto retry = [&]() { + if (!uptoface.isSame(_uptoface)) { + // retry using the original up to face in case unnecessary failure + // due to removing the limits + uptoface = _uptoface; + return true; + } + if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) { + // It is unclear under exactly what condition extrude up to face + // can fail. Either the support face or the up to face must be part + // of the base, or maybe some thing else. + // + // To deal with it, we retry again by disregard the supplied base, + // and use up to face to extrude our own base. Later on, use the + // supplied base (i.e. _base) to calculate the final shape if the + // mode is FuseWithBase or CutWithBase. + checkBase = true; + uptoface = uptofaceCopy; + base.makeElementPrism(_uptoface, direction); + return true; + } + return false; + }; + + std::vector srcShapes; + TopoShape result; + for (;;) { + try { + result = base; + + // We do not rely on BRepFeat_MakePrism to perform fuse or cut for + // us because of its poor support of shape history. + auto mode = PrismMode::None; + + for (auto& face : profile.getSubTopoShapes( + profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) { + srcShapes.clear(); + if (!profile.isNull() && !result.findShape(profile.getShape())) { + srcShapes.push_back(profile); + } + if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) { + srcShapes.push_back(supportFace); + } + + // DO NOT include uptoface for element mapping. Because OCCT + // BRepFeat_MakePrism will report all top extruded face being + // modified by the uptoface. If there are more than one face in + // the profile, this will cause unnecessary duplicated element + // mapped name. And will also disrupte element history tracing + // back to the profile sketch. + // + // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) + // srcShapes.push_back(uptoface); + + srcShapes.push_back(result); + + PrismMaker.Init(result.getShape(), + face.getShape(), + TopoDS::Face(supportFace.getShape()), + direction, + mode, + Standard_False); + mode = PrismMode::FuseWithBase; + + PrismMaker.Perform(uptoface.getShape()); + + if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) { + FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed"); + } + + result.makeElementShape(PrismMaker, srcShapes, uptoface, op); + } + break; + } + catch (Base::Exception&) { + if (!retry()) { + throw; + } + } + catch (Standard_Failure&) { + if (!retry()) { + throw; + } + } + } + + if (!_base.isNull() && Mode != PrismMode::None) { + if (Mode == PrismMode::FuseWithBase) { + result.makeElementFuse({_base, result}); + } + else { + result.makeElementCut({_base, result}); + } + } + + *this = result; + return *this; +} TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base, const gp_Ax1& axis, diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index d6e9a31dc8..f3fe0df1aa 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -100,6 +100,36 @@ 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()) + throw Part::NullShapeException("Null shape"); + int count = shape.countSubShapes(TopAbs_SOLID); + if(count>1) { + if(allowMultiSolid()) { + 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"); + } + if(count) { + auto res = shape.getSubTopoShape(TopAbs_SOLID,1); + res.fixSolidOrientation(); + return res; + } + if (force) + return TopoShape(); + return shape; +} + int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) { int result = 0; @@ -240,6 +270,15 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) return builder.Shape(); } +TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj) +{ + BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); + if (!builder.IsDone()) + throw Base::CADKernelError("Feature: Could not create shape from base plane"); + + return TopoShape(obj->getID(), nullptr, builder.Shape()); +} + Body* Feature::getFeatureBody() const { auto body = Base::freecad_dynamic_cast(_Body.getValue()); diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index 8c47700333..fe2352fff6 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -95,6 +95,7 @@ protected: * Get a solid of the given shape. If no solid is found an exception is raised. */ 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 ); /// Grab any point from the given face @@ -102,6 +103,7 @@ protected: /// Make a shape from a base plane (convenience method) static gp_Pln makePlnFromPlane(const App::DocumentObject* obj); static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj); + static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj); }; using FeaturePython = App::FeaturePythonT; diff --git a/src/Mod/PartDesign/App/FeatureAddSub.cpp b/src/Mod/PartDesign/App/FeatureAddSub.cpp index 2d4b31fae4..fd67c1cb0c 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.cpp +++ b/src/Mod/PartDesign/App/FeatureAddSub.cpp @@ -83,6 +83,16 @@ TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) co return oldShape; } +TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const +{ + if (this->Refine.getValue()) { + TopoShape shape(oldShape); +// this->fixShape(shape); + return shape.makeElementRefine(); + } + return oldShape; +} + void FeatureAddSub::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape) { if (addSubType == Additive) diff --git a/src/Mod/PartDesign/App/FeatureAddSub.h b/src/Mod/PartDesign/App/FeatureAddSub.h index d9a32cfabd..30f73e48ee 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.h +++ b/src/Mod/PartDesign/App/FeatureAddSub.h @@ -55,6 +55,7 @@ protected: Type addSubType{Additive}; TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; + TopoShape refineShapeIfActive(const TopoShape&) const; }; using FeatureAddSubPython = App::FeaturePythonT; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 6e971d9c7a..805d278559 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -40,6 +40,9 @@ #include #include "FeatureExtrude.h" +#include "Mod/Part/App/TopoShapeOpCode.h" + +FC_LOG_LEVEL_INIT("PartDesign", true, true) using namespace PartDesign; @@ -246,6 +249,63 @@ void FeatureExtrude::generatePrism(TopoDS_Shape& prism, } } +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") { + // midplane makes no sense here + Ltotal += L2; + if (reversed) + Loffset = -L; + else if (midplane) + Loffset = -0.5 * (L2 + 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, @@ -349,3 +409,270 @@ void FeatureExtrude::updateProperties(const std::string &method) Reversed.setReadOnly(!isReversedEnabled); UpToFace.setReadOnly(!isUpToFaceEnabled); } + +void FeatureExtrude::setupObject() +{ + ProfileBased::setupObject(); +} + +App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions options) +{ + 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 method(Type.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")); + } + + Part::Feature* obj = nullptr; + TopoShape sketchshape; + try { + obj = getVerifiedObject(); + if (makeface) { + sketchshape = getVerifiedFace(); + } else { + std::vector shapes; + bool hasEdges = false; + auto subs = Profile.getSubValues(false); + if (subs.empty()) + subs.emplace_back(""); + bool failed = false; + 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); + 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) + sketchshape.makeElementWires(shapes); + else + sketchshape.makeElementCompound(shapes, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } catch (const Standard_Failure& e) { + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + + // if the Base property has a valid shape, fuse the prism into it + TopoShape base = getBaseTopoShape(true); + + // get the normal vector of the sketch + Base::Vector3d SketchVector = getProfileNormal(); + + try { + this->positionByPrevious(); + auto invObjLoc = getLocation().Inverted(); + + auto invTrsf = invObjLoc.Transformation(); + + base.move(invObjLoc); + + Base::Vector3d paddingDirection = computeDirection(SketchVector); + + // create vector in padding direction with length 1 + gp_Dir dir(paddingDirection.x, paddingDirection.y, paddingDirection.z); + + // The length of a gp_Dir is 1 so the resulting pad would have + // the length L in the direction of dir. But we want to have its height in the + // direction of the normal vector. + // Therefore we must multiply L by the factor that is necessary + // to make dir as long that its projection to the SketchVector + // equals the SketchVector. + // This is the scalar product of both vectors. + // Since the pad length cannot be negative, the factor must not be negative. + + 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")); + + // perform the length correction if not along custom vector + if (AlongSketchNormal.getValue()) { + L = L / factor; + L2 = L2 / factor; + } + + // explicitly set the Direction so that the dialog shows also the used direction + // if the sketch's normal vector was used + Direction.setValue(paddingDirection); + + dir.Transform(invTrsf); + + 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()); + + if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") { + // Note: This will return an unlimited planar face if support is a datum plane + TopoShape supportface = getSupportFace(); + supportface.move(invObjLoc); + + if (Reversed.getValue()) + dir.Reverse(); + + // Find a valid face or datum plane to extrude up to + TopoShape upToFace; + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.move(invObjLoc); + } + getUpToFace(upToFace, base, supportface, sketchshape, method, dir); + addOffsetToFace(upToFace, dir, Offset.getValue()); + + 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()*/); + // 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}); + 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 + 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 { + 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.taperAngleRev = params.taperAngleFwd; + } else { + params.lengthFwd = L; + params.lengthRev = L2; + } + if (std::fabs(params.taperAngleFwd) >= 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()) + 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()); + } + + // set the additive shape property for later usage in e.g. pattern + prism = refineShapeIfActive(prism); + this->AddSubShape.setValue(prism); + + if (!base.isNull() && fuse) { + prism.Tag = -this->getID(); + + // Let's call algorithm computing a fuse operation: + TopoShape result(0,getDocument()->getStringHasher()); + try { + const char *maker; + switch (getAddSubType()) { + case Subtractive: + maker = Part::OpCodes::Cut; + break; + 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")); + } + // 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")); + + solRes = refineShapeIfActive(solRes); + this->Shape.setValue(getSolid(solRes)); + } 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 { + prism = refineShapeIfActive(prism); + this->Shape.setValue(prism); + } + + // eventually disable some settings that are not valid for the current method + updateProperties(method); + + 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 + 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 2bcdeb6832..9ba2945ee8 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.h +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -60,12 +60,25 @@ public: /** @name methods override feature */ //@{ short mustExecute() const override; + void setupObject() override; //@} protected: Base::Vector3d computeDirection(const Base::Vector3d& sketchVector); bool hasTaperedAngle() const; + /// Options for buildExtrusion() + enum class ExtrudeOption { + MakeFace = 1, + MakeFuse = 2, + LegacyPocket = 4, + InverseDirection = 8, + }; + + using ExtrudeOptions = Base::Flags; + + App::DocumentObjectExecReturn *buildExtrusion(ExtrudeOptions options); + /** * Generates an extrusion of the input sketchshape and stores it in the given \a prism */ @@ -78,6 +91,15 @@ protected: 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, @@ -120,5 +142,6 @@ protected: } //namespace PartDesign +ENABLE_BITMASK_OPERATORS(PartDesign::FeatureExtrude::ExtrudeOption) #endif // PARTDESIGN_FEATURE_EXTRUDE_H diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 373cc9a476..b89d14e5c7 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -62,6 +62,8 @@ #include "DatumPlane.h" +FC_LOG_LEVEL_INIT("PartDesign",true,true); + using namespace PartDesign; PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub) @@ -253,6 +255,134 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { return TopoDS_Face(); } +TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent, + 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) + return TopoShape(); + throw Base::ValueError("No profile linked"); + } + const auto &subs = profile ? _subs : Profile.getSubValues(); + try { + TopoShape shape; + 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); + 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) + return shape; + throw Base::CADKernelError("Linked shape object is empty"); + } + TopoShape openshape; + if(!shape.hasSubShape(TopAbs_FACE)) { + try { + if(!shape.hasSubShape(TopAbs_WIRE)) + shape = shape.makeElementWires(); + 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()) + openwires.push_back(wire); + else + wires.push_back(wire); + } + if (openwires.size()) { + openshape.makeElementCompound(openwires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + if (wires.empty()) + shape = TopoShape(); + else + shape.makeElementCompound(wires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + } + } + if (!shape.isNull()) { + if (AllowMultiFace.getValue()) + shape = shape.makeElementFace(); // default to use FaceMakerBullseye + else + shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese"); + } + } + } catch (const Base::Exception &) { + if (silent) + return TopoShape(); + throw; + } catch (const Standard_Failure &) { + if (silent) + return TopoShape(); + throw; + } + } + int count = shape.countSubShapes(TopAbs_FACE); + 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()); +// } + + if (!openshape.isNull()) { + if (shape.isNull()) + shape = openshape; + else + shape.makeElementCompound({shape, openshape}); + } + if(count>1) { + if(AllowMultiFace.getValue() +// || allowMultiSolid() + || obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) + return shape; + FC_WARN("Found more than one face from profile"); + } + if (!openshape.isNull()) + return shape; + if (count) + return shape.getSubTopoShape(TopAbs_FACE,1); + return shape; + }catch (Standard_Failure &) { + if(silent) + return TopoShape(); + throw; + } +} + std::vector ProfileBased::getProfileWires() const { std::vector result; @@ -292,6 +422,22 @@ std::vector ProfileBased::getProfileWires() const { return result; } +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)) + return shape.getSubTopoShapes(TopAbs_WIRE); + + auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE); + 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! @@ -356,6 +502,34 @@ TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const return face; } +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; + App::DocumentObject* ref = Support.getValue(); + shape = Part::Feature::getTopoShape( + ref, Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", true); + } + if (!shape.isNull()) { + if (shape.shapeType(true) != 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)) + throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); + + return shape; + } + if (!sketch) + throw Base::RuntimeError("No planar support"); + return Feature::makeShapeFromPlane(sketch); +} + int ProfileBased::getSketchAxisCount() const { Part::Part2DObject* sketch = static_cast(Profile.getValue()); @@ -540,6 +714,54 @@ void ProfileBased::getUpToFace(TopoDS_Face& upToFace, } } +void ProfileBased::getUpToFace(TopoShape& upToFace, + const TopoShape& support, + const TopoShape& supportface, + const TopoShape& sketchshape, + const std::string& method, + gp_Dir& dir) +{ + if ((method == "UpToLast") || (method == "UpToFirst")) { + 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; + it_near = it_far = cfaces.begin(); + for (it = cfaces.begin(); it != cfaces.end(); it++) + if (it->distsq > it_far->distsq) + it_far = it; + else if (it->distsq < it_near->distsq) + it_near = it; + upToFace = (method == "UpToLast" ? it_far->face : it_near->face); + } else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) + dir = -dir; + + if (upToFace.shapeType(true) != 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) + 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!"); + } + + // We must measure from sketchshape, not supportface, here + BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face); + if (distSS.Value() < Precision::Confusion()) + throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!"); +} + void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset) { // Move the face in the extrusion direction @@ -564,6 +786,18 @@ void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, dou } } +void ProfileBased::addOffsetToFace(TopoShape& upToFace, const gp_Dir& dir, double offset) +{ + // Move the face in the extrusion direction + // TODO: For non-planar faces, we could consider offsetting the surface + if (fabs(offset) > Precision::Confusion()) { + gp_Trsf mov; + mov.SetTranslation(offset * gp_Vec(dir)); + TopLoc_Location loc(mov); + upToFace.move(loc); + } +} + double ProfileBased::getThroughAllLength() const { TopoDS_Shape profileshape; @@ -739,6 +973,13 @@ bool ProfileBased::checkLineCrossesFace(const gp_Lin& line, const TopoDS_Face& f void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape) { +#if FC_USE_TNP_FIX + (void)newShape; + // Realthunder: with the new topological naming, I don't think this function + // is necessary. A missing element will cause an explicitly error, and the + // user will be force to manually select the element. Various editors, such + // as dress up editors, can perform element guessing when activated. +#else TopTools_IndexedMapOfShape faceMap; TopExp::MapShapes(newShape, TopAbs_FACE, faceMap); @@ -830,6 +1071,7 @@ void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape) link->setValue(this, newSubValues); } } +#endif } namespace PartDesign { diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index 49bd99702b..be4204d334 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -99,15 +99,34 @@ public: */ TopoDS_Shape getVerifiedFace(bool silent = false) const; + /** + * Verifies the linked Object and returns the shape used as profile + * @param silent: if profile property is malformed and the parameter is true + * silently returns nullptr, otherwise throw a Base::Exception. + * Default is false. + * @param doFit: Whether to fitting according to the 'Fit' property + * @param allowOpen: Whether allow open wire + * @param profile: optional profile object, if not given then use 'Profile' property + * @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; + /// Returns the wires the sketch is composed of std::vector getProfileWires() const; + std::vector getTopoShapeProfileWires() const; + /// Returns the face of the sketch support (if any) const TopoDS_Face getSupportFace() const; + TopoShape getTopoShapeSupportFace() const; Base::Vector3d getProfileNormal() const; - Part::TopoShape getProfileShape() const; + TopoShape getProfileShape() const; /// retrieves the number of axes in the linked sketch (defined as construction lines) int getSketchAxisCount() const; @@ -142,6 +161,22 @@ protected: 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); + + /// Find a valid face to extrude up to + static void getUpToFace(TopoShape& upToFace, + const TopoShape& support, + const TopoShape& supportface, + const TopoShape& sketchshape, + const std::string& method, + gp_Dir& dir); + + /// Add an offset to the face + static void addOffsetToFace(TopoShape& upToFace, + const gp_Dir& dir, + double offset); /// Check whether the wire after projection on the face is inside the face static bool checkWireInsideFace(const TopoDS_Wire& wire, From 942ebd58f00d3cf52f7a3bcefb36fab24c640c15 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sun, 7 Apr 2024 12:39:32 -0400 Subject: [PATCH 2/4] 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) { From 239c3e6f78f6348a9db3461854dca2e450d4cf10 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 10 Apr 2024 12:14:01 -0400 Subject: [PATCH 3/4] Toponaming/Part: Fix review items --- src/Mod/PartDesign/App/Feature.cpp | 25 ++++--------------------- src/Mod/PartDesign/App/Feature.h | 2 +- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index 28d37c9c45..e61490b9e9 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -101,31 +101,14 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) return {}; } -TopoShape Feature::getSolid(const TopoShape& shape, bool force) +TopoShape Feature::getSolid(const TopoShape& shape) { if (shape.isNull()) { throw Part::NullShapeException("Null shape"); } - int count = shape.countSubShapes(TopAbs_SOLID); - 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"); - } - if (count) { - auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); - res.fixSolidOrientation(); - return res; - } - if (force) { - return TopoShape(); - } - return shape; + auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); + res.fixSolidOrientation(); + return res; } int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index 8f901f9eeb..8147024f49 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -96,7 +96,7 @@ protected: */ // 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); + TopoShape getSolid(const TopoShape&); static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID); /// Grab any point from the given face From 01498d92d5e69d66651d75eb235128182f3865dc Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 11 Apr 2024 18:20:26 -0400 Subject: [PATCH 4/4] Bring in missing code --- src/Mod/Part/App/TopoShape.cpp | 127 +++++++++++++++++++++- src/Mod/Part/App/TopoShape.h | 2 +- src/Mod/PartDesign/App/FeatureExtrude.cpp | 17 ++- 3 files changed, 137 insertions(+), 9 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index b25190f94c..7090a17108 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -3957,8 +3957,130 @@ TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, RefineF return *this; } -// TODO: Does the toponaming branch version of this method need to be here? -bool TopoShape::findPlane(gp_Pln &pln, double tol) const { +#ifdef FC_USE_TNP_FIX +bool TopoShape::findPlane(gp_Pln& pln, double tol, double atol) const +{ + if (_Shape.IsNull()) { + return false; + } + if (tol < 0.0) { + tol = Precision::Confusion(); + } + if (atol < 0.0) { + atol = Precision::Angular(); + } + TopoDS_Shape shape; + if (countSubShapes(TopAbs_EDGE) == 1) { + // To deal with OCCT bug of wrong edge transformation + shape = BRepBuilderAPI_Copy(_Shape).Shape(); + } + else { + shape = _Shape; + } + try { + bool found = false; + // BRepLib_FindSurface only really works on edges. We'll deal face first + for (auto& shape : getSubShapes(TopAbs_FACE)) { + gp_Pln plane; + auto face = TopoDS::Face(shape); + BRepAdaptor_Surface adapt(face); + if (adapt.GetType() == GeomAbs_Plane) { + plane = adapt.Plane(); + } + else { + TopLoc_Location loc; + Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc); + GeomLib_IsPlanarSurface check(surf); + if (check.IsPlanar()) { + plane = check.Plan(); + } + else { + return false; + } + } + if (!found) { + found = true; + pln = plane; + } + else if (!pln.Position().IsCoplanar(plane.Position(), tol, atol)) { + return false; + } + } + + // Check if there is free edges (i.e. edges does not belong to any face) + if (TopExp_Explorer(getShape(), TopAbs_EDGE, TopAbs_FACE).More()) { + // Copy shape to work around OCC transformation bug, that is, if + // edge has transformation, but underlying geometry does not (or the + // other way round), BRepLib_FindSurface returns a plane with the + // wrong transformation + BRepLib_FindSurface finder(BRepBuilderAPI_Copy(shape).Shape(), tol, Standard_True); + if (!finder.Found()) { + return false; + } + pln = GeomAdaptor_Surface(finder.Surface()).Plane(); + found = true; + } + + // Check for free vertexes + auto vertexes = getSubShapes(TopAbs_VERTEX, TopAbs_EDGE); + if (vertexes.size()) { + if (!found && vertexes.size() > 2) { + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for (int i = 0, c = (int)vertexes.size() - 1; i < c; ++i) { + builder.Add(comp, + BRepBuilderAPI_MakeEdge(TopoDS::Vertex(vertexes[i]), + TopoDS::Vertex(vertexes[i + 1])) + .Edge()); + } + BRepLib_FindSurface finder(comp, tol, Standard_True); + if (!finder.Found()) { + return false; + } + pln = GeomAdaptor_Surface(finder.Surface()).Plane(); + return true; + } + + double tt = tol * tol; + for (auto& v : vertexes) { + if (pln.SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(v))) > tt) { + return false; + } + } + } + + // To make the returned plane normal more stable, if the shape has any + // face, use the normal of the first face. + if (hasSubShape(TopAbs_FACE)) { + shape = getSubShape(TopAbs_FACE, 1); + BRepAdaptor_Surface adapt(TopoDS::Face(shape)); + double u = + adapt.FirstUParameter() + (adapt.LastUParameter() - adapt.FirstUParameter()) / 2.; + double v = + adapt.FirstVParameter() + (adapt.LastVParameter() - adapt.FirstVParameter()) / 2.; + BRepLProp_SLProps prop(adapt, u, v, 2, Precision::Confusion()); + if (prop.IsNormalDefined()) { + gp_Pnt pnt; + gp_Vec vec; + // handles the orientation state of the shape + BRepGProp_Face(TopoDS::Face(shape)).Normal(u, v, pnt, vec); + pln = gp_Pln(pnt, gp_Dir(vec)); + } + } + return true; + } + catch (Standard_Failure& e) { + // For some reason the above BRepBuilderAPI_Copy failed to copy + // the geometry of some edge, causing exception with message + // BRepAdaptor_Curve::No geometry. However, without the above + // copy, circular edges often have the wrong transformation! + FC_LOG("failed to find surface: " << e.GetMessageString()); + return false; + } +} +#else +bool TopoShape::findPlane(gp_Pln &pln, double tol, double atol) const { if(_Shape.IsNull()) return false; TopoDS_Shape shape = _Shape; @@ -4004,6 +4126,7 @@ bool TopoShape::findPlane(gp_Pln &pln, double tol) const { return false; } } +#endif bool TopoShape::isInfinite() const { diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index bc4ae8718f..d0e095278f 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -477,7 +477,7 @@ public: bool analyze(bool runBopCheck, std::ostream&) const; bool isClosed() const; bool isCoplanar(const TopoShape& other, double tol = -1) const; - bool findPlane(gp_Pln& plane, double tol = -1) const; + bool findPlane(gp_Pln& plane, double tol = -1, double atol = -1) const; /// Returns true if the expansion of the shape is infinite, false otherwise bool isInfinite() const; /// Checks whether the shape is a planar face diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 1cbd35949d..4f7dd026b0 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -1,3 +1,4 @@ + /*************************************************************************** * Copyright (c) 2020 Werner Mayer * * * @@ -567,12 +568,16 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt // Find a valid face or datum plane to extrude up to TopoShape upToFace; - if (method == "UpToFace") { - getUpToFaceFromLinkSub(upToFace, UpToFace); - upToFace.move(invObjLoc); + + if (method != "UpToShape") { + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.move(invObjLoc); + } + getUpToFace(upToFace, base, supportface, sketchshape, method, dir); + addOffsetToFace(upToFace, dir, Offset.getValue()); } - getUpToFace(upToFace, base, supportface, sketchshape, method, dir); - addOffsetToFace(upToFace, dir, Offset.getValue()); + if (!supportface.hasSubShape(TopAbs_WIRE)) { supportface = TopoShape(); @@ -627,7 +632,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt upToFace, dir, TopoShape::PrismMode::None, - false /*CheckUpToFaceLimits.getValue()*/); + true /*CheckUpToFaceLimits.getValue()*/); } else { Part::ExtrusionParameters params;