2@brief Class to load Physically Based Materials from the PhysicallyBased site.
3and convert the materials to MaterialX format for given target shading models.
6import requests, json, os, inspect
8from http
import HTTPStatus
10from typing
import Optional
14 @brief Class to load Physically Based Materials from the PhysicallyBased site.
15 The class can convert the materials to MaterialX format for given target shading models.
17 def __init__(self, mx_module, mx_stdlib : Optional[mx.Document] =
None):
19 @brief Constructor for the PhysicallyBasedMaterialLoader class.
20 Will initialize shader mappings and load the MaterialX standard library
21 if it is not passed in as an argument.
22 @param mx_module The MaterialX module. Required.
23 @param mx_stdlib The MaterialX standard library. Optional.
26 self.
logger = lg.getLogger(
'PBMXLoader')
27 lg.basicConfig(level=lg.INFO)
34 self.
uri =
'https://api.physicallybased.info/materials'
47 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module not specified.')
51 version_major, version_minor, version_patch = self.
mxmx.getVersionIntegers()
52 self.
logger.debug(f
'> MaterialX version: {version_major}.{version_minor}.{version_patch}')
53 if (version_major >=1
and version_minor >= 39)
or version_major > 1:
54 self.
logger.debug(
'> OpenPBR shading model supported')
62 libFiles = self.
mxmx.loadLibraries(mx.getDefaultDataLibraryFolders(), mx.getDefaultDataSearchPath(), self.
stdlib)
63 self.
logger.debug(f
'> Loaded standard library: {libFiles}')
67 @brief Set the debugging level for the logger.
68 @param debug True to set the logger to debug level, otherwise False.
72 self.
logger.setLevel(lg.DEBUG)
74 self.
logger.setLevel(lg.INFO)
78 @brief Get the remapping keys for a given shading model.
79 @param shadingModel The shading model to get the remapping keys for.
80 @return A dictionary of remapping keys.
85 self.
logger.warn(f
'> No remapping keys found for shading model: {shadingModel}')
90 @brief Initialize remapping keys for different shading models.
91 The currently supported shading models are:
98 standard_surface_remapKeys = {
99 'color':
'base_color',
100 'specularColor':
'specular_color',
101 'roughness':
'specular_roughness',
103 'ior':
'specular_IOR',
105 'transmission_color':
'transmission_color',
106 'thinFilmIor' :
'thin_film_IOR',
107 'thinFilmThickness' :
'thin_film_thickness',
108 'transmissionDispersion' :
'transmission_dispersion',
112 openpbr_remapKeys = {
113 'color':
'base_color',
114 'specularColor':
'specular_color',
115 'roughness':
'specular_roughness',
116 'metalness':
'base_metalness',
117 'ior':
'specular_ior',
118 'transmission':
'transmission_weight',
119 'transmission_color':
'transmission_color',
120 'subsurfaceRadius':
'subsurface_radius',
121 'thinFilmIor' :
'thin_film_ior',
122 'thinFilmThickness' :
'thin_film_thickness',
123 'transmissionDispersion' :
'transmission_dispersion_scale',
127 'color':
'base_color',
128 'specularColor':
'specular_color',
129 'roughness':
'roughness',
130 'metalness':
'metallic',
131 'transmission_color':
'attenuation_color',
137 self.
remapMap[
'standard_surface'] = standard_surface_remapKeys;
138 self.
remapMap[
'gltf_pbr'] = gltf_remapKeys;
140 self.
remapMap[
'open_pbr_surface'] = openpbr_remapKeys;
143 ''' Get the JSON object representing the Physically Based Materials '''
148 Get the list of material names from the JSON object
149 @return The list of material names
155 Get the MaterialX document
156 @return The MaterialX document
162 @brief Load the Physically Based Materials from a JSON file
163 @param fileName The filename to load the JSON file from
164 @return The JSON object representing the Physically Based Materials
168 if not os.path.exists(fileName):
169 self.
logger.error(f
'> File does not exist: {fileName}')
172 with open(fileName,
'r')
as json_file:
181 @brief Load the Physically Based Materials from a JSON string
182 @param matString The JSON string to load the Physically Based Materials from
183 @return The JSON object representing the Physically Based Materials
195 @brief Get the Physically Based Materials from the PhysicallyBased site
196 @return The JSON object representing the Physically Based Materials
203 'Accept':
'application/json'
206 response = requests.get(url, headers=headers)
208 if response.status_code == HTTPStatus.OK:
214 self.
logger.error(f
'> Status: {response.status_code}, {response.text}')
220 @brief Print the materials to the console
224 self.
logger.info(
'Material name: ' + mat[
'name'])
226 for key, value
in mat.items():
227 if (key !=
'name' and value):
228 self.
logger.info(f
'> - {key}: {value}')
232 @brief Write the materials to a JSON file
233 @param filename The filename to write the JSON file to
234 @return True if the file was written successfully, otherwise False
237 self.
logger.warning(
'No materials to write')
240 with open(filename,
'w')
as json_file:
249 @brief Utility to skip library elements when iterating over elements in a document.
250 @return True if the element is not in a library, otherwise False.
252 return not elem.hasSourceUri()
254 def _getMethodName(self):
255 frame = inspect.currentframe().f_back
256 method_name = frame.f_code.co_name
262 @brief Validate the MaterialX document
263 @param doc The MaterialX document to validate
264 @return A tuple of (valid, errors) where valid is True if the document is valid, and errors is a list of errors if the document is invalid.
267 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
271 self.
logger.warning(f
'> {self._getMethodName()}: MaterialX document is required')
274 valid, errors = doc.validate()
279 @brief Add a comment to the MaterialX document
280 @param doc The MaterialX document to add the comment to
281 @param commentString The comment string to add
284 comment = doc.addChildOfCategory(
'comment')
285 comment.setDocString(commentString)
288 remapKeys = {}, shaderPreFix ='') -> mx.Document:
290 @brief Convert the Physically Based Materials to MaterialX format for a given target shading model.
291 @param materialNames The list of material names to convert. If empty, all materials will be converted.
292 @param shaderCategory The target shading model to convert to. Default is 'standard_surface'.
293 @param remapKeys The remapping keys for the target shading model. If empty, the default remapping keys will be used.
294 @param shaderPreFix The prefix to add to the shader name. Default is an empty string.
295 @return The MaterialX document
298 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
302 self.
logger.warning(f
'> OpenPBR shading model not supported in MaterialX version {self.mx.getVersionString()}')
306 self.
logger.info(
'> No materials to convert')
309 if len(remapKeys) == 0:
311 if len(remapKeys) == 0:
312 self.
logger.warning(f
'> No remapping keys found for shading model: {shaderCategory}')
315 self.
doc = self.
mxmx.createDocument()
322 self.
addComment(self.
doc,
'Physically Based Materials from https://api.physicallybased.info ')
323 self.
addComment(self.
doc,
' Processsed via API and converted to MaterialX ')
324 self.
addComment(self.
doc,
' Target Shading Model: ' + shaderCategory)
325 self.
addComment(self.
doc,
' Utility Author: Bernard Kwok. kwokcb@gmail.com ')
329 matName = mat[
'name']
332 if len(materialNames) > 0
and matName
not in materialNames:
336 if (len(shaderPreFix) > 0):
337 matName = matName +
'_' + shaderPreFix
339 shaderName = self.
doc.createValidChildName(matName +
'_SHD_PBM')
340 self.
addComment(self.
doc,
' Generated shader: ' + shaderName +
' ')
341 shaderNode = self.
doc.addNode(shaderCategory, shaderName, self.
mxmx.SURFACE_SHADER_TYPE_STRING)
342 docString = mat[
'description']
343 refString = mat[
'reference']
344 if len(refString) > 0:
345 if len(docString) > 0:
347 docString +=
'Reference: ' + refString[0]
348 if len(docString) > 0:
349 shaderNode.setDocString(docString)
354 materialName = self.
doc.createValidChildName(matName +
'_MAT_PBM')
355 self.
addComment(self.
doc,
' Generated material: ' + materialName +
' ')
356 materialNode = self.
doc.addNode(self.
mxmx.SURFACE_MATERIAL_NODE_STRING, materialName, self.
mxmx.MATERIAL_TYPE_STRING)
357 shaderInput = materialNode.addInput(self.
mxmx.SURFACE_SHADER_TYPE_STRING, self.
mxmx.SURFACE_SHADER_TYPE_STRING)
361 skipKeys = [
'name',
"density",
"category",
"description",
"sources",
"tags",
"reference"]
367 for key, value
in mat.items():
369 if (key
not in skipKeys):
370 if key ==
'metalness':
372 if key ==
'roughness':
374 if key ==
'transmission':
381 input = shaderNode.addInputFromNodeDef(key)
384 if isinstance(value, list):
385 value =
','.join([str(x)
for x
in value])
387 elif isinstance(value, (int, float)):
389 input.setValueString(value)
393 if (transmission !=
None)
and (metallness !=
None)
and (roughness !=
None)
and (color !=
None):
394 if (metallness == 0)
and (roughness == 0):
395 if 'transmission_color' in remapKeys:
396 key = remapKeys[
'transmission_color']
397 input = shaderNode.addInputFromNodeDef(key)
399 self.
logger.debug(f
'Set transmission color {key}: {color}')
400 value =
','.join([str(x)
for x
in color])
401 input.setValueString(value)
407 @brief Write the MaterialX document to disk
408 @param filename The filename to write the MaterialX document to
412 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
415 writeOptions = self.
mxmx.XmlWriteOptions()
416 writeOptions.writeXIncludeEnable =
False
418 self.
mxmx.writeToXmlFile(self.
doc, filename, writeOptions)
422 @brief Convert the MaterialX document to a string
423 @return The MaterialX document as a string
426 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
429 writeOptions = self.
mxmx.XmlWriteOptions()
430 writeOptions.writeXIncludeEnable =
False
432 mtlx = self.
mxmx.writeToXmlString(self.
doc, writeOptions)
Class to load Physically Based Materials from the PhysicallyBased site.
dict getInputRemapping(self, shadingModel)
Get the remapping keys for a given shading model.
list getJSONMaterialNames(self)
Get the list of material names from the JSON object.
dict loadMaterialsFromString(self, matString)
Load the Physically Based Materials from a JSON string.
doc
MaterialX document used for conversion.
dict loadMaterialsFromFile(self, fileName)
Load the Physically Based Materials from a JSON file.
str uri
Root URI for the PhysicallyBased site.
writeMaterialXToFile(self, filename)
Write the MaterialX document to disk.
dict materials
Materials list.
bool support_openpbr
OpenPBR support flag.
validateMaterialXDocument(self, doc)
Validate the MaterialX document.
__init__(self, mx_module, Optional[mx.Document] mx_stdlib=None)
Constructor for the PhysicallyBasedMaterialLoader class.
convertToMaterialXString(self)
Convert the MaterialX document to a string.
setDebugging(self, debug=True)
Set the debugging level for the logger.
writeJSONToFile(self, filename)
Write the materials to a JSON file.
dict getMaterialsFromURL(self)
Get the Physically Based Materials from the PhysicallyBased site.
initializeInputRemapping(self)
Initialize remapping keys for different shading models.
stdlib
MaterialX standard library.
str MTLX_NODE_NAME_ATTRIBUTE
MaterialX node name attribute.
dict getJSON(self)
Get the JSON object representing the Physically Based Materials.
mx.Document getMaterialXDocument(self)
Get the MaterialX document.
printMaterials(self)
Print the materials to the console.
bool skipLibraryElement(elem)
Utility to skip library elements when iterating over elements in a document.
mx.Document convertToMaterialX(self, materialNames=[], shaderCategory='standard_surface', remapKeys={}, shaderPreFix='')
Convert the Physically Based Materials to MaterialX format for a given target shading model.
addComment(self, doc, commentString)
Add a comment to the MaterialX document.
list materialNames
Material names.