53ac55ac08b5909e7e839d44e7a9987dadc5b143
[blender-addons-contrib.git] / add_mesh_space_tree / __init__.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  SCA Tree Generator, a Blender addon
4 #  (c) 2013 Michel J. Anders (varkenvarken)
5 #
6 #  This program is free software; you can redistribute it and/or
7 #  modify it under the terms of the GNU General Public License
8 #  as published by the Free Software Foundation; either version 2
9 #  of the License, or (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License
17 #  along with this program; if not, write to the Free Software Foundation,
18 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #
20 # ##### END GPL LICENSE BLOCK #####
21
22 # <pep8 compliant>
23
24 bl_info = {
25     "name": "SCA Tree Generator",
26     "author": "michel anders (varkenvarken)",
27     "version": (0, 1, 2),
28     "blender": (2, 66, 0),
29     "location": "View3D > Add > Mesh",
30     "description": "Adds a tree created with the space colonization algorithm starting at the 3D cursor",
31     "warning": "",
32     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Add_Mesh/Add_Space_Tree",
33     "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
34     "category": "Add Mesh"}
35
36 from time import time
37 from random import random, gauss
38 from functools import partial
39 from math import sin, cos
40
41 import bpy
42 from bpy.props import FloatProperty, IntProperty, BoolProperty, EnumProperty
43 from mathutils import Vector, Euler, Matrix, Quaternion
44
45 from .simplefork import simplefork, simplefork2, quadfork, bridgequads  # simple skinning algorithm building blocks
46 from .sca import SCA, Branchpoint  # the core class that implements the space colonization algorithm and the definition of a segment
47 from .timer import Timer
48
49
50 def availableGroups(self, context):
51     return [(name, name, name, n) for n, name in enumerate(bpy.data.groups.keys())]
52
53
54 def availableGroupsOrNone(self, context):
55     groups = [('None', 'None', 'None', 1)]
56     return groups + [(name, name, name, n + 1) for n, name in enumerate(bpy.data.groups.keys())]
57
58
59 def availableObjects(self, context):
60     return [(name, name, name, n + 1) for n, name in enumerate(bpy.data.objects.keys())]
61
62
63 def ellipsoid(r=5, rz=5, p=Vector((0, 0, 8)), taper=0):
64     r2 = r * r
65     z2 = rz * rz
66     if rz > r:
67         r = rz
68     while True:
69         x = (random() * 2 - 1) * r
70         y = (random() * 2 - 1) * r
71         z = (random() * 2 - 1) * r
72         f = (z + r) / (2 * r)
73         f = 1 + f * taper if taper >= 0 else (1 - f) * -taper
74         if f * x * x / r2 + f * y * y / r2 + z * z / z2 <= 1:
75             yield p + Vector((x, y, z))
76
77
78 def pointInsideMesh(pointrelativetocursor, ob):
79     # adapted from http://blenderartists.org/forum/showthread.php?195605-Detecting-if-a-point-is-inside-a-mesh-2-5-API&p=1691633&viewfull=1#post1691633
80     mat = ob.matrix_world.inverted()
81     orig = mat * (pointrelativetocursor + bpy.context.scene.cursor_location)
82     count = 0
83     axis = Vector((0, 0, 1))
84     while True:
85         location, normal, index = ob.ray_cast(orig, orig + axis * 10000.0)
86         if index == -1:
87             break
88         count += 1
89         orig = location + axis * 0.00001
90     if count % 2 == 0:
91         return False
92     return True
93
94
95 def ellipsoid2(rxy=5, rz=5, p=Vector((0, 0, 8)), surfacebias=1, topbias=1):
96     while True:
97         phi = 6.283 * random()
98         theta = 3.1415 * (random() - 0.5)
99         r = random() ** (surfacebias / 2)
100         x = r * rxy * cos(theta) * cos(phi)
101         y = r * rxy * cos(theta) * sin(phi)
102         st = sin(theta)
103         st = (((st + 1) / 2) ** topbias) * 2 - 1
104         z = r * rz * st
105         #print(">>>%.2f %.2f %.2f "%(x,y,z))
106         m = p + Vector((x, y, z))
107         reject = False
108         for ob in bpy.context.selected_objects:
109             # probably we should check if each object is a mesh
110             if pointInsideMesh(m, ob):
111                 reject = True
112                 break
113         if not reject:
114             yield m
115
116
117 def halton3D(index):
118     """
119     return a quasi random 3D vector R3 in [0,1].
120     each component is based on a halton sequence.
121     quasi random is good enough for our purposes and is
122     more evenly distributed then pseudo random sequences.
123     See en.m.wikipedia.org/wiki/Halton_sequence
124     """
125
126     def halton(index, base):
127         result = 0
128         f = 1.0 / base
129         I = index
130         while I > 0:
131             result += f * (I % base)
132             I = int(I / base)
133             f /= base
134         return result
135     return Vector((halton(index, 2), halton(index, 3), halton(index, 5)))
136
137
138 def insidegroup(pointrelativetocursor, group):
139     if bpy.data.groups.find(group) < 0:
140         return False
141     for ob in bpy.data.groups[group].objects:
142         if pointInsideMesh(pointrelativetocursor, ob):
143             return True
144     return False
145
146
147 def groupdistribution(crowngroup, shadowgroup=None, seed=0, size=Vector((1, 1, 1)), pointrelativetocursor=Vector((0, 0, 0))):
148     if crowngroup == shadowgroup:
149         shadowgroup = None  # safeguard otherwise every marker would be rejected
150     nocrowngroup = bpy.data.groups.find(crowngroup) < 0
151     noshadowgroup = (shadowgroup is None) or (bpy.data.groups.find(shadowgroup) < 0) or (shadowgroup == 'None')
152     index = 100 + seed
153     nmarkers = 0
154     nyield = 0
155     while True:
156         nmarkers += 1
157         v = halton3D(index)
158         v[0] *= size[0]
159         v[1] *= size[1]
160         v[2] *= size[2]
161         v += pointrelativetocursor
162         index += 1
163         insidecrown = nocrowngroup or insidegroup(v, crowngroup)
164         outsideshadow = noshadowgroup or not insidegroup(v, shadowgroup)
165         # if shadowgroup overlaps all or a significant part of the crowngroup
166         # no markers will be yielded and we would be in an endless loop.
167         # so if we yield too few correct markers we start yielding them anyway.
168         lowyieldrate = (nmarkers > 200) and (nyield / nmarkers < 0.01)
169         if (insidecrown and outsideshadow) or lowyieldrate:
170             nyield += 1
171             yield v
172
173
174 def groupExtends(group):
175     """
176     return a size,minimum tuple both Vector elements, describing the size and position
177     of the bounding box in world space that encapsulates all objects in a group.
178     """
179     bb = []
180     if bpy.data.groups.find(group) >= 0:
181         for ob in bpy.data.groups[group].objects:
182             rot = ob.matrix_world.to_quaternion()
183             scale = ob.matrix_world.to_scale()
184             translate = ob.matrix_world.translation
185             for v in ob.bound_box:  # v is not a vector but an array of floats
186                 p = ob.matrix_world * Vector(v[0:3])
187                 bb.extend(p[0:3])
188     mx = Vector((max(bb[0::3]), max(bb[1::3]), max(bb[2::3])))
189     mn = Vector((min(bb[0::3]), min(bb[1::3]), min(bb[2::3])))
190     return mx - mn, mn
191
192
193 def createLeaves(tree, probability=0.5, size=0.5, randomsize=0.1, randomrot=0.1, maxconnections=2, bunchiness=1.0, connectoffset=-0.1):
194     p = bpy.context.scene.cursor_location
195
196     verts = []
197     faces = []
198     c1 = Vector((connectoffset, -size / 2, 0))
199     c2 = Vector((size+connectoffset, -size / 2, 0))
200     c3 = Vector((size+connectoffset, size / 2, 0))
201     c4 = Vector((connectoffset, size / 2, 0))
202     t = gauss(1.0 / probability, 0.1)
203     bpswithleaves = 0
204     for bp in tree.branchpoints:
205         if bp.connections < maxconnections:
206
207             dv = tree.branchpoints[bp.parent].v - bp.v if bp.parent else Vector((0, 0, 0))
208             dvp = Vector((0, 0, 0))
209
210             bpswithleaves += 1
211             nleavesonbp = 0
212             while t < bpswithleaves:
213                 nleavesonbp += 1
214                 rx = (random() - 0.5) * randomrot * 6.283  # TODO vertical tilt in direction of tropism
215                 ry = (random() - 0.5) * randomrot * 6.283
216                 rot = Euler((rx, ry, random() * 6.283), 'ZXY')
217                 scale = 1 + (random() - 0.5) * randomsize
218                 v = c1.copy()
219                 v.rotate(rot)
220                 verts.append(v * scale + bp.v + dvp)
221                 v = c2.copy()
222                 v.rotate(rot)
223                 verts.append(v * scale + bp.v + dvp)
224                 v = c3.copy()
225                 v.rotate(rot)
226                 verts.append(v * scale + bp.v + dvp)
227                 v = c4.copy()
228                 v.rotate(rot)
229                 verts.append(v * scale + bp.v + dvp)
230                 n = len(verts)
231                 faces.append((n - 1, n - 4, n - 3, n - 2))
232                 t += gauss(1.0 / probability, 0.1)                      # this is not the best choice of distribution because we might get negative values especially if sigma is large
233                 dvp = nleavesonbp * (dv / (probability ** bunchiness))  # TODO add some randomness to the offset
234
235     mesh = bpy.data.meshes.new('Leaves')
236     mesh.from_pydata(verts, [], faces)
237     mesh.update(calc_edges=True)
238     mesh.uv_textures.new()
239     return mesh
240
241
242 def createMarkers(tree, scale=0.05):
243     #not used as markers are parented to tree object that is created at the cursor position
244     #p=bpy.context.scene.cursor_location
245
246     verts = []
247     faces = []
248
249     tetraeder = [Vector((-1, 1, -1)), Vector((1, -1, -1)), Vector((1, 1, 1)), Vector((-1, -1, 1))]
250     tetraeder = [v * scale for v in tetraeder]
251     tfaces = [(0, 1, 2), (0, 1, 3), (1, 2, 3), (0, 3, 2)]
252
253     for ep in tree.endpoints:
254         verts.extend([ep + v for v in tetraeder])
255         n = len(faces)
256         faces.extend([(f1 + n, f2 + n, f3 + n) for f1, f2, f3 in tfaces])
257
258     mesh = bpy.data.meshes.new('Markers')
259     mesh.from_pydata(verts, [], faces)
260     mesh.update(calc_edges=True)
261     return mesh
262
263
264 def createObjects(tree, parent=None, objectname=None, probability=0.5, size=0.5, randomsize=0.1, randomrot=0.1, maxconnections=2, bunchiness=1.0):
265
266     if (parent is None) or (objectname is None) or (objectname == 'None'):
267         return
268
269     # not necessary, we parent the new objects: p=bpy.context.scene.cursor_location
270
271     theobject = bpy.data.objects[objectname]
272
273     t = gauss(1.0 / probability, 0.1)
274     bpswithleaves = 0
275     for bp in tree.branchpoints:
276         if bp.connections < maxconnections:
277
278             dv = tree.branchpoints[bp.parent].v - bp.v if bp.parent else Vector((0, 0, 0))
279             dvp = Vector((0, 0, 0))
280
281             bpswithleaves += 1
282             nleavesonbp = 0
283             while t < bpswithleaves:
284                 nleavesonbp += 1
285                 rx = (random() - 0.5) * randomrot * 6.283  # TODO vertical tilt in direction of tropism
286                 ry = (random() - 0.5) * randomrot * 6.283
287                 rot = Euler((rx, ry, random() * 6.283), 'ZXY')
288                 scale = size + (random() - 0.5) * randomsize
289
290                 # add new object and parent it
291                 obj = bpy.data.objects.new(objectname, theobject.data)
292                 obj.location = bp.v + dvp
293                 obj.rotation_mode = 'ZXY'
294                 obj.rotation_euler = rot[:]
295                 obj.scale = [scale, scale, scale]
296                 obj.parent = parent
297                 bpy.context.scene.objects.link(obj)
298
299                 t += gauss(1.0 / probability, 0.1)                      # this is not the best choice of distribution because we might get negative values especially if sigma is large
300                 dvp = nleavesonbp * (dv / (probability ** bunchiness))  # TODO add some randomness to the offset
301
302
303 def vertextend(v, dv):
304     n = len(v)
305     v.extend(dv)
306     return tuple(range(n, n + len(dv)))
307
308
309 def vertcopy(loopa, v, p):
310     dv = [v[i] + p for i in loopa]
311     #print(loopa,p,dv)
312     return vertextend(v, dv)
313
314
315 def bend(p0, p1, p2, loopa, loopb, verts):
316     # will extend this with a tri centered at p0
317     #print('bend')
318     return bridgequads(loopa, loopb, verts)
319
320
321 def extend(p0, p1, p2, loopa, verts):
322     # will extend this with a tri centered at p0
323     #print('extend')
324     #print(p0,p1,p2,[verts[i] for i in loopa])
325
326     # both difference point upward, we extend to the second
327     d1 = p1 - p0
328     d2 = p0 - p2
329     p = (verts[loopa[0]] + verts[loopa[1]] + verts[loopa[2]] + verts[loopa[3]]) / 4
330     a = d1.angle(d2, 0)
331     if abs(a) < 0.05:
332         #print('small angle')
333         loopb = vertcopy(loopa, verts, p0 - d2 / 2 - p)
334         # all verts in loopb are displaced the same amount so no need to find the minimum distance
335         n = 4
336         return ([(loopa[(i) % n], loopa[(i + 1) % n], loopb[(i + 1) % n], loopb[(i) % n]) for i in range(n)], loopa, loopb)
337
338     r = d2.cross(d1)
339     q = Quaternion(r, -a)
340     dverts = [verts[i] - p for i in loopa]
341     #print('large angle',dverts,'axis',r)
342     for dv in dverts:
343         dv.rotate(q)
344     #print('rotated',dverts)
345     for dv in dverts:
346         dv += (p0 - d2 / 2)
347     #print('moved',dverts)
348     loopb = vertextend(verts, dverts)
349     # none of the verts in loopb are rotated so no need to find the minimum distance
350     n = 4
351     return ([(loopa[(i) % n], loopa[(i + 1) % n], loopb[(i + 1) % n], loopb[(i) % n]) for i in range(n)], loopa, loopb)
352
353
354 def nonfork(bp, parent, apex, verts, p, branchpoints):
355     #print('nonfork bp    ',bp.index,bp.v,bp.loop if hasattr(bp,'loop') else None)
356     #print('nonfork parent',parent.index,parent.v,parent.loop if hasattr(parent,'loop') else None)
357     #print('nonfork apex  ',apex.index,apex.v,apex.loop if hasattr(apex,'loop') else None)
358     if hasattr(bp, 'loop'):
359         if hasattr(apex, 'loop'):
360             #print('nonfork bend bp->apex')
361             return bend(bp.v + p, parent.v + p, apex.v + p, bp.loop, apex.loop, verts)
362         else:
363             #print('nonfork extend bp->apex')
364             faces, loop1, loop2 = extend(bp.v + p, parent.v + p, apex.v + p, bp.loop, verts)
365             apex.loop = loop2
366             return faces, loop1, loop2
367     else:
368         if hasattr(parent, 'loop'):
369             #print('nonfork extend from bp->parent')
370             #faces,loop1,loop2 =  extend(bp.v+p, apex.v+p, parent.v+p, parent.loop, verts)
371             if parent.parent is None:
372                 return None, None, None
373             grandparent = branchpoints[parent.parent]
374             faces, loop1, loop2 = extend(grandparent.v + p, parent.v + p, bp.v + p, parent.loop, verts)
375             bp.loop = loop2
376             return faces, loop1, loop2
377         else:
378             #print('nonfork no loop')
379             # neither parent nor apex already have a loop calculated
380             # will fill this later ...
381             return None, None, None
382
383
384 def endpoint(bp, parent, verts, p):
385     # extrapolate to tip of branch. we do not close the tip for now
386     faces, loop1, loop2 = extend(bp.v + p, parent.v + p, bp.v + (bp.v - parent.v) + p, bp.loop, verts)
387     return faces, loop1, loop2
388
389
390 def root(bp, apex, verts, p):
391     # extrapolate non-forked roots
392     faces, loop1, loop2 = extend(bp.v + p, bp.v - (apex.v - bp.v) + p, apex.v + p, bp.loop, verts)
393     apex.loop = loop2
394     return faces, loop1, loop2
395
396
397 def skin(aloop, bloop, faces):
398     n = len(aloop)
399     for i in range(n):
400         faces.append((aloop[i], aloop[(i + 1) % n], bloop[(i + 1) % n], bloop[i]))
401
402
403 def createGeometry(tree, power=0.5, scale=0.01, addleaves=False, pleaf=0.5, leafsize=0.5, leafrandomsize=0.1, leafrandomrot=0.1,
404     nomodifiers=True, skinmethod='NATIVE', subsurface=False,
405     maxleafconnections=2, bleaf=1.0, connectoffset=-0.1,
406     timeperf=True):
407
408     timings = Timer()
409
410     p = bpy.context.scene.cursor_location
411     verts = []
412     edges = []
413     faces = []
414     radii = []
415     roots = set()
416
417     # Loop over all branchpoints and create connected edges
418     for n, bp in enumerate(tree.branchpoints):
419         verts.append(bp.v + p)
420         radii.append(bp.connections)
421         bp.index = n
422         if not (bp.parent is None):
423             edges.append((len(verts) - 1, bp.parent))
424         else:
425             nv = len(verts)
426             roots.add(nv - 1)
427
428     timings.add('skeleton')
429
430     # native skinning method
431     if nomodifiers is False and skinmethod == 'NATIVE':
432         # add a quad edge loop to all roots
433         for r in roots:
434             rootp = verts[r]
435             nv = len(verts)
436             radius = 0.7071 * ((tree.branchpoints[r].connections + 1) ** power) * scale
437             verts.extend([rootp + Vector((-radius, -radius, 0)), rootp + Vector((radius, -radius, 0)), rootp + Vector((radius, radius, 0)), rootp + Vector((-radius, radius, 0))])
438             tree.branchpoints[r].loop = (nv, nv + 1, nv + 2, nv + 3)
439             #print('root verts',tree.branchpoints[r].loop)
440             #faces.append((nv,nv+1,nv+2))
441             edges.extend([(nv, nv + 1), (nv + 1, nv + 2), (nv + 2, nv + 3), (nv + 3, nv)])
442
443         # skin all forked branchpoints, no attempt is yet made to adjust the radius
444         forkfork = set()
445         for bpi, bp in enumerate(tree.branchpoints):
446             if not(bp.apex is None or bp.shoot is None):
447                 apex = tree.branchpoints[bp.apex]
448                 shoot = tree.branchpoints[bp.shoot]
449                 p0 = bp.v
450                 r0 = ((bp.connections + 1) ** power) * scale
451                 p2 = apex.v
452                 r2 = ((apex.connections + 1) ** power) * scale
453                 p3 = shoot.v
454                 r3 = ((shoot.connections + 1) ** power) * scale
455
456                 if bp.parent is not None:
457                     parent = tree.branchpoints[bp.parent]
458                     p1 = parent.v
459                     r1 = (parent.connections ** power) * scale
460                 else:
461                     p1 = p0 - (p2 - p0)
462                     r1 = r0
463
464                 skinverts, skinfaces = quadfork(p0, p1, p2, p3, r0, r1, r2, r3)
465                 nv = len(verts)
466                 verts.extend([v + p for v in skinverts])
467                 faces.extend([tuple(v + nv for v in f) for f in skinfaces])
468
469                 # the vertices of the quads at the end of the internodes are returned as the first 12 vertices of a total of 22
470                 # we store them for reuse by non-forked internodes but first check if we have a fork to fork connection
471                 nv = len(verts)
472                 if hasattr(bp, 'loop') and not (bpi in forkfork):  # already assigned by another fork
473                     faces.extend(bridgequads(bp.loop, [nv - 22, nv - 21, nv - 20, nv - 19], verts)[0])
474                     forkfork.add(bpi)
475                 else:
476                     bp.loop = [nv - 22, nv - 21, nv - 20, nv - 19]
477
478                 if hasattr(apex, 'loop') and not (bp.apex in forkfork):  # already assigned by another fork but not yet skinned
479                     faces.extend(bridgequads(apex.loop, [nv - 18, nv - 17, nv - 16, nv - 15], verts)[0])
480                     forkfork.add(bp.apex)
481                 else:
482                     apex.loop = [nv - 18, nv - 17, nv - 16, nv - 15]
483
484                 if hasattr(shoot, 'loop') and not (bp.shoot in forkfork):  # already assigned by another fork but not yet skinned
485                     faces.extend(bridgequads(shoot.loop, [nv - 14, nv - 13, nv - 12, nv - 11], verts)[0])
486                     forkfork.add(bp.shoot)
487                 else:
488                     shoot.loop = [nv - 14, nv - 13, nv - 12, nv - 11]
489
490         # skin the roots that are not forks
491         for r in roots:
492             bp = tree.branchpoints[r]
493             if bp.apex is not None and bp.parent is None and bp.shoot is None:
494                 bfaces, apexloop, parentloop = root(bp, tree.branchpoints[bp.apex], verts, p)
495                 if bfaces is not None:
496                     faces.extend(bfaces)
497
498         # skin all non-forking branchpoints, that is those not a root or and endpoint
499         skinnednonforks = set()
500         start = -1
501         while(start != len(skinnednonforks)):
502             start = len(skinnednonforks)
503             #print('-'*20,start)
504             for bp in tree.branchpoints:
505                 if bp.shoot is None and not (bp.parent is None or bp.apex is None or bp in skinnednonforks):
506                     bfaces, apexloop, parentloop = nonfork(bp, tree.branchpoints[bp.parent], tree.branchpoints[bp.apex], verts, p, tree.branchpoints)
507                     if bfaces is not None:
508                         #print(bfaces,apexloop,parentloop)
509                         faces.extend(bfaces)
510                         skinnednonforks.add(bp)
511
512         # skin endpoints
513         for bp in tree.branchpoints:
514             if bp.apex is None and bp.parent is not None:
515                 bfaces, apexloop, parentloop = endpoint(bp, tree.branchpoints[bp.parent], verts, p)
516                 if bfaces is not None:
517                     faces.extend(bfaces)
518     # end of native skinning section
519     timings.add('nativeskin')
520
521     # create the tree mesh
522     mesh = bpy.data.meshes.new('Tree')
523     mesh.from_pydata(verts, edges, faces)
524     mesh.update(calc_edges=True)
525
526     # create the tree object an make it the only selected and active object in the scene
527     obj_new = bpy.data.objects.new(mesh.name, mesh)
528     base = bpy.context.scene.objects.link(obj_new)
529     for ob in bpy.context.scene.objects:
530         ob.select = False
531     base.select = True
532     bpy.context.scene.objects.active = obj_new
533     bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
534
535     timings.add('createmesh')
536
537     # add a subsurf modifier to smooth the branches
538     if nomodifiers is False:
539         if subsurface:
540             bpy.ops.object.modifier_add(type='SUBSURF')
541             bpy.context.active_object.modifiers[0].levels = 1
542             bpy.context.active_object.modifiers[0].render_levels = 1
543
544         # add a skin modifier
545         if skinmethod == 'BLENDER':
546             bpy.ops.object.modifier_add(type='SKIN')
547             bpy.context.active_object.modifiers[-1].use_smooth_shade = True
548             bpy.context.active_object.modifiers[-1].use_x_symmetry = True
549             bpy.context.active_object.modifiers[-1].use_y_symmetry = True
550             bpy.context.active_object.modifiers[-1].use_z_symmetry = True
551
552             skinverts = bpy.context.active_object.data.skin_vertices[0].data
553
554             for i, v in enumerate(skinverts):
555                 v.radius = [(radii[i] ** power) * scale, (radii[i] ** power) * scale]
556                 if i in roots:
557                     v.use_root = True
558
559             # add an extra subsurf modifier to smooth the skin
560             bpy.ops.object.modifier_add(type='SUBSURF')
561             bpy.context.active_object.modifiers[-1].levels = 1
562             bpy.context.active_object.modifiers[-1].render_levels = 2
563
564     timings.add('modifiers')
565
566     # create the leaves object
567     if addleaves:
568         mesh = createLeaves(tree, pleaf, leafsize, leafrandomsize, leafrandomrot, maxleafconnections, bleaf, connectoffset)
569         obj_leaves = bpy.data.objects.new(mesh.name, mesh)
570         base = bpy.context.scene.objects.link(obj_leaves)
571         obj_leaves.parent = obj_new
572         bpy.context.scene.objects.active = obj_leaves
573         bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
574         bpy.context.scene.objects.active = obj_new
575
576     timings.add('leaves')
577
578     if timeperf:
579         print(timings)
580
581     return obj_new
582
583
584 class SCATree(bpy.types.Operator):
585     bl_idname = "mesh.sca_tree"
586     bl_label = "SCATree"
587     bl_options = {'REGISTER', 'UNDO', 'PRESET'}
588
589     internodeLength = FloatProperty(name="Internode Length",
590                     description="Internode length in Blender Units",
591                     default=0.75,
592                     min=0.01,
593                     soft_max=3.0,
594                     subtype='DISTANCE',
595                     unit='LENGTH')
596     killDistance = FloatProperty(name="Kill Distance",
597                     description="Kill Distance as a multiple of the internode length",
598                     default=3,
599                     min=0.01,
600                     soft_max=100.0)
601     influenceRange = FloatProperty(name="Influence Range",
602                     description="Influence Range as a multiple of the internode length",
603                     default=15,
604                     min=0.01,
605                     soft_max=100.0)
606     tropism = FloatProperty(name="Tropism",
607                     description="The tendency of branches to bend up or down",
608                     default=0,
609                     min=-1.0,
610                     soft_max=1.0)
611     power = FloatProperty(name="Power",
612                     description="Tapering power of branch connections",
613                     default=0.3,
614                     min=0.01,
615                     soft_max=1.0)
616     scale = FloatProperty(name="Scale",
617                     description="Branch size",
618                     default=0.01,
619                     min=0.0001,
620                     soft_max=1.0)
621
622     # the group related properties are not saved as presets because on reload no groups with the same names might exist, causing an exception
623     useGroups = BoolProperty(name="Use object groups",
624                     options={'ANIMATABLE', 'SKIP_SAVE'},
625                     description="Use groups of objects to specify marker distribution",
626                     default=False)
627
628     crownGroup = EnumProperty(items=availableGroups,
629                     options={'ANIMATABLE', 'SKIP_SAVE'},
630                     name='Crown Group',
631                     description='Group of objects that specify crown shape')
632
633     shadowGroup = EnumProperty(items=availableGroupsOrNone,
634                     options={'ANIMATABLE', 'SKIP_SAVE'},
635                     name='Shadow Group',
636                     description='Group of objects subtracted from the crown shape')
637
638     exclusionGroup = EnumProperty(items=availableGroupsOrNone,
639                     options={'ANIMATABLE', 'SKIP_SAVE'},
640                     name='Exclusion Group',
641                     description='Group of objects that will not be penetrated by growing branches')
642
643     useTrunkGroup = BoolProperty(name="Use trunk group",
644                     options={'ANIMATABLE', 'SKIP_SAVE'},
645                     description="Use the locations of a group of objects to specify trunk starting points instead of 3d cursor",
646                     default=False)
647
648     trunkGroup = EnumProperty(items=availableGroups,
649                     options={'ANIMATABLE', 'SKIP_SAVE'},
650                     name='Trunk Group',
651                     description='Group of objects whose locations specify trunk starting points')
652
653     crownSize = FloatProperty(name="Crown Size",
654                     description="Crown size",
655                     default=5,
656                     min=1,
657                     soft_max=29)
658     crownShape = FloatProperty(name="Crown Shape",
659                     description="Crown shape",
660                     default=1,
661                     min=0.2,
662                     soft_max=5)
663     crownOffset = FloatProperty(name="Crown Offset",
664                     description="Crown offset (the length of the bole)",
665                     default=3,
666                     min=0,
667                     soft_max=20.0)
668     surfaceBias = FloatProperty(name="Surface Bias",
669                     description="Surface bias (how much markers are favored near the surface)",
670                     default=1,
671                     min=-10,
672                     soft_max=10)
673     topBias = FloatProperty(name="Top Bias",
674                     description="Top bias (how much markers are favored near the top)",
675                     default=1,
676                     min=-10,
677                     soft_max=10)
678     randomSeed = IntProperty(name="Random Seed",
679                     description="The seed governing random generation",
680                     default=0,
681                     min=0)
682     maxIterations = IntProperty(name="Maximum Iterations",
683                     description="The maximum number of iterations allowed for tree generation",
684                     default=40,
685                     min=0)
686     numberOfEndpoints = IntProperty(name="Number of Endpoints",
687                     description="The number of endpoints generated in the growing volume",
688                     default=100,
689                     min=0)
690     newEndPointsPer1000 = IntProperty(name="Number of new Endpoints",
691                     description="The number of new endpoints generated in the growing volume per thousand iterations",
692                     default=0,
693                     min=0)
694     maxTime = FloatProperty(name="Maximum Time",
695                     description=("The maximum time to run the generation for "
696                                 "in seconds/generation (0.0 = Disabled). Currently ignored"),
697                     default=0.0,
698                     min=0.0,
699                     soft_max=10)
700     pLeaf = FloatProperty(name="Leaves per internode",
701                     description=("The average number of leaves per internode"),
702                     default=0.5,
703                     min=0.0,
704                     soft_max=4)
705     bLeaf = FloatProperty(name="Leaf clustering",
706                     description=("How much leaves cluster to the end of the internode"),
707                     default=1,
708                     min=1,
709                     soft_max=4)
710     leafSize = FloatProperty(name="Leaf Size",
711                     description=("The leaf size"),
712                     default=0.5,
713                     min=0.0,
714                     soft_max=1)
715     leafRandomSize = FloatProperty(name="Leaf Random Size",
716                     description=("The amount of randomness to add to the leaf size"),
717                     default=0.1,
718                     min=0.0,
719                     soft_max=10)
720     leafRandomRot = FloatProperty(name="Leaf Random Rotation",
721                     description=("The amount of random rotation to add to the leaf"),
722                     default=0.1,
723                     min=0.0,
724                     soft_max=1)
725     connectoffset = FloatProperty(name="Connect Offset",
726                     description=("Offset of leaf to twig"),
727                     default=-0.1)
728     leafMaxConnections = IntProperty(name="Max Connections",
729                     description="The maximum number of connections of an internode elegible for a leaf",
730                     default=2,
731                     min=0)
732     addLeaves = BoolProperty(name="Add Leaves", default=False)
733
734     objectName = EnumProperty(items=availableObjects,
735                     options={'ANIMATABLE', 'SKIP_SAVE'},
736                     name='Object Name',
737                     description='Name of additional objects to duplicate at the branchpoints')
738     pObject = FloatProperty(name="Objects per internode",
739                     description=("The average number of objects per internode"),
740                     default=0.3,
741                     min=0.0,
742                     soft_max=1)
743     bObject = FloatProperty(name="Object clustering",
744                     description=("How much objects cluster to the end of the internode"),
745                     default=1,
746                     min=1,
747                     soft_max=4)
748     objectSize = FloatProperty(name="Object Size",
749                     description=("The object size"),
750                     default=1,
751                     min=0.0,
752                     soft_max=2)
753     objectRandomSize = FloatProperty(name="Object Random Size",
754                     description=("The amount of randomness to add to the object size"),
755                     default=0.1,
756                     min=0.0,
757                     soft_max=10)
758     objectRandomRot = FloatProperty(name="Object Random Rotation",
759                     description=("The amount of random rotation to add to the object"),
760                     default=0.1,
761                     min=0.0,
762                     soft_max=1)
763     objectMaxConnections = IntProperty(name="Max Connections for Object",
764                     description="The maximum number of connections of an internode elegible for a object",
765                     default=1,
766                     min=0)
767     addObjects = BoolProperty(name="Add Objects", default=False)
768
769     updateTree = BoolProperty(name="Update Tree", default=False)
770
771     noModifiers = BoolProperty(name="No Modifers", default=True)
772     subSurface = BoolProperty(name="Sub Surface", default=False, description="Add subsurface modifier to trunk skin")
773     skinMethod = EnumProperty(items=[('NATIVE', 'Native', 'Built in skinning method', 1), ('BLENDER', 'Skin modifier', 'Use Blenders skin modifier', 2)],
774                     options={'ANIMATABLE', 'SKIP_SAVE'},
775                     name='Skinning method',
776                     description='How to add a surface to the trunk skeleton')
777
778     showMarkers = BoolProperty(name="Show Markers", default=False)
779     markerScale = FloatProperty(name="Marker Scale",
780                     description=("The size of the markers"),
781                     default=0.05,
782                     min=0.001,
783                     soft_max=0.2)
784     timePerformance = BoolProperty(name="Time performance", default=False, description="Show duration of generation steps on console")
785
786     @classmethod
787     def poll(self, context):
788         # Check if we are in object mode
789         return context.mode == 'OBJECT'
790
791     def execute(self, context):
792
793         if not self.updateTree:
794             return {'PASS_THROUGH'}
795
796         timings = Timer()
797
798         # necessary otherwize ray casts toward these objects may fail. However if nothing is selected, we get a runtime error ...
799         try:
800             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
801             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
802         except RuntimeError:
803             pass
804
805         if self.useGroups:
806             size, minp = groupExtends(self.crownGroup)
807             volumefie = partial(groupdistribution, self.crownGroup, self.shadowGroup, self.randomSeed, size, minp - bpy.context.scene.cursor_location)
808         else:
809             volumefie = partial(ellipsoid2, self.crownSize * self.crownShape, self.crownSize, Vector((0, 0, self.crownSize + self.crownOffset)), self.surfaceBias, self.topBias)
810
811         startingpoints = []
812         if self.useTrunkGroup:
813             if bpy.data.groups.find(self.trunkGroup) >= 0:
814                 for ob in bpy.data.groups[self.trunkGroup].objects:
815                     p = ob.location - context.scene.cursor_location
816                     startingpoints.append(Branchpoint(p, None))
817
818         timings.add('scastart')
819         sca = SCA(NBP=self.maxIterations,
820             NENDPOINTS=self.numberOfEndpoints,
821             d=self.internodeLength,
822             KILLDIST=self.killDistance,
823             INFLUENCE=self.influenceRange,
824             SEED=self.randomSeed,
825             TROPISM=self.tropism,
826             volume=volumefie,
827             exclude=lambda p: insidegroup(p, self.exclusionGroup),
828             startingpoints=startingpoints)
829         timings.add('sca')
830
831         if self.showMarkers:
832             mesh = createMarkers(sca, self.markerScale)
833             obj_markers = bpy.data.objects.new(mesh.name, mesh)
834             base = bpy.context.scene.objects.link(obj_markers)
835         timings.add('showmarkers')
836
837         sca.iterate2(newendpointsper1000=self.newEndPointsPer1000, maxtime=self.maxTime)
838         timings.add('iterate')
839
840         obj_new = createGeometry(sca, self.power, self.scale, self.addLeaves, self.pLeaf, self.leafSize, self.leafRandomSize, self.leafRandomRot,
841             self.noModifiers, self.skinMethod, self.subSurface,
842             self.leafMaxConnections, self.bLeaf, self.connectoffset,
843             self.timePerformance)
844
845         timings.add('objcreationstart')
846         if self.addObjects:
847             createObjects(sca, obj_new,
848                 objectname=self.objectName,
849                 probability=self.pObject,
850                 size=self.objectSize,
851                 randomsize=self.objectRandomSize,
852                 randomrot=self.objectRandomRot,
853                 maxconnections=self.objectMaxConnections,
854                 bunchiness=self.bObject)
855         timings.add('objcreation')
856
857         if self.showMarkers:
858             obj_markers.parent = obj_new
859
860         self.updateTree = False
861
862         if self.timePerformance:
863             timings.add('Total')
864             print(timings)
865
866         return {'FINISHED'}
867
868     def draw(self, context):
869         layout = self.layout
870
871         layout.prop(self, 'updateTree', icon='MESH_DATA')
872
873         columns = layout.row()
874         col1 = columns.column()
875         col2 = columns.column()
876
877         box = col1.box()
878         box.label("Generation Settings:")
879         box.prop(self, 'randomSeed')
880         box.prop(self, 'maxIterations')
881
882         box = col1.box()
883         box.label("Shape Settings:")
884         box.prop(self, 'numberOfEndpoints')
885         box.prop(self, 'internodeLength')
886         box.prop(self, 'influenceRange')
887         box.prop(self, 'killDistance')
888         box.prop(self, 'power')
889         box.prop(self, 'scale')
890         box.prop(self, 'tropism')
891
892         newbox = col2.box()
893         newbox.label("Crown shape")
894         newbox.prop(self, 'useGroups')
895         if self.useGroups:
896             newbox.label("Object groups defining crown shape")
897             groupbox = newbox.box()
898             groupbox.prop(self, 'crownGroup')
899             groupbox = newbox.box()
900             groupbox.alert = (self.shadowGroup == self.crownGroup)
901             groupbox.prop(self, 'shadowGroup')
902             groupbox = newbox.box()
903             groupbox.alert = (self.exclusionGroup == self.crownGroup)
904             groupbox.prop(self, 'exclusionGroup')
905         else:
906             newbox.label("Simple ellipsoid defining crown shape")
907             newbox.prop(self, 'crownSize')
908             newbox.prop(self, 'crownShape')
909             newbox.prop(self, 'crownOffset')
910         newbox = col2.box()
911         newbox.prop(self, 'useTrunkGroup')
912         if self.useTrunkGroup:
913             newbox.prop(self, 'trunkGroup')
914
915         box.prop(self, 'surfaceBias')
916         box.prop(self, 'topBias')
917         box.prop(self, 'newEndPointsPer1000')
918
919         box = col2.box()
920         box.label("Skin options:")
921         box.prop(self, 'noModifiers')
922         if not self.noModifiers:
923             box.prop(self, 'skinMethod')
924             box.prop(self, 'subSurface')
925
926         layout.prop(self, 'addLeaves', icon='MESH_DATA')
927         if self.addLeaves:
928             box = layout.box()
929             box.label("Leaf Settings:")
930             box.prop(self, 'pLeaf')
931             box.prop(self, 'bLeaf')
932             box.prop(self, 'leafSize')
933             box.prop(self, 'leafRandomSize')
934             box.prop(self, 'leafRandomRot')
935             box.prop(self, 'connectoffset')
936             box.prop(self, 'leafMaxConnections')
937
938         layout.prop(self, 'addObjects', icon='MESH_DATA')
939         if self.addObjects:
940             box = layout.box()
941             box.label("Object Settings:")
942             box.prop(self, 'objectName')
943             box.prop(self, 'pObject')
944             box.prop(self, 'bObject')
945             box.prop(self, 'objectSize')
946             box.prop(self, 'objectRandomSize')
947             box.prop(self, 'objectRandomRot')
948             box.prop(self, 'objectMaxConnections')
949
950         box = layout.box()
951         box.label("Debug Settings:")
952         box.prop(self, 'showMarkers')
953         if self.showMarkers:
954             box.prop(self, 'markerScale')
955         box.prop(self, 'timePerformance')
956
957
958 def menu_func(self, context):
959     self.layout.operator(SCATree.bl_idname, text="Add Tree to Scene",
960                                                 icon='PLUGIN').updateTree = True
961
962
963 def register():
964     bpy.utils.register_module(__name__)
965     bpy.types.INFO_MT_mesh_add.append(menu_func)
966
967
968 def unregister():
969     bpy.types.INFO_MT_mesh_add.remove(menu_func)
970     bpy.utils.unregister_module(__name__)
971
972
973 if __name__ == "__main__":
974     register()