MaterialXLab API  0.0.1
APIs For MaterialXLab Libraries
Loading...
Searching...
No Matches
JsMaterialXNodeEditor.js
Go to the documentation of this file.
1
2// Globals
3var ne_mx = null; // MaterialX module
4var doc = null; // Working document
5var stdlib = null; // Standard libraries
6var customlibs = []; // Custom definition loaded in by user
7var customDocLibs = []; // Definitions loaded in as part of document
8var graph = null; // Current graph
9var graphcanvas = null; // Current graph canvas
10
26{
28 {
29 this.name = name;
30 this.debug = true;
31 this.monitoring = true;
32 this.renderer = null;
33 }
34
41 debugMessage(text, path)
42 {
43 if (this.debug) {
44 console.log(text + path);
45 }
46 }
47
54 getPath(node, parentGraph) {
55
56 let path = '';
57 if (parentGraph.length > 0)
58 path = path + parentGraph + '/'
59 path += node.title;
60 return path;
61 }
62
70 onDocumentChange(attribute, value, prevValue)
71 {
72 if (!this.monitoring)
73 {
74 return;
75 }
76
77 if (this.debug) {
78 this.debugMessage('Monitor> Document attribute "' + attribute + '" changed from: ' + prevValue + ' to: ' + value, '');
79 }
80 }
81
88 onConnectionChange(node, parentGraph)
89 {
90 if (!this.monitoring)
91 {
92 return;
93 }
94
95 if (this.debug) {
96 this.debugMessage('Monitor> Connection change: ', this.getPath(node, parentGraph));
97 }
98 }
99
110 onConnectOutput(slot, input_type, input, target_node, target_slot, node)
111 {
112 if (!this.monitoring)
113 {
114 return;
115 }
116
117 if (this.debug) {
118 let targetPath = this.getPath(node, this.getParentPath(node));
119 let targetOutput = node.outputs[slot].name;
120 let sourcePath = this.getPath(target_node, this.getParentPath(target_node));
121 let sourceInput = input.name;
122
123 this.debugMessage('Monitor> Output connection change: ' +
124 targetPath + "[" + slot + "]." + targetOutput + ' to: ' +
125 sourcePath + "[" + target_slot + "]." + sourceInput + " (" + input_type + ")", "");
126 }
127 }
128
140 onConnectInput(target_slot, output_type, output, source, slot, node) {
141
142 if (!this.monitoring)
143 {
144 return;
145 }
146
147 // checker -> input
148 if (this.debug) {
149 let sourcePath = this.getPath(source, this.getParentPath(source));
150 let sourceOutput = output.name;
151 let targetPath = this.getPath(node, this.getParentPath(node));
152 let targetInput = node.inputs[target_slot].name;
153
154 this.debugMessage('Monitor> Input connection change: ' +
155 sourcePath + "[" + slot + "]." + sourceOutput + ' to: ' +
156 targetPath + "[" + target_slot + "]." + targetInput + " (" + output_type + ")", "");
157 }
158 }
159
160
167 onNodeAdded(node, parentGraph)
168 {
169 if (!this.monitoring)
170 {
171 return;
172 }
173
174 // Go through all inputs on the node and remove hidden attribute
175 // Needed to allow cut-paste of nodes which were loaded in with
176 // hidden inputs.
177 /* for (let i = 0; i < node.inputs.length; i++) {
178 let input = node.inputs[i];
179 if (input.hidden) {
180 // Remove hidden attribute
181 delete input.hidden;
182 }
183 } */
184
185 if (this.debug) {
186 this.debugMessage('Monitor> Node added: ', this.getPath(node, parentGraph));
187 }
188 }
189
197 onNodeRemoved(node, parentGraph)
198 {
199 if (!this.monitoring)
200 {
201 return;
202 }
203
204 if (this.debug) {
205 this.debugMessage('Monitor> Node removed: ', this.getPath(node, parentGraph));
206 }
207 }
208
216 {
217 let path = '';
218 var is_subgraph = graphcanvas.graph._is_subgraph;
219 if (is_subgraph) {
220 path = graphcanvas.graph._subgraph_node.title + '/';
221 }
222 else
223 {
224 if (node.graph)
225 {
226 is_subgraph = node.graph._is_subgraph;
227 if (is_subgraph) {
228 path = node.graph._subgraph_node.title + '/';
229 }
230 }
231 }
232 return path;
233 }
234
242 onNodeRenamed(node, newName)
243 {
244 if (!this.monitoring)
245 {
246 return;
247 }
248
249 let parentPath = this.getParentPath(node);
250 let path = parentPath + node.title;
251 let newpath = parentPath + newName;
252 if (this.debug) {
253 this.debugMessage('Monitor> Node renamed: ', path + ' to: ' + newpath);
254 }
255 }
256
264 onNodeSelected(node, parentGraph)
265 {
266 if (!this.monitoring)
267 {
268 return;
269 }
270
271 if (this.debug) {
272 this.debugMessage('Monitor> Node selected: ', this.getPath(node, parentGraph));
273 }
274 }
275
283 onNodeDeselected(node, parentGraph)
284 {
285 if (!this.monitoring)
286 {
287 return;
288 }
289
290 if (this.debug) {
291 //this.debugMessage('-> Node unselected at path: ', this.getPath(node, parentGraph));
292 }
293 }
294
305 onPropertyChanged(nodeName, propertyName, newValue, previousValue, node)
306 {
307 if (!this.monitoring)
308 {
309 return;
310 }
311
312 let path = this.getParentPath(node) + nodeName;
313 if (this.debug) {
314 console.log('Monitor> Property changed:', path, '. Property: ' + propertyName +
315 '. Value: ' + newValue + '. Previous Value: ' + previousValue, '. Category:', node.nodedef_node);
316 }
317 }
318
330 onPropertyInfoChanged(nodeName, propertyName, propertyInfoName, newValue, previousValue, node)
331 {
332 if (!this.monitoring)
333 {
334 return;
335 }
336
337 let path = this.getParentPath(node) + nodeName;
338 if (this.debug) {
339 console.log('Monitor> Property Info changed:', path, '. Property: ' + propertyName +
340 '. Property Info: ' + propertyInfoName +
341 '. Value: ' + newValue + '. Previous Value: ' + previousValue, '. Category:', node.nodedef_node);
342 }
343 }
344
351 {
352 return this.name;
353 }
354
361 setRenderer(theRenderer)
362 {
363 this.renderer = theRenderer
364 }
365
371 {
372 return this.renderer;
373 }
374
382 {
383 this.monitoring = monitor;
384 }
385
392 {
393 return this.monitoring;
394 }
395
403 {
404 if (callback)
405 {
406 this.onConnectionChange = callback;
407 }
408 }
409
417 {
418 if (callback)
419 {
420 this.onNodeAdded = callback;
421 }
422 }
423
431 {
432 if (callback)
433 {
434 this.onNodeRemoved = callback;
435 }
436 }
437
445 {
446 if (callback)
447 {
448 this.onNodeRenamed = callback;
449 }
450 }
451
459 {
460 if (callback)
461 {
462 this.onNodeSelected = callback;
463 }
464 }
465
473 {
474 if (callback)
475 {
476 this.onNodeDeselected = callback;
477 }
478 }
479
487 {
488 if (callback)
489 {
490 this.onPropertyChanged = callback;
491 }
492 }
493
500 monitorGraph(theGraph, monitor)
501 {
502 // Update monitor state
503 this.monitoring = monitor;
504
505 if (!theGraph)
506 return;
507
508 theGraph.onConnectionChange = null;
509 theGraph.onNodeAdded = null;
510 theGraph.onNodeRemoved = null;
511
512 if (monitor) {
513
514 console.log('> Monitor graph: ', graph.title? graph.title : 'ROOT')
515
516 var that = this;
517
518 theGraph.onConnectionChange = function (node) {
519 let parentGraph = '';
520 var is_subgraph = node.graph._is_subgraph;
521 if (is_subgraph)
522 parentGraph = graphcanvas.graph._subgraph_node.title;
523 that.onConnectionChange(node, parentGraph);
524
525 var selected = graphcanvas.selected_nodes;
526 for (var s in selected) {
527 //console.log('update selected node:', selected[s].title);
528 MxShadingGraphEditor.theEditor.updatePropertyPanel(selected[s]);
529 break;
530 }
531 }
532
533 theGraph.onNodeAdded = function (node) {
534 let parentGraph = '';
535
536 if (node.type == 'graph/subgraph') {
537 // Use MaterialX naming for subgraphs
538
539 // Scan the subgraph for any nodes which are not in the node inputs list.
540 var node_subgraph = node.subgraph;
541 var node_graph = node.graph;
542 if (node_subgraph) {
543 //console.log('** Scan subgraph: ', node_subgraph)
544 for (var i in node_subgraph._nodes) {
545 let theNode = node_subgraph._nodes[i];
546 if (!node_graph.findNodeByTitle(theNode.title)) {
547 if (theNode.nodedef_node == 'input') {
548 node.addInput(theNode.title, theNode.nodedef_type);
549 //console.log('--> Promote input node to subgraph node.', theNode.title);
550 }
551 else if (theNode.nodedef_node == 'output') {
552 //console.log('--> Promote output node to subgraph node.', theNode.title);
553 node.addOutput(theNode.title, theNode.nodedef_type);
554 }
555 }
556 }
557 }
558 }
559
560 node.title = MxShadingGraphEditor.theEditor.handler.createValidName(node.title)
561 node.setSize(node.computeSize());
562 //console.log('-> Node Added:', node, '. Type:', node.type, '. Graph:', node.graph._2458graph);
563
564 // Expose node as an input or output on the subgraph
565 var is_subgraph = node.graph._is_subgraph;
566 if (is_subgraph) {
567 parentGraph = graphcanvas.graph._subgraph_node.title;
568
569 if (node.nodedef_node == 'input') {
570 //console.log('Adding input node to subgraph.', node.title, node.graph);
571 node.graph.addInput(node.title, node.nodedef_type);
572 }
573 else if (node.nodedef_node == 'output') {
574 //console.log('Adding output node to subgraph.');
575 node.graph.addOutput(node.title, node.nodedef_type);
576 }
577 }
578
579 if (node.type == 'graph/subgraph') {
580 that.monitorGraph(node.subgraph, monitor);
581 }
582
583 that.onNodeAdded(node, parentGraph);
584 }
585
586 //console.log('Monitor graph remove:', theGraph.title);
587 theGraph.onNodeRemoved = function (node) {
588
589 let parentGraph = '';
590 /* This is too late the graph reference has already been removed */
591 var is_subgraph = graphcanvas.graph._is_subgraph;
592 if (is_subgraph) {
593 parentGraph = graphcanvas.graph._subgraph_node.title;
594 if (node.nodedef_node == 'input') {
595 //console.log('Removing input node from subgraph.', parentGraph);
596 graphcanvas.graph.removeInput(node.title);
597 }
598 else if (node.nodedef_node == 'output') {
599 //console.log('Removing output node from subgraph.', parentGraph);
600 graphcanvas.graph.removeOutput(node.title);
601 }
602 }
603
604 that.onNodeRemoved(node, parentGraph);
605 }
606
607 for (var i in theGraph._nodes) {
608 var node = theGraph._nodes[i];
609 if (node.type == 'graph/subgraph') {
610 console.log('> Monitor subgraph:', node.title);
611 that.monitorGraph(node.subgraph, monitor);
612 node.onConnectInput = MxShadingGraphEditor.theEditor.onConnectInput;
613 }
614 }
615 }
616 }
617}
618
619
626{
627 constructor(id, extension) {
628 // Identifier
629 this.id = id;
630 // Extension of format that this handler is associated with
631 // Generally the same as the file extension (e.g. mtlx for MaterialX)
632 this.extension = extension;
633 // Editor
634 this.editor = null;
635
636 // Keep track of global color space and distance unit
637 this.DEFAULT_COLOR_SPACE = 'lin_rec709';
638 this.DEFAULT_DISTANCE_UNIT = 'meter';
639 this.sourceColorSpace = this.DEFAULT_COLOR_SPACE;
640 this.targetDistanceUnit = this.DEFAULT_DISTANCE_UNIT;
641 this.colorSpaces = [];
642 this.units = [];
643
644 // List of additional converters that can convert to the format
645 // that this handler can handle
646 this.converters = [];
647
648 // Graph monitoring instance
649 this.monitor = null;
650 }
651
658 addConverter(converter) {
659 // Add to front of list
660 this.converters.unshift(converter);
661 //this.converters.push(converter);
662 }
663
670 setMonitor(monitor)
671 {
672 this.monitor = monitor;
673 }
674
682 canExport(extension)
683 {
684 if (extension == 'mtlx')
685 {
686 return true;
687 }
688 if (this.getExporter(extension))
689 {
690 return true;
691 }
692 return false;
693 }
694
701 getExporter(extension='') {
702 for (let converter of this.converters) {
703 if (converter.exportType() == extension) {
704 return converter;
705 }
706 }
707 return null;
708 }
709
716 canImport(extension)
717 {
718 if (extension == 'mtlx' || extension == 'zip')
719 {
720 return true;
721 }
722 if (this.getImporter(extension))
723 {
724 return true;
725 }
726 return false;
727 }
728
735 getImporter(extension='') {
736 for (let converter of this.converters) {
737 if (converter.importType() == extension) {
738 return converter;
739 }
740 }
741 return null;
742 }
743
750 setColorSpaces(colorSpaces) {
751 this.colorSpaces = colorSpaces;
752 }
753
760 return this.colorSpaces;
761 }
762
769 setUnits(units) {
770 this.units = units;
771 }
772
779 return this.units;
780 }
781
789 let newSpace = this.DEFAULT_COLOR_SPACE;
790 if (colorSpace && colorSpace.length > 0)
791 newSpace = colorSpace;
792
793 if (this.monitor)
794 {
795 this.monitor.onDocumentChange('colorspace', colorSpace, this.sourceColorSpace);
796 }
797 this.sourceColorSpace = newSpace;
798 }
799
807 let newUnit = this.DEFAULT_DISTANCE_UNIT;
808 if (unit && unit.length > 0)
809 newUnit = unit;
810
811 if (this.monitor)
812 {
813 this.monitor.onDocumentChange('distanceunit', newUnit, this.targetDistanceUnit);
814 }
815 this.targetDistanceUnit = newUnit;
816 //console.log('Handler SET target distance unit:', this.targetDistanceUnit);
817 }
818
825 //console.log('Handler GET source colorspace:', this.sourceColorSpace);
826 return this.sourceColorSpace;
827 }
828
835 //console.log('Handler GET source distance unit:', this.targetDistanceUnit);
836 return this.targetDistanceUnit;
837 }
838
845 return this.extension;
846 }
847
854 initialize(editor) {
855 this.editor = editor;
856 }
857
865 return name;
866 }
867
876 getDefaultValue(value, _type) {
877 if (_type === 'string' || _type === 'filename') {
878 value = "'" + value + "'";
879 }
880 else if (this.isArray(_type)) {
881 if (value.length == 0) {
882 if (_type === 'color3')
883 value = "[0.0, 0.0, 0.0]";
884 else if (_type === 'color4')
885 value = "[0.0, 0.0, 0.0, 0.0]";
886 else if (_type === 'vector2')
887 value = "[0.0, 0.0]";
888 else if (_type === 'vector3')
889 value = "[0.0, 0.0, 0.0]";
890 else if (_type === 'vector4')
891 value = "[0.0, 0.0, 0.0, 0.0]";
892 else if (_type === 'matrix33')
893 value = "[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]";
894 else if (_type === 'matrix44')
895 value = "[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]";
896 }
897 else {
898 value = "[" + value + "]";
899 }
900 }
901 else if (_type === 'integer') {
902 if (value.length == 0) {
903 value = 0;
904 }
905 }
906 else if (_type === 'float') {
907 if (value.length == 0) {
908 value = 0.0;
909 }
910 }
911 else if (_type === 'boolean') {
912 if (value)
913 value = 'true';
914 else
915 value = 'false';
916 }
917
918 if (value.length == 0) {
919 //console.log('No value for input:', _name, 'type:', _type, 'value:', value);=
920 value = "''";
921 }
922 return value;
923 }
924};
925
933
940 constructor(id, extension) {
941 super(id, extension);
942 }
943
951 return new Promise((resolve, reject) => {
952 MaterialX().then((ne_mtlx) => {
953 // Save the MaterialX instance to the global variable
954 ne_mx = ne_mtlx;
955 console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Loaded MaterialX version: ' + ne_mx.getVersionString());
956 // Only attempt to auto-load the adsklib if a build-time manifest
957 // is available (window.ADSKLIB_FILES). This avoids fetching the
958 // directory index (which often returns 404 on static servers).
959 if (typeof window !== 'undefined' && Array.isArray(window.ADSKLIB_FILES) && window.ADSKLIB_FILES.length > 0) {
960 let adsklib = this.loadFolderToDocument('Libraries/adsklib');
961 } else {
962 console.log('Skipping auto-load of Libraries/adsklib (no manifest available)');
963 }
964 resolve();
965 }).catch((error) => {
966 reject(error);
967 });
968 });
969 }
970
977 loadLibraryDocument(editor, materialFilename) {
978
979 function loadInitialText(filePath, handler) {
980 try {
981 fetch(filePath)
982 .then(response => response.blob())
983 .then(blob => {
984 const reader = new FileReader();
985 reader.onload = function (e) {
986 console.log('Loaded document:', filePath);
987 editor.loadGraphFromString('mtlx', e.target.result, filePath, 80, true);
988 }
989 reader.readAsText(blob);
990 })
991 } catch (error) {
992 console.error('Error loading file %s:' % filePath, error);
993 }
994 }
995
996 loadInitialText(materialFilename, this);
997 }
998
1009 initialize(editor, materialFilename) {
1010 super.initialize(editor);
1011
1012 if (!ne_mx) {
1013
1014 this.loadMaterialX().then(() => {
1015
1016 // Additional logic after MaterialX is loaded
1017 editor.debugOutput("Loaded MaterialX version:" + ne_mx.getVersionString(), 0, true);
1018 doc = ne_mx.createDocument();
1019
1020 var generator = new ne_mx.EsslShaderGenerator.create();
1021 var genContext = new ne_mx.GenContext(generator);
1022 stdlib = ne_mx.loadStandardLibraries(genContext);
1023 editor.debugOutput('Loaded standard libraries definitions:' + stdlib.getNodeDefs().length, 0, false);
1024
1025 // Find units, unittype pairs available. Keep in unique list.
1026 let units = new Map();
1027 for (let ud of stdlib.getUnitDefs()) {
1028 let unittype = ud.getAttribute('unittype')
1029 for (let unit of ud.getChildren()) {
1030 units.set(unit.getName(), unittype);
1031 }
1032 }
1033 // Sort units
1034 this.setUnits(Array.from(units).sort());
1035 console.log('> Setup real-world units: ', this.getUnits());
1036
1037 // Find the colorspaces available. Keep in unique list. Hack as there
1038 // is no way to find a list of colorspaces.
1039 let colorSpaces = new Set();
1040 let docNodeDefs = stdlib.getNodeDefs();
1041 for (let i = 0; i < docNodeDefs.length; i++) {
1042 let cmnode = docNodeDefs[i];
1043 if (cmnode.getNodeGroup() === 'colortransform') {
1044 let name = cmnode.getName();
1045 name = name.replace('ND_', '');
1046 let namesplit = name.split('_to_');
1047 let type = 'color3';
1048 if (namesplit[1].includes('color4')) {
1049 namesplit[1] = namesplit[1].replace('_color4', '');
1050 type = 'color4';
1051 } else {
1052 namesplit[1] = namesplit[1].replace('_color3', '');
1053 }
1054 colorSpaces.add(namesplit[0]);
1055 colorSpaces.add(namesplit[1]);
1056 }
1057 }
1058 // Sort the colorspaces
1059 this.setColorSpaces(Array.from(colorSpaces).sort())
1060 console.log('Set up colorspaces: ', this.getColorSpaces());
1061
1062 var definitionsList = [];
1063 var result = this.createLiteGraphDefinitions(stdlib, false, true, definitionsList, 'mtlx', MxShadingGraphEditor.theEditor);
1064 var definitionsDisplayUpdater = editor.ui.definitionsDisplayUpdater;
1065 if (definitionsDisplayUpdater) {
1066 definitionsDisplayUpdater(result);
1067 }
1068
1069 editor.clearNodeTypes();
1070 try {
1071 eval(result);
1072 } catch (e) {
1073 editor.debugOutput('Error evaluating source: ' + e, 2, false);
1074 }
1075
1076 var nodeTypes = LiteGraph.registered_node_types;
1077 var i = 0;
1078 for (var typeName in nodeTypes) {
1079 i++;
1080 }
1081 editor.debugOutput("Registered node types:" + definitionsList.length, 0, false);
1082
1083 editor.displayNodeTypes();
1084
1085 if (materialFilename.length > 0) {
1086 this.loadLibraryDocument(editor, materialFilename);
1087 }
1088
1089 editor.updatePropertyPanel(null);
1090
1091 }).catch((error) => {
1092 editor.debugOutput("Error on initialization:" + error, 2);
1093 });
1094 }
1095 }
1096
1104
1105 let graphWriteOptions = { writeCustomLibs : false, saveNodePositions: false, writeOutputs : true };
1106 let mdoc = this.saveGraphToDocument(graph, graphWriteOptions);
1107 if (!mdoc) {
1108 console.log('Failed to save graph to document');
1109 return;
1110 }
1111 return this.findRenderableItemsInDoc(mdoc);
1112 }
1113
1121
1122 const materialNodes = mdoc.getMaterialNodes();
1123 let shaderList = [];
1124 let renderableItems = [];
1125
1126 for (let i = 0; i < materialNodes.length; ++i) {
1127 let materialNode = materialNodes[i];
1128 if (materialNode) {
1129 //console.log('Scan material: ', materialNode.getNamePath());
1130 let shaderNodes = ne_mx.getShaderNodes(materialNode)
1131 if (shaderNodes.length > 0) {
1132 let shaderNodePath = shaderNodes[0].getNamePath()
1133 if (!shaderList.includes(shaderNodePath)) {
1134 //console.log('-- add shader: ', shaderNodePath);
1135 shaderList.push(shaderNodePath);
1136 renderableItems.push(shaderNodePath);
1137 }
1138 }
1139 }
1140 }
1141 const nodeGraphs = mdoc.getNodeGraphs();
1142 for (let i = 0; i < nodeGraphs.length; ++i) {
1143 let nodeGraph = nodeGraphs[i];
1144 if (nodeGraph) {
1145 if (nodeGraph.hasAttribute('nodedef') || nodeGraph.hasSourceUri()) {
1146 continue;
1147 }
1148 // Skip any nodegraph that is connected to something downstream
1149 if (nodeGraph.getDownstreamPorts().length > 0) {
1150 continue
1151 }
1152 let outputs = nodeGraph.getOutputs();
1153 for (let j = 0; j < outputs.length; ++j) {
1154 let output = outputs[j];
1155 {
1156 renderableItems.push(output.getNamePath());
1157 }
1158 }
1159 }
1160 }
1161 const outputs = mdoc.getOutputs();
1162 for (let i = 0; i < outputs.length; ++i) {
1163 let output = outputs[i];
1164 if (output) {
1165 renderableItems.push(output.getNamePath());
1166 }
1167 }
1168
1169 return renderableItems;
1170 }
1171
1187 buildMetaData(colorSpace, unit, unitType, uiname, uimin, uimax, uifolder, _type) {
1188 // Create a struct with the metadata names as key and value
1189 var metaData = {};
1190 metaData['colorspace'] = colorSpace;
1191 metaData['unit'] = unit;
1192 metaData['unittype'] = unitType;
1193 metaData['uiname'] = uiname;
1194 if (_type == 'vector2' || _type == 'vector3' || _type == 'vector4' || _type == 'matrix33' || _type == 'matrix44') {
1195 if (uimin) {
1196 uimin = uimin.split(',').map(Number);
1197 }
1198 if (uimax) {
1199 uimax = uimax.split(',').map(Number);
1200 }
1201 }
1202 metaData['uimin'] = uimin;
1203 metaData['uimax'] = uimax;
1204 metaData['uifolder'] = uifolder;
1205
1206 // Return struct in an array
1207 return metaData;
1208 }
1209
1224 createLiteGraphDefinitions(doc, debug, addInputOutputs, definitionsList, libraryPrefix = 'mtlx',
1225 editor, icon = '')
1226 {
1227 var definition_code = ""
1228
1229 console.log('Creating LiteGraph definitions from MaterialX document:', doc);
1230
1231 // Get the node definitions from the MaterialX document
1232 var nodeDefs = doc.getNodeDefs();
1233
1234 if (debug)
1235 definition_code += "console.log('Loading MaterialX Definitions...');\n";
1236
1237 definition_code += "// MaterialX LiteGraph Functional Definitions\n"
1238 definition_code += "//\n";
1239 definition_code += "// MaterialX Version: " + ne_mx.getVersionString() + "\n";
1240 const date = new Date();
1241 definition_code += "// Generated on: " + date.toString() + "\n";
1242 definition_code += "//\n";
1243
1244 var TMAP = {}
1245 TMAP['float'] = 'float';
1246 TMAP['color3'] = 'color3';
1247 TMAP['color4'] = 'color4';
1248 TMAP['vector2'] = 'vector2';
1249 TMAP['vector3'] = 'vector3';
1250 TMAP['vector4'] = 'vector4';
1251 TMAP['matrix33'] = 'matrix33';
1252 TMAP['matrix44'] = 'matrix44';
1253 TMAP['integer'] = 'integer';
1254 TMAP['string'] = 'string';
1255 TMAP['boolean'] = 'boolean';
1256 TMAP['filename'] = 'filename';
1257 TMAP['BSDF'] = 'BSDF';
1258 TMAP['EDF'] = 'EDF';
1259 TMAP['VDF'] = 'VDF';
1260 TMAP['surfaceshader'] = 'surfaceshader';
1261 TMAP['volumeshader'] = 'volumeshader';
1262 TMAP['displacementshader'] = 'displacementshader';
1263 TMAP['lightshader'] = 'lightshader';
1264 TMAP['material'] = 'material';
1265 TMAP['vector2array'] = 'vector2array';
1266
1267 var CMAP = {}
1268 CMAP['integer'] = "#A32";
1269 CMAP['float'] = "#161";
1270 CMAP['vector2'] = "#265";
1271 CMAP['vector3'] = "#465";
1272 CMAP['vector4'] = "#275";
1273 CMAP['color3'] = "#37A";
1274 CMAP['color4'] = "#69A";
1275 CMAP['matrix33'] = "#333";
1276 CMAP['matrix44'] = "#444";
1277 CMAP['string'] = "#395";
1278 CMAP['filename'] = "#888";
1279 CMAP['boolean'] = "#060";
1280
1281 var inputTypes = ['float', 'color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44', 'integer', 'string', 'boolean', 'filename', 'BSDF', 'EDF', 'VDF', 'surfaceshader', 'volumeshader', 'displacementshader', 'lightshader', 'material', 'vector2array'];
1282 var outputTypes = ['float', 'color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44', 'integer', 'string', 'boolean', 'filename', 'BSDF', 'EDF', 'VDF', 'surfaceshader', 'volumeshader', 'displacementshader', 'lightshader', 'material', 'vector2array'];
1283
1284 // TODO: Support tokens
1285 var supporTokens = false;
1286 if (supporTokens) {
1287 inputTypes.push('token');
1288 TMAP['token'] = 'string';
1289 }
1290
1291 const INPUT_ND = 'ND_input_';
1292 const OUTPUT_ND = 'ND_output_';
1293 const INPUT_NODE_STRING = 'input';
1294 const OUTPUT_NODE_STRING = 'output';
1295 const LIBRARY_ICON = '';
1296
1297 // Register inputs (which have no nodedef)
1298 if (addInputOutputs) {
1299 for (var _type of inputTypes) {
1300 var id = libraryPrefix + '/input/input_' + _type;
1301 var functionName = ne_mx.createValidName(id);
1302 var titleName = 'input_' + _type;
1303 definition_code += "\n/**\n";
1304 definition_code += " * @function "+ functionName + "\n";
1305 definition_code += " * @description Library: " + libraryPrefix + ". Category: input. Type: " + _type + "\n";
1306 definition_code += " * LiteGraph id: " + id + "\n";
1307 definition_code += " */\n";
1308 definition_code += "function " + functionName + "() {\n";
1309 {
1310 definition_code += " this.nodedef_icon = '" + LIBRARY_ICON + "';\n";
1311 definition_code += " this.nodedef_name = '" + INPUT_ND + _type + "';\n";
1312 definition_code += " this.nodedef_node = '" + INPUT_NODE_STRING + "';\n";
1313 definition_code += " this.nodedef_type = '" + _type + "';\n";
1314 definition_code += " this.nodedef_group = '" + INPUT_NODE_STRING + "';\n";
1315 if (_type == 'token')
1316 _type = 'string';
1317 definition_code += " this.addInput('in', '" + TMAP[_type] + "');\n";
1318 var value = this.getDefaultValue('', _type);
1319 var metaData = this.buildMetaData('', '', '', '', null, null, '', null);
1320 metaData = JSON.stringify(metaData);
1321
1322 // TODO: It's not possible to add a default colorspace since you
1323 // don't know what node type it will feed into. i.e. the specification
1324 // is underdefined at this time (1.39)
1325 if (_type == 'filename')
1326 {
1327 ; // Nothing for now.
1328 }
1329
1330 definition_code += " this.addProperty('in', " + value + ", '" + _type + "'," + metaData + ");\n";
1331 definition_code += " this.addOutput('out', '" + TMAP[_type] + "');\n";
1332
1333 definition_code += " this.title = '" + titleName + "';\n"
1334 var desc = '"MaterialX:' + id + '"';
1335 definition_code += " this.desc = " + desc + ";\n";
1336
1337 var onNodeCreated = "function() {\n";
1338 onNodeCreated += " //console.log('Node created:', this);\n";
1339 onNodeCreated += " }";
1340 definition_code += " this.onNodeCreated = " + onNodeCreated + "\n";
1341 var onRemoved = "function() {\n";
1342 onRemoved += " //console.log('Node removed:', this);\n";
1343 onRemoved += " }";
1344 definition_code += " this.onRemoved = " + onRemoved + "\n";
1345
1346 // Property changed callback
1347 let monitor = editor.monitor;
1348 var onPropertyChanged = "function(name, value, prev_value) {\n";
1349 if (monitor)
1350 {
1351 onPropertyChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);\n";
1352 }
1353 else
1354 {
1355 onPropertyChanged += " console.log('+ Internal property changed:', this.title, name, value, prev_value, this);\n";
1356 }
1357 onPropertyChanged += " }";
1358 definition_code += " this.onPropertyChanged = " + onPropertyChanged + "\n";
1359
1360 // Property info / attribute changed callback
1361 var onPropertyInfoChanged = "function(name, info, value, prev_value) {\n";
1362 if (monitor)
1363 {
1364 onPropertyInfoChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);\n";
1365 }
1366 else
1367 {
1368 onPropertyInfoChanged += " console.log('+ Internal property info changed:', this.title, name, info, value, prev_value, this);\n";
1369 }
1370 onPropertyInfoChanged += " }"
1371 definition_code += " this.onPropertyInfoChanged = " + onPropertyInfoChanged + "\n";
1372
1373 // Output connection callback
1374 var onConnectOutput = "function(slot, input_type, input, target_node, target_slot) {\n";
1375 if (monitor)
1376 {
1377 onConnectOutput += " MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);\n";
1378 }
1379 else
1380 {
1381 onConnectOutput += " console.log('+ Output connection changed:', this.title, slot, input_type, input, target_node, target_slot);\n";
1382 }
1383 onConnectOutput += " }"
1384 definition_code += " this.onConnectOutput = " + onConnectOutput + "\n";
1385
1386 // Input connection callback
1387 var onConnectInput = "function(target_slot, output_type, output, source, slot) {\n";
1388 if (monitor)
1389 {
1390 onConnectInput += " MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);\n";
1391 }
1392 else
1393 {
1394 onConnectInput += " console.log('+ Input connection changed:', this.title, target_slot, output_type, output, source, slot);\n";
1395 }
1396 onConnectInput += " }"
1397 definition_code += " this.onConnectInput = " + onConnectInput + "\n";
1398
1399 definition_code += " this.color = '#004C94';\n";
1400 definition_code += " this.bgcolor = '#000';\n";
1401 if (_type in CMAP) {
1402 definition_code += " this.boxcolor = '" + CMAP[_type] + "';\n";
1403 }
1404 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1405
1406 definition_code += " this.onExecute = function() {\n";
1407 definition_code += " console.log('Executing node: ', this);\n";
1408 definition_code += " }\n";
1409 }
1410 definition_code += "}\n"
1411 definition_code += "LiteGraph.registerNodeType('" + id + "', " + functionName + ");\n";
1412 }
1413
1414 // Register outputs (which have no nodedef)
1415 for (var _type of outputTypes) {
1416 var id = libraryPrefix + '/output/output_' + _type;
1417 var functionName = ne_mx.createValidName(id);
1418 var titleName = 'output_' + _type;
1419
1420 definition_code += "\n/**\n";
1421 definition_code += " * @function "+ functionName + "\n";
1422 definition_code += " * @description Library: " + libraryPrefix + ". Category: output. Type: " + _type + "\n";
1423 definition_code += " * LiteGraph id: " + id + "\n";
1424 definition_code += " */\n";
1425
1426 definition_code += "function " + functionName + "() {\n";
1427 {
1428 definition_code += " this.title = '" + titleName + "';\n"
1429 var desc = '"MaterialX Node :' + id + '"';
1430 definition_code += " this.desc = " + desc + ";\n";
1431
1432 definition_code += " this.nodedef_icon = '" + LIBRARY_ICON + "';\n";
1433 definition_code += " this.nodedef_name = '" + OUTPUT_ND + + _type + "';\n";
1434 definition_code += " this.nodedef_node = '" + OUTPUT_NODE_STRING + "';\n";
1435 definition_code += " this.nodedef_type = '" + _type + "';\n";
1436 definition_code += " this.nodedef_group = '" + OUTPUT_NODE_STRING + "';\n";
1437 definition_code += " this.addInput('in', '" + TMAP[_type] + "');\n";
1438 var value = this.getDefaultValue('', _type);
1439 definition_code += " this.addProperty('in', " + value + ", '" + _type + "');\n";
1440 definition_code += " this.addOutput('out', '" + TMAP[_type] + "');\n";
1441
1442 var onNodeCreated = "function() {\n";
1443 onNodeCreated += " //console.log('Node created:', this);\n";
1444 onNodeCreated += " }";
1445 definition_code += " this.onNodeCreated = " + onNodeCreated + "\n";
1446 var onRemoved = "function() {\n";
1447 onRemoved += " //console.log('Node removed:', this);\n";
1448 onRemoved += " }";
1449 definition_code += " this.onRemoved = " + onRemoved + "\n";
1450
1451 let monitor = editor.monitor;
1452 var onPropertyChanged = "function(name, value, prev_value) {\n";
1453 if (monitor)
1454 {
1455 onPropertyChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);\n";
1456 }
1457 else
1458 {
1459 onPropertyChanged += " console.log('+ Internal property changed:', this.title, name, value, prev_value, this);\n";
1460 }
1461 onPropertyChanged += " }";
1462 definition_code += " this.onPropertyChanged = " + onPropertyChanged + "\n";
1463
1464 var onPropertyInfoChanged = "function(name, info, value, prev_value) {\n";
1465 if (monitor)
1466 {
1467 onPropertyInfoChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);\n";
1468 }
1469 else
1470 {
1471 onPropertyInfoChanged += " console.log('+ Internal property info changed:', this.title, name, info, value, prev_value, this);\n";
1472 }
1473 onPropertyInfoChanged += " }"
1474 definition_code += " this.onPropertyInfoChanged = " + onPropertyInfoChanged + "\n";
1475
1476
1477 // Output connection callback
1478 var onConnectOutput = "function(slot, input_type, input, target_node, target_slot) {\n";
1479 if (monitor)
1480 {
1481 onConnectOutput += " MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);\n";
1482 }
1483 else
1484 {
1485 onConnectOutput += " console.log('+ Output connection changed:', this.title, slot, input_type, input, target_node, target_slot);\n";
1486 }
1487 onConnectOutput += " }"
1488 definition_code += " this.onConnectOutput = " + onConnectOutput + "\n";
1489
1490 // Input connection callback
1491 var onConnectInput = "function(target_slot, output_type, output, source, slot) {\n";
1492 if (monitor)
1493 {
1494 onConnectInput += " MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);\n";
1495 }
1496 else
1497 {
1498 onConnectInput += " console.log('+ Input connection changed:', this.title, target_slot, output_type, output, source, slot);\n";
1499 }
1500 onConnectInput += " }"
1501 definition_code += " this.onConnectInput = " + onConnectInput + "\n";
1502
1503 definition_code += " this.color = '#013514';\n";
1504 definition_code += " this.bgcolor = '#000';\n";
1505 if (_type in CMAP) {
1506 definition_code += " this.boxcolor = '" + CMAP[_type] + "';\n";
1507 }
1508 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1509
1510 definition_code += " this.onExecute = function() {\n";
1511 definition_code += " console.log('Executing node:', this);\n";
1512 definition_code += " }\n";
1513 }
1514 definition_code += "}\n"
1515 definition_code += "LiteGraph.registerNodeType('" + id + "', " + functionName + ");\n";
1516 definitionsList.push(id);
1517 }
1518 }
1519
1520 // Iterate over all node definitions
1521 for (var nodeDef of nodeDefs) {
1522
1523 var nodeDefName = nodeDef.getName();
1524 var id = libraryPrefix + '/' + nodeDef.getNodeGroup() + '/' + nodeDefName;
1525 id = id.replace('ND_', '');
1526 var functionName = ne_mx.createValidName(id);
1527 var nodeType = nodeDef.getType();
1528 var titleName = nodeDef.getNodeString() + "_" + nodeType;
1529 var swatchLocation = 'https://kwokcb.github.io/MaterialX_Learn/resources/mtlx/nodedef_materials/';
1530 var outputs = nodeDef.getActiveOutputs();
1531 var outputName = outputs[0].getName(); // TODO: Handle swatch for multiple outputs
1532 var swatchId = swatchLocation + 'material_' + nodeDefName + '_' + outputName + '_genglsl.png';
1533 swatchId = swatchId.replace('ND_', '');
1534 if (debug)
1535 console.log('\n--- Registering node type:', id, '----');
1536
1537
1538 definition_code += "\n/**\n";
1539 definition_code += " * @function "+ functionName + "\n";
1540 definition_code += " * @description Library: " + libraryPrefix + ". Category: " + nodeString + ". Type: " + nodeType + "\n";
1541 definition_code += " * LiteGraph id: " + id + "\n";
1542 definition_code += " */\n";
1543
1544 definition_code += "function " + functionName + "() {\n";
1545 {
1546 var nodeGroup = nodeDef.getNodeGroup();
1547 var nodeString = nodeDef.getNodeString();
1548 var theIcon = icon;
1549 if (theIcon.length == 0) {
1550 for (var key in editor.ui.icon_map) {
1551 if (nodeString.toLowerCase().startsWith(key.toLowerCase())) {
1552 if (key in editor.ui.icon_map)
1553 theIcon = editor.ui.icon_map[key];
1554 //console.log('set icon:', theIcon, 'for:', key, nodeString);
1555 break;
1556 }
1557 else if (nodeGroup.toLowerCase().startsWith(key.toLowerCase())) {
1558 if (key in editor.ui.icon_map)
1559 theIcon = editor.ui.icon_map[key];
1560 //console.log('set icon:', theIcon, 'for:', key, nodeGroup);
1561 break;
1562 }
1563 }
1564 }
1565
1566 definition_code += " this.nodedef_icon = '" + theIcon + "';\n";
1567 definition_code += " this.nodedef_name = '" + nodeDefName + "';\n";
1568 definition_code += " this.nodedef_type = '" + nodeType + "';\n";
1569 definition_code += " this.nodedef_node = '" + nodeString + "';\n";
1570 definition_code += " this.nodedef_href = 'https://kwokcb.github.io/MaterialX_Learn/documents/definitions/" + nodeString + ".html';\n";
1571 definition_code += " this.nodedef_swatch = '" + swatchId + "';\n";
1572 definition_code += " this.nodedef_group = '" + nodeGroup + "';\n";
1573
1574 for (var input of nodeDef.getActiveInputs()) {
1575 var _name = input.getName();
1576 var _type = input.getType();
1577 if (_type in TMAP)
1578 _type = TMAP[_type];
1579 else
1580 console.log('Unmappable type:', _type)
1581 definition_code += " this.addInput('" + _name + "','" + _type + "');\n";
1582
1583 let value = input.getValueString();
1584 value = this.getDefaultValue(value, _type);
1585 let uiname = input.getAttribute('uiname');
1586
1587 let uimin = input.getAttribute('uimin');
1588 if (uimin.length == 0) {
1589 uimin = null;
1590 }
1591 let uimax = input.getAttribute('uimax');
1592 if (uimax.length == 0) {
1593 uimax = null;
1594 }
1595 let uisoftmin = input.getAttribute('uisoftmin');
1596 if (uisoftmin.length > 0) {
1597 uimin = uisoftmin;
1598 }
1599 let uisoftmax = input.getAttribute('uisoftmax');
1600 if (uisoftmax.length > 0) {
1601 uimax = uisoftmax;
1602 }
1603 var uifolder = input.getAttribute('uifolder');
1604 var metaData = this.buildMetaData('', '', '', uiname, uimin, uimax, uifolder, _type);
1605
1606 // Add colorspace on nodedefs
1607 let colorspace = input.getAttribute('colorspace');
1608 let nodeDefType = nodeDef.getType();
1609 if (_type == 'filename' && (nodeDefType == 'color3' || nodeDefType == 'color4'))
1610 {
1611 if (colorspace.length == 0)
1612 {
1613 colorspace = 'none';
1614 }
1615 }
1616 if (colorspace.length > 0)
1617 metaData['colorspace'] = colorspace;
1618
1619 // TODO: Add units, unitype on nodedefs. There is no
1620 // default unittype or units.
1621 let unitAttributes = ['unit', 'unittype'];
1622 for (let unitAttribute of unitAttributes)
1623 {
1624 let value = input.getAttribute(unitAttribute);
1625 if (value.length > 0)
1626 {
1627 metaData[unitAttribute] = value;
1628 }
1629 }
1630
1631 // Add in defaultgeomprop to denote geometric inputs.
1632 let defaultgeomprop = input.getAttribute('defaultgeomprop')
1633 metaData['defaultgeomprop'] = defaultgeomprop;
1634
1635 // Add enumerations
1636 let enums = input.getAttribute('enum');
1637 if (enums.length > 0)
1638 {
1639 metaData['enum'] = enums.split(',');
1640 metaData['enum'].map(function(x) { return x.trim(); });
1641 }
1642 let enumvalues = input.getAttribute('enumvalues');
1643 if (enumvalues.length > 0)
1644 {
1645 metaData['enumvalues'] = enumvalues.split(',');
1646 metaData['enumvalues'].map(function(x) { return x.trim(); });
1647 }
1648
1649 metaData = JSON.stringify(metaData);
1650 definition_code += " this.addProperty('" + _name + "', " + value + ", '" + _type + "'," + metaData + ");\n";
1651 }
1652 for (var output of nodeDef.getActiveOutputs()) {
1653 var _name = output.getName();
1654 var _type = output.getType();
1655 if (_type in TMAP)
1656 _type = TMAP[_type];
1657 else
1658 console.log('Unmappable type:', _type)
1659 //if(_type && _type.constructor === String)
1660 // _type = '"'+_type+'"';
1661 definition_code += " this.addOutput('" + _name + "','" + _type + "');\n";
1662 }
1663
1664 definition_code += " this.title = '" + titleName + "';\n"
1665 var desc = '"MaterialX:' + id + '"';
1666 definition_code += " this.desc = " + desc + ";\n";
1667
1668 //definition_code += " /**\n";
1669 //definition_code += " * @function " + "onNodeCreated" + "\n";
1670 //definition_code += " */";
1671
1672 var onNodeCreated = "function() {\n";
1673 onNodeCreated += " //console.log('Node created:', this);\n";
1674 onNodeCreated += "}";
1675 definition_code += " this.onNodeCreated = " + onNodeCreated + "\n";
1676 var onRemoved = "function() {\n";
1677 onRemoved += " //console.log('Node removed:', this);\n";
1678 onRemoved += " }";
1679 definition_code += " this.onRemoved = " + onRemoved + "\n";
1680
1681 let monitor = editor.monitor;
1682 var onPropertyChanged = "function(name, value, prev_value) {\n";
1683 if (monitor)
1684 {
1685 onPropertyChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);\n";
1686 }
1687 else
1688 {
1689 onPropertyChanged += " console.log('+ Internal property changed:', this.title, name, value, prev_value, this);\n";
1690 }
1691 onPropertyChanged += " }";
1692 definition_code += " this.onPropertyChanged = " + onPropertyChanged + "\n";
1693
1694 var onPropertyInfoChanged = "function(name, info, value, prev_value) {\n";
1695 if (monitor)
1696 {
1697 onPropertyInfoChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);\n";
1698 }
1699 else
1700 {
1701 onPropertyInfoChanged += " console.log('+ Internal property info changed:', this.title, name, info, value, prev_value, this);\n";
1702 }
1703 onPropertyInfoChanged += " }"
1704 definition_code += " this.onPropertyInfoChanged = " + onPropertyInfoChanged + "\n";
1705
1706 // Output connection callback
1707 var onConnectOutput = "function(slot, input_type, input, target_node, target_slot) {\n";
1708 if (monitor)
1709 {
1710 onConnectOutput += " MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);\n";
1711 }
1712 else
1713 {
1714 onConnectOutput += " console.log('+ Output connection changed:', this.title, slot, input_type, input, target_node, target_slot);\n";
1715 }
1716 onConnectOutput += " }"
1717 definition_code += " this.onConnectOutput = " + onConnectOutput + "\n";
1718
1719 // Input connection callback
1720 var onConnectInput = "function(target_slot, output_type, output, source, slot) {\n";
1721 if (monitor)
1722 {
1723 onConnectInput += " MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);\n";
1724 }
1725 else
1726 {
1727 onConnectInput += " console.log('+ Input connection changed:', this.title, target_slot, output_type, output, source, slot);\n";
1728 }
1729 onConnectInput += " }"
1730 definition_code += " this.onConnectInput = " + onConnectInput + "\n";
1731
1732 // Set the background color to slate grey
1733 definition_code += " this.bgcolor = '#111';\n";
1734 //console.log('Node group:', nodeGroup, nodeDefName);
1735 if (nodeGroup == 'conditional') {
1736 //console.log('Cond Node group:', nodeGroup)
1737 definition_code += " this.color = '#532200';\n";
1738 definition_code += " this.title_text_color = '#000';\n";
1739 definition_code += " this.shape = LiteGraph.CARD_SHAPE;\n";
1740 }
1741
1742 else if (nodeString != 'convert' &&
1743 (nodeGroup == 'shader' || nodeType == 'surfaceshader' || nodeType == 'volumshader' || nodeType == 'displacementshader')) {
1744 definition_code += " this.color = '#232';\n";
1745 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1746 }
1747 else if (nodeGroup == 'material') {
1748 definition_code += " this.color = '#151';\n";
1749 definition_code += " this.shape = LiteGraph.BOX_SHAPE;\n";
1750 }
1751 else {
1752 definition_code += " this.color = '#222';\n";
1753 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1754 }
1755 if (nodeType in CMAP) {
1756 definition_code += " this.boxcolor = '" + CMAP[nodeType] + "';\n";
1757 }
1758 }
1759 definition_code += "}\n"
1760
1761 // Register the node type
1762 definition_code += functionName + ".nodedef_name = '" + nodeDefName + "';\n";
1763 definition_code += functionName + ".nodedef_node = '" + nodeString + "';\n";
1764 definition_code += functionName + ".nodedef_href = 'https://kwokcb.github.io/MaterialX_Learn/documents/definitions/" + nodeString + ".html';\n";
1765
1766 definition_code += "LiteGraph.registerNodeType(" + "'" + id + "'," + functionName + ");\n";
1767 definitionsList.push(id);
1768 if (debug)
1769 definition_code += "console.log('Registered node type:', '" + id + "');\n";
1770 }
1771
1772 //definition_code += "}\n";
1773 return definition_code;
1774 }
1775
1783 {
1784 if (!doc || !stdlib)
1785 {
1786 return true;
1787 }
1788
1789 // Need to create a dummy "validation" doc
1790 let validationDocument = ne_mx.createDocument();
1791 validationDocument.copyContentFrom(doc);
1792 validationDocument.importLibrary(stdlib);
1793
1794 var errors = {};
1795 var valid = validationDocument.validate(errors);
1796 if (!valid) {
1797 this.editor.debugOutput('Failed to validate document:\n' + errors.message, 2);
1798 }
1799 }
1800
1808 saveGraphToDocument(graph, graphWriteOptions) {
1809
1810 if (!ne_mx) {
1811 this.editor.debugOutput("MaterialX is not initialized", 2);
1812 return;
1813 }
1814
1815 let writeCustomLibs = graphWriteOptions.writeCustomLibs;
1816 if (writeCustomLibs == undefined)
1817 {
1818 console.error('Graph output option: writeCustomLibs is undefined.')
1819 writeCustomLibs = true;
1820 }
1821
1822 var outputDoc = ne_mx.createDocument();
1823
1824 if (!stdlib) {
1825 var generator = new ne_mx.EsslShaderGenerator.create();
1826 var genContext = new ne_mx.GenContext(generator);
1827 stdlib = ne_mx.loadStandardLibraries(genContext);
1828 }
1829
1830 // Handle top level
1831 this.writeGraphToDocument(outputDoc, graph, graphWriteOptions);
1832
1833 let doc_string = ne_mx.writeToXmlString(outputDoc);
1834 //console.log(doc_string);
1835 //console.log('-----------------------------------------------')
1836
1837 if (writeCustomLibs) {
1838 console.log('Write custom libraries:', customlibs.length);
1839 for (var customlib of customlibs) {
1840 outputDoc.copyContentFrom(customlib[1]);
1841 }
1842 console.log('Write document custom definitions:', customDocLibs.length);
1843 for (var customDocLib of customDocLibs) {
1844 outputDoc.copyContentFrom(customDocLib[1]);
1845 }
1846 }
1847
1848 // TODO: Add in other globals
1849 outputDoc.setColorSpace(this.getSourceColorSpace());
1850 outputDoc.removeAttribute('fileprefix');
1851
1852 this.validateDocument(outputDoc);
1853
1854 return outputDoc;
1855 }
1856
1865 saveGraphToString(extension, graph, graphWriteOptions) {
1866
1867 if (!ne_mx) {
1868 this.editor.debugOutput("MaterialX is not initialized", 2);
1869 return ['', 'MaterialX is not initialized'];
1870 }
1871
1872 var outputDoc = this.saveGraphToDocument(graph, graphWriteOptions);
1873 if (!outputDoc) {
1874 this.editor.debugOutput("Failed to save graph to document", 2);
1875 return ['', 'Failed to save graph to document'];
1876 }
1877
1878 if (extension == 'mtlx')
1879 {
1880 const writeOptions = new ne_mx.XmlWriteOptions();
1881 writeOptions.writeXIncludeEnable = false;
1882 var data = '';
1883 try {
1884 data = ne_mx.writeToXmlString(outputDoc, writeOptions);
1885 } catch (e) {
1886 this.editor.debugOutput("Failed to write graph:" + e, 2);
1887 }
1888 return [data, ''];
1889 }
1890
1891 // Look for a registered exporter
1892 else
1893 {
1894 let exporter = this.getExporter(extension);
1895 if (!exporter) {
1896 this.editor.debugOutput('Failed to find ' + extension + ' exporter', 2);
1897 }
1898 else {
1899 let exportDoc = ne_mx.createDocument();
1900 exportDoc.copyContentFrom(outputDoc);
1901 exportDoc.importLibrary(stdlib);
1902
1903 let result = exporter.export(ne_mx, exportDoc);
1904 return result;
1905 }
1906 }
1907 return ['', 'Failed to export graph to ' + extension];
1908 }
1909
1918 saveGraphToFile(extension, graph, graphWriteOptions)
1919 {
1920 var data = this.saveGraphToString(extension, graph, graphWriteOptions);
1921 if (!data[0]) {
1922 return;
1923 }
1924
1925 var blob = new Blob([data[0]], { type: "text/plain" });
1926 var url = URL.createObjectURL(blob);
1927 var a = document.createElement("a");
1928 a.href = url;
1929 a.download = "output_graph.mtlx";
1930 a.click();
1931 }
1932
1941 writeGraphToDocument(mltxgraph, graph, graphWriteOptions) {
1942
1943 var debug = false;
1944
1945 if (debug)
1946 console.log('***** START Scan Graph:', graph.title);
1947 for (var node of graph._nodes) {
1948 if (node.type == 'graph/subgraph') {
1949 var subgraph = node.subgraph;
1950 //var subgraphNode = mltxgraph.addNodeGraph(node.title);
1951 var subgraphNode = mltxgraph.addChildOfCategory('nodegraph', node.title);
1952 if (debug)
1953 console.log('---->>> Scan NodeGraph:', node.title);
1954 this.writeGraphToDocument(subgraphNode, subgraph, graphWriteOptions);
1955 continue;
1956 }
1957
1958 if (debug)
1959 console.log('---->>> Scan Node:', node.title);
1960
1961 var nodeDefName = node.nodedef_name;
1962 /* if (!nodeDefName)
1963 {
1964 this.editor.debugOutput('Failed to find nodeDef for:' + node.title, 1);
1965 continue;
1966 } */
1967
1968 //var nodeTypes = LiteGraph.registered_node_types;
1969 //var nodeType = nodeTypes[node.type];
1970 var nodedefName = node.nodedef_name;
1971 var nodedef = null;
1972 var nodeElement = null;
1973 //if (nodeType) {
1974 // nodedefName = nodeType.nodedef_name;
1975 // nodedef = stdlib.getNodeDef(nodedefName);
1976 //}
1977
1978 //if (nodedef) {
1979 // nodeElement = mltxgraph.addNodeInstance(nodedef, name)
1980 // nodeElement.setName(node.title);
1981 //}
1982 //else
1983 {
1984 if (nodedefName) {
1985 nodeElement = mltxgraph.addChildOfCategory(node.nodedef_node, node.nodedef_type);
1986 nodeElement.setType(node.nodedef_type);
1987
1988 if (graphWriteOptions.saveNodePositions) {
1989 // TODO: Get properly remapping for xpos, ypos.
1990 nodeElement.setAttribute('xpos', JSON.stringify(node.pos[0]));
1991 nodeElement.setAttribute('ypos', JSON.stringify(node.pos[1]));
1992 }
1993 if (debug)
1994 console.log('** Create node:', nodeElement.getNamePath(), nodeElement.getType());
1995 nodeElement.setName(node.title);
1996 }
1997 }
1998
1999 if (nodeElement) {
2000 if (debug)
2001 console.log('-> Write Node:', graph.title + '/' + node.title, ' --> ', nodeElement.getNamePath());
2002 }
2003 else {
2004 console.log('Skip writing :', node.title);
2005 //this.editor.debugOutput('No nodedef for:' + node.title + 'Nodetype: ' + node.type, 0);
2006 continue;
2007 }
2008
2009 var properties = node.properties;
2010
2011 var node_inputs = node.inputs;
2012 var isInputNode = false;
2013 var isOutputNode = false;
2014 if (nodeElement.getCategory() == 'input') {
2015 isInputNode = true;
2016 node_inputs = [node];
2017 }
2018 else if (nodeElement.getCategory() == 'output') {
2019 isOutputNode = true;
2020 node_inputs = [node];
2021 }
2022
2023 // Add all outputs if the type is multioutput, or user option set
2024 if (!isInputNode && !isOutputNode)
2025 {
2026 if (node.nodedef_type == "multioutput")
2027 {
2028 console.log('Write outputs for:', node, '. type: ', node.nodedef_type);
2029 for (var output of node.outputs) {
2030 var outputName = output.name;
2031 var outputType = output.type;
2032 var outputElement = nodeElement.addOutput(outputName, outputType);
2033 if (debug) {
2034 console.log('> Read: node.nodedef_type: ', node.nodedef_type);
2035 console.log('> Write: output:', outputElement.getNamePath(), outputElement.getType());
2036 }
2037 }
2038 }
2039 }
2040
2041 // Add inputs
2042 if (node_inputs) {
2043
2044 var inputs = node_inputs;
2045 for (var i in inputs) {
2046 let input = inputs[i];
2047 if (debug)
2048 console.log('---- Write port:', input);
2049
2050 let inputName = input.name;
2051 let inputType = input.type;
2052 if (nodeElement.getCategory() == 'input' ||
2053 nodeElement.getCategory() == 'output') {
2054 inputName = 'in';
2055 inputType = node.nodedef_type;
2056 }
2057
2058 //var inputType = input.type;
2059 var inputElement = null;
2060 var nodeToCheck = node;
2061 var inputNode = null;
2062 var inputLink = null;
2063 if (isInputNode && node.graph._subgraph_node) {
2064 nodeToCheck = node.graph._subgraph_node;
2065 for (var i = 0; i < nodeToCheck.inputs.length; i++) {
2066 var nci = nodeToCheck.inputs[i];
2067 if (nci.name == node.title) {
2068 inputNode = nodeToCheck.getInputNode(i);
2069 inputLink = nodeToCheck.getInputLink(i);
2070 //console.log('--- Found parent input:', nci.name, 'inputNode:', inputNode, 'inputLink:', inputLink);
2071 break;
2072 }
2073 }
2074 }
2075 else {
2076 inputNode = node.getInputNode(i);
2077 inputLink = node.getInputLink(i);
2078 }
2079 var inputLinkOutput = '';
2080 var numInputOutputs = 0;
2081 if (inputLink) {
2082 numInputOutputs = inputNode.outputs.length;
2083 inputLinkOutput = inputNode.outputs[inputLink.origin_slot];
2084 }
2085 if (inputNode) {
2086 //console.log('inputNode', inputNode, 'inputLink:', inputLink, '. --- upsteream Output:', inputLinkOutput);
2087 if (nodeElement.getCategory() != 'input' &&
2088 nodeElement.getCategory() != 'output') {
2089 inputElement = nodeElement.getInput(inputName);
2090 //console.log('Call add input on elem', nodeElement, inputName);
2091 inputElement = nodeElement.addInput(inputName, inputType);
2092 }
2093 else {
2094 inputElement = nodeElement;
2095 }
2096
2097 if (debug) {
2098 console.log('Write connection');
2099 console.log(' - TO:', inputElement.getName() + "." + inputName);
2100 console.log(' - FROM link:', inputNode.id + "." + inputLinkOutput.name);
2101 }
2102 if (inputNode.type == 'graph/subgraph') {
2103 inputElement.setNodeGraphString(inputNode.title);
2104 // Set output string if there was an output link.
2105 if (numInputOutputs > 1 && inputLinkOutput) {
2106 inputElement.setOutputString(inputLinkOutput.name);
2107 }
2108 }
2109 else {
2110 //var upstream_nodeType = nodeTypes[inputNode.type];
2111 //if (upstream_nodeType)
2112 //console.log('Write connection: ', inputNode.title)
2113 {
2114 if (inputNode.nodedef_node == 'input') {
2115 //console.log('Set interface name:', inputNode.title, ' on ', inputElement.getNamePath());
2116 //console.log(inputNode)
2117 inputElement.setInterfaceName(inputNode.title);
2118 //console.log('---------- Get interface name:', inputElement.getInterfaceName());
2119 }
2120 else {
2121 inputElement.setNodeName(inputNode.title);
2122 // Need to check that upstream has > 1 output.
2123 // TODO: Log issue that this is annoying to disallow an explicit output in validation.
2124 if (numInputOutputs > 1 && inputNode.nodedef_node != 'output') {
2125 // Set output string if there was an output link.
2126 if (inputLinkOutput) {
2127 inputElement.setOutputString(inputLinkOutput.name);
2128 }
2129 }
2130 }
2131 }
2132 }
2133 }
2134 else {
2135
2136 var inputValue = node.properties[inputName];
2137 if (inputValue == null) {
2138 console.log('Cannot find property value for input:', inputName);
2139 }
2140 else {
2141 var origValue = inputValue;
2142 //var inputType = propInfo.type;
2143 if (['float', 'integer'].includes(inputType)) {
2144 inputValue = inputValue.toString();
2145 }
2146 else if (['vector2', 'vector3', 'vector4', 'matrix33', 'matrix44', 'color3', 'color4'].includes(inputType)) {
2147 inputValue = inputValue.toString();
2148 inputValue = inputValue.split(',').map(Number).join(', ');
2149 }
2150 else if (inputType === 'boolean') {
2151 if (inputValue === 'true')
2152 inputValue = 'true';
2153 else
2154 inputValue = 'false';
2155 }
2156 else {
2157 inputValue = inputValue.toString();
2158 }
2159 //console.log('Write input:', inputElement, node, inputName, origValue, inputValue, inputType);
2160
2161 if (nodeElement.getCategory() != 'input' &&
2162 nodeElement.getCategory() != 'output') {
2163 inputElement = nodeElement.getInput(inputName);
2164 if (!inputElement)
2165 inputElement = nodeElement.addInput(inputName, inputType);
2166 else {
2167 // TODO: LiteGraph seems that copy+paste adds same input > once.
2168 console.log('Error> Trying add input more than once:', inputName, ' to node: ', nodeElement.getNamePath());
2169 }
2170 }
2171 else {
2172 inputElement = nodeElement;
2173 }
2174
2175 try {
2176 inputElement.setValueString(inputValue, inputType);
2177 }
2178 catch (e) {
2179 console.warn("Set value error: ", e);
2180 }
2181 }
2182 }
2183
2184 if (inputElement) {
2185
2186 var propInfo = null;
2187 var skip_attributes = [];
2188 if (isInputNode || isOutputNode) {
2189 if (input.properties_info) {
2190 // Make sure not to clobber connections like interfacename
2191 skip_attributes = ['interfacename', 'nodegraph', 'nodename', 'name', 'type', 'value', 'default_value'];
2192 propInfo = input.properties_info[0];
2193 }
2194 }
2195 else {
2196 if (node.properties_info) {
2197 // Make sure not to clobber connections like interfacename
2198 skip_attributes = ['interfacename', 'nodegraph', 'nodename', 'name', 'type', 'value', 'default_value', 'uimin', 'uimax', 'uiname', 'uifolder'];
2199 propInfo = node.properties_info[i];
2200 }
2201 }
2202 if (propInfo) {
2203 //console.log('Scan propinfo:', propInfo, 'for input:', inputElement.getNamePath(), 'prop_info:', propInfo);
2204
2205 // Write node_properties metadata to input
2206 skip_attributes = skip_attributes.concat(['defaultgeomprop', 'enum', 'enumvalues']);
2207 //console.log('SKIP ATTRIBUTES:', skip_attributes);
2208 for (var propAttribute in propInfo) {
2209 if (skip_attributes.includes(propAttribute))
2210 continue;
2211
2212 //console.log('-- scan attrib:', propAttribute, 'value:', propInfo[propAttribute]);
2213 var propAttributeValue = propInfo[propAttribute];
2214 if (propAttributeValue && propAttributeValue.length > 0) {
2215 inputElement.setAttribute(propAttribute, propAttributeValue);
2216 }
2217 }
2218 }
2219 }
2220 }
2221
2222 if (debug)
2223 console.log('---- END Write inputs:', node.inputs);
2224 }
2225
2226 if (debug)
2227 console.log('---> End write node', node.title);
2228 }
2229
2230 if (debug)
2231 console.log('***** END Scan Graph:', graph.title);
2232 }
2233
2240 isArray(_type) {
2241 var ARRAY_TYPES = ['color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44'];
2242 if (ARRAY_TYPES.includes(_type)) {
2243 return true;
2244 }
2245 return false;
2246 }
2247
2248 //**
2249 // Determines if a string starts with a URI scheme.
2250 // @param s - The string to check.
2251 // */
2253 // Check if the string starts with common URI schemes
2254 const uriSchemes = ['http://', 'https://', 'ftp://', 'blob:', 'file://', 'data:'];
2255 return uriSchemes.some(scheme => s.startsWith(scheme));
2256 }
2257
2268 buildConnections(editor, node, lg_node, explicitInputs, graph, parentGraph) {
2269
2270 var nodeInputs = [];
2271 var isOutput = (node.getCategory() == 'output');
2272 var isInput = (node.getCategory() == 'input');
2273 if (isOutput || isInput) {
2274 nodeInputs = [node];
2275 }
2276 else {
2277 nodeInputs = node.getInputs();
2278 }
2279 for (var input of nodeInputs) {
2280
2281 var _name = ''
2282
2283 if (!isOutput && !isInput) {
2284 _name = input.getName();
2285 explicitInputs.push(_name);
2286 }
2287
2288 var nodeName = input.getNodeName();
2289 var nodeGraphName = input.getNodeGraphString();
2290 var inputInterfaceName = input.getInterfaceName();
2291 var outputName = input.getOutputString();
2292
2293 if (nodeName.length ||
2294 nodeGraphName.length ||
2295 inputInterfaceName.length ||
2296 outputName.length) {
2297
2298 //console.log('Test connection on input:', input.getNamePath(), 'nodeName:[ ', nodeName,
2299 // '] nodeGraphName:[', nodeGraphName,
2300 // '] inputInterfaceName:[', inputInterfaceName,
2301 // ']outputName:[', outputName, ']');
2302
2303 var target_node = lg_node;
2304 var target_slot = null;
2305 if (!isOutput && !isInput)
2306 target_slot = target_node.findInputSlot(_name);
2307 else
2308 target_slot = 0;
2309 var source_node = null;
2310 var source_slot = 0;
2311 var source_name = nodeName;
2312 if (nodeGraphName.length) {
2313 source_name = nodeGraphName;
2314 }
2315 if (inputInterfaceName.length) {
2316 source_name = inputInterfaceName;
2317 }
2318
2319 var graphToCheck = graph;
2320 if (isInput && graph._subgraph_node) {
2321 target_node = graph._subgraph_node;
2322 target_slot = target_node.findInputSlot(lg_node.title);
2323 // Go up to parent graph
2324 graphToCheck = parentGraph;
2325 //console.log(' go up to parent graph:', graphToCheck,
2326 // 'from:', graph, 'subgraph:', graph._subgraph_node,
2327 //'target_node:', target_node.title, 'target_slot:', target_slot);
2328 }
2329 source_node = graphToCheck.findNodeByTitle(source_name);
2330 if (source_node) {
2331 if (outputName) {
2332 var outputSlot = source_node.findOutputSlot(outputName);
2333 if (outputSlot >= 0) {
2334 source_slot = outputSlot;
2335 }
2336 else {
2337 editor.debugOutput('Failed to find output slot:' + outputName, 1);
2338 }
2339 var linkInfo = source_node.connect(source_slot, target_node, target_slot);
2340 if (!linkInfo) {
2341 editor.debugOutput('Failed to connect:' + source_node.title + '.' + outputName, '->', target_node.title + '.' + _name), 1, false;
2342 }
2343 }
2344 //console.log('CONNECT START: source[', source_node.title, '.', source_slot,
2345 // '] --> target[:', target_node.title, ".", target_slot);
2346 var linkInfo = null;
2347 if (source_slot == null || target_slot == null || target_node == null) {
2348 console.warning('Cannot connect!')
2349 }
2350 else {
2351 linkInfo = source_node.connect(source_slot, target_node, target_slot);
2352 }
2353 if (!linkInfo) {
2354 editor.debugOutput('Failed to connect:' + source_node.title + '.' + outputName, '->', target_node.title + '.' + _name, 1);
2355 }
2356 //console.log('CONNECT END: source[', source_node.title, '.', source_slot,
2357 // '] --> target[:', target_node.title, ".", target_slot);
2358 }
2359 else {
2360 console.log('Failed to find node ', source_name, 'in graph:', graphToCheck);
2361 this.editor.debugOutput('Failed to find source node: ' + source_node + "." +
2362 source_name, '->', lg_node.title + "." + _name, 2);
2363 }
2364 }
2365 else {
2366 const inputType = input.getAttribute(ne_mx.TypedElement.TYPE_ATTRIBUTE);
2367 let valueString = input.getValueString();
2368 if (valueString.length > 0) {
2369 let _value = '';
2370
2371 if (inputType === ne_mx.FILENAME_TYPE_STRING) {
2372 // If the string is a http or blob url then call input.getValuesString()
2373 // else call input.getResolvedValueString()
2374 if (this.isURI(valueString)) {
2375 this.editor.debugOutput('Filename is a url:' + valueString + '. Cannot use resolved path value.');
2376 _value = input.getValueString();
2377 }
2378 else {
2379 _value = input.getResolvedValueString();
2380 }
2381 }
2382 else
2383 {
2384 _value = input.getResolvedValueString(); // input.getValueString();
2385
2386 if (this.isArray(input.getType())) {
2387 let valueArray = "[" + _value + "]"
2388 valueArray = JSON.parse(valueArray);
2389 //valueArray = _value.split(/[\s,]+/); - This does not parse all configs properly.
2390 //console.log('>>> Read array value:', _value, 'as:', valueArray);
2391 _value = valueArray;
2392 }
2393 }
2394
2395 //console.log('-- Value Input:',
2396 //lg_node.title + "." + _name, 'value:', _value);
2397 lg_node.setProperty(_name, _value);
2398 }
2399 }
2400
2401 var property_info = lg_node.getPropertyInfo(_name);
2402 this.loadInputMetaData(node, input, property_info);
2403 }
2404 }
2405
2414 loadInputMetaData(node, input, property_info) {
2415 if (input && property_info) {
2416
2417 // Load in basic meta-data
2418 var colorspace = input.getColorSpace();
2419 if (colorspace.length > 0)
2420 property_info['colorspace'] = colorspace;
2421
2422 var unit = input.getUnit();
2423 if (unit.length > 0)
2424 property_info['unit'] = unit;
2425
2426 var uiname = input.getAttribute('uiname');
2427 if (uiname.length > 0)
2428 property_info['uiname'] = uiname;
2429
2430 var uimin = input.getAttribute('uimin');
2431 if (uimin.length > 0)
2432 property_info['uimin'] = uimin;
2433
2434 var uimax = input.getAttribute('uimax');
2435 if (uimax.length > 0)
2436 property_info['uimax'] = uimax;
2437 var uisoftmin = input.getAttribute('uisoftmin');
2438 if (uisoftmin.length > 0)
2439 property_info['uimin'] = uisoftmin;
2440
2441 var uisoftmax = input.getAttribute('uisoftmax');
2442 if (uisoftmax.length > 0)
2443 property_info['uimax'] = uisoftmax;
2444
2445 var uifolder = input.getAttribute('uifolder');
2446 if (uifolder.length > 0)
2447 property_info['uifolder'] = uifolder;
2448
2449 var basicMetaData = ['colorspace', 'unit', 'uiname', 'uimin', 'uimax', 'uifolder', 'name', 'type', 'output', 'nodename', 'nodegraph'];
2450 for (var attrName of input.getAttributeNames()) {
2451 if (!basicMetaData.includes(attrName)) {
2452 property_info[attrName] = input.getAttribute(attrName);
2453 }
2454 }
2455
2456 if (node && input.getType() == 'filename')
2457 {
2458 let nodeType = node.getType();
2459 let colorTypes = ['color3', 'color4'];
2460 //console.log('Load input metadata for:', input.getName(), input.getType(), property_info, nodeType);
2461 if (colorTypes.includes(nodeType))
2462 {
2463 if (!property_info['colorspace']) {
2464 console.log('Auto create "none" colorspace for input:', input.getName());
2465 let doc = node.getDocument();
2466 let defaultColorSpace = 'none';
2467 // For now don't use the document color space as 'none' is more flexible.
2468 //let docColorSpace = doc.getAttribute('colorspace');
2469 //if (docColorSpace.length > 0)
2470 // defaultColorSpace = docColorSpace;
2471 property_info['colorspace'] = defaultColorSpace;
2472 }
2473 }
2474 }
2475
2476 //console.log('load input metadata for:', input.getNamePath(), property_info);
2477 }
2478 }
2479
2488 buildGraphFromDoc(doc, editor, auto_arrange) {
2489 let debug = false;
2490 let loadNodePositions = false; // TODO: Some issues with UI update when setting xpos, ypos. To address.
2491
2492 //console.log('Build graph from doc. auto_arrange: ', auto_arrange);
2493 if (!ne_mx) {
2494 editor.debugOutput("MaterialX is not initialized", 2);
2495 return;
2496 }
2497
2498 editor.clearGraph();
2499
2500 // Don't try and update the graph while building it
2501 editor.monitor.monitorGraph(graph, false);
2502
2503 // Index here is index into litegraph nodes
2504 var mtlxNodes = [];
2505 var mtlxNodeDefs = [];
2506
2507 for (var interfaceInput of doc.getInputs()) {
2508 var _type = interfaceInput.getType();
2509 var id = 'mtlx/input/input_' + _type;
2510
2511 var lg_node = LiteGraph.createNode(id);
2512 if (lg_node) {
2513 lg_node.title = interfaceInput.getName();
2514 if (debug)
2515 console.log('Add top level input:', lg_node.title, 'to graph', graph);
2516
2517 var _value = interfaceInput.getValueString();
2518 if (_value && _value.length > 0) {
2519 if (this.isArray(interfaceInput.getType())) {
2520 _value = "[" + _value + "]"
2521 _value = JSON.parse(_value);
2522 }
2523 lg_node.setProperty('in', _value);
2524 }
2525
2526 if (loadNodePositions) {
2527 var xpos = interfaceInput.getAttribute('xpos');
2528 var ypos = interfaceInput.getAttribute('ypos');
2529 if (xpos.length > 0 && ypos.length > 0) {
2530 lg_node.pos[0] = xpos;
2531 lg_node.pos[1] = ypos;
2532 //lg_node.flags.collapsed = false;
2533 }
2534 }
2535
2536 // Make sure size is updated
2537 lg_node.setSize(lg_node.computeSize());
2538
2539 graph.add(lg_node);
2540
2541 //mtlxNodes.push([interfaceInput, lg_node, graph]);
2542 }
2543 }
2544
2545 for (var interfaceOutput of doc.getOutputs()) {
2546 var _type = interfaceOutput.getType()
2547 var id = 'mtlx/output/output_' + _type;
2548
2549 var lg_node = LiteGraph.createNode(id);
2550 if (lg_node) {
2551 lg_node.title = interfaceOutput.getName();
2552 graph.add(lg_node);
2553 if (debug) {
2554 console.log('Add graph output:', lg_node.title);
2555 }
2556
2557 // Make sure size is updated
2558 lg_node.setSize(lg_node.computeSize());
2559
2560 if (loadNodePositions) {
2561 var xpos = interfaceOutput.getAttribute('xpos');
2562 var ypos = interfaceOutput.getAttribute('ypos');
2563 if (xpos.length > 0 && ypos.length > 0)
2564 lg_node.pos = [xpos, ypos];
2565 }
2566
2567 mtlxNodes.push([interfaceOutput, lg_node, graph]);
2568 }
2569 }
2570
2571 for (var node of doc.getNodes()) {
2572 var nodeDef = node.getNodeDef();
2573 if (!nodeDef) {
2574 editor.debugOutput('Skip node w/o nodedef:' + node.getName(), 1)
2575 continue;
2576 }
2577
2578 // mtlx/pbr/gltf_pbr_surfaceshader
2579 var id = 'mtlx/' + nodeDef.getNodeGroup() + '/' + nodeDef.getName();
2580 id = id.replace('ND_', '');
2581 if (debug)
2582 console.log('Load node:', node.getName(), ' -> ', id);
2583
2584 var lg_node = LiteGraph.createNode(id);
2585 if (lg_node) {
2586 //console.log('LiteGraph node:', lg_node);
2587 lg_node.title = node.getName();
2588
2589 graph.add(lg_node);
2590
2591 // Make sure size is updated
2592 lg_node.setSize(lg_node.computeSize());
2593
2594 if (loadNodePositions) {
2595 var xpos = node.getAttribute('xpos');
2596 var ypos = node.getAttribute('ypos');
2597 if (xpos.length > 0 && ypos.length > 0)
2598 lg_node.pos = [xpos, ypos];
2599 }
2600
2601 mtlxNodes.push([node, lg_node, graph]);
2602 mtlxNodeDefs.push(nodeDef);
2603 }
2604 else {
2605 editor.debugOutput('Failed to create node:' + node.getName(), 2);
2606 }
2607 }
2608
2609 for (var nodegraph of doc.getNodeGraphs()) {
2610 if (nodegraph.hasSourceUri()) {
2611 continue;
2612 }
2613 var nodedefAttrib = nodegraph.getAttribute('nodedef');
2614 if (nodedefAttrib && nodedefAttrib.length > 0) {
2615 console.log('Skip loading in functional graph:', nodegraph.getName(), 'nodedef:', nodedefAttrib);
2616 continue;
2617 }
2618 if (debug)
2619 console.log('Create nodegraph:', nodegraph.getName());
2620
2621 var title = nodegraph.getName();
2622 var subgraphNode = LiteGraph.createNode("graph/subgraph", title);
2623 //var subgraph = new LiteGraph.LGraph();
2624 //subgraphNode._subgraph_node = subgraph;
2625 //subgraphNode.bgcolor = "#112";
2626 subgraphNode.bgImageUrl = "./Icons/nodegraph.png";
2627
2628 var mtlxSubGraphNodes = [];
2629 for (var interfaceInput of nodegraph.getInputs()) {
2630 var _type = interfaceInput.getType();
2631 var id = 'mtlx/input/input_' + _type;
2632
2633 var lg_node = LiteGraph.createNode(id);
2634 if (lg_node) {
2635 lg_node.title = interfaceInput.getName();
2636 this.loadInputMetaData(null, interfaceInput, lg_node.properties_info[0]);
2637 subgraphNode.subgraph.add(lg_node);
2638
2639 if (debug)
2640 console.log('-------- Add subgraph input:', lg_node.title, lg_node);
2641
2642 subgraphNode.addInput(interfaceInput.getName(), _type);
2643 subgraphNode.subgraph.addInput(interfaceInput.getName(), _type);
2644
2645 var _value = interfaceInput.getValueString();
2646 if (_value && _value.length > 0) {
2647 if (this.isArray(interfaceInput.getType())) {
2648 _value = "[" + _value + "]"
2649 _value = JSON.parse(_value);
2650 }
2651 lg_node.setProperty('in', _value);
2652 }
2653
2654 // Make sure size is updated
2655 lg_node.setSize(lg_node.computeSize());
2656
2657 if (loadNodePositions) {
2658 var xpos = nodegraph.getAttribute('xpos');
2659 var ypos = nodegraph.getAttribute('ypos');
2660 if (xpos.length > 0 && ypos.length > 0)
2661 lg_node.pos = [xpos, ypos];
2662 }
2663
2664 mtlxSubGraphNodes.push([interfaceInput, lg_node, graph]);
2665 }
2666 }
2667
2668 for (var interfaceOutput of nodegraph.getOutputs()) {
2669 var _type = interfaceOutput.getType()
2670 var id = 'mtlx/output/output_' + _type;
2671
2672 var lg_node = LiteGraph.createNode(id);
2673 if (lg_node) {
2674 lg_node.title = interfaceOutput.getName();
2675 subgraphNode.subgraph.add(lg_node);
2676 if (debug)
2677 console.log('Add subgraph output:', lg_node.title);
2678
2679 subgraphNode.addOutput(interfaceOutput.getName(), _type);
2680 subgraphNode.subgraph.addOutput(interfaceOutput.getName(), _type);
2681
2682 // Make sure size is updated
2683 lg_node.setSize(lg_node.computeSize());
2684
2685 if (loadNodePositions) {
2686 var xpos = interfaceOutput.getAttribute('xpos');
2687 var ypos = interfaceOutput.getAttribute('ypos');
2688 if (xpos.length > 0 && ypos.length > 0)
2689 lg_node.pos = [xpos, ypos];
2690 }
2691
2692 mtlxSubGraphNodes.push([interfaceOutput, lg_node, graph]);
2693 }
2694 }
2695
2696
2697 for (var node of nodegraph.getNodes()) {
2698 var nodeDef = node.getNodeDef();
2699 if (!nodeDef) {
2700 editor.debugOutput('Skip node w/o nodedef:' + node.getName(), 1)
2701 continue;
2702 }
2703
2704 // mtlx/pbr/gltf_pbr_surfaceshader
2705 var id = 'mtlx/' + nodeDef.getNodeGroup() + '/' + nodeDef.getName();
2706 id = id.replace('ND_', '');
2707
2708 var lg_node = LiteGraph.createNode(id);
2709 lg_node.title = node.getName();
2710 subgraphNode.subgraph.add(lg_node);
2711 if (debug)
2712 console.log('Add subgraph node:', lg_node.title);
2713
2714 // Make sure size is updated
2715 lg_node.setSize(lg_node.computeSize());
2716
2717 if (loadNodePositions) {
2718 var xpos = node.getAttribute('xpos');
2719 var ypos = node.getAttribute('ypos');
2720 if (xpos.length > 0 && ypos.length > 0)
2721 lg_node.pos = [xpos, ypos];
2722 }
2723
2724 mtlxSubGraphNodes.push([node, lg_node, graph]);
2725 }
2726
2727 for (var item of mtlxSubGraphNodes) {
2728 var node = item[0];
2729 var lg_node = item[1];
2730 var parentGraph = item[2];
2731 var explicitInputs = [];
2732
2733 // Make sure size is updated
2734 lg_node.setSize(lg_node.computeSize());
2735
2736 //console.log('Build connections for subgraog node:', lg_node.title);
2737 this.buildConnections(editor, node, lg_node, explicitInputs, subgraphNode.subgraph, parentGraph);
2738 }
2739
2740 if (debug)
2741 console.log('Add subgraph:', subgraphNode.title);
2742
2743 if (auto_arrange > 0) {
2744 subgraphNode.subgraph.arrange(auto_arrange);
2745 }
2746
2747 graph.add(subgraphNode);
2748
2749 }
2750
2751 // Build top level connections last after top level nodes
2752 // and nodegraph have been added.
2753 var itemCount = 0;
2754 for (var item of mtlxNodes) {
2755 var node = item[0];
2756 var lg_node = item[1];
2757
2758 // Keep track of explicit inputs
2759 var explicitInputs = [];
2760 //console.log('Build connections for:', lg_node.title);
2761 this.buildConnections(editor, node, lg_node, explicitInputs, graph, null);
2762
2763 if (lg_node.nodedef_node == 'input' || lg_node.nodedef_node == 'output') {
2764 continue;
2765 }
2766
2767 /*
2768 var removeInputs = [];
2769 var nodeDef = mtlxNodeDefs[itemCount];
2770 if (nodeDef) {
2771 for (var nodeDefInput of nodeDef.getActiveInputs()) {
2772 var _name = nodeDefInput.getName();
2773 if (!explicitInputs.includes(_name)) {
2774 removeInputs.push(_name);
2775 }
2776 }
2777 for (var _name of removeInputs) {
2778 var slot = lg_node.findInputSlot(_name);
2779 lg_node.inputs[slot].hidden = true;
2780 console.log('Hide input:', _name, '. ', slot, ' on: ', lg_node);
2781 //lg_node.removeInput(slot);
2782 }
2783
2784 // Make sure size is updated
2785 lg_node.setSize(lg_node.computeSize());
2786 }
2787 */
2788 itemCount++;
2789 }
2790
2791 editor.monitor.monitorGraph(graph, true);
2792
2793 if (auto_arrange > 0) {
2794 graph.arrange(auto_arrange);
2795 }
2796
2797 graph.setDirtyCanvas(true, true);
2798 graphcanvas.setDirty(true, true);
2799 }
2800
2807 // Prefer build-time manifest injection when available to avoid
2808 // relying on directory index HTML. HtmlWebpackPlugin injects
2809 // `adsklibFiles` into the page as `window.ADSKLIB_FILES`.
2810 const webPath = String(dir).replace(/^\/+/, '');
2811 return (async () => {
2812 try {
2813 // If a global manifest was injected, and it contains entries
2814 // for this folder, use it.
2815 if (window && Array.isArray(window.ADSKLIB_FILES) && window.ADSKLIB_FILES.length > 0) {
2816 const prefix = webPath.replace(/\/+$|^\/+/, '');
2817 const matched = window.ADSKLIB_FILES.filter(p => p.startsWith(prefix) || p.startsWith('/' + prefix));
2818 if (matched.length > 0) {
2819 // Ensure absolute paths
2820 return matched.map(p => p.startsWith('/') ? p : '/' + p);
2821 }
2822 }
2823
2824 // Fallback: fetch directory index and parse anchor links.
2825 const res = await fetch(`/${webPath}/`);
2826 if (!res.ok) {
2827 console.warn('getAllFilesRecursive: directory fetch failed', `/${webPath}/`, res.status);
2828 return [];
2829 }
2830 const html = await res.text();
2831 const parser = new DOMParser();
2832 const doc = parser.parseFromString(html, 'text/html');
2833 const anchors = Array.from(doc.querySelectorAll('a')).map(a => a.getAttribute('href'));
2834 const files = anchors
2835 .filter(h => h && h.toLowerCase().endsWith('.mtlx'))
2836 .map(h => (h.startsWith('/') ? h : `/${webPath}/${h}`));
2837 // Normalize and deduplicate
2838 const uniq = Array.from(new Set(files));
2839 return uniq;
2840 } catch (e) {
2841 console.warn('getAllFilesRecursive: error parsing directory', dir, e);
2842 return [];
2843 }
2844 })();
2845 }
2846
2847 async loadFolderToDocument(folderPath) {
2848 // Browser-only: use getAllFilesRecursive to obtain file URLs and fetch
2849 // them. Assumes files are served under '/<folderPath>/'.
2850 let files = [];
2851 try {
2852 files = await this.getAllFilesRecursive(folderPath);
2853 console.log('===================== FOund files, count:', files.length);
2854 } catch (e) {
2855 console.error('Error listing files for', folderPath, e);
2856 return null;
2857 }
2858
2859 const userlib = ne_mx.createDocument();
2860 for (const fileUrl of files) {
2861 try {
2862 // Ensure URL is absolute-path style
2863 let fetchUrl = fileUrl;
2864 if (!fetchUrl.startsWith('/') && !fetchUrl.match(/^https?:\/\//)) {
2865 const base = String(folderPath).replace(/^\/+/, '');
2866 fetchUrl = `/${base}/${fetchUrl}`;
2867 }
2868 console.log('----------- FETCH: ', fetchUrl);
2869 const res = await fetch(fetchUrl);
2870 if (!res.ok) {
2871 console.error('Failed to fetch', fetchUrl, res.statusText);
2872 continue;
2873 }
2874 const mtlxString = await res.text();
2875 const readOptions = new ne_mx.XmlReadOptions();
2876 await ne_mx.readFromXmlString(userlib, mtlxString, '', readOptions);
2877
2878 } catch (err) {
2879 console.error('Error loading file', fileUrl, err);
2880 }
2881 }
2882 return userlib;
2883 }
2884
2891 var that = this;
2892
2893 // Load mtlx document from disk
2894 var input = document.createElement("input");
2895 input.style = this.fontSizeStyle;
2896 input.type = "file";
2897 input.accept = ".mtlx";
2898 input.onchange = function (e) {
2899 var file = e.target.files[0];
2900 console.log('Loading definitions from file: ' + file.name);
2901
2902 if (ne_mx) {
2903 // Load the content from the specified file (replace this with actual loading logic)
2904
2905 const reader = new FileReader();
2906 reader.readAsText(file, 'UTF-8');
2907
2908 reader.onload = function (e) {
2909 // Display the contents of the file in the output div
2910 let fileContents = e.target.result;
2911 //console.log(fileContents);
2912
2913 (async () => {
2914 try {
2915 const readOptions = new ne_mx.XmlReadOptions();
2916 readOptions.readXIncludes = false;
2917 var customLib = ne_mx.createDocument();
2918
2919 await ne_mx.readFromXmlString(customLib, fileContents, '', readOptions);
2920
2921 // Create JS from custom library
2922 try {
2923 console.log('Create custom library definitions', ne_mx.prettyPrint(customLib));
2924 var iconName = '';
2925 var scanForIcon = false;
2926 if (scanForIcon) {
2927 // Icon name is filename with webp as extension
2928 var iconName = file.name.replace(/\.[^/.]+$/, ".webp");
2929 // Check if iconName file exists
2930 var iconExists = await that.editor.uriExists(iconName);
2931 if (!iconExists) {
2932 iconName = '';
2933 }
2934 }
2935 var definitionsList = [];
2936 var result = that.createLiteGraphDefinitions(customLib, false, false, definitionsList, 'mtlx', that.editor, iconName);
2937 if (result) {
2938 eval(result);
2939 var definitionsListString = definitionsList.join(', ');
2940 that.editor.debugOutput("Registered custom node types: [" + definitionsListString + "]", 0, false);
2941 that.editor.displayNodeTypes();
2942 }
2943 } catch (e) {
2944 console.log('Error evaluating source:', e);
2945 }
2946
2947
2948 // Keep track of libraries loaded by filename.
2949 customlibs.push([file.name, customLib]);
2950
2951 } catch (error) {
2952 that.editor.debugOutput('Error reading definitions:' + error, 2, false);
2953 }
2954 })();
2955
2956 };
2957
2958 } else {
2959 that.editor.debugOutput("MaterialX is not initialized", 2);
2960 }
2961
2962 //customlibs
2963 };
2964 input.click();
2965 }
2966
2977 loadFromString(extension, fileContents, fileName, auto_arrange, rerender=false) {
2978 if (!ne_mx) {
2979 console.log('MaterialX is not initialized');
2980 return;
2981 }
2982
2983 // Check if we need to pre-convert from extension type to mtlx
2984 if (extension != 'mtlx')
2985 {
2986 let converter = this.getImporter(extension);
2987 if (converter) {
2988 let result = converter.import(ne_mx, fileContents, stdlib);
2989 if (result) {
2990 fileContents = result[0];
2991 }
2992 else {
2993 console.log('Failed to convert from:', extension, 'to mtlx. Errors:', result[1]);
2994 return;
2995 }
2996 }
2997 else
2998 {
2999 console.log('Failed to find converter from:', extension, 'to mtlx.');
3000 return;
3001 }
3002 }
3003
3004 (async () => {
3005 try {
3006 const readOptions = new ne_mx.XmlReadOptions();
3007 readOptions.readXIncludes = false;
3008
3009 doc.clearContent();
3010
3011 doc.importLibrary(stdlib);
3012 for (var item of customlibs) {
3013 console.log('Import custom library:', item[0]);
3014 doc.importLibrary(item[1]);
3015 }
3016 var loadDoc = ne_mx.createDocument();
3017 await ne_mx.readFromXmlString(loadDoc, fileContents, '', readOptions);
3018
3019 // Check if nodedef is not in existingDefs
3020 //
3021 var customLib = ne_mx.createDocument();
3022 customLib.copyContentFrom(loadDoc);
3023 var keepChildren = [];
3024 var existingDefs = []
3025 var saveCustomLib = false;
3026 doc.getNodeDefs().forEach(def => { existingDefs.push(def.getName()); });
3027 for (var nodedef of loadDoc.getNodeDefs()) {
3028 var nodedefName = nodedef.getName();
3029 if (!existingDefs.includes(nodedefName)) {
3030 keepChildren.push(nodedef.getName());
3031 saveCustomLib = true;
3032 }
3033 }
3034 for (var ng of loadDoc.getNodeGraphs()) {
3035 if (ng.getAttribute('nodedef') && ng.getAttribute('nodedef').length > 0) {
3036 saveCustomLib = true;
3037 keepChildren.push(ng.getName());
3038 }
3039 }
3040
3041 if (saveCustomLib) {
3042
3043 for (var child of customLib.getChildren()) {
3044 if (!keepChildren.includes(child.getName())) {
3045 //console.log('Remove child:', child.getName());
3046 customLib.removeChild(child.getName());
3047 }
3048 }
3049
3050 var additionDefs = [];
3051 console.log('Create custom library definitions from addtionDefs:',
3052 ne_mx.prettyPrint(customLib));
3053
3054 var result = this.createLiteGraphDefinitions(customLib, true, false, additionDefs, 'mtlx', MxShadingGraphEditor.theEditor);
3055 try {
3056 eval(result);
3057 console.log('Loaded local definitions: ', additionDefs);
3058 } catch (e) {
3059 console.log('Error evaluating source:', e);
3060 }
3061 }
3062
3063 doc.copyContentFrom(loadDoc);
3064
3065 this.validateDocument(doc);
3066
3067 this.buildGraphFromDoc(doc, MxShadingGraphEditor.theEditor, auto_arrange);
3068
3069 // Must do this after build as build will clear customDocLibs array
3070 if (saveCustomLib) {
3071 customDocLibs.push([fileName, customLib]);
3072 }
3073
3074 // Get the document's colorspace and set it as the active colorspace
3075 var documentColorSpace = doc.getColorSpace();
3076 this.setSourceColorSpace(documentColorSpace);
3077 documentColorSpace = this.getSourceColorSpace();
3079
3080 // Cleanup document, and get up-to-date contents after any possible upgrade.
3081 loadDoc.removeAttribute('fileprefix');
3082 fileContents = ne_mx.writeToXmlString(loadDoc);
3083
3084 this.validateDocument(loadDoc);
3085
3086 MxShadingGraphEditor.theEditor.debugOutput('Loaded document: "' + fileName + '"', 0, false);
3087
3088 // Update mtlx text area
3089 let documentDisplayUpdater = MxShadingGraphEditor.theEditor.ui.documentDisplayUpdater;
3090 if (documentDisplayUpdater) {
3091 documentDisplayUpdater(fileContents);
3092 }
3093 else {
3094 console.log('No docDisplayUpdater!!!');
3095 }
3096
3097 // Update render items in UI
3098 let renderableItemUpdater = MxShadingGraphEditor.theEditor.ui.renderableItemUpdater;
3099 if (renderableItemUpdater) {
3100 let renderableItems = this.findRenderableItemsInDoc(doc);
3101 if (!renderableItems || renderableItems.length == 0) {
3102 MxShadingGraphEditor.theEditor.debugOutput('No renderable items found in graph: ' + fileName, 1, false);
3103 }
3104 renderableItemUpdater(renderableItems);
3105 }
3106
3107 //
3108
3109 if (rerender)
3110 {
3111 console.log('> Update rendering from document');
3112 let theRenderer = this.editor.monitor.renderer;
3113 //console.log(this.editor.monitor, this.editor.monitor.renderer)
3114 if (theRenderer)
3115 theRenderer.updateMaterialFromText(fileContents);
3116 }
3117
3118 } catch (error) {
3119 MxShadingGraphEditor.theEditor.debugOutput('Error reading document: ' + fileName + '. Error: ' + error, 2, false);
3120 }
3121 })();
3122 }
3123
3134 loadFromFile(extension, file, fileName, editor, auto_arrange) {
3135 var debug = false;
3136
3137 if (ne_mx) {
3138 if (!this.loadMaterialXLibraries(stdlib))
3139 return;
3140
3141 // Load the content from the specified file (replace this with actual loading logic)
3142
3143 const reader = new FileReader();
3144 reader.readAsText(file, 'UTF-8');
3145 reader.accept = '.mtlx';
3146
3147 var that = this;
3148 console.log('loadFromFile:', file, fileName);
3149 try {
3150 reader.onload = function (e) {
3151 // Display the contents of the file in the output div
3152 let fileContents = e.target.result;
3153 console.log("read file: ", file.name, " with extension: ", extension, " and length: ", fileContents.length);
3154
3155 that.loadFromString('mtlx', fileContents, fileName, auto_arrange, true);
3156 };
3157 } catch (error) {
3158 MxShadingGraphEditor.theEditor.debugOutput('Error reading document: ' + fileName + '. Error: ' + error, 2, false);
3159 }
3160
3161 } else {
3162 editor.debugOutput("MaterialX is not initialized", 2, false);
3163 }
3164 }
3165
3166 loadFromZip(extension, file, fileName, editor, auto_arrange, rerender=false)
3167 {
3168 if (ne_mx) {
3169 // Load the content from the specified file (replace this with actual loading logic)
3170 console.log("Loading content from zip:", file.name);
3171
3172 const reader = new FileReader();
3173
3174 var that = this;
3175 reader.onload = async function (e) {
3176 try {
3177 const zipData = new Uint8Array(e.target.result); // Convert to Uint8Array
3178 const result = await MxZipUtils.unzipMaterialXData(zipData);
3179 let documents = result[0];
3180 let docText = documents[0].content;
3181 let docFile = documents[0].name;
3182 console.log('Documents:', docText);
3183
3184 let textures = result[1];
3185 for (let i = 0; i < textures.length; i++) {
3186 const url = textures[i].url;
3187 const textureName = textures[i].name;
3188 // Replace fileName with url in docText.
3189 // Do not care about case sensitivity since some examples
3190 // from GPUOpen have the incorrect case.
3191 docText = docText.replace(new RegExp(textureName, 'i'), url);
3192 console.log('Replace reference:' + textureName + ' with blob: ' + url);
3193
3194 }
3195 that.loadFromString('mtlx', docText, docFile, auto_arrange, rerender);
3196
3197 } catch (error) {
3198 //editor.debugOutput("MaterialX is not initialized", 2, false);
3199 console.error('Error loading ZIP file:', error);
3200 }
3201 };
3202
3203 reader.readAsArrayBuffer(file);
3204
3205 } else {
3206 console.error("MaterialX is not initialized");
3207 }
3208 }
3209
3216 {
3217 console.log('------------------------------- LOAD MTLX LIBRARIES -------------------------------');
3218 if (stdlib)
3219 return stdlib;
3220
3221 if (!ne_mx) {
3222 MxShadingGraphEditor.theEditor.debugOutput("MaterialX is not initialized", 2);
3223 return null;
3224 }
3225
3226 var generator = new ne_mx.EsslShaderGenerator.create();
3227 var genContext = new ne_mx.GenContext(generator);
3228 {
3229 stdlib = ne_mx.loadStandardLibraries(genContext);
3230 console.log('Loaded standard libraries:', stdlib.getNodeDefs().length);
3231 }
3232
3233 let adsklib = this.loadFolderToDocument('./Libraries/adsklib');
3234
3235 return stdlib;
3236 }
3237
3245 createValidName(name, msg = null) {
3246 if (name.length == 0) {
3247 if (msg) {
3248 msg = 'Setting empty name as "blank"';
3249 }
3250 name = "blank";
3251 }
3252
3253 // Get list of all names in graph.
3254 var graph = graphcanvas.graph;
3255 var nodes = graph._nodes;
3256 var nodenames = [];
3257 for (var node of nodes) {
3258 nodenames.push(node.title);
3259 }
3260 //console.log('Current graph nodes:', nodenames);
3261
3262 name = ne_mx.createValidName(name);
3263
3264 if (!nodenames.includes(name)) {
3265 return name;
3266 }
3267
3268 // Get starting number and root name
3269 var rootName = name;
3270 var i = 1;
3271 var number = name.match(/\d+$/);
3272 if (number) {
3273 i = (parseInt(number) + 1)
3274 rootName = name.slice(0, -number[0].length);
3275 }
3276
3277 var valid_name = rootName + i.toString();
3278 while (nodenames.includes(valid_name)) {
3279 i++;
3280 valid_name = rootName + i.toString();
3281 }
3282 return valid_name;
3283 }
3284};
3285
3293{
3298 if (!MxShadingGraphEditor.theEditor) {
3299 MxShadingGraphEditor.theEditor = this;
3300
3301 this.ui = null;
3302 this.fontSizeStyle = 'font-size: 11px;';
3303
3304 this.handler = new MxMaterialXHandler('MaterialX Handler', 'mtlx');
3305 let gltfConverter = new glTFMaterialX();
3306 this.handler.addConverter(gltfConverter);
3307
3308 console.log('Create new editor with exporter for:', gltfConverter.exportType());
3309
3310 }
3311 return MxShadingGraphEditor.theEditor;
3312 }
3313
3320 setUI(ui) {
3321 this.ui = ui;
3322 }
3323
3327 setDirty(w = null, h = null) {
3328 if (graph)
3329 graph.setDirtyCanvas(true, true);
3330 if (graphcanvas) {
3331 graphcanvas.resize(w,h);
3332 }
3333 }
3334
3343 debugOutput(text, severity, clear = null) {
3344 var consoleLog = MxShadingGraphEditor.theEditor.ui.consoleLogger;
3345 if (consoleLog) {
3346 consoleLog(text, severity, clear);
3347 }
3348 else {
3349 console.log('> ', text, ' severity:', severity);
3350 }
3351 }
3352
3360 if (this.handler) {
3361 this.handler.setSourceColorSpace(colorSpace);
3362 }
3363 }
3364
3372 if (this.handler) {
3373 this.handler.setTargetDistanceUnit(unit);
3374 }
3375 }
3376
3383 if (this.handler) {
3384 return this.handler.getSourceColorSpace();
3385 }
3386 return 'lin_rec709';
3387 }
3388
3395 if (this.handler) {
3396 return this.handler.getTargetDistanceUnit();
3397 }
3398 return 'meter';
3399 }
3400
3407 {
3408 if (title.length == 0) {
3409 return;
3410 }
3411 if (graphcanvas)
3412 {
3413 let nodesFound = [];
3414 let theGraph = graphcanvas.graph;
3415
3416 // TODO: Add a this search logic to LiteGraph.
3417 const pattern = new RegExp(title);
3418 for (var i = 0, l = theGraph._nodes.length; i < l; ++i) {
3419 if (pattern.test(theGraph._nodes[i].title)) {
3420 console.log('-- add found node:', theGraph._nodes[i].title);
3421 nodesFound.push(theGraph._nodes[i]);
3422
3423 // If exact match then only select that node
3424 if (theGraph._nodes[i].title == title)
3425 {
3426 nodesFound.length = 0;
3427 nodesFound.push(theGraph._nodes[i]);
3428 break;
3429 }
3430 }
3431 }
3432 //let node = graphcanvas.graph.findNodeByTitle(title);
3433 if (nodesFound.length > 0)
3434 {
3435 graphcanvas.selectNodes(nodesFound);
3436 graphcanvas.centerOnGraph(true);
3437 MxShadingGraphEditor.theEditor.updatePropertyPanel(nodesFound[0]);
3438 }
3439 else
3440 {
3441 this.debugOutput('Node not found: ' + title, 0, false);
3442 }
3443 }
3444 }
3445
3452 arrangeGraph(spacing = 80) {
3453 // This does not track the current subgraph.
3454 if (graphcanvas) {
3455 graphcanvas.graph.arrange(spacing);
3456 }
3457 }
3458
3463 var selected = graphcanvas.selected_nodes;
3464 for (var s in selected) {
3465 var node = selected[s];
3466 if (node.type == 'graph/subgraph') {
3467 graphcanvas.openSubgraph(node.subgraph);
3468 break;
3469 }
3470 }
3471 }
3472
3477 if (graphcanvas) {
3478 graphcanvas.closeSubgraph();
3479 }
3480 }
3481
3486 if (graphcanvas) {
3487 graphcanvas.ds.reset();
3488 graphcanvas.setDirty(true, true);
3489 graphcanvas.centerOnGraph(false);
3490 }
3491 }
3492
3497 if (graphcanvas) {
3498 graphcanvas.selectNodes();
3499 graphcanvas.setDirty(true, true);
3500 }
3501 }
3502
3508
3509 this.handler.sourceColorSpace = this.handler.DEFAULT_COLOR_SPACE;
3510 this.handler.targetDistanceUnits = this.handler.DEFAULT_DISTANCE_UNITS;
3512 this.updatePropertyPanel(null);
3513 if (graphcanvas) {
3514 // Set back to top graph
3515 graphcanvas.setGraph(graph);
3516 graphcanvas.graph.clear();
3517 graphcanvas.ds.reset();
3518 graphcanvas.setDirty(true, true);
3519 }
3520 customDocLibs = [];
3521 }
3522
3527 var data = JSON.stringify(graph.serialize(), null, 2);
3528 var blob = new Blob([data], { type: "text/plain" });
3529 var url = URL.createObjectURL(blob);
3530 var a = document.createElement("a");
3531 a.href = url;
3532 a.download = "serialized_graph.json";
3533 a.click();
3534 }
3535
3540 MxShadingGraphEditor.theEditor.clearGraph();
3541
3542 var input = document.createElement("input");
3543 input.style = this.fontSizeStyle;
3544 input.type = "file";
3545 input.accept = ".json";
3546 input.onchange = function (e) {
3547 var file = e.target.files[0];
3548 var reader = new FileReader();
3549 reader.onload = function (event) {
3550 var data = JSON.parse(event.target.result);
3551 graph.configure(data);
3552 };
3553 reader.readAsText(file);
3554 };
3555 input.click();
3556 }
3557
3565 saveGraphToFile(extension, graphWriteOptions) {
3566 if (this.handler.canExport(extension)) {
3567 this.handler.saveGraphToFile(extension, graph, graphWriteOptions);
3568 }
3569 else
3570 {
3571 this.debugOutput('Unsupported extension for saving graph:' + extension, 2, false);
3572 }
3573 }
3574
3581 saveGraphToString(extension, graphWriteOptions) {
3582 if (this.handler.canExport(extension)) {
3583 return this.handler.saveGraphToString(extension, graph, graphWriteOptions);
3584 }
3585 else
3586 {
3587 this.debugOutput('Unsupported extension for saving graph: ' + extension, 2, false);
3588 return '';
3589 }
3590 }
3591
3599 if (extension == 'mtlx') {
3600 this.handler.loadDefinitionsFromFile();
3601 }
3602 else
3603 {
3604 this.debugOutput('Unsupported extension for loading definitions: ' + extension, 2, false);
3605 }
3606 }
3607
3616 loadGraphFromFile(extension, auto_arrange, rerender=false) {
3617
3618 if (!this.handler.canImport(extension)) {
3619 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3620 return;
3621 }
3622
3623 // Load document from disk.
3624 if (extension == 'mtlx')
3625 {
3626 var input = document.createElement("input");
3627 input.style = this.fontSizeStyle;
3628 input.type = "file";
3629 input.accept = "." + this.handler.getExtension();
3630 input.onchange = function (e) {
3631 var file = e.target.files[0];
3632 console.log('Loading file: ' + file.name);
3633 MxShadingGraphEditor.theEditor.handler.loadFromFile(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange, rerender);
3634 };
3635 input.click();
3636 }
3637 else if (extension == 'zip')
3638 {
3639 var input = document.createElement("input");
3640 input.style = this.fontSizeStyle;
3641 input.type = "file";
3642 input.accept = ".zip";
3643 input.onchange = function (e) {
3644 var file = e.target.files[0];
3645 if (file) {
3646 console.log('Loading zip file: ' + file.name);
3647 MxShadingGraphEditor.theEditor.handler.loadFromZip(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange, rerender);
3648 }
3649 };
3650 input.click();
3651 }
3652 }
3653
3660 return this.handler.findRenderableItems(graph);
3661 }
3662
3673 loadGraphFromString(extension, content, fileName, auto_arrange, rerender=false) {
3674 if (!this.handler.canImport(extension)) {
3675 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3676 return;
3677 }
3678
3679 if (content.length > 0)
3680 this.handler.loadFromString(extension, content, fileName, auto_arrange, rerender);
3681 else
3682 MxShadingGraphEditor.theEditor.debugOutput('No content to load', 2, false);
3683 }
3684
3692 if (!rgb) {
3693 console.log('rgbToHex empty !', rgb);
3694 return "#000000";
3695 }
3696 return '#' + rgb.map(x => {
3697 var hex = Math.round(x * 255).toString(16);
3698 return hex.length === 1 ? '0' + hex : hex;
3699 }).join('');
3700 }
3701
3710 createButtonWithImageAndText(imageSrc, text, id) {
3711 // Create image element
3712 var img = document.createElement("img");
3713 img.id = id + "_img";
3714 img.src = imageSrc;
3715 img.classList.add("img-fluid");
3716
3717 // Create text element
3718 var span = document.createElement("span");
3719 span.id = id + "_text";
3720 span.textContent = " " + text;
3721
3722 // Create button element
3723 var button = document.createElement("button");
3724 button.id = id;
3725 button.classList.add("btn", "btn-sm", "btn-outline-secondary", "form-control", "form-control-sm");
3726 button.style = this.fontSizeStyle;
3727 button.appendChild(img);
3728 button.appendChild(span);
3729
3730 return button;
3731 }
3732
3740 openImageDialog(theNode, updateProp, wantURI) {
3741
3742 // Dynamically create a file input element
3743 var fileInput = document.createElement('input');
3744 fileInput.type = 'file';
3745 fileInput.accept = '*'; // Accept any image file
3746 fileInput.style.display = 'none';
3747 document.body.appendChild(fileInput);
3748
3749 fileInput.click();
3750
3751 // TODO : Cache the fileURI on the node so can display without loading...
3752 fileInput.addEventListener('change', function () {
3753 var fileURI = fileInput.value.split('\\').pop(); // Get the filename without the full path
3754 var file = fileInput.files[0];
3755 //if (wantURI)
3756 fileURI = URL.createObjectURL(file);
3757
3758 var updateElementId = '__pp:' + updateProp;
3759 var textInput = document.getElementById(updateElementId);
3760 //console.log('New filename:', fileURI, 'updateElementId:', updateElementId, 'updateProp:', updateProp);
3761 textInput.value = fileURI;
3762 theNode.setProperty(updateProp, fileURI);
3763
3764 var propertypanel_preview = document.getElementById('propertypanel_preview');
3765 if (propertypanel_preview) {
3766 propertypanel_preview.src = URL.createObjectURL(file);
3767 propertypanel_preview.style.display = "block";
3768 }
3769
3770 var previewImage = false;
3771 if (previewImage) {
3772 if (propertypanel_preview) {
3773 var reader = new FileReader();
3774 reader.onload = function (event) {
3775 propertypanel_preview.src = event.target.result;
3776 };
3777
3778 // Read the file as a data URL (base64 encoded string)
3779 reader.readAsDataURL(file);
3780 propertypanel_preview.style.display = "block";
3781 }
3782 }
3783
3784 document.body.removeChild(fileInput);
3785 });
3786 }
3787
3795 return fetch(uri)
3796 .then(response => {
3797 if (response.ok) {
3798 return Promise.resolve(true);
3799 } else {
3800 return Promise.resolve(false);
3801 }
3802 })
3803 .catch(error => {
3804 console.log('Error checking URI:', error);
3805 return Promise.resolve(false);
3806 });
3807 }
3808
3816 createColorSpaceInput(colorSpaces, activeItem) {
3817 var select = document.createElement("select");
3818 select.className = "form-control form-control-sm";
3819 select.style = this.fontSizeStyle;
3820 select.id = "propertypanel_colorspace";
3821 for (var i = 0; i < colorSpaces.length; i++) {
3822 var option = document.createElement("option");
3823 option.value = colorSpaces[i];
3824 option.text = colorSpaces[i];
3825 select.add(option);
3826 }
3827 // Add "none" option
3828 var option = document.createElement("option");
3829 option.value = "none";
3830 option.text = "none";
3831 select.add(option);
3832
3833 select.value = activeItem;
3834 return select;
3835 }
3836
3845 createUnitsInput(units, unittype, activeItem) {
3846 var select = document.createElement("select");
3847 select.className = "form-control form-control-sm";
3848 select.style = this.fontSizeStyle;
3849 select.id = "propertypanel_units";
3850 for (var i = 0; i < units.length; i++) {
3851 var option = document.createElement("option");
3852 var unit_pair = units[i];
3853 if (unit_pair[1] == unittype) {
3854 option.value = unit_pair[0];
3855 option.text = unit_pair[0];
3856 select.add(option);
3857 }
3858 }
3859 select.value = activeItem;
3860 return select;
3861 }
3862
3870 var propertypanel_preview = document.getElementById('propertypanel_preview');
3871 if (curImage && propertypanel_preview) {
3872 this.uriExists(curImage)
3873 .then(exists => {
3874 if (exists) {
3875 propertypanel_preview.src = curImage;
3876 propertypanel_preview.style.display = "block";
3877 } else {
3878 //propertypanel_preview.style.display = "none";
3879 propertypanel_preview.src = "./Icons/no_image.png";
3880 propertypanel_preview.style.display = "block";
3881 MxShadingGraphEditor.theEditor.debugOutput('Image does not exist: ' + curImage, 1);
3882 }
3883 });
3884 }
3885 }
3886
3898 const TRUNC_TEXT = 20;
3899
3900 //console.log('Update Panel For:', node);
3901 var propertypanelcontent = MxShadingGraphEditor.theEditor.ui.propertypanel_content;
3902 if (!propertypanelcontent) {
3903 console.error('No property panel content widget found!');
3904 return;
3905 }
3906 // Delete all children
3907 while (propertypanelcontent.firstChild) {
3908 propertypanelcontent.removeChild(propertypanelcontent.firstChild);
3909 }
3910
3911 // Update icon
3912 var panelIcon = MxShadingGraphEditor.theEditor.ui.propertypanel_icon;
3913 if (node && node.nodedef_icon) {
3914 panelIcon.src = node.nodedef_icon;
3915 }
3916 else if (this.ui.icon_map) {
3917 if (!node || node.type == 'graph/subgraph') {
3918 panelIcon.src = this.ui.icon_map['_default_graph_'];
3919 } else {
3920 panelIcon.src = this.ui.icon_map['_default_'];
3921 }
3922 }
3923
3924 propertypanelcontent.innerHTML = "";
3925
3926 let colorSpaces = this.handler.getColorSpaces();
3927 let targetUnits = this.handler.getUnits();
3928
3929 let inUnselectedNodeGraph = false
3930 if (!node && graphcanvas.graph._subgraph_node) {
3931 node = graphcanvas.graph._subgraph_node;
3932 inUnselectedNodeGraph = true
3933 // Note openSubgraph() was modified to select the subgraph node so this
3934 // will trigger properly on opening a subgraph !!!
3935 // See: this.selectNode(graph._subgraph_node);
3936 //console.log('In subgraph but no node selected. Select subgraph node', node)
3937 }
3938 else if (!node && !graphcanvas.graph._is_subgraph) {
3939 var docInfo = [['Colorspace', this.getSourceColorSpace()],
3940 ['Distance', this.getTargetDistanceUnit()]];
3941
3942 for (let item of docInfo) {
3943
3944 let elem = document.createElement("div");
3945 elem.className = "row px-1 py-0";
3946 let label = document.createElement("div");
3947 label.className = "col py-0 col-form-label-sm text-left";
3948 label.style = this.fontSizeStyle;
3949 label.innerHTML = "<b>" + item[0] + "</b>";
3950 elem.appendChild(label);
3951
3952 if (item[0] == 'Colorspace' && colorSpaces.length > 0) {
3953 // Create colorspace drop down
3954 var inputCol = document.createElement("div");
3955 inputCol.className = "col text-left";
3956 var select = this.createColorSpaceInput(colorSpaces, item[1]);
3957 select.onchange = function (e) {
3958 MxShadingGraphEditor.theEditor.setSourceColorSpace(e.target.value);
3959 }
3960 inputCol.appendChild(select);
3961 elem.appendChild(inputCol);
3962 }
3963 else if (item[0] == 'Distance' && targetUnits.length > 0) {
3964 // Create units drop down
3965 var inputCol = document.createElement("div");
3966 inputCol.className = "col text-left";
3967 var select = this.createUnitsInput(targetUnits, 'distance', item[1]);
3968 select.onchange = function (e) {
3969 MxShadingGraphEditor.theEditor.setTargetDistanceUnit(e.target.value);
3970 }
3971 inputCol.appendChild(select);
3972 elem.appendChild(inputCol);
3973 }
3974 /* var inputCol = document.createElement("div");
3975 inputCol.className = "col text-left";
3976 var nameInput = document.createElement("input");
3977 nameInput.type = "text";
3978 nameInput.value = item[1];
3979 nameInput.className = "form-control form-control-sm";
3980 nameInput.disabled = true;
3981 elem.appendChild(inputCol);
3982 inputCol.appendChild(nameInput);
3983 }
3984 */
3985 propertypanelcontent.appendChild(elem);
3986 }
3987 return;
3988 }
3989
3990 var _category = node.nodedef_node;
3991 var _type = node.nodedef_type;
3992
3993 var isNodeGraph = node.type == 'graph/subgraph';
3994 if (isNodeGraph) {
3995 //console.log('>> Update subgraph property panel:', node);
3996 _category = 'nodegraph';
3997 if (node.outputs) {
3998 if (node.outputs.length > 1) {
3999 _type = 'multi';
4000 }
4001 else if (node.outputs.length > 0) {
4002 _type = node.outputs[0].type;
4003 }
4004 }
4005 else {
4006 _type = '';
4007 }
4008 }
4009 else {
4010 if (_category == 'surfacematerial') {
4011 _type = '';
4012 }
4013 }
4014
4015 // Identification row
4016 var elem = document.createElement("div");
4017 elem.className = "row px-1 py-1";
4018
4019 // Node category and output type
4020 var label = document.createElement("div");
4021 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4022 label.style = this.fontSizeStyle;
4023 label.innerHTML = "<b>" + _category;
4024 if (_type.length > 0) {
4025 label.innerHTML += '<br>' + _type;
4026 }
4027 label.innerHTML += "</b>";
4028 elem.appendChild(label);
4029
4030 // Node name / title
4031 var inputCol = document.createElement("div");
4032 inputCol.className = "col py-0";
4033 var nameInput = document.createElement("input");
4034 nameInput.style = this.fontSizeStyle;
4035 nameInput.type = "text";
4036 nameInput.value = node.title;
4037 nameInput.className = "form-control form-control-sm";
4038 let that = this;
4039 nameInput.onchange = function (e) {
4040 var oldTitle = node.title;
4041 var newTitle = MxShadingGraphEditor.theEditor.handler.createValidName(e.target.value);
4042 if (newTitle != oldTitle)
4043 {
4044 that.monitor.onNodeRenamed(node, newTitle);
4045 node.title = newTitle;
4046 }
4047 e.target.value = node.title;
4048 //console.log('node.graph._is_subgraph:', node)
4049 if (node.graph._is_subgraph) {
4050 if (node.nodedef_node == 'input') {
4051 //console.log('-------- Rename subgraph input:', node.title);
4052 node.graph.renameInput(oldTitle, node.title);
4053 }
4054 else if (node.nodedef_node == 'output') {
4055 //console.log('----------- Rename subgraph output:', node.title);
4056 node.graph.renameOutput(oldTitle, node.title);
4057 }
4058 }
4059
4060 // Note: there is a custom size fo subgraphs.
4061 node.setSize(node.computeSize());
4062 node.setDirtyCanvas(true, true);
4063 }
4064 inputCol.appendChild(nameInput);
4065
4066 // TODO: Generate swatches on the fly
4067 if (node.nodedef_node != 'input' && node.nodedef_node != 'output'
4068 && node.type != 'graph/subgraph') {
4069 var imagePreview = document.createElement("img");
4070 imagePreview.src = "./Icons/no_image.png";
4071 var previewSet = false;
4072 //console.log('Check for preview:', node.nodedef_swatch, 'category:', _category)
4073 imagePreview.style.display = "none";
4074 imagePreview.src = "./Icons/no_image.png";
4075 /* if (node.nodedef_swatch &&
4076 (_type == 'BSDF' || _type == 'EDF' || _type == 'surfaceshader'))
4077 {
4078 this.uriExists(node.nodedef_swatch)
4079 .then(exists => {
4080 if (exists) {
4081 previewSet = true;
4082 imagePreview.style.display = "block";
4083 imagePreview.src = node.nodedef_swatch;
4084 }
4085 });
4086 } */
4087 imagePreview.id = "propertypanel_preview";
4088 imagePreview.className = "img-fluid form-control form-control-sm";
4089 inputCol.appendChild(imagePreview);
4090 }
4091
4092 elem.appendChild(label);
4093 elem.appendChild(inputCol);
4094
4095 // Toggle show/hide of inputs with default values
4096 if (!isNodeGraph)
4097 {
4098 var filterCol = document.createElement("div");
4099 filterCol.className = "col-2 py-0";
4100 filterCol.width = 16;
4101 var filterIcon = document.createElement("button");
4102 //filterIcon.setAttribute(data-bs-toggle, "tooltip");
4103 //filterIcon.setAttribute(data-bs-title, "Show/Hide Default Value Inputs");
4104 if (node.showDefaultValueInputs == null)
4105 {
4106 node.showDefaultValueInputs = true;
4107 }
4108 var img = document.createElement("img");
4109 if (node.showDefaultValueInputs)
4110 {
4111 img.src = "./Icons/funnel_white.svg";
4112 filterIcon.className = "btn btn-sm btn-outline-secondary";
4113 }
4114 else
4115 {
4116 img.src = "./Icons/funnel-fill_white.svg";
4117 filterIcon.className = "btn btn-sm btn-outline-warning";
4118 }
4119 filterIcon.appendChild(img);
4120 filterIcon.onclick = function (e) {
4121 node.showDefaultValueInputs = !node.showDefaultValueInputs;
4123 }
4124 filterCol.appendChild(filterIcon);
4125 elem.appendChild(filterCol);
4126 }
4127
4128 propertypanelcontent.appendChild(elem);
4129
4130 let hr = document.createElement("hr");
4131 hr.classList.add("my-1");
4132 propertypanelcontent.appendChild(hr);
4133
4134 let current_details = null;
4135 let first_details = true;
4136 let nodeInputs = node.inputs;
4137 //console.log('Outputs:', nodeOutputs);
4138
4139 let targetNodes = [];
4140 for (var i in nodeInputs) {
4141 let nodeInput = nodeInputs[i];
4142
4143 let inputName = nodeInput.name;
4144 let nodeInputLink = nodeInput.link;
4145 let uiName = inputName;
4146 // remove "_" from uiName
4147 uiName = uiName.replace(/_/g, ' ');
4148 let uimin = null;
4149 let uimax = null;
4150 let colorspace = '';
4151 let units = '';
4152 let defaultgeomprop = '';
4153
4154 //console.log('Scan input:', inputName, ' on node: ', node.graph);
4155
4156 let property_info = node.getPropertyInfo(inputName);
4157 let ng_property_info = null;
4158 if (isNodeGraph)
4159 {
4160 //console.log('Check subgraph input node property_info:')
4161 let sg = node.subgraph;
4162 if (sg)
4163 {
4164 let sg_nodes = sg._nodes;
4165 for (var sg_node of sg_nodes)
4166 {
4167 if (sg_node.title == inputName)
4168 {
4169 //console.log('Found subgraph node:', sg_node.title);
4170 ng_property_info = sg_node.getPropertyInfo("in");
4171 if (ng_property_info)
4172 {
4173 //console.log('Use subgraph node input property info:', ng_property_info);
4174 break;
4175 }
4176 }
4177 }
4178 }
4179 }
4180 //console.log('1. get property info for i: ', inputName, 'info: ', property_info)
4181
4182 var skipInterorConnectedInput = false;
4183 if (node.graph._is_subgraph) {
4184 // Find input on subgraph node
4185 //console.log('Check subgraph for link:', node.graph)
4186 var sg_node = node.graph._subgraph_node;
4187 if (sg_node) {
4188 //console.log('Check for input on sg node', sg_node, node.title);
4189 var slot = sg_node.findInputSlot(node.title);
4190 if (slot != null) {
4191 if (sg_node.inputs) {
4192 //property_info = sg_node.properties_info[slot];
4193 var slotInput = sg_node.inputs[slot];
4194 //console.log('check slot: ', slotInput.link);
4195 if (slotInput != null && slotInput.link != null) {
4196 skipInterorConnectedInput = true;
4197 }
4198 }
4199 else {
4200 //console.log('Error: no subgraph node inputs for subgraph input!', sg_node, node.title);
4201 }
4202 }
4203 }
4204 }
4205
4206 if (skipInterorConnectedInput) {
4207 console.log('Skip interior connected input: ', nodeInput);
4208 continue;
4209 }
4210
4211 //console.log('Property info:', property_info, ' for input:', inputName);
4212 if (ng_property_info) {
4213 //console.log('Replace property info:', property_info);
4214 property_info = ng_property_info;
4215 }
4216 if (property_info) {
4217 //console.log('Extract input property info:', property_info);
4218 if (property_info.defaultgeomprop)
4219 {
4220 defaultgeomprop = property_info.defaultgeomprop;
4221 }
4222 if (property_info.colorspace) {
4223 colorspace = property_info.colorspace;
4224 }
4225 if (property_info.unit) {
4226 units = property_info.unit;
4227 }
4228 if (property_info.uiname) {
4229 uiName = property_info.uiname;
4230 }
4231 if (property_info.uimin) {
4232 uimin = property_info.uimin;
4233 }
4234 if (property_info.uimax) {
4235 uimax = property_info.uimax;
4236 }
4237 if (property_info.uifolder && property_info.uifolder.length > 0) {
4238 // Create a details element
4239 if (current_details == null || current_details.id != property_info.uifolder) {
4240 //console.log('Create new details:', property_info.uifolder);
4241 current_details = document.createElement("details");
4242 current_details.id = property_info.uifolder;
4243 current_details.open = first_details;
4244 current_details.classList.add('w-100', 'p-1', 'border', 'border-secondary', 'rounded', 'my-1');
4245 first_details = false;
4246 var summary = document.createElement('summary')
4247 summary.style = this.fontSizeStyle;
4248 summary.innerHTML = "<b>" + property_info.uifolder + "</b>"
4249 //summary.classList.add('btn', 'btn-sm', 'btn-outline-secondary', 'btn-block');
4250 current_details.appendChild(summary);
4251
4252 }
4253 else {
4254 //current_details = null;
4255 }
4256 }
4257 else {
4258 current_details = null;
4259 }
4260 //console.log('2. uiName:', uiName, 'uimin:', uimin, 'uimax:', uimax, 'uiFolder:', property_info.uifolder);
4261 }
4262 else {
4263 current_details = null;
4264 }
4265
4266 var elem = null;
4267
4268 // Check if there is a link
4269 if (nodeInputLink) {
4270 let upstreamLink = null;
4271
4272 let nodegraph = node.graph;
4273 let link = nodegraph.links[nodeInputLink];
4274 //console.log('link:', link);
4275 let linkId = link && link.origin_id;
4276 let linkNode = linkId && nodegraph.getNodeById(linkId);
4277
4278 if (linkNode) {
4279
4280 //console.log('linkNode:', linkNode);`
4281 let linkSlot = link.origin_slot;
4282 //console.log('linkSlot:', linkSlot);
4283 let linkOutput = linkNode.outputs[linkSlot];
4284 //console.log('linkOutput:', linkOutput);
4285 upstreamLink = linkNode.title + '.' + linkOutput.name;
4286 //console.log('upstreamLink:', upstreamLink);
4287
4288 let id = "__pp:" + inputName;
4289 let buttonText = upstreamLink;
4290 // Truncate long names
4291 if (buttonText.length > TRUNC_TEXT) {
4292 buttonText = buttonText.substring(0, TRUNC_TEXT) + "...";
4293 }
4294 let input = this.createButtonWithImageAndText("./Icons/arrow_up_white.svg", buttonText, id);
4295
4296 input.onclick = function (e) {
4297
4298 var inputName = e.target.id;
4299 inputName = inputName.replace('__pp:', '');
4300 inputName = inputName.replace('_text', '');
4301 inputName = inputName.replace('_img', '');
4302 console.log('Clicked traversal button:', inputName);
4303
4304 console.log('Jump to node:', linkNode.title);
4305 graphcanvas.selectNodes([linkNode]);
4307 MxShadingGraphEditor.theEditor.updatePropertyPanel(linkNode);
4308 node.setDirtyCanvas(true, true);
4309 }
4310
4311 // Add new row
4312 elem = document.createElement("div");
4313 elem.className = "row px-1 py-0";
4314
4315 input.id = "__pp:" + inputName;
4316
4317 var label = document.createElement("div");
4318 // invert-button
4319 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4320 label.style = this.fontSizeStyle;
4321 label.innerHTML = uiName;
4322 label.for = input.id;
4323 elem.appendChild(label);
4324
4325 // form-control
4326 if (useFormControl) {
4327 input.classList.add("form-control");
4328 }
4329 input.classList.add("form-control-sm");
4330 // Disable if don't want interaction.
4331 if (!graphcanvas.allow_interaction)
4332 input.disabled = true;
4333
4334 var propvalue = document.createElement("div");
4335 propvalue.className = "col p-1";
4336 propvalue.appendChild(input);
4337
4338 elem.appendChild(propvalue);
4339 }
4340 }
4341
4342 else {
4343
4344 targetNodes[i] = node;
4345 let targetNode = targetNodes[i];
4346 let propertyKey = inputName;
4347
4348 var property = targetNode.properties[inputName];
4349 if (property == null) {
4350 if (isNodeGraph) {
4351 var subgraph = targetNode.subgraph;
4352 if (subgraph) {
4353 //console.log('Find node by title', inputName, ' in subgraph', subgraph._nodes);
4354 var subNode = subgraph.findNodeByTitle(inputName);
4355 if (subNode) {
4356 targetNodes[i] = subNode;
4357 propertyKey = 'in';
4358 property = targetNodes[i].properties['in'];
4359 //console.log('Route to subgraph target node:', targetNode, targetNode.title, '. ', inputName, ' = ', JSON.stringify(property), 'propkey=', propertyKey);
4360 }
4361 }
4362 }
4363 if (property == null) {
4364 console.log('Update: Cannot find property value for input:', inputName);
4365 continue;
4366 }
4367 }
4368
4369 // Check if there is a default property value. If so skip showing it
4370 if (defaultgeomprop)
4371 {
4372 //console.log('Skip input with defaultgeomprop: ' + inputName);
4373 continue;
4374 }
4375
4376 // Check if property value is same as property info default value
4377 if (!node.showDefaultValueInputs && !isNodeGraph)
4378 {
4379 let isDefault = node.isDefaultValue(inputName);
4380 if (isDefault)
4381 {
4382 continue;
4383 }
4384 }
4385
4386 // Add new row
4387 elem = document.createElement("div");
4388 elem.className = "row px-1 py-0";
4389
4390 var input = null;
4391 var input_btn = null;
4392 let input_slider = null;
4393 var colorspace_unit_btn = null;
4394 var useFormControl = true;
4395
4396 // Add colorspace drop-down if specified.
4397 if (colorspace.length > 0) {
4398 // Create drop-down menu to choose colorspace from list stored
4399 // in the handler class getColorSpaces() method.
4400 //
4401 colorspace_unit_btn = this.createColorSpaceInput(colorSpaces, colorspace);
4402 let theNode = targetNodes[i];
4403 colorspace_unit_btn.onchange = function (e) {
4404
4405 theNode.setPropertyInfo(inputName, 'colorspace', e.target.value);
4406 }
4407 }
4408 else if (units.length > 0 && property_info.unittype) {
4409 // Add units drop-down if specified.
4410 colorspace_unit_btn = this.createUnitsInput(targetUnits, property_info.unittype, units);
4411 let theNode = targetNodes[i];
4412 colorspace_unit_btn.onchange = function (e) {
4413 theNode.setPropertyInfo(inputName, 'unit', e.target.value);
4414 }
4415 }
4416
4417 let proptype = nodeInput.type;
4418 if (proptype == 'float' || proptype == 'integer') {
4419 var isFloat = proptype == 'float';
4420
4421 input = document.createElement("input");
4422 input.id = propertyKey + '_box';
4423 input.style = this.fontSizeStyle;
4424 input.type = 'number';
4425 input.classList.add("form-control", "form-control-sm", "ps-0");
4426 input.setAttribute('propertyKey', propertyKey);
4427
4428 input_slider = document.createElement("input");
4429 input_slider.id = propertyKey + '_slider';
4430 //input_slider.style = this.fontSizeStyle;
4431 input_slider.type = 'range';
4432 input_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4433 input_slider.setAttribute('propertyKey', propertyKey);
4434
4435 if (uimin) {
4436 input.min = uimin;
4437 }
4438 else {
4439 input.min = Math.min(property, 0);
4440 }
4441 if (uimax) {
4442 input.max = uimax;
4443 }
4444 else {
4445 if (isFloat)
4446 {
4447 input.max = Math.max(property*3, 10.0);
4448 }
4449 else {
4450 input.max = Math.max(property*3, 100);
4451 }
4452 }
4453
4454
4455 input_slider.min = input.min;
4456 input_slider.max = input.max;
4457 if (isFloat) {
4458 input.step = (input.max - input.min) / 100.0;
4459 input_slider.step = input.step;
4460 }
4461 else {
4462 input_slider.step = 1;
4463 input.step = 1;
4464 }
4465
4466 input.value = input_slider.value = property;
4467
4468 /* console.log('> ' + propertyKey + ' - Set up slider: min, max, value',
4469 input_slider.min, input_slider.max, input_slider.value
4470 );
4471 console.log('> ' + propertyKey + ' - Set up box: min, max, value',
4472 input.min, input.max, input.value
4473 ); */
4474
4475 let theBox = input;
4476 let theSlider = input_slider;
4477 let theNode = targetNodes[i];
4478 input_slider.onchange = function (e) {
4479 var pi = e.target.getAttribute('propertyKey');
4480 var val = parseFloat(e.target.value);
4481 theNode.setProperty(pi, val);
4482 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4483 }
4484 input_slider.oninput = function(e) {
4485 var pi = e.target.getAttribute('propertyKey');
4486 var val = parseFloat(e.target.value);
4487 theNode.setProperty(pi, val);
4488 theBox.value = e.target.value;
4489 }
4490
4491 input.onchange = function (e) {
4492 var pi = e.target.getAttribute('propertyKey');
4493 var val = parseFloat(e.target.value);
4494 theNode.setProperty(pi, val);
4495 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4496 }
4497 input.oninput = function(e) {
4498 var pi = e.target.getAttribute('propertyKey');
4499 var val = parseFloat(e.target.value);
4500 theNode.setProperty(pi, val);
4501 theSlider.value = e.target.value;
4502 }
4503 }
4504 else if (proptype == 'string' || proptype == 'filename') {
4505 input = document.createElement("input");
4506 input.style = this.fontSizeStyle;
4507 input.type = "text";
4508 if (proptype == 'filename') {
4509 var curImage = property;
4510 this.updateImagePreview(curImage);
4511
4512 input_btn = document.createElement("button");
4513 input_btn.classList.add("btn", "btn-sm", "btn-outline-secondary");
4514 input_btn.innerHTML = "+";
4515 input_btn.setAttribute('propertyKey', propertyKey);
4516 var fileId = "__pp:" + inputName;
4517 let theNode = targetNodes[i];
4518 input_btn.onclick = function (e) {
4519 var pi = e.target.getAttribute('propertyKey');
4520 MxShadingGraphEditor.theEditor.openImageDialog(theNode, pi, false);
4521 }
4522 }
4523
4524 else
4525 {
4526 // Check if there is a 'enm' property
4527 //console.log('------------------- handle enum property info:', property_info, '. property:', property);
4528 if (property_info && property_info.enum) {
4529
4530 //console.log('----------------- found enum property info:', property_info.enum);
4531
4532 // Create drop-down menu to choose from list stored in the handler class.
4533 input = document.createElement("select");
4534 input.style = this.fontSizeStyle;
4535 input.classList.add("form-control", "form-control-sm");
4536
4537 input.setAttribute('propertyKey', propertyKey);
4538 let theNode = targetNodes[i];
4539 let enums = property_info.enum;
4540 for (let j = 0; j < enums.length; j++) {
4541 let option = document.createElement("option");
4542 option.value = enums[j];
4543 option.text = enums[j];
4544 input.add(option);
4545 }
4546 input.value = property;
4547 input.setAttribute('propertyKey', propertyKey);
4548 input.onchange = function (e) {
4549 var pi = e.target.getAttribute('propertyKey');
4550 theNode.setProperty(pi, e.target.value);
4551 //console.log('Update string property:', pi, theNode.properties[pi])
4552 }
4553 }
4554 }
4555
4556 if (property_info && !property_info.enm) {
4557 input.value = property;
4558 input.setAttribute('propertyKey', propertyKey);
4559 let theNode = targetNodes[i];
4560 let isFilename = proptype == 'filename';
4561 let that = this;
4562 input.onchange = function (e) {
4563 var pi = e.target.getAttribute('propertyKey');
4564 //theNode.properties[pi] = e.target.value;
4565 theNode.setProperty(pi, e.target.value);
4566 if (isFilename) {
4567 //console.log('Update filename property:', pi, theNode.properties[pi])
4568 that.updateImagePreview(e.target.value);
4569 }
4570 else {
4571 //console.log('Update string property:', pi, theNode.properties[pi])
4572 }
4573 }
4574 }
4575 }
4576 else if (proptype == 'boolean') {
4577 //console.log('Add Boolean property:', property);
4578 input = document.createElement("input");
4579 input.style = this.fontSizeStyle;
4580 input.type = "checkbox";
4581 input.classList = "form-check-input";
4582 useFormControl = false;
4583 input.checked = property;
4584 input.setAttribute('propertyKey', propertyKey);
4585 let theNode = targetNodes[i];
4586 input.onchange = function (e) {
4587 var pi = e.target.getAttribute('propertyKey');
4588 //theNode.properties[pi] = e.target.checked;
4589 theNode.setProperty(pi, e.target.checked);
4590 //console.log('Update boolean property:', pi, theNode.properties[pi]);
4591 }
4592 }
4593
4594 else if (proptype == 'vector2' || proptype == 'vector3' || proptype == 'vector4')
4595 {
4596 // Find index of proptype in ['vector2', 'vector3', 'vector4' ]
4597 var vector_size = ['vector2', 'vector3', 'vector4'].indexOf(proptype) + 2;
4598 input = document.createElement("div");
4599 useFormControl = false;
4600
4601 input.className = "row py-1 ps-4 pe-0";
4602
4603 for (let v=0; v<vector_size; v++)
4604 {
4605 //console.log('Vector property:[', 0, '] = ', property[0], proptype)
4606 let subinput = document.createElement("input");
4607 subinput.style = this.fontSizeStyle;
4608 subinput.type = 'number';
4609 subinput.classList.add("form-control");
4610 subinput.classList.add("form-control-sm");
4611 subinput.setAttribute('propertyKey', propertyKey);
4612
4613 let subinput_slider = document.createElement("input");
4614 subinput_slider.id = propertyKey + '_slider';
4615 subinput_slider.type = 'range';
4616 subinput_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4617 subinput_slider.setAttribute('propertyKey', propertyKey);
4618
4619 if (uimin) {
4620 subinput.min = uimin[v];
4621 }
4622 else {
4623 subinput.min = Math.min(property[v]*3, 0);
4624 }
4625 if (uimax) {
4626 subinput.max = uimax[v];
4627 }
4628 else {
4629 subinput.max = Math.max(property[v]*3, 10.0);
4630 }
4631
4632 subinput_slider.min = subinput.min;
4633 subinput_slider.max = subinput.max;
4634 subinput.step = (subinput.max - subinput.min) / 100.0;
4635 subinput_slider.step = subinput.step;
4636
4637 subinput.value = subinput_slider.value = property[v];
4638
4639 let theNode = targetNodes[i];
4640 let vector_index = v;
4641 let theBox = subinput;
4642 let theSlider = subinput_slider;
4643 theBox.onchange = function (e) {
4644 let pi = e.target.getAttribute('propertyKey');
4645 let value = parseFloat(e.target.value);
4646 let newValue = theNode.properties[pi].map(item => item);
4647 newValue[vector_index] = value;
4648 theNode.setProperty(pi, newValue);
4649 //theNode.properties[pi][0] = value;
4650 //console.log('Update Vector property:"', pi, '"', 0, parseFloat(e.target.value), theNode.properties[pi])
4651 }
4652 theBox.oninput = function(e) {
4653 let pi = e.target.getAttribute('propertyKey');
4654 let value = parseFloat(e.target.value);
4655 let newValue = theNode.properties[pi].map(item => item);
4656 newValue[vector_index] = value;
4657 theNode.setProperty(pi, newValue);
4658 theSlider.value = e.target.value;
4659 }
4660
4661 theSlider.onchange = function (e) {
4662 let pi = e.target.getAttribute('propertyKey');
4663 let value = parseFloat(e.target.value);
4664 let newValue = theNode.properties[pi].map(item => item);
4665 newValue[vector_index] = value;
4666 theNode.setProperty(pi, newValue);
4667 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4668 }
4669 theSlider.oninput = function(e) {
4670 let pi = e.target.getAttribute('propertyKey');
4671 let value = parseFloat(e.target.value);
4672 let newValue = theNode.properties[pi].map(item => item);
4673 newValue[vector_index] = value;
4674 theNode.setProperty(pi, newValue);
4675 theBox.value = e.target.value;
4676 }
4677
4678
4679 let propvalue_slider = document.createElement("div");
4680 propvalue_slider.className = "col p-0";
4681 propvalue_slider.appendChild(subinput_slider);
4682
4683 let propvalue_box = document.createElement("div");
4684 propvalue_box.className = "col p-0";
4685 propvalue_box.appendChild(subinput);
4686
4687 let input_row = document.createElement("div");
4688 input_row.className = "row p-0";
4689 input_row.appendChild(propvalue_slider);
4690 input_row.appendChild(propvalue_box);
4691
4692 input.appendChild(input_row);
4693 }
4694 }
4695 else if (proptype == 'color3' || proptype == 'color4') {
4696 input = document.createElement("input");
4697 input.type = "color";
4698 //console.log('set color property:', property.length, property);
4699 if (property.length == 4) {
4700 input.value = this.rgbToHex([ property[0], property[1], property[2] ]);
4701 }
4702 else {
4703 input.value = this.rgbToHex(property);
4704 }
4705 input.setAttribute('propertyKey', propertyKey);
4706 let theNode = targetNodes[i];
4707 input.onchange = function (e) {
4708 // Convert hex to rgb in 0..1 range
4709 var hex = e.target.value;
4710 let fprecision = 4
4711 let rgb = [0,0,0]
4712 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4713 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4714 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4715 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4716 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4717 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4718 if (proptype == 'color4')
4719 rgb[3] = 1.0;
4720
4721 var pi = e.target.getAttribute('propertyKey');
4722 theNode.setProperty(pi, rgb);
4723 }
4724 let func = function (e) {
4725 // Convert hex to rgb in 0..1 range
4726 var hex = e.target.value;
4727 let rgb = [0, 0, 0];
4728 let fprecision = 4
4729 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4730 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4731 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4732 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4733 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4734 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4735 if (proptype == 'color4')
4736 rgb[3] = 1.0;
4737
4738 var pi = e.target.getAttribute('propertyKey');
4739 theNode.setProperty(pi, rgb);
4740 }
4741 input.onchange = func;
4742 input.oninput = func;
4743 }
4744 else {
4745 input = document.createElement("input");
4746 input.style = this.fontSizeStyle;
4747 input.type = "text";
4748 input.value = property;
4749 let propertyKey = inputName;
4750 let theNode = targetNodes[i];
4751 input.onchange = function (e) {
4752 theNode.setProperty(propertyKey, e.target.value);
4753 //theNode.properties[propertyKey] = e.target.value;
4754 }
4755 }
4756
4757 if (input) {
4758 input.id = "__pp:" + inputName;
4759 //console.log('> Add input:', input.id);
4760
4761 var label = document.createElement("div");
4762 label.className = "col-4 p-0 col-form-label-sm text-end";
4763 label.style = this.fontSizeStyle;
4764 label.innerHTML = uiName;
4765 label.for = input.id;
4766 elem.appendChild(label);
4767
4768 // form-control
4769 if (useFormControl) {
4770 input.classList.add("form-control");
4771 }
4772 input.classList.add("form-control-sm");
4773 // Disable if don't want interaction.
4774 if (!graphcanvas.allow_interaction)
4775 input.disabled = true;
4776
4777 var propvalue = document.createElement("div");
4778 propvalue.className = "col py-0";
4779 if (input_slider)
4780 {
4781 propvalue.classList.add('ps-1');
4782 }
4783 propvalue.appendChild(input);
4784
4785 if (input_btn) {
4786 var propbutton = document.createElement("div");
4787 propbutton.className = "col-2 py-0";
4788 //console.log('Add input button:', input_btn);
4789 propbutton.appendChild(input_btn);
4790 elem.appendChild(propbutton);
4791 }
4792 if (colorspace_unit_btn) {
4793 //console.log('Add cs / unit button:', input_btn);
4794 var propbutton = document.createElement("div");
4795 propbutton.className = "col col-form-label-sm";
4796 var details = document.createElement("details");
4797 var summary = document.createElement('summary')
4798 summary.style = this.fontSizeStyle;
4799 if (colorspace.length > 0)
4800 summary.innerHTML = "Colorspace";
4801 else if (targetUnits.length > 0)
4802 summary.innerHTML = "Units";
4803 details.appendChild(summary);
4804 details.appendChild(colorspace_unit_btn);
4805 propbutton.appendChild(details);
4806 propvalue.appendChild(propbutton);
4807 }
4808
4809 if (input_slider)
4810 {
4811 var propvalue_slider = document.createElement("div");
4812 propvalue_slider.className = "col py-0 pe-0";
4813 propvalue_slider.appendChild(input_slider);
4814 elem.appendChild(propvalue_slider);
4815 }
4816
4817 elem.appendChild(propvalue);
4818 }
4819 }
4820 //elem.innerHTML = "<em>" + i + "</em> : " + property;
4821 if (elem) {
4822 if (current_details) {
4823 //console.log('3a. append child to details:', current_details.id, elem, inputName);
4824 current_details.appendChild(elem);
4825 // It current_details not already in the propertypanelcontent, add it.
4826 if (current_details.parentElement == null) {
4827 propertypanelcontent.appendChild(current_details);
4828 }
4829 }
4830 else {
4831 propertypanelcontent.appendChild(elem);
4832 //console.log('3b. append child to parent content:', elem, inputName);
4833 }
4834 }
4835 }
4836
4837 //propertypanelcontent
4838
4839 let output_details = null;
4840 let nodeOutputs = null;
4841 let isSubgraph = graphcanvas.graph;
4842 //console.log('>> graph: ', graph, '. isnodegraph:', isNodeGraph,
4843 // 'inUnselectedNodeGraph: ', inUnselectedNodeGraph)
4844 if (!inUnselectedNodeGraph)
4845 nodeOutputs = node.outputs;
4846 for (let k in nodeOutputs) {
4847 let nodeOutput = nodeOutputs[k];
4848 //console.log('For node:', node.title, '.Found output:', nodeOutput.name, nodeOutput)
4849 let nodeOutputLinks = nodeOutput.links;
4850 for (let j in nodeOutputLinks) {
4851 let link_id = nodeOutputLinks[j];
4852 // Find the link in the graph's links array
4853 //console.log('-------------- linkid: ', link_id);
4854 //console.log('graphcanvas.graph:', node.graph)
4855 const link = graphcanvas.graph.links[link_id];
4856
4857 if (link) {
4858 if (!output_details) {
4859 output_details = document.createElement("details");
4860 output_details.id = "pp::outputs";
4861 output_details.open = true;
4862 output_details.classList.add('w-100', 'p-1', 'border', 'border-secondary', 'rounded', 'my-1');
4863 first_details = false;
4864 var summary = document.createElement('summary');
4865 {
4866 summary.style = this.fontSizeStyle;
4867 summary.innerHTML = "<b>Outputs</b>"
4868 }
4869 //summary.classList.add('btn', 'btn-sm', 'btn-outline-secondary', 'btn-block');
4870 output_details.appendChild(summary);
4871 }
4872
4873 //console.log('got link:', link);
4874 // Find the target node using the link's target node ID
4875 const targetNode = graphcanvas.graph.getNodeById(link.target_id);
4876
4877 if (targetNode) {
4878
4879 let targetSlot = link.target_slot;
4880 let targetInput = targetNode.inputs[targetSlot];
4881 //console.log(`- Node ${targetNode.title} (Input: ${targetInput.name})`);
4882
4883 let downstreamLink = targetNode.title + '.' + targetInput.name;
4884
4885 let id = "__pp:" + nodeOutput.name;
4886 let buttonText = downstreamLink;
4887 // Truncate long names
4888 if (buttonText.length > TRUNC_TEXT) {
4889 buttonText = buttonText.substring(0, TRUNC_TEXT) + "...";
4890 }
4891 let output = this.createButtonWithImageAndText("./Icons/arrow_down_white.svg", buttonText, id);
4892
4893 output.onclick = function (e) {
4894
4895 var inputName = e.target.id;
4896 inputName = inputName.replace('__pp:', '');
4897 inputName = inputName.replace('_text', '');
4898 inputName = inputName.replace('_img', '');
4899 console.log('Clicked traversal button:', inputName);
4900
4901 console.log('Jump to node:', targetNode.title);
4902 graphcanvas.selectNodes([targetNode]);
4904 MxShadingGraphEditor.theEditor.updatePropertyPanel(targetNode);
4905 node.setDirtyCanvas(true, true);
4906 }
4907
4908 // Add new row
4909 elem = document.createElement("div");
4910 elem.className = "row px-1 py-0";
4911
4912 let outputName = nodeOutput.name;
4913 output.id = "__pp:" + outputName;
4914
4915 var label = document.createElement("div");
4916 // invert-button
4917 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4918 label.style = this.fontSizeStyle;
4919 label.innerHTML = outputName;
4920 label.for = nodeOutput.id;
4921 elem.appendChild(label);
4922
4923 // form-control
4924 if (useFormControl) {
4925 output.classList.add("form-control");
4926 }
4927 output.classList.add("form-control-sm");
4928 // Disable if don't want interaction.
4929 if (!graphcanvas.allow_interaction)
4930 output.disabled = true;
4931
4932 var propvalue = document.createElement("div");
4933 propvalue.className = "col p-1";
4934 propvalue.appendChild(output);
4935
4936 elem.appendChild(propvalue);
4937 //console.log('DOM output', propvalue);
4938 output_details.appendChild(elem);
4939
4940 } else {
4941 console.log(`- Node with ID ${link.target_id} not found.`);
4942 console.log('--- Available nodes:', graphcanvas.graph._nodes_by_id);
4943 }
4944 } else {
4945 console.log(`- Link with ID ${link_id} not found.`);
4946 }
4947 }
4948
4949 }
4950 if (output_details) {
4951 propertypanelcontent.appendChild(output_details);
4952 }
4953 }
4954
4955
4963 initializeLiteGraph(canvas, readOnly = false) {
4964 // Initialize LiteGraph
4965 graph = new LiteGraph.LGraph();
4966 graphcanvas = new LiteGraph.LGraphCanvas(canvas, graph);
4967
4968 if (readOnly)
4969 {
4970 // Put red border around canvas as an indicator
4971 //canvas.style.border = "1px solid #ff1107";
4972 }
4973
4974 //
4975 // Set up graph overrides
4976 //
4977
4978 // Set up connection colors (off = not connected, on = connected)
4979 // TODO: Move this to application site and expose settings to user.
4980 graphcanvas.default_connection_color_byTypeOff = {
4981 integer: "#A32",
4982 float: "#161",
4983 vector2: "#265",
4984 vector3: "#465",
4985 vector4: "#275",
4986 color3: "#37A",
4987 color4: "#69A",
4988 matrix33: "#555",
4989 matrix44: "#666",
4990 string: "#395",
4991 filename: "#888",
4992 boolean: "#060",
4993 };
4994
4995 graphcanvas.default_connection_color_byType = {
4996 integer: "#D52",
4997 float: "#1D1",
4998 vector2: "#4D4",
4999 vector3: "#7D7",
5000 vector4: "#9D9",
5001 color3: "#4AF",
5002 color4: "#6CF",
5003 matrix33: "#AAA",
5004 matrix44: "#BBB",
5005 string: "#3F4",
5006 filename: "#FFF",
5007 boolean: "#0F0",
5008 };
5009
5010 // Making this a no-op as will not use the default panel
5011 graphcanvas.onShowNodePanel = function (node) {
5012 ;
5013 }
5014
5015 // Override to handle node selection
5016 graphcanvas.onNodeSelected = function (node) {
5017 if (MxShadingGraphEditor.theEditor.monitor)
5018 {
5019 let parentGraph = '';
5020 var is_subgraph = graphcanvas.graph._is_subgraph;
5021 if (is_subgraph)
5022 parentGraph = graphcanvas.graph._subgraph_node.title;
5023 MxShadingGraphEditor.theEditor.monitor.onNodeSelected(node, parentGraph);
5024 }
5026 }
5027
5028 // Override to handle node deselection
5029 graphcanvas.onNodeDeselected = function (node) {
5030 if (MxShadingGraphEditor.theEditor.monitor)
5031 {
5032 let parentGraph = '';
5033 var is_subgraph = graphcanvas.graph._is_subgraph;
5034 if (is_subgraph)
5035 parentGraph = graphcanvas.graph._subgraph_node.title;
5036 MxShadingGraphEditor.theEditor.monitor.onNodeDeselected(node, parentGraph);
5037 }
5039 }
5040
5041 // Add monitoring method for property info changes.
5042 // This API does not currently exist in LiteGraph, only getPropertyInfo() does.
5043 LGraphNode.prototype.setPropertyInfo = function(property, propertyInfo, value)
5044 {
5045 var info = null;
5046
5047 if (this.properties_info) {
5048 for (var i = 0; i < this.properties_info.length; ++i) {
5049 if (this.properties_info[i].name == property) {
5050 info = this.properties_info[i];
5051 break;
5052 }
5053 }
5054 }
5055
5056 if (info && info[propertyInfo])
5057 {
5058 if (this.onPropertyInfoChanged)
5059 {
5060 this.onPropertyInfoChanged(property, propertyInfo, value, info[propertyInfo]);
5061 }
5062 info[propertyInfo] = value;
5063 }
5064 else
5065 {
5066 console.warning('Failed to set property: ', property, '. info: ', propertyInfo, '. Value: ', value, '. Infos: ', this.properties_info);
5067 }
5068 }
5069
5070 // Add in a method to check if an input / property is the default value
5071 LGraphNode.prototype.isDefaultValue = function(property)
5072 {
5073 let info = null;
5074
5075 // Check if the property exists
5076 if (this.properties[property] == null)
5077 {
5078 console.warn('> Property value does not exist:', property);
5079 return false;
5080 }
5081 // Check if the property is linked
5082 if (this.getInputLink(property))
5083 {
5084 return false;
5085 }
5086
5087 if (this.properties_info != null)
5088 {
5089 for (let i = 0; i < this.properties_info.length; ++i) {
5090 if (this.properties_info[i].name == property) {
5091 info = this.properties_info[i];
5092 break;
5093 }
5094 }
5095 }
5096
5097 if (info != null && info.default_value != null)
5098 {
5099 let property_string = this.properties[property];
5100 let default_value_string = info.default_value;
5101 let isDefault = false;
5102 if (Array.isArray(default_value_string)) {
5103 default_value_string = default_value_string.map(String); // or .map(element => String(element))
5104 property_string = property_string.map(String); // or .map(element => String(element))
5105 isDefault = (JSON.stringify(default_value_string) == JSON.stringify(property_string));
5106 }
5107 else
5108 {
5109 isDefault = (default_value_string == property_string);
5110 }
5111 return isDefault;
5112 }
5113 else
5114 {
5115 console.warn('> Default value does not exist for:', property);
5116 }
5117 return false;
5118 }
5119
5120 //
5121 // Set up graph
5122 //
5123 graphcanvas.resize();
5124 this.monitor.monitorGraph(graph, true);
5125 graph.arrange(80);
5126
5127 // Run the graph. TODO: Add execution control.
5128 //graph.runStep();
5129
5130 // Override global options
5131 //graphcanvas.hide_unconnected = false;
5132 console.log('> Read only mode: ', readOnly);
5133 graphcanvas.read_only = readOnly;
5134 graphcanvas.allow_interaction = true; // Allow user interaction. TODO: Add option to turn this off
5135 graphcanvas.read_only_select = true; // Allow selection in read-only mode
5136 graphcanvas.allow_dragnodes = !readOnly; // Allow dragging nodes
5137 graphcanvas.allow_searchbox = !readOnly; // Allow search box
5138 graphcanvas.render_connections_arrows = true; // Render connection arrows
5139 graphcanvas.clear_background_color = "#222223"; // Set background color
5140 graphcanvas.max_zoom = 0.15; // Set maximum zoom level
5141 graphcanvas.connections_width = 2; // Set connection width
5142 graphcanvas.render_canvas_border = false; // Set canvas border
5143 graphcanvas.align_to_grid = false; // Align to grid
5144 graphcanvas.render_connection_arrows = false; // Render connection arrows
5145 graphcanvas.render_curved_connections = true; // Render curved connections
5146 //graphcanvas.background_image = null; // Set background image
5147 graphcanvas.show_info = false; // Turn off HUD
5148 graph.ctrl_shift_v_paste_connect_unselected_outputs = true;
5149
5150 //
5151 // Event handler overrides. TODO: Add more shortcuts
5152 //
5153 // Ad event handler to call centerOnNode with f key press within the canvas area
5154 canvas.addEventListener("keydown", function (e) {
5155 if (e.key === "f") {
5156 MxShadingGraphEditor.theEditor.centerNode();
5157 }
5158 });
5159
5160 // Ad event handler to call array with l key press within the canvas area
5161 canvas.addEventListener("keydown", function (e) {
5162 if (e.key === "l") {
5164 }
5165 });
5166
5167
5168 var isIdle = true;
5169 var context = canvas.getContext('2d');
5170
5171 function drawstart(event) {
5172 //context.beginPath();
5173 //context.moveTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
5174 console.log('>>>>>>>>>>> draw start');
5175 isIdle = false;
5176 }
5177
5178 function drawmove(event) {
5179 if (isIdle) return;
5180 //context.lineTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
5181 //context.stroke();
5182 console.log('>>>>>>>>>>> draw move');
5183 }
5184
5185 function drawend(event) {
5186 if (isIdle) return;
5187 drawmove(event);
5188 console.log('>>>>>>>>>>> draw move');
5189 isIdle = true;
5190 }
5191
5192 function touchstart(event) {
5193 drawstart(event.touches[0]);
5194 }
5195
5196 function touchmove(event) {
5197 drawmove(event.touches[0]);
5198 //event.preventDefault();
5199 }
5200
5201 function touchend(event) {
5202 drawend(event.changedTouches[0]);
5203 }
5204
5205 //canvas.addEventListener('touchstart', touchstart, false);
5206 //canvas.addEventListener('touchmove', touchmove, false);
5207 //canvas.addEventListener('touchend', touchend, false);
5208
5209 //canvas.addEventListener('mousedown', drawstart, false);
5210 //canvas.addEventListener('mousemove', drawmove, false);
5211 //canvas.addEventListener('mouseup', drawend, false);
5212
5213 }
5214
5219 var selected = graphcanvas.selected_nodes;
5220 var haveSelected = false;
5221 for (var s in selected) {
5222 haveSelected = true;
5223 break;
5224 }
5225 console.log('Center nodes:', selected, '. Have selected:', haveSelected);
5226 graphcanvas.centerOnGraph(haveSelected);
5227 }
5228
5233 LiteGraph.searchbox_extras = [];
5234 var nodeTypes = LiteGraph.registered_node_types;
5235 for (var typeName in nodeTypes) {
5236 if (typeName !== "graph/subgraph") {
5237 console.log('Removing node type:', LiteGraph.getNodeType(typeName));
5238 LiteGraph.unregisterNodeType(typeName);
5239 }
5240 }
5241 }
5242
5246 collapseNode(node, collapse) {
5247 if (node.constructor.collapsable === false) {
5248 return false;
5249 }
5250 if (node.flags.collapsed != collapse) {
5251 node.flags.collapsed = collapse;
5252 return true;
5253 }
5254 return false;
5255 }
5256
5261 var curGraph = graphcanvas.graph;
5262
5263 var selected_nodes = graphcanvas.selected_nodes;
5264 //console.log('Selected nodes:', selected_nodes);
5265 var modified = false;
5266 if (selected_nodes) {
5267 for (var i in selected_nodes) {
5268 var node = selected_nodes[i];
5269 //console.log('Collapse/Expand:', node.title, collapse);
5270 if (this.collapseNode(node, collapse))
5271 modified = true;
5272 }
5273 }
5274 if (!modified) {
5275 var nodes = curGraph._nodes;
5276 for (var i in nodes) {
5277 var node = nodes[i];
5278 if (this.collapseNode(node, collapse))
5279 modified = true;
5280 }
5281 }
5282
5283 if (modified) {
5284 graph._version++;
5285 graph.setDirtyCanvas(true, true);
5286 }
5287 }
5288
5293 graphcanvas.copyToClipboard();
5294 }
5295
5300 graphcanvas.pasteFromClipboard(true);
5301 }
5302
5307 var selected = graphcanvas.selected_nodes;
5308 if (selected.length == 0) {
5309 console.log('No nodes selected.');
5310 return;
5311 }
5312
5313 var subgraphsSelected = []
5314 for (var i in selected) {
5315 var node = selected[i];
5316 if (node.type == 'graph/subgraph') {
5317 subgraphsSelected.push(node);
5318 }
5319 }
5320 if (subgraphsSelected.length == 0) {
5321 console.log('No subgraphs selected.');
5322 return;
5323 }
5324
5325 // Select subgraph nodes
5326 var subGraph = subgraphsSelected[0];
5327 var subGraphNodes = subGraph.subgraph._nodes;
5328 for (var i in subGraphNodes) {
5329 var node = subGraphNodes[i];
5330 //console.log('Select subgraph node:', node.title);
5331 }
5332
5333 graphcanvas.openSubgraph(subGraph.subgraph);
5334 graphcanvas.selectNodes(subGraphNodes);
5335 // Copy the selected nodes to the clipboard
5336 graphcanvas.copyToClipboard();
5337
5338 // Paste the copied nodes into the graph
5339 graphcanvas.closeSubgraph();
5340 graphcanvas.pasteFromClipboard();
5341 }
5342
5348 // Disallow testing for now.
5349 if (graphcanvas.graph._is_subgraph) {
5350 this.debugOutput('Cannot create nest subgraphs.', 1);
5351 return;
5352 }
5353
5354 // Check for selected nodes
5355 var selected = graphcanvas.selected_nodes;
5356 if (selected.length == 0) {
5357 console.log('No nodes selected.');
5358 return;
5359 }
5360
5361 // Copy the selected nodes to the clipboard
5362 graphcanvas.copyToClipboard();
5363
5364 // Create a new graph/subgraph node
5365 var node = LiteGraph.createNode('graph/subgraph');
5366 graph.add(node);
5367 node.title = MxShadingGraphEditor.theEditor.handler.createValidName('group');
5368 // Open subgraph
5369 graphcanvas.openSubgraph(node.subgraph);
5370 // Paste the copied nodes into the subgraph
5371 graphcanvas.pasteFromClipboard();
5372
5373 node.subgraph.arrange(80);
5374 graphcanvas.ds.reset();
5375 graphcanvas.setDirty(true, true);
5376 }
5377
5382 // Get the node list display updater
5383 var nodeTypesListUpdater = this.ui.nodeTypesListUpdater;
5384 if (!nodeTypesListUpdater) {
5385 return;
5386 }
5387
5388 // Get the list of available node types
5389 var nodeTypes = LiteGraph.registered_node_types;
5390 nodeTypesListUpdater(nodeTypes);
5391 }
5392
5402 initialize(canvas, ui, monitor, materialFilename, readOnly = false) {
5403
5404 this.setUI(ui);
5405 if (monitor) {
5406 console.log('Set custom monitor:', monitor.getName());
5407 }
5408 this.monitor = monitor;
5409 this.initializeLiteGraph(canvas, readOnly);
5410 this.handler.initialize(MxShadingGraphEditor.theEditor, materialFilename);
5411 this.handler.setMonitor(this.monitor);
5412 }
5413}
var customlibs
var customDocLibs
var graphcanvas
Base class for graph handlers.
getColorSpaces()
Get the color spaces used by the handler.
setMonitor(monitor)
Set the monitor for the handler.
getDefaultValue(value, _type)
Get default value as a string for the given value and type.
setSourceColorSpace(colorSpace)
Set the source color space for the handler.
canImport(extension)
Return if the handler can import the given extension / format.
createValidName(name)
Create a valid name for the given name.
getImporter(extension='')
Find the first importer that can import the given extension / format.
getUnits()
Get the units used by the handler.
setTargetDistanceUnit(unit)
Set the target distance unit for the handler.
initialize(editor)
Initialize the handler for the given editor.
getSourceColorSpace()
Get the source color space for the handler.
getTargetDistanceUnit()
Get the target distance unit for the handler.
getExporter(extension='')
Find the first exporter that can export to the given extension / format.
getExtension()
Get the extension /format for the handler.
canExport(extension)
Return if the handler can export to the given extension / format.
constructor(id, extension)
setColorSpaces(colorSpaces)
Set the color spaces used by the handler.
setUnits(units)
Set the units used by the handler.
addConverter(converter)
Add a converter to the handler.
This class provides a monitoring interface for the graph editor.
getRenderer()
Get the renderer for the monitor.
debugMessage(text, path)
Output a debug message to the console.
onConnectOutput(slot, input_type, input, target_node, target_slot, node)
Callback for connection to output.
onNodeDeselected(node, parentGraph)
Callback for when a node is deselected in the graph.
getPath(node, parentGraph)
Get a '/' separated path.
setOnNodeRemoved(callback)
Set node removed callback.
setMonitoring(monitor)
Set the monitoring state of the monitor.
onNodeRemoved(node, parentGraph)
Callback for when a node is removed from the graph.
onNodeSelected(node, parentGraph)
Callback for when a node is selected in the graph.
setOnNodeDeselected(callback)
Set node deselected callback.
onConnectInput(target_slot, output_type, output, source, slot, node)
Callback for connection to output.
getMonitoring()
Get the monitoring state of the monitor.
onPropertyInfoChanged(nodeName, propertyName, propertyInfoName, newValue, previousValue, node)
Callback for when a property info changes on a node in the graph.
onNodeAdded(node, parentGraph)
Callback for when a node is added to the graph.
monitorGraph(theGraph, monitor)
Core monitoring of graph changes.
setOnNodeAdded(callback)
Set node added callback.
setRenderer(theRenderer)
Set the renderer for the monitor.
setOnNodeSelected(callback)
Set node selected callback.
setOnPropertyChanged(callback)
Set property changed callback.
onNodeRenamed(node, newName)
Callback for when a node is renamed in the graph.
setOnConnectionChange(callback)
Set connection change callback.
onPropertyChanged(nodeName, propertyName, newValue, previousValue, node)
Callback for when a property changes on a node in the graph.
getName()
Get the name of the monitor.
onDocumentChange(attribute, value, prevValue)
Callback for when a scene / document level change is made.
setOnNodeRenamed(callback)
Set node renamed callback.
getParentPath(node)
Get the parent path of a node.
onConnectionChange(node, parentGraph)
Callback for when a connection changes in the graph.
This class extends the MxGraphHandler class to provide MaterialX-specific functionality for handling ...
loadFromString(extension, fileContents, fileName, auto_arrange, rerender=false)
Load graph editor from a string.
saveGraphToDocument(graph, graphWriteOptions)
Saves the graph to a MaterialX document.
validateDocument(doc)
Validates the provided MaterialX document.
constructor(id, extension)
Constructor for the MxMaterialXHandler class.
buildConnections(editor, node, lg_node, explicitInputs, graph, parentGraph)
Builds the connections between MaterialX nodes.
loadLibraryDocument(editor, materialFilename)
Load the MaterialX document from library into the editor.
loadFromFile(extension, file, fileName, editor, auto_arrange)
Load graph editor from a file.
async loadFolderToDocument(folderPath)
findRenderableItemsInDoc(mdoc)
Find all renderable items in the MaterialX document.
loadFromZip(extension, file, fileName, editor, auto_arrange, rerender=false)
loadDefinitionsFromFile()
Load MaterialX document containing node definitions from a file.
isArray(_type)
Determines if the specified type is an array type.
initialize(editor, materialFilename)
Initialize the MaterialX handler for the given editor.
createLiteGraphDefinitions(doc, debug, addInputOutputs, definitionsList, libraryPrefix='mtlx', editor, icon='')
Creates LiteGraph node definitions based on the MaterialX document.
buildMetaData(colorSpace, unit, unitType, uiname, uimin, uimax, uifolder, _type)
Builds and returns metadata for a node based on the provided parameters.
loadMaterialXLibraries(stdlib)
Load MaterialX definition libraries.
writeGraphToDocument(mltxgraph, graph, graphWriteOptions)
Writes the graph to the specified MaterialX document.
buildGraphFromDoc(doc, editor, auto_arrange)
Builds the LiteGraph graph from the specified MaterialX document.
saveGraphToFile(extension, graph, graphWriteOptions)
Saves the graph to a file with the specified extension.
loadMaterialX()
Load in the MaterialX library.
saveGraphToString(extension, graph, graphWriteOptions)
Saves the graph to a string in the specified format.
createValidName(name, msg=null)
Create a valid MaterialX name within the context of the current graph.
getAllFilesRecursive(dir)
Recursively collects all file paths in a folder.
findRenderableItems(graph)
Find all MaterialX renderable items in a graph.
loadInputMetaData(node, input, property_info)
Set the meta-data for the specified input based on LiteGraph node property info.
This class is a wrapper around the LiteGraph library to provide a MaterialX node editor.
saveGraphToString(extension, graphWriteOptions)
Save the graph to a string with the specified extension.
displayNodeTypes()
Display the available node types in the UI if a UI updater is set.
createColorSpaceInput(colorSpaces, activeItem)
Create a color space input element.
collapseExpandNodes(collapse)
Collapse or expand selected nodes.
clearGraph()
Reset the graph editor.
rgbToHex(rgb)
Utility to convert a color from RGB to hex.
searchGraph(title)
Search the graph for the specified node.
initialize(canvas, ui, monitor, materialFilename, readOnly=false)
Initialize the editor.
clearNodeTypes()
Remove built in node types, except for subgraph.
findRenderableItems()
Find all renderable elements in the graph using the handler.
initializeLiteGraph(canvas, readOnly=false)
Initialize the LiteGraph graph editor for a given canvas element.
uriExists(uri)
Check if a URI exists.
createButtonWithImageAndText(imageSrc, text, id)
Create a DOM button with an image and text.
closeSubgraph()
Close the current subgraph open, if any.
saveSerialization()
Use built-in serialization to save the graph to file.
loadSerialization()
Use built-in serialization to load the graph from file.
constructor()
Default constructor.
debugOutput(text, severity, clear=null)
Output debug information to the console or the UI console logger.
copyToClipboard()
Copy selected nodes to the clipboard.
updateImagePreview(curImage)
Update the image preview in the property panel.
saveGraphToFile(extension, graphWriteOptions)
Save the graph to a file with the specified extension.
setTargetDistanceUnit(unit)
Set the global distance unit for the editor.
createUnitsInput(units, unittype, activeItem)
Create a units input element.
getTargetDistanceUnit()
Get the global distance unit for the editor.
selectNodes()
Select all nodes in the graph.
loadGraphFromString(extension, content, fileName, auto_arrange, rerender=false)
Load the graph from a string with the specified extension.
setSourceColorSpace(colorSpace)
Set global color space for the editor.
getSourceColorSpace()
Get the global color space for the editor.
resetView()
Reset the view of the graph.
pasteFromClipboard()
Paste nodes from the clipboard.
setDirty(w=null, h=null)
Notify the editor to update it's graph.
centerNode()
Center the graph on a selected node.
loadGraphFromFile(extension, auto_arrange, rerender=false)
Load the graph from a file with the specified extension.
collapseNode(node, collapse)
Collapse or expand a node.
createNodeGraph()
Create a new node subgraph from selected.
openSubgraph()
Open the subgraph for the selected node.
extractNodeGraph()
Extract the nodes in a subgraph to the main graph.
setUI(ui)
Set the UI callbacks for the editor.
openImageDialog(theNode, updateProp, wantURI)
Open a dialog to select a image file to load.
arrangeGraph(spacing=80)
Auto layout the nodes in the graph.
updatePropertyPanel(node)
This method is called when a node is selected in the graph.
loadDefinitionsFromFile(extension)
Load the graph from a file with the specified extension.
Utility class for unzipping ZIP which contain MaterialX files and dependent images .
static async unzipMaterialXData(zipData)
Unzips the given ZIP data and returns the MaterialX documents and textures.