Skip to content

Commit 4ce5ca8

Browse files
authored
[BUG FIX] Fix URDF color overwrite. (#2065)
* Fix URDF color overwrite. * Fix zero-copy being disabled for numpy. * Add warning when enabling requires_grad while using GsTaichi dynamic arrays. * More robust unit tests.
1 parent a2787f5 commit 4ce5ca8

File tree

10 files changed

+139
-89
lines changed

10 files changed

+139
-89
lines changed

genesis/engine/scene.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ def _validate_options(
276276
gs.raise_exception("`renderer` should be an instance of `gs.renderers.Renderer`.")
277277

278278
# Validate rigid_options against sim_options
279+
if sim_options.requires_grad and gs.use_ndarray:
280+
gs.logger.warning(
281+
"Use GsTaichi dynamic array mode while enabling gradient computation is not recommended. Please "
282+
"enable performance mode at init for efficiency, e.g. 'gs.init(..., performance_mode=True)'."
283+
)
279284
if rigid_options.box_box_detection is None:
280285
rigid_options.box_box_detection = not sim_options.requires_grad
281286
elif rigid_options.box_box_detection and sim_options.requires_grad:

genesis/engine/solvers/rigid/rigid_solver_decomp.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ def build(self):
258258
sparse_solve=self._options.sparse_solve,
259259
integrator=self._integrator,
260260
solver_type=self._options.constraint_solver,
261-
is_backward=False,
262261
)
263262
# Add terms for static inner loops, use -1 if not requires_grad to avoid re-compilation
264263
if self.sim.options.requires_grad:
@@ -281,7 +280,6 @@ def build(self):
281280
enable_collision=self._enable_collision,
282281
integrator=gs.integrator.approximate_implicitfast,
283282
solver_type=gs.constraint_solver.CG,
284-
is_backward=False,
285283
)
286284

287285
if self._static_rigid_sim_config.requires_grad:
@@ -7103,11 +7101,9 @@ def kernel_update_geoms_render_T(
71037101
ti.loop_config(serialize=static_rigid_sim_config.para_level < gs.PARA_LEVEL.ALL)
71047102
for i_g, i_b in ti.ndrange(n_geoms, _B):
71057103
geom_T = gu.ti_trans_quat_to_T(
7106-
geoms_state.pos[i_g, i_b] + rigid_global_info.envs_offset[i_b],
7107-
geoms_state.quat[i_g, i_b],
7108-
EPS,
7104+
geoms_state.pos[i_g, i_b] + rigid_global_info.envs_offset[i_b], geoms_state.quat[i_g, i_b], EPS
71097105
)
7110-
for J in ti.static(ti.grouped(ti.static(ti.ndrange(4, 4)))):
7106+
for J in ti.static(ti.grouped(ti.ndrange(4, 4))):
71117107
geoms_render_T[(i_g, i_b, *J)] = ti.cast(geom_T[J], ti.float32)
71127108

71137109

@@ -7127,9 +7123,7 @@ def kernel_update_vgeoms_render_T(
71277123
ti.loop_config(serialize=static_rigid_sim_config.para_level < gs.PARA_LEVEL.ALL)
71287124
for i_g, i_b in ti.ndrange(n_vgeoms, _B):
71297125
geom_T = gu.ti_trans_quat_to_T(
7130-
vgeoms_state.pos[i_g, i_b] + rigid_global_info.envs_offset[i_b],
7131-
vgeoms_state.quat[i_g, i_b],
7132-
EPS,
7126+
vgeoms_state.pos[i_g, i_b] + rigid_global_info.envs_offset[i_b], vgeoms_state.quat[i_g, i_b], EPS
71337127
)
71347128
for J in ti.static(ti.grouped(ti.ndrange(4, 4))):
71357129
vgeoms_render_T[(i_g, i_b, *J)] = ti.cast(geom_T[J], ti.float32)

genesis/utils/array_class.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,10 +1795,10 @@ class StructRigidSimStaticConfig(metaclass=AutoInitMeta):
17951795
batch_links_info: bool = False
17961796
batch_dofs_info: bool = False
17971797
batch_joints_info: bool = False
1798-
enable_mujoco_compatibility: bool = False
1798+
enable_mujoco_compatibility: bool = True
17991799
enable_multi_contact: bool = False
18001800
enable_joint_limit: bool = False
1801-
box_box_detection: bool = True
1801+
box_box_detection: bool = False
18021802
sparse_solve: bool = False
18031803
integrator: int = gs.integrator.approximate_implicitfast
18041804
solver_type: int = gs.constraint_solver.CG

genesis/utils/misc.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -553,11 +553,10 @@ def ti_to_python(
553553
data_type = type(value)
554554
use_zerocopy = (
555555
gs.use_zerocopy
556-
and (to_torch or gs.backend == gs.cpu)
557556
and not issubclass(data_type, ti.Field)
558557
# and (gs.backend != gs.metal or not issubclass(data_type, ti.Field))
559558
)
560-
if not use_zerocopy:
559+
if not use_zerocopy or (not to_torch and gs.backend != gs.cpu):
561560
if copy is False:
562561
gs.raise_exception(
563562
"Specifying 'copy=False' is not supported if 'gs.use_zerocopy=False' or ('to_torch=False' and "
@@ -570,21 +569,22 @@ def ti_to_python(
570569
# Leverage zero-copy if enabled
571570
batch_shape = value.shape
572571
if use_zerocopy:
573-
try:
574-
if to_torch or gs.backend != gs.cpu:
575-
out = value._T_tc if transpose else value._tc
576-
else:
577-
out = value._T_np if transpose else value._np
578-
except AttributeError:
579-
value._tc = torch.utils.dlpack.from_dlpack(value.to_dlpack())
580-
value._T_tc = value._tc.movedim(batch_ndim - 1, 0) if (batch_ndim := len(batch_shape)) > 1 else value._tc
581-
if to_torch:
582-
out = value._T_tc if transpose else value._tc
583-
if gs.backend == gs.cpu:
584-
value._np = value._tc.numpy()
585-
value._T_np = value._T_tc.numpy()
586-
if not to_torch:
572+
while True:
573+
try:
574+
if to_torch or gs.backend != gs.cpu:
575+
out = value._T_tc if transpose else value._tc
576+
else:
587577
out = value._T_np if transpose else value._np
578+
break
579+
except AttributeError:
580+
value._tc = torch.utils.dlpack.from_dlpack(value.to_dlpack())
581+
if (batch_ndim := len(batch_shape)) > 1:
582+
value._T_tc = value._tc.movedim(batch_ndim - 1, 0)
583+
else:
584+
value._T_tc = value._tc
585+
if gs.backend == gs.cpu:
586+
value._np = value._tc.numpy()
587+
value._T_np = value._T_tc.numpy()
588588
if copy:
589589
if to_torch:
590590
out = out.clone()

genesis/utils/urdf.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
from itertools import chain
33
from pathlib import Path
44

5-
import trimesh
65
import numpy as np
6+
import trimesh
7+
from trimesh.visual import ColorVisuals, TextureVisuals
8+
from trimesh.visual.color import VertexColor
79

810
import genesis as gs
911
from genesis.ext import urdfpy
@@ -131,7 +133,11 @@ def parse_urdf(morph, surface):
131133
"'parse_glb_with_zup=True' in morph options if you find the mesh is 90-degree rotated. "
132134
)
133135

134-
if not geom_is_col and (morph.prioritize_urdf_material or not tmesh.visual.defined):
136+
visual = mesh.trimesh.visual
137+
has_color_override = (isinstance(visual, (ColorVisuals, TextureVisuals)) and visual.defined) or (
138+
isinstance(visual, VertexColor) and visual.vertex_colors.size > 0
139+
)
140+
if not geom_is_col and (morph.prioritize_urdf_material or not has_color_override):
135141
if geom.material is not None and geom.material.color is not None:
136142
mesh.set_color(geom.material.color)
137143

tests/test_examples.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
IGNORE_SCRIPT_NAMES = {
2525
"ddp_multi_gpu.py",
2626
"multi_gpu.py",
27+
"visualization.py", # FIXME: Flaky because of possibly undefined shadow map when running in thread
2728
"single_franka_batch_render.py", # FIXME: segfault on exit
2829
"fem_cube_linked_with_arm.py", # FIXME: segfault on exit (corrupted double-linked list)
2930
}
@@ -32,7 +33,7 @@
3233
"cut_dragon.py",
3334
}
3435

35-
TIMEOUT = 450.0
36+
TIMEOUT = 500.0
3637

3738

3839
pytestmark = [

tests/test_grad.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ def compute_loss(input_mass, input_jac, input_aref, input_efc_D, input_force):
407407
assert_allclose(dL_error, 0.0, atol=RTOL)
408408

409409

410+
@pytest.mark.slow # ~250s
410411
@pytest.mark.required
411412
@pytest.mark.parametrize("backend", [gs.cpu, gs.gpu])
412413
def test_differentiable_rigid(show_viewer):

tests/test_pbd.py

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,22 @@ def test_maxvolume(pbd_material, show_viewer, box_obj_path):
2929

3030
# Mesh without any maximum-element-volume constraint
3131
pbd1 = scene.add_entity(
32-
morph=gs.morphs.Mesh(file=box_obj_path, nobisect=False, verbose=1),
32+
morph=gs.morphs.Mesh(
33+
file=box_obj_path,
34+
nobisect=False,
35+
verbose=1,
36+
),
3337
material=pbd_material,
3438
)
3539

3640
# Mesh with maximum element volume limited to 0.001
3741
pbd2 = scene.add_entity(
38-
morph=gs.morphs.Mesh(file=box_obj_path, nobisect=False, maxvolume=0.001, verbose=1),
42+
morph=gs.morphs.Mesh(
43+
file=box_obj_path,
44+
nobisect=False,
45+
maxvolume=0.001,
46+
verbose=1,
47+
),
3948
material=pbd_material,
4049
)
4150

@@ -154,18 +163,37 @@ def test_cloth_attach_rigid_link(show_viewer):
154163
box_height = 2.25
155164

156165
scene = gs.Scene(
157-
sim_options=gs.options.SimOptions(dt=2e-2, substeps=10, gravity=(0.0, 0.0, 0.0)),
158-
pbd_options=gs.options.PBDOptions(particle_size=particle_size),
166+
sim_options=gs.options.SimOptions(
167+
dt=2e-2,
168+
substeps=10,
169+
gravity=(0.0, 0.0, 0.0),
170+
),
171+
pbd_options=gs.options.PBDOptions(
172+
particle_size=particle_size,
173+
),
174+
viewer_options=gs.options.ViewerOptions(
175+
camera_pos=(2.5, 2.0, 4.0),
176+
camera_lookat=(-1.0, -1.0, 1.0),
177+
),
159178
show_viewer=show_viewer,
160179
)
161-
scene.add_entity(gs.morphs.Plane(), gs.materials.Rigid(needs_coup=True, coup_friction=0.0))
180+
scene.add_entity(
181+
gs.morphs.Plane(),
182+
material=gs.materials.Rigid(
183+
needs_coup=True,
184+
coup_friction=0.0,
185+
),
186+
)
162187

163188
box = scene.add_entity(
164189
morph=gs.morphs.Box(
165190
pos=(0.25, 0.25, box_height),
166191
size=(0.25, 0.25, 0.25),
167192
),
168-
material=gs.materials.Rigid(needs_coup=True, coup_friction=0.0),
193+
material=gs.materials.Rigid(
194+
needs_coup=True,
195+
coup_friction=0.0,
196+
),
169197
)
170198
cloth = scene.add_entity(
171199
morph=gs.morphs.Mesh(
@@ -174,9 +202,10 @@ def test_cloth_attach_rigid_link(show_viewer):
174202
scale=0.5,
175203
),
176204
material=gs.materials.PBD.Cloth(),
177-
surface=gs.surfaces.Default(color=(0.2, 0.4, 0.8, 1.0)),
205+
surface=gs.surfaces.Default(
206+
color=(0.2, 0.4, 0.8, 1.0),
207+
),
178208
)
179-
180209
scene.build(n_envs=2)
181210

182211
particles_idx = [0, 1, 2, 3, 4, 5, 6, 7]
@@ -190,38 +219,28 @@ def test_cloth_attach_rigid_link(show_viewer):
190219
cloth_pos0 = cloth.get_particles_pos()[:, particles_idx].clone()
191220
link_pos0 = scene.rigid_solver.links[box_link_idx].get_pos().clone()
192221

193-
for _ in range(25):
222+
for _ in range(20):
194223
scene.step()
195224

196225
# wait for the box to stop
197226
box.set_dofs_velocity(np.zeros_like(vel), dofs_idx_local=[0, 1, 2])
198-
for _ in range(5):
227+
for _ in range(10):
199228
scene.step()
200229

201230
# Check that the attached particles followed the link displacement per env
202231
cloth_pos1 = cloth.get_particles_pos()[:, particles_idx].clone()
203232
link_pos1 = scene.rigid_solver.links[box_link_idx].get_pos().clone()
233+
assert_allclose((cloth_pos1 - cloth_pos0).movedim(0, -2), link_pos1 - link_pos0, atol=2e-5)
204234

205-
cloth_disp = cloth_pos1 - cloth_pos0
206-
link_disp = link_pos1 - link_pos0
207-
# broadcast link_disp to match cloth_disp shape
208-
link_disp = link_disp.unsqueeze(1)
209-
210-
assert_allclose(cloth_disp, link_disp, atol=2e-5)
211-
212-
# Release cloth and revert box's speed
235+
# Release cloth and restore box's speed
213236
box.set_dofs_velocity(vel, dofs_idx_local=[0, 1, 2])
214237
cloth.release_particle(particles_idx)
215-
for i in range(25):
238+
for _ in range(15):
216239
scene.step()
217240

218241
# Make sure that the cloth is laying on the ground without moving
219-
cloth_pos2 = cloth.get_particles_pos()[:, particles_idx].clone()
220-
link_pos2 = scene.rigid_solver.links[box_link_idx].get_pos().clone()
221-
cloth_disp = cloth_pos2 - cloth_pos1
242+
cloth_pos2 = cloth.get_particles_pos()[:, particles_idx]
243+
link_pos2 = scene.rigid_solver.links[box_link_idx].get_pos()
222244
link_disp = link_pos2 - link_pos1
223-
link_disp = link_disp.unsqueeze(1)
224-
225-
link_disp = link_disp.norm(dim=-1)
226-
cloth_disp = cloth_disp.norm(dim=-1)
227-
assert (link_disp - cloth_disp).norm(dim=-1).mean() > 0.1
245+
cloth_disp = cloth_pos2 - cloth_pos1
246+
assert ((cloth_disp.movedim(0, -2) - link_disp).norm(dim=-1) > 0.2).all()

tests/test_render.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,13 @@ def test_camera_follow_entity(n_envs, renderer, show_viewer):
637637
GUI=show_viewer,
638638
)
639639
cam.follow_entity(obj, smoothing=None)
640+
cam.unfollow_entity()
640641

641642
scene.build(n_envs=n_envs)
642643

644+
for cam, obj in zip(scene.visualizer.cameras, scene.entities):
645+
cam.follow_entity(obj, smoothing=None)
646+
643647
# First render
644648
seg_mask = None
645649
for entity_idx, cam in enumerate(scene.visualizer.cameras, 1):

0 commit comments

Comments
 (0)