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' || extension == 'zip')
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 let doc_string = ne_mx.writeToXmlString(outputDoc);
1816 //console.log(doc_string);
1817 //console.log('-----------------------------------------------')
1818
1819 if (writeCustomLibs) {
1820 console.log('Write custom libraries:', customlibs.length);
1821 for (var customlib of customlibs) {
1822 outputDoc.copyContentFrom(customlib[1]);
1823 }
1824 console.log('Write document custom definitions:', customDocLibs.length);
1825 for (var customDocLib of customDocLibs) {
1826 outputDoc.copyContentFrom(customDocLib[1]);
1827 }
1828 }
1829
1830 // TODO: Add in other globals
1831 outputDoc.setColorSpace(this.getSourceColorSpace());
1832 outputDoc.removeAttribute('fileprefix');
1833
1834 this.validateDocument(outputDoc);
1835
1836 return outputDoc;
1837 }
1838
1847 saveGraphToString(extension, graph, graphWriteOptions) {
1848
1849 if (!ne_mx) {
1850 this.editor.debugOutput("MaterialX is not initialized", 2);
1851 return ['', 'MaterialX is not initialized'];
1852 }
1853
1854 var outputDoc = this.saveGraphToDocument(graph, graphWriteOptions);
1855 if (!outputDoc) {
1856 this.editor.debugOutput("Failed to save graph to document", 2);
1857 return ['', 'Failed to save graph to document'];
1858 }
1859
1860 if (extension == 'mtlx')
1861 {
1862 const writeOptions = new ne_mx.XmlWriteOptions();
1863 writeOptions.writeXIncludeEnable = false;
1864 var data = '';
1865 try {
1866 data = ne_mx.writeToXmlString(outputDoc, writeOptions);
1867 } catch (e) {
1868 this.editor.debugOutput("Failed to write graph:" + e, 2);
1869 }
1870 return [data, ''];
1871 }
1872
1873 // Look for a registered exporter
1874 else
1875 {
1876 let exporter = this.getExporter(extension);
1877 if (!exporter) {
1878 this.editor.debugOutput('Failed to find ' + extension + ' exporter', 2);
1879 }
1880 else {
1881 let exportDoc = ne_mx.createDocument();
1882 exportDoc.copyContentFrom(outputDoc);
1883 exportDoc.importLibrary(stdlib);
1884
1885 let result = exporter.export(ne_mx, exportDoc);
1886 return result;
1887 }
1888 }
1889 return ['', 'Failed to export graph to ' + extension];
1890 }
1891
1900 saveGraphToFile(extension, graph, graphWriteOptions)
1901 {
1902 var data = this.saveGraphToString(extension, graph, graphWriteOptions);
1903 if (!data[0]) {
1904 return;
1905 }
1906
1907 var blob = new Blob([data[0]], { type: "text/plain" });
1908 var url = URL.createObjectURL(blob);
1909 var a = document.createElement("a");
1910 a.href = url;
1911 a.download = "output_graph.mtlx";
1912 a.click();
1913 }
1914
1923 writeGraphToDocument(mltxgraph, graph, graphWriteOptions) {
1924
1925 var debug = false;
1926
1927 if (debug)
1928 console.log('***** START Scan Graph:', graph.title);
1929 for (var node of graph._nodes) {
1930 if (node.type == 'graph/subgraph') {
1931 var subgraph = node.subgraph;
1932 //var subgraphNode = mltxgraph.addNodeGraph(node.title);
1933 var subgraphNode = mltxgraph.addChildOfCategory('nodegraph', node.title);
1934 if (debug)
1935 console.log('---->>> Scan NodeGraph:', node.title);
1936 this.writeGraphToDocument(subgraphNode, subgraph, graphWriteOptions);
1937 continue;
1938 }
1939
1940 if (debug)
1941 console.log('---->>> Scan Node:', node.title);
1942
1943 var nodeDefName = node.nodedef_name;
1944 /* if (!nodeDefName)
1945 {
1946 this.editor.debugOutput('Failed to find nodeDef for:' + node.title, 1);
1947 continue;
1948 } */
1949
1950 //var nodeTypes = LiteGraph.registered_node_types;
1951 //var nodeType = nodeTypes[node.type];
1952 var nodedefName = node.nodedef_name;
1953 var nodedef = null;
1954 var nodeElement = null;
1955 //if (nodeType) {
1956 // nodedefName = nodeType.nodedef_name;
1957 // nodedef = stdlib.getNodeDef(nodedefName);
1958 //}
1959
1960 //if (nodedef) {
1961 // nodeElement = mltxgraph.addNodeInstance(nodedef, name)
1962 // nodeElement.setName(node.title);
1963 //}
1964 //else
1965 {
1966 if (nodedefName) {
1967 nodeElement = mltxgraph.addChildOfCategory(node.nodedef_node, node.nodedef_type);
1968 nodeElement.setType(node.nodedef_type);
1969
1970 if (graphWriteOptions.saveNodePositions) {
1971 // TODO: Get properly remapping for xpos, ypos.
1972 nodeElement.setAttribute('xpos', JSON.stringify(node.pos[0]));
1973 nodeElement.setAttribute('ypos', JSON.stringify(node.pos[1]));
1974 }
1975 if (debug)
1976 console.log('** Create node:', nodeElement.getNamePath(), nodeElement.getType());
1977 nodeElement.setName(node.title);
1978 }
1979 }
1980
1981 if (nodeElement) {
1982 if (debug)
1983 console.log('-> Write Node:', graph.title + '/' + node.title, ' --> ', nodeElement.getNamePath());
1984 }
1985 else {
1986 console.log('Skip writing :', node.title);
1987 //this.editor.debugOutput('No nodedef for:' + node.title + 'Nodetype: ' + node.type, 0);
1988 continue;
1989 }
1990
1991 var properties = node.properties;
1992
1993 var node_inputs = node.inputs;
1994 var isInputNode = false;
1995 var isOutputNode = false;
1996 if (nodeElement.getCategory() == 'input') {
1997 isInputNode = true;
1998 node_inputs = [node];
1999 }
2000 else if (nodeElement.getCategory() == 'output') {
2001 isOutputNode = true;
2002 node_inputs = [node];
2003 }
2004
2005 // Add all outputs if the type is multioutput, or user option set
2006 if (!isInputNode && !isOutputNode)
2007 {
2008 if (node.nodedef_type == "multioutput")
2009 {
2010 console.log('Write outputs for:', node, '. type: ', node.nodedef_type);
2011 for (var output of node.outputs) {
2012 var outputName = output.name;
2013 var outputType = output.type;
2014 var outputElement = nodeElement.addOutput(outputName, outputType);
2015 if (debug) {
2016 console.log('> Read: node.nodedef_type: ', node.nodedef_type);
2017 console.log('> Write: output:', outputElement.getNamePath(), outputElement.getType());
2018 }
2019 }
2020 }
2021 }
2022
2023 // Add inputs
2024 if (node_inputs) {
2025
2026 var inputs = node_inputs;
2027 for (var i in inputs) {
2028 let input = inputs[i];
2029 if (debug)
2030 console.log('---- Write port:', input);
2031
2032 let inputName = input.name;
2033 let inputType = input.type;
2034 if (nodeElement.getCategory() == 'input' ||
2035 nodeElement.getCategory() == 'output') {
2036 inputName = 'in';
2037 inputType = node.nodedef_type;
2038 }
2039
2040 //var inputType = input.type;
2041 var inputElement = null;
2042 var nodeToCheck = node;
2043 var inputNode = null;
2044 var inputLink = null;
2045 if (isInputNode && node.graph._subgraph_node) {
2046 nodeToCheck = node.graph._subgraph_node;
2047 for (var i = 0; i < nodeToCheck.inputs.length; i++) {
2048 var nci = nodeToCheck.inputs[i];
2049 if (nci.name == node.title) {
2050 inputNode = nodeToCheck.getInputNode(i);
2051 inputLink = nodeToCheck.getInputLink(i);
2052 //console.log('--- Found parent input:', nci.name, 'inputNode:', inputNode, 'inputLink:', inputLink);
2053 break;
2054 }
2055 }
2056 }
2057 else {
2058 inputNode = node.getInputNode(i);
2059 inputLink = node.getInputLink(i);
2060 }
2061 var inputLinkOutput = '';
2062 var numInputOutputs = 0;
2063 if (inputLink) {
2064 numInputOutputs = inputNode.outputs.length;
2065 inputLinkOutput = inputNode.outputs[inputLink.origin_slot];
2066 }
2067 if (inputNode) {
2068 //console.log('inputNode', inputNode, 'inputLink:', inputLink, '. --- upsteream Output:', inputLinkOutput);
2069 if (nodeElement.getCategory() != 'input' &&
2070 nodeElement.getCategory() != 'output') {
2071 inputElement = nodeElement.getInput(inputName);
2072 //console.log('Call add input on elem', nodeElement, inputName);
2073 inputElement = nodeElement.addInput(inputName, inputType);
2074 }
2075 else {
2076 inputElement = nodeElement;
2077 }
2078
2079 if (debug) {
2080 console.log('Write connection');
2081 console.log(' - TO:', inputElement.getName() + "." + inputName);
2082 console.log(' - FROM link:', inputNode.id + "." + inputLinkOutput.name);
2083 }
2084 if (inputNode.type == 'graph/subgraph') {
2085 inputElement.setNodeGraphString(inputNode.title);
2086 // Set output string if there was an output link.
2087 if (numInputOutputs > 1 && inputLinkOutput) {
2088 inputElement.setOutputString(inputLinkOutput.name);
2089 }
2090 }
2091 else {
2092 //var upstream_nodeType = nodeTypes[inputNode.type];
2093 //if (upstream_nodeType)
2094 //console.log('Write connection: ', inputNode.title)
2095 {
2096 if (inputNode.nodedef_node == 'input') {
2097 //console.log('Set interface name:', inputNode.title, ' on ', inputElement.getNamePath());
2098 //console.log(inputNode)
2099 inputElement.setInterfaceName(inputNode.title);
2100 //console.log('---------- Get interface name:', inputElement.getInterfaceName());
2101 }
2102 else {
2103 inputElement.setNodeName(inputNode.title);
2104 // Need to check that upstream has > 1 output.
2105 // TODO: Log issue that this is annoying to disallow an explicit output in validation.
2106 if (numInputOutputs > 1 && inputNode.nodedef_node != 'output') {
2107 // Set output string if there was an output link.
2108 if (inputLinkOutput) {
2109 inputElement.setOutputString(inputLinkOutput.name);
2110 }
2111 }
2112 }
2113 }
2114 }
2115 }
2116 else {
2117
2118 var inputValue = node.properties[inputName];
2119 if (inputValue == null) {
2120 console.log('Cannot find property value for input:', inputName);
2121 }
2122 else {
2123 var origValue = inputValue;
2124 //var inputType = propInfo.type;
2125 if (['float', 'integer'].includes(inputType)) {
2126 inputValue = inputValue.toString();
2127 }
2128 else if (['vector2', 'vector3', 'vector4', 'matrix33', 'matrix44', 'color3', 'color4'].includes(inputType)) {
2129 inputValue = inputValue.toString();
2130 inputValue = inputValue.split(',').map(Number).join(', ');
2131 }
2132 else if (inputType === 'boolean') {
2133 if (inputValue === 'true')
2134 inputValue = 'true';
2135 else
2136 inputValue = 'false';
2137 }
2138 else {
2139 inputValue = inputValue.toString();
2140 }
2141 //console.log('Write input:', inputElement, node, inputName, origValue, inputValue, inputType);
2142
2143 if (nodeElement.getCategory() != 'input' &&
2144 nodeElement.getCategory() != 'output') {
2145 inputElement = nodeElement.getInput(inputName);
2146 if (!inputElement)
2147 inputElement = nodeElement.addInput(inputName, inputType);
2148 else {
2149 // TODO: LiteGraph seems that copy+paste adds same input > once.
2150 console.log('Error> Trying add input more than once:', inputName, ' to node: ', nodeElement.getNamePath());
2151 }
2152 }
2153 else {
2154 inputElement = nodeElement;
2155 }
2156
2157 try {
2158 inputElement.setValueString(inputValue, inputType);
2159 }
2160 catch (e) {
2161 console.warn("Set value error: ", e);
2162 }
2163 }
2164 }
2165
2166 if (inputElement) {
2167
2168 var propInfo = null;
2169 var skip_attributes = [];
2170 if (isInputNode || isOutputNode) {
2171 if (input.properties_info) {
2172 // Make sure not to clobber connections like interfacename
2173 skip_attributes = ['interfacename', 'nodegraph', 'nodename', 'name', 'type', 'value', 'default_value'];
2174 propInfo = input.properties_info[0];
2175 }
2176 }
2177 else {
2178 if (node.properties_info) {
2179 // Make sure not to clobber connections like interfacename
2180 skip_attributes = ['interfacename', 'nodegraph', 'nodename', 'name', 'type', 'value', 'default_value', 'uimin', 'uimax', 'uiname', 'uifolder'];
2181 propInfo = node.properties_info[i];
2182 }
2183 }
2184 if (propInfo) {
2185 //console.log('Scan propinfo:', propInfo, 'for input:', inputElement.getNamePath(), 'prop_info:', propInfo);
2186
2187 // Write node_properties metadata to input
2188 skip_attributes = skip_attributes.concat(['defaultgeomprop', 'enum', 'enumvalues']);
2189 //console.log('SKIP ATTRIBUTES:', skip_attributes);
2190 for (var propAttribute in propInfo) {
2191 if (skip_attributes.includes(propAttribute))
2192 continue;
2193
2194 //console.log('-- scan attrib:', propAttribute, 'value:', propInfo[propAttribute]);
2195 var propAttributeValue = propInfo[propAttribute];
2196 if (propAttributeValue && propAttributeValue.length > 0) {
2197 inputElement.setAttribute(propAttribute, propAttributeValue);
2198 }
2199 }
2200 }
2201 }
2202 }
2203
2204 if (debug)
2205 console.log('---- END Write inputs:', node.inputs);
2206 }
2207
2208 if (debug)
2209 console.log('---> End write node', node.title);
2210 }
2211
2212 if (debug)
2213 console.log('***** END Scan Graph:', graph.title);
2214 }
2215
2222 isArray(_type) {
2223 var ARRAY_TYPES = ['color3', 'color4', 'vector2', 'vector3', 'vector4', 'matrix33', 'matrix44'];
2224 if (ARRAY_TYPES.includes(_type)) {
2225 return true;
2226 }
2227 return false;
2228 }
2229
2240 buildConnections(editor, node, lg_node, explicitInputs, graph, parentGraph) {
2241
2242 var nodeInputs = [];
2243 var isOutput = (node.getCategory() == 'output');
2244 var isInput = (node.getCategory() == 'input');
2245 if (isOutput || isInput) {
2246 nodeInputs = [node];
2247 }
2248 else {
2249 nodeInputs = node.getInputs();
2250 }
2251 for (var input of nodeInputs) {
2252
2253 var _name = ''
2254
2255 if (!isOutput && !isInput) {
2256 _name = input.getName();
2257 explicitInputs.push(_name);
2258 }
2259
2260 var nodeName = input.getNodeName();
2261 var nodeGraphName = input.getNodeGraphString();
2262 var inputInterfaceName = input.getInterfaceName();
2263 var outputName = input.getOutputString();
2264
2265 if (nodeName.length ||
2266 nodeGraphName.length ||
2267 inputInterfaceName.length ||
2268 outputName.length) {
2269
2270 //console.log('Test connection on input:', input.getNamePath(), 'nodeName:[ ', nodeName,
2271 // '] nodeGraphName:[', nodeGraphName,
2272 // '] inputInterfaceName:[', inputInterfaceName,
2273 // ']outputName:[', outputName, ']');
2274
2275 var target_node = lg_node;
2276 var target_slot = null;
2277 if (!isOutput && !isInput)
2278 target_slot = target_node.findInputSlot(_name);
2279 else
2280 target_slot = 0;
2281 var source_node = null;
2282 var source_slot = 0;
2283 var source_name = nodeName;
2284 if (nodeGraphName.length) {
2285 source_name = nodeGraphName;
2286 }
2287 if (inputInterfaceName.length) {
2288 source_name = inputInterfaceName;
2289 }
2290
2291 var graphToCheck = graph;
2292 if (isInput && graph._subgraph_node) {
2293 target_node = graph._subgraph_node;
2294 target_slot = target_node.findInputSlot(lg_node.title);
2295 // Go up to parent graph
2296 graphToCheck = parentGraph;
2297 //console.log(' go up to parent graph:', graphToCheck,
2298 // 'from:', graph, 'subgraph:', graph._subgraph_node,
2299 //'target_node:', target_node.title, 'target_slot:', target_slot);
2300 }
2301 source_node = graphToCheck.findNodeByTitle(source_name);
2302 if (source_node) {
2303 if (outputName) {
2304 var outputSlot = source_node.findOutputSlot(outputName);
2305 if (outputSlot >= 0) {
2306 source_slot = outputSlot;
2307 }
2308 else {
2309 editor.debugOutput('Failed to find output slot:' + outputName, 1);
2310 }
2311 var linkInfo = source_node.connect(source_slot, target_node, target_slot);
2312 if (!linkInfo) {
2313 editor.debugOutput('Failed to connect:' + source_node.title + '.' + outputName, '->', target_node.title + '.' + _name), 1, false;
2314 }
2315 }
2316 //console.log('CONNECT START: source[', source_node.title, '.', source_slot,
2317 // '] --> target[:', target_node.title, ".", target_slot);
2318 var linkInfo = null;
2319 if (source_slot == null || target_slot == null || target_node == null) {
2320 console.warning('Cannot connect!')
2321 }
2322 else {
2323 linkInfo = source_node.connect(source_slot, target_node, target_slot);
2324 }
2325 if (!linkInfo) {
2326 editor.debugOutput('Failed to connect:' + source_node.title + '.' + outputName, '->', target_node.title + '.' + _name, 1);
2327 }
2328 //console.log('CONNECT END: source[', source_node.title, '.', source_slot,
2329 // '] --> target[:', target_node.title, ".", target_slot);
2330 }
2331 else {
2332 console.log('Failed to find node ', source_name, 'in graph:', graphToCheck);
2333 this.editor.debugOutput('Failed to find source node: ' + source_node + "." +
2334 source_name, '->', lg_node.title + "." + _name, 2);
2335 }
2336 }
2337 else {
2338 var _value = input.getResolvedValueString(); // input.getValueString();
2339 if (_value.length > 0) {
2340 if (this.isArray(input.getType())) {
2341 // split by commas or spaces
2342 let valueArray = _value.split(/[\s,]+/);
2343 _value = valueArray;
2344 }
2345
2346 //console.log('-- Value Input:',
2347 //lg_node.title + "." + _name, 'value:', _value);
2348 lg_node.setProperty(_name, _value);
2349 }
2350 }
2351
2352 var property_info = lg_node.getPropertyInfo(_name);
2353 this.loadInputMetaData(node, input, property_info);
2354 }
2355 }
2356
2365 loadInputMetaData(node, input, property_info) {
2366 if (input && property_info) {
2367
2368 // Load in basic meta-data
2369 var colorspace = input.getColorSpace();
2370 if (colorspace.length > 0)
2371 property_info['colorspace'] = colorspace;
2372
2373 var unit = input.getUnit();
2374 if (unit.length > 0)
2375 property_info['unit'] = unit;
2376
2377 var uiname = input.getAttribute('uiname');
2378 if (uiname.length > 0)
2379 property_info['uiname'] = uiname;
2380
2381 var uimin = input.getAttribute('uimin');
2382 if (uimin.length > 0)
2383 property_info['uimin'] = uimin;
2384
2385 var uimax = input.getAttribute('uimax');
2386 if (uimax.length > 0)
2387 property_info['uimax'] = uimax;
2388 var uisoftmin = input.getAttribute('uisoftmin');
2389 if (uisoftmin.length > 0)
2390 property_info['uimin'] = uisoftmin;
2391
2392 var uisoftmax = input.getAttribute('uisoftmax');
2393 if (uisoftmax.length > 0)
2394 property_info['uimax'] = uisoftmax;
2395
2396 var uifolder = input.getAttribute('uifolder');
2397 if (uifolder.length > 0)
2398 property_info['uifolder'] = uifolder;
2399
2400 var basicMetaData = ['colorspace', 'unit', 'uiname', 'uimin', 'uimax', 'uifolder', 'name', 'type', 'output', 'nodename', 'nodegraph'];
2401 for (var attrName of input.getAttributeNames()) {
2402 if (!basicMetaData.includes(attrName)) {
2403 property_info[attrName] = input.getAttribute(attrName);
2404 }
2405 }
2406
2407 if (node && input.getType() == 'filename')
2408 {
2409 let nodeType = node.getType();
2410 let colorTypes = ['color3', 'color4'];
2411 //console.log('Load input metadata for:', input.getName(), input.getType(), property_info, nodeType);
2412 if (colorTypes.includes(nodeType))
2413 {
2414 if (!property_info['colorspace']) {
2415 console.log('Auto create "none" colorspace for input:', input.getName());
2416 let doc = node.getDocument();
2417 let defaultColorSpace = 'none';
2418 // For now don't use the document color space as 'none' is more flexible.
2419 //let docColorSpace = doc.getAttribute('colorspace');
2420 //if (docColorSpace.length > 0)
2421 // defaultColorSpace = docColorSpace;
2422 property_info['colorspace'] = defaultColorSpace;
2423 }
2424 }
2425 }
2426
2427 //console.log('load input metadata for:', input.getNamePath(), property_info);
2428 }
2429 }
2430
2439 buildGraphFromDoc(doc, editor, auto_arrange) {
2440 let debug = false;
2441 let loadNodePositions = false; // TODO: Some issues with UI update when setting xpos, ypos. To address.
2442
2443 //console.log('Build graph from doc. auto_arrange: ', auto_arrange);
2444 if (!ne_mx) {
2445 editor.debugOutput("MaterialX is not initialized", 2);
2446 return;
2447 }
2448
2449 editor.clearGraph();
2450
2451 // Don't try and update the graph while building it
2452 editor.monitor.monitorGraph(graph, false);
2453
2454 // Index here is index into litegraph nodes
2455 var mtlxNodes = [];
2456 var mtlxNodeDefs = [];
2457
2458 for (var interfaceInput of doc.getInputs()) {
2459 var _type = interfaceInput.getType();
2460 var id = 'mtlx/input/input_' + _type;
2461
2462 var lg_node = LiteGraph.createNode(id);
2463 if (lg_node) {
2464 lg_node.title = interfaceInput.getName();
2465 if (debug)
2466 console.log('Add top level input:', lg_node.title, 'to graph', graph);
2467
2468 var _value = interfaceInput.getValueString();
2469 if (_value && _value.length > 0) {
2470 if (this.isArray(interfaceInput.getType())) {
2471 _value = "[" + _value + "]"
2472 _value = JSON.parse(_value);
2473 }
2474 lg_node.setProperty('in', _value);
2475 }
2476
2477 if (loadNodePositions) {
2478 var xpos = interfaceInput.getAttribute('xpos');
2479 var ypos = interfaceInput.getAttribute('ypos');
2480 if (xpos.length > 0 && ypos.length > 0) {
2481 lg_node.pos[0] = xpos;
2482 lg_node.pos[1] = ypos;
2483 //lg_node.flags.collapsed = false;
2484 }
2485 }
2486
2487 // Make sure size is updated
2488 lg_node.setSize(lg_node.computeSize());
2489
2490 graph.add(lg_node);
2491
2492 //mtlxNodes.push([interfaceInput, lg_node, graph]);
2493 }
2494 }
2495
2496 for (var interfaceOutput of doc.getOutputs()) {
2497 var _type = interfaceOutput.getType()
2498 var id = 'mtlx/output/output_' + _type;
2499
2500 var lg_node = LiteGraph.createNode(id);
2501 if (lg_node) {
2502 lg_node.title = interfaceOutput.getName();
2503 graph.add(lg_node);
2504 if (debug) {
2505 console.log('Add graph output:', lg_node.title);
2506 }
2507
2508 // Make sure size is updated
2509 lg_node.setSize(lg_node.computeSize());
2510
2511 if (loadNodePositions) {
2512 var xpos = interfaceOutput.getAttribute('xpos');
2513 var ypos = interfaceOutput.getAttribute('ypos');
2514 if (xpos.length > 0 && ypos.length > 0)
2515 lg_node.pos = [xpos, ypos];
2516 }
2517
2518 mtlxNodes.push([interfaceOutput, lg_node, graph]);
2519 }
2520 }
2521
2522 for (var node of doc.getNodes()) {
2523 var nodeDef = node.getNodeDef();
2524 if (!nodeDef) {
2525 editor.debugOutput('Skip node w/o nodedef:' + node.getName(), 1)
2526 continue;
2527 }
2528
2529 // mtlx/pbr/gltf_pbr_surfaceshader
2530 var id = 'mtlx/' + nodeDef.getNodeGroup() + '/' + nodeDef.getName();
2531 id = id.replace('ND_', '');
2532 if (debug)
2533 console.log('Load node:', node.getName(), ' -> ', id);
2534
2535 var lg_node = LiteGraph.createNode(id);
2536 if (lg_node) {
2537 //console.log('LiteGraph node:', lg_node);
2538 lg_node.title = node.getName();
2539
2540 graph.add(lg_node);
2541
2542 // Make sure size is updated
2543 lg_node.setSize(lg_node.computeSize());
2544
2545 if (loadNodePositions) {
2546 var xpos = node.getAttribute('xpos');
2547 var ypos = node.getAttribute('ypos');
2548 if (xpos.length > 0 && ypos.length > 0)
2549 lg_node.pos = [xpos, ypos];
2550 }
2551
2552 mtlxNodes.push([node, lg_node, graph]);
2553 mtlxNodeDefs.push(nodeDef);
2554 }
2555 else {
2556 editor.debugOutput('Failed to create node:' + node.getName(), 2);
2557 }
2558 }
2559
2560 for (var nodegraph of doc.getNodeGraphs()) {
2561 if (nodegraph.hasSourceUri()) {
2562 continue;
2563 }
2564 var nodedefAttrib = nodegraph.getAttribute('nodedef');
2565 if (nodedefAttrib && nodedefAttrib.length > 0) {
2566 console.log('Skip loading in functional graph:', nodegraph.getName(), 'nodedef:', nodedefAttrib);
2567 continue;
2568 }
2569 if (debug)
2570 console.log('Create nodegraph:', nodegraph.getName());
2571
2572 var title = nodegraph.getName();
2573 var subgraphNode = LiteGraph.createNode("graph/subgraph", title);
2574 //var subgraph = new LiteGraph.LGraph();
2575 //subgraphNode._subgraph_node = subgraph;
2576 //subgraphNode.bgcolor = "#112";
2577 subgraphNode.bgImageUrl = "./Icons/nodegraph.png";
2578
2579 var mtlxSubGraphNodes = [];
2580 for (var interfaceInput of nodegraph.getInputs()) {
2581 var _type = interfaceInput.getType();
2582 var id = 'mtlx/input/input_' + _type;
2583
2584 var lg_node = LiteGraph.createNode(id);
2585 if (lg_node) {
2586 lg_node.title = interfaceInput.getName();
2587 this.loadInputMetaData(null, interfaceInput, lg_node.properties_info[0]);
2588 subgraphNode.subgraph.add(lg_node);
2589
2590 if (debug)
2591 console.log('-------- Add subgraph input:', lg_node.title, lg_node);
2592
2593 subgraphNode.addInput(interfaceInput.getName(), _type);
2594 subgraphNode.subgraph.addInput(interfaceInput.getName(), _type);
2595
2596 var _value = interfaceInput.getValueString();
2597 if (_value && _value.length > 0) {
2598 if (this.isArray(interfaceInput.getType())) {
2599 _value = "[" + _value + "]"
2600 _value = JSON.parse(_value);
2601 }
2602 lg_node.setProperty('in', _value);
2603 }
2604
2605 // Make sure size is updated
2606 lg_node.setSize(lg_node.computeSize());
2607
2608 if (loadNodePositions) {
2609 var xpos = nodegraph.getAttribute('xpos');
2610 var ypos = nodegraph.getAttribute('ypos');
2611 if (xpos.length > 0 && ypos.length > 0)
2612 lg_node.pos = [xpos, ypos];
2613 }
2614
2615 mtlxSubGraphNodes.push([interfaceInput, lg_node, graph]);
2616 }
2617 }
2618
2619 for (var interfaceOutput of nodegraph.getOutputs()) {
2620 var _type = interfaceOutput.getType()
2621 var id = 'mtlx/output/output_' + _type;
2622
2623 var lg_node = LiteGraph.createNode(id);
2624 if (lg_node) {
2625 lg_node.title = interfaceOutput.getName();
2626 subgraphNode.subgraph.add(lg_node);
2627 if (debug)
2628 console.log('Add subgraph output:', lg_node.title);
2629
2630 subgraphNode.addOutput(interfaceOutput.getName(), _type);
2631 subgraphNode.subgraph.addOutput(interfaceOutput.getName(), _type);
2632
2633 // Make sure size is updated
2634 lg_node.setSize(lg_node.computeSize());
2635
2636 if (loadNodePositions) {
2637 var xpos = interfaceOutput.getAttribute('xpos');
2638 var ypos = interfaceOutput.getAttribute('ypos');
2639 if (xpos.length > 0 && ypos.length > 0)
2640 lg_node.pos = [xpos, ypos];
2641 }
2642
2643 mtlxSubGraphNodes.push([interfaceOutput, lg_node, graph]);
2644 }
2645 }
2646
2647
2648 for (var node of nodegraph.getNodes()) {
2649 var nodeDef = node.getNodeDef();
2650 if (!nodeDef) {
2651 editor.debugOutput('Skip node w/o nodedef:' + node.getName(), 1)
2652 continue;
2653 }
2654
2655 // mtlx/pbr/gltf_pbr_surfaceshader
2656 var id = 'mtlx/' + nodeDef.getNodeGroup() + '/' + nodeDef.getName();
2657 id = id.replace('ND_', '');
2658
2659 var lg_node = LiteGraph.createNode(id);
2660 lg_node.title = node.getName();
2661 subgraphNode.subgraph.add(lg_node);
2662 if (debug)
2663 console.log('Add subgraph node:', lg_node.title);
2664
2665 // Make sure size is updated
2666 lg_node.setSize(lg_node.computeSize());
2667
2668 if (loadNodePositions) {
2669 var xpos = node.getAttribute('xpos');
2670 var ypos = node.getAttribute('ypos');
2671 if (xpos.length > 0 && ypos.length > 0)
2672 lg_node.pos = [xpos, ypos];
2673 }
2674
2675 mtlxSubGraphNodes.push([node, lg_node, graph]);
2676 }
2677
2678 for (var item of mtlxSubGraphNodes) {
2679 var node = item[0];
2680 var lg_node = item[1];
2681 var parentGraph = item[2];
2682 var explicitInputs = [];
2683
2684 // Make sure size is updated
2685 lg_node.setSize(lg_node.computeSize());
2686
2687 //console.log('Build connections for subgraog node:', lg_node.title);
2688 this.buildConnections(editor, node, lg_node, explicitInputs, subgraphNode.subgraph, parentGraph);
2689 }
2690
2691 if (debug)
2692 console.log('Add subgraph:', subgraphNode.title);
2693
2694 if (auto_arrange > 0) {
2695 subgraphNode.subgraph.arrange(auto_arrange);
2696 }
2697
2698 graph.add(subgraphNode);
2699
2700 }
2701
2702 // Build top level connections last after top level nodes
2703 // and nodegraph have been added.
2704 var itemCount = 0;
2705 for (var item of mtlxNodes) {
2706 var node = item[0];
2707 var lg_node = item[1];
2708
2709 // Keep track of explicit inputs
2710 var explicitInputs = [];
2711 //console.log('Build connections for:', lg_node.title);
2712 this.buildConnections(editor, node, lg_node, explicitInputs, graph, null);
2713
2714 if (lg_node.nodedef_node == 'input' || lg_node.nodedef_node == 'output') {
2715 continue;
2716 }
2717
2718 /*
2719 var removeInputs = [];
2720 var nodeDef = mtlxNodeDefs[itemCount];
2721 if (nodeDef) {
2722 for (var nodeDefInput of nodeDef.getActiveInputs()) {
2723 var _name = nodeDefInput.getName();
2724 if (!explicitInputs.includes(_name)) {
2725 removeInputs.push(_name);
2726 }
2727 }
2728 for (var _name of removeInputs) {
2729 var slot = lg_node.findInputSlot(_name);
2730 lg_node.inputs[slot].hidden = true;
2731 console.log('Hide input:', _name, '. ', slot, ' on: ', lg_node);
2732 //lg_node.removeInput(slot);
2733 }
2734
2735 // Make sure size is updated
2736 lg_node.setSize(lg_node.computeSize());
2737 }
2738 */
2739 itemCount++;
2740 }
2741
2742 editor.monitor.monitorGraph(graph, true);
2743
2744 if (auto_arrange > 0) {
2745 graph.arrange(auto_arrange);
2746 }
2747
2748 graph.setDirtyCanvas(true, true);
2749 graphcanvas.setDirty(true, true);
2750 }
2751
2758 var that = this;
2759
2760 // Load mtlx document from disk
2761 var input = document.createElement("input");
2762 input.style = this.fontSizeStyle;
2763 input.type = "file";
2764 input.accept = ".mtlx";
2765 input.onchange = function (e) {
2766 var file = e.target.files[0];
2767 console.log('Loading definitions from file: ' + file.name);
2768
2769 if (ne_mx) {
2770 // Load the content from the specified file (replace this with actual loading logic)
2771
2772 const reader = new FileReader();
2773 reader.readAsText(file, 'UTF-8');
2774
2775 reader.onload = function (e) {
2776 // Display the contents of the file in the output div
2777 let fileContents = e.target.result;
2778 //console.log(fileContents);
2779
2780 (async () => {
2781 try {
2782 const readOptions = new ne_mx.XmlReadOptions();
2783 readOptions.readXIncludes = false;
2784 var customLib = ne_mx.createDocument();
2785
2786 await ne_mx.readFromXmlString(customLib, fileContents, '', readOptions);
2787
2788 // Create JS from custom library
2789 try {
2790 console.log('Create custom library definitions', ne_mx.prettyPrint(customLib));
2791 var iconName = '';
2792 var scanForIcon = false;
2793 if (scanForIcon) {
2794 // Icon name is filename with webp as extension
2795 var iconName = file.name.replace(/\.[^/.]+$/, ".webp");
2796 // Check if iconName file exists
2797 var iconExists = await that.editor.uriExists(iconName);
2798 if (!iconExists) {
2799 iconName = '';
2800 }
2801 }
2802 var definitionsList = [];
2803 var result = that.createLiteGraphDefinitions(customLib, false, false, definitionsList, 'mtlx', that.editor, iconName);
2804 if (result) {
2805 eval(result);
2806 var definitionsListString = definitionsList.join(', ');
2807 that.editor.debugOutput("Registered custom node types: [" + definitionsListString + "]", 0, false);
2808 that.editor.displayNodeTypes();
2809 }
2810 } catch (e) {
2811 console.log('Error evaluating source:', e);
2812 }
2813
2814
2815 // Keep track of libraries loaded by filename.
2816 customlibs.push([file.name, customLib]);
2817
2818 } catch (error) {
2819 that.editor.debugOutput('Error reading definitions:' + error, 2, false);
2820 }
2821 })();
2822
2823 };
2824
2825 } else {
2826 that.editor.debugOutput("MaterialX is not initialized", 2);
2827 }
2828
2829 //customlibs
2830 };
2831 input.click();
2832 }
2833
2843 loadFromString(extension, fileContents, fileName, auto_arrange) {
2844 if (!ne_mx) {
2845 console.log('MaterialX is not initialized');
2846 return;
2847 }
2848
2849 // Check if we need to pre-convert from extension type to mtlx
2850 if (extension != 'mtlx')
2851 {
2852 let converter = this.getImporter(extension);
2853 if (converter) {
2854 let result = converter.import(ne_mx, fileContents, stdlib);
2855 if (result) {
2856 fileContents = result[0];
2857 }
2858 else {
2859 console.log('Failed to convert from:', extension, 'to mtlx. Errors:', result[1]);
2860 return;
2861 }
2862 }
2863 else
2864 {
2865 console.log('Failed to find converter from:', extension, 'to mtlx.');
2866 return;
2867 }
2868 }
2869
2870 (async () => {
2871 try {
2872 const readOptions = new ne_mx.XmlReadOptions();
2873 readOptions.readXIncludes = false;
2874
2875 doc.clearContent();
2876
2877 doc.importLibrary(stdlib);
2878 for (var item of customlibs) {
2879 console.log('Import custom library:', item[0]);
2880 doc.importLibrary(item[1]);
2881 }
2882 var loadDoc = ne_mx.createDocument();
2883 await ne_mx.readFromXmlString(loadDoc, fileContents, '', readOptions);
2884
2885 // Check if nodedef is not in existingDefs
2886 //
2887 var customLib = ne_mx.createDocument();
2888 customLib.copyContentFrom(loadDoc);
2889 var keepChildren = [];
2890 var existingDefs = []
2891 var saveCustomLib = false;
2892 doc.getNodeDefs().forEach(def => { existingDefs.push(def.getName()); });
2893 for (var nodedef of loadDoc.getNodeDefs()) {
2894 var nodedefName = nodedef.getName();
2895 if (!existingDefs.includes(nodedefName)) {
2896 keepChildren.push(nodedef.getName());
2897 saveCustomLib = true;
2898 }
2899 }
2900 for (var ng of loadDoc.getNodeGraphs()) {
2901 if (ng.getAttribute('nodedef') && ng.getAttribute('nodedef').length > 0) {
2902 saveCustomLib = true;
2903 keepChildren.push(ng.getName());
2904 }
2905 }
2906
2907 if (saveCustomLib) {
2908
2909 for (var child of customLib.getChildren()) {
2910 if (!keepChildren.includes(child.getName())) {
2911 //console.log('Remove child:', child.getName());
2912 customLib.removeChild(child.getName());
2913 }
2914 }
2915
2916 var additionDefs = [];
2917 console.log('Create custom library definitions from addtionDefs:',
2918 ne_mx.prettyPrint(customLib));
2919
2920 var result = this.createLiteGraphDefinitions(customLib, true, false, additionDefs, 'mtlx', MxShadingGraphEditor.theEditor);
2921 try {
2922 eval(result);
2923 console.log('Loaded local definitions: ', additionDefs);
2924 } catch (e) {
2925 console.log('Error evaluating source:', e);
2926 }
2927 }
2928
2929 doc.copyContentFrom(loadDoc);
2930
2931 this.validateDocument(doc);
2932
2933 this.buildGraphFromDoc(doc, MxShadingGraphEditor.theEditor, auto_arrange);
2934
2935 // Must do this after build as build will clear customDocLibs array
2936 if (saveCustomLib) {
2937 customDocLibs.push([fileName, customLib]);
2938 }
2939
2940 // Get the document's colorspace and set it as the active colorspace
2941 var documentColorSpace = doc.getColorSpace();
2942 this.setSourceColorSpace(documentColorSpace);
2943 documentColorSpace = this.getSourceColorSpace();
2944 MxShadingGraphEditor.theEditor.updatePropertyPanel(null);
2945
2946 // Cleanup document, and get up-to-date contents after any possible upgrade.
2947 loadDoc.removeAttribute('fileprefix');
2948 fileContents = ne_mx.writeToXmlString(loadDoc);
2949
2950 this.validateDocument(loadDoc);
2951
2952 MxShadingGraphEditor.theEditor.debugOutput('Loaded document: "' + fileName + '"', 0, false);
2953
2954 // Update mtlx text area
2955 let documentDisplayUpdater = MxShadingGraphEditor.theEditor.ui.documentDisplayUpdater;
2956 if (documentDisplayUpdater) {
2957 documentDisplayUpdater(fileContents);
2958 }
2959 else {
2960 console.log('No docDisplayUpdater!!!');
2961 }
2962
2963 // Update render items in UI
2964 let renderableItemUpdater = MxShadingGraphEditor.theEditor.ui.renderableItemUpdater;
2965 if (renderableItemUpdater) {
2966 let renderableItems = this.findRenderableItemsInDoc(doc);
2967 if (!renderableItems || renderableItems.length == 0) {
2968 MxShadingGraphEditor.theEditor.debugOutput('No renderable items found in graph: ' + fileName, 1, false);
2969 }
2970 renderableItemUpdater(renderableItems);
2971 }
2972
2973 } catch (error) {
2974 MxShadingGraphEditor.theEditor.debugOutput('Error reading document: ' + fileName + '. Error: ' + error, 2, false);
2975 }
2976 })();
2977 }
2978
2989 loadFromFile(extension, file, fileName, editor, auto_arrange) {
2990 var debug = false;
2991
2992 if (ne_mx) {
2993 if (!this.loadMaterialXLibraries(stdlib))
2994 return;
2995
2996 // Load the content from the specified file (replace this with actual loading logic)
2997
2998 const reader = new FileReader();
2999 reader.readAsText(file, 'UTF-8');
3000 reader.accept = '.mtlx';
3001
3002 var that = this;
3003 console.log('loadFromFile:', file, fileName);
3004 try {
3005 reader.onload = function (e) {
3006 // Display the contents of the file in the output div
3007 let fileContents = e.target.result;
3008 console.log("read file: ", file.name, " with extension: ", extension, " and length: ", fileContents.length);
3009
3010 that.loadFromString('mtlx', fileContents, fileName, auto_arrange);
3011 };
3012 } catch (error) {
3013 MxShadingGraphEditor.theEditor.debugOutput('Error reading document: ' + fileName + '. Error: ' + error, 2, false);
3014 }
3015
3016 } else {
3017 editor.debugOutput("MaterialX is not initialized", 2, false);
3018 }
3019 }
3020
3021 loadFromZip(extension, file, fileName, editor, auto_arrange)
3022 {
3023 if (ne_mx) {
3024 // Load the content from the specified file (replace this with actual loading logic)
3025 console.log("Loading content from zip:", file.name);
3026
3027 const reader = new FileReader();
3028
3029 var that = this;
3030 reader.onload = async function (e) {
3031 try {
3032 const zipData = new Uint8Array(e.target.result); // Convert to Uint8Array
3033 const result = await MxZipUtils.unzipMaterialXData(zipData);
3034 let documents = result[0];
3035 let docText = documents[0].content;
3036 let docFile = documents[0].name;
3037 console.log('Documents:', docText);
3038
3039 let textures = result[1];
3040 for (let i = 0; i < textures.length; i++) {
3041 const url = textures[i].url;
3042 const textureName = textures[i].name;
3043 // Replace fileName with url in docText.
3044 // Do not care about case sensitivity since some examples
3045 // from GPUOpen have the incorrect case.
3046 docText = docText.replace(new RegExp(textureName, 'i'), url);
3047 console.log('Replace reference:' + textureName + ' with blob: ' + url);
3048
3049 }
3050 that.loadFromString('mtlx', docText, docFile, auto_arrange);
3051
3052 } catch (error) {
3053 //editor.debugOutput("MaterialX is not initialized", 2, false);
3054 console.error('Error loading ZIP file:', error);
3055 }
3056 };
3057
3058 reader.readAsArrayBuffer(file);
3059
3060 } else {
3061 console.error("MaterialX is not initialized");
3062 }
3063 }
3064
3071 {
3072 if (stdlib)
3073 return stdlib;
3074
3075 if (!ne_mx) {
3076 MxShadingGraphEditor.theEditor.debugOutput("MaterialX is not initialized", 2);
3077 return null;
3078 }
3079
3080 var generator = new ne_mx.EsslShaderGenerator();
3081 var genContext = new ne_mx.GenContext(generator);
3082 {
3083 stdlib = ne_mx.loadStandardLibraries(genContext);
3084 console.log('Loaded standard libraries:', stdlib.getNodeDefs().length);
3085 }
3086
3087 return stdlib;
3088 }
3089
3097 createValidName(name, msg = null) {
3098 if (name.length == 0) {
3099 if (msg) {
3100 msg = 'Setting empty name as "blank"';
3101 }
3102 name = "blank";
3103 }
3104
3105 // Get list of all names in graph.
3106 var graph = graphcanvas.graph;
3107 var nodes = graph._nodes;
3108 var nodenames = [];
3109 for (var node of nodes) {
3110 nodenames.push(node.title);
3111 }
3112 //console.log('Current graph nodes:', nodenames);
3113
3114 name = ne_mx.createValidName(name);
3115
3116 if (!nodenames.includes(name)) {
3117 return name;
3118 }
3119
3120 // Get starting number and root name
3121 var rootName = name;
3122 var i = 1;
3123 var number = name.match(/\d+$/);
3124 if (number) {
3125 i = (parseInt(number) + 1)
3126 rootName = name.slice(0, -number[0].length);
3127 }
3128
3129 var valid_name = rootName + i.toString();
3130 while (nodenames.includes(valid_name)) {
3131 i++;
3132 valid_name = rootName + i.toString();
3133 }
3134 return valid_name;
3135 }
3136};
3137
3145{
3149 constructor() {
3150 if (!MxShadingGraphEditor.theEditor) {
3151 MxShadingGraphEditor.theEditor = this;
3152
3153 this.ui = null;
3154 this.fontSizeStyle = 'font-size: 11px;';
3155
3156 this.handler = new MxMaterialXHandler('MaterialX Handler', 'mtlx');
3157 let gltfConverter = new glTFMaterialX();
3158 this.handler.addConverter(gltfConverter);
3159
3160 console.log('Create new editor with exporter for:', gltfConverter.exportType());
3161
3162 }
3163 return MxShadingGraphEditor.theEditor;
3164 }
3165
3172 setUI(ui) {
3173 this.ui = ui;
3174 }
3175
3179 setDirty(w = null, h = null) {
3180 if (graph)
3181 graph.setDirtyCanvas(true, true);
3182 if (graphcanvas) {
3183 graphcanvas.resize(w,h);
3184 }
3185 }
3186
3195 debugOutput(text, severity, clear = null) {
3196 var consoleLog = MxShadingGraphEditor.theEditor.ui.consoleLogger;
3197 if (consoleLog) {
3198 consoleLog(text, severity, clear);
3199 }
3200 else {
3201 console.log('> ', text, ' severity:', severity);
3202 }
3203 }
3204
3211 setSourceColorSpace(colorSpace) {
3212 if (this.handler) {
3213 this.handler.setSourceColorSpace(colorSpace);
3214 }
3215 }
3216
3223 setTargetDistanceUnit(unit) {
3224 if (this.handler) {
3225 this.handler.setTargetDistanceUnit(unit);
3226 }
3227 }
3228
3234 getSourceColorSpace() {
3235 if (this.handler) {
3236 return this.handler.getSourceColorSpace();
3237 }
3238 return 'lin_rec709';
3239 }
3240
3246 getTargetDistanceUnit() {
3247 if (this.handler) {
3248 return this.handler.getTargetDistanceUnit();
3249 }
3250 return 'meter';
3251 }
3252
3258 searchGraph(title)
3259 {
3260 if (title.length == 0) {
3261 return;
3262 }
3263 if (graphcanvas)
3264 {
3265 let nodesFound = [];
3266 let theGraph = graphcanvas.graph;
3267
3268 // TODO: Add a this search logic to LiteGraph.
3269 const pattern = new RegExp(title);
3270 for (var i = 0, l = theGraph._nodes.length; i < l; ++i) {
3271 if (pattern.test(theGraph._nodes[i].title)) {
3272 console.log('-- add found node:', theGraph._nodes[i].title);
3273 nodesFound.push(theGraph._nodes[i]);
3274
3275 // If exact match then only select that node
3276 if (theGraph._nodes[i].title == title)
3277 {
3278 nodesFound.length = 0;
3279 nodesFound.push(theGraph._nodes[i]);
3280 break;
3281 }
3282 }
3283 }
3284 //let node = graphcanvas.graph.findNodeByTitle(title);
3285 if (nodesFound.length > 0)
3286 {
3287 graphcanvas.selectNodes(nodesFound);
3288 graphcanvas.centerOnGraph(true);
3289 MxShadingGraphEditor.theEditor.updatePropertyPanel(nodesFound[0]);
3290 }
3291 else
3292 {
3293 this.debugOutput('Node not found: ' + title, 0, false);
3294 }
3295 }
3296 }
3297
3304 arrangeGraph(spacing = 80) {
3305 // This does not track the current subgraph.
3306 if (graphcanvas) {
3307 graphcanvas.graph.arrange(spacing);
3308 }
3309 }
3310
3314 openSubgraph() {
3315 var selected = graphcanvas.selected_nodes;
3316 for (var s in selected) {
3317 var node = selected[s];
3318 if (node.type == 'graph/subgraph') {
3319 graphcanvas.openSubgraph(node.subgraph);
3320 break;
3321 }
3322 }
3323 }
3324
3328 closeSubgraph() {
3329 if (graphcanvas) {
3330 graphcanvas.closeSubgraph();
3331 }
3332 }
3333
3337 resetView() {
3338 if (graphcanvas) {
3339 graphcanvas.ds.reset();
3340 graphcanvas.setDirty(true, true);
3341 graphcanvas.centerOnGraph(false);
3342 }
3343 }
3344
3348 selectNodes() {
3349 if (graphcanvas) {
3350 graphcanvas.selectNodes();
3351 graphcanvas.setDirty(true, true);
3352 }
3353 }
3354
3359 clearGraph() {
3360
3361 this.handler.sourceColorSpace = this.handler.DEFAULT_COLOR_SPACE;
3362 this.handler.targetDistanceUnits = this.handler.DEFAULT_DISTANCE_UNITS;
3363 MxShadingGraphEditor.theEditor.updatePropertyPanel(null);
3364 this.updatePropertyPanel(null);
3365 if (graphcanvas) {
3366 // Set back to top graph
3367 graphcanvas.setGraph(graph);
3368 graphcanvas.graph.clear();
3369 graphcanvas.ds.reset();
3370 graphcanvas.setDirty(true, true);
3371 }
3372 customDocLibs = [];
3373 }
3374
3378 saveSerialization() {
3379 var data = JSON.stringify(graph.serialize(), null, 2);
3380 var blob = new Blob([data], { type: "text/plain" });
3381 var url = URL.createObjectURL(blob);
3382 var a = document.createElement("a");
3383 a.href = url;
3384 a.download = "serialized_graph.json";
3385 a.click();
3386 }
3387
3391 loadSerialization() {
3392 MxShadingGraphEditor.theEditor.clearGraph();
3393
3394 var input = document.createElement("input");
3395 input.style = this.fontSizeStyle;
3396 input.type = "file";
3397 input.accept = ".json";
3398 input.onchange = function (e) {
3399 var file = e.target.files[0];
3400 var reader = new FileReader();
3401 reader.onload = function (event) {
3402 var data = JSON.parse(event.target.result);
3403 graph.configure(data);
3404 };
3405 reader.readAsText(file);
3406 };
3407 input.click();
3408 }
3409
3417 saveGraphToFile(extension, graphWriteOptions) {
3418 if (this.handler.canExport(extension)) {
3419 this.handler.saveGraphToFile(extension, graph, graphWriteOptions);
3420 }
3421 else
3422 {
3423 this.debugOutput('Unsupported extension for saving graph:' + extension, 2, false);
3424 }
3425 }
3426
3433 saveGraphToString(extension, graphWriteOptions) {
3434 if (this.handler.canExport(extension)) {
3435 return this.handler.saveGraphToString(extension, graph, graphWriteOptions);
3436 }
3437 else
3438 {
3439 this.debugOutput('Unsupported extension for saving graph: ' + extension, 2, false);
3440 return '';
3441 }
3442 }
3443
3450 loadDefinitionsFromFile(extension) {
3451 if (extension == 'mtlx') {
3452 this.handler.loadDefinitionsFromFile();
3453 }
3454 else
3455 {
3456 this.debugOutput('Unsupported extension for loading definitions: ' + extension, 2, false);
3457 }
3458 }
3459
3467 loadGraphFromFile(extension, auto_arrange) {
3468
3469 if (!this.handler.canImport(extension)) {
3470 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3471 return;
3472 }
3473
3474 // Load document from disk.
3475 if (extension == 'mtlx')
3476 {
3477 var input = document.createElement("input");
3478 input.style = this.fontSizeStyle;
3479 input.type = "file";
3480 input.accept = "." + this.handler.getExtension();
3481 input.onchange = function (e) {
3482 var file = e.target.files[0];
3483 console.log('Loading file: ' + file.name);
3484 MxShadingGraphEditor.theEditor.handler.loadFromFile(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange);
3485 };
3486 input.click();
3487 }
3488 else if (extension == 'zip')
3489 {
3490 var input = document.createElement("input");
3491 input.style = this.fontSizeStyle;
3492 input.type = "file";
3493 input.accept = ".zip";
3494 input.onchange = function (e) {
3495 var file = e.target.files[0];
3496 if (file) {
3497 console.log('Loading zip file: ' + file.name);
3498 MxShadingGraphEditor.theEditor.handler.loadFromZip(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange);
3499 }
3500 };
3501 input.click();
3502 }
3503 }
3504
3510 findRenderableItems() {
3511 return this.handler.findRenderableItems(graph);
3512 }
3513
3523 loadGraphFromString(extension, content, fileName, auto_arrange) {
3524 if (!this.handler.canImport(extension)) {
3525 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3526 return;
3527 }
3528
3529 if (content.length > 0)
3530 this.handler.loadFromString(extension, content, fileName, auto_arrange);
3531 else
3532 MxShadingGraphEditor.theEditor.debugOutput('No content to load', 2, false);
3533 }
3534
3541 rgbToHex(rgb) {
3542 if (!rgb) {
3543 console.log('rgbToHex empty !', rgb);
3544 return "#000000";
3545 }
3546 return '#' + rgb.map(x => {
3547 var hex = Math.round(x * 255).toString(16);
3548 return hex.length === 1 ? '0' + hex : hex;
3549 }).join('');
3550 }
3551
3560 createButtonWithImageAndText(imageSrc, text, id) {
3561 // Create image element
3562 var img = document.createElement("img");
3563 img.id = id + "_img";
3564 img.src = imageSrc;
3565 img.classList.add("img-fluid");
3566
3567 // Create text element
3568 var span = document.createElement("span");
3569 span.id = id + "_text";
3570 span.textContent = " " + text;
3571
3572 // Create button element
3573 var button = document.createElement("button");
3574 button.id = id;
3575 button.classList.add("btn", "btn-sm", "btn-outline-secondary", "form-control", "form-control-sm");
3576 button.style = this.fontSizeStyle;
3577 button.appendChild(img);
3578 button.appendChild(span);
3579
3580 return button;
3581 }
3582
3590 openImageDialog(theNode, updateProp, wantURI) {
3591
3592 // Dynamically create a file input element
3593 var fileInput = document.createElement('input');
3594 fileInput.type = 'file';
3595 fileInput.accept = 'image/*'; // Accept any image file
3596 fileInput.style.display = 'none';
3597 document.body.appendChild(fileInput);
3598
3599 fileInput.click();
3600
3601 // TODO : Cache the fileURI on the node so can display without loading...
3602 fileInput.addEventListener('change', function () {
3603 var fileURI = fileInput.value.split('\\').pop(); // Get the filename without the full path
3604 var file = fileInput.files[0];
3605 //if (wantURI)
3606 fileURI = URL.createObjectURL(file);
3607
3608 var updateElementId = '__pp:' + updateProp;
3609 var textInput = document.getElementById(updateElementId);
3610 //console.log('New filename:', fileURI, 'updateElementId:', updateElementId, 'updateProp:', updateProp);
3611 textInput.value = fileURI;
3612 theNode.setProperty(updateProp, fileURI);
3613
3614 var propertypanel_preview = document.getElementById('propertypanel_preview');
3615 if (propertypanel_preview) {
3616 propertypanel_preview.src = URL.createObjectURL(file);
3617 propertypanel_preview.style.display = "block";
3618 }
3619
3620 var previewImage = false;
3621 if (previewImage) {
3622 if (propertypanel_preview) {
3623 var reader = new FileReader();
3624 reader.onload = function (event) {
3625 propertypanel_preview.src = event.target.result;
3626 };
3627
3628 // Read the file as a data URL (base64 encoded string)
3629 reader.readAsDataURL(file);
3630 propertypanel_preview.style.display = "block";
3631 }
3632 }
3633
3634 document.body.removeChild(fileInput);
3635 });
3636 }
3637
3644 uriExists(uri) {
3645 return fetch(uri)
3646 .then(response => {
3647 if (response.ok) {
3648 return Promise.resolve(true);
3649 } else {
3650 return Promise.resolve(false);
3651 }
3652 })
3653 .catch(error => {
3654 console.log('Error checking URI:', error);
3655 return Promise.resolve(false);
3656 });
3657 }
3658
3666 createColorSpaceInput(colorSpaces, activeItem) {
3667 var select = document.createElement("select");
3668 select.className = "form-control form-control-sm";
3669 select.style = this.fontSizeStyle;
3670 select.id = "propertypanel_colorspace";
3671 for (var i = 0; i < colorSpaces.length; i++) {
3672 var option = document.createElement("option");
3673 option.value = colorSpaces[i];
3674 option.text = colorSpaces[i];
3675 select.add(option);
3676 }
3677 // Add "none" option
3678 var option = document.createElement("option");
3679 option.value = "none";
3680 option.text = "none";
3681 select.add(option);
3682
3683 select.value = activeItem;
3684 return select;
3685 }
3686
3695 createUnitsInput(units, unittype, activeItem) {
3696 var select = document.createElement("select");
3697 select.className = "form-control form-control-sm";
3698 select.style = this.fontSizeStyle;
3699 select.id = "propertypanel_units";
3700 for (var i = 0; i < units.length; i++) {
3701 var option = document.createElement("option");
3702 var unit_pair = units[i];
3703 if (unit_pair[1] == unittype) {
3704 option.value = unit_pair[0];
3705 option.text = unit_pair[0];
3706 select.add(option);
3707 }
3708 }
3709 select.value = activeItem;
3710 return select;
3711 }
3712
3719 updateImagePreview(curImage) {
3720 var propertypanel_preview = document.getElementById('propertypanel_preview');
3721 if (curImage && propertypanel_preview) {
3722 this.uriExists(curImage)
3723 .then(exists => {
3724 if (exists) {
3725 propertypanel_preview.src = curImage;
3726 propertypanel_preview.style.display = "block";
3727 } else {
3728 //propertypanel_preview.style.display = "none";
3729 propertypanel_preview.src = "./Icons/no_image.png";
3730 propertypanel_preview.style.display = "block";
3731 MxShadingGraphEditor.theEditor.debugOutput('Image does not exist: ' + curImage, 1);
3732 }
3733 });
3734 }
3735 }
3736
3747 updatePropertyPanel(node) {
3748 //console.log('Update Panel For:', node);
3749 var propertypanelcontent = MxShadingGraphEditor.theEditor.ui.propertypanel_content;
3750 if (!propertypanelcontent) {
3751 console.error('No property panel content widget found!');
3752 return;
3753 }
3754 // Delete all children
3755 while (propertypanelcontent.firstChild) {
3756 propertypanelcontent.removeChild(propertypanelcontent.firstChild);
3757 }
3758
3759 // Update icon
3760 var panelIcon = MxShadingGraphEditor.theEditor.ui.propertypanel_icon;
3761 if (node && node.nodedef_icon) {
3762 panelIcon.src = node.nodedef_icon;
3763 }
3764 else if (this.ui.icon_map) {
3765 if (!node || node.type == 'graph/subgraph') {
3766 panelIcon.src = this.ui.icon_map['_default_graph_'];
3767 } else {
3768 panelIcon.src = this.ui.icon_map['_default_'];
3769 }
3770 }
3771
3772 propertypanelcontent.innerHTML = "";
3773
3774 let colorSpaces = this.handler.getColorSpaces();
3775 let targetUnits = this.handler.getUnits();
3776
3777 if (!node && graphcanvas.graph._subgraph_node) {
3778 node = graphcanvas.graph._subgraph_node;
3779 //console.log('In subgraph but no node selected. Select subgraph node', node)
3780 }
3781 else if (!node && !graphcanvas.graph._is_subgraph) {
3782 var docInfo = [['Colorspace', this.getSourceColorSpace()],
3783 ['Distance', this.getTargetDistanceUnit()]];
3784
3785 for (let item of docInfo) {
3786
3787 let elem = document.createElement("div");
3788 elem.className = "row px-1 py-0";
3789 let label = document.createElement("div");
3790 label.className = "col py-0 col-form-label-sm text-left";
3791 label.style = this.fontSizeStyle;
3792 label.innerHTML = "<b>" + item[0] + "</b>";
3793 elem.appendChild(label);
3794
3795 if (item[0] == 'Colorspace' && colorSpaces.length > 0) {
3796 // Create colorspace drop down
3797 var inputCol = document.createElement("div");
3798 inputCol.className = "col text-left";
3799 var select = this.createColorSpaceInput(colorSpaces, item[1]);
3800 select.onchange = function (e) {
3801 MxShadingGraphEditor.theEditor.setSourceColorSpace(e.target.value);
3802 }
3803 inputCol.appendChild(select);
3804 elem.appendChild(inputCol);
3805 }
3806 else if (item[0] == 'Distance' && targetUnits.length > 0) {
3807 // Create units drop down
3808 var inputCol = document.createElement("div");
3809 inputCol.className = "col text-left";
3810 var select = this.createUnitsInput(targetUnits, 'distance', item[1]);
3811 select.onchange = function (e) {
3812 MxShadingGraphEditor.theEditor.setTargetDistanceUnit(e.target.value);
3813 }
3814 inputCol.appendChild(select);
3815 elem.appendChild(inputCol);
3816 }
3817 /* var inputCol = document.createElement("div");
3818 inputCol.className = "col text-left";
3819 var nameInput = document.createElement("input");
3820 nameInput.type = "text";
3821 nameInput.value = item[1];
3822 nameInput.className = "form-control form-control-sm";
3823 nameInput.disabled = true;
3824 elem.appendChild(inputCol);
3825 inputCol.appendChild(nameInput);
3826 }
3827 */
3828 propertypanelcontent.appendChild(elem);
3829 }
3830 return;
3831 }
3832
3833 var _category = node.nodedef_node;
3834 var _type = node.nodedef_type;
3835
3836 var isNodeGraph = node.type == 'graph/subgraph';
3837 if (isNodeGraph) {
3838 //console.log('>> Update subgraph property panel:', node);
3839 _category = 'nodegraph';
3840 if (node.outputs) {
3841 if (node.outputs.length > 1) {
3842 _type = 'multi';
3843 }
3844 else if (node.outputs.length > 0) {
3845 _type = node.outputs[0].type;
3846 }
3847 }
3848 else {
3849 _type = '';
3850 }
3851 }
3852 else {
3853 if (_category == 'surfacematerial') {
3854 _type = '';
3855 }
3856 }
3857
3858 // Identification row
3859 var elem = document.createElement("div");
3860 elem.className = "row px-1 py-1";
3861
3862 // Node category and output type
3863 var label = document.createElement("div");
3864 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
3865 label.style = this.fontSizeStyle;
3866 label.innerHTML = "<b>" + _category;
3867 if (_type.length > 0) {
3868 label.innerHTML += '<br>' + _type;
3869 }
3870 label.innerHTML += "</b>";
3871 elem.appendChild(label);
3872
3873 // Node name / title
3874 var inputCol = document.createElement("div");
3875 inputCol.className = "col py-0";
3876 var nameInput = document.createElement("input");
3877 nameInput.style = this.fontSizeStyle;
3878 nameInput.type = "text";
3879 nameInput.value = node.title;
3880 nameInput.className = "form-control form-control-sm";
3881 let that = this;
3882 nameInput.onchange = function (e) {
3883 var oldTitle = node.title;
3884 var newTitle = MxShadingGraphEditor.theEditor.handler.createValidName(e.target.value);
3885 if (newTitle != oldTitle)
3886 {
3887 that.monitor.onNodeRenamed(node, newTitle);
3888 node.title = newTitle;
3889 }
3890 e.target.value = node.title;
3891 //console.log('node.graph._is_subgraph:', node)
3892 if (node.graph._is_subgraph) {
3893 if (node.nodedef_node == 'input') {
3894 //console.log('-------- Rename subgraph input:', node.title);
3895 node.graph.renameInput(oldTitle, node.title);
3896 }
3897 else if (node.nodedef_node == 'output') {
3898 //console.log('----------- Rename subgraph output:', node.title);
3899 node.graph.renameOutput(oldTitle, node.title);
3900 }
3901 }
3902
3903 // Note: there is a custom size fo subgraphs.
3904 node.setSize(node.computeSize());
3905 node.setDirtyCanvas(true, true);
3906 }
3907 inputCol.appendChild(nameInput);
3908
3909 // TODO: Generate swatches on the fly
3910 if (node.nodedef_node != 'input' && node.nodedef_node != 'output'
3911 && node.type != 'graph/subgraph') {
3912 var imagePreview = document.createElement("img");
3913 imagePreview.src = "./Icons/no_image.png";
3914 var previewSet = false;
3915 //console.log('Check for preview:', node.nodedef_swatch, 'category:', _category)
3916 imagePreview.style.display = "none";
3917 imagePreview.src = "./Icons/no_image.png";
3918 /* if (node.nodedef_swatch &&
3919 (_type == 'BSDF' || _type == 'EDF' || _type == 'surfaceshader'))
3920 {
3921 this.uriExists(node.nodedef_swatch)
3922 .then(exists => {
3923 if (exists) {
3924 previewSet = true;
3925 imagePreview.style.display = "block";
3926 imagePreview.src = node.nodedef_swatch;
3927 }
3928 });
3929 } */
3930 imagePreview.id = "propertypanel_preview";
3931 imagePreview.className = "img-fluid form-control form-control-sm";
3932 inputCol.appendChild(imagePreview);
3933 }
3934
3935 elem.appendChild(label);
3936 elem.appendChild(inputCol);
3937
3938 // Toggle show/hide of inputs with default values
3939 if (!isNodeGraph)
3940 {
3941 var filterCol = document.createElement("div");
3942 filterCol.className = "col-2 py-0";
3943 filterCol.width = 16;
3944 var filterIcon = document.createElement("button");
3945 //filterIcon.setAttribute(data-bs-toggle, "tooltip");
3946 //filterIcon.setAttribute(data-bs-title, "Show/Hide Default Value Inputs");
3947 if (node.showDefaultValueInputs == null)
3948 {
3949 node.showDefaultValueInputs = true;
3950 }
3951 var img = document.createElement("img");
3952 if (node.showDefaultValueInputs)
3953 {
3954 img.src = "./Icons/funnel_white.svg";
3955 filterIcon.className = "btn btn-sm btn-outline-secondary";
3956 }
3957 else
3958 {
3959 img.src = "./Icons/funnel-fill_white.svg";
3960 filterIcon.className = "btn btn-sm btn-outline-warning";
3961 }
3962 filterIcon.appendChild(img);
3963 filterIcon.onclick = function (e) {
3964 node.showDefaultValueInputs = !node.showDefaultValueInputs;
3965 MxShadingGraphEditor.theEditor.updatePropertyPanel(node);
3966 }
3967 filterCol.appendChild(filterIcon);
3968 elem.appendChild(filterCol);
3969 }
3970
3971 propertypanelcontent.appendChild(elem);
3972
3973 var hr = document.createElement("hr");
3974 hr.classList.add("my-1");
3975 propertypanelcontent.appendChild(hr);
3976
3977 var current_details = null;
3978 var first_details = true;
3979 var nodeInputs = node.inputs
3980
3981 let targetNodes = [];
3982 for (var i in nodeInputs) {
3983 let nodeInput = nodeInputs[i];
3984
3985 let inputName = nodeInput.name;
3986 let nodeInputLink = nodeInput.link;
3987 let uiName = inputName;
3988 // remove "_" from uiName
3989 uiName = uiName.replace(/_/g, ' ');
3990 let uimin = null;
3991 let uimax = null;
3992 let colorspace = '';
3993 let units = '';
3994 let defaultgeomprop = '';
3995
3996 //console.log('Scan input:', inputName, ' on node: ', node.graph);
3997
3998 let property_info = node.getPropertyInfo(inputName);
3999 let ng_property_info = null;
4000 if (isNodeGraph)
4001 {
4002 //console.log('Check subgraph input node property_info:')
4003 let sg = node.subgraph;
4004 if (sg)
4005 {
4006 let sg_nodes = sg._nodes;
4007 for (var sg_node of sg_nodes)
4008 {
4009 if (sg_node.title == inputName)
4010 {
4011 //console.log('Found subgraph node:', sg_node.title);
4012 ng_property_info = sg_node.getPropertyInfo("in");
4013 if (ng_property_info)
4014 {
4015 //console.log('Use subgraph node input property info:', ng_property_info);
4016 break;
4017 }
4018 }
4019 }
4020 }
4021 }
4022 //console.log('1. get property info for i: ', inputName, 'info: ', property_info)
4023
4024 var skipInterorConnectedInput = false;
4025 if (node.graph._is_subgraph) {
4026 // Find input on subgraph node
4027 //console.log('Check subgraph for link:', node.graph)
4028 var sg_node = node.graph._subgraph_node;
4029 if (sg_node) {
4030 //console.log('Check for input on sg node', sg_node, node.title);
4031 var slot = sg_node.findInputSlot(node.title);
4032 if (slot != null) {
4033 if (sg_node.inputs) {
4034 //property_info = sg_node.properties_info[slot];
4035 var slotInput = sg_node.inputs[slot];
4036 //console.log('check slot: ', slotInput.link);
4037 if (slotInput != null && slotInput.link != null) {
4038 skipInterorConnectedInput = true;
4039 }
4040 }
4041 else {
4042 //console.log('Error: no subgraph node inputs for subgraph input!', sg_node, node.title);
4043 }
4044 }
4045 }
4046 }
4047
4048 if (skipInterorConnectedInput) {
4049 console.log('Skip interior connected input: ', nodeInput);
4050 continue;
4051 }
4052
4053 //console.log('Property info:', property_info, ' for input:', inputName);
4054 if (ng_property_info) {
4055 //console.log('Replace property info:', property_info);
4056 property_info = ng_property_info;
4057 }
4058 if (property_info) {
4059 //console.log('Extract input property info:', property_info);
4060 if (property_info.defaultgeomprop)
4061 {
4062 defaultgeomprop = property_info.defaultgeomprop;
4063 }
4064 if (property_info.colorspace) {
4065 colorspace = property_info.colorspace;
4066 }
4067 if (property_info.unit) {
4068 units = property_info.unit;
4069 }
4070 if (property_info.uiname) {
4071 uiName = property_info.uiname;
4072 }
4073 if (property_info.uimin) {
4074 uimin = property_info.uimin;
4075 }
4076 if (property_info.uimax) {
4077 uimax = property_info.uimax;
4078 }
4079 if (property_info.uifolder && property_info.uifolder.length > 0) {
4080 // Create a details element
4081 if (current_details == null || current_details.id != property_info.uifolder) {
4082 //console.log('Create new details:', property_info.uifolder);
4083 current_details = document.createElement("details");
4084 current_details.id = property_info.uifolder;
4085 current_details.open = first_details;
4086 current_details.classList.add('w-100', 'p-1', 'border', 'border-secondary', 'rounded', 'my-1');
4087 first_details = false;
4088 var summary = document.createElement('summary')
4089 summary.style = this.fontSizeStyle;
4090 summary.innerHTML = "<b>" + property_info.uifolder + "</b>"
4091 //summary.classList.add('btn', 'btn-sm', 'btn-outline-secondary', 'btn-block');
4092 current_details.appendChild(summary);
4093
4094 }
4095 else {
4096 //current_details = null;
4097 }
4098 }
4099 else {
4100 current_details = null;
4101 }
4102 //console.log('2. uiName:', uiName, 'uimin:', uimin, 'uimax:', uimax, 'uiFolder:', property_info.uifolder);
4103 }
4104 else {
4105 current_details = null;
4106 }
4107
4108 var elem = null;
4109
4110 // Check if there is a link
4111 if (nodeInputLink) {
4112 let upstreamLink = null;
4113
4114 let nodegraph = node.graph;
4115 let link = nodegraph.links[nodeInputLink];
4116 //console.log('link:', link);
4117 let linkId = link && link.origin_id;
4118 let linkNode = linkId && nodegraph.getNodeById(linkId);
4119 if (linkNode) {
4120
4121
4122 //console.log('linkNode:', linkNode);`
4123 let linkSlot = link.origin_slot;
4124 //console.log('linkSlot:', linkSlot);
4125 let linkOutput = linkNode.outputs[linkSlot];
4126 //console.log('linkOutput:', linkOutput);
4127 upstreamLink = linkNode.title + '.' + linkOutput.name;
4128 //console.log('upstreamLink:', upstreamLink);
4129
4130 let id = "__pp:" + inputName;
4131 let buttonText = upstreamLink;
4132 // Truncate long names
4133 if (buttonText.length > 15) {
4134 buttonText = buttonText.substring(0, 15) + "...";
4135 }
4136 let input = this.createButtonWithImageAndText("./Icons/arrow_up_white.svg", buttonText, id);
4137
4138 input.onclick = function (e) {
4139
4140 var inputName = e.target.id;
4141 inputName = inputName.replace('__pp:', '');
4142 inputName = inputName.replace('_text', '');
4143 inputName = inputName.replace('_img', '');
4144 console.log('Clicked traversal button:', inputName);
4145
4146 console.log('Jump to node:', linkNode.title);
4147 graphcanvas.selectNodes([linkNode]);
4148 MxShadingGraphEditor.theEditor.centerNode()
4149 MxShadingGraphEditor.theEditor.updatePropertyPanel(linkNode);
4150 node.setDirtyCanvas(true, true);
4151 }
4152
4153 // Add new row
4154 elem = document.createElement("div");
4155 elem.className = "row px-1 py-0";
4156
4157 input.id = "__pp:" + inputName;
4158
4159 var label = document.createElement("div");
4160 // invert-button
4161 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4162 label.style = this.fontSizeStyle;
4163 label.innerHTML = uiName;
4164 label.for = input.id;
4165 elem.appendChild(label);
4166
4167 // form-control
4168 if (useFormControl) {
4169 input.classList.add("form-control");
4170 }
4171 input.classList.add("form-control-sm");
4172 // Disable if don't want interaction.
4173 if (!graphcanvas.allow_interaction)
4174 input.disabled = true;
4175
4176 var propvalue = document.createElement("div");
4177 propvalue.className = "col p-1";
4178 propvalue.appendChild(input);
4179
4180 elem.appendChild(propvalue);
4181 }
4182 }
4183
4184 else {
4185
4186 targetNodes[i] = node;
4187 let targetNode = targetNodes[i];
4188 let propertyKey = inputName;
4189
4190 var property = targetNode.properties[inputName];
4191 if (property == null) {
4192 if (isNodeGraph) {
4193 var subgraph = targetNode.subgraph;
4194 if (subgraph) {
4195 //console.log('Find node by title', inputName, ' in subgraph', subgraph._nodes);
4196 var subNode = subgraph.findNodeByTitle(inputName);
4197 if (subNode) {
4198 targetNodes[i] = subNode;
4199 propertyKey = 'in';
4200 property = targetNodes[i].properties['in'];
4201 //console.log('Route to subgraph target node:', targetNode, targetNode.title, '. ', inputName, ' = ', JSON.stringify(property), 'propkey=', propertyKey);
4202 }
4203 }
4204 }
4205 if (property == null) {
4206 console.log('Update: Cannot find property value for input:', inputName);
4207 continue;
4208 }
4209 }
4210
4211 // Check if there is a default property value. If so skip showing it
4212 if (defaultgeomprop)
4213 {
4214 //console.log('Skip input with defaultgeomprop: ' + inputName);
4215 continue;
4216 }
4217
4218 // Check if property value is same as property info default value
4219 if (!node.showDefaultValueInputs && !isNodeGraph)
4220 {
4221 let isDefault = node.isDefaultValue(inputName);
4222 if (isDefault)
4223 {
4224 continue;
4225 }
4226 }
4227
4228 // Add new row
4229 elem = document.createElement("div");
4230 elem.className = "row px-1 py-0";
4231
4232 var input = null;
4233 var input_btn = null;
4234 let input_slider = null;
4235 var colorspace_unit_btn = null;
4236 var useFormControl = true;
4237
4238 // Add colorspace drop-down if specified.
4239 if (colorspace.length > 0) {
4240 // Create drop-down menu to choose colorspace from list stored
4241 // in the handler class getColorSpaces() method.
4242 //
4243 colorspace_unit_btn = this.createColorSpaceInput(colorSpaces, colorspace);
4244 let theNode = targetNodes[i];
4245 colorspace_unit_btn.onchange = function (e) {
4246
4247 theNode.setPropertyInfo(inputName, 'colorspace', e.target.value);
4248 }
4249 }
4250 else if (units.length > 0 && property_info.unittype) {
4251 // Add units drop-down if specified.
4252 colorspace_unit_btn = this.createUnitsInput(targetUnits, property_info.unittype, units);
4253 let theNode = targetNodes[i];
4254 colorspace_unit_btn.onchange = function (e) {
4255 theNode.setPropertyInfo(inputName, 'unit', e.target.value);
4256 }
4257 }
4258
4259 let proptype = nodeInput.type;
4260 if (proptype == 'float' || proptype == 'integer') {
4261 var isFloat = proptype == 'float';
4262
4263 input = document.createElement("input");
4264 input.id = propertyKey + '_box';
4265 input.style = this.fontSizeStyle;
4266 input.type = 'number';
4267 input.classList.add("form-control", "form-control-sm", "ps-0");
4268 input.setAttribute('propertyKey', propertyKey);
4269
4270 input_slider = document.createElement("input");
4271 input_slider.id = propertyKey + '_slider';
4272 //input_slider.style = this.fontSizeStyle;
4273 input_slider.type = 'range';
4274 input_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4275 input_slider.setAttribute('propertyKey', propertyKey);
4276
4277 if (uimin) {
4278 input.min = uimin;
4279 }
4280 else {
4281 input.min = Math.min(property, 0);
4282 }
4283 if (uimax) {
4284 input.max = uimax;
4285 }
4286 else {
4287 if (isFloat)
4288 {
4289 input.max = Math.max(property*3, 10.0);
4290 }
4291 else {
4292 input.max = Math.max(property*3, 100);
4293 }
4294 }
4295
4296
4297 input_slider.min = input.min;
4298 input_slider.max = input.max;
4299 if (isFloat) {
4300 input.step = (input.max - input.min) / 100.0;
4301 input_slider.step = input.step;
4302 }
4303 else {
4304 input_slider.step = 1;
4305 input.step = 1;
4306 }
4307
4308 input.value = input_slider.value = property;
4309
4310 /* console.log('> ' + propertyKey + ' - Set up slider: min, max, value',
4311 input_slider.min, input_slider.max, input_slider.value
4312 );
4313 console.log('> ' + propertyKey + ' - Set up box: min, max, value',
4314 input.min, input.max, input.value
4315 ); */
4316
4317 let theBox = input;
4318 let theSlider = input_slider;
4319 let theNode = targetNodes[i];
4320 input_slider.onchange = function (e) {
4321 var pi = e.target.getAttribute('propertyKey');
4322 var val = parseFloat(e.target.value);
4323 theNode.setProperty(pi, val);
4324 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4325 }
4326 input_slider.oninput = function(e) {
4327 var pi = e.target.getAttribute('propertyKey');
4328 var val = parseFloat(e.target.value);
4329 theNode.setProperty(pi, val);
4330 theBox.value = e.target.value;
4331 }
4332
4333 input.onchange = function (e) {
4334 var pi = e.target.getAttribute('propertyKey');
4335 var val = parseFloat(e.target.value);
4336 theNode.setProperty(pi, val);
4337 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4338 }
4339 input.oninput = function(e) {
4340 var pi = e.target.getAttribute('propertyKey');
4341 var val = parseFloat(e.target.value);
4342 theNode.setProperty(pi, val);
4343 theSlider.value = e.target.value;
4344 }
4345 }
4346 else if (proptype == 'string' || proptype == 'filename') {
4347 input = document.createElement("input");
4348 input.style = this.fontSizeStyle;
4349 input.type = "text";
4350 if (proptype == 'filename') {
4351 var curImage = property;
4352 this.updateImagePreview(curImage);
4353
4354 input_btn = document.createElement("button");
4355 input_btn.classList.add("btn", "btn-sm", "btn-outline-secondary");
4356 input_btn.innerHTML = "+";
4357 input_btn.setAttribute('propertyKey', propertyKey);
4358 var fileId = "__pp:" + inputName;
4359 let theNode = targetNodes[i];
4360 input_btn.onclick = function (e) {
4361 var pi = e.target.getAttribute('propertyKey');
4362 MxShadingGraphEditor.theEditor.openImageDialog(theNode, pi, false);
4363 }
4364 }
4365
4366 else
4367 {
4368 // Check if there is a 'enm' property
4369 //console.log('------------------- handle enum property info:', property_info, '. property:', property);
4370 if (property_info && property_info.enum) {
4371
4372 //console.log('----------------- found enum property info:', property_info.enum);
4373
4374 // Create drop-down menu to choose from list stored in the handler class.
4375 input = document.createElement("select");
4376 input.style = this.fontSizeStyle;
4377 input.classList.add("form-control", "form-control-sm");
4378
4379 input.setAttribute('propertyKey', propertyKey);
4380 let theNode = targetNodes[i];
4381 let enums = property_info.enum;
4382 for (let j = 0; j < enums.length; j++) {
4383 let option = document.createElement("option");
4384 option.value = enums[j];
4385 option.text = enums[j];
4386 input.add(option);
4387 }
4388 input.value = property;
4389 input.setAttribute('propertyKey', propertyKey);
4390 input.onchange = function (e) {
4391 var pi = e.target.getAttribute('propertyKey');
4392 theNode.setProperty(pi, e.target.value);
4393 //console.log('Update string property:', pi, theNode.properties[pi])
4394 }
4395 }
4396 }
4397
4398 if (property_info && !property_info.enm) {
4399 input.value = property;
4400 input.setAttribute('propertyKey', propertyKey);
4401 let theNode = targetNodes[i];
4402 let isFilename = proptype == 'filename';
4403 let that = this;
4404 input.onchange = function (e) {
4405 var pi = e.target.getAttribute('propertyKey');
4406 //theNode.properties[pi] = e.target.value;
4407 theNode.setProperty(pi, e.target.value);
4408 if (isFilename) {
4409 //console.log('Update filename property:', pi, theNode.properties[pi])
4410 that.updateImagePreview(e.target.value);
4411 }
4412 else {
4413 //console.log('Update string property:', pi, theNode.properties[pi])
4414 }
4415 }
4416 }
4417 }
4418 else if (proptype == 'boolean') {
4419 //console.log('Add Boolean property:', property);
4420 input = document.createElement("input");
4421 input.style = this.fontSizeStyle;
4422 input.type = "checkbox";
4423 input.classList = "form-check-input";
4424 useFormControl = false;
4425 input.checked = property;
4426 input.setAttribute('propertyKey', propertyKey);
4427 let theNode = targetNodes[i];
4428 input.onchange = function (e) {
4429 var pi = e.target.getAttribute('propertyKey');
4430 //theNode.properties[pi] = e.target.checked;
4431 theNode.setProperty(pi, e.target.checked);
4432 //console.log('Update boolean property:', pi, theNode.properties[pi]);
4433 }
4434 }
4435
4436 else if (proptype == 'vector2' || proptype == 'vector3' || proptype == 'vector4')
4437 {
4438 // Find index of proptype in ['vector2', 'vector3', 'vector4' ]
4439 var vector_size = ['vector2', 'vector3', 'vector4'].indexOf(proptype) + 2;
4440 input = document.createElement("div");
4441 useFormControl = false;
4442
4443 input.className = "row py-1 ps-4 pe-0";
4444
4445 for (let v=0; v<vector_size; v++)
4446 {
4447 //console.log('Vector property:[', 0, '] = ', property[0], proptype)
4448 let subinput = document.createElement("input");
4449 subinput.style = this.fontSizeStyle;
4450 subinput.type = 'number';
4451 subinput.classList.add("form-control");
4452 subinput.classList.add("form-control-sm");
4453 subinput.setAttribute('propertyKey', propertyKey);
4454
4455 let subinput_slider = document.createElement("input");
4456 subinput_slider.id = propertyKey + '_slider';
4457 subinput_slider.type = 'range';
4458 subinput_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4459 subinput_slider.setAttribute('propertyKey', propertyKey);
4460
4461 if (uimin) {
4462 subinput.min = uimin[v];
4463 }
4464 else {
4465 subinput.min = Math.min(property[v]*3, 0);
4466 }
4467 if (uimax) {
4468 subinput.max = uimax[v];
4469 }
4470 else {
4471 subinput.max = Math.max(property[v]*3, 10.0);
4472 }
4473
4474 subinput_slider.min = subinput.min;
4475 subinput_slider.max = subinput.max;
4476 subinput.step = (subinput.max - subinput.min) / 100.0;
4477 subinput_slider.step = subinput.step;
4478
4479 subinput.value = subinput_slider.value = property[v];
4480
4481 let theNode = targetNodes[i];
4482 let vector_index = v;
4483 let theBox = subinput;
4484 let theSlider = subinput_slider;
4485 theBox.onchange = function (e) {
4486 let pi = e.target.getAttribute('propertyKey');
4487 let value = parseFloat(e.target.value);
4488 let newValue = theNode.properties[pi].map(item => item);
4489 newValue[vector_index] = value;
4490 theNode.setProperty(pi, newValue);
4491 //theNode.properties[pi][0] = value;
4492 //console.log('Update Vector property:"', pi, '"', 0, parseFloat(e.target.value), theNode.properties[pi])
4493 }
4494 theBox.oninput = function(e) {
4495 let pi = e.target.getAttribute('propertyKey');
4496 let value = parseFloat(e.target.value);
4497 let newValue = theNode.properties[pi].map(item => item);
4498 newValue[vector_index] = value;
4499 theNode.setProperty(pi, newValue);
4500 theSlider.value = e.target.value;
4501 }
4502
4503 theSlider.onchange = function (e) {
4504 let pi = e.target.getAttribute('propertyKey');
4505 let value = parseFloat(e.target.value);
4506 let newValue = theNode.properties[pi].map(item => item);
4507 newValue[vector_index] = value;
4508 theNode.setProperty(pi, newValue);
4509 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4510 }
4511 theSlider.oninput = function(e) {
4512 let pi = e.target.getAttribute('propertyKey');
4513 let value = parseFloat(e.target.value);
4514 let newValue = theNode.properties[pi].map(item => item);
4515 newValue[vector_index] = value;
4516 theNode.setProperty(pi, newValue);
4517 theBox.value = e.target.value;
4518 }
4519
4520
4521 let propvalue_slider = document.createElement("div");
4522 propvalue_slider.className = "col p-0";
4523 propvalue_slider.appendChild(subinput_slider);
4524
4525 let propvalue_box = document.createElement("div");
4526 propvalue_box.className = "col p-0";
4527 propvalue_box.appendChild(subinput);
4528
4529 let input_row = document.createElement("div");
4530 input_row.className = "row p-0";
4531 input_row.appendChild(propvalue_slider);
4532 input_row.appendChild(propvalue_box);
4533
4534 input.appendChild(input_row);
4535 }
4536 }
4537 else if (proptype == 'color3' || proptype == 'color4') {
4538 input = document.createElement("input");
4539 input.type = "color";
4540 //console.log('set color property:', rgbToHex(property));
4541 if (property.length == 4) {
4542 input.value = this.rgbToHex([ property[0], property[1], property[2] ]);
4543 }
4544 else {
4545 input.value = this.rgbToHex(property);
4546 }
4547 input.setAttribute('propertyKey', propertyKey);
4548 let theNode = targetNodes[i];
4549 input.onchange = function (e) {
4550 // Convert hex to rgb in 0..1 range
4551 var hex = e.target.value;
4552 let fprecision = 4
4553 let rgb = [0,0,0]
4554 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4555 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4556 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4557 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4558 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4559 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4560 if (proptype == 'color4')
4561 rgb[3] = 1.0;
4562
4563 var pi = e.target.getAttribute('propertyKey');
4564 theNode.setProperty(pi, rgb);
4565 }
4566 let func = function (e) {
4567 // Convert hex to rgb in 0..1 range
4568 var hex = e.target.value;
4569 let rgb = [0, 0, 0];
4570 let fprecision = 4
4571 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4572 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4573 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4574 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4575 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4576 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4577 if (proptype == 'color4')
4578 rgb[3] = 1.0;
4579
4580 var pi = e.target.getAttribute('propertyKey');
4581 theNode.setProperty(pi, rgb);
4582 }
4583 input.onchange = func;
4584 input.oninput = func;
4585 }
4586 else {
4587 input = document.createElement("input");
4588 input.style = this.fontSizeStyle;
4589 input.type = "text";
4590 input.value = property;
4591 let propertyKey = inputName;
4592 let theNode = targetNodes[i];
4593 input.onchange = function (e) {
4594 theNode.setProperty(propertyKey, e.target.value);
4595 //theNode.properties[propertyKey] = e.target.value;
4596 }
4597 }
4598
4599 if (input) {
4600 input.id = "__pp:" + inputName;
4601 //console.log('> Add input:', input.id);
4602
4603 var label = document.createElement("div");
4604 label.className = "col-4 p-0 col-form-label-sm text-end";
4605 label.style = this.fontSizeStyle;
4606 label.innerHTML = uiName;
4607 label.for = input.id;
4608 elem.appendChild(label);
4609
4610 // form-control
4611 if (useFormControl) {
4612 input.classList.add("form-control");
4613 }
4614 input.classList.add("form-control-sm");
4615 // Disable if don't want interaction.
4616 if (!graphcanvas.allow_interaction)
4617 input.disabled = true;
4618
4619 var propvalue = document.createElement("div");
4620 propvalue.className = "col py-0";
4621 if (input_slider)
4622 {
4623 propvalue.classList.add('ps-1');
4624 }
4625 propvalue.appendChild(input);
4626
4627 if (input_btn) {
4628 var propbutton = document.createElement("div");
4629 propbutton.className = "col-2 py-0";
4630 //console.log('Add input button:', input_btn);
4631 propbutton.appendChild(input_btn);
4632 elem.appendChild(propbutton);
4633 }
4634 if (colorspace_unit_btn) {
4635 //console.log('Add cs / unit button:', input_btn);
4636 var propbutton = document.createElement("div");
4637 propbutton.className = "col col-form-label-sm";
4638 var details = document.createElement("details");
4639 var summary = document.createElement('summary')
4640 summary.style = this.fontSizeStyle;
4641 if (colorspace.length > 0)
4642 summary.innerHTML = "Colorspace";
4643 else if (targetUnits.length > 0)
4644 summary.innerHTML = "Units";
4645 details.appendChild(summary);
4646 details.appendChild(colorspace_unit_btn);
4647 propbutton.appendChild(details);
4648 propvalue.appendChild(propbutton);
4649 }
4650
4651 if (input_slider)
4652 {
4653 var propvalue_slider = document.createElement("div");
4654 propvalue_slider.className = "col py-0 pe-0";
4655 propvalue_slider.appendChild(input_slider);
4656 elem.appendChild(propvalue_slider);
4657 }
4658
4659 elem.appendChild(propvalue);
4660 }
4661 }
4662 //elem.innerHTML = "<em>" + i + "</em> : " + property;
4663 if (elem) {
4664 if (current_details) {
4665 //console.log('3a. append child to details:', current_details.id, elem, inputName);
4666 current_details.appendChild(elem);
4667 // It current_details not already in the propertypanelcontent, add it.
4668 if (current_details.parentElement == null) {
4669 propertypanelcontent.appendChild(current_details);
4670 }
4671 }
4672 else {
4673 propertypanelcontent.appendChild(elem);
4674 //console.log('3b. append child to parent content:', elem, inputName);
4675 }
4676 }
4677 }
4678
4679 }
4680
4681
4689 initializeLiteGraph(canvas, readOnly = false) {
4690 // Initialize LiteGraph
4691 graph = new LiteGraph.LGraph();
4692 graphcanvas = new LiteGraph.LGraphCanvas(canvas, graph);
4693
4694 if (readOnly)
4695 {
4696 // Put red border around canvas as an indicator
4697 //canvas.style.border = "1px solid #ff1107";
4698 }
4699
4700 //
4701 // Set up graph overrides
4702 //
4703
4704 // Set up connection colors (off = not connected, on = connected)
4705 // TODO: Move this to application site and expose settings to user.
4706 graphcanvas.default_connection_color_byTypeOff = {
4707 integer: "#A32",
4708 float: "#161",
4709 vector2: "#265",
4710 vector3: "#465",
4711 vector4: "#275",
4712 color3: "#37A",
4713 color4: "#69A",
4714 matrix33: "#555",
4715 matrix44: "#666",
4716 string: "#395",
4717 filename: "#888",
4718 boolean: "#060",
4719 };
4720
4721 graphcanvas.default_connection_color_byType = {
4722 integer: "#D52",
4723 float: "#1D1",
4724 vector2: "#4D4",
4725 vector3: "#7D7",
4726 vector4: "#9D9",
4727 color3: "#4AF",
4728 color4: "#6CF",
4729 matrix33: "#AAA",
4730 matrix44: "#BBB",
4731 string: "#3F4",
4732 filename: "#FFF",
4733 boolean: "#0F0",
4734 };
4735
4736 // Making this a no-op as will not use the default panel
4737 graphcanvas.onShowNodePanel = function (node) {
4738 ;
4739 }
4740
4741 // Override to handle node selection
4742 graphcanvas.onNodeSelected = function (node) {
4743 if (MxShadingGraphEditor.theEditor.monitor)
4744 {
4745 let parentGraph = '';
4746 var is_subgraph = graphcanvas.graph._is_subgraph;
4747 if (is_subgraph)
4748 parentGraph = graphcanvas.graph._subgraph_node.title;
4749 MxShadingGraphEditor.theEditor.monitor.onNodeSelected(node, parentGraph);
4750 }
4751 MxShadingGraphEditor.theEditor.updatePropertyPanel(node);
4752 }
4753
4754 // Override to handle node deselection
4755 graphcanvas.onNodeDeselected = function (node) {
4756 if (MxShadingGraphEditor.theEditor.monitor)
4757 {
4758 let parentGraph = '';
4759 var is_subgraph = graphcanvas.graph._is_subgraph;
4760 if (is_subgraph)
4761 parentGraph = graphcanvas.graph._subgraph_node.title;
4762 MxShadingGraphEditor.theEditor.monitor.onNodeDeselected(node, parentGraph);
4763 }
4764 MxShadingGraphEditor.theEditor.updatePropertyPanel(null);
4765 }
4766
4767 // Add monitoring method for property info changes.
4768 // This API does not currently exist in LiteGraph, only getPropertyInfo() does.
4769 LGraphNode.prototype.setPropertyInfo = function(property, propertyInfo, value)
4770 {
4771 var info = null;
4772
4773 if (this.properties_info) {
4774 for (var i = 0; i < this.properties_info.length; ++i) {
4775 if (this.properties_info[i].name == property) {
4776 info = this.properties_info[i];
4777 break;
4778 }
4779 }
4780 }
4781
4782 if (info && info[propertyInfo])
4783 {
4784 if (this.onPropertyInfoChanged)
4785 {
4786 this.onPropertyInfoChanged(property, propertyInfo, value, info[propertyInfo]);
4787 }
4788 info[propertyInfo] = value;
4789 }
4790 else
4791 {
4792 console.warning('Failed to set property: ', property, '. info: ', propertyInfo, '. Value: ', value, '. Infos: ', this.properties_info);
4793 }
4794 }
4795
4796 // Add in a method to check if an input / property is the default value
4797 LGraphNode.prototype.isDefaultValue = function(property)
4798 {
4799 let info = null;
4800
4801 // Check if the property exists
4802 if (this.properties[property] == null)
4803 {
4804 console.warn('> Property value does not exist:', property);
4805 return false;
4806 }
4807 // Check if the property is linked
4808 if (this.getInputLink(property))
4809 {
4810 return false;
4811 }
4812
4813 if (this.properties_info != null)
4814 {
4815 for (let i = 0; i < this.properties_info.length; ++i) {
4816 if (this.properties_info[i].name == property) {
4817 info = this.properties_info[i];
4818 break;
4819 }
4820 }
4821 }
4822
4823 if (info != null && info.default_value != null)
4824 {
4825 let property_string = this.properties[property];
4826 let default_value_string = info.default_value;
4827 let isDefault = false;
4828 if (Array.isArray(default_value_string)) {
4829 default_value_string = default_value_string.map(String); // or .map(element => String(element))
4830 property_string = property_string.map(String); // or .map(element => String(element))
4831 isDefault = (JSON.stringify(default_value_string) == JSON.stringify(property_string));
4832 }
4833 else
4834 {
4835 isDefault = (default_value_string == property_string);
4836 }
4837 return isDefault;
4838 }
4839 else
4840 {
4841 console.warn('> Default value does not exist for:', property);
4842 }
4843 return false;
4844 }
4845
4846 //
4847 // Set up graph
4848 //
4849 graphcanvas.resize();
4850 this.monitor.monitorGraph(graph, true);
4851 graph.arrange(80);
4852
4853 // Run the graph. TODO: Add execution control.
4854 //graph.runStep();
4855
4856 // Override global options
4857 //graphcanvas.hide_unconnected = false;
4858 console.log('> Read only mode: ', readOnly);
4859 graphcanvas.read_only = readOnly;
4860 graphcanvas.allow_interaction = true; // Allow user interaction. TODO: Add option to turn this off
4861 graphcanvas.read_only_select = true; // Allow selection in read-only mode
4862 graphcanvas.allow_dragnodes = !readOnly; // Allow dragging nodes
4863 graphcanvas.allow_searchbox = !readOnly; // Allow search box
4864 graphcanvas.render_connections_arrows = true; // Render connection arrows
4865 graphcanvas.clear_background_color = "#222223"; // Set background color
4866 graphcanvas.max_zoom = 0.15; // Set maximum zoom level
4867 graphcanvas.connections_width = 2; // Set connection width
4868 graphcanvas.render_canvas_border = false; // Set canvas border
4869 graphcanvas.align_to_grid = false; // Align to grid
4870 graphcanvas.render_connection_arrows = false; // Render connection arrows
4871 graphcanvas.render_curved_connections = true; // Render curved connections
4872 //graphcanvas.background_image = null; // Set background image
4873 graphcanvas.show_info = false; // Turn off HUD
4874 graph.ctrl_shift_v_paste_connect_unselected_outputs = true;
4875
4876 //
4877 // Event handler overrides. TODO: Add more shortcuts
4878 //
4879 // Ad event handler to call centerOnNode with f key press within the canvas area
4880 canvas.addEventListener("keydown", function (e) {
4881 if (e.key === "f") {
4882 MxShadingGraphEditor.theEditor.centerNode();
4883 }
4884 });
4885
4886 // Ad event handler to call array with l key press within the canvas area
4887 canvas.addEventListener("keydown", function (e) {
4888 if (e.key === "l") {
4889 MxShadingGraphEditor.theEditor.arrangeGraph();
4890 }
4891 });
4892
4893
4894 var isIdle = true;
4895 var context = canvas.getContext('2d');
4896
4897 function drawstart(event) {
4898 //context.beginPath();
4899 //context.moveTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
4900 console.log('>>>>>>>>>>> draw start');
4901 isIdle = false;
4902 }
4903
4904 function drawmove(event) {
4905 if (isIdle) return;
4906 //context.lineTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
4907 //context.stroke();
4908 console.log('>>>>>>>>>>> draw move');
4909 }
4910
4911 function drawend(event) {resources/Lights/rural_crossroads_1k
4912 if (isIdle) return;
4913 drawmove(event);
4914 console.log('>>>>>>>>>>> draw move');
4915 isIdle = true;
4916 }
4917
4918 function touchstart(event) {
4919 drawstart(event.touches[0]);
4920 }
4921
4922 function touchmove(event) {
4923 drawmove(event.touches[0]);
4924 //event.preventDefault();
4925 }
4926
4927 function touchend(event) {
4928 drawend(event.changedTouches[0]);
4929 }
4930
4931 //canvas.addEventListener('touchstart', touchstart, false);
4932 //canvas.addEventListener('touchmove', touchmove, false);
4933 //canvas.addEventListener('touchend', touchend, false);
4934
4935 //canvas.addEventListener('mousedown', drawstart, false);
4936 //canvas.addEventListener('mousemove', drawmove, false);
4937 //canvas.addEventListener('mouseup', drawend, false);
4938
4939 }
4940
4944 centerNode() {
4945 var selected = graphcanvas.selected_nodes;
4946 var haveSelected = false;
4947 for (var s in selected) {
4948 haveSelected = true;
4949 break;
4950 }
4951 console.log('Center nodes:', selected, '. Have selected:', haveSelected);
4952 graphcanvas.centerOnGraph(haveSelected);
4953 }
4954
4958 clearNodeTypes() {
4959 LiteGraph.searchbox_extras = [];
4960 var nodeTypes = LiteGraph.registered_node_types;
4961 for (var typeName in nodeTypes) {
4962 if (typeName !== "graph/subgraph") {
4963 console.log('Removing node type:', LiteGraph.getNodeType(typeName));
4964 LiteGraph.unregisterNodeType(typeName);
4965 }
4966 }
4967 }
4968
4972 collapseNode(node, collapse) {
4973 if (node.constructor.collapsable === false) {
4974 return false;
4975 }
4976 if (node.flags.collapsed != collapse) {
4977 node.flags.collapsed = collapse;
4978 return true;
4979 }
4980 return false;
4981 }
4982
4986 collapseExpandNodes(collapse) {
4987 var curGraph = graphcanvas.graph;
4988
4989 var selected_nodes = graphcanvas.selected_nodes;
4990 //console.log('Selected nodes:', selected_nodes);
4991 var modified = false;
4992 if (selected_nodes) {
4993 for (var i in selected_nodes) {
4994 var node = selected_nodes[i];
4995 //console.log('Collapse/Expand:', node.title, collapse);
4996 if (this.collapseNode(node, collapse))
4997 modified = true;
4998 }
4999 }
5000 if (!modified) {
5001 var nodes = curGraph._nodes;
5002 for (var i in nodes) {
5003 var node = nodes[i];
5004 if (this.collapseNode(node, collapse))
5005 modified = true;
5006 }
5007 }
5008
5009 if (modified) {
5010 graph._version++;
5011 graph.setDirtyCanvas(true, true);
5012 }
5013 }
5014
5018 copyToClipboard() {
5019 graphcanvas.copyToClipboard();
5020 }
5021
5025 pasteFromClipboard() {
5026 graphcanvas.pasteFromClipboard(true);
5027 }
5028
5032 extractNodeGraph() {
5033 var selected = graphcanvas.selected_nodes;
5034 if (selected.length == 0) {
5035 console.log('No nodes selected.');
5036 return;
5037 }
5038
5039 var subgraphsSelected = []
5040 for (var i in selected) {
5041 var node = selected[i];
5042 if (node.type == 'graph/subgraph') {
5043 subgraphsSelected.push(node);
5044 }
5045 }
5046 if (subgraphsSelected.length == 0) {
5047 console.log('No subgraphs selected.');
5048 return;
5049 }
5050
5051 // Select subgraph nodes
5052 var subGraph = subgraphsSelected[0];
5053 var subGraphNodes = subGraph.subgraph._nodes;
5054 for (var i in subGraphNodes) {
5055 var node = subGraphNodes[i];
5056 //console.log('Select subgraph node:', node.title);
5057 }
5058
5059 graphcanvas.openSubgraph(subGraph.subgraph);
5060 graphcanvas.selectNodes(subGraphNodes);
5061 // Copy the selected nodes to the clipboard
5062 graphcanvas.copyToClipboard();
5063
5064 // Paste the copied nodes into the graph
5065 graphcanvas.closeSubgraph();
5066 graphcanvas.pasteFromClipboard();
5067 }
5068
5073 createNodeGraph() {
5074 // Disallow testing for now.
5075 if (graphcanvas.graph._is_subgraph) {
5076 this.debugOutput('Cannot create nest subgraphs.', 1);
5077 return;
5078 }
5079
5080 // Check for selected nodes
5081 var selected = graphcanvas.selected_nodes;
5082 if (selected.length == 0) {
5083 console.log('No nodes selected.');
5084 return;
5085 }
5086
5087 // Copy the selected nodes to the clipboard
5088 graphcanvas.copyToClipboard();
5089
5090 // Create a new graph/subgraph node
5091 var node = LiteGraph.createNode('graph/subgraph');
5092 graph.add(node);
5093 node.title = MxShadingGraphEditor.theEditor.handler.createValidName('group');
5094 // Open subgraph
5095 graphcanvas.openSubgraph(node.subgraph);
5096 // Paste the copied nodes into the subgraph
5097 graphcanvas.pasteFromClipboard();
5098
5099 node.subgraph.arrange(80);
5100 graphcanvas.ds.reset();
5101 graphcanvas.setDirty(true, true);
5102 }
5103
5107 displayNodeTypes() {
5108 // Get the node list display updater
5109 var nodeTypesListUpdater = this.ui.nodeTypesListUpdater;
5110 if (!nodeTypesListUpdater) {
5111 return;
5112 }
5113
5114 // Get the list of available node types
5115 var nodeTypes = LiteGraph.registered_node_types;
5116 nodeTypesListUpdater(nodeTypes);
5117 }
5118
5128 initialize(canvas, ui, monitor, materialFilename, readOnly = false) {
5129
5130 this.setUI(ui);
5131 if (monitor) {
5132 console.log('Set custom monitor:', monitor.getName());
5133 }
5134 this.monitor = monitor;
5135 this.initializeLiteGraph(canvas, readOnly);
5136 this.handler.initialize(MxShadingGraphEditor.theEditor, materialFilename);
5137 this.handler.setMonitor(this.monitor);
5138 }
5139}
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.
loadFromZip(extension, file, fileName, editor, auto_arrange)
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.
Utility class for unzipping ZIP which contain MaterialX files and dependent images .
static async unzipMaterialXData(zipData)
Unzips the given ZIP data and returns the MaterialX documents and textures.