尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Text-to-CAD实战:让大模型写代码生成可编辑三维模型

Text-to-CAD实战:让大模型写代码生成可编辑三维模型 去年年底到今年上半年我一直在折腾 text-to-cad 这个方向。起因很简单一个做产品的朋友发来一张照片问我能不能帮忙把里面的小零件画成三维模型方便他发给工厂打样。我当时第一反应是“直接开个建模软件慢慢描”但转念一想现在 AI 不是已经能写代码、能画画了为什么不让 AI 直接把文字变成 CAD 模型结果一查才发现text-to-cad 这个看起来“一步到位”的需求里面藏的东西比我想象中多得多。它跟市面上常见的“文字生成 3D 模型”根本是两条路线而且真正能落地到工程设计、能改尺寸、能出工程图的方案走的其实是“让大模型写程序化建模代码”这条路。这篇文章把我从零开始摸清楚的东西、搭起来的最小可用流程、踩过的坑以及现在每天都在用的干活姿势完整记录下来。适合正在做设计工具、创客项目、3D 打印建模或者对程序化建模感兴趣的工程师朋友参考。1. 先搞清楚 text-to-cad 解决的真问题text-to-cad 字面意思是“文字转 CAD 模型”但它解决的不是“生成一个好看的 3D 图片”这种问题而是“把设计意图直接变成可编辑、可制造、有精确尺寸的几何实体”。这两件事的差距几乎是天壤之别。1.1 需求本质从“意图”到“参数化实体”你可以让 AI 用一句话生成一张“看起来像椅子的 3D 渲染图”但你没法让工厂拿着这张图去加工。真正的 CAD 模型需要满足几个硬指标尺寸精确到毫米甚至微米、几何拓扑正确没有破面、没有自相交、可再次编辑改一个孔的位置整体能跟着更新以及能导出成 STEP、STL、IGES 这些标准格式给下游软件用。text-to-cad 要解决的就是把这个“设计意图”翻译成一套精确的几何描述。而问题在于大模型根本不擅长直接输出这种精确的几何描述。你让它输出一个网格文件它容易写出一堆顶点坐标和三角面但出来的结果往往漏洞百出、精度感人。于是这个领域的主流做法换了一个思路既然大模型擅长写代码那就让它写“能生成 CAD 模型的代码”——这就是程序化建模的用武之地。1.2 它跟 text-to-3D 的本质区别很多人一开始会把 text-to-cad 和 text-to-3D 混为一谈但用过一次就知道区别在哪。text-to-3D 工具比如那些生成 gLTF、OBJ 网格模型的方案擅长的是“形状创意”生成结果通常是雕刻级或渲染级的三角网格好看但不是一个严格的实体。你在 SolidWorks 里打开一个 OBJ会发现它是一个“网格体”无法直接做布尔运算、倒角、抽壳更没法标注尺寸公差。text-to-cad 的目标则是生成“边界表示”B-rep或参数化特征树也就是真正意义上的工程模型。打个比方text-to-3D 像是一个雕塑家用粘土捏了一个很漂亮的人像你能看能摸但不能拆开它换个零件text-to-cad 像是一个工程师按照图纸把每个零件车铣出来再装配每个尺寸都能改每个特征都能追溯。1.3 谁应该关心这个方向我给三类人画个像如果你符合其中之一这篇文章对你会很有用非机械背景的工程师/创客需要经常做外壳、支架、夹具但有不想为一个小零件打开重型 CAD 软件。产品设计师需要快速产出可编辑的初稿模型让结构工程师在此基础上细化。从事 CAD 工具链开发、AI 应用开发的人需要理解“LLM 程序化建模”的可行架构、限制以及验证闭环。我自己属于第一类和第三类的混合体所以我选择搭建一套基于 Python 和 CadQuery、OpenSCAD 的本地工作流而不是依赖某个没有源码的黑盒在线服务。接下来的内容都是沿着这条路线展开的。2. 为什么核心路径是“让大模型写代码”而不是“让大模型生成模型”这个问题我想了很久也是被自己的试错教育过后才彻底明白的。最开始我尝试过直接用大模型输出点云、输出 mesh甚至直接生成 STEP 文件结果无一例外以失败告终。后来我才明白text-to-cad 的正确中间表示不是几何数据本身而是生成几何数据的程序。2.1 两条路线的对比路线中间产物优点致命缺点代表思路路线ALLM生成程序化建模代码OpenSCAD / CadQuery 代码参数化、可编辑、尺寸精确、可复用需要代码能力强的模型代码可能跑不通CadQuery LLM路线BLLM生成网格/隐式场mesh / SDF / NeRF形状自由度高、视觉效果好非实体、精度差、无法编辑、不可制造text-to-3D 类工具路线 B 看起来很炫但它根本过不了“导入 Fusion 360 后做一次布尔运算”这一关。网格模型在转成 B-rep 时会出现大量退化面、缝隙和自相交修复成本比手工建模还高。而路线 A 天然继承了程序化建模的全部优点精度由代码里的参数决定模型由特征树唯一描述改一个数字就能更新整个模型。更重要的是代码是文本大模型对文本的生成能力是当前最强项这两件事一拍即合。2.2 为什么程序化建模对 LLM 特别友好我自己想了一个类比你让 AI 直接画一张精细的建筑施工图它大概率画得乱七八糟但你让 AI 写一套生成施工图的绘图脚本它反而能写得有模有样。原因很简单代码的生成过程是高度结构化的有语法约束、有逻辑顺序、有可验证性。模型只要记住 API 的用法就能按规则组合出合法的程序而直接生成几何数据则要求模型在几十万个浮点数里保持拓扑一致性这是当前模型完全不擅长的事。CadQuery 和 OpenSCAD 这两个工具尤其适合当“LLM 的建模语言”因为它们的语法足够简洁API 数量不算庞大社区也有大量示例代码可供模型在训练时学习。OpenSCAD 是声明式 CSG构造实体几何一个cylinder()加一个difference()就能表达非常复杂的零件CadQuery 是命令式链式调用用Workplane、box()、cutBlind()这些方法从上往下描述特征。LLM 写这两种代码语法错误率比直接写几何原件低得多而且出错了我们能拿到错误信息反喂给它。2.3 一个骨架级别的思维转变当我理解了“text-to-cad 的本质是代码生成”之后整个项目的架构思路就清晰了用户文字需求 → LLM生成建模代码 → 沙箱中执行代码 → 校验几何结果 → 输出STEP/STL → 用户编辑/切片 ↑ ↓ └─────── 报错信息反馈 ──────┘这个架构里最关键的一点是LLM 不是一次性输出最终模型而是输出一个“可以被反复执行和修正”的代码片段。每一步都有验证每一步的错误都能闭环回去。这其实就是我后面搭最小系统的骨架。3. 自己搭一套最小可用的 text-to-cad 流程理论说完了上实操。我搭的这套流程完全跑在本地核心组件四个大模型 API、Python 环境、CadQuery、OpenSCAD 命令行工具。整个流程不需要重型 CAD 软件参与但对工程师来说已经足够完成从“文字需求”到“标准模型文件”的最后一公里。3.1 环境准备与选型理由先说环境。我用的是 Python 3.11CadQuery 用 2.4.0 版本OpenSCAD 直接装官方命令行版。为什么选 CadQuery 而不是 SolidPython 或者直接 OpenSCAD因为 CadQuery 生成的几何可以直接用cq.exporters.export()导出 STEP且它是基于 OCCTOpenCASCADE内核的布尔运算和倒角的鲁棒性比 OpenSCAD 的 CGAL 好很多。OpenSCAD 虽然在复杂曲面和 2D 轮廓拉伸上有优势但它的布尔运算在大网格零件上偶尔会出现渲染慢的问题。模型选型我也做了对比测试。闭源模型里Claude 的代码生成质量确实高另一家的强推理模型在多步逻辑上也表现不错开源模型里Qwen2.5-Coder-32B 和 DeepSeek-Coder-V2-Lite 跑 CadQuery 的效果超出我心里预期。小参数模型7B 级别不是不能用但在生成多特征零件时括号不匹配、API 参数名幻觉的问题出现频率明显升高用它调试的时间比省下来的钱还多。所以我的结论是如果只是玩玩7B 够呛如果真要干活推荐 32B 及以上的开源模型或者干脆用闭源 API省心。3.2 系统提示词模板这是最关键的一步LLM 写代码的能力是有的但你不给它足够的约束它就会乱发挥。我试过直接输入“画一个带法兰的圆管”模型有时候输出 OpenSCAD 代码有时候输出 CadQuery 代码有时候甚至输出一段解释文字。后来我把系统提示词固定成一个模板效果立刻稳定下来。下面是我目前在用的精简版你是一个程序化 CAD 建模专家。用户会给出一个文字描述的三维零件需求。 你的任务是输出一段 CadQuery Python 代码或者 OpenSCAD 代码按用户要求代码必须满足 1. 所有尺寸严格等于用户给出的数值单位为毫米不要擅自缩放或四舍五入。 2. 使用参数化方式把关键尺寸定义为变量放在代码顶部方便后续修改。 3. 代码可以直接运行不依赖任何外部文件。 4. 代码末尾必须把结果输出为 STEP 文件CadQuery或 STL 文件OpenSCAD。 5. 如果需求有歧义在代码注释中写出你的假设但不要中断代码输出。 只输出代码不要输出解释。这个模板解决的最大问题是“输出格式漂移”。当模型知道它只需要输出一段可执行的代码、并且必须参数化之后生成的代码明显更有章法。另外在提示词里强调“如果有歧义在注释里写假设”能让你在审核代码时快速发现模型的误解点而不是它一声不吭给你一个匪夷所思的造型。3.3 完整流程实操示例假设用户需求是“生成一个外径 40mm、内径 30mm、高度 50mm 的圆管底部有外径 60mm、厚度 5mm 的法兰”。我构造的 prompt 是生成一个CadQuery模型外径40mm、内径30mm、高度50mm的圆管 底部有外径60mm、厚度5mm的法兰。模型第一次输出的代码大概是这样的import cadquery as cq # 参数定义 pipe_od 40.0 pipe_id 30.0 pipe_height 50.0 flange_od 60.0 flange_thickness 5.0 result ( cq.Workplane(XY) .circle(pipe_od / 2) .extrude(pipe_height) .faces(Z) .workplane() .circle(pipe_id / 2) .cutBlind(-pipe_height) # 创建法兰 .faces(Z) .workplane() .circle(flange_od / 2) .extrude(flange_thickness) ) cq.exporters.export(result, flange_pipe.step)这段代码粗看没问题但其实有个典型的几何错误法兰是向正 Z 方向拉伸了 5mm它和管子的底部完全重叠了而不是从底部向下延伸形成一个台阶。直观上看起来是一块圆形薄片贴在管底而不是标准的法兰台阶。模型没有理解“底部有法兰”这个空间方位。我把这个错误拿去手动修了一下改成.extrude(-flange_thickness)就对了。这就是为什么我一直强调“代码能跑通”不等于“几何正确”。你必须把生成的 STEP 文件导入一个能看 B-rep 的工具里检查或者用 CadQuery 的 API 去断言某些关键尺寸。后面专门有一节讲验证闭环。3.4 隔离执行绝不能直接 exec 模型输出的代码模型输出的代码是不能盲目信任的——这是所有 text-to-cad 工程化的第一铁律。你直接在本地exec()一段 LLM 生成的 Python 代码等同于让一个陌生人进你家电脑翻抽屉。我之前就碰到过一次模型输出了一段os.system(rm -rf /tmp/cad_tmp/*)的清理代码虽然只是清我指定的临时目录但这一下彻底惊醒了我任何模型输出都必须在一个受限的子进程里跑。我用的沙箱方案很简单用subprocess把模型生成的代码写到一个临时脚本文件里在子进程中执行同时设置 resource 限制和超时。比如import subprocess import resource import tempfile import os def run_cad_code(code: str, timeout: int 30) - tuple: with tempfile.TemporaryDirectory() as tmpdir: script_path os.path.join(tmpdir, gen_model.py) with open(script_path, w, encodingutf-8) as f: f.write(code) def limit_resources(): resource.setrlimit(resource.RLIMIT_CPU, (10, 10)) resource.setrlimit(resource.RLIMIT_AS, (2 * 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024)) # 2GB try: proc subprocess.run( [python, script_path], capture_outputTrue, textTrue, timeouttimeout, preexec_fnlimit_resources, cwdtmpdir ) return proc.returncode, proc.stdout, proc.stderr except subprocess.TimeoutExpired: return -1, , TIMEOUT except Exception as e: return -1, , str(e)CadQuery 和 OpenSCAD 本身没有恶意行为但 LLM 生成的代码里混入意外的文件操作、循环死锁都是可能的所以一套简单的资源限制脚本就是保命底线。OpenSCAD 那边更简单直接用命令openscad -o out.stl code.scad跑也放进沙箱进程里即可。4. 实测翻车现场与规避策略这一节全是真金白银换来的教训。text-to-cad 的“翻车”有非常多样的形态很多错误不细看根本发现不了等到你拿模型去切片或出工程图时问题才爆发。4.1 “看起来对了拓扑错了”球内圆柱通孔的陷阱我测试过一个需求“生成一个直径 20mm 的球体在里面打一个直径 10mm 的圆柱通孔”。模型输出了这样的代码import cadquery as cq result ( cq.Workplane(XY) .sphere(10) .translate((0, 0, 15)) .cylinder(30, 5) ) cq.exporters.export(result, ball_hole.step)这代码的问题非常隐蔽它压根没做布尔相减只是把一个球和一个圆柱叠在一起。如果用网格查看器打开球体内部多了一个圆柱实体拓扑上是两个实体相交而不是一个带通孔的球。如果直接拿去 3D 打印切片器会把它当成“球 圆柱”圆柱部分全变成实心外观完全错误。正确的代码应该用cut()做布尔差import cadquery as cq ball cq.Workplane(XY).sphere(10) hole_axle ( cq.Workplane(XY) .cylinder(30, 5) ) result ball.cut(hole_axle)我在修这个错误的时候就意识到 text-to-cad 的验证必须包含“几何体是否为一个有效实体”以及“是否包含预期的空腔”。这给了我很深的印象视觉上看起来“像”的东西在几何内核里可能完全是另一回事。4.2 参数计算灾难齿轮代码的集体翻车接着我又试了一个更复杂的零件——标准渐开线圆柱齿轮。这个需求很能检验模型的几何功底。我给的参数是模数 m2齿数 z20。齿轮的关键尺寸是分度圆直径 dm*z40齿顶圆 dad2m44齿根圆 dfd-2.5m35。模型第一次生成的代码里分度圆半径直接写了 20但画齿廓的时候又把直径当半径结果齿形全部错位。更离谱的是有个模型把模数公式记成了 d2mz算出来分度圆直径 80整个齿轮形状大了一圈。这类错误不是代码语法问题而是“几何公式理解”问题。大模型对机械设计里的标准公式记忆并不靠谱它会一本正经地输出一个自洽但完全错误的公式。我的规避策略是让模型在生成代码前把关键计算过程用注释写出来然后再写对应的代码行。有点像以前数学考试要求“写清步骤”一样。加了这条提示词之后齿轮的生成正确率从不到 30% 提升到了 70% 左右。剩下的 30% 翻车就靠后面的验证闭环兜底。4.3 函数名幻觉与 API 误用CadQuery 的 API 其实不少模型很容易把名字记错。比如把cutBlind()写成cutThrough()把.workplane()写成.workplane(offset5)实际上偏移要用.workplane(offset5)这个其实也行但很多情况会被模型误用把cq.selectors.NearestToPointSelector写成cq.selectors.PointSelector诸如此类。这些错误在静态检查阶段就能发现所以我在验证闭环里加了一步 AST 语法检查先确保代码是合法 Python再进沙箱运行。对于 CadQuery 特有的 API 幻觉我做了个笨办法整理了一份常用的 CadQuery API 速查表把它作为上下文喂给 LLM。这个速查表不需要很长几十个核心方法就够了重点是box/extrude/cut/cutBlind/faces/workplane/circle/rect/translate/rotate/union/chamfer/fillet这些高频方法。实测反馈效果很好。对开源模型尤其明显因为它们的上下文窗口本来就紧张一份干净的最小 API 清单比让它在记忆里大海捞针靠谱得多。4.4 危险的“自圆其说”与需求歧义还有一类翻车是需求理解层面的。我让模型生成“一个带悬挂孔的矩形安装板”它生成了一块板子但孔开在了板子的正中间而不是边缘的悬挂位置。这种歧义其实是用户描述不精确导致的——但这不代表我们没办法。我的做法是在系统提示词里约定一个“假设注释”规则模型遇到不明确的方位、样式、连接方式时必须在代码注释里写明它的假设。比如上面这个例子模型注释会写# 假设悬挂孔位于板上方两侧这样我在审核代码时一眼就能看出它的理解是否对而不用等几何结果出来再猜。5. 从“能出图”到“能交活”验证闭环与批量参数化一套 text-to-cad 工作流能不能“交活”关键看有没有验证闭环。LLM 的生成结果不经过自动验证永远只能算是“草稿”离可用还差一步。5.1 几何验证器的三阶检查我写了一个三阶几何验证器每次模型生成完代码以后自动跑一遍第一阶语法检查。用 Python 的ast.parse()检查代码是否是合法 PythonOpenSCAD 代码则调openscad --check-parameters其实没有这个参数我一般直接用openscad -o /dev/null code.scad测试是否报语法错误。第二阶运行检查。在沙箱里跑代码看是否能导出 STEP/STL 文件导出文件大小是否大于某个阈值比如 1KB防止输出一个空模型。第三阶几何属性检查。用 CadQuery 读回导出的 STEP 文件检查isValid()是否为 True、体积是否大概符合预期范围、边界框bounding box尺寸是否在用户设定的合理范围内。import cadquery as cq def validate_step(step_path: str, expected_volume: tuple[float, float]) - bool: shape cq.importers.importStep(step_path) if not shape.val().isValid(): return False volume shape.val().Volume() if not (expected_volume[0] volume expected_volume[1]): return False bbox shape.val().BoundingBox() max_dimension max(bbox.xlen, bbox.ylen, bbox.zlen) if max_dimension 1000: # 超过1米基本不合理 return False return True这个验证器把很多肉眼看不出来的问题直接暴露了。球内圆柱通孔的例子如果只检查isValid()是能通过的因为布尔叠加的实体仍是一个有效实体但加上“边界框检查”和“体积检查”就能发现如果球和圆柱只是叠加体积会明显大于球体积 圆柱体积的一部分就是不正常。再配合“空腔检查”会更精确但作为最小闭环前面这两步已经足够过滤掉 70% 的明显错误。5.2 错误回环让 LLM 看到自己的报错再去修验证器拿到结果后不是简单告诉用户“失败”就完了而是把报错信息和几何检查失败原因格式化成一段反馈丢回给 LLM 让它自我修正。这个过程可以重复 2 到 3 次。比如你的代码运行报错TypeError: circle() got an unexpected keyword argument diameter 请修正后重新输出完整代码。注意CadQuery的circle()只接受半径。有意思的是当我明确地把“只接受半径”这句提示写进去LLM 的修正成功率非常高。这说明很多错误不是模型不会而是它在生成时没有自觉地做 API 自查。给它一个外部的“评审视角”比让它凭空生成更可靠。这个“LLM 生成 → 沙箱执行 → 自动验证 → 错误回环”的循环是整套系统里最核心的架构模式。说句实话这比我之前想象中的“一句话直接出模型”笨重很多但它每一环都有检查、所有失败都有反馈所以稳定性非常高。这也是所有 AI 生成内容的通用心法不要期待一次性成功要让中间每个环节可验证、可回环。5.3 让模型输出“参数化模板”而不是一次性模型text-to-cad 真正的价值在于复用而不是一次性生成。所以我始终要求模型把关键尺寸定义为变量并且额外要求它生成一个对外暴露的修改函数。下面是我让模型生成的一个支架类代码框架import cadquery as cq def make_bracket(length80.0, width40.0, height30.0, thickness3.0, hole_d6.0): result ( cq.Workplane(XY) .box(length, width, thickness) .faces(Z) .workplane() .rect(length - 20, width - 20) .cutBlind(-thickness) .faces(Z) .workplane() .pushPoints([(length/2 - 15, width/2 - 10), (-length/2 15, -width/2 10)]) .hole(hole_d) ) return result result make_bracket() cq.exporters.export(result, bracket.step)这样我就能在生成之后通过改参数批量产出不同尺寸的支架比如批量生成 10 个不同长度、不同孔距的版本直接用于装配测试或对照 3D 打印。这种“文字转 CAD 模板”的做法才是把 text-to-cad 从玩具升级为生产工具的关键一步。5.4 多候选采样与人工兜底虽然自动验证能过滤很多错误但“几何正确”不等于“设计合理”。所以我采用了一个折中策略对同一个需求让 LLM 用不同的 temperature 采样比如 0.3、0.6、0.9生成 5 个左右的候选代码全部跑过自动验证然后我把候选结果导出成缩略图人工快速扫一眼。这一步的“人工审核”不是检查尺寸——尺寸由自动验证得到保证——而是检查“设计感”哪一版更符合用户没说出口的意图哪一版的结构更整洁、更利于加工。这个多候选思路是从生成式设计领域借来的效果出乎意料地好。特别是当用户需求里带着“大概”“尽量”“类似”这种模糊词时多个候选方案让你有机会给用户挑而不是直接给出一个唯一的答案。6. 我现在用它干活的姿势与生态观察绕了一大圈这套 text-to-cad 流程已经是我日常工作里离不了的助手了。最后分享一下当前真实状态下的使用姿势以及我对这个方向现状和限制的观察。6.1 我的标准工作流现在接到一个“帮我画个零件”的需求我的处理流程是需求拆解清单把用户模糊的描述拆成明确尺寸、功能面、装配方式、加工约束列表。这一步我自己做不交给 AI因为需求理解的质量决定最终模型的质量。分部件生成把复杂的零件拆成几个子部件比如主体、法兰、加强筋每个子部件单独用 text-to-cad 生成一次。一次让模型生成一个包含 20 个特征的零件Error 率会指数级上升拆成 5 个小任务每个只包含 3~5 个特征可靠性高很多。自动验证 多候选渲染跑前面说的三阶验证器导出候选缩略图人工扫一眼设计感。参数化封装把最终可用的代码封装成带参数的函数模板丢进自己的模型库里下次同类零件直接改参数复制。交付导出 STEP 给结构工程师去深化同时导出 STL 给 3D 打印机做验证件。这个流程下来一个中等复杂度的支架类零件从需求到拿到可用的 STEP 文件大概 15 到 20 分钟几乎全部时间花在需求拆解和人工审核上真正“画图”的环节几乎是一瞬间完成的。6.2 它目前的硬边界精度与创意我必须说清楚text-to-cad 目前的边界也很明显。第一它对高精度曲面比如汽车 A 级曲面、复杂自由曲面几乎无能为力因为 LLM 写代码时没法用几百个控制点去精确描述一个曲面——这是数学表达层面的限制不是工程技巧能突破的。第二它对 GDT几何公差标注毫无概念生成的模型没有任何标注公差属性完全缺失。所以它目前的定位是“草图和初稿生成器”而不是“最终制造交付工具”。第三它对输入描述的歧义处理还比较笨拙你如果只说“好看就行”它会给出一堆天马行空但无法制造的东西你需要把一个工程需求像喂孩子一样喂给它它才能给你一个工程化的结果。6.3 换个角度看它不是替代 CAD而是改写 CAD 的使用门槛我以前觉得 AI 建模是要替代 SolidWorks、替代 Fusion 360现在我不这么看了。text-to-cad 真正改变的不是“建模”这个操作而是“把需求转换成建模操作”的过程。以前你需要会选草图平面、会画约束、会做拉伸切除现在你只需要会清楚地描述需求、会审核结果、会在关键时刻修正代码。对于非机械专业的工程师、创客、产品经理来说这是一个从“学会工具”到“表达需求”的转变。最后再分享一个小经验如果你也想搭一套自己的 text-to-cad 工作流别急着追求“一步到位”。先把 CadQuery 的官方示例跑熟感受一下程序化建模的思维方式再拿 5 到 10 个自己工作中最常遇到的零件类型用 prompt 反复试最后再上自动验证闭环。这套顺序是最省时间的因为我一开始走了反路直接在真实需求上打磨 prompt结果翻车密度太高一度以为这个方向根本没戏。到现在我反而觉得text-to-cad 就像早期的编译器——它写的代码可能有点笨时不时要你去补一补但只要你给它清晰的需求边界和验证反馈它就能稳定地把你的话翻译成几何而把你在值得发挥创造力的地方留给设计。
返回列表