☰
text-to-cad 实战:从自然语言到三维 CAD 模型的最小原型搭建
2026/10/10 7:11:51 网站建设 项目流程

1. 从一句话到三维模型:text-to-cad 到底在解决什么问题

第一次听到 “text-to-cad” 这个说法,是在一个做机械设计的朋友那里。他当时正对着屏幕上一堆拉伸、旋转、倒角的操作发愁,嘴里念叨着:“要是能直接打一行字,模型就自己长出来就好了。” 这句话其实就点中了 text-to-cad 的核心——用自然语言描述一个零件的形状、尺寸、特征,由系统自动生成对应的三维 CAD 模型。

传统 CAD 建模的流程,不管是 SolidWorks、Fusion 360 还是 FreeCAD,本质上都是“手工作业”:选基准面、画草图、标尺寸、加约束、做特征、改参数。一个中等复杂度的零件,熟练工程师也要花上几十分钟到几个小时。而 text-to-cad 想做的事情,是把这段重复性极高的劳动压缩成一句描述,比如“一个长 80mm、宽 40mm、高 20mm 的长方体,四个角倒 R5 圆角,中心打一个直径 10mm 的通孔”,然后由程序解析这句话,输出 STEP 或 STL 文件。

这件事的价值不在于“取代工程师”,而在于把工程师从大量低价值的重复建模里解放出来。它特别适合几类人:一是需要快速验证结构方案的产品经理和硬件创业者,他们不一定精通 CAD 操作,但需要看到三维形态;二是做批量零件生成的工艺人员,比如标准件库、夹具库的快速搭建;三是做 AI 辅助设计研究的开发者,想探索语言模型和几何内核之间的桥接方式。哪怕你只是刚接触三维建模的新手,理解 text-to-cad 的拆解思路,也能帮你把“描述形状”这件事想得更清楚。

我下面要拆的,不是某个具体商业产品的使用教程,而是这类系统背后通用的技术路径、实现要点和踩坑经验。因为标题只给了 “text-to-cad” 这一个词,所以我会基于这个领域里最常见的工程实践,把整套逻辑补全,让你看完能自己动手搭一个最小可用的原型。

2. 整体设计思路:语言模型和几何内核怎么接起来

2.1 为什么不能直接让大模型“画”出模型

很多人第一反应是:现在语言模型这么强,直接让它输出一个三维模型文件不就行了?实测下来这条路走不通,原因有两个。第一,三维模型的数据量远超文本。一个稍微像样的零件,网格顶点动辄几万个,让模型逐点生成,既慢又容易出错,而且它根本没有空间几何的连续概念。第二,CAD 模型的核心不是网格,而是参数化特征树——拉伸、切除、圆角、阵列这些操作的顺序和参数。语言模型擅长的是符号和语义,不是精确的数值几何运算。

所以正确的思路是:让语言模型做它擅长的事——理解自然语言,把它翻译成结构化的建模指令;然后由专门的几何内核去执行这些指令,生成精确的 B-rep 或网格模型。语言模型负责“听懂人话”,几何内核负责“算准形状”,两者各司其职。

2.2 三种主流技术路线对比

在实际工程里,text-to-cad 的实现大致分三条路,我整理成表格方便对照:

路线核心做法优点缺点适用场景
代码生成路线让模型输出 CadQuery / OpenSCAD 脚本,再执行脚本灵活、可表达复杂特征、易调试依赖模型代码能力、脚本可能报错快速原型、参数化零件
指令序列路线模型输出 JSON 格式的特征操作序列,由自研解释器执行结构可控、易校验、安全需要自己定义指令集、覆盖面有限标准化零件、批量生成
直接参数路线模型只输出关键尺寸参数,套用预定义模板最稳定、结果可预测只能做固定几类形状标准件、简单几何体

我个人的建议是:新手从代码生成路线入手,因为它对“指令集设计”的要求最低,CadQuery 这类库本身就有很好的 Python API,语言模型对 Python 的熟悉程度也最高。等你跑通了,再考虑往指令序列路线收敛,因为那条路在工业场景里更可控。

2.3 一个最小系统的模块划分

不管走哪条路,一个能跑的 text-to-cad 系统至少包含四个模块:

  1. 输入解析模块:接收自然语言,做基本的清洗和意图识别,判断用户是要生成新模型、修改已有模型,还是查询参数。
  2. 语言到中间表示模块:调用语言模型,把描述转成代码或指令序列。这一步是整个系统的核心,也是不确定性最大的地方。
  3. 几何执行模块:用 CadQuery、OpenCASCADE 或自研内核执行中间表示,生成三维实体。
  4. 输出与校验模块:导出 STEP/STL,同时做基本校验——比如尺寸是否为正、特征是否成功、有没有产生空实体。

这四个模块里,第二和第三之间的接口设计最关键。接口定得太死,表达能力就弱;定得太松,执行阶段就容易崩。我后面会详细讲怎么定这个接口。

3. 核心细节解析:从一句话到可执行代码的关键环节

3.1 自然语言里的几何信息怎么抽取

用户说“一个直径 50mm、厚 10mm 的圆盘,边缘倒角 2mm”,这句话里其实藏了四类信息:基本形状(圆盘/圆柱)、尺寸参数(直径 50、厚 10)、特征操作(倒角)、特征参数(2mm)。一个合格的解析流程要把这些分开处理。

我的做法是先让语言模型做一次“结构化抽取”,输出一个中间 JSON,比如:

{ "base_shape": "cylinder", "params": {"diameter": 50, "height": 10}, "features": [ {"type": "chamfer", "target": "top_edge", "size": 2} ] }

然后再由另一个提示词或规则,把这个 JSON 转成 CadQuery 代码。为什么要分两步?因为一步到位让模型直接写代码,它容易在尺寸和特征之间搞混,尤其是多个特征叠加的时候。分两步之后,第一步只关心“抽得对不对”,第二步只关心“写得对不对”,排查问题会清晰很多。

注意:单位是 text-to-cad 里最容易翻车的地方。用户说“50”,可能是毫米也可能是厘米,甚至可能是英寸。我的经验是,在系统提示词里强制约定默认单位为毫米,并要求模型在输出里显式带上单位字段,执行前再做一次单位归一化。

3.2 提示词工程:怎么让模型稳定输出可执行代码

直接对模型说“帮我生成一个零件的 CAD 代码”,结果往往不可控。我试过很多版提示词,最后稳定下来的结构是这样的:

  • 角色设定:明确告诉模型它是一个 CAD 脚本生成器,只输出 Python 代码,不输出解释文字。
  • 库约束:指定只能用 CadQuery 的哪些 API,避免它调用不存在的函数。
  • 输出格式:要求代码以result = ...结尾,方便后续统一取结果。
  • 错误预防:明确禁止使用需要外部文件、网络请求或交互式输入的写法。

一个实际可用的提示词骨架大概长这样:

你是一个 CadQuery 脚本生成器。根据用户的几何描述,输出一段可直接执行的 Python 代码。 要求: 1. 只使用 cadquery 库,导入方式为 import cadquery as cq 2. 所有尺寸单位为毫米 3. 代码最后必须把最终实体赋值给变量 result 4. 不要输出任何解释、注释或 markdown 标记 5. 如果描述缺少关键尺寸,使用合理默认值并在代码中用变量注明

这套提示词跑下来,简单零件的首次成功率能到七八成。剩下的失败案例,大多是特征顺序问题或者边界条件没考虑,比如倒角尺寸大于板厚。

3.3 几何内核的选择与执行环境

执行环节我推荐用 CadQuery,它底层是 OpenCASCADE,能输出 STEP 这种真正的 B-rep 格式,不是单纯的网格。相比之下,OpenSCAD 更轻量,但它是基于 CSG 的,复杂曲面和倒角处理起来不如 CadQuery 顺手。

执行环境一定要做沙箱隔离。因为模型生成的代码本质上是不可信代码,万一它写出个死循环或者删文件的语句,直接在主环境跑就麻烦了。我的做法是用子进程执行,设置超时(比如 30 秒),并且限制可用的模块。下面是一个简化的执行封装:

import subprocess import tempfile import os def run_cadquery_code(code: str, timeout: int = 30): with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: f.write(code) f.write("\nimport cadquery as cq\ncq.exporters.export(result, 'output.step')\n") script_path = f.name try: proc = subprocess.run( ['python', script_path], capture_output=True, text=True, timeout=timeout ) if proc.returncode != 0: return None, proc.stderr return 'output.step', None except subprocess.TimeoutExpired: return None, '执行超时' finally: os.unlink(script_path)

这段代码里,超时设置和错误捕获是必须的。我踩过的坑是:有一次模型生成了一个带巨大阵列的代码,直接把内存吃满,机器卡死。从那以后,我不仅加超时,还会在提示词里限制阵列数量上限。

3.4 结果校验:怎么判断生成的模型“对不对”

模型能跑通、能导出文件,不代表结果是对的。常见的错误包括:尺寸单位错了、特征作用在了错误的面上、布尔运算后实体为空。我一般做三层校验:

  • 语法层:代码能否执行,有无异常。
  • 几何层:导出的实体体积是否大于零,包围盒尺寸是否和描述一致。
  • 语义层:把生成的模型关键参数反查一遍,和用户描述做比对。

第三层最难自动化,我的折中方案是:让语言模型在生成代码的同时,额外输出一份“预期参数表”,执行后从模型里读出实际参数,两者做数值比对,偏差超过阈值就报警。这个思路虽然不完美,但能拦住大部分低级错误。

4. 实操过程:搭一个能跑的最小原型

4.1 环境准备与依赖安装

先把基础环境搭起来。我用的组合是 Python 3.10 + CadQuery + 一个本地或云端的大模型接口。CadQuery 的安装用 conda 最省事,因为它的依赖里有不少几何库,pip 直接装容易缺东西。

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

装完之后,跑一句import cadquery as cq; print(cq.__version__)确认没问题。如果这一步报错,多半是 OpenCASCADE 的动态库没找到,重新用 conda 装一遍通常能解决。

4.2 完整流程代码拆解

下面是我实际在用的一个最小流程,从用户输入到导出 STEP,一共不到一百行。我分段解释关键部分。

第一步,定义提示词和调用模型:

import cadquery as cq from openai import OpenAI client = OpenAI(api_key="你的密钥", base_url="你的接口地址") SYSTEM_PROMPT = """你是一个 CadQuery 脚本生成器。根据用户的几何描述,输出一段可直接执行的 Python 代码。 要求: 1. 只使用 cadquery 库,导入方式为 import cadquery as cq 2. 所有尺寸单位为毫米 3. 代码最后必须把最终实体赋值给变量 result 4. 不要输出任何解释、注释或 markdown 标记 5. 如果描述缺少关键尺寸,使用合理默认值并在代码中用变量注明 """ def generate_code(user_input: str) -> str: resp = client.chat.completions.create( model="你的模型名", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ], temperature=0.2 ) return resp.choices[0].message.content.strip()

这里temperature我设成 0.2,因为几何生成需要稳定,不需要创意。温度高了,同样的描述每次生成的代码结构都不一样,调试起来很痛苦。

第二步,执行代码并导出:

def execute_and_export(code: str, output_path: str = "output.step"): full_code = code + f"\ncq.exporters.export(result, '{output_path}')\n" local_vars = {} try: exec(full_code, {"cq": cq}, local_vars) return output_path, None except Exception as e: return None, str(e)

生产环境里我会把exec换成子进程,但原型阶段用exec更快,方便看报错。

第三步,串起来跑一个例子:

user_input = "一个长80mm、宽40mm、高20mm的长方体,四个竖直边倒R5圆角,中心打一个直径10mm的通孔" code = generate_code(user_input) print("生成的代码:\n", code) path, err = execute_and_export(code) if err: print("执行失败:", err) else: print("导出成功:", path)

我实测跑这句话,模型生成的代码大致是这样的:

import cadquery as cq length = 80 width = 40 height = 20 fillet_radius = 5 hole_diameter = 10 result = ( cq.Workplane("XY") .box(length, width, height) .edges("|Z") .fillet(fillet_radius) .faces(">Z") .workplane() .hole(hole_diameter) )

这段代码逻辑是对的:先建长方体,选竖直边倒圆角,再在上表面打孔。跑出来能正常导出 STEP。但这里有个细节值得说:.edges("|Z")选的是平行于 Z 轴的边,也就是四条竖直边,这个选择器用对了。如果模型写成.edges()不带参数,就会把所有边都倒角,结果就不对了。

4.3 参数计算与选择过程

上面例子里有几个参数是我在提示词里没写、但模型自己补的,比如圆角半径和孔径。这其实是 text-to-cad 的一个隐患:用户没说清楚的地方,模型会“猜”。猜得合理还好,猜得不合理就麻烦了。

我的处理原则是:关键尺寸必须由用户给出,非关键尺寸允许模型用默认值,但要在输出里标注。比如孔径 10mm 如果用户没说,模型可以默认,但最好在返回结果里附带一句“孔径采用默认值 10mm”。这样用户至少知道哪里被自动决定了。

另外,倒角半径和板厚之间有个经验关系:圆角半径一般不超过最短边长的四分之一,否则几何运算容易失败。上面例子里最短边是 20mm,四分之一是 5mm,正好卡在边界上。如果用户要求 R8,那就得提醒他可能失败,或者自动降到安全值。

4.4 批量生成的实操记录

单件生成跑通之后,我试过批量场景:给一个 CSV,每行是一个零件描述,批量生成 STEP 文件。这时候要注意两件事。第一,模型调用要加限流和重试,因为批量请求容易触发接口的频率限制。第二,每个零件的执行要独立隔离,一个失败不能影响后面的。

我当时的做法是用一个简单的队列,每个任务单独起子进程,失败就记录到日志,继续下一个。跑了两百多个标准件描述,成功率大概在 85% 左右。失败的案例里,一半是描述本身有歧义,一半是模型生成的代码有语法或几何错误。这个成功率对于辅助生成来说已经够用了,剩下的靠人工修正。

5. 常见问题与排查技巧实录

5.1 生成代码报错的高频原因

我把实际遇到过的报错整理成了一张速查表,按出现频率排序:

报错现象可能原因排查方法解决思路
NameError: name 'cq' is not defined代码里没导入 cadquery检查生成代码首行在提示词里强制要求导入语句
ValueError: 无法倒角圆角半径大于相邻边尺寸检查半径和最短边关系限制半径上限或提示用户
布尔运算后实体为空切除特征完全穿透或位置错误检查切除位置和尺寸调整特征参数或加校验
执行超时阵列数量过大或死循环看代码里的循环和阵列参数限制阵列上限,加超时
导出的 STEP 打不开实体不是有效 B-rep用 CadQuery 重新读取验证检查是否只生成了线框

这张表里,倒角失败和布尔运算为空是最常见的两个。倒角失败的本质是几何约束不满足,比如你要在一条 2mm 的边上倒 5mm 的角,内核算不出来。布尔运算为空通常是切除体没有和主体相交,或者完全把主体切没了。

5.2 描述歧义怎么处理

自然语言最大的问题是歧义。“一个带孔的板”这句话,孔在哪、多大、几个,全都没说。我的处理策略是分级:

  • 必须明确的:整体外形和主要尺寸。缺了这些,直接反问用户。
  • 可以默认的:孔的位置(默认中心)、圆角大小(默认最小边十分之一)、材料(不影响几何,忽略)。
  • 需要确认的:特征数量、阵列方式、特殊约束。

实际做的时候,我让模型在生成代码前先输出一个“理解确认”,列出它打算用的所有参数。用户看一眼就能发现哪里理解错了。这个确认步骤虽然多了一次交互,但能省下大量返工。

5.3 独家避坑经验

说几个文档里不会写、但实际很要命的点。

第一,别信模型对“对称”的理解。你说“左右对称”,它可能只做了一边的特征然后镜像,但镜像面选错了,结果就不对称。我的做法是,涉及对称的描述,要求模型显式写出镜像操作,而不是靠它自己理解。

第二,单位换算要放在执行前。我遇到过模型把“5 厘米”直接写成5,然后按毫米执行,结果小了十倍。后来我在执行前加了一道单位归一化,把所有尺寸统一转成毫米再传给内核。

第三,STEP 和 STL 的用途要分清。STEP 是精确 B-rep,适合后续编辑和加工;STL 是网格,适合 3D 打印预览。如果你的流程下游是 3D 打印,导出 STL 时要控制网格精度,太粗会丢特征,太细文件巨大。我一般用 0.1mm 的线性偏差,平衡质量和体积。

第四,保存中间代码。每次生成的 CadQuery 代码都存下来,一方面方便复现,另一方面积累多了能看出模型的常见错误模式,反过来优化提示词。

5.4 性能与成本的实际感受

跑一个简单零件,从输入到导出,端到端大概 5 到 15 秒,其中大部分时间花在模型接口调用上,几何执行本身通常一两秒就完了。如果换成更大的模型或者更复杂的描述,时间会拉长到半分钟以上。

成本方面,主要开销是模型调用。我的经验是,把提示词写紧凑、限制输出长度,能明显降低成本。另外,简单零件其实可以用更小的模型,复杂零件再上大模型,做一个路由判断,整体成本能降不少。

6. 这套东西还能往哪些方向扩展

跑通最小原型之后,我陆续试了几个扩展方向,这里分享两个我觉得最有价值的。

第一个是参数化模板库。把常见零件(法兰、支架、齿轮毛坯)做成参数化模板,语言模型只负责从描述里抽参数,然后套模板生成。这样稳定性和速度都大幅提升,因为几何部分不再依赖模型生成代码,而是走预定义的安全路径。代价是覆盖面有限,只能处理模板内的形状。

第二个是多轮修改。用户看到生成的模型后,说“把孔改大一点”“再加一个沉头”,系统要能在已有模型基础上做增量修改,而不是从头生成。实现上,我让模型输出的是“修改指令”而不是完整代码,比如{"action": "modify_hole", "diameter": 15},然后由执行层在已有实体上应用。这条路比单轮生成难不少,但更接近真实的设计迭代场景。

如果你刚开始接触 text-to-cad,我的建议是先把单轮、单零件的流程跑顺,把提示词和执行封装打磨稳定,再去碰多轮和模板。因为单轮都跑不稳的话,多轮只会把问题放大。我自己在这个方向上踩过的坑,大多是因为一开始贪多,想一步到位做复杂特征,结果连最基本的尺寸对齐都没做好。后来退回来,老老实实从长方体、圆柱体这些基础形状开始,反而进展更快。

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

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

立即咨询