refactor using pimpl and fix test

This commit is contained in:
AndreaRigoni
2026-03-25 16:18:07 +00:00
parent a467b7385b
commit 7d4acaef6d
17 changed files with 479 additions and 412 deletions

View File

@@ -8,13 +8,20 @@
#include <algorithm>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkObjectFactory.h>
#include <vtkAxesActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkNew.h>
#include <vtkCornerAnnotation.h>
#include <vtkOrientationMarkerWidget.h>
#include <vtkCameraOrientationWidget.h>
#include <vtkPlaneSource.h>
#include <vtkActor.h>
#include <vtkAxes.h>
#include <vtkNamedColors.h>
#include <vtkCellPicker.h>
#include <vtkTextProperty.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkCallbackCommand.h>
#include <vtkMath.h>
@@ -25,23 +32,50 @@
namespace uLib {
namespace Vtk {
struct ViewportData {
vtkSmartPointer<vtkRenderer> m_Renderer;
vtkSmartPointer<vtkCornerAnnotation> m_Annotation;
vtkSmartPointer<vtkOrientationMarkerWidget> m_Marker;
vtkSmartPointer<vtkCameraOrientationWidget> m_CameraWidget;
vtkSmartPointer<vtkPlaneSource> m_GridSource;
vtkSmartPointer<vtkActor> m_GridActor;
vtkSmartPointer<vtkAxes> m_OriginAxes;
vtkSmartPointer<vtkActor> m_OriginAxesActor;
vtkSmartPointer<vtkNamedColors> m_Colors;
vtkSmartPointer<vtkHandlerWidget> m_HandlerWidget;
vtkSmartPointer<vtkCellPicker> m_Picker;
vtkSmartPointer<vtkCallbackCommand> m_KeyCallback;
ViewportData()
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
, m_CameraWidget(nullptr)
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
{}
};
Viewport::Viewport()
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
, m_CameraWidget(nullptr)
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
: d(new ViewportData())
, m_GridAxis(Y)
{
}
Viewport::~Viewport()
{
if (m_Renderer) {
m_Renderer->RemoveAllViewProps();
if (d->m_Renderer) {
d->m_Renderer->RemoveAllViewProps();
}
delete d;
}
vtkRenderer* Viewport::GetRenderer() { return d->m_Renderer; }
vtkCornerAnnotation* Viewport::GetAnnotation() { return d->m_Annotation; }
vtkCameraOrientationWidget* Viewport::GetCameraWidget() { return d->m_CameraWidget; }
void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
{
if (!iren) return;
@@ -51,49 +85,49 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
iren->SetInteractorStyle(style);
// Corner annotation
m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
m_Annotation->GetTextProperty()->SetFontFamilyToArial();
m_Annotation->GetTextProperty()->SetOpacity(0.5);
m_Annotation->SetMaximumFontSize(10);
m_Annotation->SetText(0, "uLib VTK viewer.");
m_Renderer->AddViewProp(m_Annotation);
d->m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
d->m_Annotation->GetTextProperty()->SetFontFamilyToArial();
d->m_Annotation->GetTextProperty()->SetOpacity(0.5);
d->m_Annotation->SetMaximumFontSize(10);
d->m_Annotation->SetText(0, "uLib VTK viewer.");
d->m_Renderer->AddViewProp(d->m_Annotation);
// right corner annotation
m_Annotation->SetText(1, "Grid: -");
d->m_Annotation->SetText(1, "Grid: -");
// Orientation axes marker (bottom-left corner)
vtkNew<vtkAxesActor> axes;
m_Marker->SetInteractor(iren);
m_Marker->SetOrientationMarker(axes);
m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
m_Marker->SetEnabled(true);
m_Marker->InteractiveOff();
d->m_Marker->SetInteractor(iren);
d->m_Marker->SetOrientationMarker(axes);
d->m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
d->m_Marker->SetEnabled(true);
d->m_Marker->InteractiveOff();
// Grid Plane centered at (0,0,0)
m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
m_GridActor = vtkSmartPointer<vtkActor>::New();
d->m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
d->m_GridActor = vtkSmartPointer<vtkActor>::New();
vtkNew<vtkPolyDataMapper> gridMapper;
gridMapper->SetInputConnection(m_GridSource->GetOutputPort());
gridMapper->SetInputConnection(d->m_GridSource->GetOutputPort());
m_GridActor->SetMapper(gridMapper);
m_GridActor->GetProperty()->SetRepresentationToWireframe();
m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
m_GridActor->GetProperty()->SetLighting(0);
m_GridActor->GetProperty()->SetOpacity(0.5);
m_GridActor->PickableOff();
m_Renderer->AddActor(m_GridActor);
d->m_GridActor->SetMapper(gridMapper);
d->m_GridActor->GetProperty()->SetRepresentationToWireframe();
d->m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
d->m_GridActor->GetProperty()->SetLighting(0);
d->m_GridActor->GetProperty()->SetOpacity(0.5);
d->m_GridActor->PickableOff();
d->m_Renderer->AddActor(d->m_GridActor);
// Global Origin Axes
m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
m_OriginAxes->SetScaleFactor(1.0); // will be updated
d->m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
d->m_OriginAxes->SetScaleFactor(1.0); // will be updated
vtkNew<vtkPolyDataMapper> axesMapper;
axesMapper->SetInputConnection(m_OriginAxes->GetOutputPort());
axesMapper->SetInputConnection(d->m_OriginAxes->GetOutputPort());
m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
m_OriginAxesActor->SetMapper(axesMapper);
m_OriginAxesActor->PickableOff();
m_Renderer->AddActor(m_OriginAxesActor);
d->m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
d->m_OriginAxesActor->SetMapper(axesMapper);
d->m_OriginAxesActor->PickableOff();
d->m_Renderer->AddActor(d->m_OriginAxesActor);
UpdateGrid();
@@ -104,31 +138,31 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
static_cast<Viewport*>(clientdata)->UpdateGrid();
});
iren->AddObserver(vtkCommand::InteractionEvent, interactionCallback);
m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
d->m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
// Camera-orientation widget (VTK >= 9)
#if VTK_MAJOR_VERSION >= 9
m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
m_CameraWidget->SetParentRenderer(m_Renderer);
m_CameraWidget->SetInteractor(iren);
m_CameraWidget->On();
d->m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
d->m_CameraWidget->SetParentRenderer(d->m_Renderer);
d->m_CameraWidget->SetInteractor(iren);
d->m_CameraWidget->On();
#endif
m_Renderer->SetBackground(0.15, 0.15, 0.15);
m_Renderer->ResetCamera();
d->m_Renderer->SetBackground(0.15, 0.15, 0.15);
d->m_Renderer->ResetCamera();
// Setup layering for overimposed rendering
if (iren->GetRenderWindow()) {
iren->GetRenderWindow()->SetNumberOfLayers(2);
m_Renderer->SetLayer(0);
d->m_Renderer->SetLayer(0);
}
// Setup Handler Widget
m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
m_HandlerWidget->SetInteractor(iren);
m_HandlerWidget->SetCurrentRenderer(m_Renderer);
if (m_HandlerWidget->GetOverlayRenderer()) {
m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
d->m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
d->m_HandlerWidget->SetInteractor(iren);
d->m_HandlerWidget->SetCurrentRenderer(d->m_Renderer);
if (d->m_HandlerWidget->GetOverlayRenderer()) {
d->m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
}
// Observe InteractionEvent to update the selected puppet when the widget moves it
@@ -142,10 +176,10 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
}
}
});
m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
d->m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
// Picking for selection
m_Picker = vtkSmartPointer<vtkCellPicker>::New();
d->m_Picker = vtkSmartPointer<vtkCellPicker>::New();
vtkNew<vtkCallbackCommand> clickCallback;
clickCallback->SetClientData(this);
clickCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
@@ -153,8 +187,8 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
auto* self = static_cast<Viewport*>(clientdata);
int* pos = iren->GetEventPosition();
self->m_Picker->Pick(pos[0], pos[1], 0, self->m_Renderer);
vtkProp* picked = self->m_Picker->GetViewProp();
self->d->m_Picker->Pick(pos[0], pos[1], 0, self->d->m_Renderer);
vtkProp* picked = self->d->m_Picker->GetViewProp();
Puppet* target = nullptr;
if (picked) {
@@ -190,59 +224,59 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
iren->AddObserver(vtkCommand::LeftButtonPressEvent, clickCallback);
// Keyboard events for widget coordinate frame
m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
m_KeyCallback->SetClientData(this);
m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
d->m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
d->m_KeyCallback->SetClientData(this);
d->m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
auto* self = static_cast<Viewport*>(clientdata);
std::string key = iren->GetKeySym();
bool handled = false;
if (self->m_HandlerWidget && self->m_HandlerWidget->GetEnabled()) {
if (self->d->m_HandlerWidget && self->d->m_HandlerWidget->GetEnabled()) {
if (key == "l") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
self->d->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
std::cout << "Widget Frame: LOCAL" << std::endl;
}
handled = true;
}
else if (key == "g") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
self->d->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
std::cout << "Widget Frame: GLOBAL" << std::endl;
}
handled = true;
}
else if (key == "c") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
self->d->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
std::cout << "Widget Frame: CENTER" << std::endl;
}
handled = true;
}
else if (key == "k") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
self->d->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
std::cout << "Widget Frame: CENTER_LOCAL" << std::endl;
}
handled = true;
}
else if (key == "1") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetTranslationEnabled(!self->m_HandlerWidget->GetTranslationEnabled());
self->d->m_HandlerWidget->SetTranslationEnabled(!self->d->m_HandlerWidget->GetTranslationEnabled());
}
handled = true;
}
else if (key == "2") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetRotationEnabled(!self->m_HandlerWidget->GetRotationEnabled());
self->d->m_HandlerWidget->SetRotationEnabled(!self->d->m_HandlerWidget->GetRotationEnabled());
}
handled = true;
}
else if (key == "3") {
if (event == vtkCommand::KeyPressEvent) {
self->m_HandlerWidget->SetScalingEnabled(!self->m_HandlerWidget->GetScalingEnabled());
self->d->m_HandlerWidget->SetScalingEnabled(!self->d->m_HandlerWidget->GetScalingEnabled());
}
handled = true;
}
@@ -256,12 +290,12 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
}
if (handled) {
self->m_KeyCallback->SetAbortFlag(1);
self->d->m_KeyCallback->SetAbortFlag(1);
iren->Render();
}
});
iren->AddObserver(vtkCommand::KeyPressEvent, m_KeyCallback, 1.0);
iren->AddObserver(vtkCommand::CharEvent, m_KeyCallback, 1.0);
iren->AddObserver(vtkCommand::KeyPressEvent, d->m_KeyCallback, 1.0);
iren->AddObserver(vtkCommand::CharEvent, d->m_KeyCallback, 1.0);
}
void Viewport::Reset()
@@ -272,15 +306,15 @@ void Viewport::Reset()
void Viewport::ZoomAuto()
{
if (m_Renderer) {
m_Renderer->ResetCameraClippingRange();
m_Renderer->ResetCamera();
if (d->m_Renderer) {
d->m_Renderer->ResetCameraClippingRange();
d->m_Renderer->ResetCamera();
}
}
void Viewport::ZoomSelected()
{
if (!m_Renderer) return;
if (!d->m_Renderer) return;
Puppet* selected = nullptr;
for (auto* p : m_Puppets) {
@@ -316,15 +350,15 @@ void Viewport::ZoomSelected()
newBounds[2*i+1] = center[i] + 2.5 * current_h;
}
m_Renderer->ResetCamera(newBounds);
m_Renderer->ResetCameraClippingRange();
d->m_Renderer->ResetCamera(newBounds);
d->m_Renderer->ResetCameraClippingRange();
this->Render();
}
void Viewport::AddPuppet(Puppet& prop)
{
m_Puppets.push_back(&prop);
prop.ConnectRenderer(m_Renderer);
prop.ConnectRenderer(d->m_Renderer);
Render();
}
@@ -333,7 +367,7 @@ void Viewport::RemovePuppet(Puppet& prop)
if (prop.IsSelected()) SelectPuppet(nullptr);
auto it = std::find(m_Puppets.begin(), m_Puppets.end(), &prop);
if (it != m_Puppets.end()) m_Puppets.erase(it);
prop.DisconnectRenderer(m_Renderer);
prop.DisconnectRenderer(d->m_Renderer);
Render();
}
@@ -343,17 +377,17 @@ void Viewport::SelectPuppet(Puppet* prop)
p->SetSelected(p == prop);
}
if (m_HandlerWidget) {
if (d->m_HandlerWidget) {
if (prop) {
vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp());
if (prop3d) {
m_HandlerWidget->SetProp3D(prop3d);
m_HandlerWidget->SetEnabled(1);
m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
d->m_HandlerWidget->SetProp3D(prop3d);
d->m_HandlerWidget->SetEnabled(1);
d->m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
}
} else {
m_HandlerWidget->SetEnabled(0);
m_HandlerWidget->SetProp3D(nullptr);
d->m_HandlerWidget->SetEnabled(0);
d->m_HandlerWidget->SetProp3D(nullptr);
}
}
@@ -362,16 +396,16 @@ void Viewport::SelectPuppet(Puppet* prop)
void Viewport::SetGridVisible(bool visible)
{
if (m_GridActor) {
m_GridActor->SetVisibility(visible);
if (d->m_GridActor) {
d->m_GridActor->SetVisibility(visible);
Render();
}
}
bool Viewport::GetGridVisible() const
{
if (m_GridActor) {
return m_GridActor->GetVisibility() != 0;
if (d->m_GridActor) {
return d->m_GridActor->GetVisibility() != 0;
}
return false;
}
@@ -385,26 +419,26 @@ void Viewport::SetGridAxis(Axis axis)
void Viewport::addProp(vtkProp* prop)
{
if (m_Renderer) {
m_Renderer->AddActor(prop);
if (d->m_Renderer) {
d->m_Renderer->AddActor(prop);
Render();
}
}
void Viewport::RemoveProp(vtkProp* prop)
{
if (m_Renderer) {
m_Renderer->RemoveViewProp(prop);
if (d->m_Renderer) {
d->m_Renderer->RemoveViewProp(prop);
Render();
}
}
void Viewport::UpdateGrid()
{
if (!m_Renderer || !m_GridSource) return;
if (m_GridActor && !m_GridActor->GetVisibility()) return;
if (!d->m_Renderer || !d->m_GridSource) return;
if (d->m_GridActor && !d->m_GridActor->GetVisibility()) return;
vtkCamera* camera = m_Renderer->GetActiveCamera();
vtkCamera* camera = d->m_Renderer->GetActiveCamera();
if (!camera) return;
// Determine the "scale" of the view (how many units are visible vertically)
@@ -425,6 +459,9 @@ void Viewport::UpdateGrid()
double log10Spacing = std::floor(std::log10(viewHeight / 5.0));
double spacing = std::pow(10.0, log10Spacing);
// Spacing should be at least 1mm
if (spacing < 1.0) spacing = 1.0;
// Get current focal point to center the grid near what we're looking at
double focalPoint[3];
camera->GetFocalPoint(focalPoint);
@@ -455,31 +492,28 @@ void Viewport::UpdateGrid()
p1[idxH] = maxH; p1[idxV] = minV; p1[idxN] = centerN;
p2[idxH] = minH; p2[idxV] = maxV; p2[idxN] = centerN;
m_GridSource->SetOrigin(origin);
m_GridSource->SetPoint1(p1);
m_GridSource->SetPoint2(p2);
m_GridSource->SetXResolution(numLines);
m_GridSource->SetYResolution(numLines);
m_GridSource->Update();
d->m_GridSource->SetOrigin(origin);
d->m_GridSource->SetPoint1(p1);
d->m_GridSource->SetPoint2(p2);
d->m_GridSource->SetXResolution(numLines);
d->m_GridSource->SetYResolution(numLines);
d->m_GridSource->Update();
if (m_OriginAxes) {
m_OriginAxes->SetScaleFactor(spacing);
if (d->m_OriginAxes) {
d->m_OriginAxes->SetScaleFactor(spacing);
}
// Update annotation for grid size
char gridLabel[32];
if (spacing >= 1000.0) {
sprintf(gridLabel, "Grid: %.0f m", spacing / 1000.0);
sprintf(gridLabel, "Grid: %.1f m", spacing / 1000.0);
} else if (spacing >= 10.0) {
sprintf(gridLabel, "Grid: %.0f cm", spacing / 10.0);
sprintf(gridLabel, "Grid: %.1f cm", spacing / 10.0);
} else {
sprintf(gridLabel, "Grid: %.0f mm", spacing);
}
m_Annotation->SetText(1, gridLabel);
d->m_Annotation->SetText(1, gridLabel);
}
} // namespace Vtk
} // namespace uLib