refactor: update transformation system, improve template readability, and reorganize VTK assembly management
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mutom",
|
||||
"description": "",
|
||||
"displayName": "",
|
||||
"inherits": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
21
docs/update_properties.md
Normal 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.
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,76 @@
|
||||
#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 {
|
||||
|
||||
|
||||
@@ -107,7 +177,6 @@ public:
|
||||
|
||||
|
||||
|
||||
|
||||
class Debug {
|
||||
typedef detail::DebugAdapterInterface AdapterInterface;
|
||||
typedef SmartPointer<detail::DebugAdapterInterface> Adapter;
|
||||
|
||||
@@ -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 <typename T> struct Signal {
|
||||
typedef boost::signals2::signal<T> type;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "Math/Transform.h"
|
||||
#include <utility>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace uLib {
|
||||
|
||||
/**
|
||||
@@ -99,9 +101,9 @@ public:
|
||||
*/
|
||||
template <class ArchiveT>
|
||||
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(Origin);
|
||||
ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object<TRS>(*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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -246,6 +246,11 @@ public:
|
||||
this->GetTransform() = GetAffineMatrix();
|
||||
}
|
||||
|
||||
void Updated() override {
|
||||
this->SyncMatrix();
|
||||
this->AffineTransform::Updated();
|
||||
}
|
||||
|
||||
template <class ArchiveT>
|
||||
void serialize(ArchiveT & ar, const unsigned int version) {
|
||||
ar & HRPU(position, "mm");
|
||||
|
||||
@@ -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)) {}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::Run() {
|
||||
template <typename VoxelT, typename AlgorithmT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::Run() {
|
||||
VoxImage<VoxelT> 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 <typename VoxelT, typename AlgorithmT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::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 <typename VoxelT, typename AlgorithmT>
|
||||
float VoxImageFilter<VoxelT, AlgorithmT>::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 <typename VoxelT, typename AlgorithmT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelNumericXZY(
|
||||
const std::vector<float> &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 <typename VoxelT, typename AlgorithmT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::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 <class ShapeT>
|
||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) {
|
||||
template <typename VoxelT, typename AlgorithmT> template <class ShapeT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelSpherical(ShapeT shape) {
|
||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||
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 <typename VoxelT, typename AlgorithmT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelWeightFunction(
|
||||
float (*shape)(const Vector3f &)) {
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i id;
|
||||
@@ -207,8 +206,8 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_ template <class ShapeT>
|
||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
||||
template <typename VoxelT, typename AlgorithmT> template <class ShapeT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::SetKernelWeightFunction(ShapeT shape) {
|
||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||
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 <typename VoxelT, typename AlgorithmT>
|
||||
void VoxImageFilter<VoxelT, AlgorithmT>::SetImage(Abstract::VoxImage *image) {
|
||||
this->m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
||||
this->SetKernelOffset();
|
||||
}
|
||||
|
||||
_TPL_
|
||||
float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer,
|
||||
template <typename VoxelT, typename AlgorithmT>
|
||||
float VoxImageFilter<VoxelT, AlgorithmT>::Convolve(const VoxImage<VoxelT> &buffer,
|
||||
int index) {
|
||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
||||
const DataAllocator<VoxelT> &vker = m_KernelData.ConstData();
|
||||
@@ -252,8 +251,8 @@ float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer,
|
||||
return conv / ksum;
|
||||
}
|
||||
|
||||
#undef _TPLT_
|
||||
#undef _TPL_
|
||||
|
||||
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
|
||||
@@ -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<DetectorChamber *>(m_Content);
|
||||
}
|
||||
|
||||
void vtkDetectorChamber::contentUpdate() {
|
||||
this->BaseClass::contentUpdate();
|
||||
void vtkDetectorChamber::Update() {
|
||||
this->BaseClass::Update();
|
||||
|
||||
if (!m_Content) return;
|
||||
DetectorChamber *c = this->GetContent();
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
|
||||
Content *GetContent();
|
||||
|
||||
virtual void contentUpdate() override;
|
||||
virtual void Update() override;
|
||||
|
||||
protected:
|
||||
vtkActor *m_PlaneActor;
|
||||
|
||||
@@ -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<MuonScatter *>(&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<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
|
||||
actor->SetMapper(mapper);
|
||||
this->SetProp(actor);
|
||||
m_Asm->AddPart(actor);
|
||||
|
||||
mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||
mapper->SetInputConnection(m_LineOut->GetOutputPort());
|
||||
actor = vtkSmartPointer<vtkActor>::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<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
|
||||
actor->SetMapper(mapper);
|
||||
this->SetProp(actor);
|
||||
m_Asm->AddPart(actor);
|
||||
}
|
||||
|
||||
HPoint3f vtkMuonScatter::GetPocaPoint() {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#define VTKMUONSCATTER_H
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkAssembly.h>
|
||||
#include <vtkAppendPolyData.h>
|
||||
#include <vtkLineSource.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
@@ -85,6 +86,7 @@ private:
|
||||
vtkLineSource *m_LineOut;
|
||||
vtkSphereSource *m_SpherePoca;
|
||||
vtkPolyData *m_PolyData;
|
||||
vtkAssembly *m_Asm;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -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<vtkPolyDataMapper> mapper =
|
||||
vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||
|
||||
mapper->SetInputConnection(append->GetOutputPort());
|
||||
|
||||
vtkSmartPointer<vtkActor> actor = vtkActor::New();
|
||||
actor->SetMapper(mapper);
|
||||
actor->GetProperty()->SetColor(0.6, 0.6, 1);
|
||||
this->SetProp(actor);
|
||||
m_Asm->AddPart(actor);
|
||||
|
||||
mapper = vtkSmartPointer<vtkPolyDataMapper>::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<vtkTransformPolyDataFilter> polyfilter =
|
||||
vtkSmartPointer<vtkTransformPolyDataFilter>::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
|
||||
|
||||
@@ -107,6 +107,7 @@ private:
|
||||
bool m_HasPoca;
|
||||
|
||||
Scalarf default_radius;
|
||||
vtkSmartPointer<vtkAssembly> m_Asm;
|
||||
vtkAppendPolyData *m_RayLine;
|
||||
vtkActor *m_RayLineActor;
|
||||
vtkActor *m_RayRepresentationActor;
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -48,18 +48,28 @@ namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct ContainerBoxData {
|
||||
vtkSmartPointer<vtkActor> m_Cube;
|
||||
vtkSmartPointer<vtkActor> m_Axes;
|
||||
vtkSmartPointer<vtkActor> m_Cube;
|
||||
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() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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<vtkMatrix4x4> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<vtkMatrix4x4> 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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <vtkVolume.h>
|
||||
#include <vtkXMLImageDataReader.h>
|
||||
#include <vtkXMLImageDataWriter.h>
|
||||
#include <vtkAssembly.h>
|
||||
|
||||
#include <Math/VoxImage.h>
|
||||
|
||||
@@ -77,6 +78,7 @@ private:
|
||||
vtkImageData *m_Image;
|
||||
vtkCubeSource *m_Outline;
|
||||
vtkActor *m_OutlineActor;
|
||||
vtkAssembly *m_Asm;
|
||||
|
||||
vtkXMLImageDataReader *m_Reader;
|
||||
vtkXMLImageDataWriter *m_Writer;
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkFeatureEdges.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkCubeSource.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
|
||||
#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<vtkRendererCollection>::New()),
|
||||
m_Assembly(vtkSmartPointer<vtkAssembly>::New()),
|
||||
m_Prop(nullptr),
|
||||
m_ShowBoundingBox(false),
|
||||
m_ShowScaleMeasures(false),
|
||||
m_Representation(Puppet::Surface),
|
||||
@@ -103,7 +105,7 @@ public:
|
||||
Puppet *m_Puppet;
|
||||
// members //
|
||||
vtkSmartPointer<vtkRendererCollection> m_Renderers;
|
||||
vtkSmartPointer<vtkAssembly> m_Assembly;
|
||||
vtkSmartPointer<vtkProp3D> m_Prop;
|
||||
|
||||
vtkSmartPointer<vtkOutlineSource> m_OutlineSource;
|
||||
vtkSmartPointer<vtkActor> 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<uLib::AffineTransform*>(m_Puppet->GetContent())) {
|
||||
vtkNew<vtkMatrix4x4> 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<vtkFeatureEdges> edges = vtkSmartPointer<vtkFeatureEdges>::New();
|
||||
edges->BoundaryEdgesOn();
|
||||
edges->FeatureEdgesOn();
|
||||
edges->SetFeatureAngle(30);
|
||||
edges->NonManifoldEdgesOn();
|
||||
edges->ManifoldEdgesOff();
|
||||
edges->SetInputData(polydata);
|
||||
vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::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<vtkActor>::New();
|
||||
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::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<uLib::TRS*>(content)) {
|
||||
vtkNew<vtkMatrix4x4> 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<int>(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<uLib::AffineTransform*>(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<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)
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
#ifndef 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 <ostream>
|
||||
#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 //
|
||||
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<uLib::PropertyBase*>& GetDisplayProperties() const { return m_DisplayProperties; }
|
||||
void RegisterDisplayProperty(uLib::PropertyBase* prop) { m_DisplayProperties.push_back(prop); }
|
||||
const std::vector<uLib::PropertyBase *> &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<uLib::PropertyBase*> m_DisplayProperties;
|
||||
std::vector<uLib::PropertyBase *> 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<display_properties_archive> {
|
||||
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<display_properties_archive>(boost::archive::no_header),
|
||||
display_properties_archive(Vtk::Puppet *puppet)
|
||||
: boost::archive::detail::common_oarchive<display_properties_archive>(
|
||||
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<class T>
|
||||
void save_override(const boost::serialization::hrp<T> &t) {
|
||||
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());
|
||||
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 <class T> void save_override(const boost::serialization::hrp<T> &t) {
|
||||
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());
|
||||
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<class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
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());
|
||||
m_Puppet->RegisterDisplayProperty(p);
|
||||
Vtk::Puppet* puppet = m_Puppet;
|
||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||
}
|
||||
template <class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
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());
|
||||
m_Puppet->RegisterDisplayProperty(p);
|
||||
Vtk::Puppet *puppet = m_Puppet;
|
||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated,
|
||||
[puppet]() { puppet->Update(); });
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
|
||||
if (t.name()) m_GroupStack.push_back(t.name());
|
||||
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||
if (t.name()) m_GroupStack.pop_back();
|
||||
}
|
||||
template <class T> void save_override(const boost::serialization::nvp<T> &t) {
|
||||
if (t.name())
|
||||
m_GroupStack.push_back(t.name());
|
||||
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||
if (t.name())
|
||||
m_GroupStack.pop_back();
|
||||
}
|
||||
|
||||
// Recursion for nested classes, ignore primitives
|
||||
template<class T> void save_override(const T &t) {
|
||||
this->save_helper(t, typename boost::is_class<T>::type());
|
||||
}
|
||||
// Recursion for nested classes, ignore primitives
|
||||
template <class T> void save_override(const T &t) {
|
||||
this->save_helper(t, typename boost::is_class<T>::type());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::true_) {
|
||||
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
||||
}
|
||||
template <class T> void save_helper(const T &t, boost::mpl::true_) {
|
||||
boost::serialization::serialize_adl(*this, const_cast<T &>(t), 0);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::false_) {}
|
||||
template <class T> 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<std::string> m_GroupStack;
|
||||
Vtk::Puppet *m_Puppet;
|
||||
std::vector<std::string> 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
|
||||
|
||||
|
||||
@@ -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<vtkMatrix4x4> 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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user