diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 1eb6ff50f8..967bf5bcb2 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ #include "Application.h" #include "CleanupProcess.h" #include "ComplexGeoData.h" +#include "Services.h" #include "DocumentObjectFileIncluded.h" #include "DocumentObjectGroup.h" #include "DocumentObjectGroupPy.h" @@ -2217,6 +2219,8 @@ void Application::initTypes() new Base::ExceptionProducer; new Base::ExceptionProducer; new Base::ExceptionProducer; + + Base::registerServiceImplementation(new NullCenterOfMass); } namespace { diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 8512c365ea..a4ab592db6 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -289,6 +289,7 @@ SET(FreeCADApp_CPP_SRCS MetadataPyImp.cpp ElementNamingUtils.cpp SafeMode.cpp + Services.cpp StringHasher.cpp StringHasherPyImp.cpp StringIDPyImp.cpp @@ -313,6 +314,7 @@ SET(FreeCADApp_HPP_SRCS MeasureManager.h Metadata.h ElementNamingUtils.h + Services.h StringHasher.h ) diff --git a/src/App/ComplexGeoData.cpp b/src/App/ComplexGeoData.cpp index e8d50030a4..b8a1c67c6a 100644 --- a/src/App/ComplexGeoData.cpp +++ b/src/App/ComplexGeoData.cpp @@ -184,6 +184,17 @@ bool ComplexGeoData::getCenterOfGravity(Base::Vector3d& unused) const return false; } +std::optional ComplexGeoData::centerOfGravity() const +{ + Base::Vector3d centerOfGravity; + + if (getCenterOfGravity(centerOfGravity)) { + return centerOfGravity; + } + + return {}; +} + const std::string& ComplexGeoData::elementMapPrefix() { static std::string prefix(ELEMENT_MAP_PREFIX); diff --git a/src/App/ComplexGeoData.h b/src/App/ComplexGeoData.h index 995b797944..8c0f8dc59b 100644 --- a/src/App/ComplexGeoData.h +++ b/src/App/ComplexGeoData.h @@ -28,6 +28,8 @@ #define APP_COMPLEX_GEO_DATA_H #include +#include + #include #include #include @@ -200,6 +202,7 @@ public: * The default implementation only returns false. */ virtual bool getCenterOfGravity(Base::Vector3d& center) const; + virtual std::optional centerOfGravity() const; //@} static const std::string& elementMapPrefix(); diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index d87f405ceb..344aea1a58 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -61,12 +61,7 @@ void GeoFeature::transformPlacement(const Base::Placement& transform) Base::Placement GeoFeature::globalPlacement() const { - auto* group = GeoFeatureGroupExtension::getGroupOfObject(this); - if (group) { - auto ext = group->getExtensionByType(); - return ext->globalGroupPlacement() * Placement.getValue(); - } - return Placement.getValue(); + return GeoFeature::getGlobalPlacement(this); } const PropertyComplexGeoData* GeoFeature::getPropertyOfGeometry() const @@ -316,6 +311,11 @@ Base::Placement GeoFeature::getGlobalPlacement(App::DocumentObject* targetObj, return plc; } + if (rootObj->isLink()) { + // Update doc in case its an external link. + doc = rootObj->getLinkedObject()->getDocument(); + } + for (auto& name : names) { App::DocumentObject* obj = doc->getObject(name.c_str()); if (!obj) { @@ -351,3 +351,20 @@ Base::Placement GeoFeature::getGlobalPlacement(App::DocumentObject* targetObj, return getGlobalPlacement(targetObj, prop->getValue(), subs[0]); } + +Base::Placement GeoFeature::getGlobalPlacement(const DocumentObject* obj) +{ + auto placementProperty = obj->getPropertyByName("Placement"); + + if (!placementProperty) { + return {}; + } + + auto* group = GeoFeatureGroupExtension::getGroupOfObject(obj); + if (group) { + auto ext = group->getExtensionByType(); + return ext->globalGroupPlacement() * placementProperty->getValue(); + } + + return placementProperty->getValue(); +} diff --git a/src/App/GeoFeature.h b/src/App/GeoFeature.h index cb4dcfbd66..abc36912c5 100644 --- a/src/App/GeoFeature.h +++ b/src/App/GeoFeature.h @@ -195,6 +195,7 @@ public: static Base::Placement getGlobalPlacement(DocumentObject* targetObj, DocumentObject* rootObj, const std::string& sub); static Base::Placement getGlobalPlacement(DocumentObject* targetObj, PropertyXLinkSub* prop); + static Base::Placement getGlobalPlacement(const DocumentObject* obj); protected: void onChanged(const Property* prop) override; diff --git a/src/App/Services.cpp b/src/App/Services.cpp new file mode 100644 index 0000000000..ae70735441 --- /dev/null +++ b/src/App/Services.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "Services.h" + +std::optional +App::NullCenterOfMass::ofDocumentObject([[maybe_unused]] DocumentObject* object) const +{ + return std::nullopt; +} \ No newline at end of file diff --git a/src/App/Services.h b/src/App/Services.h new file mode 100644 index 0000000000..297819e0c6 --- /dev/null +++ b/src/App/Services.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef APP_SERVICES_H +#define APP_SERVICES_H + +#include "DocumentObject.h" + +#include +#include + +namespace App +{ + +/** +* This service should provide placement of given sub object (like for example face). +* This feature is not implemented in the core and so it must be provided by module. +*/ +class SubObjectPlacementProvider +{ +public: + virtual ~SubObjectPlacementProvider() = default; + + /** + * Returns placement of sub object relative to the base placement. + */ + virtual Base::Placement calculate(SubObjectT object, Base::Placement basePlacement) const = 0; +}; + +/** +* This service should provide center of mass calculation; +*/ +class CenterOfMassProvider +{ +public: + virtual ~CenterOfMassProvider() = default; + + virtual std::optional ofDocumentObject(DocumentObject* object) const = 0; +}; + +/** +* Default implementation for the center of mass contract +* It always returns empty optional +*/ +class NullCenterOfMass final : public CenterOfMassProvider +{ +public: + std::optional ofDocumentObject(DocumentObject* object) const override; +}; + +} + + +#endif // APP_SERVICES_H diff --git a/src/Base/Rotation.cpp b/src/Base/Rotation.cpp index e1c871e0f7..623b41dd2d 100644 --- a/src/Base/Rotation.cpp +++ b/src/Base/Rotation.cpp @@ -81,6 +81,19 @@ Rotation::Rotation(const Vector3d& rotateFrom, const Vector3d& rotateTo) this->setValue(rotateFrom, rotateTo); } +Rotation Rotation::fromNormalVector(const Vector3d& normal) +{ + // We rotate Z axis to be aligned with the supplied normal vector + return Rotation(Vector3d(0, 0, 1), normal); +} + +Rotation Rotation::fromEulerAngles(EulerSequence theOrder, double alpha, double beta, double gamma) +{ + Rotation rotation; + rotation.setEulerAngles(theOrder, alpha, beta, gamma); + return rotation; +} + const double* Rotation::getValue() const { return &this->quat[0]; diff --git a/src/Base/Rotation.h b/src/Base/Rotation.h index f663c81510..9f8e6b5c16 100644 --- a/src/Base/Rotation.h +++ b/src/Base/Rotation.h @@ -49,26 +49,6 @@ public: Rotation(const Rotation& rot) = default; Rotation(Rotation&& rot) = default; ~Rotation() = default; - //@} - - /** Methods to get or set rotations. */ - //@{ - const double* getValue() const; - void getValue(double& q0, double& q1, double& q2, double& q3) const; - void setValue(double q0, double q1, double q2, double q3); - /// If not a null quaternion then \a axis will be normalized - void getValue(Vector3d& axis, double& rfAngle) const; - /// Does the same as the method above unless normalizing the axis. - void getRawValue(Vector3d& axis, double& rfAngle) const; - void getValue(Matrix4D& matrix) const; - void setValue(const double q[4]); - void setValue(const Matrix4D& matrix); - void setValue(const Vector3d& axis, double fAngle); - void setValue(const Vector3d& rotateFrom, const Vector3d& rotateTo); - /// Euler angles in yaw,pitch,roll notation - void setYawPitchRoll(double y, double p, double r); - /// Euler angles in yaw,pitch,roll notation - void getYawPitchRoll(double& y, double& p, double& r) const; enum EulerSequence { @@ -112,6 +92,34 @@ public: EulerSequenceLast, }; + + /// Utility function to create Rotation based on direction / normal vector + /// Z base vector is assumed to represent the normal vector + static Rotation fromNormalVector(const Vector3d& normal); + /// Utility function to create Rotation based on euler angles + static Rotation + fromEulerAngles(EulerSequence theOrder, double alpha, double beta, double gamma); + //@} + + /** Methods to get or set rotations. */ + //@{ + const double* getValue() const; + void getValue(double& q0, double& q1, double& q2, double& q3) const; + void setValue(double q0, double q1, double q2, double q3); + /// If not a null quaternion then \a axis will be normalized + void getValue(Vector3d& axis, double& rfAngle) const; + /// Does the same as the method above unless normalizing the axis. + void getRawValue(Vector3d& axis, double& rfAngle) const; + void getValue(Matrix4D& matrix) const; + void setValue(const double q[4]); + void setValue(const Matrix4D& matrix); + void setValue(const Vector3d& axis, double fAngle); + void setValue(const Vector3d& rotateFrom, const Vector3d& rotateTo); + /// Euler angles in yaw,pitch,roll notation + void setYawPitchRoll(double y, double p, double r); + /// Euler angles in yaw,pitch,roll notation + void getYawPitchRoll(double& y, double& p, double& r) const; + static const char* eulerSequenceName(EulerSequence seq); static EulerSequence eulerSequenceFromName(const char* name); void getEulerAngles(EulerSequence theOrder, double& alpha, double& beta, double& gamma) const; diff --git a/src/Base/ServiceProvider.cpp b/src/Base/ServiceProvider.cpp index 1f99a53100..7401c3e9cf 100644 --- a/src/Base/ServiceProvider.cpp +++ b/src/Base/ServiceProvider.cpp @@ -26,8 +26,7 @@ #include "ServiceProvider.h" -Base::ServiceProvider& Base::ServiceProvider::get() +namespace Base { - static Base::ServiceProvider instance; - return instance; +Base::ServiceProvider globalServiceProvider; } diff --git a/src/Base/ServiceProvider.h b/src/Base/ServiceProvider.h index 738c56d172..8746de7baa 100644 --- a/src/Base/ServiceProvider.h +++ b/src/Base/ServiceProvider.h @@ -36,6 +36,49 @@ namespace Base { +/** + * Class that implements basic service container that can be used to obtain different implementation + * of various services. + * + * Primary use of such container is to provide ability to define global services that can be + * implemented within non-core modules. This for example allows to use code that is available only + * in Part module from Base with the only requirement being that Part implements specific interface + * and registers the service within service provider. + * + * For ease of use global service provider instance is provided with convenience functions: + * - Base::provideService + * - Base::provideServiceImplementations + * - Base::registerServiceImplementation + * + * As the example, we can define service that provides placement of sub objects in App: + * @code + * class SubObjectPlacementProvider + * { + * public: + * virtual Base::Placement calculate(SubObjectT object, Base::Placement basePlacement) const = + * 0; + * }; + * @endcode + * + * App does not know how to implement this service, but it can be implemented within Part module: + * @code + * class AttacherSubObjectPlacement final: public App::SubObjectPlacementProvider { ... } + * + * // later in module initialization method + * + * Base::registerServiceImplementation(new + * AttacherSubObjectPlacement); + * @endcode + * + * This service can then be obtained inside other modules, without them being aware of the + * implementation - only the interface: + * + * @code + * auto subObjectPlacementProvider = Base::provideService(); + * @endcode + * + * This function can (and should) be used as default for constructor injection of services. + */ class BaseExport ServiceProvider { struct ServiceDescriptor @@ -106,35 +149,53 @@ public: * @tparam T Service interface */ template - void implement(T* contract) + void registerImplementation(T* contract) { ServiceDescriptor descriptor {typeid(T).name(), contract}; _implementations[typeid(T).name()].push_front(descriptor); } - static ServiceProvider& get(); - private: - std::map> _implementations; + std::map> _implementations; }; +BaseExport extern ServiceProvider globalServiceProvider; + +/** + * Obtains primary implementation of requested service from the global service provider. + * + * @tparam T Service kind to obtain. + * @return Primary implementation of the service or nullptr if there is no implementation available. + */ template -T* provideImplementation() +T* provideService() { - return ServiceProvider::get().provide(); + return globalServiceProvider.provide(); } +/** + * Obtains all available implementations of requested service in the global service provider. + * + * @tparam T Service kind to obtain. + * @return List of available service implementation. + */ template -std::list provideAllImplementations() +std::list provideServiceImplementations() { - return ServiceProvider::get().all(); + return globalServiceProvider.all(); } +/** + * Registers implementation of service in the global service provider. + * + * @tparam T Service kind to obtain. + * @return List of available service implementation. + */ template -void implementContract(T* implementation) +void registerServiceImplementation(T* implementation) { - ServiceProvider::get().implement(implementation); + globalServiceProvider.registerImplementation(implementation); } } // namespace Base diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 9e71c9a4f7..ede57f85ba 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -402,6 +402,7 @@ SET(Gui_UIC_SRCS SceneInspector.ui InputVector.ui Placement.ui + TaskCSysDragger.ui TextureMapping.ui TaskView/TaskAppearance.ui TaskView/TaskOrientation.ui diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index 1ca7d684e2..1ab6a81e84 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -405,10 +405,18 @@ void QuantitySpinBox::resizeEvent(QResizeEvent * event) resizeWidget(); } -void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent *event) +void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event) { - if (!handleKeyEvent(event->text())) + const auto isEnter = event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return; + + if (isEnter && !isNormalized()) { + normalize(); + return; + } + + if (!handleKeyEvent(event->text())) { QAbstractSpinBox::keyPressEvent(event); + } } void Gui::QuantitySpinBox::paintEvent(QPaintEvent*) @@ -441,7 +449,7 @@ void QuantitySpinBox::updateEdit(const QString& text) edit->setText(text); - cursor = qBound(0, cursor, edit->displayText().size() - d->unitStr.size()); + cursor = qBound(0, cursor, qMax(0, edit->displayText().size() - d->unitStr.size())); if (selsize > 0) { edit->setSelection(0, cursor); } @@ -477,6 +485,24 @@ double QuantitySpinBox::rawValue() const return d->quantity.getValue(); } +void QuantitySpinBox::normalize() +{ + // this does not really change the value, only the representation + QSignalBlocker blocker(this); + + Q_D(const QuantitySpinBox); + return setValue(d->quantity); +} + +bool QuantitySpinBox::isNormalized() +{ + static const QRegularExpression operators(QStringLiteral("[+\\-/*]"), + QRegularExpression::CaseInsensitiveOption); + + Q_D(const QuantitySpinBox); + return !d->validStr.contains(operators); +} + void QuantitySpinBox::setValue(const Base::Quantity& value) { Q_D(QuantitySpinBox); @@ -884,6 +910,7 @@ void QuantitySpinBox::focusInEvent(QFocusEvent * event) void QuantitySpinBox::focusOutEvent(QFocusEvent * event) { validateInput(); + normalize(); QToolTip::hideText(); QAbstractSpinBox::focusOutEvent(event); diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h index dadc28d906..780553718c 100644 --- a/src/Gui/QuantitySpinBox.h +++ b/src/Gui/QuantitySpinBox.h @@ -57,6 +57,9 @@ public: /// Get the current quantity without unit double rawValue() const; + void normalize(); + bool isNormalized(); + /// Gives the current state of the user input, gives true if it is a valid input with correct quantity /// or returns false if the input is a unparsable string or has a wrong unit. bool hasValidInput() const; diff --git a/src/Gui/SoFCCSysDragger.cpp b/src/Gui/SoFCCSysDragger.cpp index c7e049e5c7..3d4ad6b650 100644 --- a/src/Gui/SoFCCSysDragger.cpp +++ b/src/Gui/SoFCCSysDragger.cpp @@ -27,10 +27,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -46,6 +44,9 @@ #include #include #include +#include +#include +#include #endif #include @@ -56,6 +57,8 @@ #include "MainWindow.h" #include "SoFCDB.h" +#include + /* GENERAL NOTE ON COIN3D CUSTOM DRAGGERS @@ -100,14 +103,20 @@ TDragger::TDragger() this->ref(); #endif - SO_KIT_ADD_CATALOG_ENTRY(translatorSwitch, SoSwitch, TRUE, geomSeparator, "", TRUE); - SO_KIT_ADD_CATALOG_ENTRY(translator, SoSeparator, TRUE, translatorSwitch, "", TRUE); - SO_KIT_ADD_CATALOG_ENTRY(translatorActive, SoSeparator, TRUE, translatorSwitch, "", TRUE); + SO_KIT_ADD_CATALOG_ENTRY(translator, SoSeparator, TRUE, geomSeparator, "", TRUE); + SO_KIT_ADD_CATALOG_ENTRY(activeSwitch, SoSwitch, TRUE, translator, "", TRUE); + SO_KIT_ADD_CATALOG_ENTRY(activeColor, SoBaseColor, TRUE, activeSwitch, "", TRUE); + SO_KIT_ADD_CATALOG_ENTRY(coneSeparator, SoSeparator, TRUE, translator, "", TRUE); + SO_KIT_ADD_CATALOG_ENTRY(cylinderSeparator, SoSeparator, TRUE, translator, "", TRUE); + SO_KIT_ADD_CATALOG_ENTRY(labelSeparator, SoSeparator, TRUE, translator, "", TRUE); if (SO_KIT_IS_FIRST_INSTANCE()) { buildFirstInstance(); } + SO_KIT_ADD_CATALOG_ENTRY(translator, SoSeparator, TRUE, geomSeparator, "", TRUE); + + SO_KIT_ADD_FIELD(label, ("")); SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0)); SO_KIT_ADD_FIELD(translationIncrement, (1.0)); SO_KIT_ADD_FIELD(translationIncrementCount, (0)); @@ -118,11 +127,15 @@ TDragger::TDragger() // initialize default parts. // first is from 'SO_KIT_CATALOG_ENTRY_HEADER' macro // second is unique name from buildFirstInstance(). - this->setPartAsDefault("translator", "CSysDynamics_TDragger_Translator"); - this->setPartAsDefault("translatorActive", "CSysDynamics_TDragger_TranslatorActive"); + SoInteractionKit::setPartAsDefault("coneSeparator", "CSysDynamics_TDragger_Cone"); + SoInteractionKit::setPartAsDefault("cylinderSeparator", "CSysDynamics_TDragger_Cylinder"); + SoInteractionKit::setPartAsDefault("activeColor", "CSysDynamics_TDragger_ActiveColor"); + + SoInteractionKit::setPart("labelSeparator", buildLabelGeometry()); + + auto sw = SO_GET_ANY_PART(this, "activeSwitch", SoSwitch); + SoInteractionKit::setSwitchValue(sw, SO_SWITCH_NONE); - SoSwitch* sw = SO_GET_ANY_PART(this, "translatorSwitch", SoSwitch); - SoInteractionKit::setSwitchValue(sw, 0); this->addStartCallback(&TDragger::startCB); this->addMotionCallback(&TDragger::motionCB); @@ -150,34 +163,22 @@ TDragger::~TDragger() void TDragger::buildFirstInstance() { - SoGroup* geometryGroup = buildGeometry(); + auto cylinderSeparator = buildCylinderGeometry(); + auto coneSeparator = buildConeGeometry(); + auto activeColor = buildActiveColor(); - auto localTranslator = new SoSeparator(); - localTranslator->setName("CSysDynamics_TDragger_Translator"); - localTranslator->addChild(geometryGroup); - SoFCDB::getStorage()->addChild(localTranslator); + cylinderSeparator->setName("CSysDynamics_TDragger_Cylinder"); + coneSeparator->setName("CSysDynamics_TDragger_Cone"); + activeColor->setName("CSysDynamics_TDragger_ActiveColor"); - auto localTranslatorActive = new SoSeparator(); - localTranslatorActive->setName("CSysDynamics_TDragger_TranslatorActive"); - auto colorActive = new SoBaseColor(); - colorActive->rgb.setValue(1.0, 1.0, 0.0); - localTranslatorActive->addChild(colorActive); - localTranslatorActive->addChild(geometryGroup); - SoFCDB::getStorage()->addChild(localTranslatorActive); + SoFCDB::getStorage()->addChild(cylinderSeparator); + SoFCDB::getStorage()->addChild(coneSeparator); + SoFCDB::getStorage()->addChild(activeColor); } -SoGroup* TDragger::buildGeometry() +SoSeparator* TDragger::buildCylinderGeometry() const { - // this builds one leg in the Y+ direction because of default done direction. - // the location anchor for shapes is the center of shape. - - auto root = new SoGroup(); - - // cylinder - float cylinderHeight = 10.0; - float cylinderRadius = 0.1f; auto cylinderSeparator = new SoSeparator(); - root->addChild(cylinderSeparator); auto cylinderLightModel = new SoLightModel(); cylinderLightModel->model = SoLightModel::BASE_COLOR; @@ -192,14 +193,15 @@ SoGroup* TDragger::buildGeometry() cylinder->height.setValue(cylinderHeight); cylinderSeparator->addChild(cylinder); - // cone - float coneBottomRadius = 0.8F; - float coneHeight = 2.5; - auto coneSeparator = new SoSeparator(); - root->addChild(coneSeparator); + return cylinderSeparator; +} +SoSeparator* TDragger::buildConeGeometry() const +{ auto coneLightModel = new SoLightModel(); coneLightModel->model = SoLightModel::BASE_COLOR; + + auto coneSeparator = new SoSeparator(); coneSeparator->addChild(coneLightModel); auto pickStyle = new SoPickStyle(); @@ -216,7 +218,35 @@ SoGroup* TDragger::buildGeometry() cone->height.setValue(coneHeight); coneSeparator->addChild(cone); - return root; + return coneSeparator; +} + +SoSeparator* TDragger::buildLabelGeometry() +{ + auto labelSeparator = new SoSeparator(); + + auto labelTranslation = new SoTranslation(); + labelTranslation->translation.setValue(0.0, cylinderHeight + coneHeight * 1.5, 0.0); + labelSeparator->addChild(labelTranslation); + + auto label = new SoFrameLabel(); + label->string.connectFrom(&this->label); + label->textColor.setValue(1.0, 1.0, 1.0); + label->horAlignment = SoImage::CENTER; + label->vertAlignment = SoImage::HALF; + label->border = false; + label->backgroundUseBaseColor = true; + labelSeparator->addChild(label); + + return labelSeparator; +} + +SoBaseColor* TDragger::buildActiveColor() +{ + auto colorActive = new SoBaseColor(); + colorActive->rgb.setValue(1.0, 1.0, 0.0); + + return colorActive; } void TDragger::startCB(void*, SoDragger* d) @@ -274,8 +304,8 @@ void TDragger::valueChangedCB(void*, SoDragger* d) void TDragger::dragStart() { SoSwitch* sw; - sw = SO_GET_ANY_PART(this, "translatorSwitch", SoSwitch); - SoInteractionKit::setSwitchValue(sw, 1); + sw = SO_GET_ANY_PART(this, "activeSwitch", SoSwitch); + SoInteractionKit::setSwitchValue(sw, SO_SWITCH_ALL); // do an initial projection to eliminate discrepancies // in arrow head pick. we define the arrow in the y+ direction @@ -334,8 +364,8 @@ void TDragger::drag() void TDragger::dragFinish() { SoSwitch* sw; - sw = SO_GET_ANY_PART(this, "translatorSwitch", SoSwitch); - SoInteractionKit::setSwitchValue(sw, 0); + sw = SO_GET_ANY_PART(this, "activeSwitch", SoSwitch); + SoInteractionKit::setSwitchValue(sw, SO_SWITCH_NONE); } SbBool TDragger::setUpConnections(SbBool onoff, SbBool doitalways) @@ -1014,7 +1044,7 @@ SoFCCSysDragger::SoFCCSysDragger() SO_KIT_ADD_CATALOG_ENTRY(annotation, So3DAnnotation, TRUE, geomSeparator, "", TRUE); SO_KIT_ADD_CATALOG_ENTRY(scaleNode, SoScale, TRUE, annotation, "", TRUE); - + SO_KIT_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, TRUE, annotation, "", TRUE); // Translator SO_KIT_ADD_CATALOG_ENTRY(xTranslatorSwitch, SoSwitch, TRUE, annotation, "", TRUE); @@ -1142,7 +1172,6 @@ SoFCCSysDragger::SoFCCSysDragger() SO_KIT_ADD_CATALOG_ENTRY(zRotatorDragger, RDragger, TRUE, zRotatorSeparator, "", TRUE); // Other - SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0)); SO_KIT_ADD_FIELD(translationIncrement, (1.0)); SO_KIT_ADD_FIELD(translationIncrementCountX, (0)); @@ -1158,6 +1187,10 @@ SoFCCSysDragger::SoFCCSysDragger() SO_KIT_ADD_FIELD(draggerSize, (1.0)); SO_KIT_ADD_FIELD(autoScaleResult, (1.0)); + SO_KIT_ADD_FIELD(xAxisLabel, ("X")); + SO_KIT_ADD_FIELD(yAxisLabel, ("Y")); + SO_KIT_ADD_FIELD(zAxisLabel, ("Z")); + SO_KIT_INIT_INSTANCE(); // Colors @@ -1165,21 +1198,22 @@ SoFCCSysDragger::SoFCCSysDragger() SbColor(0, 1.0, 0).getPackedValue(0.0f), SbColor(0, 0, 1.0).getPackedValue(0.0f)); - // Increments - // Translator TDragger* tDragger; tDragger = SO_GET_ANY_PART(this, "xTranslatorDragger", TDragger); tDragger->translationIncrement.connectFrom(&this->translationIncrement); tDragger->autoScaleResult.connectFrom(&this->autoScaleResult); + tDragger->label.connectFrom(&xAxisLabel); translationIncrementCountX.connectFrom(&tDragger->translationIncrementCount); tDragger = SO_GET_ANY_PART(this, "yTranslatorDragger", TDragger); tDragger->translationIncrement.connectFrom(&this->translationIncrement); tDragger->autoScaleResult.connectFrom(&this->autoScaleResult); + tDragger->label.connectFrom(&yAxisLabel); translationIncrementCountY.connectFrom(&tDragger->translationIncrementCount); tDragger = SO_GET_ANY_PART(this, "zTranslatorDragger", TDragger); tDragger->translationIncrement.connectFrom(&this->translationIncrement); tDragger->autoScaleResult.connectFrom(&this->autoScaleResult); + tDragger->label.connectFrom(&zAxisLabel); translationIncrementCountZ.connectFrom(&tDragger->translationIncrementCount); // Planar Translator TPlanarDragger* tPlanarDragger; @@ -1275,6 +1309,9 @@ SoFCCSysDragger::SoFCCSysDragger() localScaleNode->scaleFactor.connectFrom(&scaleEngine->vector); autoScaleResult.connectFrom(&draggerSize); + SoPickStyle* localPickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + localPickStyle->style = SoPickStyle::SHAPE_ON_TOP; + addValueChangedCallback(&SoFCCSysDragger::valueChangedCB); translationSensor.setFunction(&SoFCCSysDragger::translationSensorCB); diff --git a/src/Gui/SoFCCSysDragger.h b/src/Gui/SoFCCSysDragger.h index 20364506b1..99c7a77ac6 100644 --- a/src/Gui/SoFCCSysDragger.h +++ b/src/Gui/SoFCCSysDragger.h @@ -29,10 +29,12 @@ #include #include #include +#include #include #include #include #include +#include #include class SoCamera; @@ -50,12 +52,23 @@ namespace Gui class TDragger : public SoDragger { SO_KIT_HEADER(TDragger); - SO_KIT_CATALOG_ENTRY_HEADER(translatorSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(activeSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(activeColor); SO_KIT_CATALOG_ENTRY_HEADER(translator); - SO_KIT_CATALOG_ENTRY_HEADER(translatorActive); + SO_KIT_CATALOG_ENTRY_HEADER(cylinderSeparator); + SO_KIT_CATALOG_ENTRY_HEADER(coneSeparator); + SO_KIT_CATALOG_ENTRY_HEADER(labelSeparator); + + static constexpr float coneBottomRadius { 0.8f }; + static constexpr float coneHeight { 2.5f }; + + static constexpr float cylinderHeight { 10.0f }; + static constexpr float cylinderRadius { 0.1f }; public: static void initClass(); TDragger(); + + SoSFString label; //!< set from outside and used to label SoSFVec3f translation; //!< set from outside and used from outside for single precision. SoSFDouble translationIncrement; //!< set from outside and used for rounding. SoSFInt32 translationIncrementCount; //!< number of steps. used from outside. @@ -82,6 +95,12 @@ private: void buildFirstInstance(); SbVec3f roundTranslation(const SbVec3f &vecIn, float incrementIn); SoGroup* buildGeometry(); + + SoSeparator* buildCylinderGeometry() const; + SoSeparator* buildConeGeometry() const; + SoSeparator* buildLabelGeometry(); + SoBaseColor* buildActiveColor(); + using inherited = SoDragger; }; @@ -198,6 +217,7 @@ class GuiExport SoFCCSysDragger : public SoDragger SO_KIT_HEADER(SoFCCSysDragger); SO_KIT_CATALOG_ENTRY_HEADER(annotation); SO_KIT_CATALOG_ENTRY_HEADER(scaleNode); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); // Translator SO_KIT_CATALOG_ENTRY_HEADER(xTranslatorSwitch); SO_KIT_CATALOG_ENTRY_HEADER(yTranslatorSwitch); @@ -263,6 +283,10 @@ public: SoSFInt32 rotationIncrementCountY; //!< used from outside for rotation y steps. SoSFInt32 rotationIncrementCountZ; //!< used from outside for rotation z steps. + SoSFString xAxisLabel; //!< label for X axis + SoSFString yAxisLabel; //!< label for Y axis + SoSFString zAxisLabel; //!< label for Z axis + void clearIncrementCounts(); //!< used to reset after drag update. /*! @brief Overall scale of dragger node. diff --git a/src/Gui/SoTextLabel.cpp b/src/Gui/SoTextLabel.cpp index 6b2f55d53c..1a4a29c0aa 100644 --- a/src/Gui/SoTextLabel.cpp +++ b/src/Gui/SoTextLabel.cpp @@ -379,6 +379,8 @@ SoFrameLabel::SoFrameLabel() SO_NODE_ADD_FIELD(name, ("Helvetica")); SO_NODE_ADD_FIELD(size, (12)); SO_NODE_ADD_FIELD(frame, (true)); + SO_NODE_ADD_FIELD(border, (true)); + SO_NODE_ADD_FIELD(backgroundUseBaseColor, (false)); //SO_NODE_ADD_FIELD(image, (SbVec2s(0,0), 0, NULL)); } @@ -388,7 +390,6 @@ void SoFrameLabel::setIcon(const QPixmap &pixMap) drawImage(); } - void SoFrameLabel::notify(SoNotList * list) { SoField *f = list->getLastField(); @@ -398,9 +399,11 @@ void SoFrameLabel::notify(SoNotList * list) f == &this->justification || f == &this->name || f == &this->size || - f == &this->frame) { + f == &this->frame || + f == &this->border) { drawImage(); } + inherited::notify(list); } @@ -418,11 +421,12 @@ void SoFrameLabel::drawImage() int w = 0; int h = fm.height() * num; const SbColor& b = backgroundColor.getValue(); - QColor brush; - brush.setRgbF(b[0],b[1],b[2]); + QColor backgroundBrush; + backgroundBrush.setRgbF(b[0],b[1],b[2]); const SbColor& t = textColor.getValue(); QColor front; front.setRgbF(t[0],t[1],t[2]); + const QPen borderPen(QColor(0,0,127), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); QStringList lines; for (int i=0; igetState(); + const SbColor& diffuse = SoLazyElement::getDiffuse(state, 0); + + if (diffuse != this->backgroundColor.getValue()) { + this->backgroundColor.setValue(diffuse); + } + } + inherited::GLRender(action); } diff --git a/src/Gui/SoTextLabel.h b/src/Gui/SoTextLabel.h index b91feb1980..56b9726711 100644 --- a/src/Gui/SoTextLabel.h +++ b/src/Gui/SoTextLabel.h @@ -118,6 +118,8 @@ public: SoSFName name; SoSFInt32 size; SoSFBool frame; + SoSFBool border; + SoSFBool backgroundUseBaseColor; //SoSFImage image; QPixmap iconPixmap; diff --git a/src/Gui/TaskCSysDragger.cpp b/src/Gui/TaskCSysDragger.cpp index ab23bc533a..c59a9f3f89 100644 --- a/src/Gui/TaskCSysDragger.cpp +++ b/src/Gui/TaskCSysDragger.cpp @@ -25,12 +25,19 @@ #include #include #include -#include #endif +#include +#include + #include -#include "Document.h" // must be before TaskCSysDragger.h -#include "TaskCSysDragger.h" +#include +#include +#include +#include +#include + +#include "Document.h" // must be before TaskCSysDragger.h #include "Application.h" #include "BitmapFactory.h" #include "Command.h" @@ -39,139 +46,717 @@ #include "ViewProviderDragger.h" #include "TaskView/TaskView.h" +#include "TaskCSysDragger.h" +#include "ui_TaskCSysDragger.h" + +#include using namespace Gui; - -static double degreesToRadians(const double °reesIn) +namespace { - return degreesIn * (M_PI / 180.0); -} - -TaskCSysDragger::TaskCSysDragger(Gui::ViewProviderDocumentObject* vpObjectIn, Gui::SoFCCSysDragger* draggerIn) : - dragger(draggerIn) +void alignGridLayoutColumns(const std::list& layouts, unsigned column = 0) { - assert(vpObjectIn); - assert(draggerIn); - vpObject = vpObjectIn->getObject(); - dragger->ref(); + std::vector widths; - setupGui(); -} + auto getActualWidth = [&](const QGridLayout* layout) -> int { + if (auto const item = layout->itemAtPosition(0, column)) { + return item->geometry().width(); + } -TaskCSysDragger::~TaskCSysDragger() -{ - dragger->unref(); - Gui::Application::Instance->commandManager().getCommandByName("Std_OrthographicCamera")->setEnabled(true); - Gui::Application::Instance->commandManager().getCommandByName("Std_PerspectiveCamera")->setEnabled(true); -} + return 0; + }; -void TaskCSysDragger::dragStartCallback(void *, SoDragger *) -{ - // This is called when a manipulator is about to manipulating - if(firstDrag) - { - Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Transform")); - firstDrag=false; + 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); } } -void TaskCSysDragger::setupGui() +} // namespace + +TaskTransform::TaskTransform(Gui::ViewProviderDragger* vp, + Gui::SoFCCSysDragger* 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_TaskCSysDragger) { - auto incrementsBox = new Gui::TaskView::TaskBox( - Gui::BitmapFactory().pixmap("Std_TransformManip"), - tr("Transform"), true, nullptr); + blockSelection(true); - auto gridLayout = new QGridLayout(); - gridLayout->setColumnStretch(1, 1); + dragger->addStartCallback(dragStartCallback, this); + dragger->addMotionCallback(dragMotionCallback, this); - auto tLabel = new QLabel(tr("Translation Increment:"), incrementsBox); - gridLayout->addWidget(tLabel, 0, 0, Qt::AlignRight); + vp->resetTransformOrigin(); - QFontMetrics metrics(QApplication::font()); - int spinBoxWidth = metrics.averageCharWidth() * 20; - tSpinBox = new QuantitySpinBox(incrementsBox); - tSpinBox->setMinimum(0.0); - tSpinBox->setMaximum(std::numeric_limits::max()); - tSpinBox->setUnit(Base::Unit::Length); - tSpinBox->setMinimumWidth(spinBoxWidth); - gridLayout->addWidget(tSpinBox, 0, 1, Qt::AlignLeft); + referencePlacement = vp->getObjectPlacement(); + referenceRotation = referencePlacement.getRotation(); - auto rLabel = new QLabel(tr("Rotation Increment:"), incrementsBox); - gridLayout->addWidget(rLabel, 1, 0, Qt::AlignRight); + globalOrigin = vp->getObjectPlacement() * App::GeoFeature::getGlobalPlacement(vp->getObject()).inverse(); - rSpinBox = new QuantitySpinBox(incrementsBox); - rSpinBox->setMinimum(0.0); - rSpinBox->setMaximum(180.0); - rSpinBox->setUnit(Base::Unit::Angle); - rSpinBox->setMinimumWidth(spinBoxWidth); - gridLayout->addWidget(rSpinBox, 1, 1, Qt::AlignLeft); - - incrementsBox->groupLayout()->addLayout(gridLayout); - Content.push_back(incrementsBox); - - connect(tSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, &TaskCSysDragger::onTIncrementSlot); - connect(rSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, &TaskCSysDragger::onRIncrementSlot); + setupGui(); } -void TaskCSysDragger::onTIncrementSlot(double freshValue) +TaskTransform::~TaskTransform() { - dragger->translationIncrement.setValue(freshValue); + Gui::Application::Instance->commandManager() + .getCommandByName("Std_OrthographicCamera") + ->setEnabled(true); + + Gui::Application::Instance->commandManager() + .getCommandByName("Std_PerspectiveCamera") + ->setEnabled(true); + + savePreferences(); } -void TaskCSysDragger::onRIncrementSlot(double freshValue) +void TaskTransform::dragStartCallback(void* data, SoDragger*) { - dragger->rotationIncrement.setValue(degreesToRadians(freshValue)); + // 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, 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, + [=](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(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(); +} + +TaskCSysDragger::TaskCSysDragger(ViewProviderDragger* vp, SoFCCSysDragger* dragger) + : vp(vp) +{ + transform = new TaskTransform(vp, dragger); + Content.push_back(transform); } void TaskCSysDragger::open() { - dragger->addStartCallback(dragStartCallback, this); - //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); -// dragger->translationIncrement.setValue(lastTranslationIncrement); -// dragger->rotationIncrement.setValue(lastRotationIncrement); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/History/Dragger"); - double lastTranslationIncrement = hGrp->GetFloat("LastTranslationIncrement", 1.0); - double lastRotationIncrement = hGrp->GetFloat("LastRotationIncrement", 15.0); - tSpinBox->setValue(lastTranslationIncrement); - rSpinBox->setValue(lastRotationIncrement); + // we can't have user switching camera types while dragger is shown. + Gui::Application::Instance->commandManager() + .getCommandByName("Std_OrthographicCamera") + ->setEnabled(false); - Gui::TaskView::TaskDialog::open(); + Gui::Application::Instance->commandManager() + .getCommandByName("Std_PerspectiveCamera") + ->setEnabled(false); + + Gui::TaskView::TaskDialog::open(); + + Gui::Application::Instance->activeDocument()->openCommand( + QT_TRANSLATE_NOOP("Command", "Transform")); } bool TaskCSysDragger::accept() { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/History/Dragger"); - hGrp->SetFloat("LastTranslationIncrement", tSpinBox->rawValue()); - hGrp->SetFloat("LastRotationIncrement", rSpinBox->rawValue()); + if (auto documentObject = vp->getObject()) { + Gui::Document* document = + Gui::Application::Instance->getDocument(documentObject->getDocument()); + assert(document); + document->commitCommand(); + document->resetEdit(); + document->getDocument()->recompute(); + } - App::DocumentObject* dObject = vpObject.getObject(); - if (dObject) { - Gui::Document* document = Gui::Application::Instance->getDocument(dObject->getDocument()); - assert(document); - firstDrag = true; - document->commitCommand(); - document->resetEdit(); - document->getDocument()->recompute(); - } - return Gui::TaskView::TaskDialog::accept(); + return Gui::TaskView::TaskDialog::accept(); } bool TaskCSysDragger::reject() { - App::DocumentObject* dObject = vpObject.getObject(); - if (dObject) { - Gui::Document* document = Gui::Application::Instance->getDocument(dObject->getDocument()); - assert(document); - firstDrag = true; - document->abortCommand(); - document->resetEdit(); - document->getDocument()->recompute(); - } - return Gui::TaskView::TaskDialog::reject(); + if (auto documentObject = vp->getObject()) { + Gui::Document* document = + Gui::Application::Instance->getDocument(documentObject->getDocument()); + assert(document); + document->abortCommand(); + document->resetEdit(); + document->getDocument()->recompute(); + } + + return Gui::TaskView::TaskDialog::reject(); } #include "moc_TaskCSysDragger.cpp" diff --git a/src/Gui/TaskCSysDragger.h b/src/Gui/TaskCSysDragger.h index ba223e5d2c..0f7fed2cca 100644 --- a/src/Gui/TaskCSysDragger.h +++ b/src/Gui/TaskCSysDragger.h @@ -25,39 +25,152 @@ #define TASKCSYSDRAGGER_H #include "TaskView/TaskDialog.h" -#include +#include "TaskView/TaskView.h" +#include "ViewProviderDragger.h" + +#include +#include class SoDragger; -namespace Gui +namespace Attacher { - class QuantitySpinBox; - class SoFCCSysDragger; - class ViewProviderDragger; - - class TaskCSysDragger : public Gui::TaskView::TaskDialog - { - Q_OBJECT - public: - TaskCSysDragger(ViewProviderDocumentObject *vpObjectIn, SoFCCSysDragger *draggerIn); - ~TaskCSysDragger() override; - QDialogButtonBox::StandardButtons getStandardButtons() const override - { return QDialogButtonBox::Ok | QDialogButtonBox::Cancel;} - void open() override; - bool accept() override; - bool reject() override; - private Q_SLOTS: - void onTIncrementSlot(double freshValue); - void onRIncrementSlot(double freshValue); - private: - static inline bool firstDrag = true; - static void dragStartCallback(void * data, SoDragger * d); - void setupGui(); - App::DocumentObjectT vpObject; - SoFCCSysDragger *dragger; - QuantitySpinBox *tSpinBox; - QuantitySpinBox *rSpinBox; - }; + class AttachEngine; } -#endif // TASKCSYSDRAGGER_H +namespace Gui +{ +class QuantitySpinBox; +class SoFCCSysDragger; +class ViewProviderDragger; +class Ui_TaskCSysDragger; + +class TaskTransform : public Gui::TaskView::TaskBox, public Gui::SelectionObserver +{ + Q_OBJECT + + static constexpr double tolerance = 1e-7; + +public: + enum class SelectionMode { None, SelectTransformOrigin, SelectAlignTarget }; + enum class PlacementMode { ObjectOrigin, Centroid, Custom }; + enum class PositionMode { Local, Global }; + + struct CoordinateSystem + { + std::array labels; + Base::Placement origin; + }; + + Q_ENUM(SelectionMode) + Q_ENUM(PlacementMode) + Q_ENUM(PositionMode) + + TaskTransform(Gui::ViewProviderDragger* vp, + Gui::SoFCCSysDragger* dragger, + QWidget* parent = nullptr, + App::SubObjectPlacementProvider* subObjectPlacementProvider = + Base::provideService(), + App::CenterOfMassProvider* centerOfMassProvider = + Base::provideService()); + ~TaskTransform() override; + +private: + void onSelectionChanged(const SelectionChanges& msg) override; + +private Q_SLOTS: + void onPlacementModeChange(int index); + + void onPickTransformOrigin(); + void onTransformOriginReset(); + void onAlignRotationChanged(); + + void onAlignToOtherObject(); + void onFlip(); + + void onCoordinateSystemChange(int mode); + + void onPositionChange(); + void onRotationChange(QuantitySpinBox* changed); + +private: + static inline bool firstDrag = true; + static void dragStartCallback(void* data, SoDragger* d); + static void dragMotionCallback(void* data, SoDragger* d); + + void setSelectionMode(SelectionMode mode); + SelectionMode getSelectionMode() const; + + CoordinateSystem globalCoordinateSystem() const; + CoordinateSystem localCoordinateSystem() const; + CoordinateSystem currentCoordinateSystem() const; + + Base::Rotation::EulerSequence eulerSequence() const; + + void setupGui(); + + void loadPreferences(); + void savePreferences(); + + void loadPositionModeItems() const; + void loadPlacementModeItems() const; + + void updatePositionAndRotationUi() const; + void updateDraggerLabels() const; + void updateInputLabels() const; + void updateIncrements() const; + void updateTransformOrigin(); + void updateSpinBoxesReadOnlyStatus() const; + + void resetReferencePlacement(); + void resetReferenceRotation(); + + void moveObjectToDragger(); + + bool isDraggerAlignedToCoordinateSystem() const; + + ViewProviderDragger* vp; + + const App::SubObjectPlacementProvider* subObjectPlacementProvider; + const App::CenterOfMassProvider *centerOfMassProvider; + + CoinPtr dragger; + + Ui_TaskCSysDragger *ui; + + SelectionMode selectionMode { SelectionMode::None }; + PlacementMode placementMode { PlacementMode::ObjectOrigin }; + PositionMode positionMode { PositionMode::Local }; + + std::optional customTransformOrigin {}; + Base::Placement referencePlacement {}; + Base::Placement globalOrigin {}; + Base::Rotation referenceRotation {}; + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/History/Dragger"); +}; + +class TaskCSysDragger: public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskCSysDragger(ViewProviderDragger* vp, SoFCCSysDragger* dragger); + ~TaskCSysDragger() override = default; + + QDialogButtonBox::StandardButtons getStandardButtons() const override + { + return QDialogButtonBox::Ok | QDialogButtonBox::Cancel; + } + + void open() override; + bool accept() override; + bool reject() override; + +private: + ViewProviderDragger* vp; + TaskTransform* transform; +}; +} // namespace Gui + +#endif // TASKCSYSDRAGGER_H diff --git a/src/Gui/TaskCSysDragger.ui b/src/Gui/TaskCSysDragger.ui new file mode 100644 index 0000000000..0371c734a7 --- /dev/null +++ b/src/Gui/TaskCSysDragger.ui @@ -0,0 +1,532 @@ + + + Gui::TaskCSysDragger + + + + 0 + 0 + 450 + 1012 + + + + Placement + + + + + + 0 + + + + + Coordinate System + + + positionModeComboBox + + + + + + + + Local Coordinate System + + + + + Global Coordinate System + + + + + + + + + + 0 + + + + + align dragger rotation with selected coordinate system + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 41 + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 16 + + + + + + + + Translation + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + 0 + 0 + + + + X + + + xPositionSpinBox + + + + + + + + 0 + 0 + + + + Y + + + yPositionSpinBox + + + + + + + + 0 + 0 + + + + Z + + + zPositionSpinBox + + + + + + + + + + + + + + + + + Utilities + + + + + + Move to other object + + + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + + Flip + + + + + + + + + + Dragger + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + + + + 0 + 0 + + + + QFrame::Shape::NoFrame + + + <b>Snapping</b> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + + 0 + 0 + + + + Translation + + + translationIncrementSpinBox + + + + + + + 0.000000000000000 + + + 360.000000000000000 + + + 5.000000000000000 + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 10 + + + + + + + + + QLayout::SizeConstraint::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 68 + 0 + + + + Reference + + + referenceLineEdit + + + + + + + pick reference + + + + + + + true + + + + + + + + + + 0.000000000000000 + + + 2147483647.000000000000000 + + + 1.000000000000000 + + + + + + + Mode + + + placementComboBox + + + + + + + + 0 + 0 + + + + Rotation + + + rotationIncrementSpinBox + + + + + + + + + + Rotation + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + Y + + + yRotationSpinBox + + + + + + + + 0 + 0 + + + + Z + + + zRotationSpinBox + + + + + + + + + + + + + + 0 + 0 + + + + X + + + xRotationSpinBox + + + + + + + + + + + + + + + Gui::QuantitySpinBox + QAbstractSpinBox +
Gui/QuantitySpinBox.h
+
+
+ + placementComboBox + referenceLineEdit + pickTransformOriginButton + translationIncrementSpinBox + rotationIncrementSpinBox + positionModeComboBox + alignRotationCheckBox + xPositionSpinBox + yPositionSpinBox + zPositionSpinBox + xRotationSpinBox + yRotationSpinBox + zRotationSpinBox + alignToOtherObjectButton + flipPartButton + + + +
diff --git a/src/Gui/Utilities.h b/src/Gui/Utilities.h index b9e8019464..42079bf1e1 100644 --- a/src/Gui/Utilities.h +++ b/src/Gui/Utilities.h @@ -104,6 +104,28 @@ struct vec_traits { private: const vec_type& v; }; + +template <> +inline SbMatrix convertTo(const Base::Matrix4D& vec2) +{ + double dMtrx[16]; + vec2.getGLMatrix(dMtrx); + return SbMatrix(dMtrx[0], dMtrx[1], dMtrx[2], dMtrx[3], // clazy:exclude=rule-of-two-soft + dMtrx[4], dMtrx[5], dMtrx[6], dMtrx[7], + dMtrx[8], dMtrx[9], dMtrx[10], dMtrx[11], + dMtrx[12],dMtrx[13],dMtrx[14], dMtrx[15]); +} + +template <> +inline Base::Matrix4D convertTo(const SbMatrix& vec2) +{ + Base::Matrix4D mat; + for(int i=0;i<4;++i) { + for(int j=0;j<4;++j) + mat[i][j] = vec2[j][i]; + } + return mat; +} } namespace App{ class DocumentObject; } diff --git a/src/Gui/ViewProvider.cpp b/src/Gui/ViewProvider.cpp index 95085b677d..5fbaeb758e 100644 --- a/src/Gui/ViewProvider.cpp +++ b/src/Gui/ViewProvider.cpp @@ -56,6 +56,8 @@ #include "ViewProviderLink.h" #include "ViewProviderPy.h" +#include + FC_LOG_LEVEL_INIT("ViewProvider", true, true) @@ -345,13 +347,7 @@ QIcon ViewProvider::mergeColorfulOverlayIcons (const QIcon & orig) const void ViewProvider::setTransformation(const Base::Matrix4D &rcMatrix) { - double dMtrx[16]; - rcMatrix.getGLMatrix(dMtrx); - - pcTransform->setMatrix(SbMatrix(dMtrx[0], dMtrx[1], dMtrx[2], dMtrx[3], - dMtrx[4], dMtrx[5], dMtrx[6], dMtrx[7], - dMtrx[8], dMtrx[9], dMtrx[10], dMtrx[11], - dMtrx[12],dMtrx[13],dMtrx[14], dMtrx[15])); + pcTransform->setMatrix(convert(rcMatrix)); } void ViewProvider::setTransformation(const SbMatrix &rcMatrix) @@ -361,24 +357,12 @@ void ViewProvider::setTransformation(const SbMatrix &rcMatrix) SbMatrix ViewProvider::convert(const Base::Matrix4D &rcMatrix) { - //NOLINTBEGIN - double dMtrx[16]; - rcMatrix.getGLMatrix(dMtrx); - return SbMatrix(dMtrx[0], dMtrx[1], dMtrx[2], dMtrx[3], // clazy:exclude=rule-of-two-soft - dMtrx[4], dMtrx[5], dMtrx[6], dMtrx[7], - dMtrx[8], dMtrx[9], dMtrx[10], dMtrx[11], - dMtrx[12],dMtrx[13],dMtrx[14], dMtrx[15]); - //NOLINTEND + return Base::convertTo(rcMatrix); } Base::Matrix4D ViewProvider::convert(const SbMatrix &smat) { - Base::Matrix4D mat; - for(int i=0;i<4;++i) { - for(int j=0;j<4;++j) - mat[i][j] = smat[j][i]; - } - return mat; + return Base::convertTo(smat); } void ViewProvider::addDisplayMaskMode(SoNode *node, const char* type) diff --git a/src/Gui/ViewProviderDragger.cpp b/src/Gui/ViewProviderDragger.cpp index 2e20b3f52c..2484ee197e 100644 --- a/src/Gui/ViewProviderDragger.cpp +++ b/src/Gui/ViewProviderDragger.cpp @@ -23,16 +23,19 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include #endif #include #include +#include +#include #include "Gui/ViewParams.h" #include "Application.h" @@ -44,20 +47,27 @@ #include "TaskCSysDragger.h" #include "View3DInventorViewer.h" #include "ViewProviderDragger.h" +#include "Utilities.h" +#include +#include +#include using namespace Gui; PROPERTY_SOURCE(Gui::ViewProviderDragger, Gui::ViewProviderDocumentObject) -ViewProviderDragger::ViewProviderDragger() = default; +ViewProviderDragger::ViewProviderDragger() +{ + ADD_PROPERTY_TYPE(TransformOrigin, ({}), nullptr, App::Prop_Hidden, nullptr); +}; ViewProviderDragger::~ViewProviderDragger() = default; void ViewProviderDragger::updateData(const App::Property* prop) { - if (prop->isDerivedFrom(App::PropertyPlacement::getClassTypeId()) && - strcmp(prop->getName(), "Placement") == 0) { + if (prop->isDerivedFrom(App::PropertyPlacement::getClassTypeId()) + && 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 @@ -73,6 +83,30 @@ void ViewProviderDragger::updateData(const App::Property* prop) ViewProviderDocumentObject::updateData(prop); } +void ViewProviderDragger::setTransformOrigin(const Base::Placement& placement) +{ + TransformOrigin.setValue(placement); +} + +void ViewProviderDragger::resetTransformOrigin() +{ + setTransformOrigin({}); +} + +void ViewProviderDragger::onChanged(const App::Property* property) +{ + if (property == &TransformOrigin) { + updateDraggerPosition(); + } + + ViewProviderDocumentObject::onChanged(property); +} + +TaskView::TaskDialog* ViewProviderDragger::getTransformDialog() +{ + return new TaskCSysDragger(this, csysDragger); +} + bool ViewProviderDragger::doubleClicked() { Gui::Application::Instance->activeDocument()->setEdit(this, (int)ViewProvider::Default); @@ -81,249 +115,228 @@ bool ViewProviderDragger::doubleClicked() void ViewProviderDragger::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { - QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg")); + 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) { - _linkDragger = nullptr; +ViewProvider* ViewProviderDragger::startEditing(int mode) +{ + forwardedViewProvider = nullptr; + auto ret = ViewProviderDocumentObject::startEditing(mode); - if(!ret) + if (!ret) { return ret; - return _linkDragger?_linkDragger:ret; + } + + return forwardedViewProvider ? forwardedViewProvider : ret; } -bool ViewProviderDragger::checkLink() { +bool ViewProviderDragger::forwardToLink() +{ // Trying to detect if the editing request is forwarded by a link object, // usually by doubleClicked(). If so, we route the request back. There shall // be no risk of infinite recursion, as ViewProviderLink handles // ViewProvider::Transform request by itself. - ViewProviderDocumentObject *vpParent = nullptr; + ViewProviderDocumentObject* vpParent = nullptr; std::string subname; + auto doc = Application::Instance->editDocument(); - if(!doc) + if (!doc) { return false; - doc->getInEdit(&vpParent,&subname); - if(!vpParent) + } + + doc->getInEdit(&vpParent, &subname); + if (!vpParent) { return false; - auto sobj = vpParent->getObject()->getSubObject(subname.c_str()); - if(!sobj || sobj==getObject() || sobj->getLinkedObject(true)!=getObject()) + } + + if (vpParent == this) { return false; - auto vp = Application::Instance->getViewProvider(sobj); - if(!vp) + } + + if (!vpParent->isDerivedFrom()) { return false; - _linkDragger = vp->startEditing(ViewProvider::Transform); - if(_linkDragger) - return true; - return false; + } + + forwardedViewProvider = vpParent->startEditing(ViewProvider::Transform); + + return forwardedViewProvider != nullptr; } bool ViewProviderDragger::setEdit(int ModNum) { - Q_UNUSED(ModNum); + Q_UNUSED(ModNum); - if (checkLink()) { - return true; - } - - App::DocumentObject *genericObject = this->getObject(); - - if (genericObject->isDerivedFrom(App::GeoFeature::getClassTypeId())) { - auto geoFeature = static_cast(genericObject); - const Base::Placement &placement = geoFeature->Placement.getValue(); - auto tempTransform = new SoTransform(); - tempTransform->ref(); - updateTransform(placement, tempTransform); + if (forwardToLink()) { + return true; + } assert(!csysDragger); + csysDragger = new SoFCCSysDragger(); - csysDragger->setAxisColors( - Gui::ViewParams::instance()->getAxisXColor(), - Gui::ViewParams::instance()->getAxisYColor(), - Gui::ViewParams::instance()->getAxisZColor() - ); + csysDragger->setAxisColors(Gui::ViewParams::instance()->getAxisXColor(), + Gui::ViewParams::instance()->getAxisYColor(), + Gui::ViewParams::instance()->getAxisZColor()); csysDragger->draggerSize.setValue(ViewParams::instance()->getDraggerScale()); - csysDragger->translation.setValue(tempTransform->translation.getValue()); - csysDragger->rotation.setValue(tempTransform->rotation.getValue()); - - tempTransform->unref(); - - pcTransform->translation.connectFrom(&csysDragger->translation); - pcTransform->rotation.connectFrom(&csysDragger->rotation); + csysDragger->addStartCallback(dragStartCallback, this); csysDragger->addFinishCallback(dragFinishCallback, this); + csysDragger->addMotionCallback(dragMotionCallback, this); - // dragger node is added to viewer's editing root in setEditViewer - // pcRoot->insertChild(csysDragger, 0); - csysDragger->ref(); + Gui::Control().showDialog(getTransformDialog()); - auto task = new TaskCSysDragger(this, csysDragger); - Gui::Control().showDialog(task); - } + updateDraggerPosition(); - return true; + return true; } void ViewProviderDragger::unsetEdit(int ModNum) { - Q_UNUSED(ModNum); + Q_UNUSED(ModNum); - if(csysDragger) - { - pcTransform->translation.disconnect(&csysDragger->translation); - pcTransform->rotation.disconnect(&csysDragger->rotation); + csysDragger.reset(); - // dragger node is added to viewer's editing root in setEditViewer - // pcRoot->removeChild(csysDragger); //should delete csysDragger - csysDragger->unref(); - csysDragger = nullptr; - } - Gui::Control().closeDialog(); + Gui::Control().closeDialog(); } void ViewProviderDragger::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum) { Q_UNUSED(ModNum); - if (csysDragger && viewer) - { - auto rootPickStyle = new SoPickStyle(); - rootPickStyle->style = SoPickStyle::UNPICKABLE; - auto selection = static_cast(viewer->getSceneGraph()); - selection->insertChild(rootPickStyle, 0); - viewer->setSelectionEnabled(false); - csysDragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera()); + if (csysDragger && viewer) { + csysDragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera()); - auto mat = viewer->getDocument()->getEditingTransform(); - viewer->getDocument()->setEditingTransform(mat); - auto feat = dynamic_cast(getObject()); - if(feat) { - auto matInverse = feat->Placement.getValue().toMatrix(); - matInverse.inverse(); - mat *= matInverse; - } - viewer->setupEditingRoot(csysDragger,&mat); + auto originPlacement = App::GeoFeature::getGlobalPlacement(getObject()) * getObjectPlacement().inverse(); + auto mat = originPlacement.toMatrix(); + + viewer->getDocument()->setEditingTransform(mat); + viewer->setupEditingRoot(csysDragger, &mat); } } void ViewProviderDragger::unsetEditViewer(Gui::View3DInventorViewer* viewer) -{ - auto selection = static_cast(viewer->getSceneGraph()); - SoNode *child = selection->getChild(0); - if (child && child->isOfType(SoPickStyle::getClassTypeId())) { - selection->removeChild(child); - viewer->setSelectionEnabled(true); - } -} +{} -void ViewProviderDragger::dragFinishCallback(void *data, SoDragger *d) +void ViewProviderDragger::dragStartCallback(void* data, [[maybe_unused]] SoDragger* d) { // This is called when a manipulator has done manipulating + auto vp = static_cast(data); - auto sudoThis = static_cast(data); - auto dragger = static_cast(d); - updatePlacementFromDragger(sudoThis, dragger); - - //Gui::Application::Instance->activeDocument()->commitCommand(); + vp->draggerPlacement = vp->getDraggerPlacement(); + vp->csysDragger->clearIncrementCounts(); } -void ViewProviderDragger::updatePlacementFromDragger(ViewProviderDragger* sudoThis, SoFCCSysDragger* draggerIn) +void ViewProviderDragger::dragFinishCallback(void* data, SoDragger* d) { - App::DocumentObject *genericObject = sudoThis->getObject(); - if (!genericObject->isDerivedFrom(App::GeoFeature::getClassTypeId())) - return; - auto geoFeature = static_cast(genericObject); - Base::Placement originalPlacement = geoFeature->Placement.getValue(); - double pMatrix[16]; - originalPlacement.toMatrix().getMatrix(pMatrix); - Base::Placement freshPlacement = originalPlacement; + // This is called when a manipulator has done manipulating + auto vp = static_cast(data); - //local cache for brevity. - double translationIncrement = draggerIn->translationIncrement.getValue(); - double rotationIncrement = draggerIn->rotationIncrement.getValue(); - int tCountX = draggerIn->translationIncrementCountX.getValue(); - int tCountY = draggerIn->translationIncrementCountY.getValue(); - int tCountZ = draggerIn->translationIncrementCountZ.getValue(); - int rCountX = draggerIn->rotationIncrementCountX.getValue(); - int rCountY = draggerIn->rotationIncrementCountY.getValue(); - int rCountZ = draggerIn->rotationIncrementCountZ.getValue(); + vp->draggerPlacement = vp->getDraggerPlacement(); + vp->csysDragger->clearIncrementCounts(); - //just as a little sanity check make sure only 1 or 2 fields has changed. - int numberOfFieldChanged = 0; - if (tCountX) numberOfFieldChanged++; - if (tCountY) numberOfFieldChanged++; - if (tCountZ) numberOfFieldChanged++; - if (rCountX) numberOfFieldChanged++; - if (rCountY) numberOfFieldChanged++; - if (rCountZ) numberOfFieldChanged++; - if (numberOfFieldChanged == 0) - return; - assert(numberOfFieldChanged == 1 || numberOfFieldChanged == 2); + vp->updatePlacementFromDragger(); +} - //helper lambdas. - auto getVectorX = [&pMatrix]() {return Base::Vector3d(pMatrix[0], pMatrix[4], pMatrix[8]);}; - auto getVectorY = [&pMatrix]() {return Base::Vector3d(pMatrix[1], pMatrix[5], pMatrix[9]);}; - auto getVectorZ = [&pMatrix]() {return Base::Vector3d(pMatrix[2], pMatrix[6], pMatrix[10]);}; +void ViewProviderDragger::dragMotionCallback(void* data, SoDragger* d) +{ + auto vp = static_cast(data); - if (tCountX) - { - Base::Vector3d movementVector(getVectorX()); - movementVector *= (tCountX * translationIncrement); - freshPlacement.move(movementVector); - geoFeature->Placement.setValue(freshPlacement); - } - if (tCountY) - { - Base::Vector3d movementVector(getVectorY()); - movementVector *= (tCountY * translationIncrement); - freshPlacement.move(movementVector); - geoFeature->Placement.setValue(freshPlacement); - } - if (tCountZ) - { - Base::Vector3d movementVector(getVectorZ()); - movementVector *= (tCountZ * translationIncrement); - freshPlacement.move(movementVector); - geoFeature->Placement.setValue(freshPlacement); - } - if (rCountX) - { - Base::Vector3d rotationVector(getVectorX()); - Base::Rotation rotation(rotationVector, rCountX * rotationIncrement); - freshPlacement.setRotation(rotation * freshPlacement.getRotation()); - geoFeature->Placement.setValue(freshPlacement); - } - if (rCountY) - { - Base::Vector3d rotationVector(getVectorY()); - Base::Rotation rotation(rotationVector, rCountY * rotationIncrement); - freshPlacement.setRotation(rotation * freshPlacement.getRotation()); - geoFeature->Placement.setValue(freshPlacement); - } - if (rCountZ) - { - Base::Vector3d rotationVector(getVectorZ()); - Base::Rotation rotation(rotationVector, rCountZ * rotationIncrement); - freshPlacement.setRotation(rotation * freshPlacement.getRotation()); - geoFeature->Placement.setValue(freshPlacement); - } + vp->updateTransformFromDragger(); +} - draggerIn->clearIncrementCounts(); +void ViewProviderDragger::updatePlacementFromDragger() +{ + const auto placement = getObject()->getPropertyByName("Placement"); + + if (!placement) { + return; + } + + placement->setValue(getDraggerPlacement() * getTransformOrigin().inverse()); +} + +void ViewProviderDragger::updateTransformFromDragger() +{ + const auto placement = getDraggerPlacement() * getTransformOrigin().inverse(); + + pcTransform->translation.setValue(Base::convertTo(placement.getPosition())); + pcTransform->rotation.setValue(Base::convertTo(placement.getRotation())); +} + +Base::Placement ViewProviderDragger::getObjectPlacement() const +{ + if (auto placement = getObject()->getPropertyByName("Placement")) { + return placement->getValue(); + } + + return {}; +} + +Base::Placement ViewProviderDragger::getDraggerPlacement() const +{ + const double translationStep = csysDragger->translationIncrement.getValue(); + const int xSteps = csysDragger->translationIncrementCountX.getValue(); + const int ySteps = csysDragger->translationIncrementCountY.getValue(); + const int zSteps = csysDragger->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 = csysDragger->rotationIncrement.getValue(); + const int xRotationSteps = csysDragger->rotationIncrementCountX.getValue(); + const int yRotationSteps = csysDragger->rotationIncrementCountY.getValue(); + const int zRotationSteps = csysDragger->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) +{ + csysDragger->translation.setValue(Base::convertTo(placement.getPosition())); + csysDragger->rotation.setValue(Base::convertTo(placement.getRotation())); + + draggerPlacement = placement; + csysDragger->clearIncrementCounts(); +} + +void ViewProviderDragger::updateDraggerPosition() +{ + if (!csysDragger) { + return; + } + + auto placement = getObjectPlacement() * getTransformOrigin(); + + setDraggerPlacement(placement); } void ViewProviderDragger::updateTransform(const Base::Placement& from, SoTransform* to) { - auto q0 = (float)from.getRotation().getValue()[0]; - auto q1 = (float)from.getRotation().getValue()[1]; - auto q2 = (float)from.getRotation().getValue()[2]; - auto q3 = (float)from.getRotation().getValue()[3]; - auto px = (float)from.getPosition().x; - auto py = (float)from.getPosition().y; - auto pz = (float)from.getPosition().z; - to->rotation.setValue(q0,q1,q2,q3); - to->translation.setValue(px,py,pz); - to->center.setValue(0.0f,0.0f,0.0f); - to->scaleFactor.setValue(1.0f,1.0f,1.0f); + to->rotation.setValue(Base::convertTo(from.getRotation())); + to->translation.setValue(Base::convertTo(from.getPosition())); + to->center.setValue(0.0f, 0.0f, 0.0f); + to->scaleFactor.setValue(1.0f, 1.0f, 1.0f); } diff --git a/src/Gui/ViewProviderDragger.h b/src/Gui/ViewProviderDragger.h index 40502f016a..21808413a7 100644 --- a/src/Gui/ViewProviderDragger.h +++ b/src/Gui/ViewProviderDragger.h @@ -25,16 +25,20 @@ #define GUI_VIEWPROVIDER_DRAGGER_H #include "ViewProviderDocumentObject.h" +#include "SoFCCSysDragger.h" +#include +#include class SoDragger; class SoTransform; -namespace Base { class Placement;} - namespace Gui { +namespace TaskView { + class TaskDialog; +} + class View3DInventorViewer; -class SoFCCSysDragger; /** * The base class for all view providers modifying the placement @@ -52,6 +56,19 @@ public: /// destructor. ~ViewProviderDragger() override; + /// Origin used when object is transformed. It temporarily changes the origin of object. + /// Dragger is normally placed at the transform origin, unless explicitly overridden via + /// ViewProviderDragger#setDraggerPlacement() method. + App::PropertyPlacement TransformOrigin; + + /// Convenience method to obtain the transform origin + Base::Placement getTransformOrigin() const { return TransformOrigin.getValue(); } + /// Convenience method to set the transform origin + void setTransformOrigin(const Base::Placement& placement); + /// Resets transform origin to the object origin + void resetTransformOrigin(); + +public: /** @name Edit methods */ //@{ bool doubleClicked() override; @@ -63,21 +80,48 @@ 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(); + /// updates transform of object based on dragger position, can be used to preview movement + void updateTransformFromDragger(); + + /// Gets object placement relative to its coordinate system + Base::Placement getObjectPlacement() const; + /// Gets current dragger placement, including current dragger movement + Base::Placement getDraggerPlacement() const; + /// Gets original dragger placement, without current dragger movement + Base::Placement getOriginalDraggerPlacement() const; + /// Sets placement of dragger relative to objects origin + void setDraggerPlacement(const Base::Placement& placement); + protected: bool setEdit(int ModNum) override; void unsetEdit(int ModNum) override; void setEditViewer(View3DInventorViewer*, int ModNum) override; void unsetEditViewer(View3DInventorViewer*) override; //@} - SoFCCSysDragger *csysDragger = nullptr; + + void onChanged(const App::Property* prop) override; + + bool forwardToLink(); + + /** + * Returns a newly create dialog for the part to be placed in the task view + * Must be reimplemented in subclasses. + */ + virtual TaskView::TaskDialog* getTransformDialog(); + + CoinPtr csysDragger = nullptr; + ViewProvider *forwardedViewProvider = nullptr; private: - static void dragFinishCallback(void * data, SoDragger * d); - static void updatePlacementFromDragger(ViewProviderDragger *sudoThis, SoFCCSysDragger *draggerIn); + static void dragStartCallback(void *data, SoDragger *d); + static void dragFinishCallback(void *data, SoDragger *d); + static void dragMotionCallback(void *data, SoDragger *d); - bool checkLink(); + void updateDraggerPosition(); - ViewProvider *_linkDragger = nullptr; + Base::Placement draggerPlacement { }; }; } // namespace Gui diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp index a2cad7a67f..3e0d0b5244 100644 --- a/src/Gui/ViewProviderLink.cpp +++ b/src/Gui/ViewProviderLink.cpp @@ -1624,7 +1624,7 @@ static const char *_LinkElementIcon = "LinkElement"; ViewProviderLink::ViewProviderLink() :linkType(LinkTypeNone),hasSubName(false),hasSubElement(false) - ,useCenterballDragger(false),childVp(nullptr),overlayCacheKey(0) + ,childVp(nullptr),overlayCacheKey(0) { sPixmap = _LinkIcon; @@ -1670,7 +1670,7 @@ ViewProviderLink::~ViewProviderLink() } bool ViewProviderLink::isSelectable() const { - return !pcDragger && Selectable.getValue(); + return Selectable.getValue(); } void ViewProviderLink::attach(App::DocumentObject *pcObj) { @@ -1851,7 +1851,7 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App: auto propLinkPlacement = ext->getLinkPlacementProperty(); if(!propLinkPlacement || propLinkPlacement == prop) { const auto &pla = static_cast(prop)->getValue(); - ViewProviderGeometryObject::updateTransform(pla, pcTransform); + // ViewProviderGeometryObject::updateTransform(pla, pcTransform); const auto &v = ext->getScaleVector(); if(canScale(v)) pcTransform->scaleFactor.setValue(v.x,v.y,v.z); @@ -2764,25 +2764,28 @@ ViewProvider *ViewProviderLink::startEditing(int mode) { } static thread_local bool _pendingTransform; - static thread_local Base::Matrix4D _editingTransform; + static thread_local Matrix4D _editingTransform; auto doc = Application::Instance->editDocument(); - if(mode==ViewProvider::Transform) { - if(_pendingTransform && doc) + if (mode == ViewProvider::Transform) { + if (_pendingTransform && doc) { doc->setEditingTransform(_editingTransform); + } - if(!initDraggingPlacement()) + if (!initDraggingPlacement()) { return nullptr; - if(useCenterballDragger) - pcDragger = CoinPtr(new SoCenterballDragger); - else - pcDragger = CoinPtr(new SoFCCSysDragger); - updateDraggingPlacement(dragCtx->initialPlacement,true); - pcDragger->addStartCallback(dragStartCallback, this); - pcDragger->addFinishCallback(dragFinishCallback, this); - pcDragger->addMotionCallback(dragMotionCallback, this); - return inherited::startEditing(mode); + } + + if (auto result = inherited::startEditing(mode)) { + csysDragger->addStartCallback(dragStartCallback, this); + csysDragger->addFinishCallback(dragFinishCallback, this); + csysDragger->addMotionCallback(dragMotionCallback, this); + + setDraggerPlacement(dragCtx->initialPlacement); + + return result; + } } if(!linkEdit()) { @@ -2839,6 +2842,7 @@ bool ViewProviderLink::setEdit(int ModNum) Selection().clearSelection(); return true; } + return inherited::setEdit(ModNum); } @@ -2849,125 +2853,21 @@ void ViewProviderLink::setEditViewer(Gui::View3DInventorViewer* viewer, int ModN return; } - if (pcDragger && viewer) - { - auto rootPickStyle = new SoPickStyle(); - rootPickStyle->style = SoPickStyle::UNPICKABLE; - static_cast( - viewer->getSceneGraph())->insertChild(rootPickStyle, 0); - - if(useCenterballDragger) { - auto dragger = static_cast(pcDragger.get()); - auto group = new SoAnnotation; - auto pickStyle = new SoPickStyle; - pickStyle->setOverride(true); - group->addChild(pickStyle); - group->addChild(pcDragger); - - // Because the dragger is not grouped with the actual geometry, - // we use an invisible cube sized by the bounding box obtained from - // initDraggingPlacement() to scale the centerball dragger properly - - auto * ss = static_cast(dragger->getPart("surroundScale", TRUE)); - ss->numNodesUpToContainer = 3; - ss->numNodesUpToReset = 2; - - auto *geoGroup = new SoGroup; - group->addChild(geoGroup); - auto *style = new SoDrawStyle; - style->style.setValue(SoDrawStyle::INVISIBLE); - style->setOverride(TRUE); - geoGroup->addChild(style); - auto *cube = new SoCube; - geoGroup->addChild(cube); - auto length = std::max(std::max(dragCtx->bbox.LengthX(), - dragCtx->bbox.LengthY()), dragCtx->bbox.LengthZ()); - cube->width = length; - cube->height = length; - cube->depth = length; - - viewer->setupEditingRoot(group,&dragCtx->preTransform); - } else { - auto dragger = static_cast(pcDragger.get()); - dragger->draggerSize.setValue(ViewParams::instance()->getDraggerScale()); - dragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera()); - viewer->setupEditingRoot(pcDragger,&dragCtx->preTransform); - - auto task = new TaskCSysDragger(this, dragger); - Gui::Control().showDialog(task); - } - } + ViewProviderDragger::setEditViewer(viewer, ModNum); } void ViewProviderLink::unsetEditViewer(Gui::View3DInventorViewer* viewer) { - SoNode *child = static_cast(viewer->getSceneGraph())->getChild(0); - if (child && child->isOfType(SoPickStyle::getClassTypeId())) - static_cast(viewer->getSceneGraph())->removeChild(child); - pcDragger.reset(); dragCtx.reset(); - Gui::Control().closeDialog(); + + inherited::unsetEditViewer(viewer); } -Base::Placement ViewProviderLink::currentDraggingPlacement() const -{ - // if there isn't an active dragger return a default placement - if (!pcDragger) - return Base::Placement(); - - SbVec3f v; - SbRotation r; - if (useCenterballDragger) { - auto dragger = static_cast(pcDragger.get()); - v = dragger->center.getValue(); - r = dragger->rotation.getValue(); - } - else { - auto dragger = static_cast(pcDragger.get()); - v = dragger->translation.getValue(); - r = dragger->rotation.getValue(); - } - - float q1,q2,q3,q4; - r.getValue(q1,q2,q3,q4); - return Base::Placement(Base::Vector3d(v[0],v[1],v[2]),Base::Rotation(q1,q2,q3,q4)); -} - -void ViewProviderLink::enableCenterballDragger(bool enable) { - if(enable == useCenterballDragger) - return; - if(pcDragger) - LINK_THROW(Base::RuntimeError,"Cannot change dragger during dragging"); - useCenterballDragger = enable; -} - -void ViewProviderLink::updateDraggingPlacement(const Base::Placement &pla, bool force) { - if(pcDragger && (force || currentDraggingPlacement()!=pla)) { - const auto &pos = pla.getPosition(); - const auto &rot = pla.getRotation(); - FC_LOG("updating dragger placement (" << pos.x << ", " << pos.y << ", " << pos.z << ')'); - if(useCenterballDragger) { - auto dragger = static_cast(pcDragger.get()); - SbBool wasenabled = dragger->enableValueChangedCallbacks(FALSE); - SbMatrix matrix; - matrix = convert(pla.toMatrix()); - dragger->center.setValue(SbVec3f(0,0,0)); - dragger->setMotionMatrix(matrix); - if (wasenabled) { - dragger->enableValueChangedCallbacks(TRUE); - dragger->valueChanged(); - } - }else{ - auto dragger = static_cast(pcDragger.get()); - dragger->translation.setValue(SbVec3f(pos.x,pos.y,pos.z)); - dragger->rotation.setValue(rot[0],rot[1],rot[2],rot[3]); - } - } -} - -bool ViewProviderLink::callDraggerProxy(const char *fname, bool update) { - if(!pcDragger) +bool ViewProviderLink::callDraggerProxy(const char* fname) { + if (!csysDragger) { return false; + } + Base::PyGILStateLocker lock; try { auto* proxy = getPropertyByName("Proxy"); @@ -2986,48 +2886,32 @@ bool ViewProviderLink::callDraggerProxy(const char *fname, bool update) { return true; } - if(update) { - auto ext = getLinkExtension(); - if(ext) { - const auto &pla = currentDraggingPlacement(); - auto prop = ext->getLinkPlacementProperty(); - if(!prop) - prop = ext->getPlacementProperty(); - if(prop) { - auto plaNew = pla * Base::Placement(dragCtx->mat); - if(prop->getValue()!=plaNew) - prop->setValue(plaNew); - } - updateDraggingPlacement(pla); - } - } return false; } void ViewProviderLink::dragStartCallback(void *data, SoDragger *) { auto me = static_cast(data); - me->dragCtx->initialPlacement = me->currentDraggingPlacement(); - if(!me->callDraggerProxy("onDragStart",false)) { - me->dragCtx->cmdPending = true; - me->getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Link Transform")); - }else - me->dragCtx->cmdPending = false; + + me->dragCtx->initialPlacement = me->getDraggerPlacement(); + me->callDraggerProxy("onDragStart"); } void ViewProviderLink::dragFinishCallback(void *data, SoDragger *) { auto me = static_cast(data); - me->callDraggerProxy("onDragEnd",true); - if(me->dragCtx->cmdPending) { - if(me->currentDraggingPlacement() == me->dragCtx->initialPlacement) + me->callDraggerProxy("onDragEnd"); + + if (me->dragCtx->cmdPending) { + if (me->getDraggerPlacement() == me->dragCtx->initialPlacement) { me->getDocument()->abortCommand(); - else + } else { me->getDocument()->commitCommand(); + } } } void ViewProviderLink::dragMotionCallback(void *data, SoDragger *) { auto me = static_cast(data); - me->callDraggerProxy("onDragMotion",true); + me->callDraggerProxy("onDragMotion"); } void ViewProviderLink::updateLinks(ViewProvider *vp) { diff --git a/src/Gui/ViewProviderLink.h b/src/Gui/ViewProviderLink.h index fc296f2561..79ead6d8cf 100644 --- a/src/Gui/ViewProviderLink.h +++ b/src/Gui/ViewProviderLink.h @@ -184,10 +184,10 @@ protected: Py::Object PythonObject; }; -class GuiExport ViewProviderLink : public ViewProviderDocumentObject +class GuiExport ViewProviderLink : public ViewProviderDragger { PROPERTY_HEADER_WITH_OVERRIDE(Gui::ViewProviderLink); - using inherited = ViewProviderDocumentObject; + using inherited = ViewProviderDragger; public: App::PropertyBool OverrideMaterial; @@ -248,11 +248,6 @@ public: static void updateLinks(ViewProvider *vp); - void updateDraggingPlacement(const Base::Placement &pla, bool force=false); - Base::Placement currentDraggingPlacement() const; - void enableCenterballDragger(bool enable); - bool isUsingCenterballDragger() const { return useCenterballDragger; } - std::map getElementColors(const char *subname=nullptr) const override; void setElementColors(const std::map &colors) override; @@ -311,7 +306,7 @@ protected: ViewProvider *getLinkedView(bool real,const App::LinkBaseExtension *ext=nullptr) const; bool initDraggingPlacement(); - bool callDraggerProxy(const char *fname, bool update); + bool callDraggerProxy(const char* fname); private: static void dragStartCallback(void * data, SoDragger * d); @@ -323,7 +318,6 @@ protected: LinkType linkType; bool hasSubName; bool hasSubElement; - bool useCenterballDragger; struct DraggerContext{ Base::Matrix4D preTransform; @@ -333,7 +327,6 @@ protected: bool cmdPending; }; std::unique_ptr dragCtx; - CoinPtr pcDragger; ViewProviderDocumentObject *childVp; LinkInfoPtr childVpLink; mutable qint64 overlayCacheKey; diff --git a/src/Gui/ViewProviderLinkPy.xml b/src/Gui/ViewProviderLinkPy.xml index 88aaa0747f..09641c2cf8 100644 --- a/src/Gui/ViewProviderLinkPy.xml +++ b/src/Gui/ViewProviderLinkPy.xml @@ -19,12 +19,6 @@ - - - Get/set dragger type - - - Get the associated LinkView object diff --git a/src/Gui/ViewProviderLinkPyImp.cpp b/src/Gui/ViewProviderLinkPyImp.cpp index c1f3588e71..066c87f2e9 100644 --- a/src/Gui/ViewProviderLinkPyImp.cpp +++ b/src/Gui/ViewProviderLinkPyImp.cpp @@ -46,29 +46,16 @@ std::string ViewProviderLinkPy::representation() const Py::Object ViewProviderLinkPy::getDraggingPlacement() const { return Py::asObject(new Base::PlacementPy(new Base::Placement( - getViewProviderLinkPtr()->currentDraggingPlacement()))); + getViewProviderLinkPtr()->getDraggerPlacement()))); } void ViewProviderLinkPy::setDraggingPlacement(Py::Object arg) { if(!PyObject_TypeCheck(arg.ptr(),&Base::PlacementPy::Type)) throw Py::TypeError("expects a placement"); - getViewProviderLinkPtr()->updateDraggingPlacement( + getViewProviderLinkPtr()->setDraggerPlacement( *static_cast(arg.ptr())->getPlacementPtr()); } -Py::Boolean ViewProviderLinkPy::getUseCenterballDragger() const { - return {getViewProviderLinkPtr()->isUsingCenterballDragger()}; -} - -void ViewProviderLinkPy::setUseCenterballDragger(Py::Boolean arg) { - try { - getViewProviderLinkPtr()->enableCenterballDragger(arg); - }catch(const Base::Exception &e){ - e.setPyException(); - throw Py::Exception(); - } -} - Py::Object ViewProviderLinkPy::getLinkView() const { return Py::Object(getViewProviderLinkPtr()->getPyLinkView(),true); } diff --git a/src/Mod/Part/App/AppPart.cpp b/src/Mod/Part/App/AppPart.cpp index c244d033a5..7668f6d9dd 100644 --- a/src/Mod/Part/App/AppPart.cpp +++ b/src/Mod/Part/App/AppPart.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include "ArcOfCirclePy.h" @@ -187,8 +188,12 @@ #include #include "MeasureClient.h" + #include +#include +#include + namespace Part { extern PyObject* initModule(); } @@ -572,7 +577,10 @@ PyMOD_INIT_FUNC(Part) .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/Part/Boolean"); Part::FuzzyHelper::setBooleanFuzzy(hGrp->GetFloat("BooleanFuzzy",10.0)); - + + Base::registerServiceImplementation(new AttacherSubObjectPlacement); + Base::registerServiceImplementation(new PartCenterOfMass); + PyMOD_Return(partModule); } // clang-format on diff --git a/src/Mod/Part/App/Attacher.cpp b/src/Mod/Part/App/Attacher.cpp index f3800133e6..5009ce260b 100644 --- a/src/Mod/Part/App/Attacher.cpp +++ b/src/Mod/Part/App/Attacher.cpp @@ -69,6 +69,8 @@ #include "AttachExtension.h" #include "Tools.h" +#include + using namespace Part; using namespace Attacher; @@ -136,6 +138,7 @@ const char* AttachEngine::eMapModeStrings[]= { "OYX", "ParallelPlane", + "MidPoint", nullptr}; @@ -388,8 +391,8 @@ void AttachEngine::suggestMapModes(SuggestResult &result) const result.message = SuggestResult::srLinkBroken; result.bestFitMode = mmDeactivated; - std::vector shapes; - std::vector shapeStorage; + std::vector shapes; + std::vector shapeStorage; std::vector typeStr; try{ readLinks(getRefObjects(),subnames, shapes, shapeStorage, typeStr); @@ -578,8 +581,8 @@ eRefType AttachEngine::getShapeType(const App::DocumentObject *obj, const std::s //const_cast is worth here, to keep obj argument const. We are not going to write anything to obj through this temporary link. tmpLink.setValue(const_cast(obj), subshape.c_str()); - std::vector shapes; - std::vector copiedShapeStorage; + std::vector shapes; + std::vector copiedShapeStorage; std::vector types; readLinks(tmpLink.getValues(), tmpLink.getSubValues(), shapes, copiedShapeStorage, types); @@ -740,13 +743,14 @@ eRefType AttachEngine::getRefTypeByName(const std::string& typeName) throw AttachEngineException(errmsg.str()); } -GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector &shapes) +GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector &shapes) { //explode compounds TopTools_HSequenceOfShape totalSeq; - for (const TopoDS_Shape* pSh : shapes) { + for (auto tSh : shapes) { + auto pSh = tSh->getShape(); ShapeExtend_Explorer xp; - totalSeq.Append( xp.SeqFromCompound(*pSh, /*recursive=*/true)); + totalSeq.Append( xp.SeqFromCompound(pSh, /*recursive=*/true)); } if (totalSeq.Length() == 0) throw AttachEngineException("AttachEngine::getInertialPropsOfShape: no geometry provided"); @@ -820,75 +824,96 @@ GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector& objs, const std::vector &subs, - std::vector &shapes, - std::vector &storage, + std::vector &shapes, + std::vector &storage, std::vector &types) { storage.reserve(objs.size()); shapes.resize(objs.size()); types.resize(objs.size()); + for (std::size_t i = 0; i < objs.size(); i++) { - auto* geof = dynamic_cast(objs[i]); + auto geof = extractGeoFeature(objs[i]); if (!geof) { - // Accept App::Links to GeoFeatures - geof = dynamic_cast(objs[i]->getLinkedObject()); - if (!geof) { - FC_THROWM(AttachEngineException, - "AttachEngine3D: attached to a non App::GeoFeature '" << objs[i]->getNameInDocument() << "'"); - } - } - TopoDS_Shape myShape; - - try { - // getTopoShape support fully qualified subnames and should return shape with correct - // global placement. - Part::TopoShape shape = Part::Feature::getTopoShape(objs[i], subs[i].c_str(), true); - for (;;) { - if (shape.isNull()) { - FC_THROWM(AttachEngineException, - "AttachEngine3D: subshape not found " - << objs[i]->getNameInDocument() << '.' << subs[i]); - } - if (shape.shapeType() != TopAbs_COMPOUND - || shape.countSubShapes(TopAbs_SHAPE) != 1) { - break; - } - // auto extract the single sub-shape from a compound - shape = shape.getSubTopoShape(TopAbs_SHAPE, 1); - } - - myShape = shape.getShape(); - } - catch (Standard_Failure& e) { FC_THROWM(AttachEngineException, - "AttachEngine3D: subshape not found " << objs[i]->getNameInDocument() - << '.' << subs[i] << std::endl - << e.GetMessageString()); - } - catch (Base::CADKernelError& e) { - FC_THROWM(AttachEngineException, - "AttachEngine3D: subshape not found " << objs[i]->getNameInDocument() - << '.' << subs[i] << std::endl - << e.what()); - } - if (myShape.IsNull()) { - FC_THROWM(AttachEngineException, - "AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.' - << subs[i]); + "AttachEngine3D: attached to a non App::GeoFeature '" + << objs[i]->getNameInDocument() << "'"); } - storage.emplace_back(myShape); + auto shape = extractSubShape(objs[i], subs[i]); + if (shape.isNull()) { + FC_THROWM(AttachEngineException, + "AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.' + << subs[i]); + } + + storage.emplace_back(shape); shapes[i] = &(storage.back()); // FIXME: unpack single-child compounds here? Compounds are not used so far, so it should be // considered later, when the need arises. - types[i] = getShapeType(*(shapes[i])); + types[i] = getShapeType(shapes[i]->getShape()); + if (subs[i].length() == 0) { types[i] = eRefType(types[i] | rtFlagHasPlacement); } } } +App::GeoFeature* AttachEngine::extractGeoFeature(App::DocumentObject *obj) +{ + if (auto geof = dynamic_cast(obj)) { + return geof; + } + + auto linkedObject = obj->getLinkedObject(); + if (auto linkedGeof = dynamic_cast(linkedObject)) { + return linkedGeof; + } + + return nullptr; +} + +TopoShape AttachEngine::extractSubShape(App::DocumentObject* obj, const std::string& subname) +{ + TopoShape shape; + + try { + // getTopoShape support fully qualified subnames and should return shape with correct + // global placement. + shape = Feature::getTopoShape(obj, subname.c_str(), true); + + for (;;) { + if (shape.isNull()) { + FC_THROWM(AttachEngineException, + "AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.' + << subname); + } + + if (shape.shapeType() != TopAbs_COMPOUND || shape.countSubShapes(TopAbs_SHAPE) != 1) { + break; + } + + // auto extract the single sub-shape from a compound + shape = shape.getSubTopoShape(TopAbs_SHAPE, 1); + } + } + catch (Standard_Failure& e) { + FC_THROWM(AttachEngineException, + "AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.' + << subname << std::endl + << e.GetMessageString()); + } + catch (Base::CADKernelError& e) { + FC_THROWM(AttachEngineException, + "AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.' + << subname << std::endl + << e.what()); + } + + return shape; +} + void AttachEngine::throwWrongMode(eMapMode mmode) { std::stringstream errmsg; @@ -1140,8 +1165,8 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vector shapes; - std::vector copiedShapeStorage; + std::vector shapes; + std::vector copiedShapeStorage; std::vector types; readLinks(objs, subs, shapes, copiedShapeStorage, types); @@ -1172,7 +1197,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorgetShape(); if (sh.IsNull()) { throw Base::ValueError( "Null shape in AttachEngine3D::calculateAttachedPlacement()!"); @@ -1207,7 +1232,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vector 0) { - const TopoDS_Edge& e = TopoDS::Edge(*shapes[0]); + const TopoDS_Edge& e = TopoDS::Edge(shapes[0]->getShape()); BRepAdaptor_Curve adapt(e); gp_Ax3 pos; switch (adapt.GetType()) { @@ -1259,7 +1284,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorgetShape()); } catch (...) { } @@ -1330,7 +1355,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorgetShape()); } catch (...) { } @@ -1387,14 +1412,14 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorShapeType() == TopAbs_VERTEX) { + if (shapes[0]->shapeType() == TopAbs_VERTEX) { std::swap(shapes[0], shapes[1]); bThruVertex = true; } TopoDS_Face face; try { - face = TopoDS::Face(*(shapes[0])); + face = TopoDS::Face(shapes[0]->getShape()); } catch (...) { } @@ -1405,7 +1430,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorgetShape()); } catch (...) { } @@ -1472,14 +1497,14 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorShapeType() == TopAbs_VERTEX && shapes.size() >= 2) { + if (shapes[0]->shapeType() == TopAbs_VERTEX && shapes.size() >= 2) { std::swap(shapes[0], shapes[1]); bThruVertex = true; } TopoDS_Edge path; try { - path = TopoDS::Edge(*(shapes[0])); + path = TopoDS::Edge(shapes[0]->getShape()); } catch (...) { } @@ -1506,7 +1531,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vector= 2) { TopoDS_Vertex vertex; try { - vertex = TopoDS::Vertex(*(shapes[1])); + vertex = TopoDS::Vertex(shapes[1]->getShape()); } catch (...) { } @@ -1633,7 +1658,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vector points; for (const auto & shape : shapes) { - const TopoDS_Shape &sh = *shape; + const TopoDS_Shape &sh = shape->getShape(); if (sh.IsNull()) { throw Base::ValueError( "Null shape in AttachEngine3D::calculateAttachedPlacement()!"); @@ -1725,7 +1750,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorgetShape()); } catch (...) { } @@ -1845,28 +1870,28 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorIsNull()) { + if (shapes[0]->isNull()) { THROWM(Base::TypeError, "AttachEngine3D::calculateAttachedPlacement: null shape!") } - if (shapes[0]->ShapeType() != TopAbs_VERTEX) { + if (shapes[0]->shapeType() != TopAbs_VERTEX) { THROWM(Base::TypeError, "AttachEngine3D::calculateAttachedPlacement: first reference must be a " "vertex, it's not") } - SketchBasePoint = BRep_Tool::Pnt(TopoDS::Vertex(*(shapes[0]))); + SketchBasePoint = BRep_Tool::Pnt(TopoDS::Vertex(shapes[0]->getShape())); // read out axes directions for (size_t i = 1; i < 3 && i < shapes.size(); ++i) { - if (shapes[i]->IsNull()) { + if (shapes[i]->isNull()) { THROWM(Base::TypeError, "AttachEngine3D::calculateAttachedPlacement: null shape!") } - if (shapes[i]->ShapeType() == TopAbs_VERTEX) { - gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(*(shapes[i]))); + if (shapes[i]->shapeType() == TopAbs_VERTEX) { + gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(shapes[i]->getShape())); dirs[order[i - 1]] = gp_Vec(SketchBasePoint, p); } - else if (shapes[i]->ShapeType() == TopAbs_EDGE) { - const TopoDS_Edge& e = TopoDS::Edge(*(shapes[i])); + else if (shapes[i]->shapeType() == TopAbs_EDGE) { + const TopoDS_Edge& e = TopoDS::Edge(shapes[i]->getShape()); BRepAdaptor_Curve crv(e); double u1 = crv.FirstParameter(); double u2 = crv.LastParameter(); @@ -1896,6 +1921,90 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorattachmentOffset; return plm; } break; + case mmMidpoint: { + Base::Placement placement; + + // special case for planes + if (auto plane = dynamic_cast(objs[0])) { + return plane->Placement.getValue() * attachmentOffset; + } + + auto shape = shapes.front(); + auto geom = Geometry::fromShape(shape->getShape()); + + switch (shape->shapeType()) { + case TopAbs_VERTEX: { + if (auto point = dynamic_cast(geom.get())) { + placement.setPosition(point->getPoint()); + } + } + break; + + case TopAbs_EDGE: { + if (auto conic = dynamic_cast(geom.get())) { + placement.setPosition(conic->getLocation()); + placement.setRotation(conic->getRotation().value_or(Base::Rotation {})); + } else if (auto line = dynamic_cast(geom.get())) { + auto u1 = line->getFirstParameter(); + auto u2 = line->getLastParameter(); + + auto middle = (u1 + u2) / 2; + + placement.setPosition(line->pointAtParameter(middle)); + + Base::Vector3d direction; + if (!line->normalAt(middle, direction)) { + line->tangent(middle, direction); + } + + placement.setRotation(Base::Rotation::fromNormalVector(direction)); + } + } + break; + + case TopAbs_FACE: { + auto surface = dynamic_cast(geom.get()); + + if (auto sphere = dynamic_cast(geom.get())) { + placement.setPosition(sphere->getLocation()); + } else if (auto cone = dynamic_cast(geom.get())) { + placement.setPosition(cone->getApex()); + } else if (auto com = shape->centerOfGravity()) { + placement.setPosition(*com); + } else { + placement.setPosition(shape->getBoundBox().GetCenter()); + } + + if (auto rotation = surface->getRotation()) { + placement.setRotation(*rotation); + } else { + auto adaptorSurface = BRepAdaptor_Surface(TopoDS::Face(shape->getShape()), true); + + auto u1 = adaptorSurface.FirstUParameter(); + auto u2 = adaptorSurface.LastUParameter(); + auto v1 = adaptorSurface.FirstVParameter(); + auto v2 = adaptorSurface.LastVParameter(); + + // calculate the normal at midpoint of the surface and use it as Z axis + gp_Dir dir; + surface->normal((u1 + u2) / 2, (v1 + v2) / 2, dir); + + placement.setRotation(Base::Rotation::fromNormalVector(Base::convertTo(-dir))); + } + } + break; + + default: + THROWM(Base::TypeError, + "AttachEngine3D::calculateAttachedPlacement: Unsupported shape type, " + "must be one of: Vertex, Edge, Face"); + break; + } + + return placement * attachmentOffset; + + break; + } default: throwWrongMode(mmode); } // switch (MapMode) @@ -2083,8 +2192,8 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vector shapes; - std::vector copiedShapeStorage; + std::vector shapes; + std::vector copiedShapeStorage; std::vector types; readLinks(objs, subs, shapes, copiedShapeStorage, types); @@ -2157,7 +2266,7 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vector points; for (const auto & shape : shapes) { - const TopoDS_Shape &sh = *shape; + const TopoDS_Shape &sh = shape->getShape(); if (sh.IsNull()) { throw Base::ValueError( "Null shape in AttachEngineLine::calculateAttachedPlacement()!"); @@ -2197,13 +2306,13 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vectorIsNull()) { + if (shapes[0]->isNull()) { throw Base::ValueError( "Null shape in AttachEngineLine::calculateAttachedPlacement()!"); } TopoDS_Edge e; try { - e = TopoDS::Edge(*(shapes[0])); + e = TopoDS::Edge(shapes[0]->getShape()); } catch (...) { } @@ -2228,13 +2337,13 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vectorIsNull()) { + if (shapes[0]->isNull()) { throw Base::ValueError( "Null shape in AttachEngineLine::calculateAttachedPlacement()!"); } TopoDS_Edge e; try { - e = TopoDS::Edge(*(shapes[0])); + e = TopoDS::Edge(shapes[0]->getShape()); } catch (...) { } @@ -2283,13 +2392,13 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vectorIsNull() || shapes[1]->IsNull()) { + if (shapes[0]->isNull() || shapes[1]->isNull()) { throw Base::ValueError( "Null shape in AttachEngineLine::calculateAttachedPlacement()!"); } - const TopoDS_Face& face1 = TopoDS::Face(*(shapes[0])); - const TopoDS_Face& face2 = TopoDS::Face(*(shapes[1])); + const TopoDS_Face& face1 = TopoDS::Face(shapes[0]->getShape()); + const TopoDS_Face& face2 = TopoDS::Face(shapes[1]->getShape()); Handle(Geom_Surface) hSurf1 = BRep_Tool::Surface(face1); Handle(Geom_Surface) hSurf2 = BRep_Tool::Surface(face2); @@ -2324,15 +2433,15 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vectorIsNull()) { + if (shapes[0]->isNull()) { throw Base::ValueError( "Null shape in AttachEngineLine::calculateAttachedPlacement()!"); } - if (shapes[1]->IsNull()) { + if (shapes[1]->isNull()) { throw Base::ValueError( "Null shape in AttachEngineLine::calculateAttachedPlacement()!"); } - BRepExtrema_DistShapeShape distancer(*(shapes[0]), *(shapes[1])); + BRepExtrema_DistShapeShape distancer(shapes[0]->getShape(), shapes[1]->getShape()); if (!distancer.IsDone()) { throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: " "proximity calculation failed."); @@ -2450,8 +2559,8 @@ AttachEnginePoint::_calculateAttachedPlacement(const std::vector shapes; - std::vector copiedShapeStorage; + std::vector shapes; + std::vector copiedShapeStorage; std::vector types; readLinks(objs, subs, shapes, copiedShapeStorage, types); @@ -2470,7 +2579,7 @@ AttachEnginePoint::_calculateAttachedPlacement(const std::vector points; assert(!shapes.empty()); - const TopoDS_Shape& sh = *shapes[0]; + const TopoDS_Shape& sh = shapes[0]->getShape(); if (sh.IsNull()) { throw Base::ValueError( "Null shape in AttachEnginePoint::calculateAttachedPlacement()!"); @@ -2492,13 +2601,13 @@ AttachEnginePoint::_calculateAttachedPlacement(const std::vectorIsNull()) { + if (shapes[0]->isNull()) { throw Base::ValueError( "Null shape in AttachEnginePoint::calculateAttachedPlacement()!"); } TopoDS_Edge e; try { - e = TopoDS::Edge(*(shapes[0])); + e = TopoDS::Edge(shapes[0]->getShape()); } catch (...) { } @@ -2546,16 +2655,16 @@ AttachEnginePoint::_calculateAttachedPlacement(const std::vectorIsNull()) { + if (shapes[0]->isNull()) { throw Base::ValueError( "Null shape in AttachEnginePoint::calculateAttachedPlacement()!"); } - if (shapes[1]->IsNull()) { + if (shapes[1]->isNull()) { throw Base::ValueError( "Null shape in AttachEnginePoint::calculateAttachedPlacement()!"); } - BasePoint = getProximityPoint(mmode, *(shapes[0]), *(shapes[1])); + BasePoint = getProximityPoint(mmode, shapes[0]->getShape(), shapes[1]->getShape()); } break; case mm0CenterOfMass: { GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes); diff --git a/src/Mod/Part/App/Attacher.h b/src/Mod/Part/App/Attacher.h index dd0c8ba569..a990069f0b 100644 --- a/src/Mod/Part/App/Attacher.h +++ b/src/Mod/Part/App/Attacher.h @@ -108,6 +108,7 @@ enum eMapMode { mmOYX, mmParallelPlane, + mmMidpoint, mmDummy_NumberOfModes//a value useful to check the validity of mode value };//see also eMapModeStrings[] definition in .cpp @@ -363,7 +364,7 @@ public://helper functions that may be useful outside of the class static eRefType getRefTypeByName(const std::string &typeName); - static GProp_GProps getInertialPropsOfShape(const std::vector &shapes); + static GProp_GProps getInertialPropsOfShape(const std::vector &shapes); std::vector getRefObjects() const; const std::vector &getSubValues() const {return subnames;} @@ -430,11 +431,36 @@ protected: } static void readLinks(const std::vector &objs, const std::vector &subs, - std::vector& shapes, std::vector &storage, + + std::vector& shapes, + std::vector &storage, std::vector &types); static void throwWrongMode(eMapMode mmode); + /** + * Extracts GeoFeature instance from given DocumentObject. + * + * In case of object itself being GeoFeature it returns itself, in other cases (like links) + * the method should return pointer to associated GeoFeature or nullptr if none is available. + * + * @param obj The document object to extract the GeoFeature. + * + * @return App::GeoFeature pointer associated with this document object + */ + static App::GeoFeature* extractGeoFeature(App::DocumentObject* obj); + + /** + * Tries to extract sub shape from document object with given subname. + * + * @param obj DocumentObject containing the sub shape + * @param subname Name of the sub shape to extract + * + * @return Extracted sub shape. Can be null. + * + * @throws AttachEngineException If given sub shape does not exist or is impossible to obtain. + */ + static Part::TopoShape extractSubShape(App::DocumentObject* obj, const std::string& subname); }; diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index 71bedcac65..871d58c2df 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -553,6 +553,8 @@ SET(Part_SRCS PreCompiled.h ProgressIndicator.cpp ProgressIndicator.h + Services.cpp + Services.h TopoShape.cpp TopoShape.h TopoShapeCache.cpp diff --git a/src/Mod/Part/App/Geometry.cpp b/src/Mod/Part/App/Geometry.cpp index 9a47464e2b..f786c75c68 100644 --- a/src/Mod/Part/App/Geometry.cpp +++ b/src/Mod/Part/App/Geometry.cpp @@ -149,6 +149,8 @@ #include "ToroidPy.h" #include "TopoShape.h" +#include + #if OCC_VERSION_HEX >= 0x070600 using GeomAdaptor_HCurve = GeomAdaptor_Curve; @@ -2127,10 +2129,25 @@ void GeomConic::setLocation(const Base::Vector3d& Center) Base::Vector3d GeomConic::getCenter() const { - Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(handle()); + Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(handle()); gp_Ax1 axis = conic->Axis(); const gp_Pnt& loc = axis.Location(); - return Base::Vector3d(loc.X(),loc.Y(),loc.Z()); + return Base::Vector3d(loc.X(), loc.Y(), loc.Z()); +} + +std::optional GeomConic::getRotation() const +{ + Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(handle()); + + if (!conic) { + return {}; + } + + gp_Trsf trsf; + trsf.SetTransformation(conic->Position(), gp_Ax3()); + + auto q = trsf.GetRotation(); + return Base::Rotation(q.X(), q.Y(), q.Z(), q.W()); } void GeomConic::setCenter(const Base::Vector3d& Center) @@ -2142,7 +2159,6 @@ void GeomConic::setCenter(const Base::Vector3d& Center) conic->SetLocation(p1); } catch (Standard_Failure& e) { - THROWM(Base::CADKernelError,e.GetMessageString()) } } @@ -4803,28 +4819,36 @@ GeomPlane* GeomSurface::toPlane(bool clone, double tol) const if (isDerivedFrom(GeomPlane::getClassTypeId())) { if (clone) { return dynamic_cast(this->clone()); - } else { + } + else { return dynamic_cast(this->copy()); } } gp_Pln pln; - if (!isPlanar(&pln, tol)) + if (!isPlanar(&pln, tol)) { return nullptr; + } auto res = new GeomPlane(pln); res->copyNonTag(this); - if (clone) + if (clone) { res->tag = this->tag; + } return res; } +std::optional GeomSurface::getRotation() const +{ + return {}; +} + TopoDS_Shape GeomSurface::toShape() const { Handle(Geom_Surface) s = Handle(Geom_Surface)::DownCast(handle()); - Standard_Real u1,u2,v1,v2; - s->Bounds(u1,u2,v1,v2); - BRepBuilderAPI_MakeFace mkBuilder(s, u1, u2, v1, v2, Precision::Confusion() ); + Standard_Real u1, u2, v1, v2; + s->Bounds(u1, u2, v1, v2); + BRepBuilderAPI_MakeFace mkBuilder(s, u1, u2, v1, v2, Precision::Confusion()); return mkBuilder.Shape(); } @@ -5180,9 +5204,24 @@ GeomElementarySurface::~GeomElementarySurface() Base::Vector3d GeomElementarySurface::getLocation(void) const { - Handle(Geom_ElementarySurface) surf = Handle(Geom_ElementarySurface)::DownCast(handle()); + Handle(Geom_ElementarySurface) surf = Handle(Geom_ElementarySurface)::DownCast(handle()); gp_Pnt loc = surf->Location(); - return Base::Vector3d(loc.X(),loc.Y(),loc.Z()); + return Base::Vector3d(loc.X(), loc.Y(), loc.Z()); +} + +std::optional GeomPlane::getRotation() const +{ + Handle(Geom_ElementarySurface) s = Handle(Geom_ElementarySurface)::DownCast(handle()); + + if (!s) { + return {}; + } + + gp_Trsf trsf; + trsf.SetTransformation(s->Position().Ax2(),gp_Ax3()); + auto q = trsf.GetRotation(); + + return Base::Rotation(q.X(),q.Y(),q.Z(),q.W()); } Base::Vector3d GeomElementarySurface::getDir(void) const @@ -5411,6 +5450,12 @@ double GeomCone::getSemiAngle() const return mySurface->SemiAngle(); } +Base::Vector3d GeomCone::getApex() const +{ + Handle(Geom_ConicalSurface) s = Handle(Geom_ConicalSurface)::DownCast(handle()); + return Base::convertTo(s->Apex()); +} + bool GeomCone::isSame(const Geometry &_other, double tol, double atol) const { if(_other.getTypeId() != getTypeId()) diff --git a/src/Mod/Part/App/Geometry.h b/src/Mod/Part/App/Geometry.h index 4af1809128..376b16b99a 100644 --- a/src/Mod/Part/App/Geometry.h +++ b/src/Mod/Part/App/Geometry.h @@ -58,6 +58,7 @@ #include #include #include +#include #include @@ -409,6 +410,7 @@ public: */ Base::Vector3d getCenter() const; Base::Vector3d getLocation() const; + std::optional getRotation() const; void setLocation(const Base::Vector3d& Center); /*! * \deprecated use setLocation @@ -882,6 +884,8 @@ public: GeomPlane *toPlane(bool clone=true, double tol=1e-7) const; + virtual std::optional getRotation() const; + bool tangentU(double u, double v, gp_Dir& dirU) const; bool tangentV(double u, double v, gp_Dir& dirV) const; bool normal(double u, double v, gp_Dir& dir) const; @@ -961,6 +965,7 @@ public: ~GeomElementarySurface() override; Base::Vector3d getLocation() const; + Base::Vector3d getDir() const; Base::Vector3d getXDir() const; Base::Vector3d getYDir() const; @@ -1014,6 +1019,8 @@ public: double getRadius() const; double getSemiAngle() const; + Base::Vector3d getApex() const; + bool isSame(const Geometry &other, double tol, double atol) const override; void setHandle(const Handle(Geom_ConicalSurface)&); @@ -1091,6 +1098,8 @@ public: ~GeomPlane() override; Geometry *copy() const override; + std::optional getRotation() const override; + // Persistence implementer --------------------- unsigned int getMemSize() const override; void Save(Base::Writer &/*writer*/) const override; diff --git a/src/Mod/Part/App/Services.cpp b/src/Mod/Part/App/Services.cpp new file mode 100644 index 0000000000..65eac11cdb --- /dev/null +++ b/src/Mod/Part/App/Services.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "Services.h" + +AttacherSubObjectPlacement::AttacherSubObjectPlacement() + : attacher(std::make_unique()) +{ + attacher->setUp({}, Attacher::mmMidpoint); +} + +Base::Placement AttacherSubObjectPlacement::calculate(App::SubObjectT object, + Base::Placement basePlacement) const +{ + attacher->setReferences({object}); + + auto calculatedAttachment = attacher->calculateAttachedPlacement(basePlacement); + + return basePlacement.inverse() * calculatedAttachment; +} + +std::optional PartCenterOfMass::ofDocumentObject(App::DocumentObject* object) const +{ + if (const auto feature = dynamic_cast(object)) { + const auto shape = feature->Shape.getShape(); + + if (const auto cog = shape.centerOfGravity()) { + const Base::Placement comPlacement { *cog, Base::Rotation { } }; + + return (feature->Placement.getValue().inverse() * comPlacement).getPosition(); + } + } + + return {}; +} \ No newline at end of file diff --git a/src/Mod/Part/App/Services.h b/src/Mod/Part/App/Services.h new file mode 100644 index 0000000000..217ff47e8d --- /dev/null +++ b/src/Mod/Part/App/Services.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef PART_SERVICES_H +#define PART_SERVICES_H + +#include +#include + +class AttacherSubObjectPlacement final: public App::SubObjectPlacementProvider +{ +public: + AttacherSubObjectPlacement(); + + Base::Placement calculate(App::SubObjectT object, Base::Placement basePlacement) const override; + +private: + std::unique_ptr attacher; +}; + +class PartCenterOfMass final: public App::CenterOfMassProvider +{ +public: + std::optional ofDocumentObject(App::DocumentObject* object) const override; +}; + +#endif // PART_SERVICES_H diff --git a/src/Mod/PartDesign/Gui/ViewProvider.cpp b/src/Mod/PartDesign/Gui/ViewProvider.cpp index 2b1d364aec..9da111e119 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.cpp +++ b/src/Mod/PartDesign/Gui/ViewProvider.cpp @@ -81,7 +81,16 @@ void ViewProvider::setupContextMenu(QMenu* menu, QObject* receiver, const char* bool ViewProvider::setEdit(int ModNum) { - if (ModNum == ViewProvider::Default ) { + if (ModNum == ViewProvider::Transform) { + if (forwardToLink()) { + return true; + } + + // this is feature so we need to forward the transform to the body + forwardedViewProvider = getBodyViewProvider(); + return forwardedViewProvider->startEditing(ModNum); + } + else if (ModNum == ViewProvider::Default) { // When double-clicking on the item for this feature the // object unsets and sets its edit mode without closing // the task panel @@ -194,6 +203,22 @@ void ViewProvider::onChanged(const App::Property* prop) { PartGui::ViewProviderPartExt::onChanged(prop); } +Gui::ViewProvider* ViewProvider::startEditing(int ModNum) +{ + // in case of transform we forward the request to body + if (ModNum == Transform) { + forwardedViewProvider = nullptr; + + if (!ViewProviderPart::startEditing(ModNum)) { + return nullptr; + } + + return forwardedViewProvider; + } + + return ViewProviderPart::startEditing(ModNum); +} + void ViewProvider::setTipIcon(bool onoff) { isSetTipIcon = onoff; diff --git a/src/Mod/PartDesign/Gui/ViewProvider.h b/src/Mod/PartDesign/Gui/ViewProvider.h index 9b8e16c38d..6e303a0e6e 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.h +++ b/src/Mod/PartDesign/Gui/ViewProvider.h @@ -55,6 +55,8 @@ public: void updateData(const App::Property*) override; void onChanged(const App::Property* prop) override; + Gui::ViewProvider* startEditing(int ModNum) override; + void setTipIcon(bool onoff); //body mode means that the object is part of a body and that the body is used to set the diff --git a/tests/src/Base/ServiceProvider.cpp b/tests/src/Base/ServiceProvider.cpp index 405c8cf134..0d6b41a773 100644 --- a/tests/src/Base/ServiceProvider.cpp +++ b/tests/src/Base/ServiceProvider.cpp @@ -62,7 +62,7 @@ TEST(ServiceProvider, provideImplementation) // Arrange Base::ServiceProvider serviceProvider; - serviceProvider.implement(new FirstServiceImplementation); + serviceProvider.registerImplementation(new FirstServiceImplementation); // Act auto implementation = serviceProvider.provide(); @@ -77,8 +77,8 @@ TEST(ServiceProvider, provideLatestImplementation) // Arrange Base::ServiceProvider serviceProvider; - serviceProvider.implement(new FirstServiceImplementation); - serviceProvider.implement(new SecondServiceImplementation); + serviceProvider.registerImplementation(new FirstServiceImplementation); + serviceProvider.registerImplementation(new SecondServiceImplementation); // Act auto implementation = serviceProvider.provide(); @@ -93,8 +93,8 @@ TEST(ServiceProvider, provideAllImplementations) // Arrange Base::ServiceProvider serviceProvider; - serviceProvider.implement(new FirstServiceImplementation); - serviceProvider.implement(new SecondServiceImplementation); + serviceProvider.registerImplementation(new FirstServiceImplementation); + serviceProvider.registerImplementation(new SecondServiceImplementation); // Act auto implementations = serviceProvider.all(); diff --git a/tests/src/Mod/Part/App/Attacher.cpp b/tests/src/Mod/Part/App/Attacher.cpp index 67b25e466b..facb6d1a0d 100644 --- a/tests/src/Mod/Part/App/Attacher.cpp +++ b/tests/src/Mod/Part/App/Attacher.cpp @@ -79,8 +79,8 @@ TEST_F(AttacherTest, TestGetShapeType) TEST_F(AttacherTest, TestGetInertialPropsOfShape) { auto& attacher = _boxes[1]->attacher(); - std::vector result; - auto faces = _boxes[1]->Shape.getShape().getSubShapes(TopAbs_FACE); + std::vector result; + auto faces = _boxes[1]->Shape.getShape().getSubTopoShapes(TopAbs_FACE); result.emplace_back(&faces[0]); auto shapeType = attacher.getInertialPropsOfShape(result); EXPECT_EQ(result.size(), 1);