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