Skip to content

Commit 348bdbc

Browse files
authored
Fixed material overrides (#51)
We now follow rviz & urdfviewer where embedded OBJ/DAE materials take priority over URDF materials
1 parent 8199f31 commit 348bdbc

File tree

5 files changed

+94
-11
lines changed

5 files changed

+94
-11
lines changed

docs/concept_mapping.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,10 +594,12 @@ Mapping the material & shader concepts of these reference formats is outside the
594594

595595
Whether supporting a partial or a full material conversion, they should be converted to appropriate `UsdShadeMaterial` and `UsdShadeShader` prims, stored alongside the native URDF materials in a library, and bound to the UsdGeom prims using an applied `MaterialBindingAPI`.
596596

597-
If there are also direct URDF materials assigned to the same geometry, the native URDF assignments should take precedence (see [material](#material) for guidance).
598-
599597
In USD, the subset concept maps to `UsdGeomSubset` Prims, which should be children of the `UsdGeomMesh` (see [mesh](#element-mesh)). Each `UsdGeomSubset` can have a `MaterialBindingAPI` applied to it, targeting a different `UsdShadeMaterial`. There is some runtime cost associated with Subsets, so if only one material is applied to the entire mesh, it is preferable to avoid Subset prims & recommended to use a material binding to the mesh itself.
600598

599+
If embedded materials are assigned to meshes or a subset of faces in the OBJ/DAE files, they should be converted to `UsdShadeMaterials` and bound to the appropriate `UsdGeomMesh` or `UsdGeomSubset`. If URDF materials also exist for these meshes/subsets, they should be ignored. Native URDF materials should only be bound to meshes or subsets which do not already receive material bindings from the embedded OBJ/DAE files.
600+
601+
See [material](#material) for further guidance.
602+
601603
### joint
602604

603605
In URDF joints define the relative motion of links & describe the kinematic hierarchy of the robot, but defining motion degrees of freedom between a specified parent link and a child link, as well as the initial 3D position & orientation of the child link.

tests/data/material_mesh_override.urdf

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,27 @@
2020
<material name="blue"/>
2121
</visual>
2222
</link>
23+
<link name="link_dae">
24+
<visual>
25+
<geometry>
26+
<!-- This dae file has a material: Material. -->
27+
<mesh filename="assets/box.dae" scale="0.5 0.5 0.5"/>
28+
</geometry>
29+
<!-- Override the material with blue. -->
30+
<!-- dae has only one mesh and already has a material assigned. -->
31+
<!-- This URDF material specification will be skipped. -->
32+
<material name="blue"/>
33+
</visual>
34+
</link>
2335

2436
<joint name="joint_box_obj" type="fixed">
2537
<origin rpy="0 0 0" xyz="1 0 0"/>
2638
<parent link="link_box"/>
2739
<child link="link_obj"/>
2840
</joint>
41+
<joint name="joint_obj_dae" type="fixed">
42+
<origin rpy="0 0 0" xyz="1 0 0"/>
43+
<parent link="link_obj"/>
44+
<child link="link_dae"/>
45+
</joint>
2946
</robot>

tests/testMaterial.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -534,23 +534,79 @@ def test_material_mesh_override(self):
534534
self.assertTrue(blue_material.GetPrim().HasAuthoredReferences())
535535

536536
diffuse_color = self.get_material_diffuse_color(blue_material)
537-
self.assertEqual(diffuse_color, Gf.Vec3f(0, 0, 1))
537+
self.assertTrue(Gf.IsClose(diffuse_color, Gf.Vec3f(0, 0, 1), 1e-6))
538538
opacity = self.get_material_opacity(blue_material)
539-
self.assertEqual(opacity, 1.0)
539+
self.assertAlmostEqual(opacity, 1.0, places=6)
540+
541+
red_material_prim = material_scope_prim.GetChild("red_mat")
542+
self.assertTrue(red_material_prim.IsValid())
543+
self.assertTrue(red_material_prim.IsA(UsdShade.Material))
544+
545+
red_material = UsdShade.Material(red_material_prim)
546+
self.assertTrue(red_material)
547+
self.assertTrue(red_material.GetPrim().HasAuthoredReferences())
548+
549+
diffuse_color = self.get_material_diffuse_color(red_material)
550+
self.assertTrue(Gf.IsClose(diffuse_color, Gf.Vec3f(1, 0, 0), 1e-6))
551+
opacity = self.get_material_opacity(red_material)
552+
self.assertAlmostEqual(opacity, 1.0, places=6)
553+
554+
green_material_prim = material_scope_prim.GetChild("green_mat")
555+
self.assertTrue(green_material_prim.IsValid())
556+
self.assertTrue(green_material_prim.IsA(UsdShade.Material))
557+
558+
green_material = UsdShade.Material(green_material_prim)
559+
self.assertTrue(green_material)
560+
self.assertTrue(green_material.GetPrim().HasAuthoredReferences())
561+
562+
diffuse_color = self.get_material_diffuse_color(green_material)
563+
self.assertTrue(Gf.IsClose(diffuse_color, Gf.Vec3f(0, 1, 0), 1e-6))
564+
opacity = self.get_material_opacity(green_material)
565+
self.assertAlmostEqual(opacity, 1.0, places=6)
566+
567+
dae_material_prim = material_scope_prim.GetChild("Material")
568+
self.assertTrue(dae_material_prim.IsValid())
569+
self.assertTrue(dae_material_prim.IsA(UsdShade.Material))
570+
dae_material = UsdShade.Material(dae_material_prim)
571+
self.assertTrue(dae_material)
572+
self.assertTrue(dae_material.GetPrim().HasAuthoredReferences())
573+
574+
diffuse_color = self.get_material_diffuse_color(dae_material)
575+
diffuse_color = usdex.core.linearToSrgb(diffuse_color)
576+
self.assertTrue(Gf.IsClose(diffuse_color, Gf.Vec3f(0.8, 0.8, 0.8), 1e-6))
577+
opacity = self.get_material_opacity(dae_material)
578+
self.assertAlmostEqual(opacity, 1.0, places=6)
540579

580+
# Check material bindings.
541581
default_prim = stage.GetDefaultPrim()
542582
geometry_scope_prim = default_prim.GetChild("Geometry")
543583
self.assertTrue(geometry_scope_prim.IsValid())
544584
self.assertTrue(geometry_scope_prim.IsA(UsdGeom.Scope))
545585

546-
two_boxes_prim = geometry_scope_prim.GetChild("link_box").GetChild("link_obj").GetChild("two_boxes")
586+
link_obj_prim = geometry_scope_prim.GetChild("link_box").GetChild("link_obj")
587+
self.assertTrue(link_obj_prim.IsValid())
588+
589+
two_boxes_prim = link_obj_prim.GetChild("two_boxes")
547590
self.assertTrue(two_boxes_prim.IsValid())
548591
self.assertTrue(two_boxes_prim.IsA(UsdGeom.Xform))
549592
self.assertTrue(two_boxes_prim.HasAuthoredReferences())
550593

551594
# Check that the material bind is overwritten with blue_material.
552595
self.check_material_binding(two_boxes_prim, blue_material)
553596

597+
# Check that the material bind is overwritten with red_material.
598+
cube_red_prim = two_boxes_prim.GetChild("Cube_Red")
599+
self.assertTrue(cube_red_prim.IsValid())
600+
self.assertTrue(cube_red_prim.IsA(UsdGeom.Mesh))
601+
self.check_material_binding(cube_red_prim, red_material)
602+
603+
# Check that the material bind is dae_material.
604+
# Since the material by dae is already assigned, it will not be overwritten by blue_material.
605+
dae_box_prim = link_obj_prim.GetChild("link_dae").GetChild("box")
606+
self.assertTrue(dae_box_prim.IsValid())
607+
self.assertTrue(dae_box_prim.IsA(UsdGeom.Mesh))
608+
self.check_material_binding(dae_box_prim, dae_material)
609+
554610
def test_dae_materials(self):
555611
input_path = "tests/data/dae_materials.urdf"
556612
output_dir = self.tmpDir()

urdf_usd_converter/_impl/geometry.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ def convert_geometry(parent: Usd.Prim, name: str, safe_name: str, geometry: Elem
4646
apply_physics_collision(prim.GetPrim(), data)
4747

4848
# If the visual has a material, bind the material.
49-
# If the Visual element does not have a material, bind a material per mesh.
49+
# After binding materials to each mesh, bind the URDF material to any shapes that did not receive materials from the embedded mesh files.
5050
if isinstance(geometry, ElementVisual):
51+
if isinstance(geometry.geometry.shape, ElementMesh):
52+
bind_mesh_material(prim.GetPrim(), geometry.geometry.shape.filename, data)
5153
if geometry.material and geometry.material.name:
5254
bind_material(prim.GetPrim(), None, geometry.material.name, data)
53-
elif isinstance(geometry.geometry.shape, ElementMesh):
54-
bind_mesh_material(prim.GetPrim(), geometry.geometry.shape.filename, data)
5555

5656
return prim
5757

urdf_usd_converter/_impl/material.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,15 @@ def bind_material(geom_prim: Usd.Prim, mesh_file_path: pathlib.Path | None, mate
432432
Tf.Warn(f"Material '{material_name}' not found in Material Library {data.libraries[Tokens.Materials].GetRootLayer().identifier}")
433433
return
434434

435+
geom_over = data.content[Tokens.Materials].OverridePrim(geom_prim.GetPath())
436+
437+
# If the geometry already has a material binding, skip the binding.
438+
material_binding = UsdShade.MaterialBindingAPI(geom_over)
439+
if material_binding:
440+
binding_rel = material_binding.GetDirectBindingRel()
441+
if len(binding_rel.GetTargets()) > 0:
442+
return
443+
435444
# If the material does not exist in the Material layer, define the reference.
436445
material_prim = UsdShade.Material(local_materials.GetChild(ref_material.GetPrim().GetName()))
437446
if not material_prim:
@@ -448,7 +457,6 @@ def bind_material(geom_prim: Usd.Prim, mesh_file_path: pathlib.Path | None, mate
448457
break
449458

450459
# Bind the material to the geometry.
451-
geom_over = data.content[Tokens.Materials].OverridePrim(geom_prim.GetPath())
452460
usdex.core.bindMaterial(geom_over, material_prim)
453461

454462

@@ -479,9 +487,9 @@ def _get_material_name_from_prim(prim: Usd.Prim, resolved_file_path: pathlib.Pat
479487
return None
480488

481489
prim_name = prim.GetName()
482-
if prim.IsA(UsdGeom.Mesh):
490+
if prim.IsA(UsdGeom.Mesh) and len(prim.GetAllChildrenNames()) == 0:
483491
# If there are no child prims, it is a single mesh with no GeomSubset.
484-
if prim.HasAuthoredReferences() and len(prim.GetAllChildrenNames()) == 0:
492+
if prim.HasAuthoredReferences():
485493
# Get the first material name list.
486494
# In this case, a prim reference exists, and it refers to the single mesh itself.
487495
# When referencing, the prim_name may not match the name stored in 'data.mesh_material_references',

0 commit comments

Comments
 (0)