/*************************************************************************** * Copyright (c) 2015 Thomas Anderson * * * * 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 "PreCompiled.h" #ifndef _PreComp_ #include #include #include #endif #include #include #include #include #include #include #include #include #include "Document.h" // must be before TaskTransform.h #include "Application.h" #include "BitmapFactory.h" #include "Command.h" #include "Inventor/Draggers/SoTransformDragger.h" #include "QuantitySpinBox.h" #include "ViewProviderDragger.h" #include "TaskView/TaskView.h" #include "TaskTransform.h" #include "ui_TaskTransform.h" #include using namespace Gui; namespace { void alignGridLayoutColumns(const std::list& layouts, unsigned column = 0) { std::vector widths; auto getActualWidth = [&](const QGridLayout* layout) -> int { if (auto const item = layout->itemAtPosition(0, column)) { return item->geometry().width(); } return 0; }; for (const auto layout : layouts) { widths.push_back(getActualWidth(layout)); } const auto maxWidth = *std::max_element(widths.begin(), widths.end()); for (const auto layout : layouts) { layout->setColumnMinimumWidth(column, maxWidth); } } } // namespace TaskTransform::TaskTransform(Gui::ViewProviderDragger* vp, Gui::SoTransformDragger* dragger, QWidget* parent, App::SubObjectPlacementProvider* subObjectPlacemenProvider, App::CenterOfMassProvider* centerOfMassProvider) : TaskBox(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"), tr("Transform"), false, parent) , vp(vp) , subObjectPlacementProvider(subObjectPlacemenProvider) , centerOfMassProvider(centerOfMassProvider) , dragger(dragger) , ui(new Ui_TaskTransformDialog) { blockSelection(true); dragger->addStartCallback(dragStartCallback, this); dragger->addMotionCallback(dragMotionCallback, this); vp->resetTransformOrigin(); referencePlacement = vp->getObjectPlacement(); referenceRotation = referencePlacement.getRotation(); globalOrigin = vp->getObjectPlacement() * App::GeoFeature::getGlobalPlacement(vp->getObject()).inverse(); setupGui(); } TaskTransform::~TaskTransform() { Gui::Application::Instance->commandManager() .getCommandByName("Std_OrthographicCamera") ->setEnabled(true); Gui::Application::Instance->commandManager() .getCommandByName("Std_PerspectiveCamera") ->setEnabled(true); savePreferences(); } void TaskTransform::dragStartCallback([[maybe_unused]] void* data, [[maybe_unused]] SoDragger* dragger) { // This is called when a manipulator is about to manipulating if (firstDrag) { Gui::Application::Instance->activeDocument()->openCommand( QT_TRANSLATE_NOOP("Command", "Transform")); firstDrag = false; } } void TaskTransform::dragMotionCallback(void* data, [[maybe_unused]] SoDragger* dragger) { auto task = static_cast(data); const auto currentRotation = task->vp->getOriginalDraggerPlacement().getRotation(); const auto updatedRotation = task->vp->getDraggerPlacement().getRotation(); const auto rotationAxisHasChanged = [task](auto first, auto second) { double alpha, beta, gamma; (first.inverse() * second).getEulerAngles(task->eulerSequence(), alpha, beta, gamma); auto angles = {alpha, beta, gamma}; const int changed = std::count_if(angles.begin(), angles.end(), [](double angle) { return std::fabs(angle) > tolerance; }); // if representation of both differs by more than one axis the axis of rotation must be // different return changed > 1; }; if (!updatedRotation.isSame(currentRotation, tolerance)) { task->resetReferencePlacement(); if (rotationAxisHasChanged(task->referenceRotation, updatedRotation)) { task->referenceRotation = currentRotation; } } task->updatePositionAndRotationUi(); } void TaskTransform::loadPlacementModeItems() const { ui->placementComboBox->clear(); ui->placementComboBox->addItem(tr("Object origin"), QVariant::fromValue(PlacementMode::ObjectOrigin)); if (centerOfMassProvider->ofDocumentObject(vp->getObject()).has_value()) { ui->placementComboBox->addItem(tr("Center of mass / Centroid"), QVariant::fromValue(PlacementMode::Centroid)); } if (subObjectPlacementProvider) { ui->placementComboBox->addItem(tr("Custom"), QVariant::fromValue(PlacementMode::Custom)); } } void TaskTransform::loadPositionModeItems() const { ui->positionModeComboBox->clear(); ui->positionModeComboBox->addItem(tr("Local"), QVariant::fromValue(PositionMode::Local)); ui->positionModeComboBox->addItem(tr("Global"), QVariant::fromValue(PositionMode::Global)); } void TaskTransform::setupGui() { auto proxy = new QWidget(this); ui->setupUi(proxy); this->groupLayout()->addWidget(proxy); loadPlacementModeItems(); loadPositionModeItems(); ui->referencePickerWidget->hide(); ui->alignRotationCheckBox->hide(); for (auto positionSpinBox : {ui->translationIncrementSpinBox, ui->xPositionSpinBox, ui->yPositionSpinBox, ui->zPositionSpinBox}) { positionSpinBox->setUnit(Base::Unit::Length); } for (auto rotationSpinBox : {ui->rotationIncrementSpinBox, ui->xRotationSpinBox, ui->yRotationSpinBox, ui->zRotationSpinBox}) { rotationSpinBox->setUnit(Base::Unit::Angle); } connect(ui->translationIncrementSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, [this](double) { updateIncrements(); }); connect(ui->rotationIncrementSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, [this](double) { updateIncrements(); }); connect(ui->positionModeComboBox, qOverload(&QComboBox::currentIndexChanged), this, &TaskTransform::onCoordinateSystemChange); connect(ui->placementComboBox, qOverload(&QComboBox::currentIndexChanged), this, &TaskTransform::onPlacementModeChange); connect(ui->pickTransformOriginButton, &QPushButton::clicked, this, &TaskTransform::onPickTransformOrigin); connect(ui->alignToOtherObjectButton, &QPushButton::clicked, this, &TaskTransform::onAlignToOtherObject); connect(ui->flipPartButton, &QPushButton::clicked, this, &TaskTransform::onFlip); connect(ui->alignRotationCheckBox, &QCheckBox::clicked, this, &TaskTransform::onAlignRotationChanged); for (auto positionSpinBox : {ui->xPositionSpinBox, ui->yPositionSpinBox, ui->zPositionSpinBox}) { connect(positionSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, [this](double) { onPositionChange(); }); } for (auto rotationSpinBox : {ui->xRotationSpinBox, ui->yRotationSpinBox, ui->zRotationSpinBox}) { connect(rotationSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, [this,rotationSpinBox](double) { onRotationChange(rotationSpinBox); }); } alignGridLayoutColumns({ui->absolutePositionLayout, ui->absoluteRotationLayout, ui->transformOriginLayout, ui->referencePickerLayout}); loadPreferences(); updateInputLabels(); updateDraggerLabels(); updateIncrements(); updatePositionAndRotationUi(); } void TaskTransform::loadPreferences() { double lastTranslationIncrement = hGrp->GetFloat("LastTranslationIncrement", 1.0); double lastRotationIncrement = hGrp->GetFloat("LastRotationIncrement", 5.0); ui->translationIncrementSpinBox->setValue(lastTranslationIncrement); ui->rotationIncrementSpinBox->setValue(lastRotationIncrement); } void TaskTransform::savePreferences() { hGrp->SetFloat("LastTranslationIncrement", ui->translationIncrementSpinBox->rawValue()); hGrp->SetFloat("LastRotationIncrement", ui->rotationIncrementSpinBox->rawValue()); } void TaskTransform::updatePositionAndRotationUi() const { const auto referencePlacement = currentCoordinateSystem().origin; const auto xyzPlacement = vp->getDraggerPlacement(); const auto uvwPlacement = referencePlacement.inverse() * xyzPlacement; auto fixNegativeZero = [](const double value) { return std::fabs(value) < Base::Precision::Confusion() ? 0.0 : value; }; auto setPositionValues = [&](const Base::Vector3d& vec, auto* x, auto* y, auto* z) { [[maybe_unused]] auto blockers = {QSignalBlocker(x), QSignalBlocker(y), QSignalBlocker(z)}; x->setValue(fixNegativeZero(vec.x)); y->setValue(fixNegativeZero(vec.y)); z->setValue(fixNegativeZero(vec.z)); }; auto setRotationValues = [&](const Base::Rotation& rot, auto* x, auto* y, auto* z) { [[maybe_unused]] auto blockers = {QSignalBlocker(x), QSignalBlocker(y), QSignalBlocker(z)}; double alpha, beta, gamma; rot.getEulerAngles(eulerSequence(), alpha, beta, gamma); x->setValue(fixNegativeZero(alpha)); y->setValue(fixNegativeZero(beta)); z->setValue(fixNegativeZero(gamma)); }; setPositionValues(uvwPlacement.getPosition(), ui->xPositionSpinBox, ui->yPositionSpinBox, ui->zPositionSpinBox); setRotationValues(positionMode == PositionMode::Local ? referenceRotation.inverse() * xyzPlacement.getRotation() : uvwPlacement.getRotation(), ui->xRotationSpinBox, ui->yRotationSpinBox, ui->zRotationSpinBox); } void TaskTransform::updateInputLabels() const { auto [xLabel, yLabel, zLabel] = currentCoordinateSystem().labels; ui->xPositionLabel->setText(QString::fromStdString(xLabel)); ui->yPositionLabel->setText(QString::fromStdString(yLabel)); ui->zPositionLabel->setText(QString::fromStdString(zLabel)); ui->xRotationLabel->setText(QString::fromStdString(xLabel)); ui->yRotationLabel->setText(QString::fromStdString(yLabel)); ui->zRotationLabel->setText(QString::fromStdString(zLabel)); } void TaskTransform::updateDraggerLabels() const { auto coordinateSystem = isDraggerAlignedToCoordinateSystem() ? globalCoordinateSystem() : localCoordinateSystem(); auto [xLabel, yLabel, zLabel] = coordinateSystem.labels; dragger->xAxisLabel.setValue(xLabel.c_str()); dragger->yAxisLabel.setValue(yLabel.c_str()); dragger->zAxisLabel.setValue(zLabel.c_str()); } void TaskTransform::updateIncrements() const { dragger->translationIncrement.setValue( std::max(ui->translationIncrementSpinBox->rawValue(), 0.001)); dragger->rotationIncrement.setValue( Base::toRadians(std::max(ui->rotationIncrementSpinBox->rawValue(), 0.01))); } void TaskTransform::setSelectionMode(SelectionMode mode) { Gui::Selection().clearSelection(); SoPickStyle* draggerPickStyle = SO_GET_PART(dragger, "pickStyle", SoPickStyle); ui->pickTransformOriginButton->setText(tr("Pick reference")); ui->alignToOtherObjectButton->setText(tr("Move to other object")); switch (mode) { case SelectionMode::SelectTransformOrigin: draggerPickStyle->style = SoPickStyle::UNPICKABLE; draggerPickStyle->setOverride(true); blockSelection(false); ui->referenceLineEdit->setText(tr("Select face, edge or vertex...")); ui->pickTransformOriginButton->setText(tr("Cancel")); break; case SelectionMode::SelectAlignTarget: draggerPickStyle->style = SoPickStyle::UNPICKABLE; draggerPickStyle->setOverride(true); ui->alignToOtherObjectButton->setText(tr("Cancel")); blockSelection(false); break; case SelectionMode::None: draggerPickStyle->style = SoPickStyle::SHAPE_ON_TOP; draggerPickStyle->setOverride(false); blockSelection(true); vp->setTransformOrigin(vp->getTransformOrigin()); break; } selectionMode = mode; updateSpinBoxesReadOnlyStatus(); } TaskTransform::SelectionMode TaskTransform::getSelectionMode() const { return selectionMode; } TaskTransform::CoordinateSystem TaskTransform::localCoordinateSystem() const { auto origin = referencePlacement; origin.setRotation(vp->getDraggerPlacement().getRotation()); return {{"U", "V", "W"}, origin}; } TaskTransform::CoordinateSystem TaskTransform::globalCoordinateSystem() const { return {{"X", "Y", "Z"}, globalOrigin}; } TaskTransform::CoordinateSystem TaskTransform::currentCoordinateSystem() const { return ui->positionModeComboBox->currentIndex() == 0 ? localCoordinateSystem() : globalCoordinateSystem(); } Base::Rotation::EulerSequence TaskTransform::eulerSequence() const { return positionMode == PositionMode::Local ? Base::Rotation::Intrinsic_XYZ : Base::Rotation::Extrinsic_XYZ; } void TaskTransform::onSelectionChanged(const SelectionChanges& msg) { const auto isSupportedMessage = msg.Type == SelectionChanges::AddSelection || msg.Type == SelectionChanges::SetPreselect; if (!isSupportedMessage) { return; } if (!subObjectPlacementProvider) { return; } if (!msg.pOriginalMsg) { // this should not happen! Original should contain unresolved message. return; } auto doc = Application::Instance->getDocument(msg.pDocName); auto obj = doc->getDocument()->getObject(msg.pObjectName); auto orgDoc = Application::Instance->getDocument(msg.pOriginalMsg->pDocName); auto orgObj = orgDoc->getDocument()->getObject(msg.pOriginalMsg->pObjectName); auto globalPlacement = App::GeoFeature::getGlobalPlacement(obj, orgObj, msg.pOriginalMsg->pSubName); auto localPlacement = App::GeoFeature::getPlacementFromProp(obj, "Placement"); auto rootPlacement = App::GeoFeature::getGlobalPlacement(vp->getObject()); auto attachedPlacement = subObjectPlacementProvider->calculate(msg.Object, localPlacement); auto selectedObjectPlacement = rootPlacement.inverse() * globalPlacement * attachedPlacement; auto label = QStringLiteral("%1#%2.%3") .arg(QLatin1String(msg.pOriginalMsg->pObjectName), QLatin1String(msg.pObjectName), QLatin1String(msg.pSubName)); switch (selectionMode) { case SelectionMode::SelectTransformOrigin: { if (msg.Type == SelectionChanges::AddSelection) { ui->referenceLineEdit->setText(label); customTransformOrigin = selectedObjectPlacement; updateTransformOrigin(); setSelectionMode(SelectionMode::None); } else { vp->setTransformOrigin(selectedObjectPlacement); } break; } case SelectionMode::SelectAlignTarget: { vp->setDraggerPlacement(vp->getObjectPlacement() * selectedObjectPlacement); if (msg.Type == SelectionChanges::AddSelection) { moveObjectToDragger(); setSelectionMode(SelectionMode::None); } break; } default: // no-op break; } } void TaskTransform::onAlignRotationChanged() { updateDraggerLabels(); updateTransformOrigin(); } void TaskTransform::onAlignToOtherObject() { if (selectionMode == SelectionMode::SelectAlignTarget) { setSelectionMode(SelectionMode::None); return; } setSelectionMode(SelectionMode::SelectAlignTarget); } void TaskTransform::moveObjectToDragger() { vp->updateTransformFromDragger(); vp->updatePlacementFromDragger(); resetReferenceRotation(); resetReferencePlacement(); updatePositionAndRotationUi(); } void TaskTransform::onFlip() { auto placement = vp->getDraggerPlacement(); placement.setRotation(placement.getRotation() * Base::Rotation::fromNormalVector(Base::Vector3d(0, 0, -1))); vp->setDraggerPlacement(placement); moveObjectToDragger(); } void TaskTransform::onPickTransformOrigin() { setSelectionMode(selectionMode == SelectionMode::None ? SelectionMode::SelectTransformOrigin : SelectionMode::None); } void TaskTransform::onPlacementModeChange([[maybe_unused]] int index) { placementMode = ui->placementComboBox->currentData().value(); updateTransformOrigin(); } void TaskTransform::updateTransformOrigin() { auto getTransformOrigin = [this](const PlacementMode& mode) -> Base::Placement { switch (mode) { case PlacementMode::ObjectOrigin: return {}; case PlacementMode::Centroid: if (const auto com = centerOfMassProvider->ofDocumentObject(vp->getObject())) { return {*com, {}}; } return {}; case PlacementMode::Custom: return customTransformOrigin.value_or(Base::Placement {}); default: return {}; } }; ui->referencePickerWidget->setVisible(placementMode == PlacementMode::Custom); if (placementMode == PlacementMode::Custom && !customTransformOrigin.has_value()) { setSelectionMode(SelectionMode::SelectTransformOrigin); return; } auto transformOrigin = getTransformOrigin(placementMode); if (isDraggerAlignedToCoordinateSystem()) { transformOrigin.setRotation( (vp->getObjectPlacement().inverse() * globalCoordinateSystem().origin).getRotation()); } vp->setTransformOrigin(transformOrigin); resetReferencePlacement(); resetReferenceRotation(); updatePositionAndRotationUi(); updateDraggerLabels(); } void TaskTransform::updateSpinBoxesReadOnlyStatus() const { const bool isReadOnly = selectionMode != SelectionMode::None; const auto controls = { ui->xPositionSpinBox, ui->yPositionSpinBox, ui->zPositionSpinBox, ui->xRotationSpinBox, ui->yRotationSpinBox, ui->zRotationSpinBox, }; for (const auto& control : controls) { control->setReadOnly(isReadOnly); } } void TaskTransform::resetReferencePlacement() { referencePlacement = vp->getDraggerPlacement(); } void TaskTransform::resetReferenceRotation() { referenceRotation = vp->getDraggerPlacement().getRotation(); } bool TaskTransform::isDraggerAlignedToCoordinateSystem() const { return positionMode == PositionMode::Global && ui->alignRotationCheckBox->isChecked(); } void TaskTransform::onTransformOriginReset() { vp->resetTransformOrigin(); } void TaskTransform::onCoordinateSystemChange([[maybe_unused]] int mode) { positionMode = ui->positionModeComboBox->currentData().value(); ui->alignRotationCheckBox->setVisible(positionMode != PositionMode::Local); updateInputLabels(); updatePositionAndRotationUi(); updateTransformOrigin(); } void TaskTransform::onPositionChange() { const auto uvwPosition = Base::Vector3d(ui->xPositionSpinBox->rawValue(), ui->yPositionSpinBox->rawValue(), ui->zPositionSpinBox->rawValue()); const auto xyzPosition = currentCoordinateSystem().origin.getPosition() + currentCoordinateSystem().origin.getRotation().multVec(uvwPosition); const auto placement = vp->getDraggerPlacement(); vp->setDraggerPlacement({xyzPosition, placement.getRotation()}); vp->updateTransformFromDragger(); vp->updatePlacementFromDragger(); } void TaskTransform::onRotationChange(QuantitySpinBox* changed) { if (positionMode == PositionMode::Local) { for (auto rotationSpinBox : {ui->xRotationSpinBox, ui->yRotationSpinBox, ui->zRotationSpinBox}) { QSignalBlocker blocker(rotationSpinBox); // if any other spinbox contains non-zero value we need to reset rotation reference first if (std::fabs(rotationSpinBox->rawValue()) > tolerance && rotationSpinBox != changed) { resetReferenceRotation(); rotationSpinBox->setValue(0.0); } } } const auto uvwRotation = Base::Rotation::fromEulerAngles(eulerSequence(), ui->xRotationSpinBox->rawValue(), ui->yRotationSpinBox->rawValue(), ui->zRotationSpinBox->rawValue()); auto referenceRotation = positionMode == PositionMode::Local ? this->referenceRotation : currentCoordinateSystem().origin.getRotation(); const auto xyzRotation = referenceRotation * uvwRotation; const auto placement = vp->getDraggerPlacement(); vp->setDraggerPlacement({placement.getPosition(), xyzRotation}); vp->updateTransformFromDragger(); vp->updatePlacementFromDragger(); resetReferencePlacement(); } TaskTransformDialog::TaskTransformDialog(ViewProviderDragger* vp, SoTransformDragger* dragger) : vp(vp) { transform = new TaskTransform(vp, dragger); Content.push_back(transform); } void TaskTransformDialog::open() { // we can't have user switching camera types while dragger is shown. Gui::Application::Instance->commandManager() .getCommandByName("Std_OrthographicCamera") ->setEnabled(false); Gui::Application::Instance->commandManager() .getCommandByName("Std_PerspectiveCamera") ->setEnabled(false); Gui::TaskView::TaskDialog::open(); openCommand(); } void TaskTransformDialog::openCommand() { if (auto document = vp->getDocument()) { if (!document->hasPendingCommand()) { document->openCommand(QT_TRANSLATE_NOOP("Command", "Transform")); } } } void TaskTransformDialog::updateDraggerPlacement() { const auto placement = vp->getObjectPlacement(); vp->setDraggerPlacement(placement); } void TaskTransformDialog::onUndo() { updateDraggerPlacement(); openCommand(); } void TaskTransformDialog::onRedo() { updateDraggerPlacement(); openCommand(); } bool TaskTransformDialog::accept() { if (auto document = vp->getDocument()) { document->commitCommand(); document->resetEdit(); document->getDocument()->recompute(); } return Gui::TaskView::TaskDialog::accept(); } bool TaskTransformDialog::reject() { if (auto document = vp->getDocument()) { document->abortCommand(); document->resetEdit(); document->getDocument()->recompute(); } return Gui::TaskView::TaskDialog::reject(); } #include "moc_TaskTransform.cpp"