Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ jobs:
test_rendering_lift_kuka_homo.py,
test_rendering_franka_cloth.py,
test_rendering_franka_soft.py,
test_rendering_franka_cable.py,
test_rendering_registered_tasks.py,
test_rendering_shadow_hand.py
test-node-ids-file: ${{ github.event_name == 'push' && '.github/test-subsets/postmerge-rendering.toml' || '' }}
Expand Down Expand Up @@ -843,6 +844,7 @@ jobs:
test_rendering_lift_kuka_homo_kitless.py,
test_rendering_franka_cloth_kitless.py,
test_rendering_franka_soft_kitless.py,
test_rendering_franka_cable_kitless.py,
test_rendering_shadow_hand_kitless.py
test-k-expr: legacy
test-node-ids-file: ${{ github.event_name == 'push' && '.github/test-subsets/postmerge-rendering.toml' || '' }}
Expand Down Expand Up @@ -887,6 +889,7 @@ jobs:
test_rendering_lift_kuka_homo_kitless.py,
test_rendering_franka_cloth_kitless.py,
test_rendering_franka_soft_kitless.py,
test_rendering_franka_cable_kitless.py,
test_rendering_shadow_hand_kitless.py
test-k-expr: ovstage
test-node-ids-file: ${{ github.event_name == 'push' && '.github/test-subsets/postmerge-rendering.toml' || '' }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Added
^^^^^

* Added :meth:`~isaaclab_newton.physics.NewtonManager.collect_cable_segment_shapes`, which maps each
renderable cable prim path to its ordered Newton segment shape ids. It reads only the Newton model
and the USD stage, so kit-less renderers can drive cable points without a Fabric sync path.

Changed
^^^^^^^

* Changed :meth:`~isaaclab_newton.physics.NewtonManager.sync_cables_to_usd` to select its Fabric
prims on the simulation device and run its kernel there, instead of mirroring the Newton model to
the host and running on the CPU. The host mirror existed because the RTX Hydra render delegate
could not read GPU-backed Fabric arrays for ``BasisCurves.points``; that gap is fixed upstream.
This removes a device-to-host copy of the whole model's ``body_q`` on every dirty render frame,
which scaled with total body count rather than with the number of cable segments actually read.

Fixed
^^^^^

* Fixed the Fabric cable sync silently doing nothing for a whole session when the Fabric stage was
not yet available at ``start_simulation``. The stage handle was resolved exactly once and cached,
so cables simulated correctly and rendered frozen at their spawn pose. The cable sync now
re-acquires the stage on first use, and ``start_simulation`` warns instead of dereferencing a
handle it does not have.
* Fixed cables rendering frozen under Kit-based renderers even when the Fabric write succeeded. The
sync writes ``points`` in place from a Warp kernel, which leaves a render delegate's cached curve
untouched, so each cable prim is now invalidated explicitly after the write.
* Fixed :class:`~isaaclab_newton.physics.NewtonManager` being unimportable inside a Kit session
whose bundled Warp lags the one Newton's solvers require, by deferring a module-level
``newton.solvers`` import.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import re
from collections.abc import Sequence
from typing import TYPE_CHECKING

Expand All @@ -22,6 +23,7 @@
from isaaclab.utils.warp import ProxyArray

from isaaclab_newton.physics import NewtonManager as SimulationManager
from isaaclab_newton.physics.newton_manager import CableRegistryEntry

from .cable_object_data import CableObjectData
from .kernels import (
Expand Down Expand Up @@ -215,12 +217,59 @@ def is_cable_curve(prim) -> bool:
self._ALL_ENV_MASK = wp.ones((self.num_instances,), dtype=wp.bool, device=self.device)

self._data = CableObjectData(self.root_view, self.device)
self._curve_path_expr = curve_path_expr
self._register_cable_render_entry()
self._physics_ready_handle = SimulationManager.register_callback(
self._rebind,
PhysicsEvent.PHYSICS_READY,
name=f"cable_object_rebind_{self.cfg.prim_path}",
)

def _resolve_curve_path_for_env(self, env_idx: int) -> str:
"""Resolve the per-env concrete BasisCurves path from the stored path expression."""
path = self._curve_path_expr
# Regex-style env wildcard (``env_.*``), matching deformable fabric registration.
resolved = re.sub(r"(?<=[Ee]nv_)\.\*", str(env_idx), path)
if resolved != path:
return resolved
# Glob-style env wildcard (``env_*``) returned by resolve_matching_prims_from_source.
resolved = re.sub(r"(?<=[Ee]nv_)\*", str(env_idx), path)
if resolved != path:
return resolved
if self.num_instances == 1:
return path
raise RuntimeError(
f"CableObject '{self.cfg.prim_path}' curve path '{self._curve_path_expr}' has no"
f" env wildcard but num_instances={self.num_instances}."
)

def _register_cable_render_entry(self) -> None:
"""Publish this cable's curve prims and Newton segment shape ids for Fabric/OVRTX."""
shape_groups = SimulationManager._cable_shape_groups_from_model()
instance_curve_paths: list[str] = []
instance_segment_shape_ids: list[list[int]] = []
for env_idx in range(self.num_instances):
curve_path = self._resolve_curve_path_for_env(env_idx)
segments = shape_groups.get(curve_path)
if segments is None:
raise RuntimeError(
f"CableObject '{self.cfg.prim_path}' could not resolve Newton segment shapes for"
f" curve prim '{curve_path}' (env {env_idx})."
)
if set(segments) != set(range(self.num_segments)):
raise RuntimeError(f"CableObject '{curve_path}' requires {self.num_segments} ordered segment shapes.")
instance_curve_paths.append(curve_path)
instance_segment_shape_ids.append([segments[segment] for segment in range(self.num_segments)])

SimulationManager.register_cable_entry(
CableRegistryEntry(
curve_prim_path=self._curve_path_expr,
segments_per_cable=self.num_segments,
instance_curve_paths=instance_curve_paths,
instance_segment_shape_ids=instance_segment_shape_ids,
)
)

def _resolve_env_ids(self, env_ids: Sequence[int] | torch.Tensor | wp.array(dtype=wp.int32) | None) -> wp.array(
dtype=wp.int32
):
Expand Down Expand Up @@ -271,6 +320,7 @@ def _iter_states(self):
def _rebind(self, _: object) -> None:
"""Rebind simulation arrays after a Newton model rebuild."""
self._data._create_simulation_bindings()
self._register_cable_render_entry()

def _clear_callbacks(self) -> None:
"""Clear all registered callbacks."""
Expand Down
Loading
Loading