1. 先搞清楚text-to-cad是干什么的:需求翻译器,不是设计终结者
最近我花了不少时间折腾text-to-cad方向的实践,越跑越觉得这个领域被很多人误解了。如果你以为text-to-cad就是对着屏幕说一句“给我画个零件”,电脑就直接吐出一个能开模的完整模型,那大概率会失望。但换个角度理解——把产品需求快速转译成可编辑的三维参数模型——它现在就已经能在概念设计阶段帮我省掉大量重复劳动。
我身边有做结构设计的朋友,听到text-to-cad的第一反应是“CAD要完蛋了”。我每次都直接泼冷水:恰恰相反,它最合理的定位是给CAD打工。一个真实的场景是:工厂客户发来一段文字需求,说“需要一块安装板,八十乘四十,厚两毫米,四角打孔,中间开个大孔,左上角加个定位凸台”。这种描述在机械设计里太常见了,以前你得手动建工作平面、拉伸、定位孔位,没有三五分钟下不来。如果需求改三版,你就得跟着改三遍模型。text-to-cad真正解决的,正是这种“从自然语言到几何语义”的翻译成本。
1.1 概念设计与“给需求做个形状”之间的断层
我们不妨拆开看:设计师拿到一段需求文字时,脑子里发生的事情并不只有“画一个形状”。先要做语义解析——哪些词是尺寸,哪些词是特征,哪些词是位置关系;然后要做坐标规划——底板中心放在哪,孔距边缘留多少;接着要做特征顺序——先拉伸主体,再打孔,再添加凸台;最后还要考虑加工合理性——孔不能太小、壁厚不能过薄。
传统CAD把人变成了翻译器,你要把脑子里的几何规划,一步步转成软件能懂的建模命令。text-to-cad的意义在于把这一层交给大语言模型加参数化内核去完成。注意,这里不是“替代设计师”,而是把设计师从重复的拉伸-挖孔-倒角中解放出来,放到更高层的结构规划和方案评估上。就像你不必自己写每一行SQL才能查数据库,但你还是得知道自己要查什么数据。
1.2 谁最需要text-to-cad,谁不需要它
从我自己的实践来看,下面几类人最值得关心这个方向:
- 机械结构工程师:经常处理标准件、钣金支架、安装板、外壳开孔这类“特征规律明显”的零件,把文字需求变成STEP文件是刚需。
- 非专业建模人员:比如项目经理、采购、供应商,他们需要一种方式来快速表达自己的需求,让建模的人拿到一个“能看的初稿”,而不是只靠语言空对空地描述。
- 自动化设计与批量生成场景:一套产品参数有成百上千个变体,你对每个变体手动建模会崩溃,但写一个模板、让text-to-cad按参数生成,效率是量级提升。
反过来,如果你天天处理的是自由曲面、汽车A级曲面、复杂装配体之间的运动约束,那text-to-cad目前基本帮不上忙。它不是设计终结者,而是重复建模环节的自动化助手。把预期摆正了,后面踩坑就少一半。
1.3 别用它当CAD的替代品:能做什么不能做什么
我见过最典型的错误用法是:让模型生成一个“漂亮的雕塑造型”,然后希望拿去3D打印。结果不是结构自相交,就是壁厚不均匀,打印出来一碰就碎。text-to-cad擅长的是“参数清晰、特征明确、规则性强”的工程零件,不擅长“玄学造型”。同样是生成一个零件,你说“直径为20的轴,开一个5毫米的键槽,两端倒角”,它生成出来的东西基本一次就能用;你说“设计一个有机形态的灯具外壳”,它大概率给你一堆没法制造的网格,你还不如用细分建模软件自己拉。
所以,我总结它的能力边界就一句话:把有约束、有尺寸、有特征的文字需求,变成有参数语义、可继续编辑的实体模型。这篇文章后面所有内容,都是围绕这一句话展开的。
2. 从一句话到三维实体:这条技术链路为什么非要走代码中间层
很多人以为text-to-cad是“文本直接变成3D模型”,实际链路并不是一步到位的。我在本地搭过完整流程,它的本质是:文本 → 中间代码 → CAD内核求值 → 实体模型。中间代码这一步看起来多绕了一道,但恰恰是整个方案可控性的关键。
2.1 为什么不让大模型直接生成网格文件
听到“生成三维模型”,很多人第一反应是让模型输出一个.stl网格。但你去查一下就明白,网格由成千上万个三角形顶点组成,大语言模型本质上是在做概率化的符号序列生成,让它逐个顶点去猜坐标,生成的网格基本不可用。更重要的是,就算你运气好生成了一堆还算封闭的三角面,这个模型也没有“语义”——它是直径20的孔还是直径19.8的孔?孔心距多少?壁厚多少?这些信息全部丢失了。
网格模型最大的问题还不是精度,而是不可编辑、不可参数化。你想把孔从20改成25,如果拿到的是STL网格,你得重新挖一遍,或者整个重新生成。CAD世界里真正值钱的从来不是那堆三角形,而是尺寸、约束、特征树、参数关系。这也是为什么工程向的text-to-cad产品不约而同选择了代码中间层。
2.2 CadQuery与OpenSCAD:作为中间语言的合理性
代码中间层里,我接触最多的两个是CadQuery和OpenSCAD。CadQuery基于OpenCASCADE(OCCT)内核,属于B-Rep(边界表示法)建模,生成的模型是工业标准的实体,能导出STEP格式,后续放进FreeCAD、SolidWorks、Onshape都能继续编辑,这对我来说是最重要的。OpenSCAD则走CSG(构造实体几何)路线,用布尔运算组合基本体,它的语法更接近声明式描述,对大模型来说更容易生成。
为什么选代码而不是JSON或者某种专门的建模指令集?因为代码本身就是可执行的参数化描述。比如cq.Workplane("XY").box(80, 40, 2)这一行,不只是生成一个80×40×2的长方体,它还保留了“长度80、宽度40、高度2”这些参数。你在代码里把box(80, 40, 2)改成box(90, 40, 2),整个模型重新求值就变了,这是任何网格文件都做不到的。
2.3 完整链路里每个环节各自负责什么
我把自己搭建的链路拆成四段,每一段只有一个职责,出了问题也容易定位:
- 大语言模型:负责语义理解与代码生成。输入一段自然语言,输出符合CadQuery语法、能在受限环境里直接执行的Python代码。这步的质量决定了后面的一切。
- 代码解析与安全校验:负责过滤LLM输出中的意外内容。去掉Markdown标记、检查Python语法、限制可调用的模块,避免模型偶然生成一个爆炸性的文件操作指令。
- CAD内核求值:CadQuery把生成的代码翻译成真正的B-Rep实体,执行拉伸、打孔、倒角、布尔运算,得到一个可以量测体积、检查包围盒的几何对象。
- 导出与后处理:把实体导出成STEP或STL,喂给本地查看器预览,或者直接丢进下游制造流程。
这四条链路里,第二步是我自己写的最重要的一环。LLM天然是个“概率世界模拟器”,它经常会给你看起来完全正确、但一运行就崩的代码。做好拦截和兜底,是text-to-cad能不能落地的分水岭。下一章我会把市面上的工具挨个聊一遍,再讲我为什么最终选择自建链路而不是直接用现成产品。
3. 现有工具实测印象:Zoo.dev、BlenderGPT、Spline AI与开源路线
网上搜text-to-cad,能搜到一大堆产品,但真正动手试过之后你会发现,它们的目标用户、输出格式、工程可用性差异非常大。我花了两周时间把几条主流路线都跑了一遍,这里直接说实测印象。
3.1 Zoo.dev Text-to-CAD:工程向的代码转STEP路线
Zoo.dev的Text-to-CAD是市面上最接近“工程师可用”的方案之一。它的思路是把LLM生成的CadQuery代码放到云端执行,然后返回一个可以在浏览器里预览的模型,同时提供STEP下载。我实际跑下来,对简单机械件——比如带孔安装板、轴承座、法兰盘——生成成功率比较高,而且因为输出是CadQuery代码,你可以拿到代码再看一遍,确认孔位、尺寸有没有跑偏。
它的优势是交互层做得省心,在线编辑器里可以反复修改提示词重新生成,也保留了代码查看和手工微调的能力。不过它毕竟是一个托管服务,我对它最大的顾虑是:代码执行环境是封闭的,遇到想改内核参数、想挂自己的后处理脚本时,灵活性不够。如果你是个人工程师,想快速验证text-to-cad是不是靠谱,我建议先拿Zoo.dev跑几个零件试试,找一找“文字转工程模型”的感觉,再决定要不要自建。
3.2 BlenderGPT与Spline AI:更偏可视化表达而非制造
BlenderGPT的思路是用LLM生成Blender Python脚本,直接在Blender里构建场景和网格。这套方案对3D视觉设计师来说很友好,因为Blender的脚本API能控制材质、灯光、动画,生成结果的呈现效果很直观。但Blender毕竟不是CAD软件,它生成的模型本质上是多边形网格,没有参数化特征树,也没有工业级公差概念。我试过让它生成一个简单的齿轮,看起来像个齿轮,但你没法知道模数是多少、压力角是多少,更别提导出STEP继续做装配分析了。
Spline AI又是另一个方向,它面向的是UI设计师和交互原型制作者,能在网页端生成可交互的三维场景,拖拽一下就改形态,上手门槛很低。但这套生态离工程制造更远,生成的模型基本没法用来做结构仿真或机加工。我个人的结论是:这两个工具适合做“视觉表达”和“快速原型展示”,不适合做“制造意图表达”,如果你需要的是能开模、能装配、能仿真的模型,还是得走CadQuery/OpenSCAD这类参数化路线。
3.3 开源路线怎么选:LLM的差异比工具差异更大
我最终选择自建,本质原因是想跑一批批量生成的实验——几百个不同尺寸的钣金支架,每个还要按规格命名、按目录归档。现成产品做不到这种批量自动化,而自建一条“LLM + CadQuery”的管线,可以完全按自己的需求定制。
在开源的LLM选择上,我建议你根据硬件和精度要求来取舍。我比较过几档:参数小于7B的小模型,生成的CadQuery代码基本只能处理最简单的box加hole,稍微复杂一点就出语法错;14B到32B级别的模型,能处理中等复杂度的零件,偶尔需要人工修正;如果精度优先、不差那点接口费用,直接调用大厂API模型,成功率会明显高一截。不要神化某一个小模型,也不要以为API模型是万能的——我后面会专门讲,代码解析层做得好不好,有时候比模型选哪个影响更大。
4. 自己搭一个最小可用的text-to-cad服务:从提示词到STEP文件
下面这部分是我自己一直在用的那套最小链路。跟着做一遍,你就能在自己电脑上,把一段中文需求变成一个STEP文件。这里我默认你熟悉Python基础,并且本机已经装好了Python 3.11+。
4.1 环境与选型依据:本地小模型和云端大模型怎么权衡
我先说选型。我跑通的最小链路用的是Ollama加本地模型,原因有两个:一是本地执行不需要考虑数据外发,适合处理一些内部图纸相关的描述;二是批量生成几十上百个零件时,接口费用会是个不可忽视的成本,本地模型只要跑得动,生成多少次都不花钱。
本地模型我目前推荐从qwen2.5-coder:7b或者llama3.1:8b开始试。它们生成CadQuery代码的能力,在我测过的几个开源模型里属于比较稳的,但如果你手头有32B的显存,优先上32B版本,复杂提示词的生成成功率会明显上升。云端API模型,我推荐用成熟的多模态大模型,它们在指令跟随和格式控制上好很多。如果你不是硬性要求数据不出内网,我建议第一版就直接用API模型,先把整条链路调通,再回来调本地模型。
- 安装Ollama后,拉取模型:
ollama pull qwen2.5-coder:7b - 确认CadQuery能正常导入:
pip install cadquery - 用FastAPI做服务层,方便后续通过HTTP调用,也方便接内部工具。
4.2 提示词模板:把约束说全,比把需求说满更重要
这是我最想强调的一点。给LLM的提示词,关键不在于把需求描述得多“像人话”,而在于把输出格式和约束规则钉死。我实测下来,如果不加约束,模型会给你返回一段带解释的代码块,甚至返回一个print调试语句,写死了也可以把执行环境炸掉。我长期在用的系统提示词长这样:
你是一个专业的CAD脚本生成器。你只输出CadQuery 2.x版本的Python代码,不输出解释、不输出markdown代码块标记。 规则: 1. 单位一律是毫米。 2. 使用`import cadquery as cq`,通过`result = cq.Workplane("XY").xxx().yyy()`构建模型,最后只输出`result`变量。 3. 特征顺序:先建主体,再用hole()开孔、cutBlind()切除、box()添加凸台。 4. 当需求中有冲突(比如尺寸自相矛盾、孔径大于板宽)时,在代码第一行注释中说明冲突点,并按“先保证几何不自交、再保证语义正确”的优先级执行。 5. 执行环境里预置了cq,但你仍然要显式import。这套提示词的思路是:与其期望模型“理解”业务,不如强制它“遵守契约”。让它把冲突写进注释而不是自作主张瞎编,这个细节能救你很多次。很多冲突模型其实知道,但它一旦按自己的理解偷偷改掉,你看着输出的模型不对,还要从头排查,反而更费时间。
4.3 后端解析与校验代码:授权它出错,但拦住它乱跑
模型返回的代码不能直接exec。我写了一个很小的解析层,先过滤掉Markdown标记,用ast做语法检查,再放进一个白名单命名空间里执行。注意__builtins__那里一定要限制,否则模型哪次突然生成一个os.remove,你的文件夹就安静了。
import ast from pathlib import Path import cadquery as cq from cadquery import exporters def extract_python(raw: str) -> str: text = raw.strip() if text.startswith("```"): parts = text.split("```") for part in parts: if part.startswith("python\n") or part.startswith("python"): return part.replace("python", "", 1).strip() raise ValueError("没有找到python代码块") return text def safe_build(raw: str, work_dir: Path): code = extract_python(raw) tree = ast.parse(code) # 语法检查,能拦住一半问题 namespace = {"cq": cq, "Workplane": cq.Workplane, "__builtins__": {}} exec(compile(tree, "<generated>", "exec"), namespace) result = namespace["result"] if not isinstance(result, cq.Workplane): raise TypeError("LLM没有输出Workplane对象,实际类型是" + str(type(result))) # 自动校验包围盒,和需求尺寸对照 bb = result.val().BoundingBox() print(f"包围盒: {bb.xlen:.2f} x {bb.ylen:.2f} x {bb.zlen:.2f} mm") exporters.export(result, str(work_dir / "output.step")) exporters.export(result, str(work_dir / "output.stl")) return result执行完之后再算一个包围盒,这一步非常重要。比如你要的是80×40×2的板,代码执行完算出包围盒是800×40×2,说明模型很可能是把单位理解错了,直接就能发现,不用等拿到STEP再量。
4.4 用curl跑一个真实例子:安装板带孔与凸台
我用一个最常见的例子测全流程。输入这段中文:
生成一个80×40×2mm的安装板,四角有直径4.2mm通孔,中心有直径20mm通孔,左上角有一个10×10×5mm的定位凸台。
在我本地的7B模型下,生成出来的代码大致是这个样子:
import cadquery as cq result = ( cq.Workplane("XY") .box(80, 40, 2) .faces(">Z").workplane() .rect(64, 24, forConstruction=True) .vertices() .hole(4.2) .faces(">Z").workplane() .center(0, 0) .hole(20) .faces(">Z").workplane() .moveTo(-35, 15) .box(10, 10, 5) )这段代码我解释一下。box(80, 40, 2)建立主体,坐标系默认在中心;faces(">Z").workplane()把工作平面移到顶面;rect(64, 24, forConstruction=True)建立辅助矩形,四角和板边缘留出8毫米边距;.vertices().hole(4.2)在四个角点打出直径4.2的孔。中点那个hole(20)就是中心通孔。最后moveTo(-35, 15)把坐标移到左上角,放一个10×10×5的凸台。代码不算复杂,但它能稳定输出一个可编辑STEP,这就是最基础的价值。
不过一个小模型一次就成功不代表次次成功,我给API模型同样一段描述,偶尔会出现把凸台生成在四个角、或者把通孔画成盲孔的情况。这就说明后面校验环节不能省。
4.5 接入FastAPI把能力暴露成服务
本地脚本调试通过之后,我顺手包了一层FastAPI。这个不是炫技,是因为批量生成的时候,你不可能手工跑一遍脚本,得让上游的工单系统直接调接口。
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import ollama app = FastAPI() class CadRequest(BaseModel): prompt: str def llm_complete(prompt: str) -> str: response = ollama.chat( model="qwen2.5-coder:7b", messages=[ {"role": "system", "content": TEXT_TO_CAD_SYSTEM_PROMPT}, {"role": "user", "content": prompt}, ], ) return response["message"]["content"] @app.post("/generate") def generate_text_to_cad(req: CadRequest): from pathlib import Path raw = llm_complete(req.prompt) work_dir = Path("/tmp/text2cad") work_dir.mkdir(exist_ok=True) try: result = safe_build(raw, work_dir) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) return {"status": "ok", "step": str(work_dir / "output.step"), "stl": str(work_dir / "output.stl")}然后一条curl就能调:
curl -X POST http://localhost:8000/generate \ -H "Content-Type: application/json" \ -d '{"prompt":"生成一个80×40×2mm的安装板,四角有直径4.2mm通孔,中心有直径20mm通孔,左上角有一个10×10×5mm的定位凸台"}'到我写这篇文章的时候,这套服务在我本地已经稳定跑了三个多月,中间也给同事的内部小工具提供过接口。它不算什么大工程,但“文本到STEP”这个最核心的闭环算是打通了。
5. 跑通不代表能用:几何校验、单位陷阱与代码生成翻车清单
如果你以为上面那段代码能跑起来就万事大吉,那后面这些坑迟早会找上你。我用三个月的时间把这些坑基本踩了一遍,下面按“危害程度”排序。
5.1 单位混乱:看起来微小,翻车最狠
这是我一上来就栽的坑。提示词里写“直径20mm”,模型可能生成circle(20),在CadQuery里hole(20)确实代表直径20,但circle(20)在某些语境下代表半径20。更常见的是,模型会把“2毫米”理解成2英寸,生成一个接近51毫米的厚板,你光看渲染图根本看不出比例问题,只有量包围盒时才发现差了25.4倍。
我建议在提示词第1条强制固定“单位一律是毫米”,然后在后端校验里加一句:对包围盒的数值范围做合理性检查。比如你要求最大尺寸在100毫米以内,生成结果超过1000毫米,直接判定为失败。单位错误是结构性错误,不是改个参数就能蒙混过关的。
5.2 布尔运算和拓扑自交:CAD内核帮你兜底但有限
CadQuery底层是OpenCASCADE内核,布尔运算比大多数网格软件可靠得多。但你拿到的需求经常不像它想的那么简单,一个典型的失败案例是:模型在同一个面上连续执行多次cutBlind,切的深度公差稍微不对,结果生成了自相交面或者非流形边。流形是三维模型能被制造的基础要求,非流形模型在切片软件里就是一出戏。
我的经验是:让LLM尽量用hole()而不是手动cut再rect。hole()在CadQuery里会自动沿着法向打通,处理厚板的时候特别省心。只要提示词里定义了“主体建好后再统一打孔”的特征顺序,就能避开一大批自相交问题。
5.3 生成代码的“优雅无效输出”问题
这是最磨人的一种失败:模型输出的代码语法完全正确,也能跑通,但生成出来的不是你想要的东西。比如“四个角各打一个孔”被理解成“在四个角各加一根柱子”,“中心开孔”被理解成“中心画个圆不切除”。CadQuery的:faces(">Z").workplane().circle(20)画出来的只是一个线框圆,不cut也不hole,模型看代码觉得没问题,但导出STL之后你才发现多了一根线。
根治方案只有一个:加一个语义检查的中间层。我目前的做法是让模型输出JSON格式的“特征清单”,里面有特征类型、位置、尺寸,后端先校验这个清单是否符合需求,再执行代码。如果你的量没有大到要写完整校验器,那至少让提示词要求“每个hole旁边注明直径,每个box旁边注明长宽高”,人工拿到STEP后还能快速对照。
5.4 输出质量的自动检查:体积、包围盒与特征数
我批量跑模型时,把自动检查做成了强制门槛。每次生成结束后,后端至少要做三件事:
- 用
BoundingBox()量包围盒,和需求尺寸做比例比较,误差超过5%直接标红。 - 用
val().Volume()算体积,和理论体积做对比。比如一个80×40×2的板,理论体积是6400立方毫米,加上孔洞和凸台会有偏差,但偏差范围应该在一个量级以内。 - 检查特征数量,比如需求里写了4个孔,就在代码里数
hole出现的次数,不一致就拒绝。
这三关都过了,模型才会进到导出环节。批量生成几百个零件时,这种自动检查能挡住80%以上的低级错误。
5.5 需求描述里的隐性冲突怎么处理
文字需求经常自带冲突,比如“板宽40毫米,但四角孔之间距离要60毫米”,这种需求物理上就不成立。一开始我让LLM直接忽略矛盾,后来发现不行——它可能悄悄把板宽改成60,你自己没发现,到装配阶段就出大问题。
现在我的提示词明确要求:遇到冲突必须修改需求,而是在代码第一行写注释说明。后端看到注释就会在返回结果里挂一个warning,由流程上游的人做决策。这个细节的价值是:让每一个错误都可追溯。几分钟的“试探性建模”不怕错,怕的是错了你不知道错在哪。
6. 三个月的实际工作流变化,以及这个方向真正卡住的地方
跑了三个月text-to-cad之后,我最大的变化是工作效率和心态。以前接到一个“带孔安装板加定位凸台”这种需求,我要打开CAD软件、建基准面、画草图、加约束、拉伸切除,现在只需要复制需求、粘贴到接口、等几秒钟,拿到STEP文件进FastCAD再精修。省下来的时间不是用来摸鱼,而是用来做结构布局方案的对比——同一个安装空间,斜着放支架和横着放支架,散热和走线完全不一样,我现在有精力把这类问题想得更细。
6.1 从玩具到草稿生成器:我给它重新定位了
很多人把text-to-cad当“自动化设计工具”,觉得生成完就能直接投到车间。我三个月用下来,更愿意叫它“草稿生成器”。它负责在你脑子里还没有完整几何的时候,先把一个看得见摸得着的实体摆到屏幕上。这个草稿带了尺寸、带了特征树、带了解释,你改起参数来也方便。
比如我最近做一个设备外壳的安装接口板,客户提了一堆关于开孔位置的要求,我先把文字丢给text-to-cad生成了三版方案,分别对应不同的走线方向。每个方案都是一个完整的STEP文件,导进FreeCAD里测了一下螺丝干涉,最后选出最合适的一版继续细化。这在以前是不可能的,以前我只会因为嫌麻烦,而只做一版“看起来差不多”的方案。
6.2 目前真正卡住的三个问题
瓶颈一:装配体与约束语义。单个零件没问题,但你说“让这根轴和那个轴承座装配,轴端留2毫米间隙”,模型就抓瞎了。它连“装配”这个动作要转换成什么代码都不知道。
瓶颈二:自由曲面和高级造型。text-to-cad目前能做的事情基本集中在平面、拉伸、旋转、打孔、倒角,对A级曲面、样条曲面、自由造型束手无策。别指望它给你生成一个鼠标外壳或者汽车翼子板。
瓶颈三:生成结果的可逆修正。“孔往右移3毫米”,人类设计师改一个标注就完成了,text-to-cad得重新生成一次,而且重新生成可能把之前正确的特征一起改歪。解决这个问题需要引入“编辑意图”的概念,让模型基于已有代码做增量修改,而不是从零写一遍。
6.3 基于这个思路下一步可以怎么扩展
如果你也被text-to-cad勾起了兴趣,我建议从两条线往下走。一条是往“领域模板”方向走:把你所在行业最常见的二十种零件特征做成模板库,让提示词直接引用模板名而不是每次描述全部几何细节。另一条是往“参数化批量生成”方向走:用一段JSON定义一批零部件的参数,循环调用接口,把生成结果自动归档成带编号的零件库。这两件事我都已经在做了,效果比通用提示词好很多。
我现在的体会是:text-to-cad最有价值的地方,不是省掉了建模这个操作,而是省掉了把口头需求转译成几何语义的这个过程。它未必能帮你设计出一个惊世骇俗的产品,但它能帮你把一万个重复的“换个尺寸再来一次”变得不那么反人类。如果你也恰好被这类重复劳动折磨过,不妨照着上面这套链路,先搭一个最简陋的版本跑几个零件试试。第一批结果大概率粗糙,但你会发现,从“说清楚”到“看到模型”之间的距离,从来没有这么短过。