
CAD 这行当有个特别拧巴的地方设计师脑子里的东西是三维的但落到软件里第一步永远是画二维草图、拉约束、标尺寸一套流程走下来创意早就凉了半截。text-to-cad 想干的事就是把这段翻译过程直接跳过——你用自然语言描述一个零件系统吐出可用的 CAD 模型文件。听起来像科幻但这两年随着大模型对几何理解能力的提升这条路已经能跑通一部分场景了。我最近花了几周时间把这条链路从头到尾搭了一遍踩的坑比想象中多也比想象中有意思。这篇就把整个思路、技术选型、实操步骤和我自己趟出来的经验完整摊开讲适合对 CAD 自动化、参数化建模、AI 辅助设计感兴趣的工程师和爱好者参考不管你是刚入门 CAD 制图的新手还是天天跟 STEP、URDF 打交道的老手应该都能捞到点东西。1. 先搞清楚 text-to-cad 到底在解决什么问题1.1 传统 CAD 建模流程里最耗时的环节在哪大部分人学 CAD 的第一课都是画直线、画圆、加约束然后拉伸成实体。这个流程本身没问题问题是它把想和做强行绑在了一起。你脑子里想的是一个带四个安装孔的法兰盘但手上要做的是新建草图、画外圆、画内孔、画四个螺栓孔、加同心约束、加尺寸约束、退出草图、拉伸、倒角、再开孔。这一套下来熟练工也得几分钟不熟练的半小时起步。真正耗时的不是操作本身而是反复修改。客户说孔距从 50 改成 60你得回去找草图、改尺寸、重建特征、检查有没有报错。如果这个零件还牵扯到装配体改一个尺寸可能引发连锁反应。text-to-cad 的价值就在这里如果模型是参数化生成的改一句话就能重建而不是重新画一遍。1.2 自然语言到几何模型之间隔着什么很多人以为 text-to-cad 就是让 AI 画图其实中间隔着一整套转换链路。自然语言描述的是意图比如一个直径 80 毫米、厚 10 毫米的圆盘中心有个直径 30 的孔边缘均匀分布 6 个直径 8 的孔。而 CAD 内核需要的是精确的几何与拓扑信息点的坐标、边的方向、面的法向量、特征之间的依赖关系。这中间至少要做三件事语义解析把自然语言拆成结构化的参数识别出直径厚度数量均匀分布这些关键词对应的数值和约束。几何推理根据参数算出所有关键点的坐标确定特征的生成顺序先拉伸还是先打孔结果可能不一样。格式输出把几何信息写成目标格式STEP 是通用交换格式URDF 是机器人描述格式G-code 是加工指令三者用途完全不同。我一开始低估了第二件事的难度。自然语言里边缘均匀分布这五个字翻译成几何就是在半径为 R 的圆周上以角度 2π/n 为间隔取 n 个点还要考虑起始角度、是否避开某个特征。这些细节如果不在提示词里说清楚生成结果就会飘。1.3 哪些场景真的适合用 text-to-cad不是所有建模任务都适合自然语言驱动。我实测下来以下几类场景收益最明显场景类型适合程度原因标准件批量生成高螺栓、垫片、法兰等结构固定参数明确参数化系列产品高同一结构不同尺寸改参数即可概念阶段快速原型中精度要求低主要看形状复杂曲面造型低自然语言难以描述自由曲面精密配合件低公差、配合关系需要精确控制提示如果你的零件有大量自由曲面、扫描曲线或者复杂的拔模角度现阶段还是老老实实手动建模别为难自己。2. 整条链路的技术选型为什么我最终选了这套组合2.1 大模型负责翻译CAD 内核负责落地最直觉的方案是让大模型直接输出 STEP 文件。我试过不行。STEP 是 ISO 10303 标准定义的文本格式里面有大量的实体引用、坐标系变换、拓扑关系大模型生成的 STEP 文件十有八九是语法正确但几何自相矛盾的——比如一个面的边界曲线不闭合或者实体之间有重叠。正确的分工应该是大模型只负责把自然语言转成结构化的参数描述比如 JSON真正的几何生成交给专业的 CAD 内核。这样职责清晰出错也容易定位。我用的参数描述格式大概长这样{ type: flange, outer_diameter: 80, thickness: 10, center_hole_diameter: 30, bolt_holes: { count: 6, diameter: 8, pitch_circle_diameter: 60 }, unit: mm }这个 JSON 就是大模型和 CAD 内核之间的合同。大模型的任务是把人话翻译成这份合同内核的任务是按合同施工。2.2 为什么选 CadQuery 而不是 FreeCAD 脚本Python 生态里能生成 CAD 模型的库不少我重点对比了三个FreeCAD 的 Python API功能全但 API 设计偏底层写起来啰嗦而且 FreeCAD 本身启动慢做批量生成时开销大。OpenSCAD脚本化建模的鼻祖但它是基于 CSG构造实体几何的做圆角、倒角这类操作很别扭而且输出格式有限。CadQuery基于 OpenCASCADE 内核API 设计接近人话链式调用写起来很顺支持 STEP、STL、DXF 多种输出。最后选 CadQuery 的决定性因素是它的选择器语法。比如你要选一个长方体顶面的所有边直接写box.faces(Z).edges()就行不用去算坐标。这在参数化建模里太重要了因为参数一变坐标全变但顶面这个语义是不变的。2.3 URDF 和 G-code 的定位差异热词里出现了 URDF 和 G-code这两个跟 STEP 完全不是一回事得说清楚STEP通用三维交换格式几乎所有 CAD 软件都能读适合做设计交付。URDF统一机器人描述格式描述的是机器人的连杆、关节、碰撞体重点在运动学关系而不是精确几何。把 CAD 模型转 URDF 通常是为了做仿真。G-code数控加工指令描述的是刀具路径是怎么加工而不是是什么形状。所以 text-to-cad 的输出格式取决于下游用途。做设计就出 STEP做机器人仿真就出 URDF要上机床就出 G-code。我这次主要打通的是 STEP 链路URDF 和 G-code 作为扩展方向在后面章节讲。3. 从零搭建环境准备与核心代码实现3.1 环境安装里最容易翻车的地方CadQuery 的安装是第一个坑。它依赖 OpenCASCADE 的 Python 绑定在 Windows 上直接pip install cadquery大概率会失败因为需要编译 C 扩展。我的建议是用 conda 装conda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery用 conda-forge 渠道能直接拿到预编译好的二进制包省去编译的麻烦。如果你坚持用 pip那得先装好 Visual Studio Build Tools还要配好 OpenCASCADE 的环境变量折腾程度翻倍。大模型这边我用的是 API 调用方式不依赖本地显卡。装个openai或者对应的 SDK 就行pip install openai注意CadQuery 对 Python 版本有要求3.10 和 3.11 最稳3.12 有些依赖还没跟上别问我怎么知道的。3.2 提示词工程怎么让大模型稳定输出结构化参数提示词是整条链路里最需要反复打磨的部分。我一开始写得很随意结果大模型输出的 JSON 字段名每次都不一样有时候用outer_diameter有时候用outer_radius还有时候直接给个size字段塞个数组。后来我用了三个手段来约束第一给完整的 schema 示例。不要只说输出 JSON要把每个字段的名字、类型、单位、含义都列出来。第二用 few-shot 示例。给两三个输入输出对让大模型照着格式来。第三加校验和重试。拿到 JSON 后用 Pydantic 做校验字段缺失或类型不对就带着错误信息重新请求。我的系统提示词核心部分是这样的SYSTEM_PROMPT 你是一个 CAD 参数解析助手。用户会用自然语言描述一个机械零件 你需要把它转换成结构化的 JSON 参数。 输出必须严格遵循以下 schema { type: 零件类型如 flange/plate/shaft, dimensions: { length: 数值或 null, width: 数值或 null, height: 数值或 null, diameter: 数值或 null }, features: [ {type: hole, diameter: 数值, position: [x, y], count: 数值} ], unit: mm 或 inch } 规则 1. 所有尺寸默认单位为毫米除非用户明确指定其他单位 2. 如果用户描述模糊选择最常见的工程默认值 3. 不要输出任何解释文字只输出 JSON 这套提示词跑下来结构化输出的成功率从最初的六成提到了九成以上。3.3 用 CadQuery 把参数变成实体拿到 JSON 之后就是 CadQuery 的活了。以法兰盘为例核心代码大概是这样import cadquery as cq import json def build_flange(params): d params[dimensions] outer_r d[diameter] / 2 thickness d[height] # 基础圆盘 result ( cq.Workplane(XY) .circle(outer_r) .extrude(thickness) ) # 中心孔 center_hole next( f for f in params[features] if f[type] hole and f[position] [0, 0] ) result result.faces(Z).workplane().hole(center_hole[diameter]) # 螺栓孔 bolt_holes next( f for f in params[features] if f[type] hole and f[count] 1 ) pitch_r bolt_holes.get(pitch_circle_diameter, outer_r * 0.75) / 2 result ( result.faces(Z).workplane() .polarArray(pitch_r, 0, 360, bolt_holes[count]) .hole(bolt_holes[diameter]) ) return result # 导出 STEP model build_flange(params) cq.exporters.export(model, flange.step)这段代码里有几个关键点值得说faces(Z)是选择器意思是法向量朝 Z 轴正方向的面也就是顶面。用选择器而不是硬编码坐标参数变了代码不用改。polarArray是环形阵列专门用来做均匀分布的孔比手动算坐标靠谱得多。hole()方法会自动做布尔减运算不用手动写cut。3.4 导出 STEP 时坐标系和单位的坑STEP 文件本身是带单位信息的但不同软件对单位的处理方式不一样。CadQuery 默认导出的是毫米但如果你在代码里用了英寸导出时不会自动转换得手动乘 25.4。坐标系也是个大坑。CadQuery 默认的工作平面是 XY 平面Z 轴朝上。但有些 CAD 软件尤其是某些机器人仿真工具默认 Z 轴朝前或者 Y 轴朝上。导出前最好确认一下下游软件的坐标系约定不然模型导进去是躺着的。# 如果需要调整坐标系可以在导出前旋转 model model.rotate((0, 0, 0), (1, 0, 0), 90) cq.exporters.export(model, flange_rotated.step)4. 实测中暴露的问题与我的处理方式4.1 大模型对空间关系的理解经常出错这是最头疼的问题。你让大模型描述在圆盘边缘均匀分布 6 个孔它可能在 JSON 里给你一个count: 6但位置信息是空的或者给一个明显不对的坐标。因为大模型本质上是在做文本预测它没有真正的空间推理能力。我的处理方式是在提示词里强制要求输出可计算的参数而不是坐标。比如环形阵列只要求输出阵列半径和数量具体坐标由 CadQuery 的polarArray算。这样就把空间推理的负担从大模型转移到了确定性的几何库上。对于更复杂的空间关系比如这个孔要在那个凸台的左侧我会在提示词里要求大模型先输出一个中间表示比如孔相对于凸台的位置关系左侧距离 10mm然后再由代码解析这个关系。多一层转换但稳定性高很多。4.2 参数缺失时的默认值策略用户描述零件时经常漏参数。说一个圆盘没说直径说打个孔没说孔径。这时候有两种策略一是追问二是用默认值。我选的是混合策略关键参数决定零件基本形状的缺失就追问次要参数倒角半径、孔位微调用默认值。默认值的选取参考了机械设计手册里的常见比例比如法兰盘螺栓孔节圆直径默认取外径的 0.75 倍倒角默认取板厚的 1/10但不小于 0.5mm孔径默认取板厚的 0.8 倍这些默认值不一定符合你的具体工况但至少能生成一个看起来合理的模型方便后续调整。4.3 生成模型的几何有效性校验大模型给的参数组合有时候是自相矛盾的比如孔径比外径还大或者孔的位置超出了零件边界。CadQuery 在遇到这种情况时可能直接抛异常也可能生成一个破面模型。我的做法是在生成之前加一层参数合理性检查def validate_params(params): d params[dimensions] errors [] if d.get(diameter) and d[diameter] 0: errors.append(直径必须为正数) for f in params.get(features, []): if f[type] hole: if f[diameter] d.get(diameter, float(inf)): errors.append(f孔径 {f[diameter]} 超过零件外径) return errors校验不通过就把错误信息回传给大模型让它重新生成参数。这个生成-校验-重试的循环最多跑三次三次还不行就报错让用户手动介入。4.4 批量生成时的性能优化单个零件生成很快一两秒的事。但如果你要批量生成几百个变体性能就成了问题。我实测下来瓶颈主要在两个地方大模型 API 的响应时间和 CadQuery 的建模时间。大模型这边能并发的就并发但要注意 API 的速率限制。CadQuery 这边可以复用 Workplane 对象避免重复初始化。另外导出 STEP 比导出 STL 慢不少如果只是做预览先导 STL 就够了。from concurrent.futures import ThreadPoolExecutor def batch_generate(descriptions): with ThreadPoolExecutor(max_workers4) as executor: results list(executor.map(generate_single, descriptions)) return results5. 扩展到 URDF 和 G-code 的思路5.1 从 STEP 转 URDF 做机器人仿真URDF 描述的是机器人的运动学结构核心元素是 link连杆和 joint关节。把 CAD 模型转 URDF通常是为了在仿真环境里做运动规划。转换的关键是简化几何。CAD 模型里的倒角、螺纹、小孔在仿真里都是负担需要简化成碰撞体。我的做法是用 CadQuery 生成简化版的包围盒或凸包作为 URDF 里的 collision geometry原始 STEP 作为 visual geometry。# 生成简化碰撞体 collision_mesh model.faces(Z).wires().toPending().extrude(-thickness) # 导出为 STL 供 URDF 引用 cq.exporters.export(collision_mesh, collision.stl)URDF 文件本身是 XML 格式手写也不难关键是把 link 的 origin、joint 的 axis 和 limit 配对。5.2 生成 G-code 需要补哪些信息G-code 是加工指令光有几何模型不够还需要知道毛坯尺寸、刀具参数、加工策略、进给速度、主轴转速。这些信息自然语言描述里通常没有得单独配置。我的思路是把 text-to-cad 的输出分成两层几何层STEP和工艺层加工参数。几何层由大模型生成工艺层由用户配置或者从工艺库匹配。两层合并后再用 CAM 库比如 FreeCAD 的 Path 模块生成 G-code。这条路我还没完全跑通主要卡在工艺参数的标准化上。不同材料、不同刀具、不同机床参数差异很大很难用一套默认值覆盖。5.3 多格式输出的统一抽象为了避免每加一种输出格式就改一遍代码我抽了一层接口class ModelExporter: def export(self, model, path, **kwargs): raise NotImplementedError class StepExporter(ModelExporter): def export(self, model, path, **kwargs): cq.exporters.export(model, path) class StlExporter(ModelExporter): def export(self, model, path, **kwargs): cq.exporters.export(model, path, tolerance0.01)这样新增格式只需要加一个类主流程不用动。6. 几个我踩过的坑和对应的经验6.1 中文描述里的单位歧义中文里毫米经常被省略用户说直径 80默认就是毫米。但有时候用户说的是直径 8 公分那就得转成 80 毫米。更麻烦的是寸可能是英寸也可能是市寸得结合上下文判断。我的处理方式是在提示词里明确要求大模型输出单位字段并且在解析时做一次归一化。如果单位缺失默认按毫米处理但在返回结果里标注单位未指定按毫米处理让用户知道。6.2 浮点数精度导致的几何错误CAD 内核做布尔运算时对精度很敏感。两个面如果理论上应该重合但浮点误差导致差了 1e-10布尔运算就可能失败。CadQuery 内部有容差处理但参数传递过程中如果做了不必要的浮点运算误差会被放大。我的经验是参数从 JSON 到 CadQuery 的过程中尽量不要做中间计算。比如需要半径就传半径不要传直径然后在代码里除以 2因为除以 2 可能引入误差。让 CadQuery 内部去处理这些转换。6.3 大模型 API 的不稳定性处理API 调用偶尔会超时或者返回格式错误。我的做法是加指数退避重试import time def call_llm_with_retry(prompt, max_retries3): for i in range(max_retries): try: response client.chat.completions.create( modelgpt-4, messages[{role: user, content: prompt}] ) return response.choices[0].message.content except Exception as e: if i max_retries - 1: raise time.sleep(2 ** i)重试的时候最好把上次的错误信息也带上让大模型知道哪里出了问题。6.4 生成结果的版本管理text-to-cad 的一个隐藏价值是可追溯。每次生成的参数 JSON、提示词、模型文件都应该存档这样出了问题能回溯。我用的是简单的文件命名规则{timestamp}_{hash}.json和{timestamp}_{hash}.step配合一个 SQLite 记录每次生成的元数据。这套东西搭起来之后改一个参数重新生成对比两个版本的差异比在 CAD 软件里手动改快太多了。7. 我对这套方案边界的判断text-to-cad 现在能稳定处理的是规则几何体板、轴、法兰、支架、简单的装配体。这些零件的共同特点是结构可以用有限的参数描述清楚特征之间的依赖关系不复杂。处理不了的是自由曲面和高度依赖工程判断的设计。比如一个要考虑流体力学的外壳或者一个要满足特定装配顺序的机构自然语言描述不清楚大模型也推理不出来。我的判断是这套方案短期内不会取代 CAD 工程师但会改变工作方式。工程师的角色从画图的人变成描述需求的人和校验结果的人。重复性的建模工作交给自动化人专注于真正需要判断力的部分。如果你也想试试这条路我的建议是从最简单的零件开始先把自然语言到参数 JSON 到 STEP这条链路跑通再逐步增加复杂度。别一上来就挑战复杂装配体会打击信心。CadQuery 的文档写得不错配合官方示例一两天就能上手。大模型这边提示词多迭代几轮把结构化输出的稳定性提上来后面就顺了。