MaterialXMaterials 0.0.3
Utilities for retrieving materials from remote servers
Loading...
Searching...
No Matches
JsMaterialXPhysicallyBased.js
1let MTLX_NODE_NAME_ATTRIBUTE = 'nodename';
2
13 url = '';
18 headers = {};
23 materials = null;
38 mx = null;
43 doc = null;
48 stdlib = null;
54
60 constructor(mtlx_module = null, mtlx_stdlib = null)
61 {
62 this.url = 'https://api.physicallybased.info/materials';
63 this.headers = { 'Accept': 'application/json' };
64
65 this.materials = null;
66 this.materialNames = [];
67
68 this.mxMaterialNames = [];
69 this.mx = null;
70 if (mtlx_module) {
71 this.mx = mtlx_module;
72 }
73 this.stdlib = null;
74 if (mtlx_stdlib) {
75 this.stdlib = mtlx_stdlib;
76 }
77 this.doc = null;
78
80 }
81
87 {
88 return this.materials
89 }
90
95 {
96 return this.materialNames
97 }
98
103 {
104 return this.doc;
105 }
106
112 {
113 if (this.doc) {
114 let errors = {}
115 let errorString = ''
116 var valid = this.doc.validate(errors);
117 if (!valid) {
118 errorString = errors.message;
119 }
120 return [valid, errorString]
121 }
122 return [false, 'No MaterialX document'];
123 }
124
130 getInputRemapping(shadingModel)
131 {
132 if (shadingModel in this.remapMap) {
133 return this.remapMap[shadingModel];
134 }
135 return {};
136 }
137
143 {
144 const standard_surface_remapKeys = {
145 "color": "base_color",
146 "specularColor": "specular_color",
147 "roughness": "specular_roughness",
148 "metalness": "metalness",
149 "ior": "specular_IOR",
150 "subsurfaceRadius": "subsurface_radius",
151 "transmission": "transmission",
152 "transmission_color": "transmission_color",
153 "transmissionDispersion": "transmission_dispersion",
154 "thinFilmThickness": "thin_film_thickness",
155 "thinFilmIor": "thin_film_IOR"
156 };
157
158 const openpbr_remapKeys = {
159 "color": "base_color",
160 "specularColor": "specular_color",
161 "roughness": "specular_roughness",
162 "metalness": "base_metalness",
163 "ior": "specular_ior",
164 "subsurfaceRadius": "subsurface_radius",
165 "transmission": "transmission_weight",
166 "transmission_color": "transmission_color",
167 "transmissionDispersion": "transmission_dispersion_abbe_number",
168 "thinFilmThickness": "thin_film_thickness",
169 "thinFilmIor": "thin_film_ior"
170 };
171
172 const gltf_remapKeys = {
173 "color": "base_color",
174 "specularColor": "specular_color",
175 "roughness": "roughness",
176 "metalness": "metallic",
177 "ior": "ior",
178 "transmission": "transmission",
179 "transmission_color": "attenuation_color",
180 "thinFilmThickness": "iridescence_thickness",
181 "thinFilmIor": "iridescence_ior"
182 };
183
184 this.remapMap = {
185 'standard_surface': standard_surface_remapKeys,
186 'gltf_pbr': gltf_remapKeys,
187 'open_pbr_surface': openpbr_remapKeys
188 };
189 }
190
191
197 {
198 console.log('Initializing input remapping for Physically Based Materials...');
199 this.remapMap = null;
200
201 const remapKeyURL = 'https://raw.githubusercontent.com/kwokcb/materialxMaterials/refs/heads/main/src/materialxMaterials/data/PhysicallyBasedMaterialX/PhysicallyBasedToMtlxMappings.json';
202
203 fetch(remapKeyURL)
204 .then((response) =>
205 {
206 if (!response.ok) {
207 console.warn(`HTTP error! Status: ${response.status}`);
208 return null;
209 }
210 return response.json();
211 })
212 .then((data) => {
213 if (data) {
214 this.remapMap = data;
215 console.log('- Remap keys loaded from repo:', this.remapMap);
216 } else {
217 console.warn('- No remap keys from repo. Using default remap keys.');
218 this.setDefaultRemapKeys();
219 }
220 })
221 .catch((error) => {
222 console.log('- Error loading remap keys:', error);
223 this.setDefaultRemapKeys();
224 console.warn('- Using default remap keys.', this.remapMap);
225 });
226 }
227
233 {
234 return new Promise((resolve, reject) => {
235 MaterialX().then((mtlx) => {
236 this.mx = mtlx;
237 resolve();
238 }).catch((error) => {
239 reject(error);
240 });
241 });
242 }
243
249 {
250 try {
251 this.materials = null
252 this.materialNames = [];
253
254 const response = await fetch(this.url, {
255 method: 'GET',
256 headers: this.headers
257 });
258
259 if (!response.ok) {
260 throw new Error('Network response was not ok ' + response.statusText);
261 }
262
263 this.materials = await response.json();
264 for (let i = 0; i < this.materials.length; i++) {
265 this.materialNames.push(this.materials[i]['name']);
266 }
267 return this.materials;
268 } catch (error) {
269 console.error('There has been a problem with your fetch operation:', error);
270 }
271
272 return null;
273 }
274
280 {
281 if (!this.mx) {
282 // Call the asynchronous function and then perform additional logic
283 this.loadMaterialX().then(() => {
284
285 this.esslgenerator = new this.mx.EsslShaderGenerator();
286 this.esslgenContext = new this.mx.GenContext(this.esslgenerator);
287 this.stdlib = this.mx.loadStandardLibraries(this.esslgenContext);
288 let children = this.stdlib.getChildren();
289 for (let i = 0; i < children.length; i++) {
290 let child = children[i];
291 child.setSourceUri('STDLIB_ELEMENT');
292 }
293
294 console.log("MaterialX is loaded");
295 }).catch((error) => {
296 console.error("Error loading MaterialX:", error);
297 });
298 }
299 }
300
307 {
308 return !elem.hasSourceUri()
309 }
310
316 {
317 if (!this.doc) {
318 console.error('No MaterialX document to convert');
319 return '';
320 }
321
322 // Create write options
323 const writeOptions = new this.mx.XmlWriteOptions();
324 writeOptions.writeXIncludeEnable = false;
325 //writeOptions.writeXIncludes = false;
326 writeOptions.elementPredicate = this.skipLibraryElement;
327
328 // Convert the MaterialX document to a string
329 const mtlx = this.mx.writeToXmlString(this.doc, writeOptions);
330 return mtlx;
331 }
332
338 addComment(doc, commentString)
339 {
340 let comment = doc.addChildOfCategory('comment')
341 comment.setDocString(commentString)
342 }
343
344
355 convertToMaterialX(shaderCategory, references, addAllInputs = false, materialNames = [], remapKeys = {}, shaderPreFix = '')
356 {
357 if (!this.mx) {
358 console.error('MaterialX module is not loaded');
359 return false;
360 }
361
362 if (!this.materials) {
363 console.warn('No Physically Based Materials to convert');
364 return false;
365 }
366
367 if (remapKeys.length == 0) {
368 remapKeys = this.getInputRemapping(shaderCategory);
369 }
370
371 // Create a dummy doc with the surface shader with all inputs
372 // as reference
373 let refDoc = this.mx.createDocument();
374 refDoc.importLibrary(this.stdlib);
375 const refNode = refDoc.addNode(shaderCategory, 'refShader', this.mx.SURFACE_SHADER_TYPE_STRING);
376 //refNode.addInputsFromNodeDef() -- This is missing from the JS API.
377 this.doc = this.mx.createDocument();
378
379 // Add document level accreditation
380 let docString = 'Physically Based Materials from https://api.physicallybased.info.\n'
381 docString += ' Content Author: Anton Palmqvist, https://antonpalmqvist.com/ \n'
382 docString += ` Content processsed via REST API and mapped to MaterialX V${this.mx.getVersionString()} \n`
383 docString += ` Target Shading Model: ${shaderCategory} \n`
384 docString += ' Utility Author: Bernard Kwok. kwokcb@gmail.com '
385 this.doc.setDocString(docString);
386
387 // Add properties to the material
388 for (let i = 0; i < this.materials.length; i++) {
389 const mat = this.materials[i];
390 let matName = mat['name'];
391
392 // Filter by material name(s)
393 if (materialNames.length > 0 && !materialNames.includes(matName)) {
394 // Skip material
395 console.log('Skipping material:', matName);
396 continue;
397 }
398
399
400 if (shaderPreFix.length > 0) {
401 matName = shaderPreFix + '_' + matName;
402 }
403
404 const shaderName = this.doc.createValidChildName('SPB_' + matName + '_' + shaderCategory);
405 this.addComment(this.doc, ' Generated shader: ' + matName + ' ');
406 const shaderNode = this.doc.addNode(shaderCategory, shaderName, this.mx.SURFACE_SHADER_TYPE_STRING);
407
408 const category = mat['category'];
409 const group = mat['group'];
410 let uifolder = '';
411 if (category && category.length > 0) {
412 uifolder = category[0];
413 }
414 if (group && group.length > 0) {
415 if (uifolder.length > 0) {
416 uifolder += '/';
417 }
418 uifolder += group[0];
419 }
420 if (uifolder.length > 0) {
421 shaderNode.setAttribute('uifolder', uifolder);
422 }
423
424 let docString = ''
425 if (mat['description'].length > 0) {
426 docString += 'Description: ' + mat['description'];
427 }
428 const refString = mat['reference'];
429 if (refString.length > 0) {
430 if (docString.length > 0) {
431 docString += '. ';
432 }
433 docString += 'Reference: ' + refString[0];
434
435 let referenceItem = { name: matName, reference: refString[0] };
436 console.log('Add Reference:', referenceItem);
437 references.push(referenceItem);
438 }
439 if (docString.length > 0) {
440 shaderNode.setDocString(docString);
441 }
442
443 // Create a new material
444 const materialName = this.doc.createValidChildName('MPB_' + matName + '_' + shaderCategory);
445 this.addComment(this.doc, ' Generated material: ' + matName + ' ');
446 const materialNode = this.doc.addNode(this.mx.SURFACE_MATERIAL_NODE_STRING, materialName, this.mx.MATERIAL_TYPE_STRING);
447 const shaderInput = materialNode.addInput(this.mx.SURFACE_SHADER_TYPE_STRING, this.mx.SURFACE_SHADER_TYPE_STRING);
448 shaderInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, shaderNode.getName());
449
450 // Warning this is a bit bespoke for remapping keys
451 // to Autodesk Standard Surface shader inputs
452 const skipKeys = ['name', "density", "category", "description", "sources", "tags", "reference"];
453
454 let metallness = null;
455 let roughness = null;
456 let transmission_color = null;
457 let transmission = null;
458 Object.entries(mat).forEach(([key, value]) => {
459
460 if (!skipKeys.includes(key)) {
461
462 if (key == 'metalness') {
463 metallness = value;
464 //console.log('Metalness:', metallness);
465 }
466 if (key == 'roughness') {
467 roughness = value;
468 //console.log('Roughness:', roughness);
469 }
470 if (key == 'transmission') {
471 transmission = value;
472 //console.log('Transmission:', transmission);
473 }
474 if (key == 'color') {
475 transmission_color = value;
476 //console.log('Color:', color);
477 }
478
479 if (remapKeys[key]) {
480 key = remapKeys[key];
481 }
482
483 let refInput = refNode.getInput(key);
484 if (!refInput)
485 refInput = refNode.addInputFromNodeDef(key);
486 if (refInput) {
487 const input = shaderNode.addInput(key);
488 input.copyContentFrom(refInput);
489 if (input) {
490 // Convert number vector to string
491 if (Array.isArray(value)) {
492 value = value.join(',');
493 }
494 // Convert number to string
495 else if (typeof value === 'number') {
496 value = value.toString();
497 }
498 // Note: This API has side-effects as the
499 // type is set to "string" when the value is set. Thus
500 // we must explicitly set the type here.
501 input.setValueString(value, refInput.getType());
502 }
503 }
504 else {
505 //console.log('>>> Cannot create input:', key)
506 }
507 }
508 });
509
510 if (transmission !== null && metallness !== null && roughness !== null && transmission_color !== null)
511 {
512 if (metallness == 0 && roughness == 0)
513 {
514 if (remapKeys['transmission_color']) {
515 let inputName = remapKeys['transmission_color'];
516 let input = shaderNode.addInput(inputName);
517 if (input) {
518 let value = transmission_color.join(',');
519 //console.log(`Add "${inputName}": "${value}"`);
520 input.setValueString(value, 'color3');
521 }
522 }
523 }
524 };
525 }
526 return true;
527 }
528
529}
Javascript class for querying materials from the Physically Based database and creating MaterialX mat...
skipLibraryElement(element)
Predicate to skip library elements.
getJSONMaterialNames()
Get list of the Physically Based Material names.
initializeInputRemapping()
Initialize the input remapping for different shading models.
getInputRemapping(shadingModel)
Get the remapping keys for a given shading model.
getJSON()
Get the Physically Based Materials as JSON.
getMaterialXDocument()
Get the MaterialX document.
remapMap
Remap keys for input values for different shading models.
materials
List of Physically Based Materials.
loadStandardLibraries()
Load the MaterialX standard libraries.
constructor(mtlx_module=null, mtlx_stdlib=null)
Constructor for the PhysicallyBasedMaterialLoader.
getMaterialXString()
Get the MaterialX document as a string.
setDefaultRemapKeys()
Set the default remapping keys for different shading models : glTF, OpenPBR, and Autodesk Standard Su...
async getPhysicallyBasedMaterials()
Get the Physically Based Materials from the API.
materialNames
List of Physically Based Material names.
mxMaterialNames
List of MaterialX Material names.
headers
Headers for the fetch operation.
url
URL to fetch the Physically Based Materials.
addComment(doc, commentString)
Add a comment to the MaterialX document.
validateDocument()
Validate the MaterialX document.
convertToMaterialX(shaderCategory, references, addAllInputs=false, materialNames=[], remapKeys={}, shaderPreFix='')
Convert the Physically Based Materials to MaterialX.