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