1. 从一句话到三维模型:text-to-cad 到底在解决什么问题
第一次听到 “text-to-cad” 这个词,很多人脑子里浮现的画面大概是:对着电脑说一句“给我画个齿轮”,屏幕上就自动蹦出一个可以旋转查看的三维模型。这个想象不算离谱,但真正落地到工程场景里,它要解决的问题比“画个齿轮”复杂得多,也有意思得多。
我最早接触这个方向,是因为手头有一批参数化零件需要反复修改。每次改尺寸都要打开 CAD 软件,找到对应的草图,改约束,重新拉伸,再导出。一个零件还好,几十个零件来回折腾,时间全耗在重复劳动上。后来我就想,能不能用代码把这件事自动化——用一段文字描述零件的几何特征和尺寸,程序自动生成对应的 CAD 文件。这就是 text-to-cad 最朴素的需求来源。
所谓 text-to-cad,字面意思就是“文本转 CAD”。这里的“文本”可以是一句自然语言描述,比如“一个直径 50 毫米、厚度 10 毫米、中心带 20 毫米通孔的圆盘”;也可以是一段结构化的参数配置,比如 JSON 或 YAML 里写好的尺寸表。而“CAD”则涵盖了从三维实体模型到工程图纸的一系列产物,常见的输出格式包括STEP、STL、IGES,以及在机器人领域用得很多的URDF,还有面向加工制造的G-code。
它解决的核心问题,是把“人操作软件”变成“程序生成模型”。这个转变带来的价值很直接:批量生成、参数化迭代、版本可控、和上下游系统打通。比如你做机器人仿真,需要为不同规格的机械臂生成 URDF 模型;比如你做钣金设计,需要根据订单参数批量出图;再比如你做教学演示,想让学生通过改几个数字就理解尺寸变化对模型的影响。这些场景里,text-to-cad 都能派上用场。
适合看这篇内容的人,我大致分三类。第一类是有编程基础、想把手动建模自动化的工程师,你可能写过 Python 脚本处理数据,但还没试过用代码生成三维模型。第二类是做机器人、仿真、增材制造相关工作的朋友,你需要频繁和 URDF、STEP、G-code 打交道,想找个更高效的生成方式。第三类是对 CAD 二次开发感兴趣的学习者,你想知道从文本到模型这条链路到底由哪些环节组成,每个环节用什么工具、踩什么坑。不管你是哪一类,接下来的内容都会从实际可操作的角度展开,尽量把每个环节讲透。
2. 整体技术链路拆解:从文本到模型要过几道关
2.1 核心环节的划分与职责
把 text-to-cad 拆开看,它本质上是一条数据处理流水线。输入端是文本,输出端是 CAD 文件,中间要经过好几个环节。我习惯把它分成四层:文本解析层、几何建模层、格式转换层、验证输出层。每一层各司其职,层与层之间通过明确的数据结构传递信息。
文本解析层负责把自然语言或结构化文本变成程序能理解的参数。如果输入是“直径 50、厚度 10 的圆盘”,解析层要提取出“直径=50”“厚度=10”“形状=圆盘”这几个关键信息。如果输入是 JSON,那解析层的工作就轻很多,主要是校验字段完整性和数值范围。这一层的难点在于自然语言的歧义性——“大一点的孔”到底是多大?“薄壁”是几个毫米?所以实际项目中,纯自然语言的解析往往需要配合领域词表和默认值策略。
几何建模层是核心,它拿到参数后,调用几何内核生成三维实体。这里的选择直接决定了整个方案的能力和上限。常见的几何内核有OpenCASCADE(开源、功能全)、CGAL(计算几何强)、FreeCAD 的 Part 模块(基于 OpenCASCADE 封装)。如果你只是生成简单的拉伸体、旋转体,用 FreeCAD 的 Python API 就够;如果要处理复杂的布尔运算、倒角、曲面,OpenCASCADE 的原生接口会更灵活。
格式转换层负责把生成的模型导出成目标格式。STEP 适合做数据交换,STL 适合 3D 打印,URDF 适合机器人仿真,G-code 适合数控加工。不同格式对模型的要求不一样,比如 STL 要求模型是封闭的流形,URDF 要求模型有明确的连杆和关节定义。这一层经常出问题,后面会详细讲。
验证输出层是最后一道关,检查生成的模型是否合法、尺寸是否正确、有没有破面或自相交。很多人会跳过这一步,结果导出的文件在 downstream 软件里打不开,回头排查更费时间。
2.2 方案选型:为什么我最终选了 Python + FreeCAD + OpenCASCADE
市面上做 text-to-cad 的路线不止一条。有人用OpenSCAD,它的脚本语言很简洁,适合做参数化建模,但它的几何内核是 CGAL,对复杂曲面的支持有限,而且导出 STEP 的能力较弱。有人用CadQuery,它是基于 OpenCASCADE 的 Python 库,语法优雅,适合做工程零件,但学习曲线比 OpenSCAD 陡一些。还有人直接用SolidWorks API或AutoCAD API,功能强大但依赖商业软件,部署成本高。
我最终选的是Python + FreeCAD + OpenCASCADE这条路线。原因有几个。第一,FreeCAD 是开源免费的,部署灵活,不依赖特定商业授权。第二,FreeCAD 的 Python API 封装得比较友好,入门快,同时又能通过Part模块直接调用 OpenCASCADE 的底层功能,兼顾易用性和灵活性。第三,Python 生态丰富,文本解析、数据处理、批量任务调度都有现成的库可用。第四,FreeCAD 支持导出 STEP、STL、IGES 等多种格式,URDF 虽然不直接支持,但可以通过额外的脚本转换。
当然这条路线也有缺点。FreeCAD 的文档不算完善,很多 API 要靠读源码或社区帖子才能搞明白。OpenCASCADE 的报错信息有时候很晦涩,排查问题需要耐心。另外 FreeCAD 的启动速度偏慢,如果做高频批量生成,需要考虑常驻进程或改用更轻量的方案。但综合来看,对于大多数中小规模的 text-to-cad 需求,这条路线是性价比很高的选择。
2.3 数据流的组织方式
在实际项目里,我不会把文本解析和几何建模混在一起写。更好的做法是定义一个中间数据结构,比如用 Python 的 dataclass 或字典,把解析结果标准化。这样做的好处是,解析层和建模层可以独立开发和测试。解析层改进了,建模层不用动;建模层换了内核,解析层也不用重写。
我通常定义的中间结构包含这几类字段:几何类型(如 cylinder、box、gear)、尺寸参数(如 diameter、height、teeth_count)、位置和方向(如 translation、rotation)、布尔操作(如 cut、fuse、intersect)、输出配置(如 format、tolerance)。这个结构可以序列化成 JSON,方便调试和版本管理。每次生成模型时,先把文本转成这个结构,再把这个结构喂给建模函数。如果模型有问题,可以单独检查这个中间结构,快速定位是解析错了还是建模错了。
3. 核心细节解析:文本解析、几何建模与格式转换的实操要点
3.1 文本解析:从自然语言到结构化参数
文本解析是整条链路的入口,也是最容易被低估的环节。很多人觉得“解析一句话有什么难的”,但实际做起来,坑比想象的多。
先说自然语言的情况。假设用户输入“生成一个外径 80、内径 40、高 25 的法兰盘,带 6 个直径 8 的螺栓孔,均匀分布”。这句话里包含了主几何(法兰盘)、尺寸(外径、内径、高)、特征(螺栓孔)、数量(6 个)、尺寸(直径 8)、分布方式(均匀分布)。如果纯靠正则表达式提取,写起来会非常脆弱,稍微换个说法就匹配不上。
我的做法是分两步走。第一步用关键词匹配 + 正则提取显式的数值和单位,比如“外径 80”对应outer_diameter=80,“6 个”对应count=6。第二步用领域词表把同义词归一化,比如“外径”“外圆直径”“OD”都映射到outer_diameter,“螺栓孔”“安装孔”“通孔”都映射到hole。对于“均匀分布”这种描述,直接映射到pattern=circular,默认按圆周等分。
如果输入是结构化文本,比如 JSON,解析就简单很多,但校验不能省。我一般会检查这几项:必填字段是否存在、数值是否在合理范围内、单位是否统一。比如直径不能是负数,孔的数量不能是小数,单位如果是英寸要转换成毫米。这些校验看起来琐碎,但能避免很多后续的建模失败。
注意:自然语言解析不要追求一步到位。先支持几种固定句式,跑通链路,再逐步扩展。一上来就想支持任意说法,很容易陷入无穷无尽的边界情况。
3.2 几何建模:用 FreeCAD 生成实体的关键步骤
几何建模是 text-to-cad 的核心。以 FreeCAD 为例,生成一个带孔法兰盘的基本流程是这样的。
首先导入 FreeCAD 的 Part 模块,创建一个文档对象。然后根据参数生成基础实体。法兰盘的主体可以用Part.makeCylinder(radius, height)生成一个圆柱体。接着生成内孔,同样用makeCylinder生成一个半径更小的圆柱体,然后用cut操作从主体中减去。螺栓孔的做法类似,先生成一个小圆柱,再把它移动到圆周上的指定位置,最后从主体中减去。
这里有几个关键点。第一,布尔运算的顺序会影响结果。一般先做大的减法(内孔),再做小的减法(螺栓孔),这样出错时容易定位。第二,圆柱体的位置默认在原点,需要通过translate或rotate调整到正确位置。螺栓孔的圆周分布需要计算每个孔的中心坐标,公式是x = R * cos(theta),y = R * sin(theta),其中theta是等分角度。第三,布尔运算可能失败,尤其是当两个实体刚好相切或重合时。解决办法是给一个微小的偏移量,比如 0.001 毫米,避免临界情况。
对于更复杂的零件,比如齿轮,就不能只用圆柱体拼了。齿轮的齿廓需要用到渐开线方程,或者直接调用 FreeCAD 的InvoluteGear功能(如果版本支持)。如果 FreeCAD 自带功能不够用,可以通过 OpenCASCADE 的BRepBuilderAPI手动构建齿廓曲线,再拉伸成实体。这部分代码量较大,建议封装成独立的函数,传入模数、齿数、压力角等参数,返回齿轮实体。
实操心得:建模函数一定要做参数校验。比如齿轮的齿数必须是大于等于 6 的整数,模数必须是正数。如果参数不合法,直接抛出明确的错误信息,而不是让几何内核报一个看不懂的异常。
3.3 格式转换:STEP、URDF、G-code 的导出差异
模型建好后,导出成什么格式,取决于下游怎么用。这一步看似简单,但不同格式的要求差别很大。
STEP是工程领域最通用的交换格式,支持实体、曲面、装配体。FreeCAD 导出 STEP 用Part.export或exportStep方法,一般不会有大问题。但要注意,STEP 文件对模型的拓扑合法性有要求,如果模型有破面或自相交,导出可能失败或生成无效文件。导出前最好用Part.__sortEdges__或isValid检查一下。
STL是 3D 打印常用的格式,它把模型表面三角化。导出 STL 时需要指定公差,公差越小,三角面片越密,文件越大,但表面越光滑。我一般用 0.1 毫米的公差做预览,0.01 毫米做最终输出。如果模型有薄壁结构,公差太大可能导致壁厚丢失,需要特别留意。
URDF是机器人描述格式,它不直接描述几何,而是描述连杆和关节的层级关系,几何部分通过引用 STL 或 DAE 文件来实现。所以导出 URDF 其实是两步:先把每个连杆导出成 STL,再写一个 URDF 文件把这些 STL 和关节关系组织起来。URDF 文件是 XML 格式,需要定义 link、joint、visual、collision、inertial 等元素。惯性参数如果算不准,可以用简化公式估算,但仿真结果会受影响。
G-code是数控加工指令,它不是模型格式,而是加工路径。从 CAD 到 G-code 需要经过 CAM 环节,通常要用到切片或路径规划算法。FreeCAD 有 Path 模块可以做这件事,但配置比较复杂。如果只是做 3D 打印的 G-code,用切片软件更直接。text-to-cad 生成 G-code 的场景,一般是定制化的加工路径,比如在曲面上走特定的轨迹。
| 格式 | 主要用途 | 导出要点 | 常见问题 |
|---|---|---|---|
| STEP | 工程交换 | 检查拓扑合法性 | 破面导致导出失败 |
| STL | 3D 打印 | 设置合理公差 | 公差过大丢失细节 |
| URDF | 机器人仿真 | 分连杆导出 + XML 组织 | 惯性参数不准 |
| G-code | 数控加工 | 需要 CAM 路径规划 | 路径碰撞、过切 |
4. 完整实操流程:从零搭建一个 text-to-cad 脚本
4.1 环境准备与依赖安装
在开始写代码之前,先把环境搭好。我用的组合是Python 3.9+加FreeCAD 0.20+。FreeCAD 的安装方式取决于操作系统。Windows 下直接下载安装包,安装时勾选“Python 支持”。Linux 下可以用包管理器安装,比如apt install freecad。macOS 下可以用 Homebrew 安装。
安装完成后,需要确认 Python 能导入 FreeCAD 的模块。FreeCAD 自带的 Python 解释器和系统 Python 可能不是同一个,所以最好直接用 FreeCAD 的 Python 环境,或者把 FreeCAD 的库路径加到系统 Python 的sys.path里。我通常的做法是在 FreeCAD 的 Python 控制台里测试代码,确认没问题后再写成脚本,用 FreeCAD 的命令行模式执行。
除了 FreeCAD,可能还需要一些辅助库。比如numpy用于数值计算,jsonschema用于校验 JSON 输入,lxml用于生成 URDF 的 XML。这些库用 pip 安装即可。
注意:FreeCAD 的版本更新可能带来 API 变化。写脚本时尽量用稳定的接口,避免依赖内部实现。如果升级 FreeCAD 后脚本报错,先查 API 变更日志。
4.2 定义输入格式与解析函数
我设计的输入格式是一个 JSON 对象,包含shape、parameters、operations、output四个字段。shape指定几何类型,比如flange、gear、box。parameters是尺寸参数字典。operations是布尔操作列表。output指定导出格式和路径。
解析函数的工作是把 JSON 读进来,校验字段,然后转换成内部的数据结构。我一般用 dataclass 定义几个类,比如GeometrySpec、OperationSpec、OutputSpec。解析函数返回这些类的实例,后续建模函数直接使用。
对于自然语言输入,我会先写一个简单的规则解析器,支持几种固定句式。比如“生成一个[形状],[参数1]为[值1],[参数2]为[值2]”这种模式。用正则提取形状和参数对,再映射到 JSON 结构。这个解析器不追求覆盖所有说法,先支持最常用的几种,后续再扩展。
4.3 建模函数与参数计算
建模函数是核心。以法兰盘为例,我写一个build_flange函数,接收参数字典,返回 FreeCAD 的 Shape 对象。函数内部先校验参数,然后生成主体圆柱、内孔圆柱、螺栓孔圆柱,依次做布尔减法。螺栓孔的位置用循环计算,每个孔的角度是2 * pi * i / count。
参数计算有几个细节要注意。螺栓孔的分布圆直径通常是外径和内径的平均值,但也可以单独指定。如果用户没指定,就用默认公式。孔的直径不能大于分布圆直径,否则孔会超出法兰盘边缘。这些约束要在函数里检查,不满足就报错。
对于齿轮,参数计算更复杂。齿顶圆直径、齿根圆直径、分度圆直径都有公式。分度圆直径等于模数乘以齿数。齿顶圆直径等于分度圆直径加上两倍模数。齿根圆直径等于分度圆直径减去 2.5 倍模数。这些公式要写进代码,确保生成的齿轮符合标准。
4.4 导出与验证
模型建好后,调用导出函数。STEP 用Part.export([shape], path),STL 用shape.exportStl(path),URDF 需要额外写 XML。导出后,我会用 FreeCAD 重新打开文件,检查模型是否完整。对于 STL,可以用numpy-stl库读取并检查三角面片数量。对于 STEP,可以用 FreeCAD 的Part.open打开,检查实体数量。
验证环节还包括尺寸检查。比如生成的法兰盘,用shape.BoundBox获取包围盒,确认外径和高度是否符合预期。如果偏差超过公差,说明建模有问题,需要回头排查。
实操心得:批量生成时,不要每生成一个就导出一次。先在内存里生成所有模型,检查无误后再统一导出。这样能减少磁盘 IO,也方便做整体校验。
5. 常见问题与排查技巧实录
5.1 布尔运算失败:原因与解法
布尔运算是建模中最容易出问题的环节。常见原因有几种。第一,两个实体没有相交,做cut或fuse时结果不符合预期。第二,两个实体刚好相切,几何内核无法判断内外。第三,实体本身有破面或自相交,布尔运算无法进行。
排查方法是先单独检查每个实体是否合法。用shape.isValid()判断,如果返回 False,说明实体有问题。对于相切的情况,给一个微小的偏移量,比如把孔的半径增加 0.001 毫米,避免临界。对于破面,可以用shape.fix()尝试修复,或者检查生成实体的步骤,看哪一步产生了非法几何。
5.2 导出文件打不开:格式兼容性排查
导出的 STEP 或 STL 在别的软件里打不开,通常有几个原因。一是文件路径有中文或特殊字符,某些软件不支持。二是模型单位不对,比如 FreeCAD 默认单位是毫米,但导出时可能被解释成米。三是模型太大或太小,超出了软件的显示范围。
解决办法是导出时明确指定单位,路径用纯英文。对于 STL,检查公差设置,太小会导致文件过大,太大则丢失细节。对于 STEP,可以用文本编辑器打开文件头,确认单位声明是否正确。
5.3 批量生成时的性能优化
批量生成几十上百个模型时,性能会成为瓶颈。FreeCAD 的启动和文档创建比较耗时,如果每个模型都新建文档,开销很大。优化方法是复用同一个文档,每次生成前清空内容,而不是新建。另外,布尔运算尽量合并,比如多个螺栓孔可以一次性生成一个复合实体,再做一次减法,而不是逐个减。
如果模型之间没有依赖关系,可以用多进程并行生成。但要注意 FreeCAD 不是线程安全的,多线程可能出问题,多进程更稳妥。每个进程独立处理一部分模型,最后合并结果。
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 布尔运算结果为空 | 实体不相交或相切 | 检查包围盒是否重叠 | 调整位置或加偏移量 |
| 导出 STEP 失败 | 模型有破面 | 用 isValid 检查 | 修复几何或重建 |
| STL 文件过大 | 公差设置过小 | 查看文件大小 | 增大公差 |
| URDF 导入仿真失败 | 惯性参数缺失 | 检查 XML 元素 | 补充 inertial 标签 |
| 批量生成速度慢 | 频繁新建文档 | 计时各环节 | 复用文档、并行处理 |
5.4 自然语言解析的边界处理
自然语言解析最怕遇到模糊描述。比如“大孔”“小孔”“厚一点”“薄一点”,这些没有绝对标准。我的处理方式是设置默认值,并在解析结果里标记“使用了默认值”,让用户知道哪些参数是猜的。如果用户不满意,可以显式指定数值。
另一个问题是单位混用。有人写“直径 2 英寸”,有人写“直径 50 毫米”。解析时要统一转换成毫米,并在内部记录原始单位,方便追溯。如果单位缺失,默认按毫米处理,但要在输出里提示。
注意:不要试图用自然语言解析替代结构化输入。对于精度要求高的场景,直接让用户填 JSON 或表格,比猜自然语言可靠得多。
6. 工具选型与扩展方向
6.1 几何内核的对比与选择
除了 FreeCAD + OpenCASCADE,还有其他几何内核可选。CGAL在计算几何方面很强,适合做三角化、布尔运算、网格处理,但它的 CAD 功能较弱,不适合做工程零件。OpenSCAD的脚本语言简单,适合快速原型,但导出 STEP 能力有限。CadQuery基于 OpenCASCADE,语法比 FreeCAD 更现代,适合写复杂的参数化零件,但社区规模较小。
选择哪个内核,取决于你的需求。如果要做工程零件、导出 STEP,FreeCAD 或 CadQuery 更合适。如果要做网格处理、3D 打印,CGAL 或 OpenSCAD 也可以考虑。如果团队已经用了商业 CAD,直接用它的 API 可能更省事,但要注意授权和部署限制。
6.2 与下游系统的集成
text-to-cad 生成的模型,最终要流向哪里,决定了集成的重点。如果流向仿真软件,比如机器人仿真,URDF 的关节定义和惯性参数要准确。如果流向 3D 打印,STL 的流形性和公差要控制好。如果流向数控加工,G-code 的路径规划要避免碰撞。
集成的另一个方向是反向反馈。比如仿真发现模型有问题,能不能自动调整参数重新生成?这需要把 text-to-cad 脚本封装成服务,接收参数,返回模型,并支持迭代。这种闭环在优化设计中很有价值,但实现复杂度也高。
6.3 后续可以扩展的能力
这个脚本目前支持几种基本形状,后续可以扩展更多。比如支持装配体,用多个零件组合成产品。支持工程图,自动生成三视图和尺寸标注。支持材料属性,给模型附加密度、弹性模量等信息,方便做力学分析。
另一个扩展方向是接入大语言模型,让自然语言解析更智能。但要注意,大语言模型的输出不稳定,可能生成不合法的参数。所以即使接入了,也要保留校验层,不能直接信任模型的输出。
我个人在实际操作中的体会是,text-to-cad 的价值不在于替代 CAD 软件,而在于把重复性的建模工作自动化。它适合参数化程度高、批量需求大的场景,不适合做创意设计或复杂曲面。把这条链路跑通,哪怕只支持两三种形状,也能省下大量重复劳动。后面再慢慢扩展形状库和解析能力,逐步覆盖更多需求。