Initial commit

This commit is contained in:
2026-11-02 17:10:39 +08:00
commit 102fdbaf47
79 changed files with 56509 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
"""Explicit name-based mapping between mjlab and canonical S10 model tensors."""
import torch
from s10_supermimic.robot.config import (
LEG_JOINT_NAMES, WHEEL_BODY_NAMES, WHEEL_GEOM_NAMES, WHEEL_JOINT_NAMES,
)
from .world_model import STATE_DIM, ACTION_DIM
class S10Bridge:
def __init__(self, env):
self.env = env
self.robot = env.scene["robot"]
self.legs, _ = self.robot.find_joints(LEG_JOINT_NAMES, preserve_order=True)
self.wheels, _ = self.robot.find_joints(WHEEL_JOINT_NAMES, preserve_order=True)
if len(self.legs) != 12 or len(self.wheels) != 4:
raise ValueError("S10 requires 12 leg joints and 4 wheel joints")
pos = env.action_manager.get_term("joint_pos")
vel = env.action_manager.get_term("wheel_vel")
if list(env.cfg.actions) != ["joint_pos", "wheel_vel"]:
raise ValueError("Expected joint_pos then wheel_vel action terms")
names = list(pos.target_names) + list(vel.target_names)
canonical = LEG_JOINT_NAMES + WHEEL_JOINT_NAMES
if len(names) != ACTION_DIM or set(names) != set(canonical):
raise ValueError("Unexpected S10 action targets")
self.to_canonical = [names.index(n) for n in canonical]
self.to_env = [canonical.index(n) for n in names]
geoms, _ = self.robot.find_geoms(WHEEL_GEOM_NAMES, preserve_order=True)
self.geoms = self.robot.indexing.geom_ids[geoms]
body, _ = self.robot.find_bodies("base_link")
self.base = self.robot.indexing.body_ids[body[0]]
# XML reference, not an already-randomized simulator value after reset.
from s10_supermimic.robot.config import get_spec
self.nominal_com = torch.as_tensor(get_spec().body("base_link").ipos.copy(),
device=env.device, dtype=torch.float32)
def canonical_action(self, action):
return action[:, self.to_canonical]
def env_action(self, action):
return action[:, self.to_env]
def state(self):
d = self.robot.data
s = torch.cat((d.root_link_pos_w[:, 2:3], d.projected_gravity_b,
d.root_link_lin_vel_b, d.root_link_ang_vel_b,
(d.joint_pos - d.default_joint_pos)[:, self.legs],
d.joint_vel[:, self.legs], d.joint_vel[:, self.wheels]), -1)
assert s.shape[-1] == STATE_DIM
return s
def pose(self):
d = self.robot.data
return torch.cat((d.root_link_pos_w[:, :2], d.heading_w[:, None]), -1)
def context(self):
model = self.env.sim.model
friction = model.geom_friction
com = model.body_ipos
mu = friction[:, self.geoms, 0] if friction.ndim == 3 else friction[self.geoms, 0].expand(self.env.num_envs, -1)
offset = com[:, self.base] if com.ndim == 3 else com[self.base].expand(self.env.num_envs, -1)
return torch.cat((mu, offset - self.nominal_com), -1)
def contacts(self):
"""Endpoint targets: contact flags4, net wheel forces12 in base frame (N).
Explicit names prevent silent FL/FR swaps in sensor expansion order.
Terminal/reset endpoints are excluded by replay validity in the learner.
"""
from mjlab.utils.lab_api.math import quat_apply_inverse
sensor = self.env.scene["wheel_ground_contact"]
names = [name.split('/')[-1] for name in sensor.primary_names]
order = [names.index(name) for name in WHEEL_BODY_NAMES]
found = (sensor.data.found[:,order] > 0).float().reshape(-1,4)
force = sensor.data.force[:,order].reshape(-1,4,3)
quat = self.robot.data.root_link_quat_w[:,None,:].expand(-1,4,-1)
force = quat_apply_inverse(quat.reshape(-1,4),force.reshape(-1,3)).reshape(-1,12)
return torch.cat((found,force),-1)
def pose_delta(before, after):
dxy = after[:, :2] - before[:, :2]
c, s = before[:, 2].cos(), before[:, 2].sin()
yaw = after[:, 2] - before[:, 2]
return torch.stack((c * dxy[:, 0] + s * dxy[:, 1],
-s * dxy[:, 0] + c * dxy[:, 1],
torch.atan2(yaw.sin(), yaw.cos())), -1)