143 @class GLTF2MtlxReader
144 @brief Class to read glTF and convert to MaterialX.
146 @brief Log string for storing conversion status and error messages.
147 @details This string accumulates log entries during the glTF to MaterialX conversion process.
148 It can be cleared with clearLog() and retrieved with getLog().
150 @brief Options for the glTF to MaterialX conversion process.
151 @details This is an instance of GLTF2MtlxOptions that holds various options for controlling the conversion process, such as whether to add all inputs from node definitions, whether to create material assignments, and whether to print debug output.
154 Class to read glTF and convert to MaterialX.
160 _options = GLTF2MtlxOptions()
164 @brief Clear the log string.
170 @brief Return the log string.
171 @return The log string.
175 def log(self, string):
177 @brief Add a string to the log.
178 @param string The string to add to the log.
180 self._log += string +
'\n'
182 def setOptions(self, options):
184 @brief Set the options for the reader.
185 @param options The options to set.
187 self._options = options
189 def getOptions(self) -> GLTF2MtlxOptions:
191 @brief Get the options for the reader.
196 def addNodeDefOutputs(self, mx_node):
200 if mx_node.getType() == MULTI_OUTPUT_TYPE_STRING:
201 mx_node_def = mx_node.getNodeDef()
203 for mx_output
in mx_node_def.getActiveOutputs():
204 mx_output_name = mx_output.getName()
205 if not mx_node.getOutput(mx_output_name):
206 mx_output_type = mx_output.getType()
207 mx_node.addOutput(mx_output_name, mx_output_type)
209 def addMtlxImage(self, materials, nodeName, fileName, nodeCategory, nodeDefId, nodeType, colorspace='') -> mx.Node:
211 Create a MaterialX image lookup.
212 @param materials MaterialX document to add the image node to.
213 @param nodeName Name of the image node.
214 @param fileName File name of the image.
215 @param nodeCategory Category of the image node.
216 @param nodeDefId Node definition id of the image node.
217 @param nodeType Type of the image node.
218 @param colorspace Color space of the image node. Default is empty string.
219 @return The created image node.
221 nodeName = materials.createValidChildName(nodeName)
222 imageNode = materials.addNode(nodeCategory, nodeName, nodeType)
224 if not imageNode.getNodeDef():
225 self.log(
'Failed to create image node. Category,name,type: %s %s %s' % (nodeCategory, nodeName, nodeType))
229 imageNode.setAttribute(mx.InterfaceElement.NODE_DEF_ATTRIBUTE, nodeDefId)
231 fileInput = imageNode.addInputFromNodeDef(mx.Implementation.FILE_ATTRIBUTE)
233 fileInput.setValue(fileName, mx.FILENAME_TYPE_STRING)
236 colorspaceattr = MTLX_COLOR_SPACE_ATTRIBUTE
237 fileInput.setAttribute(colorspaceattr, colorspace)
239 self.log(
'-- failed to create file input for name: %s' % fileName)
241 self.addNodeDefOutputs(imageNode)
245 def addMTLXTexCoordNode(self, image, uvindex) -> mx.Node:
247 @brief Create a MaterialX texture coordinate lookup
248 @param image The image node to connect the texture coordinate node to.
249 @param uvindex The uv index to use for the texture coordinate lookup.
250 @return The created texture coordinate node.
252 parent = image.getParent()
253 if not parent.isA(mx.GraphElement):
259 texcoordName = parent.createValidChildName(
'texcoord')
260 texcoordNode = parent.addNode(
'texcoord', texcoordName,
'vector2')
264 uvIndexInput = texcoordNode.addInputFromNodeDef(
'index')
266 uvIndexInput.setValue(uvindex)
269 texcoordInput = image.addInputFromNodeDef(
'texcoord')
271 texcoordInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, texcoordNode.getName())
275 def getGLTFTextureUri(self, texture, images) -> str:
277 @brief Get the uri of a glTF texture.
278 @param texture The glTF texture.
279 @param images The set of glTF images.
280 @return The uri of the texture.
283 if texture
and 'source' in texture:
284 source = texture[
'source']
285 if source < len(images):
286 image = images[source]
292 def readGLTFImageProperties(self, imageNode, gltfTexture, gltfSamplers):
294 @brief Convert gltF to MaterialX image properties
295 @param imageNode The MaterialX image node to set properties on.
296 @param gltfTexture The glTF texture to read properties from.
297 @param gltfSamplers The set of glTF samplers to examine properties from.
299 texcoord = gltfTexture[
'texCoord']
if 'texCoord' in gltfTexture
else None
301 extensions = gltfTexture[
'extensions']
if 'extensions' in gltfTexture
else None
302 transformExtension = extensions[
'KHR_texture_transform']
if extensions
and 'KHR_texture_transform' in extensions
else None
303 if transformExtension:
304 rotation = transformExtension[
'rotation']
if 'rotation' in transformExtension
else None
306 input = imageNode.addInputFromNodeDef(
'rotate')
310 input.setValueString (str(rotation * TO_DEGREE))
311 offset = transformExtension[
'offset']
if 'offset' in transformExtension
else None
313 input = imageNode.addInputFromNodeDef(
'offset')
315 input.setValueString ( str(offset).removeprefix(
'[').removesuffix(
']'))
316 scale = transformExtension[
'scale']
if 'scale' in transformExtension
else None
318 input = imageNode.addInputFromNodeDef(
'scale')
320 input.setValueString (str(scale).removeprefix(
'[').removesuffix(
']') )
323 texcoordt = transformExtension[
'texCoord']
if 'texCoord' in transformExtension
else None
329 self.addMTLXTexCoordNode(imageNode, texcoord)
332 samplerIndex = gltfTexture[
'sampler']
if 'sampler' in gltfTexture
else None
333 if samplerIndex !=
None and samplerIndex >= 0:
334 sampler = gltfSamplers[samplerIndex]
if samplerIndex < len(gltfSamplers)
else None
337 filterMap[9728] =
"closest"
338 filterMap[9729] =
"linear"
339 filterMap[9984] =
"cubic"
340 filterMap[9985] =
"closest"
341 filterMap[9986] =
"linear"
342 filterMap[9987] =
"cubic"
346 magFilter = sampler[
'magFilter']
if 'magFilter' in sampler
else None
348 input = imageNode.addInputFromNodeDef(
'filtertype')
350 filterString = filterMap[magFilter]
351 input.setValueString (filterString)
352 minFilter = sampler[
'minFilter']
if 'minFilter' in sampler
else None
354 input = imageNode.addInputFromNodeDef(
'filtertype')
356 filterString = filterMap[minFilter]
357 input.setValueString (filterString)
360 wrapMap[33071] =
"clamp"
361 wrapMap[33648] =
"mirror"
362 wrapMap[10497] =
"periodic"
363 wrapS = sampler[
'wrapS']
if 'wrapS' in sampler
else None
365 input = imageNode.addInputFromNodeDef(
'uaddressmode')
367 input.setValueString (wrapMap[wrapS])
369 self.log(
'Failed to add uaddressmode input')
370 wrapT = sampler[
'wrapT']
if 'wrapT' in sampler
else None
372 input = imageNode.addInputFromNodeDef(
'vaddressmode')
374 input.setValueString (wrapMap[wrapT])
376 self.log(
'*** failed to add vaddressmode input')
379 def readInput(self, materials, texture, values, imageNodeName, nodeCategory, nodeType, nodeDefId,
380 shaderNode, inputNames, gltf_textures, gltf_images, gltf_samplers) -> mx.Node:
382 @brief Read glTF material input and set input values or add upstream connected nodes
383 @param materials MaterialX document to update
384 @param texture The glTF texture to read properties from.
385 @param values The values to set on the shader node inputs.
386 @param imageNodeName The name of the image node to create if mapped
387 @param nodeCategory The category of the image node to create if mapped
388 @param nodeType The type of the image node to create if mapped
389 @param nodeDefId The node definition id of the image node to create if mapped
390 @param shaderNode The shader node to update inputs on.
391 @param inputNames The names of the inputs to update on the shader node.
392 @param gltf_textures The set of glTF textures to examine
393 @param gltf_images The set of glTF images to examine
394 @param gltf_samplers The set of glTF samplers to examine
395 @return The created image node if mapped, otherwise None.
401 textureIndex = texture[
'index']
402 texture = gltf_textures[textureIndex]
if textureIndex < len(gltf_textures)
else None
403 uri = self.getGLTFTextureUri(texture, gltf_images)
404 imageNodeName = materials.createValidChildName(imageNodeName)
405 imageNode = self.addMtlxImage(materials, imageNodeName, uri, nodeCategory, nodeDefId,
406 nodeType, EMPTY_STRING)
408 self.readGLTFImageProperties(imageNode, texture, gltf_samplers)
410 for inputName
in inputNames:
411 input = shaderNode.addInputFromNodeDef(inputName)
413 input.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, imageNode.getName())
414 input.removeAttribute(MTLX_VALUE_ATTRIBUTE)
418 if len(values) > 0
and (len(values) == len(inputNames)):
419 for i
in range(0, len(values)):
420 inputName = inputNames[i]
422 input = shaderNode.addInputFromNodeDef(inputName)
424 input.setValue(float(value))
428 def versionGreaterThan(self, major, minor, patch):
431 mx_major, mx_minor, mx_patch = mx.getVersionIntegers()
432 print(
'-------------- version: ', mx_major, mx_minor, mx_patch,
'. vs', major, minor, mx_patch)
441 def readColorInput(self, materials, colorTexture, color, imageNodeName, nodeCategory, nodeType, nodeDefId,
442 shaderNode, colorInputName, alphaInputName,
443 gltf_textures, gltf_images, gltf_samplers, colorspace=MTLX_DEFAULT_COLORSPACE):
445 @brief Read glTF material color input and set input values or add upstream connected nodes
446 @param materials MaterialX document to update
447 @param colorTexture The glTF texture to read properties from.
448 @param color The color to set on the shader node inputs if unmapped
449 @param imageNodeName The name of the image node to create if mapped
450 @param nodeCategory The category of the image node to create if mapped
451 @param nodeType The type of the image node to create if mapped
452 @param nodeDefId The node definition id of the image node to create if mapped
453 @param shaderNode The shader node to update inputs on.
454 @param colorInputName The name of the color input to update on the shader node.
455 @param alphaInputName The name of the alpha input to update on the shader node.
456 @param gltf_textures The set of glTF textures to examine
457 @param gltf_images The set of glTF images to examine
458 @param gltf_samplers The set of glTF samplers to examine
459 @param colorspace The colorspace to set on the image node if mapped. Default is assumed to be srgb_texture.
460 to match glTF convention.
463 assignedColorTexture =
False
464 assignedAlphaTexture =
False
467 addAllInputs = self._options[
'addAllInputs']
469 shaderNode.addInputsFromNodeDef()
470 shaderNode.removeChild(
'tangent')
471 shaderNode.removeChild(
'normal')
472 shaderNode.removeChild(
'clearcoat_normal')
473 shaderNode.removeChild(
'attenuation_distance')
478 textureIndex = colorTexture[
'index']
479 texture = gltf_textures[textureIndex]
if textureIndex < len(gltf_textures)
else None
480 uri = self.getGLTFTextureUri(texture, gltf_images)
481 imageNodeName = materials.createValidChildName(imageNodeName)
482 imageNode = self.addMtlxImage(materials, imageNodeName, uri, nodeCategory, nodeDefId, nodeType, colorspace)
485 self.readGLTFImageProperties(imageNode, colorTexture, gltf_samplers)
487 newTextureName = imageNode.getName()
490 if len(colorInputName):
491 colorInput = shaderNode.addInputFromNodeDef(colorInputName)
493 self.log(
'Failed to add color input:' + colorInputName)
495 colorInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, newTextureName)
496 colorInput.setOutputString(
'outcolor')
497 colorInput.removeAttribute(MTLX_VALUE_ATTRIBUTE)
498 assignedColorTexture =
True
501 if len(alphaInputName)
and self.versionGreaterThan(1, 38, 10):
502 alphaInput = shaderNode.addInputFromNodeDef(alphaInputName)
504 self.log(
'Failed to add alpha input:' + alphaInputName)
506 alphaInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, newTextureName)
507 alphaInput.setOutputString(
'outa')
508 alphaInput.removeAttribute(MTLX_VALUE_ATTRIBUTE)
510 assignedAlphaTexture =
True
514 if not assignedColorTexture
and len(colorInputName):
515 colorInput = shaderNode.addInputFromNodeDef(colorInputName)
517 nd = shaderNode.getNodeDef()
518 self.log(
'Failed to add color input: %s' % colorInputName)
520 colorInput.setValue(mx.Color3(color[0], color[1], color[2]))
522 colorspaceattr = MTLX_COLOR_SPACE_ATTRIBUTE
523 colorInput.setAttribute(colorspaceattr, colorspace)
524 if not assignedAlphaTexture
and len(alphaInputName):
525 alphaInput = shaderNode.addInputFromNodeDef(alphaInputName)
527 self.log(
'Failed to add alpha input: %s' % alphaInputName)
530 alphaInput.setValue(float(color[3]))
532 def glTF2MaterialX(self, doc, gltfDoc) -> bool:
534 @brief Convert glTF document to a MaterialX document.
535 @param doc The MaterialX document to update.
536 @param gltfDoc The glTF document to read from.
537 @return True if successful, otherwise False.
540 materials = gltfDoc[
'materials']
if 'materials' in gltfDoc
else []
541 textures = gltfDoc[
'textures']
if 'textures' in gltfDoc
else []
542 images = gltfDoc[
'images']
if 'images' in gltfDoc
else []
543 samplers = gltfDoc[
'samplers']
if 'samplers' in gltfDoc
else []
545 if not materials
or len(materials) == 0:
546 self.log(
'No materials found to convert')
551 alphaModeMap[
'OPAQUE'] = 0
552 alphaModeMap[
'MASK'] = 1
553 alphaModeMap[
'BLEND'] = 2
555 for material
in materials:
558 shaderName = MTLX_DEFAULT_SHADER_NAME
559 materialName = MTLX_DEFAULT_MATERIAL_NAME
560 if 'name' in material:
561 gltfMaterialName = material[
'name']
562 materialName = MTLX_MATERIAL_PREFIX + gltfMaterialName
563 shaderName = MTLX_SHADER_PREFIX + gltfMaterialName
564 shaderName = gltfMaterialName
565 shaderName = doc.createValidChildName(shaderName)
566 materialName = doc.createValidChildName(materialName)
568 material[
'name'] = materialName
571 use_unlit =
True if 'unlit' in material
else False
572 if 'extensions' in material:
573 mat_extensions = material[
'extensions']
574 if 'KHR_materials_unlit' in mat_extensions:
577 shaderCategory = MTLX_UNLIT_CATEGORY_STRING
if use_unlit
else MTLX_GLTF_PBR_CATEGORY
578 nodedefString =
'ND_surface_unlit' if use_unlit
else 'ND_gltf_pbr_surfaceshader'
579 comment = doc.addChildOfCategory(
'comment')
580 comment.setDocString(
' Generated shader: ' + shaderName +
' ')
581 shaderNode = doc.addNode(shaderCategory, shaderName, mx.SURFACE_SHADER_TYPE_STRING)
582 shaderNode.setAttribute(mx.InterfaceElement.NODE_DEF_ATTRIBUTE, nodedefString)
584 addInputsFromNodeDef = self._options[
'addAllInputs']
585 if addInputsFromNodeDef:
586 shaderNode.addInputsFromNodeDef()
587 shaderNode.removeChild(
'tangent')
588 shaderNode.removeChild(
'normal')
591 comment = doc.addChildOfCategory(
'comment')
592 comment.setDocString(
' Generated material: ' + materialName +
' ')
593 materialNode = doc.addNode(mx.SURFACE_MATERIAL_NODE_STRING, materialName, mx.MATERIAL_TYPE_STRING)
594 shaderInput = materialNode.addInput(mx.SURFACE_SHADER_TYPE_STRING, mx.SURFACE_SHADER_TYPE_STRING)
595 shaderInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, shaderNode.getName())
597 if self._options[
'debugOutput']:
598 print(
'- Convert gLTF material to MateriaLX: %s' % materialName)
601 haveSeparateOcclusion =
False
606 if 'pbrMetallicRoughness' in material:
607 pbrMetallicRoughness = material[
'pbrMetallicRoughness']
611 baseColorTexture =
None
612 if 'baseColorTexture' in pbrMetallicRoughness:
613 baseColorTexture = pbrMetallicRoughness[
'baseColorTexture']
614 baseColorFactor =
None
615 if 'baseColorFactor' in pbrMetallicRoughness:
616 baseColorFactor = pbrMetallicRoughness[
'baseColorFactor']
617 if baseColorTexture
or baseColorFactor:
618 colorInputName =
'base_color'
619 alphaInputName =
'alpha'
621 colorInputName =
'emission_color'
622 alphaInputName =
'opacity'
623 imagename =
'image_' + colorInputName
624 self.readColorInput(doc, baseColorTexture, baseColorFactor, imagename,
625 MTLX_GLTF_COLOR_IMAGE, MULTI_OUTPUT_TYPE_STRING,
626 '', shaderNode, colorInputName, alphaInputName,
627 textures, images, samplers, MTLX_DEFAULT_COLORSPACE)
631 if 'metallicFactor' in pbrMetallicRoughness:
632 metallicFactor = pbrMetallicRoughness[
'metallicFactor']
633 metallicFactor = str(metallicFactor)
634 metallicInput = shaderNode.addInputFromNodeDef(
'metallic')
635 metallicInput.setValueString(metallicFactor)
639 if 'roughnessFactor' in pbrMetallicRoughness:
640 roughnessFactor = pbrMetallicRoughness[
'roughnessFactor']
641 roughnessFactor = str(roughnessFactor)
642 roughnessInput = shaderNode.addInputFromNodeDef(
'roughness')
643 roughnessInput.setValueString(roughnessFactor)
650 if 'metallicRoughnessTexture' in pbrMetallicRoughness:
651 texture = pbrMetallicRoughness[
'metallicRoughnessTexture']
653 imageNode = self.readInput(doc, texture, [],
'image_orm', MTLX_GLTF_IMAGE, MTLX_VEC3_STRING,
'',
654 shaderNode, [
'metallic',
'roughness',
'occlusion'], textures, images, samplers)
655 self.readGLTFImageProperties(imageNode, texture, samplers)
658 indexName = [
'x',
'y',
'z' ]
659 outputName = [
'outx',
'outy',
'outz' ]
660 metallicInput = shaderNode.addInputFromNodeDef(
'metallic')
661 roughnessInput = shaderNode.addInputFromNodeDef(
'roughness')
662 occlusionInput =
None if haveSeparateOcclusion
else shaderNode.addInputFromNodeDef(
'occlusion')
663 inputs = [ occlusionInput, roughnessInput, metallicInput ]
664 addSeparateNode =
False
665 addExtractNode =
True
669 separateNodeName = doc.createValidChildName(
'separate_orm')
670 separateNode = doc.addNode(
'separate3', separateNodeName, MULTI_OUTPUT_TYPE_STRING)
671 seperateInput = separateNode.addInputFromNodeDef(MTLX_IN_STRING)
672 seperateInput.setType(MTLX_VEC3_STRING)
673 seperateInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, imageNode.getName())
677 input.setType(MTLX_FLOAT_STRING)
679 extractNodeName = doc.createValidChildName(
'extract_orm')
680 extractNode = doc.addNode(
'extract', extractNodeName, MTLX_FLOAT_STRING)
681 extractNode.addInputsFromNodeDef()
682 extractNodeInput = extractNode.getInput(MTLX_IN_STRING)
683 extractNodeInput.setType(MTLX_VEC3_STRING)
684 extractNodeInput.removeAttribute(MTLX_VALUE_STRING)
685 extractNodeInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, imageNode.getName())
686 extractNodeInput = extractNode.getInput(
'index')
687 extractNodeInput.setValue(i)
689 input.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, extractNode.getName())
691 input.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, separateNode.getName())
692 input.setOutputString(outputName[i])
696 if 'normalTexture' in material:
697 normalTexture = material[
'normalTexture']
698 self.readInput(doc, normalTexture, [],
'image_normal', MTLX_GLTF_NORMALMAP_IMAGE, MTLX_VEC3_STRING,
'',
699 shaderNode, [
'normal'], textures, images, samplers)
703 occlusionTexture =
None
704 if 'occlusionTexture' in material:
705 occlusionTexture = material[
'occlusionTexture']
706 self.readInput(doc, occlusionTexture, [],
'image_occlusion', MTLX_GLTF_IMAGE, MTLX_FLOAT_STRING,
'',
707 shaderNode, [
'occlusion'], textures, images, samplers)
711 emissiveTexture =
None
712 if 'emissiveTexture' in material:
713 emissiveTexture = material[
'emissiveTexture']
714 emissiveFactor = [0.0, 0.0, 0.0]
715 if 'emissiveFactor' in material:
716 emissiveFactor = material[
'emissiveFactor']
717 self.readColorInput(doc, emissiveTexture, emissiveFactor,
'image_emissive',
718 MTLX_GLTF_COLOR_IMAGE, MULTI_OUTPUT_TYPE_STRING,
719 '', shaderNode,
'emissive',
'', textures, images, samplers, MTLX_DEFAULT_COLORSPACE)
723 if 'alphaMode' in material:
724 alphaModeString = material[
'alphaMode']
726 if alphaModeString
in alphaModeMap:
727 alphaMode = alphaModeMap[alphaModeString]
729 alphaModeInput = shaderNode.addInputFromNodeDef(
'alpha_mode')
730 alphaModeInput.setValue(alphaMode)
734 if 'alphaCutoff' in material:
735 alphaCutOff = material[
'alphaCutoff']
736 if alphaCutOff != 0.5:
737 alphaCutOffInput = shaderNode.addInputFromNodeDef(
'alpha_cutoff')
738 alphaCutOffInput.setValue(float(alphaCutOff))
742 if 'extensions' in material:
743 extensions = material[
'extensions']
747 if 'KHR_materials_ior' in extensions:
748 iorExtension = extensions[
'KHR_materials_ior']
749 if 'ior' in iorExtension:
750 ior = iorExtension[
'ior']
751 iorInput = shaderNode.addInputFromNodeDef(
'ior')
752 iorInput.setValue(float(ior))
755 if 'KHR_materials_specular' in extensions:
756 specularExtension = extensions[
'KHR_materials_specular']
757 specularColorFactor =
None
758 if 'specularColorFactor' in specularExtension:
759 specularColorFactor = specularExtension[
'specularColorFactor']
760 specularColorTexture =
None
761 if 'specularColorTexture' in specularExtension:
762 specularColorTexture = specularExtension[
'specularColorTexture']
763 if specularColorFactor
or specularColorTexture:
764 self.readColorInput(doc, specularColorTexture, specularColorFactor,
'image_specularcolor',
765 MTLX_GLTF_COLOR_IMAGE, MULTI_OUTPUT_TYPE_STRING,
766 '', shaderNode,
'specular_color',
'', textures, images, MTLX_DEFAULT_COLORSPACE)
768 specularTexture = specularFactor =
None
769 if 'specularFactor' in specularExtension:
770 specularFactor = specularExtension[
'specularFactor']
771 if 'specularTexture' in specularExtension:
772 specularTexture = specularExtension[
'specularTexture']
773 if specularFactor
or specularTexture:
774 self.readInput(doc, specularTexture, [specularFactor],
'image_specular', MTLX_GLTF_IMAGE,
775 MTLX_FLOAT_STRING,
'', shaderNode, [
'specular'], textures, images, samplers)
778 if 'KHR_materials_transmission' in extensions:
779 transmissionExtension = extensions[
'KHR_materials_transmission']
780 if 'transmissionFactor' in transmissionExtension:
781 transmissionFactor = transmissionExtension[
'transmissionFactor']
782 transmissionInput = shaderNode.addInputFromNodeDef(
'transmission')
783 transmissionInput.setValue(float(transmissionFactor))
786 if 'KHR_materials_iridescence' in extensions:
787 iridescenceExtension = extensions[
'KHR_materials_iridescence']
788 if iridescenceExtension:
791 iridescenceFactor = iridescenceTexture =
None
792 if 'iridescenceFactor' in iridescenceExtension:
793 iridescenceFactor = iridescenceExtension[
'iridescenceFactor']
794 if 'iridescenceTexture' in iridescenceExtension:
795 iridescenceTexture = iridescenceExtension[
'iridescenceTexture']
796 if iridescenceFactor
or iridescenceTexture:
797 self.readInput(doc, iridescenceTexture, [iridescenceFactor],
'image_iridescence', MTLX_GLTF_IMAGE,
798 MTLX_FLOAT_STRING,
'', shaderNode, [
'iridescence'], textures, images, samplers)
801 if 'iridescenceIor' in iridescenceExtension:
802 iridescenceIor = iridescenceExtension[
'iridescenceIor']
803 self.readInput(doc,
None, [iridescenceIor],
'',
'',
804 MTLX_FLOAT_STRING,
'', shaderNode, [
'iridescence_ior'], textures, images, samplers)
807 iridescenceThicknessMinimum = iridescenceExtension[
'iridescenceThicknessMinimum']
if 'iridescenceThicknessMinimum' in iridescenceExtension
else None
808 iridescenceThicknessMaximum = iridescenceExtension[
'iridescenceThicknessMaximum']
if 'iridescenceThicknessMaximum' in iridescenceExtension
else None
809 iridescenceThicknessTexture = iridescenceExtension[
'iridescenceThicknessTexture']
if 'iridescenceThicknessTexture' in iridescenceExtension
else None
810 if iridescenceThicknessMinimum
or iridescenceThicknessMaximum
or iridescenceThicknessTexture:
811 floatInput = shaderNode.addInputFromNodeDef(
"iridescence_thickness")
814 if iridescenceThicknessTexture:
815 textureIndex = iridescenceThicknessTexture[
'index']
816 texture = textures[textureIndex]
if textureIndex < len(textures)
else None
817 uri = self.getGLTFTextureUri(texture, images)
819 imageNodeName = doc.createValidChildName(
"image_iridescence_thickness")
820 newTexture = self.addMtlxImage(doc, imageNodeName, uri,
'gltf_iridescence_thickness',
'', MTLX_FLOAT_STRING,
'')
822 floatInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, newTexture.getName())
823 floatInput.removeAttribute(MTLX_VALUE_STRING)
825 if iridescenceThicknessMinimum:
826 minInput = newTexture.addInputFromNodeDef(
"thicknessMin")
828 minInput.setValue(float(iridescenceThicknessMinimum))
829 if iridescenceThicknessMaximum:
830 maxInput = newTexture.addInputFromNodeDef(
"thicknessMax")
832 maxInput.setValue(float(iridescenceThicknessMaximum))
834 if 'KHR_materials_emissive_strength' in extensions:
835 emissiveStrengthExtension = extensions[
'KHR_materials_emissive_strength']
836 if 'emissiveStrength' in emissiveStrengthExtension:
837 emissiveStrength = emissiveStrengthExtension[
'emissiveStrength']
838 self.readInput(doc,
None, [emissiveStrength],
'',
'',
'',
'',
839 shaderNode, [
'emissive_strength'], textures, images, samplers)
842 if 'KHR_materials_volume' in extensions:
843 volumeExtension = extensions[
'KHR_materials_volume']
846 thicknessFactor = thicknessTexture =
None
847 if 'thicknessFactor' in volumeExtension:
848 thicknessFactor = volumeExtension[
'thicknessFactor']
849 if 'thicknessTexture' in volumeExtension:
850 thicknessTexture = volumeExtension[
'thicknessTexture']
851 if thicknessFactor
or thicknessTexture:
852 self.readInput(doc, thicknessTexture, [thicknessFactor],
'image_thickness', MTLX_GLTF_IMAGE, MTLX_FLOAT_STRING,
'',
853 shaderNode, [
'thickness'], textures, images, samplers)
856 if 'attenuationColor' in volumeExtension:
857 attenuationColor = volumeExtension[
'attenuationColor']
858 attenuationInput = shaderNode.addInputFromNodeDef(
'attenuation_color')
859 attenuationInput.setValue(mx.Color3(attenuationColor[0], attenuationColor[1], attenuationColor[2]))
862 if 'attenuationDistance' in volumeExtension:
863 attenuationDistance = volumeExtension[
'attenuationDistance']
864 attenuationDistance = str(attenuationDistance)
865 attenuationInput = shaderNode.addInputFromNodeDef(
'attenuation_distance')
866 attenuationInput.setValueString(attenuationDistance)
869 if 'KHR_materials_clearcoat' in extensions:
870 clearcoat = extensions[
'KHR_materials_clearcoat']
872 clearcoatFactor = clearcoatTexture =
None
873 if 'clearcoatFactor' in clearcoat:
874 clearcoatFactor = clearcoat[
'clearcoatFactor']
875 if 'clearcoatTexture' in clearcoat:
876 clearcoatTexture = clearcoat[
'clearcoatTexture']
877 if clearcoatFactor
or clearcoatTexture:
878 self.readInput(doc, clearcoatTexture, [clearcoatFactor],
'image_clearcoat',
879 MTLX_GLTF_IMAGE, MTLX_FLOAT_STRING,
'', shaderNode, [
'clearcoat'],
880 textures, images, samplers)
882 clearcoatRoughnessFactor = clearcoatRoughnessTexture =
None
883 if 'clearcoatRoughnessFactor' in clearcoat:
884 clearcoatRoughnessFactor = clearcoat[
'clearcoatRoughnessFactor']
885 if 'clearcoatRoughnessTexture' in clearcoat:
886 clearcoatRoughnessTexture = clearcoat[
'clearcoatRoughnessTexture']
887 if clearcoatRoughnessFactor
or clearcoatRoughnessTexture:
888 self.readInput(doc, clearcoatRoughnessTexture, [clearcoatRoughnessFactor],
889 'image_clearcoat_roughness',
890 MTLX_GLTF_IMAGE, MTLX_FLOAT_STRING,
'', shaderNode, [
'clearcoat_roughness'],
891 textures, images, samplers)
893 if 'clearcoatNormalTexture' in clearcoat:
894 clearcoatNormalTexture = clearcoat[
'clearcoatNormalTexture']
895 self.readInput(doc, clearcoatNormalTexture, [
None],
'image_clearcoat_normal',
896 MTLX_GLTF_NORMALMAP_IMAGE, MTLX_VEC3_STRING,
'',
897 shaderNode, [
'clearcoat_normal'], textures, images, samplers)
900 if 'KHR_materials_sheen' in extensions:
901 sheen = extensions[
'KHR_materials_sheen']
903 sheenColorFactor = sheenColorTexture =
None
904 if 'sheenColorFactor' in sheen:
905 sheenColorFactor = sheen[
'sheenColorFactor']
906 if 'sheenColorTexture' in sheen:
907 sheenColorTexture = sheen[
'sheenColorTexture']
908 if sheenColorFactor
or sheenColorTexture:
909 self.readColorInput(doc, sheenColorTexture, sheenColorFactor,
'image_sheen',
910 MTLX_GLTF_COLOR_IMAGE, MULTI_OUTPUT_TYPE_STRING,
911 '', shaderNode,
'sheen_color',
'', textures, images, MTLX_DEFAULT_COLORSPACE)
913 sheenRoughnessFactor = sheenRoughnessTexture =
None
914 if 'sheenRoughnessFactor' in sheen:
915 sheenRoughnessFactor = sheen[
'sheenRoughnessFactor']
916 if 'sheenRoughnessTexture' in sheen:
917 sheenRoughnessTexture = sheen[
'sheenRoughnessTexture']
918 if sheenRoughnessFactor
or sheenRoughnessTexture:
919 self.readInput(doc, sheenRoughnessTexture, [sheenRoughnessFactor],
'image_sheen_roughness', MTLX_GLTF_IMAGE, MTLX_FLOAT_STRING,
'',
920 shaderNode, [
'sheen_roughness'], textures, images, samplers)
924 def computeMeshMaterials(self, materialMeshList, materialCPVList, cnode, path, nodeCount, meshCount, meshes, nodes, materials):
926 @brief Recursively computes mesh to material assignments.
927 @param materialMeshList The dictionary of material to mesh assignments to update.
928 @param materialCPVList The list of materials that require CPV.
929 @param cnode The current node to examine.
930 @param path The current string path to the node. If a node, mesh has no name then a default name is used. Primtives are named by index.
931 @param nodeCount The current node count.
932 @param meshCount The current mesh count.
933 @param meshes The set of meshes to examine.
934 @param nodes The set of nodes to examine.
935 @param materials The set of materials to examine.
942 cnodeName = cnode[
'name']
944 cnodeName = GLTF_DEFAULT_NODE_PREFIX + str(nodeCount)
945 nodeCount = nodeCount + 1
946 path = path +
'/' + ( mx.createValidName(cnodeName) )
950 meshIndex = cnode[
'mesh']
951 cmesh = meshes[meshIndex]
957 meshName = cmesh[
'name']
959 meshName = self.GLTF_DEFAULT_MESH_PREFIX + str(meshCount)
960 meshCount = meshCount + 1
961 path = path +
'/' + mx.createValidName(meshName)
963 if 'primitives' in cmesh:
964 primitives = cmesh[
'primitives']
969 for primitive
in primitives:
972 if 'material' in primitive:
973 materialIndex = primitive[
'material']
974 material = materials[materialIndex]
978 materialName = material[
'name']
980 if materialName
not in materialMeshList:
981 materialMeshList[materialName] = []
982 if len(primitives) == 1:
983 materialMeshList[materialName].append(path)
986 materialMeshList[materialName].append(path +
'/' + GLTF_DEFAULT_PRIMITIVE_PREFIX + str(primitiveIndex))
988 if 'attributes' in primitive:
989 attributes = primitive[
'attributes']
990 if 'COLOR' in attributes:
991 if self._options[
'debugOutput']:
992 print(
'CPV attribute found')
996 materialCPVList.append(materialName)
998 primitiveIndex = primitiveIndex + 1
1000 if 'children' in cnode:
1001 children = cnode[
'children']
1002 for childIndex
in children:
1003 child = nodes[childIndex]
1004 self.computeMeshMaterials(materialMeshList, materialCPVList, child, path, nodeCount, meshCount, meshes, nodes, materials)
1009 def buildMaterialAssociations(self, gltfDoc) -> dict:
1011 @brief Build a dictionary of material assignments.
1012 @param gltfDoc The glTF document to read from.
1013 @return A dictionary of material assignments if successful, otherwise None.
1015 materials = gltfDoc[
'materials']
if 'materials' in gltfDoc
else []
1016 meshes = gltfDoc[
'meshes']
if 'meshes' in gltfDoc
else []
1018 if not materials
or not meshes:
1021 meshNameTemplate =
"mesh"
1023 materialAssignments : dict = {}
1025 if 'primitives' in mesh:
1026 meshName = mesh[
'name']
if 'name' in mesh
else meshNameTemplate + str(meshCount)
1027 primitives = mesh[
'primitives']
1029 for primitive
in primitives:
1030 if 'material' in primitive:
1031 materialIndex = primitive[
'material']
1032 if materialIndex < len(materials):
1033 material = materials[materialIndex]
1034 if 'name' in material:
1035 materialName = material[
'name']
1036 primitiveName = GLTF_DEFAULT_PRIMITIVE_PREFIX + str(primitiveCount)
1037 if materialName
not in materialAssignments:
1038 materialAssignments[materialName] = []
1039 if len(primitives) == 1:
1040 materialAssignments[materialName].append(meshName)
1042 materialAssignments[materialName].append(meshName +
'/' + primitiveName)
1046 return materialAssignments
1048 def convert(self, gltfFileName) -> mx.Document:
1050 @brief Convert a glTF file to a MaterialX document.
1051 @param gltfFileName The glTF file to convert.
1052 @return A MaterialX document if successful, otherwise None.
1055 if not os.path.exists(gltfFileName):
1056 if self._options[
'debugOutput']:
1057 print(
'File not found:', gltfFileName)
1058 self.log(
'File not found:' + gltfFileName)
1063 self.log(
'Read glTF file:' + gltfFileName)
1064 gltfFile = open(gltfFileName,
'r')
1067 gltfJson = json.load(gltfFile)
1068 gltfString = json.dumps(gltfJson, indent=2)
1069 self.log(
'GLTF JSON' + gltfString)
1071 doc, libFiles = Util.createMaterialXDoc()
1072 self.glTF2MaterialX(doc, gltfJson)
1084 assignments : dict = {}
1085 materialCPVList : dict = {}
1086 meshes = gltfJson[
'meshes']
if 'meshes' in gltfJson
else []
1087 nodes = gltfJson[
'nodes']
if 'nodes' in gltfJson
else []
1088 scenes = gltfJson[
'scenes']
if 'scenes' in gltfJson
else []
1089 if meshes
and nodes
and scenes:
1090 for scene
in gltfJson[
'scenes']:
1091 self.log(
'Scan scene for materials: ' + str(scene))
1095 for nodeIndex
in scene[
'nodes']:
1096 node = nodes[nodeIndex]
1097 self.computeMeshMaterials(assignments, materialCPVList, node, path, nodeCount, meshCount, meshes, nodes, gltfJson[
'materials'])
1100 for materialName
in materialCPVList:
1101 materialNode = doc.getNode(materialName)
1102 shaderInput = materialNode.getInput(
'surfaceshader')
if materialNode
else None
1103 shaderNode = shaderInput.getConnectedNode()
if shaderInput
else None
1104 baseColorInput = shaderNode.getInput(
'base_color')
if shaderNode
else None
1105 baseColorNode = baseColorInput.getConnectedNode()
if baseColorInput
else None
1107 geomcolorInput = baseColorNode.addInputFromNodeDef(
'geomcolor')
1109 geomcolor = doc.addNode(
'geomcolor', EMPTY_STRING,
'color4')
1110 geomcolorInput.setNodeName(geomcolor.getName())
1113 assign_xform = self._options[
'assignXform']
if 'assignXform' in self._options
else False
1114 if self._options[
'createAssignments']
and len(assignments) > 0:
1115 comment = doc.addChildOfCategory(
'comment')
1116 comment.setDocString(
' Generated material assignments ')
1117 look = doc.addLook(
'look')
1118 for assignMaterial
in assignments:
1119 matassign = look.addMaterialAssign(assignMaterial)
1120 matassign.setMaterial(assignMaterial)
1123 geoms = assignments[assignMaterial]
1126 pathParts = geom.split(
'/')
1127 if len(pathParts) > 1:
1128 xformedGeom.append(
'/'.join(pathParts[:-1]))
1130 xformedGeom.append(geom)
1131 matassign.setGeom(
','.join(xformedGeom))
1133 matassign.setGeom(
','.join(assignments[assignMaterial]))