WorldRepresentation / render_scripts /render_exp3_grasp.py
Nirav Madhani
Update World Representation v0.3
7387a8b
Raw
History Blame Contribute Delete
10.4 kB
"""
Render Experiment 3: Grasp Force Selection
Simulates a parallel-jaw gripper closing on objects with different forces.
Shows: too weak (drops), correct (lifts), too strong (crushes / deforms).
"""
import mujoco
import numpy as np
import imageio
WIDTH, HEIGHT = 1280, 720
FPS = 60
OUTPUT_DIR = "."
XML_GRASP = """
<mujoco model="grasp_demo">
<option timestep="0.002" gravity="0 0 -9.81"/>
<visual>
<global offwidth="{w}" offheight="{h}"/>
<quality shadowsize="4096"/>
<headlight ambient="0.3 0.3 0.3" diffuse="0.6 0.6 0.6"/>
</visual>
<asset>
<texture name="grid" type="2d" builtin="checker" rgb1="0.35 0.35 0.38" rgb2="0.30 0.30 0.33"
width="256" height="256"/>
<material name="grid_mat" texture="grid" texrepeat="8 8"/>
<material name="wood_mat" rgba="0.45 0.35 0.25 1"/>
<material name="finger_mat" rgba="0.3 0.3 0.35 1" specular="0.6" shininess="0.8"/>
<material name="obj_mat" rgba="{r} {g} {b} 1" specular="0.3" shininess="0.5"/>
</asset>
<worldbody>
<light pos="0 -0.4 1.5" dir="0 0.3 -1" castshadow="true"/>
<light pos="0.4 0.3 1.0" dir="-0.2 -0.2 -1" castshadow="false" diffuse="0.3 0.3 0.35"/>
<geom name="floor" type="plane" size="2 2 0.1" material="grid_mat"/>
<body name="table" pos="0 0 0.38">
<geom type="box" size="0.3 0.2 0.02" material="wood_mat"/>
<geom type="cylinder" size="0.012 0.18" pos="-0.26 -0.16 -0.2" rgba="0.4 0.3 0.2 1"/>
<geom type="cylinder" size="0.012 0.18" pos=" 0.26 -0.16 -0.2" rgba="0.4 0.3 0.2 1"/>
<geom type="cylinder" size="0.012 0.18" pos="-0.26 0.16 -0.2" rgba="0.4 0.3 0.2 1"/>
<geom type="cylinder" size="0.012 0.18" pos=" 0.26 0.16 -0.2" rgba="0.4 0.3 0.2 1"/>
</body>
<!-- Object to grasp -->
<body name="object" pos="0 0 0.435">
<joint type="free"/>
<geom name="obj_geom" type="{shape}" size="{size}" mass="{mass}" material="obj_mat"
friction="{friction} 0.005 0.0001"/>
</body>
<!-- Left finger -->
<body name="finger_left" pos="0 -0.06 0.435">
<joint name="finger_left_j" type="slide" axis="0 1 0" range="-0.1 0.1"/>
<geom type="box" size="0.025 0.008 0.03" material="finger_mat"/>
</body>
<!-- Right finger -->
<body name="finger_right" pos="0 0.06 0.435">
<joint name="finger_right_j" type="slide" axis="0 -1 0" range="-0.1 0.1"/>
<geom type="box" size="0.025 0.008 0.03" material="finger_mat"/>
</body>
<!-- Lift mechanism (invisible) -->
<body name="lift_bar" pos="0 0 0.5">
<joint name="lift_j" type="slide" axis="0 0 1" range="-0.2 0.3"/>
<geom type="box" size="0.001 0.001 0.001" rgba="0 0 0 0"/>
</body>
<camera name="main" pos="0.15 -0.4 0.6" xyaxes="1 0 0 0 0.5 0.87" fovy="45"/>
<camera name="front" pos="0 -0.35 0.5" xyaxes="1 0 0 0 0.45 0.89" fovy="40"/>
</worldbody>
<actuator>
<position joint="finger_left_j" kp="200" ctrlrange="-0.1 0.1"/>
<position joint="finger_right_j" kp="200" ctrlrange="-0.1 0.1"/>
<position joint="lift_j" kp="500" ctrlrange="-0.2 0.3"/>
</actuator>
</mujoco>
"""
OBJECTS = {
"egg": {
"mass": 0.06, "friction": 0.3,
"shape": "ellipsoid", "size": "0.02 0.02 0.027",
"color": (0.95, 0.92, 0.85),
"crush_force": 3.0,
},
"rubber_ball": {
"mass": 0.4, "friction": 0.8,
"shape": "sphere", "size": "0.025",
"color": (0.2, 0.2, 0.22),
"crush_force": 50.0,
},
"wood_block": {
"mass": 0.5, "friction": 0.4,
"shape": "box", "size": "0.025 0.025 0.025",
"color": (0.65, 0.45, 0.25),
"crush_force": 30.0,
},
"glass_cup": {
"mass": 0.3, "friction": 0.15,
"shape": "cylinder", "size": "0.02 0.03",
"color": (0.7, 0.85, 0.9),
"crush_force": 8.0,
},
}
def render_grasp_attempt(obj_name, grip_force_target, filename, camera="front"):
"""Render a single grasp attempt: approach, squeeze, lift."""
obj = OBJECTS[obj_name]
xml = XML_GRASP.format(
w=WIDTH, h=HEIGHT,
mass=obj["mass"], friction=obj["friction"],
shape=obj["shape"], size=obj["size"],
r=obj["color"][0], g=obj["color"][1], b=obj["color"][2],
)
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH)
cam_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, camera)
obj_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_BODY, "object")
mujoco.mj_forward(model, data)
# Settle
for _ in range(200):
mujoco.mj_step(model, data)
frames = []
frames_per_step = max(1, int(1.0 / (FPS * model.opt.timestep)))
# Phase 1: Show object (0.5s)
for _ in range(int(0.5 / model.opt.timestep)):
data.ctrl[0] = 0 # fingers open
data.ctrl[1] = 0
data.ctrl[2] = 0 # no lift
mujoco.mj_step(model, data)
if _ % frames_per_step == 0:
renderer.update_scene(data, camera=cam_id)
frames.append(renderer.render().copy())
# Phase 2: Close fingers (1s)
# Map grip_force_target to finger position
# Higher force = tighter grip = smaller gap
finger_pos = min(0.06, 0.02 + 0.005 * grip_force_target / 10)
close_steps = int(1.0 / model.opt.timestep)
for step in range(close_steps):
t = step / close_steps
data.ctrl[0] = t * finger_pos
data.ctrl[1] = t * finger_pos
data.ctrl[2] = 0
mujoco.mj_step(model, data)
if step % frames_per_step == 0:
renderer.update_scene(data, camera=cam_id)
frames.append(renderer.render().copy())
# Phase 3: Lift (1.5s)
lift_steps = int(1.5 / model.opt.timestep)
obj_z_before = data.xpos[obj_id][2]
for step in range(lift_steps):
t = step / lift_steps
data.ctrl[0] = finger_pos
data.ctrl[1] = finger_pos
data.ctrl[2] = t * 0.15 # lift 15cm
mujoco.mj_step(model, data)
if step % frames_per_step == 0:
renderer.update_scene(data, camera=cam_id)
frames.append(renderer.render().copy())
obj_z_after = data.xpos[obj_id][2]
lifted = (obj_z_after - obj_z_before) > 0.05
crushed = grip_force_target > obj["crush_force"]
# Phase 4: Hold / show result (0.5s)
for _ in range(int(0.5 / model.opt.timestep)):
data.ctrl[2] = 0.15
mujoco.mj_step(model, data)
if _ % frames_per_step == 0:
renderer.update_scene(data, camera=cam_id)
frames.append(renderer.render().copy())
output_path = f"{OUTPUT_DIR}/{filename}"
writer = imageio.get_writer(output_path, fps=FPS, codec='libx264',
output_params=['-crf', '20', '-preset', 'medium'])
for frame in frames:
writer.append_data(frame)
writer.close()
result = "LIFTED" if lifted else "DROPPED"
if crushed:
result = "CRUSHED (force > threshold)"
print(f" {obj_name} @ {grip_force_target}N -> {result} | Saved: {output_path} ({len(frames)} frames)")
renderer.close()
def render_grasp_comparison(obj_name, forces, filename):
"""Render multiple grasp attempts at different forces, concatenated."""
obj = OBJECTS[obj_name]
xml = XML_GRASP.format(
w=WIDTH, h=HEIGHT,
mass=obj["mass"], friction=obj["friction"],
shape=obj["shape"], size=obj["size"],
r=obj["color"][0], g=obj["color"][1], b=obj["color"][2],
)
model = mujoco.MjModel.from_xml_string(xml)
renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH)
cam_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, "front")
all_frames = []
for force in forces:
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
obj_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_BODY, "object")
for _ in range(200):
mujoco.mj_step(model, data)
frames_per_step = max(1, int(1.0 / (FPS * model.opt.timestep)))
finger_pos = min(0.06, 0.02 + 0.005 * force / 10)
# Quick approach + close + lift cycle
phases = [
("show", 0.3, lambda t: (0, 0, 0)),
("close", 0.6, lambda t: (t * finger_pos, t * finger_pos, 0)),
("lift", 1.0, lambda t: (finger_pos, finger_pos, t * 0.12)),
("hold", 0.3, lambda t: (finger_pos, finger_pos, 0.12)),
]
for phase_name, dur, ctrl_fn in phases:
steps = int(dur / model.opt.timestep)
for step in range(steps):
t = step / max(steps, 1)
c = ctrl_fn(t)
data.ctrl[0] = c[0]
data.ctrl[1] = c[1]
data.ctrl[2] = c[2]
mujoco.mj_step(model, data)
if step % frames_per_step == 0:
renderer.update_scene(data, camera=cam_id)
all_frames.append(renderer.render().copy())
output_path = f"{OUTPUT_DIR}/{filename}"
writer = imageio.get_writer(output_path, fps=FPS, codec='libx264',
output_params=['-crf', '20', '-preset', 'medium'])
for frame in all_frames:
writer.append_data(frame)
writer.close()
print(f" Saved: {output_path} ({len(all_frames)} frames, {len(all_frames)/FPS:.1f}s)")
renderer.close()
if __name__ == "__main__":
print("=== Rendering Experiment 3: Grasp Force Selection ===\n")
# Individual grasp attempts
for obj_name in ["egg", "rubber_ball", "wood_block"]:
print(f"\n--- {obj_name} ---")
obj = OBJECTS[obj_name]
min_f = obj["mass"] * 9.81 / (obj["friction"] * 2)
# Three attempts: too weak, just right, too strong
attempts = [
(min_f * 0.5, "weak"),
(min_f * 1.5, "good"),
(obj["crush_force"] * 1.5, "crush"),
]
for force, label in attempts:
render_grasp_attempt(
obj_name, force,
f"exp3_{obj_name}_{label}.mp4"
)
# Comparison videos
print("\n--- Comparison: egg at multiple forces ---")
render_grasp_comparison("egg", [0.5, 1.5, 5.0], "exp3_egg_comparison.mp4")
print("\nDone!")