Example API Usage¶
The following notebook shows some basic usage of the API.
MaterialX to glTF Conversion Example¶
The following example loads a test data MaterialX file and writes the results to glTF JSON output.
# Import support modules
import MaterialX as mx
import json
from IPython.display import display, Markdown
# Import conversion module utilities
from gltf_materialx_converter import converter as MxGLTFPT
from gltf_materialx_converter import utilities as MxGLTFPTUtil
input_file = "../tests/data/checkerboard_graph.mtlx" # Replace with desired file name
# Set up definitions and read in a sample file
stdlib, libFiles = MxGLTFPTUtil.load_standard_libraries()
mxdoc = MxGLTFPTUtil.create_working_document([stdlib])
mx.readFromXmlFile(mxdoc, input_file)
# Set up string to display just the contents of the file w/o the libraries
mxdoc2 = MxGLTFPTUtil.create_working_document([])
mx.readFromXmlFile(mxdoc2, input_file)
mtlx_string = mx.writeToXmlString(mxdoc2)
# Convert
converter = MxGLTFPT.glTFMaterialXConverter()
json_string, status = converter.materialX_to_glTF(mxdoc)
display(Markdown(f"#### Converted glTF\n```json\n{json_string}\n```"))
display(Markdown(f"#### Original MaterialX\n```xml\n{mtlx_string}\n```"))
INFO:glTFMtlx:> Convert shader to glTF: Gltf_pbr. Category: gltf_pbr
Converted glTF¶
{
"images": [
{
"uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z/AfAAQAAf/zKSWvAAAAAElFTkSuQmCC",
"name": "KHR_texture_procedural_fallback"
}
],
"textures": [
{
"source": 0
}
],
"extensions": {
"KHR_texture_procedurals": {
"procedurals": [
{
"name": "NG_main",
"nodetype": "nodegraph",
"type": "color3",
"inputs": [
{
"name": "uvtiling",
"nodetype": "input",
"type": "vector2",
"value": [
8.0,
8.0
]
},
{
"name": "color1",
"nodetype": "input",
"type": "color3",
"value": [
1.0,
0.094118,
0.031373
]
},
{
"name": "color2",
"nodetype": "input",
"type": "color3",
"value": [
0.035294,
0.090196,
0.878431
]
}
],
"outputs": [
{
"name": "output_N_mtlxmix_out",
"nodetype": "output",
"type": "color3",
"node": 0
}
],
"nodes": [
{
"name": "N_mtlxmix",
"nodetype": "mix",
"type": "color3",
"inputs": [
{
"name": "fg",
"nodetype": "input",
"type": "color3",
"input": 1
},
{
"name": "bg",
"nodetype": "input",
"type": "color3",
"input": 2
},
{
"name": "mix",
"nodetype": "input",
"type": "float",
"node": 5
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "color3"
}
]
},
{
"name": "N_mtlxdotproduct",
"nodetype": "dotproduct",
"type": "float",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "vector2",
"node": 4
},
{
"name": "in2",
"nodetype": "input",
"type": "vector2",
"value": [
1.0,
1.0
]
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "float"
}
]
},
{
"name": "N_mtlxmult",
"nodetype": "multiply",
"type": "vector2",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "vector2",
"node": 6
},
{
"name": "in2",
"nodetype": "input",
"type": "vector2",
"input": 0
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
},
{
"name": "N_mtlxsubtract",
"nodetype": "subtract",
"type": "vector2",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "vector2",
"node": 2
},
{
"name": "in2",
"nodetype": "input",
"type": "vector2",
"value": [
0.0,
0.0
]
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
},
{
"name": "N_mtlxfloor",
"nodetype": "floor",
"type": "vector2",
"inputs": [
{
"name": "in",
"nodetype": "input",
"type": "vector2",
"node": 3
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
},
{
"name": "N_modulo",
"nodetype": "modulo",
"type": "float",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "float",
"node": 1
},
{
"name": "in2",
"nodetype": "input",
"type": "float",
"value": 2.0
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "float"
}
]
},
{
"name": "Texcoord",
"nodetype": "texcoord",
"type": "vector2",
"inputs": [
{
"name": "index",
"nodetype": "input",
"type": "integer",
"value": 0
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
}
],
"xpos": "-1.2309688043141684",
"ypos": "3.189328265723073"
}
]
}
},
"materials": [
{
"name": "Gltf_pbr",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"extensions": {
"KHR_texture_procedurals": {
"index": 0,
"output": 0
}
}
}
}
}
],
"asset": {
"version": "2.0",
"generator": "MaterialX 1.39 / glTF 2.0 Texture Procedural Converter"
},
"extensionsUsed": [
"KHR_texture_procedurals",
"EXT_texture_procedurals_mx_1_39"
]
}
Original MaterialX¶
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709">
<nodegraph name="NG_main" xpos="-1.2309688043141684" ypos="3.189328265723073">
<input name="uvtiling" type="vector2" value="8,8" />
<input name="color1" type="color3" value="1,0.094118,0.031373" />
<input name="color2" type="color3" value="0.035294,0.090196,0.878431" />
<output name="output_N_mtlxmix_out" type="color3" nodename="N_mtlxmix" />
<mix name="N_mtlxmix" type="color3">
<input name="fg" type="color3" interfacename="color1" />
<input name="bg" type="color3" interfacename="color2" />
<input name="mix" type="float" nodename="N_modulo" />
</mix>
<dotproduct name="N_mtlxdotproduct" type="float">
<input name="in1" type="vector2" nodename="N_mtlxfloor" />
<input name="in2" type="vector2" value="1,1" />
</dotproduct>
<multiply name="N_mtlxmult" type="vector2">
<input name="in1" type="vector2" nodename="Texcoord" />
<input name="in2" type="vector2" interfacename="uvtiling" />
</multiply>
<subtract name="N_mtlxsubtract" type="vector2">
<input name="in1" type="vector2" nodename="N_mtlxmult" />
<input name="in2" type="vector2" value="0,0" />
</subtract>
<floor name="N_mtlxfloor" type="vector2">
<input name="in" type="vector2" nodename="N_mtlxsubtract" />
</floor>
<modulo name="N_modulo" type="float">
<input name="in1" type="float" nodename="N_mtlxdotproduct" />
<input name="in2" type="float" value="2" />
</modulo>
<texcoord name="Texcoord" type="vector2">
<input name="index" type="integer" value="0" />
</texcoord>
</nodegraph>
<gltf_pbr name="Gltf_pbr" type="surfaceshader">
<input name="base_color" type="color3" nodegraph="NG_main" />
</gltf_pbr>
<surfacematerial name="MAT_Gltf_pbr" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="Gltf_pbr" />
</surfacematerial>
</materialx>
glTF to MaterialX Convertion Example¶
The same test data example is used by taking the glTF result from the previous example and re-converting this back into MaterialX.
converter.set_add_asset_info(True)
mxdoc3 = converter.gltf_string_to_materialX(json_string, stdlib)
mtlx_string3 = mx.writeToXmlString(mxdoc3)
display(Markdown(f"#### Converted MaterialX\n```xml\n{mtlx_string3}\n```"))
INFO:glTFMtlx:> Importing 1 procedural graphs INFO:glTFMtlx:> Create new nodegraph: NG_main INFO:glTFMtlx:> Scan 1 procedural outputs INFO:glTFMtlx:> Import material: MATERIAL_Gltf_pbr. Shader: Gltf_pbr
Converted MaterialX¶
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709" doc="glTF version: 2.0. glTF generator: MaterialX 1.39 / glTF 2.0 Texture Procedural Converter. ">
<nodegraph name="NG_main" xpos="-1.2309688043141684" ypos="3.189328265723073">
<input name="uvtiling" type="vector2" value="8, 8" />
<input name="color1" type="color3" value="1, 0.094118, 0.031373" />
<input name="color2" type="color3" value="0.035294, 0.090196, 0.878431" />
<mix name="N_mtlxmix" type="color3">
<input name="fg" type="color3" interfacename="color1" />
<input name="bg" type="color3" interfacename="color2" />
<input name="mix" type="float" nodename="N_modulo" />
</mix>
<dotproduct name="N_mtlxdotproduct" type="float">
<input name="in1" type="vector2" nodename="N_mtlxfloor" />
<input name="in2" type="vector2" value="1, 1" />
</dotproduct>
<multiply name="N_mtlxmult" type="vector2">
<input name="in1" type="vector2" nodename="Texcoord" />
<input name="in2" type="vector2" interfacename="uvtiling" />
</multiply>
<subtract name="N_mtlxsubtract" type="vector2">
<input name="in1" type="vector2" nodename="N_mtlxmult" />
<input name="in2" type="vector2" value="0, 0" />
</subtract>
<floor name="N_mtlxfloor" type="vector2">
<input name="in" type="vector2" nodename="N_mtlxsubtract" />
</floor>
<modulo name="N_modulo" type="float">
<input name="in1" type="float" nodename="N_mtlxdotproduct" />
<input name="in2" type="float" value="2" />
</modulo>
<texcoord name="Texcoord" type="vector2">
<input name="index" type="integer" value="0" />
</texcoord>
<output name="output_N_mtlxmix_out" type="color3" nodename="N_mtlxmix" />
</nodegraph>
<gltf_pbr name="Gltf_pbr" type="surfaceshader">
<input name="base_color" type="color3" nodegraph="NG_main" />
</gltf_pbr>
<surfacematerial name="MATERIAL_Gltf_pbr" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="Gltf_pbr" />
</surfacematerial>
</materialx>
Equivalency Check¶
As of MaterialX version 1.39.2 a "functional" equivalency test is available. We perform this test to check that the re-converted MaterialX file is functionally equivalent to the original document.
We explicitly set the options to perform value comparisons via the performValueComparisons
option (as opposed to straight string comparisons) to perform the desired check. Note that this option is set by default.
have_required_version = MxGLTFPTUtil.have_version(1, 39, 2)
if have_required_version:
print(f'MaterialX version is 1.39.2 or higher. Perform equivalence check:')
# Set up options
equivalence_opts = mx.ElementEquivalenceOptions()
# Always use value comparisons vs string comparisons
equivalence_opts.performValueComparisons = True
# Always skip documentation attributes
equivalence_opts.attributeExclusionList = { 'doc' }
# Remove shaders and materials from the documents
for mnode in mxdoc2.getMaterialNodes():
mxdoc2.removeNode(mnode.getName())
for mnode in mxdoc3.getMaterialNodes():
mxdoc3.removeNode(mnode.getName())
# Check for equivalence
equivalent, message = mxdoc3.isEquivalent(mxdoc2, equivalence_opts)
if equivalent:
print(f"- Documents are functionally equivalent.")
else:
print(f"- Documents are not functionally equivalent: '{message}'.")
else:
print(f"- Cannot compare documents because MaterialX version is less than 1.39.2")
MaterialX version is 1.39.2 or higher. Perform equivalence check: - Documents are functionally equivalent.
Conversion With Unnamed glTF Element¶
glTF does not require any string name identifiers to identify specific elements. As such name-generation is required when converting from such content to name-based data models such as MaterialX or OpenUSD.
In the example below, we remove all the names from the JSON content and then perform the conversion back to MaterialX again. The glTF_graph_clear_names
method is used to clear the names.
# Remove names from JSON
json_object = json.loads(json_string)
converter.glTF_graph_clear_names(json_object)
json_string_cleared = json.dumps(json_object, indent=2)
display(Markdown(f"#### Source glTF Without Names\n```json\n{json_string_cleared}\n```"))
Source glTF Without Names¶
{
"images": [
{
"uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z/AfAAQAAf/zKSWvAAAAAElFTkSuQmCC",
"name": "KHR_texture_procedural_fallback"
}
],
"textures": [
{
"source": 0
}
],
"extensions": {
"KHR_texture_procedurals": {
"procedurals": [
{
"nodetype": "nodegraph",
"type": "color3",
"inputs": [
{
"nodetype": "input",
"type": "vector2",
"value": [
8.0,
8.0
]
},
{
"nodetype": "input",
"type": "color3",
"value": [
1.0,
0.094118,
0.031373
]
},
{
"nodetype": "input",
"type": "color3",
"value": [
0.035294,
0.090196,
0.878431
]
}
],
"outputs": [
{
"nodetype": "output",
"type": "color3",
"node": 0
}
],
"nodes": [
{
"nodetype": "mix",
"type": "color3",
"inputs": [
{
"name": "fg",
"nodetype": "input",
"type": "color3",
"input": 1
},
{
"name": "bg",
"nodetype": "input",
"type": "color3",
"input": 2
},
{
"name": "mix",
"nodetype": "input",
"type": "float",
"node": 5
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "color3"
}
]
},
{
"nodetype": "dotproduct",
"type": "float",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "vector2",
"node": 4
},
{
"name": "in2",
"nodetype": "input",
"type": "vector2",
"value": [
1.0,
1.0
]
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "float"
}
]
},
{
"nodetype": "multiply",
"type": "vector2",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "vector2",
"node": 6
},
{
"name": "in2",
"nodetype": "input",
"type": "vector2",
"input": 0
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
},
{
"nodetype": "subtract",
"type": "vector2",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "vector2",
"node": 2
},
{
"name": "in2",
"nodetype": "input",
"type": "vector2",
"value": [
0.0,
0.0
]
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
},
{
"nodetype": "floor",
"type": "vector2",
"inputs": [
{
"name": "in",
"nodetype": "input",
"type": "vector2",
"node": 3
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
},
{
"nodetype": "modulo",
"type": "float",
"inputs": [
{
"name": "in1",
"nodetype": "input",
"type": "float",
"node": 1
},
{
"name": "in2",
"nodetype": "input",
"type": "float",
"value": 2.0
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "float"
}
]
},
{
"nodetype": "texcoord",
"type": "vector2",
"inputs": [
{
"name": "index",
"nodetype": "input",
"type": "integer",
"value": 0
}
],
"outputs": [
{
"nodetype": "output",
"name": "out",
"type": "vector2"
}
]
}
],
"xpos": "-1.2309688043141684",
"ypos": "3.189328265723073"
}
]
}
},
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"extensions": {
"KHR_texture_procedurals": {
"index": 0,
"output": 0
}
}
}
}
}
],
"asset": {
"version": "2.0",
"generator": "MaterialX 1.39 / glTF 2.0 Texture Procedural Converter"
},
"extensionsUsed": [
"KHR_texture_procedurals",
"EXT_texture_procedurals_mx_1_39"
]
}
When converting to MaterialX, names are auto-matically generated for unnamed elements.
To provide more user-friendly names the category of the element is used as a prefix. e.g. the string INPUT_<number>
is used for unnamed procedural graph inputs where <number>
starts at 0 and subsequent names are generated through the MaterialX API. Note that pre-generation is required so that connection references are set to the correct name identifiers.
mxdoc_cleared = converter.gltf_string_to_materialX(json_string_cleared, stdlib)
mxdoc_cleared_string = mx.writeToXmlString(mxdoc_cleared)
display(Markdown(f"#### MaterialX With Generated Names\n```xml\n{mxdoc_cleared_string}\n```"))
INFO:glTFMtlx:> Importing 1 procedural graphs INFO:glTFMtlx:> Create new nodegraph: GRAPH_0 INFO:glTFMtlx:> Scan 1 procedural outputs INFO:glTFMtlx:> Import material: MATERIAL_SHADER_0. Shader: SHADER_0
MaterialX With Generated Names¶
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709" doc="glTF version: 2.0. glTF generator: MaterialX 1.39 / glTF 2.0 Texture Procedural Converter. ">
<nodegraph name="GRAPH_0" xpos="-1.2309688043141684" ypos="3.189328265723073">
<input name="INPUT_0" type="vector2" value="8, 8" />
<input name="INPUT_1" type="color3" value="1, 0.094118, 0.031373" />
<input name="INPUT_2" type="color3" value="0.035294, 0.090196, 0.878431" />
<mix name="NODE_0" type="color3">
<input name="fg" type="color3" interfacename="INPUT_1" />
<input name="bg" type="color3" interfacename="INPUT_2" />
<input name="mix" type="float" nodename="NODE_5" />
</mix>
<dotproduct name="NODE_1" type="float">
<input name="in1" type="vector2" nodename="NODE_4" />
<input name="in2" type="vector2" value="1, 1" />
</dotproduct>
<multiply name="NODE_2" type="vector2">
<input name="in1" type="vector2" nodename="NODE_6" />
<input name="in2" type="vector2" interfacename="INPUT_0" />
</multiply>
<subtract name="NODE_3" type="vector2">
<input name="in1" type="vector2" nodename="NODE_2" />
<input name="in2" type="vector2" value="0, 0" />
</subtract>
<floor name="NODE_4" type="vector2">
<input name="in" type="vector2" nodename="NODE_3" />
</floor>
<modulo name="NODE_5" type="float">
<input name="in1" type="float" nodename="NODE_1" />
<input name="in2" type="float" value="2" />
</modulo>
<texcoord name="NODE_6" type="vector2">
<input name="index" type="integer" value="0" />
</texcoord>
<output name="OUTPUT_0" type="color3" nodename="NODE_0" />
</nodegraph>
<gltf_pbr name="SHADER_0" type="surfaceshader">
<input name="base_color" type="color3" nodegraph="GRAPH_0" />
</gltf_pbr>
<surfacematerial name="MATERIAL_SHADER_0" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="SHADER_0" />
</surfacematerial>
</materialx>