diff --git a/src/Core/Archives.h b/src/Core/Archives.h index 5a6348a..665f527 100644 --- a/src/Core/Archives.h +++ b/src/Core/Archives.h @@ -80,6 +80,9 @@ template class polymorphic_iarchive_route; namespace boost { namespace serialization { template struct hrp; +template struct hrp_val; +template struct hrp_enum; +template struct hrp_enum_val; } } // namespace boost @@ -177,6 +180,24 @@ public: return *this->This(); } + template + Archive &operator>>(const boost::serialization::hrp_val &t) { + this->This()->load_override(const_cast &>(t)); + return *this->This(); + } + + template + Archive &operator>>(const boost::serialization::hrp_enum &t) { + this->This()->load_override(const_cast &>(t)); + return *this->This(); + } + + template + Archive &operator>>(const boost::serialization::hrp_enum_val &t) { + this->This()->load_override(const_cast &>(t)); + return *this->This(); + } + // the & operator template Archive &operator&(T &t) { return *(this->This()) >> t; } @@ -190,6 +211,21 @@ public: return *(this->This()) >> t; } + template + Archive &operator&(const boost::serialization::hrp_val &t) { + return *(this->This()) >> t; + } + + template + Archive &operator&(const boost::serialization::hrp_enum &t) { + return *(this->This()) >> t; + } + + template + Archive &operator&(const boost::serialization::hrp_enum_val &t) { + return *(this->This()) >> t; + } + // the == operator template Archive &operator==(T &t) { return this->operator&(t); } @@ -229,12 +265,62 @@ public: this->This()->save_override(t); return *this->This(); } + + template Archive &operator<<(const boost::serialization::hrp &t) { + this->This()->save_override(t); + return *this->This(); + } + + template Archive &operator<<(const boost::serialization::hrp_val &t) { + this->This()->save_override(t); + return *this->This(); + } + + template Archive &operator<<(const boost::serialization::hrp_enum &t) { + this->This()->save_override(t); + return *this->This(); + } + + template Archive &operator<<(const boost::serialization::hrp_enum_val &t) { + this->This()->save_override(t); + return *this->This(); + } + + template Archive &operator<<(const boost::serialization::nvp &t) { + this->This()->save_override(t); + return *this->This(); + } // the & operator template Archive &operator&(const T &t) { return *this->This() << t; } + template + Archive &operator&(const boost::serialization::hrp &t) { + return *this->This() << t; + } + + template + Archive &operator&(const boost::serialization::hrp_val &t) { + return *this->This() << t; + } + + template + Archive &operator&(const boost::serialization::hrp_enum &t) { + return *this->This() << t; + } + + template + Archive &operator&(const boost::serialization::hrp_enum_val &t) { + return *this->This() << t; + } + + template + Archive &operator&(const boost::serialization::nvp &t) { + return *this->This() << t; + } + // the == operator template Archive &operator==(T &t) { return this->operator&(t); } diff --git a/src/Core/CMakeLists.txt b/src/Core/CMakeLists.txt index ed80177..2a4c0e0 100644 --- a/src/Core/CMakeLists.txt +++ b/src/Core/CMakeLists.txt @@ -21,6 +21,7 @@ set(HEADERS StringReader.h Threads.h Monitor.h + Property.h Types.h Uuid.h Vector.h diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index e75e779..8ae3cbd 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -65,10 +65,19 @@ public: std::vector slov; std::vector m_Properties; std::vector m_DynamicProperties; + std::vector m_DisplayProperties; bool m_SignalsBlocked; }; // Implementations of Property methods +void Object::RegisterDisplayProperty(PropertyBase* prop) { + if (prop) d->m_DisplayProperties.push_back(prop); +} + +const std::vector& Object::GetDisplayProperties() const { + return d->m_DisplayProperties; +} + void Object::RegisterProperty(PropertyBase* prop) { if (prop) { d->m_Properties.push_back(prop); @@ -104,32 +113,9 @@ void Object::NotifyPropertiesUpdated() { 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. - -template -void Object::serialize(ArchiveT &ar, const unsigned int version) { - ar & boost::serialization::make_nvp("InstanceName", d->m_InstanceName); - for (auto* prop : d->m_Properties) { - prop->serialize(ar, 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) {} - -// Explicitly instantiate for all uLib archives -template void Object::serialize(Archive::xml_oarchive &, const unsigned int); -template void Object::serialize(Archive::xml_iarchive &, const unsigned int); -template void Object::serialize(Archive::text_oarchive &, const unsigned int); -template void Object::serialize(Archive::text_iarchive &, const unsigned int); -template void Object::serialize(Archive::hrt_oarchive &, const unsigned int); -template void Object::serialize(Archive::hrt_iarchive &, const unsigned int); -template void Object::serialize(Archive::log_archive &, const unsigned int); - //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Core/Object.h b/src/Core/Object.h index 99bd91b..5ac02e9 100644 --- a/src/Core/Object.h +++ b/src/Core/Object.h @@ -92,9 +92,11 @@ public: //////////////////////////////////////////////////////////////////////////// // PROPERTIES // - void RegisterProperty(PropertyBase* prop); - void RegisterDynamicProperty(PropertyBase* prop); - const std::vector& GetProperties() const; + virtual void RegisterProperty(PropertyBase* property); + virtual void RegisterDynamicProperty(PropertyBase* property); + virtual void RegisterDisplayProperty(PropertyBase* property); + virtual const std::vector& GetProperties() const; + virtual const std::vector& GetDisplayProperties() 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. */ @@ -124,7 +126,7 @@ public: virtual void serialize(Archive::log_archive & ar, const unsigned int version) {} template - void save_override(ArchiveT &ar, const unsigned int version); + void save_override(ArchiveT &ar, const unsigned int version) {} void SaveConfig(std::ostream &os, int version = 0); void LoadConfig(std::istream &is, int version = 0); diff --git a/src/Core/Property.h b/src/Core/Property.h index 70e08e6..1d86bab 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -5,19 +5,24 @@ #include #include #include -#include // Added +#include #include #include -#include #include #include -#include +#include +#include #include "Core/Archives.h" #include "Core/Signal.h" #include "Core/Object.h" namespace uLib { +namespace Archive { +class property_register_archive; +class display_properties_archive; +} + /** * @brief Base class for properties to allow runtime listing and identification. */ @@ -27,7 +32,7 @@ public: virtual const std::string& GetName() const = 0; virtual const char* GetTypeName() const = 0; virtual std::string GetValueAsString() const = 0; - virtual std::type_index GetTypeIndex() const = 0; // Added + virtual std::type_index GetTypeIndex() const = 0; virtual const std::string& GetUnits() const = 0; virtual void SetUnits(const std::string& units) = 0; virtual const std::vector& GetEnumLabels() const { @@ -61,42 +66,30 @@ public: 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; + + virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) = 0; + virtual void serialize(Archive::display_properties_archive & ar, const unsigned int v) = 0; }; - - - - - - - /** * @brief Template class for typed properties. */ 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 = "", 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_ReadOnly(false) { - if (m_owner) { - m_owner->RegisterProperty(this); - } + 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 = "", 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_ReadOnly(false) { - if (m_owner) { - m_owner->RegisterProperty(this); - } + if (m_owner) m_owner->RegisterProperty(this); } - virtual ~Property() { - if (m_own) delete m_value; - } + virtual ~Property() { if (m_own) delete m_value; } // Identification virtual const std::string& GetName() const override { return m_name; } @@ -107,36 +100,16 @@ public: 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 { - try { - return boost::lexical_cast(*m_value); - } catch (const boost::bad_lexical_cast&) { - std::stringstream ss; - ss << *m_value; - return ss.str(); - } + try { return boost::lexical_cast(*m_value); } + catch (...) { std::stringstream ss; ss << *m_value; return ss.str(); } } // 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) { T val = value; - ValidateT(val); + if (m_HasRange) { if (val < m_Min) val = m_Min; if (val > m_Max) val = m_Max; } if (*m_value != val) { *m_value = val; ULIB_SIGNAL_EMIT(Property::PropertyChanged); @@ -150,65 +123,56 @@ public: void SetReadOnly(bool ro) { m_ReadOnly = ro; } virtual bool IsReadOnly() const override { return m_ReadOnly; } - 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 double GetMin() const override { return m_HasRange ? convert_to_double(m_Min) : 0.0; } + virtual double GetMax() const override { return m_HasRange ? convert_to_double(m_Max) : 0.0; } + + const T& GetMinTyped() const { return m_Min; } + const T& GetMaxTyped() const { return m_Max; } virtual bool HasDefault() const override { return m_HasDefault; } virtual std::string GetDefaultValueAsString() const override { - try { return boost::lexical_cast(m_Default); } - catch (...) { return ""; } + try { return boost::lexical_cast(m_Default); } catch (...) { return ""; } } - // Operators for seamless usage + // Operators operator const T&() const { return *m_value; } - Property& operator=(const T& value) { - Set(value); - return *this; - } + Property& operator=(const T& value) { Set(value); return *this; } - // Signals signals: virtual void PropertyChanged() { ULIB_SIGNAL_EMIT(Property::PropertyChanged); } +private: + template + static double convert_to_double(const U& val) { + return convert_to_double_impl(val, typename std::is_arithmetic::type()); + } + template + static double convert_to_double_impl(const U& val, std::true_type) { return (double)val; } + template + static double convert_to_double_impl(const U& val, std::false_type) { return 0.0; } + +public: // Serialization template - void serialize_impl(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp(m_name.c_str(), *m_value); + void serialize_helper(ArchiveT & ar, const unsigned int version) { + ar & boost::serialization::make_hrp(m_name.c_str(), *m_value, m_units.c_str()); } - void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } - void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } - void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } - void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } - void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } - 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 serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } + virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } + virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } + virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } + virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } + virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); } + virtual void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_helper(ar, v); } + + virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override; + virtual void serialize(Archive::display_properties_archive & ar, const unsigned int v) override; - virtual void Updated() override { - PropertyBase::Updated(); - this->PropertyChanged(); - } + virtual void Updated() override { PropertyBase::Updated(); this->PropertyChanged(); } -private: +protected: std::string m_name; std::string m_units; std::string m_group; @@ -224,20 +188,7 @@ private: }; /** - * @brief Conveninent typedefs for common property types. - */ -typedef Property StringProperty; -typedef Property IntProperty; -typedef Property UIntProperty; -typedef Property LongProperty; -typedef Property ULongProperty; -typedef Property FloatProperty; -typedef Property DoubleProperty; -typedef Property BoolProperty; - - -/** - * @brief Property specialized for enumerations, providing labels for GUI representations. + * @brief Property specialized for enumerations. */ class EnumProperty : public Property { public: @@ -248,68 +199,50 @@ public: const char* GetTypeName() const override { return "Enum"; } virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(EnumProperty)); } + template + void serialize_enum_helper(ArchiveT & ar, const unsigned int version) { + ar & boost::serialization::make_hrp_enum(m_name.c_str(), *m_value, m_Labels, m_units.c_str()); + } + + virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + virtual void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); } + + virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override; + virtual void serialize(Archive::display_properties_archive & ar, const unsigned int v) override; + private: std::vector m_Labels; }; - - - - - - - - - - -/** - * @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) \ - 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 -{ +class property_register_archive + : public boost::archive::detail::common_oarchive { +protected: Object* m_Object; + bool m_DisplayOnly; public: friend class boost::archive::detail::interface_oarchive; friend class boost::archive::save_access; - typedef boost::archive::detail::common_oarchive detail_common_oarchive; + using boost::archive::detail::common_oarchive::save_override; - property_register_archive(Object* obj) : + property_register_archive(Object* obj, bool displayOnly = false) : boost::archive::detail::common_oarchive(boost::archive::no_header), - m_Object(obj) {} + m_Object(obj), m_DisplayOnly(displayOnly) { + if (obj) m_Visited.insert(dynamic_cast(obj)); + } + + template property_register_archive &operator&(const T &t) { this->save_override(t); return *this; } + template property_register_archive &operator<<(const T &t) { this->save_override(t); return *this; } std::string GetCurrentGroup() const { std::string group; @@ -320,77 +253,91 @@ public: 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() : "", 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 register_property(Property& p) { + save_property_impl(p.GetName().c_str(), const_cast(p.Get()), p.GetUnits().c_str(), + p.HasRange(), p.GetMinTyped(), p.GetMaxTyped(), p.IsReadOnly()); + } + + void register_enum_property(EnumProperty& p) { + if (!m_Object) return; + EnumProperty* newP = new EnumProperty(m_Object, p.GetName(), const_cast(&p.Get()), p.GetEnumLabels(), p.GetUnits(), GetCurrentGroup()); + newP->SetReadOnly(p.IsReadOnly()); + if (m_DisplayOnly) { + m_Object->RegisterDisplayProperty(newP); + Object* obj = m_Object; + Object::connect(newP, &PropertyBase::Updated, [obj]() { obj->Updated(); }); + } else { + m_Object->RegisterDynamicProperty(newP); } } - template - void save_override(const boost::serialization::hrp_val &t) { + template void save_property_impl(const char* name, T& val, const char* units, bool hasRange, const T& minVal, const T& maxVal, bool isReadOnly) { 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); + Property* p = new Property(m_Object, name, &val, units ? units : "", GetCurrentGroup()); + set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic::type()); + p->SetReadOnly(isReadOnly); + if (m_DisplayOnly) { + m_Object->RegisterDisplayProperty(p); + Object* obj = m_Object; + Object::connect(p, &PropertyBase::Updated, [obj]() { obj->Updated(); }); + } else { + m_Object->RegisterDynamicProperty(p); + } } } - template - void save_override(const boost::serialization::hrp_enum &t) { + template static void set_range_helper(Property* p, bool hasRange, const U& minVal, const U& maxVal, std::true_type) { if (hasRange) p->SetRange(minVal, maxVal); } + template static void set_range_helper(Property* p, bool hasRange, const U& minVal, const U& maxVal, std::false_type) {} + + template void save_override(const boost::serialization::hrp &t) { + // To handle T correctly without deduction issues, we assume T can be passed to save_property_impl + T dummy = T(); // Ensure we can construct T + save_property_impl(t.name(), const_cast&>(t).value(), t.units(), t.has_range(), t.has_range() ? t.min_val() : dummy, t.has_range() ? t.max_val() : dummy, t.is_read_only()); + } + template void save_override(const boost::serialization::hrp_val &t) { + T dummy = T(); + save_property_impl(t.name(), const_cast&>(t).value(), t.units(), t.has_range(), t.has_range() ? t.min_val() : dummy, t.has_range() ? t.max_val() : dummy, t.is_read_only()); + } + 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() : "", GetCurrentGroup()); p->SetReadOnly(t.is_read_only()); - m_Object->RegisterDynamicProperty(p); + if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &PropertyBase::Updated, [obj]() { obj->Updated(); }); } + else { m_Object->RegisterDynamicProperty(p); } } } - - template - void save_override(const boost::serialization::hrp_enum_val &t) { + 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); + if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &PropertyBase::Updated, [obj]() { obj->Updated(); }); } + else { m_Object->RegisterDynamicProperty(p); } } } - // Handle standard NVPs by recursing (important for base classes) - template - void save_override(const boost::serialization::nvp &t) { + 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()); + void save_override(const std::string &t) {} + template void save_override(T * const & t) { + if (!t) return; + this->save_pointer_helper(t, typename boost::is_base_of::type()); } - - template - void save_helper(const T &t, boost::mpl::true_) { - boost::serialization::serialize_adl(*this, const_cast(t), 0); + template void save_pointer_helper(T* t, boost::mpl::true_) { + const void* ptr = dynamic_cast(t); + if (m_Visited.find(ptr) != m_Visited.end()) return; + m_Visited.insert(ptr); + this->save_override(*t); } + template void save_pointer_helper(T* t, boost::mpl::false_) {} + 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); } + void save_helper(const std::string &t, boost::mpl::true_) {} + template void save_helper(const T &t, boost::mpl::false_) {} - 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) {} void save_override(const boost::archive::object_reference_type & t) {} void save_override(const boost::archive::version_type & t) {} @@ -400,30 +347,45 @@ public: void save_override(const boost::archive::class_name_type & t) {} void save_override(const boost::archive::tracking_type & t) {} -private: +protected: std::vector m_GroupStack; + std::set m_Visited; }; +class display_properties_archive : public property_register_archive { +public: + friend class boost::archive::detail::interface_oarchive; + display_properties_archive(Object* obj) : property_register_archive(obj, true) {} +}; +} // namespace Archive +} // namespace uLib + +namespace uLib { + +template +inline void Property::serialize(Archive::property_register_archive & ar, const unsigned int v) { + ar.register_property(*this); +} + +template +inline void Property::serialize(Archive::display_properties_archive & ar, const unsigned int v) { + ar.register_property(*this); +} + +inline void EnumProperty::serialize(Archive::property_register_archive & ar, const unsigned int v) { + ar.register_enum_property(*this); +} + +inline void EnumProperty::serialize(Archive::display_properties_archive & ar, const unsigned int v) { + ar.register_enum_property(*this); +} + +namespace Archive { -/** - * @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 { \ @@ -434,6 +396,18 @@ private: \ } _prop_activator{this}; } // namespace Archive + +template +void serialize_properties_helper(ArchiveT &ar, const std::vector &props, unsigned int version) { + for (auto* prop : props) prop->serialize(ar, version); +} + +template +void Object::serialize(ArchiveT &ar, const unsigned int version) { + ar & boost::serialization::make_nvp("InstanceName", this->GetInstanceName()); + serialize_properties_helper(ar, this->GetProperties(), version); +} + } // namespace uLib #endif // U_CORE_PROPERTY_H diff --git a/src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp b/src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp index 435b352..2ab8694 100644 --- a/src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp +++ b/src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp @@ -61,8 +61,8 @@ int main(int argc, char** argv) { vtkTess.AddToViewer(viewer); // Color them differently - vtkActor::SafeDownCast(vtkBox.GetProp())->GetProperty()->SetColor(0.8, 0.2, 0.2); // Redish box - vtkActor::SafeDownCast(vtkTess.GetProp())->GetProperty()->SetColor(0.2, 0.8, 0.2); // Greenish tess + vtkBox.SetColor(0.8, 0.2, 0.2); // Redish box + vtkTess.SetColor(0.2, 0.8, 0.2); // Greenish tess // Position tessellated solid away from box Matrix4f trans = Matrix4f::Identity(); diff --git a/src/Vtk/HEP/Geant/vtkBoxSolid.cpp b/src/Vtk/HEP/Geant/vtkBoxSolid.cpp index 1626737..59d915c 100644 --- a/src/Vtk/HEP/Geant/vtkBoxSolid.cpp +++ b/src/Vtk/HEP/Geant/vtkBoxSolid.cpp @@ -13,43 +13,104 @@ #include #include #include +#include +#include +#include +#include +#include "Vtk/Math/vtkDense.h" namespace uLib { namespace Vtk { vtkBoxSolid::vtkBoxSolid(Geant::BoxSolid *content) - : vtkGeantSolid(content), m_BoxContent(content) { - // Re-run Update for box-specific pipe + : vtkGeantSolid(content), m_BoxContent(content), m_BoxPuppet(nullptr) { + + if (m_BoxContent && m_BoxContent->GetObject()) { + m_BoxPuppet = new vtkContainerBox(m_BoxContent->GetObject()); + // Use the specialized box puppet's representation as our main prop + this->SetProp(m_BoxPuppet->GetProp()); + } + + // Connect the model's Updated event to updateTransform to ensure VTK sync + Object::connect(m_BoxContent, &uLib::Object::Updated, this, &vtkBoxSolid::UpdateTransform); + + // Initial sync this->Update(); } -vtkBoxSolid::~vtkBoxSolid() {} +vtkBoxSolid::~vtkBoxSolid() { + if (m_BoxPuppet) { + delete m_BoxPuppet; + } +} void vtkBoxSolid::Update() { this->UpdateGeometry(); this->UpdateTransform(); + // Ensure base Puppet properties (color, opacity, etc) are applied + this->Puppet::Update(); +} + +void vtkBoxSolid::SyncFromVtk() { + vtkProp3D *root = vtkProp3D::SafeDownCast(this->GetProp()); + if (root && m_BoxContent) { + vtkMatrix4x4 *rootMat = root->GetUserMatrix(); + if (rootMat) { + Matrix4f vtkWorld = VtkToMatrix4f(rootMat); + m_BoxContent->SetTransform(vtkWorld); + m_BoxContent->Updated(); + } + } } void vtkBoxSolid::UpdateGeometry() { - if (!m_BoxContent || !m_BoxContent->GetObject()) { - // Fallback to base tessellation if no model object + if (!m_BoxContent || !m_BoxContent->GetObject() || !m_BoxPuppet) { + // Fallback to base tessellation if no model object is available vtkGeantSolid::UpdateGeometry(); return; } - // Use the underlying ContainerBox for precise geometry - Vector3f size = m_BoxContent->GetObject()->GetSize(); - - vtkNew cube; - cube->SetXLength(size(0)); - cube->SetYLength(size(1)); - cube->SetZLength(size(2)); - cube->Update(); + // The vtkContainerBox manages its own geometry update + m_BoxPuppet->Update(); +} - vtkPolyData *poly = GetPolyData(); - if (poly) { - poly->ShallowCopy(cube->GetOutput()); - poly->Modified(); +void vtkBoxSolid::UpdateTransform() { + if (!m_BoxContent || !m_BoxPuppet) { + vtkGeantSolid::UpdateTransform(); + return; + } + + // 1. Sync the inner box TRS (local box properties like size and offset) + m_BoxPuppet->Update(); + + // 2. Sync the Geant4-level placement (world position/rotation) + vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); + if (root && m_BoxContent->GetPhysical()) { + auto *phys = m_BoxContent->GetPhysical(); + G4ThreeVector pos = phys->GetTranslation(); + const G4RotationMatrix *rot = phys->GetRotation(); + + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->Identity(); + transform->Translate(pos.x(), pos.y(), pos.z()); + + if (rot) { + // G4RotationMatrix stores the inverse of the rotation for placement + G4RotationMatrix invRot = rot->inverse(); + double elements[16] = { + invRot.xx(), invRot.xy(), invRot.xz(), 0, + invRot.yx(), invRot.yy(), invRot.yz(), 0, + invRot.zx(), invRot.zy(), invRot.zz(), 0, + 0, 0, 0, 1 + }; + vtkSmartPointer mat = vtkSmartPointer::New(); + mat->DeepCopy(elements); + transform->Concatenate(mat); + } + // Apply the Geant4 transform on top of the local box's UserMatrix + root->SetUserTransform(transform); + } else if (root) { + root->SetUserTransform(nullptr); } } diff --git a/src/Vtk/HEP/Geant/vtkBoxSolid.h b/src/Vtk/HEP/Geant/vtkBoxSolid.h index 7df7715..64946c8 100644 --- a/src/Vtk/HEP/Geant/vtkBoxSolid.h +++ b/src/Vtk/HEP/Geant/vtkBoxSolid.h @@ -26,7 +26,12 @@ #ifndef U_VTKBOXSOLID_H #define U_VTKBOXSOLID_H +#include "Core/Types.h" +#include "Core/Property.h" +#include "Core/Serializable.h" + #include "vtkGeantSolid.h" +#include "Vtk/Math/vtkContainerBox.h" namespace uLib { namespace Vtk { @@ -35,15 +40,27 @@ namespace Vtk { * @brief VTK Puppet for visualizing a Geant::BoxSolid. */ class vtkBoxSolid : public vtkGeantSolid { + uLibTypeMacro(vtkBoxSolid, uLib::Vtk::vtkGeantSolid) + public: vtkBoxSolid(Geant::BoxSolid *content); virtual ~vtkBoxSolid(); virtual void Update() override; virtual void UpdateGeometry() override; + virtual void UpdateTransform() override; + virtual void SyncFromVtk() override; + + template + void serialize(Ar &ar, const unsigned int version) { + ar & NVP("BoxSolid", *m_BoxContent); + } + -protected: Geant::BoxSolid *m_BoxContent; + vtkContainerBox *m_BoxPuppet; + + ULIB_DECLARE_PROPERTIES(vtkBoxSolid) }; } // namespace Vtk diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index 74640fb..f7518b4 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -54,6 +54,7 @@ struct ContainerBoxData { vtkSmartPointer m_Affine; uLib::Connection m_UpdateSignal; + ContainerBoxData() : m_Cube(vtkSmartPointer::New()), m_Axes(vtkSmartPointer::New()), m_VtkAsm(vtkSmartPointer::New()), diff --git a/src/Vtk/Math/vtkContainerBox.h b/src/Vtk/Math/vtkContainerBox.h index b27a9c5..e1c18e1 100644 --- a/src/Vtk/Math/vtkContainerBox.h +++ b/src/Vtk/Math/vtkContainerBox.h @@ -62,9 +62,9 @@ protected: virtual void InstallPipe(); struct ContainerBoxData *d; - Content *m_Content; - bool m_BlockUpdate = false; + ContainerBox *m_Content; + ULIB_DECLARE_PROPERTIES(vtkContainerBox) }; } // namespace Vtk diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 95015c5..f705ffd 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -560,28 +560,55 @@ bool Puppet::IsSelected() const return pd->m_Selected; } +void Puppet::ApplyPuppetTransform(vtkProp3D* prop) +{ + if (!prop) return; + if (auto* content = this->GetContent()) { + if (auto* tr = dynamic_cast(content)) { + vtkNew m; + Matrix4fToVtk(tr->GetMatrix(), m); + prop->SetUserMatrix(m); + prop->Modified(); + } + } +} + +void Puppet::SyncFromVtk() +{ + if (auto* content = this->GetContent()) { + if (auto* tr = dynamic_cast(content)) { + if (auto* proxy = this->GetProxyProp()) { + if (vtkMatrix4x4* mat = proxy->GetUserMatrix()) { + tr->FromMatrix(VtkToMatrix4f(mat)); + content->Updated(); + } + } + } + } +} + void Puppet::Update() { - // 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); + // Apply content transform via virtual GetProp() / ApplyPuppetTransform(), + // so all derived classes benefit without duplicating the matrix code. + this->ApplyPuppetTransform(vtkProp3D::SafeDownCast(this->GetProp())); - pd->ApplyAppearance(pd->m_Prop); + // Use virtual GetProp() for appearance so overriders (e.g. vtkVoxImage) + // that never call SetProp() are handled correctly. + pd->ApplyAppearance(this->GetProp()); if (pd->m_Selected) { pd->UpdateHighlight(); } - - if (pd->m_Prop) { - if (pd->m_ShowBoundingBox) { - double* bounds = pd->m_Prop->GetBounds(); + + if (auto* prop = this->GetProp()) { + if (pd->m_ShowBoundingBox && pd->m_OutlineSource) { + double* bounds = 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); + if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) { + pd->m_CubeAxesActor->SetBounds(prop->GetBounds()); } } @@ -598,6 +625,7 @@ void Puppet::Update() } + void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) { } diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index f735833..d797f84 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -35,6 +35,8 @@ #include #include #include +#include +#include // vtk classes forward declaration // class vtkProp; @@ -106,7 +108,7 @@ uLibTypeMacro(Puppet, uLib::Object) * 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() {} + virtual void SyncFromVtk(); enum Representation { Points = 0, @@ -149,6 +151,7 @@ protected: void ApplyAppearance(vtkProp *prop); void ApplyTransform(vtkProp3D *p3d); + void ApplyPuppetTransform(vtkProp3D *p3d); std::vector m_DisplayProperties; mutable uLib::RecursiveMutex m_UpdateMutex; @@ -179,10 +182,17 @@ class display_properties_archive : public boost::archive::detail::common_oarchive< display_properties_archive> { public: - display_properties_archive(Vtk::Puppet *puppet) + friend class boost::archive::detail::interface_oarchive; + friend class boost::archive::save_access; + + using boost::archive::detail::common_oarchive::save_override; + display_properties_archive(Vtk::Puppet *p) : boost::archive::detail::common_oarchive( boost::archive::no_header), - m_Puppet(puppet) {} + m_Puppet(p) { + if (p) + m_Visited.insert(dynamic_cast(p)); + } std::string GetCurrentGroup() const { std::string group; @@ -234,6 +244,24 @@ public: m_GroupStack.pop_back(); } + // Follow pointers to discover properties in child objects + template + void save_override(T * const & t) { + if (!t) return; + this->save_pointer_helper(t, typename boost::is_base_of::type()); + } + + template + void save_pointer_helper(T* t, boost::mpl::true_) { + const void* ptr = dynamic_cast(t); + if (m_Visited.find(ptr) != m_Visited.end()) return; + m_Visited.insert(ptr); + this->save_override(*t); + } + + template + void save_pointer_helper(T* t, boost::mpl::false_) {} + // Recursion for nested classes, ignore primitives template void save_override(const T &t) { this->save_helper(t, typename boost::is_class::type()); @@ -243,6 +271,8 @@ public: boost::serialization::serialize_adl(*this, const_cast(t), 0); } + void save_helper(const std::string &t, boost::mpl::true_) {} + template void save_helper(const T &t, boost::mpl::false_) {} void save_override(const boost::archive::object_id_type &t) {} @@ -257,6 +287,7 @@ public: private: Vtk::Puppet *m_Puppet; std::vector m_GroupStack; + std::set m_Visited; }; } // namespace Archive