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