MaterialXMaterials 1.39.5
Utilities for retrieving materials from remote servers
Loading...
Searching...
No Matches
physicallyBasedMaterialXCmd.py
1#/usr/bin/env python3
2'''
3@brief Command to convert Physically Based Materials to MaterialX Command Line Utility
4@details This script converts Physically Based Materials to MaterialX using the MaterialX Python API.
5@details The script can be run from the command line with the following options:
6@details --shadingModel: Shading models to use for conversion. If not specified then all will be used.
7@details Options: standard_surface, gltf_pbr, open_pbr_surface
8@details --outputDir: Output directory for MaterialX files
9@details --writeJSON: Write materials JSON file. Default is True
10@details --separateFiles: Convert individual MaterialX files per material. Default is false
11@details Example usage:
12@details - python physicallyBasedMaterialXCmd.py
13@details - python physicallyBasedMaterialXCmd.py --outputDir=myfolder
14@details - python physicallyBasedMaterialXCmd.py --writeJSON=False
15@details - python physicallyBasedMaterialXCmd.py --shadingModel=gltf_pbr,open_pbr_surface
16@details - python physicallyBasedMaterialXCmd.py --shadingModel=open_pbr_surface --separateFiles=True
17'''
18from pydoc import doc
19from unittest import loader
20import os, sys, argparse, logging
21from venv import logger
22
23import MaterialX as mx # type: ignore
24import physicallyBasedMaterialX as pbmx
25
27 '''
28 Command to parse PhysicallyBased materials and create MaterialX materials
29 '''
30 logger = logging.getLogger('PB_CMD')
31 logging.basicConfig(level=logging.INFO)
32
33 parser = argparse.ArgumentParser(description='Convert Physically Based Materials to MaterialX')
34 parser.add_argument('-m', '--shadingModel', type=str, default='', help='Shading models to use for conversion. '
35 ' If not specified then all will be used. '
36 ' Options: standard_surface, gltf_pbr, open_pbr_surface')
37 parser.add_argument('-o', '--outputDir', type=str, default='',
38 help='Output directory for MaterialX files. Default location is PhysicallyBasedMaterialX')
39 parser.add_argument('-j', '--writeJSON', type=bool, default=True,
40 help='Write materials JSON file. Default is True')
41 parser.add_argument('-s', '--separateFiles', type=bool, default=False,
42 help='Convert individual MaterialX files per material. Default is false')
43 parser.add_argument('-l', '--loadFromFile', type=str, default='', help='Load materials a specified file')
44 parser.add_argument('-wr', '--writeRemapping', type=bool, default=False, help='Write remapping from PhysicallyBased to MaterialX. Default is False')
45 parser.add_argument('-rr', '--readRemapping', type=str, default='', help='Read remapping from PhysicallyBased to MaterialX. Default is empty')
46 parser.add_argument('-nd', '--createNodeDef', type=bool, default=False, help='Create NodeDef for Physically Based Material inputs. Default is False')
47
48 # V2_TODO : Expose this argument when all materials support all colorspaces.
49 support_colorspaces = False
50 if support_colorspaces:
51 parser.add_argument('-cs', '--colorspace', type=str, default='', help='Write colors using this color space. Default is to use srgb-linear = lin_rec709. Options incllude: srgb-linear, acescg')
52
53 opts = parser.parse_args()
54
55 outputDir = 'PhysicallyBasedMaterialX'
56 if opts.outputDir:
57 if not os.path.exists(opts.outputDir):
58 logger.info(f'Error: Output directory does not exist: {opts.outputDir}')
59 # Create the output directory
60 try:
61 os.makedirs(opts.outputDir)
62 logger.info(f'> Created output directory: {opts.outputDir}')
63 outputDir = opts.outputDir
64 except Exception as e:
65 logger.info(f'> Error: Could not create output directory: {opts.outputDir}')
66 sys.exit(1)
67 else:
68 outputDir = opts.outputDir
69
70 shadingModels = []
71 if opts.shadingModel:
72 shadingModels = opts.shadingModel.split(',')
73 shadingModePrefixMap = { 'standard_surface': 'SS', 'gltf_pbr': 'GLTF', 'open_pbr_surface': 'OPBR' }
74 shadingModelPrefixes = []
75 if len(shadingModels) == 0:
76 shadingModels = ['standard_surface', 'gltf_pbr', 'open_pbr_surface']
77 shadingModelPrefixes = ['SS', 'GLTF', 'OPBR']
78 else:
79 for shadingModel in shadingModels:
80 shadingModelPrefixes.append(shadingModePrefixMap[shadingModel])
81
82 writeJSON = opts.writeJSON
83 separateFiles = opts.separateFiles
84
85 material_file = opts.loadFromFile
86 if material_file:
87 if not os.path.exists(opts.loadFromFile):
88 logger.info(f'> Error: File does not exist: {material_file}')
89 sys.exit(1)
90 logger.info(f'> Load materials from file: {material_file}')
91
92 # Create loader and get PhysicallyBasedMaterials
93 # Uses default remapping.
94 loader = pbmx.PhysicallyBasedMaterialLoader(mx, None, material_file)
95 if support_colorspaces and opts.colorspace:
96 loader.set_desired_color_space(opts.colorspace)
97
98 readRemapping = opts.readRemapping
99 if readRemapping:
100 if not os.path.exists(readRemapping):
101 logger.info(f'> Error: Remapping file does not exist: {readRemapping}')
102 logger.info(f'> Read remapping file: {readRemapping}')
103 loader.readRemappingFile(readRemapping)
104 else:
105 writeRemapping = opts.writeRemapping
106 if writeRemapping:
107 outputFile = os.path.join(outputDir, 'PhysicallyBasedToMtlxMappings.json')
108 logger.info(f'> Write remapping file: {outputFile}')
109 loader.writeRemappingFile(outputFile)
110
111 jsonMat = loader.getJSON()
112 if jsonMat:
113
114 # Create folder for MaterialX call PhysicallyBasedMaterialX
115 os.makedirs(outputDir, exist_ok=True)
116
117 create_nodedef = opts.createNodeDef
118 if create_nodedef:
119
120 # Write PhysicallyBased BSDF definition
121 #
122 logger.info('> Write definition for PhysicallyBased materials')
123 definitions_doc = loader.get_physlib()
124 if definitions_doc:
125 nodedef_file_name = os.path.join(outputDir, 'physbased_pbr.mtlx')
126 mx.writeToXmlFile(definitions_doc, nodedef_file_name)
127 logger.info(f'> Write definition file: {nodedef_file_name}')
128
129 # Get PhysicallyBased materials using the definition
130 #
131 doc_mat = loader.get_physlib_materials()
132 if doc_mat:
133 status, error = doc_mat.validate()
134 if not status:
135 logger.error('> Error validating definition materials document:')
136 logger.error(error)
137 else:
138 logger.info('> Definition materials document passed validation.')
139
140 nodedef_mat_file_name = os.path.join(outputDir, 'physbased_pbr_materials.mtlx')
141 mx.writeToXmlFile(doc_mat, mx.FilePath(nodedef_mat_file_name))
142 logger.info(f'> Write materials file: {nodedef_mat_file_name}')
143
144 # Write out translator definitions
145 #
146 translators_doc = loader.get_translators()
147 translators = translators_doc.getNodeDefs()
148 print(f'Found translator definitions: {len(translators)}')
149
150 if translators_doc and translators:
151 output_file_name = 'physbased_pbr_translators.mtlx'
152 output_path = os.path.join(outputDir, output_file_name)
153 logger.info('> Write translator file:' + output_path)
154 mx.writeToXmlFile(translators_doc, mx.FilePath(output_path))
155 logger.info(f'> Write translator file: {output_path}')
156
157 # Get doc with all required definitions: stdlib, PhysicallyBased definition, and translator definitions
158 stdlib = loader.get_definitions()
159
160 # Export single file with all translated materials
161 if not separateFiles:
162 translated_doc = loader.get_physlib_materials()
163
164 # Copy over materials + reference libraries
165 #translated_doc.copyContentFrom(doc_mat)
166 #translated_doc.setDataLibrary(doc_mat.getDataLibrary())
167
168 for shadingModel, prefix in zip(shadingModels, shadingModelPrefixes):
169
170 for node in translated_doc.getNodes():
171 if node.getCategory() == loader.get_physlib_category():
172 trans_result = loader.translate_node(translated_doc, loader.get_physlib_category(), shadingModel, node)
173
174 logger.info(f'> Generate MaterialX using nodedefs for shading model: {shadingModel}')
175 fileName = os.path.join(outputDir, f'PhysicallyBasedMaterialX_translated_{prefix}.mtlx')
176 loader.writeMaterialXToFile(fileName, translated_doc)
177 logger.info(f'> Write: {fileName}')
178
179 # Export separate files per translated material
180 else:
181 matDir = os.path.join(outputDir, 'Sep')
182 os.makedirs(matDir, exist_ok=True)
183 for shadingModel, prefix in zip(shadingModels, shadingModelPrefixes):
184 converted = []
185 for mat in loader.getJSONMaterialNames():
186 materialFilter = [mat]
187
188 # Create doc with single material
189 matdoc = loader.create_definition_materials(None, materialFilter)
190 if matdoc is not None:
191
192 # Translate the material
193 mat_name = mx.createValidName(mat)
194 node = matdoc.getNode(mat_name)
195 if node:
196 trans_result = loader.translate_node(matdoc, loader.get_physlib_category(), shadingModel, node)
197 if not trans_result:
198 logger.warning(f'Failed to translate node: {mat_name} for shading model: {shadingModel}')
199 else:
200 converted.append(trans_result['targetNode'].getName())
201 valid, errors = loader.validateMaterialXDocument(matdoc)
202 if valid:
203 #logger.info(f'> Generate material {mat_name} for shading model: {shadingModel}')
204 fileName = os.path.join(matDir, f'PB_{prefix}_{mat}.mtlx')
205 loader.writeMaterialXToFile(fileName, matdoc)
206 logger.info(f'> Write: {fileName}')
207
208 logger.info(f'> Converted {len(converted)} materials for shading model: {shadingModel}')
209
210 if writeJSON:
211 logger.info(f'> Write PB material file: {outputDir}/PhysicallyBasedMaterial.json')
212 loader.writeJSONToFile(os.path.join(outputDir, 'PhysicallyBasedMaterial.json'))
213
214 if create_nodedef:
215 return
216
217 # Direct translation from JSON to MaterialX path. Skip if using PhysicallyBased nodedefs
218 if not separateFiles:
219 for shadingModel, prefix in zip(shadingModels, shadingModelPrefixes):
220 logger.info(f'> Generate MaterialX for shading model: {shadingModel}')
221 matdoc = loader.convertToMaterialX([], shadingModel, {}, prefix)
222 valid, errors = loader.validateMaterialXDocument(matdoc)
223 if not valid:
224 logger.error(f'> Error validating MaterialX document for shading model: {shadingModel}')
225 logger.error(errors)
226
227 if support_colorspaces and opts.colorspace:
228 prefix = prefix + '_' + opts.colorspace
229 fileName = os.path.join(outputDir, f'PhysicallyBasedMaterialX_{prefix}.mtlx')
230 loader.writeMaterialXToFile(fileName)
231 logger.info(f'> Write: {fileName}')
232
233 else:
234 for shadingModel, prefix in zip(shadingModels, shadingModelPrefixes):
235 logger.info(f'> Generate MaterialX for shading model: {shadingModel}')
236 for mat in loader.getJSONMaterialNames():
237 materialFilter = [mat]
238 matdoc = loader.convertToMaterialX(materialFilter, shadingModel, {}, prefix)
239 if matdoc is not None:
240 valid, errors = loader.validateMaterialXDocument(matdoc)
241 if not valid:
242 logger.error(f'> Error validating MaterialX document for material: {mat} shading model: {shadingModel}')
243 logger.error(errors)
244
245 if support_colorspaces and opts.colorspace:
246 prefix = prefix + '_' + opts.colorspace
247 fileName = os.path.join(outputDir, f'PB_{prefix}_{mat}.mtlx')
248 loader.writeMaterialXToFile(fileName)
249 logger.info(f'> Write: {fileName}')
250
251 else:
252 logger.info('Could not retrieve PhysicallyBased Materials')
253
254if __name__ == '__main__':
physicallBasedMaterialXCmd()
Command to parse PhysicallyBased materials and create MaterialX materials.