Skip to content

Commit 05c50e1

Browse files
committed
update change logs
1 parent ba5c119 commit 05c50e1

File tree

9 files changed

+90
-41
lines changed

9 files changed

+90
-41
lines changed

scripts/demos/multi.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene):
228228
# Extract scene entities
229229
# note: we only do this here for readability.
230230
rigid_object: RigidObject | None = scene["object"] if "object" in scene.keys() else None
231-
rigid_object_collection: RigidObjectCollection | None = scene["object_collection"] if "object_collection" in scene.keys() else None
231+
rigid_object_collection: RigidObjectCollection | None = (
232+
scene["object_collection"] if "object_collection" in scene.keys() else None
233+
)
232234
robot: Articulation | None = scene["robot"] if "robot" in scene.keys() else None
233235
# Define simulation stepping
234236
sim_dt = sim.get_physics_dt()

source/isaaclab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.47.7"
4+
version = "0.48.0"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
11
Changelog
22
---------
33

4+
0.48.0 (2025-11-04)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Changed
8+
^^^^^^^
9+
10+
* Removed hard dependency on the Isaac Sim Cloner for scene replication. Replication now uses internal utilities
11+
:func:`~isaaclab.scene.cloner.usd_replicate` and :func:`~isaaclab.scene.cloner.physx_replicate`, reducing coupling
12+
to Isaac Sim. Public APIs in :class:`~isaaclab.scene.interactive_scene.InteractiveScene` remain unchanged; code
13+
directly importing the external Cloner should migrate to these utilities.
14+
15+
16+
Added
17+
^^^^^
18+
19+
* Added optional random prototype selection during environment cloning in
20+
:class:`~isaaclab.scene.interactive_scene.InteractiveScene` via
21+
:attr:`~isaaclab.scene.interactive_scene_cfg.InteractiveSceneCfg.random_heterogenous_cloning`.
22+
Defaults to ``True``; round-robin (modulo) mapping remains available by setting it to ``False``.
23+
24+
* Added flexible per-object cloning path in
25+
:class:`~isaaclab.scene.interactive_scene.InteractiveScene`: when environments are heterogeneous
26+
(different prototypes across envs), replication switches to per-object instead of whole-env cloning.
27+
This reduces PhysX cloning time in heterogeneous scenes.
28+
29+
30+
Deprecated
31+
^^^^^^^^^^
32+
33+
* Deprecated :attr:`~isaaclab.sim.spawners.wrappers.MultiAssetSpawnerCfg.random_choice` and
34+
:attr:`~isaaclab.sim.spawners.wrappers.MultiUsdFileCfg.random_choice`. Use
35+
:attr:`~isaaclab.scene.interactive_scene_cfg.InteractiveSceneCfg.random_heterogenous_cloning` to control whether
36+
assets are selected randomly (``True``) or via round-robin (``False``) across environments.
37+
38+
439
0.47.7 (2025-10-31)
540
~~~~~~~~~~~~~~~~~~~
641

source/isaaclab/isaaclab/scene/cloner.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
from pxr import Usd, UsdUtils, Sdf, UsdGeom, PhysxSchema, Gf, Vt
2-
from omni.physx import get_physx_replicator_interface
3-
import torch
1+
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
46
import math
7+
import torch
8+
9+
from omni.physx import get_physx_replicator_interface
10+
from pxr import Gf, PhysxSchema, Sdf, Usd, UsdGeom, UsdUtils, Vt
511

612

713
def usd_replicate(
@@ -86,10 +92,10 @@ def dp_depth(template: str) -> int:
8692

8793
def physx_replicate(
8894
stage: Usd.Stage,
89-
sources: list[str], # e.g. ["/World/Template/A", "/World/Template/B"]
90-
destinations: list[str], # e.g. ["/World/envs/env_{}/Robot", "/World/envs/env_{}/Object"]
91-
env_ids: torch.Tensor, # env_ids
92-
mapping: torch.Tensor, # (num_sources, num_envs) bool; True -> place sources[i] into world=j
95+
sources: list[str], # e.g. ["/World/Template/A", "/World/Template/B"]
96+
destinations: list[str], # e.g. ["/World/envs/env_{}/Robot", "/World/envs/env_{}/Object"]
97+
env_ids: torch.Tensor, # env_ids
98+
mapping: torch.Tensor, # (num_sources, num_envs) bool; True -> place sources[i] into world=j
9399
use_fabric: bool = False,
94100
) -> None:
95101
"""Replicate prims via PhysX replicator with per-row mapping.
@@ -127,7 +133,13 @@ def attach_end_fn(_stage_id: int):
127133
for i, src in enumerate(sources):
128134
current_worlds[:] = env_ids[mapping[i]].tolist()
129135
current_template = destinations[i]
130-
rep.replicate(_stage_id, src, len(current_worlds), useEnvIds=len(current_worlds) == num_envs, useFabricForReplication=use_fabric)
136+
rep.replicate(
137+
_stage_id,
138+
src,
139+
len(current_worlds),
140+
useEnvIds=len(current_worlds) == num_envs,
141+
useFabricForReplication=use_fabric,
142+
)
131143
# unregister only AFTER all replicate() calls completed
132144
rep.unregister_replicator(_stage_id)
133145

@@ -139,7 +151,7 @@ def filter_collisions(
139151
physicsscene_path: str,
140152
collision_root_path: str,
141153
prim_paths: list[str],
142-
global_paths: list[str] = []
154+
global_paths: list[str] = [],
143155
) -> None:
144156
"""Create inverted collision groups for clones.
145157
@@ -165,7 +177,7 @@ def filter_collisions(
165177

166178
# Make sure we create the collision_scope in the RootLayer since the edit target may be a live layer in the case of Live Sync.
167179
with Usd.EditContext(stage, Usd.EditTarget(stage.GetRootLayer())):
168-
collision_scope = UsdGeom.Scope.Define(stage, collision_root_path)
180+
UsdGeom.Scope.Define(stage, collision_root_path)
169181

170182
with Sdf.ChangeBlock():
171183
if len(global_paths) > 0:
@@ -178,9 +190,7 @@ def filter_collisions(
178190
"PhysicsCollisionGroup",
179191
)
180192
# prepend collision API schema
181-
global_collision_group.SetInfo(
182-
Usd.Tokens.apiSchemas, Sdf.TokenListOp.Create({"CollectionAPI:colliders"})
183-
)
193+
global_collision_group.SetInfo(Usd.Tokens.apiSchemas, Sdf.TokenListOp.Create({"CollectionAPI:colliders"}))
184194

185195
# expansion rule
186196
expansion_rule = Sdf.AttributeSpec(

source/isaaclab/isaaclab/scene/interactive_scene.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import omni.log
1111
from isaacsim.core.prims import XFormPrim
1212
from isaacsim.core.utils.stage import get_current_stage
13-
from pxr import Sdf, PhysxSchema
13+
from pxr import PhysxSchema, Sdf
1414

1515
import isaaclab.sim as sim_utils
1616
from isaaclab.assets import (
@@ -31,7 +31,7 @@
3131
from isaaclab.sim.utils import get_current_stage_id
3232
from isaaclab.terrains import TerrainImporter, TerrainImporterCfg
3333

34-
from .cloner import physx_replicate, filter_collisions, usd_replicate, grid_transforms
34+
from .cloner import filter_collisions, grid_transforms, physx_replicate, usd_replicate
3535
from .interactive_scene_cfg import InteractiveSceneCfg
3636

3737

@@ -145,16 +145,18 @@ def __init__(self, cfg: InteractiveSceneCfg):
145145
prim.CreateAttribute("physxScene:envIdInBoundsBitCount", Sdf.ValueTypeNames.Int).Set(4)
146146
self._default_env_origins, _ = grid_transforms(self.cfg.num_envs, self.cfg.env_spacing, device=self.device)
147147
# copy empty prim of env_0 to env_1, env_2, ..., env_{num_envs-1} with correct location.
148-
usd_replicate(self.stage, [self.env_fmt.format(0)], [self.env_fmt], self._ALL_INDICES, positions=self._default_env_origins)
148+
usd_replicate(
149+
self.stage, [self.env_fmt.format(0)], [self.env_fmt], self._ALL_INDICES, positions=self._default_env_origins
150+
)
149151

150152
self._global_prim_paths = list()
151153
if self._is_scene_setup_from_cfg():
152154
clone_plan = {"src": [], "dest": [], "mapping": torch.empty((0,), dtype=torch.bool).to(self.device)}
153155
self._global_template_prim_paths = list() # store paths that are in global collision filter from templates
154156
self._add_entities_from_cfg()
155157
for prim_path in self._global_template_prim_paths:
156-
i = clone_plan['src'].index(prim_path)
157-
self._global_prim_paths.extend(sim_utils.find_matching_prim_paths(clone_plan['dest'][i].format(".*")))
158+
i = clone_plan["src"].index(prim_path)
159+
self._global_prim_paths.extend(sim_utils.find_matching_prim_paths(clone_plan["dest"][i].format(".*")))
158160

159161
self.clone_environments()
160162

@@ -173,9 +175,9 @@ def clone_environments(self, copy_from_source: bool = False):
173175
clone_plan = {"src": [], "dest": [], "mapping": torch.empty((0,), dtype=torch.bool).to(self.device)}
174176
prototypes = sim_utils.get_all_matching_child_prims(
175177
self.template_path,
176-
predicate=lambda prim: str(prim.GetPath()).split("/")[-1].startswith(self.prototype_name)
178+
predicate=lambda prim: str(prim.GetPath()).split("/")[-1].startswith(self.prototype_name),
177179
)
178-
prototype_root_set = set(["/".join(str(prototype.GetPath()).split("/")[:-1]) for prototype in prototypes])
180+
prototype_root_set = {"/".join(str(prototype.GetPath()).split("/")[:-1]) for prototype in prototypes}
179181
for prototype_root in prototype_root_set:
180182
protos = sim_utils.find_matching_prim_paths(f"{prototype_root}/.*")
181183
protos = [proto for proto in protos if proto.split("/")[-1].startswith(self.prototype_name)]
@@ -187,22 +189,22 @@ def clone_environments(self, copy_from_source: bool = False):
187189
else:
188190
m[self._ALL_INDICES % len(protos), self._ALL_INDICES] = True
189191

190-
clone_plan['src'].extend(protos)
191-
clone_plan['dest'].extend([prototype_root.replace(self.template_path, self.env_fmt)] * len(protos))
192-
clone_plan['mapping'] = torch.cat((clone_plan['mapping'].reshape(-1, m.size(1)), m), dim=0)
192+
clone_plan["src"].extend(protos)
193+
clone_plan["dest"].extend([prototype_root.replace(self.template_path, self.env_fmt)] * len(protos))
194+
clone_plan["mapping"] = torch.cat((clone_plan["mapping"].reshape(-1, m.size(1)), m), dim=0)
193195

194-
proto_idx = clone_plan['mapping'].to(torch.int32).argmax(dim=1)
195-
proto_mask = torch.zeros_like(clone_plan['mapping'])
196-
proto_mask.scatter_(1, proto_idx.view(-1, 1).to(torch.long), clone_plan['mapping'].any(dim=1, keepdim=True))
197-
usd_replicate(self.stage, clone_plan['src'], clone_plan['dest'], self._ALL_INDICES, proto_mask)
196+
proto_idx = clone_plan["mapping"].to(torch.int32).argmax(dim=1)
197+
proto_mask = torch.zeros_like(clone_plan["mapping"])
198+
proto_mask.scatter_(1, proto_idx.view(-1, 1).to(torch.long), clone_plan["mapping"].any(dim=1, keepdim=True))
199+
usd_replicate(self.stage, clone_plan["src"], clone_plan["dest"], self._ALL_INDICES, proto_mask)
198200
self.stage.GetPrimAtPath(self.template_path).SetActive(False)
199201

200202
# If all prototypes map to env_0, clone whole env_0 to all envs; else clone per-object
201203
if torch.all(proto_idx == 0):
202-
replicate_args = [self.env_fmt.format(0)], [self.env_fmt], self._ALL_INDICES, clone_plan['mapping']
204+
replicate_args = [self.env_fmt.format(0)], [self.env_fmt], self._ALL_INDICES, clone_plan["mapping"]
203205
else:
204-
src = [tpl.format(int(idx)) for tpl, idx in zip(clone_plan['dest'], proto_idx.tolist())]
205-
replicate_args = src, clone_plan['dest'], self._ALL_INDICES, clone_plan['mapping']
206+
src = [tpl.format(int(idx)) for tpl, idx in zip(clone_plan["dest"], proto_idx.tolist())]
207+
replicate_args = src, clone_plan["dest"], self._ALL_INDICES, clone_plan["mapping"]
206208
else:
207209
mapping = torch.ones((1, self.num_envs), device=self.device, dtype=torch.bool)
208210
replicate_args = [self.env_fmt.format(0)], [self.env_fmt], self._ALL_INDICES, mapping
@@ -677,7 +679,7 @@ def _add_entities_from_cfg(self):
677679
# prepared /World/template path, once all template is ready, cloner can determine what rules to follow
678680
# to combine, and distribute the templates to cloned environments.
679681
destinations_regex_ns = asset_cfg.prim_path.format(ENV_REGEX_NS=self.env_regex_ns)
680-
if (self.env_regex_ns[:-2] in destinations_regex_ns):
682+
if self.env_regex_ns[:-2] in destinations_regex_ns:
681683
require_clone = True
682684
prototype_root = asset_cfg.prim_path.format(ENV_REGEX_NS=self.template_path)
683685
asset_cfg.prim_path = f"{prototype_root}/{self.prototype_name}_.*"

source/isaaclab/isaaclab/sensors/camera/camera.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import json
99
import numpy as np
10-
import re
1110
import torch
1211
from collections.abc import Sequence
1312
from typing import TYPE_CHECKING, Any, Literal

source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@
55

66
from __future__ import annotations
77

8-
import re
9-
import torch
108
from typing import TYPE_CHECKING
119

1210
import isaacsim.core.utils.prims as prim_utils
13-
import isaacsim.core.utils.stage as stage_utils
14-
from isaacsim.core.utils.stage import get_current_stage
1511
from pxr import Usd
1612

1713
from isaaclab.sim.spawners.from_files import UsdFileCfg

source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class MultiAssetSpawnerCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg):
3939
4040
If False, the asset configurations are spawned in the order they are provided in the list.
4141
If True, a random asset configuration is selected for each spawn.
42+
43+
.. warning::
44+
45+
This attribute is deprecated. Use
46+
:attr:`~isaaclab.scene.interactive_scene_cfg.InteractiveSceneCfg.random_heterogenous_cloning` instead.
4247
"""
4348

4449

@@ -64,8 +69,9 @@ class MultiUsdFileCfg(UsdFileCfg):
6469
6570
If False, the asset configurations are spawned in the order they are provided in the list.
6671
If True, a random asset configuration is selected for each spawn.
72+
6773
.. warning::
6874
69-
This attribute is deprecated. Use :attr:`~isaaclab.scene.InteractiveSceneCfg.random_heterogenous_spawning`
70-
instead.
75+
This attribute is deprecated. Use
76+
:attr:`~isaaclab.scene.interactive_scene_cfg.InteractiveSceneCfg.random_heterogenous_cloning` instead.
7177
"""

source/isaaclab/isaaclab/sim/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import functools
1212
import inspect
1313
import re
14-
import torch
1514
from collections.abc import Callable, Generator
1615
from typing import TYPE_CHECKING, Any
1716

0 commit comments

Comments
 (0)