refactor: extend Object property system and implement recursive property discovery in Vtk::Puppet archive

This commit is contained in:
AndreaRigoni
2026-04-03 08:54:37 +00:00
parent 6396bdfebf
commit a6a1539663
12 changed files with 459 additions and 272 deletions

View File

@@ -80,6 +80,9 @@ template <class ArchiveImplementation> class polymorphic_iarchive_route;
namespace boost {
namespace serialization {
template <typename T> struct hrp;
template <typename T> struct hrp_val;
template <typename T> struct hrp_enum;
template <typename T> struct hrp_enum_val;
}
} // namespace boost
@@ -177,6 +180,24 @@ public:
return *this->This();
}
template <class T>
Archive &operator>>(const boost::serialization::hrp_val<T> &t) {
this->This()->load_override(const_cast<boost::serialization::hrp_val<T> &>(t));
return *this->This();
}
template <class T>
Archive &operator>>(const boost::serialization::hrp_enum<T> &t) {
this->This()->load_override(const_cast<boost::serialization::hrp_enum<T> &>(t));
return *this->This();
}
template <class T>
Archive &operator>>(const boost::serialization::hrp_enum_val<T> &t) {
this->This()->load_override(const_cast<boost::serialization::hrp_enum_val<T> &>(t));
return *this->This();
}
// the & operator
template <class T> Archive &operator&(T &t) { return *(this->This()) >> t; }
@@ -190,6 +211,21 @@ public:
return *(this->This()) >> t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp_val<T> &t) {
return *(this->This()) >> t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp_enum<T> &t) {
return *(this->This()) >> t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp_enum_val<T> &t) {
return *(this->This()) >> t;
}
// the == operator
template <class T> Archive &operator==(T &t) { return this->operator&(t); }
@@ -229,12 +265,62 @@ public:
this->This()->save_override(t);
return *this->This();
}
template <class T> Archive &operator<<(const boost::serialization::hrp<T> &t) {
this->This()->save_override(t);
return *this->This();
}
template <class T> Archive &operator<<(const boost::serialization::hrp_val<T> &t) {
this->This()->save_override(t);
return *this->This();
}
template <class T> Archive &operator<<(const boost::serialization::hrp_enum<T> &t) {
this->This()->save_override(t);
return *this->This();
}
template <class T> Archive &operator<<(const boost::serialization::hrp_enum_val<T> &t) {
this->This()->save_override(t);
return *this->This();
}
template <class T> Archive &operator<<(const boost::serialization::nvp<T> &t) {
this->This()->save_override(t);
return *this->This();
}
// the & operator
template <class T> Archive &operator&(const T &t) {
return *this->This() << t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp<T> &t) {
return *this->This() << t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp_val<T> &t) {
return *this->This() << t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp_enum<T> &t) {
return *this->This() << t;
}
template <class T>
Archive &operator&(const boost::serialization::hrp_enum_val<T> &t) {
return *this->This() << t;
}
template <class T>
Archive &operator&(const boost::serialization::nvp<T> &t) {
return *this->This() << t;
}
// the == operator
template <class T> Archive &operator==(T &t) { return this->operator&(t); }

View File

@@ -21,6 +21,7 @@ set(HEADERS
StringReader.h
Threads.h
Monitor.h
Property.h
Types.h
Uuid.h
Vector.h

View File

@@ -65,10 +65,19 @@ public:
std::vector<Slot> slov;
std::vector<PropertyBase*> m_Properties;
std::vector<PropertyBase*> m_DynamicProperties;
std::vector<PropertyBase*> 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<PropertyBase*>& 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 <class ArchiveT>
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 <class ArchiveT>
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);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

View File

@@ -92,9 +92,11 @@ public:
////////////////////////////////////////////////////////////////////////////
// PROPERTIES //
void RegisterProperty(PropertyBase* prop);
void RegisterDynamicProperty(PropertyBase* prop);
const std::vector<PropertyBase*>& GetProperties() const;
virtual void RegisterProperty(PropertyBase* property);
virtual void RegisterDynamicProperty(PropertyBase* property);
virtual void RegisterDisplayProperty(PropertyBase* property);
virtual const std::vector<PropertyBase*>& GetProperties() const;
virtual const std::vector<PropertyBase*>& 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 <class ArchiveT>
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);

View File

@@ -5,19 +5,24 @@
#include <vector>
#include <sstream>
#include <typeinfo>
#include <typeindex> // Added
#include <typeindex>
#include <boost/serialization/nvp.hpp>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/serialization/serialization.hpp>
#include <set>
#include <boost/type_traits/is_base_of.hpp>
#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<std::string>& 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 <typename T>
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<std::string>(*m_value);
} catch (const boost::bad_lexical_cast&) {
std::stringstream ss;
ss << *m_value;
return ss.str();
}
try { return boost::lexical_cast<std::string>(*m_value); }
catch (...) { std::stringstream ss; ss << *m_value; return ss.str(); }
}
// Accessors
const T& Get() const { return *m_value; }
template<typename U = T>
typename std::enable_if<std::is_arithmetic<U>::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 U = T>
typename std::enable_if<!std::is_arithmetic<U>::value, void>::type
ValidateT(T& val) {
}
void Set(const T& value) {
T val = value;
ValidateT<T>(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<T>::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 U = T>
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
GetMinT() const { return (double)m_Min; }
template<typename U = T>
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
GetMinT() const { return 0.0; }
template<typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
GetMaxT() const { return (double)m_Max; }
template<typename U = T>
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
GetMaxT() const { return 0.0; }
virtual double GetMin() const override { return GetMinT<T>(); }
virtual double GetMax() const override { return GetMaxT<T>(); }
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<std::string>(m_Default); }
catch (...) { return ""; }
try { return boost::lexical_cast<std::string>(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<T>::PropertyChanged); }
private:
template <typename U>
static double convert_to_double(const U& val) {
return convert_to_double_impl(val, typename std::is_arithmetic<U>::type());
}
template <typename U>
static double convert_to_double_impl(const U& val, std::true_type) { return (double)val; }
template <typename U>
static double convert_to_double_impl(const U& val, std::false_type) { return 0.0; }
public:
// Serialization
template <class ArchiveT>
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<std::string> StringProperty;
typedef Property<int> IntProperty;
typedef Property<unsigned int> UIntProperty;
typedef Property<long> LongProperty;
typedef Property<unsigned long> ULongProperty;
typedef Property<float> FloatProperty;
typedef Property<double> DoubleProperty;
typedef Property<Bool_t> BoolProperty;
/**
* @brief Property specialized for enumerations, providing labels for GUI representations.
* @brief Property specialized for enumerations.
*/
class EnumProperty : public Property<int> {
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 <class ArchiveT>
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<std::string> 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<type> name = Property<type>(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<uLib::Archive::property_register_archive>
: public uLib_interface_oarchive<uLib::Archive::property_register_archive> {};
} // 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<property_register_archive>
{
class property_register_archive
: public boost::archive::detail::common_oarchive<property_register_archive> {
protected:
Object* m_Object;
bool m_DisplayOnly;
public:
friend class boost::archive::detail::interface_oarchive<property_register_archive>;
friend class boost::archive::save_access;
typedef boost::archive::detail::common_oarchive<property_register_archive> detail_common_oarchive;
using boost::archive::detail::common_oarchive<property_register_archive>::save_override;
property_register_archive(Object* obj) :
property_register_archive(Object* obj, bool displayOnly = false) :
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
m_Object(obj) {}
m_Object(obj), m_DisplayOnly(displayOnly) {
if (obj) m_Visited.insert(dynamic_cast<const void*>(obj));
}
template<class T> property_register_archive &operator&(const T &t) { this->save_override(t); return *this; }
template<class T> 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<class T>
void save_override(const boost::serialization::hrp<T> &t) {
if (m_Object) {
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(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<class T> void register_property(Property<T>& p) {
save_property_impl(p.GetName().c_str(), const_cast<T&>(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<int*>(&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<class T>
void save_override(const boost::serialization::hrp_val<T> &t) {
template<class T> 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<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp_val<T>&>(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<T>* p = new Property<T>(m_Object, name, &val, units ? units : "", GetCurrentGroup());
set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic<T>::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<class T>
void save_override(const boost::serialization::hrp_enum<T> &t) {
template<class U> static void set_range_helper(Property<U>* p, bool hasRange, const U& minVal, const U& maxVal, std::true_type) { if (hasRange) p->SetRange(minVal, maxVal); }
template<class U> static void set_range_helper(Property<U>* p, bool hasRange, const U& minVal, const U& maxVal, std::false_type) {}
template<class T> void save_override(const boost::serialization::hrp<T> &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<boost::serialization::hrp<T>&>(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<class T> void save_override(const boost::serialization::hrp_val<T> &t) {
T dummy = T();
save_property_impl(t.name(), const_cast<boost::serialization::hrp_val<T>&>(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<class T> void save_override(const boost::serialization::hrp_enum<T> &t) {
if (m_Object) {
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(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<class T>
void save_override(const boost::serialization::hrp_enum_val<T> &t) {
template<class T> void save_override(const boost::serialization::hrp_enum_val<T> &t) {
if (m_Object) {
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum_val<T>&>(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<class T>
void save_override(const boost::serialization::nvp<T> &t) {
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
if (t.name()) m_GroupStack.push_back(t.name());
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
if (t.name()) m_GroupStack.pop_back();
}
// Recursion for nested classes, ignore primitives
template<class T>
void save_override(const T &t) {
this->save_helper(t, typename boost::is_class<T>::type());
void save_override(const std::string &t) {}
template<class T> void save_override(T * const & t) {
if (!t) return;
this->save_pointer_helper(t, typename boost::is_base_of<Object, T>::type());
}
template<class T>
void save_helper(const T &t, boost::mpl::true_) {
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
template<class T> void save_pointer_helper(T* t, boost::mpl::true_) {
const void* ptr = dynamic_cast<const void*>(t);
if (m_Visited.find(ptr) != m_Visited.end()) return;
m_Visited.insert(ptr);
this->save_override(*t);
}
template<class T> void save_pointer_helper(T* t, boost::mpl::false_) {}
template<class T> void save_override(const T &t) { this->save_helper(t, typename boost::is_class<T>::type()); }
template<class T> void save_helper(const T &t, boost::mpl::true_) { boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0); }
void save_helper(const std::string &t, boost::mpl::true_) {}
template<class T> void save_helper(const T &t, boost::mpl::false_) {}
template<class T>
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<std::string> m_GroupStack;
std::set<const void*> m_Visited;
};
class display_properties_archive : public property_register_archive {
public:
friend class boost::archive::detail::interface_oarchive<display_properties_archive>;
display_properties_archive(Object* obj) : property_register_archive(obj, true) {}
};
} // namespace Archive
} // namespace uLib
namespace uLib {
template <typename T>
inline void Property<T>::serialize(Archive::property_register_archive & ar, const unsigned int v) {
ar.register_property(*this);
}
template <typename T>
inline void Property<T>::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 <class ArchiveT>
void serialize_properties_helper(ArchiveT &ar, const std::vector<PropertyBase*> &props, unsigned int version) {
for (auto* prop : props) prop->serialize(ar, version);
}
template <class ArchiveT>
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

View File

@@ -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();

View File

@@ -13,43 +13,104 @@
#include <vtkCubeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkAssembly.h>
#include <vtkTransform.h>
#include <vtkMatrix4x4.h>
#include <Geant4/G4VPhysicalVolume.hh>
#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<vtkCubeSource> 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<vtkTransform> transform = vtkSmartPointer<vtkTransform>::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<vtkMatrix4x4> mat = vtkSmartPointer<vtkMatrix4x4>::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);
}
}

View File

@@ -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 <typename Ar>
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

View File

@@ -54,6 +54,7 @@ struct ContainerBoxData {
vtkSmartPointer<vtkMatrix4x4> m_Affine;
uLib::Connection m_UpdateSignal;
ContainerBoxData() : m_Cube(vtkSmartPointer<vtkActor>::New()),
m_Axes(vtkSmartPointer<vtkActor>::New()),
m_VtkAsm(vtkSmartPointer<vtkAssembly>::New()),

View File

@@ -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

View File

@@ -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<uLib::TRS*>(content)) {
vtkNew<vtkMatrix4x4> m;
Matrix4fToVtk(tr->GetMatrix(), m);
prop->SetUserMatrix(m);
prop->Modified();
}
}
}
void Puppet::SyncFromVtk()
{
if (auto* content = this->GetContent()) {
if (auto* tr = dynamic_cast<uLib::TRS*>(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)
{
}

View File

@@ -35,6 +35,8 @@
#include <iomanip>
#include <ostream>
#include <vector>
#include <set>
#include <boost/type_traits/is_base_of.hpp>
// 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<uLib::PropertyBase *> 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<display_properties_archive>;
friend class boost::archive::save_access;
using boost::archive::detail::common_oarchive<display_properties_archive>::save_override;
display_properties_archive(Vtk::Puppet *p)
: boost::archive::detail::common_oarchive<display_properties_archive>(
boost::archive::no_header),
m_Puppet(puppet) {}
m_Puppet(p) {
if (p)
m_Visited.insert(dynamic_cast<const void *>(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<class T>
void save_override(T * const & t) {
if (!t) return;
this->save_pointer_helper(t, typename boost::is_base_of<uLib::Object, T>::type());
}
template<class T>
void save_pointer_helper(T* t, boost::mpl::true_) {
const void* ptr = dynamic_cast<const void*>(t);
if (m_Visited.find(ptr) != m_Visited.end()) return;
m_Visited.insert(ptr);
this->save_override(*t);
}
template<class T>
void save_pointer_helper(T* t, boost::mpl::false_) {}
// Recursion for nested classes, ignore primitives
template <class T> void save_override(const T &t) {
this->save_helper(t, typename boost::is_class<T>::type());
@@ -243,6 +271,8 @@ public:
boost::serialization::serialize_adl(*this, const_cast<T &>(t), 0);
}
void save_helper(const std::string &t, boost::mpl::true_) {}
template <class T> 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<std::string> m_GroupStack;
std::set<const void *> m_Visited;
};
} // namespace Archive