| """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 |
| 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 |
| |
| lp0, lq0 = eef_root(L, Lbn, lroot, lrootq) |
| rp_home, rq_home = eef_root(R, Rbn, rroot, rrootq) |
|
|
| |
| 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) |
| |
| 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 |
| |
| |
| |
| grasp = apple_root + np.array([0, 0, -0.02], np.float32) |
| pre = grasp + np.array([-0.18, 0.0, 0.0], np.float32) |
| lift = apple_root + np.array([0, 0, 0.22], np.float32) |
| |
| |
| |
| ready = (apple_w*0 + np.array([0.075,-0.200,0.609],np.float32)) - rroot |
| 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) |
| |
| 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) |
| 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) |
|
|