ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

文本转CAD实战:LLM+代码生成可编辑STEP模型的路线与避坑

文本转CAD实战:LLM+代码生成可编辑STEP模型的路线与避坑 最近圈子里到处都在聊 text-to-cad很多人把它当成“AI 画画”的升级版觉得无非是输入一句话、吐出一个三维模型。我一开始也这么以为结果真动手做了一轮测试之后发现这个方向的水比想象中深得多文本生成出来的模型能不能用不取决于它长得多好看而取决于它是不是一个真正可编辑、可加工、可仿真的 CAD 实体。这篇文章我不会去复述官方文档也不会堆概念而是把我在实际折腾 text-to-cad 过程中总结的路线选择、实现链路、踩坑记录和一些能直接抄作业的代码思路分享出来。无论你是做机械设计、结构设计还是单纯想给 3D 打印流程加点自动化都应该能从里面找到一点值得参考的东西。1. 为什么我说 text-to-cad 不是“用 AI 画画”先聊一个最常见的误区。很多人以为 text-to-CAD 和 text-to-3D 是一回事其实两者差的非常远。text-to-3D 工具生成的通常是网格模型也就是用一堆三角面片拼出来的外壳长得很像模型但内部没有拓扑关系没有参数特征也不能像真正的 CAD 文件那样被特征树回放、改参数、出工程图。而 text-to-CAD 的目标从名字就能看出来终点必须是“ CAD 文件”是边界表示B-rep加参数化特征能被主流 CAD 软件直接打开和编辑。1.1 先分清 CAD 模型和 3D 网格打个比方。网格模型像你在路边随手拍的一张人像照片五官都在光线也对但它只是一张二维像素阵列你没法把它重新拉回三维空间里转一圈也没法修改某个器官的位置。CAD 模型则像一尊雕塑家的作品背后有骨架、有结构逻辑每一块面都能追溯到原始特征。 3D 打印一个花瓶网格模型完全够用但你要设计一个带四个安装孔的法兰盘必须要用 CAD 模型因为法兰盘不是一堆三角形拼出来的它是由旋转特征、拉伸特征、阵列孔特征构成的。从我自己的测试经验来看很多开源 demo 喜欢把“文本生成网格”包装成“文本生成 CAD”这里面的水分很大。生成一个看起来圆润的零件容易生成一个每一条边、每一个面都精确闭合的实体难得多。如果你拿到一个输出文件后发现它的扩展名是 .obj 或 .glb那它大概率只是视觉模型不是真正可编辑的 CAD 模型。1.2 text-to-cad 到底想解决什么问题我总结下来这个方向解决的是“从想法到模型”这一段被长期忽视的链路。设计师脑子里想的是“一个直径 30 mm、高度 50 mm 的圆柱顶部带一个直径 10 mm 的沉孔”但他要花十几分钟甚至半小时打开软件、建草图、添加约束、拉伸、打孔。text-to-CAD 想做的就是把这段操作自动化让设计者把精力放在“我要什么”而不是“怎么画出来”。还有一个不容忽视的场景是工程师的早期方案探索。以前改一版外形可能要重新建模或者花时间改特征树有了 text-to-CAD很多重复性的零件设计可以直接从需求描述生成初稿然后再回到专业软件里做详细设计和校验。它的价值不是替代 CAD 软件而是替代那些重复、琐碎、低创造力的建模动作让 CAD 软件真正变成“审图工具”而不是“画图工具”。2. 三种主流实现路线从一句话到实体的不同走法真正深入这个领域之后你会发现“文本生成 CAD”本身没有统一的技术路线。市面上有论文、有开源项目、也有商业产品底层思路大致能分成三类。这三条路线各有优劣我建议你先想清楚自己的应用场景再决定去向哪条路线靠拢。2.1 路线一把 CAD 建模过程当成语言生成任务这条思路最接近大家对“ AI ”的直觉就是把一个 CAD 文件的生成过程视作一串指令序列。一个有经验的工程师从零建模一个零件他会做的事情无非是新建草图、画圆、添加约束、拉伸、再画孔、再阵列。如果把这些操作全部序列化就形成了一种类自然语言的“ CAD 指令语言”。训练一个 Transformer 模型去学习这串指令的生成概率输入自然语言输出指令序列再由解释器一步步执行指令最终得到模型。这个方向的代表工作之一是 DeepCAD它把大量 CAD 文件解析成草图-拉伸-打孔等特征序列再训练自回归模型生成。理论上这种路线的输出纯天然就是参数化 CAD因为它由建模指令构成每个特征都保留了参数空间修改起来非常自然。但实际用下来这条路线的短板也很明显。首先是序列长度过长一个稍复杂的零件可能有几十上百条指令模型很容易在长序列上丢失上下文导致生成出来的特征顺序混乱。其次是数值精度问题语言模型擅长生成离散 token但尺寸这类连续数值一旦 token 化就会损失精度容易出现 10.0001 mm 这种微小的误差。对于不需要加工的场景无所谓但对于要进加工中心的模型0.01 mm 的误差可能就足够了。2.2 路线二扩散模型先生成几何再想办法转成边界表示另一类思路是从 text-to-3D 领域迁移过来的先用文本生成体素、点云或隐式场得到“长得像”的几何体再做表面重建最后尝试转换成 CAD 软件能用的 BREP 格式。这个方向在视觉呈现上最有冲击力因为扩散模型很容易生成复杂、有机、自由曲面造型的物体而传统的参数化建模恰恰最怕这种形态。不过问题出在最后一步“转换”。网格模型是由大量平面三角形逼近的曲面一个圆孔在网格里实际上是一条多段折线几十个三角形拼出来。要把这样的网格转成光滑的圆柱面、平面、圆锥面等解析几何面需要做大量的曲面拟合和拓扑修复。我做过的测试里转换结果经常出现碎面、缝隙、重叠面放到 SolidWorks 里打开后报一堆错。即使修复成功得到的也是一个“死”的实体没有参数特征用户无法通过拖动尺寸重新生成。这条路线比较适合艺术造型、快速概念模型这类不追求精确尺寸和工程约束的场景但如果目标是机械加工它目前离“能用”还有距离。2.3 路线三LLM 程序化建模库目前我实测最稳的路径第三条路线是我个人目前在项目里最推荐的一条也是我下面要重点展开的让大语言模型写程序化建模代码再由建模库执行代码生成 CAD 模型。直白一点就是让 LLM 扮演一个“建模程序员”它输出 CadQuery、OpenSCAD 或类似库的代码而不是直接输出几何体。为什么这条路线最稳因为它把几何生成的重任交给了成熟的几何内核比如 CadQuery 底层是 OpenCASCADE本身就是几十年的工业级内核布尔运算、倒角、放样这些操作都是可靠的。LLM 只需要负责一件事把自然语言描述翻译成一段正确的建模代码。这是一个典型的代码生成任务而代码生成恰恰是当前大模型最擅长的领域。模型的输出不是几何体而是代码只要语法正确、语义正确执行出来的 CAD 模型在几何上就是严格有效的。这条路线还有一个隐藏优势代码天然就是参数化的记录。LLM 生成 CadQuery 代码的同时逻辑上就等于生成了一个完整的参数化特征树用户后续改尺寸、改位置、替换某个特征都是在改代码比去点三维软件里的菜单还直观。3. 实操跑通一条最稳的 text-to-cad 生成链路说再多概念不如直接上手跑一遍。下面这套链路是我在实际项目中验证过的最小可用方案流程是文本提示词 → LLM 生成 CadQuery 代码 → 执行代码生成 STEP 文件 → 校验结果。它不需要你掌握深度学习训练也不需要昂贵的显卡只要有一个能调用的大模型接口加上一个 Python 环境就能跑通。3.1 为什么选 CadQuery 当“翻译目标”市面上程序化建模的 Python 库不止一个我最终选择 CadQuery主要基于几个非常实际的考虑输出格式标准CadQuery 可以直接导出 STEP 格式而 STEP 是几乎所有主流 CAD 软件和 CAM 软件都能识别的中性格式不会像 STL 那样丢拓扑信息。底层内核可靠CadQuery 基于 OpenCASCADE建模能力覆盖拉伸、旋转、扫掠、放样、布尔、倒角、圆角等绝大多数机械零件特征不是玩具级库。代码风格接近直觉CadQuery 的链式调用读起来非常接近人类的建模思路。比如从一个平面开始画草图拉伸再在端面上打孔每一步都对应实际建模动作非常利于 LLM 学习。社区活跃、案例丰富在训练语料里 CadQuery 的代码量足够多大模型见过很多生成质量比冷门库稳定得多。相比之下OpenSCAD 也是选项但它的语法更像函数式编程写复杂零件可读性差而且它输出的是 CSG 树转 STEP 有时不如 CadQuery 顺滑。如果你只是做简单的 3D 打印外壳OpenSCAD 可以但如果你要把生成结果继续送进 SolidWorks 做详细设计我建议直接用 CadQuery。3.2 最小链路搭建提示词到 STEP 文件我的实现方案分成三个模块提示词模板、代码生成与校验、几何执行与导出。先看看提示词模板这是整个链路中最容易被轻视的部分。我实测下来一个有效的系统提示词至少要包含输出格式限定、单位约定、建模规范、代码约束。下面是我常用的一个模板你是一个专业的 CAD 建模工程师使用 CadQuery 库版本 2.x编写 Python 代码。 要求 1. 只输出 Python 代码不要输出任何解释或 Markdown 标记。 2. 使用毫米作为单位。 3. 建模方向约定默认 Z 轴向上零件底面放在 XY 平面上。 4. 使用变量定义关键尺寸方便后续修改。 5. 所有尺寸必须使用 float 类型不要用 int。 6. 生成的代码必须能被 CadQuery 2.x 的 cq.exporters.export 正确导出为 STEP 文件。用户输入部分则直接写需求比如生成一个法兰盘外径 80mm内径 30mm厚度 12mm外圈均匀分布 6 个直径 6mm 的安装孔孔心所在的圆直径为 60mm。接下来在 Python 里把它们拼起来调用 LLM API 生成代码。这里我建议在模型请求里拿到结果后先做一次“代码提取”把响应中可能残留的一小段解释性文字去掉确保直接传给 exec 或 subprocess 的内容是纯净代码。我在项目里会用正则匹配三种代码块格式但更保险的做法是直接在系统提示词里要求模型“只输出代码”把解释抑制在源头。下面是执行与导出的核心片段from cadquery import exporters import tempfile, os, subprocess, textwrap def generate_step_from_code(gen_code: str, output_path: str) - bool: 将模型生成的 CadQuery 代码放到子进程里执行 避免当前进程被异常代码污染。 runner_code textwrap.dedent( f import sys import cadquery as cq from cadquery import exporters ns {{}} exec({gen_code}, ns) result ns.get(result) if result is None: print(ERROR: 代码中没有定义名为 result 的最终对象) sys.exit(1) exporters.export(result, r{output_path}, exportTypeSTEP) print(OK) ) proc subprocess.run( [sys.executable, -c, runner_code], capture_outputTrue, textTrue, timeout60 ) if proc.returncode ! 0: print(执行失败:, proc.stderr) return False return proc.stdout.strip().endswith(OK)这里我特意用了子进程而不是直接在当前进程里 exec原因后面会在踩坑部分展开。函数调用方只需要把模型返回的代码和输出路径传进来就能得到 STEP 文件。一个具体的生成例子。针对前面那个法兰盘需求模型通常会写出类似这样的 CadQuery 代码import cadquery as cq result ( cq.Workplane(XY) .circle(60/2) .extrude(12) .faces(Z) .workplane() .hole(30/2) .faces(Z) .workplane() .pushPoints([(30, 0), (-30, 0), (0, 30), (0, -30), (30/2**0.5, 30/2**0.5), (-30/2**0.5, -30/2**0.5)]) .hole(6/2) )这段代码的理解成本很低在 XY 平面画直径 60 的圆拉伸 12 mm在顶面打直径 30 的中心孔再在顶面用六个均匀分布的点阵列打六个直径 6 的安装孔。执行这段代码后导出的 STEP 文件可以直接拖进 FreeCAD、SolidWorks 或者 PTC Creo特征树里能看到“拉伸、孔、孔阵列”等步骤完全不是一块死网格。3.3 生成结果的三个硬指标不要被“代码能跑、模型能显示”骗过去。我在项目里给每一个生成结果定了三个硬性检查指标任何一个不达标都算失败几何有效性STEP 文件必须能被 CAD 内核正常导入且是一个实体Solid而不是壳或碎面。可以用 CadQuery 读取后检查.val()是否是一个实体对象或者用 OpenCASCADE 的BRepCheck_Analyzer做更严格检查。尺寸校验生成后要自动对关键尺寸做一次抽样测量。比如法兰盘的外圆直径我会拿脚本去查询圆柱面半径跟目标值比对。不要相信代码里的数值因为 LLM 可能把 60 写成 50而且代码不报错模型显示看起来也像模像样。参数可变性代码里关键尺寸必须是变量不能直接散落成魔法数字。否则用户想改外径就得从一堆数字里找哪个是 60体验等于回到参数化 CAD 发明之前。这三条都过了生成的结果才敢说“能用”。如果只是打眼一看觉得像那离可交付还差得远。4. 实测中反复踩到的坑跑通最小链路不难难的是让它稳定服务于真实需求。我在大量测试中踩过不少坑下面挑几个最具代表性的每个都配有原因分析和对应的避免方法。4.1 文本歧义一句人话十种建模结果自然语言本身就不是为精确建模设计的一句话里藏着大量工程师之间靠常识补全的信息。比如“法兰盘外径 80内径 30厚度 12”看起来很清楚但模型拿到的信息缺少一个关键前提内孔是什么孔是通孔还是盲孔如果没有额外说明CadQuery 默认的通孔逻辑可能是贯穿拉伸方向也可能只在当前面打一个有限深度的孔。模型在生成时只能靠概率猜测经常猜错。我再举一个高频歧义“在中心打一个孔”。这句话对人是够的但对 CAD 代码来说远远不够。孔的深度是多少孔的方向是沿哪个轴孔底是什么形状是不是带沉头我测试时发现同一个提示词在一周内跑出来的模型一会生成通孔一会生成深度 2 mm 的盲孔搞得我一度以为是模型抽风后来才意识到是提示词没有把“贯穿整个厚度”这个意图说清楚。解决方案也不复杂在提示词里给出显式的建模规则。我的做法是在系统提示词里加一条“如果用户没有明确说明孔是盲孔默认生成通孔如果用户没有说明倒角尺寸默认不生成倒角所有孔默认沿当前工作平面的法向方向。” 规则越明确模型发挥空间越小错误率直线下降。4.2 大模型写的代码经常败给 API 版本变化CadQuery 2.x 是一个变化很大的版本。很多网上找得到的示例代码基于 1.x 写函数名、参数位置都不一样。模型训练数据里如果混入了大量旧版本代码生成结果就可能出现调用circle((30, 0))这种旧语法在 2.x 里直接报 TypeError。这不是模型能力问题而是数据时效问题。我建议在生成代码后做一次静态语法检查至少做到三点检查所有import是否在白名单内检查调用的方法名是否存在于当前安装的 CadQuery 版本用 Python 的compile函数检查语法是否正确。在更关键的项目里我会把生成的代码放进一个临时环境跑一遍能执行成功才算过。还有一个更底层的坑语法检查通过了但执行结果不是实体。CadQuery 里Workplane对象和最终的 Solid 对象容易混淆。模型可能写了一个绘制线框的代码导出的 STEP 文件打开后是一条边而不是体。这个问题靠语法检查发现不了只能靠执行后检查对象类型。4.3 随意 exec 生成代码是给自己埋雷这是我在安全性上踩过最深的一次。最开始偷懒直接在当前 Python 进程里 exec 模型生成的代码结果有一次模型“灵光一闪”在代码里写了自己递归循环直接把我的解释器吃满了。后来我学乖了所有生成代码一律放到subprocess里执行配合超时设置就算代码跑飞最多杀子进程主程序还能保住。如果生成的是 CadQuery 代码还好因为 CadQuery 的世界相对封闭。但如果未来你想让 LLM 生成完整的自动化脚本比如“生成模型后自动转 STL 并压缩上传”那风险等级就完全不一样。我的建议是把一切带副作用的操作拆出来白名单化。代码生成模块只负责生成模型文件文件上传、发消息、调用外部程序这些动作全部由主流程控制绝不让模型代码自己调。4.4 尺寸、单位、方向机器比你想象的更“一根筋”我遇到过最无语的一次让模型生成一个“直径 25 mm 的圆盘”结果导出的 STEP 文件打开看是一个半径 25 的圆盘整整大了一倍。为什么因为我没有在提示词里明确是“直径”还是“半径”模型默认把问题里的数字直接赋给了圆参数。中文里“直径”和“半径”是两个概念但模型代码里只有一个circle()它不知道该填哪个。单位也是个老大难。虽然 CadQuery 默认用毫米但只要模型在代码里混入inch计算整个零件体积就会莫名其妙地膨胀。更隐蔽的是方向约定我默认底面在 XY 平面拉伸方向是 Z但模型有时候会从 XZ 平面开始画导致整个零件躺倒在 X 方向上。这种错误不会报错但是一导入装配体就会发现零件姿态不对。解决方向问题我的办法是在系统提示词里反复强调“默认 Z 轴向上、底面在 XY 平面”并且在代码执行后加一步自动姿态校正用包围盒判断零件的最低面如果不在 XY 平面就自动旋转对正。这一步非常简单但很救命。5. 把 text-to-cad 从“能跑”做成“好用”的扩展思路走到这里你已经有一个能从文本生成 STEP 文件的最小流水线了。但“能跑”和“好用”之间还有很远的距离。下面分享几个我在项目里真正用上的扩展方向。5.1 生成结果加参数面板让用户像调旋钮一样改尺寸CadQuery 代码最大的优势是参数化。我在生成代码后不会把结果直接导出 STEP 就结束而是先解析代码里定义的变量把它们变成一组可配置参数。一个简单粗暴的做法是要求模型把关键尺寸全部定义在代码前部的变量区比如outer_d 60.0、thickness 12.0然后我用正则提取这些变量名作为参数面板的条目用户改完之后重新执行一遍代码即可。这个功能放在 web 页面里体验极佳。用户输入“给我一个法兰盘”系统生成模型后左侧出现一排滑块外径、内径、厚度、孔数、孔径用户拖动滑块模型实时重建。这套交互逻辑完全建立在“ LLM 生成代码 - 参数化重建”的路线上如果是纯网格生成很难做到这么顺滑。5.2 多轮修改用代码 diff 代替重新生成text-to-CAD 真正让人上头的时刻不是第一次生成而是生成之后的修改。比如用户看完初稿说“孔再大一点”“厚度薄一些”“把倒角去掉”。如果每次都重新调用模型从零生成模型很可能把已经确定的尺寸也改了导致改一处、崩全盘。更好的做法是把历史代码记录下来把用户的修改请求当作对现有代码的更新指令让 LLM 对着旧代码做局部修改而不是重写。我现在的实现是把上一轮生成的 CadQuery 代码直接拼进提示词告诉模型“下面是当前模型的代码请根据用户最新需求修改代码只输出修改后的完整代码。” 这样做能显著提高局部修改的稳定性因为大模型在已有代码基础上做增量修改比凭空生成更擅长。5.3 输出端不要止步于 STEPSTEP 是 CAD 交换的中性格式但在实际工作中往往还需要其他格式。我在流水线里集成了多格式输出3D 打印场景导出 STL需要数控加工但精度要求不高的场景导出 DXF 或 STEP做装配预览时导出 glTF 放到 web 端展示。CadQuery 的exporters.export支持多种格式我在执行代码后会自动生成一份同名的 STL 和 STEP供下游不同工具链使用。另外我自己还加了一步“语义检查”导出 STEP 后用 FreeCAD 的 Python API 重新打开确认文件能被工业级软件正常解析而不是只能在 CadQuery 自己的环境里显示。这一步很笨拙但能过滤掉不少低级错误。5.4 往装配体和制造端延伸单个零件生成只是第一步text-to-CAD 更有想象力的方向是装配体生成。举例来说用户输入“设计一个简单的减速器支架包含底板、两个轴承座、四颗安装螺栓”系统需要输出的是多个零件的模型以及它们之间的装配约束关系。这比单零件生成难得多因为涉及坐标系对齐、标准件调用、装配树结构。我现在实验的方案是让 LLM 生成每一个零件的 CadQuery 代码并按装配关系生成一个简单的装配描述文件。装配时通过给每个零件的基准面指定一致的坐标系来实现对齐。这个方向的坑比单零件多好几个数量级但一旦跑通对实际设计流程的帮助是颠覆性的尤其适合标准件选型和重复性组合设计。我自己折腾下来的最大体会是text-to-CAD 当前最实用的形态不是做一个“替代设计师的超级 AI”而是做“设计师和 CAD 软件之间的翻译官”。翻译的目标不是花哨的网格而是严谨的参数化代码。如果你只是想快速看个外形用 text-to-3D 就够了但如果你想生成的东西能被打开、被修改、被加工出来那一定要盯住代码生成这条路把输出结果往 STEP 和参数化方向逼。这条路现在虽然还有很多粗糙的地方但从我实测的进展看它已经能实实在在帮我省掉大量重复建模的时间了。
返回列表