☰
Text-to-CAD 实战:从文本描述到三维模型生成全链路解析
2026/10/27 5:23:21 网站建设 项目流程

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

第一次听到 text-to-cad 这个词,我的反应是:这不就是把自然语言变成 CAD 模型吗?听起来像是给工程师省事的工具,但真正动手做过之后才发现,它背后牵扯的东西远比想象中复杂。简单来说,text-to-cad 是一类将文本描述直接转换为计算机辅助设计(CAD)文件的技术方案,输出格式通常包括 STEP、URDF、G-code 等。它的核心价值在于:让不会画图的人也能通过文字描述生成可用的三维模型,让会画图的人从重复性的建模劳动中解放出来。

我最初接触这个方向是因为一个实际需求——手头有一批参数化的零件需要反复调整尺寸,每次改动都要在 CAD 软件里重新拉伸、打孔、倒角,效率极低。后来我想,能不能用代码或者文字描述来驱动这些操作?顺着这个思路摸索下去,发现 text-to-cad 这个领域已经有不少成熟的方案和工具链了。

这篇文章适合几类人看:一是对 CAD 建模有基础但想探索自动化路径的工程师;二是做机器人仿真、需要批量生成 URDF 模型的开发者;三是做增材制造、需要从文本直接生成 G-code 的从业者;四是对 AI 辅助设计感兴趣、想了解当前技术边界的产品经理和研究者。不管你是哪种,我都会从实际操作的角度的出发,把踩过的坑和验证过的方案讲清楚。

2. 核心技术路线拆解:从文本到 CAD 文件的完整链路

2.1 文本解析与意图理解层

text-to-cad 的第一步永远是对输入文本的解析。这里的文本可能是“一个长 50mm、宽 30mm、高 20mm 的长方体,中心有一个直径 10mm 的通孔”,也可能是“一个法兰盘,外径 100mm,内径 60mm,厚度 15mm,均匀分布 6 个直径 8mm 的螺栓孔”。这两种描述的复杂度不同,解析难度也完全不同。

目前主流的做法有两种:一种是基于规则模板的解析,另一种是基于大语言模型的语义理解。规则模板的好处是稳定、可控,适合参数化程度高、描述格式固定的场景。比如你可以定义一套模板:“创建一个[形状],尺寸为[长]x[宽]x[高],在[位置]添加[特征]”。用户输入只要匹配这个模板,就能被准确解析成结构化的参数。

但规则模板的局限性也很明显——用户不会总是按照你预设的格式说话。这时候就需要大语言模型来兜底。我实测下来,用 LLM 做意图理解和参数抽取,准确率在简单几何体上能到 90% 以上,但一旦涉及复杂装配体或者工程语义(比如“配合公差”、“表面粗糙度”),准确率就会明显下降。所以我的建议是:规则模板处理高频简单场景,LLM 处理长尾复杂场景,两者结合使用。

注意:LLM 输出的参数一定要做合法性校验。我遇到过模型把“直径 10mm”理解成“半径 10mm”的情况,如果不校验直接建模,出来的零件直接报废。

2.2 几何建模与特征生成层

文本解析完之后,下一步就是把结构化参数变成实际的几何体。这一步的核心工具是几何内核,常见的有 OpenCASCADE、CGAL、OpenSCAD 等。OpenCASCADE 是工业级的内核,功能强大但学习曲线陡峭;OpenSCAD 更轻量,适合快速原型,但复杂曲面能力有限。

我个人的选择是:如果目标是输出 STEP 文件用于后续工程加工,优先用 OpenCASCADE;如果只是做概念验证或者简单可视化,OpenSCAD 足够用。以 OpenCASCADE 为例,你可以用 Python 的pythonocc-core库来调用它,代码大概长这样:

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.gp import gp_Pnt # 创建一个 50x30x20 的长方体 box = BRepPrimAPI_MakeBox(gp_Pnt(0, 0, 0), 50, 30, 20).Shape()

这段代码创建了一个长方体,但还没有打孔。打孔需要用到布尔运算:

from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder # 创建一个直径 10mm 的圆柱体作为孔 cylinder = BRepPrimAPI_MakeCylinder(5, 20).Shape() # 布尔减运算 result = BRepAlgoAPI_Cut(box, cylinder).Shape()

这里有个细节:圆柱体的位置默认在原点,如果孔不在原点,你需要先做平移变换。我见过不少人在这里翻车——孔的位置偏了,但视觉上不明显,直到导出 STEP 文件去加工才发现问题。

2.3 文件导出与格式转换层

生成几何体之后,最后一步是导出成目标格式。不同的应用场景需要不同的格式:

格式适用场景特点
STEP工程加工、跨软件交换保留精确几何信息,通用性强
URDF机器人仿真描述连杆和关节,配合 Coppeliasim 等仿真环境
G-code增材制造、CNC 加工直接驱动机床,与具体设备强相关
STL3D 打印预览三角网格,丢失精确几何信息

导出 STEP 用 OpenCASCADE 的STEPControl_Writer就行:

from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs writer = STEPControl_Writer() writer.Transfer(result, STEPControl_AsIs) writer.Write("output.step")

导出 URDF 就复杂一些,因为 URDF 本质上是 XML 格式的机器人描述文件,你需要定义 link 和 joint 的层级关系。如果你的模型是单个零件,导出 URDF 意义不大;但如果是多关节的机械臂,就需要把每个连杆的几何体和关节的旋转轴都描述清楚。我一般会先用 CAD 生成各个连杆的 STEP 文件,再用脚本把它们组装成 URDF。

G-code 的生成又是另一条路。你需要先切片(slicing),再根据切片结果生成路径。常用的工具是 Cura 的引擎或者 PrusaSlicer 的命令行接口。这里的关键参数是层高、填充率、打印速度,这些参数直接决定最终成品的质量和强度。

3. 实操环境搭建:从零开始跑通一条 text-to-cad 流水线

3.1 工具选型与依赖安装

我推荐的技术栈是:Python 3.10+、pythonocc-core、trimesh、numpy。如果你需要 LLM 做文本解析,再加一个 openai 或者本地模型的 SDK。安装 pythonocc-core 是最容易出问题的一步,因为它依赖 OpenCASCADE 的底层库。

在 Ubuntu 上,你可以用 conda 安装:

conda install -c conda-forge pythonocc-core

在 Windows 上,我建议直接用 conda 而不是 pip,因为 pip 版本的 pythonocc-core 经常缺少预编译的二进制文件。如果你坚持用 pip,可能需要自己编译 OpenCASCADE,那个过程相当痛苦。

实操心得:我试过在 Windows 上用 pip 装 pythonocc-core,折腾了两个小时都没成功,最后换 conda 五分钟搞定。所以如果你在 Windows 上,别跟自己过不去,直接用 conda。

3.2 文本解析模块的实现

假设我们要处理这样一段输入:“创建一个法兰盘,外径 120mm,内径 80mm,厚度 20mm,均匀分布 8 个直径 10mm 的螺栓孔,螺栓孔中心距外径边缘 15mm。”

首先定义一个解析函数,把这段文字变成结构化参数:

import re def parse_flange(text): params = {} params['outer_diameter'] = float(re.search(r'外径\s*(\d+)', text).group(1)) params['inner_diameter'] = float(re.search(r'内径\s*(\d+)', text).group(1)) params['thickness'] = float(re.search(r'厚度\s*(\d+)', text).group(1)) params['bolt_count'] = int(re.search(r'(\d+)\s*个', text).group(1)) params['bolt_diameter'] = float(re.search(r'直径\s*(\d+)mm\s*的螺栓孔', text).group(1)) params['edge_distance'] = float(re.search(r'边缘\s*(\d+)mm', text).group(1)) return params

这个正则表达式版本只适合格式固定的输入。如果用户说“做一个圆盘,外面直径 12 厘米,中间挖个 8 厘米的洞”,正则就匹配不上了。这时候就需要 LLM 出场:

def parse_with_llm(text): prompt = f"""从以下描述中提取法兰盘参数,以 JSON 格式返回: 描述:{text} 需要的字段:outer_diameter, inner_diameter, thickness, bolt_count, bolt_diameter, edge_distance 单位统一为毫米。""" # 调用 LLM API 并解析返回的 JSON response = call_llm(prompt) return json.loads(response)

3.3 几何建模的完整实现

拿到参数之后,开始建模。法兰盘的本质是一个大圆柱减去一个小圆柱,再减去若干个螺栓孔圆柱。用 pythonocc-core 实现:

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut from OCC.Core.gp import gp_Ax2, gp_Pnt, gp_Dir import math def build_flange(params): # 外圆柱 outer_radius = params['outer_diameter'] / 2 thickness = params['thickness'] outer = BRepPrimAPI_MakeCylinder(outer_radius, thickness).Shape() # 内圆柱(中心孔) inner_radius = params['inner_diameter'] / 2 inner = BRepPrimAPI_MakeCylinder(inner_radius, thickness).Shape() result = BRepAlgoAPI_Cut(outer, inner).Shape() # 螺栓孔 bolt_radius = params['bolt_diameter'] / 2 bolt_circle_radius = outer_radius - params['edge_distance'] for i in range(params['bolt_count']): angle = 2 * math.pi * i / params['bolt_count'] x = bolt_circle_radius * math.cos(angle) y = bolt_circle_radius * math.sin(angle) axis = gp_Ax2(gp_Pnt(x, y, 0), gp_Dir(0, 0, 1)) bolt_hole = BRepPrimAPI_MakeCylinder(axis, bolt_radius, thickness).Shape() result = BRepAlgoAPI_Cut(result, bolt_hole).Shape() return result

这段代码里有个关键点:螺栓孔的定位。bolt_circle_radius是螺栓孔中心所在的圆半径,它等于外半径减去边缘距离。角度用2 * pi * i / n计算,保证均匀分布。我见过有人用360 / n算角度然后传给cos和sin,结果全错了——因为 Python 的三角函数用的是弧度不是角度。

3.4 导出与验证

建模完成后,导出 STEP 文件:

from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs def export_step(shape, filename): writer = STEPControl_Writer() writer.Transfer(shape, STEPControl_AsIs) writer.Write(filename)

导出之后,我强烈建议用 FreeCAD 或者 CAD Assistant 打开检查一下。我遇到过布尔运算失败导致模型有破面的情况,在代码里看不出来,但打开文件一看就发现孔没打穿或者多了一块。

注意事项:布尔运算的顺序会影响结果。先减中心孔再减螺栓孔,和反过来,在某些边界情况下结果不同。我的经验是先从大的特征开始减,小的特征后减,这样稳定性更好。

4. 不同输出格式的实战要点与避坑指南

4.1 STEP 文件导出的精度控制

STEP 文件有两种模式:精确几何(exact geometry)和近似几何(approximate geometry)。默认情况下 OpenCASCADE 导出的是精确几何,文件体积小、精度高。但如果你在建模过程中用了网格化操作(比如从 STL 导入的模型),导出的 STEP 可能就变成了近似几何。

我踩过的一个坑是:从 STL 文件导入一个模型,做了布尔运算之后导出 STEP,结果文件体积暴涨到几百 MB。原因是 STL 的三角网格在布尔运算后产生了大量碎片面。解决办法是先用ShapeFix_Shape做修复和简化:

from OCC.Core.ShapeFix import ShapeFix_Shape fixer = ShapeFix_Shape(shape) fixer.Perform() fixed_shape = fixer.Shape()

4.2 URDF 导出的关节定义

URDF 的核心是 link 和 joint。link 是刚体,joint 是连接两个 link 的关节。如果你要把一个多关节机械臂的 CAD 模型转成 URDF,需要做几件事:

  1. 把每个连杆单独导出成 STL 或 STEP
  2. 定义每个关节的类型(旋转、平移、固定)
  3. 定义关节的旋转轴和限位
  4. 定义每个 link 的惯性矩阵

惯性矩阵是最容易被忽略的部分。很多人只填了几何形状就导出 URDF,结果在 Coppeliasim 里仿真时物理行为完全不对——因为仿真引擎需要惯性信息来计算动力学。你可以用 trimesh 来计算惯性矩:

import trimesh mesh = trimesh.load('link.stl') mesh.density = 1000 # 设置材料密度 kg/m^3 inertia = mesh.moment_inertia

然后在 URDF 里填入对应的值。这里要注意单位:URDF 默认用米和千克,如果你的 CAD 模型是毫米,需要先缩放。

4.3 G-code 生成的关键参数

从 CAD 到 G-code 的链路通常是:CAD → STL → 切片 → G-code。切片参数直接决定打印结果:

参数推荐值影响
层高0.1-0.3mm越小精度越高,但打印时间越长
填充率15%-30%影响强度和材料消耗
打印速度40-80mm/s太快会导致层间粘合不良
喷嘴温度根据材料定PLA 约 200°C,ABS 约 240°C

我一般用 PrusaSlicer 的命令行模式来批量生成 G-code:

prusa-slicer --export-gcode --layer-height 0.2 --fill-density 20% model.stl

实操心得:批量生成 G-code 时,一定要给每个文件加上参数标识,比如model_layer02_fill20.gcode。否则几十个文件混在一起,你根本分不清哪个是哪个参数生成的。

5. 常见问题排查与性能优化实录

5.1 布尔运算失败的排查思路

布尔运算失败是 text-to-cad 里最常见的问题。表现是:代码不报错,但导出的模型有破面或者缺失特征。排查步骤:

  1. 检查两个几何体是否有重叠区域。如果没有重叠,布尔减运算会返回空或者原形状。
  2. 检查几何体是否有效。用BRepCheck_Analyzer验证:
from OCC.Core.BRepCheck import BRepCheck_Analyzer analyzer = BRepCheck_Analyzer(shape) print(analyzer.IsValid())
  1. 如果几何体无效,用ShapeFix_Shape修复后再运算。
  2. 如果还是失败,尝试调整运算顺序,或者把复杂布尔运算拆成多步。

5.2 大模型解析的稳定性优化

用 LLM 做文本解析时,输出格式不稳定是最大的痛点。同一个描述,模型可能这次返回 JSON,下次返回 Markdown 表格。我的解决方案是:

  • 在 prompt 里明确要求“只返回 JSON,不要任何其他文字”
  • 用temperature=0降低随机性
  • 加一层后处理,用正则从返回文本里提取 JSON 部分
  • 对关键参数做范围校验,超出合理范围就报错
def safe_parse(response_text): # 尝试直接解析 try: return json.loads(response_text) except: pass # 尝试从文本中提取 JSON match = re.search(r'\{.*\}', response_text, re.DOTALL) if match: return json.loads(match.group()) raise ValueError("无法解析 LLM 返回内容")

5.3 批量生成的性能瓶颈

如果你需要生成几百个模型,性能就成了问题。我实测下来,单个法兰盘建模加导出 STEP 大约需要 0.5-1 秒,其中大部分时间花在布尔运算上。优化方向:

  • 用多进程并行处理,Python 的multiprocessing模块就能搞定
  • 如果几何体之间没有依赖关系,并行效率接近线性
  • 导出 STEP 时关闭不必要的元数据写入,能省 10%-20% 的时间
from multiprocessing import Pool def process_one(params): shape = build_flange(params) export_step(shape, f"flange_{params['id']}.step") with Pool(8) as p: p.map(process_one, all_params)

注意:OpenCASCADE 不是线程安全的,所以用多进程而不是多线程。多线程会导致随机崩溃,这个坑我踩过。

6. 从原型到落地:我的实际项目经验

6.1 一个真实的参数化零件生成案例

去年我接了一个小项目,需要为一批非标设备生成安装支架的 CAD 模型。支架的基本形状固定,但每个设备的安装孔位和尺寸都不同。手动建模的话,每个支架大概需要 15 分钟,200 个支架就是 50 个小时。

我用 text-to-cad 的思路做了一套自动化流程:把每个支架的参数写在 Excel 里,用脚本读取参数、生成模型、导出 STEP。整个流程跑完 200 个支架只用了 8 分钟。当然,前期调试花了大概两天时间,主要是处理各种边界情况——比如孔位太靠近边缘导致布尔运算失败、支架厚度太薄导致导出后破面等等。

这个项目的经验告诉我:text-to-cad 的价值不在于完全替代人工,而在于处理那些重复性高、参数化程度高的任务。对于真正需要创造力的设计工作,它目前还远远不够。

6.2 与 Coppeliasim 仿真的衔接

另一个让我印象深刻的场景是 URDF 导入 Coppeliasim。我帮一个做机器人研究的朋友把他们的机械臂 CAD 模型转成 URDF,然后在 Coppeliasim 里做运动仿真。过程中最大的问题是坐标系对齐——CAD 软件里的装配坐标系和 URDF 里的 link 坐标系往往不一致,需要手动调整每个 joint 的原点和轴向。

我的做法是:先在 CAD 里把每个连杆的坐标系调整到关节旋转中心,再导出 STL。然后在 URDF 里用<origin>标签微调位置。这个过程需要反复试错,但一旦调好,后续的仿真就很顺畅了。

6.3 当前方案的局限与改进方向

说实话,目前的 text-to-cad 方案还远没有到“输入一句话就出图”的程度。它的局限主要在三个方面:

第一,复杂语义理解不够。用户说“做一个好看的支架”,模型完全不知道“好看”是什么意思。工程语义里的“配合”、“公差”、“表面处理”更是难以用文本准确描述。

第二,几何特征的组合爆炸。简单几何体(圆柱、长方体、孔)的生成已经很成熟,但自由曲面、复杂扫掠、放样这些高级特征,目前还没有可靠的文本驱动方案。

第三,验证环节缺失。生成的模型是否满足工程要求,目前只能靠人工检查。未来如果能结合仿真验证(比如自动做应力分析),整个链路的可靠性会大幅提升。

我个人的判断是:text-to-cad 在参数化标准件、简单结构件领域会很快落地,但在复杂产品设计领域,它更多是辅助工具而不是替代方案。如果你现在想入局,建议从自己熟悉的细分场景切入,把一个小闭环跑通,比追求大而全的方案更有价值。

最后分享一个小技巧:如果你在 Windows 上做开发,建议用 WSL2 而不是原生 Windows。OpenCASCADE 在 Linux 下的编译和运行都稳定得多,而且 conda 的包管理也更顺畅。我切换过去之后,环境问题至少减少了 70%。

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

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

立即咨询