第一次把一张 A0 钢结构施工图交给大模型时,我很快发现,真正要命的问题不是它答错,而是我无从知道它到底读到了什么。
它说"这是一张包含多个视图的工程图纸"------对,但跟没说一样;它要敢给个数字------"共 12 件零件"------我信还是不信?信不得,也证不得。渲染图里 0.12pt 细线糊成一团灰,尺寸数字在 PDF 里根本不是文本而是 glyph 轮廓路径,整份 SVG 灌进上下文就爆。它"看见"了什么,我没有一条路能验证。答对,分不清是读懂还是蒙的;答错,也定位不到错在哪一步。
三周之后我把这个"不可知"一点点拆掉,结论一条:给模型两份东西------管几何的"骨",管语义的"魂"。下面每一处设计,说到底都是同一件事:把不可知变成可测。
骨和魂:为什么一份不够
单载体我试过两条路,都治不了"不可知"。纯 SVG:像素级可复刻,但模型面对坐标流,"会画不懂",它"懂"了什么无从核对;纯 MD:语义可读可编辑,但"看懂画不准",细节一省略就丢,丢了哪条查不出来。所以双载体,各管一半,同一套 ID 互联,两边的账对得上:
flowchart TD PDF"源图 PDF(矢量线画 / 无文字层)" PDF -->|"提取归层"| SVG"SVG 骨\
页面 pt 坐标 / 图层 / 视图分组" PDF -->|"同一次提取"| MD"MD 魂\
语义注解 / 尺寸值 / 工艺 / BOM" SVG <-->|"同一 ID + crosswalk"| MD MD -->|"默认主读"| LLM"LLM 消费" SVG -->|"按 data-view 切片"| LLM
三条规矩:单一事实源,每类信息只在一处为真,另一处只引用;ID 互联而非坐标复制,MD 留局部 mm 参数坐标(可编辑),SVG 留页面 pt 原生坐标(精确),换算写进 crosswalk;token 可行,MB 级 SVG 永不整份进上下文。冲突:几何以 SVG 为准,值与语义以 MD 为准。
八步流水线
PDF → 1 提取分层 → 2 视图聚类 → 3 文本恢复 → 4 MD 增补 → 5 配准 → 6 三方互校 → 7 LLM 验收 → 8 齐套交付
四个坑
颜色即语义。 get_drawings() 能按图元输出路径与颜色,而工程图纸的颜色不是装饰:黑=主轮廓、青=中心线、黄=细线/标题栏、绿=尺寸、红=特殊。按颜色归层,语义切分自动完成,模型声称读到的每段都能定位到图层。先剔无描边的纯白填充矩形------一个 f 图元曾把整页盖住。
视图聚类的串簇问题。 用 outline+dimension+centerline 三层做空间连通聚类把视图拆开。图框线和标题栏线横跨全页,黄层(thin)若参与聚类,所有视图串成一个大簇。排除黄层后世界清净;粘连处人工指定 bbox。
文本恢复:数字要背靠背核对。 尺寸值靠视觉多模态模型认。优先级:MD 已有值 > 绿层高 DPI 裁切复读 > glyph OCR------OCR 对细线 glyph 太脆,162° 能读成 163°。两源独立读再对账------单源读对读错都是它一张嘴。对不上进 unclear,不臆造。读不清不丢人,编数才丢人。
ccw90:调了我一个下午。 横放图纸塞在竖放页面里,渲染出来是歪的:
竖放页面
┌────────────┐
│ 内容转了90° │
│ ┌────────┐ │
│ │ 图纸内容 │ │ ← 内容的"上"朝左
│ └────────┘ │
└────────────┘
回正 = ccw90 → fitz page.set_rotation(270)
我一开始写成 set_rotation(90),图"正"了但左右颠倒,配准对不上。救了我的是 self_check 断言:局部原点 (0,0) 换算后必须落在该视图 bbox 左下角。换算链:s = 2.83465 / 比例分母,X_L = x·s + tx,Y_L = −y·s + ty,竖放页面再 x_p = W − Y_L,y_p = X_L。旋转错一步,断言立刻炸------这也是全方案的缩影:不靠肉眼抽查,靠断言当场抓住。
ID 是锚
"指认"是我最看重的维度,它正面回答"不可知":模型说的每句话,得能落回图上确切位置。靠统一 ID------视图 V{nn},图元 V{nn}-P{mm},尺寸 V{nn}-D{mm},气球 V{nn}-B{mm},SVG 与 MD 共用一套编号:
V15(吊耳分解详图,1:20)
V15-P02 → SVG <path data-prim-id="V15-P02"> MD {"id":"V15-P02","type":"POLYLINE"} 件11 轮廓
V15-D03 → SVG <path data-dim-id="V15-D03"> + <text data-dim-id="V15-D03" data-value="930">
MD | V15-D03 | 930 | linear/v | 件10 高度方向 |
V15-B11 → MD V15-B11 → 件11 主吊耳 δ30,圆头 R150,起吊孔 Ø80
于是"V15-D03 是哪里的什么"有唯一答案:V15 视图、件10、高度方向。crosswalk 带 bbox 与换算参数,指认精确到页面 pt;它由脚本同源生成、SVG metadata 放同一份,禁手改,防漂移。
BOM 也是:有的图标题栏根本没明细表。降级:记录已识别零件编号 + 标注"BOM 未提供",不臆造材料重量。诚实的"不知道"比漂亮的"编的"值钱。
交付前,先考它
"模型准确理解了这张图"不能是感觉,得可测------这是对"不可知"的正面回答。我把它拆成四个分量:几何精度 × 语义完整 × 可指认 × token 可行,缺一不可。校验两层。其一,三方互校:MD 参数重绘与 SVG 叠合做几何差集、绿层值集合与 dims 核对、BOM 与气球核对。其二,验收题库:每图至少 20 题,覆盖结构、几何、关系、工艺、指认五类;只喂交付物不喂方案文档,前四类 ≥95%、指认类 100% 才算过。
喂法也有讲究
整份 SVG(约 9000+ path) ████████████████████ MB 级,永不整份喂
按视图切片(data-view="V15") ████ 10~40KB,要坐标时喂
MD 单章(一个视图章节) █ 3~10KB,默认主读
默认只读 MD,按视图章节分块;要精确坐标时,按 data-view 抽对应 SVG 组连同 MD 章节一起喂,切片剔除 glyph 路径段。重绘改图以 MD 参数为输入,校验测量以 SVG 切片为输入。
边界与后话
这套方法适合"矢量线画 + 无文字层"的 PDF 工程图,A0 钢结构施工图是典型场景。三类情况别用:已有文字层且无需精确几何;只要渲染图给人看;栅格扫描件,矢量化另案。剩余风险:crispEdges 吞细线靠生成约束内建;缺 cairo 用 PIL 直读 get_drawings;换版重跑 diff 变更单。
这套踩坑经验我整理成了一个 skill:SVG/MD 双载体规范、八步流水线各步要点、验收标准都在里面,拿到新图纸可直接按规范执行:
https://github.com/znlgis/opengis-skills/blob/main/cad/design-drawing-svg-md/SKILL.md