1. 从一句话到三维模型:text-to-cad 到底在解决什么问题
第一次听到 “text-to-cad” 这个词,很多人脑子里浮现的画面大概是:对着电脑敲一行字,比如“一个长宽高 100×60×30 毫米、四角带 M4 沉头孔的安装底板”,然后软件“唰”地一下吐出一个可以旋转查看的三维模型。这个画面在几年前还属于科幻范畴,但现在已经有一批开源项目和商业工具在认真做这件事了。text-to-cad 的核心目标非常直白——把自然语言描述转换成计算机辅助设计(CAD)能识别的几何数据,最终落地成 STEP、STL、URDF 这类标准格式文件,让下游的仿真、3D 打印、机器人建模等环节可以直接消费。
它解决的问题其实很现实。传统 CAD 建模的门槛不在于“想不出形状”,而在于“把想法翻译成软件操作”这一步太耗时。一个熟练的 SolidWorks 或中望 CAD 用户画一个带孔法兰盘可能要十分钟,但一个只懂业务、不懂建模的工程师描述这个法兰盘只需要一句话。text-to-cad 想做的就是吃掉这中间的翻译成本。适合关注这个方向的人包括:做机器人仿真的工程师(需要批量生成 URDF 模型)、做参数化设计的机械从业者、研究 LLM 与几何推理交叉领域的技术人,以及想用 Python 把设计流程自动化的开发者。
需要先泼一盆冷水:目前的 text-to-cad 远没有达到“说人话就出精确工程图”的程度。它更擅长生成概念级、结构相对规整的几何体,对于复杂曲面、装配约束、公差标注这些真正的工程难点,仍然需要人工介入。所以把它定位成“设计流程的加速器”而不是“设计师替代品”,心态会稳很多。下面我会从整体架构、核心格式、实操流程到踩坑经验,把这条链路完整拆一遍。
2. 整体架构与方案选型:为什么是 Python + STEP + URDF 这套组合
2.1 三种主流技术路线的取舍逻辑
在动手之前,得先想清楚“文本怎么变成几何”这件事有哪几条路可走。我梳理下来大致是三类,各有各的适用边界。
第一类是基于代码生成(Code-as-Representation)。核心思路是让大模型输出一段建模脚本,比如 CadQuery、OpenSCAD 或 build123d 的 Python 代码,然后执行这段代码得到几何体。这条路的最大优势是可解释、可复现、可参数化——你拿到的不只是一个模型,而是一段能改参数的源码。缺点是模型必须能用代码描述,遇到自由曲面就抓瞎。
第二类是基于几何原语拼接。让模型输出一系列基本体(长方体、圆柱、球)的位置、尺寸、旋转,再通过布尔运算组合。这条路实现简单,但表达能力有限,稍微复杂点的形状就拼不出来。
第三类是基于隐式表示或神经场,直接回归出点云或 SDF。学术味最浓,但工程落地最难,生成结果往往不可编辑,对下游 CAD 流程不友好。
我最终选择第一类为主、第二类为辅,原因很直接:工程场景要的是“能改、能导出、能进仿真”,而不是“看起来像”。一段 CadQuery 脚本生成的 STEP 文件,可以无缝导入 FreeCAD、中望 CAD 或者任何支持 STEP 的软件,还能直接喂给 URDF 导出器做机器人模型。这个链路是通的,而神经场那条路目前还断在“如何转成 B-rep”这一步。
2.2 为什么锁定 STEP 和 URDF 两个输出格式
输出格式的选择直接决定了这个项目能服务哪些下游场景。STEP(.step/.stp)是 ISO 10303 标准下的三维数据交换格式,几乎是所有正经 CAD 软件的“普通话”。你用它导出,别人用中望 CAD、SolidWorks、Fusion 360 都能打开,而且保留 B-rep 边界表示,尺寸精确、可继续编辑。这是给机械设计和 3D 打印准备的。
URDF(Unified Robot Description Format)则是机器人领域的 XML 描述格式,描述的是“连杆+关节”的树状结构,配合可视化网格(通常是 STL 或 DAE)使用。做机器人仿真的人对这个格式再熟悉不过——导入 CoppeliaSim、Gazebo、PyBullet 都靠它。text-to-cad 如果能直接吐出 URDF,意味着你可以用一句话描述一个机械臂的连杆尺寸和关节类型,然后立刻在仿真环境里跑起来。这个价值对做机器人快速原型的人来说是巨大的。
提示:STEP 和 URDF 不是二选一的关系。一个典型的做法是先用 CadQuery 生成实体并导出 STEP,同时把同一批几何体导出为 STL 网格,再写一个 URDF 文件引用这些网格。这样一套几何数据同时服务设计和仿真两条线。
2.3 Python 生态的不可替代性
选 Python 几乎是必然的。CadQuery 本身就是 Python 库,build123d 也是,trimesh 处理网格是 Python,URDF 的解析和生成有 urdfpy、yourdfpy 这些库,连调用大模型 API 都是 Python 最顺手。整个链路从“文本解析”到“代码生成”到“几何构建”到“格式导出”,可以全部在一个 Python 进程里完成,不需要在多个软件之间倒腾文件。
更关键的是 Python 让参数化变得自然。比如用户说“生成一个直径 50 毫米、厚度 10 毫米的法兰盘,中心孔直径 20 毫米,均布 6 个直径 6 毫米的螺栓孔”,模型生成的代码里这些数字都是变量,用户改一个数字重新执行就能得到新模型。这种“文本→参数→几何”的映射,用 Python 表达是最清晰的。
3. 核心细节解析:文本如何被翻译成可执行的建模代码
3.1 提示词工程:把模糊描述逼成结构化参数
text-to-cad 最容易翻车的地方不是几何构建,而是文本理解。用户说“一个大一点的盒子”,这个“大一点”到底是多大?没有默认单位、没有参照物,模型只能瞎猜。所以第一步必须做的是提示词工程,把自然语言里的模糊量词逼成结构化参数。
我的做法是在系统提示里强制模型输出 JSON,字段包括几何类型、尺寸参数、单位、特征列表。比如输入“一个 100x60x30 的底板,四角打 M4 孔”,期望输出类似:
{ "type": "plate", "dimensions": {"length": 100, "width": 60, "thickness": 30}, "unit": "mm", "features": [ {"type": "hole", "diameter": 4.5, "count": 4, "position": "corners", "inset": 8} ] }注意这里 M4 孔我映射成了直径 4.5 毫米,因为 M4 是螺纹公称直径,实际过孔要留间隙,这是机械常识。模型不一定知道这个,所以需要在提示里补充“M 系列螺纹孔按公称直径加 0.5 毫米作为过孔直径”。这类领域知识的注入,是 text-to-cad 能不能用于实际工程的分水岭。
注意:单位缺失是最常见的坑。我现在的策略是——如果用户没写单位,默认按毫米处理,同时在返回结果里明确标注“已按毫米解释”。如果用户写了“米”或“英寸”,必须做单位换算,否则导出的 STEP 尺寸会差 25.4 倍,这种错误在装配时是灾难性的。
3.2 从 JSON 到 CadQuery 代码的映射规则
拿到结构化 JSON 之后,下一步是把它翻译成 CadQuery 代码。这一步我建议用模板 + 规则的方式,而不是再让大模型自由发挥。原因很简单:几何构建代码一旦出错,往往不是报错而是静默生成错误形状,调试成本极高。用确定性模板能大幅降低不确定性。
以底板为例,CadQuery 的核心代码大概长这样:
import cadquery as cq length, width, thickness = 100, 60, 30 hole_d, inset = 4.5, 8 result = ( cq.Workplane("XY") .box(length, width, thickness) .faces(">Z") .workplane() .rect(length - 2*inset, width - 2*inset, forConstruction=True) .vertices() .hole(hole_d) ) cq.exporters.export(result, "plate.step")这段代码的逻辑是:先在 XY 平面建一个长方体,选中顶面,在顶面建立一个工作平面,画一个比底板小一圈的构造矩形,取它的四个顶点作为孔位,然后打孔。forConstruction=True表示这个矩形只是用来定位,不参与最终几何。这种“用构造几何定位特征”的写法是 CadQuery 的惯用法,比手动算四个角的坐标要可靠得多。
3.3 参数校验:在生成几何之前拦住错误
我踩过最深的坑是:模型生成了一个厚度为负数的板,CadQuery 直接抛异常,整个流程中断。后来我加了一层参数校验,在生成代码之前先检查数值合理性。校验规则包括:所有尺寸必须为正数;孔径不能大于所在面的最小边长;孔间距不能小于孔径的 1.5 倍(否则孔会连在一起);圆角半径不能超过相邻边的一半。
这层校验看起来啰嗦,但它把大量“几何上不可能”的输入挡在了建模之前,用户体验提升非常明显。与其让用户看到一个莫名其妙的 CadQuery 报错,不如直接告诉他“你要求的 6 个直径 10 毫米的孔在 40 毫米宽的面上放不下,最小需要 60 毫米宽”。
4. 实操过程:从零搭一条 text-to-cad 流水线
4.1 环境准备与依赖安装
先把环境搭起来。我用的 Python 3.10,太新的版本有些几何库还没跟上。核心依赖是 CadQuery、trimesh、yourdfpy 和 openai(或任何兼容的模型接口)。
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install cadquery trimesh yourdfpy numpyCadQuery 的安装在某些平台上需要编译 OCP 绑定,如果 pip 装不上,可以考虑用 conda:conda install -c conda-forge cadquery。这一步卡住的人不少,尤其是 Windows 上缺 C++ 构建工具的情况。如果遇到编译错误,先装 Visual Studio Build Tools,勾选“使用 C++ 的桌面开发”。
提示:如果你只是想快速验证想法,可以用 CadQuery 的 Docker 镜像,省去环境配置的麻烦。但长期开发还是建议本地装,调试方便。
4.2 文本解析模块的实现
文本解析模块的职责是把用户输入变成结构化 JSON。我用的是“系统提示 + few-shot 示例”的方式。系统提示里写清楚支持的几何类型、参数命名规范、单位约定,然后给三到五个示例覆盖常见场景(板、轴、法兰、简单装配)。
关键技巧是在提示里明确列出“不支持的请求”,比如“不支持自由曲面”“不支持装配约束求解”“不支持公差标注”。这样模型遇到超纲请求时会主动说明,而不是硬编一个错误的几何出来。我实测下来,明确边界比模糊承诺的满意度高得多,因为用户知道什么能做、什么不能做,预期是清晰的。
解析结果拿到后,先过一遍 3.3 节的参数校验,通过后再进入代码生成。
4.3 几何构建与 STEP 导出
代码生成模块用 Jinja2 模板,每种几何类型一个模板。底板、圆柱、法兰、简单支架这几类覆盖了大部分概念设计需求。生成代码后用exec()执行,拿到 CadQuery 的 Workplane 对象,然后导出。
import cadquery as cq def build_and_export(params, out_path): code = render_template(params) # Jinja2 渲染 local_ns = {"cq": cq} exec(code, local_ns) solid = local_ns["result"] cq.exporters.export(solid, out_path) return out_path导出 STEP 时有个细节:CadQuery 默认导出的 STEP 单位是毫米,这符合大多数工程场景。如果你的输入是英寸,记得在导出前把几何缩放 25.4 倍,或者在 CadQuery 里用cq.Unit做单位转换。我见过有人导出后发现模型大了 25 倍,就是因为单位没对齐。
4.4 URDF 生成:从实体到机器人描述
URDF 的生成要复杂一些,因为它描述的是“连杆+关节”的树结构,而不是单个实体。我的做法是让用户用文本描述一个简单的串联机构,比如“一个两连杆机械臂,第一段长 200 毫米,第二段长 150 毫米,关节都是旋转关节”。
解析后,为每个连杆生成一个简单的几何体(通常是圆柱或长方体)作为可视化网格,导出为 STL,然后写 URDF 文件引用这些网格,并定义关节的原点和轴向。
<robot name="arm"> <link name="link1"> <visual> <geometry> <mesh filename="link1.stl"/> </geometry> </visual> </link> <joint name="joint1" type="revolute"> <parent link="base"/> <child link="link1"/> <origin xyz="0 0 0.1" rpy="0 0 0"/> <axis xyz="0 0 1"/> </joint> </robot>生成后一定要在 CoppeliaSim 或 PyBullet 里加载验证一遍。我遇到过 URDF 里关节原点写错,导致机械臂在仿真里直接穿模的情况。URDF 本身不会报错,但仿真时会露馅,所以验证环节不能省。
5. 常见问题与排查技巧实录
5.1 几何构建阶段的典型故障
| 问题现象 | 可能原因 | 排查与解决 |
|---|---|---|
CadQuery 抛ValueError: Null TopoDS | 布尔运算结果为空,通常是孔位超出实体范围 | 检查孔位坐标和实体边界,加参数校验 |
| 导出的 STEP 在 CAD 里打开是空文件 | 导出对象不是实体而是草图或线框 | 确认result是 Workplane 且包含 solid,用result.val()检查 |
| 模型尺寸差 25.4 倍 | 单位混淆,英寸当毫米 | 统一在解析阶段转成毫米,导出前再确认 |
| 孔打在了错误的面 | 工作平面选择错误 | 用faces(">Z")明确指定方向,避免用默认面 |
| 圆角导致几何失败 | 圆角半径大于相邻边 | 限制圆角半径不超过最短边的一半 |
5.2 文本解析阶段的坑
最常见的是模型“自作主张”补充参数。比如用户说“一个法兰盘”,没给尺寸,模型可能自己编一个直径 100 毫米。这在演示时看着挺好,但实际用起来很危险,因为用户以为是自己没说的部分被合理默认了,结果拿到一个完全不符合预期的模型。我的处理是:关键尺寸缺失时必须追问,不允许静默默认。只有非关键参数(比如倒角大小)才允许用默认值,并在输出里注明。
另一个坑是多义描述。“打几个孔”里的“几个”到底是几个?这种必须追问。我现在的策略是维护一个“必须明确”的参数清单,缺失就返回追问,而不是猜。
5.3 URDF 导入仿真环境的注意事项
URDF 导入 CoppeliaSim 时,最常见的报错是网格文件路径找不到。URDF 里的filename是相对路径,相对于 URDF 文件所在目录。如果你把 URDF 和 STL 放在不同目录,一定要写对相对路径,或者用package://形式(需要配置资源路径)。
还有一个隐蔽的坑:STL 网格的坐标系原点。CadQuery 导出的 STL 原点在几何中心还是角落,取决于建模时的定位。如果 URDF 里假设原点在连杆一端,但 STL 原点在中心,装配出来就会错位。我的做法是导出 STL 前统一把几何体平移,让原点落在关节连接处,这样 URDF 的 origin 写起来最直观。
提示:PyBullet 加载 URDF 时可以用
p.connect(p.GUI)可视化检查,比在 CoppeliaSim 里调快得多。先用 PyBullet 验证几何和关节轴向,再进 CoppeliaSim 做完整场景。
6. 参数化扩展与批量生成思路
text-to-cad 真正体现价值的地方不是生成单个模型,而是批量生成参数化变体。比如你要做一系列不同长度的支架,传统方式是一个个改尺寸重画,而用这套流水线,你只需要准备一个文本列表或者一个参数表,循环调用生成函数即可。
lengths = [100, 150, 200, 250] for L in lengths: params = {"type": "bracket", "length": L, "width": 40, "thickness": 5} build_and_export(params, f"bracket_{L}.step")这种批量能力在机器人仿真里特别有用——你要为不同规格的机械臂生成 URDF,或者为强化学习环境生成大量随机尺寸的物体,手工建模根本不可能,而 text-to-cad 流水线几秒钟就能跑完一批。
扩展方向上,我建议优先做两件事:一是建立几何模板库,把常见零件类型(板、轴、法兰、支架、齿轮毛坯)的模板沉淀下来,文本解析只需要匹配到模板并填参数;二是接入尺寸约束求解,让用户可以用“A 是 B 的两倍”这种相对描述,而不是只能给绝对值。这两步做完,实用性会上一个台阶。
7. 我在实际项目里踩过的几个真实坑
说几个文档里不会写、但实际做的时候一定会遇到的教训。
第一个是浮点数精度。CadQuery 内部用浮点运算,当你做大量布尔运算后,偶尔会出现极薄的面或者几乎重合的边,导致后续操作失败。我的经验是:在关键尺寸上做圆整,比如把 33.333333 圆整到 33.33,能显著降低几何失败的频率。别小看这个,我有一批模型就是因为这个原因随机失败,排查了两天才定位到。
第二个是大模型的“幻觉尺寸”。你让它生成一个“标准 M8 螺栓”,它可能给你一个直径 8 毫米、长度 50 毫米的圆柱,但真实 M8 螺栓的头部尺寸、螺纹长度都有标准。如果下游要拿这个模型做装配干涉检查,就会出问题。所以涉及标准件时,我现在的做法是查表而不是靠模型生成,把标准件库作为外部数据源接进来。
第三个是导出文件的命名和版本管理。批量生成时如果文件名冲突,后面的会覆盖前面的,而且没有任何提示。我现在的做法是文件名里带上参数哈希或者时间戳,同时在日志里记录每次生成的参数和输出路径,方便回溯。这个习惯在调试阶段救过我好几次。
最后分享一个提高解析准确率的小技巧:让用户用“字段式”描述而不是“散文式”描述。比如与其说“我想要一个大概这么长的板子”,不如引导用户填“长度:___ 宽度:___ 厚度:___”。我在界面上做了一个结构化输入框,用户填完再拼成文本送给模型,解析准确率比纯自由文本高出一大截。text-to-cad 的“text”不一定是自由文本,结构化的文本也是文本,而且更可靠。