mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-25 22:25:07 +00:00
stuff
This commit is contained in:
268
MaterialX/python/Scripts/creatematerial.py
Normal file
268
MaterialX/python/Scripts/creatematerial.py
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Construct a MaterialX file from the textures in the given folder, using the standard data libraries
|
||||
to build a mapping from texture filenames to shader inputs.
|
||||
|
||||
By default the standard_surface shading model is assumed, with the --shadingModel option used to
|
||||
select any other shading model in the data libraries.
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
import MaterialX as mx
|
||||
|
||||
UDIM_TOKEN = '.<UDIM>.'
|
||||
UDIM_REGEX = r'\.\d+\.'
|
||||
TEXTURE_EXTENSIONS = [ "exr", "png", "jpg", "jpeg", "tif", "hdr" ]
|
||||
INPUT_ALIASES = { "roughness": "specular_roughness" }
|
||||
|
||||
class UdimFilePath(mx.FilePath):
|
||||
|
||||
def __init__(self, pathString):
|
||||
super().__init__(pathString)
|
||||
|
||||
self._isUdim = False
|
||||
self._udimFiles = []
|
||||
self._udimRegex = re.compile(UDIM_REGEX)
|
||||
|
||||
textureDir = self.getParentPath()
|
||||
textureName = self.getBaseName()
|
||||
textureExtension = self.getExtension()
|
||||
|
||||
if not self._udimRegex.search(textureName):
|
||||
self._udimFiles = [self]
|
||||
return
|
||||
|
||||
self._isUdim = True
|
||||
fullNamePattern = self._udimRegex.sub(self._udimRegex.pattern.replace('\\', '\\\\'),
|
||||
textureName)
|
||||
|
||||
udimFiles = filter(
|
||||
lambda f: re.search(fullNamePattern, f.asString()),
|
||||
textureDir.getFilesInDirectory(textureExtension)
|
||||
)
|
||||
self._udimFiles = [textureDir / f for f in udimFiles]
|
||||
|
||||
def __str__(self):
|
||||
return self.asPattern()
|
||||
|
||||
def asPattern(self):
|
||||
if not self._isUdim:
|
||||
return self.asString()
|
||||
|
||||
textureDir = self.getParentPath()
|
||||
textureName = self.getBaseName()
|
||||
|
||||
pattern = textureDir / mx.FilePath(
|
||||
self._udimRegex.sub(UDIM_TOKEN, textureName))
|
||||
return pattern.asString()
|
||||
|
||||
def isUdim(self):
|
||||
return self._isUdim
|
||||
|
||||
def getUdimFiles(self):
|
||||
return self._udimFiles
|
||||
|
||||
def getUdimNumbers(self):
|
||||
def _extractUdimNumber(_file):
|
||||
pattern = self._udimRegex.search(_file.getBaseName())
|
||||
if pattern:
|
||||
return re.search(r"\d+", pattern.group()).group()
|
||||
|
||||
return list(map(_extractUdimNumber, self._udimFiles))
|
||||
|
||||
def getNameWithoutExtension(self):
|
||||
if self._isUdim:
|
||||
name = self._udimRegex.split(self.getBaseName())[0]
|
||||
else:
|
||||
name = self.getBaseName().rsplit('.', 1)[0]
|
||||
|
||||
return re.sub(r'[^\w\s]+', '_', name)
|
||||
|
||||
def listTextures(textureDir, texturePrefix=None):
|
||||
'''
|
||||
Return a list of texture filenames matching known extensions.
|
||||
'''
|
||||
|
||||
texturePrefix = texturePrefix or ""
|
||||
allTextures = []
|
||||
for ext in TEXTURE_EXTENSIONS:
|
||||
textures = [textureDir / f for f in textureDir.getFilesInDirectory(ext)
|
||||
if f.asString().lower().startswith(texturePrefix.lower())]
|
||||
while textures:
|
||||
textureFile = UdimFilePath(textures[0].asString())
|
||||
allTextures.append(textureFile)
|
||||
for udimFile in textureFile.getUdimFiles():
|
||||
textures.remove(udimFile)
|
||||
return allTextures
|
||||
|
||||
def findBestMatch(textureName, shadingModel):
|
||||
'''
|
||||
Given a texture name and shading model, return the shader input that is the closest match.
|
||||
'''
|
||||
|
||||
parts = textureName.rsplit("_")
|
||||
|
||||
baseTexName = parts[-1]
|
||||
if baseTexName.lower() == 'color':
|
||||
baseTexName = ''.join(parts[-2:])
|
||||
if baseTexName in INPUT_ALIASES:
|
||||
baseTexName = INPUT_ALIASES.get(baseTexName.lower())
|
||||
|
||||
shaderInputs = shadingModel.getActiveInputs()
|
||||
ratios = []
|
||||
for shaderInput in shaderInputs:
|
||||
inputName = shaderInput.getName()
|
||||
inputName = re.sub(r'[^a-zA-Z0-9\s]', '', inputName).lower()
|
||||
baseTexName = re.sub(r'[^a-zA-Z0-9\s]', '', baseTexName).lower()
|
||||
|
||||
sequenceScore = SequenceMatcher(None, inputName, baseTexName).ratio()
|
||||
ratios.append(sequenceScore * 100)
|
||||
|
||||
highscore = max(ratios)
|
||||
if highscore < 50:
|
||||
return None
|
||||
|
||||
idx = ratios.index(highscore)
|
||||
return shaderInputs[idx]
|
||||
|
||||
def buildDocument(textureFiles, mtlxFile, shadingModel, colorspace, useTiledImage):
|
||||
'''
|
||||
Build a MaterialX document from the given textures and shading model.
|
||||
'''
|
||||
|
||||
# Find the default library nodedef, if any, for the requested shading model.
|
||||
stdlib = mx.createDocument()
|
||||
mx.loadLibraries(mx.getDefaultDataLibraryFolders(), mx.getDefaultDataSearchPath(), stdlib)
|
||||
matchingNodeDefs = stdlib.getMatchingNodeDefs(shadingModel)
|
||||
if not matchingNodeDefs:
|
||||
print('Shading model', shadingModel, 'not found in the MaterialX data libraries')
|
||||
return None
|
||||
shadingModelNodeDef = matchingNodeDefs[0]
|
||||
for nodeDef in matchingNodeDefs:
|
||||
if nodeDef.getAttribute('isdefaultversion') == 'true':
|
||||
shadingModelNodeDef = nodeDef
|
||||
|
||||
# Create content document.
|
||||
doc = mx.createDocument()
|
||||
materialName = mx.createValidName(mtlxFile.getBaseName().rsplit('.', 1)[0])
|
||||
nodeGraph = doc.addNodeGraph('NG_' + materialName)
|
||||
shaderNode = doc.addNode(shadingModel, 'SR_' + materialName, 'surfaceshader')
|
||||
doc.addMaterialNode('M_' + materialName, shaderNode)
|
||||
|
||||
# Iterate over texture files.
|
||||
imageNodeCategory = 'tiledimage' if useTiledImage else 'image'
|
||||
udimNumbers = set()
|
||||
for textureFile in textureFiles:
|
||||
textureName = textureFile.getNameWithoutExtension()
|
||||
shaderInput = findBestMatch(textureName, shadingModelNodeDef)
|
||||
|
||||
if not shaderInput:
|
||||
print('Skipping', textureFile.getBaseName(), 'which does not match any', shadingModel, 'input')
|
||||
continue
|
||||
|
||||
inputName = shaderInput.getName()
|
||||
inputType = shaderInput.getType()
|
||||
|
||||
# Skip inputs that have already been created, e.g. in multi-UDIM materials.
|
||||
if shaderNode.getInput(inputName) or nodeGraph.getChild(textureName):
|
||||
continue
|
||||
|
||||
mtlInput = shaderNode.addInput(inputName)
|
||||
textureName = nodeGraph.createValidChildName(textureName)
|
||||
imageNode = nodeGraph.addNode(imageNodeCategory, textureName, inputType)
|
||||
|
||||
# Set color space.
|
||||
if shaderInput.isColorType():
|
||||
imageNode.setColorSpace(colorspace)
|
||||
|
||||
# Set file path.
|
||||
filePathString = os.path.relpath(textureFile.asPattern(), mtlxFile.getParentPath().asString())
|
||||
imageNode.setInputValue('file', filePathString, 'filename')
|
||||
|
||||
# Apply special cases for normal maps.
|
||||
inputNode = imageNode
|
||||
connNode = imageNode
|
||||
inBetweenNodes = []
|
||||
if inputName.endswith('normal') and shadingModel == 'standard_surface':
|
||||
inBetweenNodes = ["normalmap"]
|
||||
for inNodeName in inBetweenNodes:
|
||||
connNode = nodeGraph.addNode(inNodeName, textureName + '_' + inNodeName, inputType)
|
||||
connNode.setConnectedNode('in', inputNode)
|
||||
inputNode = connNode
|
||||
|
||||
# Create output.
|
||||
outputNode = nodeGraph.addOutput(textureName + '_output', inputType)
|
||||
outputNode.setConnectedNode(connNode)
|
||||
mtlInput.setConnectedOutput(outputNode)
|
||||
mtlInput.setType(inputType)
|
||||
|
||||
if textureFile.isUdim():
|
||||
udimNumbers.update(set(textureFile.getUdimNumbers()))
|
||||
|
||||
# Create udim set
|
||||
if udimNumbers:
|
||||
geomInfoName = doc.createValidChildName('GI_' + materialName)
|
||||
geomInfo = doc.addGeomInfo(geomInfoName)
|
||||
geomInfo.setGeomPropValue('udimset', list(udimNumbers), "stringarray")
|
||||
|
||||
# Return the new document
|
||||
return doc
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--outputFilename', dest='outputFilename', type=str, help='Filename of the output MaterialX document.')
|
||||
parser.add_argument('--shadingModel', dest='shadingModel', type=str, default="standard_surface", help='The shading model used in analyzing input textures.')
|
||||
parser.add_argument('--colorSpace', dest='colorSpace', type=str, help='The colorspace in which input textures should be interpreted, defaulting to srgb_texture.')
|
||||
parser.add_argument('--texturePrefix', dest='texturePrefix', type=str, help='Filter input textures by the given prefix.')
|
||||
parser.add_argument('--tiledImage', dest='tiledImage', action="store_true", help='Request tiledimage nodes instead of image nodes.')
|
||||
parser.add_argument(dest='inputDirectory', nargs='?', help='Input folder that will be scanned for textures, defaulting to the current working directory.')
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
texturePath = mx.FilePath.getCurrentPath()
|
||||
if options.inputDirectory:
|
||||
texturePath = mx.FilePath(options.inputDirectory)
|
||||
if not texturePath.isDirectory():
|
||||
print('Input folder not found:', texturePath)
|
||||
return
|
||||
|
||||
mtlxFile = texturePath / mx.FilePath('material.mtlx')
|
||||
if options.outputFilename:
|
||||
mtlxFile = mx.FilePath(options.outputFilename)
|
||||
|
||||
textureFiles = listTextures(texturePath, texturePrefix=options.texturePrefix)
|
||||
if not textureFiles:
|
||||
print('No matching textures found in input folder.')
|
||||
return
|
||||
|
||||
# Get shading model and color space.
|
||||
shadingModel = 'standard_surface'
|
||||
colorspace = 'srgb_texture'
|
||||
if options.shadingModel:
|
||||
shadingModel = options.shadingModel
|
||||
if options.colorSpace:
|
||||
colorspace = options.colorSpace
|
||||
print('Analyzing textures in the', texturePath.asString(), 'folder for the', shadingModel, 'shading model.')
|
||||
|
||||
# Create the MaterialX document.
|
||||
doc = buildDocument(textureFiles, mtlxFile, shadingModel, colorspace, options.tiledImage)
|
||||
if not doc:
|
||||
return
|
||||
|
||||
if options.outputFilename:
|
||||
# Write the document to disk.
|
||||
if not mtlxFile.getParentPath().exists():
|
||||
mtlxFile.getParentPath().createDirectory()
|
||||
mx.writeToXmlFile(doc, mtlxFile.asString())
|
||||
print('Wrote MaterialX document to disk:', mtlxFile.asString())
|
||||
else:
|
||||
# Print the document to the standard output.
|
||||
print('Generated MaterialX document:')
|
||||
print(mx.writeToXmlString(doc))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user