☰
text-to-cad实战:从自然语言到参数化三维模型的完整链路
2026/10/10 7:13:40 网站建设 项目流程

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

第一次听到 “text-to-cad” 这个说法,是在一个做机械设计的朋友那里。他当时正对着屏幕上一堆拉伸、旋转、倒角特征发愁,嘴里念叨着:“要是能直接打一行字,模型就自己长出来就好了。” 这句话其实点中了这个方向最核心的痛点——用自然语言直接生成 CAD 模型,跳过繁琐的草图绘制和特征操作,把“描述”变成“几何”。

text-to-cad,拆开看就是 text(文本)到 CAD(计算机辅助设计)的映射。它不是简单地把文字贴到模型上,而是让程序理解“一个边长 50 毫米、带四个 M6 沉头孔的方形法兰盘”这样的描述,然后自动输出可编辑的实体模型文件。这件事如果做成,受益最大的是三类人:一是需要快速验证结构方案的产品经理和工业设计师,二是经常要建标准件库、重复劳动多的机械工程师,三是做创客项目、偶尔需要定制零件但不会复杂建模的开发者。

我花了大概两个月时间,断断续续地折腾这个方向,从最开始的“用大模型直接吐 STL”到后来老老实实做参数化解析,中间踩了不少坑。这篇文章就把我整个探索过程拆开来讲,包括整体思路怎么定、核心环节怎么实现、参数怎么算、遇到问题怎么排查。如果你也在琢磨怎么让文字变成三维模型,或者单纯想了解这个方向目前能做到什么程度,下面的内容应该能帮你省下不少试错时间。

2. 整体方案怎么选:三条路线和我的取舍逻辑

2.1 直接生成网格 vs 参数化建模 vs 混合路线

在动手之前,我先梳理了三条可能的技术路线,每条都有明显的优缺点。

第一条是直接生成网格。思路很简单:拿一个在大量三维数据上训练过的生成模型,输入文本描述,直接输出 STL 或 OBJ 格式的三角网格。这条路听起来最“端到端”,但实际用下来问题很大。生成的网格往往拓扑混乱,面片数量爆炸,而且尺寸完全不可控——你说“边长 50 毫米”,它可能给你一个比例差不多但实际尺寸是 37.2 毫米的东西。更麻烦的是,这种网格没法直接用于后续的装配和加工,因为孔位、平面度、垂直度这些工程约束全丢了。

第二条是参数化建模。核心思路是把自然语言解析成一组结构化的参数和操作序列,然后调用 CAD 内核(比如 OpenCASCADE 或类似的几何引擎)来执行建模命令。比如“一个 100x60x10 的板,四角各有一个直径 8 的通孔,孔中心距边 15 毫米”,解析出来就是:创建长方体(长 100、宽 60、高 10),然后在四个角点位置各打一个直径 8 的圆柱孔,孔心坐标由边距 15 推算。这条路生成的模型是真正的 B-rep 实体,可以导出 STEP、IGES,也能继续编辑。缺点是自然语言到参数的解析需要处理很多歧义,而且只能覆盖规则几何体。

第三条是混合路线。用参数化方法处理规则结构,遇到自由曲面或复杂有机形状时,再调用网格生成模型来补。我最终选的是这条路线,但把重心放在了参数化上,网格生成只作为兜底方案。原因很实际:工业场景里 80% 的需求是法兰、支架、壳体、连接件这类规则零件,参数化能覆盖大部分情况,而且生成的模型质量高、可编辑、尺寸准。自由曲面虽然看起来酷,但实际工程中用到的比例没那么高,而且一旦尺寸不准,后续装配就是灾难。

2.2 为什么不用端到端大模型直接出 CAD 命令

有人可能会问:现在大模型写代码这么强,为什么不直接让它输出 CAD 脚本(比如 OpenSCAD 或 CadQuery 的代码),然后执行脚本生成模型?我试过,而且试了很久。结论是:可以,但需要非常严格的约束和校验。

直接让模型写 CadQuery 代码,最大的问题是它经常“编造”不存在的 API,或者把参数单位搞混。比如你让它画一个“直径 20 的圆”,它可能写成circle(20),但 CadQuery 里circle的参数是半径,结果出来直径 40。再比如布尔运算的顺序,先并后差和先差后并,结果完全不同,模型经常搞反。更隐蔽的问题是坐标系——它可能默认把孔打在 Z 轴方向,但你的描述里隐含的是沿 Y 轴贯穿。

所以我的做法是:不让模型直接写最终代码,而是让它输出结构化的 JSON 描述,再由我自己写的解析器把 JSON 翻译成 CAD 内核调用。JSON 的 schema 是我预先定义好的,包含shape_type、dimensions、features、constraints这些字段。模型只需要填参数,不需要关心 API 细节。这样既利用了大模型的语义理解能力,又把几何操作的确定性牢牢握在自己手里。

2.3 工具链选型:解析、建模、渲染各用什么

工具链方面,我最终定下来的是这样一套组合:

  • 文本解析:用一个中等规模的语言模型做意图识别和参数抽取,输出 JSON。没有用最大的模型,因为解析任务相对固定,中等模型够用,而且推理速度快,本地跑得动。
  • 几何建模:CadQuery 作为主要建模库,底层是 OpenCASCADE。选它是因为 Python 生态好,代码可读性强,而且导出的 STEP 文件兼容性不错。
  • 校验与修复:自己写了一套规则校验,检查尺寸是否为正、孔是否在实体内部、壁厚是否过薄等。发现问题就回退到模型重新解析,或者给出提示让用户补充描述。
  • 可视化:用 trimesh 做快速预览,把 CadQuery 生成的实体转成网格显示在网页上,方便确认形状对不对。

这套组合不是唯一解,但对我来说平衡了开发效率和生成质量。如果你更熟悉 OpenSCAD,把 CadQuery 换掉也行,核心思路是一样的:解析层和建模层解耦,中间用结构化数据传递。

3. 核心细节拆解:从一句话到参数表的完整链路

3.1 文本解析:怎么把“人话”变成结构化参数

文本解析是整个流程的第一道关,也是最容易出问题的地方。用户说“一个 80 毫米长、40 毫米宽、5 毫米厚的板,中间有个直径 10 的孔”,这句话里包含了三个尺寸、一个形状类型、一个特征。但用户也可能说“一块 8 厘米乘 4 厘米的薄板,中心打一个 10 毫米的洞”,单位混用了,而且“薄板”没有明确厚度。

我的处理策略是分三步走。第一步是实体识别,先判断用户要的是什么基本形状:板、圆柱、法兰、支架、壳体,还是别的。这一步用分类模型做,准确率比较高,因为关键词很明显。第二步是参数抽取,把尺寸、位置、数量这些数值和它们对应的物理量抽出来。这里要处理单位换算,我统一转成毫米,厘米乘 10,米乘 1000,英寸乘 25.4。第三步是特征解析,识别孔、槽、倒角、圆角这些附加特征,以及它们的位置约束。

注意:单位混用是最高频的错误来源。我的做法是在解析阶段就强制统一,并且在返回给用户的确认信息里明确标注“已按毫米计算”,让用户有机会纠正。

参数抽取的 prompt 我改了很多版,最后稳定下来的格式是这样的:

{ "shape": "plate", "dimensions": { "length": 80, "width": 40, "thickness": 5 }, "features": [ { "type": "hole", "diameter": 10, "position": "center", "through": true } ], "unit": "mm" }

这个 JSON 就是后续建模的唯一输入。模型不需要知道 CadQuery 怎么用,只需要把这段话翻译成这个结构。

3.2 参数校验:哪些尺寸组合会直接导致建模失败

拿到 JSON 之后,不能直接扔给建模引擎,必须先过一遍校验。我踩过的坑包括:厚度为负、孔径大于板宽、孔的位置跑到实体外面、圆角半径大于最小边的一半。这些在数学上可能能算出结果,但几何上要么报错,要么生成一个自相交的无效实体。

校验规则我列了一个表,每次遇到新问题就加一条:

校验项规则失败处理
尺寸正负所有尺寸必须大于 0返回错误,提示用户
孔径与壁厚孔边缘到实体边缘距离 ≥ 1mm自动调整或提示
孔位置孔中心必须在实体投影范围内返回错误
圆角半径半径 ≤ 相邻边长的 1/2自动截断并提示
长宽比长宽比 > 20 时警告提示可能不是预期形状

这张表看起来简单,但每一条都是实际跑失败之后总结出来的。比如“孔边缘到实体边缘距离 ≥ 1mm”这条,是因为有一次生成的法兰盘孔打得太靠边,导出 STEP 之后在别的软件里直接破面了。后来我统一加了最小壁厚检查,问题就少了很多。

3.3 建模执行:CadQuery 代码的生成逻辑与参数计算

校验通过之后,就进入建模执行阶段。我的做法是写一个通用的建模函数,根据 JSON 里的shape字段分派到不同的处理分支。以最常见的“板+孔”为例,核心逻辑是这样的:

import cadquery as cq def build_plate(params): length = params["dimensions"]["length"] width = params["dimensions"]["width"] thickness = params["dimensions"]["thickness"] # 创建基础板 result = cq.Workplane("XY").box(length, width, thickness) # 处理孔特征 for feature in params.get("features", []): if feature["type"] == "hole": diameter = feature["diameter"] if feature["position"] == "center": result = result.faces(">Z").workplane().hole(diameter) elif feature["position"] == "corners": # 四角孔,需要计算孔心坐标 edge_dist = feature.get("edge_distance", 10) x = length/2 - edge_dist y = width/2 - edge_dist points = [(-x, -y), (x, -y), (x, y), (-x, y)] result = result.faces(">Z").workplane().pushPoints(points).hole(diameter) return result

这段代码里有个关键点:faces(">Z")选的是顶面,然后workplane()把工作平面移到顶面上,再打孔。如果不选面直接打孔,孔的方向可能不对。另外hole()默认是贯穿整个实体的,如果你只想打一定深度,需要传depth参数。

对于更复杂的形状,比如带沉头孔的法兰,逻辑会复杂一些。沉头孔需要两步操作:先打一个直径较大的浅孔,再打一个直径较小的通孔。这里有个顺序问题——必须先打大孔再打小孔,反过来会把大孔的位置也钻穿。我一开始搞反了,结果沉头部分直接消失了。

3.4 导出与兼容性:STEP、STL、DXF 怎么选

模型建好之后,导出格式的选择也很讲究。我一般同时导出三种:

  • STEP:用于后续在专业 CAD 软件里继续编辑,保留完整的 B-rep 信息。这是给工程师用的。
  • STL:用于 3D 打印和快速预览,网格格式,几乎所有切片软件都认。这是给创客用的。
  • DXF:用于二维切割,比如激光切割板材。只导出轮廓线,不包含厚度信息。

导出 STEP 的时候要注意单位。CadQuery 默认单位是毫米,但有些 CAD 软件打开 STEP 时会按英寸解释,导致模型缩小 25.4 倍。我的做法是在导出时显式设置单位,并且在文件名里标注_mm后缀,减少混淆。

实操心得:如果你要把生成的模型发给加工厂,最好同时提供 STEP 和一张标注了关键尺寸的二维图。我遇到过好几次,工厂那边打开 STEP 之后发现孔的位置和他们的预期不一致,其实就是坐标系方向的问题。后来我养成了习惯,导出前先把模型摆正,让主视图方向符合常规习惯。

4. 实操过程全记录:从零搭一个可用的原型

4.1 环境准备与依赖安装

我是在一台普通开发机上搭的原型,配置不算高,但跑中等规模的模型解析和生成完全够用。环境方面,Python 3.10 是基础,CadQuery 用 pip 装就行:

pip install cadquery pip install trimesh pip install numpy

CadQuery 的安装稍微有点重,因为它依赖 OpenCASCADE 的 Python 绑定。如果 pip 装不上,可以试试 conda:

conda install -c conda-forge cadquery

文本解析部分我用的是一个本地部署的中等规模语言模型,通过 API 调用。如果你没有本地模型,用任何可用的文本生成接口都行,核心是 prompt 的设计和输出格式的约束。

4.2 第一个可运行版本:只支持“板+中心孔”

我的第一个版本极其简单,只支持一种形状:矩形板,中心一个通孔。输入文本限定为“长 X 宽 Y 厚 Z 的板,中心有直径 D 的孔”。解析部分用正则表达式硬匹配,没用模型。建模部分就是上面那段 CadQuery 代码的简化版。

这个版本跑通之后,我做了大概二十次测试,发现几个问题:一是用户经常不按格式说,比如“X 乘 Y 的板”和“X 宽 Y 长的板”混着来;二是单位有时候写“mm”有时候写“毫米”有时候什么都不写;三是“中心”有时候指板的中心,有时候指某个边的中心。这些问题逼着我从正则转向模型解析,因为正则的规则越写越多,维护成本太高。

4.3 加入法兰和支架:多特征组合的处理

第二个版本我加入了法兰和 L 型支架。法兰的特点是有一个中心大孔和一圈均布的小孔,支架的特点是有一个底板和一个立板,可能还有加强筋。这些形状的共同点是多个特征按一定规则排列。

以法兰为例,用户说“外径 100、内径 60、厚度 10 的法兰,均布 6 个直径 8 的孔,孔中心距外缘 10 毫米”。解析出来的 JSON 里,features数组会包含一个center_hole和一个bolt_pattern。bolt_pattern里需要计算每个孔的中心坐标:

import math def bolt_hole_positions(count, pcd): """计算均布孔的位置,pcd 是孔中心圆直径""" positions = [] for i in range(count): angle = 2 * math.pi * i / count x = (pcd / 2) * math.cos(angle) y = (pcd / 2) * math.sin(angle) positions.append((x, y)) return positions

这里pcd是孔中心圆直径,等于外径减去两倍的边距。比如外径 100、边距 10,那 pcd 就是 80。这个计算看起来简单,但实际用的时候要注意角度起始位置——是从 0 度开始还是从 90 度开始,会影响孔相对于坐标轴的位置。我默认从 0 度开始,也就是第一个孔在 X 轴正方向上。

4.4 参数计算实例:一个完整法兰的尺寸推导

拿一个实际例子走一遍完整流程。用户输入:“做一个外径 120 毫米、内径 80 毫米、厚度 15 毫米的法兰,均布 8 个 M8 的螺栓孔,孔中心距外缘 12 毫米。”

解析步骤:

  1. 识别形状为法兰,外径 120,内径 80,厚度 15。
  2. 识别螺栓孔特征:数量 8,规格 M8,边距 12。
  3. M8 螺栓孔的标准通孔直径是 8.4 毫米(留一点间隙),但我这里简化处理,直接用 8 毫米。
  4. 孔中心圆直径 pcd = 120 - 2×12 = 96 毫米。
  5. 孔中心圆半径 = 48 毫米。
  6. 每个孔的位置按 45 度间隔均布,起始角度 0 度。

建模步骤:

outer_r = 60 inner_r = 40 thickness = 15 pcd_r = 48 hole_d = 8 hole_count = 8 # 创建圆环体 result = ( cq.Workplane("XY") .circle(outer_r) .circle(inner_r) .extrude(thickness) ) # 打螺栓孔 positions = [] for i in range(hole_count): angle = 2 * math.pi * i / hole_count x = pcd_r * math.cos(angle) y = pcd_r * math.sin(angle) positions.append((x, y)) result = result.faces(">Z").workplane().pushPoints(positions).hole(hole_d)

这段代码跑出来的模型,外径 120、内径 80、厚 15,8 个直径 8 的孔均布在直径 96 的圆上。导出 STEP 之后在 CAD 软件里量一下,尺寸完全对得上。

注意:circle(outer_r).circle(inner_r)这个写法是先画外圆再画内圆,然后extrude的时候会自动把内圆挖掉,形成一个环。如果你先 extrude 再挖孔,逻辑就多了一步。CadQuery 的链式调用支持这种“先画轮廓再拉伸”的方式,代码更简洁。

4.5 批量生成与自动化:一次处理多个描述

原型跑通之后,我加了一个批量处理功能。把多个描述写在一个文本文件里,每行一个,程序逐行解析、建模、导出,文件名按序号自动生成。这个功能在需要建标准件库的时候特别有用。比如你要建一套不同规格的法兰,从 DN50 到 DN200,每个规格一行描述,跑一遍就全出来了。

批量处理的时候要注意错误隔离。某一行解析失败不能影响其他行,我的做法是用 try-except 包住每一行的处理逻辑,失败的行记录到日志里,最后统一报告。这样即使有几行格式不对,其他行照样能跑完。

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

5.1 解析歧义:用户说的“中心”到底是哪个中心

这是最高频的问题。用户说“板中心有个孔”,大部分情况是指矩形板的几何中心,也就是长宽的中点。但如果板上有其他特征,比如一个角被切掉了,“中心”可能指剩余形状的形心。我的处理策略是:默认按几何中心处理,同时在返回结果里注明“孔位于板几何中心”,让用户确认。如果用户纠正,再调整。

另一个歧义是“边距”。说“孔距边 10 毫米”,可能指孔边缘到板边缘的距离,也可能指孔中心到板边缘的距离。工程上通常指孔中心到边缘的距离,我按这个默认值处理,但在文档里写清楚。

5.2 建模失败:布尔运算报错和自相交怎么修

布尔运算是建模阶段最容易报错的地方。常见错误包括:孔完全在实体外面、两个实体只有点接触或线接触、运算后产生零厚度壁。CadQuery 底层是 OpenCASCADE,它对自相交和零厚度比较敏感,一旦出现就直接抛异常。

我的排查流程是这样的:

  1. 先检查所有特征的坐标是否在实体范围内。
  2. 检查孔与孔之间是否重叠,如果重叠,合并成一个大孔或者调整位置。
  3. 检查最小壁厚,如果小于 0.5 毫米,自动加厚或者提示用户。
  4. 如果还是报错,把模型拆成两步:先做基础形状,导出看看;再加特征,逐步定位问题。

实操心得:CadQuery 的clean()方法可以清理一些微小的几何瑕疵,在布尔运算之后调用一下,能减少很多莫名其妙的报错。但clean()不是万能的,如果几何本身有问题,它也没办法。

5.3 导出文件打不开:STEP 兼容性问题的排查

STEP 文件打不开或者打开后破面,通常有几个原因。一是模型本身有无效几何,比如自相交面或者零面积面。二是导出时的单位设置和打开软件的单位设置不一致。三是文件版本不兼容,有些老软件只认 AP203,不认 AP214。

我的做法是:导出时统一用 AP214,单位显式设为毫米。如果对方还是打不开,就同时给一个 STL 作为参考。STL 虽然精度低一点,但兼容性几乎没问题。

5.4 性能优化:复杂模型生成太慢怎么办

当特征数量超过 20 个,或者形状比较复杂的时候,CadQuery 的生成速度会明显下降。我实测下来,一个带 30 个孔的板,生成时间大概在 3 到 5 秒。如果批量处理上百个模型,总时间就有点长了。

优化手段有几个:一是减少不必要的布尔运算,能合并的特征尽量合并;二是用Workplane的批量操作,比如pushPoints一次打多个孔,比循环单个打孔快很多;三是如果只是预览,可以先生成低精度的网格,确认形状后再生成精确的 B-rep。

问题类型典型表现排查手段解决方式
解析歧义孔位置不对检查 JSON 中的 position 字段增加确认步骤或默认规则
布尔失败报错退出检查特征坐标和壁厚调整参数或分步建模
导出异常文件打不开检查单位和几何有效性换格式或修复几何
性能瓶颈生成超过 10 秒统计特征数量和布尔次数合并特征或降低精度

这张表是我自己排查问题时用的速查表,基本上覆盖了 90% 的常见故障。每次遇到新问题,我就往表里加一行,慢慢就形成了一套自己的知识库。

5.5 精度与公差:生成模型能不能直接用于加工

这个问题我被问过很多次。答案是:取决于加工方式。如果是 3D 打印,生成的模型直接能用,因为打印本身有收缩和层纹,公差要求没那么严。如果是 CNC 加工,生成的模型可以作为参考,但实际加工前还需要工程师根据刀具半径和装夹方式做调整。如果是激光切割,导出 DXF 轮廓线就行,但要注意切割缝的补偿。

我的建议是:把 text-to-cad 生成的模型定位为“设计意图的快速表达”,而不是“可直接加工的最终图纸”。它能帮你快速验证形状和尺寸,但真要上机床,还是得走正规的工程流程。

6. 这个方向还能怎么扩展

原型跑通之后,我试过几个扩展方向,有的效果不错,有的还在摸索。

第一个扩展是从二维图纸反推。用户上传一张手绘草图或者 PDF 图纸,程序识别其中的尺寸标注和视图,自动生成三维模型。这个比纯文本难很多,因为图纸里的信息更密集,而且有投影关系。我目前只做到了简单三视图的识别,复杂图纸还不行。

第二个扩展是参数化模板库。把常见的零件类型(法兰、支架、齿轮、轴承座)做成模板,用户只需要填参数,不需要描述形状。这个在实际使用中效率最高,因为大部分需求都是标准件。模板库的好处是生成速度快、质量稳定,缺点是灵活性差,遇到非标形状就没办法了。

第三个扩展是与仿真工具联动。模型生成之后,自动导入有限元分析软件做强度校核,如果应力超标就自动调整尺寸重新生成。这个想法很美好,但实际做起来链路太长,每个环节都有不确定性。我目前只是手动导出到仿真软件,还没有做到全自动。

如果你也想入坑这个方向,我的建议是从最简单的形状开始,先把“文本→参数→模型→导出”这条链路跑通,再逐步增加形状类型和特征数量。不要一上来就追求自由曲面和复杂装配,那些问题的难度比规则几何高一个数量级。先把规则几何做稳,覆盖 80% 的日常需求,剩下的 20% 再慢慢啃。

最后分享一个我在调试过程中养成的习惯:每次生成模型之后,除了看预览图,一定要导出 STEP 再用另一个软件打开确认一遍。因为预览用的网格和实际导出的 B-rep 可能有差异,有些几何问题在网格上看不出来,但在 B-rep 里会暴露。这个习惯帮我提前发现了好几个隐蔽的 bug,省了不少返工时间。

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

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

立即咨询