feat(vtk): 多三维体并发切片渲染重构(OPT-002:issue2+③+反向②)
核心:InteractionManager 单 image_ → 按 volumeDsId 的多体 map;各切片附着到各自体的 image。 - issue2 选第二个体→第一个体切片消失:setVolumeImage 不再 closeAll 全部切片;改 upsert 某体(同体重建 才关该体切片)。syncSlices 改为「勾选 + 父体已渲染」即显示(不再限当前体)→ 多体切片并存 - ③ 右键体却建到 current 体:sliceRequested 带目标体 dsId;addSlice(axis,volumeDsId) 用该体 image; 保存切片/创建异常的 volumeDsId 改用 selectedSliceVolumeDsId(选中切片所属体)而非 currentVolume - 反向② VTK→树:InteractionManager.onSliceSelectionChanged(selectByTool/onPicked 触发)→ CategorySection::selectItem 程序化选中(屏蔽信号防环路) - VtkSceneView 按 dsId 存 volumes_(image/cs/vmin/vmax);addVolume 登记/removeDataset 移除并回退 current/clear 清空;volume(dsId) 取指定体→色阶编辑支持非当前体;SliceTool 加 volumeDsId 标签 - nearestSlice 阈值/onWheel 步长/导出上色 改用切片所属体的 bounds/色阶 未解:④ 切片拾取串选(nearestSlice 启发式,VTK9.6 不暴露切片 actor 难精确化;widget 交互选中是精确的) 构建:app 链接通过;434/434 测试通过
This commit is contained in:
parent
85ae48ebfb
commit
69e8790810
|
|
@ -111,6 +111,7 @@ void VtkSceneView::clear() {
|
||||||
// 体素 image 失效:置空并通知上层关闭切片(防切片附着到已移除的 image)。
|
// 体素 image 失效:置空并通知上层关闭切片(防切片附着到已移除的 image)。
|
||||||
currentVolumeImage_ = nullptr;
|
currentVolumeImage_ = nullptr;
|
||||||
volumeOwnerDs_.clear();
|
volumeOwnerDs_.clear();
|
||||||
|
volumes_.clear(); // 多体并发:清场移除所有体 image
|
||||||
frameAnchoredToData_ = false; // 新一轮选择重新按其首个真实剖面重锚原点
|
frameAnchoredToData_ = false; // 新一轮选择重新按其首个真实剖面重锚原点
|
||||||
if (onVolumeChanged) onVolumeChanged();
|
if (onVolumeChanged) onVolumeChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +171,7 @@ void VtkSceneView::addVolume(const std::string& dsId, const geopro::data::Volume
|
||||||
currentVmin_ = vol.vmin;
|
currentVmin_ = vol.vmin;
|
||||||
currentVmax_ = vol.vmax;
|
currentVmax_ = vol.vmax;
|
||||||
volumeOwnerDs_ = dsId;
|
volumeOwnerDs_ = dsId;
|
||||||
|
volumes_[dsId] = VolumeRec{image, cs, vol.vmin, vol.vmax}; // 多体并发:登记本体 image
|
||||||
if (onVolumeChanged) onVolumeChanged();
|
if (onVolumeChanged) onVolumeChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -201,11 +203,21 @@ void VtkSceneView::removeDataset(const std::string& dsId) {
|
||||||
if (it == dsProps_.end()) return;
|
if (it == dsProps_.end()) return;
|
||||||
removeProps(it->second);
|
removeProps(it->second);
|
||||||
dsProps_.erase(it);
|
dsProps_.erase(it);
|
||||||
if (volumeOwnerDs_ == dsId) { // 该 ds 的体素被移除 → 切片源失效
|
const bool wasVolume = volumes_.erase(dsId) > 0;
|
||||||
currentVolumeImage_ = nullptr;
|
if (volumeOwnerDs_ == dsId) { // 移除的是"当前体" → currentImage 回退到剩余某体,无则置空
|
||||||
volumeOwnerDs_.clear();
|
if (!volumes_.empty()) {
|
||||||
if (onVolumeChanged) onVolumeChanged();
|
const auto& last = *volumes_.rbegin();
|
||||||
|
volumeOwnerDs_ = last.first;
|
||||||
|
currentVolumeImage_ = last.second.image;
|
||||||
|
currentColorScale_ = last.second.cs;
|
||||||
|
currentVmin_ = last.second.vmin;
|
||||||
|
currentVmax_ = last.second.vmax;
|
||||||
|
} else {
|
||||||
|
currentVolumeImage_ = nullptr;
|
||||||
|
volumeOwnerDs_.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (wasVolume && onVolumeChanged) onVolumeChanged(); // 任一体移除 → 上层多体同步切片
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneView::addAnomaly(const geopro::core::Anomaly& a) {
|
void VtkSceneView::addAnomaly(const geopro::core::Anomaly& a) {
|
||||||
|
|
|
||||||
|
|
@ -116,11 +116,29 @@ private:
|
||||||
// 持引用以便重建前移除旧 prop,避免叠加(评审 HIGH)。
|
// 持引用以便重建前移除旧 prop,避免叠加(评审 HIGH)。
|
||||||
vtkSmartPointer<vtkCubeAxesActor> currentAxes_;
|
vtkSmartPointer<vtkCubeAxesActor> currentAxes_;
|
||||||
|
|
||||||
// 当前体素 image + 色阶(P3 切片附着源);无体素时为空。
|
// 当前体素 image + 色阶(P3 切片附着源);无体素时为空。「当前」=最后添加/活动的体(保存切片/
|
||||||
|
// 创建异常的默认归属)。多体并发下各体 image 另存 volumes_。
|
||||||
vtkSmartPointer<vtkImageData> currentVolumeImage_;
|
vtkSmartPointer<vtkImageData> currentVolumeImage_;
|
||||||
geopro::core::ColorScale currentColorScale_;
|
geopro::core::ColorScale currentColorScale_;
|
||||||
double currentVmin_ = 0.0;
|
double currentVmin_ = 0.0;
|
||||||
double currentVmax_ = 0.0;
|
double currentVmax_ = 0.0;
|
||||||
|
// 多体并发:按 dsId 持各已渲染体的 image + 色阶(供 InteractionManager 让各体切片各用自己的 image)。
|
||||||
|
struct VolumeRec {
|
||||||
|
vtkSmartPointer<vtkImageData> image;
|
||||||
|
geopro::core::ColorScale cs;
|
||||||
|
double vmin = 0.0, vmax = 0.0;
|
||||||
|
};
|
||||||
|
std::map<std::string, VolumeRec> volumes_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const std::map<std::string, VolumeRec>& volumes() const { return volumes_; } // 已渲染各体 image/色阶
|
||||||
|
bool isVolumeRendered(const std::string& dsId) const { return volumes_.count(dsId) > 0; }
|
||||||
|
const VolumeRec* volume(const std::string& dsId) const { // 取指定已渲染体的 image/色阶(缺=nullptr)
|
||||||
|
auto it = volumes_.find(dsId);
|
||||||
|
return it != volumes_.end() ? &it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
// 增量渲染:按 dsId 跟踪该数据集的 props(帘面/体素),支持单独移除而不全量重建;
|
// 增量渲染:按 dsId 跟踪该数据集的 props(帘面/体素),支持单独移除而不全量重建;
|
||||||
// miscProps_ 为非数据集 prop(地形/测线),仅随 clear 全量移除。底图由 TileBasemap 自管、不在此。
|
// miscProps_ 为非数据集 prop(地形/测线),仅随 clear 全量移除。底图由 TileBasemap 自管、不在此。
|
||||||
|
|
|
||||||
|
|
@ -409,22 +409,20 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
// 勾选且父体=当前体 → 显示(按 spec 还原);否则移除。须在 onVolumeChanged(体到场/移除)末尾
|
// 勾选且父体=当前体 → 显示(按 spec 还原);否则移除。须在 onVolumeChanged(体到场/移除)末尾
|
||||||
// 及分析栏勾选变化时调用。注:setVolumeImage 会 closeAll,故体变更后由本函数重建。
|
// 及分析栏勾选变化时调用。注:setVolumeImage 会 closeAll,故体变更后由本函数重建。
|
||||||
auto checkedSliceIds = std::make_shared<std::set<std::string>>();
|
auto checkedSliceIds = std::make_shared<std::set<std::string>>();
|
||||||
auto syncSlices = [interactionMgr, sceneView, scene3dRepo, checkedSliceIds]() {
|
auto syncSlices = [interactionMgr, scene3dRepo, checkedSliceIds]() {
|
||||||
const std::string curVol = sceneView->currentVolumeDsId();
|
// 多体并发:切片只要勾选 + 其父体已渲染就显示(不再限定"当前体")→ 多个体的切片可并存。
|
||||||
// 移除:已显示但不再需要(未勾选 / 父体非当前体 / 无活动体)。
|
|
||||||
for (const std::string& shownId : interactionMgr->shownSavedSliceIds()) {
|
for (const std::string& shownId : interactionMgr->shownSavedSliceIds()) {
|
||||||
geopro::data::I3dSceneRepository::SliceSpec sp;
|
geopro::data::I3dSceneRepository::SliceSpec sp;
|
||||||
const bool wanted = !curVol.empty() && checkedSliceIds->count(shownId) > 0 &&
|
const bool wanted = checkedSliceIds->count(shownId) > 0 &&
|
||||||
scene3dRepo->sliceSpec(shownId, sp) && sp.volumeDsId == curVol;
|
scene3dRepo->sliceSpec(shownId, sp) &&
|
||||||
|
interactionMgr->isVolumeRendered(sp.volumeDsId);
|
||||||
if (!wanted) interactionMgr->hideSavedSlice(shownId);
|
if (!wanted) interactionMgr->hideSavedSlice(shownId);
|
||||||
}
|
}
|
||||||
// 添加:勾选 + 父体=当前体 + 未显示(showSavedSlice 内部去重)。按精确三点几何还原。
|
for (const std::string& id : *checkedSliceIds) {
|
||||||
if (!curVol.empty()) {
|
geopro::data::I3dSceneRepository::SliceSpec sp;
|
||||||
for (const std::string& id : *checkedSliceIds) {
|
if (scene3dRepo->sliceSpec(id, sp) && interactionMgr->isVolumeRendered(sp.volumeDsId))
|
||||||
geopro::data::I3dSceneRepository::SliceSpec sp;
|
interactionMgr->showSavedSlice(id, sp.axis, sp.origin, sp.point1, sp.point2,
|
||||||
if (scene3dRepo->sliceSpec(id, sp) && sp.volumeDsId == curVol)
|
sp.volumeDsId);
|
||||||
interactionMgr->showSavedSlice(id, sp.axis, sp.origin, sp.point1, sp.point2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -455,13 +453,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
|
|
||||||
// 体素变化(重建/清场)后把体素 image 推给 InteractionManager(切片基底),并调和已保存切片 + 异常。
|
// 体素变化(重建/清场)后把体素 image 推给 InteractionManager(切片基底),并调和已保存切片 + 异常。
|
||||||
sceneView->onVolumeChanged = [interactionMgr, sceneView, syncSlices, refreshAnomalies]() {
|
sceneView->onVolumeChanged = [interactionMgr, sceneView, syncSlices, refreshAnomalies]() {
|
||||||
if (sceneView->hasVolume())
|
// 多体并发:先移除 interactionMgr 中已不再渲染的体(关其切片),再 upsert 当前所有已渲染体 image。
|
||||||
interactionMgr->setVolumeImage(sceneView->currentVolumeImage(),
|
for (const std::string& id : interactionMgr->volumeIds())
|
||||||
sceneView->currentColorScale(), sceneView->currentVmin(),
|
if (!sceneView->isVolumeRendered(id)) interactionMgr->removeVolumeImage(id);
|
||||||
sceneView->currentVmax());
|
for (const auto& kv : sceneView->volumes())
|
||||||
else
|
interactionMgr->setVolumeImage(kv.first, kv.second.image.Get(), kv.second.cs,
|
||||||
interactionMgr->setVolumeImage(nullptr, sceneView->currentColorScale(), 0.0, 0.0);
|
kv.second.vmin, kv.second.vmax);
|
||||||
syncSlices(); // 体到场/移除后重建当前体下已勾选的切片
|
syncSlices(); // 体到场/移除后调和各体下已勾选切片(多体并存)
|
||||||
refreshAnomalies(); // 同步重载异常 actor + 刷新异常列表
|
refreshAnomalies(); // 同步重载异常 actor + 刷新异常列表
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -532,7 +530,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
const ri::Vec3 e1{{p1[0] - o[0], p1[1] - o[1], p1[2] - o[2]}};
|
const ri::Vec3 e1{{p1[0] - o[0], p1[1] - o[1], p1[2] - o[2]}};
|
||||||
const ri::Vec3 e2{{p2[0] - o[0], p2[1] - o[1], p2[2] - o[2]}};
|
const ri::Vec3 e2{{p2[0] - o[0], p2[1] - o[1], p2[2] - o[2]}};
|
||||||
const ri::Vec3 normal = ri::normalize(ri::cross(e1, e2));
|
const ri::Vec3 normal = ri::normalize(ri::cross(e1, e2));
|
||||||
const std::string volId = sceneView->currentVolumeDsId();
|
// 多体并发:异常挂到"选中切片所属体"(非 currentVolume),无选中切片回退当前体。
|
||||||
|
std::string volId = interactionMgr->selectedSliceVolumeDsId();
|
||||||
|
if (volId.empty()) volId = sceneView->currentVolumeDsId();
|
||||||
// 异常归属(spec §8):当前选中切片已保存(selectedSliceDsId 非空)→挂该切片;临时切片→挂体。
|
// 异常归属(spec §8):当前选中切片已保存(selectedSliceDsId 非空)→挂该切片;临时切片→挂体。
|
||||||
const std::string savedSliceId = interactionMgr->selectedSliceDsId();
|
const std::string savedSliceId = interactionMgr->selectedSliceDsId();
|
||||||
anomalyDrawTool->start(
|
anomalyDrawTool->start(
|
||||||
|
|
@ -592,7 +592,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
geopro::render::interact::Vec3 o{}, p1{}, p2{};
|
geopro::render::interact::Vec3 o{}, p1{}, p2{};
|
||||||
if (!interactionMgr->selectedSlicePlane(axis, o, p1, p2)) return;
|
if (!interactionMgr->selectedSlicePlane(axis, o, p1, p2)) return;
|
||||||
geopro::data::I3dSceneRepository::SliceSpec spec;
|
geopro::data::I3dSceneRepository::SliceSpec spec;
|
||||||
spec.volumeDsId = sceneView->currentVolumeDsId();
|
// 多体并发:切片归属"选中切片所属体"(非 currentVolume),无则回退当前体。
|
||||||
|
spec.volumeDsId = interactionMgr->selectedSliceVolumeDsId();
|
||||||
|
if (spec.volumeDsId.empty()) spec.volumeDsId = sceneView->currentVolumeDsId();
|
||||||
spec.axis = axis;
|
spec.axis = axis;
|
||||||
spec.origin = o;
|
spec.origin = o;
|
||||||
spec.point1 = p1;
|
spec.point1 = p1;
|
||||||
|
|
@ -801,8 +803,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
// (O点位置/字体、旧栏「生成三维体」「勾选→渲染」接线均已退役——分别由 analysisTab 的
|
// (O点位置/字体、旧栏「生成三维体」「勾选→渲染」接线均已退役——分别由 analysisTab 的
|
||||||
// generateVolumeRequested / checkedDatasetsChanged 接管。)
|
// generateVolumeRequested / checkedDatasetsChanged 接管。)
|
||||||
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::sliceRequested, vtkWidget,
|
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::sliceRequested, vtkWidget,
|
||||||
[interactionMgr](geopro::render::interact::SliceAxis axis) {
|
[interactionMgr](geopro::render::interact::SliceAxis axis, const QString& volumeDsId) {
|
||||||
interactionMgr->addSlice(axis);
|
// 切片建到被右键的三维体上(③:不再用 currentVolume)。该体须已渲染(有 image)。
|
||||||
|
interactionMgr->addSlice(axis, volumeDsId.toStdString());
|
||||||
});
|
});
|
||||||
// 列表切片「保存」=把当前(可能被拖动过的)位姿覆盖更新到该 dd_slice;须该切片正在渲染才有位姿可取。
|
// 列表切片「保存」=把当前(可能被拖动过的)位姿覆盖更新到该 dd_slice;须该切片正在渲染才有位姿可取。
|
||||||
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::sliceSaveRequested, &window,
|
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::sliceSaveRequested, &window,
|
||||||
|
|
@ -816,7 +819,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
geopro::render::interact::Vec3 o{}, p1{}, p2{};
|
geopro::render::interact::Vec3 o{}, p1{}, p2{};
|
||||||
interactionMgr->selectedSlicePlane(axis, o, p1, p2);
|
interactionMgr->selectedSlicePlane(axis, o, p1, p2);
|
||||||
geopro::data::I3dSceneRepository::SliceSpec spec;
|
geopro::data::I3dSceneRepository::SliceSpec spec;
|
||||||
spec.volumeDsId = sceneView->currentVolumeDsId();
|
// 多体并发:保位姿归属"该切片所属体"(非 currentVolume)。
|
||||||
|
spec.volumeDsId = interactionMgr->selectedSliceVolumeDsId();
|
||||||
|
if (spec.volumeDsId.empty()) spec.volumeDsId = sceneView->currentVolumeDsId();
|
||||||
spec.axis = axis;
|
spec.axis = axis;
|
||||||
spec.origin = o;
|
spec.origin = o;
|
||||||
spec.point1 = p1;
|
spec.point1 = p1;
|
||||||
|
|
@ -880,7 +885,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::colorScaleRequested, &window,
|
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::colorScaleRequested, &window,
|
||||||
[&window, &colorTplRepo, &nav, sceneCtrl, sceneView](const QString& qid) {
|
[&window, &colorTplRepo, &nav, sceneCtrl, sceneView](const QString& qid) {
|
||||||
const std::string dsId = qid.toStdString();
|
const std::string dsId = qid.toStdString();
|
||||||
if (sceneView->currentVolumeDsId() != dsId || !sceneView->hasVolume()) {
|
// 多体并发:编辑"该体"(任一已渲染体,不限当前体)的色阶。
|
||||||
|
const auto* vol = sceneView->volume(dsId);
|
||||||
|
if (!vol) {
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
&window, QStringLiteral("色阶"),
|
&window, QStringLiteral("色阶"),
|
||||||
QStringLiteral("请先勾选该三维体使其渲染后再编辑色阶。"));
|
QStringLiteral("请先勾选该三维体使其渲染后再编辑色阶。"));
|
||||||
|
|
@ -889,7 +896,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
// 等积分层需原始标量:从当前体素 image 抽取(无则等积退化线性)。
|
// 等积分层需原始标量:从当前体素 image 抽取(无则等积退化线性)。
|
||||||
// 大体素按步长抽样(等积分位无需全量点),避免主线程长循环卡 UI。
|
// 大体素按步长抽样(等积分位无需全量点),避免主线程长循环卡 UI。
|
||||||
std::vector<double> samples;
|
std::vector<double> samples;
|
||||||
if (vtkImageData* img = sceneView->currentVolumeImage()) {
|
if (vtkImageData* img = vol->image.Get()) {
|
||||||
if (vtkDataArray* sc = img->GetPointData()->GetScalars()) {
|
if (vtkDataArray* sc = img->GetPointData()->GetScalars()) {
|
||||||
const vtkIdType n = sc->GetNumberOfTuples();
|
const vtkIdType n = sc->GetNumberOfTuples();
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
|
|
@ -907,8 +914,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
// 「另存为/打开」与「新建色阶/配色方案」走色阶模板仓储,projectId 取当前项目。
|
// 「另存为/打开」与「新建色阶/配色方案」走色阶模板仓储,projectId 取当前项目。
|
||||||
// 3D 体无来源 lvl 模板 → lvlTemplateId 传空(覆盖复选框禁用,行为不变)。
|
// 3D 体无来源 lvl 模板 → lvlTemplateId 传空(覆盖复选框禁用,行为不变)。
|
||||||
geopro::app::ColorScaleConfigDialog dlg(
|
geopro::app::ColorScaleConfigDialog dlg(
|
||||||
sceneView->currentColorScale(), sceneView->currentVmin(),
|
vol->cs, vol->vmin, vol->vmax, std::move(samples), {}, &colorTplRepo,
|
||||||
sceneView->currentVmax(), std::move(samples), {}, &colorTplRepo,
|
|
||||||
nav.currentProjectId(), QString(), &window);
|
nav.currentProjectId(), QString(), &window);
|
||||||
if (dlg.exec() == QDialog::Accepted)
|
if (dlg.exec() == QDialog::Accepted)
|
||||||
sceneCtrl->setVolumeColorScale(dsId, dlg.colorScale());
|
sceneCtrl->setVolumeColorScale(dsId, dlg.colorScale());
|
||||||
|
|
@ -932,6 +938,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
interactionMgr->selectSavedSlice(id); // 选中已渲染的该切片(高亮)
|
interactionMgr->selectSavedSlice(id); // 选中已渲染的该切片(高亮)
|
||||||
renderWindowPtr->Render();
|
renderWindowPtr->Render();
|
||||||
});
|
});
|
||||||
|
// 反向 VTK→list:在 VTK 里点中/选中一张切片 → 在三维体段树里同步选中该切片行(②反向)。
|
||||||
|
interactionMgr->onSliceSelectionChanged = [drawer](const std::string& dsId) {
|
||||||
|
if (auto* sec = drawer->analysisTab()->section("voxel"))
|
||||||
|
sec->selectItem(QString::fromStdString(dsId));
|
||||||
|
};
|
||||||
// 异常双击属性(R83)/右键删除已并入 analysisTab 的 detailRequested(dd_anomaly) /
|
// 异常双击属性(R83)/右键删除已并入 analysisTab 的 detailRequested(dd_anomaly) /
|
||||||
// deleteDatasetRequested(dd_anomaly);列表选中→VTK高亮(R84)随旧栏退役暂缺,待新段补 anomalySelected。
|
// deleteDatasetRequested(dd_anomaly);列表选中→VTK高亮(R84)随旧栏退役暂缺,待新段补 anomalySelected。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ signals:
|
||||||
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name);
|
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name);
|
||||||
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除切片/异常
|
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除切片/异常
|
||||||
// ── 三维体段操作转发(迁自旧 Column3DAnalysis,全接)──
|
// ── 三维体段操作转发(迁自旧 Column3DAnalysis,全接)──
|
||||||
void sliceRequested(geopro::render::interact::SliceAxis axis);
|
void sliceRequested(geopro::render::interact::SliceAxis axis, const QString& volumeDsId);
|
||||||
void colorScaleRequested(const QString& dsId);
|
void colorScaleRequested(const QString& dsId);
|
||||||
void sliceSaveRequested(const QString& dsId);
|
void sliceSaveRequested(const QString& dsId);
|
||||||
void sliceSaveAsRequested(const QString& dsId);
|
void sliceSaveAsRequested(const QString& dsId);
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,16 @@ void CategorySection::setDatasets(const std::vector<DsRow>& rows) {
|
||||||
rebuildList();
|
rebuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CategorySection::selectItem(const QString& dsId) {
|
||||||
|
const QSignalBlocker block(list_); // 程序化选中(VTK→list)不回发 datasetSelected,避免环路
|
||||||
|
for (QTreeWidgetItemIterator it(list_); *it; ++it)
|
||||||
|
if (!dsId.isEmpty() && (*it)->data(0, kDsIdRole).toString() == dsId) {
|
||||||
|
list_->setCurrentItem(*it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list_->setCurrentItem(nullptr); // 空 dsId / 未找到 → 清选中
|
||||||
|
}
|
||||||
|
|
||||||
void CategorySection::setChecked(const QString& dsId, bool on) {
|
void CategorySection::setChecked(const QString& dsId, bool on) {
|
||||||
for (QTreeWidgetItemIterator it(list_); *it; ++it)
|
for (QTreeWidgetItemIterator it(list_); *it; ++it)
|
||||||
if ((*it)->data(0, kDsIdRole).toString() == dsId &&
|
if ((*it)->data(0, kDsIdRole).toString() == dsId &&
|
||||||
|
|
@ -294,11 +304,11 @@ void CategorySection::showContextMenu(const QPoint& pos) {
|
||||||
menu.addAction(QStringLiteral("详情"), this,
|
menu.addAction(QStringLiteral("详情"), this,
|
||||||
[this, id, ddCode, name] { emit detailRequested(id, ddCode, name); });
|
[this, id, ddCode, name] { emit detailRequested(id, ddCode, name); });
|
||||||
if (ddCode == QStringLiteral("dd_voxel")) { // 三维体
|
if (ddCode == QStringLiteral("dd_voxel")) { // 三维体
|
||||||
QMenu* sl = menu.addMenu(QStringLiteral("生成切片"));
|
QMenu* sl = menu.addMenu(QStringLiteral("生成切片")); // id=被右键的三维体 dsId(切片建到该体上)
|
||||||
sl->addAction(QStringLiteral("上下"), this, [this] { emit sliceRequested(SliceAxis::UpDown); });
|
sl->addAction(QStringLiteral("上下"), this, [this, id] { emit sliceRequested(SliceAxis::UpDown, id); });
|
||||||
sl->addAction(QStringLiteral("前后"), this, [this] { emit sliceRequested(SliceAxis::FrontBack); });
|
sl->addAction(QStringLiteral("前后"), this, [this, id] { emit sliceRequested(SliceAxis::FrontBack, id); });
|
||||||
sl->addAction(QStringLiteral("左右"), this, [this] { emit sliceRequested(SliceAxis::LeftRight); });
|
sl->addAction(QStringLiteral("左右"), this, [this, id] { emit sliceRequested(SliceAxis::LeftRight, id); });
|
||||||
sl->addAction(QStringLiteral("任意"), this, [this] { emit sliceRequested(SliceAxis::Oblique); });
|
sl->addAction(QStringLiteral("任意"), this, [this, id] { emit sliceRequested(SliceAxis::Oblique, id); });
|
||||||
menu.addAction(QStringLiteral("色阶…"), this, [this, id] { emit colorScaleRequested(id); });
|
menu.addAction(QStringLiteral("色阶…"), this, [this, id] { emit colorScaleRequested(id); });
|
||||||
} else if (ddCode == QStringLiteral("dd_slice")) { // 切片
|
} else if (ddCode == QStringLiteral("dd_slice")) { // 切片
|
||||||
menu.addAction(QStringLiteral("保存位姿"), this, [this, id] { emit sliceSaveRequested(id); });
|
menu.addAction(QStringLiteral("保存位姿"), this, [this, id] { emit sliceSaveRequested(id); });
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ public:
|
||||||
void setStructure(const std::vector<geopro::data::StructNode>& nodes);
|
void setStructure(const std::vector<geopro::data::StructNode>& nodes);
|
||||||
void setDatasets(const std::vector<geopro::data::DsRow>& rows);
|
void setDatasets(const std::vector<geopro::data::DsRow>& rows);
|
||||||
void setChecked(const QString& dsId, bool on); // 按 dsId 勾选/取消(新建切片自动勾选等场景)
|
void setChecked(const QString& dsId, bool on); // 按 dsId 勾选/取消(新建切片自动勾选等场景)
|
||||||
|
void selectItem(const QString& dsId); // 程序化选中某行(VTK→list 反向联动);空/未找到=清选中
|
||||||
QStringList checkedIds() const { return checkedDsIds(); } // 当前勾选 ds(异常显隐同步用)
|
QStringList checkedIds() const { return checkedDsIds(); } // 当前勾选 ds(异常显隐同步用)
|
||||||
void refreshArrayFilter() { refreshArrayCombo(); } // 装置枚举异步加载后重填下拉
|
void refreshArrayFilter() { refreshArrayCombo(); } // 装置枚举异步加载后重填下拉
|
||||||
const CategorySpec& spec() const { return spec_; }
|
const CategorySpec& spec() const { return spec_; }
|
||||||
|
|
@ -43,7 +44,7 @@ signals:
|
||||||
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 双击/右键=详情
|
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 双击/右键=详情
|
||||||
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除(切片/异常)
|
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除(切片/异常)
|
||||||
// ── 三维体段右键操作(迁自旧 Column3DAnalysis,全接)──
|
// ── 三维体段右键操作(迁自旧 Column3DAnalysis,全接)──
|
||||||
void sliceRequested(geopro::render::interact::SliceAxis axis); // 体→生成切片(轴)
|
void sliceRequested(geopro::render::interact::SliceAxis axis, const QString& volumeDsId); // 体→生成切片(轴+目标体)
|
||||||
void colorScaleRequested(const QString& dsId); // 体/切片→色阶
|
void colorScaleRequested(const QString& dsId); // 体/切片→色阶
|
||||||
void sliceSaveRequested(const QString& dsId); // 切片→保存位姿
|
void sliceSaveRequested(const QString& dsId); // 切片→保存位姿
|
||||||
void sliceSaveAsRequested(const QString& dsId); // 切片→另存
|
void sliceSaveAsRequested(const QString& dsId); // 切片→另存
|
||||||
|
|
|
||||||
|
|
@ -95,42 +95,82 @@ void InteractionManager::updateSelectionVisual() {
|
||||||
slices_[i]->setSelected(static_cast<int>(i) == selected_);
|
slices_[i]->setSelected(static_cast<int>(i) == selected_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractionManager::setVolumeImage(vtkImageData* image, const geopro::core::ColorScale& cs,
|
const InteractionManager::VolumeImg* InteractionManager::volumeOf(const std::string& volumeDsId) const {
|
||||||
double vmin, double vmax) {
|
auto it = volumes_.find(volumeDsId);
|
||||||
// 体素重建/变更:先释放旧切片(旧 image 即将失效),再附着新 image。
|
return it != volumes_.end() ? &it->second : nullptr;
|
||||||
closeAll();
|
|
||||||
image_ = image;
|
|
||||||
colorScale_ = cs;
|
|
||||||
vmin_ = vmin;
|
|
||||||
vmax_ = vmax;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractionManager::addSlice(SliceAxis axis) {
|
vtkImageData* InteractionManager::selectedVolumeImage() const {
|
||||||
if (!image_ || !interactor_) return;
|
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return nullptr;
|
||||||
auto tool = std::make_unique<SliceTool>(image_, interactor_, axis, colorScale_, vmin_, vmax_);
|
const VolumeImg* v = volumeOf(slices_[static_cast<std::size_t>(selected_)]->volumeDsId());
|
||||||
|
return v ? v->image : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractionManager::setVolumeImage(const std::string& volumeDsId, vtkImageData* image,
|
||||||
|
const geopro::core::ColorScale& cs, double vmin, double vmax) {
|
||||||
|
if (volumeDsId.empty()) return;
|
||||||
|
auto it = volumes_.find(volumeDsId);
|
||||||
|
// 同体 image 变更(重建/改色阶):旧 image 即将失效 → 先关该体已显示切片(上层 syncSlices 用新 image 重现)。
|
||||||
|
if (it != volumes_.end() && it->second.image != image) closeSlicesOfVolume(volumeDsId);
|
||||||
|
volumes_[volumeDsId] = VolumeImg{image, cs, vmin, vmax};
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractionManager::removeVolumeImage(const std::string& volumeDsId) {
|
||||||
|
if (!volumes_.count(volumeDsId)) return;
|
||||||
|
closeSlicesOfVolume(volumeDsId); // 体取消渲染 → 关其下所有切片
|
||||||
|
volumes_.erase(volumeDsId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> InteractionManager::volumeIds() const {
|
||||||
|
std::vector<std::string> ids;
|
||||||
|
ids.reserve(volumes_.size());
|
||||||
|
for (const auto& kv : volumes_) ids.push_back(kv.first);
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractionManager::closeSlicesOfVolume(const std::string& volumeDsId) {
|
||||||
|
for (std::size_t i = slices_.size(); i-- > 0;) {
|
||||||
|
if (slices_[i]->volumeDsId() != volumeDsId) continue;
|
||||||
|
slices_[i]->close();
|
||||||
|
slices_.erase(slices_.begin() + static_cast<long>(i));
|
||||||
|
}
|
||||||
|
selected_ = slices_.empty() ? -1 : std::min(selected_, static_cast<int>(slices_.size()) - 1);
|
||||||
|
updateSelectionVisual();
|
||||||
|
safeRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractionManager::addSlice(SliceAxis axis, const std::string& volumeDsId) {
|
||||||
|
const VolumeImg* v = volumeOf(volumeDsId);
|
||||||
|
if (!v || !v->image || !interactor_) return;
|
||||||
|
auto tool = std::make_unique<SliceTool>(v->image, interactor_, axis, v->cs, v->vmin, v->vmax);
|
||||||
|
tool->setVolumeDsId(volumeDsId);
|
||||||
// 触碰本切片(拖动/点击切面) → 设为选中(widget 开启交互后独占切面事件,选中靠此回调)。
|
// 触碰本切片(拖动/点击切面) → 设为选中(widget 开启交互后独占切面事件,选中靠此回调)。
|
||||||
SliceTool* tp = tool.get();
|
SliceTool* tp = tool.get();
|
||||||
tool->onInteract = [this, tp]() { selectByTool(tp); };
|
tool->onInteract = [this, tp]() { selectByTool(tp); };
|
||||||
slices_.push_back(std::move(tool));
|
slices_.push_back(std::move(tool));
|
||||||
selected_ = static_cast<int>(slices_.size()) - 1; // 新切片选中
|
selected_ = static_cast<int>(slices_.size()) - 1; // 新切片选中
|
||||||
updateSelectionVisual();
|
updateSelectionVisual();
|
||||||
|
if (onSliceSelectionChanged) onSliceSelectionChanged(std::string{}); // 新建(未保存)切片→清列表选中
|
||||||
safeRender();
|
safeRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractionManager::showSavedSlice(const std::string& dsId, int axis, const Vec3& origin,
|
void InteractionManager::showSavedSlice(const std::string& dsId, int axis, const Vec3& origin,
|
||||||
const Vec3& point1, const Vec3& point2) {
|
const Vec3& point1, const Vec3& point2,
|
||||||
if (!image_ || !interactor_ || dsId.empty()) return;
|
const std::string& volumeDsId) {
|
||||||
|
const VolumeImg* v = volumeOf(volumeDsId);
|
||||||
|
if (!v || !v->image || !interactor_ || dsId.empty()) return;
|
||||||
for (const auto& s : slices_)
|
for (const auto& s : slices_)
|
||||||
if (s->dsId() == dsId) return; // 已显示 → 去重跳过
|
if (s->dsId() == dsId) return; // 已显示 → 去重跳过
|
||||||
const SliceAxis ax = static_cast<SliceAxis>(axis);
|
const SliceAxis ax = static_cast<SliceAxis>(axis);
|
||||||
auto tool = std::make_unique<SliceTool>(image_, interactor_, ax, colorScale_, vmin_, vmax_,
|
auto tool = std::make_unique<SliceTool>(v->image, interactor_, ax, v->cs, v->vmin, v->vmax,
|
||||||
origin, point1, point2); // 三点精确还原
|
origin, point1, point2); // 三点精确还原
|
||||||
tool->setDsId(dsId);
|
tool->setDsId(dsId);
|
||||||
|
tool->setVolumeDsId(volumeDsId);
|
||||||
SliceTool* tp = tool.get();
|
SliceTool* tp = tool.get();
|
||||||
tool->onInteract = [this, tp]() { selectByTool(tp); };
|
tool->onInteract = [this, tp]() { selectByTool(tp); };
|
||||||
slices_.push_back(std::move(tool));
|
slices_.push_back(std::move(tool));
|
||||||
selected_ = static_cast<int>(slices_.size()) - 1;
|
selected_ = static_cast<int>(slices_.size()) - 1;
|
||||||
updateSelectionVisual();
|
updateSelectionVisual(); // 程序化显示(syncSlices):不发 onSliceSelectionChanged,避免列表选中被刷
|
||||||
safeRender();
|
safeRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,6 +212,8 @@ void InteractionManager::selectByTool(const SliceTool* tool) {
|
||||||
if (idx < 0) return;
|
if (idx < 0) return;
|
||||||
selected_ = idx;
|
selected_ = idx;
|
||||||
updateSelectionVisual();
|
updateSelectionVisual();
|
||||||
|
if (onSliceSelectionChanged) // 反向 VTK→list:选中切片 → 列表同步选中(dsId 空=临时切片)
|
||||||
|
onSliceSelectionChanged(slices_[static_cast<std::size_t>(idx)]->dsId());
|
||||||
|
|
||||||
// 双击切片正视(D40):同一切片在 350ms 内两次交互 → 视为双击 → 正视。
|
// 双击切片正视(D40):同一切片在 350ms 内两次交互 → 视为双击 → 正视。
|
||||||
const double now = std::chrono::duration<double, std::milli>(
|
const double now = std::chrono::duration<double, std::milli>(
|
||||||
|
|
@ -239,6 +281,11 @@ std::string InteractionManager::selectedSliceDsId() const {
|
||||||
return slices_[static_cast<std::size_t>(selected_)]->dsId();
|
return slices_[static_cast<std::size_t>(selected_)]->dsId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string InteractionManager::selectedSliceVolumeDsId() const {
|
||||||
|
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return {};
|
||||||
|
return slices_[static_cast<std::size_t>(selected_)]->volumeDsId();
|
||||||
|
}
|
||||||
|
|
||||||
void InteractionManager::tagSelectedSlice(const std::string& dsId) {
|
void InteractionManager::tagSelectedSlice(const std::string& dsId) {
|
||||||
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return;
|
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return;
|
||||||
slices_[static_cast<std::size_t>(selected_)]->setDsId(dsId);
|
slices_[static_cast<std::size_t>(selected_)]->setDsId(dsId);
|
||||||
|
|
@ -269,8 +316,11 @@ vtkSmartPointer<vtkImageData> InteractionManager::selectedSliceColorImage() cons
|
||||||
std::max(1, static_cast<int>(ny * f)), 1);
|
std::max(1, static_cast<int>(ny * f)), 1);
|
||||||
resize->Update();
|
resize->Update();
|
||||||
|
|
||||||
// 用与切片显示同一色阶 LUT 上色(colorScale_/vmin_/vmax_ 即当前体/切片着色区间)。
|
// 用与切片显示同一色阶 LUT 上色:取选中切片所属体的色阶(多体并发各体色阶不同)。
|
||||||
auto lut = buildLut(colorScale_, vmin_, vmax_);
|
const VolumeImg* v = (selected_ >= 0 && selected_ < static_cast<int>(slices_.size()))
|
||||||
|
? volumeOf(slices_[static_cast<std::size_t>(selected_)]->volumeDsId())
|
||||||
|
: nullptr;
|
||||||
|
auto lut = v ? buildLut(v->cs, v->vmin, v->vmax) : buildLut(geopro::core::ColorScale{}, 0.0, 1.0);
|
||||||
vtkNew<vtkImageMapToColors> map;
|
vtkNew<vtkImageMapToColors> map;
|
||||||
map->SetInputConnection(resize->GetOutputPort());
|
map->SetInputConnection(resize->GetOutputPort());
|
||||||
map->SetLookupTable(lut);
|
map->SetLookupTable(lut);
|
||||||
|
|
@ -321,7 +371,9 @@ int InteractionManager::nearestSlice(const Vec3& worldPoint) const {
|
||||||
const int idx = nearestPlane(centers, normals, worldPoint);
|
const int idx = nearestPlane(centers, normals, worldPoint);
|
||||||
if (idx < 0) return -1;
|
if (idx < 0) return -1;
|
||||||
// 阈值:命中点离最近切面太远(> 体对角线 5%)视为"没点在切片上",不改选中(评审 M2)。
|
// 阈值:命中点离最近切面太远(> 体对角线 5%)视为"没点在切片上",不改选中(评审 M2)。
|
||||||
const std::array<double, 6> b = imageBounds(image_);
|
// 多体并发:用该切片所属体的包围盒(各体大小不同)。
|
||||||
|
const VolumeImg* vol = volumeOf(slices_[static_cast<std::size_t>(idx)]->volumeDsId());
|
||||||
|
const std::array<double, 6> b = imageBounds(vol ? vol->image : nullptr);
|
||||||
const double dx = b[1] - b[0], dy = b[3] - b[2], dz = b[5] - b[4];
|
const double dx = b[1] - b[0], dy = b[3] - b[2], dz = b[5] - b[4];
|
||||||
const double diag = std::sqrt(dx * dx + dy * dy + dz * dz);
|
const double diag = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
const double dist = slices_[static_cast<std::size_t>(idx)]->distanceToPlane(worldPoint);
|
const double dist = slices_[static_cast<std::size_t>(idx)]->distanceToPlane(worldPoint);
|
||||||
|
|
@ -334,6 +386,10 @@ void InteractionManager::onPicked(const Vec3& worldPoint) {
|
||||||
// 解决"选了切片无法取消":点击切片之外即清选中,滚轮恢复缩放(见 onWheel)。
|
// 解决"选了切片无法取消":点击切片之外即清选中,滚轮恢复缩放(见 onWheel)。
|
||||||
selected_ = nearestSlice(worldPoint);
|
selected_ = nearestSlice(worldPoint);
|
||||||
updateSelectionVisual();
|
updateSelectionVisual();
|
||||||
|
if (onSliceSelectionChanged) // 反向 VTK→list:点中切片→列表同步选中;点空(idx<0)→清列表选中
|
||||||
|
onSliceSelectionChanged(selected_ >= 0
|
||||||
|
? slices_[static_cast<std::size_t>(selected_)]->dsId()
|
||||||
|
: std::string{});
|
||||||
safeRender();
|
safeRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,7 +424,7 @@ bool InteractionManager::onWheel(int dir) {
|
||||||
// 配合 onPicked 的"点击切片外取消选中":取消后滚轮即恢复缩放,解决"选了切片无法缩放"。
|
// 配合 onPicked 的"点击切片外取消选中":取消后滚轮即恢复缩放,解决"选了切片无法缩放"。
|
||||||
// (不采用"悬停即推进":推进时鼠标难持续压在移动的切片上,且过敏感。)
|
// (不采用"悬停即推进":推进时鼠标难持续压在移动的切片上,且过敏感。)
|
||||||
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return false;
|
if (selected_ < 0 || selected_ >= static_cast<int>(slices_.size())) return false;
|
||||||
const double step = wheelStep(imageBounds(image_), dir);
|
const double step = wheelStep(imageBounds(selectedVolumeImage()), dir); // 选中切片所属体
|
||||||
slices_[static_cast<std::size_t>(selected_)]->advance(step);
|
slices_[static_cast<std::size_t>(selected_)]->advance(step);
|
||||||
safeRender();
|
safeRender();
|
||||||
return true; // 消费滚轮(推进选中切片,不缩放)
|
return true; // 消费滚轮(推进选中切片,不缩放)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -38,19 +39,23 @@ public:
|
||||||
InteractionManager(const InteractionManager&) = delete;
|
InteractionManager(const InteractionManager&) = delete;
|
||||||
InteractionManager& operator=(const InteractionManager&) = delete;
|
InteractionManager& operator=(const InteractionManager&) = delete;
|
||||||
|
|
||||||
// 设置当前体素 image + 色阶(体素重建后调;image 变更先 closeAll 再附着新 image)。
|
// 新增/更新某三维体的 image + 色阶(多体并发:不影响其它体的切片)。同体 image 变更(重建/改色阶)
|
||||||
// image=nullptr → 清空附着,切片创建无效。
|
// 会先关该体已显示切片(旧 image 失效),由上层 syncSlices 用新 image 重现。
|
||||||
void setVolumeImage(vtkImageData* image, const geopro::core::ColorScale& cs, double vmin,
|
void setVolumeImage(const std::string& volumeDsId, vtkImageData* image,
|
||||||
double vmax);
|
const geopro::core::ColorScale& cs, double vmin, double vmax);
|
||||||
|
// 移除某体(体被取消渲染):关闭其下所有切片并去除该体 image。
|
||||||
|
void removeVolumeImage(const std::string& volumeDsId);
|
||||||
|
// 当前已附着的三维体 dsId 列表(上层据此 diff 出需移除的体)。
|
||||||
|
std::vector<std::string> volumeIds() const;
|
||||||
|
|
||||||
// 创建一张切片(轴向/任意)。无体素 image 则忽略。新切片自动设为选中。
|
// 在指定三维体上创建一张切片(轴向/任意)。该体无 image 则忽略。新切片自动设为选中。
|
||||||
void addSlice(SliceAxis axis);
|
void addSlice(SliceAxis axis, const std::string& volumeDsId);
|
||||||
|
|
||||||
// ── 已保存切片(dd_slice)按 dsId 显隐(3b:三维分析栏勾选/取消已保存切片)──────────
|
// ── 已保存切片(dd_slice)按 dsId 显隐(3b:三维分析栏勾选/取消已保存切片)──────────
|
||||||
// showSavedSlice:在当前体上按精确三点几何还原一张带 dsId 标签的切片;已显示则跳过(去重)。
|
// showSavedSlice:在当前体上按精确三点几何还原一张带 dsId 标签的切片;已显示则跳过(去重)。
|
||||||
// 须在父体 image 已 setVolumeImage 后调用(无 image 则忽略)。axis 仅决定是否锁旋转。
|
// 须在父体 image 已 setVolumeImage 后调用(无 image 则忽略)。axis 仅决定是否锁旋转。
|
||||||
void showSavedSlice(const std::string& dsId, int axis, const Vec3& origin, const Vec3& point1,
|
void showSavedSlice(const std::string& dsId, int axis, const Vec3& origin, const Vec3& point1,
|
||||||
const Vec3& point2);
|
const Vec3& point2, const std::string& volumeDsId);
|
||||||
void hideSavedSlice(const std::string& dsId);
|
void hideSavedSlice(const std::string& dsId);
|
||||||
std::vector<std::string> shownSavedSliceIds() const; // 当前已显示的已保存切片 dsId 列表
|
std::vector<std::string> shownSavedSliceIds() const; // 当前已显示的已保存切片 dsId 列表
|
||||||
// 选中已显示的某 dsId 切片(列表操作定位到对应渲染切片);找到返回 true。
|
// 选中已显示的某 dsId 切片(列表操作定位到对应渲染切片);找到返回 true。
|
||||||
|
|
@ -60,8 +65,11 @@ public:
|
||||||
void closeSelected();
|
void closeSelected();
|
||||||
// 关闭并释放所有切片(切到二维 / 清场 / 体素重建前调)。
|
// 关闭并释放所有切片(切到二维 / 清场 / 体素重建前调)。
|
||||||
void closeAll();
|
void closeAll();
|
||||||
|
// 关闭并释放某体下的所有切片(该体移除/重建时;不动其它体的切片)。
|
||||||
|
void closeSlicesOfVolume(const std::string& volumeDsId);
|
||||||
|
|
||||||
bool hasVolume() const { return image_ != nullptr; }
|
bool hasVolume() const { return !volumes_.empty(); }
|
||||||
|
bool isVolumeRendered(const std::string& volumeDsId) const { return volumes_.count(volumeDsId) > 0; }
|
||||||
bool hasSlices() const { return !slices_.empty(); }
|
bool hasSlices() const { return !slices_.empty(); }
|
||||||
int sliceCount() const { return static_cast<int>(slices_.size()); }
|
int sliceCount() const { return static_cast<int>(slices_.size()); }
|
||||||
|
|
||||||
|
|
@ -74,6 +82,8 @@ public:
|
||||||
bool selectedSlicePlane(int& axis, Vec3& origin, Vec3& point1, Vec3& point2) const;
|
bool selectedSlicePlane(int& axis, Vec3& origin, Vec3& point1, Vec3& point2) const;
|
||||||
// 选中切片的归属 dsId(已保存切片非空;未保存为空)。无选中返回空字符串。
|
// 选中切片的归属 dsId(已保存切片非空;未保存为空)。无选中返回空字符串。
|
||||||
std::string selectedSliceDsId() const;
|
std::string selectedSliceDsId() const;
|
||||||
|
// 选中切片所属三维体 dsId(保存切片/创建异常时定位到正确的体)。无选中返回空字符串。
|
||||||
|
std::string selectedSliceVolumeDsId() const;
|
||||||
// 给当前选中(未保存)切片打 dsId 标签:保存=把当前切片链接到新数据集(不重绘、不重复)。
|
// 给当前选中(未保存)切片打 dsId 标签:保存=把当前切片链接到新数据集(不重绘、不重复)。
|
||||||
void tagSelectedSlice(const std::string& dsId);
|
void tagSelectedSlice(const std::string& dsId);
|
||||||
// 选中切片的重采样 2D 标量影像(导出 dat 用);无选中返回 nullptr。
|
// 选中切片的重采样 2D 标量影像(导出 dat 用);无选中返回 nullptr。
|
||||||
|
|
@ -88,6 +98,10 @@ public:
|
||||||
// 仅 closeSelected(用户主动关闭) 触发;closeAll(体变更/清场) 不触发(切片应随体回来再现)。
|
// 仅 closeSelected(用户主动关闭) 触发;closeAll(体变更/清场) 不触发(切片应随体回来再现)。
|
||||||
std::function<void(const std::string& dsId)> onSliceClosed;
|
std::function<void(const std::string& dsId)> onSliceClosed;
|
||||||
|
|
||||||
|
// 选中切片变化(VTK 内拾取/widget 交互选中一张已保存切片)→ 回调其 dsId,供上层在列表里同步选中
|
||||||
|
// (反向 VTK→list 联动)。选中临时(未保存)切片或取消选中(空 dsId)也会回调。
|
||||||
|
std::function<void(const std::string& dsId)> onSliceSelectionChanged;
|
||||||
|
|
||||||
// 安装/卸载自定义交互样式(构造时安装;析构卸载恢复原样式)。
|
// 安装/卸载自定义交互样式(构造时安装;析构卸载恢复原样式)。
|
||||||
void installStyle();
|
void installStyle();
|
||||||
void uninstallStyle();
|
void uninstallStyle();
|
||||||
|
|
@ -121,9 +135,17 @@ private:
|
||||||
vtkRenderWindow* renderWindow_;
|
vtkRenderWindow* renderWindow_;
|
||||||
vtkRenderer* renderer_;
|
vtkRenderer* renderer_;
|
||||||
|
|
||||||
vtkImageData* image_ = nullptr; // 非拥有;当前体素 image
|
// 多体并发:按三维体 dsId 持各体的 image + 色阶(切片附着到各自体的 image,互不影响)。
|
||||||
geopro::core::ColorScale colorScale_;
|
struct VolumeImg {
|
||||||
double vmin_ = 0.0, vmax_ = 0.0;
|
vtkImageData* image = nullptr; // 非拥有
|
||||||
|
geopro::core::ColorScale cs;
|
||||||
|
double vmin = 0.0, vmax = 0.0;
|
||||||
|
};
|
||||||
|
std::map<std::string, VolumeImg> volumes_; // 三维体 dsId → image/色阶
|
||||||
|
// 取某体 image(缺则 nullptr)。
|
||||||
|
const VolumeImg* volumeOf(const std::string& volumeDsId) const;
|
||||||
|
// 选中切片所属体的 image(nearestSlice 阈值/滚轮步长用);无选中或体缺则 nullptr。
|
||||||
|
vtkImageData* selectedVolumeImage() const;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<SliceTool>> slices_;
|
std::vector<std::unique_ptr<SliceTool>> slices_;
|
||||||
int selected_ = -1; // 选中切片索引(-1=无)
|
int selected_ = -1; // 选中切片索引(-1=无)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ public:
|
||||||
const std::string& dsId() const { return dsId_; }
|
const std::string& dsId() const { return dsId_; }
|
||||||
void setDsId(std::string id) { dsId_ = std::move(id); }
|
void setDsId(std::string id) { dsId_ = std::move(id); }
|
||||||
|
|
||||||
|
// 本切片所属三维体 dsId(多体并发:每张切片附着到各自体的 image;用于按体定位/移除/取色阶)。
|
||||||
|
const std::string& volumeDsId() const { return volumeDsId_; }
|
||||||
|
void setVolumeDsId(std::string id) { volumeDsId_ = std::move(id); }
|
||||||
|
|
||||||
// 取当前切面精确三点(保存用)。
|
// 取当前切面精确三点(保存用)。
|
||||||
void planePoints(double origin[3], double point1[3], double point2[3]) const;
|
void planePoints(double origin[3], double point1[3], double point2[3]) const;
|
||||||
|
|
||||||
|
|
@ -79,7 +83,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SliceAxis axis_;
|
SliceAxis axis_;
|
||||||
std::string dsId_; // 已保存切片归属标签(空=临时交互切片)
|
std::string dsId_; // 已保存切片归属标签(空=临时交互切片)
|
||||||
|
std::string volumeDsId_; // 所属三维体 dsId(多体并发用)
|
||||||
vtkImageData* image_; // 非拥有;生命周期由调用方(VtkSceneView 的 currentVolumeImage_)保证
|
vtkImageData* image_; // 非拥有;生命周期由调用方(VtkSceneView 的 currentVolumeImage_)保证
|
||||||
// 把已存在的 image 接入 widget 的 producer:须随 SliceTool 保活(否则构造后析构→管线断裂崩溃,评审 H1)。
|
// 把已存在的 image 接入 widget 的 producer:须随 SliceTool 保活(否则构造后析构→管线断裂崩溃,评审 H1)。
|
||||||
vtkSmartPointer<vtkTrivialProducer> producer_;
|
vtkSmartPointer<vtkTrivialProducer> producer_;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue