From b45cde0badfdb02a66c819ba9c4e514705c0ae05 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Tue, 24 Mar 2026 11:36:46 +0000 Subject: [PATCH] fix transforms from handler --- app/gcompose/src/ContextPanel.cpp | 3 +- app/gcompose/src/MainPanel.cpp | 3 +- app/gcompose/src/PropertiesPanel.cpp | 1 + app/gcompose/src/StyleManager.cpp | 10 +- src/Core/ObjectsContext.cpp | 2 + src/Core/Property.h | 2 + src/Math/Transform.h | 2 +- src/Vtk/Math/testing/CMakeLists.txt | 1 + src/Vtk/Math/testing/vtkContainerBoxTest2.cpp | 45 ++++++ src/Vtk/vtkHandlerWidget.cpp | 148 +++++++++++++++--- src/Vtk/vtkHandlerWidget.h | 10 ++ 11 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 src/Vtk/Math/testing/vtkContainerBoxTest2.cpp diff --git a/app/gcompose/src/ContextPanel.cpp b/app/gcompose/src/ContextPanel.cpp index 3496d64..6f92c9d 100644 --- a/app/gcompose/src/ContextPanel.cpp +++ b/app/gcompose/src/ContextPanel.cpp @@ -14,6 +14,8 @@ ContextPanel::ContextPanel(QWidget* parent) : QWidget(parent) , m_context(nullptr) { + this->setObjectName("ContextPanel"); + this->setAttribute(Qt::WA_StyledBackground); m_layout = new QVBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); @@ -88,6 +90,5 @@ void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QIte } emit objectSelected(target); - m_propertiesPanel->setObject(target); } diff --git a/app/gcompose/src/MainPanel.cpp b/app/gcompose/src/MainPanel.cpp index ba09995..23fed2c 100644 --- a/app/gcompose/src/MainPanel.cpp +++ b/app/gcompose/src/MainPanel.cpp @@ -17,6 +17,8 @@ #include "StyleManager.h" MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) { + this->setObjectName("MainPanel"); + this->setAttribute(Qt::WA_StyledBackground); auto* mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); @@ -158,7 +160,6 @@ void MainPanel::setContext(uLib::ObjectsContext* context) { } uLib::Object::connect(context, &uLib::Object::Updated, [viewport]() { - viewport->ZoomAuto(); viewport->Render(); }); viewport->ZoomAuto(); diff --git a/app/gcompose/src/PropertiesPanel.cpp b/app/gcompose/src/PropertiesPanel.cpp index 52a8a1e..4966e63 100644 --- a/app/gcompose/src/PropertiesPanel.cpp +++ b/app/gcompose/src/PropertiesPanel.cpp @@ -7,6 +7,7 @@ PropertiesPanel::PropertiesPanel(QWidget* parent) : QWidget(parent) { this->setObjectName("PropertiesPanel"); + this->setAttribute(Qt::WA_StyledBackground); m_layout = new QVBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); diff --git a/app/gcompose/src/StyleManager.cpp b/app/gcompose/src/StyleManager.cpp index 469836f..3808af8 100644 --- a/app/gcompose/src/StyleManager.cpp +++ b/app/gcompose/src/StyleManager.cpp @@ -11,8 +11,9 @@ QLabel#TitleLabel { font-weight: bold; margin-left: 2px; } QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #ccc; } QToolButton#PaneCloseButton:hover { color: white; background: #c42b1c; } -/* Viewport & Properties Panels */ -QWidget#DisplayPropertiesPanel, QWidget#PropertiesPanel { background-color: #252526; border-left: 1px solid #3e3e42; } +/* Global & Panel Backgrounds */ +QMainWindow, QWidget#MainPanel { background-color: #1e1e1e; } +QWidget#DisplayPropertiesPanel, QWidget#PropertiesPanel, QWidget#ContextPanel { background-color: #252526; border-left: 1px solid #3e3e42; } QPushButton#DisplayToggleBtn { background-color: #333337; border: 1px solid #3e3e42; border-radius: 2px; color: #f1f1f1; font-size: 11px; } QPushButton#DisplayToggleBtn:checked { background-color: #0078d7; color: white; border-color: #005a9e; font-weight: bold; } QPushButton#DisplayToggleBtn:hover { border-color: #0078d7; } @@ -53,8 +54,9 @@ QLabel#TitleLabel { font-weight: bold; margin-left: 2px; } QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #666; } QToolButton#PaneCloseButton:hover { color: white; background: #e81123; } -/* Viewport & Properties Panels */ -QWidget#DisplayPropertiesPanel { background-color: #ffffff; border-left: 1px solid #cccccc; } +/* Global & Panel Backgrounds */ +QMainWindow, QWidget#MainPanel { background-color: #f3f3f3; } +QWidget#DisplayPropertiesPanel, QWidget#PropertiesPanel, QWidget#ContextPanel { background-color: #ffffff; border-left: 1px solid #cccccc; } QPushButton#DisplayToggleBtn { background-color: #ffffff; border: 1px solid #cccccc; border-radius: 2px; color: #333; font-size: 11px; } QPushButton#DisplayToggleBtn:checked { background-color: #0078d7; color: white; border-color: #005a9e; font-weight: bold; } QPushButton#DisplayToggleBtn:hover { border-color: #0078d7; } diff --git a/src/Core/ObjectsContext.cpp b/src/Core/ObjectsContext.cpp index dcdb26a..a9c416b 100644 --- a/src/Core/ObjectsContext.cpp +++ b/src/Core/ObjectsContext.cpp @@ -10,6 +10,8 @@ ObjectsContext::~ObjectsContext() {} void ObjectsContext::AddObject(Object* obj) { if (obj && std::find(m_objects.begin(), m_objects.end(), obj) == m_objects.end()) { m_objects.push_back(obj); + // Connect child's update to context's update to trigger re-renders + Object::connect(obj, &Object::Updated, this, &Object::Updated); ULIB_SIGNAL_EMIT(ObjectsContext::ObjectAdded, obj); this->Updated(); // Signal that the context has been updated } diff --git a/src/Core/Property.h b/src/Core/Property.h index 7363699..91d817f 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -86,6 +86,8 @@ public: if (*m_value != value) { *m_value = value; ULIB_SIGNAL_EMIT(Property::PropertyChanged); + this->Updated(); + if (m_owner) m_owner->Updated(); } } diff --git a/src/Math/Transform.h b/src/Math/Transform.h index d640eee..232ed75 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -102,7 +102,7 @@ public: Matrix3f GetRotation() const { return this->m_T.rotation(); } - void Translate(const Vector3f v) { this->m_T.pretranslate(v); } + void Translate(const Vector3f v) { this->m_T.translate(v); } void Scale(const Vector3f v) { this->m_T.scale(v); } diff --git a/src/Vtk/Math/testing/CMakeLists.txt b/src/Vtk/Math/testing/CMakeLists.txt index db5a1a4..4199727 100644 --- a/src/Vtk/Math/testing/CMakeLists.txt +++ b/src/Vtk/Math/testing/CMakeLists.txt @@ -5,6 +5,7 @@ set(TESTS vtkQuadMeshTest vtkVoxImageTest vtkVoxImageInteractiveTest + vtkContainerBoxTest2 ) set(LIBRARIES diff --git a/src/Vtk/Math/testing/vtkContainerBoxTest2.cpp b/src/Vtk/Math/testing/vtkContainerBoxTest2.cpp new file mode 100644 index 0000000..4e76178 --- /dev/null +++ b/src/Vtk/Math/testing/vtkContainerBoxTest2.cpp @@ -0,0 +1,45 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova +// All rights reserved +//////////////////////////////////////////////////////////////////////////////*/ + +#include "Vtk/uLibVtkViewer.h" +#include "Math/ContainerBox.h" +#include "Math/Units.h" +#include "Vtk/vtkContainerBox.h" +#include + +using namespace uLib; + +int main() { + std::cout << "Creating ContainerBox..." << std::endl; + ContainerBox box(Vector3f(1.0, 1.0, 1.0)); // 1x1x1 unit box + box.SetInstanceName("MyTestBox"); + + std::cout << "Creating VTK representation..." << std::endl; + Vtk::vtkContainerBox v_box(&box); + v_box.SetRepresentation(Vtk::Puppet::Wireframe); + v_box.SetColor(1.0, 0.0, 0.0); // Red + + // // 1. Initial Visualization setup (handled by Viewer) + + // // 3. Apply scaling of half the size along x + // std::cout << "Applying scaling of 0.5 along X..." << std::endl; + // box.Scale(Vector3f(0.5f, 1.0f, 1.0f)); + // box.Updated(); + + // // 2. Apply rotation along Y axes of 45 deg + // std::cout << "Applying 45 deg rotation along Y..." << std::endl; + // // box.Rotate(45.0_deg, Vector3f::UnitY()); + // box.EulerYZYRotate(Vector3f(45.0_deg, 0, 0)); + // box.Updated(); + + std::cout << "Starting viewer (close window to exit)..." << std::endl; + Vtk::Viewer viewer; + viewer.AddPuppet(v_box); + viewer.Start(); + + return 0; +} diff --git a/src/Vtk/vtkHandlerWidget.cpp b/src/Vtk/vtkHandlerWidget.cpp index 44ff8fb..8bbf030 100644 --- a/src/Vtk/vtkHandlerWidget.cpp +++ b/src/Vtk/vtkHandlerWidget.cpp @@ -71,11 +71,31 @@ vtkHandlerWidget::vtkHandlerWidget() { this->m_TranslationEnabled = true; this->m_RotationEnabled = true; this->m_ScalingEnabled = true; + this->m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New(); + this->m_BaseMatrix->Identity(); this->CreateGizmos(); } vtkHandlerWidget::~vtkHandlerWidget() {} +void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) { + if (this->Prop3D == prop) { + return; + } + this->Prop3D = prop; + if (this->Prop3D) { + // Initialize m_BaseMatrix from the object's current matrix + if (this->Prop3D->GetUserMatrix()) { + this->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix()); + } else { + this->m_BaseMatrix->Identity(); + } + this->m_TransformChain.clear(); // Clear any previous transform chain + this->UpdateGizmoPosition(); + } + this->Modified(); +} + void vtkHandlerWidget::SetEnabled(int enabling) { if (enabling) { if (this->Enabled) @@ -109,6 +129,8 @@ void vtkHandlerWidget::SetEnabled(int enabling) { this->Priority); i->AddObserver(::vtkCommand::RenderEvent, this->EventCallbackCommand, this->Priority); + i->AddObserver(::vtkCommand::KeyPressEvent, this->EventCallbackCommand, + this->Priority); this->UpdateGizmoPosition(); @@ -181,6 +203,37 @@ void vtkHandlerWidget::ProcessEvents(::vtkObject *caller, unsigned long event, case ::vtkCommand::RenderEvent: self->UpdateGizmoPosition(); break; + case ::vtkCommand::KeyPressEvent: + self->OnKeyPress(); + break; + } +} + +void vtkHandlerWidget::OnKeyPress() { + std::string key = this->Interactor->GetKeySym(); + bool ctrl = (this->Interactor->GetControlKey() != 0); + + if (ctrl && key == "z") { + if (!this->m_TransformChain.empty()) { + std::cout << "Undoing last transform action..." << std::endl; + this->m_TransformChain.pop_back(); + + // Update object from chain + vtkNew total; + total->PostMultiply(); + total->SetMatrix(this->m_BaseMatrix.GetPointer()); + for (auto& t : m_TransformChain) { + total->Concatenate(t); + } + + if (this->Prop3D && this->Prop3D->GetUserMatrix()) { + this->Prop3D->GetUserMatrix()->DeepCopy(total->GetMatrix()); + this->Prop3D->Modified(); + this->UpdateGizmoPosition(); + this->InvokeEvent(::vtkCommand::InteractionEvent, nullptr); + this->Interactor->Render(); + } + } } } @@ -229,7 +282,17 @@ void vtkHandlerWidget::OnLeftButtonDown() { vtkNew vmat; this->Prop3D->SetUserMatrix(vmat); } - this->m_InitialTransform->SetMatrix(this->Prop3D->GetUserMatrix()); + + // If the chain is empty, initialize base from current state? + // Actually, if we just started selecting this object, we should have initialized BaseMatrix. + // For now, let's keep m_InitialTransform as the state BEFORE this drag + vtkNew current; + current->PostMultiply(); + current->SetMatrix(this->m_BaseMatrix.GetPointer()); + for (auto& t : m_TransformChain) { + current->Concatenate(t); + } + this->m_InitialTransform->SetMatrix(current->GetMatrix()); } this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr); @@ -241,6 +304,27 @@ void vtkHandlerWidget::OnLeftButtonUp() { if (this->Interaction == IDLE) return; + // Finalize the current interaction into the chain + int X = this->Interactor->GetEventPosition()[0]; + int Y = this->Interactor->GetEventPosition()[1]; + + // We need to re-calculate the final 'op' to store it + // Actually, we could have stored it in OnMouseMove, but let's re-calculate or + // just capture the delta between m_InitialTransform and current UserMatrix. + if (this->Prop3D && this->Prop3D->GetUserMatrix()) { + vtkNew inv; + vtkMatrix4x4::Invert(this->m_InitialTransform->GetMatrix(), inv); + + vtkNew final_op_mat; + vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat); + + vtkNew final_op; + final_op->SetMatrix(final_op_mat); + + this->m_TransformChain.push_back(final_op); + std::cout << "Action finalized. Chain size: " << this->m_TransformChain.size() << std::endl; + } + this->Interaction = IDLE; this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(::vtkCommand::EndInteractionEvent, nullptr); @@ -390,19 +474,46 @@ void vtkHandlerWidget::OnMouseMove() { } break; case SCALE_X: - mag = get_motion_magnitude(gx, gpos); - op->Scale(std::max(0.1, 1.0 + mag), 1.0, 1.0); - // Note: Scale might need orient_inv/orient if we want to scale along local axes nicely - // but the current logic for scale already handles this if we concatenated basis. - // For now we keep it simple since user only reported rotation issue. - break; case SCALE_Y: - mag = get_motion_magnitude(gy, gpos); - op->Scale(1.0, std::max(0.1, 1.0 + mag), 1.0); - break; case SCALE_Z: - mag = get_motion_magnitude(gz, gpos); - op->Scale(1.0, 1.0, std::max(0.1, 1.0 + mag)); + { + // 1. Calculate magnitude + if (this->Interaction == SCALE_X) mag = get_motion_magnitude(gx, gpos); + else if (this->Interaction == SCALE_Y) mag = get_motion_magnitude(gy, gpos); + else mag = get_motion_magnitude(gz, gpos); + + double s = std::max(0.1, 1.0 + mag); + + // 2. Build a strictly orthonormal basis from the gizmo axes + double X[3] = {gx[0], gx[1], gx[2]}; + double Y[3] = {gy[0], gy[1], gy[2]}; + double Z[3]; + + vtkMath::Normalize(X); + double dot = vtkMath::Dot(X, Y); + for(int i=0; i<3; ++i) Y[i] -= dot * X[i]; + vtkMath::Normalize(Y); + vtkMath::Cross(X, Y, Z); // Z is now orthogonal to X and Y + + vtkNew basis; + basis->Identity(); + for(int i=0; i<3; ++i) { + basis->SetElement(i, 0, X[i]); + basis->SetElement(i, 1, Y[i]); + basis->SetElement(i, 2, Z[i]); + } + + vtkNew basis_inv; + vtkMatrix4x4::Invert(basis, basis_inv); + + // 3. Assemble oriented scale: T(gpos) * basis * S * basis_inv * T(-gpos) + // With PostMultiply: Result = T(gpos) * (basis * (S * (basis_inv * (T(-gpos) * Ident)))) + op->Concatenate(basis_inv); + if (this->Interaction == SCALE_X) op->Scale(s, 1.0, 1.0); + else if (this->Interaction == SCALE_Y) op->Scale(1.0, s, 1.0); + else op->Scale(1.0, 1.0, s); + op->Concatenate(basis); + } break; case ROT_CAM: { double camPos[3]; @@ -428,16 +539,15 @@ void vtkHandlerWidget::OnMouseMove() { op->Translate(gpos[0], gpos[1], gpos[2]); - // Apply op on top of the initial object state - vtkNew final_t; - final_t->PostMultiply(); - final_t->SetMatrix(this->m_InitialTransform->GetMatrix()); - final_t->Concatenate(op); + // Total transform = Base * Chain * Interaction + vtkNew total; + total->PostMultiply(); + total->SetMatrix(this->m_InitialTransform->GetMatrix()); // m_InitialTransform is already Base*Chain + total->Concatenate(op); vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix(); if (targetMat) { - targetMat->DeepCopy(final_t->GetMatrix()); - // std::cout << "Updated UserMatrix for interaction " << this->Interaction << std::endl; + targetMat->DeepCopy(total->GetMatrix()); } this->Prop3D->Modified(); diff --git a/src/Vtk/vtkHandlerWidget.h b/src/Vtk/vtkHandlerWidget.h index b25e5a0..5129b82 100644 --- a/src/Vtk/vtkHandlerWidget.h +++ b/src/Vtk/vtkHandlerWidget.h @@ -31,6 +31,8 @@ #include #include +#include +#include // Forward declarations of VTK classes in global namespace class vtkActor; @@ -39,6 +41,7 @@ class vtkCellPicker; class vtkTransform; class vtkObject; class vtkPlane; +class vtkProp3D; class vtkRenderWindowInteractor; namespace uLib { @@ -136,6 +139,13 @@ protected: double StartEventPosition[2]; double m_StartPickPosition[3]; vtkSmartPointer<::vtkTransform> m_InitialTransform; + + std::vector> m_TransformChain; + vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix; + +public: + virtual void SetProp3D(::vtkProp3D *prop) override; + void OnKeyPress(); private: vtkHandlerWidget(const vtkHandlerWidget &) = delete;