feat: add projection toggle button to switch between perspective and orthographic views
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ struct ViewerData {
|
||||
vtkRenderWindow *m_RenderWindow;
|
||||
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
|
||||
vtkSmartPointer<vtkButtonWidget> m_GridButton;
|
||||
vtkSmartPointer<vtkButtonWidget> 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<vtkImageCanvasSource2D> 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<vtkImageData> 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<vtkImageData> imgOrtho;
|
||||
imgOrtho->DeepCopy(canvas->GetOutput());
|
||||
|
||||
vtkNew<vtkTexturedButtonRepresentation2D> rep;
|
||||
rep->SetNumberOfStates(2);
|
||||
rep->SetButtonTexture(0, imgPersp);
|
||||
rep->SetButtonTexture(1, imgOrtho);
|
||||
|
||||
dv->m_ProjButton = vtkSmartPointer<vtkButtonWidget>::New();
|
||||
dv->m_ProjButton->SetInteractor(dv->m_RenderWindow->GetInteractor());
|
||||
dv->m_ProjButton->SetRepresentation(rep);
|
||||
|
||||
UpdateProjButtonPosition();
|
||||
|
||||
vtkNew<vtkCallbackCommand> resizeCallback;
|
||||
resizeCallback->SetClientData(this);
|
||||
resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
||||
static_cast<Viewer*>(clientdata)->UpdateProjButtonPosition();
|
||||
});
|
||||
dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback);
|
||||
|
||||
vtkNew<vtkCallbackCommand> stateCallback;
|
||||
stateCallback->SetClientData(this);
|
||||
stateCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
||||
auto* btn = vtkButtonWidget::SafeDownCast(caller);
|
||||
auto* v = static_cast<Viewer*>(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; }
|
||||
|
||||
@@ -38,6 +38,9 @@ private:
|
||||
void SetupGridButton();
|
||||
void UpdateGridButtonPosition();
|
||||
|
||||
void SetupProjButton();
|
||||
void UpdateProjButtonPosition();
|
||||
|
||||
struct ViewerData *dv;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user