MS Access to MagmaNodes

Hello folks,

I’m trying to get access to the MagmaNodes via MaxScript, but maybe I oversaw somehting in the manual or maybe I’m just to stupid,
but I don’t get it to work. I tried to get the props of the modifier, but didn’t found any entries which direct me to the nodes,
besides an array which holds every information contained in the flow. Exposing nodes to the modifier either didn’t worked
because I also found no according property for that.

Help would be awesome.

By the way: you did an awesome job on

KRRAKKKKAAAATOOOOAAA! AARRR!

  •                 [img]http://www.gifs-paradise.com/animated_gifs/parrots/animated-gifs-parrots-105.gif[/img]

KKRRRRAKAAAAATTTTOOOOOOOAAAAAA!!! :laughing:

Don’t be so hard on yourself, it is simply not documented anywhere.
You are not supposed to create flows with MAXScript directly, but it is certainly possible.

The KCM is a scripted modifier and its source code is available in the \Krakatoa\Scripts folder.
The MagmaFlow editor’s source code is also available there.
So it is possible to look at the code and functions there and see how it works.

When you add a KCM to a PRT object, you will see

show $.modifiers[1] .Flow : string .internalFlow : string .note : string .trackID : string .TextureMapSources : texturemap array .currentPreset : string .currentFolder : string .autoUpdate : boolean .interactiveMode : boolean .AutomaticRenameOFF : boolean .GeometryObjectsList : string array .needCAupdate : boolean .isRenderElement : boolean .forceUpdate : float false

The .internalFlow property contains the original definition of the flow which is a MAXScript Array of Arrays converted to a string - this is what gets loaded and saved as KMF file.
The .flow property contains the expanded flow definition used by the Magma compiler. This version of the flow is created automatically and is “flattened” to remove any nested operators (BlackOps) which normally exist as sub-arrays in the .internalFlow array.

Thus, if you add a fresh KCM to a PRT Volume and look at the .InternalFlow property, you will see something like

"#(#("Output", #(2), #("Color","float16",3), #([350,0],false, false,undefined,undefined,undefined,undefined,"Output" ), true ) ,#("Input", #(), #("Channel", "Color"), #([200,100], false, true, undefined, undefined, undefined, true, "Color", "Color"), true))"

In this simple case, the .flow property contains the exact same array as string, because there is nothing to expand.

Let’s now look at the first node (the Output node sub-array):

#("Output", #(2), #("Color","float16",3), #([350,0],false, false,undefined,undefined,undefined,undefined,"Output" ), true )
*The first element of the node array defines the Type of the Node - in this case “Output”. Currently it is assumed that the first node is always the Output node, but we might change this in the future.
*The second element of the node array defines the connections of the node to other nodes. The Output node has only one input socket, so #(2) means “Connect first input socket to node with index 2” which is the “Input” node defined in the second sub-array of the flow. If there are more sockets, the list can look like #(2,5,8) - this defines 3 connections to 3 input sockets, to the nodes with index 2, 5 and 8 within the .internalFlow definition. Note that when the .flow is expanded, these numbers can change as the indexing within the expanded list can also change when BlackOps are replaced with their content.
*The 3rd element of the node array defines the settings of the node itself - these are used by the Magma Compiler to actually create the node of the given type. In the case of the Output, the first element contains the Channel name, the second the channel type and the 3rd the arity (components count). In the case of the Input node, the first element is the input node type (“Channel” in this case), the second is the name of the channel (“Color” in this case). The number of elements in this array varies depending on the node type. The Compiler expects a specific number of elements and it will fail if the number of elements does not match.
*The 4th element is another array used only by the MagmaFlow Editor for display purposes. It has the following layout (taken from the “Krakatoa_Channel_Node_Editor.ms” file:

--[4] #( --this array is used by the KCE editor and is NOT checked for consistency by the Compiler. Thus, it can contain arbitrary data. The records below are used internally: --[4][1]:[X,Y], --used by all nodes, stores the position of the node in the schematic view --[4][2]: Collapsed, --used by all nodes except for Output, true when collapsed, false when expanded. --[4][3]: Selected, --used by all nodes, stores the selected state of the node (this means that saving and loading a flow preserves selection!) --[4][4]:ID, --used by Input nodes to define the unique ID of the TrackView track used to store its controllers --[4][5]:#(Int,Float,Vector,LastGeoList) --used by Input nodes to store the last values of Integer, Float and Vector track. --[4][6]:"LastScriptCode", --used by Input nodes to store the last used script --[4][7]:isConnected, --true when the node is connected, false when it is free-floating, causes the node to be drawn darker --[4][8]:"UserName" --used by all Nodes --[4][9]:"LastChannelType" --used by Input nodes --[4][10]:"Last Transform Object Name" --used by ToSpace/FromSpace Operators --[4][11]:"Position Locked" --allows the locking of a node to prevent moving with the mouse. RESERVED. Not implemented in the KCE yet. --[4][12]:"Control Exposed" --true/false - exposed to Modifier's UI or not --[4][13]:"BlackOp Info Array. -- stores info about the blackOp a node belongs to --[4][14]: Output Socket Flipped --true/false - whether the output socket is flipped or not. --[4][15]: Debug Default Values for Channels - array containing the default values for debugging purposes --[4][16]: User Notes - a string containing a user description of the node/BlackOp. --[4][17]: Integer Value Connection Controller. Undefined if no connection --[4][18]: Float Value Connection Controller. Undefined if no connection --[4][19]: Vector Value Connection Controller. Undefined if no connection --[4][20]: #(Pan, Zoom) Last Pan And Zoom values (stored in Output Node Only)
*The 5th element is used both by the Magma Compiler and the MagmaFlow Editor and defines whether the node is enabled or disabled. When it is disabled, the first input of an Operator will be piped into the output without processing.

Knowing this layout, it should be possible to create any flow you want by setting the .internalFlow and .flow properties to strings containing the correct arrays.
Alternatively, with the MagmaFlow editor open, you could set some global variables and call some functions to update both the display and the modifier’s properties. I could show you how this is done if you want to know.

But in general we haven’t really provided a simple function for changing the flow via MAXScript. It is a fair request and we will look into adding better support for that ASAP.

Thousand thanks for this incredible fast and accurate description! You r the man bobo! *worshipping

Now if you want to modify existing or add new nodes with MAXScript, it should be much easier than what I posted previously.

When you have MagmaFlow open with a specific modifier selected, you can use the following global variables and functions to access and modify the flow:

FranticParticleRenderMXS.openMagmaFlowEditor $.modifiers[1] --this will open the MagmaFlow Editor so you can work on its content. The argument is the KCM to edit.
KrakatoaChannelEditor_BaseNodeTreeData --this global variable contains the array form of the .internalFlow property (see previous post)

For example, if you want to change the input node in the default KCM from “Color” to “Normal”, you can say

KrakatoaChannelEditor_BaseNodeTreeData[2][3][2] = KrakatoaChannelEditor_BaseNodeTreeData[2][4][8] = "Normal" --set the channel name KrakatoaChannelEditor_BaseNodeTreeData[2][4][9] = "Normal" --set the node userName KrakatoaChannelEditor_BaseNodeTreeData[2][4][3] = true --select the node (useful for the next step) KrakatoaChannelsEditor_Functions.buildDisplayTree() --builds the KrakatoaChannelEditor_DisplayNodeTreeData array which is used for the display of the current flow in the Editor KrakatoaChannelNodeEditor_Rollout.createNodeTree init:true --build the Helium node tree - this updates the Editor display KrakatoaChannelsEditor_Functions.updateParentModifier() --update the .flow and .internalFlow properties in the currently selected modifier

The first line has to set two properties because the one property is what the compiler sees, the other is what the editor uses to remember the last channel name. If you set only the compiler name, when the editor is updated, it will overwrite it with the old value, or if you set only the editor one, the compiler won’t see the change and reflect it in the viewport.

If you want to multiply the Normal channel by the floating point number 2.0, you could then do this:

Since we set the selected state of the node in the previous step, adding a Multiply Operator node will auto-insert into the flow, so we can simply say

KrakatoaChannelNodeEditor_Rollout.addNewOperator type:"Multiply" KrakatoaChannelNodeEditor_Rollout.addNewInput type:#value valueType:#float defaultValue:2.0

and the Normal channel will be multiplied by the float 2.0…

Thanks a lot for your engagement, this is great support!!
A bit of stuff I’ve got try out by tomorrow!

I am working on a simplified interface struct which will let you do most of the MagmaFlow editing with simple commands.
Something like

MagmaFlowScript.EditStart $.modifiers[1] MagmaFlowScript.switchChannelInput 2 "Normal" MagmaFlowScript.addOperator "Multiply" MagmaFlowScript.selectNode 2 MagmaFlowScript.addValueInput type:#float defaultValue:5 MagmaFlowScript.connectNodes 4 3 2 MagmaFlowScript.setValueInput 4 type:#vector MagmaFlowScript.EditEnd()

This will take me some time to write, so the above code doesn’t do anything right now, but in my internal version it mostly works already…

Here is the first iteration of a MagmaFlow MAXScript exposure struct.
It provides the basic functions for creating, editing, connecting, selecting and deleting nodes in MagmaFlow.
It is Work In Progress and I will update it as I add more functionality.

It currently lacks the ability to create TextureMap, Geometry and Script input nodes, as well as BlackOp nodes.
It cannot create, navigate or edit BlackOps, only the top-level flow.
There could also be some functions for switching various options in the UI like order mode, auto-update mode etc.

The script file contains some examples in the header. Evaluate it after starting Max, or put it in the \Scripts\Startup folder.

EDIT: Replaced script with a new one that provides TextureMap and Script support. Changed the name of all “switchSomething” functions to “setSomething”, e.g. “SwitchOperator()” is now “SetOperator()”.
Krakatoa_MagmaFlowScript.zip (3.88 KB)

Thanks a bunch, I’m at a loss for words. :open_mouth:

. Still impressed…

Tried it and works like a charm!
Keeping you updated if there are any bugs or so. Thanks again!!

I replaced the ZIP with a new one with 4 new functions for adding and changing Script and TextureMap nodes.

Hey Bobo, Im adding a new KCM to a PRT_FumeFX object and setting the internalflow parameter by script.
Is there a way to update the viewport by script as the partices arent updated until the editor is opened and closed

In theory, any change to the modifier stack or the KCM’s parameter block should trigger a recompile of the flow.
I am not sure why it is not updating - setting the internal flow and the flow parameters by script IS a change of the paramblock and should trigger an update.
Try calling

$.modifiers[1].autoUpdate = on

and see if it changes anything. You could also turn it off and on like

$.modifiers[1].autoUpdate = off
$.modifiers[1].autoUpdate = on

which is equivalent to unchecking >AUTO UPDATE and checking it again - it normally forces a recompile.
Another thing that sometimes works as a good hack is to set the TimeSlider’s time without changing it, like

sliderTime += 0

This will trigger a time change notification which should force an update of any objects that need one.
Let me know if neither of these methods works for you.

Neither of those work :frowning:
The particles just stay black.
I have tried scrubbing the timeline too and deselecting/selecting the PRT_FumeFX without success.
Im accessing the modifier within a function like this:

theKCM = KrakatoaChannelsModifier() addmodifier obj theKCM theKCM.internalflow = "<flow stuff here>"

This points at a problem with the internal flow you are setting - no matter what you are doing, it won’t get any more updated than that. When you open and close the Editor, you are probably not getting the flow you set but the default flow the KCM has at creation. Unless you are providing the .flow property with the right data too, but your code does not show it.
Can you post the “” you are using so I can see what you are attempting?

this is what im setting:

theKCM.internalflow = "#(#(\"Output\", #(3), #(\"Velocity\", \"float16\", 3), #([350,0], false, false, undefined, undefined, undefined, true, \"Output\", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, #([0,0], 1.0)), true), #(\"Input\", #(), #(\"Channel\", \"Velocity\"), #([140,140], false, false, \"301072_617971\", undefined, undefined, true, \"Velocity\", \"Velocity\"), true), #(\"Operator\", #(2, 4, 0), #(\"Multiply\"), #([317,215], false, false, undefined, undefined, undefined, true, \"Multiply\", \"Multiply\"), true), #(\"Input\", #(), #(\"Value\", \"Float\", \"trackViewNodes.KrakatoaChannelEditor.KCE_5026996_9433759.Input_585097_74750\"), #([104,291], false, true, \"585097_74750\", #(1, "+(mult as string)+", [1,1,1]), undefined, true, \"Float\", \"Float\", \"Color\"), true))"

mult is an integer passed to the function (last line above)

if I run this the .flow property is still the same as the default one initially, but when I open the magma flow the .flow property is correct. Also the on opening the magma flow the flow is not the default one, it is the velocity multiplied one as defined above.

here is the whole function:

[code]
fn addKCMVelocityMult obj mult=
(
if classof obj != PRT_FumeFX then return false
else
(
theKCM = KrakatoaChannelsModifier()
addmodifier obj theKCM
theKCM.internalflow = “#(#("Output", #(3), #("Velocity", "float16", 3), #([350,0], false, false, undefined, undefined, undefined, true, "Output", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, #([0,0], 1.0)), true), #("Input", #(), #("Channel", "Velocity"), #([140,140], false, false, "301072_617971", undefined, undefined, true, "Velocity", "Velocity"), true), #("Operator", #(2, 4, 0), #("Multiply"), #([317,215], false, false, undefined, undefined, undefined, true, "Multiply", "Multiply"), true), #("Input", #(), #("Value", "Float", "trackViewNodes.KrakatoaChannelEditor.KCE_5026996_9433759.Input_585097_74750"), #([104,291], false, true, "585097_74750", #(1, “+(mult as string)+”, [1,1,1]), undefined, true, "Float", "Float", "Color"), true))”
theKCM.name = ("KCM Velocity X "+(mult as string))
)
)

addKCMVelocityMult $ 75[/code]

I see.

You cannot do that because your controllers do not exist. You have in your code parts like

\"trackViewNodes.KrakatoaChannelEditor.KCE_5026996_9433759.Input_585097_74750\"

but this is invalid (because it is Modifier-specific). The first portion of it, KCE_5026996_9433759, is the signature of a specific KCM you copied the flow from.
But when you add a new KCM with the script, its unique ID (shown just below the Notes field in the Command Panel) will be different, so the track with that number will not exist.
When you open and close the editor, the flow is being tested for missing controllers using the ID of the Modifier itself and the ID of the Input node (which in this case is 585097_74750 and is stored in a separate element of the array). If a track is not found, it will be recreated using the correct IDs.

To fix your code, you can use this snippet which is based on the code used by the KCM when loading a new flow or cloning a modifier (both operations require the recreation of the tracks for the new Modifier’s ID):

KrakatoaChannelNodeEditor_CurrentModifier = theKCM
KrakatoaChannelEditor_NodeTreeData = execute theKCM.internalFlow
KrakatoaChannelsEditor_Functions.fixFlowIDsAfterLoading KrakatoaChannelEditor_NodeTreeData
theKCM.internalFlow = with PrintAllElements true (KrakatoaChannelEditor_NodeTreeData as string)
theKCM.flow = KrakatoaChannelsEditor_Functions.expandFlow theKCM.internalFlow		

Thanks Bobo, that worked :smiley: