diff --git a/src/Mod/PartDesign/App/FeatureGroove.cpp b/src/Mod/PartDesign/App/FeatureGroove.cpp index d9b1f57167..fd042cdcd5 100644 --- a/src/Mod/PartDesign/App/FeatureGroove.cpp +++ b/src/Mod/PartDesign/App/FeatureGroove.cpp @@ -25,10 +25,9 @@ # include # include # include -# include -# include # include - +# include +# include #include #include @@ -36,7 +35,6 @@ #include "FeatureGroove.h" #include "Mod/Part/App/TopoShapeOpCode.h" - using namespace PartDesign; namespace PartDesign { @@ -47,7 +45,7 @@ const char* Groove::TypeEnums[]= {"Angle", "ThroughAll", "UpToFirst", "UpToFace" PROPERTY_SOURCE(PartDesign::Groove, PartDesign::ProfileBased) -const App::PropertyAngle::Constraints Groove::floatAngle = { Base::toDegrees(Precision::Angular()), 360.0, 1.0 }; +const App::PropertyAngle::Constraints Groove::floatAngle = { 0.0, 360.0, 1.0 }; Groove::Groove() { @@ -58,10 +56,10 @@ Groove::Groove() ADD_PROPERTY_TYPE(Base, (Base::Vector3d(0.0f,0.0f,0.0f)), "Groove", App::PropertyType(App::Prop_ReadOnly | App::Prop_Hidden), "Base"); ADD_PROPERTY_TYPE(Axis, (Base::Vector3d(0.0f,1.0f,0.0f)), "Groove", App::PropertyType(App::Prop_ReadOnly | App::Prop_Hidden), "Axis"); ADD_PROPERTY_TYPE(Angle, (360.0),"Groove", App::Prop_None, "Angle"); - ADD_PROPERTY_TYPE(Angle2, (60.0), "Groove", App::Prop_None, "Groove length in 2nd direction"); + ADD_PROPERTY_TYPE(Angle2, (0.0), "Groove", App::Prop_None, "Groove length in 2nd direction"); ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Groove", App::Prop_None, "Face where groove will end"); Angle.setConstraints(&floatAngle); - ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Groove", (App::PropertyType)(App::Prop_None), "Reference axis of Groove"); + ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Groove", (App::Prop_None), "Reference axis of groove"); } short Groove::mustExecute() const @@ -81,57 +79,63 @@ App::DocumentObjectExecReturn *Groove::execute() { if (onlyHaveRefined()) { return App::DocumentObject::StdReturn; } + constexpr double maxDegree = 360.0; + auto method = methodFromString(Type.getValueAsString()); + // Validate parameters - double angle = Angle.getValue(); - if (angle > 360.0) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too large")); - - angle = Base::toRadians(angle); - if (angle < Precision::Angular()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too small")); - - // Reverse angle if selected - if (Reversed.getValue() && !Midplane.getValue()) - angle *= (-1.0); - - TopoShape sketchshape; - try { - sketchshape = getTopoShapeVerifiedFace(); - } catch (const Base::Exception& e) { - return new App::DocumentObjectExecReturn(e.what()); + double angleDeg = Angle.getValue(); + if (angleDeg > maxDegree) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Angle of groove too large")); } - // if the Base property has a valid shape, fuse the prism into it + double angle = Base::toRadians(angleDeg); + if (angle < Precision::Angular() && method == RevolMethod::Angle) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Angle of groove too small")); + } + + double angle2 = Base::toRadians(Angle2.getValue()); + if (std::fabs(angle + angle2) < Precision::Angular() && method == RevolMethod::TwoAngles) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Angles of groove nullify each other")); + } + + TopoShape sketchshape = getTopoShapeVerifiedFace(); + + // if the Base property has a valid shape, fuse the AddShape into it TopoShape base; try { base = getBaseTopoShape(); } catch (const Base::Exception&) { - std::string text(QT_TRANSLATE_NOOP("Exception", "The requested feature cannot be created. The reason may be that:\n" - " - the active Body does not contain a base shape, so there is no\n" - " material to be removed;\n" - " - the selected sketch does not belong to the active Body.")); - return new App::DocumentObjectExecReturn(text); + // fall back to support (for legacy features) } - updateAxis(); - - // get revolve axis - Base::Vector3d b = Base.getValue(); - gp_Pnt pnt(b.x,b.y,b.z); - Base::Vector3d v = Axis.getValue(); - gp_Dir dir(v.x,v.y,v.z); + // update Axis from ReferenceAxis + try { + updateAxis(); + } + catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } try { - if (sketchshape.isNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); + // get revolve axis + Base::Vector3d b = Base.getValue(); + gp_Pnt pnt(b.x, b.y, b.z); + Base::Vector3d v = Axis.getValue(); - // Rotate the face by half the angle to get Groove symmetric to sketch plane - if (Midplane.getValue()) { - gp_Trsf mov; - mov.SetRotation(gp_Ax1(pnt, dir), Base::toRadians(Angle.getValue()) * (-1.0) / 2.0); - TopLoc_Location loc(mov); - sketchshape.move(loc); + if (v.IsNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Reference axis is invalid")); + } + + gp_Dir dir(v.x, v.y, v.z); + + if (sketchshape.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); } this->positionByPrevious(); @@ -144,66 +148,112 @@ App::DocumentObjectExecReturn *Groove::execute() // Check distance between sketchshape and axis - to avoid failures and crashes TopExp_Explorer xp; xp.Init(sketchshape.getShape(), TopAbs_FACE); - for (;xp.More(); xp.Next()) { - if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); - } - - // revolve the face to a solid - TopoShape result(0); - try { - result.makeElementRevolve(sketchshape, gp_Ax1(pnt, dir), angle); - }catch(Standard_Failure &) { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!")); - } - this->AddSubShape.setValue(result); - - if(base.isNull()) { - Shape.setValue(getSolid(result)); - return App::DocumentObject::StdReturn; - } - - result.Tag = -getID(); - TopoShape boolOp(0); - - try { - const char *maker; - switch (getAddSubType()) { - case Additive: - maker = Part::OpCodes::Fuse; - break; -// case Intersecting: -// maker = Part::OpCodes::Common; -// break; - default: - maker = Part::OpCodes::Cut; + for (; xp.More(); xp.Next()) { + if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); } -// this->fixShape(result); - boolOp.makeElementBoolean(maker, {base,result}); - }catch(Standard_Failure &) { - return new App::DocumentObjectExecReturn("Failed to cut base feature"); } - TopoShape solid = this->getSolid(boolOp); - if (solid.isNull()) - return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); - // store shape before refinement - this->rawShape = boolOp; - boolOp = refineShapeIfActive(boolOp); - if (!isSingleSolidRuleSatisfied(boolOp.getShape())) { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: enable 'Allow Compound' in the active body.")); + // Create a fresh support even when base exists so that it can be used for patterns + TopoShape result(0); + TopoShape supportface(0); + try { + supportface = getSupportFace(); } - boolOp = getSolid(boolOp); - Shape.setValue(boolOp); + catch(...) { + // do nothing, null shape is handled below + } + + supportface.move(invObjLoc); + + if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst) { + TopoShape upToFace; + if (method == RevolMethod::ToFace) { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.move(invObjLoc); + } + else { + throw Base::RuntimeError( + "ProfileBased: Revolution up to first/last is not yet supported"); + } + + if (Reversed.getValue()) { + dir.Reverse(); + } + + TopExp_Explorer Ex(supportface.getShape(), TopAbs_WIRE); + if (!Ex.More()) { + supportface = TopoDS_Face(); + } + + try { + result = base.makeElementRevolution(base, + TopoDS::Face(sketchshape.getShape()), + gp_Ax1(pnt, dir), + TopoDS::Face(supportface.getShape()), + TopoDS::Face(upToFace.getShape()), + nullptr, + Part::RevolMode::CutFromBase, + Standard_True); + } + catch (Standard_Failure&) { + return new App::DocumentObjectExecReturn("Could not revolve the sketch!"); + } + } + else { + bool midplane = Midplane.getValue(); + bool reversed = Reversed.getValue(); + generateRevolution(result, + sketchshape.getShape(), + gp_Ax1(pnt, dir), + angle, + angle2, + midplane, + reversed, + method); + } + + if (!result.isNull()) { + // store shape before refinement + this->rawShape = result; + result = refineShapeIfActive(result); + // set the additive shape property for later usage in e.g. pattern + this->AddSubShape.setValue(result); + + if (!base.isNull()) { + result = base.makeElementCut(result); + // store shape before refinement + this->rawShape = result; + result = refineShapeIfActive(result); + } + if (!isSingleSolidRuleSatisfied(result.getShape())) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: enable 'Allow Compound' in the active body.")); + } + result = getSolid(result); + this->Shape.setValue(result); + } + else { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!")); + } + + // 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 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 in a sketch are not allowed.")); + } + else { return new App::DocumentObjectExecReturn(e.GetMessageString()); + } } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); @@ -212,7 +262,12 @@ App::DocumentObjectExecReturn *Groove::execute() bool Groove::suggestReversed() { - updateAxis(); + try { + updateAxis(); + } catch (const Base::Exception&) { + return false; + } + return ProfileBased::getReversedAngle(Base.getValue(), Axis.getValue()) > 0.0; } @@ -224,16 +279,14 @@ void Groove::updateAxis() Base::Vector3d dir; getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NotParallelWithNormal); - if (dir.Length() > Precision::Confusion()) { - Base.setValue(base.x,base.y,base.z); - Axis.setValue(dir.x,dir.y,dir.z); - } + Base.setValue(base.x,base.y,base.z); + Axis.setValue(dir.x,dir.y,dir.z); } Groove::RevolMethod Groove::methodFromString(const std::string& methodStr) { if (methodStr == "Angle") - return RevolMethod::Dimension; + return RevolMethod::Angle; if (methodStr == "UpToLast") return RevolMethod::ToLast; if (methodStr == "ThroughAll") @@ -243,14 +296,13 @@ Groove::RevolMethod Groove::methodFromString(const std::string& methodStr) if (methodStr == "UpToFace") return RevolMethod::ToFace; if (methodStr == "TwoAngles") - return RevolMethod::TwoDimensions; + return RevolMethod::TwoAngles; throw Base::ValueError("Groove:: No such method"); - return RevolMethod::Dimension; } -void Groove::generateRevolution(TopoDS_Shape& revol, - const TopoDS_Shape& sketchshape, +void Groove::generateRevolution(TopoShape& revol, + const TopoShape& sketchshape, const gp_Ax1& axis, const double angle, const double angle2, @@ -258,11 +310,11 @@ void Groove::generateRevolution(TopoDS_Shape& revol, const bool reversed, RevolMethod method) { - if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) { + if (method == RevolMethod::Angle || method == RevolMethod::TwoAngles || method == RevolMethod::ThroughAll) { double angleTotal = angle; double angleOffset = 0.; - if (method == RevolMethod::TwoDimensions) { + if (method == RevolMethod::TwoAngles) { // Rotate the face by `angle2`/`angle` to get "second" angle angleTotal += angle2; angleOffset = angle2 * -1.0; @@ -271,43 +323,41 @@ void Groove::generateRevolution(TopoDS_Shape& revol, angleTotal = 2 * std::numbers::pi; } else if (midplane) { - // Rotate the face by half the angle to get Groove symmetric to sketch plane + // Rotate the face by half the angle to get Revolution symmetric to sketch plane angleOffset = -angle / 2; } if (fabs(angleTotal) < Precision::Angular()) throw Base::ValueError("Cannot create a revolution with zero angle."); - TopoDS_Shape from = sketchshape; - if (method == RevolMethod::TwoDimensions || midplane) { - gp_Trsf mov; - mov.SetRotation(axis, angleOffset); - TopLoc_Location loc(mov); - from.Move(loc); + gp_Ax1 revolAx(axis); + if (reversed) { + revolAx.Reverse(); } - else if (reversed) { - angleTotal *= -1.0; + + TopoShape from = sketchshape; + if (method == RevolMethod::TwoAngles || midplane) { + gp_Trsf mov; + mov.SetRotation(revolAx, angleOffset); + TopLoc_Location loc(mov); + from.move(loc); } // revolve the face to a solid // BRepPrimAPI is the only option that allows use of this shape for patterns. // See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673. - BRepPrimAPI_MakeRevol RevolMaker(from, axis, angleTotal); - - if (!RevolMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!"); - else - revol = RevolMaker.Shape(); - } - else { + revol = from; + revol = revol.makeElementRevolve(revolAx,angleTotal); + revol.Tag = -getID(); + } else { std::stringstream str; - str << "ProfileBased: Internal error: Unknown method for generateGroove()"; + str << "ProfileBased: Internal error: Unknown method for generateRevolution()"; throw Base::RuntimeError(str.str()); } } -void Groove::generateRevolution(TopoDS_Shape& revol, - const TopoDS_Shape& baseshape, +void Groove::generateRevolution(TopoShape& revol, + const TopoShape& baseshape, const TopoDS_Shape& profileshape, const TopoDS_Face& supportface, const TopoDS_Face& uptoface, @@ -317,20 +367,8 @@ void Groove::generateRevolution(TopoDS_Shape& revol, Standard_Boolean Modify) { if (method == RevolMethod::ToFirst || method == RevolMethod::ToFace || method == RevolMethod::ToLast) { - BRepFeat_MakeRevol RevolMaker; - TopoDS_Shape base = baseshape; - for (TopExp_Explorer xp(profileshape, TopAbs_FACE); xp.More(); xp.Next()) { - RevolMaker.Init(base, xp.Current(), supportface, axis, Mode, Modify); - RevolMaker.Perform(uptoface); - if (!RevolMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: Up to face: Could not revolve the sketch!"); - - base = RevolMaker.Shape(); - if (Mode == RevolMode::None) - Mode = RevolMode::FuseWithBase; - } - - revol = base; + revol = revol.makeElementRevolution(baseshape, profileshape, axis, supportface, uptoface, nullptr, + static_cast(Mode), Modify, nullptr); } else { std::stringstream str; @@ -348,7 +386,7 @@ void Groove::updateProperties(RevolMethod method) bool isMidplaneEnabled = false; bool isReversedEnabled = false; bool isUpToFaceEnabled = false; - if (method == RevolMethod::Dimension) { + if (method == RevolMethod::Angle) { isAngleEnabled = true; isMidplaneEnabled = true; isReversedEnabled = !Midplane.getValue(); @@ -367,7 +405,7 @@ void Groove::updateProperties(RevolMethod method) isReversedEnabled = true; isUpToFaceEnabled = true; } - else if (method == RevolMethod::TwoDimensions) { + else if (method == RevolMethod::TwoAngles) { isAngleEnabled = true; isAngle2Enabled = true; isReversedEnabled = true; @@ -380,7 +418,5 @@ void Groove::updateProperties(RevolMethod method) UpToFace.setReadOnly(!isUpToFaceEnabled); } - } - diff --git a/src/Mod/PartDesign/App/FeatureGroove.h b/src/Mod/PartDesign/App/FeatureGroove.h index 8e9c2d40c6..9856fc1e12 100644 --- a/src/Mod/PartDesign/App/FeatureGroove.h +++ b/src/Mod/PartDesign/App/FeatureGroove.h @@ -69,12 +69,12 @@ public: bool suggestReversed(); enum class RevolMethod { - Dimension, + Angle, ThroughAll, ToLast = ThroughAll, ToFirst, ToFace, - TwoDimensions + TwoAngles }; protected: @@ -95,8 +95,8 @@ protected: /** * Generates a [groove] of the input sketchshape and stores it in the given \a revol. */ - void generateRevolution(TopoDS_Shape& revol, - const TopoDS_Shape& sketchshape, + void generateRevolution(TopoShape& revol, + const TopoShape& sketchshape, const gp_Ax1& ax1, const double angle, const double angle2, @@ -108,8 +108,8 @@ protected: * Generates a [groove] of the input \a profileshape. * It will be a stand-alone solid created with BRepFeat_MakeRevol. */ - void generateRevolution(TopoDS_Shape& revol, - const TopoDS_Shape& baseshape, + void generateRevolution(TopoShape& revol, + const TopoShape& baseshape, const TopoDS_Shape& profileshape, const TopoDS_Face& supportface, const TopoDS_Face& uptoface,