text-to-cad实战:从自然语言到STEP与URDF的Python自动化链路
发布时间:2026/10/27 21:38:21 锦皓数字建站

1. 项目缘起与核心思路拆解第一次听到 text-to-cad 这个词我的直觉是终于有人把大语言模型和传统 CAD 工作流之间的那堵墙凿开了一个口子。过去几年我们做机械设计、机器人建模或者产品结构验证绕不开的一个环节就是手动在 CAD 软件里拉草图、约束尺寸、拉伸切除、装配配合。一个中等复杂度的零件从需求到能用的三维模型熟练工也得花上小半天。而 text-to-cad 想做的事情非常直接你用自然语言描述一个零件或结构系统自动生成可用的 CAD 文件通常是 STEP 格式有时还会输出 URDF 用于机器人仿真。这个项目的核心价值不在于“替代设计师”而在于把重复性建模、概念验证、参数化变体生成这些环节的耗时压缩到分钟级。我实测过几个类似的流程一个带孔法兰盘从写描述到拿到 STEP 文件大概两分钟。如果手动建模算上打开软件、建草图、标注、拉伸、打孔、导出十五分钟起步。对于需要快速迭代几十个变体的场景这个效率差距是数量级的。适合谁来参考这篇文章三类人一是做机器人仿真的工程师经常需要根据文字描述快速生成 URDF 和配套的 STEP 模型二是产品设计前期做概念验证的人需要快速把想法变成可视化的三维文件三是对 Python 和 CAD 自动化感兴趣的技术爱好者想理解从文本到几何体这条链路到底是怎么打通的。不需要你是 CAD 专家但最好对 Python 有基本了解知道什么是虚拟环境、怎么装库。整个 text-to-cad 的技术链路我把它拆成四层语义解析层、几何参数化层、CAD 内核调用层、格式导出层。语义解析层负责把自然语言变成结构化的几何参数比如“一个长 100mm、宽 50mm、厚 10mm 的板四角各有一个直径 5mm 的孔”。几何参数化层把这些参数映射成 CAD 内核能理解的草图、特征和约束。CAD 内核调用层通常用 Python 的 cadquery 或者 OpenCASCADE 绑定来执行建模操作。格式导出层负责把模型写成 STEP、STL 或 URDF。每一层都有坑后面我会逐个拆开讲。为什么选 Python 作为主力语言因为 CAD 领域的开源生态里Python 的绑定最成熟。cadquery 基于 OpenCASCADEAPI 设计得比较符合直觉而且能直接导出 STEP。URDF 那边有 urdfpy 和你的机器人描述需求对接。再加上 Python 在数据处理和 AI 集成上的天然优势整条链路用 Python 串起来是最顺的。如果你用 C 直接调 OpenCASCADE性能会好一些但开发效率会下降很多对于快速验证的场景不划算。注意text-to-cad 不是要取代传统 CAD 软件。它擅长的是参数化、规则明确的零件。对于自由曲面、复杂装配体、工程图标注这些任务目前还远远不够。把它当成一个“快速草稿生成器”来用心态会好很多。2. 核心细节解析与实操要点2.1 语义解析从一句话到几何参数表这一步是整个流程里最“AI”的部分但也是最容易出问题的部分。大语言模型很擅长理解“一个圆柱体直径 30mm高 50mm”这种描述但一旦描述里出现隐含约束比如“一个 L 形支架竖板高 80mm横板长 60mm厚度都是 8mm转角处倒圆角 R5”模型就需要推断出竖板和横板的相对位置、圆角作用在哪个边。我的经验是不要指望一次解析就完美要在提示词里明确要求输出 JSON 格式的参数表并且给出字段定义。比如你可以这样设计提示词模板prompt_template 你是一个 CAD 参数解析器。请把下面的自然语言描述转换成 JSON 格式的几何参数表。 字段定义 - shape_type: 基础形状类型可选值有 box, cylinder, plate_with_holes, l_bracket, flange - dimensions: 一个对象包含该形状所需的尺寸参数单位统一为毫米 - features: 一个数组每个元素描述一个特征如孔、倒角、圆角 - material: 材料名称如果描述中没有提到填 unknown 用户描述{user_input} 只输出 JSON不要输出其他内容。 这样做的好处是后续的几何参数化层可以直接消费这个 JSON不需要再做复杂的文本解析。我试过让模型直接输出 cadquery 代码虽然也能跑但出错率明显更高因为模型对 cadquery 的 API 细节掌握得并不牢靠经常写出不存在的函数名或者参数顺序错误。先转 JSON再由确定性代码生成 cadquery 脚本稳定性会好很多。还有一个坑是单位。用户可能说“长 10 厘米”也可能说“长 100”默认单位是毫米还是米我的做法是在提示词里强制要求模型把所有尺寸转换成毫米并且在输出 JSON 里加一个unit字段值固定为mm。这样后续处理就不用猜了。2.2 几何参数化把 JSON 映射成 CAD 操作序列拿到 JSON 之后下一步是把它翻译成 cadquery 的操作序列。cadquery 的编程模型是链式调用比如import cadquery as cq result ( cq.Workplane(XY) .box(100, 50, 10) .faces(Z) .workplane() .rect(80, 30) .cutThruAll() )这段代码生成一个 100x50x10 的板然后在顶面切一个 80x30 的通槽。对于 text-to-cad 来说你需要写一个映射函数根据shape_type和features动态生成这样的链式调用。我的做法是给每种形状写一个独立的构建函数然后用一个调度器根据 JSON 里的类型字段调用对应的函数。以plate_with_holes为例构建函数大概长这样def build_plate_with_holes(params): length params[dimensions][length] width params[dimensions][width] thickness params[dimensions][thickness] holes params[features] result cq.Workplane(XY).box(length, width, thickness) for hole in holes: if hole[type] hole: diameter hole[diameter] positions hole[positions] # 列表每个元素是 (x, y) for pos in positions: result ( result.faces(Z) .workplane() .center(pos[0], pos[1]) .hole(diameter) ) return result这里有个细节hole()方法默认是打穿整个零件的但如果你在错误的工作平面上操作孔的方向会不对。我踩过的坑是在faces(Z)之后没有调用workplane()导致center()的坐标系还是全局坐标系孔的位置偏了。正确的做法是每次选择面之后都显式调用workplane()把工作平面重置到该面上。另一个坑是孔的位置坐标。用户描述“四角各有一个孔”时模型可能输出(0, 0), (length, 0), (0, width), (length, width)但实际加工时孔中心通常要离边缘留一定距离比如 5mm。我的处理方式是在提示词里加一条规则“如果用户没有指定孔到边缘的距离默认取孔径的 1.5 倍且不小于 3mm。”这样模型会自动计算偏移后的坐标减少后续手动调整。2.3 CAD 内核调用cadquery 的稳定性技巧cadquery 底层是 OpenCASCADE功能很强但有些操作在特定版本下会出问题。我目前用的是 cadquery 2.4 版本配合 Python 3.10实测比较稳。安装的时候建议用 conda 而不是 pip因为 OpenCASCADE 的二进制依赖在 pip 下偶尔会有兼容性问题。命令是conda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery如果你只能用 pip那就pip install cadquery但要注意pip 安装的 cadquery 在某些 Linux 发行版上需要手动装 libGL 和 libGLU否则导入会报错。Ubuntu 下的命令是sudo apt-get install libgl1-mesa-glx libglu1-mesa还有一个常见问题是导出 STEP 时文件名包含中文或特殊字符会导致导出失败。我的习惯是统一用英文和数字命名比如flange_001.step。导出代码很简单cq.exporters.export(result, flange_001.step)如果你需要同时导出 STL 用于 3D 打印预览可以cq.exporters.export(result, flange_001.stl)提示cadquery 的export函数会根据文件扩展名自动选择导出格式所以扩展名一定要写对。.step和.stp都支持但建议统一用.step。2.4 URDF 导出从 CAD 到机器人仿真URDF 是机器人描述格式用来定义机器人的连杆、关节和运动学结构。text-to-cad 生成的 URDF 通常用于 CoppeliaSim 这类仿真环境。这里的关键是URDF 需要的是网格文件通常是 STL 或 DAE加上关节的坐标系变换而不是 STEP 文件。所以流程是先用 cadquery 生成 STEP再转成 STL然后写 URDF 的 XML。转 STL 的代码前面已经给了。写 URDF 的时候最容易出错的是关节的原点和轴向。比如一个旋转关节它的原点应该定义在旋转轴上轴向用axis标签指定。我一般会用一个 Python 脚本根据 JSON 里的关节信息自动生成 URDFdef generate_urdf(link_name, mesh_file, joint_name, joint_type, origin_xyz, axis_xyz): urdf f?xml version1.0? robot name{link_name}_robot link name{link_name} visual geometry mesh filename{mesh_file} scale1 1 1/ /geometry /visual collision geometry mesh filename{mesh_file} scale1 1 1/ /geometry /collision /link joint name{joint_name} type{joint_type} parent linkbase_link/ child link{link_name}/ origin xyz{origin_xyz} rpy0 0 0/ axis xyz{axis_xyz}/ /joint /robot return urdf导入 CoppeliaSim 的时候注意 URDF 文件里的 mesh 路径要写相对路径或者绝对路径CoppeliaSim 对路径比较敏感。我一般把 STL 和 URDF 放在同一个目录下mesh 文件名只写文件名不写路径这样 CoppeliaSim 会自动在同目录下找。3. 实操过程与核心环节实现3.1 环境搭建从零到能跑通第一个模型我建议用 conda 来管理环境因为 cadquery 的依赖比较多conda 能自动处理 OpenCASCADE 的二进制包。步骤如下安装 Miniconda 或 Anaconda。如果已经装了 Python也可以直接用 venv但 cadquery 在 venv 下安装有时会缺系统库。创建虚拟环境conda create -n text2cad python3.10激活环境conda activate text2cad安装 cadqueryconda install -c conda-forge cadquery安装其他依赖pip install openai如果你用 OpenAI 的 API 做语义解析、pip install urdfpy用于 URDF 校验验证安装python -c import cadquery; print(cadquery.__version__)如果这一步报错说找不到OCP模块说明 OpenCASCADE 的 Python 绑定没装好。解决办法是重新用 conda 安装不要混用 pip 和 conda。3.2 完整流程从一句话到一个 STEP 文件我拿一个实际例子来走一遍。用户输入“生成一个法兰盘外径 120mm内径 60mm厚度 15mm沿圆周均匀分布 6 个直径 10mm 的螺栓孔螺栓孔中心圆直径 90mm。”第一步语义解析。把这句话发给大语言模型要求输出 JSON。我用的提示词前面已经给了这里直接看输出{ shape_type: flange, dimensions: { outer_diameter: 120, inner_diameter: 60, thickness: 15 }, features: [ { type: bolt_holes, count: 6, diameter: 10, pitch_circle_diameter: 90 } ], material: unknown, unit: mm }第二步几何参数化。写一个build_flange函数import cadquery as cq import math def build_flange(params): od params[dimensions][outer_diameter] id_ params[dimensions][inner_diameter] thickness params[dimensions][thickness] bolt_holes params[features][0] count bolt_holes[count] hole_dia bolt_holes[diameter] pcd bolt_holes[pitch_circle_diameter] # 创建外圆柱 result cq.Workplane(XY).circle(od / 2).extrude(thickness) # 切内孔 result result.faces(Z).workplane().hole(id_) # 切螺栓孔 for i in range(count): angle 2 * math.pi * i / count x (pcd / 2) * math.cos(angle) y (pcd / 2) * math.sin(angle) result ( result.faces(Z) .workplane() .center(x, y) .hole(hole_dia) ) return result第三步调用并导出flange build_flange(json_params) cq.exporters.export(flange, flange_001.step)实测下来从输入文字到生成 STEP 文件整个过程在普通笔记本上大约 3 到 5 秒其中大部分时间花在调用大语言模型 API 上。如果本地部署一个小模型做语义解析速度会更快但解析准确率会下降。我的建议是对于精度要求高的场景用大模型 API对于批量生成、精度要求不高的场景可以用本地小模型加规则校验。3.3 参数计算螺栓孔位置和倒角尺寸的确定螺栓孔的位置计算看起来简单但有几个细节容易忽略。第一角度步长是2π / count但如果 count 是奇数起始角度最好偏移半个步长这样孔不会正好落在水平轴上视觉上更对称。第二孔中心到边缘的距离要大于孔径的一半加上最小壁厚。比如外径 120mm螺栓孔中心圆直径 90mm那么孔中心到外边缘的距离是(120 - 90) / 2 15mm孔径 10mm半径 5mm剩余壁厚 10mm足够。如果这个值小于 3mm就要提醒用户调整参数。倒角尺寸的确定也有讲究。对于法兰盘外边缘通常倒角 C1 或 C2内孔边缘倒角 C0.5 或 C1。这些在用户描述里往往不会提到但实际加工时需要。我的做法是在提示词里加一条“如果用户没有指定倒角默认外边缘倒角 C1内孔边缘倒角 C0.5。”然后在几何参数化层自动加上倒角操作result result.edges(|Z).chamfer(1.0)edges(|Z)选择所有平行于 Z 轴的边也就是外圆柱面和内孔面的边。chamfer(1.0)表示倒角 1mm。注意如果零件上有其他平行于 Z 轴的边比如螺栓孔的内壁也会被选中。这时候需要更精确的选择器比如edges(%CIRCLE)选择圆形边再根据半径过滤。3.4 URDF 导入 CoppeliaSim 的实操记录生成 URDF 之后导入 CoppeliaSim 的步骤是打开 CoppeliaSim菜单栏File-Import-URDF选择 URDF 文件。如果一切正常你会看到模型出现在场景里。但有几个常见问题模型位置不对URDF 里的origin标签定义了关节的原点如果原点设成了(0, 0, 0)模型可能会沉到地面以下。解决办法是调整origin的 z 值让模型底部刚好接触地面。网格不显示检查 STL 文件路径是否正确CoppeliaSim 的控制台会输出错误信息告诉你哪个文件找不到。关节不能动URDF 里的关节类型要写对revolute是旋转关节prismatic是滑动关节。如果写成了fixed关节就不会动。我一般会在 CoppeliaSim 里手动调整一下模型的初始位置然后保存成.ttt文件这样下次直接打开就行不用重新导入。4. 常见问题与排查技巧实录4.1 语义解析不准模型理解偏差的修正方法大语言模型有时候会把“直径”理解成“半径”或者把“厚度”理解成“高度”。我的应对策略是在提示词里加几个示例也就是 few-shot prompting。比如示例1 用户描述一个圆柱直径 20mm高 30mm 输出{shape_type: cylinder, dimensions: {diameter: 20, height: 30}, ...} 示例2 用户描述一个方块长 50宽 30高 20 输出{shape_type: box, dimensions: {length: 50, width: 30, height: 20}, ...}加了示例之后解析准确率明显提升。另外可以在输出 JSON 之后加一个校验步骤检查尺寸是否在合理范围内。比如直径不能为负数厚度不能大于外径的一半等等。如果校验失败就重新调用模型并在提示词里加上错误信息让模型修正。4.2 cadquery 报错常见异常与解决cadquery 的报错信息有时候不太直观。我整理了几个常见的报错信息可能原因解决办法ValueError: Null TopoDS_Shape object操作产生了空形状比如切孔时孔的位置在零件外部检查孔坐标是否在零件范围内Standard_Failure: BRep_API: command not done倒角或圆角尺寸过大超过了边的长度减小倒角尺寸或检查边的选择器ImportError: cannot import name OCPOpenCASCADE 绑定未正确安装用 conda 重新安装 cadqueryRuntimeError: Export failed导出路径不存在或文件名包含非法字符检查路径用英文命名还有一个坑是cadquery 的hole()方法默认是打穿整个零件但如果你在非平面的工作平面上调用可能会失败。解决办法是确保工作平面是平面或者用cutThruAll()代替。4.3 URDF 导入失败路径和坐标系问题URDF 导入 CoppeliaSim 失败最常见的原因是 mesh 文件路径不对。URDF 里的mesh filename.../如果写的是绝对路径换一台机器就失效了。建议写相对路径并且把 STL 文件和 URDF 文件放在同一个目录下。另外CoppeliaSim 对 STL 的单位很敏感默认按米处理。如果你的 STL 是按毫米生成的导入后会变成巨大或极小的模型。解决办法是在导出 STL 时指定单位或者在 CoppeliaSim 里手动缩放。坐标系问题也很常见。URDF 的坐标系是右手系Z 轴向上。如果你的 CAD 模型是 Y 轴向上导入后模型会躺倒。解决办法是在导出 STL 之前用 cadquery 旋转模型result result.rotate((0, 0, 0), (1, 0, 0), -90)这样就把 Y 轴向上的模型转成了 Z 轴向上。4.4 批量生成时的性能优化如果你需要批量生成几十个模型每次调用大语言模型 API 会很慢。我的优化方案是先用大模型生成一批 JSON 参数表缓存到本地文件然后写一个循环逐个读取 JSON调用 cadquery 生成 STEP。这样 API 调用只发生一次后续的几何生成都是本地操作速度快很多。另外cadquery 的建模操作可以并行化用 Python 的multiprocessing模块开多个进程每个进程处理一个 JSON。实测下来8 核机器上并行生成 50 个模型总时间从 3 分钟降到 30 秒左右。注意并行化的时候每个进程要独立导入 cadquery不要共享同一个 Workplane 对象否则会出现线程安全问题。4.5 独家避坑技巧汇总单位统一所有尺寸在进入几何参数化层之前强制转换成毫米。我见过太多因为单位混乱导致模型大 1000 倍的案例。参数校验在生成模型之前加一个校验函数检查尺寸是否为正数、孔是否在零件内部、倒角是否小于最小边长的三分之一。这些检查能拦截 80% 的明显错误。版本锁定cadquery 的 API 在不同版本之间有细微变化建议在项目里锁定版本号比如cadquery2.4。升级之前先在测试环境跑一遍回归。日志记录每次生成模型时把输入的 JSON、生成的 Python 代码、导出的文件路径都记到日志里。出问题的时候翻日志比重新跑一遍快得多。人工复核对于关键零件生成 STEP 之后一定要在 CAD 软件里打开看一眼。自动生成的模型偶尔会有微小的几何缺陷比如面不闭合、边不连续这些在导出时不一定报错但会影响后续加工。5. 扩展方向与个人体会text-to-cad 这条链路跑通之后我陆续试了几个扩展方向。一个是加装配体支持让用户描述“一个减速箱包含箱体、输入轴、输出轴、齿轮”系统自动生成多个零件并装配。这个难度比单零件大很多因为涉及配合关系和运动约束。另一个方向是加工程图输出生成 STEP 之后自动投影三视图标注尺寸。这个用 cadquery 的exporters.export配合 SVG 导出可以做到但标注的自动布局还需要不少规则。我个人在实际操作中的体会是text-to-cad 目前最适合的场景是“参数化变体生成”。比如你有一组标准零件需要根据客户需求调整尺寸手动改模型很繁琐用 text-to-cad 写一个模板输入不同参数就能批量出图。这种场景下语义解析的准确率要求不高因为参数是结构化的可以直接从表格读取不需要经过大语言模型。真正需要大语言模型的场景是“从模糊描述到初步模型”这时候不要追求一次完美先出一个粗略版本再手动微调效率反而更高。最后分享一个小技巧如果你经常需要生成类似的零件可以建一个“零件模板库”每个模板对应一个 Python 函数和一段提示词。用户输入描述后先用一个分类模型判断属于哪个模板再调用对应的函数。这样比让大语言模型从零生成代码要稳定得多而且生成速度也快。模板库可以随着使用不断扩充慢慢覆盖你工作中 80% 的常见零件。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。