
1. 从一段文字到三维模型text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词我脑子里蹦出来的画面是对着电脑敲一句“给我画一个长宽高 100×60×30 毫米、四角带 M4 沉头孔的法兰底座”然后屏幕上直接出现一个可以旋转查看、能导出 STEP 文件送进加工中心的三维实体。这个画面在几年前还属于科幻范畴但现在已经有一批工具和方案在往这个方向靠了。text-to-cad直译就是“文本转 CAD”。它的核心逻辑是把自然语言描述或者结构化的文本参数转换成计算机辅助设计软件能够识别的几何模型数据最终输出成 STEP、GLB、STL 这类通用格式。它解决的是一个非常具体的痛点——大量非专业建模人员比如产品经理、硬件创业者、机械专业的学生、做创客项目的开发者脑子里有明确的结构想法但卡在不会用 SolidWorks、Fusion 360、中望 CAD 这些工具上。传统路径是想清楚需求 → 找会建模的人 → 反复沟通 → 改图 → 出图周期长、成本高、沟通损耗大。text-to-cad 想砍掉中间环节让“描述即建模”成为可能。这篇文章适合谁看如果你是做硬件产品原型的、搞机械设计的、玩 3D 打印的、或者单纯对“用代码和文字生成三维模型”这件事好奇的开发者那接下来的内容应该对你有用。我会从整体设计思路讲到具体实现路径把 STEP、GLB、STL 这几种格式的取舍讲清楚再给出一套可以跟着操作的流程最后把我踩过的坑和排查经验整理出来。全文基于我自己的实践和行业里常见的做法来写不保证是唯一解但保证是能跑通的方案。2. 整体设计思路为什么不是“一句话直接出图”那么简单2.1 文本到几何的映射难点在哪里很多人对 text-to-cad 的期待是“输入一句话输出一个精确的工程图”。这个期待本身没问题但实现起来有几个硬骨头要啃。第一个难点是语义到几何的映射不确定性。你说“一个圆柱”直径多少高度多少是实心还是空心这些参数在自然语言里经常是缺失的。人类工程师听到“圆柱”会默认追问参数但机器不行。所以 text-to-cad 系统要么要求用户提供结构化参数要么用大语言模型去“猜”一个合理默认值再让用户确认。我试过几种方案最稳的还是“半结构化输入”——用类似 JSON 或者 YAML 的格式把关键尺寸写清楚自然语言只用来描述拓扑关系和特征类型。第二个难点是几何内核的精度和鲁棒性。CAD 模型不是随便画个网格就完事了它需要精确的边界表示B-rep需要支持布尔运算、倒角、抽壳这些操作。OpenCASCADE 是目前开源方案里最成熟的内核Python 的 cadquery 和 build123d 都是基于它封装的。但 OpenCASCADE 的布尔运算在极端情况下会失败比如两个面几乎共面的时候或者倒角半径大于相邻边长度的时候。这些坑在纯文本生成场景下更容易触发因为用户不会像在 GUI 里那样实时看到预览并调整。第三个难点是输出格式的适配。STEP 是精确的 B-rep 格式适合后续在 CAD 软件里继续编辑和出工程图STL 是三角网格适合 3D 打印和渲染但丢失了特征信息GLB 是 glTF 的二进制版本适合网页展示和 AR/VR 场景。text-to-cad 系统需要根据下游用途选择输出格式有时候甚至要同时输出多种格式。2.2 为什么选择“代码化建模”作为中间层直接让大语言模型输出 STEP 文件是不现实的因为 STEP 的语法极其冗长且对拓扑一致性要求极高。目前业界比较务实的做法是用代码作为中间表示。具体来说就是让模型生成 Python 代码调用 cadquery 或 build123d 这类库然后执行代码得到几何体再导出成目标格式。这个设计的好处很明显。第一代码是可读、可审查、可修改的用户看到生成的代码能理解模型是怎么构建的不满意可以直接改参数。第二代码执行环境是确定的同样的代码跑出来一定是同样的几何体不会像直接生成网格那样每次都有随机性。第三代码可以版本控制一个 text-to-cad 项目本质上就是一堆参数化脚本的集合用 Git 管理起来非常方便。我自己的项目里就是走这条路用户输入一段描述后端调用大语言模型生成 cadquery 脚本然后在沙箱里执行脚本捕获几何体导出 STEP 和 STL最后把文件链接返回给前端。整个链路跑通之后从输入文字到拿到可下载的模型文件大概在 10 到 30 秒之间取决于模型复杂度和 API 响应速度。2.3 格式选型STEP、GLB、STL 各自的位置这三种格式在 text-to-cad 流程里扮演不同角色不能混为一谈。格式类型典型用途优点缺点STEPB-rep 精确几何后续 CAD 编辑、CNC 加工、出工程图精度高、保留特征、通用性强文件较大、解析复杂STL三角网格3D 打印、快速渲染格式简单、兼容性好丢失特征、精度受网格密度影响GLB二进制 glTF网页展示、AR/VR、游戏引擎加载快、支持材质和动画非精确几何、不适合加工我的建议是主输出用 STEP辅输出用 STL 和 GLB。STEP 作为“源文件”保存STL 给 3D 打印用GLB 给前端预览用。这样一套数据可以覆盖从设计到展示到制造的全链路。3. 核心细节解析从文本到几何的关键环节3.1 输入解析怎么把一句话变成结构化参数输入解析是整个流程的第一道关卡也是最容易被低估的环节。我见过太多项目在这里翻车——用户说“画一个盒子”系统生成一个 1×1×1 毫米的立方体用户直接懵了。我的做法是设计一个两阶段解析流程。第一阶段用大语言模型做意图识别和参数抽取输出一个结构化的 JSON 对象。比如用户输入“一个 100×60×30 的铝制法兰底座四角有 M4 沉头孔孔中心距边缘 10 毫米”模型应该输出类似这样的结构{ shape_type: flange_base, dimensions: { length: 100, width: 60, height: 30, unit: mm }, features: [ { type: counterbore_hole, count: 4, position: corners, edge_offset: 10, thread: M4, counterbore_diameter: 8, counterbore_depth: 4 } ], material: aluminum }第二阶段是参数校验和默认值填充。如果用户没给单位默认用毫米如果没给沉头孔深度根据 M4 标准查表填一个合理值如果长宽高比例异常比如长度是宽度的 100 倍给用户一个警告提示确认。这一步看起来简单但能避免大量“生成出来完全不能用”的情况。注意不要指望大语言模型一次就能抽取出完美参数。我的经验是在 prompt 里明确要求模型“对于缺失的参数标注为 null 并给出建议值”然后在后端做二次处理。这样比让模型瞎猜要可靠得多。3.2 代码生成cadquery 还是 build123d目前 Python 生态里做参数化 CAD 生成主流选择是 cadquery 和 build123d。两者都基于 OpenCASCADE但 API 风格不同。cadquery 用的是链式调用风格代码写起来比较紧凑import cadquery as cq result ( cq.Workplane(XY) .box(100, 60, 30) .faces(Z) .workplane() .rect(80, 40, forConstructionTrue) .vertices() .cskHole(4.5, 8, 90) )build123d 更偏向上下文管理器和面向对象风格可读性更好一些from build123d import * with BuildPart() as flange: Box(100, 60, 30) with Locations(flange.faces().sort_by(Axis.Z)[-1]): with GridLocations(80, 40, 2, 2): CounterBoreHole(2.25, 4, 4)我最终选了 cadquery原因是它的社区更大、文档更全、大语言模型对它的代码生成准确率更高。实测下来同样的 promptcadquery 生成的代码一次通过率比 build123d 高大概 20 个百分点。这个差距在批量生成场景下非常关键。3.3 几何内核的坑布尔运算失败与倒角溢出OpenCASCADE 虽然强大但在自动化场景下有几个高频翻车点。布尔运算失败是最常见的。当你对两个几乎共面的实体做差集运算时内核可能因为浮点精度问题无法确定面的归属直接抛异常。我的应对策略是在代码生成阶段就加入“安全间隙”逻辑。比如要在底板上挖一个槽槽的深度比板厚小 0.1 毫米避免切穿导致零厚度面。这个 0.1 毫米的间隙在视觉上几乎看不出来但能大幅降低布尔运算失败率。倒角溢出是另一个高频问题。如果你对一个 10 毫米厚的板边缘做 15 毫米的倒角内核会直接报错。解决方案是在生成倒角代码之前先计算相邻边的最小长度如果倒角半径超过这个长度的一半就自动缩小到安全值并在返回结果里标注“倒角半径已自动调整”。抽壳失败也经常遇到。抽壳操作要求实体有明确的内外方向如果实体本身有自相交或者非流形边抽壳会直接失败。我的做法是在抽壳之前先跑一遍isValid()检查不合法就先做修复或者简化。实操心得在沙箱里执行生成的代码时一定要设置超时和内存限制。我遇到过生成的代码里有个死循环直接把服务器内存吃满的情况。后来加了 30 秒超时和 2GB 内存上限稳多了。4. 实操过程搭一套能跑的 text-to-cad 流水线4.1 环境准备与依赖安装先说一下我的环境配置这套配置在 Ubuntu 22.04 和 macOS 上都跑通过。# 创建虚拟环境 python -m venv text2cad-env source text2cad-env/bin/activate # 安装核心依赖 pip install cadquery2.4.0 pip install ocp7.7.2 pip install openai1.12.0 pip install fastapi0.109.0 pip install uvicorn0.27.0 pip install trimesh4.0.0 pip install numpy1.26.0cadquery 的安装在不同平台上差异很大。Linux 上通常可以直接 pip 安装Windows 上建议用 conda 装macOS 上如果遇到 OCP 编译问题可以用conda install -c conda-forge cadquery来解决。我试过在 Windows 上纯 pip 装 cadquery折腾了两个小时才搞定后来换 conda 五分钟就完事了。4.2 大语言模型调用与代码生成我用的是 OpenAI 的 API但换成任何兼容 OpenAI 接口的模型都可以。关键是 prompt 的设计。下面是我实际用的 system prompt 的简化版SYSTEM_PROMPT 你是一个 CAD 代码生成助手。根据用户的描述生成 cadquery Python 代码。 要求 1. 代码必须完整可执行包含必要的 import 2. 最终结果赋值给变量 result 3. 所有尺寸单位默认为毫米 4. 如果用户未指定某个参数使用合理的工程默认值 5. 对于可能失败的布尔运算加入安全间隙 6. 代码中不要包含任何文件读写操作 7. 只输出代码不要输出解释文字 示例输出格式 python import cadquery as cq result cq.Workplane(XY).box(100, 60, 30)调用的时候把用户输入和 system prompt 一起发给模型拿到返回的代码字符串然后做一次语法检查用 ast.parse通过之后再送进沙箱执行。 ### 4.3 沙箱执行与几何体导出 沙箱执行是整个流程里最需要小心的地方。我用的方案是 subprocess 加资源限制 python import subprocess import tempfile import os def execute_cadquery_code(code: str, timeout: int 30): with tempfile.NamedTemporaryFile(modew, suffix.py, deleteFalse) as f: f.write(code) f.write(\n\nimport cadquery as cq\n) f.write(cq.exporters.export(result, /tmp/output.step)\n) f.write(cq.exporters.export(result, /tmp/output.stl)\n) script_path f.name try: proc subprocess.run( [python, script_path], timeouttimeout, capture_outputTrue, textTrue, env{**os.environ, PYTHONPATH: } ) if proc.returncode ! 0: return None, proc.stderr return /tmp/output.step, None except subprocess.TimeoutExpired: return None, 执行超时 finally: os.unlink(script_path)导出 STEP 用cq.exporters.export(result, output.step)导出 STL 用同样的函数但换扩展名。GLB 的导出稍微麻烦一点需要先用 trimesh 加载 STL再转成 GLBimport trimesh mesh trimesh.load(/tmp/output.stl) mesh.export(/tmp/output.glb)4.4 前端预览与文件下载前端我用的是 three.js 加载 GLB 文件做预览。整个交互流程是用户在输入框里写描述点生成前端显示 loading后端返回 GLB 的 URLthree.js 加载并渲染同时提供 STEP 和 STL 的下载链接。这里有个细节要注意GLB 文件要设置正确的 MIME 类型model/gltf-binary否则某些浏览器会当成普通二进制文件下载而不是渲染。另外 three.js 的 GLTFLoader 需要配合 DRACOLoader 来解压压缩过的 GLB如果后端用了 Draco 压缩的话。5. 常见问题与排查技巧实录5.1 生成代码执行报错怎么办这是最高频的问题没有之一。我整理了一个排查顺序表按这个顺序走能解决 90% 以上的报错。报错类型典型信息排查方向解决方案语法错误SyntaxError检查生成的代码是否有拼写错误重新生成或手动修正导入错误ModuleNotFoundError检查依赖是否安装补装缺失的包几何运算失败Standard_Failure布尔运算或倒角溢出加入安全间隙或缩小倒角超时TimeoutExpired代码有死循环或模型过于复杂优化代码或增加超时时间内存溢出MemoryError网格密度过高或实体过多降低网格精度或简化模型我遇到最多的是几何运算失败。有一次生成一个带 20 个孔的板代码跑出来报BRep_API: command not done。排查后发现是其中两个孔的位置重叠了导致布尔运算无法确定材料去除区域。解决方案是在生成孔之前先做碰撞检测如果两个孔的中心距小于孔径之和就自动调整位置或者合并成一个孔。5.2 导出的 STEP 在 CAD 软件里打不开这个问题通常有两个原因。一是 STEP 文件的版本不兼容有些老版本 CAD 软件只支持 AP203 而不支持 AP214。cadquery 默认导出 AP214可以在导出时指定cq.exporters.export(result, output.step, exportTypeSTEP, opt{write_pcurves: False})二是模型本身有几何缺陷比如非流形边或者自相交面。这种情况下需要先用result.val().isValid()检查如果不合法用result.val().fix()尝试修复。5.3 STL 文件太大或太小STL 的网格密度直接影响文件大小和打印质量。cadquery 导出 STL 时可以指定公差参数cq.exporters.export(result, output.stl, tolerance0.01, angularTolerance0.1)tolerance是线性公差单位毫米越小网格越密angularTolerance是角度公差单位弧度。我的经验值是普通 3D 打印用tolerance0.05就够了精细模型用0.01快速预览用0.2。文件大小和公差大致成平方关系公差缩小 10 倍文件大小增加约 100 倍所以要按需选择。5.4 大语言模型生成的代码“看起来对但跑不通”这是最让人头疼的情况。代码语法没问题逻辑看起来也合理但执行就是报错。常见原因有几个模型用了不存在的 API 方法、参数顺序搞错了、或者对 cadquery 的坐标系理解有偏差。我的应对策略是维护一个常用代码片段库在 prompt 里把高频操作的示例代码放进去让模型参考。比如画孔、倒角、抽壳、阵列这些操作各给一个标准示例。实测下来加了示例之后代码一次通过率从 60% 左右提升到了 85% 以上。避坑技巧在 prompt 里明确告诉模型“不要使用cq.Workplane的shell方法做抽壳改用faces().shell()”因为前者在某些版本里有 bug。这种细节只有踩过坑才知道。6. 进阶方向从单次生成到参数化模板库跑通基础流程之后我发现一个更有价值的模式把高频生成的模型沉淀成参数化模板。比如法兰、支架、齿轮、外壳这些常见零件与其每次都让模型重新生成代码不如预先写好参数化脚本用户只需要填参数就行。这样做的好处是生成速度从 30 秒降到 1 秒以内而且质量稳定可控。我的做法是用 Jinja2 模板引擎把 cadquery 代码里的可变部分抽成变量from jinja2 import Template TEMPLATE import cadquery as cq result ( cq.Workplane(XY) .box({{ length }}, {{ width }}, {{ height }}) .faces(Z).workplane() .rect({{ length - 2 * edge_offset }}, {{ width - 2 * edge_offset }}, forConstructionTrue) .vertices() .cskHole({{ hole_diameter }}, {{ cb_diameter }}, {{ cb_angle }}) ) template Template(TEMPLATE) code template.render(length100, width60, height30, edge_offset10, hole_diameter4.5, cb_diameter8, cb_angle90)这个模式特别适合批量生成场景。我有个做创客教育的朋友用这套方案给学生批量生成不同尺寸的机器人底盘模型每个学生填自己的参数系统秒出 STEP 文件直接送 3D 打印机。以前他手动建模要花一整天现在十分钟搞定。另一个方向是多轮对话式建模。用户第一轮说“画一个盒子”系统生成一个基础盒子第二轮说“在顶面加四个孔”系统在已有模型基础上继续操作。这需要维护一个会话状态把之前的几何体保存下来每次新指令都在这个几何体上做增量修改。技术实现上可以用 cadquery 的Workplane对象序列化或者把每一步的操作记录成代码片段最后拼接执行。7. 我在这条路上踩过的几个坑第一个坑是低估了单位问题。早期版本没做单位校验用户输入“画一个 10 厘米的立方体”模型生成的是 10 毫米差了 10 倍。后来加了单位识别和转换逻辑支持毫米、厘米、米、英寸的自动换算才把这个坑填上。第二个坑是没有做几何有效性检查。生成的模型直接导出结果有些模型在 CAD 软件里打开是空心的或者有破面。后来在导出之前强制跑isValid()和fix()问题少了很多。第三个坑是忽略了并发场景。单用户测试的时候一切正常上线之后多个用户同时生成沙箱目录冲突、临时文件互相覆盖各种诡异问题。后来给每个请求分配独立的临时目录用 UUID 做前缀才彻底解决。第四个坑是对 STL 网格质量的预期管理。用户看到 STL 预览觉得“表面不够光滑”其实是网格公差设太大了。后来在前端加了网格精度选项让用户自己选“快速预览”还是“精细输出”投诉少了一大半。这套东西我断断续续搞了大半年从最初只能生成简单方块到现在能处理带孔、带槽、带倒角的复杂零件中间踩的坑比写过的代码还多。但每次看到用户输入一段文字、几秒钟后拿到一个可以直接打印的模型文件那种“这事成了”的感觉还是挺爽的。如果你也在搞类似的东西建议先从最简单的立方体开始把整条链路跑通再逐步加复杂度。别一上来就想生成齿轮箱那样容易卡在某个环节出不来。