From 10c7f23b17489178618d55480aa3d65194d8af97 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 2 Aug 2026 20:46:02 -0400 Subject: [PATCH] feat: report shape topology checks --- docs/freecad-full-parity-plan.zh-CN.md | 1 + src/facade/mockFacade.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/freecad-full-parity-plan.zh-CN.md b/docs/freecad-full-parity-plan.zh-CN.md index 634e837..5e9337c 100644 --- a/docs/freecad-full-parity-plan.zh-CN.md +++ b/docs/freecad-full-parity-plan.zh-CN.md @@ -331,6 +331,7 @@ Geometry Facade 还接入了 Bitbybit IO 的 STEP 和 ASCII STL 导出,作为 | FC-03 ZIP 安全读取 | 先读中央目录再解压;限制 archive/entry/total 字节、压缩比、条目数;拒绝路径穿越、重复路径、加密项、ZIP64 和 XML entity | 缺 Document.xml、路径穿越、压缩炸弹测试通过 | 尚未恢复 BRep/Shape、视图、表达式和参数化对象 | | FC-04/FC-07 只读报告 | `project.fcstd.inspect()` 解析 Document.xml 对象声明、Label、属性数量和类型;未知类型为 proxy,Python 类型为 blocked;脚本资源只告警且不执行 | recognized/proxy/blocked 分类和 Facade-only 测试通过 | `metadata-compatible` 仅表示 XML 元数据可读,不表示 FCStd 可编辑或 round-trip | | P2-04 Document load | `app.document.load()` 将 Project 快照作为活动 Document 事务恢复,清理旧重算/Shape/Task 状态并保留 Undo 边界;Projects 页面已调用该 Facade API | 保存→修改→加载后属性恢复、选择清空、任务清空测试通过 | FCStd 导入仍是只读 metadata/proxy;跨文档 ShapeHandle 和恢复后自动几何重算仍待完成 | +| PART-07 Shape check | `check-shape` 使用 Facade `geometry.topology()` 生成面/边/顶点数量和 `SHAPE_CHECK_PASSED` 信息诊断;空缓存、空拓扑和 Worker 错误分别返回结构化失败诊断 | 无 Shape、错误选择前置和拓扑调用边界通过测试/构建 | 当前是网格派生拓扑检查,不等价于 OCCT `BRepCheck_Analyzer`,完整修复建议和自交诊断仍待实现 | | P1-02 命令准确性 | manifest 仍可完整展示;Part `primitive/union/cut/intersection` 与 Part Design 核心命令已有 Facade executor;命令状态同时检查工作台和对象类型选择谓词;其余未接入 executor 的命令返回 disabled 与 `COMMAND_UNIMPLEMENTED`,不再静默完成 | Part 基本体/布尔 Shape 缓存、选择不兼容时的禁用原因、`linear-pattern` 状态与诊断测试通过 | 其余工作台要逐项增加真实 executor、Task 参数和黄金回放后才能启用 | 后续按以下顺序连续开发: diff --git a/src/facade/mockFacade.ts b/src/facade/mockFacade.ts index 0d90acb..f29acc1 100644 --- a/src/facade/mockFacade.ts +++ b/src/facade/mockFacade.ts @@ -557,9 +557,14 @@ export function createMockFacade(): BitBybitWebCadFacade { notify(message) } if (!shape) reportFailure('SHAPE_NOT_RECOMPUTED', `No valid Shape is cached for ${objectId}; recompute the document first.`) - else void geometryRuntime.mesh(shape, 0.05).then((mesh) => { - if (mesh.indices.length === 0 || mesh.positions.length === 0) throw new Error('Shape mesh is empty.') - notify(`Shape check passed: ${mesh.subshapes?.length || 0} subshapes`) + else void geometryRuntime.topology(shape, 0.05).then((topology) => { + const count = topology.faces.length + topology.edges.length + topology.vertices.length + if (count === 0) throw new Error('Shape topology is empty.') + const message = `Shape check passed: ${topology.faces.length} faces, ${topology.edges.length} edges, ${topology.vertices.length} vertices` + const diagnostic: Diagnostic = { id: `diag-${++requestSequence}`, severity: 'info', code: 'SHAPE_CHECK_PASSED', message, objectId, requestId } + state = { ...state, diagnostics: [...state.diagnostics, diagnostic] } + emit({ type: 'diagnostic.added', diagnostic, context }) + notify(message) }).catch((error: unknown) => reportFailure('SHAPE_CHECK_FAILED', error instanceof Error ? error.message : String(error))) } else if (commandId === 'solve-sketch') { solveSketchObject(state.selectedObjectId); notify('Sketch solver completed') }