From d4fd2d391499b618734f166edbba8c082d5af3a2 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Tue, 31 Mar 2026 16:04:03 +0000 Subject: [PATCH] refactor: update transformation system, improve template readability, and reorganize VTK assembly management --- CMakePresets.json | 6 + docs/transformation_system.md | 73 ----- docs/update_properties.md | 21 ++ src/Core/Debug.h | 71 ++++- src/Core/Signal.h | 2 + src/Math/Assembly.h | 7 +- src/Math/ContainerBox.h | 11 +- src/Math/Cylinder.h | 5 +- src/Math/Transform.h | 5 + src/Math/VoxImageFilter.hpp | 51 ++-- src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx | 6 +- src/Vtk/HEP/Detectors/vtkDetectorChamber.h | 2 +- src/Vtk/HEP/Detectors/vtkMuonScatter.cxx | 13 +- src/Vtk/HEP/Detectors/vtkMuonScatter.h | 2 + .../vtkVoxRaytracerRepresentation.cpp | 10 +- .../vtkVoxRaytracerRepresentation.h | 1 + src/Vtk/Math/vtkAssembly.cpp | 55 ++-- src/Vtk/Math/vtkAssembly.h | 6 +- src/Vtk/Math/vtkContainerBox.cpp | 92 +++--- src/Vtk/Math/vtkContainerBox.h | 10 +- src/Vtk/Math/vtkCylinder.cpp | 71 +++-- src/Vtk/Math/vtkCylinder.h | 5 +- src/Vtk/Math/vtkVoxImage.cpp | 7 +- src/Vtk/Math/vtkVoxImage.h | 2 + src/Vtk/uLibVtkInterface.cxx | 281 ++++++++---------- src/Vtk/uLibVtkInterface.h | 223 ++++++++------ src/Vtk/vtkHandlerWidget.cpp | 18 +- src/Vtk/vtkObjectsContext.cpp | 6 + src/Vtk/vtkObjectsContext.h | 3 + src/Vtk/vtkViewport.cpp | 4 +- 30 files changed, 568 insertions(+), 501 deletions(-) delete mode 100644 docs/transformation_system.md create mode 100644 docs/update_properties.md diff --git a/CMakePresets.json b/CMakePresets.json index 99c234f..64f2dd9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -11,6 +11,12 @@ "CMAKE_BUILD_TYPE": "Debug", "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" } + }, + { + "name": "mutom", + "description": "", + "displayName": "", + "inherits": [] } ] } \ No newline at end of file diff --git a/docs/transformation_system.md b/docs/transformation_system.md deleted file mode 100644 index a5cc8f8..0000000 --- a/docs/transformation_system.md +++ /dev/null @@ -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. diff --git a/docs/update_properties.md b/docs/update_properties.md new file mode 100644 index 0000000..15c1790 --- /dev/null +++ b/docs/update_properties.md @@ -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. + + + diff --git a/src/Core/Debug.h b/src/Core/Debug.h index 37e68aa..4a5614a 100644 --- a/src/Core/Debug.h +++ b/src/Core/Debug.h @@ -38,6 +38,76 @@ #include + +/** + * @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 { @@ -107,7 +177,6 @@ public: - class Debug { typedef detail::DebugAdapterInterface AdapterInterface; typedef SmartPointer Adapter; diff --git a/src/Core/Signal.h b/src/Core/Signal.h index f8307e4..edace95 100644 --- a/src/Core/Signal.h +++ b/src/Core/Signal.h @@ -95,6 +95,8 @@ namespace uLib { typedef boost::signals2::signal_base SignalBase; typedef boost::signals2::connection Connection; +typedef boost::signals2::shared_connection_block ConnectionBlock; + template struct Signal { typedef boost::signals2::signal type; diff --git a/src/Math/Assembly.h b/src/Math/Assembly.h index 4bca2d6..64246d4 100644 --- a/src/Math/Assembly.h +++ b/src/Math/Assembly.h @@ -96,10 +96,13 @@ public: signals: virtual void Updated() override { - if (m_InUpdated) return; // break signal recursion + if (m_InUpdated) return; m_InUpdated = true; + + // Synchronize TRS part + this->TRS::Updated(); + this->ComputeBoundingBox(); - ULIB_SIGNAL_EMIT(Object::Updated); m_InUpdated = false; } diff --git a/src/Math/ContainerBox.h b/src/Math/ContainerBox.h index eb782fa..ec83c1c 100644 --- a/src/Math/ContainerBox.h +++ b/src/Math/ContainerBox.h @@ -33,6 +33,8 @@ #include "Math/Transform.h" #include +#include + namespace uLib { /** @@ -99,9 +101,9 @@ public: */ template void serialize(ArchiveT & ar, const unsigned int version) { - ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); ar & HRP(Size); ar & HRP(Origin); + ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object(*this)); } /** @@ -216,8 +218,13 @@ signals: /** Signal emitted when properties change */ virtual void Updated() override { + // 1. Synchronize local box part (Size/Origin -> m_LocalT) 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: diff --git a/src/Math/Cylinder.h b/src/Math/Cylinder.h index aa1e7c7..688149c 100644 --- a/src/Math/Cylinder.h +++ b/src/Math/Cylinder.h @@ -177,8 +177,11 @@ public: signals: /** Signal emitted when properties change */ virtual void Updated() override { + // 1. Synchronize local cylinder part (Radius/Height/Axis -> m_LocalT) this->Sync(); - ULIB_SIGNAL_EMIT(Object::Updated); + + // 2. Synchronize TRS part (position/rotation/scaling -> m_T) and emit signal + this->TRS::Updated(); } private: diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 9f66e43..6ae1ada 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -246,6 +246,11 @@ public: this->GetTransform() = GetAffineMatrix(); } + void Updated() override { + this->SyncMatrix(); + this->AffineTransform::Updated(); + } + template void serialize(ArchiveT & ar, const unsigned int version) { ar & HRPU(position, "mm"); diff --git a/src/Math/VoxImageFilter.hpp b/src/Math/VoxImageFilter.hpp index 9b3f243..032e958 100644 --- a/src/Math/VoxImageFilter.hpp +++ b/src/Math/VoxImageFilter.hpp @@ -98,15 +98,14 @@ template void Kernel::PrintSelf(std::ostream &o) const { //////////////////////////////////////////////////////////////////////////////// -#define _TPL_ template -#define _TPLT_ VoxelT, AlgorithmT -_TPL_ -VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size) + +template +VoxImageFilter::VoxImageFilter(const Vector3i &size) : m_KernelData(size), t_Algoritm(static_cast(this)) {} -_TPL_ -void VoxImageFilter<_TPLT_>::Run() { +template +void VoxImageFilter::Run() { VoxImage buffer = *m_Image; #pragma omp parallel for for (int i = 0; i < m_Image->Data().size(); ++i) @@ -114,8 +113,8 @@ void VoxImageFilter<_TPLT_>::Run() { #pragma omp barrier } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelOffset() { +template +void VoxImageFilter::SetKernelOffset() { Vector3i id(0, 0, 0); for (int z = 0; z < m_KernelData.GetDims()(2); ++z) { for (int x = 0; x < m_KernelData.GetDims()(0); ++x) { @@ -127,8 +126,8 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() { } } -_TPL_ -float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) { +template +float VoxImageFilter::Distance2(const Vector3i &v) { Vector3i tmp = v; const Vector3i &dim = this->m_KernelData.GetDims(); 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))); } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelNumericXZY( +template +void VoxImageFilter::SetKernelNumericXZY( const std::vector &numeric) { // set data order // StructuredData::Order order = m_KernelData.GetDataOrder(); @@ -159,8 +158,8 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY( // m_KernelData.SetDataOrder(order); } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) { +template +void VoxImageFilter::SetKernelSpherical(float (*shape)(float)) { Vector3i id; for (int y = 0; y < m_KernelData.GetDims()(1); ++y) { for (int z = 0; z < m_KernelData.GetDims()(2); ++z) { @@ -172,8 +171,8 @@ void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) { } } -_TPL_ template -void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) { +template template +void VoxImageFilter::SetKernelSpherical(ShapeT shape) { Interface::IsA(); Vector3i id; for (int y = 0; y < m_KernelData.GetDims()(1); ++y) { @@ -186,8 +185,8 @@ void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) { } } -_TPL_ -void VoxImageFilter<_TPLT_>::SetKernelWeightFunction( +template +void VoxImageFilter::SetKernelWeightFunction( float (*shape)(const Vector3f &)) { const Vector3i &dim = m_KernelData.GetDims(); Vector3i id; @@ -207,8 +206,8 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction( } } -_TPL_ template -void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) { +template template +void VoxImageFilter::SetKernelWeightFunction(ShapeT shape) { Interface::IsA(); const Vector3i &dim = m_KernelData.GetDims(); Vector3i id; @@ -228,14 +227,14 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) { } } -_TPL_ -void VoxImageFilter<_TPLT_>::SetImage(Abstract::VoxImage *image) { +template +void VoxImageFilter::SetImage(Abstract::VoxImage *image) { this->m_Image = reinterpret_cast *>(image); this->SetKernelOffset(); } -_TPL_ -float VoxImageFilter<_TPLT_>::Convolve(const VoxImage &buffer, +template +float VoxImageFilter::Convolve(const VoxImage &buffer, int index) { const DataAllocator &vbuf = buffer.ConstData(); const DataAllocator &vker = m_KernelData.ConstData(); @@ -252,8 +251,8 @@ float VoxImageFilter<_TPLT_>::Convolve(const VoxImage &buffer, return conv / ksum; } -#undef _TPLT_ -#undef _TPL_ + + } // namespace uLib diff --git a/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx b/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx index af033a6..6207602 100644 --- a/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx +++ b/src/Vtk/HEP/Detectors/vtkDetectorChamber.cxx @@ -64,7 +64,7 @@ vtkDetectorChamber::vtkDetectorChamber(DetectorChamber *content) this->SetProp(m_PlaneActor); - this->contentUpdate(); + this->Update(); } vtkDetectorChamber::~vtkDetectorChamber() { @@ -76,8 +76,8 @@ DetectorChamber *vtkDetectorChamber::GetContent() { return static_cast(m_Content); } -void vtkDetectorChamber::contentUpdate() { - this->BaseClass::contentUpdate(); +void vtkDetectorChamber::Update() { + this->BaseClass::Update(); if (!m_Content) return; DetectorChamber *c = this->GetContent(); diff --git a/src/Vtk/HEP/Detectors/vtkDetectorChamber.h b/src/Vtk/HEP/Detectors/vtkDetectorChamber.h index 0dc53ee..70ec31c 100644 --- a/src/Vtk/HEP/Detectors/vtkDetectorChamber.h +++ b/src/Vtk/HEP/Detectors/vtkDetectorChamber.h @@ -56,7 +56,7 @@ public: Content *GetContent(); - virtual void contentUpdate() override; + virtual void Update() override; protected: vtkActor *m_PlaneActor; diff --git a/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx b/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx index 1d1c2bd..1272674 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx +++ b/src/Vtk/HEP/Detectors/vtkMuonScatter.cxx @@ -40,20 +40,21 @@ namespace Vtk { vtkMuonScatter::vtkMuonScatter(MuonScatter &content) : m_Content(&content), m_LineIn(vtkLineSource::New()), m_LineOut(vtkLineSource::New()), m_PolyData(vtkPolyData::New()), - m_SpherePoca(NULL) { + m_SpherePoca(NULL), m_Asm(vtkAssembly::New()) { InstallPipe(); } vtkMuonScatter::vtkMuonScatter(const MuonScatter &content) : m_Content(const_cast(&content)), 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(); } vtkMuonScatter::~vtkMuonScatter() { m_LineIn->Delete(); m_LineOut->Delete(); + m_Asm->Delete(); if (m_SpherePoca) m_SpherePoca->Delete(); } @@ -87,13 +88,15 @@ void vtkMuonScatter::InstallPipe() { mapper->SetInputConnection(m_LineIn->GetOutputPort()); vtkSmartPointer actor = vtkSmartPointer::New(); actor->SetMapper(mapper); - this->SetProp(actor); + m_Asm->AddPart(actor); mapper = vtkSmartPointer::New(); mapper->SetInputConnection(m_LineOut->GetOutputPort()); actor = vtkSmartPointer::New(); actor->SetMapper(mapper); - this->SetProp(actor); + m_Asm->AddPart(actor); + + this->SetProp(m_Asm); } vtkPolyData *vtkMuonScatter::GetPolyData() const { @@ -123,7 +126,7 @@ void vtkMuonScatter::AddPocaPoint(HPoint3f poca) { mapper->SetInputConnection(m_SpherePoca->GetOutputPort()); vtkSmartPointer actor = vtkSmartPointer::New(); actor->SetMapper(mapper); - this->SetProp(actor); + m_Asm->AddPart(actor); } HPoint3f vtkMuonScatter::GetPocaPoint() { diff --git a/src/Vtk/HEP/Detectors/vtkMuonScatter.h b/src/Vtk/HEP/Detectors/vtkMuonScatter.h index 6eda4ba..1381f3b 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonScatter.h +++ b/src/Vtk/HEP/Detectors/vtkMuonScatter.h @@ -27,6 +27,7 @@ #define VTKMUONSCATTER_H #include +#include #include #include #include @@ -85,6 +86,7 @@ private: vtkLineSource *m_LineOut; vtkSphereSource *m_SpherePoca; vtkPolyData *m_PolyData; + vtkAssembly *m_Asm; }; } // namespace Vtk diff --git a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp index 15f2c07..3d3c910 100644 --- a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp +++ b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.cpp @@ -50,6 +50,7 @@ vtkVoxRaytracerRepresentation::vtkVoxRaytracerRepresentation(Content &content) m_RayRepresentation(vtkAppendPolyData::New()), m_RayRepresentationActor(vtkActor::New()), m_Transform(vtkTransform::New()), + m_Asm(vtkAssembly::New()), m_HasMuon(false), m_HasPoca(false) { default_radius = content.GetImage()->GetSpacing()(0) / 4; m_Sphere1->SetRadius(default_radius); @@ -313,20 +314,19 @@ void vtkVoxRaytracerRepresentation::InstallPipe() { vtkSmartPointer mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(append->GetOutputPort()); vtkSmartPointer actor = vtkActor::New(); actor->SetMapper(mapper); actor->GetProperty()->SetColor(0.6, 0.6, 1); - this->SetProp(actor); + m_Asm->AddPart(actor); mapper = vtkSmartPointer::New(); mapper->SetInputConnection(m_RayLine->GetOutputPort()); m_RayLineActor->SetMapper(mapper); m_RayLineActor->GetProperty()->SetColor(1, 0, 0); - this->SetProp(m_RayLineActor); + m_Asm->AddPart(m_RayLineActor); vtkSmartPointer polyfilter = vtkSmartPointer::New(); @@ -343,7 +343,9 @@ void vtkVoxRaytracerRepresentation::InstallPipe() { vra->GetProperty()->SetEdgeVisibility(true); vra->GetProperty()->SetColor(0.5, 0.5, 0.5); - this->SetProp(vra); + m_Asm->AddPart(vra); + + this->SetProp(m_Asm); } } // namespace Vtk diff --git a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h index 2e7bab9..67b1fa5 100644 --- a/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h +++ b/src/Vtk/HEP/MuonTomography/vtkVoxRaytracerRepresentation.h @@ -107,6 +107,7 @@ private: bool m_HasPoca; Scalarf default_radius; + vtkSmartPointer m_Asm; vtkAppendPolyData *m_RayLine; vtkActor *m_RayLineActor; vtkActor *m_RayRepresentationActor; diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index a699c2f..523ac91 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -39,7 +39,7 @@ Assembly::Assembly(uLib::Assembly *content) this->InstallPipe(); if (m_Content) { Object::connect(m_Content, &uLib::Assembly::Updated, - this, &Assembly::contentUpdate); + this, &Assembly::Update); } } @@ -84,29 +84,27 @@ void Assembly::InstallPipe() { } } - // 4. Apply initial transform - this->UpdateTransform(); - 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; + // 4. Force initial visual sync + this->Update(); } // ------------------------------------------------------------------ // void Assembly::Update() { if (m_InUpdate) return; m_InUpdate = true; - this->contentUpdate(); + + if (m_Content && m_VtkAsm) { + // Apply world matrix from the assembly content + vtkNew 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; } @@ -116,14 +114,11 @@ void Assembly::SyncFromVtk() { m_InUpdate = true; - double pos[3], ori[3], scale[3]; - m_VtkAsm->GetPosition(pos); - m_VtkAsm->GetOrientation(ori); - m_VtkAsm->GetScale(scale); - - 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])); + // VTK -> Model: Update world matrix (accounting for model parents) + if (vtkProp3D* proxy = this->GetProxyProp()) { + m_Content->SetWorldMatrix(VtkToMatrix4f(proxy->GetUserMatrix())); + m_Content->FromMatrix(m_Content->GetMatrix()); + } this->UpdateBoundingBox(); if (m_ChildContext) @@ -134,14 +129,6 @@ void Assembly::SyncFromVtk() { m_InUpdate = false; } -// ------------------------------------------------------------------ // -void Assembly::UpdateTransform() { - if (!m_Content || !m_VtkAsm) return; - - this->ApplyTransform(m_VtkAsm); - m_VtkAsm->Modified(); -} - // ------------------------------------------------------------------ // void Assembly::UpdateBoundingBox() { if (!m_Content || !m_BBoxActor) return; diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index 5ad2960..f63c8d7 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -50,14 +50,14 @@ public: virtual uLib::Object* GetContent() const override { return (uLib::Object*)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. */ vtkObjectsContext *GetChildrenContext() const; private: - void UpdateTransform(); void UpdateBoundingBox(); void InstallPipe(); diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index f4ffa56..9c7b5aa 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -48,18 +48,28 @@ namespace uLib { namespace Vtk { struct ContainerBoxData { - vtkSmartPointer m_Cube; - vtkSmartPointer m_Axes; + vtkSmartPointer m_Cube; + vtkSmartPointer m_Axes; + vtkSmartPointer m_VtkAsm; + vtkSmartPointer m_Affine; + uLib::Connection m_UpdateSignal; - ContainerBoxData() : m_Cube(vtkSmartPointer::New()), m_Axes(vtkSmartPointer::New()) {} + ContainerBoxData() : m_Cube(vtkSmartPointer::New()), + m_Axes(vtkSmartPointer::New()), + m_VtkAsm(vtkSmartPointer::New()), + m_Affine(vtkSmartPointer::New()) {} ~ContainerBoxData() { } }; + + + + vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content) : d(new ContainerBoxData()), m_Content(content) { 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() { @@ -72,47 +82,50 @@ vtkPolyData *vtkContainerBox::GetPolyData() const { } -void vtkContainerBox::contentUpdate() { +void vtkContainerBox::Update() { RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); - if (!m_Content) - return; + if (!m_Content) return; vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); - if (!root) return; + if (root) { + // Apply local full matrix (TRS * LocalBox) so that nested assemblies work correctly + Matrix4f fullLocal = m_Content->GetMatrix() * m_Content->GetLocalMatrix(); + vtkNew m; + Matrix4fToVtk(fullLocal, m); + root->SetUserMatrix(m); + root->Modified(); + } - d->m_Cube->SetUserMatrix(nullptr); - d->m_Axes->SetUserMatrix(nullptr); - - TRS trs(*m_Content); - this->ApplyTransform(root); - - root->Modified(); - m_BlockUpdate = false; - Puppet::Update(); + // Delegate rest of update (appearance, render, etc) + ConnectionBlock blocker(d->m_UpdateSignal); + this->Puppet::Update(); } -void vtkContainerBox::Update() { - this->contentUpdate(); -} - void vtkContainerBox::SyncFromVtk() { RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); if (!m_Content) return; - - vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); - if (!assembly) return; - double pos[3], ori[3], scale[3]; - assembly->GetPosition(pos); - assembly->GetOrientation(ori); - assembly->GetScale(scale); + vtkProp3D* root = this->GetProxyProp(); + if (!root) return; + + // VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center + vtkMatrix4x4* rootMat = root->GetUserMatrix(); + if (rootMat) { + std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; + rootMat->Print(std::cout); + } - 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])); + Matrix4f vtkWorld = VtkToMatrix4f(rootMat); - 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->Update(); - this->SetProp(d->m_Cube); - this->SetProp(d->m_Axes); + d->m_VtkAsm->AddPart(d->m_Cube); + 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) { - TRS trs(*c); - root->SetPosition(trs.position.x(), trs.position.y(), trs.position.z()); - 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); + d->m_Affine = Matrix4fToVtk(m_Content->GetMatrix()); + root->SetUserMatrix(d->m_Affine); } + this->Update(); } } // namespace Vtk diff --git a/src/Vtk/Math/vtkContainerBox.h b/src/Vtk/Math/vtkContainerBox.h index 3dc2d68..b27a9c5 100644 --- a/src/Vtk/Math/vtkContainerBox.h +++ b/src/Vtk/Math/vtkContainerBox.h @@ -46,9 +46,14 @@ public: virtual class vtkPolyData *GetPolyData() const; - virtual void contentUpdate(); - + /** + * @brief Updates the VTK representation from the internal state. + */ virtual void Update() override; + + /** + * @brief Synchronizes the model from the VTK representation (VTK→model). + */ virtual void SyncFromVtk() override; virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } @@ -59,6 +64,7 @@ protected: struct ContainerBoxData *d; Content *m_Content; bool m_BlockUpdate = false; + }; } // namespace Vtk diff --git a/src/Vtk/Math/vtkCylinder.cpp b/src/Vtk/Math/vtkCylinder.cpp index 3e14246..92b2767 100644 --- a/src/Vtk/Math/vtkCylinder.cpp +++ b/src/Vtk/Math/vtkCylinder.cpp @@ -40,7 +40,7 @@ namespace Vtk { vtkCylinder::vtkCylinder(vtkCylinder::Content *content) : m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) { this->InstallPipe(); - Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate); + Object::connect(m_Content, &uLib::Object::Updated, this, &vtkCylinder::Update); } vtkCylinder::~vtkCylinder() { @@ -48,59 +48,56 @@ vtkCylinder::~vtkCylinder() { if (m_VtkAsm) m_VtkAsm->Delete(); } -void vtkCylinder::contentUpdate() { +void vtkCylinder::Update() { if (!m_Content) return; vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); - if (!root) return; + if (root) { + // 1. Placement handled specifically from content (use TRS GetMatrix, not World) + vtkNew m; + Matrix4fToVtk(m_Content->GetMatrix(), m); + root->SetUserMatrix(m); - // 1. Placement handled by base Puppet class via Sync / Update logic - // Update internal pd->m_Transform from content - Puppet::Update(); + // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor + // These are relative to the root assembly + vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform()); + if (alignment) { + alignment->Identity(); + alignment->PostMultiply(); + + // Initial source is centered Y-cylinder (Radial XZ [-1,1], Height Y [-0.5, 0.5]) + // Apply Radius and Height scaling + alignment->Scale(m_Content->GetRadius(), m_Content->GetHeight(), m_Content->GetRadius()); - // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor - // These are relative to the root assembly - vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform()); - if (alignment) { - alignment->Identity(); - alignment->PostMultiply(); - - // Initial source is centered Y-cylinder (Radial XZ [-1,1], Height Y [-0.5, 0.5]) - // Apply Radius and Height scaling - alignment->Scale(m_Content->GetRadius(), m_Content->GetHeight(), m_Content->GetRadius()); + // Apply Axis alignment + int axis = m_Content->GetAxis(); + if (axis == 0) alignment->RotateZ(-90); // Y -> X + else if (axis == 1) ; // Y -> Y (identity) + else if (axis == 2) alignment->RotateX(90); // Y -> Z + } - // Apply Axis alignment - int axis = m_Content->GetAxis(); - if (axis == 0) alignment->RotateZ(-90); // Y -> X - else if (axis == 1) ; // Y -> Y (identity) - else if (axis == 2) alignment->RotateX(90); // Y -> Z + root->Modified(); } - root->Modified(); -} - -void vtkCylinder::Update() { - this->contentUpdate(); + // Use base class sync, which handles appearance and children + this->Puppet::Update(); } void vtkCylinder::SyncFromVtk() { if (!m_Content) return; - vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); + vtkProp3D* assembly = this->GetProxyProp(); if (!assembly) return; - double pos[3], ori[3], scale[3]; - assembly->GetPosition(pos); - assembly->GetOrientation(ori); - assembly->GetScale(scale); + // VTK -> Model: Update TRS properties from VTK matrix via world transform + m_Content->SetWorldMatrix(VtkToMatrix4f(assembly->GetUserMatrix())); - m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); - // Convert VTK degrees to model radians - m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); - m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + // Resync TRS property members (pos/rot/scale) from the newly set local matrix + m_Content->FromMatrix(m_Content->GetMatrix()); - 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() { @@ -127,7 +124,7 @@ void vtkCylinder::InstallPipe() { m_VtkAsm->AddPart(m_Actor); - this->contentUpdate(); + this->Update(); } } // namespace Vtk diff --git a/src/Vtk/Math/vtkCylinder.h b/src/Vtk/Math/vtkCylinder.h index 1965293..9d431ee 100644 --- a/src/Vtk/Math/vtkCylinder.h +++ b/src/Vtk/Math/vtkCylinder.h @@ -48,10 +48,7 @@ public: vtkCylinder(Content *content); virtual ~vtkCylinder(); - /** Synchronizes the VTK actor with the uLib model matrix */ - virtual void contentUpdate(); - - /** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */ + /** Synchronizes the VTK actor with the uLib model matrix and vice-versa */ virtual void Update() override; /** Synchronizes the uLib model matrix with the VTK actor specifically for gizmo interactions */ diff --git a/src/Vtk/Math/vtkVoxImage.cpp b/src/Vtk/Math/vtkVoxImage.cpp index da14a8c..a266621 100644 --- a/src/Vtk/Math/vtkVoxImage.cpp +++ b/src/Vtk/Math/vtkVoxImage.cpp @@ -124,6 +124,7 @@ void vtkVoxImage::SetContent() { vtkVoxImage::vtkVoxImage(Content &content) : m_Content(content), m_Actor(vtkVolume::New()), + m_Asm(vtkAssembly::New()), m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()), m_OutlineActor(vtkActor::New()), m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6), @@ -136,6 +137,7 @@ vtkVoxImage::vtkVoxImage(Content &content) vtkVoxImage::~vtkVoxImage() { m_Image->Delete(); m_Actor->Delete(); + m_Asm->Delete(); m_Outline->Delete(); m_OutlineActor->Delete(); } @@ -330,8 +332,9 @@ void vtkVoxImage::InstallPipe() { m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); m_OutlineActor->GetProperty()->SetAmbient(0.7); - this->SetProp(m_Actor); - this->SetProp(m_OutlineActor); + m_Asm->AddPart(m_Actor); + m_Asm->AddPart(m_OutlineActor); + this->SetProp(m_Asm); // Default look this->SetRepresentation(Surface); diff --git a/src/Vtk/Math/vtkVoxImage.h b/src/Vtk/Math/vtkVoxImage.h index 647445d..7b0b087 100644 --- a/src/Vtk/Math/vtkVoxImage.h +++ b/src/Vtk/Math/vtkVoxImage.h @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -77,6 +78,7 @@ private: vtkImageData *m_Image; vtkCubeSource *m_Outline; vtkActor *m_OutlineActor; + vtkAssembly *m_Asm; vtkXMLImageDataReader *m_Reader; vtkXMLImageDataWriter *m_Writer; diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index cbf85a7..106e054 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -58,6 +58,7 @@ #include #include #include +#include #include #include "uLibVtkInterface.h" @@ -65,6 +66,7 @@ #include "Math/Dense.h" #include "Vtk/Math/vtkDense.h" #include "Core/Property.h" +#include "Math/Transform.h" @@ -83,7 +85,7 @@ public: PuppetData(Puppet* owner) : m_Puppet(owner), m_Renderers(vtkSmartPointer::New()), - m_Assembly(vtkSmartPointer::New()), + m_Prop(nullptr), m_ShowBoundingBox(false), m_ShowScaleMeasures(false), m_Representation(Puppet::Surface), @@ -103,7 +105,7 @@ public: Puppet *m_Puppet; // members // vtkSmartPointer m_Renderers; - vtkSmartPointer m_Assembly; + vtkSmartPointer m_Prop; vtkSmartPointer m_OutlineSource; vtkSmartPointer m_OutlineActor; @@ -152,30 +154,26 @@ public: } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) { // Recursively apply to parts of the assembly vtkProp3DCollection *parts = asm_p->GetParts(); - parts->InitTraversal(); - for (int i = 0; i < parts->GetNumberOfItems(); ++i) { - this->ApplyAppearance(parts->GetNextProp3D()); + if (parts) { + parts->InitTraversal(); + for (int i = 0; i < parts->GetNumberOfItems(); ++i) { + this->ApplyAppearance(parts->GetNextProp3D()); + } } } } void ApplyTransform(vtkProp3D* p3d) { if (p3d) { - if (auto* content = dynamic_cast(m_Puppet->GetContent())) { - vtkNew m; - Matrix4fToVtk(content->GetWorldMatrix(), m); - p3d->SetUserMatrix(m); - } else { - p3d->SetUserMatrix(nullptr); - p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); - - // Convert Model Radians to VTK Degrees - p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, - m_Transform.rotation.y() / CLHEP::degree, - m_Transform.rotation.z() / CLHEP::degree); - - p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); - } + p3d->SetUserMatrix(nullptr); + p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); + + // Convert Model Radians to VTK Degrees + p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, + m_Transform.rotation.y() / CLHEP::degree, + m_Transform.rotation.z() / CLHEP::degree); + + p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); } } @@ -183,13 +181,21 @@ public: if (m_Selected) { // Find first polydata in assembly to highlight vtkPolyData* polydata = nullptr; - vtkPropCollection *parts = m_Assembly->GetParts(); - parts->InitTraversal(); - for (int i = 0; i < parts->GetNumberOfItems(); ++i) { - vtkActor *actor = vtkActor::SafeDownCast(parts->GetNextProp()); - if (actor && actor->GetMapper()) { + if (vtkActor *actor = vtkActor::SafeDownCast(m_Prop)) { + if (actor->GetMapper()) { polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput()); - if (polydata) break; + } + } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(m_Prop)) { + vtkPropCollection *parts = asm_p->GetParts(); + if (parts) { + parts->InitTraversal(); + for (int i = 0; i < parts->GetNumberOfItems(); ++i) { + vtkActor *a = vtkActor::SafeDownCast(parts->GetNextProp()); + if (a && a->GetMapper()) { + polydata = vtkPolyData::SafeDownCast(a->GetMapper()->GetDataSetInput()); + if (polydata) break; + } + } } } @@ -205,40 +211,50 @@ public: } if (!m_HighlightActor) { - vtkSmartPointer edges = vtkSmartPointer::New(); - edges->BoundaryEdgesOn(); - edges->FeatureEdgesOn(); - edges->SetFeatureAngle(30); - edges->NonManifoldEdgesOn(); - edges->ManifoldEdgesOff(); - edges->SetInputData(polydata); + vtkSmartPointer cube = vtkSmartPointer::New(); + double bounds[6]; + polydata->GetBounds(bounds); + // Add a small padding to prevent z-fighting + 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); m_HighlightActor = vtkSmartPointer::New(); vtkSmartPointer mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(edges->GetOutputPort()); + mapper->SetInputConnection(cube->GetOutputPort()); 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()->SetLighting(0); } else { - // Update input data (safe even if same) if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { - if (auto* edges = vtkFeatureEdges::SafeDownCast(mapper->GetInputAlgorithm())) { - edges->SetInputData(polydata); + if (auto* cube = vtkCubeSource::SafeDownCast(mapper->GetInputAlgorithm())) { + 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 - vtkProp3D* root = nullptr; - if (m_Assembly->GetParts()->GetNumberOfItems() == 1) { - root = vtkProp3D::SafeDownCast(m_Assembly->GetParts()->GetLastProp()); - } else { - root = m_Assembly; - } - - if (root) { - m_HighlightActor->SetUserMatrix(root->GetMatrix()); + // Update highlight matrix from the model world matrix + if (m_Puppet) { + if (auto* content = m_Puppet->GetContent()) { + if (auto* tr = dynamic_cast(content)) { + vtkNew vwm; + Matrix4fToVtk(tr->GetWorldMatrix(), vwm); + m_HighlightActor->SetUserMatrix(vwm); + } + } } m_Renderers->InitTraversal(); @@ -285,36 +301,38 @@ Puppet::~Puppet() vtkProp *Puppet::GetProp() { - if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) - return pd->m_Assembly->GetParts()->GetLastProp(); - else - return pd->m_Assembly; + return pd->m_Prop; +} + +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) { if(prop) { prop->SetPickable(pd->m_Selectable); - if (auto* p3d = vtkProp3D::SafeDownCast(prop)) { - pd->m_Assembly->AddPart(p3d); - } + pd->m_Prop = vtkProp3D::SafeDownCast(prop); pd->ApplyAppearance(prop); // For the first actor added, seed the tracked display values from the VTK // actor's current state so the display properties panel shows meaningful // initial values instead of the -1 "not-overriding" sentinels. - if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) { - if (auto* actor = vtkActor::SafeDownCast(prop)) { - vtkProperty* vp = actor->GetProperty(); - if (pd->m_Representation < 0) - pd->m_Representation = vp->GetRepresentation(); - if (pd->m_Opacity < 0) - pd->m_Opacity = vp->GetOpacity(); - if (pd->m_Color.x() < 0) { - double c[3]; - vp->GetColor(c); - pd->m_Color = Vector3d(c[0], c[1], c[2]); - } + if (auto* actor = vtkActor::SafeDownCast(prop)) { + vtkProperty* vp = actor->GetProperty(); + if (pd->m_Representation < 0) + pd->m_Representation = vp->GetRepresentation(); + if (pd->m_Opacity < 0) + pd->m_Opacity = vp->GetOpacity(); + if (pd->m_Color.x() < 0) { + double c[3]; + vp->GetColor(c); + pd->m_Color = Vector3d(c[0], c[1], c[2]); } } } @@ -338,12 +356,18 @@ void Puppet::ApplyTransform(vtkProp3D* p3d) 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() { - 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) @@ -397,7 +421,8 @@ vtkRendererCollection *Puppet::GetRenderers() const void Puppet::PrintSelf(std::ostream &o) const { 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"; 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); } - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_OutlineSource->SetBounds(bounds); - pd->m_OutlineSource->Update(); + if (pd->m_Prop) { + double* bounds = pd->m_Prop->GetBounds(); + pd->m_OutlineSource->SetBounds(bounds); + pd->m_OutlineSource->Update(); + } pd->m_Renderers->InitTraversal(); 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); } - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_CubeAxesActor->SetBounds(bounds); + if (pd->m_Prop) { + double* bounds = pd->m_Prop->GetBounds(); + pd->m_CubeAxesActor->SetBounds(bounds); + } pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { @@ -472,12 +501,7 @@ void Puppet::ShowScaleMeasures(bool show) void Puppet::SetRepresentation(Representation mode) { pd->m_Representation = static_cast(mode); - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); } 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[1] = g; pd->m_Color[2] = b; - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); } void Puppet::SetOpacity(double alpha) { pd->m_Opacity = alpha; - - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - pd->ApplyAppearance(props->GetNextProp3D()); - } + pd->ApplyAppearance(pd->m_Prop); } @@ -525,11 +539,7 @@ void Puppet::SetOpacity(double alpha) void Puppet::SetSelectable(bool selectable) { pd->m_Selectable = selectable; - vtkProp3DCollection *props = pd->m_Assembly->GetParts(); - props->InitTraversal(); - for (int i = 0; i < props->GetNumberOfItems(); ++i) { - props->GetNextProp3D()->SetPickable(selectable); - } + pd->ApplyAppearance(pd->m_Prop); } bool Puppet::IsSelectable() const @@ -552,38 +562,27 @@ bool Puppet::IsSelected() const void Puppet::Update() { - vtkProp* root = this->GetProp(); - if (root) { - // Handle transformation synchronization from content - if (auto* content = dynamic_cast(GetContent())) { - pd->m_Transform = *content; // Uses TRS(const AffineTransform&) - } + // 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); - if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - 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()); - } + pd->ApplyAppearance(pd->m_Prop); if (pd->m_Selected) { pd->UpdateHighlight(); } - if (pd->m_ShowBoundingBox) { - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_OutlineSource->SetBounds(bounds); - pd->m_OutlineSource->Update(); - } - - if (pd->m_ShowScaleMeasures) { - double* bounds = pd->m_Assembly->GetBounds(); - pd->m_CubeAxesActor->SetBounds(bounds); + if (pd->m_Prop) { + if (pd->m_ShowBoundingBox) { + double* bounds = pd->m_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); + } } // Notify that the object has been updated (important for UI refresh) @@ -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(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 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) { } @@ -653,7 +621,8 @@ struct AppearanceProxy { void serialize(Archive & ar, const unsigned int version) { 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_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("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index 3b0f806..f735833 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -26,15 +26,15 @@ #ifndef ULIBVTKINTERFACE_H #define ULIBVTKINTERFACE_H +#include "Core/Monitor.h" +#include "Core/Object.h" +#include "Core/Property.h" +#include +#include +#include #include #include #include -#include -#include -#include -#include "Core/Object.h" -#include "Core/Property.h" -#include "Core/Monitor.h" // vtk classes forward declaration // class vtkProp; @@ -46,22 +46,27 @@ class vtkRendererCollection; class vtkRenderWindowInteractor; namespace uLib { - namespace Archive { class display_properties_archive; } - namespace Vtk { class Puppet; class Viewer; } +namespace Archive { +class display_properties_archive; } +namespace Vtk { +class Puppet; +class Viewer; +} // namespace Vtk +} // namespace uLib namespace uLib { namespace Vtk { class Puppet : public uLib::Object { - uLibTypeMacro(Puppet, uLib::Object) +uLibTypeMacro(Puppet, uLib::Object) -public: - Puppet(); + public : Puppet(); virtual ~Puppet(); virtual vtkProp *GetProp(); + virtual vtkProp3D *GetProxyProp(); virtual vtkPropCollection *GetParts(); @@ -87,10 +92,31 @@ public: void SetSelected(bool selected = true); 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 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(const char *mode); @@ -101,10 +127,15 @@ public: vtkRendererCollection *GetRenderers() const; - const std::vector& GetDisplayProperties() const { return m_DisplayProperties; } - void RegisterDisplayProperty(uLib::PropertyBase* prop) { m_DisplayProperties.push_back(prop); } + const std::vector &GetDisplayProperties() const { + 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); @@ -116,15 +147,15 @@ protected: void RemoveProp(vtkProp *prop); - void ApplyAppearance(vtkProp* prop); - void ApplyTransform(vtkProp3D* p3d); + void ApplyAppearance(vtkProp *prop); + void ApplyTransform(vtkProp3D *p3d); - std::vector m_DisplayProperties; + std::vector m_DisplayProperties; mutable uLib::RecursiveMutex m_UpdateMutex; private: - Puppet(const Puppet&) = delete; - Puppet& operator=(const Puppet&) = delete; + Puppet(const Puppet &) = delete; + Puppet &operator=(const Puppet &) = delete; friend class PuppetData; class PuppetData *pd; @@ -133,104 +164,110 @@ private: } // namespace Vtk } // namespace uLib - - - - - - - - - // -------------------------------------------------------------------------- // -// DISPLAY PROPERTIES SERIALIZE +// DISPLAY PROPERTIES SERIALIZE // -------------------------------------------------------------------------- // namespace uLib { 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 { +class display_properties_archive + : public boost::archive::detail::common_oarchive< + display_properties_archive> { public: - display_properties_archive(Vtk::Puppet* puppet) : - boost::archive::detail::common_oarchive(boost::archive::no_header), + display_properties_archive(Vtk::Puppet *puppet) + : boost::archive::detail::common_oarchive( + boost::archive::no_header), m_Puppet(puppet) {} - - std::string GetCurrentGroup() const { - std::string group; - for (const auto& g : m_GroupStack) { - if (!group.empty()) group += "."; - group += g; - } - return group; + + std::string GetCurrentGroup() const { + std::string group; + for (const auto &g : m_GroupStack) { + if (!group.empty()) + group += "."; + group += g; } + return group; + } - template - void save_override(const boost::serialization::hrp &t) { - if (m_Puppet) { - uLib::Property* p = new uLib::Property(m_Puppet, t.name(), &const_cast&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup()); - if (t.has_range()) p->SetRange(t.min_val(), t.max_val()); - if (t.has_default()) p->SetDefault(t.default_val()); - - m_Puppet->RegisterDisplayProperty(p); - Vtk::Puppet* puppet = m_Puppet; - uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); - } + template void save_override(const boost::serialization::hrp &t) { + if (m_Puppet) { + uLib::Property *p = new uLib::Property( + m_Puppet, t.name(), + &const_cast &>(t).value(), + t.units() ? t.units() : "", GetCurrentGroup()); + if (t.has_range()) + p->SetRange(t.min_val(), t.max_val()); + if (t.has_default()) + p->SetDefault(t.default_val()); + + m_Puppet->RegisterDisplayProperty(p); + Vtk::Puppet *puppet = m_Puppet; + uLib::Object::connect(p, &uLib::PropertyBase::Updated, + [puppet]() { puppet->Update(); }); } + } - template - void save_override(const boost::serialization::hrp_enum &t) { - if (m_Puppet) { - uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); - m_Puppet->RegisterDisplayProperty(p); - Vtk::Puppet* puppet = m_Puppet; - uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); - } + template + void save_override(const boost::serialization::hrp_enum &t) { + if (m_Puppet) { + uLib::EnumProperty *p = new uLib::EnumProperty( + m_Puppet, t.name(), + (int *)&const_cast &>(t).value(), + t.labels(), t.units() ? t.units() : "", GetCurrentGroup()); + m_Puppet->RegisterDisplayProperty(p); + Vtk::Puppet *puppet = m_Puppet; + uLib::Object::connect(p, &uLib::PropertyBase::Updated, + [puppet]() { puppet->Update(); }); } + } - template void save_override(const boost::serialization::nvp &t) { - if (t.name()) m_GroupStack.push_back(t.name()); - this->save_helper(t.const_value(), typename boost::is_class::type()); - if (t.name()) m_GroupStack.pop_back(); - } + template void save_override(const boost::serialization::nvp &t) { + if (t.name()) + m_GroupStack.push_back(t.name()); + this->save_helper(t.const_value(), typename boost::is_class::type()); + if (t.name()) + m_GroupStack.pop_back(); + } - // Recursion for nested classes, ignore primitives - template void save_override(const T &t) { - this->save_helper(t, typename boost::is_class::type()); - } + // Recursion for nested classes, ignore primitives + template void save_override(const T &t) { + this->save_helper(t, typename boost::is_class::type()); + } - template - void save_helper(const T &t, boost::mpl::true_) { - boost::serialization::serialize_adl(*this, const_cast(t), 0); - } + template void save_helper(const T &t, boost::mpl::true_) { + boost::serialization::serialize_adl(*this, const_cast(t), 0); + } - template - void save_helper(const T &t, boost::mpl::false_) {} + template 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_reference_type & t) {} - void save_override(const boost::archive::version_type & t) {} - void save_override(const boost::archive::class_id_type & t) {} - void save_override(const boost::archive::class_id_optional_type & t) {} - void save_override(const boost::archive::class_id_reference_type & t) {} - void save_override(const boost::archive::class_name_type & t) {} - void save_override(const boost::archive::tracking_type & t) {} + void save_override(const boost::archive::object_id_type &t) {} + void save_override(const boost::archive::object_reference_type &t) {} + void save_override(const boost::archive::version_type &t) {} + void save_override(const boost::archive::class_id_type &t) {} + void save_override(const boost::archive::class_id_optional_type &t) {} + void save_override(const boost::archive::class_id_reference_type &t) {} + void save_override(const boost::archive::class_name_type &t) {} + void save_override(const boost::archive::tracking_type &t) {} private: - Vtk::Puppet* m_Puppet; - std::vector m_GroupStack; + Vtk::Puppet *m_Puppet; + std::vector m_GroupStack; }; } // namespace Archive -// This macro MUST be defined after both Puppet and display_properties_archive are fully defined. -#define ULIB_ACTIVATE_DISPLAY_PROPERTIES \ - { \ - uLib::Archive::display_properties_archive dar(this); \ - this->serialize_display(dar, 0); \ - } +// This macro MUST be defined after both Puppet and display_properties_archive +// are fully defined. +#define ULIB_ACTIVATE_DISPLAY_PROPERTIES \ + { \ + uLib::Archive::display_properties_archive dar(this); \ + this->serialize_display(dar, 0); \ + } } // namespace uLib diff --git a/src/Vtk/vtkHandlerWidget.cpp b/src/Vtk/vtkHandlerWidget.cpp index dd7dabc..294910a 100644 --- a/src/Vtk/vtkHandlerWidget.cpp +++ b/src/Vtk/vtkHandlerWidget.cpp @@ -547,16 +547,14 @@ void vtkHandlerWidget::OnMouseMove() { total->Concatenate(op); if (this->Prop3D) { - double p[3], r[3], s[3]; - total->GetPosition(p); - total->GetOrientation(r); - total->GetScale(s); - this->Prop3D->SetPosition(p); - // VTK GetOrientation already returned degrees, so r is in degrees. - // We apply it directly back to VTK. - this->Prop3D->SetOrientation(r); - this->Prop3D->SetScale(s); - this->Prop3D->SetUserMatrix(nullptr); + vtkNew result; + total->GetMatrix(result); + this->Prop3D->SetUserMatrix(result); + + // Reset individual TRS components so UserMatrix is the single source of truth + this->Prop3D->SetPosition(0, 0, 0); + this->Prop3D->SetOrientation(0, 0, 0); + this->Prop3D->SetScale(1, 1, 1); } this->Prop3D->Modified(); diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 4e17ea0..4894890 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -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) { if (!obj) return nullptr; diff --git a/src/Vtk/vtkObjectsContext.h b/src/Vtk/vtkObjectsContext.h index cfb480a..3299de3 100644 --- a/src/Vtk/vtkObjectsContext.h +++ b/src/Vtk/vtkObjectsContext.h @@ -30,6 +30,9 @@ public: /** @brief Updates all managed puppets. */ virtual void Update() override; + /** @brief Synchronizes all managed puppets back to their models. */ + virtual void SyncFromVtk() override; + public: virtual void PuppetAdded(Puppet* puppet); virtual void PuppetRemoved(Puppet* puppet); diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index f4c2026..d6b8521 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -505,11 +505,11 @@ void Viewport::SelectPuppet(Puppet* prop) if (pv->m_HandlerWidget) { if (prop) { - vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp()); + vtkProp3D* prop3d = prop->GetProxyProp(); if (prop3d) { pv->m_HandlerWidget->SetProp3D(prop3d); pv->m_HandlerWidget->SetEnabled(1); - pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); + pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); //TODO: FIX ! } } else { pv->m_HandlerWidget->SetEnabled(0);