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 #####
26 from mathutils
import (
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
38 # Initialise the split error and axis vectors
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
47 def __init__(self
, spline
, curvature
, curvatureV
, attractUp
, segments
, maxSegs
,
48 segLength
, childStems
, stemRadStart
, stemRadEnd
, splineNum
, ofst
, pquat
):
50 self
.p
= spline
.bezier_points
[-1]
52 self
.curvV
= curvatureV
53 self
.vertAtt
= attractUp
57 self
.children
= childStems
58 self
.radS
= stemRadStart
59 self
.radE
= stemRadEnd
62 self
.patentQuat
= pquat
66 # This method determines the quaternion of the end of the spline
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')
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
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
83 self
.p
= self
.spline
.bezier_points
[-1]
87 # This class contains the data for a point where a new branch will sprout
89 def __init__(self
, coords
, quat
, radiusPar
, offset
, sOfst
, lengthPar
, parBone
):
92 self
.radiusPar
= radiusPar
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):
102 return 0.05 + 0.95 * ratio
# 0.2 + 0.8 * ratio
104 return 0.2 + 0.8 * sin(pi
* ratio
)
106 return 0.2 + 0.8 * sin(0.5 * pi
* ratio
)
110 return 0.5 + 0.5 * ratio
113 return 0.05 + 0.95 * ratio
/ 0.7
115 return 0.05 + 0.95 * (1.0 - ratio
) / 0.3
117 return 1.0 - 0.8 * ratio
120 return 0.5 + 0.5 * ratio
/ 0.7
122 return 0.5 + 0.5 * (1.0 - ratio
) / 0.3
128 pos
= (r
- custom
[2]) / (1 - custom
[2])
129 # if (custom[0] >= custom[1] <= custom[3]) or (custom[0] <= custom[1] >= custom[3]):
131 v
= (pos
* (custom
[3] - custom
[1])) + custom
[1]
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]
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
)
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
155 nEff
= round(n
+ splitError
, 0)
156 splitError
-= (nEff
- n
)
178 # Determine the declination from a given quaternion
179 def declination(quat
):
180 tempVec
= zAxis
.copy()
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()
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:
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
):
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
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
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
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
252 tTemp
= length
- index
254 points
[index
].co
, points
[index
].handle_right
,
255 points
[index
+ 1].handle_left
, points
[index
+ 1].co
, tTemp
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
267 coord
, quat
, (parRad
, radius
), t
, lPar
, 'bone' +
268 (str(stem
.splN
).rjust(3, '0')) + '.' + (str(index
).rjust(3, '0')))
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
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
290 tTemp
= length
- index
293 points
[index
].co
, points
[index
].handle_right
,
294 points
[index
+ 1].handle_left
, points
[index
+ 1].co
, tTemp
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
307 coord
, quat
, (parRad
, radius
), t
, ofst
, lPar
,
308 'bone' + (str(stem
.splN
).rjust(3, '0')) + '.' + (str(index
).rjust(3, '0')))
313 coord
= points
[-1].co
314 quat
= (points
[-1].handle_right
- points
[-1].co
).to_track_quat('Z', 'Y')
315 radius
= points
[-1].radius
318 coord
, quat
, (parRad
, radius
), 1, 1, lPar
,
319 'bone' + (str(stem
.splN
).rjust(3, '0')) + '.' + (str(index
).rjust(3, '0'))
326 # round down bone number
327 def roundBone(bone
, step
):
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
336 return [radians(a
) for a
in list]
339 def anglemean(a1
, a2
, fac
):
344 x
= x1
+ (x2
- x1
) * fac
345 y
= y1
+ (y2
- y1
) * fac
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
):
356 if (n
== 0) and (kp
<= splitHeight
):
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
366 curveVarMat
= Matrix
.Rotation(curveVar
, 3, 'Y')
368 # First find the current direction of the stem
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')
383 dec
= declination(dir) / 180
385 tf
= 1 - (dec
* taperCrown
* 30)
391 if (n
> 0) and (kp
> 0) and (outAtt
> 0):
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()
400 parWeight = kp * degrees(stem.curvV) * pi
401 parWeight = parWeight * kp
403 if (n > 1) and (parWeight != 0):
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
420 cuData
= stem
.spline
.id_data
.name
421 cu
= bpy
.data
.curves
[cuData
]
424 angle
= choice([-1, 1]) * (splitAng
+ uniform(-splitAngV
, splitAngV
))
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
):
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')
449 stem
.radS
* (1 - stem
.seg
/ stem
.segMax
) + stem
.radE
* (stem
.seg
/ stem
.segMax
)
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
)
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
482 # split length variation
483 stemL
= stemsegL
* lenV
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
)
497 stem
.radS
* (1 - (stem
.seg
+ 1) / stem
.segMax
) +
498 stem
.radE
* ((stem
.seg
+ 1) / stem
.segMax
)
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
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
)
531 spreadMat
= Matrix
.Rotation(-spreadangle
, 3, 'Y')
532 if n
!= 0: # Special case for trunk splits
533 dirVec
.rotate(spreadMat
)
537 stem
.splitlast
= 1 # numSplit #keep track of numSplit for next stem
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
)
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
)
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
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':
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))]
608 normal
= Vector((0, 0, 1))
611 rotMat
= Matrix
.Rotation(oldRot
, 3, 'Y')
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
618 oldRot
= -copysign(rotate
+ uniform(-rotateV
, rotateV
), oldRot
)
620 # If the special -ve flag for leaves is used we need a different rotation of the leaf geometry
623 rotMat
= Matrix
.Rotation(0, 3, 'Y')
625 oldRot
+= rotate
/ (-leaves
- 1)
627 oldRot
+= rotate
+ uniform(-rotateV
, rotateV
)
630 rotMat = Matrix.Rotation(oldRot, 3, 'Y')
632 rotMat = Matrix.Rotation(oldRot, 3, 'Z')
635 # downRotMat = Matrix.Rotation(downAngle+uniform(-downAngleV, downAngleV), 3, 'X')
638 downV
= -downAngleV
* offset
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))
650 leafScale
= leafScale
* (1 - (1 - f
) * -leafScaleT
)
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()
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
685 v
.x
*= leafScaleX
* leafScale
687 v
.rotate(Euler((0, 0, radians(180))))
690 v
.rotate(Matrix
.Rotation(radians(-leafangle
), 3, 'X'))
693 v
.rotate(Euler((0, 0, radians(90))))
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')
707 if (bend
!= 0.0) and (leaves
> 0):
708 # Correct the rotation
710 v
.rotate(rotateZOrien
)
712 v
.rotate(rotateZOrien2
)
714 if leafShape
== 'dVert':
718 vertsList
.append([v
.x
, v
.y
, v
.z
])
722 vertsList
.append([v
.x
, v
.y
, v
.z
])
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')
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
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
):
772 # Get some data about the spline like length and number of points
773 numPoints
= len(s
.bezier_points
) - 1
775 # find branching level
777 for l
, c
in enumerate(levelCount
):
781 level
= min(level
, 3)
783 step
= boneStep
[level
]
785 # Calculate things for animation
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
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
809 for n
in range(0, numPoints
, step
):
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
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
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
832 b
.parent
= arm
.edit_bones
[parBone
]
833 # Otherwise, we need to attach it to the previous bone in the spline
837 # If there isn't a previous bone then it shouldn't be attached
839 b
.use_connect
= False
841 # Add the animation to the armature if required
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
853 ag
= (wind
* gust
/ 50) * a0
864 swayFreq
= gustF
* (tau
/ fps
) * frameRate
# animSpeed # .075 # 0.02
866 swayFreq
= 1 / (loopFrames
/ tau
)
868 # Prevent tree base from rotating
869 if (boneName
== "bone000.000") or (boneName
== "bone000.001"):
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
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
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])
933 bname
= "leaf" + str(i
)
934 b
= arm
.edit_bones
.new(bname
)
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
] = [
942 leafMesh
.vertices
[leafVertSize
* i
:(leafVertSize
* i
+ leafVertSize
)]
946 # Define all the required parameters of the wind sway by the dimension of the spline
950 bMult
= (1 / animSpeed
) * 6
951 bMult
*= 1 / max(af2
, .001)
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')
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
993 if leafParent
not in vertexGroups
:
994 vertexGroups
[leafParent
] = []
995 vertexGroups
[leafParent
].extend(
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)
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
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.
1054 # use fancy child point selection / rotation
1055 if (n
== 1) and (rMode
!= "original"):
1056 childP_T
= OrderedDict()
1062 if p
.offset
not in childP_T
:
1063 childP_T
[p
.offset
] = [p
]
1065 childP_T
[p
.offset
].append(p
)
1067 childP_T
= [childP_T
[k
] for k
in sorted(childP_T
.keys())]
1072 if rMode
== "rotate":
1074 oldRotate
= -copysign(rotate
[n
], oldRotate
)
1076 oldRotate
+= rotate
[n
]
1077 bRotate
= oldRotate
+ uniform(-rotateV
[n
], rotateV
[n
])
1079 # choose start point whose angle is closest to the rotate angle
1083 a2
= atan2(a
.co
[0], -a
.co
[1])
1084 d
= min((a1
- a2
+ tau
) % tau
, (a2
- a1
+ tau
) % tau
)
1087 idx
= a_diff
.index(min(a_diff
))
1089 # find actual rotate angle from branch location
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)
1103 downA
= downAngle
[n
]
1104 if downA
< (.5 * pi
):
1105 downA
= sin(downA
) ** 2
1111 bv
= Vector((b
[0], -b
[1]))
1113 a
= atan2(cv
[0], cv
[1])
1116 # add fill points at top #experimental
1117 fillHeight = 1 - degrees(rotateV[3]) # 0.8
1119 w = (p[0].offset - fillHeight) / (1- fillHeight)
1120 prob_b = random() < w
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
])
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
))
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]
1147 tempPos
= zAxis
.copy()
1148 # If the -ve flag for downAngle is used we need a special formula to find it
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
1154 downV
= uniform(-downAngleV
[n
], downAngleV
[n
])
1156 if downAngleV
[n
] < 0.0:
1157 downV
= uniform(-downAngleV
[n
], downAngleV
[n
])
1159 downV
= -downAngleV
[n
] * (1 - (1 - p
.offset
) / (1 - baseSize
)) ** 2 # (110, 80) = (60, -50)
1162 downRotMat
= Matrix
.Rotation(0, 3, 'X')
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
1169 oldRotate
= -copysign(rotate
[n
], oldRotate
)
1170 # Otherwise just generate a random number in the specified range
1172 oldRotate
+= rotate
[n
]
1173 bRotate
= oldRotate
+ uniform(-rotateV
[n
], rotateV
[n
])
1175 if (n
== 1) and (rMode
== "rotate"):
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
)
1185 if (rMode
== "rotate") and (n
== 1) and (p
.offset
!= 1):
1187 edir
= p
.quat
.to_euler('XYZ', Euler((0, 0, bRotate
), 'XYZ'))
1192 tempPos
.rotate(edir
)
1194 dec
= declination(p
.quat
)
1195 tempPos
.rotate(Matrix
.Rotation(radians(dec
), 3, 'X'))
1198 tempPos
.rotate(edir
)
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.
1209 for l
in length
[:n
+ 1]:
1211 lMax
= length
[n
] # * uniform(1 - lenV, 1 + lenV)
1213 lShape
= shapeRatio(shape
, (1 - p
.stemOffset
) / (1 - baseSize
), custom
=customShape
)
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):
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])
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
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
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])
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):
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
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
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
1294 newPoint
.radius
= st
.radS
1295 splineToBone
= originalSplineToBone
1297 # Initialise the spline list for those contained in the current level of branching
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
1312 kp
= (k
/ (curveRes
[n
] - 1)) # * 2
1317 splitValue
= segSplits
[n
]
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
:
1325 lastsplit
= getattr(spl
, 'splitlast', 0)
1326 splitVal
= splitValue
1328 splitVal
= splitValue
* 1.33
1329 elif lastsplit
== 1:
1330 splitVal
= splitValue
* splitValue
1334 elif (n
== 0) and (k
< ((curveRes
[n
] - 1) * splitHeight
)) and (k
!= 1):
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):
1342 if (n
>= 1) and splitByLen
:
1343 L
= ((spl
.segL
* curveRes
[n
]) / scaleVal
)
1345 for l
in length
[:n
+ 1]:
1348 numSplit
= splits2(splitVal
* L
)
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 *
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
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
1372 (coordMag
/ scaleVal
) < pruneWidth
* shapeRatio(9, ratio
, pruneWidthPeak
, prunePowerHigh
,
1374 # If the point is not inside then we adjust the scale and current search bounds
1377 currentMax
= currentScale
1378 currentScale
= 0.5 * (currentMax
+ currentMin
)
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
1385 currentScale
= 0.5 * (currentMax
+ currentMin
)
1386 if insideBool
and ((currentMax
- 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
)
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
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]]
1409 tVals
= [t
for t
in tVals
if t
> baseSize
]
1411 # branch distribution
1413 tVals
= [((t
- baseSize
) / (1 - baseSize
)) for t
in tVals
]
1414 if branchDist
< 1.0:
1415 tVals
= [t
** (1 / branchDist
) for t
in tVals
]
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
1428 # If pruning isn't enabled then make sure it doesn't loop
1431 return ratio
, splineToBone
1434 # calculate taper automatically
1435 def findtaper(length
, taper
, shape
, shapeS
, levels
, customShape
):
1437 for i
, t
in enumerate(length
):
1441 shp
= shapeRatio(shape
, 0, custom
=customShape
)
1443 shp
= shapeRatio(shapeS
, 0)
1448 for i
, t
in enumerate(taperS
):
1450 for x
in range(i
+ 1):
1455 for i
, t
in enumerate(taperP
):
1456 t
= sum(taperP
[i
:levels
])
1460 for i
, t
in enumerate(taperR
):
1462 t
= taperP
[i
] / taperR
[i
]
1463 except ZeroDivisionError:
1467 taperT
= [t
* taper
[i
] for i
, t
in enumerate(taperT
)]
1474 # startTime = time.time()
1475 # Set the seed for repeatable results
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
1492 splitAngle
= toRad(props
.splitAngle
)
1493 splitAngleV
= toRad(props
.splitAngleV
)
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
1508 minRadius
= props
.minRadius
1509 closeTip
= props
.closeTip
1510 rootFlare
= props
.rootFlare
1511 autoTaper
= props
.autoTaper
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
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
1539 leafangle
= props
.leafangle
1540 horzLeaves
= props
.horzLeaves
1541 leafDist
= int(props
.leafDist
)
1542 bevelRes
= props
.bevelRes
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
1563 makeMesh
= props
.makeMesh
1564 armLevels
= props
.armLevels
1565 boneStep
= props
.boneStep
1567 useOldDownAngle
= props
.useOldDownAngle
1568 useParentAngle
= props
.useParentAngle
1571 boneStep
= [1, 1, 1, 1]
1575 taper
= findtaper(length
, taper
, shape
, shapeS
, levels
, customShape
)
1576 # pLevels = branches[0]
1577 # taper = findtaper(length, taper, shape, shapeS, pLevels, customShape)
1581 # Some effects can be turned ON and OFF, the necessary variables are changed here
1587 if not props
.showLeaves
:
1590 leaves
= props
.leaves
1592 if props
.handleType
== '0':
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
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
),
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
),
1660 (newPoint
.handle_right_type
, newPoint
.handle_left_type
) = (enHandle
, enHandle
)
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
):
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
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
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
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
1698 baseSize
*= baseSize_s
# decrease at each level
1699 if (n
== levels
- 1):
1703 # Now grow each of the stems in the list of those to be extended
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()
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()
1723 deleteSpline
= False
1724 originalSplineToBone
= copy
.copy(splineToBone
)
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
,
1741 levelCount
.append(len(cu
.splines
))
1743 # If we need to add leaves, we do it here
1748 leafMesh
= None # in case we aren't creating leaves, we'll still have the variable
1754 # For each of the child points we add leaves
1756 # If the special flag is set then we need to add several leaves at the same location
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
)
1772 # Otherwise just add the leaves like splines
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
)
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
1804 if leafDupliObj
not in "NONE":
1805 bpy
.data
.objects
[leafDupliObj
].parent
= leafObj
1808 elif leafShape
== 'dVert':
1809 leafObj
.instance_type
= "VERTS"
1810 leafObj
.use_instance_vertices_rotation
= True
1812 if leafDupliObj
not in "NONE":
1813 bpy
.data
.objects
[leafDupliObj
].parent
= leafObj
1818 if leafShape
== 'rect':
1819 leafMesh
.uv_layers
.new(name
='leafUV')
1820 uvlayer
= leafMesh
.uv_layers
.active
.data
1822 u1
= .5 * (1 - leafScaleX
)
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
)
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))
1851 leafVertSize
= {'hex': 6, 'rect': 4, 'dFace': 4, 'dVert': 1}[leafShape
]
1853 armLevels
= min(armLevels
, levels
)
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
1865 # Create the armature and objects
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)
1878 treeMesh
= bpy
.data
.meshes
.new('treemesh')
1879 treeObj
= bpy
.data
.objects
.new('treemesh', treeMesh
)
1880 bpy
.context
.scene
.collection
.objects
.link(treeObj
)
1886 vertexGroups
= OrderedDict()
1889 for i
, curve
in enumerate(cu
.splines
):
1890 points
= curve
.bezier_points
1892 # find branching level
1894 for l
, c
in enumerate(levelCount
):
1898 level
= min(level
, 3)
1900 step
= boneStep
[level
]
1901 vindex
= len(treeVerts
)
1905 # add extra vertex for splits
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))
1914 root_vert
.append(False)
1915 vert_radius
.append((p1
.radius
* .75, p1
.radius
* .75))
1916 treeEdges
.append([vindex
, vindex
+ 1])
1920 parent
= lastVerts
[int(splineToBone
[i
][4:-4])]
1924 treeVerts
.append(p1
.co
)
1925 root_vert
.append(True)
1926 vert_radius
.append((p1
.radius
, p1
.radius
))
1928 # add extra vertex for splits
1931 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, .001)
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])
1938 # dont make vertex group if above armLevels
1939 if (i
>= levelCount
[armLevels
]):
1941 groupName
= splineToBone
[idx
]
1943 while groupName
not in vertexGroups
:
1944 # find parent bone of parent bone
1945 b
= splineToBone
[idx
]
1947 groupName
= splineToBone
[idx
]
1951 for n
, p2
in enumerate(points
[1:]):
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:
1961 vertexGroups
[groupName
].append(vindex
- 1)
1963 vertexGroups
[splineToBone
[i
]].append(vindex
- 1)
1965 for f
in range(1, resU
+ 1):
1967 p
= evalBez(p1
.co
, p1
.handle_right
, p2
.handle_left
, p2
.co
, pos
)
1968 radius
= p1
.radius
+ (p2
.radius
- p1
.radius
) * pos
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
]
1977 edge
= [n
* resU
+ f
+ vindex
- 1, n
* resU
+ f
+ vindex
]
1979 vertexGroups
[groupName
].append(n
* resU
+ f
+ vindex
- 1)
1980 treeEdges
.append(edge
)
1982 vertexGroups
[groupName
].append(n
* resU
+ resU
+ vindex
)
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')
1996 armMod
= treeObj
.modifiers
.new('windSway', 'ARMATURE')
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
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
)