Skip to main content
Glama

stage_bake_simulation

Convert physics simulation data into keyframe animations for export to R3F/Remotion video rendering workflows.

Instructions

Bake physics simulation to keyframe animations.

Converts physics simulation data into keyframes that can be exported to R3F/Remotion for video rendering. Args: scene_id: Scene identifier simulation_id: Physics simulation ID from chuk-mcp-physics fps: Frames per second for sampling (default 60) duration: Duration in seconds to bake (if None, bakes entire simulation) physics_server_url: Optional Rapier HTTP server URL If None, defaults to public Rapier service (https://rapier.chukai.io) Can be overridden with RAPIER_SERVICE_URL environment variable Returns: BakeSimulationResponse with frame count and baked object list Tips for LLMs: - Run physics simulation first (chuk-mcp-physics step_simulation or record_trajectory) - Bind objects to physics bodies (stage_bind_physics) - Bake simulation to convert physics โ†’ animation keyframes - Then export scene to R3F/Remotion with animation data Example: # After running simulation and binding objects result = await stage_bake_simulation( scene_id=scene_id, simulation_id=sim.sim_id, fps=60, duration=10.0 ) print(f"Baked {result.total_frames} frames for {len(result.baked_objects)} objects")

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
scene_idYes
simulation_idYes
fpsNo
durationNo
physics_server_urlNo

Implementation Reference

  • Main handler function stage_bake_simulation that converts physics simulation data into keyframe animations. It retrieves objects with physics bindings, uses PhysicsBridge to bake simulation data, stores animations in the scene VFS, and returns BakeSimulationResponse with frame counts and baked object IDs.
    @requires_auth() @tool # type: ignore[arg-type] async def stage_bake_simulation( scene_id: str, simulation_id: str, fps: int = 60, duration: Optional[float] = None, physics_server_url: Optional[str] = None, ) -> BakeSimulationResponse: """Bake physics simulation to keyframe animations. Converts physics simulation data into keyframes that can be exported to R3F/Remotion for video rendering. Args: scene_id: Scene identifier simulation_id: Physics simulation ID from chuk-mcp-physics fps: Frames per second for sampling (default 60) duration: Duration in seconds to bake (if None, bakes entire simulation) physics_server_url: Optional Rapier HTTP server URL If None, defaults to public Rapier service (https://rapier.chukai.io) Can be overridden with RAPIER_SERVICE_URL environment variable Returns: BakeSimulationResponse with frame count and baked object list Tips for LLMs: - Run physics simulation first (chuk-mcp-physics step_simulation or record_trajectory) - Bind objects to physics bodies (stage_bind_physics) - Bake simulation to convert physics โ†’ animation keyframes - Then export scene to R3F/Remotion with animation data Example: # After running simulation and binding objects result = await stage_bake_simulation( scene_id=scene_id, simulation_id=sim.sim_id, fps=60, duration=10.0 ) print(f"Baked {result.total_frames} frames for {len(result.baked_objects)} objects") """ manager = get_scene_manager() scene = await manager.get_scene(scene_id) # Find all objects with physics bindings bound_objects = [ (obj_id, obj) for obj_id, obj in scene.objects.items() if obj.physics_binding and simulation_id in obj.physics_binding ] if not bound_objects: raise ValueError(f"No objects bound to simulation {simulation_id}") # Extract body IDs from bindings body_ids = [] for obj_id, obj in bound_objects: # Parse "rapier://sim-{sim_id}/body-{body_id}" parts = obj.physics_binding.split("/") # type: ignore if len(parts) >= 2: body_id = parts[-1].replace("body-", "") body_ids.append(body_id) logger.info(f"Baking simulation {simulation_id} for {len(body_ids)} bodies") # Use physics bridge to bake async with PhysicsBridge(physics_server_url) as bridge: baked_data = await bridge.bake_simulation(simulation_id, body_ids, fps, duration) # Store baked animations in scene workspace vfs = await manager.get_scene_vfs(scene_id) await vfs.mkdir("/animations") total_frames = 0 baked_object_ids = [] for obj_id, obj in bound_objects: # Get body ID body_id = obj.physics_binding.split("/")[-1].replace("body-", "") # type: ignore if body_id in baked_data: keyframes = baked_data[body_id] total_frames = max(total_frames, len(keyframes)) # Save keyframes to VFS keyframes_json = PhysicsBridge.keyframes_to_json(keyframes) animation_path = f"/animations/{obj_id}.json" await vfs.write_text(animation_path, keyframes_json) # Add baked animation to scene baked_anim = BakedAnimation( object_id=obj_id, source=simulation_id, fps=fps, frames=len(keyframes), data_path=animation_path, ) await manager.add_baked_animation(scene_id, obj_id, baked_anim) baked_object_ids.append(obj_id) return BakeSimulationResponse( scene_id=scene_id, baked_objects=baked_object_ids, total_frames=total_frames, fps=fps, message=f"Baked {total_frames} frames for {len(baked_object_ids)} objects", )
  • Tool registration using @tool decorator and @requires_auth() decorator to expose stage_bake_simulation as an MCP tool.
    @requires_auth() @tool # type: ignore[arg-type] async def stage_bake_simulation( scene_id: str, simulation_id: str, fps: int = 60, duration: Optional[float] = None, physics_server_url: Optional[str] = None, ) -> BakeSimulationResponse:
  • BakeSimulationResponse schema defining the response structure with scene_id, baked_objects list, total_frames, fps, and message fields.
    class BakeSimulationResponse(BaseModel): """Response from baking simulation.""" scene_id: str baked_objects: list[str] # Object IDs that got animation data total_frames: int fps: int message: str = "Simulation baked successfully"
  • BakedAnimation schema defining stored animation metadata including object_id, source simulation ID, fps, frame count, and VFS data path.
    class BakedAnimation(BaseModel): """Baked animation data from physics simulation.""" object_id: str source: str # Physics simulation ID fps: int = Field(default=60, description="Frames per second") frames: int = Field(description="Total number of frames") data_path: str = Field(description="VFS path to animation data (binary or JSON)")
  • PhysicsBridge.bake_simulation method that calls the Rapier physics server HTTP API to retrieve trajectory data and convert it to keyframe format with position, rotation, and velocity data.
    async def bake_simulation( self, simulation_id: str, body_ids: list[str], fps: int = 60, duration: Optional[float] = None, ) -> dict[str, list[dict]]: """Bake physics simulation to keyframe data. Args: simulation_id: Physics simulation ID (e.g., from chuk-mcp-physics) body_ids: List of physics body IDs to bake fps: Frames per second for sampling duration: Duration in seconds (if None, bakes entire simulation) Returns: Dict mapping body_id to list of keyframes Each keyframe: {"time": float, "position": [x,y,z], "rotation": [x,y,z,w]} Raises: ValueError: If physics server is not configured httpx.HTTPError: If physics server request fails """ if not self._client: raise ValueError( f"Physics client not initialized. Ensure PhysicsBridge is used as async context manager. " f"Rapier service URL: {self.physics_server_url}" ) logger.info(f"Baking simulation {simulation_id} for {len(body_ids)} bodies at {fps} FPS") # Request trajectory data from physics server # This assumes chuk-mcp-physics has a compatible endpoint # For now, we'll use the record_trajectory tool via HTTP API baked_data: dict[str, list[dict]] = {} for body_id in body_ids: # Calculate number of steps from duration and fps if duration: steps = int(duration * fps) else: # Get current simulation time/steps steps = 600 # Default 10 seconds at 60 FPS # Call physics server to get trajectory # Rapier service endpoint: POST /simulations/{sim_id}/bodies/{body_id}/trajectory try: response = await self._client.post( f"/simulations/{simulation_id}/bodies/{body_id}/trajectory", json={ "steps": steps, "dt": 1.0 / fps, }, ) response.raise_for_status() trajectory_data = response.json() # Convert to our keyframe format keyframes = [] for frame in trajectory_data.get("frames", []): keyframes.append( { "time": frame["time"], "position": frame["position"], "rotation": frame["orientation"], "velocity": frame.get("velocity", [0, 0, 0]), } ) baked_data[body_id] = keyframes logger.info(f"Baked {len(keyframes)} frames for body {body_id}") except httpx.HTTPError as e: logger.error(f"Failed to bake trajectory for {body_id}: {e}") raise return baked_data

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrishayuk/chuk-mcp-stage'

If you have feedback or need assistance with the MCP directory API, please join our Discord server