From acdc4041fdfaa4a2c428ce9c971703fa00e86ca3 Mon Sep 17 00:00:00 2001 From: Captain <87000456+captain0xff@users.noreply.github.com> Date: Sun, 25 Jan 2026 04:30:07 +0530 Subject: [PATCH] PartDesign: Added interactive gizmo for draft operation (#27111) * PartDesign: Add interactive gizmo for draft operation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/Gui/Inventor/Draggers/Gizmo.cpp | 4 +- src/Gui/Inventor/Draggers/Gizmo.h | 4 +- .../Inventor/Draggers/SoRotationDragger.cpp | 8 ++ src/Mod/Part/App/GizmoHelper.cpp | 76 ++++++++++++- src/Mod/Part/App/GizmoHelper.h | 14 ++- src/Mod/PartDesign/App/FeatureDraft.cpp | 10 +- src/Mod/PartDesign/App/FeatureDraft.h | 21 ++++ .../PartDesign/Gui/TaskDraftParameters.cpp | 100 ++++++++++++++++-- src/Mod/PartDesign/Gui/TaskDraftParameters.h | 17 ++- 9 files changed, 232 insertions(+), 22 deletions(-) diff --git a/src/Gui/Inventor/Draggers/Gizmo.cpp b/src/Gui/Inventor/Draggers/Gizmo.cpp index ff766c2df7..01ee52caa4 100644 --- a/src/Gui/Inventor/Draggers/Gizmo.cpp +++ b/src/Gui/Inventor/Draggers/Gizmo.cpp @@ -461,13 +461,13 @@ void RotationGizmo::draggingContinued() void RotationGizmo::orientAlongCamera(SoCamera* camera) { - if (linearGizmo == nullptr || automaticOrientation == false) { + if (!automaticOrientation) { return; } SbVec3f cameraDir {0, 0, 1}; camera->orientation.getValue().multVec(cameraDir, cameraDir); - SbVec3f pointerDir = linearGizmo->getDraggerContainer()->getPointerDirection(); + SbVec3f pointerDir = getDraggerContainer()->getPointerDirection(); pointerDir.normalize(); auto proj = cameraDir - cameraDir.dot(pointerDir) * pointerDir; diff --git a/src/Gui/Inventor/Draggers/Gizmo.h b/src/Gui/Inventor/Draggers/Gizmo.h index f909ad185a..4b63878840 100644 --- a/src/Gui/Inventor/Draggers/Gizmo.h +++ b/src/Gui/Inventor/Draggers/Gizmo.h @@ -140,6 +140,9 @@ public: // Distance between the linear gizmo base and rotation gizmo double sepDistance = 0; + // Controls if the gizmo is automatically rotated around the pointer in the + // to always face the camera + bool automaticOrientation = false; // Returns the position and rotation of the base of the dragger GizmoPlacement getDraggerPlacement() override; @@ -164,7 +167,6 @@ private: SoRotationDraggerContainer* draggerContainer = nullptr; SoFieldSensor translationSensor; LinearGizmo* linearGizmo = nullptr; - bool automaticOrientation = false; QMetaObject::Connection quantityChangedConnection; QMetaObject::Connection formulaDialogConnection; diff --git a/src/Gui/Inventor/Draggers/SoRotationDragger.cpp b/src/Gui/Inventor/Draggers/SoRotationDragger.cpp index 6d7ce52d77..b5dadbdbfb 100644 --- a/src/Gui/Inventor/Draggers/SoRotationDragger.cpp +++ b/src/Gui/Inventor/Draggers/SoRotationDragger.cpp @@ -399,6 +399,14 @@ void SoRotationDraggerContainer::setArcNormalDirection(const SbVec3f& dir) auto currentRot = rotation.getValue(); currentRot.multVec(currentNormal, currentNormal); + // If the two directions are collinear and opposite then ensure that the + // dragger is rotated along the pointer axis + if (dir.equals(-currentNormal, 1e-5)) { + SbRotation rot {getPointerDirection(), std::numbers::pi_v}; + rotation = currentRot * rot; + return; + } + SbRotation rot {currentNormal, dir}; rotation = currentRot * rot; } diff --git a/src/Mod/Part/App/GizmoHelper.cpp b/src/Mod/Part/App/GizmoHelper.cpp index 9492301be1..02ddc0450c 100644 --- a/src/Mod/Part/App/GizmoHelper.cpp +++ b/src/Mod/Part/App/GizmoHelper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,8 @@ #include #include #include +#include +#include #include #include @@ -170,7 +173,7 @@ DraggerPlacementProps getDraggerPlacementFromEdgeAndFace(Part::TopoShape& edge, dir = -dir; } - return {position, dir, tangent}; + return {position, dir}; } DraggerPlacementProps getDraggerPlacementFromEdgeAndFace(Part::TopoShape& edge, Part::TopoShape& face) @@ -245,3 +248,74 @@ Base::Vector3d getMidPointFromProfile(Part::TopoShape& profile) return midPoint; } + +std::optional getDraggerPlacementFromPlaneAndFace( + Part::TopoShape& face, + gp_Pln& plane +) +{ + TopoDS_Face TDSFace = TopoDS::Face(face.getShape()); + if (TDSFace.IsNull()) { + return std::nullopt; + } + + auto cog = getCentreOfMassFromFace(TDSFace); + auto orientation = TDSFace.Orientation(); + + auto getPropsFromShapePlaneIntersection = + [&cog](auto&& shape, const gp_Pln& plane) -> std::optional { + if (plane.Axis().IsNormal(shape.Axis(), Precision::Angular())) { + return std::nullopt; + } + + gp_Lin line(shape.Axis()); + IntAna_IntConicQuad intersector(line, plane, Precision::Confusion()); + if (intersector.IsDone() && intersector.NbPoints() > 0) { + auto pos = Base::convertTo(intersector.Point(1)); + return DraggerPlacementPropsWithNormals { + .placementProps = {.position = pos, .dir = cog - pos}, + .normalProps = std::nullopt + }; + } + return std::nullopt; + }; + + auto getPropsFromPlanePlaneIntersection = [&cog, orientation]( + const gp_Pln&& facePlane, + const gp_Pln& plane + ) -> std::optional { + if (plane.Axis().IsParallel(facePlane.Axis(), Precision::Angular())) { + return std::nullopt; + } + + IntAna_QuadQuadGeo intersector(facePlane, plane, Precision::Angular(), Precision::Confusion()); + if (intersector.IsDone() && intersector.NbSolutions() > 0) { + gp_Lin line = intersector.Line(1); + Standard_Real u = ElCLib::Parameter(line, Base::convertTo(cog)); + auto pos = Base::convertTo(ElCLib::Value(u, line)); + auto lineDir = Base::convertTo(line.Direction()); + auto faceNormal = Base::convertTo(facePlane.Axis().Direction()); + if (orientation == TopAbs_REVERSED) { + faceNormal *= -1; + } + + return DraggerPlacementPropsWithNormals { + .placementProps = {.position = pos, .dir = cog - pos}, + .normalProps = DraggerNormalProps {.normal = lineDir, .faceNormal = faceNormal} + }; + } + return std::nullopt; + }; + + BRepAdaptor_Surface adapt(TDSFace); + switch (adapt.GetType()) { + case GeomAbs_Cylinder: + return getPropsFromShapePlaneIntersection(adapt.Cylinder(), plane); + case GeomAbs_Cone: + return getPropsFromShapePlaneIntersection(adapt.Cone(), plane); + case GeomAbs_Plane: + return getPropsFromPlanePlaneIntersection(adapt.Plane(), plane); + default: + return std::nullopt; + } +} diff --git a/src/Mod/Part/App/GizmoHelper.h b/src/Mod/Part/App/GizmoHelper.h index 045d6287fb..f16e4bfbe0 100644 --- a/src/Mod/Part/App/GizmoHelper.h +++ b/src/Mod/Part/App/GizmoHelper.h @@ -64,7 +64,6 @@ struct PartExport DraggerPlacementProps { Base::Vector3d position; Base::Vector3d dir; - Base::Vector3d tangent; }; DraggerPlacementProps PartExport getDraggerPlacementFromEdgeAndFace(Part::TopoShape& edge, TopoDS_Face& face); @@ -78,4 +77,17 @@ Base::Vector3d PartExport getMidPointFromFace(Part::TopoShape& face); Base::Vector3d PartExport getMidPointFromProfile(Part::TopoShape& profile); +struct PartExport DraggerNormalProps +{ + Base::Vector3d normal; + Base::Vector3d faceNormal; +}; +struct PartExport DraggerPlacementPropsWithNormals +{ + DraggerPlacementProps placementProps; + std::optional normalProps; +}; +std::optional PartExport +getDraggerPlacementFromPlaneAndFace(Part::TopoShape& face, gp_Pln& plane); + #endif /* GIZMO_HELPER_H */ diff --git a/src/Mod/PartDesign/App/FeatureDraft.cpp b/src/Mod/PartDesign/App/FeatureDraft.cpp index 14a004b05b..9b11bfdbdc 100644 --- a/src/Mod/PartDesign/App/FeatureDraft.cpp +++ b/src/Mod/PartDesign/App/FeatureDraft.cpp @@ -214,14 +214,12 @@ App::DocumentObjectExecReturn* Draft::execute() if (!intersector.IsDone() || intersector.NbLines() < 1) { continue; } - Handle(Geom_Curve) icurve = intersector.Line(1); + const Handle(Geom_Curve) & icurve = intersector.Line(1); if (!icurve->IsKind(STANDARD_TYPE(Geom_Line))) { continue; } - // TODO: How to extract the line from icurve without creating an edge first? - TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(icurve); - BRepAdaptor_Curve c(edge); - neutralPlane = gp_Pln(pm, c.Line().Direction()); + Handle(Geom_Line) line = Handle(Geom_Line)::DownCast(icurve); + neutralPlane = gp_Pln(pm, line->Lin().Direction()); found = true; break; } @@ -312,6 +310,8 @@ App::DocumentObjectExecReturn* Draft::execute() angle *= -1.0; } + computeProps = {pullDirection, neutralPlane}; + this->positionByBaseFeature(); // create an untransformed copy of the base shape Part::TopoShape baseShape(TopShape); diff --git a/src/Mod/PartDesign/App/FeatureDraft.h b/src/Mod/PartDesign/App/FeatureDraft.h index 52ed8bafcc..08f9415909 100644 --- a/src/Mod/PartDesign/App/FeatureDraft.h +++ b/src/Mod/PartDesign/App/FeatureDraft.h @@ -25,6 +25,9 @@ #ifndef PARTDESIGN_FEATUREDRAFT_H #define PARTDESIGN_FEATUREDRAFT_H +#include +#include + #include #include #include @@ -32,6 +35,11 @@ namespace PartDesign { +struct PartDesignExport DraftComputeProps +{ + gp_Dir pullDirection; + gp_Pln neutralPlane; +}; class PartDesignExport Draft: public DressUp { @@ -57,6 +65,17 @@ public: } //@} + /** + * @brief getLastComputedProps: Returns the Pull Direction and Neutral Plane + * computed during the last call of the execute method. + * Note: The returned values might be in the default initialized state if + * they were not computed or computation failed + */ + DraftComputeProps getLastComputedProps() const + { + return computeProps; + } + private: void handleChangedPropertyType( Base::XMLReader& reader, @@ -64,6 +83,8 @@ private: App::Property* prop ) override; static const App::PropertyAngle::Constraints floatAngle; + + DraftComputeProps computeProps; }; } // namespace PartDesign diff --git a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp index d29338ecce..2b8fc3f023 100644 --- a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp @@ -27,17 +27,20 @@ #include #include - -#include #include #include +#include #include +#include #include #include #include +#include +#include #include #include #include +#include #include "ui_TaskDraftParameters.h" #include "TaskDraftParameters.h" @@ -117,6 +120,8 @@ TaskDraftParameters::TaskDraftParameters(ViewProviderDressUp* DressUpView, QWidg else { hideOnError(); } + + setupGizmos(DressUpView); } void TaskDraftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) @@ -145,11 +150,12 @@ void TaskDraftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) getDressUpView()->highlightReferences(true); // hide the draft if there was a computation error hideOnError(); + setGizmoPositions(); } else if (selectionMode == line) { auto pcDraft = getObject(); std::vector edges; - App::DocumentObject* selObj; + App::DocumentObject* selObj = nullptr; getReferencedSelection(pcDraft, msg, selObj, edges); if (!selObj) { return; @@ -163,8 +169,14 @@ void TaskDraftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) getDressUpView()->highlightReferences(true); // hide the draft if there was a computation error hideOnError(); + setGizmoPositions(); } } + else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + // TODO: the gizmo position should be only recalculated when the feature associated + // with the gizmo is removed from the list + setGizmoPositions(); + } } void TaskDraftParameters::setButtons(const selectionModes mode) @@ -244,15 +256,17 @@ double TaskDraftParameters::getAngle() const return ui->draftAngle->value().getValue(); } -void TaskDraftParameters::onReversedChanged(const bool on) +void TaskDraftParameters::onReversedChanged(const bool reversed) { if (auto draft = getObject()) { setButtons(none); setupTransaction(); - draft->Reversed.setValue(on); + draft->Reversed.setValue(reversed); draft->recomputeFeature(); // hide the draft if there was a computation error hideOnError(); + + setGizmoPositions(); } } @@ -291,15 +305,83 @@ void TaskDraftParameters::apply() TaskDressUpParameters::apply(); } + +void TaskDraftParameters::setupGizmos(ViewProvider* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + angleGizmo = new Gui::RotationGizmo(ui->draftAngle); + + gizmoContainer = GizmoContainer::create({angleGizmo}, vp); + + setGizmoPositions(); +} + +void TaskDraftParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + gizmoContainer->visible = false; + + auto draft = getObject(); + if (!draft || draft->isError()) { + return; + } + Part::TopoShape baseShape = draft->getBaseTopoShape(true); + auto faces = draft->getFaces(baseShape); + if (faces.empty()) { + return; + } + + auto [pullDirection, neutralPlane] = draft->getLastComputedProps(); + + std::optional props + = getDraggerPlacementFromPlaneAndFace(faces[0], neutralPlane); + if (!props) { + return; + } + + if (auto normalProps = props->normalProps) { + auto pos = Base::convertTo(props->placementProps.position); + auto dir = Base::convertTo(props->placementProps.dir); + auto lineDir = Base::convertTo(normalProps->normal); + auto pp = Base::convertTo(pullDirection); + + angleGizmo->setDraggerPlacement(pos, (dir.dot(pp) < 0) ? -pp : pp); + + auto rotDir = Base::convertTo(normalProps->faceNormal).cross(pp); + if (lineDir.dot(rotDir) < 0) { + lineDir *= -1; + } + if (draft->Reversed.getValue()) { + lineDir = -lineDir; + } + angleGizmo->getDraggerContainer()->setArcNormalDirection(lineDir); + angleGizmo->automaticOrientation = false; + } + else { + // The face is cone or cylinder + angleGizmo->setDraggerPlacement( + Base::convertTo(props->placementProps.position), + Base::convertTo(props->placementProps.dir) + ); + angleGizmo->automaticOrientation = true; + } + gizmoContainer->visible = true; +} + //************************************************************************** //************************************************************************** // TaskDialog //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -TaskDlgDraftParameters::TaskDlgDraftParameters(ViewProviderDraft* DressUpView) - : TaskDlgDressUpParameters(DressUpView) +TaskDlgDraftParameters::TaskDlgDraftParameters(ViewProviderDraft* DraftView) + : TaskDlgDressUpParameters(DraftView) { - parameter = new TaskDraftParameters(DressUpView); + parameter = new TaskDraftParameters(DraftView); Content.push_back(parameter); Content.push_back(preview); @@ -319,7 +401,7 @@ bool TaskDlgDraftParameters::accept() parameter->apply(); std::vector strings; - App::DocumentObject* obj; + App::DocumentObject* obj = nullptr; TaskDraftParameters* draftparameter = static_cast(parameter); draftparameter->getPlane(obj, strings); diff --git a/src/Mod/PartDesign/Gui/TaskDraftParameters.h b/src/Mod/PartDesign/Gui/TaskDraftParameters.h index 9b31b8d872..209b8ee6d1 100644 --- a/src/Mod/PartDesign/Gui/TaskDraftParameters.h +++ b/src/Mod/PartDesign/Gui/TaskDraftParameters.h @@ -30,6 +30,12 @@ class Ui_TaskDraftParameters; +namespace Gui +{ +class RotationGizmo; +class GizmoContainer; +} // namespace Gui + namespace PartDesignGui { @@ -52,17 +58,22 @@ public: private Q_SLOTS: void onAngleChanged(double angle); void onReversedChanged(bool reversed); - void onButtonPlane(const bool checked); - void onButtonLine(const bool checked); + void onButtonPlane(bool checked); + void onButtonLine(bool checked); void onRefDeleted() override; protected: - void setButtons(const selectionModes mode) override; + void setButtons(selectionModes mode) override; void changeEvent(QEvent* e) override; void onSelectionChanged(const Gui::SelectionChanges& msg) override; private: std::unique_ptr ui; + + std::unique_ptr gizmoContainer; + Gui::RotationGizmo* angleGizmo = nullptr; + void setupGizmos(ViewProvider* vp); + void setGizmoPositions(); }; /// simulation dialog for the TaskView