diff --git a/src/Mod/PartDesign/App/FeatureGroove.cpp b/src/Mod/PartDesign/App/FeatureGroove.cpp index 0370343b5e..a71c95f7ff 100644 --- a/src/Mod/PartDesign/App/FeatureGroove.cpp +++ b/src/Mod/PartDesign/App/FeatureGroove.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -41,9 +42,10 @@ using namespace PartDesign; namespace PartDesign { - /* TRANSLATOR PartDesign::Groove */ +const char* Groove::TypeEnums[]= {"Angle", "ThroughAll", "UpToFirst", "UpToFace", "TwoAngles", nullptr}; + PROPERTY_SOURCE(PartDesign::Groove, PartDesign::ProfileBased) const App::PropertyAngle::Constraints Groove::floatAngle = { Base::toDegrees(Precision::Angular()), 360.0, 1.0 }; @@ -52,11 +54,15 @@ Groove::Groove() { addSubType = FeatureAddSub::Subtractive; - ADD_PROPERTY_TYPE(Base,(Base::Vector3d(0.0f,0.0f,0.0f)),"Groove", App::Prop_ReadOnly, "Base"); - ADD_PROPERTY_TYPE(Axis,(Base::Vector3d(0.0f,1.0f,0.0f)),"Groove", App::Prop_ReadOnly, "Axis"); - ADD_PROPERTY_TYPE(Angle,(360.0),"Groove", App::Prop_None, "Angle"); + ADD_PROPERTY_TYPE(Type, (0L), "Groove", App::Prop_None, "Groove type"); + Type.setEnums(TypeEnums); + ADD_PROPERTY_TYPE(Base, (Base::Vector3d(0.0f,0.0f,0.0f)), "Groove", App::Prop_ReadOnly, "Base"); + ADD_PROPERTY_TYPE(Axis, (Base::Vector3d(0.0f,1.0f,0.0f)), "Groove", App::Prop_ReadOnly, "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(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::PropertyType)(App::Prop_None), "Reference axis of Groove"); } short Groove::mustExecute() const @@ -65,7 +71,9 @@ short Groove::mustExecute() const ReferenceAxis.isTouched() || Axis.isTouched() || Base.isTouched() || - Angle.isTouched()) + UpToFace.isTouched() || + Angle.isTouched() || + Angle2.isTouched()) return 1; return ProfileBased::mustExecute(); } @@ -73,17 +81,16 @@ short Groove::mustExecute() const App::DocumentObjectExecReturn *Groove::execute() { // Validate parameters - double angle = Angle.getValue(); - if (angle > 360.0) + // All angles are in radians unless explicitly stated + double angleDeg = Angle.getValue(); + if (angleDeg > 360.0) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too large")); - angle = Base::toRadians(angle); + double angle = Base::toRadians(angleDeg); 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); + double angle2 = Base::toRadians(Angle2.getValue()); TopoDS_Shape sketchshape; try { @@ -105,7 +112,12 @@ App::DocumentObjectExecReturn *Groove::execute() return new App::DocumentObjectExecReturn(text); } - updateAxis(); + // update Axis from ReferenceAxis + try { + updateAxis(); + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } // get revolve axis Base::Vector3d b = Base.getValue(); @@ -117,13 +129,7 @@ App::DocumentObjectExecReturn *Groove::execute() if (sketchshape.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); - // 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); - } + RevolMethod method = methodFromString(Type.getValueAsString()); this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); @@ -140,11 +146,61 @@ App::DocumentObjectExecReturn *Groove::execute() return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); } - // revolve the face to a solid - BRepPrimAPI_MakeRevol RevolMaker(sketchshape, gp_Ax1(pnt, dir), angle); + // Create a fresh support even when base exists so that it can be used for patterns + TopoDS_Shape result; + TopoDS_Face supportface = getSupportFace(); + supportface.Move(invObjLoc); + + if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst) { + TopoDS_Face upToFace; + if (method == RevolMethod::ToFace) { + getFaceFromLinkSub(upToFace, UpToFace); + upToFace.Move(invObjLoc); + } + else + throw Base::RuntimeError("ProfileBased: Groove up to first is not yet supported"); + + // TODO: This method is designed for extrusions. needs to be adapted for grooves. + // getUpToFace(upToFace, base, supportface, sketchshape, method, dir); + + TopoDS_Face supportface = getSupportFace(); + supportface.Move(invObjLoc); + + if (Reversed.getValue()) + dir.Reverse(); + + TopExp_Explorer Ex(supportface,TopAbs_WIRE); + if (!Ex.More()) + supportface = TopoDS_Face(); + RevolMode mode = RevolMode::CutFromBase; + generateRevolution(result, base, sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True); + + result = refineShapeIfActive(result); + + // the result we get here is the shape _after_ the operation is done + // And the really expensive way to get the SubShape... + BRepAlgoAPI_Cut mkCut(base, result); + if (!mkCut.IsDone()) + return new App::DocumentObjectExecReturn("Groove: Up to face: Could not get SubShape!"); + // FIXME: In some cases this affects the Shape property: It is set to the same shape as the SubShape!!!! + TopoDS_Shape subshape = refineShapeIfActive(mkCut.Shape()); + this->AddSubShape.setValue(subshape); + + int resultCount = countSolids(result); + if (resultCount > 1) { + return new App::DocumentObjectExecReturn("Groove: Result has multiple solids. This is not supported at this time."); + } + + this->Shape.setValue(getSolid(result)); + } + else { + bool midplane = Midplane.getValue(); + bool reversed = Reversed.getValue(); + generateRevolution(result, sketchshape, gp_Ax1(pnt, dir), angle, angle2, midplane, reversed, method); + + if (result.IsNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!")); - if (RevolMaker.IsDone()) { - TopoDS_Shape result = RevolMaker.Shape(); // set the subtractive shape property for later usage in e.g. pattern result = refineShapeIfActive(result); this->AddSubShape.setValue(result); @@ -167,10 +223,10 @@ App::DocumentObjectExecReturn *Groove::execute() if (solidCount > 1) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); } - } - 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; } @@ -207,4 +263,155 @@ void Groove::updateAxis() } } +Groove::RevolMethod Groove::methodFromString(const std::string& methodStr) +{ + if (methodStr == "Angle") + return RevolMethod::Dimension; + if (methodStr == "UpToLast") + return RevolMethod::ToLast; + if (methodStr == "ThroughAll") + return RevolMethod::ThroughAll; + if (methodStr == "UpToFirst") + return RevolMethod::ToFirst; + if (methodStr == "UpToFace") + return RevolMethod::ToFace; + if (methodStr == "TwoAngles") + return RevolMethod::TwoDimensions; + + throw Base::ValueError("Groove:: No such method"); + return RevolMethod::Dimension; +} + +void Groove::generateRevolution(TopoDS_Shape& revol, + const TopoDS_Shape& sketchshape, + const gp_Ax1& axis, + const double angle, + const double angle2, + const bool midplane, + const bool reversed, + RevolMethod method) +{ + if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) { + double angleTotal = angle; + double angleOffset = 0.; + + if (method == RevolMethod::TwoDimensions) { + // Rotate the face by `angle2`/`angle` to get "second" angle + angleTotal += angle2; + angleOffset = angle2 * -1.0; + } + else if (method == RevolMethod::ThroughAll) { + angleTotal = 2 * M_PI; + } + else if (midplane) { + // Rotate the face by half the angle to get Groove 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); + } + else if (reversed) { + angleTotal *= -1.0; + } + + // 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 { + std::stringstream str; + str << "ProfileBased: Internal error: Unknown method for generateGroove()"; + throw Base::RuntimeError(str.str()); + } +} + +void Groove::generateRevolution(TopoDS_Shape& revol, + const TopoDS_Shape& baseshape, + const TopoDS_Shape& profileshape, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const gp_Ax1& axis, + RevolMethod method, + RevolMode Mode, + 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; + } + else { + std::stringstream str; + str << "ProfileBased: Internal error: Unknown method for generateRevolution()"; + throw Base::RuntimeError(str.str()); + } +} + +void Groove::updateProperties(RevolMethod method) +{ + // disable settings that are not valid on the current method + // disable everything unless we are sure we need it + bool isAngleEnabled = false; + bool isAngle2Enabled = false; + bool isMidplaneEnabled = false; + bool isReversedEnabled = false; + bool isUpToFaceEnabled = false; + if (method == RevolMethod::Dimension) { + isAngleEnabled = true; + isMidplaneEnabled = true; + isReversedEnabled = !Midplane.getValue(); + } + else if (method == RevolMethod::ToLast) { + isReversedEnabled = true; + } + else if (method == RevolMethod::ThroughAll) { + isMidplaneEnabled = true; + isReversedEnabled = !Midplane.getValue(); + } + else if (method == RevolMethod::ToFirst) { + isReversedEnabled = true; + } + else if (method == RevolMethod::ToFace) { + isReversedEnabled = true; + isUpToFaceEnabled = true; + } + else if (method == RevolMethod::TwoDimensions) { + isAngleEnabled = true; + isAngle2Enabled = true; + isReversedEnabled = true; + } + + Angle.setReadOnly(!isAngleEnabled); + Angle2.setReadOnly(!isAngle2Enabled); + Midplane.setReadOnly(!isMidplaneEnabled); + Reversed.setReadOnly(!isReversedEnabled); + UpToFace.setReadOnly(!isUpToFaceEnabled); +} + + } diff --git a/src/Mod/PartDesign/App/FeatureGroove.h b/src/Mod/PartDesign/App/FeatureGroove.h index 0ddd3f4960..8e9c2d40c6 100644 --- a/src/Mod/PartDesign/App/FeatureGroove.h +++ b/src/Mod/PartDesign/App/FeatureGroove.h @@ -37,9 +37,11 @@ class PartDesignExport Groove : public ProfileBased public: Groove(); + App::PropertyEnumeration Type; App::PropertyVector Base; App::PropertyVector Axis; App::PropertyAngle Angle; + App::PropertyAngle Angle2; /** if this property is set to a valid link, both Axis and Base properties * are calculated according to the linked line @@ -65,11 +67,64 @@ public: /// suggests a value for Reversed flag so that material is always removed from the support bool suggestReversed(); + + enum class RevolMethod { + Dimension, + ThroughAll, + ToLast = ThroughAll, + ToFirst, + ToFace, + TwoDimensions + }; + protected: /// updates Axis from ReferenceAxis void updateAxis(); static const App::PropertyAngle::Constraints floatAngle; + + // See BRepFeat_MakeRevol + enum RevolMode { + CutFromBase = 0, + FuseWithBase = 1, + None = 2 + }; + + RevolMethod methodFromString(const std::string& methodStr); + + /** + * Generates a [groove] of the input sketchshape and stores it in the given \a revol. + */ + void generateRevolution(TopoDS_Shape& revol, + const TopoDS_Shape& sketchshape, + const gp_Ax1& ax1, + const double angle, + const double angle2, + const bool midplane, + const bool reversed, + RevolMethod method); + + /** + * 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, + const TopoDS_Shape& profileshape, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const gp_Ax1& ax1, + RevolMethod method, + RevolMode Mode, + Standard_Boolean Modify); + + /** + * Disables settings that are not valid for the current method + */ + void updateProperties(RevolMethod method); + +private: + static const char* TypeEnums[]; }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/App/FeatureRevolution.cpp b/src/Mod/PartDesign/App/FeatureRevolution.cpp index 57312f172b..769e2545ef 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.cpp +++ b/src/Mod/PartDesign/App/FeatureRevolution.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -42,6 +43,7 @@ using namespace PartDesign; namespace PartDesign { +const char* Revolution::TypeEnums[]= {"Angle", "UpToLast", "UpToFirst", "UpToFace", "TwoAngles", nullptr}; PROPERTY_SOURCE(PartDesign::Revolution, PartDesign::ProfileBased) @@ -51,9 +53,14 @@ Revolution::Revolution() { addSubType = FeatureAddSub::Additive; + ADD_PROPERTY_TYPE(Type, (0L), "Revolution", App::Prop_None, "Revolution type"); + Type.setEnums(TypeEnums); ADD_PROPERTY_TYPE(Base,(Base::Vector3d(0.0,0.0,0.0)),"Revolution", App::Prop_ReadOnly, "Base"); ADD_PROPERTY_TYPE(Axis,(Base::Vector3d(0.0,1.0,0.0)),"Revolution", App::Prop_ReadOnly, "Axis"); ADD_PROPERTY_TYPE(Angle,(360.0),"Revolution", App::Prop_None, "Angle"); + ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Revolution", App::Prop_None, "Face where revolution will end"); + ADD_PROPERTY_TYPE(Angle2, (60.0), "Revolution", App::Prop_None, "Revolution length in 2nd direction"); + Angle.setConstraints(&floatAngle); ADD_PROPERTY_TYPE(ReferenceAxis,(nullptr),"Revolution",(App::Prop_None),"Reference axis of revolution"); } @@ -64,7 +71,9 @@ short Revolution::mustExecute() const ReferenceAxis.isTouched() || Axis.isTouched() || Base.isTouched() || - Angle.isTouched()) + UpToFace.isTouched() || + Angle.isTouched() || + Angle2.isTouched()) return 1; return ProfileBased::mustExecute(); } @@ -72,17 +81,16 @@ short Revolution::mustExecute() const App::DocumentObjectExecReturn *Revolution::execute() { // Validate parameters - double angle = Angle.getValue(); - if (angle > 360.0) + // All angles are in radians unless explicitly stated + double angleDeg = Angle.getValue(); + if (angleDeg > 360.0) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large")); - angle = Base::toRadians(angle); + double angle = Base::toRadians(angleDeg); if (angle < Precision::Angular()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small")); - // Reverse angle if selected - if (Reversed.getValue() && !Midplane.getValue()) - angle *= (-1.0); + double angle2 = Base::toRadians(Angle2.getValue()); TopoDS_Shape sketchshape; try { @@ -117,13 +125,7 @@ App::DocumentObjectExecReturn *Revolution::execute() if (sketchshape.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); - // Rotate the face by half the angle to get Revolution 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); - } + RevolMethod method = methodFromString(Type.getValueAsString()); this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); @@ -140,11 +142,42 @@ App::DocumentObjectExecReturn *Revolution::execute() return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); } - // revolve the face to a solid - BRepPrimAPI_MakeRevol RevolMaker(sketchshape, gp_Ax1(pnt, dir), angle); + // Create a fresh support even when base exists so that it can be used for patterns + TopoDS_Shape result; + TopoDS_Face supportface = getSupportFace(); + supportface.Move(invObjLoc); - if (RevolMaker.IsDone()) { - TopoDS_Shape result = RevolMaker.Shape(); + if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst || method == RevolMethod::ToLast) { + TopoDS_Face upToFace; + if (method == RevolMethod::ToFace) { + getFaceFromLinkSub(upToFace, UpToFace); + upToFace.Move(invObjLoc); + } + else + throw Base::RuntimeError("ProfileBased: Revolution up to first/last is not yet supported"); + + // TODO: This method is designed for extrusions. needs to be adapted for revolutions. + // getUpToFace(upToFace, base, supportface, sketchshape, method, dir); + + TopoDS_Face supportface = getSupportFace(); + supportface.Move(invObjLoc); + + if (Reversed.getValue()) + dir.Reverse(); + + TopExp_Explorer Ex(supportface,TopAbs_WIRE); + if (!Ex.More()) + supportface = TopoDS_Face(); + RevolMode mode = RevolMode::None; + generateRevolution(result, base, sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True); + } + else { + bool midplane = Midplane.getValue(); + bool reversed = Reversed.getValue(); + generateRevolution(result, sketchshape, gp_Ax1(pnt, dir), angle, angle2, midplane, reversed, method); + } + + if (!result.IsNull()) { result = refineShapeIfActive(result); // set the additive shape property for later usage in e.g. pattern this->AddSubShape.setValue(result); @@ -164,6 +197,9 @@ App::DocumentObjectExecReturn *Revolution::execute() 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) { @@ -202,4 +238,153 @@ void Revolution::updateAxis() Axis.setValue(dir.x,dir.y,dir.z); } +Revolution::RevolMethod Revolution::methodFromString(const std::string& methodStr) +{ + if (methodStr == "Angle") + return RevolMethod::Dimension; + if (methodStr == "UpToLast") + return RevolMethod::ToLast; + if (methodStr == "ThroughAll") + return RevolMethod::ThroughAll; + if (methodStr == "UpToFirst") + return RevolMethod::ToFirst; + if (methodStr == "UpToFace") + return RevolMethod::ToFace; + if (methodStr == "TwoAngles") + return RevolMethod::TwoDimensions; + + throw Base::ValueError("Revolution:: No such method"); + return RevolMethod::Dimension; +} + +void Revolution::generateRevolution(TopoDS_Shape& revol, + const TopoDS_Shape& sketchshape, + const gp_Ax1& axis, + const double angle, + const double angle2, + const bool midplane, + const bool reversed, + RevolMethod method) +{ + if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) { + double angleTotal = angle; + double angleOffset = 0.; + + if (method == RevolMethod::TwoDimensions) { + // Rotate the face by `angle2`/`angle` to get "second" angle + angleTotal += angle2; + angleOffset = angle2 * -1.0; + } + else if (midplane) { + // 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."); + + gp_Ax1 revolAx(axis); + if (reversed) { + revolAx.Reverse(); + } + + TopoDS_Shape from = sketchshape; + if (method == RevolMethod::TwoDimensions || 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, revolAx, angleTotal); + + if (!RevolMaker.IsDone()) + throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!"); + else + revol = RevolMaker.Shape(); + } + else { + std::stringstream str; + str << "ProfileBased: Internal error: Unknown method for generateRevolution()"; + throw Base::RuntimeError(str.str()); + } +} + +void Revolution::generateRevolution(TopoDS_Shape& revol, + const TopoDS_Shape& baseshape, + const TopoDS_Shape& profileshape, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const gp_Ax1& axis, + RevolMethod method, + RevolMode Mode, + 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; + } + else { + std::stringstream str; + str << "ProfileBased: Internal error: Unknown method for generateRevolution()"; + throw Base::RuntimeError(str.str()); + } +} + +void Revolution::updateProperties(RevolMethod method) +{ + // disable settings that are not valid on the current method + // disable everything unless we are sure we need it + bool isAngleEnabled = false; + bool isAngle2Enabled = false; + bool isMidplaneEnabled = false; + bool isReversedEnabled = false; + bool isUpToFaceEnabled = false; + if (method == RevolMethod::Dimension) { + isAngleEnabled = true; + isMidplaneEnabled = true; + isReversedEnabled = !Midplane.getValue(); + } + else if (method == RevolMethod::ToLast) { + isReversedEnabled = true; + } + else if (method == RevolMethod::ThroughAll) { + isMidplaneEnabled = true; + isReversedEnabled = !Midplane.getValue(); + } + else if (method == RevolMethod::ToFirst) { + isReversedEnabled = true; + } + else if (method == RevolMethod::ToFace) { + isReversedEnabled = true; + isUpToFaceEnabled = true; + } + else if (method == RevolMethod::TwoDimensions) { + isAngleEnabled = true; + isAngle2Enabled = true; + isReversedEnabled = true; + } + + Angle.setReadOnly(!isAngleEnabled); + Angle2.setReadOnly(!isAngle2Enabled); + Midplane.setReadOnly(!isMidplaneEnabled); + Reversed.setReadOnly(!isReversedEnabled); + UpToFace.setReadOnly(!isUpToFaceEnabled); +} + } diff --git a/src/Mod/PartDesign/App/FeatureRevolution.h b/src/Mod/PartDesign/App/FeatureRevolution.h index 23478f46c2..bf80298875 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.h +++ b/src/Mod/PartDesign/App/FeatureRevolution.h @@ -37,9 +37,11 @@ class PartDesignExport Revolution : public ProfileBased public: Revolution(); + App::PropertyEnumeration Type; App::PropertyVector Base; App::PropertyVector Axis; App::PropertyAngle Angle; + App::PropertyAngle Angle2; /** if this property is set to a valid link, both Axis and Base properties * are calculated according to the linked line @@ -65,11 +67,64 @@ public: /// suggests a value for Reversed flag so that material is always added to the support bool suggestReversed(); + + enum class RevolMethod { + Dimension, + ThroughAll, + ToLast = ThroughAll, + ToFirst, + ToFace, + TwoDimensions + }; + protected: /// updates Axis from ReferenceAxis void updateAxis(); static const App::PropertyAngle::Constraints floatAngle; + + // See BRepFeat_MakeRevol + enum RevolMode { + CutFromBase = 0, + FuseWithBase = 1, + None = 2 + }; + + RevolMethod methodFromString(const std::string& methodStr); + + /** + * Generates a revolution of the input sketchshape and stores it in the given \a revol. + */ + void generateRevolution(TopoDS_Shape& revol, + const TopoDS_Shape& sketchshape, + const gp_Ax1& ax1, + const double angle, + const double angle2, + const bool midplane, + const bool reversed, + RevolMethod method); + + /** + * Generates a revolution 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, + const TopoDS_Shape& profileshape, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const gp_Ax1& ax1, + RevolMethod method, + RevolMode Mode, + Standard_Boolean Modify); + + /** + * Disables settings that are not valid for the current method + */ + void updateProperties(RevolMethod method); + +private: + static const char* TypeEnums[]; }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp index 134bd92399..6db855f57f 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp @@ -49,7 +49,8 @@ using namespace Gui; TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* RevolutionView, QWidget *parent) : TaskSketchBasedParameters(RevolutionView, parent, "PartDesign_Revolution", tr("Revolution parameters")), ui(new Ui_TaskRevolutionParameters), - proxy(new QWidget(this)) + proxy(new QWidget(this)), + isGroove(false) { // we need a separate container widget to add all controls to ui->setupUi(proxy); @@ -59,34 +60,36 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* // bind property mirrors if (auto *rev = dynamic_cast(vp->getObject())) { this->propAngle = &(rev->Angle); + this->propAngle2 = &(rev->Angle2); this->propMidPlane = &(rev->Midplane); this->propReferenceAxis = &(rev->ReferenceAxis); this->propReversed = &(rev->Reversed); + this->propUpToFace = &(rev->UpToFace); ui->revolveAngle->bind(rev->Angle); + ui->revolveAngle2->bind(rev->Angle2); } else if (auto *rev = dynamic_cast(vp->getObject())) { + isGroove = true; this->propAngle = &(rev->Angle); + this->propAngle2 = &(rev->Angle2); this->propMidPlane = &(rev->Midplane); this->propReferenceAxis = &(rev->ReferenceAxis); this->propReversed = &(rev->Reversed); + this->propUpToFace = &(rev->UpToFace); ui->revolveAngle->bind(rev->Angle); + ui->revolveAngle2->bind(rev->Angle2); } else { throw Base::TypeError("The object is neither a Groove nor a Revolution."); } - ui->checkBoxMidplane->setChecked(propMidPlane->getValue()); - ui->checkBoxReversed->setChecked(propReversed->getValue()); - - ui->revolveAngle->setValue(propAngle->getValue()); - ui->revolveAngle->setMaximum(propAngle->getMaximum()); - ui->revolveAngle->setMinimum(propAngle->getMinimum()); + setupDialog(); blockUpdate = false; updateUI(); connectSignals(); - setFocus (); + setFocus(); // show the parts coordinate system axis for selection PartDesign::Body * body = PartDesign::Body::findBodyOf ( vp->getObject () ); @@ -103,6 +106,54 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* } } +void TaskRevolutionParameters::setupDialog() +{ + ui->checkBoxMidplane->setChecked(propMidPlane->getValue()); + ui->checkBoxReversed->setChecked(propReversed->getValue()); + + ui->revolveAngle->setValue(propAngle->getValue()); + ui->revolveAngle->setMaximum(propAngle->getMaximum()); + ui->revolveAngle->setMinimum(propAngle->getMinimum()); + + int index = 0; + + // TODO: This should also be implemented for groove + if (!isGroove) { + PartDesign::Revolution* rev = static_cast(vp->getObject()); + ui->revolveAngle2->setValue(propAngle2->getValue()); + ui->revolveAngle2->setMaximum(propAngle2->getMaximum()); + ui->revolveAngle2->setMinimum(propAngle2->getMinimum()); + + index = rev->Type.getValue(); + } + else { + PartDesign::Groove* rev = static_cast(vp->getObject()); + ui->revolveAngle2->setValue(propAngle2->getValue()); + ui->revolveAngle2->setMaximum(propAngle2->getMaximum()); + ui->revolveAngle2->setMinimum(propAngle2->getMinimum()); + + index = rev->Type.getValue(); + } + + translateModeList(index); +} + +void TaskRevolutionParameters::translateModeList(int index) +{ + ui->changeMode->clear(); + ui->changeMode->addItem(tr("Dimension")); + if (!isGroove) { + ui->changeMode->addItem(tr("To last")); + } + else { + ui->changeMode->addItem(tr("Through all")); + } + ui->changeMode->addItem(tr("To first")); + ui->changeMode->addItem(tr("Up to face")); + ui->changeMode->addItem(tr("Two dimensions")); + ui->changeMode->setCurrentIndex(index); +} + void TaskRevolutionParameters::fillAxisCombo(bool forceRefill) { Base::StateLocker lock(blockUpdate, true); @@ -179,44 +230,213 @@ void TaskRevolutionParameters::addAxisToCombo(App::DocumentObject* linkObj, lnk.setValue(linkObj,std::vector(1,linkSubname)); } -void TaskRevolutionParameters::connectSignals() +void TaskRevolutionParameters::setCheckboxes(PartDesign::Revolution::RevolMethod mode) { - connect(ui->revolveAngle, qOverload(&QuantitySpinBox::valueChanged), this, - &TaskRevolutionParameters::onAngleChanged); - connect(ui->axis, qOverload(&QComboBox::activated), this, - &TaskRevolutionParameters::onAxisChanged); - connect(ui->checkBoxMidplane, &QCheckBox::toggled, this, - &TaskRevolutionParameters::onMidplane); - connect(ui->checkBoxReversed, &QCheckBox::toggled, this, - &TaskRevolutionParameters::onReversed); - connect(ui->checkBoxUpdateView, &QCheckBox::toggled, this, - &TaskRevolutionParameters::onUpdateView); + // disable/hide everything unless we are sure we don't need it + // exception: the direction parameters are in any case visible + bool isRevolveAngleVisible = false; + bool isRevolveAngle2Visible = false; + bool isMidplaneEnabled = false; + bool isMidplaneVisible = false; + bool isReversedEnabled = false; + bool isFaceEditEnabled = false; + + if (mode == PartDesign::Revolution::RevolMethod::Dimension) { + isRevolveAngleVisible = true; + ui->revolveAngle->selectNumber(); + QMetaObject::invokeMethod(ui->revolveAngle, "setFocus", Qt::QueuedConnection); + isMidplaneVisible = true; + isMidplaneEnabled = true; + // Reverse only makes sense if Midplane is not true + isReversedEnabled = !ui->checkBoxMidplane->isChecked(); + } + else if (mode == PartDesign::Revolution::RevolMethod::ThroughAll && isGroove) { + isMidplaneEnabled = true; + isMidplaneVisible = true; + isReversedEnabled = !ui->checkBoxMidplane->isChecked(); + } + else if (mode == PartDesign::Revolution::RevolMethod::ToLast && !isGroove) { + isReversedEnabled = true; + } + else if (mode == PartDesign::Revolution::RevolMethod::ToFirst) { + isReversedEnabled = true; + } + else if (mode == PartDesign::Revolution::RevolMethod::ToFace) { + isReversedEnabled = true; + isFaceEditEnabled = true; + QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); + // Go into reference selection mode if no face has been selected yet + if (ui->lineFaceName->property("FeatureName").isNull()) + ui->buttonFace->setChecked(true); + } + else if (mode == PartDesign::Revolution::RevolMethod::TwoDimensions) { + isRevolveAngleVisible = true; + isRevolveAngle2Visible = true; + isReversedEnabled = true; + } + + ui->revolveAngle->setVisible(isRevolveAngleVisible); + ui->revolveAngle->setEnabled(isRevolveAngleVisible); + ui->labelAngle->setVisible(isRevolveAngleVisible); + + ui->revolveAngle2->setVisible(isRevolveAngle2Visible); + ui->revolveAngle2->setEnabled(isRevolveAngle2Visible); + ui->labelAngle2->setVisible(isRevolveAngle2Visible); + + ui->checkBoxMidplane->setEnabled(isMidplaneEnabled); + ui->checkBoxMidplane->setVisible(isMidplaneVisible); + + ui->checkBoxReversed->setEnabled(isReversedEnabled); + + ui->buttonFace->setEnabled(isFaceEditEnabled); + ui->lineFaceName->setEnabled(isFaceEditEnabled); + if (!isFaceEditEnabled) { + ui->buttonFace->setChecked(false); + } } -void TaskRevolutionParameters::updateUI() +void TaskRevolutionParameters::connectSignals() +{ + connect(ui->revolveAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), + this, &TaskRevolutionParameters::onAngleChanged); + connect(ui->revolveAngle2, qOverload(&Gui::QuantitySpinBox::valueChanged), + this, &TaskRevolutionParameters::onAngle2Changed); + connect(ui->axis, qOverload(&QComboBox::activated), + this, &TaskRevolutionParameters::onAxisChanged); + connect(ui->checkBoxMidplane, &QCheckBox::toggled, + this, &TaskRevolutionParameters::onMidplane); + connect(ui->checkBoxReversed, &QCheckBox::toggled, + this, &TaskRevolutionParameters::onReversed); + connect(ui->checkBoxUpdateView, &QCheckBox::toggled, + this, &TaskRevolutionParameters::onUpdateView); + connect(ui->changeMode, qOverload(&QComboBox::currentIndexChanged), + this, &TaskRevolutionParameters::onModeChanged); + connect(ui->buttonFace, &QPushButton::toggled, + this, &TaskRevolutionParameters::onButtonFace); + connect(ui->lineFaceName, &QLineEdit::textEdited, + this, &TaskRevolutionParameters::onFaceName); +} + +void TaskRevolutionParameters::updateUI(int index) { if (blockUpdate) return; Base::StateLocker lock(blockUpdate, true); fillAxisCombo(); + setCheckboxes(static_cast(index)); } void TaskRevolutionParameters::onSelectionChanged(const Gui::SelectionChanges& msg) { if (msg.Type == Gui::SelectionChanges::AddSelection) { + if (selectionFace) { + QString refText = onAddSelection(msg); + if (refText.length() > 0) { + QSignalBlocker block(ui->lineFaceName); + ui->lineFaceName->setText(refText); + ui->lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); + ui->lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); + // Turn off reference selection mode + ui->buttonFace->setChecked(false); + } + else { + clearFaceName(); + } + } + else { + exitSelectionMode(); + std::vector axis; + App::DocumentObject* selObj; + if (getReferencedSelection(vp->getObject(), msg, selObj, axis) && selObj) { + propReferenceAxis->setValue(selObj, axis); - exitSelectionMode(); - std::vector axis; - App::DocumentObject* selObj; - if (getReferencedSelection(vp->getObject(), msg, selObj, axis) && selObj) { - propReferenceAxis->setValue(selObj, axis); + recomputeFeature(); + updateUI(); + } + } + } + else if (msg.Type == Gui::SelectionChanges::ClrSelection && selectionFace) { + clearFaceName(); + } +} - recomputeFeature(); - updateUI(); +void TaskRevolutionParameters::onButtonFace(const bool pressed) +{ + // to distinguish that this is the direction selection + selectionFace = true; + + // only faces are allowed + TaskSketchBasedParameters::onSelectReference(pressed ? AllowSelection::FACE : AllowSelection::NONE); +} + +void TaskRevolutionParameters::onFaceName(const QString& text) +{ + if (text.isEmpty()) { + // if user cleared the text field then also clear the properties + ui->lineFaceName->setProperty("FeatureName", QVariant()); + ui->lineFaceName->setProperty("FaceName", QVariant()); + } + else { + // expect that the label of an object is used + QStringList parts = text.split(QChar::fromLatin1(':')); + QString label = parts[0]; + QVariant name = objectNameByLabel(label, ui->lineFaceName->property("FeatureName")); + if (name.isValid()) { + parts[0] = name.toString(); + QString uptoface = parts.join(QString::fromLatin1(":")); + ui->lineFaceName->setProperty("FeatureName", name); + ui->lineFaceName->setProperty("FaceName", setUpToFace(uptoface)); + } + else { + ui->lineFaceName->setProperty("FeatureName", QVariant()); + ui->lineFaceName->setProperty("FaceName", QVariant()); } } } +void TaskRevolutionParameters::translateFaceName() +{ + ui->lineFaceName->setPlaceholderText(tr("No face selected")); + QVariant featureName = ui->lineFaceName->property("FeatureName"); + if (featureName.isValid()) { + QStringList parts = ui->lineFaceName->text().split(QChar::fromLatin1(':')); + QByteArray upToFace = ui->lineFaceName->property("FaceName").toByteArray(); + int faceId = -1; + bool ok = false; + if (upToFace.indexOf("Face") == 0) { + faceId = upToFace.remove(0,4).toInt(&ok); + } + + if (ok) { + ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") + .arg(parts[0]) + .arg(tr("Face")) + .arg(faceId)); + } + else { + ui->lineFaceName->setText(parts[0]); + } + } +} + +QString TaskRevolutionParameters::getFaceName(void) const +{ + QVariant featureName = ui->lineFaceName->property("FeatureName"); + if (featureName.isValid()) { + QString faceName = ui->lineFaceName->property("FaceName").toString(); + return getFaceReference(featureName.toString(), faceName); + } + + return QString::fromLatin1("None"); +} + +void TaskRevolutionParameters::clearFaceName() +{ + QSignalBlocker block(ui->lineFaceName); + ui->lineFaceName->clear(); + ui->lineFaceName->setProperty("FeatureName", QVariant()); + ui->lineFaceName->setProperty("FaceName", QVariant()); +} void TaskRevolutionParameters::onAngleChanged(double len) { @@ -225,6 +445,14 @@ void TaskRevolutionParameters::onAngleChanged(double len) recomputeFeature(); } +void TaskRevolutionParameters::onAngle2Changed(double len) +{ + if (propAngle2) + propAngle2->setValue(len); + exitSelectionMode(); + recomputeFeature(); +} + void TaskRevolutionParameters::onAxisChanged(int num) { if (blockUpdate) @@ -301,6 +529,42 @@ void TaskRevolutionParameters::onReversed(bool on) recomputeFeature(); } +void TaskRevolutionParameters::onModeChanged(int index) +{ + App::PropertyEnumeration* pcType; + if (!isGroove) + pcType = &(static_cast(vp->getObject())->Type); + else + pcType = &(static_cast(vp->getObject())->Type); + + switch (static_cast(index)) { + case PartDesign::Revolution::RevolMethod::Dimension: + pcType->setValue("Angle"); + // Avoid error message + // if (ui->revolveAngle->value() < Base::Quantity(Precision::Angular(), Base::Unit::Angle)) // TODO: Ensure radians/degree consistency + // ui->revolveAngle->setValue(5.0); + break; + case PartDesign::Revolution::RevolMethod::ToLast: + if (!isGroove) + pcType->setValue("UpToLast"); + else + pcType->setValue("ThroughAll"); + break; + case PartDesign::Revolution::RevolMethod::ToFirst: + pcType->setValue("UpToFirst"); + break; + case PartDesign::Revolution::RevolMethod::ToFace: + pcType->setValue("UpToFace"); + break; + case PartDesign::Revolution::RevolMethod::TwoDimensions: + pcType->setValue("TwoAngles"); + break; + } + + updateUI(index); + recomputeFeature(); +} + void TaskRevolutionParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const { if (axesInList.empty()) @@ -354,6 +618,9 @@ void TaskRevolutionParameters::changeEvent(QEvent *event) TaskBox::changeEvent(event); if (event->type() == QEvent::LanguageChange) { ui->retranslateUi(proxy); + + // Translate mode items + translateModeList(ui->changeMode->currentIndex()); } } @@ -361,14 +628,22 @@ void TaskRevolutionParameters::apply() { //Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Revolution changed")); ui->revolveAngle->apply(); + ui->revolveAngle2->apply(); std::vector sub; App::DocumentObject* obj; getReferenceAxis(obj, sub); std::string axis = buildLinkSingleSubPythonStr(obj, sub); auto tobj = vp->getObject(); - FCMD_OBJ_CMD(tobj,"ReferenceAxis = " << axis); - FCMD_OBJ_CMD(tobj,"Midplane = " << (getMidplane() ? 1 : 0)); - FCMD_OBJ_CMD(tobj,"Reversed = " << (getReversed() ? 1 : 0)); + FCMD_OBJ_CMD(tobj, "ReferenceAxis = " << axis); + FCMD_OBJ_CMD(tobj, "Midplane = " << (getMidplane() ? 1 : 0)); + FCMD_OBJ_CMD(tobj, "Reversed = " << (getReversed() ? 1 : 0)); + int mode = ui->changeMode->currentIndex(); + FCMD_OBJ_CMD(tobj, "Type = " << mode); + QString facename = QString::fromLatin1("None"); + if (static_cast(mode) == PartDesign::Revolution::RevolMethod::ToFace) { + facename = getFaceName(); + } + FCMD_OBJ_CMD(tobj, "UpToFace = " << facename.toLatin1().data()); } //************************************************************************** diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h index 1d81ea59a4..c00d7819b2 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h @@ -23,6 +23,8 @@ #ifndef GUI_TASKVIEW_TaskRevolutionParameters_H #define GUI_TASKVIEW_TaskRevolutionParameters_H +#include +#include #include "TaskSketchBasedParameters.h" #include "ViewProviderRevolution.h" @@ -61,9 +63,13 @@ public: private Q_SLOTS: void onAngleChanged(double); + void onAngle2Changed(double); void onAxisChanged(int); void onMidplane(bool); void onReversed(bool); + void onModeChanged(int); + void onButtonFace(const bool pressed = true); + void onFaceName(const QString& text); protected: void onSelectionChanged(const Gui::SelectionChanges& msg) override; @@ -71,21 +77,32 @@ protected: void getReferenceAxis(App::DocumentObject *&obj, std::vector &sub) const; bool getMidplane() const; bool getReversed() const; + QString getFaceName() const; + void setupDialog(void); + void setCheckboxes(PartDesign::Revolution::RevolMethod mode); //mirrors of revolution's or groove's properties //should have been done by inheriting revolution and groove from common class... App::PropertyAngle* propAngle; + App::PropertyAngle* propAngle2; App::PropertyBool* propReversed; App::PropertyBool* propMidPlane; App::PropertyLinkSub* propReferenceAxis; + App::PropertyLinkSub* propUpToFace; private: void connectSignals(); - void updateUI(); + void updateUI(int index=0); // TODO: Implement for index and remove default + void translateModeList(int index); + // TODO: This is common with extrude. Maybe send to superclass. + void translateFaceName(); + void clearFaceName(); private: std::unique_ptr ui; QWidget *proxy; + bool selectionFace; + bool isGroove; /** * @brief axesInList is the list of links corresponding to axis combo; must diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.ui b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.ui index 5f55ae1e12..d9c57167aa 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.ui @@ -14,6 +14,26 @@ Form + + + + + + Type + + + + + + + + Dimension + + + + + + @@ -62,7 +82,7 @@ - + Angle: @@ -109,6 +129,56 @@ + + + + + + 2nd angle + + + + + + + false + + + deg + + + 0.000000000000000 + + + 360.000000000000000 + + + 10.000000000000000 + + + 60.000000000000000 + + + + + + + + + + + Face + + + true + + + + + + + +