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', 'donut', '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 > 20)
186 uiItem = uiItem.substring(0, 20) + '...';
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 zip event listener
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 // Add load materialx graph from text event listener
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 // Generate a name for the graph
520 if (mtlxdoc.length > 0) {
521 var name = 'MaterialXGraph' + texAreaNumber++;
522 editor.loadGraphFromString('mtlx', mtlxdoc, name, auto_arrange_size);
524 }
525 });
526 }
527
528 // Add load definitions event listener
529 var loadMaterialXDefinitions = document.getElementById('loadMaterialXDefinitions');
530 if (loadMaterialXDefinitions) {
531 loadMaterialXDefinitions.addEventListener('click', function () {
532 editor.loadDefinitionsFromFile('mtlx');
533 });
534 }
535
536 // Add clear graph event listener
537 var clearGraphButton = document.getElementById('clearGraph');
538 if (clearGraphButton) {
539 clearGraphButton.addEventListener('click', function () {
540 editor.clearGraph();
542 });
543 }
544
545 // Add save materialx graph event listener
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 // Add save materialx graph text event listener
558 var saveMaterialXGraphText = document.getElementById('saveMaterialXGraphText');
559 if (saveMaterialXGraphText) {
560 saveMaterialXGraphText.addEventListener('click', function () {
561 saveToStringUI();
562 });
563 }
564
565 // Search graph
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 // Add open subgraph event handler
575 var openSubgraph = document.getElementById('openSubgraph');
576 if (openSubgraph) {
577 openSubgraph.addEventListener('click', function () {
578 editor.openSubgraph();
579 });
580 }
581
582 // Add close subgraph event handler
583 var closeSubgraph = document.getElementById('closeSubgraph');
584 if (closeSubgraph) {
585 closeSubgraph.addEventListener('click', function () {
586 editor.closeSubgraph();
587 });
588 }
589
590 // Add reset view event handler
591 var resetView = document.getElementById('resetView');
592 if (resetView) {
593 resetView.addEventListener('click', function () {
594 editor.resetView();
595 });
596 }
597
598 // Add arrange graph event listener
599 var arrangeGraphButton = document.getElementById('arrangeGraph');
600 if (arrangeGraphButton) {
601 arrangeGraphButton.addEventListener('click', function () {
602 editor.arrangeGraph();
603 });
604 }
605
606 // Add center node event listener
607 var centerNodeButton = document.getElementById('centerNode');
608 if (centerNodeButton) {
609 centerNodeButton.addEventListener('click', function () {
610 editor.centerNode();
611 });
612 }
613
614 // Add collapse/expand nodes event listener
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 // Add select all event listener
629 var selectNodesButton = document.getElementById('selectNodes');
630 if (selectNodesButton) {
631 selectNodesButton.addEventListener('click', function () {
632 editor.selectNodes();
633 });
634 }
635
636 // Add copy selected event listener
637 var copySelectedButton = document.getElementById('copySelected');
638 if (copySelectedButton) {
639 copySelectedButton.addEventListener('click', function () {
640 editor.copyToClipboard();
641 });
642 }
643
644 // Add paste selected event listener
645 var pasteSelectedButton = document.getElementById('pasteSelected');
646 if (pasteSelectedButton) {
647 pasteSelectedButton.addEventListener('click', function () {
648 editor.pasteFromClipboard();
649 });
650 }
651
652 // Add create subgraph event listener
653 var createNodeGraphButton = document.getElementById('createNodeGraph');
654 if (createNodeGraphButton) {
655 createNodeGraphButton.addEventListener('click', function () {
656 editor.createNodeGraph();
657 });
658 }
659
660 // Add extract subgraph event listener
661 var extractNodeGraphButton = document.getElementById('extractNodeGraph');
662 if (extractNodeGraphButton) {
663 extractNodeGraphButton.addEventListener('click', function () {
664 editor.extractNodeGraph();
665 });
666 }
667
668 /*
669 // Add load serialization event listener
670 var loadSerialization = document.getElementById('loadSerialization');
671 loadSerialization.addEventListener('click', function () {
672 editor.loadSerialization();
673 });
674
675 // Add download graph event listener
676 var downloadGraph = document.getElementById('downloadGraph');
677 downloadGraph.addEventListener('click', function () {
678 editor.saveSerialization();
679 }); */
680
681 // Add xml to graph event listener
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 // Add graph to xml event listener
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 // Add graph to gltf event listener
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 // Add gltf to graph listener
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 // Handle turntable option
758 let turntableEnabledUI = document.getElementById('turntableEnabled');
759 if (turntableEnabledUI) {
760 turntableEnabledUI.addEventListener('click', (e) => {
761 // Toggle inverting the button colors no toggling danger
762 turntableEnabledUI.classList.toggle('btn-secondary');
763 if (customRenderer)
764 customRenderer.toggleTurntable();
765 });
766 }
767
768 // Handle render disabled option
769 let disableRenderingUI = document.getElementById('disableRendering');
770 if (disableRenderingUI) {
771 disableRenderingUI.addEventListener('click', (e) => {
772 // Toggle inverting the button colors
773 disableRenderingUI.classList.toggle('btn-danger');
774 if (customRenderer)
775 customRenderer.toggleRendering();
776 });
777 }
778
779 // Handle background display option
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 // Handle reset camera option
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 // Handle renderable geometry option
800 function loadFromMenu(e) {
801 var uiItem = e.target.value;
802 if (uiItem == '_loadFromFile_') {
803 // Create a file dialog to get the filename
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 // Convert to lowercase and remove spaces
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 // Handle geometry item changed
830 let geometryItemSelect = document.getElementById('loadGeometry');
831 if (geometryItemSelect) {
832
833 // Add built-in geometry options
834 var geometryItems = ['Teapot', 'Shader Ball', 'Sphere', 'Plane', 'Cube', 'Cylinder', 'Donut', '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 // Add event handler for selection
843 geometryItemSelect.addEventListener('change', (e) => {
844 loadFromMenu(e);
845 if (e.target.value == '_loadFromFile_')
846 e.target.value = 'Custom Geometry'
847 });
848
849 // Set initial geometry.
850 if (selectGeometryUI) {
851 // Set the default geometry
852 geometryItemSelect.value = geometryId;
853 }
854 }
855
856 // Handle material selection change
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 // Get the canvas element and its container
870 var canvas = document.getElementById('mygraphcanvas');
871 var canvasContainer = document.getElementById('canvasContainer');
872 var colContainer = document.getElementById('colContainer');
873
874 // Create a new ResizeObserver
875 var observer = new ResizeObserver(function (entries) {
876 //for (var entry of entries) {
877 // Get the new width and height of the column
878 //let newWidth = entry.contentRect.right;
879 //let newHeight = entry.contentRect.height;
880
881 var parent = canvas.parentNode;
882 let newWidth = parent.offsetWidth;
883 let newHeight = parent.offsetHeight;
884
885 // Set the canvas size to match the column
886 //canvas.width = newWidth;
887 //canvas.height = newHeight;
888
889 // Mark the editor as dirty to redraw the graph.
890 console.log('Resize node graph canvas to:', newWidth, newHeight);
891 editor.setDirty(newWidth, newHeight);
892 //console.log('Resized node graph canvas to:', canvas.width, canvas.height);
893 //}
894 });
895
896 // Start observing the canvas container
897 observer.observe(canvasContainer);
898
899 }
900
901 function setupGLTFSyntax() {
902 // Initialize CodeMirror for GLTF syntax highlighting
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 // Optional: Set an initial value for the textarea
914 const initialGLTF = '';
915 gltfTextArea.value = initialGLTF;
916 cmeditor.setValue(initialGLTF);
917
918 // Update CodeMirror whenever the textarea content changes
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 // Initialize CodeMirror for JS syntax highlighting
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 // Update CodeMirror whenever the textarea content changes
949 cmeditor.on('change', () => {
950 elem.value = cmeditor.getValue();
951 });
952
953 return cmeditor;
954 }
955
956
957 function setupXMLSyntax() {
958 // Initialize CodeMirror for XML syntax highlighting
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 // Optional: Set an initial value for the textarea
968 const initialXML = '';
969 materialXTextArea.value = initialXML;
970 cmeditor.setValue(initialXML);
971
972 // Update CodeMirror whenever the textarea content changes
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}
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()