知识不是单独的,一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏:Visual Studio。
基于读取出来的 STL 模型,实现当用户点击鼠标左键时,程序将获取点击位置的点,显示其坐标,并设置它为模型的旋转原点。
详细流程为:点击 Select
按钮,鼠标具备选择的功能。当按下 Select Done
鼠标删除掉此功能。
主要是通过两个类和一个函数来实现的:
class PointPickedSignal : public QObject
class MouseInteractorCommand : public vtkCommand
void A::on_pushButtonSelected_clicked()
void A::onPointPicked(double* pos)
下边依次分析。
class PointPickedSignal : public QObject
class PointPickedSignal : public QObject
{
Q_OBJECT
public:
PointPickedSignal(QObject* parent = nullptr) : QObject(parent) {}
signals:
void pointPicked(double* pos);
};
这个类继承自 QObject
类,用于实现一个名为 pointPicked
的 Qt 信号,当一个点被选中时发出该信号。这个信号将被用于通知其他对象选中的点的坐标。
class MouseInteractorCommand : public vtkCommand
class MouseInteractorCommand : public vtkCommand
{
public:
vtkTypeMacro(MouseInteractorCommand, vtkCommand);
static MouseInteractorCommand* New()
{
return new MouseInteractorCommand;
}
virtual void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData))
{
vtkRenderWindowInteractor* interactor = vtkRenderWindowInteractor::SafeDownCast(caller);
int* clickPos = interactor->GetEventPosition();
vtkSmartPointer<vtkCellPicker> picker = vtkSmartPointer<vtkCellPicker>::New();
picker->SetTolerance(0.0005);
if (picker->Pick(clickPos[0], clickPos[1], 0, interactor->GetRenderWindow()->GetRenderers()->GetFirstRenderer()))
{
double* pos = picker->GetPickPosition();
memcpy(pickedPoint, pos, sizeof(double) * 3);
emit signal->pointPicked(pickedPoint);
}
}
double pickedPoint[3];
PointPickedSignal* signal; // this will emit the pointPicked signal when a point is picked
};
这个类继承自 vtkCommand
类,其功能是监听鼠标左键的点击事件。当用户点击鼠标左键时,会触发 Execute
方法。在这个方法中,代码首先从事件的发起者中获取交互器,并从交互器中获取点击的位置。然后,它创建一个 vtkCellPicker
对象并尝试拾取点击位置的点。如果成功拾取了一个点,它将获取该点的坐标,并使用 memcpy
将这些坐标复制到 pickedPoint
数组中。最后,它发出 pointPicked
信号,将选中的点的坐标作为参数。
void A::on_pushButtonSelected_clicked()
void A::on_pushButtonSelected_clicked() {
ui.textBrowser->insertPlainText("Button Clicked");
PointPickedSignal* signal = new PointPickedSignal(this);
vtkSmartPointer<MouseInteractorCommand> command = vtkSmartPointer<MouseInteractorCommand>::New();
command->signal = signal;
ui.qvtkWidget->interactor()->AddObserver(vtkCommand::LeftButtonPressEvent, command);
QEventLoop loop;
connect(signal, &PointPickedSignal::pointPicked, this, &A::onPointPicked);
connect(signal, &PointPickedSignal::pointPicked, &loop, &QEventLoop::quit);
loop.exec();
onPointPicked(command->pickedPoint);
}
这个方法首先创建一个 PointPickedSignal
对象和一个 MouseInteractorCommand
对象。然后,它将 PointPickedSignal
对象赋值给 MouseInteractorCommand
对象的 signal 成员,然后将这个 MouseInteractorCommand
对象添加为 QVTKWidget
对象的交互器的观察者,这样当交互器收到左键按下事件时,就会执行 MouseInteractorCommand
对象的 Execute
方法。
然后,这个方法创建一个 QEventLoop
对象并开始执行事件循环。在事件循环中,当 pointPicked
信号被发出时,它将调用 A::onPointPicked()
方法,并结束事件循环。
void A::onPointPicked(double* pos)
void A::onPointPicked(double* pos) {
ui.textBrowser->insertPlainText(QString("Point picked: %1 %2 %3\n").arg(pos[0]).arg(pos[1]).arg(pos[2]));
ui.textBrowser->moveCursor(QTextCursor::End);
mandibleActor->SetOrigin(pos);
}
当这个方法被调用时,它将在文本浏览器中显示选中的点的坐标,并将这个点设置为模型的旋转原点。
通过这种方式,当用户点击鼠标左键时,程序将获取点击位置的点,显示其坐标,并设置它为模型的旋转原点。
完整版代码如下:
A.h
// A.h
#pragma once
#include
#include "ui_A.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class A : public QMainWindow
{
Q_OBJECT
public:
A(QWidget* parent = nullptr);
~A();
private slots:
void on_pushButtonSelect_clicked();
void on_pushButtonSelDone_clicked();
void onPointPicked(double* pos);
void rotate();
private:
Ui::AClass ui;
void initVTK();
vtkSmartPointer<vtkActor> actor;
vtkSmartPointer<vtkRenderer> renderer;
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow;
QTimer* timer;
};
class PointPickedSignal : public QObject
{
Q_OBJECT
public:
PointPickedSignal(QObject* parent = nullptr) : QObject(parent) {}
signals:
void pointPicked(double* pos);
};
class MouseInteractorCommand : public vtkCommand
{
public:
vtkTypeMacro(MouseInteractorCommand, vtkCommand);
static MouseInteractorCommand* New()
{
return new MouseInteractorCommand;
}
virtual void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData))
{
vtkRenderWindowInteractor* interactor = vtkRenderWindowInteractor::SafeDownCast(caller);
int* clickPos = interactor->GetEventPosition();
vtkSmartPointer<vtkCellPicker> picker = vtkSmartPointer<vtkCellPicker>::New();
picker->SetTolerance(0.0005);
if (picker->Pick(clickPos[0], clickPos[1], 0, interactor->GetRenderWindow()->GetRenderers()->GetFirstRenderer()))
{
double* pos = picker->GetPickPosition();
memcpy(pickedPoint, pos, sizeof(double) * 3);
emit signal->pointPicked(pickedPoint);
}
}
double pickedPoint[3];
PointPickedSignal* signal; // this will emit the pointPicked signal when a point is picked
};
A.cpp
// A.cpp
#include "A.h"
A::A(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
// 配置 VTK 的初始设置
initVTK();
// 定时器,50ms 更新触发一次 checkPositionChange()
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(rotate()));
timer->start(100);
}
A::~A()
{
}
void A::initVTK()
{
// 读取 STL 文件
vtkSmartPointer<vtkSTLReader> reader = vtkSmartPointer<vtkSTLReader>::New();
reader->SetFileName("skull_50.stl"); // 请替换为你的 STL 文件路径
reader->Update();
// 创建映射器和演员
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(reader->GetOutputPort());
actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
// 创建渲染器
renderer = vtkSmartPointer<vtkRenderer>::New();
// 添加演员到渲染器
renderer->AddActor(actor);
// 创建渲染窗口和渲染窗口交互器
renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(renderer);
// 添加到 qvtkWidget 控件中显示
ui.qvtkWidget->setRenderWindow(renderWindow);
}
void A::on_pushButtonSelect_clicked() {
ui.textBrowser->insertPlainText("Select button clicked!\n");
PointPickedSignal* signal = new PointPickedSignal(this);
vtkSmartPointer<MouseInteractorCommand> command = vtkSmartPointer<MouseInteractorCommand>::New();
command->signal = signal;
ui.qvtkWidget->interactor()->AddObserver(vtkCommand::LeftButtonPressEvent, command);
QEventLoop loop;
connect(signal, &PointPickedSignal::pointPicked, this, &A::onPointPicked);
connect(signal, &PointPickedSignal::pointPicked, &loop, &QEventLoop::quit);
loop.exec();
onPointPicked(command->pickedPoint);
}
void A::onPointPicked(double* pos) {
ui.textBrowser->insertPlainText(QString("Point picked: %1 %2 %3\n").arg(pos[0]).arg(pos[1]).arg(pos[2]));
ui.textBrowser->moveCursor(QTextCursor::End);
actor->SetOrigin(pos);
}
void A::on_pushButtonSelDone_clicked() {
ui.textBrowser->insertPlainText("Selection done, restore the default interactor style.\n");
// 移除左键按下事件的观察者
ui.qvtkWidget->interactor()->RemoveObservers(vtkCommand::LeftButtonPressEvent);
// 恢复默认的交互器样式。
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
ui.qvtkWidget->interactor()->SetInteractorStyle(style);
}
void A::rotate()
{
actor->RotateX(5);
renderWindow->Render();
}