5.6 KiB
Properties and the vtk-gui representation
This is the rationale behind the connection between TRS properties and Puppet Transformation.
The properties from model get propoagated via Object signalling system (the Update signal) to the vtkRepresentation and to the Qt widgets so that the overall transformation of the model reflects into a modification of its representation in vtk and in the gui.
In addition the properties need to be adjusted also from vtk, for example if user uses handlerwidget to change the transformation this is eventually applied to Puppet and Puppet should propagate the transformation change to the vtk representation object (for instance vtkContainerBox) and the latter eventually propagates the change into the model.
the Puppet or the vtk representation wrapper ( vtkContainerBox for instance is the wrapper od ContainerBox ) should not directly show the transformation of the handlerwidget but it should show the transformation of the model once applied so we are always seeing the actual aspect of the model reflected to the vtk representation and not the other way around.
So in syntesis the model is the master and the vtk representation and the gui are the slaves of any modification, but the vtkHandlerWidget is able to apply a transform that should be applied to the model and then the model should propagate the transformation change to the vtk representation and to the gui.
The Puppet
The puppet is the proxy of the spatial placement of objects in the scene. Puppets should have an internal ContainerBox that is shown in the scene around the content to be able to pick Puppet from vtkViewport using the handler widget. The HandlerWidget moves the Puppet ContainerBox (the red Highlight element whe selected) to reflect the handler current transformation, but the transformation is propagated to the derived Puppet classes like vtkContainerBox.
The vtkHandlerWidget should handle the transformation of the puppet internal ContainerBox. The changes of the ContainerBox will be propagated to the derived classes and eventually to the model.
ACTIVATE PROPERTIES
ULIB_ACTIVATE_PROPERTIES must run after all member initialization, with the vtable pointing to the most-derived type. This is why it has to be in each constructor — in C++, virtual dispatch only works correctly after a class's vtable is installed, which happens at the start of each level's constructor body.
Option 1 — End-of-class macro (no constructor boilerplate)
Declare a private member activator as the last member of the class. Its constructor runs after all other members, and at that point the vtable is already Derived's:
// In Property.h, add alongside ULIB_ACTIVATE_PROPERTIES:
#define ULIB_DECLARE_PROPERTIES(SelfType)
private:
struct _PropActivator {
_PropActivator(SelfType* self) {
uLib::Archive::property_register_archive ar(self);
ar & *self;
}
} _prop_activator{this};
Usage in ContainerBox.h — place it just before the closing brace:
class ContainerBox : public TRS { public: // ... all constructors, no more ULIB_ACTIVATE_PROPERTIES(*this)
ULIB_DECLARE_PROPERTIES(ContainerBox) // ← replaces all 3 constructor calls
}; Tradeoff: Works perfectly for single-level classes. For hierarchies where multiple levels use the macro, RegisterDynamicProperty must deduplicate by name (skip if already registered). Requires one line per class in the class body, but zero lines in constructors.
Option 2 — Lazy init via virtual InitProperties() in Object
Modify Object to call a virtual hook on first GetProperties():
// In Object.h: class Object { protected: virtual void InitProperties() {} // override in derived public: const std::vector<PropertyBase*>& GetProperties() const { if (!m_propertiesInitialized) { const_cast<Object*>(this)->m_propertiesInitialized = true; const_cast<Object*>(this)->InitProperties(); } return m_properties; } }; Then a CRTP base handles the rest without any macro:
template class PropertyObject : public Object { protected: void InitProperties() override { uLib::Archive::property_register_archive ar(this); ar & static_cast<Derived>(this); } }; Usage — just change the base class:
class ContainerBox : public PropertyObject, public TRS { ... }; // Nothing else needed — properties activated on first GetProperties() call Tradeoff: Most "automatic" — pure inheritance, no constructor or class-body macros. But requires modifying Object (adding m_propertiesInitialized flag + virtual hook), and lazy init means properties aren't available until first access. Also doesn't work well with multiple inheritance (which TRS likely involves).
Option 3 — CRTP doesn't work from the base constructor Just to be explicit: a CRTP base that calls ULIB_ACTIVATE_PROPERTIES in its own constructor won't work, because when PropertyObject's constructor runs, the vtable is PropertyObject's — Derived::serialize() hasn't been installed yet. So ar & *self calls Object::serialize() (a no-op).
Recommendation Option 1 is the least invasive and safest. Add deduplication to RegisterDynamicProperty in Object.cpp to guard against re-registration when hierarchies stack activators, then replace every ULIB_ACTIVATE_PROPERTIES(*this) in constructors with a single ULIB_DECLARE_PROPERTIES(ClassName) at the end of the class body.
Option 2 is cleaner to use but requires changing the Object interface and has the lazy-init semantic change — only worth it if you want zero-touch activation across the entire framework.