17let MTLX_MATERIAL_PREFIX = 
'MAT_' 
   23let MTLX_DEFAULT_SHADER_NAME = 
'SHD_0' 
   29let MTLX_SHADER_PREFIX = 
'SHD_' 
   36let MTLX_INTERFACEINPUT_NAME_ATTRIBUTE = 
'interfacename';
 
   54let MTLX_GLTF_PBR_CATEGORY = 
'gltf_pbr' 
   60let MTLX_UNLIT_CATEGORY_STRING = 
'surface_unlit' 
   66let MULTI_OUTPUT_TYPE_STRING = 
'multioutput' 
   79        return (() => { 
throw new Error(
"Method not implemented. This is a required base class method."); })();
 
   88        return (() => { 
throw new Error(
"Method not implemented. This is a required base class method."); })();
 
  100        return (() => { 
throw new Error(
"Method not implemented. This is a required base class method."); })();
 
  109        return (() => { 
throw new Error(
"Method not implemented. This is a required base class method."); })();
 
  118class glTFMaterialX 
extends MxConverter {
 
  150    scalarToString(value, type) {
 
  151        let returnvalue = 
null;
 
  153        const supportedTypes = [
'string', 
'integer', 
'matrix33', 
'matrix44', 
'vector2', 
'vector3', 
'vector4', 
'float', 
'color3', 
'color4'];
 
  154        const arrayTypes = [
'matrix33', 
'matrix44', 
'vector2', 
'vector3', 
'vector4', 
'color3', 
'color4'];
 
  156        if (supportedTypes.includes(type)) {
 
  157            if (arrayTypes.includes(type)) {
 
  158                returnvalue = value.map(x => String(x)).join(
', ');
 
  160                returnvalue = String(value);
 
  174    stringToScalar(value, type) {
 
  175        let returnvalue = value;
 
  177        const scalarTypes = [
'integer', 
'matrix33', 
'matrix44', 
'vector2', 
'vector3', 
'vector4', 
'float', 
'color3', 
'color4'];
 
  179        if (scalarTypes.includes(type)) {
 
  180            const splitvalue = value.split(
',');
 
  182            if (splitvalue.length > 1) {
 
  183                returnvalue = splitvalue.map(x => parseFloat(x));
 
  185                if (type === 
'integer') {
 
  186                    returnvalue = parseInt(value, 10);
 
  188                    returnvalue = parseFloat(value);
 
  206    initialize_gtlf_texture(texture, name, uri, images) {
 
  218        texture.source = images.length - 1;
 
  228    getGLTFTextureUri(texture, images) {
 
  230        if (texture && 
'source' in texture) {
 
  231            const source = texture.source;
 
  232            if (source < images.length) {
 
  233                const image = images[source];
 
  234                if (
'uri' in image) {
 
  249    haveExtensions(glTFDoc) {
 
  251        let extensionsUsed = glTFDoc.extensionsUsed || 
null;
 
  252        if (extensionsUsed === 
null) {
 
  253            return [
null, 
'No extension used'];
 
  255        let found = [
false, 
false];
 
  256        for (let ext of extensionsUsed) {
 
  257            if (ext === 
'KHR_texture_procedurals') {
 
  260            if (ext === 
'EXT_texture_procedurals_mx_1_39') {
 
  265            return [
null, 
'Missing KHR_texture_procedurals extension'];
 
  267            return [
null, 
'Missing EXT_texture_procedurals_mx_1_39 extension'];
 
  279    addInputsFromNodeDef(node, nodeDef) {
 
  281            for (let nodeDefInput of nodeDef.getActiveInputs()) {
 
  282                let inputName = nodeDefInput.getName();
 
  283                let nodeInput = node.getInput(inputName);
 
  286                    nodeInput = node.addInput(inputName, nodeDefInput.getType());
 
  287                    if (nodeDefInput.hasValueString()) {
 
  288                        nodeInput.setValueString(nodeDefInput.getValueString(), nodeDefInput.getType());
 
  304            return [
null, 
'MaterialX runtime not passed in'];
 
  306        let glTFDoc = JSON.parse(glTFDocString);
 
  308        let extensionCheck = this.haveExtensions(glTFDoc);
 
  309        if (extensionCheck[0] === 
null) {
 
  310            return extensionCheck;
 
  316        this.importGraphs(
doc, glTFDoc);
 
  318        let global_extensions = glTFDoc.extensions || 
null;
 
  319        let procedurals = 
null;
 
  320        if (global_extensions && global_extensions.KHR_texture_procedurals) {
 
  321            procedurals = global_extensions.KHR_texture_procedurals.procedurals || 
null;
 
  322            console.log(
'Imported all procedurals:', procedurals);
 
  326        let shaderName = MTLX_DEFAULT_SHADER_NAME;
 
  328        let materialIndex = 1;
 
  329        let glTFmaterials = glTFDoc.materials || 
null;
 
  334            inputMaps[MTLX_GLTF_PBR_CATEGORY] = [
 
  335                [
'base_color', 
'baseColorTexture', 
'pbrMetallicRoughness'],
 
  336                [
'metallic', 
'metallicRoughnessTexture', 
'pbrMetallicRoughness'],
 
  337                [
'roughness', 
'metallicRoughnessTexture', 
'pbrMetallicRoughness'],
 
  338                [
'occlusion', 
'occlusionTexture', 
''],
 
  339                [
'normal', 
'normalTexture', 
''],
 
  340                [
'emissive', 
'emissiveTexture', 
'']
 
  342            inputMaps[MTLX_UNLIT_CATEGORY_STRING] = [[
'emission_color', 
'baseColorTexture', 
'pbrMetallicRoughness']]
 
  344            for (let glTFmaterial of glTFmaterials) {
 
  347                let mtlxShaderName = glTFmaterial.name;
 
  348                let mtlxMaterialName = 
'';
 
  349                if (mtlxShaderName.length == 0) {
 
  350                    mtlxShaderName = shaderName + String(materialIndex);
 
  352                    mtlxMaterialName = materialName + String(materialIndex);
 
  355                    mtlxMaterialName = 
'MAT_' + mtlxShaderName;
 
  357                mtlxShaderName = 
doc.createValidChildName(mtlxShaderName);
 
  358                mtlxMaterialName = 
doc.createValidChildName(mtlxMaterialName);
 
  362                let use_unlit = 
false;
 
  363                let extensions = glTFmaterial.extensions || 
null;
 
  365                    let KHR_materials_unlit = extensions.KHR_materials_unlit || 
null;
 
  366                    if (KHR_materials_unlit) {
 
  371                let shaderCategory = MTLX_GLTF_PBR_CATEGORY;
 
  372                let nodedefString = 
'ND_gltf_pbr_surfaceshader';
 
  374                    shaderCategory = MTLX_UNLIT_CATEGORY_STRING;
 
  375                    nodedefString = 
'ND_surface_unlit';
 
  377                let comment = 
doc.addChildOfCategory(
'comment')
 
  378                comment.setDocString(
' Generated shader: ' + mtlxShaderName + 
' ')
 
  379                let shaderNode = 
doc.addNode(shaderCategory, mtlxShaderName, 
ne_mx.SURFACE_SHADER_TYPE_STRING);
 
  381                shaderNode.setAttribute(
ne_mx.InterfaceElement.NODE_DEF_ATTRIBUTE, nodedefString)
 
  383                    let nodedef = 
stdlib.getNodeDef(nodedefString);
 
  385                        this.addInputsFromNodeDef(shaderNode, nodedef);
 
  389                let defaultGraphName = 
'nodegraph';
 
  391                    let currentMap = inputMaps[shaderCategory];
 
  392                    for (let map of currentMap) {
 
  393                        let destInput = map[0];
 
  394                        let sourceTexture = map[1];
 
  395                        let sourceParent = map[2];
 
  396                        if (sourceParent.length > 0) {
 
  397                            if (sourceParent == 
'pbrMetallicRoughness') {
 
  398                                let newSource = glTFmaterial.pbrMetallicRoughness[sourceTexture];
 
  400                                sourceTexture = newSource;
 
  404                            sourceTexture = glTFmaterial[sourceTexture];
 
  407                        let baseColorTexture = sourceTexture;
 
  409                        if (baseColorTexture) {
 
  411                            if (baseColorTexture) {
 
  412                                let extensions = baseColorTexture.extensions || 
null;
 
  415                                    let KHR_texture_procedurals = extensions.KHR_texture_procedurals || 
null;
 
  416                                    if (KHR_texture_procedurals) {
 
  417                                        let pindex = KHR_texture_procedurals.index;
 
  418                                        let output = KHR_texture_procedurals.output;
 
  420                                        if (pindex != 
null) {
 
  421                                            if (pindex < procedurals.length) {
 
  423                                                let proc = procedurals[pindex];
 
  425                                                    let nodegraphName = defaultGraphName;
 
  427                                                        nodegraphName = proc.name;
 
  429                                                    let graphOutputs = proc.outputs || 
null;
 
  430                                                    if (graphOutputs && graphOutputs.length > 0) {
 
  431                                                        let proc_output = graphOutputs[0];
 
  432                                                        if (output != 
null) {
 
  433                                                            proc_output = graphOutputs[output];
 
  436                                                            let input = shaderNode.getInput(destInput);
 
  438                                                                input = shaderNode.addInput(destInput, proc_output.type);
 
  441                                                                input.setNodeGraphString(nodegraphName);
 
  442                                                                input.setAttribute(
'output', proc_output.name);
 
  456                comment = 
doc.addChildOfCategory(
'comment')
 
  457                comment.setDocString(
' Generated material: ' + mtlxMaterialName + 
' ')
 
  458                let materialNode = 
doc.addNode(
ne_mx.SURFACE_MATERIAL_NODE_STRING, mtlxMaterialName, 
ne_mx.MATERIAL_TYPE_STRING)
 
  459                let shaderInput = materialNode.addInput(
ne_mx.SURFACE_SHADER_TYPE_STRING, 
ne_mx.SURFACE_SHADER_TYPE_STRING)
 
  463                console.log(
'>> Import material:', materialNode.getName(), 
'Shader:', shaderNode.getName());
 
  468        let asset = glTFDoc.asset || 
null;
 
  472            let version = asset.version || 
null;
 
  474                let comment = 
doc.addChildOfCategory(
'comment');
 
  475                docDoc += 
'glTF version: ' + version + 
'. ';
 
  476                comment.setDocString(
'glTF version: ' + version);
 
  478            let generator = asset.generator || 
null;
 
  480                let comment = 
doc.addChildOfCategory(
'comment');
 
  481                docDoc += 
'glTF generator: ' + generator + 
'. ';
 
  482                comment.setDocString(
'glTF generator: ' + generator);
 
  484            let copyRight = asset.copyright || 
null;
 
  486                let comment = 
doc.addChildOfCategory(
'comment');
 
  487                docDoc += 
'Copyright: ' + copyRight + 
'. ';
 
  488                comment.setDocString(
'Copyright: ' + copyRight);
 
  491            if (docDoc.length > 0) {
 
  492                doc.setAttribute(
'doc', docDoc);
 
  499        var valid = 
doc.validate(errors);
 
  502            errorString = errors.message
 
  505        let docString = 
ne_mx.writeToXmlString(
doc);
 
  507        return [docString, errorString];
 
  517    importGraphs(
doc, gltfDoc) {
 
  518        let root_mtlx = 
null;
 
  521        let extensions = gltfDoc.extensions || 
null;
 
  522        let procedurals = 
null;
 
  524            procedurals = extensions.KHR_texture_procedurals.procedurals || 
null;
 
  527        if (procedurals === 
null) {
 
  528            console.log(
'> Error - no procedurals array found');
 
  536        for (let proc of procedurals) {
 
  537            console.log(`> Scan procedural ${procindex} of ${procedurals.length} :`);
 
  538            if (!proc.nodetype) {
 
  539                console.log(
'>> Warning: No nodetype found in procedural. SKipping');
 
  543            if (proc.nodetype !== 
'nodegraph') {
 
  544                console.log(
'>> Skip unsupported rocedural nodetype:', proc.nodetype);
 
  548            let graphname = 
'graph_' + graph_index;
 
  550                graphname = proc.name;
 
  554            let mtlxgraph = 
doc.addNodeGraph(graphname);
 
  555            root_mtlx = mtlxgraph;
 
  558            let output_index = 0;
 
  561            let inputs = proc.inputs || [];
 
  562            let outputs = proc.outputs || [];
 
  563            let nodes = proc.nodes || [];
 
  566            for (let input of inputs) {
 
  567                let inputname = input.name || 
'input_' + input_index;
 
  572                let inputtype = input.type || 
null;
 
  574                    console.log(
'>> Error - Input type not found for graph input:', inputname);
 
  578                let mtlxinput = mtlxgraph.addInput(inputname, inputtype);
 
  580                if (input.colorspace) {
 
  581                    mtlxinput.setAttribute(
'colorspace', input.colorspace);
 
  584                if (inputtype === 
'filename') {
 
  585                    let textureIndex = input.texture || 
null;
 
  586                    if (textureIndex !== 
null) {
 
  587                        let gltftextures = gltfDoc.textures || 
null;
 
  588                        let gltfimages = gltfDoc.images || 
null;
 
  589                        if (gltftextures && gltfimages) {
 
  590                            let gltftexture = gltftextures[textureIndex] || 
null;
 
  592                                let uri = this.getGLTFTextureUri(gltftexture, gltfimages);
 
  593                                mtlxinput.setValueString(uri, inputtype);
 
  599                let inputvalue = input.value || 
null;
 
  600                if (inputvalue !== 
null) {
 
  601                    let mtlxvalue = this.scalarToString(inputvalue, inputtype);
 
  602                    if (mtlxvalue !== 
null) {
 
  603                        mtlxinput.setValueString(mtlxvalue, inputtype);
 
  605                        console.log(
'>> Error - Unsupported handle input type:', inputtype, 
'. Performing straight assignment.');
 
  606                        mtlxinput.setValueString(String(inputvalue, inputtype));
 
  609                    console.log(
'>> Invalid usage of top level graph connections:', inputname);
 
  610                    if (input.procedural) {
 
  617            for (let output of outputs) {
 
  618                let outputname = output.name || 
'output_' + output_index;
 
  619                let outputtype = output.type || 
null;
 
  620                let mtlxgraph_output = mtlxgraph.addOutput(outputname, outputtype);
 
  622                let connectable = 
null;
 
  623                if (output.input !== undefined) {
 
  624                    connectable = inputs[output.input];
 
  626                        mtlxgraph_output.setAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE, connectable.name);
 
  628                        console.log(
'>> Error - Input not found:', output.input, inputs);
 
  637                else if (output.node !== undefined) {
 
  638                    connectable = nodes[output.node];
 
  642                        if (output.output !== undefined) {
 
  643                            mtlxgraph_output.setAttribute(
'output', output.output);
 
  647                        console.log(
'>> Error - Output node not found:', output.node, nodes);
 
  652            for (let node of nodes) {
 
  653                let nodename = node.name || 
'node_' + node_index;
 
  654                let nodetype = node.nodetype || 
null;
 
  655                let outputType = node.type || 
null;
 
  656                let node_outputs = node.outputs || [];
 
  657                if (node_outputs.length > 1) {
 
  658                    outputType = MULTI_OUTPUT_TYPE_STRING;
 
  660                let mtlxnode = mtlxgraph.addChildOfCategory(nodetype);
 
  661                mtlxnode.setName(nodename);
 
  663                    mtlxnode.setType(outputType);
 
  665                    console.log(
'>> Error - no output type for node:', nodename);
 
  669                let node_inputs = node.inputs || [];
 
  670                for (let input of node_inputs) {
 
  671                    let inputname = input.name || 
'input_' + input_index;
 
  672                    let inputtype = input.type || 
null;
 
  673                    let mtlxinput = mtlxnode.addInput(inputname, inputtype);
 
  675                    if (input.colorspace) {
 
  676                        mtlxinput.setAttribute(
'colorspace', input.colorspace);
 
  679                    if (inputtype === 
'filename') {
 
  680                        let textureIndex = input.texture || 
null;
 
  681                        if (textureIndex !== 
null) {
 
  682                            let gltftextures = gltfDoc.textures || 
null;
 
  683                            let gltfimages = gltfDoc.images || 
null;
 
  684                            if (gltftextures && gltfimages) {
 
  685                                let gltftexture = gltftextures[textureIndex] || 
null;
 
  687                                    let uri = this.getGLTFTextureUri(gltftexture, gltfimages);
 
  688                                    mtlxinput.setValueString(uri, inputtype);
 
  694                    let inputvalue = input.value || 
null;
 
  695                    if (inputvalue !== 
null) {
 
  696                        let mtlxvalue = this.scalarToString(inputvalue, inputtype);
 
  697                        if (mtlxvalue !== 
null) {
 
  698                            mtlxinput.setValueString(mtlxvalue, inputtype);
 
  700                            console.log(
'>> Error - Unsupported input type:', inputtype, 
'. Performing straight assignment.');
 
  701                            mtlxinput.setValueString(String(inputvalue), inputtype);
 
  704                        let connectable = 
null;
 
  705                        if (input.input !== undefined) {
 
  706                            connectable = inputs[input.input];
 
  707                            mtlxinput.setAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE, connectable.name);
 
  708                        } 
else if (input.output !== undefined) {
 
  709                            if (input.node !== undefined) {
 
  710                                mtlxinput.setAttribute(
'output', input.output);
 
  712                                connectable = outputs[input.output];
 
  713                                mtlxinput.setAttribute(
'output', connectable.name);
 
  716                        if (input.node !== undefined) {
 
  717                            connectable = nodes[input.node];
 
  724                for (let output of node_outputs) {
 
  725                    let outputname = output.name || 
'output_' + output_index;
 
  726                    let outputtype = output.type || 
null;
 
  727                    let mtlxoutput = mtlxnode.addOutput(outputname, outputtype);
 
  729                    let connectable = 
null;
 
  730                    if (output.input !== undefined) {
 
  731                        connectable = inputs[output.input];
 
  732                        mtlxoutput.setAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE, connectable.name);
 
  733                    } 
else if (output.output !== undefined) {
 
  734                        connectable = outputs[output.output];
 
  735                        mtlxoutput.setAttribute(
'output', connectable.name);
 
  737                    if (output.node !== undefined) {
 
  738                        connectable = nodes[output.node];
 
  758        let no_result = [
null, 
null, 
null]
 
  761            console.log(
'MaterialX runtime not passed in')
 
  765        let graphOutputs = 
graph.getOutputs();
 
  766        if (graphOutputs.length == 0) {
 
  767            console.log(
'No graph outputs found on graph: ', 
graph.getNamePath())
 
  772        const usePaths = 
false;
 
  775        const fallback = 
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z/AfAAQAAf/zKSWvAAAAAElFTkSuQmCC';
 
  776        let fallbackIndex = -1;
 
  778        let imageArray = json[
'images'] || [];
 
  779        if (!json[
'images']) json[
'images'] = imageArray;
 
  781        for (let i = 0; i < imageArray.length; i++) {
 
  782            const image = imageArray[i];
 
  783            if (image[
'uri'] === fallback) {
 
  789        let fallbackTextureIndex = -1;
 
  790        if (fallbackIndex === -1) {
 
  793                'name': 
'KHR_texture_procedural_fallback' 
  795            imageArray.push(image);
 
  796            fallbackIndex = imageArray.length - 1;
 
  799        let textureArray = json[
'textures'] || [];
 
  800        if (!json[
'textures']) json[
'textures'] = textureArray;
 
  802        for (let i = 0; i < textureArray.length; i++) {
 
  803            const texture = textureArray[i];
 
  804            if (texture[
'source'] === fallbackIndex) {
 
  805                fallbackTextureIndex = i;
 
  810        if (fallbackTextureIndex === -1) {
 
  811            textureArray.push({ 
'source': fallbackIndex });
 
  812            fallbackTextureIndex = textureArray.length - 1;
 
  815        const skipAttr = [
'uiname', 
'xpos', 
'ypos'];
 
  816        const procDictNodes = {};
 
  817        const procDictInputs = {};
 
  818        const procDictOutputs = {};
 
  820        let extensions = json[
'extensions'] || {};
 
  821        if (!json[
'extensions']) json[
'extensions'] = extensions;
 
  823        let KHR_texture_procedurals = extensions[
'KHR_texture_procedurals'] || {};
 
  824        if (!extensions[
'KHR_texture_procedurals']) extensions[
'KHR_texture_procedurals'] = KHR_texture_procedurals;
 
  826        if (!KHR_texture_procedurals[
'procedurals']) {
 
  827            KHR_texture_procedurals[
'procedurals'] = [];
 
  830        const procs = KHR_texture_procedurals[
'procedurals'];
 
  832            'name': usePaths ? 
graph.getNamePath() : 
graph.getName(),
 
  833            'nodetype': 
graph.getCategory()
 
  836        nodegraph[
'type'] = graphOutputs.length > 1 ? 
'multioutput' : graphOutputs[0].getType();
 
  837        const nodegraphInputs = nodegraph[
'inputs'] = [];
 
  838        const nodegraphOutputs = nodegraph[
'outputs'] = [];
 
  839        const nodegraphNodes = nodegraph[
'nodes'] = [];
 
  840        procs.push(nodegraph);
 
  842        const metadata = [
'colorspace', 
'unit', 
'unittype', 
'uiname', 
'uimin', 
'uimax', 
'uifolder', 
'doc'];
 
  844        graph.getNodes().forEach((node) => {
 
  846                'name': usePaths ? node.getNamePath() : node.getName()
 
  848            nodegraphNodes.push(jsonNode);
 
  849            procDictNodes[node.getNamePath()] = nodegraphNodes.length - 1;
 
  852        graph.getInputs().forEach((input) => {
 
  854                'name': usePaths ? input.getNamePath() : input.getName(),
 
  855                'nodetype': input.getCategory()
 
  858            metadata.forEach((meta) => {
 
  859                if (input.getAttribute(meta)) {
 
  860                    jsonNode[meta] = input.getAttribute(meta);
 
  864            if (input.getValue() !== 
null) {
 
  865                const inputType = input.getAttribute(
ne_mx.TypedElement.TYPE_ATTRIBUTE);
 
  866                jsonNode[
'type'] = inputType;
 
  867                if (inputType === 
ne_mx.FILENAME_TYPE_STRING) {
 
  870                    const filename = input.getResolvedValueString();
 
  872                    this.initialize_gtlf_texture(texture, input.getNamePath(), filename, imageArray);
 
  873                    textureArray.push(texture);
 
  874                    jsonNode[
'texture'] = textureArray.length - 1;
 
  876                    let value = input.getValueString();
 
  877                    value = this.stringToScalar(value, inputType);
 
  878                    jsonNode[
'value'] = value;
 
  880                nodegraphInputs.push(jsonNode);
 
  881                procDictInputs[input.getNamePath()] = nodegraphInputs.length - 1;
 
  890                    console.error(
'Error: no value or invalid connection specified for input. Input skipped:', input.getNamePath());
 
  895        graphOutputs.forEach((output) => {
 
  897                'name': usePaths ? output.getNamePath() : output.getName()
 
  899            nodegraphOutputs.push(jsonNode);
 
  900            procDictOutputs[output.getNamePath()] = nodegraphOutputs.length - 1;
 
  902            jsonNode[
'nodetype'] = output.getCategory();
 
  903            jsonNode[
'type'] = output.getType();
 
  905            let connection = output.getAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE);
 
  906            if (connection.length == 0)
 
  909            const connectionNode = 
graph.getChild(connection);
 
  910            if (connectionNode) {
 
  911                let connectionPath = connectionNode.getNamePath()
 
  913                    jsonNode[
'debug_connection_path'] = connectionPath;
 
  915                if (procDictInputs[connectionPath] != 
null) {
 
  916                    jsonNode[
'input'] = procDictInputs[connectionPath];
 
  917                } 
else if (procDictNodes[connectionPath] != 
null) {
 
  918                    jsonNode[
'node'] = procDictNodes[connectionPath];
 
  920                    console.error(
'Invalid output connection to:', connectionPath);
 
  923                const outputString = output.getAttribute(
'output');
 
  924                if (outputString.length > 0) {
 
  925                    jsonNode[
'output'] = outputString;
 
  930        graph.getNodes().forEach((node) => {
 
  932            const index = procDictNodes[node.getNamePath()];
 
  933            jsonNode = nodegraphNodes[index];
 
  934            jsonNode[
'nodetype'] = node.getCategory();
 
  935            const nodedef = node.getNodeDef();
 
  937            if (debug && nodedef && nodedef.getNodeGroup().length) {
 
  938                jsonNode[
'nodegroup'] = nodedef.getNodeGroup();
 
  941            node.getAttributeNames().forEach((attrName) => {
 
  942                if (!skipAttr.includes(attrName)) {
 
  943                    jsonNode[attrName] = node.getAttribute(attrName);
 
  948            node.getInputs().forEach((input) => {
 
  950                    'name': input.getName(),
 
  954                metadata.forEach((meta) => {
 
  955                    if (input.getAttribute(meta)) {
 
  956                        inputItem[meta] = input.getAttribute(meta);
 
  960                const inputType = input.getAttribute(
ne_mx.TypedElement.TYPE_ATTRIBUTE);
 
  961                inputItem[
'type'] = inputType;
 
  963                if (input.getValue() !== 
null) {
 
  964                    if (inputType === 
ne_mx.FILENAME_TYPE_STRING) {
 
  967                        const filename = input.getResolvedValueString();
 
  968                        this.initialize_gtlf_texture(texture, input.getNamePath(), filename, imageArray);
 
  969                        textureArray.push(texture);
 
  970                        inputItem[
'texture'] = textureArray.length - 1;
 
  972                        let value = input.getValueString();
 
  973                        value = this.stringToScalar(value, inputType);
 
  974                        inputItem[
'value'] = value;
 
  977                    let isInterface = 
true;
 
  978                    let connection = input.getAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE);
 
  979                    if (!connection.length) {
 
  984                    if (connection.length > 0) {
 
  985                        const connectionNode = 
graph.getChild(connection);
 
  986                        if (connectionNode) {
 
  987                            const inputType = input.getAttribute(
ne_mx.TypedElement.TYPE_ATTRIBUTE);
 
  988                            inputItem[
'type'] = inputType;
 
  989                            let connectionPath = connectionNode.getNamePath();
 
  991                                inputItem[
'debug_connection_path'] = connectionPath;
 
  994                            if (isInterface && procDictInputs[connectionPath] != 
null) {
 
  995                                inputItem[
'input'] = procDictInputs[connectionPath];
 
  996                            } 
else if (procDictNodes[connectionPath] != 
null) {
 
  997                                inputItem[
'node'] = procDictNodes[connectionPath];
 
 1000                            const outputString = input.getAttribute(
'output');
 
 1001                            if (outputString.length > 0) {
 
 1002                                const connectedNodeOutputs = connectionNode.getOutputs();
 
 1003                                for (let i = 0; i < connectedNodeOutputs.length; i++) {
 
 1004                                    if (connectedNodeOutputs[i].getName() === outputString) {
 
 1005                                        inputItem[
'output'] = i;
 
 1011                            console.error(
'Error: Invalid input connection to:', connection, 
' from: input:', input.getNamePath(), 
' node:', node.getNamePath());
 
 1016                        console.error(
'Error: Invalid input connection to:', connection, 
' from: input:', input.getNamePath(), 
' node:', node.getNamePath());
 
 1020                inputs.push(inputItem);
 
 1023            if (inputs.length > 0) {
 
 1024                jsonNode[
'inputs'] = inputs;
 
 1028            node.getOutputs().forEach((output) => {
 
 1029                const outputItem = {
 
 1030                    'nodetype': 
'output',
 
 1031                    'name': output.getName(),
 
 1032                    'type': output.getType()
 
 1034                outputs.push(outputItem);
 
 1038                nodedef.getOutputs().forEach((output) => {
 
 1040                    outputs.forEach((outputItem) => {
 
 1041                        if (outputItem[
'name'] === output.getName()) {
 
 1047                        const outputItem = {
 
 1048                            'nodetype': 
'output',
 
 1049                            'name': output.getName(),
 
 1050                            'type': output.getType()
 
 1052                        outputs.push(outputItem);
 
 1056                console.error(
'Missing nodedef for node:', node.getNamePath());
 
 1059            if (outputs.length > 0) {
 
 1060                jsonNode[
'outputs'] = outputs;
 
 1064        return [procs, procDictOutputs, procDictNodes, fallbackTextureIndex];
 
 1073    export(
ne_mx, glTFDoc) {
 
 1076            status = 
'MaterialX runtime not passed in' 
 1077            return [
null, status];
 
 1082            status = 
'Invalid document to convert';
 
 1083            return [
null, status];
 
 1087        let mxMaterials = glTFDoc.getMaterialNodes();
 
 1088        if (mxMaterials.length == 0) {
 
 1096            "generator": 
"MaterialX 1.39 to glTF 2.0 procedural textures converter",
 
 1097            "copyright": 
"Copyright (c) 2024, Bernard Kwok" 
 1101        inputMaps[MTLX_GLTF_PBR_CATEGORY] = [
 
 1102            [
'base_color', 
'baseColorTexture', 
'pbrMetallicRoughness'],
 
 1103            [
'metallic', 
'metallicRoughnessTexture', 
'pbrMetallicRoughness'],
 
 1104            [
'roughness', 
'metallicRoughnessTexture', 
'pbrMetallicRoughness'],
 
 1105            [
'occlusion', 
'occlusionTexture', 
''],
 
 1106            [
'normal', 
'normalTexture', 
''],
 
 1107            [
'emissive', 
'emissiveTexture', 
'']
 
 1109        inputMaps[MTLX_UNLIT_CATEGORY_STRING] = [[
'emission_color', 
'baseColorTexture', 
'pbrMetallicRoughness']]
 
 1112        let fallbackTextureIndex = -1;
 
 1114        let exportGraphNames = [];
 
 1116        for (let mxMaterial of mxMaterials) {
 
 1117            let mxshaders = 
ne_mx.getShaderNodes(mxMaterial);
 
 1118            for (let shaderNode of mxshaders) {
 
 1119                let category = shaderNode.getCategory();
 
 1120                let path = shaderNode.getNamePath()
 
 1121                let isUnlit = (category == MTLX_UNLIT_CATEGORY_STRING);
 
 1122                let isPBR = (category == MTLX_GLTF_PBR_CATEGORY);
 
 1123                if ((isPBR || isUnlit) && pbrNodes[path] == 
null) {
 
 1124                    console.log(
'> Convert shader to glTF:', shaderNode.getNamePath(), 
'Category:', category);
 
 1126                    pbrNodes[path] = shaderNode;
 
 1130                        let base_color_input = 
null;
 
 1131                        let base_color_output = 
'';
 
 1132                        let inputPairs = inputMaps[category];
 
 1133                        for (let inputPair of inputPairs) {
 
 1135                            base_color_input = shaderNode.getInput(inputPair[0]);
 
 1136                            base_color_output = inputPair[1];
 
 1138                            if (!base_color_input) {
 
 1142                            let nodeGraphName = base_color_input.getNodeGraphString();
 
 1143                            if (nodeGraphName.length == 0) {
 
 1147                            let nodeGraphOutput = base_color_input.getOutputString();
 
 1148                            material[
'name'] = path;
 
 1150                            let parent = material;
 
 1151                            if (inputPair[2].length > 0) {
 
 1152                                if (!material[inputPair[2]]) {
 
 1153                                    material[inputPair[2]] = {};
 
 1155                                parent = material[inputPair[2]]
 
 1159                            let graphIndex = -1;
 
 1160                            let outputIndex = -1;
 
 1163                                for (let proc of procs) {
 
 1164                                    if (proc[
'name'] == nodeGraphName) {
 
 1166                                        if (nodeGraphOutput.length > 0) {
 
 1167                                            let outputs = proc[
'outputs'];
 
 1169                                            for (let output of outputs) {
 
 1170                                                if (output[
'name'] == nodeGraphOutput) {
 
 1183                            if (graphIndex >= 0) {
 
 1184                                let baseColorTexture = parent[base_color_output] = {}
 
 1185                                baseColorTexture[
'index'] = fallbackTextureIndex;
 
 1186                                let ext = baseColorTexture[
'extensions'] = {}
 
 1189                                        ext[
'KHR_materials_unlit'] = {};
 
 1191                                    let lookup = ext[
'KHR_texture_procedurals'] = {}
 
 1192                                    lookup[
'index'] = graphIndex;
 
 1193                                    if (outputIndex >= 0) {
 
 1194                                        lookup[
'output'] = outputIndex;
 
 1199                                let 
graph = glTFDoc.getNodeGraph(nodeGraphName);
 
 1200                                exportGraphNames.push(nodeGraphName);
 
 1202                                let gltfInfo = this.exportGraph(
ne_mx, 
graph, json, materials)
 
 1205                                let outputNodes = gltfInfo[1];
 
 1206                                let proceduralNodes = gltfInfo[2];
 
 1207                                fallbackTextureIndex = gltfInfo[3];
 
 1213                                let baseColorTexture = parent[base_color_output] = {}
 
 1214                                baseColorTexture[
'index'] = fallbackTextureIndex;
 
 1215                                let ext = baseColorTexture[
'extensions'] = {}
 
 1218                                        ext[
'KHR_materials_unlit'] = {};
 
 1220                                    let lookup = ext[
'KHR_texture_procedurals'] = {}
 
 1221                                    lookup[
'index'] = procs.length - 1;
 
 1222                                    let outputIndex = -1;
 
 1224                                    if (nodeGraphOutput.length > 0) {
 
 1225                                        for (let outputNodeName in outputNodes) {
 
 1226                                            let nodeGraphOutputPath = (nodeGraphName + 
'/' + nodeGraphOutput);
 
 1227                                            if (outputNodeName == nodeGraphOutputPath) {
 
 1228                                                outputIndex = outputNodes[outputNodeName];
 
 1232                                        if (outputIndex == -1) {
 
 1233                                            console.error(
'Failed to find output:', nodeGraphOutput, 
' in:', outputNodes)
 
 1235                                        lookup[
'output'] = outputIndex;
 
 1239                                        lookup[
'output'] = 0;
 
 1246                    if (material[
'name']) {
 
 1247                        materials.push(material);
 
 1253        let unconnectedGraphs = [];
 
 1254        for (let ng of glTFDoc.getNodeGraphs()) {
 
 1255            let ng_name = ng.getName();
 
 1256            if (ng.getAttribute(
'nodedef') || ng.hasSourceUri()) {
 
 1260            if (!exportGraphNames.includes(ng_name)) {
 
 1261                unconnectedGraphs.push(ng_name);
 
 1263                let gltfInfo = this.exportGraph(
ne_mx, ng, json, materials)
 
 1265                let outputNodes = gltfInfo[1];
 
 1266                let proceduralNodes = gltfInfo[2];
 
 1267                fallbackTextureIndex = gltfInfo[3];
 
 1271        if (materials.length > 0) {
 
 1272            json[
'materials'] = materials;
 
 1273            if (unconnectedGraphs.length > 0) {
 
 1274                status = 
'Exported unconnected graphs: ' + unconnectedGraphs.join(
', ');
 
 1278            if (unconnectedGraphs.length > 0) {
 
 1279                status = 
'Exported unconnected graphs: ' + unconnectedGraphs.join(
', ');
 
 1282                status = 
'No appropriate glTF shader graphs found';
 
 1288        if (procs.length > 0) {
 
 1289            json[
'asset'] = json_asset;
 
 1290            json[
'extensionsUsed'] = [];
 
 1291            json[
'extensionsUsed'].push(
'KHR_texture_procedurals');
 
 1292            json[
'extensionsUsed'].push(
'EXT_texture_procedurals_mx_1_39');
 
 1296        let jsonString = json ? JSON.stringify(json, 
null, 2) : 
'';
 
 1297        if (jsonString == 
'{}') {
 
 1301        return [jsonString, status];
 
let MTLX_NODEGRAPH_NAME_ATTRIBUTE
MTLX_NODEGRAPH_NAME_ATTRIBUTE.
let MTLX_NODE_NAME_ATTRIBUTE
MTLX_NODE_NAME_ATTRIBUTE.
let MTLX_DEFAULT_MATERIAL_NAME
jsMaterialXglTF