Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions tests/data/assets/box_with_texture.mtl
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ Ni 1.450000
d 1.000000
illum 2
map_Kd ./grid.png
norm ./normal.png
map_Pr ./roughness.png
map_Pm ./metallic.png
Binary file added tests/data/assets/metallic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/assets/normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/assets/roughness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions tests/data/assets/two_boxes.mtl
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
Pr 0.3
Pm 0.05
illum 2
4 changes: 2 additions & 2 deletions tests/testConverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ def test_ros_packages(self):
self.assertTrue(texture_material)
self.assertTrue(texture_material.GetPrim().HasAuthoredReferences())

texture_path = self.get_material_diffuse_color_texture_path(texture_material)
texture_path = self.get_material_texture_path(texture_material, "diffuseColor")
self.assertEqual(texture_path, pathlib.Path("./Textures/grid.png"))
diffuse_color = self.get_material_diffuse_color(texture_material)
self.assertEqual(diffuse_color, None)
opacity = self.get_material_opacity(texture_material)
self.assertEqual(opacity, 1.0)
self.assertAlmostEqual(opacity, 1.0, places=6)
24 changes: 17 additions & 7 deletions tests/testMaterial.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def test_material_texture(self):
self.assertEqual(diffuse_color, None)
opacity = self.get_material_opacity(texture_material)
self.assertEqual(opacity, 1.0)
diffuse_color_texture_path = self.get_material_diffuse_color_texture_path(texture_material)
diffuse_color_texture_path = self.get_material_texture_path(texture_material, "diffuseColor")
self.assertEqual(diffuse_color_texture_path, pathlib.Path("./Textures/grid.png"))

color_texture_material_prim = material_scope_prim.GetChild("color_texture_material")
Expand All @@ -163,8 +163,8 @@ def test_material_texture(self):
opacity = self.get_material_opacity(color_texture_material)
self.assertEqual(opacity, 1.0)

# This texture is multiplied, so the color value is obtained from the scale.
diffuse_color = self.get_material_diffuse_color_texture_scale(color_texture_material)
# This texture is multiplied, so the color value is obtained from the fallback.
diffuse_color = self.get_material_diffuse_color_texture_fallback(color_texture_material)
diffuse_color = Gf.Vec3f(*diffuse_color[:3])
diffuse_color = usdex.core.linearToSrgb(diffuse_color)
self.assertTrue(Gf.IsClose(diffuse_color, Gf.Vec3f(0.5, 0.2, 0.5), 1e-6))
Expand Down Expand Up @@ -347,7 +347,11 @@ def test_material_mesh_color(self):
diffuse_color = usdex.core.linearToSrgb(diffuse_color)
self.assertTrue(Gf.IsClose(diffuse_color, Gf.Vec3f(1, 0, 0), 1e-6))
opacity = self.get_material_opacity(red_material)
self.assertEqual(opacity, 1.0)
self.assertAlmostEqual(opacity, 1.0, places=6)
roughness = self.get_material_roughness(red_material)
self.assertAlmostEqual(roughness, 0.3, places=6)
metallic = self.get_material_metallic(red_material)
self.assertAlmostEqual(metallic, 0.05, places=6)

mesh_prim = two_boxes_prim.GetChild("Cube_Green")
self.assertTrue(mesh_prim.IsValid())
Expand Down Expand Up @@ -407,9 +411,15 @@ def test_material_mesh_texture(self):
diffuse_color = self.get_material_diffuse_color(texture_material)
self.assertIsNone(diffuse_color)
opacity = self.get_material_opacity(texture_material)
self.assertEqual(opacity, 1.0)
texture_path = self.get_material_diffuse_color_texture_path(texture_material)
self.assertEqual(texture_path, pathlib.Path("./Textures/grid.png"))
self.assertAlmostEqual(opacity, 1.0, places=6)
diffuse_color_texture_path = self.get_material_texture_path(texture_material, "diffuseColor")
self.assertEqual(diffuse_color_texture_path, pathlib.Path("./Textures/grid.png"))
normal_texture_path = self.get_material_texture_path(texture_material, "normal")
self.assertEqual(normal_texture_path, pathlib.Path("./Textures/normal.png"))
roughness_texture_path = self.get_material_texture_path(texture_material, "roughness")
self.assertEqual(roughness_texture_path, pathlib.Path("./Textures/roughness.png"))
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())
Expand Down
4 changes: 2 additions & 2 deletions tests/testROSPackagesCli.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ def check_usd_converted_from_urdf(self, usd_path: pathlib.Path):
self.assertTrue(texture_material)
self.assertTrue(texture_material.GetPrim().HasAuthoredReferences())

texture_path = self.get_material_diffuse_color_texture_path(texture_material)
texture_path = self.get_material_texture_path(texture_material, "diffuseColor")
self.assertEqual(texture_path, pathlib.Path("./Textures/grid.png"))
diffuse_color = self.get_material_diffuse_color(texture_material)
self.assertEqual(diffuse_color, None)
opacity = self.get_material_opacity(texture_material)
self.assertEqual(opacity, 1.0)
self.assertAlmostEqual(opacity, 1.0, places=6)
26 changes: 22 additions & 4 deletions tests/util/ConverterTestCase.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,40 @@ def get_material_opacity(self, material: UsdShade.Material) -> float:
shader: UsdShade.Shader = usdex.core.computeEffectivePreviewSurfaceShader(material)
return shader.GetInput("opacity").Get()

def get_material_diffuse_color_texture_path(self, material: UsdShade.Material) -> pathlib.Path:
def get_material_roughness(self, material: UsdShade.Material) -> float:
shader: UsdShade.Shader = usdex.core.computeEffectivePreviewSurfaceShader(material)
texture_input: UsdShade.Input = shader.GetInput("diffuseColor")
return shader.GetInput("roughness").Get()

def get_material_metallic(self, material: UsdShade.Material) -> float:
shader: UsdShade.Shader = usdex.core.computeEffectivePreviewSurfaceShader(material)
return shader.GetInput("metallic").Get()

def get_material_texture_path(self, material: UsdShade.Material, texture_type: str = "diffuseColor") -> pathlib.Path:
"""
Get the texture path for the given texture type.

Args:
material: The material.
texture_type: The texture type. Valid values are "diffuseColor", "normal", "roughness" and "metallic".

Returns:
The texture path.
"""
shader: UsdShade.Shader = usdex.core.computeEffectivePreviewSurfaceShader(material)
texture_input: UsdShade.Input = shader.GetInput(texture_type)
self.assertTrue(texture_input.HasConnectedSource())

connected_source = texture_input.GetConnectedSource()
texture_prim = connected_source[0].GetPrim()
texture_file_attr = texture_prim.GetAttribute("inputs:file")
return pathlib.Path(texture_file_attr.Get().path)

def get_material_diffuse_color_texture_scale(self, material: UsdShade.Material) -> Gf.Vec4f | None:
def get_material_diffuse_color_texture_fallback(self, material: UsdShade.Material) -> Gf.Vec4f | None:
shader: UsdShade.Shader = usdex.core.computeEffectivePreviewSurfaceShader(material)
diffuse_color_input = shader.GetInput("diffuseColor")
if diffuse_color_input.HasConnectedSource():
source = diffuse_color_input.GetConnectedSource()
if len(source) > 0 and isinstance(source[0], UsdShade.ConnectableAPI) and source[0].GetPrim().IsA(UsdShade.Shader):
diffuse_texture_shader = UsdShade.Shader(source[0].GetPrim())
return diffuse_texture_shader.GetInput("scale").Get()
return diffuse_texture_shader.GetInput("fallback").Get()
return None
9 changes: 6 additions & 3 deletions urdf_usd_converter/_impl/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,17 @@ def convert(self, input_file: str, output_dir: str) -> Sdf.AssetPath:
# 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_materials(data)

# author the mesh library
convert_meshes(data)
# setup a content layer for referenced meshes
data.content[Tokens.Geometry] = usdex.core.addAssetContent(data.content[Tokens.Contents], Tokens.Geometry, format="usda")

# author the material library and setup the content layer for materials only if there are materials
convert_materials(data)

# setup a content layer for physics
data.content[Tokens.Physics] = usdex.core.addAssetContent(data.content[Tokens.Contents], Tokens.Physics, format="usda")
data.content[Tokens.Physics].SetMetadata(UsdPhysics.Tokens.kilogramsPerUnit, 1)
Expand Down
168 changes: 147 additions & 21 deletions urdf_usd_converter/_impl/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from .ros_package import resolve_ros_package_paths

__all__ = [
"add_diffuse_texture_to_preview_material",
"bind_material",
"convert_materials",
"convert_obj_materials",
"get_material_texture_paths",
]

Expand Down Expand Up @@ -169,42 +169,93 @@ def convert_material(
if resolved_texture_path:
add_diffuse_texture_to_preview_material(material_prim, resolved_texture_path, data)

# If both color and texture are present, they are multiplied together.
# For UsdPreviewSurface, multiplication uses bias.
if texture_path and color != Gf.Vec3f(1, 1, 1):
shader = usdex.core.computeEffectivePreviewSurfaceShader(material_prim)
diffuse_color_input = shader.GetInput("diffuseColor")
if diffuse_color_input.HasConnectedSource():
source = diffuse_color_input.GetConnectedSource()
if len(source) > 0 and isinstance(source[0], UsdShade.ConnectableAPI) and source[0].GetPrim().IsA(UsdShade.Shader):
diffuse_texture_shader = UsdShade.Shader(source[0].GetPrim())
diffuse_texture_shader.CreateInput("scale", Sdf.ValueTypeNames.Float4).Set(Gf.Vec4f(color[0], color[1], color[2], 1.0))

return material_prim


def add_diffuse_texture_to_preview_material(material_prim: UsdShade.Material, texture_path: pathlib.Path, data: ConversionData):
def _get_texture_asset_path(texture_path: pathlib.Path, data: ConversionData) -> Sdf.AssetPath:
"""
Add the diffuse texture to the preview material.
Get the asset path for the texture.

Args:
material_prim: The preview material prim.
texture_path: The path to the texture.
data: The conversion data.

Returns:
The asset path for the texture.
"""
# The path to the texture to reference. If None, the texture does not exist.
unique_file_name = data.texture_paths.get(texture_path, None)
if not unique_file_name:
return None

# If the texture exists, add the texture to the material.
if unique_file_name:
payload_dir = pathlib.Path(data.content[Tokens.Contents].GetRootLayer().identifier).parent
local_texture_dir = payload_dir / Tokens.Textures
local_texture_path = local_texture_dir / unique_file_name
relative_texture_path = local_texture_path.relative_to(payload_dir)
asset_path = Sdf.AssetPath(f"./{relative_texture_path.as_posix()}")
payload_dir = pathlib.Path(data.content[Tokens.Contents].GetRootLayer().identifier).parent
local_texture_dir = payload_dir / Tokens.Textures
local_texture_path = local_texture_dir / unique_file_name
relative_texture_path = local_texture_path.relative_to(payload_dir)
return Sdf.AssetPath(f"./{relative_texture_path.as_posix()}")


def add_diffuse_texture_to_preview_material(material_prim: UsdShade.Material, texture_path: pathlib.Path, data: ConversionData):
"""
Add the diffuse texture to the preview material.

Args:
material_prim: The preview material prim.
texture_path: The path to the texture.
data: The conversion data.
"""
# Get the asset path for the texture.
asset_path = _get_texture_asset_path(texture_path, data)
if asset_path:
usdex.core.addDiffuseTextureToPreviewMaterial(material_prim, asset_path)


def add_normal_texture_to_preview_material(material_prim: UsdShade.Material, texture_path: pathlib.Path, data: ConversionData):
"""
Add the normal texture to the preview material.

Args:
material_prim: The preview material prim.
texture_path: The path to the texture.
data: The conversion data.
"""
# Get the asset path for the texture.
asset_path = _get_texture_asset_path(texture_path, data)
if asset_path:
usdex.core.addNormalTextureToPreviewMaterial(material_prim, asset_path)


def add_roughness_texture_to_preview_material(material_prim: UsdShade.Material, texture_path: pathlib.Path, data: ConversionData):
"""
Add the roughness texture to the preview material.

Args:
material_prim: The preview material prim.
texture_path: The path to the texture.
data: The conversion data.
"""
# Get the asset path for the texture.
asset_path = _get_texture_asset_path(texture_path, data)
if asset_path:
usdex.core.addRoughnessTextureToPreviewMaterial(material_prim, asset_path)


def add_metallic_texture_to_preview_material(material_prim: UsdShade.Material, texture_path: pathlib.Path, data: ConversionData):
"""
Add the metallic texture to the preview material.

Args:
material_prim: The preview material prim.
texture_path: The path to the texture.
data: The conversion data.
"""
# Get the asset path for the texture.
asset_path = _get_texture_asset_path(texture_path, data)
if asset_path:
usdex.core.addMetallicTextureToPreviewMaterial(material_prim, asset_path)


def _get_mesh_texture_paths(input_path: pathlib.Path) -> list[pathlib.Path]:
"""
Get the texture paths from the mesh file.
Expand Down Expand Up @@ -243,6 +294,18 @@ def _get_obj_texture_paths(input_path: pathlib.Path) -> list[pathlib.Path]:
if material.diffuse_texname and material.diffuse_texname not in tex_names:
tex_names.append(material.diffuse_texname)

# Normal texture (norm)
if material.normal_texname and material.normal_texname not in tex_names:
tex_names.append(material.normal_texname)

# Roughness texture (map_Pr)
if material.roughness_texname and material.roughness_texname not in tex_names:
tex_names.append(material.roughness_texname)

# Metallic texture (map_Bump)
if material.metallic_texname and material.metallic_texname not in tex_names:
tex_names.append(material.metallic_texname)

# obj file directory
obj_dir = input_path.parent

Expand All @@ -254,6 +317,69 @@ def _get_obj_texture_paths(input_path: pathlib.Path) -> list[pathlib.Path]:
return texture_paths


def convert_obj_materials(prim: Usd.Prim, input_path: pathlib.Path, reader: tinyobjloader.ObjReader, data: ConversionData) -> dict[str, Usd.Prim]:
"""
Convert the materials from the OBJ file to USD.

Args:
prim: The prim to convert the materials to.
input_path: The path to the OBJ file.
reader: The tinyobjloader reader.
data: The conversion data.

Returns:
A dictionary of material names and their prims.
"""

materials_prims = {}
materials = reader.GetMaterials()
for material in materials:
color = usdex.core.sRgbToLinear(Gf.Vec3f(material.diffuse[0], material.diffuse[1], material.diffuse[2]))
opacity = material.dissolve

# The following is the extended specification of obj.
roughness = material.roughness if material.roughness else 0.5
metallic = material.metallic if material.metallic else 0.0

material_kwargs = {
"color": color,
"opacity": opacity,
"roughness": roughness,
"metallic": metallic,
}

material_scope = prim.GetChild(Tokens.Materials)
if not material_scope:
material_scope = usdex.core.defineScope(prim, Tokens.Materials)

# Define the material.
material_prim = usdex.core.definePreviewMaterial(material_scope.GetPrim(), material.name, **material_kwargs)
if not material_prim:
Tf.Warn(f'Failed to convert material "{material.name}"')

# Add the diffuse texture to the material.
else:
diffuse_texture_path = (input_path.parent / material.diffuse_texname) if material.diffuse_texname else None
if diffuse_texture_path:
add_diffuse_texture_to_preview_material(material_prim, diffuse_texture_path, data)

normal_texture_path = (input_path.parent / material.normal_texname) if material.normal_texname else None
if normal_texture_path:
add_normal_texture_to_preview_material(material_prim, normal_texture_path, data)

roughness_texture_path = (input_path.parent / material.roughness_texname) if material.roughness_texname else None
if roughness_texture_path:
add_roughness_texture_to_preview_material(material_prim, roughness_texture_path, data)

metallic_texture_path = (input_path.parent / material.metallic_texname) if material.metallic_texname else None
if metallic_texture_path:
add_metallic_texture_to_preview_material(material_prim, metallic_texture_path, data)

materials_prims[material.name] = material_prim

return materials_prims


def bind_material(geom_prim: Usd.Prim, name: str, data: ConversionData):
"""
Bind the material to the geometries.
Expand Down
Loading
Loading