Function to initialize the MaterialX node editor.
138 {
139 let my_icon_map = {
140 "_default_": "./Icons/materialx_logo.webp",
141 "_default_graph_": "./Icons/nodegraph_white.svg"
142 };
143
144 let geometryValues = ['teapot', 'shaderball', 'sphere', 'plane', 'cube', 'cylinder', 'twist', '_loadFromFile_']
145
146 if (user_icon_map) {
147
148 for (var key in user_icon_map) {
149 my_icon_map[key] = user_icon_map[key];
150 }
151 }
152
153
154 function uriExists(uri) {
155 return fetch(uri)
156 .then(response => {
157 if (response.ok) {
158 return Promise.resolve(true);
159 } else {
160 return Promise.resolve(false);
161 }
162 })
163 .catch(error => {
164 console.log('Error checking URI:', error);
165 return Promise.resolve(false);
166 });
167 }
168
169
170 function renderableItemUpdater(renderableItems) {
171 let renderableItemSelect = document.getElementById('renderableItem');
172 if (renderableItemSelect) {
173
174 const TRUNCATION_LENGTH = 12;
175
176 while (renderableItemSelect.firstChild) {
177 renderableItemSelect.removeChild(renderableItemSelect.firstChild);
178 }
179 for (let i = 0; i < renderableItems.length; i++) {
180 let item = renderableItems[i];
181 let option = document.createElement('option');
182 option.value = item;
183 let uiItem = item;
184
185 if (uiItem.length > 20)
186 uiItem = uiItem.substring(0, 20) + '...';
187 option.text = uiItem;
188 renderableItemSelect.appendChild(option);
189 }
190 }
191 }
192
193
194
195 function consoleLog(text, severity, clear = null) {
196 if (severity === 2) {
197 text = '> Error: ' + text
198 }
199 else if (severity === 1) {
200 text = '> Warning: ' + text
201 }
202 else {
203 if (text.length)
204 text = '> ' + text;
205 }
206
207 let console_area = document.getElementById('console_area');
208 if (console_area) {
209 if (clear) {
210 console_area.value = text + '\n';
211 }
212 else {
213 console_area.value = console_area.value + text + '\n';
214 }
215
216 console_area.scrollTop = console_area.scrollHeight;
217 }
218 else {
219 console.log(text);
220 }
221 }
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
244
245 function createMenuItem(text, filename) {
246 let menuItem = document.createElement('li');
247 menuItem.className = 'dropdown-item';
248 menuItem.innerText = text;
249 menuItem.onclick = function () {
250 console.log('Load library file:', filename);
252
253 let dropdownMenu = document.getElementById('libraryDropdown');
254 dropdownMenu.classList.remove('show');
255 };
256 return menuItem;
257 }
258
264
265 function createSubMenu(title, auto_close = false) {
266
267 let li = document.createElement('li');
268 li.className = "dropend dropdown";
269 li.id = key;
270
271 let subMenu = document.createElement('a');
272 subMenu.className = "dropdown-item dropdown-toggle";
273 subMenu.setAttribute('data-bs-toggle', "dropdown");
274 if (auto_close) {
275 subMenu.setAttribute('data-bs-auto-close', 'outside');
276 }
277 subMenu.setAttribute('aria-expanded', 'false');
278 subMenu.setAttribute('aria-haspopup', 'true');
279 subMenu.innerHTML = title;
280 li.appendChild(subMenu);
281
282 return li;
283 }
284
290
291 function createLibraryMenu(sampleFiles, libraryDropdown) {
292 for (let key in sampleFiles) {
293
294 let li = createSubMenu(key, true);
295 libraryDropdown.appendChild(li);
296
297 let value = sampleFiles[key];
298
299
300 if (typeof value === 'string') {
301 let value = sampleFiles[key];
302 li.appendChild(createMenuItem(key, value));
303 }
304 else if (typeof value === 'object') {
305
306 let subMenuList = document.createElement('ul');
307 subMenuList.className = 'dropdown-menu';
308 subMenuList.id = key;
309
310 for (key in value) {
311
312
313 if (typeof value[key] === 'string') {
314 subMenuList.appendChild(createMenuItem(key, value[key]));
315 }
316
317 else if (typeof value[key] === 'object') {
318
319
320 let sli = createSubMenu(key, false);
321 subMenuList.appendChild(sli);
322
323 let ssubMenuList = document.createElement('ul');
324 ssubMenuList.className = 'dropdown-menu';
325 for (let skey in value[key]) {
326 ssubMenuList.appendChild(createMenuItem(skey, (value[key])[skey]));
327 }
328 sli.appendChild(ssubMenuList);
329 }
330 }
331
332 li.appendChild(subMenuList);
333 }
334
335 }
336 }
337
338
339 if (sampleFiles && libraryDropdown) {
340 createLibraryMenu(sampleFiles, libraryDropdown);
341 }
342
343
344 let selectGeometryUI = false;
345 if (customRenderer) {
346
347 let geometryURL = geometryId;
348 if (geometryId.length > 0 && geometryValues.includes(geometryId)) {
349 geometryURL = 'Geometry/' + geometryId + '.glb';
350 selectGeometryUI = true;
351 }
352 var viewer = customRenderer.initialize(materialFilename, geometryURL, readOnly);
353 console.log('Setup renderer:', viewer);
354 }
355 else {
356 let preview_panel = document.getElementById("preview_panel");
357
358 if (preview_panel)
359 preview_panel.style.display = 'none';
360 }
361
362
367
368 function displayNodeTypes(nodeTypes) {
369
370 var nodeList = document.getElementById('nodeTypesList');
371 if (!nodeList) {
372 return;
373 }
374
375
376 while (nodeList.firstChild) {
377 nodeList.removeChild(nodeList.firstChild);
378 }
379
380
381 for (var typeName in nodeTypes) {
382
383 var rowItem = document.createElement("tr");
384
385 var cellItem = document.createElement("td");
386 cellItem.textContent = typeName;
387 rowItem.appendChild(cellItem);
388
389 cellItem = document.createElement("td");
390 var nodeDefString = '<None>';
391 var nodeDefName = nodeTypes[typeName].nodedef_name;
392 var nodeDefNode = nodeTypes[typeName].nodedef_node
393 var nodeDefHref = nodeTypes[typeName].nodedef_href;
394 if (nodeDefName) {
395 if (nodeDefNode) {
396 var link = document.createElement("a");
397 link.target = "_blank";
398 link.href = nodeDefHref;
399 link.textContent = nodeDefNode + " ( " + nodeDefName + " )";
400 cellItem.appendChild(link);
401 }
402 else {
403 cellItem.textContent = nodeDefName;
404 }
405 }
406 else {
407 cellItem.textContent = nodeDefString;
408 }
409 rowItem.appendChild(cellItem);
410
411 nodeList.appendChild(rowItem);
412 }
413 }
414
415
416 var cmeditor = setupXMLSyntax();
417 var cmeditor2 = setupJavascriptSyntax();
418 var cmeditor3 = setupGLTFSyntax();
419
424
425 function docDisplayUpdater(contents) {
426 if (cmeditor)
427 cmeditor.setValue(contents);
428 }
429
434
435 function gltfDisplayUpdater(contents) {
436 if (!contents || contents.length == 0) {
437 contents = '{}';
438 }
439 if (cmeditor3)
440 cmeditor3.setValue(contents);
441 else
442 console.log(contents);
443 }
444
449
450 function jsDefinitionsDisplayUpdater(contents) {
451 if (cmeditor2)
452 cmeditor2.setValue(contents);
453 }
454
455
456 var canvas = document.getElementById('mygraphcanvas');
457 var ui = {
458 consoleLogger: consoleLog,
459 nodeTypesListUpdater: displayNodeTypes,
460 renderableItemUpdater: renderableItemUpdater,
461 documentDisplayUpdater: docDisplayUpdater,
462 gltfDocumentDisplayUpdater: gltfDisplayUpdater,
463 definitionsDisplayUpdater: jsDefinitionsDisplayUpdater,
464 propertypanel_content: document.getElementById('propertypanel_content'),
465 propertypanel_icon: document.getElementById('propertypanel_icon'),
466 icon_map: my_icon_map,
467 };
469
471 monitor.setRenderer(customRenderer);
472 editor.initialize(canvas, ui, monitor, materialFilename, readOnly);
473
477
478 function addUIHandlers() {
479
480 var saveCanvasButton = document.getElementById('captureGraph');
481 if (saveCanvasButton) {
482 saveCanvasButton.addEventListener('click', function () {
483 var canvas = document.getElementById('mygraphcanvas');
484 var dataURL = canvas.toDataURL('image/png');
485 var link = document.createElement('a');
486 link.href = dataURL;
487 link.download = 'graph_capture.png';
488 link.click();
489 });
490 }
491
492
493 var auto_arrange_size = 80;
494
495
496 var loadMaterialXDocumentFromFile = document.getElementById('loadMaterialXDocumentFromFile');
497 if (loadMaterialXDocumentFromFile) {
498 loadMaterialXDocumentFromFile.addEventListener('click', function () {
499 editor.loadGraphFromFile('mtlx', auto_arrange_size);
501 });
502 }
503
504
505 var loadMaterialXDocumentFromZip = document.getElementById('loadMaterialXDocumentFromZip');
506 if (loadMaterialXDocumentFromZip) {
507 loadMaterialXDocumentFromZip.addEventListener('click', function () {
508 editor.loadGraphFromFile('zip', auto_arrange_size);
510 });
511 }
512
513
514 var texAreaNumber = 0;
515 var loadMaterialXDocumentFromText = document.getElementById('loadMaterialXDocumentFromText');
516 if (loadMaterialXDocumentFromText) {
517 loadMaterialXDocumentFromText.addEventListener('click', function () {
518 var mtlxdoc = document.getElementById('mtlxdoc').value;
519
520 if (mtlxdoc.length > 0) {
521 var name = 'MaterialXGraph' + texAreaNumber++;
522 editor.loadGraphFromString('mtlx', mtlxdoc, name, auto_arrange_size);
524 }
525 });
526 }
527
528
529 var loadMaterialXDefinitions = document.getElementById('loadMaterialXDefinitions');
530 if (loadMaterialXDefinitions) {
531 loadMaterialXDefinitions.addEventListener('click', function () {
532 editor.loadDefinitionsFromFile('mtlx');
533 });
534 }
535
536
537 var clearGraphButton = document.getElementById('clearGraph');
538 if (clearGraphButton) {
539 clearGraphButton.addEventListener('click', function () {
540 editor.clearGraph();
542 });
543 }
544
545
546 var saveMaterialXGraph = document.getElementById('saveMaterialXGraph');
547 if (saveMaterialXGraph) {
548 saveMaterialXGraph.addEventListener('click', function () {
549 var sl = document.getElementById('writeCustomLibs').checked;
550 var sp = document.getElementById('saveNodePositions').checked;
551 var wo = true;
552 var graphWriteOptions = { writeCustomLibs: sl, saveNodePositions: sp, writeOutputs: wo };
553 editor.saveGraphToFile('mtlx', graphWriteOptions);
554 });
555 }
556
557
558 var saveMaterialXGraphText = document.getElementById('saveMaterialXGraphText');
559 if (saveMaterialXGraphText) {
560 saveMaterialXGraphText.addEventListener('click', function () {
561 saveToStringUI();
562 });
563 }
564
565
566 var searchGraph = document.getElementById('searchGraph');
567 if (searchGraph) {
568 searchGraph.addEventListener('click', function () {
569 var search = document.getElementById('searchGraphText').value;
570 editor.searchGraph(search);
571 });
572 }
573
574
575 var openSubgraph = document.getElementById('openSubgraph');
576 if (openSubgraph) {
577 openSubgraph.addEventListener('click', function () {
578 editor.openSubgraph();
579 });
580 }
581
582
583 var closeSubgraph = document.getElementById('closeSubgraph');
584 if (closeSubgraph) {
585 closeSubgraph.addEventListener('click', function () {
586 editor.closeSubgraph();
587 });
588 }
589
590
591 var resetView = document.getElementById('resetView');
592 if (resetView) {
593 resetView.addEventListener('click', function () {
594 editor.resetView();
595 });
596 }
597
598
599 var arrangeGraphButton = document.getElementById('arrangeGraph');
600 if (arrangeGraphButton) {
601 arrangeGraphButton.addEventListener('click', function () {
602 editor.arrangeGraph();
603 });
604 }
605
606
607 var centerNodeButton = document.getElementById('centerNode');
608 if (centerNodeButton) {
609 centerNodeButton.addEventListener('click', function () {
610 editor.centerNode();
611 });
612 }
613
614
615 var collapseNodesButton = document.getElementById('collapseNodes');
616 if (collapseNodesButton) {
617 collapseNodesButton.addEventListener('click', function () {
618 editor.collapseExpandNodes(true);
619 });
620 }
621 var expandNodesButton = document.getElementById('expandNodes');
622 if (expandNodesButton) {
623 expandNodesButton.addEventListener('click', function () {
624 editor.collapseExpandNodes(false);
625 });
626 }
627
628
629 var selectNodesButton = document.getElementById('selectNodes');
630 if (selectNodesButton) {
631 selectNodesButton.addEventListener('click', function () {
632 editor.selectNodes();
633 });
634 }
635
636
637 var copySelectedButton = document.getElementById('copySelected');
638 if (copySelectedButton) {
639 copySelectedButton.addEventListener('click', function () {
640 editor.copyToClipboard();
641 });
642 }
643
644
645 var pasteSelectedButton = document.getElementById('pasteSelected');
646 if (pasteSelectedButton) {
647 pasteSelectedButton.addEventListener('click', function () {
648 editor.pasteFromClipboard();
649 });
650 }
651
652
653 var createNodeGraphButton = document.getElementById('createNodeGraph');
654 if (createNodeGraphButton) {
655 createNodeGraphButton.addEventListener('click', function () {
656 editor.createNodeGraph();
657 });
658 }
659
660
661 var extractNodeGraphButton = document.getElementById('extractNodeGraph');
662 if (extractNodeGraphButton) {
663 extractNodeGraphButton.addEventListener('click', function () {
664 editor.extractNodeGraph();
665 });
666 }
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682 var xmlToGraph = document.getElementById('xmltograph');
683 if (xmlToGraph) {
684 xmlToGraph.addEventListener('click', function () {
685 var name = 'MaterialXGraph' + texAreaNumber++;
686 var mtlxdoc = document.getElementById('mtlxdoc').value;
687 editor.loadGraphFromString('mtlx', mtlxdoc, 'MaterialXGraph', auto_arrange_size);
689 });
690 }
691
692 function updateRenderableItemUI() {
693 let renderableItems = editor.findRenderableItems();
694 renderableItemUpdater(renderableItems);
695 }
696
697 function saveToStringUI() {
698 var cl = document.getElementById('writeCustomLibs').checked;
699 var sp = document.getElementById('saveNodePositions').checked;
700 var wo = true;
701 var graphWriteOptions = { writeCustomLibs: cl, saveNodePositions: sp, writeOutputs: wo };
702 console.log('Save with options: ', graphWriteOptions);
703 var result = editor.saveGraphToString('mtlx', graphWriteOptions);
704
705 cmeditor.setValue(result[0]);
706
707 if (customRenderer) {
708 customRenderer.setSourceColorSpace(editor.getSourceColorSpace());
709 customRenderer.setTargetDistanceUnit(editor.getTargetDistanceUnit());
710 customRenderer.updateMaterialFromText(result[0]);
711 updateRenderableItemUI();
712 }
713 }
714
715
716 var graphtoxml = document.getElementById('graphtoxml');
717 if (graphtoxml) {
718 graphtoxml.addEventListener('click', function () {
719 saveToStringUI();
720 });
721 }
722
723 let graphtoxml2 = document.getElementById('graphtoxml2');
724 if (graphtoxml2) {
725 graphtoxml2.addEventListener('click', function () {
726 saveToStringUI();
727 graphtoxml2.classList.remove('btn-outline-warning');
728 graphtoxml2.classList.add('btn-outline-secondary');
729 });
730 }
731
732
733 var graphtogltf = document.getElementById('graphtogltf');
734 if (graphtogltf) {
735 graphtogltf.addEventListener('click', function () {
736 var graphWriteOptions = { writeCustomLibs: false, saveNodePositions: false, writeOutputs: true };
737 var result = editor.saveGraphToString('gltf', graphWriteOptions);
738 gltfDisplayUpdater(result[0]);
739 if (result[1]) {
740 consoleLog(result[1], 1, false);
741 }
742 });
743 }
744
745
746 var gltftograph = document.getElementById('gltftograph');
747 if (gltftograph) {
748 gltftograph.addEventListener('click', function () {
749 var gltfdoc = document.getElementById('gltfgraph').value;
750 if (gltfdoc.length > 0) {
751 editor.loadGraphFromString('gltf', gltfdoc, 'GLTFGraph', auto_arrange_size);
753 }
754 });
755 }
756
757
758 let turntableEnabledUI = document.getElementById('turntableEnabled');
759 if (turntableEnabledUI) {
760 turntableEnabledUI.addEventListener('click', (e) => {
761
762 turntableEnabledUI.classList.toggle('btn-secondary');
763 if (customRenderer)
764 customRenderer.toggleTurntable();
765 });
766 }
767
768
769 let disableRenderingUI = document.getElementById('disableRendering');
770 if (disableRenderingUI) {
771 disableRenderingUI.addEventListener('click', (e) => {
772
773 disableRenderingUI.classList.toggle('btn-danger');
774 if (customRenderer)
775 customRenderer.toggleRendering();
776 });
777 }
778
779
780 let toggleBackgroundTextureUI = document.getElementById('toggleBackgroundTexture');
781 if (toggleBackgroundTextureUI) {
782 toggleBackgroundTextureUI.addEventListener('click', (e) => {
783 toggleBackgroundTextureUI.classList.toggle('btn-primary');
784 if (customRenderer)
785 customRenderer.toggleBackgroundTexture();
786 });
787 }
788
789
790 let resetCameraUI = document.getElementById('resetCamera');
791 if (resetCameraUI) {
792 resetCameraUI.addEventListener('click', (e) => {
793 if (customRenderer) {
794 customRenderer.resetCamera();
795 }
796 });
797 }
798
799
800 function loadFromMenu(e) {
801 var uiItem = e.target.value;
802 if (uiItem == '_loadFromFile_') {
803
804 var fileInput = document.createElement('input');
805 fileInput.type = 'file';
806 fileInput.accept = '.glb';
807
808 fileInput.onchange = function(event) {
809 var file = event.target.files[0];
810 if (file) {
811 var fileURL = URL.createObjectURL(file);
812 if (customRenderer)
813 customRenderer.setRenderGeometry(fileURL);
814 console.log('Change geometry to:', fileURL, 'from file:', file.name);
815 }
816 }
817 fileInput.click();
818 }
819 else {
820
821 var geometryURL = uiItem.toLowerCase().replace(/\s/g, '');
822 var geometryPath = 'Geometry/' + geometryURL + '.glb';
823 console.log('Change geometry to:', geometryPath);
824 if (customRenderer)
825 customRenderer.setRenderGeometry(geometryPath);
826 }
827 }
828
829
830 let geometryItemSelect = document.getElementById('loadGeometry');
831 if (geometryItemSelect) {
832
833
834 var geometryItems = ['Teapot', 'Shader Ball', 'Sphere', 'Plane', 'Cube', 'Cylinder', 'Twist', 'Custom...'];
835 for (var i = 0; i < geometryItems.length; i++) {
836 var option = document.createElement('option');
837 option.value = geometryValues[i];
838 option.text = geometryItems[i];
839 geometryItemSelect.appendChild(option);
840 }
841
842
843 geometryItemSelect.addEventListener('change', (e) => {
844 loadFromMenu(e);
845 if (e.target.value == '_loadFromFile_')
846 e.target.value = 'Custom Geometry'
847 });
848
849
850 if (selectGeometryUI) {
851
852 geometryItemSelect.value = geometryId;
853 }
854 }
855
856
857 let renderableItemSelect = document.getElementById('renderableItem');
858 if (renderableItemSelect) {
859 renderableItemSelect.addEventListener('change', (e) => {
860 let index = e.target.value;
861 if (customRenderer)
862 {
863 customRenderer.setRenderMaterial(index);
864 editor.searchGraph(index);
865 }
866 });
867 }
868
869
870 var canvas = document.getElementById('mygraphcanvas');
871 var canvasContainer = document.getElementById('canvasContainer');
872 var colContainer = document.getElementById('colContainer');
873
874
875 var observer = new ResizeObserver(function (entries) {
876
877
878
879
880
881 var parent = canvas.parentNode;
882 let newWidth = parent.offsetWidth;
883 let newHeight = parent.offsetHeight;
884
885
886
887
888
889
890 console.log('Resize node graph canvas to:', newWidth, newHeight);
891 editor.setDirty(newWidth, newHeight);
892
893
894 });
895
896
897 observer.observe(canvasContainer);
898
899 }
900
901 function setupGLTFSyntax() {
902
903 let cmeditor = null;
904 const gltfTextArea = document.getElementById('gltfgraph');
905 if (gltfTextArea) {
906 cmeditor = CodeMirror.fromTextArea(gltfTextArea, {
907 mode: 'application/json',
908 lineNumbers: true,
909 dragDrop: false,
910 theme: 'dracula'
911 });
912
913
914 const initialGLTF = '';
915 gltfTextArea.value = initialGLTF;
916 cmeditor.setValue(initialGLTF);
917
918
919 cmeditor.on('change', (e) => {
920 gltfTextArea.value = cmeditor.getValue();
921 });
922
923 var pasteButton = document.getElementById('gltfgraph_paste');
924 if (pasteButton)
925 addPasteHandler(pasteButton, cmeditor);
926
927 }
928 return cmeditor;
929 }
930
931 function setupJavascriptSyntax() {
932
933 const elem = document.getElementById('mtlxlib');
934 if (!elem) {
935 return;
936 }
937 let cmeditor = CodeMirror.fromTextArea(elem, {
938 mode: 'application/javascript',
939 lineNumbers: true,
940 dragDrop: false,
941 theme: 'dracula',
942 readOnly: true
943 });
944
945 elem.value = '';
946 cmeditor.setValue('');
947
948
949 cmeditor.on('change', () => {
950 elem.value = cmeditor.getValue();
951 });
952
953 return cmeditor;
954 }
955
956
957 function setupXMLSyntax() {
958
959 const materialXTextArea = document.getElementById('mtlxdoc');
960 let cmeditor = CodeMirror.fromTextArea(materialXTextArea, {
961 mode: 'application/xml',
962 lineNumbers: true,
963 dragDrop: true,
964 theme: 'night'
965 });
966
967
968 const initialXML = '';
969 materialXTextArea.value = initialXML;
970 cmeditor.setValue(initialXML);
971
972
973 cmeditor.on('change', (e) => {
974 materialXTextArea.value = cmeditor.getValue();
975 });
976
977 var pasteButton = document.getElementById('mtlxdoc_paste');
978 if (pasteButton)
979 addPasteHandler(pasteButton, cmeditor);
980
981 return cmeditor;
982 }
983
984 addUIHandlers();
985 addCopyHandlers();
986}
Custom monitor class for MaterialX graph.
This class is a wrapper around the LiteGraph library to provide a MaterialX node editor.
function toggleRequireUpdateUI()