1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
10 from mathutils
import (
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
22 # Initialise the split error and axis vectors
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
31 def __init__(self
, spline
, curvature
, curvatureV
, attractUp
, segments
, maxSegs
,
32 segLength
, childStems
, stemRadStart
, stemRadEnd
, splineNum
, ofst
, pquat
):
34 self
.p
= spline
.bezier_points
[-1]
36 self
.curvV
= curvatureV
37 self
.vertAtt
= attractUp
41 self
.children
= childStems
42 self
.radS
= stemRadStart
43 self
.radE
= stemRadEnd
46 self
.patentQuat
= pquat
50 # This method determines the quaternion of the end of the spline
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')
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
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
67 self
.p
= self
.spline
.bezier_points
[-1]
71 # This class contains the data for a point where a new branch will sprout
73 def __init__(self
, coords
, quat
, radiusPar
, offset
, sOfst
, lengthPar
, parBone
):
76 self
.radiusPar
= radiusPar
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):
86 return 0.05 + 0.95 * ratio
# 0.2 + 0.8 * ratio
88 return 0.2 + 0.8 * sin(pi
* ratio
)
90 return 0.2 + 0.8 * sin(0.5 * pi
* ratio
)
94 return 0.5 + 0.5 * ratio
97 return 0.05 + 0.95 * ratio
/ 0.7
99 return 0.05 + 0.95 * (1.0 - ratio
) / 0.3
101 return 1.0 - 0.8 * ratio
104 return 0.5 + 0.5 * ratio
/ 0.7
106 return 0.5 + 0.5 * (1.0 - ratio
) / 0.3
112 pos
= (r
- custom
[2]) / (1 - custom
[2])
113 # if (custom[0] >= custom[1] <= custom[3]) or (custom[0] <= custom[1] >= custom[3]):
115 v
= (pos
* (custom
[3] - custom
[1])) + custom
[1]
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]
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
)
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
139 nEff
= round(n
+ splitError
, 0)
140 splitError
-= (nEff
- n
)
162 # Determine the declination from a given quaternion
163 def declination(quat
):
164 tempVec
= zAxis
.copy()
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()
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:
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
):
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
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
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
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
236 tTemp
= length
- index
238 points
[index
].co
, points
[index
].handle_right
,
239 points
[index
+ 1].handle_left
, points
[index
+ 1].co
, tTemp
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
251 coord
, quat
, (parRad
, radius
), t
, lPar
, 'bone' +
252 (str(stem
.splN
).rjust(3, '0')) + '.' + (str(index
).rjust(3, '0')))
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
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
274 tTemp
= length
- index
277 points
[index
].co
, points
[index
].handle_right
,
278 points
[index
+ 1].handle_left
, points
[index
+ 1].co
, tTemp
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
291 coord
, quat
, (parRad
, radius
), t
, ofst
, lPar
,
292 'bone' + (str(stem
.splN
).rjust(3, '0')) + '.' + (str(index
).rjust(3, '0')))
297 coord
= points
[-1].co
298 quat
= (points
[-1].handle_right
- points
[-1].co
).to_track_quat('Z', 'Y')
299 radius
= points
[-1].radius
302 coord
, quat
, (parRad
, radius
), 1, 1, lPar
,
303 'bone' + (str(stem
.splN
).rjust(3, '0')) + '.' + (str(index
).rjust(3, '0'))
310 # round down bone number
311 def roundBone(bone
, step
):
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
320 return [radians(a
) for a
in list]
323 def anglemean(a1
, a2
, fac
):
328 x
= x1
+ (x2
- x1
) * fac
329 y
= y1
+ (y2
- y1
) * fac
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
):
340 if (n
== 0) and (kp
<= splitHeight
):
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
350 curveVarMat
= Matrix
.Rotation(curveVar
, 3, 'Y')
352 # First find the current direction of the stem
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')
367 dec
= declination(dir) / 180
369 tf
= 1 - (dec
* taperCrown
* 30)
375 if (n
> 0) and (kp
> 0) and (outAtt
> 0):
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()
384 parWeight = kp * degrees(stem.curvV) * pi
385 parWeight = parWeight * kp
387 if (n > 1) and (parWeight != 0):
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
404 cuData
= stem
.spline
.id_data
.name
405 cu
= bpy
.data
.curves
[cuData
]
408 angle
= choice([-1, 1]) * (splitAng
+ uniform(-splitAngV
, splitAngV
))
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
):
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')
433 stem
.radS
* (1 - stem
.seg
/ stem
.segMax
) + stem
.radE
* (stem
.seg
/ stem
.segMax
)
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
)
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
466 # split length variation
467 stemL
= stemsegL
* lenV
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
)
481 stem
.radS
* (1 - (stem
.seg
+ 1) / stem
.segMax
) +
482 stem
.radE
* ((stem
.seg
+ 1) / stem
.segMax
)
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
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
)
515 spreadMat
= Matrix
.Rotation(-spreadangle
, 3, 'Y')
516 if n
!= 0: # Special case for trunk splits
517 dirVec
.rotate(spreadMat
)
521 stem
.splitlast
= 1 # numSplit #keep track of numSplit for next stem
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
)
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
)
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
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':
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))]
592 normal
= Vector((0, 0, 1))
595 rotMat
= Matrix
.Rotation(oldRot
, 3, 'Y')
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
602 oldRot
= -copysign(rotate
+ uniform(-rotateV
, rotateV
), oldRot
)
604 # If the special -ve flag for leaves is used we need a different rotation of the leaf geometry
607 rotMat
= Matrix
.Rotation(0, 3, 'Y')
609 oldRot
+= rotate
/ (-leaves
- 1)
611 oldRot
+= rotate
+ uniform(-rotateV
, rotateV
)
614 rotMat = Matrix.Rotation(oldRot, 3, 'Y')
616 rotMat = Matrix.Rotation(oldRot, 3, 'Z')
619 # downRotMat = Matrix.Rotation(downAngle+uniform(-downAngleV, downAngleV), 3, 'X')
622 downV
= -downAngleV
* offset
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))
634 leafScale
= leafScale
* (1 - (1 - f
) * -leafScaleT
)
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()
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
669 v
.x
*= leafScaleX
* leafScale
671 v
.rotate(Euler((0, 0, radians(180))))
674 v
.rotate(Matrix
.Rotation(radians(-leafangle
), 3, 'X'))
677 v
.rotate(Euler((0, 0, radians(90))))
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')
691 if (bend
!= 0.0) and (leaves
> 0):
692 # Correct the rotation
694 v
.rotate(rotateZOrien
)
696 v
.rotate(rotateZOrien2
)
698 if leafShape
== 'dVert':
702 vertsList
.append([v
.x
, v
.y
, v
.z
])
706 vertsList
.append([v
.x
, v
.y
, v
.z
])
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')
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
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
):
756 # Get some data about the spline like length and number of points
757 numPoints
= len(s
.bezier_points
) - 1
759 # find branching level
761 for l
, c
in enumerate(levelCount
):
765 level
= min(level
, 3)
767 step
= boneStep
[level
]
769 # Calculate things for animation
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
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
793 for n
in range(0, numPoints
, step
):
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
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
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
816 b
.parent
= arm
.edit_bones
[parBone
]
817 # Otherwise, we need to attach it to the previous bone in the spline
821 # If there isn't a previous bone then it shouldn't be attached
823 b
.use_connect
= False
825 # Add the animation to the armature if required
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
837 ag
= (wind
* gust
/ 50) * a0
848 swayFreq
= gustF
* (tau
/ fps
) * frameRate
# animSpeed # .075 # 0.02
850 swayFreq
= 1 / (loopFrames
/ tau
)
852 # Prevent tree base from rotating
853 if (boneName
== "bone000.000") or (boneName
== "bone000.001"):
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
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
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])
917 bname
= "leaf" + str(i
)
918 b
= arm
.edit_bones
.new(bname
)
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
] = [
926 leafMesh
.vertices
[leafVertSize
* i
:(leafVertSize
* i
+ leafVertSize
)]
930 # Define all the required parameters of the wind sway by the dimension of the spline
934 bMult
= (1 / animSpeed
) * 6
935 bMult
*= 1 / max(af2
, .001)
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')
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
977 if leafParent
not in vertexGroups
:
978 vertexGroups
[leafParent
] = []
979 vertexGroups
[leafParent
].extend(
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)
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
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.
1038 # use fancy child point selection / rotation
1039 if (n
== 1) and (rMode
!= "original"):
1040 childP_T
= OrderedDict()
1046 if p
.offset
not in childP_T
:
1047 childP_T
[p
.offset
] = [p
]
1049 childP_T
[p
.offset
].append(p
)
1051 childP_T
= [childP_T
[k
] for k
in sorted(childP_T
.keys())]
1056 if rMode
== "rotate":
1058 oldRotate
= -copysign(rotate
[n
], oldRotate
)
1060 oldRotate
+= rotate
[n
]
1061 bRotate
= oldRotate
+ uniform(-rotateV
[n
], rotateV
[n
])
1063 # choose start point whose angle is closest to the rotate angle
1067 a2
= atan2(a
.co
[0], -a
.co
[1])
1068 d
= min((a1
- a2
+ tau
) % tau
, (a2
- a1
+ tau
) % tau
)
1071 idx
= a_diff
.index(min(a_diff
))
1073 # find actual rotate angle from branch location
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)
1087 downA
= downAngle
[n
]
1088 if downA
< (.5 * pi
):
1089 downA
= sin(downA
) ** 2
1095 bv
= Vector((b
[0], -b
[1]))
1097 a
= atan2(cv
[0], cv
[1])
1100 # add fill points at top #experimental
1101 fillHeight = 1 - degrees(rotateV[3]) # 0.8
1103 w = (p[0].offset - fillHeight) / (1- fillHeight)
1104 prob_b = random() < w
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
])
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
))
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]
1131 tempPos
= zAxis
.copy()
1132 # If the -ve flag for downAngle is used we need a special formula to find it
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
1138 downV
= uniform(-downAngleV
[n
], downAngleV
[n
])
1140 if downAngleV
[n
] < 0.0:
1141 downV
= uniform(-downAngleV
[n
], downAngleV
[n
])
1143 downV
= -downAngleV
[n
] * (1 - (1 - p
.offset
) / (1 - baseSize
)) ** 2 # (110, 80) = (60, -50)
1146 downRotMat
= Matrix
.Rotation(0, 3, 'X')
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
1153 oldRotate
= -copysign(rotate
[n
], oldRotate
)
1154 # Otherwise just generate a random number in the specified range
1156 oldRotate
+= rotate
[n
]
1157 bRotate
= oldRotate
+ uniform(-rotateV
[n
], rotateV
[n
])
1159 if (n
== 1) and (rMode
== "rotate"):
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
)
1169 if (rMode
== "rotate") and (n
== 1) and (p
.offset
!= 1):
1171 edir
= p
.quat
.to_euler('XYZ', Euler((0, 0, bRotate
), 'XYZ'))
1176 tempPos
.rotate(edir
)
1178 dec
= declination(p
.quat
)
1179 tempPos
.rotate(Matrix
.Rotation(radians(dec
), 3, 'X'))
1182 tempPos
.rotate(edir
)
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.
1193 for l
in length
[:n
+ 1]:
1195 lMax
= length
[n
] # * uniform(1 - lenV, 1 + lenV)
1197 lShape
= shapeRatio(shape
, (1 - p
.stemOffset
) / (1 - baseSize
), custom
=customShape
)
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):
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])
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
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
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])
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):
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
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
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
1278 newPoint
.radius
= st
.radS
1279 splineToBone
= originalSplineToBone
1281 # Initialise the spline list for those contained in the current level of branching
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
1296 kp
= (k
/ (curveRes
[n
] - 1)) # * 2
1301 splitValue
= segSplits
[n
]
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
:
1309 lastsplit
= getattr(spl
, 'splitlast', 0)
1310 splitVal
= splitValue
1312 splitVal
= splitValue
* 1.33
1313 elif lastsplit
== 1:
1314 splitVal
= splitValue
* splitValue
1318 elif (n
== 0) and (k
< ((curveRes
[n
] - 1) * splitHeight
)) and (k
!= 1):
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):
1326 if (n
>= 1) and splitByLen
:
1327 L
= ((spl
.segL
* curveRes
[n
]) / scaleVal
)
1329 for l
in length
[:n
+ 1]:
1332 numSplit
= splits2(splitVal
* L
)
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 *
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
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
1356 (coordMag
/ scaleVal
) < pruneWidth
* shapeRatio(9, ratio
, pruneWidthPeak
, prunePowerHigh
,
1358 # If the point is not inside then we adjust the scale and current search bounds
1361 currentMax
= currentScale
1362 currentScale
= 0.5 * (currentMax
+ currentMin
)
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
1369 currentScale
= 0.5 * (currentMax
+ currentMin
)
1370 if insideBool
and ((currentMax
- 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
)
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
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]]
1393 tVals
= [t
for t
in tVals
if t
> baseSize
]
1395 # branch distribution
1397 tVals
= [((t
- baseSize
) / (1 - baseSize
)) for t
in tVals
]
1398 if branchDist
< 1.0:
1399 tVals
= [t
** (1 / branchDist
) for t
in tVals
]
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
1412 # If pruning isn't enabled then make sure it doesn't loop
1415 return ratio
, splineToBone
1418 # calculate taper automatically
1419 def findtaper(length
, taper
, shape
, shapeS
, levels
, customShape
):
1421 for i
, t
in enumerate(length
):
1425 shp
= shapeRatio(shape
, 0, custom
=customShape
)
1427 shp
= shapeRatio(shapeS
, 0)
1432 for i
, t
in enumerate(taperS
):
1434 for x
in range(i
+ 1):
1439 for i
, t
in enumerate(taperP
):
1440 t
= sum(taperP
[i
:levels
])
1444 for i
, t
in enumerate(taperR
):
1446 t
= taperP
[i
] / taperR
[i
]
1447 except ZeroDivisionError:
1451 taperT
= [t
* taper
[i
] for i
, t
in enumerate(taperT
)]
1458 # startTime = time.time()
1459 # Set the seed for repeatable results
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
1476 splitAngle
= toRad(props
.splitAngle
)
1477 splitAngleV
= toRad(props
.splitAngleV
)
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
1492 minRadius
= props
.minRadius
1493 closeTip
= props
.closeTip
1494 rootFlare
= props
.rootFlare
1495 autoTaper
= props
.autoTaper
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
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
1523 leafangle
= props
.leafangle
1524 horzLeaves
= props
.horzLeaves
1525 leafDist
= int(props
.leafDist
)
1526 bevelRes
= props
.bevelRes
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
1547 makeMesh
= props
.makeMesh
1548 armLevels
= props
.armLevels
1549 boneStep
= props
.boneStep
1551 useOldDownAngle
= props
.useOldDownAngle
1552 useParentAngle
= props
.useParentAngle
1555 boneStep
= [1, 1, 1, 1]
1559 taper
= findtaper(length
, taper
, shape
, shapeS
, levels
, customShape
)
1560 # pLevels = branches[0]
1561 # taper = findtaper(length, taper, shape, shapeS, pLevels, customShape)
1565 # Some effects can be turned ON and OFF, the necessary variables are changed here
1571 if not props
.showLeaves
:
1574 leaves
= props
.leaves
1576 if props
.handleType
== '0':
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
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
),
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
),
1644 (newPoint
.handle_right_type
, newPoint
.handle_left_type
) = (enHandle
, enHandle
)
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
):
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
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
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
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
1682 baseSize
*= baseSize_s
# decrease at each level
1683 if (n
== levels
- 1):
1687 # Now grow each of the stems in the list of those to be extended
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()
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()
1707 deleteSpline
= False
1708 originalSplineToBone
= copy
.copy(splineToBone
)
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
,
1725 levelCount
.append(len(cu
.splines
))
1727 # If we need to add leaves, we do it here
1732 leafMesh
= None # in case we aren't creating leaves, we'll still have the variable
1738 # For each of the child points we add leaves
1740 # If the special flag is set then we need to add several leaves at the same location
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
)
1756 # Otherwise just add the leaves like splines
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
)
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
1788 if leafDupliObj
not in "NONE":
1789 bpy
.data
.objects
[leafDupliObj
].parent
= leafObj
1792 elif leafShape
== 'dVert':
1793 leafObj
.instance_type
= "VERTS"
1794 leafObj
.use_instance_vertices_rotation
= True
1796 if leafDupliObj
not in "NONE":
1797 bpy
.data
.objects
[leafDupliObj
].parent
= leafObj
1802 if leafShape
== 'rect':
1803 leafMesh
.uv_layers
.new(name
='leafUV')
1804 uvlayer
= leafMesh
.uv_layers
.active
.data
1806 u1
= .5 * (1 - leafScaleX
)
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
)
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))
1835 leafVertSize
= {'hex': 6, 'rect': 4, 'dFace': 4, 'dVert': 1}[leafShape
]
1837 armLevels
= min(armLevels
, levels
)
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
1849 # Create the armature and objects
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)
1862 treeMesh
= bpy
.data
.meshes
.new('treemesh')
1863 treeObj
= bpy
.data
.objects
.new('treemesh', treeMesh
)
1864 bpy
.context
.scene
.collection
.objects
.link(treeObj
)
1870 vertexGroups
= OrderedDict()
1873 for i
, curve
in enumerate(cu
.splines
):
1874 points
= curve
.bezier_points
1876 # find branching level
1878 for l
, c
in enumerate(levelCount
):
1882 level
= min(level
, 3)
1884 step
= boneStep
[level
]
1885 vindex
= len(treeVerts
)
1889 # add extra vertex for splits
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))
1898 root_vert
.append(False)
1899 vert_radius
.append((p1
.radius
* .75, p1
.radius
* .75))
1900 treeEdges
.append([vindex
, vindex
+ 1])
1904 parent
= lastVerts
[int(splineToBone
[i
][4:-4])]
1908 treeVerts
.append(p1
.co
)
1909 root_vert
.append(True)
1910 vert_radius
.append((p1
.radius
, p1
.radius
))
1912 # add extra vertex for splits
1915 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, .001)
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])
1922 # dont make vertex group if above armLevels
1923 if (i
>= levelCount
[armLevels
]):
1925 groupName
= splineToBone
[idx
]
1927 while groupName
not in vertexGroups
:
1928 # find parent bone of parent bone
1929 b
= splineToBone
[idx
]
1931 groupName
= splineToBone
[idx
]
1935 for n
, p2
in enumerate(points
[1:]):
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:
1945 vertexGroups
[groupName
].append(vindex
- 1)
1947 vertexGroups
[splineToBone
[i
]].append(vindex
- 1)
1949 for f
in range(1, resU
+ 1):
1951 p
= evalBez(p1
.co
, p1
.handle_right
, p2
.handle_left
, p2
.co
, pos
)
1952 radius
= p1
.radius
+ (p2
.radius
- p1
.radius
) * pos
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
]
1961 edge
= [n
* resU
+ f
+ vindex
- 1, n
* resU
+ f
+ vindex
]
1963 vertexGroups
[groupName
].append(n
* resU
+ f
+ vindex
- 1)
1964 treeEdges
.append(edge
)
1966 vertexGroups
[groupName
].append(n
* resU
+ resU
+ vindex
)
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')
1980 armMod
= treeObj
.modifiers
.new('windSway', 'ARMATURE')
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
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
)