1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # NOTE: moved the winmgr properties to __init__ and scene
20 # search for context.scene.advanced_objects1
23 "name": "Laplacian Lightning",
24 "author": "teldredge",
25 "blender": (2, 78, 0),
26 "location": "3D View > Toolshelf > Create > Laplacian Lightning",
27 "description": "Lightning mesh generator using laplacian growth algorithm",
31 # BLENDER LAPLACIAN LIGHTNING
34 # https://developer.blender.org/T27189
36 # using algorithm from
37 # FAST SIMULATION OF LAPLACIAN GROWTH (FSLG)
38 # http://gamma.cs.unc.edu/FRAC/
40 # and a few ideas ideas from
41 # FAST ANIMATION OF LIGHTNING USING AN ADAPTIVE MESH (FALUAM)
42 # http://gamma.cs.unc.edu/FAST_LIGHTNING/
46 ----- RELEASE LOG/NOTES/PONTIFICATIONS -----
48 basic generate functions and UI
49 object creation report (Custom Properties: FSLG_REPORT)
51 started spelling laplacian right.
52 add curve function (not in UI) ...twisting problem
53 classify stroke by MAIN path, h-ORDER paths, TIP paths
54 jitter cells for mesh creation
55 add materials if present
57 mesh classification speedup
59 fxns to write/read array to file
60 restrict growth to insulator cells (object bounding box)
61 origin/ground defineable by object
62 gridunit more like 'resolution'
64 cloud attractor object (termintates loop if hit)
65 secondary path orders (hOrder) disabled in UI (set to 1)
67 fixed object selection in UI
68 will not run if required object not selected
69 moved to view 3d > toolbox
72 single mesh output (for build modifier)
75 scale/pos on 'write to cubes' works now
76 if origin obj is mesh, uses all verts as initial charges
78 speedups, faster dedupe fxn, faster classification
79 use any shape mesh obj as insulator mesh
80 must have rot=0, scale=1, origin set to geometry
81 often fails to block bolt with curved/complex shapes
82 separate single and multi mesh creation
84 fixed the issue that prevented enabling the add-on
85 fixed makeMeshCube fxn
86 disabled visualization for voxels
89 -prevent create_setup_objects from generating duplicates
90 -fix vis fxn to only buildCPGraph once for VM or VS
91 -improve list fxns (rid of ((x,y,z),w) and use (x,y,z,w)), use 'sets'
92 -create python cmodule for a few of most costly fxns
93 i have pretty much no idea how to do this yet
94 -cloud and insulator can be groups of MESH objs
95 -text output, possibly to save on interrupt, allow continue from text
96 -?hook modifiers from tips->sides->main, weight w/ vert groups
97 -user defined 'attractor' path
98 -fix add curve function
99 -animated arcs via. ionization path
100 -environment map boundary conditions - requires Eqn. 15 from FSLG.
101 -assign wattage at each segment for HDRI
102 -?default settings for -lightning, -teslacoil, -spark/arc
103 -fix hOrder functionality
104 -multiple 'MAIN' brances for non-lightning discharges
105 -n-symmetry option, create mirror images, snowflakes, etc...
111 from bpy
.types
import (
115 # from math import sqrt
116 from mathutils
import Vector
122 notZero
= 0.0000000001
123 # set to True to enable debug prints
129 # func - function name, text - message, var - variable to print
130 # it can have one variable to observe
131 def debug_prints(func
="", text
="Message", var
=None):
134 print("\n[{}]\nmessage: {}".format(func
, text
))
136 print("variable: ", var
)
139 # pass variables just like for the regular prints
140 def debug_print_vars(*args
, **kwargs
):
143 print(*args
, **kwargs
)
147 # CHECK IF x - d <= y <= x + d
148 if x
- d
<= y
and x
+ d
>= y
:
154 def dist(ax
, ay
, az
, bx
, by
, bz
):
155 dv
= Vector((ax
, ay
, az
)) - Vector((bx
, by
, bz
))
160 def splitList(aList
, idx
):
167 def splitListCo(aList
):
170 ll
.append((p
[0], p
[1], p
[2]))
174 def getLowHigh(aList
):
185 def weightedRandomChoice(aList
):
188 for a
in range(len(aList
)):
193 tL
.append((tweight
, idex
))
194 i
= bisect
.bisect(tL
, (random
.uniform(0, tweight
), None))
199 def getStencil3D_26(x
, y
, z
):
201 for xT
in range(x
- 1, x
+ 2):
202 for yT
in range(y
- 1, y
+ 2):
203 for zT
in range(z
- 1, z
+ 2):
204 nL
.append((xT
, yT
, zT
))
209 def jitterCells(aList
, jit
):
213 ax
= a
[0] + random
.uniform(-j
, j
)
214 ay
= a
[1] + random
.uniform(-j
, j
)
215 az
= a
[2] + random
.uniform(-j
, j
)
216 bList
.append((ax
, ay
, az
))
220 def deDupe(seq
, idfun
=None):
221 # Thanks to this guy - http://www.peterbe.com/plog/uniqifiers-benchmark
236 # Visulization functions
238 def writeArrayToVoxel(arr
, filename
):
240 half
= int(gridS
/ 2)
242 aGrid
= [[[0 for z
in range(gridS
)] for y
in range(gridS
)] for x
in range(gridS
)]
245 aGrid
[a
[0] + half
][a
[1] + half
][a
[2] + half
] = bitOn
247 debug_prints(func
="writeArrayToVoxel", text
="Particle beyond voxel domain")
249 file = open(filename
, "wb")
250 for z
in range(gridS
):
251 for y
in range(gridS
):
252 for x
in range(gridS
):
253 file.write(struct
.pack('B', aGrid
[x
][y
][z
]))
258 def writeArrayToFile(arr
, filename
):
259 file = open(filename
, "w")
261 tstr
= str(a
[0]) + ',' + str(a
[1]) + ',' + str(a
[2]) + '\n'
266 def readArrayFromFile(filename
):
267 file = open(filename
, "r")
270 pt
= f
[0:-1].split(',')
271 arr
.append((int(pt
[0]), int(pt
[1]), int(pt
[2])))
275 def makeMeshCube_OLD(msize
):
277 mmesh
= bpy
.data
.meshes
.new('q')
278 mmesh
.vertices
.add(8)
279 mmesh
.vertices
[0].co
= [-msize
, -msize
, -msize
]
280 mmesh
.vertices
[1].co
= [-msize
, msize
, -msize
]
281 mmesh
.vertices
[2].co
= [msize
, msize
, -msize
]
282 mmesh
.vertices
[3].co
= [msize
, -msize
, -msize
]
283 mmesh
.vertices
[4].co
= [-msize
, -msize
, msize
]
284 mmesh
.vertices
[5].co
= [-msize
, msize
, msize
]
285 mmesh
.vertices
[6].co
= [msize
, msize
, msize
]
286 mmesh
.vertices
[7].co
= [msize
, -msize
, msize
]
288 mmesh
.faces
[0].vertices_raw
= [0, 1, 2, 3]
289 mmesh
.faces
[1].vertices_raw
= [0, 4, 5, 1]
290 mmesh
.faces
[2].vertices_raw
= [2, 1, 5, 6]
291 mmesh
.faces
[3].vertices_raw
= [3, 2, 6, 7]
292 mmesh
.faces
[4].vertices_raw
= [0, 3, 7, 4]
293 mmesh
.faces
[5].vertices_raw
= [5, 4, 7, 6]
294 mmesh
.update(calc_edges
=True)
299 def makeMeshCube(msize
):
301 # 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)]
302 verts
= [(-m2
, -m2
, -m2
), (-m2
, m2
, -m2
), (m2
, m2
, -m2
), (m2
, -m2
, -m2
),
303 (-m2
, -m2
, m2
), (-m2
, m2
, m2
), (m2
, m2
, m2
), (m2
, -m2
, m2
)]
305 (0, 1, 2, 3), (4, 5, 6, 7), (0, 4, 5, 1),
306 (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)
308 # Define mesh and object
309 mmesh
= bpy
.data
.meshes
.new("Cube")
312 mmesh
.from_pydata(verts
, [], faces
)
313 mmesh
.update(calc_edges
=True)
317 def writeArrayToCubes(arr
, gridBU
, orig
, cBOOL
=False, jBOOL
=True):
322 me
= makeMeshCube(gridBU
)
323 ob
= bpy
.data
.objects
.new('xCUBE', me
)
324 ob
.location
.x
= (x
* gridBU
) + orig
[0]
325 ob
.location
.y
= (y
* gridBU
) + orig
[1]
326 ob
.location
.z
= (z
* gridBU
) + orig
[2]
328 if cBOOL
: # mostly unused
329 # pos + blue, neg - red, zero: black
330 col
= (1.0, 1.0, 1.0, 1.0)
332 col
= (0.0, 0.0, 0.0, 1.0)
334 col
= (-a
[3], 0.0, 0.0, 1.0)
336 col
= (0.0, 0.0, a
[3], 1.0)
338 bpy
.context
.scene
.objects
.link(ob
)
339 bpy
.context
.scene
.update()
342 # Selects all cubes w/ ?bpy.ops.object.join() b/c
343 # Can't join all cubes to a single mesh right... argh...
344 for q
in bpy
.context
.scene
.objects
:
346 if q
.name
[0:5] == 'xCUBE':
348 bpy
.context
.scene
.objects
.active
= q
351 def addVert(ob
, pt
, conni
=-1):
353 mmesh
.vertices
.add(1)
354 vcounti
= len(mmesh
.vertices
) - 1
355 mmesh
.vertices
[vcounti
].co
= [pt
[0], pt
[1], pt
[2]]
358 ecounti
= len(mmesh
.edges
) - 1
359 mmesh
.edges
[ecounti
].vertices
= [conni
, vcounti
]
363 def addEdge(ob
, va
, vb
):
366 ecounti
= len(mmesh
.edges
) - 1
367 mmesh
.edges
[ecounti
].vertices
= [va
, vb
]
372 mmesh
= bpy
.data
.meshes
.new(mname
)
373 omesh
= bpy
.data
.objects
.new(mname
, mmesh
)
374 bpy
.context
.scene
.objects
.link(omesh
)
378 def writeArrayToMesh(mname
, arr
, gridBU
, rpt
=None):
380 mob
.scale
= (gridBU
, gridBU
, gridBU
)
382 addReportProp(mob
, rpt
)
383 addVert(mob
, arr
[0], -1)
384 for ai
in range(1, len(arr
)):
386 addVert(mob
, a
, ai
- 1)
390 # out of order - some problem with it adding (0,0,0)
391 def writeArrayToCurves(cname
, arr
, gridBU
, bd
=.05, rpt
=None):
392 cur
= bpy
.data
.curves
.new('fslg_curve', 'CURVE')
393 cur
.use_fill_front
= False
394 cur
.use_fill_back
= False
396 cur
.bevel_resolution
= 2
397 cob
= bpy
.data
.objects
.new(cname
, cur
)
398 cob
.scale
= (gridBU
, gridBU
, gridBU
)
401 addReportProp(cob
, rpt
)
402 bpy
.context
.scene
.objects
.link(cob
)
403 cur
.splines
.new('BEZIER')
404 cspline
= cur
.splines
[0]
405 div
= 1 # spacing for handles (2 - 1/2 way, 1 - next bezier)
407 for a
in range(len(arr
)):
408 cspline
.bezier_points
.add(1)
409 bp
= cspline
.bezier_points
[len(cspline
.bezier_points
) - 1]
413 hx
= arr
[a
][0] - ((arr
[a
][0] - arr
[a
- 1][0]) / div
)
414 hy
= arr
[a
][1] - ((arr
[a
][1] - arr
[a
- 1][1]) / div
)
415 hz
= arr
[a
][2] - ((arr
[a
][2] - arr
[a
- 1][2]) / div
)
418 if a
+ 1 > len(arr
) - 1:
421 hx
= arr
[a
][0] + ((arr
[a
+ 1][0] - arr
[a
][0]) / div
)
422 hy
= arr
[a
][1] + ((arr
[a
+ 1][1] - arr
[a
][1]) / div
)
423 hz
= arr
[a
][2] + ((arr
[a
+ 1][2] - arr
[a
][2]) / div
)
430 def addArrayToMesh(mob
, arr
):
431 addVert(mob
, arr
[0], -1)
433 vcounti
= len(mmesh
.vertices
) - 1
434 for ai
in range(1, len(arr
)):
436 addVert(mob
, a
, len(mmesh
.vertices
) - 1)
439 def addMaterial(ob
, matname
):
440 mat
= bpy
.data
.materials
[matname
]
441 ob
.active_material
= mat
444 def writeStokeToMesh(arr
, jarr
, MAINi
, HORDERi
, TIPSi
, orig
, gs
, rpt
=None):
446 debug_prints(func
="writeStokeToMesh", text
='Writing main branch')
450 llmain
.append(jarr
[x
])
451 mob
= writeArrayToMesh('la0MAIN', llmain
, gs
)
455 for hOi
in range(len(HORDERi
)):
456 debug_prints(func
="writeStokeToMesh", text
="Writing order", var
=hOi
)
458 hob
= newMesh('la1H' + str(hOi
))
464 addArrayToMesh(hob
, llHO
)
465 hob
.scale
= (gs
, gs
, gs
)
469 debug_prints(func
="writeStokeToMesh", text
="Writing tip paths")
470 tob
= newMesh('la2TIPS')
475 addArrayToMesh(tob
, llt
)
476 tob
.scale
= (gs
, gs
, gs
)
479 # add materials to objects (if they exist)
481 addMaterial(mob
, 'edgeMAT-h0')
482 addMaterial(hob
, 'edgeMAT-h1')
483 addMaterial(tob
, 'edgeMAT-h2')
484 debug_prints(func
="writeStokeToMesh", text
="Added materials")
487 debug_prints(func
="writeStokeToMesh", text
="Materials not found")
489 # add generation report to all meshes
491 addReportProp(mob
, rpt
)
492 addReportProp(hob
, rpt
)
493 addReportProp(tob
, rpt
)
496 def writeStokeToSingleMesh(arr
, jarr
, orig
, gs
, mct
, rpt
=None):
497 sgarr
= buildCPGraph(arr
, mct
)
500 Aob
= newMesh('laALL')
503 for cpi
in range(len(sgarr
)):
508 Aob
.scale
= ((gs
, gs
, gs
))
511 addReportProp(Aob
, rpt
)
514 def visualizeArray(cg
, oob
, gs
, vm
, vs
, vc
, vv
, rst
):
515 winmgr
= bpy
.context
.scene
.advanced_objects1
516 # IN: (cellgrid, origin, gridscale,
517 # mulimesh, single mesh, cubes, voxels, report string)
518 origin
= oob
.location
520 # deal with vert multi-origins
522 if oob
.type == 'MESH':
523 oct = len(oob
.data
.vertices
)
527 cjarr
= jitterCells(cg
, 1)
529 if vm
: # write array to multi mesh
531 aMi
, aHi
, aTi
= classifyStroke(cg
, oct, winmgr
.HORDER
)
532 debug_prints(func
="visualizeArray", text
="Writing to multi-mesh")
533 writeStokeToMesh(cg
, cjarr
, aMi
, aHi
, aTi
, origin
, gs
, rst
)
534 debug_prints(func
="visualizeArray", text
="Multi-mesh written")
536 if vs
: # write to single mesh
537 debug_prints(func
="visualizeArray", text
="Writing to single mesh")
538 writeStokeToSingleMesh(cg
, cjarr
, origin
, gs
, oct, rst
)
539 debug_prints(func
="visualizeArray", text
="Single mesh written")
541 if vc
: # write array to cube objects
542 debug_prints(func
="visualizeArray", text
="Writing to cubes")
543 writeArrayToCubes(cg
, gs
, origin
)
544 debug_prints(func
="visualizeArray", text
="Cubes written")
546 if vv
: # write array to voxel data file
547 debug_prints(func
="visualizeArray", text
="Writing to voxels")
548 fname
= "FSLGvoxels.raw"
549 path
= os
.path
.dirname(bpy
.data
.filepath
)
550 writeArrayToVoxel(cg
, path
+ "\\" + fname
)
552 debug_prints(func
="visualizeArray",
553 text
="Voxel data written to:", var
=path
+ "\\" + fname
)
555 # read/write array to file (might not be necessary)
556 # tfile = 'c:\\testarr.txt'
557 # writeArrayToFile(cg, tfile)
558 # cg = readArrayFromFile(tfile)
560 # read/write array to curves (out of order)
561 # writeArrayToCurves('laMAIN', llmain, .10, .25)
564 # Algorithm functions
566 # plus some stuff i made up
568 def buildCPGraph(arr
, sti
=2):
569 # in -xyz array as built by generator
570 # out -[(childindex, parentindex)]
571 # sti - start index, 2 for empty, len(me.vertices) for mesh
575 for ai
in range(sti
, len(arr
)):
578 cslap
= getStencil3D_26(cs
[0], cs
[1], cs
[2])
584 sgarr
.append((ai
, cti
))
589 def buildCPGraph_WORKINPROGRESS(arr
, sti
=2):
590 # in -xyz array as built by generator
591 # out -[(childindex, parentindex)]
592 # sti - start index, 2 for empty, len(me.vertices) for mesh
596 for ai
in range(sti
, len(arr
)):
600 cslap
= getStencil3D_26(cs
[0], cs
[1], cs
[2])
604 # cti = cpts.index(nc)
605 cti
= ctix
+ cpts
.index(nc
)
606 ctix
= cpts
.index(nc
)
608 sgarr
.append((ai
, cti
))
613 def findChargePath(oc
, fc
, ngraph
, restrict
=[], partial
=True):
614 # oc -origin charge index, fc -final charge index
615 # ngraph -node graph, restrict- index of sites cannot traverse
616 # partial -return partial path if restriction encountered
617 cList
= splitList(ngraph
, 0)
618 pList
= splitList(ngraph
, 1)
621 for x
in range(len(ngraph
)):
622 pNODE
= pList
[cList
.index(cNODE
)]
625 npNODECOUNT
= cList
.count(pNODE
)
626 if cNODE
== oc
: # stop if origin found
627 aRi
.append(cNODE
) # return path
629 if npNODECOUNT
== 0: # stop if no parents
630 return [] # return []
631 if pNODE
in restrict
: # stop if parent is in restriction
632 if partial
: # return partial or []
641 for ai
in arr
[0: len(arr
) - 1]:
654 def findChannelRoots(path
, ngraph
, restrict
=[]):
656 for ai
in range(len(ngraph
)):
659 if par
in path
and chi
not in path
and chi
not in restrict
:
661 droots
= deDupe(roots
)
666 def findChannels(roots
, tips
, ngraph
, restrict
):
668 for ri
in range(len(roots
)):
672 for ti
in range(len(tips
)):
676 tPATHi
= findChargePath(r
, t
, ngraph
, restrict
, False)
679 if countChildrenOnPath(tPATHi
, ngraph
) > 1:
686 "\n[findChannels]\n",
687 "found path/idex from", ri
, 'of',
688 len(roots
), "possible | tips:", tTEMP
, tiTEMP
690 cPATHS
.append(sPATHi
)
696 def findChannels_WORKINPROGRESS(roots
, ttips
, ngraph
, restrict
):
699 for ri
in range(len(roots
)):
703 tipREMOVE
= [] # checked tip indexes, to be removed for next loop
704 for ti
in range(len(tips
)):
709 tPATHi
= findChargePath(r
, t
, ngraph
, restrict
, False)
712 if countChildrenOnPath(tPATHi
, ngraph
) > 1:
721 "\n[findChannels_WORKINPROGRESS]\n",
722 "found path from root idex", ri
, 'of',
723 len(roots
), "possible roots | of tips= ", len(tips
)
725 cPATHS
.append(sPATHi
)
733 def countChildrenOnPath(aPath
, ngraph
, quick
=True):
734 # return how many branches
735 # count when node is a parent >1 times
736 # quick -stop and return after first
738 pList
= splitList(ngraph
, 1)
740 for ai
in range(len(aPath
) - 1):
750 # classify channels into 'main', 'hORDER/secondary' and 'side'
751 def classifyStroke(sarr
, mct
, hORDER
=1):
752 debug_prints(func
="classifyStroke", text
="Classifying stroke")
753 # build child/parent graph (indexes of sarr)
754 sgarr
= buildCPGraph(sarr
, mct
)
757 debug_prints(func
="classifyStroke", text
="Finding MAIN")
758 oCharge
= sgarr
[0][1]
759 fCharge
= sgarr
[len(sgarr
) - 1][0]
760 aMAINi
= findChargePath(oCharge
, fCharge
, sgarr
)
763 debug_prints(func
="classifyStroke", text
="Finding TIPS")
764 aTIPSi
= findTips(sgarr
)
766 # find horder channel roots
767 # hcount = orders between main and side/tips
769 hRESTRICT
= list(aMAINi
) # add to this after each time
770 allHPATHSi
= [] # all ho paths: [[h0], [h1]...]
771 curPATHSi
= [aMAINi
] # list of paths find roots on
773 for h
in range(hORDER
):
774 allHPATHSi
.append([])
775 for pi
in range(len(curPATHSi
)): # loop through all paths in this order
777 # get roots for this path
778 aHROOTSi
= findChannelRoots(p
, sgarr
, hRESTRICT
)
780 "\n[classifyStroke]\n",
781 "found", len(aHROOTSi
), "roots in ORDER", h
, ":paths:", len(curPATHSi
)
783 # get channels for these roots
784 if len(aHROOTSi
) == 0:
785 debug_prints(func
="classifyStroke", text
="No roots for found for channel")
789 aHPATHSiD
= findChannels(aHROOTSi
, aTIPSi
, sgarr
, hRESTRICT
)
791 allHPATHSi
[h
] += aHPATHSi
792 # set these channels as restrictions for next iterations
797 # side branches, final order of hierarchy
798 # from tips that are not in an existing path
799 # back to any other point that is already on a path
802 for oH
in allHPATHSi
:
808 aPATHi
= findChargePath(oCharge
, a
, sgarr
, aDRAWNi
)
810 aTPATHSi
.append(aPATHi
)
812 return aMAINi
, allHPATHSi
, aTPATHSi
815 def voxelByVertex(ob
, gs
):
816 # 'voxelizes' verts in a mesh to list [(x,y,z),(x,y,z)]
817 # w/ respect gscale and ob origin (b/c should be origin obj)
820 for v
in ob
.data
.vertices
:
829 def voxelByRays(ob
, orig
, gs
):
830 # mesh into a 3dgrid w/ respect gscale and bolt origin
831 # - does not take object rotation/scale into account
832 # - this is a horrible, inefficient function
833 # maybe the raycast/grid thing are a bad idea. but i
834 # have to 'voxelize the object w/ resct to gscale/origin
842 xct
= int((bbxR
- bbxL
) / gs
)
843 yct
= int((bbyR
- bbyL
) / gs
)
844 zct
= int((bbzR
- bbzL
) / gs
)
851 "Casting", xct
, '/', yct
, '/', zct
, 'cells, total:',
852 xct
* yct
* zct
, 'in obj-', ob
.name
855 rc
= 100 # distance to cast from
857 debug_prints(func
="voxelByRays", text
="Raycasting top/bottom")
861 xco
= bbxL
+ (x
* gs
)
862 yco
= bbyL
+ (y
* gs
)
863 v1
= ((xco
, yco
, rc
))
864 v2
= ((xco
, yco
, -rc
))
865 vz1
= ob
.ray_cast(v1
, v2
)
866 vz2
= ob
.ray_cast(v2
, v1
)
869 "\n[voxelByRays]\n", "vz1 is: ", vz1
, "\nvz2 is: ", vz2
871 # Note: the API raycast return has changed now it is
872 # (result, location, normal, index) - result is a boolean
874 ll
.append((x
- xs
, y
- ys
, int(vz1
[1][2] * (1 / gs
))))
876 ll
.append((x
- xs
, y
- ys
, int(vz2
[1][2] * (1 / gs
))))
879 debug_prints(func
="voxelByRays", text
="Raycasting front/back")
883 xco
= bbxL
+ (x
* gs
)
884 zco
= bbzL
+ (z
* gs
)
885 v1
= ((xco
, rc
, zco
))
886 v2
= ((xco
, -rc
, zco
))
887 vy1
= ob
.ray_cast(v1
, v2
)
888 vy2
= ob
.ray_cast(v2
, v1
)
890 ll
.append((x
- xs
, int(vy1
[1][1] * (1 / gs
)), z
- zs
))
892 ll
.append((x
- xs
, int(vy2
[1][1] * (1 / gs
)), z
- zs
))
895 debug_prints(func
="voxelByRays", text
="Raycasting left/right")
899 yco
= bbyL
+ (y
* gs
)
900 zco
= bbzL
+ (z
* gs
)
901 v1
= ((rc
, yco
, zco
))
902 v2
= ((-rc
, yco
, zco
))
903 vx1
= ob
.ray_cast(v1
, v2
)
904 vx2
= ob
.ray_cast(v2
, v1
)
906 ll
.append((int(vx1
[1][0] * (1 / gs
)), y
- ys
, z
- zs
))
908 ll
.append((int(vx2
[1][0] * (1 / gs
)), y
- ys
, z
- zs
))
910 # add in neighbors so bolt wont go through
913 nl
= getStencil3D_26(l
[0], l
[1], l
[2])
917 debug_prints(func
="voxelByRays", text
="Added neighbors, deduping...")
918 rlist
= deDupe(ll
+ nlist
)
921 # relocate grid w/ respect gscale and bolt origin
922 # !!!need to add in obj rot/scale here somehow...
924 ((ob
.location
[0] - orig
[0]) / gs
,
925 (ob
.location
[1] - orig
[1]) / gs
,
926 (ob
.location
[2] - orig
[2]) / gs
)
929 qlist
.append((r
[0] + int(od
[0]), r
[1] + int(od
[1]), r
[2] + int(od
[2])))
934 def fakeGroundChargePlane(z
, charge
):
937 eCL
+= [(0, 0, z
, charge
)]
938 eCL
+= [(xy
, 0, z
, charge
)]
939 eCL
+= [(0, xy
, z
, charge
)]
940 eCL
+= [(-xy
, 0, z
, charge
)]
941 eCL
+= [(0, -xy
, z
, charge
)]
946 def addCharges(ll
, charge
):
947 # in: ll - [(x,y,z), (x,y,z)], charge - w
948 # out clist - [(x,y,z,w), (x,y,z,w)]
951 clist
.append((l
[0], l
[1], l
[2], charge
))
955 # algorithm functions #
958 def getGrowthProbability_KEEPFORREFERENCE(uN
, aList
):
959 # in: un -user term, clist -candidate sites, olist -candidate site charges
960 # out: list of [(xyz), pot, prob]
961 cList
= splitList(aList
, 0)
962 oList
= splitList(aList
, 1)
963 Omin
, Omax
= getLowHigh(oList
)
969 E
= notZero
# divisor for (fslg - eqn. 12)
972 Uj
= (o
- Omin
) / (Omax
- Omin
) # (fslg - eqn. 13)
975 for oi
in range(len(oList
)):
977 Ui
= (o
- Omin
) / (Omax
- Omin
)
978 Pd
= (pow(Ui
, uN
)) / E
# (fslg - eqn. 12)
985 # work in progress, trying to speed these up
986 def fslg_e13(x
, min, max, u
):
987 return pow((x
- min) / (max - min), u
)
994 def fslg_e12(x
, min, max, u
, e
):
995 return (fslg_e13(x
, min, max, u
) / e
) * 100
998 def getGrowthProbability(uN
, aList
):
999 # In: uN - user_term, cList - candidate sites, oList - candidate site charges
1001 cList
= splitList(aList
, 0)
1002 oList
= splitList(aList
, 1)
1003 Omin
, Omax
= getLowHigh(oList
)
1011 minL
= [Omin
for q
in range(len(oList
))]
1012 maxL
= [Omax
for q
in range(len(oList
))]
1013 uNL
= [uN
for q
in range(len(oList
))]
1014 E
= sum(map(fslg_e13
, oList
, minL
, maxL
, uNL
))
1015 EL
= [E
for q
in range(len(oList
))]
1016 mp
= map(fslg_e12
, oList
, minL
, maxL
, uNL
, EL
)
1024 def updatePointCharges(p
, cList
, eList
=[]):
1025 # In: pNew - new growth cell
1026 # cList - old candidate sites, eList -SAME
1027 # Out: list of new charge at candidate sites
1028 r1
= 1 / 2 # (FSLG - Eqn. 10)
1031 for oi
in range(len(cList
)):
1035 rit
= dist(c
[0], c
[1], c
[2], p
[0], p
[1], p
[2])
1036 iOe
+= (1 - (r1
/ rit
))
1038 nOiL
.append((c
, Oit
))
1043 def initialPointCharges(pList
, cList
, eList
=[]):
1044 # In: p -CHARGED CELL (XYZ), cList -candidate sites (XYZ, POT, PROB)
1045 # Out: cList -with potential calculated
1046 r1
= 1 / 2 # (FSLG - Eqn. 10)
1050 npList
.append(((p
[0], p
[1], p
[2]), 1.0))
1053 npList
.append(((e
[0], e
[1], e
[2]), e
[3]))
1060 rij
= dist(i
[0], i
[1], i
[2], j
[0][0], j
[0][1], j
[0][2])
1061 Oi
+= (1 - (r1
/ rij
)) * j
[1] # charge influence
1062 OiL
.append(((i
[0], i
[1], i
[2]), Oi
))
1067 def getCandidateSites(aList
, iList
=[]):
1068 # In: aList -(X,Y,Z) of charged cell sites, iList - insulator sites
1069 # Out: candidate list of growth sites [(X,Y,Z)]
1072 tempList
= getStencil3D_26(c
[0], c
[1], c
[2])
1074 if t
not in aList
and t
not in iList
:
1076 ncList
= deDupe(cList
)
1084 winmgr
= bpy
.context
.scene
.advanced_objects1
1085 oOB
= bpy
.data
.objects
.new('ELorigin', None)
1086 oOB
.location
= ((0, 0, 10))
1087 bpy
.context
.scene
.objects
.link(oOB
)
1089 gOB
= bpy
.data
.objects
.new('ELground', None)
1090 gOB
.empty_draw_type
= 'ARROWS'
1091 bpy
.context
.scene
.objects
.link(gOB
)
1093 cME
= makeMeshCube(1)
1094 cOB
= bpy
.data
.objects
.new('ELcloud', cME
)
1095 cOB
.location
= ((-2, 8, 12))
1096 cOB
.hide_render
= True
1097 bpy
.context
.scene
.objects
.link(cOB
)
1099 iME
= makeMeshCube(1)
1100 for v
in iME
.vertices
:
1103 v
.co
[0] = v
.co
[0] * xyl
1104 v
.co
[1] = v
.co
[1] * xyl
1105 v
.co
[2] = v
.co
[2] * zl
1106 iOB
= bpy
.data
.objects
.new('ELinsulator', iME
)
1107 iOB
.location
= ((0, 0, 5))
1108 iOB
.hide_render
= True
1109 bpy
.context
.scene
.objects
.link(iOB
)
1112 winmgr
.OOB
= 'ELorigin'
1113 winmgr
.GOB
= 'ELground'
1114 winmgr
.COB
= 'ELcloud'
1115 winmgr
.IOB
= 'ELinsulator'
1120 def checkSettings():
1122 winmgr
= bpy
.context
.scene
.advanced_objects1
1124 if winmgr
.OOB
== "":
1125 message
= "Error: no origin object selected"
1128 if winmgr
.GROUNDBOOL
and winmgr
.GOB
== "":
1129 message
= "Error: no ground object selected"
1132 if winmgr
.CLOUDBOOL
and winmgr
.COB
== "":
1133 message
= "Error: no cloud object selected"
1136 if winmgr
.IBOOL
and winmgr
.IOB
== "":
1137 message
= "Error: no insulator object selected"
1141 debug_prints(func
="checkSettings", text
=message
)
1143 # return state and the message for the operator report
1144 return check
, message
1150 winmgr
= bpy
.context
.scene
.advanced_objects1
1151 # fast simulation of laplacian growth
1152 debug_prints(func
="FSLG",
1153 text
="Go go gadget: fast simulation of laplacian growth")
1155 TSTEPS
= winmgr
.TSTEPS
1157 obORIGIN
= bpy
.context
.scene
.objects
[winmgr
.OOB
]
1158 obGROUND
= bpy
.context
.scene
.objects
[winmgr
.GOB
]
1159 winmgr
.ORIGIN
= obORIGIN
.location
1160 winmgr
.GROUNDZ
= int((obGROUND
.location
[2] - winmgr
.ORIGIN
[2]) / winmgr
.GSCALE
)
1162 # 1) insert initial charge(s) point (uses verts if mesh)
1165 if obORIGIN
.type == 'MESH':
1168 text
="Origin object is mesh, 'voxelizing' initial charges from verts"
1170 cgrid
= voxelByVertex(obORIGIN
, winmgr
.GSCALE
)
1175 text
="Cannot classify stroke from vert origins yet, no multi-mesh output"
1177 winmgr
.VMMESH
= False
1178 winmgr
.VSMESH
= True
1180 # ground charge cell / insulator lists (echargelist/iclist)
1183 if winmgr
.GROUNDBOOL
:
1184 eChargeList
= fakeGroundChargePlane(winmgr
.GROUNDZ
, winmgr
.GROUNDC
)
1186 if winmgr
.CLOUDBOOL
:
1189 text
="'Voxelizing' cloud object (could take some time)"
1191 obCLOUD
= bpy
.context
.scene
.objects
[winmgr
.COB
]
1192 eChargeListQ
= voxelByRays(obCLOUD
, winmgr
.ORIGIN
, winmgr
.GSCALE
)
1193 eChargeList
= addCharges(eChargeListQ
, winmgr
.CLOUDC
)
1196 text
="cloud object cell count", var
=len(eChargeList
)
1202 text
="'Voxelizing' insulator object (could take some time)"
1204 obINSULATOR
= bpy
.context
.scene
.objects
[winmgr
.IOB
]
1205 icList
= voxelByRays(obINSULATOR
, winmgr
.ORIGIN
, winmgr
.GSCALE
)
1209 text
="Insulator object cell count", var
=len(icList
)
1212 # 2) locate candidate sites around charge
1213 cSites
= getCandidateSites(cgrid
, icList
)
1215 # 3) calc potential at each site (eqn. 10)
1216 cSites
= initialPointCharges(cgrid
, cSites
, eChargeList
)
1220 # 1) select new growth site (eqn. 12)
1221 # get probabilities at candidate sites
1222 gProbs
= getGrowthProbability(winmgr
.BIGVAR
, cSites
)
1223 # choose new growth site based on probabilities
1224 gSitei
= weightedRandomChoice(gProbs
)
1225 gsite
= cSites
[gSitei
][0]
1227 # 2) add new point charge at growth site
1228 # add new growth cell to grid
1230 # remove new growth cell from candidate sites
1231 cSites
.remove(cSites
[gSitei
])
1233 # 3) update potential at candidate sites (eqn. 11)
1234 cSites
= updatePointCharges(gsite
, cSites
, eChargeList
)
1236 # 4) add new candidates surrounding growth site
1237 # get candidate 'stencil'
1238 ncSitesT
= getCandidateSites([gsite
], icList
)
1239 # remove candidates already in candidate list or charge grid
1241 cSplit
= splitList(cSites
, 0)
1243 if cn
not in cSplit
and cn
not in cgrid
:
1244 ncSites
.append((cn
, 0))
1246 # 5) calc potential at new candidate sites (eqn. 10)
1247 ncSplit
= splitList(ncSites
, 0)
1248 ncSites
= initialPointCharges(cgrid
, ncSplit
, eChargeList
)
1250 # add new candidate sites to candidate list
1254 # iteration complete
1255 istr1
= ':::T-STEP: ' + str(ts
) + '/' + str(TSTEPS
)
1256 istr12
= ' | GROUNDZ: ' + str(winmgr
.GROUNDZ
) + ' | '
1257 istr2
= 'CANDS: ' + str(len(cSites
)) + ' | '
1258 istr3
= 'GSITE: ' + str(gsite
)
1261 text
="Iteration complete",
1262 var
=istr1
+ istr12
+ istr2
+ istr3
1266 # early termination for ground/cloud strike
1267 if winmgr
.GROUNDBOOL
:
1268 if gsite
[2] == winmgr
.GROUNDZ
:
1272 text
="Early termination due to groundstrike"
1276 if winmgr
.CLOUDBOOL
:
1277 if gsite
in splitListCo(eChargeList
):
1281 text
="Early termination due to cloudstrike"
1289 text
="Laplacian growth loop completed",
1290 var
=str(len(cgrid
)) + " / " + str(tcRUN
)[0:5] + " Seconds"
1292 debug_prints(func
="FSLG", text
="Visualizing data")
1294 reportSTRING
= getReportString(tcRUN
)
1298 cgrid
, obORIGIN
, winmgr
.GSCALE
,
1299 winmgr
.VMMESH
, winmgr
.VSMESH
,
1300 winmgr
.VCUBE
, winmgr
.VVOX
, reportSTRING
1303 debug_prints(func
="FSLG", text
="COMPLETE")
1308 class runFSLGLoopOperator(Operator
):
1309 bl_idname
= "object.runfslg_operator"
1310 bl_label
= "run FSLG Loop Operator"
1311 bl_description
= "By The Mighty Hammer Of Thor!!!"
1313 def execute(self
, context
):
1314 # tuple - state, report text
1315 is_conditions
, message
= checkSettings()
1320 self
.report({'WARNING'}, message
+ " Operation Cancelled")
1322 return {'CANCELLED'}
1327 class setupObjectsOperator(Operator
):
1328 bl_idname
= "object.setup_objects_operator"
1329 bl_label
= "Setup Objects Operator"
1330 bl_description
= "Create origin/ground/cloud/insulator objects"
1332 def execute(self
, context
):
1338 class OBJECT_PT_fslg(Panel
):
1339 bl_label
= "Laplacian Lightning"
1340 bl_space_type
= "VIEW_3D"
1341 bl_region_type
= "TOOLS"
1342 bl_context
= "objectmode"
1343 bl_category
= "Create"
1344 bl_options
= {'DEFAULT_CLOSED'}
1346 def draw(self
, context
):
1347 layout
= self
.layout
1348 winmgr
= context
.scene
.advanced_objects1
1350 col
= layout
.column(align
=True)
1351 col
.prop(winmgr
, "TSTEPS")
1352 col
.prop(winmgr
, "GSCALE")
1353 col
.prop(winmgr
, "BIGVAR")
1355 col
= layout
.column()
1356 col
.operator("object.setup_objects_operator", text
="Create Setup objects")
1357 col
.label("Origin object")
1358 col
.prop_search(winmgr
, "OOB", context
.scene
, "objects")
1362 col
.prop(winmgr
, "GROUNDBOOL")
1363 if winmgr
.GROUNDBOOL
:
1364 col
.prop_search(winmgr
, "GOB", context
.scene
, "objects")
1365 col
.prop(winmgr
, "GROUNDC")
1369 col
.prop(winmgr
, "CLOUDBOOL")
1370 if winmgr
.CLOUDBOOL
:
1371 col
.prop_search(winmgr
, "COB", context
.scene
, "objects")
1372 col
.prop(winmgr
, "CLOUDC")
1376 col
.prop(winmgr
, "IBOOL")
1378 col
.prop_search(winmgr
, "IOB", context
.scene
, "objects")
1380 col
= layout
.column()
1381 col
.operator("object.runfslg_operator",
1382 text
="Generate Lightning", icon
="RNDCURVE")
1384 row
= layout
.row(align
=True)
1385 row
.prop(winmgr
, "VMMESH", toggle
=True)
1386 row
.prop(winmgr
, "VSMESH", toggle
=True)
1387 row
.prop(winmgr
, "VCUBE", toggle
=True)
1390 def getReportString(rtime
):
1391 winmgr
= bpy
.context
.scene
.advanced_objects1
1392 rSTRING1
= 't:' + str(winmgr
.TSTEPS
) + ',sc:' + str(winmgr
.GSCALE
)[0:4] + ',uv:' + str(winmgr
.BIGVAR
)[0:4] + ','
1393 rSTRING2
= 'ori:' + str(winmgr
. ORIGIN
[0]) + '/' + str(winmgr
. ORIGIN
[1]) + '/' + str(winmgr
. ORIGIN
[2]) + ','
1394 rSTRING3
= 'gz:' + str(winmgr
.GROUNDZ
) + ',gc:' + str(winmgr
.GROUNDC
) + ',rtime:' + str(int(rtime
))
1395 return rSTRING1
+ rSTRING2
+ rSTRING3
1398 def addReportProp(ob
, str):
1399 bpy
.types
.Object
.FSLG_REPORT
= bpy
.props
.StringProperty(
1400 name
='fslg_report', default
='')
1401 ob
.FSLG_REPORT
= str
1405 bpy
.utils
.register_class(runFSLGLoopOperator
)
1406 bpy
.utils
.register_class(setupObjectsOperator
)
1407 bpy
.utils
.register_class(OBJECT_PT_fslg
)
1411 bpy
.utils
.unregister_class(runFSLGLoopOperator
)
1412 bpy
.utils
.unregister_class(setupObjectsOperator
)
1413 bpy
.utils
.unregister_class(OBJECT_PT_fslg
)
1416 if __name__
== "__main__":
1421 # Benchmarks Function
1424 debug_prints(func
="BENCH", text
="BEGIN BENCHMARK")
1429 for x
in range(tsize
):
1430 for y
in range(tsize
):
1431 for z
in range(tsize
):
1432 tlist
.append((x
, y
, z
))
1433 tlist
.append((x
, y
, z
))
1441 debug_prints(func
="BENCH", text
="SETUP TIME", var
=btRUNa
)
1442 debug_prints(func
="BENCH", text
="BENCHMARK TIME", var
=btRUNb
)
1445 "GRIDSIZE: ", tsize
, ' - ', tsize
* tsize
* tsize