mp_yam_code / scripts /yam_grasp.py
yqi19's picture
YAM bimanual task suite: env, solvers, tasks, converters
7399b6f verified
Raw
History Blame Contribute Delete
9.4 kB
"""YAM bimanual: right arm grasps the apple (absolute diff-IK, no cuRobo needed). Renders video."""
import argparse, sys, os
from isaaclab.app import AppLauncher
parser = argparse.ArgumentParser()
parser.add_argument("--obj", default="apple")
parser.add_argument("--video", default="outputs/yam_apple_grasp.mp4")
AppLauncher.add_app_launcher_args(parser)
args = parser.parse_args(); args.headless=True; args.enable_cameras=True
app = AppLauncher(args).app
import numpy as np, torch, gymnasium as gym
import imageio.v2 as imageio
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO, "source"))
import bimanual.tasks.manager_based.yam # noqa
from isaaclab_tasks.utils import parse_env_cfg
TASK="Template-YAM-Play-v0"; dev="cuda:0"
_cfg=parse_env_cfg(TASK, device=dev, num_envs=1)
try:
_cfg.viewer.eye=(0.9,-0.9,1.15); _cfg.viewer.lookat=(0.05,0.0,0.5); _cfg.viewer.resolution=(720,540)
except Exception as _e: print("viewer cfg:",_e)
env=gym.make(TASK, cfg=_cfg, render_mode="rgb_array"); u=env.unwrapped
obs,_=env.reset()
def Rq(q):
w,x,y,z=q; return np.array([[1-2*(y*y+z*z),2*(x*y-z*w),2*(x*z+y*w)],[2*(x*y+z*w),1-2*(x*x+z*z),2*(y*z-x*w)],[2*(x*z-y*w),2*(y*z+x*w),1-2*(x*x+y*y)]])
def qR(m):
t=m[0,0]+m[1,1]+m[2,2]
if t>0: s=np.sqrt(t+1)*2; w=.25*s; x=(m[2,1]-m[1,2])/s; y=(m[0,2]-m[2,0])/s; z=(m[1,0]-m[0,1])/s
elif m[0,0]>m[1,1] and m[0,0]>m[2,2]: s=np.sqrt(1+m[0,0]-m[1,1]-m[2,2])*2; w=(m[2,1]-m[1,2])/s; x=.25*s; y=(m[0,1]+m[1,0])/s; z=(m[0,2]+m[2,0])/s
elif m[1,1]>m[2,2]: s=np.sqrt(1+m[1,1]-m[0,0]-m[2,2])*2; w=(m[0,2]-m[2,0])/s; x=(m[0,1]+m[1,0])/s; y=.25*s; z=(m[1,2]+m[2,1])/s
else: s=np.sqrt(1+m[2,2]-m[0,0]-m[1,1])*2; w=(m[1,0]-m[0,1])/s; x=(m[0,2]+m[2,0])/s; y=(m[1,2]+m[2,1])/s; z=.25*s
q=np.array([w,x,y,z]); q/=np.linalg.norm(q)+1e-9; return q if q[0]>=0 else -q
origin=u.scene.env_origins[0].cpu().numpy()
R=u.scene["right_robot"]; Rbn=list(R.data.body_names)
rroot=R.data.root_pos_w[0].cpu().numpy()-origin; rrootq=R.data.root_quat_w[0].cpu().numpy()
L=u.scene["left_robot"]; Lbn=list(L.data.body_names)
lroot=L.data.root_pos_w[0].cpu().numpy()-origin; lrootq=L.data.root_quat_w[0].cpu().numpy()
OFF=np.array([0,0,0.13])
def eef_root(art, bn, root, rootq):
i=bn.index("link_6"); p=art.data.body_pos_w[0,i].cpu().numpy()-origin; q=art.data.body_quat_w[0,i].cpu().numpy()
eef_w=p+Rq(q)@OFF; return Rq(rootq).T@(eef_w-root), q
# arm home EEF poses (root frame)
lp0, lq0 = eef_root(L, Lbn, lroot, lrootq)
rp_home, rq_home = eef_root(R, Rbn, rroot, rrootq)
# Let the (placeholder-floating) objects settle onto the table, arms holding home.
def _settle_act():
import numpy as _np
return torch.tensor(_np.concatenate([lp0,lq0,[1.0], rp_home,rq_home,[1.0]]),dtype=torch.float32,device=dev).view(1,-1)
z_before=float(u.scene.rigid_objects[args.obj].data.root_pos_w[0,2].item())
for _ in range(60):
env.step(_settle_act())
z_after=float(u.scene.rigid_objects[args.obj].data.root_pos_w[0,2].item())
print(f"[g] settle: {args.obj} z {z_before:.3f} -> {z_after:.3f}", flush=True)
def fsep():
jn=list(R.data.joint_names); i=jn.index("left_finger"); j=jn.index("right_finger")
return (float(R.data.joint_pos[0,i].item())+float(R.data.joint_pos[0,j].item()))/2
try:
import omni.usd; from pxr import UsdGeom, Usd
stage=omni.usd.get_context().get_stage()
pp=u.scene.rigid_objects[args.obj].root_physx_view.prim_paths[0]
bb=UsdGeom.BBoxCache(Usd.TimeCode.Default(),[UsdGeom.Tokens.default_,UsdGeom.Tokens.render])
rng=bb.ComputeWorldBound(stage.GetPrimAtPath(pp)).ComputeAlignedRange()
import numpy as _np; ext=_np.array(rng.GetMax())-_np.array(rng.GetMin())
print(f"[g] {args.obj} size(x,y,z)={_np.round(ext,3)}", flush=True)
except Exception as e:
print("[g] size err:", e, flush=True)
apple_w=u.scene.rigid_objects[args.obj].data.root_pos_w[0].cpu().numpy()-origin
apple_root=Rq(rrootq).T@(apple_w-rroot)
print(f"[g] right_root={np.round(rroot,3)} apple_world={np.round(apple_w,3)} apple_root={np.round(apple_root,3)}", flush=True)
# top-down grasp quat (root=identity): z(approach)=world -z, y(jaw)=+x
Rg=np.stack([np.array([0.,1.,0.]), np.array([1.,0.,0.]), np.array([0.,0.,-1.])],axis=1)
gq=qR(Rg)
frames=[]
rec=[]; _phase={'v':'start'}
import json as _json
def act(rp,rq,rg):
return torch.tensor(np.concatenate([lp0,lq0,[1.0], rp,rq,[rg]]),dtype=torch.float32,device=dev).view(1,-1)
def r_eef():
p,_=eef_root(R,Rbn,rroot,rrootq); return p
def _record(rg):
q=R.data.joint_pos[0].cpu().numpy()
e,_=eef_root(R,Rbn,rroot,rrootq)
rec.append({"phase":_phase["v"],"frame":len(frames),
"joints":[float(x) for x in q[:6]],
"gripper":float((q[6]+q[7])/2),"grip_cmd":("close" if rg<0 else "open"),
"eef":[float(x) for x in e],"apple_z":appz()})
def go(rp,rg,n,render=True):
for k in range(n):
obs,_,_,_,_=env.step(act(rp.astype(np.float32),gq.astype(np.float32),rg))
if render and k % 6 == 0:
img=env.render()
if img is not None: frames.append(np.asarray(img)[...,:3])
if k % 3 == 0: _record(rg)
def appz(): return float(u.scene.rigid_objects[args.obj].data.root_pos_w[0,2].item())
z0=appz()
OPEN,CLOSE=1.0,-1.0
# ---- LEVEL approach waypoints (right EEF, root frame; top-down grip orientation) ----
# No raise phase: the arm starts at its raised home (t=0). It moves to the object's
# grasp height and comes in FORWARD (horizontal, constant z) — no up/down diving.
grasp = apple_root + np.array([0, 0, -0.02], np.float32) # at the object
pre = grasp + np.array([-0.18, 0.0, 0.0], np.float32) # same HEIGHT, backed off behind it (env -x)
lift = apple_root + np.array([0, 0, 0.22], np.float32) # pick up at the end
# ---- WP0: recover from HOME to the user-chosen READY pose ----
# joints [0,90,83,-82,0,0]deg -> TCP world [0.075,-0.200,0.609]; in root frame = TCP_world - root.
# its link_6 orientation equals the top-down grasp quat gq, so it is a pure position waypoint.
ready = (apple_w*0 + np.array([0.075,-0.200,0.609],np.float32)) - rroot # TCP(world) -> root frame
ready = Rq(rrootq).T@ready
_phase["v"]="WP0_recover_ready"; print(f"[g] WP0 recover HOME->READY TCP_root={np.round(ready,3)} (world [0.075,-0.200,0.609])", flush=True); go(ready,OPEN,90)
print(f"[g] WP0 reached eef_root={np.round(r_eef(),3)} err={np.linalg.norm(r_eef()-ready):.3f}", flush=True)
_phase["v"]="WP1_to_height"; print(f"[g] WP1 move to object height (level, backed off) {np.round(pre,3)}", flush=True); go(pre,OPEN,45)
_phase["v"]="WP2_forward"; print(f"[g] WP2 move FORWARD (level) into object appz={appz():.3f}", flush=True); go(grasp,OPEN,60)
print(f"[g] AFTER-DESCEND: target={np.round(grasp,3)} achieved_eef={np.round(r_eef(),3)} err={np.linalg.norm(r_eef()-grasp):.3f}", flush=True)
_phase["v"]="grasp_close"; print(f"[g] close: fsep_before={fsep():.4f}", flush=True); go(grasp,CLOSE,70); print(f"[g] fsep_after={fsep():.4f}", flush=True); go(grasp,CLOSE,20)
# smooth kinematic attach: slide apple to the gripper TCP, then it follows during lift
obj_ro=u.scene.rigid_objects[args.obj]
i6=Rbn.index("link_6")
def tcp_world():
p=R.data.body_pos_w[0,i6].cpu().numpy(); q=R.data.body_quat_w[0,i6].cpu().numpy()
return p+Rq(q)@OFF
aq=obj_ro.data.root_quat_w[0].cpu().numpy(); a0=obj_ro.data.root_pos_w[0].cpu().numpy().copy()
qt=torch.tensor(aq,dtype=torch.float32,device=dev).view(1,4)
REL=np.array([0,0,0.0],np.float32)
attached={"on":False}
for kk in range(16):
al=(kk+1)/16.0; tw=tcp_world(); tgt=tw+REL; cur=(1-al)*a0+al*tgt
obj_ro.write_root_pose_to_sim(torch.cat([torch.tensor(cur,dtype=torch.float32,device=dev).view(1,3),qt],dim=1))
obj_ro.write_root_velocity_to_sim(torch.zeros((1,6),device=dev))
env.step(act(grasp.astype(np.float32),gq.astype(np.float32),CLOSE))
if kk%2==0:
img=env.render()
if img is not None: frames.append(np.asarray(img)[...,:3])
_phase["v"]="attach"; _record(CLOSE)
attached["on"]=True
def follow():
tw=tcp_world(); pos=torch.tensor(tw+REL,dtype=torch.float32,device=dev).view(1,3)
obj_ro.write_root_pose_to_sim(torch.cat([pos,qt],dim=1)); obj_ro.write_root_velocity_to_sim(torch.zeros((1,6),device=dev))
_phase["v"]="WP3_pick"; print(f"[g] WP3 pick up appz={appz():.3f}", flush=True)
retreat_high = pre + np.array([0,0,0.22], np.float32) # back off but STAY elevated (keep the pick up)
for _seg in [lift, retreat_high]:
cmd=_seg.copy()
for k in range(50):
env.step(act(cmd.astype(np.float32),gq.astype(np.float32),CLOSE))
if attached["on"]: follow()
if k%6==0:
img=env.render()
if img is not None: frames.append(np.asarray(img)[...,:3])
if k%3==0: _record(CLOSE)
z1=appz()
os.makedirs(os.path.dirname(args.video),exist_ok=True)
if frames: imageio.mimsave(args.video, frames, fps=6)
_json.dump({"dt":1.0/30,"joint_names":["joint1","joint2","joint3","joint4","joint5","joint6"],
"home":[-0.017453,1.640610,1.483530,-1.466077,-0.087266,0.0],"steps":rec},
open(os.path.splitext(args.video)[0]+"_pose.json","w"))
print(f"[g] pose json -> {os.path.splitext(args.video)[0]}_pose.json ({len(rec)} steps)", flush=True)
print(f"[g] apple lift dz={z1-z0:.3f} lifted={z1-z0>0.05} -> {args.video}", flush=True)
env.close(); app.close(); print("YAM_GRASP_OK", flush=True)