本文档定义 SimpleCADAPI 当前用于导出、交换、replay、以及外部转译适配的 JSON 规范。
目标读者:
- 需要消费
export_graph_json()/export_model_json()输出的适配器开发者 - 需要把当前 JSON 图转译到其他 CAD / DSL / workflow 系统的实现者
- 需要确认字段语义、版本边界、以及节点参数格式的集成方
本文档描述的是当前实现行为,而不是理想化设计稿。外部适配请以本文档和当前测试为准。
当前存在两类相关 JSON 载荷:
-
graph JSON- 由
export_graph_json(graph)导出 - 表示 API 级操作图
schema_version当前为2.0- 适合轻量 roundtrip、节点级检查、简单 replay
- 由
-
model JSON- 由
export_model_json(session)导出 - 表示完整 2.0 interchange payload
schema_version当前为2.0- 包含
graph、leaf_ids、expression_graph、frame_graph、registry/log 等附加信息 - 是当前对外推荐的 interchange / replay 边界
- 由
- 导出版本:
schema_version = "2.0" - 导入兼容规则:
import_graph_json()仅接受2.x - 也就是说,适配器应把 graph payload 视为
2.x系列 schema
- 导出版本:
schema_version = "2.0" - 导入兼容规则:
import_model_json()仅接受 exactly2.0 - 当前 canonical contract 版本:
canonical_contract.contract_version = "2.0"
SimpleCADAPI v2 明确区分三层契约:
| Layer | Boundary | Stability Rule |
|---|---|---|
| source API | Python public functions such as make_box_rsolid(...), union_rsolid(...), fillet_rsolid(...) |
用户调用层;可以是 convenience API 或 macro API |
| canonical graph op | graph.nodes[].op values inside graph/model JSON |
interchange/replay 层;必须来自 frozen canonical op set |
| model JSON schema | top-level model payload with schema_version = "2.0" and canonical_contract.contract_version = "2.0" |
payload envelope 层;不得混用 draft/final-state version strings |
source API 名字不等于 canonical graph op 名字。Composite source API 可以展开为多个 canonical graph ops,例如 make_box_rsolid(...) 展开为 rectangle/face/extrude chain;model JSON schema 只规定 payload envelope 和 contract metadata。
producer_version是 Python package 版本号- 它用于调试和排查,不应用作 schema 判断依据
graph 是唯一真相源的 canonical low-level graph。
特点:
- 必须限制在冻结的 canonical op set 内
- 不能泄漏 convenience-only 或 macro-only op
- composite builtin 可以内部构造表达式和 low-level 调用,但最终写入图中的节点只能是 low-level op
- FreeCAD translator 可以对安全的单消费者 transform 做 lowering 优化,例如把
make_translate_rshape折叠进上游Part::Extrusion.Placement,但 canonical graph 节点仍然保留,并在.FCStd对象的SimpleCADFoldedOps中记录 evidence
典型结果:
make_box-> rectangle face chain +extrudemake_circle_rface->make_circle_redge+make_wire_from_edges_rwire+make_face_from_wire_rfacelinear_pattern-> 多个显式translateradial_pattern-> 多个显式rotatehelical_sweep不会作为独立 core node 出现
leaf_ids 是最终结果集的显式 node id 列表。
规则:
- 多输出场景不能依赖
graph.leaf_nodes()猜测最终结果 leaf_ids是模型导出时的显式返回集合
expression_graph 记录参数表达式 DAG。
规则:
params中保存的是数值快照- 若某参数来自
var(...)/Expr,则param_exprs中会保存到 expression node 的引用 - 适配器如果只做纯几何转译,可以只消费
params - 适配器如果需要恢复参数化关系,应同时消费
param_exprs + expression_graph
frame_graph 记录每个 operation node 在记录时的坐标系快照。
规则:
- 每个记录节点通常对应一个
frame:<node_id>frame node.context保存该节点记录时已经完整合成的绝对工作平面;嵌套父帧不会在 replay 时再次相乘- replay 对每个节点进入其
node.context,再用节点中的局部点和向量参数重建几何 frame_graph提供同一坐标快照的显式注册表,外部转译器也可用它恢复局部工作坐标语义
export_graph_json() 的顶层对象结构如下:
{
"schema_version": "2.0",
"producer_version": "2.0.4b1",
"capabilities": {
"selection_ref_strategies": true,
"geo_select_nodes": true,
"selector_hint_fallback": true,
"display_payload": true,
"sketch_constraints": true,
"sketch_solve_snapshots": true,
"topology_delta_summary": false,
"assembly_graph": false,
"scalar_field_graph": false,
"expression_graph": true,
"dimension_tolerances": true
},
"graph_id": "graph_xxxxxxxx",
"nodes": [...],
"edges": [["src_node_id", "dst_node_id"]]
}| Field | Type | Required | Meaning |
|---|---|---|---|
schema_version |
string |
yes | graph schema version, current 2.0 |
producer_version |
string |
yes | package version of exporter |
capabilities |
object |
yes | feature flags for downstream consumers |
graph_id |
string |
yes | unique graph instance id |
nodes |
array<object> |
yes | operation nodes in topological order |
edges |
array<[string,string]> |
yes | adjacency list, each entry is [src, dst] |
当前导出固定包含以下字段:
| Field | Type | Meaning |
|---|---|---|
selection_ref_strategies |
bool |
detail feature 支持显式 topo refs / index / query 等多种选择策略 |
geo_select_nodes |
bool |
detail selections from QL or indexed child-geometry getters can be serialized as make_select_* geo selector nodes |
selector_hint_fallback |
bool |
replay 时支持 selector hint 近似匹配回退 |
display_payload |
bool |
node 中包含 display 字段 |
sketch_constraints |
bool |
graph/model JSON 支持声明式 constrained sketch nodes |
sketch_solve_snapshots |
bool |
sketch promotion nodes carry solve evidence for strict replay validation |
topology_delta_summary |
bool |
当前为 false,表示没有额外 summary-only delta schema |
assembly_graph |
bool |
当前 graph JSON 本身不承载 assembly graph |
scalar_field_graph |
bool |
当前为 false;SDF / scalar field graph 暂时不在支持范围内 |
expression_graph |
bool |
session/model payload 支持 expression graph |
dimension_tolerances |
bool |
session/model payload supports variable tolerances and a tolerance requirement graph |
nodes[] 中每个元素是一个 operation node。
{
"node_id": "node_xxxxxxxx",
"op": "extrude",
"params": {...},
"inputs": ["node_a", "node_b"],
"output_count": 1,
"tags": [],
"display": {...},
"param_exprs": {...},
"context": {...},
"semantic_delta": {...},
"topo_delta": {...}
}| Field | Type | Required | Meaning |
|---|---|---|---|
node_id |
string |
yes | unique node id within graph |
op |
string |
yes | operation type |
params |
object |
yes | replay / interchange parameter payload |
inputs |
array<string> |
yes | upstream node ids in data-flow order |
output_count |
int |
yes | number of shape outputs produced by this node |
tags |
array<string> |
yes | normalized semantic labels attached at record time |
display |
object |
yes | UI-friendly derived summary; advisory only |
param_exprs |
object |
no | mapping from param name to expression references |
context |
object |
no | fully composed absolute workplane snapshot used while replaying this node |
semantic_delta |
object |
no | semantic entity delta |
topo_delta |
object |
no | topology lineage delta |
适配器应假设并维持以下不变量:
nodes按拓扑序输出inputs中的 node id 必须已在更早节点中出现edges与inputs表达同一依赖关系,二者必须一致output_count >= 1- 多输出节点的输出 slot 范围是
[0, output_count - 1]
display 是冗余的、面向 UI 的派生字段,不是 replay 所必需。
当前结构:
| Field | Type | Required | Meaning |
|---|---|---|---|
label |
string |
yes | 例如 Box、Fillet |
category |
string |
yes | primitive / profile / feature / boolean / detail / transform / pattern / operation |
summary |
string |
yes | 基于 params 自动生成的短摘要 |
selection_count |
int |
no | 当 selected_edges 或 selected_faces 存在时出现 |
消费建议:
- 可直接忽略
display - 不要把
display.summary当成结构化数据来源
当前 context 形状:
{
"origin": [0.0, 0.0, 0.0],
"x_axis": [1.0, 0.0, 0.0],
"y_axis": [0.0, 1.0, 0.0],
"z_axis": [0.0, 0.0, 1.0]
}字段含义:
origin: 当前局部坐标系原点x_axis: 当前局部 X 轴方向y_axis: 当前局部 Y 轴方向z_axis: 当前局部 Z 轴方向
params 保存已求值后的数值;param_exprs 保存哪些参数来自表达式图。
示例:
{
"params": {
"radius": 2.0
},
"param_exprs": {
"radius": {
"expr_id": "var_119b16e4"
}
}
}规则:
- 如果某个参数没有表达式来源,则
param_exprs中没有该键 - 计数和 legacy fallback 索引参数不会被表达式提升,例如:
selected_edge_indicesselected_face_indicescountedge_countprofile_countoutput_count
注意:
- 由于 canonicalize 规则,非离散标量通常会变成 JSON number,当前实现多数会导出成浮点数
- 即使原始输入是整数,也不要假设它一定以 integer 形式出现
semantic_delta 表示语义实体层面的变化。
结构:
{
"created": [SemanticRef, ...],
"modified": [SemanticRef, ...],
"deleted": [SemanticRef, ...],
"metadata": {...}
}{
"graph_id": "graph_xxx",
"node_id": "node_xxx",
"entity_type": "Feature",
"entity_id": "extrude:0"
}字段含义:
| Field | Type | Meaning |
|---|---|---|
graph_id |
string |
所属 graph id |
node_id |
string |
产生该语义实体的 node id |
entity_type |
string |
语义实体类型,如 Body / Feature / Sketch / Profile / Point / ShapeOutput |
entity_id |
string |
在 node 内部稳定的实体 id,通常是 <op>:<slot> |
当前实现的默认映射如下:
make_point_rvertex->Point- 草图/轮廓类节点 ->
Profile或Sketch - primitive solid / transform body ->
Body - feature / detail / boolean 类节点 ->
Feature - 无法分类时 ->
ShapeOutput
其中:
make_*_face与make_face_from_wire->Sketchmake_*_edge/make_*_wire->Profilemake_extrude_rsolid/make_revolve_rsolid/make_loft_rsolid/make_sweep_rsolid/make_twisted_sweep_rsolid/make_fillet_rsolid/make_chamfer_rsolid/make_shell_rsolid/make_cut_rsolid/make_union_rsolid/make_intersect_rsolid->Feature
topo_delta 表示子拓扑级别的 lineage 变化。
结构:
{
"preserved": [TopoRef, ...],
"modified": [TopoRef, ...],
"generated": [TopoRef, ...],
"deleted": [TopoRef, ...],
"section_edges": [TopoRef, ...],
"entries": [TopoEntry, ...],
"raw_event": {...}
}{
"graph_id": "graph_xxx",
"node_id": "node_xxx",
"output_slot": 0,
"kind": "EDGE",
"topo_id": "edge_640974"
}字段含义:
| Field | Type | Meaning |
|---|---|---|
graph_id |
string |
所属 graph id |
node_id |
string |
产生该 subshape 的 node id |
output_slot |
number |
对应 node 输出槽位;消费端应按整数解释 |
kind |
string |
VERTEX / EDGE / WIRE / FACE / SOLID |
topo_id |
string |
当前实现生成的 opaque subshape id |
注意:
output_slot在 node-level topo refs 中通常是整数- 但当
TopoRef被嵌入params.selected_edges[]等深层对象并经过参数 canonicalize 后,可能表现为0.0 - 消费端应将其按整数语义处理
当前 schema 支持 richer entry,但多数情况下 entries 为空数组。
结构:
{
"ref": TopoRef,
"event": "GENERATED",
"origin_role": "tool",
"parent_refs": [TopoRef, ...],
"metadata": {...}
}- 当前
raw_event为保留字段,通常为空对象 - 许多 primitive node 没有
topo_delta - 追踪型 feature / boolean node 更可能包含
topo_delta
detail feature 使用显式选择引用来稳定 replay。
model.json.canonical_contract.selection_ref_schema 当前固定声明:
{
"edge_param": "selected_edges",
"face_param": "selected_faces",
"edge_index_param": "selected_edge_indices",
"face_index_param": "selected_face_indices",
"required_topo_ref_fields": [
"graph_id",
"node_id",
"output_slot",
"kind",
"topo_id"
],
"optional_fields": ["selector_hint", "geo_selector", "selected_*_node_ids"],
"replay_resolution_order": [
"geo_select_nodes",
"selection_query",
"explicit_topo_refs",
"legacy_indices",
"selector_hint"
]
}selected_edges[] / selected_faces[] 的元素结构:
{
"graph_id": "graph_xxx",
"node_id": "node_xxx",
"output_slot": 0.0,
"kind": "EDGE",
"topo_id": "edge_640974",
"selector_hint": {
"kind": "edge",
"tags": ["edge", "left"],
"length": 4.0,
"start": [-2.0, -2.0, 4.0],
"end": [-2.0, -2.0, 0.0]
}
}selector_hint 是最后一级 fallback 信息,不保证稳定命中,但可提高恢复率。
不同 shape kind 的可见字段:
| Shape Kind | Possible Hint Fields |
|---|---|
edge |
kind, tags, length, start, end, center |
face |
kind, tags, area, center, normal |
wire |
kind, tags, edge_count, closed |
vertex |
kind, tags, coordinates |
solid |
kind, tags, volume, bbox |
当 detail feature 使用 QL selector 或 get_edges(index) / get_faces(index) 这类 indexed child-geometry getter 时,新 graph 不把 QL 文本或 Python list position 作为主要事实来源。运行时读取每个被选中子形状的几何事实,并为每一项生成一个 canonical select 节点:
make_select_redgemake_select_rfacemake_select_rwiremake_select_rvertexmake_select_rsolid
如果选择结果是 list,则每个元素对应一个独立 select 节点。feature node 通过 selected_edge_node_ids 或 selected_face_node_ids 指向这些节点。
select node 示例:
{
"op": "make_select_redge",
"params": {
"target_kind": "edge",
"geo_selector": {
"mode": "geo_exact",
"kind": "edge",
"geom_type": "CIRCLE",
"length": 6.283185307179586,
"center": [0.0, 0.0, 0.0],
"start": [1.0, 0.0, 0.0],
"end": [1.0, 0.0, 0.0],
"bbox": {
"min": [-1.0, -1.0, 0.0],
"max": [1.0, 1.0, 0.0]
},
"metadata_geo": {"edge_index": 2}
}
},
"inputs": ["node_for_source_solid"],
"output_count": 1
}geo_selector 不包含 tags,也不通过 tag 搜索。它固定到运行时选中对象的完整可见几何事实;完整几何数据用于 replay、审计和外部 adapter 校验。
旧 payload 或手写 payload 仍可携带 selection_query。这是 ShapeSelector.to_dict() 的结果,示例:
{
"target_kind": "edge",
"order_desc": false,
"cardinality": {"exactly": 1},
"predicate": {
"kind": "property_compare",
"data": {
"path": "geom.type",
"op": "==",
"value": "CIRCLE"
},
"children": []
},
"order_key": {
"kind": "property",
"data": {
"path": "geom.center.z",
"default": null
}
},
"limit": 1
}可选 source scope 字段:
| Field | Type | Meaning |
|---|---|---|
source_node_id |
string |
replay 时先定位该 graph node 的输出作为 selector scope |
source_output_slot |
int |
graph node 输出槽位,默认 0 |
order_keys |
array<object> |
多 key 排序,按声明顺序做 lexicographic ordering |
cardinality 支持:
| Field | Meaning |
|---|---|
exactly |
selector 必须解析出准确数量 |
at_least |
selector 必须解析出至少该数量 |
at_most |
selector 必须解析出至多该数量 |
更复杂的 traversal selector 会出现:
sourcetraversal
例如 boundary traversal:
{
"target_kind": "edge",
"source": {
"target_kind": "wire",
"source": {
"target_kind": "face",
"predicate": {...},
"limit": 1,
"cardinality": {"exactly": 1},
"order_desc": true,
"order_key": {...}
},
"traversal": {"relation": "boundary"},
"predicate": {...},
"limit": 1,
"cardinality": {"exactly": 1},
"order_desc": false
},
"traversal": {"relation": "boundary"},
"order_desc": false,
"cardinality": {"exactly": 4}
}detail feature replay 的固定解析顺序为:
selected_edge_node_ids/selected_face_node_ids指向的 geo select nodesselection_queryfallbackselected_edges/selected_faces中的显式 topo refsselected_edge_indices/selected_face_indiceslegacy fallbackselector_hint
适配器若要实现等价行为,必须保持这个顺序。
normalized tags 是默认 tag schema。tags[] 应只承载满足以下 grammar 的语义标签:
[a-z][a-z0-9_-]*(.[a-z][a-z0-9_-]*)*
常用 namespace:
| Prefix | Meaning |
|---|---|
role.* |
user/domain role, anchor lookup highest priority |
anchor.* |
explicit anchor alias |
face.* |
face-level semantic tag |
edge.* |
edge-level semantic tag |
wire.* |
wire-level semantic tag |
vertex.* |
vertex-level semantic tag |
solid.* |
solid-level semantic tag |
geom.* |
primitive/geometry classification tag, e.g. geom.primitive.box |
op.* |
operation lineage tags |
origin.* |
boolean/tracking origin role tags |
Legacy numeric or descriptive tags such as "0", "size: 1x2x3", or "bottom center: ..." must not be emitted into tags[] by new code. Store that data under metadata["geo"] instead, for example:
{
"tags": ["geom.primitive.box", "solid.primitive"],
"metadata": {
"geo": {
"type": "box",
"size": {"x": 1.0, "y": 2.0, "z": 3.0},
"bottom_face_center": [0.0, 0.0, 0.0]
}
}
}Anchor lookup priority is explicit and stable:
role.<name>anchor.<name><topology-kind>.<name>, such asface.top,edge.left,wire.outer- legacy aliases, including bare names such as
topor legacy-prefixed names
QL examples should use normalized tags:
from simplecadapi import ql as Q
top = Q.faces().where(Q.tag("face.top")).take(1).exactly(1)
mount = Q.faces().where(Q.tag("role.mounting_surface")).take(1).exactly(1)
outer_edges = Q.faces().where(Q.tag("face.top")).boundary("wire").where(Q.tag("wire.outer")).boundary("edge")expression_graph 结构:
{
"nodes": [
{
"expr_id": "var_119b16e4",
"kind": "var",
"name": "r",
"default": 2.0,
"unit": "mm",
"tolerance": {
"lower_deviation": -0.1,
"upper_deviation": 0.2
},
"tolerance_unit": "mm"
}
]
}{
"expr_id": "const_xxx",
"kind": "const",
"value": 3.0
}{
"expr_id": "var_xxx",
"kind": "var",
"name": "radius",
"default": 2.0,
"unit": "mm",
"tolerance": {
"lower_deviation": -0.05,
"upper_deviation": 0.1
},
"tolerance_unit": "mm"
}{
"expr_id": "expr_xxx",
"kind": "expr",
"op": "mul",
"args": ["var_xxx", "const_xxx"]
}当前实现支持:
addsubmuldivpownegabssincostansqrtacosasinatanatan2
Variable nodes may include:
| Field | Type | Required | Meaning |
|---|---|---|---|
unit |
`string | unit object` | no |
tolerance |
object |
no | signed deviations in tolerance_unit |
tolerance_unit |
`string | unit object` | no |
Built-in units serialize as symbols such as mm, in, deg, or rad. Custom
units serialize in full:
{
"symbol": "thou",
"dimension": {"length": 1, "angle": 0},
"scale_to_canonical": 0.0254
}Dimensions contain required integer length and angle exponents. Canonical
numeric values used by operation-node params and tolerance analysis are mm,
mm^2, mm^3, deg, or 1 for the named dimensions.
Import rebuilds the complete DAG and reruns dimension inference. Addition and subtraction require matching dimensions; multiplication/division combine exponents; dimensioned powers require supported constant exponents; square root requires even exponents; and trigonometric operations enforce Angle/Dimensionless inputs. A graph cannot mix unit-declared variables with legacy variables lacking units. Unitless legacy graphs remain accepted.
Variable tolerance values are signed deviations from default. A scalar source dimension must use lower_deviation <= 0 <= upper_deviation.
Session/model payloads may include a sibling tolerance_graph:
{
"requirements": [
{
"requirement_id": "req.clearance",
"target_expr_id": "expr_clearance",
"tolerance": {
"lower_deviation": -0.2,
"upper_deviation": 0.3
},
"method": "worst_case",
"name": "clearance",
"tolerance_unit": "mm",
"target_dimension": {
"length": 1,
"angle": 0
}
}
],
"validation": {
"passed": true,
"checks": []
}
}Supported methods are worst_case and rss. Unit-aware requirements must target
Length or Angle. tolerance_unit must match the inferred target dimension and is
converted to the canonical unit before comparison. Importers recompute validation
from expression_graph, compare the inferred result to target_dimension, and do
not trust serialized validation evidence. Missing tolerance_graph is treated as
an empty graph for backward compatibility. Legacy requirements may omit both unit
fields when their target expression is unitless.
frame_graph 结构:
{
"nodes": [
{
"frame_id": "frame:node_abc",
"origin": [0.0, 0.0, 0.0],
"x_axis": [1.0, 0.0, 0.0],
"y_axis": [0.0, 1.0, 0.0],
"z_axis": [0.0, 0.0, 1.0],
"parent_frame_id": null,
"metadata": {"node_id": "node_abc"}
}
]
}字段含义:
| Field | Type | Meaning |
|---|---|---|
frame_id |
string |
frame 唯一 id |
origin |
vec3 |
frame 原点 |
x_axis |
vec3 |
frame X 轴 |
y_axis |
vec3 |
frame Y 轴 |
z_axis |
vec3 |
frame Z 轴 |
parent_frame_id |
`string | null` |
metadata |
object |
当前至少包含 node_id |
export_model_json() 的顶层结构:
{
"schema_version": "2.0",
"canonical_contract": {...},
"graph": {...},
"expression_graph": {...},
"tolerance_graph": {...},
"frame_graph": {...},
"geometry_registry": [...],
"semantic_entity_registry": [...],
"sketch_profile_registry": [...],
"semantic_delta_log": [...],
"topology_delta_log": [...],
"leaf_ids": [...]
}| Field | Type | Required | Meaning |
|---|---|---|---|
schema_version |
string |
yes | current 2.0 |
canonical_contract |
object |
yes | machine-readable interchange contract |
graph |
graph object |
yes | canonical low-level graph and only source of truth |
leaf_ids |
array<string> |
yes | explicit result set for multi-output graph replay/export |
expression_graph |
object |
yes | expression DAG |
tolerance_graph |
object |
no | dimension-chain requirements and validation evidence; defaults to an empty graph |
frame_graph |
object |
yes | frame snapshots |
geometry_registry |
array<object> |
yes | output geometry registry |
semantic_entity_registry |
array<object> |
yes | semantic entity registry |
sketch_profile_registry |
array<object> |
yes | sketch/profile node registry |
semantic_delta_log |
array<object> |
yes | semantic delta log |
topology_delta_log |
array<object> |
yes | topology delta log |
当前固定结构:
{
"contract_version": "2.0",
"graph_roles": {
"graph": "canonical_low_level_graph",
"leaf_ids": "explicit_result_set"
},
"replay_policy": {
"preferred_graph": "graph",
"default_mode": "strict",
"permissive_mode": "explicit_opt_in"
},
"core_op_set": [...],
"selection_ref_schema": {...}
}消费规则:
replay_model_json()直接使用graph- 多输出 graph 的最终结果集由
leaf_ids显式声明 - replay 默认 strict;permissive 仅通过 API 参数显式 opt-in
每条记录结构:
{
"graph_id": "graph_xxx",
"node_id": "node_xxx",
"entity_type": "Feature",
"entity_id": "extrude:0",
"source_op": "extrude"
}来源规则:
- 若 node 有
semantic_delta.created,则按其 created refs 写入 registry - 若 node 没有 semantic delta,则按
output_countfallback 生成ShapeOutput
当前内容与 geometry_registry 高度重叠,但语义上强调 semantic entity 视角。Assembly/constraint semantic entities are not emitted while the assembly system is redesigned.
仅收录草图/轮廓相关 op,当前集合包括:
make_pointmake_linemake_segment_wiremake_circle_edgemake_circle_wiremake_circle_facemake_rectangle_wiremake_rectangle_facemake_three_point_arcmake_three_point_arc_wiremake_angle_arcmake_angle_arc_wiremake_splinemake_spline_wiremake_polyline_wiremake_helixmake_helix_wiremake_wire_from_edgesmake_face_from_wire
New canonical profile nodes use the make_*_r* names listed in canonical_contract.core_op_set; legacy short names are only tolerated here as older registry metadata, not as canonical model graph ops.
记录结构:
{
"graph_id": "graph_xxx",
"node_id": "node_xxx",
"op": "make_circle_face",
"params": {...}
}两者都按 graph 的拓扑顺序记录。
结构分别为:
{
"node_id": "node_xxx",
"op": "extrude",
"delta": {...}
}当前 canonical low-level graph op set 固定为下面的 source-API-like op names。注意这些是 graph/model JSON 的 canonical op names,不是旧版短 op names:
make_point_rvertexmake_line_redgemake_circle_redgemake_three_point_arc_redgemake_angle_arc_redgemake_spline_redgemake_helix_redgemake_wire_from_edges_rwiremake_face_from_wire_rfacemake_sketch_rsketchadd_point_rsketchadd_line_rsketchadd_circle_rsketchadd_arc_rsketchadd_bspline_rsketchmake_constrain_coincident_rsketchmake_constrain_point_on_rsketchmake_constrain_horizontal_rsketchmake_constrain_vertical_rsketchmake_constrain_parallel_rsketchmake_constrain_perpendicular_rsketchmake_constrain_collinear_rsketchmake_constrain_tangent_rsketchmake_constrain_concentric_rsketchmake_constrain_midpoint_rsketchmake_constrain_symmetric_rsketchmake_constrain_equal_length_rsketchmake_constrain_equal_radius_rsketchmake_constrain_distance_rsketchmake_constrain_distance_x_rsketchmake_constrain_distance_y_rsketchmake_constrain_length_rsketchmake_constrain_angle_rsketchmake_constrain_radius_rsketchmake_constrain_diameter_rsketchmake_constrain_fix_rsketchmake_wire_from_sketch_rwiremake_face_from_sketch_rfacemake_extrude_rsolidmake_revolve_rsolidmake_loft_rsolidmake_sweep_rsolidmake_twisted_sweep_rsolidmake_translate_rshapemake_rotate_rshapemake_mirror_rshapemake_cut_rsolidmake_union_rsolidmake_intersect_rsolidmake_fillet_rsolidmake_chamfer_rsolidmake_shell_rsolid
下列 node 不允许出现在 canonical graph:
make_boxmake_cylindermake_spheremake_conemake_pointmake_linemake_circle_edgemake_three_point_arcmake_angle_arcmake_splinemake_helixmake_wire_from_edgesmake_face_from_wireextruderevolveloftsweeptranslaterotatemirrorcutunionintersectfilletchamfershellmake_circle_facemake_rectangle_facemake_circle_wiremake_rectangle_wiremake_polyline_wiremake_segment_wiremake_three_point_arc_wiremake_angle_arc_wiremake_spline_wiremake_helix_wirelinear_patternradial_patternhelical_sweepmake_sketch_point_rsketchrefmake_solve_sketch_rsketchresult
Tests must treat this list as a hard constraint: every exported model graph op must be a subset of canonical_contract.core_op_set, and legacy op names must be rejected or isolated outside canonical model JSON.
Sketch solve is a promotion process, not a standalone graph result. Canonical sketch-to-geometry nodes (make_wire_from_sketch_rwire, make_face_from_sketch_rface) include solve_snapshot evidence and replay checks that evidence in strict mode.
本节定义当前会出现在 graph/model JSON 中的 operation node 格式。
说明约定:
Inputs表示inputs[]依赖的上游输出类型Outputs表示当前 node 的 shape 输出类型Params只写当前真正落盘的键,不写 Python API 的全部形参名- 如果某个 Python API 会被宏展开而不是单独记录,会单独说明
- Inputs: none
- Outputs: 1
Vertex - Params:
| Key | Type | Meaning |
|---|---|---|
x |
scalar | X coordinate |
y |
scalar | Y coordinate |
z |
scalar | Z coordinate |
- Inputs: none
- Outputs: 1
Edge - Params:
| Key | Type | Meaning |
|---|---|---|
start |
vec3 |
start point |
end |
vec3 |
end point |
Notes:
make_segment_redge()是别名 API,但记录成make_line_redge
- Inputs: none
- Outputs: 1
Wire - Params:
start,end
- Inputs: none
- Outputs: 1
Edge - Params:
center,radius,normal
- Inputs: none
- Outputs: 1
Wire - Params:
center,radius,normal
- Inputs: none
- Outputs: 1
Face - Params:
center,radius,normal
- Inputs: none
- Outputs: 1
Wire - Params:
width,height,center,normal
- Inputs: none
- Outputs: 1
Face - Params:
width,height,center,normal
- Inputs: 1
Wire - Outputs: 1
Face - Params:
| Key | Type | Meaning |
|---|---|---|
normal |
vec3 |
desired normal hint |
- Inputs: N
Edge - Outputs: 1
Wire - Params:
| Key | Type | Meaning |
|---|---|---|
edge_count |
int |
number of consumed edges |
- Inputs: none
- Outputs: 1
Edge - Params:
start,middle,end
- Inputs: none
- Outputs: 1
Wire - Params:
start,middle,end
- Inputs: none
- Outputs: 1
Edge - Params:
center,radius,start_angle,end_angle,normal
- Inputs: none
- Outputs: 1
Wire - Params:
center,radius,start_angle,end_angle,normal
- Inputs: none
- Outputs: 1
Edge - Semantics: exact B-spline definition.
control_pointsare poles, not sampled/interpolated curve points. Usefit_cubic_bspline_control_points(...)to convert sampled curve points into this exact payload before calling the geometry API. - Params:
| Key | Type | Meaning |
|---|---|---|
control_points |
`array<vec2 | vec3>` |
degree |
int |
B-spline degree, default/source canonical examples use 3 |
knots |
array<number> |
strictly increasing unique knot values |
multiplicities |
array<int> |
multiplicities aligned with knots |
weights |
`array | null` |
periodic |
bool |
whether the exact curve is periodic |
- Inputs: none
- Outputs: 1
Wire - Params:
control_points,degree,knots,multiplicities,weights,periodic
- Inputs: none
- Outputs: 1
Wire - Params:
points,closed
- Inputs: none
- Outputs: 1
Edge - Params:
pitch,height,radius,center,dir
- Inputs: none
- Outputs: 1
Wire - Params:
pitch,height,radius,center,dir
- Inputs: none
- Outputs: 1
Sketch - Params:
name,plane,sketch_id
- Inputs: 1
Sketch - Outputs: 1 updated
Sketch - Params:
sketch_id,point_id,x,y
- Inputs: 1
Sketch - Outputs: 1 updated
Sketch - Params:
sketch_id,entity_id,start,end,construction startandendare stable point target ids such asp0,p1, or endpoint paths such asline.start.
- Inputs: 1
Sketch - Outputs: 1 updated
Sketch - Params:
sketch_id,entity_id,center,radius,construction centeris a stable point target id.
- Inputs: 1
Sketch - Outputs: 1 updated
Sketch - Params:
sketch_id,entity_id,start,end,center,construction
- Inputs: 1
Sketch - Outputs: 1 updated
Sketch - Params:
sketch_id,entity_id,start,end,control_points,degree,knots,multiplicities,weights,periodic,construction
- Inputs: 1
Sketch - Outputs: 1 updated
Sketch - Params:
sketch_id,kind,targets,value,constraint_id,driving,metadata targetscontains stable target ids, not graph object ids. Examples:bottom,p0,bottom.end,hole.center.
- Inputs: 1
Sketch - Outputs: 1 promoted
Wire - Params:
profile,sketch,require_fully_constrained,strict,tolerance,max_iterations,solve_snapshot,promotion_map - The solver runs inside this promotion step.
solve_snapshotis evidence for strict replay validation, not a separate graph leaf.
- Inputs: 1
Sketch - Outputs: 1 promoted
Face - Params:
profile,sketch,require_fully_constrained,strict,tolerance,max_iterations,solve_snapshot,promotion_map - The promoted face/wire/edges carry
source_sketch,sketch_solve, and sketch entity tags/metadata.
- Inputs: none
- Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
w |
scalar | width |
h |
scalar | height |
d |
scalar | depth |
bottom_face_center |
vec3 |
placement anchor |
Important:
- 该 source API 在 active
GraphSession中展开为 canonical low-level graph ops,不作为make_boxnode 落入 canonical model JSON - geometry details such as size and bottom anchor belong in
metadata["geo"], not legacy descriptive tags
- Inputs: none
- Outputs: 1
Solid - Params:
radius,height,bottom_face_center,axis
- Inputs: none
- Outputs: 1
Solid - Params:
bottom_radius,top_radius,height,bottom_face_center,axis
- Inputs: none
- Outputs: 1
Solid - Params:
radius,center
- Inputs: 1 shape
- Outputs: 1 shape
- Params:
vector
- Inputs: 1 shape
- Outputs: 1 shape
- Params:
angle,axis,origin
- Inputs: 1 shape
- Outputs: 1 shape
- Params:
plane_origin,plane_normal
- Inputs: 1
WireorFace - Outputs: 1
Solid - Params:
direction,distance
- Inputs: 1
WireorFace - Outputs: 1
Solid - Params:
axis,angle,origin
- Inputs: N
Wire - Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
profile_count |
int |
number of profiles |
ruled |
bool |
whether ruled loft mode was requested |
- Inputs: 1 profile + 1 path
- Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
is_frenet |
bool |
sweep orientation mode |
- Inputs: 1 profile
Face - Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
axis |
3-number array | sweep and rotation-axis direction in caller coordinates |
origin |
3-number array | sweep start and a point on the rotation axis |
distance |
positive number | axial sweep distance |
twist_angle |
number | signed total rotation in degrees |
guide_radius |
positive number | internal auxiliary-spine radius |
Replay rebuilds the auxiliary spine deterministically inside the operation. It does not infer section counts or lower to transient loft nodes.
- Inputs: N
Solid - Outputs: 1
Solid - Params base fields:
| Key | Type | Meaning |
|---|---|---|
input_count |
int |
number of input solids |
clean |
bool |
whether post-clean was requested |
glue |
bool |
OCC glue mode flag |
tol |
`number | null` |
Notes:
output_countis always 1 forunion_rsolid(...).- If the kernel cannot produce exactly one merged solid, the API raises instead of returning disconnected pieces.
- replay passes recorded
clean,glue, andtolback tounion_rsolid(...); the tracking builder uses the sameglueand fuzzy tolerance as the actual boolean builder.
- Inputs: base solid + tool solids
- Outputs: 1
Solid(wrapped in a single-output node) - Params:
| Key | Type | Meaning |
|---|---|---|
tool_count |
int |
number of subtractive tool solids |
skip_non_intersecting |
bool |
whether non-intersecting tools are skipped |
Notes:
- Public
cut_rsolid(...)defaults this flag totruefor interactive convenience. - Graph replay defaults missing
skip_non_intersectingtofalseso malformed or drifting graphs fail diagnostically instead of silently skipping tools. - Multi-tool cut preserves the full topology delta chain across all performed tool cuts.
- Inputs: N
Solid - Outputs: 1
Solidwhen overlap exists - Params:
| Key | Type | Meaning |
|---|---|---|
input_count |
int |
number of intersected solids |
Notes:
- Multi-tool intersect preserves the full topology delta chain across all performed intersection steps.
- Inputs: 1 source
Solidplus optionalmake_select_redgenodes - Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
radius |
scalar | fillet radius |
edge_count |
int |
number of targeted edges |
selected_edges |
array<TopoRefWithHint> |
explicit edge refs |
selected_edge_node_ids |
array<string> |
primary geo select node refs for QL/index-derived selections |
selected_edge_indices |
array<int> |
legacy index fallback when select nodes are unavailable |
selection_query |
ShapeSelector object |
legacy/fallback serialized QL selector |
- Inputs: 1 source
Solidplus optionalmake_select_redgenodes - Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
distance |
scalar | chamfer distance |
edge_count |
int |
number of targeted edges |
selected_edges |
array<TopoRefWithHint> |
explicit edge refs |
selected_edge_node_ids |
array<string> |
primary geo select node refs for QL/index-derived selections |
selected_edge_indices |
array<int> |
legacy index fallback when select nodes are unavailable |
selection_query |
ShapeSelector object |
legacy/fallback serialized QL selector |
- Inputs: 1 source
Solidplus optionalmake_select_rfacenodes - Outputs: 1
Solid - Params:
| Key | Type | Meaning |
|---|---|---|
thickness |
scalar | shell thickness |
removed_face_count |
int |
number of faces removed |
selected_faces |
array<TopoRefWithHint> |
explicit face refs |
selected_face_node_ids |
array<string> |
primary geo select node refs for QL/index-derived selections |
selected_face_indices |
array<int> |
legacy index fallback when select nodes are unavailable |
selection_query |
ShapeSelector object |
legacy/fallback serialized QL selector |
- Inputs: 1 shape
- Outputs:
countshape instances - Params:
| Key | Type | Meaning |
|---|---|---|
direction |
vec3 |
translation direction |
count |
int |
number of instances |
spacing |
number |
distance between adjacent instances |
Notes:
- canonical graph does not keep a single
linear_patternnode - the pattern must expand into explicit
make_translate_rshapeinstance nodes and declare final outputs vialeaf_ids
- Inputs: 1 shape
- Outputs:
countshape instances - Params:
| Key | Type | Meaning |
|---|---|---|
center |
vec3 |
rotation center |
axis |
vec3 |
rotation axis |
count |
int |
number of instances |
total_rotation_angle |
number |
angular coverage of the pattern |
Notes:
- canonical graph does not keep a single
radial_patternnode - the pattern must expand into explicit
make_rotate_rshape/make_translate_rshapeinstance nodes and declare final outputs vialeaf_ids
这不是一个稳定记录节点。
规则:
- 当
GraphSession处于激活状态时,helical_sweep_rsolid()不记录helical_sweep - 它会显式展开为:
make_helix_redgemake_wire_from_edges_rwiremake_face_from_wire_rfacemake_sweep_rsolidwithis_frenet=true
- 因此 graph JSON / model JSON 中不应期待存在
op == "helical_sweep"
- 这是
make_line_redge()的别名 API - 记录时仍然是
op == "make_line"
SDF / scalar field modeling is temporarily removed from the supported public surface.
simplecadapi.fieldis not exported.make_field_surface_rsolidis not exported.make_field_surface_rsolidis not part ofCANONICAL_CORE_OP_SET.- New graph/model JSON payloads must not contain
op == "make_field_surface_rsolid". - Historical payloads that contain scalar field nodes should be treated as unsupported until a new SDF contract is designed.
replay_graph(graph):
- 逐节点按拓扑序执行
- 使用节点
op + params + inputs恢复几何 - 默认 strict,缺参数、缺输入、未知 op、leaf 无 output、selection cardinality mismatch 都会失败
- 只有调用
replay_graph(graph, strict=False)时才启用 permissive fallback - 返回 leaf node outputs
replay_model_json(json_str):
- 先导入 model payload
- 直接 replay
graph - 若存在
leaf_ids,返回这些显式 leaf ids 对应的 outputs - 否则返回 graph leaf outputs
默认 strict,只有调用 replay_model_json(json_str, strict=False) 时才启用 permissive fallback。
- 如果未显式指定 leaf ids,最终结果等于 graph 的 leaf node outputs 拼接
- 多输出 node 会把其所有 output slots 依序加入结果
- 优先消费
model.json - 若要做工业 interchange,请直接消费 canonical low-level
graph - 若要恢复参数化,请同时读取
params、param_exprs、expression_graph - detail feature 必须按声明顺序解析选择:geo select nodes -> query fallback -> explicit refs -> legacy indices -> hint
- 不要依赖
display.summary解析业务语义
消费端应对以下实现细节保持宽容:
- 数值字段可能表现为整数或浮点数
output_slot在深层 param 对象中可能是0.0tags可能为空semantic_delta/topo_delta可能缺失selection_query是旧 payload / 手写 payload fallback;新 detail selections 应优先产生 geo select nodes
如果外部系统只需做几何重建,最小可消费集合是:
model.schema_versionmodel.graph- 每个 node 的
op/params/inputs/output_count leaf_ids
如果需要稳定 detail feature 选择,还必须消费:
selected_edge_node_ids/selected_face_node_idsand theirmake_select_*nodesselected_edges/selected_facesselected_edge_indices/selected_face_indicesas legacy fallbackselection_queryfallback, when presentselector_hint
helical_sweep不是独立 stable node,而是宏展开opaque_callablefield surface 不可 replayassembly_graphcapability is false; assembly/constraint model JSON fields are currently not emitteddisplay是派生字段,不保证长期稳定- composite builtins may expand into multiple low-level nodes, so node count/granularity should not be assumed from public API call count
{
"node_id": "node_caf7b43f",
"op": "fillet",
"params": {
"radius": 0.3,
"edge_count": 2,
"selected_edges": [
{
"graph_id": "graph_24fc9c20",
"node_id": "node_f7e1ea08",
"output_slot": 0.0,
"kind": "EDGE",
"topo_id": "edge_640974",
"selector_hint": {
"kind": "edge",
"tags": ["edge.left", "role.mounting_edge"],
"length": 4.0,
"start": [-2.0, -2.0, 4.0],
"end": [-2.0, -2.0, 0.0]
}
}
],
"selected_edge_node_ids": ["node_select_edge_0", "node_select_edge_1"]
},
"inputs": ["node_f7e1ea08", "node_select_edge_0", "node_select_edge_1"],
"output_count": 1
}{
"op": "make_circle_face",
"params": {
"center": [0.0, 0.0, 0.0],
"radius": 2.0,
"normal": [0.0, 0.0, 1.0]
},
"param_exprs": {
"radius": {
"expr_id": "var_119b16e4"
}
}
}可以把当前 JSON interchange 简化理解为:
graph= 唯一真相源的 canonical low-level 图expression_graph= 参数化依赖图frame_graph= 工作坐标快照semantic/topology delta= 语义与拓扑 lineage 补充信息
外部转译适配的主实现建议是:
- 读
model.json - 直接使用
graph - 按本文件的 operation catalog 解释
params - 对 detail feature 按固定顺序恢复选择对象
- 在需要参数化恢复时联动
param_exprs + expression_graph