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,