Skip to content

Introduce runtime scene packages for MuJoCo and Rerun#2594

Merged
Nabla7 merged 21 commits into
mainfrom
pim/feat/runtime-scene-loading
Jun 30, 2026
Merged

Introduce runtime scene packages for MuJoCo and Rerun#2594
Nabla7 merged 21 commits into
mainfrom
pim/feat/runtime-scene-loading

Conversation

@Nabla7

@Nabla7 Nabla7 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

DimOS has no clean runtime concept for a simulated environment.

Until now, a "scene" was usually a loose set of backend-specific files: MuJoCo XML, meshes, browser assets, object metadata, and local path assumptions. That makes scenes hard to reuse across simulation, visualization, navigation, and future renderers.

This PR introduces scene packages: cooked environments with one metadata file and a stable runtime contract.

Scene Packages

A scene package is a directory with scene.meta.json plus backend-specific artifacts.

The metadata describes what the package contains and where each consumer should look. A package can include:

  • MuJoCo XML for physics
  • optional MuJoCo .mjb binaries for faster loading of large scenes
  • visual assets for Rerun or browser viewers
  • future visual assets such as Gaussian splats
  • static environment geometry
  • dynamic or separately spawned entities
  • object prototypes reused by many instances
  • coordinate-frame and scale alignment metadata

The core rule is: runtime code resolves a scene package once, then asks the package for the artifact it needs. Consumers should not hardcode scene-specific folders.

Runtime API

Scene inputs are resolved through the catalog/loader:

from dimos.simulation.scenes.catalog import resolve_scene_package

package = resolve_scene_package(global_config.scene)

Supported inputs:

  • None / --scene none
  • named packages such as office or supermarket
  • path to scene.meta.json
  • path to a scene package directory
  • path to a precompiled MuJoCo .mjb in consumers that support direct binary loading

Once resolved, consumers use the package object instead of knowing folder layout:

if package is not None:
    mujoco_xml = package.mujoco_scene_path
    rerun_visual = package.browser_visual_path("rerun")
    browser_collision = package.browser_collision_path
    objects_json = package.objects_path
    entities = package.entities
    alignment = package.alignment

For example:

  • MuJoCo loads package.mujoco_scene_path, or a direct .mjb path when a large scene has been precompiled.
  • Rerun asks for package.browser_visual_path("rerun").
  • Future browser renderers can ask for their own target-specific assets.
  • Future splat renderers can be added as another artifact target without changing every consumer.

What This PR Adds

Adds the runtime scene package contract and resolver.

Adds MuJoCo loading for scene packages, including XML scenes, optional .mjb loading, entity metadata, and precomposed G1 demo scenes for large environments.

Adds Rerun support for scene package visuals, G1 URDF robot visualization, raycast lidar, map/costmap/path visualization, and command velocity wiring from the Rerun WASD controls.

Uses unitree-g1-groot-wbc as the first full integration path. The same blueprint can now run with no scene, the office scene, or the supermarket scene.

Ships the required runtime data through LFS:

  • scene_packages.tar.gz: dimos_office, supermarket
  • g1_urdf.tar.gz: G1 URDF and meshes for Rerun visualization

Why This Touches Multiple Systems

The reusable piece is the scene package contract.

The extra plumbing is here because the first consumer is a real robot stack, not a metadata-only test. To prove the package works, G1 needs to spawn in the scene, see it with lidar, build a map, plan through it, show the scene and robot in Rerun, reset cleanly, and respond to velocity commands.

Non-Goals

This PR does not add the scene cooking pipeline.

It also does not solve every large-scene problem. Large scenes may need .mjb artifacts for startup time, and many dynamic objects remain a MuJoCo scaling issue. The package format is designed to support richer dynamic assets, reused prototypes, browser assets, and future render targets such as splats, but this PR focuses on runtime loading and the first end-to-end G1 demo.

How To Test

No scene:

dimos \
  --simulation mujoco \
  --scene none \
  --viewer rerun \
  --rerun-open native \
  --n-workers 12 \
  run unitree-g1-groot-wbc

Office:

dimos \
  --simulation mujoco \
  --scene office \
  --viewer rerun \
  --rerun-open native \
  --n-workers 12 \
  run unitree-g1-groot-wbc

Supermarket:

dimos \
  --simulation mujoco \
  --scene supermarket \
  --viewer rerun \
  --rerun-open native \
  --n-workers 12 \
  run unitree-g1-groot-wbc

Prefer headless MuJoCo with Rerun native for normal testing. mujocosimmodule.headless=false opens the MuJoCo viewer but can run much slower.

Review Guide

Start with:

  • dimos/simulation/scene_assets/spec.py
  • dimos/simulation/scenes/catalog.py

Then review the consumers:

  • MuJoCo scene loading
  • Rerun scene and robot visualization
  • G1 Groot blueprint integration
  • LFS package updates

Checks

Focused checks run locally across the branch included ruff, py_compile, mypy on touched Rerun scene code, commit hooks, LFS checks, and runtime smoke testing through the G1 MuJoCo/Rerun commands above.

Contributor License Agreement

  • I have read and approved the CLA.

@Nabla7 Nabla7 mentioned this pull request Jun 25, 2026
1 task
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2375 1 2374 74
View the full list of 1 ❄️ flaky test(s)
dimos.e2e_tests.test_dimsim_spatial_memory::test_go_to_the_bed

Flake rate in main: 20.00% (Passed 48 times, Failed 12 times)

Stack Traces | 566s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7b3b098c4ef0>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7b3b06fbdbc0>
human_input = <function human_input.<locals>.send_human_input at 0x7b3b06fbdc60>
dim_sim = <dimos.e2e_tests.dim_sim_client.DimSimClient object at 0x7b3b1c00a480>
explore_house = <function explore_house.<locals>.explore at 0x7b3b06fbe160>

    @pytest.mark.self_hosted_large
    def test_go_to_the_bed(lcm_spy, start_blueprint, human_input, dim_sim, explore_house) -> None:
        start_blueprint(
            "run",
            "unitree-go2-agentic",
            simulator="dimsim",
        )
        lcm_spy.save_topic(".../McpClient/on_system_modules/res")
        lcm_spy.wait_for_saved_topic(".../McpClient/on_system_modules/res", timeout=1200.0)
    
        explore_house()
    
        human_input("go to the bed")
    
>       lcm_spy.wait_until_odom_position(-3.567, -1.332, threshold=2, timeout=180)

dim_sim    = <dimos.e2e_tests.dim_sim_client.DimSimClient object at 0x7b3b1c00a480>
explore_house = <function explore_house.<locals>.explore at 0x7b3b06fbe160>
human_input = <function human_input.<locals>.send_human_input at 0x7b3b06fbdc60>
lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7b3b098c4ef0>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7b3b06fbdbc0>

dimos/e2e_tests/test_dimsim_spatial_memory.py:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/e2e_tests/lcm_spy.py:182: in wait_until_odom_position
    self.wait_for_message_result(
        predicate  = <function LcmSpy.wait_until_odom_position.<locals>.predicate at 0x7b3b06fbe340>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7b3b098c4ef0>
        threshold  = 2
        timeout    = 180
        x          = -3.567
        y          = -1.332
dimos/e2e_tests/lcm_spy.py:168: in wait_for_message_result
    self.wait_until(
        event      = <threading.Event at 0x7b3b0a2884d0: unset>
        fail_message = 'Failed to get to position x=-3.567, y=-1.332'
        listener   = <function LcmSpy.wait_for_message_result.<locals>.listener at 0x7b3b06fbe200>
        predicate  = <function LcmSpy.wait_until_odom_position.<locals>.predicate at 0x7b3b06fbe340>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7b3b098c4ef0>
        timeout    = 180
        topic      = '/odom#geometry_msgs.PoseStamped'
        type       = <class 'dimos.msgs.geometry_msgs.PoseStamped.PoseStamped'>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7b3b098c4ef0>

    def wait_until(
        self,
        *,
        condition: Callable[[], bool],
        timeout: float,
        error_message: str,
        poll_interval: float = 0.1,
    ) -> None:
        start_time = time.time()
        while time.time() - start_time < timeout:
            if condition():
                return
            time.sleep(poll_interval)
>       raise TimeoutError(error_message)
E       TimeoutError: Failed to get to position x=-3.567, y=-1.332

condition  = <bound method Event.is_set of <threading.Event at 0x7b3b0a2884d0: unset>>
error_message = 'Failed to get to position x=-3.567, y=-1.332'
poll_interval = 0.1
self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7b3b098c4ef0>
start_time = 1782850206.2148845
timeout    = 180

dimos/e2e_tests/lcm_spy.py:105: TimeoutError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@Nabla7 Nabla7 changed the title Runtime scene package loading for G1 MuJoCo Introduce runtime scene packages for MuJoCo and Rerun Jun 26, 2026
@Nabla7 Nabla7 added the backport:skip Skip creating a backport to any release branches label Jun 26, 2026 — with ChatGPT Codex Connector
@Nabla7 Nabla7 marked this pull request as ready for review June 26, 2026 22:39
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 26, 2026
@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Introduces a ScenePackage runtime contract with a scene.meta.json metadata file, a named-scene catalog resolver, composed MuJoCo model construction (scene XML + robot MJCF + entity injection + penetration audit), MuJoCo-native raycast lidar, and generic URDF/Rerun visualization — all wired into the G1 Groot WBC blueprint as the first end-to-end consumer.

  • spec.py / catalog.py: New ScenePackage dataclass with multi-target browser visuals, composed-binary metadata, entity path rewriting, and artifact-frame validation; named catalog resolves office/supermarket aliases, validates both MuJoCo and Rerun artifacts, and raises early with the package path on failure.
  • mujoco_engine.py / mujoco_sim_module.py: MJB binary loading, spawn-pose override, multi-waiter reset (replaces single threading.Event), mj_multiRay raycast lidar with robot-exclusion, respawn_at RPC with ground-height query, and _compose_model pipeline that attaches robot via MjSpec.attach and audits penetrating entities.
  • urdf_robot.py / scene_package.py / bridge.py: URDF static-mesh and joint-state Rerun factories, scene GLB visual factory with alignment transform, is_rerun_multi dispatch in _log_static, and message-timestamp propagation to Rerun timeline.

Confidence Score: 5/5

Safe to merge; the threading and scene-lookup regressions flagged in earlier reviews are addressed, and the new runtime paths are well-guarded against missing files and bad physics state.

The scene-package contract, composed-model pipeline, and raycast lidar are all implemented defensively: missing artifacts raise early with clear messages, penetrating entities are re-welded rather than ejected, and the multi-waiter reset correctly releases all blocked callers. No data-loss or correctness regressions were found in the changed paths.

dimos/visualization/rerun/urdf_robot.py uses Rerun internal Arrow APIs that may break under future Rerun upgrades. dimos/simulation/engines/mujoco_engine.py lidar height filter silently assumes horizontal camera orientation.

Important Files Changed

Filename Overview
dimos/simulation/scene_assets/spec.py New core ScenePackage dataclass and load_scene_package deserializer with per-artifact frame validation, multi-target browser visuals, composed binary metadata, and entity path rewriting. Well-structured and thoroughly defensive.
dimos/simulation/scenes/catalog.py Scene name resolver with aliases and artifact validation; validation now enforces both MuJoCo and Rerun artifacts for every named package, but the error message omits which artifact is absent.
dimos/simulation/engines/mujoco_engine.py Adds MJB loading, spawn-pose override, multi-wait reset mechanism (regression test added), raycast lidar via mj_multiRay, and thread-safe get_root_pose; the max_height lidar filter uses camera-Y rather than world-Z.
dimos/simulation/engines/mujoco_sim_module.py Adds composed-model startup path (MjSpec attach + entity injection + penetration audit), reset/respawn_at RPC endpoints, MuJoCo-native lidar pointcloud mode, and SHM key derivation from robot MJCF.
dimos/simulation/mujoco/entity_scene.py New runtime entity composer: resolves collision shapes (primitive/CoACD hulls/AABB fallback), attaches freejoint for dynamic entities, and audits spawn penetration — all with clear fallback warnings.
dimos/visualization/rerun/urdf_robot.py Generic URDF-to-Rerun bridge: static mesh logging and animated joint transforms. _rerun_transform_without_frames extracts values via Rerun's internal .as_arrow_array() API which may break under future Rerun releases.
dimos/visualization/rerun/scene_package.py Rerun scene visual factory and MJB-to-package resolver; walks to filesystem root when resolving a .mjb path, which could pick up an unrelated scene.meta.json in a higher ancestor directory.
dimos/robot/unitree/g1/blueprints/basic/unitree_g1_groot_wbc.py Wires scene-package selection, precompiled MJB lookup by metadata key, nav stack, URDF/costmap Rerun visualization, and cmd_vel topic routing into the G1 blueprint.
dimos/visualization/rerun/bridge.py Adds _set_rerun_message_time for sensor timestamp propagation, broadens static-entity type to Any to accommodate multi-entity factories, and adds is_rerun_multi dispatch in _log_static.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as dimos CLI
    participant Catalog as scenes/catalog.py
    participant Spec as scene_assets/spec.py
    participant SimModule as MujocoSimModule
    participant Engine as MujocoEngine
    participant EntityScene as entity_scene.py
    participant Rerun as RerunBridgeModule
    participant ScenePkg as rerun/scene_package.py

    CLI->>Catalog: resolve_scene_package(name)
    Catalog->>Spec: load_scene_package(meta.json)
    Spec-->>Catalog: ScenePackage
    Catalog->>Catalog: _validate_package_artifacts()
    Catalog-->>CLI: ScenePackage

    CLI->>SimModule: MujocoSimModule.blueprint(scene_xml, robot_mjcf, entities)
    SimModule->>SimModule: _compose_model()
    SimModule->>EntityScene: add_entities_to_spec(spec, entities)
    EntityScene-->>SimModule: spec with entity bodies
    SimModule->>SimModule: spec.compile() to MjModel
    SimModule->>EntityScene: spawn_penetrators(model)
    EntityScene-->>SimModule: frozenset[penetrator_ids]
    SimModule->>EntityScene: "add_entities_to_spec(fresh_spec, force_static=penetrators)"
    SimModule->>Engine: "MujocoEngine(model=compiled_model)"
    Engine->>Engine: _apply_spawn_pose_unlocked()

    loop sim thread
        Engine->>Engine: mj_step()
        Engine->>Engine: _raycast_lidars() via mj_multiRay
        Engine->>Engine: _render_cameras()
    end

    CLI->>ScenePkg: scene_package_static_entities(scene)
    ScenePkg->>Catalog: resolve_scene_package(scene)
    ScenePkg-->>Rerun: entity_path to SceneVisualFactory
    Rerun->>Rerun: _log_static() logs Transform3D and Asset3D as static

    Engine-->>SimModule: RaycastLidarFrame(points)
    SimModule->>SimModule: _generate_mujoco_lidar_pointcloud()
    SimModule->>Rerun: pointcloud topic to world-frame PointCloud2
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI as dimos CLI
    participant Catalog as scenes/catalog.py
    participant Spec as scene_assets/spec.py
    participant SimModule as MujocoSimModule
    participant Engine as MujocoEngine
    participant EntityScene as entity_scene.py
    participant Rerun as RerunBridgeModule
    participant ScenePkg as rerun/scene_package.py

    CLI->>Catalog: resolve_scene_package(name)
    Catalog->>Spec: load_scene_package(meta.json)
    Spec-->>Catalog: ScenePackage
    Catalog->>Catalog: _validate_package_artifacts()
    Catalog-->>CLI: ScenePackage

    CLI->>SimModule: MujocoSimModule.blueprint(scene_xml, robot_mjcf, entities)
    SimModule->>SimModule: _compose_model()
    SimModule->>EntityScene: add_entities_to_spec(spec, entities)
    EntityScene-->>SimModule: spec with entity bodies
    SimModule->>SimModule: spec.compile() to MjModel
    SimModule->>EntityScene: spawn_penetrators(model)
    EntityScene-->>SimModule: frozenset[penetrator_ids]
    SimModule->>EntityScene: "add_entities_to_spec(fresh_spec, force_static=penetrators)"
    SimModule->>Engine: "MujocoEngine(model=compiled_model)"
    Engine->>Engine: _apply_spawn_pose_unlocked()

    loop sim thread
        Engine->>Engine: mj_step()
        Engine->>Engine: _raycast_lidars() via mj_multiRay
        Engine->>Engine: _render_cameras()
    end

    CLI->>ScenePkg: scene_package_static_entities(scene)
    ScenePkg->>Catalog: resolve_scene_package(scene)
    ScenePkg-->>Rerun: entity_path to SceneVisualFactory
    Rerun->>Rerun: _log_static() logs Transform3D and Asset3D as static

    Engine-->>SimModule: RaycastLidarFrame(points)
    SimModule->>SimModule: _generate_mujoco_lidar_pointcloud()
    SimModule->>Rerun: pointcloud topic to world-frame PointCloud2
Loading

Reviews (6): Last reviewed commit: "Merge branch 'main' into pim/feat/runtim..." | Re-trigger Greptile

Comment thread dimos/simulation/engines/mujoco_engine.py Outdated
Comment thread dimos/simulation/scenes/catalog.py Outdated
Comment thread dimos/robot/unitree/g1/blueprints/basic/unitree_g1_groot_wbc.py
Comment thread dimos/simulation/scenes/catalog.py
Comment thread dimos/visualization/rerun/urdf_robot.py Outdated
Comment thread dimos/visualization/rerun/scene_package.py Outdated
Comment thread dimos/control/tasks/g1_groot_wbc_task/test_g1_groot_wbc_task.py
Zero callers in the repo; the runtime path is
MujocoSimModule._compose_model, which calls add_entities_to_spec
directly on the MjSpec. Drop the now-unused ScenePackage import.
Comment thread dimos/hardware/whole_body/registry.py
Comment thread dimos/core/global_config.py Outdated
Add reset_runtime_state to the ControlTask Protocol with a no-op
default on BaseControlTask, so the coordinator can call it directly
instead of probing each task with inspect.signature. All concrete
tasks inherit BaseControlTask, so the direct call is uniform. Drops
the now-unused import inspect.
The MujocoEngine in test_engine_request_reset_to_applies_pose_in_sim_loop
was torn down via a hand-rolled try/finally. Move creation and
disconnect into a fixture so teardown runs even on failure.
Comment thread dimos/robot/unitree/g1/blueprints/basic/unitree_g1_groot_wbc.py Outdated
Comment thread dimos/simulation/engines/test_mujoco_sim_module.py Outdated
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Jun 30, 2026
@mintlify

mintlify Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jun 30, 2026, 6:49 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 30, 2026
@Nabla7 Nabla7 merged commit 9c7ff54 into main Jun 30, 2026
27 of 30 checks passed
@Nabla7 Nabla7 deleted the pim/feat/runtime-scene-loading branch June 30, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip Skip creating a backport to any release branches PlzReview ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants