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>
This commit is contained in:
Captain
2026-01-25 04:30:07 +05:30
committed by GitHub
parent 09e4d8c059
commit acdc4041fd
9 changed files with 232 additions and 22 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<float>};
rotation = currentRot * rot;
return;
}
SbRotation rot {currentNormal, dir};
rotation = currentRot * rot;
}

View File

@@ -28,6 +28,7 @@
#include <BOPTools_AlgoTools3D.hxx>
#include <BRep_Tool.hxx>
#include <BRepGProp.hxx>
#include <ElCLib.hxx>
#include <GProp_GProps.hxx>
#include <GeomAPI_ProjectPointOnSurf.hxx>
#include <TopExp_Explorer.hxx>
@@ -35,6 +36,8 @@
#include <TopoDS.hxx>
#include <Precision.hxx>
#include <IntTools_Context.hxx>
#include <IntAna_IntConicQuad.hxx>
#include <IntAna_QuadQuadGeo.hxx>
#include <Base/BoundBox.h>
#include <Base/Converter.h>
@@ -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<DraggerPlacementPropsWithNormals> 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<DraggerPlacementPropsWithNormals> {
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<Base::Vector3d>(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<DraggerPlacementPropsWithNormals> {
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<gp_Pnt>(cog));
auto pos = Base::convertTo<Base::Vector3d>(ElCLib::Value(u, line));
auto lineDir = Base::convertTo<Base::Vector3d>(line.Direction());
auto faceNormal = Base::convertTo<Base::Vector3d>(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;
}
}

View File

@@ -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<DraggerNormalProps> normalProps;
};
std::optional<DraggerPlacementPropsWithNormals> PartExport
getDraggerPlacementFromPlaneAndFace(Part::TopoShape& face, gp_Pln& plane);
#endif /* GIZMO_HELPER_H */

View File

@@ -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);

View File

@@ -25,6 +25,9 @@
#ifndef PARTDESIGN_FEATUREDRAFT_H
#define PARTDESIGN_FEATUREDRAFT_H
#include <gp_Pln.hxx>
#include <gp_Dir.hxx>
#include <App/PropertyStandard.h>
#include <App/PropertyUnits.h>
#include <App/PropertyLinks.h>
@@ -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

View File

@@ -27,17 +27,20 @@
#include <QListWidget>
#include <QMessageBox>
#include <Base/Interpreter.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <Base/Converter.h>
#include <Gui/Command.h>
#include <Base/Interpreter.h>
#include <Gui/Selection/Selection.h>
#include <Gui/ViewProvider.h>
#include <Gui/Inventor/Draggers/Gizmo.h>
#include <Gui/Inventor/Draggers/SoRotationDragger.h>
#include <Gui/Utilities.h>
#include <Mod/PartDesign/App/FeatureDraft.h>
#include <Mod/PartDesign/Gui/ReferenceSelection.h>
#include <Mod/Part/App/GizmoHelper.h>
#include <Mod/Part/App/Tools.h>
#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<PartDesign::Draft>();
std::vector<std::string> 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<PartDesign::Draft>()) {
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<PartDesign::Draft>();
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<DraggerPlacementPropsWithNormals> props
= getDraggerPlacementFromPlaneAndFace(faces[0], neutralPlane);
if (!props) {
return;
}
if (auto normalProps = props->normalProps) {
auto pos = Base::convertTo<SbVec3f>(props->placementProps.position);
auto dir = Base::convertTo<SbVec3f>(props->placementProps.dir);
auto lineDir = Base::convertTo<SbVec3f>(normalProps->normal);
auto pp = Base::convertTo<SbVec3f>(pullDirection);
angleGizmo->setDraggerPlacement(pos, (dir.dot(pp) < 0) ? -pp : pp);
auto rotDir = Base::convertTo<SbVec3f>(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<SbVec3f>(props->placementProps.position),
Base::convertTo<SbVec3f>(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<std::string> strings;
App::DocumentObject* obj;
App::DocumentObject* obj = nullptr;
TaskDraftParameters* draftparameter = static_cast<TaskDraftParameters*>(parameter);
draftparameter->getPlane(obj, strings);

View File

@@ -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_TaskDraftParameters> ui;
std::unique_ptr<Gui::GizmoContainer> gizmoContainer;
Gui::RotationGizmo* angleGizmo = nullptr;
void setupGizmos(ViewProvider* vp);
void setGizmoPositions();
};
/// simulation dialog for the TaskView