我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Python实现text-to-cad:从自然语言到STEP与URDF自动生成

Python实现text-to-cad:从自然语言到STEP与URDF自动生成 1. 从一段文字到三维模型text-to-cad 到底在解决什么问题第一次听到 “text-to-cad” 这个词很多人脑子里浮现的画面可能是对着电脑敲一句“画一个长宽高 100×50×30 的盒子”然后屏幕上就自动出现一个三维模型。这个理解方向没错但只看到了冰山一角。text-to-cad 真正要解决的是从自然语言描述到结构化 CAD 数据之间的自动化转换——它不只是“画个图”而是生成可以被下游工具链直接消费的几何文件比如 STEP、URDF、STL 这些格式。我最初接触这个方向是因为一个机器人仿真项目。当时需要把几十个不同规格的机械臂连杆模型导入仿真环境每个连杆的尺寸、关节位置、质量属性都要手动在 CAD 软件里建一遍再导出 URDF。一个模型折腾半小时几十个就是十几个小时而且改一个参数就得从头再来。那时候我就在想能不能用代码把“描述”直接变成“模型”后来发现text-to-cad 这个思路正好切中了这个痛点。它的核心价值在于三点。第一降低建模门槛。不是每个人都会用 SolidWorks 或中望 CAD但每个人都会用自然语言描述一个物体的形状。第二打通自动化链路。在参数化设计、批量生成、仿真测试这些场景里手动建模是瓶颈而 text-to-cad 可以把建模变成一段可编程的流程。第三让 CAD 数据可版本控制。传统 CAD 文件是二进制的diff 起来很痛苦而 text-to-cad 的输入是文本输出是标准格式天然适合 Git 管理。适合谁来参考这篇内容如果你是从零开始学 Python 的初学者想找一个有实际产出的练手项目text-to-cad 是个不错的选择因为它涉及文件解析、几何计算、命令行交互等多个基础知识点。如果你是有经验的工程师想把手头的重复建模工作自动化这里面的工具选型和踩坑经验能帮你省不少时间。如果你在做机器人仿真、URDF 导入 Coppeliasim 这类工作那更是直接对口。注意text-to-cad 目前并不是一个“一句话生成复杂装配体”的成熟方案。它更适合生成规则几何体、简单零件、参数化结构。指望它直接画出汽车发动机现阶段还不现实。2. 核心思路拆解为什么选 Python STEP URDF 这条路线2.1 为什么用 Python 而不是其他语言做 text-to-cad语言选型其实没太多悬念。Python 在这个领域有天然优势几何处理库多、CAD 文件解析库成熟、和机器人工具链衔接顺畅。我试过用 JavaScript 做类似的事情光是找一个能稳定输出 STEP 文件的库就折腾了两天最后还是回到 Python。具体来说Python 生态里有几个关键库。cadquery是目前最成熟的参数化 CAD 建模库之一它基于 OpenCASCADE 内核能直接导出 STEP、STL、DXF 等格式。numpy用来做矩阵运算和几何变换比如旋转矩阵、平移向量这些。trimesh适合处理网格数据如果你需要做 STL 相关的操作它比 cadquery 更轻量。urdfpy或者yourdfpy用来生成和解析 URDF 文件做机器人仿真的话这个少不了。还有一个现实原因网上关于 Python 安装、Python 环境变量配置、pip 安装库的教程铺天盖地新手遇到问题容易搜到答案。你让一个初学者去配 C 的 OpenCASCADE 环境光是编译就能劝退一半人。2.2 STEP 和 URDF 分别扮演什么角色这两个格式经常被混在一起说但它们的用途完全不同。STEP是 CAD 领域的通用交换格式全称是 Standard for the Exchange of Product Data。它记录的是精确的边界表示B-rep也就是说一个圆柱在 STEP 文件里就是一个数学上精确的圆柱面而不是一堆三角形拼出来的近似体。这意味着 STEP 文件可以无损地导入到 SolidWorks、中望 CAD、Fusion 360 这些软件里继续编辑。text-to-cad 生成 STEP 的意义在于你用代码生成的模型设计师可以接着在 CAD 软件里细化。URDF是 Unified Robot Description Format专门用于描述机器人的运动学和动力学结构。它不关心一个连杆长什么样它关心的是这个连杆的质量是多少、惯性矩阵是什么、和下一个连杆通过什么类型的关节连接、关节的旋转轴在哪。URDF 导入 Coppeliasim 或者 ROS 之后仿真引擎才能正确计算运动。所以一个完整的 text-to-cad 流程往往是自然语言描述 → Python 解析 → 生成几何体 → 导出 STEP 用于 CAD 编辑 → 同时导出 URDF 用于仿真。两条输出线并行各取所需。2.3 整体架构设计我自己的项目里架构分成四层。最上面是输入层接收自然语言描述可以是一句话也可以是一个结构化的 YAML 文件。中间是解析层把描述拆解成几何参数和装配关系。下面是建模层调用 cadquery 生成实体计算质量属性。最底下是输出层根据目标格式导出 STEP、URDF 或 STL。这个分层的好处是每一层都可以独立替换。比如你不想用自然语言了直接传 JSON 也行你不想输出 URDF 了只导 STEP 也行。耦合度低调试起来也方便。实操心得不要一上来就追求“完全自然语言”。先把结构化输入跑通再逐步加入自然语言解析。我见过太多项目卡在 NLP 那一层结果核心的几何生成逻辑反而没时间打磨。3. 环境准备与工具链搭建从零到能跑通第一个模型3.1 Python 环境安装与避坑如果你电脑上还没装 Python直接去 Python 官网下载 3.8 以上的版本。为什么强调 3.8因为 cadquery 对 Python 版本有要求太老的版本装不上太新的版本有时候依赖库还没跟上。我实测下来3.8 到 3.10 之间最稳。安装的时候有一个关键选项一定要勾选 “Add Python to PATH”。这个选项如果不勾后面在命令行里敲python会提示找不到命令然后你就得手动配环境变量对新手来说又是一道坎。我帮人排查过好几次“python 安装后 cmd 里用不了”的问题九成都是这个原因。装完之后打开命令行敲python --version能显示版本号就说明安装成功。如果显示的是 “Python 不是内部或外部命令”那就回去重新安装记得勾选 PATH。3.2 核心库安装与版本匹配接下来安装核心依赖。我建议用 pip 逐个安装不要一次性pip install -r requirements.txt因为 cadquery 的依赖比较多逐个装能看到每一步的状态出问题也好定位。pip install cadquery pip install numpy pip install trimesh pip install urdfpy这里有几个坑要提前说。第一cadquery 在 Windows 上安装时可能会因为缺少 C 编译环境而失败。如果你遇到 “Microsoft Visual C 14.0 is required” 这类错误去装一个 Visual Studio Build Tools 就行。第二numpy 的版本要和 cadquery 兼容我遇到过 numpy 版本过高导致 cadquery 导入报错的情况后来锁定在 1.21 版本就正常了。第三如果你用的是 Linux 系统安装 cadquery 之前先确认libgl1和libglu1-mesa这些系统库有没有装否则导入时会报 OpenGL 相关的错误。注意不要混用 conda 和 pip 安装同一个库。我见过有人先用 conda 装了 numpy又用 pip 装了 cadquery结果 cadquery 内部调用的 numpy 版本和 conda 环境里的冲突排查了半天。选一个包管理器从头用到尾。3.3 验证环境是否可用装完之后写一个最简单的测试脚本import cadquery as cq result cq.Workplane(XY).box(10, 20, 30) cq.exporters.export(result, test.step) print(STEP file exported successfully)如果当前目录下生成了一个test.step文件说明环境没问题。用 CAD 软件打开这个文件应该能看到一个 10×20×30 的长方体。这一步虽然简单但能帮你排除掉大部分环境问题。4. 从文字到几何体核心建模逻辑与参数计算4.1 自然语言描述的结构化拆解假设用户输入这样一句话“生成一个长 100 毫米、宽 50 毫米、高 30 毫米的盒子壁厚 2 毫米顶部开口。”这句话里包含了哪些信息基本形状是盒子外形尺寸是 100×50×30壁厚是 2开口位置是顶部。如果要让程序理解这句话最直接的方式是用正则表达式提取数字和关键词。比如用re.findall(r\d, text)提取所有数字再根据关键词“长”“宽”“高”来分配。但正则的局限性很明显换个说法就失效了。比如“做一个 100 乘 50 乘 30 的方盒”数字之间的分隔符变了关键词也变了。所以更稳妥的做法是定义一个参数模板让用户按照固定格式输入或者用简单的键值对解析。我自己的做法是折中支持两种输入模式。一种是自然语言用正则加关键词匹配做初步解析另一种是结构化输入直接传 JSON 或 YAML。自然语言模式适合快速测试结构化模式适合集成到自动化流程里。4.2 几何参数的计算过程拿到参数之后不能直接丢给 cadquery 建模中间还有一些计算要做。以“顶部开口的盒子”为例实际需要生成的是一个壳体而不是实心体。cadquery 里可以用shell方法来做抽壳但抽壳的方向和开口面的选择需要明确。具体步骤是这样的先创建一个 100×50×30 的实心长方体然后选择顶面作为开口面执行抽壳操作壁厚设为 2。代码大概长这样import cadquery as cq length, width, height, thickness 100, 50, 30, 2 result ( cq.Workplane(XY) .box(length, width, height) .faces(Z) .shell(-thickness) ) cq.exporters.export(result, box_open_top.step)这里faces(Z)的意思是选择 Z 轴正方向最上面的那个面shell(-thickness)表示向内抽壳壁厚为 2。负号很关键正号是向外抽壳结果会不一样。如果你还需要计算这个盒子的质量那就得知道材料密度。假设用 ABS 塑料密度约 1.04 g/cm³体积可以通过 cadquery 的Volume()方法获取然后质量 体积 × 密度。这些计算在生成 URDF 的时候会用到因为 URDF 里需要填写质量、惯性矩阵等物理属性。4.3 复杂形状的分解策略不是所有东西都能用一个盒子搞定。遇到复杂形状怎么办我的经验是分解成基本体的布尔运算。比如一个带法兰的圆柱可以拆成“圆柱 圆盘”然后做并集。一个带孔的板可以拆成“长方体 - 圆柱”做差集。cadquery 支持链式调用布尔运算写起来很直观result ( cq.Workplane(XY) .box(100, 50, 10) .faces(Z) .workplane() .hole(20) )这段代码生成一个 100×50×10 的板然后在顶面中心打一个直径 20 的孔。hole方法默认是贯穿孔如果需要盲孔可以指定深度。实操心得布尔运算的顺序很重要。先做并集再做差集和先做差集再做并集结果可能完全不同。我建议在代码里把每一步的中间结果都导出成 STL用查看器看一眼确认无误再继续下一步。这样虽然麻烦一点但比最后发现模型错了再回头排查要省时间。5. 导出 STEP 与 URDF两条输出线的具体实现5.1 STEP 导出的参数与注意事项STEP 导出本身很简单一行代码的事cq.exporters.export(result, output.step)但有几个细节会影响导出质量。第一单位。cadquery 默认使用毫米作为单位导出的 STEP 文件也是毫米。如果你下游的 CAD 软件默认单位是英寸导入后尺寸会不对。解决办法是在导出时明确指定单位或者在导入时手动缩放。第二精度。STEP 文件记录的是精确几何但导出时可以设置公差。公差越小文件越大但形状越精确。对于大多数机械零件默认公差就够用了。如果你做的是光学元件或者精密配合件可以适当调小公差。第三坐标系。cadquery 生成的模型默认以原点为中心但有些 CAD 软件习惯以某个角点为原点。如果下游对坐标系有要求可以在导出前做一次平移变换。5.2 URDF 文件的结构与生成逻辑URDF 文件本质是一个 XML 文件描述的是机器人连杆和关节的树状结构。一个最简单的 URDF 长这样robot namesimple_robot link namebase_link visual geometry box size0.1 0.05 0.03/ /geometry /visual collision geometry box size0.1 0.05 0.03/ /geometry /collision inertial mass value0.5/ inertia ixx0.001 ixy0 ixz0 iyy0.001 iyz0 izz0.001/ /inertial /link /robot注意这里的尺寸单位是米不是毫米。这是 URDF 和 STEP 之间最容易出错的地方。如果你从 STEP 导出的模型尺寸是 100 毫米写进 URDF 的时候要除以 1000变成 0.1 米。我刚开始做的时候没注意这个导入 Coppeliasim 之后发现模型大了 1000 倍整个场景都装不下。质量属性的计算也需要留意。URDF 里的惯性矩阵需要根据几何形状和密度来算。对于长方体惯性矩的公式是Ixx (1/12) × m × (h² d²)Iyy (1/12) × m × (w² d²)Izz (1/12) × m × (w² h²)其中 m 是质量w、h、d 分别是长、高、宽。这些公式在 cadquery 里可以自动算但如果你手动写 URDF就得自己算清楚。5.3 用 Python 自动生成 URDF手动写 URDF 容易出错用 Python 生成更靠谱。可以用urdfpy库也可以用字符串模板。我倾向于用模板因为可控性更强。def generate_urdf_link(name, size, mass, inertia): template f link name{name} visual geometry box size{size[0]} {size[1]} {size[2]}/ /geometry /visual collision geometry box size{size[0]} {size[1]} {size[2]}/ /geometry /collision inertial mass value{mass}/ inertia ixx{inertia[0]} ixy0 ixz0 iyy{inertia[1]} iyz0 izz{inertia[2]}/ /inertial /link return template这个函数接收连杆名称、尺寸、质量和惯性矩返回一段 URDF 片段。多个连杆拼起来再加上关节定义就是一个完整的 URDF 文件。注意URDF 里的尺寸单位是米质量单位是千克惯性矩单位是千克·平方米。这三个单位必须统一否则仿真结果会完全不对。6. 常见问题与排查技巧实录6.1 环境类问题速查问题现象可能原因解决方法python命令找不到安装时未勾选 PATH重新安装并勾选 Add to PATHpip install cadquery失败缺少 C 编译环境安装 Visual Studio Build Tools导入 cadquery 报 OpenGL 错误缺少系统图形库Linux 下安装 libgl1 和 libglu1-mesanumpy 版本冲突conda 和 pip 混用统一用一个包管理器STEP 文件导入后尺寸不对单位不一致确认导出和导入的单位设置6.2 建模类问题排查问题一抽壳失败。有时候shell方法会报错提示无法生成壳体。原因通常是壁厚太大超过了几何体的最小尺寸。比如一个 10 毫米厚的板你抽壳 8 毫米剩下的部分太薄内核算不出来。解决办法是减小壁厚或者检查几何体是否有自相交。问题二布尔运算结果异常。两个实体做差集之后有时候会出现多余的碎片或者面。这通常是公差设置的问题。可以尝试调整cq.Workplane的tolerance参数或者在做布尔运算之前先做一次clean操作。问题三URDF 导入 Coppeliasim 后模型不可见。检查三个地方一是 visual 和 collision 的 geometry 是否正确二是尺寸单位是不是米三是 mesh 文件路径是不是对的。如果用的是基本几何体box、cylinder不需要 mesh 文件如果用的是自定义网格路径必须正确。6.3 性能优化经验当模型数量多起来之后生成速度会变慢。我做过一个测试生成 100 个简单盒子并导出 STEP耗时大约 12 秒。如果每个盒子都做复杂的布尔运算时间会成倍增加。优化手段有几个。第一复用 Workplane 对象。不要每次都新建可以在一个 Workplane 上连续操作。第二批量导出。cadquery 支持一次导出多个对象到同一个 STEP 文件比逐个导出快。第三并行处理。如果模型之间没有依赖关系可以用 Python 的multiprocessing并行生成速度能提升好几倍。实操心得如果你的模型生成脚本要跑很多次建议加一个缓存机制。把已经生成过的模型参数和对应的 STEP 文件路径存到一个字典里下次遇到相同参数直接跳过生成直接返回文件路径。这个技巧在批量处理时特别管用。7. 进阶方向从单零件到装配体7.1 装配关系的描述与实现单零件生成只是第一步。真正的 CAD 工作大部分是装配体。text-to-cad 要处理装配体就需要描述零件之间的位置关系和约束关系。位置关系好办就是平移和旋转。在 cadquery 里可以用translate和rotate方法把零件放到指定位置。旋转可以用欧拉角或者四元数表示我一般用欧拉角因为直观。约束关系就复杂一些。比如“这个孔和那个轴同心”“这个面和那个面贴合”这些约束在传统 CAD 里是交互式定义的在代码里需要用数学方式表达。我的做法是简化处理只支持几种常见的约束类型比如同心、贴合、对齐用坐标变换来实现。7.2 参数化模板的设计思路如果你经常生成同一类模型比如不同尺寸的法兰盘、不同长度的连杆可以做一个参数化模板。模板里定义好几何形状和参数占位符用户只需要填参数就能生成对应的模型。def generate_flange(outer_diameter, inner_diameter, thickness, bolt_count, bolt_circle_diameter): result ( cq.Workplane(XY) .circle(outer_diameter / 2) .circle(inner_diameter / 2) .extrude(thickness) ) # 添加螺栓孔 for i in range(bolt_count): angle 2 * 3.14159 * i / bolt_count x (bolt_circle_diameter / 2) * math.cos(angle) y (bolt_circle_diameter / 2) * math.sin(angle) result result.faces(Z).workplane().center(x, y).hole(bolt_hole_diameter) return result这个模板接收外径、内径、厚度、螺栓数量、螺栓圆直径等参数生成一个法兰盘。改参数就能生成不同规格的法兰不用重新写代码。7.3 与下游工具的衔接生成的 STEP 文件可以导入中望 CAD、SolidWorks 等软件继续编辑。生成的 URDF 可以导入 Coppeliasim、Gazebo 等仿真环境。如果你做的是 3D 打印还可以导出 STL 格式直接切片。有一个细节值得注意STEP 文件导入 CAD 软件后特征树里只有一个“导入的实体”没有建模步骤。这意味着设计师不能直接修改某个特征的参数只能在这个实体基础上继续操作。如果你希望下游能参数化编辑可以考虑同时输出一份 Python 脚本让设计师能看到建模逻辑。提示text-to-cad 生成的模型建议在导出前做一次几何检查确认没有自相交、没有零厚度面、没有重复顶点。这些缺陷在 CAD 软件里可能看不出来但导入仿真引擎后会导致计算失败。8. 我踩过的坑与最后分享的几个技巧第一个坑是单位混乱。我一开始没在意 STEP 和 URDF 的单位差异结果生成的模型导入仿真环境后尺寸完全不对。后来我在代码里加了一个统一的单位转换层所有内部计算用毫米导出 URDF 时自动除以 1000这个问题就再也没出现过。第二个坑是文件路径。Windows 上路径用反斜杠Linux 上用正斜杠如果代码里硬编码了路径分隔符换系统就报错。解决办法是用os.path.join或者pathlib让 Python 自己处理。第三个坑是依赖版本。cadquery 更新比较频繁有时候新版本会改 API旧代码就跑不了了。我的做法是在项目里锁定版本号比如cadquery2.2这样至少保证自己的代码不会因为库更新而突然挂掉。最后分享一个小技巧如果你要生成大量模型建议把参数和生成结果都记录到一个 CSV 文件里。这样万一某个模型生成失败你可以快速定位是哪个参数出了问题不用从头再跑一遍。这个习惯帮我省了很多重复劳动的时间。另外如果你在做 URDF 导入 Coppeliasim 的工作记得在 URDF 里把 collision 和 visual 分开定义。visual 可以用精细的 meshcollision 用简化的几何体这样仿真计算会快很多。我一开始偷懒两个都用同一个 mesh结果仿真帧率低得没法看后来把 collision 换成简单盒子帧率直接翻倍。
返回列表