""" Render Scene Overview: Multi-object tabletop Rotating camera showing the full scene with 5 objects of different materials. This is the "hero" shot for the playbook. """ import mujoco import numpy as np import imageio WIDTH, HEIGHT = 1280, 720 FPS = 60 OUTPUT_DIR = "." DURATION_SEC = 6.0 # Full rotation XML = """ """.format(w=WIDTH, h=HEIGHT) def render_orbit(filename, radius=0.75, height=0.65, n_frames=None): """Render a smooth camera orbit around the tabletop.""" model = mujoco.MjModel.from_xml_string(XML) data = mujoco.MjData(model) renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH) mujoco.mj_forward(model, data) # Settle for _ in range(500): mujoco.mj_step(model, data) if n_frames is None: n_frames = int(DURATION_SEC * FPS) frames = [] scene = mujoco.MjvScene(model, maxgeom=1000) camera = mujoco.MjvCamera() option = mujoco.MjvOption() # Camera target: center of table camera.lookat[:] = [0, 0, 0.45] camera.distance = radius camera.elevation = -25 for i in range(n_frames): t = i / n_frames angle = t * 360 # full rotation camera.azimuth = angle + 135 # start from a nice angle # Gentle vertical oscillation camera.elevation = -25 + 5 * np.sin(t * 2 * np.pi) mujoco.mjv_updateScene(model, data, option, None, camera, mujoco.mjtCatBit.mjCAT_ALL, scene) renderer.update_scene(data) # Use the movable camera renderer._scene.camera[0].pos[:] = [ radius * np.cos(np.radians(angle + 135)), radius * np.sin(np.radians(angle + 135)), height + 0.05 * np.sin(t * 2 * np.pi) ] # Alternative: just render with default scene and move camera # Simpler approach using lookat cam = mujoco.MjvCamera() cam.lookat[:] = [0, 0, 0.43] cam.distance = radius cam.azimuth = angle + 135 cam.elevation = -25 + 5 * np.sin(t * 2 * np.pi) renderer.update_scene(data, camera=cam) frame = renderer.render() frames.append(frame.copy()) if i % FPS == 0: print(f" Frame {i}/{n_frames} ({t*100:.0f}%)") output_path = f"{OUTPUT_DIR}/{filename}" writer = imageio.get_writer(output_path, fps=FPS, codec='libx264', output_params=['-crf', '18', '-preset', 'medium']) for frame in frames: writer.append_data(frame) writer.close() print(f" Saved: {output_path} ({len(frames)} frames, {len(frames)/FPS:.1f}s)") renderer.close() def render_static_views(prefix="scene"): """Render a few static views as individual frames (PNG).""" model = mujoco.MjModel.from_xml_string(XML) data = mujoco.MjData(model) renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH) mujoco.mj_forward(model, data) for _ in range(500): mujoco.mj_step(model, data) views = [ ("front", 0.7, 180, -20), ("angle", 0.75, 135, -25), ("top", 0.9, 90, -80), ("close", 0.45, 160, -15), ] for name, dist, azimuth, elev in views: cam = mujoco.MjvCamera() cam.lookat[:] = [0, 0, 0.43] cam.distance = dist cam.azimuth = azimuth cam.elevation = elev renderer.update_scene(data, camera=cam) frame = renderer.render() output_path = f"{OUTPUT_DIR}/{prefix}_{name}.png" imageio.imwrite(output_path, frame) print(f" Saved: {output_path}") renderer.close() if __name__ == "__main__": print("=== Rendering Scene Overview ===\n") print("--- Static views ---") render_static_views() print("\n--- Orbit video ---") render_orbit("scene_overview.mp4") print("\nDone!")