Merge branch 'blender-v2.92-release'
[blender-addons.git] / add_curve_sapling / utils.py
bloba4beb313b432afac235c047547aa5f9955b5672c
1 # -*- coding: utf-8 -*-
2 # ##### BEGIN GPL LICENSE BLOCK #####
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ##### END GPL LICENSE BLOCK #####
21 import bpy
23 import time
24 import copy
26 from mathutils import (
27 Euler,
28 Matrix,
29 Vector,
31 from math import pi, sin, degrees, radians, atan2, copysign, cos, acos
32 from math import floor
33 from random import random, uniform, seed, choice, getstate, setstate, randint
34 from collections import deque, OrderedDict
36 tau = 2 * pi
38 # Initialise the split error and axis vectors
39 splitError = 0.0
40 zAxis = Vector((0, 0, 1))
41 yAxis = Vector((0, 1, 0))
42 xAxis = Vector((1, 0, 0))
45 # This class will contain a part of the tree which needs to be extended and the required tree parameters
46 class stemSpline:
47 def __init__(self, spline, curvature, curvatureV, attractUp, segments, maxSegs,
48 segLength, childStems, stemRadStart, stemRadEnd, splineNum, ofst, pquat):
49 self.spline = spline
50 self.p = spline.bezier_points[-1]
51 self.curv = curvature
52 self.curvV = curvatureV
53 self.vertAtt = attractUp
54 self.seg = segments
55 self.segMax = maxSegs
56 self.segL = segLength
57 self.children = childStems
58 self.radS = stemRadStart
59 self.radE = stemRadEnd
60 self.splN = splineNum
61 self.offsetLen = ofst
62 self.patentQuat = pquat
63 self.curvSignx = 1
64 self.curvSigny = 1
66 # This method determines the quaternion of the end of the spline
67 def quat(self):
68 if len(self.spline.bezier_points) == 1:
69 return ((self.spline.bezier_points[-1].handle_right -
70 self.spline.bezier_points[-1].co).normalized()).to_track_quat('Z', 'Y')
71 else:
72 return ((self.spline.bezier_points[-1].co -
73 self.spline.bezier_points[-2].co).normalized()).to_track_quat('Z', 'Y')
75 # Determine the declination
76 def dec(self):
77 tempVec = zAxis.copy()
78 tempVec.rotate(self.quat())
79 return zAxis.angle(tempVec)
81 # Update the end of the spline and increment the segment count
82 def updateEnd(self):
83 self.p = self.spline.bezier_points[-1]
84 self.seg += 1
87 # This class contains the data for a point where a new branch will sprout
88 class childPoint:
89 def __init__(self, coords, quat, radiusPar, offset, sOfst, lengthPar, parBone):
90 self.co = coords
91 self.quat = quat
92 self.radiusPar = radiusPar
93 self.offset = offset
94 self.stemOffset = sOfst
95 self.lengthPar = lengthPar
96 self.parBone = parBone
99 # This function calculates the shape ratio as defined in the paper
100 def shapeRatio(shape, ratio, pruneWidthPeak=0.0, prunePowerHigh=0.0, prunePowerLow=0.0, custom=None):
101 if shape == 0:
102 return 0.05 + 0.95 * ratio # 0.2 + 0.8 * ratio
103 elif shape == 1:
104 return 0.2 + 0.8 * sin(pi * ratio)
105 elif shape == 2:
106 return 0.2 + 0.8 * sin(0.5 * pi * ratio)
107 elif shape == 3:
108 return 1.0
109 elif shape == 4:
110 return 0.5 + 0.5 * ratio
111 elif shape == 5:
112 if ratio <= 0.7:
113 return 0.05 + 0.95 * ratio / 0.7
114 else:
115 return 0.05 + 0.95 * (1.0 - ratio) / 0.3
116 elif shape == 6:
117 return 1.0 - 0.8 * ratio
118 elif shape == 7:
119 if ratio <= 0.7:
120 return 0.5 + 0.5 * ratio / 0.7
121 else:
122 return 0.5 + 0.5 * (1.0 - ratio) / 0.3
123 elif shape == 8:
124 r = 1 - ratio
125 if r == 1:
126 v = custom[3]
127 elif r >= custom[2]:
128 pos = (r - custom[2]) / (1 - custom[2])
129 # if (custom[0] >= custom[1] <= custom[3]) or (custom[0] <= custom[1] >= custom[3]):
130 pos = pos * pos
131 v = (pos * (custom[3] - custom[1])) + custom[1]
132 else:
133 pos = r / custom[2]
134 # if (custom[0] >= custom[1] <= custom[3]) or (custom[0] <= custom[1] >= custom[3]):
135 pos = 1 - (1 - pos) * (1 - pos)
136 v = (pos * (custom[1] - custom[0])) + custom[0]
138 return v
140 elif shape == 9:
141 if (ratio < (1 - pruneWidthPeak)) and (ratio > 0.0):
142 return ((ratio / (1 - pruneWidthPeak))**prunePowerHigh)
143 elif (ratio >= (1 - pruneWidthPeak)) and (ratio < 1.0):
144 return (((1 - ratio) / pruneWidthPeak)**prunePowerLow)
145 else:
146 return 0.0
148 elif shape == 10:
149 return 0.5 + 0.5 * (1 - ratio)
152 # This function determines the actual number of splits at a given point using the global error
153 def splits(n):
154 global splitError
155 nEff = round(n + splitError, 0)
156 splitError -= (nEff - n)
157 return int(nEff)
160 def splits2(n):
161 r = random()
162 if r < n:
163 return 1
164 else:
165 return 0
168 def splits3(n):
169 ni = int(n)
170 nf = n - int(n)
171 r = random()
172 if r < nf:
173 return ni + 1
174 else:
175 return ni + 0
178 # Determine the declination from a given quaternion
179 def declination(quat):
180 tempVec = zAxis.copy()
181 tempVec.rotate(quat)
182 tempVec.normalize()
183 return degrees(acos(tempVec.z))
186 # Determines the angle of upward rotation of a segment due to attractUp
187 def curveUp(attractUp, quat, curveRes):
188 tempVec = yAxis.copy()
189 tempVec.rotate(quat)
190 tempVec.normalize()
192 dec = radians(declination(quat))
193 curveUpAng = attractUp * dec * abs(tempVec.z) / curveRes
194 if (-dec + curveUpAng) < -pi:
195 curveUpAng = -pi + dec
196 if (dec - curveUpAng) < 0:
197 curveUpAng = dec
198 return curveUpAng
201 # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
202 def evalBez(p1, h1, h2, p2, t):
203 return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2
206 # Evaluate the unit tangent on a bezier curve for t
207 def evalBezTan(p1, h1, h2, p2, t):
208 return (
209 (-3 * (1 - t)**2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t)**2) * h1 +
210 (-3 * (t**2) + 6 * t * (1 - t)) * h2 + (3 * t**2) * p2
211 ).normalized()
214 # Determine the range of t values along a splines length where child stems are formed
215 def findChildPoints(stemList, numChild):
216 numPoints = sum([len(n.spline.bezier_points) for n in stemList])
217 numSplines = len(stemList)
218 numSegs = numPoints - numSplines
219 numPerSeg = numChild / numSegs
220 numMain = round(numPerSeg * stemList[0].segMax, 0)
221 return [(a + 1) / (numMain) for a in range(int(numMain))]
224 def findChildPoints2(stemList, numChild):
225 return [(a + 1) / (numChild) for a in range(int(numChild))]
228 # Find the coordinates, quaternion and radius for each t on the stem
229 def interpStem1(stem, tVals, lPar, parRad):
230 points = stem.spline.bezier_points
231 numPoints = len(points)
232 checkVal = (stem.segMax - (numPoints - 1)) / stem.segMax
233 # Loop through all the parametric values to be determined
234 tempList = deque()
235 for t in tVals:
236 if t == 1.0:
237 index = numPoints - 2
238 coord = points[-1].co
239 quat = (points[-1].handle_right - points[-1].co).to_track_quat('Z', 'Y')
240 radius = points[-1].radius
242 tempList.append(
243 childPoint(coord, quat, (parRad, radius), t, lPar, 'bone' +
244 (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0')))
247 elif (t >= checkVal) and (t < 1.0):
248 scaledT = (t - checkVal) / ((1 - checkVal) + .0001)
249 length = (numPoints - 1) * scaledT
250 index = int(length)
252 tTemp = length - index
253 coord = evalBez(
254 points[index].co, points[index].handle_right,
255 points[index + 1].handle_left, points[index + 1].co, tTemp
257 quat = (
258 evalBezTan(
259 points[index].co, points[index].handle_right,
260 points[index + 1].handle_left, points[index + 1].co, tTemp)
261 ).to_track_quat('Z', 'Y')
262 # Not sure if this is the parent radius at the child point or parent start radius
263 radius = (1 - tTemp) * points[index].radius + tTemp * points[index + 1].radius
265 tempList.append(
266 childPoint(
267 coord, quat, (parRad, radius), t, lPar, 'bone' +
268 (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0')))
270 return tempList
273 def interpStem(stem, tVals, lPar, parRad, maxOffset, baseSize):
274 points = stem.spline.bezier_points
275 numSegs = len(points) - 1
276 stemLen = stem.segL * numSegs
278 checkBottom = stem.offsetLen / maxOffset
279 checkTop = checkBottom + (stemLen / maxOffset)
281 # Loop through all the parametric values to be determined
282 tempList = deque()
283 for t in tVals:
284 if (t >= checkBottom) and (t <= checkTop) and (t < 1.0):
285 scaledT = (t - checkBottom) / (checkTop - checkBottom)
286 ofst = ((t - baseSize) / (checkTop - baseSize)) * (1 - baseSize) + baseSize
288 length = numSegs * scaledT
289 index = int(length)
290 tTemp = length - index
292 coord = evalBez(
293 points[index].co, points[index].handle_right,
294 points[index + 1].handle_left, points[index + 1].co, tTemp
296 quat = (
297 evalBezTan(
298 points[index].co, points[index].handle_right,
299 points[index + 1].handle_left, points[index + 1].co, tTemp
301 ).to_track_quat('Z', 'Y')
302 # Not sure if this is the parent radius at the child point or parent start radius
303 radius = (1 - tTemp) * points[index].radius + tTemp * points[index + 1].radius
305 tempList.append(
306 childPoint(
307 coord, quat, (parRad, radius), t, ofst, lPar,
308 'bone' + (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0')))
311 # add stem at tip
312 index = numSegs - 1
313 coord = points[-1].co
314 quat = (points[-1].handle_right - points[-1].co).to_track_quat('Z', 'Y')
315 radius = points[-1].radius
316 tempList.append(
317 childPoint(
318 coord, quat, (parRad, radius), 1, 1, lPar,
319 'bone' + (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0'))
323 return tempList
326 # round down bone number
327 def roundBone(bone, step):
328 bone_i = bone[:-3]
329 bone_n = int(bone[-3:])
330 bone_n = int(bone_n / step) * step
331 return bone_i + str(bone_n).rjust(3, '0')
334 # Convert a list of degrees to radians
335 def toRad(list):
336 return [radians(a) for a in list]
339 def anglemean(a1, a2, fac):
340 x1 = sin(a1)
341 y1 = cos(a1)
342 x2 = sin(a2)
343 y2 = cos(a2)
344 x = x1 + (x2 - x1) * fac
345 y = y1 + (y2 - y1) * fac
346 return atan2(x, y)
349 # This is the function which extends (or grows) a given stem.
350 def growSpline(n, stem, numSplit, splitAng, splitAngV, splineList,
351 hType, splineToBone, closeTip, kp, splitHeight, outAtt, stemsegL,
352 lenVar, taperCrown, boneStep, rotate, rotateV):
354 # curv at base
355 sCurv = stem.curv
356 if (n == 0) and (kp <= splitHeight):
357 sCurv = 0.0
359 # curveangle = sCurv + (uniform(-stem.curvV, stem.curvV) * kp)
360 # curveVar = uniform(-stem.curvV, stem.curvV) * kp
361 curveangle = sCurv + (uniform(0, stem.curvV) * kp * stem.curvSignx)
362 curveVar = uniform(0, stem.curvV) * kp * stem.curvSigny
363 stem.curvSignx *= -1
364 stem.curvSigny *= -1
366 curveVarMat = Matrix.Rotation(curveVar, 3, 'Y')
368 # First find the current direction of the stem
369 dir = stem.quat()
371 if n == 0:
372 adir = zAxis.copy()
373 adir.rotate(dir)
375 ry = atan2(adir[0], adir[2])
376 adir.rotate(Euler((0, -ry, 0)))
377 rx = atan2(adir[1], adir[2])
379 dir = Euler((-rx, ry, 0), 'XYZ')
381 # length taperCrown
382 if n == 0:
383 dec = declination(dir) / 180
384 dec = dec ** 2
385 tf = 1 - (dec * taperCrown * 30)
386 tf = max(.1, tf)
387 else:
388 tf = 1.0
390 # outward attraction
391 if (n > 0) and (kp > 0) and (outAtt > 0):
392 p = stem.p.co.copy()
393 d = atan2(p[0], -p[1]) + tau
394 edir = dir.to_euler('XYZ', Euler((0, 0, d), 'XYZ'))
395 d = anglemean(edir[2], d, (kp * outAtt))
396 dirv = Euler((edir[0], edir[1], d), 'XYZ')
397 dir = dirv.to_quaternion()
399 # parent weight
400 parWeight = kp * degrees(stem.curvV) * pi
401 parWeight = parWeight * kp
402 parWeight = kp
403 if (n > 1) and (parWeight != 0):
404 d1 = zAxis.copy()
405 d2 = zAxis.copy()
406 d1.rotate(dir)
407 d2.rotate(stem.patentQuat)
409 x = d1[0] + ((d2[0] - d1[0]) * parWeight)
410 y = d1[1] + ((d2[1] - d1[1]) * parWeight)
411 z = d1[2] + ((d2[2] - d1[2]) * parWeight)
413 d3 = Vector((x, y, z))
414 dir = d3.to_track_quat('Z', 'Y')
417 # If the stem splits, we need to add new splines etc
418 if numSplit > 0:
419 # Get the curve data
420 cuData = stem.spline.id_data.name
421 cu = bpy.data.curves[cuData]
423 # calc split angles
424 angle = choice([-1, 1]) * (splitAng + uniform(-splitAngV, splitAngV))
425 if n > 0:
426 # make branches flatter
427 angle *= max(1 - declination(dir) / 90, 0) * .67 + .33
428 spreadangle = choice([-1, 1]) * (splitAng + uniform(-splitAngV, splitAngV))
430 # branchRotMat = Matrix.Rotation(radians(uniform(0, 360)), 3, 'Z')
431 if not hasattr(stem, 'rLast'):
432 stem.rLast = radians(uniform(0, 360))
434 br = rotate[0] + uniform(-rotateV[0], rotateV[0])
435 branchRot = stem.rLast + br
436 branchRotMat = Matrix.Rotation(branchRot, 3, 'Z')
437 stem.rLast = branchRot
439 # Now for each split add the new spline and adjust the growth direction
440 for i in range(numSplit):
441 # find split scale
442 lenV = uniform(1 - lenVar, 1 + lenVar)
443 bScale = min(lenV * tf, 1)
445 newSpline = cu.splines.new('BEZIER')
446 newPoint = newSpline.bezier_points[-1]
447 (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (stem.p.co, 'VECTOR', 'VECTOR')
448 newPoint.radius = (
449 stem.radS * (1 - stem.seg / stem.segMax) + stem.radE * (stem.seg / stem.segMax)
450 ) * bScale
451 # Here we make the new "sprouting" stems diverge from the current direction
452 divRotMat = Matrix.Rotation(angle + curveangle, 3, 'X')
453 dirVec = zAxis.copy()
454 dirVec.rotate(divRotMat)
456 # horizontal curvature variation
457 dirVec.rotate(curveVarMat)
459 if n == 0: # Special case for trunk splits
460 dirVec.rotate(branchRotMat)
462 ang = pi - ((tau) / (numSplit + 1)) * (i + 1)
463 dirVec.rotate(Matrix.Rotation(ang, 3, 'Z'))
465 # Spread the stem out in a random fashion
466 spreadMat = Matrix.Rotation(spreadangle, 3, 'Y')
467 if n != 0: # Special case for trunk splits
468 dirVec.rotate(spreadMat)
470 dirVec.rotate(dir)
472 # Introduce upward curvature
473 upRotAxis = xAxis.copy()
474 upRotAxis.rotate(dirVec.to_track_quat('Z', 'Y'))
475 curveUpAng = curveUp(stem.vertAtt, dirVec.to_track_quat('Z', 'Y'), stem.segMax)
476 upRotMat = Matrix.Rotation(-curveUpAng, 3, upRotAxis)
477 dirVec.rotate(upRotMat)
479 # Make the growth vec the length of a stem segment
480 dirVec.normalize()
482 # split length variation
483 stemL = stemsegL * lenV
484 dirVec *= stemL * tf
485 ofst = stem.offsetLen + (stem.segL * (len(stem.spline.bezier_points) - 1))
487 # dirVec *= stem.segL
489 # Get the end point position
490 end_co = stem.p.co.copy()
492 # Add the new point and adjust its coords, handles and radius
493 newSpline.bezier_points.add(1)
494 newPoint = newSpline.bezier_points[-1]
495 (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType)
496 newPoint.radius = (
497 stem.radS * (1 - (stem.seg + 1) / stem.segMax) +
498 stem.radE * ((stem.seg + 1) / stem.segMax)
499 ) * bScale
500 if (stem.seg == stem.segMax - 1) and closeTip:
501 newPoint.radius = 0.0
502 # If this isn't the last point on a stem, then we need to add it
503 # to the list of stems to continue growing
504 # print(stem.seg != stem.segMax, stem.seg, stem.segMax)
505 # if stem.seg != stem.segMax: # if probs not necessary
506 nstem = stemSpline(
507 newSpline, stem.curv, stem.curvV, stem.vertAtt, stem.seg + 1,
508 stem.segMax, stemL, stem.children,
509 stem.radS * bScale, stem.radE * bScale, len(cu.splines) - 1, ofst, stem.quat()
511 nstem.splitlast = 1 # numSplit # keep track of numSplit for next stem
512 nstem.rLast = branchRot + pi
513 splineList.append(nstem)
514 bone = 'bone' + (str(stem.splN)).rjust(3, '0') + '.' + \
515 (str(len(stem.spline.bezier_points) - 2)).rjust(3, '0')
516 bone = roundBone(bone, boneStep[n])
517 splineToBone.append((bone, False, True, len(stem.spline.bezier_points) - 2))
519 # The original spline also needs to keep growing so adjust its direction too
520 divRotMat = Matrix.Rotation(-angle + curveangle, 3, 'X')
521 dirVec = zAxis.copy()
522 dirVec.rotate(divRotMat)
524 # horizontal curvature variation
525 dirVec.rotate(curveVarMat)
527 if n == 0: # Special case for trunk splits
528 dirVec.rotate(branchRotMat)
530 # spread
531 spreadMat = Matrix.Rotation(-spreadangle, 3, 'Y')
532 if n != 0: # Special case for trunk splits
533 dirVec.rotate(spreadMat)
535 dirVec.rotate(dir)
537 stem.splitlast = 1 # numSplit #keep track of numSplit for next stem
539 else:
540 # If there are no splits then generate the growth direction without accounting for spreading of stems
541 dirVec = zAxis.copy()
542 divRotMat = Matrix.Rotation(curveangle, 3, 'X')
543 dirVec.rotate(divRotMat)
545 # horizontal curvature variation
546 dirVec.rotate(curveVarMat)
548 dirVec.rotate(dir)
550 stem.splitlast = 0 # numSplit #keep track of numSplit for next stem
552 # Introduce upward curvature
553 upRotAxis = xAxis.copy()
554 upRotAxis.rotate(dirVec.to_track_quat('Z', 'Y'))
555 curveUpAng = curveUp(stem.vertAtt, dirVec.to_track_quat('Z', 'Y'), stem.segMax)
556 upRotMat = Matrix.Rotation(-curveUpAng, 3, upRotAxis)
557 dirVec.rotate(upRotMat)
559 dirVec.normalize()
560 dirVec *= stem.segL * tf
562 # Get the end point position
563 end_co = stem.p.co.copy()
565 stem.spline.bezier_points.add(1)
566 newPoint = stem.spline.bezier_points[-1]
567 (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType)
568 newPoint.radius = stem.radS * (1 - (stem.seg + 1) / stem.segMax) + \
569 stem.radE * ((stem.seg + 1) / stem.segMax)
571 if (stem.seg == stem.segMax - 1) and closeTip:
572 newPoint.radius = 0.0
573 # There are some cases where a point cannot have handles as VECTOR straight away, set these now
574 if len(stem.spline.bezier_points) == 2:
575 tempPoint = stem.spline.bezier_points[0]
576 (tempPoint.handle_left_type, tempPoint.handle_right_type) = ('VECTOR', 'VECTOR')
577 # Update the last point in the spline to be the newly added one
578 stem.updateEnd()
579 # return splineList
582 def genLeafMesh(leafScale, leafScaleX, leafScaleT, leafScaleV, loc, quat,
583 offset, index, downAngle, downAngleV, rotate, rotateV, oldRot,
584 bend, leaves, leafShape, leafangle, horzLeaves):
585 if leafShape == 'hex':
586 verts = [
587 Vector((0, 0, 0)), Vector((0.5, 0, 1 / 3)), Vector((0.5, 0, 2 / 3)),
588 Vector((0, 0, 1)), Vector((-0.5, 0, 2 / 3)), Vector((-0.5, 0, 1 / 3))
590 edges = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0], [0, 3]]
591 faces = [[0, 1, 2, 3], [0, 3, 4, 5]]
592 elif leafShape == 'rect':
593 # verts = [Vector((1, 0, 0)), Vector((1, 0, 1)), Vector((-1, 0, 1)), Vector((-1, 0, 0))]
594 verts = [Vector((.5, 0, 0)), Vector((.5, 0, 1)), Vector((-.5, 0, 1)), Vector((-.5, 0, 0))]
595 edges = [[0, 1], [1, 2], [2, 3], [3, 0]]
596 faces = [[0, 1, 2, 3]]
597 elif leafShape == 'dFace':
598 verts = [Vector((.5, .5, 0)), Vector((.5, -.5, 0)), Vector((-.5, -.5, 0)), Vector((-.5, .5, 0))]
599 edges = [[0, 1], [1, 2], [2, 3], [3, 0]]
600 faces = [[0, 3, 2, 1]]
601 elif leafShape == 'dVert':
602 verts = [Vector((0, 0, 1))]
603 edges = []
604 faces = []
606 vertsList = []
607 facesList = []
608 normal = Vector((0, 0, 1))
610 if leaves < 0:
611 rotMat = Matrix.Rotation(oldRot, 3, 'Y')
612 else:
613 rotMat = Matrix.Rotation(oldRot, 3, 'Z')
615 # If the -ve flag for rotate is used we need to find which side of the stem
616 # the last child point was and then grow in the opposite direction
617 if rotate < 0.0:
618 oldRot = -copysign(rotate + uniform(-rotateV, rotateV), oldRot)
619 else:
620 # If the special -ve flag for leaves is used we need a different rotation of the leaf geometry
621 if leaves == -1:
622 # oldRot = 0
623 rotMat = Matrix.Rotation(0, 3, 'Y')
624 elif leaves < -1:
625 oldRot += rotate / (-leaves - 1)
626 else:
627 oldRot += rotate + uniform(-rotateV, rotateV)
629 if leaves < 0:
630 rotMat = Matrix.Rotation(oldRot, 3, 'Y')
631 else:
632 rotMat = Matrix.Rotation(oldRot, 3, 'Z')
634 if leaves >= 0:
635 # downRotMat = Matrix.Rotation(downAngle+uniform(-downAngleV, downAngleV), 3, 'X')
637 if downAngleV > 0.0:
638 downV = -downAngleV * offset
639 else:
640 downV = uniform(-downAngleV, downAngleV)
641 downRotMat = Matrix.Rotation(downAngle + downV, 3, 'X')
643 # leaf scale variation
644 if (leaves < -1) and (rotate != 0):
645 f = 1 - abs((oldRot - (rotate / (-leaves - 1))) / (rotate / 2))
646 else:
647 f = offset
649 if leafScaleT < 0:
650 leafScale = leafScale * (1 - (1 - f) * -leafScaleT)
651 else:
652 leafScale = leafScale * (1 - f * leafScaleT)
654 leafScale = leafScale * uniform(1 - leafScaleV, 1 + leafScaleV)
656 if leafShape == 'dFace':
657 leafScale = leafScale * .1
659 # If the bending of the leaves is used we need to rotate them differently
660 if (bend != 0.0) and (leaves >= 0):
661 normal = yAxis.copy()
662 orientationVec = zAxis.copy()
664 normal.rotate(quat)
665 orientationVec.rotate(quat)
667 thetaPos = atan2(loc.y, loc.x)
668 thetaBend = thetaPos - atan2(normal.y, normal.x)
669 rotateZ = Matrix.Rotation(bend * thetaBend, 3, 'Z')
670 normal.rotate(rotateZ)
671 orientationVec.rotate(rotateZ)
673 phiBend = atan2((normal.xy).length, normal.z)
674 orientation = atan2(orientationVec.y, orientationVec.x)
675 rotateZOrien = Matrix.Rotation(orientation, 3, 'X')
677 rotateX = Matrix.Rotation(bend * phiBend, 3, 'Z')
679 rotateZOrien2 = Matrix.Rotation(-orientation, 3, 'X')
681 # For each of the verts we now rotate and scale them, then append them to the list to be added to the mesh
682 for v in verts:
683 v.z *= leafScale
684 v.y *= leafScale
685 v.x *= leafScaleX * leafScale
687 v.rotate(Euler((0, 0, radians(180))))
689 # leafangle
690 v.rotate(Matrix.Rotation(radians(-leafangle), 3, 'X'))
692 if rotate < 0:
693 v.rotate(Euler((0, 0, radians(90))))
694 if oldRot < 0:
695 v.rotate(Euler((0, 0, radians(180))))
697 if (leaves > 0) and (rotate > 0) and horzLeaves:
698 nRotMat = Matrix.Rotation(-oldRot + rotate, 3, 'Z')
699 v.rotate(nRotMat)
701 if leaves > 0:
702 v.rotate(downRotMat)
704 v.rotate(rotMat)
705 v.rotate(quat)
707 if (bend != 0.0) and (leaves > 0):
708 # Correct the rotation
709 v.rotate(rotateZ)
710 v.rotate(rotateZOrien)
711 v.rotate(rotateX)
712 v.rotate(rotateZOrien2)
714 if leafShape == 'dVert':
715 normal = verts[0]
716 normal.normalize()
717 v = loc
718 vertsList.append([v.x, v.y, v.z])
719 else:
720 for v in verts:
721 v += loc
722 vertsList.append([v.x, v.y, v.z])
723 for f in faces:
724 facesList.append([f[0] + index, f[1] + index, f[2] + index, f[3] + index])
726 return vertsList, facesList, normal, oldRot
729 def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSize, leaves,
730 levelCount, splineToBone, treeOb, wind, gust, gustF, af1, af2, af3,
731 leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep):
732 arm = bpy.data.armatures.new('tree')
733 armOb = bpy.data.objects.new('treeArm', arm)
734 bpy.context.scene.collection.objects.link(armOb)
735 # Create a new action to store all animation
736 newAction = bpy.data.actions.new(name='windAction')
737 armOb.animation_data_create()
738 armOb.animation_data.action = newAction
739 arm.display_type = 'STICK'
740 # Add the armature modifier to the curve
741 armMod = treeOb.modifiers.new('windSway', 'ARMATURE')
742 if previewArm:
743 armMod.show_viewport = False
744 arm.display_type = 'WIRE'
745 treeOb.hide_viewport = True
746 armMod.use_apply_on_spline = True
747 armMod.object = armOb
748 armMod.use_bone_envelopes = True
749 armMod.use_vertex_groups = False # curves don't have vertex groups (yet)
750 # If there are leaves then they need a modifier
751 if leaves:
752 armMod = leafObj.modifiers.new('windSway', 'ARMATURE')
753 armMod.object = armOb
754 armMod.use_bone_envelopes = False
755 armMod.use_vertex_groups = True
757 # Make sure all objects are deselected (may not be required?)
758 for ob in bpy.context.view_layer.objects:
759 ob.select_set(state=False)
761 fps = bpy.context.scene.render.fps
762 animSpeed = (24 / fps) * frameRate
764 # Set the armature as active and go to edit mode to add bones
765 bpy.context.view_layer.objects.active = armOb
766 bpy.ops.object.mode_set(mode='EDIT')
767 # For all the splines in the curve we need to add bones at each bezier point
768 for i, parBone in enumerate(splineToBone):
769 if (i < levelCount[armLevels]) or (armLevels == -1) or (not makeMesh):
770 s = cu.splines[i]
771 b = None
772 # Get some data about the spline like length and number of points
773 numPoints = len(s.bezier_points) - 1
775 # find branching level
776 level = 0
777 for l, c in enumerate(levelCount):
778 if i < c:
779 level = l
780 break
781 level = min(level, 3)
783 step = boneStep[level]
785 # Calculate things for animation
786 if armAnim:
787 splineL = numPoints * ((s.bezier_points[0].co - s.bezier_points[1].co).length)
788 # Set the random phase difference of the animation
789 bxOffset = uniform(0, tau)
790 byOffset = uniform(0, tau)
791 # Set the phase multiplier for the spline
792 # bMult_r = (s.bezier_points[0].radius / max(splineL, 1e-6)) * (1 / 15) * (1 / frameRate)
793 # This shouldn't have to be in degrees but it looks much better in animation
794 # bMult = degrees(bMult_r)
795 bMult = (1 / max(splineL ** .5, 1e-6)) * (1 / 4)
796 # print((1 / bMult) * tau) #print wavelength in frames
798 windFreq1 = bMult * animSpeed
799 windFreq2 = 0.7 * bMult * animSpeed
800 if loopFrames != 0:
801 bMult_l = 1 / (loopFrames / tau)
802 fRatio = max(1, round(windFreq1 / bMult_l))
803 fgRatio = max(1, round(windFreq2 / bMult_l))
804 windFreq1 = fRatio * bMult_l
805 windFreq2 = fgRatio * bMult_l
807 # For all the points in the curve (less the last) add a bone and name it by the spline it will affect
808 nx = 0
809 for n in range(0, numPoints, step):
810 oldBone = b
811 boneName = 'bone' + (str(i)).rjust(3, '0') + '.' + (str(n)).rjust(3, '0')
812 b = arm.edit_bones.new(boneName)
813 b.head = s.bezier_points[n].co
814 nx += step
815 nx = min(nx, numPoints)
816 b.tail = s.bezier_points[nx].co
818 b.head_radius = s.bezier_points[n].radius
819 b.tail_radius = s.bezier_points[n + 1].radius
820 b.envelope_distance = 0.001
822 # If there are leaves then we need a new vertex group so they will attach to the bone
823 if not leafAnim:
824 if (len(levelCount) > 1) and (i >= levelCount[-2]) and leafObj:
825 leafObj.vertex_groups.new(name=boneName)
826 elif (len(levelCount) == 1) and leafObj:
827 leafObj.vertex_groups.new(name=boneName)
829 # If this is first point of the spline then it must be parented to the level above it
830 if n == 0:
831 if parBone:
832 b.parent = arm.edit_bones[parBone]
833 # Otherwise, we need to attach it to the previous bone in the spline
834 else:
835 b.parent = oldBone
836 b.use_connect = True
837 # If there isn't a previous bone then it shouldn't be attached
838 if not oldBone:
839 b.use_connect = False
841 # Add the animation to the armature if required
842 if armAnim:
843 # Define all the required parameters of the wind sway by the dimension of the spline
844 # a0 = 4 * splineL * (1 - n / (numPoints + 1)) / max(s.bezier_points[n].radius, 1e-6)
845 a0 = 2 * (splineL / numPoints) * (1 - n / (numPoints + 1)) / max(s.bezier_points[n].radius, 1e-6)
846 a0 = a0 * min(step, numPoints)
847 # a0 = (splineL / numPoints) / max(s.bezier_points[n].radius, 1e-6)
848 a1 = (wind / 50) * a0
849 a2 = a1 * .65 # (windGust / 50) * a0 + a1 / 2
851 p = s.bezier_points[nx].co - s.bezier_points[n].co
852 p.normalize()
853 ag = (wind * gust / 50) * a0
854 a3 = -p[0] * ag
855 a4 = p[2] * ag
857 a1 = radians(a1)
858 a2 = radians(a2)
859 a3 = radians(a3)
860 a4 = radians(a4)
862 # wind bending
863 if loopFrames == 0:
864 swayFreq = gustF * (tau / fps) * frameRate # animSpeed # .075 # 0.02
865 else:
866 swayFreq = 1 / (loopFrames / tau)
868 # Prevent tree base from rotating
869 if (boneName == "bone000.000") or (boneName == "bone000.001"):
870 a1 = 0
871 a2 = 0
872 a3 = 0
873 a4 = 0
875 # Add new fcurves for each sway as well as the modifiers
876 swayX = armOb.animation_data.action.fcurves.new(
877 'pose.bones["' + boneName + '"].rotation_euler', index=0
879 swayY = armOb.animation_data.action.fcurves.new(
880 'pose.bones["' + boneName + '"].rotation_euler', index=2
882 swayXMod1 = swayX.modifiers.new(type='FNGENERATOR')
883 swayXMod2 = swayX.modifiers.new(type='FNGENERATOR')
885 swayYMod1 = swayY.modifiers.new(type='FNGENERATOR')
886 swayYMod2 = swayY.modifiers.new(type='FNGENERATOR')
888 # Set the parameters for each modifier
889 swayXMod1.amplitude = a1
890 swayXMod1.phase_offset = bxOffset
891 swayXMod1.phase_multiplier = windFreq1
893 swayXMod2.amplitude = a2
894 swayXMod2.phase_offset = 0.7 * bxOffset
895 swayXMod2.phase_multiplier = windFreq2
896 swayXMod2.use_additive = True
898 swayYMod1.amplitude = a1
899 swayYMod1.phase_offset = byOffset
900 swayYMod1.phase_multiplier = windFreq1
902 swayYMod2.amplitude = a2
903 swayYMod2.phase_offset = 0.7 * byOffset
904 swayYMod2.phase_multiplier = windFreq2
905 swayYMod2.use_additive = True
907 # wind bending
908 swayYMod3 = swayY.modifiers.new(type='FNGENERATOR')
909 swayYMod3.amplitude = a3
910 swayYMod3.phase_multiplier = swayFreq
911 swayYMod3.value_offset = .6 * a3
912 swayYMod3.use_additive = True
914 swayXMod3 = swayX.modifiers.new(type='FNGENERATOR')
915 swayXMod3.amplitude = a4
916 swayXMod3.phase_multiplier = swayFreq
917 swayXMod3.value_offset = .6 * a4
918 swayXMod3.use_additive = True
920 if leaves:
921 bonelist = [b.name for b in arm.edit_bones]
922 vertexGroups = OrderedDict()
923 for i, cp in enumerate(leafP):
924 # find leafs parent bone
925 leafParent = roundBone(cp.parBone, boneStep[armLevels])
926 idx = int(leafParent[4:-4])
927 while leafParent not in bonelist:
928 # find parent bone of parent bone
929 leafParent = splineToBone[idx]
930 idx = int(leafParent[4:-4])
932 if leafAnim:
933 bname = "leaf" + str(i)
934 b = arm.edit_bones.new(bname)
935 b.head = cp.co
936 b.tail = cp.co + Vector((0, 0, .02))
937 b.envelope_distance = 0.0
938 b.parent = arm.edit_bones[leafParent]
940 vertexGroups[bname] = [
941 v.index for v in
942 leafMesh.vertices[leafVertSize * i:(leafVertSize * i + leafVertSize)]
945 if armAnim:
946 # Define all the required parameters of the wind sway by the dimension of the spline
947 a1 = wind * .25
948 a1 *= af1
950 bMult = (1 / animSpeed) * 6
951 bMult *= 1 / max(af2, .001)
953 ofstRand = af3
954 bxOffset = uniform(-ofstRand, ofstRand)
955 byOffset = uniform(-ofstRand, ofstRand)
957 # Add new fcurves for each sway as well as the modifiers
958 swayX = armOb.animation_data.action.fcurves.new(
959 'pose.bones["' + bname + '"].rotation_euler', index=0
961 swayY = armOb.animation_data.action.fcurves.new(
962 'pose.bones["' + bname + '"].rotation_euler', index=2
964 # Add keyframe so noise works
965 swayX.keyframe_points.add(1)
966 swayY.keyframe_points.add(1)
967 swayX.keyframe_points[0].co = (0, 0)
968 swayY.keyframe_points[0].co = (0, 0)
970 # Add noise modifiers
971 swayXMod = swayX.modifiers.new(type='NOISE')
972 swayYMod = swayY.modifiers.new(type='NOISE')
974 if loopFrames != 0:
975 swayXMod.use_restricted_range = True
976 swayXMod.frame_end = loopFrames
977 swayXMod.blend_in = 4
978 swayXMod.blend_out = 4
979 swayYMod.use_restricted_range = True
980 swayYMod.frame_end = loopFrames
981 swayYMod.blend_in = 4
982 swayYMod.blend_out = 4
984 swayXMod.scale = bMult
985 swayXMod.strength = a1
986 swayXMod.offset = bxOffset
988 swayYMod.scale = bMult
989 swayYMod.strength = a1
990 swayYMod.offset = byOffset
992 else:
993 if leafParent not in vertexGroups:
994 vertexGroups[leafParent] = []
995 vertexGroups[leafParent].extend(
996 [v.index for v in
997 leafMesh.vertices[leafVertSize * i:(leafVertSize * i + leafVertSize)]]
1000 for group in vertexGroups:
1001 leafObj.vertex_groups.new(name=group)
1002 leafObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
1004 # Now we need the rotation mode to be 'XYZ' to ensure correct rotation
1005 bpy.ops.object.mode_set(mode='OBJECT')
1006 for p in armOb.pose.bones:
1007 p.rotation_mode = 'XYZ'
1008 treeOb.parent = armOb
1011 def kickstart_trunk(addstem, levels, leaves, branches, cu, curve, curveRes,
1012 curveV, attractUp, length, lengthV, ratio, ratioPower,
1013 resU, scale0, scaleV0, scaleVal, taper, minRadius, rootFlare):
1014 newSpline = cu.splines.new('BEZIER')
1015 cu.resolution_u = resU
1016 newPoint = newSpline.bezier_points[-1]
1017 newPoint.co = Vector((0, 0, 0))
1018 newPoint.handle_right = Vector((0, 0, 1))
1019 newPoint.handle_left = Vector((0, 0, -1))
1020 # (newPoint.handle_right_type, newPoint.handle_left_type) = ('VECTOR', 'VECTOR')
1021 branchL = scaleVal * length[0]
1022 curveVal = curve[0] / curveRes[0]
1023 # curveVal = curveVal * (branchL / scaleVal)
1024 if levels == 1:
1025 childStems = leaves
1026 else:
1027 childStems = branches[1]
1028 startRad = scaleVal * ratio * scale0 * uniform(1 - scaleV0, 1 + scaleV0) # * (scale0 + uniform(-scaleV0, scaleV0))
1029 endRad = (startRad * (1 - taper[0])) ** ratioPower
1030 startRad = max(startRad, minRadius)
1031 endRad = max(endRad, minRadius)
1032 newPoint.radius = startRad * rootFlare
1033 addstem(
1034 stemSpline(
1035 newSpline, curveVal, curveV[0] / curveRes[0], attractUp[0],
1036 0, curveRes[0], branchL / curveRes[0],
1037 childStems, startRad, endRad, 0, 0, None
1042 def fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, curve, curveBack,
1043 curveRes, curveV, attractUp, downAngle, downAngleV, leafDist, leaves, length,
1044 lengthV, levels, n, ratioPower, resU, rotate, rotateV, scaleVal, shape, storeN,
1045 taper, shapeS, minRadius, radiusTweak, customShape, rMode, segSplits,
1046 useOldDownAngle, useParentAngle, boneStep):
1048 # prevent baseSize from going to 1.0
1049 baseSize = min(0.999, baseSize)
1051 # Store the old rotation to allow new stems to be rotated away from the previous one.
1052 oldRotate = 0
1054 # use fancy child point selection / rotation
1055 if (n == 1) and (rMode != "original"):
1056 childP_T = OrderedDict()
1057 childP_L = []
1058 for p in childP:
1059 if p.offset == 1:
1060 childP_L.append(p)
1061 else:
1062 if p.offset not in childP_T:
1063 childP_T[p.offset] = [p]
1064 else:
1065 childP_T[p.offset].append(p)
1067 childP_T = [childP_T[k] for k in sorted(childP_T.keys())]
1069 childP = []
1070 rot_a = []
1071 for p in childP_T:
1072 if rMode == "rotate":
1073 if rotate[n] < 0.0:
1074 oldRotate = -copysign(rotate[n], oldRotate)
1075 else:
1076 oldRotate += rotate[n]
1077 bRotate = oldRotate + uniform(-rotateV[n], rotateV[n])
1079 # choose start point whose angle is closest to the rotate angle
1080 a1 = bRotate % tau
1081 a_diff = []
1082 for a in p:
1083 a2 = atan2(a.co[0], -a.co[1])
1084 d = min((a1 - a2 + tau) % tau, (a2 - a1 + tau) % tau)
1085 a_diff.append(d)
1087 idx = a_diff.index(min(a_diff))
1089 # find actual rotate angle from branch location
1090 br = p[idx]
1091 b = br.co
1092 vx = sin(bRotate)
1093 vy = cos(bRotate)
1094 v = Vector((vx, vy))
1096 bD = ((b[0] * b[0] + b[1] * b[1]) ** .5)
1097 bL = br.lengthPar * length[1] * shapeRatio(shape, (1 - br.offset) / (1 - baseSize), custom=customShape)
1099 # account for down angle
1100 if downAngleV[1] > 0:
1101 downA = downAngle[n] + (-downAngleV[n] * (1 - (1 - br.offset) / (1 - baseSize)) ** 2)
1102 else:
1103 downA = downAngle[n]
1104 if downA < (.5 * pi):
1105 downA = sin(downA) ** 2
1106 bL *= downA
1108 bL *= 0.33
1109 v *= (bD + bL)
1111 bv = Vector((b[0], -b[1]))
1112 cv = v - bv
1113 a = atan2(cv[0], cv[1])
1114 # rot_a.append(a)
1116 # add fill points at top #experimental
1117 fillHeight = 1 - degrees(rotateV[3]) # 0.8
1118 if fillHeight < 1:
1119 w = (p[0].offset - fillHeight) / (1- fillHeight)
1120 prob_b = random() < w
1121 else:
1122 prob_b = False
1124 if (p[0].offset > fillHeight): # prob_b and (len(p) > 1): ##(p[0].offset > fillHeight) and
1125 childP.append(p[randint(0, len(p)-1)])
1126 rot_a.append(bRotate)# + pi)
1128 childP.append(p[idx])
1129 rot_a.append(a)
1131 else:
1132 idx = randint(0, len(p) - 1)
1133 childP.append(p[idx])
1134 # childP.append(p[idx])
1136 childP.extend(childP_L)
1137 rot_a.extend([0] * len(childP_L))
1139 oldRotate = 0
1141 for i, p in enumerate(childP):
1142 # Add a spline and set the coordinate of the first point.
1143 newSpline = cu.splines.new('BEZIER')
1144 cu.resolution_u = resU
1145 newPoint = newSpline.bezier_points[-1]
1146 newPoint.co = p.co
1147 tempPos = zAxis.copy()
1148 # If the -ve flag for downAngle is used we need a special formula to find it
1149 if useOldDownAngle:
1150 if downAngleV[n] < 0.0:
1151 downV = downAngleV[n] * (1 - 2 * (.2 + .8 * ((1 - p.offset) / (1 - baseSize))))
1152 # Otherwise just find a random value
1153 else:
1154 downV = uniform(-downAngleV[n], downAngleV[n])
1155 else:
1156 if downAngleV[n] < 0.0:
1157 downV = uniform(-downAngleV[n], downAngleV[n])
1158 else:
1159 downV = -downAngleV[n] * (1 - (1 - p.offset) / (1 - baseSize)) ** 2 # (110, 80) = (60, -50)
1161 if p.offset == 1:
1162 downRotMat = Matrix.Rotation(0, 3, 'X')
1163 else:
1164 downRotMat = Matrix.Rotation(downAngle[n] + downV, 3, 'X')
1166 # If the -ve flag for rotate is used we need to find which side of the stem
1167 # the last child point was and then grow in the opposite direction
1168 if rotate[n] < 0.0:
1169 oldRotate = -copysign(rotate[n], oldRotate)
1170 # Otherwise just generate a random number in the specified range
1171 else:
1172 oldRotate += rotate[n]
1173 bRotate = oldRotate + uniform(-rotateV[n], rotateV[n])
1175 if (n == 1) and (rMode == "rotate"):
1176 bRotate = rot_a[i]
1178 rotMat = Matrix.Rotation(bRotate, 3, 'Z')
1180 # Rotate the direction of growth and set the new point coordinates
1181 tempPos.rotate(downRotMat)
1182 tempPos.rotate(rotMat)
1184 # use quat angle
1185 if (rMode == "rotate") and (n == 1) and (p.offset != 1):
1186 if useParentAngle:
1187 edir = p.quat.to_euler('XYZ', Euler((0, 0, bRotate), 'XYZ'))
1188 edir[0] = 0
1189 edir[1] = 0
1191 edir[2] = -edir[2]
1192 tempPos.rotate(edir)
1194 dec = declination(p.quat)
1195 tempPos.rotate(Matrix.Rotation(radians(dec), 3, 'X'))
1197 edir[2] = -edir[2]
1198 tempPos.rotate(edir)
1199 else:
1200 tempPos.rotate(p.quat)
1202 newPoint.handle_right = p.co + tempPos
1204 # Make length variation inversely proportional to segSplits
1205 # lenV = (1 - min(segSplits[n], 1)) * lengthV[n]
1207 # Find branch length and the number of child stems.
1208 maxbL = scaleVal
1209 for l in length[:n + 1]:
1210 maxbL *= l
1211 lMax = length[n] # * uniform(1 - lenV, 1 + lenV)
1212 if n == 1:
1213 lShape = shapeRatio(shape, (1 - p.stemOffset) / (1 - baseSize), custom=customShape)
1214 else:
1215 lShape = shapeRatio(shapeS, (1 - p.stemOffset) / (1 - baseSize))
1216 branchL = p.lengthPar * lMax * lShape
1217 childStems = branches[min(3, n + 1)] * (0.1 + 0.9 * (branchL / maxbL))
1219 # If this is the last level before leaves then we need to generate the child points differently
1220 if (storeN == levels - 1):
1221 if leaves < 0:
1222 childStems = False
1223 else:
1224 childStems = leaves * (0.1 + 0.9 * (branchL / maxbL)) * shapeRatio(leafDist, (1 - p.offset))
1226 # print("n=%d, levels=%d, n'=%d, childStems=%s"%(n, levels, storeN, childStems))
1228 # Determine the starting and ending radii of the stem using the tapering of the stem
1229 startRad = min((p.radiusPar[0] * ((branchL / p.lengthPar) ** ratioPower)) * radiusTweak[n], p.radiusPar[1])
1230 if p.offset == 1:
1231 startRad = p.radiusPar[1]
1232 endRad = (startRad * (1 - taper[n])) ** ratioPower
1233 startRad = max(startRad, minRadius)
1234 endRad = max(endRad, minRadius)
1235 newPoint.radius = startRad
1237 # stem curvature
1238 curveVal = curve[n] / curveRes[n]
1239 curveVar = curveV[n] / curveRes[n]
1241 # curveVal = curveVal * (branchL / scaleVal)
1243 # Add the new stem to list of stems to grow and define which bone it will be parented to
1244 addstem(
1245 stemSpline(
1246 newSpline, curveVal, curveVar, attractUp[n],
1247 0, curveRes[n], branchL / curveRes[n], childStems,
1248 startRad, endRad, len(cu.splines) - 1, 0, p.quat
1252 bone = roundBone(p.parBone, boneStep[n - 1])
1253 if p.offset == 1:
1254 isend = True
1255 else:
1256 isend = False
1257 addsplinetobone((bone, isend))
1260 def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, currentScale, curve,
1261 curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, originalSplineToBone,
1262 originalCo, originalCurv, originalCurvV, originalHandleL, originalHandleR, originalLength,
1263 originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio, pruneWidth, pruneBase,
1264 pruneWidthPeak, randState, ratio, scaleVal, segSplits, splineToBone, splitAngle, splitAngleV,
1265 st, startPrune, branchDist, length, splitByLen, closeTip, nrings, splitBias, splitHeight,
1266 attractOut, rMode, lengthV, taperCrown, boneStep, rotate, rotateV):
1267 while startPrune and ((currentMax - currentMin) > 0.005):
1268 setstate(randState)
1270 # If the search will halt after this iteration, then set the adjustment of stem
1271 # length to take into account the pruning ratio
1272 if (currentMax - currentMin) < 0.01:
1273 currentScale = (currentScale - 1) * pruneRatio + 1
1274 startPrune = False
1275 forceSprout = True
1276 # Change the segment length of the stem by applying some scaling
1277 st.segL = originalLength * currentScale
1278 # To prevent millions of splines being created we delete any old ones and
1279 # replace them with only their first points to begin the spline again
1280 if deleteSpline:
1281 for x in splineList:
1282 cu.splines.remove(x.spline)
1283 newSpline = cu.splines.new('BEZIER')
1284 newPoint = newSpline.bezier_points[-1]
1285 newPoint.co = originalCo
1286 newPoint.handle_right = originalHandleR
1287 newPoint.handle_left = originalHandleL
1288 (newPoint.handle_left_type, newPoint.handle_right_type) = ('VECTOR', 'VECTOR')
1289 st.spline = newSpline
1290 st.curv = originalCurv
1291 st.curvV = originalCurvV
1292 st.seg = originalSeg
1293 st.p = newPoint
1294 newPoint.radius = st.radS
1295 splineToBone = originalSplineToBone
1297 # Initialise the spline list for those contained in the current level of branching
1298 splineList = [st]
1300 # split length variation
1301 stemsegL = splineList[0].segL # initial segment length used for variation
1302 splineList[0].segL = stemsegL * uniform(1 - lengthV[n], 1 + lengthV[n]) # variation for first stem
1304 # For each of the segments of the stem which must be grown we have to add to each spline in splineList
1305 for k in range(curveRes[n]):
1306 # Make a copy of the current list to avoid continually adding to the list we're iterating over
1307 tempList = splineList[:]
1308 # print('Leng: ', len(tempList))
1310 # for curve variation
1311 if curveRes[n] > 1:
1312 kp = (k / (curveRes[n] - 1)) # * 2
1313 else:
1314 kp = 1.0
1316 # split bias
1317 splitValue = segSplits[n]
1318 if n == 0:
1319 splitValue = ((2 * splitBias) * (kp - .5) + 1) * splitValue
1320 splitValue = max(splitValue, 0.0)
1322 # For each of the splines in this list set the number of splits and then grow it
1323 for spl in tempList:
1324 # adjust numSplit
1325 lastsplit = getattr(spl, 'splitlast', 0)
1326 splitVal = splitValue
1327 if lastsplit == 0:
1328 splitVal = splitValue * 1.33
1329 elif lastsplit == 1:
1330 splitVal = splitValue * splitValue
1332 if k == 0:
1333 numSplit = 0
1334 elif (n == 0) and (k < ((curveRes[n] - 1) * splitHeight)) and (k != 1):
1335 numSplit = 0
1336 elif (k == 1) and (n == 0):
1337 numSplit = baseSplits
1338 # always split at splitHeight
1339 elif (n == 0) and (k == int((curveRes[n] - 1) * splitHeight) + 1) and (splitVal > 0):
1340 numSplit = 1
1341 else:
1342 if (n >= 1) and splitByLen:
1343 L = ((spl.segL * curveRes[n]) / scaleVal)
1344 lf = 1
1345 for l in length[:n + 1]:
1346 lf *= l
1347 L = L / lf
1348 numSplit = splits2(splitVal * L)
1349 else:
1350 numSplit = splits2(splitVal)
1352 if (k == int(curveRes[n] / 2 + 0.5)) and (curveBack[n] != 0):
1353 spl.curv += 2 * (curveBack[n] / curveRes[n]) # was -4 *
1355 growSpline(
1356 n, spl, numSplit, splitAngle[n], splitAngleV[n], splineList,
1357 handles, splineToBone, closeTip, kp, splitHeight, attractOut[n],
1358 stemsegL, lengthV[n], taperCrown, boneStep, rotate, rotateV
1361 # If pruning is enabled then we must check to see if the end of the spline is within the envelope
1362 if prune:
1363 # Check each endpoint to see if it is inside
1364 for s in splineList:
1365 coordMag = (s.spline.bezier_points[-1].co.xy).length
1366 ratio = (scaleVal - s.spline.bezier_points[-1].co.z) / (scaleVal * max(1 - pruneBase, 1e-6))
1367 # Don't think this if part is needed
1368 if (n == 0) and (s.spline.bezier_points[-1].co.z < pruneBase * scaleVal):
1369 insideBool = True # Init to avoid UnboundLocalError later
1370 else:
1371 insideBool = (
1372 (coordMag / scaleVal) < pruneWidth * shapeRatio(9, ratio, pruneWidthPeak, prunePowerHigh,
1373 prunePowerLow))
1374 # If the point is not inside then we adjust the scale and current search bounds
1375 if not insideBool:
1376 oldMax = currentMax
1377 currentMax = currentScale
1378 currentScale = 0.5 * (currentMax + currentMin)
1379 break
1380 # If the scale is the original size and the point is inside then
1381 # we need to make sure it won't be pruned or extended to the edge of the envelope
1382 if insideBool and (currentScale != 1):
1383 currentMin = currentScale
1384 currentMax = oldMax
1385 currentScale = 0.5 * (currentMax + currentMin)
1386 if insideBool and ((currentMax - currentMin) == 1):
1387 currentMin = 1
1389 # If the search will halt on the next iteration then we need
1390 # to make sure we sprout child points to grow the next splines or leaves
1391 if (((currentMax - currentMin) < 0.005) or not prune) or forceSprout:
1392 if (n == 0) and (rMode != "original"):
1393 tVals = findChildPoints2(splineList, st.children)
1394 else:
1395 tVals = findChildPoints(splineList, st.children)
1396 # print("debug tvals[%d] , splineList[%d], %s" % ( len(tVals), len(splineList), st.children))
1397 # If leaves is -ve then we need to make sure the only point which sprouts is the end of the spline
1398 if not st.children:
1399 tVals = [1.0]
1400 # remove some of the points because of baseSize
1401 trimNum = int(baseSize * (len(tVals) + 1))
1402 tVals = tVals[trimNum:]
1404 # grow branches in rings
1405 if (n == 0) and (nrings > 0):
1406 # tVals = [(floor(t * nrings)) / nrings for t in tVals[:-1]]
1407 tVals = [(floor(t * nrings) / nrings) * uniform(.995, 1.005) for t in tVals[:-1]]
1408 tVals.append(1)
1409 tVals = [t for t in tVals if t > baseSize]
1411 # branch distribution
1412 if n == 0:
1413 tVals = [((t - baseSize) / (1 - baseSize)) for t in tVals]
1414 if branchDist < 1.0:
1415 tVals = [t ** (1 / branchDist) for t in tVals]
1416 else:
1417 tVals = [1 - (1 - t) ** branchDist for t in tVals]
1418 tVals = [t * (1 - baseSize) + baseSize for t in tVals]
1420 # For all the splines, we interpolate them and add the new points to the list of child points
1421 maxOffset = max([s.offsetLen + (len(s.spline.bezier_points) - 1) * s.segL for s in splineList])
1422 for s in splineList:
1423 # print(str(n)+'level: ', s.segMax*s.segL)
1424 childP.extend(interpStem(s, tVals, s.segMax * s.segL, s.radS, maxOffset, baseSize))
1426 # Force the splines to be deleted
1427 deleteSpline = True
1428 # If pruning isn't enabled then make sure it doesn't loop
1429 if not prune:
1430 startPrune = False
1431 return ratio, splineToBone
1434 # calculate taper automatically
1435 def findtaper(length, taper, shape, shapeS, levels, customShape):
1436 taperS = []
1437 for i, t in enumerate(length):
1438 if i == 0:
1439 shp = 1.0
1440 elif i == 1:
1441 shp = shapeRatio(shape, 0, custom=customShape)
1442 else:
1443 shp = shapeRatio(shapeS, 0)
1444 t = t * shp
1445 taperS.append(t)
1447 taperP = []
1448 for i, t in enumerate(taperS):
1449 pm = 1
1450 for x in range(i + 1):
1451 pm *= taperS[x]
1452 taperP.append(pm)
1454 taperR = []
1455 for i, t in enumerate(taperP):
1456 t = sum(taperP[i:levels])
1457 taperR.append(t)
1459 taperT = []
1460 for i, t in enumerate(taperR):
1461 try:
1462 t = taperP[i] / taperR[i]
1463 except ZeroDivisionError:
1464 t = 1.0
1465 taperT.append(t)
1467 taperT = [t * taper[i] for i, t in enumerate(taperT)]
1469 return taperT
1472 def addTree(props):
1473 global splitError
1474 # startTime = time.time()
1475 # Set the seed for repeatable results
1476 seed(props.seed)
1478 # Set all other variables
1479 levels = props.levels
1480 length = props.length
1481 lengthV = props.lengthV
1482 taperCrown = props.taperCrown
1483 branches = props.branches
1484 curveRes = props.curveRes
1485 curve = toRad(props.curve)
1486 curveV = toRad(props.curveV)
1487 curveBack = toRad(props.curveBack)
1488 baseSplits = props.baseSplits
1489 segSplits = props.segSplits
1490 splitByLen = props.splitByLen
1491 rMode = props.rMode
1492 splitAngle = toRad(props.splitAngle)
1493 splitAngleV = toRad(props.splitAngleV)
1494 scale = props.scale
1495 scaleV = props.scaleV
1496 attractUp = props.attractUp
1497 attractOut = props.attractOut
1498 shape = int(props.shape)
1499 shapeS = int(props.shapeS)
1500 customShape = props.customShape
1501 branchDist = props.branchDist
1502 nrings = props.nrings
1503 baseSize = props.baseSize
1504 baseSize_s = props.baseSize_s
1505 splitHeight = props.splitHeight
1506 splitBias = props.splitBias
1507 ratio = props.ratio
1508 minRadius = props.minRadius
1509 closeTip = props.closeTip
1510 rootFlare = props.rootFlare
1511 autoTaper = props.autoTaper
1512 taper = props.taper
1513 radiusTweak = props.radiusTweak
1514 ratioPower = props.ratioPower
1515 downAngle = toRad(props.downAngle)
1516 downAngleV = toRad(props.downAngleV)
1517 rotate = toRad(props.rotate)
1518 rotateV = toRad(props.rotateV)
1519 scale0 = props.scale0
1520 scaleV0 = props.scaleV0
1521 prune = props.prune
1522 pruneWidth = props.pruneWidth
1523 pruneBase = props.pruneBase
1524 pruneWidthPeak = props.pruneWidthPeak
1525 prunePowerLow = props.prunePowerLow
1526 prunePowerHigh = props.prunePowerHigh
1527 pruneRatio = props.pruneRatio
1528 leafDownAngle = radians(props.leafDownAngle)
1529 leafDownAngleV = radians(props.leafDownAngleV)
1530 leafRotate = radians(props.leafRotate)
1531 leafRotateV = radians(props.leafRotateV)
1532 leafScale = props.leafScale
1533 leafScaleX = props.leafScaleX
1534 leafScaleT = props.leafScaleT
1535 leafScaleV = props.leafScaleV
1536 leafShape = props.leafShape
1537 leafDupliObj = props.leafDupliObj
1538 bend = props.bend
1539 leafangle = props.leafangle
1540 horzLeaves = props.horzLeaves
1541 leafDist = int(props.leafDist)
1542 bevelRes = props.bevelRes
1543 resU = props.resU
1545 useArm = props.useArm
1546 previewArm = props.previewArm
1547 armAnim = props.armAnim
1548 leafAnim = props.leafAnim
1549 frameRate = props.frameRate
1550 loopFrames = props.loopFrames
1552 # windSpeed = props.windSpeed
1553 # windGust = props.windGust
1555 wind = props.wind
1556 gust = props.gust
1557 gustF = props.gustF
1559 af1 = props.af1
1560 af2 = props.af2
1561 af3 = props.af3
1563 makeMesh = props.makeMesh
1564 armLevels = props.armLevels
1565 boneStep = props.boneStep
1567 useOldDownAngle = props.useOldDownAngle
1568 useParentAngle = props.useParentAngle
1570 if not makeMesh:
1571 boneStep = [1, 1, 1, 1]
1573 # taper
1574 if autoTaper:
1575 taper = findtaper(length, taper, shape, shapeS, levels, customShape)
1576 # pLevels = branches[0]
1577 # taper = findtaper(length, taper, shape, shapeS, pLevels, customShape)
1579 leafObj = None
1581 # Some effects can be turned ON and OFF, the necessary variables are changed here
1582 if not props.bevel:
1583 bevelDepth = 0.0
1584 else:
1585 bevelDepth = 1.0
1587 if not props.showLeaves:
1588 leaves = 0
1589 else:
1590 leaves = props.leaves
1592 if props.handleType == '0':
1593 handles = 'AUTO'
1594 else:
1595 handles = 'VECTOR'
1597 for ob in bpy.context.view_layer.objects:
1598 ob.select_set(state=False)
1600 # Initialise the tree object and curve and adjust the settings
1601 cu = bpy.data.curves.new('tree', 'CURVE')
1602 treeOb = bpy.data.objects.new('tree', cu)
1603 bpy.context.scene.collection.objects.link(treeOb)
1605 # treeOb.location=bpy.context.scene.cursor.location attractUp
1607 cu.dimensions = '3D'
1608 cu.fill_mode = 'FULL'
1609 cu.bevel_depth = bevelDepth
1610 cu.bevel_resolution = bevelRes
1612 # Fix the scale of the tree now
1613 scaleVal = scale + uniform(-scaleV, scaleV)
1614 scaleVal += copysign(1e-6, scaleVal) # Move away from zero to avoid div by zero
1616 pruneBase = min(pruneBase, baseSize)
1617 # If pruning is turned on we need to draw the pruning envelope
1618 if prune:
1619 enHandle = 'VECTOR'
1620 enNum = 128
1621 enCu = bpy.data.curves.new('envelope', 'CURVE')
1622 enOb = bpy.data.objects.new('envelope', enCu)
1623 enOb.parent = treeOb
1624 bpy.context.scene.collection.objects.link(enOb)
1625 newSpline = enCu.splines.new('BEZIER')
1626 newPoint = newSpline.bezier_points[-1]
1627 newPoint.co = Vector((0, 0, scaleVal))
1628 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1629 # Set the coordinates by varying the z value, envelope will be aligned to the x-axis
1630 for c in range(enNum):
1631 newSpline.bezier_points.add(1)
1632 newPoint = newSpline.bezier_points[-1]
1633 ratioVal = (c + 1) / (enNum)
1634 zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
1635 newPoint.co = Vector(
1637 scaleVal * pruneWidth *
1638 shapeRatio(9, ratioVal, pruneWidthPeak, prunePowerHigh, prunePowerLow),
1639 0, zVal
1642 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1643 newSpline = enCu.splines.new('BEZIER')
1644 newPoint = newSpline.bezier_points[-1]
1645 newPoint.co = Vector((0, 0, scaleVal))
1646 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1647 # Create a second envelope but this time on the y-axis
1648 for c in range(enNum):
1649 newSpline.bezier_points.add(1)
1650 newPoint = newSpline.bezier_points[-1]
1651 ratioVal = (c + 1) / (enNum)
1652 zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
1653 newPoint.co = Vector(
1655 0, scaleVal * pruneWidth *
1656 shapeRatio(9, ratioVal, pruneWidthPeak, prunePowerHigh, prunePowerLow),
1657 zVal
1660 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1662 childP = []
1663 stemList = []
1665 levelCount = []
1666 splineToBone = deque([''])
1667 addsplinetobone = splineToBone.append
1669 # Each of the levels needed by the user we grow all the splines
1670 for n in range(levels):
1671 storeN = n
1672 stemList = deque()
1673 addstem = stemList.append
1674 # If n is used as an index to access parameters for the tree
1675 # it must be at most 3 or it will reference outside the array index
1676 n = min(3, n)
1677 splitError = 0.0
1679 # closeTip only on last level
1680 closeTipp = all([(n == levels - 1), closeTip])
1682 # If this is the first level of growth (the trunk) then we need some special work to begin the tree
1683 if n == 0:
1684 kickstart_trunk(addstem, levels, leaves, branches, cu, curve, curveRes,
1685 curveV, attractUp, length, lengthV, ratio, ratioPower, resU,
1686 scale0, scaleV0, scaleVal, taper, minRadius, rootFlare)
1687 # If this isn't the trunk then we may have multiple stem to initialise
1688 else:
1689 # For each of the points defined in the list of stem starting points we need to grow a stem.
1690 fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, curve, curveBack,
1691 curveRes, curveV, attractUp, downAngle, downAngleV, leafDist, leaves, length, lengthV,
1692 levels, n, ratioPower, resU, rotate, rotateV, scaleVal, shape, storeN,
1693 taper, shapeS, minRadius, radiusTweak, customShape, rMode, segSplits,
1694 useOldDownAngle, useParentAngle, boneStep)
1696 # change base size for each level
1697 if n > 0:
1698 baseSize *= baseSize_s # decrease at each level
1699 if (n == levels - 1):
1700 baseSize = 0
1702 childP = []
1703 # Now grow each of the stems in the list of those to be extended
1704 for st in stemList:
1705 # When using pruning, we need to ensure that the random effects
1706 # will be the same for each iteration to make sure the problem is linear
1707 randState = getstate()
1708 startPrune = True
1709 lengthTest = 0.0
1710 # Store all the original values for the stem to make sure
1711 # we have access after it has been modified by pruning
1712 originalLength = st.segL
1713 originalCurv = st.curv
1714 originalCurvV = st.curvV
1715 originalSeg = st.seg
1716 originalHandleR = st.p.handle_right.copy()
1717 originalHandleL = st.p.handle_left.copy()
1718 originalCo = st.p.co.copy()
1719 currentMax = 1.0
1720 currentMin = 0.0
1721 currentScale = 1.0
1722 oldMax = 1.0
1723 deleteSpline = False
1724 originalSplineToBone = copy.copy(splineToBone)
1725 forceSprout = False
1726 # Now do the iterative pruning, this uses a binary search and halts once the difference
1727 # between upper and lower bounds of the search are less than 0.005
1728 ratio, splineToBone = perform_pruning(
1729 baseSize, baseSplits, childP, cu, currentMax, currentMin,
1730 currentScale, curve, curveBack, curveRes, deleteSpline, forceSprout,
1731 handles, n, oldMax, originalSplineToBone, originalCo, originalCurv,
1732 originalCurvV, originalHandleL, originalHandleR, originalLength,
1733 originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio,
1734 pruneWidth, pruneBase, pruneWidthPeak, randState, ratio, scaleVal,
1735 segSplits, splineToBone, splitAngle, splitAngleV, st, startPrune,
1736 branchDist, length, splitByLen, closeTipp, nrings, splitBias,
1737 splitHeight, attractOut, rMode, lengthV, taperCrown, boneStep,
1738 rotate, rotateV
1741 levelCount.append(len(cu.splines))
1743 # If we need to add leaves, we do it here
1744 leafVerts = []
1745 leafFaces = []
1746 leafNormals = []
1748 leafMesh = None # in case we aren't creating leaves, we'll still have the variable
1750 leafP = []
1751 if leaves:
1752 oldRot = 0.0
1753 n = min(3, n + 1)
1754 # For each of the child points we add leaves
1755 for cp in childP:
1756 # If the special flag is set then we need to add several leaves at the same location
1757 if leaves < 0:
1758 oldRot = -leafRotate / 2
1759 for g in range(abs(leaves)):
1760 (vertTemp, faceTemp, normal, oldRot) = genLeafMesh(
1761 leafScale, leafScaleX, leafScaleT,
1762 leafScaleV, cp.co, cp.quat, cp.offset,
1763 len(leafVerts), leafDownAngle, leafDownAngleV,
1764 leafRotate, leafRotateV,
1765 oldRot, bend, leaves, leafShape,
1766 leafangle, horzLeaves
1768 leafVerts.extend(vertTemp)
1769 leafFaces.extend(faceTemp)
1770 leafNormals.extend(normal)
1771 leafP.append(cp)
1772 # Otherwise just add the leaves like splines
1773 else:
1774 (vertTemp, faceTemp, normal, oldRot) = genLeafMesh(
1775 leafScale, leafScaleX, leafScaleT, leafScaleV,
1776 cp.co, cp.quat, cp.offset, len(leafVerts),
1777 leafDownAngle, leafDownAngleV, leafRotate,
1778 leafRotateV, oldRot, bend, leaves, leafShape,
1779 leafangle, horzLeaves
1781 leafVerts.extend(vertTemp)
1782 leafFaces.extend(faceTemp)
1783 leafNormals.extend(normal)
1784 leafP.append(cp)
1786 # Create the leaf mesh and object, add geometry using from_pydata,
1787 # edges are currently added by validating the mesh which isn't great
1788 leafMesh = bpy.data.meshes.new('leaves')
1789 leafObj = bpy.data.objects.new('leaves', leafMesh)
1790 bpy.context.scene.collection.objects.link(leafObj)
1791 leafObj.parent = treeOb
1792 leafMesh.from_pydata(leafVerts, (), leafFaces)
1794 # set vertex normals for dupliVerts
1795 if leafShape == 'dVert':
1796 leafMesh.vertices.foreach_set('normal', leafNormals)
1798 # enable duplication
1799 if leafShape == 'dFace':
1800 leafObj.instance_type = "FACES"
1801 leafObj.use_instance_faces_scale = True
1802 leafObj.instance_faces_scale = 10.0
1803 try:
1804 if leafDupliObj not in "NONE":
1805 bpy.data.objects[leafDupliObj].parent = leafObj
1806 except KeyError:
1807 pass
1808 elif leafShape == 'dVert':
1809 leafObj.instance_type = "VERTS"
1810 leafObj.use_instance_vertices_rotation = True
1811 try:
1812 if leafDupliObj not in "NONE":
1813 bpy.data.objects[leafDupliObj].parent = leafObj
1814 except KeyError:
1815 pass
1817 # add leaf UVs
1818 if leafShape == 'rect':
1819 leafMesh.uv_layers.new(name='leafUV')
1820 uvlayer = leafMesh.uv_layers.active.data
1822 u1 = .5 * (1 - leafScaleX)
1823 u2 = 1 - u1
1825 for i in range(0, len(leafFaces)):
1826 uvlayer[i * 4 + 0].uv = Vector((u2, 0))
1827 uvlayer[i * 4 + 1].uv = Vector((u2, 1))
1828 uvlayer[i * 4 + 2].uv = Vector((u1, 1))
1829 uvlayer[i * 4 + 3].uv = Vector((u1, 0))
1831 elif leafShape == 'hex':
1832 leafMesh.uv_layers.new(name='leafUV')
1833 uvlayer = leafMesh.uv_layers.active.data
1835 u1 = .5 * (1 - leafScaleX)
1836 u2 = 1 - u1
1838 for i in range(0, int(len(leafFaces) / 2)):
1839 uvlayer[i * 8 + 0].uv = Vector((.5, 0))
1840 uvlayer[i * 8 + 1].uv = Vector((u1, 1 / 3))
1841 uvlayer[i * 8 + 2].uv = Vector((u1, 2 / 3))
1842 uvlayer[i * 8 + 3].uv = Vector((.5, 1))
1844 uvlayer[i * 8 + 4].uv = Vector((.5, 0))
1845 uvlayer[i * 8 + 5].uv = Vector((.5, 1))
1846 uvlayer[i * 8 + 6].uv = Vector((u2, 2 / 3))
1847 uvlayer[i * 8 + 7].uv = Vector((u2, 1 / 3))
1849 leafMesh.validate()
1851 leafVertSize = {'hex': 6, 'rect': 4, 'dFace': 4, 'dVert': 1}[leafShape]
1853 armLevels = min(armLevels, levels)
1854 armLevels -= 1
1856 # unpack vars from splineToBone
1857 splineToBone1 = splineToBone
1858 splineToBone = [s[0] if len(s) > 1 else s for s in splineToBone1]
1859 isend = [s[1] if len(s) > 1 else False for s in splineToBone1]
1860 issplit = [s[2] if len(s) > 2 else False for s in splineToBone1]
1861 splitPidx = [s[3] if len(s) > 2 else 0 for s in splineToBone1]
1863 # If we need an armature we add it
1864 if useArm:
1865 # Create the armature and objects
1866 create_armature(
1867 armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSize,
1868 leaves, levelCount, splineToBone, treeOb, wind, gust, gustF, af1,
1869 af2, af3, leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep
1872 # print(time.time()-startTime)
1874 # mesh branches
1875 if makeMesh:
1876 t1 = time.time()
1878 treeMesh = bpy.data.meshes.new('treemesh')
1879 treeObj = bpy.data.objects.new('treemesh', treeMesh)
1880 bpy.context.scene.collection.objects.link(treeObj)
1882 treeVerts = []
1883 treeEdges = []
1884 root_vert = []
1885 vert_radius = []
1886 vertexGroups = OrderedDict()
1887 lastVerts = []
1889 for i, curve in enumerate(cu.splines):
1890 points = curve.bezier_points
1892 # find branching level
1893 level = 0
1894 for l, c in enumerate(levelCount):
1895 if i < c:
1896 level = l
1897 break
1898 level = min(level, 3)
1900 step = boneStep[level]
1901 vindex = len(treeVerts)
1903 p1 = points[0]
1905 # add extra vertex for splits
1906 if issplit[i]:
1907 pb = int(splineToBone[i][4:-4])
1908 pn = splitPidx[i] # int(splineToBone[i][-3:])
1909 p_1 = cu.splines[pb].bezier_points[pn]
1910 p_2 = cu.splines[pb].bezier_points[pn + 1]
1911 p = evalBez(p_1.co, p_1.handle_right, p_2.handle_left, p_2.co, 1 - 1 / (resU + 1))
1912 treeVerts.append(p)
1914 root_vert.append(False)
1915 vert_radius.append((p1.radius * .75, p1.radius * .75))
1916 treeEdges.append([vindex, vindex + 1])
1917 vindex += 1
1919 if isend[i]:
1920 parent = lastVerts[int(splineToBone[i][4:-4])]
1921 vindex -= 1
1922 else:
1923 # add first point
1924 treeVerts.append(p1.co)
1925 root_vert.append(True)
1926 vert_radius.append((p1.radius, p1.radius))
1928 # add extra vertex for splits
1929 if issplit[i]:
1930 p2 = points[1]
1931 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, .001)
1932 treeVerts.append(p)
1933 root_vert.append(False)
1934 vert_radius.append((p1.radius, p1.radius)) #(p1.radius * .95, p1.radius * .95)
1935 treeEdges.append([vindex,vindex+1])
1936 vindex += 1
1938 # dont make vertex group if above armLevels
1939 if (i >= levelCount[armLevels]):
1940 idx = i
1941 groupName = splineToBone[idx]
1942 g = True
1943 while groupName not in vertexGroups:
1944 # find parent bone of parent bone
1945 b = splineToBone[idx]
1946 idx = int(b[4:-4])
1947 groupName = splineToBone[idx]
1948 else:
1949 g = False
1951 for n, p2 in enumerate(points[1:]):
1952 if not g:
1953 groupName = 'bone' + (str(i)).rjust(3, '0') + '.' + (str(n)).rjust(3, '0')
1954 groupName = roundBone(groupName, step)
1955 if groupName not in vertexGroups:
1956 vertexGroups[groupName] = []
1958 # parent first vert in split to parent branch bone
1959 if issplit[i] and n == 0:
1960 if g:
1961 vertexGroups[groupName].append(vindex - 1)
1962 else:
1963 vertexGroups[splineToBone[i]].append(vindex - 1)
1965 for f in range(1, resU + 1):
1966 pos = f / resU
1967 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, pos)
1968 radius = p1.radius + (p2.radius - p1.radius) * pos
1970 treeVerts.append(p)
1971 root_vert.append(False)
1972 vert_radius.append((radius, radius))
1974 if (isend[i]) and (n == 0) and (f == 1):
1975 edge = [parent, n * resU + f + vindex]
1976 else:
1977 edge = [n * resU + f + vindex - 1, n * resU + f + vindex]
1978 # add vert to group
1979 vertexGroups[groupName].append(n * resU + f + vindex - 1)
1980 treeEdges.append(edge)
1982 vertexGroups[groupName].append(n * resU + resU + vindex)
1984 p1 = p2
1986 lastVerts.append(len(treeVerts) - 1)
1988 treeMesh.from_pydata(treeVerts, treeEdges, ())
1990 for group in vertexGroups:
1991 treeObj.vertex_groups.new(name=group)
1992 treeObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
1994 # add armature
1995 if useArm:
1996 armMod = treeObj.modifiers.new('windSway', 'ARMATURE')
1997 if previewArm:
1998 bpy.data.objects['treeArm'].hide_viewport = True
1999 bpy.data.armatures['tree'].display_type = 'STICK'
2000 armMod.object = bpy.data.objects['treeArm']
2001 armMod.use_bone_envelopes = False
2002 armMod.use_vertex_groups = True
2003 treeObj.parent = bpy.data.objects['treeArm']
2005 # add skin modifier and set data
2006 skinMod = treeObj.modifiers.new('Skin', 'SKIN')
2007 skinMod.use_smooth_shade = True
2008 if previewArm:
2009 skinMod.show_viewport = False
2010 skindata = treeObj.data.skin_vertices[0].data
2011 for i, radius in enumerate(vert_radius):
2012 skindata[i].radius = radius
2013 skindata[i].use_root = root_vert[i]
2015 print("mesh time", time.time() - t1)