|
| 1 | +# Scene and entities |
| 2 | + |
| 3 | +`genelab.scene.InteractiveScene` is the composition root that owns a Genesis |
| 4 | +`gs.Scene` together with the articulated robot, rigid props, optional terrain, and |
| 5 | +the mouse-interaction plugin. `ManagerBasedRlEnv` defers all sim-level orchestration |
| 6 | +to the scene; the env layer only manages the seven MDP terms. |
| 7 | + |
| 8 | +## Composing a Genesis scene |
| 9 | + |
| 10 | +A scene is built from two cfg objects passed to the env. `SimulationCfg` carries the |
| 11 | +Genesis runtime knobs (timestep, parallel-env count, viewer toggle); `InteractiveSceneCfg` |
| 12 | +carries the composition (entities, terrain, sensors, mouse plugin, BatchRenderer |
| 13 | +toggle). The scene parses the cfg, allocates wrapper objects, then defers the actual |
| 14 | +Genesis allocation to the `build()` call. |
| 15 | + |
| 16 | +```python |
| 17 | +from genelab.asset_zoo import CartpoleCfg |
| 18 | +from genelab.configs import InteractiveSceneCfg, SimulationCfg |
| 19 | +from genelab.envs.manager_based_rl_env import ManagerBasedRlEnvCfg |
| 20 | + |
| 21 | +env_cfg = ManagerBasedRlEnvCfg( |
| 22 | + simulation=SimulationCfg(num_envs=4096, dt=0.005, substeps=1, vis=False), |
| 23 | + scene=InteractiveSceneCfg(env_spacing=(2.5, 2.5)), |
| 24 | + robot=CartpoleCfg(), |
| 25 | +) |
| 26 | +``` |
| 27 | + |
| 28 | +## Lifecycle |
| 29 | + |
| 30 | +`InteractiveScene` follows a strict three-step lifecycle, enforced at runtime: |
| 31 | + |
| 32 | +1. **Construct**: `InteractiveScene(sim_cfg, scene_cfg, device_hint=...)` parses the |
| 33 | + cfgs and allocates wrapper objects for every entity declared in |
| 34 | + `scene_cfg.entities`. No Genesis import yet. |
| 35 | +2. **Add entities**: `add_entity(name, cfg)` registers extra entities — the env uses |
| 36 | + this to inject `ManagerBasedRlEnvCfg.robot` under the name `"robot"`. Calling |
| 37 | + `add_entity` after `build()` raises `RuntimeError`. |
| 38 | +3. **Build**: `build()` imports Genesis, initialises the backend, creates the |
| 39 | + `gs.Scene`, spawns ground / terrain / entities / cameras, attaches the mouse |
| 40 | + plugin when configured, then calls `gs_scene.build(n_envs, env_spacing)`. |
| 41 | + |
| 42 | +`ManagerBasedRlEnv.__init__` runs all three steps in order and post-build binds every |
| 43 | +articulation so the per-joint and per-link tensors land. |
| 44 | + |
| 45 | +## SimulationCfg |
| 46 | + |
| 47 | +| Field | Default | Meaning | |
| 48 | +|---|---|---| |
| 49 | +| `vis` | `False` | Open the Genesis viewer. Forces `num_envs=1` in practice; the viewer can only render env 0. | |
| 50 | +| `gpu` | `False` | Use the CUDA backend (`gs.gpu`). Required for `BatchRenderer`. | |
| 51 | +| `steps` | `240` | Default number of steps the CLI `play` runner takes when no override is given. | |
| 52 | +| `dt` | `0.01` | Physics timestep in seconds. | |
| 53 | +| `substeps` | `4` | Genesis substeps per physics step. | |
| 54 | +| `num_envs` | `1` | Parallel-env count. Most managers tensorise across this dimension. | |
| 55 | + |
| 56 | +## InteractiveSceneCfg |
| 57 | + |
| 58 | +| Field | Default | Meaning | |
| 59 | +|---|---|---| |
| 60 | +| `env_spacing` | `(2.0, 2.0)` | Per-env XY offset Genesis applies when `num_envs > 1`. | |
| 61 | +| `sensors` | `()` | Tuple of `SensorCfg` instances built and bound after the scene is constructed. | |
| 62 | +| `mouse_interaction` | `False` | Attach the GeneLab mouse-drag plugin to the viewer (only effective with `vis=True`). | |
| 63 | +| `entities` | `{}` | Additional `ArticulationCfg` / `RigidObjectCfg` keyed by name. The env injects the robot as `"robot"`. | |
| 64 | +| `terrain` | `None` | A `TerrainGeneratorCfg`. Default `None` adds a flat `gs.morphs.Plane`. | |
| 65 | +| `batch_render` | `False` | Pass `gs.renderers.BatchRenderer(use_rasterizer=False)` to `gs.Scene`. Required for `CameraSensor` to emit per-env RGB-D tensors. Linux x86-64 + CUDA only. | |
| 66 | + |
| 67 | +## Articulation |
| 68 | + |
| 69 | +`Articulation` wraps an articulated robot. Construct from `ArticulationCfg` and add |
| 70 | +to the scene via `scene.add_entity("robot", cfg)` (or set |
| 71 | +`ManagerBasedRlEnvCfg.robot` and let the env wire it). |
| 72 | + |
| 73 | +| Field | Type | Meaning | |
| 74 | +|---|---|---| |
| 75 | +| `mjcf_path` | `str` | Absolute path to a MuJoCo XML file. | |
| 76 | +| `init_pos` | `tuple[float, float, float]` | World-frame spawn offset in metres. | |
| 77 | +| `init_quat` | `tuple[float, float, float, float]` | World-frame spawn orientation (wxyz). | |
| 78 | +| `default_joint_pos` | `dict[str, float]` | Regex-keyed default joint position; last match wins. | |
| 79 | +| `actuators` | `dict[str, ActuatorBaseCfg]` | Joint groups. Every actuated joint must be covered by exactly one group. | |
| 80 | +| `foot_link_names` | `tuple[str, ...]` | Optional metadata for downstream MDP terms (e.g. contact sensors). | |
| 81 | + |
| 82 | +The pre-M3 `joint_kp`, `joint_kv`, and dict-shaped `action_scale` knobs are |
| 83 | +removed — configure equivalent groups through `actuators`. Passive joints take an |
| 84 | +explicit zero-gain `ImplicitPDActuatorCfg` so the topology stays visible. |
| 85 | + |
| 86 | +```python |
| 87 | +from genelab.actuator import ImplicitPDActuatorCfg |
| 88 | +from genelab.entity import ArticulationCfg |
| 89 | + |
| 90 | +cartpole_cfg = ArticulationCfg( |
| 91 | + mjcf_path="/path/to/cartpole.xml", |
| 92 | + init_pos=(0.0, 0.0, 1.5), |
| 93 | + default_joint_pos={"cart_slide": 0.0, "pole_hinge": 0.0}, |
| 94 | + actuators={ |
| 95 | + "cart": ImplicitPDActuatorCfg( |
| 96 | + target_names_expr=("cart_slide",), stiffness=80.0, damping=8.0, |
| 97 | + ), |
| 98 | + "pole": ImplicitPDActuatorCfg( |
| 99 | + target_names_expr=("pole_hinge",), stiffness=0.0, damping=0.0, |
| 100 | + ), |
| 101 | + }, |
| 102 | +) |
| 103 | +``` |
| 104 | + |
| 105 | +## RigidObject |
| 106 | + |
| 107 | +`RigidObject` represents a non-articulated body. No actuators, no per-step state — it |
| 108 | +is geometry that participates in collisions and contacts but is never driven. Spawn |
| 109 | +via `scene.add_entity("name", RigidObjectCfg(...))` before `build()`. |
| 110 | + |
| 111 | +| Field | Type | Meaning | |
| 112 | +|---|---|---| |
| 113 | +| `morph` | `Literal["plane", "box", "sphere", "mesh", "mjcf"]` | Genesis morph selector. | |
| 114 | +| `file` | `str \| None` | Path to mesh / MJCF; required for `"mesh"` / `"mjcf"`. | |
| 115 | +| `size` | `tuple[float, ...]` | `(x, y, z)` for box, `(radius,)` for sphere. | |
| 116 | +| `init_pos`, `init_quat` | tuple | World-frame spawn pose (wxyz quat). | |
| 117 | +| `fixed` | `bool` | When `True`, the body is welded to the world frame. | |
| 118 | + |
| 119 | +Typical uses are static obstacles, target markers, and passive payloads. For |
| 120 | +articulated joints — even one — use `Articulation` instead. |
| 121 | + |
| 122 | +## Failure modes worth knowing |
| 123 | + |
| 124 | +!!! warning "`add_entity` after `build`" |
| 125 | + `InteractiveScene.add_entity` raises `RuntimeError` once `build()` has run. |
| 126 | + Construct the env, then add every extra entity, then let the env call `build()`. |
| 127 | + |
| 128 | +!!! warning "Empty `actuators` dict" |
| 129 | + `Articulation.bind` rejects an empty `actuators` dict. Every actuated joint must |
| 130 | + be covered by exactly one group; unmatched or doubly-matched joints raise |
| 131 | + `ValueError` with the conflicting joint names listed. |
| 132 | + |
| 133 | +!!! warning "Genesis backend mismatch" |
| 134 | + `SimulationCfg.gpu=True` requires a working CUDA install. On macOS or CPU-only |
| 135 | + Linux, leave `gpu=False`; the CPU backend handles single-env play loops fine. |
| 136 | + `batch_render=True` further requires Linux x86-64 + CUDA + Madrona — there is no |
| 137 | + CPU fallback. |
| 138 | + |
| 139 | +!!! note "Viewer and parallel envs" |
| 140 | + `SimulationCfg.vis=True` is only meaningful with `num_envs=1`. The viewer |
| 141 | + renders env 0 regardless, so leave `num_envs` high for training and switch to a |
| 142 | + dedicated `play_env` with `num_envs=1` for visual inspection. |
| 143 | + |
| 144 | +!!! tip "Viewer closed mid-rollout" |
| 145 | + When the user closes the Genesis viewer, the kernel catches |
| 146 | + `GenesisException("Viewer closed.")` inside `InteractiveScene.step` and flips |
| 147 | + `env.viewer_closed = True`. Subsequent `env.step` calls become no-ops. Loop |
| 148 | + drivers should poll the flag instead of writing their own try / except: |
| 149 | + ```python |
| 150 | + for step in range(max_steps): |
| 151 | + obs, *_ = env.step(action) |
| 152 | + if env.viewer_closed: |
| 153 | + break |
| 154 | + ``` |
| 155 | + |
| 156 | +## See also |
| 157 | + |
| 158 | +- [Configs](configs.md) |
| 159 | +- [Actuators](actuators.md) |
| 160 | +- [Asset zoo](asset_zoo.md) |
0 commit comments