// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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, (ViewParams::instance()->getPlacementIndicatorScale())); SO_NODE_ADD_FIELD(axisLength, (axisLengthDefault)); SO_NODE_ADD_FIELD(parts, (AxisCross)); SO_NODE_ADD_FIELD(axes, (AllAxes)); SO_NODE_DEFINE_ENUM_VALUE(Part, Axes); SO_NODE_DEFINE_ENUM_VALUE(Part, ArrowHeads); SO_NODE_DEFINE_ENUM_VALUE(Part, Labels); SO_NODE_DEFINE_ENUM_VALUE(Part, AxisCross); SO_NODE_DEFINE_ENUM_VALUE(Part, PlaneIndicator); SO_NODE_DEFINE_ENUM_VALUE(Part, OriginIndicator); SO_NODE_DEFINE_ENUM_VALUE(Part, AllParts); SO_NODE_SET_SF_ENUM_TYPE(parts, Part); 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::createOriginIndicator() { const uint32_t originColor = ViewParams::instance()->getOriginColor(); auto sep = new SoSeparator(); auto pcBaseColor = new SoBaseColor(); pcBaseColor->rgb.setValue(Base::Color::fromPackedRGBA(originColor)); auto pcSphere = new SoSphere(); // the factor aligns radius of sphere and arrow head visually, without it, it looks too small constexpr float visualAdjustmentFactor = 1.2F; pcSphere->radius = arrowHeadRadius * visualAdjustmentFactor; sep->addChild(pcBaseColor); sep->addChild(pcSphere); return sep; } SoSeparator* SoFCPlacementIndicatorKit::createGeometry() { const uint32_t neutralColor = ViewParams::instance()->getNeutralColor(); auto sep = new SoSeparator(); auto pcBaseColor = new SoBaseColor(); pcBaseColor->rgb.setValue(Base::Color::fromPackedRGBA(neutralColor)); 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() & OriginIndicator) { sep->addChild(createOriginIndicator()); } 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) { Base::Color axisColor(packedColor); Base::Rotation rotation(Base::Vector3d::UnitY, axis); auto sep = new SoSeparator; 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.F; 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; }; double additionalAxisMargin = (parts.getValue() & OriginIndicator) ? axisThickness * 4 : 0; double xyOffset = (parts.getValue() & PlaneIndicator) ? planeIndicatorRadius + planeIndicatorMargin : axisMargin + additionalAxisMargin; auto sep = new SoSeparator; 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) { double zOffset = (parts.getValue() & PlaneIndicator) ? planeIndicatorMargin : axisMargin + additionalAxisMargin; 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 = Base::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