Advanced options for move to other object in the transform tool.

Allows for masking of individual translation or rotation axes.
This commit is contained in:
Max Wilfinger
2025-05-29 20:42:16 +02:00
committed by Kacper Donat
parent 2933eaf819
commit d64efad081
4 changed files with 389 additions and 8 deletions

View File

@@ -242,6 +242,21 @@ void TaskTransform::setupGui()
&QPushButton::clicked,
this,
&TaskTransform::onAlignToOtherObject);
connect(ui->moveOptionsButton,
&QPushButton::toggled,
ui->frameMoveOptions,
&QWidget::setVisible);
connect(ui->translateCheckbox, &QCheckBox::toggled, this, [this](bool translateChecked) {
ui->matchXcheckbox->setEnabled(translateChecked);
ui->matchYcheckbox->setEnabled(translateChecked);
ui->matchZcheckbox->setEnabled(translateChecked);
});
connect(ui->rotateCheckbox, &QCheckBox::toggled, this, [this](bool rotateChecked) {
ui->alignXcheckbox->setEnabled(rotateChecked);
ui->alignYcheckbox->setEnabled(rotateChecked);
ui->alignZcheckbox->setEnabled(rotateChecked);
});
connect(ui->flipPartButton, &QPushButton::clicked, this, &TaskTransform::onFlip);
connect(ui->alignRotationCheckBox,
@@ -289,6 +304,7 @@ void TaskTransform::loadPreferences()
ui->translationIncrementSpinBox->setValue(lastTranslationIncrement);
ui->rotationIncrementSpinBox->setValue(lastRotationIncrement);
ui->moveOptionsButton->setIcon(Gui::BitmapFactory().pixmap("Std_DlgParameter"));
}
void TaskTransform::savePreferences()
@@ -531,8 +547,39 @@ void TaskTransform::onAlignToOtherObject()
void TaskTransform::moveObjectToDragger()
{
// Check which dragger components should be considered
ViewProviderDragger::DraggerComponents components;
if (ui->matchXcheckbox->isChecked()) {
components |= ViewProviderDragger::DraggerComponent::XPos;
}
if (ui->matchYcheckbox->isChecked()) {
components |= ViewProviderDragger::DraggerComponent::YPos;
}
if (ui->matchZcheckbox->isChecked()) {
components |= ViewProviderDragger::DraggerComponent::ZPos;
}
if (ui->alignXcheckbox->isChecked()) {
components |= ViewProviderDragger::DraggerComponent::XRot;
}
if (ui->alignYcheckbox->isChecked()) {
components |= ViewProviderDragger::DraggerComponent::YRot;
}
if (ui->alignZcheckbox->isChecked()) {
components |= ViewProviderDragger::DraggerComponent::ZRot;
}
if (!ui->translateCheckbox->isChecked()) {
components &= ~ViewProviderDragger::DraggerComponent::XPos;
components &= ~ViewProviderDragger::DraggerComponent::YPos;
components &= ~ViewProviderDragger::DraggerComponent::ZPos;
}
if (!ui->rotateCheckbox->isChecked()) {
components &= ~ViewProviderDragger::DraggerComponent::XRot;
components &= ~ViewProviderDragger::DraggerComponent::YRot;
components &= ~ViewProviderDragger::DraggerComponent::ZRot;
}
vp->updateTransformFromDragger();
vp->updatePlacementFromDragger();
vp->updatePlacementFromDragger(components);
resetReferenceRotation();
resetReferencePlacement();

View File

@@ -194,10 +194,219 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="alignToOtherObjectButton">
<property name="text">
<string>Move to other object</string>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="alignToOtherObjectButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Move to other object</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveOptionsButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="preferences-other"/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frameMoveOptions">
<property name="visible">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="translateCheckbox">
<property name="text">
<string>Translate</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="rotateCheckbox">
<property name="text">
<string>Rotate</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="1">
<widget class="QCheckBox" name="matchXcheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Match U/X</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="matchYcheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Match V/Y</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="matchZcheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Match W/Z</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="alignXcheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Align U/X</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="alignYcheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Align V/Y</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QCheckBox" name="alignZcheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Align W/Z</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>18</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>18</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>

View File

@@ -252,7 +252,7 @@ void ViewProviderDragger::dragMotionCallback(void* data, [[maybe_unused]] SoDrag
vp->updateTransformFromDragger();
}
void ViewProviderDragger::updatePlacementFromDragger()
void ViewProviderDragger::updatePlacementFromDragger(DraggerComponents components)
{
const auto placement = getObject()->getPropertyByName<App::PropertyPlacement>("Placement");
@@ -260,7 +260,112 @@ void ViewProviderDragger::updatePlacementFromDragger()
return;
}
placement->setValue(getDraggerPlacement() * getTransformOrigin().inverse());
// Get new target dragger placement
Base::Placement newDraggerPlacement = getDraggerPlacement();
Base::Vector3d newDraggerPosition = newDraggerPlacement.getPosition();
Base::Rotation newDraggerRotation = newDraggerPlacement.getRotation();
// Get old dragger placement before movement
const Base::Placement oldObjectPlacement = placement->getValue();
const Base::Placement oldDraggerPlacement = oldObjectPlacement * getTransformOrigin();
const Base::Vector3d oldDraggerPosition = oldDraggerPlacement.getPosition();
const Base::Rotation oldDraggerRotation = oldDraggerPlacement.getRotation();
// --- Mask translation ---
const Base::Vector3d deltaPositionGlobal = newDraggerPosition - oldDraggerPosition;
const Base::Vector3d deltaPositionLocal = oldDraggerRotation.inverse().multVec(deltaPositionGlobal);
Base::Vector3d maskedDeltaPositionLocal = deltaPositionLocal;
if (!components.testFlag(DraggerComponent::XPos)) {
maskedDeltaPositionLocal.x = 0.0;
}
if (!components.testFlag(DraggerComponent::YPos)) {
maskedDeltaPositionLocal.y = 0.0;
}
if (!components.testFlag(DraggerComponent::ZPos)) {
maskedDeltaPositionLocal.z = 0.0;
}
const Base::Vector3d maskedDeltaPositionGlobal = oldDraggerRotation.multVec(maskedDeltaPositionLocal);
Base::Vector3d finalPosition = oldDraggerPosition + maskedDeltaPositionGlobal;
// --- Mask rotation ---
Base::Vector3d oldX = oldDraggerRotation.multVec(Base::Vector3d::UnitX);
Base::Vector3d oldY = oldDraggerRotation.multVec(Base::Vector3d::UnitY);
Base::Vector3d oldZ = oldDraggerRotation.multVec(Base::Vector3d::UnitZ);
Base::Vector3d newX = newDraggerRotation.multVec(Base::Vector3d::UnitX);
Base::Vector3d newY = newDraggerRotation.multVec(Base::Vector3d::UnitY);
Base::Vector3d newZ = newDraggerRotation.multVec(Base::Vector3d::UnitZ);
// Choose which axes to align
Base::Vector3d x = components.testFlag(DraggerComponent::XRot) ? newX : oldX;
Base::Vector3d y = components.testFlag(DraggerComponent::YRot) ? newY : oldY;
Base::Vector3d z = components.testFlag(DraggerComponent::ZRot) ? newZ : oldZ;
Base::Rotation finalRotation = orthonormalize(x, y, z, components);
// Create new dragger placement, only if components are masked
Base::Placement finalDraggerPlacement(newDraggerPosition, newDraggerRotation);
if (!components.testFlag(DraggerComponent::All)){
finalDraggerPlacement.setPosition(finalPosition);
finalDraggerPlacement.setRotation(finalRotation);
}
placement->setValue((finalDraggerPlacement * getTransformOrigin().inverse()));
}
Base::Rotation Gui::ViewProviderDragger::orthonormalize(Base::Vector3d x,
Base::Vector3d y,
Base::Vector3d z,
ViewProviderDragger::DraggerComponents components)
{
// Orthonormalize (GramSchmidt process) to find perpendicular unit vector depending on masked axes
if (components.testFlag(Gui::ViewProviderDragger::DraggerComponent::XRot)
&& components.testFlag(Gui::ViewProviderDragger::DraggerComponent::YRot)) {
x.Normalize();
y = y - x * (x * y);
y.Normalize();
z = x.Cross(y);
z.Normalize();
}
else if (components.testFlag(Gui::ViewProviderDragger::DraggerComponent::XRot) && components.testFlag(Gui::ViewProviderDragger::DraggerComponent::ZRot)) {
x.Normalize();
z = z - x * (x * z);
z.Normalize();
y = z.Cross(x);
y.Normalize();
}
else if (components.testFlag(Gui::ViewProviderDragger::DraggerComponent::YRot) && components.testFlag(Gui::ViewProviderDragger::DraggerComponent::ZRot)) {
y.Normalize();
z = z - y * (y * z);
z.Normalize();
x = y.Cross(z);
x.Normalize();
}
else if (components.testFlag(Gui::ViewProviderDragger::DraggerComponent::XRot)) {
x.Normalize();
y = y - x * (x * y);
y.Normalize();
z = x.Cross(y);
z.Normalize();
}
else if (components.testFlag(Gui::ViewProviderDragger::DraggerComponent::YRot)) {
y.Normalize();
x = x - y * (x * y);
x.Normalize();
z = x.Cross(y);
z.Normalize();
}
else if (components.testFlag(Gui::ViewProviderDragger::DraggerComponent::ZRot)) {
z.Normalize();
x = x - z * (x * z);
x.Normalize();
y = z.Cross(x);
y.Normalize();
}
return Base::Rotation::makeRotationByAxes(x, y, z);
}
void ViewProviderDragger::updateTransformFromDragger()

View File

@@ -27,6 +27,7 @@
#include "ViewProviderDocumentObject.h"
#include <Base/Placement.h>
#include <App/PropertyGeo.h>
#include <Base/Bitmask.h>
class SoDragger;
class SoTransform;
@@ -82,8 +83,20 @@ public:
/*! synchronize From FC placement to Coin placement*/
static void updateTransform(const Base::Placement &from, SoTransform *to);
/// updates placement of object based on dragger position
void updatePlacementFromDragger();
enum class DraggerComponent
{
None = 0,
XPos = 1 << 0,
YPos = 1 << 1,
ZPos = 1 << 2,
XRot = 1 << 3,
YRot = 1 << 4,
ZRot = 1 << 5,
All = XPos | YPos | ZPos | XRot | YRot | ZRot
};
using DraggerComponents = Base::Flags<DraggerComponent>;
/// updates placement of object based on dragger position and chosen axes components
void updatePlacementFromDragger(DraggerComponents components = DraggerComponent::All);
/// updates transform of object based on dragger position, can be used to preview movement
void updateTransformFromDragger();
@@ -125,10 +138,17 @@ private:
void updateDraggerPosition();
Base::Placement draggerPlacement { };
// Rotation by orthonormalizing depending on given axes components
Base::Rotation orthonormalize(Base::Vector3d x,
Base::Vector3d y,
Base::Vector3d z,
ViewProviderDragger::DraggerComponents components = DraggerComponent::All);
};
} // namespace Gui
ENABLE_BITMASK_OPERATORS(Gui::ViewProviderDragger::DraggerComponent)
#endif // GUI_VIEWPROVIDER_DRAGGER_H