feat/vtk-3d-view #7
|
|
@ -1040,11 +1040,9 @@ git commit -m "feat(data): createVolume/createSlice 扩参+请求体DTO组装(mo
|
||||||
- Test: `tests/...`(remarkSource 归属判定纯函数 + Api3d 异常 mock 存查)
|
- Test: `tests/...`(remarkSource 归属判定纯函数 + Api3d 异常 mock 存查)
|
||||||
|
|
||||||
**Interfaces / 数据模型:**
|
**Interfaces / 数据模型:**
|
||||||
- `enum class RemarkSourceType { Volume = 1, Slice = 2 };`(对齐后端 remark 概念)
|
- `Anomaly`:`volumeDsId` 改名为 `std::string remarkSourceId;`(挂载实体 dsId = 体 or 切片)。**不加** type 字段——挂体/挂切片由 `remarkSourceId` 查 `isVolume/isSlice` 区分,展示树按 `parentId=remarkSourceId` 自动挂载。(⚠️ 后端 `remarkSourceType` 是标注几何形态 1-4 = `markType`,勿混。)
|
||||||
- `Anomaly`:删 `volumeDsId`,加 `std::string remarkSourceId;`(体 dsId 或 切片 dsId)+ `RemarkSourceType remarkSourceType;`。
|
- 纯函数(可单测):`std::string resolveAnomalyMount(bool sliceIsSaved, const std::string& savedSliceDsId, const std::string& volumeDsId);`
|
||||||
- 纯函数(可单测):`struct AnomalyMount { std::string remarkSourceId; RemarkSourceType type; };`
|
—— 已保存切片→`savedSliceDsId`;否则→`volumeDsId`。返回挂载实体 dsId(= remarkSourceId)。
|
||||||
`AnomalyMount resolveAnomalyMount(bool sliceIsSaved, const std::string& savedSliceDsId, const std::string& volumeDsId);`
|
|
||||||
—— 已保存切片→{sliceDsId, Slice};否则→{volumeDsId, Volume}。
|
|
||||||
|
|
||||||
- [ ] **Step 1(逻辑层,可单测): Anomaly 模型 + resolveAnomalyMount**
|
- [ ] **Step 1(逻辑层,可单测): Anomaly 模型 + resolveAnomalyMount**
|
||||||
改 `Anomaly`(remarkSourceId/Type,全量改其引用点:VtkSceneView addAnomaly/removeAnomaly 按 id 跟踪不受影响,仅 main 创建处赋值变);写 `resolveAnomalyMount` 纯函数 + 单测(已保存切片挂切片 / 临时切片挂体 两例)。**build test 绿**。
|
改 `Anomaly`(remarkSourceId/Type,全量改其引用点:VtkSceneView addAnomaly/removeAnomaly 按 id 跟踪不受影响,仅 main 创建处赋值变);写 `resolveAnomalyMount` 纯函数 + 单测(已保存切片挂切片 / 临时切片挂体 两例)。**build test 绿**。
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,9 @@ checkedSourcesChanged
|
||||||
|
|
||||||
- **三维体段**:列已生成的体(客户端 mock + 后端 `dd_voxel`),按归属(项目/GS/TM)分组。(**「正在生成…」状态**:现 `createVolume` 同步登记、首次 `loadVolume` 惰性插值,本期**不引入异步生成态机**、体即时出行。)体节点下挂:① 基于该体生成的**切片**子节点;② **直接挂体的异常**(见归属规则)。多体可同时勾选渲染(`dsProps_` 按 dsId 各存 actor),切片/异常操作针对「当前激活体」`volumeOwnerDs_`(=切片源体 `currentVolumeImage_`)。
|
- **三维体段**:列已生成的体(客户端 mock + 后端 `dd_voxel`),按归属(项目/GS/TM)分组。(**「正在生成…」状态**:现 `createVolume` 同步登记、首次 `loadVolume` 惰性插值,本期**不引入异步生成态机**、体即时出行。)体节点下挂:① 基于该体生成的**切片**子节点;② **直接挂体的异常**(见归属规则)。多体可同时勾选渲染(`dsProps_` 按 dsId 各存 actor),切片/异常操作针对「当前激活体」`volumeOwnerDs_`(=切片源体 `currentVolumeImage_`)。
|
||||||
- **异常归属(核心规则)**:异常**必基于切片**(在某切片平面上画),切片**必基于体**(`SliceSpec.volumeDsId`)。查找链 `异常 → 所在切片 → 切片所属体`。挂载目标按**该切片是否已保存成 `dd_slice`** 决定:
|
- **异常归属(核心规则)**:异常**必基于切片**(在某切片平面上画),切片**必基于体**(`SliceSpec.volumeDsId`)。查找链 `异常 → 所在切片 → 切片所属体`。挂载目标按**该切片是否已保存成 `dd_slice`** 决定:
|
||||||
- 切片**已保存**(是 `dd_slice` 实体)→ 异常挂**该切片**(`remarkSourceType=切片`,`remarkSourceId=切片dsId`)。
|
- 切片**已保存**(是 `dd_slice` 实体)→ 异常挂**该切片**(`remarkSourceId=切片dsId`)。
|
||||||
- 切片**未保存**(临时圈定平面)→ 异常挂**切片所属体**(`remarkSourceType=体`,`remarkSourceId=体dsId=volumeOwnerDs_`)。
|
- 切片**未保存**(临时圈定平面)→ 异常挂**切片所属体**(`remarkSourceId=体dsId=volumeOwnerDs_`)。
|
||||||
- 数据模型:`Anomaly` 由原写死的 `volumeDsId` 改为 `remarkSourceId` + `remarkSourceType`(体/切片二选一),对齐后端 remark 概念。仍 mock 存储。
|
- 数据模型:`Anomaly` 的 `volumeDsId` 改名为 `remarkSourceId`(= 挂载实体 dsId,体 or 切片;对齐后端 `remarkSourceId=dsObjectId`)。挂体/挂切片由 `remarkSourceId` 指向的实体类型区分(查 `isVolumeDataset`/`isSliceDataset`),展示树按 `parentId=remarkSourceId` 自动挂到对应节点——**不引入新 type 字段**。⚠️ 后端 `remarkSourceType` 已是**标注几何形态**(1点/2线/3面/4文字 = `Anomaly.markType`),勿与"挂体/挂切片"混淆。仍 mock 存储。
|
||||||
- **切片段**:列已保存切片(`dd_slice`),按父体分组(`parentId` = 所属体)。与三维体段内的切片子节点同源(同一批 `sliceRows`)。
|
- **切片段**:列已保存切片(`dd_slice`),按父体分组(`parentId` = 所属体)。与三维体段内的切片子节点同源(同一批 `sliceRows`)。
|
||||||
|
|
||||||
体素 / 切片 / 异常的渲染、生成、保存路径不变(`VtkSceneController` / `InteractionManager` / `Api3dRepository`),只改列表的承载位置。
|
体素 / 切片 / 异常的渲染、生成、保存路径不变(`VtkSceneController` / `InteractionManager` / `Api3dRepository`),只改列表的承载位置。
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ AnomalyPropertiesDialog::AnomalyPropertiesDialog(const geopro::core::Anomaly& a,
|
||||||
.row(QStringLiteral("名称"), orDash(a.name))
|
.row(QStringLiteral("名称"), orDash(a.name))
|
||||||
.row(QStringLiteral("类型"), orDash(a.typeName))
|
.row(QStringLiteral("类型"), orDash(a.typeName))
|
||||||
.row(QStringLiteral("标记类型"), markTypeLabel(a.markType))
|
.row(QStringLiteral("标记类型"), markTypeLabel(a.markType))
|
||||||
.row(QStringLiteral("归属三维体"), orDash(a.volumeDsId))
|
.row(QStringLiteral("归属"), orDash(a.remarkSourceId))
|
||||||
.row(QStringLiteral("异常体"), a.consortiumId.empty()
|
.row(QStringLiteral("异常体"), a.consortiumId.empty()
|
||||||
? QStringLiteral("(未分组)")
|
? QStringLiteral("(未分组)")
|
||||||
: QString::fromStdString(a.consortiumId));
|
: QString::fromStdString(a.consortiumId));
|
||||||
|
|
|
||||||
|
|
@ -507,7 +507,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
||||||
geopro::core::Anomaly a;
|
geopro::core::Anomaly a;
|
||||||
a.markType = geopro::core::AnomalyMarkType::Polygon;
|
a.markType = geopro::core::AnomalyMarkType::Polygon;
|
||||||
a.volumeDsId = volId;
|
a.remarkSourceId = volId; // Step1 暂挂体;Step3 按所在切片是否已保存改 resolveAnomalyMount
|
||||||
a.lineColor = "#ff3030";
|
a.lineColor = "#ff3030";
|
||||||
a.lineWidth = 2.0;
|
a.lineWidth = 2.0;
|
||||||
a.dashed = false;
|
a.dashed = false;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ struct Vec3 { double x, y, z; };
|
||||||
|
|
||||||
struct Anomaly {
|
struct Anomaly {
|
||||||
std::string id; // 持久化 id(VTK 三维按 id 跟踪 actor 显隐/选中;2D 详情可空)
|
std::string id; // 持久化 id(VTK 三维按 id 跟踪 actor 显隐/选中;2D 详情可空)
|
||||||
std::string volumeDsId; // 归属三维体 ds id(= remarkSourceId;异常挂三维体,非切片)
|
std::string remarkSourceId; // 挂载实体 dsId(体 or 切片;= 后端 remarkSourceId=dsObjectId)。
|
||||||
|
// 挂体/挂切片由该 id 查 isVolume/isSlice 区分(spec §8);
|
||||||
|
// 注:后端 remarkSourceType 是标注几何形态(1-4)=markType,与此无关。
|
||||||
std::string consortiumId; // 异常体分组 id(空 = 未分组/loose)
|
std::string consortiumId; // 异常体分组 id(空 = 未分组/loose)
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string typeName; // exceptionTypeName
|
std::string typeName; // exceptionTypeName
|
||||||
|
|
@ -42,4 +44,11 @@ struct Anomaly {
|
||||||
double textOpacity = 1.0; // customLegend.opacity(0–1)
|
double textOpacity = 1.0; // customLegend.opacity(0–1)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 异常挂载实体解析(spec §8):在切片平面画异常时——
|
||||||
|
// 切片已保存成 dd_slice → 挂该切片;临时未保存切片 → 挂切片所属体。返回挂载实体 dsId(= remarkSourceId)。
|
||||||
|
inline std::string resolveAnomalyMount(bool sliceIsSaved, const std::string& savedSliceDsId,
|
||||||
|
const std::string& volumeDsId) {
|
||||||
|
return (sliceIsSaved && !savedSliceDsId.empty()) ? savedSliceDsId : volumeDsId;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace geopro::core
|
} // namespace geopro::core
|
||||||
|
|
|
||||||
|
|
@ -335,13 +335,13 @@ void Api3dRepository::deleteSlice(const std::string& dsId, std::function<void()>
|
||||||
// ── 异常 / 异常体(后端真实端点存在,但异常挂三维体、三维体仍 mock → 异常暂内存 mock;
|
// ── 异常 / 异常体(后端真实端点存在,但异常挂三维体、三维体仍 mock → 异常暂内存 mock;
|
||||||
// 挂载结构按"异常→三维体",整链端点就绪后切真实,见记忆 vtk-3d-persistence-structure)──
|
// 挂载结构按"异常→三维体",整链端点就绪后切真实,见记忆 vtk-3d-persistence-structure)──
|
||||||
|
|
||||||
void Api3dRepository::loadAnomalyTree(const std::string& volumeDsId,
|
void Api3dRepository::loadAnomalyTree(const std::string& remarkSourceId,
|
||||||
std::function<void(AnomalyTree)> onOk, OnError /*onErr*/) {
|
std::function<void(AnomalyTree)> onOk, OnError /*onErr*/) {
|
||||||
// 按归属三维体过滤;按 consortiumId 分组(异常体),空 consortiumId → loose(未分组)。
|
// 按归属实体(体/切片)过滤;按 consortiumId 分组(异常体),空 consortiumId → loose(未分组)。
|
||||||
AnomalyTree tree;
|
AnomalyTree tree;
|
||||||
std::map<std::string, std::size_t> bodyIndex; // consortiumId → tree.bodies 下标
|
std::map<std::string, std::size_t> bodyIndex; // consortiumId → tree.bodies 下标
|
||||||
for (const auto& [id, sa] : anomalies_) {
|
for (const auto& [id, sa] : anomalies_) {
|
||||||
if (!volumeDsId.empty() && sa.a.volumeDsId != volumeDsId) continue;
|
if (!remarkSourceId.empty() && sa.a.remarkSourceId != remarkSourceId) continue;
|
||||||
if (sa.a.consortiumId.empty()) {
|
if (sa.a.consortiumId.empty()) {
|
||||||
tree.loose.push_back(sa.a);
|
tree.loose.push_back(sa.a);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ private:
|
||||||
std::map<std::string, StoredSlice> slices_; // dsId → 切片
|
std::map<std::string, StoredSlice> slices_; // dsId → 切片
|
||||||
int sliceCounter_ = 0;
|
int sliceCounter_ = 0;
|
||||||
|
|
||||||
// 内存态异常存储(mock;挂三维体 = a.volumeDsId)。异常体(consortium)分组用 a.consortiumId。
|
// 内存态异常存储(mock;挂载实体 = a.remarkSourceId,体 or 切片)。异常体(consortium)分组用 a.consortiumId。
|
||||||
struct StoredAnomaly {
|
struct StoredAnomaly {
|
||||||
geopro::core::Anomaly a;
|
geopro::core::Anomaly a;
|
||||||
std::string screenshotPath;
|
std::string screenshotPath;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,18 @@ TEST(DataModel, AnomalyHolds) {
|
||||||
EXPECT_EQ(a.localPts.size(), 2u);
|
EXPECT_EQ(a.localPts.size(), 2u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 异常挂载实体解析(spec §8):切片已保存挂切片、临时切片挂体。
|
||||||
|
TEST(ResolveAnomalyMount, SavedSliceMountsOnSlice) {
|
||||||
|
EXPECT_EQ(resolveAnomalyMount(true, "slice-1", "vol-1"), "slice-1");
|
||||||
|
}
|
||||||
|
TEST(ResolveAnomalyMount, UnsavedSliceMountsOnVolume) {
|
||||||
|
EXPECT_EQ(resolveAnomalyMount(false, "", "vol-1"), "vol-1");
|
||||||
|
}
|
||||||
|
TEST(ResolveAnomalyMount, SavedFlagButEmptySliceIdFallsBackToVolume) {
|
||||||
|
// 防御:标记已保存但切片 id 缺失 → 退回挂体(不产出空 remarkSourceId)。
|
||||||
|
EXPECT_EQ(resolveAnomalyMount(true, "", "vol-1"), "vol-1");
|
||||||
|
}
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
TEST(GridNaN, HasValueReflectsNaN) {
|
TEST(GridNaN, HasValueReflectsNaN) {
|
||||||
geopro::core::Grid g(2, 2);
|
geopro::core::Grid g(2, 2);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue