From 5d0efb3078d619453be66b971a0471a9f801c013 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Mon, 23 Mar 2026 15:09:35 +0000 Subject: [PATCH] add vtk Properties --- src/Core/Object.cpp | 15 +++- src/Core/Object.h | 1 + src/Core/Property.h | 117 ++++++++++++++++++++++--- src/Core/testing/HRPTest.cpp | 36 +++++++- src/Vtk/testing/CMakeLists.txt | 1 + src/Vtk/testing/PuppetPropertyTest.cpp | 68 ++++++++++++++ src/Vtk/uLibVtkInterface.cxx | 28 +++++- src/Vtk/uLibVtkInterface.h | 12 ++- src/Vtk/vtkObjectsContext.h | 2 +- 9 files changed, 263 insertions(+), 17 deletions(-) create mode 100644 src/Vtk/testing/PuppetPropertyTest.cpp diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index c6eb47e..eb1d666 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -64,6 +64,7 @@ public: Vector sigv; Vector slov; std::vector m_Properties; + std::vector m_DynamicProperties; }; // Implementations of Property methods @@ -71,6 +72,13 @@ void Object::RegisterProperty(PropertyBase* prop) { if (prop) d->m_Properties.push_back(prop); } +void Object::RegisterDynamicProperty(PropertyBase* prop) { + if (prop) { + d->m_DynamicProperties.push_back(prop); + d->m_Properties.push_back(prop); + } +} + const std::vector& Object::GetProperties() const { return d->m_Properties; } @@ -109,7 +117,12 @@ Object::Object() : d(new ObjectPrivate) {} Object::Object(const Object ©) : d(new ObjectPrivate(*copy.d)) {} -Object::~Object() { delete d; } +Object::~Object() { + for (auto* p : d->m_DynamicProperties) { + delete p; + } + delete d; +} void Object::DeepCopy(const Object ©) { // should lock to be tread safe // diff --git a/src/Core/Object.h b/src/Core/Object.h index 93d0bac..ae73a31 100644 --- a/src/Core/Object.h +++ b/src/Core/Object.h @@ -85,6 +85,7 @@ public: //////////////////////////////////////////////////////////////////////////// // PROPERTIES // void RegisterProperty(PropertyBase* prop); + void RegisterDynamicProperty(PropertyBase* prop); const std::vector& GetProperties() const; //////////////////////////////////////////////////////////////////////////// diff --git a/src/Core/Property.h b/src/Core/Property.h index 66616f0..9194fa7 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -42,38 +42,50 @@ public: template class Property : public PropertyBase { public: - Property(Object* owner, const std::string& name, const T& defaultValue = T()) - : m_owner(owner), m_name(name), m_value(defaultValue) { + // PROXY: Use an existing variable as back-end storage + Property(Object* owner, const std::string& name, T* valuePtr) + : m_owner(owner), m_name(name), m_value(valuePtr), m_own(false) { if (m_owner) { m_owner->RegisterProperty(this); } } - virtual ~Property() {} + + // MANAGED: Create and own internal storage + Property(Object* owner, const std::string& name, const T& defaultValue = T()) + : m_owner(owner), m_name(name), m_value(new T(defaultValue)), m_own(true) { + if (m_owner) { + m_owner->RegisterProperty(this); + } + } + + virtual ~Property() { + if (m_own) delete m_value; + } const std::string& GetName() const override { return m_name; } const char* GetTypeName() const override { return typeid(T).name(); } std::string GetValueAsString() const override { try { - return boost::lexical_cast(m_value); + return boost::lexical_cast(*m_value); } catch (const boost::bad_lexical_cast&) { std::stringstream ss; - ss << m_value; + ss << *m_value; return ss.str(); } } // Accessors - const T& Get() const { return m_value; } + const T& Get() const { return *m_value; } void Set(const T& value) { - if (m_value != value) { - m_value = value; + if (*m_value != value) { + *m_value = value; ULIB_SIGNAL_EMIT(Property::PropertyChanged); } } // Operators for seamless usage - operator const T&() const { return m_value; } + operator const T&() const { return *m_value; } Property& operator=(const T& value) { Set(value); return *this; @@ -86,7 +98,7 @@ public: // Serialization template void serialize_impl(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp(m_name.c_str(), m_value); + ar & boost::serialization::make_nvp(m_name.c_str(), *m_value); } void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } @@ -99,7 +111,8 @@ public: private: std::string m_name; - T m_value; + T* m_value; + bool m_own; Object* m_owner; }; @@ -118,11 +131,91 @@ typedef Property BoolProperty; /** * @brief Macro to simplify property declaration within a class. * Usage: ULIB_PROPERTY(float, Width, 1.0f) + * It creates a raw member m_Width and a Property proxy Width. */ #define ULIB_PROPERTY(type, name, defaultValue) \ - Property name = Property(this, #name, defaultValue); + type m_##name = defaultValue; \ + Property name = Property(this, #name, &m_##name); } // namespace uLib +namespace uLib { +namespace Archive { +class property_register_archive; +} // namespace Archive +} // namespace uLib + +namespace boost { +namespace archive { +namespace detail { +template <> +class interface_oarchive + : public uLib_interface_oarchive {}; +} // namespace detail +} // namespace archive +} // namespace boost + +namespace uLib { +namespace Archive { + +/** + * @brief A special archive that creates and registers Property proxies + * for any member it encounters wrapped in HRP(). + */ +class property_register_archive : + public boost::archive::detail::common_oarchive +{ + Object* m_Object; +public: + friend class boost::archive::detail::interface_oarchive; + friend class boost::archive::save_access; + + typedef boost::archive::detail::common_oarchive detail_common_oarchive; + + property_register_archive(Object* obj) : + boost::archive::detail::common_oarchive(boost::archive::no_header), + m_Object(obj) {} + + // Core logic: encounter HRP -> Create Dynamic Property + template + void save_override(const boost::serialization::hrp &t) { + if (m_Object) { + // We use const_cast because we are just creating a proxy to the member + m_Object->RegisterDynamicProperty( + new Property(m_Object, t.name(), &const_cast&>(t).value()) + ); + } + } + + // Handle standard NVPs by recursing (important for base classes) + template + void save_override(const boost::serialization::nvp &t) { + this->detail_common_oarchive::save_override(t.const_value()); + } + + // Ignore everything else + template void save_override(const T &t) {} + + // Required attribute overrides for common_oarchive + 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) {} +}; + +/** + * @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); } + +} // namespace Archive +} // namespace uLib + #endif // U_CORE_PROPERTY_H diff --git a/src/Core/testing/HRPTest.cpp b/src/Core/testing/HRPTest.cpp index e142f59..11f6e6a 100644 --- a/src/Core/testing/HRPTest.cpp +++ b/src/Core/testing/HRPTest.cpp @@ -1,5 +1,7 @@ #include #include +#include "Core/Object.h" +#include "Core/Property.h" #include "Core/Serializable.h" #include "Core/Archives.h" @@ -8,13 +10,29 @@ using namespace uLib; struct SimpleObject { int value; std::string name; - + template void serialize(Archive & ar, const unsigned int version) { ar & HRP(value); ar & HRP(name); } }; + +struct DynamicObject : public Object { + float width; + int height; + + DynamicObject() : width(10.0f), height(20) { + // Automatic registration of properties based on serialize/HRP + ULIB_ACTIVATE_PROPERTIES(*this); + } + + template + void serialize(Archive & ar, const unsigned int version) { + ar & HRP(width); + ar & HRP(height); + } +}; int main() { SimpleObject obj; @@ -45,5 +63,21 @@ int main() { } std::cout << ss.str() << std::endl; + std::cout << "Testing Dynamic Property Creation via ULIB_ACTIVATE_PROPERTIES macro..." << std::endl; + DynamicObject dynObj; + // (properties were already created in DynamicObject constructor via macro) + + std::cout << "Registered Properties in dynObj:" << std::endl; + const auto& props = dynObj.GetProperties(); + for (auto* p : props) { + std::cout << " - [" << p->GetTypeName() << "] " << p->GetName() << " = " << p->GetValueAsString() << std::endl; + } + + if (props.size() == 2) { + std::cout << "Dynamic Property Creation SUCCESS!" << std::endl; + } else { + std::cout << "Dynamic Property Creation FAILED (Expected 2, got " << props.size() << ")" << std::endl; + } + return 0; } diff --git a/src/Vtk/testing/CMakeLists.txt b/src/Vtk/testing/CMakeLists.txt index ab8b4e0..26d9c02 100644 --- a/src/Vtk/testing/CMakeLists.txt +++ b/src/Vtk/testing/CMakeLists.txt @@ -3,6 +3,7 @@ set( TESTS vtkViewerTest vtkContainerBoxTest vtkHandlerWidget + PuppetPropertyTest # vtkVoxImageTest # vtkTriangleMeshTest ) diff --git a/src/Vtk/testing/PuppetPropertyTest.cpp b/src/Vtk/testing/PuppetPropertyTest.cpp new file mode 100644 index 0000000..81ec10a --- /dev/null +++ b/src/Vtk/testing/PuppetPropertyTest.cpp @@ -0,0 +1,68 @@ +/*////////////////////////////////////////////////////////////////////////////// +// 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 "Vtk/uLibVtkInterface.h" +#include "Core/Property.h" +#include +#include +#include "testing-prototype.h" + +using namespace uLib; +using namespace Vtk; + +int main() { + BEGIN_TESTING(Puppet Property Registration Test); + + std::cout << "Creating Puppet object..." << std::endl; + Puppet p; + + // At this point, the Puppet constructor has called ULIB_ACTIVATE_PROPERTIES. + // This should have discovered the members used in Puppet::serialize() + // and registered them as uLib properties. + + const auto& props = p.GetProperties(); + std::cout << "Total registered properties: " << props.size() << std::endl; + + // Verify specific properties exist + Property* opacityProp = nullptr; + Property* colorRProp = nullptr; + + for (auto* prop : props) { + std::cout << " - [" << prop->GetTypeName() << "] " << prop->GetName() + << " = " << prop->GetValueAsString() << std::endl; + + if (prop->GetName() == "Opacity") { + opacityProp = dynamic_cast*>(prop); + } + if (prop->GetName() == "ColorR") { + colorRProp = dynamic_cast*>(prop); + } + } + + assert(opacityProp != nullptr && "Opacity property not registered!"); + assert(colorRProp != nullptr && "ColorR property not registered!"); + + // Test modification via uLib Property interface + std::cout << "Modifying Opacity via property proxy (0.25)..." << std::endl; + *opacityProp = 0.25; + + // Verify the proxy correctly updated the underlying data + assert(opacityProp->Get() == 0.25); + assert(opacityProp->GetValueAsString().find("0.25") != std::string::npos); + + std::cout << "Modifying ColorR via property proxy (0.9)..." << std::endl; + *colorRProp = 0.9; + assert(colorRProp->Get() == 0.9); + + std::cout << "All Puppet Property Registration Tests PASSED!" << std::endl; + + END_TESTING; +} diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 440ce90..b904bb4 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -60,6 +60,7 @@ #include "uLibVtkInterface.h" #include "vtkHandlerWidget.h" +#include "Core/Property.h" @@ -197,7 +198,12 @@ public: -Puppet::Puppet() : d(new PuppetData) { } +Puppet::Puppet() : Object(), d(new PuppetData) { + ULIB_ACTIVATE_PROPERTIES(*this); + for (auto* p : this->GetProperties()) { + uLib::Object::connect(p, &uLib::PropertyBase::Updated, this, &Puppet::Update); + } +} Puppet::~Puppet() { @@ -442,6 +448,12 @@ bool Puppet::IsSelected() const void Puppet::Update() { + vtkProp3DCollection *props = d->m_Assembly->GetParts(); + props->InitTraversal(); + for (int i = 0; i < props->GetNumberOfItems(); ++i) { + d->ApplyAppearance(props->GetNextProp3D()); + } + if (d->m_Selected) { d->UpdateHighlight(); } @@ -462,5 +474,19 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) { } +template +void Puppet::serialize(Archive & ar, const unsigned int version) { + ar & boost::serialization::make_hrp("ColorR", d->m_Color[0]); + ar & boost::serialization::make_hrp("ColorG", d->m_Color[1]); + ar & boost::serialization::make_hrp("ColorB", d->m_Color[2]); + ar & boost::serialization::make_hrp("Opacity", d->m_Opacity); + ar & boost::serialization::make_hrp("Representation", d->m_Representation); +} + +void Puppet::save_override(Archive::xml_oarchive & ar, const unsigned int v) { serialize(ar, v); } +void Puppet::save_override(Archive::hrt_oarchive & ar, const unsigned int v) { serialize(ar, v); } +void Puppet::save_override(Archive::log_archive & ar, const unsigned int v) { serialize(ar, v); } +void Puppet::save_override(Archive::text_oarchive & ar, const unsigned int v) { serialize(ar, v); } + } // namespace Vtk } // namespace uLib diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index 2e37294..1bed7d3 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -28,6 +28,7 @@ #include #include +#include "Core/Object.h" // vtk classes forward declaration // class vtkProp; @@ -41,7 +42,8 @@ class vtkRenderWindowInteractor; namespace uLib { namespace Vtk { -class Puppet { +class Puppet : public uLib::Object { + uLibTypeMacro(Puppet, uLib::Object) public: Puppet(); ~Puppet(); @@ -90,6 +92,14 @@ public: virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor); + // Serialization and Reflection + template + void serialize(Archive & ar, const unsigned int version); + void save_override(Archive::xml_oarchive & ar, const unsigned int version); + void save_override(Archive::hrt_oarchive & ar, const unsigned int version); + void save_override(Archive::log_archive & ar, const unsigned int version); + void save_override(Archive::text_oarchive & ar, const unsigned int version); + protected: void SetProp(vtkProp *prop); diff --git a/src/Vtk/vtkObjectsContext.h b/src/Vtk/vtkObjectsContext.h index db25902..0408e47 100644 --- a/src/Vtk/vtkObjectsContext.h +++ b/src/Vtk/vtkObjectsContext.h @@ -13,7 +13,7 @@ namespace Vtk { /** * @brief vtkObjectsContext manages VTK representations (Puppets) for a collection of uLib::Objects. */ -class vtkObjectsContext : public uLib::Object, public Puppet { +class vtkObjectsContext : public Puppet { public: virtual const char* GetClassName() const override { return "vtkObjectsContext"; } vtkObjectsContext(uLib::ObjectsContext *context);