CAD 这行当有个特别拧巴的地方:脑子里想清楚一个零件只要几秒钟,把它变成能加工、能仿真、能出图的文件,却要花上几十分钟甚至几个小时。画一个法兰、一根支架、一套齿轮箱,重复劳动占了大头。text-to-cad 这个方向,说白了就是想把“脑子里那几秒”直接翻译成机器能读的几何文件,跳过中间那段枯燥的手工建模。我最近花了不少时间在这条链路上折腾,从自然语言解析到几何生成,再到 STEP、DXF、URDF 这几种格式的落地,踩的坑比想象中多得多。这篇文章不讲空泛的概念,只讲我实际跑通的流程、参数怎么定、哪些地方容易翻车,以及不同格式到底该在什么场景下用。不管你是刚接触 CAD 制图的新手,还是已经在用 Python 批量改图纸的老手,应该都能从里面找到能直接抄作业的东西。
1. 整体设计思路与格式选型逻辑
1.1 为什么不是“一句话直接出图”那么简单
很多人对 text-to-cad 的第一印象是:我打一句“画一个直径 80、厚 10 的法兰”,软件就吐出一个三维模型。这个愿景没问题,但真正落地时会发现,自然语言和几何内核之间隔着好几层翻译。第一层是语义理解,得把“直径 80”识别成外径参数,把“厚 10”识别成拉伸高度;第二层是几何构造,得决定用旋转体、拉伸体还是布尔运算来生成;第三层是格式序列化,得把内部几何数据写成 STEP、DXF 或 URDF 能读的结构。
我一开始也想一步到位,直接让语言模型输出几何代码,结果发现模型对坐标系、单位、拓扑关系的理解极不稳定。同一个描述跑两次,一次给你一个实体,一次给你两个分离的面。后来我改成“分阶段流水线”:先解析成结构化的参数 JSON,再由确定性的几何代码去消费这个 JSON。这样即使语言层有波动,几何层也是可复现的。这个思路的代价是多写一层解析逻辑,但换来的是稳定性,非常值。
提示:不要指望语言模型直接输出可用的几何文件。让它输出参数和意图,几何生成交给确定性代码,这是目前最稳的工程做法。
1.2 STEP、DXF、URDF 到底各管什么
这三个格式经常被混在一起提,但它们的定位完全不同,选错了后面全是麻烦。
| 格式 | 本质 | 典型用途 | 是否含装配关系 | 是否含运动学 |
|---|---|---|---|---|
| STEP | 三维实体边界表示 | 加工、3D 打印、跨软件交换 | 部分支持 | 否 |
| DXF | 二维矢量图纸 | 激光切割、钣金、平面加工 | 否 | 否 |
| URDF | 机器人描述 XML | 仿真、运动学、关节定义 | 是 | 是 |
STEP 是三维实体的通用交换格式,ISO 10303 标准,几乎所有 CAD 软件都能读写。它的核心是 B-rep(边界表示),记录的是面、边、顶点的拓扑关系,所以精度高、体积也大。DXF 是二维的,本质是图层上的线段、圆弧、多段线集合,激光切割和钣金下料基本都用它。URDF 则是机器人领域的描述文件,它不关心你长什么样,只关心连杆怎么连、关节怎么转、坐标系怎么摆。
我踩过的一个坑是:拿 STEP 去喂仿真软件做运动学,结果发现它根本没有关节信息,只能当静态模型看。后来才明白,要做仿真必须走 URDF,而且 URDF 里的几何通常用简单的碰撞体近似,不是精细的加工模型。所以选型的第一原则是:先问这个文件拿去干什么,再决定格式。
1.3 流水线的整体架构
我最终跑通的架构分四段:输入解析、参数校验、几何生成、格式导出。输入解析负责把自然语言变成结构化参数;参数校验负责检查数值范围、单位一致性、必填项;几何生成调用几何内核构造实体;格式导出把实体写成目标格式。
这个架构的关键在于“参数校验”这一层,很多人会跳过它。我一开始也跳过了,结果出现过“直径 -5”这种输入直接把几何内核搞崩的情况。加了校验之后,所有非法输入在进入几何层之前就被拦下来,报错信息也清晰得多。校验层还负责单位换算,比如用户说“80 毫米”,内部统一用毫米,但如果用户说“3 英寸”,就得在这里转成 76.2 毫米。
2. 核心细节解析与实操要点
2.1 自然语言到参数的映射规则
把一句话拆成参数,核心是识别“实体类型 + 尺寸 + 位置 + 约束”这四类信息。我用的做法是定义一套参数模板,每种实体类型对应一个模板,然后用规则加模型混合的方式去填充。
以法兰为例,模板大概是这样的:
{ "type": "flange", "outer_diameter": 80, "inner_diameter": 30, "thickness": 10, "bolt_holes": { "count": 6, "diameter": 8, "circle_diameter": 60 }, "unit": "mm" }解析的时候,先匹配实体类型关键词(法兰、支架、齿轮、轴套等),再从句子里抽取数值和单位。数值抽取看起来简单,其实坑很多。“直径 80”和“80 直径”都得认,“M8 的孔”要理解成直径 8 的孔,“6 个均布”要理解成 6 个孔均匀分布在分度圆上。我一开始用纯正则,遇到“六个”这种中文数字就歇菜了,后来加了一层中文数字归一化才解决。
注意:单位是重灾区。用户可能说“80”、“80mm”、“8 厘米”、“0.08 米”,内部必须统一。我的做法是默认单位设为毫米,遇到其他单位立即换算,并且在返回结果里显式标注单位,避免下游误读。
2.2 几何生成的关键参数计算
几何生成这一步,很多参数不是用户直接给的,而是要根据约束算出来。比如“6 个孔均布在分度圆上”,分度圆直径用户可能给了,也可能只给了外径和边距,需要反推。再比如齿轮,用户说“模数 2、齿数 20”,分度圆直径就是模数乘齿数等于 40,齿顶圆和齿根圆还要按标准公式算。
我拿法兰的螺栓孔举例。假设外径 80,用户要求孔中心到外缘留 8 毫米边距,那么分度圆直径就是 80 减去两倍边距,等于 64。如果用户直接给了分度圆直径 60,那就以用户给的为准。孔的位置角度按 360 除以孔数均分,第一个孔通常放在 0 度方向,也就是正右方。这些计算看着简单,但如果不写清楚,不同人实现出来的孔位可能差一个角度,装配时就对不上。
import math def bolt_hole_positions(count, circle_diameter, start_angle_deg=0): positions = [] radius = circle_diameter / 2.0 for i in range(count): angle = math.radians(start_angle_deg + i * 360.0 / count) x = radius * math.cos(angle) y = radius * math.sin(angle) positions.append((round(x, 4), round(y, 4))) return positions这段代码我用了很久,唯一要注意的是浮点数精度。round 到四位小数是为了避免 1.2246e-16 这种科学计数法尾巴,导出 DXF 的时候如果坐标带这种尾巴,有些老软件会读出错。
2.3 格式导出的技术要点
STEP 导出依赖几何内核,我用的是开源的 OpenCASCADE 系(通过 Python 绑定调用)。它的优势是 B-rep 精度高,导出的 STEP 能被主流 CAD 软件读取。要注意的是,导出前必须确保实体是“闭合”的,如果有未缝合的面,STEP 文件会变成一个空壳或者报错。我遇到过拉伸体因为草图没闭合导致导出失败,排查了半天才发现是草图里有个微小的缺口。
DXF 导出相对简单,本质是把二维轮廓写成组码。但 DXF 的版本很多,R12、R14、2000、2007 各有差异。激光切割机通常吃 R12 或 R14,太新的版本反而不认。我一般导出 R12,兼容性最好。另外 DXF 里的图层要规划好,轮廓线、中心线、标注分不同图层,方便下游筛选。
URDF 导出是另一套逻辑。它需要你先定义连杆和关节的树状结构,每个连杆挂一个几何体(通常是 STL 或简单几何),每个关节定义类型(旋转、平移、固定)、轴向、限位。URDF 本身是 XML,手写也行,但连杆多了容易乱。我的做法是用脚本生成,把连杆和关节定义成数据结构,再序列化成 XML。
<robot name="simple_arm"> <link name="base_link"> <visual> <geometry> <cylinder radius="0.05" length="0.1"/> </geometry> </visual> </link> <joint name="joint1" type="revolute"> <parent link="base_link"/> <child link="arm_link"/> <axis xyz="0 0 1"/> <limit lower="-1.57" upper="1.57" effort="10" velocity="1"/> </joint> </robot>这段 URDF 里,单位是米不是毫米,这是 URDF 的惯例,很多人第一次写会栽在这里。导入仿真环境时如果发现模型大了 1000 倍,八成就是单位没换。
3. 实操过程与核心环节实现
3.1 环境搭建与依赖选择
我用的主力语言是 Python,原因是几何库和解析库生态最全。核心依赖有三个:几何内核绑定(做实体构造和 STEP 导出)、ezdxf(做 DXF 读写)、以及一个 XML 库(做 URDF 生成,标准库的 xml.etree 就够)。
安装几何内核绑定的时候要注意版本匹配。不同版本的绑定 API 有差异,尤其是布尔运算和倒角相关的接口。我建议锁定一个稳定版本,不要追最新。另外 Windows 上编译几何内核有时候会遇到 C++ 运行库的问题,如果安装时报运行库错误,先装对应的 Visual C++ Redistributable,这是最常见的原因。
pip install ezdxf pip install cadquerycadquery 是我比较推荐的几何层方案,它把 OpenCASCADE 封装得比较友好,写起来接近“描述式建模”。比如画一个带孔的法兰,几行代码就能搞定,比直接调底层 API 舒服得多。
3.2 从一句话到 STEP 文件的完整流程
我拿“画一个外径 80、内径 30、厚 10 的法兰,6 个直径 8 的孔均布在直径 60 的分度圆上”这句话走一遍完整流程。
第一步,解析。识别出实体类型是法兰,外径 80,内径 30,厚度 10,孔数 6,孔径 8,分度圆直径 60,单位默认毫米。这一步输出结构化 JSON。
第二步,校验。检查外径大于内径,厚度为正,孔径小于分度圆半径减内径半径,孔数在合理范围(比如 2 到 24)。如果外径 80、内径 30,那么壁厚是 25,分度圆直径 60 意味着孔中心在半径 30 处,孔半径 4,孔外缘到外径的距离是 40 减 30 减 4 等于 6 毫米,够用。校验通过。
第三步,几何生成。先画外圆拉伸成圆柱,再画内圆挖空,最后在分度圆上打 6 个孔。用 cadquery 写大概是这样:
import cadquery as cq import math outer_d = 80 inner_d = 30 thickness = 10 hole_count = 6 hole_d = 8 circle_d = 60 result = ( cq.Workplane("XY") .circle(outer_d / 2) .circle(inner_d / 2) .extrude(thickness) ) for i in range(hole_count): angle = math.radians(i * 360.0 / hole_count) x = (circle_d / 2) * math.cos(angle) y = (circle_d / 2) * math.sin(angle) result = ( result.faces(">Z").workplane() .center(x, y) .hole(hole_d) ) cq.exporters.export(result, "flange.step")这里有个细节:.faces(">Z")是选中顶面作为打孔的工作平面,如果不选,孔可能打错方向。另外.hole()默认是通孔,如果要盲孔得指定深度。
第四步,导出。STEP 文件生成后,我会用另一个 CAD 软件打开验证一遍,确认实体是闭合的、孔位正确、尺寸无误。这一步不能省,自动化生成的模型一定要人工抽检。
3.3 DXF 二维图纸的生成与图层规划
很多加工场景其实不需要三维模型,只要二维轮廓。比如激光切割一块钣金,你给它 STEP 它还得自己投影,不如直接给 DXF。从同一个参数 JSON 生成 DXF,思路是把三维实体的投影轮廓提取出来,或者干脆用二维绘图 API 直接画。
我倾向于直接画二维,因为可控性更强。用 ezdxf 画法兰的俯视图:
import ezdxf import math doc = ezdxf.new("R12") msp = doc.modelspace() doc.layers.add("OUTLINE", color=7) doc.layers.add("HOLES", color=1) doc.layers.add("CENTER", color=3) msp.add_circle((0, 0), 40, dxfattribs={"layer": "OUTLINE"}) msp.add_circle((0, 0), 15, dxfattribs={"layer": "OUTLINE"}) for i in range(6): angle = math.radians(i * 60) x = 30 * math.cos(angle) y = 30 * math.sin(angle) msp.add_circle((x, y), 4, dxfattribs={"layer": "HOLES"}) msp.add_line((-45, 0), (45, 0), dxfattribs={"layer": "CENTER"}) msp.add_line((0, -45), (0, 45), dxfattribs={"layer": "CENTER"}) doc.saveas("flange.dxf")图层规划是 DXF 的灵魂。轮廓一个层、孔一个层、中心线一个层,下游的切割软件可以按层设置不同的加工参数,比如轮廓走外切、孔走内切、中心线不加工只做参考。如果不分层,所有线混在一起,操作员得手动挑,效率极低还容易出错。
提示:DXF 的 R12 版本不支持某些新特性,但兼容性最好。如果你的下游设备比较老,优先出 R12。如果确定设备支持新版本,可以用 R2000 以上,图层和线型支持更丰富。
3.4 URDF 生成与仿真导入
URDF 的生成逻辑和前面两个完全不同,它关注的是“结构”而不是“形状”。我拿一个简单的两连杆机械臂举例,用户描述“一个底座上面接一根 200 毫米的臂,臂末端接一个 150 毫米的前臂,两个关节都能转”。
第一步是确定连杆树:base_link 接 arm_link,arm_link 接 forearm_link。第二步是确定每个连杆的几何,底座用圆柱,臂用长方体或圆柱。第三步是确定关节,两个都是旋转关节,轴向通常是 Z 轴或 Y 轴,限位按实际行程给。
from xml.etree import ElementTree as ET robot = ET.Element("robot", name="two_link_arm") base = ET.SubElement(robot, "link", name="base_link") visual = ET.SubElement(base, "visual") geom = ET.SubElement(visual, "geometry") ET.SubElement(geom, "cylinder", radius="0.05", length="0.1") arm = ET.SubElement(robot, "link", name="arm_link") visual = ET.SubElement(arm, "visual") geom = ET.SubElement(visual, "geometry") ET.SubElement(geom, "box", size="0.04 0.04 0.2") joint1 = ET.SubElement(robot, "joint", name="joint1", type="revolute") ET.SubElement(joint1, "parent", link="base_link") ET.SubElement(joint1, "child", link="arm_link") ET.SubElement(joint1, "axis", xyz="0 0 1") ET.SubElement(joint1, "limit", lower="-1.57", upper="1.57", effort="10", velocity="1") tree = ET.ElementTree(robot) ET.indent(tree, space=" ") tree.write("arm.urdf", encoding="utf-8", xml_declaration=True)生成之后导入仿真环境验证,重点看三件事:模型尺寸对不对(单位问题)、关节能不能动(轴向和类型问题)、连杆之间的相对位置对不对(origin 问题)。origin 是最容易错的,它定义了子连杆相对父连杆的位姿,包括平移和旋转。如果 origin 没写对,模型会散架或者重叠。
4. 常见问题与排查技巧实录
4.1 几何生成阶段的典型故障
几何生成阶段的问题往往最隐蔽,因为报错信息经常很模糊。我整理了几个高频故障和排查思路。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| STEP 导出后打开是空文件 | 实体未闭合或布尔运算失败 | 检查草图是否闭合,布尔运算前确认实体有效 |
| 孔位偏移 | 坐标系或角度起始点不一致 | 打印孔位坐标,和预期对比 |
| 模型尺寸差 1000 倍 | 单位混用(米/毫米) | 检查输入解析和导出环节的单位设置 |
| 倒角失败 | 倒角半径大于相邻边长度 | 减小倒角半径或调整顺序 |
| 布尔运算结果异常 | 实体自相交或法线方向错误 | 用几何内核的检查工具验证实体有效性 |
我印象最深的一次是布尔运算失败,排查了两个小时,最后发现是两个实体刚好共面,导致内核无法判断内外。解决办法是把其中一个实体偏移 0.001 毫米,避开共面。这种问题在文档里基本找不到,只能靠经验积累。
4.2 DXF 导出的兼容性问题
DXF 的兼容性问题主要集中在版本和编码上。老设备读 R12 最稳,但 R12 不支持 Unicode 图层名,如果图层名用了中文,导出后可能乱码。我的做法是图层名一律用英文大写,比如 OUTLINE、HOLES、CENTER,避免编码问题。
另一个坑是坐标精度。有些设备对坐标的小数位数敏感,太多位会读错,太少位精度不够。我一般保留三位小数,兼顾精度和兼容性。如果发现切割出来的零件尺寸有偏差,先检查 DXF 里的坐标精度。
还有一点,DXF 里的多段线(LWPOLYLINE)和普通线段(LINE)在有些软件里处理方式不同。多段线是连续的,线段是离散的。如果轮廓是闭合的,用多段线更好,下游软件能识别成一条闭合路径。如果是一堆离散线段,软件可能认为是开放的,导致切割路径不闭合。
4.3 URDF 导入仿真的常见报错
URDF 导入仿真环境报错,八成是这几个原因:XML 格式错误、连杆或关节名称重复、parent 和 child 引用不存在的连杆、单位不对、mesh 文件路径错误。
XML 格式错误最常见的是标签没闭合或者属性引号缺失。我建议生成后用 XML 校验工具过一遍,别直接扔进仿真环境。名称重复的问题在连杆多了之后容易出现,尤其是复制粘贴改的时候忘了改名。parent 和 child 引用错误通常是改名后没同步更新。
mesh 文件路径是另一个大坑。URDF 里引用 STL 或 DAE 文件时,路径可以是相对路径也可以是绝对路径。相对路径是相对于 URDF 文件所在目录,但不同仿真环境对相对路径的解析方式可能不同。我一般用 package:// 开头的路径配合功能包结构,或者干脆用绝对路径,省得折腾。
注意:URDF 里的惯性参数(质量、转动惯量)如果乱填,仿真时会出现奇怪的抖动或者直接飞出去。如果只是做运动学验证,可以把质量设小一点,转动惯量设成合理值。如果要做动力学仿真,惯性参数必须准确,最好从 CAD 软件里导出。
4.4 参数校验的边界条件清单
参数校验这层,我总结了一份边界条件清单,每次新增实体类型都对照检查:
- 尺寸必须为正数,直径、半径、长度、厚度都不能小于等于零
- 内径必须小于外径,否则壁厚为负
- 孔的分度圆直径必须大于内径且小于外径,孔不能超出实体范围
- 孔数必须是正整数,且均布角度要能整除 360
- 倒角半径必须小于相邻边的最小长度
- 拉伸高度必须为正,且不能大到超出合理范围
- 单位必须明确,默认毫米,其他单位立即换算
这份清单帮我拦下了大量低级错误。尤其是孔位超出实体范围这种,如果不校验,几何内核可能生成一个破面实体,导出后才发现问题,排查成本很高。
5. 批量处理与自动化扩展
5.1 用 Python 批量修改 CAD 文件的思路
单个文件生成跑通之后,下一步自然是批量。实际工作中经常遇到“把这一百个 DXF 的图层名统一改掉”或者“给这批 STEP 文件统一加个倒角”这种需求。手动改不现实,必须脚本化。
批量处理 DXF 用 ezdxf 很顺手。遍历目录下的所有 DXF,打开、修改、保存。比如统一图层名:
import ezdxf import os src_dir = "./dxf_files" for filename in os.listdir(src_dir): if not filename.endswith(".dxf"): continue path = os.path.join(src_dir, filename) doc = ezdxf.readfile(path) for layer in doc.layers: if layer.dxf.name == "0": layer.dxf.name = "DEFAULT" doc.saveas(path)批量处理 STEP 要复杂一些,因为 STEP 是实体模型,修改需要几何内核支持。简单的操作比如平移、旋转、缩放可以批量做,复杂的比如改孔位,得重新生成。我的经验是:如果修改涉及几何拓扑变化,重新生成比修改现有实体更可靠。
5.2 参数化模板库的建立
批量生成的效率瓶颈往往不在代码,而在参数整理。我后来建了一个参数模板库,把常用零件(法兰、支架、轴套、齿轮、钣金件)的参数结构固化下来,每次生成只需要填参数,不用重新定义结构。
模板库的好处是复用和一致性。同一个法兰模板,这次生成外径 80 的,下次生成外径 120 的,结构完全一样,只是数值不同。这样下游的加工程序、检验标准都能复用,不用每个新零件都重新走一遍流程。
模板库的维护要注意版本管理。参数结构一旦定了,尽量不要改,因为改了之后所有依赖它的脚本都得跟着改。如果确实要改,做好向后兼容,比如新增字段给默认值,不要直接删字段。
5.3 与下游流程的衔接
生成的 CAD 文件最终要进入下游流程,可能是加工、仿真、出图或者入库。衔接做得好不好,直接决定这套自动化有没有实际价值。
对接加工的话,重点是文件格式和图层规范。激光切割要 DXF,CNC 要 STEP,3D 打印要 STL。图层和命名要符合加工厂的惯例,不然对方还得手动处理。我一般会随文件附一份说明,写清楚单位、图层含义、材料厚度假设。
对接仿真的话,重点是 URDF 的完整性和惯性参数。如果仿真环境需要 mesh 文件,记得把 STL 一起打包,路径用相对路径并保持目录结构。
对接出图的话,重点是视图和标注。自动生成的二维图往往缺少标注,需要额外补。这块我目前还是半自动,自动生成轮廓,人工补标注,全自动的标注逻辑太复杂,投入产出比不高。
6. 实操心得与避坑经验
6.1 我踩过的三个印象最深的坑
第一个坑是单位。早期我没做单位归一化,用户说“80”,我默认毫米,结果有人输入“3 英寸”,生成出来的零件小了 25 倍。后来强制所有输入都带单位解析,没带单位的按默认毫米处理,并且在输出里显式标注。
第二个坑是浮点精度。DXF 导出时坐标带科学计数法尾巴,导致某些老软件读取失败。解决办法是导出前统一 round 到合理位数,一般三到四位小数够用。
第三个坑是 URDF 的 origin。我一开始没写 origin,以为默认就行,结果导入仿真后所有连杆都堆在原点。origin 必须显式定义,平移和旋转都要写清楚,否则仿真环境不知道子连杆该放在哪。
6.2 提升生成质量的两个实用技巧
第一个技巧是加一层“几何有效性检查”。生成实体后,用几何内核的检查工具验证实体是否闭合、是否有自相交、法线方向是否正确。这一步能拦下大部分会导致下游出问题的模型。检查不通过的直接报错,不要导出。
第二个技巧是保留中间参数 JSON。每次生成都存一份参数 JSON,文件名和输出文件对应。这样出了问题可以追溯,也能基于同一份参数重新生成不同格式的文件。我现在的流程是:参数 JSON 是源头,STEP、DXF、URDF 都是它的衍生物。
6.3 关于工具选型的个人建议
几何内核这块,cadquery 适合快速上手,API 友好,文档也还行。如果要做更底层的控制,可以直接用 OpenCASCADE 的 Python 绑定,但学习曲线陡。DXF 处理 ezdxf 基本是唯一选择,成熟稳定。URDF 生成用标准库的 XML 就够了,没必要上额外的库。
语言模型这块,如果要用,建议只用来做自然语言到参数的解析,不要让它碰几何。解析结果一定要经过校验层,不能直接信任。模型对数值的提取有时候会出错,尤其是复杂句子里的多个数值,容易张冠李戴。
最后说一句,text-to-cad 这个方向目前还远没到“一句话出成品”的程度,但“一句话出参数化草模”已经能跑通了。把重复性的建模工作自动化掉,把精力留给真正需要判断力的设计决策,这才是现阶段最实际的用法。我现在的做法是:标准件和常用结构走自动生成,复杂曲面和装配关系还是手工做,两者结合,效率提升很明显。