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) {
|
if (std::getenv("CTEST_PROJECT_NAME") == nullptr) {
|
||||||
// Run application for a while to see the result
|
// Run application for a while to see the result
|
||||||
// return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
END_TESTING;
|
END_TESTING;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ struct ViewerData {
|
|||||||
vtkRenderWindow *m_RenderWindow;
|
vtkRenderWindow *m_RenderWindow;
|
||||||
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
|
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
|
||||||
vtkSmartPointer<vtkButtonWidget> m_GridButton;
|
vtkSmartPointer<vtkButtonWidget> m_GridButton;
|
||||||
|
vtkSmartPointer<vtkButtonWidget> m_ProjButton;
|
||||||
|
|
||||||
ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {}
|
ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {}
|
||||||
~ViewerData() {
|
~ViewerData() {
|
||||||
@@ -97,6 +98,11 @@ Viewer::~Viewer() {
|
|||||||
dv->m_GridButton->SetInteractor(nullptr);
|
dv->m_GridButton->SetInteractor(nullptr);
|
||||||
dv->m_GridButton = 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()) {
|
if (this->GetRenderWindow()) {
|
||||||
this->GetRenderWindow()->RemoveAllObservers();
|
this->GetRenderWindow()->RemoveAllObservers();
|
||||||
}
|
}
|
||||||
@@ -123,6 +129,7 @@ void Viewer::InstallPipe() {
|
|||||||
// Setup native grid button
|
// Setup native grid button
|
||||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
SetupGridButton();
|
SetupGridButton();
|
||||||
|
SetupProjButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
// BUT we want to override the style with our custom NoSpin version
|
// BUT we want to override the style with our custom NoSpin version
|
||||||
@@ -238,9 +245,91 @@ void Viewer::UpdateGridButtonPosition() {
|
|||||||
rep->PlaceWidget(bds);
|
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;
|
if (std::getenv("CTEST_PROJECT_NAME")) return;
|
||||||
dv->m_RenderWindow->GetInteractor()->Start();
|
dv->m_RenderWindow->GetInteractor()->Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; }
|
vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; }
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ private:
|
|||||||
void SetupGridButton();
|
void SetupGridButton();
|
||||||
void UpdateGridButtonPosition();
|
void UpdateGridButtonPosition();
|
||||||
|
|
||||||
|
void SetupProjButton();
|
||||||
|
void UpdateProjButtonPosition();
|
||||||
|
|
||||||
struct ViewerData *dv;
|
struct ViewerData *dv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ QViewport::QViewport(QWidget* parent)
|
|||||||
, Viewport()
|
, Viewport()
|
||||||
, m_VtkWidget(nullptr)
|
, m_VtkWidget(nullptr)
|
||||||
, m_GridButton(nullptr)
|
, m_GridButton(nullptr)
|
||||||
|
, m_ProjButton(nullptr)
|
||||||
{
|
{
|
||||||
// Build the layout – zero margins so VTK fills the entire widget
|
// Build the layout – zero margins so VTK fills the entire widget
|
||||||
auto* layout = new QVBoxLayout(this);
|
auto* layout = new QVBoxLayout(this);
|
||||||
@@ -58,6 +59,36 @@ QViewport::QViewport(QWidget* parent)
|
|||||||
m_GridButton->setChecked(true); // Grid is on by default
|
m_GridButton->setChecked(true); // Grid is on by default
|
||||||
connect(m_GridButton, &QPushButton::clicked, this, &QViewport::onGridButtonClicked);
|
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,
|
// After the Qt widget exists but before the first paint,
|
||||||
// attach the renderer and configure the pipeline.
|
// attach the renderer and configure the pipeline.
|
||||||
SetupPipeline();
|
SetupPipeline();
|
||||||
@@ -100,6 +131,11 @@ void QViewport::onGridButtonClicked()
|
|||||||
SetGridVisible(m_GridButton->isChecked());
|
SetGridVisible(m_GridButton->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QViewport::onProjButtonClicked()
|
||||||
|
{
|
||||||
|
SetParallelProjection(m_ProjButton->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
void QViewport::OnSelectionChanged(Puppet* p)
|
void QViewport::OnSelectionChanged(Puppet* p)
|
||||||
{
|
{
|
||||||
emit puppetSelected(p);
|
emit puppetSelected(p);
|
||||||
@@ -112,9 +148,14 @@ void QViewport::resizeEvent(QResizeEvent* event)
|
|||||||
// Position under the gizmo (top-right corner).
|
// Position under the gizmo (top-right corner).
|
||||||
// Standard CameraOrientationWidget is usually 150-180px.
|
// Standard CameraOrientationWidget is usually 150-180px.
|
||||||
int x = width() - m_GridButton->width() - 10;
|
int x = width() - m_GridButton->width() - 10;
|
||||||
int y = 160;
|
int y = 160;
|
||||||
m_GridButton->move(x, y);
|
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
|
} // namespace Vtk
|
||||||
|
|||||||
@@ -51,12 +51,14 @@ protected:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onGridButtonClicked();
|
void onGridButtonClicked();
|
||||||
|
void onProjButtonClicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetupPipeline();
|
void SetupPipeline();
|
||||||
|
|
||||||
QVTKOpenGLNativeWidget* m_VtkWidget;
|
QVTKOpenGLNativeWidget* m_VtkWidget;
|
||||||
QPushButton* m_GridButton;
|
QPushButton* m_GridButton;
|
||||||
|
QPushButton* m_ProjButton;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
|
|||||||
@@ -537,6 +537,21 @@ bool Viewport::GetGridVisible() const
|
|||||||
return false;
|
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)
|
void Viewport::SetGridAxis(Axis axis)
|
||||||
{
|
{
|
||||||
m_GridAxis = axis;
|
m_GridAxis = axis;
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ public:
|
|||||||
void SetGridAxis(Axis axis);
|
void SetGridAxis(Axis axis);
|
||||||
Axis GetGridAxis() const { return m_GridAxis; }
|
Axis GetGridAxis() const { return m_GridAxis; }
|
||||||
|
|
||||||
|
// Projection control
|
||||||
|
void SetParallelProjection(bool parallel);
|
||||||
|
bool GetParallelProjection() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SetupPipeline(vtkRenderWindowInteractor* iren);
|
void SetupPipeline(vtkRenderWindowInteractor* iren);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user