MaterialXLab API  0.0.1
APIs For MaterialXLab Libraries
Loading...
Searching...
No Matches
MxShadingGraphEditor Class Reference

This class is a wrapper around the LiteGraph library to provide a MaterialX node editor. More...

Public Member Functions

 constructor ()
 Default constructor.
 
 setUI (ui)
 Set the UI callbacks for the editor.
 
 setDirty (w=null, h=null)
 Notify the editor to update it's graph.
 
 debugOutput (text, severity, clear=null)
 Output debug information to the console or the UI console logger.
 
 setSourceColorSpace (colorSpace)
 Set global color space for the editor.
 
 setTargetDistanceUnit (unit)
 Set the global distance unit for the editor.
 
 getSourceColorSpace ()
 Get the global color space for the editor.
 
 getTargetDistanceUnit ()
 Get the global distance unit for the editor.
 
 searchGraph (title)
 Search the graph for the specified node.
 
 arrangeGraph (spacing=80)
 Auto layout the nodes in the graph.
 
 openSubgraph ()
 Open the subgraph for the selected node.
 
 closeSubgraph ()
 Close the current subgraph open, if any.
 
 resetView ()
 Reset the view of the graph.
 
 selectNodes ()
 Select all nodes in the graph.
 
 clearGraph ()
 Reset the graph editor.
 
 saveSerialization ()
 Use built-in serialization to save the graph to file.
 
 loadSerialization ()
 Use built-in serialization to load the graph from file.
 
 saveGraphToFile (extension, graphWriteOptions)
 Save the graph to a file with the specified extension.
 
 saveGraphToString (extension, graphWriteOptions)
 Save the graph to a string with the specified extension.
 
 loadDefinitionsFromFile (extension)
 Load the graph from a file with the specified extension.
 
 loadGraphFromFile (extension, auto_arrange, rerender=false)
 Load the graph from a file with the specified extension.
 
 findRenderableItems ()
 Find all renderable elements in the graph using the handler.
 
 loadGraphFromString (extension, content, fileName, auto_arrange, rerender=false)
 Load the graph from a string with the specified extension.
 
 rgbToHex (rgb)
 Utility to convert a color from RGB to hex.
 
 createButtonWithImageAndText (imageSrc, text, id)
 Create a DOM button with an image and text.
 
 openImageDialog (theNode, updateProp, wantURI)
 Open a dialog to select a image file to load.
 
 uriExists (uri)
 Check if a URI exists.
 
 createColorSpaceInput (colorSpaces, activeItem)
 Create a color space input element.
 
 createUnitsInput (units, unittype, activeItem)
 Create a units input element.
 
 updateImagePreview (curImage)
 Update the image preview in the property panel.
 
 updatePropertyPanel (node)
 This method is called when a node is selected in the graph.
 
 initializeLiteGraph (canvas, readOnly=false)
 Initialize the LiteGraph graph editor for a given canvas element.
 
 centerNode ()
 Center the graph on a selected node.
 
 clearNodeTypes ()
 Remove built in node types, except for subgraph.
 
 collapseNode (node, collapse)
 Collapse or expand a node.
 
 collapseExpandNodes (collapse)
 Collapse or expand selected nodes.
 
 copyToClipboard ()
 Copy selected nodes to the clipboard.
 
 pasteFromClipboard ()
 Paste nodes from the clipboard.
 
 extractNodeGraph ()
 Extract the nodes in a subgraph to the main graph.
 
 createNodeGraph ()
 Create a new node subgraph from selected.
 
 displayNodeTypes ()
 Display the available node types in the UI if a UI updater is set.
 
 initialize (canvas, ui, monitor, materialFilename, readOnly=false)
 Initialize the editor.
 

Detailed Description

This class is a wrapper around the LiteGraph library to provide a MaterialX node editor.

It is designed to work with the MaterialX JavaScript API.

Definition at line 3292 of file JsMaterialXNodeEditor.js.

Member Function Documentation

◆ arrangeGraph()

MxShadingGraphEditor::arrangeGraph ( spacing = 80)

Auto layout the nodes in the graph.

Parameters
spacing- The spacing between nodes.
Returns
{void}

Definition at line 3452 of file JsMaterialXNodeEditor.js.

3452 {
3453 // This does not track the current subgraph.
3454 if (graphcanvas) {
3455 graphcanvas.graph.arrange(spacing);
3456 }
3457 }
var graphcanvas

◆ centerNode()

MxShadingGraphEditor::centerNode ( )

Center the graph on a selected node.

Definition at line 5218 of file JsMaterialXNodeEditor.js.

5218 {
5219 var selected = graphcanvas.selected_nodes;
5220 var haveSelected = false;
5221 for (var s in selected) {
5222 haveSelected = true;
5223 break;
5224 }
5225 console.log('Center nodes:', selected, '. Have selected:', haveSelected);
5226 graphcanvas.centerOnGraph(haveSelected);
5227 }

◆ clearGraph()

MxShadingGraphEditor::clearGraph ( )

Reset the graph editor.

Will also reset the global color space and distance unit to their default values.

Definition at line 3507 of file JsMaterialXNodeEditor.js.

3507 {
3508
3509 this.handler.sourceColorSpace = this.handler.DEFAULT_COLOR_SPACE;
3510 this.handler.targetDistanceUnits = this.handler.DEFAULT_DISTANCE_UNITS;
3512 this.updatePropertyPanel(null);
3513 if (graphcanvas) {
3514 // Set back to top graph
3515 graphcanvas.setGraph(graph);
3516 graphcanvas.graph.clear();
3517 graphcanvas.ds.reset();
3518 graphcanvas.setDirty(true, true);
3519 }
3520 customDocLibs = [];
3521 }
var customDocLibs
This class is a wrapper around the LiteGraph library to provide a MaterialX node editor.
updatePropertyPanel(node)
This method is called when a node is selected in the graph.

◆ clearNodeTypes()

MxShadingGraphEditor::clearNodeTypes ( )

Remove built in node types, except for subgraph.

Definition at line 5232 of file JsMaterialXNodeEditor.js.

5232 {
5233 LiteGraph.searchbox_extras = [];
5234 var nodeTypes = LiteGraph.registered_node_types;
5235 for (var typeName in nodeTypes) {
5236 if (typeName !== "graph/subgraph") {
5237 console.log('Removing node type:', LiteGraph.getNodeType(typeName));
5238 LiteGraph.unregisterNodeType(typeName);
5239 }
5240 }
5241 }

◆ closeSubgraph()

MxShadingGraphEditor::closeSubgraph ( )

Close the current subgraph open, if any.

Definition at line 3476 of file JsMaterialXNodeEditor.js.

3476 {
3477 if (graphcanvas) {
3478 graphcanvas.closeSubgraph();
3479 }
3480 }

◆ collapseExpandNodes()

MxShadingGraphEditor::collapseExpandNodes ( collapse)

Collapse or expand selected nodes.

Definition at line 5260 of file JsMaterialXNodeEditor.js.

5260 {
5261 var curGraph = graphcanvas.graph;
5262
5263 var selected_nodes = graphcanvas.selected_nodes;
5264 //console.log('Selected nodes:', selected_nodes);
5265 var modified = false;
5266 if (selected_nodes) {
5267 for (var i in selected_nodes) {
5268 var node = selected_nodes[i];
5269 //console.log('Collapse/Expand:', node.title, collapse);
5270 if (this.collapseNode(node, collapse))
5271 modified = true;
5272 }
5273 }
5274 if (!modified) {
5275 var nodes = curGraph._nodes;
5276 for (var i in nodes) {
5277 var node = nodes[i];
5278 if (this.collapseNode(node, collapse))
5279 modified = true;
5280 }
5281 }
5282
5283 if (modified) {
5284 graph._version++;
5285 graph.setDirtyCanvas(true, true);
5286 }
5287 }
collapseNode(node, collapse)
Collapse or expand a node.

◆ collapseNode()

MxShadingGraphEditor::collapseNode ( node,
collapse )

Collapse or expand a node.

Definition at line 5246 of file JsMaterialXNodeEditor.js.

5246 {
5247 if (node.constructor.collapsable === false) {
5248 return false;
5249 }
5250 if (node.flags.collapsed != collapse) {
5251 node.flags.collapsed = collapse;
5252 return true;
5253 }
5254 return false;
5255 }

◆ constructor()

MxShadingGraphEditor::constructor ( )

Default constructor.

Initializes the handler along with any converters

Definition at line 3297 of file JsMaterialXNodeEditor.js.

3297 {
3298 if (!MxShadingGraphEditor.theEditor) {
3299 MxShadingGraphEditor.theEditor = this;
3300
3301 this.ui = null;
3302 this.fontSizeStyle = 'font-size: 11px;';
3303
3304 this.handler = new MxMaterialXHandler('MaterialX Handler', 'mtlx');
3305 let gltfConverter = new glTFMaterialX();
3306 this.handler.addConverter(gltfConverter);
3307
3308 console.log('Create new editor with exporter for:', gltfConverter.exportType());
3309
3310 }
3311 return MxShadingGraphEditor.theEditor;
3312 }
This class extends the MxGraphHandler class to provide MaterialX-specific functionality for handling ...

◆ copyToClipboard()

MxShadingGraphEditor::copyToClipboard ( )

Copy selected nodes to the clipboard.

Definition at line 5292 of file JsMaterialXNodeEditor.js.

5292 {
5293 graphcanvas.copyToClipboard();
5294 }

◆ createButtonWithImageAndText()

MxShadingGraphEditor::createButtonWithImageAndText ( imageSrc,
text,
id )

Create a DOM button with an image and text.

Parameters
imageSrc- The source of the image.
text- The text to display.
id- The identifier for the button.
Returns
{Element} The button element.

Definition at line 3710 of file JsMaterialXNodeEditor.js.

3710 {
3711 // Create image element
3712 var img = document.createElement("img");
3713 img.id = id + "_img";
3714 img.src = imageSrc;
3715 img.classList.add("img-fluid");
3716
3717 // Create text element
3718 var span = document.createElement("span");
3719 span.id = id + "_text";
3720 span.textContent = " " + text;
3721
3722 // Create button element
3723 var button = document.createElement("button");
3724 button.id = id;
3725 button.classList.add("btn", "btn-sm", "btn-outline-secondary", "form-control", "form-control-sm");
3726 button.style = this.fontSizeStyle;
3727 button.appendChild(img);
3728 button.appendChild(span);
3729
3730 return button;
3731 }

◆ createColorSpaceInput()

MxShadingGraphEditor::createColorSpaceInput ( colorSpaces,
activeItem )

Create a color space input element.

Parameters
colorSpaces- The list of color spaces.
activeItem- The selected color space.
Returns
{Element} The color space input element.

Definition at line 3816 of file JsMaterialXNodeEditor.js.

3816 {
3817 var select = document.createElement("select");
3818 select.className = "form-control form-control-sm";
3819 select.style = this.fontSizeStyle;
3820 select.id = "propertypanel_colorspace";
3821 for (var i = 0; i < colorSpaces.length; i++) {
3822 var option = document.createElement("option");
3823 option.value = colorSpaces[i];
3824 option.text = colorSpaces[i];
3825 select.add(option);
3826 }
3827 // Add "none" option
3828 var option = document.createElement("option");
3829 option.value = "none";
3830 option.text = "none";
3831 select.add(option);
3832
3833 select.value = activeItem;
3834 return select;
3835 }

◆ createNodeGraph()

MxShadingGraphEditor::createNodeGraph ( )

Create a new node subgraph from selected.

TODO: Handle connections between new graph and parent graph.

Definition at line 5347 of file JsMaterialXNodeEditor.js.

5347 {
5348 // Disallow testing for now.
5349 if (graphcanvas.graph._is_subgraph) {
5350 this.debugOutput('Cannot create nest subgraphs.', 1);
5351 return;
5352 }
5353
5354 // Check for selected nodes
5355 var selected = graphcanvas.selected_nodes;
5356 if (selected.length == 0) {
5357 console.log('No nodes selected.');
5358 return;
5359 }
5360
5361 // Copy the selected nodes to the clipboard
5362 graphcanvas.copyToClipboard();
5363
5364 // Create a new graph/subgraph node
5365 var node = LiteGraph.createNode('graph/subgraph');
5366 graph.add(node);
5367 node.title = MxShadingGraphEditor.theEditor.handler.createValidName('group');
5368 // Open subgraph
5369 graphcanvas.openSubgraph(node.subgraph);
5370 // Paste the copied nodes into the subgraph
5371 graphcanvas.pasteFromClipboard();
5372
5373 node.subgraph.arrange(80);
5374 graphcanvas.ds.reset();
5375 graphcanvas.setDirty(true, true);
5376 }
debugOutput(text, severity, clear=null)
Output debug information to the console or the UI console logger.
openSubgraph()
Open the subgraph for the selected node.

◆ createUnitsInput()

MxShadingGraphEditor::createUnitsInput ( units,
unittype,
activeItem )

Create a units input element.

Parameters
units- The list of units.
unittype- The type of unit.
activeItem- The selected unit.
Returns
{Element} The units input element.

Definition at line 3845 of file JsMaterialXNodeEditor.js.

3845 {
3846 var select = document.createElement("select");
3847 select.className = "form-control form-control-sm";
3848 select.style = this.fontSizeStyle;
3849 select.id = "propertypanel_units";
3850 for (var i = 0; i < units.length; i++) {
3851 var option = document.createElement("option");
3852 var unit_pair = units[i];
3853 if (unit_pair[1] == unittype) {
3854 option.value = unit_pair[0];
3855 option.text = unit_pair[0];
3856 select.add(option);
3857 }
3858 }
3859 select.value = activeItem;
3860 return select;
3861 }

◆ debugOutput()

MxShadingGraphEditor::debugOutput ( text,
severity,
clear = null )

Output debug information to the console or the UI console logger.

Parameters
text- The text to output.
severity- The severity of the message.
clear- True to clear the console, false otherwise.
Returns
{void}

Definition at line 3343 of file JsMaterialXNodeEditor.js.

3343 {
3344 var consoleLog = MxShadingGraphEditor.theEditor.ui.consoleLogger;
3345 if (consoleLog) {
3346 consoleLog(text, severity, clear);
3347 }
3348 else {
3349 console.log('> ', text, ' severity:', severity);
3350 }
3351 }

◆ displayNodeTypes()

MxShadingGraphEditor::displayNodeTypes ( )

Display the available node types in the UI if a UI updater is set.

Definition at line 5381 of file JsMaterialXNodeEditor.js.

5381 {
5382 // Get the node list display updater
5383 var nodeTypesListUpdater = this.ui.nodeTypesListUpdater;
5384 if (!nodeTypesListUpdater) {
5385 return;
5386 }
5387
5388 // Get the list of available node types
5389 var nodeTypes = LiteGraph.registered_node_types;
5390 nodeTypesListUpdater(nodeTypes);
5391 }

◆ extractNodeGraph()

MxShadingGraphEditor::extractNodeGraph ( )

Extract the nodes in a subgraph to the main graph.

Definition at line 5306 of file JsMaterialXNodeEditor.js.

5306 {
5307 var selected = graphcanvas.selected_nodes;
5308 if (selected.length == 0) {
5309 console.log('No nodes selected.');
5310 return;
5311 }
5312
5313 var subgraphsSelected = []
5314 for (var i in selected) {
5315 var node = selected[i];
5316 if (node.type == 'graph/subgraph') {
5317 subgraphsSelected.push(node);
5318 }
5319 }
5320 if (subgraphsSelected.length == 0) {
5321 console.log('No subgraphs selected.');
5322 return;
5323 }
5324
5325 // Select subgraph nodes
5326 var subGraph = subgraphsSelected[0];
5327 var subGraphNodes = subGraph.subgraph._nodes;
5328 for (var i in subGraphNodes) {
5329 var node = subGraphNodes[i];
5330 //console.log('Select subgraph node:', node.title);
5331 }
5332
5333 graphcanvas.openSubgraph(subGraph.subgraph);
5334 graphcanvas.selectNodes(subGraphNodes);
5335 // Copy the selected nodes to the clipboard
5336 graphcanvas.copyToClipboard();
5337
5338 // Paste the copied nodes into the graph
5339 graphcanvas.closeSubgraph();
5340 graphcanvas.pasteFromClipboard();
5341 }

◆ findRenderableItems()

MxShadingGraphEditor::findRenderableItems ( )

Find all renderable elements in the graph using the handler.

Returns
The list of renderable items.

Definition at line 3659 of file JsMaterialXNodeEditor.js.

3659 {
3660 return this.handler.findRenderableItems(graph);
3661 }

◆ getSourceColorSpace()

MxShadingGraphEditor::getSourceColorSpace ( )

Get the global color space for the editor.

Returns
The color space.

Definition at line 3382 of file JsMaterialXNodeEditor.js.

3382 {
3383 if (this.handler) {
3384 return this.handler.getSourceColorSpace();
3385 }
3386 return 'lin_rec709';
3387 }

◆ getTargetDistanceUnit()

MxShadingGraphEditor::getTargetDistanceUnit ( )

Get the global distance unit for the editor.

Returns
The distance unit.

Definition at line 3394 of file JsMaterialXNodeEditor.js.

3394 {
3395 if (this.handler) {
3396 return this.handler.getTargetDistanceUnit();
3397 }
3398 return 'meter';
3399 }

◆ initialize()

MxShadingGraphEditor::initialize ( canvas,
ui,
monitor,
materialFilename,
readOnly = false )

Initialize the editor.

Parameters
canvasThe canvas element to use for the graph editor.
uiThe UI updater object to use for the editor.
monitorThe monitor object to use for the editor.
materialFilenameThe optional filename of the material to load.
readOnlySet to true to make the editor read only.
Returns
{void}

Definition at line 5402 of file JsMaterialXNodeEditor.js.

5402 {
5403
5404 this.setUI(ui);
5405 if (monitor) {
5406 console.log('Set custom monitor:', monitor.getName());
5407 }
5408 this.monitor = monitor;
5409 this.initializeLiteGraph(canvas, readOnly);
5410 this.handler.initialize(MxShadingGraphEditor.theEditor, materialFilename);
5411 this.handler.setMonitor(this.monitor);
5412 }
initializeLiteGraph(canvas, readOnly=false)
Initialize the LiteGraph graph editor for a given canvas element.
setUI(ui)
Set the UI callbacks for the editor.

◆ initializeLiteGraph()

MxShadingGraphEditor::initializeLiteGraph ( canvas,
readOnly = false )

Initialize the LiteGraph graph editor for a given canvas element.

Parameters
canvasThe canvas element to use for the graph editor.
readOnlyIs the canvas in read-only mode
Returns
{void}

Definition at line 4963 of file JsMaterialXNodeEditor.js.

4963 {
4964 // Initialize LiteGraph
4965 graph = new LiteGraph.LGraph();
4966 graphcanvas = new LiteGraph.LGraphCanvas(canvas, graph);
4967
4968 if (readOnly)
4969 {
4970 // Put red border around canvas as an indicator
4971 //canvas.style.border = "1px solid #ff1107";
4972 }
4973
4974 //
4975 // Set up graph overrides
4976 //
4977
4978 // Set up connection colors (off = not connected, on = connected)
4979 // TODO: Move this to application site and expose settings to user.
4980 graphcanvas.default_connection_color_byTypeOff = {
4981 integer: "#A32",
4982 float: "#161",
4983 vector2: "#265",
4984 vector3: "#465",
4985 vector4: "#275",
4986 color3: "#37A",
4987 color4: "#69A",
4988 matrix33: "#555",
4989 matrix44: "#666",
4990 string: "#395",
4991 filename: "#888",
4992 boolean: "#060",
4993 };
4994
4995 graphcanvas.default_connection_color_byType = {
4996 integer: "#D52",
4997 float: "#1D1",
4998 vector2: "#4D4",
4999 vector3: "#7D7",
5000 vector4: "#9D9",
5001 color3: "#4AF",
5002 color4: "#6CF",
5003 matrix33: "#AAA",
5004 matrix44: "#BBB",
5005 string: "#3F4",
5006 filename: "#FFF",
5007 boolean: "#0F0",
5008 };
5009
5010 // Making this a no-op as will not use the default panel
5011 graphcanvas.onShowNodePanel = function (node) {
5012 ;
5013 }
5014
5015 // Override to handle node selection
5016 graphcanvas.onNodeSelected = function (node) {
5017 if (MxShadingGraphEditor.theEditor.monitor)
5018 {
5019 let parentGraph = '';
5020 var is_subgraph = graphcanvas.graph._is_subgraph;
5021 if (is_subgraph)
5022 parentGraph = graphcanvas.graph._subgraph_node.title;
5023 MxShadingGraphEditor.theEditor.monitor.onNodeSelected(node, parentGraph);
5024 }
5026 }
5027
5028 // Override to handle node deselection
5029 graphcanvas.onNodeDeselected = function (node) {
5030 if (MxShadingGraphEditor.theEditor.monitor)
5031 {
5032 let parentGraph = '';
5033 var is_subgraph = graphcanvas.graph._is_subgraph;
5034 if (is_subgraph)
5035 parentGraph = graphcanvas.graph._subgraph_node.title;
5036 MxShadingGraphEditor.theEditor.monitor.onNodeDeselected(node, parentGraph);
5037 }
5039 }
5040
5041 // Add monitoring method for property info changes.
5042 // This API does not currently exist in LiteGraph, only getPropertyInfo() does.
5043 LGraphNode.prototype.setPropertyInfo = function(property, propertyInfo, value)
5044 {
5045 var info = null;
5046
5047 if (this.properties_info) {
5048 for (var i = 0; i < this.properties_info.length; ++i) {
5049 if (this.properties_info[i].name == property) {
5050 info = this.properties_info[i];
5051 break;
5052 }
5053 }
5054 }
5055
5056 if (info && info[propertyInfo])
5057 {
5058 if (this.onPropertyInfoChanged)
5059 {
5060 this.onPropertyInfoChanged(property, propertyInfo, value, info[propertyInfo]);
5061 }
5062 info[propertyInfo] = value;
5063 }
5064 else
5065 {
5066 console.warning('Failed to set property: ', property, '. info: ', propertyInfo, '. Value: ', value, '. Infos: ', this.properties_info);
5067 }
5068 }
5069
5070 // Add in a method to check if an input / property is the default value
5071 LGraphNode.prototype.isDefaultValue = function(property)
5072 {
5073 let info = null;
5074
5075 // Check if the property exists
5076 if (this.properties[property] == null)
5077 {
5078 console.warn('> Property value does not exist:', property);
5079 return false;
5080 }
5081 // Check if the property is linked
5082 if (this.getInputLink(property))
5083 {
5084 return false;
5085 }
5086
5087 if (this.properties_info != null)
5088 {
5089 for (let i = 0; i < this.properties_info.length; ++i) {
5090 if (this.properties_info[i].name == property) {
5091 info = this.properties_info[i];
5092 break;
5093 }
5094 }
5095 }
5096
5097 if (info != null && info.default_value != null)
5098 {
5099 let property_string = this.properties[property];
5100 let default_value_string = info.default_value;
5101 let isDefault = false;
5102 if (Array.isArray(default_value_string)) {
5103 default_value_string = default_value_string.map(String); // or .map(element => String(element))
5104 property_string = property_string.map(String); // or .map(element => String(element))
5105 isDefault = (JSON.stringify(default_value_string) == JSON.stringify(property_string));
5106 }
5107 else
5108 {
5109 isDefault = (default_value_string == property_string);
5110 }
5111 return isDefault;
5112 }
5113 else
5114 {
5115 console.warn('> Default value does not exist for:', property);
5116 }
5117 return false;
5118 }
5119
5120 //
5121 // Set up graph
5122 //
5123 graphcanvas.resize();
5124 this.monitor.monitorGraph(graph, true);
5125 graph.arrange(80);
5126
5127 // Run the graph. TODO: Add execution control.
5128 //graph.runStep();
5129
5130 // Override global options
5131 //graphcanvas.hide_unconnected = false;
5132 console.log('> Read only mode: ', readOnly);
5133 graphcanvas.read_only = readOnly;
5134 graphcanvas.allow_interaction = true; // Allow user interaction. TODO: Add option to turn this off
5135 graphcanvas.read_only_select = true; // Allow selection in read-only mode
5136 graphcanvas.allow_dragnodes = !readOnly; // Allow dragging nodes
5137 graphcanvas.allow_searchbox = !readOnly; // Allow search box
5138 graphcanvas.render_connections_arrows = true; // Render connection arrows
5139 graphcanvas.clear_background_color = "#222223"; // Set background color
5140 graphcanvas.max_zoom = 0.15; // Set maximum zoom level
5141 graphcanvas.connections_width = 2; // Set connection width
5142 graphcanvas.render_canvas_border = false; // Set canvas border
5143 graphcanvas.align_to_grid = false; // Align to grid
5144 graphcanvas.render_connection_arrows = false; // Render connection arrows
5145 graphcanvas.render_curved_connections = true; // Render curved connections
5146 //graphcanvas.background_image = null; // Set background image
5147 graphcanvas.show_info = false; // Turn off HUD
5148 graph.ctrl_shift_v_paste_connect_unselected_outputs = true;
5149
5150 //
5151 // Event handler overrides. TODO: Add more shortcuts
5152 //
5153 // Ad event handler to call centerOnNode with f key press within the canvas area
5154 canvas.addEventListener("keydown", function (e) {
5155 if (e.key === "f") {
5156 MxShadingGraphEditor.theEditor.centerNode();
5157 }
5158 });
5159
5160 // Ad event handler to call array with l key press within the canvas area
5161 canvas.addEventListener("keydown", function (e) {
5162 if (e.key === "l") {
5164 }
5165 });
5166
5167
5168 var isIdle = true;
5169 var context = canvas.getContext('2d');
5170
5171 function drawstart(event) {
5172 //context.beginPath();
5173 //context.moveTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
5174 console.log('>>>>>>>>>>> draw start');
5175 isIdle = false;
5176 }
5177
5178 function drawmove(event) {
5179 if (isIdle) return;
5180 //context.lineTo(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop);
5181 //context.stroke();
5182 console.log('>>>>>>>>>>> draw move');
5183 }
5184
5185 function drawend(event) {
5186 if (isIdle) return;
5187 drawmove(event);
5188 console.log('>>>>>>>>>>> draw move');
5189 isIdle = true;
5190 }
5191
5192 function touchstart(event) {
5193 drawstart(event.touches[0]);
5194 }
5195
5196 function touchmove(event) {
5197 drawmove(event.touches[0]);
5198 //event.preventDefault();
5199 }
5200
5201 function touchend(event) {
5202 drawend(event.changedTouches[0]);
5203 }
5204
5205 //canvas.addEventListener('touchstart', touchstart, false);
5206 //canvas.addEventListener('touchmove', touchmove, false);
5207 //canvas.addEventListener('touchend', touchend, false);
5208
5209 //canvas.addEventListener('mousedown', drawstart, false);
5210 //canvas.addEventListener('mousemove', drawmove, false);
5211 //canvas.addEventListener('mouseup', drawend, false);
5212
5213 }
centerNode()
Center the graph on a selected node.
arrangeGraph(spacing=80)
Auto layout the nodes in the graph.

◆ loadDefinitionsFromFile()

MxShadingGraphEditor::loadDefinitionsFromFile ( extension)

Load the graph from a file with the specified extension.

Currently only definitions specified using MaterialX are supported.

Parameters
extension- The extension to load the graph from.

Definition at line 3598 of file JsMaterialXNodeEditor.js.

3598 {
3599 if (extension == 'mtlx') {
3600 this.handler.loadDefinitionsFromFile();
3601 }
3602 else
3603 {
3604 this.debugOutput('Unsupported extension for loading definitions: ' + extension, 2, false);
3605 }
3606 }

◆ loadGraphFromFile()

MxShadingGraphEditor::loadGraphFromFile ( extension,
auto_arrange,
rerender = false )

Load the graph from a file with the specified extension.

Parameters
extension- The extension to load the graph from.
auto_arrange- True to auto-arrange the graph, false otherwise.
rerender- True to re-render the scene after loading, false otherwise. Default is false.
Returns
{void}

Definition at line 3616 of file JsMaterialXNodeEditor.js.

3616 {
3617
3618 if (!this.handler.canImport(extension)) {
3619 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3620 return;
3621 }
3622
3623 // Load document from disk.
3624 if (extension == 'mtlx')
3625 {
3626 var input = document.createElement("input");
3627 input.style = this.fontSizeStyle;
3628 input.type = "file";
3629 input.accept = "." + this.handler.getExtension();
3630 input.onchange = function (e) {
3631 var file = e.target.files[0];
3632 console.log('Loading file: ' + file.name);
3633 MxShadingGraphEditor.theEditor.handler.loadFromFile(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange, rerender);
3634 };
3635 input.click();
3636 }
3637 else if (extension == 'zip')
3638 {
3639 var input = document.createElement("input");
3640 input.style = this.fontSizeStyle;
3641 input.type = "file";
3642 input.accept = ".zip";
3643 input.onchange = function (e) {
3644 var file = e.target.files[0];
3645 if (file) {
3646 console.log('Loading zip file: ' + file.name);
3647 MxShadingGraphEditor.theEditor.handler.loadFromZip(extension, file, file.name, MxShadingGraphEditor.theEditor, auto_arrange, rerender);
3648 }
3649 };
3650 input.click();
3651 }
3652 }

◆ loadGraphFromString()

MxShadingGraphEditor::loadGraphFromString ( extension,
content,
fileName,
auto_arrange,
rerender = false )

Load the graph from a string with the specified extension.

Parameters
extension- The extension to load the graph from.
content- The content of the graph.
fileName- The identifier for the source file, or a arbitrary name.
auto_arrange- True to auto-arrange the graph, false otherwise.
rerender- True to re-render the scene after loading, false otherwise. Default is false.
Returns
{void}

Definition at line 3673 of file JsMaterialXNodeEditor.js.

3673 {
3674 if (!this.handler.canImport(extension)) {
3675 this.debugOutput('Unsupported extension for loading graph: ' + extension, 2, false);
3676 return;
3677 }
3678
3679 if (content.length > 0)
3680 this.handler.loadFromString(extension, content, fileName, auto_arrange, rerender);
3681 else
3682 MxShadingGraphEditor.theEditor.debugOutput('No content to load', 2, false);
3683 }

◆ loadSerialization()

MxShadingGraphEditor::loadSerialization ( )

Use built-in serialization to load the graph from file.

Definition at line 3539 of file JsMaterialXNodeEditor.js.

3539 {
3540 MxShadingGraphEditor.theEditor.clearGraph();
3541
3542 var input = document.createElement("input");
3543 input.style = this.fontSizeStyle;
3544 input.type = "file";
3545 input.accept = ".json";
3546 input.onchange = function (e) {
3547 var file = e.target.files[0];
3548 var reader = new FileReader();
3549 reader.onload = function (event) {
3550 var data = JSON.parse(event.target.result);
3551 graph.configure(data);
3552 };
3553 reader.readAsText(file);
3554 };
3555 input.click();
3556 }
clearGraph()
Reset the graph editor.

◆ openImageDialog()

MxShadingGraphEditor::openImageDialog ( theNode,
updateProp,
wantURI )

Open a dialog to select a image file to load.

Parameters
theNode- The node to update.
updateProp- The property to update.
wantURI- True to get the URI, false to get the file.

Definition at line 3740 of file JsMaterialXNodeEditor.js.

3740 {
3741
3742 // Dynamically create a file input element
3743 var fileInput = document.createElement('input');
3744 fileInput.type = 'file';
3745 fileInput.accept = '*'; // Accept any image file
3746 fileInput.style.display = 'none';
3747 document.body.appendChild(fileInput);
3748
3749 fileInput.click();
3750
3751 // TODO : Cache the fileURI on the node so can display without loading...
3752 fileInput.addEventListener('change', function () {
3753 var fileURI = fileInput.value.split('\\').pop(); // Get the filename without the full path
3754 var file = fileInput.files[0];
3755 //if (wantURI)
3756 fileURI = URL.createObjectURL(file);
3757
3758 var updateElementId = '__pp:' + updateProp;
3759 var textInput = document.getElementById(updateElementId);
3760 //console.log('New filename:', fileURI, 'updateElementId:', updateElementId, 'updateProp:', updateProp);
3761 textInput.value = fileURI;
3762 theNode.setProperty(updateProp, fileURI);
3763
3764 var propertypanel_preview = document.getElementById('propertypanel_preview');
3765 if (propertypanel_preview) {
3766 propertypanel_preview.src = URL.createObjectURL(file);
3767 propertypanel_preview.style.display = "block";
3768 }
3769
3770 var previewImage = false;
3771 if (previewImage) {
3772 if (propertypanel_preview) {
3773 var reader = new FileReader();
3774 reader.onload = function (event) {
3775 propertypanel_preview.src = event.target.result;
3776 };
3777
3778 // Read the file as a data URL (base64 encoded string)
3779 reader.readAsDataURL(file);
3780 propertypanel_preview.style.display = "block";
3781 }
3782 }
3783
3784 document.body.removeChild(fileInput);
3785 });
3786 }

◆ openSubgraph()

MxShadingGraphEditor::openSubgraph ( )

Open the subgraph for the selected node.

The first node selected is used.

Definition at line 3462 of file JsMaterialXNodeEditor.js.

3462 {
3463 var selected = graphcanvas.selected_nodes;
3464 for (var s in selected) {
3465 var node = selected[s];
3466 if (node.type == 'graph/subgraph') {
3467 graphcanvas.openSubgraph(node.subgraph);
3468 break;
3469 }
3470 }
3471 }

◆ pasteFromClipboard()

MxShadingGraphEditor::pasteFromClipboard ( )

Paste nodes from the clipboard.

Definition at line 5299 of file JsMaterialXNodeEditor.js.

5299 {
5300 graphcanvas.pasteFromClipboard(true);
5301 }

◆ resetView()

MxShadingGraphEditor::resetView ( )

Reset the view of the graph.

Definition at line 3485 of file JsMaterialXNodeEditor.js.

3485 {
3486 if (graphcanvas) {
3487 graphcanvas.ds.reset();
3488 graphcanvas.setDirty(true, true);
3489 graphcanvas.centerOnGraph(false);
3490 }
3491 }

◆ rgbToHex()

MxShadingGraphEditor::rgbToHex ( rgb)

Utility to convert a color from RGB to hex.

Parameters
rgb- The RGB color to convert.
Returns
The hex color.

Definition at line 3691 of file JsMaterialXNodeEditor.js.

3691 {
3692 if (!rgb) {
3693 console.log('rgbToHex empty !', rgb);
3694 return "#000000";
3695 }
3696 return '#' + rgb.map(x => {
3697 var hex = Math.round(x * 255).toString(16);
3698 return hex.length === 1 ? '0' + hex : hex;
3699 }).join('');
3700 }

◆ saveGraphToFile()

MxShadingGraphEditor::saveGraphToFile ( extension,
graphWriteOptions )

Save the graph to a file with the specified extension.

Parameters
extension- The extension to save the graph to.
graphWriteOptions- The options for writing the graph.
Returns
{void}

Definition at line 3565 of file JsMaterialXNodeEditor.js.

3565 {
3566 if (this.handler.canExport(extension)) {
3567 this.handler.saveGraphToFile(extension, graph, graphWriteOptions);
3568 }
3569 else
3570 {
3571 this.debugOutput('Unsupported extension for saving graph:' + extension, 2, false);
3572 }
3573 }

◆ saveGraphToString()

MxShadingGraphEditor::saveGraphToString ( extension,
graphWriteOptions )

Save the graph to a string with the specified extension.

Parameters
extension- The extension to save the graph to.
graphWriteOptions- The options for writing the graph.

Definition at line 3581 of file JsMaterialXNodeEditor.js.

3581 {
3582 if (this.handler.canExport(extension)) {
3583 return this.handler.saveGraphToString(extension, graph, graphWriteOptions);
3584 }
3585 else
3586 {
3587 this.debugOutput('Unsupported extension for saving graph: ' + extension, 2, false);
3588 return '';
3589 }
3590 }

◆ saveSerialization()

MxShadingGraphEditor::saveSerialization ( )

Use built-in serialization to save the graph to file.

Definition at line 3526 of file JsMaterialXNodeEditor.js.

3526 {
3527 var data = JSON.stringify(graph.serialize(), null, 2);
3528 var blob = new Blob([data], { type: "text/plain" });
3529 var url = URL.createObjectURL(blob);
3530 var a = document.createElement("a");
3531 a.href = url;
3532 a.download = "serialized_graph.json";
3533 a.click();
3534 }

◆ searchGraph()

MxShadingGraphEditor::searchGraph ( title)

Search the graph for the specified node.

If the node is found then select it and center the view on it.

Parameters
title- The title of the node to search for.

Definition at line 3406 of file JsMaterialXNodeEditor.js.

3407 {
3408 if (title.length == 0) {
3409 return;
3410 }
3411 if (graphcanvas)
3412 {
3413 let nodesFound = [];
3414 let theGraph = graphcanvas.graph;
3415
3416 // TODO: Add a this search logic to LiteGraph.
3417 const pattern = new RegExp(title);
3418 for (var i = 0, l = theGraph._nodes.length; i < l; ++i) {
3419 if (pattern.test(theGraph._nodes[i].title)) {
3420 console.log('-- add found node:', theGraph._nodes[i].title);
3421 nodesFound.push(theGraph._nodes[i]);
3422
3423 // If exact match then only select that node
3424 if (theGraph._nodes[i].title == title)
3425 {
3426 nodesFound.length = 0;
3427 nodesFound.push(theGraph._nodes[i]);
3428 break;
3429 }
3430 }
3431 }
3432 //let node = graphcanvas.graph.findNodeByTitle(title);
3433 if (nodesFound.length > 0)
3434 {
3435 graphcanvas.selectNodes(nodesFound);
3436 graphcanvas.centerOnGraph(true);
3437 MxShadingGraphEditor.theEditor.updatePropertyPanel(nodesFound[0]);
3438 }
3439 else
3440 {
3441 this.debugOutput('Node not found: ' + title, 0, false);
3442 }
3443 }
3444 }

◆ selectNodes()

MxShadingGraphEditor::selectNodes ( )

Select all nodes in the graph.

Definition at line 3496 of file JsMaterialXNodeEditor.js.

3496 {
3497 if (graphcanvas) {
3498 graphcanvas.selectNodes();
3499 graphcanvas.setDirty(true, true);
3500 }
3501 }

◆ setDirty()

MxShadingGraphEditor::setDirty ( w = null,
h = null )

Notify the editor to update it's graph.

Definition at line 3327 of file JsMaterialXNodeEditor.js.

3327 {
3328 if (graph)
3329 graph.setDirtyCanvas(true, true);
3330 if (graphcanvas) {
3331 graphcanvas.resize(w,h);
3332 }
3333 }

◆ setSourceColorSpace()

MxShadingGraphEditor::setSourceColorSpace ( colorSpace)

Set global color space for the editor.

Parameters
colorSpace- The color space to set.
Returns
{void}

Definition at line 3359 of file JsMaterialXNodeEditor.js.

3359 {
3360 if (this.handler) {
3361 this.handler.setSourceColorSpace(colorSpace);
3362 }
3363 }

◆ setTargetDistanceUnit()

MxShadingGraphEditor::setTargetDistanceUnit ( unit)

Set the global distance unit for the editor.

Parameters
unit- The distance unit to set.
Returns
{void}

Definition at line 3371 of file JsMaterialXNodeEditor.js.

3371 {
3372 if (this.handler) {
3373 this.handler.setTargetDistanceUnit(unit);
3374 }
3375 }

◆ setUI()

MxShadingGraphEditor::setUI ( ui)

Set the UI callbacks for the editor.

Parameters
ui- The UI object containing the callbacks.
Returns
{void}

Definition at line 3320 of file JsMaterialXNodeEditor.js.

3320 {
3321 this.ui = ui;
3322 }

◆ updateImagePreview()

MxShadingGraphEditor::updateImagePreview ( curImage)

Update the image preview in the property panel.

Parameters
curImage- The image to preview.
Returns
{void}

Definition at line 3869 of file JsMaterialXNodeEditor.js.

3869 {
3870 var propertypanel_preview = document.getElementById('propertypanel_preview');
3871 if (curImage && propertypanel_preview) {
3872 this.uriExists(curImage)
3873 .then(exists => {
3874 if (exists) {
3875 propertypanel_preview.src = curImage;
3876 propertypanel_preview.style.display = "block";
3877 } else {
3878 //propertypanel_preview.style.display = "none";
3879 propertypanel_preview.src = "./Icons/no_image.png";
3880 propertypanel_preview.style.display = "block";
3881 MxShadingGraphEditor.theEditor.debugOutput('Image does not exist: ' + curImage, 1);
3882 }
3883 });
3884 }
3885 }
uriExists(uri)
Check if a URI exists.

◆ updatePropertyPanel()

MxShadingGraphEditor::updatePropertyPanel ( node)

This method is called when a node is selected in the graph.

  • It will update the property panel with the properties of the selected node.
  • If a subgraph is selected, it updates the property panel with the subgraph properties.
  • If the parent graph is the root and node node is selected, it updates the property panel with the document properties.
  • If the parent graph is a subgraph and no node is selected, it updates the property panel with the subgraph properties.
Parameters
node- The selected node in the graph.
Returns
{void}

Definition at line 3897 of file JsMaterialXNodeEditor.js.

3897 {
3898 const TRUNC_TEXT = 20;
3899
3900 //console.log('Update Panel For:', node);
3901 var propertypanelcontent = MxShadingGraphEditor.theEditor.ui.propertypanel_content;
3902 if (!propertypanelcontent) {
3903 console.error('No property panel content widget found!');
3904 return;
3905 }
3906 // Delete all children
3907 while (propertypanelcontent.firstChild) {
3908 propertypanelcontent.removeChild(propertypanelcontent.firstChild);
3909 }
3910
3911 // Update icon
3912 var panelIcon = MxShadingGraphEditor.theEditor.ui.propertypanel_icon;
3913 if (node && node.nodedef_icon) {
3914 panelIcon.src = node.nodedef_icon;
3915 }
3916 else if (this.ui.icon_map) {
3917 if (!node || node.type == 'graph/subgraph') {
3918 panelIcon.src = this.ui.icon_map['_default_graph_'];
3919 } else {
3920 panelIcon.src = this.ui.icon_map['_default_'];
3921 }
3922 }
3923
3924 propertypanelcontent.innerHTML = "";
3925
3926 let colorSpaces = this.handler.getColorSpaces();
3927 let targetUnits = this.handler.getUnits();
3928
3929 let inUnselectedNodeGraph = false
3930 if (!node && graphcanvas.graph._subgraph_node) {
3931 node = graphcanvas.graph._subgraph_node;
3932 inUnselectedNodeGraph = true
3933 // Note openSubgraph() was modified to select the subgraph node so this
3934 // will trigger properly on opening a subgraph !!!
3935 // See: this.selectNode(graph._subgraph_node);
3936 //console.log('In subgraph but no node selected. Select subgraph node', node)
3937 }
3938 else if (!node && !graphcanvas.graph._is_subgraph) {
3939 var docInfo = [['Colorspace', this.getSourceColorSpace()],
3940 ['Distance', this.getTargetDistanceUnit()]];
3941
3942 for (let item of docInfo) {
3943
3944 let elem = document.createElement("div");
3945 elem.className = "row px-1 py-0";
3946 let label = document.createElement("div");
3947 label.className = "col py-0 col-form-label-sm text-left";
3948 label.style = this.fontSizeStyle;
3949 label.innerHTML = "<b>" + item[0] + "</b>";
3950 elem.appendChild(label);
3951
3952 if (item[0] == 'Colorspace' && colorSpaces.length > 0) {
3953 // Create colorspace drop down
3954 var inputCol = document.createElement("div");
3955 inputCol.className = "col text-left";
3956 var select = this.createColorSpaceInput(colorSpaces, item[1]);
3957 select.onchange = function (e) {
3958 MxShadingGraphEditor.theEditor.setSourceColorSpace(e.target.value);
3959 }
3960 inputCol.appendChild(select);
3961 elem.appendChild(inputCol);
3962 }
3963 else if (item[0] == 'Distance' && targetUnits.length > 0) {
3964 // Create units drop down
3965 var inputCol = document.createElement("div");
3966 inputCol.className = "col text-left";
3967 var select = this.createUnitsInput(targetUnits, 'distance', item[1]);
3968 select.onchange = function (e) {
3969 MxShadingGraphEditor.theEditor.setTargetDistanceUnit(e.target.value);
3970 }
3971 inputCol.appendChild(select);
3972 elem.appendChild(inputCol);
3973 }
3974 /* var inputCol = document.createElement("div");
3975 inputCol.className = "col text-left";
3976 var nameInput = document.createElement("input");
3977 nameInput.type = "text";
3978 nameInput.value = item[1];
3979 nameInput.className = "form-control form-control-sm";
3980 nameInput.disabled = true;
3981 elem.appendChild(inputCol);
3982 inputCol.appendChild(nameInput);
3983 }
3984 */
3985 propertypanelcontent.appendChild(elem);
3986 }
3987 return;
3988 }
3989
3990 var _category = node.nodedef_node;
3991 var _type = node.nodedef_type;
3992
3993 var isNodeGraph = node.type == 'graph/subgraph';
3994 if (isNodeGraph) {
3995 //console.log('>> Update subgraph property panel:', node);
3996 _category = 'nodegraph';
3997 if (node.outputs) {
3998 if (node.outputs.length > 1) {
3999 _type = 'multi';
4000 }
4001 else if (node.outputs.length > 0) {
4002 _type = node.outputs[0].type;
4003 }
4004 }
4005 else {
4006 _type = '';
4007 }
4008 }
4009 else {
4010 if (_category == 'surfacematerial') {
4011 _type = '';
4012 }
4013 }
4014
4015 // Identification row
4016 var elem = document.createElement("div");
4017 elem.className = "row px-1 py-1";
4018
4019 // Node category and output type
4020 var label = document.createElement("div");
4021 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4022 label.style = this.fontSizeStyle;
4023 label.innerHTML = "<b>" + _category;
4024 if (_type.length > 0) {
4025 label.innerHTML += '<br>' + _type;
4026 }
4027 label.innerHTML += "</b>";
4028 elem.appendChild(label);
4029
4030 // Node name / title
4031 var inputCol = document.createElement("div");
4032 inputCol.className = "col py-0";
4033 var nameInput = document.createElement("input");
4034 nameInput.style = this.fontSizeStyle;
4035 nameInput.type = "text";
4036 nameInput.value = node.title;
4037 nameInput.className = "form-control form-control-sm";
4038 let that = this;
4039 nameInput.onchange = function (e) {
4040 var oldTitle = node.title;
4041 var newTitle = MxShadingGraphEditor.theEditor.handler.createValidName(e.target.value);
4042 if (newTitle != oldTitle)
4043 {
4044 that.monitor.onNodeRenamed(node, newTitle);
4045 node.title = newTitle;
4046 }
4047 e.target.value = node.title;
4048 //console.log('node.graph._is_subgraph:', node)
4049 if (node.graph._is_subgraph) {
4050 if (node.nodedef_node == 'input') {
4051 //console.log('-------- Rename subgraph input:', node.title);
4052 node.graph.renameInput(oldTitle, node.title);
4053 }
4054 else if (node.nodedef_node == 'output') {
4055 //console.log('----------- Rename subgraph output:', node.title);
4056 node.graph.renameOutput(oldTitle, node.title);
4057 }
4058 }
4059
4060 // Note: there is a custom size fo subgraphs.
4061 node.setSize(node.computeSize());
4062 node.setDirtyCanvas(true, true);
4063 }
4064 inputCol.appendChild(nameInput);
4065
4066 // TODO: Generate swatches on the fly
4067 if (node.nodedef_node != 'input' && node.nodedef_node != 'output'
4068 && node.type != 'graph/subgraph') {
4069 var imagePreview = document.createElement("img");
4070 imagePreview.src = "./Icons/no_image.png";
4071 var previewSet = false;
4072 //console.log('Check for preview:', node.nodedef_swatch, 'category:', _category)
4073 imagePreview.style.display = "none";
4074 imagePreview.src = "./Icons/no_image.png";
4075 /* if (node.nodedef_swatch &&
4076 (_type == 'BSDF' || _type == 'EDF' || _type == 'surfaceshader'))
4077 {
4078 this.uriExists(node.nodedef_swatch)
4079 .then(exists => {
4080 if (exists) {
4081 previewSet = true;
4082 imagePreview.style.display = "block";
4083 imagePreview.src = node.nodedef_swatch;
4084 }
4085 });
4086 } */
4087 imagePreview.id = "propertypanel_preview";
4088 imagePreview.className = "img-fluid form-control form-control-sm";
4089 inputCol.appendChild(imagePreview);
4090 }
4091
4092 elem.appendChild(label);
4093 elem.appendChild(inputCol);
4094
4095 // Toggle show/hide of inputs with default values
4096 if (!isNodeGraph)
4097 {
4098 var filterCol = document.createElement("div");
4099 filterCol.className = "col-2 py-0";
4100 filterCol.width = 16;
4101 var filterIcon = document.createElement("button");
4102 //filterIcon.setAttribute(data-bs-toggle, "tooltip");
4103 //filterIcon.setAttribute(data-bs-title, "Show/Hide Default Value Inputs");
4104 if (node.showDefaultValueInputs == null)
4105 {
4106 node.showDefaultValueInputs = true;
4107 }
4108 var img = document.createElement("img");
4109 if (node.showDefaultValueInputs)
4110 {
4111 img.src = "./Icons/funnel_white.svg";
4112 filterIcon.className = "btn btn-sm btn-outline-secondary";
4113 }
4114 else
4115 {
4116 img.src = "./Icons/funnel-fill_white.svg";
4117 filterIcon.className = "btn btn-sm btn-outline-warning";
4118 }
4119 filterIcon.appendChild(img);
4120 filterIcon.onclick = function (e) {
4121 node.showDefaultValueInputs = !node.showDefaultValueInputs;
4123 }
4124 filterCol.appendChild(filterIcon);
4125 elem.appendChild(filterCol);
4126 }
4127
4128 propertypanelcontent.appendChild(elem);
4129
4130 let hr = document.createElement("hr");
4131 hr.classList.add("my-1");
4132 propertypanelcontent.appendChild(hr);
4133
4134 let current_details = null;
4135 let first_details = true;
4136 let nodeInputs = node.inputs;
4137 //console.log('Outputs:', nodeOutputs);
4138
4139 let targetNodes = [];
4140 for (var i in nodeInputs) {
4141 let nodeInput = nodeInputs[i];
4142
4143 let inputName = nodeInput.name;
4144 let nodeInputLink = nodeInput.link;
4145 let uiName = inputName;
4146 // remove "_" from uiName
4147 uiName = uiName.replace(/_/g, ' ');
4148 let uimin = null;
4149 let uimax = null;
4150 let colorspace = '';
4151 let units = '';
4152 let defaultgeomprop = '';
4153
4154 //console.log('Scan input:', inputName, ' on node: ', node.graph);
4155
4156 let property_info = node.getPropertyInfo(inputName);
4157 let ng_property_info = null;
4158 if (isNodeGraph)
4159 {
4160 //console.log('Check subgraph input node property_info:')
4161 let sg = node.subgraph;
4162 if (sg)
4163 {
4164 let sg_nodes = sg._nodes;
4165 for (var sg_node of sg_nodes)
4166 {
4167 if (sg_node.title == inputName)
4168 {
4169 //console.log('Found subgraph node:', sg_node.title);
4170 ng_property_info = sg_node.getPropertyInfo("in");
4171 if (ng_property_info)
4172 {
4173 //console.log('Use subgraph node input property info:', ng_property_info);
4174 break;
4175 }
4176 }
4177 }
4178 }
4179 }
4180 //console.log('1. get property info for i: ', inputName, 'info: ', property_info)
4181
4182 var skipInterorConnectedInput = false;
4183 if (node.graph._is_subgraph) {
4184 // Find input on subgraph node
4185 //console.log('Check subgraph for link:', node.graph)
4186 var sg_node = node.graph._subgraph_node;
4187 if (sg_node) {
4188 //console.log('Check for input on sg node', sg_node, node.title);
4189 var slot = sg_node.findInputSlot(node.title);
4190 if (slot != null) {
4191 if (sg_node.inputs) {
4192 //property_info = sg_node.properties_info[slot];
4193 var slotInput = sg_node.inputs[slot];
4194 //console.log('check slot: ', slotInput.link);
4195 if (slotInput != null && slotInput.link != null) {
4196 skipInterorConnectedInput = true;
4197 }
4198 }
4199 else {
4200 //console.log('Error: no subgraph node inputs for subgraph input!', sg_node, node.title);
4201 }
4202 }
4203 }
4204 }
4205
4206 if (skipInterorConnectedInput) {
4207 console.log('Skip interior connected input: ', nodeInput);
4208 continue;
4209 }
4210
4211 //console.log('Property info:', property_info, ' for input:', inputName);
4212 if (ng_property_info) {
4213 //console.log('Replace property info:', property_info);
4214 property_info = ng_property_info;
4215 }
4216 if (property_info) {
4217 //console.log('Extract input property info:', property_info);
4218 if (property_info.defaultgeomprop)
4219 {
4220 defaultgeomprop = property_info.defaultgeomprop;
4221 }
4222 if (property_info.colorspace) {
4223 colorspace = property_info.colorspace;
4224 }
4225 if (property_info.unit) {
4226 units = property_info.unit;
4227 }
4228 if (property_info.uiname) {
4229 uiName = property_info.uiname;
4230 }
4231 if (property_info.uimin) {
4232 uimin = property_info.uimin;
4233 }
4234 if (property_info.uimax) {
4235 uimax = property_info.uimax;
4236 }
4237 if (property_info.uifolder && property_info.uifolder.length > 0) {
4238 // Create a details element
4239 if (current_details == null || current_details.id != property_info.uifolder) {
4240 //console.log('Create new details:', property_info.uifolder);
4241 current_details = document.createElement("details");
4242 current_details.id = property_info.uifolder;
4243 current_details.open = first_details;
4244 current_details.classList.add('w-100', 'p-1', 'border', 'border-secondary', 'rounded', 'my-1');
4245 first_details = false;
4246 var summary = document.createElement('summary')
4247 summary.style = this.fontSizeStyle;
4248 summary.innerHTML = "<b>" + property_info.uifolder + "</b>"
4249 //summary.classList.add('btn', 'btn-sm', 'btn-outline-secondary', 'btn-block');
4250 current_details.appendChild(summary);
4251
4252 }
4253 else {
4254 //current_details = null;
4255 }
4256 }
4257 else {
4258 current_details = null;
4259 }
4260 //console.log('2. uiName:', uiName, 'uimin:', uimin, 'uimax:', uimax, 'uiFolder:', property_info.uifolder);
4261 }
4262 else {
4263 current_details = null;
4264 }
4265
4266 var elem = null;
4267
4268 // Check if there is a link
4269 if (nodeInputLink) {
4270 let upstreamLink = null;
4271
4272 let nodegraph = node.graph;
4273 let link = nodegraph.links[nodeInputLink];
4274 //console.log('link:', link);
4275 let linkId = link && link.origin_id;
4276 let linkNode = linkId && nodegraph.getNodeById(linkId);
4277
4278 if (linkNode) {
4279
4280 //console.log('linkNode:', linkNode);`
4281 let linkSlot = link.origin_slot;
4282 //console.log('linkSlot:', linkSlot);
4283 let linkOutput = linkNode.outputs[linkSlot];
4284 //console.log('linkOutput:', linkOutput);
4285 upstreamLink = linkNode.title + '.' + linkOutput.name;
4286 //console.log('upstreamLink:', upstreamLink);
4287
4288 let id = "__pp:" + inputName;
4289 let buttonText = upstreamLink;
4290 // Truncate long names
4291 if (buttonText.length > TRUNC_TEXT) {
4292 buttonText = buttonText.substring(0, TRUNC_TEXT) + "...";
4293 }
4294 let input = this.createButtonWithImageAndText("./Icons/arrow_up_white.svg", buttonText, id);
4295
4296 input.onclick = function (e) {
4297
4298 var inputName = e.target.id;
4299 inputName = inputName.replace('__pp:', '');
4300 inputName = inputName.replace('_text', '');
4301 inputName = inputName.replace('_img', '');
4302 console.log('Clicked traversal button:', inputName);
4303
4304 console.log('Jump to node:', linkNode.title);
4305 graphcanvas.selectNodes([linkNode]);
4307 MxShadingGraphEditor.theEditor.updatePropertyPanel(linkNode);
4308 node.setDirtyCanvas(true, true);
4309 }
4310
4311 // Add new row
4312 elem = document.createElement("div");
4313 elem.className = "row px-1 py-0";
4314
4315 input.id = "__pp:" + inputName;
4316
4317 var label = document.createElement("div");
4318 // invert-button
4319 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4320 label.style = this.fontSizeStyle;
4321 label.innerHTML = uiName;
4322 label.for = input.id;
4323 elem.appendChild(label);
4324
4325 // form-control
4326 if (useFormControl) {
4327 input.classList.add("form-control");
4328 }
4329 input.classList.add("form-control-sm");
4330 // Disable if don't want interaction.
4331 if (!graphcanvas.allow_interaction)
4332 input.disabled = true;
4333
4334 var propvalue = document.createElement("div");
4335 propvalue.className = "col p-1";
4336 propvalue.appendChild(input);
4337
4338 elem.appendChild(propvalue);
4339 }
4340 }
4341
4342 else {
4343
4344 targetNodes[i] = node;
4345 let targetNode = targetNodes[i];
4346 let propertyKey = inputName;
4347
4348 var property = targetNode.properties[inputName];
4349 if (property == null) {
4350 if (isNodeGraph) {
4351 var subgraph = targetNode.subgraph;
4352 if (subgraph) {
4353 //console.log('Find node by title', inputName, ' in subgraph', subgraph._nodes);
4354 var subNode = subgraph.findNodeByTitle(inputName);
4355 if (subNode) {
4356 targetNodes[i] = subNode;
4357 propertyKey = 'in';
4358 property = targetNodes[i].properties['in'];
4359 //console.log('Route to subgraph target node:', targetNode, targetNode.title, '. ', inputName, ' = ', JSON.stringify(property), 'propkey=', propertyKey);
4360 }
4361 }
4362 }
4363 if (property == null) {
4364 console.log('Update: Cannot find property value for input:', inputName);
4365 continue;
4366 }
4367 }
4368
4369 // Check if there is a default property value. If so skip showing it
4370 if (defaultgeomprop)
4371 {
4372 //console.log('Skip input with defaultgeomprop: ' + inputName);
4373 continue;
4374 }
4375
4376 // Check if property value is same as property info default value
4377 if (!node.showDefaultValueInputs && !isNodeGraph)
4378 {
4379 let isDefault = node.isDefaultValue(inputName);
4380 if (isDefault)
4381 {
4382 continue;
4383 }
4384 }
4385
4386 // Add new row
4387 elem = document.createElement("div");
4388 elem.className = "row px-1 py-0";
4389
4390 var input = null;
4391 var input_btn = null;
4392 let input_slider = null;
4393 var colorspace_unit_btn = null;
4394 var useFormControl = true;
4395
4396 // Add colorspace drop-down if specified.
4397 if (colorspace.length > 0) {
4398 // Create drop-down menu to choose colorspace from list stored
4399 // in the handler class getColorSpaces() method.
4400 //
4401 colorspace_unit_btn = this.createColorSpaceInput(colorSpaces, colorspace);
4402 let theNode = targetNodes[i];
4403 colorspace_unit_btn.onchange = function (e) {
4404
4405 theNode.setPropertyInfo(inputName, 'colorspace', e.target.value);
4406 }
4407 }
4408 else if (units.length > 0 && property_info.unittype) {
4409 // Add units drop-down if specified.
4410 colorspace_unit_btn = this.createUnitsInput(targetUnits, property_info.unittype, units);
4411 let theNode = targetNodes[i];
4412 colorspace_unit_btn.onchange = function (e) {
4413 theNode.setPropertyInfo(inputName, 'unit', e.target.value);
4414 }
4415 }
4416
4417 let proptype = nodeInput.type;
4418 if (proptype == 'float' || proptype == 'integer') {
4419 var isFloat = proptype == 'float';
4420
4421 input = document.createElement("input");
4422 input.id = propertyKey + '_box';
4423 input.style = this.fontSizeStyle;
4424 input.type = 'number';
4425 input.classList.add("form-control", "form-control-sm", "ps-0");
4426 input.setAttribute('propertyKey', propertyKey);
4427
4428 input_slider = document.createElement("input");
4429 input_slider.id = propertyKey + '_slider';
4430 //input_slider.style = this.fontSizeStyle;
4431 input_slider.type = 'range';
4432 input_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4433 input_slider.setAttribute('propertyKey', propertyKey);
4434
4435 if (uimin) {
4436 input.min = uimin;
4437 }
4438 else {
4439 input.min = Math.min(property, 0);
4440 }
4441 if (uimax) {
4442 input.max = uimax;
4443 }
4444 else {
4445 if (isFloat)
4446 {
4447 input.max = Math.max(property*3, 10.0);
4448 }
4449 else {
4450 input.max = Math.max(property*3, 100);
4451 }
4452 }
4453
4454
4455 input_slider.min = input.min;
4456 input_slider.max = input.max;
4457 if (isFloat) {
4458 input.step = (input.max - input.min) / 100.0;
4459 input_slider.step = input.step;
4460 }
4461 else {
4462 input_slider.step = 1;
4463 input.step = 1;
4464 }
4465
4466 input.value = input_slider.value = property;
4467
4468 /* console.log('> ' + propertyKey + ' - Set up slider: min, max, value',
4469 input_slider.min, input_slider.max, input_slider.value
4470 );
4471 console.log('> ' + propertyKey + ' - Set up box: min, max, value',
4472 input.min, input.max, input.value
4473 ); */
4474
4475 let theBox = input;
4476 let theSlider = input_slider;
4477 let theNode = targetNodes[i];
4478 input_slider.onchange = function (e) {
4479 var pi = e.target.getAttribute('propertyKey');
4480 var val = parseFloat(e.target.value);
4481 theNode.setProperty(pi, val);
4482 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4483 }
4484 input_slider.oninput = function(e) {
4485 var pi = e.target.getAttribute('propertyKey');
4486 var val = parseFloat(e.target.value);
4487 theNode.setProperty(pi, val);
4488 theBox.value = e.target.value;
4489 }
4490
4491 input.onchange = function (e) {
4492 var pi = e.target.getAttribute('propertyKey');
4493 var val = parseFloat(e.target.value);
4494 theNode.setProperty(pi, val);
4495 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4496 }
4497 input.oninput = function(e) {
4498 var pi = e.target.getAttribute('propertyKey');
4499 var val = parseFloat(e.target.value);
4500 theNode.setProperty(pi, val);
4501 theSlider.value = e.target.value;
4502 }
4503 }
4504 else if (proptype == 'string' || proptype == 'filename') {
4505 input = document.createElement("input");
4506 input.style = this.fontSizeStyle;
4507 input.type = "text";
4508 if (proptype == 'filename') {
4509 var curImage = property;
4510 this.updateImagePreview(curImage);
4511
4512 input_btn = document.createElement("button");
4513 input_btn.classList.add("btn", "btn-sm", "btn-outline-secondary");
4514 input_btn.innerHTML = "+";
4515 input_btn.setAttribute('propertyKey', propertyKey);
4516 var fileId = "__pp:" + inputName;
4517 let theNode = targetNodes[i];
4518 input_btn.onclick = function (e) {
4519 var pi = e.target.getAttribute('propertyKey');
4520 MxShadingGraphEditor.theEditor.openImageDialog(theNode, pi, false);
4521 }
4522 }
4523
4524 else
4525 {
4526 // Check if there is a 'enm' property
4527 //console.log('------------------- handle enum property info:', property_info, '. property:', property);
4528 if (property_info && property_info.enum) {
4529
4530 //console.log('----------------- found enum property info:', property_info.enum);
4531
4532 // Create drop-down menu to choose from list stored in the handler class.
4533 input = document.createElement("select");
4534 input.style = this.fontSizeStyle;
4535 input.classList.add("form-control", "form-control-sm");
4536
4537 input.setAttribute('propertyKey', propertyKey);
4538 let theNode = targetNodes[i];
4539 let enums = property_info.enum;
4540 for (let j = 0; j < enums.length; j++) {
4541 let option = document.createElement("option");
4542 option.value = enums[j];
4543 option.text = enums[j];
4544 input.add(option);
4545 }
4546 input.value = property;
4547 input.setAttribute('propertyKey', propertyKey);
4548 input.onchange = function (e) {
4549 var pi = e.target.getAttribute('propertyKey');
4550 theNode.setProperty(pi, e.target.value);
4551 //console.log('Update string property:', pi, theNode.properties[pi])
4552 }
4553 }
4554 }
4555
4556 if (property_info && !property_info.enm) {
4557 input.value = property;
4558 input.setAttribute('propertyKey', propertyKey);
4559 let theNode = targetNodes[i];
4560 let isFilename = proptype == 'filename';
4561 let that = this;
4562 input.onchange = function (e) {
4563 var pi = e.target.getAttribute('propertyKey');
4564 //theNode.properties[pi] = e.target.value;
4565 theNode.setProperty(pi, e.target.value);
4566 if (isFilename) {
4567 //console.log('Update filename property:', pi, theNode.properties[pi])
4568 that.updateImagePreview(e.target.value);
4569 }
4570 else {
4571 //console.log('Update string property:', pi, theNode.properties[pi])
4572 }
4573 }
4574 }
4575 }
4576 else if (proptype == 'boolean') {
4577 //console.log('Add Boolean property:', property);
4578 input = document.createElement("input");
4579 input.style = this.fontSizeStyle;
4580 input.type = "checkbox";
4581 input.classList = "form-check-input";
4582 useFormControl = false;
4583 input.checked = property;
4584 input.setAttribute('propertyKey', propertyKey);
4585 let theNode = targetNodes[i];
4586 input.onchange = function (e) {
4587 var pi = e.target.getAttribute('propertyKey');
4588 //theNode.properties[pi] = e.target.checked;
4589 theNode.setProperty(pi, e.target.checked);
4590 //console.log('Update boolean property:', pi, theNode.properties[pi]);
4591 }
4592 }
4593
4594 else if (proptype == 'vector2' || proptype == 'vector3' || proptype == 'vector4')
4595 {
4596 // Find index of proptype in ['vector2', 'vector3', 'vector4' ]
4597 var vector_size = ['vector2', 'vector3', 'vector4'].indexOf(proptype) + 2;
4598 input = document.createElement("div");
4599 useFormControl = false;
4600
4601 input.className = "row py-1 ps-4 pe-0";
4602
4603 for (let v=0; v<vector_size; v++)
4604 {
4605 //console.log('Vector property:[', 0, '] = ', property[0], proptype)
4606 let subinput = document.createElement("input");
4607 subinput.style = this.fontSizeStyle;
4608 subinput.type = 'number';
4609 subinput.classList.add("form-control");
4610 subinput.classList.add("form-control-sm");
4611 subinput.setAttribute('propertyKey', propertyKey);
4612
4613 let subinput_slider = document.createElement("input");
4614 subinput_slider.id = propertyKey + '_slider';
4615 subinput_slider.type = 'range';
4616 subinput_slider.classList.add('form-range', 'custom-slider', 'pe-0');
4617 subinput_slider.setAttribute('propertyKey', propertyKey);
4618
4619 if (uimin) {
4620 subinput.min = uimin[v];
4621 }
4622 else {
4623 subinput.min = Math.min(property[v]*3, 0);
4624 }
4625 if (uimax) {
4626 subinput.max = uimax[v];
4627 }
4628 else {
4629 subinput.max = Math.max(property[v]*3, 10.0);
4630 }
4631
4632 subinput_slider.min = subinput.min;
4633 subinput_slider.max = subinput.max;
4634 subinput.step = (subinput.max - subinput.min) / 100.0;
4635 subinput_slider.step = subinput.step;
4636
4637 subinput.value = subinput_slider.value = property[v];
4638
4639 let theNode = targetNodes[i];
4640 let vector_index = v;
4641 let theBox = subinput;
4642 let theSlider = subinput_slider;
4643 theBox.onchange = function (e) {
4644 let pi = e.target.getAttribute('propertyKey');
4645 let value = parseFloat(e.target.value);
4646 let newValue = theNode.properties[pi].map(item => item);
4647 newValue[vector_index] = value;
4648 theNode.setProperty(pi, newValue);
4649 //theNode.properties[pi][0] = value;
4650 //console.log('Update Vector property:"', pi, '"', 0, parseFloat(e.target.value), theNode.properties[pi])
4651 }
4652 theBox.oninput = function(e) {
4653 let pi = e.target.getAttribute('propertyKey');
4654 let value = parseFloat(e.target.value);
4655 let newValue = theNode.properties[pi].map(item => item);
4656 newValue[vector_index] = value;
4657 theNode.setProperty(pi, newValue);
4658 theSlider.value = e.target.value;
4659 }
4660
4661 theSlider.onchange = function (e) {
4662 let pi = e.target.getAttribute('propertyKey');
4663 let value = parseFloat(e.target.value);
4664 let newValue = theNode.properties[pi].map(item => item);
4665 newValue[vector_index] = value;
4666 theNode.setProperty(pi, newValue);
4667 //console.log('Update scalar property:', pi, parseFloat(e.target.value), theNode.title, theNode.properties)
4668 }
4669 theSlider.oninput = function(e) {
4670 let pi = e.target.getAttribute('propertyKey');
4671 let value = parseFloat(e.target.value);
4672 let newValue = theNode.properties[pi].map(item => item);
4673 newValue[vector_index] = value;
4674 theNode.setProperty(pi, newValue);
4675 theBox.value = e.target.value;
4676 }
4677
4678
4679 let propvalue_slider = document.createElement("div");
4680 propvalue_slider.className = "col p-0";
4681 propvalue_slider.appendChild(subinput_slider);
4682
4683 let propvalue_box = document.createElement("div");
4684 propvalue_box.className = "col p-0";
4685 propvalue_box.appendChild(subinput);
4686
4687 let input_row = document.createElement("div");
4688 input_row.className = "row p-0";
4689 input_row.appendChild(propvalue_slider);
4690 input_row.appendChild(propvalue_box);
4691
4692 input.appendChild(input_row);
4693 }
4694 }
4695 else if (proptype == 'color3' || proptype == 'color4') {
4696 input = document.createElement("input");
4697 input.type = "color";
4698 //console.log('set color property:', property.length, property);
4699 if (property.length == 4) {
4700 input.value = this.rgbToHex([ property[0], property[1], property[2] ]);
4701 }
4702 else {
4703 input.value = this.rgbToHex(property);
4704 }
4705 input.setAttribute('propertyKey', propertyKey);
4706 let theNode = targetNodes[i];
4707 input.onchange = function (e) {
4708 // Convert hex to rgb in 0..1 range
4709 var hex = e.target.value;
4710 let fprecision = 4
4711 let rgb = [0,0,0]
4712 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4713 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4714 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4715 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4716 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4717 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4718 if (proptype == 'color4')
4719 rgb[3] = 1.0;
4720
4721 var pi = e.target.getAttribute('propertyKey');
4722 theNode.setProperty(pi, rgb);
4723 }
4724 let func = function (e) {
4725 // Convert hex to rgb in 0..1 range
4726 var hex = e.target.value;
4727 let rgb = [0, 0, 0];
4728 let fprecision = 4
4729 rgb[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
4730 rgb[0] = parseFloat(rgb[0].toFixed(fprecision));
4731 rgb[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
4732 rgb[1] = parseFloat(rgb[1].toFixed(fprecision));
4733 rgb[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
4734 rgb[2] = parseFloat(rgb[2].toFixed(fprecision));
4735 if (proptype == 'color4')
4736 rgb[3] = 1.0;
4737
4738 var pi = e.target.getAttribute('propertyKey');
4739 theNode.setProperty(pi, rgb);
4740 }
4741 input.onchange = func;
4742 input.oninput = func;
4743 }
4744 else {
4745 input = document.createElement("input");
4746 input.style = this.fontSizeStyle;
4747 input.type = "text";
4748 input.value = property;
4749 let propertyKey = inputName;
4750 let theNode = targetNodes[i];
4751 input.onchange = function (e) {
4752 theNode.setProperty(propertyKey, e.target.value);
4753 //theNode.properties[propertyKey] = e.target.value;
4754 }
4755 }
4756
4757 if (input) {
4758 input.id = "__pp:" + inputName;
4759 //console.log('> Add input:', input.id);
4760
4761 var label = document.createElement("div");
4762 label.className = "col-4 p-0 col-form-label-sm text-end";
4763 label.style = this.fontSizeStyle;
4764 label.innerHTML = uiName;
4765 label.for = input.id;
4766 elem.appendChild(label);
4767
4768 // form-control
4769 if (useFormControl) {
4770 input.classList.add("form-control");
4771 }
4772 input.classList.add("form-control-sm");
4773 // Disable if don't want interaction.
4774 if (!graphcanvas.allow_interaction)
4775 input.disabled = true;
4776
4777 var propvalue = document.createElement("div");
4778 propvalue.className = "col py-0";
4779 if (input_slider)
4780 {
4781 propvalue.classList.add('ps-1');
4782 }
4783 propvalue.appendChild(input);
4784
4785 if (input_btn) {
4786 var propbutton = document.createElement("div");
4787 propbutton.className = "col-2 py-0";
4788 //console.log('Add input button:', input_btn);
4789 propbutton.appendChild(input_btn);
4790 elem.appendChild(propbutton);
4791 }
4792 if (colorspace_unit_btn) {
4793 //console.log('Add cs / unit button:', input_btn);
4794 var propbutton = document.createElement("div");
4795 propbutton.className = "col col-form-label-sm";
4796 var details = document.createElement("details");
4797 var summary = document.createElement('summary')
4798 summary.style = this.fontSizeStyle;
4799 if (colorspace.length > 0)
4800 summary.innerHTML = "Colorspace";
4801 else if (targetUnits.length > 0)
4802 summary.innerHTML = "Units";
4803 details.appendChild(summary);
4804 details.appendChild(colorspace_unit_btn);
4805 propbutton.appendChild(details);
4806 propvalue.appendChild(propbutton);
4807 }
4808
4809 if (input_slider)
4810 {
4811 var propvalue_slider = document.createElement("div");
4812 propvalue_slider.className = "col py-0 pe-0";
4813 propvalue_slider.appendChild(input_slider);
4814 elem.appendChild(propvalue_slider);
4815 }
4816
4817 elem.appendChild(propvalue);
4818 }
4819 }
4820 //elem.innerHTML = "<em>" + i + "</em> : " + property;
4821 if (elem) {
4822 if (current_details) {
4823 //console.log('3a. append child to details:', current_details.id, elem, inputName);
4824 current_details.appendChild(elem);
4825 // It current_details not already in the propertypanelcontent, add it.
4826 if (current_details.parentElement == null) {
4827 propertypanelcontent.appendChild(current_details);
4828 }
4829 }
4830 else {
4831 propertypanelcontent.appendChild(elem);
4832 //console.log('3b. append child to parent content:', elem, inputName);
4833 }
4834 }
4835 }
4836
4837 //propertypanelcontent
4838
4839 let output_details = null;
4840 let nodeOutputs = null;
4841 let isSubgraph = graphcanvas.graph;
4842 //console.log('>> graph: ', graph, '. isnodegraph:', isNodeGraph,
4843 // 'inUnselectedNodeGraph: ', inUnselectedNodeGraph)
4844 if (!inUnselectedNodeGraph)
4845 nodeOutputs = node.outputs;
4846 for (let k in nodeOutputs) {
4847 let nodeOutput = nodeOutputs[k];
4848 //console.log('For node:', node.title, '.Found output:', nodeOutput.name, nodeOutput)
4849 let nodeOutputLinks = nodeOutput.links;
4850 for (let j in nodeOutputLinks) {
4851 let link_id = nodeOutputLinks[j];
4852 // Find the link in the graph's links array
4853 //console.log('-------------- linkid: ', link_id);
4854 //console.log('graphcanvas.graph:', node.graph)
4855 const link = graphcanvas.graph.links[link_id];
4856
4857 if (link) {
4858 if (!output_details) {
4859 output_details = document.createElement("details");
4860 output_details.id = "pp::outputs";
4861 output_details.open = true;
4862 output_details.classList.add('w-100', 'p-1', 'border', 'border-secondary', 'rounded', 'my-1');
4863 first_details = false;
4864 var summary = document.createElement('summary');
4865 {
4866 summary.style = this.fontSizeStyle;
4867 summary.innerHTML = "<b>Outputs</b>"
4868 }
4869 //summary.classList.add('btn', 'btn-sm', 'btn-outline-secondary', 'btn-block');
4870 output_details.appendChild(summary);
4871 }
4872
4873 //console.log('got link:', link);
4874 // Find the target node using the link's target node ID
4875 const targetNode = graphcanvas.graph.getNodeById(link.target_id);
4876
4877 if (targetNode) {
4878
4879 let targetSlot = link.target_slot;
4880 let targetInput = targetNode.inputs[targetSlot];
4881 //console.log(`- Node ${targetNode.title} (Input: ${targetInput.name})`);
4882
4883 let downstreamLink = targetNode.title + '.' + targetInput.name;
4884
4885 let id = "__pp:" + nodeOutput.name;
4886 let buttonText = downstreamLink;
4887 // Truncate long names
4888 if (buttonText.length > TRUNC_TEXT) {
4889 buttonText = buttonText.substring(0, TRUNC_TEXT) + "...";
4890 }
4891 let output = this.createButtonWithImageAndText("./Icons/arrow_down_white.svg", buttonText, id);
4892
4893 output.onclick = function (e) {
4894
4895 var inputName = e.target.id;
4896 inputName = inputName.replace('__pp:', '');
4897 inputName = inputName.replace('_text', '');
4898 inputName = inputName.replace('_img', '');
4899 console.log('Clicked traversal button:', inputName);
4900
4901 console.log('Jump to node:', targetNode.title);
4902 graphcanvas.selectNodes([targetNode]);
4904 MxShadingGraphEditor.theEditor.updatePropertyPanel(targetNode);
4905 node.setDirtyCanvas(true, true);
4906 }
4907
4908 // Add new row
4909 elem = document.createElement("div");
4910 elem.className = "row px-1 py-0";
4911
4912 let outputName = nodeOutput.name;
4913 output.id = "__pp:" + outputName;
4914
4915 var label = document.createElement("div");
4916 // invert-button
4917 label.className = "col-4 px-1 py-0 col-form-label-sm text-end";
4918 label.style = this.fontSizeStyle;
4919 label.innerHTML = outputName;
4920 label.for = nodeOutput.id;
4921 elem.appendChild(label);
4922
4923 // form-control
4924 if (useFormControl) {
4925 output.classList.add("form-control");
4926 }
4927 output.classList.add("form-control-sm");
4928 // Disable if don't want interaction.
4929 if (!graphcanvas.allow_interaction)
4930 output.disabled = true;
4931
4932 var propvalue = document.createElement("div");
4933 propvalue.className = "col p-1";
4934 propvalue.appendChild(output);
4935
4936 elem.appendChild(propvalue);
4937 //console.log('DOM output', propvalue);
4938 output_details.appendChild(elem);
4939
4940 } else {
4941 console.log(`- Node with ID ${link.target_id} not found.`);
4942 console.log('--- Available nodes:', graphcanvas.graph._nodes_by_id);
4943 }
4944 } else {
4945 console.log(`- Link with ID ${link_id} not found.`);
4946 }
4947 }
4948
4949 }
4950 if (output_details) {
4951 propertypanelcontent.appendChild(output_details);
4952 }
4953 }
createColorSpaceInput(colorSpaces, activeItem)
Create a color space input element.
rgbToHex(rgb)
Utility to convert a color from RGB to hex.
createButtonWithImageAndText(imageSrc, text, id)
Create a DOM button with an image and text.
updateImagePreview(curImage)
Update the image preview in the property panel.
setTargetDistanceUnit(unit)
Set the global distance unit for the editor.
createUnitsInput(units, unittype, activeItem)
Create a units input element.
getTargetDistanceUnit()
Get the global distance unit for the editor.
setSourceColorSpace(colorSpace)
Set global color space for the editor.
getSourceColorSpace()
Get the global color space for the editor.
openImageDialog(theNode, updateProp, wantURI)
Open a dialog to select a image file to load.

◆ uriExists()

MxShadingGraphEditor::uriExists ( uri)

Check if a URI exists.

Parameters
uri- The URI to check.
Returns
true if the URI exists, false otherwise.

Definition at line 3794 of file JsMaterialXNodeEditor.js.

3794 {
3795 return fetch(uri)
3796 .then(response => {
3797 if (response.ok) {
3798 return Promise.resolve(true);
3799 } else {
3800 return Promise.resolve(false);
3801 }
3802 })
3803 .catch(error => {
3804 console.log('Error checking URI:', error);
3805 return Promise.resolve(false);
3806 });
3807 }

The documentation for this class was generated from the following file: