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 // To avoid toggle a change when the colorspace is not set
30 if (attribute == 'colorspace' && !value)
31 {
32 value = 'lin_rec709';
33 }
34
35 if (!this.monitoring || (prevValue == value)) {
36 return;
37 }
38
39 if (this.renderer)
40 {
42 }
43 if (this.debug) {
44 this.debugMessage('Monitor> Document attribute "' + attribute + '" changed from: ' + prevValue + ' to: ' + value, '');
45 }
46 }
47
48 onConnectionChange(node, parentGraph) {
49 if (!this.monitoring) {
50 return;
51 }
52
53 if (this.renderer)
54 {
56 }
57 if (this.debug) {
58 this.debugMessage('Monitor> Connection change: ', this.getPath(node, parentGraph));
59 }
60 }
61
62 onNodeRemoved(node, parentGraph) {
63 if (!this.monitoring) {
64 return;
65 }
66
67 if (this.renderer)
68 {
70 }
71 if (this.debug) {
72 this.debugMessage('Monitor> Node removed: ', this.getPath(node, parentGraph));
73 }
74 }
75
76 onNodeRenamed(node, newName) {
77 if (!this.monitoring) {
78 return;
79 }
80
81 if (this.renderer)
82 {
84 }
85
86 if (this.debug) {
87 let parentPath = this.getParentPath(node);
88 let path = parentPath + node.title;
89 let newpath = parentPath + newName;
90 this.debugMessage('Monitor> Node renamed: ', path + ' to: ' + newpath);
91 }
92 }
93
94 onPropertyInfoChanged(nodeName, propertyName, propertyInfoName, newValue, previousValue, node) {
95 if (!this.monitoring || (previousValue == newValue)) {
96 return;
97 }
98
99 if (this.renderer)
100 {
102 }
103
104
105 if (this.debug) {
106 let path = this.getParentPath(node) + nodeName;
107 console.log('Monitor> Property Info changed:', path, '. Property: ' + propertyName +
108 '. Property Info: ' + propertyInfoName +
109 '. Value: ' + newValue + '. Previous Value: ' + previousValue, '. Category:', node.nodedef_node);
110 }
111 }
112
113 onPropertyChanged(nodeName, propertyName, newValue, previousValue, node) {
114 if (!this.monitoring || (previousValue == newValue)) {
115 return;
116 }
117
118 let path = this.getParentPath(node) + nodeName;
119
120 if (this.renderer) {
121 if (typeof newValue == 'string') {
123 if (this.debug) {
124 console.log('Renderer> Build required for string change:', path, '. Property: ' + propertyName +
125 '. Value: ' + newValue + '. Previous Value: ' + previousValue + '. Node: ' + node.nodedef_node);
126 }
127 }
128 else {
129 if (node.nodedef_node != 'input')
130 path = path + '/' + propertyName;
131 this.renderer.updateShader(path, newValue);
132 }
133 }
134 else {
135 if (this.debug) {
136 console.log('Monitor> Property changed:', path, '. Property: ' + propertyName +
137 '. Value: ' + newValue + '. Previous Value: ' + previousValue + '. Node: ' + node.nodedef_node);
138 }
139 }
140 }
141}
142
143
154export function initializeNodeEditor(materialFilename, geometryId, customRenderer, user_icon_map = null, sampleFiles = null,
155 readOnly = false) {
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 // add items in user icon map. Overwrite any existing items
165 for (var key in user_icon_map) {
166 my_icon_map[key] = user_icon_map[key];
167 }
168 }
169
170 // Check if URI exists
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 // Renderable item UI updater
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 // Truncate the name so it will fit into UI.
202 if (uiItem.length > 20)
203 uiItem = uiItem.substring(0, 20) + '...';
204 option.text = uiItem;
205 renderableItemSelect.appendChild(option);
206 }
207 }
208 }
209
210 // Logger
211 // TODO: Pass in a logger object instead of looking for a DOM element.
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 // Scroll to latest entry.
233 console_area.scrollTop = console_area.scrollHeight;
234 }
235 else {
236 console.log(text);
237 }
238 }
239
240 /* function createMenuStructure(obj) {
241 let items = [];
242 for (let key in obj) {
243 if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
244 // It's a nested object, create a submenu
245 let subItems = createMenuStructure(obj[key]); // Recursively handle nested objects
246 items.push(createSubMenu(key, subItems));
247 } else {
248 items.push(createMenuItem(key, obj[key]));
249 }
250 console.log('<<< END SCAN');
251 }
252 return items;
253 } */
254
255
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);
268 MxShadingGraphEditor.theEditor.handler.loadLibraryDocument(MxShadingGraphEditor.theEditor, filename);
269 // Collapse the dropdown menu
270 let dropdownMenu = document.getElementById('libraryDropdown');
271 dropdownMenu.classList.remove('show');
272 };
273 return menuItem;
274 }
275
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
308 function createLibraryMenu(sampleFiles, libraryDropdown) {
309 for (let key in sampleFiles) {
310 // Create top level menus
311 let li = createSubMenu(key, true);
312 libraryDropdown.appendChild(li);
313
314 let value = sampleFiles[key];
315
316 // Add items to the submenu
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 // Check if value is a string
330 if (typeof value[key] === 'string') {
331 subMenuList.appendChild(createMenuItem(key, value[key]));
332 }
333
334 else if (typeof value[key] === 'object') {
335
336 // Create sub level menus
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 // Build material library menu UI
356 if (sampleFiles && libraryDropdown) {
357 createLibraryMenu(sampleFiles, libraryDropdown);
358 }
359
360 // Update selected geometry menu UI
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 // Hide preview_panel DOM element
375 if (preview_panel)
376 preview_panel.style.display = 'none';
377 }
378
379 // TODO: Pass in a ui function instead of looking for a DOM element.
385 function displayNodeTypes(nodeTypes) {
386 // Get the list container
387 var nodeList = document.getElementById('nodeTypesList');
388 if (!nodeList) {
389 return;
390 }
391
392 // Clear all children of nodeList
393 while (nodeList.firstChild) {
394 nodeList.removeChild(nodeList.firstChild);
395 }
396
397 // Iterate over the node types and add them to the list
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 // Set up syntax highlighting for text areas
433 var cmeditor = setupXMLSyntax();
434 var cmeditor2 = setupJavascriptSyntax();
435 var cmeditor3 = setupGLTFSyntax();
436
442 function docDisplayUpdater(contents) {
443 if (cmeditor)
444 cmeditor.setValue(contents);
445 }
446
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
467 function jsDefinitionsDisplayUpdater(contents) {
468 if (cmeditor2)
469 cmeditor2.setValue(contents);
470 }
471
472 // Set up graphing UI
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 };
485 var editor = new MxShadingGraphEditor();
486
487 let monitor = new MxMaterialXMonitor('Custom MaterialX Graph Monitor');
488 monitor.setRenderer(customRenderer);
489 editor.initialize(canvas, ui, monitor, materialFilename, readOnly);
490
495 function addUIHandlers() {
496 // Add event listener to save canvas as image when button is clicked
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 // TODO: Make this a user option
510 var auto_arrange_size = 80;
511
512 // Add load materialx graph event listener
513 var loadMaterialXDocumentFromFile = document.getElementById('loadMaterialXDocumentFromFile');
514 if (loadMaterialXDocumentFromFile) {
515 loadMaterialXDocumentFromFile.addEventListener('click', function () {
516 editor.loadGraphFromFile('mtlx', auto_arrange_size, true);
517 //toggleRequireUpdateUI();
518 });
519 }
520
521 // Add load materialx zip event listener
522 var loadMaterialXDocumentFromZip = document.getElementById('loadMaterialXDocumentFromZip');
523 if (loadMaterialXDocumentFromZip) {
524 loadMaterialXDocumentFromZip.addEventListener('click', function () {
525 editor.loadGraphFromFile('zip', auto_arrange_size, true);
526 //toggleRequireUpdateUI();
527 });
528 }
529
530 // Add load materialx graph from text event listener
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 // Generate a name for the graph
537 if (mtlxdoc.length > 0) {
538 var name = 'MaterialXGraph' + texAreaNumber++;
539 editor.loadGraphFromString('mtlx', mtlxdoc, name, auto_arrange_size, true);
540 //toggleRequireUpdateUI();
541 }
542 });
543 }
544
545 // Add load definitions event listener
546 var loadMaterialXDefinitions = document.getElementById('loadMaterialXDefinitions');
547 if (loadMaterialXDefinitions) {
548 loadMaterialXDefinitions.addEventListener('click', function () {
549 editor.loadDefinitionsFromFile('mtlx');
550 });
551 }
552
553 // Add clear graph event listener
554 var clearGraphButton = document.getElementById('clearGraph');
555 if (clearGraphButton) {
556 clearGraphButton.addEventListener('click', function () {
557 editor.clearGraph();
559 });
560 }
561
562 // Add save materialx graph event listener
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 // Add save materialx graph text event listener
575 var saveMaterialXGraphText = document.getElementById('saveMaterialXGraphText');
576 if (saveMaterialXGraphText) {
577 saveMaterialXGraphText.addEventListener('click', function () {
578 saveToStringUI();
579 });
580 }
581
582 // Search graph
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 // Add open subgraph event handler
592 var openSubgraph = document.getElementById('openSubgraph');
593 if (openSubgraph) {
594 openSubgraph.addEventListener('click', function () {
595 editor.openSubgraph();
596 });
597 }
598
599 // Add close subgraph event handler
600 var closeSubgraph = document.getElementById('closeSubgraph');
601 if (closeSubgraph) {
602 closeSubgraph.addEventListener('click', function () {
603 editor.closeSubgraph();
604 });
605 }
606
607 // Add reset view event handler
608 var resetView = document.getElementById('resetView');
609 if (resetView) {
610 resetView.addEventListener('click', function () {
611 editor.resetView();
612 });
613 }
614
615 // Add arrange graph event listener
616 var arrangeGraphButton = document.getElementById('arrangeGraph');
617 if (arrangeGraphButton) {
618 arrangeGraphButton.addEventListener('click', function () {
619 editor.arrangeGraph();
620 });
621 }
622
623 // Add center node event listener
624 var centerNodeButton = document.getElementById('centerNode');
625 if (centerNodeButton) {
626 centerNodeButton.addEventListener('click', function () {
627 editor.centerNode();
628 });
629 }
630
631 // Add collapse/expand nodes event listener
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 // Add select all event listener
646 var selectNodesButton = document.getElementById('selectNodes');
647 if (selectNodesButton) {
648 selectNodesButton.addEventListener('click', function () {
649 editor.selectNodes();
650 });
651 }
652
653 // Add copy selected event listener
654 var copySelectedButton = document.getElementById('copySelected');
655 if (copySelectedButton) {
656 copySelectedButton.addEventListener('click', function () {
657 editor.copyToClipboard();
658 });
659 }
660
661 // Add paste selected event listener
662 var pasteSelectedButton = document.getElementById('pasteSelected');
663 if (pasteSelectedButton) {
664 pasteSelectedButton.addEventListener('click', function () {
665 editor.pasteFromClipboard();
666 });
667 }
668
669 // Add create subgraph event listener
670 var createNodeGraphButton = document.getElementById('createNodeGraph');
671 if (createNodeGraphButton) {
672 createNodeGraphButton.addEventListener('click', function () {
673 editor.createNodeGraph();
674 });
675 }
676
677 // Add extract subgraph event listener
678 var extractNodeGraphButton = document.getElementById('extractNodeGraph');
679 if (extractNodeGraphButton) {
680 extractNodeGraphButton.addEventListener('click', function () {
681 editor.extractNodeGraph();
682 });
683 }
684
685 /*
686 // Add load serialization event listener
687 var loadSerialization = document.getElementById('loadSerialization');
688 loadSerialization.addEventListener('click', function () {
689 editor.loadSerialization();
690 });
691
692 // Add download graph event listener
693 var downloadGraph = document.getElementById('downloadGraph');
694 downloadGraph.addEventListener('click', function () {
695 editor.saveSerialization();
696 }); */
697
698 // Add xml to graph event listener
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 //toggleRequireUpdateUI();
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 // Add graph to xml event listener
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 // Add graph to gltf event listener
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 // Add gltf to graph listener
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 //toggleRequireUpdateUI();
770 }
771 });
772 }
773
774 // Handle turntable option
775 let turntableEnabledUI = document.getElementById('turntableEnabled');
776 if (turntableEnabledUI) {
777 turntableEnabledUI.addEventListener('click', (e) => {
778 // Toggle inverting the button colors no toggling danger
779 turntableEnabledUI.classList.toggle('btn-secondary');
780 if (customRenderer)
781 customRenderer.toggleTurntable();
782 });
783 }
784
785 // Handle render disabled option
786 let disableRenderingUI = document.getElementById('disableRendering');
787 if (disableRenderingUI) {
788 disableRenderingUI.addEventListener('click', (e) => {
789 // Toggle inverting the button colors
790 disableRenderingUI.classList.toggle('btn-danger');
791 if (customRenderer)
792 customRenderer.toggleRendering();
793 });
794 }
795
796 // Handle background display option
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 // Handle reset camera option
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 // Handle renderable geometry option
817 function loadFromMenu(e) {
818 var uiItem = e.target.value;
819 if (uiItem == '_loadFromFile_') {
820 // Create a file dialog to get the filename
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 // Convert to lowercase and remove spaces
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 // Handle geometry item changed
847 let geometryItemSelect = document.getElementById('loadGeometry');
848 if (geometryItemSelect) {
849
850 // Add built-in geometry options
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 // Add event handler for selection
860 geometryItemSelect.addEventListener('change', (e) => {
861 loadFromMenu(e);
862 if (e.target.value == '_loadFromFile_')
863 e.target.value = 'Custom Geometry'
864 });
865
866 // Set initial geometry.
867 if (selectGeometryUI) {
868 // Set the default geometry
869 geometryItemSelect.value = geometryId;
870 }
871 }
872
873 // Handle material selection change
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 // Get the canvas element and its container
887 var canvas = document.getElementById('mygraphcanvas');
888 var canvasContainer = document.getElementById('canvasContainer');
889 var colContainer = document.getElementById('colContainer');
890
891 // Create a new ResizeObserver
892 var observer = new ResizeObserver(function (entries) {
893 //for (var entry of entries) {
894 // Get the new width and height of the column
895 //let newWidth = entry.contentRect.right;
896 //let newHeight = entry.contentRect.height;
897
898 var parent = canvas.parentNode;
899 let newWidth = parent.offsetWidth;
900 let newHeight = parent.offsetHeight;
901
902 // Set the canvas size to match the column
903 //canvas.width = newWidth;
904 //canvas.height = newHeight;
905
906 // Mark the editor as dirty to redraw the graph.
907 console.log('Resize node graph canvas to:', newWidth, newHeight);
908 editor.setDirty(newWidth, newHeight);
909 //console.log('Resized node graph canvas to:', canvas.width, canvas.height);
910 //}
911 });
912
913 // Start observing the canvas container
914 observer.observe(canvasContainer);
915
916 }
917
918 function setupGLTFSyntax() {
919 // Initialize CodeMirror for GLTF syntax highlighting
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 // Optional: Set an initial value for the textarea
931 const initialGLTF = '';
932 gltfTextArea.value = initialGLTF;
933 cmeditor.setValue(initialGLTF);
934
935 // Update CodeMirror whenever the textarea content changes
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 // Initialize CodeMirror for JS syntax highlighting
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 // Update CodeMirror whenever the textarea content changes
966 cmeditor.on('change', () => {
967 elem.value = cmeditor.getValue();
968 });
969
970 return cmeditor;
971 }
972
973
974 function setupXMLSyntax() {
975 // Initialize CodeMirror for XML syntax highlighting
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 // Optional: Set an initial value for the textarea
985 const initialXML = '';
986 materialXTextArea.value = initialXML;
987 cmeditor.setValue(initialXML);
988
989 // Update CodeMirror whenever the textarea content changes
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}
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()