diff --git a/src/Vtk/Math/testing/vtkAssemblyTest.cpp b/src/Vtk/Math/testing/vtkAssemblyTest.cpp index 882e8ac..4f919e4 100644 --- a/src/Vtk/Math/testing/vtkAssemblyTest.cpp +++ b/src/Vtk/Math/testing/vtkAssemblyTest.cpp @@ -9,96 +9,101 @@ //////////////////////////////////////////////////////////////////////////////*/ +#include "Vtk/Math/vtkAssembly.h" +#include "Math/Units.h" #include "Vtk/Math/vtkAssembly.h" #include "Vtk/Math/vtkContainerBox.h" #include "Vtk/Math/vtkCylinder.h" -#include "Vtk/Math/vtkAssembly.h" -#include "Vtk/vtkObjectsContext.h" #include "Vtk/uLibVtkViewer.h" -#include "Math/Units.h" +#include "Vtk/vtkObjectsContext.h" #include -#include #include +#include #include using namespace uLib; int main(int argc, char **argv) { - bool interactive = (argc > 1 && std::string(argv[1]) == "-i"); + bool interactive = (argc > 1 && std::string(argv[1]) == "-i"); - // ---- 1. Build model objects ---- - ContainerBox box1; - box1.Scale(Vector3f(1_m, 2_m, 0.5_m)); - box1.SetPosition(Vector3f(0, 0, 0)); + // ---- 1. Build model objects ---- + ContainerBox box1; + box1.Scale(Vector3f(1_m, 2_m, 0.5_m)); + box1.SetPosition(Vector3f(0, 0, 0)); - ContainerBox box2; - box2.Scale(Vector3f(0.5_m, 0.5_m, 3_m)); - box2.SetPosition(Vector3f(2_m, 0, 0)); + ContainerBox box2; + box2.Scale(Vector3f(0.5_m, 0.5_m, 3_m)); + box2.SetPosition(Vector3f(2_m, 0, 0)); - Cylinder cyl(0.3_m, 1.5_m, 1); - cyl.SetPosition(Vector3f(0, 3_m, 0)); + Cylinder cyl(0.3_m, 1.5_m, 1); + cyl.SetPosition(Vector3f(0, 3_m, 0)); - // ---- 2. Create an Assembly and add objects ---- - Assembly assembly; - assembly.AddObject(&box1); - assembly.AddObject(&box2); - assembly.AddObject(&cyl); - assembly.SetShowBoundingBox(true); + // ---- 2. Create an Assembly and add objects ---- + Assembly assembly; + assembly.AddObject(&box1); + assembly.AddObject(&box2); + assembly.AddObject(&cyl); + assembly.SetShowBoundingBox(true); - // ---- 3. Apply a group transform ---- - assembly.SetPosition(Vector3f(1_m, 1_m, 0)); + // ---- 3. Apply a group transform ---- + assembly.SetPosition(Vector3f(1_m, 1_m, 0)); - // ---- 5. Visualize (create prop3ds to set properties) ---- - Vtk::Assembly vtkAsm(&assembly); + // ---- 5. Visualize (create prop3ds to set properties) ---- + Vtk::Assembly vtkAsm(&assembly); - Vtk::Viewer viewer; - vtkAsm.AddToViewer(viewer); // This triggers prop3d creation via ConnectRenderer which eventually calls Prop3D::GetProp - - // Explicitly update to ensure prop3ds exist and are added to assemblies - vtkAsm.Update(); + Vtk::Viewer viewer; + vtkAsm.AddToViewer( + viewer); // This triggers prop3d creation via ConnectRenderer which + // eventually calls Prop3D::GetProp - // Use the child context to find child prop3ds and set colors - if (auto* childCtx = vtkAsm.GetChildrenContext()) { - auto setProps = [](Vtk::Prop3D* 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); - } - } - }; + // Explicitly update to ensure prop3ds exist and are added to assemblies + vtkAsm.Update(); - setProps(childCtx->GetProp3D(&box1), 1.0, 0.0, 0.0); // Red - setProps(childCtx->GetProp3D(&box2), 0.0, 1.0, 0.0); // Green - setProps(childCtx->GetProp3D(&cyl), 0.0, 0.0, 1.0); // Blue - } + // Use the child context to find child prop3ds and set colors + if (auto *childCtx = vtkAsm.GetChildrenContext()) { + auto setProps = [](Vtk::Prop3D *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); + } + } + }; - std::cout << "Prop3Ds in viewport: " << viewer.getProp3Ds().size() << " (Expected 4: 1 assembly + 3 children)" << std::endl; + setProps(childCtx->GetProp3D(&box1), 1.0, 0.0, 0.0); // Red + setProps(childCtx->GetProp3D(&box2), 0.0, 1.0, 0.0); // Green + setProps(childCtx->GetProp3D(&cyl), 0.0, 0.0, 1.0); // Blue + } - // ---- 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 << "Prop3Ds in viewport: " << viewer.getProp3Ds().size() + << " (Expected 4: 1 assembly + 3 children)" << std::endl; - std::cout << "==================================================\n"; - std::cout << " vtkAssemblyTest\n"; - std::cout << " 2 boxes + 1 cylinder grouped in an assembly\n"; - std::cout << "==================================================" << 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; - if (interactive) { - viewer.ZoomAuto(); - viewer.Start(); - } else { - std::cout << "Non-interactive test passed." << std::endl; - } + std::cout << "==================================================\n"; + std::cout << " vtkAssemblyTest\n"; + std::cout << " 2 boxes + 1 cylinder grouped in an assembly\n"; + std::cout << "==================================================" + << std::endl; - return 0; + if (interactive) { + viewer.ZoomAuto(); + viewer.Start(); + } else { + std::cout << "Non-interactive test passed." << std::endl; + } + + return 0; } diff --git a/src/Vtk/Math/testing/vtkContainerBoxTest.cpp b/src/Vtk/Math/testing/vtkContainerBoxTest.cpp index f383f8e..50d0f43 100644 --- a/src/Vtk/Math/testing/vtkContainerBoxTest.cpp +++ b/src/Vtk/Math/testing/vtkContainerBoxTest.cpp @@ -35,20 +35,17 @@ using namespace uLib; int main() { BEGIN_TESTING(vtk ContainerBox Test); - { - ContainerBox* box = new ContainerBox(); - box->Scale(Vector3f(1_m, 2_m, 1_m)); - box->SetPosition(Vector3f(0, 0, 0)); + ContainerBox* box = new ContainerBox(); + box->SetSize(Vector3f(1_m, 2_m, 1_m)); + box->SetPosition(Vector3f(0, 0, 0)); - Vtk::ContainerBox v_box(box); - v_box.Update(); + Vtk::ContainerBox v_box(box); + v_box.Update(); - v_box.SetRepresentation(Vtk::Prop3D::Surface); - v_box.SetOpacity(0.5); - v_box.SetSelectable(true); - } + v_box.SetRepresentation(Vtk::Prop3D::Surface); + v_box.SetOpacity(0.5); + v_box.SetSelectable(true); - Vtk::ContainerBox v_box; v_box.findOrAddSignal(&Object::Updated)->connect([&v_box]() { std::cout << "box updated: " << v_box.GetWrapped()->GetWorldPoint(HPoint3f(1, 1, 1)) << std::endl; diff --git a/src/Vtk/Math/testing/vtkVoxImageInteractiveTest.cpp b/src/Vtk/Math/testing/vtkVoxImageInteractiveTest.cpp index fe2cb50..4f8373c 100644 --- a/src/Vtk/Math/testing/vtkVoxImageInteractiveTest.cpp +++ b/src/Vtk/Math/testing/vtkVoxImageInteractiveTest.cpp @@ -59,9 +59,9 @@ int main(int argc, char **argv) { // --- Image 1: Spherical Shell --- Vector3i dims1(64, 64, 64); - VoxImage img1(dims1); - img1.SetSpacing(Vector3f(1.0, 1.0, 1.0)); - img1.SetPosition(Vector3f(-40, -32, -32)); + VoxImage* img1 = new VoxImage(dims1); + img1->SetSpacing(Vector3f(1.0, 1.0, 1.0)); + img1->SetPosition(Vector3f(-40, -32, -32)); for (int z = 0; z < dims1(2); ++z) { for (int y = 0; y < dims1(1); ++y) { @@ -76,16 +76,16 @@ int main(int argc, char **argv) { } else { v.Value = 0.0f; } - img1[Vector3i(x, y, z)] = v; + img1->operator[](Vector3i(x, y, z)) = v; } } } // --- Image 2: Axes Gradient --- Vector3i dims2(64, 64, 64); - VoxImage img2(dims2); - img2.SetSpacing(Vector3f(1.0, 1.0, 1.0)); - img2.SetPosition(Vector3f(40, -32, -32)); + VoxImage* img2 = new VoxImage(dims2); + img2->SetSpacing(Vector3f(1.0, 1.0, 1.0)); + img2->SetPosition(Vector3f(40, -32, -32)); for (int z = 0; z < dims2(2); ++z) { for (int y = 0; y < dims2(1); ++y) { @@ -96,15 +96,15 @@ int main(int argc, char **argv) { (float(x) / dims2(0) + float(y) / dims2(1) + float(z) / dims2(2)) / 3.0f; v.Value = (40.0f * val) / factor; - img2[Vector3i(x, y, z)] = v; + img2->operator[](Vector3i(x, y, z)) = v; } } } - Vtk::VoxImage vtk_img1(&img1); + Vtk::VoxImage vtk_img1(img1); vtk_img1.setShadingPreset(0); - Vtk::VoxImage vtk_img2(&img2); + Vtk::VoxImage vtk_img2(img2); vtk_img2.setShadingPreset(1); // Use Composite without MIP for variety Vtk::Viewer viewer; diff --git a/src/Vtk/Math/testing/vtkVoxImageTest.cpp b/src/Vtk/Math/testing/vtkVoxImageTest.cpp index 13e8314..cd1aaec 100644 --- a/src/Vtk/Math/testing/vtkVoxImageTest.cpp +++ b/src/Vtk/Math/testing/vtkVoxImageTest.cpp @@ -40,12 +40,12 @@ BOOST_AUTO_TEST_CASE(vtkVoxImageConstruction) { TestVoxel zero = {0, 0}; TestVoxel nonzero = {5.5f * 1e-6f, 100}; - VoxImage img(Vector3i(10, 10, 10)); - img.SetSpacing(Vector3f(3, 3, 3)); - img.InitVoxels(zero); - img[Vector3i(3, 3, 3)] = nonzero; + VoxImage* img = new VoxImage(Vector3i(10, 10, 10)); + img->SetSpacing(Vector3f(3, 3, 3)); + img->InitVoxels(zero); + (*img)[Vector3i(3, 3, 3)] = nonzero; - Vtk::VoxImage vtk_img(&img); + Vtk::VoxImage vtk_img(img); vtk_img.SaveToXMLFile("test_vtkvoximage.vti"); if (std::getenv("CTEST_PROJECT_NAME") == nullptr) { diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index c67ffd8..ba3fafb 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -44,6 +44,10 @@ Assembly::Assembly(uLib::Assembly *content) } Assembly::~Assembly() { + if (this->m_model) { + Object::disconnect(this->m_model.get(), &uLib::Assembly::Updated, + this, &Assembly::Update); + } delete m_ChildContext; if (m_BBoxActor) m_BBoxActor->Delete(); if (m_VtkAsm) m_VtkAsm->Delete(); diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index 15e337e..ff68a9c 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include @@ -50,25 +52,33 @@ struct ContainerBoxData { vtkSmartPointer m_Cube; vtkSmartPointer m_Axes; vtkSmartPointer m_VtkAsm; + vtkSmartPointer m_CubeSource; + vtkSmartPointer m_AxesSource; uLib::Connection m_UpdateSignal; ContainerBoxData() : m_Cube(vtkSmartPointer::New()), m_Axes(vtkSmartPointer::New()), - m_VtkAsm(vtkSmartPointer::New()) {} - ~ContainerBoxData() {} + m_VtkAsm(vtkSmartPointer::New()), + m_CubeSource(vtkSmartPointer::New()), + m_AxesSource(vtkSmartPointer::New()) {} }; -ContainerBox::ContainerBox(ContainerBox::Content *content) - : d(new ContainerBoxData()), - ObjectWrapper(content ? content : new Content()) { +ContainerBox::ContainerBox(uLib::ContainerBox *model) + : Prop3D(), d(new ContainerBoxData()) { + this->m_model.reset(model); this->InstallPipe(); + d->m_UpdateSignal = Object::connect( this->m_model.get(), &uLib::Object::Updated, this, &ContainerBox::Update); - this->Update(); + this->Update(); } -ContainerBox::~ContainerBox() { delete d; } +ContainerBox::~ContainerBox() { + uLib::Object::disconnect(this->m_model.get(), &uLib::Object::Updated, this, + &ContainerBox::Update); + delete d; +} vtkPolyData *ContainerBox::GetPolyData() const { // TODO @@ -80,22 +90,35 @@ void ContainerBox::Update() { if (!this->m_model) return; - vtkProp3D *prop = vtkProp3D::SafeDownCast(this->GetProp()); - if (prop) { - // Apply the TRS matrix to the assembly - vtkNew m; - Matrix4fToVtk(this->m_model->GetMatrix(), m); - prop->SetUserMatrix(m); - prop->Modified(); - } + // Update the sources with the model's dimensions. + // This makes the "natural" bounds of the actors correct for VTK gizmos. + Vector3f size = this->m_model->GetSize(); + Vector3f origin = this->m_model->GetOrigin(); - // Apply the local shape transformation (Size/Origin) to the cube actor - vtkNew localM; - Matrix4fToVtk(this->m_model->GetLocalMatrix(), localM); - d->m_Cube->SetUserMatrix(localM); + // HandlerWidget relies on vtkProp3D::GetBounds() to determine the size + // and position of its transformation gizmos. Previously, we were applying + // the Size of the container using the actor's UserMatrix. While this looks + // correct visually, some VTK utilities (including certain internal paths + // of GetBounds()) may prioritize the bounding box of the input geometry + // (the PolyData) over the UserMatrix. This resulted in the gizmo defaulting + // to a 1x1x1 size because the underlying vtkCubeSource was still 1x1x1. - // Delegate rest of update (appearance, render, etc) - ConnectionBlock blocker(d->m_UpdateSignal); + d->m_CubeSource->SetBounds(origin.x(), origin.x() + size.x(), origin.y(), + origin.y() + size.y(), origin.z(), + origin.z() + size.z()); + d->m_CubeSource->Update(); + + d->m_AxesSource->SetOrigin(origin.x(), origin.y(), origin.z()); + d->m_AxesSource->SetScaleFactor(std::max({size.x(), size.y(), size.z()})); + d->m_AxesSource->Update(); + + // Ensure actors have identity UserMatrix since scaling is in the source. + d->m_Cube->SetUserMatrix(nullptr); + d->m_Axes->SetUserMatrix(nullptr); + + // Delegate the rest of the update (appearance, TR, render, etc) to Prop3D. + // Prop3D::Update() applies the "outer" TRS matrix (Position/Rotation/Scaling) + // to the assembly. this->Prop3D::Update(); } @@ -104,66 +127,35 @@ void ContainerBox::SyncFromVtk() { if (!this->m_model) return; - 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(); - Matrix4f vtkWorld = VtkToMatrix4f(rootMat); - - // Synchronize TRS property members from the updated local matrix - this->m_model->FromMatrix(vtkWorld); - - // Since we modified the model, notify observers, but block the loop back to - // VTK ConnectionBlock blocker(d->m_UpdateSignal); - this->m_model->Updated(); + // Sync the "outer" TRS from the assembly's matrix + this->Prop3D::SyncFromVtk(); } void ContainerBox::InstallPipe() { if (!this->m_model) return; - Content *c = this->m_model; - // CUBE + vtkSmartPointer mapper = + vtkSmartPointer::New(); - vtkSmartPointer mapper = vtkSmartPointer::New(); - vtkSmartPointer cube = vtkSmartPointer::New(); - - // cube->SetBounds(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5); - mapper->SetInputConnection(cube->GetOutputPort()); - mapper->Update(); + // CUBE // + mapper->SetInputConnection(d->m_CubeSource->GetOutputPort()); d->m_Cube->SetMapper(mapper); d->m_Cube->GetProperty()->SetRepresentationToWireframe(); d->m_Cube->GetProperty()->SetAmbient(0.7); // AXES // - vtkSmartPointer axes = vtkSmartPointer::New(); - axes->SetOrigin(0, 0, 0); mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(axes->GetOutputPort()); - mapper->Update(); + mapper->SetInputConnection(d->m_AxesSource->GetOutputPort()); d->m_Axes->SetMapper(mapper); d->m_Axes->GetProperty()->SetLineWidth(3); d->m_Axes->GetProperty()->SetAmbient(0.4); d->m_Axes->GetProperty()->SetSpecular(0); - // PIVOT // - axes = vtkSmartPointer::New(); - axes->SetOrigin(0, 0, 0); - mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(axes->GetOutputPort()); - mapper->Update(); - d->m_VtkAsm->AddPart(d->m_Cube); d->m_VtkAsm->AddPart(d->m_Axes); this->SetProp(d->m_VtkAsm); - // vtkProp3D* root = d->m_VtkAsm; - // if (root) { - // this->ApplyProp3DTransform(root); - // } this->Update(); } diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 9a9386b..408031f 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -36,6 +36,12 @@ ObjectsContext::ObjectsContext(uLib::ObjectsContext *context) } ObjectsContext::~ObjectsContext() { + if (m_Context) { + Object::disconnect(m_Context, &uLib::ObjectsContext::ObjectAdded, this, + &ObjectsContext::OnObjectAdded); + Object::disconnect(m_Context, &uLib::ObjectsContext::ObjectRemoved, this, + &ObjectsContext::OnObjectRemoved); + } for (auto const &[obj, prop3d] : m_Prop3Ds) { delete prop3d; }