fix(assets): stabilize L20 fixed-base PhysX preview

原因:保存 L20 资产、固定基座、地面光照、相邻碰撞过滤和拇指受限驱动,
修正过短时间轴;USD 依赖通过 Git LFS 管理,转换元数据移除个人路径。
包含 GUI 有限步数拇指回归入口与验证记录。

验证:用户 Isaac Sim 6.0.1 GUI 中等价回归代码完成 3840 物理步,PASS。
提交前 pxr Stage.Open/ComputeAllDependencies:10 层、0 未解析依赖、0 组合错误。
Python AST 语法、git diff --cached --check、LFS 指针哈希与 git lfs fsck 均 PASS。
pre-commit run --all-files:BLOCKED,命令未安装;仓库许可证模板也缺失。
Isaac Lab 任务发现/训练 E2E NOT_RUN:当前资产尚未接入任务,不启动额外仿真。

兼容性:包版本维持 0.1.0,现有 Cartpole 配置未改变;需要 Git LFS。
armature 为未标定数值参数;mimic 耦合、其他手指大动作、reset/replay 和
长时间 RTX 渲染资源耗尽仍未验收。本提交不表示训练或发布就绪。
This commit is contained in:
2026-09-10 16:44:45 +08:00
parent 35621dde47
commit 071a1bdff5
14 changed files with 208 additions and 1 deletions
+1
View File
@@ -15,3 +15,4 @@ source/isaaclab_tasks/test/golden_images/**/*.png filter=lfs diff=lfs merge=lfs
*.bat text eol=crlf *.bat text eol=crlf
*.sh text eol=lf *.sh text eol=lf
assets/robots/dex_hand/linkerhand_g20_left/linkerhand_g20_left/payloads/geometries.usd filter=lfs diff=lfs merge=lfs -text
@@ -0,0 +1,102 @@
# L20 LEFT — 固定基座 PhysX 预览
用户确认该 G20 left 命名对应当前 L20;保留导入器生成的名称和目录结构。
## 入口与来源
- USD 入口:`linkerhand_g20_left/linkerhand_g20_left.usda`(USDA 是 USD 的文本格式)。
- `linkerhand_g20_left/payloads/` 为完整依赖,需随入口一起移动。
- 来源:用户提供的 `Linkerbot_URDF/L20/LEFT/linkerhand_g20_left.urdf` 及其 `meshes/`
- 源仓库提交:`afc0ac84a4e7cee4245ba421fb985bab014768a0`;转换时源仓库工作区干净。
- 许可:未在源仓库根目录及其下一层找到许可证,分发授权待确认。
- 同目录 `g20_left_G20.json` 不属于该转换脚本的输入,未导入为控制接口。
## 转换
从项目根目录执行,将 SOURCE_DIR 替换为本地源目录:
```bash
SOURCE_DIR="/path/to/Linkerbot_URDF/L20/LEFT"
timeout 300s ~/isaacsim/python.sh ~/IsaacLab/scripts/tools/convert_urdf.py \
"$SOURCE_DIR/linkerhand_g20_left.urdf" \
assets/robots/dex_hand/linkerhand_g20_left --headless
```
实际使用 Python 3.12.13Isaac Lab 提交
`6a7acb0320a0bdc15b13e44e83b575e00797faf4`
转换缓存 `.asset_hash` 和包含本机路径的 `config.yaml` 不纳入版本。
以下记录原始转换参数;当前预览覆盖配置见后文。
- 浮动基座(未传 `--fix-base`),不合并固定关节。
- 位置驱动,stiffness=100、damping=1,均为工具默认值,非实测标定。
- Convex Hull 碰撞,self_collision=False。
- 默认物理 variant 为 `physx``mujoco` 层由转换器自动生成,不代表已经开展 MuJoCo 验证。
- USD 单位:米、千克,Z-up。
## 验证与限制
- **PASS**:上述转换命令退出码 0;原始 URDF 的 22 个网格文件引用均存在。
- **PASS**:使用 `~/isaacsim/python.sh``pxr.Usd.Stage.Open`
`UsdUtils.ComputeAllDependencies` 检查当前入口,10 个 USD 层、0 个未解析依赖、
0 个组合错误;默认 prim 有效,默认 physx variant 中有 22 个刚体、21 个关节。
- **风险**:源 URDF 有 5 个 mimic 约束,但生成的物理层未找到 mimic/coupling/gearing 定义。
不得声称耦合已正确保留,也不得把 21 个关节当成 21 个独立驱动。
- **NOT_RUN**:完整耦合行为、reset/replay、任务发现及训练 E2E。
未接入任务;当前仅验证后文限定的拇指动作,不作为训练就绪资产。
- 转换日志位于本机 `/tmp/dex_l20_convert.log`,含 OmniHub 启动失败重试及扩展警告;
未阻止本地转换和上述静态 USD 检查,不代表运行环境无问题。
- `payloads/geometries.usd` 为 6,384,624 字节,超过普通 Git 的 2000 KB 门槛;
经用户确认 Gitea 支持,使用 Git LFS 管理;克隆后需执行 `git lfs pull`
- 当前 Cartpole 任务、包版本和训练配置未改变;源 URDF 未修改。
## 固定基座预览场景(2026-09-10
当前入口接入同目录 `fixed_base_setup.usda`:世界固定关节、4 m × 4 m
静态碰撞地面(顶面 z=-0.02 m)、DomeLight 和 DistantLight。
固定关节承载唯一的 PhysicsArticulationRootAPI;手掌处移除原来的
PhysicsArticulationRootAPI 和隐式继承该 API 的 NewtonArticulationRootAPI。
本入口现在用于固定基座 PhysX 预览,其他物理 variant 未回归。
PhysX 使用 240 Hz、关节系统位置/速度迭代 16/4、自碰撞关闭。
60 Hz 下实际 Play 出现关节 NaN;仅增加迭代次数仍失败。
当前配置与有效验证见下文。曾报告的两次“10 秒通过”发生于过短循环时间轴,
只能代表累计回调,不能作为连续运行或 Stop→Play 稳定性证据。
停止状态下 MCP `step_simulation(120)` 仅刷新界面,也不计为物理验证。
## 拇指驱动与有效验证
以本节为当前配置:保留 240 Hz、16/4 迭代和 21 对相邻连杆显式碰撞过滤。
五个 `thumb_*` 关节使用 force 类型位置驱动,USD stiffness=0.017453292、
damping=0.00034906585、maxForce=1 N·m、armature=0.0001 kg·m²;
PhysX 张量读回的增益为 1 N·m/rad 和 0.02 N·m·s/rad。
armature 是附加到关节自由度的数值稳定参数,会改变有效惯量,尚未经硬件标定。
其他手指驱动未调整,不能将本次结果推广到其他关节的大幅动作。
入口移除误保存的 `/Render` 编辑器节点,时间范围设为 0–1440 帧(24 fps60 秒),
当前 GUI 时间轴关闭循环。旧播放区间约为 0.042 秒且开启循环,故前文两次
“10 秒通过”只代表累计物理回调,撤回其连续运行与重启稳定性的验收结论。
实际在用户 Isaac Sim 6.0.1 窗口执行 `scripts/assets/check_thumb_drive_editor.py`
等价代码:0→30→60→80→0→−20→100→0 度,每段 480 个实际物理步,
共 3840 步。结果 PASS:无 NaN/Inf 或连杆逃逸,最大关节速度 1.000008 rad/s
30/60/80 度指令的末端读数分别为 28.669/59.048/79.459 度;
−20/100 度指令被物理限位约束在约 0/80.214 度,再次回零约 0.000134 度。
这验证了本序列的数值稳定与 3 度内跟随,不代表高精度控制或真实力矩验收。
本地结果保存在 `/tmp/l20_thumb_drive_result.json`(不提交)。
先前崩溃日志出现 RTX `Out of resource descriptors!`,明确属于渲染资源耗尽。
移除持久化编辑器渲染节点后本轮未复现,但未做长时间渲染泄漏验证,
不能宣称所有编辑器卡死问题已解决。未执行 Isaac Lab 任务或训练 E2E。
## 转换时的原始哈希(非当前修改后文件)
| 文件 | SHA-256 |
| --- | --- |
| 源 URDF | `b8ef22e436ab311fb61f87091b72ae717821092471d628d0ea9475b5088daa5d` |
| `linkerhand_g20_left/linkerhand_g20_left.usda` | `83571a19a9ac6be575ced1ffff46708f752ad6462fde88c6a15435ac1ee431d5` |
| `linkerhand_g20_left/payloads/geometries.usd` | `6ba619abc15fd30b10c05cd494ec8b4839647004f43a17376e105f4654f1bfcd` |
回归入口 `scripts/assets/check_thumb_drive_editor.py` 在已打开的 Isaac Sim Script Editor 中运行,
要求干净初始场景、至少 16 秒的播放范围并关闭循环。完成后暂停,结果位于
`builtins._l20_drive_result`;脚本会改变 target,测试运行态不要保存为资产初始状态。
未验证的早期 smoke 实验脚本不纳入本次提交。
@@ -0,0 +1,61 @@
"""Run inside Isaac Sim to test L20 thumb slider jumps; pauses on completion.
Results: builtins._l20_drive_result. This changes the slider target temporarily.
Requires a clean initial scene and a nonlooping timeline long enough for 16 seconds.
"""
import asyncio, builtins
async def drive_test():
import asyncio, time, builtins, numpy as np, omni.usd, omni.timeline, omni.kit.app, omni.physx
from isaacsim.core.simulation_manager import SimulationManager
from pxr import Usd
s = omni.usd.get_context().get_stage()
tl = omni.timeline.get_timeline_interface()
joint = s.GetPrimAtPath('/tn__linkerhand_g20_lefturdf_cZ0/Physics/thumb_cmc_roll')
target = joint.GetAttribute('drive:angular:physics:targetPosition')
r = {'status': 'RUNNING', 'steps': 0, 'segments': [], 'max_speed': 0.0}
builtins._l20_drive_result = r
def step(dt):
r['steps'] += 1
sub = omni.physx.get_physx_interface().subscribe_physics_step_events(step)
def arr(x):
return x.numpy() if hasattr(x, 'numpy') else np.asarray(x)
try:
tl.play()
a = None
deadline = time.monotonic() + 60
for goal in [0, 30, 60, 80, 0, -20, 100, 0]:
with Usd.EditContext(s, s.GetRootLayer()):
target.Set(float(goal))
start = r['steps']
while r['steps'] - start < 480:
await asyncio.wait_for(omni.kit.app.get_app().next_update_async(), 10)
assert time.monotonic() < deadline, 'Wall timeout'
if r['steps'] < 4:
continue
if a is None:
a = SimulationManager.get_physics_simulation_view().create_articulation_view('/tn__linkerhand_g20_lefturdf_cZ0/Physics/fixed_base_joint')
names = list(a.shared_metatype.dof_names)
idx = names.index('thumb_cmc_roll')
q = arr(a.get_dof_positions())
v = arr(a.get_dof_velocities())
links = arr(a.get_link_transforms())
assert all((np.isfinite(x).all() for x in [q, v, links])), 'Nonfinite state'
r['max_speed'] = max(r['max_speed'], float(np.abs(v).max()))
assert np.abs(v).max() < 10, 'Excessive speed'
assert np.abs(links[..., :3]).max() < 0.5, 'Escaped link'
assert q[0, idx] > -0.05 and q[0, idx] < 1.45, 'Roll limit violation'
actual = float(np.rad2deg(q[0, idx]))
r['segments'].append({'target_deg': goal, 'actual_deg': actual})
assert abs(actual - min(80.214, max(0, goal))) < 3, 'Tracking error ' + str(actual)
r['status'] = 'PASS'
except Exception as e:
r['status'] = 'FAIL'
r['error'] = str(e)
finally:
tl.pause()
sub = None
builtins._l20_drive_task = asyncio.ensure_future(drive_test())
print('Started bounded thumb target-jump test')
+13
View File
@@ -1,6 +1,19 @@
Changelog Changelog
--------- ---------
Unreleased
~~~~~~~~~~
Fixed
^^^^^
* Configure the L20 PhysX preview with a world-fixed articulation root, ground,
lighting, and 240 Hz physics to prevent the observed 60 Hz instability.
Add a bounded editor smoke script; Isaac Lab task integration is not included.
* Stabilize L20 thumb target reversals with bounded position drives and numerical
armature, explicitly filter adjacent links, and correct the preview timeline.
Remove persisted editor render nodes; long-duration RTX stability remains unverified.
0.1.0 (2026-09-10) 0.1.0 (2026-09-10)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~