Skip to content

Commit 3d4a82c

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into jichuanh/warp-global-determinism
2 parents 803ce8f + 6b8e133 commit 3d4a82c

251 files changed

Lines changed: 3839 additions & 2795 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ jobs:
346346
isaacsim-base-image: ${{ needs.config.outputs.isaacsim_image_name }}
347347
isaacsim-version: ${{ needs.config.outputs.isaacsim_image_tag }}
348348
filter-pattern: "not isaaclab_"
349+
extra-uv-packages: "opencv-python-headless==4.13.0.90"
349350
shard-index: "0"
350351
shard-count: "3"
351352
warp-cache: restore
@@ -370,6 +371,7 @@ jobs:
370371
isaacsim-base-image: ${{ needs.config.outputs.isaacsim_image_name }}
371372
isaacsim-version: ${{ needs.config.outputs.isaacsim_image_tag }}
372373
filter-pattern: "not isaaclab_"
374+
extra-uv-packages: "opencv-python-headless==4.13.0.90"
373375
shard-index: "1"
374376
shard-count: "3"
375377
warp-cache: restore
@@ -394,6 +396,7 @@ jobs:
394396
isaacsim-base-image: ${{ needs.config.outputs.isaacsim_image_name }}
395397
isaacsim-version: ${{ needs.config.outputs.isaacsim_image_tag }}
396398
filter-pattern: "not isaaclab_"
399+
extra-uv-packages: "opencv-python-headless==4.13.0.90"
397400
shard-index: "2"
398401
shard-count: "3"
399402
warp-cache: restore

.github/workflows/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
# which the CI credential can reach.
1111
isaacsim_image_name: nvcr.io/0947644777160149/internal/isaac-sim
1212
# Isaac Sim 6.1.0-alpha.50 (b86cf6ce) includes Kit 110.3.0-360924's fix for NVBug 6566677.
13-
isaacsim_image_tag: latest-develop@sha256:50d2491d9e04da52bb39f0c8a993fba991d2d13939e88f2008577e00a17c4b03
13+
isaacsim_image_tag: latest-develop@sha256:5633a399fc6a69f3b80a8b497ebbfce209e75877afcdf7f1f62e95f51f5e5288
1414
isaaclab_image_name: nvcr.io/0947644777160149/internal/isaac-lab
1515
ovphysx_wheelhouse_image: ""

docker/test/test_dockerfile_nonroot.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ def test_kitless_dockerfile_installs_newton_rl_ov_and_visualizers_without_isaac_
169169
)
170170

171171

172+
def test_container_test_runner_only_links_an_actual_isaac_sim_runtime():
173+
"""Cache mount points under /isaac-sim must not masquerade as an Isaac Sim installation."""
174+
runner_text = (REPO_ROOT / ".github/actions/run-tests/run_tests.sh").read_text(encoding="utf-8")
175+
guarded_link = re.compile(r"if \[ -x /isaac-sim/python\.sh \]; then\s+ln -s /isaac-sim _isaac_sim;?\s+fi")
176+
177+
assert guarded_link.search(runner_text)
178+
assert runner_text.count("ln -s /isaac-sim _isaac_sim") == 1
179+
180+
172181
# --------------------------------------------------------------------------- #
173182
# Volume mount-point writability
174183
#

docs/source/features/isaac_teleop.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,7 @@ Haptic glove (per-finger grip)
15251525
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15261526

15271527
Use :class:`~isaaclab_teleop.GloveHapticFeedbackCfg`. Each hand's contact sensor is **filtered
1528-
against the grasped object** (``filter_prim_paths_expr``), so ``force_matrix_w`` reports each
1528+
against the grasped object** (``filter_prim_paths_expr``), so ``normal_force_matrix_w`` reports each
15291529
finger's force on that object and nothing else. The driver orders the per-finger magnitudes
15301530
Thumb..Pinky (matched from the sensor's body names via ``finger_order``) into a
15311531
``FingerPowerVector`` rendered by a cross-process glove device on ``collection_id``.

docs/source/features/population_based_training.rst

Lines changed: 181 additions & 125 deletions
Large diffs are not rendered by default.

docs/source/how-to/cloning.rst

Lines changed: 70 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ by hand would be hopelessly slow. Cloning is Isaac Lab's answer: you author a
1111
small representative scene under ``/World/envs/env_n`` and the cloner expands it
1212
across the rest of the env population for you, optionally with per-env variation.
1313

14-
The expansion itself is performed by each physics backend's native replicator —
15-
USD, PhysX, or Newton — wrapped by Isaac Lab's core :mod:`isaaclab.cloner` module
16-
behind a single uniform surface so the same user code works regardless of which
17-
backend is active.
14+
The expansion itself is performed by USD and the active physics backend's native
15+
replicator, wrapped by Isaac Lab's core :mod:`isaaclab.cloner` module behind a
16+
single uniform surface.
1817

1918
.. contents:: On this page
2019
:local:
@@ -24,24 +23,29 @@ backend is active.
2423
The Backend Layer
2524
-----------------
2625

27-
At the bottom of the stack, each backend exposes a single function that takes a
28-
flat description of the world layout and materializes it on its runtime. The
29-
signatures are deliberately parallel so the layers above can target every backend
30-
through one interface:
26+
At the bottom of the stack, each backend exposes a raw function that takes a flat
27+
description of the world layout. These functions are useful for standalone tools
28+
and tests and deliberately have parallel signatures:
3129

3230
.. code-block:: text
3331
34-
backend_replicate(stage, sources, destinations, env_ids, mask, positions=None, quaternions=None, ...)
32+
backend_replicate(stage, sources, destinations, env_ids, selection, positions=None, quaternions=None, ...)
3533
3634
The arguments are parallel arrays describing the layout:
3735

3836
* ``sources`` — source prim paths already authored on the stage.
3937
* ``destinations`` — destination templates containing ``"{}"``, formatted with each env id.
40-
* ``env_ids`` — long tensor of target env indices.
41-
* ``mask`` — bool tensor of shape ``[len(sources), num_envs]``; ``mask[i, j]`` is
42-
``True`` when env ``j`` should be populated from source ``i``.
38+
* ``env_ids`` — NumPy integer array of target env indices.
39+
* ``selection`` — NumPy boolean array of shape ``[len(sources), num_envs]``;
40+
``selection[i, j]`` is ``True`` when env ``j`` should be populated from source ``i``.
41+
The raw USD function names this argument ``mask``; physics functions name it ``mapping``.
4342
* ``positions`` / ``quaternions`` — optional per-env world transforms.
4443

44+
Production scene construction stores those arrays once in a
45+
:class:`~isaaclab.cloner.ClonePlan`. Simulation-owned backend contexts consume the
46+
same value through ``context.replicate(plan)``; no backend rebuilds the mapping
47+
from a second queue of array arguments.
48+
4549

4650
Standalone Examples
4751
~~~~~~~~~~~~~~~~~~~
@@ -54,7 +58,7 @@ control. Production code reaches for one of the ways in
5458

5559
.. code-block:: python
5660
57-
import torch
61+
import numpy as np
5862
import isaaclab.sim as sim_utils
5963
from isaaclab.cloner import usd_replicate
6064
@@ -65,10 +69,10 @@ control. Production code reaches for one of the ways in
6569
6670
usd_replicate(
6771
stage,
68-
sources=["/World/envs/env_0/Cube"],
69-
destinations=["/World/envs/env_{}/Cube"],
70-
env_ids=torch.arange(num_envs, device="cuda:0"),
71-
mask=torch.ones((1, num_envs), dtype=torch.bool, device="cuda:0"),
72+
sources=("/World/envs/env_0/Cube",),
73+
destinations=("/World/envs/env_{}/Cube",),
74+
env_ids=np.arange(num_envs),
75+
mask=np.ones((1, num_envs), dtype=np.bool_),
7276
)
7377
7478
**PhysX** — call PhysX and USD on the same sources and destinations (either order):
@@ -77,16 +81,28 @@ control. Production code reaches for one of the ways in
7781
7882
from isaaclab_physx.cloner import physx_replicate
7983
80-
physx_replicate(stage, sources, destinations, env_ids, mask)
81-
usd_replicate(stage, sources, destinations, env_ids, mask)
84+
sources = ("/World/envs/env_0/Cube",)
85+
destinations = ("/World/envs/env_{}/Cube",)
86+
env_ids = np.arange(num_envs)
87+
mapping = np.ones((1, num_envs), dtype=np.bool_)
88+
physx_replicate(stage, sources, destinations, env_ids, mapping=mapping)
89+
usd_replicate(stage, sources, destinations, env_ids, mask=mapping)
8290
8391
**Newton**:
8492

8593
.. code-block:: python
8694
8795
from isaaclab_newton.cloner import newton_physics_replicate
8896
89-
newton_physics_replicate(stage, sources, destinations, env_ids, mapping=mask)
97+
newton_physics_replicate(stage, sources, destinations, env_ids, mapping=mapping)
98+
99+
**OvPhysX**:
100+
101+
.. code-block:: python
102+
103+
from isaaclab_ov.cloner import ovphysx_replicate
104+
105+
ovphysx_replicate(stage, sources, destinations, env_ids, mapping=mapping)
90106
91107
92108
Cloning in a Backend-Agnostic Way
@@ -106,13 +122,10 @@ code never branches on the backend.
106122
ClonePlan
107123
~~~~~~~~~
108124

109-
A plan holds the parallel arrays a backend replicate consumes — sources,
125+
A plan holds the parallel arrays used by production clone contexts — sources,
110126
destinations, mask, env ids — in one place. Conceptually it is a small table
111127
where each row describes one distinct prototype-to-destination mapping; the
112-
fields listed below are that table's columns. Every entry point in
113-
:mod:`isaaclab.cloner` either produces a plan, consumes a plan, or both, so a
114-
quick look at the fields is the fastest way to build intuition for the rest of
115-
this page:
128+
fields listed below are that table's columns:
116129

117130
.. list-table::
118131
:header-rows: 1
@@ -125,16 +138,18 @@ this page:
125138
* - ``destinations``
126139
- Destination templates with ``"{}"`` for the env id, one per row.
127140
* - ``clone_mask``
128-
- Bool tensor ``[len(sources), num_envs]``; ``True`` when env ``j`` comes from row ``i``.
141+
- NumPy boolean array ``[len(sources), num_envs]``; ``True`` when env ``j`` comes from row ``i``.
129142
* - ``env_ids``
130-
- Long tensor of target env ids.
143+
- Optional NumPy integer array of target env ids; execution requires it.
131144
* - ``positions``
132145
- Optional per-env world positions [m], shape ``[num_envs, 3]``.
133146
* - ``global_paths``
134147
- Unique prim paths for scene assets shared by every env and therefore not replicated.
148+
* - ``context_rows``
149+
- Clone-context types mapped to the rows they consume.
135150

136-
The plan is stage-agnostic by design — the same instance can be replayed against a
137-
different stage, inspected by tooling, or serialized.
151+
The plan does not own a stage. Simulation-owned contexts supply their own runtime
152+
when they consume it.
138153

139154
When every env is a copy of env_0:
140155

@@ -193,7 +208,7 @@ column ``j`` stands for ``env_ids[j]``, and the queries speak ids throughout.
193208

194209
A plan is the *what*. Putting one together and handing it to the backends is
195210
the *how*, and Isaac Lab exposes three idiomatic ways to do that. All three end
196-
in the same ``cloner.replicate(plan, stage=...)`` call, so the choice between
211+
in the same ``cloner.replicate(plan)`` call, so the choice between
197212
them is purely about ergonomics:
198213

199214
* The first wraps both phases in a context manager and is what
@@ -214,11 +229,11 @@ them is purely about ergonomics:
214229
:class:`~isaaclab.cloner.ReplicateSession` is a context manager that brackets the
215230
whole cloning lifecycle. Entering the block builds the plan, the body is where
216231
you construct your assets (each one registers itself as part of its constructor),
217-
and exiting the block drains every registration against the plan:
232+
and exiting the block clears those constructor registrations and dispatches the plan:
218233

219234
.. code-block:: python
220235
221-
with cloner.ReplicateSession(cfgs, num_clones=N, env_spacing=2.0, device=device, stage=stage):
236+
with cloner.ReplicateSession(cfgs, num_clones=N, env_spacing=2.0):
222237
for cfg in cfgs:
223238
cfg.class_type(cfg)
224239
@@ -253,10 +268,10 @@ intervene before replication actually happens:
253268

254269
.. code-block:: python
255270
256-
plan = cloner.make_clone_plan(cfgs, num_clones=N, env_spacing=2.0, device=device)
271+
plan = cloner.make_clone_plan(cfgs, num_clones=N, env_spacing=2.0)
257272
for cfg in cfgs:
258273
cfg.class_type(cfg)
259-
cloner.replicate(plan, stage=stage)
274+
cloner.replicate(plan)
260275
261276
``clone_plan_from_env_0`` + ``replicate``
262277
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -276,10 +291,10 @@ subclasses use — they author the env-0 prototype prim by prim in
276291
# ... any other assets ...
277292
278293
src, dest = "/World/envs/env_0", "/World/envs/env_{}"
279-
pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing, device=self.device)[0]
294+
pos = cloner.grid_transforms(self.scene.num_envs, self.scene.cfg.env_spacing)[0]
280295
global_paths = ("/World/ground",)
281-
plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, self.device, pos, global_paths=global_paths)
282-
cloner.replicate(plan, stage=self.scene.stage)
296+
plan = cloner.clone_plan_from_env_0(src, dest, self.scene.num_envs, pos, global_paths=global_paths)
297+
cloner.replicate(plan)
283298
284299
Every env receives the same prototype. When envs need to differ, use one of the
285300
other two. Hand-built scenes must pass every shared asset root in ``global_paths``;
@@ -289,118 +304,37 @@ use ``()`` when there are none.
289304
Under the Hood
290305
--------------
291306

292-
To see how the backend-agnostic surface works, follow one asset through the
293-
system. Suppose you write ``Articulation(cfg)`` for a PhysX articulation
294-
somewhere inside a :class:`~isaaclab.cloner.ReplicateSession`. The constructor
295-
does not actually clone anything yet — at that moment the plan describing how
296-
the full env population should be laid out may not even exist. Instead the
297-
constructor *registers* the asset with the cloner, the cloner files the
298-
registration into a queue, and later — when the session exits and the cloner
299-
runs replication — that registration is handed to the backend code that knows
300-
how to replicate a PhysX articulation, with the plan telling it where each
301-
clone goes.
302-
303-
The story has to look like this because the engines underneath disagree about
304-
*when* and *how* replication actually happens:
305-
306-
* **PhysX** defers the real work to physics runtime. At construction time the
307-
only thing user code can do is register intent; PhysX replays those
308-
registrations entity by entity when the simulation comes up.
309-
* **USD** is declarative and immediate — calling :func:`~isaaclab.cloner.usd_replicate`
310-
materializes the clones in place, right then and there.
311-
* **Newton** is also declarative and immediate, but it insists on replicating
312-
the whole world in one shot rather than asset by asset, so the framework
313-
cannot just hand it one cfg at a time — everything Newton-related has to be
314-
assembled first.
315-
316-
Isaac Lab reconciles these into one surface with two small pieces of plumbing.
317-
Every backend supplies its own :class:`~isaaclab.cloner.UsdReplicateContext` /
318-
``PhysxReplicateContext`` / ``NewtonReplicateContext``, a class that hides the
319-
timing and granularity differences above behind a single uniform interface. A
320-
shared :data:`~isaaclab.cloner.REPLICATION_QUEUE` then remembers which asset
321-
cfgs participate until it is time to run. The three
322-
subsections below explain the queue, the contexts, and the function that joins
323-
them against a plan.
324-
325-
The registration queue
326-
~~~~~~~~~~~~~~~~~~~~~~
327-
328-
Assets do not replicate inline. Construction only registers the asset's cfg
329-
through :func:`~isaaclab.cloner.queue_replication`; the queue records *which*
330-
cfgs participate:
307+
Planning maps each cfg to rows in ``cfg_rows`` and each participating backend to
308+
its subset in ``context_rows``. The active physics manager registers its clone
309+
context during simulation initialization. Assets use that context by default;
310+
:attr:`~isaaclab.assets.AssetBaseCfg.cloning_contexts` can select an explicitly
311+
registered context instead. Planning also registers
312+
:class:`~isaaclab.cloner.UsdReplicateContext` for spawned assets when Kit is
313+
available.
331314

332-
.. code-block:: text
333-
334-
REPLICATION_QUEUE
335-
cartpole_cfg
336-
cube_cfg
337-
camera_cfg
338-
...
339-
340-
*How* each cfg is cloned is resolved by :func:`~isaaclab.cloner.replicate` at
341-
dispatch. The physics side comes from the cfg's
342-
:attr:`~isaaclab.assets.AssetBaseCfg.cloning_contexts` when set, otherwise the
343-
active backend's default physics context, which each backend cloner exports as
344-
``PHYSICS_CONTEXT`` (PhysX and Newton replicate natively; OvPhysX replays its
345-
own clones). :func:`~isaaclab.cloner.replicate` adds
346-
:class:`~isaaclab.cloner.UsdReplicateContext` automatically whenever a cfg has a
347-
spawner and Kit is available, so USD clones accompany physics replication under
348-
Kit and are skipped by default in headless runs. An explicit cfg override may
349-
still request USD replication without Kit. With
350-
:attr:`~isaaclab.cloner.CloneCfg.replicate_physics` disabled, cloning is
351-
USD-only: every physics context is dropped and the physics engine parses the
352-
per-env USD prims directly.
353-
354-
Deferring the work like this buys three things at once:
355-
356-
* Replication can wait until the plan is fully built, so the final layout is
357-
known before any prims are spawned.
358-
* Every asset's request is batched into a single backend call instead of one
359-
call per asset.
360-
* Asset code stays free of any branching on which backend is active — it just
361-
registers and lets the framework take it from there.
362-
363-
:attr:`~isaaclab.scene.InteractiveSceneCfg.replicate_physics` is piped into
364-
:attr:`~isaaclab.cloner.CloneCfg.replicate_physics` and applied at dispatch;
365-
an asset whose only cloning mechanism is physics replication is then simply
366-
not cloned.
367-
368-
Backend contexts
369-
~~~~~~~~~~~~~~~~
370-
371-
Each backend ships a small adapter class — its *replicate context* — that
372-
knows how to take a registered cfg and replicate it on the backend's specific
373-
runtime:
315+
The backend packages expose different context implementations behind one
316+
execution contract:
374317

375318
.. code-block:: text
376319
377320
UsdReplicateContext # replicates USD prim subtrees
378321
PhysxReplicateContext # replicates PhysX rigid bodies and articulations
379322
NewtonReplicateContext # replicates Newton bodies in its parallel pipeline
380323
381-
A single asset usually resolves to more than one context — PhysX pairs its
382-
context with USD so physics and visuals both follow; Newton's default stack
383-
includes USD only under Kit, so kitless runs skip the authoring cost. This is
384-
where backend differences are absorbed: swapping a scene from PhysX to Newton
385-
swaps which default stack resolves at dispatch, while the cfgs and the rest of
386-
the user code stay unchanged.
387-
388-
Running replication
389-
~~~~~~~~~~~~~~~~~~~
390-
391-
:func:`~isaaclab.cloner.replicate` is what actually runs the registered work.
392-
The dispatch shape is roughly:
324+
:func:`~isaaclab.cloner.replicate` resolves these types through the
325+
:class:`~isaaclab.sim.SimulationContext` backend registry, orders them by
326+
``replicate_priority``, and passes the same plan to each one:
393327

394328
.. code-block:: python
395329
396-
def replicate(plan, stage):
397-
for context_cls, rows in group_queue_by_context(plan):
398-
context_cls().replicate(rows=rows, stage=stage)
330+
def replicate(plan):
331+
for context_type in plan.context_rows:
332+
simulation_backends[context_type].replicate(plan)
399333
publish(plan)
400334
401-
Contexts run in a priority order that puts physics ahead of visuals, and the
402-
plan is published to :class:`~isaaclab.sim.SimulationContext` so the rest of the
403-
framework can read the per-env layout back.
335+
USD runs before native physics contexts so the destination topology exists when
336+
they consume it. No fallback context is constructed during dispatch. The plan is
337+
then published to the simulation context for downstream consumers.
404338

405339
Collision Filtering
406340
-------------------

docs/source/how-to/proxy_array.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Quick Start
2020
~~~~~~~~~~~
2121

2222
Every property on asset and sensor data classes (e.g., ``robot.data.joint_pos``,
23-
``sensor.data.net_forces_w``) returns a ``ProxyArray``:
23+
``sensor.data.net_normal_forces_w``) returns a ``ProxyArray``:
2424

2525
.. code-block:: python
2626

0 commit comments

Comments
 (0)