Skip to content

脚本即事实源:Script 编辑与版本对比 ​

面向设计师的编辑与版本模型,一句话概括:Python 构建脚本是 IFC 的唯一对应表示,IFC 是脚本受限执行的产物。人和 AI 的修改都落在脚本上,平台只在版本之间和暂存步之间做对比。

design JSON 的定位 ​

design JSON 只是 AI 起草阶段的草稿,不是模型的完整表示,不进版本,不参与 diff。与 IFC 一一对应的只有构建脚本。AI 生成复杂模型时可以先写一份 design JSON 辅助构思,见 AI Skill,但交付物永远是脚本。

工作流 ​

plan / design 草稿(可选,AI 构思辅助,不落版本)
   → 构建脚本 v{n}.py(唯一事实源:PARAMS + build())
   → 沙箱执行 → IFC v{n}(派生物,可随脚本重建)

三个核心概念 ​

构建脚本 ​

每个模型对应一个完整 Python 脚本,要遵循脚本契约:

  • 头部 PARAMS = {...} 是顶层字面量 dict,所有可调参数集中在这里。
  • 构件的 GlobalId 由 key 确定性派生,并写入 Pset_AIIFC.designKey。同一脚本跑多少次 id 都不变,跨版本 diff 才能对齐。
  • 审查可见的构件必须经契约工厂 script_lib.create_entity 创建,工厂自动写确定性身份并记录调用点。禁止绕过工厂直接建实体。
  • 需要 web 表单可编辑的参数必须是标量字面量或 PARAMS 引用,不能是任意表达式,否则定向改写会拒绝。
  • 入口是 build(params, out_path),产物必须过 ifcopenshell.validate。

暂存区 ​

脚本编辑先进暂存区,最多 10 步,原子落盘,重启恢复,可撤销重做。暂存后三个去向:放弃,什么都不留;试运行,沙箱执行预览产物但不产生版本;保存,晋升为大版本。

大版本与回退 ​

大版本是用户或 AI 主动保存的点。脚本和定位 map 全量保留,编号只增不改。产物文件只物化最新一份,历史版本 diff 或下载时从脚本重建,结果进缓存。回滚就是恢复某版脚本重新执行,脚本和产物永远一致,不存在改了产物没改脚本的分叉。

AI 下次介入时拿到的输入是当前脚本加两层 diff 摘要,因此它的修改是增量式的,不是推倒重写。

差异引擎 ​

对比分两个粒度。大版本之间做完整对比;暂存链相邻步之间做轻量行内对比。

层对象受众
脚本文本 diff 加 PARAMS 键级变更v{n-1}.py 对 v{n}.py,以及暂存步间AI 的下一轮上下文,用户看脚本 diff 视图
IFC 语义 diff,属性级 GlobalId 对齐两个版本的 IFC用户在 Diff Viewer 看,见编辑工作流
外部上传模型无脚本时按 GlobalId 做属性级对比用户

前端界面 ​

Design 面板解析脚本的 PARAMS 块自动生成参数表单,不执行脚本;下钻进脚本编辑器直接改代码。表单提交或脚本保存算暂存一步,保存版本产生大版本。版本对比面板可以选两个大版本看脚本 diff 和语义 diff,也可以看暂存步之间的小 diff。

执行安全 ​

编辑服务在服务端沙箱里执行构建脚本,共享实现见沙箱执行环境。要点:bwrap 按需只读挂载加断网,没有 bwrap 直接拒绝执行;超时 60 秒,连孙进程一起终止;脚本可以经 PEP 723 声明依赖,由宿主机的 uv 构建隔离环境后沙箱内执行。

网络安全取决于部署:编辑服务自身无鉴权,务必只监听 127.0.0.1,外部一律经 Go server 代理。

API ​

经 Go server 代理的端点:

端点语义
GET /api/v1/models/{id}/script当前脚本,暂存态或最近保存
PUT /api/v1/models/{id}/script暂存一次编辑。plain 模型首次暂存自动保留原件为 bootstrap.ifc
GET /api/v1/models/{id}/script/params当前 PARAMS,ast 提取不执行
POST /api/v1/models/{id}/script/undo|redo|discard暂存导航与放弃
POST /api/v1/models/{id}/script/run沙箱试运行,无版本
POST /api/v1/models/{id}/script/save晋升大版本;有 bootstrap 时响应带 alignment
GET /api/v1/models/{id}/scripts大版本列表
POST /api/v1/models/{id}/script/rollback恢复某版脚本并重跑
POST /api/v1/models/{id}/script/diff两个大版本的脚本 diff
GET /api/v1/models/{id}/script/staging/diff暂存步间小 diff
GET /api/v1/models/{id}/script/locate?guid=guid 定位调用点;查不到返回 found:false,暂存与 map 分叉返回 stale:true

仅直连可用的端点:

端点语义
POST /models/{id}/script/edit-calllibcst 重写一个标量参数后沙箱验证,成功等同一次暂存;参数不合法返回 422 零副作用

定位链路 ​

每次沙箱执行时,契约工厂记下每个构件的调用点,落成 map sidecar,按脚本哈希绑定发布。map 的行号只对生成它的那份脚本有效,暂存了新脚本但没运行时两者会分叉:edit-call 对分叉直接拒绝,locate 降级返回 stale:true,前端提示先运行脚本。

python
ScriptMap = dict[designKey, {"line": int, "col": int, "snippet": str,
                             "origin": "literal" | "params" | "traced"}]
# 发布信封(current.map.json / v{n}.map.json):
# {"scriptHash": sha256(脚本全文), "map": ScriptMap}

origin 决定前端改写策略,见编辑工作流。

bootstrap:上传 IFC 转脚本 ​

plain 模型经 AI 复现转为 script-backed:AI 通过 MCP 读上传的 IFC,写复现脚本,首次暂存时平台保留原件为 bootstrap.ifc,验证后保存 v1。首次保存响应里的 alignment 计数是原件和生成结果的差异摘要,用它检验复现质量;对齐计算失败不影响保存本身。

与直改链路的关系 ​

直改链路已退役,脚本生成的模型全部走本页模型:选中、定位、改写、沙箱、暂存、大版本。外部上传的 IFC 无编辑入口,仅查看和审查,对比走属性级语义 diff。

Apache-2.0 · xeokit AGPL 注意事项见项目介绍