ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从自然语言到三维CAD模型:text-to-cad技术解析与工程落地实践

从自然语言到三维CAD模型:text-to-cad技术解析与工程落地实践

最近我在帮一个非标自动化团队搭内部工具链,leader提了个需求:能不能让产线工程师直接对着系统说一句“我要一个底板,长200宽150厚8,四角开M6通孔”,然后系统自动吐出STEP文件?这个需求本质上就是text-to-cad——把自然语言变成可直接用于制造的三维CAD模型。现在公开的生成模型和LLM API已经能让这件事在Demo里跑通,但真实工程落地远没有那么光鲜:单位错乱、特征顺序冲突、倒角被吃掉,都是家常便饭。

这篇文章不聊那些夸大的宣传,只讲我实际调用、调试、部署text-to-cad工具链的经验。会先拆清楚它和普通文生图/文生Mesh的区别,再对比当下几种主流实现路线,然后用一个CadQuery+LLM的完整例子带你走一遍从自然语言到STEP文件的流程,最后把我踩过的坑、用过的校验策略和选型建议都摊开讲。无论你是做机械设计、3D打印,还是想在企业内部搭一套“语言驱动设计”的原型,这里的内容都能直接参考。

1. 从一句需求到CAD图纸:text-to-cad到底解决谁的痛点

1.1 一个每天都在发生的低效场景

机械设计里最耗时的往往不是建模本身,而是需求沟通。工程师告诉设计员“我要一个安装支架,能固定直径32的气缸,侧面开两个腰孔”,设计员得先理解需求,再查手册、算尺寸、建模型,来回改好几轮。如果是在团队协作里,口头描述和最终模型经常对不上——等模型出来才发现“我说的是背面固定,你装到前面了”。

text-to-cad瞄准的正是这个环节:把设计意图的语义直接转译成几何约束和参数化特征。它和传统的“模板化建模”不同,模板只能处理固定结构,而语言驱动可以自由组合特征、修改参数、增删孔槽,理论上覆盖的形态空间大得多。

1.2 它不等同于“文生图”或“文生Mesh”

很多人把text-to-cad想象成文生图的3D版,这是个很大的误解。文生图输出像素,精度和可编辑性都不重要;文生Mesh(比如基于扩散模型生成三角形网格)能出很漂亮的形状,但网格是离散的,没有拓扑特征、没有尺寸标注,机床和CAM软件也没法直接拿它编程。

CAD模型的核心是可参数化、可重计算、可制造。比如一个M6螺纹孔,它不是一个圆洞,而是包含了直径、深度、螺纹类型、底孔直径等信息的特征。text-to-cad要做的不是画一个像螺丝孔的孔,而是生成一个“在某个位置、朝某个方向、带M6规格”的可编辑特征。这个要求决定了它在技术路线上必须靠近“约束求解+特征建模”,而不是纯视觉生成。

2. 拆开来看:text-to-cad背后的技术架构

2.1 语义解析层:LLM如何把自然语言变成参数化意图

整个系统最核心的一步,是把自然语言变成“意图树”。比如“长200宽150厚8”不是一句话,而是三个带数值的关键属性;“四角开M6通孔”是循环特征,需要分解成四个位置(四角)和一个操作(开孔)。

我在实际使用中通常让LLM输出结构化的中间表示(JSON或类似OpenSCAD的脚本片段),而不是直接生成最终模型。原因很简单:直接端到端生成很难校验,而中间表示可以逐项检查。比如让模型先填一张表:

{ "base": {"length": 200, "width": 150, "height": 8}, "holes": [ {"position": "corner_1", "diameter": 6, "through": true}, {"position": "corner_2", "diameter": 6, "through": true}, {"position": "corner_3", "diameter": 6, "through": true}, {"position": "corner_4", "diameter": 6, "through": true} ] }

这种结构化输出有几个好处:第一,LLM的幻觉被限制在字段值里,不容易跑偏;第二,我们可以用规则校验数值范围,比如直径不能大于板宽;第三,下游建模程序直接消费JSON,不需要二次解析自然语言。

2.2 模型生成层:CAD内核与脚本化建模

拿到参数化意图之后,怎么变成真正的CAD模型?目前主流做法是借助脚本化CAD库,例如CadQuery、OpenSCAD、Python的OCC封装。CadQuery尤其适合这种场景,因为它用代码构建的“特征”和参数是强对应的:加料、挖槽、开孔、倒角,每一步都是可追溯的操作。

我常用的方式是把LLM输出的JSON翻译成CadQuery代码。注意,这一步尽量不要让LLM直接写整个CadQuery脚本,因为LLM对CAD库API的理解往往有偏差,经常出现对象名拼错、参数类型不对、布尔运算结果不符合预期等问题。更稳的做法是内置一批“特征函数”,比如add_box、add_hole、add_fillet,LLM只需要选择函数并填参数,而不是自由编写几何逻辑。

2.3 校验反馈层:为什么必须回读几何数据

模型生成了,不代表就结束了。文本转CAD的自动流程里最容易被忽视的是校验环节。生成完CAD后,必须回读几何数据,跟原始需求做比对:体积、包围盒、孔位坐标、孔径是否满足公差范围。

我会在每个生成流程后面加一道自动检查:

  • 用Shape.Volume()和BoundBox核对尺寸是否在公差带内;
  • 检查特征数量(比如应该有4个孔,实际生成了几个);
  • 用CAD内核的布尔运算检查是否有自相交或退化面。

这些校验信息会反馈给LLM形成迭代闭环。否则,模型生成一个“看起来对但实际上孔径差了0.5毫米”的零件,直接拿去加工就是废品。

3. 当前三条主流技术路线对比

3.1 路线一:LLM + 脚本化CAD库(最易落地)

把自然语言交给LLM,让它输出结构化的建模指令或调用预置函数,然后用CadQuery/OpenSCAD执行。这个方案的好处是工程上非常可控,每一步都能调试,也能复用现有CAD库的成熟算法(切割、倒角、布尔运算稳定性高)。缺点是表达能力受限于内置特征库,特别复杂的曲面造型很难通过简单的脚本拼出来。

适合的场景:企业内部工具、标准化零件库、快速概念验证。我在大多数非标自动化需求里都用这条路线,因为它和现有PLM/ERP系统最容易集成。

3.2 路线二:专用生成模型直接输出B-rep

一些研究团队和商业公司训练专门的生成模型,直接输出B-rep(边界表示)或参数化特征序列。这种方式上限很高,可以处理复杂的曲面和自由形状,因为模型学的是海量CAD数据的底层分布。但问题也很现实:训练数据难搞(高质量的CAD数据集本身稀有),模型可解释性差,生成结果偶尔会有拓扑错误,需要后处理修复。

当前这路线更适合做概念设计的前端,或者用于生成“灵感草图”。离精密制造还有距离。

3.3 路线三:商业封装与定制工作流

市面上已经有一些产品把text-to-cad做成了“自然语言转CAD操作”的插件,比如在主流CAD软件里输入提示词,自动生成草图、拉伸、打孔等操作记录。好处是无需自己搭环境,坏处是黑盒、不可定制,而且对于复杂多特征模型,生成速度和成功率都还不稳定。

我的建议是:如果你只是尝鲜,用商业插件;如果你要集成到自己的设计流程里,走路线一——自己管LLM和CAD库,可控性最强。

技术路线对比:

维度LLM + 脚本化CAD库专用生成模型直接输出B-rep商业封装工作流
落地难度低,有CAD基础即可高,需要大量数据和算力最低,直接用
可控性强,代码可审计弱,生成过程不透明中,依赖平台约束
复杂曲面能力弱,依赖于特征库强,可学习自由形状中,看平台实现
可定制性高,全链路自控低,基本不可定制低,只能调参数
典型场景工程件、结构件概念设计、外观快速演示、内部试点

4. 实操:用自然语言驱动CadQuery生成一个零件模型

4.1 准备环境与模型约定

我直接给出一个可以跑通的例子。环境用Python + CadQuery,版本建议cadhq 2.4以上,模型用LLM API(我用的是OpenAI风格接口,其他LLM也能改)。先安装:

pip install cadquery

同时准备一个外部的LLM调用函数,把用户的需求发送给模型,返回结构化JSON。为了减少变量,我会先让模型只负责“提取意图+填参数”,不让他写CadQuery代码。

4.2 设计一个可控的Prompt模板

Prompt写得好,是成功的一半。我通常这样写:

你是一个机械设计意图解析器。用户会给出中文描述,你需要提取其中的几何特征,输出JSON。 可用特征: - add_box(length, width, height, center=[x,y,z]) - add_hole(diameter, depth, position=[x,y,z], direction=[0,0,1]) - add_fillet(radius, edge_ids=[...]) 规则: 1. 所有数值使用毫米为单位。 2. 如果用户没有指定方向,孔默认沿Z轴负方向(从上往下)。 3. 输出必须是合法JSON,不要包含任何解释。

这个Prompt有几点设计意图:一是限定特征类型,让模型在封闭集合里“填空”,而不是自由发挥;二是明确默认规则,减少歧义;三是强制只输出JSON,方便程序解析。

4.3 生成、执行、校验全流程

用户输入:“一个底板,长200宽150厚8,四角倒圆角半径10,中心开直径30的通孔。”

LLM会返回类似这样的JSON:

{ "features": [ {"type": "add_box", "params": {"length": 200, "width": 150, "height": 8, "center": [0, 0, 4]}}, {"type": "add_fillet", "params": {"radius": 10, "edge_ids": ["bottom_corners"]}}, {"type": "add_hole", "params": {"diameter": 30, "depth": 8, "position": [0, 0, 8], "direction": [0, 0, -1]}} ] }

我写一个执行器,把JSON映射成CadQuery调用:

import cadquery as cq def build_model(features): result = None for feat in features: if feat["type"] == "add_box": p = feat["params"] result = cq.Workplane("XY").box(p["length"], p["width"], p["height"], centered=(True, True, True)) elif feat["type"] == "add_hole": p = feat["params"] result = result.faces(">Z").workplane() \ .pushPoints([(p["position"][0], p["position"][1])]) \ .hole(p["diameter"], p["depth"]) # ... 其他特征 return result

注意add_fillet我没有直接实现,因为自动识别“四角倒圆角”的边需要一点几何运算。更稳妥的做法是让模型输出“圆角作用于箱体的四个竖直边”,我预先根据box的尺寸计算出这四条边的坐标范围,然后传给edges()选择器。简单点说,先让模型只输出“radius”和“target: vertical_corners”,由程序找到对应边。

生成之后立刻校验:

shape = build_model(features) bbox = shape.val().BoundingBox() print("尺寸:", bbox.xlen, bbox.ylen, bbox.zlen) assert abs(bbox.xlen - 200) < 0.1 assert abs(bbox.ylen - 150) < 0.1 assert abs(bbox.zlen - 8) < 0.1

如果尺寸不对,就把错误信息反馈给LLM,让它修正JSON。这一步迭代通常一两次就能收敛。

4.4 多轮修正:让LLM读懂尺寸标注

我遇到最多的情况是:“中心开直径30的通孔”这个描述挺清楚,但模型输出的position是[30, 0, 8]而不是[0,0,8]——它把“直径30”误解成离中心30毫米了。这种语义错误很难通过规则发现,只能靠回读孔位坐标比对。

我的解法是在校验阶段对每个孔生成“位置-孔径”表格,用自然语言回填给LLM:

校验失败:期望中心孔位置(0,0),实际生成孔位置(30,0)。请检查JSON中hole的position字段,确保中心孔位于板中心。

这种“校验—反馈—重生成”的循环,本质上就是在给模型一把尺子。跑通之后你会发现,text-to-cad真正可用的部分不是第一次生成,而是这个闭环修正机制。

5. 我在真实项目里踩过的坑

5.1 单位与坐标系:最容易静默出错

CAD圈子里最经典的问题就是单位。LLM在互联网语料里见过太多英制数据,可能把“厚度8”理解成8英寸,或者把32理解成32英尺。哪怕模型只犯一次错,生成的模型就直接报废。

我后来在Prompt里反复强调“metric”,并在校验阶段用公差带强制检查。更进一步的做法,是在JSON schema里直接限定数值范围。比如:

{"height": {"type": "number", "minimum": 0.1, "maximum": 1000}}

这样模型输出的离谱数值会被schema拦住,从源头避免错误。

坐标系也需要约定。CAD中有全局坐标系和局部坐标系,孔的位置是相对底板中心还是相对角落?如果不约定,模型会随机理解。我在Prompt里明确“所有坐标以板件底面几何中心为原点”,同时在特征函数里固定这个原点,能减少大量歧义。

5.2 特征冲突:倒角与孔位顺序导致失败

有一次用户描述:“在板上开四个孔,然后整个板倒圆角。”LLM输出先开孔再倒角,CadQuery执行时报错——因为倒角选中了孔的边缘,导致几何体拓扑混乱。

这个问题的本质是特征顺序对几何操作结果的影响。倒角会改变边和面的拓扑,之后再进行钻孔时,定位基准面可能已经被吃掉。正确做法是:先做基础实体,再打孔,最后倒角;或者全做完后统一倒角。

我调整了Prompt,明确告诉模型“特征执行顺序规则:1. 基础体 2. 切割特征 3. 圆角倒角”。同时在执行器里写死顺序,不依赖LLM排序。这样虽然牺牲了一点灵活性,但换来了可靠性。

5.3 幻觉问题:模型能跑,但尺寸语义被吞了

有一次我让系统生成“长200宽150厚8的板,四周开四个直径8的孔,孔距边缘20毫米”,结果执行成功,但孔的位置全部偏移到了板外。原因不是模型不理解,而是它在构建JSON时“忘了”孔距边缘的约束,只填了四个绝对坐标,而这些坐标是基于错误的原点推测的。

这种幻觉在复杂的多重约束下尤其容易被激发。我的处理方式是拆解需求,把每一个约束都显式化为一个独立字段,比如:

{"hole_spacing_from_edge": 20, "hole_positions_relative_to": "edge"}

然后让程序根据这些约束计算实际坐标,而不是让模型直接输出坐标。也就是说,把几何计算从LLM中剥离,LLM只做语义理解,计算交给代码。这几乎消除了一类最隐蔽的错误。

6. 从试用到落地:我的一些选型与部署建议

6.1 什么时候该用text-to-cad,什么时候别用

经过一段时间的实践,我的结论是它非常适合:

  • 标准件、通用结构件的快速建模;
  • 对已有模型的参数化修改(改尺寸、加孔、换材料);
  • 非标自动化前期的方案沟通,快速给出可视化草案。

但它目前不适合:

  • 复杂曲面或自由造型(汽车A面、消费电子产品外观);
  • 精密配合件,公差要求远高于常规标注的场景;
  • 需要完全复现历史图纸细节的场景,因为LLM对老图纸的识别仍有很大偏差。

判断标准很简单:如果这个零件的几何信息能用不超过50个参数完整表达,text-to-cad大概率能做;如果要用上百个曲线控制点,不如老老实实手动建模。

6.2 质量评估:如何衡量生成的CAD可用性

我在落地时建立了一套三层评估机制:

第一层是文本贴合度,用校验脚本比对生成模型的包围盒、孔数量和位置是否满足输入描述,这一步可以用程序自动跑。第二层是几何合法性,用CAD内核的check()方法检测是否有自相交、开放面、退化边;第三层是工艺可行性,这部分需要人判断,比如是否有过于薄的壁厚、是否无法加工。

三层层层递进。第一层没过直接回去改Prompt;第二层没过说明执行器或特征逻辑有bug;第三层才是真正的领域经验,目前没有太好办法自动化。

6.3 后续可以扩展的方向

我最看好的一个方向是把text-to-cad和参数化模板库结合。企业内部积累的典型零件模板,通过LLM实现“自然语言映射到模板参数”,而不是从零生成代码。这样生成的CAD模型天然满足企业标准,也更容易进入下游仿真和CAM环节。

另外,可以在生成的STEP文件里嵌入特征历史树,让设计师在CAD软件里还能继续编辑。目前很多生成结果是“死模型”,不能改参数,这对设计流程是致命的缺陷。如果能让生成的模型保留可编辑性,text-to-cad才能真正成为设计师的助手,而不是一次性玩具。

我个人在实际操作中还有一个体会:不要一上来就追求让LLM直接生成一个完整零件,而是让它先输出“设计意图摘要”,由人确认后再进入建模。这个确认步骤只多花十秒钟,却能把成功率提升一大截。语言模型的语义理解已经不是瓶颈,真正稀缺的是对几何约束和制造知识的严格建模。谁能把这两者结合好,谁才是text-to-cad落地时代的赢家。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进