refactor: update transformation system, improve template readability, and reorganize VTK assembly management

This commit is contained in:
AndreaRigoni
2026-03-31 16:04:03 +00:00
parent 22d0041942
commit d4fd2d3914
30 changed files with 568 additions and 501 deletions

View File

@@ -11,6 +11,12 @@
"CMAKE_BUILD_TYPE": "Debug", "CMAKE_BUILD_TYPE": "Debug",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
} }
},
{
"name": "mutom",
"description": "",
"displayName": "",
"inherits": []
} }
] ]
} }

View File

@@ -1,73 +0,0 @@
# Transformation Flow and Synchronization System
This document describes how transformations are applied and synchronized between the interactive 3D viewport, the visualization puppets, and the underlying mathematical models within the `uLib` framework.
## Architecture Overview
The system follows a Model-View-Controller (MVC) like pattern where:
- **Model**: `uLib::AffineTransform` (or derived classes like `ContainerBox`).
- **View/Puppet**: `uLib::Vtk::Puppet` (and specialized derivations like `Vtk::Assembly`).
- **Controller/Interaction**: `vtkHandlerWidget` (the transformation gizmo).
---
## 1. Interaction Flow (Gizmo -> Model)
When a user interacts with the `vtkHandlerWidget` (dragging arrows, rings, or cubes), the following chain of events occurs:
```mermaid
sequenceDiagram
participant User
participant HW as vtkHandlerWidget
participant VP as vtkViewport
participant P as vtkPuppet
participant M as uLib Model
User->>HW: Drag handle (MouseMove)
HW->>HW: Calculate Delta Matrix (op)
HW->>HW: Total = StartState * op
HW->>HW: Decompose Total into P, O, S
HW->>P: SetPosition, SetOrientation, SetScale
HW-->>VP: Invoke InteractionEvent
VP->>P: SyncFromVtk()
P->>P: Get local matrix from VTK Prop
P->>M: SetMatrix(matrix)
M-->>M: Update local properties (P, O, S)
M-->>P: Emit Updated signal
P->>P: Puppet::Update()
P->>P: (Redundant sanity write to Prop)
```
### Key Principles:
- **Single Source of Truth**: The `uLib::AffineTransform` is the owner of the transformation state.
- **Internal TRS vs UserMatrix**: We apply transformations directly to VTK's internal `Position`, `Orientation`, and `Scale` properties. This ensures the data is "visible" to VTK actors and simplifies decomposition.
- **Cumulative Bias Avoidance**: The `HandlerWidget` calculates transformations relative to the state at the start of the click, preventing numerical drift during a single drag operation.
---
## 2. Synchronization Loop Resolution
To prevent infinite loops and "double-transformation" artifacts (especially in assemblies), the following protections are in place:
1. **Hierarchy Isolation**: The `Puppet` base class distinguishes between the **Root Property** (which receives the puppet's master transformation) and **Sub-Parts** (which only receive appearance updates like color/visibility). This prevents parts from inheriting the same displacement twice.
2. **Re-entrancy Guards**: Puppets use an `m_InUpdate` flag to prevent a feedback loop where `SyncFromVtk` triggers a Model Update, which then re-triggers the Puppet Update.
3. **Signal Blocking**: In specialized cases (like `vtkAssembly`), `m_BlockUpdate` is used to prevent the model-to-puppet push during a puppet-to-model sync.
---
## 3. Undo System (Ctrl-Z)
### Current Implementation (Delta Chain)
Currently, the system maintains a `m_TransformChain` of delta matrices.
- **Record**: After every drag, a delta matrix ($M_{delta} = M_{end} \cdot M_{start}^{-1}$) is appended to the chain.
- **Undo**: The last delta is removed, and the prop is reconstructed by reapplying the remaining chain from a `BaseMatrix`.
### Planned Improvement (TRS Snapshots)
We are migrating to a `uLib::TRS` snapshot system for Undo.
- **Record**: At the start of a drag, the current `TRS` state of the object is pushed to the `m_UndoStack`.
- **Undo**: The top `TRS` is popped and applied directly to the model.
This approach is more robust because:
- It eliminates matrix multiplication error accumulation.
- It bypasses rotation convention/order issues (Gimbal lock in deltas).
- It returns the object to exactly its previous property values.

21
docs/update_properties.md Normal file
View File

@@ -0,0 +1,21 @@
# 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.

View File

@@ -38,6 +38,76 @@
#include <boost/any.hpp> #include <boost/any.hpp>
/**
* @file Debug.h
* @brief Unified Debugging and Monitoring System for uLib.
*
* # Debug System Documentation
*
* The `Debug` system provides a flexible, adapter-based mechanism for monitoring
* and outputting internal variables and states without hardcoding output logic
* into core classes.
*
* ## Architecture
*
* The system follows an adapter pattern:
*
* - **`DebugAdapterInterface`**: The base interface for all output targets.
* Subclasses define how primitive types and strings are handled (e.g., printing
* to `std::cout`, writing to a log file, or updating a real-time UI widget).
*
* - **`Debug` Class**: The central registry. It stores:
* 1. A list of "Adapters" (`DebugAdapterInterface`).
* 2. A list of "Items" to monitor. Each item is a reference to a variable
* associated with a name.
*
* - **Type Safety**: Although variables are stored using `boost::any` (type erasure),
* the system automatically preserves the original type information via
* internal template adapters (`AnyCastAdapter`), ensuring that the correct
* overload of the adapter interface is called.
*
* ## Core Components
*
* | Component | Description |
* | :--- | :--- |
* | `Debug` | The main controller used to add adapters and register variables. |
* | `DebugAdapterInterface` | Virtual base for creating new output methods. |
* | `DebugAdapterText` | A simple built-in adapter for `std::ostream` output. |
*
* ## How to Use
*
* ### 1. Initialize the Debug Object
* ```cpp
* uLib::Debug dbg;
* ```
*
* ### 2. Add an Adapter
* ```cpp
* uLib::DebugAdapterText console(std::cout);
* dbg.AddAdapter(console);
* ```
*
* ### 3. Register Variables to Monitor
* Use the `operator()` to bind a variable by reference.
* ```cpp
* int frameCount = 0;
* std::string state = "Initializing";
* dbg("Frames", frameCount);
* dbg("Status", state);
* ```
*
* ### 4. Update
* Call `Update()` periodically (e.g., once per frame) to push the current
* values of all registered variables to all connected adapters.
* ```cpp
* while(running) {
* frameCount++;
* dbg.Update(); // This triggers the output
* }
* ```
*/
namespace uLib { namespace uLib {
@@ -107,7 +177,6 @@ public:
class Debug { class Debug {
typedef detail::DebugAdapterInterface AdapterInterface; typedef detail::DebugAdapterInterface AdapterInterface;
typedef SmartPointer<detail::DebugAdapterInterface> Adapter; typedef SmartPointer<detail::DebugAdapterInterface> Adapter;

View File

@@ -95,6 +95,8 @@ namespace uLib {
typedef boost::signals2::signal_base SignalBase; typedef boost::signals2::signal_base SignalBase;
typedef boost::signals2::connection Connection; typedef boost::signals2::connection Connection;
typedef boost::signals2::shared_connection_block ConnectionBlock;
template <typename T> struct Signal { template <typename T> struct Signal {
typedef boost::signals2::signal<T> type; typedef boost::signals2::signal<T> type;

View File

@@ -96,10 +96,13 @@ public:
signals: signals:
virtual void Updated() override { virtual void Updated() override {
if (m_InUpdated) return; // break signal recursion if (m_InUpdated) return;
m_InUpdated = true; m_InUpdated = true;
// Synchronize TRS part
this->TRS::Updated();
this->ComputeBoundingBox(); this->ComputeBoundingBox();
ULIB_SIGNAL_EMIT(Object::Updated);
m_InUpdated = false; m_InUpdated = false;
} }

View File

@@ -33,6 +33,8 @@
#include "Math/Transform.h" #include "Math/Transform.h"
#include <utility> #include <utility>
#include <iostream>
namespace uLib { namespace uLib {
/** /**
@@ -99,9 +101,9 @@ public:
*/ */
template <class ArchiveT> template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) { void serialize(ArchiveT & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object<TRS>(*this));
ar & HRP(Size); ar & HRP(Size);
ar & HRP(Origin); ar & HRP(Origin);
ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object<TRS>(*this));
} }
/** /**
@@ -216,8 +218,13 @@ signals:
/** Signal emitted when properties change */ /** Signal emitted when properties change */
virtual void Updated() override { virtual void Updated() override {
// 1. Synchronize local box part (Size/Origin -> m_LocalT)
this->Sync(); this->Sync();
ULIB_SIGNAL_EMIT(Object::Updated);
// 2. Synchronize TRS part (position/rotation/scaling -> m_T) and emit signal
this->TRS::Updated();
// std::cout << "ContainerBox::Updated()" << std::endl;
} }
private: private:

View File

@@ -177,8 +177,11 @@ public:
signals: signals:
/** Signal emitted when properties change */ /** Signal emitted when properties change */
virtual void Updated() override { virtual void Updated() override {
// 1. Synchronize local cylinder part (Radius/Height/Axis -> m_LocalT)
this->Sync(); this->Sync();
ULIB_SIGNAL_EMIT(Object::Updated);
// 2. Synchronize TRS part (position/rotation/scaling -> m_T) and emit signal
this->TRS::Updated();
} }
private: private:

View File

@@ -246,6 +246,11 @@ public:
this->GetTransform() = GetAffineMatrix(); this->GetTransform() = GetAffineMatrix();
} }
void Updated() override {
this->SyncMatrix();
this->AffineTransform::Updated();
}
template <class ArchiveT> template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) { void serialize(ArchiveT & ar, const unsigned int version) {
ar & HRPU(position, "mm"); ar & HRPU(position, "mm");

View File

@@ -98,15 +98,14 @@ template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#define _TPL_ template <typename VoxelT, typename AlgorithmT>
#define _TPLT_ VoxelT, AlgorithmT
_TPL_
VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size) template <typename VoxelT, typename AlgorithmT>
VoxImageFilter<VoxelT, AlgorithmT>::VoxImageFilter(const Vector3i &size)
: m_KernelData(size), t_Algoritm(static_cast<AlgorithmT *>(this)) {} : m_KernelData(size), t_Algoritm(static_cast<AlgorithmT *>(this)) {}
_TPL_ template <typename VoxelT, typename AlgorithmT>
void VoxImageFilter<_TPLT_>::Run() { void VoxImageFilter<VoxelT, AlgorithmT>::Run() {
VoxImage<VoxelT> buffer = *m_Image; VoxImage<VoxelT> buffer = *m_Image;
#pragma omp parallel for #pragma omp parallel for
for (int i = 0; i < m_Image->Data().size(); ++i) for (int i = 0; i < m_Image->Data().size(); ++i)
@@ -114,8 +113,8 @@ void VoxImageFilter<_TPLT_>::Run() {
#pragma omp barrier #pragma omp barrier
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
void VoxImageFilter<_TPLT_>::SetKernelOffset() { void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelOffset() {
Vector3i id(0, 0, 0); Vector3i id(0, 0, 0);
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) { for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) { for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
@@ -127,8 +126,8 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() {
} }
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) { float VoxImageFilter<VoxelT, AlgorithmT>::Distance2(const Vector3i &v) {
Vector3i tmp = v; Vector3i tmp = v;
const Vector3i &dim = this->m_KernelData.GetDims(); const Vector3i &dim = this->m_KernelData.GetDims();
Vector3i center = dim / 2; Vector3i center = dim / 2;
@@ -140,8 +139,8 @@ float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2))); 0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
void VoxImageFilter<_TPLT_>::SetKernelNumericXZY( void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelNumericXZY(
const std::vector<float> &numeric) { const std::vector<float> &numeric) {
// set data order // // set data order //
StructuredData::Order order = m_KernelData.GetDataOrder(); StructuredData::Order order = m_KernelData.GetDataOrder();
@@ -159,8 +158,8 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
// m_KernelData.SetDataOrder(order); // m_KernelData.SetDataOrder(order);
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) { void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelSpherical(float (*shape)(float)) {
Vector3i id; Vector3i id;
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) { for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) { for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
@@ -172,8 +171,8 @@ void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) {
} }
} }
_TPL_ template <class ShapeT> template <typename VoxelT, typename AlgorithmT> template <class ShapeT>
void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) { void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelSpherical(ShapeT shape) {
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>(); Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
Vector3i id; Vector3i id;
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) { for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
@@ -186,8 +185,8 @@ void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) {
} }
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction( void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelWeightFunction(
float (*shape)(const Vector3f &)) { float (*shape)(const Vector3f &)) {
const Vector3i &dim = m_KernelData.GetDims(); const Vector3i &dim = m_KernelData.GetDims();
Vector3i id; Vector3i id;
@@ -207,8 +206,8 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
} }
} }
_TPL_ template <class ShapeT> template <typename VoxelT, typename AlgorithmT> template <class ShapeT>
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) { void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelWeightFunction(ShapeT shape) {
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>(); Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
const Vector3i &dim = m_KernelData.GetDims(); const Vector3i &dim = m_KernelData.GetDims();
Vector3i id; Vector3i id;
@@ -228,14 +227,14 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
} }
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
void VoxImageFilter<_TPLT_>::SetImage(Abstract::VoxImage *image) { void VoxImageFilter<VoxelT, AlgorithmT>::SetImage(Abstract::VoxImage *image) {
this->m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image); this->m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
this->SetKernelOffset(); this->SetKernelOffset();
} }
_TPL_ template <typename VoxelT, typename AlgorithmT>
float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer, float VoxImageFilter<VoxelT, AlgorithmT>::Convolve(const VoxImage<VoxelT> &buffer,
int index) { int index) {
const DataAllocator<VoxelT> &vbuf = buffer.ConstData(); const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
const DataAllocator<VoxelT> &vker = m_KernelData.ConstData(); const DataAllocator<VoxelT> &vker = m_KernelData.ConstData();
@@ -252,8 +251,8 @@ float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer,
return conv / ksum; return conv / ksum;
} }
#undef _TPLT_
#undef _TPL_
} // namespace uLib } // namespace uLib

View File

@@ -64,7 +64,7 @@ vtkDetectorChamber::vtkDetectorChamber(DetectorChamber *content)
this->SetProp(m_PlaneActor); this->SetProp(m_PlaneActor);
this->contentUpdate(); this->Update();
} }
vtkDetectorChamber::~vtkDetectorChamber() { vtkDetectorChamber::~vtkDetectorChamber() {
@@ -76,8 +76,8 @@ DetectorChamber *vtkDetectorChamber::GetContent() {
return static_cast<DetectorChamber *>(m_Content); return static_cast<DetectorChamber *>(m_Content);
} }
void vtkDetectorChamber::contentUpdate() { void vtkDetectorChamber::Update() {
this->BaseClass::contentUpdate(); this->BaseClass::Update();
if (!m_Content) return; if (!m_Content) return;
DetectorChamber *c = this->GetContent(); DetectorChamber *c = this->GetContent();

View File

@@ -56,7 +56,7 @@ public:
Content *GetContent(); Content *GetContent();
virtual void contentUpdate() override; virtual void Update() override;
protected: protected:
vtkActor *m_PlaneActor; vtkActor *m_PlaneActor;

View File

@@ -40,20 +40,21 @@ namespace Vtk {
vtkMuonScatter::vtkMuonScatter(MuonScatter &content) vtkMuonScatter::vtkMuonScatter(MuonScatter &content)
: m_Content(&content), m_LineIn(vtkLineSource::New()), : m_Content(&content), m_LineIn(vtkLineSource::New()),
m_LineOut(vtkLineSource::New()), m_PolyData(vtkPolyData::New()), m_LineOut(vtkLineSource::New()), m_PolyData(vtkPolyData::New()),
m_SpherePoca(NULL) { m_SpherePoca(NULL), m_Asm(vtkAssembly::New()) {
InstallPipe(); InstallPipe();
} }
vtkMuonScatter::vtkMuonScatter(const MuonScatter &content) vtkMuonScatter::vtkMuonScatter(const MuonScatter &content)
: m_Content(const_cast<MuonScatter *>(&content)), : m_Content(const_cast<MuonScatter *>(&content)),
m_LineIn(vtkLineSource::New()), m_LineOut(vtkLineSource::New()), m_LineIn(vtkLineSource::New()), m_LineOut(vtkLineSource::New()),
m_PolyData(vtkPolyData::New()), m_SpherePoca(NULL) { m_PolyData(vtkPolyData::New()), m_SpherePoca(NULL), m_Asm(vtkAssembly::New()) {
InstallPipe(); InstallPipe();
} }
vtkMuonScatter::~vtkMuonScatter() { vtkMuonScatter::~vtkMuonScatter() {
m_LineIn->Delete(); m_LineIn->Delete();
m_LineOut->Delete(); m_LineOut->Delete();
m_Asm->Delete();
if (m_SpherePoca) if (m_SpherePoca)
m_SpherePoca->Delete(); m_SpherePoca->Delete();
} }
@@ -87,13 +88,15 @@ void vtkMuonScatter::InstallPipe() {
mapper->SetInputConnection(m_LineIn->GetOutputPort()); mapper->SetInputConnection(m_LineIn->GetOutputPort());
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper); actor->SetMapper(mapper);
this->SetProp(actor); m_Asm->AddPart(actor);
mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(m_LineOut->GetOutputPort()); mapper->SetInputConnection(m_LineOut->GetOutputPort());
actor = vtkSmartPointer<vtkActor>::New(); actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper); actor->SetMapper(mapper);
this->SetProp(actor); m_Asm->AddPart(actor);
this->SetProp(m_Asm);
} }
vtkPolyData *vtkMuonScatter::GetPolyData() const { vtkPolyData *vtkMuonScatter::GetPolyData() const {
@@ -123,7 +126,7 @@ void vtkMuonScatter::AddPocaPoint(HPoint3f poca) {
mapper->SetInputConnection(m_SpherePoca->GetOutputPort()); mapper->SetInputConnection(m_SpherePoca->GetOutputPort());
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper); actor->SetMapper(mapper);
this->SetProp(actor); m_Asm->AddPart(actor);
} }
HPoint3f vtkMuonScatter::GetPocaPoint() { HPoint3f vtkMuonScatter::GetPocaPoint() {

View File

@@ -27,6 +27,7 @@
#define VTKMUONSCATTER_H #define VTKMUONSCATTER_H
#include <vtkActor.h> #include <vtkActor.h>
#include <vtkAssembly.h>
#include <vtkAppendPolyData.h> #include <vtkAppendPolyData.h>
#include <vtkLineSource.h> #include <vtkLineSource.h>
#include <vtkPolyDataMapper.h> #include <vtkPolyDataMapper.h>
@@ -85,6 +86,7 @@ private:
vtkLineSource *m_LineOut; vtkLineSource *m_LineOut;
vtkSphereSource *m_SpherePoca; vtkSphereSource *m_SpherePoca;
vtkPolyData *m_PolyData; vtkPolyData *m_PolyData;
vtkAssembly *m_Asm;
}; };
} // namespace Vtk } // namespace Vtk

View File

@@ -50,6 +50,7 @@ vtkVoxRaytracerRepresentation::vtkVoxRaytracerRepresentation(Content &content)
m_RayRepresentation(vtkAppendPolyData::New()), m_RayRepresentation(vtkAppendPolyData::New()),
m_RayRepresentationActor(vtkActor::New()), m_RayRepresentationActor(vtkActor::New()),
m_Transform(vtkTransform::New()), m_Transform(vtkTransform::New()),
m_Asm(vtkAssembly::New()),
m_HasMuon(false), m_HasPoca(false) { m_HasMuon(false), m_HasPoca(false) {
default_radius = content.GetImage()->GetSpacing()(0) / 4; default_radius = content.GetImage()->GetSpacing()(0) / 4;
m_Sphere1->SetRadius(default_radius); m_Sphere1->SetRadius(default_radius);
@@ -313,20 +314,19 @@ void vtkVoxRaytracerRepresentation::InstallPipe() {
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper> mapper =
vtkSmartPointer<vtkPolyDataMapper>::New(); vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(append->GetOutputPort()); mapper->SetInputConnection(append->GetOutputPort());
vtkSmartPointer<vtkActor> actor = vtkActor::New(); vtkSmartPointer<vtkActor> actor = vtkActor::New();
actor->SetMapper(mapper); actor->SetMapper(mapper);
actor->GetProperty()->SetColor(0.6, 0.6, 1); actor->GetProperty()->SetColor(0.6, 0.6, 1);
this->SetProp(actor); m_Asm->AddPart(actor);
mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(m_RayLine->GetOutputPort()); mapper->SetInputConnection(m_RayLine->GetOutputPort());
m_RayLineActor->SetMapper(mapper); m_RayLineActor->SetMapper(mapper);
m_RayLineActor->GetProperty()->SetColor(1, 0, 0); m_RayLineActor->GetProperty()->SetColor(1, 0, 0);
this->SetProp(m_RayLineActor); m_Asm->AddPart(m_RayLineActor);
vtkSmartPointer<vtkTransformPolyDataFilter> polyfilter = vtkSmartPointer<vtkTransformPolyDataFilter> polyfilter =
vtkSmartPointer<vtkTransformPolyDataFilter>::New(); vtkSmartPointer<vtkTransformPolyDataFilter>::New();
@@ -343,7 +343,9 @@ void vtkVoxRaytracerRepresentation::InstallPipe() {
vra->GetProperty()->SetEdgeVisibility(true); vra->GetProperty()->SetEdgeVisibility(true);
vra->GetProperty()->SetColor(0.5, 0.5, 0.5); vra->GetProperty()->SetColor(0.5, 0.5, 0.5);
this->SetProp(vra); m_Asm->AddPart(vra);
this->SetProp(m_Asm);
} }
} // namespace Vtk } // namespace Vtk

View File

@@ -107,6 +107,7 @@ private:
bool m_HasPoca; bool m_HasPoca;
Scalarf default_radius; Scalarf default_radius;
vtkSmartPointer<vtkAssembly> m_Asm;
vtkAppendPolyData *m_RayLine; vtkAppendPolyData *m_RayLine;
vtkActor *m_RayLineActor; vtkActor *m_RayLineActor;
vtkActor *m_RayRepresentationActor; vtkActor *m_RayRepresentationActor;

View File

@@ -39,7 +39,7 @@ Assembly::Assembly(uLib::Assembly *content)
this->InstallPipe(); this->InstallPipe();
if (m_Content) { if (m_Content) {
Object::connect(m_Content, &uLib::Assembly::Updated, Object::connect(m_Content, &uLib::Assembly::Updated,
this, &Assembly::contentUpdate); this, &Assembly::Update);
} }
} }
@@ -84,29 +84,27 @@ void Assembly::InstallPipe() {
} }
} }
// 4. Apply initial transform // 4. Force initial visual sync
this->UpdateTransform(); this->Update();
this->UpdateBoundingBox();
}
// ------------------------------------------------------------------ //
void Assembly::contentUpdate() {
if (m_InUpdate) return;
m_InUpdate = true;
this->UpdateTransform();
this->UpdateBoundingBox();
if (m_ChildContext)
m_ChildContext->Update();
Puppet::Update();
m_InUpdate = false;
} }
// ------------------------------------------------------------------ // // ------------------------------------------------------------------ //
void Assembly::Update() { void Assembly::Update() {
if (m_InUpdate) return; if (m_InUpdate) return;
m_InUpdate = true; m_InUpdate = true;
this->contentUpdate();
if (m_Content && m_VtkAsm) {
// Apply world matrix from the assembly content
vtkNew<vtkMatrix4x4> m;
Matrix4fToVtk(m_Content->GetMatrix(), m);
m_VtkAsm->SetUserMatrix(m);
m_VtkAsm->Modified();
}
this->Puppet::Update();
this->UpdateBoundingBox();
if (m_ChildContext)
m_ChildContext->Update();
m_InUpdate = false; m_InUpdate = false;
} }
@@ -116,14 +114,11 @@ void Assembly::SyncFromVtk() {
m_InUpdate = true; m_InUpdate = true;
double pos[3], ori[3], scale[3]; // VTK -> Model: Update world matrix (accounting for model parents)
m_VtkAsm->GetPosition(pos); if (vtkProp3D* proxy = this->GetProxyProp()) {
m_VtkAsm->GetOrientation(ori); m_Content->SetWorldMatrix(VtkToMatrix4f(proxy->GetUserMatrix()));
m_VtkAsm->GetScale(scale); m_Content->FromMatrix(m_Content->GetMatrix());
}
m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2]));
m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
this->UpdateBoundingBox(); this->UpdateBoundingBox();
if (m_ChildContext) if (m_ChildContext)
@@ -134,14 +129,6 @@ void Assembly::SyncFromVtk() {
m_InUpdate = false; m_InUpdate = false;
} }
// ------------------------------------------------------------------ //
void Assembly::UpdateTransform() {
if (!m_Content || !m_VtkAsm) return;
this->ApplyTransform(m_VtkAsm);
m_VtkAsm->Modified();
}
// ------------------------------------------------------------------ // // ------------------------------------------------------------------ //
void Assembly::UpdateBoundingBox() { void Assembly::UpdateBoundingBox() {
if (!m_Content || !m_BBoxActor) return; if (!m_Content || !m_BBoxActor) return;

View File

@@ -50,14 +50,14 @@ public:
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
virtual uLib::ObjectsContext* GetChildren() override { return (uLib::ObjectsContext*)m_Content; } virtual uLib::ObjectsContext* GetChildren() override { return (uLib::ObjectsContext*)m_Content; }
/** @brief Called when the model signals an update (model→VTK push). */ /**
void contentUpdate(); * @brief Returns the puppet managing child objects.
*/
/** @brief Returns the puppet managing child objects. */ /** @brief Returns the puppet managing child objects. */
vtkObjectsContext *GetChildrenContext() const; vtkObjectsContext *GetChildrenContext() const;
private: private:
void UpdateTransform();
void UpdateBoundingBox(); void UpdateBoundingBox();
void InstallPipe(); void InstallPipe();

View File

@@ -50,16 +50,26 @@ namespace Vtk {
struct ContainerBoxData { struct ContainerBoxData {
vtkSmartPointer<vtkActor> m_Cube; vtkSmartPointer<vtkActor> m_Cube;
vtkSmartPointer<vtkActor> m_Axes; vtkSmartPointer<vtkActor> m_Axes;
vtkSmartPointer<vtkAssembly> m_VtkAsm;
vtkSmartPointer<vtkMatrix4x4> m_Affine;
uLib::Connection m_UpdateSignal;
ContainerBoxData() : m_Cube(vtkSmartPointer<vtkActor>::New()), m_Axes(vtkSmartPointer<vtkActor>::New()) {} ContainerBoxData() : m_Cube(vtkSmartPointer<vtkActor>::New()),
m_Axes(vtkSmartPointer<vtkActor>::New()),
m_VtkAsm(vtkSmartPointer<vtkAssembly>::New()),
m_Affine(vtkSmartPointer<vtkMatrix4x4>::New()) {}
~ContainerBoxData() { ~ContainerBoxData() {
} }
}; };
vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content) vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content)
: d(new ContainerBoxData()), m_Content(content) { : d(new ContainerBoxData()), m_Content(content) {
this->InstallPipe(); this->InstallPipe();
Object::connect(m_Content, &Content::Updated, this, &vtkContainerBox::contentUpdate); d->m_UpdateSignal = Object::connect(m_Content, &uLib::Object::Updated, this, &vtkContainerBox::Update);
} }
vtkContainerBox::~vtkContainerBox() { vtkContainerBox::~vtkContainerBox() {
@@ -72,47 +82,50 @@ vtkPolyData *vtkContainerBox::GetPolyData() const {
} }
void vtkContainerBox::contentUpdate() { void vtkContainerBox::Update() {
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
if (!m_Content) if (!m_Content) return;
return;
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return; if (root) {
// Apply local full matrix (TRS * LocalBox) so that nested assemblies work correctly
d->m_Cube->SetUserMatrix(nullptr); Matrix4f fullLocal = m_Content->GetMatrix() * m_Content->GetLocalMatrix();
d->m_Axes->SetUserMatrix(nullptr); vtkNew<vtkMatrix4x4> m;
Matrix4fToVtk(fullLocal, m);
TRS trs(*m_Content); root->SetUserMatrix(m);
this->ApplyTransform(root);
root->Modified(); root->Modified();
m_BlockUpdate = false;
Puppet::Update();
} }
// Delegate rest of update (appearance, render, etc)
void vtkContainerBox::Update() { ConnectionBlock blocker(d->m_UpdateSignal);
this->contentUpdate(); this->Puppet::Update();
} }
void vtkContainerBox::SyncFromVtk() { void vtkContainerBox::SyncFromVtk() {
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
if (!m_Content) return; if (!m_Content) return;
vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = this->GetProxyProp();
if (!assembly) return; if (!root) return;
double pos[3], ori[3], scale[3]; // VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center
assembly->GetPosition(pos); vtkMatrix4x4* rootMat = root->GetUserMatrix();
assembly->GetOrientation(ori); if (rootMat) {
assembly->GetScale(scale); std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl;
rootMat->Print(std::cout);
}
m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); Matrix4f vtkWorld = VtkToMatrix4f(rootMat);
m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
m_Content->Updated(); // Notify change // Synchronize TRS property members from the updated local matrix
m_Content->FromMatrix(vtkWorld);
std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl;
// Since we modified the model, notify observers, but block the loop back to VTK
// ConnectionBlock blocker(d->m_UpdateSignal);
m_Content->Updated();
} }
@@ -154,17 +167,16 @@ void vtkContainerBox::InstallPipe() {
mapper->SetInputConnection(axes->GetOutputPort()); mapper->SetInputConnection(axes->GetOutputPort());
mapper->Update(); mapper->Update();
this->SetProp(d->m_Cube); d->m_VtkAsm->AddPart(d->m_Cube);
this->SetProp(d->m_Axes); d->m_VtkAsm->AddPart(d->m_Axes);
this->SetProp(d->m_VtkAsm);
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = d->m_VtkAsm;
if (root) { if (root) {
TRS trs(*c); d->m_Affine = Matrix4fToVtk(m_Content->GetMatrix());
root->SetPosition(trs.position.x(), trs.position.y(), trs.position.z()); root->SetUserMatrix(d->m_Affine);
root->SetOrientation(trs.rotation.x(), trs.rotation.y(), trs.rotation.z());
root->SetScale(trs.scaling.x(), trs.scaling.y(), trs.scaling.z());
root->SetUserMatrix(nullptr);
} }
this->Update();
} }
} // namespace Vtk } // namespace Vtk

View File

@@ -46,9 +46,14 @@ public:
virtual class vtkPolyData *GetPolyData() const; virtual class vtkPolyData *GetPolyData() const;
virtual void contentUpdate(); /**
* @brief Updates the VTK representation from the internal state.
*/
virtual void Update() override; virtual void Update() override;
/**
* @brief Synchronizes the model from the VTK representation (VTK→model).
*/
virtual void SyncFromVtk() override; virtual void SyncFromVtk() override;
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
@@ -59,6 +64,7 @@ protected:
struct ContainerBoxData *d; struct ContainerBoxData *d;
Content *m_Content; Content *m_Content;
bool m_BlockUpdate = false; bool m_BlockUpdate = false;
}; };
} // namespace Vtk } // namespace Vtk

View File

@@ -40,7 +40,7 @@ namespace Vtk {
vtkCylinder::vtkCylinder(vtkCylinder::Content *content) vtkCylinder::vtkCylinder(vtkCylinder::Content *content)
: m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) { : m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) {
this->InstallPipe(); this->InstallPipe();
Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate); Object::connect(m_Content, &uLib::Object::Updated, this, &vtkCylinder::Update);
} }
vtkCylinder::~vtkCylinder() { vtkCylinder::~vtkCylinder() {
@@ -48,16 +48,16 @@ vtkCylinder::~vtkCylinder() {
if (m_VtkAsm) m_VtkAsm->Delete(); if (m_VtkAsm) m_VtkAsm->Delete();
} }
void vtkCylinder::contentUpdate() { void vtkCylinder::Update() {
if (!m_Content) if (!m_Content)
return; return;
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return; if (root) {
// 1. Placement handled specifically from content (use TRS GetMatrix, not World)
// 1. Placement handled by base Puppet class via Sync / Update logic vtkNew<vtkMatrix4x4> m;
// Update internal pd->m_Transform from content Matrix4fToVtk(m_Content->GetMatrix(), m);
Puppet::Update(); root->SetUserMatrix(m);
// 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor
// These are relative to the root assembly // These are relative to the root assembly
@@ -80,27 +80,24 @@ void vtkCylinder::contentUpdate() {
root->Modified(); root->Modified();
} }
void vtkCylinder::Update() { // Use base class sync, which handles appearance and children
this->contentUpdate(); this->Puppet::Update();
} }
void vtkCylinder::SyncFromVtk() { void vtkCylinder::SyncFromVtk() {
if (!m_Content) return; if (!m_Content) return;
vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* assembly = this->GetProxyProp();
if (!assembly) return; if (!assembly) return;
double pos[3], ori[3], scale[3]; // VTK -> Model: Update TRS properties from VTK matrix via world transform
assembly->GetPosition(pos); m_Content->SetWorldMatrix(VtkToMatrix4f(assembly->GetUserMatrix()));
assembly->GetOrientation(ori);
assembly->GetScale(scale);
m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); // Resync TRS property members (pos/rot/scale) from the newly set local matrix
// Convert VTK degrees to model radians m_Content->FromMatrix(m_Content->GetMatrix());
m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
m_Content->Updated(); // Notify change // Since we modified the model, notify observers, but block the loop back to VTK
m_Content->Updated();
} }
void vtkCylinder::InstallPipe() { void vtkCylinder::InstallPipe() {
@@ -127,7 +124,7 @@ void vtkCylinder::InstallPipe() {
m_VtkAsm->AddPart(m_Actor); m_VtkAsm->AddPart(m_Actor);
this->contentUpdate(); this->Update();
} }
} // namespace Vtk } // namespace Vtk

View File

@@ -48,10 +48,7 @@ public:
vtkCylinder(Content *content); vtkCylinder(Content *content);
virtual ~vtkCylinder(); virtual ~vtkCylinder();
/** Synchronizes the VTK actor with the uLib model matrix */ /** Synchronizes the VTK actor with the uLib model matrix and vice-versa */
virtual void contentUpdate();
/** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */
virtual void Update() override; virtual void Update() override;
/** Synchronizes the uLib model matrix with the VTK actor specifically for gizmo interactions */ /** Synchronizes the uLib model matrix with the VTK actor specifically for gizmo interactions */

View File

@@ -124,6 +124,7 @@ void vtkVoxImage::SetContent() {
vtkVoxImage::vtkVoxImage(Content &content) vtkVoxImage::vtkVoxImage(Content &content)
: m_Content(content), m_Actor(vtkVolume::New()), : m_Content(content), m_Actor(vtkVolume::New()),
m_Asm(vtkAssembly::New()),
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()), m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
m_OutlineActor(vtkActor::New()), m_OutlineActor(vtkActor::New()),
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6), m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6),
@@ -136,6 +137,7 @@ vtkVoxImage::vtkVoxImage(Content &content)
vtkVoxImage::~vtkVoxImage() { vtkVoxImage::~vtkVoxImage() {
m_Image->Delete(); m_Image->Delete();
m_Actor->Delete(); m_Actor->Delete();
m_Asm->Delete();
m_Outline->Delete(); m_Outline->Delete();
m_OutlineActor->Delete(); m_OutlineActor->Delete();
} }
@@ -330,8 +332,9 @@ void vtkVoxImage::InstallPipe() {
m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); m_OutlineActor->GetProperty()->SetRepresentationToWireframe();
m_OutlineActor->GetProperty()->SetAmbient(0.7); m_OutlineActor->GetProperty()->SetAmbient(0.7);
this->SetProp(m_Actor); m_Asm->AddPart(m_Actor);
this->SetProp(m_OutlineActor); m_Asm->AddPart(m_OutlineActor);
this->SetProp(m_Asm);
// Default look // Default look
this->SetRepresentation(Surface); this->SetRepresentation(Surface);

View File

@@ -31,6 +31,7 @@
#include <vtkVolume.h> #include <vtkVolume.h>
#include <vtkXMLImageDataReader.h> #include <vtkXMLImageDataReader.h>
#include <vtkXMLImageDataWriter.h> #include <vtkXMLImageDataWriter.h>
#include <vtkAssembly.h>
#include <Math/VoxImage.h> #include <Math/VoxImage.h>
@@ -77,6 +78,7 @@ private:
vtkImageData *m_Image; vtkImageData *m_Image;
vtkCubeSource *m_Outline; vtkCubeSource *m_Outline;
vtkActor *m_OutlineActor; vtkActor *m_OutlineActor;
vtkAssembly *m_Asm;
vtkXMLImageDataReader *m_Reader; vtkXMLImageDataReader *m_Reader;
vtkXMLImageDataWriter *m_Writer; vtkXMLImageDataWriter *m_Writer;

View File

@@ -58,6 +58,7 @@
#include <vtkPolyData.h> #include <vtkPolyData.h>
#include <vtkFeatureEdges.h> #include <vtkFeatureEdges.h>
#include <vtkTransform.h> #include <vtkTransform.h>
#include <vtkCubeSource.h>
#include <vtkRenderWindow.h> #include <vtkRenderWindow.h>
#include "uLibVtkInterface.h" #include "uLibVtkInterface.h"
@@ -65,6 +66,7 @@
#include "Math/Dense.h" #include "Math/Dense.h"
#include "Vtk/Math/vtkDense.h" #include "Vtk/Math/vtkDense.h"
#include "Core/Property.h" #include "Core/Property.h"
#include "Math/Transform.h"
@@ -83,7 +85,7 @@ public:
PuppetData(Puppet* owner) : PuppetData(Puppet* owner) :
m_Puppet(owner), m_Puppet(owner),
m_Renderers(vtkSmartPointer<vtkRendererCollection>::New()), m_Renderers(vtkSmartPointer<vtkRendererCollection>::New()),
m_Assembly(vtkSmartPointer<vtkAssembly>::New()), m_Prop(nullptr),
m_ShowBoundingBox(false), m_ShowBoundingBox(false),
m_ShowScaleMeasures(false), m_ShowScaleMeasures(false),
m_Representation(Puppet::Surface), m_Representation(Puppet::Surface),
@@ -103,7 +105,7 @@ public:
Puppet *m_Puppet; Puppet *m_Puppet;
// members // // members //
vtkSmartPointer<vtkRendererCollection> m_Renderers; vtkSmartPointer<vtkRendererCollection> m_Renderers;
vtkSmartPointer<vtkAssembly> m_Assembly; vtkSmartPointer<vtkProp3D> m_Prop;
vtkSmartPointer<vtkOutlineSource> m_OutlineSource; vtkSmartPointer<vtkOutlineSource> m_OutlineSource;
vtkSmartPointer<vtkActor> m_OutlineActor; vtkSmartPointer<vtkActor> m_OutlineActor;
@@ -152,20 +154,17 @@ public:
} else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) { } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) {
// Recursively apply to parts of the assembly // Recursively apply to parts of the assembly
vtkProp3DCollection *parts = asm_p->GetParts(); vtkProp3DCollection *parts = asm_p->GetParts();
if (parts) {
parts->InitTraversal(); parts->InitTraversal();
for (int i = 0; i < parts->GetNumberOfItems(); ++i) { for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
this->ApplyAppearance(parts->GetNextProp3D()); this->ApplyAppearance(parts->GetNextProp3D());
} }
} }
} }
}
void ApplyTransform(vtkProp3D* p3d) { void ApplyTransform(vtkProp3D* p3d) {
if (p3d) { if (p3d) {
if (auto* content = dynamic_cast<uLib::AffineTransform*>(m_Puppet->GetContent())) {
vtkNew<vtkMatrix4x4> m;
Matrix4fToVtk(content->GetWorldMatrix(), m);
p3d->SetUserMatrix(m);
} else {
p3d->SetUserMatrix(nullptr); p3d->SetUserMatrix(nullptr);
p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z());
@@ -177,21 +176,28 @@ public:
p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z());
} }
} }
}
void UpdateHighlight() { void UpdateHighlight() {
if (m_Selected) { if (m_Selected) {
// Find first polydata in assembly to highlight // Find first polydata in assembly to highlight
vtkPolyData* polydata = nullptr; vtkPolyData* polydata = nullptr;
vtkPropCollection *parts = m_Assembly->GetParts(); if (vtkActor *actor = vtkActor::SafeDownCast(m_Prop)) {
if (actor->GetMapper()) {
polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput());
}
} else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(m_Prop)) {
vtkPropCollection *parts = asm_p->GetParts();
if (parts) {
parts->InitTraversal(); parts->InitTraversal();
for (int i = 0; i < parts->GetNumberOfItems(); ++i) { for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
vtkActor *actor = vtkActor::SafeDownCast(parts->GetNextProp()); vtkActor *a = vtkActor::SafeDownCast(parts->GetNextProp());
if (actor && actor->GetMapper()) { if (a && a->GetMapper()) {
polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput()); polydata = vtkPolyData::SafeDownCast(a->GetMapper()->GetDataSetInput());
if (polydata) break; if (polydata) break;
} }
} }
}
}
if (!polydata) { if (!polydata) {
if (m_HighlightActor) { if (m_HighlightActor) {
@@ -205,40 +211,50 @@ public:
} }
if (!m_HighlightActor) { if (!m_HighlightActor) {
vtkSmartPointer<vtkFeatureEdges> edges = vtkSmartPointer<vtkFeatureEdges>::New(); vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::New();
edges->BoundaryEdgesOn(); double bounds[6];
edges->FeatureEdgesOn(); polydata->GetBounds(bounds);
edges->SetFeatureAngle(30); // Add a small padding to prevent z-fighting
edges->NonManifoldEdgesOn(); double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]});
edges->ManifoldEdgesOff(); double pad = maxDim * 0.02;
edges->SetInputData(polydata); if(pad < 1e-4) pad = 0.05;
cube->SetBounds(bounds[0]-pad, bounds[1]+pad,
bounds[2]-pad, bounds[3]+pad,
bounds[4]-pad, bounds[5]+pad);
m_HighlightActor = vtkSmartPointer<vtkActor>::New(); m_HighlightActor = vtkSmartPointer<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(edges->GetOutputPort()); mapper->SetInputConnection(cube->GetOutputPort());
m_HighlightActor->SetMapper(mapper); m_HighlightActor->SetMapper(mapper);
m_HighlightActor->GetProperty()->SetColor(1.0, 0.5, 0.0); // Orange m_HighlightActor->GetProperty()->SetRepresentationToWireframe();
m_HighlightActor->GetProperty()->SetColor(1.0, 0.0, 0.0); // Red
m_HighlightActor->GetProperty()->SetLineWidth(2.0); m_HighlightActor->GetProperty()->SetLineWidth(2.0);
m_HighlightActor->GetProperty()->SetLighting(0); m_HighlightActor->GetProperty()->SetLighting(0);
} else { } else {
// Update input data (safe even if same)
if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) {
if (auto* edges = vtkFeatureEdges::SafeDownCast(mapper->GetInputAlgorithm())) { if (auto* cube = vtkCubeSource::SafeDownCast(mapper->GetInputAlgorithm())) {
edges->SetInputData(polydata); double bounds[6];
polydata->GetBounds(bounds);
double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]});
double pad = maxDim * 0.02;
if(pad < 1e-4) pad = 0.05;
cube->SetBounds(bounds[0]-pad, bounds[1]+pad,
bounds[2]-pad, bounds[3]+pad,
bounds[4]-pad, bounds[5]+pad);
cube->Modified();
} }
} }
} }
// Update highlight matrix from the root prop // Update highlight matrix from the model world matrix
vtkProp3D* root = nullptr; if (m_Puppet) {
if (m_Assembly->GetParts()->GetNumberOfItems() == 1) { if (auto* content = m_Puppet->GetContent()) {
root = vtkProp3D::SafeDownCast(m_Assembly->GetParts()->GetLastProp()); if (auto* tr = dynamic_cast<uLib::TRS*>(content)) {
} else { vtkNew<vtkMatrix4x4> vwm;
root = m_Assembly; Matrix4fToVtk(tr->GetWorldMatrix(), vwm);
m_HighlightActor->SetUserMatrix(vwm);
}
} }
if (root) {
m_HighlightActor->SetUserMatrix(root->GetMatrix());
} }
m_Renderers->InitTraversal(); m_Renderers->InitTraversal();
@@ -285,25 +301,28 @@ Puppet::~Puppet()
vtkProp *Puppet::GetProp() vtkProp *Puppet::GetProp()
{ {
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) return pd->m_Prop;
return pd->m_Assembly->GetParts()->GetLastProp(); }
else
return pd->m_Assembly; vtkProp3D *Puppet::GetProxyProp()
{
// The handler should manipulate the highlight actor if it exists
if (pd->m_HighlightActor) {
return pd->m_HighlightActor;
}
return vtkProp3D::SafeDownCast(this->GetProp());
} }
void Puppet::SetProp(vtkProp *prop) void Puppet::SetProp(vtkProp *prop)
{ {
if(prop) { if(prop) {
prop->SetPickable(pd->m_Selectable); prop->SetPickable(pd->m_Selectable);
if (auto* p3d = vtkProp3D::SafeDownCast(prop)) { pd->m_Prop = vtkProp3D::SafeDownCast(prop);
pd->m_Assembly->AddPart(p3d);
}
pd->ApplyAppearance(prop); pd->ApplyAppearance(prop);
// For the first actor added, seed the tracked display values from the VTK // For the first actor added, seed the tracked display values from the VTK
// actor's current state so the display properties panel shows meaningful // actor's current state so the display properties panel shows meaningful
// initial values instead of the -1 "not-overriding" sentinels. // initial values instead of the -1 "not-overriding" sentinels.
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) {
if (auto* actor = vtkActor::SafeDownCast(prop)) { if (auto* actor = vtkActor::SafeDownCast(prop)) {
vtkProperty* vp = actor->GetProperty(); vtkProperty* vp = actor->GetProperty();
if (pd->m_Representation < 0) if (pd->m_Representation < 0)
@@ -318,7 +337,6 @@ void Puppet::SetProp(vtkProp *prop)
} }
} }
} }
}
void Puppet::RemoveProp(vtkProp *prop) void Puppet::RemoveProp(vtkProp *prop)
{ {
@@ -338,12 +356,18 @@ void Puppet::ApplyTransform(vtkProp3D* p3d)
vtkPropCollection *Puppet::GetParts() vtkPropCollection *Puppet::GetParts()
{ {
return pd->m_Assembly->GetParts(); if (auto* asm_p = vtkAssembly::SafeDownCast(pd->m_Prop)) {
return asm_p->GetParts();
}
return nullptr;
} }
vtkPropCollection *Puppet::GetProps() vtkPropCollection *Puppet::GetProps()
{ {
return pd->m_Assembly->GetParts(); if (auto* asm_p = vtkAssembly::SafeDownCast(pd->m_Prop)) {
return asm_p->GetParts();
}
return nullptr;
} }
void Puppet::ConnectRenderer(vtkRenderer *renderer) void Puppet::ConnectRenderer(vtkRenderer *renderer)
@@ -397,7 +421,8 @@ vtkRendererCollection *Puppet::GetRenderers() const
void Puppet::PrintSelf(std::ostream &o) const void Puppet::PrintSelf(std::ostream &o) const
{ {
o << "Props Assembly: \n"; o << "Props Assembly: \n";
pd->m_Assembly->PrintSelf(o,vtkIndent(1)); if (pd->m_Prop)
pd->m_Prop->PrintSelf(o,vtkIndent(1));
o << "Connected Renderers: \n"; o << "Connected Renderers: \n";
pd->m_Renderers->PrintSelf(o,vtkIndent(1)); pd->m_Renderers->PrintSelf(o,vtkIndent(1));
@@ -417,9 +442,11 @@ void Puppet::ShowBoundingBox(bool show)
pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0); pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
} }
double* bounds = pd->m_Assembly->GetBounds(); if (pd->m_Prop) {
double* bounds = pd->m_Prop->GetBounds();
pd->m_OutlineSource->SetBounds(bounds); pd->m_OutlineSource->SetBounds(bounds);
pd->m_OutlineSource->Update(); pd->m_OutlineSource->Update();
}
pd->m_Renderers->InitTraversal(); pd->m_Renderers->InitTraversal();
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
@@ -449,8 +476,10 @@ void Puppet::ShowScaleMeasures(bool show)
pd->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0); pd->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
} }
double* bounds = pd->m_Assembly->GetBounds(); if (pd->m_Prop) {
double* bounds = pd->m_Prop->GetBounds();
pd->m_CubeAxesActor->SetBounds(bounds); pd->m_CubeAxesActor->SetBounds(bounds);
}
pd->m_Renderers->InitTraversal(); pd->m_Renderers->InitTraversal();
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
@@ -472,12 +501,7 @@ void Puppet::ShowScaleMeasures(bool show)
void Puppet::SetRepresentation(Representation mode) void Puppet::SetRepresentation(Representation mode)
{ {
pd->m_Representation = static_cast<int>(mode); pd->m_Representation = static_cast<int>(mode);
pd->ApplyAppearance(pd->m_Prop);
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
} }
void Puppet::SetRepresentation(const char *mode) void Puppet::SetRepresentation(const char *mode)
@@ -497,23 +521,13 @@ void Puppet::SetColor(double r, double g, double b)
pd->m_Color[0] = r; pd->m_Color[0] = r;
pd->m_Color[1] = g; pd->m_Color[1] = g;
pd->m_Color[2] = b; pd->m_Color[2] = b;
pd->ApplyAppearance(pd->m_Prop);
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
} }
void Puppet::SetOpacity(double alpha) void Puppet::SetOpacity(double alpha)
{ {
pd->m_Opacity = alpha; pd->m_Opacity = alpha;
pd->ApplyAppearance(pd->m_Prop);
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
} }
@@ -525,11 +539,7 @@ void Puppet::SetOpacity(double alpha)
void Puppet::SetSelectable(bool selectable) void Puppet::SetSelectable(bool selectable)
{ {
pd->m_Selectable = selectable; pd->m_Selectable = selectable;
vtkProp3DCollection *props = pd->m_Assembly->GetParts(); pd->ApplyAppearance(pd->m_Prop);
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
props->GetNextProp3D()->SetPickable(selectable);
}
} }
bool Puppet::IsSelectable() const bool Puppet::IsSelectable() const
@@ -552,39 +562,28 @@ bool Puppet::IsSelected() const
void Puppet::Update() void Puppet::Update()
{ {
vtkProp* root = this->GetProp(); // Derived classes should have updated the transform if they override Update()
if (root) { // or we can apply base transform if it's default:
// Handle transformation synchronization from content // pd->ApplyTransform(pd->m_Prop);
if (auto* content = dynamic_cast<uLib::AffineTransform*>(GetContent())) {
pd->m_Transform = *content; // Uses TRS(const AffineTransform&)
}
if (auto* p3d = vtkProp3D::SafeDownCast(root)) { pd->ApplyAppearance(pd->m_Prop);
pd->ApplyTransform(p3d);
}
pd->ApplyAppearance(root);
}
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
if (pd->m_Selected) { if (pd->m_Selected) {
pd->UpdateHighlight(); pd->UpdateHighlight();
} }
if (pd->m_Prop) {
if (pd->m_ShowBoundingBox) { if (pd->m_ShowBoundingBox) {
double* bounds = pd->m_Assembly->GetBounds(); double* bounds = pd->m_Prop->GetBounds();
pd->m_OutlineSource->SetBounds(bounds); pd->m_OutlineSource->SetBounds(bounds);
pd->m_OutlineSource->Update(); pd->m_OutlineSource->Update();
} }
if (pd->m_ShowScaleMeasures) { if (pd->m_ShowScaleMeasures) {
double* bounds = pd->m_Assembly->GetBounds(); double* bounds = pd->m_Prop->GetBounds();
pd->m_CubeAxesActor->SetBounds(bounds); pd->m_CubeAxesActor->SetBounds(bounds);
} }
}
// Notify that the object has been updated (important for UI refresh) // Notify that the object has been updated (important for UI refresh)
this->Object::Updated(); this->Object::Updated();
@@ -599,37 +598,6 @@ void Puppet::Update()
} }
void Puppet::SyncFromVtk()
{
vtkProp* root = this->GetProp();
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
// Handle content synchronization if it's an AffineTransform
if (auto* content = dynamic_cast<uLib::AffineTransform*>(GetContent())) {
// Apply current VTK world transform to model.
// When 'sinking the chain', p3d's matrix represents the world matrix
// because it has no parent in VTK (nesting was removed).
vtkNew<vtkMatrix4x4> m;
p3d->GetMatrix(m);
content->SetWorldMatrix(VtkToMatrix4f(m));
// Sync local TRS from the newly updated model
pd->m_Transform = *content;
}
else {
// Fallback for simple props
double pos[3], ori[3], scale[3];
p3d->GetPosition(pos);
p3d->GetOrientation(ori);
p3d->GetScale(scale);
pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]);
pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree;
pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]);
}
this->Object::Updated();
}
}
void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor) void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
{ {
} }
@@ -653,7 +621,8 @@ struct AppearanceProxy {
void serialize(Archive & ar, const unsigned int version) { void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("Color", pd->m_Color, "color"); ar & boost::serialization::make_hrp("Color", pd->m_Color, "color");
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity).range(0.0, 1.0).set_default(1.0); ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity).range(0.0, 1.0).set_default(1.0);
ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); ar & boost::serialization::make_hrp_enum("Representation",
pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"});
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);

View File

@@ -26,15 +26,15 @@
#ifndef ULIBVTKINTERFACE_H #ifndef ULIBVTKINTERFACE_H
#define ULIBVTKINTERFACE_H #define ULIBVTKINTERFACE_H
#include "Core/Monitor.h"
#include "Core/Object.h"
#include "Core/Property.h"
#include <boost/mpl/bool.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/type_traits/is_class.hpp>
#include <iomanip> #include <iomanip>
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/serialization/serialization.hpp>
#include "Core/Object.h"
#include "Core/Property.h"
#include "Core/Monitor.h"
// vtk classes forward declaration // // vtk classes forward declaration //
class vtkProp; class vtkProp;
@@ -46,9 +46,14 @@ class vtkRendererCollection;
class vtkRenderWindowInteractor; class vtkRenderWindowInteractor;
namespace uLib { namespace uLib {
namespace Archive { class display_properties_archive; } namespace Archive {
namespace Vtk { class Puppet; class Viewer; } class display_properties_archive;
} }
namespace Vtk {
class Puppet;
class Viewer;
} // namespace Vtk
} // namespace uLib
namespace uLib { namespace uLib {
namespace Vtk { namespace Vtk {
@@ -57,11 +62,11 @@ class Puppet : public uLib::Object {
uLibTypeMacro(Puppet, uLib::Object) uLibTypeMacro(Puppet, uLib::Object)
public: public : Puppet();
Puppet();
virtual ~Puppet(); virtual ~Puppet();
virtual vtkProp *GetProp(); virtual vtkProp *GetProp();
virtual vtkProp3D *GetProxyProp();
virtual vtkPropCollection *GetParts(); virtual vtkPropCollection *GetParts();
@@ -87,10 +92,31 @@ public:
void SetSelected(bool selected = true); void SetSelected(bool selected = true);
bool IsSelected() const; bool IsSelected() const;
/**
* @brief Synchronizes the VTK representation with the internal state and properties.
*
* This method should be called whenever the underlying model or display properties
* are modified to ensure the visual representation in VTK is consistent.
*/
virtual void Update(); virtual void Update();
virtual void SyncFromVtk();
enum Representation { Points = 0, Wireframe = 1, Surface = 2, SurfaceWithEdges = 3, Volume = 4, Outline = 5, Slice = 6 }; /**
* @brief Synchronizes the internal state and properties from the VTK representation.
*
* 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() {}
enum Representation {
Points = 0,
Wireframe = 1,
Surface = 2,
SurfaceWithEdges = 3,
Volume = 4,
Outline = 5,
Slice = 6
};
void SetRepresentation(Representation mode); void SetRepresentation(Representation mode);
void SetRepresentation(const char *mode); void SetRepresentation(const char *mode);
@@ -101,10 +127,15 @@ public:
vtkRendererCollection *GetRenderers() const; vtkRendererCollection *GetRenderers() const;
const std::vector<uLib::PropertyBase*>& GetDisplayProperties() const { return m_DisplayProperties; } const std::vector<uLib::PropertyBase *> &GetDisplayProperties() const {
void RegisterDisplayProperty(uLib::PropertyBase* prop) { m_DisplayProperties.push_back(prop); } return m_DisplayProperties;
}
void RegisterDisplayProperty(uLib::PropertyBase *prop) {
m_DisplayProperties.push_back(prop);
}
virtual void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0); virtual void serialize_display(uLib::Archive::display_properties_archive &ar,
const unsigned int version = 0);
virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor); virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor);
@@ -133,15 +164,6 @@ private:
} // namespace Vtk } // namespace Vtk
} // namespace uLib } // namespace uLib
// -------------------------------------------------------------------------- // // -------------------------------------------------------------------------- //
// DISPLAY PROPERTIES SERIALIZE // DISPLAY PROPERTIES SERIALIZE
// -------------------------------------------------------------------------- // // -------------------------------------------------------------------------- //
@@ -150,50 +172,66 @@ namespace uLib {
namespace Archive { namespace Archive {
/** /**
* @brief Specialized archive for registering display-only properties in Puppets. * @brief Specialized archive for registering display-only properties in
* Puppets.
*/ */
class display_properties_archive : public boost::archive::detail::common_oarchive<display_properties_archive> { class display_properties_archive
: public boost::archive::detail::common_oarchive<
display_properties_archive> {
public: public:
display_properties_archive(Vtk::Puppet* puppet) : display_properties_archive(Vtk::Puppet *puppet)
boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header), : boost::archive::detail::common_oarchive<display_properties_archive>(
boost::archive::no_header),
m_Puppet(puppet) {} m_Puppet(puppet) {}
std::string GetCurrentGroup() const { std::string GetCurrentGroup() const {
std::string group; std::string group;
for (const auto &g : m_GroupStack) { for (const auto &g : m_GroupStack) {
if (!group.empty()) group += "."; if (!group.empty())
group += ".";
group += g; group += g;
} }
return group; return group;
} }
template<class T> template <class T> void save_override(const boost::serialization::hrp<T> &t) {
void save_override(const boost::serialization::hrp<T> &t) {
if (m_Puppet) { if (m_Puppet) {
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); uLib::Property<T> *p = new uLib::Property<T>(
if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); m_Puppet, t.name(),
if (t.has_default()) p->SetDefault(t.default_val()); &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());
m_Puppet->RegisterDisplayProperty(p); m_Puppet->RegisterDisplayProperty(p);
Vtk::Puppet *puppet = m_Puppet; Vtk::Puppet *puppet = m_Puppet;
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); uLib::Object::connect(p, &uLib::PropertyBase::Updated,
[puppet]() { puppet->Update(); });
} }
} }
template <class T> template <class T>
void save_override(const boost::serialization::hrp_enum<T> &t) { void save_override(const boost::serialization::hrp_enum<T> &t) {
if (m_Puppet) { if (m_Puppet) {
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); uLib::EnumProperty *p = new uLib::EnumProperty(
m_Puppet, t.name(),
(int *)&const_cast<boost::serialization::hrp_enum<T> &>(t).value(),
t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
m_Puppet->RegisterDisplayProperty(p); m_Puppet->RegisterDisplayProperty(p);
Vtk::Puppet *puppet = m_Puppet; Vtk::Puppet *puppet = m_Puppet;
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); uLib::Object::connect(p, &uLib::PropertyBase::Updated,
[puppet]() { puppet->Update(); });
} }
} }
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()); if (t.name())
m_GroupStack.push_back(t.name());
this->save_helper(t.const_value(), typename boost::is_class<T>::type()); this->save_helper(t.const_value(), typename boost::is_class<T>::type());
if (t.name()) m_GroupStack.pop_back(); if (t.name())
m_GroupStack.pop_back();
} }
// Recursion for nested classes, ignore primitives // Recursion for nested classes, ignore primitives
@@ -201,13 +239,11 @@ public:
this->save_helper(t, typename boost::is_class<T>::type()); this->save_helper(t, typename boost::is_class<T>::type());
} }
template<class T> template <class T> void save_helper(const T &t, boost::mpl::true_) {
void save_helper(const T &t, boost::mpl::true_) {
boost::serialization::serialize_adl(*this, const_cast<T &>(t), 0); boost::serialization::serialize_adl(*this, const_cast<T &>(t), 0);
} }
template<class T> template <class T> void save_helper(const T &t, boost::mpl::false_) {}
void save_helper(const T &t, boost::mpl::false_) {}
void save_override(const boost::archive::object_id_type &t) {} void save_override(const boost::archive::object_id_type &t) {}
void save_override(const boost::archive::object_reference_type &t) {} void save_override(const boost::archive::object_reference_type &t) {}
@@ -225,7 +261,8 @@ private:
} // namespace Archive } // namespace Archive
// This macro MUST be defined after both Puppet and display_properties_archive are fully defined. // This macro MUST be defined after both Puppet and display_properties_archive
// are fully defined.
#define ULIB_ACTIVATE_DISPLAY_PROPERTIES \ #define ULIB_ACTIVATE_DISPLAY_PROPERTIES \
{ \ { \
uLib::Archive::display_properties_archive dar(this); \ uLib::Archive::display_properties_archive dar(this); \

View File

@@ -547,16 +547,14 @@ void vtkHandlerWidget::OnMouseMove() {
total->Concatenate(op); total->Concatenate(op);
if (this->Prop3D) { if (this->Prop3D) {
double p[3], r[3], s[3]; vtkNew<vtkMatrix4x4> result;
total->GetPosition(p); total->GetMatrix(result);
total->GetOrientation(r); this->Prop3D->SetUserMatrix(result);
total->GetScale(s);
this->Prop3D->SetPosition(p); // Reset individual TRS components so UserMatrix is the single source of truth
// VTK GetOrientation already returned degrees, so r is in degrees. this->Prop3D->SetPosition(0, 0, 0);
// We apply it directly back to VTK. this->Prop3D->SetOrientation(0, 0, 0);
this->Prop3D->SetOrientation(r); this->Prop3D->SetScale(1, 1, 1);
this->Prop3D->SetScale(s);
this->Prop3D->SetUserMatrix(nullptr);
} }
this->Prop3D->Modified(); this->Prop3D->Modified();

View File

@@ -107,6 +107,12 @@ void vtkObjectsContext::Update() {
} }
} }
void vtkObjectsContext::SyncFromVtk() {
for (auto const& [obj, puppet] : m_Puppets) {
puppet->SyncFromVtk();
}
}
Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
if (!obj) return nullptr; if (!obj) return nullptr;

View File

@@ -30,6 +30,9 @@ public:
/** @brief Updates all managed puppets. */ /** @brief Updates all managed puppets. */
virtual void Update() override; virtual void Update() override;
/** @brief Synchronizes all managed puppets back to their models. */
virtual void SyncFromVtk() override;
public: public:
virtual void PuppetAdded(Puppet* puppet); virtual void PuppetAdded(Puppet* puppet);
virtual void PuppetRemoved(Puppet* puppet); virtual void PuppetRemoved(Puppet* puppet);

View File

@@ -505,11 +505,11 @@ void Viewport::SelectPuppet(Puppet* prop)
if (pv->m_HandlerWidget) { if (pv->m_HandlerWidget) {
if (prop) { if (prop) {
vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp()); vtkProp3D* prop3d = prop->GetProxyProp();
if (prop3d) { if (prop3d) {
pv->m_HandlerWidget->SetProp3D(prop3d); pv->m_HandlerWidget->SetProp3D(prop3d);
pv->m_HandlerWidget->SetEnabled(1); pv->m_HandlerWidget->SetEnabled(1);
pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); //TODO: FIX !
} }
} else { } else {
pv->m_HandlerWidget->SetEnabled(0); pv->m_HandlerWidget->SetEnabled(0);