137export
function initializeNodeEditor(materialFilename, geometryId, customRenderer, user_icon_map =
null, sampleFiles =
null,
140 "_default_":
"./Icons/materialx_logo.webp",
141 "_default_graph_":
"./Icons/nodegraph_white.svg"
144 let geometryValues = [
'teapot',
'shaderball',
'sphere',
'plane',
'cube',
'cylinder',
'twist',
'_loadFromFile_']
148 for (var key in user_icon_map) {
149 my_icon_map[key] = user_icon_map[key];
154 function uriExists(uri) {
158 return Promise.resolve(
true);
160 return Promise.resolve(
false);
164 console.log(
'Error checking URI:', error);
165 return Promise.resolve(
false);
170 function renderableItemUpdater(renderableItems) {
171 let renderableItemSelect = document.getElementById(
'renderableItem');
172 if (renderableItemSelect) {
174 const TRUNCATION_LENGTH = 12;
176 while (renderableItemSelect.firstChild) {
177 renderableItemSelect.removeChild(renderableItemSelect.firstChild);
179 for (let i = 0; i < renderableItems.length; i++) {
180 let item = renderableItems[i];
181 let option = document.createElement(
'option');
185 if (uiItem.length > 10)
186 uiItem = uiItem.substring(0, 10) +
'...';
187 option.text = uiItem;
188 renderableItemSelect.appendChild(option);
195 function consoleLog(text, severity, clear =
null) {
196 if (severity === 2) {
197 text =
'> Error: ' + text
199 else if (severity === 1) {
200 text =
'> Warning: ' + text
207 let console_area = document.getElementById(
'console_area');
210 console_area.value = text +
'\n';
213 console_area.value = console_area.value + text +
'\n';
216 console_area.scrollTop = console_area.scrollHeight;
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);
253 let dropdownMenu = document.getElementById(
'libraryDropdown');
254 dropdownMenu.classList.remove(
'show');
265 function createSubMenu(title, auto_close =
false) {
267 let li = document.createElement(
'li');
268 li.className =
"dropend dropdown";
271 let subMenu = document.createElement(
'a');
272 subMenu.className =
"dropdown-item dropdown-toggle";
273 subMenu.setAttribute(
'data-bs-toggle',
"dropdown");
275 subMenu.setAttribute(
'data-bs-auto-close',
'outside');
277 subMenu.setAttribute(
'aria-expanded',
'false');
278 subMenu.setAttribute(
'aria-haspopup',
'true');
279 subMenu.innerHTML = title;
280 li.appendChild(subMenu);
291 function createLibraryMenu(sampleFiles, libraryDropdown) {
292 for (let key in sampleFiles) {
294 let li = createSubMenu(key,
true);
295 libraryDropdown.appendChild(li);
297 let value = sampleFiles[key];
300 if (typeof value ===
'string') {
301 let value = sampleFiles[key];
302 li.appendChild(createMenuItem(key, value));
304 else if (typeof value ===
'object') {
306 let subMenuList = document.createElement(
'ul');
307 subMenuList.className =
'dropdown-menu';
308 subMenuList.id = key;
313 if (typeof value[key] ===
'string') {
314 subMenuList.appendChild(createMenuItem(key, value[key]));
317 else if (typeof value[key] ===
'object') {
320 let sli = createSubMenu(key,
false);
321 subMenuList.appendChild(sli);
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]));
328 sli.appendChild(ssubMenuList);
332 li.appendChild(subMenuList);
339 if (sampleFiles && libraryDropdown) {
340 createLibraryMenu(sampleFiles, libraryDropdown);
344 let selectGeometryUI =
false;
345 if (customRenderer) {
347 let geometryURL = geometryId;
348 if (geometryId.length > 0 && geometryValues.includes(geometryId)) {
349 geometryURL =
'Geometry/' + geometryId +
'.glb';
350 selectGeometryUI =
true;
352 var viewer = customRenderer.initialize(materialFilename, geometryURL, readOnly);
353 console.log(
'Setup renderer:', viewer);
356 let preview_panel = document.getElementById(
"preview_panel");
359 preview_panel.style.display =
'none';
368 function displayNodeTypes(nodeTypes) {
370 var nodeList = document.getElementById(
'nodeTypesList');
376 while (nodeList.firstChild) {
377 nodeList.removeChild(nodeList.firstChild);
381 for (var typeName in nodeTypes) {
383 var rowItem = document.createElement(
"tr");
385 var cellItem = document.createElement(
"td");
386 cellItem.textContent = typeName;
387 rowItem.appendChild(cellItem);
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;
396 var link = document.createElement(
"a");
397 link.target =
"_blank";
398 link.href = nodeDefHref;
399 link.textContent = nodeDefNode +
" ( " + nodeDefName +
" )";
400 cellItem.appendChild(link);
403 cellItem.textContent = nodeDefName;
407 cellItem.textContent = nodeDefString;
409 rowItem.appendChild(cellItem);
411 nodeList.appendChild(rowItem);
416 var cmeditor = setupXMLSyntax();
417 var cmeditor2 = setupJavascriptSyntax();
418 var cmeditor3 = setupGLTFSyntax();
425 function docDisplayUpdater(contents) {
427 cmeditor.setValue(contents);
435 function gltfDisplayUpdater(contents) {
436 if (!contents || contents.length == 0) {
440 cmeditor3.setValue(contents);
442 console.log(contents);
450 function jsDefinitionsDisplayUpdater(contents) {
452 cmeditor2.setValue(contents);
456 var canvas = document.getElementById(
'mygraphcanvas');
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,
471 monitor.setRenderer(customRenderer);
472 editor.initialize(canvas, ui, monitor, materialFilename, readOnly);
478 function addUIHandlers() {
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');
487 link.download =
'graph_capture.png';
493 var auto_arrange_size = 80;
496 var loadMaterialXDocumentFromFile = document.getElementById(
'loadMaterialXDocumentFromFile');
497 if (loadMaterialXDocumentFromFile) {
498 loadMaterialXDocumentFromFile.addEventListener(
'click',
function () {
499 editor.loadGraphFromFile(
'mtlx', auto_arrange_size);
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;
511 if (mtlxdoc.length > 0) {
512 var name =
'MaterialXGraph' + texAreaNumber++;
513 editor.loadGraphFromString(
'mtlx', mtlxdoc, name, auto_arrange_size);
520 var loadMaterialXDefinitions = document.getElementById(
'loadMaterialXDefinitions');
521 if (loadMaterialXDefinitions) {
522 loadMaterialXDefinitions.addEventListener(
'click',
function () {
523 editor.loadDefinitionsFromFile(
'mtlx');
528 var clearGraphButton = document.getElementById(
'clearGraph');
529 if (clearGraphButton) {
530 clearGraphButton.addEventListener(
'click',
function () {
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;
543 var graphWriteOptions = { writeCustomLibs: sl, saveNodePositions: sp, writeOutputs: wo };
544 editor.saveGraphToFile(
'mtlx', graphWriteOptions);
549 var saveMaterialXGraphText = document.getElementById(
'saveMaterialXGraphText');
550 if (saveMaterialXGraphText) {
551 saveMaterialXGraphText.addEventListener(
'click',
function () {
557 var openSubgraph = document.getElementById(
'openSubgraph');
559 openSubgraph.addEventListener(
'click',
function () {
560 editor.openSubgraph();
565 var closeSubgraph = document.getElementById(
'closeSubgraph');
567 closeSubgraph.addEventListener(
'click',
function () {
568 editor.closeSubgraph();
573 var resetView = document.getElementById(
'resetView');
575 resetView.addEventListener(
'click',
function () {
581 var arrangeGraphButton = document.getElementById(
'arrangeGraph');
582 if (arrangeGraphButton) {
583 arrangeGraphButton.addEventListener(
'click',
function () {
584 editor.arrangeGraph();
589 var centerNodeButton = document.getElementById(
'centerNode');
590 if (centerNodeButton) {
591 centerNodeButton.addEventListener(
'click',
function () {
597 var collapseNodesButton = document.getElementById(
'collapseNodes');
598 if (collapseNodesButton) {
599 collapseNodesButton.addEventListener(
'click',
function () {
600 editor.collapseExpandNodes(
true);
603 var expandNodesButton = document.getElementById(
'expandNodes');
604 if (expandNodesButton) {
605 expandNodesButton.addEventListener(
'click',
function () {
606 editor.collapseExpandNodes(
false);
611 var copySelectedButton = document.getElementById(
'copySelected');
612 if (copySelectedButton) {
613 copySelectedButton.addEventListener(
'click',
function () {
614 editor.copyToClipboard();
619 var pasteSelectedButton = document.getElementById(
'pasteSelected');
620 if (pasteSelectedButton) {
621 pasteSelectedButton.addEventListener(
'click',
function () {
622 editor.pasteFromClipboard();
627 var createNodeGraphButton = document.getElementById(
'createNodeGraph');
628 if (createNodeGraphButton) {
629 createNodeGraphButton.addEventListener(
'click',
function () {
630 editor.createNodeGraph();
635 var extractNodeGraphButton = document.getElementById(
'extractNodeGraph');
636 if (extractNodeGraphButton) {
637 extractNodeGraphButton.addEventListener(
'click',
function () {
638 editor.extractNodeGraph();
656 var xmlToGraph = document.getElementById(
'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);
666 function updateRenderableItemUI() {
667 let renderableItems = editor.findRenderableItems();
668 renderableItemUpdater(renderableItems);
671 function saveToStringUI() {
672 var cl = document.getElementById(
'writeCustomLibs').checked;
673 var sp = document.getElementById(
'saveNodePositions').checked;
675 var graphWriteOptions = { writeCustomLibs: cl, saveNodePositions: sp, writeOutputs: wo };
676 console.log(
'Save with options: ', graphWriteOptions);
677 var result = editor.saveGraphToString(
'mtlx', graphWriteOptions);
679 cmeditor.setValue(result[0]);
681 if (customRenderer) {
682 customRenderer.setSourceColorSpace(editor.getSourceColorSpace());
683 customRenderer.setTargetDistanceUnit(editor.getTargetDistanceUnit());
684 customRenderer.updateMaterialFromText(result[0]);
685 updateRenderableItemUI();
690 var graphtoxml = document.getElementById(
'graphtoxml');
692 graphtoxml.addEventListener(
'click',
function () {
697 let graphtoxml2 = document.getElementById(
'graphtoxml2');
699 graphtoxml2.addEventListener(
'click',
function () {
701 graphtoxml2.classList.remove(
'btn-outline-warning');
702 graphtoxml2.classList.add(
'btn-outline-secondary');
707 var graphtogltf = document.getElementById(
'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]);
714 consoleLog(result[1], 1,
false);
720 var gltftograph = document.getElementById(
'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);
732 let turntableEnabledUI = document.getElementById(
'turntableEnabled');
733 if (turntableEnabledUI) {
734 turntableEnabledUI.addEventListener(
'click', (e) => {
736 turntableEnabledUI.classList.toggle(
'btn-secondary');
738 customRenderer.toggleTurntable();
743 let disableRenderingUI = document.getElementById(
'disableRendering');
744 if (disableRenderingUI) {
745 disableRenderingUI.addEventListener(
'click', (e) => {
747 disableRenderingUI.classList.toggle(
'btn-danger');
749 customRenderer.toggleRendering();
754 let toggleBackgroundTextureUI = document.getElementById(
'toggleBackgroundTexture');
755 if (toggleBackgroundTextureUI) {
756 toggleBackgroundTextureUI.addEventListener(
'click', (e) => {
757 toggleBackgroundTextureUI.classList.toggle(
'btn-primary');
759 customRenderer.toggleBackgroundTexture();
764 let resetCameraUI = document.getElementById(
'resetCamera');
766 resetCameraUI.addEventListener(
'click', (e) => {
767 if (customRenderer) {
768 customRenderer.resetCamera();
774 function loadFromMenu(e) {
775 var uiItem = e.target.value;
776 if (uiItem ==
'_loadFromFile_') {
778 var fileInput = document.createElement(
'input');
779 fileInput.type =
'file';
780 fileInput.accept =
'.glb';
782 fileInput.onchange =
function(event) {
783 var file =
event.target.files[0];
785 var fileURL = URL.createObjectURL(file);
787 customRenderer.setRenderGeometry(fileURL);
788 console.log(
'Change geometry to:', fileURL,
'from file:', file.name);
795 var geometryURL = uiItem.toLowerCase().replace(/\s/g,
'');
796 var geometryPath =
'Geometry/' + geometryURL +
'.glb';
797 console.log(
'Change geometry to:', geometryPath);
799 customRenderer.setRenderGeometry(geometryPath);
804 let geometryItemSelect = document.getElementById(
'loadGeometry');
805 if (geometryItemSelect) {
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);
817 geometryItemSelect.addEventListener(
'change', (e) => {
819 if (e.target.value ==
'_loadFromFile_')
820 e.target.value =
'Custom Geometry'
824 if (selectGeometryUI) {
826 geometryItemSelect.value = geometryId;
831 let renderableItemSelect = document.getElementById(
'renderableItem');
832 if (renderableItemSelect) {
833 renderableItemSelect.addEventListener(
'change', (e) => {
834 let index = e.target.value;
836 customRenderer.setRenderMaterial(index);
841 var canvas = document.getElementById(
'mygraphcanvas');
842 var canvasContainer = document.getElementById(
'canvasContainer');
843 var colContainer = document.getElementById(
'colContainer');
846 var observer =
new ResizeObserver(
function (entries) {
852 var parent = canvas.parentNode;
853 let newWidth = parent.offsetWidth;
854 let newHeight = parent.offsetHeight;
861 console.log(
'Resize node graph canvas to:', newWidth, newHeight);
862 editor.setDirty(newWidth, newHeight);
868 observer.observe(canvasContainer);
872 function setupGLTFSyntax() {
875 const gltfTextArea = document.getElementById(
'gltfgraph');
877 cmeditor = CodeMirror.fromTextArea(gltfTextArea, {
878 mode:
'application/json',
885 const initialGLTF =
'';
886 gltfTextArea.value = initialGLTF;
887 cmeditor.setValue(initialGLTF);
890 cmeditor.on(
'change', (e) => {
891 gltfTextArea.value = cmeditor.getValue();
894 var pasteButton = document.getElementById(
'gltfgraph_paste');
896 addPasteHandler(pasteButton, cmeditor);
902 function setupJavascriptSyntax() {
904 const elem = document.getElementById(
'mtlxlib');
908 let cmeditor = CodeMirror.fromTextArea(elem, {
909 mode:
'application/javascript',
917 cmeditor.setValue(
'');
920 cmeditor.on(
'change', () => {
921 elem.value = cmeditor.getValue();
928 function setupXMLSyntax() {
930 const materialXTextArea = document.getElementById(
'mtlxdoc');
931 let cmeditor = CodeMirror.fromTextArea(materialXTextArea, {
932 mode:
'application/xml',
939 const initialXML =
'';
940 materialXTextArea.value = initialXML;
941 cmeditor.setValue(initialXML);
944 cmeditor.on(
'change', (e) => {
945 materialXTextArea.value = cmeditor.getValue();
948 var pasteButton = document.getElementById(
'mtlxdoc_paste');
950 addPasteHandler(pasteButton, cmeditor);