☰
text-to-cad实战:从自然语言到STEP与URDF的参数化建模链路
2026/10/27 16:10:25 网站建设 项目流程

1. 从一句话到三维模型:text-to-cad 到底在解决什么问题

第一次听到 “text-to-cad” 这个词,很多人会下意识觉得它离自己很远,像是实验室里的概念。但如果你真正在机械设计、机器人仿真或者工业建模这条线上待过,就会明白它戳中的是一个特别痛的痛点:从自然语言描述到可用的 CAD 几何模型之间,横着一条又长又陡的鸿沟。

传统流程是什么样?客户或者产品经理丢过来一句“做一个 200 毫米见方、带四个安装孔的底板”,你得打开 CAD 软件,新建草图,画矩形,标尺寸,拉伸,再打孔阵列,最后导出 STEP。整个过程熟练工也要十几分钟,复杂一点的装配体动辄几小时。而 text-to-cad 想做的事情,就是把这句自然语言直接翻译成结构化的几何数据,最终产出 STEP、URDF 这类下游能直接消费的文件。

我最初接触这个方向,是因为要批量生成一批机器人连杆模型喂给仿真环境。手动建模几十个变体,改一个参数就要重画一遍,效率低到让人崩溃。后来开始琢磨用 Python 脚本驱动参数化建模,再往后就自然走到了“能不能让脚本理解人话”这一步。text-to-cad 本质上就是这条链路的终点形态:输入是文本,中间是参数化逻辑,输出是标准几何文件。

它适合谁?三类人最该关注。第一类是做机器人仿真的工程师,需要快速产出 URDF 和配套的网格文件;第二类是做参数化产品设计的开发者,想把设计意图用代码固化下来;第三类是想入门 CAD 自动化的 Python 玩家,手里有 Python 基础,但不知道怎么和 CAD 生态对接。这篇文章我会把整条链路拆开讲,从文本解析、参数建模、几何内核调用,一直到 STEP 和 URDF 的导出,尽量给到能直接抄作业的代码和踩坑记录。

需要先说明一点:text-to-cad 目前没有哪个开源方案能做到“随便说一句话就出完美模型”。它更像是一个半自动的翻译层,文本负责描述意图和关键尺寸,参数化模板负责保证几何合法性。指望它替代资深结构工程师,短期内不现实;但用它把重复劳动压缩掉七八成,完全可行。

2. 整体架构设计:文本、参数与几何三层解耦

2.1 为什么不能一步到位直接生成几何

很多人第一版方案会想:我直接让大模型输出一段 CadQuery 或者 OpenSCAD 代码不就行了?我试过,能跑通简单例子,但一旦涉及多特征、装配关系、尺寸约束,生成的代码就开始胡言乱语——要么变量名对不上,要么布尔运算顺序错误导致几何体自相交。

根本原因在于,几何建模对错误的容忍度极低。文本层面一个词理解偏差,到了几何层面就是模型直接废掉。所以稳妥的架构一定是分层的:文本层负责抽取意图和参数,参数层负责校验和补全,几何层负责用确定性的代码生成模型。三层之间用明确的数据结构通信,任何一层出问题都能定位。

我最终采用的架构是这样的:

  • 文本解析层:把自然语言转成结构化的参数字典,比如{"shape": "plate", "length": 200, "width": 200, "thickness": 10, "holes": [{"x": 50, "y": 50, "d": 8}]}
  • 参数校验层:检查尺寸是否为正、孔位是否在板内、壁厚是否足够,补全缺省值
  • 几何生成层:用 CadQuery 或 build123d 这类基于 OpenCASCADE 的库,把参数字典渲染成实体
  • 导出层:根据目标格式输出 STEP、STL 或 URDF

这样设计的好处是,文本解析层可以随便换模型、换规则,只要输出格式不变,下游完全不受影响。我甚至做过一个版本,文本解析用的是正则表达式加关键词匹配,照样能覆盖八成常见需求,根本不需要大模型。

2.2 几何内核选型:为什么是 OpenCASCADE 系

Python 生态里能做参数化 CAD 的库不少,我重点对比过三个:CadQuery、build123d、pythonocc。pythonocc 是 OpenCASCADE 的原生 Python 绑定,能力最全但 API 极其底层,写一个带孔底板要几十行。CadQuery 在它之上做了流畅的链式 API,build123d 则是 CadQuery 团队后来推出的更现代的实现,语法更接近 Python 原生。

选型时我主要看三点:是否支持 STEP 导出、是否支持布尔运算、社区是否活跃。CadQuery 三项全占,而且它的Workplane概念和传统 CAD 的草图-拉伸思路高度一致,迁移成本低。build123d 更优雅,但生态和文档还不如 CadQuery 成熟。所以本文的示例以 CadQuery 为主,关键处会提 build123d 的差异。

提示:CadQuery 底层依赖 OpenCASCADE,安装时如果遇到编译问题,优先用 conda 安装而不是 pip,能省掉大量依赖地狱。

2.3 URDF 在整条链路里的位置

URDF 是机器人描述格式,它本身不存几何,而是通过<geometry>标签引用 STL 或 DAE 网格文件,同时定义关节、连杆、惯性矩阵。所以 text-to-cad 生成 URDF 时,实际是两步:先用几何层生成每个连杆的网格文件,再生成描述连杆关系和关节的 XML。

这里有个容易忽略的点:URDF 对网格的原点和坐标系有严格要求。CAD 里建模时习惯把零件放在方便的位置,但 URDF 要求每个连杆的坐标系原点通常在关节处。所以导出前必须做坐标变换,把网格平移到正确位置。我踩过这个坑,模型在 CAD 里看着没问题,导入 CoppeliaSim 后所有连杆都错位,排查了半天才发现是原点没对齐。

3. 文本解析层:把“人话”变成结构化参数

3.1 参数字典的设计原则

文本解析的产出物,我定义为一份“建模意图描述”,用 Python 字典承载。设计这份字典时有个核心原则:字段名要贴近几何语义,而不是贴近某一种建模库的 API。比如用length/width/thickness而不是 CadQuery 的dx/dy/dz,这样换库时不用改数据结构。

一个典型的底板描述长这样:

{ "part_type": "plate", "length": 200.0, "width": 200.0, "thickness": 10.0, "corner_radius": 5.0, "holes": [ {"x": 50, "y": 50, "diameter": 8.0, "depth": "through"}, {"x": 150, "y": 50, "diameter": 8.0, "depth": "through"}, {"x": 50, "y": 150, "diameter": 8.0, "depth": "through"}, {"x": 150, "y": 150, "diameter": 8.0, "depth": "through"} ], "units": "mm" }

字段设计上我坚持两条:尺寸一律用浮点数,单位单独存字段;特征用列表承载,方便扩展。这样后面加圆角、加凸台、加螺纹孔,都不用动主结构。

3.2 用规则匹配还是用大模型

这是被问最多的问题。我的答案是:看你的输入可控程度。如果输入来自固定表单或者有限模板,正则加关键词匹配又快又稳,还不用联网。如果输入是自由文本、口语化严重,那大模型确实更合适。

我实际项目里用的是混合策略:先用规则匹配尝试抽取,命中率不够时再调用大模型兜底。规则部分主要抓数字和单位,比如(\d+(?:\.\d+)?)\s*(mm|毫米|cm|厘米),把“200 毫米”转成200.0加单位mm。大模型部分则负责理解“四个角各打一个孔”这种需要空间推理的描述。

注意:大模型输出的参数一定要过校验层,我遇到过模型把孔位算到板子外面、把厚度输出成负数的情况。校验层不是可选项,是必需项。

3.3 尺寸补全与默认值策略

真实场景里,用户很少把所有参数说全。比如只说“做一块 200 的方板”,厚度没说,孔也没说。这时候补全策略就很重要。我的做法是维护一份默认值表,按part_type分类:

零件类型缺省厚度缺省圆角缺省孔
plate10mm0mm无
bracket5mm2mm两个安装孔
cylinder高度=直径无无

补全后还要做一次合理性检查,比如厚度不能小于 1mm,否则打印或加工时根本做不出来。这些经验值都是从实际加工反馈里攒出来的,文档里不会写。

4. 几何生成层:用 CadQuery 把参数变成实体

4.1 环境搭建与依赖安装

先把环境弄干净。我强烈建议用 conda 建独立环境,因为 CadQuery 依赖的 OpenCASCADE 在 pip 下经常编译失败。

conda create -n text2cad python=3.10 conda activate text2cad conda install -c conda-forge cadquery

装完验证一下:

import cadquery as cq print(cq.__version__)

能打印版本号就说明几何内核正常。如果报OCP相关的导入错误,基本是 OpenCASCADE 没装好,重装 conda 包即可。Python 版本我推荐 3.10 或 3.11,3.12 在部分平台上 CadQuery 还没跟上。

4.2 从参数字典生成底板实体

核心生成函数我写成这样,输入就是上一层的参数字典:

import cadquery as cq def build_plate(params): length = params["length"] width = params["width"] thickness = params["thickness"] radius = params.get("corner_radius", 0.0) # 先画矩形草图 sketch = cq.Workplane("XY").rect(length, width) # 有圆角就先倒角 if radius > 0: sketch = sketch.vertices().fillet(radius) # 拉伸成实体 solid = sketch.extrude(thickness) # 打孔 for hole in params.get("holes", []): solid = ( solid.faces(">Z") .workplane() .center(-length/2 + hole["x"], -width/2 + hole["y"]) .hole(hole["diameter"]) ) return solid

这段代码有几个细节值得说。rect默认以原点为中心,所以孔位坐标要减去半长半宽做偏移,否则孔会跑到板子外面。faces(">Z")是选朝上的那个面作为打孔基准面,这是 CadQuery 的选择器语法,>Z表示法向朝 Z 正方向的面。hole()默认是通孔,要做盲孔得用cboreHole或手动切。

4.3 参数计算:孔位与壁厚的经验公式

孔位不是随便放的。以四孔底板为例,孔中心到板边的距离通常取1.5 到 2 倍孔径,太小会崩边,太大浪费材料。假设孔径 8mm,边距取 12 到 16mm 比较合理。如果用户只说“四角打孔”,我会按边距 = 2 倍孔径自动计算孔位:

def auto_corner_holes(length, width, diameter): margin = diameter * 2 return [ {"x": margin, "y": margin, "diameter": diameter}, {"x": length - margin, "y": margin, "diameter": diameter}, {"x": margin, "y": width - margin, "diameter": diameter}, {"x": length - margin, "y": width - margin, "diameter": diameter}, ]

壁厚方面,如果零件要承受载荷,最小壁厚建议不低于 2mm;如果是 3D 打印件,不低于 1.2mm(约三倍喷嘴直径)。这些数值我会写进校验层,超限就报警告。

4.4 多零件装配的参数化处理

单个零件好办,装配体才是难点。我的思路是:每个零件独立生成,再用位置和旋转参数组装。参数字典里加一个assembly字段,描述零件间的相对关系:

{ "parts": [ {"id": "base", "type": "plate", "length": 200, ...}, {"id": "post", "type": "cylinder", "diameter": 20, "height": 100, ...} ], "constraints": [ {"part": "post", "attach_to": "base", "position": [100, 100, 10]} ] }

生成时先建所有零件,再按约束平移旋转。CadQuery 里用translate和rotate就能实现。这里要注意坐标系一致性,我统一约定所有零件建模时以自身几何中心或底面中心为原点,装配时再变换,避免混乱。

5. 导出层:STEP、STL 与 URDF 的正确姿势

5.1 STEP 导出与常见坑

STEP 是工业界通用的交换格式,CadQuery 导出就一行:

cq.exporters.export(solid, "output.step")

但实际用起来有几个坑。第一,单位问题。CadQuery 内部按毫米处理,导出的 STEP 默认也是毫米,但有些下游软件会按米解读,导致模型缩小一千倍。稳妥做法是在导出前确认目标软件的默认单位,必要时在 STEP 头部显式声明。第二,多实体导出。如果一个文件里有多个独立实体,导出后可能变成一个复合体,下游软件识别不了。我的做法是每个零件单独导出,装配关系交给 URDF 或装配文件描述。

5.2 STL 导出与网格精度控制

STL 是网格格式,用于仿真和 3D 打印。导出时要控制网格精度,太粗模型失真,太细文件爆炸:

cq.exporters.export( solid, "output.stl", tolerance=0.01, angularTolerance=0.1 )

tolerance是线性偏差,单位毫米,0.01 意味着曲面离散误差不超过 0.01mm。angularTolerance是角度偏差,单位弧度。对于机器人仿真用的连杆,我一般用 0.05 和 0.2,文件大小和精度比较平衡。如果模型要用于碰撞检测,精度可以再降,因为碰撞检测本身就有容差。

5.3 URDF 生成:连杆、关节与惯性矩阵

URDF 是 XML 格式,我一般用 Python 的urdf_parser_py或者直接手写模板。核心结构是<link>和<joint>:

<robot name="text2cad_robot"> <link name="base_link"> <visual> <geometry> <mesh filename="base.stl" scale="1 1 1"/> </geometry> </visual> <collision> <geometry> <mesh filename="base.stl"/> </geometry> </collision> <inertial> <mass value="1.5"/> <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.02"/> </inertial> </link> <joint name="joint1" type="revolute"> <parent link="base_link"/> <child link="arm_link"/> <origin xyz="0 0 0.1" rpy="0 0 0"/> <axis xyz="0 0 1"/> <limit lower="-1.57" upper="1.57" effort="10" velocity="1"/> </joint> </robot>

惯性矩阵是新手最容易糊弄的地方。如果随便填,仿真时物体会飘或者抖动。对于规则几何体,可以用解析公式算。比如长方体绕中心轴的转动惯量I = m(a²+b²)/12。我在生成 URDF 时会根据零件类型自动算惯性,比手填靠谱得多。

提示:URDF 里的网格路径是相对路径,导入 CoppeliaSim 时如果找不到文件,先检查路径和大小写。Linux 下大小写敏感,Windows 下不敏感,跨平台时特别容易翻车。

5.4 导入 CoppeliaSim 的实操记录

CoppeliaSim 导入 URDF 的流程是:菜单Plugins -> URDF import -> 选择 urdf 文件。导入后如果模型散架,八成是关节的origin没设对。我的排查顺序是:先看每个 link 的网格是否在正确位置,再看 joint 的父子关系,最后看坐标系方向。CoppeliaSim 默认 Z 轴朝上,而有些 CAD 习惯 Y 轴朝上,导入后可能需要整体旋转。

实测下来,只要 URDF 里的 origin 和网格原点对齐,导入基本一次成功。我现在的做法是生成网格时就把原点设在关节位置,URDF 里 origin 全填零,省去变换的麻烦。

6. 常见问题与排查技巧实录

6.1 几何生成失败速查表

现象可能原因排查方法
布尔运算报错实体自相交或退化检查孔位是否重叠、圆角是否过大
导出 STEP 为空实体未闭合用solid.val().isValid()检查
模型尺寸不对单位混淆确认输入单位与库内部单位一致
打孔位置偏移坐标系原点理解错打印草图边界坐标核对
圆角失败圆角半径大于边长一半限制 radius < min(length,width)/2

这张表是我从几十次失败里攒出来的,基本覆盖九成问题。遇到新问题,我的第一反应是把中间实体导出成 STL 看一眼,很多时候图形化一看就知道哪里错了。

6.2 文本解析的边界情况处理

文本解析最怕歧义。比如“打四个孔”,是四角还是均布?“200 的板”,是边长还是对角线?我的处理原则是:能推断就推断,不能推断就报错让用户确认。宁可多问一句,也不要生成一个错误模型让用户白等。

具体做法是在参数字典里加一个confidence字段,解析置信度低于阈值时,把不确定的字段列出来让用户确认。这个交互设计比闷头生成强太多。

6.3 性能优化:批量生成时的缓存策略

如果要生成几百个变体,每次都从头建几何会很慢。我的优化是:把不变的几何部分缓存成中间实体,只重建变化的部分。比如一批底板只有孔位不同,那就把带圆角的板体缓存起来,每次只重新打孔。实测能把生成时间从每个 2 秒降到 0.3 秒。

另一个技巧是并行生成。CadQuery 的几何运算释放了 GIL,用multiprocessing能真正并行。我一般按 CPU 核心数开进程池,几百个模型几分钟就跑完。

6.4 踩过的坑与独家经验

说几个文档里绝对找不到的坑。第一,CadQuery 的hole()在非平面上的行为很怪,如果基准面是曲面,孔的方向可能不对,一定要先workplane()建立局部坐标系。第二,STEP 导出时如果实体有微小缝隙,下游软件可能读成空,导出前用solid.val().fix()修一下。第三,URDF 的惯性矩阵如果全填零,CoppeliaSim 会直接忽略物理仿真,模型变成纯视觉,这个坑我排查了一整天才发现。

还有一个经验:别追求一步到位生成复杂装配体。先把单零件生成跑通,再加装配,再加 URDF,每步都验证。我见过太多人一上来就想搞全自动,结果卡在某个环节动弹不得。分步走,每步都有可交付物,才是可持续的做法。

7. 后续扩展方向与个人体会

这套 text-to-cad 的链路跑通之后,能扩展的地方很多。比如接一个 Web 界面,用户输入文本,后台生成模型并提供 STEP 下载;比如接入参数优化循环,给定目标函数自动搜索最优尺寸;再比如把生成的 URDF 直接喂给强化学习环境,做机器人控制训练。

我个人在实际操作中的体会是,text-to-cad 的价值不在于完全替代人工,而在于把“想法到可验证模型”的周期从小时级压缩到分钟级。设计师可以快速试错,工程师可以批量产出仿真资产,这才是它真正落地的地方。至于那些追求“一句话生成复杂装配体”的宣传,听听就好,真做项目还得靠扎实的分层架构和严格的校验。

最后分享一个小技巧:如果你手头有大量历史 CAD 模型,可以反向提取它们的参数,建成模板库。这样文本解析时优先匹配模板,命中率会高很多,生成质量也稳定。模板加参数,比纯生成靠谱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询