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
373 {
374 this.monitoring = monitor;
375 }
376
383 {
384 return this.monitoring;
385 }
386
394 {
395 if (callback)
396 {
397 this.onConnectionChange = callback;
398 }
399 }
400
408 {
409 if (callback)
410 {
411 this.onNodeAdded = callback;
412 }
413 }
414
422 {
423 if (callback)
424 {
425 this.onNodeRemoved = callback;
426 }
427 }
428
436 {
437 if (callback)
438 {
439 this.onNodeRenamed = callback;
440 }
441 }
442
450 {
451 if (callback)
452 {
453 this.onNodeSelected = callback;
454 }
455 }
456
464 {
465 if (callback)
466 {
467 this.onNodeDeselected = callback;
468 }
469 }
470
478 {
479 if (callback)
480 {
481 this.onPropertyChanged = callback;
482 }
483 }
484
491 monitorGraph(theGraph, monitor)
492 {
493 // Update monitor state
494 this.monitoring = monitor;
495
496 if (!theGraph)
497 return;
498
499 theGraph.onConnectionChange = null;
500 theGraph.onNodeAdded = null;
501 theGraph.onNodeRemoved = null;
502
503 if (monitor) {
504
505 console.log('> Monitor graph: ', graph.title? graph.title : 'ROOT')
506
507 var that = this;
508
509 theGraph.onConnectionChange = function (node) {
510 let parentGraph = '';
511 var is_subgraph = node.graph._is_subgraph;
512 if (is_subgraph)
513 parentGraph = graphcanvas.graph._subgraph_node.title;
514 that.onConnectionChange(node, parentGraph);
515
516 var selected = graphcanvas.selected_nodes;
517 for (var s in selected) {
518 //console.log('update selected node:', selected[s].title);
519 MxShadingGraphEditor.theEditor.updatePropertyPanel(selected[s]);
520 break;
521 }
522 }
523
524 theGraph.onNodeAdded = function (node) {
525 let parentGraph = '';
526
527 if (node.type == 'graph/subgraph') {
528 // Use MaterialX naming for subgraphs
529
530 // Scan the subgraph for any nodes which are not in the node inputs list.
531 var node_subgraph = node.subgraph;
532 var node_graph = node.graph;
533 if (node_subgraph) {
534 //console.log('** Scan subgraph: ', node_subgraph)
535 for (var i in node_subgraph._nodes) {
536 let theNode = node_subgraph._nodes[i];
537 if (!node_graph.findNodeByTitle(theNode.title)) {
538 if (theNode.nodedef_node == 'input') {
539 node.addInput(theNode.title, theNode.nodedef_type);
540 //console.log('--> Promote input node to subgraph node.', theNode.title);
541 }
542 else if (theNode.nodedef_node == 'output') {
543 //console.log('--> Promote output node to subgraph node.', theNode.title);
544 node.addOutput(theNode.title, theNode.nodedef_type);
545 }
546 }
547 }
548 }
549 }
550
551 node.title = MxShadingGraphEditor.theEditor.handler.createValidName(node.title)
552 node.setSize(node.computeSize());
553 //console.log('-> Node Added:', node, '. Type:', node.type, '. Graph:', node.graph._2458graph);
554
555 // Expose node as an input or output on the subgraph
556 var is_subgraph = node.graph._is_subgraph;
557 if (is_subgraph) {
558 parentGraph = graphcanvas.graph._subgraph_node.title;
559
560 if (node.nodedef_node == 'input') {
561 //console.log('Adding input node to subgraph.', node.title, node.graph);
562 node.graph.addInput(node.title, node.nodedef_type);
563 }
564 else if (node.nodedef_node == 'output') {
565 //console.log('Adding output node to subgraph.');
566 node.graph.addOutput(node.title, node.nodedef_type);
567 }
568 }
569
570 if (node.type == 'graph/subgraph') {
571 that.monitorGraph(node.subgraph, monitor);
572 }
573
574 that.onNodeAdded(node, parentGraph);
575 }
576
577 //console.log('Monitor graph remove:', theGraph.title);
578 theGraph.onNodeRemoved = function (node) {
579
580 let parentGraph = '';
581 /* This is too late the graph reference has already been removed */
582 var is_subgraph = graphcanvas.graph._is_subgraph;
583 if (is_subgraph) {
584 parentGraph = graphcanvas.graph._subgraph_node.title;
585 if (node.nodedef_node == 'input') {
586 //console.log('Removing input node from subgraph.', parentGraph);
587 graphcanvas.graph.removeInput(node.title);
588 }
589 else if (node.nodedef_node == 'output') {
590 //console.log('Removing output node from subgraph.', parentGraph);
591 graphcanvas.graph.removeOutput(node.title);
592 }
593 }
594
595 that.onNodeRemoved(node, parentGraph);
596 }
597
598 for (var i in theGraph._nodes) {
599 var node = theGraph._nodes[i];
600 if (node.type == 'graph/subgraph') {
601 console.log('> Monitor subgraph:', node.title);
602 that.monitorGraph(node.subgraph, monitor);
603 node.onConnectInput = MxShadingGraphEditor.theEditor.onConnectInput;
604 }
605 }
606 }
607 }
608}
609
610
617{
618 constructor(id, extension) {
619 // Identifier
620 this.id = id;
621 // Extension of format that this handler is associated with
622 // Generally the same as the file extension (e.g. mtlx for MaterialX)
623 this.extension = extension;
624 // Editor
625 this.editor = null;
626
627 // Keep track of global color space and distance unit
628 this.DEFAULT_COLOR_SPACE = 'lin_rec709';
629 this.DEFAULT_DISTANCE_UNIT = 'meter';
630 this.sourceColorSpace = this.DEFAULT_COLOR_SPACE;
631 this.targetDistanceUnit = this.DEFAULT_DISTANCE_UNIT;
632 this.colorSpaces = [];
633 this.units = [];
634
635 // List of additional converters that can convert to the format
636 // that this handler can handle
637 this.converters = [];
638
639 // Graph monitoring instance
640 this.monitor = null;
641 }
642
649 addConverter(converter) {
650 // Add to front of list
651 this.converters.unshift(converter);
652 //this.converters.push(converter);
653 }
654
661 setMonitor(monitor)
662 {
663 this.monitor = monitor;
664 }
665
673 canExport(extension)
674 {
675 if (extension == 'mtlx')
676 {
677 return true;
678 }
679 if (this.getExporter(extension))
680 {
681 return true;
682 }
683 return false;
684 }
685
692 getExporter(extension='') {
693 for (let converter of this.converters) {
694 if (converter.exportType() == extension) {
695 return converter;
696 }
697 }
698 return null;
699 }
700
707 canImport(extension)
708 {
709 if (extension == 'mtlx')
710 {
711 return true;
712 }
713 if (this.getImporter(extension))
714 {
715 return true;
716 }
717 return false;
718 }
719
726 getImporter(extension='') {
727 for (let converter of this.converters) {
728 if (converter.importType() == extension) {
729 return converter;
730 }
731 }
732 return null;
733 }
734
741 setColorSpaces(colorSpaces) {
742 this.colorSpaces = colorSpaces;
743 }
744
751 return this.colorSpaces;
752 }
753
760 setUnits(units) {
761 this.units = units;
762 }
763
770 return this.units;
771 }
772
780 let newSpace = this.DEFAULT_COLOR_SPACE;
781 if (colorSpace && colorSpace.length > 0)
782 newSpace = colorSpace;
783
784 if (this.monitor)
785 {
786 this.monitor.onDocumentChange('colorspace', colorSpace, this.sourceColorSpace);
787 }
788 this.sourceColorSpace = newSpace;
789 }
790
798 let newUnit = this.DEFAULT_DISTANCE_UNIT;
799 if (unit && unit.length > 0)
800 newUnit = unit;
801
802 if (this.monitor)
803 {
804 this.monitor.onDocumentChange('distanceunit', newUnit, this.targetDistanceUnit);
805 }
806 this.targetDistanceUnit = newUnit;
807 //console.log('Handler SET target distance unit:', this.targetDistanceUnit);
808 }
809
816 //console.log('Handler GET source colorspace:', this.sourceColorSpace);
817 return this.sourceColorSpace;
818 }
819
826 //console.log('Handler GET source distance unit:', this.targetDistanceUnit);
827 return this.targetDistanceUnit;
828 }
829
836 return this.extension;
837 }
838
845 initialize(editor) {
846 this.editor = editor;
847 }
848
856 return name;
857 }
858
867 getDefaultValue(value, _type) {
868 if (_type === 'string' || _type === 'filename') {
869 value = "'" + value + "'";
870 }
871 else if (this.isArray(_type)) {
872 if (value.length == 0) {
873 if (_type === 'color3')
874 value = "[0.0, 0.0, 0.0]";
875 else if (_type === 'color4')
876 value = "[0.0, 0.0, 0.0, 0.0]";
877 else if (_type === 'vector2')
878 value = "[0.0, 0.0]";
879 else if (_type === 'vector3')
880 value = "[0.0, 0.0, 0.0]";
881 else if (_type === 'vector4')
882 value = "[0.0, 0.0, 0.0, 0.0]";
883 else if (_type === 'matrix33')
884 value = "[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]";
885 else if (_type === 'matrix44')
886 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]";
887 }
888 else {
889 value = "[" + value + "]";
890 }
891 }
892 else if (_type === 'integer') {
893 if (value.length == 0) {
894 value = 0;
895 }
896 }
897 else if (_type === 'float') {
898 if (value.length == 0) {
899 value = 0.0;
900 }
901 }
902 else if (_type === 'boolean') {
903 if (value)
904 value = 'true';
905 else
906 value = 'false';
907 }
908
909 if (value.length == 0) {
910 //console.log('No value for input:', _name, 'type:', _type, 'value:', value);=
911 value = "''";
912 }
913 return value;
914 }
915};
916
924
931 constructor(id, extension) {
932 super(id, extension);
933 }
934
942 return new Promise((resolve, reject) => {
943 MaterialX().then((ne_mtlx) => {
944 // Save the MaterialX instance to the global variable
945 ne_mx = ne_mtlx;
946 resolve();
947 }).catch((error) => {
948 reject(error);
949 });
950 });
951 }
952
959 loadLibraryDocument(editor, materialFilename) {
960
961 function loadInitialText(filePath, handler) {
962 try {
963 fetch(filePath)
964 .then(response => response.blob())
965 .then(blob => {
966 const reader = new FileReader();
967 reader.onload = function (e) {
968 console.log('Loaded document:', filePath);
969 editor.loadGraphFromString('mtlx', e.target.result, filePath, 80);
970 }
971 reader.readAsText(blob);
972 })
973 } catch (error) {
974 console.error('Error loading file %s:' % filePath, error);
975 }
976 }
977
978 loadInitialText(materialFilename, this);
979 }
980
991 initialize(editor, materialFilename) {
992 super.initialize(editor);
993
994 if (!ne_mx) {
995
996 this.loadMaterialX().then(() => {
997
998 // Additional logic after MaterialX is loaded
999 editor.debugOutput("Loaded MaterialX version:" + ne_mx.getVersionString(), 0, true);
1000 doc = ne_mx.createDocument();
1001
1002 var generator = new ne_mx.EsslShaderGenerator();
1003 var genContext = new ne_mx.GenContext(generator);
1004 stdlib = ne_mx.loadStandardLibraries(genContext);
1005 editor.debugOutput('Loaded standard libraries definitions:' + stdlib.getNodeDefs().length, 0, false);
1006
1007 // Find units, unittype pairs available. Keep in unique list.
1008 let units = new Map();
1009 for (let ud of stdlib.getUnitDefs()) {
1010 let unittype = ud.getAttribute('unittype')
1011 for (let unit of ud.getChildren()) {
1012 units.set(unit.getName(), unittype);
1013 }
1014 }
1015 // Sort units
1016 this.setUnits(Array.from(units).sort());
1017 console.log('> Setup real-world units: ', this.getUnits());
1018
1019 // Find the colorspaces available. Keep in unique list. Hack as there
1020 // is no way to find a list of colorspaces.
1021 let colorSpaces = new Set();
1022 let docNodeDefs = stdlib.getNodeDefs();
1023 for (let i = 0; i < docNodeDefs.length; i++) {
1024 let cmnode = docNodeDefs[i];
1025 if (cmnode.getNodeGroup() === 'colortransform') {
1026 let name = cmnode.getName();
1027 name = name.replace('ND_', '');
1028 let namesplit = name.split('_to_');
1029 let type = 'color3';
1030 if (namesplit[1].includes('color4')) {
1031 namesplit[1] = namesplit[1].replace('_color4', '');
1032 type = 'color4';
1033 } else {
1034 namesplit[1] = namesplit[1].replace('_color3', '');
1035 }
1036 colorSpaces.add(namesplit[0]);
1037 colorSpaces.add(namesplit[1]);
1038 }
1039 }
1040 // Sort the colorspaces
1041 this.setColorSpaces(Array.from(colorSpaces).sort())
1042 console.log('Set up colorspaces: ', this.getColorSpaces());
1043
1044 var definitionsList = [];
1045 var result = this.createLiteGraphDefinitions(stdlib, false, true, definitionsList, 'mtlx', MxShadingGraphEditor.theEditor);
1046 var definitionsDisplayUpdater = editor.ui.definitionsDisplayUpdater;
1047 if (definitionsDisplayUpdater) {
1048 definitionsDisplayUpdater(result);
1049 }
1050
1051 editor.clearNodeTypes();
1052 try {
1053 eval(result);
1054 } catch (e) {
1055 editor.debugOutput('Error evaluating source: ' + e, 2, false);
1056 }
1057
1058 var nodeTypes = LiteGraph.registered_node_types;
1059 var i = 0;
1060 for (var typeName in nodeTypes) {
1061 i++;
1062 }
1063 editor.debugOutput("Registered node types:" + definitionsList.length, 0, false);
1064
1065 editor.displayNodeTypes();
1066
1067 if (materialFilename.length > 0) {
1068 this.loadLibraryDocument(editor, materialFilename);
1069 }
1070
1071 editor.updatePropertyPanel(null);
1072
1073 }).catch((error) => {
1074 editor.debugOutput("Error on initialization:" + error, 2);
1075 });
1076 }
1077 }
1078
1086
1087 let graphWriteOptions = { writeCustomLibs : false, saveNodePositions: false, writeOutputs : true };
1088 let mdoc = this.saveGraphToDocument(graph, graphWriteOptions);
1089 if (!mdoc) {
1090 console.log('Failed to save graph to document');
1091 return;
1092 }
1093 return this.findRenderableItemsInDoc(mdoc);
1094 }
1095
1103
1104 const materialNodes = mdoc.getMaterialNodes();
1105 let shaderList = [];
1106 let renderableItems = [];
1107
1108 for (let i = 0; i < materialNodes.length; ++i) {
1109 let materialNode = materialNodes[i];
1110 if (materialNode) {
1111 //console.log('Scan material: ', materialNode.getNamePath());
1112 let shaderNodes = ne_mx.getShaderNodes(materialNode)
1113 if (shaderNodes.length > 0) {
1114 let shaderNodePath = shaderNodes[0].getNamePath()
1115 if (!shaderList.includes(shaderNodePath)) {
1116 //console.log('-- add shader: ', shaderNodePath);
1117 shaderList.push(shaderNodePath);
1118 renderableItems.push(shaderNodePath);
1119 }
1120 }
1121 }
1122 }
1123 const nodeGraphs = mdoc.getNodeGraphs();
1124 for (let i = 0; i < nodeGraphs.length; ++i) {
1125 let nodeGraph = nodeGraphs[i];
1126 if (nodeGraph) {
1127 if (nodeGraph.hasAttribute('nodedef') || nodeGraph.hasSourceUri()) {
1128 continue;
1129 }
1130 // Skip any nodegraph that is connected to something downstream
1131 if (nodeGraph.getDownstreamPorts().length > 0) {
1132 continue
1133 }
1134 let outputs = nodeGraph.getOutputs();
1135 for (let j = 0; j < outputs.length; ++j) {
1136 let output = outputs[j];
1137 {
1138 renderableItems.push(output.getNamePath());
1139 }
1140 }
1141 }
1142 }
1143 const outputs = mdoc.getOutputs();
1144 for (let i = 0; i < outputs.length; ++i) {
1145 let output = outputs[i];
1146 if (output) {
1147 renderableItems.push(output.getNamePath());
1148 }
1149 }
1150
1151 return renderableItems;
1152 }
1153
1169 buildMetaData(colorSpace, unit, unitType, uiname, uimin, uimax, uifolder, _type) {
1170 // Create a struct with the metadata names as key and value
1171 var metaData = {};
1172 metaData['colorspace'] = colorSpace;
1173 metaData['unit'] = unit;
1174 metaData['unittype'] = unitType;
1175 metaData['uiname'] = uiname;
1176 if (_type == 'vector2' || _type == 'vector3' || _type == 'vector4' || _type == 'matrix33' || _type == 'matrix44') {
1177 if (uimin) {
1178 uimin = uimin.split(',').map(Number);
1179 }
1180 if (uimax) {
1181 uimax = uimax.split(',').map(Number);
1182 }
1183 }
1184 metaData['uimin'] = uimin;
1185 metaData['uimax'] = uimax;
1186 metaData['uifolder'] = uifolder;
1187
1188 // Return struct in an array
1189 return metaData;
1190 }
1191
1206 createLiteGraphDefinitions(doc, debug, addInputOutputs, definitionsList, libraryPrefix = 'mtlx',
1207 editor, icon = '')
1208 {
1209 var definition_code = ""
1210
1211 console.log('Creating LiteGraph definitions from MaterialX document:', doc);
1212
1213 // Get the node definitions from the MaterialX document
1214 var nodeDefs = doc.getNodeDefs();
1215
1216 if (debug)
1217 definition_code += "console.log('Loading MaterialX Definitions...');\n";
1218
1219 definition_code += "// MaterialX LiteGraph Functional Definitions\n"
1220 definition_code += "//\n";
1221 definition_code += "// MaterialX Version: " + ne_mx.getVersionString() + "\n";
1222 const date = new Date();
1223 definition_code += "// Generated on: " + date.toString() + "\n";
1224 definition_code += "//\n";
1225
1226 var TMAP = {}
1227 TMAP['float'] = 'float';
1228 TMAP['color3'] = 'color3';
1229 TMAP['color4'] = 'color4';
1230 TMAP['vector2'] = 'vector2';
1231 TMAP['vector3'] = 'vector3';
1232 TMAP['vector4'] = 'vector4';
1233 TMAP['matrix33'] = 'matrix33';
1234 TMAP['matrix44'] = 'matrix44';
1235 TMAP['integer'] = 'integer';
1236 TMAP['string'] = 'string';
1237 TMAP['boolean'] = 'boolean';
1238 TMAP['filename'] = 'filename';
1239 TMAP['BSDF'] = 'BSDF';
1240 TMAP['EDF'] = 'EDF';
1241 TMAP['VDF'] = 'VDF';
1242 TMAP['surfaceshader'] = 'surfaceshader';
1243 TMAP['volumeshader'] = 'volumeshader';
1244 TMAP['displacementshader'] = 'displacementshader';
1245 TMAP['lightshader'] = 'lightshader';
1246 TMAP['material'] = 'material';
1247 TMAP['vector2array'] = 'vector2array';
1248
1249 var CMAP = {}
1250 CMAP['integer'] = "#A32";
1251 CMAP['float'] = "#161";
1252 CMAP['vector2'] = "#265";
1253 CMAP['vector3'] = "#465";
1254 CMAP['vector4'] = "#275";
1255 CMAP['color3'] = "#37A";
1256 CMAP['color4'] = "#69A";
1257 CMAP['matrix33'] = "#333";
1258 CMAP['matrix44'] = "#444";
1259 CMAP['string'] = "#395";
1260 CMAP['filename'] = "#888";
1261 CMAP['boolean'] = "#060";
1262
1263 var inputTypes = ['float', 'color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44', 'integer', 'string', 'boolean', 'filename', 'BSDF', 'EDF', 'VDF', 'surfaceshader', 'volumeshader', 'displacementshader', 'lightshader', 'material', 'vector2array'];
1264 var outputTypes = ['float', 'color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44', 'integer', 'string', 'boolean', 'filename', 'BSDF', 'EDF', 'VDF', 'surfaceshader', 'volumeshader', 'displacementshader', 'lightshader', 'material', 'vector2array'];
1265
1266 // TODO: Support tokens
1267 var supporTokens = false;
1268 if (supporTokens) {
1269 inputTypes.push('token');
1270 TMAP['token'] = 'string';
1271 }
1272
1273 const INPUT_ND = 'ND_input_';
1274 const OUTPUT_ND = 'ND_output_';
1275 const INPUT_NODE_STRING = 'input';
1276 const OUTPUT_NODE_STRING = 'output';
1277 const LIBRARY_ICON = '';
1278
1279 // Register inputs (which have no nodedef)
1280 if (addInputOutputs) {
1281 for (var _type of inputTypes) {
1282 var id = libraryPrefix + '/input/input_' + _type;
1283 var functionName = ne_mx.createValidName(id);
1284 var titleName = 'input_' + _type;
1285 definition_code += "\n/**\n";
1286 definition_code += " * @function "+ functionName + "\n";
1287 definition_code += " * @description Library: " + libraryPrefix + ". Category: input. Type: " + _type + "\n";
1288 definition_code += " * LiteGraph id: " + id + "\n";
1289 definition_code += " */\n";
1290 definition_code += "function " + functionName + "() {\n";
1291 {
1292 definition_code += " this.nodedef_icon = '" + LIBRARY_ICON + "';\n";
1293 definition_code += " this.nodedef_name = '" + INPUT_ND + _type + "';\n";
1294 definition_code += " this.nodedef_node = '" + INPUT_NODE_STRING + "';\n";
1295 definition_code += " this.nodedef_type = '" + _type + "';\n";
1296 definition_code += " this.nodedef_group = '" + INPUT_NODE_STRING + "';\n";
1297 if (_type == 'token')
1298 _type = 'string';
1299 definition_code += " this.addInput('in', '" + TMAP[_type] + "');\n";
1300 var value = this.getDefaultValue('', _type);
1301 var metaData = this.buildMetaData('', '', '', '', null, null, '', null);
1302 metaData = JSON.stringify(metaData);
1303
1304 // TODO: It's not possible to add a default colorspace since you
1305 // don't know what node type it will feed into. i.e. the specification
1306 // is underdefined at this time (1.39)
1307 if (_type == 'filename')
1308 {
1309 ; // Nothing for now.
1310 }
1311
1312 definition_code += " this.addProperty('in', " + value + ", '" + _type + "'," + metaData + ");\n";
1313 definition_code += " this.addOutput('out', '" + TMAP[_type] + "');\n";
1314
1315 definition_code += " this.title = '" + titleName + "';\n"
1316 var desc = '"MaterialX:' + id + '"';
1317 definition_code += " this.desc = " + desc + ";\n";
1318
1319 var onNodeCreated = "function() {\n";
1320 onNodeCreated += " //console.log('Node created:', this);\n";
1321 onNodeCreated += " }";
1322 definition_code += " this.onNodeCreated = " + onNodeCreated + "\n";
1323 var onRemoved = "function() {\n";
1324 onRemoved += " //console.log('Node removed:', this);\n";
1325 onRemoved += " }";
1326 definition_code += " this.onRemoved = " + onRemoved + "\n";
1327
1328 // Property changed callback
1329 let monitor = editor.monitor;
1330 var onPropertyChanged = "function(name, value, prev_value) {\n";
1331 if (monitor)
1332 {
1333 onPropertyChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);\n";
1334 }
1335 else
1336 {
1337 onPropertyChanged += " console.log('+ Internal property changed:', this.title, name, value, prev_value, this);\n";
1338 }
1339 onPropertyChanged += " }";
1340 definition_code += " this.onPropertyChanged = " + onPropertyChanged + "\n";
1341
1342 // Property info / attribute changed callback
1343 var onPropertyInfoChanged = "function(name, info, value, prev_value) {\n";
1344 if (monitor)
1345 {
1346 onPropertyInfoChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);\n";
1347 }
1348 else
1349 {
1350 onPropertyInfoChanged += " console.log('+ Internal property info changed:', this.title, name, info, value, prev_value, this);\n";
1351 }
1352 onPropertyInfoChanged += " }"
1353 definition_code += " this.onPropertyInfoChanged = " + onPropertyInfoChanged + "\n";
1354
1355 // Output connection callback
1356 var onConnectOutput = "function(slot, input_type, input, target_node, target_slot) {\n";
1357 if (monitor)
1358 {
1359 onConnectOutput += " MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);\n";
1360 }
1361 else
1362 {
1363 onConnectOutput += " console.log('+ Output connection changed:', this.title, slot, input_type, input, target_node, target_slot);\n";
1364 }
1365 onConnectOutput += " }"
1366 definition_code += " this.onConnectOutput = " + onConnectOutput + "\n";
1367
1368 // Input connection callback
1369 var onConnectInput = "function(target_slot, output_type, output, source, slot) {\n";
1370 if (monitor)
1371 {
1372 onConnectInput += " MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);\n";
1373 }
1374 else
1375 {
1376 onConnectInput += " console.log('+ Input connection changed:', this.title, target_slot, output_type, output, source, slot);\n";
1377 }
1378 onConnectInput += " }"
1379 definition_code += " this.onConnectInput = " + onConnectInput + "\n";
1380
1381 definition_code += " this.color = '#004C94';\n";
1382 definition_code += " this.bgcolor = '#000';\n";
1383 if (_type in CMAP) {
1384 definition_code += " this.boxcolor = '" + CMAP[_type] + "';\n";
1385 }
1386 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1387
1388 definition_code += " this.onExecute = function() {\n";
1389 definition_code += " console.log('Executing node: ', this);\n";
1390 definition_code += " }\n";
1391 }
1392 definition_code += "}\n"
1393 definition_code += "LiteGraph.registerNodeType('" + id + "', " + functionName + ");\n";
1394 }
1395
1396 // Register outputs (which have no nodedef)
1397 for (var _type of outputTypes) {
1398 var id = libraryPrefix + '/output/output_' + _type;
1399 var functionName = ne_mx.createValidName(id);
1400 var titleName = 'output_' + _type;
1401
1402 definition_code += "\n/**\n";
1403 definition_code += " * @function "+ functionName + "\n";
1404 definition_code += " * @description Library: " + libraryPrefix + ". Category: output. Type: " + _type + "\n";
1405 definition_code += " * LiteGraph id: " + id + "\n";
1406 definition_code += " */\n";
1407
1408 definition_code += "function " + functionName + "() {\n";
1409 {
1410 definition_code += " this.title = '" + titleName + "';\n"
1411 var desc = '"MaterialX Node :' + id + '"';
1412 definition_code += " this.desc = " + desc + ";\n";
1413
1414 definition_code += " this.nodedef_icon = '" + LIBRARY_ICON + "';\n";
1415 definition_code += " this.nodedef_name = '" + OUTPUT_ND + + _type + "';\n";
1416 definition_code += " this.nodedef_node = '" + OUTPUT_NODE_STRING + "';\n";
1417 definition_code += " this.nodedef_type = '" + _type + "';\n";
1418 definition_code += " this.nodedef_group = '" + OUTPUT_NODE_STRING + "';\n";
1419 definition_code += " this.addInput('in', '" + TMAP[_type] + "');\n";
1420 var value = this.getDefaultValue('', _type);
1421 definition_code += " this.addProperty('in', " + value + ", '" + _type + "');\n";
1422 definition_code += " this.addOutput('out', '" + TMAP[_type] + "');\n";
1423
1424 var onNodeCreated = "function() {\n";
1425 onNodeCreated += " //console.log('Node created:', this);\n";
1426 onNodeCreated += " }";
1427 definition_code += " this.onNodeCreated = " + onNodeCreated + "\n";
1428 var onRemoved = "function() {\n";
1429 onRemoved += " //console.log('Node removed:', this);\n";
1430 onRemoved += " }";
1431 definition_code += " this.onRemoved = " + onRemoved + "\n";
1432
1433 let monitor = editor.monitor;
1434 var onPropertyChanged = "function(name, value, prev_value) {\n";
1435 if (monitor)
1436 {
1437 onPropertyChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);\n";
1438 }
1439 else
1440 {
1441 onPropertyChanged += " console.log('+ Internal property changed:', this.title, name, value, prev_value, this);\n";
1442 }
1443 onPropertyChanged += " }";
1444 definition_code += " this.onPropertyChanged = " + onPropertyChanged + "\n";
1445
1446 var onPropertyInfoChanged = "function(name, info, value, prev_value) {\n";
1447 if (monitor)
1448 {
1449 onPropertyInfoChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);\n";
1450 }
1451 else
1452 {
1453 onPropertyInfoChanged += " console.log('+ Internal property info changed:', this.title, name, info, value, prev_value, this);\n";
1454 }
1455 onPropertyInfoChanged += " }"
1456 definition_code += " this.onPropertyInfoChanged = " + onPropertyInfoChanged + "\n";
1457
1458
1459 // Output connection callback
1460 var onConnectOutput = "function(slot, input_type, input, target_node, target_slot) {\n";
1461 if (monitor)
1462 {
1463 onConnectOutput += " MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);\n";
1464 }
1465 else
1466 {
1467 onConnectOutput += " console.log('+ Output connection changed:', this.title, slot, input_type, input, target_node, target_slot);\n";
1468 }
1469 onConnectOutput += " }"
1470 definition_code += " this.onConnectOutput = " + onConnectOutput + "\n";
1471
1472 // Input connection callback
1473 var onConnectInput = "function(target_slot, output_type, output, source, slot) {\n";
1474 if (monitor)
1475 {
1476 onConnectInput += " MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);\n";
1477 }
1478 else
1479 {
1480 onConnectInput += " console.log('+ Input connection changed:', this.title, target_slot, output_type, output, source, slot);\n";
1481 }
1482 onConnectInput += " }"
1483 definition_code += " this.onConnectInput = " + onConnectInput + "\n";
1484
1485 definition_code += " this.color = '#004C94';\n";
1486 definition_code += " this.bgcolor = '#000';\n";
1487 if (_type in CMAP) {
1488 definition_code += " this.boxcolor = '" + CMAP[_type] + "';\n";
1489 }
1490 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1491
1492 definition_code += " this.onExecute = function() {\n";
1493 definition_code += " console.log('Executing node:', this);\n";
1494 definition_code += " }\n";
1495 }
1496 definition_code += "}\n"
1497 definition_code += "LiteGraph.registerNodeType('" + id + "', " + functionName + ");\n";
1498 definitionsList.push(id);
1499 }
1500 }
1501
1502 // Iterate over all node definitions
1503 for (var nodeDef of nodeDefs) {
1504
1505 var nodeDefName = nodeDef.getName();
1506 var id = libraryPrefix + '/' + nodeDef.getNodeGroup() + '/' + nodeDefName;
1507 id = id.replace('ND_', '');
1508 var functionName = ne_mx.createValidName(id);
1509 var nodeType = nodeDef.getType();
1510 var titleName = nodeDef.getNodeString() + "_" + nodeType;
1511 var swatchLocation = 'https://kwokcb.github.io/MaterialX_Learn/resources/mtlx/nodedef_materials/';
1512 var outputs = nodeDef.getActiveOutputs();
1513 var outputName = outputs[0].getName(); // TODO: Handle swatch for multiple outputs
1514 var swatchId = swatchLocation + 'material_' + nodeDefName + '_' + outputName + '_genglsl.png';
1515 swatchId = swatchId.replace('ND_', '');
1516 if (debug)
1517 console.log('\n--- Registering node type:', id, '----');
1518
1519
1520 definition_code += "\n/**\n";
1521 definition_code += " * @function "+ functionName + "\n";
1522 definition_code += " * @description Library: " + libraryPrefix + ". Category: " + nodeString + ". Type: " + nodeType + "\n";
1523 definition_code += " * LiteGraph id: " + id + "\n";
1524 definition_code += " */\n";
1525
1526 definition_code += "function " + functionName + "() {\n";
1527 {
1528 var nodeGroup = nodeDef.getNodeGroup();
1529 var nodeString = nodeDef.getNodeString();
1530 var theIcon = icon;
1531 if (theIcon.length == 0) {
1532 for (var key in editor.ui.icon_map) {
1533 if (nodeString.toLowerCase().startsWith(key.toLowerCase())) {
1534 if (key in editor.ui.icon_map)
1535 theIcon = editor.ui.icon_map[key];
1536 //console.log('set icon:', theIcon, 'for:', key, nodeString);
1537 break;
1538 }
1539 else if (nodeGroup.toLowerCase().startsWith(key.toLowerCase())) {
1540 if (key in editor.ui.icon_map)
1541 theIcon = editor.ui.icon_map[key];
1542 //console.log('set icon:', theIcon, 'for:', key, nodeGroup);
1543 break;
1544 }
1545 }
1546 }
1547
1548 definition_code += " this.nodedef_icon = '" + theIcon + "';\n";
1549 definition_code += " this.nodedef_name = '" + nodeDefName + "';\n";
1550 definition_code += " this.nodedef_type = '" + nodeType + "';\n";
1551 definition_code += " this.nodedef_node = '" + nodeString + "';\n";
1552 definition_code += " this.nodedef_href = 'https://kwokcb.github.io/MaterialX_Learn/documents/definitions/" + nodeString + ".html';\n";
1553 definition_code += " this.nodedef_swatch = '" + swatchId + "';\n";
1554 definition_code += " this.nodedef_group = '" + nodeGroup + "';\n";
1555
1556 for (var input of nodeDef.getActiveInputs()) {
1557 var _name = input.getName();
1558 var _type = input.getType();
1559 if (_type in TMAP)
1560 _type = TMAP[_type];
1561 else
1562 console.log('Unmappable type:', _type)
1563 definition_code += " this.addInput('" + _name + "','" + _type + "');\n";
1564
1565 let value = input.getValueString();
1566 value = this.getDefaultValue(value, _type);
1567 let uiname = input.getAttribute('uiname');
1568
1569 let uimin = input.getAttribute('uimin');
1570 if (uimin.length == 0) {
1571 uimin = null;
1572 }
1573 let uimax = input.getAttribute('uimax');
1574 if (uimax.length == 0) {
1575 uimax = null;
1576 }
1577 let uisoftmin = input.getAttribute('uisoftmin');
1578 if (uisoftmin.length > 0) {
1579 uimin = uisoftmin;
1580 }
1581 let uisoftmax = input.getAttribute('uisoftmax');
1582 if (uisoftmax.length > 0) {
1583 uimax = uisoftmax;
1584 }
1585 var uifolder = input.getAttribute('uifolder');
1586 var metaData = this.buildMetaData('', '', '', uiname, uimin, uimax, uifolder, _type);
1587
1588 // Add colorspace on nodedefs
1589 let colorspace = input.getAttribute('colorspace');
1590 let nodeDefType = nodeDef.getType();
1591 if (_type == 'filename' && (nodeDefType == 'color3' || nodeDefType == 'color4'))
1592 {
1593 if (colorspace.length == 0)
1594 {
1595 colorspace = 'none';
1596 }
1597 }
1598 if (colorspace.length > 0)
1599 metaData['colorspace'] = colorspace;
1600
1601 // TODO: Add units, unitype on nodedefs. There is no
1602 // default unittype or units.
1603 let unitAttributes = ['unit', 'unittype'];
1604 for (let unitAttribute of unitAttributes)
1605 {
1606 let value = input.getAttribute(unitAttribute);
1607 if (value.length > 0)
1608 {
1609 metaData[unitAttribute] = value;
1610 }
1611 }
1612
1613 // Add in defaultgeomprop to denote geometric inputs.
1614 let defaultgeomprop = input.getAttribute('defaultgeomprop')
1615 metaData['defaultgeomprop'] = defaultgeomprop;
1616
1617 // Add enumerations
1618 let enums = input.getAttribute('enum');
1619 if (enums.length > 0)
1620 {
1621 metaData['enum'] = enums.split(',');
1622 metaData['enum'].map(function(x) { return x.trim(); });
1623 }
1624 let enumvalues = input.getAttribute('enumvalues');
1625 if (enumvalues.length > 0)
1626 {
1627 metaData['enumvalues'] = enumvalues.split(',');
1628 metaData['enumvalues'].map(function(x) { return x.trim(); });
1629 }
1630
1631 metaData = JSON.stringify(metaData);
1632 definition_code += " this.addProperty('" + _name + "', " + value + ", '" + _type + "'," + metaData + ");\n";
1633 }
1634 for (var output of nodeDef.getActiveOutputs()) {
1635 var _name = output.getName();
1636 var _type = output.getType();
1637 if (_type in TMAP)
1638 _type = TMAP[_type];
1639 else
1640 console.log('Unmappable type:', _type)
1641 //if(_type && _type.constructor === String)
1642 // _type = '"'+_type+'"';
1643 definition_code += " this.addOutput('" + _name + "','" + _type + "');\n";
1644 }
1645
1646 definition_code += " this.title = '" + titleName + "';\n"
1647 var desc = '"MaterialX:' + id + '"';
1648 definition_code += " this.desc = " + desc + ";\n";
1649
1650 //definition_code += " /**\n";
1651 //definition_code += " * @function " + "onNodeCreated" + "\n";
1652 //definition_code += " */";
1653
1654 var onNodeCreated = "function() {\n";
1655 onNodeCreated += " //console.log('Node created:', this);\n";
1656 onNodeCreated += "}";
1657 definition_code += " this.onNodeCreated = " + onNodeCreated + "\n";
1658 var onRemoved = "function() {\n";
1659 onRemoved += " //console.log('Node removed:', this);\n";
1660 onRemoved += " }";
1661 definition_code += " this.onRemoved = " + onRemoved + "\n";
1662
1663 let monitor = editor.monitor;
1664 var onPropertyChanged = "function(name, value, prev_value) {\n";
1665 if (monitor)
1666 {
1667 onPropertyChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);\n";
1668 }
1669 else
1670 {
1671 onPropertyChanged += " console.log('+ Internal property changed:', this.title, name, value, prev_value, this);\n";
1672 }
1673 onPropertyChanged += " }";
1674 definition_code += " this.onPropertyChanged = " + onPropertyChanged + "\n";
1675
1676 var onPropertyInfoChanged = "function(name, info, value, prev_value) {\n";
1677 if (monitor)
1678 {
1679 onPropertyInfoChanged += " MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);\n";
1680 }
1681 else
1682 {
1683 onPropertyInfoChanged += " console.log('+ Internal property info changed:', this.title, name, info, value, prev_value, this);\n";
1684 }
1685 onPropertyInfoChanged += " }"
1686 definition_code += " this.onPropertyInfoChanged = " + onPropertyInfoChanged + "\n";
1687
1688 // Output connection callback
1689 var onConnectOutput = "function(slot, input_type, input, target_node, target_slot) {\n";
1690 if (monitor)
1691 {
1692 onConnectOutput += " MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);\n";
1693 }
1694 else
1695 {
1696 onConnectOutput += " console.log('+ Output connection changed:', this.title, slot, input_type, input, target_node, target_slot);\n";
1697 }
1698 onConnectOutput += " }"
1699 definition_code += " this.onConnectOutput = " + onConnectOutput + "\n";
1700
1701 // Input connection callback
1702 var onConnectInput = "function(target_slot, output_type, output, source, slot) {\n";
1703 if (monitor)
1704 {
1705 onConnectInput += " MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);\n";
1706 }
1707 else
1708 {
1709 onConnectInput += " console.log('+ Input connection changed:', this.title, target_slot, output_type, output, source, slot);\n";
1710 }
1711 onConnectInput += " }"
1712 definition_code += " this.onConnectInput = " + onConnectInput + "\n";
1713
1714 // Set the background color to slate grey
1715 definition_code += " this.bgcolor = '#111';\n";
1716 //console.log('Node group:', nodeGroup, nodeDefName);
1717 if (nodeGroup == 'conditional') {
1718 //console.log('Cond Node group:', nodeGroup)
1719 definition_code += " this.color = '#532200';\n";
1720 definition_code += " this.title_text_color = '#000';\n";
1721 definition_code += " this.shape = LiteGraph.CARD_SHAPE;\n";
1722 }
1723
1724 else if (nodeString != 'convert' &&
1725 (nodeGroup == 'shader' || nodeType == 'surfaceshader' || nodeType == 'volumshader' || nodeType == 'displacementshader')) {
1726 definition_code += " this.color = '#232';\n";
1727 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1728 }
1729 else if (nodeGroup == 'material') {
1730 definition_code += " this.color = '#151';\n";
1731 definition_code += " this.shape = LiteGraph.BOX_SHAPE;\n";
1732 }
1733 else {
1734 definition_code += " this.color = '#222';\n";
1735 definition_code += " this.shape = LiteGraph.ROUND_SHAPE;\n";
1736 }
1737 if (nodeType in CMAP) {
1738 definition_code += " this.boxcolor = '" + CMAP[nodeType] + "';\n";
1739 }
1740 }
1741 definition_code += "}\n"
1742
1743 // Register the node type
1744 definition_code += functionName + ".nodedef_name = '" + nodeDefName + "';\n";
1745 definition_code += functionName + ".nodedef_node = '" + nodeString + "';\n";
1746 definition_code += functionName + ".nodedef_href = 'https://kwokcb.github.io/MaterialX_Learn/documents/definitions/" + nodeString + ".html';\n";
1747
1748 definition_code += "LiteGraph.registerNodeType(" + "'" + id + "'," + functionName + ");\n";
1749 definitionsList.push(id);
1750 if (debug)
1751 definition_code += "console.log('Registered node type:', '" + id + "');\n";
1752 }
1753
1754 //definition_code += "}\n";
1755 return definition_code;
1756 }
1757
1765 {
1766 if (!doc || !stdlib)
1767 {
1768 return true;
1769 }
1770
1771 // Need to create a dummy "validation" doc
1772 let validationDocument = ne_mx.createDocument();
1773 validationDocument.copyContentFrom(doc);
1774 validationDocument.importLibrary(stdlib);
1775
1776 var errors = {};
1777 var valid = validationDocument.validate(errors);
1778 if (!valid) {
1779 this.editor.debugOutput('Failed to validate document:\n' + errors.message, 2);
1780 }
1781 }
1782
1790 saveGraphToDocument(graph, graphWriteOptions) {
1791
1792 if (!ne_mx) {
1793 this.editor.debugOutput("MaterialX is not initialized", 2);
1794 return;
1795 }
1796
1797 let writeCustomLibs = graphWriteOptions.writeCustomLibs;
1798 if (writeCustomLibs == undefined)
1799 {
1800 console.error('Graph output option: writeCustomLibs is undefined.')
1801 writeCustomLibs = true;
1802 }
1803
1804 var outputDoc = ne_mx.createDocument();
1805
1806 if (!stdlib) {
1807 var generator = new ne_mx.EsslShaderGenerator();
1808 var genContext = new ne_mx.GenContext(generator);
1809 stdlib = ne_mx.loadStandardLibraries(genContext);
1810 }
1811
1812 // Handle top level
1813 this.writeGraphToDocument(outputDoc, graph, graphWriteOptions);
1814
1815 if (writeCustomLibs) {
1816 console.log('Write custom libraries:', customlibs.length);
1817 for (var customlib of customlibs) {
1818 outputDoc.importLibrary(customlib[1]);
1819 }
1820 console.log('Write document custom definitions:', customDocLibs.length);
1821 for (var customDocLib of customDocLibs) {
1822 outputDoc.importLibrary(customDocLib[1]);
1823 }
1824 }
1825
1826 // TODO: Add in other globals
1827 outputDoc.setColorSpace(this.getSourceColorSpace());
1828 outputDoc.removeAttribute('fileprefix');
1829
1830 this.validateDocument(outputDoc);
1831
1832 return outputDoc;
1833 }
1834
1843 saveGraphToString(extension, graph, graphWriteOptions) {
1844
1845 if (!ne_mx) {
1846 this.editor.debugOutput("MaterialX is not initialized", 2);
1847 return ['', 'MaterialX is not initialized'];
1848 }
1849
1850 var outputDoc = this.saveGraphToDocument(graph, graphWriteOptions);
1851 if (!outputDoc) {
1852 this.editor.debugOutput("Failed to save graph to document", 2);
1853 return ['', 'Failed to save graph to document'];
1854 }
1855
1856 if (extension == 'mtlx')
1857 {
1858 const writeOptions = new ne_mx.XmlWriteOptions();
1859 writeOptions.writeXIncludeEnable = false;
1860 var data = '';
1861 try {
1862 data = ne_mx.writeToXmlString(outputDoc, writeOptions);
1863 } catch (e) {
1864 this.editor.debugOutput("Failed to write graph:" + e, 2);
1865 }
1866 return [data, ''];
1867 }
1868
1869 // Look for a registered exporter
1870 else
1871 {
1872 let exporter = this.getExporter(extension);
1873 if (!exporter) {
1874 this.editor.debugOutput('Failed to find ' + extension + ' exporter', 2);
1875 }
1876 else {
1877 let exportDoc = ne_mx.createDocument();
1878 exportDoc.copyContentFrom(outputDoc);
1879 exportDoc.importLibrary(stdlib);
1880
1881 let result = exporter.export(ne_mx, exportDoc);
1882 return result;
1883 }
1884 }
1885 return ['', 'Failed to export graph to ' + extension];
1886 }
1887
1896 saveGraphToFile(extension, graph, graphWriteOptions)
1897 {
1898 var data = this.saveGraphToString(extension, graph, graphWriteOptions);
1899 if (!data[0]) {
1900 return;
1901 }
1902
1903 var blob = new Blob([data[0]], { type: "text/plain" });
1904 var url = URL.createObjectURL(blob);
1905 var a = document.createElement("a");
1906 a.href = url;
1907 a.download = "output_graph.mtlx";
1908 a.click();
1909 }
1910
1919 writeGraphToDocument(mltxgraph, graph, graphWriteOptions) {
1920
1921 var debug = false;
1922
1923 if (debug)
1924 console.log('***** START Scan Graph:', graph.title);
1925 for (var node of graph._nodes) {
1926 if (node.type == 'graph/subgraph') {
1927 var subgraph = node.subgraph;
1928 //var subgraphNode = mltxgraph.addNodeGraph(node.title);
1929 var subgraphNode = mltxgraph.addChildOfCategory('nodegraph', node.title);
1930 if (debug)
1931 console.log('---->>> Scan NodeGraph:', node.title);
1932 this.writeGraphToDocument(subgraphNode, subgraph, graphWriteOptions);
1933 continue;
1934 }
1935
1936 if (debug)
1937 console.log('---->>> Scan Node:', node.title);
1938
1939 var nodeDefName = node.nodedef_name;
1940 /* if (!nodeDefName)
1941 {
1942 this.editor.debugOutput('Failed to find nodeDef for:' + node.title, 1);
1943 continue;
1944 } */
1945
1946 //var nodeTypes = LiteGraph.registered_node_types;
1947 //var nodeType = nodeTypes[node.type];
1948 var nodedefName = node.nodedef_name;
1949 var nodedef = null;
1950 var nodeElement = null;
1951 //if (nodeType) {
1952 // nodedefName = nodeType.nodedef_name;
1953 // nodedef = stdlib.getNodeDef(nodedefName);
1954 //}
1955
1956 //if (nodedef) {
1957 // nodeElement = mltxgraph.addNodeInstance(nodedef, name)
1958 // nodeElement.setName(node.title);
1959 //}
1960 //else
1961 {
1962 if (nodedefName) {
1963 nodeElement = mltxgraph.addChildOfCategory(node.nodedef_node, node.nodedef_type);
1964 nodeElement.setType(node.nodedef_type);
1965
1966 if (graphWriteOptions.saveNodePositions) {
1967 // TODO: Get properly remapping for xpos, ypos.
1968 nodeElement.setAttribute('xpos', JSON.stringify(node.pos[0]));
1969 nodeElement.setAttribute('ypos', JSON.stringify(node.pos[1]));
1970 }
1971 if (debug)
1972 console.log('** Create node:', nodeElement.getNamePath(), nodeElement.getType());
1973 nodeElement.setName(node.title);
1974 }
1975 }
1976
1977 if (nodeElement) {
1978 if (debug)
1979 console.log('-> Write Node:', graph.title + '/' + node.title, ' --> ', nodeElement.getNamePath());
1980 }
1981 else {
1982 console.log('Skip writing :', node.title);
1983 //this.editor.debugOutput('No nodedef for:' + node.title + 'Nodetype: ' + node.type, 0);
1984 continue;
1985 }
1986
1987 var properties = node.properties;
1988
1989 var node_inputs = node.inputs;
1990 var isInputNode = false;
1991 var isOutputNode = false;
1992 if (nodeElement.getCategory() == 'input') {
1993 isInputNode = true;
1994 node_inputs = [node];
1995 }
1996 else if (nodeElement.getCategory() == 'output') {
1997 isOutputNode = true;
1998 node_inputs = [node];
1999 }
2000
2001 // Add all outputs if the type is multioutput, or user option set
2002 if (!isInputNode && !isOutputNode)
2003 {
2004 if (node.nodedef_type == "multioutput")
2005 {
2006 console.log('Write outputs for:', node, '. type: ', node.nodedef_type);
2007 for (var output of node.outputs) {
2008 var outputName = output.name;
2009 var outputType = output.type;
2010 var outputElement = nodeElement.addOutput(outputName, outputType);
2011 if (debug) {
2012 console.log('> Read: node.nodedef_type: ', node.nodedef_type);
2013 console.log('> Write: output:', outputElement.getNamePath(), outputElement.getType());
2014 }
2015 }
2016 }
2017 }
2018
2019 // Add inputs
2020 if (node_inputs) {
2021
2022 var inputs = node_inputs;
2023 for (var i in inputs) {
2024 let input = inputs[i];
2025 if (debug)
2026 console.log('---- Write port:', input);
2027
2028 let inputName = input.name;
2029 let inputType = input.type;
2030 if (nodeElement.getCategory() == 'input' ||
2031 nodeElement.getCategory() == 'output') {
2032 inputName = 'in';
2033 inputType = node.nodedef_type;
2034 }
2035
2036 //var inputType = input.type;
2037 var inputElement = null;
2038 var nodeToCheck = node;
2039 var inputNode = null;
2040 var inputLink = null;
2041 if (isInputNode && node.graph._subgraph_node) {
2042 nodeToCheck = node.graph._subgraph_node;
2043 for (var i = 0; i < nodeToCheck.inputs.length; i++) {
2044 var nci = nodeToCheck.inputs[i];
2045 if (nci.name == node.title) {
2046 inputNode = nodeToCheck.getInputNode(i);
2047 inputLink = nodeToCheck.getInputLink(i);
2048 //console.log('--- Found parent input:', nci.name, 'inputNode:', inputNode, 'inputLink:', inputLink);
2049 break;
2050 }
2051 }
2052 }
2053 else {
2054 inputNode = node.getInputNode(i);
2055 inputLink = node.getInputLink(i);
2056 }
2057 var inputLinkOutput = '';
2058 var numInputOutputs = 0;
2059 if (inputLink) {
2060 numInputOutputs = inputNode.outputs.length;
2061 inputLinkOutput = inputNode.outputs[inputLink.origin_slot];
2062 }
2063 if (inputNode) {
2064 //console.log('inputNode', inputNode, 'inputLink:', inputLink, '. --- upsteream Output:', inputLinkOutput);
2065 if (nodeElement.getCategory() != 'input' &&
2066 nodeElement.getCategory() != 'output') {
2067 inputElement = nodeElement.getInput(inputName);
2068 //console.log('Call add input on elem', nodeElement, inputName);
2069 inputElement = nodeElement.addInput(inputName, inputType);
2070 }
2071 else {
2072 inputElement = nodeElement;
2073 }
2074
2075 if (debug) {
2076 console.log('Write connection');
2077 console.log(' - TO:', inputElement.getName() + "." + inputName);
2078 console.log(' - FROM link:', inputNode.id + "." + inputLinkOutput.name);
2079 }
2080 if (inputNode.type == 'graph/subgraph') {
2081 inputElement.setNodeGraphString(inputNode.title);
2082 // Set output string if there was an output link.
2083 if (numInputOutputs > 1 && inputLinkOutput) {
2084 inputElement.setOutputString(inputLinkOutput.name);
2085 }
2086 }
2087 else {
2088 //var upstream_nodeType = nodeTypes[inputNode.type];
2089 //if (upstream_nodeType)
2090 //console.log('Write connection: ', inputNode.title)
2091 {
2092 if (inputNode.nodedef_node == 'input') {
2093 inputElement.setInterfaceName(inputNode.title);
2094 }
2095 else {
2096 inputElement.setNodeName(inputNode.title);
2097 // Need to check that upstream has > 1 output.
2098 // TODO: Log issue that this is annoying to disallow an explicit output in validation.
2099 if (numInputOutputs > 1 && inputNode.nodedef_node != 'output') {
2100 // Set output string if there was an output link.
2101 if (inputLinkOutput) {
2102 inputElement.setOutputString(inputLinkOutput.name);
2103 }
2104 }
2105 }
2106 }
2107 }
2108 }
2109 else {
2110
2111 var inputValue = node.properties[inputName];
2112 if (inputValue == null) {
2113 console.log('Cannot find property value for input:', inputName);
2114 }
2115 else {
2116
2117
2118 var origValue = inputValue;
2119 //var inputType = propInfo.type;
2120 if (inputType in ['float', 'integer', 'vector2', 'vector3', 'vector4',
2121 'matrix33', 'matrix44', 'color3', 'color4']) {
2122 inputValue = '"' + parseFloat(inputValue) + '"';
2123 }
2124 else if (inputType === 'boolean') {
2125 if (inputValue === 'true')
2126 inputValue = 'true';
2127 else
2128 inputValue = 'false';
2129 }
2130 else {
2131 inputValue = inputValue.toString();
2132 }
2133 //console.log('Write input:', inputElement, node, inputName, origValue, inputValue, inputType);
2134
2135 if (nodeElement.getCategory() != 'input' &&
2136 nodeElement.getCategory() != 'output') {
2137 inputElement = nodeElement.getInput(inputName);
2138 if (!inputElement)
2139 inputElement = nodeElement.addInput(inputName, inputType);
2140 else {
2141 // TODO: LiteGraph seems that copy+paste adds same input > once.
2142 console.log('Error> Trying add input more than once:', inputName, ' to node: ', nodeElement.getNamePath());
2143 }
2144 }
2145 else {
2146 inputElement = nodeElement;
2147 }
2148
2149 try {
2150 inputElement.setValueString(inputValue, inputType);
2151 }
2152 catch (e) {
2153 console.warn("Set value error: ", e);
2154 }
2155 }
2156 }
2157
2158 if (inputElement) {
2159 var propInfo = null;
2160 var skip_attributes = [];
2161 if (isInputNode || isOutputNode) {
2162 if (input.properties_info) {
2163 skip_attributes = ['name', 'type', 'value', 'default_value'];
2164 propInfo = input.properties_info[0];
2165 }
2166 }
2167 else {
2168 if (node.properties_info) {
2169 skip_attributes = ['name', 'type', 'value', 'default_value', 'uimin', 'uimax', 'uiname', 'uifolder'];
2170 propInfo = node.properties_info[i];
2171 }
2172 }
2173 if (propInfo) {
2174 //console.log('Scan propinfo:', propInfo, 'for input:', inputElement.getNamePath(), 'prop_info:', propInfo);
2175
2176 // Write node_properties metadata to input
2177 skip_attributes = skip_attributes.concat(['defaultgeomprop', 'enum', 'enumvalues']);
2178 //console.log('SKIP ATTRIBUTES:', skip_attributes);
2179 for (var propAttribute in propInfo) {
2180 if (skip_attributes.includes(propAttribute))
2181 continue;
2182
2183 //console.log('-- scan attrib:', propAttribute);
2184 var propAttributeValue = propInfo[propAttribute];
2185 if (propAttributeValue && propAttributeValue.length > 0) {
2186 inputElement.setAttribute(propAttribute, propAttributeValue);
2187 }
2188 }
2189 }
2190 }
2191 }
2192
2193 if (debug)
2194 console.log('---- END Write inputs:', node.inputs);
2195 }
2196
2197 if (debug)
2198 console.log('---> End write node', node.title);
2199 }
2200
2201 if (debug)
2202 console.log('***** END Scan Graph:', graph.title);
2203 }
2204
2211 isArray(_type) {
2212 var ARRAY_TYPES = ['color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44'];
2213 if (ARRAY_TYPES.includes(_type)) {
2214 return true;
2215 }
2216 return false;
2217 }
2218
2229 buildConnections(editor, node, lg_node, explicitInputs, graph, parentGraph) {
2230
2231 var nodeInputs = [];
2232 var isOutput = (node.getCategory() == 'output');
2233 var isInput = (node.getCategory() == 'input');
2234 if (isOutput || isInput) {
2235 nodeInputs = [node];
2236 }
2237 else {
2238 nodeInputs = node.getInputs();
2239 }
2240 for (var input of nodeInputs) {
2241
2242 var _name = ''
2243
2244 if (!isOutput && !isInput) {
2245 _name = input.getName();
2246 explicitInputs.push(_name);
2247 }
2248
2249 var nodeName = input.getNodeName();
2250 var nodeGraphName = input.getNodeGraphString();
2251 var inputInterfaceName = input.getInterfaceName();
2252 var outputName = input.getOutputString();
2253
2254 if (nodeName.length ||
2255 nodeGraphName.length ||
2256 inputInterfaceName.length ||
2257 outputName.length) {
2258
2259 //console.log('Test connection on input:', input.getNamePath(), 'nodeName:[ ', nodeName,
2260 // '] nodeGraphName:[', nodeGraphName,
2261 // '] inputInterfaceName:[', inputInterfaceName,
2262 // ']outputName:[', outputName, ']');
2263
2264 var target_node = lg_node;
2265 var target_slot = null;
2266 if (!isOutput && !isInput)
2267 target_slot = target_node.findInputSlot(_name);
2268 else
2269 target_slot = 0;
2270 var source_node = null;
2271 var source_slot = 0;
2272 var source_name = nodeName;
2273 if (nodeGraphName.length) {
2274 source_name = nodeGraphName;
2275 }
2276 if (inputInterfaceName.length) {
2277 source_name = inputInterfaceName;
2278 }
2279
2280 var graphToCheck = graph;
2281 if (isInput && graph._subgraph_node) {
2282 target_node = graph._subgraph_node;
2283 target_slot = target_node.findInputSlot(lg_node.title);
2284 // Go up to parent graph
2285 graphToCheck = parentGraph;
2286 //console.log(' go up to parent graph:', graphToCheck,
2287 // 'from:', graph, 'subgraph:', graph._subgraph_node,
2288 //'target_node:', target_node.title, 'target_slot:', target_slot);
2289 }
2290 source_node = graphToCheck.findNodeByTitle(source_name);
2291 if (source_node) {
2292 if (outputName) {
2293 var outputSlot = source_node.findOutputSlot(outputName);
2294 if (outputSlot >= 0) {
2295 source_slot = outputSlot;
2296 }
2297 else {
2298 editor.debugOutput('Failed to find output slot:' + outputName, 1);
2299 }
2300 var linkInfo = source_node.connect(source_slot, target_node, target_slot);
2301 if (!linkInfo) {
2302 editor.debugOutput('Failed to connect:' + source_node.title + '.' + outputName, '->', target_node.title + '.' + _name), 1, false;
2303 }
2304 }
2305 //console.log('CONNECT START: source[', source_node.title, '.', source_slot,
2306 // '] --> target[:', target_node.title, ".", target_slot);
2307 var linkInfo = null;
2308 if (source_slot == null || target_slot == null || target_node == null) {
2309 console.warning('Cannot connect!')
2310 }
2311 else {
2312 linkInfo = source_node.connect(source_slot, target_node, target_slot);
2313 }
2314 if (!linkInfo) {
2315 editor.debugOutput('Failed to connect:' + source_node.title + '.' + outputName, '->', target_node.title + '.' + _name, 1);
2316 }
2317 //console.log('CONNECT END: source[', source_node.title, '.', source_slot,
2318 // '] --> target[:', target_node.title, ".", target_slot);
2319 }
2320 else {
2321 console.log('Failed to find node ', source_name, 'in graph:', graphToCheck);
2322 this.editor.debugOutput('Failed to find source node: ' + source_node + "." +
2323 source_name, '->', lg_node.title + "." + _name, 2);
2324 }
2325 }
2326 else {
2327 var _value = input.getResolvedValueString(); // input.getValueString();
2328 if (_value.length > 0) {
2329 if (this.isArray(input.getType())) {
2330 // split by commas or spaces
2331 let valueArray = _value.split(/[\s,]+/);
2332 _value = valueArray;
2333 }
2334
2335 //console.log('-- Value Input:',
2336 //lg_node.title + "." + _name, 'value:', _value);
2337 lg_node.setProperty(_name, _value);
2338 }
2339 }
2340
2341 var property_info = lg_node.getPropertyInfo(_name);
2342 this.loadInputMetaData(node, input, property_info);
2343 }
2344 }
2345
2354 loadInputMetaData(node, input, property_info) {
2355 if (input && property_info) {
2356
2357 // Load in basic meta-data
2358 var colorspace = input.getColorSpace();
2359 if (colorspace.length > 0)
2360 property_info['colorspace'] = colorspace;
2361
2362 var unit = input.getUnit();
2363 if (unit.length > 0)
2364 property_info['unit'] = unit;
2365
2366 var uiname = input.getAttribute('uiname');
2367 if (uiname.length > 0)
2368 property_info['uiname'] = uiname;
2369
2370 var uimin = input.getAttribute('uimin');
2371 if (uimin.length > 0)
2372 property_info['uimin'] = uimin;
2373
2374 var uimax = input.getAttribute('uimax');
2375 if (uimax.length > 0)
2376 property_info['uimax'] = uimax;
2377 var uisoftmin = input.getAttribute('uisoftmin');
2378 if (uisoftmin.length > 0)
2379 property_info['uimin'] = uisoftmin;
2380
2381 var uisoftmax = input.getAttribute('uisoftmax');
2382 if (uisoftmax.length > 0)
2383 property_info['uimax'] = uisoftmax;
2384
2385 var uifolder = input.getAttribute('uifolder');
2386 if (uifolder.length > 0)
2387 property_info['uifolder'] = uifolder;
2388
2389 var basicMetaData = ['colorspace', 'unit', 'uiname', 'uimin', 'uimax', 'uifolder', 'name', 'type', 'output', 'nodename', 'nodegraph'];
2390 for (var attrName of input.getAttributeNames()) {
2391 if (!basicMetaData.includes(attrName)) {
2392 property_info[attrName] = input.getAttribute(attrName);
2393 }
2394 }
2395
2396 if (node && input.getType() == 'filename')
2397 {
2398 let nodeType = node.getType();
2399 let colorTypes = ['color3', 'color4'];
2400 //console.log('Load input metadata for:', input.getName(), input.getType(), property_info, nodeType);
2401 if (colorTypes.includes(nodeType))
2402 {
2403 if (!property_info['colorspace']) {
2404 console.log('Auto create "none" colorspace for input:', input.getName());
2405 let doc = node.getDocument();
2406 let defaultColorSpace = 'none';
2407 // For now don't use the document color space as 'none' is more flexible.
2408 //let docColorSpace = doc.getAttribute('colorspace');
2409 //if (docColorSpace.length > 0)
2410 // defaultColorSpace = docColorSpace;
2411 property_info['colorspace'] = defaultColorSpace;
2412 }
2413 }
2414 }
2415
2416 //console.log('load input metadata for:', input.getNamePath(), property_info);
2417 }
2418 }
2419
2428 buildGraphFromDoc(doc, editor, auto_arrange) {
2429 let debug = false;
2430 let loadNodePositions = false; // TODO: Some issues with UI update when setting xpos, ypos. To address.
2431
2432 //console.log('Build graph from doc. auto_arrange: ', auto_arrange);
2433 if (!ne_mx) {
2434 editor.debugOutput("MaterialX is not initialized", 2);
2435 return;
2436 }
2437
2438 editor.clearGraph();
2439
2440 // Don't try and update the graph while building it
2441 editor.monitor.monitorGraph(graph, false);
2442
2443 // Index here is index into litegraph nodes
2444 var mtlxNodes = [];
2445 var mtlxNodeDefs = [];
2446
2447 for (var interfaceInput of doc.getInputs()) {
2448 var _type = interfaceInput.getType();
2449 var id = 'mtlx/input/input_' + _type;
2450
2451 var lg_node = LiteGraph.createNode(id);
2452 if (lg_node) {
2453 lg_node.title = interfaceInput.getName();
2454 if (debug)
2455 console.log('Add top level input:', lg_node.title, 'to graph', graph);
2456
2457 var _value = interfaceInput.getValueString();
2458 if (_value && _value.length > 0) {
2459 if (this.isArray(interfaceInput.getType())) {
2460 _value = "[" + _value + "]"
2461 _value = JSON.parse(_value);
2462 }
2463 lg_node.setProperty('in', _value);
2464 }
2465
2466 if (loadNodePositions) {
2467 var xpos = interfaceInput.getAttribute('xpos');
2468 var ypos = interfaceInput.getAttribute('ypos');
2469 if (xpos.length > 0 && ypos.length > 0) {
2470 lg_node.pos[0] = xpos;
2471 lg_node.pos[1] = ypos;
2472 //lg_node.flags.collapsed = false;
2473 }
2474 }
2475
2476 // Make sure size is updated
2477 lg_node.setSize(lg_node.computeSize());
2478
2479 graph.add(lg_node);
2480
2481 //mtlxNodes.push([interfaceInput, lg_node, graph]);
2482 }
2483 }
2484
2485 for (var interfaceOutput of doc.getOutputs()) {
2486 var _type = interfaceOutput.getType()
2487 var id = 'mtlx/output/output_' + _type;
2488
2489 var lg_node = LiteGraph.createNode(id);
2490 if (lg_node) {
2491 lg_node.title = interfaceOutput.getName();
2492 graph.add(lg_node);
2493 if (debug) {
2494 console.log('Add graph output:', lg_node.title);
2495 }
2496
2497 // Make sure size is updated
2498 lg_node.setSize(lg_node.computeSize());
2499
2500 if (loadNodePositions) {
2501 var xpos = interfaceOutput.getAttribute('xpos');
2502 var ypos = interfaceOutput.getAttribute('ypos');
2503 if (xpos.length > 0 && ypos.length > 0)
2504 lg_node.pos = [xpos, ypos];
2505 }
2506
2507 mtlxNodes.push([interfaceOutput, lg_node, graph]);
2508 }
2509 }
2510
2511 for (var node of doc.getNodes()) {
2512 var nodeDef = node.getNodeDef();
2513 if (!nodeDef) {
2514 editor.debugOutput('Skip node w/o nodedef:' + node.getName(), 1)
2515 continue;
2516 }
2517
2518 // mtlx/pbr/gltf_pbr_surfaceshader
2519 var id = 'mtlx/' + nodeDef.getNodeGroup() + '/' + nodeDef.getName();
2520 id = id.replace('ND_', '');
2521 if (debug)
2522 console.log('Load node:', node.getName(), ' -> ', id);
2523
2524 var lg_node = LiteGraph.createNode(id);
2525 if (lg_node) {
2526 //console.log('LiteGraph node:', lg_node);
2527 lg_node.title = node.getName();
2528
2529 graph.add(lg_node);
2530
2531 // Make sure size is updated
2532 lg_node.setSize(lg_node.computeSize());
2533
2534 if (loadNodePositions) {
2535 var xpos = node.getAttribute('xpos');
2536 var ypos = node.getAttribute('ypos');
2537 if (xpos.length > 0 && ypos.length > 0)
2538 lg_node.pos = [xpos, ypos];
2539 }
2540
2541 mtlxNodes.push([node, lg_node, graph]);
2542 mtlxNodeDefs.push(nodeDef);
2543 }
2544 else {
2545 editor.debugOutput('Failed to create node:' + node.getName(), 2);
2546 }
2547 }
2548
2549 for (var nodegraph of doc.getNodeGraphs()) {
2550 if (nodegraph.hasSourceUri()) {
2551 continue;
2552 }
2553 var nodedefAttrib = nodegraph.getAttribute('nodedef');
2554 if (nodedefAttrib && nodedefAttrib.length > 0) {
2555 console.log('Skip loading in functional graph:', nodegraph.getName(), 'nodedef:', nodedefAttrib);
2556 continue;
2557 }
2558 if (debug)
2559 console.log('Create nodegraph:', nodegraph.getName());
2560
2561 var title = nodegraph.getName();
2562 var subgraphNode = LiteGraph.createNode("graph/subgraph", title);
2563 //var subgraph = new LiteGraph.LGraph();
2564 //subgraphNode._subgraph_node = subgraph;
2565 //subgraphNode.bgcolor = "#112";
2566 subgraphNode.bgImageUrl = "./Icons/nodegraph.png";
2567
2568 var mtlxSubGraphNodes = [];
2569 for (var interfaceInput of nodegraph.getInputs()) {
2570 var _type = interfaceInput.getType();
2571 var id = 'mtlx/input/input_' + _type;
2572
2573 var lg_node = LiteGraph.createNode(id);
2574 if (lg_node) {
2575 lg_node.title = interfaceInput.getName();
2576 this.loadInputMetaData(null, interfaceInput, lg_node.properties_info[0]);
2577 subgraphNode.subgraph.add(lg_node);
2578
2579 if (debug)
2580 console.log('-------- Add subgraph input:', lg_node.title, lg_node);
2581
2582 subgraphNode.addInput(interfaceInput.getName(), _type);
2583 subgraphNode.subgraph.addInput(interfaceInput.getName(), _type);
2584
2585 var _value = interfaceInput.getValueString();
2586 if (_value && _value.length > 0) {
2587 if (this.isArray(interfaceInput.getType())) {
2588 _value = "[" + _value + "]"
2589 _value = JSON.parse(_value);
2590 }
2591 lg_node.setProperty('in', _value);
2592 }
2593
2594 // Make sure size is updated
2595 lg_node.setSize(lg_node.computeSize());
2596
2597 if (loadNodePositions) {
2598 var xpos = nodegraph.getAttribute('xpos');
2599 var ypos = nodegraph.getAttribute('ypos');
2600 if (xpos.length > 0 && ypos.length > 0)
2601 lg_node.pos = [xpos, ypos];
2602 }
2603
2604 mtlxSubGraphNodes.push([interfaceInput, lg_node, graph]);
2605 }
2606 }
2607
2608 for (var interfaceOutput of nodegraph.getOutputs()) {
2609 var _type = interfaceOutput.getType()
2610 var id = 'mtlx/output/output_' + _type;
2611
2612 var lg_node = LiteGraph.createNode(id);
2613 if (lg_node) {
2614 lg_node.title = interfaceOutput.getName();
2615 subgraphNode.subgraph.add(lg_node);
2616 if (debug)
2617 console.log('Add subgraph output:', lg_node.title);
2618
2619 subgraphNode.addOutput(interfaceOutput.getName(), _type);
2620 subgraphNode.subgraph.addOutput(interfaceOutput.getName(), _type);
2621
2622 // Make sure size is updated
2623 lg_node.setSize(lg_node.computeSize());
2624
2625 if (loadNodePositions) {
2626 var xpos = interfaceOutput.getAttribute('xpos');
2627 var ypos = interfaceOutput.getAttribute('ypos');
2628 if (xpos.length > 0 && ypos.length > 0)
2629 lg_node.pos = [xpos, ypos];
2630 }
2631
2632 mtlxSubGraphNodes.push([interfaceOutput, lg_node, graph]);
2633 }
2634 }
2635
2636
2637 for (var node of nodegraph.getNodes()) {
2638 var nodeDef = node.getNodeDef();
2639 if (!nodeDef) {
2640 editor.debugOutput('Skip node w/o nodedef:' + node.getName(), 1)
2641 continue;
2642 }
2643
2644 // mtlx/pbr/gltf_pbr_surfaceshader
2645 var id = 'mtlx/' + nodeDef.getNodeGroup() + '/' + nodeDef.getName();
2646 id = id.replace('ND_', '');
2647
2648 var lg_node = LiteGraph.createNode(id);
2649 lg_node.title = node.getName();
2650 subgraphNode.subgraph.add(lg_node);
2651 if (debug)
2652 console.log('Add subgraph node:', lg_node.title);
2653
2654 // Make sure size is updated
2655 lg_node.setSize(lg_node.computeSize());
2656
2657 if (loadNodePositions) {
2658 var xpos = node.getAttribute('xpos');
2659 var ypos = node.getAttribute('ypos');
2660 if (xpos.length > 0 && ypos.length > 0)
2661 lg_node.pos = [xpos, ypos];
2662 }
2663
2664 mtlxSubGraphNodes.push([node, lg_node, graph]);
2665 }
2666
2667 for (var item of mtlxSubGraphNodes) {
2668 var node = item[0];
2669 var lg_node = item[1];
2670 var parentGraph = item[2];
2671 var explicitInputs = [];
2672
2673 // Make sure size is updated
2674 lg_node.setSize(lg_node.computeSize());
2675
2676 //console.log('Build connections for subgraog node:', lg_node.title);
2677 this.buildConnections(editor, node, lg_node, explicitInputs, subgraphNode.subgraph, parentGraph);
2678 }
2679
2680 if (debug)
2681 console.log('Add subgraph:', subgraphNode.title);
2682
2683 if (auto_arrange > 0) {
2684 subgraphNode.subgraph.arrange(auto_arrange);
2685 }
2686
2687 graph.add(subgraphNode);
2688
2689 }
2690
2691 // Build top level connections last after top level nodes
2692 // and nodegraph have been added.
2693 var itemCount = 0;
2694 for (var item of mtlxNodes) {
2695 var node = item[0];
2696 var lg_node = item[1];
2697
2698 // Keep track of explicit inputs
2699 var explicitInputs = [];
2700 //console.log('Build connections for:', lg_node.title);
2701 this.buildConnections(editor, node, lg_node, explicitInputs, graph, null);
2702
2703 if (lg_node.nodedef_node == 'input' || lg_node.nodedef_node == 'output') {
2704 continue;
2705 }
2706
2707 /*
2708 var removeInputs = [];
2709 var nodeDef = mtlxNodeDefs[itemCount];
2710 if (nodeDef) {
2711 for (var nodeDefInput of nodeDef.getActiveInputs()) {
2712 var _name = nodeDefInput.getName();
2713 if (!explicitInputs.includes(_name)) {
2714 removeInputs.push(_name);
2715 }
2716 }
2717 for (var _name of removeInputs) {
2718 var slot = lg_node.findInputSlot(_name);
2719 lg_node.inputs[slot].hidden = true;
2720 console.log('Hide input:', _name, '. ', slot, ' on: ', lg_node);
2721 //lg_node.removeInput(slot);
2722 }
2723
2724 // Make sure size is updated
2725 lg_node.setSize(lg_node.computeSize());
2726 }
2727 */
2728 itemCount++;
2729 }
2730
2731 editor.monitor.monitorGraph(graph, true);
2732
2733 if (auto_arrange > 0) {
2734 graph.arrange(auto_arrange);
2735 }
2736
2737 graph.setDirtyCanvas(true, true);
2738 graphcanvas.setDirty(true, true);
2739 }
2740
2747 var that = this;
2748
2749 // Load mtlx document from disk
2750 var input = document.createElement("input");
2751 input.style = this.fontSizeStyle;
2752 input.type = "file";
2753 input.accept = ".mtlx";
2754 input.onchange = function (e) {
2755 var file = e.target.files[0];
2756 console.log('Loading definitions from file: ' + file.name);
2757
2758 if (ne_mx) {
2759 // Load the content from the specified file (replace this with actual loading logic)
2760
2761 const reader = new FileReader();
2762 reader.readAsText(file, 'UTF-8');
2763
2764 reader.onload = function (e) {
2765 // Display the contents of the file in the output div
2766 let fileContents = e.target.result;
2767 //console.log(fileContents);
2768
2769 (async () => {
2770 try {
2771 const readOptions = new ne_mx.XmlReadOptions();
2772 readOptions.readXIncludes = false;
2773 var customLib = ne_mx.createDocument();
2774
2775 await ne_mx.readFromXmlString(customLib, fileContents, '', readOptions);
2776
2777 // Create JS from custom library
2778 try {
2779 console.log('Create custom library definitions')
2780 var iconName = '';
2781 var scanForIcon = false;
2782 if (scanForIcon) {
2783 // Icon name is filename with webp as extension
2784 var iconName = file.name.replace(/\.[^/.]+$/, ".webp");
2785 // Check if iconName file exists
2786 var iconExists = await that.editor.uriExists(iconName);
2787 if (!iconExists) {
2788 iconName = '';
2789 }
2790 }
2791 var definitionsList = [];
2792 var result = that.createLiteGraphDefinitions(customLib, false, false, definitionsList, 'mtlx', that.editor, iconName);
2793 if (result) {
2794 eval(result);
2795 var definitionsListString = definitionsList.join(', ');
2796 that.editor.debugOutput("Registered custom node types: [" + definitionsListString + "]", 0, false);
2797 that.editor.displayNodeTypes();
2798 }
2799 } catch (e) {
2800 console.log('Error evaluating source:', e);
2801 }
2802
2803
2804 // Keep track of libraries loaded by filename.
2805 customlibs.push([file.name, customLib]);
2806
2807 } catch (error) {
2808 that.editor.debugOutput('Error reading definitions:' + error, 2, false);
2809 }
2810 })();
2811
2812 };
2813
2814 } else {
2815 that.editor.debugOutput("MaterialX is not initialized", 2);
2816 }
2817
2818 //customlibs
2819 };
2820 input.click();
2821 }
2822
2832 loadFromString(extension, fileContents, fileName, auto_arrange) {
2833 if (!ne_mx) {
2834 console.log('MaterialX is not initialized');
2835 return;
2836 }
2837
2838 // Check if we need to pre-convert from extension type to mtlx
2839 if (extension != 'mtlx')
2840 {
2841 let converter = this.getImporter(extension);
2842 if (converter) {
2843 let result = converter.import(ne_mx, fileContents, stdlib);
2844 if (result) {
2845 fileContents = result[0];
2846 }
2847 else {
2848 console.log('Failed to convert from:', extension, 'to mtlx. Errors:', result[1]);
2849 return;
2850 }
2851 }
2852 else
2853 {
2854 console.log('Failed to find converter from:', extension, 'to mtlx.');
2855 return;
2856 }
2857 }
2858
2859 (async () => {
2860 try {
2861 const readOptions = new ne_mx.XmlReadOptions();
2862 readOptions.readXIncludes = false;
2863
2864 doc.clearContent();
2865
2866 doc.importLibrary(stdlib);
2867 for (var item of customlibs) {
2868 console.log('Import custom library:', item[0]);
2869 doc.importLibrary(item[1]);
2870 }
2871 var loadDoc = ne_mx.createDocument();
2872 await ne_mx.readFromXmlString(loadDoc, fileContents, '', readOptions);
2873
2874 // Check if nodedef is not in existingDefs
2875 //
2876 var customLib = ne_mx.createDocument();
2877 customLib.copyContentFrom(loadDoc);
2878 var keepChildren = [];
2879 var existingDefs = []
2880 var saveCustomLib = false;
2881 doc.getNodeDefs().forEach(def => { existingDefs.push(def.getName()); });
2882 for (var nodedef of loadDoc.getNodeDefs()) {
2883 var nodedefName = nodedef.getName();
2884 if (!existingDefs.includes(nodedefName)) {
2885 keepChildren.push(nodedef.getName());
2886 saveCustomLib = true;
2887 }
2888 }
2889 for (var ng of loadDoc.getNodeGraphs()) {
2890 if (ng.getAttribute('nodedef') && ng.getAttribute('nodedef').length > 0) {
2891 saveCustomLib = true;
2892 keepChildren.push(ng.getName());
2893 }
2894 }
2895
2896 if (saveCustomLib) {
2897
2898 for (var child of customLib.getChildren()) {
2899 if (!keepChildren.includes(child.getName())) {
2900 //console.log('Remove child:', child.getName());
2901 customLib.removeChild(child.getName());
2902 }
2903 }
2904
2905 var additionDefs = [];
2906 var result = this.createLiteGraphDefinitions(customLib, true, false, additionDefs, 'mtlx', MxShadingGraphEditor.theEditor);
2907 try {
2908 eval(result);
2909 console.log('Loaded local definitions: ', additionDefs);
2910 } catch (e) {
2911 console.log('Error evaluating source:', e);
2912 }
2913 }
2914
2915 doc.copyContentFrom(loadDoc);
2916
2917 this.validateDocument(doc);
2918
2919 this.buildGraphFromDoc(doc, MxShadingGraphEditor.theEditor, auto_arrange);
2920
2921 // Must do this after build as build will clear customDocLibs array
2922 if (saveCustomLib) {
2923 customDocLibs.push([fileName, customLib]);
2924 }
2925
2926 // Get the document's colorspace and set it as the active colorspace
2927 var documentColorSpace = doc.getColorSpace();
2928 this.setSourceColorSpace(documentColorSpace);
2929 documentColorSpace = this.getSourceColorSpace();
2930 MxShadingGraphEditor.theEditor.updatePropertyPanel(null);
2931
2932 // Cleanup document, and get up-to-date contents after any possible upgrade.
2933 loadDoc.removeAttribute('fileprefix');
2934 fileContents = ne_mx.writeToXmlString(loadDoc);
2935
2936 this.validateDocument(loadDoc);
2937
2938 MxShadingGraphEditor.theEditor.debugOutput('Loaded document: "' + fileName + '"', 0, false);
2939
2940 // Update mtlx text area
2941 let documentDisplayUpdater = MxShadingGraphEditor.theEditor.ui.documentDisplayUpdater;
2942 if (documentDisplayUpdater) {
2943 documentDisplayUpdater(fileContents);
2944 }
2945 else {
2946 console.log('No docDisplayUpdater!!!');
2947 }
2948
2949 // Update render items in UI
2950 let renderableItemUpdater = MxShadingGraphEditor.theEditor.ui.renderableItemUpdater;
2951 if (renderableItemUpdater) {
2952 let renderableItems = this.findRenderableItemsInDoc(doc);
2953 if (!renderableItems || renderableItems.length == 0) {
2954 MxShadingGraphEditor.theEditor.debugOutput('No renderable items found in graph: ' + fileName, 1, false);
2955 }
2956 renderableItemUpdater(renderableItems);
2957 }
2958
2959 } catch (error) {
2960 MxShadingGraphEditor.theEditor.debugOutput('Error reading document: ' + fileName + '. Error: ' + error, 2, false);
2961 }
2962 })();
2963 }
2964
2975 loadFromFile(extension, file, fileName, editor, auto_arrange) {
2976 var debug = false;
2977
2978 if (ne_mx) {
2979 if (!this.loadMaterialXLibraries(stdlib))
2980 return;
2981
2982 // Load the content from the specified file (replace this with actual loading logic)
2983
2984 const reader = new FileReader();
2985 reader.readAsText(file, 'UTF-8');
2986 reader.accept = '.mtlx';
2987
2988 var that = this;
2989 console.log('loadFromFile:', file, fileName);
2990 try {
2991 reader.onload = function (e) {
2992 // Display the contents of the file in the output div
2993 let fileContents = e.target.result;
2994 console.log("read file: ", file.name, " with extension: ", extension, " and length: ", fileContents.length);
2995
2996 that.loadFromString(extension, fileContents, fileName, auto_arrange);
2997 };
2998 } catch (error) {
2999 MxShadingGraphEditor.theEditor.debugOutput('Error reading document: ' + fileName + '. Error: ' + error, 2, false);
3000 }
3001
3002 } else {
3003 editor.debugOutput("MaterialX is not initialized", 2, false);
3004 }
3005 }
3006
3013 {
3014 if (stdlib)
3015 return stdlib;
3016
3017 if (!ne_mx) {
3018 MxShadingGraphEditor.theEditor.debugOutput("MaterialX is not initialized", 2);
3019 return null;
3020 }
3021
3022 var generator = new ne_mx.EsslShaderGenerator();
3023 var genContext = new ne_mx.GenContext(generator);
3024 {
3025 stdlib = ne_mx.loadStandardLibraries(genContext);
3026 console.log('Loaded standard libraries:', stdlib.getNodeDefs().length);
3027 }
3028
3029 return stdlib;
3030 }
3031
3039 createValidName(name, msg = null) {
3040 if (name.length == 0) {
3041 if (msg) {
3042 msg = 'Setting empty name as "blank"';
3043 }
3044 name = "blank";
3045 }
3046
3047 // Get list of all names in graph.
3048 var graph = graphcanvas.graph;
3049 var nodes = graph._nodes;
3050 var nodenames = [];
3051 for (var node of nodes) {
3052 nodenames.push(node.title);
3053 }
3054 //console.log('Current graph nodes:', nodenames);
3055
3056 name = ne_mx.createValidName(name);
3057
3058 if (!nodenames.includes(name)) {
3059 return name;
3060 }
3061
3062 // Get starting number and root name
3063 var rootName = name;
3064 var i = 1;
3065 var number = name.match(/\d+$/);
3066 if (number) {
3067 i = (parseInt(number) + 1)
3068 rootName = name.slice(0, -number[0].length);
3069 }
3070
3071 var valid_name = rootName + i.toString();
3072 while (nodenames.includes(valid_name)) {
3073 i++;
3074 valid_name = rootName + i.toString();
3075 }
3076 return valid_name;
3077 }
3078};
3079
3087{
3091 constructor() {
3092 if (!MxShadingGraphEditor.theEditor) {
3093 MxShadingGraphEditor.theEditor = this;
3094
3095 this.ui = null;
3096 this.fontSizeStyle = 'font-size: 11px;';
3097
3098 this.handler = new MxMaterialXHandler('MaterialX Handler', 'mtlx');
3099 let gltfConverter = new glTFMaterialX();
3100 this.handler.addConverter(gltfConverter);
3101
3102 console.log('Create new editor with exporter for:', gltfConverter.exportType());
3103
3104 }
3105 return MxShadingGraphEditor.theEditor;
3106 }
3107
3114 setUI(ui) {
3115 this.ui = ui;
3116 }
3117
3121 setDirty(w = null, h = null) {
3122 if (graph)
3123 graph.setDirtyCanvas(true, true);
3124 if (graphcanvas) {
3125 graphcanvas.resize(w,h);
3126 }
3127 }
3128
3137 debugOutput(text, severity, clear = null) {
3138 var consoleLog = MxShadingGraphEditor.theEditor.ui.consoleLogger;
3139 if (consoleLog) {
3140 consoleLog(text, severity, clear);
3141 }
3142 else {
3143 console.log('> ', text, ' severity:', severity);
3144 }
3145 }
3146
3153 setSourceColorSpace(colorSpace) {
3154 if (this.handler) {
3155 this.handler.setSourceColorSpace(colorSpace);
3156 }
3157 }
3158
3165 setTargetDistanceUnit(unit) {
3166 if (this.handler) {
3167 this.handler.setTargetDistanceUnit(unit);
3168 }
3169 }
3170
3176 getSourceColorSpace() {
3177 if (this.handler) {
3178 return this.handler.getSourceColorSpace();
3179 }
3180 return 'lin_rec709';
3181 }
3182
3188 getTargetDistanceUnit() {
3189 if (this.handler) {
3190 return this.handler.getTargetDistanceUnit();
3191 }
3192 return 'meter';
3193 }
3194
3201 arrangeGraph(spacing = 80) {
3202 // This does not track the current subgraph.
3203 if (graphcanvas) {
3204 graphcanvas.graph.arrange(spacing);
3205 }
3206 }
3207
3211 openSubgraph() {
3212 var selected = graphcanvas.selected_nodes;
3213 for (var s in selected) {
3214 var node = selected[s];
3215 if (node.type == 'graph/subgraph') {
3216 graphcanvas.openSubgraph(node.subgraph);
3217 break;
3218 }
3219 }
3220 }
3221
3225 closeSubgraph() {
3226 if (graphcanvas) {
3227 graphcanvas.closeSubgraph();
3228 }
3229 }
3230
3234 resetView() {
3235 if (graphcanvas) {
3236 graphcanvas.ds.reset();
3237 graphcanvas.setDirty(true, true);
3238 graphcanvas.centerOnGraph(false);
3239 }
3240 }
3241
3246 clearGraph() {
3247
3248 this.handler.sourceColorSpace = this.handler.DEFAULT_COLOR_SPACE;
3249 this.handler.targetDistanceUnits = this.handler.DEFAULT_DISTANCE_UNITS;
3250 MxShadingGraphEditor.theEditor.updatePropertyPanel(null);
3251 this.updatePropertyPanel(null);
3252 if (graphcanvas) {
3253 // Set back to top graph
3254 graphcanvas.setGraph(graph);
3255 graphcanvas.graph.clear();
3256 graphcanvas.ds.reset();
3257 graphcanvas.setDirty(true, true);
3258 }
3259 customDocLibs = [];
3260 }
3261
3265 saveSerialization() {
3266 var data = JSON.stringify(graph.serialize(), null, 2);
3267 var blob = new Blob([data], { type: "text/plain" });
3268 var url = URL.createObjectURL(blob);
3269 var a = document.createElement("a");
3270 a.href = url;
3271 a.download = "serialized_graph.json";
3272 a.click();
3273 }
3274
3278 loadSerialization() {
3279 MxShadingGraphEditor.theEditor.clearGraph();
3280
3281 var input = document.createElement("input");
3282 input.style = this.fontSizeStyle;
3283 input.type = "file";
3284 input.accept = ".json";
3285 input.onchange = function (e) {
3286 var file = e.target.files[0];
3287 var reader = new FileReader();
3288 reader.onload = function (event) {
3289 var data = JSON.parse(event.target.result);
3290 graph.configure(data);
3291 };
3292 reader.readAsText(file);
3293 };
3294 input.click();
3295 }
3296
3304 saveGraphToFile(extension, graphWriteOptions) {
3305 if (this.handler.canExport(extension)) {
3306 this.handler.saveGraphToFile(extension, graph, graphWriteOptions);
3307 }
3308 else
3309 {
3310 this.debugOutput('Unsupported extension for saving graph:' + extension, 2, false);
3311 }
3312 }
3313
3320 saveGraphToString(extension, graphWriteOptions) {
3321 if (this.handler.canExport(extension)) {
3322 return this.handler.saveGraphToString(extension, graph, graphWriteOptions);
3323 }
3324 else
3325 {
3326 this.debugOutput('Unsupported extension for saving graph: ' + extension, 2, false);
3327 return '';
3328 }
3329 }
3330
3337 loadDefinitionsFromFile(extension) {
3338 if (extension == 'mtlx') {
3339 this.handler.loadDefinitionsFromFile();
3340 }
3341 else
3342 {
3343 this.debugOutput('Unsupported extension for loading definitions: ' + extension, 2, false);
3344 }
3345 }
3346
3354 loadGraphFromFile(extension, auto_arrange) {
3355
3356 if (!this.handler.canImport(extension)) {
3357 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3358 return;
3359 }
3360
3361 // Load document from disk.
3362 var input = document.createElement("input");
3363 input.style = this.fontSizeStyle;
3364 input.type = "file";
3365 input.accept = "." + this.handler.getExtension();
3366 input.onchange = function (e) {
3367 var file = e.target.files[0];
3368 console.log('Loading file: ' + file.name);
3369 MxShadingGraphEditor.theEditor.handler.loadFromFile(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange);
3370 };
3371 input.click();
3372 }
3373
3379 findRenderableItems() {
3380 return this.handler.findRenderableItems(graph);
3381 }
3382
3392 loadGraphFromString(extension, content, fileName, auto_arrange) {
3393 if (!this.handler.canImport(extension)) {
3394 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3395 return;
3396 }
3397
3398 if (content.length > 0)
3399 this.handler.loadFromString(extension, content, fileName, auto_arrange);
3400 else
3401 MxShadingGraphEditor.theEditor.debugOutput('No content to load', 2, false);
3402 }
3403
3410 rgbToHex(rgb) {
3411 if (!rgb) {
3412 console.log('rgbToHex empty !', rgb);
3413 return "#000000";
3414 }
3415 return '#' + rgb.map(x => {
3416 var hex = Math.round(x * 255).toString(16);
3417 return hex.length === 1 ? '0' + hex : hex;
3418 }).join('');
3419 }
3420
3429 createButtonWithImageAndText(imageSrc, text, id) {
3430 // Create image element
3431 var img = document.createElement("img");
3432 img.id = id + "_img";
3433 img.src = imageSrc;
3434 img.classList.add("img-fluid");
3435
3436 // Create text element
3437 var span = document.createElement("span");
3438 span.id = id + "_text";
3439 span.textContent = " " + text;
3440
3441 // Create button element
3442 var button = document.createElement("button");
3443 button.id = id;
3444 button.classList.add("btn", "btn-sm", "btn-outline-secondary", "form-control", "form-control-sm");
3445 button.style = this.fontSizeStyle;
3446 button.appendChild(img);
3447 button.appendChild(span);
3448
3449 return button;
3450 }
3451
3459 openImageDialog(theNode, updateProp, wantURI) {
3460
3461 // Dynamically create a file input element
3462 var fileInput = document.createElement('input');
3463 fileInput.type = 'file';
3464 fileInput.accept = 'image/*'; // Accept any image file
3465 fileInput.style.display = 'none';
3466 document.body.appendChild(fileInput);
3467
3468 fileInput.click();
3469
3470 // TODO : Cache the fileURI on the node so can display without loading...
3471 fileInput.addEventListener('change', function () {
3472 var fileURI = fileInput.value.split('\\').pop(); // Get the filename without the full path
3473 var file = fileInput.files[0];
3474 //if (wantURI)
3475 fileURI = URL.createObjectURL(file);
3476
3477 var updateElementId = '__pp:' + updateProp;
3478 var textInput = document.getElementById(updateElementId);
3479 //console.log('New filename:', fileURI, 'updateElementId:', updateElementId, 'updateProp:', updateProp);
3480 textInput.value = fileURI;
3481 theNode.setProperty(updateProp, fileURI);
3482
3483 var propertypanel_preview = document.getElementById('propertypanel_preview');
3484 if (propertypanel_preview) {
3485 propertypanel_preview.src = URL.createObjectURL(file);
3486 propertypanel_preview.style.display = "block";
3487 }
3488
3489 var previewImage = false;
3490 if (previewImage) {
3491 if (propertypanel_preview) {
3492 var reader = new FileReader();
3493 reader.onload = function (event) {
3494 propertypanel_preview.src = event.target.result;
3495 };
3496
3497 // Read the file as a data URL (base64 encoded string)
3498 reader.readAsDataURL(file);
3499 propertypanel_preview.style.display = "block";
3500 }
3501 }
3502
3503 document.body.removeChild(fileInput);
3504 });
3505 }
3506
3513 uriExists(uri) {
3514 return fetch(uri)
3515 .then(response => {
3516 if (response.ok) {
3517 return Promise.resolve(true);
3518 } else {
3519 return Promise.resolve(false);
3520 }
3521 })
3522 .catch(error => {
3523 console.log('Error checking URI:', error);
3524 return Promise.resolve(false);
3525 });
3526 }
3527
3535 createColorSpaceInput(colorSpaces, activeItem) {
3536 var select = document.createElement("select");
3537 select.className = "form-control form-control-sm";
3538 select.style = this.fontSizeStyle;
3539 select.id = "propertypanel_colorspace";
3540 for (var i = 0; i < colorSpaces.length; i++) {
3541 var option = document.createElement("option");
3542 option.value = colorSpaces[i];
3543 option.text = colorSpaces[i];
3544 select.add(option);
3545 }
3546 // Add "none" option
3547 var option = document.createElement("option");
3548 option.value = "none";
3549 option.text = "none";
3550 select.add(option);
3551
3552 select.value = activeItem;
3553 return select;
3554 }
3555
3564 createUnitsInput(units, unittype, activeItem) {
3565 var select = document.createElement("select");
3566 select.className = "form-control form-control-sm";
3567 select.style = this.fontSizeStyle;
3568 select.id = "propertypanel_units";
3569 for (var i = 0; i < units.length; i++) {
3570 var option = document.createElement("option");
3571 var unit_pair = units[i];
3572 if (unit_pair[1] == unittype) {
3573 option.value = unit_pair[0];
3574 option.text = unit_pair[0];
3575 select.add(option);
3576 }
3577 }
3578 select.value = activeItem;
3579 return select;
3580 }
3581
3588 updateImagePreview(curImage) {
3589 var propertypanel_preview = document.getElementById('propertypanel_preview');
3590 if (curImage && propertypanel_preview) {
3591 this.uriExists(curImage)
3592 .then(exists => {
3593 if (exists) {
3594 propertypanel_preview.src = curImage;
3595 propertypanel_preview.style.display = "block";
3596 } else {
3597 //propertypanel_preview.style.display = "none";
3598 propertypanel_preview.src = "./Icons/no_image.png";
3599 propertypanel_preview.style.display = "block";
3600 MxShadingGraphEditor.theEditor.debugOutput('Image does not exist: ' + curImage, 1);
3601 }
3602 });
3603 }
3604 }
3605
3616 updatePropertyPanel(node) {
3617 //console.log('Update Panel For:', node);
3618 var propertypanelcontent = MxShadingGraphEditor.theEditor.ui.propertypanel_content;
3619 if (!propertypanelcontent) {
3620 console.error('No property panel content widget found!');
3621 return;
3622 }
3623 // Delete all children
3624 while (propertypanelcontent.firstChild) {
3625 propertypanelcontent.removeChild(propertypanelcontent.firstChild);
3626 }
3627
3628 // Update icon
3629 var panelIcon = MxShadingGraphEditor.theEditor.ui.propertypanel_icon;
3630 if (node && node.nodedef_icon) {
3631 panelIcon.src = node.nodedef_icon;
3632 }
3633 else if (this.ui.icon_map) {
3634 if (!node)
3635 panelIcon.src = this.ui.icon_map['_default_graph_'];
3636 else
3637 panelIcon.src = this.ui.icon_map['_default_'];
3638 }
3639
3640 propertypanelcontent.innerHTML = "";
3641
3642 let colorSpaces = this.handler.getColorSpaces();
3643 let targetUnits = this.handler.getUnits();
3644
3645 if (!node && graphcanvas.graph._subgraph_node) {
3646 node = graphcanvas.graph._subgraph_node;
3647 //console.log('In subgraph but no node selected. Select subgraph node', node)
3648 }
3649 else if (!node && !graphcanvas.graph._is_subgraph) {
3650 var docInfo = [['Colorspace', this.getSourceColorSpace()],
3651 ['Distance', this.getTargetDistanceUnit()]];
3652
3653 for (let item of docInfo) {
3654
3655 let elem = document.createElement("div");
3656 elem.className = "row px-1 py-0";
3657 let label = document.createElement("div");
3658 label.className = "col py-0 col-form-label-sm text-left";
3659 label.style = this.fontSizeStyle;
3660 label.innerHTML = "<b>" + item[0] + "</b>";
3661 elem.appendChild(label);
3662
3663 if (item[0] == 'Colorspace' && colorSpaces.length > 0) {
3664 // Create colorspace drop down
3665 var inputCol = document.createElement("div");
3666 inputCol.className = "col text-left";
3667 var select = this.createColorSpaceInput(colorSpaces, item[1]);
3668 select.onchange = function (e) {
3669 MxShadingGraphEditor.theEditor.setSourceColorSpace(e.target.value);
3670 }
3671 inputCol.appendChild(select);
3672 elem.appendChild(inputCol);
3673 }
3674 else if (item[0] == 'Distance' && targetUnits.length > 0) {
3675 // Create units drop down
3676 var inputCol = document.createElement("div");
3677 inputCol.className = "col text-left";
3678 var select = this.createUnitsInput(targetUnits, 'distance', item[1]);
3679 select.onchange = function (e) {
3680 MxShadingGraphEditor.theEditor.setTargetDistanceUnit(e.target.value);
3681 }
3682 inputCol.appendChild(select);
3683 elem.appendChild(inputCol);
3684 }
3685 /* var inputCol = document.createElement("div");
3686 inputCol.className = "col text-left";
3687 var nameInput = document.createElement("input");
3688 nameInput.type = "text";
3689 nameInput.value = item[1];
3690 nameInput.className = "form-control form-control-sm";
3691 nameInput.disabled = true;
3692 elem.appendChild(inputCol);
3693 inputCol.appendChild(nameInput);
3694 }
3695 */
3696 propertypanelcontent.appendChild(elem);
3697 }
3698 return;
3699 }
3700
3701 var _category = node.nodedef_node;
3702 var _type = node.nodedef_type;
3703
3704 var isNodeGraph = node.type == 'graph/subgraph';
3705 if (isNodeGraph) {
3706 _category = 'nodegraph';
3707 if (node.outputs) {
3708 if (node.outputs.length > 1) {
3709 _type = 'multi';
3710 }
3711 else if (node.outputs.length > 0) {
3712 _type = node.outputs[0].type;
3713 }
3714 }
3715 else {
3716 _type = '';
3717 }
3718 }
3719 else {
3720 if (_category == 'surfacematerial') {
3721 _type = '';
3722 }
3723 }
3724
3725 // Identification row
3726 var elem = document.createElement("div");
3727 elem.className = "row px-1 py-1";
3728
3729 // Node category and output type
3730 var label = document.createElement("div");
3731 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
3732 label.style = this.fontSizeStyle;
3733 label.innerHTML = "<b>" + _category;
3734 if (_type.length > 0) {
3735 label.innerHTML += '<br>' + _type;
3736 }
3737 label.innerHTML += "</b>";
3738 elem.appendChild(label);
3739
3740 // Node name / title
3741 var inputCol = document.createElement("div");
3742 inputCol.className = "col py-0";
3743 var nameInput = document.createElement("input");
3744 nameInput.style = this.fontSizeStyle;
3745 nameInput.type = "text";
3746 nameInput.value = node.title;
3747 nameInput.className = "form-control form-control-sm";
3748 let that = this;
3749 nameInput.onchange = function (e) {
3750 var oldTitle = node.title;
3751 var newTitle = MxShadingGraphEditor.theEditor.handler.createValidName(e.target.value);
3752 if (newTitle != oldTitle)
3753 {
3754 that.monitor.onNodeRenamed(node, newTitle);
3755 node.title = newTitle;
3756 }
3757 e.target.value = node.title;
3758 //console.log('node.graph._is_subgraph:', node)
3759 if (node.graph._is_subgraph) {
3760 if (node.nodedef_node == 'input') {
3761 //console.log('Rename subgraph input:');
3762 node.graph.renameInput(oldTitle, node.title);
3763 }
3764 else if (node.nodedef_node == 'output') {
3765 //console.log('Rename subgraph output:');
3766 node.graph.renameOutput(oldTitle, node.title);
3767 }
3768 }
3769
3770 // Note: there is a custom size fo subgraphs.
3771 node.setSize(node.computeSize());
3772 node.setDirtyCanvas(true, true);
3773 }
3774 inputCol.appendChild(nameInput);
3775
3776 // TODO: Generate swatches on the fly
3777 if (node.nodedef_node != 'input' && node.nodedef_node != 'output'
3778 && node.type != 'graph/subgraph') {
3779 var imagePreview = document.createElement("img");
3780 imagePreview.src = "./Icons/no_image.png";
3781 var previewSet = false;
3782 //console.log('Check for preview:', node.nodedef_swatch, 'category:', _category)
3783 imagePreview.style.display = "none";
3784 imagePreview.src = "./Icons/no_image.png";
3785 /* if (node.nodedef_swatch &&
3786 (_type == 'BSDF' || _type == 'EDF' || _type == 'surfaceshader'))
3787 {
3788 this.uriExists(node.nodedef_swatch)
3789 .then(exists => {
3790 if (exists) {
3791 previewSet = true;
3792 imagePreview.style.display = "block";
3793 imagePreview.src = node.nodedef_swatch;
3794 }
3795 });
3796 } */
3797 imagePreview.id = "propertypanel_preview";
3798 imagePreview.className = "img-fluid form-control form-control-sm";
3799 inputCol.appendChild(imagePreview);
3800 }
3801
3802 elem.appendChild(label);
3803 elem.appendChild(inputCol);
3804
3805 // Toggle show/hide of inputs with default values
3806 if (!isNodeGraph)
3807 {
3808 var filterCol = document.createElement("div");
3809 filterCol.className = "col-2 py-0";
3810 filterCol.width = 16;
3811 var filterIcon = document.createElement("button");
3812 //filterIcon.setAttribute(data-bs-toggle, "tooltip");
3813 //filterIcon.setAttribute(data-bs-title, "Show/Hide Default Value Inputs");
3814 if (node.showDefaultValueInputs == null)
3815 {
3816 node.showDefaultValueInputs = true;
3817 }
3818 var img = document.createElement("img");
3819 if (node.showDefaultValueInputs)
3820 {
3821 img.src = "./Icons/funnel_white.svg";
3822 filterIcon.className = "btn btn-sm btn-outline-secondary";
3823 }
3824 else
3825 {
3826 img.src = "./Icons/funnel-fill_white.svg";
3827 filterIcon.className = "btn btn-sm btn-outline-warning";
3828 }
3829 filterIcon.appendChild(img);
3830 filterIcon.onclick = function (e) {
3831 node.showDefaultValueInputs = !node.showDefaultValueInputs;
3832 MxShadingGraphEditor.theEditor.updatePropertyPanel(node);
3833 }
3834 filterCol.appendChild(filterIcon);
3835 elem.appendChild(filterCol);
3836 }
3837
3838 propertypanelcontent.appendChild(elem);
3839
3840 var hr = document.createElement("hr");
3841 hr.classList.add("my-1");
3842 propertypanelcontent.appendChild(hr);
3843
3844 var current_details = null;
3845 var first_details = true;
3846 var nodeInputs = node.inputs
3847
3848 let targetNodes = [];
3849 for (var i in nodeInputs) {
3850 let nodeInput = nodeInputs[i];
3851
3852 let inputName = nodeInput.name;
3853 let nodeInputLink = nodeInput.link;
3854 let uiName = inputName;
3855 // remove "_" from uiName
3856 uiName = uiName.replace(/_/g, ' ');
3857 let uimin = null;
3858 let uimax = null;
3859 let colorspace = '';
3860 let units = '';
3861 let defaultgeomprop = '';
3862
3863 //console.log('Scan input:', inputName, ' on node: ', node.graph);
3864
3865 let property_info = node.getPropertyInfo(inputName);
3866 //console.log('1. get property info for i: ', inputName, 'info: ', property_info)
3867
3868 var skipInterorConnectedInput = false;
3869 if (node.graph._is_subgraph) {
3870 // Find input on subgraph node
3871 //console.log('Check subgraph for link:', node.graph)
3872 var sg_node = node.graph._subgraph_node;
3873 if (sg_node) {
3874 //console.log('Check for input on sg node', sg_node, node.title);
3875 var slot = sg_node.findInputSlot(node.title);
3876 if (slot != null) {
3877 if (sg_node.inputs) {
3878 //property_info = sg_node.properties_info[slot];
3879 var slotInput = sg_node.inputs[slot];
3880 //console.log('check slot: ', slotInput.link);
3881 if (slotInput != null && slotInput.link != null) {
3882 skipInterorConnectedInput = true;
3883 }
3884 }
3885 else {
3886 //console.log('Error: no subgraph node inputs for subgraph input!', sg_node, node.title);
3887 }
3888 }
3889 }
3890 }
3891
3892 if (skipInterorConnectedInput) {
3893 console.log('Skip interior connected input: ', nodeInput);
3894 continue;
3895 }
3896
3897 //console.log('Property info:', property_info, ' for input:', inputName);
3898 if (property_info) {
3899 if (property_info.defaultgeomprop)
3900 {
3901 defaultgeomprop = property_info.defaultgeomprop;
3902 }
3903 if (property_info.colorspace) {
3904 colorspace = property_info.colorspace;
3905 }
3906 if (property_info.unit) {
3907 units = property_info.unit;
3908 }
3909 if (property_info.uiname) {
3910 uiName = property_info.uiname;
3911 }
3912 if (property_info.uimin) {
3913 uimin = property_info.uimin;
3914 }
3915 if (property_info.uimax) {
3916 uimax = property_info.uimax;
3917 }
3918 if (property_info.uifolder && property_info.uifolder.length > 0) {
3919 // Create a details element
3920 if (current_details == null || current_details.id != property_info.uifolder) {
3921 //console.log('Create new details:', property_info.uifolder);
3922 current_details = document.createElement("details");
3923 current_details.id = property_info.uifolder;
3924 current_details.open = first_details;
3925 current_details.classList.add('w-100', 'p-1', 'border', 'border-secondary', 'rounded', 'my-1');
3926 first_details = false;
3927 var summary = document.createElement('summary')
3928 summary.style = this.fontSizeStyle;
3929 summary.innerHTML = "<b>" + property_info.uifolder + "</b>"
3930 //summary.classList.add('btn', 'btn-sm', 'btn-outline-secondary', 'btn-block');
3931 current_details.appendChild(summary);
3932
3933 }
3934 else {
3935 //current_details = null;
3936 }
3937 }
3938 else {
3939 current_details = null;
3940 }
3941 //console.log('2. uiName:', uiName, 'uimin:', uimin, 'uimax:', uimax, 'uiFolder:', property_info.uifolder);
3942 }
3943 else {
3944 current_details = null;
3945 }
3946
3947 var elem = null;
3948
3949 // Check if there is a link
3950 if (nodeInputLink) {
3951 let upstreamLink = null;
3952
3953 let nodegraph = node.graph;
3954 let link = nodegraph.links[nodeInputLink];
3955 //console.log('link:', link);
3956 let linkId = link && link.origin_id;
3957 let linkNode = linkId && nodegraph.getNodeById(linkId);
3958 if (linkNode) {
3959
3960
3961 //console.log('linkNode:', linkNode);`
3962 let linkSlot = link.origin_slot;
3963 //console.log('linkSlot:', linkSlot);
3964 let linkOutput = linkNode.outputs[linkSlot];
3965 //console.log('linkOutput:', linkOutput);
3966 upstreamLink = linkNode.title + '.' + linkOutput.name;
3967 //console.log('upstreamLink:', upstreamLink);
3968
3969 let id = "__pp:" + inputName;
3970 let buttonText = upstreamLink;
3971 // Truncate long names
3972 if (buttonText.length > 15) {
3973 buttonText = buttonText.substring(0, 15) + "...";
3974 }
3975 let input = this.createButtonWithImageAndText("./Icons/arrow_up_white.svg", buttonText, id);
3976
3977 input.onclick = function (e) {
3978
3979 var inputName = e.target.id;
3980 inputName = inputName.replace('__pp:', '');
3981 inputName = inputName.replace('_text', '');
3982 inputName = inputName.replace('_img', '');
3983 console.log('Clicked traversal button:', inputName);
3984
3985 console.log('Jump to node:', linkNode.title);
3986 graphcanvas.selectNodes([linkNode]);
3987 MxShadingGraphEditor.theEditor.updatePropertyPanel(linkNode);
3988 node.setDirtyCanvas(true, true);
3989 }
3990
3991 // Add new row
3992 elem = document.createElement("div");
3993 elem.className = "row px-1 py-0";
3994
3995 input.id = "__pp:" + inputName;
3996
3997 var label = document.createElement("div");
3998 // invert-button
3999 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4000 label.style = this.fontSizeStyle;
4001 label.innerHTML = uiName;
4002 label.for = input.id;
4003 elem.appendChild(label);
4004
4005 // form-control
4006 if (useFormControl) {
4007 input.classList.add("form-control");
4008 }
4009 input.classList.add("form-control-sm");
4010 // Disable if don't want interaction.
4011 if (!graphcanvas.allow_interaction)
4012 input.disabled = true;
4013
4014 var propvalue = document.createElement("div");
4015 propvalue.className = "col p-1";
4016 propvalue.appendChild(input);
4017
4018 elem.appendChild(propvalue);
4019 }
4020 }
4021
4022 else {
4023
4024 targetNodes[i] = node;
4025 let targetNode = targetNodes[i];
4026 let propertyKey = inputName;
4027
4028 var property = targetNode.properties[inputName];
4029 if (property == null) {
4030 if (isNodeGraph) {
4031 var subgraph = targetNode.subgraph;
4032 if (subgraph) {
4033 //console.log('Find node by title', inputName, ' in subgraph', subgraph._nodes);
4034 var subNode = subgraph.findNodeByTitle(inputName);
4035 if (subNode) {
4036 targetNodes[i] = subNode;
4037 propertyKey = 'in';
4038 property = targetNodes[i].properties['in'];
4039 //console.log('Route to subgraph target node:', targetNode, targetNode.title, '. ', inputName, ' = ', JSON.stringify(property), 'propkey=', propertyKey);
4040 }
4041 }
4042 }
4043 if (property == null) {
4044 console.log('Update: Cannot find property value for input:', inputName);
4045 continue;
4046 }
4047 }
4048
4049 // Check if there is a default property value. If so skip showing it
4050 if (defaultgeomprop)
4051 {
4052 //console.log('Skip input with defaultgeomprop: ' + inputName);
4053 continue;
4054 }
4055
4056 // Check if property value is same as property info default value
4057 if (!node.showDefaultValueInputs && !isNodeGraph)
4058 {
4059 let isDefault = node.isDefaultValue(inputName);
4060 if (isDefault)
4061 {
4062 continue;
4063 }
4064 }
4065
4066 // Add new row
4067 elem = document.createElement("div");
4068 elem.className = "row px-1 py-0";
4069
4070 var input = null;
4071 var input_btn = null;
4072 let input_slider = null;
4073 var colorspace_unit_btn = null;
4074 var useFormControl = true;
4075
4076 // Add colorspace drop-down if specified.
4077 if (colorspace.length > 0) {
4078 // Create drop-down menu to choose colorspace from list stored
4079 // in the handler class getColorSpaces() method.
4080 //
4081 colorspace_unit_btn = this.createColorSpaceInput(colorSpaces, colorspace);
4082 let theNode = targetNodes[i];
4083 colorspace_unit_btn.onchange = function (e) {
4084
4085 theNode.setPropertyInfo(inputName, 'colorspace', e.target.value);
4086 }
4087 }
4088 else if (units.length > 0 && property_info.unittype) {
4089 // Add units drop-down if specified.
4090 colorspace_unit_btn = this.createUnitsInput(targetUnits, property_info.unittype, units);
4091 let theNode = targetNodes[i];
4092 colorspace_unit_btn.onchange = function (e) {
4093 theNode.setPropertyInfo(inputName, 'unit', e.target.value);
4094 }
4095 }
4096
4097 var proptype = nodeInput.type;
4098 if (proptype == 'float' || proptype == 'integer') {
4099 var isFloat = proptype == 'float';
4100
4101 input = document.createElement("input");
4102 input.id = propertyKey + '_box';
4103 input.style = this.fontSizeStyle;
4104 input.type = 'number';
4105 input.classList.add("form-control", "form-control-sm", "ps-0");
4106 input.setAttribute('propertyKey', propertyKey);
4107
4108 input_slider = document.createElement("input");
4109 input_slider.id = propertyKey + '_slider';
4110 //input_slider.style = this.fontSizeStyle;
4111 input_slider.type = 'range';
4112 input_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4113 input_slider.setAttribute('propertyKey', propertyKey);
4114
4115 if (uimin) {
4116 input.min = uimin;
4117 }
4118 else {
4119 input.min = Math.min(property, 0);
4120 }
4121 if (uimax) {
4122 input.max = uimax;
4123 }
4124 else {
4125 if (isFloat)
4126 {
4127 input.max = Math.max(property*3, 10.0);
4128 }
4129 else {
4130 input.max = Math.max(property*3, 100);
4131 }
4132 }
4133
4134
4135 input_slider.min = input.min;
4136 input_slider.max = input.max;
4137 if (isFloat) {
4138 input.step = (input.max - input.min) / 100.0;
4139 input_slider.step = input.step;
4140 }
4141 else {
4142 input_slider.step = 1;
4143 input.step = 1;
4144 }
4145
4146 input.value = input_slider.value = property;
4147
4148 /* console.log('> ' + propertyKey + ' - Set up slider: min, max, value',
4149 input_slider.min, input_slider.max, input_slider.value
4150 );
4151 console.log('> ' + propertyKey + ' - Set up box: min, max, value',
4152 input.min, input.max, input.value
4153 ); */
4154
4155 let theBox = input;
4156 let theSlider = input_slider;
4157 let theNode = targetNodes[i];
4158 input_slider.onchange = function (e) {
4159 var pi = e.target.getAttribute('propertyKey');
4160 var val = parseFloat(e.target.value);
4161 theNode.setProperty(pi, val);
4162 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4163 }
4164 input_slider.oninput = function(e) {
4165 var pi = e.target.getAttribute('propertyKey');
4166 var val = parseFloat(e.target.value);
4167 theNode.setProperty(pi, val);
4168 theBox.value = e.target.value;
4169 }
4170
4171 input.onchange = function (e) {
4172 var pi = e.target.getAttribute('propertyKey');
4173 var val = parseFloat(e.target.value);
4174 theNode.setProperty(pi, val);
4175 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4176 }
4177 input.oninput = function(e) {
4178 var pi = e.target.getAttribute('propertyKey');
4179 var val = parseFloat(e.target.value);
4180 theNode.setProperty(pi, val);
4181 theSlider.value = e.target.value;
4182 }
4183 }
4184 else if (proptype == 'string' || proptype == 'filename') {
4185 input = document.createElement("input");
4186 input.style = this.fontSizeStyle;
4187 input.type = "text";
4188 if (proptype == 'filename') {
4189 var curImage = property;
4190 this.updateImagePreview(curImage);
4191
4192 input_btn = document.createElement("button");
4193 input_btn.classList.add("btn", "btn-sm", "btn-outline-secondary");
4194 input_btn.innerHTML = "+";
4195 input_btn.setAttribute('propertyKey', propertyKey);
4196 var fileId = "__pp:" + inputName;
4197 let theNode = targetNodes[i];
4198 input_btn.onclick = function (e) {
4199 var pi = e.target.getAttribute('propertyKey');
4200 MxShadingGraphEditor.theEditor.openImageDialog(theNode, pi, false);
4201 }
4202 }
4203
4204 else
4205 {
4206 // Check if there is a 'enm' property
4207 console.log('------------------- handle enum property info:', property_info, '. property:', property);
4208 if (property_info && property_info.enum) {
4209
4210 console.log('----------------- found enum property info:', property_info.enum);
4211
4212 // Create drop-down menu to choose from list stored in the handler class.
4213 input = document.createElement("select");
4214 input.style = this.fontSizeStyle;
4215 input.classList.add("form-control", "form-control-sm");
4216
4217 input.setAttribute('propertyKey', propertyKey);
4218 let theNode = targetNodes[i];
4219 let enums = property_info.enum;
4220 for (let j = 0; j < enums.length; j++) {
4221 let option = document.createElement("option");
4222 option.value = enums[j];
4223 option.text = enums[j];
4224 input.add(option);
4225 }
4226 input.value = property;
4227 input.setAttribute('propertyKey', propertyKey);
4228 input.onchange = function (e) {
4229 var pi = e.target.getAttribute('propertyKey');
4230 theNode.setProperty(pi, e.target.value);
4231 //console.log('Update string property:', pi, theNode.properties[pi])
4232 }
4233 }
4234 }
4235
4236 if (property_info && !property_info.enm) {
4237 input.value = property;
4238 input.setAttribute('propertyKey', propertyKey);
4239 let theNode = targetNodes[i];
4240 let isFilename = proptype == 'filename';
4241 let that = this;
4242 input.onchange = function (e) {
4243 var pi = e.target.getAttribute('propertyKey');
4244 //theNode.properties[pi] = e.target.value;
4245 theNode.setProperty(pi, e.target.value);
4246 if (isFilename) {
4247 //console.log('Update filename property:', pi, theNode.properties[pi])
4248 that.updateImagePreview(e.target.value);
4249 }
4250 else {
4251 //console.log('Update string property:', pi, theNode.properties[pi])
4252 }
4253 }
4254 }
4255 }
4256 else if (proptype == 'boolean') {
4257 //console.log('Add Boolean property:', property);
4258 input = document.createElement("input");
4259 input.style = this.fontSizeStyle;
4260 input.type = "checkbox";
4261 input.classList = "form-check-input";
4262 useFormControl = false;
4263 input.checked = property;
4264 input.setAttribute('propertyKey', propertyKey);
4265 let theNode = targetNodes[i];
4266 input.onchange = function (e) {
4267 var pi = e.target.getAttribute('propertyKey');
4268 //theNode.properties[pi] = e.target.checked;
4269 theNode.setProperty(pi, e.target.checked);
4270 //console.log('Update boolean property:', pi, theNode.properties[pi]);
4271 }
4272 }
4273
4274 else if (proptype == 'vector2' || proptype == 'vector3' || proptype == 'vector4')
4275 {
4276 // Find index of proptype in ['vector2', 'vector3', 'vector4' ]
4277 var vector_size = ['vector2', 'vector3', 'vector4'].indexOf(proptype) + 2;
4278 input = document.createElement("div");
4279 useFormControl = false;
4280
4281 input.className = "row py-1 ps-4 pe-0";
4282
4283 for (let v=0; v<vector_size; v++)
4284 {
4285 //console.log('Vector property:[', 0, '] = ', property[0], proptype)
4286 let subinput = document.createElement("input");
4287 subinput.style = this.fontSizeStyle;
4288 subinput.type = 'number';
4289 subinput.classList.add("form-control");
4290 subinput.classList.add("form-control-sm");
4291 subinput.setAttribute('propertyKey', propertyKey);
4292
4293 let subinput_slider = document.createElement("input");
4294 subinput_slider.id = propertyKey + '_slider';
4295 subinput_slider.type = 'range';
4296 subinput_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4297 subinput_slider.setAttribute('propertyKey', propertyKey);
4298
4299 if (uimin) {
4300 subinput.min = uimin[v];
4301 }
4302 else {
4303 subinput.min = Math.min(property[v]*3, 0);
4304 }
4305 if (uimax) {
4306 subinput.max = uimax[v];
4307 }
4308 else {
4309 subinput.max = Math.max(property[v]*3, 10.0);
4310 }
4311
4312 subinput_slider.min = subinput.min;
4313 subinput_slider.max = subinput.max;
4314 subinput.step = (subinput.max - subinput.min) / 100.0;
4315 subinput_slider.step = subinput.step;
4316
4317 subinput.value = subinput_slider.value = property[v];
4318
4319 let theNode = targetNodes[i];
4320 let vector_index = v;
4321 let theBox = subinput;
4322 let theSlider = subinput_slider;
4323 theBox.onchange = function (e) {
4324 let pi = e.target.getAttribute('propertyKey');
4325 let value = parseFloat(e.target.value);
4326 let newValue = theNode.properties[pi].map(item => item);
4327 newValue[vector_index] = value;
4328 theNode.setProperty(pi, newValue);
4329 //theNode.properties[pi][0] = value;
4330 //console.log('Update Vector property:"', pi, '"', 0, parseFloat(e.target.value), theNode.properties[pi])
4331 }
4332 theBox.oninput = function(e) {
4333 let pi = e.target.getAttribute('propertyKey');
4334 let value = parseFloat(e.target.value);
4335 let newValue = theNode.properties[pi].map(item => item);
4336 newValue[vector_index] = value;
4337 theNode.setProperty(pi, newValue);
4338 theSlider.value = e.target.value;
4339 }
4340
4341 theSlider.onchange = function (e) {
4342 let pi = e.target.getAttribute('propertyKey');
4343 let value = parseFloat(e.target.value);
4344 let newValue = theNode.properties[pi].map(item => item);
4345 newValue[vector_index] = value;
4346 theNode.setProperty(pi, newValue);
4347 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4348 }
4349 theSlider.oninput = function(e) {
4350 let pi = e.target.getAttribute('propertyKey');
4351 let value = parseFloat(e.target.value);
4352 let newValue = theNode.properties[pi].map(item => item);
4353 newValue[vector_index] = value;
4354 theNode.setProperty(pi, newValue);
4355 theBox.value = e.target.value;
4356 }
4357
4358
4359 let propvalue_slider = document.createElement("div");
4360 propvalue_slider.className = "col p-0";
4361 propvalue_slider.appendChild(subinput_slider);
4362
4363 let propvalue_box = document.createElement("div");
4364 propvalue_box.className = "col p-0";
4365 propvalue_box.appendChild(subinput);
4366
4367 let input_row = document.createElement("div");
4368 input_row.className = "row p-0";
4369 input_row.appendChild(propvalue_slider);
4370 input_row.appendChild(propvalue_box);
4371
4372 input.appendChild(input_row);
4373 }
4374 }
4375 else if (proptype == 'color3' || proptype == 'color4') {
4376 input = document.createElement("input");
4377 input.type = "color";
4378 //console.log('set color property:', rgbToHex(property));
4379 input.value = this.rgbToHex(property);
4380 input.setAttribute('propertyKey', propertyKey);
4381 let theNode = targetNodes[i];
4382 input.onchange = function (e) {
4383 // Convert hex to rgb in 0..1 range
4384 var hex = e.target.value;
4385 let fprecision = 4
4386 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4387 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4388 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4389 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4390 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4391 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4392
4393 var pi = e.target.getAttribute('propertyKey');
4394 theNode.setProperty(pi, rgb);
4395 }
4396 let func = function (e) {
4397 // Convert hex to rgb in 0..1 range
4398 var hex = e.target.value;
4399 var rgb = [0, 0, 0];
4400 let fprecision = 4
4401 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4402 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4403 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4404 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4405 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4406 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4407
4408 var pi = e.target.getAttribute('propertyKey');
4409 theNode.setProperty(pi, rgb);
4410 }
4411 input.onchange = func;
4412 input.oninput = func;
4413 }
4414 else {
4415 input = document.createElement("input");
4416 input.style = this.fontSizeStyle;
4417 input.type = "text";
4418 input.value = property;
4419 let propertyKey = inputName;
4420 let theNode = targetNodes[i];
4421 input.onchange = function (e) {
4422 theNode.setProperty(propertyKey, e.target.value);
4423 //theNode.properties[propertyKey] = e.target.value;
4424 }
4425 }
4426
4427 if (input) {
4428 input.id = "__pp:" + inputName;
4429 //console.log('> Add input:', input.id);
4430
4431 var label = document.createElement("div");
4432 label.className = "col-4 p-0 col-form-label-sm text-end";
4433 label.style = this.fontSizeStyle;
4434 label.innerHTML = uiName;
4435 label.for = input.id;
4436 elem.appendChild(label);
4437
4438 // form-control
4439 if (useFormControl) {
4440 input.classList.add("form-control");
4441 }
4442 input.classList.add("form-control-sm");
4443 // Disable if don't want interaction.
4444 if (!graphcanvas.allow_interaction)
4445 input.disabled = true;
4446
4447 var propvalue = document.createElement("div");
4448 propvalue.className = "col py-0";
4449 if (input_slider)
4450 {
4451 propvalue.classList.add('ps-1');
4452 }
4453 propvalue.appendChild(input);
4454
4455 if (input_btn) {
4456 var propbutton = document.createElement("div");
4457 propbutton.className = "col-2 py-0";
4458 //console.log('Add input button:', input_btn);
4459 propbutton.appendChild(input_btn);
4460 elem.appendChild(propbutton);
4461 }
4462 if (colorspace_unit_btn) {
4463 //console.log('Add cs / unit button:', input_btn);
4464 var propbutton = document.createElement("div");
4465 propbutton.className = "col col-form-label-sm";
4466 var details = document.createElement("details");
4467 var summary = document.createElement('summary')
4468 summary.style = this.fontSizeStyle;
4469 if (colorspace.length > 0)
4470 summary.innerHTML = "Colorspace";
4471 else if (targetUnits.length > 0)
4472 summary.innerHTML = "Units";
4473 details.appendChild(summary);
4474 details.appendChild(colorspace_unit_btn);
4475 propbutton.appendChild(details);
4476 propvalue.appendChild(propbutton);
4477 }
4478
4479 if (input_slider)
4480 {
4481 var propvalue_slider = document.createElement("div");
4482 propvalue_slider.className = "col py-0 pe-0";
4483 propvalue_slider.appendChild(input_slider);
4484 elem.appendChild(propvalue_slider);
4485 }
4486
4487 elem.appendChild(propvalue);
4488 }
4489 }
4490 //elem.innerHTML = "<em>" + i + "</em> : " + property;
4491 if (elem) {
4492 if (current_details) {
4493 //console.log('3a. append child to details:', current_details.id, elem, inputName);
4494 current_details.appendChild(elem);
4495 // It current_details not already in the propertypanelcontent, add it.
4496 if (current_details.parentElement == null) {
4497 propertypanelcontent.appendChild(current_details);
4498 }
4499 }
4500 else {
4501 propertypanelcontent.appendChild(elem);
4502 //console.log('3b. append child to parent content:', elem, inputName);
4503 }
4504 }
4505 }
4506
4507 }
4508
4509
4517 initializeLiteGraph(canvas, readOnly = false) {
4518 // Initialize LiteGraph
4519 graph = new LiteGraph.LGraph();
4520 graphcanvas = new LiteGraph.LGraphCanvas(canvas, graph);
4521
4522 if (readOnly)
4523 {
4524 // Put red border around canvas as an indicator
4525 //canvas.style.border = "1px solid #ff1107";
4526 }
4527
4528 //
4529 // Set up graph overrides
4530 //
4531
4532 // Set up connection colors (off = not connected, on = connected)
4533 // TODO: Move this to application site and expose settings to user.
4534 graphcanvas.default_connection_color_byTypeOff = {
4535 integer: "#A32",
4536 float: "#161",
4537 vector2: "#265",
4538 vector3: "#465",
4539 vector4: "#275",
4540 color3: "#37A",
4541 color4: "#69A",
4542 matrix33: "#555",
4543 matrix44: "#666",
4544 string: "#395",
4545 filename: "#888",
4546 boolean: "#060",
4547 };
4548
4549 graphcanvas.default_connection_color_byType = {
4550 integer: "#D52",
4551 float: "#1D1",
4552 vector2: "#4D4",
4553 vector3: "#7D7",
4554 vector4: "#9D9",
4555 color3: "#4AF",
4556 color4: "#6CF",
4557 matrix33: "#AAA",
4558 matrix44: "#BBB",
4559 string: "#3F4",
4560 filename: "#FFF",
4561 boolean: "#0F0",
4562 };
4563
4564 // Making this a no-op as will not use the default panel
4565 graphcanvas.onShowNodePanel = function (node) {
4566 ;
4567 }
4568
4569 // Override to handle node selection
4570 graphcanvas.onNodeSelected = function (node) {
4571 if (MxShadingGraphEditor.theEditor.monitor)
4572 {
4573 let parentGraph = '';
4574 var is_subgraph = graphcanvas.graph._is_subgraph;
4575 if (is_subgraph)
4576 parentGraph = graphcanvas.graph._subgraph_node.title;
4577 MxShadingGraphEditor.theEditor.monitor.onNodeSelected(node, parentGraph);
4578 }
4579 MxShadingGraphEditor.theEditor.updatePropertyPanel(node);
4580 }
4581
4582 // Override to handle node deselection
4583 graphcanvas.onNodeDeselected = function (node) {
4584 if (MxShadingGraphEditor.theEditor.monitor)
4585 {
4586 let parentGraph = '';
4587 var is_subgraph = graphcanvas.graph._is_subgraph;
4588 if (is_subgraph)
4589 parentGraph = graphcanvas.graph._subgraph_node.title;
4590 MxShadingGraphEditor.theEditor.monitor.onNodeDeselected(node, parentGraph);
4591 }
4592 MxShadingGraphEditor.theEditor.updatePropertyPanel(null);
4593 }
4594
4595 // Add monitoring method for property info changes.
4596 // This API does not currently exist in LiteGraph, only getPropertyInfo() does.
4597 LGraphNode.prototype.setPropertyInfo = function(property, propertyInfo, value)
4598 {
4599 var info = null;
4600
4601 if (this.properties_info) {
4602 for (var i = 0; i < this.properties_info.length; ++i) {
4603 if (this.properties_info[i].name == property) {
4604 info = this.properties_info[i];
4605 break;
4606 }
4607 }
4608 }
4609
4610 if (info && info[propertyInfo])
4611 {
4612 if (this.onPropertyInfoChanged)
4613 {
4614 this.onPropertyInfoChanged(property, propertyInfo, value, info[propertyInfo]);
4615 }
4616 info[propertyInfo] = value;
4617 }
4618 else
4619 {
4620 console.warning('Failed to set property: ', property, '. info: ', propertyInfo, '. Value: ', value, '. Infos: ', this.properties_info);
4621 }
4622 }
4623
4624 // Add in a method to check if an input / property is the default value
4625 LGraphNode.prototype.isDefaultValue = function(property)
4626 {
4627 let info = null;
4628
4629 // Check if the property exists
4630 if (this.properties[property] == null)
4631 {
4632 console.warn('> Property value does not exist:', property);
4633 return false;
4634 }
4635 // Check if the property is linked
4636 if (this.getInputLink(property))
4637 {
4638 return false;
4639 }
4640
4641 if (this.properties_info != null)
4642 {
4643 for (let i = 0; i < this.properties_info.length; ++i) {
4644 if (this.properties_info[i].name == property) {
4645 info = this.properties_info[i];
4646 break;
4647 }
4648 }
4649 }
4650
4651 if (info != null && info.default_value != null)
4652 {
4653 let property_string = this.properties[property];
4654 let default_value_string = info.default_value;
4655 let isDefault = false;
4656 if (Array.isArray(default_value_string)) {
4657 default_value_string = default_value_string.map(String); // or .map(element => String(element))
4658 property_string = property_string.map(String); // or .map(element => String(element))
4659 isDefault = (JSON.stringify(default_value_string) == JSON.stringify(property_string));
4660 }
4661 else
4662 {
4663 isDefault = (default_value_string == property_string);
4664 }
4665 return isDefault;
4666 }
4667 else
4668 {
4669 console.warn('> Default value does not exist for:', property);
4670 }
4671 return false;
4672 }
4673
4674 //
4675 // Set up graph
4676 //
4677 graphcanvas.resize();
4678 this.monitor.monitorGraph(graph, true);
4679 graph.arrange(80);
4680
4681 // Run the graph. TODO: Add execution control.
4682 //graph.runStep();
4683
4684 // Override global options
4685 //graphcanvas.hide_unconnected = false;
4686 console.log('> Read only mode: ', readOnly);
4687 graphcanvas.read_only = readOnly;
4688 graphcanvas.allow_interaction = true; // Allow user interaction. TODO: Add option to turn this off
4689 graphcanvas.read_only_select = true; // Allow selection in read-only mode
4690 graphcanvas.allow_dragnodes = !readOnly; // Allow dragging nodes
4691 graphcanvas.allow_searchbox = !readOnly; // Allow search box
4692 graphcanvas.render_connections_arrows = true; // Render connection arrows
4693 graphcanvas.clear_background_color = "#222223"; // Set background color
4694 graphcanvas.max_zoom = 0.15; // Set maximum zoom level
4695 graphcanvas.connections_width = 2; // Set connection width
4696 graphcanvas.render_canvas_border = false; // Set canvas border
4697 graphcanvas.align_to_grid = false; // Align to grid
4698 graphcanvas.render_connection_arrows = false; // Render connection arrows
4699 graphcanvas.render_curved_connections = true; // Render curved connections
4700 //graphcanvas.background_image = null; // Set background image
4701 graphcanvas.show_info = false; // Turn off HUD
4702 graph.ctrl_shift_v_paste_connect_unselected_outputs = true;
4703
4704 //
4705 // Event handler overrides. TODO: Add more shortcuts
4706 //
4707 // Ad event handler to call centerOnNode with f key press within the canvas area
4708 canvas.addEventListener("keydown", function (e) {
4709 if (e.key === "f") {
4710 MxShadingGraphEditor.theEditor.centerNode();
4711 }
4712 });
4713
4714 // Ad event handler to call array with l key press within the canvas area
4715 canvas.addEventListener("keydown", function (e) {
4716 if (e.key === "l") {
4717 MxShadingGraphEditor.theEditor.arrangeGraph();
4718 }
4719 });
4720
4721
4722 var isIdle = true;
4723 var context = canvas.getContext('2d');
4724
4725 function drawstart(event) {
4726 //context.beginPath();
4727 //context.moveTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
4728 console.log('>>>>>>>>>>> draw start');
4729 isIdle = false;
4730 }
4731
4732 function drawmove(event) {
4733 if (isIdle) return;
4734 //context.lineTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
4735 //context.stroke();
4736 console.log('>>>>>>>>>>> draw move');
4737 }
4738
4739 function drawend(event) {
4740 if (isIdle) return;
4741 drawmove(event);
4742 console.log('>>>>>>>>>>> draw move');
4743 isIdle = true;
4744 }
4745
4746 function touchstart(event) {
4747 drawstart(event.touches[0]);
4748 }
4749
4750 function touchmove(event) {
4751 drawmove(event.touches[0]);
4752 //event.preventDefault();
4753 }
4754
4755 function touchend(event) {
4756 drawend(event.changedTouches[0]);
4757 }
4758
4759 //canvas.addEventListener('touchstart', touchstart, false);
4760 //canvas.addEventListener('touchmove', touchmove, false);
4761 //canvas.addEventListener('touchend', touchend, false);
4762
4763 //canvas.addEventListener('mousedown', drawstart, false);
4764 //canvas.addEventListener('mousemove', drawmove, false);
4765 //canvas.addEventListener('mouseup', drawend, false);
4766
4767 }
4768
4772 centerNode() {
4773 var selected = graphcanvas.selected_nodes;
4774 var haveSelected = false;
4775 for (var s in selected) {
4776 haveSelected = true;
4777 break;
4778 }
4779 console.log('Center nodes:', selected, '. Have selected:', haveSelected);
4780 graphcanvas.centerOnGraph(haveSelected);
4781 }
4782
4786 clearNodeTypes() {
4787 LiteGraph.searchbox_extras = [];
4788 var nodeTypes = LiteGraph.registered_node_types;
4789 for (var typeName in nodeTypes) {
4790 if (typeName !== "graph/subgraph") {
4791 console.log('Removing node type:', LiteGraph.getNodeType(typeName));
4792 LiteGraph.unregisterNodeType(typeName);
4793 }
4794 }
4795 }
4796
4800 collapseNode(node, collapse) {
4801 if (node.constructor.collapsable === false) {
4802 return false;
4803 }
4804 if (node.flags.collapsed != collapse) {
4805 node.flags.collapsed = collapse;
4806 return true;
4807 }
4808 return false;
4809 }
4810
4814 collapseExpandNodes(collapse) {
4815 var curGraph = graphcanvas.graph;
4816
4817 var selected_nodes = graphcanvas.selected_nodes;
4818 //console.log('Selected nodes:', selected_nodes);
4819 var modified = false;
4820 if (selected_nodes) {
4821 for (var i in selected_nodes) {
4822 var node = selected_nodes[i];
4823 //console.log('Collapse/Expand:', node.title, collapse);
4824 if (this.collapseNode(node, collapse))
4825 modified = true;
4826 }
4827 }
4828 if (!modified) {
4829 var nodes = curGraph._nodes;
4830 for (var i in nodes) {
4831 var node = nodes[i];
4832 if (this.collapseNode(node, collapse))
4833 modified = true;
4834 }
4835 }
4836
4837 if (modified) {
4838 graph._version++;
4839 graph.setDirtyCanvas(true, true);
4840 }
4841 }
4842
4846 copyToClipboard() {
4847 graphcanvas.copyToClipboard();
4848 }
4849
4853 pasteFromClipboard() {
4854 graphcanvas.pasteFromClipboard(true);
4855 }
4856
4860 extractNodeGraph() {
4861 var selected = graphcanvas.selected_nodes;
4862 if (selected.length == 0) {
4863 console.log('No nodes selected.');
4864 return;
4865 }
4866
4867 var subgraphsSelected = []
4868 for (var i in selected) {
4869 var node = selected[i];
4870 if (node.type == 'graph/subgraph') {
4871 subgraphsSelected.push(node);
4872 }
4873 }
4874 if (subgraphsSelected.length == 0) {
4875 console.log('No subgraphs selected.');
4876 return;
4877 }
4878
4879 // Select subgraph nodes
4880 var subGraph = subgraphsSelected[0];
4881 var subGraphNodes = subGraph.subgraph._nodes;
4882 for (var i in subGraphNodes) {
4883 var node = subGraphNodes[i];
4884 //console.log('Select subgraph node:', node.title);
4885 }
4886
4887 graphcanvas.openSubgraph(subGraph.subgraph);
4888 graphcanvas.selectNodes(subGraphNodes);
4889 // Copy the selected nodes to the clipboard
4890 graphcanvas.copyToClipboard();
4891
4892 // Paste the copied nodes into the graph
4893 graphcanvas.closeSubgraph();
4894 graphcanvas.pasteFromClipboard();
4895 }
4896
4901 createNodeGraph() {
4902 // Disallow testing for now.
4903 if (graphcanvas.graph._is_subgraph) {
4904 this.debugOutput('Cannot create nest subgraphs.', 1);
4905 return;
4906 }
4907
4908 // Check for selected nodes
4909 var selected = graphcanvas.selected_nodes;
4910 if (selected.length == 0) {
4911 console.log('No nodes selected.');
4912 return;
4913 }
4914
4915 // Copy the selected nodes to the clipboard
4916 graphcanvas.copyToClipboard();
4917
4918 // Create a new graph/subgraph node
4919 var node = LiteGraph.createNode('graph/subgraph');
4920 graph.add(node);
4921 node.title = MxShadingGraphEditor.theEditor.handler.createValidName('group');
4922 // Open subgraph
4923 graphcanvas.openSubgraph(node.subgraph);
4924 // Paste the copied nodes into the subgraph
4925 graphcanvas.pasteFromClipboard();
4926
4927 node.subgraph.arrange(80);
4928 graphcanvas.ds.reset();
4929 graphcanvas.setDirty(true, true);
4930 }
4931
4935 displayNodeTypes() {
4936 // Get the node list display updater
4937 var nodeTypesListUpdater = this.ui.nodeTypesListUpdater;
4938 if (!nodeTypesListUpdater) {
4939 return;
4940 }
4941
4942 // Get the list of available node types
4943 var nodeTypes = LiteGraph.registered_node_types;
4944 nodeTypesListUpdater(nodeTypes);
4945 }
4946
4956 initialize(canvas, ui, monitor, materialFilename, readOnly = false) {
4957
4958 this.setUI(ui);
4959 if (monitor) {
4960 console.log('Set custom monitor:', monitor.getName());
4961 }
4962 this.monitor = monitor;
4963 this.initializeLiteGraph(canvas, readOnly);
4964 this.handler.initialize(MxShadingGraphEditor.theEditor, materialFilename);
4965 this.handler.setMonitor(this.monitor);
4966 }
4967}
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.
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 ...
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.
findRenderableItemsInDoc(mdoc)
Find all renderable items in the MaterialX document.
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.
loadFromString(extension, fileContents, fileName, auto_arrange)
Load graph editor from a string.
createValidName(name, msg=null)
Create a valid MaterialX name within the context of the current graph.
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.