feat/vtk-3d-view #7
|
|
@ -543,6 +543,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
auto* btnSliceFrontBack = new QPushButton(QStringLiteral("前后"));
|
||||
auto* btnSliceLeftRight = new QPushButton(QStringLiteral("左右"));
|
||||
auto* btnSliceOblique = new QPushButton(QStringLiteral("任意"));
|
||||
auto* btnSliceFace = new QPushButton(QStringLiteral("正视")); // 正视选中切片(E54)
|
||||
auto* btnSliceFlip = new QPushButton(QStringLiteral("翻转"));
|
||||
auto* btnSliceClose = new QPushButton(QStringLiteral("关闭"));
|
||||
sliceLayout->addWidget(sliceLabel);
|
||||
|
|
@ -550,6 +551,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
sliceLayout->addWidget(btnSliceFrontBack);
|
||||
sliceLayout->addWidget(btnSliceLeftRight);
|
||||
sliceLayout->addWidget(btnSliceOblique);
|
||||
sliceLayout->addWidget(btnSliceFace);
|
||||
sliceLayout->addWidget(btnSliceFlip);
|
||||
sliceLayout->addWidget(btnSliceClose);
|
||||
sliceBar->setVisible(false); // 默认二维,不显示
|
||||
|
|
@ -557,13 +559,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
|
||||
// 切片按钮可用性:仅三维 + 有体素时创建/翻转可用;关闭仅在有切片时可用。
|
||||
auto updateSliceButtons = [interactionMgr, btnSliceUpDown, btnSliceFrontBack, btnSliceLeftRight,
|
||||
btnSliceOblique, btnSliceFlip, btnSliceClose, sceneView]() {
|
||||
btnSliceOblique, btnSliceFace, btnSliceFlip, btnSliceClose, sceneView]() {
|
||||
const bool canSlice = sceneView->hasVolume() && interactionMgr->hasVolume();
|
||||
btnSliceUpDown->setEnabled(canSlice);
|
||||
btnSliceFrontBack->setEnabled(canSlice);
|
||||
btnSliceLeftRight->setEnabled(canSlice);
|
||||
btnSliceOblique->setEnabled(canSlice);
|
||||
btnSliceFlip->setEnabled(canSlice);
|
||||
btnSliceFace->setEnabled(interactionMgr->hasSlices()); // 正视需有(选中)切片
|
||||
btnSliceClose->setEnabled(interactionMgr->hasSlices());
|
||||
};
|
||||
updateSliceButtons();
|
||||
|
|
@ -581,6 +584,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
[addSlice]() { addSlice(SliceAxis::LeftRight); });
|
||||
QObject::connect(btnSliceOblique, &QPushButton::clicked, vtkWidget,
|
||||
[addSlice]() { addSlice(SliceAxis::Oblique); });
|
||||
QObject::connect(btnSliceFace, &QPushButton::clicked, vtkWidget,
|
||||
[interactionMgr]() { interactionMgr->faceSelected(); });
|
||||
QObject::connect(btnSliceFlip, &QPushButton::clicked, vtkWidget,
|
||||
[interactionMgr]() { interactionMgr->flipView(); });
|
||||
QObject::connect(btnSliceClose, &QPushButton::clicked, vtkWidget,
|
||||
|
|
|
|||
|
|
@ -77,12 +77,26 @@ void InteractionManager::setVolumeImage(vtkImageData* image, const geopro::core:
|
|||
void InteractionManager::addSlice(SliceAxis axis) {
|
||||
if (!image_ || !interactor_) return;
|
||||
auto tool = std::make_unique<SliceTool>(image_, interactor_, axis, colorScale_, vmin_, vmax_);
|
||||
// 触碰本切片(拖动/点击切面) → 设为选中(widget 开启交互后独占切面事件,选中靠此回调)。
|
||||
SliceTool* tp = tool.get();
|
||||
tool->onInteract = [this, tp]() { selectByTool(tp); };
|
||||
slices_.push_back(std::move(tool));
|
||||
selected_ = static_cast<int>(slices_.size()) - 1; // 新切片选中
|
||||
updateSelectionVisual();
|
||||
safeRender();
|
||||
}
|
||||
|
||||
void InteractionManager::selectByTool(const SliceTool* tool) {
|
||||
for (std::size_t i = 0; i < slices_.size(); ++i) {
|
||||
if (slices_[i].get() == tool) {
|
||||
selected_ = static_cast<int>(i);
|
||||
updateSelectionVisual();
|
||||
safeRender();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InteractionManager::closeSelected() {
|
||||
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return;
|
||||
slices_[static_cast<std::size_t>(selected_)]->close();
|
||||
|
|
@ -143,12 +157,18 @@ void InteractionManager::onPicked(const Vec3& worldPoint) {
|
|||
}
|
||||
|
||||
void InteractionManager::onDoubleClicked(const Vec3& worldPoint) {
|
||||
// 双击命中切片 → 正视(widget 开启交互后双击多被其吞,正视主入口改工具条按钮 faceSelected)。
|
||||
const int idx = nearestSlice(worldPoint);
|
||||
if (idx < 0 || !renderer_) return;
|
||||
auto* cam = renderer_->GetActiveCamera();
|
||||
if (!cam) return;
|
||||
if (idx < 0) return;
|
||||
selected_ = idx;
|
||||
updateSelectionVisual();
|
||||
faceSlice(idx);
|
||||
}
|
||||
|
||||
void InteractionManager::faceSlice(int idx) {
|
||||
if (idx < 0 || idx >= static_cast<int>(slices_.size()) || !renderer_) return;
|
||||
auto* cam = renderer_->GetActiveCamera();
|
||||
if (!cam) return;
|
||||
const Vec3 focal = slices_[static_cast<std::size_t>(idx)]->center();
|
||||
const Vec3 normal = slices_[static_cast<std::size_t>(idx)]->normal();
|
||||
const double dist = cam->GetDistance(); // 保持当前观察距离
|
||||
|
|
@ -161,6 +181,8 @@ void InteractionManager::onDoubleClicked(const Vec3& worldPoint) {
|
|||
safeRender();
|
||||
}
|
||||
|
||||
void InteractionManager::faceSelected() { faceSlice(selected_); }
|
||||
|
||||
bool InteractionManager::onWheel(int dir) {
|
||||
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return false;
|
||||
const double step = wheelStep(imageBounds(image_), dir);
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ public:
|
|||
// 视图翻转:水平旋转 180°(E55)。
|
||||
void flipView();
|
||||
|
||||
// 正视选中切片(E54/D40):相机转到正对选中切面法向。无选中则忽略。
|
||||
// (改用工具条按钮触发,因 widget 开启交互后会抢双击事件。)
|
||||
void faceSelected();
|
||||
|
||||
// 安装/卸载自定义交互样式(构造时安装;析构卸载恢复原样式)。
|
||||
void installStyle();
|
||||
void uninstallStyle();
|
||||
|
|
@ -67,6 +71,10 @@ private:
|
|||
|
||||
// 找离世界点最近的切片索引;无切片返回 -1。
|
||||
int nearestSlice(const Vec3& worldPoint) const;
|
||||
// 按 SliceTool 指针设为选中(widget 交互回调用:触碰即选中)。
|
||||
void selectByTool(const SliceTool* tool);
|
||||
// 相机正视给定切面(focal=center, 沿 normal 退 dist)。
|
||||
void faceSlice(int idx);
|
||||
|
||||
// 统一重绘:析构进行中(destroying_)跳过,避免 Qt 拆台时对半析构窗口 Render 崩溃(评审 H3)。
|
||||
void safeRender();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <vtkCallbackCommand.h>
|
||||
#include <vtkCommand.h>
|
||||
#include <vtkImageData.h>
|
||||
#include <vtkImagePlaneWidget.h>
|
||||
#include <vtkLookupTable.h>
|
||||
|
|
@ -77,10 +79,15 @@ SliceTool::SliceTool(vtkImageData* image, vtkRenderWindowInteractor* interactor,
|
|||
}
|
||||
|
||||
widget_->On();
|
||||
// 关闭 widget 自身的鼠标交互(窗位/光标/拖动):否则它会"吃掉"落在切片面上的左键,
|
||||
// 自定义 PickInteractorStyle 收不到 → 单击选中/双击正视/绕点旋转全失效(实测根因)。
|
||||
// 关掉后切片仍正常显示,点击穿透到样式;切面移动改由滚轮(advance)驱动。
|
||||
widget_->InteractionOff();
|
||||
// 保持 widget 交互开启:任意切片可拖动调整角度/位置(F25 '可任意调整')。
|
||||
// 监听其交互开始事件 → 触碰本切片即回调 onInteract(上层据此设为选中)。
|
||||
interactObserver_ = vtkSmartPointer<vtkCallbackCommand>::New();
|
||||
interactObserver_->SetClientData(this);
|
||||
interactObserver_->SetCallback([](vtkObject*, unsigned long, void* client, void*) {
|
||||
auto* self = static_cast<SliceTool*>(client);
|
||||
if (self && self->onInteract) self->onInteract();
|
||||
});
|
||||
widget_->AddObserver(vtkCommand::StartInteractionEvent, interactObserver_);
|
||||
}
|
||||
|
||||
SliceTool::~SliceTool() { close(); }
|
||||
|
|
@ -146,6 +153,11 @@ void SliceTool::setSelected(bool sel) {
|
|||
|
||||
void SliceTool::close() {
|
||||
if (!widget_) return;
|
||||
onInteract = nullptr; // 先断业务回调,避免 Off 期间触发到上层
|
||||
if (interactObserver_) {
|
||||
widget_->RemoveObserver(interactObserver_);
|
||||
interactObserver_ = nullptr;
|
||||
}
|
||||
widget_->Off();
|
||||
widget_->SetInteractor(nullptr); // 解除观察者,防悬挂崩溃
|
||||
widget_ = nullptr; // 置空 → 二次 close()/析构真正幂等(不再 Off 已解绑 widget)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include <vtkSmartPointer.h>
|
||||
|
||||
|
|
@ -8,6 +9,7 @@
|
|||
|
||||
class vtkImageData;
|
||||
class vtkImagePlaneWidget;
|
||||
class vtkCallbackCommand;
|
||||
class vtkRenderWindowInteractor;
|
||||
class vtkTrivialProducer;
|
||||
|
||||
|
|
@ -48,6 +50,10 @@ public:
|
|||
// 选中视觉反馈:选中→高亮边框(亮黄+粗线),未选中→暗淡细线。
|
||||
void setSelected(bool sel);
|
||||
|
||||
// 用户开始操作本切片(拖动/点击切面)时回调 → 上层据此把本切片设为选中。
|
||||
// 因 widget 开启交互后独占切面鼠标事件,选中靠监听 widget 交互而非拾取。
|
||||
std::function<void()> onInteract;
|
||||
|
||||
// 世界点到本切面(无限平面)的垂直距离绝对值。供 picker 命中判定"点在哪张切片上"。
|
||||
double distanceToPlane(const Vec3& worldPoint) const;
|
||||
|
||||
|
|
@ -60,6 +66,7 @@ private:
|
|||
// 把已存在的 image 接入 widget 的 producer:须随 SliceTool 保活(否则构造后析构→管线断裂崩溃,评审 H1)。
|
||||
vtkSmartPointer<vtkTrivialProducer> producer_;
|
||||
vtkSmartPointer<vtkImagePlaneWidget> widget_;
|
||||
vtkSmartPointer<vtkCallbackCommand> interactObserver_; // 监听 widget StartInteractionEvent → onInteract
|
||||
|
||||
std::array<double, 6> imageBounds() const;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue