1. 从一句话到三维模型:text-to-cad 到底在解决什么问题
第一次听到 “text-to-cad” 这个词,很多人会下意识觉得它离自己很远,像是实验室里的概念。但如果你真的动手做过参数化建模,就会明白它戳中的是一个特别具体的痛点:从“脑子里有个形状”到“手里有个能用的模型文件”之间,隔着一条又长又枯燥的手工链路。传统流程里,你得打开 CAD 软件,一根线一根线地画草图,一个尺寸一个尺寸地约束,再拉伸、旋转、倒角,最后导出 STEP 或者 STL。整个过程里,真正需要你动脑判断的部分可能只占两成,剩下八成全是重复劳动。
text-to-cad 想做的事情,就是把这八成重复劳动交给程序。你用自然语言描述一个零件,比如“一个外径 60mm、内径 40mm、厚度 10mm 的圆环,边缘倒 2mm 圆角”,程序解析这句话,生成对应的几何体,再导出成 STEP 这种通用格式。听起来简单,但里面牵扯到的东西不少:文本怎么解析、几何怎么参数化、约束怎么表达、导出格式怎么选、生成结果怎么验证。这一整套链路,才是 text-to-cad 真正的技术含量所在。
我之所以对这个方向感兴趣,是因为它天然把几个热门领域串在了一起。Python负责文本处理和流程编排,CAD内核负责几何运算,STEP负责数据交换,URDF负责把模型送进机器人仿真环境。这几个词单独拎出来都是各自领域的常客,但组合起来就形成了一个从“语言”到“物理模型”的完整通路。尤其是做机器人或者自动化方向的朋友,URDF 导入 CoppeliaSim 这类需求非常普遍,而 text-to-cad 恰好能把这个环节的建模成本压下来。
这篇文章适合几类人看。一类是经常和 CAD 打交道、被重复建模折磨的工程师,你想知道能不能用代码替代手工;一类是 Python 开发者,你想找一个能把编程能力和三维建模结合起来的落地场景;还有一类是学生或者刚入门的朋友,你可能对 CAD 制图初学入门还在摸索阶段,想看看有没有更省力的路径。不管你是哪一类,下面的内容都会从原理讲到实操,尽量让你看完就能自己搭一套出来。
需要先说明一点:text-to-cad 不是一个现成的、开箱即用的商业软件,它更像是一种技术思路和工具组合。市面上有一些产品在往这个方向做,但真正落地时,你往往需要自己拼装一套流程。所以这篇文章的重点不是推荐某个软件,而是把这条链路拆开,告诉你每一环用什么工具、为什么这么选、坑在哪里。
2. 整体设计思路:为什么是 Python + CAD 内核 + STEP 这条链路
2.1 文本到几何的三种技术路线对比
在动手之前,得先想清楚一件事:文本怎么变成几何?这个问题有好几种解法,选错了路线,后面会越做越别扭。我把常见的三种路线列出来对比一下。
| 路线 | 核心思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 模板填充 | 预定义参数化模板,文本只负责填参数 | 实现简单、结果可控、速度快 | 只能生成预设形状,灵活性差 | 标准化零件批量生成 |
| 规则解析 | 用正则或语法解析文本,映射到建模指令 | 灵活度中等、可解释性强 | 规则维护成本高,复杂描述难覆盖 | 结构相对固定的零件描述 |
| 大模型生成 | 用语言模型理解文本,输出建模代码或几何参数 | 灵活度最高、能处理模糊描述 | 结果不稳定、需要验证环节 | 探索性建模、快速原型 |
我实际试下来,规则解析 + 模板填充的混合方案在工程上最稳。纯大模型生成看起来很美好,但它有个致命问题:同样的描述,两次生成的结果可能不一样,而且它可能生成一个语法正确但几何上完全错误的模型。对于工程用途来说,这种不确定性是灾难性的。所以我的建议是,把大模型当成“翻译器”,让它把自然语言翻译成结构化的参数,再由确定性的几何代码去执行。这样既保留了灵活性,又保证了结果可复现。
2.2 为什么选 Python 作为编排语言
Python 在这个链路里的角色是“胶水”和“大脑”。文本解析、参数校验、调用几何内核、导出文件,这些环节用 Python 串起来最顺手。原因有几个:一是 Python 的字符串处理和正则能力足够强,解析文本描述很自然;二是主流的 CAD 内核基本都有 Python 绑定,比如 OpenCASCADE 的 Python 封装、CadQuery、build123d 这些库;三是 Python 生态里有大量现成的工具,比如用 numpy 做数值计算、用 cv2 做图像辅助验证,都能无缝接进来。
还有一个很现实的原因:Python 的学习曲线平缓。如果你之前只写过一点脚本,甚至只是看过 Python 安装教程,也能较快上手。相比之下,用 C++ 直接调 OpenCASCADE 虽然性能更好,但开发效率会低很多,对于原型验证阶段不划算。
2.3 STEP 与 URDF 的分工:一个管交换,一个管仿真
很多人会混淆 STEP 和 URDF 的用途,这里必须说清楚。STEP 是几何交换格式,它描述的是“这个零件长什么样”,包含完整的边界表示(BRep)信息,可以被几乎所有 CAD 软件读取。你生成的模型要拿去加工、装配、出图,走 STEP 就对了。
URDF 是机器人描述格式,它描述的是“这个机器人由哪些连杆和关节组成、它们怎么连接、质量惯量是多少”。URDF 里的几何通常用简单的碰撞体或者网格文件表示,它不追求几何精度,追求的是运动学和动力学的正确性。所以当你需要把模型导入 CoppeliaSim 做仿真时,URDF 才是正确的入口,而不是 STEP。
理解了这层分工,整个链路就清晰了:文本 → Python 解析 → 几何内核建模 → 导出 STEP(用于交换)→ 转换 URDF(用于仿真)。每一步都有明确的输入输出,出了问题也容易定位。
3. 核心细节解析:几何内核、参数表达与文本解析
3.1 几何内核选型:CadQuery 还是 build123d
几何内核是整个项目的发动机,选型直接决定了开发体验。目前 Python 生态里最主流的两条路是 CadQuery 和 build123d,它们底层都基于 OpenCASCADE,但上层 API 风格差别很大。
CadQuery 用的是链式调用风格,代码写起来像流水线。比如画一个圆环,大概是这样:
import cadquery as cq result = ( cq.Workplane("XY") .circle(30) .circle(20) .extrude(10) .edges() .fillet(2) )build123d 则更接近上下文管理器的风格,用 with 语句组织建模过程,读起来更像在描述一个装配过程:
from build123d import * with BuildPart() as ring: with BuildSketch() as sk: Circle(30) Circle(20, mode=Mode.SUBTRACT) extrude(amount=10) fillet(ring.edges(), radius=2)我个人的选择是:如果你追求代码简洁、快速出结果,CadQuery 更顺手;如果你要做复杂装配、需要更精细的控制,build123d 的结构更清晰。两者都能导出 STEP,性能差异在日常使用中几乎感觉不到。对于 text-to-cad 这种需要动态生成代码的场景,CadQuery 的链式风格更容易用字符串拼接出来,所以我早期用 CadQuery 比较多。
3.2 参数化表达:把自然语言映射成数字
文本解析的核心任务,是把“外径 60、内径 40、厚度 10”这样的描述,变成程序能用的变量。这里有个关键设计:参数必须有明确的类型和单位。我见过太多项目因为单位混乱而翻车,比如把毫米当成厘米,生成的模型大了十倍。
我的做法是定义一个参数 schema,每个参数包含名称、类型、单位、取值范围。比如:
PARAM_SCHEMA = { "outer_diameter": {"type": "float", "unit": "mm", "min": 1, "max": 1000}, "inner_diameter": {"type": "float", "unit": "mm", "min": 0, "max": 1000}, "thickness": {"type": "float", "unit": "mm", "min": 0.1, "max": 500}, "fillet_radius": {"type": "float", "unit": "mm", "min": 0, "max": 50}, }有了 schema,解析出来的参数就能自动校验。比如内径大于外径,程序应该直接报错而不是生成一个空心的怪东西。这一步看起来不起眼,但它是保证结果可用的第一道防线。
3.3 文本解析的实操策略:正则打底,模型兜底
解析自然语言描述,我的策略是两层处理。第一层用正则表达式匹配常见模式,覆盖大部分标准描述;第二层用语言模型处理正则搞不定的模糊表达。
正则这一层,关键是设计好模式。比如“外径 60mm”可以匹配外径\s*(\d+(?:\.\d+)?)\s*(mm|厘米|cm)?,然后根据单位做换算。常见的描述词要尽量穷举:外径、内径、直径、半径、厚度、高度、长度、宽度、倒角、圆角、孔、槽。这些词覆盖了大部分简单零件的描述。
语言模型这一层,我通常让它输出 JSON 格式的参数,而不是直接输出建模代码。这样即使模型理解有偏差,参数校验环节也能拦住。提示词可以这样设计:“把下面的零件描述转换成 JSON,字段包括 shape_type、dimensions、features,单位统一用毫米。” 实测下来,这种结构化输出的稳定性比让它直接写代码高得多。
注意:语言模型的输出一定要做二次校验,尤其是数值范围。我遇到过模型把“厚度 10”理解成“厚度 10 米”的情况,虽然罕见,但一旦发生就是灾难。
4. 实操过程:从零搭一套 text-to-cad 流程
4.1 环境准备与依赖安装
先把环境搭起来。Python 版本建议 3.9 以上,太老的版本在装几何库时容易遇到编译问题。安装 CadQuery 最省事的方式是用 conda,因为它的依赖里有不少 C++ 库,pip 装起来容易缺东西。
conda create -n text2cad python=3.10 conda activate text2cad conda install -c conda-forge cadquery pip install numpy如果你坚持用 pip,也可以试试pip install cadquery,但要做好心理准备,可能会卡在 OCP 或者 vtk 的编译上。我踩过的坑是:在 Windows 上直接用 pip 装,经常报 C++ 编译错误,后来换成 conda 就一路顺畅。Linux 环境下 pip 的成功率会高一些,但也建议优先 conda。
验证安装是否成功,跑一段最简单的代码:
import cadquery as cq box = cq.Workplane("XY").box(10, 10, 10) cq.exporters.export(box, "test.step") print("导出成功")如果当前目录下出现了 test.step,说明环境没问题。用 CAD 软件打开这个文件,应该能看到一个 10mm 的立方体。
4.2 定义零件模板与参数映射
接下来定义几个常用模板。模板的作用是把参数和几何操作绑定起来,这样解析出参数后直接套模板就行。我一般从最简单的开始:圆柱、圆环、方块、带孔板。
以圆环为例,模板函数长这样:
def make_ring(outer_d, inner_d, thickness, fillet_r=0): if inner_d >= outer_d: raise ValueError("内径必须小于外径") result = ( cq.Workplane("XY") .circle(outer_d / 2) .circle(inner_d / 2) .extrude(thickness) ) if fillet_r > 0: result = result.edges().fillet(fillet_r) return result这里有个细节值得说:倒角操作要放在最后。如果你先倒角再拉伸,或者顺序搞反,很容易出现几何错误。OpenCASCADE 对操作顺序比较敏感,边倒角尤其如此。我的经验是,所有基于边的操作(倒角、圆角)都放在实体成型之后。
4.3 文本解析到参数再到模型的完整串联
把前面几块拼起来,主流程大概是这样:
import re import json from cadquery import exporters def parse_text(text): params = {} patterns = { "outer_diameter": r"外径\s*(\d+(?:\.\d+)?)", "inner_diameter": r"内径\s*(\d+(?:\.\d+)?)", "thickness": r"厚度\s*(\d+(?:\.\d+)?)", "fillet_radius": r"倒角\s*(\d+(?:\.\d+)?)", } for key, pattern in patterns.items(): match = re.search(pattern, text) if match: params[key] = float(match.group(1)) return params def build_model(params): return make_ring( params["outer_diameter"], params["inner_diameter"], params["thickness"], params.get("fillet_radius", 0), ) text = "一个外径60mm、内径40mm、厚度10mm的圆环,边缘倒2mm圆角" params = parse_text(text) print("解析参数:", params) model = build_model(params) exporters.export(model, "ring.step")跑完这段,你会得到一个 ring.step。用 CAD 软件打开,检查尺寸对不对。这一步的验证很重要,因为文本解析可能出错,几何生成也可能出错,必须用肉眼确认一次。
4.4 导出 STEP 与转换 URDF 的关键差异
导出 STEP 很简单,一行代码的事。但如果你要转 URDF,事情就复杂一些。URDF 需要的不只是几何,还有坐标系、质量、惯量、关节类型。对于单个零件,你可以把它当成一个连杆,但质量惯量得自己算或者估。
一个简化的 URDF 片段长这样:
<robot name="ring_robot"> <link name="ring_link"> <visual> <geometry> <mesh filename="ring.stl"/> </geometry> </visual> <collision> <geometry> <mesh filename="ring.stl"/> </geometry> </collision> <inertial> <mass value="0.1"/> <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/> </inertial> </link> </robot>注意 URDF 里的几何通常用 STL 或 DAE,而不是 STEP。所以你需要先把模型导出成 STL,再在 URDF 里引用。质量惯量如果不想手算,可以用 CadQuery 或者 trimesh 这类库自动计算。我一般用 trimesh 算体积和惯量,再填进 URDF,比手算靠谱得多。
导入 CoppeliaSim 时,直接把 URDF 拖进去就行。如果模型位置不对,检查 URDF 里的 origin 设置。常见问题是模型原点不在几何中心,导致导入后偏到一边。解决办法是在建模时就把坐标系对齐到质心,或者在 URDF 里加偏移。
5. 常见问题与排查技巧实录
5.1 几何生成失败的典型原因
几何操作报错是最常见的问题,尤其是倒角和布尔运算。下面这张表是我踩坑总结出来的:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| fillet 报错 | 倒角半径大于相邻边长度 | 减小半径,或先检查边长度 |
| 布尔运算结果为空 | 两个实体没有交集 | 检查位置和尺寸 |
| 导出 STEP 失败 | 模型不是有效实体 | 用model.val().isValid()检查 |
| 模型显示破面 | 曲面自交或退化 | 简化几何,避免极端参数 |
| 尺寸不对 | 单位混淆 | 统一用毫米,解析时做换算 |
倒角报错特别常见。比如一个厚度只有 2mm 的板,你让它倒 5mm 圆角,几何上根本不可能。程序不会自动帮你判断这个,所以参数校验里要加一条:倒角半径不能超过最小壁厚的一半。这个规则不是绝对的,但能拦住大部分错误。
5.2 文本解析的歧义处理
自然语言天然有歧义。“直径 60”到底是外径还是内径?如果文本里只说了直径,我一般默认是外径,因为这是更常见的表述。但更稳妥的做法是,在解析结果里标记出不确定的字段,让用户确认。
还有一种情况是单位缺失。“厚度 10”没写单位,默认按毫米处理。但如果用户实际想表达厘米,结果就差了十倍。我的做法是在解析后打印一条提示:“未检测到单位,默认按毫米处理,如有误请补充单位。” 这种小提示能省掉很多来回沟通。
5.3 导出文件在 CAD 软件里打不开怎么办
STEP 文件打不开,通常是两个原因:一是文件本身损坏,二是版本不兼容。CadQuery 导出的 STEP 默认是 AP214 或者 AP203,大部分 CAD 软件都支持。如果打不开,先试试用 FreeCAD 打开,FreeCAD 对 STEP 的兼容性很好,能打开说明文件没问题,是目标软件的问题。
另一个坑是文件名带中文或特殊字符。有些 CAD 软件对路径很敏感,建议导出时用纯英文文件名。我遇到过中文文件名导致导入失败的情况,改成英文就好了。
5.4 性能优化:批量生成时怎么提速
如果你要批量生成几百个模型,单线程跑会很慢。几何运算本身是 CPU 密集型的,可以用多进程加速。Python 的 multiprocessing 在这里很合适,因为每个模型的生成是独立的,没有共享状态。
from multiprocessing import Pool def generate_one(params): model = build_model(params) exporters.export(model, f"output/{params['id']}.step") with Pool(8) as p: p.map(generate_one, param_list)注意不要用多线程,因为 Python 的 GIL 会限制性能,几何运算又释放不了 GIL。多进程虽然有点内存开销,但提速效果明显。我实测下来,8 核机器上批量生成能快 5 到 6 倍。
提示:批量生成时记得给每个任务加超时和异常捕获,否则一个模型卡住会导致整个批次失败。
6. 这条链路还能怎么扩展
把基础流程跑通之后,能扩展的方向其实很多。往上游走,可以接一个更聪明的文本理解层,支持多轮对话式的建模,用户说“再大一点”“孔往左移”,程序能增量修改。往下游走,可以把生成的模型直接接到仿真或者 3D 打印切片流程,形成从描述到实物的闭环。
我最近在试的一个方向是参数化模板库。把常见的标准件,比如法兰、齿轮、轴承座,都做成模板,文本只需要描述关键尺寸,模板负责生成完整几何。这样既保证了标准化,又保留了灵活性。对于做机械设计的朋友,这套东西能省下大量重复建模的时间。
另一个有意思的扩展是几何验证自动化。生成模型后,自动检查壁厚、干涉、最小孔径这些工程约束,把不合格的模型拦在导出之前。这一步用 CadQuery 的几何查询能力就能做,比如遍历所有面,检查相邻面的距离。虽然不能替代专业 CAE 分析,但作为第一道筛子足够了。
最后分享一个小技巧:如果你经常需要把 CAD 模型转成 URDF 做仿真,可以写一个统一的转换脚本,把 STEP 到 STL 到 URDF 的流程固化下来。这样每次只需要换输入文件,输出自动生成,省得每次手动操作。我在实际项目里就是这么干的,一个命令跑完整个链路,比手工操作快得多,也不容易出错。