Merge pull request #19671 from kadet1090/placement-indicator

Gui: Add Show Placement helper
This commit is contained in:
Chris Hennes
2025-02-25 17:40:58 +00:00
committed by GitHub
12 changed files with 458 additions and 2 deletions

View File

@@ -119,6 +119,7 @@ public:
inline BoundBox2d(double fX1, double fY1, double fX2, double fY2);
~BoundBox2d() = default;
inline bool IsValid() const;
inline bool IsInfinite() const;
inline bool IsEqual(const BoundBox2d& bbox, double tolerance) const;
// operators
@@ -477,6 +478,11 @@ inline bool BoundBox2d::IsValid() const
return (MaxX >= MinX) && (MaxY >= MinY);
}
inline bool BoundBox2d::IsInfinite() const
{
return MaxX >= DOUBLE_MAX && MaxY >= DOUBLE_MAX && MinX <= -DOUBLE_MAX && MinY <= -DOUBLE_MAX;
}
inline bool BoundBox2d::IsEqual(const BoundBox2d& bbox, double tolerance) const
{
return Vector2d(MinX, MinY).IsEqual(Vector2d(bbox.MinX, bbox.MinY), tolerance)

View File

@@ -101,6 +101,10 @@ template<class float_type>
class Vector3
{
public:
static const Vector3 UnitX;
static const Vector3 UnitY;
static const Vector3 UnitZ;
using num_type = float_type;
using traits_type = float_traits<num_type>;
[[nodiscard]] static constexpr num_type epsilon()
@@ -256,7 +260,12 @@ public:
//@}
};
// global functions
template<class float_type>
Vector3<float_type> const Vector3<float_type>::UnitX(1.0, 0.0, 0.0);
template<class float_type>
Vector3<float_type> const Vector3<float_type>::UnitY(0.0, 1.0, 0.0);
template<class float_type>
Vector3<float_type> const Vector3<float_type>::UnitZ(0.0, 0.0, 1.0);
/// Returns the distance between two points
template<class float_type>

View File

@@ -1080,6 +1080,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
@@ -1112,6 +1113,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

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 Kacper Donat <kacper@kadet.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/>. *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <sstream>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoComplexity.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoCylinder.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoRotation.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/nodes/SoFontStyle.h>
#endif
#include "SoFCPlacementIndicatorKit.h"
#include "So3DAnnotation.h"
#include "SoAxisCrossKit.h"
#include <SoTextLabel.h>
#include <Utilities.h>
#include <ViewParams.h>
#include <ViewProvider.h>
#include <App/Color.h>
#include <Base/Placement.h>
#include <Base/Vector3D.h>
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<SbVec3f>((cylinderOffset + offset) * axis));
pcTranslate->rotation.setValue(Base::convertTo<SbRotation>(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<SbColor>(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<SbRotation>(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<SbColor>(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

View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 Kacper Donat <kacper@kadet.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 SOFCPLACEMENTINDICATORKIT_H
#define SOFCPLACEMENTINDICATORKIT_H
#include <Inventor/nodekits/SoBaseKit.h>
#include <FCGlobal.h>
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

View File

@@ -79,6 +79,8 @@
#include "propertyeditor/PropertyItem.h"
#include "ArcEngine.h"
#include <Inventor/SoFCPlacementIndicatorKit.h>
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();

View File

@@ -381,6 +381,7 @@ SoFrameLabel::SoFrameLabel()
SO_NODE_ADD_FIELD(frame, (true));
SO_NODE_ADD_FIELD(border, (true));
SO_NODE_ADD_FIELD(backgroundUseBaseColor, (false));
SO_NODE_ADD_FIELD(textUseBaseColor, (false));
//SO_NODE_ADD_FIELD(image, (SbVec2s(0,0), 0, NULL));
}
@@ -508,6 +509,15 @@ void SoFrameLabel::GLRender(SoGLRenderAction *action)
}
}
if (textUseBaseColor.getValue()) {
SoState* state = action->getState();
const SbColor& diffuse = SoLazyElement::getDiffuse(state, 0);
if (diffuse != this->textColor.getValue()) {
this->textColor.setValue(diffuse);
}
}
inherited::GLRender(action);
}

View File

@@ -120,6 +120,7 @@ public:
SoSFBool frame;
SoSFBool border;
SoSFBool backgroundUseBaseColor;
SoSFBool textUseBaseColor;
//SoSFImage image;
QPixmap iconPixmap;

View File

@@ -43,6 +43,7 @@
#include "Control.h"
#include "Document.h"
#include "SoFCCSysDragger.h"
#include "Inventor/SoFCPlacementIndicatorKit.h"
#include "SoFCUnifiedSelection.h"
#include "TaskCSysDragger.h"
#include "View3DInventorViewer.h"
@@ -60,6 +61,14 @@ PROPERTY_SOURCE(Gui::ViewProviderDragger, Gui::ViewProviderDocumentObject)
ViewProviderDragger::ViewProviderDragger()
{
ADD_PROPERTY_TYPE(TransformOrigin, ({}), nullptr, App::Prop_Hidden, nullptr);
ADD_PROPERTY_TYPE(ShowPlacement,
(false),
"Display Options",
App::Prop_None,
"If true, placement of object is additionally rendered.");
pcPlacement = new SoSwitch;
pcPlacement->whichChild = SO_SWITCH_NONE;
};
ViewProviderDragger::~ViewProviderDragger() = default;
@@ -98,6 +107,9 @@ void ViewProviderDragger::onChanged(const App::Property* property)
if (property == &TransformOrigin) {
updateDraggerPosition();
}
else if (property == &ShowPlacement) {
pcPlacement->whichChild = ShowPlacement.getValue() ? SO_SWITCH_ALL : SO_SWITCH_NONE;
}
ViewProviderDocumentObject::onChanged(property);
}
@@ -322,6 +334,16 @@ void ViewProviderDragger::setDraggerPlacement(const Base::Placement& placement)
csysDragger->clearIncrementCounts();
}
void ViewProviderDragger::attach(App::DocumentObject* pcObject)
{
ViewProviderDocumentObject::attach(pcObject);
getAnnotation()->addChild(pcPlacement);
auto* pcAxisCrossKit = new Gui::SoFCPlacementIndicatorKit();
pcPlacement->addChild(pcAxisCrossKit);
}
void ViewProviderDragger::updateDraggerPosition()
{
if (!csysDragger) {

View File

@@ -56,11 +56,17 @@ public:
/// destructor.
~ViewProviderDragger() override;
/// Property controlling visibility of the placement indicator, useful for displaying origin
/// position of attached Document Object.
App::PropertyBool ShowPlacement;
/// Origin used when object is transformed. It temporarily changes the origin of object.
/// Dragger is normally placed at the transform origin, unless explicitly overridden via
/// ViewProviderDragger#setDraggerPlacement() method.
App::PropertyPlacement TransformOrigin;
void attach(App::DocumentObject* pcObject) override;
/// Convenience method to obtain the transform origin
Base::Placement getTransformOrigin() const { return TransformOrigin.getValue(); }
/// Convenience method to set the transform origin
@@ -114,6 +120,7 @@ protected:
CoinPtr<SoFCCSysDragger> csysDragger = nullptr;
ViewProvider *forwardedViewProvider = nullptr;
CoinPtr<SoSwitch> pcPlacement;
private:
static void dragStartCallback(void *data, SoDragger *d);
static void dragFinishCallback(void *data, SoDragger *d);

View File

@@ -396,6 +396,12 @@ void ViewProvider2DObject::updatePlane()
Base::ViewOrthoProjMatrix proj(place.inverse().toMatrix());
Base::BoundBox2d bb = bbox.ProjectBox(&proj);
// when projection of invalid it often results in infinite shapes
// if that happens we simply use some small bounding box to mark plane
if (bb.IsInfinite() || !bb.IsValid()) {
bb = Base::BoundBox2d(-1, -1, 1, 1);
}
SbVec3f verts[4] = {
SbVec3f(bb.MinX - horizontalPlanePadding, bb.MinY - verticalPlanePadding, 0),
SbVec3f(bb.MinX - horizontalPlanePadding, bb.MaxY + verticalPlanePadding, 0),

View File

@@ -42,7 +42,7 @@ class PartGuiExport ViewProvider2DObject : public PartGui::ViewProviderPart
PROPERTY_HEADER_WITH_OVERRIDE(PartGui::ViewProvider2DObject);
static constexpr float horizontalPlanePadding = 8;
static constexpr float verticalPlanePadding = 5;
static constexpr float verticalPlanePadding = 6;
public:
/// constructor