add assembly

This commit is contained in:
AndreaRigoni
2026-03-25 22:48:04 +00:00
parent 422113a0e9
commit 2c5d6842c3
18 changed files with 800 additions and 171 deletions

View File

@@ -10,6 +10,7 @@ set(MATH_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.cpp
PARENT_SCOPE)
set(MATH_HEADERS
@@ -20,6 +21,7 @@ set(MATH_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.h
PARENT_SCOPE)
if(BUILD_TESTING)

View File

@@ -7,6 +7,7 @@ set(TESTS
vtkVoxImageInteractiveTest
vtkContainerBoxTest
vtkContainerBoxTest2
vtkAssemblyTest
)
set(LIBRARIES

View File

@@ -1,109 +1,104 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
// All rights reserved
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
All rights reserved
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
//////////////////////////////////////////////////////////////////////////////*/
#include "Vtk/uLibVtkViewer.h"
#include "Vtk/Math/vtkAssembly.h"
#include "Vtk/Math/vtkCylinder.h"
#include "Vtk/Math/vtkContainerBox.h"
#include "Math/Assembly.h"
#include "Math/Cylinder.h"
#include "Math/ContainerBox.h"
#include "Math/Cylinder.h"
#include "Vtk/Math/vtkAssembly.h"
#include "Vtk/vtkObjectsContext.h"
#include "Vtk/uLibVtkViewer.h"
#include "Math/Units.h"
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkPropCollection.h>
#include <iostream>
using namespace uLib;
/**
* @brief This test verifies that uLib::Vtk::Assembly correctly visualizes a collection
* of objects and that transformations applied to the assembly are propagated to its children.
* It also checks that the assembly appears as a bounding box of its contents.
*/
int main() {
std::cout << "Starting vtkAssemblyTest..." << std::endl;
int main(int argc, char **argv) {
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
// 1. Setup Core Geometry
std::cout << " - Creating core Assembly..." << std::endl;
uLib::Assembly* core_assembly = new uLib::Assembly();
core_assembly->SetInstanceName("MainAssembly");
// ---- 1. Build model objects ----
ContainerBox box1;
box1.Scale(Vector3f(1_m, 2_m, 0.5_m));
box1.SetPosition(Vector3f(0, 0, 0));
// Add a box
std::cout << " - Adding ChildBox (Red)..." << std::endl;
ContainerBox* box = new ContainerBox();
box->SetInstanceName("ChildBox");
box->Translate(Vector3f(1.0, 0, 0));
core_assembly->AddObject(box);
ContainerBox box2;
box2.Scale(Vector3f(0.5_m, 0.5_m, 3_m));
box2.SetPosition(Vector3f(2_m, 0, 0));
// Add a cylinder
// std::cout << " - Adding ChildCylinder (Blue)..." << std::endl;
// Cylinder* cyl = new Cylinder(0.5, 2.0);
// cyl->SetInstanceName("ChildCylinder");
// cyl->Translate(Vector3f(-2.0, 0, 0));
// core_assembly->AddObject(cyl);
Cylinder cyl(0.3_m, 1.5_m, 1);
cyl.SetPosition(Vector3f(0, 3_m, 0));
std::cout << " - Adding another box (Green)..." << std::endl;
ContainerBox* box2 = new ContainerBox();
box2->Scale(Vector3f(1.0, 1.0, 2.0));
box2->SetInstanceName("ChildBox2");
box2->Translate(Vector3f(0, 0, 1.0));
core_assembly->AddObject(box2);
// ---- 2. Create an Assembly and add objects ----
Assembly assembly;
assembly.AddObject(&box1);
assembly.AddObject(&box2);
assembly.AddObject(&cyl);
assembly.SetShowBoundingBox(true);
// 2. Setup VTK Representation
std::cout << " - Creating VTK Assembly representation..." << std::endl;
Vtk::Assembly vtk_assembly(core_assembly);
// Find and colorized the children puppets
Vtk::Puppet* p_box = vtk_assembly.GetPuppet(box);
if (p_box) {
p_box->SetColor(1.0, 0.0, 0.0); // Red
} else {
std::cerr << "Warning: Could not find puppet for box!" << std::endl;
}
// ---- 3. Apply a group transform ----
assembly.SetPosition(Vector3f(1_m, 1_m, 0));
// Vtk::Puppet* p_cyl = vtk_assembly.GetPuppet(cyl);
// if (p_cyl) {
// p_cyl->SetColor(0.0, 0.0, 1.0); // Blue
// } else {
// std::cerr << "Warning: Could not find puppet for cylinder!" << std::endl;
// }
Vtk::Puppet* p_box2 = vtk_assembly.GetPuppet(box2);
if (p_box2) {
p_box2->SetColor(0.0, 1.0, 0.0); // Green
} else {
std::cerr << "Warning: Could not find puppet for box2!" << std::endl;
}
// 3. Test Transformation Propagation
std::cout << " - Rotating Assembly 45 degrees around Z..." << std::endl;
core_assembly->Rotate(45.0_deg, Vector3f::UnitZ());
std::cout << " - Translating Assembly up (+Y)..." << std::endl;
core_assembly->Translate(Vector3f(0, 2.0, 0));
// Notify all puppets of the change
core_assembly->Updated();
// 4. Run Visualization
std::cout << "Starting viewer (close to exit)..." << std::endl;
std::cout << "Expected: A white bounding box containing a red box and a blue cylinder, "
<< "all rotated and translated as a group." << std::endl;
// ---- 5. Visualize (create puppets to set properties) ----
Vtk::Assembly vtkAsm(&assembly);
Vtk::Viewer viewer;
viewer.AddPuppet(*p_box);
viewer.AddPuppet(*p_box2);
viewer.AddPuppet(vtk_assembly);
viewer.Start();
vtkAsm.AddToViewer(viewer); // This triggers puppet creation via ConnectRenderer which eventually calls Puppet::GetProp
// Explicitly update to ensure puppets exist and are added to assemblies
vtkAsm.Update();
// Clean up
delete core_assembly;
delete box;
delete box2;
// delete cyl;
// Use the child context to find child puppets and set colors
if (auto* childCtx = vtkAsm.GetChildrenContext()) {
auto setProps = [](Vtk::Puppet* p, float r, float g, float b) {
if (!p) return;
vtkPropCollection* props = p->GetProps();
props->InitTraversal();
for (int i=0; i < props->GetNumberOfItems(); ++i) {
if (auto* actor = vtkActor::SafeDownCast(props->GetNextProp())) {
actor->GetProperty()->SetColor(r, g, b);
actor->GetProperty()->SetRepresentationToSurface();
actor->GetProperty()->SetOpacity(0.5);
}
}
};
setProps(childCtx->GetPuppet(&box1), 1.0, 0.0, 0.0); // Red
setProps(childCtx->GetPuppet(&box2), 0.0, 1.0, 0.0); // Green
setProps(childCtx->GetPuppet(&cyl), 0.0, 0.0, 1.0); // Blue
}
std::cout << "Puppets in viewport: " << viewer.getPuppets().size() << " (Expected 4: 1 assembly + 3 children)" << std::endl;
// ---- 4. Query the bounding box for terminal output ----
Vector3f bbMin, bbMax;
assembly.GetBoundingBox(bbMin, bbMax);
std::cout << "Assembly bounding box:" << std::endl;
std::cout << " min = " << bbMin.transpose() << std::endl;
std::cout << " max = " << bbMax.transpose() << std::endl;
std::cout << "==================================================\n";
std::cout << " vtkAssemblyTest\n";
std::cout << " 2 boxes + 1 cylinder grouped in an assembly\n";
std::cout << "==================================================" << std::endl;
if (interactive) {
viewer.ZoomAuto();
viewer.Start();
} else {
std::cout << "Non-interactive test passed." << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,210 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
All rights reserved
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
//////////////////////////////////////////////////////////////////////////////*/
#include "vtkAssembly.h" // uLib::Vtk::Assembly
#include "Vtk/vtkObjectsContext.h"
#include "Math/vtkDense.h"
#include <vtkAssembly.h> // VTK library ::vtkAssembly
#include <vtkActor.h>
#include <vtkCubeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkMatrix4x4.h>
#include <vtkPropCollection.h>
#include <vtkNew.h>
#include <vtkProp3D.h>
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
namespace uLib {
namespace Vtk {
// ------------------------------------------------------------------ //
Assembly::Assembly(uLib::Assembly *content)
: m_Content(content),
m_ChildContext(nullptr),
m_BBoxActor(nullptr),
m_VtkAsm(nullptr),
m_InUpdate(false),
m_BlockUpdate(false) {
this->InstallPipe();
if (m_Content) {
Object::connect(m_Content, &uLib::Assembly::Updated,
this, &Assembly::contentUpdate);
}
}
Assembly::~Assembly() {
delete m_ChildContext;
if (m_BBoxActor) m_BBoxActor->Delete();
if (m_VtkAsm) m_VtkAsm->Delete();
}
// ------------------------------------------------------------------ //
void Assembly::InstallPipe() {
// 1. Create the VTK library assembly that groups everything
m_VtkAsm = ::vtkAssembly::New();
this->SetProp(m_VtkAsm);
// 2. Create the bounding-box wireframe actor
vtkNew<vtkCubeSource> cube;
cube->SetBounds(0, 1, 0, 1, 0, 1);
cube->Update();
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(cube->GetOutputPort());
m_BBoxActor = vtkActor::New();
m_BBoxActor->SetMapper(mapper);
m_BBoxActor->GetProperty()->SetRepresentationToWireframe();
m_BBoxActor->GetProperty()->SetColor(1.0, 0.85, 0.0); // gold wireframe
m_BBoxActor->GetProperty()->SetLineWidth(1.5);
m_BBoxActor->GetProperty()->SetOpacity(0.6);
m_BBoxActor->SetVisibility(m_Content ? m_Content->GetShowBoundingBox() : false);
m_VtkAsm->AddPart(m_BBoxActor);
// 3. Build a child-objects context (auto-creates puppets for each child)
if (m_Content) {
m_ChildContext = new vtkObjectsContext(m_Content);
// The vtkObjectsContext's own prop is already a ::vtkAssembly;
// nest it inside ours so everything moves together.
if (auto *childProp = vtkProp3D::SafeDownCast(m_ChildContext->GetProp()))
m_VtkAsm->AddPart(childProp);
}
// 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();
m_BlockUpdate = true;
Puppet::Update();
m_InUpdate = false;
}
// ------------------------------------------------------------------ //
void Assembly::Update() {
if (m_InUpdate) return;
if (!m_Content || !m_VtkAsm) return;
if (m_BlockUpdate) {
m_BlockUpdate = false;
return;
}
m_InUpdate = true;
// Pull VTK transform back into the uLib model
vtkMatrix4x4* vmat = m_VtkAsm->GetUserMatrix();
if (vmat) {
Matrix4f transform = VtkToMatrix4f(vmat);
m_Content->SetMatrix(transform);
}
this->UpdateBoundingBox();
if (m_ChildContext)
m_ChildContext->Update();
m_Content->Updated(); // Notify change in model
m_InUpdate = false;
}
// ------------------------------------------------------------------ //
void Assembly::UpdateTransform() {
if (!m_Content || !m_VtkAsm) return;
Matrix4f mat = m_Content->GetMatrix();
vtkNew<vtkMatrix4x4> vmat;
Matrix4fToVtk(mat, vmat);
m_VtkAsm->SetUserMatrix(vmat);
m_VtkAsm->Modified();
}
// ------------------------------------------------------------------ //
void Assembly::UpdateBoundingBox() {
if (!m_Content || !m_BBoxActor) return;
m_BBoxActor->SetVisibility(m_Content->GetShowBoundingBox());
Vector3f bbMin, bbMax;
m_Content->GetBoundingBox(bbMin, bbMax);
// Avoid degenerate boxes
Vector3f size = bbMax - bbMin;
if (size.norm() < 1e-6f) return;
// Rebuild the corner segments to match the AABB
vtkNew<vtkPoints> points;
vtkNew<vtkCellArray> lines;
float bounds[2][3] = {
{bbMin(0), bbMin(1), bbMin(2)},
{bbMax(0), bbMax(1), bbMax(2)}
};
// Corner segment length: 10% of dimension
float len[3] = {
(bbMax(0) - bbMin(0)) * 0.1f,
(bbMax(1) - bbMin(1)) * 0.1f,
(bbMax(2) - bbMin(2)) * 0.1f
};
for (int i = 0; i < 8; ++i) {
float p[3];
p[0] = bounds[(i & 1) ? 1 : 0][0];
p[1] = bounds[(i & 2) ? 1 : 0][1];
p[2] = bounds[(i & 4) ? 1 : 0][2];
for (int axis = 0; axis < 3; ++axis) {
float p2[3] = {p[0], p[1], p[2]};
float delta = (i & (1 << axis)) ? -len[axis] : len[axis];
p2[axis] += delta;
vtkIdType id1 = points->InsertNextPoint(p);
vtkIdType id2 = points->InsertNextPoint(p2);
lines->InsertNextCell(2);
lines->InsertCellPoint(id1);
lines->InsertCellPoint(id2);
}
}
vtkNew<vtkPolyData> poly;
poly->SetPoints(points);
poly->SetLines(lines);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(poly);
m_BBoxActor->SetMapper(mapper);
m_BBoxActor->Modified();
}
// ------------------------------------------------------------------ //
vtkObjectsContext *Assembly::GetChildrenContext() const {
return m_ChildContext;
}
} // namespace Vtk
} // namespace uLib

View File

@@ -0,0 +1,71 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
All rights reserved
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
//////////////////////////////////////////////////////////////////////////////*/
#ifndef U_VTK_ASSEMBLY_H
#define U_VTK_ASSEMBLY_H
#include "Math/Assembly.h"
#include "uLibVtkInterface.h"
class vtkActor;
class vtkAssembly; // VTK library forward declaration (must be before namespace)
namespace uLib {
namespace Vtk {
class vtkObjectsContext; // forward
/**
* @brief VTK Puppet for visualizing uLib::Assembly.
*
* Manages a VTK assembly (vtkAssembly from the VTK library) that groups
* all child puppets and applies the Assembly's AffineTransform. It also
* renders an optional bounding box wireframe computed from the Assembly's AABB.
*
* @note This class is uLib::Vtk::Assembly. It internally uses
* the VTK library class vtkAssembly for grouping, but the two
* are distinct.
*/
class Assembly : public Puppet {
public:
virtual const char *GetClassName() const override { return "Vtk.Assembly"; }
Assembly(uLib::Assembly *content);
virtual ~Assembly();
/** @brief Updates the VTK representation from the model (model→VTK). */
virtual void Update() override;
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
/** @brief Called when the model signals an update (model→VTK push). */
void contentUpdate();
/** @brief Returns the puppet managing child objects. */
vtkObjectsContext *GetChildrenContext() const;
private:
void UpdateTransform();
void UpdateBoundingBox();
void InstallPipe();
uLib::Assembly *m_Content;
vtkObjectsContext *m_ChildContext;
vtkActor *m_BBoxActor;
::vtkAssembly *m_VtkAsm; // VTK library assembly — NOT this class
bool m_InUpdate; // re-entrancy guard
bool m_BlockUpdate; // block feedback from contentUpdate→Update
};
} // namespace Vtk
} // namespace uLib
#endif // U_VTK_ASSEMBLY_H

View File

@@ -51,6 +51,8 @@ public:
virtual void Update();
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
protected:
virtual void InstallPipe();

View File

@@ -25,6 +25,7 @@
#include "Vtk/Math/vtkCylinder.h"
#include <vtkActor.h>
#include <vtkAssembly.h>
#include <vtkCylinderSource.h>
#include <vtkMatrix4x4.h>
#include <vtkPolyDataMapper.h>
@@ -37,13 +38,14 @@ namespace uLib {
namespace Vtk {
vtkCylinder::vtkCylinder(vtkCylinder::Content *content)
: m_Actor(vtkActor::New()), m_Content(content) {
: m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) {
this->InstallPipe();
Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate);
}
vtkCylinder::~vtkCylinder() {
m_Actor->Delete();
if (m_Actor) m_Actor->Delete();
if (m_VtkAsm) m_VtkAsm->Delete();
}
void vtkCylinder::contentUpdate() {
@@ -53,28 +55,31 @@ void vtkCylinder::contentUpdate() {
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return;
// 1. Placement (Position/Rotation/Model-level Scale) goes to the root prop
vtkMatrix4x4* vmat = root->GetUserMatrix();
if (!vmat) {
vtkNew<vtkMatrix4x4> mat;
root->SetUserMatrix(mat);
vmat = mat;
}
// Multiply the placement matrix by the volume scaling (Radius, Radius, Height)
Matrix4f transform = m_Content->GetMatrix() * m_Content->GetLocalMatrix();
Matrix4f transform = m_Content->GetMatrix();
Matrix4fToVtk(transform, vmat);
// Update internal alignment based on active axis
// 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor
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
// We keep it centered as per latest user preference in Step 677
// alignment->Translate(0, 0, 0); // Implicit
}
root->Modified();
@@ -90,16 +95,9 @@ void vtkCylinder::Update() {
vtkMatrix4x4* vmat = root->GetUserMatrix();
if (!vmat) return;
Matrix4f fullTransform = VtkToMatrix4f(vmat);
Matrix4f placementScale = m_Content->GetLocalMatrix().inverse();
Matrix4f transform = fullTransform * placementScale;
if (m_Content->GetParent()) {
Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform;
m_Content->SetMatrix(localT);
} else {
m_Content->SetMatrix(transform);
}
// Pull the placement matrix directly from VTK
Matrix4f transform = VtkToMatrix4f(vmat);
m_Content->SetMatrix(transform);
m_Content->Updated();
}
@@ -108,36 +106,27 @@ void vtkCylinder::InstallPipe() {
if (!m_Content)
return;
m_VtkAsm = ::vtkAssembly::New();
this->SetProp(m_VtkAsm);
vtkNew<vtkCylinderSource> cylinder;
cylinder->SetRadius(1.0);
cylinder->SetHeight(1.0);
cylinder->SetResolution(32);
m_Actor = vtkActor::New();
vtkNew<vtkTransform> alignment;
alignment->Identity();
alignment->Translate(0, 0, -0.5);
// Default to Y alignment (Identity) as per latest request
int axis = m_Content->GetAxis();
if (axis == 0) alignment->RotateZ(-90);
else if (axis == 2) alignment->RotateX(90);
m_Actor->SetUserTransform(alignment);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(cylinder->GetOutputPort());
m_Actor->SetMapper(mapper);
m_Actor->SetUserTransform(alignment);
m_Actor->GetProperty()->SetRepresentationToWireframe();
m_Actor->GetProperty()->SetAmbient(0.6);
this->SetProp(m_Actor);
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (root) {
vtkNew<vtkMatrix4x4> vmat;
Matrix4fToVtk(m_Content->GetMatrix() * m_Content->GetLocalMatrix(), vmat);
root->SetUserMatrix(vmat);
}
m_VtkAsm->AddPart(m_Actor);
this->contentUpdate();
}
} // namespace Vtk

View File

@@ -29,6 +29,7 @@
#include "Math/Cylinder.h"
#include "Vtk/uLibVtkInterface.h"
#include <vtkActor.h>
class vtkAssembly;
namespace uLib {
namespace Vtk {
@@ -53,11 +54,14 @@ public:
/** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */
virtual void Update();
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
protected:
/** Sets up the VTK visualization pipeline */
virtual void InstallPipe();
vtkActor *m_Actor;
::vtkAssembly *m_VtkAsm;
Content *m_Content;
};