MaterialXLab API  0.0.1
APIs For MaterialXLab Libraries
Loading...
Searching...
No Matches
node_editor.js
Go to the documentation of this file.
1/*
2 Interface setup for MaterialX node editor. This script sets up the UI and event handlers for
3 the node editor. It also initializes the CodeMirror instances for syntax highlighting of
4 MaterialX documents and JavaScript code.
5
6 The node editor is initialized with the specified MaterialX document and geometry file.
7 An optional renderer can be used to display the material and geometry in a viewer.
8*/
9
11 let graphtoxml2 = document.getElementById('graphtoxml2');
12 if (graphtoxml2) {
13 graphtoxml2.classList.remove('btn-outline-secondary');
14 graphtoxml2.classList.add('btn-outline-warning');
15 }
16}
17
25 super(name);
26 }
27
28 onDocumentChange(attribute, value, prevValue) {
29 if (!this.monitoring) {
30 return;
31 }
32
33 if (this.renderer)
35 if (this.debug) {
36 this.debugMessage('Monitor> Document attribute "' + attribute + '" changed from: ' + prevValue + ' to: ' + value, '');
37 }
38 }
39
40 onConnectionChange(node, parentGraph) {
41 if (!this.monitoring) {
42 return;
43 }
44
45 if (this.renderer)
47 if (this.debug) {
48 this.debugMessage('Monitor> Connection change: ', this.getPath(node, parentGraph));
49 }
50 }
51
52 onNodeRemoved(node, parentGraph) {
53 if (!this.monitoring) {
54 return;
55 }
56
57 if (this.renderer)
59 if (this.debug) {
60 this.debugMessage('Monitor> Node removed: ', this.getPath(node, parentGraph));
61 }
62 }
63
64 onNodeRenamed(node, newName) {
65 if (!this.monitoring) {
66 return;
67 }
68
69 if (this.renderer)
71
72 if (this.debug) {
73 let parentPath = this.getParentPath(node);
74 let path = parentPath + node.title;
75 let newpath = parentPath + newName;
76 this.debugMessage('Monitor> Node renamed: ', path + ' to: ' + newpath);
77 }
78 }
79
80 onPropertyInfoChanged(nodeName, propertyName, propertyInfoName, newValue, previousValue, node) {
81 if (!this.monitoring) {
82 return;
83 }
84
85 if (this.renderer)
87
88 if (this.debug) {
89 let path = this.getParentPath(node) + nodeName;
90 console.log('Monitor> Property Info changed:', path, '. Property: ' + propertyName +
91 '. Property Info: ' + propertyInfoName +
92 '. Value: ' + newValue + '. Previous Value: ' + previousValue, '. Category:', node.nodedef_node);
93 }
94 }
95
96 onPropertyChanged(nodeName, propertyName, newValue, previousValue, node) {
97 if (!this.monitoring) {
98 return;
99 }
100
101 let path = this.getParentPath(node) + nodeName;
102
103 if (this.renderer) {
104 if (typeof newValue == 'string') {
106 if (this.debug) {
107 console.log('Renderer> Build required for string change:', path, '. Property: ' + propertyName +
108 '. Value: ' + newValue + '. Previous Value: ' + previousValue + '. Node: ' + node.nodedef_node);
109 }
110 }
111 else {
112 if (node.nodedef_node != 'input')
113 path = path + '/' + propertyName;
114 this.renderer.updateShader(path, newValue);
115 }
116 }
117 else {
118 if (this.debug) {
119 console.log('Monitor> Property changed:', path, '. Property: ' + propertyName +
120 '. Value: ' + newValue + '. Previous Value: ' + previousValue + '. Node: ' + node.nodedef_node);
121 }
122 }
123 }
124}
125
126
137export function initializeNodeEditor(materialFilename, geometryId, customRenderer, user_icon_map = null, sampleFiles = null,
138 readOnly = false) {
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 // add items in user icon map. Overwrite any existing items
148 for (var key in user_icon_map) {
149 my_icon_map[key] = user_icon_map[key];
150 }
151 }
152
153 // Check if URI exists
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 // Renderable item UI updater
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 // Truncate the name so it will fit into UI.
185 if (uiItem.length > 10)
186 uiItem = uiItem.substring(0, 10) + '...';
187 option.text = uiItem;
188 renderableItemSelect.appendChild(option);
189 }
190 }
191 }
192
193 // Logger
194 // TODO: Pass in a logger object instead of looking for a DOM element.
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 // Scroll to latest entry.
216 console_area.scrollTop = console_area.scrollHeight;
217 }
218 else {
219 console.log(text);
220 }
221 }
222
223 /* function createMenuStructure(obj) {
224 let items = [];
225 for (let key in obj) {
226 if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
227 // It's a nested object, create a submenu
228 let subItems = createMenuStructure(obj[key]); // Recursively handle nested objects
229 items.push(createSubMenu(key, subItems));
230 } else {
231 items.push(createMenuItem(key, obj[key]));
232 }
233 console.log('<<< END SCAN');
234 }
235 return items;
236 } */
237
238
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);
251 MxShadingGraphEditor.theEditor.handler.loadLibraryDocument(MxShadingGraphEditor.theEditor, filename);
252 // Collapse the dropdown menu
253 let dropdownMenu = document.getElementById('libraryDropdown');
254 dropdownMenu.classList.remove('show');
255 };
256 return menuItem;
257 }
258
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
291 function createLibraryMenu(sampleFiles, libraryDropdown) {
292 for (let key in sampleFiles) {
293 // Create top level menus
294 let li = createSubMenu(key, true);
295 libraryDropdown.appendChild(li);
296
297 let value = sampleFiles[key];
298
299 // Add items to the submenu
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 // Check if value is a string
313 if (typeof value[key] === 'string') {
314 subMenuList.appendChild(createMenuItem(key, value[key]));
315 }
316
317 else if (typeof value[key] === 'object') {
318
319 // Create sub level menus
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 // Build material library menu UI
339 if (sampleFiles && libraryDropdown) {
340 createLibraryMenu(sampleFiles, libraryDropdown);
341 }
342
343 // Update selected geometry menu UI
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 // Hide preview_panel DOM element
358 if (preview_panel)
359 preview_panel.style.display = 'none';
360 }
361
362 // TODO: Pass in a ui function instead of looking for a DOM element.
368 function displayNodeTypes(nodeTypes) {
369 // Get the list container
370 var nodeList = document.getElementById('nodeTypesList');
371 if (!nodeList) {
372 return;
373 }
374
375 // Clear all children of nodeList
376 while (nodeList.firstChild) {
377 nodeList.removeChild(nodeList.firstChild);
378 }
379
380 // Iterate over the node types and add them to the list
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 // Set up syntax highlighting for text areas
416 var cmeditor = setupXMLSyntax();
417 var cmeditor2 = setupJavascriptSyntax();
418 var cmeditor3 = setupGLTFSyntax();
419
425 function docDisplayUpdater(contents) {
426 if (cmeditor)
427 cmeditor.setValue(contents);
428 }
429
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
450 function jsDefinitionsDisplayUpdater(contents) {
451 if (cmeditor2)
452 cmeditor2.setValue(contents);
453 }
454
455 // Set up graphing UI
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 };
468 var editor = new MxShadingGraphEditor();
469
470 let monitor = new MxMaterialXMonitor('Custom MaterialX Graph Monitor');
471 monitor.setRenderer(customRenderer);
472 editor.initialize(canvas, ui, monitor, materialFilename, readOnly);
473
478 function addUIHandlers() {
479 // Add event listener to save canvas as image when button is clicked
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 // TODO: Make this a user option
493 var auto_arrange_size = 80;
494
495 // Add load materialx graph event listener
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 // Add load materialx graph from text event listener
505 var texAreaNumber = 0;
506 var loadMaterialXDocumentFromText = document.getElementById('loadMaterialXDocumentFromText');
507 if (loadMaterialXDocumentFromText) {
508 loadMaterialXDocumentFromText.addEventListener('click', function () {
509 var mtlxdoc = document.getElementById('mtlxdoc').value;
510 // Generate a name for the graph
511 if (mtlxdoc.length > 0) {
512 var name = 'MaterialXGraph' + texAreaNumber++;
513 editor.loadGraphFromString('mtlx', mtlxdoc, name, auto_arrange_size);
515 }
516 });
517 }
518
519 // Add load definitions event listener
520 var loadMaterialXDefinitions = document.getElementById('loadMaterialXDefinitions');
521 if (loadMaterialXDefinitions) {
522 loadMaterialXDefinitions.addEventListener('click', function () {
523 editor.loadDefinitionsFromFile('mtlx');
524 });
525 }
526
527 // Add clear graph event listener
528 var clearGraphButton = document.getElementById('clearGraph');
529 if (clearGraphButton) {
530 clearGraphButton.addEventListener('click', function () {
531 editor.clearGraph();
533 });
534 }
535
536 // Add save materialx graph event listener
537 var saveMaterialXGraph = document.getElementById('saveMaterialXGraph');
538 if (saveMaterialXGraph) {
539 saveMaterialXGraph.addEventListener('click', function () {
540 var sl = document.getElementById('writeCustomLibs').checked;
541 var sp = document.getElementById('saveNodePositions').checked;
542 var wo = true;
543 var graphWriteOptions = { writeCustomLibs: sl, saveNodePositions: sp, writeOutputs: wo };
544 editor.saveGraphToFile('mtlx', graphWriteOptions);
545 });
546 }
547
548 // Add save materialx graph text event listener
549 var saveMaterialXGraphText = document.getElementById('saveMaterialXGraphText');
550 if (saveMaterialXGraphText) {
551 saveMaterialXGraphText.addEventListener('click', function () {
552 saveToStringUI();
553 });
554 }
555
556 // Add open subgraph event handler
557 var openSubgraph = document.getElementById('openSubgraph');
558 if (openSubgraph) {
559 openSubgraph.addEventListener('click', function () {
560 editor.openSubgraph();
561 });
562 }
563
564 // Add close subgraph event handler
565 var closeSubgraph = document.getElementById('closeSubgraph');
566 if (closeSubgraph) {
567 closeSubgraph.addEventListener('click', function () {
568 editor.closeSubgraph();
569 });
570 }
571
572 // Add reset view event handler
573 var resetView = document.getElementById('resetView');
574 if (resetView) {
575 resetView.addEventListener('click', function () {
576 editor.resetView();
577 });
578 }
579
580 // Add arrange graph event listener
581 var arrangeGraphButton = document.getElementById('arrangeGraph');
582 if (arrangeGraphButton) {
583 arrangeGraphButton.addEventListener('click', function () {
584 editor.arrangeGraph();
585 });
586 }
587
588 // Add center node event listener
589 var centerNodeButton = document.getElementById('centerNode');
590 if (centerNodeButton) {
591 centerNodeButton.addEventListener('click', function () {
592 editor.centerNode();
593 });
594 }
595
596 // Add collapse/expand nodes event listener
597 var collapseNodesButton = document.getElementById('collapseNodes');
598 if (collapseNodesButton) {
599 collapseNodesButton.addEventListener('click', function () {
600 editor.collapseExpandNodes(true);
601 });
602 }
603 var expandNodesButton = document.getElementById('expandNodes');
604 if (expandNodesButton) {
605 expandNodesButton.addEventListener('click', function () {
606 editor.collapseExpandNodes(false);
607 });
608 }
609
610 // Add copy selected event listener
611 var copySelectedButton = document.getElementById('copySelected');
612 if (copySelectedButton) {
613 copySelectedButton.addEventListener('click', function () {
614 editor.copyToClipboard();
615 });
616 }
617
618 // Add paste selected event listener
619 var pasteSelectedButton = document.getElementById('pasteSelected');
620 if (pasteSelectedButton) {
621 pasteSelectedButton.addEventListener('click', function () {
622 editor.pasteFromClipboard();
623 });
624 }
625
626 // Add create subgraph event listener
627 var createNodeGraphButton = document.getElementById('createNodeGraph');
628 if (createNodeGraphButton) {
629 createNodeGraphButton.addEventListener('click', function () {
630 editor.createNodeGraph();
631 });
632 }
633
634 // Add extract subgraph event listener
635 var extractNodeGraphButton = document.getElementById('extractNodeGraph');
636 if (extractNodeGraphButton) {
637 extractNodeGraphButton.addEventListener('click', function () {
638 editor.extractNodeGraph();
639 });
640 }
641
642 /*
643 // Add load serialization event listener
644 var loadSerialization = document.getElementById('loadSerialization');
645 loadSerialization.addEventListener('click', function () {
646 editor.loadSerialization();
647 });
648
649 // Add download graph event listener
650 var downloadGraph = document.getElementById('downloadGraph');
651 downloadGraph.addEventListener('click', function () {
652 editor.saveSerialization();
653 }); */
654
655 // Add xml to graph event listener
656 var xmlToGraph = document.getElementById('xmltograph');
657 if (xmlToGraph) {
658 xmlToGraph.addEventListener('click', function () {
659 var name = 'MaterialXGraph' + texAreaNumber++;
660 var mtlxdoc = document.getElementById('mtlxdoc').value;
661 editor.loadGraphFromString('mtlx', mtlxdoc, 'MaterialXGraph', auto_arrange_size);
663 });
664 }
665
666 function updateRenderableItemUI() {
667 let renderableItems = editor.findRenderableItems();
668 renderableItemUpdater(renderableItems);
669 }
670
671 function saveToStringUI() {
672 var cl = document.getElementById('writeCustomLibs').checked;
673 var sp = document.getElementById('saveNodePositions').checked;
674 var wo = true;
675 var graphWriteOptions = { writeCustomLibs: cl, saveNodePositions: sp, writeOutputs: wo };
676 console.log('Save with options: ', graphWriteOptions);
677 var result = editor.saveGraphToString('mtlx', graphWriteOptions);
678
679 cmeditor.setValue(result[0]);
680
681 if (customRenderer) {
682 customRenderer.setSourceColorSpace(editor.getSourceColorSpace());
683 customRenderer.setTargetDistanceUnit(editor.getTargetDistanceUnit());
684 customRenderer.updateMaterialFromText(result[0]);
685 updateRenderableItemUI();
686 }
687 }
688
689 // Add graph to xml event listener
690 var graphtoxml = document.getElementById('graphtoxml');
691 if (graphtoxml) {
692 graphtoxml.addEventListener('click', function () {
693 saveToStringUI();
694 });
695 }
696
697 let graphtoxml2 = document.getElementById('graphtoxml2');
698 if (graphtoxml2) {
699 graphtoxml2.addEventListener('click', function () {
700 saveToStringUI();
701 graphtoxml2.classList.remove('btn-outline-warning');
702 graphtoxml2.classList.add('btn-outline-secondary');
703 });
704 }
705
706 // Add graph to gltf event listener
707 var graphtogltf = document.getElementById('graphtogltf');
708 if (graphtogltf) {
709 graphtogltf.addEventListener('click', function () {
710 var graphWriteOptions = { writeCustomLibs: false, saveNodePositions: false, writeOutputs: true };
711 var result = editor.saveGraphToString('gltf', graphWriteOptions);
712 gltfDisplayUpdater(result[0]);
713 if (result[1]) {
714 consoleLog(result[1], 1, false);
715 }
716 });
717 }
718
719 // Add gltf to graph listener
720 var gltftograph = document.getElementById('gltftograph');
721 if (gltftograph) {
722 gltftograph.addEventListener('click', function () {
723 var gltfdoc = document.getElementById('gltfgraph').value;
724 if (gltfdoc.length > 0) {
725 editor.loadGraphFromString('gltf', gltfdoc, 'GLTFGraph', auto_arrange_size);
727 }
728 });
729 }
730
731 // Handle turntable option
732 let turntableEnabledUI = document.getElementById('turntableEnabled');
733 if (turntableEnabledUI) {
734 turntableEnabledUI.addEventListener('click', (e) => {
735 // Toggle inverting the button colors no toggling danger
736 turntableEnabledUI.classList.toggle('btn-secondary');
737 if (customRenderer)
738 customRenderer.toggleTurntable();
739 });
740 }
741
742 // Handle render disabled option
743 let disableRenderingUI = document.getElementById('disableRendering');
744 if (disableRenderingUI) {
745 disableRenderingUI.addEventListener('click', (e) => {
746 // Toggle inverting the button colors
747 disableRenderingUI.classList.toggle('btn-danger');
748 if (customRenderer)
749 customRenderer.toggleRendering();
750 });
751 }
752
753 // Handle background display option
754 let toggleBackgroundTextureUI = document.getElementById('toggleBackgroundTexture');
755 if (toggleBackgroundTextureUI) {
756 toggleBackgroundTextureUI.addEventListener('click', (e) => {
757 toggleBackgroundTextureUI.classList.toggle('btn-primary');
758 if (customRenderer)
759 customRenderer.toggleBackgroundTexture();
760 });
761 }
762
763 // Handle reset camera option
764 let resetCameraUI = document.getElementById('resetCamera');
765 if (resetCameraUI) {
766 resetCameraUI.addEventListener('click', (e) => {
767 if (customRenderer) {
768 customRenderer.resetCamera();
769 }
770 });
771 }
772
773 // Handle renderable geometry option
774 function loadFromMenu(e) {
775 var uiItem = e.target.value;
776 if (uiItem == '_loadFromFile_') {
777 // Create a file dialog to get the filename
778 var fileInput = document.createElement('input');
779 fileInput.type = 'file';
780 fileInput.accept = '.glb';
781
782 fileInput.onchange = function(event) {
783 var file = event.target.files[0];
784 if (file) {
785 var fileURL = URL.createObjectURL(file);
786 if (customRenderer)
787 customRenderer.setRenderGeometry(fileURL);
788 console.log('Change geometry to:', fileURL, 'from file:', file.name);
789 }
790 }
791 fileInput.click();
792 }
793 else {
794 // Convert to lowercase and remove spaces
795 var geometryURL = uiItem.toLowerCase().replace(/\s/g, '');
796 var geometryPath = 'Geometry/' + geometryURL + '.glb';
797 console.log('Change geometry to:', geometryPath);
798 if (customRenderer)
799 customRenderer.setRenderGeometry(geometryPath);
800 }
801 }
802
803 // Handle geometry item changed
804 let geometryItemSelect = document.getElementById('loadGeometry');
805 if (geometryItemSelect) {
806
807 // Add built-in geometry options
808 var geometryItems = ['Teapot', 'Shader Ball', 'Sphere', 'Plane', 'Cube', 'Cylinder', 'Twist', 'Custom...'];
809 for (var i = 0; i < geometryItems.length; i++) {
810 var option = document.createElement('option');
811 option.value = geometryValues[i];
812 option.text = geometryItems[i];
813 geometryItemSelect.appendChild(option);
814 }
815
816 // Add event handler for selection
817 geometryItemSelect.addEventListener('change', (e) => {
818 loadFromMenu(e);
819 if (e.target.value == '_loadFromFile_')
820 e.target.value = 'Custom Geometry'
821 });
822
823 // Set initial geometry.
824 if (selectGeometryUI) {
825 // Set the default geometry
826 geometryItemSelect.value = geometryId;
827 }
828 }
829
830 // Handle material selection change
831 let renderableItemSelect = document.getElementById('renderableItem');
832 if (renderableItemSelect) {
833 renderableItemSelect.addEventListener('change', (e) => {
834 let index = e.target.value;
835 if (customRenderer)
836 customRenderer.setRenderMaterial(index);
837 });
838 }
839
840 // Get the canvas element and its container
841 var canvas = document.getElementById('mygraphcanvas');
842 var canvasContainer = document.getElementById('canvasContainer');
843 var colContainer = document.getElementById('colContainer');
844
845 // Create a new ResizeObserver
846 var observer = new ResizeObserver(function (entries) {
847 //for (var entry of entries) {
848 // Get the new width and height of the column
849 //let newWidth = entry.contentRect.right;
850 //let newHeight = entry.contentRect.height;
851
852 var parent = canvas.parentNode;
853 let newWidth = parent.offsetWidth;
854 let newHeight = parent.offsetHeight;
855
856 // Set the canvas size to match the column
857 //canvas.width = newWidth;
858 //canvas.height = newHeight;
859
860 // Mark the editor as dirty to redraw the graph.
861 console.log('Resize node graph canvas to:', newWidth, newHeight);
862 editor.setDirty(newWidth, newHeight);
863 //console.log('Resized node graph canvas to:', canvas.width, canvas.height);
864 //}
865 });
866
867 // Start observing the canvas container
868 observer.observe(canvasContainer);
869
870 }
871
872 function setupGLTFSyntax() {
873 // Initialize CodeMirror for GLTF syntax highlighting
874 let cmeditor = null;
875 const gltfTextArea = document.getElementById('gltfgraph');
876 if (gltfTextArea) {
877 cmeditor = CodeMirror.fromTextArea(gltfTextArea, {
878 mode: 'application/json',
879 lineNumbers: true,
880 dragDrop: false,
881 theme: 'dracula'
882 });
883
884 // Optional: Set an initial value for the textarea
885 const initialGLTF = '';
886 gltfTextArea.value = initialGLTF;
887 cmeditor.setValue(initialGLTF);
888
889 // Update CodeMirror whenever the textarea content changes
890 cmeditor.on('change', (e) => {
891 gltfTextArea.value = cmeditor.getValue();
892 });
893
894 var pasteButton = document.getElementById('gltfgraph_paste');
895 if (pasteButton)
896 addPasteHandler(pasteButton, cmeditor);
897
898 }
899 return cmeditor;
900 }
901
902 function setupJavascriptSyntax() {
903 // Initialize CodeMirror for JS syntax highlighting
904 const elem = document.getElementById('mtlxlib');
905 if (!elem) {
906 return;
907 }
908 let cmeditor = CodeMirror.fromTextArea(elem, {
909 mode: 'application/javascript',
910 lineNumbers: true,
911 dragDrop: false,
912 theme: 'dracula',
913 readOnly: true
914 });
915
916 elem.value = '';
917 cmeditor.setValue('');
918
919 // Update CodeMirror whenever the textarea content changes
920 cmeditor.on('change', () => {
921 elem.value = cmeditor.getValue();
922 });
923
924 return cmeditor;
925 }
926
927
928 function setupXMLSyntax() {
929 // Initialize CodeMirror for XML syntax highlighting
930 const materialXTextArea = document.getElementById('mtlxdoc');
931 let cmeditor = CodeMirror.fromTextArea(materialXTextArea, {
932 mode: 'application/xml',
933 lineNumbers: true,
934 dragDrop: true,
935 theme: 'night'
936 });
937
938 // Optional: Set an initial value for the textarea
939 const initialXML = '';
940 materialXTextArea.value = initialXML;
941 cmeditor.setValue(initialXML);
942
943 // Update CodeMirror whenever the textarea content changes
944 cmeditor.on('change', (e) => {
945 materialXTextArea.value = cmeditor.getValue();
946 });
947
948 var pasteButton = document.getElementById('mtlxdoc_paste');
949 if (pasteButton)
950 addPasteHandler(pasteButton, cmeditor);
951
952 return cmeditor;
953 }
954
955 addUIHandlers();
956 addCopyHandlers();
957}
This class provides a monitoring interface for the graph editor.
debugMessage(text, path)
Output a debug message to the console.
getPath(node, parentGraph)
Get a '/' separated path.
getParentPath(node)
Get the parent path of a node.
Custom monitor class for MaterialX graph.
onNodeRemoved(node, parentGraph)
onConnectionChange(node, parentGraph)
onNodeRenamed(node, newName)
onDocumentChange(attribute, value, prevValue)
onPropertyChanged(nodeName, propertyName, newValue, previousValue, node)
onPropertyInfoChanged(nodeName, propertyName, propertyInfoName, newValue, previousValue, node)
This class is a wrapper around the LiteGraph library to provide a MaterialX node editor.
function export initializeNodeEditor(materialFilename, geometryId, customRenderer, user_icon_map=null, sampleFiles=null, readOnly=false)
Function to initialize the MaterialX node editor.
function toggleRequireUpdateUI()