☰
text-to-cad 实战:从自然语言到三维实体的工程化落地
2026/10/7 17:10:01 网站建设 项目流程

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

第一次听到 "text-to-cad" 这个词,很多人脑子里浮现的画面大概是:对着电脑说一句"给我画个法兰盘",屏幕上就自动长出一个带螺栓孔的三维模型。这个想象不算离谱,但真正落地的时候,它解决的问题比"省几次鼠标点击"要深得多。

传统 CAD 工作流的本质是人肉翻译:需求方用自然语言描述一个零件,工程师在脑子里把它翻译成几何约束、尺寸链、特征树,再用手一根根画线、一个个拉伸。这个过程中最耗时的往往不是建模本身,而是反复确认"你说的倒角 2mm 是内倒角还是外倒角""这个孔是通孔还是盲孔"。text-to-cad 想做的,是把"自然语言 → 几何参数"这一段翻译工作交给程序,让工程师从重复劳动里抽身,专注在真正需要判断力的地方。

它适合谁?三类人最该关注。第一类是做参数化零件批量生成的工程师,比如标准件库、系列化产品,这类场景天然适合文本驱动;第二类是做仿真前处理的人,需要把一批模型转成 URDF 或 STEP 再喂给下游工具;第三类是做 LLM 应用落地的开发者,想找一个"看得见摸得着"的垂直场景练手。如果你只是偶尔画两张图,那这套东西的投入产出比未必划算,但如果你面对的是成百上千个结构相似、参数不同的模型,text-to-cad 的价值会立刻显现。

需要先泼一盆冷水:当前阶段的 text-to-cad 不是"一句话生成任意复杂装配体"的魔法。它更现实的定位是"参数化模板的自然语言前端"——你预先定义好几何逻辑,用文本去填充参数、选择变体、触发组合。理解这个边界,后面的所有设计才不会跑偏。热词里那些 "cad 制图初学入门""cad 教程" 的搜索者,如果抱着"学会这个就不用学 CAD 了"的心态进来,大概率会失望;但如果是想给自己的建模流程加一层自动化外壳,那方向就对了。

2. 文本到几何的翻译链路:LLM 该在哪一层介入

2.1 三种介入深度,决定了系统的天花板

把自然语言变成 CAD 模型,LLM 可以站在三个不同的位置上,难度和可控性差别巨大。

第一种是"参数填充器"。你有一个写死的参数化脚本(比如用 CadQuery 或 OpenSCAD 写的法兰盘生成器),LLM 只负责从文本里抽出outer_diameter=80、bolt_count=6、thickness=12这些数值,然后调用脚本。这是最稳的方案,LLM 出错最多是参数错,几何逻辑永远正确。

第二种是"代码生成器"。LLM 直接输出 CadQuery/OpenSCAD 代码,由 CAD 内核执行。灵活度高,能处理没预定义过的形状,但风险也大——生成的代码可能语法错误、可能几何自交、可能尺寸离谱。你需要一套沙箱执行 + 几何校验的兜底机制。

第三种是"特征序列生成器"。LLM 输出一串抽象操作(拉伸、倒角、打孔、阵列),由中间层翻译成具体 API 调用。这是学术界比较热的方向,工程上落地案例还不多,因为特征序列的语义空间太大,校验成本高。

我的建议很直接:生产环境从第一种做起,把第二种当作探索性功能。原因很简单,参数填充的失败模式是可枚举的(数值越界、单位混淆、必填缺失),而代码生成的失败模式几乎是无限的。你不可能为一个"LLM 偶尔生成自交曲面"的问题写穷举测试。

2.2 为什么 STEP 是绕不开的中间格式

热词里 "STEP" 排在前列不是偶然。text-to-cad 生成的模型最终要流向哪里?可能是仿真软件、可能是 CAM 加工、可能是 PLM 系统。这些下游工具对格式的挑剔程度远超想象。

STEP(AP214/AP242)是目前工业界接受度最广的交换格式,它保留 B-rep 边界表示,能携带颜色、图层、装配层级信息。相比之下,STL 只有三角网格,丢了拓扑关系,倒角和圆角会变成一堆碎面;OBJ 更偏渲染,工程语义几乎为零。所以一条靠谱的 text-to-cad 流水线,中间产物应该是 STEP,而不是直接吐 STL。

这里有个实操细节:CadQuery 导出 STEP 时默认精度是 0.1,做小零件(比如 M3 螺纹孔)时这个精度会导致圆孔变成多边形。你需要显式设置:

import cadquery as cq result = cq.Workplane("XY").circle(40).extrude(12) cq.exporters.export(result, "flange.step", tolerance=0.001, angularTolerance=0.1)

tolerance控制线性偏差,angularTolerance控制角度偏差。做精密件时把这两个值压小,文件会变大但几何更准。我踩过的坑是:导出时没设精度,导入到下游软件做干涉检查,两个本该同轴的孔因为多边形化偏差报了干涉,排查了半天才发现是导出精度问题。

2.3 URDF 和 G-code:两个容易被混淆的下游出口

热词里同时出现了 "URDF" 和 "G-code",这两个方向经常被新手搞混,值得单独说清楚。

URDF是机器人描述格式,它关心的不是"零件长什么样",而是"连杆之间怎么连接、关节怎么运动"。text-to-cad 生成 URDF 的典型场景是:你有一批机械臂连杆的 CAD 模型,需要批量转成 URDF 做仿真。这时候文本输入可能是"生成一个 6 自由度机械臂的 URDF,基座半径 100mm,大臂 300mm……",系统调用 CAD 生成几何,再按运动学关系组装成 URDF。热词里 "urdf 导入 coppeliasim" 说明很多人卡在仿真软件这一环——URDF 的<origin>和<axis>写错一个符号,模型在仿真里就会飞出去。

G-code是数控加工指令,它关心的是"刀具怎么走"。从 text-to-cad 到 G-code 中间还隔着 CAM 工序规划,不是简单转换。文本输入更可能是"把这个零件用 6mm 立铣刀加工,留 0.2mm 精加工余量",系统生成刀路再后处理成 G-code。这条链路目前自动化程度还很低,因为刀具选择、装夹方案、切削参数都强依赖工艺经验。

我的判断是:URDF 方向比 G-code 方向更容易做出可用产品,因为 URDF 的规则是确定的、可校验的,而 G-code 的生成质量高度依赖工艺知识,很难用纯文本驱动。

3. 用 CadQuery 搭一条最小可用的 text-to-cad 流水线

3.1 环境准备里最容易翻车的三个点

先说环境。CadQuery 的安装是新手第一道坎,热词里 "cad 安装""安装 cad 一直出现 c++2005cpi 错误" 这类问题在 Python CAD 生态里同样存在,只是换了个形式。

第一个坑是 OCP 依赖。CadQuery 2.x 底层依赖 OCP(OpenCASCADE 的 Python 绑定),这个包体积大、编译复杂。用 pip 直接装经常卡在编译阶段。稳妥做法是用 conda:

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

conda-forge 有预编译好的 OCP 二进制,省去编译痛苦。如果你非要用 pip,至少确保 Python 版本在 3.9-3.11 之间,太新或太旧都可能没有对应的 wheel。

第二个坑是显示后端。CadQuery 的可视化依赖 VTK,在无头服务器上跑会报错。如果你只是做批量生成不需要交互预览,可以完全不装可视化组件,只装核心库。

第三个坑是单位。CadQuery 默认单位是毫米,但如果你从别处导入的模型是英寸,混用会导致尺寸差 25.4 倍。我的习惯是在脚本开头显式注释单位,并且在参数入口做一次单位归一化。

3.2 一个法兰盘生成器的完整拆解

光说概念没意思,直接上一个能跑的完整例子。假设我们要做一个"根据文本描述生成法兰盘"的最小系统。

先定义参数化模板:

import cadquery as cq from pydantic import BaseModel, Field class FlangeParams(BaseModel): outer_dia: float = Field(..., gt=20, lt=500, description="外径 mm") inner_dia: float = Field(..., gt=0, description="内径 mm") thickness: float = Field(..., gt=1, lt=100, description="厚度 mm") bolt_count: int = Field(..., ge=3, le=24, description="螺栓孔数量") bolt_circle_dia: float = Field(..., description="螺栓孔分布圆直径 mm") bolt_hole_dia: float = Field(8.0, description="螺栓孔直径 mm") def build_flange(p: FlangeParams): if p.inner_dia >= p.outer_dia: raise ValueError("内径必须小于外径") if p.bolt_circle_dia >= p.outer_dia or p.bolt_circle_dia <= p.inner_dia: raise ValueError("螺栓分布圆必须落在内外径之间") body = ( cq.Workplane("XY") .circle(p.outer_dia / 2) .circle(p.inner_dia / 2) .extrude(p.thickness) ) holes = ( cq.Workplane("XY") .polarArray(p.bolt_circle_dia / 2, 0, 360, p.bolt_count) .circle(p.bolt_hole_dia / 2) .extrude(p.thickness) ) return body.cut(holes)

注意几个设计决策。为什么用 pydantic 做参数校验?因为 LLM 抽出来的参数经常越界,比如把"外径 80"抽成 800,或者把螺栓数量抽成 0。在几何生成之前拦截这些错误,比生成完再检查几何要便宜得多。为什么在 build 函数里再做一次逻辑校验?因为 pydantic 只能校验单字段范围,跨字段的几何约束(内径必须小于外径)它管不了。

为什么用polarArray而不是手动循环?手动循环生成 N 个圆柱再布尔减,在 N 大时性能会崩。polarArray是内核级操作,效率高一个数量级。我实测过 24 个孔的阵列,手动循环要 3 秒多,polarArray 只要 0.2 秒。

3.3 把 LLM 接进来的正确姿势

有了模板,接 LLM 就简单了。核心是用结构化输出约束 LLM,而不是让它自由发挥:

from openai import OpenAI import json client = OpenAI() SYSTEM_PROMPT = """你是一个 CAD 参数抽取助手。 从用户描述中抽取法兰盘参数,输出 JSON。 字段定义: - outer_dia: 外径,单位 mm - inner_dia: 内径,单位 mm - thickness: 厚度,单位 mm - bolt_count: 螺栓孔数量,整数 - bolt_circle_dia: 螺栓孔分布圆直径,单位 mm - bolt_hole_dia: 螺栓孔直径,单位 mm,默认 8 如果用户没提到某个字段,不要瞎猜,返回 null。 单位换算:1 英寸 = 25.4 mm,1 cm = 10 mm。 """ def parse_flange_text(user_text: str) -> dict: resp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_text}, ], response_format={"type": "json_object"}, temperature=0, ) return json.loads(resp.choices[0].message.content)

这里有几个关键点。temperature 设为 0,参数抽取是确定性任务,不需要创造性。用 json_object 模式,避免 LLM 输出一堆解释性文字。明确要求缺失字段返回 null,而不是让模型"合理推测"——推测出来的参数比缺失更危险,因为用户不知道哪个值是编的。

拿到参数后,缺失的字段要么用默认值,要么反问用户。我的做法是:关键尺寸(外径、内径、厚度)缺失就反问,次要尺寸(螺栓孔直径)缺失就用默认值。这个策略在交互体验和自动化程度之间取得了平衡。

4. 实测中暴露的五个真实问题与应对

4.1 单位混淆:最隐蔽也最致命

我做过一个测试,给系统输入"外径 3 英寸的法兰盘",LLM 抽出来的outer_dia是 3。如果直接喂给模板,生成的就是一个 3mm 的迷你法兰盘,肉眼几乎看不见。

根因是 LLM 对单位的处理不稳定,有时候换算有时候不换算。解决方案是在 prompt 里强制要求输出单位,然后在代码里做二次校验:

def normalize_unit(value: float, unit: str) -> float: factors = {"mm": 1.0, "cm": 10.0, "m": 1000.0, "inch": 25.4, "in": 25.4} if unit not in factors: raise ValueError(f"未知单位: {unit}") return value * factors[unit]

更稳的做法是让 LLM 输出{"value": 3, "unit": "inch"}这样的结构,而不是直接输出换算后的数值。换算交给确定性代码做,LLM 只负责识别。

4.2 几何自交:布尔运算的经典陷阱

当螺栓孔分布圆太靠近外径时,孔会切穿外壁,生成的实体出现开口。更糟的是,某些情况下布尔运算会生成自交曲面,STEP 导出后下游软件直接报错。

排查链路是这样的:先看生成的模型体积是否异常(自交模型体积往往偏小或为负),再用Shape.isValid()检查拓扑有效性:

solid = build_flange(params) if not solid.val().isValid(): raise RuntimeError("生成的几何无效,请检查参数")

但isValid()不是万能的,有些自交它能过。更可靠的办法是导出 STEP 后再用独立工具校验,比如用pythonocc重新读入检查。我一般会在流水线里加一道"导出-重读-校验"的关卡,虽然慢一点,但能拦住 90% 的坏模型。

4.3 参数越界:LLM 的"合理想象"

用户说"做一个大法兰盘",LLM 可能抽出一个outer_dia=1000。这在数值上合法,但可能远超实际需求。这类问题的本质是自然语言里的模糊量词没有对应到具体数值。

我的处理方式是在 prompt 里给出参考区间,比如"常规法兰盘外径在 50-300mm 之间,如果用户描述模糊,取中间值并标注为估计值"。同时在返回结果里带上confidence字段,让用户知道哪些值是确定的、哪些是猜的。

4.4 批量生成时的内存泄漏

做系列化零件时,你可能要在一个进程里生成几百个模型。CadQuery 底层是 C++ 对象,Python 的垃圾回收对它们不完全有效。跑几百个之后内存会持续上涨,最后 OOM。

解决办法是显式释放:

import gc for params in param_list: solid = build_flange(params) cq.exporters.export(solid, f"{params.outer_dia}.step") del solid gc.collect()

del加gc.collect()能回收大部分内存。如果还不行,就把生成任务拆成多个子进程,每个进程处理一批就退出,用进程隔离来兜底。

4.5 下游格式转换的精度损失

从 STEP 转 STL 做 3D 打印时,如果精度设得太粗,圆孔会变成明显的多边形。热词里 "cad 转 pdf""cad 图纸合并" 这类需求背后其实是同一个问题:格式转换时的精度参数没调对。

转 STL 时关键参数是linearDeflection和angularDeflection:

cq.exporters.export( solid, "part.stl", tolerance=0.01, # 线性偏差,越小越精细 angularTolerance=0.1, # 角度偏差,弧度制 )

经验值:做 3D 打印用tolerance=0.01,做快速预览用0.1。文件大小和精度基本是平方关系,精度提高 10 倍,文件大 100 倍,要按实际需求权衡。

5. 从单件生成到批量流水线:工程化的几个关键决策

5.1 模板库怎么组织才不会被自己坑

当模板从 1 个变成 20 个,管理就成了问题。我见过有人把所有模板塞进一个templates.py,结果改一个法兰盘参数影响了齿轮的生成。

推荐的组织方式是按"几何族"分文件,每个文件一个族,族内共享基础几何函数:

templates/ __init__.py registry.py # 模板注册表 flanges.py # 法兰盘族 gears.py # 齿轮族 brackets.py # 支架族 common.py # 共享几何工具

registry.py维护一个name -> (param_model, build_func)的映射,LLM 先做意图分类决定用哪个模板,再抽参数。这样新增模板不用改主流程,符合开闭原则。

5.2 意图分类:比参数抽取更容易出错的一环

用户说"做个连接件",系统怎么知道是法兰、支架还是接头?意图分类的准确率直接决定用户体验,而且它比参数抽取更难,因为类别之间的边界是模糊的。

我的做法是用 few-shot 示例 + 明确的类别定义,而不是让 LLM 自由判断:

INTENT_PROMPT = """判断用户想要生成的零件类型,从以下类别中选择: - flange: 法兰盘,特征是圆盘状、有中心孔和一圈螺栓孔 - bracket: 支架,特征是 L 形或 U 形、有安装孔 - gear: 齿轮,特征是轮齿、有中心孔 - shaft: 轴,特征是细长圆柱、可能有键槽 - unknown: 无法判断 示例: "做个圆盘带六个孔的" -> flange "做个 L 形固定件" -> bracket "做个 20 齿的传动轮" -> gear """

如果分类结果是unknown,就反问用户,而不是硬猜。宁可多问一句,也不要生成一个完全不对的零件,后者对用户信任的伤害更大。

5.3 缓存与增量生成

批量场景下,很多参数组合是重复的。比如生成 100 个法兰盘,其中 30 个只是螺栓孔数量不同,外径内径都一样。对几何结果做缓存能省大量时间。

缓存的 key 是参数的哈希:

import hashlib import json def param_hash(params: dict) -> str: canonical = json.dumps(params, sort_keys=True) return hashlib.md5(canonical.encode()).hexdigest()

但要注意,缓存的是几何结果还是文件路径要分清。缓存几何对象会占内存,缓存文件路径更省但读取有 IO 开销。我的选择是缓存 STEP 文件路径,因为下游通常也是按文件消费的。

5.4 错误处理:让失败可追溯

流水线跑批量任务时,最怕的是"跑到第 73 个挂了,但不知道为啥"。每个生成任务都要有独立的日志和错误捕获:

import logging logger = logging.getLogger("t2cad") def safe_generate(params: dict, out_path: str) -> dict: try: solid = build_flange(FlangeParams(**params)) cq.exporters.export(solid, out_path) return {"status": "ok", "path": out_path} except Exception as e: logger.exception(f"生成失败: {params}") return {"status": "error", "params": params, "error": str(e)}

返回结构化结果而不是抛异常,这样批量任务能继续跑,最后统一汇总失败项。失败项要带上原始参数,方便复现和修正。

6. 这套东西的边界在哪里,以及我踩过的那些坑

6.1 复杂曲面:text-to-cad 目前的天花板

参数化模板能搞定的是规则几何:圆柱、圆锥、平面、规则阵列。一旦涉及自由曲面(比如涡轮叶片、人体工学手柄),模板方法就无能为力了。这类形状需要 NURBS 控制点或网格驱动,自然语言到控制点的映射目前还没有可靠方案。

所以别指望用 text-to-cad 生成一个汽车外形。它的舒适区是标准件、系列化零件、结构件。认清这个边界,能省下大量试错时间。

6.2 装配体:从零件到产品的鸿沟

单件生成跑通后,很自然会想"能不能生成整个装配体"。答案是能,但难度陡增。装配涉及配合关系(同轴、贴合、间隙)、运动约束(旋转副、滑动副)、干涉检查。自然语言描述装配关系比描述单个零件模糊得多,"把轴插进孔里"到底是过盈配合还是间隙配合?

我的建议是装配关系用结构化配置描述,而不是自然语言。文本只负责生成零件,装配用 YAML 或 JSON 定义:

assembly: - part: shaft params: {dia: 20, length: 100} - part: bearing params: {inner_dia: 20, outer_dia: 42} mate: {type: coaxial, with: shaft, offset: 30}

这样职责清晰:LLM 管零件参数,配置文件管装配逻辑。

6.3 我踩过的最大的一个坑:过度信任 LLM 的数值

早期我做参数抽取时,没做范围校验,结果 LLM 把一个"厚度 5mm"抽成了thickness=5e-3(大概是把它当成米了)。生成的模型薄如蝉翼,导出后下游软件直接崩溃。从那以后我所有的数值参数都加了 pydantic 的gt/lt约束,宁可报错也不让离谱值流下去。

另一个坑是LLM 会"补全"用户没说的参数。用户只说"外径 80 的法兰盘",LLM 可能自作主张补上inner_dia=40、thickness=10。这些值看起来合理,但用户根本没要求。解决办法是在 prompt 里反复强调"未提及的字段返回 null",并且在代码里对 null 做显式处理(用默认值或反问),而不是让 LLM 填。

6.4 关于热词里那些 CAD 安装问题的题外话

热词里大量出现 "cad 安装""cad 激活页面脚本发生错误""cad 如何彻底卸载不影响二次安装" 这类问题,说明很多人的痛点还停留在"把软件装起来"这一层。如果你正在这个阶段,我的建议是:做 text-to-cad 不需要装任何商业 CAD 软件。CadQuery、OpenSCAD、FreeCAD 的 Python 接口都是免费开源的,装好 Python 环境就能跑。商业 CAD 的二次开发接口(比如某些软件的 API)反而更封闭、更贵、更难自动化。把精力放在几何逻辑和文本解析上,比折腾软件安装划算得多。

至于 "cad 切地形""cad 图纸合并""cad 导入 layout" 这些偏工程制图的需求,和 text-to-cad 的关系不大,属于另一个技术栈。text-to-cad 聚焦的是从零生成三维实体,不是处理已有的二维图纸。分清楚这个区别,能避免走弯路。

6.5 一个实用的小技巧:用自然语言做参数扫描

text-to-cad 有个容易被忽略的用法:批量参数扫描。做优化或 DOE(实验设计)时,你需要生成一系列参数渐变的模型。用文本描述这个扫描过程,比写循环更直观:

scan_spec = "外径从 60 到 120,步长 10,其他参数固定" # 解析成参数列表 param_sets = [ {"outer_dia": d, "inner_dia": 30, "thickness": 10, "bolt_count": 6, "bolt_circle_dia": d - 15} for d in range(60, 121, 10) ]

这个用法把 text-to-cad 从"生成单个模型"扩展到了"生成模型族",对做仿真分析的人特别有用。我实测下来,用这种方式准备 50 个仿真模型,比手动建模快了不止一个数量级。

最后分享一个我在实际项目里总结的原则:文本负责"想要什么",代码负责"怎么实现",校验负责"是否合理"。这三层职责分清楚,系统就稳了。把 LLM 当成一个"能听懂人话的参数录入员",而不是"全能的建模师",心态摆正,落地就顺了。

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

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

立即咨询