Skip to content

Honor replicate_physics via cfg-registered replication lists#6550

Open
ooctipus wants to merge 10 commits into
isaac-sim:developfrom
ooctipus:zhengyuz/fix/replicate-physics-cfg-registration
Open

Honor replicate_physics via cfg-registered replication lists#6550
ooctipus wants to merge 10 commits into
isaac-sim:developfrom
ooctipus:zhengyuz/fix/replicate-physics-cfg-registration

Conversation

@ooctipus

@ooctipus ooctipus commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Description

Since the replication-session refactor (#5770), InteractiveSceneCfg.replicate_physics has been ignored: scenes configured with replicate_physics=False invoked native physics replication anyway, silently discarding per-environment USD differences (e.g. prestartup scale randomization). Identified by @nblauch in #6541 with a multi-GPU heterogeneous-cloning repro.

This PR restores the flag as cloner-side policy directed by asset cfgs:

  • CloneCfg.replicate_physics is the policy's home. InteractiveScene pipes its cfg flag into it and forwards it through ReplicateSession to replicate() — the scene contains no policy code.
  • REPLICATION_QUEUE holds bare asset cfgs. Construction only registers which cfgs participate (queue_replication(cfg)); this stays at construction because ClonePlan.from_env_0 scans the queue after constructors run in direct workflows, and because construction distinguishes clonable assets from lights/terrain.
  • replicate() resolves how each cfg clones at dispatch: the cfg's cloning_contexts field when set (None → backend default, () → not cloned), otherwise the active backend's default stack, exported under the conventional name isaaclab_<backend>.cloner.REPLICATION:
    • PhysX: (UsdReplicateContext, PhysxReplicateContext) — USD clones are required beyond rendering (collision groups are authored on the per-env prims), and PhysX has no kitless mode.
    • Newton: (Usd, Newton) under Kit, (Newton,) kitless — headless runs skip the USD authoring cost, with the Kit check made at import under the documented app-first timing contract.
    • OvPhysX: (OvPhysxReplicateContext,) — its clone replay authors USD itself.
  • With replicate_physics=False, cloning is USD-only: every context except UsdReplicateContext is dropped and the physics engine parses the per-env USD prims directly. An asset whose contexts are all physics-based is simply not cloned; if that matters, it surfaces at asset initialization. The Newton limitation is documented on the cfg.
  • Asset implementations know nothing about cloning beyond registering their cfg. Per-asset control is a cfg assignment (e.g. MPMObjectCfg pins Newton-only contexts because particle rendering syncs through Fabric).
  • The per-backend queue_*_replication helpers are removed in favor of the single path (changelogs carry the migration; fragments are .major for the affected packages).

Relation to #6541: same regression, alternative mechanism, per the design discussion — policy lives in the cloner cfg rather than the dispatcher signature, and asset cfgs are the single source of truth for how an asset clones.

Validation

  • test_cloner.py: 52 passed — queue/drain/dedup/priority under the bare-cfg shape, a drain-level USD-only filter test, and plan-publish forwarding of the flag
  • test_interactive_scene.py: spy-based test asserts the PhysX replicator is registered with replicate_physics=True and NOT registered with False while envs still simulate; verified to fail with the drain filter removed (regression rule); cfg-override test covers cloning_contexts
  • Newton default stack verified in both modes: (Usd, Newton) under Kit, (Newton,) kitless
  • Pre-commit passes on all files

Type of change

  • Bug fix (restores documented behavior)
  • Breaking change (REPLICATION_QUEUE shape; removed queue helpers — changelog fragments carry migration guidance)

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective
  • I have added changelog fragments for all touched packages
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

🤖 Generated with Claude Code

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team labels Jul 15, 2026
Comment thread source/isaaclab/isaaclab/cloner/replicate_session.py Outdated
@ooctipus
ooctipus requested a review from masoudmoghani as a code owner July 15, 2026 23:52
@ooctipus
ooctipus force-pushed the zhengyuz/fix/replicate-physics-cfg-registration branch from ff7f851 to 85917aa Compare July 16, 2026 00:33
@ooctipus
ooctipus requested a review from pascal-roth as a code owner July 16, 2026 01:36
@ooctipus
ooctipus force-pushed the zhengyuz/fix/replicate-physics-cfg-registration branch 2 times, most recently from c792488 to 7845667 Compare July 16, 2026 05:45
@ooctipus

Copy link
Copy Markdown
Collaborator Author

@greptile-apps review again

@isaac-sim isaac-sim deleted a comment from greptile-apps Bot Jul 17, 2026
@ooctipus

Copy link
Copy Markdown
Collaborator Author

@greptile-apps review again

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores InteractiveSceneCfg.replicate_physics behavior that was silently ignored since the replication-session refactor (#5770): scenes with replicate_physics=False were invoking native physics replication anyway, discarding per-environment USD differences. The fix threads the flag from CloneCfgReplicateSessionreplicate(), consolidates registration into AssetBase.__init__ via a new queue_replication(cfg) helper, and moves per-cfg context resolution (physics vs USD-only) out of asset constructors and into the drain function.

  • replicate_physics=False filter: replicate() now resolves contexts per-cfg from cloning_contexts or the backend's PHYSICS_CONTEXT sentinel, then drops physics contexts when the flag is False. A concurrently added _is_self_only guard in PhysxReplicateContext skips registering the PhysX replicator for fully-heterogeneous 1:1 layouts, fixing an mGPU double-free.
  • API cleanup: Per-backend queue_<backend>_replication helpers are removed in favour of the single queue_replication path; REPLICATION_QUEUE now holds bare cfgs instead of (cfg, CtxCls) tuples."

Confidence Score: 4/5

The core propagation of replicate_physics from scene cfg through to the drain is correct, but the physics-filter logic in replicate() drops UsdReplicateContext from explicit cloning_contexts entries when spawn is absent, contradicting the documented USD-only guarantee and causing the new test to fail on that code path.

The mGPU double-free fix and the end-to-end flag-threading are solid. The one issue is in the replicate_physics=False branch of replicate(): it zeroes out ALL resolved contexts then re-adds UsdReplicateContext only via the spawn heuristic. A cfg that explicitly sets cloning_contexts=(UsdReplicateContext, PhysicsCtx) but has no spawn loses its USD replication entirely. The accompanying test test_replicate_physics_false_keeps_usd_only exposes this because its SimpleNamespace cfg carries no spawn — the IsValid() assertion on env_1 would fail, meaning the regression guard it is supposed to provide does not actually run.

source/isaaclab/isaaclab/cloner/replicate_session.py (lines 87-91, the physics-filter block) and source/isaaclab/test/sim/test_cloner.py (test_replicate_physics_false_keeps_usd_only, which needs a spawn attribute on the test cfg or a corrected filter).

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/cloner/replicate_session.py Core drain/dispatch logic; replicate_physics=False filter incorrectly drops UsdReplicateContext from explicit cloning_contexts when spawn is absent, contradicting documented semantics and breaking the new test.
source/isaaclab/test/sim/test_cloner.py test_replicate_physics_false_keeps_usd_only builds a cfg with no spawn attribute but expects USD replication to produce env_1; this assertion will fail given the current filter logic.
source/isaaclab_physx/isaaclab_physx/cloner/replicate.py Moves self._queue.clear() before the _is_self_only early-return so the queue is always drained; fixes potential stale-entry double-free under mGPU. Clean and targeted.
source/isaaclab/isaaclab/assets/asset_base.py Centralises replication registration: queue_replication(cfg) is called on the original cfg (before copy) so id(cfg) matches what ClonePlan later keys on. Correct.
source/isaaclab/isaaclab/assets/asset_base_cfg.py Adds cloning_contexts field (default None → backend default stack). Clean extension; sensors override this via SensorBaseCfg.cloning_contexts.
source/isaaclab/isaaclab/sensors/sensor_base_cfg.py Sets cloning_contexts=("isaaclab.cloner:UsdReplicateContext",) for all sensors. When replicate_physics=False and spawn=None, this context is also dropped by the current filter — same root cause as the main finding.
source/isaaclab/isaaclab/cloner/cloner_cfg.py Adds replicate_physics: bool = True to CloneCfg. Straightforward field addition; correctly threaded through ReplicateSession.
source/isaaclab/isaaclab/scene/interactive_scene.py Pipes cfg.replicate_physics into CloneCfg and ReplicateSession. Two-line change; correctly propagates the flag end-to-end.
source/isaaclab_ovphysx/isaaclab_ovphysx/cloner/replicate.py Replaces queue_ovphysx_replication with PHYSICS_CONTEXT sentinel. TODO acknowledges that OvPhysxReplicateContext bundles USD+physics, so full replicate_physics=False isolation is deferred.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Scene as InteractiveScene
    participant CC as CloneCfg
    participant RS as ReplicateSession
    participant AB as AssetBase.__init__
    participant RQ as REPLICATION_QUEUE
    participant Rep as replicate()
    participant Ctx as BackendCtxCls

    Scene->>CC: "CloneCfg(replicate_physics=cfg.replicate_physics)"
    Scene->>RS: "ReplicateSession(..., replicate_physics=cloner_cfg.replicate_physics)"
    RS->>RS: __enter__: make_clone_plan(cfgs)

    loop For each asset constructor
        AB->>RQ: queue_replication(original_cfg)
    end

    RS->>Rep: __exit__: replicate(plan, stage, replicate_physics)
    loop For each cfg in queue
        Rep->>Rep: resolve contexts from cfg.cloning_contexts or PHYSICS_CONTEXT
        alt "replicate_physics=False"
            Rep->>Rep: filter keep only UsdReplicateContext
        end
        alt cfg.spawn set and has_kit()
            Rep->>Rep: setdefault(UsdReplicateContext)
        end
        Rep->>Ctx: queue_mapping(sources, destinations, ...)
    end
    Rep->>Ctx: replicate() per sorted priority
    Rep->>Scene: set_clone_plan(plan)
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 Scene as InteractiveScene
    participant CC as CloneCfg
    participant RS as ReplicateSession
    participant AB as AssetBase.__init__
    participant RQ as REPLICATION_QUEUE
    participant Rep as replicate()
    participant Ctx as BackendCtxCls

    Scene->>CC: "CloneCfg(replicate_physics=cfg.replicate_physics)"
    Scene->>RS: "ReplicateSession(..., replicate_physics=cloner_cfg.replicate_physics)"
    RS->>RS: __enter__: make_clone_plan(cfgs)

    loop For each asset constructor
        AB->>RQ: queue_replication(original_cfg)
    end

    RS->>Rep: __exit__: replicate(plan, stage, replicate_physics)
    loop For each cfg in queue
        Rep->>Rep: resolve contexts from cfg.cloning_contexts or PHYSICS_CONTEXT
        alt "replicate_physics=False"
            Rep->>Rep: filter keep only UsdReplicateContext
        end
        alt cfg.spawn set and has_kit()
            Rep->>Rep: setdefault(UsdReplicateContext)
        end
        Rep->>Ctx: queue_mapping(sources, destinations, ...)
    end
    Rep->>Ctx: replicate() per sorted priority
    Rep->>Scene: set_clone_plan(plan)
Loading

Reviews (2): Last reviewed commit: "Rename _PHYSICS_CONTEXT to PHYSICS_CONTE..." | Re-trigger Greptile

Comment on lines +87 to +91
if not replicate_physics:
contexts = []
ctx_set = dict.fromkeys(contexts)
if getattr(cfg, "spawn", None) is not None and has_kit():
ctx_set.setdefault(UsdReplicateContext, None)

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.

P1 replicate_physics=False silently drops explicit UsdReplicateContext entries

The PR description states "every context except UsdReplicateContext is dropped" when replicate_physics=False, but the implementation first clears ALL contexts unconditionally (contexts = []), then re-adds UsdReplicateContext only when cfg.spawn is not None and has_kit(). A cfg that explicitly sets cloning_contexts=(UsdReplicateContext, PhysicsCtx) but has spawn=None — exactly the shape used in test_replicate_physics_false_keeps_usd_only — ends up with no context at all: the FakePhysicsCtx is correctly dropped but UsdReplicateContext is also lost, so env_1 never gets created. The test's final assertion stage.GetPrimAtPath("/World/envs/env_1/Robot").IsValid() would fail on that code path.

The correct filter keeps only UsdReplicateContext from the resolved context list rather than zeroing it:

if not replicate_physics:
    contexts = [c for c in contexts if c is UsdReplicateContext]

The subsequent ctx_set.setdefault(UsdReplicateContext, None) call is then a safe no-op when UsdReplicateContext is already present, and the spawn-based heuristic still adds it for cfgs that rely on the default stack.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores the long-broken replicate_physics=False path by moving policy ownership into CloneCfg and resolving cloning contexts at dispatch time rather than at construction. It also ships a secondary fix that prevents a native heap-corruption crash in fully-heterogeneous mGPU layouts by skipping register_replicator when every source is already in its own environment.

  • Core change: REPLICATION_QUEUE now holds bare asset cfgs; replicate() resolves which context classes to use at drain time from AssetBaseCfg.cloning_contexts (or the backend's PHYSICS_CONTEXT constant), then auto-adds UsdReplicateContext via a spawn check. InteractiveScene pipes cfg.replicate_physicsCloneCfgReplicateSessionreplicate().
  • Breaking change: per-backend queue_<backend>_replication helpers are removed in favour of the single queue_replication(cfg) call in AssetBase.__init__; major changelog fragments are provided for all affected packages.
  • PhysX fix: _queue.clear() is moved before the heterogeneous-1:1 early-return in PhysxReplicateContext.replicate(), closing a queue-leak that left stale entries across sessions, alongside the new self-only short-circuit that avoids register_replicator for mGPU heterogeneous scenes.

Confidence Score: 4/5

Safe to merge after addressing one defect: any asset that sets cloning_contexts = () to opt out of all cloning will silently receive USD replication if it also carries a spawner, directly contradicting the documented API promise.

The overall architecture is sound and the primary regression is correctly addressed. The single concrete defect is in replicate(): when a cfg has cloning_contexts = (), the docstring says cloning is suppressed, but the unconditional spawn check calls ctx_set.setdefault(UsdReplicateContext, None) and re-introduces USD replication for any cfg whose spawn attribute is non-None.

source/isaaclab/isaaclab/cloner/replicate_session.py — the spawn-check at lines 89-91 needs to be gated on cfg.cloning_contexts is None so that explicitly supplied (including empty) tuples are honoured without override.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/cloner/replicate_session.py Core drain/dispatch logic restructured; cloning_contexts=() documented to suppress all cloning but the spawn-check re-adds UsdReplicateContext even for an explicitly empty tuple, violating the API contract
source/isaaclab/isaaclab/assets/asset_base_cfg.py Adds cloning_contexts field; docstring promises empty tuple suppresses all cloning, but spawn-check in replicate() overrides this
source/isaaclab/isaaclab/sensors/sensor_base_cfg.py Correctly defaults sensors to USD-only cloning; sensors don't inherit from AssetBase so self-registration in Camera/BaseRayCaster is still needed
source/isaaclab/isaaclab/cloner/cloner_cfg.py Adds replicate_physics field that is the single policy home, cleanly forwarded through ReplicateSession to replicate()
source/isaaclab_physx/isaaclab_physx/cloner/replicate.py Moves _queue.clear() before the heterogeneous 1:1 early-return to fix a queue-leak bug; also skips register_replicator for fully self-mapped heterogeneous layouts (mGPU double-free fix)
source/isaaclab/isaaclab/scene/interactive_scene.py Pipes cfg.replicate_physics into CloneCfg and forwards it to ReplicateSession correctly
source/isaaclab_newton/isaaclab_newton/assets/mpm_object/mpm_object_cfg.py Pins Newton-only contexts for MPM because particle rendering syncs through Fabric; correct use of explicit cloning_contexts

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Scene as InteractiveScene
    participant CloneCfg as CloneCfg
    participant Session as ReplicateSession
    participant Asset as AssetBase.__init__
    participant Plan as ClonePlan.from_env_0
    participant Q as REPLICATION_QUEUE
    participant Rep as replicate()

    Scene->>CloneCfg: "CloneCfg(replicate_physics=cfg.replicate_physics)"
    Scene->>Session: "enter (replicate_physics=cloner_cfg.replicate_physics)"
    Session->>Plan: make_clone_plan(cfgs)

    loop For each asset constructor
        Asset->>Q: queue_replication(cfg)
    end

    Session->>Plan: ClonePlan.from_env_0(REPLICATION_QUEUE)
    Note over Plan: cfg_rows keyed by id(cfg)

    Session->>Rep: replicate(plan, stage, replicate_physics)
    Rep->>Q: snapshot + clear REPLICATION_QUEUE

    loop For each cfg in snapshot
        alt cloning_contexts is None
            Rep->>Rep: "contexts = [PHYSICS_CONTEXT]"
        else cloning_contexts is set
            Rep->>Rep: "contexts = resolve(cloning_contexts)"
        end
        alt replicate_physics is False
            Rep->>Rep: "contexts = [] (all cleared)"
        end
        alt spawn is not None AND has_kit()
            Rep->>Rep: ctx_set.setdefault(UsdReplicateContext)
        end
        Rep->>Rep: backend_rows[Ctx].update(rows)
    end

    loop For each backend context (sorted by priority)
        Rep->>Rep: ctx.queue_mapping(sources, dests, env_ids)
        Rep->>Rep: ctx.replicate()
    end

    Rep->>Scene: set_clone_plan(plan)
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 Scene as InteractiveScene
    participant CloneCfg as CloneCfg
    participant Session as ReplicateSession
    participant Asset as AssetBase.__init__
    participant Plan as ClonePlan.from_env_0
    participant Q as REPLICATION_QUEUE
    participant Rep as replicate()

    Scene->>CloneCfg: "CloneCfg(replicate_physics=cfg.replicate_physics)"
    Scene->>Session: "enter (replicate_physics=cloner_cfg.replicate_physics)"
    Session->>Plan: make_clone_plan(cfgs)

    loop For each asset constructor
        Asset->>Q: queue_replication(cfg)
    end

    Session->>Plan: ClonePlan.from_env_0(REPLICATION_QUEUE)
    Note over Plan: cfg_rows keyed by id(cfg)

    Session->>Rep: replicate(plan, stage, replicate_physics)
    Rep->>Q: snapshot + clear REPLICATION_QUEUE

    loop For each cfg in snapshot
        alt cloning_contexts is None
            Rep->>Rep: "contexts = [PHYSICS_CONTEXT]"
        else cloning_contexts is set
            Rep->>Rep: "contexts = resolve(cloning_contexts)"
        end
        alt replicate_physics is False
            Rep->>Rep: "contexts = [] (all cleared)"
        end
        alt spawn is not None AND has_kit()
            Rep->>Rep: ctx_set.setdefault(UsdReplicateContext)
        end
        Rep->>Rep: backend_rows[Ctx].update(rows)
    end

    loop For each backend context (sorted by priority)
        Rep->>Rep: ctx.queue_mapping(sources, dests, env_ids)
        Rep->>Rep: ctx.replicate()
    end

    Rep->>Scene: set_clone_plan(plan)
Loading

Reviews (3): Last reviewed commit: "Rename _PHYSICS_CONTEXT to PHYSICS_CONTE..." | Re-trigger Greptile

ooctipus added 10 commits July 17, 2026 16:03
Since the replication-session refactor, InteractiveSceneCfg.replicate_physics
was ignored: scenes configured with replicate_physics=False invoked native
physics replication anyway, silently discarding per-environment USD
differences (identified in isaac-sim#6541).

The flag now lives where the policy executes:

- CloneCfg.replicate_physics is the policy's home; InteractiveScene pipes its
  cfg flag into it and forwards it through ReplicateSession to replicate().
- REPLICATION_QUEUE holds bare asset cfgs: construction only registers which
  cfgs participate (required by ClonePlan.from_env_0 in direct workflows and
  to distinguish clonable assets from lights/terrain).
- replicate() resolves how each cfg clones at dispatch: the cfg's
  cloning_contexts field when set, otherwise the active backend's default
  stack exported as isaaclab_<backend>.cloner.REPLICATION. PhysX pairs
  physics replication with USD clones (collision groups are authored on the
  per-env prims; PhysX has no kitless mode), Newton includes USD only under
  Kit, and OvPhysX replicates alone since its clone replay authors USD.
- With replicate_physics=False, cloning is USD-only: the physics engine
  parses the per-env USD prims directly; an asset whose contexts are all
  physics-based is simply not cloned.

Asset implementations know nothing about cloning beyond registering their
cfg; per-asset overrides are a cfg assignment. The per-backend
queue_*_replication helpers are removed in favor of this single path.
For scenes where every source maps only to its own environment
(one variant per env, fully-heterogeneous 1:1), calling rep.replicate()
once per source with a single self-target intermittently triggers native
heap corruption (double-free / SIGABRT) under mGPU.

Detect this layout by checking that all queued rows are self-only and
skip register_replicator entirely. PhysX then parses the source prims
directly from the stage, which is both correct and safe.

Reproducer: mgpu_repro.zip -- heterogeneous mGPU with 192 variants and
4 GPUs, replicate_physics=True.
Three test fixes:
- test_usd_replicate_self_copy_skips_copy_spec: patch isaaclab.cloner.usd.Sdf
  instead of cloner_utils.Sdf (Sdf moved to usd module after refactor)
- test_resolve_clone_plan_source_partial_coverage_raises: rename and update
  to assert new behavior (returns first active source, no longer raises
  NotImplementedError for partial coverage)
- test_collect_asset_cfgs_resolves_env_regex_macros / orders_sensors_last:
  set scene._env_ns when bypassing __init__ (required by _collect_asset_cfgs
  after env_ns was promoted to a property backed by _env_ns)
Each backend asset class now declares _PHYSICS_CLONING_CONTEXT for its
physics context. AssetBase.__init__ stamps it onto cfg.cloning_contexts
when the user hasn't set one. replicate() auto-adds UsdReplicateContext
via dict.fromkeys() when cfg.spawn is not None and has_kit(), so USD
replication is never hard-coded anywhere.

SensorBaseCfg.cloning_contexts default changed from explicit USD string
to None — sensors with spawn get USD automatically, contact sensors
(spawn=None) get nothing.

OvPhysxReplicateContext keeps its monolithic shape for now; a TODO marks
the future decomposition into separate USD + physics contexts.
Replace the REPLICATION=(USD, Physics) tuples in each backend cloner
with a private _PHYSICS_CONTEXT pointing only to the physics context
class. replicate() looks up _PHYSICS_CONTEXT when cloning_contexts is
None, then auto-adds UsdReplicateContext via dict.fromkeys() when
cfg.spawn is not None and has_kit().

No asset class files touched.
contexts = [] was unconditionally zeroing the resolved list, losing any
UsdReplicateContext that was explicitly set in cloning_contexts when
spawn=None (so the spawn+Kit heuristic would not re-add it).

Filter to keep only UsdReplicateContext entries instead.
The partial-coverage test was rewritten to expect
resolve_clone_plan_source to return the first active row's source, but
the matching production change was omitted, so the old
NotImplementedError guard still fired in CI. Replace the guard with
selection of the first matching row that has any active env, returning
None when none is active so callers fall back to direct stage
resolution. The destination glob resolves only to the envs that
actually received the asset.
The REPLICATION tuple was removed in favor of a single backend
PHYSICS_CONTEXT, with UsdReplicateContext now added per spawned cfg by
isaaclab.cloner.replicate() when Kit is available, but the contrib test
still imported the deleted tuple. Assert the Newton physics context
directly; the Kit-gated USD behavior is covered by the replicate
session tests in test_cloner.py.
@ooctipus
ooctipus force-pushed the zhengyuz/fix/replicate-physics-cfg-registration branch from 02fa39e to 1403655 Compare July 17, 2026 23:06
@ooctipus
ooctipus requested a review from a team July 17, 2026 23:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant