Step 4, copied all values for the nodes from source to target; can't connect yet as not all target nodes are created yet. This tested ok:
# Copying the materials from isoField to IsoSurfaceN mat1 = isoField.Material('Preview') mat2 = isoSurface.Material('Preview') for layer1 in mat1.Layers(): # Find the material layer in target or create it try: layerName = layer1.Name() layer2 = mat2.LayerByName( layerName ) except: mat2.CreateLayer( layerName ) layer2 = mat2.LayerByName( layerName ) # There's a bug in the API; need to retrieve ShaderTree() via # the material before you can retrieve it from the layer for newly # created props. Oh well. Don't remove the two dummy line below dummyTree = mat2.ShaderTree() tree1 = layer1.ShaderTree() tree2 = layer2.ShaderTree() # Remove all nodes in the target tree for node2 in tree2.Nodes(): node2.Delete() # Now create all nodes in the target tree for node1 in tree1.Nodes(): node2 = tree2.CreateNode( node1.Type() ) node2.SetLocation( node1.Location(), node1.Location() ) node2.SetName( node1.Name() ) # Check all node inputs for nNodeInput1 in xrange(0,node1.NumInputs()): # Get the inputs; they will be at same indexes nodeInput1 = node1.Input(nNodeInput1) nodeInput2 = node2.Input(nNodeInput1) # Set values nodeInput2.SetAnimated( nodeInput1.Animated() ) value1 = nodeInput1.Value() if isinstance( value1, float ): nodeInput2.SetFloat( value1 ) elif isinstance( value1, basestring ): nodeInput2.SetString( value1 ) else: nodeInput2.SetColor( value1,value1,value1 )
That part went really well. The API is really well built for the materials thing - although it does take some coding to fully copy the entire shader node from one prop to another. But once that's done it can be easily reused by putting that in a library.
This is a random (and rather ugly, but I'm sleepy) shader tree that starts as this at frame 1:
and ends as this at frame 20:
The script copies all layers, nodes, values, connections and animated settings from the IsoField prop to each surface prop. It only copies for 'Preview' material, but it's easy to copy for all materials - just put a loop on top.
This is tonight's test with this: scaling, translation and rotations in all metaballs, scaling, translation and rotations in the parenting field, a 2-layer texture with some random nodes and some animated parameters:
And best thing, not a single crash since I abandoned the callbacks and SetGeometry() thingie.
I get very cranky with crashes. I hate crashes with determination. Specially since if one of my applications crashes at work within 15 minutes I end in a call with 60 people (no kidding!) to get things back up, explain why it crashed, how it crashed and tell how I'll make it never crash again, and I get someone breathing at my neck for weeks until we put a patch for the thing.
One crash is one crash too many, that's what I say.
These are the scripts at this point:
Still to do Hide Metaballs and Remove Metaballs.
@fbs7 just got back to this after some real-world commitments.
First run of the new scripts and I notice discrepancies between the material shaders copied from the isoField prop to the isoSurface props:
IsoField material tree:
vs. isoSurface material tree:
The isoField material is constant throughout the frame range. The isoSurface material differences are:
Multiple surface node render engine selections are not copied. CyclesSurface root node should have SuperFly Root checked.
Multiple-output nodes are not being connected to the correct output. LightPath node should connect to the second output "Is Shadow Ray", rather than the first "Is Camera Ray"
Everything else looks fine.
anomalaus last edited by anomalaus
Hmmm, I can see how to set the isoSurface CyclesSurface node to be the SuperFly Root, using
if node1.IsRoot() and node1 is tree1.RendererRootNode(poser.kRenderEngineCodeSUPERFLY): tree2.SetRendererRootNode(poser.kRenderEngineCodeSUPERFLY,node2)
where node2 is the new isoSurface's copy of node1.
But I can't see how to tell the node.ConnectToInput(<nodeInput>) method how to select which output of the source node is to be connected to nodeInput!!!!!
Another Gaping API hole!!!The LightPath node certainly reports that it has NumOutputs() == 12.
Looking at the inputs of the isoSurface's MixClosure node, I can see:
>>> for input in mc2.Inputs(): ... input.Name(),input.InNode().Name(),input.InOutput().Name() ... (u'Fac', u'LightPath', u'Is Camera Ray') (u'Closure1', u'GlassBsdf', u'BSDF') (u'Closure2', u'TransparentBsdf', u'BSDF')
But there's no corresponding SetInOutput() method to let me connect to the 'Is Shadow Ray' output of the LightPath node! Unless there's some funky, undocumented way to use node.SetSelectedOutput(OutputIndex) to force ConnectToInput to use something other than the first output...
Nup. Still connects 'Is Camera Ray' instead of 'Is Shadow Ray'... Thinks...
A Hah! For nodes with more than one output, you can't use the node's own ConnectToInput() method, you have to use the specific Output's ConnectToInput() method!!!
The fix, then, is to either test for the number of outputs and loop through the outputs if there is more than one, or better still, always loop through the outputs (every node has at least one, including the root nodes, though theirs is hidden), keeping the current output's index and only call that output's ConnectToInput() method if the index matches the isoField node's input.InOutput() return value.
Ran out of time. The last paragraph should read [since InOutput() returns an output, not an output index]:
The fix, then, is to either test for the number of outputs and loop through the outputs if there is more than one, or better still, always loop through the outputs (every node has at least one, including the root nodes, though theirs is hidden), and only call that output's ConnectToInput() method if the output.InternalName() matches the isoField node's input.InOutput().InternalName().
Oh, that's wonderful finding, anomalous. I shall fix that right way. Thanks so much!
You're the best!
By the way, I didn't know you could have multiple outputs; I thought the thing was a real tree (that is parent 1:N child), as the regular nodes have that. I had no idea there are these crazy nodes with multiple inputs and multiple outputs, so the thing really has a relationship parent N:M child.
I think next steps are those:
(a) Replace the tiny cube handler with bigger spheres that more closely resemble a ball size = 2
(b) Add metacube/metacylinder/metaplane/metaline
(c) Add the Stop Simulation and Clear Simulation scripts
(d) Add a little GUI with 4 buttons, and integrate the 4 scripts into one
Thinking of using an icosahedron for the sphere, as it's very simple to generate and has very few vertexes... what do you think?
anomalaus last edited by anomalaus
@fbs7 multiple outputs came in with compound nodes (that's why input.InNode(flag) has the flag to select whether to return the compound shell node or the first real shader node within the compound node). Many of the cycles nodes (like LightPath with 12) have multiple outputs. Despite not being visible, the root nodes actually report 1 for their NumOutputs() method.
@fbs7 "metaicosahedraballs" works for me. ;-)
Had you seen @Snarlygribbly 's Particles script? Any thoughts about integration with that? I saw one of your demo movies which reminded me of water pouring into a tank, and it made me think of that.
I didn't. Any link for the source?
Today's work: added a little GUI, some help text, and merged all scripts into a single one. Now it's just a Metaballs.py that does it all with a tiny GUI:
The GUI is pretty simple with wxWidgets:
######################################################################################################## # # GUI # frame = wx.Frame(None, wx.ID_ANY, 'Metaballs 0.01') frame.SetSize( wx.Size(200,400) ) frame.SetWindowStyle( frame.GetWindowStyle() & ~wx.MAXIMIZE_BOX ) sizer = wx.BoxSizer( wx.VERTICAL ) frame.SetSizer(sizer) button1 = wx.Button( frame, wx.ID_ANY, 'Create Metaball' ) button2 = wx.Button( frame, wx.ID_ANY, 'Run Metaballs' ) button3 = wx.Button( frame, wx.ID_ANY, 'Hide/Show Metaball Surfaces' ) button4 = wx.Button( frame, wx.ID_ANY, 'Hide/Show Metaball Handlers' ) button5 = wx.Button( frame, wx.ID_ANY, 'Remove Surfaces' ) button6 = wx.Button( frame, wx.ID_ANY, 'Heeeeeeeelp!' ) button1.Bind(wx.EVT_BUTTON, CreateMetaball) button2.Bind(wx.EVT_BUTTON, RunMetaballs) button6.Bind(wx.EVT_BUTTON, Help) sizer.Add(button1, 1, wx.EXPAND, 5) sizer.Add(button2, 1, wx.EXPAND, 5) sizer.Add(button3, 1, wx.EXPAND, 5) sizer.Add(button4, 1, wx.EXPAND, 5) sizer.Add(button5, 1, wx.EXPAND, 5) sizer.Add(button6, 1, wx.EXPAND, 5) frame.Show(True)
By the way, I like how it behaves (with no parent), because if you click in the Poser window or in the minimize button the GUI goes away to the taskbar, and the user can click in the taskbar to bring it back.