Skip to content

Commit 2112d67

Browse files
nvlukaszeric-heidenCopilot
authored
Fix root shapes in ArticulationView with fixed base (#1639)
Signed-off-by: Eric Heiden <eric-heiden@outlook.com> Co-authored-by: Eric Heiden <eric-heiden@outlook.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent a434292 commit 2112d67

2 files changed

Lines changed: 41 additions & 28 deletions

File tree

newton/_src/utils/selection.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import functools
1717
from fnmatch import fnmatch
18+
from types import NoneType
1819
from typing import Any
1920

2021
import warp as wp
@@ -1004,38 +1005,29 @@ def _get_attribute_array(self, name: str, source: Model | State | Control, _slic
10041005
# handle custom slice
10051006
if isinstance(_slice, Slice):
10061007
_slice = _slice.get()
1007-
elif isinstance(_slice, int):
1008-
_slice = slice(_slice, _slice + 1)
1008+
elif not isinstance(_slice, (NoneType, int, slice)):
1009+
raise ValueError(f"Invalid slice type: expected slice or int, got {type(_slice)}")
10091010

10101011
if _slice is None:
1012+
value_slice = layout.indices if is_indexed else layout.slice
10111013
value_count = layout.value_count
1012-
if is_indexed:
1013-
value_slice = layout.indices
1014-
else:
1015-
value_slice = layout.slice
10161014
else:
1017-
value_count = _slice.stop - _slice.start
1018-
if is_indexed:
1019-
value_slice = layout.indices[_slice]
1020-
else:
1021-
value_slice = _slice
1022-
1023-
shape = (self.world_count, self.count_per_world, value_count)
1024-
strides = (
1025-
layout.stride_between_worlds * value_stride,
1026-
layout.stride_within_worlds * value_stride,
1027-
value_stride,
1028-
)
1029-
slices = (slice(self.world_count), slice(self.count_per_world), value_slice)
1015+
value_slice = _slice
1016+
value_count = 1 if isinstance(_slice, int) else _slice.stop - _slice.start
10301017

10311018
# trailing dimensions for multidimensional attributes
10321019
trailing_shape = attrib.shape[1:]
10331020
trailing_strides = attrib.strides[1:]
10341021
trailing_slices = [slice(s) for s in trailing_shape]
10351022

1036-
shape = (*shape, *trailing_shape)
1037-
strides = (*strides, *trailing_strides)
1038-
slices = (*slices, *trailing_slices)
1023+
shape = (self.world_count, self.count_per_world, value_count, *trailing_shape)
1024+
strides = (
1025+
layout.stride_between_worlds * value_stride,
1026+
layout.stride_within_worlds * value_stride,
1027+
value_stride,
1028+
*trailing_strides,
1029+
)
1030+
slices = (slice(self.world_count), slice(self.count_per_world), value_slice, *trailing_slices)
10391031

10401032
# construct reshaped attribute array
10411033
attrib = wp.array(

newton/tests/test_selection.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,26 @@ def test_empty_selection(self):
4747
self.assertEqual(selection.get_dof_velocities(model).shape, (1, 1, 0))
4848
self.assertEqual(selection.get_dof_forces(control).shape, (1, 1, 0))
4949

50-
def test_selection_shapes(self):
50+
def _test_selection_shapes(self, floating: bool):
5151
# load articulation
5252
ant = newton.ModelBuilder()
5353
ant.add_mjcf(
5454
newton.examples.get_asset("nv_ant.xml"),
5555
ignore_names=["floor", "ground"],
56+
floating=floating,
5657
)
5758

5859
L = 9 # num links
5960
J = 9 # num joints
60-
D = 14 # num joint dofs
61-
C = 15 # num joint coords
6261
S = 13 # num shapes
6362

63+
if floating:
64+
D = 14 # num joint dofs
65+
C = 15 # num joint coords
66+
else:
67+
D = 8 # num joint dofs
68+
C = 8 # num joint coords
69+
6470
# scene with just one ant
6571
single_ant_model = ant.finalize()
6672

@@ -69,7 +75,10 @@ def test_selection_shapes(self):
6975
self.assertEqual(single_ant_view.world_count, 1)
7076
self.assertEqual(single_ant_view.count_per_world, 1)
7177
self.assertEqual(single_ant_view.get_root_transforms(single_ant_model).shape, (1, 1))
72-
self.assertEqual(single_ant_view.get_root_velocities(single_ant_model).shape, (1, 1))
78+
if floating:
79+
self.assertEqual(single_ant_view.get_root_velocities(single_ant_model).shape, (1, 1))
80+
else:
81+
self.assertIsNone(single_ant_view.get_root_velocities(single_ant_model))
7382
self.assertEqual(single_ant_view.get_link_transforms(single_ant_model).shape, (1, 1, L))
7483
self.assertEqual(single_ant_view.get_link_velocities(single_ant_model).shape, (1, 1, L))
7584
self.assertEqual(single_ant_view.get_dof_positions(single_ant_model).shape, (1, 1, C))
@@ -92,7 +101,10 @@ def test_selection_shapes(self):
92101
self.assertEqual(single_ant_per_world_view.world_count, W)
93102
self.assertEqual(single_ant_per_world_view.count_per_world, 1)
94103
self.assertEqual(single_ant_per_world_view.get_root_transforms(single_ant_per_world_model).shape, (W, 1))
95-
self.assertEqual(single_ant_per_world_view.get_root_velocities(single_ant_per_world_model).shape, (W, 1))
104+
if floating:
105+
self.assertEqual(single_ant_per_world_view.get_root_velocities(single_ant_per_world_model).shape, (W, 1))
106+
else:
107+
self.assertIsNone(single_ant_per_world_view.get_root_velocities(single_ant_per_world_model))
96108
self.assertEqual(single_ant_per_world_view.get_link_transforms(single_ant_per_world_model).shape, (W, 1, L))
97109
self.assertEqual(single_ant_per_world_view.get_link_velocities(single_ant_per_world_model).shape, (W, 1, L))
98110
self.assertEqual(single_ant_per_world_view.get_dof_positions(single_ant_per_world_model).shape, (W, 1, C))
@@ -128,7 +140,10 @@ def test_selection_shapes(self):
128140
self.assertEqual(multi_ant_per_world_view.world_count, W)
129141
self.assertEqual(multi_ant_per_world_view.count_per_world, A)
130142
self.assertEqual(multi_ant_per_world_view.get_root_transforms(multi_ant_per_world_model).shape, (W, A))
131-
self.assertEqual(multi_ant_per_world_view.get_root_velocities(multi_ant_per_world_model).shape, (W, A))
143+
if floating:
144+
self.assertEqual(multi_ant_per_world_view.get_root_velocities(multi_ant_per_world_model).shape, (W, A))
145+
else:
146+
self.assertIsNone(multi_ant_per_world_view.get_root_velocities(multi_ant_per_world_model))
132147
self.assertEqual(multi_ant_per_world_view.get_link_transforms(multi_ant_per_world_model).shape, (W, A, L))
133148
self.assertEqual(multi_ant_per_world_view.get_link_velocities(multi_ant_per_world_model).shape, (W, A, L))
134149
self.assertEqual(multi_ant_per_world_view.get_dof_positions(multi_ant_per_world_model).shape, (W, A, C))
@@ -149,6 +164,12 @@ def test_selection_shapes(self):
149164
multi_ant_per_world_view.get_attribute("shape_thickness", multi_ant_per_world_model).shape, (W, A, S)
150165
)
151166

167+
def test_selection_shapes_floating_base(self):
168+
self._test_selection_shapes(floating=True)
169+
170+
def test_selection_shapes_fixed_base(self):
171+
self._test_selection_shapes(floating=False)
172+
152173
def test_selection_shape_values_noncontiguous(self):
153174
"""Test that shape attribute values are correct when shape selection is non-contiguous."""
154175
# Build a 3-link chain: base -> link1 -> link2

0 commit comments

Comments
 (0)