diff --git a/src/data/api/Api3dRepository.cpp b/src/data/api/Api3dRepository.cpp index 152d73e..d825cb5 100644 --- a/src/data/api/Api3dRepository.cpp +++ b/src/data/api/Api3dRepository.cpp @@ -134,6 +134,22 @@ std::vector Api3dRepository::volumeRows() const { return rows; } +std::vector Api3dRepository::anomalyRows() const { + std::vector rows; + rows.reserve(anomalies_.size()); + for (const auto& [id, sa] : anomalies_) { + DsRow r; + r.id = id; + r.dsName = sa.a.name; + r.ddCode = "dd_anomaly"; + r.typeName = sa.a.typeName.empty() ? std::string("异常") : sa.a.typeName; + r.createTime = sa.a.createTime; + r.parentId = sa.a.remarkSourceId; // 挂归属实体(体/切片)下;三级树按 parentId 自动挂载 + rows.push_back(std::move(r)); + } + return rows; +} + bool Api3dRepository::volumeInfo(const std::string& dsId, VolumeInfo& out) const { auto it = volumes_.find(dsId); if (it == volumes_.end()) return false; diff --git a/src/data/api/Api3dRepository.hpp b/src/data/api/Api3dRepository.hpp index 8a69048..7a767b2 100644 --- a/src/data/api/Api3dRepository.hpp +++ b/src/data/api/Api3dRepository.hpp @@ -62,6 +62,9 @@ public: bool volumeInfo(const std::string& dsId, VolumeInfo& out) const; // 已保存切片的列表行(ddCode="dd_slice",parentId=所属体 dsId → 树中挂父体下),供三维分析栏合并。 std::vector sliceRows() const; + // 异常列表行(ddCode="dd_anomaly",parentId=remarkSourceId=归属实体[体/切片] dsId → 三级树自动挂载), + // 供三维体段「体→切片/异常」三级树合并注入(spec §8)。 + std::vector anomalyRows() const; // 该 dsId 是否为已保存切片(3b:分析栏勾选 dd_slice 走切片重渲染路径,不进控制器帘面/体素路径)。 bool isSliceDataset(const std::string& dsId) const; // 取回已保存切片位姿(还原渲染用);不存在返回 false。 diff --git a/tests/data/test_3d_repo.cpp b/tests/data/test_3d_repo.cpp index 5d8b380..20b8fa1 100644 --- a/tests/data/test_3d_repo.cpp +++ b/tests/data/test_3d_repo.cpp @@ -156,3 +156,36 @@ TEST(Api3dRepo, VolumeInfoUnknownIdReturnsFalse) { Api3dRepository::VolumeInfo info; EXPECT_FALSE(repo.volumeInfo("not-a-volume", info)); } + +// anomalyRows:异常按 remarkSourceId 存;行 parentId=归属实体 id(挂体/挂切片各回得来)。 +TEST(Api3dRepo, AnomalyRowsCarryMountAsParent) { + StubAsyncRepo dsRepo; + auto frame = std::make_shared(22.0, 114.0); + Api3dRepository repo(dsRepo, frame); + + geopro::core::Anomaly onVol; + onVol.name = "异常A"; onVol.typeName = "断层"; onVol.remarkSourceId = "vol-1"; + geopro::core::Anomaly onSlice; + onSlice.name = "异常B"; onSlice.remarkSourceId = "slice-9"; // 已保存切片 + std::string idV, idS; + repo.saveAnomaly(onVol, "", [&](std::string id) { idV = id; }, [](const std::string&) {}); + repo.saveAnomaly(onSlice, "", [&](std::string id) { idS = id; }, [](const std::string&) {}); + + const auto rows = repo.anomalyRows(); + ASSERT_EQ(rows.size(), 2u); + auto find = [&](const std::string& id) -> const geopro::data::DsRow* { + for (const auto& r : rows) + if (r.id == id) return &r; + return nullptr; + }; + const auto* rv = find(idV); + ASSERT_NE(rv, nullptr); + EXPECT_EQ(rv->ddCode, "dd_anomaly"); + EXPECT_EQ(rv->parentId, "vol-1"); // 挂体 + EXPECT_EQ(rv->dsName, "异常A"); + EXPECT_EQ(rv->typeName, "断层"); + const auto* rs = find(idS); + ASSERT_NE(rs, nullptr); + EXPECT_EQ(rs->parentId, "slice-9"); // 挂切片 + EXPECT_EQ(rs->typeName, "异常"); // typeName 空 → 回退"异常" +}