Handling Material Binding / Property Modification Failures After CreateAndBindMdlMaterialFromLibrary

Issue Overview

In my current workflow, I create MDL materials using the CreateAndBindMdlMaterialFromLibrary command. While the material itself is created successfully, subsequent binding or property modification operations often fail or produce unexpected results.

Root Cause Analysis

Through extensive testing, I have identified the following key points:

  1. CreateAndBindMdlMaterialFromLibrary is an asynchronous operation.
    The material does not appear immediately in the USD stage after the command is executed. Instead, the command triggers a read of the .mdl file and loads the material via asynchronous I/O. As a result, the material prim is not yet available in the stage immediately after the command returns.

  2. Immediate subsequent operations fail due to missing material prim.
    If I attempt to bind the material or modify its attributes (e.g., using BindMaterial, ChangeProperty, or directly accessing the shader prim) right after calling CreateAndBindMdlMaterialFromLibrary, the operation typically fails—because the material prim does not yet exist in the stage. This often leads to errors such as:

    Empty typeName for </World/Looks/OmniSurface/Shader.inputs:diffuse_reflection_weight>
    
  3. The material may appear later, but early operations already failed.
    The material is eventually added to the stage after the asynchronous load completes. However, because the binding/modification commands were executed too early, they fail or have no effect—leaving the material in the scene but without the desired bindings or property values.

Solution

Since CreateAndBindMdlMaterialFromLibrary is inherently asynchronous, all dependent operations (binding, property modification, etc.) must wait for the material to be fully loaded before proceeding.

The correct approach is to await the command (or wrap it in an async function) to ensure the material is ready before executing any subsequent commands.

Here is a simplified example of the corrected workflow:

class MaterialManager:
    """Manage creation, update, and deletion of USD materials."""
	async def create_optical_material(name: str, ref: Tuple, tra: Tuple, is_leaf):
        """执行 USD 命令创建材质结构"""
        async def set_shader_attributes(shader_prim: Usd.Prim, ref, tran):
            # 可以在创建材质阶段通过on_created_fn控制属性。
            shader_prim.CreateAttribute('inputs:diffuse_reflection_weight', Sdf.ValueTypeNames.Float).Set(1.0)
            shader_prim.CreateAttribute('inputs:specular_reflection_weight', Sdf.ValueTypeNames.Float).Set(0.0)
        def callback(prim):
            asyncio.ensure_future(set_shader_attributes(prim, ref, tra))
        omni.kit.commands.execute(
            'CreateAndBindMdlMaterialFromLibrary',
            mdl_name='OmniSurface.mdl',
            mtl_name='OmniSurface',
            prim_name=name,
            on_created_fn=callback)
# 创建材质
await MaterialManager.update_stage_materials(stage, is_foliage, material_name, color)
# 绑定材质
omni.kit.commands.execute('BindMaterial',
    material_path=material_path,
    prim_path=[mesh_prim.GetPath().pathString],
    strength=['weakerThanDescendants'],
    material_purpose='')
# 更改材质属性
# GetAttribute方式
stage = omni.usd.get_context().get_stage()
material_shader_prim = stage.GetPrimAtPath("/World/Looks/OmniSurface/Shader")
material_shader_prim.GetAttribute('inputs:diffuse_reflection_weight').Set(float(0.5))
# command命令方式
omni.kit.commands.execute('ChangeProperty',
   prop_path=Sdf.Path('/World/Looks/OmniSurface/Shader.inputs:diffuse_reflection_color'),
    value=Gf.Vec3f(1.0, 0.0, 0.0),
    prev=Gf.Vec3f(1.0, 1.0, 1.0))

Closing Notes

It is important to emphasize that silent failures are common when binding operations are attempted before material loading completes—the command may not throw an error, but the binding simply does not take effect. This makes the issue particularly subtle to debug. Always ensure proper async handling in your material creation pipeline.