Repository: FreeCAD/FreeCAD
Pull request #20602: Gui: Add Origin indicator to SoFCPlacementIndicator
Author: login: kadet1090
Labels: name: Mod: Core
description: Issue or PR touches core sections (App, Gui, Base) of FreeCAD
Created: 2025-04-02T21:49:55Z | Merged: 2025-04-09T15:48:16Z

This adds optional origin indicator of `SoFCPlacementIndicator`. This is something that can be used to clearly indicate origin placement with small sphere.

![image](https://github.com/user-attachments/assets/660a0dbe-8293-4cf7-9574-4d57fad1a5ab)

It is not enabled for the "Show Placement" indicator to make it a bit less noisy. It can be however used by other components to put further emphasis on the origin.


Diff:
diff --git a/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp b/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
index 2da7d2438235..fb0ca80b67f7 100644
--- a/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
+++ b/src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
@@ -36,6 +36,7 @@
 #include <Inventor/nodes/SoMaterial.h>
 #include <Inventor/nodes/SoRotation.h>
 #include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoSphere.h>
 #include <Inventor/nodes/SoTransform.h>
 #include <Inventor/nodes/SoTranslation.h>
 #include <Inventor/nodes/SoFontStyle.h>
@@ -68,19 +69,20 @@ SoFCPlacementIndicatorKit::SoFCPlacementIndicatorKit()
     SO_KIT_INIT_INSTANCE();
 
     SO_NODE_ADD_FIELD(coloredAxis, (true));
-    SO_NODE_ADD_FIELD(scaleFactor, (scaleFactorDefault));
+    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, PlaneIndicator);
-    SO_NODE_DEFINE_ENUM_VALUE(Part, Labels);
     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_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);
@@ -120,12 +122,34 @@ void SoFCPlacementIndicatorKit::recomputeGeometry()
     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<SbColor>(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(0.7f, 0.7f, 0.5f);
+    pcBaseColor->rgb.setValue(Base::Color::fromPackedRGBA<SbColor>(neutralColor));
 
     auto pcLightModel = new SoLightModel();
     pcLightModel->model = SoLightModel::BASE_COLOR;
@@ -137,6 +161,10 @@ SoSeparator* SoFCPlacementIndicatorKit::createGeometry()
         sep->addChild(createPlaneIndicator());
     }
 
+    if (parts.getValue() & OriginIndicator) {
+        sep->addChild(createOriginIndicator());
+    }
+
     if (parts.getValue() & Axes) {
         sep->addChild(createAxes());
     }
@@ -146,24 +174,24 @@ SoSeparator* SoFCPlacementIndicatorKit::createGeometry()
 
 SoSeparator* SoFCPlacementIndicatorKit::createAxes()
 {
-    const auto cylinderOffset = axisLength.getValue() / 2.f;
+    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 rotation = Base::Rotation(Base::Vector3d::UnitY, axis);
         auto pcTranslate = new SoTransform();
         pcTranslate->translation.setValue(
             Base::convertTo<SbVec3f>((cylinderOffset + offset) * axis));
         pcTranslate->rotation.setValue(Base::convertTo<SbRotation>(rotation));
 
         auto pcArrowShaft = new SoCylinder();
-        pcArrowShaft->radius = axisThickness / 2.0f;
+        pcArrowShaft->radius = axisThickness / 2.F;
         pcArrowShaft->height = axisLength;
 
         if (coloredAxis.getValue()) {
@@ -216,11 +244,12 @@ SoSeparator* SoFCPlacementIndicatorKit::createAxes()
         return sep;
     };
 
-    auto sep = new SoSeparator;
-
-    auto xyOffset = (parts.getValue() & PlaneIndicator)
+    double additionalAxisMargin = (parts.getValue() & OriginIndicator) ? axisThickness * 4 : 0;
+    double xyOffset = (parts.getValue() & PlaneIndicator)
         ? planeIndicatorRadius + planeIndicatorMargin
-        : axisMargin;
+        : axisMargin + additionalAxisMargin;
+
+    auto sep = new SoSeparator;
 
     if (axes.getValue() & X) {
         sep->addChild(createAxis("X",
@@ -237,9 +266,10 @@ SoSeparator* SoFCPlacementIndicatorKit::createAxes()
     }
 
     if (axes.getValue() & Z) {
-        auto zOffset = (parts.getValue() & PlaneIndicator)
+        double zOffset = (parts.getValue() & PlaneIndicator)
             ? planeIndicatorMargin
-            : axisMargin;
+            : axisMargin + additionalAxisMargin;
+
         sep->addChild(createAxis("Z",
                                  Base::Vector3d::UnitZ,
                                  ViewParams::instance()->getAxisZColor(),
diff --git a/src/Gui/Inventor/SoFCPlacementIndicatorKit.h b/src/Gui/Inventor/SoFCPlacementIndicatorKit.h
index 2346c8d5fef7..264628529a52 100644
--- a/src/Gui/Inventor/SoFCPlacementIndicatorKit.h
+++ b/src/Gui/Inventor/SoFCPlacementIndicatorKit.h
@@ -62,13 +62,14 @@ class GuiExport SoFCPlacementIndicatorKit : public SoBaseKit {
     // clang-format off
     enum Part
     {
-        Axes           = 1 << 0,
-        PlaneIndicator = 1 << 1,
-        Labels         = 1 << 2,
-        ArrowHeads     = 1 << 3,
+        Axes            = 1 << 0,
+        ArrowHeads      = 1 << 1,
+        Labels          = 1 << 2,
+        PlaneIndicator  = 1 << 3,
+        OriginIndicator = 1 << 4,
 
         // common configurations
-        AllParts       = Axes | PlaneIndicator | Labels | ArrowHeads,
+        AllParts       = Axes | OriginIndicator | Labels | ArrowHeads,
         AxisCross      = Axes | Labels | ArrowHeads
     };
 
diff --git a/src/Gui/Tools/color_traits.h b/src/Gui/Tools/color_traits.h
deleted file mode 100644
index 94fe6f133645..000000000000
--- a/src/Gui/Tools/color_traits.h
+++ /dev/null
@@ -1,288 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-/***************************************************************************
- *   Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net>     *
- *                                                                         *
- *   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                               *
- *   <https://www.gnu.org/licenses/>.                                      *
- *                                                                         *
- **************************************************************************/
-
-#ifndef GUI_COLORTRAITS_H
-#define GUI_COLORTRAITS_H
-
-#include <Base/Color.h>
-#include <Inventor/SbColor.h>
-#include <Inventor/SbColor4f.h>
-#include <QColor>
-
-namespace Base
-{
-// Specialization for SbColor
-template<>
-struct color_traits<SbColor>
-{
-    using color_type = SbColor;
-    color_traits() = default;
-    explicit color_traits(const color_type& ct)
-        : ct(ct)
-    {}
-    float redF() const
-    {
-        return ct[0];
-    }
-    float greenF() const
-    {
-        return ct[1];
-    }
-    float blueF() const
-    {
-        return ct[2];
-    }
-    float alphaF() const
-    {
-        return 1.0F;
-    }
-    void setRedF(float red)
-    {
-        ct[0] = red;
-    }
-    void setGreenF(float green)
-    {
-        ct[1] = green;
-    }
-    void setBlueF(float blue)
-    {
-        ct[2] = blue;
-    }
-    void setAlphaF(float alpha)
-    {
-        (void)alpha;
-    }
-    int red() const
-    {
-        return int(std::lround(ct[0] * 255.0F));
-    }
-    int green() const
-    {
-        return int(std::lround(ct[1] * 255.0F));
-    }
-    int blue() const
-    {
-        return int(std::lround(ct[2] * 255.0F));
-    }
-    int alpha() const
-    {
-        return 255;
-    }
-    void setRed(int red)
-    {
-        ct[0] = static_cast<float>(red) / 255.0F;
-    }
-    void setGreen(int green)
-    {
-        ct[1] = static_cast<float>(green) / 255.0F;
-    }
-    void setBlue(int blue)
-    {
-        ct[2] = static_cast<float>(blue) / 255.0F;
-    }
-    void setAlpha(int alpha)
-    {
-        (void)alpha;
-    }
-    static color_type makeColor(int red, int green, int blue, int alpha = 255)
-    {
-        (void)alpha;
-        return color_type{static_cast<float>(red) / 255.0F,
-                          static_cast<float>(green) / 255.0F,
-                          static_cast<float>(blue) / 255.0F};
-    }
-
-private:
-    color_type ct;
-};
-
-// Specialization for SbColor4f
-template<>
-struct color_traits<SbColor4f>
-{
-    using color_type = SbColor4f;
-    color_traits() = default;
-    explicit color_traits(const color_type& ct)
-        : ct(ct)
-    {}
-    float redF() const
-    {
-        return ct[0];
-    }
-    float greenF() const
-    {
-        return ct[1];
-    }
-    float blueF() const
-    {
-        return ct[2];
-    }
-    float alphaF() const
-    {
-        return ct[3];
-    }
-    void setRedF(float red)
-    {
-        ct[0] = red;
-    }
-    void setGreenF(float green)
-    {
-        ct[1] = green;
-    }
-    void setBlueF(float blue)
-    {
-        ct[2] = blue;
-    }
-    void setAlphaF(float alpha)
-    {
-        ct[3] = alpha;
-    }
-    int red() const
-    {
-        return int(std::lround(ct[0] * 255.0F));
-    }
-    int green() const
-    {
-        return int(std::lround(ct[1] * 255.0F));
-    }
-    int blue() const
-    {
-        return int(std::lround(ct[2] * 255.0F));
-    }
-    int alpha() const
-    {
-        return int(std::lround(ct[3] * 255.0F));
-    }
-    void setRed(int red)
-    {
-        ct[0] = static_cast<float>(red) / 255.0F;
-    }
-    void setGreen(int green)
-    {
-        ct[1] = static_cast<float>(green) / 255.0F;
-    }
-    void setBlue(int blue)
-    {
-        ct[2] = static_cast<float>(blue) / 255.0F;
-    }
-    void setAlpha(int alpha)
-    {
-        ct[3] = static_cast<float>(alpha) / 255.0F;
-    }
-    static color_type makeColor(int red, int green, int blue, int alpha = 255)
-    {
-        return color_type{static_cast<float>(red) / 255.0F,
-                          static_cast<float>(green) / 255.0F,
-                          static_cast<float>(blue) / 255.0F,
-                          static_cast<float>(alpha) / 255.0F};
-    }
-
-private:
-    color_type ct;
-};
-
-// Specialization for QColor
-template<>
-struct color_traits<QColor>
-{
-    using color_type = QColor;
-    color_traits() = default;
-    explicit color_traits(const color_type& ct)
-        : ct(ct)
-    {}
-    float redF() const
-    {
-        return static_cast<float>(ct.redF());
-    }
-    float greenF() const
-    {
-        return static_cast<float>(ct.greenF());
-    }
-    float blueF() const
-    {
-        return static_cast<float>(ct.blueF());
-    }
-    float alphaF() const
-    {
-        return static_cast<float>(ct.alphaF());
-    }
-    void setRedF(float red)
-    {
-        ct.setRedF(red);
-    }
-    void setGreenF(float green)
-    {
-        ct.setGreenF(green);
-    }
-    void setBlueF(float blue)
-    {
-        ct.setBlueF(blue);
-    }
-    void setAlphaF(float alpha)
-    {
-        ct.setAlphaF(alpha);
-    }
-    int red() const
-    {
-        return ct.red();
-    }
-    int green() const
-    {
-        return ct.green();
-    }
-    int blue() const
-    {
-        return ct.blue();
-    }
-    int alpha() const
-    {
-        return ct.alpha();
-    }
-    void setRed(int red)
-    {
-        ct.setRed(red);
-    }
-    void setGreen(int green)
-    {
-        ct.setGreen(green);
-    }
-    void setBlue(int blue)
-    {
-        ct.setBlue(blue);
-    }
-    void setAlpha(int alpha)
-    {
-        ct.setAlpha(alpha);
-    }
-    static color_type makeColor(int red, int green, int blue, int alpha = 255)
-    {
-        return color_type{red, green, blue, alpha};
-    }
-
-private:
-    color_type ct;
-};
-
-}  // namespace App
-
-#endif  // GUI_COLORTRAITS_H
diff --git a/src/Gui/Utilities.h b/src/Gui/Utilities.h
index cdb4a8652335..13814cfccd81 100644
--- a/src/Gui/Utilities.h
+++ b/src/Gui/Utilities.h
@@ -29,6 +29,7 @@
 #include <Base/Converter.h>
 #include <Base/ViewProj.h>
 #include <Inventor/SbColor.h>
+#include <Inventor/SbColor4f.h>
 #include <Inventor/SbMatrix.h>
 #include <Inventor/SbRotation.h>
 #include <Inventor/SbVec2f.h>
@@ -117,6 +118,258 @@ struct vec_traits<QColor> {
     const vec_type& v;
 };
 
+// Specialization for SbColor
+template<>
+struct color_traits<SbColor>
+{
+    using color_type = SbColor;
+    color_traits() = default;
+    explicit color_traits(const color_type& ct)
+        : ct(ct)
+    {}
+    float redF() const
+    {
+        return ct[0];
+    }
+    float greenF() const
+    {
+        return ct[1];
+    }
+    float blueF() const
+    {
+        return ct[2];
+    }
+    float alphaF() const
+    {
+        return 1.0F;
+    }
+    void setRedF(float red)
+    {
+        ct[0] = red;
+    }
+    void setGreenF(float green)
+    {
+        ct[1] = green;
+    }
+    void setBlueF(float blue)
+    {
+        ct[2] = blue;
+    }
+    void setAlphaF(float alpha)
+    {
+        (void)alpha;
+    }
+    int red() const
+    {
+        return int(std::lround(ct[0] * 255.0F));
+    }
+    int green() const
+    {
+        return int(std::lround(ct[1] * 255.0F));
+    }
+    int blue() const
+    {
+        return int(std::lround(ct[2] * 255.0F));
+    }
+    int alpha() const
+    {
+        return 255;
+    }
+    void setRed(int red)
+    {
+        ct[0] = static_cast<float>(red) / 255.0F;
+    }
+    void setGreen(int green)
+    {
+        ct[1] = static_cast<float>(green) / 255.0F;
+    }
+    void setBlue(int blue)
+    {
+        ct[2] = static_cast<float>(blue) / 255.0F;
+    }
+    void setAlpha(int alpha)
+    {
+        (void)alpha;
+    }
+    static color_type makeColor(int red, int green, int blue, int alpha = 255)
+    {
+        (void)alpha;
+        return color_type{static_cast<float>(red) / 255.0F,
+                          static_cast<float>(green) / 255.0F,
+                          static_cast<float>(blue) / 255.0F};
+    }
+
+private:
+    color_type ct;
+};
+
+// Specialization for SbColor4f
+template<>
+struct color_traits<SbColor4f>
+{
+    using color_type = SbColor4f;
+    color_traits() = default;
+    explicit color_traits(const color_type& ct)
+        : ct(ct)
+    {}
+    float redF() const
+    {
+        return ct[0];
+    }
+    float greenF() const
+    {
+        return ct[1];
+    }
+    float blueF() const
+    {
+        return ct[2];
+    }
+    float alphaF() const
+    {
+        return ct[3];
+    }
+    void setRedF(float red)
+    {
+        ct[0] = red;
+    }
+    void setGreenF(float green)
+    {
+        ct[1] = green;
+    }
+    void setBlueF(float blue)
+    {
+        ct[2] = blue;
+    }
+    void setAlphaF(float alpha)
+    {
+        ct[3] = alpha;
+    }
+    int red() const
+    {
+        return int(std::lround(ct[0] * 255.0F));
+    }
+    int green() const
+    {
+        return int(std::lround(ct[1] * 255.0F));
+    }
+    int blue() const
+    {
+        return int(std::lround(ct[2] * 255.0F));
+    }
+    int alpha() const
+    {
+        return int(std::lround(ct[3] * 255.0F));
+    }
+    void setRed(int red)
+    {
+        ct[0] = static_cast<float>(red) / 255.0F;
+    }
+    void setGreen(int green)
+    {
+        ct[1] = static_cast<float>(green) / 255.0F;
+    }
+    void setBlue(int blue)
+    {
+        ct[2] = static_cast<float>(blue) / 255.0F;
+    }
+    void setAlpha(int alpha)
+    {
+        ct[3] = static_cast<float>(alpha) / 255.0F;
+    }
+    static color_type makeColor(int red, int green, int blue, int alpha = 255)
+    {
+        return color_type{static_cast<float>(red) / 255.0F,
+                          static_cast<float>(green) / 255.0F,
+                          static_cast<float>(blue) / 255.0F,
+                          static_cast<float>(alpha) / 255.0F};
+    }
+
+private:
+    color_type ct;
+};
+
+// Specialization for QColor
+template<>
+struct color_traits<QColor>
+{
+    using color_type = QColor;
+    color_traits() = default;
+    explicit color_traits(const color_type& ct)
+        : ct(ct)
+    {}
+    float redF() const
+    {
+        return static_cast<float>(ct.redF());
+    }
+    float greenF() const
+    {
+        return static_cast<float>(ct.greenF());
+    }
+    float blueF() const
+    {
+        return static_cast<float>(ct.blueF());
+    }
+    float alphaF() const
+    {
+        return static_cast<float>(ct.alphaF());
+    }
+    void setRedF(float red)
+    {
+        ct.setRedF(red);
+    }
+    void setGreenF(float green)
+    {
+        ct.setGreenF(green);
+    }
+    void setBlueF(float blue)
+    {
+        ct.setBlueF(blue);
+    }
+    void setAlphaF(float alpha)
+    {
+        ct.setAlphaF(alpha);
+    }
+    int red() const
+    {
+        return ct.red();
+    }
+    int green() const
+    {
+        return ct.green();
+    }
+    int blue() const
+    {
+        return ct.blue();
+    }
+    int alpha() const
+    {
+        return ct.alpha();
+    }
+    void setRed(int red)
+    {
+        ct.setRed(red);
+    }
+    void setGreen(int green)
+    {
+        ct.setGreen(green);
+    }
+    void setBlue(int blue)
+    {
+        ct.setBlue(blue);
+    }
+    void setAlpha(int alpha)
+    {
+        ct.setAlpha(alpha);
+    }
+    static color_type makeColor(int red, int green, int blue, int alpha = 255)
+    {
+        return color_type{red, green, blue, alpha};
+    }
+
+private:
+    color_type ct;
+};
+
 template <>
 inline SbMatrix convertTo<SbMatrix, Base::Matrix4D>(const Base::Matrix4D& vec2)
 {
diff --git a/src/Gui/ViewParams.h b/src/Gui/ViewParams.h
index 5e7abc4a77d8..04ef455b1e02 100644
--- a/src/Gui/ViewParams.h
+++ b/src/Gui/ViewParams.h
@@ -66,6 +66,9 @@ class GuiExport ViewParams: public ParameterGrp::ObserverType {
     FC_VIEW_PARAM(AxisXColor,unsigned long,Unsigned,0xCC333300) \
     FC_VIEW_PARAM(AxisYColor,unsigned long,Unsigned,0x33CC3300) \
     FC_VIEW_PARAM(AxisZColor,unsigned long,Unsigned,0x3333CC00) \
+    FC_VIEW_PARAM(OriginColor,unsigned long,Unsigned,0xFBD62900) \
+    FC_VIEW_PARAM(NeutralColor,unsigned long,Unsigned,0xB3B38000) \
+    FC_VIEW_PARAM(PlacementIndicatorScale,double,Float,40.0) \
     FC_VIEW_PARAM(DraggerScale,double,Float,0.03) \
 
 


Reviews:
user: login: hyarion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T00:01:32Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:28:32Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:29:30Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:32:03Z

Review comments:
diff_hunk: @@ -120,12 +122,32 @@ void SoFCPlacementIndicatorKit::recomputeGeometry()
     root->setPart("shape", createGeometry());
 }
 
+SoSeparator* SoFCPlacementIndicatorKit::createOriginIndicator()
+{
+    auto sep = new SoSeparator();
+
+    auto pcBaseColor = new SoBaseColor();
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
user: login: hyarion
body: Don't we usually write out the asterisk for pointers?
```suggestion
    auto* pcBaseColor = new SoBaseColor();
```
created_at: 2025-04-03T23:47:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,12 +122,32 @@ void SoFCPlacementIndicatorKit::recomputeGeometry()
     root->setPart("shape", createGeometry());
 }
 
+SoSeparator* SoFCPlacementIndicatorKit::createOriginIndicator()
+{
+    auto sep = new SoSeparator();
+
+    auto pcBaseColor = new SoBaseColor();
+    pcBaseColor->rgb.setValue(
+        Base::Color::fromPackedRGBA<SbColor>(ViewParams::instance()->getOriginColor()));
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
user: login: hyarion
body: ```suggestion
    auto originColor = ViewParams::instance()->getOriginColor();
    pcBaseColor->rgb.setValue(Base::Color::fromPackedRGBA<SbColor>(originColor));
```

created_at: 2025-04-03T23:49:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,12 +122,32 @@ void SoFCPlacementIndicatorKit::recomputeGeometry()
     root->setPart("shape", createGeometry());
 }
 
+SoSeparator* SoFCPlacementIndicatorKit::createOriginIndicator()
+{
+    auto sep = new SoSeparator();
+
+    auto pcBaseColor = new SoBaseColor();
+    pcBaseColor->rgb.setValue(
+        Base::Color::fromPackedRGBA<SbColor>(ViewParams::instance()->getOriginColor()));
+
+    auto pcSphere = new SoSphere();
+    // the factor aligns radius of sphere and arrow head visually, without it, it looks too small
+    static constexpr float visualAdjustmentFactor = 1.2F;
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
user: login: hyarion
body: There's no real benefit of making `constexpr` `static` for primary data types.

For data accessed with pointers or if the data is big (like an array or object) then it might be of value.

I would opt to remove it here to reduce noise:
```suggestion
    constexpr float visualAdjustmentFactor = 1.2F;
```

created_at: 2025-04-03T23:55:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,13 +62,14 @@ class GuiExport SoFCPlacementIndicatorKit : public SoBaseKit {
     // clang-format off
     enum Part
     {
-        Axes           = 1 << 0,
-        PlaneIndicator = 1 << 1,
-        Labels         = 1 << 2,
-        ArrowHeads     = 1 << 3,
+        Axes            = 1 << 0,
+        ArrowHeads      = 1 << 1,
+        Labels          = 1 << 2,
+        PlaneIndicator  = 1 << 3,
+        OriginIndicator = 1 << 4,
 
         // common configurations
-        AllParts       = Axes | PlaneIndicator | Labels | ArrowHeads,
+        AllParts       = Axes | OriginIndicator | Labels | ArrowHeads,
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.h
user: login: hyarion
body: Shouldn't this include planes?
```suggestion
        AllParts       = Axes | PlaneIndicator | OriginIndicator | Labels | ArrowHeads,
```

created_at: 2025-04-03T23:59:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,12 +122,32 @@ void SoFCPlacementIndicatorKit::recomputeGeometry()
     root->setPart("shape", createGeometry());
 }
 
+SoSeparator* SoFCPlacementIndicatorKit::createOriginIndicator()
+{
+    auto sep = new SoSeparator();
+
+    auto pcBaseColor = new SoBaseColor();
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
user: login: kadet1090
body: That file predates discussion on that and I figured that it is better to keep consistency within the file. I'm happy to change it in the whole file however. 
created_at: 2025-04-04T08:28:32Z
author_association: MEMBER
diff_hunk: @@ -120,12 +122,32 @@ void SoFCPlacementIndicatorKit::recomputeGeometry()
     root->setPart("shape", createGeometry());
 }
 
+SoSeparator* SoFCPlacementIndicatorKit::createOriginIndicator()
+{
+    auto sep = new SoSeparator();
+
+    auto pcBaseColor = new SoBaseColor();
+    pcBaseColor->rgb.setValue(
+        Base::Color::fromPackedRGBA<SbColor>(ViewParams::instance()->getOriginColor()));
+
+    auto pcSphere = new SoSphere();
+    // the factor aligns radius of sphere and arrow head visually, without it, it looks too small
+    static constexpr float visualAdjustmentFactor = 1.2F;
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.cpp
user: login: kadet1090
body: Nice, I was not aware about that when writing this! Thanks.
created_at: 2025-04-04T08:29:30Z
author_association: MEMBER
diff_hunk: @@ -62,13 +62,14 @@ class GuiExport SoFCPlacementIndicatorKit : public SoBaseKit {
     // clang-format off
     enum Part
     {
-        Axes           = 1 << 0,
-        PlaneIndicator = 1 << 1,
-        Labels         = 1 << 2,
-        ArrowHeads     = 1 << 3,
+        Axes            = 1 << 0,
+        ArrowHeads      = 1 << 1,
+        Labels          = 1 << 2,
+        PlaneIndicator  = 1 << 3,
+        OriginIndicator = 1 << 4,
 
         // common configurations
-        AllParts       = Axes | PlaneIndicator | Labels | ArrowHeads,
+        AllParts       = Axes | OriginIndicator | Labels | ArrowHeads,
path: src/Gui/Inventor/SoFCPlacementIndicatorKit.h
user: login: kadet1090
body: In that case no - the plane indicator in that case refers to the circular XY plane indicator. Having both would be very noisy and the plane indicator should be used only in scenarios where we can put some emphasis on the plane and Z direction - so for example connectors for assembly. 
created_at: 2025-04-04T08:32:03Z
author_association: MEMBER

PR comments:
user: login: maxwxyz
created_at: 2025-04-03T05:26:09Z
body: @FreeCAD/design-working-group FYI
author_association: CONTRIBUTOR
user: login: pierreporte
created_at: 2025-04-04T11:42:22Z
body: Is it themable, and is the size adjustable, or the appearance is hardcoded?
author_association: NONE
user: login: kadet1090
created_at: 2025-04-04T11:49:00Z
body: > Is it themable, and is the size adjustable, or the appearance is hardcoded?

It is themable, but good point about size adjustability - that one is hardcoded, I'll add multiplier for it so it can be adjusted in config.
author_association: MEMBER
user: login: MisterMakerNL
created_at: 2025-04-08T19:22:40Z
body: Looks pretty awesome!

I just have some remarks about the visuals. We should show a clear difference between a static element and a drag-able element. I could understand this looks very similar to the modifier tool, and so someone would try and drag it.
Probably because of this most other programs use a more simple and toned back icon.

There is also a risk in having conflicts with coordinate systems in CAM workbench. Because you often use different coordinate systems in CAM.

Examples of Solidworks and inventor:
![hqdefault-2345559812](https://github.com/user-attachments/assets/d94bf01d-8ff6-4758-8b01-7d0153cd39e8)
![0EM3g000000gAJS-2103779218](https://github.com/user-attachments/assets/a82cf505-1704-4f8c-8e2f-077f131c9014)


author_association: CONTRIBUTOR
user: login: kadet1090
created_at: 2025-04-08T19:49:30Z
body: > I just have some remarks about the visuals. We should show a clear difference between a static element and a drag-able element. I could understand this looks very similar to the modifier tool, and so someone would try and drag it.
> Probably because of this most other programs use a more simple and toned back icon.

That node has flag that disables colors so it is covered already if someone wants to use that:
![image](https://github.com/user-attachments/assets/5688b673-e316-4299-8969-79161ff1f20e)

For now though, FreeCAD is pretty colorful and it should not be changed in that particular PR.
author_association: MEMBER
user: login: MisterMakerNL
created_at: 2025-04-08T19:50:21Z
body: I kinda like that way more @kadet1090 so I am content 👍 
author_association: CONTRIBUTOR
user: login: luzpaz
created_at: 2025-04-09T15:51:52Z
body: Can someone post a screencast of this feature in action ? TIA
author_association: CONTRIBUTOR