File size: 8,344 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
"""YAM DUAL-ARM parallel pick(-and-place). Task allocation (which arm -> which object) comes
from the LLM/nearest-arm layer; each arm then runs PRM (planning-only walls) + the REAL
friction grasp IN PARALLEL. Renders a real Isaac Sim video. No kinematic attach anywhere."""
import argparse, sys, os
from isaaclab.app import AppLauncher
parser = argparse.ArgumentParser()
parser.add_argument("--video", default="outputs/yam_dualarm.mp4")
parser.add_argument("--place", action="store_true", help="also carry each object aside and release")
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, json as _json
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")); sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import yam_prm
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)
# 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 as _e: print("viewer cfg:",_e)
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"]; Lbn=list(L.data.body_names)
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); lroot,lrootq=root_of(L)
OFF=np.array([0,0,0.13])
def eef_root(a,bn,root,rootq):
    i=bn.index("link_6"); p=a.data.body_pos_w[0,i].cpu().numpy()-origin; q=a.data.body_quat_w[0,i].cpu().numpy()
    return Rq(rootq).T@((p+Rq(q)@OFF)-root), q

# ---- ALLOCATION: each arm picks its own cup (parallel), then places it aside ----
ASSIGN={"left_robot":"rw_cup2","right_robot":"rw_cup"}
print(f"[da] allocation: left->{ASSIGN['left_robot']} right->{ASSIGN['right_robot']} (parallel)", flush=True)
lp0,lq0=eef_root(L,Lbn,lroot,lrootq); rp0,rq0=eef_root(R,Rbn,rroot,rrootq)
OPEN,CLOSE=1.0,-1.0
def act2(lp,lq,lg,rp,rq,rg):
    return torch.tensor(np.concatenate([lp,lq,[lg],rp,rq,[rg]]),dtype=torch.float32,device=dev).view(1,-1)
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))
reposition("rw_cup",[0.05,-0.14]); reposition("rw_cup2",[0.05,0.14])   # symmetric reachable spots
for _ in range(60): env.step(act2(lp0,lq0,OPEN,rp0,rq0,OPEN))
def boost(view,s=1.6,d=1.4):
    try:
        m=view.get_material_properties().clone(); m[...,0]=s; m[...,1]=d
        view.set_material_properties(m, torch.arange(m.shape[0],dtype=torch.int32,device=m.device)); return True
    except Exception as e: print("[da] fric fail",e); return False
boost(R.root_physx_view); boost(L.root_physx_view)
boost(u.scene.rigid_objects["rw_cup"].root_physx_view); boost(u.scene.rigid_objects["rw_cup2"].root_physx_view)
Rg=np.stack([np.array([0.,1.,0.]),np.array([1.,0.,0.]),np.array([0.,0.,-1.])],axis=1); gq=qR(Rg)
TABLE=0.45; CENTER_Z=0.47   # cup geometric-centre world height (rests on table, ~4.5cm tall)
def obj_root(name,root,rootq):
    w=u.scene.rigid_objects[name].data.root_pos_w[0].cpu().numpy()-origin
    return Rq(rootq).T@(np.array([w[0],w[1],CENTER_Z],np.float32)-root)
def objz(name): return float(u.scene.rigid_objects[name].data.root_pos_w[0,2].item())
L_obj=obj_root(ASSIGN["left_robot"],lroot,lrootq); R_obj=obj_root(ASSIGN["right_robot"],rroot,rrootq)
L_pre=L_obj+np.array([0,0,0.12],np.float32); L_grasp=L_obj+np.array([0,0,-0.05],np.float32); L_lift=L_grasp+np.array([0,0,0.22],np.float32)
R_pre=R_obj+np.array([0,0,0.12],np.float32); R_grasp=R_obj+np.array([0,0,-0.05],np.float32); R_lift=R_grasp+np.array([0,0,0.22],np.float32)
print(f"[da] left obj_root={np.round(L_obj,3)}  right obj_root={np.round(R_obj,3)}", flush=True)

def plan(start,goal):
    prm=yam_prm.PRM(bounds_lo=np.array([-0.1,-0.45,-0.05]),bounds_hi=np.array([0.55,0.45,0.35]),
                    obstacles=[],clearance=0.04,num_samples=200,k=10,seed=1)
    p=prm.plan(start.astype(np.float64),goal.astype(np.float64))
    if p is None: p=np.stack([start,goal]).astype(np.float32)
    return yam_prm.resample_polyline(yam_prm.shortcut(p,[],0.04,iters=100,seed=2),12).astype(np.float32)
L_path=plan(lp0,L_pre); R_path=plan(rp0,R_pre)

frames=[]
def snap():
    img=env.render()
    if img is not None: frames.append(np.asarray(img)[...,:3])
def eefL(): p,_=eef_root(L,Lbn,lroot,lrootq); return p
def eefR(): p,_=eef_root(R,Rbn,rroot,rrootq); return p
def drive(lt,lg,rt,rg,n,tol=None):
    ls=eefL(); rs=eefR(); lt=np.asarray(lt,np.float32); rt=np.asarray(rt,np.float32)  # smooth LERP (no jerk)
    for k in range(n):
        a=(k+1)/float(n); lc=(1-a)*ls+a*lt; rc=(1-a)*rs+a*rt
        env.step(act2(lc,gq,lg,rc,gq,rg))
        if k%4==0: snap()
        if tol and a>=1.0 and np.linalg.norm(eefL()-lt)<tol and np.linalg.norm(eefR()-rt)<tol: break

# ---- PHASE 1: parallel approach along each arm's PRM path ----
m=max(len(L_path),len(R_path))
for i in range(1,m):
    lt=L_path[min(i,len(L_path)-1)]; rt=R_path[min(i,len(R_path)-1)]; last=(i==m-1)
    drive(lt,OPEN,rt,OPEN, 80 if last else 20, tol=(0.02 if last else None))
print(f"[da] approached: L_err={np.linalg.norm(eefL()-L_pre):.3f} R_err={np.linalg.norm(eefR()-R_pre):.3f}", flush=True)
# ---- PHASE 2: parallel descend ----
drive(L_grasp,OPEN,R_grasp,OPEN,90)
print(f"[da] descended: Leef={np.round(eefL(),3)} Reef={np.round(eefR(),3)}", flush=True)
# ---- PHASE 3: parallel close (both clamp) ----
for k in range(120):
    env.step(act2(L_grasp,gq,CLOSE,R_grasp,gq,CLOSE))
    if k%6==0: snap()
# ---- PHASE 4: parallel lift (verify hold) ----
zL0=objz(ASSIGN["left_robot"]); zR0=objz(ASSIGN["right_robot"])
drive(L_lift,CLOSE,R_lift,CLOSE,80)
zL1=objz(ASSIGN["left_robot"]); zR1=objz(ASSIGN["right_robot"])
# ---- optional PHASE 5: place aside + release ----
if args.place:
    L_drop=np.array([0.10,-0.05,0.10],np.float32); R_drop=np.array([0.10,0.05,0.10],np.float32)  # swap sides (cross place)
    drive(L_lift+np.array([0,0,0.02],np.float32),CLOSE, R_lift+np.array([0,0,0.02],np.float32),CLOSE, 10)
    drive(np.array([L_drop[0],L_drop[1],L_lift[2]],np.float32),CLOSE, np.array([R_drop[0],R_drop[1],R_lift[2]],np.float32),CLOSE, 70)
    drive(L_drop,CLOSE,R_drop,CLOSE,60)
    for k in range(40):
        env.step(act2(L_drop,gq,OPEN,R_drop,gq,OPEN))
        if k%6==0: snap()

os.makedirs(os.path.dirname(args.video),exist_ok=True)
if frames: imageio.mimsave(args.video, frames, fps=6)
print(f"[da] LEFT  {ASSIGN['left_robot']}: dz={zL1-zL0:.3f} lifted={zL1-zL0>0.05}", flush=True)
print(f"[da] RIGHT {ASSIGN['right_robot']}: dz={zR1-zR0:.3f} lifted={zR1-zR0>0.05}", flush=True)
print(f"[da] -> {args.video} ({len(frames)} frames)", flush=True)
env.close(); app.close(); print("YAM_DUALARM_OK", flush=True)