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/Gui/TaskRevolutionParameters.cpp b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp index 6a6bd25e89..0a1e806952 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); @@ -68,11 +69,15 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* 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."); @@ -103,7 +108,6 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* void TaskRevolutionParameters::setupDialog() { - PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); ui->checkBoxMidplane->setChecked(propMidPlane->getValue()); ui->checkBoxReversed->setChecked(propReversed->getValue()); @@ -114,7 +118,7 @@ void TaskRevolutionParameters::setupDialog() int index = 0; // TODO: This should also be implemented for groove - if (pcFeat->isDerivedFrom(PartDesign::Revolution::getClassTypeId())) { + if (!isGroove) { PartDesign::Revolution* rev = static_cast(vp->getObject()); ui->revolveAngle2->setValue(propAngle2->getValue()); ui->revolveAngle2->setMaximum(propAngle2->getMaximum()); @@ -122,6 +126,14 @@ void TaskRevolutionParameters::setupDialog() 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); } @@ -130,7 +142,12 @@ void TaskRevolutionParameters::translateModeList(int index) { ui->changeMode->clear(); ui->changeMode->addItem(tr("Dimension")); - ui->changeMode->addItem(tr("To last")); + 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")); @@ -448,26 +465,33 @@ void TaskRevolutionParameters::onReversed(bool on) void TaskRevolutionParameters::onModeChanged(int index) { - PartDesign::Revolution* pcRevolution = static_cast(vp->getObject()); + 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: - pcRevolution->Type.setValue("Angle"); + 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: - pcRevolution->Type.setValue("UpToLast"); + if (!isGroove) + pcType->setValue("UpToLast"); + else + pcType->setValue("ThroughAll"); break; case PartDesign::Revolution::RevolMethod::ToFirst: - pcRevolution->Type.setValue("UpToFirst"); + pcType->setValue("UpToFirst"); break; case PartDesign::Revolution::RevolMethod::ToFace: - pcRevolution->Type.setValue("UpToFace"); + pcType->setValue("UpToFace"); break; case PartDesign::Revolution::RevolMethod::TwoDimensions: - pcRevolution->Type.setValue("TwoAngles"); + pcType->setValue("TwoAngles"); break; } diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h index 505c9baa20..fbb0dde990 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h @@ -99,6 +99,7 @@ private: std::unique_ptr ui; QWidget *proxy; bool selectionFace; + bool isGroove; /** * @brief axesInList is the list of links corresponding to axis combo; must