feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
12 changed files with 326 additions and 10 deletions
Showing only changes of commit bdebe54859 - Show all commits

View File

@ -9,9 +9,10 @@
- **A已实现 ✅build+439测试全绿未提交**一场景两相机。切「二维分析」tab → 近俯视(下压12°≈78°俯角)+禁旋转(左键改平移、仅平移/缩放);按维度翻 actor `SetVisibility`(轨迹↔体/帘面/异常,**不清空**);切片 `SetEnabled` 显隐(不销毁);地形+底图常驻;切回三维还原相机快照。**待用户实跑**:①近俯视角度是否合适②切换是否瞬时③左键平移手感④切回三维视角还原是否自然。 - **A已实现 ✅build+439测试全绿未提交**一场景两相机。切「二维分析」tab → 近俯视(下压12°≈78°俯角)+禁旋转(左键改平移、仅平移/缩放);按维度翻 actor `SetVisibility`(轨迹↔体/帘面/异常,**不清空**);切片 `SetEnabled` 显隐(不销毁);地形+底图常驻;切回三维还原相机快照。**待用户实跑**:①近俯视角度是否合适②切换是否瞬时③左键平移手感④切回三维视角还原是否自然。
- 改动文件:`CameraPreset.{hpp,cpp}`(applyNearTop2D)、`PickInteractorStyle.{hpp,cpp}`(setLock2D)、`SliceTool.{hpp,cpp}`(setVisible)、`InteractionManager.{hpp,cpp}`(setMode2D)、`VtkSceneView.{hpp,cpp}`(setAnalysisMode2D+mapLineDs_+相机快照)、`ColumnDrawer.{hpp,cpp}`(analysisModeChanged 信号)、`main.cpp`(接信号)。 - 改动文件:`CameraPreset.{hpp,cpp}`(applyNearTop2D)、`PickInteractorStyle.{hpp,cpp}`(setLock2D)、`SliceTool.{hpp,cpp}`(setVisible)、`InteractionManager.{hpp,cpp}`(setMode2D)、`VtkSceneView.{hpp,cpp}`(setAnalysisMode2D+mapLineDs_+相机快照)、`ColumnDrawer.{hpp,cpp}`(analysisModeChanged 信号)、`main.cpp`(接信号)。
- 已知小风险2D 取景 `computeDataBounds` 含隐藏的 3D 体包围盒(地形主导,影响小);切片 `SetEnabled` 显隐属 GUI 不可自测项。 - 已知小风险2D 取景 `computeDataBounds` 含隐藏的 3D 体包围盒(地形主导,影响小);切片 `SetEnabled` 显隐属 GUI 不可自测项。
- **B下一步**:二维里选中 2D 内容(单/多选)→ 竖向拖动只改**高程 Z**、锁 XY、实时高程读数。锚点新增 2D 拾取-拖动交互(仅 Z 平移),可参考切片 widget`PickInteractorStyle` 在 lock2D 下保留拾取A 期为简化已禁拾取B 期需放开 2D 内容拾取)。 - **B已实现 ✅build+441测试全绿未提交待实跑**:二维里选中足迹(单/Ctrl 多选)→ 竖向拖动只改**高程 Z**、锁 XY、顶部实时高程读数浮层Z 偏移按 dsId 持久(切走再回/全量重建保留)。手势:单击足迹=选中、Ctrl+单击=多选切换、点空白=取消+平移、(多)选后竖向拖动=整体改 Z。
- **注意**A 期 lock2D 下 `OnLeftButtonDown` 直接 StartPan、跳过拾取。B 期要支持选中 2D 内容拖动,需改为「命中 2D 足迹→进入 Z 拖动;否则平移」。 - 实现:`VtkSceneView` 加 `pickMapLineAt/nudgeSelectedMapLinesZ/selectedMapLineZ/clearMapLineSelection`vtkCellPicker+PickFromList 只拾可见足迹、选中高亮黄加粗、`mapLineZOffset_` 持久);`PickInteractorStyle` lock2D 下命中足迹→Z 拖动(`onPick2D/onDrag2D/onDrag2DEnd`+`worldPerPixelZ` 像素→世界Z)、否则平移;`InteractionManager::pickStyle()` 暴露样式;`main.cpp` 接回调 + 高程读数浮层(复用提示样式)。
- **C**dd_raster 纳入 2D 过滤 + 按 ddCode 分派渲染 + 栅格地理配准贴地形。**阻塞dd_raster 数据端点未确认**。 - **待用户实跑**:①拾取灵敏度(tol 0.012)②拖动 Z 灵敏度/方向(上移=抬高)③多选拖动④读数是否合理(现为 actor 包围盒中心世界 Z含 placement+偏移,未除 VE)。
- **C下一步**dd_raster 纳入 2D 过滤 + 按 ddCode 分派渲染 + 栅格地理配准贴地形。**阻塞dd_raster 数据端点未确认**(需后端给「像素 + 四至/投影」端点)。
--- ---

View File

@ -11,7 +11,9 @@
#include <vtkActor.h> #include <vtkActor.h>
#include <vtkProperty.h> #include <vtkProperty.h>
#include <vtkBoundingBox.h> #include <vtkBoundingBox.h>
#include <vtkCellPicker.h>
#include <vtkCubeAxesActor.h> #include <vtkCubeAxesActor.h>
#include <vtkNew.h>
#include <vtkProp.h> #include <vtkProp.h>
#include <vtkRenderWindow.h> #include <vtkRenderWindow.h>
#include <vtkRenderer.h> #include <vtkRenderer.h>
@ -104,7 +106,8 @@ void VtkSceneView::clear() {
// 只移除数据 prop按 ds 跟踪)+ 杂项(地形/测线)+ 坐标轴;不动底图(TileBasemap 自管)→ 重建不丢图。 // 只移除数据 prop按 ds 跟踪)+ 杂项(地形/测线)+ 坐标轴;不动底图(TileBasemap 自管)→ 重建不丢图。
for (auto& kv : dsProps_) removeProps(kv.second); for (auto& kv : dsProps_) removeProps(kv.second);
dsProps_.clear(); dsProps_.clear();
mapLineDs_.clear(); // 2D 足迹维度记录随数据图元一并清(模式标志/相机快照保留) mapLineDs_.clear(); // 2D 足迹维度记录随数据图元一并清(模式标志保留)
selectedMapLines_.clear(); // 选中态随图元清(actor 已销毁)Z 偏移 mapLineZOffset_ 保留→重建后复位高度
removeProps(miscProps_); removeProps(miscProps_);
clearAnomalies(); // 异常 actor 随清场一并移除 clearAnomalies(); // 异常 actor 随清场一并移除
if (currentAxes_) { if (currentAxes_) {
@ -193,6 +196,8 @@ void VtkSceneView::addMapLine(const std::string& dsId, const geopro::data::MapLi
auto actor = geopro::render::buildMapLine(line.lat, line.lon, worldZ, *frame_); auto actor = geopro::render::buildMapLine(line.lat, line.lon, worldZ, *frame_);
if (actor) { if (actor) {
actor->SetVisibility(analysisMode2D_ ? 1 : 0); // 足迹=2D内容仅二维分析下显示 actor->SetVisibility(analysisMode2D_ ? 1 : 0); // 足迹=2D内容仅二维分析下显示
auto off = mapLineZOffset_.find(dsId); // B 期:复用持久 Z 偏移(全量重建后仍在该高度)
if (off != mapLineZOffset_.end()) actor->AddPosition(0.0, 0.0, off->second);
scene_.addActor(actor); scene_.addActor(actor);
dsProps_[dsId].push_back(actor); dsProps_[dsId].push_back(actor);
mapLineDs_.insert(dsId); // 记录此 ds 为 2D 足迹(切 tab 按维度翻可见) mapLineDs_.insert(dsId); // 记录此 ds 为 2D 足迹(切 tab 按维度翻可见)
@ -317,6 +322,7 @@ void VtkSceneView::fitView() {
void VtkSceneView::setAnalysisMode2D(bool is2D) { void VtkSceneView::setAnalysisMode2D(bool is2D) {
if (is2D == analysisMode2D_) return; // 幂等:同模式重复切不做事 if (is2D == analysisMode2D_) return; // 幂等:同模式重复切不做事
analysisMode2D_ = is2D; analysisMode2D_ = is2D;
if (!is2D) clearMapLineSelection(); // 离开二维分析:清足迹选中(三维下不可拖 Z)Z 偏移仍持久
// ① 按维度翻可见标志(不清空、不重建→切换瞬时)2D 足迹↔3D 帘面/体;异常属 3D。 // ① 按维度翻可见标志(不清空、不重建→切换瞬时)2D 足迹↔3D 帘面/体;异常属 3D。
// 地形/测线(miscProps_)与底图(TileBasemap 自管)两边常驻、不动。 // 地形/测线(miscProps_)与底图(TileBasemap 自管)两边常驻、不动。
@ -335,6 +341,115 @@ void VtkSceneView::setAnalysisMode2D(bool is2D) {
render(/*is2D ViewMode=*/false, /*resetCamera=*/true); render(/*is2D ViewMode=*/false, /*resetCamera=*/true);
} }
// ── 二维分析改造 B 期:选中 2D 足迹沿高程 Z 拖动 ───────────────────────────────────
void VtkSceneView::applyMapLineSelectionVisual() {
for (auto& kv : dsProps_) {
if (!mapLineDs_.count(kv.first)) continue;
const bool sel = selectedMapLines_.count(kv.first) > 0;
for (auto& p : kv.second) {
auto* a = vtkActor::SafeDownCast(p);
if (!a) continue;
if (sel) { // 选中:黄高亮 + 加粗
a->GetProperty()->SetColor(1.0, 0.85, 0.2);
a->GetProperty()->SetLineWidth(6.0);
} else { // 未选:复原 buildMapLine 默认(橙 3.0)
a->GetProperty()->SetColor(0.95, 0.55, 0.10);
a->GetProperty()->SetLineWidth(3.0);
}
}
}
}
void VtkSceneView::clearMapLineSelection() {
if (selectedMapLines_.empty()) return;
selectedMapLines_.clear();
applyMapLineSelectionVisual();
if (renderWindow_) renderWindow_->Render();
if (onMapLineSelectionChanged) onMapLineSelectionChanged(); // VTK→列表同步清空
}
std::vector<std::string> VtkSceneView::selectedMapLines() const {
return std::vector<std::string>(selectedMapLines_.begin(), selectedMapLines_.end());
}
void VtkSceneView::setSelectedMapLines(const std::vector<std::string>& dsIds) {
// 列表→VTK按 dsId 设选中(仅已渲染足迹),高亮+渲染;不回调 onMapLineSelectionChanged(防回环)。
selectedMapLines_.clear();
for (const auto& id : dsIds)
if (mapLineDs_.count(id)) selectedMapLines_.insert(id);
applyMapLineSelectionVisual();
if (renderWindow_) renderWindow_->Render();
}
bool VtkSceneView::pickMapLineAt(int screenX, int screenY, bool additive) {
auto* ren = scene_.renderer();
if (!ren) return false;
// 只在"可见足迹"中拾取(PickFromList):避免地形/底图/隐藏的 3D 体抢命中。
vtkNew<vtkCellPicker> picker;
picker->SetTolerance(0.012);
picker->PickFromListOn();
bool any = false;
for (auto& kv : dsProps_) {
if (!mapLineDs_.count(kv.first)) continue;
for (auto& p : kv.second)
if (p && p->GetVisibility()) { picker->AddPickList(p); any = true; }
}
if (!any) return false; // 无可见足迹 → 不拦截(交由平移)
if (!picker->Pick(screenX, screenY, 0.0, ren)) {
if (!additive) clearMapLineSelection(); // 点空白(非多选)→ 取消选中
return false;
}
vtkProp* hit = picker->GetViewProp();
std::string hitDs;
for (auto& kv : dsProps_) {
if (!mapLineDs_.count(kv.first)) continue;
for (auto& p : kv.second)
if (p.Get() == hit) { hitDs = kv.first; break; }
if (!hitDs.empty()) break;
}
if (hitDs.empty()) {
if (!additive) clearMapLineSelection();
return false;
}
if (additive) { // Ctrl 多选:切换该足迹
if (selectedMapLines_.count(hitDs)) selectedMapLines_.erase(hitDs);
else selectedMapLines_.insert(hitDs);
} else if (!selectedMapLines_.count(hitDs)) { // 单击未选中的线 → 替换为它
selectedMapLines_.clear();
selectedMapLines_.insert(hitDs);
}
// 单击已选中的线(可能为多选之一):保持当前选中集 → 起手即可整体拖动,不塌缩为单选。
applyMapLineSelectionVisual();
if (renderWindow_) renderWindow_->Render();
if (onMapLineSelectionChanged) onMapLineSelectionChanged(); // VTK→列表同步选中
return !selectedMapLines_.empty(); // 有选中 → 交互样式进入 Z 拖动
}
void VtkSceneView::nudgeSelectedMapLinesZ(double worldDz) {
if (selectedMapLines_.empty() || worldDz == 0.0) return;
for (const auto& dsId : selectedMapLines_) {
mapLineZOffset_[dsId] += worldDz; // 持久累计(全量重建后 addMapLine 复用)
auto it = dsProps_.find(dsId);
if (it == dsProps_.end()) continue;
for (auto& p : it->second) {
auto* a = vtkActor::SafeDownCast(p);
if (a) a->AddPosition(0.0, 0.0, worldDz); // 仅改 Z锁 XY
}
}
if (scene_.renderer()) scene_.renderer()->ResetCameraClippingRange(); // Z 抬升后防被裁剪面切
if (renderWindow_) renderWindow_->Render();
}
double VtkSceneView::selectedMapLineZ() const {
if (selectedMapLines_.empty()) return 0.0;
// 代表性 Z = 任一选中足迹 actor 的包围盒中心 Z(含 placement worldZ + 已累计偏移)。
auto it = dsProps_.find(*selectedMapLines_.begin());
if (it == dsProps_.end()) return 0.0;
for (const auto& p : it->second)
if (p) { if (double* b = p->GetBounds()) return 0.5 * (b[4] + b[5]); }
return 0.0;
}
void VtkSceneView::rebuildAxes() { void VtkSceneView::rebuildAxes() {
// 先移除上一次的坐标轴 proprender 可能在一次 rebuild 内多次调用(末尾统一 render + // 先移除上一次的坐标轴 proprender 可能在一次 rebuild 内多次调用(末尾统一 render +
// 异步回灌 render不先移除会叠加坐标轴评审 HIGH。移除后再算 bounds仅数据图元 // 异步回灌 render不先移除会叠加坐标轴评审 HIGH。移除后再算 bounds仅数据图元

View File

@ -90,6 +90,22 @@ public:
void setAnalysisMode2D(bool is2D); void setAnalysisMode2D(bool is2D);
bool isAnalysisMode2D() const { return analysisMode2D_; } bool isAnalysisMode2D() const { return analysisMode2D_; }
// ── 二维分析改造 B 期:选中 2D 足迹沿高程 Z 拖动 ───────────────────────────────
// 仅二维分析下用。pickMapLineAt在屏幕(x,y)拾取足迹(只考虑可见足迹,不被地形/底图干扰);命中则
// 选中(additive=Ctrl 多选切换,否则单选替换)并高亮,返回是否有选中(交互样式据此决定 Z 拖动/平移)。
// nudgeSelectedMapLinesZ选中足迹世界 Z += worldDz(锁 XY);偏移按 dsId 持久(切走再回/全量重建保留)。
// selectedMapLineZ代表性当前世界 Z(高程读数浮层用);无选中返回 0。
bool pickMapLineAt(int screenX, int screenY, bool additive);
void clearMapLineSelection();
bool hasMapLineSelection() const { return !selectedMapLines_.empty(); }
void nudgeSelectedMapLinesZ(double worldDz);
double selectedMapLineZ() const;
// 双向选择联动列表↔VTK。selectedMapLines 取当前选中 dsIdsetSelectedMapLines 由列表设置选中
// (高亮,不回调,避免环)。VTK 内拾取改变选中时触发 onMapLineSelectionChanged → 上层同步列表。
std::vector<std::string> selectedMapLines() const;
void setSelectedMapLines(const std::vector<std::string>& dsIds);
std::function<void()> onMapLineSelectionChanged;
private: private:
// 首个带经纬数据(剖面/足迹)到达时把共享 frame 重锚到其 lat/lon 包围盒中心:使数据落在世界原点近旁 // 首个带经纬数据(剖面/足迹)到达时把共享 frame 重锚到其 lat/lon 包围盒中心:使数据落在世界原点近旁
// (否则样本默认原点可能离真实数据数百公里→图元在视锥外、移动视角也找不到)。已锚或无经纬则跳过。 // (否则样本默认原点可能离真实数据数百公里→图元在视锥外、移动视角也找不到)。已锚或无经纬则跳过。
@ -159,6 +175,11 @@ private:
// 哪些 dsProps_ 条目是 2D 足迹(addMapLine):切 tab 按此区分维度翻可见(其余 dsProps_=帘面/体=3D)。 // 哪些 dsProps_ 条目是 2D 足迹(addMapLine):切 tab 按此区分维度翻可见(其余 dsProps_=帘面/体=3D)。
std::set<std::string> mapLineDs_; std::set<std::string> mapLineDs_;
bool analysisMode2D_ = false; // 当前是否处二维分析(默认三维启动在「三维分析」tab) bool analysisMode2D_ = false; // 当前是否处二维分析(默认三维启动在「三维分析」tab)
// B 期:选中的足迹 dsId(Z 拖动目标) + 各足迹累计 Z 偏移(持久,全量重建后 addMapLine 复用)。
std::set<std::string> selectedMapLines_;
std::map<std::string, double> mapLineZOffset_;
void applyMapLineSelectionVisual(); // 选中足迹加粗变亮、其余复原(橙 3.0)
}; };
} // namespace geopro::app } // namespace geopro::app

View File

@ -61,8 +61,9 @@ VtkViewToolbar::VtkViewToolbar(QWidget* parent) : QWidget(parent) {
{"", ViewDir::Bottom}, {"", ViewDir::Left}, {"", ViewDir::Right}}; {"", ViewDir::Bottom}, {"", ViewDir::Left}, {"", ViewDir::Right}};
for (const V& v : views) { for (const V& v : views) {
const ViewDir d = v.d; const ViewDir d = v.d;
connect(textBtn(QString::fromUtf8(v.t)), &QToolButton::clicked, this, auto* b = textBtn(QString::fromUtf8(v.t));
[this, d] { emit viewRequested(d); }); connect(b, &QToolButton::clicked, this, [this, d] { emit viewRequested(d); });
viewDirButtons_.push_back(b); // 二维分析下禁用(会改朝向、破坏近俯视锁定)
} }
sep(); sep();
// ── 段3缩放 / 复位 ── // ── 段3缩放 / 复位 ──
@ -84,4 +85,12 @@ VtkViewToolbar::VtkViewToolbar(QWidget* parent) : QWidget(parent) {
adjustSize(); adjustSize();
} }
void VtkViewToolbar::setAnalysisMode2D(bool is2D) {
for (auto* b : viewDirButtons_) {
if (!b) continue;
b->setEnabled(!is2D);
b->setToolTip(is2D ? QStringLiteral("二维分析下不可用(已锁定近俯视)") : QString());
}
}
} // namespace geopro::app } // namespace geopro::app

View File

@ -1,7 +1,10 @@
#pragma once #pragma once
#include <QWidget> #include <QWidget>
#include <vector>
#include "I3dSceneView.hpp" // geopro::controller::ViewDir #include "I3dSceneView.hpp" // geopro::controller::ViewDir
class QToolButton;
namespace geopro::app { namespace geopro::app {
// VTK 画布竖排工具条spec §9全局视图控制——设置(坐标轴)/前后上下左右/放大缩小复位。 // VTK 画布竖排工具条spec §9全局视图控制——设置(坐标轴)/前后上下左右/放大缩小复位。
@ -11,12 +14,20 @@ class VtkViewToolbar : public QWidget {
public: public:
explicit VtkViewToolbar(QWidget* parent = nullptr); explicit VtkViewToolbar(QWidget* parent = nullptr);
public slots:
// 二维分析激活时禁用不适用的工具6 向快捷视图会改相机朝向→破坏二维近俯视锁定,故二维下禁用;
// 缩放/适配/坐标轴设置(含 VE)仍可用。切回三维恢复。
void setAnalysisMode2D(bool is2D);
signals: signals:
void axesSettingsRequested(); // 设置 → 弹 AxesSettingsDialog void axesSettingsRequested(); // 设置 → 弹 AxesSettingsDialog
void viewRequested(geopro::controller::ViewDir dir); // 前/后/上/下/左/右 void viewRequested(geopro::controller::ViewDir dir); // 前/后/上/下/左/右
void zoomInRequested(); void zoomInRequested();
void zoomOutRequested(); void zoomOutRequested();
void fitRequested(); // 复位=适配 void fitRequested(); // 复位=适配
private:
std::vector<QToolButton*> viewDirButtons_; // 6 向快捷视图按钮:二维分析下禁用
}; };
} // namespace geopro::app } // namespace geopro::app

View File

@ -152,6 +152,7 @@
#include "Scene.hpp" #include "Scene.hpp"
#include "VoxelFromScatters.hpp" #include "VoxelFromScatters.hpp"
#include "interact/InteractionManager.hpp" #include "interact/InteractionManager.hpp"
#include "interact/PickInteractorStyle.hpp"
#include "interact/SlicePlaneMath.hpp" #include "interact/SlicePlaneMath.hpp"
#include "actors/AnomalyActor.hpp" #include "actors/AnomalyActor.hpp"
#include "actors/CurtainActor.hpp" #include "actors/CurtainActor.hpp"
@ -410,6 +411,62 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
"border:1px solid {{accent/primary}};padding:8px 12px;}")); "border:1px solid {{accent/primary}};padding:8px 12px;}"));
anomalyHint->hide(); anomalyHint->hide();
// ── 二维分析 B 期:高程 Z 拖动读数浮层(顶部居中)+ 足迹拾取/拖动回调注入交互样式 ──────────
// 拖动选中足迹时显示其当前世界 Z松开隐藏不挡画布鼠标。深底方角同异常提示坑规避
auto* elevHint = new QLabel(vtkWidget);
elevHint->setObjectName(QStringLiteral("elevHint"));
elevHint->setAttribute(Qt::WA_TransparentForMouseEvents);
geopro::app::applyTokenizedStyleSheet(
elevHint, QStringLiteral("QLabel#elevHint{background:#0E1A2D;color:#E6ECF5;"
"border:1px solid {{accent/primary}};padding:6px 12px;}"));
elevHint->hide();
// 滚轮升降时读数浮层 1.2s 后自动隐藏(拖动则在松开时隐藏)。
auto* zHideTimer = new QTimer(vtkWidget);
zHideTimer->setSingleShot(true);
QObject::connect(zHideTimer, &QTimer::timeout, elevHint, [elevHint]() { elevHint->hide(); });
auto showZReadout = std::make_shared<std::function<void()>>([sceneView, elevHint, vtkWidget]() {
elevHint->setText(
QStringLiteral("高程 Z%1 m").arg(sceneView->selectedMapLineZ(), 0, 'f', 1));
elevHint->adjustSize();
elevHint->move((vtkWidget->width() - elevHint->width()) / 2, 12); // 顶部居中
elevHint->show();
elevHint->raise();
});
if (auto* style = interactionMgr->pickStyle()) {
// 命中可见足迹→选中(Ctrl 多选)并返回是否进入 Z 拖动;未命中(返回 false)→交互样式回退平移。
style->onPick2D = [sceneView](int x, int y, bool additive) {
return sceneView->pickMapLineAt(x, y, additive);
};
// 拖动中:施加世界 Z 增量(仅改 Z),并把选中足迹当前高程显示在顶部读数浮层。
style->onDrag2D = [sceneView, showZReadout](double worldDz) {
sceneView->nudgeSelectedMapLinesZ(worldDz);
(*showZReadout)();
};
style->onDrag2DEnd = [elevHint]() { elevHint->hide(); };
// 滚轮升降:有选中足迹则施加 Z 增量并显示读数(1.2s 后自动隐藏),返回 true 消费滚轮;否则缩放。
style->onWheel2D = [sceneView, showZReadout, zHideTimer](double worldDz) {
if (!sceneView->hasMapLineSelection()) return false;
sceneView->nudgeSelectedMapLinesZ(worldDz);
(*showZReadout)();
zHideTimer->start(1200);
return true;
};
}
// 双向选择联动:列表行选中 ↔ VTK 足迹高亮。两向各自屏蔽回环(setSelectedMapLines 不回调、
// setSelectedDsIds 屏蔽信号),故无需额外守卫。
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::selectedDatasetsChanged, &window,
[sceneView](const QStringList& ids) {
std::vector<std::string> v;
for (const QString& s : ids) v.push_back(s.toStdString());
sceneView->setSelectedMapLines(v);
});
sceneView->onMapLineSelectionChanged = [sceneView, drawer]() {
QStringList ids;
for (const std::string& s : sceneView->selectedMapLines())
ids << QString::fromStdString(s);
drawer->col2D()->setSelectedDsIds(ids);
};
// 坐标轴设置抽屉面板:叠加 vtkWidget、工具条右侧滑出默认隐藏点设置 toggle // 坐标轴设置抽屉面板:叠加 vtkWidget、工具条右侧滑出默认隐藏点设置 toggle
auto* axesPanel = new geopro::app::AxesSettingsPanel(vtkWidget); auto* axesPanel = new geopro::app::AxesSettingsPanel(vtkWidget);
axesPanel->hide(); axesPanel->hide();
@ -1084,10 +1141,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 渲染 [VtkSceneView]。顺序:先 ①②(都不渲染),最后 ③ 收尾统一渲染。只翻可见标志、不清空/重建 → // 渲染 [VtkSceneView]。顺序:先 ①②(都不渲染),最后 ③ 收尾统一渲染。只翻可见标志、不清空/重建 →
// 切换瞬时;地形+底图常驻。 // 切换瞬时;地形+底图常驻。
QObject::connect(drawer, &geopro::app::ColumnDrawer::analysisModeChanged, &window, QObject::connect(drawer, &geopro::app::ColumnDrawer::analysisModeChanged, &window,
[interactionMgr, sceneCtrl, sceneView](bool is2D) { [interactionMgr, sceneCtrl, sceneView, viewToolbar](bool is2D) {
interactionMgr->setMode2D(is2D); interactionMgr->setMode2D(is2D);
sceneCtrl->onAnalysisModeChanged(is2D); sceneCtrl->onAnalysisModeChanged(is2D);
sceneView->setAnalysisMode2D(is2D); sceneView->setAnalysisMode2D(is2D);
viewToolbar->setAnalysisMode2D(is2D); // 二维下禁用 6 向快捷视图
}); });
// 首个真实剖面到达 → frame 重锚到数据 lat/lon 后,把选中的底图加载到数据所在位置 // 首个真实剖面到达 → frame 重锚到数据 lat/lon 后,把选中的底图加载到数据所在位置

View File

@ -72,6 +72,7 @@ Column2DDataset::Column2DDataset(QWidget* parent) : QWidget(parent) {
list_ = new QTreeWidget(); list_ = new QTreeWidget();
list_->setHeaderHidden(true); list_->setHeaderHidden(true);
list_->setRootIsDecorated(true); list_->setRootIsDecorated(true);
list_->setSelectionMode(QAbstractItemView::ExtendedSelection); // 多选行(与 VTK 多选拖动联动)
applyDatasetCardDelegate(list_); applyDatasetCardDelegate(list_);
connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) {
QStringList ids; QStringList ids;
@ -81,6 +82,13 @@ Column2DDataset::Column2DDataset(QWidget* parent) : QWidget(parent) {
} }
emit checkedDatasetsChanged(ids); emit checkedDatasetsChanged(ids);
}); });
// 行选中变化 → 上抛选中 dsId(高亮联动 VTK与勾选/渲染独立)。
connect(list_, &QTreeWidget::itemSelectionChanged, this, [this]() {
QStringList ids;
for (QTreeWidgetItem* it : list_->selectedItems())
ids << it->data(0, kDsIdRole).toString();
emit selectedDatasetsChanged(ids);
});
root->addWidget(list_, 1); root->addWidget(list_, 1);
} }
@ -111,4 +119,11 @@ void Column2DDataset::setDatasets(const std::vector<geopro::data::DsRow>& rows)
emit checkedDatasetsChanged(ids); emit checkedDatasetsChanged(ids);
} }
void Column2DDataset::setSelectedDsIds(const QStringList& dsIds) {
QSignalBlocker blocker(list_); // 防回环VTK→列表 设置选中不再上抛 selectedDatasetsChanged
list_->clearSelection();
for (QTreeWidgetItemIterator it(list_); *it; ++it)
if (dsIds.contains((*it)->data(0, kDsIdRole).toString())) (*it)->setSelected(true);
}
} // namespace geopro::app } // namespace geopro::app

View File

@ -14,12 +14,15 @@ class Column2DDataset : public QWidget {
public: public:
explicit Column2DDataset(QWidget* parent = nullptr); explicit Column2DDataset(QWidget* parent = nullptr);
void setDatasets(const std::vector<geopro::data::DsRow>& rows); void setDatasets(const std::vector<geopro::data::DsRow>& rows);
// VTK→列表 选择联动:按 dsId 选中对应行(高亮),内部屏蔽信号避免回环。
void setSelectedDsIds(const QStringList& dsIds);
signals: signals:
void basemapChanged(int index); // 0 天地图 / 1 Google / 2 隐藏 void basemapChanged(int index); // 0 天地图 / 1 Google / 2 隐藏
void view2DModeChanged(int index); // 0 关闭 /1 Z=0 /2 顶部 /3 底部 /4 自定义 void view2DModeChanged(int index); // 0 关闭 /1 Z=0 /2 顶部 /3 底部 /4 自定义
void customZChanged(double z); // 世界绝对高程(米),向上为正 void customZChanged(double z); // 世界绝对高程(米),向上为正
void checkedDatasetsChanged(const QStringList& dsIds); void checkedDatasetsChanged(const QStringList& dsIds); // 勾选(渲染开关)变化
void selectedDatasetsChanged(const QStringList& dsIds); // 行选中(高亮联动)变化,非勾选
private: private:
QTreeWidget* list_ = nullptr; QTreeWidget* list_ = nullptr;

View File

@ -260,6 +260,8 @@ void InteractionManager::closeAll() {
safeRender(); safeRender();
} }
PickInteractorStyle* InteractionManager::pickStyle() const { return style_; }
void InteractionManager::setMode2D(bool is2D) { void InteractionManager::setMode2D(bool is2D) {
// 切片属三维内容:二维分析隐藏(不销毁→切回零重建)、三维分析显示。 // 切片属三维内容:二维分析隐藏(不销毁→切回零重建)、三维分析显示。
for (auto& s : slices_) for (auto& s : slices_)

View File

@ -112,6 +112,10 @@ public:
void installStyle(); void installStyle();
void uninstallStyle(); void uninstallStyle();
// 暴露交互样式:供 app 层注入二维分析 B 期的足迹拾取/Z 拖动回调onPick2D/onDrag2D/onDrag2DEnd
// 定义在 .cpp此处 PickInteractorStyle 仅前置声明vtkSmartPointer→裸指针下转需完整类型
PickInteractorStyle* pickStyle() const;
private: private:
// 拾取回调实现PickInteractorStyle 注入)。 // 拾取回调实现PickInteractorStyle 注入)。
void onPicked(const Vec3& worldPoint); // 选中所在切片 + 焦点 void onPicked(const Vec3& worldPoint); // 选中所在切片 + 焦点

View File

@ -1,6 +1,7 @@
#include "interact/PickInteractorStyle.hpp" #include "interact/PickInteractorStyle.hpp"
#include <chrono> #include <chrono>
#include <cmath>
#include <vtkCallbackCommand.h> #include <vtkCallbackCommand.h>
#include <vtkCamera.h> #include <vtkCamera.h>
@ -48,12 +49,19 @@ bool PickInteractorStyle::pickWorld(Vec3& out) {
void PickInteractorStyle::OnLeftButtonDown() { void PickInteractorStyle::OnLeftButtonDown() {
auto* iren = this->GetInteractor(); auto* iren = this->GetInteractor();
// 二维分析:左键拖动=平移(等同中键),不拾取/不旋转 → 仅平移+缩放。抬键由基类按 State 收尾。 // 二维分析:左键命中足迹→进入高程 Z 拖动(B 期);否则=平移(等同中键),禁旋转。抬键由 OnLeftButtonUp 收尾。
if (lock2D_) { if (lock2D_) {
const int* p = iren ? iren->GetEventPosition() : nullptr; const int* p = iren ? iren->GetEventPosition() : nullptr;
if (p) this->FindPokedRenderer(p[0], p[1]); if (p) this->FindPokedRenderer(p[0], p[1]);
if (!this->CurrentRenderer) return; if (!this->CurrentRenderer) return;
this->GrabFocus(this->EventCallbackCommand); const bool additive = iren && iren->GetControlKey(); // Ctrl=多选
if (onPick2D && p && onPick2D(p[0], p[1], additive)) { // 命中足迹 → Z 拖动
dragging2D_ = true;
lastDragY_ = p[1];
this->GrabFocus(this->EventCallbackCommand);
return;
}
this->GrabFocus(this->EventCallbackCommand); // 未命中 → 平移
this->StartPan(); this->StartPan();
return; return;
} }
@ -137,12 +145,62 @@ void PickInteractorStyle::Rotate() {
rwi->Render(); rwi->Render();
} }
double PickInteractorStyle::worldPerPixelZ() const {
if (!this->CurrentRenderer) return 1.0;
auto* cam = this->CurrentRenderer->GetActiveCamera();
auto* rw = this->CurrentRenderer->GetRenderWindow();
if (!cam || !rw) return 1.0;
const int* sz = rw->GetSize();
const double h = (sz && sz[1] > 0) ? static_cast<double>(sz[1]) : 800.0;
if (cam->GetParallelProjection())
return 2.0 * cam->GetParallelScale() / h; // 平行投影:可见世界高度=2*parallelScale
// 透视:可见世界高度 = 2*d*tan(viewAngle/2)d=相机到焦点距离。
double pos[3], fp[3];
cam->GetPosition(pos);
cam->GetFocalPoint(fp);
const double dx = pos[0] - fp[0], dy = pos[1] - fp[1], dz = pos[2] - fp[2];
const double d = std::sqrt(dx * dx + dy * dy + dz * dz);
const double va = vtkMath::RadiansFromDegrees(cam->GetViewAngle());
return 2.0 * d * std::tan(va * 0.5) / h;
}
void PickInteractorStyle::OnMouseMove() {
if (dragging2D_) { // B 期:竖向拖动 → 选中足迹 Z 增量(仅改 Z)。鼠标上移(y 增)→ 抬高。
auto* rwi = this->Interactor;
if (rwi) {
const int y = rwi->GetEventPosition()[1];
const int dyPix = y - lastDragY_;
lastDragY_ = y;
if (dyPix != 0 && onDrag2D) onDrag2D(worldPerPixelZ() * dyPix);
}
return; // 不走基类(不平移/不旋转)
}
Superclass::OnMouseMove();
}
void PickInteractorStyle::OnLeftButtonUp() {
if (dragging2D_) { // 结束 Z 拖动
dragging2D_ = false;
if (this->Interactor) this->ReleaseFocus();
if (onDrag2DEnd) onDrag2DEnd();
return;
}
Superclass::OnLeftButtonUp(); // 平移/旋转/缩放等由基类按 State 收尾
}
namespace {
constexpr double kWheelStepPx = 24.0; // 滚轮一格升降 ≈ 拖动 24 像素的世界 Z 量(与拖动手感一致)
}
void PickInteractorStyle::OnMouseWheelForward() { void PickInteractorStyle::OnMouseWheelForward() {
// 二维分析有选中足迹 → 滚轮抬升其高程(消费滚轮);否则按切片推进 / 默认缩放。
if (lock2D_ && onWheel2D && onWheel2D(worldPerPixelZ() * kWheelStepPx)) return;
if (onWheelStep && onWheelStep(+1)) return; // 有选中切片 → 推进,消费滚轮 if (onWheelStep && onWheelStep(+1)) return; // 有选中切片 → 推进,消费滚轮
Superclass::OnMouseWheelForward(); // 否则默认缩放 Superclass::OnMouseWheelForward(); // 否则默认缩放
} }
void PickInteractorStyle::OnMouseWheelBackward() { void PickInteractorStyle::OnMouseWheelBackward() {
if (lock2D_ && onWheel2D && onWheel2D(-worldPerPixelZ() * kWheelStepPx)) return;
if (onWheelStep && onWheelStep(-1)) return; if (onWheelStep && onWheelStep(-1)) return;
Superclass::OnMouseWheelBackward(); Superclass::OnMouseWheelBackward();
} }

View File

@ -35,6 +35,19 @@ public:
void setLock2D(bool on) { lock2D_ = on; } void setLock2D(bool on) { lock2D_ = on; }
bool isLock2D() const { return lock2D_; } bool isLock2D() const { return lock2D_; }
// ── 二维分析 B 期:选中足迹沿高程 Z 拖动 ──(仅 lock2D 下生效;回调由 app 层注入)
// onPick2D左键按下时在(x,y)拾取足迹(additive=Ctrl 多选),返回是否有选中→有则进入 Z 拖动、否则平移。
// onDrag2D拖动中把竖向像素换算成的世界 Z 增量(本类按相机算)交给 app 施加到选中足迹(仅改 Z)。
// onDrag2DEnd松开结束拖动(供 app 收起高程读数浮层)。
std::function<bool(int x, int y, bool additive)> onPick2D;
std::function<void(double worldDz)> onDrag2D;
std::function<void()> onDrag2DEnd;
// 滚轮升降:有选中足迹时滚轮改其高程 Z(本类按相机算 worldDz)app 施加并返回是否消费(无选中→false→默认缩放)。
std::function<bool(double worldDz)> onWheel2D;
void OnMouseMove() override;
void OnLeftButtonUp() override;
void OnLeftButtonDown() override; void OnLeftButtonDown() override;
void OnMouseWheelForward() override; void OnMouseWheelForward() override;
void OnMouseWheelBackward() override; void OnMouseWheelBackward() override;
@ -48,6 +61,8 @@ protected:
private: private:
// 在当前鼠标位置拾取世界点;命中返回 true 并填 out。 // 在当前鼠标位置拾取世界点;命中返回 true 并填 out。
bool pickWorld(Vec3& out); bool pickWorld(Vec3& out);
// 当前相机下:竖向一屏幕像素对应的世界 Z米/像素),用于把拖动像素换算成 Z 增量。
double worldPerPixelZ() const;
// 手动双击判定QVTK+Windows 下 vtkRenderWindowInteractor::GetRepeatCount() 不可靠(评审 M5 // 手动双击判定QVTK+Windows 下 vtkRenderWindowInteractor::GetRepeatCount() 不可靠(评审 M5
// 记上次左键按下时刻+屏幕位置,两次按下间隔 < kDoubleClickMs 且位置相近视为双击。 // 记上次左键按下时刻+屏幕位置,两次按下间隔 < kDoubleClickMs 且位置相近视为双击。
@ -56,6 +71,10 @@ private:
// 二维分析模式:左键=平移、禁旋转(仅平移+缩放)。由 InteractionManager 在切 tab 时设。 // 二维分析模式:左键=平移、禁旋转(仅平移+缩放)。由 InteractionManager 在切 tab 时设。
bool lock2D_ = false; bool lock2D_ = false;
// B 期足迹 Z 拖动状态:左键命中足迹时进入,记上次鼠标 y 以算增量。
bool dragging2D_ = false;
int lastDragY_ = 0;
}; };
} // namespace geopro::render::interact } // namespace geopro::render::interact