Metaballs script

  • Thank you; as soon as I get the local transformation in I'll post the updated versions.

  • Alright, after a lot of frustration with these local transformations (SM, please indicate in the manual that Actor.LocalMatrix() returns the transformation matrix as the transposed of what everybody else uses... ), finally got the metaball local rotations and scaling under control.

    This is tonight's test:

  • Another fast test:

  • Okay, so where to go from here. Thinking of next things to tackle:

    • Add metacylinder (field equation seems straightforward), metacube (doesn't seem tough either), metacone (no idea about field equation for this guy), metathorus (another one with odd field equation)

    • Find a way to copy the whole material tree from the field prop (the big wireframe thingie) to the surface

    • Find a way to extend the surface beyond the boundaries of the field; now that the calculation is a volume crawler it should be possible, by replacing the field array lattice[nX,nY,nZ] with a dictionary lattice(nX,nY,nZ); not sure about performance with that - dictionaries are much slower than matrices. Another thought is to keep resizing the field lattice whenever the isosurface touches the boundaries... hmmm...

    • Change from a volume crawler to a surface crawler; this might expedite processing and avoid lengthy calculations of internal volumes for large metaballs, but it does create a problem about finding the proper seed points for the crawler

    • Add a GUI dialog to Add Metaball and Run Simulation. I guess this should be easy, I think.

    • Add a callback to recalculate the isosurface at the current frame whenever the user changes one of the metaballs. That might make it easier to see the effects of manipulation in real time.

    Plus I have a random Poser crash that happens during playback. This is infuriating. The thing only blows up, and Poser doesn't even have the decency of telling why it crashed (like "Invalid geometry", or "Update failed" or whatever). How can I fix something if the stupid darn idiotic damned mongrel of a thing doesn't give me any debugging information!!

  • Hmm... actually I do have a problem with my crawler, that I didn't think of before. The volume crawler starts from the center of the metaballs and keeps crawling the volume while the field value is > threshold. I thought that would work with negative metaballs because the seed on the negative metaball would fail, but then the seed on the positive metaball would crawl its volume entirely.

    But, here's the problem:


    A positive metaball in green, and a negative metaball in red that makes the field potential at the center of the positive metaball to be < threshold. In this case the crawler will stop instead of calculating a volume in the shaded blue (which would make a thorus-like kind of thingie), because it will think it's outside the metaball.... hmm... how to fix this... hmmm....

    I think that each lattice point I should separate the sum of the positive contributions and the sum of the negative contributions. Then if the sum of the positive contributions only is > threshold, then add the neighboring points to the crawler.

    So, a volume crawler with that fix would be like this:

    for all metaballs
    	push center into volume crawler list
    while volume crawler list is not empty
    	pop point
    	for all metaballs
    		f1 = sum of all positive metaball fields at that point
    		f2 = sum of all negative metaball fields at that point
    	lattice[point] = f1+f2
    	if f1 > 1.0
    		push all 26 neighbor points into the volume crawler list
    		push all 8 west & south neighbor points into the triangle generation list

    It's just 2 lines change from what I'm doing right now. But how to change that from a volume crawler to a surface crawler when there are negative metaballs? hmm... not easy problem...

  • During my research I came across the paper that proposed the marching cubes thing in 1987 (Lorensen & Cline):

    It's pretty cool. Everybody just copies the same algorithm since then. Well done to have an algorithm copied to the letter for 30 years.

    Now, the surface crawler thing is easy when all metaballs are positive (just start from the center of the metaball, then choose a direction like X and keep going until it flips from > threshold to < threshold; then crawl around the neighbor cubes that some vertices are > threshold and some are < threshold.

    But when you have negative metaballs can't do that anymore, as the surfaces may be completely split. I can't think of a general way to handle that. The volume crawler with the separate f1 and f2 functions as above seems to be the only general way that I see, and should handle solids with holes and also complete splits.

    So, not spending more time with the surface crawler thingie.

  • Hi! I'm really Interested in this.
    Will you make it compatible with octane render?

  • Hello Spartan; I'm not sure how Octane works, so it's difficult to predict.

    For rendering a single frame I'd say most probably yes, because that field is just a regular prop with regular texture nodes, which is regenerated on each frame.

    For rendering a movie that will have to be seen, as the prop has variable geometry, so it has to be fully regenerated on each frame. That's done through a Python callback. So if Octane refreshes everything for each frame, then it should work, but if it retains information from the previous frame then it might not work.

    One thing I'm thinking, though, to avoid the trouble with the callback, is to build the field as a series of props, and just hide/make them visible in the right frames. That way it should be compatible with any renderers.

  • What a pain in the rear is SeoGeometry() and Python callbacks!!! The thing will crash randomly on you with no information of what's wrong, and the manual provides minimum insights on any of the callbacks. For example, it has this very useless description of a typical callback:

    Set a per-update callback to process actor (vertices) while in local deformed
    space. The user-defined function will be called back on each full update of an
    actor and allows for manipulation of the vertices (or other information) at the
    point in the display pipeline of Poser which processes vertex level deformations.
    The callback function should take an actor object and make desired
    modifications to it.

    It refers to a "display pipeline" but gives no information of how that pipeline is, so I have no clue where this actually fits in the flow. It tells I can make "desired modifications" on "other information" but doesn't say what I can change - can I change textures? geometry? only vertex coordinates? Also, what's a "full update"? If the frame changes but the actor is static will it update?

    The Python API is incredibly vague and limited about geometry changes! What a shame! It seems that whoever wrote it thought paper was expensive, so he/she limited the description to the smallest amount possible.

    So because someone didnt' want to spend 15 minutes describing an API function fully, I (and probably other customers) had to spend 15 hours of my life trying to identify what I can and cannot do inside a callback.

    I'm giving up on this sleazy function. Mission now is to dynamically change the geometry of a prop without using that loser of a function which is SetGeometry().

  • @fbs7 would you like me to see whether the Mac version is less prone to the crashes you are experiencing? I'm quite happy to bulk the think up with debugging statements, and I have unbuffered logging routines that write to an external file which can be externally monitored in real time, so Poser crashing won't prevent any of the previously issued logging being swallowed by the output buffering.
    I could, alternatively, send the logging routines to you, if you prefer.

  • @fbs7 have you made progress on transferring material trees? I have developed Python routines which can save and load pose, character and scene files to and from the library, bypassing Poser's own load and save routines, but have yet to implement the material tree creation. It's been a while since I last looked at it, but IIRC I found something missing from the Python materials API which meant I couldn't just create all the nodes and trees. I'll have another look, but the simplest solution for transferring materials may end up being just save from the field prop and reload them from the library onto the metaball prop(s).

  • @anomalaus said in Metaballs script:

    @fbs7 would you like me to see whether the Mac version is less prone to the crashes you are experiencing? I'm quite happy to bulk the think up with debugging statements, and I have unbuffered logging routines that write to an external file which can be externally monitored in real time, so Poser crashing won't prevent any of the previously issued logging being swallowed by the output buffering.

    That's a good idea. Try these two scripts, if you would. This one is "Create":

    import poser
    from numpy import oldnumeric
    # This script creates a cube to limit an isosurface, and a handler for a metaball
    # fbs 12/7/2017
    # Check if 'IsoField' already exists, if not create one
    scene = poser.Scene()
    	isoField = scene.Actor('IsoField')
    	# Vertices of a cube
    	c = 1.0         # Cube size
    	verts = oldnumeric.array( [[0,0,0],[0,0,c],[0,c,0],[0,c,c],[c,0,0],[c,0,c],[c,c,0],[c,c,c]] )
    	# Sets = vertex indexes
    	sets = numpy.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    	# Polys = faces, index to sets
    	polys = oldnumeric.array( [[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]] )
    	# Create the geometry
    	meshGeom = poser.NewGeometry()
    	meshGeom.AddGeneralMesh( polys, sets, verts )
    	# Create the prop
    	isoField = scene.CreatePropFromGeom(meshGeom,'IsoField')
    	# Set draw style to wireframe
    	isoField.SetDisplayStyle( poser.kDisplayCodeWIREFRAME )
    	# End Frame
    	parmEndFrame = isoField.CreateValueParameter('End Frame')
    	# Start Frame
    	parmStartFrame = isoField.CreateValueParameter('Start Frame')
    	# Resolution
    	parmResolution = isoField.CreateValueParameter('Resolution')
    	# Threshold
    	parmThreshold = isoField.CreateValueParameter('Threshold')
    # Find the smallest N that 'MetaBallN' does not already exist as children of the iso field
    childList = isoField.Children()
    found = 0
    for n in xrange(1,101):
    	metaBallName = u'MetaBall' + str(n)	# Poser needs Unicode
    	found = 0
    	for child in childList:
    		if child.Name() == metaBallName:
    			found = 1
    	if found == 0:
    # Now let's create a metaball handler; it will be a little cube; code is the same as above
    c = 0.01
    verts = oldnumeric.array( [[0,0,0],[0,0,c],[0,c,0],[0,c,c],[c,0,0],[c,0,c],[c,c,0],[c,c,c]] ) - c/2
    sets = oldnumeric.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    polys = oldnumeric.array( [[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]] )
    # Create the geometry
    meshGeom = poser.NewGeometry()
    meshGeom.AddGeneralMesh( polys, sets, verts )
    # Create the prop and make it a child of isoField
    metaBall = scene.CreatePropFromGeom(meshGeom,metaBallName )
    parmStickness = metaBall.CreateValueParameter('Stickness')
    metaBall.SetParent(isoField, 0, 0)
    parmStrength = metaBall.CreateValueParameter('Strength')
    # Position the prop in the middle of the iso field
    geom = isoField.Geometry()
    vertex7 = geom.Vertex(7)
    sX = vertex7.X()
    sY = vertex7.Y()
    sZ = vertex7.Z()
    # All done; now we have a the cube for the iso field, and a smaller cube to center metaballs in that field

  • hmm... can't post Run; too large; and can't attach .txt or .py either...

    let's see if I can find some file sharing service somewhere

  • @fbs7 I use DropBox

  • Try this guy:

    link text

  • Now, these crashes are sporadic. In my last test it took some 3 hours of continuous running and some 20 simulations to get it to crash, so it's not like it runs once and crashes. Some people might live with a crash in 3 hours, but that's not good enough.

    I've disabled some aspects of it to see if the crashes would go away, so testing will have to be in a particular sequence.

    Suggest this:
    (a) Start from blank document
    (b) Execute "Create Metaball" 3 times; it will create a big wireframe box and 3 small boxes one on top of the other
    (c) At frame 1, separate the 3 small boxes into 3 positions within the big wireframe
    (d) Check the big frame "isoField" properties; set threshold=1, resolution=20, start frame=1, end frame=20
    (e) Check the small boxes "Metaball n" properties; set strength=1.0, stickness=2.0
    (d) At frame 20, move the metaballs to other positions, rotate and scale them, can change the field strength (a field strength=10 means the metaball diameter covers the whole field, =1 means its diameter is 10% of the field), change stickness slightly (stickness=2 means it will stick to other metaballs up to 2x of its radius away)
    (e) Save the file so you don't have to redo this again
    (f) Execute "Run Metaballs"; once it says "Generate mesh frame 20" it's done; then click on Play to play the animation
    (g) repeat (d) to (f); eventually it will crash

    Thanks for the help!

  • @fbs7 first problem is numpy namespace definition

    Traceback (most recent call last):
      File "/Applications/Poser 11/Runtime/Python/poserScripts/ScriptsMenu/Partners/FBS/Create", line 19, in <module>
        sets = numpy.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    NameError: name 'numpy' is not defined

    I'll try just:

    import numpy

  • Hmm.. how odd; that works ok for me; I took that import from one of the samples in PP11, but some of these samples look broken

  • Actually, scrap that; that was my typo; I copying stuff to the Python script a bit ago and touched that line; please chaneg to this

    	# Sets = vertex indexes
    	sets = oldnumeric.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )

  • Anomalous, have you tried anything about using that SetGeometry() little monster to set a dynamic geometry?

    If that thing worked reliably, then it would be easy to manipulate any geometry dynamically. Just bake it in memory, and do the switcharoo in an appropriate callback.

    For example, one could do 3D Join and Minus operations with solids, or switch figures with a reduced poly version of them for faster preview.