diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 2ac56b46c848..756585864b4a 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -51,8 +51,7 @@ from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg # isort:skip from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG # isort:skip -from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG # isort:skip -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_common import ShadowHandRobotCfg # isort:skip +from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG, SHADOW_HAND_NEWTON_CFG # isort:skip if TYPE_CHECKING: from isaaclab.assets import Articulation @@ -95,7 +94,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Origin 2 with Shadow Hand sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot - shadow_hand_cfg = ShadowHandRobotCfg().newton_mjwarp if args_cli.physics == "newton_mjwarp" else SHADOW_HAND_CFG + shadow_hand_cfg = SHADOW_HAND_NEWTON_CFG if args_cli.physics == "newton_mjwarp" else SHADOW_HAND_CFG shadow_hand_cfg = shadow_hand_cfg.replace(prim_path="/World/Origin2/Robot") shadow_hand = shadow_hand_cfg.class_type(shadow_hand_cfg) diff --git a/source/isaaclab/changelog.d/fix-shadow-hand-demo-config.rst b/source/isaaclab/changelog.d/fix-shadow-hand-demo-config.rst new file mode 100644 index 000000000000..b59387adba8d --- /dev/null +++ b/source/isaaclab/changelog.d/fix-shadow-hand-demo-config.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed the Newton Shadow Hand demo failing at startup after inheriting the reorientation task's + prototype spawn path. diff --git a/source/isaaclab/test/app/test_standalone_scripts.py b/source/isaaclab/test/app/test_standalone_scripts.py index 99dd7ab14936..5e5fa312bd7b 100644 --- a/source/isaaclab/test/app/test_standalone_scripts.py +++ b/source/isaaclab/test/app/test_standalone_scripts.py @@ -242,6 +242,21 @@ def test_commands_respect_script_launcher_capabilities(): assert "--enable_cameras" not in usd_camera_case.command() +def test_hands_demo_uses_asset_owned_shadow_hand_configs(): + """The generic hands demo must not inherit task-specific spawn policy.""" + path = script_cases.ROOT / "scripts/demos/hands.py" + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + imports = { + (node.module, alias.name) for node in ast.walk(tree) if isinstance(node, ast.ImportFrom) for alias in node.names + } + + assert not {module for module, _ in imports if module and module.startswith("isaaclab_tasks")} + assert { + ("isaaclab_assets.robots.shadow_hand", "SHADOW_HAND_CFG"), + ("isaaclab_assets.robots.shadow_hand", "SHADOW_HAND_NEWTON_CFG"), + } <= imports + + @pytest.mark.parametrize( "relative_path", [ diff --git a/source/isaaclab_newton/changelog.d/fix-newton-segmentation-prototype-labels.rst b/source/isaaclab_newton/changelog.d/fix-newton-segmentation-prototype-labels.rst new file mode 100644 index 000000000000..6619b5b9fd1a --- /dev/null +++ b/source/isaaclab_newton/changelog.d/fix-newton-segmentation-prototype-labels.rst @@ -0,0 +1,9 @@ +Fixed +^^^^^ + +* Fixed the Newton Warp renderer reporting UNLABELLED semantic and instance segmentation for every + environment but the prototype when the scene spawns only the prototype + (:attr:`~isaaclab.sim.spawners.SpawnerCfg.spawn_path`) and relies on backend replication. Shapes + cloned by the physics backend now resolve their labels through the prototype environment recorded + in the clone plan, with the matched ancestor rebased into the cloned environment so instance ids + stay distinct per environment. diff --git a/source/isaaclab_newton/isaaclab_newton/renderers/newton_warp_renderer.py b/source/isaaclab_newton/isaaclab_newton/renderers/newton_warp_renderer.py index 6e65ff50f513..26b22ce63f3f 100644 --- a/source/isaaclab_newton/isaaclab_newton/renderers/newton_warp_renderer.py +++ b/source/isaaclab_newton/isaaclab_newton/renderers/newton_warp_renderer.py @@ -502,7 +502,8 @@ def create_render_data(self, spec: CameraRenderSpec) -> RenderData: or RenderBufferKind.INSTANCE_SEGMENTATION in spec.cfg.data_types ): if self._seg_mapper is None: - self._seg_mapper = NewtonSegmentationMapper(self._newton_model, self._stage, self.cfg) + clone_plan = SimulationContext.instance().get_clone_plan() + self._seg_mapper = NewtonSegmentationMapper(self._newton_model, self._stage, self.cfg, clone_plan) if RenderBufferKind.SEMANTIC_SEGMENTATION in spec.cfg.data_types: self._seg_mapper.build_mapping( RenderBufferKind.SEMANTIC_SEGMENTATION, bool(self.cfg.colorize_semantic_segmentation) diff --git a/source/isaaclab_newton/isaaclab_newton/renderers/segmentation.py b/source/isaaclab_newton/isaaclab_newton/renderers/segmentation.py index 04e701cb01b6..efd8a1aa3b6f 100644 --- a/source/isaaclab_newton/isaaclab_newton/renderers/segmentation.py +++ b/source/isaaclab_newton/isaaclab_newton/renderers/segmentation.py @@ -28,6 +28,8 @@ # Colorization (host ``random_color_from_id`` / ``pack_rgba``) and the reserved BACKGROUND / UNLABELLED # ids are shared with the RTX and OVRTX renderers to keep colorized segmentation visually consistent. +from isaaclab.cloner import path as cloner_path +from isaaclab.cloner import query as cloner_query from isaaclab.renderers.segmentation_colors import BACKGROUND_ID, UNLABELLED_ID, pack_rgba, random_color_from_id from isaaclab.utils.timer import Timer @@ -36,6 +38,8 @@ from pxr import Usd + from isaaclab.cloner import ClonePlan + _UNLABELLED_COLOR: int = 0xFF000000 """Packed RGBA color for UNLABELLED pixels: ``(0, 0, 0, 255)`` opaque black.""" @@ -254,8 +258,8 @@ def convert_shape_index_to_output(self, shape_index: wp.array, out_view: wp.arra class NewtonSegmentationMapper: """Builds per-shape segmentation lookup tables from a Newton model and its USD stage.""" - def __init__(self, model: newton.Model, stage: Usd.Stage | None, cfg) -> None: - """Initialize the mapper from the Newton model, USD stage, and renderer config. + def __init__(self, model: newton.Model, stage: Usd.Stage | None, cfg, clone_plan: ClonePlan) -> None: + """Initialize the mapper from the Newton model, USD stage, renderer config, and clone plan. Construction is cheap — it only captures references and snapshots ``model.shape_label``. Call :meth:`build_mapping` to do the actual per-shape USD resolution and id assignment. @@ -265,6 +269,9 @@ def __init__(self, model: newton.Model, stage: Usd.Stage | None, cfg) -> None: stage: The live USD stage used to read :class:`UsdSemantics.LabelsAPI` labels. May be ``None`` in stageless setups, in which case every shape is treated as unlabelled. cfg: Renderer config exposing ``semantic_filter`` and ``semantic_segmentation_mapping``. + clone_plan: The scene's published :class:`~isaaclab.cloner.ClonePlan`, used to fall back + to the prototype env when a replicated shape has no prim on the stage (backend-only + replication). See :meth:`_resolve_via_prototype`. """ self._model = model self._stage = stage @@ -276,6 +283,7 @@ def __init__(self, model: newton.Model, stage: Usd.Stage | None, cfg) -> None: # Cache of prim path -> (matched_labels or None); labels resolved with ancestor inheritance. self._matched_cache: dict[str, tuple[dict[SemanticType, SemanticLabels], SemanticPrimPath] | None] = {} self._mappings: dict[tuple[str, bool], NewtonSegmentationMapping] = {} + self._clone_plan = clone_plan def build_mapping(self, kind: _SegKind, colorize: bool) -> None: """Build and cache the :class:`NewtonSegmentationMapping` for ``kind`` at the requested colorization.""" @@ -311,10 +319,28 @@ def _resolve_semantic_match( short-circuits the traversal immediately. All newly-visited paths are back-filled with ``result`` at the end, so sibling shapes that share an ancestry prefix resolve in O(1) on subsequent calls without re-walking the hierarchy or re-querying USD. + + When the walk finds nothing, :meth:`_resolve_via_prototype` retries against the prototype + environment, covering scenes replicated only in the physics backend. """ if prim_path in self._matched_cache: return self._matched_cache[prim_path] + result = self._walk_for_labels(prim_path) + if result is None: + result = self._resolve_via_prototype(prim_path) + # Overwrite the ``None`` the walk back-filled for this path; ancestors keep theirs so + # a sibling shape re-enters the prototype fallback rather than reusing a stale miss. + self._matched_cache[prim_path] = result + return result + + def _walk_for_labels(self, prim_path: str) -> tuple[dict[SemanticType, SemanticLabels], SemanticPrimPath] | None: + """Walk ``prim_path`` up to the stage root for the nearest ancestor passing the filter. + + The stage-only half of :meth:`_resolve_semantic_match`: it performs the traversal and the + cache back-fill described there, and returns ``None`` when no ancestor carries a matching + label — including when ``prim_path`` names no prim at all. + """ result: tuple[dict[SemanticType, SemanticLabels], SemanticPrimPath] | None = None # Paths visited this traversal that were not already in the cache; back-filled at the end. traversed: list[str] = [] @@ -347,6 +373,52 @@ def _resolve_semantic_match( self._matched_cache[prim_path] = result return result + def _resolve_via_prototype( + self, prim_path: str + ) -> tuple[dict[SemanticType, SemanticLabels], SemanticPrimPath] | None: + """Resolve a replicated shape's labels through the prototype env it was cloned from. + + Newton replicates the *model*, rewriting each cloned shape's ``shape_label`` to its per-env + path (see ``isaaclab_newton.cloner.rename_builder_labels``), but a scene that spawns only + the prototype (:attr:`~isaaclab.sim.spawners.SpawnerCfg.spawn_path`) authors USD prims — + and therefore :class:`UsdSemantics.LabelsAPI` labels — for that one env only. Those clones + would otherwise resolve to UNLABELLED, so their labels are read off the prototype instead. + + The matched ancestor is rebased back onto the clone before being returned, because + ``instance_segmentation`` groups by that path: leaving it on the prototype side would + collapse every environment into a single instance id. + + Returns: + The prototype's ``(filtered_labels, matched_ancestor_path)`` with the ancestor rebased + into ``prim_path``'s environment, or ``None`` when ``prim_path`` is not a clone, or the + prototype itself is unlabelled. + + Raises: + ValueError: When ``prim_path`` is owned by multiple distinct, equally near destination + templates — a malformed clone plan, not a state to resolve around. + """ + resolved = cloner_query.path_to_source(self._clone_plan, prim_path) + if resolved is None: + # ``prim_path`` is not owned by the clone plan at all (e.g. an un-cloned ground plane or + # other static prim) — nothing to fall back to, so it stays unlabelled. + return None + source_root, _, asset_suffix = resolved + prototype_path = source_root + asset_suffix + # The prototype path is where the labels actually live, so a plain stage walk — not another + # round of clone-plan resolution — is all that's needed here. When ``prim_path`` names the + # prototype's own env, this re-walks the same path the caller already walked; the cache it + # left behind makes that a cheap no-op rather than a correctness concern. + match = self._walk_for_labels(prototype_path) + if match is None: + return None + matched, ancestor_path = match + # ``asset_suffix`` is the part of ``prim_path`` below the destination template, so trimming + # it yields this clone's counterpart of ``source_root``. An ancestor above ``source_root`` + # (a label authored outside the cloned subtree) is genuinely shared and ``rebase`` leaves + # it alone, keeping such shapes in one instance group across envs. + clone_root = prim_path[: len(prim_path) - len(asset_suffix)] if asset_suffix else prim_path + return matched, cloner_path.rebase(ancestor_path, source_root, clone_root) + def _apply_filter(self, labels: dict[SemanticType, SemanticLabels]) -> dict[SemanticType, SemanticLabels]: """Restrict ``labels`` (``{type: [labels]}``) to the types/labels passing the semantic filter. diff --git a/source/isaaclab_newton/test/renderers/test_segmentation.py b/source/isaaclab_newton/test/renderers/test_segmentation.py index f8296cbc5d03..929e03c252e6 100644 --- a/source/isaaclab_newton/test/renderers/test_segmentation.py +++ b/source/isaaclab_newton/test/renderers/test_segmentation.py @@ -12,9 +12,11 @@ import pytest pytest.importorskip("numpy") +pytest.importorskip("torch") pytest.importorskip("warp") pytest.importorskip("pxr") +import torch from isaaclab_newton.renderers.segmentation import NewtonSegmentationMapper from pxr import Usd @@ -22,10 +24,16 @@ # The color palette / reserved ids live in core and are unit-tested there # (``isaaclab/test/renderers/test_segmentation_colors.py``); here they are only an oracle for the # mapper's info-dict keys. +from isaaclab.cloner import ClonePlan from isaaclab.renderers.segmentation_colors import BACKGROUND_ID, UNLABELLED_ID, pack_rgba, random_color_from_id from isaaclab.sim.utils.semantics import add_labels +def _empty_clone_plan() -> ClonePlan: + """A clone plan owning nothing, standing in for scenes with no replicated shapes to fall back to.""" + return ClonePlan(sources=(), destinations=(), clone_mask=torch.zeros(0, 0, dtype=torch.bool)) + + def _cfg(**overrides): """Minimal renderer-cfg stand-in exposing only the fields the mapper reads.""" base = { @@ -63,7 +71,7 @@ def _model(shape_paths): def test_semantic_segmentation_shares_class_id_across_envs(): """All cartpole shapes across envs share one class id; the unlabelled ground is UNLABELLED.""" stage, shape_paths = _scene() - mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg()) + mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(), _empty_clone_plan()) mapper.build_mapping("semantic_segmentation", colorize=False) mapping = mapper.get_mapping("semantic_segmentation", colorize=False) @@ -81,7 +89,7 @@ def test_semantic_segmentation_shares_class_id_across_envs(): def test_instance_segmentation_groups_by_labelled_ancestor(): """Shapes group by their nearest labelled ancestor; idToSemantics carries the class label.""" stage, shape_paths = _scene() - mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg()) + mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(), _empty_clone_plan()) mapper.build_mapping("instance_segmentation", colorize=False) mapping = mapper.get_mapping("instance_segmentation", colorize=False) @@ -97,7 +105,7 @@ def test_instance_segmentation_groups_by_labelled_ancestor(): def test_colorize_info_keys_are_color_tuples(): """With colorization, info keys are ``(r, g, b, a)`` color tuples and a color palette is built.""" stage, shape_paths = _scene() - mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg()) + mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(), _empty_clone_plan()) mapper.build_mapping("semantic_segmentation", colorize=True) mapping = mapper.get_mapping("semantic_segmentation", colorize=True) @@ -109,7 +117,7 @@ def test_colorize_info_keys_are_color_tuples(): def test_semantic_filter_excludes_non_matching_types(): """A filter restricted to an absent type marks every shape UNLABELLED.""" stage, shape_paths = _scene() - mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(semantic_filter=["shape"])) + mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(semantic_filter=["shape"]), _empty_clone_plan()) mapper.build_mapping("semantic_segmentation", colorize=False) mapping = mapper.get_mapping("semantic_segmentation", colorize=False) @@ -135,7 +143,9 @@ def test_semantic_filter_comma_separated_type_clauses(): add_labels(stage.GetPrimAtPath("/World/robot"), labels=["cartpole"], instance_name="class") add_labels(stage.GetPrimAtPath("/World/shelf"), labels=["wood"], instance_name="material") - mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(semantic_filter="class:cartpole, material:wood")) + mapper = NewtonSegmentationMapper( + _model(shape_paths), stage, _cfg(semantic_filter="class:cartpole, material:wood"), _empty_clone_plan() + ) mapper.build_mapping("semantic_segmentation", colorize=False) mapping = mapper.get_mapping("semantic_segmentation", colorize=False) @@ -158,7 +168,7 @@ def test_ancestor_cache_prevents_redundant_get_labels_calls(): import isaaclab.sim.utils.semantics as _semantics_mod stage, shape_paths = _scene() - mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg()) + mapper = NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(), _empty_clone_plan()) original_get_labels = _semantics_mod.get_labels queried_paths: list[str] = [] @@ -174,12 +184,101 @@ def counting_get_labels(prim): assert not duplicates, f"Prims queried more than once (ancestor cache missed): {duplicates}" +def _prototype_only_scene(): + """A scene replicated by the physics backend only: USD prims exist for env_0 alone. + + Mirrors a cfg that sets :attr:`~isaaclab.sim.spawners.SpawnerCfg.spawn_path` to the prototype + env, so the clone plan spreads the asset to env_1 while the stage never gains env_1 prims. + Returns the stage, the per-shape prim-path list, and the matching clone plan. + """ + import torch + + from isaaclab.cloner import ClonePlan + + stage = Usd.Stage.CreateInMemory() + # Only the prototype env is authored; env_1 exists in the Newton model alone. + for path in ("/World/envs/env_0/Robot/pole/geom", "/World/envs/env_0/Robot/cart/geom", "/World/ground/geom"): + stage.DefinePrim(path, "Mesh") + add_labels(stage.GetPrimAtPath("/World/envs/env_0/Robot"), labels=["cartpole"], instance_name="class") + + # ``shape_label`` still names every env: Newton's replication renames the cloned shapes. + shape_paths = [ + "/World/envs/env_0/Robot/pole/geom", + "/World/envs/env_0/Robot/cart/geom", + "/World/envs/env_1/Robot/pole/geom", + "/World/envs/env_1/Robot/cart/geom", + "/World/ground/geom", + ] + plan = ClonePlan( + sources=("/World/envs/env_0/Robot",), + destinations=("/World/envs/env_{}/Robot",), + clone_mask=torch.ones(1, 2, dtype=torch.bool), + env_ids=torch.tensor([0, 1]), + ) + return stage, shape_paths, plan + + +def _mapper_with_plan(shape_paths, stage, plan, **cfg_overrides): + """Build a mapper that sees ``plan``.""" + return NewtonSegmentationMapper(_model(shape_paths), stage, _cfg(**cfg_overrides), plan) + + +def test_semantic_labels_resolve_through_prototype_env(): + """Backend-replicated shapes inherit the prototype env's class id instead of going UNLABELLED. + + When only the prototype env is authored on the stage, env_1's shapes have no prim to walk, so + before the fix they all resolved to UNLABELLED and rendered as background. + """ + stage, shape_paths, plan = _prototype_only_scene() + mapper = _mapper_with_plan(shape_paths, stage, plan) + mapper.build_mapping("semantic_segmentation", colorize=False) + mapping = mapper.get_mapping("semantic_segmentation", colorize=False) + + ids = mapping.shape_to_id.numpy().tolist() + # Every robot shape, prototype or clone, shares the one cartpole class id. + assert ids[0] == ids[1] == ids[2] == ids[3] + assert ids[0] >= 2 + assert mapping.info["idToLabels"][ids[0]] == {"class": "cartpole"} + # The un-cloned ground plane is unaffected by the fallback. + assert ids[4] == UNLABELLED_ID + + +def test_instance_ids_stay_distinct_per_env_through_prototype(): + """The prototype fallback rebases the matched ancestor, so each env keeps its own instance id.""" + stage, shape_paths, plan = _prototype_only_scene() + mapper = _mapper_with_plan(shape_paths, stage, plan) + mapper.build_mapping("instance_segmentation", colorize=False) + mapping = mapper.get_mapping("instance_segmentation", colorize=False) + + ids = mapping.shape_to_id.numpy().tolist() + assert ids[0] == ids[1], "prototype pole and cart are one instance" + assert ids[2] == ids[3], "cloned pole and cart are one instance" + assert ids[2] != ids[0], "the clone must not collapse into the prototype's instance" + assert mapping.info["idToLabels"][ids[0]] == "/World/envs/env_0/Robot" + assert mapping.info["idToLabels"][ids[2]] == "/World/envs/env_1/Robot" + assert mapping.info["idToSemantics"][ids[2]] == {"class": "cartpole"} + assert ids[4] == UNLABELLED_ID + + +def test_prototype_fallback_respects_semantic_filter(): + """A clone is UNLABELLED when the prototype's labels are filtered out, not silently labelled.""" + stage, shape_paths, plan = _prototype_only_scene() + mapper = _mapper_with_plan(shape_paths, stage, plan, semantic_filter=["shape"]) + mapper.build_mapping("semantic_segmentation", colorize=False) + mapping = mapper.get_mapping("semantic_segmentation", colorize=False) + + assert mapping.shape_to_id.numpy().tolist() == [UNLABELLED_ID] * len(shape_paths) + + def test_semantic_segmentation_mapping_overrides_color(): """``semantic_segmentation_mapping`` forces the class color and its info key.""" stage, shape_paths = _scene() override = (255, 36, 66, 255) mapper = NewtonSegmentationMapper( - _model(shape_paths), stage, _cfg(semantic_segmentation_mapping={"class:cartpole": override}) + _model(shape_paths), + stage, + _cfg(semantic_segmentation_mapping={"class:cartpole": override}), + _empty_clone_plan(), ) mapper.build_mapping("semantic_segmentation", colorize=True) mapping = mapper.get_mapping("semantic_segmentation", colorize=True) diff --git a/source/isaaclab_tasks/changelog.d/fix-reorient-replication.rst b/source/isaaclab_tasks/changelog.d/fix-reorient-replication.rst new file mode 100644 index 000000000000..97321fbc24ac --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/fix-reorient-replication.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed Shadow Hand reorientation scene setup to author only the prototype environment before + backend replication while preserving ``{ENV_REGEX_NS}`` for runtime views. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py index 604d7b5f2887..c117c99bb10e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py @@ -154,22 +154,30 @@ class ShadowHandManagerEventPresetCfg(PresetCfg): @configclass class ShadowHandRobotCfg(PresetCfg): - physx = SHADOW_HAND_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot").replace( + physx = SHADOW_HAND_CFG.replace( + prim_path="{ENV_REGEX_NS}/Robot", + spawn=SHADOW_HAND_CFG.spawn.replace(spawn_path="/World/envs/env_0/Robot"), init_state=ArticulationCfg.InitialStateCfg( pos=(0.0, 0.0, 0.5), rot=(0.0, 0.0, 0.0, 1.0), joint_pos={".*": 0.0}, - ) + ), ) isaacsim_physx = physx # Newton robot lives in the asset (see isaaclab_assets.robots.shadow_hand); reorient # uses its default gains. The handover task consumes the same asset cfg and overrides # only the finger gains. - newton_mjwarp = SHADOW_HAND_NEWTON_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") + newton_mjwarp = SHADOW_HAND_NEWTON_CFG.replace( + prim_path="{ENV_REGEX_NS}/Robot", + spawn=SHADOW_HAND_NEWTON_CFG.spawn.replace(spawn_path="/World/envs/env_0/Robot"), + ) ovphysx = SHADOW_HAND_CFG.replace( prim_path="{ENV_REGEX_NS}/Robot", # OVPhysX does not expose the fixed-tendon runtime API, so spawn without tendon overrides. - spawn=SHADOW_HAND_CFG.spawn.replace(fixed_tendons_props=None), + spawn=SHADOW_HAND_CFG.spawn.replace( + spawn_path="/World/envs/env_0/Robot", + fixed_tendons_props=None, + ), init_state=ArticulationCfg.InitialStateCfg( pos=(0.0, 0.0, 0.5), rot=(0.0, 0.0, 0.0, 1.0), @@ -182,6 +190,7 @@ class ShadowHandRobotCfg(PresetCfg): CUBE_CFG = RigidObjectCfg( prim_path="{ENV_REGEX_NS}/object", spawn=sim_utils.UsdFileCfg( + spawn_path="/World/envs/env_0/object", usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", rigid_props=sim_utils.RigidBodyPropertiesCfg( kinematic_enabled=False, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py index 072597150ac2..3f73a3f4ee41 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py @@ -75,7 +75,11 @@ class _ShadowHandBaseTiledCameraCfg(CameraCfg): ) data_types: list[str] = [] spawn: sim_utils.PinholeCameraCfg = sim_utils.PinholeCameraCfg( - focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0) + spawn_path="/World/envs/env_0/Camera", + focal_length=24.0, + focus_distance=400.0, + horizontal_aperture=20.955, + clipping_range=(0.1, 20.0), ) width: int = 120 height: int = 120