MaterialXLab API  0.0.1
APIs For MaterialXLab Libraries
Loading...
Searching...
No Matches
JsMaterialXPhysicallyBased.js
Go to the documentation of this file.
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 else
136 {
137 console.warn('>> No remap keys for shading model:', shadingModel);
138 }
139 return {};
140 }
141
147 {
148 return this.remapMap;
149 }
150
156 {
157 const standard_surface_remapKeys = {
158 "color": "base_color",
159 "specularColor": "specular_color",
160 "roughness": "specular_roughness",
161 "metalness": "metalness",
162 "ior": "specular_IOR",
163 "subsurfaceRadius": "subsurface_radius",
164 "transmission": "transmission",
165 "transmission_color": "transmission_color",
166 "transmissionDispersion": "transmission_dispersion",
167 "thinFilmThickness": "thin_film_thickness",
168 "thinFilmIor": "thin_film_IOR"
169 };
170
171 const openpbr_remapKeys = {
172 "color": "base_color",
173 "specularColor": "specular_color",
174 "roughness": "specular_roughness",
175 "metalness": "base_metalness",
176 "ior": "specular_ior",
177 "subsurfaceRadius": "subsurface_radius",
178 "transmission": "transmission_weight",
179 "transmission_color": "transmission_color",
180 "transmissionDispersion": "transmission_dispersion_abbe_number",
181 "thinFilmThickness": "thin_film_thickness",
182 "thinFilmIor": "thin_film_ior"
183 };
184
185 const gltf_remapKeys = {
186 "color": "base_color",
187 "specularColor": "specular_color",
188 "roughness": "roughness",
189 "metalness": "metallic",
190 "ior": "ior",
191 "transmission": "transmission",
192 "transmission_color": "attenuation_color",
193 "thinFilmThickness": "iridescence_thickness",
194 "thinFilmIor": "iridescence_ior"
195 };
196
197 this.remapMap = {
198 'standard_surface': standard_surface_remapKeys,
199 'gltf_pbr': gltf_remapKeys,
200 'open_pbr_surface': openpbr_remapKeys
201 };
202 }
203
204
210 {
211 console.log('Initializing input remapping for Physically Based Materials...');
212 this.remapMap = null;
213
214 const remapKeyURL = 'https://raw.githubusercontent.com/kwokcb/materialxMaterials/refs/heads/main/src/materialxMaterials/data/PhysicallyBasedMaterialX/PhysicallyBasedToMtlxMappings.json';
215
216 fetch(remapKeyURL)
217 .then((response) =>
218 {
219 if (!response.ok) {
220 console.warn(`HTTP error! Status: ${response.status}`);
221 return null;
222 }
223 return response.json();
224 })
225 .then((data) => {
226 if (data) {
227 this.remapMap = data;
228 console.log('- Remap keys loaded from repo:', this.remapMap);
229 } else {
230 console.warn('- No remap keys from repo. Using default remap keys.');
231 this.setDefaultRemapKeys();
232 }
233 })
234 .catch((error) => {
235 console.log('- Error loading remap keys:', error);
236 this.setDefaultRemapKeys();
237 console.warn('- Using default remap keys.', this.remapMap);
238 });
239 }
240
246 {
247 return new Promise((resolve, reject) => {
248 MaterialX().then((mtlx) => {
249 this.mx = mtlx;
250 resolve();
251 }).catch((error) => {
252 reject(error);
253 });
254 });
255 }
256
262 {
263 try {
264 this.materials = null
265 this.materialNames = [];
266
267 const response = await fetch(this.url, {
268 method: 'GET',
269 headers: this.headers
270 });
271
272 if (!response.ok) {
273 throw new Error('Network response was not ok ' + response.statusText);
274 }
275
276 this.materials = await response.json();
277 for (let i = 0; i < this.materials.length; i++) {
278 this.materialNames.push(this.materials[i]['name']);
279 }
280 return this.materials;
281 } catch (error) {
282 console.error('There has been a problem with your fetch operation:', error);
283 }
284
285 return null;
286 }
287
293 {
294 if (!this.mx) {
295 // Call the asynchronous function and then perform additional logic
296 this.loadMaterialX().then(() => {
297
298 this.esslgenerator = new this.mx.EsslShaderGenerator.create();
299 this.esslgenContext = new this.mx.GenContext(this.esslgenerator);
300 this.stdlib = this.mx.loadStandardLibraries(this.esslgenContext);
301 let children = this.stdlib.getChildren();
302 for (let i = 0; i < children.length; i++) {
303 let child = children[i];
304 child.setSourceUri('STDLIB_ELEMENT');
305 }
306
307 console.log("MaterialX is loaded");
308 }).catch((error) => {
309 console.error("Error loading MaterialX:", error);
310 });
311 }
312 }
313
320 {
321 return !elem.hasSourceUri()
322 }
323
329 {
330 if (!this.doc) {
331 console.error('No MaterialX document to convert');
332 return '';
333 }
334
335 // Create write options
336 const writeOptions = new this.mx.XmlWriteOptions();
337 writeOptions.writeXIncludeEnable = false;
338 //writeOptions.writeXIncludes = false;
339 writeOptions.elementPredicate = this.skipLibraryElement;
340
341 // Convert the MaterialX document to a string
342 const mtlx = this.mx.writeToXmlString(this.doc, writeOptions);
343 return mtlx;
344 }
345
351 addComment(doc, commentString)
352 {
353 let comment = doc.addChildOfCategory('comment')
354 comment.setDocString(commentString)
355 }
356
357
363 {
364 let references = [];
365 if (this.materials) {
366 for (let i = 0; i < this.materials.length; i++) {
367 const mat = this.materials[i];
368 const matName = mat['name'];
369 const refString = mat['reference'];
370 const tags = mat['tags'];
371 const category = mat['category'];
372 if (refString.length > 0)
373 {
374 let referenceItem = { name: matName, reference: refString[0], tags: tags, category: category };
375 //console.log('Add Reference:', referenceItem);
376 references.push(referenceItem);
377 }
378 }
379 // Sort references by name
380 references.sort((a, b) => a.name.localeCompare(b.name));
381 }
382 return references;
383 }
384
395 convertToMaterialX(shaderCategory, references, addAllInputs = false, materialNames = [], remapKeys = {}, shaderPreFix = '')
396 {
397 if (!this.mx) {
398 console.error('MaterialX module is not loaded');
399 return false;
400 }
401
402 if (!this.materials) {
403 console.warn('No Physically Based Materials to convert');
404 return false;
405 }
406
407 if (Object.keys(remapKeys).length === 0)
408 {
409 remapKeys = this.getInputRemapping(shaderCategory);
410 }
411 //console.log('Using remap keys for shading model:', shaderCategory, remapKeys);
412
413 // Create a dummy doc with the surface shader with all inputs
414 // as reference
415 let refDoc = this.mx.createDocument();
416 refDoc.importLibrary(this.stdlib);
417 const refNode = refDoc.addNode(shaderCategory, 'refShader', this.mx.SURFACE_SHADER_TYPE_STRING);
418 if (addAllInputs) {
419 console.warn('MaterialX JS API missing addInputsFromNodeDef()');
420 //refNode.addInputsFromNodeDef() -- This is missing from the JS API.
421 }
422 this.doc = this.mx.createDocument();
423
424 // Add document level accreditation
425 let docString = 'Physically Based Materials from https://api.physicallybased.info.\n'
426 docString += ' Content Author: Anton Palmqvist, https://antonpalmqvist.com/ \n'
427 docString += ` Content processsed via REST API and mapped to MaterialX V${this.mx.getVersionString()} \n`
428 docString += ` Target Shading Model: ${shaderCategory} \n`
429 docString += ' Utility Author: Bernard Kwok. kwokcb@gmail.com '
430 this.doc.setDocString(docString);
431
432 // Add properties to the material
433 for (let i = 0; i < this.materials.length; i++) {
434 const mat = this.materials[i];
435 let matName = mat['name'];
436
437 // Filter by material name(s)
438 let skipGeneration = materialNames.length > 0 && !materialNames.includes(matName);
439
440 let shaderNode = null;
441 if (!skipGeneration)
442 {
443 if (shaderPreFix.length > 0) {
444 matName = shaderPreFix + '_' + matName;
445 }
446
447 const shaderName = this.doc.createValidChildName(matName + '_' + shaderCategory + '_SPB');
448 this.addComment(this.doc, ' Generated shader: ' + matName + ' ');
449 shaderNode = this.doc.addNode(shaderCategory, shaderName, this.mx.SURFACE_SHADER_TYPE_STRING);
450
451 const category = mat['category'];
452 const group = mat['group'];
453 let uifolder = '';
454 if (category && category.length > 0) {
455 uifolder = category[0];
456 }
457 if (group && group.length > 0) {
458 if (uifolder.length > 0) {
459 uifolder += '/';
460 }
461 uifolder += group[0];
462 }
463 if (uifolder.length > 0) {
464 shaderNode.setAttribute('uifolder', uifolder);
465 }
466
467 let docString = ''
468 if (mat['description'].length > 0) {
469 docString += 'Description: ' + mat['description'];
470 }
471 }
472
473 // Always want to build the ference
474 const refString = mat['reference'];
475 if (refString.length > 0) {
476 if (docString.length > 0) {
477 docString += '. ';
478 }
479 docString += 'Reference: ' + refString[0];
480
481 let referenceItem = { name: matName, reference: refString[0] };
482 //console.log('Add Reference:', referenceItem);
483 references.push(referenceItem);
484 }
485 // Sort references by name
486 references.sort((a, b) => a.name.localeCompare(b.name));
487
488 if (!shaderNode) {
489 continue;
490 }
491 if (docString.length > 0) {
492 shaderNode.setDocString(docString);
493 }
494
495 // Create a new material
496 const materialName = this.doc.createValidChildName(matName + '_' + shaderCategory + '_MPB');
497 this.addComment(this.doc, ' Generated material: ' + matName + ' ');
498 const materialNode = this.doc.addNode(this.mx.SURFACE_MATERIAL_NODE_STRING, materialName, this.mx.MATERIAL_TYPE_STRING);
499 const shaderInput = materialNode.addInput(this.mx.SURFACE_SHADER_TYPE_STRING, this.mx.SURFACE_SHADER_TYPE_STRING);
500 shaderInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, shaderNode.getName());
501
502 // Warning this is a bit bespoke for remapping keys
503 // to Autodesk Standard Surface shader inputs
504 const skipKeys = ['name', "density", "category", "description", "sources", "tags", "reference"];
505
506 let metallness = null;
507 let roughness = null;
508 let transmission_color = null;
509 let transmission = null;
510 Object.entries(mat).forEach(([key, value]) => {
511
512 if (!skipKeys.includes(key)) {
513
514 //console.log(`-- Processing key: "${key}" with value:`, value);
515
516 if (key == 'metalness') {
517 metallness = value;
518 //console.log('Metalness:', metallness);
519 }
520 if (key == 'roughness') {
521 roughness = value;
522 //console.log('Roughness:', roughness);
523 }
524 if (key == 'transmission') {
525 transmission = value;
526 //console.log('Transmission:', transmission);
527 }
528 if (key == 'color') {
529 transmission_color = value;
530 //console.log('Color:', color);
531 }
532
533 //console.log(`-- Remapping key "${key}" to "${remapKeys[key]}"`);
534 if (remapKeys[key]) {
535 key = remapKeys[key];
536 }
537
538 let refInput = refNode.getInput(key);
539 if (!refInput)
540 refInput = refNode.addInputFromNodeDef(key);
541 if (refInput) {
542 const input = shaderNode.addInput(key);
543 input.copyContentFrom(refInput);
544 if (input) {
545 // Convert number vector to string
546 if (Array.isArray(value)) {
547 value = value.join(',');
548 }
549 // Convert number to string
550 else if (typeof value === 'number') {
551 value = value.toString();
552 }
553 // Note: This API has side-effects as the
554 // type is set to "string" when the value is set. Thus
555 // we must explicitly set the type here.
556 input.setValueString(value, refInput.getType());
557 }
558 }
559 else {
560 //console.log('>> Could not map input:', key, 'to node definition')
561 }
562 }
563 });
564
565 if (transmission !== null && metallness !== null && roughness !== null && transmission_color !== null)
566 {
567 if (metallness == 0 && roughness == 0)
568 {
569 if (remapKeys['transmission_color']) {
570 let inputName = remapKeys['transmission_color'];
571 let input = shaderNode.addInput(inputName);
572 if (input) {
573 let value = transmission_color.join(',');
574 //console.log(`Add "${inputName}": "${value}"`);
575 input.setValueString(value, 'color3');
576 }
577 }
578 }
579 };
580 }
581 return true;
582 }
583
584}
let MTLX_NODE_NAME_ATTRIBUTE
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.
getReferenceList()
Return a sorted list reference names mapped reference images.
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.