diff --git a/src/Vtk/testing/vtkQViewportTest.cpp b/src/Vtk/testing/vtkQViewportTest.cpp index 2f36184..a4feb39 100644 --- a/src/Vtk/testing/vtkQViewportTest.cpp +++ b/src/Vtk/testing/vtkQViewportTest.cpp @@ -79,8 +79,9 @@ int main(int argc, char** argv) if (std::getenv("CTEST_PROJECT_NAME") == nullptr) { // Run application for a while to see the result - // return app.exec(); + return app.exec(); } + END_TESTING; } diff --git a/src/Vtk/uLibVtkViewer.cpp b/src/Vtk/uLibVtkViewer.cpp index 88c2454..9e96cae 100644 --- a/src/Vtk/uLibVtkViewer.cpp +++ b/src/Vtk/uLibVtkViewer.cpp @@ -72,6 +72,7 @@ struct ViewerData { vtkRenderWindow *m_RenderWindow; vtkSmartPointer m_Interactor; vtkSmartPointer m_GridButton; + vtkSmartPointer m_ProjButton; ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {} ~ViewerData() { @@ -97,6 +98,11 @@ Viewer::~Viewer() { dv->m_GridButton->SetInteractor(nullptr); dv->m_GridButton = nullptr; } + if (dv->m_ProjButton) { + dv->m_ProjButton->Off(); + dv->m_ProjButton->SetInteractor(nullptr); + dv->m_ProjButton = nullptr; + } if (this->GetRenderWindow()) { this->GetRenderWindow()->RemoveAllObservers(); } @@ -123,6 +129,7 @@ void Viewer::InstallPipe() { // Setup native grid button if (!std::getenv("CTEST_PROJECT_NAME")) { SetupGridButton(); + SetupProjButton(); } // BUT we want to override the style with our custom NoSpin version @@ -238,9 +245,91 @@ void Viewer::UpdateGridButtonPosition() { rep->PlaceWidget(bds); } -void Viewer::Start() { +void Viewer::SetupProjButton() { + if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return; + + vtkNew canvas; + canvas->SetScalarTypeToUnsignedChar(); + canvas->SetNumberOfScalarComponents(4); + canvas->SetExtent(0, 63, 0, 63, 0, 0); + + // State 0: Perspective (gray trapezoid-like lines) + canvas->SetDrawColor(0, 0, 0, 0); + canvas->FillBox(0, 63, 0, 63); + canvas->SetDrawColor(120, 120, 120, 255); + canvas->DrawSegment(16, 16, 48, 16); + canvas->DrawSegment(48, 16, 56, 48); + canvas->DrawSegment(56, 48, 8, 48); + canvas->DrawSegment(8, 48, 16, 16); + canvas->Update(); + + vtkNew imgPersp; + imgPersp->DeepCopy(canvas->GetOutput()); + + // State 1: Orthographic (white rectangle) + canvas->SetDrawColor(0, 0, 0, 0); + canvas->FillBox(0, 63, 0, 63); + canvas->SetDrawColor(255, 255, 255, 255); + canvas->DrawSegment(12, 16, 52, 16); + canvas->DrawSegment(52, 16, 52, 48); + canvas->DrawSegment(52, 48, 12, 48); + canvas->DrawSegment(12, 48, 12, 16); + canvas->Update(); + + vtkNew imgOrtho; + imgOrtho->DeepCopy(canvas->GetOutput()); + + vtkNew rep; + rep->SetNumberOfStates(2); + rep->SetButtonTexture(0, imgPersp); + rep->SetButtonTexture(1, imgOrtho); + + dv->m_ProjButton = vtkSmartPointer::New(); + dv->m_ProjButton->SetInteractor(dv->m_RenderWindow->GetInteractor()); + dv->m_ProjButton->SetRepresentation(rep); + + UpdateProjButtonPosition(); + + vtkNew resizeCallback; + resizeCallback->SetClientData(this); + resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){ + static_cast(clientdata)->UpdateProjButtonPosition(); + }); + dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback); + + 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->SetParallelProjection(r->GetState() == 1); + }); + dv->m_ProjButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback); + dv->m_ProjButton->On(); + + rep->SetState(GetParallelProjection() ? 1 : 0); +} + +void Viewer::UpdateProjButtonPosition() { + if (!dv->m_ProjButton || !dv->m_RenderWindow) return; + auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_ProjButton->GetRepresentation()); + if (!rep) return; + + int *sz = dv->m_RenderWindow->GetSize(); + if (sz[0] == 0 || sz[1] == 0) return; + + int margin_right = 23; + int margin_top = 220; // below the grid button (170 + 50) + int btnSz = 100; + double bds[6] = { (double)sz[0] - btnSz - margin_right, (double)sz[0] - margin_right, + (double)sz[1] - margin_top - btnSz/2.0, (double)sz[1] - margin_top + btnSz/2.0, 0, 0 }; + rep->PlaceWidget(bds); +} + +void Viewer::Start() { if (std::getenv("CTEST_PROJECT_NAME")) return; - dv->m_RenderWindow->GetInteractor()->Start(); + dv->m_RenderWindow->GetInteractor()->Start(); } vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; } diff --git a/src/Vtk/uLibVtkViewer.h b/src/Vtk/uLibVtkViewer.h index 69dd9bd..d70e635 100644 --- a/src/Vtk/uLibVtkViewer.h +++ b/src/Vtk/uLibVtkViewer.h @@ -38,6 +38,9 @@ private: void SetupGridButton(); void UpdateGridButtonPosition(); + void SetupProjButton(); + void UpdateProjButtonPosition(); + struct ViewerData *dv; }; diff --git a/src/Vtk/vtkQViewport.cpp b/src/Vtk/vtkQViewport.cpp index f9baf19..bc3f80f 100644 --- a/src/Vtk/vtkQViewport.cpp +++ b/src/Vtk/vtkQViewport.cpp @@ -19,6 +19,7 @@ QViewport::QViewport(QWidget* parent) , Viewport() , m_VtkWidget(nullptr) , m_GridButton(nullptr) + , m_ProjButton(nullptr) { // Build the layout – zero margins so VTK fills the entire widget auto* layout = new QVBoxLayout(this); @@ -58,6 +59,36 @@ QViewport::QViewport(QWidget* parent) m_GridButton->setChecked(true); // Grid is on by default connect(m_GridButton, &QPushButton::clicked, this, &QViewport::onGridButtonClicked); + // Projection Toggle Button (below grid button) + m_ProjButton = new QPushButton(m_VtkWidget); + m_ProjButton->setText("P"); + m_ProjButton->setFixedSize(40, 40); + m_ProjButton->setToolTip("Toggle Perspective / Orthographic"); + m_ProjButton->setStyleSheet( + "QPushButton {" + " border-radius: 20px;" + " 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);" + " color: white;" + " border: 1.5px solid rgba(255, 255, 255, 120);" + "}" + "QPushButton:pressed {" + " background-color: rgba(0, 90, 160, 220);" + "}" + ); + m_ProjButton->setCheckable(true); + m_ProjButton->setChecked(false); // Perspective by default + connect(m_ProjButton, &QPushButton::clicked, this, &QViewport::onProjButtonClicked); + // After the Qt widget exists but before the first paint, // attach the renderer and configure the pipeline. SetupPipeline(); @@ -100,6 +131,11 @@ void QViewport::onGridButtonClicked() SetGridVisible(m_GridButton->isChecked()); } +void QViewport::onProjButtonClicked() +{ + SetParallelProjection(m_ProjButton->isChecked()); +} + void QViewport::OnSelectionChanged(Puppet* p) { emit puppetSelected(p); @@ -112,9 +148,14 @@ void QViewport::resizeEvent(QResizeEvent* event) // Position under the gizmo (top-right corner). // Standard CameraOrientationWidget is usually 150-180px. int x = width() - m_GridButton->width() - 10; - int y = 160; + int y = 160; m_GridButton->move(x, y); } + if (m_ProjButton) { + int x = width() - m_ProjButton->width() - 10; + int y = 210; + m_ProjButton->move(x, y); + } } } // namespace Vtk diff --git a/src/Vtk/vtkQViewport.h b/src/Vtk/vtkQViewport.h index 168a926..9daae05 100644 --- a/src/Vtk/vtkQViewport.h +++ b/src/Vtk/vtkQViewport.h @@ -51,12 +51,14 @@ protected: private slots: void onGridButtonClicked(); + void onProjButtonClicked(); private: void SetupPipeline(); QVTKOpenGLNativeWidget* m_VtkWidget; QPushButton* m_GridButton; + QPushButton* m_ProjButton; }; } // namespace Vtk diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index d6b8521..1b638b0 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -537,6 +537,21 @@ bool Viewport::GetGridVisible() const return false; } +void Viewport::SetParallelProjection(bool parallel) +{ + if (pv->m_Renderer && pv->m_Renderer->GetActiveCamera()) { + pv->m_Renderer->GetActiveCamera()->SetParallelProjection(parallel ? 1 : 0); + Render(); + } +} + +bool Viewport::GetParallelProjection() const +{ + if (pv->m_Renderer && pv->m_Renderer->GetActiveCamera()) + return pv->m_Renderer->GetActiveCamera()->GetParallelProjection() != 0; + return false; +} + void Viewport::SetGridAxis(Axis axis) { m_GridAxis = axis; diff --git a/src/Vtk/vtkViewport.h b/src/Vtk/vtkViewport.h index a97d53f..c2b4db4 100644 --- a/src/Vtk/vtkViewport.h +++ b/src/Vtk/vtkViewport.h @@ -73,6 +73,10 @@ public: void SetGridAxis(Axis axis); Axis GetGridAxis() const { return m_GridAxis; } + // Projection control + void SetParallelProjection(bool parallel); + bool GetParallelProjection() const; + protected: void SetupPipeline(vtkRenderWindowInteractor* iren);