From 2a6dcf02bdaa553f47d386e199cf370f79fe8983 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 26 Mar 2026 23:13:43 +0000 Subject: [PATCH 01/24] add properties groups --- .agents/rules/micromamba.md | 7 ++ app/gcompose/src/PropertyWidgets.cpp | 131 ++++++++++++++++------ app/gcompose/src/PropertyWidgets.h | 17 ++- app/gcompose/src/Settings.h | 75 +++++++++++++ app/gcompose/src/ViewportPane.cpp | 39 ++++--- app/gcompose/src/ViewportPane.h | 2 + src/Core/Object.cpp | 4 +- src/Core/Property.h | 61 ++++++++-- src/Core/testing/CMakeLists.txt | 1 + src/Core/testing/PropertyGroupingTest.cpp | 78 +++++++++++++ src/Vtk/uLibVtkInterface.cxx | 78 +++++++------ src/Vtk/uLibVtkInterface.h | 34 +++++- 12 files changed, 420 insertions(+), 107 deletions(-) create mode 100644 .agents/rules/micromamba.md create mode 100644 app/gcompose/src/Settings.h create mode 100644 src/Core/testing/PropertyGroupingTest.cpp diff --git a/.agents/rules/micromamba.md b/.agents/rules/micromamba.md new file mode 100644 index 0000000..be21db6 --- /dev/null +++ b/.agents/rules/micromamba.md @@ -0,0 +1,7 @@ +--- +trigger: always_on +--- + +build in build directory using always micromamba "mutom" env. +build with make flag -j$(nproc). + diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index 83ef094..f12fd29 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -7,6 +7,7 @@ #include "Vtk/uLibVtkInterface.h" #include "Math/Units.h" #include "Math/Dense.h" +#include "Settings.h" namespace uLib { namespace Qt { @@ -15,8 +16,21 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent) : QWidget(parent), m_BaseProperty(prop) { m_Layout = new QHBoxLayout(this); m_Layout->setContentsMargins(4, 2, 4, 2); - m_Label = new QLabel(QString::fromStdString(prop->GetName()), this); - m_Label->setMinimumWidth(100); + + std::string unit = prop->GetUnits(); + QString labelText = QString::fromStdString(prop->GetName()); + if (!unit.empty()) { + auto dim = Settings::Instance().IdentifyDimension(unit); + std::string pref = Settings::Instance().GetPreferredUnit(dim); + if (!pref.empty()) { + labelText += " [" + QString::fromStdString(pref) + "]"; + } else { + labelText += " [" + QString::fromStdString(unit) + "]"; + } + } + + m_Label = new QLabel(labelText, this); + m_Label->setMinimumWidth(120); m_Layout->addWidget(m_Label); } PropertyWidgetBase::~PropertyWidgetBase() { @@ -115,9 +129,6 @@ void UnitLineEdit::updateText() { s += ".0"; } } - if (!m_Suffix.isEmpty()) { - s += " " + m_Suffix; - } setText(s); } @@ -129,11 +140,12 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) { DoublePropertyWidget::DoublePropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_Edit = new UnitLineEdit(this); - QString units = QString::fromStdString(prop->GetUnits()); - if (!units.isEmpty()) { - double factor = 1.0; - parseWithUnits("1 " + units, &factor); - m_Edit->setUnits(units, factor); + std::string unit = prop->GetUnits(); + if (!unit.empty()) { + auto dim = Settings::Instance().IdentifyDimension(unit); + std::string pref = Settings::Instance().GetPreferredUnit(dim); + double factor = Settings::Instance().GetUnitFactor(pref); + m_Edit->setUnits(QString::fromStdString(pref), factor); } m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); @@ -146,11 +158,12 @@ DoublePropertyWidget::DoublePropertyWidget(Property* prop, QWidget* pare FloatPropertyWidget::FloatPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_Edit = new UnitLineEdit(this); - QString units = QString::fromStdString(prop->GetUnits()); - if (!units.isEmpty()) { - double factor = 1.0; - parseWithUnits("1 " + units, &factor); - m_Edit->setUnits(units, factor); + std::string unit = prop->GetUnits(); + if (!unit.empty()) { + auto dim = Settings::Instance().IdentifyDimension(unit); + std::string pref = Settings::Instance().GetPreferredUnit(dim); + double factor = Settings::Instance().GetUnitFactor(pref); + m_Edit->setUnits(QString::fromStdString(pref), factor); } m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); @@ -164,11 +177,12 @@ IntPropertyWidget::IntPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_Edit = new UnitLineEdit(this); m_Edit->setIntegerOnly(true); - QString units = QString::fromStdString(prop->GetUnits()); - if (!units.isEmpty()) { - double factor = 1.0; - parseWithUnits("1 " + units, &factor); - m_Edit->setUnits(units, factor); + std::string unit = prop->GetUnits(); + if (!unit.empty()) { + auto dim = Settings::Instance().IdentifyDimension(unit); + std::string pref = Settings::Instance().GetPreferredUnit(dim); + double factor = Settings::Instance().GetUnitFactor(pref); + m_Edit->setUnits(QString::fromStdString(pref), factor); } m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); @@ -211,6 +225,26 @@ StringPropertyWidget::StringPropertyWidget(Property* prop, QWidget* } StringPropertyWidget::~StringPropertyWidget() {} +class GroupHeaderWidget : public QWidget { +public: + GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) { + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 8, 0, 4); + auto* line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + line->setStyleSheet("color: #555;"); + layout->addWidget(line); + auto* label = new QLabel(name, this); + QFont font = label->font(); + font.setBold(true); + font.setPointSize(font.pointSize() + 1); + label->setFont(font); + label->setStyleSheet("color: #aaa; text-transform: uppercase;"); + layout->addWidget(label); + } +}; + class EnumPropertyWidget : public PropertyWidgetBase { PropertyBase* m_Prop; QComboBox* m_Combo; @@ -305,26 +339,51 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { } } + // Group properties by their group string + std::map> groupedProps; + std::vector groupOrder; + for (auto* prop : *props) { - // Priority 1: Check if it provides enum labels - if (!prop->GetEnumLabels().empty()) { - m_ContainerLayout->addWidget(new EnumPropertyWidget(prop, m_Container)); - continue; + std::string group = prop->GetGroup(); + if (groupedProps.find(group) == groupedProps.end()) { + groupOrder.push_back(group); + } + groupedProps[group].push_back(prop); + } + + for (const auto& groupName : groupOrder) { + if (!groupName.empty()) { + m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container)); } - // Priority 2: Standard factory lookup - auto it = m_Factories.find(prop->GetTypeIndex()); - if (it != m_Factories.end()) { - QWidget* widget = it->second(prop, m_Container); - m_ContainerLayout->addWidget(widget); - } else { - // Debug info for unknown types - std::cout << "PropertyEditor: No factory for " << prop->GetName() - << " (Type: " << prop->GetTypeName() << ")" << std::endl; + for (auto* prop : groupedProps[groupName]) { + QWidget* widget = nullptr; + + // Priority 1: Check if it provides enum labels + if (!prop->GetEnumLabels().empty()) { + widget = new EnumPropertyWidget(prop, m_Container); + } else { + // Priority 2: Standard factory lookup + auto it = m_Factories.find(prop->GetTypeIndex()); + if (it != m_Factories.end()) { + widget = it->second(prop, m_Container); + } else { + // Debug info for unknown types + std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName() + << " (Type: " << prop->GetTypeName() << ")" << std::endl; - QWidget* fallback = new PropertyWidgetBase(prop, m_Container); - fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); - m_ContainerLayout->addWidget(fallback); + widget = new PropertyWidgetBase(prop, m_Container); + widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); + } + } + + if (widget) { + if (!groupName.empty()) { + // Indent grouped properties + widget->setContentsMargins(16, 0, 0, 0); + } + m_ContainerLayout->addWidget(widget); + } } } m_ContainerLayout->addStretch(1); diff --git a/app/gcompose/src/PropertyWidgets.h b/app/gcompose/src/PropertyWidgets.h index 43ee623..4891432 100644 --- a/app/gcompose/src/PropertyWidgets.h +++ b/app/gcompose/src/PropertyWidgets.h @@ -16,6 +16,7 @@ #include "Core/Object.h" #include "Core/Signal.h" #include "Math/Dense.h" +#include "Settings.h" namespace uLib { namespace Qt { @@ -93,18 +94,24 @@ class VectorPropertyWidget : public PropertyWidgetBase { public: VectorPropertyWidget(Property* prop, QWidget* parent = nullptr) : PropertyWidgetBase(prop, parent), m_Prop(prop) { - QString units = QString::fromStdString(prop->GetUnits()); + + std::string unit = prop->GetUnits(); double factor = 1.0; - if (!units.isEmpty()) { - parseWithUnits("1 " + units, &factor); + QString prefSuffix; + if (!unit.empty()) { + auto dim = Settings::Instance().IdentifyDimension(unit); + std::string pref = Settings::Instance().GetPreferredUnit(dim); + factor = Settings::Instance().GetUnitFactor(pref); + prefSuffix = QString::fromStdString(pref); } + for (int i = 0; i < Size; ++i) { m_Edits[i] = new UnitLineEdit(this); if (std::is_integral::value) { m_Edits[i]->setIntegerOnly(true); } - if (!units.isEmpty()) { - m_Edits[i]->setUnits(units, factor); + if (!prefSuffix.isEmpty()) { + m_Edits[i]->setUnits(prefSuffix, factor); } m_Layout->addWidget(m_Edits[i], 1); diff --git a/app/gcompose/src/Settings.h b/app/gcompose/src/Settings.h new file mode 100644 index 0000000..a867e03 --- /dev/null +++ b/app/gcompose/src/Settings.h @@ -0,0 +1,75 @@ +#ifndef GCOMPOSE_SETTINGS_H +#define GCOMPOSE_SETTINGS_H + +#include +#include +#include "Math/Units.h" + +namespace uLib { +namespace Qt { + +class Settings { +public: + static Settings& Instance() { + static Settings instance; + return instance; + } + + enum Dimension { + Length, + Angle, + Energy, + Time, + Dimensionless + }; + + void SetPreferredUnit(Dimension dim, const std::string& unit) { + m_PreferredUnits[dim] = unit; + } + + std::string GetPreferredUnit(Dimension dim) const { + auto it = m_PreferredUnits.find(dim); + if (it != m_PreferredUnits.end()) return it->second; + + switch(dim) { + case Length: return "mm"; + case Angle: return "deg"; + case Energy: return "MeV"; + case Time: return "ns"; + default: return ""; + } + } + + double GetUnitFactor(const std::string& unit) const { + if (unit == "m") return CLHEP::meter; + if (unit == "cm") return CLHEP::centimeter; + if (unit == "mm") return CLHEP::millimeter; + if (unit == "um") return CLHEP::micrometer; + if (unit == "deg") return CLHEP::degree; + if (unit == "rad") return CLHEP::radian; + if (unit == "ns") return CLHEP::nanosecond; + if (unit == "s") return CLHEP::second; + if (unit == "ms") return CLHEP::millisecond; + if (unit == "MeV") return CLHEP::megaelectronvolt; + if (unit == "GeV") return CLHEP::gigaelectronvolt; + if (unit == "eV") return CLHEP::electronvolt; + return 1.0; + } + + Dimension IdentifyDimension(const std::string& unit) const { + if (unit == "m" || unit == "cm" || unit == "mm" || unit == "um" || unit == "nm") return Length; + if (unit == "deg" || unit == "rad") return Angle; + if (unit == "MeV" || unit == "GeV" || unit == "eV" || unit == "keV" || unit == "TeV") return Energy; + if (unit == "ns" || unit == "s" || unit == "ms" || unit == "us") return Time; + return Dimensionless; + } + +private: + Settings() {} + std::map m_PreferredUnits; +}; + +} // namespace Qt +} // namespace uLib + +#endif diff --git a/app/gcompose/src/ViewportPane.cpp b/app/gcompose/src/ViewportPane.cpp index 016d68b..9e9d3e3 100644 --- a/app/gcompose/src/ViewportPane.cpp +++ b/app/gcompose/src/ViewportPane.cpp @@ -46,23 +46,25 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt m_layout->addWidget(m_titleBar); - // Main horizontal container for viewport and display panel - QWidget* mainArea = new QWidget(this); - QHBoxLayout* hLayout = new QHBoxLayout(mainArea); - hLayout->setContentsMargins(0, 0, 0, 0); - hLayout->setSpacing(0); - m_layout->addWidget(mainArea); + // Main area with splitter for viewport and display panel + m_areaSplitter = new QSplitter(Qt::Horizontal, this); + m_areaSplitter->setObjectName("ViewportAreaSplitter"); + m_layout->addWidget(m_areaSplitter, 1); // Viewport will be added here via setViewport - m_viewport = new uLib::Vtk::QViewport(mainArea); - hLayout->addWidget(m_viewport); + m_viewport = new uLib::Vtk::QViewport(m_areaSplitter); + m_areaSplitter->addWidget(m_viewport); // Display Panel (Overlay/Slide-out) - m_displayPanel = new QFrame(mainArea); + m_displayPanel = new QFrame(m_areaSplitter); m_displayPanel->setObjectName("DisplayPropertiesPanel"); - m_displayPanel->setFixedWidth(250); + m_displayPanel->setMinimumWidth(150); m_displayPanel->hide(); + m_areaSplitter->addWidget(m_displayPanel); + m_areaSplitter->setStretchFactor(0, 1); + m_areaSplitter->setStretchFactor(1, 0); + QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel); panelLayout->setContentsMargins(5, 5, 5, 5); @@ -72,8 +74,6 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel); panelLayout->addWidget(m_displayEditor); - - hLayout->addWidget(m_displayPanel); connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel); connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu); @@ -85,7 +85,15 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt ViewportPane::~ViewportPane() {} void ViewportPane::toggleDisplayPanel() { - m_displayPanel->setVisible(m_toggleBtn->isChecked()); + bool visible = m_toggleBtn->isChecked(); + m_displayPanel->setVisible(visible); + if (visible && m_areaSplitter->sizes().value(1, 0) == 0) { + QList sizes = m_areaSplitter->sizes(); + int total = sizes[0] + sizes[1]; + sizes[1] = 250; + sizes[0] = total - 250; + m_areaSplitter->setSizes(sizes); + } } void ViewportPane::setObject(uLib::Object* obj) { @@ -107,15 +115,14 @@ void ViewportPane::setObject(uLib::Object* obj) { void ViewportPane::setViewport(QWidget* viewport, const QString& title) { if (m_viewport) { - m_viewport->parentWidget()->layout()->removeWidget(m_viewport); delete m_viewport; } m_viewport = viewport; m_titleLabel->setText(title); m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - auto* mainAreaLayout = static_cast(m_displayPanel->parentWidget()->layout()); - mainAreaLayout->insertWidget(0, m_viewport); + m_areaSplitter->insertWidget(0, m_viewport); + m_areaSplitter->setStretchFactor(0, 1); } void ViewportPane::addVtkViewport() { diff --git a/app/gcompose/src/ViewportPane.h b/app/gcompose/src/ViewportPane.h index 0c74845..fb12642 100644 --- a/app/gcompose/src/ViewportPane.h +++ b/app/gcompose/src/ViewportPane.h @@ -10,6 +10,7 @@ namespace uLib { namespace Qt { class PropertyEditor; } } +class QSplitter; class QVBoxLayout; class QLabel; @@ -39,6 +40,7 @@ private: QVBoxLayout* m_layout; QWidget* m_titleBar; QLabel* m_titleLabel; + QSplitter* m_areaSplitter; QWidget* m_viewport; // Display Properties Overlay diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index 0cf37ce..0e3fe84 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -90,10 +90,10 @@ const std::vector& Object::GetProperties() const { PropertyBase* Object::GetProperty(const std::string& name) const { for (auto* p : d->m_Properties) { - if (p->GetName() == name) return p; + if (p->GetName() == name || p->GetQualifiedName() == name) return p; } for (auto* p : d->m_DynamicProperties) { - if (p->GetName() == name) return p; + if (p->GetName() == name || p->GetQualifiedName() == name) return p; } return nullptr; } diff --git a/src/Core/Property.h b/src/Core/Property.h index 274f43a..b33c6ea 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -2,11 +2,16 @@ #define U_CORE_PROPERTY_H #include +#include #include #include #include // Added #include #include +#include +#include +#include +#include #include "Core/Archives.h" #include "Core/Signal.h" #include "Core/Object.h" @@ -29,6 +34,12 @@ public: static std::vector empty; return empty; } + virtual const std::string& GetGroup() const = 0; + virtual void SetGroup(const std::string& group) = 0; + std::string GetQualifiedName() const { + if (GetGroup().empty()) return GetName(); + return GetGroup() + "." + GetName(); + } // Signal support signals: @@ -51,16 +62,16 @@ template class Property : public PropertyBase { public: // PROXY: Use an existing variable as back-end storage - Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "") - : m_owner(owner), m_name(name), m_units(units), m_value(valuePtr), m_own(false) { + Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "") + : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false) { if (m_owner) { m_owner->RegisterProperty(this); } } // MANAGED: Create and own internal storage - Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "") - : m_owner(owner), m_name(name), m_units(units), m_value(new T(defaultValue)), m_own(true) { + Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "") + : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -76,6 +87,8 @@ public: virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); } virtual const std::string& GetUnits() const override { return m_units; } virtual void SetUnits(const std::string& units) override { m_units = units; } + virtual const std::string& GetGroup() const override { return m_group; } + virtual void SetGroup(const std::string& group) override { m_group = group; } std::string GetValueAsString() const override { @@ -127,6 +140,7 @@ public: private: std::string m_name; std::string m_units; + std::string m_group; T* m_value; bool m_own; Object* m_owner; @@ -149,8 +163,8 @@ typedef Property BoolProperty; */ class EnumProperty : public Property { public: - EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector& labels, const std::string& units = "") - : Property(owner, name, valuePtr, units), m_Labels(labels) {} + EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector& labels, const std::string& units = "", const std::string& group = "") + : Property(owner, name, valuePtr, units, group), m_Labels(labels) {} const std::vector& GetEnumLabels() const override { return m_Labels; } const char* GetTypeName() const override { return "Enum"; } @@ -209,11 +223,20 @@ public: boost::archive::detail::common_oarchive(boost::archive::no_header), m_Object(obj) {} + std::string GetCurrentGroup() const { + std::string group; + for (const auto& g : m_GroupStack) { + if (!group.empty()) group += "."; + group += g; + } + return group; + } + // Core logic: encounter HRP -> Create Dynamic Property template void save_override(const boost::serialization::hrp &t) { if (m_Object) { - Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : ""); + Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); m_Object->RegisterDynamicProperty(p); } } @@ -221,7 +244,7 @@ public: template void save_override(const boost::serialization::hrp_enum &t) { if (m_Object) { - EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : ""); + EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); m_Object->RegisterDynamicProperty(p); } } @@ -229,11 +252,24 @@ public: // Handle standard NVPs by recursing (important for base classes) template void save_override(const boost::serialization::nvp &t) { - boost::archive::detail::common_oarchive::save_override(t.const_value()); + if (t.name()) m_GroupStack.push_back(t.name()); + this->save_helper(t.const_value(), typename boost::is_class::type()); + if (t.name()) m_GroupStack.pop_back(); } - // Ignore everything else - template void save_override(const T &t) {} + // Recursion for nested classes, ignore primitives + template + void save_override(const T &t) { + this->save_helper(t, typename boost::is_class::type()); + } + + template + void save_helper(const T &t, boost::mpl::true_) { + boost::serialization::serialize_adl(*this, const_cast(t), 0); + } + + template + void save_helper(const T &t, boost::mpl::false_) {} // Required attribute overrides for common_oarchive void save_override(const boost::archive::object_id_type & t) {} @@ -244,6 +280,9 @@ public: void save_override(const boost::archive::class_id_reference_type & t) {} void save_override(const boost::archive::class_name_type & t) {} void save_override(const boost::archive::tracking_type & t) {} + +private: + std::vector m_GroupStack; }; /** diff --git a/src/Core/testing/CMakeLists.txt b/src/Core/testing/CMakeLists.txt index c6f5af6..a3e3283 100644 --- a/src/Core/testing/CMakeLists.txt +++ b/src/Core/testing/CMakeLists.txt @@ -23,6 +23,7 @@ set( TESTS VectorMetaAllocatorTest PropertyTypesTest HRPTest + PropertyGroupingTest MutexTest ThreadsTest OpenMPTest diff --git a/src/Core/testing/PropertyGroupingTest.cpp b/src/Core/testing/PropertyGroupingTest.cpp new file mode 100644 index 0000000..9bfcdf6 --- /dev/null +++ b/src/Core/testing/PropertyGroupingTest.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include "Core/Object.h" +#include "Core/Property.h" + +using namespace uLib; + +struct Nested { + float x = 1.0f; + float y = 2.0f; + + ULIB_SERIALIZE_ACCESS + template + void serialize(Archive & ar, const unsigned int version) { + ar & HRP(x); + ar & HRP(y); + } +}; + +class GroupObject : public Object { + uLibTypeMacro(GroupObject, Object) +public: + Nested position; + Nested orientation; + float weight = 50.0f; + + ULIB_SERIALIZE_ACCESS + template + void serialize(Archive & ar, const unsigned int version) { + ar & boost::serialization::make_nvp("Position", position); + ar & boost::serialization::make_nvp("Orientation", orientation); + ar & HRP(weight); + } +}; + +int main() { + std::cout << "Testing Property Grouping..." << std::endl; + + GroupObject obj; + ULIB_ACTIVATE_PROPERTIES(obj); + + auto props = obj.GetProperties(); + std::cout << "Registered " << props.size() << " properties." << std::endl; + + for (auto* p : props) { + std::cout << "Prop: " << p->GetName() + << " Group: " << p->GetGroup() + << " Qualified: " << p->GetQualifiedName() << std::endl; + } + + // Check if nested properties are registered + PropertyBase* p1 = obj.GetProperty("Position.x"); + PropertyBase* p2 = obj.GetProperty("Position.y"); + PropertyBase* p3 = obj.GetProperty("Orientation.x"); + PropertyBase* p4 = obj.GetProperty("Orientation.y"); + PropertyBase* p5 = obj.GetProperty("weight"); + + assert(p1 != nullptr && "Position.x not found"); + assert(p2 != nullptr && "Position.y not found"); + assert(p3 != nullptr && "Orientation.x not found"); + assert(p4 != nullptr && "Orientation.y not found"); + assert(p5 != nullptr && "weight not found"); + + assert(p1->GetGroup() == "Position"); + assert(p2->GetGroup() == "Position"); + assert(p3->GetGroup() == "Orientation"); + assert(p4->GetGroup() == "Orientation"); + assert(p5->GetGroup() == ""); + + assert(p1->GetQualifiedName() == "Position.x"); + assert(p5->GetQualifiedName() == "weight"); + + std::cout << "Property Grouping Tests PASSED!" << std::endl; + + return 0; +} diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 5e9157a..59f21e3 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -61,6 +61,7 @@ #include "uLibVtkInterface.h" #include "vtkHandlerWidget.h" +#include "Math/Dense.h" #include "Core/Property.h" @@ -96,9 +97,9 @@ public: m_Dragable(true) { m_Color[0] = m_Color[1] = m_Color[2] = -1.0; - m_Position[0] = m_Position[1] = m_Position[2] = 0.0; - m_Orientation[0] = m_Orientation[1] = m_Orientation[2] = 0.0; - m_Scale[0] = m_Scale[1] = m_Scale[2] = 1.0; + m_Position = Vector3d::Zero(); + m_Orientation = Vector3d::Zero(); + m_Scale = Vector3d::Ones(); } ~PuppetData() { @@ -124,9 +125,9 @@ public: bool m_Selected; bool m_Visibility; bool m_Dragable; - double m_Position[3]; - double m_Orientation[3]; - double m_Scale[3]; + Vector3d m_Position; + Vector3d m_Orientation; + Vector3d m_Scale; void ApplyAppearance(vtkProp *p) { p->SetVisibility(m_Visibility); @@ -156,9 +157,9 @@ public: // Handle transformation if it's a Prop3D if (auto* p3d = vtkProp3D::SafeDownCast(p)) { // NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation - // p3d->SetPosition(m_Position); - // p3d->SetOrientation(m_Orientation); - // p3d->SetScale(m_Scale); + // p3d->SetPosition(m_Position.data()); + // p3d->SetOrientation(m_Orientation.data()); + // p3d->SetScale(m_Scale.data()); } } @@ -499,9 +500,9 @@ void Puppet::Update() // Apply transformation if it's a Prop3D if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - p3d->SetPosition(pd->m_Position); - p3d->SetOrientation(pd->m_Orientation); - p3d->SetScale(pd->m_Scale); + p3d->SetPosition(pd->m_Position.data()); + p3d->SetOrientation(pd->m_Orientation.data()); + p3d->SetScale(pd->m_Scale.data()); } } @@ -549,9 +550,9 @@ void Puppet::SyncFromVtk() // Update properties for (int i=0; i<3; ++i) { - pd->m_Position[i] = pos[i]; - pd->m_Orientation[i] = ori[i]; - pd->m_Scale[i] = scale[i]; + pd->m_Position(i) = pos[i]; + pd->m_Orientation(i) = ori[i]; + pd->m_Scale(i) = scale[i]; } // Get the properties from the object @@ -567,26 +568,37 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) { } +struct TransformProxy { + PuppetData* pd; + template + void serialize(Archive & ar, const unsigned int version) { + ar & boost::serialization::make_hrp("Position", pd->m_Position, "mm"); + ar & boost::serialization::make_hrp("Orientation", pd->m_Orientation, "deg"); + ar & boost::serialization::make_hrp("Scale", pd->m_Scale, ""); + } +}; + +struct AppearanceProxy { + PuppetData* pd; + template + void serialize(Archive & ar, const unsigned int version) { + ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]); + ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]); + ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]); + ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity); + ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); + ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); + ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); + ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); + } +}; + void Puppet::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) { - ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]); - ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]); - ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]); - ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity); - ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); - ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); - ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); - ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); + AppearanceProxy appearance{pd}; + ar & boost::serialization::make_nvp("Appearance", appearance); - // Geometry knobs (caution: these might be overridden by internal matrices) - ar & boost::serialization::make_hrp("PosX", pd->m_Position[0], "mm"); - ar & boost::serialization::make_hrp("PosY", pd->m_Position[1], "mm"); - ar & boost::serialization::make_hrp("PosZ", pd->m_Position[2], "mm"); - ar & boost::serialization::make_hrp("OriX", pd->m_Orientation[0], "deg"); - ar & boost::serialization::make_hrp("OriY", pd->m_Orientation[1], "deg"); - ar & boost::serialization::make_hrp("OriZ", pd->m_Orientation[2], "deg"); - ar & boost::serialization::make_hrp("ScaleX", pd->m_Scale[0]); - ar & boost::serialization::make_hrp("ScaleY", pd->m_Scale[1]); - ar & boost::serialization::make_hrp("ScaleZ", pd->m_Scale[2]); + TransformProxy transform{pd}; + ar & boost::serialization::make_nvp("Transform", transform); } void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { } diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index 2b086bb..d0e7332 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #include "Core/Object.h" #include "Core/Property.h" #include "Core/Monitor.h" @@ -157,10 +160,19 @@ public: boost::archive::detail::common_oarchive(boost::archive::no_header), m_Puppet(puppet) {} + std::string GetCurrentGroup() const { + std::string group; + for (const auto& g : m_GroupStack) { + if (!group.empty()) group += "."; + group += g; + } + return group; + } + template void save_override(const boost::serialization::hrp &t) { if (m_Puppet) { - uLib::Property* p = new uLib::Property(m_Puppet, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : ""); + uLib::Property* p = new uLib::Property(m_Puppet, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); m_Puppet->RegisterDisplayProperty(p); Vtk::Puppet* puppet = m_Puppet; uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); @@ -170,7 +182,7 @@ public: template void save_override(const boost::serialization::hrp_enum &t) { if (m_Puppet) { - uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : ""); + uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); m_Puppet->RegisterDisplayProperty(p); Vtk::Puppet* puppet = m_Puppet; uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); @@ -178,10 +190,23 @@ public: } template void save_override(const boost::serialization::nvp &t) { - boost::archive::detail::common_oarchive::save_override(t.const_value()); + if (t.name()) m_GroupStack.push_back(t.name()); + this->save_helper(t.const_value(), typename boost::is_class::type()); + if (t.name()) m_GroupStack.pop_back(); } - template void save_override(const T &t) {} + // Recursion for nested classes, ignore primitives + template void save_override(const T &t) { + this->save_helper(t, typename boost::is_class::type()); + } + + template + void save_helper(const T &t, boost::mpl::true_) { + boost::serialization::serialize_adl(*this, const_cast(t), 0); + } + + template + void save_helper(const T &t, boost::mpl::false_) {} void save_override(const boost::archive::object_id_type & t) {} void save_override(const boost::archive::object_reference_type & t) {} @@ -194,6 +219,7 @@ public: private: Vtk::Puppet* m_Puppet; + std::vector m_GroupStack; }; } // namespace Archive From 09859e872c7da9916329cd48f75a83df609ee63d Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 01:49:27 +0000 Subject: [PATCH 02/24] fix build --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6e0d82..59eb58f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() project(uLib) # CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available. -option(USE_CUDA "Enable CUDA support" ON) +option(USE_CUDA "Enable CUDA support" OFF) if(USE_CUDA) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler") set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") @@ -115,7 +115,7 @@ set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}") -find_package(HDF5 REQUIRED CONFIG) +find_package(HDF5 REQUIRED) find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) From 93e560256269e3de0f66cb322dcbd25c02e8f465 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 02:43:30 +0000 Subject: [PATCH 03/24] transform properties --- src/Core/ObjectsContext.h | 2 +- src/Core/Property.h | 5 + src/HEP/Geant/EmitterPrimary.hh | 2 +- src/Math/Assembly.h | 1 + src/Math/ContainerBox.h | 77 ++++++++------- src/Math/Cylinder.h | 53 ++++++---- src/Math/Geometry.h | 12 ++- src/Math/QuadMesh.h | 5 +- src/Math/Transform.h | 165 +++++++++++++++++++++++++++----- src/Math/TriangleMesh.h | 5 +- src/Vtk/uLibVtkInterface.cxx | 65 ++++++++++--- src/Vtk/vtkObjectsContext.cpp | 21 ++-- 12 files changed, 299 insertions(+), 114 deletions(-) diff --git a/src/Core/ObjectsContext.h b/src/Core/ObjectsContext.h index 4264e61..f555dd2 100644 --- a/src/Core/ObjectsContext.h +++ b/src/Core/ObjectsContext.h @@ -9,7 +9,7 @@ namespace uLib { /** * @brief ObjectsContext represents a collection of Object instances. */ -class ObjectsContext : public Object { +class ObjectsContext : virtual public Object { public: ObjectsContext(); virtual ~ObjectsContext(); diff --git a/src/Core/Property.h b/src/Core/Property.h index b33c6ea..0696818 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -137,6 +137,11 @@ public: void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_impl(ar, v); } + virtual void Updated() override { + PropertyBase::Updated(); + this->PropertyChanged(); + } + private: std::string m_name; std::string m_units; diff --git a/src/HEP/Geant/EmitterPrimary.hh b/src/HEP/Geant/EmitterPrimary.hh index 67401f6..8c81d28 100644 --- a/src/HEP/Geant/EmitterPrimary.hh +++ b/src/HEP/Geant/EmitterPrimary.hh @@ -23,7 +23,7 @@ class G4Event; namespace uLib { namespace Geant { -class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public Object, public AffineTransform +class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransform { public: diff --git a/src/Math/Assembly.h b/src/Math/Assembly.h index 7440062..9ee9242 100644 --- a/src/Math/Assembly.h +++ b/src/Math/Assembly.h @@ -45,6 +45,7 @@ namespace uLib { */ class Assembly : public ObjectsContext, public AffineTransform { public: + uLibTypeMacro(Assembly, ObjectsContext, AffineTransform) virtual const char *GetClassName() const override { return "Assembly"; } Assembly(); diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index dc8fba4..2a2f926 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -44,16 +44,17 @@ namespace uLib { * that defines the box's specific origin and size relative to its own * coordinate system. */ -class ContainerBox : public AffineTransform, public Object { - - typedef AffineTransform BaseClass; +class ContainerBox : public AffineTransform { public: + uLibTypeMacro(ContainerBox, AffineTransform) + + virtual const char * GetClassName() const override { return "ContainerBox"; } + //////////////////////////////////////////////////////////////////////////// // PROPERTIES // - Property p_Size; - Property p_Origin; - virtual const char * GetClassName() const { return "ContainerBox"; } + Vector3f Size; + Vector3f Origin; /** * @brief Default constructor. @@ -61,10 +62,10 @@ public: */ ContainerBox() : m_LocalT(this), // BaseClass is Parent of m_LocalTransform - p_Size(this, "Size", Vector3f(1.0f, 1.0f, 1.0f)), - p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) { - Object::connect(&p_Size, &Property::PropertyChanged, this, &ContainerBox::SyncSize); - Object::connect(&p_Origin, &Property::PropertyChanged, this, &ContainerBox::SyncOrigin); + Size(1.0f, 1.0f, 1.0f), + Origin(0.0f, 0.0f, 0.0f) { + ULIB_ACTIVATE_PROPERTIES(*this); + this->Sync(); } /** @@ -73,11 +74,10 @@ public: */ ContainerBox(const Vector3f &size) : m_LocalT(this), - p_Size(this, "Size", size), - p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) { - Object::connect(&p_Size, &Property::PropertyChanged, this, &ContainerBox::SyncSize); - Object::connect(&p_Origin, &Property::PropertyChanged, this, &ContainerBox::SyncOrigin); - this->SetSize(size); + Size(size), + Origin(0.0f, 0.0f, 0.0f) { + ULIB_ACTIVATE_PROPERTIES(*this); + this->Sync(); } /** @@ -85,13 +85,21 @@ public: * @param copy The ContainerBox instance to copy from. */ ContainerBox(const ContainerBox ©) - : m_LocalT(copy.m_LocalT), // Copy local transform state + : m_LocalT(this), // Reset parent to the new object AffineTransform(copy), - p_Size(this, "Size", copy.p_Size), - p_Origin(this, "Origin", copy.p_Origin) { - m_LocalT.SetParent(this); // Reset parent to the new object - Object::connect(&p_Size, &Property::PropertyChanged, this, &ContainerBox::SyncSize); - Object::connect(&p_Origin, &Property::PropertyChanged, this, &ContainerBox::SyncOrigin); + Size(copy.Size), + Origin(copy.Origin) { + ULIB_ACTIVATE_PROPERTIES(*this); + this->Sync(); + } + + /** + * @brief Serialization template for property registration and persistence. + */ + template + void serialize(ArchiveT & ar, const unsigned int version) { + ar & HRP(Size); + ar & HRP(Origin); } /** @@ -99,7 +107,7 @@ public: * @param v The origin position vector. */ void SetOrigin(const Vector3f &v) { - p_Origin = v; + Origin = v; m_LocalT.SetPosition(v); } @@ -115,7 +123,7 @@ public: * @param v The size vector (width, height, depth). */ void SetSize(const Vector3f &v) { - p_Size = v; + Size = v; Vector3f pos = this->GetOrigin(); m_LocalT = AffineTransform(this); // regenerate local transform m_LocalT.Scale(v); @@ -194,26 +202,27 @@ public: } /** Translate using transformation chain */ - using BaseClass::Translate; + using AffineTransform::Translate; /** Rotate using transformation chain */ - using BaseClass::Rotate; + using AffineTransform::Rotate; /** Scale using transformation chain */ - using BaseClass::Scale; + using AffineTransform::Scale; signals: - // signal to emit when the box is updated // - virtual void Updated() override { ULIB_SIGNAL_EMIT(ContainerBox::Updated); } - -private slots: - void SyncSize() { - this->SetSize(p_Size); + /** Signal emitted when properties change */ + virtual void Updated() override { + this->Sync(); + ULIB_SIGNAL_EMIT(ContainerBox::Updated); } - void SyncOrigin() { - this->SetOrigin(p_Origin); +private: + /** Synchronizes internal transformation with properties */ + void Sync() { + this->SetOrigin(Origin); + this->SetSize(Size); } diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index b7f2505..a9b33ce 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -39,10 +39,17 @@ namespace uLib { * The cylinder orientation is defined by the Axis property (0=X, 1=Y, 2=Z). * By default, it is aligned with the Y axis (Axis=1). */ -class Cylinder : public AffineTransform, public Object { +class Cylinder : public AffineTransform { public: - uLibTypeMacro(Cylinder, Object) + uLibTypeMacro(Cylinder, AffineTransform) + + /** + * @brief PROPERTIES + */ + float Radius; + float Height; + int Axis; virtual const char * GetClassName() const override { return "Cylinder"; } @@ -51,7 +58,7 @@ public: */ Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) { ULIB_ACTIVATE_PROPERTIES(*this); - UpdateLocalMatrix(); + this->Sync(); } /** @@ -60,7 +67,7 @@ public: Cylinder(float radius, float height, int axis = 1) : m_LocalT(this), Radius(radius), Height(height), Axis(axis) { ULIB_ACTIVATE_PROPERTIES(*this); - UpdateLocalMatrix(); + this->Sync(); } /** @@ -69,7 +76,7 @@ public: Cylinder(const Cylinder ©) : m_LocalT(this), AffineTransform(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) { ULIB_ACTIVATE_PROPERTIES(*this); - this->UpdateLocalMatrix(); + this->Sync(); } /** @@ -85,7 +92,7 @@ public: /** Sets the radius of the cylinder */ inline void SetRadius(float r) { Radius = r; - UpdateLocalMatrix(); + this->Sync(); } /** Gets the radius of the cylinder */ @@ -94,7 +101,7 @@ public: /** Sets the height of the cylinder */ inline void SetHeight(float h) { Height = h; - UpdateLocalMatrix(); + this->Sync(); } /** Gets the height of the cylinder */ @@ -103,7 +110,7 @@ public: /** Sets the main axis (0=X, 1=Y, 2=Z) */ inline void SetAxis(int axis) { Axis = axis; - UpdateLocalMatrix(); + this->Sync(); } /** Gets the main axis */ @@ -157,25 +164,33 @@ public: return Vector3f(r, theta, h); } + /** Translate using transformation chain */ + using AffineTransform::Translate; + + /** Rotate using transformation chain */ + using AffineTransform::Rotate; + + /** Scale using transformation chain */ + using AffineTransform::Scale; + signals: /** Signal emitted when properties change */ virtual void Updated() override { - this->UpdateLocalMatrix(); + this->Sync(); ULIB_SIGNAL_EMIT(Cylinder::Updated); } private: - /** Recalculates the internal local matrix based on dimensions and axis */ - void UpdateLocalMatrix() { - m_LocalT = AffineTransform(this); - if (Axis == 0) m_LocalT.Scale(Vector3f(Height, Radius, Radius)); - else if (Axis == 1) m_LocalT.Scale(Vector3f(Radius, Height, Radius)); - else m_LocalT.Scale(Vector3f(Radius, Radius, Height)); - } + /** Synchronizes internal transformation with properties */ + void Sync() { + m_LocalT = AffineTransform(this); + if (Axis == 0) m_LocalT.Scale(Vector3f(Height, Radius, Radius)); + else if (Axis == 1) m_LocalT.Scale(Vector3f(Radius, Height, Radius)); + else m_LocalT.Scale(Vector3f(Radius, Radius, Height)); + } - float Radius; - float Height; - int Axis; + +private: AffineTransform m_LocalT; }; diff --git a/src/Math/Geometry.h b/src/Math/Geometry.h index 7c35e2b..5322b68 100644 --- a/src/Math/Geometry.h +++ b/src/Math/Geometry.h @@ -35,10 +35,11 @@ namespace uLib { -class Geometry : public AffineTransform, public Object { +class Geometry : public AffineTransform { public: + uLibTypeMacro(Geometry, AffineTransform) - virtual const char * GetClassName() const { return "Geometry"; } + virtual const char * GetClassName() const override { return "Geometry"; } virtual Vector3f ToLinear(const Vector3f& curved_space) const { return curved_space; @@ -70,6 +71,7 @@ public: class CylindricalGeometry : public Geometry { public: + uLibTypeMacro(CylindricalGeometry, Geometry) CylindricalGeometry() {} Vector3f ToLinear(const Vector3f& cylindrical) const { @@ -88,9 +90,10 @@ public: class SphericalGeometry : public Geometry { public: + uLibTypeMacro(SphericalGeometry, Geometry) SphericalGeometry() {} - virtual const char * GetClassName() const { return "SphericalGeometry"; } + virtual const char * GetClassName() const override { return "SphericalGeometry"; } Vector3f ToLinear(const Vector3f& spherical) const { float r = spherical.x(); @@ -112,9 +115,10 @@ public: class ToroidalGeometry : public Geometry { public: + uLibTypeMacro(ToroidalGeometry, Geometry) ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {} - virtual const char * GetClassName() const { return "ToroidalGeometry"; } + virtual const char * GetClassName() const override { return "ToroidalGeometry"; } Vector3f ToLinear(const Vector3f& toroidal) const { float r = toroidal.x(); diff --git a/src/Math/QuadMesh.h b/src/Math/QuadMesh.h index e98d5ce..7652c0f 100644 --- a/src/Math/QuadMesh.h +++ b/src/Math/QuadMesh.h @@ -34,11 +34,12 @@ namespace uLib { -class QuadMesh : public AffineTransform, public Object +class QuadMesh : public AffineTransform { public: + uLibTypeMacro(QuadMesh, AffineTransform) - virtual const char * GetClassName() const { return "QuadMesh"; } + virtual const char * GetClassName() const override { return "QuadMesh"; } void PrintSelf(std::ostream &o); diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 73cfacd..70ab729 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -50,6 +50,7 @@ #define U_TRANSFORM_H #include +#include "Math/Units.h" #include "Math/Dense.h" @@ -59,27 +60,65 @@ namespace uLib { ///////// AFFINE TRANSFORM WRAPPER ////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -class AffineTransform { +class AffineTransform : virtual public Object { +public: + uLibTypeMacro(AffineTransform, Object) + + /** + * @brief Grouped transformation parameters for property-based control. + */ + struct { + Vector3f Position = Vector3f::Zero(); + Vector3f Orientation = Vector3f::Zero(); + Vector3f Scale = Vector3f::Ones(); + + template + void serialize(ArchiveT & ar, const unsigned int version) { + ar & HRPU(Position, "mm"); + ar & HRPU(Orientation, "deg"); + ar & HRP(Scale); + } + } Transform; + protected: Eigen::Affine3f m_T; AffineTransform *m_Parent; + public: AffineTransform() : m_T(Matrix4f::Identity()), m_Parent(NULL) - {} + { + ULIB_ACTIVATE_PROPERTIES(*this); + this->Sync(); + } virtual ~AffineTransform() {} AffineTransform(AffineTransform *parent) : m_T(Matrix4f::Identity()), m_Parent(parent) - {} + { + ULIB_ACTIVATE_PROPERTIES(*this); + this->Sync(); + } AffineTransform(const AffineTransform ©) : m_T(copy.m_T), - m_Parent(copy.m_Parent) - {} + m_Parent(copy.m_Parent), + Transform(copy.Transform) + { + ULIB_ACTIVATE_PROPERTIES(*this); + this->Sync(); + } + + /** + * @brief Registration of properties in groups. + */ + template + void serialize(ArchiveT & ar, const unsigned int version) { + ar & boost::serialization::make_nvp("Transform", Transform); + } Eigen::Affine3f& GetTransform() { return m_T; } @@ -87,7 +126,11 @@ public: void SetParent(AffineTransform *name) { this->m_Parent = name; } - void SetMatrix (Matrix4f mat) { m_T.matrix() = mat; } + void SetMatrix (Matrix4f mat) { + m_T.matrix() = mat; + this->UpdatePropertiesFromMatrix(); + } + Matrix4f GetMatrix() const { return m_T.matrix(); } Matrix4f GetWorldMatrix() const @@ -96,32 +139,51 @@ public: else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A // } - void SetPosition(const Vector3f v) { this->m_T.translation() = v; } + void SetPosition(const Vector3f v) { + this->Transform.Position = v; + this->Sync(); + } - Vector3f GetPosition() const { return this->m_T.translation(); } + Vector3f GetPosition() const { return this->Transform.Position; } - void SetRotation(const Matrix3f m) { this->m_T.linear() = m; } + void SetRotation(const Matrix3f m) { + this->m_T.linear() = m; + this->UpdatePropertiesFromMatrix(); + } Matrix3f GetRotation() const { return this->m_T.rotation(); } - void Translate(const Vector3f v) { this->m_T.translate(v); } - - void Scale(const Vector3f v) { this->m_T.scale(v); } - - Vector3f GetScale() const { - return Vector3f(m_T.linear().col(0).norm(), - m_T.linear().col(1).norm(), - m_T.linear().col(2).norm()); + void Translate(const Vector3f v) { + this->Transform.Position += v; + this->Sync(); } + void Scale(const Vector3f v) { + this->Transform.Scale = this->Transform.Scale.cwiseProduct(v); + this->Sync(); + } - void Rotate(const Matrix3f m) { this->m_T.rotate(m); } + Vector3f GetScale() const { return this->Transform.Scale; } + + void SetOrientation(const Vector3f v) { + this->Transform.Orientation = v; + this->Sync(); + } + + Vector3f GetOrientation() const { return this->Transform.Orientation; } + + + void Rotate(const Matrix3f m) { + this->m_T.rotate(m); + this->UpdatePropertiesFromMatrix(); + } void Rotate(const float angle, Vector3f axis) { - axis.normalize(); // prehaps not necessary ( see eigens ) + axis.normalize(); Eigen::AngleAxisf ax(angle,axis); this->m_T.rotate(Eigen::Quaternion(ax)); + this->UpdatePropertiesFromMatrix(); } void Rotate(const Vector3f euler_axis) { @@ -129,17 +191,14 @@ public: Rotate(angle,euler_axis); } - void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); } + void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); this->UpdatePropertiesFromMatrix(); } void QuaternionRotate(const Vector4f q) - { this->m_T.rotate(Eigen::Quaternion(q)); } + { this->m_T.rotate(Eigen::Quaternion(q)); this->UpdatePropertiesFromMatrix(); } void EulerYZYRotate(const Vector3f e) { - Matrix3f mat; - mat = Eigen::AngleAxisf(e.x(), Vector3f::UnitY()) - * Eigen::AngleAxisf(e.y(), Vector3f::UnitZ()) - * Eigen::AngleAxisf(e.z(), Vector3f::UnitY()); - m_T.rotate(mat); + this->Transform.Orientation = e; + this->Sync(); } void FlipAxes(int first, int second) @@ -147,7 +206,61 @@ public: Matrix3f mat = Matrix3f::Identity(); mat.col(first).swap(mat.col(second)); m_T.rotate(mat); + this->UpdatePropertiesFromMatrix(); } + + /** + * @brief Decomposes the internal matrix m_T back into Position, Orientation, and Scale properties. + */ + void UpdatePropertiesFromMatrix() { + // 1. Position + Transform.Position = m_T.translation(); + + // 2. Scale + Matrix3f linear = m_T.linear(); + Transform.Scale(0) = linear.col(0).norm(); + Transform.Scale(1) = linear.col(1).norm(); + Transform.Scale(2) = linear.col(2).norm(); + + // 3. Rotation (Normalization removes scale) + Matrix3f rotation = linear; + if (Transform.Scale(0) > 1e-6) rotation.col(0) /= Transform.Scale(0); + if (Transform.Scale(1) > 1e-6) rotation.col(1) /= Transform.Scale(1); + if (Transform.Scale(2) > 1e-6) rotation.col(2) /= Transform.Scale(2); + + // Euler YZY (indices 1, 2, 1) + Vector3f euler = rotation.eulerAngles(1, 2, 1); + Transform.Orientation = euler / CLHEP::degree; + + // Notify properties + PropertyBase* p; + if ((p = this->GetProperty("Transform.Position"))) p->Updated(); + if ((p = this->GetProperty("Transform.Orientation"))) p->Updated(); + if ((p = this->GetProperty("Transform.Scale"))) p->Updated(); + } + +signals: + /** Signal emitted when properties change */ + virtual void Updated() override { + this->Sync(); + ULIB_SIGNAL_EMIT(AffineTransform::Updated); + } + +private: + /** Synchronizes m_T with properties */ + void Sync() { + m_T = Eigen::Affine3f::Identity(); + m_T.translate(Transform.Position); + + // Orientation (using YZY order as implied by EulerYZYRotate) + Matrix3f mat; + mat = Eigen::AngleAxisf(Transform.Orientation.x() * CLHEP::degree, Vector3f::UnitY()) + * Eigen::AngleAxisf(Transform.Orientation.y() * CLHEP::degree, Vector3f::UnitZ()) + * Eigen::AngleAxisf(Transform.Orientation.z() * CLHEP::degree, Vector3f::UnitY()); + m_T.rotate(mat); + + m_T.scale(Transform.Scale); + } }; diff --git a/src/Math/TriangleMesh.h b/src/Math/TriangleMesh.h index 4acfe58..94f5582 100644 --- a/src/Math/TriangleMesh.h +++ b/src/Math/TriangleMesh.h @@ -37,11 +37,12 @@ namespace uLib { -class TriangleMesh : public AffineTransform, public Object +class TriangleMesh : public AffineTransform { public: + uLibTypeMacro(TriangleMesh, AffineTransform) - virtual const char * GetClassName() const { return "TriangleMesh"; } + virtual const char * GetClassName() const override { return "TriangleMesh"; } void PrintSelf(std::ostream &o); diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 59f21e3..2163f24 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -41,6 +41,7 @@ #include #include "vtkViewport.h" #include "uLibVtkInterface.h" +#include "Math/Transform.h" #include #include #include @@ -484,7 +485,7 @@ void Puppet::SetSelected(bool selected) if (!pd->m_Selectable) return; if (pd->m_Selected == selected) return; pd->m_Selected = selected; - pd->UpdateHighlight(); + pd->UpdateHighlight();0 } bool Puppet::IsSelected() const @@ -498,8 +499,25 @@ void Puppet::Update() if (root) { pd->ApplyAppearance(root); - // Apply transformation if it's a Prop3D - if (auto* p3d = vtkProp3D::SafeDownCast(root)) { + // Handle transformation synchronization from content + if (auto* content = dynamic_cast(GetContent())) { + pd->m_Position = content->GetPosition().cast(); + pd->m_Orientation = content->GetOrientation().cast(); + pd->m_Scale = content->GetScale().cast(); + + if (auto* p3d = vtkProp3D::SafeDownCast(root)) { + vtkNew vmat; + const Matrix4f& emat = content->GetMatrix(); + for(int i=0; i<4; ++i) for(int j=0; j<4; ++j) vmat->SetElement(i, j, emat(i,j)); + p3d->SetUserMatrix(vmat); + + // Clear base transform to avoid double-application + p3d->SetPosition(0,0,0); + p3d->SetOrientation(0,0,0); + p3d->SetScale(1,1,1); + } + } + else if (auto* p3d = vtkProp3D::SafeDownCast(root)) { p3d->SetPosition(pd->m_Position.data()); p3d->SetOrientation(pd->m_Orientation.data()); p3d->SetScale(pd->m_Scale.data()); @@ -539,23 +557,42 @@ void Puppet::Update() } } + void Puppet::SyncFromVtk() { vtkProp* root = this->GetProp(); if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - double pos[3], ori[3], scale[3]; - p3d->GetPosition(pos); - p3d->GetOrientation(ori); - p3d->GetScale(scale); - - // Update properties - for (int i=0; i<3; ++i) { - pd->m_Position(i) = pos[i]; - pd->m_Orientation(i) = ori[i]; - pd->m_Scale(i) = scale[i]; + // Handle content synchronization if it's an AffineTransform + if (auto* content = dynamic_cast(GetContent())) { + vtkMatrix4x4* vmat = p3d->GetUserMatrix(); + if (vmat) { + Matrix4f emat; + for (int i=0; i<4; ++i) + for (int j=0; j<4; ++j) + emat(i, j) = vmat->GetElement(i, j); + content->SetMatrix(emat); + + // Re-sync internal puppet properties from the now-updated content + pd->m_Position = content->GetPosition().cast(); + pd->m_Orientation = content->GetOrientation().cast(); + pd->m_Scale = content->GetScale().cast(); + } + } + else { + // Update internal puppet properties directly from base components + // only if no content exists (old behavior) + double pos[3], ori[3], scale[3]; + p3d->GetPosition(pos); + p3d->GetOrientation(ori); + p3d->GetScale(scale); + for (int i=0; i<3; ++i) { + pd->m_Position(i) = pos[i]; + pd->m_Orientation(i) = ori[i]; + pd->m_Scale(i) = scale[i]; + } } - // Get the properties from the object + // Notify puppet properties updated if (auto* propPos = this->GetProperty("Position")) propPos->Updated(); if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated(); if (auto* propScale = this->GetProperty("Scale")) propScale->Updated(); diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 13121d9..4e17ea0 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -110,17 +110,16 @@ void vtkObjectsContext::Update() { Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { if (!obj) return nullptr; - const char* className = obj->GetClassName(); - if (std::strcmp(className, "ContainerBox") == 0) { - return new vtkContainerBox(static_cast(obj)); - } else if (std::strcmp(className, "DetectorChamber") == 0) { - return new vtkDetectorChamber(static_cast(obj)); - } else if (std::strcmp(className, "Cylinder") == 0) { - return new vtkCylinder(static_cast(obj)); - } else if (std::strcmp(className, "VoxImage") == 0) { - return new vtkVoxImage(*static_cast(obj)); - } else if (std::strcmp(className, "Assembly") == 0) { - return new Assembly(static_cast(obj)); + if (auto* box = dynamic_cast(obj)) { + return new vtkContainerBox(box); + } else if (auto* chamber = dynamic_cast(obj)) { + return new vtkDetectorChamber(chamber); + } else if (auto* cylinder = dynamic_cast(obj)) { + return new vtkCylinder(cylinder); + } else if (auto* vox = dynamic_cast(obj)) { + return new vtkVoxImage(*vox); + } else if (auto* assembly = dynamic_cast(obj)) { + return new Assembly(assembly); } // Fallback if we don't know the exact class but it might be a context itself From 038c6f99f4af186adacf1fee2ed81308fcc75495 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 15:02:17 +0000 Subject: [PATCH 04/24] fixed most ( still units error ) --- docs/transformation_system.md | 73 ++++++++ src/Math/Transform.h | 171 +++++++++++------- src/Vtk/CMakeLists.txt | 2 - src/Vtk/HEP/Detectors/vtkMuonEvent.h | 2 +- src/Vtk/HEP/Detectors/vtkMuonScatter.h | 2 +- src/Vtk/HEP/Geant/vtkGeantEvent.h | 2 +- src/Vtk/HEP/Geant/vtkGeantSolid.h | 2 +- .../vtkMuonContainerScattering.cpp} | 26 +-- .../vtkMuonContainerScattering.h | 74 ++++++++ src/Vtk/Math/CMakeLists.txt | 2 + src/Vtk/Math/vtkAssembly.cpp | 36 ++-- src/Vtk/Math/vtkAssembly.h | 3 + src/Vtk/Math/vtkContainerBox.cpp | 54 ++---- src/Vtk/Math/vtkContainerBox.h | 3 +- src/Vtk/{ => Math}/vtkPolydata.cpp | 0 src/Vtk/{ => Math}/vtkPolydata.h | 0 src/Vtk/Math/vtkQuadMesh.h | 2 +- src/Vtk/Math/vtkTriangleMesh.h | 2 +- src/Vtk/uLibVtkInterface.cxx | 125 ++++++------- src/Vtk/uLibVtkInterface.h | 4 + src/Vtk/vtkHandlerWidget.cpp | 102 ++++------- src/Vtk/vtkViewport.cpp | 1 - 22 files changed, 411 insertions(+), 277 deletions(-) create mode 100644 docs/transformation_system.md rename src/Vtk/{vtkuLibProp.h => HEP/MuonTomography/vtkMuonContainerScattering.cpp} (83%) create mode 100644 src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h rename src/Vtk/{ => Math}/vtkPolydata.cpp (100%) rename src/Vtk/{ => Math}/vtkPolydata.h (100%) diff --git a/docs/transformation_system.md b/docs/transformation_system.md new file mode 100644 index 0000000..a5cc8f8 --- /dev/null +++ b/docs/transformation_system.md @@ -0,0 +1,73 @@ +# Transformation Flow and Synchronization System + +This document describes how transformations are applied and synchronized between the interactive 3D viewport, the visualization puppets, and the underlying mathematical models within the `uLib` framework. + +## Architecture Overview + +The system follows a Model-View-Controller (MVC) like pattern where: +- **Model**: `uLib::AffineTransform` (or derived classes like `ContainerBox`). +- **View/Puppet**: `uLib::Vtk::Puppet` (and specialized derivations like `Vtk::Assembly`). +- **Controller/Interaction**: `vtkHandlerWidget` (the transformation gizmo). + +--- + +## 1. Interaction Flow (Gizmo -> Model) + +When a user interacts with the `vtkHandlerWidget` (dragging arrows, rings, or cubes), the following chain of events occurs: + +```mermaid +sequenceDiagram + participant User + participant HW as vtkHandlerWidget + participant VP as vtkViewport + participant P as vtkPuppet + participant M as uLib Model + + User->>HW: Drag handle (MouseMove) + HW->>HW: Calculate Delta Matrix (op) + HW->>HW: Total = StartState * op + HW->>HW: Decompose Total into P, O, S + HW->>P: SetPosition, SetOrientation, SetScale + HW-->>VP: Invoke InteractionEvent + VP->>P: SyncFromVtk() + P->>P: Get local matrix from VTK Prop + P->>M: SetMatrix(matrix) + M-->>M: Update local properties (P, O, S) + M-->>P: Emit Updated signal + P->>P: Puppet::Update() + P->>P: (Redundant sanity write to Prop) +``` + +### Key Principles: +- **Single Source of Truth**: The `uLib::AffineTransform` is the owner of the transformation state. +- **Internal TRS vs UserMatrix**: We apply transformations directly to VTK's internal `Position`, `Orientation`, and `Scale` properties. This ensures the data is "visible" to VTK actors and simplifies decomposition. +- **Cumulative Bias Avoidance**: The `HandlerWidget` calculates transformations relative to the state at the start of the click, preventing numerical drift during a single drag operation. + +--- + +## 2. Synchronization Loop Resolution + +To prevent infinite loops and "double-transformation" artifacts (especially in assemblies), the following protections are in place: + +1. **Hierarchy Isolation**: The `Puppet` base class distinguishes between the **Root Property** (which receives the puppet's master transformation) and **Sub-Parts** (which only receive appearance updates like color/visibility). This prevents parts from inheriting the same displacement twice. +2. **Re-entrancy Guards**: Puppets use an `m_InUpdate` flag to prevent a feedback loop where `SyncFromVtk` triggers a Model Update, which then re-triggers the Puppet Update. +3. **Signal Blocking**: In specialized cases (like `vtkAssembly`), `m_BlockUpdate` is used to prevent the model-to-puppet push during a puppet-to-model sync. + +--- + +## 3. Undo System (Ctrl-Z) + +### Current Implementation (Delta Chain) +Currently, the system maintains a `m_TransformChain` of delta matrices. +- **Record**: After every drag, a delta matrix ($M_{delta} = M_{end} \cdot M_{start}^{-1}$) is appended to the chain. +- **Undo**: The last delta is removed, and the prop is reconstructed by reapplying the remaining chain from a `BaseMatrix`. + +### Planned Improvement (TRS Snapshots) +We are migrating to a `uLib::TRS` snapshot system for Undo. +- **Record**: At the start of a drag, the current `TRS` state of the object is pushed to the `m_UndoStack`. +- **Undo**: The top `TRS` is popped and applied directly to the model. + +This approach is more robust because: +- It eliminates matrix multiplication error accumulation. +- It bypasses rotation convention/order issues (Gimbal lock in deltas). +- It returns the object to exactly its previous property values. diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 70ab729..dd4a42f 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -56,6 +56,67 @@ namespace uLib { + +//////////////////////////////////////////////////////////////////////////////// +///////// TRS PARAMETERS ///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +typedef Eigen::Affine3f AffineMatrix; + +class TRS { +public: + Vector3f position = Vector3f::Zero(); + Vector3f rotation = Vector3f::Zero(); + Vector3f scaling = Vector3f::Ones(); + + TRS() = default; + + TRS(const class AffineTransform& at); + + TRS(const Matrix4f& mat) { + this->FromMatrix(mat); + } + + void FromMatrix(const Matrix4f& mat) { + this->position = mat.block<3,1>(0,3); + + Matrix3f linear = mat.block<3,3>(0,0); + this->scaling(0) = linear.col(0).norm(); + this->scaling(1) = linear.col(1).norm(); + this->scaling(2) = linear.col(2).norm(); + + Matrix3f rot = linear; + if (this->scaling(0) > 1e-6) rot.col(0) /= this->scaling(0); + if (this->scaling(1) > 1e-6) rot.col(1) /= this->scaling(1); + if (this->scaling(2) > 1e-6) rot.col(2) /= this->scaling(2); + + // Decompose to Euler angles matching VTK (M = Rz * Ry * Rx) + // Store internally as RADIANS (standard for uLib properties) + Vector3f euler = rot.eulerAngles(2, 1, 0); + this->rotation = Vector3f(euler(2), euler(1), euler(0)); + } + + template + void serialize(ArchiveT & ar, const unsigned int version) { + ar & HRPU(position, "mm"); + ar & HRPU(rotation, "deg"); // Metadata informs UI to convert to/from degrees + ar & HRP(scaling); + } + + AffineMatrix GetAffineMatrix() const { + AffineMatrix t = AffineMatrix::Identity(); + t.translate(position); + + // rotation is in Radians here + t.rotate(Eigen::AngleAxisf(rotation.z(), Vector3f::UnitZ())); + t.rotate(Eigen::AngleAxisf(rotation.y(), Vector3f::UnitY())); + t.rotate(Eigen::AngleAxisf(rotation.x(), Vector3f::UnitX())); + + t.scale(scaling); + return t; + } +}; + //////////////////////////////////////////////////////////////////////////////// ///////// AFFINE TRANSFORM WRAPPER ////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -64,21 +125,15 @@ class AffineTransform : virtual public Object { public: uLibTypeMacro(AffineTransform, Object) - /** - * @brief Grouped transformation parameters for property-based control. - */ - struct { - Vector3f Position = Vector3f::Zero(); - Vector3f Orientation = Vector3f::Zero(); - Vector3f Scale = Vector3f::Ones(); + TRS Transform; - template - void serialize(ArchiveT & ar, const unsigned int version) { - ar & HRPU(Position, "mm"); - ar & HRPU(Orientation, "deg"); - ar & HRP(Scale); - } - } Transform; +private: + void NotifyProperties() { + PropertyBase *p; + if ((p = this->GetProperty("Transform.position"))) p->Updated(); + if ((p = this->GetProperty("Transform.rotation"))) p->Updated(); + if ((p = this->GetProperty("Transform.scaling"))) p->Updated(); + } protected: Eigen::Affine3f m_T; @@ -140,11 +195,25 @@ public: } void SetPosition(const Vector3f v) { - this->Transform.Position = v; - this->Sync(); + this->Transform.position = v; + this->Updated(); + this->NotifyProperties(); } + Vector3f GetPosition() const { return this->Transform.position; } - Vector3f GetPosition() const { return this->Transform.Position; } + void SetOrientation(const Vector3f v) { + this->Transform.rotation = v; + this->Updated(); + this->NotifyProperties(); + } + Vector3f GetOrientation() const { return this->Transform.rotation; } + + void SetScale(const Vector3f v) { + this->Transform.scaling = v; + this->Updated(); + this->NotifyProperties(); + } + Vector3f GetScale() const { return this->Transform.scaling; } void SetRotation(const Matrix3f m) { this->m_T.linear() = m; @@ -154,24 +223,15 @@ public: Matrix3f GetRotation() const { return this->m_T.rotation(); } void Translate(const Vector3f v) { - this->Transform.Position += v; + this->Transform.position += v; this->Sync(); } void Scale(const Vector3f v) { - this->Transform.Scale = this->Transform.Scale.cwiseProduct(v); + this->Transform.scaling = this->Transform.scaling.cwiseProduct(v); this->Sync(); } - Vector3f GetScale() const { return this->Transform.Scale; } - - void SetOrientation(const Vector3f v) { - this->Transform.Orientation = v; - this->Sync(); - } - - Vector3f GetOrientation() const { return this->Transform.Orientation; } - void Rotate(const Matrix3f m) { this->m_T.rotate(m); @@ -197,7 +257,7 @@ public: { this->m_T.rotate(Eigen::Quaternion(q)); this->UpdatePropertiesFromMatrix(); } void EulerYZYRotate(const Vector3f e) { - this->Transform.Orientation = e; + this->Transform.rotation = e; this->Sync(); } @@ -213,30 +273,12 @@ public: * @brief Decomposes the internal matrix m_T back into Position, Orientation, and Scale properties. */ void UpdatePropertiesFromMatrix() { - // 1. Position - Transform.Position = m_T.translation(); + this->Transform.FromMatrix(this->GetMatrix()); - // 2. Scale - Matrix3f linear = m_T.linear(); - Transform.Scale(0) = linear.col(0).norm(); - Transform.Scale(1) = linear.col(1).norm(); - Transform.Scale(2) = linear.col(2).norm(); - - // 3. Rotation (Normalization removes scale) - Matrix3f rotation = linear; - if (Transform.Scale(0) > 1e-6) rotation.col(0) /= Transform.Scale(0); - if (Transform.Scale(1) > 1e-6) rotation.col(1) /= Transform.Scale(1); - if (Transform.Scale(2) > 1e-6) rotation.col(2) /= Transform.Scale(2); - - // Euler YZY (indices 1, 2, 1) - Vector3f euler = rotation.eulerAngles(1, 2, 1); - Transform.Orientation = euler / CLHEP::degree; - - // Notify properties - PropertyBase* p; - if ((p = this->GetProperty("Transform.Position"))) p->Updated(); - if ((p = this->GetProperty("Transform.Orientation"))) p->Updated(); - if ((p = this->GetProperty("Transform.Scale"))) p->Updated(); + PropertyBase *p; + if ((p = this->GetProperty("Transform.position"))) p->Updated(); + if ((p = this->GetProperty("Transform.rotation"))) p->Updated(); + if ((p = this->GetProperty("Transform.scaling"))) p->Updated(); } signals: @@ -247,22 +289,19 @@ signals: } private: - /** Synchronizes m_T with properties */ - void Sync() { - m_T = Eigen::Affine3f::Identity(); - m_T.translate(Transform.Position); - - // Orientation (using YZY order as implied by EulerYZYRotate) - Matrix3f mat; - mat = Eigen::AngleAxisf(Transform.Orientation.x() * CLHEP::degree, Vector3f::UnitY()) - * Eigen::AngleAxisf(Transform.Orientation.y() * CLHEP::degree, Vector3f::UnitZ()) - * Eigen::AngleAxisf(Transform.Orientation.z() * CLHEP::degree, Vector3f::UnitY()); - m_T.rotate(mat); - - m_T.scale(Transform.Scale); - } + void Sync() { + m_T.matrix() = this->Transform.GetAffineMatrix().matrix(); + } }; +inline TRS::TRS(const AffineTransform& at) { + this->position = at.GetPosition(); + this->rotation = at.GetOrientation(); + this->scaling = at.GetScale(); +} + + + } diff --git a/src/Vtk/CMakeLists.txt b/src/Vtk/CMakeLists.txt index 6517431..9600b8f 100644 --- a/src/Vtk/CMakeLists.txt +++ b/src/Vtk/CMakeLists.txt @@ -3,7 +3,6 @@ set(HEADERS uLibVtkInterface.h vtkHandlerWidget.h vtkQViewport.h vtkViewport.h - vtkPolydata.h vtkObjectsContext.h ) @@ -12,7 +11,6 @@ set(SOURCES uLibVtkInterface.cxx vtkHandlerWidget.cpp vtkQViewport.cpp vtkViewport.cpp - vtkPolydata.cpp vtkObjectsContext.cpp ) diff --git a/src/Vtk/HEP/Detectors/vtkMuonEvent.h b/src/Vtk/HEP/Detectors/vtkMuonEvent.h index 0c3abf6..068c9a8 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonEvent.h +++ b/src/Vtk/HEP/Detectors/vtkMuonEvent.h @@ -45,7 +45,7 @@ #include "HEP/Detectors/MuonEvent.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" namespace uLib { namespace Vtk { diff --git a/src/Vtk/HEP/Detectors/vtkMuonScatter.h b/src/Vtk/HEP/Detectors/vtkMuonScatter.h index ac7c85f..6eda4ba 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonScatter.h +++ b/src/Vtk/HEP/Detectors/vtkMuonScatter.h @@ -46,7 +46,7 @@ #include "HEP/Detectors/MuonScatter.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkRenderWindowInteractor; diff --git a/src/Vtk/HEP/Geant/vtkGeantEvent.h b/src/Vtk/HEP/Geant/vtkGeantEvent.h index c904baf..f139bcf 100644 --- a/src/Vtk/HEP/Geant/vtkGeantEvent.h +++ b/src/Vtk/HEP/Geant/vtkGeantEvent.h @@ -28,7 +28,7 @@ #include "HEP/Geant/GeantEvent.h" #include "uLibVtkInterface.h" -#include "vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" #include namespace uLib { diff --git a/src/Vtk/HEP/Geant/vtkGeantSolid.h b/src/Vtk/HEP/Geant/vtkGeantSolid.h index 8fb127d..e919577 100644 --- a/src/Vtk/HEP/Geant/vtkGeantSolid.h +++ b/src/Vtk/HEP/Geant/vtkGeantSolid.h @@ -28,7 +28,7 @@ #include "HEP/Geant/Solid.h" #include "uLibVtkInterface.h" -#include "vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkActor; diff --git a/src/Vtk/vtkuLibProp.h b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.cpp similarity index 83% rename from src/Vtk/vtkuLibProp.h rename to src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.cpp index fd6be35..b49a65e 100644 --- a/src/Vtk/vtkuLibProp.h +++ b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.cpp @@ -25,27 +25,11 @@ -#ifndef U_VTKULIBPROP_H -#define U_VTKULIBPROP_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif -class vtkProp; - -namespace uLib { -namespace Abstract { - -class uLibVtkProp { -public: - virtual vtkProp *GetProp() = 0; - -protected: - ~uLibVtkProp() {} -}; +#include - - -} - -} - -#endif // VTKULIBPROP_H +// TO BE CONTINUED // diff --git a/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h new file mode 100644 index 0000000..d7f12d9 --- /dev/null +++ b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h @@ -0,0 +1,74 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova + All rights reserved + + Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > + + ------------------------------------------------------------------ + This library 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 3.0 of the License, or (at your option) any later version. + + This library 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 this library. + +//////////////////////////////////////////////////////////////////////////////*/ + + + +#ifndef VTKMUONCONTAINERSCATTERING_H +#define VTKMUONCONTAINERSCATTERING_H + + + +#include "Math/Dense.h" + +#include "uLibVtkInterface.h" +#include "Detectors/MuonScatter.h" + +class vtkRenderWindowInteractor; + +namespace uLib { + +class vtkMuonContainerScattering : public Abstract::uLibVtkPolydata { + typedef MuonScatter Content; +public: + vtkMuonContainerScattering(const MuonScatter &content); + ~vtkMuonScatter(); + + Content& GetContent(); + + void PrintSelf(std::ostream &o) const; + + virtual vtkProp *GetProp(); + + virtual vtkPolyData* GetPolyData() const; + + void AddPocaPoint(HPoint3f poca); + + HPoint3f GetPocaPoint(); + + void vtkStartInteractive(); + +protected: + void ConnectInteractor(vtkRenderWindowInteractor *interactor); + +private: + void InstallPipe(); + +}; + + +} + + +#endif // VTKMUONCONTAINERSCATTERING_H diff --git a/src/Vtk/Math/CMakeLists.txt b/src/Vtk/Math/CMakeLists.txt index 0dbe4c3..cd1c110 100644 --- a/src/Vtk/Math/CMakeLists.txt +++ b/src/Vtk/Math/CMakeLists.txt @@ -11,6 +11,7 @@ set(MATH_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/vtkPolydata.cpp PARENT_SCOPE) set(MATH_HEADERS @@ -22,6 +23,7 @@ set(MATH_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.h + ${CMAKE_CURRENT_SOURCE_DIR}/vtkPolydata.h PARENT_SCOPE) if(BUILD_TESTING) diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index 19a3fd7..d25941d 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -92,13 +92,12 @@ void Assembly::InstallPipe() { void Assembly::contentUpdate() { if (m_InUpdate) return; m_InUpdate = true; - + m_BlockUpdate = false; this->UpdateTransform(); this->UpdateBoundingBox(); if (m_ChildContext) m_ChildContext->Update(); - m_BlockUpdate = true; Puppet::Update(); m_InUpdate = false; } @@ -106,25 +105,29 @@ void Assembly::contentUpdate() { // ------------------------------------------------------------------ // void Assembly::Update() { if (m_InUpdate) return; - if (!m_Content || !m_VtkAsm) return; + m_InUpdate = true; + this->contentUpdate(); + m_InUpdate = false; +} - if (m_BlockUpdate) { - m_BlockUpdate = false; - return; - } +void Assembly::SyncFromVtk() { + if (m_InUpdate) return; + if (!m_Content || !m_VtkAsm) return; m_InUpdate = true; - // Pull VTK transform back into the uLib model - vtkMatrix4x4* vmat = m_VtkAsm->GetUserMatrix(); - if (vmat) { - Matrix4f transform = VtkToMatrix4f(vmat); - m_Content->SetMatrix(transform); - } + double pos[3], ori[3], scale[3]; + m_VtkAsm->GetPosition(pos); + m_VtkAsm->GetOrientation(ori); + m_VtkAsm->GetScale(scale); + + m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); this->UpdateBoundingBox(); if (m_ChildContext) - m_ChildContext->Update(); + m_ChildContext->SyncFromVtk(); m_Content->Updated(); // Notify change in model @@ -135,10 +138,7 @@ void Assembly::Update() { void Assembly::UpdateTransform() { if (!m_Content || !m_VtkAsm) return; - Matrix4f mat = m_Content->GetMatrix(); - vtkNew vmat; - Matrix4fToVtk(mat, vmat); - m_VtkAsm->SetUserMatrix(vmat); + this->ApplyTransform(m_VtkAsm); m_VtkAsm->Modified(); } diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index 297a9c0..a63d54f 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -44,6 +44,9 @@ public: /** @brief Updates the VTK representation from the model (model→VTK). */ virtual void Update() override; + /** @brief Synchronizes the model from the VTK representation (VTK→model). */ + virtual void SyncFromVtk() override; + virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } /** @brief Called when the model signals an update (model→VTK push). */ diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index 2b8a37f..f4ffa56 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -80,54 +80,38 @@ void vtkContainerBox::contentUpdate() { vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); if (!root) return; - vtkMatrix4x4* vmat = root->GetUserMatrix(); - if (!vmat) { - // Should have been set in InstallPipe, but let's be safe - vtkNew mat; - root->SetUserMatrix(mat); - vmat = mat; - } - d->m_Cube->SetUserMatrix(nullptr); d->m_Axes->SetUserMatrix(nullptr); - Matrix4f transform = m_Content->GetMatrix(); - Matrix4fToVtk(transform, vmat); + TRS trs(*m_Content); + this->ApplyTransform(root); root->Modified(); - m_BlockUpdate = true; + m_BlockUpdate = false; Puppet::Update(); } void vtkContainerBox::Update() { + this->contentUpdate(); +} + +void vtkContainerBox::SyncFromVtk() { RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); if (!m_Content) return; - if (m_BlockUpdate) { - m_BlockUpdate = false; - return; - } - - // Use Targeted Blocking: only block the feedback connection to this puppet - // boost::signals2::shared_connection_block block(m_Connection); - vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); if (!assembly) return; - vtkMatrix4x4* vmat = assembly->GetUserMatrix(); - if (!vmat) return; + double pos[3], ori[3], scale[3]; + assembly->GetPosition(pos); + assembly->GetOrientation(ori); + assembly->GetScale(scale); + + m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); - Matrix4f transform = VtkToMatrix4f(vmat); - - // Update uLib model's affine transform - // if (m_Content->GetParent()) { - // Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform; - // m_Content->SetMatrix(localT); - // } else { - m_Content->SetMatrix(transform); - // } - m_Content->Updated(); // Notify change } @@ -175,9 +159,11 @@ void vtkContainerBox::InstallPipe() { vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); if (root) { - vtkNew vmat; - Matrix4fToVtk(c->GetMatrix(), vmat); - root->SetUserMatrix(vmat); + TRS trs(*c); + root->SetPosition(trs.position.x(), trs.position.y(), trs.position.z()); + root->SetOrientation(trs.rotation.x(), trs.rotation.y(), trs.rotation.z()); + root->SetScale(trs.scaling.x(), trs.scaling.y(), trs.scaling.z()); + root->SetUserMatrix(nullptr); } } diff --git a/src/Vtk/Math/vtkContainerBox.h b/src/Vtk/Math/vtkContainerBox.h index 4b4bfd6..6fe0873 100644 --- a/src/Vtk/Math/vtkContainerBox.h +++ b/src/Vtk/Math/vtkContainerBox.h @@ -49,7 +49,8 @@ public: virtual void contentUpdate(); - virtual void Update(); + virtual void Update() override; + virtual void SyncFromVtk() override; virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } diff --git a/src/Vtk/vtkPolydata.cpp b/src/Vtk/Math/vtkPolydata.cpp similarity index 100% rename from src/Vtk/vtkPolydata.cpp rename to src/Vtk/Math/vtkPolydata.cpp diff --git a/src/Vtk/vtkPolydata.h b/src/Vtk/Math/vtkPolydata.h similarity index 100% rename from src/Vtk/vtkPolydata.h rename to src/Vtk/Math/vtkPolydata.h diff --git a/src/Vtk/Math/vtkQuadMesh.h b/src/Vtk/Math/vtkQuadMesh.h index b34282a..abd7e3b 100644 --- a/src/Vtk/Math/vtkQuadMesh.h +++ b/src/Vtk/Math/vtkQuadMesh.h @@ -28,7 +28,7 @@ #include "Math/QuadMesh.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkPolyData; class vtkActor; diff --git a/src/Vtk/Math/vtkTriangleMesh.h b/src/Vtk/Math/vtkTriangleMesh.h index 6ef07d7..56d9607 100644 --- a/src/Vtk/Math/vtkTriangleMesh.h +++ b/src/Vtk/Math/vtkTriangleMesh.h @@ -28,7 +28,7 @@ #include "Math/TriangleMesh.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkPolyData; class vtkActor; diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 2163f24..97e525f 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -63,6 +63,7 @@ #include "uLibVtkInterface.h" #include "vtkHandlerWidget.h" #include "Math/Dense.h" +#include "Vtk/Math/vtkDense.h" #include "Core/Property.h" @@ -75,12 +76,6 @@ namespace uLib { namespace Vtk { - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// PUPPET // - // PIMPL -------------------------------------------------------------------- // class PuppetData { @@ -98,9 +93,6 @@ public: m_Dragable(true) { m_Color[0] = m_Color[1] = m_Color[2] = -1.0; - m_Position = Vector3d::Zero(); - m_Orientation = Vector3d::Zero(); - m_Scale = Vector3d::Ones(); } ~PuppetData() { @@ -126,9 +118,7 @@ public: bool m_Selected; bool m_Visibility; bool m_Dragable; - Vector3d m_Position; - Vector3d m_Orientation; - Vector3d m_Scale; + TRS m_Transform; void ApplyAppearance(vtkProp *p) { p->SetVisibility(m_Visibility); @@ -154,13 +144,19 @@ public: actor->GetProperty()->SetOpacity(m_Opacity); } } + } - // Handle transformation if it's a Prop3D - if (auto* p3d = vtkProp3D::SafeDownCast(p)) { - // NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation - // p3d->SetPosition(m_Position.data()); - // p3d->SetOrientation(m_Orientation.data()); - // p3d->SetScale(m_Scale.data()); + void ApplyTransform(vtkProp3D* p3d) { + if (p3d) { + p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); + + // Convert Model Radians to VTK Degrees + p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, + m_Transform.rotation.y() / CLHEP::degree, + m_Transform.rotation.z() / CLHEP::degree); + + p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); + p3d->SetUserMatrix(nullptr); } } @@ -203,6 +199,7 @@ public: } if (root) { + // Now that we use internal TRS, the prop's total matrix is GetMatrix() m_HighlightActor->SetUserMatrix(root->GetMatrix()); } @@ -227,6 +224,15 @@ public: + + + + + + + + + Puppet::Puppet() : Object(), pd(new PuppetData) { ULIB_ACTIVATE_DISPLAY_PROPERTIES; for (auto* p : this->GetDisplayProperties()) { @@ -278,6 +284,16 @@ void Puppet::RemoveProp(vtkProp *prop) // TODO } +void Puppet::ApplyAppearance(vtkProp* prop) +{ + pd->ApplyAppearance(prop); +} + +void Puppet::ApplyTransform(vtkProp3D* p3d) +{ + pd->ApplyTransform(p3d); +} + vtkPropCollection *Puppet::GetParts() { @@ -485,7 +501,7 @@ void Puppet::SetSelected(bool selected) if (!pd->m_Selectable) return; if (pd->m_Selected == selected) return; pd->m_Selected = selected; - pd->UpdateHighlight();0 + pd->UpdateHighlight(); } bool Puppet::IsSelected() const @@ -497,31 +513,15 @@ void Puppet::Update() { vtkProp* root = this->GetProp(); if (root) { - pd->ApplyAppearance(root); - // Handle transformation synchronization from content if (auto* content = dynamic_cast(GetContent())) { - pd->m_Position = content->GetPosition().cast(); - pd->m_Orientation = content->GetOrientation().cast(); - pd->m_Scale = content->GetScale().cast(); + pd->m_Transform = *content; // Uses TRS(const AffineTransform&) + } - if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - vtkNew vmat; - const Matrix4f& emat = content->GetMatrix(); - for(int i=0; i<4; ++i) for(int j=0; j<4; ++j) vmat->SetElement(i, j, emat(i,j)); - p3d->SetUserMatrix(vmat); - - // Clear base transform to avoid double-application - p3d->SetPosition(0,0,0); - p3d->SetOrientation(0,0,0); - p3d->SetScale(1,1,1); - } - } - else if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - p3d->SetPosition(pd->m_Position.data()); - p3d->SetOrientation(pd->m_Orientation.data()); - p3d->SetScale(pd->m_Scale.data()); + if (auto* p3d = vtkProp3D::SafeDownCast(root)) { + pd->ApplyTransform(p3d); } + pd->ApplyAppearance(root); } vtkProp3DCollection *props = pd->m_Assembly->GetParts(); @@ -564,32 +564,29 @@ void Puppet::SyncFromVtk() if (auto* p3d = vtkProp3D::SafeDownCast(root)) { // Handle content synchronization if it's an AffineTransform if (auto* content = dynamic_cast(GetContent())) { - vtkMatrix4x4* vmat = p3d->GetUserMatrix(); - if (vmat) { - Matrix4f emat; - for (int i=0; i<4; ++i) - for (int j=0; j<4; ++j) - emat(i, j) = vmat->GetElement(i, j); - content->SetMatrix(emat); - - // Re-sync internal puppet properties from the now-updated content - pd->m_Position = content->GetPosition().cast(); - pd->m_Orientation = content->GetOrientation().cast(); - pd->m_Scale = content->GetScale().cast(); - } - } - else { - // Update internal puppet properties directly from base components - // only if no content exists (old behavior) double pos[3], ori[3], scale[3]; p3d->GetPosition(pos); p3d->GetOrientation(ori); p3d->GetScale(scale); - for (int i=0; i<3; ++i) { - pd->m_Position(i) = pos[i]; - pd->m_Orientation(i) = ori[i]; - pd->m_Scale(i) = scale[i]; - } + + // Convert VTK Degrees to Model Radians + content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + + // Re-sync internal puppet properties from the now-updated content + pd->m_Transform = *content; + } + else { + // Update internal puppet TRS directly from VTK components + double pos[3], ori[3], scale[3]; + p3d->GetPosition(pos); + p3d->GetOrientation(ori); + p3d->GetScale(scale); + pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]); + // Convert VTK Degrees to internal Radians + pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree; + pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]); } // Notify puppet properties updated @@ -609,9 +606,7 @@ struct TransformProxy { PuppetData* pd; template void serialize(Archive & ar, const unsigned int version) { - ar & boost::serialization::make_hrp("Position", pd->m_Position, "mm"); - ar & boost::serialization::make_hrp("Orientation", pd->m_Orientation, "deg"); - ar & boost::serialization::make_hrp("Scale", pd->m_Scale, ""); + ar & boost::serialization::make_nvp("Transform", pd->m_Transform); } }; diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index d0e7332..74ae5ee 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -38,6 +38,7 @@ // vtk classes forward declaration // class vtkProp; +class vtkProp3D; class vtkPolyData; class vtkPropCollection; class vtkRenderer; @@ -121,6 +122,9 @@ protected: void RemoveProp(vtkProp *prop); + void ApplyAppearance(vtkProp* prop); + void ApplyTransform(vtkProp3D* p3d); + std::vector m_DisplayProperties; mutable uLib::RecursiveMutex m_UpdateMutex; diff --git a/src/Vtk/vtkHandlerWidget.cpp b/src/Vtk/vtkHandlerWidget.cpp index 4f460ac..dd7dabc 100644 --- a/src/Vtk/vtkHandlerWidget.cpp +++ b/src/Vtk/vtkHandlerWidget.cpp @@ -48,6 +48,8 @@ #include #include #include +#include "Math/Transform.h" +#include "Vtk/Math/vtkDense.h" namespace uLib { namespace Vtk { @@ -62,20 +64,23 @@ struct HandlerWidgetData { vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes + // cut plane to see only half of rotation handles vtkSmartPointer<::vtkPlane> m_ClipPlane; + // picker to select the gizmo vtkSmartPointer<::vtkCellPicker> m_Picker; + + // initial transform of the object vtkSmartPointer<::vtkTransform> m_InitialTransform; - std::vector> m_TransformChain; - vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix; + // undo stack + std::vector m_UndoStack; HandlerWidgetData() { m_Picker = vtkSmartPointer<::vtkCellPicker>::New(); m_InitialTransform = vtkSmartPointer<::vtkTransform>::New(); m_ClipPlane = vtkSmartPointer<::vtkPlane>::New(); m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New(); - m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New(); m_HighlightedProp = nullptr; } }; @@ -95,7 +100,6 @@ vtkHandlerWidget::vtkHandlerWidget() : d(new HandlerWidgetData()) { this->m_TranslationEnabled = true; this->m_RotationEnabled = true; this->m_ScalingEnabled = true; - d->m_BaseMatrix->Identity(); this->CreateGizmos(); } @@ -108,19 +112,14 @@ vtkHandlerWidget::~vtkHandlerWidget() { return d->m_OverlayRenderer; } + void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) { if (this->Prop3D == prop) { return; } this->Prop3D = prop; if (this->Prop3D) { - // Initialize d->m_BaseMatrix from the object's current matrix - if (this->Prop3D->GetUserMatrix()) { - this->d->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix()); - } else { - this->d->m_BaseMatrix->Identity(); - } - this->d->m_TransformChain.clear(); // Clear any previous transform chain + this->d->m_UndoStack.clear(); // Clear history when selecting new object this->UpdateGizmoPosition(); } this->Modified(); @@ -247,20 +246,19 @@ void vtkHandlerWidget::OnKeyPress() { bool ctrl = (this->Interactor->GetControlKey() != 0); if (ctrl && key == "z") { - if (!this->d->m_TransformChain.empty()) { + if (!this->d->m_UndoStack.empty()) { std::cout << "Undoing last transform action..." << std::endl; - this->d->m_TransformChain.pop_back(); + uLib::TRS target = this->d->m_UndoStack.back(); + this->d->m_UndoStack.pop_back(); - // Update object from chain - vtkNew total; - total->PostMultiply(); - total->SetMatrix(this->d->m_BaseMatrix.GetPointer()); - for (auto& t : d->m_TransformChain) { - total->Concatenate(t); - } - - if (this->Prop3D && this->Prop3D->GetUserMatrix()) { - this->Prop3D->GetUserMatrix()->DeepCopy(total->GetMatrix()); + if (this->Prop3D) { + this->Prop3D->SetPosition(target.position.x(), target.position.y(), target.position.z()); + // Convert Model Radians to VTK Degrees + this->Prop3D->SetOrientation(target.rotation.x() / CLHEP::degree, + target.rotation.y() / CLHEP::degree, + target.rotation.z() / CLHEP::degree); + this->Prop3D->SetScale(target.scaling.x(), target.scaling.y(), target.scaling.z()); + this->Prop3D->SetUserMatrix(nullptr); this->Prop3D->Modified(); this->UpdateGizmoPosition(); this->InvokeEvent(::vtkCommand::InteractionEvent, nullptr); @@ -311,21 +309,12 @@ void vtkHandlerWidget::OnLeftButtonDown() { this->StartEventPosition[0] = X; this->StartEventPosition[1] = Y; if (this->Prop3D) { - if (!this->Prop3D->GetUserMatrix()) { - vtkNew vmat; - this->Prop3D->SetUserMatrix(vmat); - } - - // If the chain is empty, initialize base from current state? - // Actually, if we just started selecting this object, we should have initialized BaseMatrix. - // For now, let's keep d->m_InitialTransform as the state BEFORE this drag - vtkNew current; - current->PostMultiply(); - current->SetMatrix(this->d->m_BaseMatrix.GetPointer()); - for (auto& t : d->m_TransformChain) { - current->Concatenate(t); - } - this->d->m_InitialTransform->SetMatrix(current->GetMatrix()); + // Capture current state for Undo + this->d->m_UndoStack.push_back(uLib::TRS(uLib::Vtk::VtkToMatrix4f(this->Prop3D->GetMatrix()))); + if (this->d->m_UndoStack.size() > 50) this->d->m_UndoStack.erase(this->d->m_UndoStack.begin()); + + // Use the prop's total matrix for calculation baseline + this->d->m_InitialTransform->SetMatrix(this->Prop3D->GetMatrix()); } this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr); @@ -337,27 +326,6 @@ void vtkHandlerWidget::OnLeftButtonUp() { if (this->Interaction == IDLE) return; - // Finalize the current interaction into the chain - int X = this->Interactor->GetEventPosition()[0]; - int Y = this->Interactor->GetEventPosition()[1]; - - // We need to re-calculate the final 'op' to store it - // Actually, we could have stored it in OnMouseMove, but let's re-calculate or - // just capture the delta between d->m_InitialTransform and current UserMatrix. - if (this->Prop3D && this->Prop3D->GetUserMatrix()) { - vtkNew inv; - vtkMatrix4x4::Invert(this->d->m_InitialTransform->GetMatrix(), inv); - - vtkNew final_op_mat; - vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat); - - vtkNew final_op; - final_op->SetMatrix(final_op_mat); - - this->d->m_TransformChain.push_back(final_op); - std::cout << "Action finalized. Chain size: " << this->d->m_TransformChain.size() << std::endl; - } - this->Interaction = IDLE; this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(::vtkCommand::EndInteractionEvent, nullptr); @@ -578,9 +546,17 @@ void vtkHandlerWidget::OnMouseMove() { total->SetMatrix(this->d->m_InitialTransform->GetMatrix()); // d->m_InitialTransform is already Base*Chain total->Concatenate(op); - vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix(); - if (targetMat) { - targetMat->DeepCopy(total->GetMatrix()); + if (this->Prop3D) { + double p[3], r[3], s[3]; + total->GetPosition(p); + total->GetOrientation(r); + total->GetScale(s); + this->Prop3D->SetPosition(p); + // VTK GetOrientation already returned degrees, so r is in degrees. + // We apply it directly back to VTK. + this->Prop3D->SetOrientation(r); + this->Prop3D->SetScale(s); + this->Prop3D->SetUserMatrix(nullptr); } this->Prop3D->Modified(); @@ -671,7 +647,7 @@ void vtkHandlerWidget::SetTransform(::vtkTransform *t) { void vtkHandlerWidget::GetTransform(::vtkTransform *t) { if (!t || !this->Prop3D) return; - t->SetMatrix(this->Prop3D->GetUserMatrix()); + t->SetMatrix(this->Prop3D->GetMatrix()); } void vtkHandlerWidget::CreateGizmos() { diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index d7b610b..f4c2026 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -209,7 +209,6 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren) for (auto* p : self->m_Puppets) { if (p->IsSelected()) { p->SyncFromVtk(); - p->Update(); } } }); From e40cc77a5f6efb94b8d68a80961eef7e9e71ccb5 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 15:17:54 +0000 Subject: [PATCH 05/24] fix numeric unit conversion in widget --- app/gcompose/src/PropertyWidgets.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index f12fd29..9d13fea 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -45,7 +45,7 @@ double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut double num = match.captured(1).toDouble(); QString unit = match.captured(3); - double factor = 1.0; + double factor = factorOut ? *factorOut : 1.0; if (!unit.isEmpty()) { QString u = unit.startsWith('_') ? unit.mid(1) : unit; @@ -101,10 +101,6 @@ void UnitLineEdit::onEditingFinished() { double factor = m_Factor; QString suffix = m_Suffix; double parsedVal = parseWithUnits(text(), &factor, &suffix); - if (!suffix.isEmpty()) { - m_Suffix = suffix; - m_Factor = factor; - } if (m_IsInteger) { parsedVal = std::round(parsedVal); } From fa7c0f670e5d17bfb5b9533aebbb4af699d862f2 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 15:23:59 +0000 Subject: [PATCH 06/24] fix display of cylinder --- src/Vtk/Math/vtkCylinder.cpp | 41 ++++++++++++++++++------------------ src/Vtk/Math/vtkCylinder.h | 5 ++++- src/Vtk/uLibVtkInterface.cxx | 8 +++++++ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/Vtk/Math/vtkCylinder.cpp b/src/Vtk/Math/vtkCylinder.cpp index d9f2741..3e14246 100644 --- a/src/Vtk/Math/vtkCylinder.cpp +++ b/src/Vtk/Math/vtkCylinder.cpp @@ -55,17 +55,12 @@ void vtkCylinder::contentUpdate() { vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); if (!root) return; - // 1. Placement (Position/Rotation/Model-level Scale) goes to the root prop - vtkMatrix4x4* vmat = root->GetUserMatrix(); - if (!vmat) { - vtkNew mat; - root->SetUserMatrix(mat); - vmat = mat; - } - Matrix4f transform = m_Content->GetMatrix(); - Matrix4fToVtk(transform, vmat); + // 1. Placement handled by base Puppet class via Sync / Update logic + // Update internal pd->m_Transform from content + Puppet::Update(); // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor + // These are relative to the root assembly vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform()); if (alignment) { alignment->Identity(); @@ -83,23 +78,29 @@ void vtkCylinder::contentUpdate() { } root->Modified(); - Puppet::Update(); } void vtkCylinder::Update() { + this->contentUpdate(); +} + +void vtkCylinder::SyncFromVtk() { if (!m_Content) return; - - vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); - if (!root) return; - - vtkMatrix4x4* vmat = root->GetUserMatrix(); - if (!vmat) return; - // Pull the placement matrix directly from VTK - Matrix4f transform = VtkToMatrix4f(vmat); - m_Content->SetMatrix(transform); + vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); + if (!assembly) return; - m_Content->Updated(); + double pos[3], ori[3], scale[3]; + assembly->GetPosition(pos); + assembly->GetOrientation(ori); + assembly->GetScale(scale); + + m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + // Convert VTK degrees to model radians + m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + + m_Content->Updated(); // Notify change } void vtkCylinder::InstallPipe() { diff --git a/src/Vtk/Math/vtkCylinder.h b/src/Vtk/Math/vtkCylinder.h index fc246f9..1965293 100644 --- a/src/Vtk/Math/vtkCylinder.h +++ b/src/Vtk/Math/vtkCylinder.h @@ -52,7 +52,10 @@ public: virtual void contentUpdate(); /** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */ - virtual void Update(); + virtual void Update() override; + + /** Synchronizes the uLib model matrix with the VTK actor specifically for gizmo interactions */ + virtual void SyncFromVtk() override; virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 97e525f..d390f58 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -121,6 +121,7 @@ public: TRS m_Transform; void ApplyAppearance(vtkProp *p) { + if (!p) return; p->SetVisibility(m_Visibility); p->SetPickable(m_Selectable); p->SetDragable(m_Dragable); @@ -143,6 +144,13 @@ public: if (m_Opacity != -1.0) { actor->GetProperty()->SetOpacity(m_Opacity); } + } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) { + // Recursively apply to parts of the assembly + vtkProp3DCollection *parts = asm_p->GetParts(); + parts->InitTraversal(); + for (int i = 0; i < parts->GetNumberOfItems(); ++i) { + this->ApplyAppearance(parts->GetNextProp3D()); + } } } From 171a07eb7964a1531d48e5b45944f8a622493f80 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 15:46:16 +0000 Subject: [PATCH 07/24] add min max def to properties --- app/gcompose/src/PropertyWidgets.cpp | 88 +++++++++++++++++++++++++++- app/gcompose/src/PropertyWidgets.h | 29 +++++++++ src/Core/Property.h | 70 ++++++++++++++++++++-- src/Core/Serializable.h | 26 +++++++- src/Vtk/uLibVtkInterface.cxx | 24 ++++---- src/Vtk/uLibVtkInterface.h | 3 + 6 files changed, 222 insertions(+), 18 deletions(-) diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index 9d13fea..1c586c5 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -7,6 +7,10 @@ #include "Vtk/uLibVtkInterface.h" #include "Math/Units.h" #include "Math/Dense.h" +#include +#include +#include +#include #include "Settings.h" namespace uLib { @@ -19,7 +23,7 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent) std::string unit = prop->GetUnits(); QString labelText = QString::fromStdString(prop->GetName()); - if (!unit.empty()) { + if (!unit.empty() && unit != "color") { auto dim = Settings::Instance().IdentifyDimension(unit); std::string pref = Settings::Instance().GetPreferredUnit(dim); if (!pref.empty()) { @@ -203,6 +207,76 @@ BoolPropertyWidget::BoolPropertyWidget(Property* prop, QWidget* parent) } BoolPropertyWidget::~BoolPropertyWidget() {} +RangePropertyWidget::RangePropertyWidget(Property* prop, QWidget* parent) + : PropertyWidgetBase(prop, parent), m_Prop(prop) { + m_Slider = new QSlider(::Qt::Horizontal, this); + m_Slider->setRange(0, 100); + m_Slider->setMinimumWidth(80); + + m_Edit = new UnitLineEdit(this); + m_Edit->setFixedWidth(50); + + m_Layout->addWidget(m_Slider, 1); + m_Layout->addWidget(m_Edit, 0); + + connect(m_Slider, &QSlider::valueChanged, this, &RangePropertyWidget::onSliderChanged); + connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); }); + + m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + this->updateUi(); + }); + updateUi(); +} +RangePropertyWidget::~RangePropertyWidget() { m_Connection.disconnect(); } + +void RangePropertyWidget::updateUi() { + double val = m_Prop->Get(); + m_Edit->setValue(val); + if (m_Prop->GetMax() != m_Prop->GetMin()) { + int sliderVal = (int)((val - m_Prop->GetMin()) / (m_Prop->GetMax() - m_Prop->GetMin()) * 100.0); + QSignalBlocker blocker(m_Slider); + m_Slider->setValue(sliderVal); + } +} + +void RangePropertyWidget::onSliderChanged(int val) { + double realVal = m_Prop->GetMin() + (val / 100.0) * (m_Prop->GetMax() - m_Prop->GetMin()); + m_Prop->Set(realVal); +} + +ColorPropertyWidget::ColorPropertyWidget(Property* prop, QWidget* parent) + : PropertyWidgetBase(prop, parent), m_Prop(prop) { + m_Button = new QPushButton(this); + m_Button->setFixedWidth(60); + this->updateButtonColor(); + m_Layout->addWidget(m_Button, 0, ::Qt::AlignRight); + + connect(m_Button, &QPushButton::clicked, this, &ColorPropertyWidget::onClicked); + m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + this->updateButtonColor(); + }); +} +ColorPropertyWidget::~ColorPropertyWidget() {} + +void ColorPropertyWidget::updateButtonColor() { + Vector3d c = m_Prop->Get(); + QColor color = QColor::fromRgbF(std::max(0.0, std::min(1.0, c.x())), + std::max(0.0, std::min(1.0, c.y())), + std::max(0.0, std::min(1.0, c.z()))); + m_Button->setStyleSheet(QString("background-color: %1; border: 1px solid #555; height: 18px;").arg(color.name())); +} + +void ColorPropertyWidget::onClicked() { + Vector3d c = m_Prop->Get(); + QColor current = QColor::fromRgbF(std::max(0.0, std::min(1.0, c.x())), + std::max(0.0, std::min(1.0, c.y())), + std::max(0.0, std::min(1.0, c.z()))); + QColor selected = QColorDialog::getColor(current, this, "Select Color"); + if (selected.isValid()) { + m_Prop->Set(Vector3d(selected.redF(), selected.greenF(), selected.blueF())); + } +} + StringPropertyWidget::StringPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_LineEdit = new QLineEdit(this); @@ -358,6 +432,18 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { // Priority 1: Check if it provides enum labels if (!prop->GetEnumLabels().empty()) { widget = new EnumPropertyWidget(prop, m_Container); + } else if (prop->GetUnits() == "color") { + // Color Picker for Vector3d + if (auto* pvec = dynamic_cast*>(prop)) { + widget = new ColorPropertyWidget(pvec, m_Container); + } + } else if (prop->HasRange()) { + // Slider for ranged doubles + if (auto* pdbl = dynamic_cast*>(prop)) { + widget = new RangePropertyWidget(pdbl, m_Container); + } else if (auto* pflt = dynamic_cast*>(prop)) { + // widget = new RangePropertyWidget(pflt, m_Container); + } } else { // Priority 2: Standard factory lookup auto it = m_Factories.find(prop->GetTypeIndex()); diff --git a/app/gcompose/src/PropertyWidgets.h b/app/gcompose/src/PropertyWidgets.h index 4891432..7374389 100644 --- a/app/gcompose/src/PropertyWidgets.h +++ b/app/gcompose/src/PropertyWidgets.h @@ -2,6 +2,8 @@ #define PROPERTY_WIDGETS_H #include +class QPushButton; +class QSlider; #include #include #include @@ -141,6 +143,20 @@ private: UnitLineEdit* m_Edits[Size]; }; +class RangePropertyWidget : public PropertyWidgetBase { + Q_OBJECT +public: + RangePropertyWidget(Property* prop, QWidget* parent = nullptr); + virtual ~RangePropertyWidget(); +private slots: + void onSliderChanged(int val); +private: + void updateUi(); + Property* m_Prop; + QSlider* m_Slider; + UnitLineEdit* m_Edit; +}; + class BoolPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: @@ -151,6 +167,19 @@ private: QCheckBox* m_CheckBox; }; +class ColorPropertyWidget : public PropertyWidgetBase { + Q_OBJECT +public: + ColorPropertyWidget(Property* prop, QWidget* parent = nullptr); + virtual ~ColorPropertyWidget(); +private slots: + void onClicked(); +private: + void updateButtonColor(); + Property* m_Prop; + QPushButton* m_Button; +}; + class StringPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: diff --git a/src/Core/Property.h b/src/Core/Property.h index 0696818..fe30fbe 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -36,6 +36,13 @@ public: } virtual const std::string& GetGroup() const = 0; virtual void SetGroup(const std::string& group) = 0; + + virtual bool HasRange() const { return false; } + virtual double GetMin() const { return 0; } + virtual double GetMax() const { return 0; } + virtual bool HasDefault() const { return false; } + virtual std::string GetDefaultValueAsString() const { return ""; } + std::string GetQualifiedName() const { if (GetGroup().empty()) return GetName(); return GetGroup() + "." + GetName(); @@ -63,7 +70,8 @@ class Property : public PropertyBase { public: // PROXY: Use an existing variable as back-end storage Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "") - : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false) { + : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false), + m_HasRange(false), m_HasDefault(false) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -71,7 +79,8 @@ public: // MANAGED: Create and own internal storage Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "") - : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true) { + : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true), + m_HasRange(false), m_HasDefault(true), m_Default(defaultValue) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -103,15 +112,61 @@ public: // Accessors const T& Get() const { return *m_value; } + template + typename std::enable_if::value, void>::type + ValidateT(T& val) { + if (m_HasRange) { + if (val < m_Min) val = m_Min; + if (val > m_Max) val = m_Max; + } + } + + template + typename std::enable_if::value, void>::type + ValidateT(T& val) { + } + void Set(const T& value) { - if (*m_value != value) { - *m_value = value; + T val = value; + ValidateT(val); + if (*m_value != val) { + *m_value = val; ULIB_SIGNAL_EMIT(Property::PropertyChanged); this->Updated(); if (m_owner) m_owner->Updated(); } } + void SetRange(const T& min, const T& max) { m_Min = min; m_Max = max; m_HasRange = true; } + void SetDefault(const T& def) { m_Default = def; m_HasDefault = true; } + + virtual bool HasRange() const override { return m_HasRange; } + + template + typename std::enable_if::value, double>::type + GetMinT() const { return (double)m_Min; } + + template + typename std::enable_if::value, double>::type + GetMinT() const { return 0.0; } + + template + typename std::enable_if::value, double>::type + GetMaxT() const { return (double)m_Max; } + + template + typename std::enable_if::value, double>::type + GetMaxT() const { return 0.0; } + + virtual double GetMin() const override { return GetMinT(); } + virtual double GetMax() const override { return GetMaxT(); } + + virtual bool HasDefault() const override { return m_HasDefault; } + virtual std::string GetDefaultValueAsString() const override { + try { return boost::lexical_cast(m_Default); } + catch (...) { return ""; } + } + // Operators for seamless usage operator const T&() const { return *m_value; } Property& operator=(const T& value) { @@ -149,6 +204,11 @@ private: T* m_value; bool m_own; Object* m_owner; + bool m_HasRange; + T m_Min; + T m_Max; + bool m_HasDefault; + T m_Default; }; /** @@ -242,6 +302,8 @@ public: void save_override(const boost::serialization::hrp &t) { if (m_Object) { Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); + if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); + if (t.has_default()) p->SetDefault(t.default_val()); m_Object->RegisterDynamicProperty(p); } } diff --git a/src/Core/Serializable.h b/src/Core/Serializable.h index e5e620d..03bcfe2 100644 --- a/src/Core/Serializable.h +++ b/src/Core/Serializable.h @@ -77,15 +77,30 @@ class hrp : public boost::serialization::wrapper_traits> { const char *m_name; const char *m_units; T &m_value; + bool m_has_range; + T m_min; + T m_max; + bool m_has_default; + T m_default; public: - explicit hrp(const char *name_, T &t, const char* units_ = nullptr) : m_name(name_), m_units(units_), m_value(t) {} + explicit hrp(const char *name_, T &t, const char* units_ = nullptr) + : m_name(name_), m_units(units_), m_value(t), m_has_range(false), m_has_default(false) {} + + hrp& range(const T& min_val, const T& max_val) { m_min = min_val; m_max = max_val; m_has_range = true; return *this; } + hrp& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; } const char *name() const { return this->m_name; } const char *units() const { return this->m_units; } T &value() { return this->m_value; } const T &const_value() const { return this->m_value; } + bool has_range() const { return m_has_range; } + const T& min_val() const { return m_min; } + const T& max_val() const { return m_max; } + bool has_default() const { return m_has_default; } + const T& default_val() const { return m_default; } + BOOST_SERIALIZATION_SPLIT_MEMBER() template @@ -110,16 +125,23 @@ class hrp_enum : public boost::serialization::wrapper_traits> { const char *m_units; T &m_value; std::vector m_labels; + bool m_has_default; + T m_default; public: explicit hrp_enum(const char *name_, T &t, const std::vector& labels, const char* units_ = nullptr) - : m_name(name_), m_units(units_), m_value(t), m_labels(labels) {} + : m_name(name_), m_units(units_), m_value(t), m_labels(labels), m_has_default(false) {} + + hrp_enum& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; } const char *name() const { return this->m_name; } const char *units() const { return this->m_units; } T &value() { return this->m_value; } const std::vector& labels() const { return m_labels; } + bool has_default() const { return m_has_default; } + const T& default_val() const { return m_default; } + BOOST_SERIALIZATION_SPLIT_MEMBER() template diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index d390f58..b664240 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -86,13 +86,13 @@ public: m_ShowBoundingBox(false), m_ShowScaleMeasures(false), m_Representation(Puppet::Surface), - m_Opacity(-1.0), + m_Opacity(1.0), m_Selectable(true), m_Selected(false), m_Visibility(true), m_Dragable(true) { - m_Color[0] = m_Color[1] = m_Color[2] = -1.0; + m_Color = Vector3d(-1, -1, -1); } ~PuppetData() { @@ -111,7 +111,7 @@ public: bool m_ShowBoundingBox; bool m_ShowScaleMeasures; int m_Representation; - double m_Color[3]; + Vector3d m_Color; double m_Opacity; bool m_Selectable; @@ -137,8 +137,9 @@ public: actor->GetProperty()->SetEdgeVisibility(0); } } - if (m_Color[0] != -1.0) { - actor->GetProperty()->SetColor(m_Color); + if (m_Color.x() != -1.0) { + double c[3] = {m_Color.x(), m_Color.y(), m_Color.z()}; + actor->GetProperty()->SetColor(c); } if (m_Opacity != -1.0) { @@ -280,8 +281,11 @@ void Puppet::SetProp(vtkProp *prop) pd->m_Representation = vp->GetRepresentation(); if (pd->m_Opacity < 0) pd->m_Opacity = vp->GetOpacity(); - if (pd->m_Color[0] < 0) - vp->GetColor(pd->m_Color); + if (pd->m_Color.x() < 0) { + double c[3]; + vp->GetColor(c); + pd->m_Color = Vector3d(c[0], c[1], c[2]); + } } } } @@ -622,10 +626,8 @@ struct AppearanceProxy { PuppetData* pd; template void serialize(Archive & ar, const unsigned int version) { - ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]); - ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]); - ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]); - ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity); + ar & boost::serialization::make_hrp("Color", pd->m_Color, "color"); + ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity).range(0.0, 1.0).set_default(1.0); ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index 74ae5ee..b0577f0 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -177,6 +177,9 @@ public: void save_override(const boost::serialization::hrp &t) { if (m_Puppet) { uLib::Property* p = new uLib::Property(m_Puppet, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); + if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); + if (t.has_default()) p->SetDefault(t.default_val()); + m_Puppet->RegisterDisplayProperty(p); Vtk::Puppet* puppet = m_Puppet; uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); From 46c39bc26e0e5420108abca491cbdf19192edb39 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 27 Mar 2026 16:55:26 +0000 Subject: [PATCH 08/24] add assembly to gcompose, not working yet --- app/gcompose/src/ContextModel.cpp | 140 ++++++++++++++++++++---- app/gcompose/src/ContextModel.h | 7 ++ app/gcompose/src/ContextPanel.cpp | 4 + src/Core/Object.h | 4 + src/Core/ObjectsContext.h | 1 + src/Math/Assembly.cpp | 37 +++++-- src/Math/Assembly.h | 9 +- src/Math/ContainerBox.h | 2 +- src/Math/Cylinder.h | 2 +- src/Math/MathRegistrations.cpp | 2 + src/Math/Transform.h | 2 +- src/Vtk/Math/vtkAssembly.cpp | 1 + src/Vtk/Math/vtkAssembly.h | 1 + src/Vtk/testing/CMakeLists.txt | 1 + src/Vtk/testing/PuppetParentingTest.cpp | 106 ++++++++++++++++++ 15 files changed, 287 insertions(+), 32 deletions(-) create mode 100644 src/Vtk/testing/PuppetParentingTest.cpp diff --git a/app/gcompose/src/ContextModel.cpp b/app/gcompose/src/ContextModel.cpp index dd1d42c..a8933ea 100644 --- a/app/gcompose/src/ContextModel.cpp +++ b/app/gcompose/src/ContextModel.cpp @@ -4,6 +4,11 @@ #include #include #include "Core/Object.h" +#include +#include +#include +#include +#include ContextModel::ContextModel(QObject* parent) : QAbstractItemModel(parent), m_rootContext(nullptr) {} @@ -11,12 +16,16 @@ ContextModel::ContextModel(QObject* parent) ContextModel::~ContextModel() {} void ContextModel::setContext(uLib::ObjectsContext* context) { + m_isReseting = true; beginResetModel(); m_rootContext = context; if (m_rootContext) { auto refresh = [this]() { + if (this->m_isReseting) return; + this->m_isReseting = true; this->beginResetModel(); this->endResetModel(); + this->m_isReseting = false; }; uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh); @@ -25,7 +34,6 @@ void ContextModel::setContext(uLib::ObjectsContext* context) { refresh(); }); uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) { - // Disconnect would be good here but not strictly required if refresh handles it refresh(); }); @@ -35,6 +43,7 @@ void ContextModel::setContext(uLib::ObjectsContext* context) { } } endResetModel(); + m_isReseting = false; } QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) const { @@ -48,8 +57,8 @@ QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) } } else { uLib::Object* parentObj = static_cast(parent.internalPointer()); - uLib::ObjectsContext* parentCtx = dynamic_cast(parentObj); - if (parentCtx && row < parentCtx->GetCount()) { + uLib::ObjectsContext* parentCtx = parentObj->GetChildren(); + if (parentCtx && row < (int)parentCtx->GetCount()) { return createIndex(row, column, parentCtx->GetObject(row)); } } @@ -65,36 +74,37 @@ QModelIndex ContextModel::parent(const QModelIndex& child) const { // Finding the parent of childObj is O(N) since there is no parent pointer. // We just do a recursive search starting from root context. - std::function findParent = - [&findParent](uLib::ObjectsContext* ctx, uLib::Object* target) -> uLib::ObjectsContext* { - for (const auto& obj : ctx->GetObjects()) { - if (obj == target) return ctx; - if (auto subCtx = dynamic_cast(obj)) { - if (auto p = findParent(subCtx, target)) return p; + std::function findParent = + [&findParent](uLib::Object* current, uLib::Object* target) -> uLib::Object* { + uLib::ObjectsContext* ctx = current->GetChildren(); + if (ctx) { + for (const auto& obj : ctx->GetObjects()) { + if (obj == target) return current; + if (auto p = findParent(obj, target)) return p; } } return nullptr; }; - uLib::ObjectsContext* parentCtx = findParent(m_rootContext, childObj); - if (!parentCtx || parentCtx == m_rootContext) { + uLib::Object* parentObj = findParent(m_rootContext, childObj); + if (!parentObj || parentObj == m_rootContext) { return QModelIndex(); // Root items have invalid parent index } - // Now need to find the row of parentCtx in its own parent Context. - uLib::ObjectsContext* grandParentCtx = findParent(m_rootContext, parentCtx); - if (!grandParentCtx) grandParentCtx = m_rootContext; + // Now need to find the row of parentObj in its own parent Context. + uLib::Object* grandParentObj = findParent(m_rootContext, parentObj); + uLib::ObjectsContext* grandParentCtx = grandParentObj ? grandParentObj->GetChildren() : m_rootContext; int row = -1; for (size_t i = 0; i < grandParentCtx->GetCount(); ++i) { - if (grandParentCtx->GetObject(i) == parentCtx) { + if (grandParentCtx->GetObject(i) == parentObj) { row = (int)i; break; } } if (row != -1) { - return createIndex(row, 0, parentCtx); + return createIndex(row, 0, parentObj); } return QModelIndex(); } @@ -107,8 +117,8 @@ int ContextModel::rowCount(const QModelIndex& parent) const { } uLib::Object* parentObj = static_cast(parent.internalPointer()); - if (auto parentCtx = dynamic_cast(parentObj)) { - return parentCtx->GetCount(); + if (auto parentCtx = parentObj->GetChildren()) { + return (int)parentCtx->GetCount(); } return 0; // leaf node } @@ -161,8 +171,98 @@ QVariant ContextModel::headerData(int section, Qt::Orientation orientation, int } Qt::ItemFlags ContextModel::flags(const QModelIndex& index) const { - if (!index.isValid()) return Qt::NoItemFlags; - return Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled; + if (!index.isValid()) return m_rootContext ? Qt::ItemIsDropEnabled : Qt::NoItemFlags; + + Qt::ItemFlags f = Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; + uLib::Object* obj = static_cast(index.internalPointer()); + if (dynamic_cast(obj)) { + f |= Qt::ItemIsDropEnabled; + } + return f; +} + +Qt::DropActions ContextModel::supportedDropActions() const { + return Qt::MoveAction; +} + +QStringList ContextModel::mimeTypes() const { + return {"application/x-ulib-object-ptr"}; +} + +QMimeData* ContextModel::mimeData(const QModelIndexList& indexes) const { + QMimeData* mimeData = new QMimeData(); + QByteArray encodedData; + QDataStream stream(&encodedData, QIODevice::WriteOnly); + for (const auto& idx : indexes) { + if (idx.isValid() && idx.column() == 0) { + void* ptr = idx.internalPointer(); + stream << reinterpret_cast(ptr); + } + } + mimeData->setData("application/x-ulib-object-ptr", encodedData); + return mimeData; +} + +bool ContextModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { + if (action != Qt::MoveAction || !data->hasFormat("application/x-ulib-object-ptr")) return false; + + uLib::ObjectsContext* targetCtx = m_rootContext; + if (parent.isValid()) { + uLib::Object* parentObj = static_cast(parent.internalPointer()); + targetCtx = dynamic_cast(parentObj); + } + if (!targetCtx) return false; + + QByteArray encodedData = data->data("application/x-ulib-object-ptr"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + std::vector objectsToMove; + while (!stream.atEnd()) { + qlonglong ptrVal; + stream >> ptrVal; + objectsToMove.push_back(reinterpret_cast(ptrVal)); + } + + if (objectsToMove.empty()) return false; + + // Helper to find and remove from current parent + std::function findAndRemoveRecursive = + [&findAndRemoveRecursive](uLib::Object* current, uLib::Object* target) { + if (auto ctx = current->GetChildren()) { + ctx->RemoveObject(target); + for (auto* obj : ctx->GetObjects()) { + findAndRemoveRecursive(obj, target); + } + } + }; + + m_isReseting = true; + beginResetModel(); + for (auto* obj : objectsToMove) { + // Don't drop onto itself or its descendants + bool invalid = (obj == targetCtx || obj == (uLib::Object*)targetCtx); + if (!invalid) { + // check if targetCtx is descendant of obj + std::function isDescendant = + [&isDescendant](uLib::Object* root, uLib::Object* target) -> bool { + if (auto ctx = root->GetChildren()) { + for (auto* child : ctx->GetObjects()) { + if (child == target) return true; + if (isDescendant(child, target)) return true; + } + } + return false; + }; + if (isDescendant(obj, (uLib::Object*)targetCtx)) invalid = true; + } + + if (!invalid) { + findAndRemoveRecursive(m_rootContext, obj); + targetCtx->AddObject(obj); + } + } + endResetModel(); + m_isReseting = false; + return true; } bool ContextModel::setData(const QModelIndex& index, const QVariant& value, int role) { diff --git a/app/gcompose/src/ContextModel.h b/app/gcompose/src/ContextModel.h index de6d5ea..778e6de 100644 --- a/app/gcompose/src/ContextModel.h +++ b/app/gcompose/src/ContextModel.h @@ -21,8 +21,15 @@ public: Qt::ItemFlags flags(const QModelIndex& index) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + // Drag and Drop support + Qt::DropActions supportedDropActions() const override; + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + private: uLib::ObjectsContext* m_rootContext; + bool m_isReseting = false; }; #endif // CONTEXT_MODEL_H diff --git a/app/gcompose/src/ContextPanel.cpp b/app/gcompose/src/ContextPanel.cpp index 24b7567..f34a986 100644 --- a/app/gcompose/src/ContextPanel.cpp +++ b/app/gcompose/src/ContextPanel.cpp @@ -38,6 +38,10 @@ ContextPanel::ContextPanel(QWidget* parent) m_treeView = new QTreeView(this); m_treeView->setObjectName("ContextTree"); m_treeView->setHeaderHidden(false); + m_treeView->setDragEnabled(true); + m_treeView->setAcceptDrops(true); + m_treeView->setDropIndicatorShown(true); + m_treeView->setDragDropMode(QAbstractItemView::DragDrop); m_model = new ContextModel(this); m_treeView->setModel(m_model); diff --git a/src/Core/Object.h b/src/Core/Object.h index 9eb31e1..4b0fda9 100644 --- a/src/Core/Object.h +++ b/src/Core/Object.h @@ -52,6 +52,7 @@ class polymorphic_oarchive; namespace uLib { class PropertyBase; +class ObjectsContext; class Version { public: @@ -101,6 +102,9 @@ public: // FIXX !!! virtual void DeepCopy(const Object ©); + /** @brief Returns a nested context for children objects, if any. */ + virtual ObjectsContext* GetChildren() { return nullptr; } + //////////////////////////////////////////////////////////////////////////// // SERIALIZATION // diff --git a/src/Core/ObjectsContext.h b/src/Core/ObjectsContext.h index f555dd2..57b213a 100644 --- a/src/Core/ObjectsContext.h +++ b/src/Core/ObjectsContext.h @@ -15,6 +15,7 @@ public: virtual ~ObjectsContext(); virtual const char * GetClassName() const { return "ObjectsContext"; } + virtual ObjectsContext* GetChildren() override { return this; } /** * @brief Adds an object to the context. diff --git a/src/Math/Assembly.cpp b/src/Math/Assembly.cpp index 1966fdd..ed24f2b 100644 --- a/src/Math/Assembly.cpp +++ b/src/Math/Assembly.cpp @@ -25,7 +25,9 @@ Assembly::Assembly() m_BBoxMin(Vector3f::Zero()), m_BBoxMax(Vector3f::Zero()), m_ShowBoundingBox(false), - m_GroupSelection(true) {} + m_GroupSelection(true) { + ULIB_ACTIVATE_PROPERTIES(*this); +} Assembly::Assembly(const Assembly ©) : ObjectsContext(copy), @@ -35,13 +37,25 @@ Assembly::Assembly(const Assembly ©) m_ShowBoundingBox(copy.m_ShowBoundingBox), m_GroupSelection(copy.m_GroupSelection) {} -Assembly::~Assembly() {} +Assembly::~Assembly() { + for (auto const& [obj, conn] : m_ChildConnections) { + conn.disconnect(); + } + m_ChildConnections.clear(); +} void Assembly::AddObject(Object *obj) { if (auto *at = dynamic_cast(obj)) { at->SetParent(this); } ObjectsContext::AddObject(obj); + + // Connect to child updates to recompute AABB + m_ChildConnections[obj] = Object::connect(obj, &Object::Updated, [this](){ + this->ComputeBoundingBox(); + this->Updated(); // Signal that assembly itself changed (AABB-wise) + }); + this->ComputeBoundingBox(); } void Assembly::RemoveObject(Object *obj) { @@ -49,7 +63,15 @@ void Assembly::RemoveObject(Object *obj) { if (at->GetParent() == this) at->SetParent(nullptr); } + + auto itConn = m_ChildConnections.find(obj); + if (itConn != m_ChildConnections.end()) { + itConn->second.disconnect(); + m_ChildConnections.erase(itConn); + } + ObjectsContext::RemoveObject(obj); + this->ComputeBoundingBox(); } void Assembly::ComputeBoundingBox() { @@ -64,12 +86,11 @@ void Assembly::ComputeBoundingBox() { m_BBoxMin = Vector3f(inf, inf, inf); m_BBoxMax = Vector3f(-inf, -inf, -inf); - Matrix4f invAsm = this->GetWorldMatrix().inverse(); - for (Object *obj : objects) { if (auto *box = dynamic_cast(obj)) { - // ContainerBox: wm is matrix from unit cube [0,1] to assembly base - Matrix4f m = invAsm * box->GetWorldMatrix(); + // ContainerBox: wm is matrix from unit cube [0,1] to local space + // Since it is parented to 'this', GetMatrix() is sufficient. + Matrix4f m = box->GetMatrix(); for (int i = 0; i < 8; ++i) { float x = (i & 1) ? 1.0f : 0.0f; float y = (i & 2) ? 1.0f : 0.0f; @@ -82,7 +103,7 @@ void Assembly::ComputeBoundingBox() { } } else if (auto *cyl = dynamic_cast(obj)) { // Cylinder: centered [-1, 1] radial, [-0.5, 0.5] height - Matrix4f m = invAsm * cyl->GetWorldMatrix(); + Matrix4f m = cyl->GetMatrix(); for (int i = 0; i < 8; ++i) { float x = (i & 1) ? 1.0f : -1.0f; float y = (i & 2) ? 0.5f : -0.5f; @@ -98,7 +119,7 @@ void Assembly::ComputeBoundingBox() { subAsm->ComputeBoundingBox(); Vector3f subMin, subMax; subAsm->GetBoundingBox(subMin, subMax); - Matrix4f m = invAsm * subAsm->GetWorldMatrix(); + Matrix4f m = subAsm->GetMatrix(); for (int i = 0; i < 8; ++i) { float x = (i & 1) ? subMax(0) : subMin(0); float y = (i & 2) ? subMax(1) : subMin(1); diff --git a/src/Math/Assembly.h b/src/Math/Assembly.h index 9ee9242..a96105c 100644 --- a/src/Math/Assembly.h +++ b/src/Math/Assembly.h @@ -52,6 +52,12 @@ public: Assembly(const Assembly ©); virtual ~Assembly(); + template + void serialize(ArchiveT & ar, const unsigned int version) { + ar & boost::serialization::make_nvp("AffineTransform", boost::serialization::base_object(*this)); + ar & boost::serialization::make_hrp("GroupSelection", m_GroupSelection); + } + virtual void AddObject(Object* obj) override; virtual void RemoveObject(Object* obj) override; @@ -93,7 +99,7 @@ signals: if (m_InUpdated) return; // break signal recursion m_InUpdated = true; this->ComputeBoundingBox(); - ULIB_SIGNAL_EMIT(Assembly::Updated); + ULIB_SIGNAL_EMIT(Object::Updated); m_InUpdated = false; } @@ -103,6 +109,7 @@ private: bool m_ShowBoundingBox; bool m_GroupSelection; bool m_InUpdated = false; + std::map m_ChildConnections; }; } // namespace uLib diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index 2a2f926..0e724cf 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -215,7 +215,7 @@ signals: /** Signal emitted when properties change */ virtual void Updated() override { this->Sync(); - ULIB_SIGNAL_EMIT(ContainerBox::Updated); + ULIB_SIGNAL_EMIT(Object::Updated); } private: diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index a9b33ce..7ba88fa 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -177,7 +177,7 @@ signals: /** Signal emitted when properties change */ virtual void Updated() override { this->Sync(); - ULIB_SIGNAL_EMIT(Cylinder::Updated); + ULIB_SIGNAL_EMIT(Object::Updated); } private: diff --git a/src/Math/MathRegistrations.cpp b/src/Math/MathRegistrations.cpp index 445552a..53d0903 100644 --- a/src/Math/MathRegistrations.cpp +++ b/src/Math/MathRegistrations.cpp @@ -5,12 +5,14 @@ #include "Math/TriangleMesh.h" #include "Math/QuadMesh.h" #include "Math/VoxImage.h" +#include "Math/Assembly.h" #include "Math/StructuredData.h" namespace uLib { ULIB_REGISTER_OBJECT(ContainerBox) ULIB_REGISTER_OBJECT(Cylinder) +ULIB_REGISTER_OBJECT(Assembly) ULIB_REGISTER_OBJECT(CylindricalGeometry) ULIB_REGISTER_OBJECT(SphericalGeometry) ULIB_REGISTER_OBJECT(TriangleMesh) diff --git a/src/Math/Transform.h b/src/Math/Transform.h index dd4a42f..bf92041 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -285,7 +285,7 @@ signals: /** Signal emitted when properties change */ virtual void Updated() override { this->Sync(); - ULIB_SIGNAL_EMIT(AffineTransform::Updated); + ULIB_SIGNAL_EMIT(Object::Updated); } private: diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index d25941d..55c7b71 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -70,6 +70,7 @@ void Assembly::InstallPipe() { m_BBoxActor->GetProperty()->SetColor(1.0, 0.85, 0.0); // gold wireframe m_BBoxActor->GetProperty()->SetLineWidth(1.5); m_BBoxActor->GetProperty()->SetOpacity(0.6); + m_BBoxActor->PickableOff(); m_BBoxActor->SetVisibility(m_Content ? m_Content->GetShowBoundingBox() : false); m_VtkAsm->AddPart(m_BBoxActor); diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index a63d54f..17defa9 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -48,6 +48,7 @@ public: virtual void SyncFromVtk() override; virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } + virtual uLib::ObjectsContext* GetChildren() override { return (uLib::ObjectsContext*)m_Content; } /** @brief Called when the model signals an update (model→VTK push). */ void contentUpdate(); diff --git a/src/Vtk/testing/CMakeLists.txt b/src/Vtk/testing/CMakeLists.txt index bf15373..df717ea 100644 --- a/src/Vtk/testing/CMakeLists.txt +++ b/src/Vtk/testing/CMakeLists.txt @@ -3,6 +3,7 @@ set(TESTS vtkViewerTest vtkHandlerWidget PuppetPropertyTest + PuppetParentingTest # vtkVoxImageTest # vtkTriangleMeshTest ) diff --git a/src/Vtk/testing/PuppetParentingTest.cpp b/src/Vtk/testing/PuppetParentingTest.cpp new file mode 100644 index 0000000..b9136df --- /dev/null +++ b/src/Vtk/testing/PuppetParentingTest.cpp @@ -0,0 +1,106 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova +// All rights reserved +// +// Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > +// +//////////////////////////////////////////////////////////////////////////////*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "testing-prototype.h" + +using namespace uLib; + +int main() { + BEGIN_TESTING(Puppet Parenting Test); + + ObjectsContext globalContext; + Vtk::Viewer viewer; + + // Create the display context, linked to the model context. + // It will automatically create visual puppets for each model object. + Vtk::vtkObjectsContext viewerContext(&globalContext); + viewerContext.ConnectRenderer(viewer.GetRenderer()); + + // 1. Create a model Assembly + auto* assembly = new Assembly(); + assembly->SetInstanceName("ParentAssembly"); + globalContext.AddObject(assembly); + + // Verify assembly puppet exists in the viewer context + Vtk::Puppet* assemblyPuppet = viewerContext.GetPuppet(assembly); + ASSERT_NOT_NULL(assemblyPuppet); + + // cast to Vtk::Assembly to access child context + auto* vtkAss = dynamic_cast(assemblyPuppet); + ASSERT_NOT_NULL(vtkAss); + + // 2. Create a child Box and add it to the Assembly + auto* box1 = new ContainerBox(Vector3f(10, 10, 10)); + box1->SetInstanceName("ChildBox1"); + box1->SetPosition(Vector3f(20, 0, 0)); + assembly->AddObject(box1); + + // Verify child puppet was created in the assembly's child context + Vtk::vtkObjectsContext* childVtkCtx = vtkAss->GetChildrenContext(); + ASSERT_NOT_NULL(childVtkCtx); + + Vtk::Puppet* box1Puppet = childVtkCtx->GetPuppet(box1); + ASSERT_NOT_NULL(box1Puppet); + + // 3. Move the parent and verify the child follows + assembly->SetPosition(Vector3f(100, 0, 0)); + assembly->Update(); + + // In VTK assemblies, the child's absolute matrix should reflect the parent's transform + vtkProp3D* box1Prop = vtkProp3D::SafeDownCast(box1Puppet->GetProp()); + ASSERT_NOT_NULL(box1Prop); + + vtkMatrix4x4* boxMatrix = box1Prop->GetMatrix(); + // Origin (0,0,0) + local(20,0,0) + assembly(100,0,0) = world(120,0,0) ? + // Actually, box1->GetPosition() is (20,0,0). + // The puppet ApplyTransform sets the prop orientation and position. + + std::cout << "Checking transformation chain..." << std::endl; + // std::cout << *boxMatrix << std::endl; + + // Verify relative positioning + double* pos = box1Prop->GetPosition(); + ASSERT_EQUAL(pos[0], 20.0); + + // The absolute world position can be checked via GetMatrix elements + // boxMatrix->GetElement(0, 3) should be 120.0 if the vtkAssembly nesting is working + // but vtkAssembly::GetMatrix() usually returns the LOCAL matrix unless called on the top property context? + // Actually vtkProp3D::GetMatrix() is the local matrix. + + // 4. Add another child + auto* box2 = new ContainerBox(Vector3f(5, 5, 5)); + box2->SetInstanceName("ChildBox2"); + box2->SetPosition(Vector3f(-20, 0, 0)); + assembly->AddObject(box2); + + Vtk::Puppet* box2Puppet = childVtkCtx->GetPuppet(box2); + ASSERT_NOT_NULL(box2Puppet); + + // Render if not in batch environment + if (!std::getenv("CTEST_PROJECT_NAME")) { + viewer.GetRenderer()->ResetCamera(); + viewer.Start(); + } + + END_TESTING; +} From 22d0041942d10c336c40bafae55197c33518f1af Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Mon, 30 Mar 2026 15:24:37 +0000 Subject: [PATCH 09/24] refactor: update Puppet transform logic to support AffineTransform world matrices and improve selection highlighting --- .clangd | 4 +- .vscode/settings.json | 5 +- src/Core/testing/testing-prototype.h | 2 + src/HEP/Geant/Solid.cpp | 4 +- src/Math/Assembly.cpp | 8 +- src/Math/Assembly.h | 6 +- src/Math/ContainerBox.h | 10 +- src/Math/Cylinder.h | 7 +- src/Math/Geometry.h | 146 +++++++-- src/Math/MathRegistrations.cpp | 1 + src/Math/QuadMesh.h | 4 +- src/Math/Transform.h | 366 +++++++++++------------ src/Math/TriangleMesh.h | 4 +- src/Math/testing/GeometryTest.cpp | 25 +- src/Math/testing/testing-prototype.h | 6 +- src/Python/math_bindings.cpp | 57 +++- src/Vtk/Math/testing/testing-prototype.h | 2 + src/Vtk/Math/vtkAssembly.cpp | 11 +- src/Vtk/Math/vtkAssembly.h | 1 - src/Vtk/Math/vtkContainerBox.h | 1 - src/Vtk/testing/PuppetParentingTest.cpp | 2 +- src/Vtk/testing/testing-prototype.h | 2 + src/Vtk/uLibVtkInterface.cxx | 116 ++++--- src/Vtk/uLibVtkInterface.h | 10 +- 24 files changed, 469 insertions(+), 331 deletions(-) diff --git a/.clangd b/.clangd index 432ea6a..7dfeedf 100644 --- a/.clangd +++ b/.clangd @@ -1,7 +1,7 @@ CompileFlags: CompilationDatabase: build Add: - - -I/home/rigoni/devel/cmt/ulib/src + - -I/home/rigoni/devel/cmt/uLib/src - -isystem/home/share/micromamba/envs/mutom/include - -isystem/home/share/micromamba/envs/mutom/include/eigen3 - -isystem/home/share/micromamba/envs/mutom/targets/x86_64-linux/include @@ -27,7 +27,7 @@ Diagnostics: --- If: - PathExclude: [/home/rigoni/devel/cmt/ulib/src/.*] + PathExclude: [/home/rigoni/devel/cmt/uLib/src/.*] Diagnostics: Suppress: ["*"] diff --git a/.vscode/settings.json b/.vscode/settings.json index ff5c426..598425a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "clangd.fallbackFlags": [ - "-I/home/rigoni/devel/cmt/ulib/src", + "-I/home/rigoni/devel/cmt/uLib/src", "-isystem/home/share/micromamba/envs/mutom/include", "-isystem/home/share/micromamba/envs/mutom/include/eigen3", "-isystem/home/share/micromamba/envs/mutom/targets/x86_64-linux/include", @@ -19,8 +19,7 @@ "clangd.semanticHighlighting.enable": true, "clangd.arguments": [ "--compile-commands-dir=build", - "--query-driver=/home/share/micromamba/envs/mutom/bin/g++,/home/share/micromamba/envs/mutom/bin/gcc,/home/share/micromamba/envs/mutom/bin/nvcc", - "--suppress-system-warnings", + "--query-driver=/home/share/micromamba/envs/mutom/bin/*", "--all-scopes-completion", "--completion-style=detailed", "--header-insertion=never", diff --git a/src/Core/testing/testing-prototype.h b/src/Core/testing/testing-prototype.h index 39f4e6c..f25accf 100644 --- a/src/Core/testing/testing-prototype.h +++ b/src/Core/testing/testing-prototype.h @@ -33,5 +33,7 @@ printf("..:: Testing " #name " ::..\n"); #define TEST1(val) _fail += (val)==0 #define TEST0(val) _fail += (val)!=0 +#define ASSERT_EQUAL(a,b) if((a)!=(b)) { printf("Assertion failed: " #a " != " #b " at line %d\n", __LINE__); _fail++; } +#define ASSERT_NOT_NULL(ptr) if((ptr)==NULL) { printf("Assertion failed: " #ptr " is NULL at line %d\n", __LINE__); _fail++; } #define END_TESTING return _fail; diff --git a/src/HEP/Geant/Solid.cpp b/src/HEP/Geant/Solid.cpp index e5f5ab2..54456a3 100644 --- a/src/HEP/Geant/Solid.cpp +++ b/src/HEP/Geant/Solid.cpp @@ -85,7 +85,7 @@ void Solid::SetMaterial(G4Material *material) { } void Solid::SetTransform(Matrix4f transform) { - uLib::AffineTransform t; + uLib::AffineTransform t; t.SetMatrix(transform); // 2. Extract position and rotation for Geant4 @@ -199,7 +199,7 @@ void BoxSolid::Update() { // We must rotate the offset vector because uLib box can be rotated. Vector3f center = pos + rot * (size * 0.5); - uLib::AffineTransform t; + uLib::AffineTransform t; t.SetPosition(center); t.SetRotation(rot); diff --git a/src/Math/Assembly.cpp b/src/Math/Assembly.cpp index ed24f2b..e232e3e 100644 --- a/src/Math/Assembly.cpp +++ b/src/Math/Assembly.cpp @@ -21,7 +21,7 @@ namespace uLib { Assembly::Assembly() : ObjectsContext(), - AffineTransform(), + TRS(), m_BBoxMin(Vector3f::Zero()), m_BBoxMax(Vector3f::Zero()), m_ShowBoundingBox(false), @@ -31,7 +31,7 @@ Assembly::Assembly() Assembly::Assembly(const Assembly ©) : ObjectsContext(copy), - AffineTransform(copy), + TRS(copy), m_BBoxMin(copy.m_BBoxMin), m_BBoxMax(copy.m_BBoxMax), m_ShowBoundingBox(copy.m_ShowBoundingBox), @@ -55,6 +55,10 @@ void Assembly::AddObject(Object *obj) { this->ComputeBoundingBox(); this->Updated(); // Signal that assembly itself changed (AABB-wise) }); + + // Parent -> Child propagation for world matrix updates + Object::connect(this, &Object::Updated, obj, &Object::Updated); + this->ComputeBoundingBox(); } diff --git a/src/Math/Assembly.h b/src/Math/Assembly.h index a96105c..4bca2d6 100644 --- a/src/Math/Assembly.h +++ b/src/Math/Assembly.h @@ -43,9 +43,9 @@ namespace uLib { * A bounding box is automatically computed from all contained objects and * can be queried or shown/hidden through the VTK puppet. */ -class Assembly : public ObjectsContext, public AffineTransform { +class Assembly : public ObjectsContext, public TRS { public: - uLibTypeMacro(Assembly, ObjectsContext, AffineTransform) + uLibTypeMacro(Assembly, ObjectsContext, TRS) virtual const char *GetClassName() const override { return "Assembly"; } Assembly(); @@ -54,7 +54,7 @@ public: template void serialize(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp("AffineTransform", boost::serialization::base_object(*this)); + ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); ar & boost::serialization::make_hrp("GroupSelection", m_GroupSelection); } diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index 0e724cf..eb782fa 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -39,20 +39,21 @@ namespace uLib { * @brief Represents an oriented bounding box (OBB) within a hierarchical * transformation system. * - * ContainerBox inherits from AffineTransform, which defines its parent + * ContainerBox inherits from TRS, which defines its parent * coordinate system. It contains an internal local transformation (m_LocalT) * that defines the box's specific origin and size relative to its own * coordinate system. */ -class ContainerBox : public AffineTransform { +class ContainerBox : public TRS { public: - uLibTypeMacro(ContainerBox, AffineTransform) + uLibTypeMacro(ContainerBox, TRS) virtual const char * GetClassName() const override { return "ContainerBox"; } //////////////////////////////////////////////////////////////////////////// // PROPERTIES // + Vector3f Size; Vector3f Origin; @@ -86,7 +87,7 @@ public: */ ContainerBox(const ContainerBox ©) : m_LocalT(this), // Reset parent to the new object - AffineTransform(copy), + TRS(copy), Size(copy.Size), Origin(copy.Origin) { ULIB_ACTIVATE_PROPERTIES(*this); @@ -98,6 +99,7 @@ public: */ template void serialize(ArchiveT & ar, const unsigned int version) { + ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); ar & HRP(Size); ar & HRP(Origin); } diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index 7ba88fa..aa1e7c7 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -39,10 +39,10 @@ namespace uLib { * The cylinder orientation is defined by the Axis property (0=X, 1=Y, 2=Z). * By default, it is aligned with the Y axis (Axis=1). */ -class Cylinder : public AffineTransform { +class Cylinder : public TRS { public: - uLibTypeMacro(Cylinder, AffineTransform) + uLibTypeMacro(Cylinder, TRS) /** * @brief PROPERTIES @@ -74,7 +74,7 @@ public: * @brief Copy constructor. */ Cylinder(const Cylinder ©) - : m_LocalT(this), AffineTransform(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) { + : m_LocalT(this), TRS(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) { ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } @@ -84,6 +84,7 @@ public: */ template void serialize(ArchiveT & ar, const unsigned int version) { + ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); ar & HRP(Radius); ar & HRP(Height); ar & HRP(Axis); diff --git a/src/Math/Geometry.h b/src/Math/Geometry.h index 5322b68..7d29ec7 100644 --- a/src/Math/Geometry.h +++ b/src/Math/Geometry.h @@ -35,12 +35,22 @@ namespace uLib { -class Geometry : public AffineTransform { + +class Geometry : virtual public Object { +protected: + Geometry* m_Parent = nullptr; + public: - uLibTypeMacro(Geometry, AffineTransform) + uLibTypeMacro(Geometry, Object) virtual const char * GetClassName() const override { return "Geometry"; } + virtual void SetParent(Geometry* p) { m_Parent = p; } + virtual Geometry* GetParent() const { return m_Parent; } + + virtual bool IsLinear() const { return false; } + virtual bool IsPure() const { return false; } + virtual Vector3f ToLinear(const Vector3f& curved_space) const { return curved_space; } @@ -49,38 +59,120 @@ public: return cartesian_space; } - inline Vector4f GetWorldPoint(const Vector4f v) const { - Vector3f lin = ToLinear(Vector3f(v.x(), v.y(), v.z())); - return this->GetWorldMatrix() * Vector4f(lin.x(), lin.y(), lin.z(), v.w()); + virtual Vector4f GetWorldPoint(const Vector4f v) const = 0; + virtual Vector4f GetLocalPoint(const Vector4f v) const = 0; + + virtual Vector4f GetWorldPoint(const float x, const float y, const float z) const { + return GetWorldPoint(Vector4f(x,y,z,1)); } - inline Vector4f GetWorldPoint(const float x, const float y, const float z) { - return this->GetWorldPoint(Vector4f(x,y,z,1)); + virtual Vector4f GetLocalPoint(const float x, const float y, const float z) const { + return GetLocalPoint(Vector4f(x,y,z,1)); } - inline Vector4f GetLocalPoint(const Vector4f v) const { - Vector4f loc_lin = this->GetWorldMatrix().inverse() * v; - Vector3f curv = FromLinear(Vector3f(loc_lin.x(), loc_lin.y(), loc_lin.z())); - return Vector4f(curv.x(), curv.y(), curv.z(), loc_lin.w()); + virtual Vector4f GetWorldPoint(const Vector3f v) const { + return GetWorldPoint(Vector4f(v.x(), v.y(), v.z(), 1.0f)); } - inline Vector4f GetLocalPoint(const float x, const float y, const float z) { - return this->GetLocalPoint(Vector4f(x,y,z,1)); + virtual Vector4f GetLocalPoint(const Vector3f v) const { + return GetLocalPoint(Vector4f(v.x(), v.y(), v.z(), 1.0f)); } + + virtual void Translate(Vector3f t) = 0; + virtual void Rotate(Vector3f r) = 0; + virtual void Scale(Vector3f s) = 0; + }; -class CylindricalGeometry : public Geometry { + + +class LinearGeometry : public Geometry { +protected: + Affine3f m_T = Affine3f::Identity(); + public: - uLibTypeMacro(CylindricalGeometry, Geometry) + uLibTypeMacro(LinearGeometry, Geometry) + + virtual const char * GetClassName() const override { return "LinearGeometry"; } + + virtual bool IsLinear() const override { return true; } + virtual bool IsPure() const override { return true; } + + virtual Vector4f GetWorldPoint(const Vector4f v) const override { + Vector3f lin_v = ToLinear(v.head<3>()); + Vector4f v_lin(lin_v.x(), lin_v.y(), lin_v.z(), v.w()); + + Affine3f combined = m_T; + const Geometry* curr = m_Parent; + while (curr && curr->IsLinear() && curr->IsPure()) { + combined = static_cast(curr)->m_T * combined; + curr = curr->GetParent(); + } + + Vector4f v_res = combined.matrix() * v_lin; + if (curr) return curr->GetWorldPoint(v_res); + return v_res; + } + + virtual Vector4f GetLocalPoint(const Vector4f v) const override { + Vector4f v_parent = m_Parent ? m_Parent->GetLocalPoint(v) : v; + Vector4f v_loc_lin = m_T.inverse().matrix() * v_parent; + Vector3f v_curv = FromLinear(v_loc_lin.head<3>()); + return Vector4f(v_curv.x(), v_curv.y(), v_curv.z(), v_loc_lin.w()); + } + + virtual void Translate(Vector3f t) override { + m_T.translate(t); + } + + virtual void Rotate(Vector3f r) override { + this->EulerYZYRotate(r); + } + + virtual void Scale(Vector3f s) override { + m_T.scale(s); + } + + void SetPosition(const Vector3f& v) { m_T.translation() = v; } + Vector3f GetPosition() const { return m_T.translation(); } + + void EulerYZYRotate(const Vector3f& e) { + Matrix3f mat; + mat = Eigen::AngleAxisf(e.x(), Vector3f::UnitY()) + * Eigen::AngleAxisf(e.y(), Vector3f::UnitZ()) + * Eigen::AngleAxisf(e.z(), Vector3f::UnitY()); + m_T.rotate(mat); + } + + void FlipAxes(int first, int second) { + Matrix3f mat = Matrix3f::Identity(); + mat.col(first).swap(mat.col(second)); + m_T.rotate(mat); + } + + const Affine3f& GetTransform() const { return m_T; } + void SetTransform(const Affine3f& t) { m_T = t; } +}; + + + + +class CylindricalGeometry : public LinearGeometry { +public: + uLibTypeMacro(CylindricalGeometry, LinearGeometry) CylindricalGeometry() {} - Vector3f ToLinear(const Vector3f& cylindrical) const { + virtual const char * GetClassName() const override { return "CylindricalGeometry"; } + + virtual bool IsPure() const override { return false; } + + Vector3f ToLinear(const Vector3f& cylindrical) const override { return Vector3f(cylindrical.x() * std::cos(cylindrical.y()), cylindrical.x() * std::sin(cylindrical.y()), cylindrical.z()); } - Vector3f FromLinear(const Vector3f& linear) const { + Vector3f FromLinear(const Vector3f& linear) const override { float r = std::sqrt(linear.x() * linear.x() + linear.y() * linear.y()); float phi = std::atan2(linear.y(), linear.x()); return Vector3f(r, phi, linear.z()); @@ -88,14 +180,16 @@ public: }; -class SphericalGeometry : public Geometry { +class SphericalGeometry : public LinearGeometry { public: - uLibTypeMacro(SphericalGeometry, Geometry) + uLibTypeMacro(SphericalGeometry, LinearGeometry) SphericalGeometry() {} virtual const char * GetClassName() const override { return "SphericalGeometry"; } - Vector3f ToLinear(const Vector3f& spherical) const { + virtual bool IsPure() const override { return false; } + + Vector3f ToLinear(const Vector3f& spherical) const override { float r = spherical.x(); float theta = spherical.y(); float phi = spherical.z(); @@ -104,7 +198,7 @@ public: r * std::cos(theta)); } - Vector3f FromLinear(const Vector3f& linear) const { + Vector3f FromLinear(const Vector3f& linear) const override { float r = linear.norm(); float theta = (r == 0.0f) ? 0.0f : std::acos(linear.z() / r); float phi = std::atan2(linear.y(), linear.x()); @@ -113,14 +207,16 @@ public: }; -class ToroidalGeometry : public Geometry { +class ToroidalGeometry : public LinearGeometry { public: - uLibTypeMacro(ToroidalGeometry, Geometry) + uLibTypeMacro(ToroidalGeometry, LinearGeometry) ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {} virtual const char * GetClassName() const override { return "ToroidalGeometry"; } - Vector3f ToLinear(const Vector3f& toroidal) const { + virtual bool IsPure() const override { return false; } + + Vector3f ToLinear(const Vector3f& toroidal) const override { float r = toroidal.x(); float theta = toroidal.y(); float phi = toroidal.z(); @@ -129,7 +225,7 @@ public: r * std::sin(theta)); } - Vector3f FromLinear(const Vector3f& linear) const { + Vector3f FromLinear(const Vector3f& linear) const override { float phi = std::atan2(linear.y(), linear.x()); float r_xy = std::sqrt(linear.x() * linear.x() + linear.y() * linear.y()); float delta_r = r_xy - m_Rtor; diff --git a/src/Math/MathRegistrations.cpp b/src/Math/MathRegistrations.cpp index 53d0903..86056fc 100644 --- a/src/Math/MathRegistrations.cpp +++ b/src/Math/MathRegistrations.cpp @@ -10,6 +10,7 @@ namespace uLib { +ULIB_REGISTER_OBJECT(TRS) ULIB_REGISTER_OBJECT(ContainerBox) ULIB_REGISTER_OBJECT(Cylinder) ULIB_REGISTER_OBJECT(Assembly) diff --git a/src/Math/QuadMesh.h b/src/Math/QuadMesh.h index 7652c0f..8361f70 100644 --- a/src/Math/QuadMesh.h +++ b/src/Math/QuadMesh.h @@ -34,10 +34,10 @@ namespace uLib { -class QuadMesh : public AffineTransform +class QuadMesh : public TRS { public: - uLibTypeMacro(QuadMesh, AffineTransform) + uLibTypeMacro(QuadMesh, TRS) virtual const char * GetClassName() const override { return "QuadMesh"; } diff --git a/src/Math/Transform.h b/src/Math/Transform.h index bf92041..9f66e43 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -57,21 +57,150 @@ namespace uLib { + +using Eigen::Isometry3f; +using Eigen::Isometry3d; + +using Eigen::Affine3f; +using Eigen::Affine3d; + +using Eigen::Projective3f; +using Eigen::Projective3d; + + + + + + + +//////////////////////////////////////////////////////////////////////////////// +///////// AFFINE TRANSFORM WRAPPER ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + + +class AffineTransform : virtual public Object { +public: + uLibTypeMacro(AffineTransform, Object) +protected: + + Affine3f m_T; + AffineTransform *m_Parent; + +public: + AffineTransform() : + m_T(Matrix4f::Identity()), + m_Parent(NULL) + {} + + AffineTransform(AffineTransform *parent) : + m_T(Matrix4f::Identity()), + m_Parent(parent) + {} + + AffineTransform(const AffineTransform ©) : + m_T(copy.m_T), + m_Parent(copy.m_Parent) + {} + + Affine3f& GetTransform() { return m_T; } + + AffineTransform *GetParent() const { return this->m_Parent; } + + void SetParent(AffineTransform *name) { this->m_Parent = name; } + + void SetMatrix (const Matrix4f &mat) { m_T.matrix() = mat; } + Matrix4f& GetMatrix () { return m_T.matrix(); } + const Matrix4f& GetMatrix () const { return m_T.matrix(); } + + Matrix4f GetWorldMatrix() const + { + if(!m_Parent) return m_T.matrix(); + else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A // + } + + void SetWorldMatrix(const Matrix4f &mat) + { + if(!m_Parent) m_T.matrix() = mat; + else m_T.matrix() = m_Parent->GetWorldMatrix().inverse() * mat; + } + + void SetPosition(const Vector3f &v) { this->m_T.translation() = v; } + + Vector3f GetPosition() const { return this->m_T.translation(); } + + void SetRotation(const Matrix3f &m) { this->m_T.linear() = m; } + + Matrix3f GetRotation() const { return this->m_T.rotation(); } + + void Translate(const Vector3f &v) { this->m_T.translate(v); } + + void Scale(const Vector3f &v) { this->m_T.scale(v); } + + Vector3f GetScale() const { + return Vector3f(this->m_T.linear().col(0).norm(), + this->m_T.linear().col(1).norm(), + this->m_T.linear().col(2).norm()); + } + + + void Rotate(const Matrix3f &m) { this->m_T.rotate(m); } + + void Rotate(const float angle, Vector3f axis) + { + axis.normalize(); // prehaps not necessary ( see eigens ) + Eigen::AngleAxisf ax(angle,axis); + this->m_T.rotate(Eigen::Quaternion(ax)); + } + + void Rotate(const Vector3f euler_axis) { + float angle = euler_axis.norm(); + Rotate(angle,euler_axis); + } + + void PreRotate(const Matrix3f &m) { this->m_T.prerotate(m); } + + void QuaternionRotate(const Vector4f &q) + { this->m_T.rotate(Eigen::Quaternion(q)); } + + void EulerYZYRotate(const Vector3f &e) { + Matrix3f mat; + mat = Eigen::AngleAxisf(e.x(), Vector3f::UnitY()) + * Eigen::AngleAxisf(e.y(), Vector3f::UnitZ()) + * Eigen::AngleAxisf(e.z(), Vector3f::UnitY()); + m_T.rotate(mat); + } + + void FlipAxes(int first, int second) + { + Matrix3f mat = Matrix3f::Identity(); + mat.col(first).swap(mat.col(second)); + m_T.rotate(mat); + } +}; + + + //////////////////////////////////////////////////////////////////////////////// ///////// TRS PARAMETERS ///////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// typedef Eigen::Affine3f AffineMatrix; -class TRS { +class TRS : public AffineTransform { + public: + uLibTypeMacro(TRS, AffineTransform) + Vector3f position = Vector3f::Zero(); Vector3f rotation = Vector3f::Zero(); Vector3f scaling = Vector3f::Ones(); TRS() = default; - TRS(const class AffineTransform& at); + TRS(const class AffineTransform& at) { + this->FromMatrix(at.GetMatrix()); + } TRS(const Matrix4f& mat) { this->FromMatrix(mat); @@ -90,221 +219,60 @@ public: if (this->scaling(1) > 1e-6) rot.col(1) /= this->scaling(1); if (this->scaling(2) > 1e-6) rot.col(2) /= this->scaling(2); - // Decompose to Euler angles matching VTK (M = Rz * Ry * Rx) - // Store internally as RADIANS (standard for uLib properties) Vector3f euler = rot.eulerAngles(2, 1, 0); this->rotation = Vector3f(euler(2), euler(1), euler(0)); + + this->SetMatrix(mat); + } + + void SetPosition(const Vector3f &v) { + position = v; + this->AffineTransform::SetPosition(v); + } + + void SetRotation(const Vector3f &v) { + rotation = v; + this->SyncMatrix(); + } + + void SetOrientation(const Vector3f &v) { SetRotation(v); } + + void SetScale(const Vector3f &v) { + scaling = v; + this->SyncMatrix(); + } + + void SyncMatrix() { + this->GetTransform() = GetAffineMatrix(); } template void serialize(ArchiveT & ar, const unsigned int version) { ar & HRPU(position, "mm"); - ar & HRPU(rotation, "deg"); // Metadata informs UI to convert to/from degrees + ar & HRPU(rotation, "rad"); ar & HRP(scaling); } AffineMatrix GetAffineMatrix() const { - AffineMatrix t = AffineMatrix::Identity(); - t.translate(position); - - // rotation is in Radians here - t.rotate(Eigen::AngleAxisf(rotation.z(), Vector3f::UnitZ())); - t.rotate(Eigen::AngleAxisf(rotation.y(), Vector3f::UnitY())); - t.rotate(Eigen::AngleAxisf(rotation.x(), Vector3f::UnitX())); - - t.scale(scaling); - return t; + AffineMatrix m = AffineMatrix::Identity(); + m.translate(position); + m.rotate(Eigen::AngleAxisf(rotation.z(), Vector3f::UnitZ())); + m.rotate(Eigen::AngleAxisf(rotation.y(), Vector3f::UnitY())); + m.rotate(Eigen::AngleAxisf(rotation.x(), Vector3f::UnitX())); + m.scale(scaling); + return m; + } + + Matrix4f GetMatrix() const { + return this->GetAffineMatrix().matrix(); } }; -//////////////////////////////////////////////////////////////////////////////// -///////// AFFINE TRANSFORM WRAPPER ////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -class AffineTransform : virtual public Object { -public: - uLibTypeMacro(AffineTransform, Object) - - TRS Transform; - -private: - void NotifyProperties() { - PropertyBase *p; - if ((p = this->GetProperty("Transform.position"))) p->Updated(); - if ((p = this->GetProperty("Transform.rotation"))) p->Updated(); - if ((p = this->GetProperty("Transform.scaling"))) p->Updated(); - } - -protected: - Eigen::Affine3f m_T; - AffineTransform *m_Parent; - -public: - AffineTransform() : - m_T(Matrix4f::Identity()), - m_Parent(NULL) - { - ULIB_ACTIVATE_PROPERTIES(*this); - this->Sync(); - } - - virtual ~AffineTransform() {} - - AffineTransform(AffineTransform *parent) : - m_T(Matrix4f::Identity()), - m_Parent(parent) - { - ULIB_ACTIVATE_PROPERTIES(*this); - this->Sync(); - } - - AffineTransform(const AffineTransform ©) : - m_T(copy.m_T), - m_Parent(copy.m_Parent), - Transform(copy.Transform) - { - ULIB_ACTIVATE_PROPERTIES(*this); - this->Sync(); - } - - /** - * @brief Registration of properties in groups. - */ - template - void serialize(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp("Transform", Transform); - } - - Eigen::Affine3f& GetTransform() { return m_T; } - - AffineTransform *GetParent() const { return this->m_Parent; } - - void SetParent(AffineTransform *name) { this->m_Parent = name; } - - void SetMatrix (Matrix4f mat) { - m_T.matrix() = mat; - this->UpdatePropertiesFromMatrix(); - } - - Matrix4f GetMatrix() const { return m_T.matrix(); } - - Matrix4f GetWorldMatrix() const - { - if(!m_Parent) return m_T.matrix(); - else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A // - } - - void SetPosition(const Vector3f v) { - this->Transform.position = v; - this->Updated(); - this->NotifyProperties(); - } - Vector3f GetPosition() const { return this->Transform.position; } - - void SetOrientation(const Vector3f v) { - this->Transform.rotation = v; - this->Updated(); - this->NotifyProperties(); - } - Vector3f GetOrientation() const { return this->Transform.rotation; } - - void SetScale(const Vector3f v) { - this->Transform.scaling = v; - this->Updated(); - this->NotifyProperties(); - } - Vector3f GetScale() const { return this->Transform.scaling; } - - void SetRotation(const Matrix3f m) { - this->m_T.linear() = m; - this->UpdatePropertiesFromMatrix(); - } - - Matrix3f GetRotation() const { return this->m_T.rotation(); } - - void Translate(const Vector3f v) { - this->Transform.position += v; - this->Sync(); - } - - void Scale(const Vector3f v) { - this->Transform.scaling = this->Transform.scaling.cwiseProduct(v); - this->Sync(); - } - - - void Rotate(const Matrix3f m) { - this->m_T.rotate(m); - this->UpdatePropertiesFromMatrix(); - } - - void Rotate(const float angle, Vector3f axis) - { - axis.normalize(); - Eigen::AngleAxisf ax(angle,axis); - this->m_T.rotate(Eigen::Quaternion(ax)); - this->UpdatePropertiesFromMatrix(); - } - - void Rotate(const Vector3f euler_axis) { - float angle = euler_axis.norm(); - Rotate(angle,euler_axis); - } - - void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); this->UpdatePropertiesFromMatrix(); } - - void QuaternionRotate(const Vector4f q) - { this->m_T.rotate(Eigen::Quaternion(q)); this->UpdatePropertiesFromMatrix(); } - - void EulerYZYRotate(const Vector3f e) { - this->Transform.rotation = e; - this->Sync(); - } - - void FlipAxes(int first, int second) - { - Matrix3f mat = Matrix3f::Identity(); - mat.col(first).swap(mat.col(second)); - m_T.rotate(mat); - this->UpdatePropertiesFromMatrix(); - } - - /** - * @brief Decomposes the internal matrix m_T back into Position, Orientation, and Scale properties. - */ - void UpdatePropertiesFromMatrix() { - this->Transform.FromMatrix(this->GetMatrix()); - - PropertyBase *p; - if ((p = this->GetProperty("Transform.position"))) p->Updated(); - if ((p = this->GetProperty("Transform.rotation"))) p->Updated(); - if ((p = this->GetProperty("Transform.scaling"))) p->Updated(); - } - -signals: - /** Signal emitted when properties change */ - virtual void Updated() override { - this->Sync(); - ULIB_SIGNAL_EMIT(Object::Updated); - } - -private: - void Sync() { - m_T.matrix() = this->Transform.GetAffineMatrix().matrix(); - } -}; - -inline TRS::TRS(const AffineTransform& at) { - this->position = at.GetPosition(); - this->rotation = at.GetOrientation(); - this->scaling = at.GetScale(); -} - -} +} // uLib diff --git a/src/Math/TriangleMesh.h b/src/Math/TriangleMesh.h index 94f5582..590fdf3 100644 --- a/src/Math/TriangleMesh.h +++ b/src/Math/TriangleMesh.h @@ -37,10 +37,10 @@ namespace uLib { -class TriangleMesh : public AffineTransform +class TriangleMesh : public TRS { public: - uLibTypeMacro(TriangleMesh, AffineTransform) + uLibTypeMacro(TriangleMesh, TRS) virtual const char * GetClassName() const override { return "TriangleMesh"; } diff --git a/src/Math/testing/GeometryTest.cpp b/src/Math/testing/GeometryTest.cpp index 075e750..fc0c371 100644 --- a/src/Math/testing/GeometryTest.cpp +++ b/src/Math/testing/GeometryTest.cpp @@ -53,7 +53,7 @@ int main() ///////////////// GEOMETRY TESTING /////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// - Geometry Geo; + LinearGeometry Geo; Geo.SetPosition(Vector3f(1,1,1)); Geo.EulerYZYRotate(Vector3f(0,0,0)); @@ -77,7 +77,7 @@ int main() Geo.EulerYZYRotate(Vector3f(0,0,M_PI_2)); wp = Geo.GetWorldPoint(HPoint3f(1,1,1)); - // std::cout << "Geometry matrix\n" << Geo.GetTransform() << "\n"; + // std::cout << "Geometry matrix\n" << Geo.GetTransform().matrix() << "\n"; // std::cout << "World 1,1,1 coords\n" << wp << "\n"; TEST0( Vector4f0(wp - HPoint3f(0,2,2)) ); @@ -122,6 +122,27 @@ int main() TEST0( Vector4f0(recovered.homogeneous() - tor_pt.homogeneous()) ); } + // PARENT GEOMETRY TESTING + { + LinearGeometry parent; + parent.Translate(Vector3f(10, 0, 0)); + + LinearGeometry child; + child.SetParent(&parent); + child.Translate(Vector3f(0, 5, 0)); + + HPoint3f wp = child.GetWorldPoint(HPoint3f(1, 1, 1)); + TEST0( Vector4f0(wp - HPoint3f(11, 6, 1)) ); + + CylindricalGeometry cparent; + LinearGeometry grandchild; + grandchild.SetParent(&cparent); + grandchild.Translate(Vector3f(1, 0, 0)); + + HPoint3f gp = grandchild.GetWorldPoint(HPoint3f(1, M_PI_2, 0)); + TEST0( Vector4f0(gp - HPoint3f(0, 2, 0)) ); + } + END_TESTING; } diff --git a/src/Math/testing/testing-prototype.h b/src/Math/testing/testing-prototype.h index 2e46ed8..1d30f7b 100644 --- a/src/Math/testing/testing-prototype.h +++ b/src/Math/testing/testing-prototype.h @@ -31,8 +31,10 @@ static int _fail = 0; \ printf("..:: Testing " #name " ::..\n"); -#define TEST1(val) if ((val)==0) { printf("Assertion failed: %s != 0\n", #val); _fail++; } -#define TEST0(val) if ((val)!=0) { printf("Assertion failed: %s != 0\n", #val); _fail++; } +#define TEST1(val) _fail += (val)==0 +#define TEST0(val) _fail += (val)!=0 +#define ASSERT_EQUAL(a,b) if((a)!=(b)) { printf("Assertion failed: " #a " != " #b " at line %d\n", __LINE__); _fail++; } +#define ASSERT_NOT_NULL(ptr) if((ptr)==NULL) { printf("Assertion failed: " #ptr " is NULL at line %d\n", __LINE__); _fail++; } #define END_TESTING return _fail; #define ASSERT_EQ(a, b) if ((a) != (b)) { printf("Assertion failed: %s != %s\n", #a, #b); _fail++; } \ No newline at end of file diff --git a/src/Python/math_bindings.cpp b/src/Python/math_bindings.cpp index 9c6e6fe..2d08398 100644 --- a/src/Python/math_bindings.cpp +++ b/src/Python/math_bindings.cpp @@ -162,6 +162,12 @@ void init_math(py::module_ &m) { .def_readwrite("direction_error", &HError3f::direction_error); // 3. Dynamic Vectors (uLib::Vector) + py::class_>(m, "TRS") + .def(py::init<>()) + .def_readwrite("position", &TRS::position) + .def_readwrite("rotation", &TRS::rotation) + .def_readwrite("scaling", &TRS::scaling); + py::bind_vector>(m, "Vector_i") .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) .def("MoveToRAM", &uLib::Vector::MoveToRAM); @@ -268,7 +274,7 @@ void init_math(py::module_ &m) { // 5. Core Math Structures py::class_>(m, - "AffineTransform") + "AffineTransform") .def(py::init<>()) .def("GetWorldMatrix", &AffineTransform::GetWorldMatrix) .def("SetPosition", &AffineTransform::SetPosition) @@ -278,23 +284,46 @@ void init_math(py::module_ &m) { .def("SetRotation", &AffineTransform::SetRotation) .def("GetRotation", &AffineTransform::GetRotation) .def("Rotate", - py::overload_cast(&AffineTransform::Rotate)) + py::overload_cast(&AffineTransform::Rotate)) .def("Rotate", py::overload_cast(&AffineTransform::Rotate)) .def("Rotate", py::overload_cast(&AffineTransform::Rotate)) .def("EulerYZYRotate", &AffineTransform::EulerYZYRotate) - .def("FlipAxes", &AffineTransform::FlipAxes); + .def("FlipAxes", &AffineTransform::FlipAxes) + .def("SetWorldMatrix", &AffineTransform::SetWorldMatrix); - py::class_>(m, "Geometry") + py::class_>(m, "TRS") .def(py::init<>()) - .def("GetWorldPoint", py::overload_cast( - &Geometry::GetWorldPoint, py::const_)) - .def("GetWorldPoint", - py::overload_cast(&Geometry::GetWorldPoint)) - .def("GetLocalPoint", py::overload_cast( - &Geometry::GetLocalPoint, py::const_)) - .def("GetLocalPoint", - py::overload_cast(&Geometry::GetLocalPoint)); + .def(py::init()) + .def_readwrite("position", &TRS::position) + .def_readwrite("rotation", &TRS::rotation) + .def_readwrite("scaling", &TRS::scaling) + .def("SetPosition", &TRS::SetPosition) + .def("SetRotation", &TRS::SetRotation) + .def("SetOrientation", &TRS::SetOrientation) + .def("SetScale", &TRS::SetScale) + .def("FromMatrix", &TRS::FromMatrix) + .def("GetMatrix", &TRS::GetMatrix); + + py::class_>(m, "Geometry") + .def("GetParent", &Geometry::GetParent) + .def("SetParent", &Geometry::SetParent) + .def("GetWorldPoint", py::overload_cast(&Geometry::GetWorldPoint, py::const_)) + .def("GetWorldPoint", py::overload_cast(&Geometry::GetWorldPoint, py::const_)) + .def("GetLocalPoint", py::overload_cast(&Geometry::GetLocalPoint, py::const_)) + .def("GetLocalPoint", py::overload_cast(&Geometry::GetLocalPoint, py::const_)); + + py::class_>(m, "LinearGeometry") + .def(py::init<>()) + .def("Translate", &LinearGeometry::Translate) + .def("Rotate", &LinearGeometry::Rotate) + .def("Scale", &LinearGeometry::Scale) + .def("SetPosition", &LinearGeometry::SetPosition) + .def("GetPosition", &LinearGeometry::GetPosition) + .def("EulerYZYRotate", &LinearGeometry::EulerYZYRotate) + .def("FlipAxes", &LinearGeometry::FlipAxes) + .def("GetTransform", &LinearGeometry::GetTransform) + .def("SetTransform", &LinearGeometry::SetTransform); py::class_>( m, "ContainerBox") @@ -427,7 +456,7 @@ void init_math(py::module_ &m) { .def("__getitem__", py::overload_cast(&VoxImage::operator[])); - py::class_(m, "TriangleMesh") + py::class_>(m, "TriangleMesh") .def(py::init<>()) .def("AddPoint", &TriangleMesh::AddPoint) .def("AddTriangle", @@ -439,7 +468,7 @@ void init_math(py::module_ &m) { .def("GetTriangle", &TriangleMesh::GetTriangle) .def("GetNormal", &TriangleMesh::GetNormal); - py::class_(m, "QuadMesh") + py::class_>(m, "QuadMesh") .def(py::init<>()) .def("AddPoint", &QuadMesh::AddPoint) .def("AddQuad", diff --git a/src/Vtk/Math/testing/testing-prototype.h b/src/Vtk/Math/testing/testing-prototype.h index c801261..b3eb885 100644 --- a/src/Vtk/Math/testing/testing-prototype.h +++ b/src/Vtk/Math/testing/testing-prototype.h @@ -36,6 +36,8 @@ printf("..:: Testing " #name " ::..\n"); #define TEST1(val) _fail += (val)==0 #define TEST0(val) _fail += (val)!=0 +#define ASSERT_EQUAL(a,b) if((a)!=(b)) { printf("Assertion failed: " #a " != " #b " at line %d\n", __LINE__); _fail++; } +#define ASSERT_NOT_NULL(ptr) if((ptr)==NULL) { printf("Assertion failed: " #ptr " is NULL at line %d\n", __LINE__); _fail++; } #define END_TESTING return _fail; diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index 55c7b71..a699c2f 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -35,8 +35,7 @@ Assembly::Assembly(uLib::Assembly *content) m_ChildContext(nullptr), m_BBoxActor(nullptr), m_VtkAsm(nullptr), - m_InUpdate(false), - m_BlockUpdate(false) { + m_InUpdate(false) { this->InstallPipe(); if (m_Content) { Object::connect(m_Content, &uLib::Assembly::Updated, @@ -54,6 +53,7 @@ Assembly::~Assembly() { void Assembly::InstallPipe() { // 1. Create the VTK library assembly that groups everything m_VtkAsm = ::vtkAssembly::New(); + m_VtkAsm->PickableOff(); this->SetProp(m_VtkAsm); // 2. Create the bounding-box wireframe actor @@ -78,10 +78,10 @@ void Assembly::InstallPipe() { // 3. Build a child-objects context (auto-creates puppets for each child) if (m_Content) { m_ChildContext = new vtkObjectsContext(m_Content); - // The vtkObjectsContext's own prop is already a ::vtkAssembly; - // nest it inside ours so everything moves together. - if (auto *childProp = vtkProp3D::SafeDownCast(m_ChildContext->GetProp())) + // Link the children context's assembly into our group assembly + if (auto* childProp = vtkProp3D::SafeDownCast(m_ChildContext->GetProp())) { m_VtkAsm->AddPart(childProp); + } } // 4. Apply initial transform @@ -93,7 +93,6 @@ void Assembly::InstallPipe() { void Assembly::contentUpdate() { if (m_InUpdate) return; m_InUpdate = true; - m_BlockUpdate = false; this->UpdateTransform(); this->UpdateBoundingBox(); if (m_ChildContext) diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index 17defa9..5ad2960 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -66,7 +66,6 @@ private: vtkActor *m_BBoxActor; ::vtkAssembly *m_VtkAsm; // VTK library assembly — NOT this class bool m_InUpdate; // re-entrancy guard - bool m_BlockUpdate; // block feedback from contentUpdate→Update }; } // namespace Vtk diff --git a/src/Vtk/Math/vtkContainerBox.h b/src/Vtk/Math/vtkContainerBox.h index 6fe0873..3dc2d68 100644 --- a/src/Vtk/Math/vtkContainerBox.h +++ b/src/Vtk/Math/vtkContainerBox.h @@ -29,7 +29,6 @@ #include "Math/ContainerBox.h" #include "uLibVtkInterface.h" #include "vtkPolydata.h" -#include class vtkActor; diff --git a/src/Vtk/testing/PuppetParentingTest.cpp b/src/Vtk/testing/PuppetParentingTest.cpp index b9136df..ca84e7c 100644 --- a/src/Vtk/testing/PuppetParentingTest.cpp +++ b/src/Vtk/testing/PuppetParentingTest.cpp @@ -64,7 +64,7 @@ int main() { // 3. Move the parent and verify the child follows assembly->SetPosition(Vector3f(100, 0, 0)); - assembly->Update(); + assembly->Updated(); // In VTK assemblies, the child's absolute matrix should reflect the parent's transform vtkProp3D* box1Prop = vtkProp3D::SafeDownCast(box1Puppet->GetProp()); diff --git a/src/Vtk/testing/testing-prototype.h b/src/Vtk/testing/testing-prototype.h index c801261..b3eb885 100644 --- a/src/Vtk/testing/testing-prototype.h +++ b/src/Vtk/testing/testing-prototype.h @@ -36,6 +36,8 @@ printf("..:: Testing " #name " ::..\n"); #define TEST1(val) _fail += (val)==0 #define TEST0(val) _fail += (val)!=0 +#define ASSERT_EQUAL(a,b) if((a)!=(b)) { printf("Assertion failed: " #a " != " #b " at line %d\n", __LINE__); _fail++; } +#define ASSERT_NOT_NULL(ptr) if((ptr)==NULL) { printf("Assertion failed: " #ptr " is NULL at line %d\n", __LINE__); _fail++; } #define END_TESTING return _fail; diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index b664240..cbf85a7 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -80,7 +80,8 @@ namespace Vtk { class PuppetData { public: - PuppetData() : + PuppetData(Puppet* owner) : + m_Puppet(owner), m_Renderers(vtkSmartPointer::New()), m_Assembly(vtkSmartPointer::New()), m_ShowBoundingBox(false), @@ -99,9 +100,10 @@ public: // No manual Delete needed for smart pointers } + Puppet *m_Puppet; // members // vtkSmartPointer m_Renderers; - vtkSmartPointer m_Assembly; + vtkSmartPointer m_Assembly; vtkSmartPointer m_OutlineSource; vtkSmartPointer m_OutlineActor; @@ -118,6 +120,8 @@ public: bool m_Selected; bool m_Visibility; bool m_Dragable; + + // TRS m_Transform; void ApplyAppearance(vtkProp *p) { @@ -157,20 +161,49 @@ public: void ApplyTransform(vtkProp3D* p3d) { if (p3d) { - p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); - - // Convert Model Radians to VTK Degrees - p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, - m_Transform.rotation.y() / CLHEP::degree, - m_Transform.rotation.z() / CLHEP::degree); - - p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); - p3d->SetUserMatrix(nullptr); + if (auto* content = dynamic_cast(m_Puppet->GetContent())) { + vtkNew m; + Matrix4fToVtk(content->GetWorldMatrix(), m); + p3d->SetUserMatrix(m); + } else { + p3d->SetUserMatrix(nullptr); + p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); + + // Convert Model Radians to VTK Degrees + p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, + m_Transform.rotation.y() / CLHEP::degree, + m_Transform.rotation.z() / CLHEP::degree); + + p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); + } } } void UpdateHighlight() { if (m_Selected) { + // Find first polydata in assembly to highlight + vtkPolyData* polydata = nullptr; + vtkPropCollection *parts = m_Assembly->GetParts(); + parts->InitTraversal(); + for (int i = 0; i < parts->GetNumberOfItems(); ++i) { + vtkActor *actor = vtkActor::SafeDownCast(parts->GetNextProp()); + if (actor && actor->GetMapper()) { + polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput()); + if (polydata) break; + } + } + + if (!polydata) { + if (m_HighlightActor) { + m_Renderers->InitTraversal(); + for (int i = 0; i < m_Renderers->GetNumberOfItems(); ++i) { + m_Renderers->GetNextItem()->RemoveActor(m_HighlightActor); + } + m_HighlightActor = nullptr; + } + return; + } + if (!m_HighlightActor) { vtkSmartPointer edges = vtkSmartPointer::New(); edges->BoundaryEdgesOn(); @@ -178,17 +211,7 @@ public: edges->SetFeatureAngle(30); edges->NonManifoldEdgesOn(); edges->ManifoldEdgesOff(); - - // Find first polydata in assembly to highlight - vtkPropCollection *parts = m_Assembly->GetParts(); - parts->InitTraversal(); - for (int i = 0; i < parts->GetNumberOfItems(); ++i) { - vtkActor *actor = vtkActor::SafeDownCast(parts->GetNextProp()); - if (actor && actor->GetMapper() && actor->GetMapper()->GetDataSetInput()) { - edges->SetInputData(vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput())); - break; - } - } + edges->SetInputData(polydata); m_HighlightActor = vtkSmartPointer::New(); vtkSmartPointer mapper = vtkSmartPointer::New(); @@ -197,6 +220,13 @@ public: m_HighlightActor->GetProperty()->SetColor(1.0, 0.5, 0.0); // Orange m_HighlightActor->GetProperty()->SetLineWidth(2.0); m_HighlightActor->GetProperty()->SetLighting(0); + } else { + // Update input data (safe even if same) + if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { + if (auto* edges = vtkFeatureEdges::SafeDownCast(mapper->GetInputAlgorithm())) { + edges->SetInputData(polydata); + } + } } // Update highlight matrix from the root prop @@ -208,7 +238,6 @@ public: } if (root) { - // Now that we use internal TRS, the prop's total matrix is GetMatrix() m_HighlightActor->SetUserMatrix(root->GetMatrix()); } @@ -242,7 +271,7 @@ public: -Puppet::Puppet() : Object(), pd(new PuppetData) { +Puppet::Puppet() : Object(), pd(new PuppetData(this)) { ULIB_ACTIVATE_DISPLAY_PROPERTIES; for (auto* p : this->GetDisplayProperties()) { uLib::Object::connect(p, &uLib::PropertyBase::Updated, this, &Puppet::Update); @@ -576,36 +605,27 @@ void Puppet::SyncFromVtk() if (auto* p3d = vtkProp3D::SafeDownCast(root)) { // Handle content synchronization if it's an AffineTransform if (auto* content = dynamic_cast(GetContent())) { - double pos[3], ori[3], scale[3]; - p3d->GetPosition(pos); - p3d->GetOrientation(ori); - p3d->GetScale(scale); + // Apply current VTK world transform to model. + // When 'sinking the chain', p3d's matrix represents the world matrix + // because it has no parent in VTK (nesting was removed). + vtkNew m; + p3d->GetMatrix(m); + content->SetWorldMatrix(VtkToMatrix4f(m)); - // Convert VTK Degrees to Model Radians - content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); - content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); - content->SetScale(Vector3f(scale[0], scale[1], scale[2])); - - // Re-sync internal puppet properties from the now-updated content + // Sync local TRS from the newly updated model pd->m_Transform = *content; } else { - // Update internal puppet TRS directly from VTK components + // Fallback for simple props double pos[3], ori[3], scale[3]; p3d->GetPosition(pos); p3d->GetOrientation(ori); p3d->GetScale(scale); pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]); - // Convert VTK Degrees to internal Radians pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree; - pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]); + pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]); } - // Notify puppet properties updated - if (auto* propPos = this->GetProperty("Position")) propPos->Updated(); - if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated(); - if (auto* propScale = this->GetProperty("Scale")) propScale->Updated(); - this->Object::Updated(); } } @@ -614,6 +634,11 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) { } + +// ------------------------------------------------------ // +// SERIALIZE DISPLAY PROPERTIES + + struct TransformProxy { PuppetData* pd; template @@ -643,13 +668,6 @@ void Puppet::serialize_display(Archive::display_properties_archive & ar, const u ar & boost::serialization::make_nvp("Transform", transform); } -void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { } -void Puppet::serialize(Archive::xml_iarchive & ar, const unsigned int v) { } -void Puppet::serialize(Archive::text_oarchive & ar, const unsigned int v) { } -void Puppet::serialize(Archive::text_iarchive & ar, const unsigned int v) { } -void Puppet::serialize(Archive::hrt_oarchive & ar, const unsigned int v) { } -void Puppet::serialize(Archive::hrt_iarchive & ar, const unsigned int v) { } -void Puppet::serialize(Archive::log_archive & ar, const unsigned int v) { } } // namespace Vtk } // namespace uLib diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index b0577f0..3b0f806 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -54,7 +54,9 @@ namespace uLib { namespace Vtk { class Puppet : public uLib::Object { + uLibTypeMacro(Puppet, uLib::Object) + public: Puppet(); virtual ~Puppet(); @@ -99,14 +101,6 @@ public: vtkRendererCollection *GetRenderers() const; - virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) override; - virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) override; - virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) override; - virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) override; - virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) override; - virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) override; - virtual void serialize(Archive::log_archive & ar, const unsigned int version) override; - const std::vector& GetDisplayProperties() const { return m_DisplayProperties; } void RegisterDisplayProperty(uLib::PropertyBase* prop) { m_DisplayProperties.push_back(prop); } From d4fd2d391499b618734f166edbba8c082d5af3a2 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Tue, 31 Mar 2026 16:04:03 +0000 Subject: [PATCH 10/24] refactor: update transformation system, improve template readability, and reorganize VTK assembly management --- CMakePresets.json | 6 + docs/transformation_system.md | 73 ----- docs/update_properties.md | 21 ++ src/Core/Debug.h | 71 ++++- src/Core/Signal.h | 2 + src/Math/Assembly.h | 7 +- src/Math/ContainerBox.h | 11 +- src/Math/Cylinder.h | 5 +- src/Math/Transform.h | 5 + src/Math/VoxImageFilter.hpp | 51 ++-- src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx | 6 +- src/Vtk/HEP/Detectors/vtkDetectorChamber.h | 2 +- src/Vtk/HEP/Detectors/vtkMuonScatter.cxx | 13 +- src/Vtk/HEP/Detectors/vtkMuonScatter.h | 2 + .../vtkVoxRaytracerRepresentation.cpp | 10 +- .../vtkVoxRaytracerRepresentation.h | 1 + src/Vtk/Math/vtkAssembly.cpp | 55 ++-- src/Vtk/Math/vtkAssembly.h | 6 +- src/Vtk/Math/vtkContainerBox.cpp | 92 +++--- src/Vtk/Math/vtkContainerBox.h | 10 +- src/Vtk/Math/vtkCylinder.cpp | 71 +++-- src/Vtk/Math/vtkCylinder.h | 5 +- src/Vtk/Math/vtkVoxImage.cpp | 7 +- src/Vtk/Math/vtkVoxImage.h | 2 + src/Vtk/uLibVtkInterface.cxx | 281 ++++++++---------- src/Vtk/uLibVtkInterface.h | 223 ++++++++------ src/Vtk/vtkHandlerWidget.cpp | 18 +- src/Vtk/vtkObjectsContext.cpp | 6 + src/Vtk/vtkObjectsContext.h | 3 + src/Vtk/vtkViewport.cpp | 4 +- 30 files changed, 568 insertions(+), 501 deletions(-) delete mode 100644 docs/transformation_system.md create mode 100644 docs/update_properties.md diff --git a/CMakePresets.json b/CMakePresets.json index 99c234f..64f2dd9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -11,6 +11,12 @@ "CMAKE_BUILD_TYPE": "Debug", "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" } + }, + { + "name": "mutom", + "description": "", + "displayName": "", + "inherits": [] } ] } \ No newline at end of file diff --git a/docs/transformation_system.md b/docs/transformation_system.md deleted file mode 100644 index a5cc8f8..0000000 --- a/docs/transformation_system.md +++ /dev/null @@ -1,73 +0,0 @@ -# Transformation Flow and Synchronization System - -This document describes how transformations are applied and synchronized between the interactive 3D viewport, the visualization puppets, and the underlying mathematical models within the `uLib` framework. - -## Architecture Overview - -The system follows a Model-View-Controller (MVC) like pattern where: -- **Model**: `uLib::AffineTransform` (or derived classes like `ContainerBox`). -- **View/Puppet**: `uLib::Vtk::Puppet` (and specialized derivations like `Vtk::Assembly`). -- **Controller/Interaction**: `vtkHandlerWidget` (the transformation gizmo). - ---- - -## 1. Interaction Flow (Gizmo -> Model) - -When a user interacts with the `vtkHandlerWidget` (dragging arrows, rings, or cubes), the following chain of events occurs: - -```mermaid -sequenceDiagram - participant User - participant HW as vtkHandlerWidget - participant VP as vtkViewport - participant P as vtkPuppet - participant M as uLib Model - - User->>HW: Drag handle (MouseMove) - HW->>HW: Calculate Delta Matrix (op) - HW->>HW: Total = StartState * op - HW->>HW: Decompose Total into P, O, S - HW->>P: SetPosition, SetOrientation, SetScale - HW-->>VP: Invoke InteractionEvent - VP->>P: SyncFromVtk() - P->>P: Get local matrix from VTK Prop - P->>M: SetMatrix(matrix) - M-->>M: Update local properties (P, O, S) - M-->>P: Emit Updated signal - P->>P: Puppet::Update() - P->>P: (Redundant sanity write to Prop) -``` - -### Key Principles: -- **Single Source of Truth**: The `uLib::AffineTransform` is the owner of the transformation state. -- **Internal TRS vs UserMatrix**: We apply transformations directly to VTK's internal `Position`, `Orientation`, and `Scale` properties. This ensures the data is "visible" to VTK actors and simplifies decomposition. -- **Cumulative Bias Avoidance**: The `HandlerWidget` calculates transformations relative to the state at the start of the click, preventing numerical drift during a single drag operation. - ---- - -## 2. Synchronization Loop Resolution - -To prevent infinite loops and "double-transformation" artifacts (especially in assemblies), the following protections are in place: - -1. **Hierarchy Isolation**: The `Puppet` base class distinguishes between the **Root Property** (which receives the puppet's master transformation) and **Sub-Parts** (which only receive appearance updates like color/visibility). This prevents parts from inheriting the same displacement twice. -2. **Re-entrancy Guards**: Puppets use an `m_InUpdate` flag to prevent a feedback loop where `SyncFromVtk` triggers a Model Update, which then re-triggers the Puppet Update. -3. **Signal Blocking**: In specialized cases (like `vtkAssembly`), `m_BlockUpdate` is used to prevent the model-to-puppet push during a puppet-to-model sync. - ---- - -## 3. Undo System (Ctrl-Z) - -### Current Implementation (Delta Chain) -Currently, the system maintains a `m_TransformChain` of delta matrices. -- **Record**: After every drag, a delta matrix ($M_{delta} = M_{end} \cdot M_{start}^{-1}$) is appended to the chain. -- **Undo**: The last delta is removed, and the prop is reconstructed by reapplying the remaining chain from a `BaseMatrix`. - -### Planned Improvement (TRS Snapshots) -We are migrating to a `uLib::TRS` snapshot system for Undo. -- **Record**: At the start of a drag, the current `TRS` state of the object is pushed to the `m_UndoStack`. -- **Undo**: The top `TRS` is popped and applied directly to the model. - -This approach is more robust because: -- It eliminates matrix multiplication error accumulation. -- It bypasses rotation convention/order issues (Gimbal lock in deltas). -- It returns the object to exactly its previous property values. diff --git a/docs/update_properties.md b/docs/update_properties.md new file mode 100644 index 0000000..15c1790 --- /dev/null +++ b/docs/update_properties.md @@ -0,0 +1,21 @@ + +# Properties and the vtk-gui representation + +This is the rationale behind the connection between TRS properties and Puppet Transformation. + +The properties from model get propoagated via Object signalling system (the Update signal) to the vtkRepresentation and to the Qt widgets so that the overall transformation of the model reflects into a modification of its representation in vtk and in the gui. + +In addition the properties need to be adjusted also from vtk, for example if user uses handlerwidget to change the transformation this is eventually applied to Puppet and Puppet should propagate the transformation change to the vtk representation object (for instance vtkContainerBox) and the latter eventually propagates the change into the model. + +the Puppet or the vtk representation wrapper ( vtkContainerBox for instance is the wrapper od ContainerBox ) should not directly show the transformation of the handlerwidget but it should show the transformation of the model once applied so we are always seeing the actual aspect of the model reflected to the vtk representation and not the other way around. + +So in syntesis the model is the master and the vtk representation and the gui are the slaves of any modification, but the vtkHandlerWidget is able to apply a transform that should be applied to the model and then the model should propagate the transformation change to the vtk representation and to the gui. + +## The Puppet + +The puppet is the proxy of the spatial placement of objects in the scene. Puppets should have an internal ContainerBox that is shown in the scene around the content to be able to pick Puppet from vtkViewport using the handler widget. The HandlerWidget moves the Puppet ContainerBox (the red Highlight element whe selected) to reflect the handler current transformation, but the transformation is propagated to the derived Puppet classes like vtkContainerBox. + +The vtkHandlerWidget should handle the transformation of the puppet internal ContainerBox. The changes of the ContainerBox will be propagated to the derived classes and eventually to the model. + + + diff --git a/src/Core/Debug.h b/src/Core/Debug.h index 37e68aa..4a5614a 100644 --- a/src/Core/Debug.h +++ b/src/Core/Debug.h @@ -38,6 +38,76 @@ #include + +/** + * @file Debug.h + * @brief Unified Debugging and Monitoring System for uLib. + * + * # Debug System Documentation + * + * The `Debug` system provides a flexible, adapter-based mechanism for monitoring + * and outputting internal variables and states without hardcoding output logic + * into core classes. + * + * ## Architecture + * + * The system follows an adapter pattern: + * + * - **`DebugAdapterInterface`**: The base interface for all output targets. + * Subclasses define how primitive types and strings are handled (e.g., printing + * to `std::cout`, writing to a log file, or updating a real-time UI widget). + * + * - **`Debug` Class**: The central registry. It stores: + * 1. A list of "Adapters" (`DebugAdapterInterface`). + * 2. A list of "Items" to monitor. Each item is a reference to a variable + * associated with a name. + * + * - **Type Safety**: Although variables are stored using `boost::any` (type erasure), + * the system automatically preserves the original type information via + * internal template adapters (`AnyCastAdapter`), ensuring that the correct + * overload of the adapter interface is called. + * + * ## Core Components + * + * | Component | Description | + * | :--- | :--- | + * | `Debug` | The main controller used to add adapters and register variables. | + * | `DebugAdapterInterface` | Virtual base for creating new output methods. | + * | `DebugAdapterText` | A simple built-in adapter for `std::ostream` output. | + * + * ## How to Use + * + * ### 1. Initialize the Debug Object + * ```cpp + * uLib::Debug dbg; + * ``` + * + * ### 2. Add an Adapter + * ```cpp + * uLib::DebugAdapterText console(std::cout); + * dbg.AddAdapter(console); + * ``` + * + * ### 3. Register Variables to Monitor + * Use the `operator()` to bind a variable by reference. + * ```cpp + * int frameCount = 0; + * std::string state = "Initializing"; + * dbg("Frames", frameCount); + * dbg("Status", state); + * ``` + * + * ### 4. Update + * Call `Update()` periodically (e.g., once per frame) to push the current + * values of all registered variables to all connected adapters. + * ```cpp + * while(running) { + * frameCount++; + * dbg.Update(); // This triggers the output + * } + * ``` + */ + namespace uLib { @@ -107,7 +177,6 @@ public: - class Debug { typedef detail::DebugAdapterInterface AdapterInterface; typedef SmartPointer Adapter; diff --git a/src/Core/Signal.h b/src/Core/Signal.h index f8307e4..edace95 100644 --- a/src/Core/Signal.h +++ b/src/Core/Signal.h @@ -95,6 +95,8 @@ namespace uLib { typedef boost::signals2::signal_base SignalBase; typedef boost::signals2::connection Connection; +typedef boost::signals2::shared_connection_block ConnectionBlock; + template struct Signal { typedef boost::signals2::signal type; diff --git a/src/Math/Assembly.h b/src/Math/Assembly.h index 4bca2d6..64246d4 100644 --- a/src/Math/Assembly.h +++ b/src/Math/Assembly.h @@ -96,10 +96,13 @@ public: signals: virtual void Updated() override { - if (m_InUpdated) return; // break signal recursion + if (m_InUpdated) return; m_InUpdated = true; + + // Synchronize TRS part + this->TRS::Updated(); + this->ComputeBoundingBox(); - ULIB_SIGNAL_EMIT(Object::Updated); m_InUpdated = false; } diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index eb782fa..ec83c1c 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -33,6 +33,8 @@ #include "Math/Transform.h" #include +#include + namespace uLib { /** @@ -99,9 +101,9 @@ public: */ template void serialize(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); ar & HRP(Size); ar & HRP(Origin); + ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); } /** @@ -216,8 +218,13 @@ signals: /** Signal emitted when properties change */ virtual void Updated() override { + // 1. Synchronize local box part (Size/Origin -> m_LocalT) this->Sync(); - ULIB_SIGNAL_EMIT(Object::Updated); + + // 2. Synchronize TRS part (position/rotation/scaling -> m_T) and emit signal + this->TRS::Updated(); + + // std::cout << "ContainerBox::Updated()" << std::endl; } private: diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index aa1e7c7..688149c 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -177,8 +177,11 @@ public: signals: /** Signal emitted when properties change */ virtual void Updated() override { + // 1. Synchronize local cylinder part (Radius/Height/Axis -> m_LocalT) this->Sync(); - ULIB_SIGNAL_EMIT(Object::Updated); + + // 2. Synchronize TRS part (position/rotation/scaling -> m_T) and emit signal + this->TRS::Updated(); } private: diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 9f66e43..6ae1ada 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -246,6 +246,11 @@ public: this->GetTransform() = GetAffineMatrix(); } + void Updated() override { + this->SyncMatrix(); + this->AffineTransform::Updated(); + } + template void serialize(ArchiveT & ar, const unsigned int version) { ar & HRPU(position, "mm"); diff --git a/src/Math/VoxImageFilter.hpp b/src/Math/VoxImageFilter.hpp index 9b3f243..032e958 100644 --- a/src/Math/VoxImageFilter.hpp +++ b/src/Math/VoxImageFilter.hpp @@ -98,15 +98,14 @@ template void Kernel::PrintSelf(std::ostream &o) const { //////////////////////////////////////////////////////////////////////////////// -#define _TPL_ template -#define _TPLT_ VoxelT, AlgorithmT -_TPL_ -VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size) + +template +VoxImageFilter::VoxImageFilter(const Vector3i &size) : m_KernelData(size), t_Algoritm(static_cast(this)) {} -_TPL_ -void VoxImageFilter<_TPLT_>::Run() { +template +void VoxImageFilter::Run() { VoxImage buffer = *m_Image; #pragma omp parallel for for (int i = 0; i < m_Image->Data().size(); ++i) @@ -114,8 +113,8 @@ void VoxImageFilter<_TPLT_>::Run() { #pragma omp barrier } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelOffset() { +template +void VoxImageFilter::SetKernelOffset() { Vector3i id(0, 0, 0); for (int z = 0; z < m_KernelData.GetDims()(2); ++z) { for (int x = 0; x < m_KernelData.GetDims()(0); ++x) { @@ -127,8 +126,8 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() { } } -_TPL_ -float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) { +template +float VoxImageFilter::Distance2(const Vector3i &v) { Vector3i tmp = v; const Vector3i &dim = this->m_KernelData.GetDims(); Vector3i center = dim / 2; @@ -140,8 +139,8 @@ float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) { 0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2))); } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelNumericXZY( +template +void VoxImageFilter::SetKernelNumericXZY( const std::vector &numeric) { // set data order // StructuredData::Order order = m_KernelData.GetDataOrder(); @@ -159,8 +158,8 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY( // m_KernelData.SetDataOrder(order); } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) { +template +void VoxImageFilter::SetKernelSpherical(float (*shape)(float)) { Vector3i id; for (int y = 0; y < m_KernelData.GetDims()(1); ++y) { for (int z = 0; z < m_KernelData.GetDims()(2); ++z) { @@ -172,8 +171,8 @@ void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) { } } -_TPL_ template -void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) { +template template +void VoxImageFilter::SetKernelSpherical(ShapeT shape) { Interface::IsA(); Vector3i id; for (int y = 0; y < m_KernelData.GetDims()(1); ++y) { @@ -186,8 +185,8 @@ void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) { } } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelWeightFunction( +template +void VoxImageFilter::SetKernelWeightFunction( float (*shape)(const Vector3f &)) { const Vector3i &dim = m_KernelData.GetDims(); Vector3i id; @@ -207,8 +206,8 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction( } } -_TPL_ template -void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) { +template template +void VoxImageFilter::SetKernelWeightFunction(ShapeT shape) { Interface::IsA(); const Vector3i &dim = m_KernelData.GetDims(); Vector3i id; @@ -228,14 +227,14 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) { } } -_TPL_ -void VoxImageFilter<_TPLT_>::SetImage(Abstract::VoxImage *image) { +template +void VoxImageFilter::SetImage(Abstract::VoxImage *image) { this->m_Image = reinterpret_cast *>(image); this->SetKernelOffset(); } -_TPL_ -float VoxImageFilter<_TPLT_>::Convolve(const VoxImage &buffer, +template +float VoxImageFilter::Convolve(const VoxImage &buffer, int index) { const DataAllocator &vbuf = buffer.ConstData(); const DataAllocator &vker = m_KernelData.ConstData(); @@ -252,8 +251,8 @@ float VoxImageFilter<_TPLT_>::Convolve(const VoxImage &buffer, return conv / ksum; } -#undef _TPLT_ -#undef _TPL_ + + } // namespace uLib diff --git a/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx b/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx index af033a6..6207602 100644 --- a/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx +++ b/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx @@ -64,7 +64,7 @@ vtkDetectorChamber::vtkDetectorChamber(DetectorChamber *content) this->SetProp(m_PlaneActor); - this->contentUpdate(); + this->Update(); } vtkDetectorChamber::~vtkDetectorChamber() { @@ -76,8 +76,8 @@ DetectorChamber *vtkDetectorChamber::GetContent() { return static_cast(m_Content); } -void vtkDetectorChamber::contentUpdate() { - this->BaseClass::contentUpdate(); +void vtkDetectorChamber::Update() { + this->BaseClass::Update(); if (!m_Content) return; DetectorChamber *c = this->GetContent(); diff --git a/src/Vtk/HEP/Detectors/vtkDetectorChamber.h b/src/Vtk/HEP/Detectors/vtkDetectorChamber.h index 0dc53ee..70ec31c 100644 --- a/src/Vtk/HEP/Detectors/vtkDetectorChamber.h +++ b/src/Vtk/HEP/Detectors/vtkDetectorChamber.h @@ -56,7 +56,7 @@ public: Content *GetContent(); - virtual void contentUpdate() override; + virtual void Update() override; protected: vtkActor *m_PlaneActor; diff --git a/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx b/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx index 1d1c2bd..1272674 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx +++ b/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx @@ -40,20 +40,21 @@ namespace Vtk { vtkMuonScatter::vtkMuonScatter(MuonScatter &content) : m_Content(&content), m_LineIn(vtkLineSource::New()), m_LineOut(vtkLineSource::New()), m_PolyData(vtkPolyData::New()), - m_SpherePoca(NULL) { + m_SpherePoca(NULL), m_Asm(vtkAssembly::New()) { InstallPipe(); } vtkMuonScatter::vtkMuonScatter(const MuonScatter &content) : m_Content(const_cast(&content)), m_LineIn(vtkLineSource::New()), m_LineOut(vtkLineSource::New()), - m_PolyData(vtkPolyData::New()), m_SpherePoca(NULL) { + m_PolyData(vtkPolyData::New()), m_SpherePoca(NULL), m_Asm(vtkAssembly::New()) { InstallPipe(); } vtkMuonScatter::~vtkMuonScatter() { m_LineIn->Delete(); m_LineOut->Delete(); + m_Asm->Delete(); if (m_SpherePoca) m_SpherePoca->Delete(); } @@ -87,13 +88,15 @@ void vtkMuonScatter::InstallPipe() { mapper->SetInputConnection(m_LineIn->GetOutputPort()); vtkSmartPointer actor = vtkSmartPointer::New(); actor->SetMapper(mapper); - this->SetProp(actor); + m_Asm->AddPart(actor); mapper = vtkSmartPointer::New(); mapper->SetInputConnection(m_LineOut->GetOutputPort()); actor = vtkSmartPointer::New(); actor->SetMapper(mapper); - this->SetProp(actor); + m_Asm->AddPart(actor); + + this->SetProp(m_Asm); } vtkPolyData *vtkMuonScatter::GetPolyData() const { @@ -123,7 +126,7 @@ void vtkMuonScatter::AddPocaPoint(HPoint3f poca) { mapper->SetInputConnection(m_SpherePoca->GetOutputPort()); vtkSmartPointer actor = vtkSmartPointer::New(); actor->SetMapper(mapper); - this->SetProp(actor); + m_Asm->AddPart(actor); } HPoint3f vtkMuonScatter::GetPocaPoint() { diff --git a/src/Vtk/HEP/Detectors/vtkMuonScatter.h b/src/Vtk/HEP/Detectors/vtkMuonScatter.h index 6eda4ba..1381f3b 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonScatter.h +++ b/src/Vtk/HEP/Detectors/vtkMuonScatter.h @@ -27,6 +27,7 @@ #define VTKMUONSCATTER_H #include +#include #include #include #include @@ -85,6 +86,7 @@ private: vtkLineSource *m_LineOut; vtkSphereSource *m_SpherePoca; vtkPolyData *m_PolyData; + vtkAssembly *m_Asm; }; } // namespace Vtk diff --git a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp index 15f2c07..3d3c910 100644 --- a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp +++ b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp @@ -50,6 +50,7 @@ vtkVoxRaytracerRepresentation::vtkVoxRaytracerRepresentation(Content &content) m_RayRepresentation(vtkAppendPolyData::New()), m_RayRepresentationActor(vtkActor::New()), m_Transform(vtkTransform::New()), + m_Asm(vtkAssembly::New()), m_HasMuon(false), m_HasPoca(false) { default_radius = content.GetImage()->GetSpacing()(0) / 4; m_Sphere1->SetRadius(default_radius); @@ -313,20 +314,19 @@ void vtkVoxRaytracerRepresentation::InstallPipe() { vtkSmartPointer mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(append->GetOutputPort()); vtkSmartPointer actor = vtkActor::New(); actor->SetMapper(mapper); actor->GetProperty()->SetColor(0.6, 0.6, 1); - this->SetProp(actor); + m_Asm->AddPart(actor); mapper = vtkSmartPointer::New(); mapper->SetInputConnection(m_RayLine->GetOutputPort()); m_RayLineActor->SetMapper(mapper); m_RayLineActor->GetProperty()->SetColor(1, 0, 0); - this->SetProp(m_RayLineActor); + m_Asm->AddPart(m_RayLineActor); vtkSmartPointer polyfilter = vtkSmartPointer::New(); @@ -343,7 +343,9 @@ void vtkVoxRaytracerRepresentation::InstallPipe() { vra->GetProperty()->SetEdgeVisibility(true); vra->GetProperty()->SetColor(0.5, 0.5, 0.5); - this->SetProp(vra); + m_Asm->AddPart(vra); + + this->SetProp(m_Asm); } } // namespace Vtk diff --git a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h index 2e7bab9..67b1fa5 100644 --- a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h +++ b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h @@ -107,6 +107,7 @@ private: bool m_HasPoca; Scalarf default_radius; + vtkSmartPointer m_Asm; vtkAppendPolyData *m_RayLine; vtkActor *m_RayLineActor; vtkActor *m_RayRepresentationActor; diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index a699c2f..523ac91 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -39,7 +39,7 @@ Assembly::Assembly(uLib::Assembly *content) this->InstallPipe(); if (m_Content) { Object::connect(m_Content, &uLib::Assembly::Updated, - this, &Assembly::contentUpdate); + this, &Assembly::Update); } } @@ -84,29 +84,27 @@ void Assembly::InstallPipe() { } } - // 4. Apply initial transform - this->UpdateTransform(); - this->UpdateBoundingBox(); -} - -// ------------------------------------------------------------------ // -void Assembly::contentUpdate() { - if (m_InUpdate) return; - m_InUpdate = true; - this->UpdateTransform(); - this->UpdateBoundingBox(); - if (m_ChildContext) - m_ChildContext->Update(); - - Puppet::Update(); - m_InUpdate = false; + // 4. Force initial visual sync + this->Update(); } // ------------------------------------------------------------------ // void Assembly::Update() { if (m_InUpdate) return; m_InUpdate = true; - this->contentUpdate(); + + if (m_Content && m_VtkAsm) { + // Apply world matrix from the assembly content + vtkNew m; + Matrix4fToVtk(m_Content->GetMatrix(), m); + m_VtkAsm->SetUserMatrix(m); + m_VtkAsm->Modified(); + } + + this->Puppet::Update(); + this->UpdateBoundingBox(); + if (m_ChildContext) + m_ChildContext->Update(); m_InUpdate = false; } @@ -116,14 +114,11 @@ void Assembly::SyncFromVtk() { m_InUpdate = true; - double pos[3], ori[3], scale[3]; - m_VtkAsm->GetPosition(pos); - m_VtkAsm->GetOrientation(ori); - m_VtkAsm->GetScale(scale); - - m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); - m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); - m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + // VTK -> Model: Update world matrix (accounting for model parents) + if (vtkProp3D* proxy = this->GetProxyProp()) { + m_Content->SetWorldMatrix(VtkToMatrix4f(proxy->GetUserMatrix())); + m_Content->FromMatrix(m_Content->GetMatrix()); + } this->UpdateBoundingBox(); if (m_ChildContext) @@ -134,14 +129,6 @@ void Assembly::SyncFromVtk() { m_InUpdate = false; } -// ------------------------------------------------------------------ // -void Assembly::UpdateTransform() { - if (!m_Content || !m_VtkAsm) return; - - this->ApplyTransform(m_VtkAsm); - m_VtkAsm->Modified(); -} - // ------------------------------------------------------------------ // void Assembly::UpdateBoundingBox() { if (!m_Content || !m_BBoxActor) return; diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index 5ad2960..f63c8d7 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -50,14 +50,14 @@ public: virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } virtual uLib::ObjectsContext* GetChildren() override { return (uLib::ObjectsContext*)m_Content; } - /** @brief Called when the model signals an update (model→VTK push). */ - void contentUpdate(); + /** + * @brief Returns the puppet managing child objects. + */ /** @brief Returns the puppet managing child objects. */ vtkObjectsContext *GetChildrenContext() const; private: - void UpdateTransform(); void UpdateBoundingBox(); void InstallPipe(); diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index f4ffa56..9c7b5aa 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -48,18 +48,28 @@ namespace uLib { namespace Vtk { struct ContainerBoxData { - vtkSmartPointer m_Cube; - vtkSmartPointer m_Axes; + vtkSmartPointer m_Cube; + vtkSmartPointer m_Axes; + vtkSmartPointer m_VtkAsm; + vtkSmartPointer m_Affine; + uLib::Connection m_UpdateSignal; - ContainerBoxData() : m_Cube(vtkSmartPointer::New()), m_Axes(vtkSmartPointer::New()) {} + ContainerBoxData() : m_Cube(vtkSmartPointer::New()), + m_Axes(vtkSmartPointer::New()), + m_VtkAsm(vtkSmartPointer::New()), + m_Affine(vtkSmartPointer::New()) {} ~ContainerBoxData() { } }; + + + + vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content) : d(new ContainerBoxData()), m_Content(content) { this->InstallPipe(); - Object::connect(m_Content, &Content::Updated, this, &vtkContainerBox::contentUpdate); + d->m_UpdateSignal = Object::connect(m_Content, &uLib::Object::Updated, this, &vtkContainerBox::Update); } vtkContainerBox::~vtkContainerBox() { @@ -72,47 +82,50 @@ vtkPolyData *vtkContainerBox::GetPolyData() const { } -void vtkContainerBox::contentUpdate() { +void vtkContainerBox::Update() { RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); - if (!m_Content) - return; + if (!m_Content) return; vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); - if (!root) return; + if (root) { + // Apply local full matrix (TRS * LocalBox) so that nested assemblies work correctly + Matrix4f fullLocal = m_Content->GetMatrix() * m_Content->GetLocalMatrix(); + vtkNew m; + Matrix4fToVtk(fullLocal, m); + root->SetUserMatrix(m); + root->Modified(); + } - d->m_Cube->SetUserMatrix(nullptr); - d->m_Axes->SetUserMatrix(nullptr); - - TRS trs(*m_Content); - this->ApplyTransform(root); - - root->Modified(); - m_BlockUpdate = false; - Puppet::Update(); + // Delegate rest of update (appearance, render, etc) + ConnectionBlock blocker(d->m_UpdateSignal); + this->Puppet::Update(); } -void vtkContainerBox::Update() { - this->contentUpdate(); -} - void vtkContainerBox::SyncFromVtk() { RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); if (!m_Content) return; - - vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); - if (!assembly) return; - double pos[3], ori[3], scale[3]; - assembly->GetPosition(pos); - assembly->GetOrientation(ori); - assembly->GetScale(scale); + vtkProp3D* root = this->GetProxyProp(); + if (!root) return; + + // VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center + vtkMatrix4x4* rootMat = root->GetUserMatrix(); + if (rootMat) { + std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; + rootMat->Print(std::cout); + } - m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); - m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); - m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + Matrix4f vtkWorld = VtkToMatrix4f(rootMat); - m_Content->Updated(); // Notify change + // Synchronize TRS property members from the updated local matrix + m_Content->FromMatrix(vtkWorld); + + std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; + + // Since we modified the model, notify observers, but block the loop back to VTK + // ConnectionBlock blocker(d->m_UpdateSignal); + m_Content->Updated(); } @@ -154,17 +167,16 @@ void vtkContainerBox::InstallPipe() { mapper->SetInputConnection(axes->GetOutputPort()); mapper->Update(); - this->SetProp(d->m_Cube); - this->SetProp(d->m_Axes); + d->m_VtkAsm->AddPart(d->m_Cube); + d->m_VtkAsm->AddPart(d->m_Axes); + this->SetProp(d->m_VtkAsm); - vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); + vtkProp3D* root = d->m_VtkAsm; if (root) { - TRS trs(*c); - root->SetPosition(trs.position.x(), trs.position.y(), trs.position.z()); - root->SetOrientation(trs.rotation.x(), trs.rotation.y(), trs.rotation.z()); - root->SetScale(trs.scaling.x(), trs.scaling.y(), trs.scaling.z()); - root->SetUserMatrix(nullptr); + d->m_Affine = Matrix4fToVtk(m_Content->GetMatrix()); + root->SetUserMatrix(d->m_Affine); } + this->Update(); } } // namespace Vtk diff --git a/src/Vtk/Math/vtkContainerBox.h b/src/Vtk/Math/vtkContainerBox.h index 3dc2d68..b27a9c5 100644 --- a/src/Vtk/Math/vtkContainerBox.h +++ b/src/Vtk/Math/vtkContainerBox.h @@ -46,9 +46,14 @@ public: virtual class vtkPolyData *GetPolyData() const; - virtual void contentUpdate(); - + /** + * @brief Updates the VTK representation from the internal state. + */ virtual void Update() override; + + /** + * @brief Synchronizes the model from the VTK representation (VTK→model). + */ virtual void SyncFromVtk() override; virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } @@ -59,6 +64,7 @@ protected: struct ContainerBoxData *d; Content *m_Content; bool m_BlockUpdate = false; + }; } // namespace Vtk diff --git a/src/Vtk/Math/vtkCylinder.cpp b/src/Vtk/Math/vtkCylinder.cpp index 3e14246..92b2767 100644 --- a/src/Vtk/Math/vtkCylinder.cpp +++ b/src/Vtk/Math/vtkCylinder.cpp @@ -40,7 +40,7 @@ namespace Vtk { vtkCylinder::vtkCylinder(vtkCylinder::Content *content) : m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) { this->InstallPipe(); - Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate); + Object::connect(m_Content, &uLib::Object::Updated, this, &vtkCylinder::Update); } vtkCylinder::~vtkCylinder() { @@ -48,59 +48,56 @@ vtkCylinder::~vtkCylinder() { if (m_VtkAsm) m_VtkAsm->Delete(); } -void vtkCylinder::contentUpdate() { +void vtkCylinder::Update() { if (!m_Content) return; vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); - if (!root) return; + if (root) { + // 1. Placement handled specifically from content (use TRS GetMatrix, not World) + vtkNew m; + Matrix4fToVtk(m_Content->GetMatrix(), m); + root->SetUserMatrix(m); - // 1. Placement handled by base Puppet class via Sync / Update logic - // Update internal pd->m_Transform from content - Puppet::Update(); + // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor + // These are relative to the root assembly + vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform()); + if (alignment) { + alignment->Identity(); + alignment->PostMultiply(); + + // Initial source is centered Y-cylinder (Radial XZ [-1,1], Height Y [-0.5, 0.5]) + // Apply Radius and Height scaling + alignment->Scale(m_Content->GetRadius(), m_Content->GetHeight(), m_Content->GetRadius()); - // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor - // These are relative to the root assembly - vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform()); - if (alignment) { - alignment->Identity(); - alignment->PostMultiply(); - - // Initial source is centered Y-cylinder (Radial XZ [-1,1], Height Y [-0.5, 0.5]) - // Apply Radius and Height scaling - alignment->Scale(m_Content->GetRadius(), m_Content->GetHeight(), m_Content->GetRadius()); + // Apply Axis alignment + int axis = m_Content->GetAxis(); + if (axis == 0) alignment->RotateZ(-90); // Y -> X + else if (axis == 1) ; // Y -> Y (identity) + else if (axis == 2) alignment->RotateX(90); // Y -> Z + } - // Apply Axis alignment - int axis = m_Content->GetAxis(); - if (axis == 0) alignment->RotateZ(-90); // Y -> X - else if (axis == 1) ; // Y -> Y (identity) - else if (axis == 2) alignment->RotateX(90); // Y -> Z + root->Modified(); } - root->Modified(); -} - -void vtkCylinder::Update() { - this->contentUpdate(); + // Use base class sync, which handles appearance and children + this->Puppet::Update(); } void vtkCylinder::SyncFromVtk() { if (!m_Content) return; - vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); + vtkProp3D* assembly = this->GetProxyProp(); if (!assembly) return; - double pos[3], ori[3], scale[3]; - assembly->GetPosition(pos); - assembly->GetOrientation(ori); - assembly->GetScale(scale); + // VTK -> Model: Update TRS properties from VTK matrix via world transform + m_Content->SetWorldMatrix(VtkToMatrix4f(assembly->GetUserMatrix())); - m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); - // Convert VTK degrees to model radians - m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); - m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + // Resync TRS property members (pos/rot/scale) from the newly set local matrix + m_Content->FromMatrix(m_Content->GetMatrix()); - m_Content->Updated(); // Notify change + // Since we modified the model, notify observers, but block the loop back to VTK + m_Content->Updated(); } void vtkCylinder::InstallPipe() { @@ -127,7 +124,7 @@ void vtkCylinder::InstallPipe() { m_VtkAsm->AddPart(m_Actor); - this->contentUpdate(); + this->Update(); } } // namespace Vtk diff --git a/src/Vtk/Math/vtkCylinder.h b/src/Vtk/Math/vtkCylinder.h index 1965293..9d431ee 100644 --- a/src/Vtk/Math/vtkCylinder.h +++ b/src/Vtk/Math/vtkCylinder.h @@ -48,10 +48,7 @@ public: vtkCylinder(Content *content); virtual ~vtkCylinder(); - /** Synchronizes the VTK actor with the uLib model matrix */ - virtual void contentUpdate(); - - /** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */ + /** Synchronizes the VTK actor with the uLib model matrix and vice-versa */ virtual void Update() override; /** Synchronizes the uLib model matrix with the VTK actor specifically for gizmo interactions */ diff --git a/src/Vtk/Math/vtkVoxImage.cpp b/src/Vtk/Math/vtkVoxImage.cpp index da14a8c..a266621 100644 --- a/src/Vtk/Math/vtkVoxImage.cpp +++ b/src/Vtk/Math/vtkVoxImage.cpp @@ -124,6 +124,7 @@ void vtkVoxImage::SetContent() { vtkVoxImage::vtkVoxImage(Content &content) : m_Content(content), m_Actor(vtkVolume::New()), + m_Asm(vtkAssembly::New()), m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()), m_OutlineActor(vtkActor::New()), m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6), @@ -136,6 +137,7 @@ vtkVoxImage::vtkVoxImage(Content &content) vtkVoxImage::~vtkVoxImage() { m_Image->Delete(); m_Actor->Delete(); + m_Asm->Delete(); m_Outline->Delete(); m_OutlineActor->Delete(); } @@ -330,8 +332,9 @@ void vtkVoxImage::InstallPipe() { m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); m_OutlineActor->GetProperty()->SetAmbient(0.7); - this->SetProp(m_Actor); - this->SetProp(m_OutlineActor); + m_Asm->AddPart(m_Actor); + m_Asm->AddPart(m_OutlineActor); + this->SetProp(m_Asm); // Default look this->SetRepresentation(Surface); diff --git a/src/Vtk/Math/vtkVoxImage.h b/src/Vtk/Math/vtkVoxImage.h index 647445d..7b0b087 100644 --- a/src/Vtk/Math/vtkVoxImage.h +++ b/src/Vtk/Math/vtkVoxImage.h @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -77,6 +78,7 @@ private: vtkImageData *m_Image; vtkCubeSource *m_Outline; vtkActor *m_OutlineActor; + vtkAssembly *m_Asm; vtkXMLImageDataReader *m_Reader; vtkXMLImageDataWriter *m_Writer; diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index cbf85a7..106e054 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -58,6 +58,7 @@ #include #include #include +#include #include #include "uLibVtkInterface.h" @@ -65,6 +66,7 @@ #include "Math/Dense.h" #include "Vtk/Math/vtkDense.h" #include "Core/Property.h" +#include "Math/Transform.h" @@ -83,7 +85,7 @@ public: PuppetData(Puppet* owner) : m_Puppet(owner), m_Renderers(vtkSmartPointer::New()), - m_Assembly(vtkSmartPointer::New()), + m_Prop(nullptr), m_ShowBoundingBox(false), m_ShowScaleMeasures(false), m_Representation(Puppet::Surface), @@ -103,7 +105,7 @@ public: Puppet *m_Puppet; // members // vtkSmartPointer m_Renderers; - vtkSmartPointer m_Assembly; + vtkSmartPointer m_Prop; vtkSmartPointer m_OutlineSource; vtkSmartPointer m_OutlineActor; @@ -152,30 +154,26 @@ public: } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) { // Recursively apply to parts of the assembly vtkProp3DCollection *parts = asm_p->GetParts(); - parts->InitTraversal(); - for (int i = 0; i < parts->GetNumberOfItems(); ++i) { - this->ApplyAppearance(parts->GetNextProp3D()); + if (parts) { + parts->InitTraversal(); + for (int i = 0; i < parts->GetNumberOfItems(); ++i) { + this->ApplyAppearance(parts->GetNextProp3D()); + } } } } void ApplyTransform(vtkProp3D* p3d) { if (p3d) { - if (auto* content = dynamic_cast(m_Puppet->GetContent())) { - vtkNew m; - Matrix4fToVtk(content->GetWorldMatrix(), m); - p3d->SetUserMatrix(m); - } else { - p3d->SetUserMatrix(nullptr); - p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); - - // Convert Model Radians to VTK Degrees - p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, - m_Transform.rotation.y() / CLHEP::degree, - m_Transform.rotation.z() / CLHEP::degree); - - p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); - } + p3d->SetUserMatrix(nullptr); + p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); + + // Convert Model Radians to VTK Degrees + p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, + m_Transform.rotation.y() / CLHEP::degree, + m_Transform.rotation.z() / CLHEP::degree); + + p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); } } @@ -183,13 +181,21 @@ public: if (m_Selected) { // Find first polydata in assembly to highlight vtkPolyData* polydata = nullptr; - vtkPropCollection *parts = m_Assembly->GetParts(); - parts->InitTraversal(); - for (int i = 0; i < parts->GetNumberOfItems(); ++i) { - vtkActor *actor = vtkActor::SafeDownCast(parts->GetNextProp()); - if (actor && actor->GetMapper()) { + if (vtkActor *actor = vtkActor::SafeDownCast(m_Prop)) { + if (actor->GetMapper()) { polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput()); - if (polydata) break; + } + } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(m_Prop)) { + vtkPropCollection *parts = asm_p->GetParts(); + if (parts) { + parts->InitTraversal(); + for (int i = 0; i < parts->GetNumberOfItems(); ++i) { + vtkActor *a = vtkActor::SafeDownCast(parts->GetNextProp()); + if (a && a->GetMapper()) { + polydata = vtkPolyData::SafeDownCast(a->GetMapper()->GetDataSetInput()); + if (polydata) break; + } + } } } @@ -205,40 +211,50 @@ public: } if (!m_HighlightActor) { - vtkSmartPointer edges = vtkSmartPointer::New(); - edges->BoundaryEdgesOn(); - edges->FeatureEdgesOn(); - edges->SetFeatureAngle(30); - edges->NonManifoldEdgesOn(); - edges->ManifoldEdgesOff(); - edges->SetInputData(polydata); + vtkSmartPointer cube = vtkSmartPointer::New(); + double bounds[6]; + polydata->GetBounds(bounds); + // Add a small padding to prevent z-fighting + double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); + double pad = maxDim * 0.02; + if(pad < 1e-4) pad = 0.05; + cube->SetBounds(bounds[0]-pad, bounds[1]+pad, + bounds[2]-pad, bounds[3]+pad, + bounds[4]-pad, bounds[5]+pad); m_HighlightActor = vtkSmartPointer::New(); vtkSmartPointer mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(edges->GetOutputPort()); + mapper->SetInputConnection(cube->GetOutputPort()); m_HighlightActor->SetMapper(mapper); - m_HighlightActor->GetProperty()->SetColor(1.0, 0.5, 0.0); // Orange + m_HighlightActor->GetProperty()->SetRepresentationToWireframe(); + m_HighlightActor->GetProperty()->SetColor(1.0, 0.0, 0.0); // Red m_HighlightActor->GetProperty()->SetLineWidth(2.0); m_HighlightActor->GetProperty()->SetLighting(0); } else { - // Update input data (safe even if same) if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { - if (auto* edges = vtkFeatureEdges::SafeDownCast(mapper->GetInputAlgorithm())) { - edges->SetInputData(polydata); + if (auto* cube = vtkCubeSource::SafeDownCast(mapper->GetInputAlgorithm())) { + double bounds[6]; + polydata->GetBounds(bounds); + double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); + double pad = maxDim * 0.02; + if(pad < 1e-4) pad = 0.05; + cube->SetBounds(bounds[0]-pad, bounds[1]+pad, + bounds[2]-pad, bounds[3]+pad, + bounds[4]-pad, bounds[5]+pad); + cube->Modified(); } } } - // Update highlight matrix from the root prop - vtkProp3D* root = nullptr; - if (m_Assembly->GetParts()->GetNumberOfItems() == 1) { - root = vtkProp3D::SafeDownCast(m_Assembly->GetParts()->GetLastProp()); - } else { - root = m_Assembly; - } - - if (root) { - m_HighlightActor->SetUserMatrix(root->GetMatrix()); + // Update highlight matrix from the model world matrix + if (m_Puppet) { + if (auto* content = m_Puppet->GetContent()) { + if (auto* tr = dynamic_cast(content)) { + vtkNew vwm; + Matrix4fToVtk(tr->GetWorldMatrix(), vwm); + m_HighlightActor->SetUserMatrix(vwm); + } + } } m_Renderers->InitTraversal(); @@ -285,36 +301,38 @@ Puppet::~Puppet() vtkProp *Puppet::GetProp() { - if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) - return pd->m_Assembly->GetParts()->GetLastProp(); - else - return pd->m_Assembly; + return pd->m_Prop; +} + +vtkProp3D *Puppet::GetProxyProp() +{ + // The handler should manipulate the highlight actor if it exists + if (pd->m_HighlightActor) { + return pd->m_HighlightActor; + } + return vtkProp3D::SafeDownCast(this->GetProp()); } void Puppet::SetProp(vtkProp *prop) { if(prop) { prop->SetPickable(pd->m_Selectable); - if (auto* p3d = vtkProp3D::SafeDownCast(prop)) { - pd->m_Assembly->AddPart(p3d); - } + pd->m_Prop = vtkProp3D::SafeDownCast(prop); pd->ApplyAppearance(prop); // For the first actor added, seed the tracked display values from the VTK // actor's current state so the display properties panel shows meaningful // initial values instead of the -1 "not-overriding" sentinels. - if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) { - if (auto* actor = vtkActor::SafeDownCast(prop)) { - vtkProperty* vp = actor->GetProperty(); - if (pd->m_Representation < 0) - pd->m_Representation = vp->GetRepresentation(); - if (pd->m_Opacity < 0) - pd->m_Opacity = vp->GetOpacity(); - if (pd->m_Color.x() < 0) { - double c[3]; - vp->GetColor(c); - pd->m_Color = Vector3d(c[0], c[1], c[2]); - } + if (auto* actor = vtkActor::SafeDownCast(prop)) { + vtkProperty* vp = actor->GetProperty(); + if (pd->m_Representation < 0) + pd->m_Representation = vp->GetRepresentation(); + if (pd->m_Opacity < 0) + pd->m_Opacity = vp->GetOpacity(); + if (pd->m_Color.x() < 0) { + double c[3]; + vp->GetColor(c); + pd->m_Color = Vector3d(c[0], c[1], c[2]); } } } @@ -338,12 +356,18 @@ void Puppet::ApplyTransform(vtkProp3D* p3d) vtkPropCollection *Puppet::GetParts() { - return pd->m_Assembly->GetParts(); + if (auto* asm_p = vtkAssembly::SafeDownCast(pd->m_Prop)) { + return asm_p->GetParts(); + } + return nullptr; } vtkPropCollection *Puppet::GetProps() { - return pd->m_Assembly->GetParts(); + if (auto* asm_p = vtkAssembly::SafeDownCast(pd->m_Prop)) { + return asm_p->GetParts(); + } + return nullptr; } void Puppet::ConnectRenderer(vtkRenderer *renderer) @@ -397,7 +421,8 @@ vtkRendererCollection *Puppet::GetRenderers() const void Puppet::PrintSelf(std::ostream &o) const { o << "Props Assembly: \n"; - pd->m_Assembly->PrintSelf(o,vtkIndent(1)); + if (pd->m_Prop) + pd->m_Prop->PrintSelf(o,vtkIndent(1)); o << "Connected Renderers: \n"; pd->m_Renderers->PrintSelf(o,vtkIndent(1)); @@ -417,9 +442,11 @@ void Puppet::ShowBoundingBox(bool show) pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0); } - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_OutlineSource->SetBounds(bounds); - pd->m_OutlineSource->Update(); + if (pd->m_Prop) { + double* bounds = pd->m_Prop->GetBounds(); + pd->m_OutlineSource->SetBounds(bounds); + pd->m_OutlineSource->Update(); + } pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { @@ -449,8 +476,10 @@ void Puppet::ShowScaleMeasures(bool show) pd->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0); } - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_CubeAxesActor->SetBounds(bounds); + if (pd->m_Prop) { + double* bounds = pd->m_Prop->GetBounds(); + pd->m_CubeAxesActor->SetBounds(bounds); + } pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { @@ -472,12 +501,7 @@ void Puppet::ShowScaleMeasures(bool show) void Puppet::SetRepresentation(Representation mode) { pd->m_Representation = static_cast(mode); - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); } void Puppet::SetRepresentation(const char *mode) @@ -497,23 +521,13 @@ void Puppet::SetColor(double r, double g, double b) pd->m_Color[0] = r; pd->m_Color[1] = g; pd->m_Color[2] = b; - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); } void Puppet::SetOpacity(double alpha) { pd->m_Opacity = alpha; - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); } @@ -525,11 +539,7 @@ void Puppet::SetOpacity(double alpha) void Puppet::SetSelectable(bool selectable) { pd->m_Selectable = selectable; - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - props->GetNextProp3D()->SetPickable(selectable); - } + pd->ApplyAppearance(pd->m_Prop); } bool Puppet::IsSelectable() const @@ -552,38 +562,27 @@ bool Puppet::IsSelected() const void Puppet::Update() { - vtkProp* root = this->GetProp(); - if (root) { - // Handle transformation synchronization from content - if (auto* content = dynamic_cast(GetContent())) { - pd->m_Transform = *content; // Uses TRS(const AffineTransform&) - } + // Derived classes should have updated the transform if they override Update() + // or we can apply base transform if it's default: + // pd->ApplyTransform(pd->m_Prop); - if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - pd->ApplyTransform(p3d); - } - pd->ApplyAppearance(root); - } - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); if (pd->m_Selected) { pd->UpdateHighlight(); } - if (pd->m_ShowBoundingBox) { - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_OutlineSource->SetBounds(bounds); - pd->m_OutlineSource->Update(); - } - - if (pd->m_ShowScaleMeasures) { - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_CubeAxesActor->SetBounds(bounds); + if (pd->m_Prop) { + if (pd->m_ShowBoundingBox) { + double* bounds = pd->m_Prop->GetBounds(); + pd->m_OutlineSource->SetBounds(bounds); + pd->m_OutlineSource->Update(); + } + + if (pd->m_ShowScaleMeasures) { + double* bounds = pd->m_Prop->GetBounds(); + pd->m_CubeAxesActor->SetBounds(bounds); + } } // Notify that the object has been updated (important for UI refresh) @@ -599,37 +598,6 @@ void Puppet::Update() } -void Puppet::SyncFromVtk() -{ - vtkProp* root = this->GetProp(); - if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - // Handle content synchronization if it's an AffineTransform - if (auto* content = dynamic_cast(GetContent())) { - // Apply current VTK world transform to model. - // When 'sinking the chain', p3d's matrix represents the world matrix - // because it has no parent in VTK (nesting was removed). - vtkNew m; - p3d->GetMatrix(m); - content->SetWorldMatrix(VtkToMatrix4f(m)); - - // Sync local TRS from the newly updated model - pd->m_Transform = *content; - } - else { - // Fallback for simple props - double pos[3], ori[3], scale[3]; - p3d->GetPosition(pos); - p3d->GetOrientation(ori); - p3d->GetScale(scale); - pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]); - pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree; - pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]); - } - - this->Object::Updated(); - } -} - void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) { } @@ -653,7 +621,8 @@ struct AppearanceProxy { void serialize(Archive & ar, const unsigned int version) { ar & boost::serialization::make_hrp("Color", pd->m_Color, "color"); ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity).range(0.0, 1.0).set_default(1.0); - ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); + ar & boost::serialization::make_hrp_enum("Representation", + pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index 3b0f806..f735833 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -26,15 +26,15 @@ #ifndef ULIBVTKINTERFACE_H #define ULIBVTKINTERFACE_H +#include "Core/Monitor.h" +#include "Core/Object.h" +#include "Core/Property.h" +#include +#include +#include #include #include #include -#include -#include -#include -#include "Core/Object.h" -#include "Core/Property.h" -#include "Core/Monitor.h" // vtk classes forward declaration // class vtkProp; @@ -46,22 +46,27 @@ class vtkRendererCollection; class vtkRenderWindowInteractor; namespace uLib { - namespace Archive { class display_properties_archive; } - namespace Vtk { class Puppet; class Viewer; } +namespace Archive { +class display_properties_archive; } +namespace Vtk { +class Puppet; +class Viewer; +} // namespace Vtk +} // namespace uLib namespace uLib { namespace Vtk { class Puppet : public uLib::Object { - uLibTypeMacro(Puppet, uLib::Object) +uLibTypeMacro(Puppet, uLib::Object) -public: - Puppet(); + public : Puppet(); virtual ~Puppet(); virtual vtkProp *GetProp(); + virtual vtkProp3D *GetProxyProp(); virtual vtkPropCollection *GetParts(); @@ -87,10 +92,31 @@ public: void SetSelected(bool selected = true); bool IsSelected() const; + /** + * @brief Synchronizes the VTK representation with the internal state and properties. + * + * This method should be called whenever the underlying model or display properties + * are modified to ensure the visual representation in VTK is consistent. + */ virtual void Update(); - virtual void SyncFromVtk(); - enum Representation { Points = 0, Wireframe = 1, Surface = 2, SurfaceWithEdges = 3, Volume = 4, Outline = 5, Slice = 6 }; + /** + * @brief Synchronizes the internal state and properties from the VTK representation. + * + * This method should be called when the VTK representation has been modified + * (e.g., via a gizmo) and the changes need to be pushed back to the model. + */ + virtual void SyncFromVtk() {} + + enum Representation { + Points = 0, + Wireframe = 1, + Surface = 2, + SurfaceWithEdges = 3, + Volume = 4, + Outline = 5, + Slice = 6 + }; void SetRepresentation(Representation mode); void SetRepresentation(const char *mode); @@ -101,10 +127,15 @@ public: vtkRendererCollection *GetRenderers() const; - const std::vector& GetDisplayProperties() const { return m_DisplayProperties; } - void RegisterDisplayProperty(uLib::PropertyBase* prop) { m_DisplayProperties.push_back(prop); } + const std::vector &GetDisplayProperties() const { + return m_DisplayProperties; + } + void RegisterDisplayProperty(uLib::PropertyBase *prop) { + m_DisplayProperties.push_back(prop); + } - virtual void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0); + virtual void serialize_display(uLib::Archive::display_properties_archive &ar, + const unsigned int version = 0); virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor); @@ -116,15 +147,15 @@ protected: void RemoveProp(vtkProp *prop); - void ApplyAppearance(vtkProp* prop); - void ApplyTransform(vtkProp3D* p3d); + void ApplyAppearance(vtkProp *prop); + void ApplyTransform(vtkProp3D *p3d); - std::vector m_DisplayProperties; + std::vector m_DisplayProperties; mutable uLib::RecursiveMutex m_UpdateMutex; private: - Puppet(const Puppet&) = delete; - Puppet& operator=(const Puppet&) = delete; + Puppet(const Puppet &) = delete; + Puppet &operator=(const Puppet &) = delete; friend class PuppetData; class PuppetData *pd; @@ -133,104 +164,110 @@ private: } // namespace Vtk } // namespace uLib - - - - - - - - - // -------------------------------------------------------------------------- // -// DISPLAY PROPERTIES SERIALIZE +// DISPLAY PROPERTIES SERIALIZE // -------------------------------------------------------------------------- // namespace uLib { namespace Archive { /** - * @brief Specialized archive for registering display-only properties in Puppets. + * @brief Specialized archive for registering display-only properties in + * Puppets. */ -class display_properties_archive : public boost::archive::detail::common_oarchive { +class display_properties_archive + : public boost::archive::detail::common_oarchive< + display_properties_archive> { public: - display_properties_archive(Vtk::Puppet* puppet) : - boost::archive::detail::common_oarchive(boost::archive::no_header), + display_properties_archive(Vtk::Puppet *puppet) + : boost::archive::detail::common_oarchive( + boost::archive::no_header), m_Puppet(puppet) {} - - std::string GetCurrentGroup() const { - std::string group; - for (const auto& g : m_GroupStack) { - if (!group.empty()) group += "."; - group += g; - } - return group; + + std::string GetCurrentGroup() const { + std::string group; + for (const auto &g : m_GroupStack) { + if (!group.empty()) + group += "."; + group += g; } + return group; + } - template - void save_override(const boost::serialization::hrp &t) { - if (m_Puppet) { - uLib::Property* p = new uLib::Property(m_Puppet, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); - if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); - if (t.has_default()) p->SetDefault(t.default_val()); - - m_Puppet->RegisterDisplayProperty(p); - Vtk::Puppet* puppet = m_Puppet; - uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); - } + template void save_override(const boost::serialization::hrp &t) { + if (m_Puppet) { + uLib::Property *p = new uLib::Property( + m_Puppet, t.name(), + &const_cast &>(t).value(), + t.units() ? t.units() : "", GetCurrentGroup()); + if (t.has_range()) + p->SetRange(t.min_val(), t.max_val()); + if (t.has_default()) + p->SetDefault(t.default_val()); + + m_Puppet->RegisterDisplayProperty(p); + Vtk::Puppet *puppet = m_Puppet; + uLib::Object::connect(p, &uLib::PropertyBase::Updated, + [puppet]() { puppet->Update(); }); } + } - template - void save_override(const boost::serialization::hrp_enum &t) { - if (m_Puppet) { - uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); - m_Puppet->RegisterDisplayProperty(p); - Vtk::Puppet* puppet = m_Puppet; - uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); - } + template + void save_override(const boost::serialization::hrp_enum &t) { + if (m_Puppet) { + uLib::EnumProperty *p = new uLib::EnumProperty( + m_Puppet, t.name(), + (int *)&const_cast &>(t).value(), + t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); + m_Puppet->RegisterDisplayProperty(p); + Vtk::Puppet *puppet = m_Puppet; + uLib::Object::connect(p, &uLib::PropertyBase::Updated, + [puppet]() { puppet->Update(); }); } + } - template void save_override(const boost::serialization::nvp &t) { - if (t.name()) m_GroupStack.push_back(t.name()); - this->save_helper(t.const_value(), typename boost::is_class::type()); - if (t.name()) m_GroupStack.pop_back(); - } + template void save_override(const boost::serialization::nvp &t) { + if (t.name()) + m_GroupStack.push_back(t.name()); + this->save_helper(t.const_value(), typename boost::is_class::type()); + if (t.name()) + m_GroupStack.pop_back(); + } - // Recursion for nested classes, ignore primitives - template void save_override(const T &t) { - this->save_helper(t, typename boost::is_class::type()); - } + // Recursion for nested classes, ignore primitives + template void save_override(const T &t) { + this->save_helper(t, typename boost::is_class::type()); + } - template - void save_helper(const T &t, boost::mpl::true_) { - boost::serialization::serialize_adl(*this, const_cast(t), 0); - } + template void save_helper(const T &t, boost::mpl::true_) { + boost::serialization::serialize_adl(*this, const_cast(t), 0); + } - template - void save_helper(const T &t, boost::mpl::false_) {} + template void save_helper(const T &t, boost::mpl::false_) {} - void save_override(const boost::archive::object_id_type & t) {} - void save_override(const boost::archive::object_reference_type & t) {} - void save_override(const boost::archive::version_type & t) {} - void save_override(const boost::archive::class_id_type & t) {} - void save_override(const boost::archive::class_id_optional_type & t) {} - void save_override(const boost::archive::class_id_reference_type & t) {} - void save_override(const boost::archive::class_name_type & t) {} - void save_override(const boost::archive::tracking_type & t) {} + void save_override(const boost::archive::object_id_type &t) {} + void save_override(const boost::archive::object_reference_type &t) {} + void save_override(const boost::archive::version_type &t) {} + void save_override(const boost::archive::class_id_type &t) {} + void save_override(const boost::archive::class_id_optional_type &t) {} + void save_override(const boost::archive::class_id_reference_type &t) {} + void save_override(const boost::archive::class_name_type &t) {} + void save_override(const boost::archive::tracking_type &t) {} private: - Vtk::Puppet* m_Puppet; - std::vector m_GroupStack; + Vtk::Puppet *m_Puppet; + std::vector m_GroupStack; }; } // namespace Archive -// This macro MUST be defined after both Puppet and display_properties_archive are fully defined. -#define ULIB_ACTIVATE_DISPLAY_PROPERTIES \ - { \ - uLib::Archive::display_properties_archive dar(this); \ - this->serialize_display(dar, 0); \ - } +// This macro MUST be defined after both Puppet and display_properties_archive +// are fully defined. +#define ULIB_ACTIVATE_DISPLAY_PROPERTIES \ + { \ + uLib::Archive::display_properties_archive dar(this); \ + this->serialize_display(dar, 0); \ + } } // namespace uLib diff --git a/src/Vtk/vtkHandlerWidget.cpp b/src/Vtk/vtkHandlerWidget.cpp index dd7dabc..294910a 100644 --- a/src/Vtk/vtkHandlerWidget.cpp +++ b/src/Vtk/vtkHandlerWidget.cpp @@ -547,16 +547,14 @@ void vtkHandlerWidget::OnMouseMove() { total->Concatenate(op); if (this->Prop3D) { - double p[3], r[3], s[3]; - total->GetPosition(p); - total->GetOrientation(r); - total->GetScale(s); - this->Prop3D->SetPosition(p); - // VTK GetOrientation already returned degrees, so r is in degrees. - // We apply it directly back to VTK. - this->Prop3D->SetOrientation(r); - this->Prop3D->SetScale(s); - this->Prop3D->SetUserMatrix(nullptr); + vtkNew result; + total->GetMatrix(result); + this->Prop3D->SetUserMatrix(result); + + // Reset individual TRS components so UserMatrix is the single source of truth + this->Prop3D->SetPosition(0, 0, 0); + this->Prop3D->SetOrientation(0, 0, 0); + this->Prop3D->SetScale(1, 1, 1); } this->Prop3D->Modified(); diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 4e17ea0..4894890 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -107,6 +107,12 @@ void vtkObjectsContext::Update() { } } +void vtkObjectsContext::SyncFromVtk() { + for (auto const& [obj, puppet] : m_Puppets) { + puppet->SyncFromVtk(); + } +} + Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { if (!obj) return nullptr; diff --git a/src/Vtk/vtkObjectsContext.h b/src/Vtk/vtkObjectsContext.h index cfb480a..3299de3 100644 --- a/src/Vtk/vtkObjectsContext.h +++ b/src/Vtk/vtkObjectsContext.h @@ -30,6 +30,9 @@ public: /** @brief Updates all managed puppets. */ virtual void Update() override; + /** @brief Synchronizes all managed puppets back to their models. */ + virtual void SyncFromVtk() override; + public: virtual void PuppetAdded(Puppet* puppet); virtual void PuppetRemoved(Puppet* puppet); diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index f4c2026..d6b8521 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -505,11 +505,11 @@ void Viewport::SelectPuppet(Puppet* prop) if (pv->m_HandlerWidget) { if (prop) { - vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp()); + vtkProp3D* prop3d = prop->GetProxyProp(); if (prop3d) { pv->m_HandlerWidget->SetProp3D(prop3d); pv->m_HandlerWidget->SetEnabled(1); - pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); + pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); //TODO: FIX ! } } else { pv->m_HandlerWidget->SetEnabled(0); From f3274f346b018121c260b09edf4e522830c5f1f0 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Tue, 31 Mar 2026 16:32:43 +0000 Subject: [PATCH 11/24] refactor: prevent update loops in vtkCylinder by tracking connection and blocking signals during sync --- src/Vtk/Math/vtkCylinder.cpp | 26 +++++++++++++++++--------- src/Vtk/Math/vtkCylinder.h | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Vtk/Math/vtkCylinder.cpp b/src/Vtk/Math/vtkCylinder.cpp index 92b2767..7115604 100644 --- a/src/Vtk/Math/vtkCylinder.cpp +++ b/src/Vtk/Math/vtkCylinder.cpp @@ -40,7 +40,7 @@ namespace Vtk { vtkCylinder::vtkCylinder(vtkCylinder::Content *content) : m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) { this->InstallPipe(); - Object::connect(m_Content, &uLib::Object::Updated, this, &vtkCylinder::Update); + m_UpdateSignal = Object::connect(m_Content, &uLib::Object::Updated, this, &vtkCylinder::Update); } vtkCylinder::~vtkCylinder() { @@ -80,23 +80,31 @@ void vtkCylinder::Update() { root->Modified(); } - // Use base class sync, which handles appearance and children + // Delegate rest of update (appearance, render, etc) + ConnectionBlock blocker(m_UpdateSignal); this->Puppet::Update(); } void vtkCylinder::SyncFromVtk() { if (!m_Content) return; - vtkProp3D* assembly = this->GetProxyProp(); - if (!assembly) return; + vtkProp3D* root = this->GetProxyProp(); + if (!root) return; - // VTK -> Model: Update TRS properties from VTK matrix via world transform - m_Content->SetWorldMatrix(VtkToMatrix4f(assembly->GetUserMatrix())); + // VTK -> Model: Extract new world TRS from proxy + vtkMatrix4x4* rootMat = root->GetUserMatrix(); + if (rootMat) { + std::cout << "[vtkCylinder::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; + rootMat->Print(std::cout); + } - // Resync TRS property members (pos/rot/scale) from the newly set local matrix - m_Content->FromMatrix(m_Content->GetMatrix()); + Matrix4f vtkWorld = VtkToMatrix4f(rootMat); - // Since we modified the model, notify observers, but block the loop back to VTK + // Directly sync model from the world matrix + m_Content->FromMatrix(vtkWorld); + + std::cout << "[vtkCylinder::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; + m_Content->Updated(); } diff --git a/src/Vtk/Math/vtkCylinder.h b/src/Vtk/Math/vtkCylinder.h index 9d431ee..0344421 100644 --- a/src/Vtk/Math/vtkCylinder.h +++ b/src/Vtk/Math/vtkCylinder.h @@ -63,6 +63,7 @@ protected: vtkActor *m_Actor; ::vtkAssembly *m_VtkAsm; Content *m_Content; + uLib::Connection m_UpdateSignal; }; } // namespace Vtk From 34f834d370bb343cce355dbc272e9f1c48c2d698 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Tue, 31 Mar 2026 17:05:17 +0000 Subject: [PATCH 12/24] feat: add NotifyPropertiesUpdated to Object and trigger on Transform changes for UI synchronization --- src/Core/Object.cpp | 5 +++++ src/Core/Object.h | 4 ++++ src/Math/Transform.h | 2 ++ 3 files changed, 11 insertions(+) diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index 0e3fe84..dd95980 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -98,6 +98,11 @@ PropertyBase* Object::GetProperty(const std::string& name) const { return nullptr; } +void Object::NotifyPropertiesUpdated() { + for (auto* p : d->m_Properties) p->Updated(); + for (auto* p : d->m_DynamicProperties) p->Updated(); +} + // In Object.h, the template serialize needs to be updated to call property serialization. // However, since Object::serialize is a template in the header, we might need a helper here. diff --git a/src/Core/Object.h b/src/Core/Object.h index 4b0fda9..40613ec 100644 --- a/src/Core/Object.h +++ b/src/Core/Object.h @@ -96,6 +96,9 @@ public: const std::vector& GetProperties() const; PropertyBase* GetProperty(const std::string& name) const; + /** @brief Sends an Updated signal for all properties of this object. useful for real-time UI refresh. */ + void NotifyPropertiesUpdated(); + //////////////////////////////////////////////////////////////////////////// // PARAMETERS // @@ -133,6 +136,7 @@ public: signals: virtual void Updated(); + virtual void PropertyUpdated(); // Qt4 style connector // static bool connect(const Object *ob1, const char *signal_name, diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 6ae1ada..63d0c5d 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -223,6 +223,7 @@ public: this->rotation = Vector3f(euler(2), euler(1), euler(0)); this->SetMatrix(mat); + this->NotifyPropertiesUpdated(); } void SetPosition(const Vector3f &v) { @@ -248,6 +249,7 @@ public: void Updated() override { this->SyncMatrix(); + this->NotifyPropertiesUpdated(); this->AffineTransform::Updated(); } From c0c25de694ac9a3737b7d6658e9e26a0e0219eab Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Wed, 1 Apr 2026 08:46:09 +0000 Subject: [PATCH 13/24] feat: add Geant material management classes and enhance serialization macros for NVP and HRP support --- src/Core/Serializable.h | 37 +++++++++++++++++++++++++++++++------ src/Math/ContainerBox.h | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Core/Serializable.h b/src/Core/Serializable.h index 03bcfe2..8fe0930 100644 --- a/src/Core/Serializable.h +++ b/src/Core/Serializable.h @@ -71,15 +71,16 @@ namespace serialization { // ACCESS 2 // template struct access2 {}; -// NON FUNZIONA ... SISTEMARE !!!! // ------------------------------------------ template class hrp : public boost::serialization::wrapper_traits> { + const char *m_name; const char *m_units; T &m_value; + bool m_has_range; - T m_min; - T m_max; + T m_min, m_max; + bool m_has_default; T m_default; @@ -160,8 +161,12 @@ inline hrp_enum make_hrp_enum(const char *name, T &t, const std::vector(name, t, labels, units); } -#define HRP(name) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name) -#define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units) +template +inline hrp make_nvp(const char *name, T &t, const char* units) { + return hrp(name, t, units); +} + + } // namespace serialization } // namespace boost @@ -181,7 +186,27 @@ namespace uLib { #define _AR_OP(r, data, elem) data &BOOST_SERIALIZATION_BASE_OBJECT_NVP(elem); -#define NVP(data) BOOST_SERIALIZATION_NVP(data) +// NAME VALUE PAIR // +#define NVP_GET_MACRO(_1, _2, _3, NAME, ...) NAME +#define NVP(...) NVP_GET_MACRO(__VA_ARGS__, NVP3, NVP2, NVP1)(__VA_ARGS__) + +#define NVP1(data) BOOST_SERIALIZATION_NVP(data) +#define NVP2(name, data) boost::serialization::make_nvp(name, data) +#define NVP3(name, data, units) boost::serialization::make_nvp(name, data, units) + + +// HUMAN READABLE PROPERTY // +#define HRP_GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME +#define HRP(...) HRP_GET_MACRO(__VA_ARGS__, HRP6, HRP5, HRP4, HRP3, HRP2, HRP1)(__VA_ARGS__) + +#define HRP1(data) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(data), data) +#define HRP2(name, data) boost::serialization::make_hrp(name, data) +#define HRP3(name, data, units) boost::serialization::make_hrp(name, data, units) +#define HRP4(name, data, units, default) boost::serialization::make_hrp(name, data, units).set_default(default) +#define HRP5(name, data, units, min, max) boost::serialization::make_hrp(name, data, units).range(min, max) +#define HRP6(name, data, units, default, min, max) boost::serialization::make_hrp(name, data, units).set_default(default).range(min, max) + +#define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units) //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index ec83c1c..674e571 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -103,7 +103,7 @@ public: void serialize(ArchiveT & ar, const unsigned int version) { ar & HRP(Size); ar & HRP(Origin); - ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); + ar & NVP("TRS", boost::serialization::base_object(*this)); } /** From e1bd7eb44fbb4d58ca20becdd690e0a3816cacc9 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Wed, 1 Apr 2026 11:13:28 +0000 Subject: [PATCH 14/24] feat: implement serialization read_only --- src/Core/Object.cpp | 1 + src/Core/Property.h | 38 ++++++- src/Core/Serializable.h | 125 +++++++++++++++++++++- src/Core/testing/CMakeLists.txt | 1 + src/Core/testing/ReadOnlyPropertyTest.cpp | 82 ++++++++++++++ 5 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 src/Core/testing/ReadOnlyPropertyTest.cpp diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index dd95980..cdc4837 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -115,6 +115,7 @@ void Object::serialize(ArchiveT &ar, const unsigned int version) { } void Object::Updated() { ULIB_SIGNAL_EMIT(Object::Updated); } +void Object::PropertyUpdated() { ULIB_SIGNAL_EMIT(Object::PropertyUpdated); } template void Object::save_override(ArchiveT &ar, const unsigned int version) {} diff --git a/src/Core/Property.h b/src/Core/Property.h index fe30fbe..7f35875 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -42,6 +42,7 @@ public: virtual double GetMax() const { return 0; } virtual bool HasDefault() const { return false; } virtual std::string GetDefaultValueAsString() const { return ""; } + virtual bool IsReadOnly() const = 0; std::string GetQualifiedName() const { if (GetGroup().empty()) return GetName(); @@ -71,7 +72,7 @@ public: // PROXY: Use an existing variable as back-end storage Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "") : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false), - m_HasRange(false), m_HasDefault(false) { + m_HasRange(false), m_HasDefault(false), m_ReadOnly(false) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -80,7 +81,7 @@ public: // MANAGED: Create and own internal storage Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "") : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true), - m_HasRange(false), m_HasDefault(true), m_Default(defaultValue) { + m_HasRange(false), m_HasDefault(true), m_Default(defaultValue), m_ReadOnly(false) { if (m_owner) { m_owner->RegisterProperty(this); } @@ -139,6 +140,9 @@ public: void SetRange(const T& min, const T& max) { m_Min = min; m_Max = max; m_HasRange = true; } void SetDefault(const T& def) { m_Default = def; m_HasDefault = true; } + void SetReadOnly(bool ro) { m_ReadOnly = ro; } + + virtual bool IsReadOnly() const override { return m_ReadOnly; } virtual bool HasRange() const override { return m_HasRange; } @@ -209,6 +213,7 @@ private: T m_Max; bool m_HasDefault; T m_Default; + bool m_ReadOnly; }; /** @@ -304,6 +309,25 @@ public: Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); if (t.has_default()) p->SetDefault(t.default_val()); + p->SetReadOnly(t.is_read_only()); + m_Object->RegisterDynamicProperty(p); + } + } + + template + void save_override(const boost::serialization::hrp_val &t) { + if (m_Object) { + // Note: hrp_val stores by value. Property usually points to existing data. + // But here we are registering properties from HRP wrappers. + // If it's hrp_val, it means it's an rvalue from a getter. + // The hrp_val wrapper itself owns the value. + // However, the property_register_archive is temporary. + // This is a bit tricky. Usually HRP(rvalue) is meant for read-only display. + // Let's use the address of the value in the wrapper, but mark it read-only. + Property* p = new Property(m_Object, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); + if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); + if (t.has_default()) p->SetDefault(t.default_val()); + p->SetReadOnly(t.is_read_only()); m_Object->RegisterDynamicProperty(p); } } @@ -312,6 +336,16 @@ public: void save_override(const boost::serialization::hrp_enum &t) { if (m_Object) { EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); + p->SetReadOnly(t.is_read_only()); + m_Object->RegisterDynamicProperty(p); + } + } + + template + void save_override(const boost::serialization::hrp_enum_val &t) { + if (m_Object) { + EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); + p->SetReadOnly(t.is_read_only()); m_Object->RegisterDynamicProperty(p); } } diff --git a/src/Core/Serializable.h b/src/Core/Serializable.h index 8fe0930..b08542b 100644 --- a/src/Core/Serializable.h +++ b/src/Core/Serializable.h @@ -102,6 +102,8 @@ public: bool has_default() const { return m_has_default; } const T& default_val() const { return m_default; } + static constexpr bool is_read_only() { return false; } + BOOST_SERIALIZATION_SPLIT_MEMBER() template @@ -115,11 +117,6 @@ public: } }; -template -inline hrp make_hrp(const char *name, T &t, const char* units = nullptr) { - return hrp(name, t, units); -} - template class hrp_enum : public boost::serialization::wrapper_traits> { const char *m_name; @@ -143,6 +140,8 @@ public: bool has_default() const { return m_has_default; } const T& default_val() const { return m_default; } + static constexpr bool is_read_only() { return false; } + BOOST_SERIALIZATION_SPLIT_MEMBER() template @@ -156,16 +155,120 @@ public: } }; +template +class hrp_val : public boost::serialization::wrapper_traits> { + const char *m_name; + const char *m_units; + T m_value; + bool m_has_range; + T m_min, m_max; + bool m_has_default; + T m_default; + +public: + explicit hrp_val(const char *name_, T t, const char* units_ = nullptr) + : m_name(name_), m_units(units_), m_value(t), m_has_range(false), m_has_default(false) {} + + hrp_val& range(const T& min_val, const T& max_val) { m_min = min_val; m_max = max_val; m_has_range = true; return *this; } + hrp_val& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; } + + const char *name() const { return this->m_name; } + const char *units() const { return this->m_units; } + T &value() { return this->m_value; } + const T &const_value() const { return this->m_value; } + + bool has_range() const { return m_has_range; } + const T& min_val() const { return m_min; } + const T& max_val() const { return m_max; } + bool has_default() const { return m_has_default; } + const T& default_val() const { return m_default; } + + static constexpr bool is_read_only() { return true; } + + BOOST_SERIALIZATION_SPLIT_MEMBER() + + template + void save(Archivex &ar, const unsigned int /* version */) const { + ar << boost::serialization::make_nvp(m_name, m_value); + } + + template + void load(Archivex &ar, const unsigned int /* version */) { + // Only for output archives + } +}; + +template +class hrp_enum_val : public boost::serialization::wrapper_traits> { + const char *m_name; + const char *m_units; + T m_value; + std::vector m_labels; + bool m_has_default; + T m_default; + +public: + explicit hrp_enum_val(const char *name_, T t, const std::vector& labels, const char* units_ = nullptr) + : m_name(name_), m_units(units_), m_value(t), m_labels(labels), m_has_default(false) {} + + hrp_enum_val& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; } + + const char *name() const { return this->m_name; } + const char *units() const { return this->m_units; } + T &value() { return this->m_value; } + const std::vector& labels() const { return m_labels; } + + bool has_default() const { return m_has_default; } + const T& default_val() const { return m_default; } + + static constexpr bool is_read_only() { return true; } + + BOOST_SERIALIZATION_SPLIT_MEMBER() + + template + void save(Archivex &ar, const unsigned int /* version */) const { + ar << boost::serialization::make_nvp(m_name, m_value); + } + + template + void load(Archivex &ar, const unsigned int /* version */) { + // Only for output archives + } +}; + +template +inline hrp make_hrp(const char *name, T &t, const char* units = nullptr) { + return hrp(name, t, units); +} + +// Specialization for rvalues (value-based storage) +template +inline hrp_val make_hrp(const char *name, T &&t, const char* units = nullptr) { + return hrp_val(name, t, units); +} + template inline hrp_enum make_hrp_enum(const char *name, T &t, const std::vector& labels, const char* units = nullptr) { return hrp_enum(name, t, labels, units); } +// Specialization for rvalues (value-based storage) +template +inline hrp_enum_val make_hrp_enum(const char *name, T &&t, const std::vector& labels, const char* units = nullptr) { + return hrp_enum_val(name, t, labels, units); +} + template inline hrp make_nvp(const char *name, T &t, const char* units) { return hrp(name, t, units); } +// Specialization for rvalues (value-based storage) +template +inline hrp_val make_nvp(const char *name, T &&t, const char* units) { + return hrp_val(name, t, units); +} + } // namespace serialization @@ -206,8 +309,20 @@ namespace uLib { #define HRP5(name, data, units, min, max) boost::serialization::make_hrp(name, data, units).range(min, max) #define HRP6(name, data, units, default, min, max) boost::serialization::make_hrp(name, data, units).set_default(default).range(min, max) +// LEFT FOR BACKWARD COMPATIBILITY #define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units) + + + + +namespace serialization { +using boost::serialization::make_nvp; +using boost::serialization::make_hrp; +using boost::serialization::make_hrp_enum; +} // serialization + + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Core/testing/CMakeLists.txt b/src/Core/testing/CMakeLists.txt index a3e3283..75ae59a 100644 --- a/src/Core/testing/CMakeLists.txt +++ b/src/Core/testing/CMakeLists.txt @@ -29,6 +29,7 @@ set( TESTS OpenMPTest TeamTest AffinityTest + ReadOnlyPropertyTest ) set(LIBRARIES diff --git a/src/Core/testing/ReadOnlyPropertyTest.cpp b/src/Core/testing/ReadOnlyPropertyTest.cpp new file mode 100644 index 0000000..b9200ae --- /dev/null +++ b/src/Core/testing/ReadOnlyPropertyTest.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include "Core/Object.h" +#include "Core/Property.h" +#include "Core/Serializable.h" + +#include "Core/Serializable.h" + +using namespace uLib; + +class ReadOnlyTestObject : public Object { +public: + int m_value; + int getValue() const { return m_value; } + + enum State { State1, State2 }; + State m_state; + State getState() const { return m_state; } + + ReadOnlyTestObject() : m_value(10), m_state(State1) { + ULIB_ACTIVATE_PROPERTIES(*this); + } + + template + void serialize(Archive & ar, const unsigned int version) { + // Lvalue reference - should be NOT read-only + ar & HRP("lvalue_prop", m_value); + + // Rvalue from getter - should be read-only + ar & HRP("rvalue_prop", getValue()); + + // Enum lvalue - should be NOT read-only + ar & boost::serialization::make_hrp_enum("lvalue_enum", (int&)m_state, {"State1", "State2"}); + + // Enum rvalue - should be read-only + ar & boost::serialization::make_hrp_enum("rvalue_enum", (int)getState(), {"State1", "State2"}); + } +}; + +int main() { + std::cout << "Testing Read-Only Property Feature..." << std::endl; + + ReadOnlyTestObject obj; + const auto& props = obj.GetProperties(); + + std::cout << "Registered Properties in ReadOnlyTestObject:" << std::endl; + bool found_lvalue = false; + bool found_rvalue = false; + bool found_lvalue_enum = false; + bool found_rvalue_enum = false; + + for (auto* p : props) { + bool ro = p->IsReadOnly(); + std::cout << " - Name: " << p->GetName() + << " | Type: " << p->GetTypeName() + << " | ReadOnly: " << (ro ? "YES" : "NO") << std::endl; + + if (p->GetName() == "lvalue_prop") { + if (ro) { std::cerr << "FAIL: lvalue_prop should NOT be read-only" << std::endl; return 1; } + found_lvalue = true; + } else if (p->GetName() == "rvalue_prop") { + if (!ro) { std::cerr << "FAIL: rvalue_prop SHOULD be read-only" << std::endl; return 1; } + found_rvalue = true; + } else if (p->GetName() == "lvalue_enum") { + if (ro) { std::cerr << "FAIL: lvalue_enum should NOT be read-only" << std::endl; return 1; } + found_lvalue_enum = true; + } else if (p->GetName() == "rvalue_enum") { + if (!ro) { std::cerr << "FAIL: rvalue_enum SHOULD be read-only" << std::endl; return 1; } + found_rvalue_enum = true; + } + } + + if (found_lvalue && found_rvalue && found_lvalue_enum && found_rvalue_enum) { + std::cout << "TEST PASSED SUCCESSFULLY!" << std::endl; + return 0; + } else { + std::cerr << "TEST FAILED: Some properties were not found!" << std::endl; + return 1; + } +} From 8e6e3322176c4940a27c4eec199fcc7a62da0bf2 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Wed, 1 Apr 2026 11:13:47 +0000 Subject: [PATCH 15/24] feat: implement Geant Material class, add object registration, and update PropertyWidget signal handling and read-only state --- app/gcompose/src/PropertyWidgets.cpp | 18 ++++++------ app/gcompose/src/PropertyWidgets.h | 3 +- docs/geant_integration.md | 11 +++++++ src/HEP/Geant/CMakeLists.txt | 2 ++ src/HEP/Geant/GeantRegistration.cpp | 22 ++++++++++++++ src/HEP/Geant/Matter.cpp | 21 ++++++++++++++ src/HEP/Geant/Matter.h | 43 ++++++++++++++++++++++++++-- src/HEP/Geant/Solid.cpp | 26 ++++++++++++----- src/HEP/Geant/Solid.h | 21 ++++++++++++-- src/Vtk/uLibVtkInterface.cxx | 2 ++ 10 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 docs/geant_integration.md create mode 100644 src/HEP/Geant/GeantRegistration.cpp create mode 100644 src/HEP/Geant/Matter.cpp diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index 1c586c5..3c87bf9 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -36,6 +36,8 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent) m_Label = new QLabel(labelText, this); m_Label->setMinimumWidth(120); m_Layout->addWidget(m_Label); + + this->setEnabled(!prop->IsReadOnly()); } PropertyWidgetBase::~PropertyWidgetBase() { m_Connection.disconnect(); @@ -150,7 +152,7 @@ DoublePropertyWidget::DoublePropertyWidget(Property* prop, QWidget* pare m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); }); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ m_Edit->setValue(m_Prop->Get()); }); } @@ -168,7 +170,7 @@ FloatPropertyWidget::FloatPropertyWidget(Property* prop, QWidget* parent) m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); }); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ m_Edit->setValue((double)m_Prop->Get()); }); } @@ -187,7 +189,7 @@ IntPropertyWidget::IntPropertyWidget(Property* prop, QWidget* parent) m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); }); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ m_Edit->setValue((double)m_Prop->Get()); }); } @@ -198,7 +200,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property* prop, QWidget* parent) m_CheckBox->setChecked(prop->Get()); m_Layout->addWidget(m_CheckBox, 1); connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); }); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ if (m_CheckBox->isChecked() != m_Prop->Get()) { QSignalBlocker blocker(m_CheckBox); m_CheckBox->setChecked(m_Prop->Get()); @@ -222,7 +224,7 @@ RangePropertyWidget::RangePropertyWidget(Property* prop, QWidget* parent connect(m_Slider, &QSlider::valueChanged, this, &RangePropertyWidget::onSliderChanged); connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); }); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ this->updateUi(); }); updateUi(); @@ -252,7 +254,7 @@ ColorPropertyWidget::ColorPropertyWidget(Property* prop, QWidget* pare m_Layout->addWidget(m_Button, 0, ::Qt::AlignRight); connect(m_Button, &QPushButton::clicked, this, &ColorPropertyWidget::onClicked); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ this->updateButtonColor(); }); } @@ -286,7 +288,7 @@ StringPropertyWidget::StringPropertyWidget(Property* prop, QWidget* std::string val = m_LineEdit->text().toStdString(); if (m_Prop->Get() != val) m_Prop->Set(val); }); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ if (m_LineEdit->text().toStdString() != m_Prop->Get()) { QSignalBlocker blocker(m_LineEdit); m_LineEdit->setText(QString::fromStdString(m_Prop->Get())); @@ -334,7 +336,7 @@ public: p->Set(index); }); // Store connection in base m_Connection so it's auto-disconnected on destruction. - m_Connection = uLib::Object::connect(p, &Property::PropertyChanged, [this, p](){ + m_Connection = uLib::Object::connect(p, &Property::Updated, [this, p](){ if (m_Combo->currentIndex() != p->Get()) { QSignalBlocker blocker(m_Combo); m_Combo->setCurrentIndex(p->Get()); diff --git a/app/gcompose/src/PropertyWidgets.h b/app/gcompose/src/PropertyWidgets.h index 7374389..3eca02f 100644 --- a/app/gcompose/src/PropertyWidgets.h +++ b/app/gcompose/src/PropertyWidgets.h @@ -115,6 +115,7 @@ public: if (!prefSuffix.isEmpty()) { m_Edits[i]->setUnits(prefSuffix, factor); } + m_Edits[i]->setEnabled(!prop->IsReadOnly()); m_Layout->addWidget(m_Edits[i], 1); connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){ @@ -124,7 +125,7 @@ public: }); } updateEdits(); - m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ updateEdits(); }); } diff --git a/docs/geant_integration.md b/docs/geant_integration.md new file mode 100644 index 0000000..d51b002 --- /dev/null +++ b/docs/geant_integration.md @@ -0,0 +1,11 @@ +# Geant integration + +Geant4 integration in uLib is done through the `HEP/Geant` module. +The module represets a set of wrapper for geant objects that are also deriving from uLib::Object so they can be used in the uLib::Object tree and visualized with the uLib::Vtk module and driven py properties. + +# Geant Solid integration + +Geant solid in uLib is represented by the `uLib::Geant::Solid` class and mainly BoxSolid and TessellatedSolid. The solids in Geant does not have the possibility to set properties on the fly so we need to create a new solid every time we want to change the properties of a solid. This is done by creating a new `uLib::Geant::Solid` object and setting the properties of the new solid. The new solid is then added to the `uLib::Geant::Solid` object as a child. The old solid is then removed from the `uLib::Geant::Solid` object as a child. The old solid is then deleted. However id some of the properties can be set then the library will drive the change in the solid update. + +The idea is to have a mapping of solid properties that can be used in uLib for Qt representation or vtk representation. then when the property is changed the signaling will update the property in uLib and then the solid will be updated. If the Geant property can be applied to the G4 object underneath then the update will apply the change, in case it is not possible to apply the change to the G4 object underneath then the G4 element will be recreated. +In any case a updated singal is emitted and the related element that use that solid is updated ( for instance the scene ). \ No newline at end of file diff --git a/src/HEP/Geant/CMakeLists.txt b/src/HEP/Geant/CMakeLists.txt index 7ad33ec..874483b 100644 --- a/src/HEP/Geant/CMakeLists.txt +++ b/src/HEP/Geant/CMakeLists.txt @@ -27,6 +27,8 @@ set(SOURCES Scene.cpp Solid.cpp EmitterPrimary.cpp + Matter.cpp + GeantRegistration.cpp DetectorConstruction.cpp PhysicsList.cpp ActionInitialization.cpp diff --git a/src/HEP/Geant/GeantRegistration.cpp b/src/HEP/Geant/GeantRegistration.cpp new file mode 100644 index 0000000..b148d3d --- /dev/null +++ b/src/HEP/Geant/GeantRegistration.cpp @@ -0,0 +1,22 @@ +#include "Core/ObjectFactory.h" +#include "HEP/Geant/Matter.h" +#include "HEP/Geant/Solid.h" +#include "HEP/Geant/Scene.h" +#include "HEP/Geant/EmitterPrimary.hh" +#include "HEP/Geant/GeantEvent.h" + +namespace uLib { +namespace Geant { + +ULIB_REGISTER_OBJECT(Material) +ULIB_REGISTER_OBJECT(Solid) +ULIB_REGISTER_OBJECT(TessellatedSolid) +ULIB_REGISTER_OBJECT(BoxSolid) +ULIB_REGISTER_OBJECT(Scene) +ULIB_REGISTER_OBJECT(SkyPlaneEmitterPrimary) +ULIB_REGISTER_OBJECT(CylinderEmitterPrimary) +ULIB_REGISTER_OBJECT(QuadMeshEmitterPrimary) +ULIB_REGISTER_OBJECT(GeantEvent) + +} // namespace Geant +} // namespace uLib diff --git a/src/HEP/Geant/Matter.cpp b/src/HEP/Geant/Matter.cpp new file mode 100644 index 0000000..f40345b --- /dev/null +++ b/src/HEP/Geant/Matter.cpp @@ -0,0 +1,21 @@ + +#include "HEP/Geant/Matter.h" +#include +#include + +using namespace uLib::Geant; + +Material::Material() : m_G4Data(nullptr) {} + +Material::Material(const char *name) : m_G4Data(nullptr) { + this->SetFromNist(name); +} + +Material::~Material() { + if(m_G4Data) delete m_G4Data; +} + +void Material::SetFromNist(const char *name) { + G4NistManager* man = G4NistManager::Instance(); + m_G4Data = man->FindOrBuildMaterial(name); +} \ No newline at end of file diff --git a/src/HEP/Geant/Matter.h b/src/HEP/Geant/Matter.h index 64e35d0..1df6573 100644 --- a/src/HEP/Geant/Matter.h +++ b/src/HEP/Geant/Matter.h @@ -29,9 +29,10 @@ #define MATTER_H #include "Core/Object.h" +#include +#include class G4Element; -class G4Material; namespace uLib { namespace Geant { @@ -55,19 +56,55 @@ private: //// MATERIAL ////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// +// TODO: finish from G4NistMaterialBuilder class Material : public Object { public: + enum State { + Undefined = 0, + Solid, + Liquid, + Gas + }; + virtual const char* GetClassName() const override { return "Geant.Material"; } - uLibRefMacro(G4Data,G4Material *) + Material(); + Material(const char *name); + ~Material(); + + void SetFromNist(const char *name); + + template + void serialize(Ar &ar) { + ar & HRP("name", m_G4Data->GetName()); + ar & HRP("density", m_G4Data->GetDensity()); + ar & serialization::make_hrp_enum("state", m_G4Data->GetState(), {"Undefined", "Solid", "Liquid", "Gas"}); + } + + G4Material *GetG4Material() { return m_G4Data; } + private: G4Material *m_G4Data; + }; +// class MaterialCompound : public Material { +// public: - +// MaterialCompound(const char *name) {} + +// void AddMaterial(Material *m, double fractionmass) { m_Materials.push_back(std::make_pair(m, fractionmass)); } +// void AddElement(Element *e, double fractionmass) { m_Elements.push_back(std::make_pair(e, fractionmass)); } +// void SetDensity(double density) { m_Density = density; } + +// private: +// std::vector> m_Materials; +// std::vector> m_Elements; +// double m_Density; +// }; + } } diff --git a/src/HEP/Geant/Solid.cpp b/src/HEP/Geant/Solid.cpp index 54456a3..16a99ee 100644 --- a/src/HEP/Geant/Solid.cpp +++ b/src/HEP/Geant/Solid.cpp @@ -146,6 +146,9 @@ void Solid::SetParent(Solid *parent) { +TessellatedSolid::TessellatedSolid() + : BaseClass("unnamed_tessellated"), m_Solid(new G4TessellatedSolid("unnamed_tessellated")) {} + TessellatedSolid::TessellatedSolid(const char *name) : BaseClass(name), m_Solid(new G4TessellatedSolid(name)) { } @@ -173,9 +176,15 @@ void TessellatedSolid::Update() { +BoxSolid::BoxSolid(const char *name) : + BaseClass(name), + m_ContainerBox(new ContainerBox()), + m_Solid(new G4Box(name, 1, 1, 1)) + {} + BoxSolid::BoxSolid(const char *name, ContainerBox *box) : BaseClass(name) { - m_Solid = new G4Box(name, 1,1,1); - m_Object = box; + m_Solid = new G4Box(name, 1, 1, 1); + m_ContainerBox = box; Object::connect(box, &ContainerBox::Updated, this, &BoxSolid::Update); if (m_Logical) { m_Logical->SetSolid(m_Solid); @@ -184,27 +193,30 @@ BoxSolid::BoxSolid(const char *name, ContainerBox *box) : BaseClass(name) { } void BoxSolid::Update() { - if (m_Object) { - Vector3f size = m_Object->GetSize(); + if (m_ContainerBox) { + Vector3f size = m_ContainerBox->GetSize(); m_Solid->SetXHalfLength(size(0) * 0.5); m_Solid->SetYHalfLength(size(1) * 0.5); m_Solid->SetZHalfLength(size(2) * 0.5); // Geant4 placement is relative to center. uLib Box is anchored at corner. // 1. Get position and rotation (clean, without scale) - Vector3f pos = m_Object->GetPosition(); - Matrix3f rot = m_Object->GetRotation(); + Vector3f pos = m_ContainerBox->GetPosition(); + Matrix3f rot = m_ContainerBox->GetRotation(); // 2. Center = Corner + Rotation * (Half-Size) // We must rotate the offset vector because uLib box can be rotated. Vector3f center = pos + rot * (size * 0.5); - uLib::AffineTransform t; + uLib::AffineTransform t; t.SetPosition(center); t.SetRotation(rot); this->SetTransform(t.GetMatrix()); } + + + } diff --git a/src/HEP/Geant/Solid.h b/src/HEP/Geant/Solid.h index d05459b..82fac46 100644 --- a/src/HEP/Geant/Solid.h +++ b/src/HEP/Geant/Solid.h @@ -52,6 +52,7 @@ public: void SetNistMaterial(const char *name); void SetMaterial(G4Material *material); + void SetSizeUnit(const char *unit); // Implementiamo SetParent qui, per tutti. virtual void SetParent(Solid *parent); @@ -69,6 +70,14 @@ public: return m_Logical ? m_Logical->GetName().c_str() : m_Name.c_str(); } + template < typename Ar > + void serialize(Ar &ar, const unsigned int version) { + ar & m_Name; + } + + + + protected: std::string m_Name; @@ -89,6 +98,7 @@ public: virtual const char* GetClassName() const override { return "Geant.TessellatedSolid"; } + TessellatedSolid(); TessellatedSolid(const char *name); void SetMesh(TriangleMesh &mesh); uLibGetMacro(Solid, G4TessellatedSolid *) @@ -116,16 +126,23 @@ public: virtual const char* GetClassName() const override { return "Geant.BoxSolid"; } + BoxSolid(const char *name = ""); BoxSolid(const char *name, ContainerBox *box); virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; } - ContainerBox* GetObject() const { return m_Object; } + ContainerBox* GetObject() const { return m_ContainerBox; } + + template < typename Ar > + void serialize(Ar &ar, const unsigned int version) { + ar & boost::serialization::base_object(*this); + ar & m_ContainerBox; + } public slots: void Update(); private: - ContainerBox *m_Object; + ContainerBox *m_ContainerBox; G4Box *m_Solid; }; diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 106e054..1dcbef0 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -603,6 +603,8 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) } + + // ------------------------------------------------------ // // SERIALIZE DISPLAY PROPERTIES From 9118afdd13df2215c25aaed1438520e0e79b8f07 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Wed, 1 Apr 2026 19:59:37 +0000 Subject: [PATCH 16/24] fix serialization for properties unintrusive --- docs/archives.md | 84 +++++++++ src/Core/Archives.h | 164 ++++++----------- src/Core/Property.h | 24 ++- src/Core/Serializable.h | 5 + src/Core/testing/PropertiesTest.cpp | 171 ++++++++++++------ .../testing/SerializeDreadDiamondTest.cpp | 37 +++- src/Core/testing/SerializeTest.cpp | 9 +- 7 files changed, 312 insertions(+), 182 deletions(-) create mode 100644 docs/archives.md diff --git a/docs/archives.md b/docs/archives.md new file mode 100644 index 0000000..7e60bfc --- /dev/null +++ b/docs/archives.md @@ -0,0 +1,84 @@ +# Serialization and Archives Internals + +This document explains the internal design of the `uLib` serialization system, which is built on top of **Boost.Serialization**. It provides custom archive implementations for various formats (XML, Text, Logging) and introduces **Human Readable Pairs (HRP)** for metadata-rich serialization. + +--- + +## Architecture Overview + +The `uLib` archive system extends the standard `boost::archive` templates to add domain-specific features. The main components are: + +1. **Custom Interface Layer**: Extends the default Boost archive API with additional operators and utilities. +2. **Specialized Archive Implementations**: Specialized classes for XML, Text, and Logging. +3. **HRP Support**: First-class support for `hrp` (Human Readable Pair) wrappers, which carry units, ranges, and descriptions. +4. **Static Registration System**: Macros and explicit instantiations to handle polymorphic types and compilation isolation. + +--- + +## Custom Interface Layer + +All `uLib` archives use a custom interface defined in `Archives.h` via `uLib_interface_iarchive` and `uLib_interface_oarchive`. These templates add several key features: + +| Feature | Operator/Method | Description | +|---|---|---| +| **Mapping Operator** | `operator==` | Aliased to `operator&` (Boost's standard mapping operator). | +| **Trace Operator** | `operator!=` | Used for trace/debug output of strings during serialization. | +| **Type Registration** | `register_type()` | Registers a class type with the archive's internal serializer map. | +| **Standard IO** | `operator<<` / `operator>>` | Standard redirect for saving and loading. | + +These interfaces are applied to the archives using template specialization of `boost::archive::detail::interface_iarchive` and `interface_oarchive`. + +--- + +## Archive Variants + +### XML Archives (`xml_iarchive`, `xml_oarchive`) +These inherit from `boost::archive::xml_iarchive_impl` and `xml_oarchive_impl`. +- **Internals**: They override `load_override` and `save_override` to handle `boost::serialization::hrp` specifically. +- **XML Mapping**: When saving an `hrp`, it uses `save_start(name)` and `save_end(name)` to wrap the value in a named XML tag. + +### Text Archives (`text_iarchive`, `text_oarchive`) +Standard text-based archives used for compact serialization. They use `StringReader` to consume decorative text markers during loading. + +### Human Readable Text (`hrt_iarchive`, `hrt_oarchive`) +These are "naked" text archives that suppress most of Boost's internal metadata (object IDs, class IDs, versions). +- **Goal**: Produce text output that is easy for humans to read and edit. +- **Internals**: All overrides for Boost internal types (like `object_id_type`, `version_type`, etc.) are implemented as no-ops. + +### Log Archive (`log_archive`) +An XML-based output archive specifically for debug logging. +- **Internals**: It forces every object into a Name-Value Pair (NVP) even if not provided by the user, and strips all technical metadata to keep the logs clean. + +--- + +## HRP (Human Readable Pair) Integration + +`hrp` is a core `uLib` wrapper (defined in `Serializable.h`) that extends Boost's `nvp`: + +```cpp +// Example of HRP usage +ar & HRP2("Energy", m_energy, "MeV").range(0, 100); +``` + +### Internal Handling in Archives +Archives in `Archives.h` provide specific `save_override`/`load_override` for `hrp`: +- **XML**: Maps the `name()` to an XML tag. +- **HRT**: Formats as `name: value [units]\n`. +- **Log**: Converts it to a standard Boost `nvp` for consistent XML logging. + +--- + +## Registration and Polymorphism + +### Registration Macro +The `ULIB_SERIALIZATION_REGISTER_ARCHIVE(Archive)` macro is crucial for polymorphic serialization. It instantiates the necessary template machinery to link the custom `Archive` type with any `Serializable` class exported via `BOOST_CLASS_EXPORT`. + +### Explicit Instantiation +To reduce compilation times and provide a single point of failure for link-time issues, `uLib` uses explicit instantiations in `src/Core/Archives.cpp`. This file includes the `.ipp` implementation files from Boost and instantiates the `archive_serializer_map` and implementation classes for all `uLib` archive types. + +--- + +## Utility: StringReader +The `StringReader` utility is used internally by text-based archives to parse and skip literals. For example: +- When loading a string literal from a text archive, `StringReader` consumes whitespace and ensures the stream matches the expected string, failing if there is a mismatch. +- This is vital for maintaining the structure of human-readable formats. diff --git a/src/Core/Archives.h b/src/Core/Archives.h index 1d40396..d2bb76b 100644 --- a/src/Core/Archives.h +++ b/src/Core/Archives.h @@ -59,6 +59,8 @@ class xml_iarchive; class xml_oarchive; class text_iarchive; class text_oarchive; +class hrt_iarchive; +class hrt_oarchive; class log_archive; } // namespace Archive @@ -150,7 +152,7 @@ public: Archive *This() { return static_cast(this); } template - const basic_pointer_iserializer *register_type(T * = NULL) { + const basic_pointer_iserializer *register_type(T * = nullptr) { const basic_pointer_iserializer &bpis = boost::serialization::singleton< pointer_iserializer>::get_const_instance(); this->This()->register_basic_serializer(bpis.get_basic_serializer()); @@ -161,15 +163,36 @@ public: return *this->This(); } + template + Archive &operator>>(const boost::serialization::nvp &t) { + this->This()->load_override(t); + return *this->This(); + } + + template + Archive &operator>>(const boost::serialization::hrp &t) { + this->This()->load_override(const_cast &>(t)); + return *this->This(); + } + // the & operator template Archive &operator&(T &t) { return *(this->This()) >> t; } + template + Archive &operator&(const boost::serialization::nvp &t) { + return *(this->This()) >> t; + } + + template + Archive &operator&(const boost::serialization::hrp &t) { + return *(this->This()) >> t; + } + // the == operator template Archive &operator==(T &t) { return this->operator&(t); } // the != operator for human readable access template Archive &operator!=(T &t) { - std::cerr << std::flush << "cauch string: " << t << "\n"; // REMOVE THIS ! return *this->This(); } }; @@ -191,7 +214,7 @@ public: Archive *This() { return static_cast(this); } template - const basic_pointer_oserializer *register_type(const T * = NULL) { + const basic_pointer_oserializer *register_type(const T * = nullptr) { const basic_pointer_oserializer &bpos = boost::serialization::singleton< pointer_oserializer>::get_const_instance(); this->This()->register_basic_serializer(bpos.get_basic_serializer()); @@ -215,7 +238,6 @@ public: // the != operator for human readable access template Archive &operator!=(T &t) { - std::cerr << std::flush << "cauch string: " << t << "\n"; // REMOVE THIS ! return *this->This(); } }; @@ -240,23 +262,22 @@ template <> class interface_oarchive : public uLib_interface_oarchive {}; +template <> +class interface_iarchive + : public uLib_interface_iarchive {}; + +template <> +class interface_oarchive + : public uLib_interface_oarchive {}; + +template <> +class interface_iarchive + : public uLib_interface_iarchive {}; + template <> class interface_oarchive : public uLib_interface_oarchive {}; -//// Veritical repetition macro // FINIRE !!!!!!!!!!!!!!!!!!!!!!!!! -// #define _DECL_INTERFACE_ARCHIVE_V(vz,vn,vdata) \ -// template \ -// struct inherit_nofold : \ -// BOOST_PP_REPEAT(BOOST_PP_INC(vn),_INERIT_NOFOLD_H,~) \ -// {}; - -//// Multiple size declaration // -// BOOST_PP_REPEAT(ULIB_CFG_MPL_INERIT_NOFOLD_MAXSIZE,_INERIT_NOFOLD_V,~) - -// #undef _INERIT_NOFOLD_H -// #undef _INERIT_NOFOLD_V - } // namespace detail } // namespace archive } // namespace boost @@ -275,36 +296,6 @@ class interface_oarchive namespace boost { namespace archive { -// template -// inline void load_const_override(Archive & ar, const char *t ){ -// typedef typename mpl::identity -// >::type typex; typex::invoke(ar, t); -// } - -// template -// inline void load(Archive & ar, T &t){ -// // if this assertion trips. It means we're trying to load a -// // const object with a compiler that doesn't have correct -// // funtion template ordering. On other compilers, this is -// // handled below. -// // detail::check_const_loading< T >(); -// typedef -// BOOST_DEDUCED_TYPENAME mpl::eval_if, -// mpl::identity > -// ,//else -// BOOST_DEDUCED_TYPENAME mpl::eval_if, -// mpl::identity > -// ,//else -// BOOST_DEDUCED_TYPENAME mpl::eval_if, -// mpl::identity > -// ,//else -// mpl::identity > -// > -// > -// >::type typex; -// typex::invoke(ar, t); -// } - } // namespace archive } // namespace boost @@ -312,22 +303,6 @@ namespace uLib { namespace Archive { -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// POLYMORPHIC // - -// class polymorphic_iarchive : -// public boost::archive::polymorphic_iarchive { - -// public: -// void load_override(const char *t, BOOST_PFTO int) -// { -// boost::archive::load_const_override(* this->This(), -// const_cast(t)); -// } - -//}; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -369,17 +344,13 @@ public: using base::load_override; void load_override(const char *str) { - // StringReader sr(basic_text_iprimitive::is); - // sr >> str; + // StringReader sr(basic_text_iprimitive::is); + // sr >> str; } - ~xml_iarchive() {}; + virtual ~xml_iarchive() {} }; -// typedef boost::archive::detail::polymorphic_iarchive_route< -// boost::archive::xml_iarchive_impl -//> polymorphic_xml_iarchive; - template struct polymorphic_iarchive_route : boost::archive::detail::polymorphic_iarchive_route { @@ -389,12 +360,8 @@ struct polymorphic_iarchive_route class polymorphic_xml_iarchive : public polymorphic_iarchive_route< boost::archive::xml_iarchive_impl> { - // give serialization implementation access to this class - // friend class boost::archive::detail::interface_iarchive; - // friend class boost::archive::basic_xml_iarchive; - // friend class boost::archive::load_access; public: - virtual void load_override(const char *str) { ; } + virtual void load_override(const char *str) {} }; class xml_oarchive : public boost::archive::xml_oarchive_impl { @@ -410,11 +377,6 @@ public: xml_oarchive(std::ostream &os, unsigned int flags = 0) : boost::archive::xml_oarchive_impl(os, flags) {} - // example of implementing save_override for const char* // - // void save_override(const char *t, int) { - // std::cout << "found char: " << t << "\n"; - // } - using basic_xml_oarchive::save_override; // special treatment for name-value pairs. @@ -433,10 +395,10 @@ public: void save_override(const char *str) { // Do not save any human decoration string // - // basic_text_oprimitive::save(str); + // basic_text_oprimitive::save(str); } - ~xml_oarchive() {} + virtual ~xml_oarchive() {} }; // typedef boost::archive::detail::polymorphic_oarchive_route< @@ -471,15 +433,11 @@ public: sr >> str; } - ~text_iarchive() {}; + virtual ~text_iarchive() {} }; typedef text_iarchive naked_text_iarchive; -// typedef boost::archive::detail::polymorphic_iarchive_route< -// naked_text_iarchive -//> polymorphic_text_iarchive; - class text_oarchive : public boost::archive::text_oarchive_impl { typedef text_oarchive Archive; typedef boost::archive::text_oarchive_impl base; @@ -497,13 +455,9 @@ public: void save_override(const char *str) { basic_text_oprimitive::save(str); } - ~text_oarchive() {} + virtual ~text_oarchive() {} }; -// typedef boost::archive::detail::polymorphic_oarchive_route< -// boost::archive::text_oarchive_impl -//> polymorphic_text_oarchive; - //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -540,7 +494,7 @@ public: sr >> str; } - ~hrt_iarchive() {}; + virtual ~hrt_iarchive() {} }; class hrt_oarchive : public boost::archive::text_oarchive_impl { @@ -576,7 +530,7 @@ public: *this << "\n"; } - ~hrt_oarchive() {} + virtual ~hrt_oarchive() {} }; //////////////////////////////////////////////////////////////////////////////// @@ -605,17 +559,9 @@ public: } template void save_override(const T &t) { - base::save_override(boost::serialization::make_nvp(NULL, t)); + base::save_override(boost::serialization::make_nvp(nullptr, t)); } - // activate this if you want to trap non nvp objects // - // template - // void save_override(T & t) - // { - // BOOST_MPL_ASSERT((boost::serialization::is_wrapper< T >)); - // // this->detail_common_oarchive::save_override(t); - // } - template void save_override(const boost::serialization::nvp &t) { base::save_override(t); } @@ -640,11 +586,9 @@ public: log_archive(std::ostream &os, unsigned int flags = 0) : boost::archive::xml_oarchive_impl( os, flags | boost::archive::no_header) {} -}; -// typedef boost::archive::detail::polymorphic_oarchive_route< -// boost::archive::xml_oarchive_impl -//> polymorphic_log_archive; + virtual ~log_archive() {} +}; } // namespace Archive @@ -658,10 +602,4 @@ ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::hrt_iarchive) ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::hrt_oarchive) ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::log_archive) -// ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::polymorphic_xml_iarchive) -// ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::polymorphic_xml_oarchive) -// ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::polymorphic_text_iarchive) -// ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::polymorphic_text_oarchive) -// ULIB_SERIALIZATION_REGISTER_ARCHIVE(uLib::Archive::polymorphic_log_archive) - #endif // U_CORE_ARCHIVES_H diff --git a/src/Core/Property.h b/src/Core/Property.h index 7f35875..e932a0b 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -63,6 +63,13 @@ public: virtual void serialize(Archive::log_archive & ar, const unsigned int version) = 0; }; + + + + + + + /** * @brief Template class for typed properties. */ @@ -226,7 +233,8 @@ typedef Property LongProperty; typedef Property ULongProperty; typedef Property FloatProperty; typedef Property DoubleProperty; -typedef Property BoolProperty; +typedef Property BoolProperty; + /** * @brief Property specialized for enumerations, providing labels for GUI representations. @@ -244,6 +252,16 @@ private: std::vector m_Labels; }; + + + + + + + + + + /** * @brief Macro to simplify property declaration within a class. * Usage: ULIB_PROPERTY(float, Width, 1.0f) @@ -386,12 +404,14 @@ private: std::vector m_GroupStack; }; + + /** * @brief Convenience macro to automatically activate and register all HRP members * as uLib properties. Usage: ULIB_ACTIVATE_PROPERTIES(obj) */ #define ULIB_ACTIVATE_PROPERTIES(obj) \ - { uLib::Archive::property_register_archive _ar_tmp(&(obj)); (obj).serialize(_ar_tmp, 0); } + { uLib::Archive::property_register_archive _ar_tmp(&(obj)); _ar_tmp & (obj); } } // namespace Archive } // namespace uLib diff --git a/src/Core/Serializable.h b/src/Core/Serializable.h index b08542b..ec5996e 100644 --- a/src/Core/Serializable.h +++ b/src/Core/Serializable.h @@ -440,6 +440,11 @@ using boost::serialization::make_hrp_enum; template \ void _Ob::save_override(ArchiveT &ar, const unsigned int version) + + + + + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Core/testing/PropertiesTest.cpp b/src/Core/testing/PropertiesTest.cpp index 23e4c4e..9b1bdb7 100644 --- a/src/Core/testing/PropertiesTest.cpp +++ b/src/Core/testing/PropertiesTest.cpp @@ -23,74 +23,137 @@ //////////////////////////////////////////////////////////////////////////////*/ - - - #include - -#include - +#include +#include #include "Core/Object.h" +#include "Core/Property.h" +#include "Core/Archives.h" +#include "Core/Serializable.h" #include "testing-prototype.h" -#define emit - - - - -template -class property -{ - typedef boost::signals2::signal& )> signal_t; +using namespace uLib; +/** + * @brief A test class to demonstrate property registration via SERIALIZE_OBJECT. + */ +class TestObject : public Object { public: - property() : m_changed(new signal_t) {} - property(const T in) : value(in) , m_changed(new signal_t) {} + uLibTypeMacro(TestObject, Object) - inline operator T const & () const { return value; } - inline operator T & () { return value; } - inline T & operator = (const T &i) { value = i; return value; } - template T2 & operator = (const T2 &i) { T2 &guard = value; } // Assign exact identical types only. - inline signal_t & valueChanged() { return *m_changed; } + TestObject() : m_Value(10.5f), m_Status("Initialized"), m_Counter(0) {} + + float m_Value; + std::string m_Status; + int m_Counter; + + // Static properties (registered in constructor/initializer) + ULIB_PROPERTY(int, StaticProp, 42) + + ULIB_SERIALIZE_ACCESS + + template + void serialize(Ar& ar, unsigned int version) { + ar & HRP("value", m_Value, "mm").range(0, 100).set_default(1.); + ar & HRP("status", m_Status); + ar & HRP("counter", m_Counter); + } -private: - T value; - boost::shared_ptr m_changed; }; -//template -//class property { -// typedef boost::signals2::signal signal_t; +class TestObject2 : public TestObject { +public: + uLibTypeMacro(TestObject2, TestObject) -//public: -// property() : m_changed() {} -// property(const T in) : value(in) , m_changed() {} + TestObject2() : TestObject(), m_Value2(20.5f) {} -// inline operator T const & () const { return value; } -// inline operator T & () { valueChanged()(value); return value; } -// inline T & operator = (const T &i) { value = i; valueChanged()(value); return value; } -// template T2 & operator = (const T2 &i) { T2 &guard = value; } // Assign exact identical types only. -// inline signal_t &valueChanged() { return m_changed; } - -//private: -// property(const property &); -// property &operator = (const property&); - -// T value; -// signal_t m_changed; -//}; - -// test generic void function slot // -void PrintSlot(const property &i) { std::cout << "slot called, new value = " << i << "!\n"; } - - - - - -int main() -{ + float m_Value2; + ULIB_SERIALIZE_ACCESS +}; +ULIB_SERIALIZABLE_OBJECT(TestObject2) +ULIB_SERIALIZE_OBJECT(TestObject2, TestObject) { + // std::cout << "Serializing TestObject2" << std::endl; + ar & boost::serialization::make_hrp("value2", ob.m_Value2, "mm").set_default(1.); +} + + +int main() { + BEGIN_TESTING(Properties Serialization) + + TestObject obj; + + // 1. Initial state: check static property + ASSERT_EQUAL(obj.StaticProp, 42); + + // 2. Activate dynamic properties via the property_register_archive + // This calls the serialize method with a special archive that populates m_DynamicProperties + ULIB_ACTIVATE_PROPERTIES(obj); + + const auto& props = obj.GetProperties(); + // This is problematic because GetProperties currently returns d->m_Properties (only static) + + // For now, let's just assert on the dynamic property presence if possible + PropertyBase* pVal = obj.GetProperty("value"); + ASSERT_NOT_NULL(pVal); + ASSERT_EQUAL(pVal->GetValueAsString(), "10.5"); + ASSERT_EQUAL(pVal->GetUnits(), "mm"); + + // Check other dynamic properties + ASSERT_NOT_NULL(obj.GetProperty("status")); + ASSERT_NOT_NULL(obj.GetProperty("counter")); + + // 4. Serialization round-trip (XML) + { + std::ofstream ofs("test_props.xml"); + Archive::xml_oarchive(ofs) << NVP("test_obj", obj); + } + + TestObject obj2; + obj2.m_Value = 0; + obj2.m_Status = ""; + { + std::ifstream ifs("test_props.xml"); + Archive::xml_iarchive(ifs) >> NVP("test_obj", obj2); + } + + ASSERT_EQUAL(obj2.m_Value, 10.5f); + ASSERT_EQUAL(obj2.m_Status, "Initialized"); + + TestObject2 obj3; + obj3.m_Value = 12.5; + obj3.m_Status = "Initialized"; + obj3.m_Value2 = 22.5; + + ULIB_ACTIVATE_PROPERTIES(obj3); + + PropertyBase* pVal3 = obj3.GetProperty("value2"); + ASSERT_NOT_NULL(pVal3); + ASSERT_EQUAL(pVal3->GetValueAsString(), "22.5"); + ASSERT_EQUAL(pVal3->GetUnits(), "mm"); + + // 5. Serialization round-trip (XML) + { + std::ofstream ofs("test_props2.xml"); + Archive::xml_oarchive(ofs) << NVP("test_obj2", obj3); + } + + TestObject2 obj4; + obj4.m_Value = 0; + obj4.m_Status = ""; + obj4.m_Value2 = 0; + ULIB_ACTIVATE_PROPERTIES(obj4); + { + std::ifstream ifs("test_props2.xml"); + Archive::xml_iarchive(ifs) >> NVP("test_obj2", obj4); + } + ASSERT_EQUAL(obj4.m_Value, 12.5f); + ASSERT_EQUAL(obj4.m_Status, "Initialized"); + ASSERT_EQUAL(obj4.m_Value2, 22.5f); + + + END_TESTING } diff --git a/src/Core/testing/SerializeDreadDiamondTest.cpp b/src/Core/testing/SerializeDreadDiamondTest.cpp index 2e455e0..37407bc 100644 --- a/src/Core/testing/SerializeDreadDiamondTest.cpp +++ b/src/Core/testing/SerializeDreadDiamondTest.cpp @@ -40,7 +40,7 @@ struct A : Object { }; ULIB_SERIALIZABLE_OBJECT(A) -ULIB_SERIALIZE_OBJECT(A, Object) { ar &AR(numa); } +ULIB_SERIALIZE_OBJECT(A, Object) { ar & AR(numa); } struct B : virtual Object { uLibTypeMacro(B, Object) B() : numb(5552369) {} @@ -48,7 +48,7 @@ struct B : virtual Object { }; ULIB_SERIALIZABLE_OBJECT(B) -ULIB_SERIALIZE_OBJECT(B, Object) { ar &AR(numb); } +ULIB_SERIALIZE_OBJECT(B, Object) { ar & AR(numb); } struct C : B { uLibTypeMacro(C, B) C() : numc(5552370) {} @@ -56,7 +56,7 @@ struct C : B { }; ULIB_SERIALIZABLE_OBJECT(C) -ULIB_SERIALIZE_OBJECT(C, B) { ar &AR(numc); } +ULIB_SERIALIZE_OBJECT(C, B) { ar & AR(numc); } struct D : A, B { uLibTypeMacro(D, A, B) @@ -67,10 +67,33 @@ struct D : A, B { }; ULIB_SERIALIZABLE_OBJECT(D) -ULIB_SERIALIZE_OBJECT(D, A, B) { ar &AR(numd); } +ULIB_SERIALIZE_OBJECT(D, A, B) { ar & AR(numd); } int main() { - A o; - - Archive::xml_oarchive(std::cout) << NVP(o); + BEGIN_TESTING(DreadDiamond Serialization) + + D o; + C c; + c.numb = 123; + { + std::ofstream file("test.xml"); + Archive::xml_oarchive(file) << NVP("dd_test", o) << NVP("c", c); + } + { + D o2; + C c2; + std::ifstream file("test.xml"); + Archive::xml_iarchive(file) >> NVP("dd_test", o2) >> NVP("c", c2); + + // D // + ASSERT_EQUAL(o.numa, o2.numa); + ASSERT_EQUAL(o.numb, o2.numb); + ASSERT_EQUAL(o.numd, o2.numd); + + // C // + ASSERT_EQUAL(c.numb, c2.numb); + ASSERT_EQUAL(c.numc, c2.numc); + } + + END_TESTING } diff --git a/src/Core/testing/SerializeTest.cpp b/src/Core/testing/SerializeTest.cpp index 31a5bc0..aba950c 100644 --- a/src/Core/testing/SerializeTest.cpp +++ b/src/Core/testing/SerializeTest.cpp @@ -143,22 +143,19 @@ int testing_hrt_class() { } a.a() = 0; a.p_a = "zero string"; - { - // ERRORE FIX ! - // std::ifstream file("test.xml"); - // Archive::hrt_iarchive(file) >> NVP(a); - } Archive::hrt_oarchive(std::cout) << NVP(a); return (a.a() == 5552368 && a.p_a == "A property string"); } + + int main() { BEGIN_TESTING(Serialize Test); TEST1(test_V3f()); TEST1(testing_xml_class()); - // testing_hrt_class(); ///// << ERRORE in HRT with properties + // TEST1(testing_hrt_class()); END_TESTING; } From a1c5fc26000572740b9d8e2402b4dfde2f4f5393 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Wed, 1 Apr 2026 20:30:21 +0000 Subject: [PATCH 17/24] refactor: remove Math library build configuration and add support for BoxSolid in vtkObjectsContext --- src/Math/Makefile.am | 42 ----------------------------------- src/Vtk/vtkObjectsContext.cpp | 5 +++++ 2 files changed, 5 insertions(+), 42 deletions(-) delete mode 100644 src/Math/Makefile.am diff --git a/src/Math/Makefile.am b/src/Math/Makefile.am deleted file mode 100644 index e8add8c..0000000 --- a/src/Math/Makefile.am +++ /dev/null @@ -1,42 +0,0 @@ -SUBDIRS = . - -include $(top_srcdir)/Common.am - -library_includedir = $(includedir)/libmutom-${PACKAGE_VERSION}/Math -library_include_HEADERS = ContainerBox.h \ - Dense.h \ - Geometry.h \ - Transform.h \ - StructuredData.h\ - StructuredGrid.h\ - VoxImage.h \ - VoxRaytracer.h \ - Utils.h \ - VoxImageFilter.h\ - VoxImageFilter.hpp \ - VoxImageFilterLinear.hpp \ - VoxImageFilterMedian.hpp \ - VoxImageFilterABTrim.hpp \ - VoxImageFilterBilateral.hpp \ - VoxImageFilterThreshold.hpp \ - VoxImageFilter2ndStat.hpp \ - VoxImageFilterCustom.hpp \ - Accumulator.h \ - TriangleMesh.h - - -_MATH_SOURCES = \ - VoxRaytracer.cpp \ - StructuredData.cpp \ - StructuredGrid.cpp \ - VoxImage.cpp \ - TriangleMesh.cpp \ - Dense.cpp - - - - -noinst_LTLIBRARIES = libmutommath.la -libmutommath_la_SOURCES = ${_MATH_SOURCES} - - diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 4894890..98daba8 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -3,7 +3,9 @@ #include "Vtk/Math/vtkCylinder.h" #include "Vtk/Math/vtkAssembly.h" #include "Vtk/Math/vtkVoxImage.h" + #include "HEP/Detectors/vtkDetectorChamber.h" +#include "HEP/Geant/vtkBoxSolid.h" #include #include @@ -127,6 +129,9 @@ Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { } else if (auto* assembly = dynamic_cast(obj)) { return new Assembly(assembly); } + else if (auto* box = dynamic_cast(obj)) { + return new vtkBoxSolid(box); + } // Fallback if we don't know the exact class but it might be a context itself if (auto subCtx = dynamic_cast(obj)) { From 443577648475afd1947b3b05fa594c7ea2c6edca Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 10:33:14 +0000 Subject: [PATCH 18/24] refactor: standardize object type identification using uLibTypeMacro and update serialization macros --- .gitignore | 2 + CMakeLists.txt | 2 +- docs/update_properties.md | 73 +++++++++++++++++++++++++ src/Core/Archives.h | 43 +++++++++++++-- src/Core/CMakeLists.txt | 1 + src/Core/Object.cpp | 1 + src/Core/Object.h | 3 +- src/Core/ObjectsContext.h | 2 +- src/Core/Property.h | 36 +++++++++--- src/Core/Serializable.h | 33 +++++++---- src/Core/testing/PropertiesTest.cpp | 5 +- src/Core/testing/PropertySystemTest.cpp | 3 +- src/Core/testing/PropertyTypesTest.cpp | 3 +- src/HEP/Detectors/DetectorChamber.h | 6 +- src/HEP/Geant/EmitterPrimary.hh | 12 ++-- src/HEP/Geant/GeantEvent.h | 3 +- src/HEP/Geant/Matter.h | 3 +- src/HEP/Geant/Scene.h | 3 +- src/HEP/Geant/Solid.h | 13 ++--- src/Math/Assembly.cpp | 1 - src/Math/Assembly.h | 4 +- src/Math/ContainerBox.h | 25 ++++----- src/Math/Cylinder.h | 15 +++-- src/Math/Geometry.h | 10 ++-- src/Math/Polydata.h | 2 +- src/Math/QuadMesh.h | 2 +- src/Math/Transform.h | 12 +++- src/Math/TriangleMesh.h | 2 +- src/Math/VoxImage.h | 2 +- src/Math/VoxImageFilter.h | 2 +- src/Vtk/Math/vtkAssembly.h | 2 +- src/Vtk/vtkObjectsContext.h | 2 +- 32 files changed, 229 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index f51005d..05ed939 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ src/Python/uLib/*.pyd src/Python/uLib/*.pyc src/Python/uLib/__pycache__ src/Python/uLib/.nfs* +test_props.xml +test_props2.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 59eb58f..3b61e85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ endif() # The version number. set(PROJECT_VERSION_MAJOR 0) -set(PROJECT_VERSION_MINOR 6) +set(PROJECT_VERSION_MINOR 7) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") set(PROJECT_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") diff --git a/docs/update_properties.md b/docs/update_properties.md index 15c1790..219a420 100644 --- a/docs/update_properties.md +++ b/docs/update_properties.md @@ -19,3 +19,76 @@ The vtkHandlerWidget should handle the transformation of the puppet internal Con + + + +## ACTIVATE PROPERTIES + +ULIB_ACTIVATE_PROPERTIES must run after all member initialization, with the vtable pointing to the most-derived type. This is why it has to be in each constructor — in C++, virtual dispatch only works correctly after a class's vtable is installed, which happens at the start of each level's constructor body. + +### Option 1 — End-of-class macro (no constructor boilerplate) +Declare a private member activator as the last member of the class. Its constructor runs after all other members, and at that point the vtable is already Derived's: + + +// In Property.h, add alongside ULIB_ACTIVATE_PROPERTIES: +#define ULIB_DECLARE_PROPERTIES(SelfType) \ +private: \ + struct _PropActivator { \ + _PropActivator(SelfType* self) { \ + uLib::Archive::property_register_archive ar(self); \ + ar & *self; \ + } \ + } _prop_activator{this}; +Usage in ContainerBox.h — place it just before the closing brace: + + +class ContainerBox : public TRS { +public: + // ... all constructors, no more ULIB_ACTIVATE_PROPERTIES(*this) + + ULIB_DECLARE_PROPERTIES(ContainerBox) // ← replaces all 3 constructor calls +}; +Tradeoff: Works perfectly for single-level classes. For hierarchies where multiple levels use the macro, RegisterDynamicProperty must deduplicate by name (skip if already registered). Requires one line per class in the class body, but zero lines in constructors. + +### Option 2 — Lazy init via virtual InitProperties() in Object +Modify Object to call a virtual hook on first GetProperties(): + + +// In Object.h: +class Object { +protected: + virtual void InitProperties() {} // override in derived +public: + const std::vector& GetProperties() const { + if (!m_propertiesInitialized) { + const_cast(this)->m_propertiesInitialized = true; + const_cast(this)->InitProperties(); + } + return m_properties; + } +}; +Then a CRTP base handles the rest without any macro: + + +template +class PropertyObject : public Object { +protected: + void InitProperties() override { + uLib::Archive::property_register_archive ar(this); + ar & *static_cast(this); + } +}; +Usage — just change the base class: + + +class ContainerBox : public PropertyObject, public TRS { ... }; +// Nothing else needed — properties activated on first GetProperties() call +Tradeoff: Most "automatic" — pure inheritance, no constructor or class-body macros. But requires modifying Object (adding m_propertiesInitialized flag + virtual hook), and lazy init means properties aren't available until first access. Also doesn't work well with multiple inheritance (which TRS likely involves). + +Option 3 — CRTP doesn't work from the base constructor +Just to be explicit: a CRTP base that calls ULIB_ACTIVATE_PROPERTIES in its own constructor won't work, because when PropertyObject's constructor runs, the vtable is PropertyObject's — Derived::serialize() hasn't been installed yet. So ar & *self calls Object::serialize() (a no-op). + +Recommendation +Option 1 is the least invasive and safest. Add deduplication to RegisterDynamicProperty in Object.cpp to guard against re-registration when hierarchies stack activators, then replace every ULIB_ACTIVATE_PROPERTIES(*this) in constructors with a single ULIB_DECLARE_PROPERTIES(ClassName) at the end of the class body. + +Option 2 is cleaner to use but requires changing the Object interface and has the lazy-init semantic change — only worth it if you want zero-touch activation across the entire framework. \ No newline at end of file diff --git a/src/Core/Archives.h b/src/Core/Archives.h index d2bb76b..5a6348a 100644 --- a/src/Core/Archives.h +++ b/src/Core/Archives.h @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include @@ -309,18 +311,32 @@ namespace Archive { //////////////////////////////////////////////////////////////////////////////// // XML // +// ULIB_SERIALIZATION_VERSION should be get from the build system +#ifndef ULIB_SERIALIZATION_VERSION +#define ULIB_SERIALIZATION_VERSION "0.0" +#endif + class xml_iarchive : public boost::archive::xml_iarchive_impl { typedef xml_iarchive Archive; typedef boost::archive::xml_iarchive_impl base; + unsigned int m_flags; + // give serialization implementation access to this class friend class boost::archive::detail::interface_iarchive; friend class boost::archive::basic_xml_iarchive; friend class boost::archive::load_access; - public: xml_iarchive(std::istream &is, unsigned int flags = 0) - : xml_iarchive_impl(is, flags) {} + : boost::archive::xml_iarchive_impl( + is, flags | boost::archive::no_header), m_flags(flags) { + if (0 == (flags & boost::archive::no_header)) { + std::string line; + std::getline(is, line); // + std::getline(is, line); // + std::getline(is, line); // + } + } using basic_xml_iarchive::load_override; @@ -368,14 +384,31 @@ class xml_oarchive : public boost::archive::xml_oarchive_impl { typedef xml_oarchive Archive; typedef boost::archive::xml_oarchive_impl base; + unsigned int m_flags; + // give serialization implementation access to this class friend class boost::archive::detail::interface_oarchive; friend class boost::archive::basic_xml_oarchive; friend class boost::archive::save_access; - public: xml_oarchive(std::ostream &os, unsigned int flags = 0) - : boost::archive::xml_oarchive_impl(os, flags) {} + : boost::archive::xml_oarchive_impl( + os, flags | boost::archive::no_header), m_flags(flags) { + if (0 == (flags & boost::archive::no_header)) { + this->This()->put( + "\n"); + this->This()->put("\n"); + this->This()->put("write_attribute("version", (const char *)ULIB_SERIALIZATION_VERSION); + this->This()->put(">\n"); + } + } + + virtual ~xml_oarchive() { + if (0 == (m_flags & boost::archive::no_header)) { + this->This()->put("\n"); + } + } using basic_xml_oarchive::save_override; @@ -397,8 +430,6 @@ public: // Do not save any human decoration string // // basic_text_oprimitive::save(str); } - - virtual ~xml_oarchive() {} }; // typedef boost::archive::detail::polymorphic_oarchive_route< diff --git a/src/Core/CMakeLists.txt b/src/Core/CMakeLists.txt index e64459f..ed80177 100644 --- a/src/Core/CMakeLists.txt +++ b/src/Core/CMakeLists.txt @@ -58,6 +58,7 @@ if(USE_CUDA) endif() target_link_libraries(${libname} ${LIBRARIES}) +target_compile_definitions(${libname} PUBLIC ULIB_SERIALIZATION_VERSION="${PROJECT_VERSION}") install(TARGETS ${libname} EXPORT "uLibTargets" diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index cdc4837..e75e779 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -79,6 +79,7 @@ void Object::RegisterDynamicProperty(PropertyBase* prop) { if (prop) { for (auto* existing : d->m_DynamicProperties) { if (existing == prop) return; + if (existing->GetQualifiedName() == prop->GetQualifiedName()) return; } d->m_DynamicProperties.push_back(prop); } diff --git a/src/Core/Object.h b/src/Core/Object.h index 40613ec..99bd91b 100644 --- a/src/Core/Object.h +++ b/src/Core/Object.h @@ -78,7 +78,8 @@ public: Object(const Object ©); virtual ~Object(); - virtual const char * GetClassName() const { return "Object"; } + virtual const char * GetClassName() const { return type_name(); } + virtual const char * type_name() const { return "Object"; } const std::string& GetInstanceName() const; void SetInstanceName(const std::string& name); diff --git a/src/Core/ObjectsContext.h b/src/Core/ObjectsContext.h index 57b213a..af82f59 100644 --- a/src/Core/ObjectsContext.h +++ b/src/Core/ObjectsContext.h @@ -14,7 +14,7 @@ public: ObjectsContext(); virtual ~ObjectsContext(); - virtual const char * GetClassName() const { return "ObjectsContext"; } + uLibTypeMacro(ObjectsContext, Object) virtual ObjectsContext* GetChildren() override { return this; } /** diff --git a/src/Core/Property.h b/src/Core/Property.h index e932a0b..70e08e6 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -54,13 +54,13 @@ public: virtual void Updated() override { ULIB_SIGNAL_EMIT(PropertyBase::Updated); } // Serialization support for different uLib archives - virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) = 0; - virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) = 0; - virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) = 0; - virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) = 0; - virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) = 0; - virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) = 0; - virtual void serialize(Archive::log_archive & ar, const unsigned int version) = 0; + virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) override = 0; + virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) override = 0; + virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) override = 0; + virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) override = 0; + virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) override = 0; + virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) override = 0; + virtual void serialize(Archive::log_archive & ar, const unsigned int version) override = 0; }; @@ -407,12 +407,32 @@ private: /** - * @brief Convenience macro to automatically activate and register all HRP members + * @brief Convenience macro to automatically activate and register all HRP members * as uLib properties. Usage: ULIB_ACTIVATE_PROPERTIES(obj) */ #define ULIB_ACTIVATE_PROPERTIES(obj) \ { uLib::Archive::property_register_archive _ar_tmp(&(obj)); _ar_tmp & (obj); } +/** + * @brief Declares a private member that automatically calls ULIB_ACTIVATE_PROPERTIES + * in every constructor of the class. Place this macro as the last declaration + * inside the class body (before the closing brace). + * + * Usage: ULIB_DECLARE_PROPERTIES(ClassName) + * + * This replaces per-constructor ULIB_ACTIVATE_PROPERTIES(*this) calls. + * RegisterDynamicProperty deduplicates by qualified name, so re-registration + * from inherited activators in a hierarchy is safe. + */ +#define ULIB_DECLARE_PROPERTIES(SelfType) \ +private: \ + struct _PropActivator { \ + _PropActivator(SelfType* self) { \ + uLib::Archive::property_register_archive _ar(self); \ + _ar & *self; \ + } \ + } _prop_activator{this}; + } // namespace Archive } // namespace uLib diff --git a/src/Core/Serializable.h b/src/Core/Serializable.h index ec5996e..ee14e20 100644 --- a/src/Core/Serializable.h +++ b/src/Core/Serializable.h @@ -309,6 +309,8 @@ namespace uLib { #define HRP5(name, data, units, min, max) boost::serialization::make_hrp(name, data, units).range(min, max) #define HRP6(name, data, units, default, min, max) boost::serialization::make_hrp(name, data, units).set_default(default).range(min, max) +#define HRPE(name, data, labels) boost::serialization::make_hrp_enum(name, data, labels) + // LEFT FOR BACKWARD COMPATIBILITY #define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units) @@ -349,7 +351,7 @@ using boost::serialization::make_hrp_enum; #define ULIB_SERIALIZE_OBJECT(_Ob, ...) \ _ULIB_DETAIL_UNINTRUSIVE_SERIALIZE_OBJECT(_Ob, __VA_ARGS__) #define AR(_name) _ULIB_DETAIL_UNINTRUSIVE_AR_(_name) -#define HR(_name) _ULIB_DETAIL_UNINTRUSIVE_AR_(_name) +#define HR(_name) _ULIB_DETAIL_UNINTRUSIVE_HR_(_name) #endif #define ULIB_SERIALIZE_ACCESS \ @@ -362,14 +364,14 @@ using boost::serialization::make_hrp_enum; #define ULIB_CLASS_EXPORT_OBJECT_KEY(_FullNamespaceClass) \ BOOST_CLASS_EXPORT_KEY(_FullNamespaceClass) -#define _SERIALIZE_IMPL_SEQ \ - (uLib::Archive::text_iarchive)(uLib::Archive::text_oarchive)( \ - uLib::Archive:: \ - hrt_iarchive)(uLib::Archive:: \ - hrt_oarchive)(uLib::Archive:: \ - xml_iarchive)(uLib::Archive:: \ - xml_oarchive)(uLib::Archive:: \ - log_archive) +#define _SERIALIZE_IMPL_SEQ \ + (uLib::Archive::text_iarchive) \ + (uLib::Archive::text_oarchive) \ + (uLib::Archive::hrt_iarchive) \ + (uLib::Archive::hrt_oarchive) \ + (uLib::Archive::xml_iarchive) \ + (uLib::Archive::xml_oarchive) \ + (uLib::Archive::log_archive) /** Solving virtual class check problem */ #define _ULIB_DETAIL_SPECIALIZE_IS_VIRTUAL_BASE(_Base, _Derived) \ @@ -549,7 +551,8 @@ using boost::serialization::make_hrp_enum; void serialize_parents(ArchiveT &ar, _Ob &ob, const unsigned int v) { \ /* PP serialize */ BOOST_PP_SEQ_FOR_EACH( \ _UNAR_OP, ob, BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__))); \ - /* MPL serialize */ /*uLib::mpl::for_each<_Ob::BaseList>(uLib::detail::Serializable::serialize_baseobject<_Ob,ArchiveT>(ob,ar) );*/ } \ + /* MPL serialize */ /*uLib::mpl::for_each<_Ob::BaseList> \ + (uLib::detail::Serializable::serialize_baseobject<_Ob,ArchiveT>(ob,ar) );*/ }\ template \ inline void load_construct_data(ArchiveT &ar, _Ob *ob, \ const unsigned int file_version) { \ @@ -572,10 +575,18 @@ using boost::serialization::make_hrp_enum; _SERIALIZE_IMPL_SEQ) \ template \ void boost::serialization::access2<_Ob>::save_override( \ - ArchiveT &ar, _Ob &ob, const unsigned int version) + ArchiveT &ar, _Ob &ob, const unsigned int version) + #define _ULIB_DETAIL_UNINTRUSIVE_AR_(name) \ boost::serialization::make_nvp(BOOST_PP_STRINGIZE(name), ob.name) +#define _ULIB_DETAIL_UNINTRUSIVE_HR_(name) \ + boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), ob.name) + + + + + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Core/testing/PropertiesTest.cpp b/src/Core/testing/PropertiesTest.cpp index 9b1bdb7..7d740c7 100644 --- a/src/Core/testing/PropertiesTest.cpp +++ b/src/Core/testing/PropertiesTest.cpp @@ -76,8 +76,9 @@ public: ULIB_SERIALIZABLE_OBJECT(TestObject2) ULIB_SERIALIZE_OBJECT(TestObject2, TestObject) { - // std::cout << "Serializing TestObject2" << std::endl; - ar & boost::serialization::make_hrp("value2", ob.m_Value2, "mm").set_default(1.); + std::cout << "Serializing TestObject2" << std::endl; + // ar & boost::serialization::make_hrp("value2", ob.m_Value2, "mm").set_default(1.); + ar & HRP("value2", ob.m_Value2, "mm").set_default(1.); } diff --git a/src/Core/testing/PropertySystemTest.cpp b/src/Core/testing/PropertySystemTest.cpp index 092f9f8..03d1610 100644 --- a/src/Core/testing/PropertySystemTest.cpp +++ b/src/Core/testing/PropertySystemTest.cpp @@ -8,13 +8,12 @@ using namespace uLib; class TestObject : public Object { public: + uLibTypeMacro(TestObject, Object) TestObject() : Object(), IntProp(this, "IntProp", 10), StringProp(this, "StringProp", "Initial") {} - virtual const char* GetClassName() const override { return "TestObject"; } - Property IntProp; Property StringProp; }; diff --git a/src/Core/testing/PropertyTypesTest.cpp b/src/Core/testing/PropertyTypesTest.cpp index 2eeca71..e54d631 100644 --- a/src/Core/testing/PropertyTypesTest.cpp +++ b/src/Core/testing/PropertyTypesTest.cpp @@ -9,10 +9,9 @@ using namespace uLib; class TestObject : public Object { public: + uLibTypeMacro(TestObject, Object) TestObject() : Object() {} - virtual const char* GetClassName() const override { return "TestObject"; } - // Use new typedefs StringProperty StringProp = StringProperty(this, "StringProp", "Initial"); IntProperty IntProp = IntProperty(this, "IntProp", 42); diff --git a/src/HEP/Detectors/DetectorChamber.h b/src/HEP/Detectors/DetectorChamber.h index 3878f08..de135c0 100644 --- a/src/HEP/Detectors/DetectorChamber.h +++ b/src/HEP/Detectors/DetectorChamber.h @@ -39,13 +39,11 @@ namespace uLib { class DetectorChamber : public ContainerBox { - - typedef ContainerBox BaseClass; - public: + uLibTypeMacro(DetectorChamber, ContainerBox) - virtual const char * GetClassName() const { return "DetectorChamber"; } + DetectorChamber() : BaseClass() { m_ProjectionPlane.origin = HPoint3f(0, 0, 0); diff --git a/src/HEP/Geant/EmitterPrimary.hh b/src/HEP/Geant/EmitterPrimary.hh index 8c81d28..f9fd9ef 100644 --- a/src/HEP/Geant/EmitterPrimary.hh +++ b/src/HEP/Geant/EmitterPrimary.hh @@ -26,8 +26,7 @@ namespace Geant { class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransform { public: - - virtual const char* GetClassName() const override { return "Geant.EmitterPrimary"; } + uLibTypeMacro(EmitterPrimary, Object) EmitterPrimary(); virtual ~EmitterPrimary(); @@ -47,8 +46,7 @@ class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransf class SkyPlaneEmitterPrimary : public EmitterPrimary { public: - - virtual const char* GetClassName() const override { return "Geant.SkyPlaneEmitterPrimary"; } + uLibTypeMacro(SkyPlaneEmitterPrimary, EmitterPrimary) SkyPlaneEmitterPrimary(); virtual ~SkyPlaneEmitterPrimary(); @@ -69,8 +67,7 @@ class SkyPlaneEmitterPrimary : public EmitterPrimary class CylinderEmitterPrimary : public EmitterPrimary { public: - - virtual const char* GetClassName() const override { return "Geant.CylinderEmitterPrimary"; } + uLibTypeMacro(CylinderEmitterPrimary, EmitterPrimary) CylinderEmitterPrimary(); virtual ~CylinderEmitterPrimary(); @@ -98,8 +95,7 @@ class CylinderEmitterPrimary : public EmitterPrimary class QuadMeshEmitterPrimary : public EmitterPrimary { public: - - virtual const char* GetClassName() const override { return "Geant.QuadMeshEmitterPrimary"; } + uLibTypeMacro(QuadMeshEmitterPrimary, EmitterPrimary) QuadMeshEmitterPrimary(); virtual ~QuadMeshEmitterPrimary(); diff --git a/src/HEP/Geant/GeantEvent.h b/src/HEP/Geant/GeantEvent.h index b0e6dec..ca1e37c 100644 --- a/src/HEP/Geant/GeantEvent.h +++ b/src/HEP/Geant/GeantEvent.h @@ -50,8 +50,7 @@ class SteppingAction; class GeantEvent : public Object { public: - - virtual const char* GetClassName() const override { return "Geant.GeantEvent"; } + uLibTypeMacro(GeantEvent, Object) /// A single interaction step along the muon path. struct Delta { diff --git a/src/HEP/Geant/Matter.h b/src/HEP/Geant/Matter.h index 1df6573..d5eafb9 100644 --- a/src/HEP/Geant/Matter.h +++ b/src/HEP/Geant/Matter.h @@ -60,6 +60,7 @@ private: class Material : public Object { public: + uLibTypeMacro(Material, Object) enum State { Undefined = 0, @@ -68,8 +69,6 @@ public: Gas }; - virtual const char* GetClassName() const override { return "Geant.Material"; } - Material(); Material(const char *name); ~Material(); diff --git a/src/HEP/Geant/Scene.h b/src/HEP/Geant/Scene.h index 504a56e..f2fa6d4 100644 --- a/src/HEP/Geant/Scene.h +++ b/src/HEP/Geant/Scene.h @@ -43,8 +43,7 @@ class EmitterPrimary; class Scene : public Object { public: - - virtual const char* GetClassName() const override { return "Geant.Scene"; } + uLibTypeMacro(Scene, Object) Scene(); ~Scene(); diff --git a/src/HEP/Geant/Solid.h b/src/HEP/Geant/Solid.h index 82fac46..1e81fc4 100644 --- a/src/HEP/Geant/Solid.h +++ b/src/HEP/Geant/Solid.h @@ -43,8 +43,7 @@ namespace Geant { class Solid : public Object { public: - - virtual const char* GetClassName() const override { return "Geant.Solid"; } + uLibTypeMacro(Solid, Object) Solid(); Solid(const char *name); @@ -93,10 +92,8 @@ protected: class TessellatedSolid : public Solid { - typedef Solid BaseClass; public: - - virtual const char* GetClassName() const override { return "Geant.TessellatedSolid"; } + uLibTypeMacro(TessellatedSolid, Solid) TessellatedSolid(); TessellatedSolid(const char *name); @@ -120,11 +117,9 @@ private : class BoxSolid : public Solid { - typedef Solid BaseClass; - + public: - - virtual const char* GetClassName() const override { return "Geant.BoxSolid"; } + uLibTypeMacro(BoxSolid, Solid) BoxSolid(const char *name = ""); BoxSolid(const char *name, ContainerBox *box); diff --git a/src/Math/Assembly.cpp b/src/Math/Assembly.cpp index e232e3e..3a9d816 100644 --- a/src/Math/Assembly.cpp +++ b/src/Math/Assembly.cpp @@ -26,7 +26,6 @@ Assembly::Assembly() m_BBoxMax(Vector3f::Zero()), m_ShowBoundingBox(false), m_GroupSelection(true) { - ULIB_ACTIVATE_PROPERTIES(*this); } Assembly::Assembly(const Assembly ©) diff --git a/src/Math/Assembly.h b/src/Math/Assembly.h index 64246d4..88a9df4 100644 --- a/src/Math/Assembly.h +++ b/src/Math/Assembly.h @@ -46,7 +46,7 @@ namespace uLib { class Assembly : public ObjectsContext, public TRS { public: uLibTypeMacro(Assembly, ObjectsContext, TRS) - virtual const char *GetClassName() const override { return "Assembly"; } + Assembly(); Assembly(const Assembly ©); @@ -113,6 +113,8 @@ private: bool m_GroupSelection; bool m_InUpdated = false; std::map m_ChildConnections; + + ULIB_DECLARE_PROPERTIES(Assembly) }; } // namespace uLib diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index 674e571..dcdf57e 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -29,6 +29,7 @@ #include "Geometry.h" #include "Core/Object.h" #include "Core/Property.h" +#include "Core/Serializable.h" #include "Math/Dense.h" #include "Math/Transform.h" #include @@ -48,16 +49,11 @@ namespace uLib { */ class ContainerBox : public TRS { -public: uLibTypeMacro(ContainerBox, TRS) + ULIB_SERIALIZE_ACCESS + ULIB_DECLARE_PROPERTIES(ContainerBox) - virtual const char * GetClassName() const override { return "ContainerBox"; } - - //////////////////////////////////////////////////////////////////////////// - // PROPERTIES // - - Vector3f Size; - Vector3f Origin; +public: /** * @brief Default constructor. @@ -67,7 +63,6 @@ public: : m_LocalT(this), // BaseClass is Parent of m_LocalTransform Size(1.0f, 1.0f, 1.0f), Origin(0.0f, 0.0f, 0.0f) { - ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } @@ -79,7 +74,6 @@ public: : m_LocalT(this), Size(size), Origin(0.0f, 0.0f, 0.0f) { - ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } @@ -92,13 +86,12 @@ public: TRS(copy), Size(copy.Size), Origin(copy.Origin) { - ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } - /** - * @brief Serialization template for property registration and persistence. - */ + // /** + // * @brief Serialization template for property registration and persistence. + // */ template void serialize(ArchiveT & ar, const unsigned int version) { ar & HRP(Size); @@ -236,9 +229,13 @@ private: private: + Vector3f Size; + Vector3f Origin; AffineTransform m_LocalT; + }; } // namespace uLib + #endif // CONTAINERBOX_H diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index 688149c..921d52a 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -41,8 +41,10 @@ namespace uLib { */ class Cylinder : public TRS { -public: uLibTypeMacro(Cylinder, TRS) + ULIB_DECLARE_PROPERTIES(Cylinder) + +public: /** * @brief PROPERTIES @@ -51,22 +53,20 @@ public: float Height; int Axis; - virtual const char * GetClassName() const override { return "Cylinder"; } + /** * @brief Default constructor. Aligns with Y by default. */ Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) { - ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } /** * @brief Constructor with radius and height. */ - Cylinder(float radius, float height, int axis = 1) + Cylinder(float radius, float height, int axis = 1) : m_LocalT(this), Radius(radius), Height(height), Axis(axis) { - ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } @@ -75,7 +75,6 @@ public: */ Cylinder(const Cylinder ©) : m_LocalT(this), TRS(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) { - ULIB_ACTIVATE_PROPERTIES(*this); this->Sync(); } @@ -84,10 +83,10 @@ public: */ template void serialize(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); ar & HRP(Radius); ar & HRP(Height); - ar & HRP(Axis); + ar & boost::serialization::make_hrp_enum("Axis", Axis, {"X", "Y", "Z"}); + ar & HRP("TRS", boost::serialization::base_object(*this)); } /** Sets the radius of the cylinder */ diff --git a/src/Math/Geometry.h b/src/Math/Geometry.h index 7d29ec7..139bfbf 100644 --- a/src/Math/Geometry.h +++ b/src/Math/Geometry.h @@ -43,7 +43,7 @@ protected: public: uLibTypeMacro(Geometry, Object) - virtual const char * GetClassName() const override { return "Geometry"; } + virtual void SetParent(Geometry* p) { m_Parent = p; } virtual Geometry* GetParent() const { return m_Parent; } @@ -93,7 +93,7 @@ protected: public: uLibTypeMacro(LinearGeometry, Geometry) - virtual const char * GetClassName() const override { return "LinearGeometry"; } + virtual bool IsLinear() const override { return true; } virtual bool IsPure() const override { return true; } @@ -162,7 +162,7 @@ public: uLibTypeMacro(CylindricalGeometry, LinearGeometry) CylindricalGeometry() {} - virtual const char * GetClassName() const override { return "CylindricalGeometry"; } + virtual bool IsPure() const override { return false; } @@ -185,7 +185,7 @@ public: uLibTypeMacro(SphericalGeometry, LinearGeometry) SphericalGeometry() {} - virtual const char * GetClassName() const override { return "SphericalGeometry"; } + virtual bool IsPure() const override { return false; } @@ -212,7 +212,7 @@ public: uLibTypeMacro(ToroidalGeometry, LinearGeometry) ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {} - virtual const char * GetClassName() const override { return "ToroidalGeometry"; } + virtual bool IsPure() const override { return false; } diff --git a/src/Math/Polydata.h b/src/Math/Polydata.h index a28cd77..cdfe3d4 100644 --- a/src/Math/Polydata.h +++ b/src/Math/Polydata.h @@ -36,7 +36,7 @@ class Polydata : public Object { public: - virtual const char * GetClassName() const { return "Polydata"; } + diff --git a/src/Math/QuadMesh.h b/src/Math/QuadMesh.h index 8361f70..b1f25dd 100644 --- a/src/Math/QuadMesh.h +++ b/src/Math/QuadMesh.h @@ -39,7 +39,7 @@ class QuadMesh : public TRS public: uLibTypeMacro(QuadMesh, TRS) - virtual const char * GetClassName() const override { return "QuadMesh"; } + void PrintSelf(std::ostream &o); diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 63d0c5d..becad34 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -188,9 +188,12 @@ public: typedef Eigen::Affine3f AffineMatrix; class TRS : public AffineTransform { - -public: + uLibTypeMacro(TRS, AffineTransform) + ULIB_SERIALIZE_ACCESS + // ULIB_DECLARE_PROPERTIES(TRS) + +public: Vector3f position = Vector3f::Zero(); Vector3f rotation = Vector3f::Zero(); @@ -259,6 +262,7 @@ public: ar & HRPU(rotation, "rad"); ar & HRP(scaling); } + AffineMatrix GetAffineMatrix() const { AffineMatrix m = AffineMatrix::Identity(); @@ -273,6 +277,10 @@ public: Matrix4f GetMatrix() const { return this->GetAffineMatrix().matrix(); } + + + + }; diff --git a/src/Math/TriangleMesh.h b/src/Math/TriangleMesh.h index 590fdf3..157d9ef 100644 --- a/src/Math/TriangleMesh.h +++ b/src/Math/TriangleMesh.h @@ -42,7 +42,7 @@ class TriangleMesh : public TRS public: uLibTypeMacro(TriangleMesh, TRS) - virtual const char * GetClassName() const override { return "TriangleMesh"; } + void PrintSelf(std::ostream &o); diff --git a/src/Math/VoxImage.h b/src/Math/VoxImage.h index 4dfb8c4..b50f5a1 100644 --- a/src/Math/VoxImage.h +++ b/src/Math/VoxImage.h @@ -47,7 +47,7 @@ namespace Abstract { class VoxImage : public uLib::StructuredGrid { public: - virtual const char * GetClassName() const { return "VoxImage"; } + typedef uLib::StructuredGrid BaseClass; diff --git a/src/Math/VoxImageFilter.h b/src/Math/VoxImageFilter.h index e66778a..676310f 100644 --- a/src/Math/VoxImageFilter.h +++ b/src/Math/VoxImageFilter.h @@ -61,7 +61,7 @@ class VoxImageFilter : public Abstract::VoxImageFilter, public Object { public: - virtual const char * GetClassName() const { return "VoxImageFilter"; } + VoxImageFilter(const Vector3i &size); diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index f63c8d7..69ce2dc 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -36,7 +36,7 @@ class vtkObjectsContext; // forward */ class Assembly : public Puppet { public: - virtual const char *GetClassName() const override { return "Vtk.Assembly"; } + uLibTypeMacro(Assembly, Puppet) Assembly(uLib::Assembly *content); virtual ~Assembly(); diff --git a/src/Vtk/vtkObjectsContext.h b/src/Vtk/vtkObjectsContext.h index 3299de3..bc54917 100644 --- a/src/Vtk/vtkObjectsContext.h +++ b/src/Vtk/vtkObjectsContext.h @@ -15,7 +15,7 @@ namespace Vtk { */ class vtkObjectsContext : public Puppet { public: - virtual const char* GetClassName() const override { return "vtkObjectsContext"; } + uLibTypeMacro(vtkObjectsContext, Puppet) vtkObjectsContext(uLib::ObjectsContext *context); virtual ~vtkObjectsContext(); From 6068b62e3975fa9e2844b06ede62f04fabf02e1f Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 11:56:25 +0000 Subject: [PATCH 19/24] refactor: replace HRP with NVP in Cylinder serialization and add stream operators for TRS --- .gitignore | 1 + src/Math/Cylinder.h | 2 +- src/Math/Transform.h | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 05ed939..6a07fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ src/Python/uLib/__pycache__ src/Python/uLib/.nfs* test_props.xml test_props2.xml +test_boost.cpp diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index 921d52a..b707569 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -86,7 +86,7 @@ public: ar & HRP(Radius); ar & HRP(Height); ar & boost::serialization::make_hrp_enum("Axis", Axis, {"X", "Y", "Z"}); - ar & HRP("TRS", boost::serialization::base_object(*this)); + ar & NVP("TRS", boost::serialization::base_object(*this)); } /** Sets the radius of the cylinder */ diff --git a/src/Math/Transform.h b/src/Math/Transform.h index becad34..bf5d4b4 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -287,6 +287,16 @@ public: +inline std::ostream& operator<<(std::ostream& os, const TRS& trs) { + os << trs.position << " " << trs.rotation << " " << trs.scaling; + return os; +} + +inline std::istream& operator>>(std::istream& is, TRS& trs) { + is >> trs.position >> trs.rotation >> trs.scaling; + return is; +} + } // uLib From 59a9e829fc926e083029d42985f106fd1b0c3711 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 14:08:32 +0000 Subject: [PATCH 20/24] refactor: enhance vtkVoxImage volume rendering with dynamic shader range scaling, improved transfer function management, and synchronized VTK property updates. --- app/gcompose/src/PropertyWidgets.cpp | 12 +-- src/Vtk/Math/vtkContainerBox.cpp | 10 +- src/Vtk/Math/vtkVoxImage.cpp | 149 ++++++++++++++++----------- src/Vtk/Math/vtkVoxImage.h | 11 ++ src/Vtk/uLibVtkInterface.cxx | 6 +- src/Vtk/vtkObjectsContext.cpp | 9 +- 6 files changed, 119 insertions(+), 78 deletions(-) diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index 3c87bf9..15dc1be 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -448,17 +448,17 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { } } else { // Priority 2: Standard factory lookup - auto it = m_Factories.find(prop->GetTypeIndex()); - if (it != m_Factories.end()) { - widget = it->second(prop, m_Container); - } else { + auto it = m_Factories.find(prop->GetTypeIndex()); + if (it != m_Factories.end()) { + widget = it->second(prop, m_Container); + } else { // Debug info for unknown types std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName() << " (Type: " << prop->GetTypeName() << ")" << std::endl; - widget = new PropertyWidgetBase(prop, m_Container); + widget = new PropertyWidgetBase(prop, m_Container); widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); - } + } } if (widget) { diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index 9c7b5aa..74640fb 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -111,17 +111,17 @@ void vtkContainerBox::SyncFromVtk() { // VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center vtkMatrix4x4* rootMat = root->GetUserMatrix(); - if (rootMat) { - std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; - rootMat->Print(std::cout); - } + // if (rootMat) { + // std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; + // rootMat->Print(std::cout); + // } Matrix4f vtkWorld = VtkToMatrix4f(rootMat); // Synchronize TRS property members from the updated local matrix m_Content->FromMatrix(vtkWorld); - std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; + // std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; // Since we modified the model, notify observers, but block the loop back to VTK // ConnectionBlock blocker(d->m_UpdateSignal); diff --git a/src/Vtk/Math/vtkVoxImage.cpp b/src/Vtk/Math/vtkVoxImage.cpp index a266621..87e454c 100644 --- a/src/Vtk/Math/vtkVoxImage.cpp +++ b/src/Vtk/Math/vtkVoxImage.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,7 @@ #include #include "Vtk/Math/vtkVoxImage.h" +#include "Vtk/Math/vtkDense.h" #include VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2); @@ -128,10 +130,15 @@ vtkVoxImage::vtkVoxImage(Content &content) m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()), m_OutlineActor(vtkActor::New()), m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6), - m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) { + m_Window(1.0), m_Level(0.5), m_ShadingPreset(0) { + // Transfer functions + m_ColorFun = vtkColorTransferFunction::New(); + m_OpacityFun = vtkPiecewiseFunction::New(); + m_UpdateConnection = Object::connect(&m_Content, &uLib::Object::Updated, this, &vtkVoxImage::Update); + GetContent(); InstallPipe(); - ULIB_ACTIVATE_DISPLAY_PROPERTIES; + RescaleShaderRange(); } vtkVoxImage::~vtkVoxImage() { @@ -140,6 +147,8 @@ vtkVoxImage::~vtkVoxImage() { m_Asm->Delete(); m_Outline->Delete(); m_OutlineActor->Delete(); + m_ColorFun->Delete(); + m_OpacityFun->Delete(); } vtkImageData *vtkVoxImage::GetImageData() { @@ -181,6 +190,7 @@ void vtkVoxImage::ReadFromVKTFile(const char *fname) { m_Image->DeepCopy(vtkscale->GetOutput()); SetContent(); + RescaleShaderRange(); } else { std::cerr << "Error: file does not contain structured points\n"; } @@ -200,115 +210,134 @@ void vtkVoxImage::ReadFromXMLFile(const char *fname) { m_Image->DeepCopy(vtkscale->GetOutput()); SetContent(); + RescaleShaderRange(); } void vtkVoxImage::setShadingPreset(int blendType) { m_ShadingPreset = blendType; vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper(); + if (!mapper) return; vtkVolumeProperty *property = m_Actor->GetProperty(); - static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New(); - static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New(); - float window = m_Window; float level = m_Level; - property->SetColor(colorFun); - property->SetScalarOpacity(opacityFun); + property->SetColor(m_ColorFun); + property->SetScalarOpacity(m_OpacityFun); property->SetInterpolationTypeToLinear(); - if (blendType != 6) { - colorFun->RemoveAllPoints(); - opacityFun->RemoveAllPoints(); - } + m_ColorFun->RemoveAllPoints(); + m_OpacityFun->RemoveAllPoints(); switch (blendType) { - case 0: - colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, - 1.0); + case 0: // MIP + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToMaximumIntensity(); break; - case 1: - colorFun->AddRGBSegment(level - 0.5 * window, 0.0, 0.0, 0.0, - level + 0.5 * window, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, - 1.0); + case 1: // Composite + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToComposite(); property->ShadeOff(); break; - case 2: - colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, - 1.0); + case 2: // Composite Shaded + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToComposite(); property->ShadeOn(); break; - case 3: - colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0); - colorFun->AddRGBPoint(-1000, .62, .36, .18, 0.5, 0.0); - colorFun->AddRGBPoint(-500, .88, .60, .29, 0.33, 0.45); - colorFun->AddRGBPoint(3071, .83, .66, 1, 0.5, 0.0); - opacityFun->AddPoint(-3024, 0, 0.5, 0.0); - opacityFun->AddPoint(-1000, 0, 0.5, 0.0); - opacityFun->AddPoint(-500, 1.0, 0.33, 0.45); - opacityFun->AddPoint(3071, 1.0, 0.5, 0.0); - mapper->SetBlendModeToComposite(); - property->ShadeOn(); - property->SetAmbient(0.1); - property->SetDiffuse(0.9); - property->SetSpecular(0.2); - property->SetSpecularPower(10.0); - property->SetScalarOpacityUnitDistance(0.8919); - break; - case 4: - colorFun->AddRGBPoint(0.0, 0, 0, 1); // Blue - colorFun->AddRGBPoint(level, 0, 1, 0); // Green - colorFun->AddRGBPoint(level + 0.5*window, 1, 1, 0); // Yellow - colorFun->AddRGBPoint(level + window, 1, 0, 0); // Red - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); + case 3: // Rainbow MIP + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 1); + m_ColorFun->AddRGBPoint(level, 0, 1, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 0); + m_ColorFun->AddRGBPoint(level + window, 1, 0, 0); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToMaximumIntensity(); break; - case 5: - colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); + case 4: // Additive + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToAdditive(); break; default: - vtkGenericWarningMacro("Unknown blend type."); break; } } +void vtkVoxImage::RescaleShaderRange() { + double range[2]; + m_Image->GetScalarRange(range); + m_Level = (range[0] + range[1]) / 2.0; + m_Window = range[1] - range[0]; + if (m_Window <= 1e-9) + m_Window = 1.0; + setShadingPreset(m_ShadingPreset); +} + void vtkVoxImage::SetRepresentation(Representation mode) { + Puppet::SetRepresentation(mode); // Ensure base class data state is updated + if (mode == Wireframe) { m_Actor->SetVisibility(0); m_OutlineActor->SetVisibility(1); - } else if (mode == Surface) { + m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); + } else if (mode == Volume) { m_Actor->SetVisibility(1); - m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary + m_OutlineActor->SetVisibility(1); + m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); } else { - Puppet::SetRepresentation(mode); + // Other representations (Points, Surface, etc) are handled by basic Puppet + // behavior which affects the m_Asm parts. } } void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) { - // Call base class if it has display properties + // Call base class to show Transform and Appearance properties Puppet::serialize_display(ar, version); - // Use the member variables if they are available + // Use the member variables for volume rendering parameters ar & boost::serialization::make_hrp("Window", m_Window); ar & boost::serialization::make_hrp("Level", m_Level); - ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"}); + ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, + {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"}); +} + +void vtkVoxImage::SyncFromVtk() { + if (auto *root = this->GetProxyProp()) { + vtkMatrix4x4 *rootMat = root->GetUserMatrix(); + if (rootMat) { + Matrix4f vtkLocal = VtkToMatrix4f(rootMat); + // Synchronize TRS from VTK, compensating for local volume offset + m_Content.FromMatrix(vtkLocal); // * m_Content.GetLocalMatrix().inverse()); + m_Content.Updated(); + } + } } void vtkVoxImage::Update() { + if (auto *root = vtkProp3D::SafeDownCast(this->GetProp())) { + vtkNew m; + Matrix4fToVtk(m_Content.GetMatrix(), m); // * m_Content.GetLocalMatrix(), m); + root->SetUserMatrix(m); + root->Modified(); + // std::cout << "[vtkVoxImage::Update] Set Proxy UserMatrix:" << std::endl; + // std::cout << m_Content.GetMatrix() << std::endl; + } setShadingPreset(m_ShadingPreset); m_Actor->Update(); m_Outline->SetBounds(m_Image->GetBounds()); m_Outline->Update(); + + ConnectionBlock blocker(m_UpdateConnection); + this->Puppet::Update(); } + void vtkVoxImage::InstallPipe() { vtkSmartPointer mapper = vtkSmartPointer::New(); @@ -320,7 +349,7 @@ void vtkVoxImage::InstallPipe() { mapper->Update(); m_Actor->SetMapper(mapper); - this->setShadingPreset(0); + this->setShadingPreset(m_ShadingPreset); mapper->Update(); m_Outline->SetBounds(m_Image->GetBounds()); @@ -331,13 +360,13 @@ void vtkVoxImage::InstallPipe() { m_OutlineActor->SetMapper(mmapper); m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); m_OutlineActor->GetProperty()->SetAmbient(0.7); - + m_Asm->AddPart(m_Actor); m_Asm->AddPart(m_OutlineActor); this->SetProp(m_Asm); // Default look - this->SetRepresentation(Surface); + this->SetRepresentation(Volume); } } // namespace Vtk diff --git a/src/Vtk/Math/vtkVoxImage.h b/src/Vtk/Math/vtkVoxImage.h index 7b0b087..9f16efa 100644 --- a/src/Vtk/Math/vtkVoxImage.h +++ b/src/Vtk/Math/vtkVoxImage.h @@ -39,6 +39,8 @@ class vtkImageData; class vtkActor; +class vtkColorTransferFunction; +class vtkPiecewiseFunction; namespace uLib { namespace Vtk { @@ -55,6 +57,8 @@ public: void SetContent(); + vtkProp3D *GetProp() override { return m_Asm; } + vtkImageData *GetImageData(); void SaveToXMLFile(const char *fname); @@ -65,8 +69,10 @@ public: void setShadingPreset(int blendType = 2); void SetRepresentation(Representation mode); + void RescaleShaderRange(); void Update() override; + void SyncFromVtk() override; void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override; protected: @@ -88,6 +94,11 @@ private: float m_Window; float m_Level; int m_ShadingPreset; + + Connection m_UpdateConnection; + + class vtkColorTransferFunction *m_ColorFun; + class vtkPiecewiseFunction *m_OpacityFun; }; } // namespace Vtk diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 1dcbef0..95015c5 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -134,11 +134,11 @@ public: vtkActor *actor = vtkActor::SafeDownCast(p); if (actor) { - if (m_Representation != -1) { + if (m_Representation != -1 && m_Representation != Puppet::Volume) { if (m_Representation == Puppet::SurfaceWithEdges) { actor->GetProperty()->SetRepresentation(VTK_SURFACE); actor->GetProperty()->SetEdgeVisibility(1); - } else { + } else if (m_Representation != Puppet::Outline && m_Representation != Puppet::Slice) { actor->GetProperty()->SetRepresentation(m_Representation); actor->GetProperty()->SetEdgeVisibility(0); } @@ -628,6 +628,8 @@ struct AppearanceProxy { ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); + ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox); + ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures); } }; diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 98daba8..612499c 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -118,18 +118,17 @@ void vtkObjectsContext::SyncFromVtk() { Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { if (!obj) return nullptr; - if (auto* box = dynamic_cast(obj)) { + if (auto* vox = dynamic_cast(obj)) { + return new vtkVoxImage(*vox); + } else if (auto* box = dynamic_cast(obj)) { return new vtkContainerBox(box); } else if (auto* chamber = dynamic_cast(obj)) { return new vtkDetectorChamber(chamber); } else if (auto* cylinder = dynamic_cast(obj)) { return new vtkCylinder(cylinder); - } else if (auto* vox = dynamic_cast(obj)) { - return new vtkVoxImage(*vox); } else if (auto* assembly = dynamic_cast(obj)) { return new Assembly(assembly); - } - else if (auto* box = dynamic_cast(obj)) { + } else if (auto* box = dynamic_cast(obj)) { return new vtkBoxSolid(box); } From 72e69cfca5dadad48d34e8f52438373203868ec4 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 14:27:49 +0000 Subject: [PATCH 21/24] test: add unit test for vtkQViewport and register in CMakeLists.txt --- src/Vtk/testing/CMakeLists.txt | 1 + src/Vtk/testing/vtkQViewportTest.cpp | 86 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/Vtk/testing/vtkQViewportTest.cpp diff --git a/src/Vtk/testing/CMakeLists.txt b/src/Vtk/testing/CMakeLists.txt index df717ea..324e41a 100644 --- a/src/Vtk/testing/CMakeLists.txt +++ b/src/Vtk/testing/CMakeLists.txt @@ -4,6 +4,7 @@ set(TESTS vtkHandlerWidget PuppetPropertyTest PuppetParentingTest + vtkQViewportTest # vtkVoxImageTest # vtkTriangleMeshTest ) diff --git a/src/Vtk/testing/vtkQViewportTest.cpp b/src/Vtk/testing/vtkQViewportTest.cpp new file mode 100644 index 0000000..2f36184 --- /dev/null +++ b/src/Vtk/testing/vtkQViewportTest.cpp @@ -0,0 +1,86 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova + All rights reserved + + Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > + + ------------------------------------------------------------------ + This library 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 3.0 of the License, or (at your option) any later version. + + This library 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 this library. + +//////////////////////////////////////////////////////////////////////////////*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "testing-prototype.h" + +using namespace uLib; + +int main(int argc, char** argv) +{ + // Force X11 on Linux to avoid Wayland connection issues in headless/X11 environments +#if defined(Q_OS_LINUX) + qputenv("QT_QPA_PLATFORM", "xcb"); +#endif + + BEGIN_TESTING(vtk QViewport Test); + + QApplication app(argc, argv); + app.processEvents(); + + Vtk::QViewport viewport; + viewport.resize(800, 600); + viewport.show(); + + vtkSmartPointer cube = vtkSmartPointer::New(); + cube->SetXLength(10); + cube->SetYLength(10); + cube->SetZLength(10); + cube->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetInputConnection(cube->GetOutputPort()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->GetProperty()->SetColor(1, 0, 0); + + viewport.addProp(actor); + viewport.Render(); + + ASSERT_NOT_NULL(viewport.GetRenderWindow()); + ASSERT_NOT_NULL(viewport.GetRenderer()); + + if (std::getenv("CTEST_PROJECT_NAME") == nullptr) { + // Run application for a while to see the result + // return app.exec(); + } + + END_TESTING; +} From 5c04d00d4c1edda5d12a5fb2880215acb76e4add Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 14:30:31 +0000 Subject: [PATCH 22/24] refactor: remove redundant UpdateGrid call from QViewport::Render and add Claude configuration settings --- src/Vtk/vtkQViewport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Vtk/vtkQViewport.cpp b/src/Vtk/vtkQViewport.cpp index f890bd4..f9baf19 100644 --- a/src/Vtk/vtkQViewport.cpp +++ b/src/Vtk/vtkQViewport.cpp @@ -81,7 +81,6 @@ void QViewport::SetupPipeline() void QViewport::Render() { - UpdateGrid(); if (m_VtkWidget && m_VtkWidget->renderWindow()) m_VtkWidget->renderWindow()->Render(); } From 96ab3b09304c925b446cd8a4451757b27c41b4c2 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 14:32:39 +0000 Subject: [PATCH 23/24] fix: restore ULIB_ACTIVATE_DISPLAY_PROPERTIES to vtkVoxImage constructor --- .gitignore | 1 + src/Vtk/Math/vtkVoxImage.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6a07fa1..a0df7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ src/Python/uLib/.nfs* test_props.xml test_props2.xml test_boost.cpp +.claude/settings.json diff --git a/src/Vtk/Math/vtkVoxImage.cpp b/src/Vtk/Math/vtkVoxImage.cpp index 87e454c..1555fc6 100644 --- a/src/Vtk/Math/vtkVoxImage.cpp +++ b/src/Vtk/Math/vtkVoxImage.cpp @@ -139,6 +139,7 @@ vtkVoxImage::vtkVoxImage(Content &content) GetContent(); InstallPipe(); RescaleShaderRange(); + ULIB_ACTIVATE_DISPLAY_PROPERTIES; } vtkVoxImage::~vtkVoxImage() { @@ -298,7 +299,7 @@ void vtkVoxImage::SetRepresentation(Representation mode) { void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) { // Call base class to show Transform and Appearance properties - Puppet::serialize_display(ar, version); + // Puppet::serialize_display(ar, version); // Use the member variables for volume rendering parameters ar & boost::serialization::make_hrp("Window", m_Window); From 6396bdfebf73155aacc73a57ba2829ae7aac37e1 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 14:42:38 +0000 Subject: [PATCH 24/24] feat: add projection toggle button to switch between perspective and orthographic views --- src/Vtk/testing/vtkQViewportTest.cpp | 3 +- src/Vtk/uLibVtkViewer.cpp | 93 +++++++++++++++++++++++++++- src/Vtk/uLibVtkViewer.h | 3 + src/Vtk/vtkQViewport.cpp | 43 ++++++++++++- src/Vtk/vtkQViewport.h | 2 + src/Vtk/vtkViewport.cpp | 15 +++++ src/Vtk/vtkViewport.h | 4 ++ 7 files changed, 159 insertions(+), 4 deletions(-) diff --git a/src/Vtk/testing/vtkQViewportTest.cpp b/src/Vtk/testing/vtkQViewportTest.cpp index 2f36184..a4feb39 100644 --- a/src/Vtk/testing/vtkQViewportTest.cpp +++ b/src/Vtk/testing/vtkQViewportTest.cpp @@ -79,8 +79,9 @@ int main(int argc, char** argv) if (std::getenv("CTEST_PROJECT_NAME") == nullptr) { // Run application for a while to see the result - // return app.exec(); + return app.exec(); } + END_TESTING; } diff --git a/src/Vtk/uLibVtkViewer.cpp b/src/Vtk/uLibVtkViewer.cpp index 88c2454..9e96cae 100644 --- a/src/Vtk/uLibVtkViewer.cpp +++ b/src/Vtk/uLibVtkViewer.cpp @@ -72,6 +72,7 @@ struct ViewerData { vtkRenderWindow *m_RenderWindow; vtkSmartPointer m_Interactor; vtkSmartPointer m_GridButton; + vtkSmartPointer m_ProjButton; ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {} ~ViewerData() { @@ -97,6 +98,11 @@ Viewer::~Viewer() { dv->m_GridButton->SetInteractor(nullptr); dv->m_GridButton = nullptr; } + if (dv->m_ProjButton) { + dv->m_ProjButton->Off(); + dv->m_ProjButton->SetInteractor(nullptr); + dv->m_ProjButton = nullptr; + } if (this->GetRenderWindow()) { this->GetRenderWindow()->RemoveAllObservers(); } @@ -123,6 +129,7 @@ void Viewer::InstallPipe() { // Setup native grid button if (!std::getenv("CTEST_PROJECT_NAME")) { SetupGridButton(); + SetupProjButton(); } // BUT we want to override the style with our custom NoSpin version @@ -238,9 +245,91 @@ void Viewer::UpdateGridButtonPosition() { rep->PlaceWidget(bds); } -void Viewer::Start() { +void Viewer::SetupProjButton() { + if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return; + + vtkNew canvas; + canvas->SetScalarTypeToUnsignedChar(); + canvas->SetNumberOfScalarComponents(4); + canvas->SetExtent(0, 63, 0, 63, 0, 0); + + // State 0: Perspective (gray trapezoid-like lines) + canvas->SetDrawColor(0, 0, 0, 0); + canvas->FillBox(0, 63, 0, 63); + canvas->SetDrawColor(120, 120, 120, 255); + canvas->DrawSegment(16, 16, 48, 16); + canvas->DrawSegment(48, 16, 56, 48); + canvas->DrawSegment(56, 48, 8, 48); + canvas->DrawSegment(8, 48, 16, 16); + canvas->Update(); + + vtkNew imgPersp; + imgPersp->DeepCopy(canvas->GetOutput()); + + // State 1: Orthographic (white rectangle) + canvas->SetDrawColor(0, 0, 0, 0); + canvas->FillBox(0, 63, 0, 63); + canvas->SetDrawColor(255, 255, 255, 255); + canvas->DrawSegment(12, 16, 52, 16); + canvas->DrawSegment(52, 16, 52, 48); + canvas->DrawSegment(52, 48, 12, 48); + canvas->DrawSegment(12, 48, 12, 16); + canvas->Update(); + + vtkNew imgOrtho; + imgOrtho->DeepCopy(canvas->GetOutput()); + + vtkNew rep; + rep->SetNumberOfStates(2); + rep->SetButtonTexture(0, imgPersp); + rep->SetButtonTexture(1, imgOrtho); + + dv->m_ProjButton = vtkSmartPointer::New(); + dv->m_ProjButton->SetInteractor(dv->m_RenderWindow->GetInteractor()); + dv->m_ProjButton->SetRepresentation(rep); + + UpdateProjButtonPosition(); + + vtkNew resizeCallback; + resizeCallback->SetClientData(this); + resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){ + static_cast(clientdata)->UpdateProjButtonPosition(); + }); + dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback); + + vtkNew stateCallback; + stateCallback->SetClientData(this); + stateCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){ + auto* btn = vtkButtonWidget::SafeDownCast(caller); + auto* v = static_cast(clientdata); + auto* r = vtkTexturedButtonRepresentation2D::SafeDownCast(btn->GetRepresentation()); + v->SetParallelProjection(r->GetState() == 1); + }); + dv->m_ProjButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback); + dv->m_ProjButton->On(); + + rep->SetState(GetParallelProjection() ? 1 : 0); +} + +void Viewer::UpdateProjButtonPosition() { + if (!dv->m_ProjButton || !dv->m_RenderWindow) return; + auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_ProjButton->GetRepresentation()); + if (!rep) return; + + int *sz = dv->m_RenderWindow->GetSize(); + if (sz[0] == 0 || sz[1] == 0) return; + + int margin_right = 23; + int margin_top = 220; // below the grid button (170 + 50) + int btnSz = 100; + double bds[6] = { (double)sz[0] - btnSz - margin_right, (double)sz[0] - margin_right, + (double)sz[1] - margin_top - btnSz/2.0, (double)sz[1] - margin_top + btnSz/2.0, 0, 0 }; + rep->PlaceWidget(bds); +} + +void Viewer::Start() { if (std::getenv("CTEST_PROJECT_NAME")) return; - dv->m_RenderWindow->GetInteractor()->Start(); + dv->m_RenderWindow->GetInteractor()->Start(); } vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; } diff --git a/src/Vtk/uLibVtkViewer.h b/src/Vtk/uLibVtkViewer.h index 69dd9bd..d70e635 100644 --- a/src/Vtk/uLibVtkViewer.h +++ b/src/Vtk/uLibVtkViewer.h @@ -38,6 +38,9 @@ private: void SetupGridButton(); void UpdateGridButtonPosition(); + void SetupProjButton(); + void UpdateProjButtonPosition(); + struct ViewerData *dv; }; diff --git a/src/Vtk/vtkQViewport.cpp b/src/Vtk/vtkQViewport.cpp index f9baf19..bc3f80f 100644 --- a/src/Vtk/vtkQViewport.cpp +++ b/src/Vtk/vtkQViewport.cpp @@ -19,6 +19,7 @@ QViewport::QViewport(QWidget* parent) , Viewport() , m_VtkWidget(nullptr) , m_GridButton(nullptr) + , m_ProjButton(nullptr) { // Build the layout – zero margins so VTK fills the entire widget auto* layout = new QVBoxLayout(this); @@ -58,6 +59,36 @@ QViewport::QViewport(QWidget* parent) m_GridButton->setChecked(true); // Grid is on by default connect(m_GridButton, &QPushButton::clicked, this, &QViewport::onGridButtonClicked); + // Projection Toggle Button (below grid button) + m_ProjButton = new QPushButton(m_VtkWidget); + m_ProjButton->setText("P"); + m_ProjButton->setFixedSize(40, 40); + m_ProjButton->setToolTip("Toggle Perspective / Orthographic"); + m_ProjButton->setStyleSheet( + "QPushButton {" + " border-radius: 20px;" + " background-color: rgba(40, 40, 40, 180);" + " color: white;" + " font-size: 22px;" + " border: 1.5px solid rgba(255, 255, 255, 60);" + "}" + "QPushButton:hover {" + " background-color: rgba(70, 70, 70, 200);" + " border: 1.5px solid rgba(255, 255, 255, 100);" + "}" + "QPushButton:checked {" + " background-color: rgba(0, 120, 215, 200);" + " color: white;" + " border: 1.5px solid rgba(255, 255, 255, 120);" + "}" + "QPushButton:pressed {" + " background-color: rgba(0, 90, 160, 220);" + "}" + ); + m_ProjButton->setCheckable(true); + m_ProjButton->setChecked(false); // Perspective by default + connect(m_ProjButton, &QPushButton::clicked, this, &QViewport::onProjButtonClicked); + // After the Qt widget exists but before the first paint, // attach the renderer and configure the pipeline. SetupPipeline(); @@ -100,6 +131,11 @@ void QViewport::onGridButtonClicked() SetGridVisible(m_GridButton->isChecked()); } +void QViewport::onProjButtonClicked() +{ + SetParallelProjection(m_ProjButton->isChecked()); +} + void QViewport::OnSelectionChanged(Puppet* p) { emit puppetSelected(p); @@ -112,9 +148,14 @@ void QViewport::resizeEvent(QResizeEvent* event) // Position under the gizmo (top-right corner). // Standard CameraOrientationWidget is usually 150-180px. int x = width() - m_GridButton->width() - 10; - int y = 160; + int y = 160; m_GridButton->move(x, y); } + if (m_ProjButton) { + int x = width() - m_ProjButton->width() - 10; + int y = 210; + m_ProjButton->move(x, y); + } } } // namespace Vtk diff --git a/src/Vtk/vtkQViewport.h b/src/Vtk/vtkQViewport.h index 168a926..9daae05 100644 --- a/src/Vtk/vtkQViewport.h +++ b/src/Vtk/vtkQViewport.h @@ -51,12 +51,14 @@ protected: private slots: void onGridButtonClicked(); + void onProjButtonClicked(); private: void SetupPipeline(); QVTKOpenGLNativeWidget* m_VtkWidget; QPushButton* m_GridButton; + QPushButton* m_ProjButton; }; } // namespace Vtk diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index d6b8521..1b638b0 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -537,6 +537,21 @@ bool Viewport::GetGridVisible() const return false; } +void Viewport::SetParallelProjection(bool parallel) +{ + if (pv->m_Renderer && pv->m_Renderer->GetActiveCamera()) { + pv->m_Renderer->GetActiveCamera()->SetParallelProjection(parallel ? 1 : 0); + Render(); + } +} + +bool Viewport::GetParallelProjection() const +{ + if (pv->m_Renderer && pv->m_Renderer->GetActiveCamera()) + return pv->m_Renderer->GetActiveCamera()->GetParallelProjection() != 0; + return false; +} + void Viewport::SetGridAxis(Axis axis) { m_GridAxis = axis; diff --git a/src/Vtk/vtkViewport.h b/src/Vtk/vtkViewport.h index a97d53f..c2b4db4 100644 --- a/src/Vtk/vtkViewport.h +++ b/src/Vtk/vtkViewport.h @@ -73,6 +73,10 @@ public: void SetGridAxis(Axis axis); Axis GetGridAxis() const { return m_GridAxis; } + // Projection control + void SetParallelProjection(bool parallel); + bool GetParallelProjection() const; + protected: void SetupPipeline(vtkRenderWindowInteractor* iren);