@@ -148,6 +148,73 @@ def test_selection_shapes(self):
148148 multi_ant_per_world_view .get_attribute ("shape_thickness" , multi_ant_per_world_model ).shape , (W , A , S )
149149 )
150150
151+ def test_selection_shape_values_noncontiguous (self ):
152+ """Test that shape attribute values are correct when shape selection is non-contiguous."""
153+ # Build a 3-link chain: base -> link1 -> link2
154+ # Each link has one shape with a distinct thickness value
155+ robot = newton .ModelBuilder ()
156+
157+ thicknesses = [0.001 , 0.002 , 0.003 ]
158+
159+ base = robot .add_link (xform = wp .transform ([0 , 0 , 0 ], wp .quat_identity ()), mass = 1.0 , key = "base" )
160+ robot .add_shape_box (
161+ base ,
162+ hx = 0.1 ,
163+ hy = 0.1 ,
164+ hz = 0.1 ,
165+ cfg = newton .ModelBuilder .ShapeConfig (thickness = thicknesses [0 ]),
166+ key = "shape_base" ,
167+ )
168+
169+ link1 = robot .add_link (xform = wp .transform ([0 , 0 , 0.5 ], wp .quat_identity ()), mass = 0.5 , key = "link1" )
170+ robot .add_shape_capsule (
171+ link1 ,
172+ radius = 0.05 ,
173+ half_height = 0.2 ,
174+ cfg = newton .ModelBuilder .ShapeConfig (thickness = thicknesses [1 ]),
175+ key = "shape_link1" ,
176+ )
177+
178+ link2 = robot .add_link (xform = wp .transform ([0 , 0 , 1.0 ], wp .quat_identity ()), mass = 0.3 , key = "link2" )
179+ robot .add_shape_sphere (
180+ link2 ,
181+ radius = 0.05 ,
182+ cfg = newton .ModelBuilder .ShapeConfig (thickness = thicknesses [2 ]),
183+ key = "shape_link2" ,
184+ )
185+
186+ j0 = robot .add_joint_free (child = base )
187+ j1 = robot .add_joint_revolute (parent = base , child = link1 , axis = [0 , 1 , 0 ])
188+ j2 = robot .add_joint_revolute (parent = link1 , child = link2 , axis = [0 , 1 , 0 ])
189+ robot .add_articulation ([j0 , j1 , j2 ], key = "robot" )
190+
191+ W = 3
192+ scene = newton .ModelBuilder ()
193+ # add a ground plane first so shape indices are offset
194+ scene .add_shape_plane ()
195+ scene .replicate (robot , num_worlds = W )
196+ model = scene .finalize ()
197+
198+ # exclude the middle link to make shape indices non-contiguous: [0, 2]
199+ view = ArticulationView (model , "robot" , exclude_links = ["link1" ])
200+ self .assertFalse (view .shapes_contiguous , "Expected non-contiguous shape selection" )
201+ self .assertEqual (view .shape_count , 2 )
202+
203+ # read shape_thickness through ArticulationView and check values
204+ vals = view .get_attribute ("shape_thickness" , model )
205+ self .assertEqual (vals .shape , (W , 1 , 2 ))
206+ vals_np = vals .numpy ()
207+
208+ expected = [thicknesses [0 ], thicknesses [2 ]] # base and link2 (link1 excluded)
209+ for w in range (W ):
210+ for s , expected_thickness in enumerate (expected ):
211+ self .assertAlmostEqual (
212+ float (vals_np [w , 0 , s ]),
213+ expected_thickness ,
214+ places = 6 ,
215+ msg = f"world={ w } , shape={ s } " ,
216+ )
217+
151218 def test_selection_mask (self ):
152219 # load articulation
153220 ant = newton .ModelBuilder ()
0 commit comments