YAOTU INSIGHTS

Python+CadQuery实现text-to-cad:自然语言到STEP模型

Python+CadQuery实现text-to-cad:自然语言到STEP模型
这两年AI辅助设计的话题算是在行业里彻底热起来了尤其是text-to-cad这个方向几乎每次聊到都有人问真的输一句人话就能出一份能用的CAD模型我的回答是能但得先把期望曲线拉平——它不是你喊一句“给我画个飞机”就直接出装配图而是把自然语言里的几何参数、约束关系、加工需求转成一段可执行建模脚本再通过CAD内核生成带尺寸的实体模型。我前前后后折腾了差不多一个多月用Python CadQuery搭了一套最小可用的text-to-cad工作流从一个“长宽高加四个孔”的描述到导出STEP整个过程不到二十秒。这篇文章就把我从选型到踩坑的完整过程拆开聊适合那些已经在用CAD、又想用自动化思路提高建模效率的工程师也适合刚想入坑CAD制图的新手。1. text-to-cad到底是什么从文本到可编辑模型的完整闭环1.1 一句话想法背后的真实需求text-to-cad最核心的价值不是“生成图片”而是生成真正可以编辑、可以加工、可以进装配的CAD文件。你嘴上说“我要一块400乘300乘10毫米的基板四角打直径6毫米的孔孔心距离边15毫米”系统要能理解长度、宽度、高度、阵列位置、孔径这些维度再落到CAD内核里创建特征。这和AI画图完全是两条路图片只是像素而CAD模型背后每一根边线、每一个孔都带着毫米单位的实体数据。我观察到的真实需求分三类第一类是有经验的工程师做重复性设计每天就是改几个尺寸重新出图torture得不行第二类是刚接触CAD制图的新人面对一堆草图约束和命令觉得学习曲线太陡想通过自然语言降低门槛第三类是设计迭代阶段需要快速对比多个尺寸方案手工逐个建模太慢text-to-cad正好适合做参数扫掠。这三类人其实都在问同一个问题能不能把“脑子里的形状”快速变成“能用的模型文件”text-to-cad就是切这个题。1.2 技术链条其实没有你想的那么玄完整链路可以拆成三段语义理解、参数映射、内核建模。语义理解负责把用户的话变成结构化数据比如“四个角开孔”中的“四角”转换成坐标数组参数映射负责把用户口语里的单位、参考基准、特征类型对应到CAD操作比如“距离边15mm”翻译成偏移约束内核建模则是真正调用几何内核生成实体。很多人以为难点全在第一段实际上三段都有坑。第二段最容易被忽略同一个“四角”中文里可能指“两个对角”“四个角点”还是“所有端点”模型必须靠上下文判断。我常用一个小技巧在自然语言解析之后不直接建模而是先生成一个中间JSON把它打印出来给用户确认。这个JSON里会写明operation、dimensions、hole_positions等字段相当于让AI先把“它听到的内容”复述一遍确认无误再往后走。这一步能挡掉大量歧义也方便调试。2. 技术方案选型为什么我最后选了Python CadQuery2.1 实现text-to-cad的几条主流路线想实现“文本到模型”市面上大致有四条路。第一条是让大模型直接输出OpenSCAD代码OpenSCAD本身就是脚本化建模代码即模型非常契合自动生成第二条是让大模型输出CadQuery代码CadQuery基于OpenCascade内核支持STEP/STL/AMS等格式链式API写起来比OpenSCAD更接近人类建模直觉第三条是走AutoCAD的AutoLISP或pyautocad直接驱动AutoCAD内部实体问题是依赖Windows环境和AutoCAD授权而且自动化程度容易被GUI卡住第四条是端到端的神经网络直接从文本体生成三维体素或点云再转网格速度快但精度和可编辑性很难保证。这四条路各有取舍。如果你做的是机械零件、板件、外壳这类规则几何OpenSCAD和CadQuery都够用如果你要的是真正能放进CAM加工的标准STEP文件CadQuery比OpenSCAD更直接因为OpenSCAD的导入经常出现面片化损失如果想调用现成AutoCAD图库或者干脆就在AutoCAD生态里工作pyautocad也有价值但你需要先解决“cad安装”“cad激活页面脚本发生错误”之类的软件环境问题我这种开源洁癖基本不碰。2.2 我的选型逻辑与对比结论我最后选了“大模型生成结构化JSON CadQuery执行建模”的组合。理由有三。第一CadQuery的API对参数化非常友好几何实体是由一连串操作叠加出来的而这一连串操作正好可以由程序持久化、复用、批处理第二输出STEP可以直接被中望CAD、SolidWorks、FreeCAD读写后续标注、Layout布局都不受限制第三全链路开源不需要装一堆破解工具也没有“彻底卸载不影响二次安装”那种难受的清理问题。选型过程中我列过一个对比表现在放出来给大家参考方案学习成本几何精度文件兼容性适合场景OpenSCAD LLM低中等一般STL为主简单外形快速尝试CadQuery LLM中高B-Rep内核STEP/STL/DXF/SVG都行真正可加工的机械件pyautocad高高与AutoCAD一致已有AutoCAD环境的重度用户端到端神经网络中低网格文件为主概念造型、视觉预览表格说明我的思路如果你是做结构件、钣金件、工装夹具CadQuery那条线是性价比最高的。它不像端到端方案那样“看起来聪明但落不了地”也不像pyautocad那样被商业软件绑定。2.3 环境准备绕开那些安装坑搭建环境时最大的坑是CadQuery的安装。PyPI上的cadquery包目前要求Python 3.9到3.11之间如果你图省事直接装了3.12import就会报一堆DLL错误。我建议直接用conda建独立环境指定python3.10然后pip install cadquery。另外CadQuery底层依赖OpenCascadeWindows下会用到一些Visual C运行库。热词里那句“安装cad一直出现c2005cpi错误”描写的其实就是这类问题你的系统缺VC Redistributable或者conda环境里混入了老版本的MSVC Runtime。解决办法很简单装一下微软官方的“Visual C Redistributable for Visual Studio”合集包再把conda环境重建一遍即可。这事别自己瞎折腾重装系统什么的完全没必要。3. 一个能跑通的最小系统从自然语言到STEP文件3.1 第一步先把自然语言翻译成结构化参数这一层我采用“提示词模板”的方式而不是直接让大模型写CadQuery代码。为什么不直接写代码因为代码容易多出变量命名、注释、错误分支这些噪音后面解析困难而JSON是固定的键值结构校验逻辑写起来非常省心。我的提示词大致长这样你可以直接抄去改你是一个CAD参数解析器。用户会给出机械零件的自然语言描述你需要提取出 - operation: 建模操作类型可选值为 box_with_holes, cylinder_with_holes, sweep_base - dimensions: 主尺寸对象包含length/width/height或diameter/height - fillet_radius: 统一圆角半径没有则填0 - hole_diameter: 孔径没有则填0 - hole_positions: 孔中心坐标列表格式为[{x: 毫米, y: 毫米}] 如果描述中包含四角、四个孔等短语按中心距边缘15mm推导。 只输出JSON不要任何解释。用户输入“400乘300乘10的基板四角开直径6的孔孔心距边15”大模型输出的JSON就是{ operation: box_with_holes, dimensions: {length: 400, width: 300, height: 10}, fillet_radius: 0, hole_diameter: 6, hole_positions: [ {x: 15, y: 15}, {x: 385, y: 15}, {x: 385, y: 285}, {x: 15, y: 285} ] }注意这里有一个关键点LLM给出了“x: 385”而不是“长边减去15”。CAD里很多孔位习惯用“距边距”表达坐标值本身是绝对定位如果你不把语言里的约束关系翻译成坐标后面建模就会经常出现偏移半个板厚的情况。我专门在提示词里强调了“按孔心距边推导”这样省去后面二次计算。3.2 第二步把参数扔给CadQuery生成模型拿到JSON之后建模部分就纯粹了。核心是写一个映射函数把JSON里的operation和字段变成CadQuery的链式调用。下面是我精简过的最小实现import cadquery as cq def build_from_json(data): if data[operation] box_with_holes: length data[dimensions][length] width data[dimensions][width] height data[dimensions][height] fillet data.get(fillet_radius, 0) hole_d data.get(hole_diameter, 0) positions data.get(hole_positions, []) result cq.Workplane(XY).box(length, width, height) if fillet and fillet 0: result result.edges().fillet(fillet) if hole_d and positions: workplane result.faces(Z).workplane() for pos in positions: workplane workplane.pushPoint((pos[x], pos[y])) result workplane.hole(hole_d) return result # 其他operation可以继续扩展 raise ValueError(fUnsupported operation: {data[operation]})这个方法有个细节我在每个孔位都调用了pushPoint而不是用一次pushPoints传入全部坐标。CadQuery的hole()在同一个workplane上只会作用于当前选择的所有点如果你把四个点一次性pushPoints然后调hole四个孔确实会同时出现。但实践下来分开pushPoint更利于后续给某个孔增加沉孔台阶或螺纹标记程序结构也更容易扩展。3.3 第三步导出STEP/STL并快速预览建模完成后导出是最后一道工序。CadQuery支持多种格式我的习惯是STEP为主、STL为辅。STEP文件保存的是B-Rep实体边界表示能被主流CAD软件读取STL则是三角网格适合快速预览和3D打印切片。cq.exporters.export(result, 基板.step) cq.exporters.export(result, 基板.stl)导出STL之前建议先设置弦偏差参数CadQuery的export函数默认精度对大多数零件够用但如果你做的是弧面较大的外观件导出后可能出现明显棱边。这时可以在export前用result.val().tessellate(tolerance, angleTolerance)手动控制三角化密度再自己写入STL。这里不展开提醒一句就够了。快速预览有一个很省事的办法把模型导出成SVG投影。CadQuery自带cq.exporters.export(result, preview.svg)它会按照当前视图生成一个2D线框预览图你不需要打开CAD软件就能在浏览器里看到形状对不对。我把它放在web界面上用户提交一句描述后返回STEP下载链接和SVG缩略图体验比直接发文件舒服很多。热词里“cad快速看”其实要的就是这个效果。4. 实操中跑出来的四条避坑经验4.1 LLM参数不守规矩校验比生成更重要合规性校验是text-to-cad最容易被跳过、又最容易翻车的一环。大模型想象力丰富给你输出一个负数的长度、孔径大于板宽、孔位跑到零件边界外面都是常规操作。不校验的话CadQuery轻则报错重则生成一个变形的怪异实体。我处理的办法是写一个参数校验函数在调用CadQuery之前先做一趟语义检查。对box_with_holes我至少检查所有尺寸都大于0孔径小于板长和板宽的三分之一孔位x坐标在孔径一半到长度减孔径一半之间y坐标同理。不满足条件的直接返回错误提示让用户修改描述而不是让程序硬着头皮建模。实际测试下来加入校验后成功率从七成提到九成以上。剩下的失败基本都是描述本身太模糊比如“一个中等的孔”没有给出直径这种系统没法猜只能提示用户补参数。4.2 坐标系与语义之间的鸿沟CAD里的“四角开孔”在建模时到底该放哪这件事比你想的更值得较真。CadQuery里的Workplane是以XY平面为基准box的左下角默认在原点所以四角孔的坐标是从原点开始算的。但很多用户的口语习惯是从板子中心开始算比如“距离中心线各50mm”如果你不统一基准孔位就会差出上百毫米。我后来在JSON结构里增加一个可选的ref_point字段默认是origin支持center或center_of_mass。在解析层就把参考基准统一换算成以原点为基准的绝对坐标。这样做的好处是用户描述“中心对称两个孔”解析器也能处理不需要他记住每个孔的坐标。这是text-to-cad里很有价值的一个设计决策。4.3 导出文件前先确认单位和精度单位问题出现的频率高到值得单列一条。CadQuery内部默认单位是毫米但如果你从某些渠道复制了以“cm”或“inch”描述的参数导出STEP时单位标签可能会变成英寸或保持无单位。很多软件在导入STEP时按自带单位推测尺寸结果一进CAD里零件变成了原尺寸的25.4倍或0.1倍。我在导出前固定统一设置cq.exporters.export(result, path, exportTypeSTEP)然后在文件开头检查STEP头部的单位关键字。另外建议在用户描述里强制标注单位提示词里写明“所有尺寸默认毫米”。热词里有些人搜“cad图纸合并”其实就是在对尺寸不一致的零件做适配如果源头单位就乱后面合并时全乱套。4.4 安装依赖时的常见报错与解决方案最后说说环境问题。热词里“安装cad一直出现c2005cpi错误”“cad激活页面脚本发生错误”这类搜索暴露的是Windows下软件依赖混乱的普遍痛点。CadQuery的安装也有类似的坑但没那么玄。最常见的报错是ImportError: DLL load failed while importing OCP。我建议先检查Python版本如果用的是3.9以下或3.12以上大概率版本不兼容其次检查conda环境直接conda install -c conda-forge cadquery会比纯pip少踩很多编译链的坑。还有一个隐蔽问题如果你以前装过旧版OpenCascade相关的包可能和cadquery的OCP发生冲突必须建新环境解决。还有一个来自热词但不局限的话题“cad如何彻底卸载不影响二次安装”。这其实是新手对系统环境不放心。我的经验是除非遇到严重冲突否则没必要“彻底卸载”用conda管理项目环境才是最干净的方案。想换版本就删掉整个env对系统没有任何残留污染。5. 从玩具到工具text-to-cad的进阶玩法5.1 批量变体批量生成不同尺寸的零件当你能把一个JSON转成模型之后批量变化就是水到渠成的事。我日常最常用场景是生成一批相同结构、不同尺寸的垫圈和框架零件。原来手工做一个要五分钟用text-to-cad只需把参数列表丢进一个for循环。variants [ {name: A型, length: 400, width: 300, height: 10, hole_d: 6}, {name: B型, length: 500, width: 320, height: 12, hole_d: 8}, {name: C型, length: 600, width: 350, height: 15, hole_d: 10}, ] for v in variants: data { operation: box_with_holes, dimensions: {length: v[length], width: v[width], height: v[height]}, fillet_radius: 0, hole_diameter: v[hole_d], hole_positions: [ {x: 15, y: 15}, {x: v[length]-15, y: 15}, {x: v[length]-15, y: v[width]-15}, {x: 15, y: v[width]-15} ] } part build_from_json(data) cq.exporters.export(part, f{v[name]}.step)这里有热词“python批量对cad修改”的影子和apply思路。批量生成时我给每个文件名的模板字段写在JSON词典里实际上就是一个微型产品配置表。再结合工程变更单改动非常快。这比打开CAD、一个个双击改尺寸再导出要可靠得多而且不会漏改。5.2 装配到一起让单零件走向部件单零件生成只是起点真正到做产品结构时需要把多个文本生成的零件按位置关系装起来。CadQuery里有Assembly类可以把每个零件通过add(part, loccq.Location(...))放进同一个装配坐标系然后导出STEP装配文件。我常用一个办法先在文本层约定好基准点比如“上板左下角放在坐标原点四个安装孔对应立柱位置”程序生成每个子零件后用同一个location对象装配。这样从文本描述到装配体只差一步。热词里“cad图纸合并”在传统CAD里是靠外部引用、块、新建Layout来做但在CadQuery的代码世界里合并就是数学坐标变换反而更直观。5.3 和主流CAD软件协同导入、标注、Layout布局text-to-cad的目的不是替代CAD而是把让机器头疼的重复结构化建模先做掉再把人拉回真正的设计工作中。我目前的工作流是用text-to-cad生成STEP基础件然后在主流CAD软件里做标注、装配、出图。很多朋友会问“cad导入layout步骤详解”其实在主流CAD软件里导入STEP后进入布局空间调整视口比例、标注尺寸流程稳定。CadQuery导出的STEP文件从底层是B-Rep实体不像网格文件那样会有碎面因此标注时可以直接选面选边非常顺滑。中望CAD、AutoCAD都能正常打开不需要转线框。这一步的额外好处是设计师可以把“重复劳动”甩给文本管线自己专心处理公差、表面处理、工艺结构这些机器不擅长的事情。我试过让实习生用这套流程做一个钣金外壳原来一天的工作量缩到半天还不容易出错。这也让我更看好text-to-cad的落地价值——它不制造“全自动”但实实在在把“半自动”做出了生产力。最后再说一点个人体会不要迷信一步到位的端到端生成也不要觉得必须懂图形学才能玩。我实际用下来最顺手的方式是把text-to-cad当成草稿生成器和参数探索器先快速出模型再人工调整细节效率提升非常明显。如果你正被重复建模折磨不妨从今天这个最小工作流开始把第一句“给我一块板子四角打孔”变成自己的第一个STEP文件。