Files
cadSet/SimpleCADAPI/docs/architecture/sdk-architecture-review.md
T

366 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SimpleCADAPI SDK 架构评价报告
日期:2026-05-21
视角:函数式风格 programmer + CAD 软件架构师
前提:本报告认同并保留 SimpleCADAPI 的顶层 API 风格。`make_*_r*``*_rsolid``*_rwire``GraphSession``export_model_json` 这一套方向不是问题本身,反而是这个 SDK 最有价值的部分。真正的问题在于:内部实现还没有完全配得上这个顶层风格所暗示的稳定性、可组合性和可重放性。
## 总结判断
SimpleCADAPI 的顶层 API 选择是正确的:它没有把用户拖进传统 CAD kernel 的继承层级和 builder 生命周期,而是用一组接近纯函数的建模函数表达几何变换。这是适合 Python、适合自动化、适合 LLM/脚本生成、也适合未来 GUI 低代码编排的 API 形态。
但当前架构的真实状态是:顶层像函数式 CAD DSL,底层仍是“带全局上下文、带隐藏运行时元数据、带 OCC 对象身份、带启发式 replay”的工程拼装体。它能跑通大量示例,但还没有形成足够硬的 CAD SDK 契约。最危险的地方不是 primitive 建模能力,而是拓扑命名、选择语义、replay 严格性、装配求解、schema 稳定性和测试强度。
如果用一句话评价:这是一个有正确 API 直觉的 OCP-native CAD SDK 原型,已经越过玩具阶段,但还没有越过“可靠参数化 CAD 平台”的门槛。
## 评价边界
本报告基于当前仓库源码、文档、示例和测试,不评价尚未落地的设想。
主要检查对象:
- `src/simplecadapi/__init__.py`
- `src/simplecadapi/core.py`
- `src/simplecadapi/operations.py`
- `src/simplecadapi/graph.py`
- `src/simplecadapi/serializer.py`
- `src/simplecadapi/topology.py`
- `src/simplecadapi/tracking.py`
- `src/simplecadapi/ql.py`
- `src/simplecadapi/tagging.py`
- historical assembly/constraint implementation, now removed from the active public/support surface
- historical scalar-field/SDF implementation, now removed from the active public/support surface
- `docs/core/serialization/`
- `docs/core/operation_graph_json_spec.md`
- retained replayable examples under `examples/`
- `test/``tests/`
## 顶层 API 评价
顶层 API 的方向值得保留。
`make_box_rsolid(...)``make_line_redge(...)``extrude_rsolid(...)``union_rsolid(...)` 这种命名确实有点“硬”,但它有一个重要优点:它把 CAD 操作建模成从参数到几何值的映射,返回类型在名字里显式出现。对脚本、生成式建模、图记录、schema 生成和 IDE 补全来说,这比传统 OOP fluent builder 更稳定。
强项:
- `__init__.py` 明确导出建模函数、核心类型、graph/session、serializer、expression 和 `ql` 子模块,公开面可见性好。参考:`src/simplecadapi/__init__.py`
- 大多数核心创建函数遵守返回类型后缀,例如 `make_point_rvertex -> Vertex``make_line_redge -> Edge``make_circle_rwire -> Wire``make_box_rsolid -> Solid`。参考:`src/simplecadapi/operations.py:691``src/simplecadapi/operations.py:723``src/simplecadapi/operations.py:1465`
- composite convenience API 在 `GraphSession` 内降低为 canonical low-level graph,这个设计非常正确。用户调用 `make_box_rsolid`,图里记录 rectangle/profile/extrude,这是 CAD DSL 和 replay IR 分层的正确方式。参考:`src/simplecadapi/operations.py``test/test_serialization.py`
- `union_rsolid(...)` 明确返回单个 `Solid`,失败就报错,不默默返回多个实体。这对机械 CAD 是正确的默认语义。参考:`src/simplecadapi/operations.py:2856``src/simplecadapi/operations.py:2876`
- `GraphSession` + `export_model_json` + `replay_model_json` 的主线是对的。它把脚本建模提升为可交换、可 replay 的模型记录。参考:`src/simplecadapi/graph.py:44``src/simplecadapi/serializer.py:416``src/simplecadapi/serializer.py:732`
- `ql` 被放到子模块而非全部塞进顶层,是正确的边界意识。参考:`src/simplecadapi/__init__.py``docs/api/README.md:5`
问题:
- 顶层 API 被 aliases 稀释了。`create_box``create_line``extrude``union``to_step` 等别名降低了 canonical API 的唯一性。对用户友好,但对文档、LLM、图记录和长期兼容不友好。参考:`src/simplecadapi/__init__.py:145``src/simplecadapi/__init__.py:318`
- README 说 API 函数统一用 return type 反映命名,但公开面包含 `translate_shape``rotate_shape``export_step``export_stl` 和大量 aliases。这个声明过强。参考:`README.md:200``src/simplecadapi/__init__.py:47``src/simplecadapi/__init__.py:187`
- `rsolidlist` 命名混杂了“参数类型”和“返回类型”:`cut_rsolid``intersect_rsolid` 实际返回 `Solid`,而 `linear_pattern_rsolidlist` 返回 `List[Solid]`。这会伤害 API 语义一致性。参考:`src/simplecadapi/operations.py:2994``src/simplecadapi/operations.py:3102``src/simplecadapi/operations.py:4121`
- public op 名和 graph op 名不完全一致:`translate_shape` 记录为 `make_translate_rshape``extrude_rsolid` 记录为 `make_extrude_rsolid`。这种分层可以接受,但必须正式化为“source API”和“canonical IR”的稳定映射表。参考:`src/simplecadapi/operations.py:117``src/simplecadapi/serializer.py:116`
- 一些非 session 分支仍记录 legacy op 名,如 `make_box``make_cylinder``make_segment_wire`,和 canonical `make_*_r*` 命名不一致。参考:`src/simplecadapi/operations.py:785``src/simplecadapi/operations.py:1534``src/simplecadapi/operations.py:1638`
建议:保留 `make_*_r*`,但把 aliases 降级为 compatibility surface,不作为文档主路径;把 source API 到 canonical graph op 的映射写成稳定规范;把 `rsolidlist` 这种历史命名列入 3.0 前清理清单。
## 函数式架构评价
顶层是函数式的,底层不是。
从函数式 programmer 的角度看,这套 SDK 最好的设计是“操作返回新几何值,而不是用户手动驱动 mutable builder”。但当前实现中,几何值本身携带大量隐藏可变状态:tags、metadata、runtime graph node、topo refs、track summary。这不是不能接受,但必须承认它是 effect system,而不是纯函数系统。
可取之处:
- `operations.py``_finalize_primitive_shape``_finalize_derived_shape``_finalize_tracked_solid` 把 graph recording、semantic delta、tracking summary 收敛到少数 finalizer。这个方向正确。参考:`src/simplecadapi/operations.py:597``src/simplecadapi/operations.py:629``src/simplecadapi/operations.py:650`
-`ScalarField` 尝试用 frozen dataclass 表达隐式建模 AST;该 surface 已移除,后续如恢复需先统一 SDF 语义。
- `ShapeSelector``SerializablePredicate``SerializableKey` 都是 dataclass/frozen 风格,QL 作为可序列化 AST 的方向正确。参考:`src/simplecadapi/ql.py:258``src/simplecadapi/ql.py:320``src/simplecadapi/ql.py:396`
问题:
- `GraphSession` 使用模块级 `_active_session``_recording_suspend_depth`。嵌套 session、并发 session、异步建模都会互相污染。参考:`src/simplecadapi/graph.py:40``src/simplecadapi/graph.py:69``src/simplecadapi/graph.py:91`
- `SimpleWorkplane` 使用模块级 `_current_cs` 栈,和 `GraphSession` 一样是隐式全局 effect。参考:`src/simplecadapi/core.py:276``src/simplecadapi/core.py:298`
- `attach_graph_node` 会把 graph node、node id、output slot、topo ref 写入 shape runtime/metadata。这个副作用是设计核心,但它破坏了“同样输入得到同样纯值”的直觉。参考:`src/simplecadapi/graph.py:231``src/simplecadapi/graph.py:247``src/simplecadapi/graph.py:259`
- tagging 已收敛到 `apply_tag(shape, tag)` / `list_tags(shape)` 的 functional public surface;底层仍然是对 shape tag store 的受控 mutation,需要在 docs 中明确这是 controlled tagging effect。
- `GraphSession` 记录了 context/frame,但 replay 不使用 context 重建 workplane。这个会让“记录的是建模意图”的函数式承诺打折。参考:`src/simplecadapi/graph.py:132``src/simplecadapi/topology.py:338``src/simplecadapi/serializer.py:991`
函数式层面的根本建议:把 `GraphSession`、workplane、tag/metadata/runtime 都明确视为 effect context,并用 `contextvars` 或显式 session 参数隔离;公开文档不要暗示全纯函数,而要说“functional surface with controlled recording/tagging effects”。
## CAD Kernel 边界评价
这套 SDK 是 OCP-native,不是 kernel-agnostic。这一点没有问题,但必须讲清楚。
优点:
- wrapper 很薄,直接包 OCP TopoDS,性能和能力不会被中间抽象拖死。参考:`src/simplecadapi/core.py:422``src/simplecadapi/core.py:465``src/simplecadapi/core.py:597``src/simplecadapi/core.py:713`
- `kernel/` 目录已经把 builders、booleans、curves、features、mesh、properties、topology、transforms、export 分层出来,比把所有 OCP 调用塞到 `operations.py` 更可维护。参考:`src/simplecadapi/kernel/ocp_booleans.py:1``src/simplecadapi/kernel/ocp_features.py``src/simplecadapi/kernel/ocp_transforms.py`
- tests 已经明确防止 CadQuery 依赖回流,这是好边界。参考:`tests/test_ocp_core_no_cadquery.py:9`
问题:
- `.wrapped` 是公开事实,docs 也承认 public geometry wrappers expose `.wrapped`。这不是错,但这意味着 SDK 没有真正封装 kernel,只是提供了 OCP-native convenience layer。参考:`docs/core/README.md:5``src/simplecadapi/core.py:427``src/simplecadapi/core.py:718`
- QL 内部直接用 OCP adaptor 判断 curve/surface type。这个实现合理,但进一步证明 QL 当前绑定 OCP 语义,而不是中立 CAD 语义。参考:`src/simplecadapi/ql.py:193`
- 旧 constraints 实现直接用 OCP transform 重建 solid,装配层和 kernel 层耦合很深;该 public/support surface 已移除,等待新契约重做。
- `operations.py` 仍然直接 import OCP builder 类型并手写部分 kernel 操作。kernel adapter 层还没有完全收敛。参考:`src/simplecadapi/operations.py:14``src/simplecadapi/operations.py:17`
建议:不要假装 kernel-agnostic。短期应该明确定位为 OCP-native functional SDK。中期把 `operations.py` 中剩余 OCP 直接调用继续下沉到 `kernel/`,让 public ops 只做参数验证、effect finalization 和 semantic recording。
## 拓扑身份与命名
这是当前最核心的 CAD 风险。
CAD SDK 真正难的不是画 box、cylinder、extrude,而是模型变化后还能稳定地说“我要这个孔的圆边”“我要这个安装面”。当前 SimpleCADAPI 已经意识到这个问题,但实现还没有解决它。
已有基础:
- `_TopologyEntityCache` 用 OCC `HashCode` bucket 加 `IsSame` 做同一 shape occurrence 的合并,能让一个 box 的 shared edge occurrences 共享 topo id。参考:`src/simplecadapi/core.py:42``tests/test_topology_identity.py:16`
- `TopoRef``TopoDelta``TopoEntry``SemanticDelta` 数据模型已经存在,说明架构上知道需要 graph-aware topology references。参考:`src/simplecadapi/topology.py:60``src/simplecadapi/topology.py:203``src/simplecadapi/topology.py:223`
- `tracking.py` 直接查询 OCC builder 的 `Modified()``Generated()``IsDeleted()``SectionEdges()`,方向正确。参考:`src/simplecadapi/tracking.py:1``src/simplecadapi/tracking.py:107`
硬伤:
- `TopoRef.topo_id` 文档自己承认 implementation-defined,可能是 sequential integer、hash 等。这不够 durable。参考:`src/simplecadapi/topology.py:69`
- `_safe_shape_hash` 使用 `HashCode(1000000)`fallback 是 `hash(shape)`。这只能做运行期/构建期身份辅助,不能做跨 replay 稳定身份。参考:`src/simplecadapi/core.py:22`
- `TaggedMixin.topo_id` 在无 entity 时 fallback 到 `id(self)`,这是进程内对象身份,不是 CAD 拓扑身份。参考:`src/simplecadapi/core.py:321`
- `tracking._topo_id` 也使用 OCC hash,并且注释叫 “Stable-ish”。这句话本身就是风险说明。参考:`src/simplecadapi/tracking.py:80`
- `TopoEntry` 支持 richer lineage,但 `tracking._build_boolean_result` 只把 rich entries 放进 `delta_entries` dict,没有填入 `TopoDelta.entries`。导出时 `export_model_json` 只序列化 `node.topo_delta`,丰富 lineage 没有成为 durable graph contract。参考:`src/simplecadapi/tracking.py:283``src/simplecadapi/tracking.py:291``src/simplecadapi/serializer.py:480`
- boolean、fillet、chamfer、shell 后的 topology tracking 是有用的,但不是 stable topology naming system。它记录了 OCC 的历史回答,不等于建立了语义身份。
结论:当前拓扑身份适合调试、局部 selection replay 和轻量 semantic tagging,不适合作为长期参数化模型的核心契约。必须尽快把 QL/SelectionSpec 作为 primary identity,把 raw topo refs 降级为 fallback。
## QL 与选择语义
QL 是架构上最应该继续投资的部分。
好处:
- QL 已经有 serializable predicate、serializable key、selector、order、limit、exactly cardinality。参考:`src/simplecadapi/ql.py:258``src/simplecadapi/ql.py:320``src/simplecadapi/ql.py:396`
- QL 支持 tag/meta/property/curve_type/surface_type/normal/center/length/area/volume 等基础语义。参考:`src/simplecadapi/ql.py:77``src/simplecadapi/ql.py:94``src/simplecadapi/ql.py:125`
- fillet/chamfer/shell 现在会把 QL selector 的运行时结果编译为 `make_select_*` geo select nodes`selection_query` 仅保留为旧 payload fallback。参考:`test/test_serialization.py:443``src/simplecadapi/serializer.py:1365`
问题:
- QL graph scope 现在通过 `make_select_*` 节点的 source input 表达,而不是只靠 query AST;未来还需要把这个契约扩展到非 detail-feature 的通用 selection 工作流。
- 当前 replay resolution order 已经变为 geo select nodes -> selection_query fallback -> topo refs -> indices -> hint;后续风险主要是外部 adapter 需要跟进新顺序。
- 只有 `exactly(n)`,没有 `at_least``at_most`cardinality 还不够表达真实 CAD 选择约束。参考:`src/simplecadapi/ql.py:457`
- ordering 只有单 key,不支持 lexicographic multi-key。复杂 CAD 选择里单 key 很容易产生不稳定 tie。参考:`src/simplecadapi/ql.py:429`
- `_shape_identity` 仍然优先用 topo id 或 topo_ref metadata,说明 QL 运行时仍被当前 topology identity 牵引。参考:`src/simplecadapi/ql.py:566`
建议:把 `SelectionSpec` 做成正式 schemasource scope、query AST、多 key order、limit、cardinality、fallback。所有接受 `List[Edge]``List[Face]` 的 feature API,在 GraphSession 内都应该 normalize 成 SelectionSpec。replay 必须 QL firsttopo refs/indices/hints 只能 fallback。
## Graph、Serialization 与 Replay
这是 SDK 的战略性资产,也是当前最需要变硬的契约。
做得好的地方:
- `OperationGraph` 是 DAG,节点保存 op、params、param_exprs、inputs、context、semantic_delta、topo_delta、tags。结构完整。参考:`src/simplecadapi/topology.py:318``src/simplecadapi/topology.py:476`
- `export_model_json` 输出 graph、leaf_ids、expression_graph、frame_graph、registries、delta logs、canonical contract。方向很对。参考:`src/simplecadapi/serializer.py:627`
- `_assert_graph_is_canonical` 在 export model 前拒绝非 canonical ops,这是保持 IR 干净的正确动作。参考:`src/simplecadapi/serializer.py:246``src/simplecadapi/serializer.py:624`
- `test/test_serialization.py` 系统覆盖 source API 到 operation tree 的映射,是该契约的主要回归入口。
严重问题:
- replay 过于宽松。多个 replay 分支在输入缺失时 `continue`,最终 leaf collection 用 `outputs.get(node_id, [])`。坏图可能静默变成少输出或空输出。参考:`src/simplecadapi/serializer.py:1018``src/simplecadapi/serializer.py:1051``src/simplecadapi/serializer.py:1294`
- primitive replay 使用 permissive defaults。缺少 `x` 就默认为 0,缺少 radius 就默认为 1。这会把坏 JSON 变成错误几何,而不是报错。参考:`src/simplecadapi/serializer.py:767`
- `OperationGraph.from_dict` 对 edges 和 inputs 的缺失引用采取静默跳过。对 interchange schema 来说这太软。参考:`src/simplecadapi/topology.py:692``src/simplecadapi/topology.py:701`
- graph schema version 是 `1.0`model schema 是 `2.0-draft`canonical contract 是 `2.0-final-state`。这三个版本信号混在一起,会让外部消费者不知道哪个才是稳定承诺。参考:`src/simplecadapi/topology.py:19``src/simplecadapi/serializer.py:221``src/simplecadapi/serializer.py:628`
- node.context 和 frame_graph 被记录,但 replay 不恢复 frame/workplane;所有 ops 按当前 ambient world context 执行。参考:`src/simplecadapi/graph.py:132``src/simplecadapi/serializer.py:991`
- `leaf_ids` 自动来自 graph leaves,而不是用户显式声明的 final outputs。debug/intermediate 独立节点会成为 replay 输出。参考:`src/simplecadapi/serializer.py:624``src/simplecadapi/topology.py:563`
- expression replay 是 numeric snapshot,不是 parametric replay。这个可以接受,但必须在 SDK 主文档里反复强调。参考:`test/test_serialization.py`
建议:replay 应该默认 strict。所有 missing input、missing param、unknown op、leaf missing output、selection cardinality mismatch 都应该 hard failure。需要宽松模式时显式 `strict=False`
## Boolean 与 Feature Tracking
当前 boolean API 的用户语义比很多 CAD wrapper 都清楚,但 tracking 和 replay 还不够可信。
优点:
- `union_rsolid` flatten nested inputs,使用 scale-aware fuzzy tolerance,支持 glue,默认 clean,并强制单 solid。参考:`src/simplecadapi/operations.py:221``src/simplecadapi/operations.py:2856``src/simplecadapi/kernel/ocp_booleans.py:40`
- `fuse_shapes` 使用 OCC Fuse、parallel、OBB、fuzzy value、GlueShift 和 same-domain cleanup,基本工程判断正确。参考:`src/simplecadapi/kernel/ocp_booleans.py:45`
- `tracked_cut``tracked_union``tracked_intersect` 使用 OCC history,这比只比较结果 faces 靠谱。参考:`src/simplecadapi/tracking.py:308``src/simplecadapi/tracking.py:338``src/simplecadapi/tracking.py:374`
问题:
- `tracked_union` 接收 `glue``tol`,但没有把它们设置到 `BRepAlgoAPI_Fuse`。也就是说用于结果的 union 和用于 tracking 的 union 参数可能不一致。参考:`src/simplecadapi/tracking.py:338``src/simplecadapi/tracking.py:352`
- `union_rsolid` replay 忽略记录过的 `clean``glue``tol`,只调用 `ops.union_rsolid(all_solids)`。参数不 faithful。参考:`src/simplecadapi/operations.py:2946``src/simplecadapi/serializer.py:1029`
- `cut_rsolid` 对无交集 tool 直接跳过,这对 modeling convenience 可能友好,但对 replay/diagnostic 来说可能隐藏错误。参考:`src/simplecadapi/operations.py:3033`
- 多 tool cut/intersect 只保留最后一次 delta,前面 tool 的 lineage 丢失。参考:`src/simplecadapi/operations.py:3020``src/simplecadapi/operations.py:3127`
- extrude face classification 使用 center 精确相等、normal dot 精确等于 0、angle 精确等于 pi,这在 CAD 数值几何里非常脆弱。参考:`src/simplecadapi/operations.py:2631``src/simplecadapi/operations.py:2646``src/simplecadapi/operations.py:2653`
建议:boolean result 和 boolean tracking 必须用同一 builder 或至少同一参数配置;replay 必须重放 boolean paramsfeature tagging 不应靠 exact float equality,应使用 tolerance 和 topology history。
## Tagging 与 Metadata
tagging 的目标是对的,但现在新旧体系混在一起。
优点:
- `tagging.py` 定义了 normalized tag grammar 和 `TagPolicy`,这是 QL 和 anchor semantics 的基础。参考:`src/simplecadapi/tagging.py:8``src/simplecadapi/tagging.py:58`
- `apply_tag(shape, tag)` / `list_tags(shape)` 是唯一推荐的 public tagging surface`list_tags` 输出稳定排序。
- 当前约定是不在 tags 中存数字,而是在 metadata[`geo`] 中保存结构化几何事实。
问题:
- QL、anchor lookup、auto tagging、metadata[`geo`] 之间还需要一个正式 schema 文档把它们串起来。
- tagging 仍然是 shape-local side effectgraph/session replay 文档应继续明确哪些 tags 会进入 serialized selector hints。
建议:建立 tag schema version。新代码只产生 normalized tags,旧 tags 标成 `legacy.*` 或仅作为 lookup fallback。
## Assembly 与 Constraints
旧装配 API 的顶层形态有可取之处,但 solver 只是 MVP;该 surface 已移除,等待基于新 assembly IR / constraint graph 重做。
优点:
- assembly 层和 geometry construction 分开,这是正确的 CAD 架构。geometry API 做 integrated parts,未来 assembly constraints 做最终空间关系。
- anchor/part handle 这类表达形式可以作为新设计参考,但不能直接恢复旧实现。
- solved snapshot 的函数式方向可以保留,但需要新的 solver contract 支撑。
问题:
- 旧 solver 是顺序应用约束的迭代 pose adjustment,不是 DOF solver、不是约束图求解器、没有 Jacobian/least-squares、没有 overconstraint/underconstraint 严格诊断。
- 旧 diagnostics 主要只有 non-convergence 和 unconstrained parts。对真实机械装配不够。
- 旧 bbox anchor 基于 local AABB,旋转或复杂形体下语义会很粗糙。
-`export_model_json` assembly frame 摘要不完整,因此已从当前 schema 中撤回。
建议:把当前 assembly 文档称为 “layout constraints” 或 “MVP rigid pose helper”,不要称成熟 assembly solver。下一步优先修 export frame rotation,然后再谈 DOF solver。
## Scalar Field 与 Implicit Modeling
旧 scalar field/SDF surface 已移除;下面记录的是移除前的历史评审结论。
优点:
-`ScalarField` tree 曾尝试做可序列化/replay 的隐式建模表达。
-`make_field_surface_rsolid` 曾区分 replayable scalar field tree 和 opaque callable。
问题:
- sphere 返回平方距离减 `r^2`box/capsule 接近 signed distance。smooth union/subtract 假设 field value 单位可比,但这里不成立。
- `scale` 使用 min scale 乘回 field value,对非 SDF field 更不稳定。
- 这意味着 implicit smooth operations 目前更像 demo/experimental,而不是可靠 SDF modeling kernel。
建议:明确 `ScalarField` 是 algebraic implicit field 还是 true SDF。若要支持 smooth booleans,必须统一到 SDF 或至少在每个 primitive 标记 value metric,并拒绝不兼容 smooth blend。
## 错误模型与 Developer Experience
优点:
- public operations 大量使用 `raise_harness_error` 包装上下文,错误信息对脚本用户友好。参考:`src/simplecadapi/operations.py:204`
- import/replay 的错误也有 what happened / possible causes / how to fix,这对 SDK 很有价值。参考:`src/simplecadapi/serializer.py:345``src/simplecadapi/serializer.py:743`
问题:
- 一些地方直接 `print` warning,而不是走 warning/error policy。参考:`src/simplecadapi/core.py:787``src/simplecadapi/core.py:808`
- 一些 tracking 异常被吞掉,例如 union tracking 失败就继续记录无 delta。这对 robustness 友好,但对 auditability 很差。参考:`src/simplecadapi/operations.py:2930`
- replay 对 malformed graph 静默跳过比报错更危险。参考:`src/simplecadapi/serializer.py:1018`
建议:区分 user modeling error、kernel failure、replay schema error、tracking degraded warning。replay/schema 默认必须严格。
## 测试体系评价
测试覆盖了很多 API,但 CAD robustness 测试还不够狠。
已有价值:
- 有 no-CadQuery boundary test。参考:`tests/test_ocp_core_no_cadquery.py:9`
- 有 topology occurrence identity test。参考:`tests/test_topology_identity.py:16`
- 有 graph serialization/replay tests,包括 QL selector replay、mirror、sweep、pattern 等。参考:`test/test_serialization.py:233``test/test_serialization.py:420``test/test_serialization.py:491`
不足:
- 一些断言是无效断言,如 `len(modified) >= 0``len(tagged) >= 0`,这永远成立。参考:`test/test_original_api_integration.py:23``test/test_original_api_integration.py:50`
- topology identity 测试只证明单个 box 内 shared edge occurrence,不证明跨 operation、跨 replay、跨 boolean 的 durable identity。参考:`tests/test_topology_identity.py:16`
- replay tests 多数验证 happy path volume,不够验证 malformed graph、missing inputs、schema rejection、workplane context、boolean params faithful、selection ambiguity。
- assembly tests没有证明 overconstraint/underconstraint、DOF、rotation frame export 的正确性。
建议优先加这些测试:
- GraphSession nested/concurrent isolation。
- SimpleWorkplane 内记录后在 world context replay,结果必须一致。
- replay missing param 必须报错,不允许默认生成几何。
- replay missing input 必须报错。
- union clean/glue/tol 参数 replay faithful。
- QL selection ambiguity 必须按 cardinality 报错。
- boolean tracking result 和 actual result 用同一参数。
- assembly frame export 必须保留旋转轴。
- scalar field smooth union 拒绝混合非同量纲 field 或统一 SDF。
## 最重要的架构矛盾
SimpleCADAPI 当前最大的矛盾不是“函数式 API vs OOP core”。薄 wrapper core 可以接受。
真正的矛盾是:顶层 API 和 v2 model JSON 暗示了稳定、可 replay、可交换的 CAD DSL,但底层仍依赖隐式全局状态、runtime metadata、OCC hash、fallback indices、permissive defaults 和非严格 replay。
这会带来一个危险结果:demo 看起来很强,复杂模型一旦进入参数变化、拓扑变化、跨进程 replay、GUI roundtrip、自动生成代码迭代,就会出现“没报错但选错面/少输出/变成不同模型”的问题。对 CAD 来说,静默错误比显式失败更糟。
## 优先级建议
P0:把 replay 变 strict。
- 缺参数直接报错。
- 缺输入直接报错。
- leaf id 没 output 直接报错。
- unknown op 直接报错。
- selection cardinality mismatch 直接报错。
- 保留 permissive replay 也必须显式 opt-in。
P0:修 GraphSession 和 workplane context。
- `_active_session` 改成 `contextvars.ContextVar` 或 session stack。
- `_recording_suspend_depth` 同样 context-local。
- `_current_cs` 至少 context-local,最好 frame 显式进入 replay。
- replay 必须使用 node.context 或 frame graph 重建坐标上下文。
P0:把 QL/SelectionSpec 升为 canonical selection。
- SelectionSpec 加 source node/output slot。
- replay 改成 QL first。
- topo refs、indices、hints 只做 fallback。
- 支持 multi-key order。
- 支持 `at_least``at_most`
P1:统一 canonical op 和 schema version。
- 明确 source API、canonical graph op、model JSON schema 三层。
- 移除或隔离 legacy op names。
- `2.0-draft``2.0-final-state` 不要混用。
- canonical op coverage 变成测试强约束。
P1:修 boolean/tracking/replay faithful。
- tracking builder 和 actual boolean builder 使用相同参数。
- union replay 传回 `clean``glue``tol`
- multi-tool cut/intersect 保留完整 delta chain。
- 无交集 tool 是否跳过应由参数控制,Graph replay 默认不跳过。
P1:收敛 tag schema。
- normalized tags 成为默认。
- legacy numeric tags 移出 tags,放 metadata[`geo`]。
- anchor lookup 明确 role > anchor > face/edge/wire > legacy。
- QL docs 使用 normalized tags。
P2:装配系统诚实命名并逐步增强。
- 当前称 layout solver,不称完整 assembly solver。
- 先修 frame export rotation。
- 再做 DOF/overconstraint diagnostics。
P2scalar field 数学语义统一。
- 决定是否承诺 SDF。
- smooth booleans 只允许可比 distance fields。
- surface extraction 对 closure/validity 给硬失败或明确 warning。
## 最终评价
这套 SDK 最值得肯定的是 API 直觉:用函数表达 CAD 操作,用返回类型后缀表达几何类型,用 GraphSession 捕获建模过程,用 model JSON 做 replay 边界。这条路线是对的,不应该推翻。
最需要批评的是工程契约:它现在过于相信 OCC runtime identity,过于容忍 replay 缺失,过于依赖 hidden mutable metadata,过早把 draft schema 包装成 canonical contract,过少用失败来保护用户。
CAD 软件架构里,能建出模型只是第一层;能在参数变化后稳定找到同一个语义面,能跨进程 replay 出同一个设计,能在选择歧义时拒绝继续,能在 schema 损坏时报错,才是 SDK 级别的可靠性。
SimpleCADAPI 已经有正确的外形和几块关键骨架。下一阶段不应该继续堆更多 primitive,而应该把 graph、selection、topology、schema、test 这些基础层做硬。否则 API 越漂亮,用户越容易误以为它已经是稳定 CAD 平台。
最终判断:保留顶层 API,收紧内部契约;少加功能,多加确定性;少相信对象身份,多表达语义选择;少 silent fallback,多 fail fast。