1. 从一句话到三维模型:text-to-cad 到底在解决什么问题
第一次听到 “text-to-cad” 这个词,很多人脑子里冒出来的画面大概是:对着电脑敲一句“给我画一个带四个安装孔的方形法兰盘”,然后屏幕上就自动出现一个可以旋转、可以导出、可以拿去加工的 3D 模型。这个画面听起来像是科幻,但它背后其实是一条已经能跑通的技术链路——把自然语言描述转成结构化的几何参数,再驱动 CAD 内核生成实体,最后导出成 STEP、URDF 这类通用格式。
我自己做机械设计和机器人仿真有些年头了,平时最烦的事情之一就是重复建模。一个标准件、一个简单的支架、一个常见的连接板,明明结构大同小异,却要在 CAD 软件里一遍遍拉伸、打孔、倒角。text-to-cad 这个方向吸引我的地方,不是它要取代工程师,而是它能把“描述需求”和“生成初版模型”之间的那段机械劳动压缩掉。你负责想清楚要什么,它负责把第一版草稿搭出来,剩下的精修再交回给人。
这篇文章适合几类人看:一是做机器人仿真、经常要写 URDF 的朋友,你们会关心怎么把文字描述快速变成带关节的模型;二是做机械设计、想提升建模效率的工程师;三是玩 Python、对 CAD 自动化感兴趣的技术爱好者。哪怕你之前没碰过 CAD 二次开发,只要会一点 Python,跟着思路走也能理解整条链路是怎么搭起来的。核心关键词 text-to-cad、CAD、STEP、URDF、Python 会贯穿全文,我不会只讲概念,而是把参数怎么定、代码怎么写、坑在哪里都摊开说。
需要先说明一点:text-to-cad 目前不是一个开箱即用的成熟商业软件,它更像是一种“技术方案组合”。市面上有一些研究项目和开源尝试,但真正落地时,你往往需要自己把大语言模型、几何内核、格式转换这几块拼起来。所以这篇文章的重点,是讲清楚这条链路的设计思路和实操方法,而不是推荐某个具体产品。
2. 整体方案设计:为什么是“语言模型 + 几何内核 + 格式转换”三段式
2.1 核心思路拆解
把 text-to-cad 拆开看,它本质上是一个“翻译”问题。输入是人类语言,输出是三维几何。中间隔着好几层语义鸿沟:人类说“一个厚 5 毫米的板子”,语言模型能理解“板子”和“5 毫米”,但它不知道这对应到几何内核里是一个长方体还是拉伸体;就算生成了几何,还要考虑这个模型能不能被 CAD 软件正确读取,能不能在仿真环境里正常导入。
所以合理的架构一定是分段的,我把它归纳成三段式:
- 第一段:语言理解与参数抽取。用大语言模型把自然语言解析成结构化的 JSON 或字典,里面包含形状类型、尺寸、位置、孔位、倒角等参数。
- 第二段:几何生成。用 Python 驱动几何内核(比如 CadQuery、build123d 这类基于 OpenCASCADE 的库)把参数变成实体模型。
- 第三段:格式导出与校验。把实体导出成 STEP 用于 CAD 交换,或者导出成 URDF 用于机器人仿真,并做基本的几何有效性检查。
这三段之间是松耦合的,每一段都可以单独替换。比如你不想用大语言模型,完全可以写一个规则解析器;你不想用 CadQuery,也可以换成 FreeCAD 的 Python API。这种松耦合设计的好处是,哪一段出问题都好排查,不会牵一发动全身。
2.2 为什么选 Python 作为粘合层
热词里 Python 出现频率极高,这不是偶然。CAD 领域的二次开发 traditionally 用的是 C++ 或者软件自带的脚本语言,但 Python 这几年在 CAD 自动化里越来越主流,原因很实际:
第一,几何内核的 Python 绑定已经相当成熟。OpenCASCADE 这个工业级几何内核,通过 PythonOCC、CadQuery、build123d 等库暴露出了可用的接口,你不需要懂 C++ 就能调用布尔运算、倒角、抽壳这些操作。
第二,Python 在数据处理和 AI 生态上有天然优势。大语言模型的调用、JSON 解析、参数校验,这些用 Python 写起来都很顺手。你不太可能用 C++ 去调一个语言模型 API 然后解析返回结果,那太别扭了。
第三,Python 的容错和迭代速度快。text-to-cad 这种探索性很强的项目,参数经常要调,逻辑经常要改,用 Python 改一行跑一次,比编译型语言舒服太多。
提示:如果你之前只写过 Python 脚本处理 Excel 或者爬虫,完全不用担心。CAD 自动化的 Python 代码,核心就是调用几个几何函数,逻辑复杂度并不比数据处理高多少。
2.3 几何内核选型:CadQuery 还是 build123d
这是实操中第一个要做的选择。两者都基于 OpenCASCADE,都能生成实体并导出 STEP,但风格不同。
CadQuery 出现得早,社区大,文档多,用的是链式调用风格,写起来像这样:cq.Workplane("XY").box(50, 30, 5).faces(">Z").workplane().hole(6)。它的优势是例子多,遇到问题容易搜到答案。
build123d 是后来者,语法更接近 Python 原生,支持上下文管理器和代数运算,写复杂装配体时更清晰。它的定位是 CadQuery 的“现代化替代”,但社区相对小一些。
我的建议是:如果你刚开始,先用 CadQuery,因为踩坑时能找到的参考资料多;如果你已经熟悉了几何建模,想要更干净的代码结构,可以试 build123d。两者不是互斥的,你甚至可以在同一个项目里混用,因为它们底层都是 OpenCASCADE。
| 对比项 | CadQuery | build123d |
|---|---|---|
| 语法风格 | 链式调用 | 上下文管理器/代数式 |
| 社区规模 | 大,资料多 | 较小,增长中 |
| 学习曲线 | 平缓 | 稍陡但更灵活 |
| 复杂装配 | 一般 | 更好 |
| STEP 导出 | 支持 | 支持 |
2.4 输出格式的选择逻辑
text-to-cad 的输出格式,取决于你下游要干什么。热词里 STEP 和 URDF 都出现了,这两个格式的用途完全不同。
STEP 是 CAD 交换的标准格式,几乎所有 CAD 软件都能读。你生成的模型如果要拿去 SolidWorks、中望 CAD、Fusion 360 里继续编辑,或者要发给加工厂,就导出 STEP。它是边界表示(BRep)格式,保留了精确的几何信息。
URDF 是机器人描述格式,本质是一个 XML 文件,描述的是连杆(link)和关节(joint)的层级关系,以及每个连杆的几何形状、质量、惯性矩阵。它不关心精确的 BRep,只关心碰撞体和视觉体。如果你要把模型导入 CoppeliaSim 做仿真,就需要 URDF。
所以一个完整的 text-to-cad 方案,往往要同时支持这两种导出。STEP 用于设计验证,URDF 用于仿真验证。这也是为什么我在架构里把“格式导出”单独作为一段,因为它不是简单的另存为,URDF 还需要额外计算质量和惯性参数。
3. 核心细节解析:从自然语言到几何参数的映射规则
3.1 语言模型该输出什么样的结构化数据
这是整个链路里最关键的一环。语言模型不能直接输出几何,它必须输出一个中间表示。我试过几种方案,最后觉得最稳的是让模型输出一个固定 schema 的 JSON。
举个例子,输入是“一个 80 毫米长、40 毫米宽、6 毫米厚的底板,四角各有一个直径 5 毫米的孔,孔中心距边缘 8 毫米”。模型应该输出类似这样的结构:
{ "shape": "plate", "length": 80, "width": 40, "thickness": 6, "holes": [ {"type": "circle", "diameter": 5, "x": 8, "y": 8}, {"type": "circle", "diameter": 5, "x": 72, "y": 8}, {"type": "circle", "diameter": 5, "x": 8, "y": 32}, {"type": "circle", "diameter": 5, "x": 72, "y": 32} ] }注意这里的孔位坐标是绝对坐标,不是“距边缘 8 毫米”这种相对描述。为什么?因为几何内核处理绝对坐标更直接,相对描述需要在代码里再做一次换算,容易出错。让语言模型承担这个换算工作,反而更可靠,因为它在训练数据里见过大量类似的坐标计算。
注意:一定要在提示词里明确单位。我踩过的坑是模型有时默认毫米,有时默认米,导致生成的模型尺寸差了一千倍。解决办法是在系统提示里强制声明“所有尺寸单位为毫米”。
3.2 参数校验:别让错误参数流到几何内核
语言模型会犯错,这是常识。它可能生成负数尺寸,可能让孔位跑到板子外面,可能把直径写成半径。如果这些错误参数直接传给几何内核,轻则报错,重则生成一个看起来正常但实际错误的模型。
所以中间必须加一层校验。我的做法是写一个validate_params函数,检查几件事:
- 所有尺寸必须为正数。
- 孔位坐标必须在板子边界内,且留出足够的边距(至少大于孔径的一半)。
- 如果描述里提到了孔的数量,实际生成的孔数量要匹配。
- 单位统一为毫米。
校验不通过时,不要直接抛异常,而是把错误信息返回给语言模型,让它重新生成。这就是所谓的“自我修正循环”。实测下来,加上这一层之后,一次生成的成功率能从六成提到九成以上。
3.3 几何建模的常见操作与对应函数
参数校验通过后,就进入几何生成。以 CadQuery 为例,几个最常用的操作和它们的意图:
- 拉伸成体:
Workplane("XY").rect(l, w).extrude(t),把二维矩形拉成三维板子。这是最基础的体素生成。 - 打孔:
.faces(">Z").workplane().hole(d),在指定面上打孔。>Z表示选 Z 方向最高的面。 - 倒角:
.edges("|Z").fillet(r),对平行于 Z 轴的边做圆角。倒角半径不能大于相邻边长度的一半,否则会失败。 - 布尔运算:
.cut(other)做差集,.union(other)做并集。复杂形状基本靠布尔运算拼出来。
这些函数看起来简单,但组合起来能做出相当复杂的零件。关键是要理解“工作平面”的概念——CadQuery 里几乎所有操作都发生在某个工作平面上,选错平面是新手最常见的错误。
3.4 URDF 导出的特殊处理
如果你要导出 URDF,事情会复杂一些。URDF 不只需要几何,还需要质量、惯性矩阵、关节类型。这些信息自然语言里通常不会提到,所以要么让语言模型根据材料密度估算,要么在代码里用默认值。
我的做法是:在参数 schema 里增加一个可选的material字段,默认是铝(密度 2.7 g/cm³)。质量用体积乘以密度算,惯性矩阵用立方体近似公式算。对于仿真来说,这个精度通常够用。
URDF 的几何部分用的是简化形状,通常是 box、cylinder、sphere 这三种基本体。所以从精确的 STEP 模型到 URDF,需要做一次“碰撞体简化”。这一步不能省,否则仿真会非常慢。
4. 实操过程:手把手搭一条可运行的 text-to-cad 链路
4.1 环境准备与依赖安装
先把环境搭起来。我假设你用的是 Python 3.8 以上版本,Windows、Linux、macOS 都行。核心依赖就几个:
pip install cadquery pip install openai pip install numpyCadQuery 的安装在某些系统上需要额外步骤,因为它依赖 OpenCASCADE 的二进制库。如果你用 conda,会更省事:
conda install -c conda-forge cadquery提示:CadQuery 安装失败是新手最常见的卡点。如果 pip 装不上,优先试 conda。如果 conda 也慢,可以配置国内镜像源。这一步别硬扛,换个方式往往就通了。
4.2 语言模型调用与提示词设计
我用的是通用的对话接口,你可以换成任何支持结构化输出的模型。关键是提示词要写清楚。我的系统提示大概是这样:
你是一个 CAD 参数解析器。用户会用自然语言描述一个零件,你需要输出 JSON 格式的参数。 规则: 1. 所有尺寸单位为毫米。 2. 形状类型只支持 plate(板)、cylinder(圆柱)、box(方块)。 3. 孔位用绝对坐标表示,原点在零件左下角。 4. 如果信息不全,用合理默认值,并在 warnings 字段说明。 5. 只输出 JSON,不要输出其他文字。用户输入直接拼在后面。实测下来,提示词里把“只输出 JSON”强调三遍都不为过,模型很爱加解释性文字。
4.3 参数解析与校验代码
拿到 JSON 后,先解析再校验。下面是一段可直接参考的校验代码:
import json def validate_params(params): errors = [] if params.get("length", 0) <= 0: errors.append("length must be positive") if params.get("width", 0) <= 0: errors.append("width must be positive") if params.get("thickness", 0) <= 0: errors.append("thickness must be positive") l, w = params.get("length"), params.get("width") for i, hole in enumerate(params.get("holes", [])): d = hole.get("diameter", 0) x, y = hole.get("x"), hole.get("y") if d <= 0: errors.append(f"hole {i} diameter invalid") if not (d/2 <= x <= l - d/2 and d/2 <= y <= w - d/2): errors.append(f"hole {i} out of bounds") return errors这段代码的逻辑很直白:尺寸必须正,孔必须在边界内且留出半径的余量。校验失败就把错误信息回传给模型重新生成,最多重试三次。
4.4 几何生成与 STEP 导出
校验通过后,用 CadQuery 生成模型:
import cadquery as cq def build_plate(params): l = params["length"] w = params["width"] t = params["thickness"] result = cq.Workplane("XY").rect(l, w).extrude(t) for hole in params.get("holes", []): result = (result.faces(">Z").workplane() .center(-l/2 + hole["x"], -w/2 + hole["y"]) .hole(hole["diameter"])) return result model = build_plate(params) cq.exporters.export(model, "output.step")注意center那一步的坐标换算。CadQuery 的工作平面原点默认在矩形中心,而我们的参数原点在左下角,所以要减去一半的长度和宽度。这个换算很容易搞错,建议写个小函数封装起来。
4.5 URDF 导出与 CoppeliaSim 导入验证
如果要导出 URDF,需要额外构造 XML。核心是定义 link 和 joint。对于单零件,只有一个 link:
def to_urdf(params, mesh_path): l, w, t = params["length"], params["width"], params["thickness"] mass = l * w * t * 2.7e-3 # 铝,单位克转千克 urdf = f'''<?xml version="1.0"?> <robot name="part"> <link name="base_link"> <visual> <geometry><box size="{l/1000} {w/1000} {t/1000}"/></geometry> </visual> <collision> <geometry><box size="{l/1000} {w/1000} {t/1000}"/></geometry> </collision> <inertial> <mass value="{mass/1000}"/> <inertia ixx="1e-6" iyy="1e-6" izz="1e-6" ixy="0" ixz="0" iyz="0"/> </inertial> </link> </robot>''' return urdf这里单位换算是另一个大坑。URDF 默认用米,而 CAD 参数用毫米,所以所有尺寸都要除以 1000。我见过有人忘了换算,结果模型在仿真里大了一千倍,把整个场景都撑爆了。
导入 CoppeliaSim 时,直接把 URDF 文件拖进去就行。如果模型位置不对,检查 URDF 里的 origin 设置。如果模型看不见,检查 visual 的 geometry 尺寸是不是太小。
5. 常见问题与排查技巧实录
5.1 语言模型输出不稳定怎么办
这是最高频的问题。同一个描述,模型这次输出对的,下次就漏了孔。我的应对策略有三个:
第一,降低温度参数。把 temperature 设成 0 或 0.1,输出会稳定很多。创意性在这个场景里没有价值,我们要的是确定性。
第二,用 few-shot 示例。在提示词里放两三个输入输出对,模型会模仿格式。这比单纯描述规则有效得多。
第三,加校验重试。前面说的自我修正循环,能兜住大部分偶发错误。
5.2 几何生成失败的典型原因
CadQuery 报错时,信息往往很晦涩。我整理了几种常见情况和对应排查方向:
| 报错现象 | 可能原因 | 解决办法 |
|---|---|---|
| 倒角失败 | 半径大于相邻边一半 | 减小倒角半径 |
| 布尔运算结果为空 | 两个实体没有重叠 | 检查位置参数 |
| 导出 STEP 为空 | 模型不是实体 | 检查是否只生成了面 |
| 孔位偏移 | 坐标系原点理解错误 | 统一用绝对坐标 |
提示:CadQuery 的
val()方法可以检查模型是否有效。生成后加一句assert model.val().isValid(),能提前发现问题。
5.3 单位与坐标系的坑
单位问题我前面提过,这里再强调一次。CAD 参数用毫米,URDF 用米,STEP 本身不带单位但通常按毫米解释。三套体系混在一起,不出错才怪。我的做法是在项目里定义一个MM_TO_M = 0.001常量,所有换算都走这个常量,绝不手写数字。
坐标系也一样。CadQuery 的工作平面原点在中心,参数原点在左下角,URDF 的原点在几何中心。每次跨系统传递数据,都要问一句“这个坐标是相对谁的”。
5.4 性能优化:批量生成时怎么提速
如果你要批量生成几十上百个模型,单次几秒的生成时间会累积成几分钟。几个提速技巧:
- 几何生成是 CPU 密集的,可以用多进程并行。Python 的
multiprocessing模块直接能用。 - 语言模型调用是网络密集的,可以批量发送请求,减少往返次数。
- STEP 导出比 URDF 慢,如果只需要仿真,直接生成 URDF 跳过 STEP。
实测下来,一个简单板件从描述到 STEP 大约 3 到 5 秒,其中语言模型占大头。如果本地部署小模型,能压到 1 秒以内,但解析准确率会下降,需要权衡。
5.5 模型精度与仿真需求的平衡
最后说一个容易被忽视的点:text-to-cad 生成的模型,精度够不够用?答案是看场景。用于概念设计、布局验证、仿真碰撞检测,完全够用。用于最终加工出图,不够,还需要人工在 CAD 软件里补尺寸标注、公差、表面粗糙度。
我个人的定位是把它当成“第一版草稿生成器”。它帮你跳过从零开始画草图的那半小时,但后面的精修和工程化,还是得人来。认清这个定位,就不会对它有不切实际的期待,也能更好地把它嵌进工作流里。
这个方向后续还能扩展的地方不少,比如支持装配体描述、支持从图纸反向生成参数、支持参数化模板库。但那是后话了,先把单零件的链路跑通,比什么都实在。