diff --git a/src/controller/DatasetRenderStrategy.cpp b/src/controller/DatasetRenderStrategy.cpp index d350943..26190b7 100644 --- a/src/controller/DatasetRenderStrategy.cpp +++ b/src/controller/DatasetRenderStrategy.cpp @@ -16,9 +16,21 @@ void CurtainRenderStrategy::add(const std::string& /*typeId*/, const std::string } void CurtainRenderStrategy::remove(const std::string& dsId) { ctrl_.view_.removeDataset(dsId); } -void Plane2DRenderStrategy::add(const std::string& /*typeId*/, const std::string& dsId) { - ctrl_.add2DDatasetAsync(dsId, ctrl_.rebuildGeneration_); +void Plane2DRenderStrategy::add(const std::string& typeId, const std::string& dsId) { + // 该类型平面 z:首勾以 dsZ(场景地表基准 zRefElev)定平面,后续勾选返回既有平面 z(投影)。 + const double dsZ = ctrl_.view_.zRefElev(); + const double z = planeReg_.onChecked(typeId, dsId, dsZ); + dsToType_[dsId] = typeId; // remove 仅得 dsId,记下归属类型以回收平面成员 + ctrl_.add2DDatasetAsync(dsId, ctrl_.rebuildGeneration_, z); } -void Plane2DRenderStrategy::remove(const std::string& dsId) { ctrl_.view_.removeDataset(dsId); } +void Plane2DRenderStrategy::remove(const std::string& dsId) { + ctrl_.view_.removeDataset(dsId); + auto it = dsToType_.find(dsId); + if (it == dsToType_.end()) return; + planeReg_.onUnchecked(it->second, dsId); // 该类型成员集空时平面自动消失 + dsToType_.erase(it); +} +// 该类型全消(控制器活跃计数归零):此时 planeReg_.hasPlane==false。Phase F2 在此销毁平面底图。 +void Plane2DRenderStrategy::onTypeDeactivated(const std::string& /*typeId*/) {} } // namespace geopro::controller diff --git a/src/controller/DatasetRenderStrategy.hpp b/src/controller/DatasetRenderStrategy.hpp index df28ddd..cb0236e 100644 --- a/src/controller/DatasetRenderStrategy.hpp +++ b/src/controller/DatasetRenderStrategy.hpp @@ -3,6 +3,8 @@ #include #include +#include "controller/PlaneZRegistry.hpp" + namespace geopro::controller { class VtkSceneController; // 策略委托回控制器既有渲染路径(add→addDatasetAsync/add2DDatasetAsync;remove→view.removeDataset) @@ -50,13 +52,18 @@ private: VtkSceneController& ctrl_; }; +// 每个 2D 类型(段)的勾选足迹落到该类型「同一平面」上:首勾的 ds 定平面 z,后续投影到该 z; +// 全消则平面消失(z 遗忘)。平面 z 生命周期由 PlaneZRegistry 维护;足迹摆放经 add2DDatasetAsync(z)。 class Plane2DRenderStrategy : public IDatasetRenderStrategy { public: explicit Plane2DRenderStrategy(VtkSceneController& ctrl) : ctrl_(ctrl) {} void add(const std::string& typeId, const std::string& dsId) override; void remove(const std::string& dsId) override; + void onTypeDeactivated(const std::string& typeId) override; private: VtkSceneController& ctrl_; + PlaneZRegistry planeReg_; // 按类型的平面 z 生命周期 + std::map dsToType_; // dsId→typeId(remove 只得 dsId,需自存反查) }; } // namespace geopro::controller diff --git a/src/controller/PlaneZRegistry.hpp b/src/controller/PlaneZRegistry.hpp new file mode 100644 index 0000000..a8ed503 --- /dev/null +++ b/src/controller/PlaneZRegistry.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include + +namespace geopro::controller { + +// 纯逻辑:按 2D 类型管理「平面 z + 成员集」。首勾定 z(之后固定); 全消则平面消失。见 spec §8.2。 +// 无 Qt/VTK 依赖,便于纯逻辑单测。被 Plane2DRenderStrategy 持有以摆放足迹。 +class PlaneZRegistry { +public: + // 某类型某 ds 勾选:首勾(成员集空)记录平面 z=dsZ;返回该类型当前平面 z(后续勾选投影到此)。 + double onChecked(const std::string& typeId, const std::string& dsId, double dsZ) { + auto& p = planes_[typeId]; + if (p.members.empty()) p.z = dsZ; // 首勾定 z + p.members.insert(dsId); + return p.z; + } + // 取消勾选:移出成员;该类型成员集空时清除条目(平面消失,z 遗忘)。 + void onUnchecked(const std::string& typeId, const std::string& dsId) { + auto it = planes_.find(typeId); + if (it == planes_.end()) return; + it->second.members.erase(dsId); + if (it->second.members.empty()) planes_.erase(it); // 全消 → 平面消失 + } + bool hasPlane(const std::string& typeId) const { return planes_.count(typeId) > 0; } + double planeZ(const std::string& typeId) const { + auto it = planes_.find(typeId); + return it == planes_.end() ? 0.0 : it->second.z; + } + // 滑块整体调:移动该类型既有平面 z(类型不存在则无操作)。 + void setPlaneZ(const std::string& typeId, double z) { + auto it = planes_.find(typeId); + if (it != planes_.end()) it->second.z = z; + } + +private: + struct Plane { + double z = 0.0; + std::set members; + }; + std::map planes_; +}; + +} // namespace geopro::controller diff --git a/src/controller/VtkSceneController.cpp b/src/controller/VtkSceneController.cpp index 3e1ac45..01019a6 100644 --- a/src/controller/VtkSceneController.cpp +++ b/src/controller/VtkSceneController.cpp @@ -100,21 +100,22 @@ void VtkSceneController::setCheckedDatasets( view_.renderIncremental(); // 立即反映移除 / 触发坐标轴重算 } -void VtkSceneController::add2DDatasetAsync(const std::string& dsId, unsigned long long gen) { +void VtkSceneController::add2DDatasetAsync(const std::string& dsId, unsigned long long gen, + double z) { if (loadingDs_.count(dsId)) return; // 已在加载(重复勾选竞态)→ 不重复请求 loadingDs_.insert(dsId); QPointer self(this); sceneRepo_.loadMapLine( dsId, - [self, gen, dsId](data::MapLine line) { + [self, gen, dsId, z](data::MapLine line) { if (!self) return; self->loadingDs_.erase(dsId); // gen 作废 / 已取消勾选 → 丢弃迟到回调。 if (gen != self->rebuildGeneration_ || !self->is2DChecked(dsId)) { return; } - // 单一自由场景:足迹统一摆到 Z=0(按类型平面 z 由 Phase E2 接入)。 - self->view_.addMapLine(dsId, line, 0.0); + // 足迹摆到所属 2D 类型的平面 z(首勾定、后续投影;由 Plane2DRenderStrategy 决定)。 + self->view_.addMapLine(dsId, line, z); self->onDatasetArrived(); }, [self, gen, dsId](const std::string& m) { diff --git a/src/controller/VtkSceneController.hpp b/src/controller/VtkSceneController.hpp index abd396e..b00a72e 100644 --- a/src/controller/VtkSceneController.hpp +++ b/src/controller/VtkSceneController.hpp @@ -96,8 +96,9 @@ private: void recolorDataset(const QString& dsId); // 增量加入单个 ds(帘面/体素,按图层开关);回调按 gen + 仍勾选 守护,落地后增量渲染。 void addDatasetAsync(const std::string& dsId, unsigned long long gen); - // 增量加入单个 2D 足迹(异步 loadMapLine → addMapLine at 当前摆放 Z);回调按 gen + 仍勾选 守护。 - void add2DDatasetAsync(const std::string& dsId, unsigned long long gen); + // 增量加入单个 2D 足迹(异步 loadMapLine → addMapLine at 摆放 z);回调按 gen + 仍勾选 守护。 + // z 为该 ds 所属 2D 类型的平面高程(由 Plane2DRenderStrategy 经 PlaneZRegistry 决定,§E2)。 + void add2DDatasetAsync(const std::string& dsId, unsigned long long gen, double z); void onDatasetArrived(); // 单个 ds 落地后:增量渲染 + 首批数据自动取景 bool isChecked(const std::string& dsId) const; bool is2DChecked(const std::string& dsId) const; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41454f2..bce98e3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -230,6 +230,8 @@ target_sources(geopro_tests PRIVATE controller/test_workbench_nav_controller.cpp target_sources(geopro_tests PRIVATE controller/test_vtk_scene_controller.cpp) # RenderStrategyRegistry:字符串键策略注册表解析(register/get,纯逻辑,FakeStrategy)。 target_sources(geopro_tests PRIVATE controller/test_render_strategy_registry.cpp) +# PlaneZRegistry:按 2D 类型管理平面 z 生命周期(首勾定 z / 全消消失 / setPlaneZ 整体调,纯逻辑头)。 +target_sources(geopro_tests PRIVATE controller/test_plane_z_registry.cpp) target_link_libraries(geopro_tests PRIVATE geopro_controller Qt6::Test) # io/gpr 层:.iprh 头解析 + .iprb B-scan 读取(纯 C++17,零 Qt/VTK)。 diff --git a/tests/controller/test_plane_z_registry.cpp b/tests/controller/test_plane_z_registry.cpp new file mode 100644 index 0000000..ab3fa41 --- /dev/null +++ b/tests/controller/test_plane_z_registry.cpp @@ -0,0 +1,38 @@ +#include + +#include "controller/PlaneZRegistry.hpp" + +using geopro::controller::PlaneZRegistry; + +TEST(PlaneZRegistry, FirstCheckSetsPlaneZ) { + PlaneZRegistry r; + EXPECT_DOUBLE_EQ(r.onChecked("trajectory", "a", 12.0), 12.0); + EXPECT_TRUE(r.hasPlane("trajectory")); + EXPECT_DOUBLE_EQ(r.planeZ("trajectory"), 12.0); +} +TEST(PlaneZRegistry, SecondCheckKeepsFirstZ) { + PlaneZRegistry r; + r.onChecked("trajectory", "a", 12.0); + EXPECT_DOUBLE_EQ(r.onChecked("trajectory", "b", 99.0), 12.0); // 投影到首个 ds 的平面 +} +TEST(PlaneZRegistry, PlaneDisappearsWhenAllUnchecked) { + PlaneZRegistry r; + r.onChecked("trajectory", "a", 12.0); + r.onChecked("trajectory", "b", 99.0); + r.onUnchecked("trajectory", "a"); + EXPECT_TRUE(r.hasPlane("trajectory")); // 还有 b + r.onUnchecked("trajectory", "b"); + EXPECT_FALSE(r.hasPlane("trajectory")); // 全消 → 平面消失 +} +TEST(PlaneZRegistry, RecheckAfterEmptyResetsZ) { + PlaneZRegistry r; + r.onChecked("trajectory", "a", 12.0); + r.onUnchecked("trajectory", "a"); + EXPECT_DOUBLE_EQ(r.onChecked("trajectory", "c", 7.0), 7.0); // 重新首勾 → 新 z +} +TEST(PlaneZRegistry, SetPlaneZMovesPlane) { + PlaneZRegistry r; + r.onChecked("trajectory", "a", 12.0); + r.setPlaneZ("trajectory", 30.0); + EXPECT_DOUBLE_EQ(r.planeZ("trajectory"), 30.0); +}