feat(vtk): 列表双击DS→适配到该DS空间范围+详情联动(无详情页静默)

决策6/T4:合并栏双击某 DS —
- 适配:复用 T2 subtreeDsIds + T1 datasetBounds/fitToBounds,相机适配到该
  ds 子树空间范围(与选中贴合轴同一子树盒);无盒(未渲染)则静默跳过。
- 详情联动:仅当该 ddCode 有已注册图表策略时开中下方 DatasetDetailPanel;
  三维体 dd_voxel/dd_radar_3d 等无详情页类型静默——gate 在联动入口
  (DatasetDetailController::supports),不走 openDataset→loadFailed 的状态栏提示。

双击语义与右键分离:新增 CategorySection::datasetActivated(双击=适配+图表联动),
右键「详情」仍发 detailRequested→属性弹窗(保留)。故三维体双击「只适配、静默」,
属性弹窗改由右键触发,符合验收#4。

加 T4 gate 契约单测(三维体/切片/异常 supports()=false,5 已注册类型=true)。
build.bat app 链接通过;build.bat test 474/474 绿。
This commit is contained in:
gaozheng 2026-07-01 11:03:23 +08:00
parent b1a8e02f6d
commit fcabf4c59f
7 changed files with 57 additions and 3 deletions

View File

@ -1300,6 +1300,25 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
if (!applyFittedAxes(dsId)) sceneView->showSceneAxes();
renderWindowPtr->Render();
});
// 双击 DS决策6/T4(a) 相机适配到该 ds 子树空间范围(复用 T2 subtreeDsIds + T1
// datasetBounds/fitToBounds与选中贴合轴同一子树盒(b) 有详情页的类型联动打开中下方详情面板,
// 三维体等无详情页类型静默——gate 在联动入口detailCtrl.supports不走 openDataset→loadFailed
// 的状态栏提示。属性弹窗改由右键「详情」(detailRequested) 触发,双击不再弹属性框。
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::datasetActivated, &window,
[sceneView, renderWindowPtr, analysisTab, &detailCtrl](
const QString& dsId, const QString& ddCode, const QString& name) {
const QStringList sub = analysisTab->subtreeDsIds(dsId);
std::vector<std::string> ids;
ids.reserve(static_cast<size_t>(sub.size()));
for (const QString& s : sub) ids.push_back(s.toStdString());
double box[6];
if (!ids.empty() && sceneView->datasetBounds(ids, box)) { // 未渲染/无盒 → 跳过适配(静默)
sceneView->fitToBounds(box);
renderWindowPtr->Render();
}
if (detailCtrl.supports(ddCode)) // 无详情页策略(三维体等)→ 只适配、不开面板(静默)
detailCtrl.openDataset(dsId, ddCode, name, QString());
});
// 2D 段「z 值」滑块 → 整体升降该 2D 类型平面Plane2DRenderStrategy 重摆其全部足迹)。
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::planeZChanged, sceneCtrl,
&geopro::controller::VtkSceneController::setPlaneZ);

View File

@ -53,6 +53,7 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d
// 注:「导入雷达测线」入口已迁至 TopBar「设备」菜单Task D1CategorySection 段头按钮与
// CategoryAnalysisTab::radarImportRequested 信号均已移除。
connect(sec, &CategorySection::detailRequested, this, &CategoryAnalysisTab::detailRequested);
connect(sec, &CategorySection::datasetActivated, this, &CategoryAnalysisTab::datasetActivated);
connect(sec, &CategorySection::deleteDatasetRequested, this,
&CategoryAnalysisTab::deleteDatasetRequested);
connect(sec, &CategorySection::sliceRequested, this, &CategoryAnalysisTab::sliceRequested);

View File

@ -42,7 +42,8 @@ public:
signals:
void checkedDatasetsChanged(const QStringList& dsIds); // 5 段勾选并集
void generateVolumeRequested(const QString& dsTypeCode, const QStringList& sourceDsIds);
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name);
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 右键「详情」=属性弹窗
void datasetActivated(const QString& dsId, const QString& ddCode, const QString& name); // 双击=适配+图表联动(T4)
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除切片/异常
// ── 三维体段操作转发(迁自旧 Column3DAnalysis全接──
void sliceRequested(geopro::render::interact::SliceAxis axis, const QString& volumeDsId);

View File

@ -154,7 +154,9 @@ CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc,
connect(list_, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* it, int) {
const QString id = it->data(0, kDsIdRole).toString();
if (id.isEmpty()) return;
emit detailRequested(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString());
// 双击=适配相机到该 ds 子树盒 + 联动中下方图表详情页(无图表页类型静默,见 main.cpp T4 接线)。
// 属性弹窗改由右键「详情」触发detailRequested双击不再弹属性框决策6三维体只适配、静默
emit datasetActivated(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString());
});
if (desc_.id == "voxel") { // 仅三维体段提供右键操作菜单(体/切片/异常)
list_->setContextMenuPolicy(Qt::CustomContextMenu);

View File

@ -60,7 +60,10 @@ signals:
void planeZChanged(const QString& typeId, double z); // PlaneZ 滑块:整体升降该 2D 类型平面z=绝对高程,米)
void basemapKindChanged(const QString& typeId, int kind); // 底图弹窗:矢量平面(0)/无(1)
void basemapOpacityChanged(const QString& typeId, double o); // 底图弹窗透明度滑块[0,1](防抖发射)
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 双击/右键=详情
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 右键「详情」=属性弹窗
// 双击某行决策6/T4适配相机到该 ds 子树空间范围 + 联动中下方详情面板(无详情页类型静默)。
// 与 detailRequested右键属性弹窗分开使双击「只适配 + 图表联动」,三维体等无图表页时静默。
void datasetActivated(const QString& dsId, const QString& ddCode, const QString& name);
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除(切片/异常)
// ── 三维体段右键操作(迁自旧 Column3DAnalysis全接──
void sliceRequested(geopro::render::interact::SliceAxis axis, const QString& volumeDsId); // 体→生成切片(轴+目标体)

View File

@ -31,6 +31,10 @@ public slots:
void loadTabPaged(const QString& dsId, const QString& ddCode, int tabIndex, int pageNo,
int pageSize);
void focusDataset(const QString& dsId);
public:
// 该 ddCode 是否有已注册的详情页策略(=有中下方图表详情页)。供联动入口 gate
// 无策略的类型(如 dd_voxel/dd_radar_3d 三维体)不走 openDataset避免 loadFailed 的状态栏提示,静默。
bool supports(const QString& ddCode) const { return registry_.supports(ddCode.toStdString()); }
signals:
void datasetOpened(const QString& dsId, const QString& ddCode, const QString& dsName,
const QString& tmObjectId, const std::vector<controller::TabSpec>& tabs);

View File

@ -1,6 +1,7 @@
#include <gtest/gtest.h>
#include "DatasetDetailTab.hpp"
#include "IDatasetChartStrategy.hpp" // geopro::controller控制器层
#include "panels/chart/ErtInversionStrategy.hpp"
#include "panels/chart/MeasurementStrategy.hpp"
#include "panels/chart/GrMeasurementStrategy.hpp"
#include "panels/chart/TrajectoryStrategy.hpp"
@ -39,6 +40,29 @@ TEST(ChartStrategyRegistry, ExposesTabSpecsFromStrategy) {
EXPECT_EQ(tabs[1].kind, ViewKind::FilledContour);
}
// T4 双击详情联动 gate 契约决策6DatasetDetailController::supports() 直接委托本注册表的 supports()。
// 三维体dd_voxel/dd_radar_3d等无详情页策略 → supports()=false → 双击只适配、静默不开面板;
// 5 种已注册类型 supports()=true → 双击联动打开中下方详情页。此测锁定这条 gate 的真实注册集行为。
TEST(ChartStrategyRegistry, T4GateSilentForVolumeTypesSupportsRegistered) {
ChartStrategyRegistry reg;
reg.add(std::make_unique<geopro::app::ErtInversionStrategy>());
reg.add(std::make_unique<geopro::app::MeasurementStrategy>());
reg.add(std::make_unique<geopro::app::GrMeasurementStrategy>());
reg.add(std::make_unique<geopro::app::TrajectoryStrategy>());
reg.add(std::make_unique<geopro::app::GridStrategy>());
// 有详情页 → 双击联动打开中下方详情面板。
EXPECT_TRUE(reg.supports("dd_inversion_data"));
EXPECT_TRUE(reg.supports("dd_ert_measurement_data"));
EXPECT_TRUE(reg.supports("dd_ert_measurement_gr_data"));
EXPECT_TRUE(reg.supports("dd_trajectory_data"));
EXPECT_TRUE(reg.supports("dd_grid"));
// 无详情页(三维体/切片/异常)→ 静默:双击只适配、不开面板、不弹状态栏。
EXPECT_FALSE(reg.supports("dd_voxel"));
EXPECT_FALSE(reg.supports("dd_radar_3d"));
EXPECT_FALSE(reg.supports("dd_slice"));
EXPECT_FALSE(reg.supports("dd_anomaly"));
}
TEST(MeasurementStrategy, DrivesTwoTabsScatterAndTable) {
geopro::app::MeasurementStrategy s;
EXPECT_EQ(s.ddCode(), "dd_ert_measurement_data");