夜雨聆风学习资料网

ARTICLE · 1244618

AI拼积木 LDraw-Nova 技术说明书从设计意图到可验证 LDraw 模型的完整原理

AI拼积木 LDraw-Nova 技术说明书从设计意图到可验证 LDraw 模型的完整原理

LDraw-Nova 技术说明书

从设计意图到可验证 LDraw 模型的完整原理

版本 1.0 | 2026-10-24 | 基于本仓库 ldraw_tools 源码与 docs/agent 文档整理


0. 一句话理解

LDraw-Nova 不是“让 AI 直接写积木坐标”的工具,而是一套面向智能体(Agent)的模型编译与验证系统:

把人的设计意图,先写成分层的 JSON plan,再由 plan 或 Python 生成器编译成 LDraw MPD,最后用语法、几何、连接、BOM、渲染五类证据逐层收口。

它真正解决的不是“会不会画积木”,而是三个更难的问题:

  1. 可建造性:零件是否真实存在、是否能装上、是否互相穿模。
  2. 可复现性:最终模型是否能从 plan/生成器重新生成,而不是靠手改 MPD。
  3. 可审美迭代:几何正确之后,如何通过多角度渲染把“合法但难看”改成“比例、层次、焦点都成立”。

1. 为什么是“编译器”,不是“画图器”

1.1 设计意图的三层落地

层
载体
回答的问题
设计层
design-brief.md
这是什么?主焦点是什么?用什么颜色和构图?
中间表示层
*.plan.json
有哪些模块、步骤、零件、坐标、朝向、接口?
可执行层
*.mpd
LDraw 解析器能直接读取的装配文件

一个典型的生成链路是:

自然语言需求   ↓ 设计判断design-brief.md   ↓ 结构化model.plan.json          generate.py   ↓ build                 ↓ 逐条生成 placement   └──────────┬────────────┘              ↓          model.mpd              ↓ validate / inspect / render / bom / compare-bom

plan.json 和 generate.py 不是二选一。两者都常用:

  • plan 直接描述:适合结构清楚、坐标可枚举的模型。
  • 生成器写 plan:适合参数化、重复结构、条件分支、按网格铺装、按高度阶梯计算。
  • 两者都遵守同一个 schema,最终都走同一个 build 管线。

1.2 为什么不让模型直接写 LDraw

LDraw 的 type 1 记录长这样:

1 4 0 -8 0 1 0 0 0 1 0 0 0 1 3003.dat

一个零件需要:颜色、三维平移、9 个旋转矩阵系数、文件名。再乘上几百上千个零件,手写坐标几乎必然出错。

LDraw-Nova 的判断是:

LLM 生成“产生几何的代码”通常比直接生成“几何数字”更可靠。

于是它把难点转移到 Python/JSON 层,把 LDraw 只当成最终的序列化目标。


2. 系统全貌:五层架构

LDraw-Nova 的五层结构
  知识、数据、引擎、产物、证据,缺一层都不构成完整 Skill

知识层
    instructions / docs

数据层parts / categories / db引擎层builder / validation产物层plan / mpd / glb证据层render / checks / bom

工作循环
  读文档 → 选真实零件 → 写 design brief → 写 plan/生成器 → build → validate/inspect → 打开渲染图 → 改设计 → 重建
  关键:几何通过 ≠ 外观正确;渲染成功 ≠ 物理可建;BOM 一致 ≠ 强度足够。

输入
    主题、限制、颗粒预算
    参考、配色、构图

转换intent → plan → placementspyldraw3 序列化 MPD输出与证据MPD + BOM + reportshome/front/top 等渲染图

2.1 知识层

  • instructions.md
    :Agent 的主工作流。
  • docs/agent/*.md
    :几何、验证、视觉设计、模块化、参考检索、车辆/飞船/Technic 等专题。
  • docs/ldraw-specs.pdf
    :LDraw 语言的最终权威。

知识层回答“应该怎么做”和“哪些结论不能声称”。

2.2 数据层

资源
作用
官方零件库(LDRAW_DIR)
.dat
 几何、连接器、LDConfig.ldr 颜色
data/categories/
可读的分类符号、颜色符号、尺寸快照
data/models-annotated/
OMR 参考模型
data/ldraw-info.db
模型/子模型/零件的全文检索库
data/offLibShadow/
LDCad 连接器影子库
ldraw_tools/data/rectangular-parts.json
36 个矩形砖/板的真实高度剖面

2.3 引擎层

核心模块:

模块
职责
builder.py
plan 加载、schema 校验、依赖排序、placement 求解、MPD 序列化
validation.py
LDraw 语法、MPD、矩阵、BFC、重复件、嵌入件边界
geometry.py
几何展开、碰撞候选、接触图、组件分析
collision.py
AABB 粗筛 + 15 轴 SAT + 矩形件 body 检查
connectivity.py
连接器发现、snap 候选、应用 snap
external.py
LeoCAD 渲染、临时嵌入件库、BOM 对比、GLB
document.py
MPD 命名空间、.dat/.ldr 边界、依赖闭包、提取
catalog.py
分类符号解析、颜色解析、零件检索

2.4 产物层

最小可交付集:

output//  design-brief.md  generate.py# 可选,但强烈建议  .plan.json  .mpd  build.json  validation.json  bom.json  review/    home.png    front.png    top.png  visual-review.md

2.5 证据层

证据
能证明什么
不能证明什么
语法校验
文件结构、引用、矩阵、颜色合法
外观、强度、真实库存
几何校验
解析出的实体边界、候选碰撞、嵌入几何完整性
所有材料相交、可插入性
连接分析
连接器元数据支持的接触图
真实咬合力、稳定性
BOM
引用/颜色/数量清单
零件是否买得到
渲染
能看到外观
不能自动判断美丑
人工视觉审查
比例、焦点、层次、遮挡
仍不是物理认证

3. LDraw 物理模型:所有几何错误的根源

3.1 坐标系

LDraw 使用右手坐标系,Y 轴负方向向上。

量
值
1 stud 间距
20 LDU
砖高(body)
24 LDU
板高(body)
8 LDU
凸点直径
12 LDU
凸点高度
4 LDU
1 LDU 约
0.4 mm

坐标与堆叠:负 Y 向上

  −Y(向上)
  +X

砖:body 24

板:body 8

砖:body 24

上件起点 = 下件起点 − 上件 body

凸点进入上件插座:AABB 合法重叠 4 LDU,不能当穿模。

3.2 变换公式

对 type 1 记录:

p_world = M @ p_local + tM_world = M_parent @ M_childt_world = M_parent @ t_child + t_parent

其中 M 是 3×3 旋转矩阵。装配 profile 要求:

Mᵀ M ≈ Idet(M) > 0

即只允许刚性旋转,不允许缩放、剪切、镜像。

3.3 站立堆叠公式

普通直立砖/板:

y_upper = y_lower - height_of_upper_body

例子:

  • 砖在 y=0,板放上去 → y=-8
  • 板在 y=-8,砖放上去 → y=-32

不要把凸点 4 LDU 再加进堆叠步长。凸点进入上件内部,是连接结构,不是额外高度。

3.4 半格与多格中心

一个 2×2 砖占 4 个 stud,中心在两个 stud 的中间。若按单格中心摆,就会整体偏半个 stud,和邻居发生 10 LDU 重叠。

这就是为什么手写网格生成器必须区分:

  • 单格件(1×1)的 gx 是整数。
  • 2×2 件中心是 gx + 0.5。
  • 2×4 / 4×2 件中心是格块几何中心。

很多“看起来检查通过、渲染却错位”的问题,根因都是半格对齐。

3.5 颜色

  • 16
    :继承父级当前颜色,不是固定灰。
  • 24
    :边线颜色,只能用于 type 2/5 的边,不能放在实体 placement。
  • 0x2RRGGBB
    :直接 RGB,合法但不建议用于生成模型。
  • 色号来自安装库的 LDConfig.ldr,不是 BrickLink 或 LEGO Element ID。

3.6 MPD 与物理边界

MPD 由多个 0 FILE 块组成,第一个块是主模型。

  • .ldr
    :装配子模型,参与装配层级。
  • .dat
    :零件定义,是 物理件边界;内部几何用于展开,但 BOM 只算这个物理件本身。

这条区分非常重要:

子装配里的 20 个零件是 20 个物理 placement;嵌入的 .dat 零件内部即使有 200 个 polygon,在装配 BOM 里仍然只算 1 个物理件。


4. plan.json:系统的中间表示

4.1 顶层结构

{  ”version”: 1,  ”author”: ”liangdabiao”,  ”license”: ”optional”,  ”includes”: [”other.plan.json”],  ”assets”: [”assets/clock.mpd”],  ”sections”: []}

约束:

  • version
     必须为 1。
  • sections
     至少 1 个,最多 200 个。
  • 第一个 section 是 MPD 主模型。
  • section 名必须以 .ldr 结尾,全局大小写不敏感唯一。
  • 每个 section 最多 1000 步,每步最多 1000 个 placement。
  • 坐标绝对值不超过 1,000,000 LDU。

4.2 section

{  ”name”: ”house-wall.ldr”,  ”description”: ”Front wall with a door opening”,  ”author”: ”optional override”,  ”license”: ”optional override”,  ”anchors”: {    ”base”: {”at”: [0, 8, 0]},    ”top”:  {”at”: [0, -160, 0]}  },  ”steps”: [    [      {”id”: ”brick-1”, ”ref”: ”3001.dat”, ”colour”: 15,       ”at”: [0, -24, 0], ”purpose”: ”first course”}    ]  ]}

steps 是“步”的数组,不是扁平的 placement 列表。每一“步”代表同时安装的一组零件,build 会在步骤之间写 0 STEP。

4.3 placement 的五种定位方式

每个 placement 必须且只能使用一种:

方式
用途
关键限制
at
绝对坐标
可配 yaw 或 matrix
on
在已放置的砖/板上堆叠
只支持受控的直立矩形砖/板
attach
模块锚点对锚点
两边都要声明 anchors
snap
连接器驱动的自动对位
需要完整连接器元数据与碰撞检查
{”id”:”seat”, ”ref”:”3022.dat”, ”colour”:71, ”at”:[0,-8,0], ”yaw”:90, ”purpose”:”rotated seat”}

yaw 是绕当前 section 的 +Y 轴右手旋转。yaw 与 matrix 互斥。

{”id”:”panel”, ”ref”:”3001.dat”, ”colour”:15, ”at”:[0,-40,0], ”matrix”:[[0,0,1],[0,1,0],[-1,0,0]]}

矩阵必须是 proper rotation。任意刚性朝向可以,但不能缩放/镜像。

{”id”:”plate”, ”ref”:”3022.dat”, ”colour”:15, ”on”:”brick-1”, ”offset_studs”:[0.5,0.5]}

builder 的过程:

position = support.position         + support.matrix * Vector(offset_x*20, -part_height, offset_z*20)

然后检查上件和下件是否至少有一个 stud 对齐点。偏移以支撑件的局部轴为准。

on 不是“万能吸附”:只适用于 curated 的直立矩形砖/板 profile。斜面、轮胎、夹子、横向 SNOT、子模型引用都不支持。

{”id”:”upper”, ”ref”:”floor.ldr”, ”colour”:19, ”attach”:{”to”:”lower”, ”anchor”:”next”, ”using”:”base”}}

数学解:

T_moving = T_support × A_support × T_offset × inverse(A_moving)

它对齐的是作者声明的模块接口,不是自动连接器检测。锚点必须保持实际 stud/socket 可接触。

{”id”:”cap”, ”ref”:”cap.ldr”, ”colour”:1, ”snap”:{”to”:”base”, ”near”:[0,-8,0]}}

builder 会调用连接器求解器:

  • 找 moving/fixed 的连接器特征;
  • 生成候选姿态;
  • 检查整个 moving 子树的碰撞;
  • 检查选定端点是否真的形成接触;
  • 选择未 blocked 的候选。

near 只是搜索起点,最终姿态由求解器决定。

{”id”:”post”, ”ref”:”3005.dat”, ”colour”:308, ”at”:[-100,-24,-100], ”repeat”:{”count”:8,”step”:[40,0,0]}}

生成 post-0 … post-7。step 是当前 section 轴的平移,不会跟随 yaw 旋转。repeat 只能和 at 一起用。

4.4 includes / assets

  • includes
    :加载其他 plan,路径相对声明文件;循环、超过 200 个文件、重复 section 名都会失败。
  • assets
    :嵌入已提取的 MPD 定义,保留原 header、作者、许可证与几何;命名空间必须唯一。
  • 每个 section 可以覆盖 author/license;根 plan 的 license 不会错误地传播给未授权的 include。

4.5 为什么 plan 要有 purpose

purpose 会写成 MPD 注释:

0 // brick-1: first course

它不是装饰。调试大模型时,inspect / 渲染步骤 / 结构审查都需要靠它快速定位“这块到底是什么”。


5. build:把 plan 编译成 MPD

build 的八个阶段
  任何一步失败,最终 MPD 都不会被写入

1 load_plan
    includes / assets / headers

2 schemaJSON Schema 2020-123 resolve@parts / @colours4 dependency orderchildren before parents5 placement solveat / on / attach / snap6 serializeMPD + CRLF7 validatesyntax + assembly8 geometrycollision + contacts

写入门槛
  serialize 后先重新解析并校验;任何 error 都不写。无 error 才做几何分析,最后还是无 error 才 atomic_write。

报告
  checks_passed / written / diagnostics / geometry

退出码
  0 = 通过;1 = 校验失败;2 = 输入/依赖/执行失败

5.1 load_plan

load_plan() 做四件事:

  1. 递归加载 includes,路径相对声明文件,检测循环,最多 200 个文件。
  2. 每个 section 继承所属 plan 的 author/license(若自己没有)。
  3. 收集 assets 为绝对路径。
  4. 合并 sections,保留根 plan 的非 include 字段,再做一次完整 schema 检查。

5.2 schema 与符号解析

resolve_plan() 只修改build 的副本,不改磁盘上的可编辑 plan:

{”ref”:”@plants.PlantBush”, ”colour”:”@colours.Dark_Green”}

会解析成真实 .dat 和色号。磁盘上仍保留符号,便于人读和跨库迁移。

5.3 依赖排序

dependency_order() 先构建被引用的子模块,再构建父模块。原因:

  • snap
     和 attach 需要子模块的物理件与锚点已经存在;
  • 子模块的 header、步骤和颜色必须先准备好;
  • 但 第一个声明的 section 永远是 MPD root,排序不改变根。

5.4 Placement 求解

按 placement 模式逐一算最终 position / matrix:

  • at
    :直接使用。
  • on
    :堆叠公式 + stud 对齐检查。
  • attach
    :4×4 变换合成。
  • snap
    :连接器候选 + 碰撞 + 端点接触验证。

每个 placement 先写注释,再写 type 1。

5.5 序列化

serialize_mpd() 保证:

  • 第一个 0 FILE 是主模型;
  • 所有子模块作为 submodels 只写一次;
  • UTF-8 无 BOM;
  • 统一 CRLF;
  • 文件末尾有换行。

5.6 写入门槛

build 不是“写出后再检查”,而是:

serialize → 重新解析 → validate_text → 若 error 停止          → geometry analyze → 若 error 停止          → atomic_write

所以失败构建不会留下半成品 MPD。--force 只控制覆盖已有文件,不会绕过校验。


6. 校验器:它到底检查什么

6.1 两种 profile

Profile
用途
特点
assembly
新生成的物理装配
严格刚性变换、MPD 结构、物理件边界
syntax
研究历史 OMR/遗留源
允许更宽的语言特性,用于审查现有文件

最终交付模型必须过 assembly。

6.2 assembly 检查清单

类别
规则
编码
UTF-8 无 BOM;CRLF 是警告
MPD
必须有主 0 FILE;主块第一;section 名唯一
section
.ldr
 装配;.dat 是嵌入零件定义
命名
不能 shadow 官方零件/primitive
字段数
type 1=15 字段;2=8;3=11;4/5=14
数字
所有坐标、矩阵有限
颜色
必须是安装库色号;不能对 placement 用 24
矩阵
proper rotation;det>0;禁止缩放/剪切/镜像
BFC
认证顺序、INVERTNEXT 后续必须是 type 1
物理件
不能把 primitive/subpart 当零件摆
重复
同 ref + 同世界变换 = error
可达性
未被主模型引用的 section = warning
空模型
主模型没有物理件 = error
嵌入 DAT
必须有准确 !LDRAW_ORG 分类
自定义面
装配中的 type 2–5 = 需外部审查/错误
继承色
叶子最终仍是 16 = error

6.3 为什么同一零件摆两次会报错

重复判定是世界变换级别:

(normalized ref, rounded position, rounded matrix)

不是“同一个 id”。所以:

  • 同一个子模型被实例化两次、但位置不同 → 合法。
  • 同一个零件在两个子模型里恰好落到同一世界坐标 → error。
  • 同一位置、不同颜色 → 仍算重复,因为几何重叠。

6.4 错误 / 警告 / 退出码

  • error
    :阻止 build 写入,checks_passed=false。
  • warning
    :不阻止写入,但必须阅读并解释。
  • --strict
    :把 warning 也当成失败。

退出码:

  • 0
    :命令完成且选中检查通过。
  • 1
    :校验失败、覆盖率失败、BOM 不一致、无 snap 候选等。
  • 2
    :输入错误、依赖缺失、I/O、转换、预算、执行失败。

不要只看退出码。还要读:

  • checks_passed
  • complete
  • physical_validity
  • diagnostics
  • *_truncated
  • contacts_checked

7. 几何引擎:它为什么既强大又必须被怀疑

一次碰撞检查的判定顺序
  从便宜到昂贵,从宽到严;最后仍需要人工材料审查

1 解析物理叶子
  .dat 是边界,内部展开

2 AABB 粗筛
  按 X 排序,扫描候选

3 15 轴 SAT
  有向包围盒分离

4 矩形 body 检查
  36 个砖/板真实高度

5 连接器接触
  stud / socket / pin / axle

状态不是二值
  rectangular_body_overlap / oriented_bounds_separated
  stud_zone_overlap_review_connections / review_oriented_bounds / review_aabb_only
  只有第一种是确定性 body 穿模;其余是候选或需要材料审查。

7.1 展开与预算

几何分析会递归展开所有被引用库几何。默认最多 100,000 个物理 occurrence;超过则报错,建议缩小 section 或提高预算。

7.2 physical_context 与 .dat 边界

physical_context() 会把嵌入 .dat 当作物理件边界:

  • 装配遍历停在 .dat;
  • BOM 只计这个零件;
  • 但它的内部几何仍会被展开用于碰撞/接触。

7.3 AABB → OBB → Body

  1. AABB:最快,但大量假阳性。
  2. Oriented bounds SAT:用 15 根轴检查两个有向盒是否分离。
  3. Curated rectangular body:对 36 个普通砖/板,用真实 body 高度(不含凸点)做 SAT。

第三种才会产生致命错误:

assembly.body_overlap

它意味着两个矩形砖/板 body 真的互相插入,超过 1e-5 LDU。

7.4 连接图

接触由 pyldraw3 + LDCad shadow metadata 推断:

  • confirmed
    :双方连接器证据都匹配。
  • optimistic
    :至少存在候选接触,但证据较弱或单边。
  • 多组件警告 assembly.disconnected_evidence 不自动等于悬空:
    • 可能是植物、装饰、独立人物;
    • 可能是连接器元数据缺失;
    • 可能是真的没接上。

必须具体检查被报告的零件。

7.5 500 件阈值

contacts="auto":

  • ≤ 500 个物理件:计算接触。
  • 500:显式跳过,并报 coverage.contacts_skipped。

大模型要主动选择:

validate model.mpd --geometry --contacts all

或按模块检查:

inspect model.mpd --section wall.ldr --contacts all

7.6 工具不能证明什么

  • 不能证明所有材料不相交(AABB/SAT 不是实体布尔运算)。
  • 不能证明凸点/插孔合法(连接器是推断)。
  • 不能证明强度、稳定性、抗跌落。
  • 不能证明零件在该颜色下真实生产/可购买。
  • physical_validity
     永远是 not_proven。

8. 连接器与 Snap:自动对位的边界

8.1 连接器元数据从哪里来

  1. 零件文件内联的 LDCad/pyldraw3 连接器记录。
  2. data/offLibShadow/
     影子库。
  3. Technic 专用 registry(仅结构件)。

覆盖度有三种:complete / partial / none。完整覆盖不等于每个物理连接都建模了。

8.2 一次 snap 的内部步骤

选中 moving leaf  ↓确定 moving_depth(默认只动叶子,0 动最外层子装配)  ↓收集 moving 子树与 stationary targets  ↓生成连接器候选姿态  ↓应用刚体 delta 到整个 moving 子树  ↓检查与静止件的 body 碰撞  ↓重算接触,验证选定端点真的接触  ↓按 blocked / review / contact / distance 排序

8.3 候选排序

优先级:

  1. 无 body collision;
  2. 未解决 overlap 更少;
  3. 接触更可信(confirmed 优先);
  4. 位移更短。

8.4 apply snap

apply_snap() 会:

  • 校验报告指纹是否属于当前模型 revision;
  • 沿 placement path 克隆祖先 section;
  • 只改目标 placement;
  • 不改变同一子模型的其他实例。

这保证“修一个窗户”不会把整排窗户都移动。

8.5 Snap 的边界

  • 不枚举所有铰链角度/滑动位置。
  • 保留自由 roll。
  • 连接器是接口描述,不是实体体积。
  • 接触不能抵消 body 碰撞。
  • 不证明插入路径、强度、稳定性。

9. 渲染、BOM 与外部工具

9.1 LeoCAD 适配器

render 并不是直接把 MPD 丢给 LeoCAD:

  1. 解析 MPD,识别嵌入 .dat。
  2. 为 LeoCAD 建立临时 mini library 与重写后的模型。
  3. 用唯一临时文件名渲染,避免旧图冒充成功。
  4. 导出 LeoCAD BOM。
  5. 把 BOM 里的临时名恢复成原始嵌入名。
  6. 原 MPD 与安装库保持不变。

这解决了 LeoCAD 会漏掉嵌入 DAT 零件的问题。

9.2 视图与相机

支持:

home front back left right top bottom

对 supported assembly,相机会按整个 bounds 自动取景:

  • FOV 30°;
  • 包围球半径;
  • 1.15 倍 margin;
  • 根据视图方向放置相机。

默认输出:1000×800、AA 4、full shading、无高亮、无步骤淡化。

9.3 渲染成功不等于视觉审查

渲染工具只返回:

{”visual_review”:”required: open and inspect the images”}

必须真的打开图片,检查:

  • 缩略图剪影是否能认出主题;
  • 主焦点是否被遮挡;
  • 比例是否成立;
  • 颜色是否组织成角色;
  • 侧面/背面是否穿帮;
  • 有没有悬空、错位、异常接缝。

9.4 BOM 与 compare-bom

Python BOM 与 LeoCAD BOM 必须按:

(reference, colour, quantity)

完全一致。

render model.mpd --outdir reviewcompare-bom model.mpd --csv review/leocad-bom.csv

不一致不是“渲染问题”,而是物理件统计或适配器问题,必须解释并修复。

9.5 GLB

prepare-glb.sh --file model.mpd model.glb

GLB 是派生导出,MPD 仍是权威源。转换成功不增加几何/物理验证。


10. 零件与参考检索:先看真实几何,再看描述

10.1 真相优先级

安装库的真实 .dat 几何 / 连接器    > catalog 的尺寸快照与符号    > 零件名称/描述

名称只能提示,不能证明轴向、原点或堆叠高度。

10.2 三种检索方式

命令
适用
search parts
已知关键词,查实际库
catalog parts
用分类符号、类别、尺寸快照筛选
discover search
用自然语言找零件/模型/子模型

discover 在有 Jev 时可语义重排;没有 API key / 服务不可用时,必须显式使用 --engine fts。CLI不会静默切换。

10.3 part 是最重要的单件工具

ldraw-agent part 3040b --limit 40

返回:

  • 真实 bounds(含凸点/装饰);
  • 原点相对尺寸;
  • stud 位置;
  • 连接器;
  • 元数据覆盖;
  • 矩形 profile(如果属于 36 个受控件)。

注意:bounds 高度不是堆叠高度。砖的 bounds 是 28,body 是 24。

10.4 study / extract

  • study
    :盘点层级、物理数量、复用、可达性、BOM、源问题。
  • extract
    :复制依赖闭包,重命名命名空间,保留作者/许可证,生成 manifest。

extract 的产物是“可审查副本”,不是物理认证。

10.5 part-board

对 1–12 个候选零件生成真实渲染卡片:

ldraw-agent part-board 6064b 3470 30176 --colour 74 --outdir output/board

卡片独立取景,不能凭图片大小判断真实尺寸;要对照 measured bounds。


11. 视觉设计:几何正确之后才开始

11.1 设计 brief 必须回答的七个问题

  1. Identity:这是什么地方/物体?要传达什么气氛?
  2. Silhouette:三个主要体量是什么?高度差在哪里?
  3. Focal hierarchy:第一焦点、第二焦点、第三焦点分别是什么?
  4. Palette roles:大面积、中间面、小 accent 各是什么颜色?
  5. Depth:挑檐、退台、凹窗、基座提供了什么阴影?
  6. Detail vocabulary:重复的细节母题是什么?
  7. Quiet surfaces:哪些面故意留空?

11.2 构图规则

  • 一个主焦点,两个支持特征,其余是小 accent。
  • 大面颜色不超过 3 种。
  • 细节集中在 1–2 个区域,不要全模型均匀撒。
  • 植物/装饰不能遮挡入口、桥、主立面等识别点。
  • 对称模型要有明确的轴线;非对称模型要有明确的视觉平衡。

11.3 三个尺度的审查

尺度
看什么
Detail
单个窗、门、栏杆、屋顶接口
Module
一层楼、一个屋顶、一段桥
Scene
整体剪影、前后遮挡、跨模块碰撞

模块检查不能替代整场景检查。

11.4 为什么“检查全绿但很丑”很常见

因为几何检查只看:

  • 是否穿模;
  • 是否重复;
  • 是否有连接器证据;
  • 是否合法。

它不看:

  • 比例是否像;
  • 焦点是否被挡;
  • 颜色是否平衡;
  • 细节是否有节奏;
  • 剪影是否可读。

所以实际流程必须是:

自检/校验 → 渲染 → 开图 → 改设计 → 再校验

而不是:

自检/校验 → 交付

11.5 收敛标准

工程上建议最多 2–3 轮“检查—修复”:

  1. 第 1 轮:修所有 error。
  2. 第 2 轮:修明显视觉硬伤(遮挡、比例、悬空)。
  3. 第 3 轮:只确认,不再追求完美。

超过这个范围通常是审美偏好,不是结构错误,应让用户决定。


12. 标准工作流:从零做一个模型

12.1 环境

Windows 当前工作区:

export LDRAW_DIR=”D:/LDraw/lib/ldraw”./.venv/Scripts/python.exe -m ldraw_tools.cli doctor

仓库原始 POSIX 形式:

./ldraw-agent doctor

如果 ldraw-agent 报 “Run ./setup.sh first”,优先用 .venv/Scripts/python.exe -m ldraw_tools.cli,不要重复跑 setup。

12.2 第 0 步:确认范围

写下:

  • 主题;
  • 尺度(底板 stud 数);
  • 颗粒预算;
  • 必须出现的 3 个特征;
  • 明确不要出现的东西;
  • 主视角。

12.3 第 1 步:设计 brief

不要先摆零件。先定:

  • 总平面;
  • 体量关系;
  • 焦点;
  • 配色;
  • 材质/零件族;
  • 模块划分。

12.4 第 2 步:查真实零件

对每个不熟悉的零件:

ldraw-agent part CODE --limit 40

记录:

  • 真实 bounds;
  • 原点;
  • body 高度;
  • 是否有凸点;
  • 连接器;
  • 是否需要旋转。

12.5 第 3 步:写 plan 或生成器

  • 简单模型:直接 plan。
  • 重复/参数化:Python 生成 plan。
  • 大场景:按模块拆 section,先做单模块。

12.6 第 4 步:build

ldraw-agent build output//model.plan.json \  --output output//model.mpd \  --detail summary \  --report output//build.json

12.7 第 5 步:validate / inspect

ldraw-agent validate output//model.mpd --geometry \  --contacts all --report output//validation.jsonldraw-agent inspect output//model.mpd \  --detail summary --report output//inspection.json

大模型必须显式决定 contacts all 或按 section 检查。

12.8 第 6 步:render + 开图

ldraw-agent render output//model.mpd \  --outdir output//review \  --views home front top right

打开所有图,记录:

  • 看到了什么;
  • 哪些是硬伤;
  • 哪些只是偏好;
  • 下一轮只改什么。

12.9 第 7 步:BOM / GLB / 交付

ldraw-agent bom output//model.mpd \  --report output//bom.jsonldraw-agent compare-bom output//model.mpd \  --csv output//review/leocad-bom.csv

12.10 第 8 步:复现性检查

改完最后一处后:

  1. 重跑生成器。
  2. 重建 MPD。
  3. 重跑校验、BOM、渲染。
  4. 确认生成器输出与交付 MPD 一致(哈希或逐字节)。

不能出现“MPD 已经修好,但生成器还是旧版”的状态。


13. 故障排查手册

诊断
常见根因
处理
syntax.field_count
type 1 字段不足/多余
用 build 生成,不手写
number.nonfinite
NaN/Infinity
检查生成器除零、矩阵
assembly.nonrigid
缩放、剪切、镜像
用 proper rotation;不要用负 determinant
matrix.singular
行列式为 0
重算旋转矩阵
model.unknown_part
零件名不存在
用 part/search 查真实文件名
model.unknown_colour
色号不在 LDConfig
用 colours 查
colour.edge_on_surface
对实体用了 24
24 只用于边
colour.unresolved_current
叶子最终仍继承 16
在根 placement 选显式色
assembly.duplicate
同件同世界变换
删一个;检查重复循环
mpd.unreachable
子模块没被主模型引用
主 section 显式引用
mpd.library_shadow
嵌入名撞官方件
换唯一命名空间
assembly.library_internal
把 subpart/primitive 当零件
换成物理 .dat
assembly.body_overlap
矩形砖/板实体互插
看 instances、sources、penetration
assembly.disconnected_evidence
多组件/元数据缺失/真悬空
检查具体零件,不删警告
coverage.contacts_skipped
>500 件且 auto
显式 --contacts all 或按 section
coverage.collision_review
非矩形件无 body 检查
开图 + 人工材料审查
渲染全黑/空白
背景透明/相机/进程
用自定义相机 + 合成背景;查 LeoCAD 进程
渲染超时
LeoCAD 首次扫库/僵尸进程
清进程、提高 timeout、降分辨率

13.1 “检查全绿但外观错”的排查顺序

  1. 先看俯视:总平面、遮挡、体量关系。
  2. 再看正视:立面比例、门窗、屋顶。
  3. 再看 3/4:整体剪影、阴影、焦点。
  4. 最后看侧/背:穿帮面、围栏遮挡、结构厚度。
  5. 只改“观众能一眼看出”的问题,不改洁癖问题。

13.2 常见几何陷阱

  • 多格件半格中心:2×2 件不能放在单格中心。
  • 旋转后尺寸交换:yaw 90 时 X/Z 尺寸互换。
  • 凸点不算堆叠高度:砖 body 24,不是 bounds 28。
  • 斜面件 AABB 高估实体:坡面下方是空气,不能按整盒判碰撞。
  • 楔形/倒角件接触面不是盒底:必须读零件源文件。
  • 手写 2×4 铺装:必须按 4×2 格块切分,不能按 20 LDU 步进。
  • 共享子模型实例:改一个实例要通过 clone 祖先,不能直接改共享定义。

13.3 性能与稳定性

  • 大模型先按 section 检查,不要一上来 --contacts all。
  • 超过 500 件,auto 会跳过接触;这是显式覆盖缺口,不是通过。
  • 渲染前检查僵尸 LeoCAD 进程。
  • 大场景用 --detail summary,需要时再分页 --limit/--offset。
  • 不要反复重跑同一耗时任务;先看报告定位。

14. 交付清单

一个合格交付应包含:

  • design-brief.md:范围、构图、焦点、配色。
  • *.plan.json:可编辑的结构化源。
  • generate.py:如果模型是参数化生成。
  • *.mpd:最终可打开模型。
  • build.json:构建报告。
  • validation.json:几何/连接报告。
  • bom.json:物料清单。
  • review/*.png:至少 home/front/top,复杂模型加侧/背。
  • visual-review.md:看了哪些图、发现什么、改了什么。
  •  明确写出:physical_validity 未证明、未做的检查、已知审美限制。

14.1 不能声称的事

  • 不能因为 checks_passed=true 就说“物理可建造”。
  • 不能因为渲染成功就说“视觉审查完成”。
  • 不能因为 BOM 一致就说“零件一定买得到”。
  • 不能因为连接器 confirmed 就说“强度足够”。
  • 不能因为模型好看就说“结构稳定”。

15. 本工作区的四个实战案例

项目
颗粒
类型
主要经验
output/japanese-garden
743
池泉庭园
45° 斜坡件、雁行铺装、桥/池/亭主从
output/lingnan-garden
648
岭南水庭
青砖白墙、满洲窗、趟栊门、太湖石
output/suzhou-garden
630
苏州园林
白墙黛瓦、月洞门、漏窗、方亭攒尖顶
output/european-garden
646
法式规则园
中轴、喷泉、四象限花坛、柱廊

共同结论:

  1. 先定总平面,再摆零件,比边摆边想稳定得多。
  2. 几何检查通过后必须开图;四套模型的首版都出现过“合法但不对”的问题。
  3. 多格件中心、旋转尺寸、堆叠高度是最高频的三类 bug。
  4. 用户确认前不要自行连续重构;首版可看就先交付,再按点名方向修。

16. 维护与扩展:怎么改这套 Skill

16.1 新增一个“受控矩形件” profile

如果某个普通砖/板需要支持 on 的严格堆叠、或需要 body 碰撞检查:

  1. 在 ldraw_tools/data/rectangular-parts.json 增加真实 body 尺寸:
    json    "3001": {"description":"Brick 2 x 4","x_studs":4,"z_studs":2,"height":24.0,"studs":true}    
  2. 用 part CODE 核对原点、bounds、stud 位置。
  3. 补测试:正常堆叠、半格偏移、旋转后 X/Z 交换、错误偏移必须失败。
  4. 不要用 bounds 高度代替 body 高度。

16.2 新增分类符号

data/categories/ 是静态元数据。新增符号只是让 plan 可以用可读名引用零件:

  • 必须仍然在安装库中解析真实 .dat;
  • 分类尺寸只是搜索提示,不能替代 part 的实测;
  • 不要让分类文件成为“官方零件存在性”的证明。

16.3 新增 CLI 子命令

ldraw_tools/cli.py 的模式很固定:

  1. parser()
     注册子命令与参数。
  2. run(args)
     返回 (report_dict, exit_code)。
  3. 所有报告必须是 JSON 可序列化。
  4. 退出码遵守 0/1/2 约定。
  5. 写文件必须使用 atomic_write(),并且只在检查通过后写。
  6. 增加对应测试和 docs/agent/tooling.md 条目。

16.4 新增渲染/导出适配器

渲染适配器必须保证:

  • 不修改源 MPD 和安装库;
  • 使用临时目录 + 唯一文件名;
  • 子进程设置 timeout;
  • 失败时清理临时产物;
  • 嵌入 DAT 的命名在 BOM 中恢复;
  • 返回的图片路径和 BOM 路径可验证。

16.5 升级 pyldraw3 / 影子库

重点回归:

  • 物理件边界与嵌入 DAT;
  • 旋转矩阵容差;
  • 连接器 feature ID / occupancy;
  • 方形插孔与圆凸点的适配;
  • BOM 与 LeoCAD 的一致性;
  • snap 候选与碰撞状态。

升级后如果连接器覆盖变化,必须重新跑 connectivity 和 snap 回归,不能只看语法测试。

16.6 不应修改的资源

  • 安装的 LDraw 库;
  • data/models-annotated/
     的源模型;
  • data/ldraw-info.db
    ;
  • data/offLibShadow/
     的原始影子文件;
  • 他人的作者信息与许可证。

所有新工作放 output/,所有修复留在 plan/生成器,不手改最终 MPD。


附录 D:源码阅读最短路线

如果要把这套 Skill 真正读懂,按这个顺序读,不要从 700 行的 CLI 开始:

  1. instructions.md
    :先理解工作流和交付标准。
  2. docs/agent/ldraw-reference.md
    :坐标系、type 1、MPD、颜色。
  3. ldraw_tools/data/plan.schema.json
    :先看数据契约。
  4. ldraw_tools/builder.py
     第 23–60 行:includes / schema / 继承。
  5. builder.py
     第 157–248 行:placement 求解与最终写入门槛。
  6. validation.py
     第 54–230 行:语法、矩阵、重复、可达性。
  7. geometry.py
     第 65–190 行:碰撞、接触、组件。
  8. collision.py
    :AABB / OBB / body 的三级判定。
  9. connectivity.py
     第 111–244 行:snap 候选与 apply。
  10. external.py
     第 216–247 行:渲染与 BOM。
  11. document.py
     第 22–109 行:MPD 命名空间与物理边界。
  12. docs/agent/visual-design.md
    :最后再学审美,不要反过来。

读完这条线,再看 cli.py 只是命令分发,不会迷失在参数里。


附录 A:核心命令速查

# 环境ldraw-agent doctor# 零件 truthldraw-agent part 3001 --limit 40ldraw-agent catalog parts 'plant bush' --category plants --limit 8 --measureldraw-agent colours blue# 参考ldraw-agent search parts 'brick 2 x 4'ldraw-agent study data/models-annotated/10270-1.mpd --report output/study.jsonldraw-agent extract data/models-annotated/10270-1.mpd \  --section '10270 - Pendulum_Clock.ldr' --namespace ref-clock \  --output output/assets/clock.mpd# 生成ldraw-agent build output/model.plan.json --output output/model.mpd \  --detail summary --report output/build.json# 验证ldraw-agent validate output/model.mpd --geometry --contacts all \  --report output/validation.jsonldraw-agent inspect output/model.mpd --detail summary \  --report output/inspection.json# 视觉ldraw-agent render output/model.mpd --outdir output/review \  --views home front top right# BOMldraw-agent bom output/model.mpd --report output/bom.jsonldraw-agent compare-bom output/model.mpd --csv output/review/leocad-bom.csv# 连接ldraw-agent connectors output/model.mpd --occurrence 1ldraw-agent snap output/model.mpd --moving 1 --fixed 0 --limit 5

Windows 当前工作区把 ldraw-agent 替换为:

./.venv/Scripts/python.exe -m ldraw_tools.cli

附录 B:术语表

术语
含义
LDU
LDraw Unit,1 stud = 20 LDU
MPD
多文件 LDraw 模型容器
placement
plan 中的一次零件/子模型放置
section
MPD 中的一个 FILE 块 / plan 中的一个模块
physical leaf
最终计为 BOM 的物理件实例
AABB
轴对齐包围盒
OBB / SAT
有向包围盒 / 分离轴定理
confirmed contact
双方连接器元数据匹配的接触
optimistic contact
较弱证据支持的接触候选
anchor
作者声明的模块接口坐标系
snap
连接器驱动的自动对位
body overlap
矩形砖/板实体真正互插
physical_validity
工具固定输出 not_proven,表示未做真实物理认证

附录 C:交付前自检

  •  所有零件来自真实库;不认识的件用 part 查过。
  •  堆叠高度用 body,不用 bounds。
  •  多格件使用正确中心。
  • build 无 error。
  • validate --geometry 无 error。
  •  大模型显式处理 contacts 覆盖。
  •  打开过所有关键渲染图。
  •  Python/LeoCAD BOM 一致。
  •  生成器可以重建同一 MPD。
  •  没有把“通过检查”说成“物理证明”。

相关学习资料