Fix: Node Wrangler: error when previewing without Geo output socket
[blender-addons.git] / add_curve_sapling / utils.py
blobea1026c10fb55de0541dd7f4a1b238ced8d4085f
1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
7 import time
8 import copy
10 from mathutils import (
11 Euler,
12 Matrix,
13 Vector,
15 from math import pi, sin, degrees, radians, atan2, copysign, cos, acos
16 from math import floor
17 from random import random, uniform, seed, choice, getstate, setstate, randint
18 from collections import deque, OrderedDict
20 tau = 2 * pi
22 # Initialise the split error and axis vectors
23 splitError = 0.0
24 zAxis = Vector((0, 0, 1))
25 yAxis = Vector((0, 1, 0))
26 xAxis = Vector((1, 0, 0))
29 # This class will contain a part of the tree which needs to be extended and the required tree parameters
30 class stemSpline:
31 def __init__(self, spline, curvature, curvatureV, attractUp, segments, maxSegs,
32 segLength, childStems, stemRadStart, stemRadEnd, splineNum, ofst, pquat):
33 self.spline = spline
34 self.p = spline.bezier_points[-1]
35 self.curv = curvature
36 self.curvV = curvatureV
37 self.vertAtt = attractUp
38 self.seg = segments
39 self.segMax = maxSegs
40 self.segL = segLength
41 self.children = childStems
42 self.radS = stemRadStart
43 self.radE = stemRadEnd
44 self.splN = splineNum
45 self.offsetLen = ofst
46 self.patentQuat = pquat
47 self.curvSignx = 1
48 self.curvSigny = 1
50 # This method determines the quaternion of the end of the spline
51 def quat(self):
52 if len(self.spline.bezier_points) == 1:
53 return ((self.spline.bezier_points[-1].handle_right -
54 self.spline.bezier_points[-1].co).normalized()).to_track_quat('Z', 'Y')
55 else:
56 return ((self.spline.bezier_points[-1].co -
57 self.spline.bezier_points[-2].co).normalized()).to_track_quat('Z', 'Y')
59 # Determine the declination
60 def dec(self):
61 tempVec = zAxis.copy()
62 tempVec.rotate(self.quat())
63 return zAxis.angle(tempVec)
65 # Update the end of the spline and increment the segment count
66 def updateEnd(self):
67 self.p = self.spline.bezier_points[-1]
68 self.seg += 1
71 # This class contains the data for a point where a new branch will sprout
72 class childPoint:
73 def __init__(self, coords, quat, radiusPar, offset, sOfst, lengthPar, parBone):
74 self.co = coords
75 self.quat = quat
76 self.radiusPar = radiusPar
77 self.offset = offset
78 self.stemOffset = sOfst
79 self.lengthPar = lengthPar
80 self.parBone = parBone
83 # This function calculates the shape ratio as defined in the paper
84 def shapeRatio(shape, ratio, pruneWidthPeak=0.0, prunePowerHigh=0.0, prunePowerLow=0.0, custom=None):
85 if shape == 0:
86 return 0.05 + 0.95 * ratio # 0.2 + 0.8 * ratio
87 elif shape == 1:
88 return 0.2 + 0.8 * sin(pi * ratio)
89 elif shape == 2:
90 return 0.2 + 0.8 * sin(0.5 * pi * ratio)
91 elif shape == 3:
92 return 1.0
93 elif shape == 4:
94 return 0.5 + 0.5 * ratio
95 elif shape == 5:
96 if ratio <= 0.7:
97 return 0.05 + 0.95 * ratio / 0.7
98 else:
99 return 0.05 + 0.95 * (1.0 - ratio) / 0.3
100 elif shape == 6:
101 return 1.0 - 0.8 * ratio
102 elif shape == 7:
103 if ratio <= 0.7:
104 return 0.5 + 0.5 * ratio / 0.7
105 else:
106 return 0.5 + 0.5 * (1.0 - ratio) / 0.3
107 elif shape == 8:
108 r = 1 - ratio
109 if r == 1:
110 v = custom[3]
111 elif r >= custom[2]:
112 pos = (r - custom[2]) / (1 - custom[2])
113 # if (custom[0] >= custom[1] <= custom[3]) or (custom[0] <= custom[1] >= custom[3]):
114 pos = pos * pos
115 v = (pos * (custom[3] - custom[1])) + custom[1]
116 else:
117 pos = r / custom[2]
118 # if (custom[0] >= custom[1] <= custom[3]) or (custom[0] <= custom[1] >= custom[3]):
119 pos = 1 - (1 - pos) * (1 - pos)
120 v = (pos * (custom[1] - custom[0])) + custom[0]
122 return v
124 elif shape == 9:
125 if (ratio < (1 - pruneWidthPeak)) and (ratio > 0.0):
126 return ((ratio / (1 - pruneWidthPeak))**prunePowerHigh)
127 elif (ratio >= (1 - pruneWidthPeak)) and (ratio < 1.0):
128 return (((1 - ratio) / pruneWidthPeak)**prunePowerLow)
129 else:
130 return 0.0
132 elif shape == 10:
133 return 0.5 + 0.5 * (1 - ratio)
136 # This function determines the actual number of splits at a given point using the global error
137 def splits(n):
138 global splitError
139 nEff = round(n + splitError, 0)
140 splitError -= (nEff - n)
141 return int(nEff)
144 def splits2(n):
145 r = random()
146 if r < n:
147 return 1
148 else:
149 return 0
152 def splits3(n):
153 ni = int(n)
154 nf = n - int(n)
155 r = random()
156 if r < nf:
157 return ni + 1
158 else:
159 return ni + 0
162 # Determine the declination from a given quaternion
163 def declination(quat):
164 tempVec = zAxis.copy()
165 tempVec.rotate(quat)
166 tempVec.normalize()
167 return degrees(acos(tempVec.z))
170 # Determines the angle of upward rotation of a segment due to attractUp
171 def curveUp(attractUp, quat, curveRes):
172 tempVec = yAxis.copy()
173 tempVec.rotate(quat)
174 tempVec.normalize()
176 dec = radians(declination(quat))
177 curveUpAng = attractUp * dec * abs(tempVec.z) / curveRes
178 if (-dec + curveUpAng) < -pi:
179 curveUpAng = -pi + dec
180 if (dec - curveUpAng) < 0:
181 curveUpAng = dec
182 return curveUpAng
185 # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
186 def evalBez(p1, h1, h2, p2, t):
187 return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2
190 # Evaluate the unit tangent on a bezier curve for t
191 def evalBezTan(p1, h1, h2, p2, t):
192 return (
193 (-3 * (1 - t)**2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t)**2) * h1 +
194 (-3 * (t**2) + 6 * t * (1 - t)) * h2 + (3 * t**2) * p2
195 ).normalized()
198 # Determine the range of t values along a splines length where child stems are formed
199 def findChildPoints(stemList, numChild):
200 numPoints = sum([len(n.spline.bezier_points) for n in stemList])
201 numSplines = len(stemList)
202 numSegs = numPoints - numSplines
203 numPerSeg = numChild / numSegs
204 numMain = round(numPerSeg * stemList[0].segMax, 0)
205 return [(a + 1) / (numMain) for a in range(int(numMain))]
208 def findChildPoints2(stemList, numChild):
209 return [(a + 1) / (numChild) for a in range(int(numChild))]
212 # Find the coordinates, quaternion and radius for each t on the stem
213 def interpStem1(stem, tVals, lPar, parRad):
214 points = stem.spline.bezier_points
215 numPoints = len(points)
216 checkVal = (stem.segMax - (numPoints - 1)) / stem.segMax
217 # Loop through all the parametric values to be determined
218 tempList = deque()
219 for t in tVals:
220 if t == 1.0:
221 index = numPoints - 2
222 coord = points[-1].co
223 quat = (points[-1].handle_right - points[-1].co).to_track_quat('Z', 'Y')
224 radius = points[-1].radius
226 tempList.append(
227 childPoint(coord, quat, (parRad, radius), t, lPar, 'bone' +
228 (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0')))
231 elif (t >= checkVal) and (t < 1.0):
232 scaledT = (t - checkVal) / ((1 - checkVal) + .0001)
233 length = (numPoints - 1) * scaledT
234 index = int(length)
236 tTemp = length - index
237 coord = evalBez(
238 points[index].co, points[index].handle_right,
239 points[index + 1].handle_left, points[index + 1].co, tTemp
241 quat = (
242 evalBezTan(
243 points[index].co, points[index].handle_right,
244 points[index + 1].handle_left, points[index + 1].co, tTemp)
245 ).to_track_quat('Z', 'Y')
246 # Not sure if this is the parent radius at the child point or parent start radius
247 radius = (1 - tTemp) * points[index].radius + tTemp * points[index + 1].radius
249 tempList.append(
250 childPoint(
251 coord, quat, (parRad, radius), t, lPar, 'bone' +
252 (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0')))
254 return tempList
257 def interpStem(stem, tVals, lPar, parRad, maxOffset, baseSize):
258 points = stem.spline.bezier_points
259 numSegs = len(points) - 1
260 stemLen = stem.segL * numSegs
262 checkBottom = stem.offsetLen / maxOffset
263 checkTop = checkBottom + (stemLen / maxOffset)
265 # Loop through all the parametric values to be determined
266 tempList = deque()
267 for t in tVals:
268 if (t >= checkBottom) and (t <= checkTop) and (t < 1.0):
269 scaledT = (t - checkBottom) / (checkTop - checkBottom)
270 ofst = ((t - baseSize) / (checkTop - baseSize)) * (1 - baseSize) + baseSize
272 length = numSegs * scaledT
273 index = int(length)
274 tTemp = length - index
276 coord = evalBez(
277 points[index].co, points[index].handle_right,
278 points[index + 1].handle_left, points[index + 1].co, tTemp
280 quat = (
281 evalBezTan(
282 points[index].co, points[index].handle_right,
283 points[index + 1].handle_left, points[index + 1].co, tTemp
285 ).to_track_quat('Z', 'Y')
286 # Not sure if this is the parent radius at the child point or parent start radius
287 radius = (1 - tTemp) * points[index].radius + tTemp * points[index + 1].radius
289 tempList.append(
290 childPoint(
291 coord, quat, (parRad, radius), t, ofst, lPar,
292 'bone' + (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0')))
295 # add stem at tip
296 index = numSegs - 1
297 coord = points[-1].co
298 quat = (points[-1].handle_right - points[-1].co).to_track_quat('Z', 'Y')
299 radius = points[-1].radius
300 tempList.append(
301 childPoint(
302 coord, quat, (parRad, radius), 1, 1, lPar,
303 'bone' + (str(stem.splN).rjust(3, '0')) + '.' + (str(index).rjust(3, '0'))
307 return tempList
310 # round down bone number
311 def roundBone(bone, step):
312 bone_i = bone[:-3]
313 bone_n = int(bone[-3:])
314 bone_n = int(bone_n / step) * step
315 return bone_i + str(bone_n).rjust(3, '0')
318 # Convert a list of degrees to radians
319 def toRad(list):
320 return [radians(a) for a in list]
323 def anglemean(a1, a2, fac):
324 x1 = sin(a1)
325 y1 = cos(a1)
326 x2 = sin(a2)
327 y2 = cos(a2)
328 x = x1 + (x2 - x1) * fac
329 y = y1 + (y2 - y1) * fac
330 return atan2(x, y)
333 # This is the function which extends (or grows) a given stem.
334 def growSpline(n, stem, numSplit, splitAng, splitAngV, splineList,
335 hType, splineToBone, closeTip, kp, splitHeight, outAtt, stemsegL,
336 lenVar, taperCrown, boneStep, rotate, rotateV):
338 # curv at base
339 sCurv = stem.curv
340 if (n == 0) and (kp <= splitHeight):
341 sCurv = 0.0
343 # curveangle = sCurv + (uniform(-stem.curvV, stem.curvV) * kp)
344 # curveVar = uniform(-stem.curvV, stem.curvV) * kp
345 curveangle = sCurv + (uniform(0, stem.curvV) * kp * stem.curvSignx)
346 curveVar = uniform(0, stem.curvV) * kp * stem.curvSigny
347 stem.curvSignx *= -1
348 stem.curvSigny *= -1
350 curveVarMat = Matrix.Rotation(curveVar, 3, 'Y')
352 # First find the current direction of the stem
353 dir = stem.quat()
355 if n == 0:
356 adir = zAxis.copy()
357 adir.rotate(dir)
359 ry = atan2(adir[0], adir[2])
360 adir.rotate(Euler((0, -ry, 0)))
361 rx = atan2(adir[1], adir[2])
363 dir = Euler((-rx, ry, 0), 'XYZ')
365 # length taperCrown
366 if n == 0:
367 dec = declination(dir) / 180
368 dec = dec ** 2
369 tf = 1 - (dec * taperCrown * 30)
370 tf = max(.1, tf)
371 else:
372 tf = 1.0
374 # outward attraction
375 if (n > 0) and (kp > 0) and (outAtt > 0):
376 p = stem.p.co.copy()
377 d = atan2(p[0], -p[1]) + tau
378 edir = dir.to_euler('XYZ', Euler((0, 0, d), 'XYZ'))
379 d = anglemean(edir[2], d, (kp * outAtt))
380 dirv = Euler((edir[0], edir[1], d), 'XYZ')
381 dir = dirv.to_quaternion()
383 # parent weight
384 parWeight = kp * degrees(stem.curvV) * pi
385 parWeight = parWeight * kp
386 parWeight = kp
387 if (n > 1) and (parWeight != 0):
388 d1 = zAxis.copy()
389 d2 = zAxis.copy()
390 d1.rotate(dir)
391 d2.rotate(stem.patentQuat)
393 x = d1[0] + ((d2[0] - d1[0]) * parWeight)
394 y = d1[1] + ((d2[1] - d1[1]) * parWeight)
395 z = d1[2] + ((d2[2] - d1[2]) * parWeight)
397 d3 = Vector((x, y, z))
398 dir = d3.to_track_quat('Z', 'Y')
401 # If the stem splits, we need to add new splines etc
402 if numSplit > 0:
403 # Get the curve data
404 cuData = stem.spline.id_data.name
405 cu = bpy.data.curves[cuData]
407 # calc split angles
408 angle = choice([-1, 1]) * (splitAng + uniform(-splitAngV, splitAngV))
409 if n > 0:
410 # make branches flatter
411 angle *= max(1 - declination(dir) / 90, 0) * .67 + .33
412 spreadangle = choice([-1, 1]) * (splitAng + uniform(-splitAngV, splitAngV))
414 # branchRotMat = Matrix.Rotation(radians(uniform(0, 360)), 3, 'Z')
415 if not hasattr(stem, 'rLast'):
416 stem.rLast = radians(uniform(0, 360))
418 br = rotate[0] + uniform(-rotateV[0], rotateV[0])
419 branchRot = stem.rLast + br
420 branchRotMat = Matrix.Rotation(branchRot, 3, 'Z')
421 stem.rLast = branchRot
423 # Now for each split add the new spline and adjust the growth direction
424 for i in range(numSplit):
425 # find split scale
426 lenV = uniform(1 - lenVar, 1 + lenVar)
427 bScale = min(lenV * tf, 1)
429 newSpline = cu.splines.new('BEZIER')
430 newPoint = newSpline.bezier_points[-1]
431 (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (stem.p.co, 'VECTOR', 'VECTOR')
432 newPoint.radius = (
433 stem.radS * (1 - stem.seg / stem.segMax) + stem.radE * (stem.seg / stem.segMax)
434 ) * bScale
435 # Here we make the new "sprouting" stems diverge from the current direction
436 divRotMat = Matrix.Rotation(angle + curveangle, 3, 'X')
437 dirVec = zAxis.copy()
438 dirVec.rotate(divRotMat)
440 # horizontal curvature variation
441 dirVec.rotate(curveVarMat)
443 if n == 0: # Special case for trunk splits
444 dirVec.rotate(branchRotMat)
446 ang = pi - ((tau) / (numSplit + 1)) * (i + 1)
447 dirVec.rotate(Matrix.Rotation(ang, 3, 'Z'))
449 # Spread the stem out in a random fashion
450 spreadMat = Matrix.Rotation(spreadangle, 3, 'Y')
451 if n != 0: # Special case for trunk splits
452 dirVec.rotate(spreadMat)
454 dirVec.rotate(dir)
456 # Introduce upward curvature
457 upRotAxis = xAxis.copy()
458 upRotAxis.rotate(dirVec.to_track_quat('Z', 'Y'))
459 curveUpAng = curveUp(stem.vertAtt, dirVec.to_track_quat('Z', 'Y'), stem.segMax)
460 upRotMat = Matrix.Rotation(-curveUpAng, 3, upRotAxis)
461 dirVec.rotate(upRotMat)
463 # Make the growth vec the length of a stem segment
464 dirVec.normalize()
466 # split length variation
467 stemL = stemsegL * lenV
468 dirVec *= stemL * tf
469 ofst = stem.offsetLen + (stem.segL * (len(stem.spline.bezier_points) - 1))
471 # dirVec *= stem.segL
473 # Get the end point position
474 end_co = stem.p.co.copy()
476 # Add the new point and adjust its coords, handles and radius
477 newSpline.bezier_points.add(1)
478 newPoint = newSpline.bezier_points[-1]
479 (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType)
480 newPoint.radius = (
481 stem.radS * (1 - (stem.seg + 1) / stem.segMax) +
482 stem.radE * ((stem.seg + 1) / stem.segMax)
483 ) * bScale
484 if (stem.seg == stem.segMax - 1) and closeTip:
485 newPoint.radius = 0.0
486 # If this isn't the last point on a stem, then we need to add it
487 # to the list of stems to continue growing
488 # print(stem.seg != stem.segMax, stem.seg, stem.segMax)
489 # if stem.seg != stem.segMax: # if probs not necessary
490 nstem = stemSpline(
491 newSpline, stem.curv, stem.curvV, stem.vertAtt, stem.seg + 1,
492 stem.segMax, stemL, stem.children,
493 stem.radS * bScale, stem.radE * bScale, len(cu.splines) - 1, ofst, stem.quat()
495 nstem.splitlast = 1 # numSplit # keep track of numSplit for next stem
496 nstem.rLast = branchRot + pi
497 splineList.append(nstem)
498 bone = 'bone' + (str(stem.splN)).rjust(3, '0') + '.' + \
499 (str(len(stem.spline.bezier_points) - 2)).rjust(3, '0')
500 bone = roundBone(bone, boneStep[n])
501 splineToBone.append((bone, False, True, len(stem.spline.bezier_points) - 2))
503 # The original spline also needs to keep growing so adjust its direction too
504 divRotMat = Matrix.Rotation(-angle + curveangle, 3, 'X')
505 dirVec = zAxis.copy()
506 dirVec.rotate(divRotMat)
508 # horizontal curvature variation
509 dirVec.rotate(curveVarMat)
511 if n == 0: # Special case for trunk splits
512 dirVec.rotate(branchRotMat)
514 # spread
515 spreadMat = Matrix.Rotation(-spreadangle, 3, 'Y')
516 if n != 0: # Special case for trunk splits
517 dirVec.rotate(spreadMat)
519 dirVec.rotate(dir)
521 stem.splitlast = 1 # numSplit #keep track of numSplit for next stem
523 else:
524 # If there are no splits then generate the growth direction without accounting for spreading of stems
525 dirVec = zAxis.copy()
526 divRotMat = Matrix.Rotation(curveangle, 3, 'X')
527 dirVec.rotate(divRotMat)
529 # horizontal curvature variation
530 dirVec.rotate(curveVarMat)
532 dirVec.rotate(dir)
534 stem.splitlast = 0 # numSplit #keep track of numSplit for next stem
536 # Introduce upward curvature
537 upRotAxis = xAxis.copy()
538 upRotAxis.rotate(dirVec.to_track_quat('Z', 'Y'))
539 curveUpAng = curveUp(stem.vertAtt, dirVec.to_track_quat('Z', 'Y'), stem.segMax)
540 upRotMat = Matrix.Rotation(-curveUpAng, 3, upRotAxis)
541 dirVec.rotate(upRotMat)
543 dirVec.normalize()
544 dirVec *= stem.segL * tf
546 # Get the end point position
547 end_co = stem.p.co.copy()
549 stem.spline.bezier_points.add(1)
550 newPoint = stem.spline.bezier_points[-1]
551 (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType)
552 newPoint.radius = stem.radS * (1 - (stem.seg + 1) / stem.segMax) + \
553 stem.radE * ((stem.seg + 1) / stem.segMax)
555 if (stem.seg == stem.segMax - 1) and closeTip:
556 newPoint.radius = 0.0
557 # There are some cases where a point cannot have handles as VECTOR straight away, set these now
558 if len(stem.spline.bezier_points) == 2:
559 tempPoint = stem.spline.bezier_points[0]
560 (tempPoint.handle_left_type, tempPoint.handle_right_type) = ('VECTOR', 'VECTOR')
561 # Update the last point in the spline to be the newly added one
562 stem.updateEnd()
563 # return splineList
566 def genLeafMesh(leafScale, leafScaleX, leafScaleT, leafScaleV, loc, quat,
567 offset, index, downAngle, downAngleV, rotate, rotateV, oldRot,
568 bend, leaves, leafShape, leafangle, horzLeaves):
569 if leafShape == 'hex':
570 verts = [
571 Vector((0, 0, 0)), Vector((0.5, 0, 1 / 3)), Vector((0.5, 0, 2 / 3)),
572 Vector((0, 0, 1)), Vector((-0.5, 0, 2 / 3)), Vector((-0.5, 0, 1 / 3))
574 edges = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0], [0, 3]]
575 faces = [[0, 1, 2, 3], [0, 3, 4, 5]]
576 elif leafShape == 'rect':
577 # verts = [Vector((1, 0, 0)), Vector((1, 0, 1)), Vector((-1, 0, 1)), Vector((-1, 0, 0))]
578 verts = [Vector((.5, 0, 0)), Vector((.5, 0, 1)), Vector((-.5, 0, 1)), Vector((-.5, 0, 0))]
579 edges = [[0, 1], [1, 2], [2, 3], [3, 0]]
580 faces = [[0, 1, 2, 3]]
581 elif leafShape == 'dFace':
582 verts = [Vector((.5, .5, 0)), Vector((.5, -.5, 0)), Vector((-.5, -.5, 0)), Vector((-.5, .5, 0))]
583 edges = [[0, 1], [1, 2], [2, 3], [3, 0]]
584 faces = [[0, 3, 2, 1]]
585 elif leafShape == 'dVert':
586 verts = [Vector((0, 0, 1))]
587 edges = []
588 faces = []
590 vertsList = []
591 facesList = []
592 normal = Vector((0, 0, 1))
594 if leaves < 0:
595 rotMat = Matrix.Rotation(oldRot, 3, 'Y')
596 else:
597 rotMat = Matrix.Rotation(oldRot, 3, 'Z')
599 # If the -ve flag for rotate is used we need to find which side of the stem
600 # the last child point was and then grow in the opposite direction
601 if rotate < 0.0:
602 oldRot = -copysign(rotate + uniform(-rotateV, rotateV), oldRot)
603 else:
604 # If the special -ve flag for leaves is used we need a different rotation of the leaf geometry
605 if leaves == -1:
606 # oldRot = 0
607 rotMat = Matrix.Rotation(0, 3, 'Y')
608 elif leaves < -1:
609 oldRot += rotate / (-leaves - 1)
610 else:
611 oldRot += rotate + uniform(-rotateV, rotateV)
613 if leaves < 0:
614 rotMat = Matrix.Rotation(oldRot, 3, 'Y')
615 else:
616 rotMat = Matrix.Rotation(oldRot, 3, 'Z')
618 if leaves >= 0:
619 # downRotMat = Matrix.Rotation(downAngle+uniform(-downAngleV, downAngleV), 3, 'X')
621 if downAngleV > 0.0:
622 downV = -downAngleV * offset
623 else:
624 downV = uniform(-downAngleV, downAngleV)
625 downRotMat = Matrix.Rotation(downAngle + downV, 3, 'X')
627 # leaf scale variation
628 if (leaves < -1) and (rotate != 0):
629 f = 1 - abs((oldRot - (rotate / (-leaves - 1))) / (rotate / 2))
630 else:
631 f = offset
633 if leafScaleT < 0:
634 leafScale = leafScale * (1 - (1 - f) * -leafScaleT)
635 else:
636 leafScale = leafScale * (1 - f * leafScaleT)
638 leafScale = leafScale * uniform(1 - leafScaleV, 1 + leafScaleV)
640 if leafShape == 'dFace':
641 leafScale = leafScale * .1
643 # If the bending of the leaves is used we need to rotate them differently
644 if (bend != 0.0) and (leaves >= 0):
645 normal = yAxis.copy()
646 orientationVec = zAxis.copy()
648 normal.rotate(quat)
649 orientationVec.rotate(quat)
651 thetaPos = atan2(loc.y, loc.x)
652 thetaBend = thetaPos - atan2(normal.y, normal.x)
653 rotateZ = Matrix.Rotation(bend * thetaBend, 3, 'Z')
654 normal.rotate(rotateZ)
655 orientationVec.rotate(rotateZ)
657 phiBend = atan2((normal.xy).length, normal.z)
658 orientation = atan2(orientationVec.y, orientationVec.x)
659 rotateZOrien = Matrix.Rotation(orientation, 3, 'X')
661 rotateX = Matrix.Rotation(bend * phiBend, 3, 'Z')
663 rotateZOrien2 = Matrix.Rotation(-orientation, 3, 'X')
665 # For each of the verts we now rotate and scale them, then append them to the list to be added to the mesh
666 for v in verts:
667 v.z *= leafScale
668 v.y *= leafScale
669 v.x *= leafScaleX * leafScale
671 v.rotate(Euler((0, 0, radians(180))))
673 # leafangle
674 v.rotate(Matrix.Rotation(radians(-leafangle), 3, 'X'))
676 if rotate < 0:
677 v.rotate(Euler((0, 0, radians(90))))
678 if oldRot < 0:
679 v.rotate(Euler((0, 0, radians(180))))
681 if (leaves > 0) and (rotate > 0) and horzLeaves:
682 nRotMat = Matrix.Rotation(-oldRot + rotate, 3, 'Z')
683 v.rotate(nRotMat)
685 if leaves > 0:
686 v.rotate(downRotMat)
688 v.rotate(rotMat)
689 v.rotate(quat)
691 if (bend != 0.0) and (leaves > 0):
692 # Correct the rotation
693 v.rotate(rotateZ)
694 v.rotate(rotateZOrien)
695 v.rotate(rotateX)
696 v.rotate(rotateZOrien2)
698 if leafShape == 'dVert':
699 normal = verts[0]
700 normal.normalize()
701 v = loc
702 vertsList.append([v.x, v.y, v.z])
703 else:
704 for v in verts:
705 v += loc
706 vertsList.append([v.x, v.y, v.z])
707 for f in faces:
708 facesList.append([f[0] + index, f[1] + index, f[2] + index, f[3] + index])
710 return vertsList, facesList, normal, oldRot
713 def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSize, leaves,
714 levelCount, splineToBone, treeOb, wind, gust, gustF, af1, af2, af3,
715 leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep):
716 arm = bpy.data.armatures.new('tree')
717 armOb = bpy.data.objects.new('treeArm', arm)
718 bpy.context.scene.collection.objects.link(armOb)
719 # Create a new action to store all animation
720 newAction = bpy.data.actions.new(name='windAction')
721 armOb.animation_data_create()
722 armOb.animation_data.action = newAction
723 arm.display_type = 'STICK'
724 # Add the armature modifier to the curve
725 armMod = treeOb.modifiers.new('windSway', 'ARMATURE')
726 if previewArm:
727 armMod.show_viewport = False
728 arm.display_type = 'WIRE'
729 treeOb.hide_viewport = True
730 armMod.use_apply_on_spline = True
731 armMod.object = armOb
732 armMod.use_bone_envelopes = True
733 armMod.use_vertex_groups = False # curves don't have vertex groups (yet)
734 # If there are leaves then they need a modifier
735 if leaves:
736 armMod = leafObj.modifiers.new('windSway', 'ARMATURE')
737 armMod.object = armOb
738 armMod.use_bone_envelopes = False
739 armMod.use_vertex_groups = True
741 # Make sure all objects are deselected (may not be required?)
742 for ob in bpy.context.view_layer.objects:
743 ob.select_set(state=False)
745 fps = bpy.context.scene.render.fps
746 animSpeed = (24 / fps) * frameRate
748 # Set the armature as active and go to edit mode to add bones
749 bpy.context.view_layer.objects.active = armOb
750 bpy.ops.object.mode_set(mode='EDIT')
751 # For all the splines in the curve we need to add bones at each bezier point
752 for i, parBone in enumerate(splineToBone):
753 if (i < levelCount[armLevels]) or (armLevels == -1) or (not makeMesh):
754 s = cu.splines[i]
755 b = None
756 # Get some data about the spline like length and number of points
757 numPoints = len(s.bezier_points) - 1
759 # find branching level
760 level = 0
761 for l, c in enumerate(levelCount):
762 if i < c:
763 level = l
764 break
765 level = min(level, 3)
767 step = boneStep[level]
769 # Calculate things for animation
770 if armAnim:
771 splineL = numPoints * ((s.bezier_points[0].co - s.bezier_points[1].co).length)
772 # Set the random phase difference of the animation
773 bxOffset = uniform(0, tau)
774 byOffset = uniform(0, tau)
775 # Set the phase multiplier for the spline
776 # bMult_r = (s.bezier_points[0].radius / max(splineL, 1e-6)) * (1 / 15) * (1 / frameRate)
777 # This shouldn't have to be in degrees but it looks much better in animation
778 # bMult = degrees(bMult_r)
779 bMult = (1 / max(splineL ** .5, 1e-6)) * (1 / 4)
780 # print((1 / bMult) * tau) #print wavelength in frames
782 windFreq1 = bMult * animSpeed
783 windFreq2 = 0.7 * bMult * animSpeed
784 if loopFrames != 0:
785 bMult_l = 1 / (loopFrames / tau)
786 fRatio = max(1, round(windFreq1 / bMult_l))
787 fgRatio = max(1, round(windFreq2 / bMult_l))
788 windFreq1 = fRatio * bMult_l
789 windFreq2 = fgRatio * bMult_l
791 # For all the points in the curve (less the last) add a bone and name it by the spline it will affect
792 nx = 0
793 for n in range(0, numPoints, step):
794 oldBone = b
795 boneName = 'bone' + (str(i)).rjust(3, '0') + '.' + (str(n)).rjust(3, '0')
796 b = arm.edit_bones.new(boneName)
797 b.head = s.bezier_points[n].co
798 nx += step
799 nx = min(nx, numPoints)
800 b.tail = s.bezier_points[nx].co
802 b.head_radius = s.bezier_points[n].radius
803 b.tail_radius = s.bezier_points[n + 1].radius
804 b.envelope_distance = 0.001
806 # If there are leaves then we need a new vertex group so they will attach to the bone
807 if not leafAnim:
808 if (len(levelCount) > 1) and (i >= levelCount[-2]) and leafObj:
809 leafObj.vertex_groups.new(name=boneName)
810 elif (len(levelCount) == 1) and leafObj:
811 leafObj.vertex_groups.new(name=boneName)
813 # If this is first point of the spline then it must be parented to the level above it
814 if n == 0:
815 if parBone:
816 b.parent = arm.edit_bones[parBone]
817 # Otherwise, we need to attach it to the previous bone in the spline
818 else:
819 b.parent = oldBone
820 b.use_connect = True
821 # If there isn't a previous bone then it shouldn't be attached
822 if not oldBone:
823 b.use_connect = False
825 # Add the animation to the armature if required
826 if armAnim:
827 # Define all the required parameters of the wind sway by the dimension of the spline
828 # a0 = 4 * splineL * (1 - n / (numPoints + 1)) / max(s.bezier_points[n].radius, 1e-6)
829 a0 = 2 * (splineL / numPoints) * (1 - n / (numPoints + 1)) / max(s.bezier_points[n].radius, 1e-6)
830 a0 = a0 * min(step, numPoints)
831 # a0 = (splineL / numPoints) / max(s.bezier_points[n].radius, 1e-6)
832 a1 = (wind / 50) * a0
833 a2 = a1 * .65 # (windGust / 50) * a0 + a1 / 2
835 p = s.bezier_points[nx].co - s.bezier_points[n].co
836 p.normalize()
837 ag = (wind * gust / 50) * a0
838 a3 = -p[0] * ag
839 a4 = p[2] * ag
841 a1 = radians(a1)
842 a2 = radians(a2)
843 a3 = radians(a3)
844 a4 = radians(a4)
846 # wind bending
847 if loopFrames == 0:
848 swayFreq = gustF * (tau / fps) * frameRate # animSpeed # .075 # 0.02
849 else:
850 swayFreq = 1 / (loopFrames / tau)
852 # Prevent tree base from rotating
853 if (boneName == "bone000.000") or (boneName == "bone000.001"):
854 a1 = 0
855 a2 = 0
856 a3 = 0
857 a4 = 0
859 # Add new fcurves for each sway as well as the modifiers
860 swayX = armOb.animation_data.action.fcurves.new(
861 'pose.bones["' + boneName + '"].rotation_euler', index=0
863 swayY = armOb.animation_data.action.fcurves.new(
864 'pose.bones["' + boneName + '"].rotation_euler', index=2
866 swayXMod1 = swayX.modifiers.new(type='FNGENERATOR')
867 swayXMod2 = swayX.modifiers.new(type='FNGENERATOR')
869 swayYMod1 = swayY.modifiers.new(type='FNGENERATOR')
870 swayYMod2 = swayY.modifiers.new(type='FNGENERATOR')
872 # Set the parameters for each modifier
873 swayXMod1.amplitude = a1
874 swayXMod1.phase_offset = bxOffset
875 swayXMod1.phase_multiplier = windFreq1
877 swayXMod2.amplitude = a2
878 swayXMod2.phase_offset = 0.7 * bxOffset
879 swayXMod2.phase_multiplier = windFreq2
880 swayXMod2.use_additive = True
882 swayYMod1.amplitude = a1
883 swayYMod1.phase_offset = byOffset
884 swayYMod1.phase_multiplier = windFreq1
886 swayYMod2.amplitude = a2
887 swayYMod2.phase_offset = 0.7 * byOffset
888 swayYMod2.phase_multiplier = windFreq2
889 swayYMod2.use_additive = True
891 # wind bending
892 swayYMod3 = swayY.modifiers.new(type='FNGENERATOR')
893 swayYMod3.amplitude = a3
894 swayYMod3.phase_multiplier = swayFreq
895 swayYMod3.value_offset = .6 * a3
896 swayYMod3.use_additive = True
898 swayXMod3 = swayX.modifiers.new(type='FNGENERATOR')
899 swayXMod3.amplitude = a4
900 swayXMod3.phase_multiplier = swayFreq
901 swayXMod3.value_offset = .6 * a4
902 swayXMod3.use_additive = True
904 if leaves:
905 bonelist = [b.name for b in arm.edit_bones]
906 vertexGroups = OrderedDict()
907 for i, cp in enumerate(leafP):
908 # find leafs parent bone
909 leafParent = roundBone(cp.parBone, boneStep[armLevels])
910 idx = int(leafParent[4:-4])
911 while leafParent not in bonelist:
912 # find parent bone of parent bone
913 leafParent = splineToBone[idx]
914 idx = int(leafParent[4:-4])
916 if leafAnim:
917 bname = "leaf" + str(i)
918 b = arm.edit_bones.new(bname)
919 b.head = cp.co
920 b.tail = cp.co + Vector((0, 0, .02))
921 b.envelope_distance = 0.0
922 b.parent = arm.edit_bones[leafParent]
924 vertexGroups[bname] = [
925 v.index for v in
926 leafMesh.vertices[leafVertSize * i:(leafVertSize * i + leafVertSize)]
929 if armAnim:
930 # Define all the required parameters of the wind sway by the dimension of the spline
931 a1 = wind * .25
932 a1 *= af1
934 bMult = (1 / animSpeed) * 6
935 bMult *= 1 / max(af2, .001)
937 ofstRand = af3
938 bxOffset = uniform(-ofstRand, ofstRand)
939 byOffset = uniform(-ofstRand, ofstRand)
941 # Add new fcurves for each sway as well as the modifiers
942 swayX = armOb.animation_data.action.fcurves.new(
943 'pose.bones["' + bname + '"].rotation_euler', index=0
945 swayY = armOb.animation_data.action.fcurves.new(
946 'pose.bones["' + bname + '"].rotation_euler', index=2
948 # Add keyframe so noise works
949 swayX.keyframe_points.add(1)
950 swayY.keyframe_points.add(1)
951 swayX.keyframe_points[0].co = (0, 0)
952 swayY.keyframe_points[0].co = (0, 0)
954 # Add noise modifiers
955 swayXMod = swayX.modifiers.new(type='NOISE')
956 swayYMod = swayY.modifiers.new(type='NOISE')
958 if loopFrames != 0:
959 swayXMod.use_restricted_range = True
960 swayXMod.frame_end = loopFrames
961 swayXMod.blend_in = 4
962 swayXMod.blend_out = 4
963 swayYMod.use_restricted_range = True
964 swayYMod.frame_end = loopFrames
965 swayYMod.blend_in = 4
966 swayYMod.blend_out = 4
968 swayXMod.scale = bMult
969 swayXMod.strength = a1
970 swayXMod.offset = bxOffset
972 swayYMod.scale = bMult
973 swayYMod.strength = a1
974 swayYMod.offset = byOffset
976 else:
977 if leafParent not in vertexGroups:
978 vertexGroups[leafParent] = []
979 vertexGroups[leafParent].extend(
980 [v.index for v in
981 leafMesh.vertices[leafVertSize * i:(leafVertSize * i + leafVertSize)]]
984 for group in vertexGroups:
985 leafObj.vertex_groups.new(name=group)
986 leafObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
988 # Now we need the rotation mode to be 'XYZ' to ensure correct rotation
989 bpy.ops.object.mode_set(mode='OBJECT')
990 for p in armOb.pose.bones:
991 p.rotation_mode = 'XYZ'
992 treeOb.parent = armOb
995 def kickstart_trunk(addstem, levels, leaves, branches, cu, curve, curveRes,
996 curveV, attractUp, length, lengthV, ratio, ratioPower,
997 resU, scale0, scaleV0, scaleVal, taper, minRadius, rootFlare):
998 newSpline = cu.splines.new('BEZIER')
999 cu.resolution_u = resU
1000 newPoint = newSpline.bezier_points[-1]
1001 newPoint.co = Vector((0, 0, 0))
1002 newPoint.handle_right = Vector((0, 0, 1))
1003 newPoint.handle_left = Vector((0, 0, -1))
1004 # (newPoint.handle_right_type, newPoint.handle_left_type) = ('VECTOR', 'VECTOR')
1005 branchL = scaleVal * length[0]
1006 curveVal = curve[0] / curveRes[0]
1007 # curveVal = curveVal * (branchL / scaleVal)
1008 if levels == 1:
1009 childStems = leaves
1010 else:
1011 childStems = branches[1]
1012 startRad = scaleVal * ratio * scale0 * uniform(1 - scaleV0, 1 + scaleV0) # * (scale0 + uniform(-scaleV0, scaleV0))
1013 endRad = (startRad * (1 - taper[0])) ** ratioPower
1014 startRad = max(startRad, minRadius)
1015 endRad = max(endRad, minRadius)
1016 newPoint.radius = startRad * rootFlare
1017 addstem(
1018 stemSpline(
1019 newSpline, curveVal, curveV[0] / curveRes[0], attractUp[0],
1020 0, curveRes[0], branchL / curveRes[0],
1021 childStems, startRad, endRad, 0, 0, None
1026 def fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, curve, curveBack,
1027 curveRes, curveV, attractUp, downAngle, downAngleV, leafDist, leaves, length,
1028 lengthV, levels, n, ratioPower, resU, rotate, rotateV, scaleVal, shape, storeN,
1029 taper, shapeS, minRadius, radiusTweak, customShape, rMode, segSplits,
1030 useOldDownAngle, useParentAngle, boneStep):
1032 # prevent baseSize from going to 1.0
1033 baseSize = min(0.999, baseSize)
1035 # Store the old rotation to allow new stems to be rotated away from the previous one.
1036 oldRotate = 0
1038 # use fancy child point selection / rotation
1039 if (n == 1) and (rMode != "original"):
1040 childP_T = OrderedDict()
1041 childP_L = []
1042 for p in childP:
1043 if p.offset == 1:
1044 childP_L.append(p)
1045 else:
1046 if p.offset not in childP_T:
1047 childP_T[p.offset] = [p]
1048 else:
1049 childP_T[p.offset].append(p)
1051 childP_T = [childP_T[k] for k in sorted(childP_T.keys())]
1053 childP = []
1054 rot_a = []
1055 for p in childP_T:
1056 if rMode == "rotate":
1057 if rotate[n] < 0.0:
1058 oldRotate = -copysign(rotate[n], oldRotate)
1059 else:
1060 oldRotate += rotate[n]
1061 bRotate = oldRotate + uniform(-rotateV[n], rotateV[n])
1063 # choose start point whose angle is closest to the rotate angle
1064 a1 = bRotate % tau
1065 a_diff = []
1066 for a in p:
1067 a2 = atan2(a.co[0], -a.co[1])
1068 d = min((a1 - a2 + tau) % tau, (a2 - a1 + tau) % tau)
1069 a_diff.append(d)
1071 idx = a_diff.index(min(a_diff))
1073 # find actual rotate angle from branch location
1074 br = p[idx]
1075 b = br.co
1076 vx = sin(bRotate)
1077 vy = cos(bRotate)
1078 v = Vector((vx, vy))
1080 bD = ((b[0] * b[0] + b[1] * b[1]) ** .5)
1081 bL = br.lengthPar * length[1] * shapeRatio(shape, (1 - br.offset) / (1 - baseSize), custom=customShape)
1083 # account for down angle
1084 if downAngleV[1] > 0:
1085 downA = downAngle[n] + (-downAngleV[n] * (1 - (1 - br.offset) / (1 - baseSize)) ** 2)
1086 else:
1087 downA = downAngle[n]
1088 if downA < (.5 * pi):
1089 downA = sin(downA) ** 2
1090 bL *= downA
1092 bL *= 0.33
1093 v *= (bD + bL)
1095 bv = Vector((b[0], -b[1]))
1096 cv = v - bv
1097 a = atan2(cv[0], cv[1])
1098 # rot_a.append(a)
1100 # add fill points at top #experimental
1101 fillHeight = 1 - degrees(rotateV[3]) # 0.8
1102 if fillHeight < 1:
1103 w = (p[0].offset - fillHeight) / (1- fillHeight)
1104 prob_b = random() < w
1105 else:
1106 prob_b = False
1108 if (p[0].offset > fillHeight): # prob_b and (len(p) > 1): ##(p[0].offset > fillHeight) and
1109 childP.append(p[randint(0, len(p)-1)])
1110 rot_a.append(bRotate)# + pi)
1112 childP.append(p[idx])
1113 rot_a.append(a)
1115 else:
1116 idx = randint(0, len(p) - 1)
1117 childP.append(p[idx])
1118 # childP.append(p[idx])
1120 childP.extend(childP_L)
1121 rot_a.extend([0] * len(childP_L))
1123 oldRotate = 0
1125 for i, p in enumerate(childP):
1126 # Add a spline and set the coordinate of the first point.
1127 newSpline = cu.splines.new('BEZIER')
1128 cu.resolution_u = resU
1129 newPoint = newSpline.bezier_points[-1]
1130 newPoint.co = p.co
1131 tempPos = zAxis.copy()
1132 # If the -ve flag for downAngle is used we need a special formula to find it
1133 if useOldDownAngle:
1134 if downAngleV[n] < 0.0:
1135 downV = downAngleV[n] * (1 - 2 * (.2 + .8 * ((1 - p.offset) / (1 - baseSize))))
1136 # Otherwise just find a random value
1137 else:
1138 downV = uniform(-downAngleV[n], downAngleV[n])
1139 else:
1140 if downAngleV[n] < 0.0:
1141 downV = uniform(-downAngleV[n], downAngleV[n])
1142 else:
1143 downV = -downAngleV[n] * (1 - (1 - p.offset) / (1 - baseSize)) ** 2 # (110, 80) = (60, -50)
1145 if p.offset == 1:
1146 downRotMat = Matrix.Rotation(0, 3, 'X')
1147 else:
1148 downRotMat = Matrix.Rotation(downAngle[n] + downV, 3, 'X')
1150 # If the -ve flag for rotate is used we need to find which side of the stem
1151 # the last child point was and then grow in the opposite direction
1152 if rotate[n] < 0.0:
1153 oldRotate = -copysign(rotate[n], oldRotate)
1154 # Otherwise just generate a random number in the specified range
1155 else:
1156 oldRotate += rotate[n]
1157 bRotate = oldRotate + uniform(-rotateV[n], rotateV[n])
1159 if (n == 1) and (rMode == "rotate"):
1160 bRotate = rot_a[i]
1162 rotMat = Matrix.Rotation(bRotate, 3, 'Z')
1164 # Rotate the direction of growth and set the new point coordinates
1165 tempPos.rotate(downRotMat)
1166 tempPos.rotate(rotMat)
1168 # use quat angle
1169 if (rMode == "rotate") and (n == 1) and (p.offset != 1):
1170 if useParentAngle:
1171 edir = p.quat.to_euler('XYZ', Euler((0, 0, bRotate), 'XYZ'))
1172 edir[0] = 0
1173 edir[1] = 0
1175 edir[2] = -edir[2]
1176 tempPos.rotate(edir)
1178 dec = declination(p.quat)
1179 tempPos.rotate(Matrix.Rotation(radians(dec), 3, 'X'))
1181 edir[2] = -edir[2]
1182 tempPos.rotate(edir)
1183 else:
1184 tempPos.rotate(p.quat)
1186 newPoint.handle_right = p.co + tempPos
1188 # Make length variation inversely proportional to segSplits
1189 # lenV = (1 - min(segSplits[n], 1)) * lengthV[n]
1191 # Find branch length and the number of child stems.
1192 maxbL = scaleVal
1193 for l in length[:n + 1]:
1194 maxbL *= l
1195 lMax = length[n] # * uniform(1 - lenV, 1 + lenV)
1196 if n == 1:
1197 lShape = shapeRatio(shape, (1 - p.stemOffset) / (1 - baseSize), custom=customShape)
1198 else:
1199 lShape = shapeRatio(shapeS, (1 - p.stemOffset) / (1 - baseSize))
1200 branchL = p.lengthPar * lMax * lShape
1201 childStems = branches[min(3, n + 1)] * (0.1 + 0.9 * (branchL / maxbL))
1203 # If this is the last level before leaves then we need to generate the child points differently
1204 if (storeN == levels - 1):
1205 if leaves < 0:
1206 childStems = False
1207 else:
1208 childStems = leaves * (0.1 + 0.9 * (branchL / maxbL)) * shapeRatio(leafDist, (1 - p.offset))
1210 # print("n=%d, levels=%d, n'=%d, childStems=%s"%(n, levels, storeN, childStems))
1212 # Determine the starting and ending radii of the stem using the tapering of the stem
1213 startRad = min((p.radiusPar[0] * ((branchL / p.lengthPar) ** ratioPower)) * radiusTweak[n], p.radiusPar[1])
1214 if p.offset == 1:
1215 startRad = p.radiusPar[1]
1216 endRad = (startRad * (1 - taper[n])) ** ratioPower
1217 startRad = max(startRad, minRadius)
1218 endRad = max(endRad, minRadius)
1219 newPoint.radius = startRad
1221 # stem curvature
1222 curveVal = curve[n] / curveRes[n]
1223 curveVar = curveV[n] / curveRes[n]
1225 # curveVal = curveVal * (branchL / scaleVal)
1227 # Add the new stem to list of stems to grow and define which bone it will be parented to
1228 addstem(
1229 stemSpline(
1230 newSpline, curveVal, curveVar, attractUp[n],
1231 0, curveRes[n], branchL / curveRes[n], childStems,
1232 startRad, endRad, len(cu.splines) - 1, 0, p.quat
1236 bone = roundBone(p.parBone, boneStep[n - 1])
1237 if p.offset == 1:
1238 isend = True
1239 else:
1240 isend = False
1241 addsplinetobone((bone, isend))
1244 def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, currentScale, curve,
1245 curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, originalSplineToBone,
1246 originalCo, originalCurv, originalCurvV, originalHandleL, originalHandleR, originalLength,
1247 originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio, pruneWidth, pruneBase,
1248 pruneWidthPeak, randState, ratio, scaleVal, segSplits, splineToBone, splitAngle, splitAngleV,
1249 st, startPrune, branchDist, length, splitByLen, closeTip, nrings, splitBias, splitHeight,
1250 attractOut, rMode, lengthV, taperCrown, boneStep, rotate, rotateV):
1251 while startPrune and ((currentMax - currentMin) > 0.005):
1252 setstate(randState)
1254 # If the search will halt after this iteration, then set the adjustment of stem
1255 # length to take into account the pruning ratio
1256 if (currentMax - currentMin) < 0.01:
1257 currentScale = (currentScale - 1) * pruneRatio + 1
1258 startPrune = False
1259 forceSprout = True
1260 # Change the segment length of the stem by applying some scaling
1261 st.segL = originalLength * currentScale
1262 # To prevent millions of splines being created we delete any old ones and
1263 # replace them with only their first points to begin the spline again
1264 if deleteSpline:
1265 for x in splineList:
1266 cu.splines.remove(x.spline)
1267 newSpline = cu.splines.new('BEZIER')
1268 newPoint = newSpline.bezier_points[-1]
1269 newPoint.co = originalCo
1270 newPoint.handle_right = originalHandleR
1271 newPoint.handle_left = originalHandleL
1272 (newPoint.handle_left_type, newPoint.handle_right_type) = ('VECTOR', 'VECTOR')
1273 st.spline = newSpline
1274 st.curv = originalCurv
1275 st.curvV = originalCurvV
1276 st.seg = originalSeg
1277 st.p = newPoint
1278 newPoint.radius = st.radS
1279 splineToBone = originalSplineToBone
1281 # Initialise the spline list for those contained in the current level of branching
1282 splineList = [st]
1284 # split length variation
1285 stemsegL = splineList[0].segL # initial segment length used for variation
1286 splineList[0].segL = stemsegL * uniform(1 - lengthV[n], 1 + lengthV[n]) # variation for first stem
1288 # For each of the segments of the stem which must be grown we have to add to each spline in splineList
1289 for k in range(curveRes[n]):
1290 # Make a copy of the current list to avoid continually adding to the list we're iterating over
1291 tempList = splineList[:]
1292 # print('Leng: ', len(tempList))
1294 # for curve variation
1295 if curveRes[n] > 1:
1296 kp = (k / (curveRes[n] - 1)) # * 2
1297 else:
1298 kp = 1.0
1300 # split bias
1301 splitValue = segSplits[n]
1302 if n == 0:
1303 splitValue = ((2 * splitBias) * (kp - .5) + 1) * splitValue
1304 splitValue = max(splitValue, 0.0)
1306 # For each of the splines in this list set the number of splits and then grow it
1307 for spl in tempList:
1308 # adjust numSplit
1309 lastsplit = getattr(spl, 'splitlast', 0)
1310 splitVal = splitValue
1311 if lastsplit == 0:
1312 splitVal = splitValue * 1.33
1313 elif lastsplit == 1:
1314 splitVal = splitValue * splitValue
1316 if k == 0:
1317 numSplit = 0
1318 elif (n == 0) and (k < ((curveRes[n] - 1) * splitHeight)) and (k != 1):
1319 numSplit = 0
1320 elif (k == 1) and (n == 0):
1321 numSplit = baseSplits
1322 # always split at splitHeight
1323 elif (n == 0) and (k == int((curveRes[n] - 1) * splitHeight) + 1) and (splitVal > 0):
1324 numSplit = 1
1325 else:
1326 if (n >= 1) and splitByLen:
1327 L = ((spl.segL * curveRes[n]) / scaleVal)
1328 lf = 1
1329 for l in length[:n + 1]:
1330 lf *= l
1331 L = L / lf
1332 numSplit = splits2(splitVal * L)
1333 else:
1334 numSplit = splits2(splitVal)
1336 if (k == int(curveRes[n] / 2 + 0.5)) and (curveBack[n] != 0):
1337 spl.curv += 2 * (curveBack[n] / curveRes[n]) # was -4 *
1339 growSpline(
1340 n, spl, numSplit, splitAngle[n], splitAngleV[n], splineList,
1341 handles, splineToBone, closeTip, kp, splitHeight, attractOut[n],
1342 stemsegL, lengthV[n], taperCrown, boneStep, rotate, rotateV
1345 # If pruning is enabled then we must check to see if the end of the spline is within the envelope
1346 if prune:
1347 # Check each endpoint to see if it is inside
1348 for s in splineList:
1349 coordMag = (s.spline.bezier_points[-1].co.xy).length
1350 ratio = (scaleVal - s.spline.bezier_points[-1].co.z) / (scaleVal * max(1 - pruneBase, 1e-6))
1351 # Don't think this if part is needed
1352 if (n == 0) and (s.spline.bezier_points[-1].co.z < pruneBase * scaleVal):
1353 insideBool = True # Init to avoid UnboundLocalError later
1354 else:
1355 insideBool = (
1356 (coordMag / scaleVal) < pruneWidth * shapeRatio(9, ratio, pruneWidthPeak, prunePowerHigh,
1357 prunePowerLow))
1358 # If the point is not inside then we adjust the scale and current search bounds
1359 if not insideBool:
1360 oldMax = currentMax
1361 currentMax = currentScale
1362 currentScale = 0.5 * (currentMax + currentMin)
1363 break
1364 # If the scale is the original size and the point is inside then
1365 # we need to make sure it won't be pruned or extended to the edge of the envelope
1366 if insideBool and (currentScale != 1):
1367 currentMin = currentScale
1368 currentMax = oldMax
1369 currentScale = 0.5 * (currentMax + currentMin)
1370 if insideBool and ((currentMax - currentMin) == 1):
1371 currentMin = 1
1373 # If the search will halt on the next iteration then we need
1374 # to make sure we sprout child points to grow the next splines or leaves
1375 if (((currentMax - currentMin) < 0.005) or not prune) or forceSprout:
1376 if (n == 0) and (rMode != "original"):
1377 tVals = findChildPoints2(splineList, st.children)
1378 else:
1379 tVals = findChildPoints(splineList, st.children)
1380 # print("debug tvals[%d] , splineList[%d], %s" % ( len(tVals), len(splineList), st.children))
1381 # If leaves is -ve then we need to make sure the only point which sprouts is the end of the spline
1382 if not st.children:
1383 tVals = [1.0]
1384 # remove some of the points because of baseSize
1385 trimNum = int(baseSize * (len(tVals) + 1))
1386 tVals = tVals[trimNum:]
1388 # grow branches in rings
1389 if (n == 0) and (nrings > 0):
1390 # tVals = [(floor(t * nrings)) / nrings for t in tVals[:-1]]
1391 tVals = [(floor(t * nrings) / nrings) * uniform(.995, 1.005) for t in tVals[:-1]]
1392 tVals.append(1)
1393 tVals = [t for t in tVals if t > baseSize]
1395 # branch distribution
1396 if n == 0:
1397 tVals = [((t - baseSize) / (1 - baseSize)) for t in tVals]
1398 if branchDist < 1.0:
1399 tVals = [t ** (1 / branchDist) for t in tVals]
1400 else:
1401 tVals = [1 - (1 - t) ** branchDist for t in tVals]
1402 tVals = [t * (1 - baseSize) + baseSize for t in tVals]
1404 # For all the splines, we interpolate them and add the new points to the list of child points
1405 maxOffset = max([s.offsetLen + (len(s.spline.bezier_points) - 1) * s.segL for s in splineList])
1406 for s in splineList:
1407 # print(str(n)+'level: ', s.segMax*s.segL)
1408 childP.extend(interpStem(s, tVals, s.segMax * s.segL, s.radS, maxOffset, baseSize))
1410 # Force the splines to be deleted
1411 deleteSpline = True
1412 # If pruning isn't enabled then make sure it doesn't loop
1413 if not prune:
1414 startPrune = False
1415 return ratio, splineToBone
1418 # calculate taper automatically
1419 def findtaper(length, taper, shape, shapeS, levels, customShape):
1420 taperS = []
1421 for i, t in enumerate(length):
1422 if i == 0:
1423 shp = 1.0
1424 elif i == 1:
1425 shp = shapeRatio(shape, 0, custom=customShape)
1426 else:
1427 shp = shapeRatio(shapeS, 0)
1428 t = t * shp
1429 taperS.append(t)
1431 taperP = []
1432 for i, t in enumerate(taperS):
1433 pm = 1
1434 for x in range(i + 1):
1435 pm *= taperS[x]
1436 taperP.append(pm)
1438 taperR = []
1439 for i, t in enumerate(taperP):
1440 t = sum(taperP[i:levels])
1441 taperR.append(t)
1443 taperT = []
1444 for i, t in enumerate(taperR):
1445 try:
1446 t = taperP[i] / taperR[i]
1447 except ZeroDivisionError:
1448 t = 1.0
1449 taperT.append(t)
1451 taperT = [t * taper[i] for i, t in enumerate(taperT)]
1453 return taperT
1456 def addTree(props):
1457 global splitError
1458 # startTime = time.time()
1459 # Set the seed for repeatable results
1460 seed(props.seed)
1462 # Set all other variables
1463 levels = props.levels
1464 length = props.length
1465 lengthV = props.lengthV
1466 taperCrown = props.taperCrown
1467 branches = props.branches
1468 curveRes = props.curveRes
1469 curve = toRad(props.curve)
1470 curveV = toRad(props.curveV)
1471 curveBack = toRad(props.curveBack)
1472 baseSplits = props.baseSplits
1473 segSplits = props.segSplits
1474 splitByLen = props.splitByLen
1475 rMode = props.rMode
1476 splitAngle = toRad(props.splitAngle)
1477 splitAngleV = toRad(props.splitAngleV)
1478 scale = props.scale
1479 scaleV = props.scaleV
1480 attractUp = props.attractUp
1481 attractOut = props.attractOut
1482 shape = int(props.shape)
1483 shapeS = int(props.shapeS)
1484 customShape = props.customShape
1485 branchDist = props.branchDist
1486 nrings = props.nrings
1487 baseSize = props.baseSize
1488 baseSize_s = props.baseSize_s
1489 splitHeight = props.splitHeight
1490 splitBias = props.splitBias
1491 ratio = props.ratio
1492 minRadius = props.minRadius
1493 closeTip = props.closeTip
1494 rootFlare = props.rootFlare
1495 autoTaper = props.autoTaper
1496 taper = props.taper
1497 radiusTweak = props.radiusTweak
1498 ratioPower = props.ratioPower
1499 downAngle = toRad(props.downAngle)
1500 downAngleV = toRad(props.downAngleV)
1501 rotate = toRad(props.rotate)
1502 rotateV = toRad(props.rotateV)
1503 scale0 = props.scale0
1504 scaleV0 = props.scaleV0
1505 prune = props.prune
1506 pruneWidth = props.pruneWidth
1507 pruneBase = props.pruneBase
1508 pruneWidthPeak = props.pruneWidthPeak
1509 prunePowerLow = props.prunePowerLow
1510 prunePowerHigh = props.prunePowerHigh
1511 pruneRatio = props.pruneRatio
1512 leafDownAngle = radians(props.leafDownAngle)
1513 leafDownAngleV = radians(props.leafDownAngleV)
1514 leafRotate = radians(props.leafRotate)
1515 leafRotateV = radians(props.leafRotateV)
1516 leafScale = props.leafScale
1517 leafScaleX = props.leafScaleX
1518 leafScaleT = props.leafScaleT
1519 leafScaleV = props.leafScaleV
1520 leafShape = props.leafShape
1521 leafDupliObj = props.leafDupliObj
1522 bend = props.bend
1523 leafangle = props.leafangle
1524 horzLeaves = props.horzLeaves
1525 leafDist = int(props.leafDist)
1526 bevelRes = props.bevelRes
1527 resU = props.resU
1529 useArm = props.useArm
1530 previewArm = props.previewArm
1531 armAnim = props.armAnim
1532 leafAnim = props.leafAnim
1533 frameRate = props.frameRate
1534 loopFrames = props.loopFrames
1536 # windSpeed = props.windSpeed
1537 # windGust = props.windGust
1539 wind = props.wind
1540 gust = props.gust
1541 gustF = props.gustF
1543 af1 = props.af1
1544 af2 = props.af2
1545 af3 = props.af3
1547 makeMesh = props.makeMesh
1548 armLevels = props.armLevels
1549 boneStep = props.boneStep
1551 useOldDownAngle = props.useOldDownAngle
1552 useParentAngle = props.useParentAngle
1554 if not makeMesh:
1555 boneStep = [1, 1, 1, 1]
1557 # taper
1558 if autoTaper:
1559 taper = findtaper(length, taper, shape, shapeS, levels, customShape)
1560 # pLevels = branches[0]
1561 # taper = findtaper(length, taper, shape, shapeS, pLevels, customShape)
1563 leafObj = None
1565 # Some effects can be turned ON and OFF, the necessary variables are changed here
1566 if not props.bevel:
1567 bevelDepth = 0.0
1568 else:
1569 bevelDepth = 1.0
1571 if not props.showLeaves:
1572 leaves = 0
1573 else:
1574 leaves = props.leaves
1576 if props.handleType == '0':
1577 handles = 'AUTO'
1578 else:
1579 handles = 'VECTOR'
1581 for ob in bpy.context.view_layer.objects:
1582 ob.select_set(state=False)
1584 # Initialise the tree object and curve and adjust the settings
1585 cu = bpy.data.curves.new('tree', 'CURVE')
1586 treeOb = bpy.data.objects.new('tree', cu)
1587 bpy.context.scene.collection.objects.link(treeOb)
1589 # treeOb.location=bpy.context.scene.cursor.location attractUp
1591 cu.dimensions = '3D'
1592 cu.fill_mode = 'FULL'
1593 cu.bevel_depth = bevelDepth
1594 cu.bevel_resolution = bevelRes
1596 # Fix the scale of the tree now
1597 scaleVal = scale + uniform(-scaleV, scaleV)
1598 scaleVal += copysign(1e-6, scaleVal) # Move away from zero to avoid div by zero
1600 pruneBase = min(pruneBase, baseSize)
1601 # If pruning is turned on we need to draw the pruning envelope
1602 if prune:
1603 enHandle = 'VECTOR'
1604 enNum = 128
1605 enCu = bpy.data.curves.new('envelope', 'CURVE')
1606 enOb = bpy.data.objects.new('envelope', enCu)
1607 enOb.parent = treeOb
1608 bpy.context.scene.collection.objects.link(enOb)
1609 newSpline = enCu.splines.new('BEZIER')
1610 newPoint = newSpline.bezier_points[-1]
1611 newPoint.co = Vector((0, 0, scaleVal))
1612 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1613 # Set the coordinates by varying the z value, envelope will be aligned to the x-axis
1614 for c in range(enNum):
1615 newSpline.bezier_points.add(1)
1616 newPoint = newSpline.bezier_points[-1]
1617 ratioVal = (c + 1) / (enNum)
1618 zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
1619 newPoint.co = Vector(
1621 scaleVal * pruneWidth *
1622 shapeRatio(9, ratioVal, pruneWidthPeak, prunePowerHigh, prunePowerLow),
1623 0, zVal
1626 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1627 newSpline = enCu.splines.new('BEZIER')
1628 newPoint = newSpline.bezier_points[-1]
1629 newPoint.co = Vector((0, 0, scaleVal))
1630 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1631 # Create a second envelope but this time on the y-axis
1632 for c in range(enNum):
1633 newSpline.bezier_points.add(1)
1634 newPoint = newSpline.bezier_points[-1]
1635 ratioVal = (c + 1) / (enNum)
1636 zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
1637 newPoint.co = Vector(
1639 0, scaleVal * pruneWidth *
1640 shapeRatio(9, ratioVal, pruneWidthPeak, prunePowerHigh, prunePowerLow),
1641 zVal
1644 (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
1646 childP = []
1647 stemList = []
1649 levelCount = []
1650 splineToBone = deque([''])
1651 addsplinetobone = splineToBone.append
1653 # Each of the levels needed by the user we grow all the splines
1654 for n in range(levels):
1655 storeN = n
1656 stemList = deque()
1657 addstem = stemList.append
1658 # If n is used as an index to access parameters for the tree
1659 # it must be at most 3 or it will reference outside the array index
1660 n = min(3, n)
1661 splitError = 0.0
1663 # closeTip only on last level
1664 closeTipp = all([(n == levels - 1), closeTip])
1666 # If this is the first level of growth (the trunk) then we need some special work to begin the tree
1667 if n == 0:
1668 kickstart_trunk(addstem, levels, leaves, branches, cu, curve, curveRes,
1669 curveV, attractUp, length, lengthV, ratio, ratioPower, resU,
1670 scale0, scaleV0, scaleVal, taper, minRadius, rootFlare)
1671 # If this isn't the trunk then we may have multiple stem to initialise
1672 else:
1673 # For each of the points defined in the list of stem starting points we need to grow a stem.
1674 fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, curve, curveBack,
1675 curveRes, curveV, attractUp, downAngle, downAngleV, leafDist, leaves, length, lengthV,
1676 levels, n, ratioPower, resU, rotate, rotateV, scaleVal, shape, storeN,
1677 taper, shapeS, minRadius, radiusTweak, customShape, rMode, segSplits,
1678 useOldDownAngle, useParentAngle, boneStep)
1680 # change base size for each level
1681 if n > 0:
1682 baseSize *= baseSize_s # decrease at each level
1683 if (n == levels - 1):
1684 baseSize = 0
1686 childP = []
1687 # Now grow each of the stems in the list of those to be extended
1688 for st in stemList:
1689 # When using pruning, we need to ensure that the random effects
1690 # will be the same for each iteration to make sure the problem is linear
1691 randState = getstate()
1692 startPrune = True
1693 lengthTest = 0.0
1694 # Store all the original values for the stem to make sure
1695 # we have access after it has been modified by pruning
1696 originalLength = st.segL
1697 originalCurv = st.curv
1698 originalCurvV = st.curvV
1699 originalSeg = st.seg
1700 originalHandleR = st.p.handle_right.copy()
1701 originalHandleL = st.p.handle_left.copy()
1702 originalCo = st.p.co.copy()
1703 currentMax = 1.0
1704 currentMin = 0.0
1705 currentScale = 1.0
1706 oldMax = 1.0
1707 deleteSpline = False
1708 originalSplineToBone = copy.copy(splineToBone)
1709 forceSprout = False
1710 # Now do the iterative pruning, this uses a binary search and halts once the difference
1711 # between upper and lower bounds of the search are less than 0.005
1712 ratio, splineToBone = perform_pruning(
1713 baseSize, baseSplits, childP, cu, currentMax, currentMin,
1714 currentScale, curve, curveBack, curveRes, deleteSpline, forceSprout,
1715 handles, n, oldMax, originalSplineToBone, originalCo, originalCurv,
1716 originalCurvV, originalHandleL, originalHandleR, originalLength,
1717 originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio,
1718 pruneWidth, pruneBase, pruneWidthPeak, randState, ratio, scaleVal,
1719 segSplits, splineToBone, splitAngle, splitAngleV, st, startPrune,
1720 branchDist, length, splitByLen, closeTipp, nrings, splitBias,
1721 splitHeight, attractOut, rMode, lengthV, taperCrown, boneStep,
1722 rotate, rotateV
1725 levelCount.append(len(cu.splines))
1727 # If we need to add leaves, we do it here
1728 leafVerts = []
1729 leafFaces = []
1730 leafNormals = []
1732 leafMesh = None # in case we aren't creating leaves, we'll still have the variable
1734 leafP = []
1735 if leaves:
1736 oldRot = 0.0
1737 n = min(3, n + 1)
1738 # For each of the child points we add leaves
1739 for cp in childP:
1740 # If the special flag is set then we need to add several leaves at the same location
1741 if leaves < 0:
1742 oldRot = -leafRotate / 2
1743 for g in range(abs(leaves)):
1744 (vertTemp, faceTemp, normal, oldRot) = genLeafMesh(
1745 leafScale, leafScaleX, leafScaleT,
1746 leafScaleV, cp.co, cp.quat, cp.offset,
1747 len(leafVerts), leafDownAngle, leafDownAngleV,
1748 leafRotate, leafRotateV,
1749 oldRot, bend, leaves, leafShape,
1750 leafangle, horzLeaves
1752 leafVerts.extend(vertTemp)
1753 leafFaces.extend(faceTemp)
1754 leafNormals.extend(normal)
1755 leafP.append(cp)
1756 # Otherwise just add the leaves like splines
1757 else:
1758 (vertTemp, faceTemp, normal, oldRot) = genLeafMesh(
1759 leafScale, leafScaleX, leafScaleT, leafScaleV,
1760 cp.co, cp.quat, cp.offset, len(leafVerts),
1761 leafDownAngle, leafDownAngleV, leafRotate,
1762 leafRotateV, oldRot, bend, leaves, leafShape,
1763 leafangle, horzLeaves
1765 leafVerts.extend(vertTemp)
1766 leafFaces.extend(faceTemp)
1767 leafNormals.extend(normal)
1768 leafP.append(cp)
1770 # Create the leaf mesh and object, add geometry using from_pydata,
1771 # edges are currently added by validating the mesh which isn't great
1772 leafMesh = bpy.data.meshes.new('leaves')
1773 leafObj = bpy.data.objects.new('leaves', leafMesh)
1774 bpy.context.scene.collection.objects.link(leafObj)
1775 leafObj.parent = treeOb
1776 leafMesh.from_pydata(leafVerts, (), leafFaces)
1778 # set vertex normals for dupliVerts
1779 if leafShape == 'dVert':
1780 leafMesh.vertices.foreach_set('normal', leafNormals)
1782 # enable duplication
1783 if leafShape == 'dFace':
1784 leafObj.instance_type = "FACES"
1785 leafObj.use_instance_faces_scale = True
1786 leafObj.instance_faces_scale = 10.0
1787 try:
1788 if leafDupliObj not in "NONE":
1789 bpy.data.objects[leafDupliObj].parent = leafObj
1790 except KeyError:
1791 pass
1792 elif leafShape == 'dVert':
1793 leafObj.instance_type = "VERTS"
1794 leafObj.use_instance_vertices_rotation = True
1795 try:
1796 if leafDupliObj not in "NONE":
1797 bpy.data.objects[leafDupliObj].parent = leafObj
1798 except KeyError:
1799 pass
1801 # add leaf UVs
1802 if leafShape == 'rect':
1803 leafMesh.uv_layers.new(name='leafUV')
1804 uvlayer = leafMesh.uv_layers.active.data
1806 u1 = .5 * (1 - leafScaleX)
1807 u2 = 1 - u1
1809 for i in range(0, len(leafFaces)):
1810 uvlayer[i * 4 + 0].uv = Vector((u2, 0))
1811 uvlayer[i * 4 + 1].uv = Vector((u2, 1))
1812 uvlayer[i * 4 + 2].uv = Vector((u1, 1))
1813 uvlayer[i * 4 + 3].uv = Vector((u1, 0))
1815 elif leafShape == 'hex':
1816 leafMesh.uv_layers.new(name='leafUV')
1817 uvlayer = leafMesh.uv_layers.active.data
1819 u1 = .5 * (1 - leafScaleX)
1820 u2 = 1 - u1
1822 for i in range(0, int(len(leafFaces) / 2)):
1823 uvlayer[i * 8 + 0].uv = Vector((.5, 0))
1824 uvlayer[i * 8 + 1].uv = Vector((u1, 1 / 3))
1825 uvlayer[i * 8 + 2].uv = Vector((u1, 2 / 3))
1826 uvlayer[i * 8 + 3].uv = Vector((.5, 1))
1828 uvlayer[i * 8 + 4].uv = Vector((.5, 0))
1829 uvlayer[i * 8 + 5].uv = Vector((.5, 1))
1830 uvlayer[i * 8 + 6].uv = Vector((u2, 2 / 3))
1831 uvlayer[i * 8 + 7].uv = Vector((u2, 1 / 3))
1833 leafMesh.validate()
1835 leafVertSize = {'hex': 6, 'rect': 4, 'dFace': 4, 'dVert': 1}[leafShape]
1837 armLevels = min(armLevels, levels)
1838 armLevels -= 1
1840 # unpack vars from splineToBone
1841 splineToBone1 = splineToBone
1842 splineToBone = [s[0] if len(s) > 1 else s for s in splineToBone1]
1843 isend = [s[1] if len(s) > 1 else False for s in splineToBone1]
1844 issplit = [s[2] if len(s) > 2 else False for s in splineToBone1]
1845 splitPidx = [s[3] if len(s) > 2 else 0 for s in splineToBone1]
1847 # If we need an armature we add it
1848 if useArm:
1849 # Create the armature and objects
1850 create_armature(
1851 armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSize,
1852 leaves, levelCount, splineToBone, treeOb, wind, gust, gustF, af1,
1853 af2, af3, leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep
1856 # print(time.time()-startTime)
1858 # mesh branches
1859 if makeMesh:
1860 t1 = time.time()
1862 treeMesh = bpy.data.meshes.new('treemesh')
1863 treeObj = bpy.data.objects.new('treemesh', treeMesh)
1864 bpy.context.scene.collection.objects.link(treeObj)
1866 treeVerts = []
1867 treeEdges = []
1868 root_vert = []
1869 vert_radius = []
1870 vertexGroups = OrderedDict()
1871 lastVerts = []
1873 for i, curve in enumerate(cu.splines):
1874 points = curve.bezier_points
1876 # find branching level
1877 level = 0
1878 for l, c in enumerate(levelCount):
1879 if i < c:
1880 level = l
1881 break
1882 level = min(level, 3)
1884 step = boneStep[level]
1885 vindex = len(treeVerts)
1887 p1 = points[0]
1889 # add extra vertex for splits
1890 if issplit[i]:
1891 pb = int(splineToBone[i][4:-4])
1892 pn = splitPidx[i] # int(splineToBone[i][-3:])
1893 p_1 = cu.splines[pb].bezier_points[pn]
1894 p_2 = cu.splines[pb].bezier_points[pn + 1]
1895 p = evalBez(p_1.co, p_1.handle_right, p_2.handle_left, p_2.co, 1 - 1 / (resU + 1))
1896 treeVerts.append(p)
1898 root_vert.append(False)
1899 vert_radius.append((p1.radius * .75, p1.radius * .75))
1900 treeEdges.append([vindex, vindex + 1])
1901 vindex += 1
1903 if isend[i]:
1904 parent = lastVerts[int(splineToBone[i][4:-4])]
1905 vindex -= 1
1906 else:
1907 # add first point
1908 treeVerts.append(p1.co)
1909 root_vert.append(True)
1910 vert_radius.append((p1.radius, p1.radius))
1912 # add extra vertex for splits
1913 if issplit[i]:
1914 p2 = points[1]
1915 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, .001)
1916 treeVerts.append(p)
1917 root_vert.append(False)
1918 vert_radius.append((p1.radius, p1.radius)) #(p1.radius * .95, p1.radius * .95)
1919 treeEdges.append([vindex,vindex+1])
1920 vindex += 1
1922 # dont make vertex group if above armLevels
1923 if (i >= levelCount[armLevels]):
1924 idx = i
1925 groupName = splineToBone[idx]
1926 g = True
1927 while groupName not in vertexGroups:
1928 # find parent bone of parent bone
1929 b = splineToBone[idx]
1930 idx = int(b[4:-4])
1931 groupName = splineToBone[idx]
1932 else:
1933 g = False
1935 for n, p2 in enumerate(points[1:]):
1936 if not g:
1937 groupName = 'bone' + (str(i)).rjust(3, '0') + '.' + (str(n)).rjust(3, '0')
1938 groupName = roundBone(groupName, step)
1939 if groupName not in vertexGroups:
1940 vertexGroups[groupName] = []
1942 # parent first vert in split to parent branch bone
1943 if issplit[i] and n == 0:
1944 if g:
1945 vertexGroups[groupName].append(vindex - 1)
1946 else:
1947 vertexGroups[splineToBone[i]].append(vindex - 1)
1949 for f in range(1, resU + 1):
1950 pos = f / resU
1951 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, pos)
1952 radius = p1.radius + (p2.radius - p1.radius) * pos
1954 treeVerts.append(p)
1955 root_vert.append(False)
1956 vert_radius.append((radius, radius))
1958 if (isend[i]) and (n == 0) and (f == 1):
1959 edge = [parent, n * resU + f + vindex]
1960 else:
1961 edge = [n * resU + f + vindex - 1, n * resU + f + vindex]
1962 # add vert to group
1963 vertexGroups[groupName].append(n * resU + f + vindex - 1)
1964 treeEdges.append(edge)
1966 vertexGroups[groupName].append(n * resU + resU + vindex)
1968 p1 = p2
1970 lastVerts.append(len(treeVerts) - 1)
1972 treeMesh.from_pydata(treeVerts, treeEdges, ())
1974 for group in vertexGroups:
1975 treeObj.vertex_groups.new(name=group)
1976 treeObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
1978 # add armature
1979 if useArm:
1980 armMod = treeObj.modifiers.new('windSway', 'ARMATURE')
1981 if previewArm:
1982 bpy.data.objects['treeArm'].hide_viewport = True
1983 bpy.data.armatures['tree'].display_type = 'STICK'
1984 armMod.object = bpy.data.objects['treeArm']
1985 armMod.use_bone_envelopes = False
1986 armMod.use_vertex_groups = True
1987 treeObj.parent = bpy.data.objects['treeArm']
1989 # add skin modifier and set data
1990 skinMod = treeObj.modifiers.new('Skin', 'SKIN')
1991 skinMod.use_smooth_shade = True
1992 if previewArm:
1993 skinMod.show_viewport = False
1994 skindata = treeObj.data.skin_vertices[0].data
1995 for i, radius in enumerate(vert_radius):
1996 skindata[i].radius = radius
1997 skindata[i].use_root = root_vert[i]
1999 print("mesh time", time.time() - t1)