diff --git a/src/Base/Tools.h b/src/Base/Tools.h index 6757364b2c..a0ddb2a17e 100644 --- a/src/Base/Tools.h +++ b/src/Base/Tools.h @@ -180,6 +180,49 @@ inline T fmod(T numerator, T denominator) return (modulo >= T(0)) ? modulo : modulo + denominator; } +template +inline T angularDist(T v1, T v2) +{ + return std::min(std::fabs(v1 - v2), 360 - std::fabs(v1 - v2)); +} + +// Returns a value between [0, 360) or (-180, 180] depending on if the +// minimum value was positive or negetive. This is done because the taper angle +// values in FreeCAD usually treat values like -10 and 350 differently +template +inline double clampAngle(T value, T min, T max, T precision) +{ + // Normalize the angles between 0 and 360 + value = Base::fmod(value, 360.0); + T nMin = Base::fmod(min, 360.0); + T nMax = Base::fmod(max, 360.0); + + if (std::abs(nMax - nMin) > precision) { + if (nMax > nMin) { + if (value < nMin || value > nMax) { + value = angularDist(value, nMin) > angularDist(value, nMax) ? nMax : nMin; + } + } + else { + if (value < nMin && value > nMax) { + value = angularDist(value, nMin) > angularDist(value, nMax) ? nMax : nMin; + } + } + } + + if (min >= 0.0) { + // Return in [0, 360) + return value; + } + + // Map to (-180, 180] + if (value > 180.0) { + value = value - 360; + } + return value; +} + + // ---------------------------------------------------------------------------- // NOLINTBEGIN @@ -388,4 +431,5 @@ Overloads(Ts...) -> Overloads; } // namespace Base + #endif // SRC_BASE_TOOLS_H_ diff --git a/src/Base/Vector3D.cpp b/src/Base/Vector3D.cpp index 7f9403977d..7cc67b02c6 100644 --- a/src/Base/Vector3D.cpp +++ b/src/Base/Vector3D.cpp @@ -437,6 +437,15 @@ Vector3& Vector3::Normalize() return *this; } +template +Vector3 Vector3::Normalized() const +{ + Vector3 copy = *this; + copy.Normalize(); + + return copy; +} + template bool Vector3::IsNull() const { diff --git a/src/Base/Vector3D.h b/src/Base/Vector3D.h index 6b8ec5423c..6671f8bd6e 100644 --- a/src/Base/Vector3D.h +++ b/src/Base/Vector3D.h @@ -170,6 +170,7 @@ public: [[nodiscard]] float_type Sqr() const; /// Set length to 1. Vector3& Normalize(); + Vector3 Normalized() const; /// Checks whether this is the null vector [[nodiscard]] bool IsNull() const; /// Get angle between both vectors. The returned value lies in the interval [0,pi]. diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 719004ea20..a4d6c8bff3 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -24,7 +24,7 @@ if(FC_FLATPAK) endif(FC_FLATPAK) if(WIN32) -add_definitions(-DFCGui -DQSINT_MAKEDLL -DOVR_OS_WIN32 -DQUARTER_INTERNAL -DQUARTER_MAKE_DLL -DCOIN_DLL) + add_definitions(-DFCGui -DQSINT_MAKEDLL -DOVR_OS_WIN32 -DQUARTER_INTERNAL -DQUARTER_MAKE_DLL -DCOIN_DLL) endif(WIN32) IF(CMAKE_BUILD_TYPE) @@ -1117,8 +1117,11 @@ SET(Inventor_CPP_SRCS Inventor/SoToggleSwitch.cpp Inventor/Draggers/SoTransformDragger.cpp Inventor/Draggers/SoLinearDragger.cpp + Inventor/Draggers/SoLinearDraggerGeometry.cpp Inventor/Draggers/SoPlanarDragger.cpp Inventor/Draggers/SoRotationDragger.cpp + Inventor/Draggers/SoRotationDraggerGeometry.cpp + Inventor/Draggers/Gizmo.cpp SoFCColorBar.cpp SoFCColorBarNotifier.cpp SoFCColorGradient.cpp @@ -1154,8 +1157,12 @@ SET(Inventor_SRCS Inventor/SoToggleSwitch.h Inventor/Draggers/SoTransformDragger.h Inventor/Draggers/SoLinearDragger.h + Inventor/Draggers/SoLinearDraggerGeometry.h Inventor/Draggers/SoPlanarDragger.h Inventor/Draggers/SoRotationDragger.h + Inventor/Draggers/SoRotationDraggerGeometry.h + Inventor/Draggers/Gizmo.h + Inventor/Draggers/GizmoHelper.h SoFCColorBar.h SoFCColorBarNotifier.h SoFCColorGradient.h @@ -1493,8 +1500,11 @@ if (EIGEN3_NO_DEPRECATED_COPY) Inventor/SoAutoZoomTranslation.cpp Inventor/Draggers/SoTransformDragger.cpp Inventor/Draggers/SoLinearDragger.cpp + Inventor/Draggers/SoLinearDraggerGeometry.cpp Inventor/Draggers/SoPlanarDragger.cpp Inventor/Draggers/SoRotationDragger.cpp + Inventor/Draggers/SoRotationDraggerGeometry.cpp + Inventor/Draggers/Gizmo.cpp SoFCOffscreenRenderer.cpp Selection/SoFCSelectionAction.cpp Quarter/QuarterWidget.cpp diff --git a/src/Gui/Inventor/Draggers/Gizmo.cpp b/src/Gui/Inventor/Draggers/Gizmo.cpp new file mode 100644 index 0000000000..ad6bba3988 --- /dev/null +++ b/src/Gui/Inventor/Draggers/Gizmo.cpp @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 "PreCompiled.h" + +#include "Gizmo.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SoLinearDragger.h" +#include "SoLinearDraggerGeometry.h" +#include "SoRotationDragger.h" +#include "SoRotationDraggerGeometry.h" + +using namespace Gui; + +void Gizmo::setDraggerPlacement(const Base::Vector3d& pos, const Base::Vector3d& dir) +{ + setDraggerPlacement( + Base::convertTo(pos), + Base::convertTo(dir) + ); +} + +bool Gizmo::isDelayedUpdateEnabled() +{ + static Base::Reference hGrp = App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp/Preferences/Mod/PartDesign"); + + return hGrp->GetBool("DelayedGizmoUpdate", false); +} + +double Gizmo::getMultFactor() +{ + return multFactor; +} + +double Gizmo::getAddFactor() +{ + return addFactor; +} + +bool Gizmo::getVisibility() { + return visible; +} + +LinearGizmo::LinearGizmo(QuantitySpinBox* property) +{ + setProperty(property); +} + +SoInteractionKit* LinearGizmo::initDragger() +{ + draggerContainer = new SoLinearDraggerContainer; + draggerContainer->color.setValue(1, 0, 0); + dragger = draggerContainer->getDragger(); + + dragger->addStartCallback( + [] (void* data, SoDragger*) { + static_cast(data)->draggingStarted(); + }, + this + ); + dragger->addFinishCallback( + [] (void* data, SoDragger*) { + static_cast(data)->draggingFinished(); + }, + this + ); + dragger->addMotionCallback( + [] (void* data, SoDragger*) { + static_cast(data)->draggingContinued(); + }, + this + ); + + dragger->labelVisible = false; + + setDragLength(property->value().getValue()); + + dragger->instantiateBaseGeometry(); + + // change the dragger dimensions + auto arrow = SO_GET_PART(dragger, "arrow", SoArrowGeometry); + arrow->cylinderHeight = 3.5; + arrow->cylinderRadius = 0.2; + + return draggerContainer; +} + +void LinearGizmo::uninitDragger() +{ + dragger = nullptr; + draggerContainer = nullptr; +} + +GizmoPlacement LinearGizmo::getDraggerPlacement() +{ + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + return {draggerContainer->translation.getValue(), draggerContainer->getPointerDirection()}; +} + +void LinearGizmo::setDraggerPlacement(const SbVec3f& pos, const SbVec3f& dir) +{ + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + draggerContainer->translation = pos; + draggerContainer->setPointerDirection(dir); +} + +void LinearGizmo::reverseDir() { + auto dir = getDraggerContainer()->getPointerDirection(); + getDraggerContainer()->setPointerDirection(dir * -1); +} + + +double LinearGizmo::getDragLength() +{ + double dragLength = dragger->translationIncrementCount.getValue() + * dragger->translationIncrement.getValue(); + + return (dragLength - addFactor) / multFactor; +} + +void LinearGizmo::setDragLength(double dragLength) +{ + dragLength = dragLength * multFactor + addFactor; + dragger->translation = {0, static_cast(dragLength), 0}; +} + +void LinearGizmo::setGeometryScale(float scale) +{ + dragger->geometryScale = SbVec3f(scale, scale, scale); +} + +SoLinearDraggerContainer* LinearGizmo::getDraggerContainer() +{ + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + return draggerContainer; +} + +void LinearGizmo::setProperty(QuantitySpinBox* property) +{ + if (quantityChangedConnection) { + QuantitySpinBox::disconnect(quantityChangedConnection); + } + + this->property = property; + quantityChangedConnection = QuantitySpinBox::connect( + property, qOverload(&Gui::QuantitySpinBox::valueChanged), + [this] (double value) { + setDragLength(value); + } + ); + quantityChangedConnection = QuantitySpinBox::connect( + property, &Gui::QuantitySpinBox::showFormulaDialog, + [this] (bool) { + // This will set the visibility of the actual geometry to true or false + // based on if an expression is bound and the externally set visibility + setVisibility(visible); + } + ); +} + +void LinearGizmo::setMultFactor(const double val) +{ + multFactor = val; + setDragLength(property->value().getValue()); +} + +void LinearGizmo::setAddFactor(const double val) +{ + addFactor = val; + setDragLength(property->value().getValue()); +} + +void LinearGizmo::setVisibility(bool visible) +{ + this->visible = visible; + getDraggerContainer()->visible = visible && !property->hasExpression(); +} + +void LinearGizmo::draggingStarted() +{ + initialValue = property->value().getValue(); + dragger->translationIncrementCount.setValue(0); + + if (isDelayedUpdateEnabled()) { + property->blockSignals(true); + } +} + +void LinearGizmo::draggingFinished() +{ + if (isDelayedUpdateEnabled()) { + property->blockSignals(false); + property->valueChanged(property->value().getValue()); + } +} + +void LinearGizmo::draggingContinued() +{ + double value = initialValue + getDragLength(); + // TODO: Need to change the lower limit to sudoThis->property->minimum() once the + // two direction extrude work gets merged + value = std::clamp(value, dragger->translationIncrement.getValue(), property->maximum()); + + property->setValue(value); + setDragLength(value); +} + + +RotationGizmo::RotationGizmo(QuantitySpinBox* property) +{ + setProperty(property); +} + +RotationGizmo::~RotationGizmo() +{ + translationSensor.detach(); + translationSensor.setData(nullptr); + translationSensor.setFunction(nullptr); +} + +SoInteractionKit* RotationGizmo::initDragger() +{ + multFactor = std::numbers::pi_v / 180.0; + draggerContainer = new SoRotationDraggerContainer; + + draggerContainer->color.setValue(1, 0, 0); + dragger = draggerContainer->getDragger(); + dragger->rotationIncrement = std::numbers::pi / 90.0; + + auto rotator = new SoRotatorGeometry; + rotator->arcAngle = std::numbers::pi_v / 6.0f; + rotator->arcRadius = 16.0f; + dragger->setPart("rotator", rotator); + + dragger->addStartCallback( + [] (void* data, SoDragger*) { + static_cast(data)->draggingStarted(); + }, + this + ); + dragger->addFinishCallback( + [] (void* data, SoDragger*) { + static_cast(data)->draggingFinished(); + }, + this + ); + dragger->addMotionCallback( + [] (void* data, SoDragger*) { + static_cast(data)->draggingContinued(); + }, + this + ); + + setRotAngle(property->value().getValue()); + + return draggerContainer; +} + +void RotationGizmo::uninitDragger() +{ + dragger = nullptr; + draggerContainer = nullptr; + + translationSensor.detach(); + translationSensor.setData(nullptr); + translationSensor.setFunction(nullptr); +} + +GizmoPlacement RotationGizmo::getDraggerPlacement() +{ + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + return {draggerContainer->translation.getValue(), draggerContainer->getPointerDirection()}; +} + +void RotationGizmo::setDraggerPlacement(const SbVec3f& pos, const SbVec3f& dir) +{ + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + draggerContainer->translation = pos; + draggerContainer->setPointerDirection(dir); +} + +void RotationGizmo::reverseDir() { + auto dir = getDraggerContainer()->getPointerDirection(); + getDraggerContainer()->setPointerDirection(dir * -1); +} + +void RotationGizmo::placeOverLinearGizmo(LinearGizmo* gizmo) +{ + linearGizmo = gizmo; + + GizmoPlacement placement = gizmo->getDraggerPlacement(); + + draggerContainer->translation = Base::convertTo(placement.pos); + draggerContainer->setPointerDirection(placement.dir); + + translationSensor.setData(this); + translationSensor.setFunction(translationSensorCB); + translationSensor.setPriority(0); + SoSFVec3f& translation = gizmo->getDraggerContainer()->getDragger()->translation; + translationSensor.attach(&translation); + translation.touch(); + + automaticOrientation = true; +} + +void RotationGizmo::translationSensorCB(void* data, SoSensor* sensor) +{ + auto sudoThis = static_cast(data); + auto translationSensor = static_cast(sensor); + + GizmoPlacement placement = sudoThis->linearGizmo->getDraggerPlacement(); + + SbVec3f translation = static_cast(translationSensor->getAttachedField())->getValue(); + float yComp = translation.getValue()[1]; + SbVec3f dir = placement.dir; + dir.normalize(); + sudoThis->draggerContainer->translation = placement.pos + dir * (yComp + sudoThis->sepDistance); +} + +void RotationGizmo::placeBelowLinearGizmo(LinearGizmo* gizmo) +{ + linearGizmo = gizmo; + + GizmoPlacement placement = gizmo->getDraggerPlacement(); + + draggerContainer->translation = Base::convertTo(placement.pos); + draggerContainer->setPointerDirection(-placement.dir); + + translationSensor.setData(this); + translationSensor.setFunction(translationSensorCB); + translationSensor.setPriority(0); + SoSFVec3f& translation = gizmo->getDraggerContainer()->getDragger()->translation; + translationSensor.attach(&translation); + translation.touch(); +} + +double RotationGizmo::getRotAngle() +{ + double rotAngle = dragger->rotationIncrementCount.getValue() + * dragger->rotationIncrement.getValue(); + + return (rotAngle - addFactor) / multFactor; +} + +void RotationGizmo::setRotAngle(double angle) +{ + angle = multFactor * angle + addFactor; + dragger->rotation = SbRotation({0, 0, 1.0f}, static_cast(angle)); +} + +void RotationGizmo::setGeometryScale(float scale) +{ + dragger->geometryScale = SbVec3f(scale, scale, scale); +} + +SoRotationDraggerContainer* RotationGizmo::getDraggerContainer() +{ + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + return draggerContainer; +} + +void RotationGizmo::draggingStarted() +{ + initialValue = property->value().getValue(); + dragger->rotationIncrementCount.setValue(0); + + if (isDelayedUpdateEnabled()) { + property->blockSignals(true); + } +} + +void RotationGizmo::draggingFinished() +{ + if (isDelayedUpdateEnabled()) { + property->blockSignals(false); + property->valueChanged(property->value().getValue()); + } +} + +void RotationGizmo::draggingContinued() +{ + double value = initialValue + getRotAngle(); + value = Base::clampAngle( + value, property->minimum(), property->maximum(), + Base::Precision::Confusion() + ); + + property->setValue(value); + setRotAngle(value); +} + +void RotationGizmo::orientAlongCamera(SoCamera* camera) +{ + if (linearGizmo == nullptr || automaticOrientation == false) { + return; + } + + SbVec3f cameraDir{0, 0, 1}; + camera->orientation.getValue().multVec(cameraDir, cameraDir); + SbVec3f pointerDir = linearGizmo->getDraggerContainer()->getPointerDirection(); + + pointerDir.normalize(); + auto proj = cameraDir - cameraDir.dot(pointerDir) * pointerDir; + if (proj.equals(SbVec3f{0, 0, 0}, 0.001)) { + return; + } + + assert(draggerContainer && "Forgot to call GizmoContainer::initGizmos?"); + draggerContainer->setArcNormalDirection(proj); +} + +void RotationGizmo::setProperty(QuantitySpinBox* property) +{ + if (quantityChangedConnection) { + QuantitySpinBox::disconnect(quantityChangedConnection); + } + + this->property = property; + quantityChangedConnection = QuantitySpinBox::connect( + property, qOverload(&Gui::QuantitySpinBox::valueChanged), + [this] (double value) { + setRotAngle(value); + } + ); + quantityChangedConnection = QuantitySpinBox::connect( + property, &Gui::QuantitySpinBox::showFormulaDialog, + [this] (bool) { + // This will set the visibility of the actual geometry to true or false + // based on if an expression is bound and the externally set visibility + setVisibility(visible); + } + ); +} + +void RotationGizmo::setMultFactor(const double val) +{ + multFactor = val; + setRotAngle(property->value().getValue()); +} + +void RotationGizmo::setAddFactor(const double val) +{ + addFactor = val; + setRotAngle(property->value().getValue()); +} + +void RotationGizmo::setVisibility(bool visible) +{ + this->visible = visible; + getDraggerContainer()->visible = visible && !property->hasExpression(); +} + +DirectedRotationGizmo::DirectedRotationGizmo(QuantitySpinBox* property): RotationGizmo(property) +{} + +SoInteractionKit* DirectedRotationGizmo::initDragger() +{ + SoInteractionKit* ret = inherited::initDragger(); + + auto dragger = getDraggerContainer()->getDragger(); + auto rotator = new SoRotatorGeometry2; + rotator->arcAngle = std::numbers::pi_v / 6.0f; + rotator->arcRadius = 16.0f; + rotator->rightArrowVisible = false; + dragger->setPart("rotator", rotator); + + return ret; +} + +void DirectedRotationGizmo::flipArrow() +{ + auto dragger = getDraggerContainer()->getDragger(); + auto rotator = SO_GET_PART(dragger, "rotator", SoRotatorGeometry2); + + rotator->toggleArrowVisibility(); +} + + +RadialGizmo::RadialGizmo(QuantitySpinBox* property): RotationGizmo(property) +{} + +SoInteractionKit* RadialGizmo::initDragger() +{ + SoInteractionKit* ret = inherited::initDragger(); + + auto dragger = getDraggerContainer()->getDragger(); + auto rotator = new SoRotatorArrow; + rotator->geometryScale.connectFrom(&dragger->geometryScale); + dragger->setPart("rotator", rotator); + + dragger->instantiateBaseGeometry(); + + return ret; +} + +void RadialGizmo::setRadius(float radius) +{ + auto dragger = getDraggerContainer()->getDragger(); + auto rotator = SO_GET_PART(dragger, "rotator", SoRotatorArrow); + auto baseGeom = SO_GET_PART(dragger, "baseGeom", SoRotatorBase); + + rotator->radius = baseGeom->arcRadius = radius; +} + +void RadialGizmo::flipArrow() +{ + auto dragger = getDraggerContainer()->getDragger(); + auto rotator = SO_GET_PART(dragger, "rotator", SoRotatorArrow); + + rotator->flipArrow(); +} + +SO_KIT_SOURCE(GizmoContainer) + +void GizmoContainer::initClass() +{ + SO_KIT_INIT_CLASS(GizmoContainer, SoBaseKit, "BaseKit"); +} + +GizmoContainer::GizmoContainer() +{ + SO_KIT_CONSTRUCTOR(GizmoContainer); + +#if defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + this->ref(); +#endif + + FC_ADD_CATALOG_ENTRY(annotation, So3DAnnotation, this); + FC_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, annotation); + FC_ADD_CATALOG_ENTRY(toggleSwitch, SoToggleSwitch, annotation); + FC_ADD_CATALOG_ENTRY(geometry, SoSeparator, toggleSwitch); + + SO_KIT_INIT_INSTANCE(); + + SO_KIT_ADD_FIELD(visible, (1)); + + auto pickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + pickStyle->style = SoPickStyle::SHAPE_ON_TOP; + + auto toggleSwitch = SO_GET_ANY_PART(this, "toggleSwitch", SoToggleSwitch); + toggleSwitch->on.connectFrom(&visible); + + setPart("geometry", new SoSeparator); + + cameraSensor.setFunction(&GizmoContainer::cameraChangeCallback); + cameraSensor.setData(this); + + cameraPositionSensor.setData(this); + cameraPositionSensor.setFunction(cameraPositionChangeCallback); +} + +GizmoContainer::~GizmoContainer() +{ + cameraSensor.setData(nullptr); + cameraSensor.detach(); + + cameraPositionSensor.setData(nullptr); + cameraPositionSensor.detach(); + + uninitGizmos(); +} + +void GizmoContainer::initGizmos() +{ + auto geometry = SO_GET_ANY_PART(this, "geometry", SoSeparator); + for (auto gizmo: gizmos) { + geometry->addChild(gizmo->initDragger()); + } +} + +void GizmoContainer::uninitGizmos() +{ + for (auto gizmo: gizmos) { + gizmo->uninitDragger(); + delete gizmo; + } + gizmos.clear(); +} + +void GizmoContainer::addGizmos(std::initializer_list gizmos) +{ + assert(this->gizmos.size() == 0 && "Already called GizmoContainer::addGizmos?"); + + for (auto gizmo: gizmos) { + addGizmo(gizmo); + } + initGizmos(); +} + +void GizmoContainer::addGizmo(Gizmo* gizmo) +{ + assert(std::ranges::find(gizmos, gizmo) == gizmos.end() && "this gizmo is already added!"); + gizmos.push_back(gizmo); +} + +void GizmoContainer::attachViewer(Gui::View3DInventorViewer* viewer, Base::Placement &origin) +{ + if (!viewer) { + return; + } + + auto mat = origin.toMatrix(); + + viewer->getDocument()->setEditingTransform(mat); + So3DAnnotation* annotation = SO_GET_ANY_PART(this, "annotation", So3DAnnotation); + viewer->setupEditingRoot(annotation, &mat); +} + +void GizmoContainer::setUpAutoScale(SoCamera* cameraIn) +{ + if (cameraIn->getTypeId() == SoOrthographicCamera::getClassTypeId()) { + auto localCamera = dynamic_cast(cameraIn); + cameraSensor.attach(&localCamera->height); + cameraPositionSensor.attach(&localCamera->orientation); + calculateScaleAndOrientation(); + + } + else if (cameraIn->getTypeId() == SoPerspectiveCamera::getClassTypeId()) { + auto localCamera = dynamic_cast(cameraIn); + cameraSensor.attach(&localCamera->position); + cameraPositionSensor.attach(&localCamera->orientation); + calculateScaleAndOrientation(); + } +} + +void GizmoContainer::calculateScaleAndOrientation() +{ + if (cameraSensor.getAttachedField()) { + cameraChangeCallback(this, nullptr); + cameraPositionChangeCallback(this, nullptr); + } +} + +void GizmoContainer::cameraChangeCallback(void* data, SoSensor*) +{ + auto sudoThis = static_cast(data); + + SoField* field = sudoThis->cameraSensor.getAttachedField(); + if (!field) { + return; + } + + auto camera = static_cast(field->getContainer()); + + SbViewVolume viewVolume = camera->getViewVolume(); + for (auto gizmo: sudoThis->gizmos) { + float localScale = viewVolume.getWorldToScreenScale(gizmo->getDraggerPlacement().pos, 0.015); + gizmo->setGeometryScale(localScale); + } +} + +void GizmoContainer::cameraPositionChangeCallback(void* data, SoSensor*) +{ + auto sudoThis = static_cast(data); + + SoField* field = sudoThis->cameraSensor.getAttachedField(); + if (field) { + auto camera = static_cast(field->getContainer()); + + for (auto gizmo: sudoThis->gizmos) { + gizmo->orientAlongCamera(camera); + } + } +} + +bool GizmoContainer::isEnabled() +{ + static Base::Reference hGrp = App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp/Preferences/Mod/PartDesign"); + + return hGrp->GetBool("EnableGizmos", true); +} diff --git a/src/Gui/Inventor/Draggers/Gizmo.h b/src/Gui/Inventor/Draggers/Gizmo.h new file mode 100644 index 0000000000..a87a98ff19 --- /dev/null +++ b/src/Gui/Inventor/Draggers/Gizmo.h @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 GUI_GIZMO_H +#define GUI_GIZMO_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +class SoDragger; +class SoCamera; +class SoInteractionKit; + +namespace Gui +{ +class QuantitySpinBox; +class SoLinearDragger; +class SoLinearDraggerContainer; +class SoRotationDragger; +class SoRotationDraggerContainer; +class View3DInventorViewer; + +struct GizmoPlacement +{ + SbVec3f pos; + SbVec3f dir; +}; + +class GuiExport Gizmo +{ +public: + virtual ~Gizmo() = default; + virtual SoInteractionKit* initDragger() = 0; + virtual void uninitDragger() = 0; + + virtual GizmoPlacement getDraggerPlacement() = 0; + virtual void setDraggerPlacement(const SbVec3f& pos, const SbVec3f& dir) = 0; + void setDraggerPlacement(const Base::Vector3d& pos, const Base::Vector3d& dir); + + virtual void setGeometryScale(float scale) = 0; + virtual void orientAlongCamera([[maybe_unused]] SoCamera* camera) {}; + bool isDelayedUpdateEnabled(); + + double getMultFactor(); + double getAddFactor(); + + bool getVisibility(); + +protected: + double multFactor = 1.0f; + double addFactor = 0.0f; + + QuantitySpinBox* property = nullptr; + double initialValue; + + bool visible = true; + bool hasExpression = false; +}; + +class GuiExport LinearGizmo: public Gizmo +{ +public: + LinearGizmo(QuantitySpinBox* property); + ~LinearGizmo() override = default; + + SoInteractionKit* initDragger() override; + void uninitDragger() override; + + // Returns the position and rotation of the base of the dragger + GizmoPlacement getDraggerPlacement() override; + void setDraggerPlacement(const SbVec3f& pos, const SbVec3f& dir) override; + void reverseDir(); + // Returns the drag distance from the base of the feature + double getDragLength(); + void setDragLength(double dragLength); + void setGeometryScale(float scale) override; + SoLinearDraggerContainer* getDraggerContainer(); + void setProperty(QuantitySpinBox* property); + void setMultFactor(const double val); + void setAddFactor(const double val); + void setVisibility(bool visible); + +private: + SoLinearDragger* dragger = nullptr; + SoLinearDraggerContainer* draggerContainer = nullptr; + QMetaObject::Connection quantityChangedConnection; + + void draggingStarted(); + void draggingFinished(); + void draggingContinued(); + + using inherited = Gizmo; +}; + +class GuiExport RotationGizmo: public Gizmo +{ +public: + RotationGizmo(QuantitySpinBox* property); + ~RotationGizmo() override; + + SoInteractionKit* initDragger() override; + void uninitDragger() override; + + // Distance between the linear gizmo base and rotation gizmo + double sepDistance = 0; + + // Returns the position and rotation of the base of the dragger + GizmoPlacement getDraggerPlacement() override; + void setDraggerPlacement(const SbVec3f& pos, const SbVec3f& dir) override; + void reverseDir(); + // The two gizmos are separated by sepDistance units + void placeOverLinearGizmo(LinearGizmo* gizmo); + void placeBelowLinearGizmo(LinearGizmo* gizmo); + // Returns the rotation angle wrt the normal axis + double getRotAngle(); + void setRotAngle(double angle); + void setGeometryScale(float scale) override; + SoRotationDraggerContainer* getDraggerContainer(); + void orientAlongCamera(SoCamera* camera) override; + void setProperty(QuantitySpinBox* property); + void setMultFactor(const double val); + void setAddFactor(const double val); + void setVisibility(bool visible); + +private: + SoRotationDragger* dragger = nullptr; + SoRotationDraggerContainer* draggerContainer = nullptr; + SoFieldSensor translationSensor; + LinearGizmo* linearGizmo = nullptr; + bool automaticOrientation = false; + QMetaObject::Connection quantityChangedConnection; + + void draggingStarted(); + void draggingFinished(); + void draggingContinued(); + static void translationSensorCB(void* data, SoSensor* sensor); + + using inherited = Gizmo; +}; + +class GuiExport DirectedRotationGizmo: public RotationGizmo +{ +public: + DirectedRotationGizmo(QuantitySpinBox* property); + + SoInteractionKit* initDragger() override; + + void flipArrow(); + +private: + using inherited = RotationGizmo; +}; + +class GuiExport RadialGizmo: public RotationGizmo +{ +public: + RadialGizmo(QuantitySpinBox* property); + + SoInteractionKit* initDragger() override; + + void setRadius(float radius); + void flipArrow(); + +private: + using inherited = RotationGizmo; +}; + +class GuiExport GizmoContainer: public SoBaseKit +{ + SO_KIT_HEADER(GizmoContainer); + SO_KIT_CATALOG_ENTRY_HEADER(annotation); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); + SO_KIT_CATALOG_ENTRY_HEADER(toggleSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(geometry); + +public: + static void initClass(); + GizmoContainer(); + ~GizmoContainer() override; + + SoSFBool visible; + + void initGizmos(); + void uninitGizmos(); + + template + T* getGizmo(int index) + { + assert(index >= 0 && index < static_cast(gizmos.size()) && "index out of range!"); + return dynamic_cast(gizmos[index]); + } + // This should be called only once after construction + void addGizmos(std::initializer_list gizmos); + void attachViewer(Gui::View3DInventorViewer* viewer, Base::Placement &origin); + void setUpAutoScale(SoCamera* cameraIn); + void calculateScaleAndOrientation(); + + // Checks if the gizmos are enabled in the preferences + static bool isEnabled(); + template + static inline std::unique_ptr createGizmo(std::initializer_list gizmos, T vp) + { + auto ptr = std::make_unique(); + ptr->addGizmos(gizmos); + vp->setGizmoContainer(ptr.get()); + + return ptr; + } + +private: + std::vector gizmos; + SoFieldSensor cameraSensor; + SoFieldSensor cameraPositionSensor; + + void addGizmo(Gizmo* gizmo); + + static void cameraChangeCallback(void* data, SoSensor*); + static void cameraPositionChangeCallback(void* data, SoSensor*); +}; + +} + +#endif /* GUI_GIZMO_H */ diff --git a/src/Gui/Inventor/Draggers/GizmoHelper.h b/src/Gui/Inventor/Draggers/GizmoHelper.h new file mode 100644 index 0000000000..bb77c03d17 --- /dev/null +++ b/src/Gui/Inventor/Draggers/GizmoHelper.h @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 GIZMO_HELPER_H +#define GIZMO_HELPER_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct GuiExport EdgeMidPointProps +{ + Base::Vector3d position; + Base::Vector3d tangent; + double middle; +}; +inline EdgeMidPointProps getEdgeMidPointProps(Part::TopoShape& edge) +{ + std::unique_ptr geom = Part::Geometry::fromShape(edge.getShape()); + auto curve = freecad_cast(geom.get()); + double u1 = curve->getFirstParameter(); + double u2 = curve->getLastParameter(); + double middle = (u1 + u2) / 2; + Base::Vector3d position = curve->pointAtParameter(middle); + + Base::Vector3d tangent; + bool ret = curve->tangent(middle, tangent); + if (ret) { + return {position, tangent, middle}; + } + + Base::Console().error("Failed to calculate tangent for the draggers! Please file a bug report for this."); + return {position, Base::Vector3d{0, 0, 0}, middle}; +} + +inline Base::Vector3d getCentreOfMassFromFace(TopoDS_Face& face) +{ + GProp_GProps massProps; + BRepGProp::SurfaceProperties(face, massProps); + return Base::convertTo(massProps.CentreOfMass()); +} + +inline std::optional> +getFaceNormalFromPointNearEdge(Part::TopoShape& edge, double middle, TopoDS_Face& face) +{ + auto _edge = TopoDS::Edge(edge.getShape()); + + gp_Pnt _inwardPoint; + gp_Dir _normal; + Handle(IntTools_Context) context = new IntTools_Context; + + if (!BOPTools_AlgoTools3D::GetApproxNormalToFaceOnEdge(_edge, face, middle, _inwardPoint, _normal, context)) { + return std::nullopt; + } + + return {{ + Base::convertTo(_inwardPoint), + Base::convertTo(_normal) + }}; +} + +inline Base::Vector3d getFaceNormalFromPoint(Base::Vector3d& point, TopoDS_Face& face) +{ + Handle(Geom_Surface) surf = BRep_Tool::Surface(face); + auto pt = Base::convertTo(point); + + Standard_Real u, v; + GeomAPI_ProjectPointOnSurf proj(pt, surf); + proj.LowerDistanceParameters(u, v); + GeomLProp_SLProps props(surf, u, v, 1, 0.01); + + return Base::convertTo(props.Normal()); +} + +inline std::pair getAdjacentFacesFromEdge(Part::TopoShape& edge, Part::TopoShape& baseShape) +{ + TopTools_IndexedDataMapOfShapeListOfShape edgeToFaceMap; + + TopExp::MapShapesAndAncestors(baseShape.getShape(), TopAbs_EDGE, TopAbs_FACE, edgeToFaceMap); + const TopTools_ListOfShape& faces = edgeToFaceMap.FindFromKey(edge.getShape()); + + assert(faces.Extent() >= 2 && "This is probably a bug so please report it to the issue tracker"); + + TopoDS_Face face1 = TopoDS::Face(faces.First()); + TopoDS_Face face2 = TopoDS::Face(*(++faces.begin())); + + return {face1, face2}; +} + +struct GuiExport DraggerPlacementProps +{ + Base::Vector3d position; + Base::Vector3d dir; + Base::Vector3d tangent; +}; +inline DraggerPlacementProps getDraggerPlacementFromEdgeAndFace(Part::TopoShape& edge, TopoDS_Face& face) +{ + auto [position, tangent, middle] = getEdgeMidPointProps(edge); + + Base::Vector3d normal; + Base::Vector3d inwardPoint; + if (auto ret = getFaceNormalFromPointNearEdge(edge, middle, face)) { + inwardPoint = ret->first; + normal = ret->second; + } else { + // Failed to compute the normal at a point on the face near the edge + // Fallback to the COM and hope for the best + inwardPoint = getCentreOfMassFromFace(face); + normal = getFaceNormalFromPoint(position, face); + } + + auto diff = inwardPoint - position; + + Base::Vector3d dir = normal.Cross(tangent); + // Get correct direction using the com (can we do detect this with a simpler operation?) + if (dir.Dot(diff) < 0) { + dir = -dir; + } + + return {position, dir, tangent}; +} + +inline DraggerPlacementProps getDraggerPlacementFromEdgeAndFace(Part::TopoShape& edge, Part::TopoShape& face) +{ + TopoDS_Face _face = TopoDS::Face(face.getShape()); + return getDraggerPlacementFromEdgeAndFace(edge, _face); +} + +inline std::vector getAdjacentEdgesFromFace(Part::TopoShape& face) +{ + assert(face.getShape().ShapeType() == TopAbs_FACE); + + std::vector edges; + for (TopExp_Explorer explorer(face.getShape(), TopAbs_EDGE); explorer.More(); explorer.Next()) { + edges.push_back(explorer.Current()); + } + + return edges; +} + +inline Base::Vector3d getMidPointFromFace(Part::TopoShape& face) +{ + TopoDS_Shape shape = face.getShape(); + assert(shape.ShapeType() == TopAbs_FACE); + + std::unique_ptr geom = Part::Geometry::fromShape(shape); + Part::GeomSurface* surface = freecad_cast(geom.get()); + + TopoDS_Face _face = TopoDS::Face(shape); + BRepAdaptor_Surface adaptorSurface = BRepAdaptor_Surface(_face, true); + + double u1 = adaptorSurface.FirstUParameter(); + double u2 = adaptorSurface.LastUParameter(); + double v1 = adaptorSurface.FirstVParameter(); + double v2 = adaptorSurface.LastVParameter(); + + double midU = (u1 + u2) / 2; + double midV = (v1 + v2) / 2; + + Base::Vector3d midPoint; + + if (auto sphere = freecad_cast(geom.get())) { + midPoint = sphere->getLocation(); + } else if (auto cone = freecad_cast(geom.get())) { + midPoint = cone->getApex(); + } else if (auto point = surface->point(midU, midV)) { + midPoint = *point; + } else if (auto com = face.centerOfGravity()) { + midPoint = *com; + } else { + midPoint = face.getBoundBox().GetCenter(); + } + + return midPoint; +} + +inline Base::Vector3d getMidPointFromProfile(Part::TopoShape& profile) +{ + TopoDS_Shape shape = profile.getShape(); + + // Can we handle more cases? + if (shape.ShapeType() == TopAbs_FACE) { + return getMidPointFromFace(profile); + } + + Base::Vector3d midPoint; + profile.getCenterOfGravity(midPoint); + + return midPoint; +} + +#endif /* GIZMO_HELPER_H */ diff --git a/src/Gui/Inventor/Draggers/SoLinearDragger.cpp b/src/Gui/Inventor/Draggers/SoLinearDragger.cpp index 6a24e747ad..fd4f3aac37 100644 --- a/src/Gui/Inventor/Draggers/SoLinearDragger.cpp +++ b/src/Gui/Inventor/Draggers/SoLinearDragger.cpp @@ -21,124 +21,30 @@ ***************************************************************************/ #include "PreCompiled.h" + #ifndef _PreComp_ #include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include -#include -#include #include #include #endif -#include - #include "SoLinearDragger.h" -#include "MainWindow.h" -#include "Utilities.h" +#include +#include +#include +#include +#include -#include -#include +#include "SoLinearDraggerGeometry.h" using namespace Gui; -SO_KIT_SOURCE(SoLinearGeometryKit) - -void SoLinearGeometryKit::initClass() -{ - SO_KIT_INIT_CLASS(SoLinearGeometryKit, SoBaseKit, "BaseKit"); -} - -SoLinearGeometryKit::SoLinearGeometryKit() -{ - SO_KIT_CONSTRUCTOR(SoLinearGeometryKit); - - SO_KIT_ADD_FIELD(tipPosition, (0.0, 0.0, 0.0)); - - SO_KIT_INIT_INSTANCE(); -} - -SO_KIT_SOURCE(SoArrowGeometry) - -void SoArrowGeometry::initClass() -{ - SO_KIT_INIT_CLASS(SoArrowGeometry, SoLinearGeometryKit, "LinearGeometryKit"); -} - -SoArrowGeometry::SoArrowGeometry() -{ - SO_KIT_CONSTRUCTOR(SoArrowGeometry); - SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, this, "", false); - SO_KIT_ADD_CATALOG_ENTRY(arrowBody, SoCylinder, false, this, "", true); - SO_KIT_ADD_CATALOG_ENTRY(arrowTip, SoCone, false, this, "", true); - - SO_KIT_ADD_CATALOG_ENTRY(_arrowBodyTranslation, SoTranslation, false, this, arrowBody, false); - SO_KIT_ADD_CATALOG_ENTRY(_arrowTipTranslation, SoTranslation, false, this, arrowTip, false); - - SO_KIT_ADD_FIELD(coneBottomRadius, (0.8f)); - SO_KIT_ADD_FIELD(coneHeight, (2.5f)); - SO_KIT_ADD_FIELD(cylinderHeight, (10.0f)); - SO_KIT_ADD_FIELD(cylinderRadius, (0.1f)); - - SO_KIT_INIT_INSTANCE(); - - auto arrowBody = SO_GET_ANY_PART(this, "arrowBody", SoCylinder); - arrowBody->height.connectFrom(&cylinderHeight); - arrowBody->radius.connectFrom(&cylinderRadius); - - auto arrowTip = SO_GET_ANY_PART(this, "arrowTip", SoCone); - arrowTip->height.connectFrom(&coneHeight); - arrowTip->bottomRadius.connectFrom(&coneBottomRadius); - - auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); - lightModel->model = SoLightModel::BASE_COLOR; - - // forces the notify method to get called so that the initial translations and tipPostion are set - cylinderHeight.touch(); -} - -void SoArrowGeometry::notify(SoNotList* notList) -{ - assert(notList); - SoField* lastField = notList->getLastField(); - - if (lastField == &cylinderHeight) { - auto translation = SO_GET_ANY_PART(this, "_arrowBodyTranslation", SoTranslation); - translation->translation = SbVec3f(0, cylinderHeight.getValue() / 2.0f, 0); - } - - if (lastField == &coneHeight || lastField == &cylinderHeight) { - auto translation = SO_GET_ANY_PART(this, "_arrowTipTranslation", SoTranslation); - translation->translation = SbVec3f(0, (cylinderHeight.getValue() + coneHeight.getValue()) / 2.0f, 0); - - tipPosition = {0, cylinderHeight.getValue() + 1.5f * coneHeight.getValue(), 0}; - } -} - SO_KIT_SOURCE(SoLinearDragger) void SoLinearDragger::initClass() @@ -154,6 +60,10 @@ SoLinearDragger::SoLinearDragger() this->ref(); #endif + SO_KIT_ADD_CATALOG_ENTRY(baseGeomSwitch, SoToggleSwitch, true, topSeparator, motionMatrix, true); + SO_KIT_ADD_CATALOG_ABSTRACT_ENTRY(baseGeom, SoLinearGeometryBaseKit, SoArrowBase, true, baseGeomSwitch, "", true); + + FC_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, geomSeparator); FC_ADD_CATALOG_ENTRY(activeSwitch, SoToggleSwitch, geomSeparator); FC_ADD_CATALOG_ENTRY(secondaryColor, SoBaseColor, activeSwitch); FC_ADD_CATALOG_ENTRY(labelSwitch, SoToggleSwitch, geomSeparator); @@ -165,12 +75,16 @@ SoLinearDragger::SoLinearDragger() SO_KIT_ADD_FIELD(translationIncrement, (1.0)); SO_KIT_ADD_FIELD(translationIncrementCount, (0)); SO_KIT_ADD_FIELD(autoScaleResult, (1.0)); + SO_KIT_ADD_FIELD(color, (1, 0, 0)); SO_KIT_ADD_FIELD(activeColor, (1, 1, 0)); SO_KIT_ADD_FIELD(labelVisible, (1)); SO_KIT_ADD_FIELD(geometryScale, (1, 1, 1)); + SO_KIT_ADD_FIELD(active, (false)); + SO_KIT_ADD_FIELD(baseGeomVisible, (false)); SO_KIT_INIT_INSTANCE(); + setPart("baseColor", buildColor()); setPart("labelSeparator", buildLabelGeometry()); setPart("secondaryColor", buildActiveColor()); @@ -192,7 +106,11 @@ SoLinearDragger::SoLinearDragger() this->setUpConnections(TRUE, TRUE); - FC_SET_TOGGLE_SWITCH("activeSwitch", false); + sw = SO_GET_ANY_PART(this, "activeSwitch", SoToggleSwitch); + sw->on.connectFrom(&active); + + sw = SO_GET_ANY_PART(this, "baseGeomSwitch", SoToggleSwitch); + sw->on.connectFrom(&baseGeomVisible); } SoLinearDragger::~SoLinearDragger() @@ -236,6 +154,14 @@ SoBaseColor* SoLinearDragger::buildActiveColor() return color; } +SoBaseColor* SoLinearDragger::buildColor() +{ + auto color = new SoBaseColor; + color->rgb.connectFrom(&this->color); + + return color; +} + void SoLinearDragger::startCB(void*, SoDragger* d) { auto sudoThis = static_cast(d); @@ -286,7 +212,7 @@ void SoLinearDragger::valueChangedCB(void*, SoDragger* d) void SoLinearDragger::dragStart() { - FC_SET_TOGGLE_SWITCH("activeSwitch", true); + active = true; // do an initial projection to eliminate discrepancies // in arrow head pick. we define the arrow in the y+ direction @@ -344,7 +270,7 @@ void SoLinearDragger::drag() void SoLinearDragger::dragFinish() { - FC_SET_TOGGLE_SWITCH("activeSwitch", false); + active = false; } SbBool SoLinearDragger::setUpConnections(SbBool onoff, SbBool doitalways) @@ -398,6 +324,16 @@ SbVec3f SoLinearDragger::roundTranslation(const SbVec3f& vecIn, float incrementI return out; } +void SoLinearDragger::instantiateBaseGeometry() +{ + baseGeomVisible = true; + + auto baseGeom = SO_GET_ANY_PART(this, "baseGeom", SoLinearGeometryBaseKit); + baseGeom->geometryScale.connectFrom(&geometryScale); + baseGeom->translation.connectFrom(&translation); + baseGeom->active.connectFrom(&active); +} + SO_KIT_SOURCE(SoLinearDraggerContainer) void SoLinearDraggerContainer::initClass() @@ -415,7 +351,6 @@ SoLinearDraggerContainer::SoLinearDraggerContainer() #endif FC_ADD_CATALOG_ENTRY(draggerSwitch, SoToggleSwitch, geomSeparator); - FC_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, draggerSwitch); FC_ADD_CATALOG_ENTRY(transform, SoTransform, draggerSwitch); FC_ADD_CATALOG_ENTRY(dragger, SoLinearDragger, draggerSwitch); @@ -426,19 +361,12 @@ SoLinearDraggerContainer::SoLinearDraggerContainer() SO_KIT_INIT_INSTANCE(); - setPart("baseColor", buildColor()); setPart("transform", buildTransform()); auto sw = SO_GET_ANY_PART(this, "draggerSwitch", SoToggleSwitch); sw->on.connectFrom(&visible); -} -SoBaseColor* SoLinearDraggerContainer::buildColor() -{ - auto color = new SoBaseColor; - color->rgb.connectFrom(&this->color); - - return color; + getDragger()->color.connectFrom(&color); } SoTransform* SoLinearDraggerContainer::buildTransform() { @@ -454,13 +382,20 @@ SoLinearDragger* SoLinearDraggerContainer::getDragger() return SO_GET_PART(this, "dragger", SoLinearDragger); } -void Gui::SoLinearDraggerContainer::setPointerDirection(const Base::Vector3d& dir) +SbVec3f SoLinearDraggerContainer::getPointerDirection() { // This is the direction along which the SoLinearDragger points in it local space - Base::Vector3d draggerDir{0, 1, 0}; - Base::Vector3d axis = draggerDir.Cross(dir).Normalize(); - double ang = draggerDir.GetAngleOriented(dir, axis); + SbVec3f draggerDir = SO_GET_ANY_PART(this, "arrow", SoLinearGeometryKit)->tipPosition.getValue(); + rotation.getValue().multVec(draggerDir, draggerDir); - SbRotation rot{Base::convertTo(axis), static_cast(ang)}; + return draggerDir; +} + +void SoLinearDraggerContainer::setPointerDirection(const SbVec3f& dir) +{ + // This is the direction from the origin to the tip of the dragger + SbVec3f draggerDir = SO_GET_ANY_PART(this, "arrow", SoLinearGeometryKit)->tipPosition.getValue(); + + SbRotation rot{draggerDir, dir}; rotation.setValue(rot); } diff --git a/src/Gui/Inventor/Draggers/SoLinearDragger.h b/src/Gui/Inventor/Draggers/SoLinearDragger.h index a51ec390c0..ab26d3f66d 100644 --- a/src/Gui/Inventor/Draggers/SoLinearDragger.h +++ b/src/Gui/Inventor/Draggers/SoLinearDragger.h @@ -32,67 +32,14 @@ #include #include #include -#include -#include -class SoCamera; -class SoSwitch; +#include + class SoBaseColor; class SoTransform; -class SoCalculator; namespace Gui { -class SoLinearGeometryKit: public SoBaseKit -{ - SO_KIT_HEADER(SoLinearGeometryKit); - -public: - static void initClass(); - - SoSFVec3f tipPosition; - -protected: - SoLinearGeometryKit(); - ~SoLinearGeometryKit() override = default; - -private: - using inherited = SoBaseKit; -}; - -/*! - * @brief Arrow geometry - * - * A class to contain the geometry for SoLinearDragger - */ -class SoArrowGeometry: public SoLinearGeometryKit -{ - SO_KIT_HEADER(SoArrowGeometry); - SO_KIT_CATALOG_ENTRY_HEADER(lightModel); - SO_KIT_CATALOG_ENTRY_HEADER(arrowBody); - SO_KIT_CATALOG_ENTRY_HEADER(arrowTip); - - SO_KIT_CATALOG_ENTRY_HEADER(_arrowBodyTranslation); - SO_KIT_CATALOG_ENTRY_HEADER(_arrowTipTranslation); - -public: - static void initClass(); - SoArrowGeometry(); - - SoSFFloat coneBottomRadius; - SoSFFloat coneHeight; - SoSFFloat cylinderHeight; - SoSFFloat cylinderRadius; - -protected: - ~SoArrowGeometry() override = default; - - void notify(SoNotList* notList) override; - -private: - using inherited = SoLinearGeometryKit; -}; - /*! @brief Translation Dragger. * * used for translating along axis. Set the @@ -101,9 +48,12 @@ private: * 'translationIncrement' for a full double * precision vector scalar. */ -class SoLinearDragger : public SoDragger +class GuiExport SoLinearDragger : public SoDragger { SO_KIT_HEADER(SoLinearDragger); + SO_KIT_CATALOG_ENTRY_HEADER(baseGeomSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(baseGeom); + SO_KIT_CATALOG_ENTRY_HEADER(baseColor); SO_KIT_CATALOG_ENTRY_HEADER(activeSwitch); SO_KIT_CATALOG_ENTRY_HEADER(secondaryColor); SO_KIT_CATALOG_ENTRY_HEADER(labelSwitch); @@ -120,9 +70,14 @@ public: SoSFDouble translationIncrement; //!< set from outside and used for rounding. SoSFInt32 translationIncrementCount; //!< number of steps. used from outside. SoSFFloat autoScaleResult; //!< set from parent dragger. + SoSFColor color; //!< colour of the dragger SoSFColor activeColor; //!< colour of the dragger while being dragged SoSFBool labelVisible; //!< controls the visibility of the dragger label SoSFVec3f geometryScale; //!< the scale of the dragger geometry + SoSFBool active; //!< set when the dragger is being dragged + SoSFBool baseGeomVisible; //!< toggles if the dragger has a base geometry or not + + void instantiateBaseGeometry(); protected: ~SoLinearDragger() override; @@ -146,15 +101,15 @@ private: SoSeparator* buildLabelGeometry(); SoBaseColor* buildActiveColor(); + SoBaseColor* buildColor(); using inherited = SoDragger; }; -class SoLinearDraggerContainer: public SoInteractionKit +class GuiExport SoLinearDraggerContainer: public SoInteractionKit { SO_KIT_HEADER(SoLinearDraggerContainer); SO_KIT_CATALOG_ENTRY_HEADER(draggerSwitch); - SO_KIT_CATALOG_ENTRY_HEADER(baseColor); SO_KIT_CATALOG_ENTRY_HEADER(transform); SO_KIT_CATALOG_ENTRY_HEADER(dragger); @@ -167,12 +122,12 @@ public: SoSFVec3f translation; SoSFBool visible; - void setPointerDirection(const Base::Vector3d& dir); + SbVec3f getPointerDirection(); + void setPointerDirection(const SbVec3f& dir); SoLinearDragger* getDragger(); private: - SoBaseColor* buildColor(); SoTransform* buildTransform(); using inherited = SoInteractionKit; diff --git a/src/Gui/Inventor/Draggers/SoLinearDraggerGeometry.cpp b/src/Gui/Inventor/Draggers/SoLinearDraggerGeometry.cpp new file mode 100644 index 0000000000..ec6fda42fb --- /dev/null +++ b/src/Gui/Inventor/Draggers/SoLinearDraggerGeometry.cpp @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#endif + +#include "SoLinearDraggerGeometry.h" + +using namespace Gui; + +SO_KIT_SOURCE(SoLinearGeometryKit) + +void SoLinearGeometryKit::initClass() +{ + SO_KIT_INIT_CLASS(SoLinearGeometryKit, SoBaseKit, "BaseKit"); +} + +SoLinearGeometryKit::SoLinearGeometryKit() +{ + SO_KIT_CONSTRUCTOR(SoLinearGeometryKit); + + SO_KIT_ADD_FIELD(tipPosition, (0.0, 0.0, 0.0)); + + SO_KIT_INIT_INSTANCE(); +} + +SO_KIT_SOURCE(SoArrowGeometry) + +void SoArrowGeometry::initClass() +{ + SO_KIT_INIT_CLASS(SoArrowGeometry, SoLinearGeometryKit, "LinearGeometryKit"); +} + +SoArrowGeometry::SoArrowGeometry() +{ + SO_KIT_CONSTRUCTOR(SoArrowGeometry); + SO_KIT_ADD_CATALOG_ENTRY(separator, SoSeparator, false, this, "", false); + SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(arrowBody, SoCylinder, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(arrowTip, SoCone, false, separator, "", true); + + SO_KIT_ADD_CATALOG_ENTRY(_arrowBodyTranslation, SoTranslation, false, separator, arrowBody, false); + SO_KIT_ADD_CATALOG_ENTRY(_arrowTipTranslation, SoTranslation, false, separator, arrowTip, false); + + SO_KIT_ADD_FIELD(coneBottomRadius, (0.8f)); + SO_KIT_ADD_FIELD(coneHeight, (2.5f)); + SO_KIT_ADD_FIELD(cylinderHeight, (10.0f)); + SO_KIT_ADD_FIELD(cylinderRadius, (0.1f)); + + SO_KIT_INIT_INSTANCE(); + + auto arrowBody = SO_GET_ANY_PART(this, "arrowBody", SoCylinder); + arrowBody->height.connectFrom(&cylinderHeight); + arrowBody->radius.connectFrom(&cylinderRadius); + + auto arrowTip = SO_GET_ANY_PART(this, "arrowTip", SoCone); + arrowTip->height.connectFrom(&coneHeight); + arrowTip->bottomRadius.connectFrom(&coneBottomRadius); + + auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); + lightModel->model = SoLightModel::BASE_COLOR; + + auto pickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + pickStyle->style = SoPickStyle::SHAPE_ON_TOP; + + // forces the notify method to get called so that the initial translations and tipPostion are set + cylinderHeight.touch(); +} + +void SoArrowGeometry::notify(SoNotList* notList) +{ + SoField* lastField = notList->getLastField(); + + if (lastField == &cylinderHeight) { + auto translation = SO_GET_ANY_PART(this, "_arrowBodyTranslation", SoTranslation); + translation->translation = SbVec3f(0, cylinderHeight.getValue() / 2.0f, 0); + } + + if (lastField == &coneHeight || lastField == &cylinderHeight) { + auto translation = SO_GET_ANY_PART(this, "_arrowTipTranslation", SoTranslation); + translation->translation = SbVec3f(0, (cylinderHeight.getValue() + coneHeight.getValue()) / 2.0f, 0); + + tipPosition = {0, cylinderHeight.getValue() + 1.5f * coneHeight.getValue(), 0}; + } +} + +SO_KIT_SOURCE(SoLinearGeometryBaseKit) + +void SoLinearGeometryBaseKit::initClass() +{ + SO_KIT_INIT_CLASS(SoLinearGeometryBaseKit, SoBaseKit, "BaseKit"); +} + +SoLinearGeometryBaseKit::SoLinearGeometryBaseKit() +{ + SO_KIT_CONSTRUCTOR(SoLinearGeometryBaseKit); + + SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0)); + SO_KIT_ADD_FIELD(geometryScale, (1.0, 1.0, 1.0)); + SO_KIT_ADD_FIELD(active, (false)); + + SO_KIT_INIT_INSTANCE(); +} + +SO_KIT_SOURCE(SoArrowBase) + +void SoArrowBase::initClass() +{ + SO_KIT_INIT_CLASS(SoArrowBase, SoLinearGeometryBaseKit, "LinearGeometryBaseKit"); +} + +SoArrowBase::SoArrowBase() +{ + SO_KIT_CONSTRUCTOR(SoArrowBase); + SO_KIT_ADD_CATALOG_ENTRY(separator, SoSeparator, false, this, "", false); + SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(cylinder, SoCylinder, false, separator, "", true); + + SO_KIT_ADD_CATALOG_ENTRY(_cylinderTranslation, SoTranslation, false, separator, cylinder, false); + + SO_KIT_ADD_FIELD(cylinderHeight, (1.0)); + SO_KIT_ADD_FIELD(cylinderRadius, (0.15)); + SO_KIT_ADD_FIELD(color, (0.214, 0.560, 0.930)); + + SO_KIT_INIT_INSTANCE(); + + auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); + lightModel->model = SoLightModel::BASE_COLOR; + + auto pickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + pickStyle->style = SoPickStyle::UNPICKABLE; + + // We don't want to change the color of the base geometry when the dragger is + // dragged so the active field from SoLinearGeometryBaseKit is unused + // If that is desired then just add a toggle switch with a colour after the + // baseColor node in the graph and it will work as expected + auto baseColor = SO_GET_ANY_PART(this, "baseColor", SoBaseColor); + baseColor->rgb.connectFrom(&color); + + // Forces the cylinder dimensions to be computed + cylinderHeight.touch(); + cylinderRadius.touch(); +} + +void SoArrowBase::notify(SoNotList* notList) +{ + SoField* lastField = notList->getLastField(); + + if (lastField == &cylinderHeight || lastField == &translation) { + auto cylinder = SO_GET_ANY_PART(this, "cylinder", SoCylinder); + cylinder->height = cylinderHeight.getValue() * translation.getValue()[1]; + + auto cylinderTranslation = SO_GET_ANY_PART(this, "_cylinderTranslation", SoTranslation); + cylinderTranslation->translation = {0, cylinder->height.getValue() / 2, 0 }; + } else if (lastField == &cylinderRadius || lastField == &geometryScale) { + auto cylinder = SO_GET_ANY_PART(this, "cylinder", SoCylinder); + assert(geometryScale.getValue()[0] == geometryScale.getValue()[1] + && geometryScale.getValue()[1] == geometryScale.getValue()[2] + && "Camera scale should be equal along all three axes"); + cylinder->radius = cylinderRadius.getValue() * geometryScale.getValue()[0]; + } +} diff --git a/src/Gui/Inventor/Draggers/SoLinearDraggerGeometry.h b/src/Gui/Inventor/Draggers/SoLinearDraggerGeometry.h new file mode 100644 index 0000000000..1770bb3a8a --- /dev/null +++ b/src/Gui/Inventor/Draggers/SoLinearDraggerGeometry.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 GUI_LINEAR_DRAGGER_GEOMETRY_H +#define GUI_LINEAR_DRAGGER_GEOMETRY_H + +#include +#include +#include +#include +#include + +#include + +#include + +namespace Gui +{ + +class GuiExport SoLinearGeometryKit: public SoBaseKit +{ + SO_KIT_HEADER(SoLinearGeometryKit); + +public: + static void initClass(); + + SoSFVec3f tipPosition; + +protected: + SoLinearGeometryKit(); + ~SoLinearGeometryKit() override = default; + +private: + using inherited = SoBaseKit; +}; + +class GuiExport SoArrowGeometry: public SoLinearGeometryKit +{ + SO_KIT_HEADER(SoArrowGeometry); + SO_KIT_CATALOG_ENTRY_HEADER(separator); + SO_KIT_CATALOG_ENTRY_HEADER(lightModel); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); + SO_KIT_CATALOG_ENTRY_HEADER(arrowBody); + SO_KIT_CATALOG_ENTRY_HEADER(arrowTip); + + SO_KIT_CATALOG_ENTRY_HEADER(_arrowBodyTranslation); + SO_KIT_CATALOG_ENTRY_HEADER(_arrowTipTranslation); + +public: + static void initClass(); + SoArrowGeometry(); + + SoSFFloat coneBottomRadius; + SoSFFloat coneHeight; + SoSFFloat cylinderHeight; + SoSFFloat cylinderRadius; + +protected: + ~SoArrowGeometry() override = default; + + void notify(SoNotList* notList) override; + +private: + using inherited = SoLinearGeometryKit; +}; + +class GuiExport SoLinearGeometryBaseKit: public SoBaseKit +{ + SO_KIT_HEADER(SoLinearGeometryBaseKit); + +public: + static void initClass(); + + SoSFVec3f translation; //!< set from the parent dragger + SoSFVec3f geometryScale; //!< set from the parent dragger + SoSFBool active; //!< set from the parent dragger + +protected: + SoLinearGeometryBaseKit(); + ~SoLinearGeometryBaseKit() override = default; + +private: + using inherited = SoBaseKit; +}; + +class GuiExport SoArrowBase: public SoLinearGeometryBaseKit +{ + SO_KIT_HEADER(SoArrowBase); + SO_KIT_CATALOG_ENTRY_HEADER(separator); + SO_KIT_CATALOG_ENTRY_HEADER(lightModel); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); + SO_KIT_CATALOG_ENTRY_HEADER(baseColor); + SO_KIT_CATALOG_ENTRY_HEADER(cylinder); + + SO_KIT_CATALOG_ENTRY_HEADER(_cylinderTranslation); + +public: + static void initClass(); + SoArrowBase(); + + SoSFFloat cylinderHeight; + SoSFFloat cylinderRadius; + SoSFColor color; + +protected: + ~SoArrowBase() override = default; + + void notify(SoNotList* notList) override; + +private: + using inherited = SoLinearGeometryBaseKit; +}; + +} + +#endif /* GUI_LINEAR_DRAGGER_GEOMETRY_H */ diff --git a/src/Gui/Inventor/Draggers/SoRotationDragger.cpp b/src/Gui/Inventor/Draggers/SoRotationDragger.cpp index c07c8fad7d..426ebf6aa6 100644 --- a/src/Gui/Inventor/Draggers/SoRotationDragger.cpp +++ b/src/Gui/Inventor/Draggers/SoRotationDragger.cpp @@ -26,126 +26,25 @@ #include #include -#include #include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include +#include #endif -#include - #include "SoRotationDragger.h" -#include "MainWindow.h" -#include "Utilities.h" +#include +#include +#include +#include -#include -#include +#include "SoRotationDraggerGeometry.h" using namespace Gui; -SO_KIT_SOURCE(SoRotatorGeometryKit) - -void SoRotatorGeometryKit::initClass() -{ - SO_KIT_INIT_CLASS(SoRotatorGeometryKit, SoBaseKit, "BaseKit"); -} - -SoRotatorGeometryKit::SoRotatorGeometryKit() -{ - SO_KIT_CONSTRUCTOR(SoRotatorGeometryKit); - - SO_KIT_ADD_FIELD(pivotPosition, (0.0, 0.0, 0.0)); - - SO_KIT_INIT_INSTANCE(); -} - -SO_KIT_SOURCE(SoRotatorGeometry) - -void SoRotatorGeometry::initClass() -{ - SO_KIT_INIT_CLASS(SoRotatorGeometry, SoRotatorGeometryKit, "RotatorGeometryKit"); -} - -SoRotatorGeometry::SoRotatorGeometry() -{ - SO_KIT_CONSTRUCTOR(SoRotatorGeometry); - SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, this, "", false); - SO_KIT_ADD_CATALOG_ENTRY(drawStyle, SoDrawStyle, false, this, "", false); - SO_KIT_ADD_CATALOG_ENTRY(arcCoords, SoCoordinate3, false, this, "", true); - SO_KIT_ADD_CATALOG_ENTRY(arc, SoLineSet, false, this, "", true); - SO_KIT_ADD_CATALOG_ENTRY(rotorPivot, SoSphere, false, this, "", true); - - SO_KIT_ADD_CATALOG_ENTRY(_rotorPivotTranslation, SoTranslation, false, this, rotorPivot, false); - - SO_KIT_ADD_FIELD(arcRadius, (8.0f)); - SO_KIT_ADD_FIELD(arcAngle, (std::numbers::pi_v / 2.0f)); - SO_KIT_ADD_FIELD(sphereRadius, (0.8f)); - SO_KIT_ADD_FIELD(arcThickness, (4.0f)); - - SO_KIT_INIT_INSTANCE(); - - auto rotorPivot = SO_GET_ANY_PART(this, "rotorPivot", SoSphere); - rotorPivot->radius.connectFrom(&sphereRadius); - - auto drawStyle = SO_GET_ANY_PART(this, "drawStyle", SoDrawStyle); - drawStyle->lineWidth.connectFrom(&arcThickness); - - auto translation = SO_GET_ANY_PART(this, "_rotorPivotTranslation", SoTranslation); - pivotPosition.connectFrom(&translation->translation); - - auto arc = SO_GET_ANY_PART(this, "arc", SoLineSet); - arc->numVertices = segments + 1; - - auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); - lightModel->model = SoLightModel::BASE_COLOR; - - // forces the notify method to get called so that the initial translations and other values are set - arcRadius.touch(); -} - -void SoRotatorGeometry::notify(SoNotList* notList) -{ - assert(notList); - SoField* lastField = notList->getLastField(); - - if (lastField == &arcRadius || lastField == &arcAngle) { - float angle = arcAngle.getValue(); - float radius = arcRadius.getValue(); - - auto coordinates = SO_GET_ANY_PART(this, "arcCoords", SoCoordinate3); - float angleIncrement = angle / static_cast(segments); - SbRotation rotation(SbVec3f(0.0, 0.0, 1.0), angleIncrement); - SbVec3f point(radius, 0.0, 0.0); - for (int index = 0; index <= segments; ++index) { - coordinates->point.set1Value(index, point); - rotation.multVec(point, point); - } - - auto translation = SO_GET_ANY_PART(this, "_rotorPivotTranslation", SoTranslation); - translation->translation = {radius * cos(angle / 2.0f), radius * sin(angle / 2.0f), 0}; - } -} - SO_KIT_SOURCE(SoRotationDragger) void SoRotationDragger::initClass() @@ -160,6 +59,10 @@ SoRotationDragger::SoRotationDragger() this->ref(); #endif + SO_KIT_ADD_CATALOG_ENTRY(baseGeomSwitch, SoToggleSwitch, true, topSeparator, motionMatrix, true); + SO_KIT_ADD_CATALOG_ABSTRACT_ENTRY(baseGeom, SoRotatorGeometryBaseKit, SoRotatorBase, true, baseGeomSwitch, "", true); + + FC_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, geomSeparator); FC_ADD_CATALOG_ENTRY(activeSwitch, SoToggleSwitch, geomSeparator); FC_ADD_CATALOG_ENTRY(secondaryColor, SoBaseColor, activeSwitch); FC_ADD_CATALOG_ENTRY(scale, SoScale, geomSeparator); @@ -168,16 +71,23 @@ SoRotationDragger::SoRotationDragger() SO_KIT_ADD_FIELD(rotation, (SbVec3f(0.0, 0.0, 1.0), 0.0)); SO_KIT_ADD_FIELD(rotationIncrement, (std::numbers::pi / 8.0)); SO_KIT_ADD_FIELD(rotationIncrementCount, (0)); + SO_KIT_ADD_FIELD(color, (1, 0, 0)); SO_KIT_ADD_FIELD(activeColor, (1, 1, 0)); SO_KIT_ADD_FIELD(geometryScale, (1, 1, 1)); + SO_KIT_ADD_FIELD(active, (false)); + SO_KIT_ADD_FIELD(baseGeomVisible, (false)); SO_KIT_INIT_INSTANCE(); + setPart("baseColor", buildColor()); setPart("secondaryColor", buildActiveColor()); auto scale = SO_GET_ANY_PART(this, "scale", SoScale); scale->scaleFactor.connectFrom(&geometryScale); + auto rotator = SO_GET_ANY_PART(this, "rotator", SoRotatorGeometryKit); + rotator->geometryScale.connectFrom(&geometryScale); + this->addStartCallback(&SoRotationDragger::startCB); this->addMotionCallback(&SoRotationDragger::motionCB); this->addFinishCallback(&SoRotationDragger::finishCB); @@ -204,6 +114,14 @@ SoRotationDragger::~SoRotationDragger() removeValueChangedCallback(&SoRotationDragger::valueChangedCB); } +SoBaseColor* SoRotationDragger::buildColor() +{ + auto color = new SoBaseColor; + color->rgb.connectFrom(&this->color); + + return color; +} + SoBaseColor* SoRotationDragger::buildActiveColor() { auto color = new SoBaseColor; @@ -379,6 +297,16 @@ int SoRotationDragger::roundIncrement(const float& radiansIn) return rCount; } +void SoRotationDragger::instantiateBaseGeometry() +{ + baseGeomVisible = true; + + auto baseGeom = SO_GET_ANY_PART(this, "baseGeom", SoRotatorBase); + baseGeom->geometryScale.connectFrom(&geometryScale); + baseGeom->rotation.connectFrom(&rotation); + baseGeom->active.connectFrom(&active); +} + SO_KIT_SOURCE(SoRotationDraggerContainer) void SoRotationDraggerContainer::initClass() @@ -396,7 +324,6 @@ SoRotationDraggerContainer::SoRotationDraggerContainer() #endif FC_ADD_CATALOG_ENTRY(draggerSwitch, SoToggleSwitch, geomSeparator); - FC_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, draggerSwitch); FC_ADD_CATALOG_ENTRY(transform, SoTransform, draggerSwitch); FC_ADD_CATALOG_ENTRY(dragger, SoRotationDragger, draggerSwitch); @@ -407,19 +334,12 @@ SoRotationDraggerContainer::SoRotationDraggerContainer() SO_KIT_INIT_INSTANCE(); - setPart("baseColor", buildColor()); setPart("transform", buildTransform()); auto sw = SO_GET_ANY_PART(this, "draggerSwitch", SoToggleSwitch); sw->on.connectFrom(&visible); -} -SoBaseColor* SoRotationDraggerContainer::buildColor() -{ - auto color = new SoBaseColor; - color->rgb.connectFrom(&this->color); - - return color; + getDragger()->color.connectFrom(&color); } SoTransform* SoRotationDraggerContainer::buildTransform() { @@ -435,16 +355,30 @@ SoRotationDragger* SoRotationDraggerContainer::getDragger() return SO_GET_PART(this, "dragger", SoRotationDragger); } -void Gui::SoRotationDraggerContainer::setPointerDirection(const Base::Vector3d& dir) +SbVec3f SoRotationDraggerContainer::getPointerDirection() +{ + // This is the direction along which the SoLinearDragger points in it local space + SbVec3f draggerDir = SO_GET_ANY_PART(this, "rotator", SoRotatorGeometryKit)->pivotPosition.getValue(); + rotation.getValue().multVec(draggerDir, draggerDir); + + return draggerDir; +} + +void SoRotationDraggerContainer::setPointerDirection(const SbVec3f& dir) { // This is the direction from the origin to the spherical pivot of the rotator - Base::Vector3d draggerDir = Base::convertTo( - SO_GET_ANY_PART(this, "rotator", SoRotatorGeometryKit)->pivotPosition.getValue() - ); + SbVec3f draggerDir = SO_GET_ANY_PART(this, "rotator", SoRotatorGeometryKit)->pivotPosition.getValue(); - Base::Vector3d axis = draggerDir.Cross(dir).Normalize(); - double ang = draggerDir.GetAngleOriented(dir, axis); - - SbRotation rot{Base::convertTo(axis), static_cast(ang)}; + SbRotation rot{draggerDir, dir}; rotation.setValue(rot); } + +void SoRotationDraggerContainer::setArcNormalDirection(const SbVec3f& dir) +{ + SbVec3f currentNormal = {0, 0, 1}; + auto currentRot = rotation.getValue(); + currentRot.multVec(currentNormal, currentNormal); + + SbRotation rot{currentNormal, dir}; + rotation = currentRot * rot; +} diff --git a/src/Gui/Inventor/Draggers/SoRotationDragger.h b/src/Gui/Inventor/Draggers/SoRotationDragger.h index c5051a4c45..27e0fdf60b 100644 --- a/src/Gui/Inventor/Draggers/SoRotationDragger.h +++ b/src/Gui/Inventor/Draggers/SoRotationDragger.h @@ -26,74 +26,19 @@ #include #include #include -#include #include #include -#include -#include #include -#include -#include + +#include class SoTransform; -class SoCalculator; class SoCoordinate3; +class SoBaseColor; +class SoSensor; namespace Gui { -class SoRotatorGeometryKit: public SoBaseKit -{ - SO_KIT_HEADER(SoLinearGeometryKit); - -public: - static void initClass(); - - SoSFVec3f pivotPosition; - -protected: - SoRotatorGeometryKit(); - ~SoRotatorGeometryKit() override = default; - -private: - using inherited = SoBaseKit; -}; - -/*! - * @brief Rotator geometry - * - * A class to contain the geometry for SoRotationDragger - */ -class SoRotatorGeometry: public SoRotatorGeometryKit -{ - SO_KIT_HEADER(SoArrowGeometry); - SO_KIT_CATALOG_ENTRY_HEADER(lightModel); - SO_KIT_CATALOG_ENTRY_HEADER(drawStyle); - SO_KIT_CATALOG_ENTRY_HEADER(arcCoords); - SO_KIT_CATALOG_ENTRY_HEADER(arc); - SO_KIT_CATALOG_ENTRY_HEADER(rotorPivot); - - SO_KIT_CATALOG_ENTRY_HEADER(_rotorPivotTranslation); - -public: - static void initClass(); - SoRotatorGeometry(); - - SoSFFloat arcAngle; //!< in radians - SoSFFloat arcRadius; - SoSFFloat sphereRadius; - SoSFFloat arcThickness; - -protected: - ~SoRotatorGeometry() override = default; - - void notify(SoNotList* notList) override; - -private: - constexpr static int segments = 10; //!< segments of the arc per arcAngle - - using inherited = SoRotatorGeometryKit; -}; - /*! @brief Rotation Dragger. * * used for rotating around an axis. Set the rotation @@ -101,9 +46,12 @@ private: * multiplied with rotationIncrement for full double * precision vector scalar. */ -class SoRotationDragger : public SoDragger +class GuiExport SoRotationDragger : public SoDragger { SO_KIT_HEADER(SoRotationDragger); + SO_KIT_CATALOG_ENTRY_HEADER(baseGeomSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(baseGeom); + SO_KIT_CATALOG_ENTRY_HEADER(baseColor); SO_KIT_CATALOG_ENTRY_HEADER(activeSwitch); SO_KIT_CATALOG_ENTRY_HEADER(secondaryColor); SO_KIT_CATALOG_ENTRY_HEADER(scale); @@ -116,8 +64,13 @@ public: SoSFRotation rotation; //!< set from outside and used from outside for single precision. SoSFDouble rotationIncrement; //!< set from outside and used for rounding. SoSFInt32 rotationIncrementCount; //!< number of steps. used from outside. + SoSFColor color; //!< colour of the dragger SoSFColor activeColor; //!< colour of the dragger while being dragged. SoSFVec3f geometryScale; //!< the scale of the dragger geometry + SoSFBool active; //!< set when the dragger is being dragged + SoSFBool baseGeomVisible; //!< toggles if the dragger has a base geometry or not + + void instantiateBaseGeometry(); protected: ~SoRotationDragger() override; @@ -139,15 +92,15 @@ protected: private: int roundIncrement(const float &radiansIn); SoBaseColor* buildActiveColor(); + SoBaseColor* buildColor(); using inherited = SoDragger; }; -class SoRotationDraggerContainer: public SoInteractionKit +class GuiExport SoRotationDraggerContainer: public SoInteractionKit { SO_KIT_HEADER(SoRotationDraggerContainer); SO_KIT_CATALOG_ENTRY_HEADER(draggerSwitch); - SO_KIT_CATALOG_ENTRY_HEADER(baseColor); SO_KIT_CATALOG_ENTRY_HEADER(transform); SO_KIT_CATALOG_ENTRY_HEADER(dragger); @@ -160,12 +113,13 @@ public: SoSFVec3f translation; SoSFBool visible; - void setPointerDirection(const Base::Vector3d& dir); + SbVec3f getPointerDirection(); + void setPointerDirection(const SbVec3f& dir); + void setArcNormalDirection(const SbVec3f& dir); SoRotationDragger* getDragger(); private: - SoBaseColor* buildColor(); SoTransform* buildTransform(); using inherited = SoInteractionKit; diff --git a/src/Gui/Inventor/Draggers/SoRotationDraggerGeometry.cpp b/src/Gui/Inventor/Draggers/SoRotationDraggerGeometry.cpp new file mode 100644 index 0000000000..8b2122686d --- /dev/null +++ b/src/Gui/Inventor/Draggers/SoRotationDraggerGeometry.cpp @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "SoRotationDraggerGeometry.h" + +#include + +using namespace Gui; + +SO_KIT_SOURCE(SoRotatorGeometryKit) + +void SoRotatorGeometryKit::initClass() +{ + SO_KIT_INIT_CLASS(SoRotatorGeometryKit, SoBaseKit, "BaseKit"); +} + +SoRotatorGeometryKit::SoRotatorGeometryKit() +{ + SO_KIT_CONSTRUCTOR(SoRotatorGeometryKit); + + SO_KIT_ADD_FIELD(pivotPosition, (0.0, 0.0, 0.0)); + SO_KIT_ADD_FIELD(geometryScale, (1.0, 1.0, 1.0)); + + SO_KIT_INIT_INSTANCE(); +} + +SO_KIT_SOURCE(SoRotatorGeometry) + +void SoRotatorGeometry::initClass() +{ + SO_KIT_INIT_CLASS(SoRotatorGeometry, SoRotatorGeometryKit, "RotatorGeometryKit"); +} + +SoRotatorGeometry::SoRotatorGeometry() +{ + SO_KIT_CONSTRUCTOR(SoRotatorGeometry); + SO_KIT_ADD_CATALOG_ENTRY(separator, SoSeparator, false, this, "", false); + SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(drawStyle, SoDrawStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(arcCoords, SoCoordinate3, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(arc, SoLineSet, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(pivotSeparator, SoSeparator, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(rotorPivot, SoSphere, false, pivotSeparator, "", true); + + SO_KIT_ADD_CATALOG_ENTRY(_rotorPivotTranslation, SoTranslation, false, pivotSeparator, rotorPivot, false); + + SO_KIT_ADD_FIELD(arcRadius, (8.0f)); + SO_KIT_ADD_FIELD(arcAngle, (std::numbers::pi_v / 2.0f)); + SO_KIT_ADD_FIELD(sphereRadius, (0.8f)); + SO_KIT_ADD_FIELD(arcThickness, (4.0f)); + + SO_KIT_INIT_INSTANCE(); + + auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); + lightModel->model = SoLightModel::BASE_COLOR; + + auto pickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + pickStyle->style = SoPickStyle::SHAPE_ON_TOP; + + auto rotorPivot = SO_GET_ANY_PART(this, "rotorPivot", SoSphere); + rotorPivot->radius.connectFrom(&sphereRadius); + + auto drawStyle = SO_GET_ANY_PART(this, "drawStyle", SoDrawStyle); + drawStyle->lineWidth.connectFrom(&arcThickness); + + auto translation = SO_GET_ANY_PART(this, "_rotorPivotTranslation", SoTranslation); + pivotPosition.connectFrom(&translation->translation); + + auto arc = SO_GET_ANY_PART(this, "arc", SoLineSet); + arc->numVertices = segments + 1; + + // forces the notify method to get called so that the initial translations and other values are set + arcRadius.touch(); +} + +void SoRotatorGeometry::notify(SoNotList* notList) +{ + SoField* lastField = notList->getLastField(); + + if (lastField == &arcRadius || lastField == &arcAngle) { + float angle = arcAngle.getValue(); + float radius = arcRadius.getValue(); + + auto coordinates = SO_GET_ANY_PART(this, "arcCoords", SoCoordinate3); + float angleIncrement = angle / static_cast(segments); + SbRotation rotation(SbVec3f(0.0, 0.0, 1.0), angleIncrement); + SbVec3f point(radius, 0.0, 0.0); + for (int index = 0; index <= segments; ++index) { + coordinates->point.set1Value(index, point); + rotation.multVec(point, point); + } + + auto translation = SO_GET_ANY_PART(this, "_rotorPivotTranslation", SoTranslation); + translation->translation = {radius * cos(angle / 2.0f), radius * sin(angle / 2.0f), 0}; + } +} + +SO_KIT_SOURCE(SoRotatorGeometry2) + +void SoRotatorGeometry2::initClass() +{ + SO_KIT_INIT_CLASS(SoRotatorGeometry2, SoRotatorGeometry, "SoRotatorGeometry"); +} + +SoRotatorGeometry2::SoRotatorGeometry2() +{ + SO_KIT_CONSTRUCTOR(SoRotatorGeometry2); + + SO_KIT_ADD_CATALOG_ENTRY(leftArrowSwitch, SoToggleSwitch, true, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(leftArrowSeparator, SoSeparator, true, leftArrowSwitch, "", true); + SO_KIT_ADD_CATALOG_ENTRY(leftArrowTransform, SoTransform, true, leftArrowSeparator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(leftArrow, SoCone, true, leftArrowSeparator, "", true); + + SO_KIT_ADD_CATALOG_ENTRY(rightArrowSwitch, SoToggleSwitch, true, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(rightArrowSeparator, SoSeparator, true, rightArrowSwitch, "", true); + SO_KIT_ADD_CATALOG_ENTRY(rightArrowTransform, SoTransform, true, rightArrowSeparator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(rightArrow, SoCone, true, rightArrowSeparator, "", true); + + SO_KIT_ADD_FIELD(coneBottomRadius, (0.8)); + SO_KIT_ADD_FIELD(coneHeight, (2.5)); + SO_KIT_ADD_FIELD(leftArrowVisible, (true)); + SO_KIT_ADD_FIELD(rightArrowVisible, (true)); + + SO_KIT_INIT_INSTANCE(); + + auto arrowHead = SO_GET_ANY_PART(this, "leftArrow", SoCone); + arrowHead->bottomRadius.connectFrom(&coneBottomRadius); + arrowHead->height.connectFrom(&coneHeight); + + arrowHead = SO_GET_ANY_PART(this, "rightArrow", SoCone); + arrowHead->bottomRadius.connectFrom(&coneBottomRadius); + arrowHead->height.connectFrom(&coneHeight); + + auto sw = SO_GET_ANY_PART(this, "leftArrowSwitch", SoToggleSwitch); + sw->on.connectFrom(&leftArrowVisible); + + sw = SO_GET_ANY_PART(this, "rightArrowSwitch", SoToggleSwitch); + sw->on.connectFrom(&rightArrowVisible); + + // forces the notify method to get called so that the initial transforms are set + arcAngle.touch(); +} + +void SoRotatorGeometry2::notify(SoNotList* notList) +{ + SoField* lastField = notList->getLastField(); + + inherited::notify(notList); + + if (lastField == &arcAngle || lastField == &arcRadius) { + auto angle = arcAngle.getValue(); + auto radius = arcRadius.getValue(); + + auto transform = SO_GET_ANY_PART(this, "leftArrowTransform", SoTransform); + transform->translation = SbVec3f{radius, 0, 0}; + transform->rotation = SbRotation({0, 0, 1}, std::numbers::pi_v); + + transform = SO_GET_ANY_PART(this, "rightArrowTransform", SoTransform); + transform->translation = SbVec3f{radius * std::cos(angle), radius * std::sin(angle), 0}; + transform->rotation = SbRotation({0, 0, 1}, angle); + } +} + +void SoRotatorGeometry2::toggleArrowVisibility() +{ + leftArrowVisible = !leftArrowVisible.getValue(); + rightArrowVisible = !rightArrowVisible.getValue(); +} + +SO_KIT_SOURCE(SoRotatorArrow) + +void SoRotatorArrow::initClass() +{ + SO_KIT_INIT_CLASS(SoRotatorArrow, SoRotatorGeometryKit, "RotatorGeometryKit"); +} + +SoRotatorArrow::SoRotatorArrow() +{ + SO_KIT_CONSTRUCTOR(SoRotatorArrow); + SO_KIT_ADD_CATALOG_ENTRY(separator, SoSeparator, false, this, "", false); + SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(arrowBody, SoCylinder, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(arrowTip, SoCone, false, separator, "", true); + + SO_KIT_ADD_CATALOG_ENTRY(_arrowTransform, SoTransform, false, separator, arrowBody, false); + SO_KIT_ADD_CATALOG_ENTRY(_arrowBodyTranslation, SoTranslation, false, separator, arrowBody, false); + SO_KIT_ADD_CATALOG_ENTRY(_arrowTipTranslation, SoTranslation, false, separator, arrowTip, false); + + SO_KIT_ADD_FIELD(coneBottomRadius, (0.8)); + SO_KIT_ADD_FIELD(coneHeight, (2.5)); + SO_KIT_ADD_FIELD(cylinderHeight, (3.0)); + SO_KIT_ADD_FIELD(cylinderRadius, (0.2)); + SO_KIT_ADD_FIELD(radius, (8.0)); + SO_KIT_ADD_FIELD(minRadius, (8.0)); + + SO_KIT_INIT_INSTANCE(); + + auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); + lightModel->model = SoLightModel::BASE_COLOR; + + auto pickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + pickStyle->style = SoPickStyle::SHAPE_ON_TOP; + + auto arrowBody = SO_GET_ANY_PART(this, "arrowBody", SoCylinder); + arrowBody->height.connectFrom(&cylinderHeight); + arrowBody->radius.connectFrom(&cylinderRadius); + + auto arrowTip = SO_GET_ANY_PART(this, "arrowTip", SoCone); + arrowTip->height.connectFrom(&coneHeight); + arrowTip->bottomRadius.connectFrom(&coneBottomRadius); + + auto transform = SO_GET_ANY_PART(this, "_arrowTransform", SoTransform); + transform->rotation = SbRotation({1, 0, 0}, std::numbers::pi) * SbRotation({0, 0, 1}, std::numbers::pi/2); + + // forces the notify method to get called so that the initial dimensions are set + cylinderHeight.touch(); + radius.touch(); +} + +void SoRotatorArrow::flipArrow() +{ + auto transform = SO_GET_ANY_PART(this, "_arrowTransform", SoTransform); + transform->rotation = transform->rotation.getValue() * SbRotation({0, 0, 1}, std::numbers::pi); +} + +void SoRotatorArrow::notify(SoNotList* notList) +{ + SoField* lastField = notList->getLastField(); + + if (lastField == &cylinderHeight) { + auto translation = SO_GET_ANY_PART(this, "_arrowBodyTranslation", SoTranslation); + translation->translation = SbVec3f(0, cylinderHeight.getValue() / 2.0f, 0); + } else if (lastField == &radius || lastField == &minRadius ||lastField == &geometryScale) { + auto transform = SO_GET_ANY_PART(this, "_arrowTransform", SoTransform); + // The geometry nodes are scaled from the parent dragger but in this case the translation itself shouldn't be scaled + pivotPosition = transform->translation = SbVec3f( + 0, + std::max(minRadius.getValue(), radius.getValue() / geometryScale.getValue()[1]), + 0 + ); + } + + if (lastField == &coneHeight || lastField == &cylinderHeight) { + auto translation = SO_GET_ANY_PART(this, "_arrowTipTranslation", SoTranslation); + translation->translation = SbVec3f(0, (cylinderHeight.getValue() + coneHeight.getValue()) / 2.0f, 0); + } +} + +SO_KIT_SOURCE(SoRotatorGeometryBaseKit) + +void SoRotatorGeometryBaseKit::initClass() +{ + SO_KIT_INIT_CLASS(SoRotatorGeometryBaseKit, SoBaseKit, "BaseKit"); +} + +SoRotatorGeometryBaseKit::SoRotatorGeometryBaseKit() +{ + SO_KIT_CONSTRUCTOR(SoRotatorGeometryBaseKit); + + SO_KIT_ADD_FIELD(rotation, ({0.0, 0.0, 0.0}, 0.0)); + SO_KIT_ADD_FIELD(geometryScale, (1.0, 1.0, 1.0)); + SO_KIT_ADD_FIELD(active, (false)); + + SO_KIT_INIT_INSTANCE(); +} + +SO_KIT_SOURCE(SoRotatorBase) + +void SoRotatorBase::initClass() +{ + SO_KIT_INIT_CLASS(SoRotatorBase, SoRotatorGeometryBaseKit, "RotatorGeometryBaseKit"); +} + +SoRotatorBase::SoRotatorBase() +{ + SO_KIT_CONSTRUCTOR(SoRotatorBase); + SO_KIT_ADD_CATALOG_ENTRY(separator, SoSeparator, false, this, "", false); + SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(pickStyle, SoPickStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(drawStyle, SoDrawStyle, false, separator, "", false); + SO_KIT_ADD_CATALOG_ENTRY(arcCoords, SoCoordinate3, false, separator, "", true); + SO_KIT_ADD_CATALOG_ENTRY(arc, SoLineSet, false, separator, "", true); + + SO_KIT_ADD_FIELD(minArcRadius, (8.0)); + SO_KIT_ADD_FIELD(arcRadius, (8.0)); + SO_KIT_ADD_FIELD(arcThickness, (2.0)); + SO_KIT_ADD_FIELD(color, (0.214, 0.560, 0.930)); + + SO_KIT_INIT_INSTANCE(); + + auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); + lightModel->model = SoLightModel::BASE_COLOR; + + auto pickStyle = SO_GET_ANY_PART(this, "pickStyle", SoPickStyle); + pickStyle->style = SoPickStyle::UNPICKABLE; + + // We don't want to change the color of the base geometry when the dragger is + // dragged so the active field from SoRotatorGeometryBaseKit is unused + // If that is desired then just add a toggle switch with a colour after the + // baseColor node in the graph and it will work as expected + auto baseColor = SO_GET_ANY_PART(this, "baseColor", SoBaseColor); + baseColor->rgb.connectFrom(&color); + + auto drawStyle = SO_GET_ANY_PART(this, "drawStyle", SoDrawStyle); + drawStyle->lineWidth.connectFrom(&arcThickness); + + auto arc = SO_GET_ANY_PART(this, "arc", SoLineSet); + arc->numVertices = segments + 3; + + // forces the notify method to get called so that the initial translations and other values are set + arcRadius.touch(); +} + +void SoRotatorBase::notify(SoNotList* notList) +{ + SoField* lastField = notList->getLastField(); + + if (lastField == &arcRadius || lastField == &minArcRadius || lastField == &rotation || lastField == &geometryScale) { + SbVec3f axis; + float angle; + rotation.getValue(axis, angle); + float radius = std::max(minArcRadius.getValue() * geometryScale.getValue()[0], arcRadius.getValue()); + + auto coordinates = SO_GET_ANY_PART(this, "arcCoords", SoCoordinate3); + float angleIncrement = angle / static_cast(segments); + SbRotation rotation(axis, angleIncrement); + SbVec3f point(0.0, radius, 0.0); + coordinates->point.set1Value(0, {0.0, 0.0, 0.0}); + for (int index = 0; index <= segments; ++index) { + coordinates->point.set1Value(index + 1, point); + rotation.multVec(point, point); + } + coordinates->point.set1Value(segments + 2, {0.0, 0.0, 0.0}); + } +} diff --git a/src/Gui/Inventor/Draggers/SoRotationDraggerGeometry.h b/src/Gui/Inventor/Draggers/SoRotationDraggerGeometry.h new file mode 100644 index 0000000000..0d13b42912 --- /dev/null +++ b/src/Gui/Inventor/Draggers/SoRotationDraggerGeometry.h @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Sayantan Deb * + * * + * 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 GUI_ROTATION_DRAGGER_GEOMETRY_H +#define GUI_ROTATION_DRAGGER_GEOMETRY_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Gui +{ + +class GuiExport SoRotatorGeometryKit: public SoBaseKit +{ + SO_KIT_HEADER(SoLinearGeometryKit); + +public: + static void initClass(); + + SoSFVec3f pivotPosition; + SoSFVec3f geometryScale; + +protected: + SoRotatorGeometryKit(); + ~SoRotatorGeometryKit() override = default; + +private: + using inherited = SoBaseKit; +}; + +class GuiExport SoRotatorGeometry: public SoRotatorGeometryKit +{ + SO_KIT_HEADER(SoRotatorGeometry); + SO_KIT_CATALOG_ENTRY_HEADER(separator); + SO_KIT_CATALOG_ENTRY_HEADER(lightModel); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); + SO_KIT_CATALOG_ENTRY_HEADER(drawStyle); + SO_KIT_CATALOG_ENTRY_HEADER(arcCoords); + SO_KIT_CATALOG_ENTRY_HEADER(arc); + SO_KIT_CATALOG_ENTRY_HEADER(pivotSeparator); + SO_KIT_CATALOG_ENTRY_HEADER(rotorPivot); + + SO_KIT_CATALOG_ENTRY_HEADER(_rotorPivotTranslation); + +public: + static void initClass(); + SoRotatorGeometry(); + + SoSFFloat arcAngle; //!< in radians + SoSFFloat arcRadius; + SoSFFloat sphereRadius; + SoSFFloat arcThickness; + +protected: + ~SoRotatorGeometry() override = default; + + void notify(SoNotList* notList) override; + +private: + constexpr static int segments = 10; //!< segments of the arc per arcAngle + + using inherited = SoRotatorGeometryKit; +}; + +class GuiExport SoRotatorGeometry2: public SoRotatorGeometry +{ + SO_KIT_HEADER(SoRotatorGeometry2); + SO_KIT_CATALOG_ENTRY_HEADER(leftArrowSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(leftArrowSeparator); + SO_KIT_CATALOG_ENTRY_HEADER(leftArrowTransform); + SO_KIT_CATALOG_ENTRY_HEADER(leftArrow); + SO_KIT_CATALOG_ENTRY_HEADER(rightArrowSwitch); + SO_KIT_CATALOG_ENTRY_HEADER(rightArrowSeparator); + SO_KIT_CATALOG_ENTRY_HEADER(rightArrowTransform); + SO_KIT_CATALOG_ENTRY_HEADER(rightArrow); + +public: + static void initClass(); + SoRotatorGeometry2(); + + SoSFFloat coneBottomRadius; + SoSFFloat coneHeight; + SoSFBool leftArrowVisible; + SoSFBool rightArrowVisible; + + void toggleArrowVisibility(); + +protected: + ~SoRotatorGeometry2() override = default; + + void notify(SoNotList* notList) override; + +private: + constexpr static int segments = 10; //!< segments of the arc per arcAngle + + using inherited = SoRotatorGeometry; +}; + +class GuiExport SoRotatorArrow: public SoRotatorGeometryKit +{ + SO_KIT_HEADER(SoRotatorArrow); + SO_KIT_CATALOG_ENTRY_HEADER(separator); + SO_KIT_CATALOG_ENTRY_HEADER(lightModel); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); + SO_KIT_CATALOG_ENTRY_HEADER(arrowBody); + SO_KIT_CATALOG_ENTRY_HEADER(arrowTip); + + SO_KIT_CATALOG_ENTRY_HEADER(_arrowTransform); + SO_KIT_CATALOG_ENTRY_HEADER(_arrowBodyTranslation); + SO_KIT_CATALOG_ENTRY_HEADER(_arrowTipTranslation); + +public: + static void initClass(); + SoRotatorArrow(); + + SoSFFloat coneBottomRadius; + SoSFFloat coneHeight; + SoSFFloat cylinderHeight; + SoSFFloat cylinderRadius; + SoSFFloat radius; + SoSFFloat minRadius; + + void flipArrow(); + +protected: + ~SoRotatorArrow() override = default; + + void notify(SoNotList* notList) override; + +private: + constexpr static int segments = 10; //!< segments of the arc per arcAngle + + using inherited = SoRotatorGeometryKit; +}; + +class GuiExport SoRotatorGeometryBaseKit: public SoBaseKit +{ + SO_KIT_HEADER(SoRotatorGeometryBaseKit); + +public: + static void initClass(); + + SoSFRotation rotation; //!< set from the parent dragger + SoSFVec3f geometryScale; //!< set from the parent dragger + SoSFBool active; //!< set from the parent dragger + +protected: + SoRotatorGeometryBaseKit(); + ~SoRotatorGeometryBaseKit() override = default; + +private: + using inherited = SoBaseKit; +}; + +class GuiExport SoRotatorBase: public SoRotatorGeometryBaseKit +{ + SO_KIT_HEADER(SoArrowBase); + SO_KIT_CATALOG_ENTRY_HEADER(separator); + SO_KIT_CATALOG_ENTRY_HEADER(lightModel); + SO_KIT_CATALOG_ENTRY_HEADER(pickStyle); + SO_KIT_CATALOG_ENTRY_HEADER(baseColor); + SO_KIT_CATALOG_ENTRY_HEADER(drawStyle); + SO_KIT_CATALOG_ENTRY_HEADER(arcCoords); + SO_KIT_CATALOG_ENTRY_HEADER(arc); + +public: + static void initClass(); + SoRotatorBase(); + + SoSFFloat arcRadius; + SoSFFloat minArcRadius; + SoSFFloat arcThickness; + SoSFColor color; + +protected: + ~SoRotatorBase() override = default; + + void notify(SoNotList* notList) override; + +private: + constexpr static int segments = 50; //!< segments of the arc per arcAngle + + using inherited = SoRotatorGeometryBaseKit; +}; + +} + +#endif /* GUI_ROTATION_DRAGGER_GEOMETRY_H */ diff --git a/src/Gui/Inventor/Draggers/SoTransformDragger.cpp b/src/Gui/Inventor/Draggers/SoTransformDragger.cpp index c5ead6cfbf..65bd8c5830 100644 --- a/src/Gui/Inventor/Draggers/SoTransformDragger.cpp +++ b/src/Gui/Inventor/Draggers/SoTransformDragger.cpp @@ -787,12 +787,12 @@ bool SoTransformDragger::isShownRotationZ() void SoTransformDragger::setupTranslationDraggers() { - setupTranslationDragger("xTranslatorDragger", &xAxisLabel, translationIncrementCountX, SbVec3d(1.0, 0.0, 0.0)); - setupTranslationDragger("yTranslatorDragger", &yAxisLabel, translationIncrementCountY, SbVec3d(0.0, 1.0, 0.0)); - setupTranslationDragger("zTranslatorDragger", &zAxisLabel, translationIncrementCountZ, SbVec3d(0.0, 0.0, 1.0)); + setupTranslationDragger("xTranslatorDragger", &xAxisLabel, translationIncrementCountX, SbVec3f(1.0, 0.0, 0.0)); + setupTranslationDragger("yTranslatorDragger", &yAxisLabel, translationIncrementCountY, SbVec3f(0.0, 1.0, 0.0)); + setupTranslationDragger("zTranslatorDragger", &zAxisLabel, translationIncrementCountZ, SbVec3f(0.0, 0.0, 1.0)); } -void SoTransformDragger::setupTranslationDragger(const std::string& name, SoSFString* label, SoSFInt32& incrementCount, const SbVec3d& rotDir) +void SoTransformDragger::setupTranslationDragger(const std::string& name, SoSFString* label, SoSFInt32& incrementCount, const SbVec3f& rotDir) { SoLinearDraggerContainer* draggerContainer = SO_GET_ANY_PART(this, name.c_str(), SoLinearDraggerContainer); SoLinearDragger* dragger = draggerContainer->getDragger(); @@ -802,7 +802,7 @@ void SoTransformDragger::setupTranslationDragger(const std::string& name, SoSFSt dragger->label.connectFrom(label); incrementCount.connectFrom(&dragger->translationIncrementCount); - draggerContainer->setPointerDirection(Base::convertTo(rotDir)); + draggerContainer->setPointerDirection(rotDir); } void SoTransformDragger::setupRotationDraggers() diff --git a/src/Gui/Inventor/Draggers/SoTransformDragger.h b/src/Gui/Inventor/Draggers/SoTransformDragger.h index cde2038aa0..d88ad0df85 100644 --- a/src/Gui/Inventor/Draggers/SoTransformDragger.h +++ b/src/Gui/Inventor/Draggers/SoTransformDragger.h @@ -186,7 +186,7 @@ private: void updateAxisScale(); void setupTranslationDraggers(); - void setupTranslationDragger(const std::string& name, SoSFString* label, SoSFInt32& incrementCount, const SbVec3d& rotDir); + void setupTranslationDragger(const std::string& name, SoSFString* label, SoSFInt32& incrementCount, const SbVec3f& rotDir); void setupRotationDraggers(); void setupRotationDragger(const std::string& name, SoSFInt32& incrementCount); diff --git a/src/Gui/Inventor/So3DAnnotation.h b/src/Gui/Inventor/So3DAnnotation.h index 700227be01..0e2a62c42a 100644 --- a/src/Gui/Inventor/So3DAnnotation.h +++ b/src/Gui/Inventor/So3DAnnotation.h @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace Gui @@ -103,4 +104,4 @@ protected: } // namespace Gui -#endif // GUI_SO3DANNOTATION_H \ No newline at end of file +#endif // GUI_SO3DANNOTATION_H diff --git a/src/Gui/Inventor/SoToggleSwitch.h b/src/Gui/Inventor/SoToggleSwitch.h index fe6033c98d..b6384082d2 100644 --- a/src/Gui/Inventor/SoToggleSwitch.h +++ b/src/Gui/Inventor/SoToggleSwitch.h @@ -27,6 +27,7 @@ #include #include +#include /** * A switch that can be used to show or hide all child nodes diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index c732b3b696..875d881bbf 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -52,7 +52,10 @@ #include "Flag.h" #include "Inventor/Draggers/SoTransformDragger.h" #include "Inventor/Draggers/SoLinearDragger.h" +#include "Inventor/Draggers/SoLinearDraggerGeometry.h" #include "Inventor/Draggers/SoRotationDragger.h" +#include "Inventor/Draggers/SoRotationDraggerGeometry.h" +#include "Inventor/Draggers/Gizmo.h" #include "Navigation/GestureNavigationStyle.h" #include "Navigation/NavigationStyle.h" #include "Navigation/SiemensNXNavigationStyle.h" @@ -149,9 +152,16 @@ void Gui::SoFCDB::init() SoTransformDragger ::initClass(); SoLinearGeometryKit ::initClass(); SoArrowGeometry ::initClass(); + SoLinearGeometryBaseKit ::initClass(); + SoArrowBase ::initClass(); SoRotatorGeometryKit ::initClass(); SoRotatorGeometry ::initClass(); + SoRotatorGeometry2 ::initClass(); + SoRotatorArrow ::initClass(); + SoRotatorGeometryBaseKit ::initClass(); + SoRotatorBase ::initClass(); SoToggleSwitch ::initClass(); + GizmoContainer ::initClass(); SmSwitchboard ::initClass(); SoFCSeparator ::initClass(); SoFCSelectionRoot ::initClass(); diff --git a/src/Gui/TaskTransform.cpp b/src/Gui/TaskTransform.cpp index c6567b4271..f6f010372d 100644 --- a/src/Gui/TaskTransform.cpp +++ b/src/Gui/TaskTransform.cpp @@ -83,11 +83,11 @@ void alignGridLayoutColumns(const std::list& layouts, unsigned col TaskTransform::TaskTransform(Gui::ViewProviderDragger* vp, Gui::SoTransformDragger* dragger, QWidget* parent, - App::SubObjectPlacementProvider* subObjectPlacemenProvider, + App::SubObjectPlacementProvider* subObjectPlacementProvider, App::CenterOfMassProvider* centerOfMassProvider) : TaskBox(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"), tr("Transform"), false, parent) , vp(vp) - , subObjectPlacementProvider(subObjectPlacemenProvider) + , subObjectPlacementProvider(subObjectPlacementProvider) , centerOfMassProvider(centerOfMassProvider) , dragger(dragger) , ui(new Ui_TaskTransformDialog) diff --git a/src/Gui/TaskTransform.h b/src/Gui/TaskTransform.h index 7e06bbb3b6..429ded08c0 100644 --- a/src/Gui/TaskTransform.h +++ b/src/Gui/TaskTransform.h @@ -33,11 +33,6 @@ class SoDragger; -namespace Attacher -{ - class AttachEngine; -} - namespace Gui { class QuantitySpinBox; diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp index 0f884832f9..04ffe7b683 100644 --- a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp +++ b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp @@ -62,6 +62,8 @@ void DlgSettingsGeneral::saveSettings() ui->checkSketchBaseRefine->onSave(); ui->checkObjectNaming->onSave(); ui->checkAllowCompoundBody->onSave(); + ui->enableGizmos->onSave(); + ui->delayedGizmoUpdate->onSave(); ui->comboDefaultProfileTypeForHole->onSave(); ui->checkShowFinalPreview->onSave(); ui->checkShowTransparentPreview->onSave(); @@ -75,6 +77,8 @@ void DlgSettingsGeneral::loadSettings() ui->checkSketchBaseRefine->onRestore(); ui->checkObjectNaming->onRestore(); ui->checkAllowCompoundBody->onRestore(); + ui->enableGizmos->onRestore(); + ui->delayedGizmoUpdate->onRestore(); ui->comboDefaultProfileTypeForHole->onRestore(); ui->checkShowFinalPreview->onRestore(); ui->checkShowTransparentPreview->onRestore(); diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.ui b/src/Mod/Part/Gui/DlgSettingsGeneral.ui index a5b605dbf5..d51a00e338 100644 --- a/src/Mod/Part/Gui/DlgSettingsGeneral.ui +++ b/src/Mod/Part/Gui/DlgSettingsGeneral.ui @@ -7,7 +7,7 @@ 0 0 550 - 450 + 513 @@ -167,14 +167,14 @@ - - - Switch to task panel when entering Part Design workbench - - + + Automatically switch to the task panel when the Part Design workbench is activated - + + Switch to task panel when entering Part Design workbench + + true @@ -232,6 +232,73 @@ + + + + true + + + Experimental + + + false + + + + + + These settings are experimental and may result in decreased stability, problems and undefined behaviors + + + true + + + + + + + Allow multiple solids in Part Design bodies by default + + + AllowCompoundDefault + + + Mod/PartDesign + + + + + + + Show interactive draggers during Part Design tasks + + + true + + + EnableGizmos + + + Mod/PartDesign + + + + + + + Disable recompute while dragging + + + DelayedGizmoUpdate + + + Mod/PartDesign + + + + + + diff --git a/src/Mod/Part/Gui/TaskThickness.cpp b/src/Mod/Part/Gui/TaskThickness.cpp index 618fe3b923..f59b0fff45 100644 --- a/src/Mod/Part/Gui/TaskThickness.cpp +++ b/src/Mod/Part/Gui/TaskThickness.cpp @@ -39,9 +39,12 @@ #include #include #include +#include +#include #include #include "TaskThickness.h" +#include "ViewProvider.h" #include "ui_TaskOffset.h" @@ -112,6 +115,8 @@ ThicknessWidget::ThicknessWidget(Part::Thickness* thickness, QWidget* parent) d->ui.selfIntersection->setChecked(selfint); d->ui.spinOffset->bind(d->thickness->Value); + + setupGizmos(); } ThicknessWidget::~ThicknessWidget() @@ -196,6 +201,10 @@ void ThicknessWidget::onFacesButtonToggled(bool on) Gui::Application::Instance->hideViewProvider(d->thickness); Gui::Selection().clearSelection(); Gui::Selection().addSelectionGate(new Private::FaceSelection(d->thickness->Faces.getValue())); + + if (gizmoContainer) { + gizmoContainer->visible = false; + } } else { QList c = this->findChildren(); @@ -220,6 +229,11 @@ void ThicknessWidget::onFacesButtonToggled(bool on) Gui::Application::Instance->hideViewProvider(d->thickness->Faces.getValue()); if (d->ui.updateView->isChecked()) d->thickness->getDocument()->recomputeFeature(d->thickness); + + if (gizmoContainer) { + gizmoContainer->visible = true; + setGizmoPositions(); + } } } @@ -294,6 +308,56 @@ void ThicknessWidget::changeEvent(QEvent *e) } } +void ThicknessWidget::setupGizmos() +{ + if (!Gui::GizmoContainer::isEnabled()) { + return; + } + + linearGizmo = new Gui::LinearGizmo(d->ui.spinOffset); + + auto vp = Base::freecad_cast( + Gui::Application::Instance->getViewProvider(d->thickness) + ); + if (!vp) { + delete linearGizmo; + return; + } + gizmoContainer = Gui::GizmoContainer::createGizmo({linearGizmo}, vp); + + setGizmoPositions(); +} + +void ThicknessWidget::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + Part::Thickness* thickness = getObject(); + auto base = thickness->getTopoShape(thickness->Faces.getValue(), Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); + auto faces = thickness->Faces.getSubValues(true); + + if (faces.size() == 0) { + gizmoContainer->visible = false; + } + + Part::TopoShape face = base.getSubTopoShape(faces[0].c_str()); + if (face.getShape().ShapeType() == TopAbs_FACE) { + auto edges = getAdjacentEdgesFromFace(face); + assert(edges.size() != 0 && "A face without any edges? Please file a bug report"); + DraggerPlacementProps props = getDraggerPlacementFromEdgeAndFace(edges[0], face); + + // The part thickness operation by default goes creates towards outside + // so -props.dir is taken + linearGizmo->Gizmo::setDraggerPlacement(props.position, -props.dir); + + gizmoContainer->visible = true; + + return; + } +} + /* TRANSLATOR PartGui::TaskThickness */ diff --git a/src/Mod/Part/Gui/TaskThickness.h b/src/Mod/Part/Gui/TaskThickness.h index bd3468f946..ca8c9ce15a 100644 --- a/src/Mod/Part/Gui/TaskThickness.h +++ b/src/Mod/Part/Gui/TaskThickness.h @@ -27,6 +27,11 @@ #include #include +namespace Gui { +class LinearGizmo; +class GizmoContainer; +} + namespace Part { class Thickness; } namespace PartGui { @@ -55,6 +60,11 @@ private: private: void changeEvent(QEvent *e) override; + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* linearGizmo = nullptr; + void setupGizmos(); + void setGizmoPositions(); + private: class Private; Private* d; diff --git a/src/Mod/Part/Gui/ViewProvider.cpp b/src/Mod/Part/Gui/ViewProvider.cpp index 81b2862b50..9069388700 100644 --- a/src/Mod/Part/Gui/ViewProvider.cpp +++ b/src/Mod/Part/Gui/ViewProvider.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "ViewProvider.h" @@ -109,6 +111,25 @@ void ViewProviderPart::applyTransparency(float transparency, std::vectorsetUpAutoScale(viewer->getSoRenderManager()->getCamera()); + + auto originPlacement = App::GeoFeature::getGlobalPlacement(getObject()) + * getObjectPlacement().inverse(); + gizmoContainer->attachViewer(viewer, originPlacement); + } +} + +void ViewProviderPart::setGizmoContainer(Gui::GizmoContainer* gizmoContainer) +{ + assert(gizmoContainer); + this->gizmoContainer = gizmoContainer; +} + // ---------------------------------------------------------------------------- void ViewProviderShapeBuilder::buildNodes(const App::Property* prop, std::vector& nodes) const diff --git a/src/Mod/Part/Gui/ViewProvider.h b/src/Mod/Part/Gui/ViewProvider.h index 1c12497eea..f38a0a87b2 100644 --- a/src/Mod/Part/Gui/ViewProvider.h +++ b/src/Mod/Part/Gui/ViewProvider.h @@ -24,11 +24,18 @@ #ifndef PARTGUI_VIEWPROVIDERPART_H #define PARTGUI_VIEWPROVIDERPART_H +#include + #include #include class SoSeparator; +namespace Gui +{ +class GizmoContainer; +} + namespace Part { struct ShapeHistory; } namespace PartGui { @@ -53,6 +60,8 @@ public: ~ViewProviderPart() override; bool doubleClicked() override; + void setGizmoContainer(Gui::GizmoContainer* gizmoContainer); + protected: void applyColor(const Part::ShapeHistory& hist, const std::vector& colBase, @@ -62,6 +71,11 @@ protected: std::vector& colBool); void applyTransparency(float transparency, std::vector& colors); void applyTransparency(float transparency, std::vector& colors); + + void setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum) override; + +private: + Gui::GizmoContainer* gizmoContainer = nullptr; }; } // namespace PartGui diff --git a/src/Mod/PartDesign/App/FeatureHole.h b/src/Mod/PartDesign/App/FeatureHole.h index 5c7c018bc2..579023a992 100644 --- a/src/Mod/PartDesign/App/FeatureHole.h +++ b/src/Mod/PartDesign/App/FeatureHole.h @@ -124,6 +124,9 @@ public: bool isDynamicCounterbore(const std::string &thread, const std::string &holeCutType); bool isDynamicCountersink(const std::string &thread, const std::string &holeCutType); + Base::Vector3d guessNormalDirection(const TopoShape& profileshape) const; + TopoShape findHoles(std::vector &holes, const TopoShape& profileshape, const TopoDS_Shape& protohole) const; + protected: void onChanged(const App::Property* prop) override; void setupObject() override; @@ -254,9 +257,7 @@ private: void findClosestDesignation(); void rotateToNormal(const gp_Dir& helixAxis, const gp_Dir& normalAxis, TopoDS_Shape& helixShape) const; gp_Vec computePerpendicular(const gp_Vec&) const; - Base::Vector3d guessNormalDirection(const TopoShape& profileshape) const; TopoDS_Shape makeThread(const gp_Vec&, const gp_Vec&, double); - TopoShape findHoles(std::vector &holes, const TopoShape& profileshape, const TopoDS_Shape& protohole) const; // helpers for nlohmann json friend void from_json(const nlohmann::json &j, CounterBoreDimension &t); diff --git a/src/Mod/PartDesign/App/FeatureRevolution.cpp b/src/Mod/PartDesign/App/FeatureRevolution.cpp index 352a21a208..5539e78215 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.cpp +++ b/src/Mod/PartDesign/App/FeatureRevolution.cpp @@ -62,6 +62,7 @@ Revolution::Revolution() ADD_PROPERTY_TYPE(Angle2, (60.0), "Revolution", App::Prop_None, "Revolution length in 2nd direction"); ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Revolution", App::Prop_None, "Face where revolution will end"); Angle.setConstraints(&floatAngle); + Angle2.setConstraints(&floatAngle); ADD_PROPERTY_TYPE(ReferenceAxis,(nullptr),"Revolution",(App::Prop_None),"Reference axis of revolution"); } diff --git a/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp b/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp index ca4e27d867..ec7af40633 100644 --- a/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskChamferParameters.cpp @@ -20,7 +20,6 @@ * * ***************************************************************************/ - #include "PreCompiled.h" #ifndef _PreComp_ @@ -36,7 +35,19 @@ #include #include #include +#include +#include +#include +#include #include +#include + +#include +#include +#include +#include +#include +#include #include "ui_TaskChamferParameters.h" #include "TaskChamferParameters.h" @@ -112,6 +123,8 @@ TaskChamferParameters::TaskChamferParameters(ViewProviderDressUp* DressUpView, Q else { hideOnError(); } + + setupGizmos(DressUpView); } void TaskChamferParameters::setUpUI(PartDesign::Chamfer* pcChamfer) @@ -162,6 +175,10 @@ void TaskChamferParameters::onSelectionChanged(const Gui::SelectionChanges& msg) if (selectionMode == refSel) { referenceSelected(msg, ui->listWidgetReferences); } + } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + // TODO: the gizmo position should be only recalculated when the feature associated + // with the gizmo is removed from the list + setGizmoPositions(); } } @@ -188,6 +205,7 @@ void TaskChamferParameters::setButtons(const selectionModes mode) void TaskChamferParameters::onRefDeleted() { TaskDressUpParameters::deleteRef(ui->listWidgetReferences); + setGizmoPositions(); } void TaskChamferParameters::onAddAllEdges() @@ -253,6 +271,8 @@ void TaskChamferParameters::onFlipDirection(bool flip) chamfer->recomputeFeature(); // hide the chamfer if there was a computation error hideOnError(); + + setGizmoPositions(); } } @@ -328,6 +348,90 @@ void TaskChamferParameters::apply() } } +void TaskChamferParameters::setupGizmos(ViewProviderDressUp* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + distanceGizmo = new Gui::LinearGizmo(ui->chamferSize); + secondDistanceGizmo = new Gui::LinearGizmo(ui->chamferSize); + angleGizmo = new Gui::RotationGizmo(ui->chamferAngle); + + connect(ui->chamferType, qOverload(&QComboBox::currentIndexChanged), [this] (int index) { + auto type = static_cast(index); + + switch (type) { + case Part::ChamferType::equalDistance: + secondDistanceGizmo->setVisibility(true); + angleGizmo->setVisibility(false); + + secondDistanceGizmo->setProperty(ui->chamferSize); + secondDistanceGizmo->setDragLength(ui->chamferSize->value().getValue()); + + break; + case Part::ChamferType::twoDistances: + secondDistanceGizmo->setVisibility(true); + angleGizmo->setVisibility(false); + + secondDistanceGizmo->setProperty(ui->chamferSize2); + secondDistanceGizmo->setDragLength(ui->chamferSize2->value().getValue()); + + break; + case Part::ChamferType::distanceAngle: + secondDistanceGizmo->setVisibility(false); + angleGizmo->setVisibility(true); + + } + }); + + gizmoContainer = GizmoContainer::createGizmo({ + distanceGizmo, secondDistanceGizmo, angleGizmo + }, vp); + + setGizmoPositions(); + + ui->chamferType->currentIndexChanged(ui->chamferType->currentIndex()); +} + +void TaskChamferParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + auto chamfer = getObject(); + if (!chamfer) { + gizmoContainer->visible = false; + return; + } + auto baseShape = chamfer->getBaseTopoShape(); + auto shapes = chamfer->getContinuousEdges(baseShape); + + if (shapes.size() == 0) { + gizmoContainer->visible = false; + return; + } + gizmoContainer->visible = true; + + Part::TopoShape edge = shapes[0]; + auto [face1, face2] = getAdjacentFacesFromEdge(edge, baseShape); + + DraggerPlacementProps props = getDraggerPlacementFromEdgeAndFace(edge, face1); + DraggerPlacementProps props2 = getDraggerPlacementFromEdgeAndFace(edge, face2); + if (ui->flipDirection->isChecked()) { + std::swap(props, props2); + } + + distanceGizmo->Gizmo::setDraggerPlacement(props.position, props.dir); + secondDistanceGizmo->Gizmo::setDraggerPlacement(props2.position, props2.dir); + + angleGizmo->placeBelowLinearGizmo(distanceGizmo); + angleGizmo->getDraggerContainer()->setArcNormalDirection(Base::convertTo(-props.dir.Cross(props2.dir))); + // Only show the gizmo if the chamfer type is set to distance and angle + angleGizmo->setVisibility(getType() == 2); +} + //************************************************************************** //************************************************************************** // TaskDialog diff --git a/src/Mod/PartDesign/Gui/TaskChamferParameters.h b/src/Mod/PartDesign/Gui/TaskChamferParameters.h index 0131424bcf..c3d1878f3a 100644 --- a/src/Mod/PartDesign/Gui/TaskChamferParameters.h +++ b/src/Mod/PartDesign/Gui/TaskChamferParameters.h @@ -24,6 +24,8 @@ #ifndef GUI_TASKVIEW_TaskChamferParameters_H #define GUI_TASKVIEW_TaskChamferParameters_H +#include + #include "TaskDressUpParameters.h" #include "ViewProviderChamfer.h" @@ -32,6 +34,12 @@ namespace PartDesign { class Chamfer; } +namespace Gui { +class LinearGizmo; +class RotationalGizmo; +class GizmoContainer; +} + namespace PartDesignGui { class TaskChamferParameters : public TaskDressUpParameters @@ -66,9 +74,16 @@ protected: bool getFlipDirection() const; private: + std::unique_ptr ui; + void setUpUI(PartDesign::Chamfer* pcChamfer); - std::unique_ptr ui; + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* distanceGizmo = nullptr; + Gui::LinearGizmo* secondDistanceGizmo = nullptr; + Gui::RotationGizmo* angleGizmo = nullptr; + void setupGizmos(ViewProviderDressUp* vp); + void setGizmoPositions(); }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp index 2fa0914686..63c22b1915 100644 --- a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include #include diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp index e3fb856bb0..5603fe2d8d 100644 --- a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp @@ -31,6 +31,10 @@ #include #include #include +#include +#include +#include +#include #include #include "ui_TaskPadPocketParameters.h" @@ -65,8 +69,6 @@ TaskExtrudeParameters::TaskExtrudeParameters(ViewProviderExtrude* SketchBasedVie this->groupLayout()->addWidget(proxy); } -TaskExtrudeParameters::~TaskExtrudeParameters() = default; - void TaskExtrudeParameters::setupDialog() { auto createRemoveAction = [this]() -> QAction* { @@ -114,6 +116,8 @@ void TaskExtrudeParameters::setupDialog() this->propReferenceAxis = &(extrude->ReferenceAxis); updateUI(Side::First); + + setupGizmos(); } void TaskExtrudeParameters::setupSideDialog(SideController& side) @@ -476,6 +480,8 @@ void TaskExtrudeParameters::selectedReferenceAxis(const Gui::SelectionChanges& m // update direction combobox fillDirectionCombo(); + + setGizmoPositions(); } } @@ -876,8 +882,7 @@ void TaskExtrudeParameters::onDirectionCBChanged(int num) // to distinguish that this is the direction selection setSelectionMode(SelectReferenceAxis); setDirectionMode(num); - } - else if (auto extrude = getObject()) { + } else if (auto extrude = getObject()) { if (lnk.getValue()) { if (!extrude->getDocument()->isIn(lnk.getValue())) { Base::Console().error("Object was deleted\n"); @@ -893,6 +898,8 @@ void TaskExtrudeParameters::onDirectionCBChanged(int num) extrude->ReferenceAxis.setValue(lnk.getValue(), lnk.getSubValues()); tryRecomputeFeature(); updateDirectionEdits(); + + setGizmoPositions(); } } @@ -901,6 +908,8 @@ void TaskExtrudeParameters::onAlongSketchNormalChanged(bool on) if (auto extrude = getObject()) { extrude->AlongSketchNormal.setValue(on); tryRecomputeFeature(); + + setGizmoPositions(); } } @@ -940,6 +949,7 @@ void TaskExtrudeParameters::onXDirectionEditChanged(double len) // if there was a null vector, the normal vector of the sketch is used. // therefore the vector component edits must be updated updateDirectionEdits(); + setGizmoPositions(); } } @@ -951,6 +961,7 @@ void TaskExtrudeParameters::onYDirectionEditChanged(double len) extrude->Direction.getValue().z); tryRecomputeFeature(); updateDirectionEdits(); + setGizmoPositions(); } } @@ -962,6 +973,7 @@ void TaskExtrudeParameters::onZDirectionEditChanged(double len) len); tryRecomputeFeature(); updateDirectionEdits(); + setGizmoPositions(); } } @@ -1021,6 +1033,8 @@ void TaskExtrudeParameters::onReversedChanged(bool on) // update the direction tryRecomputeFeature(); updateDirectionEdits(); + + setGizmoPositions(); } } @@ -1345,6 +1359,72 @@ void TaskExtrudeParameters::handleLineFaceNameNo(QLineEdit* lineEdit) lineEdit->setPlaceholderText(tr("No face selected")); } +void TaskExtrudeParameters::setupGizmos() +{ + if (GizmoContainer::isEnabled() == false) { + return; + } + + lengthGizmo1 = new Gui::LinearGizmo(ui->lengthEdit); + lengthGizmo2 = new Gui::LinearGizmo(ui->lengthEdit2); + taperAngleGizmo1 = new Gui::RotationGizmo(ui->taperEdit); + taperAngleGizmo2 = new Gui::RotationGizmo(ui->taperEdit2); + + connect(ui->sidesMode, qOverload(&QComboBox::currentIndexChanged), [this] (int) { + setGizmoPositions(); + }); + + gizmoContainer = GizmoContainer::createGizmo({ + lengthGizmo1, lengthGizmo2, + taperAngleGizmo1, taperAngleGizmo2 + }, vp); + + setGizmoPositions(); +} + +void TaskExtrudeParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + auto extrude = getObject(); + PartDesign::TopoShape shape = extrude->getProfileShape(); + Base::Vector3d center = getMidPointFromProfile(shape); + std::string sideType = std::string(extrude->SideType.getValueAsString()); + double dir = extrude->Reversed.getValue()? -1 : 1; + + lengthGizmo1->Gizmo::setDraggerPlacement(center, extrude->Direction.getValue() * dir); + taperAngleGizmo1->placeOverLinearGizmo(lengthGizmo1); + lengthGizmo2->Gizmo::setDraggerPlacement(center, -extrude->Direction.getValue() * dir); + lengthGizmo2->setVisibility( + sideType == "Two sides" + ); + taperAngleGizmo2->placeOverLinearGizmo(lengthGizmo2); + taperAngleGizmo2->setVisibility( + sideType == "Two sides" + ); + + Base::Vector3d padDir = extrude->Direction.getValue().Normalized(); + Base::Vector3d sketchDir = extrude->getProfileNormal().Normalized(); + + double lengthFactor = padDir.Dot(sketchDir); + double multFactor = (sideType == "Symmetric")? 0.5 : 1.0; + + // Important note: This code assumes that nothing other than alongSketchNormal + // and symmetric option influence the multFactor. If some custom gizmos changes + // it then that also should be handled properly here + if (extrude->AlongSketchNormal.getValue()) { + lengthGizmo1->setMultFactor(multFactor/lengthFactor); + lengthGizmo2->setMultFactor(multFactor/lengthFactor); + } else { + lengthGizmo1->setMultFactor(multFactor); + lengthGizmo2->setMultFactor(multFactor); + } + + gizmoContainer->calculateScaleAndOrientation(); +} + TaskDlgExtrudeParameters::TaskDlgExtrudeParameters(PartDesignGui::ViewProviderExtrude* vp) : TaskDlgSketchBasedParameters(vp) {} diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h index 905cb81fe5..7c3937092d 100644 --- a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h @@ -23,6 +23,8 @@ #ifndef GUI_TASKVIEW_TaskExtrudeParameters_H #define GUI_TASKVIEW_TaskExtrudeParameters_H +#include + #include "TaskSketchBasedParameters.h" #include "ViewProviderExtrude.h" @@ -42,6 +44,12 @@ namespace Gui { class PrefQuantitySpinBox; } +namespace Gui { +class LinearGizmo; +class RotationalGizmo; +class GizmoContainer; +} + namespace PartDesign { class ProfileBased; } @@ -86,7 +94,7 @@ public: ToShape, }; - enum SelectionMode { + enum SelectionMode { None, SelectFace, SelectShape, @@ -96,7 +104,7 @@ public: TaskExtrudeParameters(ViewProviderExtrude *ExtrudeView, QWidget *parent, const std::string& pixmapname, const QString& parname); - ~TaskExtrudeParameters() override; + ~TaskExtrudeParameters() override = default; void saveHistory() override; @@ -232,6 +240,14 @@ private: void createSideControllers(); + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* lengthGizmo1 = nullptr; + Gui::LinearGizmo* lengthGizmo2 = nullptr; + Gui::RotationGizmo* taperAngleGizmo1 = nullptr; + Gui::RotationGizmo* taperAngleGizmo2 = nullptr; + void setupGizmos(); + void setGizmoPositions(); + protected: QWidget* proxy; QAction* unselectShapeFaceAction; diff --git a/src/Mod/PartDesign/Gui/TaskFeatureParameters.h b/src/Mod/PartDesign/Gui/TaskFeatureParameters.h index 46c96b0a74..99b31be128 100644 --- a/src/Mod/PartDesign/Gui/TaskFeatureParameters.h +++ b/src/Mod/PartDesign/Gui/TaskFeatureParameters.h @@ -126,8 +126,10 @@ protected: blockUpdate = value; } -private: +protected: PartDesignGui::ViewProvider *vp; + +private: bool blockUpdate; }; diff --git a/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp b/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp index 327e1f2a73..ef647cc52e 100644 --- a/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskFilletParameters.cpp @@ -30,11 +30,16 @@ #endif #include +#include #include #include +#include #include #include #include +#include +#include +#include #include "ui_TaskFilletParameters.h" #include "TaskFilletParameters.h" @@ -103,6 +108,8 @@ TaskFilletParameters::TaskFilletParameters(ViewProviderDressUp* DressUpView, QWi else { hideOnError(); } + + setupGizmos(DressUpView); } void TaskFilletParameters::onSelectionChanged(const Gui::SelectionChanges& msg) @@ -114,6 +121,10 @@ void TaskFilletParameters::onSelectionChanged(const Gui::SelectionChanges& msg) if (selectionMode == refSel) { referenceSelected(msg, ui->listWidgetReferences); } + } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + // TODO: the gizmo position should be only recalculated when the feature associated + // with the gizmo is removed from the list + setGizmoPositions(); } } @@ -140,6 +151,7 @@ void TaskFilletParameters::setButtons(const selectionModes mode) void TaskFilletParameters::onRefDeleted() { TaskDressUpParameters::deleteRef(ui->listWidgetReferences); + setGizmoPositions(); } void TaskFilletParameters::onAddAllEdges() @@ -195,6 +207,59 @@ void TaskFilletParameters::apply() } } +void TaskFilletParameters::setupGizmos(ViewProviderDressUp* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + radiusGizmo = new Gui::LinearGizmo(ui->filletRadius); + radiusGizmo2 = new Gui::LinearGizmo(ui->filletRadius); + + gizmoContainer = GizmoContainer::createGizmo({radiusGizmo, radiusGizmo2}, vp); + + setGizmoPositions(); +} + +void TaskFilletParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + auto fillet = getObject(); + if (!fillet) { + gizmoContainer->visible = false; + return; + } + Part::TopoShape baseShape = fillet->getBaseTopoShape(); + std::vector shapes = fillet->getContinuousEdges(baseShape); + + if (shapes.size() == 0) { + gizmoContainer->visible = false; + return; + } + gizmoContainer->visible = true; + + // Attach the arrow to the first edge + Part::TopoShape edge = shapes[0]; + auto [face1, face2] = getAdjacentFacesFromEdge(edge, baseShape); + + DraggerPlacementProps props1 = getDraggerPlacementFromEdgeAndFace(edge, face1); + radiusGizmo->Gizmo::setDraggerPlacement(props1.position, props1.dir); + + DraggerPlacementProps props2 = getDraggerPlacementFromEdgeAndFace(edge, face2); + radiusGizmo2->Gizmo::setDraggerPlacement(props2.position, props2.dir); + + // The dragger length won't be equal to the radius if the two faces + // are not orthogonal so this correction is needed + double angle = props1.dir.GetAngle(props2.dir); + double correction = 1/std::tan(angle/2); + + radiusGizmo->setMultFactor(correction); + radiusGizmo2->setMultFactor(correction); +} + //************************************************************************** //************************************************************************** // TaskDialog diff --git a/src/Mod/PartDesign/Gui/TaskFilletParameters.h b/src/Mod/PartDesign/Gui/TaskFilletParameters.h index 5fdfe8767f..922bdb3004 100644 --- a/src/Mod/PartDesign/Gui/TaskFilletParameters.h +++ b/src/Mod/PartDesign/Gui/TaskFilletParameters.h @@ -24,11 +24,18 @@ #ifndef GUI_TASKVIEW_TaskFilletParameters_H #define GUI_TASKVIEW_TaskFilletParameters_H +#include + #include "TaskDressUpParameters.h" #include "ViewProviderFillet.h" class Ui_TaskFilletParameters; +namespace Gui { +class LinearGizmo; +class GizmoContainer; +} + namespace PartDesignGui { class TaskFilletParameters : public TaskDressUpParameters @@ -55,6 +62,12 @@ protected: private: std::unique_ptr ui; + + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* radiusGizmo = nullptr; + Gui::LinearGizmo* radiusGizmo2 = nullptr; + void setupGizmos(ViewProviderDressUp* vp); + void setGizmoPositions(); }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp index d57a981e3e..9414087d7e 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp @@ -27,13 +27,17 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include +#include +#include #include #include @@ -74,6 +78,8 @@ TaskHelixParameters::TaskHelixParameters(PartDesignGui::ViewProviderHelix* Helix connectSlots(); setFocus(); showCoordinateAxes(); + + setupGizmos(HelixView); } void TaskHelixParameters::initializeHelix() @@ -526,6 +532,8 @@ void TaskHelixParameters::onAxisChanged(int num) recomputeFeature(); updateStatus(); + + setGizmoPositions(); } catch (const Base::Exception& e) { e.reportException(); @@ -561,6 +569,8 @@ void TaskHelixParameters::onReversedChanged(bool on) propReversed->setValue(on); recomputeFeature(); updateUI(); + + setGizmoPositions(); } } @@ -703,6 +713,46 @@ void TaskHelixParameters::apply() // NOLINT FCMD_OBJ_CMD(tobj, "Reversed = " << (propReversed->getValue() ? 1 : 0)); } +void TaskHelixParameters::setupGizmos(ViewProviderHelix* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + heightGizmo = new Gui::LinearGizmo(ui->height); + + connect(ui->inputMode, qOverload(&QComboBox::currentIndexChanged), [this] (int index) { + bool isPitchTurnsAngle = index == static_cast(HelixMode::pitch_turns_angle); + heightGizmo->setVisibility(!isPitchTurnsAngle); + }); + + gizmoContainer = GizmoContainer::createGizmo({heightGizmo}, vp); + + setGizmoPositions(); + + ui->inputMode->currentIndexChanged(ui->inputMode->currentIndex()); +} + +void TaskHelixParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + auto helix = getObject(); + Part::TopoShape profileShape = helix->getProfileShape(); + double reversed = propReversed->getValue()? -1.0 : 1.0; + auto profileCentre = getMidPointFromProfile(profileShape); + Base::Vector3d axisDir = helix->Axis.getValue() * reversed; + Base::Vector3d basePos = helix->Base.getValue(); + + // Project the centre point of the helix to a plane passing through the com of the profile + // and along the helix axis + Base::Vector3d pos = basePos + axisDir.Dot(profileCentre - basePos) * axisDir; + + heightGizmo->Gizmo::setDraggerPlacement(pos, axisDir); +} + //************************************************************************** //************************************************************************** diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.h b/src/Mod/PartDesign/Gui/TaskHelixParameters.h index 8da0b1fdab..b88231a7ee 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.h @@ -23,6 +23,8 @@ #ifndef GUI_TASKVIEW_TaskHelixParameters_H #define GUI_TASKVIEW_TaskHelixParameters_H +#include + #include "TaskSketchBasedParameters.h" #include "ViewProviderHelix.h" @@ -32,6 +34,8 @@ class Property; } namespace Gui { +class LinearGizmo; +class GizmoContainer; class ViewProvider; } @@ -125,6 +129,11 @@ private: * when adding stuff, and delete when removing stuff. */ std::vector> axesInList; + + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* heightGizmo = nullptr; + void setupGizmos(ViewProviderHelix* vp); + void setGizmoPositions(); }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp index ed5b33de83..0fb44a8267 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp @@ -22,12 +22,18 @@ #include "PreCompiled.h" +#include + #include #include #include #include #include #include +#include +#include +#include "Gui/Inventor/Draggers/SoLinearDragger.h" +#include #include #include #include @@ -257,6 +263,8 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole* HoleView, QWidget* pare // NOLINTEND this->groupLayout()->addWidget(proxy); + + setupGizmos(HoleView); } TaskHoleParameters::~TaskHoleParameters() = default; @@ -397,6 +405,8 @@ void TaskHoleParameters::baseProfileTypeChanged(int index) if (auto hole = getObject()) { hole->BaseProfileType.setValue(PartDesign::Hole::baseProfileOption_idxToBitmask(index)); recomputeFeature(); + + setGizmoPositions(); } } @@ -542,6 +552,8 @@ void TaskHoleParameters::depthChanged(int index) ui->DrillPointAngle->setEnabled(DepthisDimension); ui->DrillForDepth->setEnabled(DepthisDimension); setCutDiagram(); + + setGizmoPositions(); } void TaskHoleParameters::depthValueChanged(double value) @@ -583,9 +595,10 @@ void TaskHoleParameters::drillForDepthChanged() void TaskHoleParameters::taperedChanged() { - ui->TaperedAngle->setEnabled(ui->Tapered->isChecked()); + bool checked = ui->Tapered->isChecked(); + ui->TaperedAngle->setEnabled(checked); if (auto hole = getObject()) { - hole->Tapered.setValue(ui->Tapered->isChecked()); + hole->Tapered.setValue(checked); recomputeFeature(); } } @@ -595,6 +608,8 @@ void TaskHoleParameters::reversedChanged() if (auto hole = getObject()) { hole->Reversed.setValue(ui->Reversed->isChecked()); recomputeFeature(); + + setGizmoPositions(); } } @@ -1146,6 +1161,97 @@ void TaskHoleParameters::updateHoleCutLimits(PartDesign::Hole* hole) ui->HoleCutDiameter->setMinimum(hole->Diameter.getValue() + minHoleCutDifference); } +void TaskHoleParameters::setupGizmos(ViewProviderHole* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + holeDepthGizmo = new LinearGizmo(ui->Depth); + + gizmoContainer = GizmoContainer::createGizmo({holeDepthGizmo}, vp); + + setGizmoPositions(); +} + +std::vector getHolePositionFromShape(const Part::TopoShape& profileshape, const long baseProfileType) +{ + using BaseProfileTypeOptions=PartDesign::Hole::BaseProfileTypeOptions; + + std::vector positions; + + // Iterate over edges and filter out non-circle/non-arc types + if (baseProfileType & BaseProfileTypeOptions::OnCircles || + baseProfileType & BaseProfileTypeOptions::OnArcs) { + for (const auto &profileEdge : profileshape.getSubTopoShapes(TopAbs_EDGE)) { + TopoDS_Edge edge = TopoDS::Edge(profileEdge.getShape()); + BRepAdaptor_Curve adaptor(edge); + + // Circle base? + if (adaptor.GetType() != GeomAbs_Circle) { + continue; + } + // Filter for circles + if (!(baseProfileType & BaseProfileTypeOptions::OnCircles) && adaptor.IsClosed()) { + continue; + } + + // Filter for arcs + if (!(baseProfileType & BaseProfileTypeOptions::OnArcs) && !adaptor.IsClosed()) { + continue; + } + + gp_Circ circle = adaptor.Circle(); + positions.push_back( + Base::convertTo(circle.Axis().Location()) + ); + } + } + + // To avoid breaking older files which where not made with + // holes on points + if (baseProfileType & BaseProfileTypeOptions::OnPoints) { + // Iterate over vertices while avoiding edges so that curve handles are ignored + for (const auto &profileVertex : profileshape.getSubTopoShapes(TopAbs_VERTEX, TopAbs_EDGE)) { + TopoDS_Vertex vertex = TopoDS::Vertex(profileVertex.getShape()); + positions.push_back( + Base::convertTo(BRep_Tool::Pnt(vertex)) + ); + } + } + + return positions; +} + +void TaskHoleParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + auto hole = getObject(); + Part::TopoShape profileShape = hole->getProfileShape( + Part::ShapeOption::NeedSubElement | + Part::ShapeOption::ResolveLink | + Part::ShapeOption::Transform | + Part::ShapeOption::DontSimplifyCompound + ); + Base::Vector3d dir = hole->guessNormalDirection(profileShape); + dir *= hole->Reversed.getValue()? -1 : 1; + std::vector holePositions = getHolePositionFromShape(profileShape, hole->BaseProfileType.getValue()); + + if (holePositions.size() == 0) { + gizmoContainer->visible = false; + return; + } + gizmoContainer->visible = true; + + holeDepthGizmo->Gizmo::setDraggerPlacement(holePositions[0] - ui->HoleCutDepth->value().getValue() * dir, -dir); + holeDepthGizmo->setVisibility( + std::string(hole->DepthType.getValueAsString()) == "Dimension" + ); +} + //************************************************************************** //************************************************************************** diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.h b/src/Mod/PartDesign/Gui/TaskHoleParameters.h index 72ce953dff..e0f22aa527 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.h @@ -34,6 +34,8 @@ class Property; } namespace Gui { +class LinearGizmo; +class GizmoContainer; class ViewProvider; } @@ -144,6 +146,11 @@ private: bool isApplying; QWidget* proxy; std::unique_ptr ui; + + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* holeDepthGizmo = nullptr; + void setupGizmos(ViewProviderHole* vp); + void setGizmoPositions(); }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp index 41c3cb4a46..bc451a1f8b 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp @@ -20,19 +20,22 @@ * * ***************************************************************************/ - #include "PreCompiled.h" #include #include #include #include +#include #include #include #include #include #include #include +#include +#include +#include #include #include #include @@ -106,6 +109,8 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* catch (const Base::Exception &ex) { ex.reportException(); } + + setupGizmos(RevolutionView); } Gui::ViewProviderCoordinateSystem* TaskRevolutionParameters::getOriginView() const @@ -408,6 +413,8 @@ void TaskRevolutionParameters::onSelectionChanged(const Gui::SelectionChanges& m recomputeFeature(); updateUI(mode); + + setGizmoPositions(); } } } @@ -577,6 +584,10 @@ void TaskRevolutionParameters::onAxisChanged(int num) } recomputeFeature(); + + if (gizmoContainer) { + setGizmoPositions(); + } } catch (const Base::Exception& e) { e.reportException(); @@ -588,6 +599,12 @@ void TaskRevolutionParameters::onMidplane(bool on) if (getObject()) { propMidPlane->setValue(on); recomputeFeature(); + + if (gizmoContainer) { + rotationGizmo->setMultFactor( + rotationGizmo->getMultFactor() * (on? 0.5: 2) + ); + } } } @@ -596,6 +613,10 @@ void TaskRevolutionParameters::onReversed(bool on) if (getObject()) { propReversed->setValue(on); recomputeFeature(); + + if (gizmoContainer) { + reverseGizmoDir(); + } } } @@ -624,6 +645,10 @@ void TaskRevolutionParameters::onModeChanged(int index) updateUI(index); recomputeFeature(); + + if (gizmoContainer) { + setGizmoVisibility(); + } } void TaskRevolutionParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const @@ -705,6 +730,93 @@ void TaskRevolutionParameters::apply() FCMD_OBJ_CMD(tobj, "UpToFace = " << facename.toLatin1().data()); } +void TaskRevolutionParameters::setupGizmos(ViewProvider* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + rotationGizmo = new Gui::RadialGizmo(ui->revolveAngle); + rotationGizmo2 = new Gui::RadialGizmo(ui->revolveAngle2); + + gizmoContainer = GizmoContainer::createGizmo({rotationGizmo, rotationGizmo2}, vp); + rotationGizmo->flipArrow(); + rotationGizmo2->flipArrow(); + + setGizmoPositions(); + if (getReversed()) { + reverseGizmoDir(); + } + setGizmoVisibility(); +} + +void TaskRevolutionParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + Base::Vector3d profileCog; + Base::Vector3d basePos; + Base::Vector3d axisDir; + + if (isGroove) { + auto groove = getObject(); + Part::TopoShape profile = groove->getProfileShape(); + + profile.getCenterOfGravity(profileCog); + basePos = groove->Base.getValue(); + axisDir = groove->Axis.getValue(); + } else { + auto revolution = getObject(); + Part::TopoShape profile = revolution->getProfileShape(); + + profile.getCenterOfGravity(profileCog); + basePos = revolution->Base.getValue(); + axisDir = revolution->Axis.getValue(); + } + + auto diff = profileCog - basePos; + axisDir.Normalize(); + auto axisComp = axisDir * diff.Dot(axisDir); + auto normalComp = diff - axisComp; + + rotationGizmo->Gizmo::setDraggerPlacement(basePos + axisComp, normalComp); + rotationGizmo->getDraggerContainer()->setArcNormalDirection(Base::convertTo(axisDir)); + + rotationGizmo2->Gizmo::setDraggerPlacement(basePos + axisComp, normalComp); + rotationGizmo2->getDraggerContainer()->setArcNormalDirection(Base::convertTo(-axisDir)); +} + +void TaskRevolutionParameters::reverseGizmoDir() +{ + rotationGizmo->setMultFactor(-rotationGizmo->getMultFactor()); + rotationGizmo->flipArrow(); + + rotationGizmo2->setMultFactor(-rotationGizmo2->getMultFactor()); + rotationGizmo2->flipArrow(); +} + +void TaskRevolutionParameters::setGizmoVisibility() +{ + auto type = static_cast(ui->changeMode->currentIndex()); + + switch (type) { + case PartDesign::Revolution::RevolMethod::Dimension: + gizmoContainer->visible = true; + rotationGizmo->setVisibility(true); + rotationGizmo2->setVisibility(false); + break; + case PartDesign::Revolution::RevolMethod::TwoDimensions: + gizmoContainer->visible = true; + rotationGizmo->setVisibility(true); + rotationGizmo2->setVisibility(true); + break; + default: + gizmoContainer->visible = false; + } +} + //************************************************************************** //************************************************************************** // TaskDialog diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h index d98de4eccd..2930a86f51 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.h @@ -35,6 +35,8 @@ class Property; } namespace Gui { +class RadialGizmo; +class Gizmo; class ViewProvider; class ViewProviderCoordinateSystem; } @@ -122,6 +124,14 @@ private: * when adding stuff, and delete when removing stuff. */ std::vector> axesInList; + + std::unique_ptr gizmoContainer; + Gui::RadialGizmo* rotationGizmo = nullptr; + Gui::RadialGizmo* rotationGizmo2 = nullptr; + void setupGizmos(ViewProvider* vp); + void setGizmoPositions(); + void reverseGizmoDir(); + void setGizmoVisibility(); }; class TaskDlgRevolutionParameters : public TaskDlgSketchBasedParameters diff --git a/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp b/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp index 6cb493221f..fb70901b01 100644 --- a/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskThicknessParameters.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "ui_TaskThicknessParameters.h" @@ -51,6 +52,8 @@ TaskThicknessParameters::TaskThicknessParameters(ViewProviderDressUp* DressUpVie { addContainerWidget(); initControls(); + + setupGizmos(DressUpView); } void TaskThicknessParameters::addContainerWidget() @@ -138,6 +141,10 @@ void TaskThicknessParameters::onSelectionChanged(const Gui::SelectionChanges& ms if (selectionMode == refSel) { referenceSelected(msg, ui->listWidgetReferences); } + } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { + // TODO: the gizmo position should be only recalculated when the feature associated + // with the gizmo is removed from the list + setGizmoPositions(); } } @@ -200,6 +207,8 @@ void TaskThicknessParameters::onReversedChanged(bool on) if (PartDesign::Thickness* thickness = onBeforeChange()) { thickness->Reversed.setValue(on); onAfterChange(thickness); + + setGizmoPositions(); } } @@ -261,6 +270,47 @@ void TaskThicknessParameters::apply() } } +void TaskThicknessParameters::setupGizmos(ViewProviderDressUp* vp) +{ + if (!GizmoContainer::isEnabled()) { + return; + } + + linearGizmo = new Gui::LinearGizmo(ui->Value); + + gizmoContainer = GizmoContainer::createGizmo({linearGizmo}, vp); + + setGizmoPositions(); +} + +void TaskThicknessParameters::setGizmoPositions() +{ + if (!gizmoContainer) { + return; + } + + auto thickness = getObject(); + if (!thickness) { + gizmoContainer->visible = false; + return; + } + auto baseShape = thickness->getBaseTopoShape(); + auto shapes = thickness->getContinuousEdges(baseShape); + auto faces = thickness->getFaces(baseShape); + + if (shapes.size() == 0 || faces.size() == 0) { + gizmoContainer->visible = false; + return; + } + gizmoContainer->visible = true; + + Part::TopoShape edge = shapes[0]; + DraggerPlacementProps props = getDraggerPlacementFromEdgeAndFace(edge, faces[0]); + props.dir *= thickness->Reversed.getValue()? 1 : -1; + + linearGizmo->Gizmo::setDraggerPlacement(props.position, props.dir); +} + //************************************************************************** //************************************************************************** // TaskDialog diff --git a/src/Mod/PartDesign/Gui/TaskThicknessParameters.h b/src/Mod/PartDesign/Gui/TaskThicknessParameters.h index 408dd33c69..cc14ce5055 100644 --- a/src/Mod/PartDesign/Gui/TaskThicknessParameters.h +++ b/src/Mod/PartDesign/Gui/TaskThicknessParameters.h @@ -24,11 +24,18 @@ #ifndef GUI_TASKVIEW_TaskThicknessParameters_H #define GUI_TASKVIEW_TaskThicknessParameters_H +#include + #include "TaskDressUpParameters.h" #include "ViewProviderThickness.h" class Ui_TaskThicknessParameters; +namespace Gui { +class LinearGizmo; +class GizmoContainer; +} + namespace PartDesign { class Thickness; @@ -75,6 +82,11 @@ private: private: std::unique_ptr ui; + + std::unique_ptr gizmoContainer; + Gui::LinearGizmo* linearGizmo = nullptr; + void setupGizmos(ViewProviderDressUp* vp); + void setGizmoPositions(); }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/ViewProvider.cpp b/src/Mod/PartDesign/Gui/ViewProvider.cpp index dd22730b4d..bd976a8b64 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.cpp +++ b/src/Mod/PartDesign/Gui/ViewProvider.cpp @@ -438,7 +438,8 @@ PyObject* ViewProvider::getPyObject() return pyViewObject; } -ViewProviderBody* ViewProvider::getBodyViewProvider() { +ViewProviderBody* ViewProvider::getBodyViewProvider() +{ auto body = PartDesign::Body::findBodyOf(getObject()); auto doc = getDocument(); @@ -459,4 +460,3 @@ PROPERTY_SOURCE_TEMPLATE(PartDesignGui::ViewProviderPython, PartDesignGui::ViewP // explicit template instantiation template class PartDesignGuiExport ViewProviderFeaturePythonT; } -