
1. 从一段文字到三维模型text-to-cad 到底在解决什么问题第一次听到 “text-to-cad” 这个词很多人脑子里冒出来的画面可能是对着电脑敲一句“给我画一个法兰盘”然后屏幕上就自动出现一个带螺栓孔的 STEP 模型。这个画面放在三年前还像是科幻但现在已经有一批工具和开源方案在往这个方向走了。我最早接触这个方向是因为手头有一批非标零件的建模需求每次都要在 CAD 软件里重复画草图、拉伸、打孔、倒角一套流程下来半小时就没了。后来我开始琢磨能不能用自然语言描述直接生成参数化模型把重复劳动压缩掉text-to-cad 的核心逻辑其实不复杂它做的事情就是把人类可读的自然语言描述翻译成 CAD 软件能理解的几何指令最终输出成 STEP、GLB、STL 这类通用三维格式。STEP 适合做精确的工程交换GLB 适合做可视化展示和 Web 端渲染STL 则是 3D 打印和网格处理的老朋友。这三个格式基本覆盖了从设计到制造到展示的全链路所以 text-to-cad 的输出格式选择不是随便定的而是对应了不同的下游场景。这篇文章适合谁看如果你是有一定 CAD 基础、想把手头重复建模工作自动化的工程师或者你是做 AI 应用开发、想把三维生成能力集成到自己产品里的开发者再或者你只是对“用文字生成模型”这件事好奇、想自己跑一遍看看效果的技术爱好者那接下来的内容应该都能给你一些可以直接抄作业的东西。我会从整体设计思路讲起然后拆解核心实现细节再给出一套可复现的实操流程最后把我踩过的坑和排查经验整理出来。2. 整体设计思路为什么是“文字→参数→几何”而不是“文字→网格”2.1 两条技术路线的取舍目前 text-to-cad 的实现大致分两条路。一条是直接生成网格也就是用类似文本生成三维点云或体素的方式最后导出 STL 或 GLB。另一条是先解析成参数化指令再调用 CAD 内核生成精确几何最后导出 STEP。这两条路各有各的适用场景但如果你要做的是工程级别的零件我强烈建议走第二条路。直接生成网格的好处是门槛低模型看起来“像那么回事”但问题也很明显尺寸不精确、拓扑结构混乱、没法做参数化修改。你拿到一个 STL 之后想改一个孔的直径基本等于重新建模。而参数化路线生成的是带特征的模型孔的位置、直径、倒角大小都是可编辑的参数后续改起来非常方便。这也是为什么 text-to-cad 在工程场景下更倾向于输出 STEP 而不是 STL。我自己的做法是用大语言模型做“语义解析器”把自然语言转成结构化的 JSON 指令然后用 Python 脚本调用 CAD 内核比如 CadQuery 或 OpenCASCADE执行这些指令最后导出 STEP 和 GLB。STL 作为附赠输出用于快速预览和 3D 打印验证。这样整条链路是可解释、可调试、可扩展的。2.2 为什么选 CadQuery 作为几何内核CadQuery 是一个基于 OpenCASCADE 的 Python 参数化建模库它的 API 设计非常接近人类的建模思维。比如你要画一个带孔的板子代码读起来就像在描述这个零件本身import cadquery as cq result ( cq.Workplane(XY) .box(60, 40, 10) .faces(Z) .workplane() .hole(8) .edges(|Z) .fillet(2) )这段代码的意思是在 XY 平面上画一个 60x40x10 的盒子选顶面打一个直径 8 的孔然后对所有平行于 Z 轴的边做半径 2 的圆角。可读性非常强而且生成的模型是精确的 B-rep 几何可以直接导出 STEP。选 CadQuery 而不是直接调 OpenCASCADE 的底层 API主要是因为开发效率。底层 API 功能强大但写起来繁琐一个简单的倒角可能要十几行代码。CadQuery 把常用操作封装成了链式调用写起来快调试也方便。另外 CadQuery 社区活跃文档和示例比较全遇到问题容易找到参考。2.3 大语言模型在链路中的角色大语言模型在这里不是用来“画图”的而是用来做语义理解和指令生成的。你给它一句“做一个 100x50x20 的铝块四个角倒 R5 圆角中心打一个直径 12 的通孔”它需要输出类似这样的结构化指令{ operation: create_part, base: { type: box, dimensions: {length: 100, width: 50, height: 20} }, features: [ {type: fillet, target: vertical_edges, radius: 5}, {type: hole, target: top_face_center, diameter: 12, through: true} ] }然后 Python 脚本读取这个 JSON映射成 CadQuery 的调用链。这样做的好处是模型输出和几何执行解耦你可以换不同的语言模型也可以换不同的几何内核只要中间的结构化指令格式不变就行。注意大语言模型输出的尺寸单位需要明确约定。我一般统一用毫米并且在 prompt 里强制要求模型输出单位字段避免出现“100”到底是毫米还是英寸的歧义。3. 核心细节解析从文字到 STEP 的每一步到底发生了什么3.1 语义解析把模糊描述变成精确参数自然语言最大的问题是模糊。“一个比较大的板子”这种描述对人来说可以理解但对机器来说就是灾难。所以在 text-to-cad 的语义解析阶段最关键的是引导模型输出精确的数值和明确的操作类型。我的做法是在 prompt 里加入 few-shot 示例让模型模仿输出格式。比如给一个“创建一个直径 50、高 30 的圆柱顶部倒角 2mm”的例子模型就会学着输出结构化的 JSON。同时我会在 prompt 里明确要求所有尺寸必须带单位所有特征必须指定作用面或作用边所有操作必须按顺序排列。实测下来GPT-4 级别的模型在 few-shot 引导下对简单零件的解析准确率能到 85% 以上。复杂零件比如带多个特征、需要布尔运算的准确率会降到 60% 左右这时候就需要人工介入修正 JSON 指令。但即便如此也比从零开始建模快很多。3.2 指令映射JSON 到 CadQuery 的转换逻辑拿到结构化指令之后下一步是把它翻译成 CadQuery 的调用链。这部分我写了一个映射器核心逻辑是一个操作类型到函数的字典OPERATION_MAP { box: lambda wp, params: wp.box(*params[dimensions].values()), cylinder: lambda wp, params: wp.cylinder(params[height], params[radius]), hole: lambda wp, params: wp.hole(params[diameter]), fillet: lambda wp, params: wp.fillet(params[radius]), chamfer: lambda wp, params: wp.chamfer(params[length]), }实际实现会比这个复杂一些因为要处理工作面选择、边选择、布尔运算顺序等问题。但核心思路就是把 JSON 里的每一个 feature 按顺序应用到当前的工作平面上每一步都产生一个新的几何体最后合并成一个完整的零件。这里有一个容易踩的坑CadQuery 的链式调用是有状态顺序的。如果你先打孔再倒角和先倒角再打孔结果可能完全不同。所以 JSON 指令里的 features 数组顺序必须和实际建模顺序一致不能随意调换。3.3 格式导出STEP、GLB、STL 各自的门道模型生成之后导出环节也有不少细节。STEP 导出相对简单CadQuery 直接支持cq.exporters.export(result, output.step)GLB 导出需要借助额外的库比如cadquery-ocp或者通过trimesh做中间转换。我的做法是先把 CadQuery 的几何体转成网格再用 trimesh 导出 GLBimport trimesh mesh result.val().tessellate(0.1) glb_mesh trimesh.Trimesh(verticesmesh[0], facesmesh[1]) glb_mesh.export(output.glb)STL 导出和 GLB 类似也是先做网格化再导出。这里的关键参数是网格精度tessellate 的容差设得太大会导致圆角变成多边形设得太小会导致文件体积爆炸。我一般用 0.1mm 的线性容差和 0.5 弧度的角度容差在精度和体积之间取一个平衡。提示STEP 是精确几何文件通常比 STL 小很多但包含的信息更丰富。如果你要做后续的 CAM 加工或者工程分析一定要用 STEP。STL 只适合 3D 打印预览和快速可视化。4. 实操过程从零搭建一套可用的 text-to-cad 流水线4.1 环境准备与依赖安装先说一下我的环境Python 3.10CadQuery 2.4OpenCASCADE 7.7trimesh 4.0。CadQuery 的安装推荐用 conda因为它的依赖比较复杂pip 安装有时候会遇到编译问题conda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery pip install trimesh openai如果你不用 conda也可以用 pip 直接装 cadquery但需要确保系统里有 C 编译环境。我在 Ubuntu 22.04 和 Windows 11 上都试过conda 的方式最省心。大语言模型部分我用的是 OpenAI 的 API你也可以换成任何支持结构化输出的模型。关键是要能稳定输出 JSON所以我在调用时开启了 JSON mode并且在 prompt 里明确要求输出格式。4.2 完整代码实现从文字到三个格式的导出下面是我实际在用的核心代码简化了一下但保留了关键逻辑。首先是语义解析部分import openai import json SYSTEM_PROMPT 你是一个 CAD 指令解析器。用户会用自然语言描述一个零件你需要输出结构化的 JSON 指令。 输出格式 { unit: mm, base: {type: box|cylinder, dimensions: {...}}, features: [ {type: hole|fillet|chamfer, params: {...}, target: ...} ] } 所有尺寸必须带单位所有特征必须指定作用对象。 def parse_text_to_json(user_input): response openai.ChatCompletion.create( modelgpt-4, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input} ], response_format{type: json_object} ) return json.loads(response.choices[0].message.content)然后是几何生成部分import cadquery as cq def build_model(instructions): base instructions[base] if base[type] box: wp cq.Workplane(XY).box( base[dimensions][length], base[dimensions][width], base[dimensions][height] ) elif base[type] cylinder: wp cq.Workplane(XY).cylinder( base[dimensions][height], base[dimensions][radius] ) for feature in instructions[features]: if feature[type] hole: wp wp.faces(Z).workplane().hole(feature[params][diameter]) elif feature[type] fillet: wp wp.edges(|Z).fillet(feature[params][radius]) elif feature[type] chamfer: wp wp.edges(|Z).chamfer(feature[params][length]) return wp最后是导出部分def export_all(model, name): cq.exporters.export(model, f{name}.step) mesh_data model.val().tessellate(0.1) import trimesh mesh trimesh.Trimesh(verticesmesh_data[0], facesmesh_data[1]) mesh.export(f{name}.glb) mesh.export(f{name}.stl)把这三段串起来就是一个完整的 text-to-cad 流水线。你输入一句描述它输出三个格式的文件。实测下来一个简单零件从文字到 STEP 大概需要 5 到 10 秒其中大部分时间花在模型推理上几何生成本身很快。4.3 参数选择与计算过程这里重点说一下网格化参数的选择。tessellate 函数的第一个参数是线性容差单位是毫米。它的含义是生成的网格和原始精确曲面之间的最大偏差不超过这个值。我一般设 0.1mm对于大多数零件来说这个精度足够让圆角看起来光滑同时文件体积不会太大。第二个参数是角度容差单位是弧度。它控制的是曲面细分时相邻面片之间的最大角度变化。我一般设 0.5 弧度大约 28 度。这个值越小曲面越光滑但面片数量也越多。对于 3D 打印预览来说0.5 弧度已经足够了。如果你要做高精度的渲染或者仿真可以把线性容差降到 0.01mm角度容差降到 0.1 弧度。但要注意这样生成的 STL 文件可能会大到几百 MB处理起来会比较慢。5. 常见问题与排查技巧实录5.1 模型生成失败或几何异常最常见的问题是模型生成出来是空的或者几何体有自相交。这种情况多半是因为指令里的特征顺序不对或者作用面选择错误。比如你先打孔再倒角倒角可能会把孔的边缘也倒掉导致几何异常。排查方法是把 JSON 指令打印出来逐步执行每一步看看哪一步之后几何体变成了空。CadQuery 的val()方法可以获取当前几何体如果返回 None 或者体积为 0就说明上一步操作有问题。另一个常见问题是单位不一致。模型输出的尺寸是英寸但 CadQuery 默认按毫米处理结果就是模型大了 25.4 倍。解决办法是在 prompt 里强制要求输出单位字段并且在代码里做单位转换。5.2 导出格式的兼容性问题STEP 导出一般没什么问题但 GLB 和 STL 有时候会在某些软件里打不开。我遇到过 STL 文件在某个切片软件里显示为空白后来发现是因为网格的法线方向反了。解决办法是在 trimesh 里调用mesh.fix_normals()修正法线。GLB 的问题通常是材质丢失或者坐标系不对。CadQuery 生成的几何体默认在 XY 平面上导出 GLB 之后可能需要旋转才能正确显示。我一般会在导出前加一个旋转变换把 Z 轴朝上改成 Y 轴朝上这样在大多数 Web 渲染器里都能正常显示。5.3 性能优化与批量处理如果你要批量生成大量模型性能会成为瓶颈。我的优化经验是把模型推理和几何生成分开跑。先用模型批量生成所有 JSON 指令存成一个列表然后再用多进程批量执行几何生成。这样模型推理的延迟可以重叠整体吞吐量能提升三到四倍。另外CadQuery 的几何生成是 CPU 密集型的多进程比多线程更有效。我用multiprocessing.Pool开 4 到 8 个进程具体数量取决于你的 CPU 核心数。每个进程独立生成一个模型最后汇总导出。问题类型典型表现排查方法解决方案几何为空导出文件体积为 0逐步执行指令检查每步几何体调整特征顺序检查作用面选择单位错误模型尺寸偏差 25.4 倍检查 JSON 中的 unit 字段强制单位转换统一为毫米STL 法线反转切片软件显示空白用 trimesh 检查法线方向调用 fix_normals() 修正GLB 坐标系不对Web 端显示方向错误检查导出时的旋转矩阵导出前做 Z 轴到 Y 轴的旋转批量生成慢单模型耗时超过 30 秒分离推理和几何生成阶段多进程并行执行几何生成实操心得我建议在 prompt 里加入“如果用户描述不完整请输出默认参数并在 JSON 中标注 assumed 字段”。这样模型不会因为信息不足而卡住同时你也能知道哪些参数是猜的后续可以人工修正。6. 这套方案还能怎么扩展跑通基础流程之后我陆续加了一些扩展功能。一个是历史记录每次生成的 JSON 指令和导出文件都存到本地数据库方便回溯和复用。另一个是参数化模板把常用零件类型法兰、支架、齿轮坯做成模板用户只需要填几个关键尺寸模型自动补全其余参数。还有一个比较有意思的方向是反向工程给一张手绘草图或者一张零件照片先用视觉模型提取尺寸和特征再走 text-to-cad 的流程生成三维模型。这个链路我还在试验阶段准确率还不够稳定但对于简单零件已经能用了。如果你想把 text-to-cad 集成到自己的工具链里我建议把核心逻辑封装成一个 REST API输入是自然语言描述输出是 STEP 文件的下载链接。这样前端、脚本、甚至 CAD 插件都可以调用同一套服务维护起来也方便。