511 lines
18 KiB
C++
511 lines
18 KiB
C++
/***************************************************************************
|
||
* Copyright (c) 2017 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
||
* *
|
||
* This file is part of the FreeCAD CAx development system. *
|
||
* *
|
||
* This library is free software; you can redistribute it and/or *
|
||
* modify it under the terms of the GNU Library General Public *
|
||
* License as published by the Free Software Foundation; either *
|
||
* version 2 of the License, or (at your option) any later version. *
|
||
* *
|
||
* This library is distributed in the hope that it will be useful, *
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||
* GNU Library General Public License for more details. *
|
||
* *
|
||
* You should have received a copy of the GNU Library General Public *
|
||
* License along with this library; see the file COPYING.LIB. If not, *
|
||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||
* Suite 330, Boston, MA 02111-1307, USA *
|
||
* *
|
||
***************************************************************************/
|
||
|
||
#include <memory>
|
||
#include <ranges>
|
||
#include <string>
|
||
#include <QAction>
|
||
#include <QMenu>
|
||
#include <Inventor/draggers/SoDragger.h>
|
||
#include <Inventor/nodes/SoPickStyle.h>
|
||
#include <Inventor/nodes/SoTransform.h>
|
||
|
||
#include <App/GeoFeature.h>
|
||
#include <App/DocumentObjectGroup.h>
|
||
#include <Base/Tools.h>
|
||
#include <Base/Placement.h>
|
||
#include <Base/Vector3D.h>
|
||
#include <Base/Converter.h>
|
||
|
||
#include "Application.h"
|
||
#include "BitmapFactory.h"
|
||
#include "Control.h"
|
||
#include "Document.h"
|
||
#include "Inventor/Draggers/SoTransformDragger.h"
|
||
#include "Inventor/Draggers/Gizmo.h"
|
||
#include "Inventor/SoFCPlacementIndicatorKit.h"
|
||
#include "Inventor/So3DAnnotation.h"
|
||
#include "SoFCUnifiedSelection.h"
|
||
#include "TaskTransform.h"
|
||
#include "View3DInventorViewer.h"
|
||
#include "ViewProviderDragger.h"
|
||
#include "Utilities.h"
|
||
#include "ViewParams.h"
|
||
#include "ViewProviderLink.h"
|
||
|
||
|
||
using namespace Gui;
|
||
|
||
PROPERTY_SOURCE(Gui::ViewProviderDragger, Gui::ViewProviderDocumentObject)
|
||
|
||
ViewProviderDragger::ViewProviderDragger()
|
||
{
|
||
ADD_PROPERTY_TYPE(TransformOrigin, ({}), nullptr, App::Prop_Hidden, nullptr);
|
||
|
||
pcPlacement = new SoSwitch;
|
||
pcPlacement->whichChild = SO_SWITCH_NONE;
|
||
};
|
||
|
||
ViewProviderDragger::~ViewProviderDragger() = default;
|
||
|
||
void ViewProviderDragger::updateData(const App::Property* prop)
|
||
{
|
||
if (prop->isDerivedFrom<App::PropertyPlacement>()
|
||
&& strcmp(prop->getName(), "Placement") == 0) {
|
||
// Note: If R is the rotation, c the rotation center and t the translation
|
||
// vector then Inventor applies the following transformation: R*(x-c)+c+t
|
||
// In FreeCAD a placement only has a rotation and a translation part but
|
||
// no rotation center. This means that the following equation must be ful-
|
||
// filled: R * (x-c) + c + t = R * x + t
|
||
// <==> R * x + t - R * c + c = R * x + t
|
||
// <==> (I-R) * c = 0 ==> c = 0
|
||
// This means that the center point must be the origin!
|
||
Base::Placement p = static_cast<const App::PropertyPlacement*>(prop)->getValue();
|
||
updateTransform(p, pcTransform);
|
||
}
|
||
|
||
ViewProviderDocumentObject::updateData(prop);
|
||
}
|
||
|
||
void ViewProviderDragger::setTransformOrigin(const Base::Placement& placement)
|
||
{
|
||
TransformOrigin.setValue(placement);
|
||
}
|
||
|
||
void ViewProviderDragger::resetTransformOrigin()
|
||
{
|
||
setTransformOrigin({});
|
||
}
|
||
|
||
|
||
void ViewProviderDragger::setGizmoContainer(Gui::GizmoContainer* gizmoContainer)
|
||
{
|
||
this->gizmoContainer = gizmoContainer;
|
||
}
|
||
|
||
void ViewProviderDragger::onChanged(const App::Property* property)
|
||
{
|
||
if (property == &TransformOrigin) {
|
||
updateDraggerPosition();
|
||
}
|
||
|
||
ViewProviderDocumentObject::onChanged(property);
|
||
}
|
||
|
||
TaskView::TaskDialog* ViewProviderDragger::getTransformDialog()
|
||
{
|
||
return new TaskTransformDialog(this, transformDragger);
|
||
}
|
||
|
||
bool ViewProviderDragger::doubleClicked()
|
||
{
|
||
Gui::Application::Instance->activeDocument()->setEdit(this, (int)ViewProvider::Default);
|
||
return true;
|
||
}
|
||
|
||
void ViewProviderDragger::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
|
||
{
|
||
QIcon iconObject =
|
||
mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
|
||
QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
|
||
act->setData(QVariant((int)ViewProvider::Transform));
|
||
ViewProviderDocumentObject::setupContextMenu(menu, receiver, member);
|
||
}
|
||
|
||
ViewProvider* ViewProviderDragger::startEditing(int mode)
|
||
{
|
||
forwardedViewProvider = nullptr;
|
||
|
||
auto ret = ViewProviderDocumentObject::startEditing(mode);
|
||
if (!ret) {
|
||
return ret;
|
||
}
|
||
|
||
return forwardedViewProvider ? forwardedViewProvider : ret;
|
||
}
|
||
|
||
bool ViewProviderDragger::forwardToLink()
|
||
{
|
||
// typically we want to transform the selected object, but if the selected object is in the link,
|
||
// we want to transform the link instead.
|
||
//
|
||
// To achieve that, we use the sub object path and look for the first link in the chain, and
|
||
// we forward the request there.
|
||
const auto findFirstLinkViewProvider = [this]() -> ViewProvider* {
|
||
ViewProviderDocumentObject* vpParent = nullptr;
|
||
std::string subname;
|
||
|
||
auto doc = Application::Instance->editDocument();
|
||
if (!doc) {
|
||
return nullptr;
|
||
}
|
||
|
||
doc->getInEdit(&vpParent, &subname);
|
||
if (!vpParent) {
|
||
return nullptr;
|
||
}
|
||
|
||
for (const auto subObjects = vpParent->getObject()->getSubObjectList(subname.c_str());
|
||
const auto subObject : std::views::reverse(subObjects)) {
|
||
|
||
// we don't want to try to edit objects in other documents
|
||
if (subObject->getDocument() != getObject()->getDocument()) {
|
||
return nullptr;
|
||
}
|
||
|
||
if (auto subObjectViewProvider = Application::Instance->getViewProvider(subObject);
|
||
subObjectViewProvider && subObjectViewProvider->isDerivedFrom<ViewProviderLink>()) {
|
||
return subObjectViewProvider;
|
||
}
|
||
}
|
||
|
||
return nullptr;
|
||
};
|
||
|
||
if (ViewProvider* subObjectViewProvider = findFirstLinkViewProvider();
|
||
subObjectViewProvider && subObjectViewProvider != this) {
|
||
forwardedViewProvider = subObjectViewProvider->startEditing(Transform);
|
||
}
|
||
|
||
return forwardedViewProvider != nullptr;
|
||
}
|
||
|
||
App::PropertyPlacement* ViewProviderDragger::getPlacementProperty() const
|
||
{
|
||
auto object = getObject();
|
||
|
||
if (auto linkExtension = object->getExtensionByType<App::LinkBaseExtension>(true)) {
|
||
if (auto linkPlacementProp = linkExtension->getLinkPlacementProperty()) {
|
||
return linkPlacementProp;
|
||
}
|
||
|
||
return linkExtension->getPlacementProperty();
|
||
}
|
||
|
||
return getObject()->getPropertyByName<App::PropertyPlacement>("Placement");
|
||
}
|
||
|
||
bool ViewProviderDragger::setEdit(int ModNum)
|
||
{
|
||
Q_UNUSED(ModNum);
|
||
|
||
if (forwardToLink()) {
|
||
return true;
|
||
}
|
||
|
||
assert(!transformDragger);
|
||
|
||
transformDragger = new SoTransformDragger();
|
||
transformDragger->setAxisColors(Gui::ViewParams::instance()->getAxisXColor(),
|
||
Gui::ViewParams::instance()->getAxisYColor(),
|
||
Gui::ViewParams::instance()->getAxisZColor());
|
||
transformDragger->draggerSize.setValue(ViewParams::instance()->getDraggerScale());
|
||
|
||
transformDragger->addStartCallback(dragStartCallback, this);
|
||
transformDragger->addFinishCallback(dragFinishCallback, this);
|
||
transformDragger->addMotionCallback(dragMotionCallback, this);
|
||
|
||
Gui::Control().showDialog(getTransformDialog());
|
||
|
||
updateDraggerPosition();
|
||
|
||
return true;
|
||
}
|
||
|
||
void ViewProviderDragger::unsetEdit(int ModNum)
|
||
{
|
||
Q_UNUSED(ModNum);
|
||
|
||
transformDragger.reset();
|
||
|
||
Gui::Control().closeDialog();
|
||
}
|
||
|
||
void ViewProviderDragger::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
|
||
{
|
||
Q_UNUSED(ModNum);
|
||
|
||
if (!viewer) {
|
||
return;
|
||
}
|
||
|
||
if (transformDragger) {
|
||
transformDragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera());
|
||
|
||
auto originPlacement = App::GeoFeature::getGlobalPlacement(getObject()) * getObjectPlacement().inverse();
|
||
auto mat = originPlacement.toMatrix();
|
||
|
||
viewer->getDocument()->setEditingTransform(mat);
|
||
viewer->setupEditingRoot(transformDragger, &mat);
|
||
}
|
||
|
||
if (gizmoContainer) {
|
||
auto originPlacement = App::GeoFeature::getGlobalPlacement(getObject())
|
||
* getObjectPlacement().inverse();
|
||
gizmoContainer->attachViewer(viewer, originPlacement);
|
||
}
|
||
}
|
||
|
||
void ViewProviderDragger::unsetEditViewer([[maybe_unused]] Gui::View3DInventorViewer* viewer)
|
||
{}
|
||
|
||
void ViewProviderDragger::dragStartCallback(void* data, [[maybe_unused]] SoDragger* d)
|
||
{
|
||
// This is called when a manipulator has done manipulating
|
||
auto vp = static_cast<ViewProviderDragger*>(data);
|
||
|
||
vp->draggerPlacement = vp->getDraggerPlacement();
|
||
vp->transformDragger->clearIncrementCounts();
|
||
}
|
||
|
||
void ViewProviderDragger::dragFinishCallback(void* data, [[maybe_unused]] SoDragger* d)
|
||
{
|
||
// This is called when a manipulator has done manipulating
|
||
auto vp = static_cast<ViewProviderDragger*>(data);
|
||
|
||
vp->draggerPlacement = vp->getDraggerPlacement();
|
||
vp->transformDragger->clearIncrementCounts();
|
||
|
||
vp->updatePlacementFromDragger();
|
||
}
|
||
|
||
void ViewProviderDragger::dragMotionCallback(void* data, [[maybe_unused]] SoDragger* d)
|
||
{
|
||
auto vp = static_cast<ViewProviderDragger*>(data);
|
||
|
||
vp->updateTransformFromDragger();
|
||
}
|
||
|
||
void ViewProviderDragger::updatePlacementFromDragger(DraggerComponents components)
|
||
{
|
||
const auto placement = getPlacementProperty();
|
||
|
||
if (!placement) {
|
||
return;
|
||
}
|
||
|
||
// 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()));
|
||
updateDraggerPosition();
|
||
}
|
||
|
||
Base::Rotation Gui::ViewProviderDragger::orthonormalize(Base::Vector3d x,
|
||
Base::Vector3d y,
|
||
Base::Vector3d z,
|
||
ViewProviderDragger::DraggerComponents components)
|
||
{
|
||
// Orthonormalize (Gram–Schmidt 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()
|
||
{
|
||
const auto placement = getDraggerPlacement() * getTransformOrigin().inverse();
|
||
|
||
pcTransform->translation.setValue(Base::convertTo<SbVec3f>(placement.getPosition()));
|
||
pcTransform->rotation.setValue(Base::convertTo<SbRotation>(placement.getRotation()));
|
||
}
|
||
|
||
Base::Placement ViewProviderDragger::getObjectPlacement() const
|
||
{
|
||
if (auto placement = getPlacementProperty()) {
|
||
return placement->getValue();
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
Base::Placement ViewProviderDragger::getDraggerPlacement() const
|
||
{
|
||
const double translationStep = transformDragger->translationIncrement.getValue();
|
||
const int xSteps = transformDragger->translationIncrementCountX.getValue();
|
||
const int ySteps = transformDragger->translationIncrementCountY.getValue();
|
||
const int zSteps = transformDragger->translationIncrementCountZ.getValue();
|
||
|
||
const auto rotation = draggerPlacement.getRotation();
|
||
const auto xBase = rotation.multVec(Base::Vector3d(1, 0, 0));
|
||
const auto yBase = rotation.multVec(Base::Vector3d(0, 1, 0));
|
||
const auto zBase = rotation.multVec(Base::Vector3d(0, 0, 1));
|
||
|
||
const auto positionIncrement =
|
||
xBase * (translationStep * xSteps) +
|
||
yBase * (translationStep * ySteps) +
|
||
zBase * (translationStep * zSteps);
|
||
|
||
const double rotationStep = transformDragger->rotationIncrement.getValue();
|
||
const int xRotationSteps = transformDragger->rotationIncrementCountX.getValue();
|
||
const int yRotationSteps = transformDragger->rotationIncrementCountY.getValue();
|
||
const int zRotationSteps = transformDragger->rotationIncrementCountZ.getValue();
|
||
|
||
auto newRotation = rotation;
|
||
newRotation = newRotation * Base::Rotation(Base::Vector3d(1, 0, 0), xRotationSteps * rotationStep);
|
||
newRotation = newRotation * Base::Rotation(Base::Vector3d(0, 1, 0), yRotationSteps * rotationStep);
|
||
newRotation = newRotation * Base::Rotation(Base::Vector3d(0, 0, 1), zRotationSteps * rotationStep);
|
||
|
||
return Base::Placement(
|
||
draggerPlacement.getPosition() + positionIncrement,
|
||
newRotation
|
||
);
|
||
}
|
||
|
||
Base::Placement ViewProviderDragger::getOriginalDraggerPlacement() const
|
||
{
|
||
return draggerPlacement;
|
||
}
|
||
|
||
void ViewProviderDragger::setDraggerPlacement(const Base::Placement& placement)
|
||
{
|
||
transformDragger->translation.setValue(Base::convertTo<SbVec3f>(placement.getPosition()));
|
||
transformDragger->rotation.setValue(Base::convertTo<SbRotation>(placement.getRotation()));
|
||
|
||
draggerPlacement = placement;
|
||
transformDragger->clearIncrementCounts();
|
||
}
|
||
|
||
void ViewProviderDragger::attach(App::DocumentObject* pcObject)
|
||
{
|
||
ViewProviderDocumentObject::attach(pcObject);
|
||
|
||
getAnnotation()->addChild(pcPlacement);
|
||
|
||
auto* pcAxisCrossKit = new Gui::SoFCPlacementIndicatorKit();
|
||
|
||
auto* pcAnnotation = new So3DAnnotation();
|
||
pcAnnotation->addChild(pcAxisCrossKit);
|
||
|
||
pcPlacement->addChild(pcAnnotation);
|
||
}
|
||
|
||
void ViewProviderDragger::updateDraggerPosition()
|
||
{
|
||
if (!transformDragger) {
|
||
return;
|
||
}
|
||
|
||
auto placement = getObjectPlacement() * getTransformOrigin();
|
||
|
||
setDraggerPlacement(placement);
|
||
}
|
||
|
||
void ViewProviderDragger::updateTransform(const Base::Placement& from, SoTransform* to)
|
||
{
|
||
to->rotation.setValue(Base::convertTo<SbRotation>(from.getRotation()));
|
||
to->translation.setValue(Base::convertTo<SbVec3f>(from.getPosition()));
|
||
to->center.setValue(0.0f, 0.0f, 0.0f);
|
||
to->scaleFactor.setValue(1.0f, 1.0f, 1.0f);
|
||
}
|