1. 从一段文字到三维模型:text-to-cad 到底在解决什么问题
第一次听到 “text-to-cad” 这个说法,我脑子里蹦出来的画面是:对着电脑敲一句“给我画一个 80×60×20 的带四个 M6 沉头孔的法兰盘”,然后 CAD 软件里就自动长出了这个零件。这个场景放在五年前还像科幻,但现在已经有一批工具和方案在往这个方向走了。所谓 text-to-cad,直译就是“文本转 CAD”,核心逻辑是把自然语言描述或者结构化文本,经过解析、参数化建模、几何运算,最终输出成 CAD 能识别的模型文件,常见的落地格式包括STEP、GLB、STL这几类。
它解决的问题很实际。传统 CAD 建模,哪怕是画一个简单的支架,你也要经历草图、约束、拉伸、打孔、倒角这一整套操作,熟手几分钟,新手半小时。而 text-to-cad 想做的事情,是把“描述需求”和“生成几何”之间的那堵墙拆掉,让不会 CAD 的人也能通过文字拿到模型,让会 CAD 的人从重复劳动里解放出来。适合关注这个方向的人其实挺广:做机械设计的工程师、搞 3D 打印的创客、做参数化建模的程序员,还有那些需要批量生成标准件、想把设计流程自动化的团队。
我先把话说在前面,text-to-cad 目前还不是“一句话生成任意复杂装配体”的魔法。它更擅长的是参数明确、结构规整、有规律可循的零件,比如法兰、支架、齿轮毛坯、外壳、连接件这类。你让它生成一个完整的汽车变速箱,那是不现实的。但你要它批量产出几百个不同尺寸的垫片、管接头,它比人快得多,而且不会手抖画错。理解这个边界,后面的内容你才不会跑偏。
2. 整体方案设计:文本怎么变成几何体
2.1 三种主流技术路线拆解
text-to-cad 落到工程实现上,我见过的方案大致分三类,各有各的适用场景,选错了路线后面会非常痛苦。
第一类是规则模板驱动。思路很朴素:预先写好一批参数化模板,每个模板对应一种零件类型,文本解析出来的参数直接填进模板的变量里,然后调用 CAD 内核生成模型。比如你定义好“法兰盘”模板,里面有外径、内径、厚度、孔数、孔径、孔位圆直径这些变量,用户输入“外径 100,内径 40,厚 15,6 个直径 8 的孔”,解析器提取数字,模板一跑,STEP 文件就出来了。这条路线的优点是稳定、可控、速度快,缺点是只能生成模板覆盖到的形状,遇到没见过的结构就歇菜。
第二类是代码生成驱动。让大语言模型根据文本描述,生成一段参数化建模脚本,比如 CadQuery、OpenSCAD 或者 FreeCAD 的 Python 脚本,然后执行脚本得到模型。这条路灵活得多,理论上能表达任意可以用代码描述的形状。但它的坑也很明显:模型生成的代码不一定能跑通,几何运算可能失败,参数可能超出合理范围,需要一套校验和重试机制兜底。
第三类是几何内核直接求解。把文本转成一种中间几何描述语言,比如 CSG 树或者 B-rep 操作序列,直接喂给几何内核(如 OpenCASCADE)去运算。这条路最接近“原生”,但对文本理解和几何表达的精度要求极高,目前更多出现在研究性质的项目里。
我个人的建议是:如果你要做的是生产可用的工具,优先走规则模板 + 代码生成混合的路线。常见零件走模板,保证稳定;特殊需求走代码生成,保证灵活。纯靠大模型生成代码而不加约束,上线之后你会被各种几何报错折磨到怀疑人生。
2.2 为什么输出格式要同时支持 STEP、GLB、STL
这三种格式不是随便选的,它们对应的是完全不同的下游用途,搞混了会出大问题。
STEP是 CAD 领域的通用交换格式,它保存的是精确的 B-rep 几何,也就是带完整曲面数学定义的实体模型。你把它导入 SolidWorks、中望 CAD、Fusion 360 里,还能继续编辑、测量、装配、出工程图。所以 STEP 是 text-to-cad 面向工程用途的主输出格式,只要你的目标是“拿到能继续加工的模型”,STEP 就是首选。
STL是 3D 打印的老朋友,它用三角网格逼近曲面,只保留表面形状,没有精确的曲面定义,也没有单位、颜色、装配关系。它的好处是几乎所有切片软件都认,坏处是精度靠网格密度堆,圆孔会变成多边形。text-to-cad 输出 STL,主要是为了对接 3D 打印流程。
GLB是 glTF 的二进制版本,主打轻量和 Web 友好,适合在浏览器里做预览、做可视化展示、做 AR 呈现。它的几何精度不如 STEP,但加载快、体积小,适合做“给客户看一眼”的场景。
所以一个成熟的 text-to-cad 流程,往往是一次生成,多格式导出:STEP 给工程师,STL 给打印机,GLB 给网页预览。下面这张表把三者的差异列清楚,选型的时候直接对照。
| 格式 | 几何类型 | 精度 | 可编辑性 | 典型用途 |
|---|---|---|---|---|
| STEP | B-rep 实体 | 精确 | 高,可继续建模 | 工程加工、装配、出图 |
| STL | 三角网格 | 近似 | 低,难编辑 | 3D 打印、快速成型 |
| GLB | 三角网格 | 近似 | 低 | Web 预览、AR、展示 |
注意:如果你的模型要送去 CNC 加工,千万别只给 STL。STL 的曲面是三角面片逼近的,加工时会出现明显的棱面,尺寸也不精确。工程用途必须给 STEP。
2.3 文本解析层的关键设计
文本到几何,中间最关键的一层是语义解析。用户说的话是模糊的,几何需要的是精确数字和明确结构,这中间的翻译质量直接决定成败。
我的做法是分两步走。第一步做实体与意图识别,判断用户要的是什么类型的零件,是法兰、支架、齿轮还是外壳。这一步可以用关键词匹配加分类模型,简单场景关键词就够了。第二步做参数抽取与归一化,把“直径 8 毫米的孔”“8mm 的孔”“孔大小 8”统一成hole_diameter=8,把“六个孔”“6 个孔”“孔数 6”统一成hole_count=6。单位也要归一化,有人写毫米有人写厘米,内部统一用毫米计算,输出时再按需转换。
这里有个容易被忽略的点:缺省值和默认规则。用户不会把所有参数都说全,比如他只说“画个法兰盘”,没说厚度。这时候你得有一套合理的默认值,或者主动追问。我倾向于给每个模板定义一套默认参数,用户没说的用默认值,同时在返回结果里标注“以下参数使用了默认值”,让用户知道哪里可能需要调整。这比直接报错说“参数不足”体验好得多。
3. 核心细节解析:几何生成里的硬骨头
3.1 参数校验与几何可行性判断
文本解析出来的参数,不能直接扔进几何内核,必须先过一遍可行性校验。这一步是很多人偷懒的地方,结果就是内核报一堆看不懂的错误。
校验分几个层次。第一层是数值范围校验,比如孔径不能是负数,厚度不能为零,孔数必须是正整数。第二层是几何关系校验,这是最容易出问题的。举个例子,法兰盘上均布一圈孔,孔的中心圆直径是 D,孔径是 d,外径是 OD,那么必须满足D + d < OD,否则孔会打到外边缘外面去。再比如内径 ID 和孔径 d 之间,如果孔位圆直径 D 减去孔径的一半小于内径的一半,孔就会切穿内孔。这些约束不检查,几何内核要么报错,要么生成一个破面模型。
第三层是拓扑可行性,比如你要在一个厚度只有 2mm 的板上打一个直径 10mm 的沉头孔,沉头深度根本放不下,这种也要提前拦下来。
我一般会把这些校验规则写成一张表,每个模板对应一组约束,解析完参数先跑校验,不通过就返回明确的错误信息,告诉用户哪个参数不合理、应该满足什么条件。这比让内核抛异常友好太多。
| 校验类型 | 检查内容 | 失败示例 | 处理方式 |
|---|---|---|---|
| 数值范围 | 参数是否为正、是否在合理区间 | 孔径 -5 | 直接拒绝,提示修正 |
| 几何关系 | 孔位、壁厚是否冲突 | 孔打到外边缘 | 提示调整孔位圆或外径 |
| 拓扑可行 | 特征是否有足够空间 | 薄板打深沉头孔 | 提示增加厚度或改孔型 |
3.2 参数化模板的变量设计
模板设计是规则驱动路线的核心工作,变量怎么定,直接决定模板的复用能力。我的经验是:变量要覆盖形状的自由度,但不要过度设计。
以法兰盘为例,最少需要这几个变量:外径、内径、厚度、孔数、孔径、孔位圆直径。这六个变量基本能描述一个标准圆法兰。如果你还想支持方形法兰、带颈法兰,那就得拆成不同模板,而不是往一个模板里塞一堆可选参数。模板越单一,逻辑越清晰,维护成本越低。
变量命名要统一,单位要统一,默认值要合理。我见过有人模板里一会儿用d一会儿用diameter,一会儿毫米一会儿英寸,最后自己都搞混。建议全部用英文小写下划线命名,单位统一毫米,在模板头部注释清楚每个变量的含义和约束。
还有一点,变量之间的依赖关系要显式声明。比如孔位圆直径依赖于外径和内径,你不能让用户随便填一个超出范围的值。这种依赖可以在校验层处理,也可以在模板里用表达式约束。CadQuery 这类工具支持在脚本里写条件判断,灵活性更高。
3.3 从脚本到实体的执行链路
如果走代码生成路线,执行链路大致是:文本 → 脚本 → 执行 → 几何对象 → 导出文件。这条链路上每一步都可能出问题,我逐个说。
脚本生成阶段,大模型可能写出语法错误、调用不存在的 API、参数类型不对。我的做法是给模型提供一份受限的 API 白名单和示例代码,让它在这个范围内生成,而不是自由发挥。同时加一层语法检查,语法不过的直接重试。
脚本执行阶段,几何运算可能失败,比如布尔运算遇到退化面、倒角半径过大、拉伸方向错误。这时候要捕获异常,把错误信息翻译成人话返回给用户,而不是抛一个堆栈。
几何对象导出阶段,STEP 导出相对稳定,STL 要注意网格精度参数,GLB 要注意坐标系和单位。STL 的网格精度如果设得太粗,圆孔会变成明显的多边形;设得太细,文件体积爆炸。一般线性偏差设 0.01 到 0.05 毫米,角度偏差设 0.1 到 0.5 弧度,具体看零件尺寸。
# 以 CadQuery 为例,一个法兰盘生成的核心逻辑示意 import cadquery as cq def make_flange(od, id_, thickness, hole_count, hole_dia, pcd): # 基础圆盘 result = ( cq.Workplane("XY") .circle(od / 2) .circle(id_ / 2) .extrude(thickness) ) # 均布孔 if hole_count > 0: result = ( result.faces(">Z") .workplane() .polarArray(pcd / 2, 0, 360, hole_count) .hole(hole_dia) ) return result # 导出 part = make_flange(100, 40, 15, 6, 8, 75) cq.exporters.export(part, "flange.step") cq.exporters.export(part, "flange.stl", tolerance=0.02)这段代码看着简单,但实际项目里要处理的情况多得多:孔数可能为 0,内径可能为 0(实心盘),厚度可能很小导致布尔运算出问题。每一个边界情况都要有对应的处理逻辑。
4. 实操过程:从零搭一个可用的 text-to-cad 流程
4.1 环境准备与工具选型
先把工具链定下来。几何内核我推荐OpenCASCADE,它是开源里最成熟的 B-rep 内核,STEP 支持完善,Python 绑定有 pythonocc。参数化建模层用CadQuery,它封装了 OpenCASCADE,API 友好,适合快速开发。文本解析层用 Python 的 NLP 库加规则引擎,简单场景正则加关键词就够,复杂场景再上模型。
环境搭建步骤:
- 装 Python 3.9 以上版本,建议用虚拟环境隔离依赖。
- 安装 CadQuery:
pip install cadquery,它会自动带上 OCP 内核。 - 安装导出相关依赖,STL 和 STEP 导出 CadQuery 自带,GLB 导出可能需要额外装
trimesh或pygltflib。 - 装一个轻量 Web 框架比如 FastAPI,用来做接口层。
提示:CadQuery 的安装在不同系统上坑不一样,Windows 上一般顺利,Linux 上注意 conda 和 pip 混用可能冲突,建议统一用 conda 装。
4.2 文本解析模块的实现
解析模块我分成三层写。第一层是预处理,把用户输入里的全角字符、多余空格、单位混写清理掉。第二层是实体识别,用关键词表匹配零件类型,比如出现“法兰”“flange”就归到法兰模板。第三层是参数抽取,用正则把数字和单位抓出来,再映射到模板变量。
import re def parse_flange(text): params = {} # 抽取带关键词的数字 patterns = { "od": r"外径\s*([\d.]+)", "id_": r"内径\s*([\d.]+)", "thickness": r"厚(?:度)?\s*([\d.]+)", "hole_count": r"(\d+)\s*个孔", "hole_dia": r"孔(?:径|大小)\s*([\d.]+)", "pcd": r"孔位圆(?:直径)?\s*([\d.]+)", } for key, pat in patterns.items(): m = re.search(pat, text) if m: params[key] = float(m.group(1)) return params这段代码很粗糙,但能跑通基本场景。实际项目里要处理“直径 8 的孔”“8mm 孔”“孔 8 毫米”这些变体,正则要写得更全,或者引入同义词表。我的经验是,先把最常见的十几种表达覆盖掉,剩下的长尾慢慢补,不要一上来就追求完美解析。
4.3 模型生成与多格式导出
参数解析完、校验通过,就进入生成环节。生成函数接收参数字典,返回几何对象,然后分别导出三种格式。这里有个细节:导出路径和文件命名要规范,建议用零件类型_时间戳_随机码的格式,避免并发时文件覆盖。
import os, time, uuid def export_all(part, prefix="model"): ts = time.strftime("%Y%m%d%H%M%S") uid = uuid.uuid4().hex[:6] base = f"{prefix}_{ts}_{uid}" os.makedirs("output", exist_ok=True) step_path = f"output/{base}.step" stl_path = f"output/{base}.stl" glb_path = f"output/{base}.glb" cq.exporters.export(part, step_path) cq.exporters.export(part, stl_path, tolerance=0.02, angularTolerance=0.2) # GLB 导出走 trimesh import trimesh mesh = trimesh.load(stl_path) mesh.export(glb_path) return {"step": step_path, "stl": stl_path, "glb": glb_path}STL 的tolerance参数控制线性偏差,单位是毫米,值越小网格越密。angularTolerance控制角度偏差,单位弧度。这两个参数要根据零件尺寸调,小零件用小值,大零件可以适当放宽,否则文件会大到没法用。
4.4 接口层与返回结构设计
接口层用 FastAPI 写一个 POST 接口,接收文本,返回生成结果的文件路径和预览信息。返回结构要包含:是否成功、使用的模板、解析出的参数、使用的默认值列表、三种格式的下载链接、以及可能的警告信息。
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class GenRequest(BaseModel): text: str @app.post("/generate") def generate(req: GenRequest): params = parse_flange(req.text) # 校验、补默认值、生成、导出 # ... return { "success": True, "template": "flange", "params": params, "defaults_used": ["thickness"], "files": {"step": "...", "stl": "...", "glb": "..."}, "warnings": [] }这个返回结构的好处是,前端可以清楚地告诉用户“你哪些参数没填,我用了默认值”,用户改起来有方向。警告信息则用来提示“孔位圆直径接近外径,建议检查”这类非致命问题。
5. 常见问题与排查技巧实录
5.1 几何生成失败的典型原因
几何内核报错是家常便饭,我把踩过的坑整理成一张速查表,遇到问题直接对照。
| 现象 | 可能原因 | 排查方向 | 解决方式 |
|---|---|---|---|
| 布尔运算失败 | 面退化、自相交 | 检查参数是否导致零厚度 | 调整参数或加容差 |
| 倒角报错 | 半径大于相邻边 | 检查倒角半径与壁厚关系 | 减小半径或改边 |
| 孔打穿外壁 | 孔位圆加孔径超外径 | 校验几何关系 | 提示用户调整 |
| STL 圆孔变多边形 | 网格精度太粗 | 检查 tolerance 参数 | 减小 tolerance |
| STEP 导入后破面 | 内核版本不兼容 | 检查导出内核版本 | 统一内核版本 |
| 导出文件为空 | 几何对象为空 | 检查生成逻辑 | 加空对象判断 |
这张表里,布尔运算失败是最常见的。很多时候不是代码写错了,而是参数组合导致了几何退化,比如两个面刚好重合、一个特征厚度为零。我的经验是,在布尔运算前加一层“最小特征尺寸”检查,任何小于 0.01mm 的特征直接拒绝,能挡掉一大半问题。
5.2 文本解析的歧义处理
用户输入“画个直径 50 的圆盘,中间开个 20 的孔”,这里的“20”是孔径还是半径?是直径还是半径?这种歧义在中文里特别常见。我的处理原则是:默认按直径理解,但在返回结果里明确标注“孔径按直径 20 处理”,让用户有机会纠正。
还有一种歧义是单位。用户说“厚 1”,是 1 毫米还是 1 厘米?如果上下文里其他参数都是几十上百,那 1 大概率是厘米或者更小的单位。但这种推断不可靠,我倾向于默认毫米,并在结果里标注单位假设。如果用户不满意,他会改。
提示:歧义处理的核心不是猜得准,而是让用户知道你是怎么猜的。透明比准确更重要,因为用户能纠正透明的结果,但没法纠正一个默默猜错的结果。
5.3 性能与并发问题
text-to-cad 的生成过程是 CPU 密集型的,几何运算很吃资源。如果接口是同步的,并发一上来就会堵死。我的做法是把生成任务丢到任务队列里,接口只负责接收和返回任务 ID,前端轮询或者用 WebSocket 拿结果。
另外,几何内核不是线程安全的,多线程同时调用可能崩溃。稳妥的做法是用多进程,每个进程独立跑一个内核实例。进程池大小根据 CPU 核数定,一般设成核数减一,留一个给系统。
文件存储也要注意,生成的文件如果都堆在一个目录里,时间长了会卡。建议按日期分目录,定期清理旧文件。如果文件要长期保存,考虑对象存储,别放本地磁盘。
5.4 精度与文件体积的平衡
STL 和 GLB 的体积和精度是一对矛盾。我实测下来,一个直径 100mm 的法兰盘,tolerance 设 0.01mm 时 STL 大概 2MB,设 0.05mm 时降到 300KB,视觉上差别不大,但打印出来的圆孔精度有区别。我的建议是:预览用粗精度,下载用细精度,生成两份文件,前端预览加载粗的,用户点下载给细的。
STEP 文件体积相对稳定,因为它存的是精确几何,不受网格精度影响。但复杂装配体的 STEP 也会很大,这时候可以考虑只导出需要的零件,而不是整个装配。
6. 几个容易被忽略的工程细节
6.1 单位系统的统一
前面提过单位要统一,这里再展开说。内部计算统一用毫米,这是工程领域的惯例。但用户可能输入英寸、厘米、米,解析层要做转换。转换表要写全:1 英寸 = 25.4 毫米,1 厘米 = 10 毫米,1 米 = 1000 毫米。转换后要在结果里标注“已将英寸转换为毫米”,避免用户困惑。
STEP 文件本身带单位信息,导出时确认单位设置正确。STL 和 GLB 不带单位,是纯数值,所以下游使用时要知道数值对应的单位。我一般在文件名或者返回结构里标注单位,比如flange_100mm.step。
6.2 坐标系与朝向
生成的模型朝向要符合直觉。法兰盘一般让轴线沿 Z 轴,圆盘在 XY 平面,这样导入 CAD 后视图是正的。如果朝向乱了,用户每次都要旋转,体验很差。CadQuery 默认的Workplane("XY")就是圆盘在 XY 平面,拉伸沿 Z 轴,符合习惯。
GLB 的坐标系和 CAD 不一样,glTF 用的是 Y 轴向上,而 CAD 常用 Z 轴向上。导出 GLB 时要做坐标转换,否则在网页里看模型是躺着的。这个坑我踩过,调了半天才发现是坐标系问题。
6.3 错误信息的可读性
几何内核的报错信息对普通用户来说就是天书,什么BRep_API: command not done,用户看了完全不知道怎么办。我的做法是在捕获异常后,根据错误类型映射成人话。比如布尔运算失败,返回“模型特征之间存在冲突,请检查孔位是否超出边界或厚度是否过小”。虽然不够精确,但至少用户知道往哪个方向改。
错误信息里还要带上当前使用的参数,让用户知道是在什么参数组合下出的错。这样他调整参数时心里有数。
6.4 版本兼容与格式演进
STEP 有 AP203、AP214、AP242 等不同版本,不同 CAD 软件支持程度不一样。AP242 支持颜色和装配信息,但老软件可能不认。稳妥起见,导出时选AP214,兼容性最好。CadQuery 默认导出的 STEP 版本可以在导出参数里指定,确认一下。
STL 有 ASCII 和二进制两种,二进制体积小,ASCII 可读。生产环境用二进制,调试时可以用 ASCII 看内容。GLB 是二进制,对应的文本版是 glTF,调试时可以导出 glTF 看结构。
7. 后续可以扩展的方向
这套流程跑通之后,能扩展的地方不少。一个是模板库的积累,把常见的标准件都做成模板,覆盖范围越广,工具越有用。另一个是与 CAD 软件的插件集成,让用户在自己熟悉的软件里直接调用,而不是切到网页。还有就是参数推荐,根据用户输入的零件类型,主动推荐合理的参数范围,减少试错。
我自己在实际操作中的体会是,text-to-cad 的价值不在于替代 CAD 工程师,而在于把重复性的、标准化的建模工作自动化。它像一个不知疲倦的绘图员,你给它明确的指令,它就能稳定产出。但复杂的、需要创造性判断的设计,还是得人来。把这两者的边界划清楚,工具才能真正落地,而不是变成一个炫技的玩具。