文字生成CAD技术解析:从自然语言到STEP与URDF的工程实践
1. 从一段文字到一张能加工的图纸,这条路到底通不通
"text-to-cad"这个词,第一次看到的人多半会愣一下:文字直接变成CAD模型?听起来像是把自然语言当成建模指令,说一句"画一个长宽高100×60×30的方块,四角倒R5圆角",然后软件自己把STEP文件吐出来。这个设想在几年前还属于实验室里的玩具,但现在已经有不少开源项目和商业工具在往这个方向走,而且效果比大多数人想象的要实用得多。
我接触这个方向,起因其实很朴素:手头有一批参数化程度很高的零件,每次改尺寸都要在CAD里点半天,重复劳动让人烦躁。后来想,既然这些零件的几何逻辑是固定的,只是数值在变,那能不能用一段结构化的文字描述直接驱动建模,把"改尺寸"这件事从手工操作变成文本编辑?顺着这个思路摸下去,就摸到了text-to-cad这条线上的一堆工具和方法。
这篇文章想聊的,不是某个具体产品的使用手册,而是把"文字生成CAD"这件事拆开来看:它背后的技术链路是什么,STEP和URDF这两种输出格式分别解决什么问题,Python在其中扮演什么角色,以及一个从业者真正落地时会遇到哪些坑。适合谁看?如果你做过CAD建模、写过Python脚本、或者正在做参数化设计、机器人仿真这类工作,这篇内容应该能帮你少走一些弯路。如果你只是好奇"文字变模型"到底靠不靠谱,那也可以把它当成一份技术地图,先看清楚全貌再决定要不要深入。
需要先说明一点:text-to-cad不是一个单一软件的名字,而是一类技术方案的统称。它的实现路径差异很大,有的走大语言模型解析意图再调用建模内核,有的走领域特定语言(DSL)做中间层,有的干脆用代码即模型(code-CAD)的思路,让Python直接描述几何。理解这些路径的区别,比记住某个工具的名字重要得多。
2. 文字到几何之间,到底隔着哪几层
2.1 自然语言不是建模语言,中间必须有一次翻译
很多人对text-to-cad的第一个误解,是以为大语言模型能直接"理解"几何。实际上,语言模型擅长的是把一句话拆解成结构化的意图,比如从"一个底面直径50、高80的圆柱,顶部中心挖一个直径20、深30的孔"里提取出:基体是圆柱,参数是直径50、高80;特征是孔,参数是直径20、深30,位置在顶部中心。这些提取出来的东西,是一组结构化的参数和操作序列,而不是几何本身。
真正把参数变成几何的,是CAD内核,比如OpenCASCADE、Parasolid这类几何引擎。语言模型负责的是"翻译意图",内核负责的是"执行几何运算",两者之间需要一个中间表示层。这个中间层可以是JSON格式的特征树,可以是一段Python代码,也可以是一种专门的建模脚本语言。中间层设计得好不好,直接决定了整个方案的稳定性和可扩展性。
我见过一些demo,直接把自然语言丢给模型,让模型输出一段建模代码,跑出来确实能看。但一旦参数变复杂、特征变多,代码就开始出错,因为模型对几何约束的理解并不牢靠。更稳的做法,是让模型只负责填参数,几何逻辑由预先写好的模板或函数库来保证。这样即使模型偶尔抽风,几何的合法性也不会崩。
2.2 为什么STEP是绕不开的交付格式
做CAD的人对STEP不会陌生。它是ISO 10303标准下的产品数据交换格式,几乎所有的CAD软件都能读写。text-to-cad方案如果最终要落到实际生产或协作环节,STEP基本是默认的输出目标。原因很直接:STL只有三角面片,丢了拓扑和曲面信息,没法做精确加工;IGES虽然也能交换曲面,但对实体和装配的支持不如STEP完整;而STEP既能表达实体、曲面、装配结构,又能携带颜色、图层、材料等属性。
从技术实现角度看,用Python生成STEP,主流路径是走OpenCASCADE的Python绑定,也就是pythonocc-core,或者用CadQuery这类基于OCCT封装的高层库。CadQuery的好处是把建模操作抽象成了链式调用,写起来接近自然语言,比如定义一个长方体再倒角,几行代码就能搞定,而且直接支持导出STEP。这也是为什么在text-to-cad的讨论里,CadQuery出现的频率特别高。
提示:STEP有AP203、AP214、AP242等多个应用协议,导出时如果涉及颜色和装配,建议选AP242,兼容性和信息完整度都更好。很多导出丢颜色的问题,根源就在协议选错了。
2.3 URDF的出现,说明text-to-cad不只服务于机械加工
热搜词里出现了URDF和CoppeliaSim,这其实透露了一个重要信息:text-to-cad的应用场景不局限于传统的机械零件出图,还包括机器人仿真。URDF是统一机器人描述格式,用XML描述机器人的连杆、关节、运动学关系。它描述的不是"一个零件长什么样",而是"一堆零件怎么连起来、怎么动"。
这就带来一个有意思的分叉:同样是文字生成模型,面向加工的场景关心的是精确的实体几何,输出STEP;面向仿真的场景关心的是运动学结构和碰撞体,输出URDF。两者的几何精度要求、坐标系约定、命名规范都不一样。一个text-to-cad工具如果声称两种都能做,那它内部一定做了格式转换和语义映射,这部分往往是坑最多的地方。
举个例子,STEP里的一个装配体,导出成URDF时,需要把每个零件的位姿转换成相对于父连杆的变换矩阵,还要定义关节类型(旋转、平移、固定)和轴向。如果原始模型里零件之间的约束关系没有清晰表达,转换出来的URDF关节就会乱套,导入仿真环境后机器人直接散架。所以做这类转换,源模型的结构清晰度比转换工具本身更重要。
2.4 Python在这条链路里的真实位置
Python在text-to-cad里不是可选项,而是事实上的粘合剂。语言模型调用、参数处理、几何生成、格式导出、批量处理,几乎每一环都能用Python串起来。热搜里那一堆python安装、numpy、cv2、环境变量配置的词,说明大量初学者卡在了环境这一步,而不是卡在算法上。
从工程角度看,Python的价值在于生态完整。你要调大语言模型API,有现成的SDK;要做几何运算,有pythonocc和CadQuery;要处理矩阵和坐标变换,有numpy;要批量读写文件,标准库就够了。把这些拼起来,一个最小可用的text-to-cad流水线可能只需要几百行代码。难的不是写代码,而是把每一环的边界条件处理好。
3. 搭一条能跑通的文字生成CAD流水线
3.1 环境准备:别一上来就装一堆库
新手最容易犯的错,是看到教程里列了一堆库就全部pip install,结果版本冲突、编译失败、环境变量乱掉,最后连Python本身都跑不起来。我的建议是分步来,先把最小环境跑通,再逐步加依赖。
第一步,装一个干净的Python。版本上,3.8到3.11之间都比较稳,太新的版本有些几何库的预编译包还没跟上。Windows用户直接从官网下安装包,安装时勾选"Add Python to PATH",这一步能省掉后面手动配环境变量的麻烦。Linux用户用系统包管理器或者pyenv都行,注意别把系统自带的Python搞坏。
第二步,建虚拟环境。这是硬性建议,不是可选项。用python -m venv cadenv建一个独立环境,激活之后再装库。这样即使某个库版本冲突,也不会污染全局环境,删掉重来成本很低。
python -m venv cadenv # Windows cadenv\Scripts\activate # Linux / macOS source cadenv/bin/activate第三步,装核心几何库。CadQuery的安装相对友好,官方提供了conda和pip两种方式。如果走pip,遇到OCCT相关依赖编译失败,换成conda通常能解决,因为conda有预编译的二进制包。
pip install cadquery # 或者 conda install -c conda-forge cadquery第四步,验证。写一个最简单的脚本,生成一个方块并导出STEP,能跑通说明环境没问题。
import cadquery as cq result = cq.Workplane("XY").box(100, 60, 30) cq.exporters.export(result, "test_block.step") print("导出完成")注意:如果这一步报错说找不到OCCT的dll,八成是环境变量或者依赖包不完整。Windows下可以试试重装cadquery,或者改用conda环境。别急着去网上找各种dll手动塞,那样只会让问题更难排查。
3.2 把一句话拆成参数:意图解析的实操思路
环境跑通之后,核心问题就变成了:怎么把一段自然语言变成建模脚本能用的参数。这里有两种主流做法,各有适用场景。
第一种是纯提示词工程。设计一个结构化的提示词,要求语言模型输出固定格式的JSON,比如包含shape_type、dimensions、features这些字段。然后在Python里解析这个JSON,映射到对应的建模函数。这种做法的优点是灵活,能处理各种描述;缺点是模型的输出不稳定,需要做校验和兜底。
第二种是模板加槽位填充。预先定义好一批建模模板,每个模板对应一类零件,模板里留出参数槽位。语言模型的任务只是从文字里抽取参数填进槽位,不负责决定几何结构。这种做法的优点是稳定,几何不会出错;缺点是覆盖面有限,遇到模板外的形状就无能为力。
实际项目里,我倾向于混合使用:常见形状走模板,复杂或新奇的描述走提示词工程,并且对模型输出做严格的schema校验。校验不通过就要求模型重新生成,或者降级到人工确认。这样既保证了稳定性,又保留了一定的灵活性。
import json def parse_intent(text, llm_client): prompt = f"""从下面的描述中提取建模参数,输出JSON。 描述:{text} 要求字段:shape_type, dimensions(字典), features(列表) 只输出JSON,不要解释。""" resp = llm_client.generate(prompt) try: data = json.loads(resp) except json.JSONDecodeError: raise ValueError("模型输出不是合法JSON,需要重试或人工介入") return data这段代码看起来简单,但实际跑起来,模型输出带markdown代码块标记、字段名拼错、数值带单位字符串,都是家常便饭。所以解析函数里要做清洗,比如去掉```json标记,把"50mm"转成50.0。这些脏活累活,才是text-to-cad落地时真正花时间的地方。
3.3 从参数到实体:CadQuery建模的常见写法
参数拿到之后,就是调用建模库生成几何。CadQuery的API设计得比较直观,用工作平面(Workplane)作为起点,链式调用各种操作。下面是一个带孔和倒角的方块示例,基本覆盖了最常见的几类操作。
import cadquery as cq def build_part(params): length = params["length"] width = params["width"] height = params["height"] hole_d = params["hole_diameter"] fillet_r = params["fillet_radius"] part = ( cq.Workplane("XY") .box(length, width, height) .edges("|Z") .fillet(fillet_r) .faces(">Z") .workplane() .hole(hole_d) ) return part part = build_part({ "length": 100, "width": 60, "height": 30, "hole_diameter": 20, "fillet_radius": 5 }) cq.exporters.export(part, "part.step")这段代码里,edges("|Z")是选择平行于Z轴的边,faces(">Z")是选择朝上的面,这种选择器语法是CadQuery的精华,也是新手最容易迷糊的地方。选择器写错,操作就作用在错误的几何元素上,结果要么报错,要么生成一个莫名其妙的形状。我的经验是,每加一个操作就导出一次看看,别一口气写完再调试,那样定位问题会很痛苦。
另外,倒角半径不能超过相邻边的最短长度的一半,否则几何运算会失败。这个约束在参数校验阶段就要检查,不能等到建模时报错才发现。类似这样的几何合法性约束还有很多,比如孔径不能大于所在面的尺寸,拉伸深度不能为负等等。把这些约束整理成一个校验函数,能挡掉大部分低级错误。
3.4 导出STEP时的那些细节
几何建好之后,导出STEP看似一行代码的事,但细节不少。首先是单位,CadQuery默认单位是毫米,导出的STEP也是毫米。如果你的输入参数是米或者英寸,一定要在建模前统一换算,否则出来的模型尺寸会差几个数量级。
其次是坐标系。默认情况下,模型的原点在建模工作平面的原点。如果下游需要特定的坐标系约定,比如机器人仿真里常见的Z轴朝上、原点在质心,那就要在导出前做变换。这一步如果漏了,导入仿真环境后模型的位置和朝向会完全不对。
再就是装配体。单个零件的导出很简单,但如果是多个零件组成的装配,需要用到Assembly对象,并且给每个零件指定位置和名称。导出的STEP会保留装配层级,下游软件打开时能看到零件树。如果只是把多个零件简单合并成一个实体导出,装配信息就丢了,后续想单独修改某个零件就麻烦了。
from cadquery import Assembly, Location, Vector assy = Assembly() assy.add(part1, name="base", loc=Location(Vector(0, 0, 0))) assy.add(part2, name="cover", loc=Location(Vector(0, 0, 50))) assy.save("assembly.step")提示:装配体导出后,建议用免费的STEP查看器打开确认一下零件树和颜色是否正常。有些导出参数设置不当,会导致零件全部合并或者颜色丢失,等到下游反馈才发现就晚了。
4. STEP和URDF之间的那道坎
4.1 两种格式描述的是完全不同的东西
很多人以为STEP转URDF就是换个文件后缀,这是最大的误解。STEP描述的是几何形状和装配结构,它关心的是"这个物体长什么样、由哪些零件组成"。URDF描述的是运动学树,它关心的是"这些零件怎么连接、怎么相对运动"。一个是几何视角,一个是运动学视角,两者的信息模型根本不同。
具体来说,URDF里的核心元素是link和joint。link是刚体,有视觉几何、碰撞几何、惯性参数;joint连接两个link,定义了关节类型和运动轴。从STEP转到URDF,需要做几件事:把每个零件映射成一个link,把零件之间的装配约束映射成joint,给每个link补上质量和惯性参数,还要定义碰撞几何。这些信息在STEP里要么没有,要么表达方式完全不同。
所以严格来说,STEP到URDF不是格式转换,而是模型重建。工具能帮你做一部分自动化,但关键的关节定义、惯性参数,往往需要人工确认或补充。指望一键转换就能得到可用的URDF,基本不现实。
4.2 关节定义:转换里最容易出错的一环
关节定义是STEP转URDF的核心难点。在CAD装配里,零件之间的约束可能是同轴、贴合、距离等,这些约束要映射成URDF的关节类型(revolute、prismatic、fixed、continuous等),还要确定关节轴的方向和位置。
问题在于,CAD里的约束信息不一定完整。有些装配是"摆"上去的,没有明确的约束关系,转换工具就无从判断两个零件之间是什么关节。这时候只能靠人工指定,或者根据零件的几何特征推断,比如两个圆柱面同轴,大概率是旋转关节。
关节轴的方向也容易出错。URDF里的轴是在关节坐标系下定义的,而CAD里的约束是在全局坐标系下。转换时需要做坐标变换,把全局的轴方向转到关节局部坐标系。这一步如果搞错,导入仿真环境后关节会绕错误的轴旋转,机器人动作完全不对。
我的做法是,转换完成后,在仿真环境里逐个关节手动测试,确认旋转或平移的方向和范围符合预期。这个验证步骤不能省,因为很多错误在静态查看时看不出来,一动就暴露了。
4.3 惯性参数:被忽视但影响仿真结果的关键
URDF里的每个link都需要惯性参数,包括质量、质心位置、惯性张量。这些参数在STEP里通常没有,因为CAD模型关注的是几何,不关注物理属性。但仿真环境需要这些参数来计算动力学,缺失或错误会导致仿真结果失真。
补惯性参数有几种办法。简单形状可以用公式算,比如长方体、圆柱体的惯性张量有解析解。复杂形状可以用CAD软件的质量属性功能,指定材料密度后自动计算。再复杂一点的,可以用网格模型做数值积分。不管用哪种方法,都要确保单位一致,质量用千克,长度用米,惯性张量用千克·平方米。
这里有个容易踩的坑:CAD模型里的零件可能是简化过的,比如省略了小孔、倒角,这些细节对几何影响不大,但对质量和惯性有影响。如果仿真对精度要求高,需要用完整模型计算惯性参数,或者做修正。如果只是做运动学验证,简化模型的惯性参数通常够用。
4.4 导入仿真环境后的验证清单
URDF写好之后,导入CoppeliaSim这类仿真环境,别急着跑复杂场景,先做基础验证。我整理了一个检查清单,按顺序过一遍,能挡掉大部分问题。
| 检查项 | 预期结果 | 常见问题 |
|---|---|---|
| 模型是否正常显示 | 所有link可见,位置正确 | 坐标系错误导致零件错位 |
| 关节数量是否正确 | 与设计一致 | 约束识别错误导致关节缺失或多余 |
| 关节运动方向 | 符合设计意图 | 轴向定义错误 |
| 关节运动范围 | 在设定范围内 | 限位参数缺失或错误 |
| 碰撞体是否合理 | 与视觉几何匹配 | 碰撞体过大或过小 |
| 质量参数 | 数值合理 | 单位错误或参数缺失 |
这个清单看起来基础,但实际项目中,我见过太多因为坐标系没对齐导致整个模型偏移、因为关节轴搞反导致动作镜像的案例。花十分钟做验证,比后面花几小时排查划算得多。
5. 落地过程中真正会卡住人的几个地方
5.1 参数校验比建模本身更重要
text-to-cad的输入是自然语言,输出是几何模型,中间任何一环出问题,最终结果都是错的。而自然语言的歧义性,决定了参数校验必须做得很扎实。比如"一个直径50的圆孔",直径50是半径还是直径?"深30"是从哪个面算起?这些在人类看来可能靠常识判断,但程序必须明确。
我的做法是,在参数解析之后、建模之前,加一层校验。校验分两类:一类是类型和范围校验,比如尺寸必须是正数,倒角半径不能超过边长的一半;另一类是几何可行性校验,比如孔的位置必须在材料内部,拉伸方向不能导致自相交。校验不通过就返回明确的错误信息,让用户或上游系统修正。
def validate_params(params): errors = [] if params["length"] <= 0: errors.append("长度必须为正数") if params["fillet_radius"] > min(params["length"], params["width"]) / 2: errors.append("倒角半径过大,会导致几何运算失败") if params["hole_diameter"] >= min(params["length"], params["width"]): errors.append("孔径不能大于所在面的最小边长") return errors这层校验看起来繁琐,但它是系统稳定性的保障。没有它,用户输入一个稍微离谱的参数,整个流水线就崩了,而且报错信息往往晦涩难懂,排查起来很痛苦。
5.2 批量处理时的性能与稳定性
单个模型生成很快,但实际项目里往往是批量处理,比如一次生成几百个变体。这时候性能和稳定性就成了问题。几何运算本身比较吃CPU,如果串行处理,几百个模型可能要跑很久。用多进程并行能提速,但要注意几何库的线程安全问题,有些库在多线程下会崩溃,用多进程更稳妥。
稳定性方面,批量处理最怕的是中途某个模型失败导致整个任务中断。解决办法是给每个模型的处理加异常捕获,失败的记录下来,继续处理下一个,最后统一报告。这样即使有个别失败,也不会影响整体进度。
from concurrent.futures import ProcessPoolExecutor def process_one(params): try: part = build_part(params) cq.exporters.export(part, f"output/{params['id']}.step") return (params["id"], "success") except Exception as e: return (params["id"], f"failed: {e}") with ProcessPoolExecutor(max_workers=4) as executor: results = list(executor.map(process_one, all_params)) failed = [r for r in results if r[1] != "success"] print(f"失败 {len(failed)} 个")注意:并行度不是越高越好。几何运算内存占用大,开太多进程可能导致内存不足。一般按CPU核心数的一半到三分之二来设,具体看模型复杂度。
5.3 版本兼容性:几何库升级带来的连锁反应
几何库的版本兼容性是个隐形炸弹。CadQuery、pythonocc、OCCT之间的版本依赖关系比较紧,升级其中一个,可能要求其他几个也跟着升。而升级之后,API可能有变化,原来能跑的代码就报错了。
我踩过一次坑:项目跑得好好的,某天手贱升级了CadQuery,结果导出STEP的接口签名变了,批量任务全部失败。排查了半天才发现是版本问题。从那以后,我在项目里固定依赖版本,用requirements.txt锁死,升级前先在测试环境验证。
cadquery==2.3.0 numpy==1.24.0锁版本的好处是可复现,坏处是错过新功能和安全更新。折中方案是定期在测试环境评估升级,确认没问题再更新生产环境的锁文件。别在生产环境直接pip install --upgrade,那是给自己找麻烦。
5.4 从demo到生产:还差哪些工程化工作
网上的text-to-cad demo很多,但能直接用于生产的少。差距主要在工程化上。demo只需要跑通一个例子,生产需要考虑异常处理、日志记录、参数校验、性能优化、版本管理、接口设计等一大堆事。
举个具体的例子:demo里模型输出直接json.loads就用了,生产里必须加schema校验和重试机制,因为模型输出不稳定是常态。再比如,demo里导出文件用固定路径,生产里要考虑文件命名冲突、目录不存在、磁盘空间不足等问题。这些看起来琐碎,但正是它们决定了系统能不能稳定运行。
我的建议是,如果你打算把text-to-cad用到实际项目里,先花时间把工程框架搭好,把异常处理、日志、配置管理这些基础设施做扎实,再往上堆功能。反过来做,后面会不断返工,成本更高。
6. 几个实操中总结出来的小经验
关于选择器语法,CadQuery的选择器很强大但也很容易写错。我的习惯是,每写一个选择器,先用val()或者打印的方式确认选中的元素数量和位置,确认无误再继续。选择器选错元素,是几何生成失败的头号原因。
关于单位,从输入到输出,全程用毫米,中间不要混用其他单位。如果上游给的是英寸,在解析阶段就换算成毫米,后面所有环节都按毫米处理。单位混乱导致的问题,排查起来特别费劲,因为模型看起来是正常的,只是尺寸不对。
关于文件组织,批量生成时,输出目录按批次或日期分文件夹,文件名带上参数哈希或序号,避免覆盖。同时保留一份参数记录,方便追溯每个模型对应什么输入。这个习惯在出问题复盘时特别有用。
关于测试,几何生成的测试不能只看代码跑没跑通,要看生成的模型对不对。我的做法是,对关键模型做体积、包围盒、面数等指标的断言,数值在预期范围内才算通过。这样即使几何库升级导致行为变化,测试也能及时发现。
关于学习路径,如果你刚接触这个方向,建议先把CadQuery的官方示例过一遍,理解工作平面和选择器的概念,再去看text-to-cad的集成方案。跳过几何基础直接搞集成,遇到问题会不知道从哪查起。几何这块的基础打牢了,后面无论换什么工具、什么格式,都能快速上手。
最后说一个心态上的体会:text-to-cad这个方向,工具和方案更新很快,今天好用的库明天可能就换了维护者。与其追着工具跑,不如把底层的几何概念、格式规范、工程方法搞清楚。这些是不太会变的东西,掌握了它们,换任何工具都能快速适应。我在这个方向上折腾了不短时间,最大的收获不是学会了某个库,而是对"文字如何变成几何"这件事有了完整的认知,这让我在面对新工具时心里有底。