☰
text-to-cad 实战:用大模型和 CadQuery 将自然语言生成 STEP/STL/GLB 三维模型
2026/10/8 13:14:24 网站建设 项目流程

1. 从一段文字到三维实体:text-to-cad 到底在解决什么问题

第一次听到 text-to-cad 这个词,很多人会下意识觉得它离自己很远,像是实验室里的概念演示。但如果你真的动手做过参数化建模,就会明白它瞄准的痛点非常具体:从"脑子里有个形状"到"软件里有个可编辑的实体"之间,横着一条又长又枯燥的手工链路。传统流程里,你得先想清楚尺寸,再打开 CAD 软件,一个草图一个草图地画,一个约束一个约束地加,最后拉伸、旋转、倒角,才能得到一个能导出 STEP 的模型。这个过程对于简单零件可能只要十分钟,但对于需要反复调整尺寸的方案,改一个参数就要重画半张草图,时间全耗在重复劳动上。

text-to-cad 想做的事情,就是把这层手工操作压缩成一句话。你输入"一个外径 60mm、内径 30mm、厚度 10mm 的圆环,边缘倒角 2mm",系统直接给你输出一个三维模型文件。这个文件可以是STEP,可以是STL,也可以是GLB。这三个格式分别对应了工程制造、3D 打印和可视化展示三条下游链路,覆盖了从设计师到工程师再到展示人员的完整需求。

我之所以对这个方向感兴趣,是因为在实际工作中接触过太多"改尺寸改到崩溃"的场景。比如做非标设备的结构件,客户今天说要加长 20mm,明天说孔径从 8mm 改成 10mm,每次改动都要重新走一遍建模流程。如果有一个工具能把自然语言直接翻译成参数化模型,哪怕只覆盖标准件和简单几何体,也能省下大量机械劳动。这就是 text-to-cad 的核心价值:它不是要取代 CAD 工程师,而是把工程师从重复建模中解放出来,让他们把精力放在真正需要判断力的地方。

这篇文章适合三类人看。第一类是对 AI 辅助设计好奇、想动手试一下的工程师;第二类是需要批量生成标准件模型、但又不想学复杂脚本的从业者;第三类是纯粹想了解"文字怎么变成三维模型"这个技术链路的技术爱好者。我会从实际可跑通的方案讲起,把每一步的选择理由、踩过的坑、以及实测效果都摊开说清楚。

2. 文字变模型的技术链路:LLM 负责理解,几何内核负责落地

2.1 为什么不能指望大模型直接吐出三维坐标

很多人第一反应是:既然大模型能写代码,那让它直接输出一个 STL 文件的顶点坐标不就行了?我一开始也这么想过,实测下来完全不可行。原因很简单:大模型擅长的是语言和逻辑,不是精确的数值计算。一个稍微复杂一点的模型,STL 文件动辄几万个三角面片,每个面片三个顶点、每个顶点三个浮点数,让模型逐字生成这些数字,既慢又容易出错,而且生成出来的东西根本没有参数化可言,改一个尺寸等于全部重来。

正确的思路是让大模型做它擅长的事——理解意图、提取参数、生成结构化描述,然后把真正的几何计算交给专业的几何内核。这个分工是整个 text-to-cad 链路的核心设计原则。大模型负责把"一个边长 50mm 的立方体,上面挖一个直径 20mm 的通孔"翻译成类似{shape: "box_with_hole", length: 50, hole_diameter: 20}这样的结构化数据,几何内核负责根据这些参数生成精确的 B-rep 实体或网格。

2.2 几何内核的选择:OpenCASCADE 与网格方案的取舍

几何内核这块,目前开源方案里最成熟的是OpenCASCADE(简称 OCCT)。它支持 B-rep 表示,能生成真正的实体模型,导出的 STEP 文件可以被主流 CAD 软件正常读取和编辑。这是它最大的优势:STEP 是工程界的通用语言,你生成的模型能直接进 SolidWorks、中望 CAD、FreeCAD 继续加工,而不是一个只能看不能改的网格壳子。

另一条路线是纯网格方案,直接生成 STL 或 GLB。这条路实现起来简单很多,不需要理解 B-rep 的拓扑结构,只要把三角面片拼出来就行。但代价是模型没有参数化信息,圆孔会变成多边形近似,尺寸精度受网格密度影响。如果你的下游是 3D 打印或者网页展示,网格方案够用;但如果要进工程链路,还是得走 OCCT。

我实测下来的组合是:用 Python 的 CadQuery 库作为几何层,它底层封装了 OCCT,同时提供了非常友好的链式 API。CadQuery 的好处是它本身就是为参数化建模设计的,写出来的代码可读性极强,而且天然支持导出 STEP、STL 两种格式。GLB 的话需要额外走一步转换,后面会讲。

2.3 从自然语言到 CadQuery 代码的映射逻辑

大模型在这里的角色是"翻译官"。你给它一段自然语言描述,它输出一段 CadQuery 的 Python 代码。为什么选 CadQuery 而不是直接输出 OCCT 的 C++ 调用?因为 CadQuery 的 API 更接近人类的建模思维,大模型训练数据里也有大量 CadQuery 示例,生成质量更稳定。

一个典型的映射关系是这样的:

自然语言片段提取的参数生成的 CadQuery 操作
边长 50mm 的立方体length=50cq.Workplane("XY").box(50, 50, 50)
直径 20mm 的通孔diameter=20.faces(">Z").workplane().hole(20)
边缘倒角 2mmradius=2.edges().chamfer(2)
绕 Z 轴旋转 45 度angle=45.rotate((0,0,0), (0,0,1), 45)

这张表看起来简单,但实际落地时会遇到很多模糊表述。比如"一个圆柱"——直径多少?高度多少?如果用户没说,你是追问还是给默认值?我的做法是给一套合理的默认值,同时在输出里明确标注哪些参数是推断的,让用户知道哪里可能需要调整。这比反复追问体验好得多。

3. 搭一套能跑的文字转 CAD 环境:依赖、版本与第一个模型

3.1 环境准备中最容易翻车的依赖问题

CadQuery 的安装是第一个坑。它依赖 OCCT 的 Python 绑定,在不同操作系统上的安装方式差异很大。我建议直接用 conda 或者 mamba 来装,pip 装 CadQuery 在部分平台上会遇到编译问题。

conda create -n text2cad python=3.10 conda activate text2cad conda install -c conda-forge cadquery

装完之后验证一下:

import cadquery as cq result = cq.Workplane("XY").box(10, 10, 10) print(result.val().Volume())

如果输出接近 1000,说明环境没问题。如果报错找不到 OCCT 相关的动态库,大概率是 conda 环境没激活对,或者系统里存在多个 Python 版本冲突。

提示:不要混用 pip 和 conda 安装 CadQuery 及其依赖,OCCT 的二进制绑定对版本非常敏感,混装极易出现符号找不到的问题。

3.2 大模型接口的接入方式与提示词设计

几何层搞定之后,接大模型。这里可以用任何提供 API 的大模型服务,核心是设计好提示词。我的提示词模板大致是这样的:

SYSTEM_PROMPT = """你是一个 CAD 建模助手。用户会用自然语言描述一个三维模型, 你需要输出一段 CadQuery Python 代码来生成这个模型。 要求: 1. 只输出代码,不要输出解释 2. 代码最后必须把结果赋值给名为 result 的变量 3. 所有尺寸单位默认为毫米 4. 如果用户没有指定某个尺寸,使用合理的默认值并在注释中标注 5. 只使用 cadquery 库,不要引入其他依赖 """

这个提示词的关键在于约束输出格式。如果不强制要求赋值给result,模型可能用各种变量名,后续提取结果时就要做兼容处理,很麻烦。强制"只输出代码"也很重要,否则模型会在代码前后加一堆解释文字,解析时还得做字符串清洗。

3.3 第一个完整可跑的例子:从一句话到 STEP 文件

把几何层和模型层串起来,一个最小可跑的例子长这样:

import cadquery as cq def text_to_cadquery(user_input, llm_client): response = llm_client.chat(SYSTEM_PROMPT, user_input) code = extract_code(response) local_vars = {} exec(code, {"cq": cq}, local_vars) return local_vars["result"] def export_step(model, filepath): cq.exporters.export(model, filepath, exportType="STEP") model = text_to_cadquery("一个外径60内径30厚度10的圆环,边缘倒角2mm", client) export_step(model, "ring.step")

这里用exec执行模型生成的代码,安全性上确实有风险,生产环境一定要做沙箱隔离或者代码白名单校验。但在个人实验阶段,这个方案能让你在十分钟内跑通整条链路,先验证可行性再考虑加固。

实测下来,对于圆环、方块、圆柱、带孔板这类基础几何体,一次生成的成功率大概在八成以上。失败的情况主要集中在两类:一是模型对 CadQuery API 记错了,比如把hole()的参数搞混;二是自然语言描述本身有歧义,比如"一个带凹槽的圆柱"没说凹槽在哪一端。

4. 三种输出格式的实战差异:STEP、STL、GLB 各自怎么用

4.1 STEP:工程链路的通行证

STEP 是 text-to-cad 最有价值的输出格式,因为它是唯一能保留完整 B-rep 拓扑信息的格式。什么意思?就是你导出的 STEP 文件拿到 SolidWorks 里打开,它还是一个实体,你能继续在上面打孔、倒角、做装配,而不是一堆散落的曲面。

导出 STEP 在 CadQuery 里就一行:

cq.exporters.export(model, "output.step", exportType="STEP")

但这里有个细节值得注意:STEP 的精度设置。CadQuery 默认的导出精度对大多数场景够用,但如果你做的是精密零件,公差要求在微米级,就需要调整tolerance参数。我一般会在导出前检查一下模型的包围盒尺寸,确认单位是毫米而不是米,因为单位搞错是新手最常见的翻车点。

4.2 STL:3D 打印的默认选择,但精度要自己控

STL 是网格格式,导出时最关键的是弦高公差(linear deflection)和角度公差(angular deflection)。这两个参数决定了曲面被离散成多少三角面片。公差越小,面片越多,文件越大,但曲面越光滑。

cq.exporters.export( model, "output.stl", exportType="STL", tolerance=0.01, angularTolerance=0.1 )

tolerance=0.01意味着曲面上的点与真实曲面的偏差不超过 0.01mm。对于 3D 打印来说,这个精度已经远超大多数打印机的分辨率了。如果你只是做个概念验证,tolerance=0.1就够了,文件能小一个数量级。

注意:STL 导出后圆孔会变成多边形。如果孔径很小而公差设得太大,孔可能直接变成三角形甚至消失。做小孔零件时务必把公差调小。

4.3 GLB:网页展示和快速预览的最优解

GLB 是 glTF 的二进制版本,优势是文件小、加载快、浏览器原生支持。如果你想把生成的模型直接嵌到网页里给客户看,GLB 是最合适的。但 CadQuery 本身不直接支持导出 GLB,需要走一步转换。

我的做法是先把模型导出为 STL,然后用trimesh库读进来再导出 GLB:

import trimesh mesh = trimesh.load("output.stl") mesh.export("output.glb")

这一步转换会丢失 B-rep 信息,但对于展示用途来说无所谓。trimesh还能顺便帮你做网格简化,如果 STL 面片太多导致 GLB 文件过大,可以在这一步降采样。

三种格式的对比总结如下:

格式保留参数化可编辑性典型用途文件大小
STEP是强工程制造、CAD 二次编辑中等
STL否弱3D 打印、网格分析较大
GLB否弱网页展示、AR/VR较小

5. 实测中暴露的问题与我的处理方案

5.1 模型生成的代码执行失败:从报错到修复的完整链路

跑通第一个例子之后,我批量测试了二十多个描述,发现大约有 15% 到 20% 的生成代码会执行失败。失败原因排下来,第一位是API 参数用错,比如box()只传了两个参数、hole()用在了没有工作平面的对象上。第二位是逻辑顺序错误,比如先倒角再打孔,导致倒角面被后续操作破坏。

处理这类问题的思路不是让模型一次生成完美代码,而是加一层自动修复循环。具体做法是:执行失败时,把报错信息和原始代码一起回传给模型,让它修正后重新生成。实测下来,第二轮修复能解决大部分问题,第三轮之后还失败的,基本就是描述本身有歧义,需要人工介入。

def generate_with_retry(user_input, max_retries=3): code = llm_generate(user_input) for i in range(max_retries): try: return execute_code(code) except Exception as e: code = llm_fix(code, str(e)) raise RuntimeError("多次修复后仍无法生成有效模型")

这个重试机制看起来简单,但效果非常明显。我的测试集里,单轮成功率从 80% 提升到了 95% 以上。

5.2 尺寸单位混乱:毫米和米的隐形陷阱

这是最隐蔽的坑。CadQuery 内部默认单位是毫米,但大模型在生成代码时,有时候会"自作主张"把用户说的"0.5 米"直接写成box(0.5, 0.5, 0.5),结果生成一个 0.5 毫米的方块,肉眼几乎看不见。更麻烦的是,这种错误不会报错,模型能正常生成,只是尺寸完全不对。

我的解决方案是在提示词里强制要求所有尺寸先转换为毫米,并且在代码执行后加一道校验:检查模型的包围盒尺寸是否在合理范围内。如果用户说的是"一个手掌大小的盒子",而生成的模型包围盒只有 0.1mm,那就触发警告。

def validate_scale(model, expected_range=(1, 1000)): bbox = model.val().BoundingBox() max_dim = max(bbox.xlen, bbox.ylen, bbox.zlen) if not (expected_range[0] <= max_dim <= expected_range[1]): print(f"警告:模型最大尺寸为 {max_dim}mm,请确认单位是否正确")

5.3 复杂模型的描述歧义:什么时候该追问,什么时候该给默认值

简单模型好办,复杂模型就麻烦了。用户说"一个带法兰的管子",法兰多大?几个螺栓孔?孔怎么分布?这些信息如果全部追问,交互体验极差;如果全部给默认值,生成的结果可能完全不是用户想要的。

我的策略是分层处理:核心尺寸(管径、管长)如果缺失就追问,因为这些参数错了整个模型就废了;次要特征(法兰厚度、螺栓孔数量)给行业常见默认值,同时在输出里明确标注"以下参数为默认值,可调整"。这样既保证了交互流畅,又给了用户修正的入口。

实测下来,这种分层策略在标准件场景下特别好用。比如法兰、轴承座、支架这类有行业惯例的零件,默认值往往就能满足需求,用户只需要微调几个关键尺寸。

6. 把 text-to-cad 用起来:几个真实场景的落地思路

6.1 批量生成标准件库:一次描述,批量出图

这是我觉得最有价值的应用场景。做非标设计的人都知道,标准件虽然叫"标准",但每次用的时候还是得重新建模,因为规格不同。如果用 text-to-cad,你可以写一个循环,把规格表里的参数逐行喂进去,批量生成 STEP 文件。

specs = [ {"name": "ring_60_30", "od": 60, "id": 30, "thickness": 10}, {"name": "ring_80_40", "od": 80, "id": 40, "thickness": 12}, {"name": "ring_100_50", "od": 100, "id": 50, "thickness": 15}, ] for spec in specs: desc = f"外径{spec['od']}内径{spec['id']}厚度{spec['thickness']}的圆环" model = text_to_cadquery(desc, client) export_step(model, f"{spec['name']}.step")

这个思路可以扩展到螺栓、垫片、型材截面等各种标准件。一次配置,批量产出,比手工建模快一个数量级。

6.2 与现有 CAD 工作流的衔接:STEP 导入后的注意事项

生成的 STEP 文件导入到主流 CAD 软件时,有几个细节要注意。第一,导入后检查单位,虽然 STEP 标准里带了单位信息,但不同软件的解析行为不一致,偶尔会出现 1000 倍缩放的问题。第二,检查实体是否有效,极少数情况下生成的 B-rep 会有微小缝隙,导致软件识别为曲面而非实体,这时候需要在 CAD 里做一次"缝合"操作。

我一般会在导入后先做一个简单的测量,确认关键尺寸和预期一致,再进行后续操作。这个习惯帮我避免了好几次因为单位问题导致的返工。

6.3 当前方案的边界:哪些描述它做不了

说了这么多好处,也得说清楚边界。目前的 text-to-cad 方案,对于自由曲面、复杂扫掠、参数化阵列这类高级建模操作,生成质量还很不稳定。比如"一个沿螺旋线扫掠的变截面管道",这种描述即使对熟练的 CAD 工程师来说也需要仔细规划建模步骤,指望大模型一次生成正确代码,成功率很低。

我的建议是:把 text-to-cad 定位为"标准件和简单几何体的快速生成器",而不是"万能建模助手"。在这个定位下,它的效率提升非常明显;超出这个范围,还是老老实实手工建模或者写脚本更靠谱。

另外,代码执行的安全性也值得重视。exec执行模型生成的代码,理论上存在被注入恶意代码的风险。个人实验无所谓,但如果要做成对外服务,一定要加沙箱,限制可调用的库和系统资源。

7. 我在这套流程里踩过的几个坑

第一个坑是过早追求功能完整。一开始我想让系统支持所有 CAD 操作,结果提示词越写越长,模型反而更容易出错。后来砍掉了一大半功能,只保留基础几何体和布尔运算,成功率立刻上来了。这让我意识到,text-to-cad 的价值在于覆盖高频简单场景,而不是追求大而全。

第二个坑是忽略了几何有效性检查。有段时间我发现生成的 STEP 文件在某些软件里打不开,排查了很久才发现是模型存在自相交或者零厚度面。后来在导出前加了一步model.val().isValid()检查,问题就少多了。

第三个坑是对模型输出的稳定性期望过高。同一个描述,不同时间调用大模型,生成的代码可能不一样,有时候能跑通,有时候报错。这不是 bug,而是大模型本身的特性。接受这一点之后,我把重试机制做成了标配,心态也平和了很多。

如果你也想动手试一下,我的建议是从最简单的几何体开始,先把整条链路跑通,再逐步增加复杂度。不要一上来就挑战复杂模型,那样很容易在环境配置和调试上耗尽耐心。跑通第一个圆环、第一个带孔方块之后,你对整个流程的理解会完全不一样。

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

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

立即咨询