moving to contrib/py/scripts/addons/object_laplace_lightning.py
[blender-addons-contrib.git] / object_laplace_lightning.py
1 # ***** BEGIN GPL LICENSE BLOCK *****
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 #
17 # ***** END GPL LICENCE BLOCK *****
18 bl_info = {
19     "name": "Laplacian Lightning",
20     "author": "teldredge",
21     "version": (0, 2, 6),
22     "blender": (2, 6, 1),
23     "api": 43252,
24     "location": "View3D > ToolShelf > Laplacian Lightning",
25     "description": "Lightning mesh generator using laplacian growth algorithm",
26     "warning": "Beta/Buggy.",
27     "wiki_url": "http://www.funkboxing.com/wordpress/?p=301",
28     "tracker_url": "https://projects.blender.org/tracker/index.php?"
29                    "func=detail&aid=27189",
30     "category": "Object"}
31         
32 ######################################################################
33 ######################################################################
34 ##################### BLENDER LAPLACIAN LIGHTNING ####################
35 ############################ teldredge ###############################
36 ######################## www.funkboxing.com ##########################
37 ######################################################################
38 ######################## using algorithm from ########################
39 ######################################################################
40 ############## FAST SIMULATION OF LAPLACIAN GROWTH (FSLG) ############
41 #################### http://gamma.cs.unc.edu/FRAC/ ###################
42 ######################################################################
43 ###################### and a few ideas ideas from ####################
44 ######################################################################
45 ##### FAST ANIMATION OF LIGHTNING USING AN ADAPTIVE MESH (FALUAM) ####
46 ################ http://gamma.cs.unc.edu/FAST_LIGHTNING/ #############
47 ######################################################################
48 ######################################################################
49 """           -----RELEASE LOG/NOTES/PONTIFICATIONS-----
50 v0.1.0 - 04.11.11
51     basic generate functions and UI
52     object creation report (Custom Properties: FSLG_REPORT)
53 v0.2.0 - 04.15.11
54     started spelling laplacian right.
55     add curve function (not in UI) ...twisting problem
56     classify stroke by MAIN path, h-ORDER paths, TIP paths
57     jitter cells for mesh creation
58     add materials if present
59 v0.2.1 - 04.16.11
60     mesh classification speedup
61 v0.2.2 - 04.21.11
62     fxns to write/read array to file 
63     restrict growth to insulator cells (object bounding box)
64     origin/ground defineable by object
65     gridunit more like 'resolution'
66 v0.2.3 - 04.24.11
67     cloud attractor object (termintates loop if hit)
68     secondary path orders (hOrder) disabled in UI (set to 1)
69 v0.2.4 - 04.26.11
70     fixed object selection in UI
71     will not run if required object not selected   
72     moved to view 3d > toolbox
73 v0.2.5 - 05.08.11
74     testing for 2.57b
75     single mesh output (for build modifier)
76     speedups (dist fxn)
77 v0.2.6 - 06.20.11
78     scale/pos on 'write to cubes' works now
79     if origin obj is mesh, uses all verts as initial charges
80     semi-helpful tooltips
81     speedups, faster dedupe fxn, faster classification
82     use any shape mesh obj as insulator mesh
83         must have rot=0, scale=1, origin set to geometry
84         often fails to block bolt with curved/complex shapes
85     separate single and multi mesh creation
86
87 v0.x -
88     -fix vis fxn to only buildCPGraph once for VM or VS
89     -improve list fxns (rid of ((x,y,z),w) and use (x,y,z,w)), use 'sets'
90     -create python cmodule for a few of most costly fxns
91         i have pretty much no idea how to do this yet
92     -cloud and insulator can be groups of MESH objs
93     -?text output, possibly to save on interrupt, allow continue from text
94     -?hook modifiers from tips->sides->main, weight w/ vert groups
95     -user defined 'attractor' path
96     -fix add curve function
97     -animated arcs via. ionization path    
98     -environment map boundary conditions - requires Eqn. 15 from FSLG...
99     -?assign wattage at each segment for HDRI
100     -?default settings for -lightning, -teslacoil, -spark/arc
101     -fix hOrder functionality
102     -multiple 'MAIN' brances for non-lightning discharges
103     -n-symmetry option, create mirror images, snowflakes, etc...
104 """
105
106 ######################################################################
107 ######################################################################
108 ######################################################################
109 import bpy
110 import time
111 import random
112 from math import sqrt
113 from mathutils import Vector
114 import struct
115 import bisect
116 import os.path
117 notZero = 0.0000000001
118 scn = bpy.context.scene
119
120 ######################################################################
121 ########################### UTILITY FXNS #############################
122 ######################################################################
123 def within(x,y,d):
124 ###---CHECK IF x-d <= y <= x+d
125     if x-d <= y and x + d >= y:
126         return True
127     else: return False
128
129 def dist(ax, ay, az ,bx, by, bz):
130     dv = Vector((ax,ay,az)) - Vector((bx,by,bz))
131     d = dv.length
132     return d
133
134 def splitList(aList, idx):
135     ll = []
136     for x in aList:
137         ll.append(x[idx])
138     return ll
139
140 def splitListCo(aList):
141     ll = []
142     for p in aList:
143         ll.append((p[0], p[1], p[2]))
144     return ll
145
146 def getLowHigh(aList):
147     tLow = aList[0]; tHigh = aList[0]
148     for a in aList:
149         if a < tLow: tLow = a
150         if a > tHigh: tHigh = a
151     return tLow, tHigh
152
153 def weightedRandomChoice(aList):
154     tL = []
155     tweight = 0
156     for a in range(len(aList)):
157         idex = a; weight = aList[a]
158         if weight > 0.0:
159             tweight += weight
160             tL.append((tweight, idex))
161     i = bisect.bisect(tL, (random.uniform(0, tweight), None))    
162     r = tL[i][1]
163     return r
164
165 def getStencil3D_26(x,y,z):
166     nL = []
167     for xT in range(x-1, x+2):
168         for yT in range(y-1, y+2):
169             for zT in range(z-1, z+2):
170                 nL.append((xT, yT, zT))
171     nL.remove((x,y,z))
172     return nL
173
174 def jitterCells(aList, jit):
175     j = jit/2
176     bList = []
177     for a in aList:
178         ax = a[0] + random.uniform(-j, j)
179         ay = a[1] + random.uniform(-j, j)
180         az = a[2] + random.uniform(-j, j)
181         bList.append((ax, ay, az))
182     return bList
183
184 def deDupe(seq, idfun=None): 
185 ###---THANKS TO THIS GUY - http://www.peterbe.com/plog/uniqifiers-benchmark
186     if idfun is None:
187         def idfun(x): return x
188     seen = {}
189     result = []
190     for item in seq:
191         marker = idfun(item)
192         if marker in seen: continue
193         seen[marker] = 1
194         result.append(item)
195     return result
196
197 ######################################################################
198 ######################## VISUALIZATION FXNS ##########################
199 ######################################################################
200 def writeArrayToVoxel(arr, filename):
201     gridS = 64
202     half = int(gridS/2)
203     bitOn = 255
204     aGrid = [[[0 for z in range(gridS)] for y in range(gridS)] for x in range(gridS)]
205     for a in arr:
206         try:
207             aGrid[a[0]+half][a[1]+half][a[2]+half] = bitOn
208         except:
209             print('Particle beyond voxel domain')
210     file = open(filename, "wb")
211     for z in range(gridS):
212         for y in range(gridS):
213             for x in range(gridS):
214                 file.write(struct.pack('B', aGrid[x][y][z]))
215     file.flush()
216     file.close()
217         
218 def writeArrayToFile(arr, filename):
219     file = open(filename, "w")
220     for a in arr:
221         tstr = str(a[0]) + ',' + str(a[1]) + ',' + str(a[2]) + '\n'
222         file.write(tstr)
223     file.close
224
225 def readArrayFromFile(filename):
226     file = open(filename, "r")
227     arr = []
228     for f in file:
229         pt = f[0:-1].split(',')
230         arr.append((int(pt[0]), int(pt[1]), int(pt[2])))
231     return arr
232
233 def makeMeshCube(msize):
234     msize = msize/2
235     mmesh = bpy.data.meshes.new('q')
236     mmesh.vertices.add(8)
237     mmesh.vertices[0].co = [-msize, -msize, -msize]
238     mmesh.vertices[1].co = [-msize,  msize, -msize]
239     mmesh.vertices[2].co = [ msize,  msize, -msize]
240     mmesh.vertices[3].co = [ msize, -msize, -msize]
241     mmesh.vertices[4].co = [-msize, -msize,  msize]
242     mmesh.vertices[5].co = [-msize,  msize,  msize]
243     mmesh.vertices[6].co = [ msize,  msize,  msize]
244     mmesh.vertices[7].co = [ msize, -msize,  msize]
245     mmesh.faces.add(6)
246     mmesh.faces[0].vertices_raw = [0,1,2,3]
247     mmesh.faces[1].vertices_raw = [0,4,5,1]
248     mmesh.faces[2].vertices_raw = [2,1,5,6]
249     mmesh.faces[3].vertices_raw = [3,2,6,7]
250     mmesh.faces[4].vertices_raw = [0,3,7,4]
251     mmesh.faces[5].vertices_raw = [5,4,7,6]
252     mmesh.update(calc_edges=True)
253     return(mmesh)
254
255 def writeArrayToCubes(arr, gridBU, orig, cBOOL = False, jBOOL = True):
256     for a in arr:
257         x = a[0]; y = a[1]; z = a[2]
258         me = makeMeshCube(gridBU)
259         ob = bpy.data.objects.new('xCUBE', me)
260         ob.location.x = (x*gridBU) + orig[0]
261         ob.location.y = (y*gridBU) + orig[1]
262         ob.location.z = (z*gridBU) + orig[2]
263         if cBOOL: ###---!!!MOSTLY UNUSED
264             ###   POS+BLUE, NEG-RED, ZERO:BLACK
265             col = (1.0, 1.0, 1.0, 1.0)
266             if a[3] == 0: col = (0.0, 0.0, 0.0, 1.0)
267             if a[3] < 0: col = (-a[3], 0.0, 0.0, 1.0)
268             if a[3] > 0: col = (0.0, 0.0, a[3], 1.0)                
269             ob.color = col
270         bpy.context.scene.objects.link(ob)
271         bpy.context.scene.update()
272     if jBOOL:
273         ###---SELECTS ALL CUBES w/ ?bpy.ops.object.join() b/c
274         ###   CAN'T JOIN ALL CUBES TO A SINGLE MESH RIGHT... ARGH...
275         for q in bpy.context.scene.objects:
276             q.select = False
277             if q.name[0:5] == 'xCUBE':
278                 q.select = True
279                 bpy.context.scene.objects.active = q
280
281 def addVert(ob, pt, conni = -1):
282     mmesh = ob.data
283     mmesh.vertices.add(1)
284     vcounti = len(mmesh.vertices)-1
285     mmesh.vertices[vcounti].co = [pt[0], pt[1], pt[2]]
286     if conni > -1:
287         mmesh.edges.add(1)
288         ecounti = len(mmesh.edges)-1
289         mmesh.edges[ecounti].vertices = [conni, vcounti]
290         mmesh.update()
291
292 def addEdge(ob, va, vb):
293     mmesh = ob.data
294     mmesh.edges.add(1)
295     ecounti = len(mmesh.edges)-1
296     mmesh.edges[ecounti].vertices = [va, vb]
297     mmesh.update()    
298
299 def newMesh(mname):
300     mmesh = bpy.data.meshes.new(mname)
301     omesh = bpy.data.objects.new(mname, mmesh)
302     bpy.context.scene.objects.link(omesh)
303     return omesh      
304
305 def writeArrayToMesh(mname, arr, gridBU, rpt = None):
306     mob = newMesh(mname)
307     mob.scale = (gridBU, gridBU, gridBU)
308     if rpt: addReportProp(mob, rpt)
309     addVert(mob, arr[0], -1)
310     for ai in range(1, len(arr)):
311         a = arr[ai]
312         addVert(mob, a, ai-1)
313     return mob        
314
315 ###---!!!OUT OF ORDER - SOME PROBLEM WITH IT ADDING (0,0,0)
316 def writeArrayToCurves(cname, arr, gridBU, bd = .05, rpt = None):
317     cur = bpy.data.curves.new('fslg_curve', 'CURVE')
318     cur.use_fill_front = False
319     cur.use_fill_back = False    
320     cur.bevel_depth = bd
321     cur.bevel_resolution = 2    
322     cob = bpy.data.objects.new(cname, cur)
323     cob.scale = (gridBU, gridBU, gridBU)
324     if rpt: addReportProp(cob, rpt)
325     bpy.context.scene.objects.link(cob)
326     cur.splines.new('BEZIER')
327     cspline = cur.splines[0]
328     div = 1 ###   SPACING FOR HANDLES (2 - 1/2 WAY, 1 - NEXT BEZIER)
329     for a in range(len(arr)):
330         cspline.bezier_points.add(1)
331         bp = cspline.bezier_points[len(cspline.bezier_points)-1]
332         if a-1 < 0: hL = arr[a]
333         else:
334             hx = arr[a][0] - ((arr[a][0]-arr[a-1][0]) / div)
335             hy = arr[a][1] - ((arr[a][1]-arr[a-1][1]) / div)
336             hz = arr[a][2] - ((arr[a][2]-arr[a-1][2]) / div)
337             hL = (hx,hy,hz)
338         
339         if a+1 > len(arr)-1: hR = arr[a]
340         else:
341             hx = arr[a][0] + ((arr[a+1][0]-arr[a][0]) / div)
342             hy = arr[a][1] + ((arr[a+1][1]-arr[a][1]) / div)
343             hz = arr[a][2] + ((arr[a+1][2]-arr[a][2]) / div)
344             hR = (hx,hy,hz)
345         bp.co = arr[a]
346         bp.handle_left = hL
347         bp.handle_right = hR
348
349 def addArrayToMesh(mob, arr):
350     addVert(mob, arr[0], -1)
351     mmesh = mob.data
352     vcounti = len(mmesh.vertices)-1
353     for ai in range(1, len(arr)):
354         a = arr[ai]
355         addVert(mob, a, len(mmesh.vertices)-1)
356
357 def addMaterial(ob, matname):
358     mat = bpy.data.materials[matname]
359     ob.active_material = mat
360
361 def writeStokeToMesh(arr, jarr, MAINi, HORDERi, TIPSi, orig, gs, rpt=None):
362     ###---MAIN BRANCH
363     print('   WRITING MAIN BRANCH')
364     llmain = []
365     for x in MAINi:
366         llmain.append(jarr[x])
367     mob = writeArrayToMesh('la0MAIN', llmain, gs)
368     mob.location = orig       
369
370     ###---hORDER BRANCHES
371     for hOi in range(len(HORDERi)):
372         print('   WRITING ORDER', hOi)        
373         hO = HORDERi[hOi]
374         hob = newMesh('la1H'+str(hOi))
375
376         for y in hO:
377             llHO = []
378             for x in y:
379                 llHO.append(jarr[x])
380             addArrayToMesh(hob, llHO)
381         hob.scale = (gs, gs, gs)
382         hob.location = orig
383
384     ###---TIPS
385     print('   WRITING TIP PATHS')    
386     tob = newMesh('la2TIPS')
387     for y in  TIPSi:
388         llt = []        
389         for x in y:
390             llt.append(jarr[x])
391         addArrayToMesh(tob, llt)
392     tob.scale = (gs, gs, gs)
393     tob.location = orig
394
395     ###---ADD MATERIALS TO OBJECTS (IF THEY EXIST)    
396     try:
397         addMaterial(mob, 'edgeMAT-h0')
398         addMaterial(hob, 'edgeMAT-h1')
399         addMaterial(tob, 'edgeMAT-h2')
400         print('   ADDED MATERIALS')
401     except: print('   MATERIALS NOT FOUND')
402     ###---ADD GENERATION REPORT TO ALL MESHES
403     if rpt:
404         addReportProp(mob, rpt)
405         addReportProp(hob, rpt)
406         addReportProp(tob, rpt)                
407
408 def writeStokeToSingleMesh(arr, jarr, orig, gs, mct, rpt=None): 
409     sgarr = buildCPGraph(arr, mct)
410     llALL = []
411
412     Aob = newMesh('laALL')
413     for pt in jarr:
414         addVert(Aob, pt)
415     for cpi in range(len(sgarr)):
416         ci = sgarr[cpi][0]
417         pi = sgarr[cpi][1]
418         addEdge(Aob, pi, ci)
419     Aob.location = orig
420     Aob.scale = ((gs,gs,gs))
421
422     if rpt:
423         addReportProp(Aob, rpt)
424
425 def visualizeArray(cg, oob, gs, vm, vs, vc, vv, rst):
426 ###---IN: (cellgrid, origin, gridscale,
427 ###   mulimesh, single mesh, cubes, voxels, report sting)
428     origin = oob.location
429
430     ###---DEAL WITH VERT MULTI-ORIGINS
431     oct = 2
432     if oob.type == 'MESH': oct = len(oob.data.vertices)
433
434     ###---JITTER CELLS
435     if vm or vs: cjarr = jitterCells(cg, 1)
436
437
438     if vm:  ###---WRITE ARRAY TO MULTI MESH
439         
440         aMi, aHi, aTi = classifyStroke(cg, oct, scn.HORDER)
441         print(':::WRITING TO MULTI-MESH')        
442         writeStokeToMesh(cg, cjarr, aMi, aHi, aTi, origin, gs, rst)
443         print(':::MULTI-MESH WRITTEN')
444
445     if vs:  ###---WRITE TO SINGLE MESH
446         print(':::WRITING TO SINGLE MESH')         
447         writeStokeToSingleMesh(cg, cjarr, origin, gs, oct, rst)
448         print(':::SINGLE MESH WRITTEN')
449         
450     if vc:  ###---WRITE ARRAY TO CUBE OBJECTS
451         print(':::WRITING TO CUBES')
452         writeArrayToCubes(cg, gs, origin)
453         print(':::CUBES WRITTEN')
454
455     if vv:  ###---WRITE ARRAY TO VOXEL DATA FILE
456         print(':::WRITING TO VOXELS')
457         fname = "FSLGvoxels.raw"
458         path = os.path.dirname(bpy.data.filepath)
459         writeArrayToVoxel(cg, path + "\\" + fname)
460         print(':::VOXEL DATA WRITTEN TO - ', path + "\\" + fname)
461
462     ###---READ/WRITE ARRAY TO FILE (MIGHT NOT BE NECESSARY)
463     #tfile = 'c:\\testarr.txt'
464     #writeArrayToFile(cg, tfile)
465     #cg = readArrayFromFile(tfile)
466
467     ###---READ/WRITE ARRAY TO CURVES (OUT OF ORDER)
468     #writeArrayToCurves('laMAIN', llmain, .10, .25)        
469
470 ######################################################################
471 ########################### ALGORITHM FXNS ###########################
472 ########################## FROM FALUAM PAPER #########################
473 ###################### PLUS SOME STUFF I MADE UP #####################
474 ######################################################################
475 def buildCPGraph(arr, sti = 2):
476 ###---IN -XYZ ARRAY AS BUILT BY GENERATOR
477 ###---OUT -[(CHILDindex, PARENTindex)]
478 ###   sti - start index, 2 for Empty, len(me.vertices) for Mesh
479     sgarr = []
480     sgarr.append((1, 0)) #
481     for ai in range(sti, len(arr)):
482         cs = arr[ai]
483         cpts = arr[0:ai]
484         cslap = getStencil3D_26(cs[0], cs[1], cs[2])
485
486         for nc in cslap:
487             ct = cpts.count(nc)
488             if ct>0:
489                 cti = cpts.index(nc)
490         sgarr.append((ai, cti))
491     return sgarr
492
493 def buildCPGraph_WORKINPROGRESS(arr, sti = 2):
494 ###---IN -XYZ ARRAY AS BUILT BY GENERATOR
495 ###---OUT -[(CHILDindex, PARENTindex)]
496 ###   sti - start index, 2 for Empty, len(me.vertices) for Mesh
497     sgarr = []
498     sgarr.append((1, 0)) #
499     ctix = 0
500     for ai in range(sti, len(arr)):             
501         cs = arr[ai]
502         #cpts = arr[0:ai]
503         cpts = arr[ctix:ai]
504         cslap = getStencil3D_26(cs[0], cs[1], cs[2])
505         for nc in cslap:
506             ct = cpts.count(nc)
507             if ct>0:
508                 #cti = cpts.index(nc)
509                 cti = ctix + cpts.index(nc)
510                 ctix = cpts.index(nc)
511                                 
512         sgarr.append((ai, cti))
513     return sgarr
514
515 def findChargePath(oc, fc, ngraph, restrict = [], partial = True):
516     ###---oc -ORIGIN CHARGE INDEX, fc -FINAL CHARGE INDEX
517     ###---ngraph -NODE GRAPH, restrict- INDEX OF SITES CANNOT TRAVERSE
518     ###---partial -RETURN PARTIAL PATH IF RESTRICTION ENCOUNTERD
519     cList = splitList(ngraph, 0)
520     pList = splitList(ngraph, 1)
521     aRi = []
522     cNODE = fc
523     for x in range(len(ngraph)):
524         pNODE = pList[cList.index(cNODE)]
525         aRi.append(cNODE)
526         cNODE = pNODE
527         npNODECOUNT = cList.count(pNODE)
528         if cNODE == oc:             ###   STOP IF ORIGIN FOUND
529             aRi.append(cNODE)       ###   RETURN PATH
530             return aRi
531         if npNODECOUNT == 0:        ###   STOP IF NO PARENTS
532             return []               ###   RETURN []
533         if pNODE in restrict:       ###   STOP IF PARENT IS IN RESTRICTION
534             if partial:             ###   RETURN PARTIAL OR []
535                 aRi.append(cNODE)
536                 return aRi
537             else: return []
538
539 def findTips(arr):
540     lt = []
541     for ai in arr[0:len(arr)-1]:
542         a = ai[0]
543         cCOUNT = 0
544         for bi in arr:
545             b = bi[1]
546             if a == b:
547                 cCOUNT += 1
548         if cCOUNT == 0:
549             lt.append(a)
550     return lt
551
552 def findChannelRoots(path, ngraph, restrict = []):
553     roots = []
554     for ai in range(len(ngraph)):
555         chi = ngraph[ai][0]
556         par = ngraph[ai][1]
557         if par in path and not chi in path and \
558             not chi in restrict:        
559             roots.append(par)
560     droots = deDupe(roots)
561     return droots
562
563 def findChannels(roots, tips, ngraph, restrict):
564     cPATHS = []
565     for ri in range(len(roots)):
566         r = roots[ri]
567         sL = 1
568         sPATHi = []
569         for ti in range(len(tips)):
570             t = tips[ti]
571             if t < r: continue
572             tPATHi = findChargePath(r, t, ngraph, restrict, False)
573             tL = len(tPATHi)
574             if tL > sL:
575                 if countChildrenOnPath(tPATHi, ngraph) > 1:
576                     sL = tL
577                     sPATHi = tPATHi
578                     tTEMP = t; tiTEMP = ti
579         if len(sPATHi) > 0:
580             print('   found path/idex from', ri, 'of', 
581                   len(roots), 'possible | tips:', tTEMP, tiTEMP)
582             cPATHS.append(sPATHi)
583             tips.remove(tTEMP)
584     return cPATHS
585
586 def findChannels_WORKINPROGRESS(roots, ttips, ngraph, restrict):
587     cPATHS = []
588     tips = list(ttips)
589     for ri in range(len(roots)):
590         r = roots[ri]
591         sL = 1
592         sPATHi = []
593         tipREMOVE = [] ###---CHECKED TIP INDEXES, TO BE REMOVED FOR NEXT LOOP
594         for ti in range(len(tips)):
595             t = tips[ti]            
596             #print('-CHECKING RT/IDEX:', r, ri, 'AGAINST TIP', t, ti)
597             #if t < r: continue
598             if ti < ri: continue
599             tPATHi = findChargePath(r, t, ngraph, restrict, False)
600             tL = len(tPATHi)
601             if tL > sL:
602                 if countChildrenOnPath(tPATHi, ngraph) > 1:
603                     sL = tL
604                     sPATHi = tPATHi
605                     tTEMP = t; tiTEMP = ti
606             if tL > 0:
607                 tipREMOVE.append(t)                    
608         if len(sPATHi) > 0:
609             print('   found path from root idex', ri, 'of', 
610                    len(roots), 'possible roots | #oftips=', len(tips))
611             cPATHS.append(sPATHi)
612         for q in tipREMOVE:  tips.remove(q)
613
614     return cPATHS
615
616 def countChildrenOnPath(aPath, ngraph, quick = True):
617     ###---RETURN HOW MANY BRANCHES 
618     ###   COUNT WHEN NODE IS A PARENT >1 TIMES
619     ###   quick -STOP AND RETURN AFTER FIRST
620     cCOUNT = 0
621     pList = splitList(ngraph,1)
622     for ai in range(len(aPath)-1):
623         ap = aPath[ai]
624         pc = pList.count(ap)
625         if quick and pc > 1: 
626             return pc
627     return cCOUNT
628
629 ###---CLASSIFY CHANNELS INTO 'MAIN', 'hORDER/SECONDARY' and 'SIDE'
630 def classifyStroke(sarr, mct, hORDER = 1):
631     print(':::CLASSIFYING STROKE')
632     ###---BUILD CHILD/PARENT GRAPH (INDEXES OF sarr)  
633     sgarr = buildCPGraph(sarr, mct)
634
635     ###---FIND MAIN CHANNEL 
636     print('   finding MAIN')
637     oCharge = sgarr[0][1]
638     fCharge = sgarr[len(sgarr)-1][0]
639     aMAINi = findChargePath(oCharge, fCharge, sgarr)
640     
641     ###---FIND TIPS
642     print('   finding TIPS')
643     aTIPSi = findTips(sgarr)
644
645     ###---FIND hORDER CHANNEL ROOTS
646     ###   hCOUNT = ORDERS BEWTEEN MAIN and SIDE/TIPS
647     ###   !!!STILL BUGGY!!!
648     hRESTRICT = list(aMAINi)    ### ADD TO THIS AFTER EACH TIME
649     allHPATHSi = []             ### ALL hO PATHS: [[h0], [h1]...]
650     curPATHSi = [aMAINi]        ### LIST OF PATHS FIND ROOTS ON
651     for h in range(hORDER):
652         allHPATHSi.append([])
653         for pi in range(len(curPATHSi)):     ###   LOOP THROUGH ALL PATHS IN THIS ORDER
654             p = curPATHSi[pi]
655             ###   GET ROOTS FOR THIS PATH
656             aHROOTSi = findChannelRoots(p, sgarr, hRESTRICT)
657             print('   found', len(aHROOTSi), 'roots in ORDER', h, ':#paths:', len(curPATHSi))
658             ### GET CHANNELS FOR THESE ROOTS
659             if len(aHROOTSi) == 0:
660                 print('NO ROOTS FOR FOUND FOR CHANNEL')
661                 aHPATHSi = []
662                 continue
663             else:
664                 aHPATHSiD = findChannels(aHROOTSi, aTIPSi, sgarr, hRESTRICT)
665                 aHPATHSi = aHPATHSiD
666                 allHPATHSi[h] += aHPATHSi
667                 ###   SET THESE CHANNELS AS RESTRICTIONS FOR NEXT ITERATIONS
668                 for hri in aHPATHSi:
669                     hRESTRICT += hri
670         curPATHSi = aHPATHSi
671     
672     ###---SIDE BRANCHES, FINAL ORDER OF HEIRARCHY
673     ###   FROM TIPS THAT ARE NOT IN AN EXISTING PATH
674     ###   BACK TO ANY OTHER POINT THAT IS ALREADY ON A PATH
675     aDRAWNi = []
676     aDRAWNi += aMAINi
677     for oH in allHPATHSi:
678         for o in oH:
679             aDRAWNi += o
680     aTPATHSi = []
681     for a in aTIPSi:
682         if not a in aDRAWNi:
683             aPATHi = findChargePath(oCharge, a, sgarr, aDRAWNi)
684             aDRAWNi += aPATHi
685             aTPATHSi.append(aPATHi)
686             
687     return aMAINi, allHPATHSi, aTPATHSi
688
689 def voxelByVertex(ob, gs):
690 ###---'VOXELIZES' VERTS IN A MESH TO LIST [(x,y,z),(x,y,z)]
691 ###   W/ RESPECT GSCALE AND OB ORIGIN (B/C SHOULD BE ORIGIN OBJ)
692     orig = ob.location
693     ll = []
694     for v in ob.data.vertices:
695         x = int( v.co.x / gs )
696         y = int( v.co.y / gs )
697         z = int( v.co.z / gs )      
698         ll.append((x,y,z))
699     return ll
700     
701 def voxelByRays(ob, orig, gs):
702 ###--- MESH INTO A 3DGRID W/ RESPECT GSCALE AND BOLT ORIGIN
703 ###   -DOES NOT TAKE OBJECT ROTATION/SCALE INTO ACCOUNT
704 ###   -THIS IS A HORRIBLE, INEFFICIENT FUNCTION
705 ###    MAYBE THE RAYCAST/GRID THING ARE A BAD IDEA. BUT I 
706 ###    HAVE TO 'VOXELIZE THE OBJECT W/ RESCT TO GSCALE/ORIGIN
707     bbox = ob.bound_box
708     bbxL = bbox[0][0]; bbxR = bbox[4][0]
709     bbyL = bbox[0][1]; bbyR = bbox[2][1]
710     bbzL = bbox[0][2]; bbzR = bbox[1][2]
711     xct = int((bbxR - bbxL) / gs)
712     yct = int((bbyR - bbyL) / gs)
713     zct = int((bbzR - bbzL) / gs)
714     xs = int(xct/2); ys = int(yct/2); zs = int(zct/2)
715     print('  CASTING', xct, '/', yct, '/', zct, 'cells, total:', xct*yct*zct, 'in obj-', ob.name)    
716     ll = []
717     rc = 100    ###---DISTANCE TO CAST FROM
718     ###---RAYCAST TOP/BOTTOM
719     print('  RAYCASTING TOP/BOTTOM')
720     for x in range(xct):
721         for y in range(yct):
722             xco = bbxL + (x*gs);  yco = bbyL + (y*gs)
723             v1 = ((xco, yco,  rc));    v2 = ((xco, yco, -rc))            
724             vz1 = ob.ray_cast(v1,v2);   vz2 = ob.ray_cast(v2,v1)            
725             if vz1[2] != -1: ll.append((x-xs, y-ys, int(vz1[0][2] * (1/gs)) ))
726             if vz2[2] != -1: ll.append((x-xs, y-ys, int(vz2[0][2] * (1/gs)) ))
727     ###---RAYCAST FRONT/BACK
728     print('  RAYCASTING FRONT/BACK')    
729     for x in range(xct):
730         for z in range(zct):
731             xco = bbxL + (x*gs);  zco = bbzL + (z*gs)
732             v1 = ((xco, rc,  zco));    v2 = ((xco, -rc, zco))            
733             vy1 = ob.ray_cast(v1,v2);   vy2 = ob.ray_cast(v2,v1)            
734             if vy1[2] != -1: ll.append((x-xs, int(vy1[0][1] * (1/gs)), z-zs))
735             if vy2[2] != -1: ll.append((x-xs, int(vy2[0][1] * (1/gs)), z-zs))
736     ###---RAYCAST LEFT/RIGHT
737     print('  RAYCASTING LEFT/RIGHT')
738     for y in range(yct):
739         for z in range(zct):
740             yco = bbyL + (y*gs);  zco = bbzL + (z*gs)
741             v1 = ((rc, yco,  zco));    v2 = ((-rc, yco, zco))            
742             vx1 = ob.ray_cast(v1,v2);   vx2 = ob.ray_cast(v2,v1)            
743             if vx1[2] != -1: ll.append((int(vx1[0][0] * (1/gs)), y-ys, z-zs))            
744             if vx2[2] != -1: ll.append((int(vx2[0][0] * (1/gs)), y-ys, z-zs))
745
746     ###---ADD IN NEIGHBORS SO BOLT WONT GO THRU
747     nlist = []
748     for l in ll:
749         nl = getStencil3D_26(l[0], l[1], l[2])
750         nlist += nl
751
752     ###---DEDUPE
753     print('  ADDED NEIGHBORS, DEDUPING...')    
754     rlist = deDupe(ll+nlist)
755     qlist = []
756     
757     ###---RELOCATE GRID W/ RESPECT GSCALE AND BOLT ORIGIN
758     ###   !!!NEED TO ADD IN OBJ ROT/SCALE HERE SOMEHOW...
759     od = Vector(( (ob.location[0] - orig[0]) / gs,
760                   (ob.location[1] - orig[1]) / gs,
761                   (ob.location[2] - orig[2]) / gs ))
762     for r in rlist:
763         qlist.append((r[0]+int(od[0]), r[1]+int(od[1]), r[2]+int(od[2]) ))
764
765     return qlist
766
767 def fakeGroundChargePlane(z, charge):
768     eCL = []
769     xy = abs(z)/2
770     eCL += [(0, 0, z, charge)]    
771     eCL += [(xy, 0, z, charge)]
772     eCL += [(0, xy, z, charge)]
773     eCL += [(-xy, 0, z, charge)]
774     eCL += [(0, -xy, z, charge)]
775     return eCL
776
777 def addCharges(ll, charge):
778 ###---IN: ll - [(x,y,z), (x,y,z)], charge - w
779 ###   OUT clist - [(x,y,z,w), (x,y,z,w)]
780     clist = []
781     for l in ll:
782         clist.append((l[0], l[1], l[2], charge))
783     return clist
784         
785 ######################################################################
786 ########################### ALGORITHM FXNS ###########################
787 ############################## FROM FSLG #############################
788 ######################################################################
789 def getGrowthProbability_KEEPFORREFERENCE(uN, aList):
790     ###---IN: uN -USER TERM, cList -CANDIDATE SITES, oList -CANDIDATE SITE CHARGES
791     ###   OUT: LIST OF [(XYZ), POT, PROB]
792     cList = splitList(aList, 0)
793     oList = splitList(aList, 1)
794     Omin, Omax = getLowHigh(oList)
795     if Omin == Omax: Omax += notZero; Omin -= notZero
796     PdL = []
797     E = 0
798     E = notZero   ###===DIVISOR FOR (FSLG - Eqn. 12)
799     for o in oList:
800         Uj = (o - Omin) / (Omax - Omin) ###===(FSLG - Eqn. 13)
801         E += pow(Uj, uN)
802     for oi in range(len(oList)):
803         o = oList[oi]
804         Ui = (o - Omin) / (Omax - Omin)
805         Pd = (pow(Ui, uN)) / E ###===(FSLG - Eqn. 12)
806         PdINT = Pd * 100
807         PdL.append(Pd)
808     return PdL 
809
810 ###---WORK IN PROGRESS, TRYING TO SPEED THESE UP
811 def fslg_e13(x, min, max, u): return pow((x - min) / (max - min), u)
812 def addit(x,y):return x+y
813 def fslg_e12(x, min, max, u, e): return (fslg_e13(x, min, max, u) / e) * 100
814
815 def getGrowthProbability(uN, aList):
816     ###---IN: uN -USER TERM, cList -CANDIDATE SITES, oList -CANDIDATE SITE CHARGES
817     ###   OUT: LIST OF PROB
818     cList = splitList(aList, 0)
819     oList = splitList(aList, 1)
820     Omin, Omax = getLowHigh(oList)
821     if Omin == Omax: Omax += notZero; Omin -= notZero
822     PdL = []
823     E = notZero
824     minL = [Omin for q in range(len(oList))]
825     maxL = [Omax for q in range(len(oList))]
826     uNL =  [uN   for q in range(len(oList))]
827     E = sum(map(fslg_e13, oList, minL, maxL, uNL))
828     EL = [E for q in range(len(oList))]
829     mp = map(fslg_e12, oList, minL, maxL, uNL, EL)
830     for m in mp: PdL.append(m)
831     return PdL 
832
833 def updatePointCharges(p, cList, eList = []):
834     ###---IN: pNew -NEW GROWTH CELL
835     ###       cList -OLD CANDIDATE SITES, eList -SAME
836     ###   OUT: LIST OF NEW CHARGE AT CANDIDATE SITES
837     r1 = 1/2        ###===(FSLG - Eqn. 10)
838     nOiL = []    
839     for oi in range(len(cList)):
840         o = cList[oi][1]
841         c = cList[oi][0]
842         iOe = 0
843         rit = dist(c[0], c[1], c[2], p[0], p[1], p[2])        
844         iOe += (1 - (r1/rit))
845         Oit =  o + iOe            
846         nOiL.append((c, Oit))
847     return nOiL
848
849 def initialPointCharges(pList, cList, eList = []):
850     ###---IN: p -CHARGED CELL (XYZ), cList -CANDIDATE SITES (XYZ, POT, PROB)
851     ###   OUT: cList -WITH POTENTIAL CALCULATED 
852     r1 = 1/2        ###===(FSLG - Eqn. 10)
853     npList = []
854     for p in pList:
855         npList.append(((p[0], p[1], p[2]), 1.0))
856     for e in eList:
857         npList.append(((e[0], e[1], e[2]), e[3]))
858     OiL = []
859     for i in cList:
860         Oi = 0
861         for j in npList:
862             if i != j[0]:
863                 rij = dist(i[0], i[1], i[2], j[0][0], j[0][1], j[0][2])
864                 Oi += (1 - (r1 / rij)) * j[1] ### CHARGE INFLUENCE
865         OiL.append(((i[0], i[1], i[2]), Oi))
866     return OiL
867
868 def getCandidateSites(aList, iList = []):
869     ###---IN: aList -(X,Y,Z) OF CHARGED CELL SITES, iList -insulator sites
870     ###   OUT: CANDIDATE LIST OF GROWTH SITES [(X,Y,Z)]
871     tt1 = time.clock()    
872     cList = []
873     for c in aList:
874         tempList = getStencil3D_26(c[0], c[1], c[2])
875         for t in tempList:
876             if not t in aList and not t in iList:
877                 cList.append(t)
878     ncList = deDupe(cList)
879     tt2 = time.clock()  
880     #print('FXNTIMER:getCandidateSites:', tt2-tt1, 'check 26 against:', len(aList)+len(iList))    
881     return ncList
882
883 ######################################################################
884 ############################# SETUP FXNS #############################
885 ######################################################################
886 def setupObjects():
887     oOB = bpy.data.objects.new('ELorigin', None)
888     oOB.location = ((0,0,10))
889     bpy.context.scene.objects.link(oOB)
890
891     gOB = bpy.data.objects.new('ELground', None)
892     gOB.empty_draw_type = 'ARROWS'
893     bpy.context.scene.objects.link(gOB)
894     
895     cME = makeMeshCube(1)
896     cOB = bpy.data.objects.new('ELcloud', cME)
897     cOB.location = ((-2,8,12))
898     cOB.hide_render = True    
899     bpy.context.scene.objects.link(cOB)
900     
901     iME = makeMeshCube(1)
902     for v in iME.vertices: 
903         xyl = 6.5; zl = .5
904         v.co[0] = v.co[0] * xyl
905         v.co[1] = v.co[1] * xyl
906         v.co[2] = v.co[2] * zl
907     iOB = bpy.data.objects.new('ELinsulator', iME)    
908     iOB.location = ((0,0,5))
909     iOB.hide_render = True
910     bpy.context.scene.objects.link(iOB)
911
912     try:
913         scn.OOB = 'ELorigin'
914         scn.GOB = 'ELground'
915         scn.COB = 'ELcloud'
916         scn.IOB = 'ELinsulator'
917     except: pass
918
919 def checkSettings():
920     check = True
921     if scn.OOB == "": 
922         print('ERROR: NO ORIGIN OBJECT SELECTED')
923         check = False
924     if scn.GROUNDBOOL and scn.GOB == "":
925         print('ERROR: NO GROUND OBJECT SELECTED')
926         check = False
927     if scn.CLOUDBOOL and scn.COB == "":
928         print('ERROR: NO CLOUD OBJECT SELECTED')        
929         check = False
930     if scn.IBOOL and scn.IOB == "":
931         print('ERROR: NO INSULATOR OBJECT SELECTED')        
932         check = False
933     #should make a popup here
934     return check
935
936
937 ######################################################################
938 ############################### MAIN #################################
939 ######################################################################
940 def FSLG():
941 ###======FAST SIMULATION OF LAPLACIAN GROWTH======###
942     print('\n<<<<<<------GO GO GADGET: FAST SIMULATION OF LAPLACIAN GROWTH!')
943     tc1 = time.clock()
944     TSTEPS = scn.TSTEPS
945
946     obORIGIN = scn.objects[scn.OOB]
947     obGROUND = scn.objects[scn.GOB]    
948     scn.ORIGIN = obORIGIN.location
949     scn.GROUNDZ = int((obGROUND.location[2] - scn.ORIGIN[2]) / scn.GSCALE)
950     
951     ###====== 1) INSERT INTIAL CHARGE(S) POINT (USES VERTS IF MESH)
952     cgrid = [(0, 0, 0)]
953     if obORIGIN.type == 'MESH':
954         print("<<<<<<------ORIGIN OBJECT IS MESH, 'VOXELIZING' INTIAL CHARGES FROM VERTS")
955         cgrid = voxelByVertex(obORIGIN, scn.GSCALE)
956         if scn.VMMESH:
957             print("<<<<<<------CANNOT CLASSIFY STROKE FROM VERT ORIGINS YET, NO MULTI-MESH OUTPUT")
958             scn.VMMESH = False; scn.VSMESH = True
959
960     ###---GROUND CHARGE CELL / INSULATOR LISTS (eChargeList/icList)
961     eChargeList = []; icList = []
962     if scn.GROUNDBOOL:
963         eChargeList = fakeGroundChargePlane(scn.GROUNDZ, scn.GROUNDC)
964     if scn.CLOUDBOOL:
965         print("<<<<<<------'VOXELIZING' CLOUD OBJECT (COULD TAKE SOME TIME)")
966         obCLOUD = scn.objects[scn.COB]
967         eChargeListQ = voxelByRays(obCLOUD, scn.ORIGIN, scn.GSCALE)
968         eChargeList = addCharges(eChargeListQ, scn.CLOUDC)
969         print('<<<<<<------CLOUD OBJECT CELL COUNT = ', len(eChargeList) )        
970     if scn.IBOOL:
971         print("<<<<<<------'VOXELIZING' INSULATOR OBJECT (COULD TAKE SOME TIME)")
972         obINSULATOR = scn.objects[scn.IOB]
973         icList = voxelByRays(obINSULATOR, scn.ORIGIN, scn.GSCALE)
974         print('<<<<<<------INSULATOR OBJECT CELL COUNT = ', len(icList) )
975         #writeArrayToCubes(icList, scn.GSCALE, scn.ORIGIN)
976         #return 'THEEND'
977         
978     ###====== 2) LOCATE CANDIDATE SITES AROUND CHARGE
979     cSites = getCandidateSites(cgrid, icList)
980     
981     ###====== 3) CALC POTENTIAL AT EACH SITE (Eqn. 10)
982     cSites = initialPointCharges(cgrid, cSites, eChargeList)
983     
984     ts = 1
985     while ts <= TSTEPS:
986         ###====== 1) SELECT NEW GROWTH SITE (Eqn. 12)
987         ###===GET PROBABILITIES AT CANDIDATE SITES
988         gProbs = getGrowthProbability(scn.BIGVAR, cSites)
989         ###===CHOOSE NEW GROWTH SITE BASED ON PROBABILITIES
990         gSitei = weightedRandomChoice(gProbs)
991         gsite  = cSites[gSitei][0]
992
993         ###====== 2) ADD NEW POINT CHARGE AT GROWTH SITE
994         ###===ADD NEW GROWTH CELL TO GRID
995         cgrid.append(gsite)
996         ###===REMOVE NEW GROWTH CELL FROM CANDIDATE SITES
997         cSites.remove(cSites[gSitei])
998
999         ###====== 3) UPDATE POTENTIAL AT CANDIDATE SITES (Eqn. 11)
1000         cSites = updatePointCharges(gsite, cSites, eChargeList)        
1001
1002         ###====== 4) ADD NEW CANDIDATES SURROUNDING GROWTH SITE
1003         ###===GET CANDIDATE 'STENCIL'
1004         ncSitesT = getCandidateSites([gsite], icList)
1005         ###===REMOVE CANDIDATES ALREADY IN CANDIDATE LIST OR CHARGE GRID
1006         ncSites = []
1007         cSplit = splitList(cSites, 0)
1008         for cn in ncSitesT:
1009             if not cn in cSplit and \
1010             not cn in cgrid:
1011                 ncSites.append((cn, 0))
1012
1013         ###====== 5) CALC POTENTIAL AT NEW CANDIDATE SITES (Eqn. 10)
1014         ncSplit = splitList(ncSites, 0)        
1015         ncSites = initialPointCharges(cgrid, ncSplit, eChargeList)
1016
1017         ###===ADD NEW CANDIDATE SITES TO CANDIDATE LIST
1018         for ncs in ncSites:
1019             cSites.append(ncs)
1020
1021         ###===ITERATION COMPLETE
1022         istr1 = ':::T-STEP: ' + str(ts) + '/' + str(TSTEPS) 
1023         istr12 = ' | GROUNDZ: ' + str(scn.GROUNDZ) + ' | '
1024         istr2 = 'CANDS: ' + str(len(cSites)) + ' | '
1025         istr3 = 'GSITE: ' + str(gsite)
1026         print(istr1 + istr12 + istr2 + istr3)        
1027         ts += 1
1028         
1029         ###---EARLY TERMINATION FOR GROUND/CLOUD STRIKE
1030         if scn.GROUNDBOOL:
1031             if gsite[2] == scn.GROUNDZ:
1032                 ts = TSTEPS+1
1033                 print('<<<<<<------EARLY TERMINATION DUE TO GROUNDSTRIKE')
1034                 continue
1035         if scn.CLOUDBOOL:
1036             #if gsite in cloudList:
1037             if gsite in splitListCo(eChargeList):
1038                 ts = TSTEPS+1
1039                 print('<<<<<<------EARLY TERMINATION DUE TO CLOUDSTRIKE')
1040                 continue            
1041
1042     tc2 = time.clock()
1043     tcRUN = tc2 - tc1
1044     print('<<<<<<------LAPLACIAN GROWTH LOOP COMPLETED: ' + str(len(cgrid)) + ' / ' + str(tcRUN)[0:5] + ' SECONDS')
1045     print('<<<<<<------VISUALIZING DATA')
1046
1047     reportSTRING = getReportString(tcRUN)    
1048     ###---VISUALIZE ARRAY
1049     visualizeArray(cgrid, obORIGIN, scn.GSCALE, scn.VMMESH, scn.VSMESH, scn.VCUBE, scn.VVOX, reportSTRING)
1050     print('<<<<<<------COMPLETE')
1051
1052 ######################################################################
1053 ################################ GUI #################################
1054 ######################################################################
1055 ###---NOT IN UI
1056 bpy.types.Scene.ORIGIN = bpy.props.FloatVectorProperty(name = "origin charge")
1057 bpy.types.Scene.GROUNDZ = bpy.props.IntProperty(name = "ground Z coordinate")
1058 bpy.types.Scene.HORDER = bpy.props.IntProperty(name = "secondary paths orders")
1059 ###---IN UI
1060 bpy.types.Scene.TSTEPS = bpy.props.IntProperty(
1061     name = "iterations", description = "number of cells to create, will end early if hits ground plane or cloud")
1062 bpy.types.Scene.GSCALE = bpy.props.FloatProperty(
1063     name = "grid unit size", description = "scale of cells, .25 = 4 cells per blenderUnit")
1064 bpy.types.Scene.BIGVAR = bpy.props.FloatProperty(
1065     name = "straightness", description = "straightness/branchiness of bolt, <2 is mush, >12 is staight line, 6.3 is good")
1066 bpy.types.Scene.GROUNDBOOL = bpy.props.BoolProperty(
1067     name = "use ground object", description = "use ground plane or not")
1068 bpy.types.Scene.GROUNDC = bpy.props.IntProperty(
1069     name = "ground charge", description = "charge of ground plane")
1070 bpy.types.Scene.CLOUDBOOL = bpy.props.BoolProperty(
1071     name = "use cloud object", description = "use cloud obj, attracts and terminates like ground but any obj instead of z plane, can slow down loop if obj is large, overrides ground")
1072 bpy.types.Scene.CLOUDC = bpy.props.IntProperty(
1073     name = "cloud charge", description = "charge of a cell in cloud object (so total charge also depends on obj size)")
1074
1075 bpy.types.Scene.VMMESH = bpy.props.BoolProperty(
1076     name = "multi mesh", description = "output to multi-meshes for different materials on main/sec/side branches")
1077 bpy.types.Scene.VSMESH = bpy.props.BoolProperty(
1078     name = "single mesh", description = "output to single mesh for using build modifier and particles for effects")
1079 bpy.types.Scene.VCUBE = bpy.props.BoolProperty(
1080     name = "cubes", description = "CTRL-J after run to JOIN, outputs a bunch of cube objest, mostly for testing")
1081 bpy.types.Scene.VVOX = bpy.props.BoolProperty(        
1082     name = "voxel (experimental)", description = "output to a voxel file to bpy.data.filepath\FSLGvoxels.raw - doesn't work well right now")
1083 bpy.types.Scene.IBOOL = bpy.props.BoolProperty(
1084     name = "use insulator object", description = "use insulator mesh object to prevent growth of bolt in areas")
1085 bpy.types.Scene.OOB = bpy.props.StringProperty(description = "origin of bolt, can be an Empty, if obj is mesh will use all verts as charges")
1086 bpy.types.Scene.GOB = bpy.props.StringProperty(description = "object to use as ground plane, uses z coord only")
1087 bpy.types.Scene.COB = bpy.props.StringProperty(description = "object to use as cloud, best to use a cube")
1088 bpy.types.Scene.IOB = bpy.props.StringProperty(description = "object to use as insulator, 'voxelized' before generating bolt, can be slow")
1089
1090 ###---DEFAULT USER SETTINGS
1091 scn.TSTEPS = 350
1092 scn.HORDER = 1
1093 scn.GSCALE = 0.12
1094 scn.BIGVAR = 6.3
1095 scn.GROUNDBOOL = True
1096 scn.GROUNDC = -250
1097 scn.CLOUDBOOL = False
1098 scn.CLOUDC = -1
1099 scn.VMMESH = True
1100 scn.VSMESH = False
1101 scn.VCUBE = False
1102 scn.VVOX = False
1103 scn.IBOOL = False
1104 try:
1105     scn.OOB = "ELorigin"
1106     scn.GOB = "ELground"
1107     scn.COB = "ELcloud"
1108     scn.IOB = "ELinsulator"    
1109 except: pass
1110 ### TESTING
1111 if False:
1112 #if True:
1113     #scn.BIGVAR = 6.3
1114     #scn.VSMESH = True
1115     #scn.VMMESH = False
1116     #scn.VCUBE = True
1117     #scn.TSTEPS = 7500
1118     scn.TSTEPS = 100
1119     #scn.GROUNDC = -500
1120     #scn.CLOUDC = -5
1121     #scn.GROUNDBOOL = False
1122     #scn.CLOUDBOOL = True
1123     
1124     #scn.IBOOL = True
1125
1126 class runFSLGLoopOperator(bpy.types.Operator):
1127     '''By The Mighty Hammer Of Thor!!!'''
1128     bl_idname = "object.runfslg_operator"
1129     bl_label = "run FSLG Loop Operator"
1130
1131     def execute(self, context):
1132         if checkSettings():
1133             FSLG()
1134         else: pass
1135         return {'FINISHED'}
1136     
1137 class setupObjectsOperator(bpy.types.Operator):
1138     '''create origin/ground/cloud/insulator objects'''
1139     bl_idname = "object.setup_objects_operator"
1140     bl_label = "Setup Objects Operator"
1141
1142     def execute(self, context):
1143         setupObjects()        
1144         return {'FINISHED'}    
1145
1146 class OBJECT_PT_fslg(bpy.types.Panel):
1147     bl_label = "Laplacian Lightning - v0.2.6"
1148     bl_space_type = "VIEW_3D"
1149     bl_region_type = "TOOLS"
1150     bl_context = "objectmode"
1151
1152     def draw(self, context):
1153         scn = context.scene
1154         layout = self.layout
1155         colR = layout.column()        
1156         #row1 = layout.row()
1157         #colL = row1.column()
1158         #colR = row1.column()
1159         colR.label('-for progress open console-')
1160         colR.label('Help > Toggle System Console')        
1161         colR.prop(scn, 'TSTEPS')
1162         colR.prop(scn, 'GSCALE')        
1163         colR.prop(scn, 'BIGVAR')
1164         colR.operator('object.setup_objects_operator', text = 'create setup objects')        
1165         colR.label('origin object')
1166         colR.prop_search(scn, "OOB",  context.scene, "objects")        
1167         colR.prop(scn, 'GROUNDBOOL')
1168         colR.prop_search(scn, "GOB",  context.scene, "objects")        
1169         colR.prop(scn, 'GROUNDC') 
1170         colR.prop(scn, 'CLOUDBOOL')
1171         colR.prop_search(scn, "COB",  context.scene, "objects")        
1172         colR.prop(scn, 'CLOUDC')
1173         colR.prop(scn, 'IBOOL')
1174         colR.prop_search(scn, "IOB",  context.scene, "objects")
1175         colR.operator('object.runfslg_operator', text = 'generate lightning')
1176         #col.prop(scn, 'HORDER')
1177         colR.prop(scn, 'VMMESH')
1178         colR.prop(scn, 'VSMESH')        
1179         colR.prop(scn, 'VCUBE')
1180         colR.prop(scn, 'VVOX')
1181
1182 def getReportString(rtime):
1183     rSTRING1 = 't:' + str(scn.TSTEPS) + ',sc:' + str(scn.GSCALE)[0:4] + ',uv:' + str(scn.BIGVAR)[0:4] + ',' 
1184     rSTRING2 = 'ori:' + str(scn. ORIGIN[0]) + '/' + str(scn. ORIGIN[1]) + '/' + str(scn. ORIGIN[2]) + ','
1185     rSTRING3 = 'gz:' + str(scn.GROUNDZ) + ',gc:' + str(scn.GROUNDC) + ',rtime:' + str(int(rtime))
1186     return rSTRING1 + rSTRING2 + rSTRING3
1187
1188 def addReportProp(ob, str):
1189     bpy.types.Object.FSLG_REPORT = bpy.props.StringProperty(
1190            name = 'fslg_report', default = '')
1191     ob.FSLG_REPORT = str
1192         
1193 def register():
1194     bpy.utils.register_class(runFSLGLoopOperator)    
1195     bpy.utils.register_class(setupObjectsOperator)
1196     bpy.utils.register_class(OBJECT_PT_fslg)
1197
1198 def unregister():
1199     bpy.utils.unregister_class(runFSLGLoopOperator)    
1200     bpy.utils.unregister_class(setupObjectsOperator)    
1201     bpy.utils.unregister_class(OBJECT_PT_fslg)
1202
1203 if __name__ == "__main__":
1204     ### RUN FOR TESTING
1205     #FSLG()
1206     
1207     ### UI
1208     register()
1209     pass
1210
1211 ###########################
1212 ##### FXN BENCHMARKS ######
1213 ###########################
1214 def BENCH():
1215     print('\n\n\n--->BEGIN BENCHMARK')
1216     bt0 = time.clock()
1217     ###---MAKE A BIG LIST
1218     tsize = 25
1219     tlist = []
1220     for x in range(tsize):
1221         for y in range(tsize):
1222             for z in range(tsize):
1223                 tlist.append((x,y,z))
1224                 tlist.append((x,y,z))
1225
1226     ###---FUNCTION TO TEST
1227     bt1 = time.clock()
1228
1229     #ll = deDupe(tlist)
1230     #ll = f5(tlist)
1231     print('LENS - ', len(tlist), len(ll) )
1232
1233     bt2 = time.clock()
1234     btRUNb = bt2 - bt1
1235     btRUNa = bt1 - bt0
1236     print('--->SETUP TIME    : ', btRUNa)
1237     print('--->BENCHMARK TIME: ', btRUNb)
1238     print('--->GRIDSIZE: ', tsize, ' - ', tsize*tsize*tsize)
1239     
1240 #BENCH()    
1241
1242
1243 ##################################
1244 ################################