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

发布时间:2026/10/25 6:38:24
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 + CadQuery

2.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.12,import就会报一堆DLL错误。我建议直接用conda建独立环境,指定python=3.10,然后pip install cadquery。

另外,CadQuery底层依赖OpenCascade,Windows下会用到一些Visual C++运行库。热词里那句“安装cad一直出现c++2005cpi错误”描写的其实就是这类问题:你的系统缺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(f"Unsupported 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, exportType='STEP'),然后在文件开头检查STEP头部的单位关键字。另外,建议在用户描述里强制标注单位,提示词里写明“所有尺寸默认毫米”。热词里有些人搜“cad图纸合并”其实就是在对尺寸不一致的零件做适配,如果源头单位就乱,后面合并时全乱套。

4.4 安装依赖时的常见报错与解决方案

最后说说环境问题。热词里“安装cad一直出现c++2005cpi错误”“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, loc=cq.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文件。