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:
@@ -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.13,Isaac 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 fps,60 秒),
|
||||||
|
当前 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 实验脚本不纳入本次提交。
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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')
|
||||||
@@ -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)
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user