diff --git a/src/Vtk/uLibVtkViewer.cpp b/src/Vtk/uLibVtkViewer.cpp index a67365b..5b2889f 100644 --- a/src/Vtk/uLibVtkViewer.cpp +++ b/src/Vtk/uLibVtkViewer.cpp @@ -38,6 +38,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include "uLibVtkViewer.h" @@ -82,6 +89,9 @@ void Viewer::InstallPipe() { // Common setup Viewport::SetupPipeline(renderWindowInteractor); + + // Setup native grid button + SetupGridButton(); // BUT we want to override the style with our custom NoSpin version vtkSmartPointer style = @@ -114,6 +124,89 @@ Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor, return widget; } +void Viewer::SetupGridButton() { + if (!m_RenderWindow || !m_RenderWindow->GetInteractor()) return; + + // Create procedural textures for the button using canvas + vtkNew canvas; + canvas->SetScalarTypeToUnsignedChar(); + canvas->SetNumberOfScalarComponents(4); + canvas->SetExtent(0, 63, 0, 63, 0, 0); + + // State 0: OFF (Gray circle, transparent background) + canvas->SetDrawColor(0, 0, 0, 0); + canvas->FillBox(0, 63, 0, 63); + canvas->SetDrawColor(120, 120, 120, 255); + canvas->DrawCircle(32, 32, 25); + canvas->Update(); + + vtkNew imgOff; + imgOff->DeepCopy(canvas->GetOutput()); + + // State 1: ON (White circle, transparent background) + canvas->SetDrawColor(0, 0, 0, 0); + canvas->FillBox(0, 63, 0, 63); + canvas->SetDrawColor(255, 255, 255, 255); + canvas->DrawCircle(32, 32, 25); + canvas->Update(); + + vtkNew imgOn; + imgOn->DeepCopy(canvas->GetOutput()); + + vtkNew rep; + rep->SetNumberOfStates(2); + rep->SetButtonTexture(0, imgOff); + rep->SetButtonTexture(1, imgOn); + + m_GridButton = vtkSmartPointer::New(); + m_GridButton->SetInteractor(m_RenderWindow->GetInteractor()); + m_GridButton->SetRepresentation(rep); + + // Position it initially + UpdateGridButtonPosition(); + + // Callback for resize (ModifiedEvent on RenderWindow) + vtkNew resizeCallback; + resizeCallback->SetClientData(this); + resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){ + auto* v = static_cast(clientdata); + v->UpdateGridButtonPosition(); + }); + m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback); + + // Callback for state change + vtkNew stateCallback; + stateCallback->SetClientData(this); + stateCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){ + auto* btn = vtkButtonWidget::SafeDownCast(caller); + auto* v = static_cast(clientdata); + auto* r = vtkTexturedButtonRepresentation2D::SafeDownCast(btn->GetRepresentation()); + v->SetGridVisible(r->GetState() == 1); + }); + + m_GridButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback); + m_GridButton->On(); + + // Set initial state + rep->SetState(GetGridVisible() ? 1 : 0); +} + +void Viewer::UpdateGridButtonPosition() { + if (!m_GridButton || !m_RenderWindow) return; + auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(m_GridButton->GetRepresentation()); + if (!rep) return; + + int *sz = m_RenderWindow->GetSize(); + if (sz[0] == 0 || sz[1] == 0) return; // Window not yet sized or hidden + + int margin_rigth = 23; + int margin_top = 170; + int btnSz = 100; // Button size in display coordinates + double bds[6] = { (double)sz[0] - btnSz - margin_rigth, (double)sz[0] - margin_rigth, + (double)sz[1] - margin_top - btnSz/2.0, (double)sz[1] - margin_top + btnSz/2.0, 0, 0 }; + rep->PlaceWidget(bds); +} + void Viewer::Start() { m_RenderWindow->GetInteractor()->Start(); } vtkRenderWindow *Viewer::GetRenderWindow() { return m_RenderWindow; } diff --git a/src/Vtk/uLibVtkViewer.h b/src/Vtk/uLibVtkViewer.h index ad4ba58..e5e2b46 100644 --- a/src/Vtk/uLibVtkViewer.h +++ b/src/Vtk/uLibVtkViewer.h @@ -62,7 +62,11 @@ private: void InstallPipe(); void UninstallPipe(); + void SetupGridButton(); + void UpdateGridButtonPosition(); + vtkRenderWindow *m_RenderWindow; + vtkSmartPointer m_GridButton; }; // template <> class Tie { diff --git a/src/Vtk/vtkHandlerWidget.cpp b/src/Vtk/vtkHandlerWidget.cpp index 3bf8802..3ea3d54 100644 --- a/src/Vtk/vtkHandlerWidget.cpp +++ b/src/Vtk/vtkHandlerWidget.cpp @@ -545,12 +545,37 @@ void vtkHandlerWidget::UpdateGizmoPosition() { if (!this->Prop3D) return; + // Calculate scaling factor: min(object_bbox, 1/5 of viewport) + double bboxSize = 1.0; + double bounds[6]; + this->Prop3D->GetBounds(bounds); + if (vtkMath::AreBoundsInitialized(bounds)) { + bboxSize = std::max({bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4]}); + } + if (bboxSize < 1e-6) bboxSize = 1.0; + + double screenLimit = bboxSize * 2.0; // Default if no renderer + if (this->CurrentRenderer) { + int *sz = this->CurrentRenderer->GetSize(); + if (sz[1] > 0) { + double pixelSize = std::min(sz[0], sz[1]) / 5.0; + vtkCamera *cam = this->CurrentRenderer->GetActiveCamera(); + if (cam->GetParallelProjection()) { + screenLimit = (pixelSize / (double)sz[1]) * 2.0 * cam->GetParallelScale(); + } else { + double dist = cam->GetDistance(); + double angleRad = vtkMath::Pi() * cam->GetViewAngle() / 180.0; + double viewHeightAtDist = 2.0 * dist * tan(angleRad / 2.0); + screenLimit = (pixelSize / (double)sz[1]) * viewHeightAtDist; + } + } + } + double scaleFactor = std::min(bboxSize, screenLimit); + vtkNew mat_gizmo; mat_gizmo->Identity(); double center[3]; - double bounds[6]; - this->Prop3D->GetBounds(bounds); center[0] = (bounds[0] + bounds[1]) / 2.0; center[1] = (bounds[2] + bounds[3]) / 2.0; center[2] = (bounds[4] + bounds[5]) / 2.0; @@ -602,6 +627,13 @@ void vtkHandlerWidget::UpdateGizmoPosition() { mat_gizmo->SetElement(2, 3, center[2]); } + // Apply scaleFactor to the gizmo matrix (only top-left 3x3) + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + mat_gizmo->SetElement(i, j, mat_gizmo->GetElement(i, j) * scaleFactor); + } + } + m_AxesX->SetUserMatrix(mat_gizmo); m_AxesY->SetUserMatrix(mat_gizmo); m_AxesZ->SetUserMatrix(mat_gizmo); @@ -635,6 +667,7 @@ void vtkHandlerWidget::UpdateGizmoPosition() { // Orient RotCam actor to face 'dir' vtkNew tcam; tcam->PostMultiply(); + tcam->Scale(scaleFactor, scaleFactor, scaleFactor); // Default circle is in XY plane (Normal Z: 0,0,1) double z[3] = {0, 0, 1}; double cross[3]; diff --git a/src/Vtk/vtkQViewport.cpp b/src/Vtk/vtkQViewport.cpp index 567b7cb..6ff12b5 100644 --- a/src/Vtk/vtkQViewport.cpp +++ b/src/Vtk/vtkQViewport.cpp @@ -1,6 +1,7 @@ #include "vtkQViewport.h" #include +#include #include #include @@ -17,6 +18,7 @@ QViewport::QViewport(QWidget* parent) : QWidget(parent) , Viewport() , m_VtkWidget(nullptr) + , m_GridButton(nullptr) { // Build the layout – zero margins so VTK fills the entire widget auto* layout = new QVBoxLayout(this); @@ -26,6 +28,36 @@ QViewport::QViewport(QWidget* parent) m_VtkWidget = new QVTKOpenGLNativeWidget(this); layout->addWidget(m_VtkWidget); + // Grid Toggle Button + m_GridButton = new QPushButton(m_VtkWidget); + m_GridButton->setText("#"); + m_GridButton->setFixedSize(40, 40); + m_GridButton->setToolTip("Toggle Grid"); + m_GridButton->setStyleSheet( + "QPushButton {" + " border-radius: 20px;" // Perfectly circular + " background-color: rgba(40, 40, 40, 180);" + " color: white;" + " font-size: 22px;" + " border: 1.5px solid rgba(255, 255, 255, 60);" + "}" + "QPushButton:hover {" + " background-color: rgba(70, 70, 70, 200);" + " border: 1.5px solid rgba(255, 255, 255, 100);" + "}" + "QPushButton:checked {" + " background-color: rgba(0, 120, 215, 200);" // Nice "active" blue + " color: white;" + " border: 1.5px solid rgba(255, 255, 255, 120);" + "}" + "QPushButton:pressed {" + " background-color: rgba(0, 90, 160, 220);" + "}" + ); + m_GridButton->setCheckable(true); + m_GridButton->setChecked(true); // Grid is on by default + connect(m_GridButton, &QPushButton::clicked, this, &QViewport::onGridButtonClicked); + // After the Qt widget exists but before the first paint, // attach the renderer and configure the pipeline. SetupPipeline(); @@ -64,5 +96,22 @@ vtkRenderWindowInteractor* QViewport::GetInteractor() return m_VtkWidget->renderWindow()->GetInteractor(); } +void QViewport::onGridButtonClicked() +{ + SetGridVisible(m_GridButton->isChecked()); +} + +void QViewport::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + if (m_GridButton) { + // Position under the gizmo (top-right corner). + // Standard CameraOrientationWidget is usually 150-180px. + int x = width() - m_GridButton->width() - 10; + int y = 160; + m_GridButton->move(x, y); + } +} + } // namespace Vtk } // namespace uLib diff --git a/src/Vtk/vtkQViewport.h b/src/Vtk/vtkQViewport.h index 83469b7..c311d66 100644 --- a/src/Vtk/vtkQViewport.h +++ b/src/Vtk/vtkQViewport.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -41,10 +42,17 @@ public: virtual vtkRenderWindowInteractor* GetInteractor() override; QVTKOpenGLNativeWidget* GetWidget() { return m_VtkWidget; } +protected: + virtual void resizeEvent(QResizeEvent* event) override; + +private slots: + void onGridButtonClicked(); + private: void SetupPipeline(); QVTKOpenGLNativeWidget* m_VtkWidget; + QPushButton* m_GridButton; }; } // namespace Vtk diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index a20f55f..ab3fed9 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -251,6 +251,22 @@ void Viewport::SelectPuppet(Puppet* prop) Render(); } +void Viewport::SetGridVisible(bool visible) +{ + if (m_GridActor) { + m_GridActor->SetVisibility(visible); + Render(); + } +} + +bool Viewport::GetGridVisible() const +{ + if (m_GridActor) { + return m_GridActor->GetVisibility() != 0; + } + return false; +} + void Viewport::addProp(vtkProp* prop) { if (m_Renderer) { @@ -270,6 +286,7 @@ void Viewport::RemoveProp(vtkProp* prop) void Viewport::UpdateGrid() { if (!m_Renderer || !m_GridSource) return; + if (m_GridActor && !m_GridActor->GetVisibility()) return; vtkCamera* camera = m_Renderer->GetActiveCamera(); if (!camera) return; diff --git a/src/Vtk/vtkViewport.h b/src/Vtk/vtkViewport.h index 2d8424a..6ae8085 100644 --- a/src/Vtk/vtkViewport.h +++ b/src/Vtk/vtkViewport.h @@ -61,6 +61,10 @@ public: vtkCornerAnnotation* GetAnnotation() { return m_Annotation; } vtkCameraOrientationWidget* GetCameraWidget(){ return m_CameraWidget; } + // Grid control + void SetGridVisible(bool visible); + bool GetGridVisible() const; + protected: void SetupPipeline(vtkRenderWindowInteractor* iren);