Skip to content

Add heterogeneous clone scene composition#6529

Open
ooctipus wants to merge 8 commits into
isaac-sim:developfrom
ooctipus:heterogeneous_cloner
Open

Add heterogeneous clone scene composition#6529
ooctipus wants to merge 8 commits into
isaac-sim:developfrom
ooctipus:heterogeneous_cloner

Conversation

@ooctipus

@ooctipus ooctipus commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Important

Depends on #6534 (task scene-binding normalization). Merge #6534 first — this PR's task-side diffs assume the canonical {ENV_REGEX_NS} bindings introduced there.

Summary

  • Added heterogeneous clone-plan support so one InteractiveScene can instantiate different task asset sets across different environment rows.
  • Added scene_add composition utilities for folding registered task scene configs into a single declarative scene with clone combinations.
  • Added scripts/demos/multitask_clone_scene.py, which composes supported task scenes, skips global floor/light assets, and runs the resulting scene directly without task envs or managers.
  • Converted env-local static task props such as tables, stands, and static scene props from plain AssetBaseCfg to kinematic RigidObjectCfg, so they participate in normal physics/USD replication instead of needing scene-level special
Screenshot from 2026-07-14 19-56-19 handling.

Motivation

This PR is meant to support multitask scene demos where each cloned environment may contain a different robot/task asset bundle. The previous clone flow assumed every env received the same source assets, which made it hard to build one scene from multiple existing task configs without manually rewriting each task into a shared schema.

The composition path keeps the existing task scene configs as the source of truth. It reads their declarative scene fields, deduplicates equivalent asset slots, and emits clone-combination rows that the cloner can use to populate only the assets needed by each task group.

Design Notes

  • scene_add only composes env-scoped, spawned, physics-backed assets rooted at literal {ENV_REGEX_NS}/Leaf paths. Global floors/lights should be skipped during composition and added once to the composed scene.
  • Plain env-scoped AssetBaseCfg is rejected in scene_add; env-local props that need per-env instances should be modeled as RigidObjectCfg or ArticulationCfg instead.
  • Static props that should not move are represented as kinematic rigid objects, which keeps them fixed while letting the existing backend replication paths handle them.
  • The demo filters out scenes outside its current scope, including Newton-only scenes and scenes without a level-0 declarative flat floor.

Test Plan

  • ./isaaclab.sh -p -m py_compile source/isaaclab/isaaclab/scene/interactive_scene.py source/isaaclab/isaaclab/scene/scene_composition.py source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_soft_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/core/reach/reach_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/stack/stack_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/stack/stack_instance_randomize_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/deploy/reach/reach_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/deploy/gear_assembly/gear_assembly_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/openarm/lift_openarm_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/reach/config/openarm/unimanual/reach_openarm_uni_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cloth_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/assemble_trocar/g129_dex3_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/place/config/agibot/place_toy2box_rmp_rel_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/place/config/agibot/place_upright_mug_rmp_rel_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/pick_place/nutpour_gr1t2_base_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/pick_place/exhaustpipe_gr1t2_base_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/locomanip_pick_place/locomanipulation_g1_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/locomanip_pick_place/fixed_base_upper_body_ik_g1_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/pick_place/pickplace_gr1t2_env_cfg.py source/isaaclab_tasks/isaaclab_tasks/contrib/pick_place/pickplace_unitree_g1_inspire_hand_env_cfg.py
  • Searched for remaining env-scoped plain table/stand/scene/cube AssetBaseCfg assignments after converting static props to RigidObjectCfg.

@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Jul 15, 2026
@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces heterogeneous clone-scene composition: a new scene_add utility folds multiple declarative task scene configs into a single InteractiveScene where each cloned env group receives only its task's assets. It also converts env-local static props (tables, stands) from plain AssetBaseCfg to kinematic RigidObjectCfg so they participate in the normal physics replication path, and adds a demo script that composes all registered task scenes in one simulation.

  • scene_add / InclusionSet: new scene_composition.py + clone_combinations field on CloneCfg + make_valid_clone_combinations in cloner_utils.py implement weighted round-robin tensor construction for heterogeneous prototype assignment.
  • AssetBaseCfgRigidObjectCfg migration: tables and similar static scene props are converted to kinematic rigid objects across ~15 task env configs, making them eligible for clone-plan replication — but also registering them in scene.rigid_objects, which any MDP term sweeping that dict will now see.
  • multitask_clone_scene.py: demo that resolves every registered task scene, filters Newton/floor-level mismatches, and steps physics in a fully composed scene without task environments.

Confidence Score: 3/5

Two real concerns need resolution before merging: the mutable-list default on CloneCfg.clone_combinations (and the parallel clone_cfg: CloneCfg = CloneCfg() field on InteractiveSceneCfg) may silently share state across scene configs if @configclass doesn't factory-wrap these fields, and the broad conversion of static props from AssetBaseCfg to RigidObjectCfg exposes tables and stands in scene.rigid_objects, which MDP reward/observation terms that sweep that dict will now see unexpectedly.

The composition engine itself (scene_add, make_valid_clone_combinations, make_clone_plan) is carefully designed and the tensor alignment between _collect_asset_cfgs and make_clone_plan is correct. The bigger risks are the AssetBaseCfg→RigidObjectCfg migration (wide blast radius across ~15 task configs, any of which could have downstream MDP breakage) and the mutable default list on CloneCfg (cross-instance pollution in the same process). The dynamically-created _AddedSceneCfg class also can't be pickled, which blocks serialisation use cases.

source/isaaclab/isaaclab/cloner/cloner_cfg.py (mutable default), source/isaaclab/isaaclab/scene/interactive_scene_cfg.py (shared CloneCfg default), and all task env configs that convert a static prop to RigidObjectCfg — particularly any that have reward/observation terms iterating scene.rigid_objects.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/scene/scene_composition.py New file: scene_add utility and helpers for heterogeneous scene composition. Correct deduplication and slot-matching logic; dynamically-created _AddedSceneCfg class is not picklable and blocks serialisation use cases.
source/isaaclab/isaaclab/cloner/cloner_cfg.py Adds InclusionSet configclass and clone_combinations: list[InclusionSet] = [] to CloneCfg; bare mutable list default may be shared across instances if @configclass doesn't apply default_factory.
source/isaaclab/isaaclab/cloner/cloner_utils.py Adds make_valid_clone_combinations (weighted round-robin tensor builder) and extends make_clone_plan for heterogeneous valid-sets with inactive (-1) entries. pxr import lazified correctly. Empty InclusionSet(assets=[]) is silently accepted.
source/isaaclab/isaaclab/scene/interactive_scene.py Passes clone_cfg from scene config into InteractiveScene, exposes env_ns/env_regex_ns properties, and feeds computed _clone_valid_set into ReplicateSession. Change looks correct; _collect_asset_cfgs correctly deduplicates variant tracking for collection children.
source/isaaclab/isaaclab/scene/interactive_scene_cfg.py Adds clone_cfg: CloneCfg = CloneCfg() to InteractiveSceneCfg; if @configclass doesn't factory-wrap this field, all subclasses share one CloneCfg instance.
scripts/demos/multitask_clone_scene.py New demo script: resolves all registered task scenes, filters by floor level and Newton physics, composes with scene_add, then steps physics. Logic is sound; requires at least two supported task scenes or raises a clear error.
source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py Converts table from AssetBaseCfg to RigidObjectCfg(kinematic_enabled=True). Table is now registered in scene.rigid_objects, potentially breaking MDP terms that iterate all rigid objects.
source/isaaclab_tasks/isaaclab_tasks/core/reach/reach_env_cfg.py Converts table in TableCfg.physx from AssetBaseCfg to RigidObjectCfg and updates prim path to use {ENV_REGEX_NS} macro. Same rigid_objects registration concern as lift_env_cfg.
source/isaaclab_tasks/isaaclab_tasks/contrib/stack/stack_env_cfg.py Converts table from AssetBaseCfg to RigidObjectCfg; same scene.rigid_objects exposure concern.
source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/joint_pos_env_cfg.py Moves robot and ee_frame scene assignments from __post_init__ into a new FrankaCabinetSceneCfg dataclass, making the scene fully declarative. Refactor is clean.

Comments Outside Diff (2)

  1. source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py, line 43-56 (link)

    P1 Table now registered as a RigidObject — visible to MDP managers

    Converting table (and similar static props across many task configs) from AssetBaseCfg to RigidObjectCfg registers the asset in scene.rigid_objects["table"]. Any reward/observation term that iterates scene.rigid_objects (e.g., collision-checking helpers, object-tracking terms) will now encounter the table as a tracked physics body. Code that previously assumed only manipulable objects live in rigid_objects may return wrong observations or fire incorrect reward signals.

    The same pattern is repeated in reach_env_cfg.py, stack_env_cfg.py, and a dozen other task configs in this PR; the impact is broadest anywhere a manager sweeps all rigid objects by name pattern.

  2. source/isaaclab/isaaclab/cloner/cloner_cfg.py, line 34-41 (link)

    P1 Mutable list default shared across CloneCfg instances

    clone_combinations: list[InclusionSet] = [] is a bare mutable list at class scope. If isaaclab's @configclass doesn't wrap this with dataclasses.field(default_factory=list) under the hood, every CloneCfg() instantiation returns the same underlying list object. Appending to scene_cfg.clone_cfg.clone_combinations on one scene would silently pollute the default shared by all other unmodified scenes in the same process. This is especially risky in the demo script, which resolves many task configs in the same Python session before Kit launches.

Reviews (1): Last reviewed commit: "pr" | Re-trigger Greptile

Comment on lines +260 to +270
if len(asset_names) != len(variant_counts):
raise ValueError(f"Expected one variant count per asset, got {len(variant_counts)} and {len(asset_names)}.")
if not asset_names:
raise ValueError("Expected at least one asset name.")
if any(count <= 0 for count in variant_counts):
raise ValueError("Variant counts must be positive.")

if not clone_combinations:
rows = itertools.product(*[range(count) for count in variant_counts])
return torch.tensor(list(rows), dtype=torch.long, device=device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty InclusionSet(assets=[]) silently produces all-inactive rows

make_valid_clone_combinations does not validate that each InclusionSet.assets list is non-empty. An entry like InclusionSet(assets=[]) is accepted: combination_assets for that row becomes an empty intersection, and the loop marks every claimed asset as inactive, producing a valid-looking tensor row of all (-1,) entries. The result is a set of environments that receive none of the task-specific assets — an empty env that is almost certainly unintended and produces no validation error.

Consider adding if not combination.assets: raise ValueError(...) before the intersection step.

Comment on lines +163 to +178
def _make_scene(
source: InteractiveSceneCfg,
entities: dict[str, AssetBaseCfg],
slots: dict[str, str],
rows: list[InclusionSet],
) -> InteractiveSceneCfg:
"""Build a configclass that InteractiveScene can parse normally."""
clone_cfg = copy.deepcopy(source.clone_cfg)
clone_cfg.clone_combinations = rows
values = {name: copy.deepcopy(getattr(source, name)) for name in InteractiveSceneCfg.__dataclass_fields__}
values["clone_cfg"] = clone_cfg
namespace = {"__module__": __name__, "__doc__": "Scene configuration produced by scene_add.", **entities}
scene_type = configclass(type("_AddedSceneCfg", (InteractiveSceneCfg,), namespace))
scene_cfg = scene_type(**values)
setattr(scene_type, _SLOT_METADATA, slots)
return scene_cfg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dynamically-created _AddedSceneCfg class is not picklable

type("_AddedSceneCfg", (InteractiveSceneCfg,), namespace) creates a fresh class on every scene_add call. Even though __module__ is set to scene_composition.__name__, the class is not actually importable from that module — pickle looks up _AddedSceneCfg in isaaclab.scene.scene_composition at deserialisation time and raises AttributeError. Any code path that tries to serialise a composed scene config (e.g., checkpointing, Ray workers, multiprocessing spawn mode) will fail silently or with a confusing error. Consider registering the produced type in the module dict (sys.modules[__name__]._AddedSceneCfg = scene_type) or documenting this constraint in the docstring.

@ooctipus
ooctipus force-pushed the heterogeneous_cloner branch from d9819f7 to 4d8df66 Compare July 15, 2026 08:10
@ooctipus
ooctipus requested a review from xyao-nv as a code owner July 15, 2026 08:10
@github-actions github-actions Bot added the isaac-mimic Related to Isaac Mimic team label Jul 15, 2026
@ooctipus
ooctipus force-pushed the heterogeneous_cloner branch from 4d8df66 to 78b2505 Compare July 15, 2026 08:16
ooctipus added a commit that referenced this pull request Jul 15, 2026
# Description

Replaces expanded `/World/envs/env_.*` prim paths with the canonical
`{ENV_REGEX_NS}` macro across task scene configurations, declares the
Franka Cabinet robot and end-effector frame in the scene configuration
instead of mutating them after construction, and aligns the Reach and
OpenArm Lift table bindings and initial states.

These are behavior-preserving config normalizations (`{ENV_REGEX_NS}`
expands to the same regex at scene construction). They are split out of
#6529, which composes task scenes and requires the canonical
literal-binding form.

## Type of change

- This change requires a documentation update (changelog fragment
included)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have added a changelog fragment under
`source/<pkg>/changelog.d/`
- [x] My name is in the `CONTRIBUTORS.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@ooctipus
ooctipus force-pushed the heterogeneous_cloner branch from a6e5a33 to 16054a6 Compare July 15, 2026 18:37
@github-actions github-actions Bot added documentation Improvements or additions to documentation isaac-sim Related to Isaac Sim team labels Jul 15, 2026
@vidurv-nvidia

Copy link
Copy Markdown

This PR is missing scripts/demos/multitask_clone_scene.py which is reported in the PR description!

ooctipus added 7 commits July 17, 2026 16:22
Enabling fabric at runtime (physx_manager._load_fabric) re-solves the
full extension graph, which fails on builds where an enabled extension
has locally-unresolvable dependencies. Loading it with the app avoids
the re-solve entirely.
Add a showroom entry and screenshot for the heterogeneous scene demo,
and fix the demo docstring usage examples that still referenced the
old multitask_clone_scene.py path.
- test_collect_asset_cfgs_resolves_env_regex_macros: the test bypasses
  __init__ via object.__new__, so _env_ns was never set; populate
  _env_regex_ns and _env_ns from CloneCfg to match what __init__ does

- test_usd_replicate_self_copy_skips_copy_spec: test patched
  isaaclab.cloner.cloner_utils.Sdf, but Sdf is imported only in
  isaaclab.cloner.usd; fix the import target

- test_resolve_clone_plan_source_partial_coverage_raises: the PR
  updated resolve_clone_plan_source to allow partial-env coverage
  (heterogeneous scenes); update the test to assert the return value
  instead of expecting NotImplementedError
The kitless-factories test forbids isaaclab.assets during config
construction, but the scene composition helpers imported AssetBaseCfg
at module scope. Defer the import to the composition function that
needs it for isinstance checks; annotations resolve lazily.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team isaac-sim Related to Isaac Sim team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants