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