周末我把一句"给我一个四角带安装孔的铝支架"丢进 text-to-cad 工具里,等了两分钟,出来一个看起来像那么回事的模型。但当我尝试改动底部圆角的位置时,整个模型直接崩了。这不是工具不行,而是我没搞明白 text-to-cad 的底层逻辑——它不是在"替我想设计",它只是把一句含糊的自然语言强行翻译成一堆几何指令,中间省略了太多我作为工程师本该先说清楚的信息。
这篇文章不打算念论文,我只想从一个经常用 CAD 做结构件的普通用户角度,聊聊 text-to-cad 到底是什么、有哪些实现路线、我实际搭出来的最小工作流长什么样,以及那些"看着对了实际用不了"的坑。如果你也是工程师、创客、产品经理,或者纯粹想拿自然语言快速出模型,这篇应该能帮你少走不少弯路。
1. text-to-cad 想解决的真实问题:从"概念描述"到"可计算几何"的鸿沟
先说清楚 text-to-cad 到底在解决什么问题。CAD 文件本质不是一张图,而是一棵有拓扑关系、尺寸约束、特征顺序的数据树。自然语言句子是线性的、离散的、充满指代和省略;CAD 几何是连续的、精确的、要求每个数字都有明确含义。比如"一个圆角矩形底座"这句话,在你脑子里可能长 200mm、宽 120mm、高 15mm、圆角 R5,但在另一个人的脑子里可能完全是另一组数字。LLM 能理解语义,但它不知道你省略了什么;几何内核则相反,它只认数字,不知道什么"差不多"。
Text-to-CAD 的完整链路,就是把语义层面的"意图"翻译成数值层面的"定义",再把定义转成可执行的建模指令。难点不在最后一步——执行几何操作本身很成熟,难在前两步:如何从一段话里准确提取几何要素,如何为缺省参数选择合理默认值,以及如何处理自然语言里大量的歧义。
1.1 你要的到底是一个模型,还是一份能改的图纸
开始用任何 text-to-cad 工具之前,我建议先回答一个问题:你最后拿到手的,必须是什么形态的东西?
- 如果只是做视觉展示或者 3D 打印外观件,一个封闭的 STL 网格可能就够了,很多文生 3D 扩散模型可以直接满足。
- 如果要拿去装配、出工程图、做 CNC 加工,你需要的是 B-Rep 实体或者带参数化历史的 CAD 文件,网格模型在这里基本是废品。
- 如果要做系列化规格,今天生成 120mm 宽,明天想改 150mm 宽再生成一版,你必须要保留参数化脚本,而不是一个焊死的 STEP 文件。
我一开始犯的错就是没定义清楚"可用"是什么,导致拿了一个网格模型兴冲冲导进 SolidWorks,结果所有面都是碎三角面,根本无法基于它修改。判断工具是否合适,第一条先看输出格式,而不看演示视频多炫。
1.2 三条技术路线的取舍:程序化建模、生成式网格、混合检索
目前市面上的 text-to-cad 项目,抛开包装,底层基本只有三条路线。
路线一是 LLM + 程序化建模。语言模型输出 CadQuery、OpenSCAD 这类建模脚本,再由几何内核执行脚本生成实体。这条路最大的优点是输出天然带参数、可编辑,几何精度由内核保证;缺点是语言模型生成长脚本不稳定,一个变量写错就全盘崩。
路线二是文生 3D 扩散模型。从文本直接生成点云、体素或者网格,代表人物/项目有点云生成和表面重建那一套方法。优点是自由形状表现力强,能生成你描述不出来的有机造型;缺点是精度低,经常产生非流形网格,且输出基本没有特征树,后续想要圆角、孔特征得靠逆向工程。
路线三是检索 + 参数化调整。先把文本匹配到已有模型库,再从最相似的模板上做参数修改。优点是非常稳定,速度和成功率都高;缺点是一旦遇到库里没见过的类别就直接无能为力。
三条路线的选择基本就决定了你的下游体验。我把它们放在一起对比过:
| 对比项 | LLM + 程序化建模 | 文生 3D 扩散模型 | 检索 + 参数调整 |
|---|---|---|---|
| 输出格式 | CAD 脚本 / STEP | STL / OBJ 网格 | 已有模型 + 新参数 |
| 几何精度 | 高,由内核保证 | 低到中,表面有三角化误差 | 中到高,取决于模板质量 |
| 可编辑性 | 强,参数可改 | 几乎不可编辑 | 仅在预设参数范围内可改 |
| 典型工具/思路 | CadQuery、OpenSCAD 项目 | Point-E、Shape-E 等学界项目 | PartNet 等参数化模型库 |
| 适合对象 | 机械零件、支架、外壳 | 概念造型、游戏资产 | 标准件、重复性场景 |
1.3 不同路线的工具选型参考
如果你做的是工程零件,我的建议很直接:优先选路线一。虽然它不如扩散模型"看起来智能",但它输出的脚本/STEP 文件能真正进入你的工作流。路线二适合工业设计初期的造型探索,拿到网格后可以用曲面重建工具转到 NURBS,但整个过程并不省时间。路线三适合企业内已经有大量标准件库的场景,可以做一个私有的 text-to-cad 入口,让用户用自然语言搜索和改参数。
我个人在实际项目中搭的是路线一,下面这套流程我跑了三个月,稳定性能接受,踩坑经历也最有代表性。
2. 从零搭一套"提示词到可编辑零件"的最小流程
我自己搭的最小流程是:自然语言提示词 -> 结构化 JSON -> CadQuery 脚本 -> 导出 STEP/STL。这套流程可以在本地跑,不依赖任何云端服务,也方便插入校验环节。
2.1 为什么我选 CadQuery 而不是 OpenSCAD 或 Blender Python
选建模库这件事值得多说两句。OpenSCAD 的 CSG 建模对简单几何很直接,但圆角、倒角这些在机械零件里最常见的操作,并不是它的"一等公民",通常要借助 minkowski、hull 这类运算变相实现,代码长得快,LLM 生成这种代码时也更容易出语法错误。Blender Python 擅长的是网格建模,出曲面、雕刻很方便,但它的几何精度和 B-Rep 表达能力跟 CAD 内核不是一回事,导进机械 CAD 后基本只剩三角面。
CadQuery 的好处是它直接封装了 OpenCASCADE 这个与 FreeCAD 同源的几何内核,建模手写 Python 代码,概念上跟 SolidWorks 的特征树很像:先建一个平面和草图,然后拉伸、挖孔、倒角。这一切都能在命令行里完成,不需要打开 GUI,非常适合接在 LLM 后面做自动执行。
安装方式也简单,虽然 Windows 上建议用 conda 环境:
pip install cadquery装好之后,所有几何操作开始前,先建立一个明确的坐标系约定。CadQuery 的 Workplane 是从某个基础平面开始的,默认的 box 原点在几何中心,这一点非常关键,后面我会专门说它有多坑。
2.2 约束型提示词模板:把自然语言拆成几何要素
我很快意识到,直接让 LLM"写一个 CadQuery 脚本"效果很差,因为自由代码生成的空间太大。更稳的做法是给它定义一个 JSON 契约,让它只输出结构化数据。这样一来,我可以在执行前做数据校验,也可以在执行后追踪每个参数来源。
我的提示词模板大概是这样的:
你是一个参数化 CAD 助手。请根据用户描述输出 JSON,不要输出任何多余内容。 JSON 字段如下: - summary: 对模型的一句话描述 - operations: 操作列表,每个操作包含 type 和 params 支持的操作类型: - box: 生成长方体底座,参数 length, width, height - hole: 在当前位置打孔,参数 diameter - fillet: 参数 radius - translate: 参数 x, y, z,单位毫米 - rotate: 参数 axis, angle 尺寸单位统一使用毫米。坐标系约定:模型底部中心为原点,+Z 向上。用户只需要在后面追加一句自然语言需求。比如"一块长 120 宽 80 厚 10 的矩形板,四角各有一个直径 6.5 的通孔,孔中心距长边和短边都是 10 毫米。"
模型输出的 JSON 长这样:
{ "summary": "四角各有一个贯穿安装孔的矩形法兰板", "operations": [ { "type": "box", "params": { "length": 120, "width": 80, "height": 10 } }, { "type": "hole", "params": { "diameter": 6.5 } } ], "hole_positions": [ { "x": 10, "y": 10 }, { "x": 110, "y": 10 }, { "x": 10, "y": 70 }, { "x": 110, "y": 70 } ] }注意这里没有让 LLM 直接写 CadQuery 命令,而是先定义"操作清单"和"位置清单",因为位置数据用列表表达,比用散落在脚本里的 center(x, y) 更容易检查。
2.3 从 JSON 到 CAD 实体的执行器实现
有了 JSON 之后,执行器其实很短。我维护这样一个 Python 脚本:
import json import sys import cadquery as cq def build_part(spec: dict) -> cq.Workplane: # 第一步:建立底板 box = spec["operations"][0]["params"] length = box["length"] width = box["width"] height = box["height"] part = cq.Workplane("XY").box(length, width, height) # 第二步:在顶面打孔 for pos in spec["hole_positions"]: part = ( part.faces(">Z") .workplane() .center( pos["x"] - length / 2, pos["y"] - width / 2, ) .hole(spec["operations"][1]["params"]["diameter"]) ) return part if __name__ == "__main__": spec = json.load(open(sys.argv[1])) part = build_part(spec) part.export("output.step") part.export("output.stl")这个脚本最容易被忽略的就是 center 里的- length / 2和- width / 2。因为 CadQuery 的 box 默认中心在原点,所以你选择的顶面 workplane 原点也在顶面中心;但用户描述的"距左边 10、距前边 10"通常是基于左下角作为零点。我在自动化流程里统一约定:所有 JSON 里的坐标都按"以底板左下角为原点"输入,执行器把它转换到 CadQuery 中心坐标系。这样 LLM 少背一个坐标系约定,生成的坐标也更贴近人的直觉。
运行方式很简单:
python cad_builder.py spec.json生成 output.step 和 output.stl。前者用于工程,后者用于 3D 打印预览。
2.4 一个完整例子:四孔法兰板
我测试时常用的 prompt 是:
"我需要一块长 120、宽 80、厚 10 的矩形板,四角各有一个直径为 6.5 的通孔,孔中心距长边和短边都是 10mm,材料默认铝。"
LLM 返回上面的 JSON,执行器生成实体,导出 STEP。之后我会检查一下:6.5mm 通孔对应 M6 螺栓,通常间隙孔取 6.5~7mm,没问题;但孔边距只有 10mm,对 6.5mm 孔径来说只能说勉强够,如果板要承受载荷,这个边距就该重新考虑。
这就是 text-to-cad 的典型工作流:它能快速给你一个"几何上正确、工程上待审"的初稿。初稿能不能用,还是靠人来判断。
3. 避坑实录:生成结果"看着对"却用不了的四个深层原因
跑了三个月之后,我发现 text-to-cad 最大的问题并不是"生成不了模型",而是"生成的模型看着没毛病,但一进工程流程就露馅"。下面这四个问题,我每一个都踩过。
3.1 歧义与省略:人类语言天然缺失几何约束
自然语言太省了。你说"一个底座,四角开孔,能过 M3 螺丝"——M3 螺丝过孔直径一般取 3.4mm,孔距边多少?没说。四角是严格正四角还是长边四角?没说。底座厚度?也没说。
LLM 为了给你一个答案,只能随机选一个"合理"解释。同一句话跑几次,可能一次孔边距 5mm,一次 20mm。这不是模型笨,而是你的表达本身有一堆开放变量。
我的应对方式是:在提示词模板里强制要求所有参数必须显式出现,未指定参数必须返回null并且执行器报错,而不是让模型默默猜。一次生成不出来的话,系统会回问用户:"M3 过孔直径取 3.4mm,孔距边默认 20mm,可以吗?" 这一步直接把成功率提高了一大截。
3.2 单位、坐标系与草绘基准面:最常见的隐性问题
有一回我让工具生成一块"厚度 0.25 inch 的板子",它输出了 0.25mm 厚。我盯着屏幕想了好久才反应过来,不是模型不聪明,是它没做单位换算,直接把英寸数字当成毫米用了。
排查链路供参考:
- 自动检查 bounding box,发现厚度跟目标差 25.4 倍。
- 回溯原始 prompt,定位到"0.25 inch"这个未归一化输入。
- 修复:在 JSON 契约里写死"所有尺寸使用毫米,非毫米输入必须换算后再输出";同时在执行器里加一个长度校验函数,如果 zlen 与声明高度差超过 5%,直接拒绝生成。
另一个高频问题是坐标系偏移。用户习惯说"孔距左边 10",这个"左边"从他看屏幕的角度是左下角;而 CadQuery box 默认中心在原点,所以用户坐标系的零点根本不在模型原点。如果 LLM 没做转换,孔位就会整体偏移半个板子。
我最终的解决方案就是 2.3 里那段统一换算逻辑。现在所有坐标都以零件左下角为原点输入,执行器统一减半。用一句话总结:模型可以生成,但坐标系的"人话"和"内核话"必须由系统负责翻译,不能让模型每次碰运气。
3.3 布尔运算和特征顺序:脚本能跑,几何却无效
更隐蔽的问题在几何内核内部。有时候脚本一点不报错,STEP 也能导出,但把文件拖进 FreeCAD,软件直接提示形状无效。这是因为 OpenCASCADE 的布尔运算对重合面、共面情况非常敏感。
我遇到过的典型场景:两个实体要拼成一个支架,我在设计上让 A 的右侧面与 B 的左侧面完全贴合,再执行 union。理论上这是最简单的拼接,但实际 OpenCASCADE 有可能在这类共面 edge 处产生非流形,导致isValid()返回 False。
排查和解决手段:
shape = part.val() print("valid:", shape.isValid()) print("volume:", shape.Volume()) print("solids:", len(shape.Solids()))如果 valid 为 False,或者 volume 为 0,基本可以断定布尔环节出了问题。我的对策是:在设计提示词里就要求 LLM 为每个实体保留一个很小的重叠区,比如 0.01mm,而不是严格贴合。这样可以绕开大多数共面计算问题。
特征顺序也影响巨大。先倒角再挖孔,和先挖孔再倒角,结果可能完全不同。如果倒角半径接近孔边距,倒角会把孔边吃掉。这种问题光靠isValid()查不出来,必须看目标尺寸是否变化。
3.4 长脚本的"上下文遗忘":多实体自动生成时的断裂
LLM 生成短小的 JSON 很稳,一旦生成超过几十行的复杂脚本,就开始出现"上下文遗忘":前面定义了一个变量叫hole_radius,后面二十行突然用了r;前面说四个孔,后面数组里只列了三个;前面坐标是按毫米写的,后面有一段代码突然用英寸。
我早期试图让模型直接输出完整 CadQuery 脚本,失败率非常高。后来改成让它只输出结构化 JSON,脚本逻辑全部固化在我的执行器里。这样模型需要处理的"内容量"大幅减少,生成稳定性明显提升。
对于多零件结构,我的建议是不要一次性生成完整装配,而是先逐个生成零件 JSON,每个零件单独导出校验,最后再用 CadQuery 的Assembly或直接通过 translate 做位置装配。分而治之,每一步的错误都更容易定位。
4. 验证生成结果是否好用:我常用的四层校验法
text-to-cad 生成完不等于结束。尤其在工程场景,眼睛看着没问题没用,必须用几何数据说话。我现在把所有生成结果都过一遍四层校验,全部通过才认为这是一次有效生成。
4.1 第一层:实体有效性检查,先确认不是一张破壳
第一个检查永远是最基本的实体有效性。在 CadQuery 里可以这样看:
shape = result.val() print("valid:", shape.isValid()) print("volume:", shape.Volume()) print("solids:", len(shape.Solids()))一个正常实体应该满足:isValid 为 True,volume 是正数,solids 数量至少为 1。volume 为 0 意味着你得到的是壳而不是实体;solids 数量为 0 说明形状在拓扑上根本没有闭合。绝大对数时候,这个检查能拦下 70% 的失败生成。
4.2 第二层:尺寸与距离自动测量
视觉上"感觉对了"不算数。我会用 bounding box 自动读取外形尺寸:
bb = shape.BoundingBox() print("xlen:", bb.xlen) print("ylen:", bb.ylen) print("zlen:", bb.zlen)把测量结果跟目标尺寸做差,超过阈值就直接失败。对于我常用的公差约定:
target = {"xlen": 120, "ylen": 80, "zlen": 10} got = {"xlen": bb.xlen, "ylen": bb.ylen, "zlen": bb.zlen} for k in target: assert abs(got[k] - target[k]) < 0.05, f"{k}偏差过大: {got[k]} vs {target[k]}"0.05mm 的容差对 3D 打印和手板原型足够,对精密机械加工不够。后者需要更严格的公差体系,不建议让 text-to-cad 直接背这个责任。
孔直径和孔位置也需要单独验证。遍历edges("%circle")读取圆边半径即可,孔的位置则通过读取圆心的坐标与目标坐标比较。
4.3 第三层:导出 STEP 后回导 FreeCAD 实测
CadQuery 里导出的 STEP 文件保留的是 B-Rep 几何,但参数化历史不会保留。我通常会把 STEP 导入 FreeCAD,用测量工具挑几个关键面、关键边看实际距离。这一步能发现很多脚本层面注意不到的问题。
为什么不建议直接用 STL 做这个检查?因为网格的三角化误差会掩盖真实的几何边界,尤其圆孔和倒角,网格看起来还算圆,实际精度差很多。STEP 是 NURBS/精确平面,量出来的数据才可信。
这一步也可以做成自动化:导出 STEP 后再用另一个脚本或者 FreeCAD 的 Python API 再次测量,把目标尺寸和生成尺寸做断言。这样整个流程可以接入 CI,每天晚上批量跑一批用例,早上看报告。
4.4 第四层:参数化能力实测——改一个数,模型还能不能变?
最后一道检查,也是最容易被忽略的:把生成结果里的一个关键参数改掉,重新生成,看模型是不是真的"可参数化"。比如把厚度从 10 改成 12,把孔径从 6.5 改成 8.5,重新执行生成脚本。
如果修改卡在一个写死的数字上,说明这次生成的脚本是一次性文件,不值得沉淀。好的 text-to-cad 生成结果应该是一个参数化的模板,而不是一张快照。我的个人判断标准很简单:如果我不能在 5 秒内改一个参数并重新生成,那这个东西只能算临时草图,不配叫 CAD 模型。
在提示词里可以明确要求:"所有尺寸必须从 JSON 读取,不允许硬编码。" 这能显著提高参数化能力。执行器里如果有默认值,也要保持每个数字都来自 spec,而不是散落在代码里。
5. 什么场景适合 text-to-cad,什么场景建议放弃
事到如今,我对 text-to-cad 的态度已经从"什么都想让它干"变成"只在合适的场景用它"。它是个工具,有清晰的边界,用对了很省事,用错了很痛苦。
5.1 真正值得用它的四类任务
第一类是标准件快速备选。法兰、垫片、支架、外壳开孔这类结构相对规整的零件,文字描述就能覆盖 90% 的几何信息,非常适合让 text-to-cad 出初稿。
第二类是概念方案对比。需要在一个小时内给客户看三五种安装支架外形时,让 AI 先生成几个版本,再从里面挑方向。比手工建模快得多,反正最后都要重新细化。
第三类是参数化族库生成。同一个支架,宽度从 100 到 300 每隔 20 生成一个实体,手工做太枯燥,用自然语言加上参数模板批量生成,效率极高。
第四类是协作初稿。设计师口头描述,AI 先出一个 3D 草案,让大家在实体的基础上讨论,而不是对着白板画箭头。这个场景下"能商量"比"够精确"更重要。
5.2 千万别指望它的三类任务
第一类是精密配合公差。轴孔 H7/g6 这种需要严格公差带的配合,文本描述很难带上完整的 GD&T 语义,而且生成的几何本身也没有公差属性。拿它出原理验证模型可以,拿它当制造依据不行。
第二类是复杂自由曲面。汽车覆盖件、风机叶片这类造型,自然语言完全描述不了 NURBS 控制点位置,用文生 3D 扩散模型也只是出一坨好看但不可制造的网格。这类任务应该走细分建模、逆向工程或者专业参数化曲面工具,别跟 text-to-cad 较劲。
第三类是强制标准合规的承压件、安全件。它生成的模型没有载荷分析、没有标准校核、没有材料认证,一旦出问题,责任完全没法追溯。这类件就算看起来能用,也只应该作为创意草稿,绝不可能作为最终交付。
5.3 进阶玩法:把 design rules 注入生成流程
如果项目再往前走一步,我建议把公司内部的设计规范注入到生成流程里。做法并不复杂:把设计规范文档做成检索知识库,生成 JSON 时让 LLM 基于检索回来的规范约束参数。比如"塑料外壳最小壁厚 1.2mm""拔模角建议 1 度以上",这些规则可以在生成阶段直接限制数值范围。
效果很明显。过去生成塑料件壁厚经常出现 0.8mm,看起来没啥问题,实际注塑根本注不满。把规则注入后,生成器会自动把壁厚修正到 1.2mm,并在 JSON 里记录"依据某条设计规则修改"。这样 text-to-cad 就从玩具级别往前迈了一大步,至少能进入半生产流程。
另外想强调一点:如果你希望下游 CAD 软件还能改特征,不要把 CadQuery 生成的 STEP 文件当作最终交付,最好把脚本和源码一起交出去。STEP 只保留 B-Rep 几何,不保留特征树,别人拿到的只是一个"没历史"的雕塑。脚本才是真正可编辑的资产。
我的最终建议是:把 text-to-cad 当做一个戴在 CAD 前面的翻译器,而不是一个自动设计引擎。使用之前定义好输出契约和校验流程,使用之后让工程师接手做判断。如果你坚持从复杂装配体开始,大概率会收获一堆彩色垃圾;但如果你从单个参数化零件开始,一步一步加校验,它会成为模型建立阶段最快的草稿工具。