Using PythonAPI to assign Normal and Opacity Maps onto a Material

Hello,

I’ve been racking my brains recently trying to figure out how to to assign Normal and Opacity Maps onto a Material in USD Composer using the Python API. I’m also using the omni.replicator extension. Has anyone achieved this before?

Many thanks in advance

What version of USD Composer? That’s likely to be a pretty old version of replicator. If you’re willing to use an up to date version of Isaac Sim (what’s recommended for SDG with replicator), you can use a “Functional” python approach, and manipulate material attributes like so.

Note the texture assignment instead would have a path to a normal texture and opacity texture, and the value names would be inputs:normalmap_texture and ‘inputs:opacity_texture respectively.

import asyncio
import omni.usd
from pxr import Gf, Usd, UsdShade, Sdf
import omni.replicator.core as rep
import numpy as np
from omni.replicator.core import functional as F

# https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/usd/materials/create-mdl-material.html

def change_mat_color3(stage, mat_prim, attribute, value):
    shader_prim = mat_prim.GetChild("Shader")
    if not shader_prim.GetAttribute(attribute).IsValid():
        shader_prim.CreateAttribute(attribute, Sdf.ValueTypeNames.Color3f, custom=True).Set((0.0, 0.0, 0.0))

    # Set the diffuse color to the input color
    shader_prim.GetAttribute(attribute).Set(Gf.Vec3f([value[0], value[1], value[2]]))

def change_mat_texture(stage, mat_prim, attribute, texture_path):
    shader_prim = mat_prim.GetChild("Shader")
    if not shader_prim.GetAttribute(attribute).IsValid():
        shader_prim.CreateAttribute(attribute, Sdf.ValueTypeNames.Asset, custom=True).Set("")

    # Set the diffuse color to the input color
    shader_prim.GetAttribute(attribute).Set(texture_path)

def change_mat_float2(stage, mat_prim, attribute, value):
    shader_prim = mat_prim.GetChild("Shader")
    if not shader_prim.GetAttribute(attribute).IsValid():        
        shader_prim.CreateAttribute(attribute, Sdf.ValueTypeNames.Float2, custom=True).Set(Gf.Vec2f(0.0, 0.0))

    # Set the diffuse color to the input color
    shader_prim.GetAttribute(attribute).Set(Gf.Vec2f([value[0], value[1]]))

def change_mat_float(stage, mat_prim, attribute, value):
    shader_prim = mat_prim.GetChild("Shader")
    if not shader_prim.GetAttribute(attribute).IsValid():        
        shader_prim.CreateAttribute(attribute, Sdf.ValueTypeNames.Float, custom=True).Set(0.0)

    # Set the diffuse color to the input color
    shader_prim.GetAttribute(attribute).Set(value)


# Set default parameters
NUM_IMAGES = 10
DIFF_TEXTURE_PATH = "https://omniverse-content-production.s3.us-west-2.amazonaws.com/Materials/2023_1/vMaterials_2/Masonry/textures/clinker_diff.jpg"

# Get the current USD context, and make a new stage
ctx = omni.usd.get_context()
ctx.new_stage()
stage = omni.usd.get_context().get_stage()

# Set the scene UP axis and the default unit size
rep.settings.set_stage_up_axis("Z")
rep.settings.set_stage_meters_per_unit(1.0)

# Randomization Seed
rng = np.random.RandomState(1234)

# Set the renderer
rep.settings.set_render_pathtraced(samples_per_pixel=16)
#rep.settings.set_render_rtx_realtime()

# Use Replicator to make a new material from OmniPBR.mdl
rep_material = F.create.material(mdl="OmniPBR.mdl", diffuse_color_constant=(0,0,1))

# Create ground plane and a blue cube
ground = F.create.plane(scale=100, position=(0,0,0))
cube_01 = F.create.cube(position=(0,0,1.25), material=rep_material, scale=1, semantics={"class": "cube"}, rotation=(0,0,-40))

# Create a distant light
light = F.create_batch.distant_light(position=(0, 2, 0), color=(1, 0.8, 0.6), intensity=800, angle=1, rotation=(0,-40,-150))

# Create a camera
camera = F.create.camera(look_at=(0,0,1.25), position=(0, 5, 3), focal_length=16)

# Create a render product
rp = rep.create.render_product(camera, (1280, 704))

# Define the use of the default Basic Writer and initialize it
writer = rep.WriterRegistry.get("BasicWriter")
writer.initialize(output_dir="material_attributes", rgb=True)

# For using `writer.schedule_write()` it is critical that `trigger=None` is set in the writer
writer.attach(rp, trigger= None)

async def capture():

    for i in range(NUM_IMAGES):

        # Use custom function to change the attributes of the material
        change_mat_color3(stage, rep_material, "inputs:diffuse_tint", rng.uniform((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)))
        change_mat_texture(stage, rep_material, "inputs:diffuse_texture", DIFF_TEXTURE_PATH)
        change_mat_float2(stage, rep_material, "inputs:texture_translate", rng.uniform((0.0, 0.0), (1.0, 1.0)))
        change_mat_float2(stage, rep_material, "inputs:texture_scale", rng.uniform((0.2, 0.2), (2.0, 2.0)))
        change_mat_float(stage, rep_material, "inputs:texture_rotate", rng.uniform(0.0, 360.0))

        await rep.orchestrator.step_async(wait_for_render=False)
        writer.schedule_write()

asyncio.ensure_future(capture())