Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/testConverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,16 @@ def test_ros_packages(self):
self.assertEqual(diffuse_color, None)
opacity = self.get_material_opacity(texture_material)
self.assertAlmostEqual(opacity, 1.0, places=6)

def test_asset_identifer(self):
model = pathlib.Path("tests/data/prismatic_joints.urdf")
model_name = model.stem
output_dir = pathlib.Path(self.tmpDir()) / model_name
usdc_path = output_dir / f"{model_name}.usdc"

asset_identifier = urdf_usd_converter.Converter(layer_structure=False).convert(model, output_dir)
self.assertTrue(usdc_path.exists())

# check that the asset identifier returned from convert() is the same as the usdc path
flattened_usdc_path = pathlib.Path(asset_identifier.path).absolute().as_posix()
self.assertEqual(flattened_usdc_path, usdc_path.absolute().as_posix())
52 changes: 33 additions & 19 deletions tests/testMaterial.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers
# SPDX-License-Identifier: Apache-2.0
import pathlib
import re
import shutil

import omni.asset_validator
import usdex.test
from pxr import Gf, Tf, Usd, UsdGeom, UsdShade

Expand Down Expand Up @@ -218,6 +220,22 @@ def test_material_texture(self):
self.assertTrue(obj_prim.IsA(UsdGeom.Mesh))
self.check_material_binding(obj_prim, color_texture_material)

@staticmethod
def allowed_issue_predicates() -> list[omni.asset_validator.IssuePredicates]:
"""
Avoid errors about texture files not being found when calling self.assertIsValidUsd.
"""

def check_unresolvable_external_dependency_issue(issue):
if re.match(".*Found unresolvable external dependency.*", issue.message):
return True

def check_cannot_be_resolved_issue(issue):
if re.match(".*Dependent Reference .* cannot be resolved.*", issue.message):
return True

return [check_unresolvable_external_dependency_issue, check_cannot_be_resolved_issue]

def test_material_texture_name_duplication_missing_texture(self):
input_path = "tests/data/material_texture_name_duplication.urdf"
output_dir = self.tmpDir()
Expand All @@ -236,7 +254,14 @@ def test_material_texture_name_duplication_missing_texture(self):
self.assertTrue(pathlib.Path(asset_path.path).exists())

stage: Usd.Stage = Usd.Stage.Open(asset_path.path)
self.assertIsValidUsd(stage)
with usdex.test.ScopedDiagnosticChecker(
self,
[
(Tf.TF_DIAGNOSTIC_WARNING_TYPE, ".*Failed to resolve reference.*"),
],
level=usdex.core.DiagnosticsLevel.eWarning,
):
self.assertIsValidUsd(stage, issuePredicates=self.allowed_issue_predicates())

# Check texture.
output_texture_path = pathlib.Path(output_dir) / "Payload" / "Textures" / "grid.png"
Expand Down Expand Up @@ -321,8 +346,8 @@ def test_material_mesh_color(self):
self.assertTrue(two_boxes_prim.IsA(UsdGeom.Xform))
self.assertTrue(two_boxes_prim.HasAuthoredReferences())

# Check the material in the geometry.
material_scope_prim = two_boxes_prim.GetChild("Materials")
# Check the materials.
material_scope_prim = default_prim.GetChild("Materials")
self.assertTrue(material_scope_prim.IsValid())

green_material_prim = material_scope_prim.GetChild("green_mat")
Expand Down Expand Up @@ -395,11 +420,11 @@ def test_material_mesh_texture(self):

box_with_texture_prim = link_obj_prim.GetChild("box_with_texture")
self.assertTrue(box_with_texture_prim.IsValid())
self.assertTrue(box_with_texture_prim.IsA(UsdGeom.Xform))
self.assertTrue(box_with_texture_prim.IsA(UsdGeom.Mesh))
self.assertTrue(box_with_texture_prim.HasAuthoredReferences())

# Check the material in the geometry.
material_scope_prim = box_with_texture_prim.GetChild("Materials")
# Check the materials.
material_scope_prim = default_prim.GetChild("Materials")
self.assertTrue(material_scope_prim.IsValid())

texture_material_prim = material_scope_prim.GetChild("texture_mat")
Expand All @@ -421,10 +446,7 @@ def test_material_mesh_texture(self):
metallic_texture_path = self.get_material_texture_path(texture_material, "metallic")
self.assertEqual(metallic_texture_path, pathlib.Path("./Textures/metallic.png"))

mesh_prim = box_with_texture_prim.GetChild("box_with_texture")
self.assertTrue(mesh_prim.IsValid())
self.assertTrue(mesh_prim.IsA(UsdGeom.Mesh))
self.check_material_binding(mesh_prim, texture_material)
self.check_material_binding(box_with_texture_prim, texture_material)

def test_material_mesh_override(self):
input_path = "tests/data/material_mesh_override.urdf"
Expand Down Expand Up @@ -469,12 +491,4 @@ def test_material_mesh_override(self):
self.assertTrue(two_boxes_prim.HasAuthoredReferences())

# Check that the material bind is overwritten with blue_material.
mesh_prim = two_boxes_prim.GetChild("Cube_Red")
self.assertTrue(mesh_prim.IsValid())
self.assertTrue(mesh_prim.IsA(UsdGeom.Mesh))
self.check_material_binding(mesh_prim, blue_material)

mesh_prim = two_boxes_prim.GetChild("Cube_Green")
self.assertTrue(mesh_prim.IsValid())
self.assertTrue(mesh_prim.IsA(UsdGeom.Mesh))
self.check_material_binding(mesh_prim, blue_material)
self.check_material_binding(two_boxes_prim, blue_material)
16 changes: 6 additions & 10 deletions urdf_usd_converter/_impl/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .data import ConversionData, Tokens
from .link import convert_links
from .link_hierarchy import LinkHierarchy
from .material import convert_materials, get_material_texture_paths
from .material import convert_materials
from .mesh import convert_meshes
from .mesh_cache import MeshCache
from .ros_package import search_ros_packages
Expand Down Expand Up @@ -89,7 +89,9 @@ def convert(self, input_file: str, output_dir: str) -> Sdf.AssetPath:
link_hierarchy=LinkHierarchy(parser.get_root_element()),
mesh_cache=MeshCache(),
ros_packages=ros_packages,
texture_paths={},
resolved_file_paths={},
material_names=[],
mesh_material_references={},
)

# setup the main output layer (which will become an asset interface later)
Expand Down Expand Up @@ -121,14 +123,8 @@ def convert(self, input_file: str, output_dir: str) -> Sdf.AssetPath:
# setup the root layer of the payload
data.content[Tokens.Contents] = usdex.core.createAssetPayload(asset_stage)

# Get a dictionary of resolved texture paths and unique names.
# It stores all the texture file paths referenced by urdf materials and each mesh.
data.texture_paths = get_material_texture_paths(data)

# author the material library and setup the content layer for materials only if there are materials
# In order to specify the bias and scale of the normal map of the UsdPreviewSurface material,
# the texture must first be copied to the destination.
# Therefore, call the material conversion before convert_meshes.
# Convert the materials.
# Here, all materials referenced by the URDF's global materials and meshes are scanned and stored.
convert_materials(data)

# author the mesh library
Expand Down
4 changes: 3 additions & 1 deletion urdf_usd_converter/_impl/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ class ConversionData:
link_hierarchy: LinkHierarchy
mesh_cache: MeshCache
ros_packages: list[dict[str, str]]
texture_paths: dict[pathlib.Path, str]
resolved_file_paths: dict[str, pathlib.Path] # [mesh_file_name, resolved_file_path]
material_names: list[dict[pathlib.Path, str, str]] # [mesh_file_path, name, safe_name]
mesh_material_references: dict[pathlib.Path, dict[str, str]] # [mesh_file_path, [mesh_safe_name, material_name]]
10 changes: 7 additions & 3 deletions urdf_usd_converter/_impl/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pxr import Gf, Tf, Usd, UsdGeom, UsdPhysics

from .data import ConversionData, Tokens
from .material import bind_material
from .material import bind_material, bind_mesh_material
from .urdf_parser.elements import (
ElementBox,
ElementCollision,
Expand Down Expand Up @@ -46,8 +46,12 @@ def convert_geometry(parent: Usd.Prim, name: str, safe_name: str, geometry: Elem
apply_physics_collision(prim.GetPrim(), data)

# If the visual has a material, bind the material.
if isinstance(geometry, ElementVisual) and geometry.material and geometry.material.name:
bind_material(prim.GetPrim(), geometry.material.name, data)
# If the Visual element does not have a material, bind a material per mesh.
if isinstance(geometry, ElementVisual):
if geometry.material and geometry.material.name:
bind_material(prim.GetPrim(), None, geometry.material.name, data)
elif isinstance(geometry.geometry.shape, ElementMesh):
bind_mesh_material(prim.GetPrim(), geometry.geometry.shape.filename, data)

return prim

Expand Down
Loading
Loading