File size: 8,975 Bytes
7399b6f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | """YAM cube tasks: SORT colored cubes into color-matched bins, or STACK cubes on top of each
other. Right arm, PRM approach + real friction grasp (no attach). Renders a labelled video.
Usage: python scripts/yam_cubes.py --headless --task sort|stack --video outputs/cubes.mp4"""
import argparse, sys, os
from isaaclab.app import AppLauncher
p=argparse.ArgumentParser()
p.add_argument("--task", default="sort", choices=["sort","stack"])
p.add_argument("--video", default="outputs/yam_cubes.mp4")
AppLauncher.add_app_launcher_args(p); args=p.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
from PIL import Image, ImageDraw
REPO=os.path.dirname(os.path.dirname(os.path.abspath(__file__))); sys.path.insert(0,os.path.join(REPO,"source")); sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
import yam_prm, 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)
# These demos script one long manipulation sequence; the 12 s task episode length would
# auto-reset the env mid-run and snap the arm back to its home joints (a visible pose jump).
_cfg.episode_length_s = 1.0e6
try:
_cfg.terminations.time_out = None
except Exception as _e:
print('[cfg] time_out disable failed:', _e)
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: pass
env=gym.make(TASK, cfg=_cfg, render_mode="rgb_array"); u=env.unwrapped; 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); L=u.scene["left_robot"]
def root_of(a): return a.data.root_pos_w[0].cpu().numpy()-origin, a.data.root_quat_w[0].cpu().numpy()
rroot,rrootq=root_of(R); OFF=np.array([0,0,0.13]); TABLE=0.45; CUBE=0.05
def eef_root(a,bn,root,rootq):
i=bn.index("link_6"); pp=a.data.body_pos_w[0,i].cpu().numpy()-origin; q=a.data.body_quat_w[0,i].cpu().numpy()
return Rq(rootq).T@((pp+Rq(q)@OFF)-root), q
lp0,lq0=eef_root(L,list(L.data.body_names),*root_of(L)); rp0,rq0=eef_root(R,Rbn,rroot,rrootq)
OPEN,CLOSE=1.0,-1.0
import isaaclab.sim as sim_utils
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)
Rg=np.stack([np.array([0.,1.,0.]),np.array([1.,0.,0.]),np.array([0.,0.,-1.])],axis=1); gq=qR(Rg)
def reposition(name,xy):
ro=u.scene.rigid_objects[name]
ro.write_root_pose_to_sim(torch.tensor(np.concatenate([origin+np.array([xy[0],xy[1],0.55]),[1,0,0,0]]),dtype=torch.float32,device=dev).view(1,7))
ro.write_root_velocity_to_sim(torch.zeros((1,6),device=dev))
# ---- scene setup per task ----
BINS=[] # (xy, color, name) for sort
if args.task=="sort":
reposition("cube_r",[0.00,0.06]); reposition("cube_b",[0.13,0.06])
for n in ["cube_g","cube_y"]: reposition(n,[3.0+np.random.rand()*0,4.0]) # keep off-scene deterministically
reposition("cube_g",[3.0,4.0]); reposition("cube_y",[3.4,4.0])
BINS=[([0.00,-0.20],(0.75,0.2,0.2),"bin_r"),([0.15,-0.20],(0.2,0.35,0.8),"bin_b")]
for xy,col,nm in BINS:
S,H,T=0.14,0.05,0.01
for sub,sz,off in [("f",(S,S,T),(0,0,T/2)),("xp",(T,S,H),(S/2,0,H/2)),("xn",(T,S,H),(-S/2,0,H/2)),("yp",(S,T,H),(0,S/2,H/2)),("yn",(S,T,H),(0,-S/2,H/2))]:
c=sim_utils.CuboidCfg(size=sz,visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=col),collision_props=sim_utils.CollisionPropertiesCfg())
c.func(f"/World/envs/env_0/{nm}_{sub}",c,translation=tuple((origin+np.array([xy[0],xy[1],TABLE])+np.array(off)).tolist()))
JOBS=[("cube_r",[0.00,-0.20]),("cube_b",[0.15,-0.20])]
else: # stack — keep every cube within reliable reach (root dist < ~0.38)
reposition("cube_g",[0.06,-0.02]); reposition("cube_r",[0.00,0.08]); reposition("cube_b",[0.12,0.02])
for n in ["cube_y"]: reposition(n,[3.0,4.0])
JOBS=[("cube_r",("stack",1)),("cube_b",("stack",2))] # onto cube_g at [0.06,-0.02]
STACK_XY=[0.06,-0.02]
for _ in range(60): env.step(act(rp0,rq0,OPEN))
lhome=L.data.joint_pos[0].clone(); _lz=torch.zeros((1,lhome.shape[0]),device=dev)
def freeze(): L.write_joint_state_to_sim(lhome.view(1,-1),_lz)
def boost(v,s=1.6,d=1.4):
try:
m=v.get_material_properties().clone(); m[...,0]=s; m[...,1]=d; v.set_material_properties(m,torch.arange(m.shape[0],dtype=torch.int32,device=m.device))
except Exception as e: print("fric",e)
boost(R.root_physx_view)
for j in (JOBS if args.task=="sort" else JOBS): boost(u.scene.rigid_objects[j[0]].root_physx_view)
frames=[]; _CUR={"a":"start","g":"OPEN"}
def r_eef(): p,_=eef_root(R,Rbn,rroot,rrootq); return p
def rsep(): jn=list(R.data.joint_names); return (R.data.joint_pos[0,jn.index("left_finger")].item()+R.data.joint_pos[0,jn.index("right_finger")].item())/2
def cap():
img=env.render()
if img is None: return
im=Image.fromarray(np.asarray(img)[...,:3].copy()); d=ImageDraw.Draw(im); e=r_eef()
lines=[f"TASK: {args.task.upper()} cubes", f"ACTION: {_CUR['a']}", f"gripper: {_CUR['g']}"]
d.rectangle([0,0,330,18*len(lines)+6],fill=(0,0,0)); y=3
for ln in lines: d.text((6,y),ln,fill=(255,235,60)); y+=18
frames.append(np.array(im))
def go(tgt,g,n,tol=None,a=None):
if a: _CUR["a"]=a
_CUR["g"]="CLOSE" if g<0 else "OPEN"
for k in range(n):
env.step(act(tgt.astype(np.float32),gq,g)); freeze()
if k%5==0: cap()
if tol and np.linalg.norm(r_eef()-tgt)<tol: break
def close_stall(tgt,n=140):
_CUR["a"]="CLOSE-GRASP"; _CUR["g"]="CLOSE"; prev=rsep(); st=0
for k in range(n):
env.step(act(tgt.astype(np.float32),gq,CLOSE)); freeze()
if k%5==0: cap()
cur=rsep()
if abs(cur-prev)<0.0002: st+=1
else: st=0
prev=cur
if st>=8 and cur<-0.002: break
def objw(n): return u.scene.rigid_objects[n].data.root_pos_w[0].cpu().numpy()-origin
def oroot(n): return Rq(rrootq).T@(objw(n)-rroot)
def pick(name):
o=oroot(name); o[2]=(TABLE+CUBE/2.0)-rroot[2] # grasp centre height (cube rests on table)
pre=o+np.array([0,0,0.13],np.float32); grasp=o+np.array([0,0,-0.03],np.float32)
go(pre,OPEN,60,tol=0.02,a="APPROACH above cube"); go(grasp,OPEN,80,a="DESCEND")
close_stall(grasp); go(grasp+np.array([0,0,0.22],np.float32),CLOSE,70,a="LIFT")
def place(xy, top_world_z, precise=False):
# command the TCP so the held cube's bottom ends just above top_world_z, then release.
# diff-IK stalls ~4cm high -> command lower for a precise stack; drop-in bins can be higher.
tcp_z = (top_world_z - 0.03 if precise else top_world_z + CUBE+0.02) # precise: counter the ~4cm IK stall
tgt=(Rq(rrootq).T@(np.array([xy[0],xy[1],tcp_z])-rroot)).astype(np.float32)
go(tgt+np.array([0,0,0.13],np.float32),CLOSE,100,tol=0.02,a="CARRY over target"); go(tgt,CLOSE,80,tol=0.02,a="LOWER")
go(tgt,OPEN,45,a="RELEASE"); go(tgt+np.array([0,0,0.14],np.float32),OPEN,30,a="RETREAT")
results=[]
if args.task=="sort":
for name,binxy in JOBS:
pick(name); place(binxy, TABLE) # drop into bin (floor at table height)
w=objw(name); inb=abs(w[0]-binxy[0])<0.09 and abs(w[1]-binxy[1])<0.09
results.append((name,inb)); print(f"[cubes] SORT {name} -> bin{binxy} in_bin={inb} world=({w[0]:.3f},{w[1]:.3f})",flush=True)
else:
for name,(_,lvl) in JOBS:
pick(name); place(STACK_XY, TABLE+lvl*CUBE, precise=True) # stack level lvl on the base cube
w=objw(name); onstack=abs(w[0]-STACK_XY[0])<0.05 and abs(w[1]-STACK_XY[1])<0.05 and w[2]>TABLE+0.5*CUBE+0.02
results.append((name,onstack)); print(f"[cubes] STACK {name} lvl{lvl} stacked={onstack} world=({w[0]:.3f},{w[1]:.3f},{w[2]:.3f})",flush=True)
_CUR["a"]="RESULT"; [cap() for _ in range(12)]
os.makedirs(os.path.dirname(args.video) or ".",exist_ok=True)
if frames: imageio.mimsave(args.video, frames, fps=7)
ok=sum(1 for _,r in results if r)
print(f"[cubes] {args.task}: {ok}/{len(results)} ok -> {args.video} ({len(frames)} frames)",flush=True)
env.close(); app.close(); print("YAM_CUBES_OK",flush=True)
|