ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

text-to-cad 实战:从文本描述到 STEP 与 URDF 的自动化生成

text-to-cad 实战:从文本描述到 STEP 与 URDF 的自动化生成 1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 “text-to-cad” 这个词很多人脑子里浮现的画面大概是对着电脑说一句“给我画个法兰盘”然后屏幕上就自动蹦出一个带倒角、带螺栓孔的 STEP 文件。这个想象不算离谱但也不完全准确。我做了几年参数化建模和自动化脚本实际接触下来text-to-cad 更准确的定位是——用自然语言或结构化文本描述驱动 CAD 内核自动生成几何模型、装配关系或工程文件。它不是一个单一软件而是一类工作流的统称核心价值在于把“人手动点鼠标建模”变成“文本描述→程序解析→几何生成→格式导出”的自动化链路。这件事为什么值得聊因为传统 CAD 建模有一个很现实的痛点重复性劳动太多。比如你要画一百个不同尺寸的螺栓每个都要拉伸、倒角、打螺纹孔手动操作不仅慢还容易出错。再比如做仿真前处理URDF 文件里的连杆几何往往需要和 CAD 模型保持一致手动导出导入来回折腾版本一多就乱套。text-to-cad 的思路就是把这些“有规律、可参数化”的部分交给代码人只负责描述需求和规则。适合看这篇内容的人我大致分三类第一类是机械、结构方向的工程师日常用 SolidWorks、中望 CAD、Fusion 360 这类工具想把手里的重复活自动化第二类是机器人或仿真方向的同学需要频繁处理 URDF、STEP、G-code 之间的转换和一致性维护第三类是写 Python 的开发者想切入 CAD 自动化但不知道从哪下手。不管你属于哪一类下面这些内容都是从实际项目里摔打出来的不是纸上谈兵。提示text-to-cad 不是要取代传统 CAD 软件而是给它们加一层“可编程的入口”。你仍然需要理解基本的建模逻辑只是操作方式从点鼠标变成了写描述。2. 核心思路拆解为什么是“文本驱动几何”而不是“语音控制鼠标”2.1 文本描述与几何内核之间的桥梁很多人会问为什么不直接让 AI 控制 CAD 软件的界面模拟鼠标点击我试过类似方案结论是极其脆弱。界面坐标会变、弹窗会挡、版本更新后按钮位置一挪整个脚本就废了。text-to-cad 走的是另一条路绕过 GUI直接调用几何内核。目前主流的开源几何内核有 OpenCASCADE简称 OCCT、CGAL商业的还有 Parasolid、ACIS。Python 生态里cadquery、build123d、pythonocc都是基于 OCCT 的封装让你用代码描述“画一个长方体在顶面中心打一个直径 10 的孔”内核负责算出精确的 B-rep 边界表示。这个桥梁的关键在于中间描述层。自然语言进来之后不能直接扔给内核因为内核只认精确的几何指令。所以通常要经过一层解析把“一个 80x60x20 的板四角各一个 M6 通孔”转成结构化的参数——长 80、宽 60、厚 20、孔位坐标、孔径 6.6M6 通孔的标准底孔、孔深贯穿。这一步可以用规则引擎做也可以用大模型做意图识别但最终落到内核的必须是确定性的数值和拓扑关系。2.2 为什么 STEP 和 URDF 是绕不开的格式做 text-to-cad导出格式的选择直接决定了你的模型能不能被下游工具吃掉。我列一个实际项目里常见的格式对照格式典型用途text-to-cad 中的角色注意事项STEP通用三维交换最终交付的主流格式保留 B-rep 精度适合加工和仿真STL3D 打印、快速预览中间检查用三角网格丢失精确曲面信息URDF机器人仿真描述连杆与关节几何常用简化体需与 STEP 对应G-code数控加工从几何生成刀路依赖 CAM 后处理不是建模输出IGES老式交换格式兼容旧系统逐渐被 STEP 替代STEP 之所以重要是因为它是唯一能在不同 CAD 系统之间保持精确几何的通用格式。你用 cadquery 生成的模型导出 STEP别人用中望 CAD 或 SolidWorks 打开尺寸不会跑。URDF 则是机器人领域的“装配说明书”它不关心你用什么内核建模只关心每个连杆的质量、惯性矩、视觉几何和碰撞几何。text-to-cad 在这里的价值是同一份参数描述可以同时生成用于加工的 STEP 和用于仿真的 URDF保证两者几何一致不用手动对齐。2.3 方案选型规则引擎、模板库还是大模型实际落地时text-to-cad 有三种典型实现路径我按可控性从高到低排纯规则引擎用正则或语法解析器提取参数套进预定义的建模函数。优点是稳定、可测试、不依赖网络缺点是只能处理你预先想到的描述模式。模板库 参数填充把常见零件做成参数化模板法兰、齿轮、支架文本只负责选模板和填参数。这是工业界最务实的做法我见过的大部分产线自动化都走这条路。大模型意图识别 代码生成让模型把自然语言转成 cadquery 或 build123d 代码再执行。灵活度最高但需要加校验层否则生成的代码可能几何自交或参数越界。我的建议是先用模板库把高频零件覆盖掉再用大模型处理长尾描述。一上来就全交给大模型调试成本会让你怀疑人生。3. 核心细节解析从文本到几何的关键环节与实操要点3.1 参数提取把“人话”变成数字和约束文本描述里最麻烦的不是数字而是隐含约束。比如“一块板四角打孔孔边距板边 10 毫米”这里“四角”隐含了对称关系“孔边距”隐含了孔中心到板边的距离。规则引擎要能识别这些模式我通常用一套“槽位填充”的思路# 伪代码示意从描述中提取板件参数 description 80x60x20 的板四角 M6 通孔边距 10 # 提取尺寸 length, width, thickness 80, 60, 20 # 提取孔规格 hole_spec M6 # M6 通孔底孔直径查表 hole_dia 6.6 # 边距 edge_margin 10 # 计算四角孔中心坐标 hole_positions [ (edge_margin, edge_margin), (length - edge_margin, edge_margin), (edge_margin, width - edge_margin), (length - edge_margin, width - edge_margin) ]这里的关键是查表。M6 通孔不是直径 6而是 6.6这是机械设计手册里的标准。text-to-cad 系统里必须内置这类工程常识表否则生成的模型装不上螺栓。我见过有人直接用公称直径打孔结果装配时全部干涉返工重做。注意参数提取阶段一定要做范围校验。比如板厚 20 毫米孔边距 10 毫米如果孔径 6.6那么孔壁到板边只剩 3.4 毫米强度可能不够。系统应该给出警告而不是闷头生成。3.2 几何生成cadquery 与 build123d 的取舍Python 生态里cadquery和build123d是目前最活跃的两个 OCCT 封装。我两个都用过说下实际感受cadquery链式调用风格Workplane对象一路.box().faces().workplane().hole()下去写起来像流水账适合快速原型。社区大例子多遇到问题好搜。build123d更接近 Python 的上下文管理器风格with BuildPart() as p:这种写法几何关系更直观类型提示更好。适合复杂装配和需要严格类型检查的项目。选哪个如果你只是做单件自动化cadquery 上手更快如果你要维护一个长期演进的参数化库build123d 的结构更清晰。我现在的项目里简单件用 cadquery复杂装配用 build123d两者可以混用因为底层都是 OCCT。生成几何时有一个必须注意的坑布尔运算的顺序。先做大的减运算再做小的加运算最后倒角。如果顺序反了倒角可能失败或者产生微小面。我一般把倒角放在最后一步并且给一个容差比如fillet(0.5)失败就降到0.3重试。3.3 格式导出STEP、URDF、G-code 的生成要点导出 STEP 相对简单cadquery 和 build123d 都有export_step方法。但 URDF 的生成要复杂得多因为 URDF 是 XML 格式需要描述连杆层级、关节类型、惯性矩阵、视觉和碰撞几何。text-to-cad 在这里要做的是从几何模型自动计算质量属性体积×密度生成惯性张量然后把几何以 STL 或简化体形式嵌入 URDF。!-- URDF 片段示意一个连杆 -- link namebase_link visual geometry mesh filenamebase_link.stl/ /geometry /visual collision geometry box size0.08 0.06 0.02/ /geometry /collision inertial mass value0.5/ inertia ixx0.0001 ixy0 ixz0 iyy0.0002 iyz0 izz0.00015/ /inertial /link惯性矩阵的计算是难点。简单形状可以查公式复杂形状要用网格积分。我通常用trimesh库做体积分解和惯性计算精度够用。碰撞几何则故意简化成包围盒或圆柱因为仿真引擎不需要精确碰撞简化体能大幅提升计算速度。G-code 的生成是另一条链路几何→CAM 刀路→后处理。text-to-cad 本身不直接生成 G-code但可以输出适合 CAM 的 STEP或者调用FreeCAD的 Path 模块做简单刀路。实际生产中G-code 还是交给专业 CAM 软件text-to-cad 负责把模型准备好。3.4 实操心得三个容易翻车的地方第一个坑是单位。CAD 内核默认单位可能是毫米也可能是米URDF 默认是米。我吃过一次亏STEP 导出时是毫米URDF 里没转换结果仿真里机器人大了 1000 倍直接飞到天上。现在我的代码里强制在入口处统一成毫米导出 URDF 时再除以 1000。第二个坑是坐标系。CAD 建模通常 Z 轴向上机器人 URDF 经常 Z 轴向前或 Y 轴向上。text-to-cad 系统里要有一个坐标变换层否则装配关系全乱。我一般用 4x4 齐次变换矩阵做统一每个零件生成时记录自己的局部坐标系装配时再乘变换。第三个坑是文件名和路径。Windows 下路径有空格或中文OCCT 有时会读失败。我现在的习惯是所有中间文件用英文下划线路径不含空格导出前先os.makedirs确保目录存在。4. 完整实操流程从一句描述到可用的 STEP 和 URDF4.1 环境准备与依赖安装先说我用的环境Python 3.10Windows 和 Ubuntu 都跑过。核心依赖就几个pip install cadquery build123d trimesh numpy lxmlcadquery安装时如果遇到 OCCT 编译问题可以直接用 condaconda install -c conda-forge cadquerybuild123d对 Python 版本要求较新3.10 以上比较稳。trimesh用来算质量和惯性lxml用来生成 URDF 的 XML。不需要装完整的 CAD 软件这些库自带几何内核。提示如果你在公司内网pip 源可能连不上提前配好镜像或者离线包。我遇到过装 cadquery 卡在 OCCT 下载半小时的情况后来直接用了 conda 的预编译包。4.2 定义参数化模板以法兰盘为例法兰盘是 text-to-cad 的经典案例因为它参数多、规律强。我定义一个函数输入外径、内径、厚度、螺栓孔数量和孔径输出 cadquery 对象import cadquery as cq import math def make_flange(outer_dia, inner_dia, thickness, bolt_count, bolt_dia, bolt_circle_dia): # 主体圆盘 flange cq.Workplane(XY).circle(outer_dia / 2).extrude(thickness) # 中心孔 flange flange.faces(Z).workplane().hole(inner_dia) # 螺栓孔 for i in range(bolt_count): angle 2 * math.pi * i / bolt_count x (bolt_circle_dia / 2) * math.cos(angle) y (bolt_circle_dia / 2) * math.sin(angle) flange flange.faces(Z).workplane().center(x, y).hole(bolt_dia) return flange这个函数里bolt_circle_dia是螺栓孔分布圆直径bolt_dia是通孔直径。实际使用时M8 螺栓通孔取 9M10 取 11这些查表值可以做成字典。生成后导出 STEPflange make_flange(100, 50, 10, 6, 9, 80) cq.exporters.export(flange, flange.step)4.3 从几何到 URDF自动计算质量与惯性法兰盘作为机器人底座的一部分时需要 URDF。我先用 trimesh 加载 STL 算质量属性import trimesh mesh trimesh.load(flange.stl) volume mesh.volume # 单位 mm^3 density 7.85e-6 # 钢kg/mm^3 mass volume * density # 惯性张量trimesh 可以直接算 inertia mesh.moment_inertia * density注意单位trimesh 的moment_inertia是基于 mm 的乘以密度后单位是 kg·mm²URDF 要 kg·m²所以还要除以 1e6。这个转换我写成了一个工具函数每次调用避免手算出错。然后生成 URDF 的 XMLfrom lxml import etree robot etree.Element(robot, nameflange_assembly) link etree.SubElement(robot, link, nameflange_link) inertial etree.SubElement(link, inertial) mass_el etree.SubElement(inertial, mass, valuestr(mass)) inertia_el etree.SubElement(inertial, inertia, ixxstr(inertia[0][0]/1e6), ixystr(inertia[0][1]/1e6), ixzstr(inertia[0][2]/1e6), iyystr(inertia[1][1]/1e6), iyzstr(inertia[1][2]/1e6), izzstr(inertia[2][2]/1e6)) # 视觉几何用 mesh visual etree.SubElement(link, visual) geometry etree.SubElement(visual, geometry) etree.SubElement(geometry, mesh, filenameflange.stl) # 碰撞几何用简化圆柱 collision etree.SubElement(link, collision) col_geometry etree.SubElement(collision, geometry) etree.SubElement(col_geometry, cylinder, radiusstr(50), lengthstr(10)) tree etree.ElementTree(robot) tree.write(flange.urdf, pretty_printTrue, xml_declarationTrue)这段代码生成的 URDF 可以直接拖进 CoppeliaSim 或 Gazebo 做仿真。碰撞几何故意用圆柱代替网格仿真速度会快很多。4.4 批量生成与参数表驱动单个零件跑通后批量就是加一层循环。我通常把参数放在 CSV 或 YAML 里- name: flange_a outer_dia: 100 inner_dia: 50 thickness: 10 bolt_count: 6 bolt_dia: 9 bolt_circle_dia: 80 - name: flange_b outer_dia: 120 inner_dia: 60 thickness: 12 bolt_count: 8 bolt_dia: 11 bolt_circle_dia: 100然后写一个主循环读一条生成一个 STEP 和一个 URDF文件名用name字段。这样一百个法兰盘也就是改改 YAML 的事不用碰代码。注意批量生成时一定要加异常捕获。某个参数组合导致布尔运算失败时记录日志继续跑下一个不要整个脚本崩掉。我一般用try...except包住单个零件的生成失败时输出参数和错误信息到error.log。5. 常见问题与排查技巧实录5.1 几何生成失败布尔运算与倒角的坑问题hole()或cut()报错提示BRep_API: command not done。排查思路先检查孔径是否大于板厚或者孔位是否在实体外部。OCCT 对“零厚度”和“自交”很敏感。我遇到过一次孔边距设成 0孔刚好切到板边内核直接拒绝。改成 0.1 毫米边距就过了。倒角失败fillet()在复杂交线处容易失败。我的做法是给倒角半径一个递减重试先试 1.0失败试 0.5再失败试 0.2最后放弃并记录。实际项目中大部分倒角失败都是因为半径大于相邻面的最小尺寸。5.2 URDF 导入仿真环境后模型异常问题URDF 导入 CoppeliaSim 后模型位置偏移或尺寸不对。排查先检查 URDF 里的origin标签。很多生成工具默认不写 origin仿真引擎会按自己的规则解释。我习惯在每个 link 和 joint 里显式写origin xyz0 0 0 rpy0 0 0/避免歧义。尺寸不对多半是单位问题STEP 是毫米URDF 是米mesh 文件如果是毫米单位需要在 URDF 里加scale0.001。惯性矩阵报错仿真引擎会检查惯性矩阵是否正定。如果质量或惯性算错会提示Inertia matrix is not positive definite。用 trimesh 算的时候确保网格是封闭的watertight否则体积和惯性都不准。可以用mesh.is_watertight检查不封闭的话先做mesh.fill_holes()。5.3 格式转换中的精度丢失问题STEP 转 STL 后曲面变成明显的多边形加工时表面质量差。原因STL 是三角网格转换时有弦高偏差chordal deviation参数。默认值可能太大。cadquery 导出 STL 时可以指定cq.exporters.export(flange, flange.stl, tolerance0.01, angularTolerance0.1)tolerance是线性偏差单位毫米0.01 已经比较精细。angularTolerance是角度偏差弧度制。这两个值越小网格越密文件越大。我一般先给 0.05 预览最终交付用 0.01。G-code 相关text-to-cad 不直接生成 G-code但如果你要把 STEP 送到 CAM注意 STEP 里的曲面必须是精确 B-rep不能是网格。有些在线转换工具会把 STEP 转成网格再输出那样 CAM 软件识别不了曲面刀路会很难看。确保你的导出链路是cadquery → STEP中间不经过 STL。5.4 常见问题速查表现象可能原因解决方法布尔运算失败零厚度、自交、孔在实体外加最小边距检查孔位坐标倒角失败半径大于相邻面尺寸递减半径重试或跳过该倒角URDF 模型偏移缺少 origin 标签显式写origin xyz0 0 0 rpy0 0 0/仿真中尺寸放大 1000 倍单位未转换URDF mesh 加scale0.001惯性矩阵不正定网格不封闭mesh.fill_holes()后重新计算STL 曲面粗糙弦高偏差太大导出时设tolerance0.01批量生成中断单个零件异常未捕获加 try-except记录错误继续中文路径读取失败OCCT 路径编码问题全英文路径不含空格5.5 独家避坑技巧我踩过的三个“隐形坑”第一个是浮点数精度。OCCT 内部用双精度但 Python 的float在某些运算后会引入微小误差。比如0.1 0.2 ! 0.3如果这个值用来定位孔可能导致孔位偏移 1e-16 毫米虽然肉眼看不见但布尔运算可能因此失败。我的做法是所有输入参数先round(x, 6)消除浮点噪声。第二个是内存泄漏。cadquery 和 build123d 的 OCCT 对象如果不显式释放批量生成几百个零件后内存会爆。我现在的习惯是每个零件生成后del对象并调用gc.collect()。虽然慢一点但稳定。第三个是版本兼容。cadquery 2.4 和 2.5 的 API 有细微差别build123d 更新更快。我建议在项目里锁定版本用requirements.txt写死比如cadquery2.4.0。否则今天跑通的代码明天升级后可能就报错。6. 扩展方向text-to-cad 还能怎么玩6.1 与参数化设计表结合text-to-cad 最自然的扩展是接上 Excel 或数据库的参数表。工程师在 Excel 里填尺寸脚本自动生成整套模型。我做过一个项目把公司标准件库的 Excel 表直接映射成 cadquery 模板原来一个人一周的建模量脚本跑十分钟。关键是模板要设计得足够通用覆盖 80% 的常用件剩下的 20% 再手动处理。6.2 自动生成装配体与爆炸图单个零件跑通后下一步是装配。cadquery 的Assembly类可以定义零件之间的约束关系然后导出装配体 STEP。爆炸图则是给每个零件一个偏移向量生成多个位置的副本。这个功能在出工程图或说明书时特别有用不用手动拖拽。6.3 与仿真流程打通URDF 生成后可以直接调用仿真引擎的 Python API 做批量仿真。比如生成一百个不同尺寸的机械臂连杆自动跑运动学仿真输出关节力矩曲线。这种“参数扫描自动仿真”的玩法在优化设计里很常见。text-to-cad 负责几何仿真引擎负责物理两者用 URDF 做接口数据流就通了。6.4 自然语言接口的边界最后说下大模型在这件事里的位置。我试过用大模型把“一个带加强筋的支架”转成 cadquery 代码结果生成的代码几何自交根本不能用。大模型擅长的是意图识别和参数提取不擅长精确几何构造。所以我的方案是大模型只负责把自然语言转成结构化参数JSON几何生成仍然走模板和规则。这样既利用了语言模型的灵活性又保证了几何的确定性。提示如果你要接大模型一定要加一层参数校验。比如模型提取出“孔径 -5”校验层直接拒绝并返回错误不要让负值进入几何内核。这个方向后续还可以这样扩展把 text-to-cad 和版本控制系统结合每次参数变更自动生成新版本模型并提交形成“设计即代码”的工作流。我在小团队里试过效果不错但需要团队都接受用文本描述需求而不是口头说“那个板改厚一点”。习惯的转变比技术本身更难。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进