Scene, Robot, Prop & World
Every task starts with build(self, scene: simulo.Scene), and this page is
what goes inside it.
You describe a scene: what exists. You put a robot in it, add movable props, give it terrain to stand on, or load a whole prebuilt world such as a warehouse. Simulo then builds that scene as many times as you ask for, one environment per copy, and runs them all at once.
Scene: the declaration
Section titled “Scene: the declaration”A scene declares what exists. You write build() once, however
many environments you run:
def build(self, scene: simulo.Scene) -> None: scene.add(simulo.Light.dome(name="light", intensity=2000.0), per_environment=False) scene.add(warehouse, at="/World/Warehouse", per_environment=False) scene.add(self.robot, at="/World/Robot") # per_environment=Trueper_environment decides whether each environment gets its own copy of an
item or all of them share one:
per_environment |
Behavior | Use it for |
|---|---|---|
True (default) |
Every environment gets its own copy, which moves independently. | A robot: each environment trains its own instance. |
False |
One copy, shared by every environment. | Terrain, lights, or a world. 512 identical warehouses would cost 512 times the memory for nothing. |
Robots and movable Prop objects can’t be shared by a scene with more than
one environment yet. Add them directly with the default
per_environment=True, or run the scene with one environment. A robot or prop
inside a Transform isn’t replicated; add it directly for a multi-environment
scene. Scene building reports the item’s name and how to fix it before the
first episode starts.
The thing you train or control:
self.robot = simulo.Robot( asset=simulo.Asset.from_registry("robot/so-arm-100:v3"), initial_pose=simulo.Pose.identity(),)scene.add(self.robot, at="/World/Robot")initial_pose is where the robot spawns. Where it is right now is
robot.state, batched across every environment:
q = self.robot.state.joint_positionsqd = self.robot.state.joint_velocitiestau = self.robot.state.joint_effortsp = self.robot.state.pose # where it is nowv_b = self.robot.state.linear_velocity_in_base_framew_b = self.robot.state.angular_velocity_in_base_frameTo start an episode from the robot’s resting pose, use the defaults on the robot rather than live state:
self.robot.reset(env_ids)joint_pos = self.robot.default_joint_positions[env_ids]joint_vel = self.robot.default_joint_velocities[env_ids]base_pose = self.robot.default_pose[env_ids]These values are what reset() writes. They have one row per environment,
and every read gives you a new tensor, so you can perturb the selected rows
before writing them back with set_joint_state() or set_root_pose() without
changing a later reset.
The live-state tensors need different handling:
- Treat them as read-only. Writing into one (
q[:] = 0) changes the simulation directly. Use the robot’s methods, such asset_joint_state, to move it on purpose. - Re-read motion state every time you use it. Simulo replaces
pose,linear_velocity, andangular_velocityeach step. It computeslinear_velocity_in_base_frameandangular_velocity_in_base_framefrom the current base orientation on every read, returning a new tensor. A reference to any of those values from an earlier step can be stale.joint_positions,joint_velocities, andjoint_effortsare updated in place, so keeping a reference to them, for example fromon_start, is safe.
robot.internals is an unstable escape hatch for anything the documented
robot and state properties do not cover. Its attributes can change between
releases, so prefer the documented properties whenever they fit the task.
Prop: an object you move or measure
Section titled “Prop: an object you move or measure”Use a Prop for a rigid object whose live pose matters to the task, such as a
block to lift, a crate to push, or a ball to catch:
self.block = simulo.Prop( simulo.Cuboid( name="block", size=(0.05, 0.05, 0.05), pose=simulo.Pose(position=[0.5, 0.0, 0.025]), physics=simulo.Physics.rigid(mass=0.05), ))scene.add(self.block, at="/World/Block")Read block.state.pose to find the object in every environment. When
episodes reset, block.set_pose(..., env_ids=env_ids) and
block.set_velocity(...) move it in just the environments that finished. If
an object never moves and you never need its pose, add the shape to the scene
directly instead.
Franka manipulation measures success from a block’s height this way, rather than guessing from the gripper’s position.
Terrain: ground you generate
Section titled “Terrain: ground you generate”scene.add(simulo.Terrain.plane(name="ground"), per_environment=False)Terrain.plane(...) is the common case: a flat, endless ground plane, one
shared copy for the whole scene.
World: a place you bring
Section titled “World: a place you bring”A world is a prebuilt space such as a warehouse, a factory floor, or a room. Publish it once, then use it by reference like any other asset:
simulo asset publish ./my-warehouse --kind worldwarehouse = simulo.Asset.from_registry("world/my-warehouse:v1") # module level
def build(self, scene: simulo.Scene) -> None: scene.add(warehouse, at="/World/Warehouse", per_environment=False)simulo.World.usd(name=..., path=...) also builds a world straight from a USD
file path. It skips the catalog, so the file is neither validated nor
version-pinned with your job.
Terrain is ground you generate; a world is a place you bring. A world almost
always wants per_environment=False, since it is usually large and every copy
costs memory. Unlike an endless plane, though, a world has edges, so choose an
env_spacing that keeps every environment’s robot inside it.
Environments: live copies of your scene
Section titled “Environments: live copies of your scene”A scene is the blueprint; an environment is one live copy of it. You get
num_envs of them:
@app.job(type="train", system=simulo.SystemType.TIER_2)def train(num_envs: int = 512) -> dict: ... Scene declared once: robot + terrain or world + lights │ │ built num_envs times, env_spacing apart ▼ ┌──────────┬──────────┬──────────┐ │ env 0 │ env 1 │ env 2 │ ... num_envs │ robot │ robot │ robot │ (per_environment=True) └──────────┴──────────┴──────────┘ └─────── one shared warehouse ───────┘ (per_environment=False)env_spacing sets how far apart the environments sit. Everything you read
back, from observations and rewards to robot.state, comes batched with one
row per environment.
Where to go next
Section titled “Where to go next”- Tasks and RL training puts a
build(scene)like the ones above into a full training job. - Volumes & Assets covers asset handles,
including
simulo.AssetSourcefor a model file that isn’t published. - Bring your own robot walks through publishing and validating a robot end to end.