Source code for isaaclab_newton.sim.views.newton_site_frame_view

# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Newton-backed FrameView using Newton body labels and injected sites."""

from __future__ import annotations

import logging
import re

import warp as wp

from pxr import UsdPhysics

import isaaclab.sim as sim_utils
from isaaclab import cloner
from isaaclab.physics import PhysicsEvent
from isaaclab.sim.views.base_frame_view import BaseFrameView
from isaaclab.sim.views.xform_space_writer import FrameViewLocalSpaceWriter, FrameViewWorldSpaceWriter
from isaaclab.utils.string import resolve_matching_names
from isaaclab.utils.warp import ProxyArray

from isaaclab_newton.physics.newton_manager import NewtonManager

logger = logging.getLogger(__name__)

WORLD_BODY_INDEX = -1

# Regex metacharacters that mark a body pattern as a genuine expression rather than a literal
# USD path. Patterns free of these can be resolved via an exact dict lookup instead of scanning
# every body label with a compiled regex.
_REGEX_TOKENS = frozenset(".*[]()+?|\\^$")


def _has_regex_tokens(pattern: str) -> bool:
    """Return whether ``pattern`` contains regex metacharacters (i.e. is not a literal path)."""
    return any(token in _REGEX_TOKENS for token in pattern)


@wp.kernel
def _compute_site_world_transforms(
    body_q: wp.array(dtype=wp.transformf),
    site_body: wp.array(dtype=wp.int32),
    site_local: wp.array(dtype=wp.transformf),
    indices: wp.array(dtype=wp.int32),
    out_pos: wp.array(dtype=wp.vec3f),
    out_quat: wp.array(dtype=wp.vec4f),
):
    """Compute world-space transforms for selected sites."""
    i = wp.tid()
    si = indices[i]
    bid = site_body[si]
    if bid == WORLD_BODY_INDEX:
        world = site_local[si]
    else:
        world = wp.transform_multiply(body_q[bid], site_local[si])
    out_pos[i] = wp.transform_get_translation(world)
    q = wp.transform_get_rotation(world)
    out_quat[i] = wp.vec4f(q[0], q[1], q[2], q[3])


@wp.kernel
def _gather_site_local_transforms(
    site_local: wp.array(dtype=wp.transformf),
    indices: wp.array(dtype=wp.int32),
    out_pos: wp.array(dtype=wp.vec3f),
    out_quat: wp.array(dtype=wp.vec4f),
):
    """Gather local transforms for selected sites."""
    i = wp.tid()
    si = indices[i]
    local_tf = site_local[si]
    out_pos[i] = wp.transform_get_translation(local_tf)
    q = wp.transform_get_rotation(local_tf)
    out_quat[i] = wp.vec4f(q[0], q[1], q[2], q[3])


@wp.kernel
def _write_site_local_from_world_poses(
    body_q: wp.array(dtype=wp.transformf),
    site_body: wp.array(dtype=wp.int32),
    indices: wp.array(dtype=wp.int32),
    world_pos: wp.array(dtype=wp.vec3f),
    world_quat: wp.array(dtype=wp.vec4f),
    site_local: wp.array(dtype=wp.transformf),
):
    """Update local offsets so selected sites reach desired world poses."""
    i = wp.tid()
    si = indices[i]
    w_pos = world_pos[i]
    w_q = world_quat[i]
    desired_world = wp.transform(w_pos, wp.quatf(w_q[0], w_q[1], w_q[2], w_q[3]))

    bid = site_body[si]
    if bid == WORLD_BODY_INDEX:
        site_local[si] = desired_world
    else:
        site_local[si] = wp.transform_multiply(wp.transform_inverse(body_q[bid]), desired_world)


@wp.kernel
def _write_site_local_from_local_poses(
    indices: wp.array(dtype=wp.int32),
    local_pos: wp.array(dtype=wp.vec3f),
    local_quat: wp.array(dtype=wp.vec4f),
    site_local: wp.array(dtype=wp.transformf),
):
    """Update local offsets for selected sites."""
    i = wp.tid()
    si = indices[i]
    l_pos = local_pos[i]
    l_q = local_quat[i]
    site_local[si] = wp.transform(l_pos, wp.quatf(l_q[0], l_q[1], l_q[2], l_q[3]))


@wp.kernel
def _gather_shape_scales(
    shape_scale: wp.array(dtype=wp.vec3f),
    shape_body: wp.array(dtype=wp.int32),
    site_body: wp.array(dtype=wp.int32),
    indices: wp.array(dtype=wp.int32),
    num_shapes: wp.int32,
    out_scales: wp.array(dtype=wp.vec3f),
):
    """Gather legacy per-site geometry scales from collision shapes on the same body."""
    i = wp.tid()
    si = indices[i]
    bid = site_body[si]
    found = int(0)
    for s in range(num_shapes):
        if shape_body[s] == bid and found == 0:
            out_scales[i] = shape_scale[s]
            found = 1
    if found == 0:
        out_scales[i] = wp.vec3f(1.0, 1.0, 1.0)


@wp.kernel
def _scatter_shape_scales(
    site_body: wp.array(dtype=wp.int32),
    indices: wp.array(dtype=wp.int32),
    new_scales: wp.array(dtype=wp.vec3f),
    shape_body: wp.array(dtype=wp.int32),
    num_shapes: wp.int32,
    shape_scale: wp.array(dtype=wp.vec3f),
):
    """Scatter legacy per-site geometry scales to collision shapes on the same body."""
    i = wp.tid()
    si = indices[i]
    bid = site_body[si]
    for s in range(num_shapes):
        if shape_body[s] == bid:
            shape_scale[s] = new_scales[i]


@wp.kernel
def _gather_xform_scales(
    site_xform_scale: wp.array(dtype=wp.vec3f),
    indices: wp.array(dtype=wp.int32),
    out_scales: wp.array(dtype=wp.vec3f),
):
    """Gather per-site xform scales."""
    i = wp.tid()
    out_scales[i] = site_xform_scale[indices[i]]


@wp.kernel
def _scatter_xform_scales(
    indices: wp.array(dtype=wp.int32),
    new_scales: wp.array(dtype=wp.vec3f),
    site_xform_scale: wp.array(dtype=wp.vec3f),
):
    """Scatter per-site xform scales."""
    i = wp.tid()
    site_xform_scale[indices[i]] = new_scales[i]


[docs] class NewtonSiteFrameView(BaseFrameView): """Batched Newton site view for non-physics frames. The public construction contract matches the generic :class:`FrameView`: callers provide a prim expression and the backend resolves the source prim into Newton body-local or world-local sites. """
[docs] def __init__( self, prim_path: str | list[str], device: str = "cpu", validate_xform_ops: bool = True, stage: object | None = None, **kwargs, ): """Initialize the Newton site frame view. Args: prim_path: User-facing frame path pattern, or list of patterns. device: Warp device for GPU arrays. validate_xform_ops: Whether to validate source USD xform ops. stage: USD stage that contains the source prims. **kwargs: Unused. """ del kwargs self._prim_paths = [prim_path] if isinstance(prim_path, str) else list(prim_path) self._prim_path = prim_path if isinstance(prim_path, str) else ", ".join(self._prim_paths) self._device = device self._prims = [] stage = sim_utils.get_current_stage() if stage is None else stage self._site_specs = self._resolve_site_specs(stage, validate_xform_ops) self._site_labels: list[str] = [] self._site_label_scales: list[tuple[float, float, float]] = [] self._site_body: wp.array | None = None self._site_local: wp.array | None = None self._site_xform_scale: wp.array | None = None self._site_indices: wp.array | None = None self._pos_buf: wp.array | None = None self._quat_buf: wp.array | None = None self._local_pos_buf: wp.array | None = None self._local_quat_buf: wp.array | None = None self._scale_buf: wp.array | None = None self._pos_ta: ProxyArray | None = None self._quat_ta: ProxyArray | None = None self._local_pos_ta: ProxyArray | None = None self._local_quat_ta: ProxyArray | None = None self._scale_ta: ProxyArray | None = None self._count = 0 model = NewtonManager.get_model() if model is not None: self._initialize_from_specs(model) else: for body_patterns, xform, scale, per_world, _env_ids in self._site_specs: if body_patterns is None: self._site_labels.append(NewtonManager.cl_register_site(None, xform, per_world=per_world)) self._site_label_scales.append(scale) else: for body_pattern in body_patterns: self._site_labels.append(NewtonManager.cl_register_site(body_pattern, xform)) self._site_label_scales.append(scale) self._physics_ready_handle = NewtonManager.register_callback( self._on_physics_ready, PhysicsEvent.PHYSICS_READY, name=f"site_view_{self._prim_path}" )
def _resolve_site_specs( self, stage, validate_xform_ops: bool ) -> list[tuple[tuple[str, ...] | None, wp.transform, tuple[float, float, float], bool, tuple[int, ...] | None]]: """Resolve source prims into Newton site registration specs.""" plan = sim_utils.SimulationContext.instance().get_clone_plan() model = NewtonManager.get_model() body_labels = list(model.body_label) if model is not None else () shape_labels = list(model.shape_label) if model is not None else () use_clone_body_pattern = model is None specs: list[ tuple[tuple[str, ...] | None, wp.transform, tuple[float, float, float], bool, tuple[int, ...] | None] ] = [] for path_expr in self._prim_paths: if resolve_matching_names(path_expr, body_labels, raise_when_no_match=False)[1]: raise ValueError( f"FrameView prim '{path_expr}' is a Newton physics body. " "FrameView should only be used for non-physics frames." ) if resolve_matching_names(path_expr, shape_labels, raise_when_no_match=False)[1]: raise ValueError( f"FrameView prim '{path_expr}' is a Newton collision shape. " "FrameView should only be used for non-physics frames." ) matches = tuple(cloner.query.iter_sources(plan, path_expr)) if plan is not None else () if matches: for source_root, destination_template, source_path, env_ids in matches: source_pattern = re.compile(source_path) source_prims = sim_utils.get_all_matching_child_prims( source_root, lambda prim: source_pattern.fullmatch(prim.GetPath().pathString) is not None, stage=stage, ) if not source_prims: raise RuntimeError(f"FrameView '{path_expr}' could not resolve source prim '{source_path}'.") specs.extend( self._resolve_source_prim( source_prim, validate_xform_ops, source_root, destination_template, env_ids, use_clone_body_pattern, stage, ) for source_prim in source_prims ) continue prims = sim_utils.find_matching_prims(path_expr, stage) if not prims: raise RuntimeError(f"FrameView '{path_expr}' could not resolve a source prim.") specs.extend( self._resolve_source_prim(prim, validate_xform_ops, None, None, None, use_clone_body_pattern, stage) for prim in prims ) return specs def _resolve_source_prim( self, prim, validate_xform_ops: bool, source_root: str | None, destination_template: str | None, env_ids: tuple[int, ...] | None, use_clone_body_pattern: bool, stage, ) -> tuple[tuple[str, ...] | None, wp.transform, tuple[float, float, float], bool, tuple[int, ...] | None]: """Resolve one source prim into body patterns, local frame, and xform scale.""" prim_path = prim.GetPath().pathString if prim.HasAPI(UsdPhysics.RigidBodyAPI) or prim.HasAPI(UsdPhysics.ArticulationRootAPI): raise ValueError( f"FrameView prim '{prim_path}' is a Newton physics body. " "FrameView should only be used for non-physics frames." ) if validate_xform_ops: sim_utils.standardize_xform_ops(prim) if not sim_utils.validate_standard_xform_ops(prim): raise ValueError(f"FrameView prim '{prim_path}' does not have standard xform ops.") scale_attr = prim.GetAttribute("xformOp:scale") scale = ( tuple(float(v) for v in scale_attr.Get()) if scale_attr and scale_attr.HasAuthoredValue() else (1.0, 1.0, 1.0) ) body_prim = prim.GetParent() while body_prim and body_prim.IsValid(): if body_prim.HasAPI(UsdPhysics.RigidBodyAPI) or body_prim.HasAPI(UsdPhysics.ArticulationRootAPI): pos, quat = sim_utils.resolve_prim_pose(prim, body_prim) body_path = body_prim.GetPath().pathString if source_root is not None and destination_template is not None: assert env_ids is not None if body_path == source_root: suffix = "" elif body_path.startswith(source_root + "/"): suffix = body_path[len(source_root) :] elif source_root.startswith(body_path + "/"): suffix = source_root[len(body_path) :] if use_clone_body_pattern: destination_root = destination_template.format(".*") if not destination_root.endswith(suffix): raise RuntimeError( f"FrameView destination root '{destination_root}' does not end with '{suffix}'." ) return (destination_root[: -len(suffix)],), wp.transform(pos, quat), scale, False, env_ids body_patterns = [] for env_id in env_ids: destination_root = destination_template.format(env_id) if not destination_root.endswith(suffix): raise RuntimeError( f"FrameView destination root '{destination_root}' does not end with '{suffix}'." ) body_patterns.append(destination_root[: -len(suffix)]) return tuple(body_patterns), wp.transform(pos, quat), scale, False, env_ids else: raise RuntimeError(f"FrameView source body '{body_path}' is not under '{source_root}'.") if use_clone_body_pattern: body_patterns = (destination_template.format(".*") + suffix,) else: body_patterns = tuple(destination_template.format(env_id) + suffix for env_id in env_ids) else: body_patterns = (body_path,) return body_patterns, wp.transform(pos, quat), scale, False, env_ids body_prim = body_prim.GetParent() ref_path = source_root if source_root is not None and destination_template is not None: template_prefix, _ = cloner.path.split(destination_template) source_suffix = cloner.path.relativize(source_root, template_prefix + "{}") if source_suffix is not None: ref_path = source_root[: -len(source_suffix)] if source_suffix else source_root ref_prim = stage.GetPrimAtPath(ref_path) if ref_path is not None else None pos, quat = sim_utils.resolve_prim_pose(prim, ref_prim if ref_prim and ref_prim.IsValid() else None) return None, wp.transform(pos, quat), scale, source_root is not None, env_ids def _on_physics_ready(self, _event) -> None: """Callback invoked when the Newton model becomes available.""" self._initialize_from_site_map(NewtonManager.get_model()) def _initialize_from_site_map(self, model) -> None: """Initialize arrays from injected Newton sites.""" site_map = NewtonManager._cl_site_index_map body_t = wp.to_torch(model.shape_body) xform_t = wp.to_torch(model.shape_transform) site_bodies: list[int] = [] site_locals: list[list[float]] = [] site_scales: list[tuple[float, float, float]] = [] for site_label, scale in zip(self._site_labels, self._site_label_scales, strict=True): global_idx, per_world = site_map[site_label] site_indices = ( [global_idx] if per_world is None else [site_idx for sites in per_world for site_idx in sites] ) for site_idx in site_indices: site_bodies.append(int(body_t[site_idx].item())) site_locals.append([float(v) for v in xform_t[site_idx].tolist()]) site_scales.append(scale) self._create_buffers(site_bodies, site_locals, site_scales) def _initialize_from_specs(self, model) -> None: """Initialize arrays directly from resolved specs and Newton body labels.""" body_labels = list(model.body_label) # Exact label -> index map, built once. Replicated frames expand to one concrete # body path per environment, so matching each against every label via regex is # ``O(num_envs * num_bodies)`` (quadratic in ``num_envs``). Fast-pathing literal # paths through this map keeps the common per-environment case linear; genuine # regex patterns (e.g. the cloned ``.*`` pattern) still fall back to a full scan. label_to_index = {label: idx for idx, label in enumerate(body_labels)} site_bodies: list[int] = [] site_locals: list[list[float]] = [] site_scales: list[tuple[float, float, float]] = [] for body_patterns, xform, scale, per_world, env_ids in self._site_specs: if body_patterns is None: if per_world: if NewtonManager._world_xforms is None: raise RuntimeError(f"FrameView '{self._prim_path}' needs Newton cloned-world transforms.") world_ids = range(len(NewtonManager._world_xforms)) if env_ids is None else env_ids for world_id in world_ids: world_xform = NewtonManager._world_xforms[world_id] site_bodies.append(WORLD_BODY_INDEX) site_locals.append([float(v) for v in wp.transform_multiply(world_xform, xform)]) site_scales.append(scale) else: site_bodies.append(WORLD_BODY_INDEX) site_locals.append([float(v) for v in xform]) site_scales.append(scale) continue for body_pattern in body_patterns: exact_index = label_to_index.get(body_pattern) if not _has_regex_tokens(body_pattern) else None if exact_index is not None: matched_indices = [exact_index] else: matched_indices, _ = resolve_matching_names(body_pattern, body_labels, raise_when_no_match=False) if not matched_indices: raise ValueError( f"FrameView '{self._prim_path}' body pattern '{body_pattern}' matched no Newton bodies." ) for body_idx in matched_indices: site_bodies.append(body_idx) site_locals.append([float(v) for v in xform]) site_scales.append(scale) self._create_buffers(site_bodies, site_locals, site_scales) def _create_buffers( self, site_bodies: list[int], site_locals: list[list[float]], site_scales: list[tuple[float, float, float]], ) -> None: """Allocate view buffers from body indices and local transforms.""" self._count = len(site_bodies) device = self._device self._site_body = wp.array(site_bodies, dtype=wp.int32, device=device) self._site_local = wp.array([wp.transform(*x) for x in site_locals], dtype=wp.transformf, device=device) self._site_xform_scale = wp.array([wp.vec3f(*scale) for scale in site_scales], dtype=wp.vec3f, device=device) self._site_indices = wp.array(list(range(self._count)), dtype=wp.int32, device=device) self._pos_buf = wp.zeros(self._count, dtype=wp.vec3f, device=device) self._quat_buf = wp.zeros(self._count, dtype=wp.vec4f, device=device) self._local_pos_buf = wp.zeros(self._count, dtype=wp.vec3f, device=device) self._local_quat_buf = wp.zeros(self._count, dtype=wp.vec4f, device=device) self._scale_buf = wp.zeros(self._count, dtype=wp.vec3f, device=device) self._pos_ta = ProxyArray(self._pos_buf) self._quat_ta = ProxyArray(self._quat_buf) self._local_pos_ta = ProxyArray(self._local_pos_buf) self._local_quat_ta = ProxyArray(self._local_quat_buf) self._scale_ta = ProxyArray(self._site_xform_scale) @property def prims(self) -> list: """List of USD prims being managed by this view. Newton site views do not retain USD prim handles. """ return self._prims @property def count(self) -> int: """Number of frames in this view.""" return self._count @property def device(self) -> str: """Device where arrays are allocated.""" return self._device # ------------------------------------------------------------------ # Writer factory hooks (pass-through; Newton has no separate Fabric storage) # ------------------------------------------------------------------ def _make_world_space_writer(self) -> FrameViewWorldSpaceWriter: return _NewtonWorldSpaceWriter(self) def _make_local_space_writer(self) -> FrameViewLocalSpaceWriter: return _NewtonLocalSpaceWriter(self) # ------------------------------------------------------------------ # Backend hooks # ------------------------------------------------------------------ def _get_world_poses_impl(self, indices: wp.array | None = None) -> tuple[ProxyArray, ProxyArray]: """Get world-space positions and orientations.""" state = NewtonManager.get_state_0() site_indices = self._site_indices if indices is None else indices n = self.count if indices is None else len(indices) pos_buf = self._pos_buf if indices is None else wp.zeros(n, dtype=wp.vec3f, device=self._device) quat_buf = self._quat_buf if indices is None else wp.zeros(n, dtype=wp.vec4f, device=self._device) wp.launch( _compute_site_world_transforms, dim=n, inputs=[state.body_q, self._site_body, self._site_local, site_indices], outputs=[pos_buf, quat_buf], device=self._device, ) if indices is None: return self._pos_ta, self._quat_ta return ProxyArray(pos_buf), ProxyArray(quat_buf) def _apply_world_pose_write( self, positions: wp.array | None = None, orientations: wp.array | None = None, indices: wp.array | None = None, ) -> None: """Set world-space positions and/or orientations.""" if positions is None and orientations is None: return state = NewtonManager.get_state_0() if positions is None or orientations is None: cur_pos_ta, cur_quat_ta = self._get_world_poses_impl(indices) if positions is None: positions = cur_pos_ta.warp if orientations is None: orientations = cur_quat_ta.warp site_indices = self._site_indices if indices is None else indices n = self.count if indices is None else len(indices) wp.launch( _write_site_local_from_world_poses, dim=n, inputs=[state.body_q, self._site_body, site_indices, positions, orientations, self._site_local], device=self._device, ) def _get_local_poses_impl(self, indices: wp.array | None = None) -> tuple[ProxyArray, ProxyArray]: """Get body-local positions and orientations.""" site_indices = self._site_indices if indices is None else indices n = self.count if indices is None else len(indices) pos_buf = self._local_pos_buf if indices is None else wp.zeros(n, dtype=wp.vec3f, device=self._device) quat_buf = self._local_quat_buf if indices is None else wp.zeros(n, dtype=wp.vec4f, device=self._device) wp.launch( _gather_site_local_transforms, dim=n, inputs=[self._site_local, site_indices], outputs=[pos_buf, quat_buf], device=self._device, ) if indices is None: return self._local_pos_ta, self._local_quat_ta return ProxyArray(pos_buf), ProxyArray(quat_buf) def _apply_local_pose_write( self, translations: wp.array | None = None, orientations: wp.array | None = None, indices: wp.array | None = None, ) -> None: """Set body-local translations and/or orientations.""" if translations is None and orientations is None: return if translations is None or orientations is None: cur_pos_ta, cur_quat_ta = self._get_local_poses_impl(indices) if translations is None: translations = cur_pos_ta.warp if orientations is None: orientations = cur_quat_ta.warp site_indices = self._site_indices if indices is None else indices n = self.count if indices is None else len(indices) wp.launch( _write_site_local_from_local_poses, dim=n, inputs=[site_indices, translations, orientations, self._site_local], device=self._device, ) # ------------------------------------------------------------------ # Scales # ------------------------------------------------------------------ def _get_world_scales_impl(self, indices: wp.array | None = None) -> ProxyArray: """Get per-site world xform scales. These are transform scales, matching the USD FrameView scale API. They are intentionally separate from Newton collision shape geometry sizes. """ if indices is None: return self._scale_ta n = len(indices) out = wp.zeros(n, dtype=wp.vec3f, device=self._device) wp.launch( _gather_xform_scales, dim=n, inputs=[self._site_xform_scale, indices], outputs=[out], device=self._device, ) return ProxyArray(out) def _get_local_scales_impl(self, indices: wp.array | None = None) -> ProxyArray: """Get per-site local xform scales. These are transform scales, matching the USD FrameView scale API. They are intentionally separate from Newton collision shape geometry sizes. """ return self._get_world_scales_impl(indices) def _apply_world_scale_write(self, scales: wp.array, indices: wp.array | None = None) -> None: """Set per-site world xform scales. These update transform scale state only; use deprecated ``set_scales`` if legacy Newton collision shape geometry-scale behavior is required. """ if indices is None: indices = self._site_indices n = self.count if indices is self._site_indices else len(indices) wp.launch( _scatter_xform_scales, dim=n, inputs=[indices, scales, self._site_xform_scale], device=self._device, ) def _apply_local_scale_write(self, scales: wp.array, indices: wp.array | None = None) -> None: """Set per-site local xform scales. These update transform scale state only; use deprecated ``set_scales`` if legacy Newton collision shape geometry-scale behavior is required. """ self._apply_world_scale_write(scales, indices) def _get_legacy_shape_scales(self, indices: wp.array | None = None) -> ProxyArray: """Get Newton legacy geometry scales from collision shapes.""" model = NewtonManager.get_model() num_shapes = model.shape_count site_indices = self._site_indices if indices is None else indices n = self.count if indices is None else len(indices) out = wp.zeros(n, dtype=wp.vec3f, device=self._device) wp.launch( _gather_shape_scales, dim=n, inputs=[model.shape_scale, model.shape_body, self._site_body, site_indices, num_shapes], outputs=[out], device=self._device, ) return ProxyArray(out) def _set_legacy_shape_scales(self, scales: wp.array, indices: wp.array | None = None) -> None: """Set Newton legacy geometry scales on collision shapes.""" model = NewtonManager.get_model() num_shapes = model.shape_count site_indices = self._site_indices if indices is None else indices n = self.count if indices is None else len(indices) wp.launch( _scatter_shape_scales, dim=n, inputs=[self._site_body, site_indices, scales, model.shape_body, num_shapes, model.shape_scale], device=self._device, ) def _get_scales_impl(self, indices: wp.array | None = None) -> ProxyArray: """Newton legacy: get_scales returns collision shape geometry scales.""" return self._get_legacy_shape_scales(indices) def _set_scales_impl(self, scales: wp.array, indices: wp.array | None = None) -> None: """Newton legacy: deprecated set_scales writes collision shape geometry scales. Newton's legacy ``set_scales`` path is *not* routed through the :class:`FrameViewSpaceWriterBase` API because it targets a different state (collision-shape geometry sizes) than the transform-scale state that the writer's :meth:`~FrameViewSpaceWriterBase.set_scales` operates on. """ self._set_legacy_shape_scales(scales, indices)
# ---------------------------------------------------------------------- # Pass-through writer classes # ---------------------------------------------------------------------- class _NewtonWorldSpaceWriter(FrameViewWorldSpaceWriter): """Newton world-space writer: pass-through to backend ``_apply_*`` hooks.""" def set_poses(self, positions=None, orientations=None, indices=None) -> None: self._view._apply_world_pose_write(positions, orientations, indices) # type: ignore[attr-defined] def set_scales(self, scales, indices=None) -> None: self._view._apply_world_scale_write(scales, indices) # type: ignore[attr-defined] def get_poses(self, indices=None) -> tuple[ProxyArray, ProxyArray]: return self._view._get_world_poses_impl(indices) # type: ignore[attr-defined] def get_scales(self, indices=None) -> ProxyArray: return self._view._get_world_scales_impl(indices) # type: ignore[attr-defined] class _NewtonLocalSpaceWriter(FrameViewLocalSpaceWriter): """Newton local-space writer: pass-through to backend ``_apply_*`` hooks.""" def set_poses(self, positions=None, orientations=None, indices=None) -> None: self._view._apply_local_pose_write(positions, orientations, indices) # type: ignore[attr-defined] def set_scales(self, scales, indices=None) -> None: self._view._apply_local_scale_write(scales, indices) # type: ignore[attr-defined] def get_poses(self, indices=None) -> tuple[ProxyArray, ProxyArray]: return self._view._get_local_poses_impl(indices) # type: ignore[attr-defined] def get_scales(self, indices=None) -> ProxyArray: return self._view._get_local_scales_impl(indices) # type: ignore[attr-defined]