Jul 112011
 

BA Thread

What it Is – ProgrAnimals is an initial framework for variable morphology pose-controlled physics-based animation in Blender, whatever that means.

What it Does – In simplest terms this script creates a game-engine physics based ‘walking ragdoll’ from a user-defined armature, from humanoid biped to something much more bizarre. I think the game ‘Spore’ is probably the best analogy, where people can make creatures and they sort of walk or hobble based on how they’re built. This is the same notion, except much less automatic right now. The user has to define a little more about structure, muscles, and gait.

Why it Be – This is inspired first and foremost by my enduring love of doing horrible things to ragdolls, for which I make no excuses or apologies. The implementation is inspired especially by Cartwheel-3d, SIMBICON, and Philippe Beaudoin, and also by Ari Shapiro’s DANCE. The future goals of this are somewhere between Endorphin, Spore, and may also include possible robotics integration with Arduino. Ultimately I’d like to create a break-dancing robot army to help me stamp out tyranny, oppression, and tired dance moves.

This is the unexpected result of some of what I learned from the whole progranimation thing. I thought I would make a more complex pose-control system, but I ended up wanting to make a more generalized system that could work with arbitrary leg configurations. Originally I think it had something to do with making circuit components walk around on a circuit board but that’s another story, might still do that though. Anyway- this is actually a vastly simplified controller compared to Uno and Deuce in progranimation. No balance feedback, no uprightness vector, not even a collision sensor on the feet, it’s just proportional derivative controllers and a timed state-machine. It’s about the most bare-bones pose-controller I think you could make, but it’s still hilarious to watch it make stuff walk around. I hope to expand from here by giving the torso and legs considerable options as far as number of states, balance sensors, state-switching reactions, etc. First I wanted to get something functional and this is it.

I hope this will be useful and interesting, but it may take a little time to get a creature working. That said, it can be very fun and I hope I’ve made it easy enough to use that most Blender users can figure it out. Here are a couple of walk-throughs that demonstrate how the process works.

I. EDITING WALK STYLES – Changing the gait of a pre-made biped rig.

1) Download this file ProgrAnimals-I-biped.blend and open in Blender2.58a

2) Press ‘P’ to run game engine. Watch as very little happens. Press ‘ESC’ to stop game.

3) Select the iARMa-pg-0 armature (pose-goal armature). Switch to POSE MODE, and change the pose to something approximating this screenshot.

[Exact settings: BONE x|y|z rotation : THIGH0 -45|0|0 | SHIN0 90|0|0 : THIGH1 15|0|0]

4a) Run game again (P).

EXPLANATION: The left leg in the pose-goal armature is the ‘Swing Leg’, the right is the ‘Stance Leg’. They are the pose-goals that the leg will try to assume when in this state. Poses can also be stored in poseLib for convenience.

4b) while not bored: change settings; run game again (P).

5) Select the iARMa armature and switch to EDIT MODE.

6) Select each THIGH bone, change the bones Custom Property legstate = 0 on both.

7a) Switch to OBJECT MODE. Run game again (P).

EXPLANATION: legstate defines if the leg starts on swing or stance, setting both to the same makes the ragdoll jump.

7b) Change one thigh back to legstate = 1 (make the ragdoll walk again)

8} Select the iARMa-THIGHl-k empty (best to select from outliner). Look at the Scale values in the ‘N’ properties view. Change the ‘X’ scale to 10. Do the same on iARMa-THIGHr-k.

9) Run game again

EXPLANATION: The scale setting on these empties define the stiffness and damping parameters for their joints. See progranimation for a better explanation of PD controllers)

10) Edit the iARMa armature. Select the TORSO bone – change the bones Custom Properties: statetimer = .25; zgoal = ‘BALL’

11) Switch to OBJECT MODE. Run game again (P)

EXPLANATION: statetimer times the switch between states, zgoal defines the heading goal of the character. zgoal can be the name of an object or an angle in degrees using the prefix ‘a:’ ex. a:45

 

II. SETTING UP A NEW CREATURE

1) Download this file ProgrAnimals-II-quad.blend and open in Blender2.58a.

2) Select the iARMq armature.

3) In the script editor make sure the variable setupSEQ = 0. Run the script ALT-P. This script will add custom properties to the bones of this armature based on the names of the bones.

BONE SETUP/NAMES:
The script will use bone names as a starting point to setup constraints with axis and angle limits for standard joints. see code for values. THIGH, SHIN, FOOT, TOE, REVKNEE, just use these strings somewhere in the bone. The names aren’t necessary but they make setup much easier as they add the necessary Custom Properties to the bones.

CUSTOM PROPERTIES ADDED TO BONES:
TORSO
statetimer: time to switch states
zgoal: goal in degress ‘a:45′ or object name ‘Cube’
THIGHS
legstate: leg start state (0-swing/1-stance)
legtype: type of leg config (for multi leg setups)
ALL BONES:
usebone: create box for this bone
xyzmin/xyzmax: values to be used to create 6dof rigid body joints
*usebone and legtype are the only values that cannot be modified after this step

4) Select the armature, enter EDIT MODE. Make sure Custom Properties were added to bones. Select the THIGH bones. Change the value of legtype so the front legs are ’0′ and the back legs are ’1′. This will let the program distinguish between the different types of legs and will create pose-goal armatures for each leg type. You can have as many types of legs as you want, but for now they only have 2 states each: swing/stance.

5) Switch to OBJECT MODE. Select the armature. In the script editor set variable setupSEQ = 1. Run the script. This will create the boxes and empties for each bone. Boxes are game objects connected by rigid body constraints. Empties define stiffness/damp gains at each joint with their xyz scale values.

6) Select the armature. In the script editor set variable setupSEQ = 2. This will create pose goal armature(s) to be used to define swing/stance state pose goals for each leg type.

7) Adjust pose-goal armature poses as desired. Press ‘P’ to run game engine.

8} Design a crazy five-legged monster and make it chase some other crazy thing around.

9) Game> Record Animation. Rinse, Repeat.

NOTES FOR MAKING YOUR OWN CREATURE:
*Legs must at least have a thigh, the rest is optional. Feet are good to have though.
*Legs can be nearly any config as long as they do not branch (no double-footed legs or anything like that, a long tail is fine though).
*Use very short names on armature or bones, under 5 chars each (sorry).
*Do not use ‘-’ (dashes) in the names of bones or armatures. I need those.
*The mass of each bone-box is determined by the length of the bone. This can make for very heavy limbs so you might want to adjust this. You may also want to change the shape of certain bone-boxes, that should work as long as you don’t change the origin.
*The settings created for THIGH, SHIN, FOOT, etc. are generic and can be changed to anything. The settings for each joint are combination of the rigid body constraint settings and the stiff/damp parameters. The initial setup of the rigid body constraint is defined by the xyzmin/xyzmax setting on the bone, you can change these Custom Properties before you create this bone-boxes, or you can adjust them in the constraint settings after they’re created.
*Characters should be created facing Y+ forwards.

 

III. TIMER OFFSET - Offsetting state switch timing.

This is a little cheat that offsets the state switch timing for a leg by a given amount. This allows sequential legs like the centipede guy, and galloping quadrupeds and whatnot. Setting it up can be a little tricky and depends on the creature design and the walk style you’re looking for.

Here is the .blend file with script with the timeroffset feature added. The example files for the previous tutorials do not have it. This file also contains all the creatures used in the demo video.

The timeroffset property is added to each leg bone in the armature when the first part of the setup is run (setupSEQ = 0). When the game objects are created the legs will use this bone property offset when that state is triggered.

That’s about it for the offset. It was a lot cheaper than breaking into more than two states and it works pretty well considering how simple it was to implement.

 

IV. BIG WARNING – BUGS and ERRORS and CRASHES! OH MY!

Creating the pose-goal armature in the setup (setupSEQ = 2) turned out to be a total fiasco. I had to con bpy.ops into making some bones for me and every once in awhile it just refuses. It’s a context thing but I never could figure out why it works sometimes and sometimes doesn’t. So enjoy that. If you have a problem with a rig, try it 3 more times, if it still wont work feel free to contact me, I’ll try to help you sort it out.


V. STUFF I HAVEN’T THOUGHT OF YET

I may add notes here in the future when I think of them.

Hey, here’s something- you can ‘deadleg’ a creature by turning all the usebone bone properties to False in a leg. I guess you could make a limping zombie character or something, though you’ll have to compensate the stiff/damp on the other leg so it’s strong enough to carry the weight and the poses will have to be offset so he stays sort of balanced.

Notes – Once you start playing with pose-goals you may notice that there is something horribly wrong with the joint torques. An extreme pose can lift the character off the ground and fly (not in a cool superman way, more like falling up). Clearly this is not physics. Working on that.

Also the feet didn’t stick to the ground like I’d expect so i added a hack where the obj.setLinVel.x.y = 0 on the last object in the chain of the stance leg, which ends up being the toe if you have one. So even in flight forward momentum is lost to this bs-friction-hack. Later I might try adding material friction to the feet or something.

In the future I’d really like to improve the whole implementation in Blender but I think the key will be porting it to the animation system once we get rigid body dynamics in the physics system. It would be great to have these rigs interact more cleanly with other dynamics. I love the game engine but the ‘Record Animation’ thing makes this a really weird workflow, glad it’s there though.

Why No UI? – mostly because the whole setup script is kind of sketchy as it is, it randomly crashes if  you keep remaking ragdolls from the same armature for some reason. Plus these are program driven animations so I think if you’re going to use this for anything you’re going to have to get into the code eventually anyway.

Personal Request – If you make a cool critter with this please show me! A render is worth a thousand comments. Hope you enjoy!

May 102011
 

I don’t even know…

Okay, I guess I do know, I made it.
Why?
I don’t even know.
How?
bge and a simple gravity script of course.
wanna hear it? here is go!

import bge
from mathutils import Vector
co = bge.logic.getCurrentController()
scene = bge.logic.getCurrentScene()

obList = []
for o in scene.objects:
    if o.__class__ == bge.types.KX_GameObject:
        obList.append(o)

def calcGrav(obA, obB, G):
    m1 = obA.mass; m2 = obB.mass
    m = m1*m2
    loc1 = obA.worldPosition
    loc2 = obB.worldPosition
    v = loc1 - loc2
    r = v.length
    F = G * ( (m) / (r*r) )
    return -v * F

def loopGrav(obs, G):
    for obA in obs:
        fV = Vector((0,0,0))
        for obB in obs:
            if obA != obB:
                fV += calcGrav(obA, obB, G)
        obA.applyForce(fV, False)

loopGrav(obList, 1)

keyb = bge.logic.keyboard
if keyb.events[bge.events.ZKEY]>0:
    for ob in obList:
        ob.applyTorque(Vector((0,0,50)), False)

if keyb.events[bge.events.XKEY]>0:
    loopGrav(obList, -2.5)

if keyb.events[bge.events.CKEY]>0:
    loopGrav(obList, 15)


HOW TO:
- copy/paste script into blender text editor.
- setup an EMPTY object with logic nodes as shown.
- change ‘Engine’ from ‘Blender Render’ to ‘Blender Game’
- set World>Physics>Gravity to 0.0 (under Bullet)
- create some objects.
- hit ‘p’

This script will (should/might) make all meshes in the scene obey Newton.

Apr 132011
 

Blender.org TrackerBA ThreadBlenderNation Article

UPDATE – 01.05.13 – v0.2.7
FIXES:
-Fixed the issue that prevented the addon from loading in Blender 2.63+. Also fixed a problem with the makeMeshCubes function.

UPDATE – 02.14.13 – v0.2.6
ISSUES:
-An API change has made the addon not load. It has something to do bpy.scene.context. I’ll look into it when I have a chance. Fortunately the script will still run as a regular script. If you want to use it, just use the .blend file below or just load it in the text editor and hit run.

UPDATE – 06.20.11 – v0.2.6
FIXES:
-Moderate speedup.
-Cubes output scale/loc corrected.
-Tooltips added.
NEW FEATURES:
-Container insulators: Arbitrary mesh shapes can be used as insulator/cloud objects. Still quite imperfect though. May slow down generation. Best for simple containers; bowl, cup, bottle. A spiral pipe would not work well. Must have rot=0, scale=1, and origin set to geometry.
-Mesh origin objects:. If the origin obj is a mesh, vert locations will be used as initial charges. However this will disable multi-mesh output. May slow down generation.

UPDATE – 05.08.11 – v.0.2.5
NEW STUFF:
-added ‘single mesh’ output option. use this mesh with build modifier to ‘grow’ lightning in animation.

If you find this really useful, please consider donating a little to the cause. Thanks!



I’m also grateful for bitcoin donations to: 1K5Yy77ejes2FZrHBG5fns3QAicnwZcduq

 

This is a partial implementation of the algorithm presented in the paper ‘Fast Simulation of Laplacian Growth’
and some concepts borrowed from Fast Animation of Lightning Using an Adaptive Mesh

It currently uses simplified spherical boundary conditions and calculates potential at candidate growth sites using FSLG-Eqn. 9
To be properly influenced by an environment map of charges and
allow artistic manipulation of growth patterns I will need to
implement FSLG-Eqn. 15, which I don’t fully understand yet.

As compared to the simulation times reported in the paper, there is no comparison. This is not fast. They report 2000 particles in 6 seconds. So far 1000 particles will take a few minutes.

A good chunk of the reason for the slowness is the weighted random choice function. Another big reason is it’s python not c. Probably the biggest reason is that I’m a hack and I barely cobbled this together so it’ll take time to get it optimized.

Anyway I think it’s better than making lightning by hand, or at least might give you a good base mesh to mess with. So it might be useful to someone. I’ll keep working on it.

DIRECTIONS:
you can use the example .blend file or load as an addon.

BLEND FILE:
-Download .blend* file, instructions in file. *right-click, save-as [works with Blender 2.5 - 2.62]
-Download .blend* file, instructions in file. *right-click, save-as [works with Blender 2.63+]

ADDON:
-Download script (object_laplace_lightningv026.rar) [this version of the addon only works with Blender 2.5 - 2.62]
-Download script (object_laplace_lightningv027.zip) [this version of the addon works with Blender 2.63+]
-Uncompress, Place in in Blender ‘addons’ folder:
Blender install folder/2.6x/scripts/addons/
-Enable addon
UI will be in >View3D>Tool Shelf>Laplacian Lightning (object mode)

-Hit ‘generate’ – try w/ defaults

-Play with the settings, try again.

iterations – how many times to run loop (number of particles) grid unit size – size of a ‘cell’ in BU
straighness – user variable to control branchiness/straighness
start charge – origin point
use ground charge plane – hacky method of simulating lightning strike. Terminate loop if lightning hits ‘ground’.
ground Z co – z coordinate of ground plane
ground charge – charge of ground plane
mesh, cube, voxel – visualization outputs
mesh – creates vert/edge mesh from data
cube – creates cube objects from data
voxel – creates a 64x64x64 voxel data file from data outputs to ‘FSLGvoxels.raw’ (experimental)

Hope you enjoy. Send me a link to some renders if you use it! Especially if you get >10,000 particles.

Mar 312011
 

[FILES UPDATED TO WORK WITH 2.57b]

.blend files (2.57b – r36339) right-click, save-as
Uno/Deuce/Pogo
Rigid Body Walking Machines
Extras

link to BA thread

This is a collection of studies and experiments in rigid body dynamics and pose control using Blenders game engine and python API. All the animation data was generated in real-time simulations (with the exception of the camera which was keyframed and the cloth which was not real-time)

———NON-BLENDER/3D NERD BRIEF———
These animations are essentially of 3d games. The games have gravity, inertia, object collisions, joints connecting objects and ranges for those joints. Each scene was created by setting up a game, and running it several times (sometimes with simple keyboard interaction which pushed or propelled a projectile). The animation data (the rotation and location of each object) was recorded during each game. The most amusing or interesting results were saved, one was selected to be rendered later.
Camera moves were keyframed (traditional/manual 3d animation) for more interesting views. Hours and hours of painful rendering, and voila.

The rectangular biped and monopod characters are seperate programs within each game. Each has the ‘joints’ and ‘muscles’ of a simplified human. The joints are functions that limit the location and rotation of an object (such as a hip) with respect to another object (such as a torso)

The muscles are ‘PD’ functions (proportional-derivative). At each frame the functions return the torque value that must be applied at a given joint to attempt to reach a given ‘pose’. These actually behave more like damped-springs than muscles.

The poses are defined by a ‘finite state machine’. This is a set of inputs and instructions that tell the robot (or a part of the robot) what state, or pose, it should try to be in. For example, the robots current ‘state’ is its left leg is down (‘stance’ state) and its right leg is swinging (‘swing’ state). The robot detects that its swing leg is now ahead of the stance leg and has made contact with the ground. The state machine will now instruct the stance leg to change to the swing state, and vice-versa.

There are other states that determine what to do when the robot is off-balance (center-of-mass outside of center-of feet) and when the legs become crossed. The monopod has other sensors and states that determine if it in at a safe position to flip or if it should just hop (sometimes comes up wrong), and compensates for linear velocity (tries to slow forward momentum after each jump)

The weird glowing red walking machines are another thing entirely.

These are not programs, they are structures ‘built’ in a game that work pretty much like they look like they work. The only ‘animation’ that is done is that their ‘motor’, red glowing thing is rotated by magical hogwarts force. Everything else is a result of that motion. Axles, cranks, rods, levers, etc, do what you’d expect them to do (though with infinite strength and sometimes invisible and/or impossible connections)

The designs are more or less lifted. The hexapod and ‘toothpicks’ are the only ones I didn’t actually look at internal blueprints of something for, and those are clearly not original mechanisms either.

There are a few other tests and experiments that worked their way into this collection but I won’t go into detail about those. If you’re curious- ask. Better yet, look it up. Better still, figure it out yourself.

———FOR THE BLENDER/3D INCLINED———

—POSE CONTROLLERS—
Simple pose controlled ‘robots’ were created in Python after studying the work of Ari Shapiro (DANCE Dynamic Animation and Control Environment) and Philippe Beaudoin (SIMBICON/Cartwheel-3d). Primarily
-Generalized Biped Walking Control. Siggraph 2010
-Composable Controllers. – Siggraph 2001
-Controller Development. – Siggraph 2007

Not surprisingly, most of the brilliant methods put forth in these papers are absent in these programs. These are just ‘steps’ towards gaining a better understanding of dynamic character control concepts.

These robots are finite state machines with proportional derivative controller ‘muscles’. They also respond to feedback provided by center-of-mass, foot contact with ground, swing/stance foot positions relative to center of mass and torso heading. Uno knows the ‘uprightness’ vector between the feet and torso. Deuce can also detect if his feet are crossed, and will attempt to correct by standing on one leg and swinging the front leg outwards. They also have threshold ‘KO velocities’ beyond which they will go limp. This should be from acceleration but this works for now.

These robots run independent copies of their controllers so they can be copied for rudimentary crowd simulation. Their individual ‘heading goal’ is controlled by a ‘game property’ string in the robots ‘torso’ object. The goal can be another objects name, or use the prefix ‘a:’ then an angle (in degrees). Do not change the object names or numeration after copying, the program depends on these.

DEUCE – v.0001a of my ultimate goal of creating a real-time interactive break-dancing/kung-fu-master/soccer-playing/gymnast pose controller. What the heck- let’s throw in piano and chess-playing too. 15 DOF: Torso-XYZ, Hips-XYZ, Knees-X, Ankles-XY. Next version I will probably add 1-DOF toes and change Ankles from Pitch/Roll to Pitch/Yaw. I plan to utilize more of the SIMBICON methods and I am also looking at work on ‘genetic algorithms’ for evolving different controller params.

UNO – Created as a study for balance feedback. Eventually learned to hop, then to flip (sort of). Uno also alters his hip angle to compensate for linear velocity to try to stabilize his momentum after each jump. 9 DOF: Same configuration as Deuce

POGO – First study of proportional derivative controller. Just a collision sensor that triggers an upward force. Uses PD to angle the peg to compensate for linear velocity on the next bounce.

—RIGID BODY CONSTRAINT WALKING MACHINES—
Obviously a ‘dumber’ approach to walking animation, though strangely visually appealing. It started with a goofy thought, and ended with hours and hours of trial-and-error learning how bge handles various constraint configuations. There is a lot more that could be done with this technique. These are just a few setups.

THEO – based on Theo Jansen’s ‘Jansen Mechanism’
GOODWIN – based on W.F. Goodwin’s – ‘Automatic Toy’
NOGOODWIN – based on W.F. Goodwin’s – ‘Horse Toy’.
HEX – a semi-original design derived from observations of several 6-legged robot designs.
LE STECCHINI – based on those ubiquitous wind up walking toys we all know and love.

—OTHER—
MORTIMER/RINGO – Extremely simple self-firing ‘mortar’ objects. Created to test uno/deuce reaction to perturbation. Actually these should serve as a warning to anyone who thinks Algebra isn’t important. I got tired of trying to learn all the stuff I avoided learning in high school so I ended up finding the midpoint between the mortar and target, choosing an arbitrary height and firing on that vector. Amazingly, it’s a functional targeting system but it’s an insult to mathematics.

DRAWN-AND-QUARTERED RAGDOLL – Joint constraints had to be created programmatically (as opposed to using Blender UI constraints) so joints could be removed in the simulation. Keyboard events trigger joint ‘breaks’. I could probably figure out a way to calculate limb tension and trigger breaks based on a threshhold- but the whole thing was kind of a lark anyway.

TAD – Failed attempt to create a stable unpowered ‘passive walker’ based on Tad McGeers work. Has never taken more than three steps. Unpowered passive walkers are pretty sensitive to begin with and I’m not sure if a game engine simulation is enough to create one that works anything like in real-life. I’ll give it another go at some point.

H.M.S. STUPID – Another early test. A board that keeps level by using 4 ‘thrusters’. Each fires only if it is below the thruster on the opposite side.

—FILES—
All the models/rigs/programs in this .blend file are free to use for any non-machines-destroying-humanity related purposes as far as I’m concerned. Though keep in mind, though this work is original, some of the concepts and designs utilized and referenced are not. The Jansen Mechanism for instance; I have no idea what kind of intellectual property that is.

If you do use these for anything I’d appreciate a nod.

Word to your matrix.

teldredge

Mar 042011
 

I got a little done on the OSC implementation for Cartwheel-3D but I probably wont be able to go much further for awhile so I thought I’d post what I had.

In the cartwheel source there is a file
\PythonAppSNMApp.py that runs the main animation loop. I edited it to send OSC messages for each joint. To do this I used the pyOSC module and parked it in the same directory for convenience.
The whole thing runs super slow through the py-interpreter. I tried to compile it with py2exe but ran into some trouble. However, I found that I could cheat and use the provided binaries.
Take the modified SNMApp.pyc (and OSC.pyc) and copy them into the \binlibrary.zip file in the binaries. It just works.

Here’s my modified SNMApp.py – the pyOSC module can be found here

Blender uses Python 3 so I had to use a different OSC module here. I just parked it in the same directory as blender.exe – also for convenience.

and here is a .blend file with my OSC listener code in it. I got it to work with the bge and the animation system, but not cleanly.

If you have any success with this let me know.

Mar 022011
 

This is a simple script I wrote that generates meshes using DLA.

I made a little demo animation where I ran the mesh as a cloth sim.

print("-----GO-----")
import bpy
import random
from math import sin, cos, pi, sqrt
from mathutils import Vector

HardRadius = 2
RandWalkRange = .15
StickThresh = .15	   #distance threshold to accrete
MaxVerts = 100		#stop when this many verts have been added
seedLOC = Vector((0.0,0.0,0.0))

D3 = True

mname = "DLAtest"

def brownian():
	dx = random.gauss(0, RandWalkRange)
	dy = random.gauss(0, RandWalkRange)
	dz = random.gauss(0, RandWalkRange) 
	return Vector((dx, dy, dz))

####returns a new wanderer on sphere
def newWanderer():  
	randAngleA = random.uniform(0,2*pi)
	randAngleB = random.uniform(0,2*pi) 
	x = HardRadius * sin(randAngleA) * cos(randAngleB)
	y = HardRadius * sin(randAngleA) * sin(randAngleB)
	z = HardRadius * cos(randAngleA)
	return Vector((x, y, z))

def dist(A, B):
	xd = B.x-A.x
	yd = B.y-A.y
	zd = B.z-A.z
	return sqrt(xd*xd + yd*yd + zd*zd)

def wanderLoop(ob):
	wand = newWanderer()
	mmesh = ob.data
	while len(mmesh.vertices) < MaxVerts:
		brown = brownian()
		wand.x += brown.x
		wand.y += brown.y
		if D3: wand.z += brown.z
		else: wand.z = 0
		if  abs(wand.x) > HardRadius or 
			abs(wand.y) > HardRadius or 
			abs(wand.z) > HardRadius:
				wand = newWanderer()
				
		for vert in range(len(mmesh.vertices)):
			dpt = dist(wand, mmesh.vertices[vert].co)
			dZ = dist(wand, seedLOC)			
			if dpt < StickThresh:
				st = str(len(mmesh.vertices)+1) + " of " + str(MaxVerts) + " has been assimilated"
				print(st, dpt)
				addPoint(ob, wand, vert)
				wand = newWanderer()
				pass

####Object, point to add, index of vert to connect to (None is -1)
def addPoint(ob, pt, conni):
	mmesh = ob.data
	mmesh.vertices.add(1)
	vcounti = len(mmesh.vertices)-1
	mmesh.vertices[vcounti].co = [pt.x, pt.y, pt.z]
	if conni > -1:
		mmesh.edges.add(1)
		ecounti = len(mmesh.edges)-1
		mmesh.edges[ecounti].vertices = [conni, vcounti]


def newDLAMesh(mname):
	mmesh = bpy.data.meshes.new(mname)
	omesh = bpy.data.objects.new(mname, mmesh)
	bpy.context.scene.objects.link(omesh)
	addPoint(omesh, seedLOC, -1)	
	return omesh

ob = newDLAMesh("testDLA")
wanderLoop(ob)


Feb 272011
 

This is a very simple script I wrote to make BVH’s exported from DANCE (Dynamic Animation and Control Environment) able to be imported into Blender.

It just adds blank channels to joints if there are less than 3.

"""
DANCE BVH EXPORT THING
-Simple BVH file thing that adds blank channels of
    position to JOINTS and FRAMES to allow BVH Exports from 
    DANCE (Dynamic Animation and Control Environment) to be
    imported into Blender.
teldredge - www.funkboxing.com
"""

infile = "c:\workspace\bvhMINE\bvhdata\skelORIG2.bvh"
outfile = "c:\workspace\bvhMINE\bvhdata\skelTESTOUT.bvh"

def loadDANCEBVH(fin, fout):
    tab = chr(9)
    f = open(fin, 'r')
    lines = f.readlines()
    f.close()
    
    newlines = list(lines)
    newlines[16] = str(tab+tab+tab+tab+"CHANNELS 3 Xrotation Yrotation Zrotationn")
    newlines[30] = str(tab+tab+tab+tab+"CHANNELS 3 Yrotation Xrotation Zrotationn")
    newlines[34] = str(tab+tab+tab+tab+"CHANNELS 3 Yrotation Zrotation Xrotationn")
    newlines[49] = str(tab+tab+tab+tab+"CHANNELS 3 Yrotation Xrotation Zrotationn")    
    newlines[53] = str(tab+tab+tab+tab+"CHANNELS 3 Yrotation Zrotation Xrotationn")    
    newlines[69] = str(tab+tab+tab+tab+"CHANNELS 3 Xrotation Yrotation Zrotationn")    
    newlines[73] = str(tab+tab+tab+tab+"CHANNELS 3 Xrotation Zrotation Yrotationn")    
    newlines[88] = str(tab+tab+tab+tab+"CHANNELS 3 Xrotation Yrotation Zrotationn")
    newlines[92] = str(tab+tab+tab+tab+"CHANNELS 3 Xrotation Zrotation Yrotationn")    

    for li in range(104, len(lines)):
        vals = lines[li].split(" ")
        vals.insert(12, '0')
        vals.insert(12, '0')
        vals.insert(19, '0')
        vals.insert(21, '0')
        vals.insert(27, '0')
        vals.insert(30, '0')
        vals.insert(30, '0')
        vals.insert(35, '0')
        vals.insert(35, '0')
        vals.insert(39, '0')
        vals.insert(44, '0')
        vals.insert(44, '0')
        vals.insert(48, '0')
        newlines[li] = " ".join(vals) 
    
    fo = open(fout, 'w')
    fo.writelines(newlines)
        
        
        
        
loadDANCEBVH(infile, outfile)
Feb 222011
 

There’s a lot of Kinect-driven mocap development going on and that is fantastic. I also wanted to show off a couple of other animation tools out there. I’d like to see more physics and algorithmic animation tools available and I found a couple of very promising pieces of open-source software.

cartwheel-3d – Physics Based Character Animation Framework


DANCE – Dynamic Animation and Control Environment


Both can be used to create animation with a combination of physics and scripted control. The details of how they work are beyond me, but what they do is pretty amazing. Both are written in c++ but they both heavily use python for scripting and UI.

I am working with cartwheel-3d to try and use some of the features in Blender. I could try to work out a simple BVH exporter for cartwheel, or maybe an OSC server to send animation data to be recorded in Blender. Or simply try to use the functions direction in Blender. Still haven’t got that far. So far all I’ve done is compile cartwheel-3d. So I’ve included some notes on how I did it so if anyone else wants to work on this too they can get started easier.

COMPILE NOTES:
-Basically I followed the instructions on the ‘Get Started‘ page for cartwheel-3d with a few important exceptions.

COMPILING C++ : MS VStudioExpress2010
*no spaces in paths!
*lib folder in workspace ie. “C:workspacelib”
*glew32.dll and glut32.dll lib and lib in PATH variable

-After getting repository and opening- VS2010 converts and messes up target and extensions. To fix this.
>VIEW>PROJECT PAGE>CONFIGURATION PROPERTIES>GENERAL>
-Change Target Name from $(ProjectName) to _$(ProjectName)
-Change Target Extension to .pyd
-Repeat on all projects except ode and gls
-Repeat on debug and release

-Make sure BROWSE INFORMATION is OFF on all projects
>VIEW>PROJECT PAGE>CONFIGURATION PROPERTIES>C/C++>BROWSE INFORMATION
>ENABLE BROWSE INFORMATION – “NO”

DEBUGGING PYTHON: ECLIPSE (CLASSIC) + PYDEV
-Open eclipse (classic)
-Install pydev
-Set interpreter to py26
-Make a copy of ‘cartwheel-3d’ project folder- rename ‘simbicon’
-Install wxpython
-Install pyopengl

-Got some error about Core.py returning _mod before assignment. not sure what I did to siz it- sorry. I remember I opened core.py in the editor to look for the error, then ran again and it worked… weird.

So that’s it for now. Hope to do something constructive with this.

Feb 182011
 

Blender 2.56 Python Script for an animation system 2D gravity/accretion simulation. I was intended to be sort of an artistic tool but the physics are correct. I still have some UI work to do but it’s on the back burner for right now. Anyway it works if you feel like playing with it.

create meshes from motion paths

optional explosion if impact velocity over threshold

optional explosion if impact velocity over threshold

Features:
-2d gravity/accretion of ‘glob’ objects
-adjustable (G)ravitational constant (not in UI)
1 blender unit = a(AU)
1 mass unit = m(Solar Mass)
24frames = t(sidereal year)
G = sqrt((2*pi/t)*(sqrt(a**2/m)))

Instructions:
-open Blender2.56b
-copy or load the script into the text editor and run script
-ui will be in physics panel
-adjust parameters
-create globs and/or sun/holes and/or add/adjust vector tails
-animate globs

##################################################
#
#	Simple Orbital Mechanics - v.0.0.01a - 01.30.11
#	GPL, Public Licence, Open-Source, Free, etc...
#	2D Gravity/Accretion Simuatior
#	Thomas Eldredge
#	www.funkboxing.com
#	teldredge1979 at g mail dott comm
#
#		Thanks to Thanassis Tsiodras for his
#"Naive simulator of gravity, in less than 200 Python lines"
#		http://users.softlab.ntua.gr/~ttsiod/gravity.html
#		it got me on the right track
#
#	>Physics Property Menu >Simple Orbital Mechanics
#	-adjust parameters
#	>createGobs, can create multiple times
#		and move if desired
#	>createSuns, or name other objects with prefix 'sun'
#		and move if desired
#
##############################################################
#
#		TODO/WISHLIST:
#		- other objects
#				arbitrary objects
#			- impliment negative mass? repulsive matter?
#					other implimentations of gravity?
#					pushes at a distance,
#			- variable gravity?
#	  !	- make 2dplane arbitrary somehow
#					maybe give each object a xyz reconfig
#					so you can reassing so you can have
#					various systems interacting on diff
#					plane 1st would just
#					be to reassign axis on keyframe
#					-make sure Z loc is not locked in any way
#					so i can play with stacking them
#					vertically, setup a sim, then
#					start camera top and dolly side to reveal dist...
#		- materials (PlanetTex patch?)
#		- sim analysis
#			run thru varions configs
#			keep those with best 'stability'
#				to make visually interesting sims
#				ie. most mass stays inside bounds
#				or most non merged mass in bounds
#				and most movement in bounds?
#		- other movement formulas?
#				maybe impliment fractal flames somehow though
#				that's probably another project
#		- drive other params such as colors with loc/vel/acc
#		- option for dynamic rotation?
#		-	color picker
#				vectors or other settings as color picker wheel?
#				lock to a black and use as vector?
#		-smaller regular partical emitter at ever merge
#		-also, maybe make all objects, attractors?#
#
#		KNOWN ISSUES
#		- accumulation of data - .unlink,
#				need to figure something else out
#				!!! have flush but crashes blender...
#		- cannot rotate vectortails directly in
#				3d view, only in 'n' panel. scale works though...
#		- CONSTRAINTS effecting start frame position
#				running sim should not change start frame
#				THE frame 1 JUMP IS ACTUALLY BECAUSE OF CONSTRANT/SOMETHING
#  			 or updating inital velocity
#		- clear animation data -FIX-
#		- sun' still getting location keyframed
#		- metballs don't really appear...
####################################################

###???ACRETION MERGERGLOB OFFSET ISN'T RIGHT. STILL
#		ALLOWS OFFSET WAY BEYOND RADIUS

###!!!3daxis interchange - just put a cust value for XYZ -> ZYX mappting
#			then adjust just before set location

###???clear animation should also clear paths?

###!!!add gloobject count to panel

###???not making very good use of the index idea...
#		probably get to that when I refactor for arbitary xyz
#		global scn, GI
#		GIsuns, GIglobs = [], []
#		GI = [GIsuns, GIglobs]
#		GI(index) will help with all kinds of stuff..
# 		also work in more build in fxns like vector ang - xy converions for tails and whatnot

###!!!also make time of partlife relative to framerun

###!!!ADD PROPULSION???? somehow add thrust/nav controls?
#		to try out different nav techniques

###???metaballs, explode into other metaballs and add into GI
#		maybe newly spawned objects are only effected by parent?
#		new custom value = generation... (1st, 2nd, 3rd, etc)
#		OR split metaELEMTS!
#		eventuallty make so metaball impacts create
#		new objects and those are then calced in gravity...

###!!!also - next progream...
#		dedicated script to gravitize a selection of any
#		objects based on bounding box...
#		... try to make this one reletivistic
#		incding lorenz... try to help visualize
# 		reletivity on pc
#		time dilation???
#		reletivistic mass? all velocities based on % of maximum (essentially c)
#		then incorporate E=mc2	(somehow so harder to accelerate the faster
# 		it's going...
#		faster to use GI index on animate
#		especuially if use keep 'merged' in index...

###!!!LATER... metaball element trails...

###???option to explode at clip? <-------------- ###!!!on create mesh path verify there is anim data			 ###!!!lockxy rotation on tails ###???figure out subdivision... #		context thing in panel? ###!!! add event to starting velocity #		make vecto suns toggle withevent... so you can change bac to random ###!!! maybe instead of merging with constraints, just  #		seup so each loop if its merged it sets keyframe to its parents #		this could make axis-remapping easier in cases of merging #		 #################################################### ### NEXT NEXT NEXT NEXT NEXT NEXT NEXT NEXT NEXT  #################################################### ###!!!----------->figure out dist to sun relative to earth size...
###!!!----------->radial glob field create option for kieper/oort  35-55au
###!!!Voyager...speed 3.5 au/yr - exit trajectory from sol	

#then ui#
#then refactor for release 00000.2 whaever
#no core refactor, just loose ends, cleanup...
#then post!!!
#then sleep?

#need some kind of collision damping

##############################################################
#-maybe some issues with significant digits at some point
#		i think python floats are 28 digits?

#-something about mass from radius...

"""sun = 1M = 1.98892e30 kg
mercury
3.3010e23/1.98892e30
venus
4.1380e24/1.98892e30
mars
6.4273e23/1.98892e30
jupiter
1.89852e27/1.98892e30
saturn
5.6846e26/1.98892e30
uranus
8.6819e25/1.98892e30
neptune
1.02431e26/1.98892e30"""

#figure out what a frame = in this setup

### got a lot of stuff working
#	but... accretion offset is still wonky...
#	and... tmass fcurve is CURVED
#	need to set curves to CONSTANT for merged, tmass, etc...

	###!!! its adding constrainy to emptys?

######################### DEFAULTS/CONFIG/SETTINGS #########################
import bpy
import sys
import random
import time
from math import pi, sqrt, sin, cos, asin, acos

"""
#derived from one mercury orbit-per-sec
1.2252211349000193/framerate = AU/frame

1 simsecond = 1 mercuryyear = 0.240 earthyear
365.25*24*60*60 = 31557600 sec/yr

0.240*31557600 = 7573824.07573824.0
1 simsecond = 7573824.07573824 realseconds

--->1 simframe = 0.240*31557600/framerate realseconds

365*24*60*60 = 31536000 sec/yr (earth)
c = 63188.25au/yr
c = 63188.25/31536000 au/sec
c = 0.0020036862633181127 au/sec
G = 6.67300e-11 m**3/kg**-1/s**2
F=G(m1*m2/r**2)
		"""

	### I THINK I HAVE SCALE/SIZE RIGHT
	###NOW NEED SOMETHING FOR KG AND SECONDS

#figure out G for this rigwith m/s/kg from mybases
scn = bpy.context.scene
#-dimensions
FRAMERUN = 300			#ui length of simulation
#AU = 1.0					#ui 1BlenderUnit = 1AU

WIDTH = 150				#ui Blender units (should I add a scale option?)
HEIGHT = 150
CLIP = 1000			#ui stop/ignore objects too far out
STARTFRAME = 1			#ui start frame of simulation

#-globs/sunholes
PARTICLES = 5			#ui number globs to add

#FRAMERATE = scn.RenderSettings.fps
FRAMERATE = 24
TIMESCALE = 1.0		#based on framerate, au, and cno idea... 1y?
#--->1 simframe = 0.240*31557600/framerate realseconds

#EdMASS = 1.0			#ui??? Earth = 1unit: to be used for mass scaling and such
#SOLMASS = 5.9736e24

A = 1.0		#astronomical unit = 149.60e6 km = 149.60e9 m
M = 1.0		#solar mass = 1.98892e30 kg
T = 365.256363	#sidereal year
#D = 1.0		#mean solar day = 86400.0025 SI seconds @ epoch J1900
#gaussian constant = 0.01720209895 A**2/3 D*-1 M*-1/2
G = sqrt((2*pi/T)*(sqrt(A**2/M)))

#GM = GRAVITY * S		#(GM product)
#GRAVITY = G				#ui effect of gravity 

#SOLDIAM = 1.0   ###!!!should be sol diam in AU?
#SOLDIAM = 8.517493549379003e-05	#Earth's diameter constant
							#		1AU = 149598000 km
							#		EarthDiameter = 12742.0 km
							#		EarthDiameter = 12742/149598000 AU

							#
DSIZE = 250				#		Solar unit draw size of glob mesh
							#		no effect on physics

AXMAP = ['X','Y','Z']#default axismapping

SIZEVAR = 1.0		  	#ui variation in glob size in feild generation

#SsUNRADIUS = 109.0		#ui default sun radius (same as Sol)
SUNRANDLOC = True		#ui default setting for random sun loc
#SUNsDSIZE = 1.0			#ui draw size of suns

#-physics
DAMP = 1.0				#ui temp use for vel and acc (1.0 = no damp)
MAXVEL = 20.0			#ui max velocity
MAXINTERAD = CLIP		#ui max dist between 2 particles too calc force
DAMPVEL = 1.0			#na damp or add velocity (1.0 = 100%)
DAMPACC = 1.0			#na damp or add acceleration (less makes more spinny animations)
DAMPVELx = DAMPVEL	#na in case someone wants asymetrical damping later
DAMPVELy = DAMPVEL
DAMPACCx = DAMPACC
DAMPACCy = DAMPACC
RANDVEL = True     	#ui generate random starting velocity

RANDVELRANGE = .125	#ui random range for staring velocity
VECMULT = 10000		#ui multiplier for vector tails should be just display

LOOPATCLIP = False	#ui option to loop at clip dist

ACCRETE = True			#ui
EXPLODE = True			#ui
EXTHRESH = .25			#ui total impact velocity required to explode
METABALLS = False

#-internal
GPREFIX = 'glob'	 	#fn prefix for globjects
MOBNAME = 'mglob'	 	#fn name of globject meshdata
SPREFIX = 'sun'		#fn name of sun objects

###!!! replace the prefix system with a 'glob' variable

print('----------GEAUX!!!----------')

################### playground ######################

#-dist, size, mass,  based on Earth = 1
def createSolSystem():
	###!!!OortCloud		2000-50,000 au
	###!!!ProximaCentauri	268000	au
	###!!!add Europa, Phobos, Demos, lots o' moons to add
	###!!!saturns rings?
	NOZERO = 0.00000000001
	print('make a mass-distance-speed / scale accurate copy of sol')
	solG2V = makeMeshCube('sun-SOLG2V', scn.DSIZE)
	solG2V.name = 'sun-SOLG2V'
	solG2V.location = [0.0, 0.0, 0.0]
	solG2V.scale = [scn.SOLDIAM*109, scn.SOLDIAM*109, scn.SOLDIAM*109]
	solG2V.tmass = scn.M
	solG2V.modifiers.new('sunbevel', 'BEVEL')
	solG2V.show_name = True
	scn.objects.link(solG2V)

	mercury = createMetaBall('glob-MERCURY', scn.DSIZE)
	mercury.name = 'glob-MERCURY'
	mercury.location = [-0.39, NOZERO, 0.0]
	mercury.scale = [scn.SOLDIAM*0.3829, scn.SOLDIAM*0.3829, scn.SOLDIAM*0.3829]
	mercury.tmass = scn.M*0.055
	mercury.vel = [0.0, -0.24, 0.0]
	mercury.show_name = True
	mercury.merged = False
	scn.objects.link(mercury)

	venus = createMetaBall('glob-VENUS', scn.DSIZE)
	venus.name = 'glob-VENUS'
	venus.location = [NOZERO, 0.72, 0.0]
	venus.scale = [scn.SOLDIAM*0.949, scn.SOLDIAM*0.949, scn.SOLDIAM*0.949]
	venus.tmass = scn.M*0.82
	venus.vel = [-0.62, 0.0, 0.0]
	venus.show_name = True
	venus.merged = False
	scn.objects.link(venus)

	earth = createMetaBall('glob-EARTH', scn.DSIZE)
	earth.name = 'glob-EARTH'
	earth.location = [1.0, NOZERO, 0.0]
	earth.scale = [scn.SOLDIAM, scn.SOLDIAM, scn.SOLDIAM]
	earth.tmass = scn.M
	earth.vel = [0.0, 1.0, 0.0]###??? EARTH BASED VELOCITY? SURE WHY NOT
	earth.show_name = True
	earth.merged =False
	scn.objects.link(earth)		###??? wrong though, it's orbital period, not velocity...

###
	luna = createMetaBall('glob-LUNA', scn.DSIZE)
	luna.name = 'glob-LUNA'
	luna.location = [1.026, NOZERO, 0.0]
	luna.scale = [scn.SOLDIAM*0.273, scn.SOLDIAM*0.273, scn.SOLDIAM*0.273]
	luna.tmass = scn.M*0.012
	luna.vel = [0.0, 1.0, 0.0]
	luna.show_name = True
	luna.merged = False
	scn.objects.link(luna)

	mars = createMetaBall('glob-MARS', scn.DSIZE)
	mars.name = 'glob-MARS'
	mars.location = [NOZERO, -1.52, 0.0]
	mars.scale = [scn.SOLDIAM*0.532, scn.SOLDIAM*0.532, scn.SOLDIAM*0.532]
	mars.tmass = scn.M*0.11
	mars.vel = [1.88, 0.0, 0.0]
	mars.show_name = True
	mars.merged = False
	scn.objects.link(mars)		

	jupiter = createMetaBall('glob-JUPITER', scn.DSIZE)
	jupiter.name = 'glob-JUPITER'
	jupiter.location = [-5.204267, NOZERO, 0.0]
	jupiter.scale = [scn.SOLDIAM*11.209, scn.SOLDIAM*11.209, scn.SOLDIAM*11.209]
	jupiter.tmass = scn.M*317.8
	jupiter.vel = [0.0, -11.86, 0.0]
	jupiter.show_name = True
	jupiter.merged = False
	scn.objects.link(jupiter)

	saturn = createMetaBall('glob-SATURN', scn.DSIZE)
	saturn.name = 'glob-SATURN'
	saturn.location = [NOZERO, 9.582, 0.0]
	saturn.scale = [scn.SOLDIAM*9.449, scn.SOLDIAM*9.449, scn.SOLDIAM*9.449]
	saturn.tmass = scn.M*95.2
	saturn.vel = [-29.46, 0.0, 0.0]
	saturn.show_name = True
	saturn.merged = False
	scn.objects.link(saturn)		

	uranus = createMetaBall('glob-URANUS', scn.DSIZE)
	uranus.name = 'glob-URANUS'
	uranus.location = [19.22, NOZERO, 0.0]
	uranus.scale = [scn.SOLDIAM*4.007, scn.SOLDIAM*4.007, scn.SOLDIAM*4.007]
	uranus.tmass = scn.M*14.6
	uranus.vel = [0.0, 84.01, 0.0]
	uranus.show_name = True
	uranus.merged = False
	scn.objects.link(uranus)		

	neptune = createMetaBall('glob-NEPTUNE', scn.DSIZE)
	neptune.name = 'glob-NEPTUNE'
	neptune.location = [NOZERO, -30.06, 0.0]
	neptune.scale = [scn.SOLDIAM*3.883, scn.SOLDIAM*3.883, scn.SOLDIAM*3.883]
	neptune.tmass = scn.M*17.2
	neptune.vel = [164.8, 0.0, 0.0]
	neptune.show_name = True
	neptune.merged = False
	scn.objects.link(neptune)		

	pluto = createMetaBall('glob-PLUTO', scn.DSIZE)
	pluto.name = 'glob-PLUTO'
	pluto.location = [-29.74, NOZERO, 0.0]
	pluto.scale = [scn.SOLDIAM*0.19, scn.SOLDIAM*0.19, scn.SOLDIAM*0.19]
	pluto.tmass = scn.M*0.002
	pluto.vel = [0.0, -248.09, 0.0]
	pluto.merged =False
	pluto.show_name = True
	scn.objects.link(pluto)

	sedna = createMetaBall('glob-SEDNA', scn.DSIZE)
	sedna.name = 'glob-SEDNA'
	sedna.location = [NOZERO, 960.0, 0.0]
	sedna.scale = [scn.SOLDIAM*0.2197, scn.SOLDIAM*0.2197, scn.SOLDIAM*0.2197]
	sedna.tmass = scn.M*0.0004185081023168609
	sedna.vel = [-118090.0, 0.0, 0.0]
	sedna.show_name = True
	sedna.merged = False
	scn.objects.link(sedna)

	voyager = makeMeshCube('glob-VOYAGER', scn.DSIZE)
	voyager.name = 'glob-VOYAGER1'
	voyager.location = [-115.0, NOZERO, 0.0]
	voyager.scale = [scn.SOLDIAM*1.5696123057604772e-06, 
		scn.SOLDIAM*1.5696123057604772e-06, scn.SOLDIAM*1.5696123057604772e-06]
	voyager.tmass = scn.M*1.2086513994910943e-22
	voyager.vel = [0.0, -10.0, 0.0]
	scn.objects.link(voyager)
	voyager.show_name = True
	voyager.merged = False	

def makeGlobField():
	for i in range(scn.PARTICLES):
		gx = random.uniform(-scn.WIDTH/2, scn.WIDTH/2)
		gy = random.uniform(-scn.HEIGHT/2, scn.HEIGHT/2)
		gz = 0.0												  #2D sim for now

		#gs = random.uniform(scn.SOLDIAM-(scn.SOLDIAM*(scn.SIZEVAR/2)), 
		#	scn.SOLDIAM+(scn.SOLDIAM*(scn.SIZEVAR/2)))
		#sun diameter = 1391000 kilometers
		#au = 149598000 kilometers
		#sun diameter = 1391000/149598000 AU
		#sun diameter = 0.009298252650436503 AU
		gs = 0.009298252650436503  #-make variable? this makes default sun size our sun

		if METABALLS:
			newob = createMetaBall('metaglobule', scn.DSIZE)
			globname = GPREFIX + 'meta-' + str(i+1000)[1:4]

		else:
			newob = makeMeshCube(MOBNAME, scn.DSIZE)
			globname = GPREFIX + '-' + str(i+1000)[1:4]

		newob.name = globname
		newob.location = [gx, gy, gz]
		newob.scale = [gs, gs, gs]		  

			#add custom properties
		newob.merged = False
		newob.bhole = False
		#newob.tmass = scn.M *random.uniform(1, SIZEVAR)
		newob.tmass = scn.M # should be a random mass var
		newob.vel = [0.0, 0.0, 0.0]
		#newob.density = scn.STDENSITY
		if scn.RANDVEL:
			gvx = random.uniform(-RANDVELRANGE/2, RANDVELRANGE/2)
			gvy = random.uniform(-RANDVELRANGE/2, RANDVELRANGE/2)
			gvz = 0.0
			newob.vel = [gvx, gvy, gvz]

		scn.objects.link(newob)

def createMetaBall(mname, msize):
	msize = msize/2 #-radius from diameter
	#-have to go into edit mode to refesh metaobj for some reason
	mball = bpy.data.metaballs.new(mname)
	for m in range(1):
		mel = mball.elements.new()
		#mel.type = 'BALL'
		mel.co = [0.0, 0.0, 0.0]
		mel.size_x = msize
		mel.size_y = msize
		mel.size_z = msize
		#mel.stiffness = 1.0
		#mel.use_negative = False		#could be fun
	oball = bpy.data.objects.new('metaglobule', mball)
	oball.data = mball
	oball.glob = True
	return(oball)

def makeMeshCube(mname, msize):
	msize = msize/2 #-radius from diameter
	mmesh = bpy.data.meshes.new(mname)
	mmesh.vertices.add(8)
	mmesh.vertices[0].co = [-msize, -msize, -msize]
	mmesh.vertices[1].co = [-msize,  msize, -msize]
	mmesh.vertices[2].co = [ msize,  msize, -msize]
	mmesh.vertices[3].co = [ msize, -msize, -msize]
	mmesh.vertices[4].co = [-msize, -msize,  msize]
	mmesh.vertices[5].co = [-msize,  msize,  msize]
	mmesh.vertices[6].co = [ msize,  msize,  msize]
	mmesh.vertices[7].co = [ msize, -msize,  msize]

	mmesh.faces.add(6)
	mmesh.faces[0].vertices_raw = [0,1,2,3]
	mmesh.faces[1].vertices_raw = [0,4,5,1]
	mmesh.faces[2].vertices_raw = [2,1,5,6]
	mmesh.faces[3].vertices_raw = [3,2,6,7]
	mmesh.faces[4].vertices_raw = [0,3,7,4]
	mmesh.faces[5].vertices_raw = [5,4,7,6]
	mmesh.update(calc_edges=True)

	omesh = bpy.data.objects.new(mname, mmesh)
	omesh.data = mmesh
	omesh.glob = True
	return(omesh)

def makeSunHole():
	holemass = 10
	ll = []
	for m in scn.objects:
		if m.name[0:len(SPREFIX)] == SPREFIX:
			ll.append(m.name)
	gx = random.uniform(-scn.WIDTH/2, scn.WIDTH/2)
	gy = random.uniform(-scn.HEIGHT/2, scn.HEIGHT/2)
	gz = 0
	gs = 0.009298252650436503 #-sol diam in AU
	newsun = makeMeshCube('sunmesh', scn.DSIZE)
	newsun.name = SPREFIX + '-' +str(len(ll)+1000)[1:4]
	if scn.SUNRANDLOC:
		newsun.location = [gx, gy, gz]
	newsun.scale = [gs, gs, gs]
	newsun.modifiers.new('sunbevel', 'BEVEL')
	newsun.merged = False
	newsun.bhole = True
	newsun.tmass = scn.M * holemass
	scn.objects.link(newsun)

def clearAnimation():
	for o in scn.objects:
		for f in range(scn.FRAMERUN):
			scn.frame_current = f+1
			if o.name[0:len(GPREFIX)] == GPREFIX 
			or o.name[0:len(SPREFIX)] == SPREFIX:
				o.keyframe_delete('location')
				o.keyframe_delete('vel')
				o.keyframe_delete('tmass')
				o.keyframe_delete('merged')
		for c in o.constraints:
			c.keyframe_delete('influence')
			o.constraints.remove(c)

		# QQQ remove constraints influence frames too?

def createMeshPath():
	###!!! set this up to use ob.glob but not ob.bhole
	#-this is UGLY, need to find how to get fcurve data easier
	for ob in scn.objects:
		if ob.name[0:len(GPREFIX)] == GPREFIX: # 
		#and len(GPREFIX)+4 == len(ob.name):	#-hope avoid making path for path
			locX, locY, locZ, frame = [], [], [], []
			for f in range(scn.STARTFRAME, scn.FRAMERUN+1):
				oanim = ob.animation_data
				ocurves = oanim.action.fcurves
				addframe = 0
				stopvert = 5
				#-some hacky bullshit to make merged blobs paths right
				for ocurve in ocurves:
					#-only add to array if object hasn's been merged. !!! BROKE!
					if ocurve.data_path == 'merged' and ocurve.evaluate(f) == False:
						addframe = 1
						stopvert = 0
				for ocurve in ocurves:
					if addframe == 1:
						if ocurve.data_path == 'location' 
						and ocurve.array_index == 0:
							locX.append(ocurve.evaluate(f))
						if ocurve.data_path == 'location' 
						and ocurve.array_index == 1:
							locY.append(ocurve.evaluate(f))

			#-dont create motion paths under a % of FRAMERUM
			PATHTHRESH = 20 #%<-----make a ui variable (or setup file)
			if len(locX)/scn.FRAMERUN < PATHTHRESH/scn.FRAMERUN: 				print(len(locX)/scn.FRAMERUN, PATHTHRESH/scn.FRAMERUN) 				continue											 			mpath = bpy.data.meshes.new('meshpath') 			mpath.use_auto_smooth = True 			for p in range(len(locX)-1): 				if p == 0 or p == len(locX)-1: 					mpath.vertices.add(1) 					mpath.vertices[len(mpath.vertices)-1].co = [locX[p], locY[p], 0.0] 				if p > 0 and p < len(locX)-1-stopvert:
					x2, y2 = locX[p-1], locY[p-1]
					x1, y1 = locX[p+1], locY[p+1]
					xM, yM = locX[p], locY[p]
					dix, diy = x2 - x1, y2 - y1
					hyp = sqrt(dix**2 + diy**2)
					if hyp == 0: hyp = 0.000000000000000000001
					ang = acos(dix/hyp)
					perpL = ang + pi/4
					perpR = ang - pi/4
					###???maybe rotate the angle dynamically?

					#-multiplying 1/hyp here makes width
					#		inv proportional to velocity
					#		because dist bt verts grow with vel, so does hyp
					###???WORKS SORT OF, it's SOMETHING WITH THAT /0 hyp thing?
					#velT = (1/ hyp)*2 #<====-----make a ui variable?
					velT = .5
					nx1 = cos(perpL)*velT
					ny1 = sin(perpL)*velT
					nx2 = cos(perpR)*velT
					ny2 = sin(perpR)*velT
					nxL = xM + nx1
					nyL = yM + ny1
					nxR = xM + nx2
					nyR = yM + ny2
					ZTIMESCALE = 50	#percent of framrun
					ztime = p*(ZTIMESCALE/100)	#<====-----make a ui variable
										#				and/or proportonal to wid/height

					mpath.vertices.add(1)
					mpath.vertices[len(mpath.vertices)-1].co = [ 
							nxL, nyL, ztime]
					#-centerline, same as loc...
					#mpath.vertices.add(1)
					#mpath.vertices[len(mpath.vertices)-1].co = [ 
					#		xM, yM, ztime]
					mpath.vertices.add(1)
					mpath.vertices[len(mpath.vertices)-1].co = [ 
							nxR, nyR, ztime]

			mpath.update()
			#-DO NOT face last 4 verts or b will crash (last -1 one is for index
			for fc in range(1, len(mpath.vertices)-5, 4):
				mpath.faces.add(1)
				mpath.faces[len(mpath.faces)-1].vertices_raw = [ 
					fc, fc+2, fc+3, fc+1]
				mpath.faces.add(1)
				mpath.faces[len(mpath.faces)-1].vertices_raw = [ 
					fc+3, fc+2, fc+4, fc+5]
				mpath.update(calc_edges=True)									

			mpath.update(calc_edges=True)
			opath = bpy.data.objects.new('path'+ob.name, mpath)
			scn.objects.link(opath)

########################################################

def addExplosivo(obQ, vx, vy):
	if len(obQ.data.faces) < 512:
		#-have to subdivide cube first
		scn.objects.active = obQ
		cuts = len(obQ.data.faces) // 512
		###???don't lnow how to do this with ops like this
		#bpy.ops.mesh.subdivide(number_cuts = cuts, fractal = 0.25)

	###??? maybe use fluids? or randomize settings?
	obQ.modifiers.new(name = 'explosemit', type = 'PARTICLE_SYSTEM')
	expart = obQ.particle_systems[0]
	#expart.settings.count = 512 # 8x8x8 cube grid
	expart.settings.count = 6 # for now
	expart.settings.rotation_factor_random = 2.5
	expart.settings.frame_start = scn.frame_current
	expart.settings.frame_end = scn.frame_current + 1
	expart.settings.lifetime = 120
	expart.settings.lifetime_random = 1
	expart.settings.use_dynamic_rotation = True
	expart.settings.render_type = 'NONE'
	expart.settings.draw_method = 'NONE'
	expart.settings.rotation_factor_random = 1.0
	expart.settings.factor_random = 10.0

	expart.settings.object_align_factor	= [vx*vx*vx, vy*vy*vy, 0]
	#expart.settings.object_factor = 2
	expart.settings.effector_weights.gravity = 0.0
	expart.settings.brownian_factor = .25
	#expart.settings.particle_size = 1
	#expart.settings.use_multiply_size_mass = True
	#expart.settings.use_size_deflect = True
	#expart.settings.size_random = 2.5
	###??? child particles?
	exmod = obQ.modifiers.new(name = 'explosivo', type = 'EXPLODE')
	exmod.show_dead = False
	exmod.use_edge_split = True
	#!!!bpy.ops.ptcache.bake(bake=True) now
	#		or bpy.ops.ptcache.bake_all() at the end of loop?

#-set all globs vectors tangent to the nearest sun
#		hopefully will create more interesting sims
def setVecTangentToNearestHole():
	#-trying set tan to nearest ob if no sun...
	holeindex = []
	for hole in scn.objects:
		if hole.bhole:
			holeindex.append(hole)
			tempmult=35
	if len(holeindex)<1:
		for gobs in scn.objects:
			if gobs.glob:
				holeindex.append(gobs)
				tempmult=10

	print(holeindex)
	for gob in scn.objects:
		if gob.glob and gob.bhole == False:
			x1, y1 = gob.location[0], gob.location[1]
			nholeinit = True
			for hole in holeindex:
				if hole.name != gob.name:
				#if sol != gob:
					x2, y2 = hole.location[0], hole.location[1]
					dix, diy = x2 - x1, y2 - y1
					disq = dix*dix + diy*diy
					dri = sqrt(disq)
					# find nearest Sun
					if nholeinit:
						nholeinit = False
						nhole = hole
						nholedri = dri
						nholex = dix
						nholey = diy
						continue
					if	dri < nholedri: 						nhole = hole 						nholedri = dri 						nholex = dix 						nholey = diy 			vang, vmag = getVectorAngle(nholex, nholey, 0.0, False) 			#-random if vector is clockwz/anticlock to sun and if  			#		magnitude is proportional/invesely to dist to sun 			coinflip = random.uniform(0,1) 			coinflip = 1	#cheat for testing 			if coinflip >=.333:
				vangNEW = vang + (pi/2)
				vmagNEW = (1/nholedri)*tempmult
				#-should include mass of second object in calc...
			if coinflip < .333: 				vangNEW = vang - (pi/2) 				vmagNEW = vmag * nholedri / VECMULT 			for c in gob.children: 				if c.name[0:7] == 'vectail': 					c.rotation_euler = [0.0, 0.0, vangNEW] 					c.scale = [vmagNEW, vmagNEW, 0] 			hyp = vmagNEW  / scn.VECMULT 			cvx = cos(vangNEW)*hyp 			cvy = sin(vangNEW)*hyp 			gob.vel = [cvx, cvy, 0.0] def updateVecFromTail(globV): 	kids = globV.children 	for c in kids: 		# get vx and vy back from rot, scale 		vecang = c.rotation_euler[2] 		vecmag = c.scale[0] 		hyp = vecmag / scn.VECMULT 		cvx = cos(vecang)*hyp 		cvy = sin(vecang)*hyp 		globV.vel = [cvx, cvy, 0.0]					 				 def getVectorAngle(vx, vy, vz, hyp): 	if hyp == False: hyp = (sqrt((vx*vx)+(vy*vy))) 	# avoid div-0, hacky I know... 	if hyp == 0: hyp = .0000000000001 	if vx >= 0 and vy >= 0:					# -I
		vecang = asin(vy/hyp)
	if vx <= 0 and vy >= 0:					# -II
		vecang = pi-asin(vy/hyp)
	if vx <= 0 and vy <= 0:					# -III	 		vecang = -pi-asin(vy/hyp)		 	if vx >= 0 and vy <= 0:					# -IV 		vecang = asin(vy/hyp)	 	vecmag = (hyp*scn.VECMULT)		#this makes return value only good for tail DISPLAY 	return(vecang, vecmag)						 					 	 def flush(): 	killall = bpy.data.objects 	for deadmesh in killall: 		if deadmesh.glob  		or deadmesh.name[0:7] == "vectail": # 		#or deadmesh.name[0:len(SPREFIX)] == SPREFIX: 			if deadmesh.users == 0: 				bpy.data.objects.remove(deadmesh) 			else: print(deadmesh.name + ' is being stubborn...') 			scn.objects.link(deadmesh) 	for deadob in killall: 		if deadob.glob  		or deadob.name[0:7] == "vectail": # 	#	or deadob.name[0:len(SPREFIX)] == SPREFIX: 			if deadob.users == 0:			 				bpy.data.objects.remove(deadob) 			else: print(deadob.name + ' is being stubborn...') 			scn.objects.link(deadob)			 ######################### PHYSICS FXNS ######################### ######################### GLOB/SUN FXNS ######################### 		 def updateAllVecFromTail(): 	for obs in scn.objects: 		if obs.glob: 			updateVecFromTail(obs) def updateTailFromVec(globV): 	kids = globV.children 	if globV.merged == False and len(kids) > 0:
		vecang, vecmag = getVectorAngle( 
			globV.vel[0], globV.vel[1], globV.vel[2], False)
		for c in kids:
			c.rotation_euler = [0.0, 0.0, vecang]
			c.scale = [vecmag, vecmag, 0.0]
			c.keyframe_insert('rotation_euler', 2)
			c.keyframe_insert('scale', 0)
			c.keyframe_insert('scale', 1)

def makeVectorTail(globV):
	mmesh = bpy.data.meshes.new('vectail')
	mmesh.vertices.add(2)
	mmesh.vertices[0].co = [0.0, 0.0, 0.0]
	mmesh.vertices[1].co = [6.0, 0.0, 0.0]
	mmesh.edges.add(1)
	mmesh.edges[0].vertices = [0,1]
	mmesh.update()
	omesh = bpy.data.objects.new('vectail', mmesh)
	scn.objects.link(omesh)
	omesh.parent = globV
	omesh.data = mmesh
	omesh.hide_render = True

	vecang, vecmag = getVectorAngle( 
		globV.vel[0], globV.vel[1], globV.vel[2], False)

	omesh.rotation_euler = [0.0, 0.0, vecang]
	omesh.scale = [vecmag, vecmag, 0]

def addVectorTails():
	for obs in scn.objects:
		if obs.glob:
			if len(obs.children) > 0:
				for kid in obs.children:
					try: scn.objects.unlink(kid)
					except: print('i dont get unlinking...')
			makeVectorTail(obs)

def removeObject(xob):
	xmesh = xob.data
	scn.objects.unlink(xob)
	try: bpy.data.objects.remove(xmesh)
	except: print('---cannot remove ' + xmesh.name + '---')
	try: bpy.data.objects.remove(ob)
	except: print('---cannot remove ' + xob.name + '---')

def delSuns():
	for xall in scn.objects:
		if xall.bhole:
			removeObject(xall)
			#bpy.context.scene.objects.unlink(xall)
	selectCheck()		

def delGlobs():
	for xall in scn.objects:
		if xall.glob 
		or xall.name[0:7] == 'vectail' 
		or xall.name[0:8] == 'pathglob':
			removeObject(xall)
	selectCheck()					

def selectCheck():
#-make sure there's an object active so panel doesn't disappear
	if scn.objects.active: return
	else:
		if len(scn.objects) > 1:
			scn.objects.active = scn.objects[0]
		else:
			newempty = bpy.ops.object.add()
			newempty.hide = False
			newempty.hide_select = False
			newempty.hide_render = False
			scn.objects.link(newempty)
			scn.objects.active=newempty

"""
def makeGlobIndex():
	GI = []
	obs = scn.objects
	for ob in obs:
		if ob.glob:
		#or ob.name[0:len(SPREFIX)] == SPREFIX:
			GI.append(ob.name)
	return GI
"""

######################### RUNLOOPS #########################

#def runit(GI):
def runit():
	for globA in scn.objects:
		if globA.glob:
			#print(globA.name, ' is a glob')
	#for i in range(0, len(GI)):
		#globA = obs[GI[i]]
		#globA = glob
			if globA.bhole: continue #-don't calc force on blackholes (immovable)
			if globA.merged: continue #-don't calc force on merged

			Ax, Ay = globA.location[0], globA.location[1]

				### ??? ### add option for round cliploop?
				# check for CLIP (border)
			if sqrt(Ax*Ax+Ay*Ay) > scn.CLIP:
				if scn.LOOPATCLIP:
					#-if loopatclip set, loop glob around
					if abs(Ax) > scn.CLIP:
						globA.location[0] = -Ax
					if abs(Ay) > scn.CLIP:
						globA.location[1] = -Ay
				else:
					#-otherwise stop and mark as merged so it'll be ignored
					globA.merged = True
					continue	#-skip to next globA loop if globA outside CLIP

			# temp loc, vel and acc vars for loop
			tloc = globA.location
			tvel = globA.vel
			tacc = [0.0, 0.0, 0.0]

			#for j in range(0, len(GI)):
			for globB in scn.objects:
				if globB.glob:
					if globB == globA or globB.merged: continue
					#globB = obs[GI[j]]
					#globB = glob2
					Bx, By = globB.location[0], globB.location[1]
					dx, dy = Bx - Ax, By - Ay
					dsq = dx*dx + dy*dy
					#if dsq==0:dsq=0.0001 #-I am such a hack...
					dr = sqrt(dsq)
					#ignore own force
					if dr > scn.MAXINTERAD: continue #ignore if beyond interaction radius

					# add acceleration/force from this glob
					#print(globA.name, ' has a problem with ', globB.name)
					force = G * globA.tmass * globB.tmass / dsq
					tacc[0] += force*dx/dr
					tacc[1] += force*dy/dr
					tloc[0] += tvel[0] * scn.DAMP #DAMPVELx
					tloc[1] += tvel[1] * scn.DAMP #DAMPVELy
					tvel[0] += tacc[0] * scn.DAMP #DAMPACCx
					tvel[1] += tacc[1] * scn.DAMP #DAMPACCy
					###!!!maybe add a mock relativity here by
					#		damping acceleration as approaches v approaches c

					#-check if over max velocity, if so set to max
					if tvel[0] > scn.MAXVEL: tvel[0] = scn.MAXVEL
					if tvel[0] < -scn.MAXVEL: tvel[0] = -scn.MAXVEL	 					if tvel[1] > scn.MAXVEL: tvel[1] = scn.MAXVEL
					if tvel[1] < -scn.MAXVEL: tvel[1] = -scn.MAXVEL
					tacc = [0.0, 0.0, 0.0]

					#-merge if close enough (relative to draw size - a cheat - I know)
					if dr <= (globA.scale[0]+globB.scale[0])*scn.DSIZE: 						if scn.ACCRETE: 							#-they're close, merge smaller to larger 							if globA.tmass >= globB.tmass:

								globMerge(globB, globA)
							else:

								globMerge(globA, globB)
					###!!!also add collision damping here based on density
						if scn.EXPLODE:
							vx1, vy1 = globA.vel[0], globA.vel[1]
							vx2, vy2 = globB.vel[0], globB.vel[1]
							vdx, vdy = vx2 = vx1, vy2 - vy1
							vdsq =vdx*vdx + vdy*vdy
							vdr = sqrt(vdsq)
							if abs(vdr) > scn.EXTHRESH:
								if globA.tmass >= globB.tmass:
									if globB.type != 'META':
										addExplosivo(globB, vdx, vdy)
								else:
									if globA.type != 'META':
										addExplosivo(globA, vdx, vdy)

			#update loc, vel, and insert kframes
				globA.location[0] = tloc[0] #/ scn.SPACEsSCALE
				globA.location[1] = tloc[1] #/ scn.SPACESsCALE
				globA.vel = tvel
			#if globA.name[0:len(SPREFIX)] != SPREFIX:
			# ignore vectails of merged globs
			if globA.merged == False:
				globA.keyframe_insert('tmass')
				globA.keyframe_insert('merged')
			if globA.bhole == False:
				globA.keyframe_insert('location', 0)
				globA.keyframe_insert('location', 1)
				globA.keyframe_insert('vel', 0)
				globA.keyframe_insert('vel', 1)
			#-check if the glob has vectails, update if they exist
				for c in globA.children:
					if c.name[0:7] == 'vectail':
						updateTailFromVec(globA)
						continue	# vectail updated, so leave loop

def loopit():
	t1 = time.ctime(time.time())
	#GI = makeGlobIndex()
	scn.frame_current = scn.STARTFRAME
	#-adjust for user changes to vectails
	updateAllVecFromTail()
	setCurvesToConstant()
	#-start on frame 2, 1st should NOT be recalculated
	for f in range(scn.STARTFRAME+1, scn.STARTFRAME+scn.FRAMERUN):
		scn.frame_current = f
		print(scn.frame_current)
		#runit(GI)
		runit()
	t2 = time.ctime(time.time())
		# calc processing time
	s1, m1 = int(t1[17:19]), int(t1[14:16])
	s2, m2 = int(t2[17:19]), int(t2[14:16])
	print(str(t1)[11:20], str(t2)[11:20])
	print('simulation time: ' + str(abs(m2-m1)) + ':' + str(abs(s2-s1)))

###!!!ABSORB SOME IMPACT IN COLLISION!!!
###	THESEARE NOT INDESTRUCTABLE...
###	ALSO MAYBE ADD POTENTIAL FOR 'GLANCING BLOW'S and 'BOUNCES'
###	because for now w acrete and axplode off all collisions will pass thru
def globMerge(globS, globL):
	print(globL.name, globL.tmass, '+', globL.tmass, globS.name)
	#if globS.bhole == 1: return
	globS.merged = True

	#-make merged globs vectail disappear
	for cs in globS.children:
		if cs.name[0:7] == 'vectail':
			cs.scale = [0.0, 0.0, 0.0]
			cs.keyframe_insert('scale', 0)
			cs.keyframe_insert('scale', 1)

	#-figure out vector between L and S to offset
	#		constraint of S on L as if stuck where hit
	#		also to direct particles/explode modifier
	SLx = globS.location[0] - globL.location[0]
	SLy = globS.location[1] - globL.location[1]
	if SLx >= globL.scale[0]*scn.DSIZE: SLx = globL.scale[0]*scn.DSIZE
	if SLy >= globL.scale[0]*scn.DSIZE: SLy = globL.scale[0]*scn.DSIZE
	if SLx < -globL.scale[0]*scn.DSIZE: SLx = -globL.scale[0]*scn.DSIZE
	if SLy < -globL.scale[0]*scn.DSIZE: SLy = -globL.scale[0]*scn.DSIZE
	#SLx = 1			###!!! sun merging is still weird
	#SLy = 1			

	#-contrain smaller glob to larger
	gcon = globS.constraints.new('COPY_LOCATION')
	gcon.use_offset = True
	gcon.target = globL

	#-add constraint influence keyframe of
	#		pre-merge state to previous frame
	ftemp = scn.frame_current
	scn.frame_current = ftemp - 1
	gcon.influence = 0
	gcon.keyframe_insert('influence')

	#-back to this frame to insert merged state keyframe
	scn.frame_current = ftemp
	gcon.influence = 1
	gcon.keyframe_insert('influence')	

	globS.location = [SLx, SLy, 0.0]						  

	#-add keyframes for post-merge
	#		location and constraint influence
	globS.keyframe_insert('location', 0)
	globS.keyframe_insert('location', 1)				  	

	# add velocity ofsmall glob to larger
	### ??? ### CHECK THIS, OBJECT VELS SEEM TO GOOF ON IMPACK
	newvx = (globL.vel[0] * globL.tmass + 
				globS.vel[0] * globS.tmass) / 
				(globL.tmass + globS.tmass)
	newvy = (globL.vel[1] * globL.tmass + 
				globS.vel[1] * globS.tmass) / 
				(globL.tmass + globS.tmass)

	#-update larger globs total mass and keyframe

	globL.tmass += globS.tmass
	globL.keyframe_insert('tmass')
	print('mass updated : ', globL.name, globL.tmass)
	globL.vel[0] = newvx
	globL.vel[1] = newvy

def setCurvesToConstant():
	for ob in scn.objects:
		if ob.glob:
			oanim = ob.animation_data
			if oanim:
				ocurves = oanim.action.fcurves
				for ocurve in ocurves:
					ocurve.extrapolation = 'CONSTANT'

###analysis fxns
def calcTotalMassRemaing():
	#also how many last past 500 frames or so...
	pass

######################### USER INTERFACE #########################

def initSceneProps(scn):
	bpy.types.Scene.FRAMERUN = bpy.props.IntProperty(
		name = "framerun")
	bpy.types.Scene.PARTICLES = bpy.props.IntProperty(
		name = "particles")		  

	bpy.types.Scene.M = bpy.props.FloatProperty(
		name = "earthmass")

	bpy.types.Scene.T = bpy.props.FloatProperty(
			name = "siderealyear")			

	bpy.types.Scene.WIDTH = bpy.props.IntProperty(
		name = "width")
	bpy.types.Scene.HEIGHT = bpy.props.IntProperty(
		name = "height")
	bpy.types.Scene.CLIP = bpy.props.IntProperty(
		name = "clip")

	bpy.types.Scene.DSIZE = bpy.props.FloatProperty(
		name = "drawsize")
	#bpy.types.Scene.SOLDIAM = bpy.props.FloatProperty(
	#	name = "earthdiameter")		  

	bpy.types.Scene.A = bpy.props.FloatProperty(
		name = "spacespace")

	bpy.types.Scene.SIZEVAR = bpy.props.FloatProperty(
		name = "sizevar")
	bpy.types.Scene.DAMP = bpy.props.FloatProperty(
		name = "damp")

	bpy.types.Scene.LOOPATCLIP = bpy.props.BoolProperty(
		name = "loopatclip")
	bpy.types.Scene.MAXVEL = bpy.props.FloatProperty(
		name = "maxiumum velocity")
	bpy.types.Scene.SOLDIAM = bpy.props.FloatProperty(
		name = "sun radius")
	bpy.types.Scene.SUNRANDLOC = bpy.props.BoolProperty(
		name = "random sun location")

	bpy.types.Scene.RANDVEL = bpy.props.BoolProperty(
        name = "random starting velocity")

	bpy.types.Scene.STARTFRAME = bpy.props.IntProperty(
		name = "startframe")		

	bpy.types.Scene.VECMULT = bpy.props.FloatProperty(
		name = "vectordisplaymult")

	bpy.types.Scene.MAXINTERAD = bpy.props.FloatProperty(
		name = "maxinteractionradius")				

	bpy.types.Scene.RANDVELRANGE = bpy.props.FloatProperty(
		name = "rendvelrange")	

	bpy.types.Scene.EXPLODE = bpy.props.BoolProperty(
        name = "explode on impact")

	bpy.types.Scene.ACCRETE = bpy.props.BoolProperty(
        name = "accrete or not")

	bpy.types.Scene.METABALLS = bpy.props.BoolProperty(
        name = "accrete or not")

	bpy.types.Scene.EXTHRESH = bpy.props.FloatProperty(
			name = "explodeimpacethreshold")	

	scn.LOOPATCLIP = LOOPATCLIP
	scn.FRAMERUN = FRAMERUN
	scn.PARTICLES = PARTICLES
	scn.M = M
	scn.T = T
	scn.WIDTH = WIDTH
	scn.HEIGHT = HEIGHT
	scn.CLIP = CLIP
	scn.DSIZE = DSIZE
	#scn.SOLDIAM = SOLDIAM
	scn.SIZEVAR = SIZEVAR
	scn.DAMP = DAMP
	scn.MAXVEL = MAXVEL
	#scn.SOLDIAM = SOLDIAM
	scn.SUNRANDLOC = SUNRANDLOC

	scn.RANDVEL = RANDVEL

	scn.STARTFRAME = STARTFRAME
	scn.RANDVELRANGE = RANDVELRANGE

	scn.MAXINTERAD = MAXINTERAD
	scn.EXTHRESH = EXTHRESH
	scn.A = A

	scn.VECMULT = VECMULT
	scn.ACCRETE = ACCRETE
	scn.EXPLODE = EXPLODE
	scn.METABALLS = METABALLS

initSceneProps(scn)
bpy.types.Object.glob = bpy.props.BoolProperty(
	name = 'glob',
	default = False)
bpy.types.Object.tmass = bpy.props.FloatProperty(
	name = 'total mass of object including others merged to it.',
	default = 0.0)
bpy.types.Object.vel = bpy.props.FloatVectorProperty(
	name = 'velocity',
	default = (0.0, 0.0, 0.0))
bpy.types.Object.merged = bpy.props.BoolProperty(
	name = 'merged',
	default = False)
bpy.types.Object.bhole = bpy.props.BoolProperty(
	name = 'blackhole',
	default = False)	

class OBJECT_PT_SOMPanel(bpy.types.Panel):
	bl_space_type = "PROPERTIES"
	bl_region_type = "WINDOW"
	bl_context = "physics"
	bl_label = "Simple Orbital Mechanics"

	def draw_header(self, context):
		layout = self.layout

	def draw(self, context):
		layout = self.layout
		scene = scn
		row1 = layout.row(align=True)
		row2 = layout.row(align=True)

		split1 = row1.split(percentage=0.5)
		colX = split1.column()
		colY = split1.column()
		split2 = row2.split(percentage=0.33)
		colL = split2.column()
		colM = split2.column()
		colR = split2.column()

		colX.label(text="2D Gravity/Accretion Simulator", icon='WORLD_DATA')
		ltext1 = 	'vx: ' + str(scn.objects.active.vel[0])[0:9] + 
					' | ' + 
					'vy: ' + str(scn.objects.active.vel[1])[0:9]
		colY.label(text=ltext1)

		colX.label(text=scn.objects.active.name)
		colY.label(text='scale: ' + str(scn.objects.active.scale[0]))
		ltext2 =  'tmass: ' + str(scn.objects.active.tmass)[0:7]
		ltext3 = 'merged: ' + str(scn.objects.active.merged)
		colX.label(text=ltext3)
		colY.label(text=ltext2)
		colX.label(text='bhole: ' + str(scn.objects.active.bhole))
		colY.label(text='')

		colX.operator("create_globfield")
		colY.operator("delete_globfield")
		colX.label(text = '')
		colY.operator("clear_animation")
		colX.operator("addVecTails")
		colY.operator("setVecTanToNearestSun")		

		colX.prop(scene, 'RANDVEL', icon='BLENDER', toggle=True)
		colY.prop(scene, 'LOOPATCLIP', icon='BLENDER', toggle=True)
		colX.prop(scene, 'RANDVELRANGE', icon='BLENDER')#, toggle=True)
		colY.prop(scene, 'VECMULT', icon='BLENDER')#, toggle=True)		

		colL.operator("animate_globfield")
		colM.prop(scene, 'FRAMERUN')

		colR.prop(scene, 'STARTFRAME')

		colL.prop(scene, 'WIDTH', icon='BLENDER')#, toggle=True)
		colM.prop(scene, 'HEIGHT', icon='BLENDER')#, toggle=True)
		colR.prop(scene, 'CLIP', icon='BLENDER')#, toggle=True)

		colL.separator()
		colM.separator()
		colR.separator()				

		colL.prop(scene, 'PARTICLES', icon='BLENDER')#, toggle=True)
		colM.prop(scene, 'MAXINTERAD', icon='BLENDER')#, toggle=True)
		colR.prop(scene, 'SIZEVAR', icon='BLENDER')#, toggle=True)

		colL.label(text='')
		colM.prop(scene, 'DSIZE', icon='BLENDER')#, toggle=True)
		#colR.prop(scene, 'SOLDIAM', icon='BLENDER')#, toggle=True)		

		colL.separator()
		colM.separator()
		colR.separator()

		colL.prop(scene, 'DAMP', icon='BLENDER')#, toggle=True, expand=False)
		#colM.prop(scene, 'GRAVITY', icon='BLENDER')#, toggle=True)
		colR.prop(scene, 'MAXVEL', icon='BLENDER')#, toggle=True)					 						

		sunBox = colL.box()
		sunBox.label(text = "Sun/Blackhole")
		sunBox.operator("create_sunhole")
		sunBox.operator("delete_sunholes")
		sunBox.prop(scene, 'SOLDIAM', icon='BLENDER')#, toggle=True)
		sunBox.prop(scene, 'SUNRANDLOC', icon='BLENDER', toggle=True)
		#sunBox.prop(scene, 'SUNsDSIZE', icon='BLENDER')#, toggle=True)		

		#colM.template_curve_mapping(scene, 'mycurve', type='VECTOR')
		#colR.template_reports_banner()
		# these would be nice, also remember to useevent = true on props
		colM.prop(scene, 'EXTHRESH', icon='BLENDER', toggle=True)
		colR.prop(scene, 'A', icon='BLENDER')#, toggle=True)

		colM.operator('createMeshPath')
		colM.operator('testit3')
		#colR.label(text='')			

		colM.label(text='')
		colR.label(text='')	

		colM.operator('testit')
		colR.operator('testit2')
		colM.prop(scene, 'EXPLODE', icon='BLENDER', toggle=True)
		colR.prop(scene, 'ACCRETE', icon='BLENDER', toggle=True)				

		#colM.prop(scene, 'TESTCOLLECT', icon='BLENDER', toggle=True)
		#colR.menu('amenu', 'irmanu')

		#colM.prop_enum(scene, 'TESTMENU', 't')
		#colM.prop_enum(scene, 'TESTMENU', 't')		

class SCENE_OT_testit3(bpy.types.Operator):
	bl_idname = "testit3"
	bl_label = "testit3"
	bl_options = {'REGISTER'}
	bl_description = "testit3"

	def invoke(self, context, event):
		print('---trying blah---')
		#tob = scn.objects.active
		#print(tob.name)
		#createMetaBall()
		setCurvesToConstant()
		return("FINISHED")

class SCENE_OT_testit(bpy.types.Operator):
	bl_idname = "testit"
	bl_label = "testit"
	bl_options = {'REGISTER'}
	bl_description = "testit"

	def invoke(self, context, event):
		print('---trying to create a metaglob---')
		createSolSystem()
		return("FINISHED")

class SCENE_OT_testit2(bpy.types.Operator):
	bl_idname = "testit2"
	bl_label = "testit2FLUSH"
	bl_options = {'REGISTER'}
	bl_description = "testit2"

	def invoke(self, context, event):
		print('---testit2---')
		flush()
		#tob = scn.objects.active
		#print(tob.name)
		#createMetaBall()
		return("FINISHED")	

class SCENE_OT_createMeshPath(bpy.types.Operator):
	bl_idname = "createMeshPath"
	bl_label = "createMeshPath"
	bl_options = {'REGISTER'}
	bl_description = "create mesh ribbon from glob paths"

	def invoke(self, context, event):
		print('---watch it grow---')
		createMeshPath()
		return("FINISHED") 

class SCENE_OT_setVecTanToNearestSun(bpy.types.Operator):
	bl_idname = "setVecTanToNearestSun"
	bl_label = "setVectorTangentToSuns"
	bl_options = {'REGISTER'}
	bl_description = "set vectors tangent to their nearest sun"

	def invoke(self, context, event):
		print('---turn away from the light---')
		setVecTangentToNearestHole()
		return("FINISHED")			

class SCENE_OT_addVecTails(bpy.types.Operator):
	bl_idname = "addVecTails"
	bl_label = "addVectorTails"
	bl_options = {'REGISTER'}
	bl_description = "add/update vector tails to globs"

	def invoke(self, context, event):
		print('---youve got tails!---')
		addVectorTails()
		return("FINISHED")			

class SCENE_OT_delete_sunholes(bpy.types.Operator):
	bl_idname = "delete_sunholes"
	bl_label = "deleteSunHoles"
	bl_options = {'REGISTER'}
	bl_description = "remove all suns"

	def invoke(self, context, event):
		print('---goodbye norma jean---')
		delSuns()
		return("FINISHED")		

class SCENE_OT_create_globfield(bpy.types.Operator):
	bl_idname = "clear_animation"
	bl_label = "clearAnimation"
	bl_options = {'REGISTER'}
	bl_description = "clear all animation data"

	def invoke(self, context, event):
		print('---be gone animation data!---')
		clearAnimation()
		return("FINISHED")	

class SCENE_OT_create_sunhole(bpy.types.Operator):
	bl_idname = "create_sunhole"
	bl_label = "createSunHole"
	bl_options = {'REGISTER'}
	bl_description = "create a Sun/Blackhole type stationary gravity well"

	def invoke(self, context, event):
		print('---mmm... matter---')
		makeSunHole()
		return("FINISHED")

class SCENE_OT_create_globfield(bpy.types.Operator):
	bl_idname = "create_globfield"
	bl_label = "createGlobs"
	bl_options = {'REGISTER'}
	bl_description = "create a field of globs based on parameters"

	def invoke(self, context, event):
		print('---let there be globs---')
		makeGlobField()
		return("FINISHED")

class SCENE_OT_delete_globfield(bpy.types.Operator):
	bl_idname = "delete_globfield"
	bl_label = "deleteGlobs"
	bl_description = "delete all globs"
	bl_options = {'REGISTER', 'UNDO'}

	def invoke(self, context, event):
		print('---destroy all globs---')
		delGlobs()
		return{'FINISHED'}

class SCENE_OT_animate_globfield(bpy.types.Operator):
	bl_idname = "animate_globfield"
	bl_label = "animateGlobs"
	bl_description = "gravitometicically animateify globjects"
	bl_options = {'REGISTER', 'UNDO'}

	def invoke(self, context, event):
		print('---i am busy calculating glob animations---')
		loopit()
		bpy.context.scene.frame_current = STARTFRAME
		return{'FINISHED'}	 

def register():
	bpy.types.Scene.some_strvar = bpy.props.StringProperty(
		name='some_strvar',
		description='a string variable i might need')

def unregister():
	del bpy.types.Scene.some_strvar

if __name__ == "__main__":
    register()

##########    DROPDOWN BOX / MENU
"""
class RENDER_PT_matdropdown(bpy.types.Panel):
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "render"
    bl_label = "Material Dropdown lists"

    def draw(self, context):
        rd = context.scene
        layout = self.layout
        layout.prop(rd, "mat_list_old", text="Replace")
        layout.prop(rd, "mat_list_new", text="by")
        layout.separator()
        row = layout.row()
        row.operator("custom.update_materiallists")
        row.operator("custom.print_materials")

def replace():
    MATERIALS = []
    for i in range(len(bpy.data.materials)):
        MATERIALS.append((str(i), bpy.data.materials[i].name, str(i)))

    bpy.types.Scene.EnumProperty( attr="mat_list_old",
        name="Replace",
        description="Choose a material to be replaced",
        items = MATERIALS, default = '0')
    bpy.types.Scene.EnumProperty( attr="mat_list_new",
        name="Replace by",
        description="Choose a replacement material",
        items = MATERIALS, default = '1')    

class CUSTOM_OT_update_materiallists(bpy.types.Operator):
    bl_idname = "CUSTOM_OT_update_materiallists"
    bl_label = "Update"
    bl_description = "Update the dropdown boxes. Necessary if you added or deleted materials in the scene."

    def invoke(self, context, event):
        replace()
        return{'FINISHED'}

class CUSTOM_OT_print_materials(bpy.types.Operator):
    bl_idname = "CUSTOM_OT_print_materials"
    bl_label = "Print"
    bl_description = "Print the selections of the dropdown boxes."
    def invoke(self, context, event):
        old = bpy.data.materials[int(bpy.context.scene.mat_list_old)]
        new = bpy.data.materials[int(bpy.context.scene.mat_list_new)]
        print("Replace",old.name,"by",new.name)
        return{'FINISHED'}

if __name__ == '__main__':
    bpy.types.register(CUSTOM_OT_update_materiallists)
    bpy.types.register(CUSTOM_OT_print_materials)
    replace()
    bpy.types.register(RENDER_PT_matdropdown)
"""