diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index d264fd83dc..2726ea0752 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1067,6 +1067,7 @@ SET(Inventor_CPP_SRCS Inventor/So3DAnnotation.cpp Inventor/SoAutoZoomTranslation.cpp Inventor/SoAxisCrossKit.cpp + Inventor/SoFCPlacementIndicatorKit.cpp Inventor/SoDrawingGrid.cpp Inventor/SoFCBackgroundGradient.cpp Inventor/SoFCBoundingBox.cpp @@ -1099,6 +1100,7 @@ SET(Inventor_SRCS Inventor/SmSwitchboard.h Inventor/SoAutoZoomTranslation.h Inventor/SoAxisCrossKit.h + Inventor/SoFCPlacementIndicatorKit.h Inventor/SoDrawingGrid.h Inventor/SoFCBackgroundGradient.h Inventor/SoFCBoundingBox.h diff --git a/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp b/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp new file mode 100644 index 0000000000..d794b4d4e5 --- /dev/null +++ b/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "SoFCPlacementIndicatorKit.h" + +#include "So3DAnnotation.h" +#include "SoAxisCrossKit.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Gui +{ + +SO_KIT_SOURCE(SoFCPlacementIndicatorKit); + +SoFCPlacementIndicatorKit::SoFCPlacementIndicatorKit() +{ + SO_KIT_CONSTRUCTOR(SoFCPlacementIndicatorKit); + + SO_KIT_ADD_CATALOG_ENTRY(root, SoShapeScale, false, this, "", true); + + SO_KIT_INIT_INSTANCE(); + + SO_NODE_ADD_FIELD(coloredAxis, (true)); + SO_NODE_ADD_FIELD(scaleFactor, (scaleFactorDefault)); + SO_NODE_ADD_FIELD(axisLength, (axisLengthDefault)); + + SO_NODE_ADD_FIELD(parts, (AxisCross)); + SO_NODE_DEFINE_ENUM_VALUE(Part, Axes); + SO_NODE_DEFINE_ENUM_VALUE(Part, PlaneIndicator); + SO_NODE_DEFINE_ENUM_VALUE(Part, Labels); + SO_NODE_DEFINE_ENUM_VALUE(Part, ArrowHeads); + SO_NODE_DEFINE_ENUM_VALUE(Part, AxisCross); + SO_NODE_DEFINE_ENUM_VALUE(Part, AllParts); + SO_NODE_SET_SF_ENUM_TYPE(parts, Part); + + SO_NODE_ADD_FIELD(axes, (AllAxes)); + SO_NODE_DEFINE_ENUM_VALUE(Axes, X); + SO_NODE_DEFINE_ENUM_VALUE(Axes, Y); + SO_NODE_DEFINE_ENUM_VALUE(Axes, Z); + SO_NODE_DEFINE_ENUM_VALUE(Axes, AllAxes); + SO_NODE_SET_SF_ENUM_TYPE(axes, Axes); + + auto root = SO_GET_ANY_PART(this, "root", SoShapeScale); + root->scaleFactor.connectFrom(&scaleFactor); + + recomputeGeometry(); +} + +void SoFCPlacementIndicatorKit::initClass() +{ + SO_KIT_INIT_CLASS(SoFCPlacementIndicatorKit, SoBaseKit, "BaseKit"); +} + +void SoFCPlacementIndicatorKit::notify(SoNotList* l) +{ + SoField* field = l->getLastField(); + + if (field == &parts || field == &axes || field == &axisLength) { + // certainly this is not the fastest way to recompute the geometry as it does recreate + // everything from the scratch. It is however very easy to implement and this node should + // not really change too often so the performance penalty is better than making code that + // is harder to maintain. + recomputeGeometry(); + return; + } + + SoBaseKit::notify(l); +} + +void SoFCPlacementIndicatorKit::recomputeGeometry() +{ + auto root = SO_GET_ANY_PART(this, "root", SoShapeScale); + root->setPart("shape", createGeometry()); +} + +SoSeparator* SoFCPlacementIndicatorKit::createGeometry() +{ + auto sep = new SoSeparator(); + + auto pcBaseColor = new SoBaseColor(); + pcBaseColor->rgb.setValue(0.7f, 0.7f, 0.5f); + + auto pcLightModel = new SoLightModel(); + pcLightModel->model = SoLightModel::BASE_COLOR; + + sep->addChild(pcBaseColor); + sep->addChild(pcLightModel); + + if (parts.getValue() & PlaneIndicator) { + sep->addChild(createPlaneIndicator()); + } + + if (parts.getValue() & Axes) { + sep->addChild(createAxes()); + } + + return sep; +} + +SoSeparator* SoFCPlacementIndicatorKit::createAxes() +{ + const auto cylinderOffset = axisLength.getValue() / 2.f; + + const auto createAxis = [&](const char* label, + Base::Vector3d axis, + uint32_t packedColor, + const double offset) { + App::Color axisColor(packedColor); + + auto sep = new SoSeparator; + + auto rotation = Base::Rotation(Base::Vector3d::UnitY, axis); + auto pcTranslate = new SoTransform(); + pcTranslate->translation.setValue( + Base::convertTo((cylinderOffset + offset) * axis)); + pcTranslate->rotation.setValue(Base::convertTo(rotation)); + + auto pcArrowShaft = new SoCylinder(); + pcArrowShaft->radius = axisThickness / 2.0f; + pcArrowShaft->height = axisLength; + + if (coloredAxis.getValue()) { + auto pcBaseColor = new SoBaseColor(); + pcBaseColor->rgb.setValue(Base::convertTo(axisColor)); + + sep->addChild(pcBaseColor); + } + + sep->addChild(pcTranslate); + sep->addChild(pcArrowShaft); + + if (parts.getValue() & ArrowHeads) { + auto pcArrowHeadTranslation = new SoTranslation(); + pcArrowHeadTranslation->translation.setValue(0.0, cylinderOffset, 0.0); + + auto pcArrowHead = new SoCone(); + pcArrowHead->bottomRadius = arrowHeadRadius; + pcArrowHead->height = arrowHeadHeight; + + auto pcArrowHeadSeparator = new SoSeparator(); + pcArrowHeadSeparator->addChild(pcArrowHeadTranslation); + pcArrowHeadSeparator->addChild(pcArrowHead); + + sep->addChild(pcArrowHeadSeparator); + } + + if (parts.getValue() & Labels) { + auto pcLabelSeparator = new SoSeparator(); + + auto pcLabelTranslation = new SoTranslation(); + pcLabelTranslation->translation.setValue(0.0, cylinderOffset + labelOffset, 0.0); + pcLabelSeparator->addChild(pcLabelTranslation); + + auto pcAxisLabel = new SoFrameLabel(); + pcAxisLabel->string.setValue(label); + pcAxisLabel->textColor.setValue(1.0, 1.0, 1.0); + pcAxisLabel->horAlignment = SoImage::CENTER; + pcAxisLabel->vertAlignment = SoImage::HALF; + pcAxisLabel->border = false; + pcAxisLabel->frame = false; + pcAxisLabel->textUseBaseColor = true; + pcAxisLabel->size = labelFontSize; + + pcLabelSeparator->addChild(pcAxisLabel); + + sep->addChild(pcLabelSeparator); + } + + return sep; + }; + + auto sep = new SoSeparator; + + auto xyOffset = (parts.getValue() & PlaneIndicator) + ? planeIndicatorRadius + planeIndicatorMargin + : axisMargin; + + if (axes.getValue() & X) { + sep->addChild(createAxis("X", + Base::Vector3d::UnitX, + ViewParams::instance()->getAxisXColor(), + xyOffset)); + } + + if (axes.getValue() & Y) { + sep->addChild(createAxis("Y", + Base::Vector3d::UnitY, + ViewParams::instance()->getAxisYColor(), + xyOffset)); + } + + if (axes.getValue() & Z) { + auto zOffset = (parts.getValue() & PlaneIndicator) + ? planeIndicatorMargin + : axisMargin; + sep->addChild(createAxis("Z", + Base::Vector3d::UnitZ, + ViewParams::instance()->getAxisZColor(), + zOffset)); + } + + return sep; +} + +SoSeparator* SoFCPlacementIndicatorKit::createPlaneIndicator() +{ + // cylinders are aligned with Y axis for some reason + auto rotation = Base::Rotation(Base::Vector3d::UnitY, Base::Vector3d::UnitZ); + + auto pcRotation = new SoRotation(); + pcRotation->rotation = Base::convertTo(rotation); + + auto pcComplexity = new SoComplexity(); + pcComplexity->value = 1.0f; + + auto pcCylinder = new SoCylinder(); + pcCylinder->height = 0.f; + pcCylinder->radius = planeIndicatorRadius; + pcCylinder->parts = SoCylinder::TOP; + + const auto gray = App::Color(0.75f, 0.75f, 0.75f); + + auto pcMaterial = new SoMaterial(); + pcMaterial->diffuseColor.setValue(Base::convertTo(gray)); + pcMaterial->ambientColor.connectFrom(&pcMaterial->diffuseColor); + pcMaterial->transparency = planeIndicatorTransparency; + + auto sep = new SoSeparator; + + sep->addChild(pcRotation); + sep->addChild(pcMaterial); + sep->addChild(pcComplexity); + sep->addChild(pcCylinder); + + return sep; +} + +SoFCPlacementIndicatorKit::~SoFCPlacementIndicatorKit() = default; + +} // namespace Gui \ No newline at end of file diff --git a/src/Gui/Inventor/SoFCPlacementIndicatorKit.h b/src/Gui/Inventor/SoFCPlacementIndicatorKit.h new file mode 100644 index 0000000000..2346c8d5fe --- /dev/null +++ b/src/Gui/Inventor/SoFCPlacementIndicatorKit.h @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef SOFCPLACEMENTINDICATORKIT_H +#define SOFCPLACEMENTINDICATORKIT_H + +#include + +#include + +namespace Gui { + +class GuiExport SoFCPlacementIndicatorKit : public SoBaseKit { + using inherited = SoBaseKit; + + static constexpr double planeIndicatorRadius = 0.4f; + static constexpr double planeIndicatorMargin = 0.2f; + static constexpr double planeIndicatorTransparency = 0.3f; + + static constexpr double axisMargin = 0.0f; + static constexpr double axisLengthDefault = 0.6f; + static constexpr double axisThickness = 0.065f; + + static constexpr double arrowHeadRadius = axisThickness * 1.25f; + static constexpr double arrowHeadHeight = arrowHeadRadius * 3.f; + + static constexpr double labelOffset = 0.4f; + static constexpr int labelFontSize = 9; + + static constexpr double scaleFactorDefault = 40.f; + + SO_KIT_HEADER(SoFCPlacementIndicatorKit); + + SO_KIT_CATALOG_ENTRY_HEADER(root); + +public: + SoFCPlacementIndicatorKit(); + static void initClass(); + + void notify(SoNotList* l) override; + + // clang-format off + enum Part + { + Axes = 1 << 0, + PlaneIndicator = 1 << 1, + Labels = 1 << 2, + ArrowHeads = 1 << 3, + + // common configurations + AllParts = Axes | PlaneIndicator | Labels | ArrowHeads, + AxisCross = Axes | Labels | ArrowHeads + }; + + enum Axis + { + X = 1 << 0, + Y = 1 << 1, + Z = 1 << 2, + AllAxes = X | Y | Z + }; + // clang-format on + + SoSFEnum parts; + SoSFEnum axes; + SoSFBool coloredAxis; + SoSFFloat scaleFactor; + SoSFFloat axisLength; + +private: + void recomputeGeometry(); + + SoSeparator* createOriginIndicator(); + SoSeparator* createGeometry(); + SoSeparator* createAxes(); + SoSeparator* createPlaneIndicator(); + + ~SoFCPlacementIndicatorKit() override; +}; + +} // Gui + +#endif //SOFCPLACEMENTINDICATORKIT_H diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 960270f208..76cb08f20c 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -79,6 +79,8 @@ #include "propertyeditor/PropertyItem.h" #include "ArcEngine.h" +#include + using namespace Gui; using namespace Gui::Inventor; @@ -148,6 +150,7 @@ void Gui::SoFCDB::init() SoMouseWheelEvent ::initClass(); So3DAnnotation ::initClass(); SoDelayedAnnotationsElement ::initClass(); + SoFCPlacementIndicatorKit ::initClass(); PropertyItem ::init(); PropertySeparatorItem ::init();