第一次接触到 text-to-cad 这个概念时,我正被一批杂七杂八的机械零件建模需求搞得焦头烂额——一个连接头要改法兰尺寸,一个支架要换螺栓孔位,电话里对方描述得眉飞色舞,我却得一句句猜着画。后来我开始尝试让程序直接听懂文字,把“描述”变成“模型”。折腾了大半年,我算是把 text-to-cad 的几条主要路径摸了个大概。这篇文章不吹不黑,只聊我实际跑通、并且能落地的方案,以及踩过的坑。
先说结论:现阶段指望对着软件说一句“给我生成一个减速器”就能得到可直接加工的总成,还不太现实。但如果你把任务缩小到一个零件、一个特征明确的单体模型,text-to-cad 已经能大幅缩短建模时间。我目前的日常流程是:用大模型解析自然语言,把几何约束翻译成参数化代码,再用 OpenSCAD 或 CadQuery 生成可编辑的实体模型。这个流程不算酷,但很稳,我也从里面拿到了一批真实用在手工打样和 3D 打印上的零件。
1. 别被“一键生成”带偏:text-to-cad 的三条真实技术路线
text-to-cad 的概念本身不复杂:人类用自然语言描述一个三维物体,程序输出一个 CAD 模型。但“CAD 模型”这四个字藏着很多坑。它可能指一个纯显示的三角网格 STL,也可能指带特征树、可参数化修改的 STEP 或原生工程图。不同输出对技术路线的要求完全不一样。
1.1 端到端式的“黑盒”生成:听起来很美
这是很多文章最喜欢提的方向:训练一个神经网络,输入一句话“一把带纹理的椅子”,直接输出一段 SDF(符号距离场)或者体素网格,再通过后处理变成网格模型。这类方法的 demo 通常很有视觉冲击力,因为 AI 可以生成非常复杂的曲面和拓扑结构。但落到工程实践中,问题一串:生成的网格经常有空洞、非流形边、法向错乱,修都修不干净;模型没有参数化特征,你没法回去改一个孔的直径或一块板的厚度;输出通常是 OBJ/GLTF 而不是 STEP,加工端根本不愿意接收。
所以我把这条路线定性为“参考和灵感阶段”:拿来看造型完全可以,拿来出工程图基本没戏。除非你的目标只是做游戏资产或单纯可视化,否则别把它当成 text-to-cad 的主流答案。
1.2 代码生成式的“白盒”路径:我真正推荐的方向
另一种做法,是让大模型写代码。这里说的代码不是 C++ 或者 JavaScript,而是建模专用脚本——OpenSCAD 的 .scad 文件,或者 CadQuery、PythonOCC 这类基于 Python 的程序化建模代码。大模型本身并不直接“画”模型,它生成的是构建模型的三维布尔运算和拉伸、扫掠命令。你运行脚本,得到的是带特征历史(feature history)的实体,改一个参数就能重新生成。
这条路径是目前工程价值最高的。一方面,脚本天然可复现,你随时能 diff 两版代码看哪里变了;另一方面,模型自带参数块,后期改尺寸比传统 CAD 还要快。更重要的是,大模型对这类结构性语言的掌握效果出奇地好——OpenSCAD 语法简单、规则明确,GPT 类的模型很少写到大错;CadQuery 稍微复杂些,但只要给出良好示例,它也能稳定输出可用代码。
1.3 混合路线:自然语言调用参数化模板
补一条我自己经常用的“半成品”方案:事先在代码库里准备一批参数化模板——类似法兰、支架、齿轮、外壳这些常用件,把尺寸、孔数、圆角半径全部写成变量。再让大模型把自然语言映射到模板参数上,例如“四个孔,直径 6,法兰外径 70”对应到flange_template(d_out=70, hole_d=6, hole_count=4)。这不算严格意义的“生成”,但胜在可靠。对团队而言,这种方案能在需求量集中时快速交付,而且不需要 AI 理解太深的几何语义。
在这三条路线里,我后面讲到的实践集中在第二条,中间偶尔会用第三条兜底。因为它们都保留了“代码”这个中间层,出了问题能检查、能修正,不会让你面对一个无法解释的黑盒。
2. 我的主战装备:让大模型写 OpenSCAD 和 CadQuery
工欲善其事,必先利其器。在 text-to-cad 这场实验里,我最早尝试的是让大模型直接输出 STL 文件——它压根做不到,输出格式一塌糊涂。后来我把目标改成生成脚本,一下就通了。现在我的工具链以两个库为核心:OpenSCAD 和 CadQuery。
2.1 为什么是 OpenSCAD 而不是“更专业”的 CAD 二次开发
很多人觉得 OpenSCAD 太玩具,不够“工业级”。我的看法相反:正是因为 OpenSCAD 的语义简单、不依赖鼠标交互、所有东西都是纯代码,它才最适合当 text-to-cad 的“接口语言”。你让模型写 AutoLISP 或者 Revit API,它会因上下文过长而遗忘很多相关 API,写出来的代码经常调用了不存在的函数。而 OpenSCAD 的核心命令就那几个:cylinder、cube、translate、rotate、difference、union、hull。大模型训练语料里这类代码太多了,生成质量稳定,调试也快。
另一个关键点是 OpenSCAD 天然是参数化建模。你可以在文件头部定义flange_d = 80;pipe_d = 34;之类变量,后文的几何全部引用这些变量。大模型非常擅长维护这种依赖关系,这让用户后续改尺寸变得非常轻松。实际上,我很多零件是先让模型生成一个粗糙版本,再用修改变量的方式手工微调,不需要重新描述整个需求。
2.2 CadQuery:应付需要加工语义的复杂零件
OpenSCAD 擅长实体造型,但遇到需要复杂圆角过渡、倒角、抽壳、或者要求输出 STEP 带精确 B-Rep 边界的时候,就力不从心了。这时我把脚本生成目标切到 CadQuery。CadQuery 本质上是 Python 库,底层依赖 OpenCascade 内核,生成的是真正的 BREP 实体,能输出 STEP、IGES,也可以直接进入 CAM 软件做刀路。
CadQuery 的编写模式比 OpenSCAD 更接近“特征建模”思维:先在二维平面画草图,然后用extrude、cut、fillet这些特征操作生成三维实体。这种结构非常适合大模型理解,因为每一步都有一个明确情感的中文描述:“在顶面上开一个直径 10 的孔”“对底面进行 2mm 倒角”。我试过让大模型写 CadQuery 代码来还原一个工业传感器支架,它居然能自己处理好cq.Workplane的层级关系,虽然首次运行报了一点错,但整体结构没偏。
2.3 两套工具的选择标准
我大致按这个规则切分:简单钣金件、外壳、结构支架用 OpenSCAD,因为代码短、生成快、视觉直观;需要出图纸、做圆角过渡、要转 STEP 给加工方的零件用 CadQuery。如果两者都搞不定,那就退回参数化模板。
这里要提醒一个小问题:不要同时让模型在两种语言之间自由穿梭。我在早期没有指定输出语言,结果模型一会儿生成 Python 代码,一会儿生成 OpenSCAD 代码,我只能手忙脚乱地开两个环境。现在我会在 system prompt 里明确“你只能用 CadQuery 生成 Python 脚本”,终结混乱。这个习惯,建议你一开始就养成。
3. 实战:一句“带法兰的管道接头”变成可编辑模型的完整过程
理论说了一堆,现在来点真格的。我拿一个真实需求走一遍完整流程:朋友要做气动管路,需要一个带法兰的管道接头,法兰上四个螺栓孔,中间主通道通 24mm 内径的气管。他用微信语音描述完,我把语音转成文字,直接丢给 text-to-cad 流程。下面是全过程。
3.1 需求拆解与提示词模板
原始描述是:“一个带法兰的管道接头,法兰外径 80,厚度 10,法兰四周均匀四个直径 9 的安装孔,中心圆孔通径 24,主管道外径 34,长度 60,整体圆角过渡自然一点。”
这段文字已经有明确几何参数。但真实情况往往更模糊,比如“法兰外径 80”可能换成“差不多拳头大小”,这时我会先让大模型反向提问——它问我是不是毫米单位,我确认后,再补充缺失的约束。我发现一个有效做法:把提示词模板固定下来,让模型养成“先解构、再输出”的习惯。模板大约长这样:
你是一个机械CAD建模专家。根据下面的描述,生成OpenSCAD代码。 要求: - 使用毫米为单位; - 所有关键尺寸定义为参数变量,放到文件头部; - 必须使用实体布尔运算构建模型; - 不要输出STL,不要输出截图,只输出.scad脚本; - 如果描述存在歧义,先列出你的假设,再生成代码。这段提示词的前三句是核心:单位约定避免把英寸毫米混用;参数变量保证后期可改;布尔运算给出建模意图。后面两句是兜底,让大模型遇到“圆角自然一点”这种模糊词时,至少会用$fn和圆角半径给出默认值,而不是束手无策。
3.2 第一次生成的代码和解读
大模型给回来的 OpenSCAD 代码大约长这样:
$fn = 64; flange_d = 80; flange_t = 10; hole_d = 9; hole_radius = 31; pipe_d = 34; pipe_len = 60; bore_d = 24; module flange_pipe() { difference() { union() { // 法兰盘 cylinder(h = flange_t, d = flange_d); // 主管道 translate([0, 0, flange_t]) cylinder(h = pipe_len - flange_t, d = pipe_d); } // 四个安装孔 for (angle = [0:90:270]) { rotate([0, 0, angle]) translate([hole_radius, 0, 0]) cylinder(h = flange_t + 1, d = hole_d); } // 中心通孔 translate([0, 0, -1]) cylinder(h = pipe_len + 2, d = bore_d); } } flange_pipe();第一眼看上去,结构是合理的:法兰盘和管道先合并,再减去安装孔和中心通孔。不过细看问题不少——四个安装孔是从 0 度开始均匀分布的,这个没问题;但hole_radius = 31是怎么来的?想象一下,法兰外径 80,螺栓孔中心圆外径大约要在法兰半径 40 之内,留 9mm 边距,31 是合理的。这里模型其实是自己做了几何推理,而不是抄了一个固定数字。这个细节让我挺意外。
3.3 从能渲染到能出加工图:试错与修正
我把脚本丢进 OpenSCAD,按 F5 预览,模型外观看着正常,但按 F6 完整渲染后我发现了问题:中心通孔和安装孔都伸过了法兰底部,底面上会留下孔洞边缘的脏面。为什么?因为安装孔用一个很长的圆柱做布尔减,却只把法兰部分穿透了,底面的“开口”是自然的,这不影响加工,但如果你想要台阶孔或者沉头孔,就必须额外操作。
真正让我恼火的是第二个问题:模型没有标注倒角,而原描述中“圆角过渡自然一点”被大模型直接忽略了。我重新带上提示词:“在所有外露锐边加 1.5mm 倒角,用$fn=32控制圆弧精细度。”第二次生成的代码里加了好几个chamfer相关操作,但 OpenSCAD 原生其实没有 chamfer 命令,它只能靠旋转扫掠一个多边形来模拟,代码变得很长且容易出错。
这时候我做了个决定:把这个零件切换到 CadQuery 重写。因为 CadQuery 内置.edges().chamfer()和.fillet()方法,处理倒角干净利落。生成的 CadQuery 代码大概是这样:
import cadquery as cq flange_d = 80 flange_t = 10 hole_d = 9 hole_radius = 31 pipe_d = 34 pipe_len = 60 bore_d = 24 result = ( cq.Workplane("XY") .circle(flange_d / 2) .extrude(flange_t) .faces(">Z").workplane() .circle(pipe_d / 2) .extrude(pipe_len - flange_t) .faces(">Z").workplane() .hole(bore_d, height=pipe_len) .faces(">Z").workplane() .pushPoints([(hole_radius, 0), (0, hole_radius), (-hole_radius, 0), (0, -hole_radius)]) .hole(hole_d, height=flange_t + 1) .faces(">Z").edges().fillet(1.5) .faces(">Z").workplane().edges().chamfer(1.0) )这段代码一次跑通。CadQuery 的优势在这里体现得很明显:.hole()直接生成穿通孔,.faces(">Z").edges()可以准确选中顶部边缘做倒角。大模型对 CadQuery 的 API 记忆有时候会出现“幻觉”——会编出不存在的方法名。但只要你把示例代码和当前版本号写进提示词,稳定性会好很多。我也是在那个时刻真正意识到:text-to-cad 的价值不只是“生成一个看起来像的模型”,而是让你能用几十行代码,把几何语义准确表达出来,还为后续修改留下极大的空间。
4. 让模型输出从“看着像”变成“能加工”:必须处理的六个细节
demo 阶段没人关心模型质量,但一落到实际加工,很多坑就冒出来了。我总结了自己反复踩过的六个细节,逐个列出来,每一条背后都有真实教训。
4.1 单位与坐标系:别看小,错一次就完蛋
我接到的第一个 text-to-cad 任务是生成一个 80mm 的固定座。大模型默认把“80”当成英寸,生成结果是 2032mm。幸运的是我在切片软件里发现了尺寸离谱,否则打出来直接浪费几卷料。你需要确保每一版提示词里都有“使用毫米、Z 轴向上、原点在底面中心”这三个约定。对 CadQuery 来说,默认的坐标平面是 XY,挤出方向是 Z 正半轴。OpenSCAD 的默认坐标系同样是右手系,Z 向上。如果模型输出的是 DXF 草图,还要额外确认是 XY 平面还是 XZ 平面。
4.2 水密性与法向:网格检查不能跳过
哪怕你用的代码生成路线,最终导出 STL 时依然可能踩到非流形网格。常见原因包括:两个圆柱表面刚好相切,导致布尔运算产生退化边;小圆角与平面相交处出现自交面;大模型生成的布尔差集顺序不合理,留下一层零厚度的薄壁。每次导出后,我会用 MeshLab 做一次“检查流形、修复法向”的操作。虽然不能 100% 解决,但能提前暴露绝大多数问题。如果问题反复出现,更好的办法是直接在 CadQuery 里检查result.val().isValid()——这个方法会检查实体是否符合 B-Rep 有效性,比事后修网格靠谱得多。
4.3 布尔运算和相交几何:别让模型“创意发挥”
大模型特别喜欢自由地给圆角、倒角、或者斜切加一些你根本没要求的东西,原因在于它的训练数据中那些机械零件的图片和模型常常有美观的过渡。但在加工中,随意添加圆角会导致后续开孔位置偏离,或者使得两段几何产生非预期相交。比如我试过让模型生成“一个带U型槽的底座”,它自己给槽底部加了一个 R3 圆角,结果原本打算用端铣刀直接一刀过的工艺,变成了需要球头刀的额外工序。所以我的经验是:在第一轮生成时明确“禁止添加任何未指定的圆角或倒角,除非描述中明确要求”,把变量留给后续手工调整。
4.4 最小特征尺寸:要匹配加工方式
与模型对话时,大多数人不会意识到加工工艺带来的尺寸下限。3D 打印的最小壁厚通常 0.8mm,CNC 铣削的内圆角受刀具半径限制,激光切割的缝隙不小于板厚。大模型不一定知道你的工艺条件,有时会生成 0.3mm 的壁厚,打印出来就是一层纱。我会在提示词里主动加上“壁厚不小于 2mm,孔直径不小于 3mm,圆角半径不小于 1mm”,并用后续参数变量去控制。如果直接生成 STL 网格,这一步几乎没法救,所以这也是我坚持使用参数化脚本的另一个原因——可以把最小尺寸当成约束写进代码里。
4.5 特征命名和可维护性
好的 text-to-cad 输出不只是一段能跑的代码,它应该具备可读性。我给自己定了个规矩:要求大模型给每个关键特征单独建一个 module(OpenSCAD)或方法(CadQuery),并给核心尺寸起有意义的名字。比如上面的管道接头,代码里用flange_t而不是t1、pipe_len而不是h2。这样做有两个好处:第一,后续跟同事协作时,别人能快速看懂模型里哪个变量控制哪段几何;第二,大模型自己读回去的时候也能准确理解,方便迭代修改。这个习惯帮了大忙,有次我拿到三个月前生成的脚本,看着变量名就能想起当初的设计意图。
4.6 参数化档位:从“生成一次”到“复用一百次”
做 text-to-cad 时,不少人的目标是“生成一个模型”,但我更看重“能不能衍生一族模型”。一个法兰接头,只要改几个变量就能变成不同管径的系列规格。每次生成后,我会把变量提取到文件头部,用表格列出默认值。下次再遇到类似需求,我先让大模型参考旧代码生成新版本,而不是从零再来。这不是偷懒,而是实际工程中大量零件都是系列化、规格化的机械设计,参数化模型让“文字改一版”变得像填表一样容易。
5. 搭建属于自己的 text-to-cad 工作流:自动化与更多思路
当模型生成质量稳定后,我开始琢磨怎么把这套东西从“手工复制粘贴”升级成“一键自动化”。目前我搭建的流程是一个 Python 脚本加三个子模块,跑一个命令就能完成从文本到最终模型导出。
5.1 用 Python 把 LLM 调用、代码生成和预览渲染串起来
我写了一个text2cad.py,流程大致是:
- 读取一个
.txt文件里的需求描述。 - 把固定的 system prompt 拼上用户描述,调用大模型 API。
- 从返回的字符串里提取第一个代码块,保存为
.scad或.py。 - 如果是 OpenSCAD,用命令行渲染出 PNG 预览图和 STL;如果是 CadQuery,直接执行 Python 脚本并导出 STEP。
- 把预览图发到企业 IM 群里,等人工确认后再进入下一步。
关键点在于第 3 步的代码块提取——大模型偶尔会啰嗦,在代码前后加上解释文本,所以必须用正则或简单字符串匹配,找到第一个 ``` 标记之间的内容。我一开始图省事直接全员拼接,结果把说明文字也拼进代码里,OpenSCAD 直接报语法错误。后来换了一个库做代码块截取,问题就解决了。
import re def extract_code(response: str) -> str: match = re.search(r"```(?:openscad|python)?\n(.*?)```", response, re.DOTALL) if not match: raise ValueError("No code block found") return match.group(1).strip()这个函数看起来简单,但救了我无数次。如果你的模型 API 返回的是 JSON,记得先把转义字符还原,再走正则。不然一个小反斜杠就会让\n变成真换行,严重破坏代码缩进。
5.2 提示词工程里我踩过的最深的坑
我用的第一个提示词很朴素,就一句话:“用 OpenSCAD 画一个带孔的板子。”结果模型生成了一堆飘在空中的圆环,或者左一块右一块的碎片。后来我意识到,问题在于模型缺少“空间参考”——它不知道板子应该平行于哪个平面、孔应该垂直于哪个面。
解决办法是给模型一个“几何锚点”约定:所有模型在建模空间里,底面放在 XY 平面上,主特征沿 Z 轴方向构建。这个约定对 OpenSCAD 和 CadQuery 都有效。我把这句话写死在 system prompt 里,生成模型的可制造性立刻提升一个档次。更神奇的是,当大模型遵循这个约定后,后续加尺寸约束时它很少跑偏,因为所有特征都有了统一的坐标基准。
另一个坑是:大模型对“内孔直径”和“螺栓孔径”的称呼很容易混淆。比如“直径 9 的孔”它可能生成d=9/2(当成了半径),我检查过好几次才发现出图尺寸差了一倍。这个问题的根因是 OpenSCAD 的参数名用d表示直径,用r表示半径,CadQuery 的circle()默认接受半径。我在提示词里明确写明“如果描述尺寸为直径,使用参数d=...;所有circle函数传入半径时,注释中标注// r = d/2”之后,这类错误大幅减少。
5.3 从“单件生成”到“装配体协作”的探索
text-to-cad 做到单件可靠之后,我开始尝试多零件装配。目前思路是先把整机拆成一系列零件描述,逐一生成,再用一个 Python 脚本布置装配位置,输出一个带有装配约束信息的 JSON。CadQuery 本身支持多实体,也能用cq.Assembly把零件拼在一起。但老实说,这一步还不够成熟——大模型很难一次理解多个零件之间的配合关系,比如轴和孔的过盈/间隙,它经常算错直径差。
我的替代方案是:先用 text-to-cad 生成每个零件的毛坯,再人工在 CadQuery 里加配合尺寸。这样做已经能省掉一半的初始建模时间。如果你也想尝试多零件生成,建议把“零件 A 的孔径比零件 B 的轴径大 0.05mm”这类间隙关系直接写进描述,不要留给模型自由发挥。
5.4 免费工具链的搭配推荐
最后给你一份我在用的免费工具链清单,全部可本地运行,不需要关注任何商业账号或者线上服务。
| 用途 | 工具 | 说明 |
|---|---|---|
| 代码生成 | 任意大模型 API 或本地模型 | 推荐系统提示词固定,不要频繁切换模型版本 |
| OpenSCAD 渲染 | OpenSCAD 命令行 | openscad -o out.png -p out.json可做参数化批处理 |
| CadQuery 建模 | cadquery Python 库 | 需要 Python 3.9+,安装 OpenCascade 内核 |
| 网格检查 | MeshLab | 检查流形、法向、修复细小瑕疵 |
| 快速测量 | FreeCAD | 打开 STEP 做简单测量与截面分析 |
这套组合的成本为零,但覆盖了我 90% 的日常需求。如果你有条件,也可以添一个付费的在线审图工具,但并不是必须。
实际用下来的感受是:text-to-cad 不会取代 CAD 工程师,但它能把工程师从重复的“描述-画图-改尺寸”循环里解放出来。我目前最爽的用法是把常用件模板交给大模型,然后用自然语言直接修改参数:“把法兰厚度改成 12,四孔改成六孔均匀分布。”这种对话式迭代,比在传统 CAD 里点十几次鼠标爽太多。不过你也别指望模型一次就给你完美结果,给自己留一个“代码审查”环节,才是让这套流程真正落地的前提。
最后分享一个实用小技巧:如果你在提示词里写“请先列出 3 条关于这个模型的假设,然后再生成代码”,模型的输出质量会明显上升,因为它被迫先审视自己有没有理解错需求。这个技巧我屡试不爽,推荐给你。