Hello NVIDIA Community,
Version isaac_sim-2023.1.1
During development encountered strange behavior of materials applied to “shell” which is created by _create_mesh_shell
in extscache/omni.replicator.core-1.10.20+105.1.lx64.r.cp310/omni/replicator/core/ogn/python/_impl/nodes/OgnCreateProjectionMaterial.py
.
Using the following script in console you can check that “shell” mesh with applied material doesn’t look normal.
from pathlib import Path
import numpy as np
import omni
import omni.kit.material.library as mat_lib
import omni.replicator.core as rep
import omni.usd
from pxr import Gf, Sdf, Semantics, Usd, UsdGeom, UsdShade
def _create_mesh_shell(target_prim, path):
"""Create a mesh shell from input mesh(es)
Concatenate points, polygons and normals and create a new mesh at the specified path.
"""
stage = omni.usd.get_context().get_stage()
projection_prim = stage.DefinePrim(path, "Mesh")
vert_combined = []
face_vert_counts_combined = []
face_vert_idx_combined = []
normals_combined = []
cur_time = 0.0
target_prim_to_world = UsdGeom.Xformable(target_prim).ComputeLocalToWorldTransform(
cur_time
)
vert_idx_offset = 0
descendants = [target_prim]
prototype_tfs = {}
while descendants:
cur_prim = descendants.pop()
if cur_prim.HasAttribute("replicatorProjection"):
continue
if cur_prim.IsInstanceable():
prototype_tfs[cur_prim.GetPrototype().GetName()] = UsdGeom.Xformable(
cur_prim
).ComputeLocalToWorldTransform(cur_time)
descendants.append(cur_prim.GetPrototype())
elif cur_prim.GetTypeName() != "Mesh":
descendants.extend(cur_prim.GetChildren())
else:
mesh_to_world = UsdGeom.Xformable(cur_prim).ComputeLocalToWorldTransform(
cur_time
)
root_path = str(cur_prim.GetPrimPath()).split("/", 2)[1]
if cur_prim.IsInPrototype() and root_path in prototype_tfs:
mesh_to_world = mesh_to_world * prototype_tfs.get(root_path)
mesh_to_target = mesh_to_world * target_prim_to_world.GetInverse()
points_raw = np.array(cur_prim.GetAttribute("points").Get())
points_transformed = (
np.pad(points_raw, ((0, 0), (0, 1)), constant_values=1) @ mesh_to_target
)[..., :3]
vert_combined.append(points_transformed)
face_vert_counts_combined.extend(
cur_prim.GetAttribute("faceVertexCounts").Get()
)
face_vert_idx_raw = np.array(
cur_prim.GetAttribute("faceVertexIndices").Get()
)
face_vert_idx = face_vert_idx_raw + vert_idx_offset
face_vert_idx_combined.append(face_vert_idx)
normals_combined.extend(cur_prim.GetAttribute("normals").Get())
vert_idx_offset += len(points_transformed)
projection_prim.GetAttribute("points").Set(np.vstack(vert_combined))
projection_prim.GetAttribute("faceVertexCounts").Set(
np.array(face_vert_counts_combined)
)
projection_prim.GetAttribute("faceVertexIndices").Set(
np.hstack(face_vert_idx_combined)
)
projection_prim.GetAttribute("normals").Set(np.array(normals_combined))
return projection_prim
def _create_mesh_shell_mod(target_prim, path):
"""Create a mesh shell from input mesh(es)
Concatenate points, polygons and normals and create a new mesh at the specified path.
"""
stage = omni.usd.get_context().get_stage()
projection_prim = stage.DefinePrim(path, "Mesh")
vert_combined = []
face_vert_counts_combined = []
face_vert_idx_combined = []
normals_combined = []
cur_time = 0.0
target_prim_to_world = UsdGeom.Xformable(target_prim).ComputeLocalToWorldTransform(
cur_time
)
vert_idx_offset = 0
descendants = [target_prim]
prototype_tfs = {}
while descendants:
cur_prim = descendants.pop()
if cur_prim.HasAttribute("replicatorProjection"):
continue
if cur_prim.IsInstanceable():
prototype_tfs[cur_prim.GetPrototype().GetName()] = UsdGeom.Xformable(
cur_prim
).ComputeLocalToWorldTransform(cur_time)
descendants.append(cur_prim.GetPrototype())
elif cur_prim.GetTypeName() != "Mesh":
descendants.extend(cur_prim.GetChildren())
else:
mesh_to_world = UsdGeom.Xformable(cur_prim).ComputeLocalToWorldTransform(
cur_time
)
root_path = str(cur_prim.GetPrimPath()).split("/", 2)[1]
if cur_prim.IsInPrototype() and root_path in prototype_tfs:
mesh_to_world = mesh_to_world * prototype_tfs.get(root_path)
mesh_to_target = mesh_to_world * target_prim_to_world.GetInverse()
points_raw = np.array(cur_prim.GetAttribute("points").Get())
points_transformed = (
np.pad(points_raw, ((0, 0), (0, 1)), constant_values=1) @ mesh_to_target
)[..., :3]
vert_combined.append(points_transformed)
face_vert_counts_combined.extend(
cur_prim.GetAttribute("faceVertexCounts").Get()
)
face_vert_idx_raw = np.array(
cur_prim.GetAttribute("faceVertexIndices").Get()
)
face_vert_idx = face_vert_idx_raw + vert_idx_offset
face_vert_idx_combined.append(face_vert_idx)
normals_combined.extend(cur_prim.GetAttribute("normals").Get())
vert_idx_offset += len(points_transformed)
projection_prim.GetAttribute("points").Set(np.vstack(vert_combined))
projection_prim.GetAttribute("faceVertexCounts").Set(
np.array(face_vert_counts_combined)
)
projection_prim.GetAttribute("faceVertexIndices").Set(
np.hstack(face_vert_idx_combined)
)
projection_prim.GetAttribute("normals").Set(np.array(normals_combined))
# Copy any primvar data from the src to the dst mesh, as they can contain
# essential data for render engine
for pv in UsdGeom.PrimvarsAPI(target_prim).GetPrimvars():
_primvar = UsdGeom.PrimvarsAPI(projection_prim).CreatePrimvar(
pv.GetName(), pv.GetTypeName()
)
_primvar.SetInterpolation(pv.GetInterpolation())
if pv.HasValue():
_primvar.Set(pv.Get())
projection_prim_mesh = UsdGeom.Mesh(projection_prim)
target_prim_mesh = UsdGeom.Mesh(target_prim)
# set mesh normals interpolation and subdivision
projection_prim_mesh.SetNormalsInterpolation(
target_prim_mesh.GetNormalsInterpolation()
)
projection_prim_mesh.CreateSubdivisionSchemeAttr("none")
return projection_prim
stage = omni.usd.get_context().get_stage()
# Create parent prim
omni.kit.commands.create("CreateMeshPrimCommand", prim_type="Cylinder").do()
parent_prim = stage.GetPrimAtPath("/World/Cylinder")
# Create Light
# rep.create.plane(scale=(10, 10, 1))
rep.create.light(rotation=(0, 45, 0), intensity=3000, light_type="distant")
rep.create.light(position=(1, 0, 1), intensity=10000, scale=0.1, light_type="sphere")
# Assign material
_mdl = "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Materials/Base/Masonry/Brick_Pavers.mdl"
on_mat = lambda mat_prim: UsdShade.MaterialBindingAPI(parent_prim).Bind(
UsdShade.Material(mat_prim), UsdShade.Tokens.weakerThanDescendants
)
mat_lib.create_mdl_material(stage, _mdl, "Brick_Pavers", on_mat)
UsdShade.MaterialBindingAPI.Apply(parent_prim)
# Make mesh copy
copy_prim = _create_mesh_shell(parent_prim, "/World/Copy")
# Fixed version
# copy_prim = _create_mesh_shell_mod(parent_prim, "/World/Copy")
on_mat = lambda mat_prim: UsdShade.MaterialBindingAPI(copy_prim).Bind(
UsdShade.Material(mat_prim), UsdShade.Tokens.weakerThanDescendants
)
mat_lib.create_mdl_material(stage, _mdl, "Brick_Pavers", on_mat)
UsdShade.MaterialBindingAPI.Apply(copy_prim)
Example:
If you enable debug mode view for Tangent U or Texture Coordinates you will see that on “shell” mesh those are flipped/missing.
I cannot say that this lack of Parent primvars on “shell” mesh is correct, because depending on material you want to use with this node in your project you can get very strange behavior like on example.
The fix is provided within above code in _create_mesh_shell_mod
. Solution is similar to _copy_base_mesh
included in omni.kit.tools module /ov/pkg/isaac_sim-2023.1.1/extscache/omni.kit.tools.mergemesh-0.1.6/omni/kit/tools/mergemesh/merge_mesh.py
.
And the result is 1:1 copy of target_prim mesh with all primvars and some additional mesh settings:
@pcallender
Could you please check this with devs also.