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.
25 self.
logger = lg.getLogger(
'PBMXLoader')
26 lg.basicConfig(level=lg.INFO)
30 self.
uri =
'https://api.physicallybased.info/materials'
38 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module not specified.')
42 version_major, version_minor, version_patch = self.
mxmx.getVersionIntegers()
43 self.
logger.debug(f
'> MaterialX version: {version_major}.{version_minor}.{version_patch}')
44 if (version_major >=1
and version_minor >= 39)
or version_major > 1:
45 self.
logger.debug(
'> OpenPBR shading model supported')
53 libFiles = self.
mxmx.loadLibraries(mx.getDefaultDataLibraryFolders(), mx.getDefaultDataSearchPath(), self.
stdlib)
54 self.
logger.debug(f
'> Loaded standard library: {libFiles}')
58 @brief Set the debugging level for the logger.
59 @param debug True to set the logger to debug level, otherwise False.
63 self.
logger.setLevel(lg.DEBUG)
65 self.
logger.setLevel(lg.INFO)
69 @brief Get the remapping keys for a given shading model.
70 @param shadingModel The shading model to get the remapping keys for.
71 @return A dictionary of remapping keys.
76 self.
logger.warn(f
'> No remapping keys found for shading model: {shadingModel}')
81 @brief Initialize remapping keys for different shading models.
82 The currently supported shading models are:
89 standard_surface_remapKeys = {
90 'color':
'base_color',
91 'specularColor':
'specular_color',
92 'roughness':
'specular_roughness',
94 'ior':
'specular_IOR',
96 'transmission_color':
'transmission_color',
97 'thinFilmIor' :
'thin_film_IOR',
98 'thinFilmThickness' :
'thin_film_thickness',
99 'transmissionDispersion' :
'transmission_dispersion',
103 openpbr_remapKeys = {
104 'color':
'base_color',
105 'specularColor':
'specular_color',
106 'roughness':
'specular_roughness',
107 'metalness':
'base_metalness',
108 'ior':
'specular_ior',
109 'transmission':
'transmission_weight',
110 'transmission_color':
'transmission_color',
111 'subsurfaceRadius':
'subsurface_radius',
112 'thinFilmIor' :
'thin_film_ior',
113 'thinFilmThickness' :
'thin_film_thickness',
114 'transmissionDispersion' :
'transmission_dispersion_scale',
118 'color':
'base_color',
119 'specularColor':
'specular_color',
120 'roughness':
'roughness',
121 'metalness':
'metallic',
122 'transmission_color':
'attenuation_color',
128 self.
remapMap[
'standard_surface'] = standard_surface_remapKeys;
129 self.
remapMap[
'gltf_pbr'] = gltf_remapKeys;
131 self.
remapMap[
'open_pbr_surface'] = openpbr_remapKeys;
134 ''' Get the JSON object representing the Physically Based Materials '''
139 Get the list of material names from the JSON object
140 @return The list of material names
146 Get the MaterialX document
147 @return The MaterialX document
153 @brief Load the Physically Based Materials from a JSON file
154 @param fileName The filename to load the JSON file from
155 @return The JSON object representing the Physically Based Materials
159 if not os.path.exists(fileName):
160 self.
logger.error(f
'> File does not exist: {fileName}')
163 with open(fileName,
'r')
as json_file:
172 @brief Load the Physically Based Materials from a JSON string
173 @param matString The JSON string to load the Physically Based Materials from
174 @return The JSON object representing the Physically Based Materials
186 @brief Get the Physically Based Materials from the PhysicallyBased site
187 @return The JSON object representing the Physically Based Materials
194 'Accept':
'application/json'
197 response = requests.get(url, headers=headers)
199 if response.status_code == HTTPStatus.OK:
205 self.
logger.error(f
'> Status: {response.status_code}, {response.text}')
211 @brief Print the materials to the console
215 self.
logger.info(
'Material name: ' + mat[
'name'])
217 for key, value
in mat.items():
218 if (key !=
'name' and value):
219 self.
logger.info(f
'> - {key}: {value}')
223 @brief Write the materials to a JSON file
224 @param filename The filename to write the JSON file to
225 @return True if the file was written successfully, otherwise False
228 self.
logger.warning(
'No materials to write')
231 with open(filename,
'w')
as json_file:
240 @brief Utility to skip library elements when iterating over elements in a document.
241 @return True if the element is not in a library, otherwise False.
243 return not elem.hasSourceUri()
245 def _getMethodName(self):
246 frame = inspect.currentframe().f_back
247 method_name = frame.f_code.co_name
253 @brief Validate the MaterialX document
254 @param doc The MaterialX document to validate
255 @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.
258 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
262 self.
logger.warning(f
'> {self._getMethodName()}: MaterialX document is required')
265 valid, errors = doc.validate()
270 @brief Add a comment to the MaterialX document
271 @param doc The MaterialX document to add the comment to
272 @param commentString The comment string to add
275 comment = doc.addChildOfCategory(
'comment')
276 comment.setDocString(commentString)
279 remapKeys = {}, shaderPreFix ='') -> mx.Document:
281 @brief Convert the Physically Based Materials to MaterialX format for a given target shading model.
282 @param materialNames The list of material names to convert. If empty, all materials will be converted.
283 @param shaderCategory The target shading model to convert to. Default is 'standard_surface'.
284 @param remapKeys The remapping keys for the target shading model. If empty, the default remapping keys will be used.
285 @param shaderPreFix The prefix to add to the shader name. Default is an empty string.
286 @return The MaterialX document
289 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
293 self.
logger.warning(f
'> OpenPBR shading model not supported in MaterialX version {self.mx.getVersionString()}')
297 self.
logger.info(
'> No materials to convert')
300 if len(remapKeys) == 0:
302 if len(remapKeys) == 0:
303 self.
logger.warning(f
'> No remapping keys found for shading model: {shaderCategory}')
306 self.
doc = self.
mxmx.createDocument()
313 self.
addComment(self.
doc,
'Physically Based Materials from https://api.physicallybased.info ')
314 self.
addComment(self.
doc,
' Processsed via API and converted to MaterialX ')
315 self.
addComment(self.
doc,
' Target Shading Model: ' + shaderCategory)
316 self.
addComment(self.
doc,
' Utility Author: Bernard Kwok. kwokcb@gmail.com ')
320 matName = mat[
'name']
323 if len(materialNames) > 0
and matName
not in materialNames:
327 if (len(shaderPreFix) > 0):
328 matName = matName +
'_' + shaderPreFix
330 shaderName = self.
doc.createValidChildName(matName +
'_SHD_PBM')
331 self.
addComment(self.
doc,
' Generated shader: ' + shaderName +
' ')
332 shaderNode = self.
doc.addNode(shaderCategory, shaderName, self.
mxmx.SURFACE_SHADER_TYPE_STRING)
333 docString = mat[
'description']
334 refString = mat[
'reference']
335 if len(refString) > 0:
336 if len(docString) > 0:
338 docString +=
'Reference: ' + refString[0]
339 if len(docString) > 0:
340 shaderNode.setDocString(docString)
345 materialName = self.
doc.createValidChildName(matName +
'_MAT_PBM')
346 self.
addComment(self.
doc,
' Generated material: ' + materialName +
' ')
347 materialNode = self.
doc.addNode(self.
mxmx.SURFACE_MATERIAL_NODE_STRING, materialName, self.
mxmx.MATERIAL_TYPE_STRING)
348 shaderInput = materialNode.addInput(self.
mxmx.SURFACE_SHADER_TYPE_STRING, self.
mxmx.SURFACE_SHADER_TYPE_STRING)
352 skipKeys = [
'name',
"density",
"category",
"description",
"sources",
"tags",
"reference"]
358 for key, value
in mat.items():
360 if (key
not in skipKeys):
361 if key ==
'metalness':
363 if key ==
'roughness':
365 if key ==
'transmission':
372 input = shaderNode.addInputFromNodeDef(key)
375 if isinstance(value, list):
376 value =
','.join([str(x)
for x
in value])
378 elif isinstance(value, (int, float)):
380 input.setValueString(value)
384 if (transmission !=
None)
and (metallness !=
None)
and (roughness !=
None)
and (color !=
None):
385 if (metallness == 0)
and (roughness == 0):
386 if 'transmission_color' in remapKeys:
387 key = remapKeys[
'transmission_color']
388 input = shaderNode.addInputFromNodeDef(key)
390 self.
logger.debug(f
'Set transmission color {key}: {color}')
391 value =
','.join([str(x)
for x
in color])
392 input.setValueString(value)
398 @brief Write the MaterialX document to disk
399 @param filename The filename to write the MaterialX document to
403 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
406 writeOptions = self.
mxmx.XmlWriteOptions()
407 writeOptions.writeXIncludeEnable =
False
409 self.
mxmx.writeToXmlFile(self.
doc, filename, writeOptions)
413 @brief Convert the MaterialX document to a string
414 @return The MaterialX document as a string
417 self.
logger.critical(f
'> {self._getMethodName()}: MaterialX module is required')
420 writeOptions = self.
mxmx.XmlWriteOptions()
421 writeOptions.writeXIncludeEnable =
False
423 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.
dict loadMaterialsFromFile(self, fileName)
Load the Physically Based Materials from a JSON file.
writeMaterialXToFile(self, filename)
Write the MaterialX document to disk.
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.
str MTLX_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.