163 KiB
SimpleCAD Evaluated Scene 与 Viewer 架构设计
文档状态
- 状态:Frozen(Scene Schema 1.0 Phase A contract)
- 冻结证据日期:2026-07-22
- Shared corpus:
test/fixtures/scene-contract/corpus.json,SHA-2564f04224333e6aa70b12fc33efaee7b8d27782894d692fe402a6ef1d394a780ca - 目标版本:Scene Schema 1.0
- 目标项目:SimpleCADAPI 2.x
- 依赖:现有 Model Schema 2.0、Product/Assembly 语义、内部三角化能力、glTF 2.0、RFC 8785 JSON Canonicalization Scheme (JCS)
- 主要影响模块:
src/simplecadapi/scene/中的 compiler、render mesh/asset、serializer、validator 和 contract artifacts,source_mapping.py,以及仓库根目录下独立的viewer/TypeScript/Three.js 工程;现有graph.py、serializer.py、product.py、_mesh.py提供输入 - 非目标:用 scene 文件替代 operation graph、在浏览器中重放全部 CAD operations、第一阶段提供协同编辑或通用游戏引擎、把 viewer 临时状态写回 canonical model
当前实现边界
当前仓库已经具有 replayable operation graph、ModelResult、显式 result capture、Part/Assembly/Component 产品层级、刚体 placement、基础 material RGB、Phase A 冻结的 Scene Schema、immutable contract documents、strict validators、resource profiles、exact GLB/ZIP vectors、Python/TypeScript parity harness,以及可工作的 Scene Compiler、triangle/edge GLB writer、entity sidecar、canonical .scene.zip exporter和browser Viewer。Viewer当前是直接DOM状态管理的TypeScript/Three.js/Vite应用,支持package loading、product tree、Features/source inspection和五种selection intent;尚未实现presentation/imported compilation、scene patch protocol、持久化workspace、独立model/scene pair-state系统或browser CAD replay。
本文将现有 model.json 定义为 parametric design/replay IR,将新增的 scene 文件定义为 evaluated presentation artifact。Scene 可以由 model 和可选 ScenePresentationSpec 重建,但不能反向替代二者,也不能成为表达设计或 presentation authoring intent 的第二份事实来源。
1. 摘要
SimpleCADAPI 接下来需要一层独立于 Operation Graph 的 evaluated scene,用于回答以下问题:
- 当前应该显示哪些对象。
- 对象之间是什么产品或实例层级。
- 每个实例的局部变换是什么。
- 每个实例引用哪个可渲染 geometry asset 和 appearance。
- 用户点击一个 triangle、face 或 component 时,如何追溯到 scene node、product component、graph output 和 semantic evidence。
- Viewer 在不运行 OpenCascade replay 的情况下,如何直接加载并显示模型。
- 同一 part 被多次实例化时,如何只保存一份 mesh。
- 几何变化、placement 变化和纯显示变化如何采用不同的缓存与更新策略。
推荐的数据流为:
Python modeling source
|
v
Operation Graph / model.json
|
| replay or direct runtime evaluation
v
Solid / Part / Assembly values
|
| Scene Compiler
v
SceneSnapshot / scene.json
|
+---- content-addressed GLB assets
+---- evaluated entity snapshots and picking maps
+---- generic package: optional embedded model.json
+---- self-contained model package:
| model/model.json
| sources/<project-relative-path>.py
|
v
Browser Viewer / Desktop Viewer
核心决策是:
model.json描述模型如何构建,scene.json描述模型求值后如何被显示和交互,render assets 描述 GPU 实际绘制的数据,workspace 描述某个用户当前的临时界面状态。
2. 为什么不能直接让 Viewer 消费 Operation Graph
Operation Graph 的边表达 operation data dependency,而不是 scene parent/child hierarchy。一个 operation node 可以是 profile、selection、boolean、constraint、material assignment 或 diagnostic semantic operation;它不一定对应一个可见对象。
如果 Viewer 直接消费 operation graph,就必须承担以下职责:
- 理解并实现全部 canonical CAD operations。
- 在浏览器中运行 OpenCascade 或等价 CAD kernel。
- 解析 operation output、semantic output 和 product output 的不同类型。
- 根据 graph leaves 推测 visibility。
- 根据 product operations 重建 definition/instance hierarchy。
- 自行生成三角网格、法线、边线和 picking 映射。
- 跟随每次 schema 或 operation set 变化升级执行器。
这会把 Viewer 变成第二个 CAD replay backend,显著增加前后端一致性成本,也使一个只想查看模型的客户端必须信任并执行完整建模程序。
Scene Snapshot 应当是已经求值的、可验证的、有限能力的显示数据。Viewer 只需要理解 scene/render asset schema;为只读Features和源码inspection,它可以解析embedded model graph的有限记录,但不会实现或执行fillet、loft、constraint solve或CADQL。读取operation records用于展示不等于把Viewer变成CAD replay backend。
3. 六种 Artifact 的边界
3.1 Design Model
当前 model.json 属于 Design Model,canonical owner 包括:
- Operation DAG 和 operation params。
- Expression intent 和 numeric snapshot。
- Tolerance requirements。
- Feature selection intent。
- Semantic bindings。
- Topology evolution evidence。
- Product construction operations。
- Replay result node IDs。
Design Model 的主要用途是 replay、编辑、参数修改、translator 和设计审计。
3.2 Evaluated Scene
新增 scene.json 属于 Evaluated Scene,canonical owner 只包括某次编译的 evaluated snapshot:
- 某个 scene revision 的 node hierarchy。
- Definition/instance 引用关系。
- Parent-relative rigid transforms。
- Geometry asset 和 appearance 绑定。
- 编译后的 Scene visibility、selectability 和 display organization。
- Scene-level source provenance。
- 由
ScenePresentationSpec求值后的 named views 和 appearance overrides;annotations 在后续 extension 中引入。 - Definition-local evaluated topology/entity snapshot 引用。
Scene 是 source model 与 optional presentation spec 某次求值的快照。任一 source 更新后 Scene 都可以失效并重新编译。Generated scene 本身不是 authoring source。
3.3 Render Assets
Render Assets 负责保存:
- Vertex positions。
- Vertex或corner normals。
- Triangle indices。
- CAD edge polylines。
- Local bounds。
- Face/edge picking ranges。
- Tessellation settings。
第一阶段推荐两个 appearance-neutral GLB profile:triangle geometry asset 和 CAD edge line asset。独立 EntitySnapshotAsset 保存 definition/source-specific evaluated topology、geometry properties、SDK tags/metadata、operation provenance 和 picking map。Asset 使用内容哈希寻址,支持不同 scene revision 复用;同一 geometry bytes 可以对应多个 provenance/metadata 不同的 entity snapshot assets。
3.4 Viewer Workspace
Viewer Workspace 负责某个用户或某次查看会话的临时状态:
- 当前 camera pose。
- Hover 和 selection。
- 用户临时隐藏的节点。
- Tree 展开状态。
- Clipping planes。
- Exploded view amount。
- 当前 display mode。
- Panel layout 和 measurement history。
Workspace 默认不属于 canonical scene,也不能修改 model semantics。若需要分享视图,用户必须把明确选择的 workspace state 发布为独立 ScenePresentationSpec,然后重新编译 scene。
3.5 Scene Presentation Spec
ScenePresentationSpec 是可选 authoring artifact,负责不可从 model 确定性重建的发布意图:
- Named cameras/views。
- Authored visibility 和 appearance overrides。
- Labels、callouts 和 read-only dimensions。
- Authored section presets。
它拥有独立 schema、logical ID 和 artifact hash,并作为 compile_scene(..., presentation=...) 的显式输入。没有 presentation spec 时,compiler 只生成 deterministic defaults,不从之前生成的 scene 反向读取 authoring state。
3.6 Connector Binding Spec
ConnectorBindingSpec 是 Viewer/Editor 产生的 revision-bound authoring command,不是 evaluated scene 的一部分。它保存用户选择的 owner、target entity、connector ID、方向选项和 source model/scene preconditions。Python SDK 或受控 backend 验证这些 preconditions,通过trusted deterministic recompilation把revision-local entity解析回唯一live geometry selection、调用canonical connector operation,并生成新的model.json和.scene.zip。
Connector binding 的 canonical owner 最终仍是 Design Model 中的 connector operations/Product semantics。Binding spec 可以作为审计或重试 command 保存,但不能只修改 scene snapshot 后宣称 model 已被编辑。
4. Canonical Owner 原则
每项事实只能有一个 canonical owner。Scene 可以保存从 model 求值得到的缓存,但必须标记来源和失效条件。
| 事实 | Canonical owner | Scene 中的表示 |
|---|---|---|
| Box、fillet、boolean 如何构建 | Operation Graph | 不复制 operation semantics |
| 参数表达式和尺寸链 | Expression/Tolerance Graph | 可复制只读摘要,不可编辑为第二份参数源 |
| Assembly definition 和 constraints | Product semantics in model | Scene 保存求值后的 hierarchy、transform 和可选 kinematic projection |
| 当前 component placement | Evaluated product state | Scene node local transform |
| Part 的实体几何 | Replayed BRep value | Content-addressed render asset |
| Material 的物理密度 | Product Material |
Scene appearance 可引用 source material ID,但不拥有密度 |
| Base color、roughness 等显示属性 | Product-to-appearance deterministic rule 或 ScenePresentationSpec |
Scene 保存 evaluated Appearance |
| Face 的 semantic tag | Semantic Binding Store | Entity snapshot 中保存 evaluated tag cache 和 binding IDs |
| SDK shape metadata | Runtime/replayed shape metadata | Entity snapshot 保存 JSON-safe snapshot |
| Engine geometry facts | Evaluated OCP BRep | Entity snapshot 保存 typed geometry property snapshot |
| Triangle 属于哪个 face | Definition-specific Entity Snapshot Asset | Scene definition 引用 picking range |
| 当前 hover/selection | Viewer Workspace | 不进入 scene snapshot |
| 作者保存的默认视角 | ScenePresentationSpec |
Scene 保存 evaluated named camera/view |
| 用户临时相机 | Viewer Workspace | 不进入 canonical scene |
| Connector 定义和 geometry anchor intent | Design Model/Product semantics | Scene 保存 evaluated connector snapshot |
| Viewer 发起的 connector 绑定命令 | ConnectorBindingSpec,成功后归并到 Design Model |
不直接修改已有 scene revision |
5. 与 capture_result 的关系
capture_result 继续只定义 Design Model 的 replay outputs。它回答:
哪些 graph-backed values 是 canonical model results。
它不回答:
- 哪些 intermediate objects 在 Viewer 中可见。
- 哪个 assembly 应该作为 scene root。
- Camera、lights 或 background 是什么。
- Component tree 如何展开。
- Geometry 使用哪个 tessellation quality。
- Viewer 当前选中了什么。
第一阶段不扩展 capture_result 的职责,也不增加隐式 display metadata。Low-level Scene compilation 使用显式 root descriptor:
result = build_gearbox()
assembly, preview = result.value
package = scad.compile_scene(
scene_id="gearbox-demo",
roots=(
scad.SceneRoot(root_id="main", value=assembly),
),
source=result,
options=scad.SceneCompileOptions(embed_source=True),
)
scad.export_scene(package=package, path="gearbox.scene.zip")
Base 1.0 roots只接受一个或多个显式SceneRoot(root_id=..., value=..., transform=..., source_element_id=...),不接受裸runtime values,因为裸value没有stable root namespace。Model/manual source中source_element_id必须省略,value允许Solid、含至少一个valid solid的Compound、Part或Assembly。Imported source中source_element_id required,必须是importer/caller从exact source artifact提供的stable non-empty element identity,value只允许Solid或上述Compound;Base 1.0不为imported Part/Assembly发明product provenance。source_element_id不能从root sequence、runtime traversal或temporary file path生成,UTF-8长度必须不超过structural ID budget。transform省略时规范化为第7.4节exact identity transform;显式transform必须valid rigid transform。Tuple输入在compile开始时按root_id unsigned UTF-8 byte order规范化,因此caller sequence不影响manifest;duplicate root ID在sort前失败。
如果只有 model.json,调用端先 replay captured results,再显式包装为 SceneRoot。若 captured result 只包含 flattened compound,scene compiler 只能生成 flat geometry scene,不能凭空恢复已经丢失的 product hierarchy。root_id 在一个 scene 内唯一,为 multi-root definition 和 occurrence identity 提供 namespace;compiler 不依赖 sequence position 生成 ID。
因此,产品模型若需要保留 hierarchy,至少应满足以下一项:
capture_result(value=assembly)将 assembly 作为 model result。- Runtime CLI 直接把
ModelResult.value中的 assembly 传给compile_scene。 - 后续 schema 显式增加 product result role,但不通过猜测 downstream compound 反推 assembly。
当前还提供显式opt-in的publishing shortcut:@model(graph_id=..., export_dir=...)在model调用结束后自动执行ModelResult.export_artifacts();也可以先取得ModelResult,再手动调用result.export_artifacts(output_dir=...)。如果作者没有调用capture_result(),@model先把返回值作为result capture。自动路径从captured values导出Part/Assembly,没有product value时才导出Solid/Compound,为roots分配capture-<index>,使用scene_id=graph_id,强制嵌入model和可解析Python source,并且在output directory外部只写一个<graph_id>.scene.zip。它不旁置写出model/session JSON、STEP、STL或FCStd。
6. 当前能力与缺口
6.1 已有能力
| 能力 | 当前来源 | Scene Compiler 可直接复用 |
|---|---|---|
| Graph provenance | OperationNode、TopoRef、graph attachment |
是 |
| Explicit model results | GraphSession.result_node_ids |
是,但只作为 fallback root evidence |
| Product hierarchy | Assembly.components |
是 |
| Repeated part instance | Component.item definition reference |
是 |
| Rigid placement | Placement |
是 |
| Basic material color | Material.color |
是 |
| Triangle/edge render mesh | RenderMesh、RenderEdgeMesh |
已接入Scene Compiler |
| Face/edge picking range | Entity sidecar groups | 已接入Viewer selection |
| Local bounds | TriMesh.bounds |
是 |
| Static collision traversal | Recursive component path + placement composition | 是,可复用 traversal semantics |
| Static screenshot | Existing tessellation and shading code | 只复用经验,不复用 Matplotlib renderer contract |
| Canonical Scene package | CompiledScenePackage、.scene.zip exporter |
已实现 |
| Source mapping | OperationNode.source、source_files |
已接入Features/source pane |
| Browser frontend | TypeScript/Three.js/Vite Viewer | 已支持package、tree、Features和五种selection |
6.2 仍缺少的核心能力
| 缺口 | 对 Viewer 的影响 |
|---|---|
| Presentation compilation 尚未实现 | 不能从authored spec生成named views/overrides |
| Imported source compilation 尚未实现 | 第一阶段compiler只接受manual/model runtime values |
| Browser尚无独立model/scene pairing | 当前只从一个validated package发现embedded model |
| Browser尚无完整operation/entity双向cross-link | Features和geometry inspector当前是独立selection flows |
| Viewer缺少自动化E2E harness | Source滚动/高亮主要依靠build和人工验证 |
| 没有 patch protocol | 每次变化只能全量刷新 |
| 没有 exact measurement boundary | Mesh Viewer 只能近似测量任意 geometry |
7. Scene Schema 1.0 总体结构
Scene JSON 顶层推荐结构如下:
{
"schema_version": "1.0",
"extensions_used": [],
"extensions_required": [],
"extensions": {},
"scene_id": "gearbox-demo",
"revision": "sha256:...",
"generator": {
"name": "simplecadapi",
"simplecadapi_version": "2.0.1b1",
"ocp_version": "7.8.1",
"ocp_bindings_version": "7.8.1",
"python_abi": "cp312",
"platform_tag": "macosx_14_0_arm64",
"toolchain_hash": "sha256:...",
"profile": "scene-1.0-ocp-glb-2"
},
"source": {
"kind": "model",
"graph_id": "gearbox",
"model_schema_version": "2.0",
"artifact_hash": "sha256:...",
"embedded_artifact_uri": "model/model.json",
"embedded_artifact_byte_length": 45678,
"source_files": [
{
"path": "models/gearbox.py",
"uri": "sources/models/gearbox.py",
"media_type": "text/x-python; charset=utf-8",
"byte_length": 12345,
"content_hash": "sha256:..."
}
]
},
"presentation_source": {
"presentation_id": "gearbox-published-view",
"schema_version": "1.0",
"artifact_hash": "sha256:...",
"embedded_artifact_uri": "presentation/presentation.json",
"embedded_artifact_byte_length": 1234
},
"coordinate_system": {
"length_unit": "mm",
"handedness": "right",
"up_axis": "+Z"
},
"compile_options": {
"linear_tolerance": 0.35,
"angular_tolerance": 0.22,
"embed_source": true,
"embed_presentation": true
},
"definitions": [],
"nodes": [],
"geometry_assets": [],
"edge_assets": [],
"appearances": [],
"entity_assets": [],
"connectors": [],
"cameras": [],
"lights": [],
"annotations": [],
"diagnostics": []
}
7.1 必填字段
第一阶段必填:
schema_versionextensions_usedextensions_requiredextensionsscene_idrevisiongeneratorsourcecoordinate_systemcompile_optionsdefinitionsnodesgeometry_assetsedge_assetsappearancesentity_assetsconnectorscameraslightsannotationsdiagnostics
这些 collection 在没有内容时仍以空 array/object 输出,不通过 missing/empty 两种状态表达额外语义。presentation_source 是 optional,且只有存在 presentation spec 时允许出现。
Base 1.0固定extensions_used=[]、extensions_required=[]、extensions={}和diagnostics=[]。Compiler validation/compilation failure通过SceneValidationReport返回,不生成带warning/error diagnostic的canonical scene。以后如需发布diagnostics或extensions,必须使用新base schema或完整注册的profile;Base 1.0 loader拒绝任一non-empty value。Workspace runtime error也不写回scene。
SceneValidationReport的非manifest diagnostic record结构固定为:
{
"severity": "warning",
"code": "topology_provenance_unavailable",
"path": "/definitions/0",
"message": "Entity ranges are available without model topology provenance."
}
severity只允许info、warning、error;code使用normative semantic rule ID,path使用RFC 6901 JSON Pointer。Message是human-readable且不参与artifact identity。Runtime loader/network/GPU diagnostics属于workspace,不修改scene revision。
source 对所有 scene 必填,并且是 discriminated union:
kind=model:必须包含 graph/model schema 和 exact input artifact bytes 的 SHA-256artifact_hash;self-contained model package还包含embedded model和source_files。kind=imported:必须包含 imported artifact format 和 exact bytes hash。kind=manual:必须包含调用方提供的 stablesource_id。
这里的 artifact_hash 不是尚未定义的 canonical model digest。对于当前 ModelResult,它只是 result.model_json.encode("utf-8") 的 exact-byte SHA-256。若 package 内嵌 source artifact,URI、byte length 和 hash 必须在加载、下载或 replay 前一致;只改变 model JSON formatting 也会改变 source artifact hash 和 scene revision,这是 Scene 1.0 可接受的保守失效策略。每个Python file独立声明其exact bytes的content_hash和byte_length,这些records作为manifest内容参与scene revision,但不改变artifact_hash的含义。
presentation_source 只在使用 ScenePresentationSpec 时存在。Artifact bytes 被定义为 normalized presentation record 的 JCS bytes,artifact_hash 是这些 bytes 的 SHA-256,因此 compile_scene() 接受 parsed spec 时也能确定性重建 hash input。Presentation spec 可以嵌入 package,也可以只保留 hash;嵌入时 embedded_artifact_uri、embedded_artifact_byte_length 必填,且文件内容必须恰好是上述 JCS bytes。生成的 cameras、visibility 和 appearance overrides 都必须能够追溯到这个 source。
Scene 1.0 loader只接受exactschema_version="1.0";未来base schema变化必须经过显式migrator。Scene 1.0对未知base fields默认fail closed。Base validator要求三个extension containers为空;不假定所有1.x自动兼容,也不接受尚未注册schema/profile的optional payload。
7.2 Base Record 字段策略
Scene 1.0 的所有 base records 和本文定义的 entity sidecar records 都采用同一规则:表格或 discriminated union 未列出的字段一律拒绝;required 字段必须存在;optional 字段没有值时必须省略,禁止用 null 代替。只有下表明确标记 nullable 的字段允许 null。所有 map key 必须是 Unicode string,所有 number 必须是 JCS 可表示的 finite IEEE-754 binary64 value。
| Record | Required fields | Optional fields | Nullable fields |
|---|---|---|---|
generator |
name, simplecadapi_version, ocp_version, ocp_bindings_version, python_abi, platform_tag, toolchain_hash, profile |
无 | 无 |
coordinate_system |
length_unit, handedness, up_axis |
无 | 无 |
compile_options |
linear_tolerance, angular_tolerance, embed_source, embed_presentation |
无 | 无 |
source(kind=model) |
kind, graph_id, model_schema_version, artifact_hash |
embedded_artifact_uri, embedded_artifact_byte_length, source_files |
无 |
SourceFile |
path, uri, media_type, byte_length, content_hash |
无 | 无 |
source(kind=imported) |
kind, format, artifact_hash |
embedded_artifact_uri, embedded_artifact_byte_length |
无 |
source(kind=manual) |
kind, source_id |
无 | 无 |
presentation_source |
presentation_id, schema_version, artifact_hash |
embedded_artifact_uri, embedded_artifact_byte_length |
无 |
SceneDefinition |
definition_id, kind, name, source, sdk_metadata |
geometry_asset_id, edge_asset_id, entity_asset_id, appearance_id |
name |
SceneNode |
node_id, parent_node_id, order, definition_id, name, transform, visible, selectable, appearance_override_id, source, sdk_metadata |
无 | parent_node_id, name, appearance_override_id |
SceneGeometryAsset |
asset_id, uri, media_type, byte_length, content_hash, scene_local_bounds, asset_to_scene, tessellation |
无 | 无 |
SceneEdgeAsset |
asset_id, uri, media_type, byte_length, content_hash, scene_local_bounds, asset_to_scene, tessellation |
无 | 无 |
SceneEntityAsset |
entity_asset_id, uri, media_type, byte_length, content_hash |
无 | 无 |
SceneAppearance |
appearance_id, name, source, base_color, metallic, roughness, alpha_mode, double_sided, edge_color, sdk_metadata |
无 | name, source |
SceneConnector |
connector_snapshot_id, owner_definition_id, connector_id, name, anchor_kind, local_transform, source, sdk_metadata |
target, forwarded_from |
name, source |
SceneCamera |
camera_id, name, projection, parent_node_id, transform, near, far and projection-specific field |
无 | parent_node_id |
embedded_artifact_uri 和 embedded_artifact_byte_length 必须成对出现。SourceFile是closed record。Schema-level generic model scene可以省略source_files;compiler-produced self-contained model package在嵌入model时总是输出该array,即使没有可解析的project-relative operation source而为空。SceneDefinition.source 是 model_output、product_model、product_manual、imported、manual union;SceneNode.source 是 product_occurrence、shape_root union。各 variant 的 exact fields 在第 8 节定义。sdk_metadata 必须通过第 10 节的 JSON-safe policy 规范化,不能包含 runtime object。
generator.name固定为simplecadapi。三个version fields、python_abi和platform_tag使用ASCII token grammar[A-Za-z0-9][A-Za-z0-9._+-]{0,127},不得使用2.x、path或缺失fallback。toolchain_hash按第10.4节toolchain descriptor求SHA-256;它覆盖所有output-affectingPython modules、native OCP/kernel binaries和build identity。Backend只有在拥有同hash的registered toolchain时才能声称可重现scene。generator.profile是deterministic compiler profile ID,并参与revision。media_type固定为对应profile定义的exact value;Base 1.0不接受等价alias。byte_length是非负integer。所有content-hash IDs使用第16节的exact grammar。
Nested common records也采用exact fields:rigid transform只允许origin、x_axis、y_axis、z_axis;bounds只允许min、max;compile_options和geometrytessellation只允许positivelinear_tolerance、positiveangular_tolerance;edgetessellation只允许positivelinear_tolerance。每个geometry asset的两个tessellation values和每个edge asset的linear value必须exact等于top-level resolvedcompile_options。asset_to_scene必须恰好是第7.3节固定的16-number matrix,不接受exporter-specific alternative。
Embedding conditions是exact contract:source.kind=model|imported且embed_source=true时source URI/length必须存在且bytes/hash匹配;embed_source=false时两者必须省略。Embedded model的URI固定为model/model.json。其source_files按path的unsigned UTF-8 bytes排序且唯一,每个URI必须exact等于sources/<path>;path必须匹配archive-safe ASCII .py grammar ^[A-Za-z0-9][A-Za-z0-9._/-]*\.py$,不得有empty、.或.. segment,也不得产生case-insensitive collision。每个source member必须是strict UTF-8并通过自己的length/hash验证。presentation_source存在且embed_presentation=true时其URI/length必须存在;false时省略。没有presentation时embed_presentation必须为false。Manual source不能embedded,要求embed_source=false。这两个booleans参与revision并使相同input tuple的package policy确定;其他canonical URI固定为source/source.bin和presentation/presentation.json。
Embedded model中每个operation node的source mapping是source mapping schema 1.0 closed record,exact fields为schema_version、path、path_kind、line、column、end_line、end_column、call_text、callsite_id和assignment_targets。path_kind=project_relative时path必须解析到一个manifest-declared source file,声明的source span必须与immutable file bytes中的call_text exact相等;path_kind=unresolved时path=null。callsite_id必须能从path、四个coordinates和call_text的canonical material重新计算。Source mapping只用于只读provenance和Viewer定位,不参与operation replay。
以下 nested records 同样是 exact contract:
SelectionRef(kind=component)required且只允许kind、scene_node_id;SelectionRef(kind=entity)required且只允许kind、scene_node_id、entity_asset_id、entity_id。- Face/edge group fields和 integer constraints由第 10.2 节定义。
ConnectorBindingSpec.target(kind=topology_entity)required且只允许kind、entity_asset_id、entity_id、expected_source、flip;expected_source必须是 entity中exactmodel_output或model_topologysource record。Vertex target强制flip=false。ConnectorBindingSpec.source_model只允许graph_id、model_schema_version、artifact_hash;source_scene只允许scene_id、revision。Top-level只允许第 14.4 节示例中的九个 fields,全部 required,其中namerequired-nullable。ScenePresentationSpec的六个 top-level fields全部 required:schema_version、presentation_id、source_scene_id、appearances、node_overrides、cameras。其 nested exact records由第 12.4 节定义。SceneNode.order是 non-negative integer;每个 root和每组 siblings分别从 0连续编号。Boolean fields必须是 JSON boolean,不能接受 0/1 coercion。
所有 JSON integer,包括 byte length、output slot、order、count、ordinal、range和GLB integer metadata,都必须位于 [0, 9007199254740991];具体resource budget通常更小。Python与TypeScript validator不得接受会在JavaScript Number中丢失精度的integer。Float fields遵循finite binary64/JCS规则。
Phase A必须提交五个structural JSON Schema 2020-12 files:scene-1.0.schema.json、entities-1.0.schema.json、presentation-1.0.schema.json、connector-binding-1.0.schema.json和normalized-product-1.schema.json。JSON Schema不能表达JCS bytes、duplicate keys、hashes、ordering、cross-file references、cycles、ZIP或GLB rules,因此它不是完整validity authority。Phase A还必须提交normativescene-1.0-rules.json:每条semantic/package rule有stable lower-snake-case ID、applicable artifact、evaluation phase、JSON Pointer policy和precedence;Python/TypeScript validators必须按phase及rule ID的unsigned UTF-8 byte order报告第一个error。五个schemas、rule registry、两个profile specs及其hash-linked pseudocode、golden boundary/malformed corpus和exact JCS/GLB/ZIP vectors共同构成freeze source of truth。本文示例不得替代它们;全部cross-language tests通过后才能把状态从Proposed改为Frozen。
7.3 坐标与单位
Scene Schema 1.0 固定:
- 右手坐标系。
+Z为 up axis。- 默认长度单位为
mm。 - Node transform 必须是 rigid transform,第一阶段不支持 scale、shear 或 reflection。
Scene hierarchy 和 bounds 使用上述 CAD scene coordinates。GLB asset 必须保持标准 glTF 2.0 坐标约定:右手、+Y up、线性单位为 meter。Compiler 使用固定转换将 CAD local points 写入 GLB:
gltf_m = (cad_x_mm, cad_z_mm, -cad_y_mm) / 1000
每个 geometry/edge asset manifest 显式保存同一固定 asset_to_scene affine transform,将 GLB meter coordinates 转回 CAD scene millimeters:
cad_mm = (1000 * gltf_x_m, -1000 * gltf_z_m, 1000 * gltf_y_m)
asset_to_scene 允许 fixed unit scale,因此不属于 Scene Node 的 rigid transform。Manifest 中的 16 个数字按 row-major、column-vector convention 解释。Renderer 的顺序必须是 node_world_transform * asset_to_scene * asset_position。不能把非标准 mm/+Z buffer 标记为 model/gltf-binary,也不能同时对 mesh 和 scene root 重复做 axis conversion。
7.4 Transform 表示
为避免 matrix、quaternion 和 axes 三套表示互相不一致,Scene 1.0 只使用一种 canonical rigid transform:
{
"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]
}
Validator 必须检查:
- 所有数字 finite。
- 每个 axis 为单位向量。
- axes 两两正交。
z_axis与cross(x_axis, y_axis)一致。- transform 是 parent-relative,不是重复保存的 world transform。
Base numeric profile固定:axis norm error、pairwise dot absolute value和cross-product component error都必须<= 1e-12。所有scene-coordinate point/origin/bounds components必须位于[-1e12, 1e12] mm;所有GLB float32 values必须finite。Bounds containment和derived frame composition使用epsilon_mm = max(1e-9, 1e-12 * max(1, max_abs_coordinate));方向/axis比较仍使用1e-12。Non-negative length/area/volume不能通过epsilon接受负数。Validator不得使用平台默认isclose或随模型大小任意变化的hidden tolerance。
Renderer 可以在加载时派生 4x4 matrix。World transform 永远由 hierarchy composition 派生,不进入 canonical scene JSON。
8. Definition 与 Instance 模型
8.1 Definition
Definition 表示可复用的 product 或 geometry definition:
{
"definition_id": "definition/main/part/input-shaft",
"kind": "part",
"name": "Input Shaft",
"geometry_asset_id": "sha256:...",
"edge_asset_id": "sha256:...",
"entity_asset_id": "sha256:...",
"appearance_id": "appearance/evaluated/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"source": {
"kind": "product_model",
"root_id": "main",
"semantic_type": "Part",
"semantic_id": "input-shaft",
"graph_id": "gearbox",
"node_id": "node_...",
"output_slot": 0
},
"sdk_metadata": {}
}
支持的 kind 初始包括:
partassemblyshape
Assembly definition 通常没有 geometry_asset_id,其可见内容来自 descendant part instances。Standalone captured Solid 或 Compound 使用 shape definition。
Definition asset conditions 是:part 和 renderable shape 必须同时包含 geometry_asset_id、edge_asset_id、entity_asset_id、appearance_id。assembly 必须省略全部四个 asset/appearance refs,其可见内容只来自 descendants。每个 geometry/entity/edge ref 必须存在并形成一致的 definition-local triple;manifest 中不允许 unreferenced asset records。Base 1.0 不表达 generic group、intentionally non-renderable Part/Shape 或 shaded-only profile;这些需求以后通过 explicit capability 引入。
Definition source variants 的 exact fields 为:
kind=product_model:requiredkind、root_id、semantic_type、semantic_id、graph_id、node_id、output_slot。semantic_type只允许Part或Assembly,并分别要求 definitionkind=part或kind=assembly。kind=product_manual:required且只允许kind、root_id、semantic_type、semantic_id;只允许 scene sourcekind=manual,不参与 model/DAG provenance。kind=model_output:requiredkind、root_id、graph_id、node_id、output_slot。kind=imported:requiredkind、root_id、source_element_id。kind=manual:requiredkind、root_id、source_id。
Top-level/source compatibility固定:
Scene source.kind |
Definition source | Entity source | Connector source | Node source |
|---|---|---|---|---|
model |
product_model or model_output |
model_output or proven model_topology |
model_operation |
product_occurrence or shape_root |
imported |
imported |
imported_primitive or unbound |
null |
shape_root |
manual |
product_manual or manual |
unbound |
manual |
product_occurrence or shape_root |
Base 1.0不允许一份scene混合不同行的provenance variants。Imported/manual entities可用于selection/inspection但connector_binding_status不能是supported。
Definition/node/source compatibility还必须满足以下exact matrix:
product_model只用于kind=part|assembly,semantic_type必须分别为Part|Assembly;top-level source必须是model,nestedgraph_id必须等于top-levelsource.graph_id。product_manual只用于kind=part|assembly且semantic_type同样匹配;top-level source必须是manual。model_output只用于kind=shape且top-level source必须是model,nestedgraph_id必须等于top-levelsource.graph_id。imported只用于kind=shape且top-level source必须是imported;source_element_id必须等于对应SceneRoot.source_element_id。Imported scene固定connectors=[]。manual只用于kind=shape且top-level source必须是manual;nestedsource_id必须exact等于top-levelSceneSource.source_id。product_occurrencenode只能引用同root_id的part|assemblydefinition,其source必须是对应top-level row的product_model|product_manual。shape_rootnode只能引用同root_id的shapedefinition,其source必须是model_output|imported|manual。- 每个node source、referenced definition source和derived structural IDs中的
root_id必须相同。Model definition/entity/connector records中的每个nestedgraph_id必须等于top-level model graph ID;manual definition/connector nestedsource_id必须等于top-level manual source ID。
sdk_metadata保存definition owner的JSON-safe evaluated metadata。Part/Assembly owner metadata按第10.2节projection policy处理。Shape-level metadata通常不放在definition record,而放在entity snapshot的owning solid/entity record,避免definition与topology sidecar两份cache分歧。唯一例外是flattened Compound root:Compound自身不是Base entity kind,因此其projected Compound-level tags和metadata存入Shape definitionsdk_metadata的exact keyscompound_tags和compound_metadata;child Solid metadata仍留在entity records。两个keys在non-Compound definition中禁止。
8.2 Scene Node
Scene node 表示 hierarchy 中的一次 occurrence:
{
"node_id": "instance/main/stage1/input-shaft",
"parent_node_id": "instance/main/stage1",
"order": 3,
"definition_id": "definition/main/part/input-shaft",
"name": "Input Shaft Instance",
"transform": {
"origin": [0.0, 0.0, 18.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]
},
"visible": true,
"selectable": true,
"appearance_override_id": null,
"source": {
"kind": "product_occurrence",
"root_id": "main",
"component_path": ["stage1", "input-shaft"]
},
"sdk_metadata": {}
}
Scene 只保存 parent_node_id,不同时保存 mutable children 数组,避免 parent/children 两份结构产生分歧。Sibling 显示顺序通过 order 显式保存。Root node 的 parent_node_id 为 null。
Node hierarchy 必须:
- 无环。
- 每个 non-root parent 存在。
node_id全局唯一。- 每个 referenced definition 存在。
- 同一个 parent 下
order不重复。
Scene 1.0 node 是 product/shape occurrence,必须引用 definition。Camera 和 light 不伪装成 product node,使用各自 record 中的 parent-relative transform。
Canonical node.visible 和 node.selectable 向 descendants传播:effective_visible = node.visible && all_ancestor_visible,effective_selectable = node.selectable && all_ancestor_selectable && effective_visible。Workspace hide/isolate在 canonical effective visibility之后叠加且不写回 scene。appearance_override_id 不继承,只影响该 node自己引用的 renderable definition;assembly node的 override必须为 null。
Node source variants 的 exact fields 为:
kind=product_occurrence:required且只允许kind、root_id、component_path。Root occurrence的component_path=[]。kind=shape_root:requiredkind、root_id。
Product occurrence identity必须完全派生:node_id等于instance/<root_id>追加每个encoded component path segment;parent的component path必须恰为child path去掉最后一段,且parent_node_id等于其derived ID。Root occurrence的parent_node_id=null、order等于roots按root_id unsigned UTF-8 byte order排序后的ordinal。每组child order来自owner Assembly declaration order并从0连续编号。visible=true、selectable=true、appearance_override_id=null是没有presentation override时的exact defaults。Definition name等于owner Part/Assembly nullable name,Shape definition name固定null。Root Product occurrence name等于root Part/Assembly name;descendant occurrence先使用nullableComponent.name,为null时使用referenced Part/Assembly name,仍缺失则为null;Shape root occurrence name固定null。Compiler不合成display label。
Root occurrence instance/<root_id>.transform必须exact等于resolved SceneRoot.transform;它是root-local frame到scene frame的唯一serialized placement。每个descendant occurrence的transform只保存其owner Assembly中parent-relative Component placement,不预乘root或ancestor transforms。World transform按第7.4节从root到leaf依次composition。Standalone Shape root使用相同root transform rule。
sdk_metadata 是 occurrence-level evaluated metadata。当前 Component API 没有 metadata hook,因此 product occurrences 输出 {};保留该 required field 是为了让 component inspector 与未来 occurrence metadata extension 有稳定位置,不能把 definition metadata复制到这里。
8.3 Repeated Instances
同一个 Part 被多个 Component 引用时:
- 只生成一个 definition。
- 只生成一个 geometry asset。
- 为每个 component path 生成独立 scene node。
- 每个 node 保存自己的 transform、visibility 和 selection identity。
Nested repeated assembly 可以展开为多组 occurrence nodes,但 descendant part nodes 继续引用相同 part definition 和 geometry asset。
9. Geometry Asset Contract
9.1 Render Mesh 不等于 Collision Mesh
当前 private TriMesh 可以作为 Scene Compiler 的算法起点,但不应直接公开。需要新增独立的 triangle RenderMesh 和 line RenderEdgeMesh contract,原因包括:
- Collision mesh 只需要 positions 和 triangles。
- Triangle render mesh 需要 normals 和 face groups;edge overlay 需要独立 line buffer。
- Picking 需要 source entity ranges。
- Render asset 需要稳定 binary encoding 和 content hash。
- 后续 LOD、compression 和 quantization 不应影响 collision verifier。
推荐的内部不可变类型:
@dataclass(frozen=True)
class RenderMesh:
positions: np.ndarray
normals: np.ndarray
indices: np.ndarray
face_groups: tuple[RenderFaceGroup, ...]
bounds: Bounds3
linear_tolerance: float
angular_tolerance: float
@dataclass(frozen=True)
class RenderEdgeMesh:
positions: np.ndarray
indices: np.ndarray
edge_groups: tuple[RenderEdgeGroup, ...]
bounds: Bounds3
linear_tolerance: float
9.2 Geometry Asset Manifest
{
"asset_id": "sha256:...",
"uri": "geometry/sha256-....glb",
"media_type": "model/gltf-binary",
"byte_length": 123456,
"content_hash": "sha256:...",
"scene_local_bounds": {
"min": [-10.0, -10.0, 0.0],
"max": [10.0, 10.0, 50.0]
},
"asset_to_scene": [
1000.0, 0.0, 0.0, 0.0,
0.0, 0.0, -1000.0, 0.0,
0.0, 1000.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0
],
"tessellation": {
"linear_tolerance": 0.35,
"angular_tolerance": 0.22
}
}
asset_id 与 content_hash 第一阶段相同,均为 deterministic GLB bytes 的 SHA-256。保留两个字段是为了将来支持 logical asset identity,但第一阶段禁止二者不一致。Geometry asset 不引用 entity asset,也不保存 graph/topology provenance;同一 geometry bytes 可以被不同 model/source definitions 复用。Geometry URI 必须是 geometry/sha256-<hex>.glb,其中 hex 与 ID 相同。
scene_local_bounds不是pre-quantization kernel bounds;它必须从GLB POSITION accessor中的canonical float32 values直接应用asset_to_scene映射到scene coordinates后逐分量计算,即cad_mm=(1000*x,-1000*z,1000*y)。Package validator从validated immutable GLB bytes重复该计算并要求JCS numeric value exact相等。Edge asset采用相同规则。Entity properties中的kernel-evaluated bounds是另一份精度更高的CAD evidence,不要求与render bounds bitwise相等。
9.3 GLB 使用范围
第一阶段 triangle GLB 只承担 appearance-neutral binary render geometry:
- positions
- normals
- indices
Scene hierarchy、product provenance、CAD topology identity 和 viewer annotations 不交给标准 GLB scene graph 管理。这些内容由 scene.json 和 entity snapshot sidecar 管理。
原因是:
- Scene hierarchy 需要 product/component path identity。
- 同一 geometry asset 需要被多个 scene node 实例化。
- Face-level picking 需要 graph/topology provenance。
- CAD-specific metadata 不应被塞入大量不受约束的 glTF
extras。
Scene 1.0 triangle GLB profile 必须满足:
- 使用标准 glTF 2.0 meter、右手、
+Yup coordinates。 - 恰好一个 scene、一个 identity node、一个 mesh 和一个
TRIANGLESprimitive。 - Primitive 使用一个
POSITION、一个NORMAL和一个 index accessor。 - Index accessor 使用 unsigned 16-bit 或 unsigned 32-bit scalar。
- 不包含 glTF materials;definition/instance appearance 由 scene manifest 绑定。
- 不包含 images、textures、animations、skins、morph targets、cameras、lights、sparse accessors 或 nested URI。
- 只允许一个 GLB BIN-backed buffer,
buffer.uri必须 absent。 - 不允许
extras、unknown extensions、Draco 或 Meshopt。 - Face range 使用 primitive-local index ordinal;Scene 1.0 固定
mesh_index=0、primitive_index=0。
一个 primitive 的限制使 face ranges 存在唯一 global index-ordinal space,也保证 appearance-only change 不改变 GLB bytes。未来多 material slot 必须在新 capability 中定义 external primitive-slot mapping,不能偷偷向同一 1.0 asset 增加 primitives。
Triangle GLB 的 JSON object 必须等价于以下 exact closed skeleton;除替换 <...> values外不得增加/省略 fields。引号中的angle-bracket tokens只是本文metavariable,实际count、byteLength、byteOffset、componentType和bounds components必须是JSON integer/number,绝不能序列化为string:
{
"accessors": [
{"bufferView": 0, "componentType": 5126, "count": "<vertex_count>", "max": ["<x>", "<y>", "<z>"], "min": ["<x>", "<y>", "<z>"], "type": "VEC3"},
{"bufferView": 1, "componentType": 5126, "count": "<vertex_count>", "type": "VEC3"},
{"bufferView": 2, "componentType": "<5123-or-5125>", "count": "<index_count>", "type": "SCALAR"}
],
"asset": {"generator": "SimpleCAD Scene GLB Profile 1", "version": "2.0"},
"bufferViews": [
{"buffer": 0, "byteLength": "<12*vertex_count>", "byteOffset": 0, "target": 34962},
{"buffer": 0, "byteLength": "<12*vertex_count>", "byteOffset": "<normal_offset>", "target": 34962},
{"buffer": 0, "byteLength": "<index_bytes>", "byteOffset": "<index_offset>", "target": 34963}
],
"buffers": [{"byteLength": "<unpadded_bin_bytes>"}],
"meshes": [{"primitives": [{"attributes": {"NORMAL": 1, "POSITION": 0}, "indices": 2, "mode": 4}]}],
"nodes": [{"mesh": 0}],
"scene": 0,
"scenes": [{"nodes": [0]}]
}
Line GLB 使用同一 exact fields,但只有两个 accessors/bufferViews:accessor 0 是 POSITION float32 VEC3并包含 min/max,accessor 1 是 index SCALAR;primitive恰为 {"attributes":{"POSITION":0},"indices":1,"mode":1}。bufferViews[0].target=34962、bufferViews[1].target=34963。不存在 NORMAL accessor。
BIN layout 对 triangle 固定为 tightly packed position float32 triples、normal float32 triples、indices;line固定为 positions、indices。每段 start 4-byte aligned,段间和最终 BIN padding为 zero。byteOffset 即上述 prefix加alignment;即使为 0也必须按 skeleton输出。buffer.byteLength 是最后一个有效byte后的 unpadded length;GLB BIN chunk length向上4-byte对齐。Accessor不输出 byteOffset、normalized、name、sparse或其他 optional fields;bufferView不输出 byteStride或name。POSITION min/max从写入buffer的 canonical glTF float32 values逐分量计算并以JCS number输出;normal/index accessors省略min/max。
Index component type在vertex_count <= 65536时固定5123,否则5125;所有vertices必须至少被一个index引用,所以该条件等价于max_index <= 65535。空geometry禁止,因此不用5121。JSON chunk是上述object的RFC 8785 JCS UTF-8 bytes,尾部只用ASCII space补到4-byte;BIN只用zero补齐。GLB header和chunk headers按glTF 2.0 little-endian exact constants写出,total length必须精确等于两chunks。任何timestamp、path、toolchain version或random value都禁止进入GLB;toolchain只在scene manifestgenerator中。
第10.4节冻结vertex/entity/group ordering和normal policy。如果OCP tessellation只保证集合等价,writer必须先执行这些规范化规则;不能把偶然的Python/OCP iteration order作为contract。Phase A必须发布triangle与line各至少一个normative exact-byte golden GLB fixture及其SHA-256,Python和TypeScript preflight都必须接受;Phase B writer必须逐byte重现这些fixtures。
9.4 Normals
当前 tessellator 没有公开 normals。Scene Compiler 必须定义明确策略:
- 优先读取 kernel triangulation normals。
- 无可用 normals 时,根据 oriented triangle 计算 face normals。
- 跨 CAD face 默认不平滑,避免圆角边界或 sharp edge 被错误平滑。
- 同一 CAD face 内也不做跨 triangle averaging;使用第 10.4 节规定的 kernel corner normal 或 oriented triangle fallback。
- 所有 normals 必须 finite 且 normalized。
Position转换为canonical glTF float32后,任意两个corner bitwise相同或profile binary32 cross product恰为zero的triangle作为collapsed tessellation丢弃。Edge endpoint转换后bitwise相同的segment同样丢弃。丢弃发生在canonical sort和group range构造前;remaining index stream不得包含未引用vertex。若因此使某个face为空,Base 1.0 compilation失败;edge为空时按第10.2节标记render_status=degenerate并省略其group。Compiler不得输出NaN normal、zero normal、degenerate primitive或zero-count accessor来隐式降级。
9.5 CAD Edge Overlay
CAD Viewer 不能用 triangle boundary 代替 CAD edge。Triangle boundary 会显示 tessellation 内部对角线,也无法保持 analytic edge 语义。
需要单独对 BRep edges 做曲线离散化,保存:
- Edge polyline positions。
- Source edge reference。
- Boundary/seam 可选分类。
- Local bounds。
Edge asset 也使用 content-addressed GLB,但采用独立 Scene 1.0 line profile:恰好一个 LINES primitive,positions 使用 standard glTF coordinates,indices 将每条 curve 离散为 segment pairs。每个 edge group 引用同一 primitive 中连续、偶数长度的 index range。Viewer 使用 line pass 绘制 edge overlay。Silhouette 可以由 GPU 派生,但不能替代完整 CAD edge asset。
Edge asset manifest 与 geometry asset manifest 使用相同的 asset_id、media type、byte length、content hash、scene-local bounds 和 asset_to_scene 字段;其 URI 固定为 edges/sha256-<hex>.glb,tessellation 只有 linear_tolerance。Base 1.0 每个 renderable definition 都必须引用 edge asset;普通 shaded-only scene 需要后续单独 profile,不能通过省略 Base required asset 隐式降级。
10. Evaluated Entity Snapshot、Picking 与 Provenance
10.1 Selection Intent 与解析
Viewer MVP 支持五种显式 selection intent:
component:选择一个 scene occurrence,identity 是scene_node_id。solid:从命中的 face 解析其 owning solid entity。face:从 triangle primitive index 解析 face range。edge:从 CAD edge line segment 解析 edge range。vertex:对 entity snapshot 中的 vertex point 建立 runtime point-picking buffer,并使用 screen-space threshold 命中。
Viewer 不从 triangle adjacency 猜测 CAD face/edge/vertex。一次 viewport hit 按以下顺序解析:GPU draw/instance -> scene occurrence -> definition -> entity asset -> render range -> entity record -> source graph/topology provenance。Solid selection 使用 face record 的 parent_entity_ids 找到唯一 owning solid;vertex selection 的 point buffer只用于 picking/highlight,不作为默认可见模型内容。
component 与 solid 不是同义词。一个 component occurrence 可以引用 assembly definition而没有 solid,也可以在后续 multi-body capability 中拥有多个 solids。Repeated instances 共享同一 entity asset,但通过不同 scene_node_id 保持 occurrence identity。
10.2 Entity Snapshot Asset
Scene manifest 中的 entity asset record 只保存 transport metadata:
{
"entity_asset_id": "sha256:...",
"uri": "entities/sha256-....json",
"media_type": "application/vnd.simplecad.entities+json",
"byte_length": 34567,
"content_hash": "sha256:..."
}
entity_asset_id 与 content_hash 在 Scene 1.0 中相同。Payload 本身不包含 entity_asset_id,避免 self-hash cycle;其 JCS bytes 定义 content hash。一个 geometry asset 可以对应多个 source/provenance/metadata 不同的 entity assets。
Entity URI 必须是 entities/sha256-<hex>.json,其中 hex 与 ID 相同。Definition 引用 geometry 时 entity asset mandatory,不能用 missing sidecar 表示“只看 mesh”;不需要 metadata/picking 的客户端可以选择不下载,但 package 仍必须完整包含并验证它。
{
"schema_version": "1.0",
"definition_id": "definition/main/part/input-shaft",
"geometry_asset_id": "sha256:...",
"edge_asset_id": "sha256:...",
"geometry_engine": {
"name": "OpenCascade",
"version": "7.8.1",
"profile": "ocp-evaluated-properties-1"
},
"entities": [
{
"entity_id": "entity/solid/0",
"kind": "solid",
"parent_entity_ids": [],
"child_entity_ids": ["entity/face/0"],
"source": {
"kind": "model_output",
"graph_id": "gearbox",
"node_id": "node_...",
"output_slot": 0
},
"geometry": {"type": "brep_solid"},
"properties": {
"quality": "kernel_evaluated",
"bounds": {"min": [-10.0, -10.0, 0.0], "max": [10.0, 10.0, 50.0]},
"volume": 12500.0,
"surface_area": 4100.0,
"centroid": [0.0, 0.0, 25.0]
},
"sdk_connector_frame": null,
"render_status": "rendered",
"connector_binding_status": "not_applicable",
"semantic_binding_ids": [],
"evaluated_tags": ["solid.body"],
"sdk_metadata": {}
},
{
"entity_id": "entity/face/0",
"kind": "face",
"parent_entity_ids": ["entity/solid/0"],
"child_entity_ids": ["entity/edge/0"],
"source": {
"kind": "model_output",
"graph_id": "gearbox",
"node_id": "node_...",
"output_slot": 0
},
"geometry": {
"type": "plane",
"origin": [0.0, 0.0, 50.0],
"normal": [0.0, 0.0, 1.0],
"x_direction": [1.0, 0.0, 0.0]
},
"properties": {
"quality": "kernel_evaluated",
"bounds": {"min": [-10.0, -10.0, 50.0], "max": [10.0, 10.0, 50.0]},
"area": 400.0,
"centroid": [0.0, 0.0, 50.0],
"orientation": "forward"
},
"sdk_connector_frame": {
"origin": [0.0, 0.0, 50.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]
},
"render_status": "rendered",
"connector_binding_status": "supported",
"semantic_binding_ids": ["binding-..."],
"evaluated_tags": ["role.mounting_surface"],
"sdk_metadata": {"manufacturing": {"finish": "ground"}}
},
{
"entity_id": "entity/edge/0",
"kind": "edge",
"parent_entity_ids": ["entity/face/0"],
"child_entity_ids": ["entity/vertex/0", "entity/vertex/1"],
"source": {"kind": "model_output", "graph_id": "gearbox", "node_id": "node_...", "output_slot": 0},
"geometry": {"type": "line", "origin": [-10.0, 0.0, 50.0], "direction": [1.0, 0.0, 0.0]},
"properties": {
"quality": "kernel_evaluated",
"bounds": {"min": [-10.0, 0.0, 50.0], "max": [10.0, 0.0, 50.0]},
"length": 20.0,
"centroid": [0.0, 0.0, 50.0]
},
"sdk_connector_frame": {
"origin": [0.0, 0.0, 50.0],
"x_axis": [0.0, 1.0, 0.0],
"y_axis": [0.0, 0.0, 1.0],
"z_axis": [1.0, 0.0, 0.0]
},
"render_status": "rendered",
"connector_binding_status": "supported",
"semantic_binding_ids": [],
"evaluated_tags": [],
"sdk_metadata": {}
},
{
"entity_id": "entity/vertex/0",
"kind": "vertex",
"parent_entity_ids": ["entity/edge/0"],
"child_entity_ids": [],
"source": {"kind": "model_output", "graph_id": "gearbox", "node_id": "node_...", "output_slot": 0},
"geometry": {"type": "point", "position": [-10.0, 0.0, 50.0]},
"properties": {
"quality": "kernel_evaluated",
"bounds": {"min": [-10.0, 0.0, 50.0], "max": [-10.0, 0.0, 50.0]},
"position": [-10.0, 0.0, 50.0]
},
"sdk_connector_frame": {
"origin": [-10.0, 0.0, 50.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]
},
"render_status": "rendered",
"connector_binding_status": "supported",
"semantic_binding_ids": [],
"evaluated_tags": [],
"sdk_metadata": {}
},
{
"entity_id": "entity/vertex/1",
"kind": "vertex",
"parent_entity_ids": ["entity/edge/0"],
"child_entity_ids": [],
"source": {"kind": "model_output", "graph_id": "gearbox", "node_id": "node_...", "output_slot": 0},
"geometry": {"type": "point", "position": [10.0, 0.0, 50.0]},
"properties": {
"quality": "kernel_evaluated",
"bounds": {"min": [10.0, 0.0, 50.0], "max": [10.0, 0.0, 50.0]},
"position": [10.0, 0.0, 50.0]
},
"sdk_connector_frame": {
"origin": [10.0, 0.0, 50.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]
},
"render_status": "rendered",
"connector_binding_status": "supported",
"semantic_binding_ids": [],
"evaluated_tags": [],
"sdk_metadata": {}
}
],
"face_groups": [
{
"group_id": 0,
"entity_id": "entity/face/0",
"mesh_index": 0,
"primitive_index": 0,
"first_index": 0,
"index_count": 180
}
],
"edge_groups": [
{
"group_id": 0,
"entity_id": "entity/edge/0",
"mesh_index": 0,
"primitive_index": 0,
"first_index": 0,
"index_count": 12
}
]
}
Payload required fields 是 schema_version、definition_id、geometry_asset_id、edge_asset_id、geometry_engine、entities、face_groups、edge_groups。geometry_engine exact fields 是 name、version、profile;name固定为OpenCascade,version必须是exact kernel version且与manifestgenerator.ocp_version相同,不接受7.x。
每个entity required fields是entity_id、kind、parent_entity_ids、child_entity_ids、source、geometry、properties、sdk_connector_frame、render_status、connector_binding_status、semantic_binding_ids、evaluated_tags、sdk_metadata,不允许其他字段。kind只允许solid、face、edge、vertex。entity_id使用asset-localentity/<kind>/<ordinal>,ordinal grammar是0|[1-9][0-9]*;它只在entity_asset_id scope内唯一。Compiler必须按第10.4节deterministic profile生成ordinal,不得宣称该ordinal跨不同model revision durable。
Entity source 是 exact discriminated union:
kind=model_output:requiredkind、graph_id、node_id、output_slot。它证明entity来自该evaluated operation output,但不声称model artifact包含该subshape的durable topology identity。kind=model_topology:requiredkind、graph_id、node_id、output_slot、topology_kind、topo_id。kind=imported_primitive:requiredkind、source_element_id。kind=unbound:只允许kind。
Graph-backed evaluated BRep 的每个entity至少使用 model_output。该tuple是拥有整个definition BRep的shape output,通常是Part body source或standalone shape root;它不是随便选取的“最近operation”。只有exact source model.json自身包含能唯一关联该entity的topology record,且trusted compiler能把该record解析到当前evaluated subshape时,compiler才可把该entity升级为model_topology。topology_kind必须与entity kind对应为SOLID、FACE、EDGE或VERTEX,且(graph_id,node_id,output_slot,topology_kind,topo_id)必须能在artifact中exact查到。由OCP traversal counter、process-local map、shape hash或scene compiler临时分配的ID不得写成topo_id。同一definition asset可以混合两种model source variants,但不得把缺失evidence从相邻entity推断出来。imported_primitive和unbound不参与DAG cross-link或connector binding。
For imported roots,definition source_element_id来自SceneRoot.source_element_id。只有trusted importer还能提供exact source artifact内stable primitive mapping时,entity才使用对应imported_primitive.source_element_id;该ID不要求等于root element ID。没有这种evidence时entity必须使用unbound,不能从OCP traversal、canonical entity ordinal或geometry hash伪造imported primitive identity。
geometry 是 typed analytic-classification snapshot,不是可 replay BRep:solid 使用 brep_solid;vertex 使用 point + position;edge 使用 line、circle、ellipse、bspline_curve 或 other_curve;face 使用 plane、cylinder、cone、sphere、torus、bspline_surface 或 other_surface。每个 variant 只允许其数学定义所需字段;unsupported kernel detail 使用 other_curve/other_surface 的 required engine_type,不能把 opaque OCP object dump 进 JSON。Axis/direction vectors normalized,radii/lengths non-negative,所有坐标使用 definition-local scene coordinates 和 scene length unit。
Geometry variant exact fields:
type |
Required fields beyond type |
Optional/nullable fields |
|---|---|---|
brep_solid |
无 | 无 |
point |
position |
无 |
line |
origin, direction |
无 |
circle |
center, normal, x_direction, radius |
无 |
ellipse |
center, normal, x_direction, major_radius, minor_radius |
无 |
bspline_curve |
degree, rational, periodic, poles_count, knots_count |
无 |
other_curve |
engine_type |
无 |
plane |
origin, normal, x_direction |
无 |
cylinder |
origin, axis, x_direction, radius |
无 |
cone |
origin, axis, x_direction, reference_radius, semi_angle_degrees |
无 |
sphere |
center, axis, x_direction, radius |
无 |
torus |
center, axis, x_direction, major_radius, minor_radius |
无 |
bspline_surface |
u_degree, v_degree, u_rational, v_rational, u_periodic, v_periodic, u_poles_count, v_poles_count, u_knots_count, v_knots_count |
无 |
other_surface |
engine_type |
无 |
Vector/point fields 是恰好三个 finite numbers。degree 和所有 *_count 是 non-negative integers;实际 B-spline validity 还必须满足 engine/profile 定义的 degree/pole/knot constraints。major_radius >= minor_radius > 0,其他 radius positive;cone semi-angle 必须落在 engine profile 允许的 open interval。Geometry record 不保存 trimmed parameter ranges、control points 或 full BRep,因此它是可查询分类/属性快照而不是 interchange geometry。
properties按kind固定:solid requiredquality、bounds、volume、surface_area、centroid;face requiredquality、bounds、area、centroid、orientation;edge requiredquality、bounds、length、centroid;vertex requiredquality、bounds、position。quality在Base 1.0固定为kernel_evaluated;faceorientation只允许forward、reversed、internal、external。Edge orientation是face-use/coedge属性,不是deduplicated edge intrinsic property,Base 1.0不序列化它。这些值是指定engine/profile对evaluated BRep的快照,不保证仅凭mesh可重算。
bounds exact fields 是 min、max,二者都是 vec3 且逐轴 min <= max。Length、area、volume 分别使用 scene unit、scene unit squared、scene unit cubed;它们必须 non-negative。Centroid/position 必须落在 bounds 内,允许 validator 的 declared numeric tolerance。Base 1.0 不接受 record-specific arbitrary property bags;新增 engine properties必须升级 profile/schema 或使用 namespaced extension。
sdk_connector_frame是使用当前SDK selector/frame derivation对该entity预计算的unflipped definition-local rigid transform。solid必须为null;face和vertex必须是valid transform;edge在current SDK能取得non-zero start-to-end direction时是valid transform,否则为null。Closed edge的coincident endpoints不能使用arbitrary fallback伪造direction,因此其frame为null。Static Viewer只预览non-null frame。Vertex target的bindingflip必须为false,UI不显示flip control。未来若新增deterministic tangent-based closed-edge frame,必须更换geometry_engine.profile。
render_status只允许rendered和degenerate。Solid、face和vertex固定rendered。Edge在canonical float32 filtering后至少有一个non-zero segment时为rendered,否则为degenerate;degenerate edge保留properties/adjacency/picking inspector identity,但没有edge group、line hit target或visible overlay。
connector_binding_status只允许not_applicable、owner_not_part、source_not_model、frame_undefined、selector_ambiguous、selector_unstable、supported。Solid固定not_applicable。Face/edge/vertex只有在owner是model-backed Part、source是model_output或model_topology、frame non-null、当前SDK生成的geo_exact selector在owner body全部同kind candidates中恰有一个score<= 1e-4且所有其他scores> 1e-4、并且clean replay后重新编译仍映射回同一canonical entity时才可标记supported。其余状态按上述优先顺序选择第一个适用reason。当前public resolver尚未拒绝ties,因此backend在advertise connector apply capability前必须增加同一unique-match validation;scene compiler不能仅因best candidate通过threshold就写supported。
拓扑adjacency必须闭合且满足exact cardinality:solid的parents必须为空、children必须是至少一个face;face必须恰好一个solid parent、children全部是至少一个edge;edge必须有一个或多个face parents、children是一个或两个vertices;vertex必须有一个或多个edge parents且children为空。每一条parent relation必须有reciprocal child relation。所有adjacency arrays按unsigned UTF-8 byte ordersort且无重复。Solid intent命中face后使用其唯一solid parent。Standalone wire/shell和shared-face compsolid不属于Base Scene 1.0 render root;遇到无法投影为上述manifold solid ownership的root必须显式失败,不能由unregistered extension绕过。
semantic_binding_ids和evaluated_tags是Semantic Binding Store对当前evaluated entity派生的只读cache。Compiler必须验证每个binding存在于exact source graph且runtime target确为该entity。Browser对model_topology entity还能把binding topology target与entity source做exact cross-check;对只有model_output的entity,browser只能验证binding ID、producer graph和schema存在,不能把cache误标为artifact-proven topology evidence。
sdk_metadata只投影evaluated metadata中不属于compiler/runtime internals的top-level keys。Base exclusion set固定为graph、topo_ref、track、source_sketch、sketch_solve、sketch_promotion以及所有以_开头的keys;这些值可能包含process-local topology IDs或runtime objects,必须省略而不是hash入entity identity。geo、std.*和其他non-reserved user keys保留。投影值必须是JSON object,递归允许null、boolean、string、finite number、array和string-keyed object;tuple规范化为array,未知Python/OCP value、bytes、set、non-string key和cycle必须使compilation失败并报告metadata path,不能静默repr()。Profile改变exclusion set必须更换profile ID。Object keys按JCS处理,metadata size计入package budgets。
Model-backed compilation只接受exact model artifact clean replay后的projected metadata。Compiler必须先replay exact model bytes,按root source tuple取得replayed values,再逐个比较caller runtime Part/Assembly/Material/Compound/Solid及其face/edge/vertex的projected tags/metadata与clean replay;任一不同以model_runtime_metadata_unreplayable失败。Connector metadata不在Scene 1.0 projection内。Canonical scene始终使用replayed projection,不使用无法由model artifact重建的runtime mutation。Manual/imported scenes可以投影其input metadata,因为其source identity不是replay promise。直到下述canonical operation实现并迁移stdlib,作者若需保存model metadata不能在build返回后调用unrecordedset_metadata()改变scene revision。
Replayable metadata operation contract固定为public keyword-only set_model_metadata_rvalue(*, owner, key, value)和graph op set_model_metadata。owner只允许Solid|Compound|Part|Assembly|Material;Face/Edge/Vertex和Connector metadata不在Base contract。key必须是non-empty Unicode string,不得以_开头或属于Base exclusion set,UTF-8长度不超过structural ID budget。value必须通过第10.2节JSON-safe normalization,normalized value连同key完整写入node params。Operation functional-copy owner及其metadata map,以normalized value replace同key的旧值并返回与input相同semantic owner type;不删除key、不原地修改input,output count固定1。Shape owner使用semantic clone保留BRep/tag/topology evidence,Product/Material owner使用immutable reconstruction保留all fields。Replay执行同一normalization/replacement且output lineage指向该node slot 0;Model Schema 2.0 exporter/importer/replay allowlist和translator capability registry必须注册exact op,未识别它时strict replay失败。Phase B迁移所有stdlib post-operation metadata writes到该API;old artifacts不会被悄悄升级,仍按runtime/replay mismatch fail closed。
Face/edge group record exact fields都是group_id、entity_id、mesh_index、primitive_index、first_index、index_count,不允许null或额外字段;除entity_id外的五个fields都必须是non-negative integers。mesh_index=0且primitive_index=0。Face groups与face entities一一对应,每个face恰有一个positive group。Edge groups只与render_status=rendered的edges一一对应,每个rendered edge恰有一个positive group;degenerate edge恰有zero groups。group_id在各自array中从0连续递增。Face groups必须按first_index排序、互不重叠,并精确partition triangle primitive的全部indices;每组index_count是3的倍数。Edge groups必须精确partition line primitive的全部indices;每组index_count是2的倍数。first_index和index_count是primitive index accessor的element ordinal,不是byte offset。Vertex没有serialized render range。任何face无法产生至少一个non-degenerate triangle时Base 1.0 compilation失败;degenerate edge不使整个solid失败。
10.3 Selection Identity
Entity selection 的完整 external identity 是:
(scene_id, revision, scene_node_id, entity_asset_id, entity_id)
Component selection identity 是 (scene_id, revision, scene_node_id)。内部 scene/presentation references 不附加 scene/revision,使用 discriminated SelectionRef:
{"kind": "component", "scene_node_id": "instance/main/stage1/input-shaft"}
{
"kind": "entity",
"scene_node_id": "instance/main/stage1/input-shaft",
"entity_asset_id": "sha256:...",
"entity_id": "entity/face/0"
}
scene_id 和 revision 只在 workspace、API 或 command envelope 中附加,避免 canonical scene manifest self-reference。不能把 OCC hash、Python object ID、当前 process enumeration index 或 topo_id 单独作为 durable identity。跨 model regeneration 优先依赖 semantic binding/query intent;无法证明同一 target 时必须 invalidated,不能用 geometry-nearest 自动替换 critical selection。
当前Viewer显示的UNIQUE QL SELECTOR只是从sidecar中evaluated tags、analytic type、exact measure/center facts和source-output hints生成的便利draft,并且只在这些facts能隔离一个同kind entity时提供。它不属于上述selection identity,不是canonical feature-selection intent,也不能未经重新验证跨revision持久化或自动应用。
10.4 Deterministic Profile 1
Base 1.0 固定 generator.profile="scene-1.0-ocp-glb-2" 和 entity geometry_engine.profile="ocp-evaluated-properties-1"。这两个 profile 的 normative rules 是:
- Phase A提交normative
scene-1.0-ocp-glb-2.profile.json和ocp-evaluated-properties-1.profile.json。前者固定OCP meshing/discretization API、all flags、parallelism、normal/writer implementation和下面的numeric algorithm;后者固定analytic classification OCP APIs、location/orientation handling、axis conventions、B-spline constraints、engine type strings、property algorithms、metadata projection、selector scoring和connector frame tie-breaker。Prose中的“current SDK rule”不能替代这两个machine-readable profile和linked pseudocode。 toolchain_hashdescriptor是closed JCS object,包含两个profile bytes hashes,以及SimpleCAD scene/compiler/product/selector modules、Python executable、OCP binding shared libraries和transitive native kernel libraries的SHA-256 records;records按logical name排序,绝对path、mtime和host name不进入descriptor。Hash是descriptor JCS bytes的SHA-256。Exact version fields只用于展示,不代替hash。- 所有subshape先消除location到definition-local coordinates。Edge wrapper在properties、discretization、selector和frame derivation前强制使用OCP
FORWARDorientation;face orientation保留,因为每个face只有一个solid owner。Base 1.0拒绝被多个solids共享的face/compsolid topology,不靠traversal选择owner。Edge-face incidence可以many-to-many,但不保存coedge orientation。 - Numeric primitive
f32(x)固定为IEEE-754 binary32 round-to-nearest-ties-to-even,再把0x80000000改为0x00000000;overflow/non-finite失败。Binary32 boundary fixture使用exact input binary64 bit patterns。Cross product在binary64中按p1=f64(a1*b1)、p2=f64(a2*b2)、c=f64(p1-p2)逐项执行,禁止FMA;norm使用binary64 sum-of-squares fixed x/y/z order和correctly-rounded square root。Normalize后每component经f32一次,若result norm不在[1-1e-6,1+1e-6]则失败。Collapsed test使用converted positions的binary32 values提升到binary64后执行上述cross;cross三components bitwise为positive/negative zero即collapsed。Profile vectors覆盖subnormal、halfway tie、cancellation、overflow和negative zero。 - 每个face先生成geometry-only canonical triangle block:不含entity ID/source/tags/metadata,包含sorted canonical
(position bits,normal bits)vertices和oriented triangles。每个rendered edge生成geometry-only canonical segment block。render_key=SHA-256(kind byte || block bytes)。Triangle/line GLB按(render_key, block bytes)排列blocks;equal blocks顺序可交换且产生相同GLB bytes。Metadata、provenance、binding或tag-only change因此不能改变geometry/edge asset hash。 - Triangle block中kernel corner normal可用时按profile读取并normalize;否则使用oriented triangle normal。禁止跨triangle/CAD face averaging。每个oriented triangle只允许cyclic rotation,不允许reversal;选择index triple lexicographic最小的cyclic rotation,再sort。Edge block中endpoint indices取
(min,max),segment pairs sort并deduplicate;different CAD edges即使geometry相同仍拥有different sidecar groups,但重复的block bytes不改变GLB block stream。 - Entity canonical labeling先以
kind + geometry/properties/source/frame/status/bindings/tags/projected metadata + render_key的JCS bytes hash建立partition,再使用sorted parent/child partition-label multisets反复refine到stable。仍有non-singleton cells时只在每个cell内执行lexicographic individualization/backtracking;全sidecar最多探索1,000,000 states,超出则compilation以entity_canonicalization_budget_exceeded失败。该有界algorithm和tie comparator在profile pseudocode中逐步固定,不允许实现选择另一graph canonicalizer。按canonical permutation和kind ordersolid < face < edge < vertex分配IDs。 - Equal render blocks的range slots按canonical entity ID分配;因此entity sidecar mapping确定,但GLB bytes不依赖entity canonical order。Face/edge groups按
first_index输出,entities按entity ID输出;all set-like entity arrays按unsigned UTF-8 byte ordersort。GLB array/buffer order遵循第9.3节。 - Default appearance exact values是:
name=null、source=null、base_color=[0.72,0.75,0.78,1.0]、metallic=0.0、roughness=0.55、alpha_mode="opaque"、double_sided=false、edge_color=[0.08,0.09,0.1,1.0]、sdk_metadata={}。 - Phase A提交closed
normalized-product-1.schema.json。Part record包含body source identity、name、material完整JSON-safe record、declaration-order connector records和projected Part metadata;Assembly record包含name、declaration-order component records、connectors、constraints、grounded IDs和projected metadata。每个component record exact包含component_id、nullablename、definition ref和local placement,declaration order保留。Connector records不含runtime_metadata。Compiler比较其JCS bytes;任一field不同即typed semantic ID collision。禁止用mesh bytes或“至少相同”heuristic判定equivalence。
Profile任何normative rule变化都必须使用新profile ID并造成cache miss。Characterization tests必须覆盖symmetric entity、bounded canonicalization failure、reversed kernel traversal、negative zero、normal fallback、closed/degenerate edge、shared-face rejection和duplicate geometry with different metadata/provenance。
11. Appearance
Product Material 当前同时包含物理属性和基础 RGB。Scene 不应直接把物理 material schema 扩展成 renderer shader schema,而应新增 Appearance:
{
"appearance_id": "appearance/evaluated/012345...",
"name": "Steel",
"source": {
"kind": "product_material",
"root_id": "main",
"material_id": "steel"
},
"base_color": [0.55, 0.58, 0.62, 1.0],
"metallic": 0.0,
"roughness": 0.55,
"alpha_mode": "opaque",
"double_sided": false,
"edge_color": [0.08, 0.09, 0.1, 1.0],
"sdk_metadata": {}
}
appearance_id 最后一段是去掉 appearance_id 字段后,对完整 JCS Appearance record 求 SHA-256 的 lowercase hex。由于 source scope 也参与 hash,不同 roots 中同名但不同来源/内容的 materials 不会碰撞。完全相同的 source-scoped appearance record 才能复用一个 ID。
Appearance source nullable;non-null variants 是:kind=product_material required kind、root_id、material_id,kind=presentation required kind、presentation_id、appearance_name。product_material只允许top-level model|manual scene,root_id必须匹配至少一个使用该Material的Part definition root;imported scene不能包含它。presentation只在presentation_source存在时允许,presentation_id必须与其相等且appearance_name必须解析到exact authoring record。Compiler neutral default使用source=null,其complete record仍通过content hash获得稳定identity。name可以为null。sdk_metadata保存source Product Material的JSON-safe metadata;presentation/default appearance使用{},不把Part metadata复制到material inspector。
base_color和edge_color恰好四个[0,1]values,按sRGB color + linear alpha编码;renderer在lighting calculation前转换RGB到linear。metallic、roughness在[0,1]。Base 1.0alpha_mode固定为opaque且两个alpha都必须1.0;不冻结cross-renderer blend sorting,也不支持alpha mask、texture或renderer-specific shader parameters。
Scene Compiler 的默认映射:
- Product material RGB ->
base_color.rgb。 - 未指定 RGB -> 第 10.4 节固定的 neutral CAD color
[0.72, 0.75, 0.78]。 - Alpha 固定为 1.0。
- Metallic/roughness始终使用
0.0/0.55deterministic defaults,因为当前Product Material没有explicit PBR fields;不根据material name猜测。 - Appearance
source保留 root-scoped physical material provenance。
后续可以增加明确的 product-to-appearance assignment API,但不能根据 steel、aluminum 等名称启发式猜测 PBR 参数并宣称为 canonical material fact。
Scene 1.0 的 node-level required-nullable field 统一命名为 appearance_override_id,用于实例显示 override。没有 override 时值为 null;非 null 时必须引用 manifest appearances 中存在的 record,只影响 presentation,不修改 Part.material。
12. Camera、Light 与 Presentation
12.1 Camera
Scene 可以保存作者发布的 named camera:
{
"camera_id": "camera/gearbox-published-view/isometric",
"name": "Isometric",
"projection": "perspective",
"parent_node_id": null,
"transform": {
"origin": [120.0, -160.0, 110.0],
"x_axis": [1.0, 0.0, 0.0],
"y_axis": [0.0, 0.0, 1.0],
"z_axis": [0.0, -1.0, 0.0]
},
"vertical_fov_degrees": 35.0,
"near": 0.1,
"far": 100000.0
}
Camera 自己保存 parent-relative rigid transform,parent_node_id 可以引用 scene occurrence,也可以为 null。Camera local -Z 是 forward,local +Y 是 up。Perspective camera 必须满足 0 < vertical_fov_degrees < 180、near > 0、far > near;Orthographic camera 使用正数 vertical_span,不能同时保存 FOV。Near、far 和 vertical span 使用 scene length unit。
如果 scene 没有 camera,Viewer 应根据 world bounds 执行 deterministic fit-to-view,而不是要求 exporter 写入一个假的 camera。
12.2 Light(Base 1.0 Reserved)
第一阶段 Viewer 使用实现定义的 studio lighting。Base Scene 1.0 要求 lights=[],不冻结半完整的 light record。Author-controlled directional、point 和 environment light 以后通过 required extension 引入,extension 必须定义 transform、color space、intensity unit 和 deterministic ordering。
12.3 Background 与 Environment
默认 background/theme 属于 Viewer preference。只有作者明确发布 presentation 时才进入 scene。Viewer preference 不应因为加载另一个 model 而永久被 scene 覆盖。
12.4 ScenePresentationSpec
Presentation spec 是独立、版本化、JCS-serializable 的 authoring input。最小结构:
{
"schema_version": "1.0",
"presentation_id": "gearbox-published-view",
"source_scene_id": "gearbox-demo",
"appearances": [
{
"name": "Highlight",
"base_color": [1.0, 0.55, 0.05, 1.0],
"metallic": 0.0,
"roughness": 0.45,
"alpha_mode": "opaque",
"double_sided": false,
"edge_color": [0.12, 0.06, 0.0, 1.0]
}
],
"node_overrides": [
{
"node_id": "instance/main/stage1/input-shaft",
"visible": true,
"appearance_name": "Highlight"
}
],
"cameras": []
}
BaseScenePresentationSpec只允许上述schema_version、presentation_id、source_scene_id、appearances、node_overrides和cameras字段;未知字段fail closed。source_scene_id必须exact等于compile targetscene_id。Appearance names、camera names和node overridenode_id分别唯一。Compiler将每个authoring appearance与presentation source scope组合、生成evaluated Appearance及content-derivedappearance_id,再把appearance_nameresolve为scene node的appearance_override_id。Appearance override只能target renderable Part/Shape,Assembly target失败;visibility override可target任意node。Presentation references stable scene node IDs或SelectionRef,不直接引用triangle ordinal、OCC hash或workspace-only GPU pick ID。Compiler必须验证所有refs;找不到target直接失败。Migration tool可在canonical compilation之外报告问题,但compiler不产生warning scene,也不从上一个evaluated manifest猜测presentation intent。
Presentation appearance exact fields是name、base_color、metallic、roughness、alpha_mode、double_sided、edge_color,其中alpha_mode="opaque"且alpha=1。Presentation camera exact fields是name、projection、parent_node_id、transform、near、far以及projection-specificvertical_fov_degrees或vertical_span;它不包含camera_id。Compiler按第16.1节统一segment encoder将authoring name编码为camera/<presentation_id>/<encoded-camera-name>。Node override只允许node_id、optionalvisible和optionalappearance_name,且后二者至少存在一项。所有nested records使用第7.2节unknown-field/null policy。Base 1.0只拥有named views,不定义active/default camera;workspace选择的当前camera不进入canonical scene。
13. Annotation、Dimension 与 Measurement
13.1 Annotation(Base 1.0 Reserved)
Scene annotation 是 presentation object,不是 CAD feature,潜在类型包括:
- Label。
- Callout。
- Section marker。
- Read-only dimension display。
- Warning/diagnostic marker。
但 Base Scene 1.0 要求 annotations=[],ScenePresentationSpec base schema 也不接受 annotations。Annotation extension 必须定义 annotation ID、kind-specific payload、units、style、SelectionRef target union、source kind 和 deterministic ordering。Source kind 至少包括:
authoredderiveddiagnostic
13.2 Measurement 精度边界
Mesh-only Viewer 可以可靠提供:
- Triangle/vertex coordinate。
- Approximate point distance。
- Approximate angle。
- Mesh bounds。
- Section visualization。
Mesh-only Viewer 不能宣称提供任意 analytic BRep exact measurement。圆柱半径、曲面距离、精确 edge length 等能力需要以下至少一项:
- 后端 CAD query/measurement service。
- Optional BRep asset 和 browser CAD kernel。
- Exporter 预计算的 typed exact properties。
任何提供 measurement 的 UI 都必须区分 exact、derived 和 mesh_approximate,不能把 mesh 测量显示为无误差的 CAD measurement。Measurement 在 Phase D,不是 Phase C Viewer MVP 的退出条件。
14. Assembly 与 Kinematics
14.1 MVP
MVP Scene 保存 solved/evaluated component transforms。Viewer 不重新运行当前 Python assembly solver。
这支持:
- Product tree。
- Part isolate/hide/show。
- Repeated instance selection。
- Exploded view。
- Current pose display。
14.2 后续 Kinematic Projection
后续 scene capability 可以从 assembly constraints 派生只读 kinematic data:
- Revolute joint axis、current angle、limits。
- Prismatic axis、current distance、limits。
- Coupling relation。
- Connector frames。
该 projection 用于 Viewer motion preview,不成为 assembly constraint canonical owner。复杂闭环或 solver-dependent motion仍应由后端求解并发送 transform updates。
14.3 Evaluated Connector Snapshot
Scene Base 1.0 的 connectors 保存 Product Part/Assembly connectors 的 evaluated、只读 projection:
{
"connector_snapshot_id": "connector/main/part/input-shaft/mount-face",
"owner_definition_id": "definition/main/part/input-shaft",
"connector_id": "mount-face",
"name": "Mount Face",
"anchor_kind": "geometry",
"local_transform": {
"origin": [0.0, 0.0, 50.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]
},
"target": {
"entity_asset_id": "sha256:...",
"entity_id": "entity/face/0"
},
"source": {
"kind": "model_operation",
"graph_id": "gearbox",
"node_id": "node_connector",
"output_slot": 0
},
"sdk_metadata": {}
}
local_transform 是 owner definition-local evaluated connector frame,不是 occurrence/world transform。Viewer 在某个 component occurrence 中显示 connector 时组合 node_world_transform * local_transform。一个 definition connector 自动出现在其所有 occurrences;Editor 必须明确提示“编辑 definition 将影响 N 个 instances”,不能误导为只修改当前 occurrence。
字段条件:
anchor_kind=geometry:owner definition必须是part;targetrequired,其exact fields是entity_asset_id、entity_id;asset必须由owner definition引用,entity kind必须是face、edge或vertex;forwarded_from必须省略。Base 1.0只接受能证明target属于owner Part body的geometry connector。Foreign-body target、standalone Shape connector或Assembly geometry connector compilation失败,不把不明坐标系的frame标记为owner-local。anchor_kind=placement:owner definition必须是part或assembly;target和forwarded_from都必须省略。anchor_kind=forwarded:owner definition必须是assembly;forwarded_fromrequired,target必须省略。forwarded_fromexact fields是source_component_id、source_definition_id、source_connector_id、source_connector_snapshot_id和nullableoffset;offset使用canonical rigid transform。前两个Product-local IDs保留authoring audit,后两个scene IDs使package validator能验证target connector。source为 nullable;非 null 时 exact variantkind=model_operation包含kind、graph_id、node_id、output_slot,kind=manual只包含kind、source_id。sdk_metadata在Base 1.0固定为{}。当前Connector_metadata既未进入Connector.to_dict()也未被model replay保存,因此compiler不得投影它;future connector metadata需要recorded connector operation和profile/schema revision。
Compiler 必须确认 geometry connector 的 resolved target 与 entity source provenance 一致,并确认 local_transform 按 SDK 现有规则求值:face origin/normal、edge midpoint/direction、vertex point/identity axes,再应用 flip 已产生的方向。Scene 不复制 flip authoring intent;该 intent 保留在 model connector operation 中。
Existing Product connector不是可以静默省略的optional snapshot。若geometry selector无法unique resolve、target不属于owner、sdk_connector_frame=null(例如current SDK closed edge)或placement/forwarded frame求值失败,整个compilation以stableconnector_frame_undefined或更specific rule失败。SceneConnector.local_transform保持required non-null。Phase A/Compiler tests必须包含一个serialized/replayed closed-edge connector并证明它明确失败;未来profile定义closed-edge tangent frame后才可导出该connector。
Forwarded snapshot必须满足以下可独立验证的resolution contract:
source_connector_snapshot_id必须引用恰好一个connector,其owner_definition_id=source_definition_id且connector_id=source_connector_id。- 对owner assembly definition的每个occurrence node,都必须存在恰好一个direct child node,child的
source.component_path等于owner occurrence path追加source_component_id,并引用source_definition_id。所有这些child nodes的local transform必须相同;否则definition equivalence已经被破坏,package无效。 - Forwarded
local_transform必须等于source child local transform * source connector local_transform * offset;offset=null按identity处理。Composition按第7.4节row-major/column-vector rigid-transform顺序,在binary64中以profile固定multiply/add order且禁止FMA;origin逐component用epsilon_mm比较,axis逐componentabsolute error<=1e-12。 - 以
connector_snapshot_id -> source_connector_snapshot_id建立directed graph,必须无self-edge、无cycle且最大chain depth不超过resource profile。Source connector可以是geometry、placement或另一个valid forwarded snapshot。 source_component_id必须是owner assembly direct component,而不是descendant path或任意matching occurrence。缺失、多个source child、definition mismatch、dangling snapshot或frame mismatch都使package失败。
Model-backed scene中,connector source必须是model_operation并能在exact graph中找到valid output slot;manual scene中必须是manual。Imported scene可以使用source=null,但不能伪装为model operation。每个owner definition内connector_id唯一;connector_snapshot_id必须等于第16.1节owner kind/semantic ID/connector ID推导的structural ID,而不是任意label。
model_operation的canonical producer不是独立Connector creation node,而是将该connector首次引入其owner Product definition的unique graph output:attached Part使用make_add_connector_rpart,attached Assembly使用make_add_connector_rassembly,forwarded connector使用make_forward_connector_rassembly,三者output_slot在Model Schema 2.0均固定为0。Compiler必须证明producer input owner尚无该connector_id、producer output owner恰有equal connector record,且从producer到selected owner definition output的replay lineage没有删除或替换该record;缺失或多个candidate以connector_source_unproven失败。Geometry/placement connector creation operation只提供anchor intent,不写入SceneConnector.source。Manual scene的connector source.source_id必须exact等于top-level manual SceneSource.source_id;connector自身identity由connector_snapshot_id提供,不另生成path、UUID或sequence-derived source ID。
14.4 Connector Binding Authoring
Viewer/Editor 通过独立 ConnectorBindingSpec 请求创建 connector。Base command 使用 JCS,exact top-level fields 为:
{
"schema_version": "1.0",
"binding_id": "bind-input-shaft-mount",
"source_model": {
"graph_id": "gearbox",
"model_schema_version": "2.0",
"artifact_hash": "sha256:..."
},
"source_scene": {
"scene_id": "gearbox-demo",
"revision": "sha256:..."
},
"owner_definition_id": "definition/main/part/input-shaft",
"selected_occurrence_node_id": "instance/main/stage1/input-shaft",
"connector_id": "mount-face",
"name": "Mount Face",
"target": {
"kind": "topology_entity",
"entity_asset_id": "sha256:...",
"entity_id": "entity/face/0",
"expected_source": {
"kind": "model_output",
"graph_id": "gearbox",
"node_id": "node_...",
"output_slot": 0
},
"flip": false
}
}
所有 top-level fields required;只有 name nullable。source_model、source_scene 和 topology expected_source 不允许额外字段。binding_id 使用 caller-authored Scene ID grammar。connector_id 使用现有 Product identifier grammar [A-Za-z][A-Za-z0-9_.:-]*,嵌入 structural ID 时按第 16.1 节 percent encode。Base target.kind=topology_entity 只接受 face、edge、vertex entities,因为当前 SDK connector operations只对这些 topology kinds 定义 frame derivation。
后续 feature_output target variant 可以引用 graph_id、node_id、output_slot 和一个版本化 frame derivation rule,例如 operation context frame;它要求新增 canonical model operation,不能在 Viewer 中把一次 evaluated transform 偷换成 durable feature binding。Solid/feature node 可用于 DAG 定位,但在该 operation 落地前不能保存为 geometry connector。
Backend apply 流程必须原子执行:
- 从受控registry取得或随request接收完整source
CompiledScenePackage,执行full package validation,并验证当前model exact-byte hash、graph ID、scene ID和revision全部匹配command preconditions。 - 验证 selected occurrence 引用 owner definition,entity asset 和 entity record 仍存在。
- 比较 entity
source与expected_source的全部字段,拒绝 stale 或 unbound/imported target。 - Replay exact model,并使用source scene中冻结的roots、compile options和toolchain profile重新编译pre-mutation scene;其revision和全部referenced asset hashes必须与command source scene相等,否则返回conflict。
- 从该次trusted compilation产生的ephemeral resolution index中,用
(owner_definition_id, entity_asset_id, entity_id)取得唯一 live face/edge/vertex。Index不是artifact,也不能通过geometry-nearest search替代;zero或multiple matches都失败。 - 从resolved live entity建立当前SDK canonical
GeometryRef,调用对应face/edge/vertex connector operation,并把connector加到owner Part definition;重复connector ID失败。 - 导出新的model artifact,再以同一stable
scene_id、root descriptors和compile options重编译scene。 - 返回新model hash、新scene revision、connector snapshot ID和
SceneValidationReport;report不进入新scene revision。
expected_source 是 provenance/staleness precondition,不是单独的subshape selector。Target entity还必须有connector_binding_status="supported"。model_topology可以提供更强artifact evidence,但Base 1.0不要求它;model_output target依靠exact source model + exact source scene + deterministic recompilation index完成revision-local resolution。Backend不得直接用topo_id、OCP hash、runtime traversal ordinal或non-unique selector score猜目标。若当前toolchain无法重现source revision,command必须失效并要求重新选择。
Recompilation recipe来自validated source manifest:root occurrence及其definition source恢复ordered SceneRoot descriptors,其中root_id来自root occurrence source、value来自exact definition/model output、transform来自root occurrence的exact transform;top-level compile_options提供resolved options,source提供exact model precondition。标记为self-contained model package时,source model和所有manifest-declared project-relative Python sources必须已经在package中;registry fallback只适用于明确发布为non-self-contained的generic package或optional presentation。若presentation_source存在,backend还必须取得hash匹配的exact presentation JCS bytes。缺少任一root output、source model、presentation artifact或matching complete generator/toolchain时,backend不能advertise cad_verified,apply返回unsupported/conflict而不是省略输入后尝试“近似重编译”。Binding command本身不复制这些可能很大的artifacts。
普通静态 Viewer 可以只导出 .json command;有 backend 的 Editor 可以直接提交。无论 transport 如何,成功前不得本地篡改 canonical scene/model;可以用 workspace-only optimistic gizmo 显示 pending connector。Revision/hash conflict 必须提示用户 reload/reselect,不能自动将 command 迁移到 geometry-nearest entity。
当前 SDK 能对一个 live Part value执行 geometry connector + add_connector_rpart,但 public serializer不能从 standalone uploaded model.json恢复一个可继续append operations、替换captured results并原子rollback的 editable GraphSession。它也不能自动把 nested immutable Part的新版本重接到所有parent assemblies。因此 Base binding command可以先稳定生成/验证,但hosted apply在新增 import_model_session/model transformation transaction前一律unsupported。该transaction必须adopt并strictly validate imported graph、append canonical operations、替换result IDs、重建affected Product operations、export新model,并在任一步失败时保持原artifact不变。
同进程保留原始 live ModelResult.session 的实验性apply也必须使用显式transaction API后才能advertise capability,不能直接mutate session并假装atomic。Nested Part apply还必须确定性重建affected parents并保持unrelated graph intent。Viewer根据backend capability response禁用unsupported apply,不能把scene occurrence局部添加connector当作成功。
Assembly connector authoring 使用 placement connector 或 forwarded connector 的独立 command variant,不从 assembly descendant face 隐式创建 geometry connector;该 variant 不属于 Base topology_entity binding。
15. Scene Package
推荐发布格式扩展名为 .scene.zip,本质是 ZIP package:
gearbox.scene.zip
├── scene.json
├── geometry/
│ ├── sha256-abc.glb
│ ├── sha256-def.glb
│ └── sha256-ghi.glb
├── edges/
│ ├── sha256-jkl.glb
│ └── sha256-mno.glb
├── entities/
│ ├── sha256-abc.json
│ ├── sha256-def.json
│ └── sha256-ghi.json
├── model/
│ └── model.json # self-contained model package required
├── sources/
│ └── models/
│ └── gearbox.py # preserved project-relative path
└── presentation/
└── presentation.json # optional
开发模式同时支持 unpacked directory,便于 diff、调试和静态 HTTP hosting。Packed 与 unpacked 形式必须拥有相同 scene.json 和 asset bytes。
15.1 Render-self-contained 与 Self-contained Model Package
Viewer 必须能够只依赖 scene.json 和 referenced geometry/edge/entity assets 完成显示与五层 selection;这是render-self-contained scene。Generic low-level package可以不嵌入model。Self-contained model package还必须包含exact model/model.json和source_files声明的Python files,用于:
- Download original model。
- Backend replay。
- Features history/source panel。
- Exact CAD service。
缺少 embedded model 或 presentation source 不得阻止普通 Viewer 加载render scene,但Features/source capability必须disabled。Compiler-produced @model(export_dir=...)和ModelResult.export_artifacts() package属于self-contained model package,总是嵌入model并输出source_files,后者没有可解析source时可以是空array。若embedded artifacts存在,loader必须按manifest中的URI、byte length和artifact hash验证,不得因为文件位于约定目录就默认信任。
15.2 URI 规则
Scene 1.0 URI是opaque ASCII ZIP member name,不是RFC URL:不执行percent decode、query/fragment split、Unicode normalize、base URI resolution或filesystem path semantics。Grammar固定为[A-Za-z0-9][A-Za-z0-9._/-]{0,1023},使用/separator;每个segment必须non-empty且不能是.或..。%、?、#、backslash、colon、NUL、leading/trailing slash、absolute path和drive prefix全部拒绝。HTTP、file://和external transport不属于Base 1.0。
Loader必须在读取任何payload前拒绝duplicate/case-insensitive-colliding member names、encrypted/non-regular entries和unsupported compression。ASCII restriction使Unicode database/version不参与package identity。Package member set必须exact等于scene.json加manifest中所有geometry/edge/entity/model/source-file/presentation URI的set;每个non-scene member至少被一个record引用,相同URI的所有records必须声明相同hash/length/media role。source_files[].path是不含sources/的project-relative path,uri必须是exact sources/<path> transformation。其他member一律拒绝。
Packed loader优先从archive stream读取,不extract到filesystem。必须extract时,每个目标的resolved realpath必须位于新建package root内,并以no-follow方式创建;unpacked directory loader同样拒绝symlink并检查每个resolved path containment。Unpacked loader必须先以no-follow enumeration/stat收集所有regular-file ASCII member names和uint64 sizes,在读取任何payload前验证name/collision/member-count、per-file limits、checked total uncompressed bytes、exact canonical stored size及non-ZIP64 representability;之后只先读取budgeted scene.json来验证exact member set,再读取referenced immutable blobs。Enumeration与open之间任何type/size/file-identity变化都以TOCTOU error失败。Archive不得有leading prefix或EOCD后的trailing bytes。Central/local header的name、size、CRC-32、flags和method必须一致;data descriptor、ZIP64、multi-disk、archive/member comment和extra field全部拒绝。Import只允许creator OS Unix、version needed/made-by 2.0、flags exact0x0800、method stored(0)或deflate(8)、disk fields 0、internal attributes 0,且external attributes high 16 bits必须是Unix regular file mode0100644。
Canonical.scene.zip exporter无directory entries,members按ASCII path bytes unsigned lexicographic order,method stored。每个local header固定:signature0x04034b50、version needed20、flags0x0800、method0、DOS time0、DOS date0x0021、actual CRC-32、equal compressed/uncompressed uint32 sizes、actual filename length、extra length0。每个central header固定:signature0x02014b50、version made by0x0314、version needed/flags/method/time/date/CRC/sizes与local一致、extra/comment/disk/internal attributes均0、external attributes0x81A40000、actual local offset。EOCD固定single-disk zero fields、matching uint16 entry counts、actual central size/offset和zero comment。Archive总长度和all offsets用checked arithmetic且不得需要ZIP64。Importer可以接受上述allowlist内deflate package,但必须在接受前按payload lengths和member name bytes计算canonical stored envelope size并满足第22.2节budget;重新export时只canonicalize envelope,payload bytes不变,不保留原compression bytes。Phase A exact ZIP vector是这些fields的normative byte oracle。
所有JSON在object construction前检测duplicate member names;发现任何层级duplicate key立即拒绝,不能依赖parser的first/last-wins行为。JSON输入必须是strict UTF-8,不接受BOM、replacement decoding、comments或trailing tokens。scene.json、entity sidecars和embedded presentation必须逐byte等于parsed value的RFC 8785 JCS encoding;noncanonical whitespace、number或member order直接拒绝。Embedded model/imported source bytes保持其source-defined raw encoding。Python source blobs是strict UTF-8 inert text,不是JCS documents;package validator按immutable bytes验证length/hash并将operation source spans与文本cross-check。Hash、byte length和validation必须消费同一份immutable bytes;validator验证的member/JSON/GLB/source bytes就是后续renderer或inspector读取的bytes,禁止按路径二次打开造成TOCTOU/parser confusion。
16. Identity、Revision 与 Content Hash
必须区分四种 identity:
| Identity | 用途 | 生成策略 |
|---|---|---|
scene_id |
一个长期 scene 的逻辑身份 | 调用方必须显式提供稳定 ID |
revision |
Scene manifest 的不可变版本 | Canonical manifest content hash |
node_id |
Revision 间尽量稳定的 occurrence identity | Product component path 优先 |
| content asset ID | Geometry、edge 或 entity snapshot bytes identity | SHA-256 content hash |
16.1 ID Grammar 与 Segment Encoding
Scene 1.0 的 caller-authored scene_id、root_id 和 presentation_id 使用:
[A-Za-z][A-Za-z0-9_.-]{0,127}
禁止 /、:、% 和 whitespace。Content IDs 使用 exact lowercase form:
sha256:[0-9a-f]{64}
Product IDs 当前允许 :,因此不能直接拼接到 structural ID。每个 product/component/graph/node semantic segment 必须先按 UTF-8 编码,再使用 RFC 3986 percent encoding;只保留 ASCII unreserved A-Z a-z 0-9 - . _ ~,其他 bytes 包括 / 都必须编码,percent hex 必须 uppercase。Decoder 必须拒绝对 unreserved byte 的多余编码、lowercase percent hex、invalid UTF-8 和 decode/re-encode 不一致。
Structural IDs 使用固定 literal segments 和 / 分隔:
definition/<root_id>/<kind>/<encoded-semantic-id>
instance/<root_id>/<encoded-component-id>/...
definition/<root_id>/shape/model/<encoded-graph-id>/<encoded-node-id>/<output-slot>
definition/<root_id>/shape/imported/<encoded-source-element-id>
definition/<root_id>/shape/manual/<encoded-source-id>
appearance/<scope>/<sha256-hex>
connector/<root_id>/<owner-kind>/<encoded-owner-id>/<encoded-connector-id>
Base 1.0 的 <scope> 固定 literal evaluated,因此所有 evaluated appearance ID恰为 appearance/evaluated/<sha256-hex>;source/presentation scope已包含在被hash record中,不再编码第二种ID grammar。
由于 data segments 不允许 raw slash,tuple-to-string mapping 无歧义。Validator 必须在构造 registry 前拒绝 duplicate canonical IDs;不能先由 mapping 覆盖再检查。
16.2 Product-backed IDs
Product hierarchy 中:
- Definition ID 使用 root namespace 和 typed semantic ID,例如
definition/<root_id>/part/<encoded-part-id>、definition/<root_id>/assembly/<encoded-assembly-id>。 - Scene node ID 来自 root identity 和完整 component path。
- 同一个 root namespace 内,同一 semantic type/ID 必须表示同一 definition;compiler 发现不同 producer/content 时失败。
- 不同 roots 可以使用相同 product ID 并生成不同 definitions;geometry bytes 仍通过 content hash 跨 definitions 去重。
- 同一 root 中 repeated Part/Assembly 复用同一 definition。
现有 Product API 没有 scene-wide global ID contract,因此 Scene 1.0 不宣称裸 part_id 在多 root scene 中全局唯一。SceneRoot.root_id 是 identity contract 的必要部分。
同一 root 内的 definition equivalence 使用第 10.4 节 exact normalized Product records,不能仅比较显示名称或 mesh bytes,因为不同 product definitions 可以具有相同 geometry。
每个 root 自身也有 root occurrence node instance/<root_id>;descendant node 是 instance/<root_id>/<encoded-component-id>/...。因此两个 roots 包含相同 assembly/component path 时仍拥有不同的全局 node IDs。
16.3 Shape-backed IDs
Standalone graph shape 没有 product ID 时,definition identity 使用:
definition/<root_id>/shape/model/<encoded-graph-id>/<encoded-node-id>/<output-slot>
Root occurrence 仍统一使用 instance/<root_id> 并引用上述 definition;不再使用第二套 shape/... node identity。Definition identity 只保证在同一个 exported model artifact 内稳定。由于当前 operation node IDs 默认是随机短 ID,独立重建的等价 model 不保证产生相同 definition ID。发生这种情况时,patch 系统应发送 full replacement 或要求作者提供稳定 semantic ID,不能用 geometry proximity 猜测对象 identity。Imported/manual shape 使用其对应 source variant;调用方没有 stable source identity 时 compilation 失败。
16.4 Canonical JSON
Scene manifest、entity snapshot sidecar、optional presentation spec 和 connector binding spec 使用 RFC 8785 JCS。为了得到稳定 revision,必须满足:
- UTF-8 和 JCS string/number/object-key encoding,包括
-0、Unicode 和 IEEE-754 finite number 的 JCS 处理。 - 禁止 NaN 和 infinity。
- Hash input 完全省略顶层
revision字段,不使用空字符串或 placeholder。 - Canonical package中的JSON member必须就是compact JCS bytes;pretty rendering只允许UI显示,不能作为valid package member。
JCS保留array order,因此compiler在JCS前必须规范化所有set-like arrays。Validator不silently normalize wire input;任何array不是canonical order就拒绝,再对unchanged parsed value重算revision。JSON object member names严格使用RFC 8785要求的UTF-16 code-unit ordering,由JCS encoder负责。除此之外,本文显式要求排序的schema arrays、IDs、logical names和ASCII package paths固定使用unsigned UTF-8 byte lexicographic order;不使用Python code-point或JavaScript默认comparator:
extensions_used=[]、extensions_required=[]和extensions={},non-empty直接拒绝。definitions按definition_idsort,nodes按node_idsort。geometry_assets和edge_assets按asset_idsort,entity_assets按entity_asset_idsort。appearances按appearance_idsort,connectors按connector_snapshot_idsort,cameras按camera_idsort。- Base 1.0的
lights、annotations和diagnostics必须为空。 - semantic binding IDs 和 evaluated tags lexicographic sort。
component_path、matrix/vector、face/edge range和其他语义上有顺序的arrays保留schema-defined order。Entity payload中的entities、face_groups和edge_groups也必须already满足第10.2/10.4节order。Scene validator只验证order并按原value JCS重算revision,不将多个wire manifests折叠到同一revision。
17. Scene Compiler
17.1 编译输入
推荐 API:
package = scad.compile_scene(
scene_id="gearbox-demo",
roots=(scad.SceneRoot(root_id="main", value=assembly),),
source=result,
presentation=None,
options=scad.SceneCompileOptions(
linear_tolerance=0.35,
angular_tolerance=0.22,
),
)
scene_id 是 required keyword-only input;compiler 不生成 UUID、timestamp 或 path-derived fallback。source 可以是 ModelResult 或显式 SceneSource descriptor。它不选择 roots,但不是可随意填写的 label:source.kind=model 时 compiler 必须拥有 source GraphSession/artifact ownership evidence,并验证每个 graph-backed root、Part body、definition source、entity source、connector source和 semantic binding都属于同一 exact source graph/model artifact。调用方只提供 hash/graph ID而没有 ownership evidence时,不允许生成 kind=model scene;应使用 imported/manual source或先通过 trusted replay service建立 evidence。presentation 可以是 ScenePresentationSpec;省略时只生成 deterministic default appearance/visibility,不保存作者自定义视图。Entity snapshots 和 CAD edge assets 是 Base Scene 1.0 renderable definition 的 mandatory output,不提供 disable option。
SceneCompileOptions在Base 1.0中是closed immutable record,只含linear_tolerance、angular_tolerance、embed_source和embed_presentation。两种tolerance省略时分别固定为0.35 mm和0.22 radians;embedding booleans都默认false。显式tolerance必须finite,0 < linear_tolerance <= 1000000.0且0 < angular_tolerance <= 3.141592653589793。同一linear tolerance同时用于triangle deflection和CAD edge discretization;不另设hidden edge default。Compiler必须把四个resolved values写入manifest并纳入cache/revision input。Meshing parallelism、thread count、OCP environment或process cache不得改变canonical output;当前profile无法满足时必须按profile单线程规范化或失败。
roots必须是non-empty tuple,root_id唯一;每个root必须产生一个root occurrence和definition。RenderablePart/Shape必须包含至少一个valid solid、face、edge和vertex,并生成non-empty triangle/line assets;empty shape、wire、shell、vertex-only、edge-only、face-only、zero-solid Compound和fully-collapsed tessellation在Base 1.0中失败。纯Assembly可以没有自身geometry,但其recursive descendants必须至少包含一个renderable definition;完全空scene只能由低层schema fixture构造,不能由compile_scene()产生。
Ownership validation至少执行 GraphSession.validate_graph_ownership() 等价检查,并验证所有 model_output/model_topology 的 (node_id, output_slot) 在exact model graph中存在、slot小于output_count;只有model_topology要求其topology tuple能在artifact records中找到。所有semantic binding IDs必须存在且target/source一致。任何imported/unbound entity不能伪装为model provenance。Package validator对embedded model重复执行纯结构cross-check;trusted compiler负责证明runtime geometry确由这些roots求值,并为connector apply产生不序列化的entity-to-live-shape index。
compile_scene() 返回 CompiledScenePackage,而不是裸 SceneSnapshot。Package 是 immutable aggregate,至少包含:
manifest: SceneSnapshotblobs: Mapping[str, bytes],key 是 manifest 中 package-relative URI- Generic package中按options选择的embedded source/presentation blobs
- Compiler-produced self-contained model package中的mandatory
model/model.json和manifest-declaredsources/**/*.py
这样 asset bytes 的所有权和生命周期是显式的,compiler 不依赖临时目录、隐藏 mutable state 或进程内 cache 才能完成 export。CompiledScenePackage不保存输入ZIP envelope或compressed sizes。当前public slice提供preflight_zip_bytes()和package validator组合加载archive bytes;后续import_scene()可以在此边界上返回同一package type。Accepted package可以payload-byte-preserving地重新export为canonical stored envelope,但不会保留原compression bytes或header布局。
不建议在 ModelResult 上自动增加 eager scene_json 属性。Scene compilation 包含昂贵 tessellation 和 binary asset generation,不能成为每次@model调用的无条件成本。当前支持两个显式opt-in入口:decorator的export_dir和调用后的ModelResult.export_artifacts(output_dir=...);省略二者时保持in-memory-only行为。
17.2 编译过程
Scene Compiler 按以下顺序工作:
- 验证root descriptors、root value类型、root IDs和graph ownership;model source先clean replay exact artifact并验证projected runtime metadata equality。
- 解析replayed model或manual root为standalone shape或product definition/instance tree;imported root只解析为standalone shape。
- 建立全局definition registry,包括flattened Compound owner metadata exception。
- 递归遍历 assembly,组合并验证 component path,但只保存 local placement。
- 对每个 unique geometry definition 生成或复用 RenderMesh。
- 生成 normals、CAD edge polylines 和 local bounds。
- 建立 solid/face/edge/vertex entity records、face/edge picking ranges 和 graph/topology provenance。
- 将 Product Material 映射为 Appearance,并求值 definition connectors。
- 当嵌入model时,从live graph nodes收集所有
path_kind=project_relativesource;验证file仍位于其pyproject.tomlproject root、path仍一致、member path archive-safe、文件存在且是strict UTF-8.py,拒绝case-fold collisions,并按path排序/deduplicate。 - 写出 deterministic triangle GLB、edge GLB、entity snapshot sidecar、exact model bytes和
sources/<project-relative-path>bytes。 - 计算所有 blob hashes,建立不含
revision的 draft manifest。 - 执行 structural draft validation。
- 使用 normalized JCS draft manifest 计算 scene revision,并构造 final manifest。
- 构造
CompiledScenePackage。 - 执行包含 URI、byte length、blob hash、GLB profile、entity adjacency/range、embedded model graph references、source span/callsite和revision 的 full package validation。
17.3 Compound 策略
Compound 没有 product hierarchy 时,第一阶段将其视为一个 shape definition 和一个 render object。不能根据 disconnected solids 的空间位置猜测 component identity。
后续可增加 explicit shape group input,但不自动把所有 compound children 提升为可持久化 product nodes。
17.4 Cache Key
Render mesh cache key 至少包括:
- Geometry source content/evaluation identity。
- Linear tolerance。
- Angular tolerance。
- Normal generation policy version。
- Edge tessellation policy version。
- Render asset schema version。
- OCP/kernel 和 tessellator build identity。
- 全部 meshing flags。
- Normal/edge generation implementation version。
- GLB writer/profile version。
- Numeric/float normalization profile。
Placement、visibility、camera 或 appearance-only changes 不得使 geometry asset cache 失效。
Deterministic asset hash的保证范围是:相同exact source artifact、相同geometry value、相同compile options、相同profile ID/profile bytes和相同registered toolchain_hash。只匹配profile ID但toolchain hash不同不能声称byte-for-byte reproducible。Scene revision还要求相同scene_id、resolved roots、presentation和完整exact generator record;任一display version、ABI/platform token、toolchain hash或profile变化都会按manifest内容产生新revision。跨OCP/tessellator/writer toolchain的byte-for-byte equality不是Scene 1.0保证;toolchain/profile变化必须cache miss并进入generator metadata。
18. Public API 草案
第一阶段推荐最小 API:
package = scad.compile_scene(
scene_id="model-preview",
roots=(scad.SceneRoot(root_id="main", value=value),),
source=model_result,
presentation=presentation,
options=options,
)
scad.validate_scene_manifest(scene=package.manifest)
scad.validate_scene_package(package=package)
scad.export_scene(package=package, path="model.scene.zip")
@scad.model(graph_id="model", export_dir="out")
def build_model():
return build_part()
result = build_model()
assert result.artifact_paths["scene"].name == "model.scene.zip"
也可以不在decorator传export_dir,随后调用result.export_artifacts(output_dir="out")。这两个automatic publishing入口只在output directory中写一个Scene ZIP;model/session JSON和Python source位于ZIP内部,不自动生成旁置STEP、STL或FCStd。Archive import convenience尚未作为public API发布;当前读取边界是preflight_zip_bytes()、parse_canonical_json()和validate_scene_package()。
核心 public types:
SceneSnapshotCompiledScenePackageSceneRootSceneSourceScenePresentationSpecSceneDefinitionSceneNodeSceneAppearanceSceneGeometryAssetSceneEdgeAssetSceneEntityAssetSceneConnectorConnectorBindingSpecSceneCameraSceneCompileOptionsSceneValidationReport
validate_scene_manifest()只验证schema、IDs、hierarchy、transforms、references和revision。validate_scene_package()还验证所有referenced bytes、GLB allowlist/profile、entity adjacency/ranges、embedded model/source artifact和所有artifact-derived resource budgets;它不验证不存在于CompiledScenePackage中的input archive length、compressed sizes或compression ratios。Packed envelope或unpacked stat table由对应preflight先验证canonical stored archive size;export_scene()在写入前重复计算该size。Exporter还必须调用package validator;manifest-only validation不能声称package安全或完整。
不建议第一阶段提供大量 mutable scene.add_node() 风格 API。先以 immutable dataclass、compiler output 和 strict serializer 为主,避免形成第二套随意构造且无法验证的 scene graph。
19. Viewer Runtime 架构
19.1 独立 TypeScript/Three.js 工程
Viewer 位于仓库根目录 viewer/,拥有独立 package.json、TypeScript config、Vite build pipeline 和发布产物。它可以依赖发布后的schema fixtures/types,但不能import Python package internals,也不能要求SimpleCAD virtualenv、OCP或scene compiler才能启动。当前实现由viewer/src/main.ts直接管理DOM/UI和Three.js runtime,并未采用React。
当前主要目录:
viewer/
├── package.json
├── src/
│ ├── main.ts # package loader, UI state, Features, inspector
│ ├── style.css # layout, source range and selection styling
│ └── components/
│ └── click-selection.ts
└── public/cases/ # optional static fixtures
TypeScript负责package/UI逻辑,Three.js WebGLRenderer负责imperative rendering。GLB只能从已经经过package member length/hash validation的in-memory bytes构造;package source不允许触发nested network/file resource resolution。Three.js是renderer adapter的实现细节,不改变Scene contract。
Camera pose、hover、GPU resources、pointer movement和renderer dirty flags保存在imperative runtime中;每帧camera update不重建UI。若未来迁移到React,仍应维持这一边界,但React store/hooks不是当前实现contract。
19.2 Package Loader 与未来配对
负责:
- 通过file picker或drag/drop加载一个local
.scene.zip。 - 验证archive member policy、manifest references、byte length和SHA-256。
- 从package中发现并解析embedded
model/model.json。 - 按
source.source_files加载strict UTF-8 Python source。 - 验证通过后并行加载 GLB 和 entity assets。
- 建立 node/definition/asset registries。
- 根据 parent relation 计算 world transforms。
当前没有独立external model slot、unpacked-directory loader或完整pair-state UI。Validated package中存在embedded model时启用Features/source;否则保持scene-only模式,Preview、product tree和geometry selection仍可用。以下independent slots和配对状态是后续architecture,不是当前Viewer行为:
| 状态 | 条件 | UI/能力 |
|---|---|---|
artifact_matched |
scene source.kind=model,且 exact model bytes SHA-256、graph ID、model schema version 全部相等 |
Preview和DAG并列可用;在结构验证前不cross-link或author |
provenance_matched |
artifact_matched 且browser完成下述全部结构cross-check |
启用read-only DAG/entity双向定位和binding command draft/export;不代表CAD replay已验证 |
scene_only |
只有 scene,且无 verified embedded model | Preview、tree、entity inspector 可用;DAG 和 authoring disabled |
model_only |
只有 model | DAG/params 可用;viewport 显示 no evaluated scene,不执行 browser replay |
mismatched |
两者都存在,且任一pairing field不同,或三个fields相等但任一required provenance cross-check失败 | 两侧仍可独立查看,显示stale/mismatch或invalid-provenance banner;禁止自动cross-link和authoring |
non_model_scene |
scene source 是 imported/manual | Preview 可用;model pairing 不适用 |
Hash 必须对用户选择的原始 ArrayBuffer 计算,不能 parse 后 reserialize 再计算。Embedded model也必须按manifest URI、byte length和hash验证后才能用于Features。未来若加入external model slot,external与embedded model不同时必须进入mismatch,不能静默优先任意一份。
Browser 对 operation JSON 只做 budgeted display/index validation,不 replay。DAG index 读取 graph.graph_id、nodes[].node_id/op/params/param_exprs/inputs/output_count/display、edges、leaf_ids 和 expression graph;必须检测 duplicate IDs、dangling inputs、cycles、edges/inputs disagreement 和超预算 payload。它不是 Python model importer 的安全替代品,connector apply backend 仍须执行 canonical model validation/replay。
Package validators已经验证每个model-backed definition/entity/connector (graph_id,node_id,output_slot)存在于embedded graph且slot有效,并验证operation source span/callsite。未来进入provenance_matched前,browser还应验证第14.3节完整connector producer contract、topology records、semantic bindings以及edges/inputs一致性。Browser静态检查不能声称已证明replayed input/output equality或geometry correspondence;后者仍由trusted compiler/backend验证。
未来的provenance_matched仍只是artifact/record consistency,不证明无OCP replay的browser重新建立了geometry correspondence。Connector command提交必须由backend返回cad_verified capability:backend重新验证exact model/scene preconditions、trusted compiler ownership evidence和target resolution后才执行mutation。
19.3 Renderer
负责:
- 按 geometry + appearance 对 repeated occurrences 做 instanced mesh rendering,并保留
instanceId -> scene_node_idmap。 - PBR 或稳定 CAD studio shading。
- CAD edge overlay。
- Mesh ID pass、CAD line hit testing 和 runtime vertex point picking。
- Clipping planes。
- Grid、axes 和 background。
- Fit-to-view。
Renderer 对外只暴露typed commands/events,例如load package、set node visibility、set selection intent、fit bounds和resolved raw hit。DOM event handlers不应绕过renderer state随意mutate unrelated Three.js objects。Material创建从SceneAppearance确定性映射,definition appearance和node override按manifest precedence解析;material-aware preview不从mesh name猜测颜色。
19.4 Product Tree 与 Features
Tree 直接使用 scene node hierarchy,不解析 operation graph。Tree node 与 viewport occurrence 共享 scene_node_id。
当前Features tree读取embedded model:从leaf_ids开始递归跟随inputs,每个graph node只显示一次,再补上未从leaf访问的nodes;它显示operation dependency而不伪装成product hierarchy。选择feature时清除occurrence selection并显示:
op、category、node_id、input node IDs、output count、tags和result/leaf status。- 完整 JSON-safe
params,不只显示 summary。 - Display summary和
assignment_targets。 - Source path/call range;project-relative mapping通过
source_files[path]显示完整Python file,高亮line..end_line并滚动到首行。 - Source unresolved或member缺失时显示
call_textfallback。
当前不实现viewport/entity到feature的双向定位,也不在选择feature时高亮所有对应geometry;param_exprs、semantic/topology delta详情也仍是planned capability。未来增加cross-link时只能使用明确且结构验证通过的provenance,不能根据名称或geometry proximity猜测对应关系。一个operation node可以没有可见entity,也可以对应多个definition/occurrence/entity,这是正常状态。
19.5 Selection Service
Selection service通过toolbar显式选择component|solid|face|edge|vertex intent,并将triangle face groups、CAD edge groups和runtime vertex points的hit解析为:
- Scene occurrence。
- Part/assembly definition。
- Component path。
- Solid/face/edge/vertex entity。
- Source graph output。
- Engine geometry classification/properties。
- Evaluated semantic bindings/tags 和 JSON-safe SDK metadata。
Selection toolbar 必须始终显示当前 intent component|solid|face|edge|vertex,不能用一次 click 同时产生五个隐式 selection。Inspector展示当前实现可用的occurrence/definition/entity identity、properties、source、tags和metadata;选中的occurrence/entity在viewport高亮。World-space point/axis/bounds是runtime derived view,并标记derived;sidecar中的definition-local values保持不变。Viewer可以生成UNIQUE QL SELECTOR convenience text,但它不是canonical selection identity或自动持久化command。
19.6 Connector Editor
Connector panel 列出 selected definition 的 evaluated connector snapshots,并可在provenance_matched pair上进入create mode。Create mode只允许connector_binding_status="supported"的face、edge或vertex target;其他entities仍可选择和inspect,但panel显示exact status reason。预览使用entitysdk_connector_frame显示local/world axes,收集stable connector ID、nullable name和face/edge flip,然后生成第14.4节的ConnectorBindingSpec;vertex固定flip=false。
Static build 提供 download command;hosted mode 提交给明确配置的 backend endpoint。Panel 必须显示 target definition 及 occurrence count、pending/accepted/conflict 状态、新 model hash 和 scene revision。scene_only、mismatched、unbound/imported entity、assembly without supported owner operation 都是 disabled state,并给出具体原因。
19.7 Workspace Store
Workspace 与 loaded scene revision 分开保存。Scene revision 改变后:
- 仍存在的 stable node IDs 保留 visibility/selection preference。
- 不存在的 IDs 被 invalidated 并产生 diagnostic。
- Entity selection 只有在 entity asset/content identity 和 entity ID 仍匹配时保留。
- 禁止 geometry-nearest 自动替换 critical semantic selection。
20. Viewer MVP 功能边界
20.1 必须完成
- 加载validated
.scene.zippackage;没有embedded model时保持scene-only fallback。 - Orbit、pan、zoom、fit-to-view。
- Assembly/product tree。
- Embedded model Features tree、完整node params、assignment targets和operation summary。
- Manifest-declared完整Python source display、originating line range highlight/scroll和
call_textfallback。 component、solid、face、edge、vertex五种 selection intent。- Hide/show、isolate 和 reset visibility。
- Basic material color 和 stable shading。
- CAD edge overlay。
- Entity inspector:engine geometry properties、graph/topology provenance、tags、SDK metadata。
- Bounds、axes 和 grid。
- Scene/asset validation error UI。
- Display scene/source/revision information。
独立external model/unpacked loading、完整pair-state UI、parameter expressions和viewport/entity/Features双向定位属于后续扩展,不是当前MVP完成条件。
20.2 推荐进入 MVP 后半段
- Section/clipping planes。
- Exploded view。
- Approximate measurement,并明确精度等级。
- Named views。
- Screenshot/export image。
- Semantic tag/filter panel。
- Connector binding command authoring;hosted apply 可以在后续 milestone 接入。
20.3 非 MVP
- Browser-side CAD replay。
- Browser-side arbitrary feature/parameter editing。
- Constraint editing。
- Multi-user collaboration。
- Physics simulation。
- Arbitrary animation timeline。
- Texture authoring。
- General-purpose game scene scripting。
- Exact BRep measurement without backend/kernel。
21. Incremental Scene Protocol
第一阶段先完成 immutable full snapshot。Patch protocol 只能建立在 stable scene IDs、asset hashes 和 strict revision validation 已经完成之后。
建议后续 message envelope:
{
"protocol_version": "1.0",
"message_type": "snapshot",
"scene_id": "gearbox-demo",
"revision": "sha256:...",
"base_revision": null,
"sequence": 1,
"message_id": "uuid",
"payload": {}
}
Patch 至少支持:
upsert_definitionremove_definitionupsert_noderemove_nodeset_transformset_visibilityset_appearanceadd_assetremove_asset_reference
Patch contract 必须定义:
base_revisionoptimistic concurrency。- Atomic transaction boundary。
- Idempotency。
- Duplicate/out-of-order message handling。
- Missing asset behavior。
- Hash mismatch behavior。
- Reconnect 后 full snapshot resync。
不建议直接使用无约束 JSON Patch。Typed scene operations 更容易做 referential integrity validation,也能区分 transform-only update 与 geometry replacement。
22. Validation 与安全
Scene 文件可能由网络或用户上传,必须比当前内部 model replay 更严格。
22.1 Schema Validation
必须验证:
- Required/unknown fields policy。
- Semantic version 格式和 capability compatibility。
- ID 唯一性和合法字符。
- Parent、definition、asset、appearance、entity、connector 和
SelectionRef全部存在。 - Top-level/definition/node/entity/connector/appearance source matrix、root IDs、graph IDs和manual source IDs满足第8.1/11/14.3节。
- Model connector source node具有正确add/forward op、slot、owner/connector params和direct inputs;trusted compiler另验证replayed equality和lineage。
- Node hierarchy 无环。
- Rigid transforms 有效。
- Bounds finite 且 min <= max。
- Asset byte length 和 hash 一致。
source_files按path排序且唯一,project-relative.pypath archive-safe且无case-insensitive collision,URI exact等于sources/<path>。- Python source member是strict UTF-8,length/hash匹配;project-relative operation mapping解析到embedded file,source span等于
call_text,callsite_id可重算,unresolved source要求path=null。 - Entity face/edge ranges 不越过对应
(mesh_index, primitive_index)的 index accessor。 - Triangle indices 不越过 vertex buffer。
- Triangle/line GLB 的 scene、node、mesh、primitive、buffer 和 accessor 数量与 allowlist profile 一致。
- GLB accessor type、component type、primitive mode、coordinate conversion 与 scene contract 一致。
- Entity adjacency、source-kind agreement、face/edge range partition、alignment 和 deterministic ordering 满足 contract。
- Revision 可重新计算。
22.2 Resource Budgets
Loader必须在分配payload-sized memory、inflate或GPU upload前应用resource profile。Base默认untrusted-input profile固定为:
| Resource | Default maximum |
|---|---|
| ZIP members | 50,000 |
| Input archive bytes | 256 MiB |
| Canonical stored archive bytes | 256 MiB |
| Total declared uncompressed bytes | 1 GiB |
| One member / one GLB | 256 MiB |
scene.json |
32 MiB |
| One entity sidecar | 64 MiB |
Embedded model.json |
64 MiB |
| Presentation JSON | 8 MiB |
| Decompression ratio per member and aggregate | 100:1 |
| JSON nesting depth | 64 |
| One JSON string UTF-8 bytes | 1 MiB |
| One URI UTF-8 bytes | 1,024 |
| One structural ID UTF-8 bytes | 4,096 |
| Definitions / nodes | 25,000 / 100,000 |
| Geometry / edge / entity assets | 25,000 each |
| Embedded Python source files | 25,000 |
| Appearances / connectors / cameras | 25,000 / 100,000 / 1,000 |
| Scene hierarchy depth | 256 |
| Forwarded connector chain depth | 64 |
| Entities per sidecar / total | 500,000 / 2,000,000 |
| Triangle vertices per asset / total | 2,000,000 / 10,000,000 |
| Triangles per asset / total | 2,000,000 / 10,000,000 |
| Line vertices per asset / total | 2,000,000 / 10,000,000 |
| Line segments per asset / total | 2,000,000 / 10,000,000 |
| Static decoded-buffer cost | 512 MiB |
MiB是1024 * 1024bytes。input archive bytes是transported complete ZIP byte length,不是member sizes之和,只由packed importer preflight验证;unpacked input没有该指标。Compression ratio定义为sum uncompressed member sizes / complete input archive length,且per-member ratio是uncompressed size / max(1, compressed size)。Archive preflight必须使用central/local header中一致的declared sizes累加并checked-arithmetic;实际inflate bytes必须恰好匹配declared size且仍受streaming counter限制。
Canonical exporter和packed importer都必须在materialize canonical output前精确计算canonical_stored_archive_bytes = 22 + sum(76 + 2 * ascii_name_byte_length + payload_byte_length);该式适用于第15.2节zero-extra/zero-comment local header、central header和EOCD profile。值必须<=256 MiB且所有entry counts、sizes、offsets和central directory fields必须适配non-ZIP64 widths。这样canonical export总能被同一Base profile重新import;一个accepted deflated input即使original archive小于256 MiB,也只有在其canonical stored representation同样不超过256 MiB时才被接受。GLB header/chunk lengths还必须适配unsigned 32-bit GLB fields。
static decoded-buffer cost是artifact-derived validity formula,不依赖implementation residency:对每个unique triangle GLB加position.byteLength + normal.byteLength + index.byteLength的CPU copy和同值GPU copy;对每个unique line GLB加position.byteLength + index.byteLength的CPU/GPU copies;对每个vertex entity另加runtime point-picking12 position bytes + 4 entity-index bytes的CPU/GPU copies;再加所有immutable JSON/text member uncompressed byte lengths一次。Python source使用general one-member/package limits,不使用64 MiB embedded model专用上限,并计入member count、total uncompressed、canonical archive和static immutable cost。Total使用checked uint64 arithmetic且必须<=512 MiB。Scene occurrences不重复计asset buffers;implementation临时parse/staging copies不影响artifact validity,但仍受process sandbox memory limit。Renderer可以lazy load降低实际residency,不能用lazy behavior接受超过static formula的package。
Browser model/DAG display另固定默认:model artifact最多64 MiB、100,000 operation nodes、500,000 dependency edges、100,000 expression nodes、graph depth 10,000。Base annotations固定为0;extension必须定义额外count budget。Implementations可以由trusted deployment显式提高limits,但不得降低schema/identity/hash检查,且UI必须显示active profile。Compiler/exporter和Python/TypeScript importers的默认tests必须使用上述同一profile。
Load/decode deadline是operational policy而不是artifact validity,因为wall-clock结果不deterministic。Viewer默认每个worker task 30 seconds并支持AbortSignal/cancellation;timeout产生workspace error,不写入manifest diagnostic。Streaming progress必须在compressed read、inflate、hash、JSON/GLB validation和GPU upload阶段报告。
22.3 Package Security
- 拒绝 ZIP path traversal。
- 拒绝 absolute path。
- 默认拒绝 external URI。
- 不执行 package 内脚本。
- Embedded
.py只作为inert UTF-8 text解码和escaped source display;不得eval、import、注入raw HTML或发送给Python runtime。 - 使用 budgeted parser 在交给通用 renderer loader 前 preflight GLB。
- 拒绝 GLB 中的 nested/external/data URI、images、textures、animations、skins、morph targets、sparse accessors、
extras和未声明 extension。 - Renderer loader 的 network/file URI resolver 必须禁用,即使 preflight 有遗漏也不能发起子资源请求。
- Hash verification 必须在 GPU upload 前完成。
22.4 Model Replay Service
若未来服务端接受 model.json 并生成 scene,还必须增加 model payload budgets、kernel replay timeout、worker isolation 和 cancellation。Scene Viewer 本身不应执行 model operations。
23. 现有 Model Serializer 的前置加固
本地 runtime 直接编译 scene 不必等待所有 model serializer 问题修完,但以下问题会阻塞“上传 model -> 服务端生成 scene”的受信边界:
- Model import 对 required fields 和 nested graph schema 的验证不完整。
- Graph
edges和 nodeinputs是两份 dependency representation,当前没有一致性检查。 output_count、leaf_ids和部分 cross-graph refs 缺少完整 import-time validation。- Derived registries/logs 没有全部从 graph 重建或交叉验证。
- 当前随机短 graph/node IDs 不适合跨独立 build 的长期 scene identity。
- 缺少 canonical model digest;Scene 1.0 暂时只能使用 exact source artifact bytes hash。
- 缺少 payload、node count、depth、kernel time 和 memory budgets。
- 缺少正式 migration registry 和 historical fixtures。
这些工作与 Scene Schema 并行,但在开放不可信 model upload 前必须完成。
24. 测试策略
24.1 Scene Schema Tests
- Minimal valid standalone shape scene。
- Valid nested assembly scene。
- The same valid/malformed corpus is accepted/rejected by Python and TypeScript with matching JSON Pointer/code。
- Duplicate/missing IDs。
- Parent cycles 和 dangling parent。
- Missing definition/asset/appearance ref。
- Invalid/non-finite transforms。
- Wrong revision/hash。
- Unknown field policy。
- Required/optional/nullable matrix for every base record and discriminated union variant。
- Stable caller-provided
scene_id; missingscene_idfails without fallback。 - Version compatibility matrix。
- RFC 8785 vectors cover Unicode keys/strings、IEEE-754 rendering、negative zero and two-pass revision omission。
- Default and boundary
compile_options、safe-integer limits、resource profile limits。
24.2 Compiler Tests
- One
Solid-> one root-aware shape definition、one root occurrence、one geometry asset、one edge asset 和 one entity asset。 - One
Part-> product definition、material mapping、provenance。 - Nested
Assembly-> correct local hierarchy。 - Same Part repeated N times -> one mesh asset、N instance nodes。
- Same subassembly repeated -> stable expanded occurrence paths。
- Flattened Compound -> no invented product hierarchy。
- Placement-only change -> same geometry asset hash。
- Material-only change -> same geometry asset hash。
- Geometry/tessellation change -> new asset hash。
- Part connectors -> evaluated definition-local connector snapshots。
- Existing closed-edge/unresolved connector causes explicit compilation failure rather than omission。
- Model runtime-only metadata mismatch with clean replay fails;replayed metadata remains deterministic。
- Flattened Compound owner tags/metadata have one definition-level projection and are not copied to child solids。
- Exact source artifact repeated with reversed/shuffled kernel traversal -> same entity、GLB and scene hashes。
- Symmetric/coincident entities -> canonical bytes remain stable and ambiguous connector selectors are not marked supported。
- Empty root、empty assembly、wire/shell/face-only root、任一collapsed face和completely empty line asset fail explicitly;individual collapsed edges remain degenerate inspector-only entities。
- Unsupported root value fails explicitly。
- Imported Part/Assembly或缺失stable
source_element_id的imported root fails explicitly;缺少primitive mapping的imported entities remainunbound。 embed_source=true写入exactmodel/model.json和sortedsource_files,每个sources/<project-relative-path>的bytes/hash/length保持一致。- 同graph ID但不同graph object的root、越界output slot和创建后被mutate的stale
ModelResultsnapshot全部fail closed。 @model(export_dir=...)和ModelResult.export_artifacts()只写一个<graph_id>.scene.zip,不旁置写model/session JSON、STEP、STL或FCStd。
24.3 Mesh Tests
- Positions/normals finite。
- Indices valid。
- Triangle orientation consistent。
- Bounds match vertices。
- Face groups exactly partition expected index ranges。
- Every face and rendered edge has exactly one positive range;degenerate edges have no range and expose an inspector-only entity。
- CAD edge polylines are not triangle boundaries。
- Deterministic GLB bytes across repeated exports。
- Triangle and line writers reproduce normative exact-byte GLB vectors and hashes。
- Round-trip GLB decode preserves counts and bounds。
24.4 Picking Tests
- Component pick resolves scene node。
- Repeated instances share asset but return distinct occurrence IDs。
- Solid intent resolves owning solid from a face hit。
- Face group resolves graph/node/output source and optional proven topology evidence without conflating them。
- Edge group resolves source edge。
- Vertex point pick resolves definition-local entity and occurrence-specific world point。
- Entity inspector returns engine geometry/properties, tags and JSON-safe SDK metadata。
- Trusted compiler binding/tag caches match runtime source; browser only claims artifact-proven target evidence for
model_topology。 - Revision change invalidates stale selection safely。
24.5 Embedded Model Provenance 与未来 Connector Tests
- Embedded model在用于Features前完成byte-length/hash、schema version、graph ID、node shape、inputs、leaf IDs和output references cross-check。
- Reformatting otherwise equivalent model JSON会改变Scene 1.0 exact-byte artifact hash。
- Scene-only package保持Preview/tree/entity selection,Features/source disabled。
- Future external model slot必须覆盖
artifact_matched、provenance_matched、model_only和mismatchedstates,且非matched状态不能启用provenance cross-link或connector command drafting。 - Binding spec stale model hash、scene revision、entity asset、entity source or duplicate connector ID fails atomically。
model_outputbinding requires exact source scene recompilation and unique ephemeral entity resolution; no nearest-geometry fallback。- Selector tie/unstable replay produces non-supported
connector_binding_statusand cannot draft a command。 - Forwarded snapshot validates direct source component、source definition/snapshot ownership、offset composition and acyclic depth。
- Missing source model/presentation/root output/toolchain profile prevents
cad_verifiedcapability。 - Backend without editable model transaction rejects apply even for a direct-root Part。
- Backend with future editable transaction still rejects nested immutable Part apply until nested Product rewrite capability is advertised。
24.6 Package Tests
- Packed and unpacked scene equivalence。
- Missing asset。
- Corrupt GLB。
- Hash mismatch。
- ZIP traversal。
- Duplicate/colliding ZIP member、backslash/NUL name、encrypted/non-regular entry 和 central/local header mismatch。
- ASCII case-insensitive collision、data descriptor、ZIP64、multi-disk、extra/comment and unsupported method rejection。
- Duplicate JSON object key、BOM、invalid UTF-8 和 trailing token。
- Missing/wrong
source_filesmember、unsafe path、wrong URI、case-fold collision、source hash/length mismatch和invalid UTF-8。 - Embedded operation source unknown/missing field、unmapped project-relative path、span mismatch和wrong
callsite_id。 - Packed extraction/unpacked directory symlink escape。
- Zip bomb/resource budget。
- External URI rejected by default。
- Canonical exporter reproduces exact archive bytes, order, timestamps, modes and CRC metadata。
24.7 Viewer End-to-End Fixtures
至少保留以下 immutable fixtures:
- Single colored bracket。
- Repeated bearing balls 或 repeated fasteners。
- Nested reducer assembly。
- Large integrated actuator。
- Symmetric part with ambiguous topology identities。
- Scene with named view 和 appearance override。
每个 fixture 保留:
- Exact source model artifact hash。
- Scene manifest golden snapshot。
- Canonical
scene.jsonbytes、revision vector、asset bytes/hashes and canonical archive hash。 - Expected node/definition/triangle counts。
- Entity/connector snapshot counts、provenance variants、binding status、DAG linkage 和 five-intent picking assertions。
- Features tree selection、assignment-target display、完整source file、active line range、scroll和unresolved-source fallback assertions。
- Reference screenshot tolerance test。
25. 实施阶段
Phase A:Scene Contract 与 Characterization
状态:已冻结并由Python/TypeScript shared corpus、generated types、exact JCS/GLB/ZIP vectors和strict validators持续验证。
工作项:
- 定义 immutable scene dataclasses。
- 提交五个structural JSON Schema 2020-12 files、normative semantic rule registry和generated Python/TypeScript types。
- 提交两个normative profile JSON、hash-linked numeric/canonicalization pseudocode和profile schemas/validators。
- 定义strict manifest/package/sidecar/spec validators和canonical JSON encoder。
- 定义 ID、revision、coordinate system 和 URI policy。
- 定义 Entity Snapshot、evaluated connector 和
ConnectorBindingSpecexact records。 - 定义
ScenePresentationSpecschema 和 deterministic no-presentation defaults。 - 定义exact triangle/line GLB skeleton、default resource profile和canonical ZIP envelope。
- 实现只接受
Mapping[str, bytes]的low-level canonical ZIP envelope encoder/decoder;它不理解scene manifest,只负责Phase A exact headers、ordering、CRC、budgets和vectors。 - 为现有 examples 记录 expected product hierarchy 和 mesh counts。
- 明确 private
TriMesh与新RenderMesh的边界。 - 增加shared valid/malformed corpus、JCS/revision vectors、exact-byte GLB vectors和ZIP metadata vectors。
- 增加current Model Schema 2.0 topology coverage、reversed traversal、symmetric entity和selector ambiguity characterization tests。
退出标准:
scene-1.0.schema.json、entities-1.0.schema.json、presentation-1.0.schema.json、connector-binding-1.0.schema.json和normalized-product-1.schema.json全部closed并通过schema metaschema validation。scene-1.0-rules.json、scene-1.0-ocp-glb-2.profile.json、ocp-evaluated-properties-1.profile.json及其linked pseudocode全部通过applicable schema、content-hash linkage和unknown-field validation。- Minimal/nested/repeated-instance scene能在Python和TypeScript构造或parse、验证和round trip。
- 两端对全部shared valid/malformed fixtures给出相同accept/reject结果及稳定JSON Pointer/error code。
- 相同scene重复序列化得到相同canonical bytes/digest;golden JCS和two-pass revision vectors逐byte通过。
- Python/TypeScript GLB preflight接受triangle/line exact-byte vectors并拒绝每个forbidden-member mutation。
- Python/TypeScript low-level archive preflight接受exact canonical ZIP vector、逐field mutations和allowlisted deflate fixture;low-level envelope encoder逐byte重现ZIP vector。
- Default/resource/numeric边界、entity adjacency/range、provenance union、forwarded connector和binding status matrices有golden coverage。
- 只有以上条件全部满足,文档和schemas才能从
Proposed改为Frozen;实现PR不能自行放宽contract。
Phase B:Scene Compiler 与 Render Assets
状态:当前slice已能从runtime values生成canonical .scene.zip;model publishing profile嵌入model/model.json和mapped Python source。
工作项:
- 实现
compile_scene。 - 实现
SceneRoot内Solid/Compound/Part/Assembly traversal。 - 实现 definition/instance deduplication。
- 实现第10.2节
set_model_metadata_rvalueexact operation并纳入Model Schema 2.0 export/import/replay和translator capability registry;将stdlib和Phase B fixtures中的post-operationset_metadata()迁移到该operation,使clean replay投影与runtime exact相等。 - 实现
RenderMesh、RenderEdgeMesh、normals 和 bounds。 - 实现 deterministic triangle/line GLB writer 与 standard glTF coordinate conversion。
- 实现 definition-specific entity snapshot、engine properties、SDK tags/metadata 和 face/edge picking ranges。
- 实现 evaluated connector snapshots。
- 将Phase A low-level envelope codec集成为scene directory/ZIP exporter/importer;增加manifest/member/hash/GLB/entity validation,不另写ZIP header implementation。
- 实现operation source mapping、
source_filescollection和embedded model/source cross-validation。 - 实现
@model(export_dir=...)与ModelResult.export_artifacts()的single-package publishing path。
退出标准:
- Example 10和当前model exporters可以生成validated self-contained package;其余保留examples按支持范围持续characterize。
- Repeated part 只产生一个 geometry asset。
- Placement-only changes 不改变 asset hash。
- CAD edge asset 不是 triangle boundary derivation,且能够通过 line pass 加载。
- Triangle/line writer逐byte重现Phase A vectors;canonical ZIP exporter逐byte重现archive vector。
- Same exact inputs在fresh processes和reversed traversal characterization harness中产生相同scene、entity、GLB和archive hashes。
- Full package validator拒绝hash/range/adjacency/profile/budget/forwarded-connector mutations before renderer consumption。
Phase C:Viewer MVP
状态:独立viewer/ TypeScript/Three.js/Vite slice已在不运行CAD replay的情况下加载.scene.zip,显示evaluated scene、embedded Features和Python source。
工作项:
- Validated package loader和content-hash checks。
- Orbit/pan/zoom/fit。
- Product tree。
- Features tree、node params、assignment targets和source range display。
- Component/solid/face/edge/vertex selection intent。
- Visibility/isolate。
- Basic appearance。
- GPU edge overlay。
- Entity/geometry/tag/metadata inspector。
- Error/diagnostic UI。
- 后续:external model/unpacked loaders、完整pair states、expressions和provenance-backed双向cross-link。
退出标准:
- 大型 Example 20 可以加载、导航、选择和 isolate components。
- Embedded model package的Features/source pane与scene viewport同时可用;scene-only package保持geometry capability。
- Feature/source browser-level automation补齐line highlight和scroll regression。
- Missing/corrupt assets 显示结构化错误,而不是白屏。
Phase D:CAD Interaction
目标:从通用 mesh viewer 进化到可用 CAD viewer。
工作项:
- Section/clipping。
- Exploded view。
- Semantic tag/filter inspection。
- Approximate measurement 与 precision labels。
- Named views 和 screenshots。
- Selection provenance panel。
- Connector binding command UI;backend apply 等待 editable model transaction capability。
退出标准:
- 用户可以从 component -> solid/face/edge/vertex -> source graph/semantic evidence 完成追溯。
- 用户可以选择 supported topology entity、预览 connector frame 并生成 validated binding command。
- Viewer 明确区分 exact 和 mesh-approximate measurement。
Phase E:Publishing 与 Incremental Updates
目标:支持 hosted scene、cache 和 transform/material 增量更新。
工作项:
- Scene snapshot protocol。
- Typed patch operations。
- Revision conflict/resync。
- Content-addressed asset service。
- Browser persistent asset cache。
- Worker-side model replay isolation。
退出标准:
- Transform-only patch 不重新下载 geometry。
- Duplicate/out-of-order patch 可检测并恢复。
- Reconnect 能通过 full snapshot 正确 resync。
Phase F:Exact CAD Services 与 Kinematics
目标:在保持 thin Viewer 的前提下增加高级 CAD 能力。
可选工作项:
- Backend exact measurement/query API。
- Optional BRep sidecar。
- Assembly joint projection 和 motion preview。
- LOD、streaming 和 mesh compression。
- Feature history linkage。
- Annotation publishing。
- Nested Product graph rewrite for connector apply。
这些能力不是 Viewer MVP 的前置条件。
26. 里程碑与优先级
| 优先级 | 里程碑 | 直接产物 |
|---|---|---|
| P0 | Scene Schema + validator | 稳定 contract、canonical digest、malformed tests |
| P0 | Runtime Scene Compiler | `SceneRoot[Solid |
| P0 | RenderMesh + triangle GLB | Browser-ready geometry、normals、bounds |
| P0 | CAD edge asset | Line GLB、正确 edge overlay、基础 edge provenance |
| P0 | Entity snapshot + five-intent picking | Viewer 与 CAD provenance/metadata 的桥梁 |
| P0 | TypeScript/Three.js package viewer | Camera、render、tree、Features/source、selection、inspector、asset reuse |
| P1 | External model pairing + E2E harness | Pair states、bidirectional provenance、source highlight browser regressions |
| P1 | Section/measure/explode | 基础 CAD inspection UX |
| P1 | Connector binding command | Topology selection 到 revision-bound authoring command |
| P1 | Persistent browser cache | 跨会话 asset reuse |
| P2 | Scene patch protocol | 增量更新和 hosted viewer |
| P2 | Exact backend services | Analytic measurement/query |
| P2 | Kinematics projection | Motion preview |
27. Viewer MVP 总体验收标准
以下条件全部满足,才可称为首个可用 Viewer,而不是 mesh demo:
- Viewer 加载 scene 时不需要 SimpleCAD Python runtime 或 OpenCascade。
- Scene 保留 Assembly/Component/Part hierarchy,不只是一块 flattened mesh。
- Repeated part instances 共享 geometry asset。
- Component、solid、face、edge、vertex selection 可以追溯到 scene occurrence、entity snapshot 和 source provenance。
- Viewer 支持product tree、Features/params、selection、hide/show、isolate和fit-to-view。
- CAD edge overlay 不显示 triangle 内部边。
- Scene、GLB 和 entity snapshot sidecar 都有 strict validation 和 content hash。
- Large assembly 的 asset loading 有明确 budgets、progress 和 error state。
- Scene IDs、revision 和 asset IDs 的含义不同且被测试固定。
- Workspace state 不污染 canonical model 或 scene。
- Viewer 若提供 mesh measurement,必须明确标记为 approximate;Phase C MVP 可以尚未提供 measurement。
- 同一exact source artifact、resolved roots、presentation、compile options、profile bytes、registered
toolchain_hash和完整generatorrecord重复导出得到deterministic scene revision和asset hashes。 - Scene-only package保持geometry capability;embedded model package增加Features/source capability。Future external pair states需要另行满足
artifact_matched、provenance_matched、model-only和mismatched测试。 - Entity inspector 显示 engine properties、topology provenance、tags、metadata 和 connectors,不依赖 browser OCP replay。
- 选择mapped feature时显示assignment targets和完整manifest-declared Python file,高亮originating range且不执行source code。
28. 风险与取舍
28.1 Scene 与 Model 信息重复
Scene 必然复制一部分 evaluated state,例如 transforms、material color 和 semantic tag cache。解决方式不是消除所有重复,而是明确 canonical owner、source artifact hash 和 invalidation。Scene 是可丢弃 build artifact;不可重建的 authored presentation 由 ScenePresentationSpec 拥有。
28.2 Topology Identity 不够稳定
当前 topo_id 和随机 operation node IDs 不能保证跨独立 build 稳定。Scene 1.0 只能保证同一 model revision 内 picking 可追溯。跨 revision selection persistence 优先依赖 product path 和 semantic bindings;无法证明时应失效。
28.3 GLB 与 CAD 坐标习惯不同
Scene 固定 +Z up 和 mm,GLB 固定遵守 glTF 2.0 meter/+Y up,asset_to_scene 保存唯一转换。不能让不同 exporter 自行选择 axis/unit,否则 transform、measurement 和 picking 会分裂。
28.4 Viewer 过早依赖具体前端框架
Scene contract、GLB 和 entity snapshot sidecar 应独立于 Three.js、Babylon.js 或其他 renderer。Frontend 可以替换,scene schema 不能由某个 framework object serialization 决定。
28.5 一开始设计完整游戏引擎 Scene
SimpleCAD 需要的是 CAD evaluated scene,不是脚本、physics、audio、particle、arbitrary ECS。Schema 应保留 extension/capability 机制,但第一阶段只实现 CAD Viewer 必需的 hierarchy、renderable、appearance、camera 和 annotation。
28.6 Scene 自动附加到每个 ModelResult
自动编译会显著增加每次model build的tessellation、内存和序列化成本,也隐藏质量参数,因此不能成为@model的无条件副作用。当前实现采用显式opt-in:@model(export_dir=...)在调用结束后自动发布,省略export_dir时保持in-memory-only;ModelResult.export_artifacts(output_dir=...)提供调用后的显式替代。两个入口都只写一个self-contained .scene.zip。
29. 推荐决策
- 新增独立 Scene Schema 1.0,不扩展 Operation Graph 为 render scene。
model.json继续是 design/replay canonical artifact;scene 是 evaluated、可重建的显示 artifact。capture_result不承担scene visibility或hierarchy;low-levelcompile_scene(scene_id=..., roots=...)显式提供logical scene identity和roots,automatic publishing wrapper只从explicit captured outputs派生roots。- 使用 Definition/Instance 分离,保留 assembly hierarchy 和 repeated mesh reuse。
- 使用
.scene.zippackage。Generic render package包含scene.json、content-addressed triangle/line GLB和definition-specific entity sidecar;self-contained model package还包含model/model.json和sources/<project-relative-path>。 - Scene 使用右手
+Zup、mm 和 parent-relative rigid transforms;GLB 保持标准 glTF meter/+Yup。 - Render mesh 与 collision mesh 分离;新增 normals、CAD edges、typed entity snapshots 和 picking ranges。
- Product material 与 render appearance 分离,RGB 只做确定性默认映射。
- Product path 是 scene occurrence identity 的首选,asset bytes 使用 SHA-256 identity。
- Viewer workspace、
ScenePresentationSpec和 evaluated scene 分离。 - 首先完成 immutable full snapshot,再设计 patch protocol。
- Browser Viewer 不执行 model replay;不可信 model replay 放在有 budget 和 isolation 的后端 worker。
- Mesh Viewer 的 measurement 默认为 approximate,exact CAD measurement 通过后端或 optional kernel 提供。
- Scene schema、compiler、asset writer和entity/picking contract先于高级frontend能力;独立
viewer/在validated package上逐步扩展pairing、provenance和panels。 - Connector authoring 使用 revision-bound command,由 SDK/backend 更新 canonical model并重编 scene;不能直接修改 evaluated snapshot。
- Operation source mapping是只读Viewer evidence,不执行代码,也不成为feature selection的第二canonical owner。
30. 建议的下一步
Phase A、compiler/exporter、source embedding和Viewer基础slice已经落地,下一步集中处理剩余边界:
- 为Viewer加入不依赖手工操作的browser E2E fixture,验证package upload、Features selection、assignment targets、完整source display、line range highlight和scroll。
- 明确当前package-only loader与未来external model/unpacked loader的产品边界,再实现pair-state UI和provenance-backed双向cross-link。
- 完成presentation/imported source compilation或将相关public types继续标记为contract-only。
- 继续以shared corpus保持Python/TypeScript validator parity,新增每个source mapping/path/member trust-boundary mutation。
- Connector command UI可以围绕schema实现;backend apply仍必须等待editable model transaction,nested Product rewrite不做隐式兼容。