1. 从一句话到三维实体:text-to-cad 到底在解决什么问题
第一次听到 “text-to-cad” 这个词,我脑子里蹦出来的画面是:对着电脑敲一行字,比如“一个外径 60mm、内孔 20mm、厚度 10mm 的法兰盘”,然后软件自动吐出一个可以打开、可以编辑、可以直接丢进切片软件的三维模型。这个画面在几年前还属于科幻范畴,但现在已经有一批工具把它做成了现实。所谓 text-to-cad,本质上是把自然语言描述转换成 CAD 可识别的几何数据,最终输出 STEP、GLB、STL 这类通用三维格式。它要解决的核心痛点非常明确:大量非专业建模人员有明确的几何需求,却卡在“不会用 CAD 软件”这道门槛上。
我接触这个方向,最初是因为帮一个做机械配件的小团队做自动化改造。他们每天要处理几十个来自客户的定制需求,很多需求其实就是“改个孔径”“加个倒角”“把长度从 80 改成 120”这种简单变更,但每次都要人工打开 CAD 软件重新画一遍,效率极低还容易出错。那时候我就在想,如果能把客户的自然语言描述直接转成参数化的三维模型,哪怕只覆盖 60% 的常见需求,也能省下大量重复劳动。text-to-cad 正好切中了这个场景。
这篇文章适合几类人看:一是做机械设计、产品设计,想了解如何用自然语言驱动建模的从业者;二是做 3D 打印、手办制作,需要快速生成基础模型的玩家;三是做自动化工具、低代码平台的开发者,想把三维生成能力集成到自己的系统里。我会从整体设计思路讲到具体实操,把踩过的坑和验证过的方案都摊开来说,尽量让不同基础的人都能拿走能用的东西。
2. 整体设计思路:为什么不是“直接生成网格”而是“先生成参数再建模”
2.1 两条技术路线的取舍逻辑
text-to-cad 目前主流有两条路线。第一条是端到端生成网格,用大模型直接输出顶点和面片数据,或者输出隐式场再提取等值面。第二条是自然语言转参数化脚本,让模型输出一段建模代码或参数化指令,再由 CAD 内核执行生成实体。我两种都试过,最后坚定地站在第二条路线上,原因有三个。
第一,可编辑性。端到端生成的网格是一坨“死”的几何,你想改个孔径,只能重新生成或者手动修网格,非常痛苦。而参数化脚本生成的是带特征的实体,孔径、壁厚、倒角都是独立参数,改一个数字就能重新出图。对于工程场景来说,这一点几乎是决定性的。
第二,精度可控。网格生成的分辨率受限于模型输出,圆孔可能变成多边形,平面可能有微小起伏。参数化建模调用的是 CAD 内核的精确几何运算,圆就是圆,平面就是平面,尺寸精度可以做到微米级。做机械配合件的时候,这个差别直接决定能不能装配。
第三,格式兼容。参数化实体可以导出 STEP,这是工业界通用的交换格式,几乎所有 CAD 软件都能打开和继续编辑。网格格式如 STL、GLB 虽然也能用,但在专业设计流程里往往只是最终交付格式,不是中间编辑格式。
注意:如果你的目标只是做视觉展示、游戏资产或者简单的 3D 打印摆件,端到端网格生成也能用,而且速度可能更快。但只要涉及尺寸配合、后续修改、工程交付,参数化路线是唯一靠谱的选择。
2.2 核心架构拆解:从文本到实体的四层结构
我把整个系统拆成四层,每一层都有明确的输入输出和职责边界。
第一层是意图理解层。输入是一句自然语言,输出是结构化的几何意图。比如“一个长 100、宽 50、高 30 的长方体,四个角倒 R5 圆角”,这一层要识别出:基本体是长方体,尺寸参数是 100/50/30,特征是倒圆角,圆角半径是 5,作用位置是四个竖直边。这一层通常用大语言模型做 few-shot 抽取,配合一套预定义的 schema 来约束输出格式。
第二层是参数校验与补全层。大模型抽取的参数经常有缺失或矛盾。比如用户说“做一个法兰盘”,没给尺寸,这时候需要根据常见工程惯例补全默认值,或者主动向用户追问。又比如用户说“内孔比外径大”,这是逻辑矛盾,需要拦截并提示。这一层的存在感很低,但少了它,后面会频繁报错。
第三层是建模脚本生成层。把结构化意图翻译成具体的建模指令。如果用 CadQuery,就是生成一段 Python 代码;如果用 OpenSCAD,就是生成对应的脚本。这一层的关键是模板化,常见几何特征都有对应的代码模板,模型只需要做参数填充和组合。
第四层是执行与导出层。调用 CAD 内核执行脚本,生成实体,然后按需导出 STEP、GLB、STL 等格式。这一层要处理内核报错、几何有效性检查、单位换算等脏活累活。
2.3 为什么选 CadQuery 作为主力内核
市面上参数化建模方案不少,我最终选 CadQuery 作为主力,理由很实际。它是基于 OpenCASCADE 的 Python 库,而 OpenCASCADE 是工业级的三维几何内核,精度和稳定性经过大量工程验证。CadQuery 的 API 设计比较符合直觉,写起来像在描述几何特征,而不是在操作底层数据结构。另外它的社区活跃,遇到问题容易找到参考。
对比 OpenSCAD,CadQuery 的优势在于它操作的是 B-rep 实体,支持真正的圆角、倒角、布尔运算,而 OpenSCAD 基于 CSG,圆角这类特征实现起来很别扭。对比直接调用 FreeCAD 的 API,CadQuery 的抽象层次更高,代码量更少,更适合做自动化生成。
当然 CadQuery 也有坑,比如某些复杂布尔运算会失败,圆角半径过大会导致几何无效,这些后面会详细讲。
3. 核心细节解析:意图抽取、参数映射与脚本模板
3.1 意图抽取的 schema 设计与提示词技巧
意图抽取是整个流程的入口,抽错了后面全错。我设计了一套 JSON schema 来约束输出,核心字段包括:primitive(基本体类型)、dimensions(尺寸字典)、features(特征列表)、units(单位)、constraints(约束条件)。基本体类型我预定义了长方体、圆柱、圆环、球体、棱柱、法兰、支架等常见类别。特征列表里每个特征包含type(如 fillet、chamfer、hole、pocket)、params(特征参数)、target(作用对象)。
提示词方面,我踩过的最大坑是不要指望模型一次抽对所有参数。早期我写了一个很长的提示词,试图让模型一次性输出完整结构,结果经常出现字段缺失、类型错误、单位混乱。后来改成两步走:第一步只让模型判断“这是什么类型的零件”和“有哪些关键尺寸”,第二步再针对具体类型做详细参数抽取。准确率明显提升。
另一个技巧是给模型提供单位换算的显式规则。用户可能说“10 公分”“两寸”“M8 的孔”,这些都要在提示词里给出换算表。M8 孔意味着直径 8mm,两寸管的外径约 60.3mm,这些工程常识要提前喂给模型,否则它会瞎猜。
# 意图抽取的 schema 示例(简化版) intent_schema = { "part_type": "flange | box | cylinder | bracket | ...", "dimensions": { "outer_diameter": "float (mm)", "inner_diameter": "float (mm)", "thickness": "float (mm)" }, "features": [ {"type": "hole", "diameter": "float", "count": "int", "pattern": "bolt_circle"} ], "units": "mm", "confidence": "float 0-1" }3.2 参数补全与冲突检测的实操规则
参数补全这块,我的原则是能推断就推断,不能推断就追问,绝不瞎编。比如用户说“做一个 M8 螺栓孔的法兰”,没给法兰外径,我会根据螺栓孔数量和常见法兰标准,推断一个合理的外径范围,并在输出里标注“外径为推断值,请确认”。如果用户说“做一个很大的板”,这个“很大”无法量化,就必须追问具体尺寸。
冲突检测我总结了几个高频场景。一是尺寸矛盾,比如内孔直径大于外径,或者壁厚为负。二是特征冲突,比如在同一个位置既要求倒圆角又要求倒直角。三是单位冲突,比如同时出现“mm”和“inch”且没有明确换算关系。这些都要在生成脚本之前拦截掉,否则 CAD 内核执行时会报一堆难以理解的错误。
实操心得:我习惯在参数补全后加一个“确认回显”步骤,把最终采用的参数用自然语言复述给用户,比如“我将生成一个外径 80mm、内孔 30mm、厚度 10mm 的法兰,带 6 个直径 8mm 的螺栓孔,均布在直径 60mm 的圆上”。这一步能拦掉大量因为理解偏差导致的返工。
3.3 CadQuery 脚本模板的编写要点
脚本模板的质量直接决定生成成功率。我的做法是按零件类型建立模板库,每个模板是一个参数化的函数,接收标准化的参数字典,返回 CadQuery 的实体对象。模板内部处理常见的几何操作,比如螺栓孔阵列、圆角、倒角、抽壳等。
写模板有几个关键点。第一,所有尺寸参数必须显式传入,不在模板内部硬编码。这样同一个模板可以覆盖不同规格的零件。第二,布尔运算的顺序要固定。先做主体,再做减材特征(孔、槽),最后做倒角和圆角。顺序错了可能导致圆角失败。第三,加异常捕获和几何有效性检查。CadQuery 的val().isValid()可以检查生成的实体是否有效,无效时返回明确的错误信息而不是直接崩溃。
import cadquery as cq def make_flange(outer_d, inner_d, thickness, bolt_hole_d, bolt_circle_d, bolt_count): # 主体圆盘 result = cq.Workplane("XY").circle(outer_d / 2).extrude(thickness) # 中心孔 result = result.faces(">Z").workplane().hole(inner_d) # 螺栓孔阵列 result = ( result.faces(">Z").workplane() .polarArray(bolt_circle_d / 2, 0, 360, bolt_count) .hole(bolt_hole_d) ) # 上下边缘倒角 result = result.edges("%CIRCLE").chamfer(1.0) return result这个模板看起来简单,但实际写的时候要考虑很多边界情况。比如螺栓孔数量为 1 时,polarArray的行为是否正常;倒角半径大于厚度时会不会失败;内孔直径接近外径时壁厚是否足够。这些都要在模板里做校验。
4. 实操过程:从零搭一套可用的 text-to-cad 流水线
4.1 环境准备与依赖安装
先把环境搭起来。我用的组合是 Python 3.10 + CadQuery 2.4 + 一个大模型 API。CadQuery 的安装推荐用 conda,因为它的依赖里有 OpenCASCADE 的二进制包,pip 安装有时候会遇到编译问题。
conda create -n text2cad python=3.10 conda activate text2cad conda install -c conda-forge cadquery=2.4 pip install openai pydantic装完之后跑一个最小验证,确认 CadQuery 能正常工作:
import cadquery as cq box = cq.Workplane("XY").box(10, 10, 10) cq.exporters.export(box, "test.step") print("CadQuery OK")如果这一步报错,大概率是 OpenCASCADE 的动态库没找到,检查 conda 环境是否激活正确。Windows 上偶尔需要手动把 conda 环境的 Library/bin 加到 PATH 里。
4.2 完整流水线的代码实现
整个流水线我写成一个主函数,输入是自然语言字符串,输出是 STEP 文件路径和生成日志。核心步骤包括:调用大模型抽取意图、校验和补全参数、选择模板、执行建模、导出文件。
import json from openai import OpenAI import cadquery as cq client = OpenAI(api_key="your-key") def text_to_cad(user_input: str, output_path: str = "output.step"): # 第一步:意图抽取 intent = extract_intent(user_input) # 第二步:参数校验与补全 params = validate_and_complete(intent) # 第三步:选择模板并执行 part = build_part(params) # 第四步:有效性检查与导出 if not part.val().isValid(): raise ValueError("生成的几何无效,请检查参数") cq.exporters.export(part, output_path) return output_path, params def extract_intent(user_input): prompt = f"""从下面的描述中抽取三维零件的几何参数,输出 JSON。 描述:{user_input} 要求:尺寸单位统一为 mm,缺失的参数标记为 null。""" resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) return json.loads(resp.choices[0].message.content)build_part函数根据part_type分发到不同的模板。我目前维护了法兰、长方体板、圆柱、L 型支架、齿轮毛坯等十几个模板,覆盖了大部分常见需求。
4.3 参数计算实例:一个法兰的完整生成过程
拿一个具体例子走一遍。用户输入:“做一个外径 100mm、内孔 40mm、厚度 12mm 的法兰,用 6 个 M8 螺栓孔,螺栓孔中心圆直径 75mm。”
意图抽取结果:part_type=flange,outer_d=100,inner_d=40,thickness=12,bolt_hole_d=8(M8 对应 8mm),bolt_count=6,bolt_circle_d=75。
参数校验:外径 100 大于内孔 40,壁厚 = (100-40)/2 = 30mm,足够。螺栓孔中心圆直径 75 介于内孔 40 和外径 100 之间,合理。螺栓孔直径 8mm,6 个孔均布在直径 75 的圆上,相邻孔中心角 60 度,弧长 = π × 75 / 6 ≈ 39.3mm,孔间距足够,不会干涉。
执行建模:调用make_flange(100, 40, 12, 8, 75, 6),生成实体,检查有效性,导出 STEP。
整个过程从输入到输出大约 8 到 15 秒,主要时间花在大模型调用上。如果本地部署小模型做意图抽取,可以压缩到 2 秒以内,但抽取准确率会下降,需要权衡。
4.4 导出格式的选择与转换
STEP 是首选导出格式,因为它是工业标准,保留了 B-rep 实体信息,可以被 SolidWorks、Fusion 360、FreeCAD 等软件直接打开和编辑。GLB 适合做可视化展示和网页预览,文件小、加载快,但丢失了精确几何信息。STL 适合 3D 打印,是网格格式,精度取决于细分程度。
我通常同时导出 STEP 和 GLB。STEP 给设计人员做后续修改,GLB 给非技术人员做快速预览。导出 GLB 需要额外装cadquery-ocp的导出模块,或者用trimesh做格式转换。
# 同时导出 STEP 和 GLB cq.exporters.export(part, "output.step") # GLB 导出需要先转成网格 from cadquery import exporters exporters.export(part, "output.glb", exportType="GLB")注意:GLB 导出时如果模型有非常小的特征(比如 0.5mm 的倒角),可能会因为网格细分不足而丢失。做可视化预览没问题,但不要用 GLB 做精度验证。
5. 常见问题与排查技巧实录
5.1 几何生成失败的典型原因与解法
CadQuery 报错最常见的是布尔运算失败和圆角失败。布尔运算失败通常是因为两个实体没有正确相交,或者相交面有微小间隙。解法是检查实体的位置和尺寸,确保减材特征完全穿透或正确嵌入主体。圆角失败通常是因为圆角半径大于相邻边的长度,或者圆角作用在非凸边上。解法是减小圆角半径,或者先做圆角再做其他特征。
还有一个隐蔽的坑是单位问题。CadQuery 默认单位是毫米,但如果从外部导入的模型是英寸,混用会导致尺寸差 25.4 倍。我在流水线里强制所有参数在进入模板前统一转成毫米,避免这个问题。
5.2 大模型抽取错误的兜底策略
大模型不是万能的,抽取错误在所难免。我的兜底策略分三层。第一层是schema 校验,用 Pydantic 定义严格的参数类型和范围,不符合的直接拒绝。第二层是几何合理性检查,比如尺寸是否为正、比例是否合理、特征是否冲突。第三层是生成后验证,检查实体是否有效、体积是否在合理范围、包围盒尺寸是否符合预期。
如果三层都过了但结果还是不对,那就需要人工介入。我会把原始输入、抽取结果、生成参数、预览图一起展示给用户,让用户确认或修正。这个反馈数据收集起来,可以用来微调抽取模型,形成正向循环。
5.3 性能优化与批量生成
单次生成十几秒可以接受,但批量生成几百个零件时就需要优化。我的做法是把大模型调用做成异步并发,同时用缓存避免重复抽取相同或相似的描述。CadQuery 的建模本身很快,瓶颈主要在网络请求上。
另外,如果需求集中在少数几种零件类型,可以考虑本地部署一个小模型专门做意图抽取,牺牲一点准确率换取速度。我实测下来,7B 级别的模型在法兰、板件、圆柱这几类上抽取准确率能到 85% 左右,配合规则校验可以提到 95% 以上,速度比调用云端 API 快一个数量级。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 布尔运算失败 | 实体未相交或有间隙 | 检查减材特征位置和尺寸 | 调整位置或加大穿透深度 |
| 圆角失败 | 半径过大或作用在非凸边 | 减小半径或换边 | 先圆角后其他特征 |
| 导出 STEP 为空 | 实体无效或未生成 | 检查 isValid() | 修正参数重新生成 |
| 尺寸偏差 25.4 倍 | 单位混用 | 检查输入单位 | 统一转毫米 |
| 大模型抽取字段缺失 | 提示词不够明确 | 查看原始输出 | 加 few-shot 示例 |
5.4 几个我踩过的坑和对应技巧
第一个坑是中文描述里的模糊量词。“稍微倒个角”“孔开大一点”这种描述,大模型会瞎猜一个值。我的处理是维护一个模糊量词映射表,“稍微”对应 0.5 到 1mm,“大一点”对应增加 10% 到 20%,并在输出里标注这是推断值。
第二个坑是多零件装配的描述。“做一个轴和一个套,轴能插进套里”这种需求,涉及两个零件和配合关系。我目前的方案是拆成两次生成,分别输出两个 STEP 文件,配合关系用注释说明,暂不做自动装配。自动装配的复杂度太高,投入产出比不划算。
第三个坑是特殊字符和单位符号。用户输入里可能有“Φ”“×”“°”这些符号,直接传给大模型有时候会乱码。我在预处理阶段做统一替换,“Φ”转“直径”,“×”转“x”,“°”转“度”,效果稳定很多。
6. 工具选型与扩展方向
6.1 CadQuery、OpenSCAD、FreeCAD 的对比
这三个是我实际用过的方案,各有适用场景。CadQuery 适合做自动化生成,Python 生态好,API 简洁,B-rep 精度高。OpenSCAD 适合做参数化设计,语法简单,但 CSG 内核在圆角和复杂布尔运算上能力有限。FreeCAD 功能最全,有完整的 GUI 和 Python API,但 API 比较底层,写自动化脚本代码量大。
如果你的场景是“批量生成简单零件”,CadQuery 是最优解。如果是“交互式参数化设计”,OpenSCAD 更直观。如果是“需要完整 CAD 功能且要自动化”,FreeCAD 更合适但开发成本高。
6.2 后续可以扩展的能力
目前这套流水线覆盖的是单零件生成。往后再走,有几个方向值得投入。一是装配体生成,支持多个零件的相对位置和配合关系。二是工程图输出,自动生成三视图和尺寸标注。三是参数优化,根据受力或工艺约束自动调整尺寸。四是与 3D 打印切片软件打通,生成模型后直接切片并估算打印时间。
我个人最看好的是装配体方向,因为实际工程需求里单零件很少,大部分是多个零件的组合。但装配的几何约束和配合关系比单零件复杂得多,需要引入更多的工程知识库。
6.3 给不同基础读者的上手建议
如果你是完全新手,建议先从 CadQuery 的官方示例入手,手动写几个零件脚本,理解参数化建模的思路。然后接一个大模型 API,从最简单的“生成长方体”开始,逐步增加特征复杂度。
如果你有 CAD 基础但不会编程,可以把 text-to-cad 当成一个“快速原型”工具,用它生成基础形状,再导入熟悉的 CAD 软件做精细调整。这样既能享受自动化的效率,又不用完全依赖生成结果。
如果你是开发者,建议把重点放在意图抽取的准确率和模板库的覆盖率上。这两个指标直接决定用户体验。几何内核的调用反而是最稳定的部分,CadQuery 已经帮你处理了大部分底层细节。
最后分享一个我在实际使用中总结的小技巧:把常用的零件描述存成模板短语。比如“标准法兰 100/40/12”对应一组固定参数,用户直接说短语就能生成,比每次描述完整参数快得多,也减少了抽取错误。这个做法在团队内部推广后,生成成功率从 70% 左右提到了 90% 以上。