把验收过的 CAD 零件装上机械臂仿真:质量核对、可移植的 MJCF 与 OpenUSD 导出

pai-design-workbench 只把已验收 CAD 评审里摘要一致的 STL 装上 MuJoCo 机械臂:网格质量须与 B-Rep 相差 3% 以内,再导出 MJCF 与 OpenUSD;Newton 核对曾查出两个导出错误。

对比图:标题“同一个零件的三种几何表示”下,三个不带编号的实心圆并排,每个圆下有一条横线和一段文字,从左到右依次为:B-Rep 实体,质量基准,参照支架 48.37 g;精确网格体积,算质量与惯量,与 B-Rep 差 ≤ 3 %;凸包,只作碰撞形状,默认凸包惯量把 31.85 g 算成 110 g。三列之间没有箭头。底部是来源说明。
同一个零件在 pai-design-workbench 机器人仿真中的三种几何表示及各自用途,三列并列,顺序不代表先后或优劣。依据 commit 70330e2 的 docs/PHYSICS.md 与 native/robot_sim.py 绘制。

一个支架在 CAD 评审里通过了几何检查和有限元检查,接下来要问的是:把它装到机械臂末端,工作单元还能不能按节拍、不碰撞地完成取放?pai-design-workbench 的物理通道用 MuJoCo 回答这个问题,但它先要确认装上去的就是那个被验收的零件,质量也算对了,再把仿真场景导出成别处能重新打开的 MJCF 和 OpenUSD。本文依据固定在 commit 70330e2 的 docs/PHYSICS.md、native/robot_sim.py 和一份保存的演示记录 docs/evidence/physics-demo.json,讲这条从 CAD 到机器人仿真的链路每一步核对什么、核对不了什么。

文中的数字都来自项目自己的文档、代码和记录([O],项目自报),本站没有运行这些测试,也没有复现演示。同一个仓库的气动通道见 把固定版本的 OpenFOAM 算例搬上 AWS Batch:哈希回传、两套网格与本地阻力对照;本文不讨论支架的寻优过程,只讲零件进入工作单元之后的事。

工作单元仿真测什么

robot_sim.py 不接受用户提供的 XML,只接受八个有界参数,再自己生成 MJCF 模型。超出范围就退出:

参数允许范围
pickDistance、placeDistance0.25–1.1
pickHeight、placeHeight0.6–1.2
pedestalHeight0.3–1.0
guardClearance0.1–1.5
speedFraction0.1–1.0
jitter0.0–0.08

上述参数范围按源码原样列出,范围定义没有单位注释。文档明确使用米表示护栏间距;speedFraction 表示额定关节速度的比例。

场景里是一台装在底座上的通用六轴臂,连杆长度按 UR5e 公开规格书的尺寸取(UR5e 级),加一个输送线取料位、一个工装放料位和四块围栏。文档写的标准做法是跑 10 个固定种子,每个种子给取料位姿加一个按种子生成的偏移。每个种子依次做三件事:在 MuJoCo 雅可比上用阻尼最小二乘求逆运动学,并限制在关节限位内;按额定关节速度的给定比例生成关节空间五次多项式轨迹,经过接近、抓取、抬起、转移、放置、退回;用位置执行器驱动,以 500 Hz 做完整刚体动力学仿真,同时检测机械臂连杆与静态场景的接触(夹爪与被抓取工件的预期接触不计)。四项检查是:

  • reach:每个种子在取料和放料处 TCP 误差 ≤ 2 mm。文档以取料和放料处的误差表述这项检查。代码在关节限位内用阻尼最小二乘为每个目标最多迭代 400 次,取六个逆运动学目标(含取放点上方 0.12 m 的接近/抬起点)中的最大 TCP 误差,超过 2 mm 即判失败。失败可能是因为目标在工作空间之外,这对应课程 物理AI实验室 1.1:坐标系与运动学,手算一条两连杆手臂怎样够到杯子 里的“可能无解”;也可能是解存在但迭代没有收敛。这项检查不区分这两种原因。
  • collision-free:运动中机械臂连杆不碰围栏、输送线支架或工装。
  • cycle-time:最慢种子的节拍(含停稳时间)≤ maxCycleSeconds。
  • success-rate:同时到位、无碰撞、满足节拍的种子占比 ≥ minSuccessRate。

参照单元和候选单元跑同样的种子,界面按种子成对列出结果,参照成功而候选失败的种子会被标出。这和 Robot Reel 的配对比较是同一种做法。PHYSICS.md 记录了把节拍目标提高到 5 秒后 npm run test:robot 的结果:

单元结果
参照,50 % 速度5.90 s,不满足新目标
75 % 速度,围栏收近到 0.12 m4.60 s,但每个种子肘部都撞围栏:拒绝
同样速度,围栏 0.30 m4.60 s,10/10 个种子成功:接受(比参照快 22 %)

保存的演示记录 physics-demo.json 是另一次运行,录制于项目的托管站点,不是上表的 npm run test:robot 结果;它的数值四舍五入后与上表一致:拒绝的单元节拍 4.598 s,10 个种子中无碰撞的为 0(0/10);参照 5.897 s;修正只改了一项 guardClearance 0.12 → 0.3,requirementsRelaxed 为 false,即没有放宽要求。(5.897 − 4.598) / 5.897 ≈ 0.22,与记录的 22 % 一致。记录里给出这次修正的引擎和模型名,但 modelEvidence 字段是 acp_advertised,即引擎自己声明的身份,不是独立核实的模型来源。

把零件装上夹爪:只接受验收过的那一份

工作单元请求可以带上 tool: { cadReviewId, payloadKg }。按 PHYSICS.md,服务器只接受同一项目中已完成且结论为接受的 CAD 评审,并用该评审记录的摘要核对 STL;未被接受的零件返回 TOOL_NOT_ACCEPTED。robot_sim.py 自己也再算一次 STL 的 SHA-256,与输入规格中的摘要(代码报错称之为 CAD 评审记录)不同就退出,并检查载荷 0–3 kg、电机轴位置 5–120 mm、B-Rep 质量 1–2000 g。

通过之后,精确的 STL 作为网格体装到夹爪上,另在电机轴位置加上声明的载荷(默认是 0.28 kg 的 NEMA 17 电机,代码里是一个指定质量的方块)。参照单元和候选单元装同一个工具,所以比较的是单元,而不是工具。零件的碰撞形状计入 collision-free:代码把 cad-tool 归入机械臂一侧,它碰到围栏也算碰撞。

同一个零件的三种表示

这一节是这条链路最值得借鉴的地方:同一个零件在仿真里有三种几何表示,用途不同,不能混用。

表示用于记录的数字
B-Rep 实体(CAD 评审)质量基准参照支架 48.37 g
精确网格体积(inertia="exact",2.70 g/cm³,非凸)MuJoCo 中的质量和惯量参照支架 48.37 g;与 B-Rep 相差超过 3 % 即失败
凸包只作碰撞形状默认的凸包惯量曾把一个 31.85 g 的零件算成 110 g

文档说,最后一行就是 3 % 检查存在的原因:110 g 约为 31.85 g 的 3.5 倍。文档把这次偏差归于默认的凸包惯量设置,这正是 3 % 检查要拦下的情况,但没有进一步说明凸包为何会把质量放大到约 3.5 倍;本文因此强调质量回读与阈值核对。代码里的核对方式是:取 cad-tool 刚体的总质量减去载荷,得到网格质量,再与输入规格中的 B-Rep 质量比较,超过 3 % 就以“tool mass mismatch”退出。

导出:MJCF 与 OpenUSD 各自核对了什么

MJCF。 scene.xml 引用放在同一目录下的 tool.stl,不含本机路径,所以整个目录拷到别处可以重新加载。test:robot 会检查这一点。

OpenUSD。 scene.usda 为 Z 轴向上、单位米;每个 MuJoCo 刚体对应一个带 MassAPI(质量、质心、对角惯量、主轴)的刚体,网格碰撞体按凸包;六个带限位的转动关节,焊接的刚体(例如夹爪上的零件)用固定关节,外加一个 articulation root。重新打开后运行 OpenUSD 26.8 注册的全部 UsdValidation 校验器,包括 UsdPhysics 的刚体、关节、articulation 和碰撞体检查,任何错误或警告都使运行失败;非固定关节不是六个也失败。

校验通过不等于是同一台机器人。 可选的 Newton 交叉核对(Newton 1.6,独立 venv,CPU Warp,不需要 GPU)把 scene.usda 导入 Newton,把 scene.xml 载入 MuJoCo,要求只有一个 articulation、6 个转动关节、没有孤立关节、刚体质量相同,并在导出位姿和 32 个限位内的种子构型上比较正运动学:每个刚体位置差 ≤ 0.1 mm、姿态差 ≤ 0.01°。文档记录,第一次运行就发现了两个所有 UsdValidation 校验器都放过的导出错误:

  1. articulation root 放在 base 上,而 base 不是平铺刚体的祖先,Newton 因此把所有关节当成孤立关节拒绝。现在 root 放在 /Workcell。
  2. 关节坐标系取自取料位姿,导致 USD 中关节角 0 和限位的含义与 MuJoCo 不同。现在取自模型常量(关节角为 0 时相对父刚体的偏移)。

修正后:33 个构型,最大位置误差 0.45 µm,姿态误差 3.1·10⁻⁵ °,质量差在 0.5 mg 以内,耗时 4.8 s。这个结果单独记录为 newton.json,属于导出一致性,不改变评审结论;它只覆盖运动学和质量,不说明两个引擎的动力学是否一致。

在本地复现

按 PHYSICS.md,在 commit 70330e2 的仓库根目录执行:

npm run setup:physics                    # Gmsh、Optuna、scikit-learn、MuJoCo 按哈希锁定安装;CalculiX 来自签名的 Ubuntu 软件源
npm run setup:physics -- --with-newton   # 可选:Newton 1.6,独立 venv
npm run test:robot

按文档,setup:physics 安装的是哈希锁定的固定版本(MuJoCo 3.14.0 等),安装回执 .state/tools/physics-install-receipt.json 记录每个 .deb 的摘要和锁文件哈希。文档说 test:robot 会装上参照支架(B-Rep 48.37 g,MuJoCo 网格 48.37 g),检查 MJCF 可移植、USD 带物理关节和 articulation root,以及未接受的零件被拒绝。所给材料没有写克隆仓库和安装 npm 依赖的步骤;工具链表也没有列出 OpenUSD 本身如何安装;文档没有说明 test:robot 是否包含 Newton 核对。

每次运行写出 robot.json(逐种子结果、四项检查、工具质量对照)、scene.xml、scene.usda 和 robot.glb;robot.json 中固定写着 "scope": "rigid-body-simulation" 和 "physicalValidation": false。

演示记录与海报

本文封面是项目的物理演示海报。演示记录写明:录屏原长 659 秒,没有剪切或重排,画面静止的等待在 2.5 秒后以 6 倍速播放;章节依次为需求、MuJoCo、AI 修正单元、单元反馈、有限元、AI 寻优和发布。记录中的反馈两项都已关闭(场景的 collision-free 和零件的 max-deflection),零件正式评审结论为接受(46.87 g,9/9 项检查),发布 R1 准入 5/5。记录没有显示演示的工作单元中是否装有任何 CAD 零件,本文不作推断;被接受的 46.87 g 零件来自 MuJoCo 和 AI 修正单元之后的有限元与 AI 寻优两章。这是在项目托管站点上录制的一次演示,不是独立实验,也不是真机测试。

局限 / 注意

  • 范围是通用机械臂与通用控制器的刚体仿真。被抓取的工件在夹爪闭合时按运动学方式附着,没有抓取物理模型;CAD 支架则在整个运行中作为网格刚体固定于夹爪。它不是厂商控制器,不是安全评估,也不是抓取物理模型;连杆长度是 UR5e 级,不代表任何具体产品的性能。
  • 零件质量按 2.70 g/cm³ 的名义密度计算;有限元一侧是线性静力、小变形、名义材料,不含接触、螺栓预紧、疲劳、热效应、公差或试验数据。结果是仿真证据,不是认证。
  • 关于凸包近似的一般几何性质(本站解释):凸包会包住凹槽,因而可能把实际不会碰的情况判为碰撞;这不是项目保存记录对具体碰撞误报的验证。
  • Newton 核对只证明导出的运动学和质量与 MuJoCo 一致,不证明 Isaac Lab 或 Newton 中的动力学行为相同。
  • 22 %、0.45 µm 等数字是项目在自己环境中的一次运行记录,本站没有重新执行。

链接