☰
text-to-cad实战:从选型到数据构造的完整踩坑记录
2026/10/27 4:08:46 网站建设 项目流程

做 CAD 建模做到怀疑人生的人,应该都幻想过一件事:能不能直接把脑子里那个零件告诉软件,让软件自己把模型画出来?我刚接触 text-to-cad 这个概念时,本来以为又是某个学术 demo 的噱头,直到我自己搭了一套原型跑通,才发现这条路已经比我以为的成熟太多了。今天这篇文章,不打算给你复述论文,而是想完整讲讲我这两个月从调研、选型、造数据到跑通实测的全过程,包括哪条路线性价比最高、数据怎么构造最省力,以及那些网上没人告诉你的坑。

先说结论:text-to-cad 的核心难点从来不是“让大模型说人话”,而是“让大模型说对参数”。自然语言输入进去,中间经过一层可编程的几何内核,最后输出一个真正的、能编辑能改尺寸的 CAD 文件,这中间任何一个环节掉了链子,出来的模型就是废的。我会按我实际操作的推进顺序来写,你照着走一遍,应该能少走相当多弯路。

1. 需求边界没搞清楚,后面全是无用功

1.1 “描述一个形状”和“修改一个特征”是两回事

开始之前,先花点时间弄清楚你的 text-to-cad 到底要解决什么问题。我见过不少团队,一上来就拿“给我生成一个发动机缸体”这种话当作目标,做了一阵子发现完全不可控。其实按我实测的经验,目前最靠谱的分法是两种:

  • 从零生成一个相对简单的零件:比如法兰盘、支架、壳体这类。
  • 在已有模型上做局部修改:比如“把孔直径改成 12mm”“把这个倒角去掉”。

这两种需求的难度完全不是一个量级。前者是生成的,后者是参数化的,前者只要模型能输出一个合理的几何体就算成功,后者却必须严格保证编辑前后其他特征不被破坏。我做原型时先选了前者,因为后者对 CAD 内核的依赖太深,做不好很容易变成更复杂的重复造轮子。

1.2 不要指望“说一句话”就真的只输出一个模型

另一个必须提前建立的认知是:text-to-cad 的实用形态,通常不是“一句完整指令 → 一个模型文件”的端到端,而是“指令 → 结构化参数 → 用参数驱动建模脚本 → 渲染成模型”。也就是说,中间一定要有一个可解析的中间表示。这个中间表示可以是 CadQuery 代码、OpenSCAD 代码、B-rep 操作序列,甚至是一份严格的 JSON 参数表。没有这层中间表示,你几乎无法校验生成结果,也谈不上后期编辑。

我刚开始也想偷懒,直接让模型输出 STEP 文件内容,结果发现大模型生成二进制格式的能力非常差,输出乱七八糟的字符串,10 次里有 8 次解析失败。后来才想明白:大模型擅长的是生成代码和结构化数据,而不是去硬编一个经过压缩的几何格式。这个观念转变,基本决定了整个项目后面的方向。

2. 三种主流实现路线,我最后选了代价最低的一种

2.1 端到端生成:最性感,也最不实用

最早吸引我注意力的确实是端到端方案:把文本描述丢进一个类似扩散模型的网络里,直接输出体素、点云或者隐式场,再从中重建出网格。这类研究方向很热,数据也是现成的,训练框架也成熟。但我测下来发现,输出结果很难直接进入工程流程。

原因其实很朴素:CAD 模型在工业里最重要的是“可编辑的参数化历史”。你给一个网格文件,工程师没法直接改尺寸,也没法回到草图步骤重新调整约束。端到端生成出来的东西,更像一个雕塑,而不是一个零件。如果你只是想快速看个造型,或做视觉设计概念图,那这条路够了。但凡正经要拿去加工、装配、做有限元分析,它就撑不住了。

2.2 大模型直接输出代码:散装最灵活,但精度要盯着

把自然语言交给大模型,让它写成 CadQuery 或 OpenSCAD 脚本,再由脚本执行生成几何体,这条路是目前社区里跑得最通的,也是我最终采用的方案。优点是灵活性高——想生成什么样的体,只要会写代码,模型都能试;缺点也很明显:代码常见的通病就是“看起来合理,实际跑不通”,或者参数没约束好,模型能出来,但尺寸错得离谱。

CadQuery 的好处是它有一套基于构建顺序的建模 API,代码在,历史就在。后续要改,直接改参数重新执行一遍就行。这一点对我的使用场景来说太关键了。而且 CadQuery 生成的可以直接导出 STEP 和 STL,STEP 能自由进 SolidWorks、FreeCAD 这类软件,实用度直接就上来了。

2.3 LLM + 参数化模板:最稳,但扩展性有限

除了写代码,还有一种我后来在调优阶段大量使用的套路:先用少量模板把零件族定义好,比如“法兰类”“轴承座类”“轴套类”,然后让大模型从文本里抽取关键参数,填入 JSON 结构,最后调用预设脚本生成模型。这样做的好处是生成成功率非常高,因为建模逻辑是我们自己封装的,模型只是做了一回“填空题”。

代价也摆在明面上:模板覆盖不到的形状,系统就不会做了。我的建议是如果你业务场景相对固定,比如公司内部就生产某几类零件,这个方案是性价比极高的。做一个通用 text-to-cad 对外服务,短期内还是绕不开 2.2 那条代码生成路线。

2.4 我为什么没有选择“重写内核”

还有一类做法是把自然语言直接翻译成 CAD 内核的命令流,比如直接生成 OpenCASCADE 的底层布尔运算调用。这个方案我试了两天就放弃了,原因特别简单:调试难度极高。底层命令流稍微错一个参数,你就得从几千个操作里翻找问题,排查成本比直接看脚本代码高一个数量级。对个人开发者或小团队来说,真没必要在一开始碰这种硬骨头。

3. 搭一套可运行的 text-to-cad 流水线,分步全记录

3.1 整体架构先搭起来

我的整套系统其实不复杂,分四层:自然语言输入层、LLM 解析层、建模执行层、结果校验层。自然语言进来后,我会先让 LLM 补全成一个标准化的描述,再交给一个微调过的模型生成 CadQuery 代码,执行过程中如果抛异常,就循环让 LLM 修 bug,修到通过为止。这一步听起来简单,但里面的工程细节比想象多。

我先给你看一个最小可用的代码骨架,这个骨架是我前期快速验证“能不能跑通”时用的:

from cadquery import Workplane def make_flange(outer_d=60, inner_d=25, thickness=8, holes=4, hole_r=3): # 先构造一个圆盘 part = Workplane("XY").circle(outer_d / 2).extrude(thickness) # 挖中心孔 part = part.faces(">Z").hole(inner_d / 2) # 环形阵列螺栓孔 part = part.faces(">Z").workplane().rect(outer_d * 0.75, outer_d * 0.75, forConstruction=True) \ .vertices().hole(hole_r) return part result = make_flange() result.exportStep("flange.step")

这是 CadQuery 最典型的花式操作:先拉伸,再打孔,再阵列。如果你让 GPT 这类大模型直接生成这种代码,大多数情况它能写对。但让它把参数从一句话里准确抠出来,又是另外一回事。

import openai user_input = "做一个法兰,外径60毫米,内孔25毫米,厚度8毫米,均匀分布四个螺栓孔,孔半径3毫米" prompt = f""" 请把下面这句中文描述解析成 JSON,只输出 JSON,不要多余文字: 描述:{user_input} 要求输出字段: - outer_d: 外径(mm) - inner_d: 中心孔直径(mm) - thickness: 厚度(mm) - holes: 螺栓孔数量 - hole_r: 螺栓孔半径(mm) """ resp = openai.ChatCompletion.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], temperature=0 ) print(resp["choices"][0]["message"]["content"])

这么做的目的,是先把参数从自然语言里抽干净,再喂给代码生成模型,而不是让模型边理解文本边写代码。两步拆开之后,错误率会明显下降。这也是我在实测里最强烈的一条体验:把“理解”和“生成”分开,比单次端到端要稳得多。

3.2 执行环境与校验层,是命门

代码生成出来是要跑的。这一步必须在受控环境里执行,绝对不能直接在你的业务服务器上裸奔。原因很简单:大模型生成的代码,大多数时候能跑通,但偶尔会给你搞出无限循环、输出超大文件、甚至写出系统级调用。我吃过这个亏,所以后面老老实实把执行放到 Docker 容器里,限制 CPU 和内存,超时直接杀掉。

import subprocess import os def run_script_in_container(script_path: str): cmd = [ "docker", "run", "--rm", "--network", "none", "--memory", "512m", "--cpus", "1", "--timeout", "30", "-v", f"{os.getcwd()}:/work", "cadquery:latest", "python", f"/work/{script_path}" ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=45) return result.returncode, result.stdout, result.stderr

执行完之后,还必须做几何校验。只做“没有报错”这一级别的判断远远不够。我会检查输出模型的三维包围盒尺寸、体积是否在合理阈值范围内,以及 B-rep 的面数是否异常。比如用户说要一个 60mm 的法兰,系统输出一个 600mm 的东西,这显然就是把单位处理错了,必须拦截。

3.3 一个重要的工程习惯:让模型写测试

我前后迭代了好几个版本,最后发现一个非常提高稳定性的习惯:在让模型生成 CadQuery 代码的同时,额外让它生成一段“自测代码”。这个自测代码用来断言模型的关键参数,比如:

from cadquery import exporters import tempfile, os def validate_model(part): bbox = part.val().BoundingBox() assert abs(bbox.xlen - 60) < 1e-3, f"xlen={bbox.xlen}" assert abs(bbox.ylen - 60) < 1e-3, f"ylen={bbox.ylen}" assert abs(bbox.zlen - 8) < 1e-3, f"zlen={bbox.zlen}"

这一步看似多余,实际作用极大。生成代码的最终目的不是“语法正确”,而是“几何符合预期”,自测代码能把这两者绑定起来。跑不过自测的,直接扔回给大模型去修,比人工一个个点开 STEP 文件检查快得多。

4. 数据构造才是真正的护城河

4.1 公开数据集够用,但领域数据得自己造

text-to-cad 当前最大的瓶颈不是模型参数,而是训练数据。公开的 Text2CAD 这类数据集虽然有大量“自然语言—代码”配对,但它基本以英文为主,而且描述风格和国内工程师口语差别很大。中文环境里,你说“带法兰边的圆盖”和“带凸缘的圆盖”完全是一回事,但模型的泛化能力可不会默认知道。

所以我自己造数据的路线是:先人工整理 200 条真实场景的文本描述,覆盖我关注的零件类型。比如“四孔方形底板”“带台阶的圆柱”“中心开方孔的板”。然后把这 200 条描述交给通用大模型,让它扩写成至少 2000 条不同表达方式的样本。这个过程我用了一个少见的技巧——把同一条描述复制多份,每份限定不同的语气词和表达方式,比如一份偏口语“我要一个……”,一份偏工程术语“零件规格如下……”,一份还掺杂不必要的细节干扰项。

4.2 合成数据的过滤比生成更重要

分离两条路跑下来,我深刻体会到“合成数据=垃圾进垃圾出”这句话不是开玩笑。我从大模型那里扩写出几千条数据,里面各种参数自相矛盾的比比皆是。比如描述里说外径 60,下一句编造出“厚度 60mm”,体积直接膨胀到夸张。

我花了不少时间写清洗规则,包括参数范围过滤、语义去重、和标准件库的参数比对。这事听起来很枯燥,却直接影响后面模型效果的上限。你要是跳过这步,后面调模型时只会更痛苦:模型学会了错误表达,你想再纠正,比从零训练还费劲。

4.3 从手动标注到半自动标注

做数据标注时,我还做了个小工具:把文本描述里的数字自动抠出来,和标准答案做对齐,并对不上的给红色提示。这样一个人一天能校对 300 条以上,而不是靠肉眼一条条看。具体的做法不复杂:用正则优先匹配带单位数字,然后映射到 JSON 字段名,再和答案比对。这套半自动标注脚本我就不贴完整代码了,思路比代码更重要——凡是重复性人工操作,都值得先想一晚上自动化。

5. 实测中常见的三类翻车现场,以及我的修法

5.1 单位错误:最普遍,也最容易被忽视

翻车第一名,单位问题。中文里说“60 毫米”,模型有时会输出 60,有时会输出 6,有时甚至会当成英寸自己乘一个 25.4。CadQuery 的默认单位是毫米,所以一旦模型把 60mm 理解成 60 英寸,最终几何体就会大得离谱。

我的解决办法是在提示词里写死单位规则,并且再三强调“所有数字都是毫米”。更重要的一招,是解析完 JSON 后加一个范围校验:所有参数必须在 0.1 到 1000 之间,超出就直接给模型打回重写。这个规则能拦截掉绝大多数翻车。

5.2 建模顺序错误:代码逻辑对,画出来的东西不对

第二类问题更隐蔽。模型生成的代码能通过语法检查,所有体积、尺寸断言也能过,但打开模型一看,孔的位置完全不对。原因是建模顺序出错了:本应先在底面草图里画阵列,再拉伸,结果模型把阵列放在了顶面上,导致孔位整体偏移。

对这类问题,我的经验是不要完全相信端到端的断言,还必须在结果校验层做一次基于面的特征检查。比如螺栓孔到底是不是四个、是不是分布在同一圆周上,可以通过 B-rep 提取顶面圆形孔的数量和位置,再做聚类验证。这些检查规则只能靠你对业务零件的理解一点点积累,没有捷径。

5.3 约束缺失导致模型带“病”通过

第三类是“约束漏了”。比如设计一个带圆角的矩形板,模型可能只在四条边里选了两条加圆角,导致剩下两边是尖锐直角。从代码层面很难一眼发现,因为圆角数量不一致不会触发异常。

后来我改成了反向校验:描述里提到的每个特征,都要在自测代码里存在对应的断言。比如描述里说“全部圆角半径 5”,就要检查边数里到底有多少条边的圆角半径等于 5。这种“文本特征树 vs 几何特征树”的对照,是我调优阶段最值得的一次投入。

6. 调优过程中可复用的四条经验

6.1 提示词里给“反例”,比给“规范”更有效

我试过在提示词里大段描述“你必须严格遵守毫米单位,不能使用英寸”,模型偶尔还是出错。后来我把写法换成了“常见错误举例:把 60 毫米当成 60 英寸,导致输出了超大模型。现在请确保数值范围为 0.1~1000,否则视为错误”。效果立竿见影。大模型对反例的感知力比对抽象规范的感知力强得多。

6.2 温度参数调成 0,不代表没有随机性

不少人都知道生成代码时 temperature 要低,但调到 0 之后依然会出现不同结果,这是因为大模型推理本身还有采样层和并行计算带来的不确定性。我的实践是保留 temperature=0 作为基线,同时在生成完代码后,用同一 Prompt 连续生成三次,三个结果都跑测试,哪个通过用哪个。这个“主备投票”机制简单又很稳。

6.3 日志里记录“输入、输出、是否通过、改了几轮”,后面全是宝

从小规模实验到实际迭代,我每次都要求系统把完整链路写到日志里:原始文本、解析出来的 JSON、生成代码、自测输出、模型 STEP 的校验结果、以及最后是否人工接管。这样做的好处是,当你想优化 prompt 或微调模型时,可以精确知道哪类样本总是失败,而不是靠拍脑袋猜测。

6.4 模型能力不够时,先别急着微调,先做“脚手架”

有些朋友一遇到生成不准,第一反应就是我要微调一个开源大模型。我的看法是,先把前端的提示词、后端的约束校验、以及多轮重试机制做到位,这个“脚手架”的价值大概率远大于微调。脚手架能兜住 70% 以上的错误,剩下的 30% 才值得用微调去解决。我这边真正开始微调,是在脚手架已经稳定跑了两周之后才做的。过早微调反而容易过拟合到那几条有限的样本上,导致泛化能力下降。

最后再说一点

text-to-cad 现在看起来已经过了“能不能做”的阶段,真正拼的是“能不能稳定复用”。我自己从最初一条指令频繁翻车,到现在小零件族生成成功率冲到九成以上,最大的体会还是那句老话:别迷信大模型的能力,要把所有能约束的地方都约束住。让模型做它擅长的理解语言,把几何生成和校验这些确定性工作交给代码来完成,这套思路比任何单一模型的升级都重要。

如果你想自己动手试,我的建议是从一个极小的零件族开始:法兰盘、底板、轴套,三五类就够了。先把数据、脚手架、校验跑通,再慢慢扩大范围。这条路不难,但很磨人,希望我这篇记录能帮你省掉一部分我踩过的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询