1. 从一句话到三维模型:text-to-cad 到底在解决什么问题
第一次听到“text-to-cad”这个说法,我脑子里蹦出来的画面是:对着电脑说一句“给我来个带法兰的六角螺栓”,屏幕上就自动长出一个可以导出加工的实体模型。这个画面在几年前还属于科幻范畴,但现在已经有一批工具和方案在往这个方向靠了。所谓 text-to-cad,直译就是“文本转计算机辅助设计”,核心思路是把自然语言描述转换成参数化的三维几何模型,或者至少转换成能驱动 CAD 软件生成模型的脚本与参数。
它解决的问题很具体。传统 CAD 建模的门槛不在“画图”,而在于你得先学会软件的操作逻辑、约束系统、特征树管理,还要理解工程语义。一个机械工程师画一个标准件可能只要三分钟,但一个产品经理、一个做概念验证的开发者、一个只想快速拿到某个形状做仿真的人,往往卡在“我不会用 SolidWorks”这一步。text-to-cad 想干的事,就是把这层操作门槛抹掉,让描述本身成为建模指令。
适合关注这个方向的人其实比想象中多。做快速原型的设计师,需要批量生成不同规格的零件做对比;做机器人仿真的研究者,需要快速搭出场景里的各种几何体;做教育的人,想让学生把注意力放在几何思维而不是软件按钮上;还有一类是做数据增强的,需要大量带参数标注的三维模型来训练模型。这些场景的共同点是:模型本身不复杂,但数量多、变体多、手动建模不划算。
我在这篇文章里会拆开讲几个层面:文本到底怎么变成几何、参数化建模为什么是绕不开的中间层、实际跑通一条链路需要哪些组件、以及我在尝试类似方案时踩过的那些坑。不会只停留在“这个概念很酷”的层面,而是尽量给到能上手复现的思路和判断依据。
2. 文本到几何的三种技术路线,以及为什么参数化脚本是当前最稳的落点
2.1 直接生成网格:看起来最直接,实际最难控
最直觉的路线是让模型直接输出三维网格,比如点云、体素或者三角面片。这条路线在学术上有很多探索,典型做法是把文本编码成一个向量,再通过某种生成模型解码成三维结构。它的优点是端到端,不需要中间表示,理论上能生成任意形状。但问题也很明显:生成的网格往往拓扑混乱、面片数不可控、尺寸没有工程意义,而且很难做后续的参数修改。
我试过用这类方法生成一个“带圆角的矩形底板”,出来的东西远看像那么回事,近看边缘是锯齿状的,圆角半径也没法精确控制。对于做概念草图可能够用,但一旦你要拿去做装配、做干涉检查、导出 STEP 文件给加工,这条路基本走不通。所以直接生成网格更适合做视觉参考,不适合做工程交付。
2.2 生成 CAD 脚本:把自然语言翻译成建模命令
第二条路线是让模型输出一段脚本,比如 OpenSCAD 的代码、FreeCAD 的 Python 脚本,或者某种 CAD 软件支持的宏命令。这段脚本再被 CAD 引擎执行,生成真正的参数化实体。这条路线的好处是:几何是精确的,尺寸是可追溯的,改一个参数就能重新生成整个模型。
举个例子,你输入“一个长 50 毫米、宽 30 毫米、高 20 毫米的长方体,中心挖一个直径 10 毫米的通孔”,模型需要输出的不是网格,而是类似这样的伪代码:
length = 50 width = 30 height = 20 hole_dia = 10 base = Box(length, width, height) hole = Cylinder(radius=hole_dia/2, height=height) result = base - hole这段代码本身不难,难的是让语言模型稳定地理解“中心”“通孔”“直径”这些工程语义,并且映射到正确的 API 调用上。我实测下来,对于结构清晰、尺寸明确的描述,这条路线已经能跑出可用的结果;但对于含糊的描述,比如“一个好看的支架”,模型就会开始自由发挥,出来的东西往往不能用。
2.3 参数抽取加模板匹配:最笨但最可靠
第三条路线是我个人最推荐的,尤其是在你需要批量生成、需要稳定复现的场景下。它的思路是:不指望模型直接写出完整脚本,而是让它从文本里抽取出关键参数和特征类型,然后套用预先写好的参数化模板。
比如你有一个“法兰盘”的模板,模板里定义了外径、内径、螺栓孔数量、螺栓孔分布圆直径、厚度这些参数。用户输入“一个外径 100、内径 40、厚度 10、6 个螺栓孔的法兰盘”,系统只需要把数字和数量抽出来,填进模板,就能生成模型。这条路线的好处是可控性极强,模板是你自己写的,几何质量有保证,模型只需要做它擅长的事——理解语言和抽取信息。
三条路线的对比如下:
| 路线 | 几何精度 | 可修改性 | 实现难度 | 适用场景 |
|---|---|---|---|---|
| 直接生成网格 | 低 | 差 | 高 | 视觉参考、概念草图 |
| 生成 CAD 脚本 | 高 | 好 | 中 | 结构清晰的零件描述 |
| 参数抽取加模板 | 高 | 好 | 低 | 批量生成、标准件变体 |
我现在的做法是混合使用:先用参数抽取加模板处理大部分标准结构,遇到模板覆盖不了的形状,再退回到生成脚本的方式,并且加一层几何合法性检查。这样既保证了稳定性,又保留了一定的灵活性。
3. 跑通一条最小链路:从文本输入到可导出模型的关键组件
3.1 语言理解层:别指望模型一次就懂工程语义
语言理解层是整个链路的第一环,也是最容易出问题的一环。很多人以为直接把用户输入丢给一个大模型就行了,但实际跑下来会发现,模型对工程术语的理解远没有想象中可靠。比如“倒角”和“圆角”在很多语境下会被混用,“沉头孔”和“埋头孔”的区分也需要额外的知识注入。
我的做法是在语言理解层加一个“术语归一化”的步骤。具体来说,维护一个工程术语映射表,把用户可能用的各种说法映射到标准术语上。比如“打穿”“打通”“贯穿”都映射到“通孔”,“倒圆”“圆边”都映射到“圆角”。这个表不需要很大,覆盖常见的一两百个术语就够用了。然后在提示词里明确告诉模型:你是一个 CAD 参数抽取器,只输出结构化的参数,不要输出解释。
输出格式我建议用 JSON,因为它的结构清晰,后续解析不容易出错。一个典型的抽取结果长这样:
{ "shape_type": "flange", "parameters": { "outer_diameter": 100, "inner_diameter": 40, "thickness": 10, "bolt_hole_count": 6, "bolt_hole_diameter": 8, "bolt_circle_diameter": 80 }, "units": "mm" }这里有个细节:单位。很多用户不会主动说“毫米”,但 CAD 建模对单位极其敏感。我的处理方式是默认毫米,同时在提示词里让模型在检测到“厘米”“英寸”等词时做换算并标注。这个细节看起来小,但实际用起来能避免大量“模型尺寸差了 25.4 倍”的尴尬。
3.2 几何生成层:OpenSCAD 和 FreeCAD 脚本的取舍
几何生成层我主要用两个引擎:OpenSCAD 和 FreeCAD。OpenSCAD 的优势是语法简单、纯代码、容易程序化生成,缺点是它的几何内核在处理复杂布尔运算时性能一般,而且导出的格式有限。FreeCAD 的优势是几何内核更强,支持 STEP 导出,适合做工程交付,缺点是 API 比较庞杂,脚本写起来更啰嗦。
我一般的判断标准是:如果模型只是用来做视觉展示或者 3D 打印,OpenSCAD 足够;如果需要导出 STEP 做后续加工或者装配,就用 FreeCAD。下面是一个用 OpenSCAD 生成法兰盘的例子:
outer_dia = 100; inner_dia = 40; thickness = 10; bolt_count = 6; bolt_dia = 8; bolt_circle = 80; difference() { cylinder(d=outer_dia, h=thickness, center=true); cylinder(d=inner_dia, h=thickness+2, center=true); for (i = [0:bolt_count-1]) { rotate([0, 0, i * 360 / bolt_count]) translate([bolt_circle/2, 0, 0]) cylinder(d=bolt_dia, h=thickness+2, center=true); } }这段代码里有个小技巧:挖孔用的圆柱高度设成thickness+2,比主体高一点,这样布尔减运算不会因为浮点误差在表面留下薄薄一层残留。这个坑我踩过好几次,模型看起来没问题,但导出 STL 之后切片软件会报“非流形边”的警告。
3.3 校验与导出层:几何合法性检查不能省
模型生成出来之后,千万别直接导出就完事。我至少会做三项检查:第一,包围盒尺寸是否和输入参数一致,防止单位或者参数映射出错;第二,体积是否为正且合理,防止布尔运算把实体减没了;第三,是否存在零厚度面或者非流形边,这个可以用 trimesh 之类的库快速检测。
import trimesh mesh = trimesh.load("output.stl") print("包围盒:", mesh.bounds) print("体积:", mesh.volume) print("是否水密:", mesh.is_watertight)如果is_watertight返回 False,说明模型有破面,拿去打印或者做仿真都会出问题。这时候要么回到几何生成层调整参数,要么在导出前做一次修复。修复工具我常用的是trimesh自带的fill_holes和fix_normals,对于简单模型够用了。
导出格式的选择也有讲究。STL 最通用,但不带单位信息,很多软件默认按毫米读,也有按英寸读的,容易出岔子。STEP 带单位、带拓扑信息,适合工程流转,但文件更大,生成也更慢。我的习惯是:内部流转用 STEP,对外给 3D 打印用 STL,并且在文件名里把单位标清楚,比如flange_100mm.stl。
4. 实测中那些让人抓狂的边界情况
4.1 模糊描述:当用户说“大一点”的时候
文本转 CAD 最头疼的不是复杂模型,而是模糊描述。用户说“一个大概这么大的板子”,你没法知道“这么大”是多大。用户说“孔开大一点”,你也不知道是直径加 2 毫米还是加 5 毫米。这类输入如果直接丢给模型,它要么瞎猜一个数,要么反问用户,但反问在自动化流程里往往不可行。
我的处理策略是设置默认值和范围约束。比如对于“板子”这个形状,如果没有给尺寸,就默认 100x100x5 毫米;如果用户说“大一点”,就在默认值基础上加 20%。同时,在输出里标注哪些参数是推断出来的,方便用户后续修改。这个做法不完美,但比让模型自由发挥要可控得多。
还有一种情况是描述里包含相对关系,比如“孔的位置在板子中心偏左”。这种“偏左”没有量化,模型很难处理。我的做法是把它转成比例,比如“偏左”映射到 x 方向偏移 25% 的板宽。这个映射规则需要根据实际场景调,没有万能公式。
4.2 单位与精度:25.4 倍的教训
前面提过单位问题,这里展开说一下。我遇到过一次典型的翻车:用户输入“一个 2 英寸的立方体”,模型抽取参数时把 2 存了进去,但单位字段写的是毫米。结果生成出来是一个 2 毫米的立方体,小了 25.4 倍。这个错误在视觉上不明显,因为模型看起来就是个立方体,但尺寸完全不对。
后来我在流程里加了两道保险:第一,在参数抽取的提示词里明确要求模型检测单位词并做换算,统一输出毫米;第二,在几何生成前做一次尺寸合理性检查,如果某个维度小于 1 毫米或者大于 10000 毫米,就触发警告,让人工确认。这两道保险加下来,单位错误基本没再出现过。
精度问题也值得提一句。CAD 建模通常用双精度浮点数,但导出 STL 的时候会做三角化,精度会损失。对于大多数 3D 打印场景,默认精度够用;但如果模型里有很小的特征,比如 0.5 毫米的倒角,就需要在导出时调高精度,否则倒角可能直接消失。OpenSCAD 里可以用$fn参数控制圆弧的分段数,FreeCAD 里可以在导出时设置偏差值。
4.3 布尔运算失败:几何内核的脾气
布尔运算是 CAD 建模里最常用的操作,也是最容易出问题的操作。两个实体做差集,如果它们恰好共面,或者有极薄的相交区域,几何内核就可能算不出来,或者算出一个破面模型。这个问题在 OpenSCAD 和 FreeCAD 里都存在,只是表现方式不同。
我的经验是:尽量避免让两个实体的面完全重合。比如你要在一个板上挖一个和板等高的孔,不要把挖孔圆柱的高度设成和板厚完全一样,而是稍微高一点,让它在板的两侧都冒出来一点。这样布尔运算就不会遇到共面情况,成功率会高很多。这个技巧在前面法兰盘的代码里已经用到了,thickness+2就是这个目的。
如果还是失败,可以尝试把模型导出成网格,用网格布尔库来做,比如trimesh的布尔功能或者pymesh。网格布尔的精度不如实体布尔,但鲁棒性更好,不容易直接报错。代价是生成的模型不再是参数化实体,后续修改会麻烦一些。
5. 把 text-to-cad 用起来的几个实际场景
5.1 批量生成标准件变体做设计对比
做机械设计的时候,经常需要对比不同规格的标准件。比如你要选一个法兰盘,外径有 80、100、120 三种,螺栓孔有 4 个和 6 个两种,组合起来就是 6 个变体。手动建模要重复六次,用 text-to-cad 的话,你只需要写一个模板,然后批量替换参数就行。
import subprocess variants = [ {"outer": 80, "bolts": 4}, {"outer": 80, "bolts": 6}, {"outer": 100, "bolts": 4}, {"outer": 100, "bolts": 6}, {"outer": 120, "bolts": 4}, {"outer": 120, "bolts": 6}, ] for v in variants: scad_code = generate_flange_scad(v["outer"], v["bolts"]) with open(f"flange_{v['outer']}_{v['bolts']}.scad", "w") as f: f.write(scad_code) subprocess.run(["openscad", "-o", f"flange_{v['outer']}_{v['bolts']}.stl", f"flange_{v['outer']}_{v['bolts']}.scad"])这个流程跑下来,六种变体几分钟就能全部生成,而且尺寸精确一致,不会出现手动建模时“这个孔好像偏了 0.5 毫米”的问题。对于需要做参数扫描或者优化迭代的场景,这种批量生成能力非常实用。
5.2 为仿真和机器人场景快速搭建几何体
做机器人仿真或者物理仿真的时候,场景里需要大量的几何体:桌子、箱子、圆柱体、斜面等等。这些几何体不需要很精细,但需要尺寸合理、位置准确。用 text-to-cad 的思路,你可以用一段文本描述整个场景,然后自动生成所有几何体并摆好位置。
比如“一个 1.2 米长、0.6 米宽、0.75 米高的桌子,桌面上放一个 0.3 米边长的立方体,桌子旁边有一个直径 0.4 米、高 0.8 米的圆柱体”。这种描述用参数抽取加模板的方式很容易处理,生成的模型可以直接导入仿真环境。相比手动在仿真软件里拖拽调整,效率提升非常明显。
5.3 教学场景:让学生专注几何思维而不是软件操作
我在和一些做工程教育的朋友交流时,发现他们有一个共同的痛点:学生花在学软件上的时间太多,花在理解几何关系和工程约束上的时间太少。text-to-cad 在这个场景下可以做一个“几何思维训练器”:学生用自然语言描述他们想要的形状,系统生成模型,学生再检查生成的模型是否符合预期。
这个过程反过来会迫使学生把描述写得更精确。比如学生一开始写“一个带孔的板”,生成的模型可能孔在正中间;他如果想要孔在角落,就必须学会说“孔位于板的左上角,距离两边各 10 毫米”。这种“描述-生成-检查-修正”的循环,其实是在训练工程语言表达能力,这个能力在实际工作中比软件操作更重要。
6. 我踩过的坑和总结出的几条实操原则
第一个坑是过度信任模型的几何理解能力。我一开始觉得,既然模型能理解“一个红色的圆”,那理解“一个带倒角的圆柱”应该也不难。实际测试下来,模型对颜色、形状这些视觉概念确实敏感,但对“倒角”“沉头”“螺纹”这些工程特征的理解很不稳定。同一个描述,换个说法,生成的结果可能就差很多。所以后来我在提示词里加了大量工程术语的示例,并且用 few-shot 的方式让模型模仿。
第二个坑是忽略了导出格式的兼容性。有一次我生成了一批模型,导出成 STL 发给合作方,对方用某个软件打开后发现尺寸全乱了。排查了半天,发现是那个软件默认按英寸读 STL,而我的模型是按毫米建的。后来我养成了习惯:导出 STL 时在文件名里带单位,同时额外导出一份 STEP 作为参考。STEP 带单位信息,不会出现这种问题。
第三个坑是布尔运算的顺序。多个布尔操作叠加的时候,顺序会影响结果,也会影响计算成功率。我的经验是:先做加法(合并),再做减法(挖孔、切槽)。因为加法通常比减法稳定,先把主体形状合并好,再在上面做减运算,出问题的概率会低一些。如果反过来,先在一个小实体上挖孔,再和大实体合并,有时候孔会被合并操作“吃掉”。
第四个坑是参数默认值的选择。一开始我没设默认值,用户不给尺寸模型就瞎猜,结果五花八门。后来我给每个模板都设了合理的默认值,并且在输出里明确标注哪些是默认值。这样即使用户描述不完整,至少能生成一个尺寸合理的模型,用户可以在上面改,而不是从零开始。
几条我总结出来的原则:能用模板就不用自由生成,能抽参数就不让模型写代码,能导出 STEP 就不只导出 STL,能在生成后做校验就不要直接交付。这几条原则看起来保守,但实际用下来,它们能把 text-to-cad 的可用性从“玩具级别”提升到“能干活级别”。
最后分享一个我最近在用的调试技巧:把每次生成的输入文本、抽取的参数、生成的脚本、导出的模型都存到一个带时间戳的文件夹里。这样当某个模型出问题时,你可以回溯到具体的输入和中间结果,快速定位是语言理解错了、参数映射错了、还是几何生成错了。这个习惯帮我省了大量排查时间,尤其是在批量生成的时候,没有这套记录根本不知道是哪个环节出的问题。