omni.mdl.pymdlsdk

This extension provides Python bindings for the MDL SDK. It consists of two modules:

  • pymdlsdk, which is a low-level binding automatically generated from the C++ API using SWIG.

  • pymdl, a hand-written high-level binding based on pymdlsdk.

Python API - pymdlsdk

The MDL Python Bindings are generated from the C++ sources using SWIG. For a complete API reference, the specification, and more information, please refer to NVIDIA Raytracing Docs.

Python API - pymdl

MDL Python Bindings - a high-level wrapper for a more efficient and user-friendly usage.

Note, this is an experimental library to simply a few of the use cases of the MDL SDK. For a complete access to the MDL SDK API, please refer to the low-level binding, pymdlsdk.py.

class omni.mdl.pymdl.Annotation(iannotation: omni.mdl.pymdlsdk.IAnnotation)

Bases: object

Wrapper around an MDL annotation.

property arguments

Dictionary with the annotations arguments and their values.

property moduleDbName

The database of the module in which the annotation is defined.

property name

The full name of the annotation consisting of the module name, the simple name and the parameter list.

property parameterTypeNames

The list of parameter type names of the signature of this annotation.

property simpleName

The name of the annotation within the module it is defined in without its parameter list.

omni.mdl.pymdl.AnnotationBlock(iannotation_block: omni.mdl.pymdlsdk.IAnnotation_block)Tuple[omni.mdl.pymdl.Annotation, ]

Creates an immutable list of annotations from an MDL annotation block.

Parameters

iannotation_block (pymdlsdk.IAnnotation_block) – Low level MDL annotation block proxy generated for the Python binding.

Returns

A list of high level wrapped annotation information read from the annotation block.

Return type

tuple(Annotation)

class omni.mdl.pymdl.Argument(type: omni.mdl.pymdl.Type, annotations: () = ())

Bases: object

Wrapper around an MDL parameter. Argument combines type information, annotations, and optionally the value of an MDL parameter.

Argument is used:

  • To describe the parameters of a FunctionCall. The value of the Argument is the value of the corresponding function call parameter.

  • To describe the parameters of a FunctionDefinition. In this case, the value of the Argument is the default value of the corresponding definition parameter if applicable (i.e. default value exists.)

  • To describe the Parameters of an Annotation along with its value.

  • To describe return types of functions and their annotations. In this case, the value is not used.

There are two kinds of Argument: ArgumentConstant and ArgumentCall. ArgumentConstant holds an actual value. ArgumentCall refers to other function calls which allows to construct expression graphs.

Argument gives access to:

type: Type

The parameter type.

annotations: tuple(Annotation)

The parameter annotations.

class omni.mdl.pymdl.ArgumentCall(iexpression: omni.mdl.pymdlsdk.IExpression_call, annotations=())

Bases: omni.mdl.pymdl.Argument

Wrapper around an MDL FunctionCall parameter.

ArgumentCall gives access to:

value: str

The DB name of the referenced function call.

class omni.mdl.pymdl.ArgumentConstant(ivalue: omni.mdl.pymdlsdk.IValue, annotations: () = ())

Bases: omni.mdl.pymdl.Argument

Wrapper around an MDL parameter value.

Type gives access to:

value: See IValueToPyValues()

Holds an actual parameter value. The MDL value is transformed using IValueToPyValues()

omni.mdl.pymdl.DowncastIExpression(iexpression: omni.mdl.pymdlsdk.IExpression)omni.mdl.pymdlsdk.IExpression

Cast the input expression into the proper derived interface depending on the kind of expression.

Here is the mapping from the expression kind to the returned interface:

  • EK_CONSTANT –> IExpression_constant

  • EK_CALL –> IExpression_call

  • EK_PARAMETER –> IExpression_parameter

  • EK_DIRECT_CALL –> IExpression_direct_call

  • EK_TEMPORARY –> IExpression_temporary

omni.mdl.pymdl.DowncastIType(itype: omni.mdl.pymdlsdk.IType)omni.mdl.pymdlsdk.IType

Cast the input type into the proper derived interface depending on the kind of type.

Here is the mapping from the type kind to the returned interface:

  • TK_ALIAS –> IType_alias

  • TK_BOOL –> IType_bool

  • TK_INT –> IType_int

  • TK_ENUM –> IType_enumeration

  • TK_FLOAT –> IType_float

  • TK_DOUBLE –> IType_double

  • TK_STRING –> IType_string

  • TK_VECTOR –> IType_vector

  • TK_MATRIX –> IType_matrix

  • TK_COLOR –> IType_color

  • TK_ARRAY –> IType_array

  • TK_STRUCT –> IType_structure

  • TK_TEXTURE –> IType_texture

  • TK_LIGHT_PROFILE –> IType_light_profile

  • TK_BSDF_MEASUREMENT –> IType_bsdf_measurement

  • TK_BSDF –> IType_bsdf

  • TK_HAIR_BSDF –> IType_hair_bsdf

  • TK_EDF –> IType_edf

  • TK_VDF –> IType_vdf

omni.mdl.pymdl.DowncastIValue(ivalue: omni.mdl.pymdlsdk.IValue)omni.mdl.pymdlsdk.IValue

Cast the input value into the proper derived interface depending on the kind of value.

Here is the mapping from the ivalue kind to the returned interface:

  • VK_BOOL –> IValue_bool

  • VK_INT –> IValue_int

  • VK_ENUM –> IValue_enumeration

  • VK_FLOAT –> IValue_float

  • VK_DOUBLE –> IValue_double

  • VK_STRING –> IValue_string

  • VK_VECTOR –> IValue_vector

  • VK_MATRIX –> IValue_matrix

  • VK_COLOR –> IValue_color

  • VK_ARRAY –> IValue_array

  • VK_STRUCT –> IValue_structure

  • VK_INVALID_DF –> IValue_invalid_df

  • VK_TEXTURE –> IValue_texture

  • VK_LIGHT_PROFILE –> IValue_light_profile

  • VK_BSDF_MEASUREMENT –> IValue_bsdf_measurement

class omni.mdl.pymdl.FunctionCall(func: omni.mdl.pymdlsdk.IFunction_call, dbName: str)

Bases: object

Wrapper around MDL function call.

FunctionCall gives access to:

functionDefinition: str

The DB name of the corresponding function definition.

mdlFunctionDefinition: str

The MDL name of the corresponding function definition.

parameters: Dict[str, Argument]

Dictionary of the function call parameters as Argument. Key is parameter name corresponding to the Argument.

class omni.mdl.pymdl.FunctionDefinition(func: omni.mdl.pymdlsdk.IFunction_definition, dbName: str)

Bases: object

Wrapper around MDL function definition.

FunctionDefinition gives access to:

annotations: AnnotationBlock

The annotations of the function definition itself, or None if there are no such annotations.

dbName: str

DB name of the function definitions.

mdlModuleName:str

The MDL name of the module containing this function definition.

mdlName: str

The MDL name of the function definition.

mdlSimpleName: str

The simple MDL name of the function definition. The simple name is the last component of the MDL name, i.e., without any packages and scope qualifiers, and without the parameter type names.

moduleDbName: str

The DB name of the module containing this function definition.

parameters: Dict[str, Argument]

Dictionary of the function definition parameters as Argument. Key is parameter name corresponding to the Argument.

parameterTypeNames: tuple(str)

The type name of all the parameters.

returnValue: Argument

The return type as an Argument.

omni.mdl.pymdl.IValueToPyValues(ivalue: omni.mdl.pymdlsdk.IValue)

Converts low level IValues to python friendly data types.

Here is the mapping from the ivalue kind to the returned values:

  • VK_BOOL –> ivalue.get_value()

  • VK_INT –> ivalue.get_value()

  • VK_FLOAT –> ivalue.get_value()

  • VK_DOUBLE –> ivalue.get_value()

  • VK_STRING –> ivalue.get_value()

  • VK_LIGHT_PROFILE –> ivalue.get_value()

  • VK_BSDF_MEASUREMENT –> ivalue.get_value()

  • VK_ENUM –> (ivalue.get_name(), ivalue.get_value())

  • VK_TEXTURE –> (ivalue.get_value(), ivalue.get_gamma())

  • VK_COLOR –> numpy.array(color components)

  • VK_ARRAY –> [IValueToPyValues(array values)]

  • VK_STRUCT –> dict(structure field names, ArgumentConstant(structure fields))

  • VK_INVALID_DF –> None

For vectors (kind is VK_VECTOR), each value is converted using IValueToPyValues(). A numpy.array is returned with type depending on the vector element kind. Here is the mapping between the vector element kind and the array type.

  • TK_FLOAT –> numpy.float32

  • TK_DOUBLE –> numpy.float64

  • TK_INT –> numpy.int32

  • TK_BOOL –> bool

For matrices (kind is VK_MATRIX), each value is converted using IValueToPyValues(). A numpy.array is returned with type depending on the vector element kind. Here is the mapping between the matrix element kind and the array type.

  • TK_FLOAT –> numpy.float32

  • TK_DOUBLE –> numpy.float64

  • TK_INT –> numpy.int32

  • TK_BOOL –> bool

class omni.mdl.pymdl.Module(transaction: omni.mdl.pymdlsdk.ITransaction, module: omni.mdl.pymdlsdk.IModule, dbName: str)

Bases: object

Wrapper around an MDL module.

Module gives access to:

dbName: str

The DB name of the module.

filename: str

The name of the MDL source file from which the module was created.

functionDbNames: tuple(str)

DB names of all the function definitions and the material definitions from this module.

functions: Dict[str, FunctionDefinition]

Dictionary of FunctionDefinition. Keys is FunctionDefinition simple name.

mdlName: str

The MDL name of the module.

mdlSimpleName: str

The simple MDL name of the module. The simple name is the last component of the MDL name, i.e., without any packages and scope qualifiers.

class omni.mdl.pymdl.Type(itype: omni.mdl.pymdlsdk.IType)

Bases: object

Wrapper around MDL type.

Type gives access to:

kind: Kind

The kind of type.

symbol: str

If type is enum or struct, this is the qualified name of the enum or struct type. Otherwise this is None.

Python Example

To use the MDL Python Bindings in OV the omni.mdl.neuraylib is required as well. Here is a simple example to illustrate the access of the neuray instance in OV, the creation of a database transaction, and the usage of the high-level bindings.

# *****************************************************************************
# Copyright 2021 NVIDIA Corporation. All rights reserved.
# *****************************************************************************

import omni.mdl.pymdlsdk    # low-level MDL python binding that matches the native SDK
import omni.mdl.pymdl       # high-level wrapper
import omni.mdl.neuraylib   # interface the OV material backend
import time

# check if a function is a material.
# materials don't allow overloads (for the forseeable future) and therefore don't need
# an argument list in the sub-identifier name
def isMaterial(trans: omni.mdl.pymdlsdk.ITransaction, functionDbName: str):
    with trans.access_as(omni.mdl.pymdlsdk.IMaterial_definition, functionDbName) as m:
        return m.is_valid_interface()

# we need the active renderer here, the rtx_scope is used by default.
# However, this is not working when Iray is active. In the future we want to get rid of that
# separation but this requires bigger changes to Iray itself.
dbScopeName = "rtx_scope" # (used as default)

# acquire neuray instance from OV
ovNeurayLib = omni.mdl.neuraylib.get_neuraylib()
ovNeurayLibHandle = ovNeurayLib.getNeurayAPI()

# feed the neuray instance into the python binding
neuray: omni.mdl.pymdlsdk.INeuray = omni.mdl.pymdlsdk.attach_ineuray(ovNeurayLibHandle)

# we need to load modules to OV using the neuraylib
# on the c++ side this is async, here it is blocking if the module is not loaded yet by the renderer
usdIdentifier = "nvidia/support_definitions.mdl"
start = time.time()
ovModule = ovNeurayLib.createMdlModule(usdIdentifier)
end = time.time()
print(f" loading '{usdIdentifier}' took {end-start} seconds.")

# create a transaction after loading so we we can see the loaded module
ovNeurayLibTransactionHandle = ovNeurayLib.createReadingTransaction(dbScopeName)
trans: omni.mdl.pymdlsdk.ITransaction = omni.mdl.pymdlsdk.attach_itransaction(ovNeurayLibTransactionHandle)

# when the module is loaded we can use the high-level python binding to inspect the module
if ovModule:
    module: omni.mdl.pymdl.Module = omni.mdl.pymdl.Module._fetchFromDb(trans, ovModule.dbName)

    # you can already free all low level objects here
    ovNeurayLib.destroyMdlModule(ovModule)

# inspect the module
if module:
    print(f" filename: {module.filename}")
    print(f" dbName: {module.dbName}")
    print(f" mdlName: {module.mdlName}")
    print(f" mdlSimpleName: {module.mdlSimpleName}")

    print(f" functions:")
    for funcName in module.functions:
        print(f" - name: {funcName}")
        overloads = module.functions[funcName]
        for func in overloads:
            if isMaterial(trans, func.dbName):
                print(f"   * Material: {func.mdlSimpleName}")
            else:
                print(f"   * Function: {func.mdlSimpleName} {func.parameterTypeNames}")

            # Annotations
            if func.annotations:
                print(f"      Annotations:")
                anno: omni.mdl.pymdl.Annotation
                for anno in func.annotations:
                    print(f"      - Simple Name: {anno.simpleName}")
                    print(f"        Qualified Name: {anno.name}")
                    arg: omni.mdl.pymdl.Argument
                    for arg_name, arg in anno.arguments.items():
                        print(f"        ({arg.type.kind}) {arg_name}: {arg.value}")

# also release the transaction and neuray
if trans:
    trans.abort()
trans = None
neuray = None