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 arm
.use_deform_delay
= True
741 # Add the armature modifier to the curve
742 armMod
= treeOb
.modifiers
.new('windSway', 'ARMATURE')
744 armMod
.show_viewport
= False
745 arm
.display_type
= 'WIRE'
746 treeOb
.hide_viewport
= True
747 armMod
.use_apply_on_spline
= True
748 armMod
.object = armOb
749 armMod
.use_bone_envelopes
= True
750 armMod
.use_vertex_groups
= False # curves don't have vertex groups (yet)
751 # If there are leaves then they need a modifier
753 armMod
= leafObj
.modifiers
.new('windSway', 'ARMATURE')
754 armMod
.object = armOb
755 armMod
.use_bone_envelopes
= False
756 armMod
.use_vertex_groups
= True
758 # Make sure all objects are deselected (may not be required?)
759 for ob
in bpy
.data
.objects
:
760 ob
.select_set(state
=False)
762 fps
= bpy
.context
.scene
.render
.fps
763 animSpeed
= (24 / fps
) * frameRate
765 # Set the armature as active and go to edit mode to add bones
766 bpy
.context
.view_layer
.objects
.active
= armOb
767 bpy
.ops
.object.mode_set(mode
='EDIT')
768 # For all the splines in the curve we need to add bones at each bezier point
769 for i
, parBone
in enumerate(splineToBone
):
770 if (i
< levelCount
[armLevels
]) or (armLevels
== -1) or (not makeMesh
):
773 # Get some data about the spline like length and number of points
774 numPoints
= len(s
.bezier_points
) - 1
776 # find branching level
778 for l
, c
in enumerate(levelCount
):
782 level
= min(level
, 3)
784 step
= boneStep
[level
]
786 # Calculate things for animation
788 splineL
= numPoints
* ((s
.bezier_points
[0].co
- s
.bezier_points
[1].co
).length
)
789 # Set the random phase difference of the animation
790 bxOffset
= uniform(0, tau
)
791 byOffset
= uniform(0, tau
)
792 # Set the phase multiplier for the spline
793 # bMult_r = (s.bezier_points[0].radius / max(splineL, 1e-6)) * (1 / 15) * (1 / frameRate)
794 # This shouldn't have to be in degrees but it looks much better in animation
795 # bMult = degrees(bMult_r)
796 bMult
= (1 / max(splineL
** .5, 1e-6)) * (1 / 4)
797 # print((1 / bMult) * tau) #print wavelength in frames
799 windFreq1
= bMult
* animSpeed
800 windFreq2
= 0.7 * bMult
* animSpeed
802 bMult_l
= 1 / (loopFrames
/ tau
)
803 fRatio
= max(1, round(windFreq1
/ bMult_l
))
804 fgRatio
= max(1, round(windFreq2
/ bMult_l
))
805 windFreq1
= fRatio
* bMult_l
806 windFreq2
= fgRatio
* bMult_l
808 # For all the points in the curve (less the last) add a bone and name it by the spline it will affect
810 for n
in range(0, numPoints
, step
):
812 boneName
= 'bone' + (str(i
)).rjust(3, '0') + '.' + (str(n
)).rjust(3, '0')
813 b
= arm
.edit_bones
.new(boneName
)
814 b
.head
= s
.bezier_points
[n
].co
816 nx
= min(nx
, numPoints
)
817 b
.tail
= s
.bezier_points
[nx
].co
819 b
.head_radius
= s
.bezier_points
[n
].radius
820 b
.tail_radius
= s
.bezier_points
[n
+ 1].radius
821 b
.envelope_distance
= 0.001
823 # If there are leaves then we need a new vertex group so they will attach to the bone
825 if (len(levelCount) > 1) and (i >= levelCount[-2]) and leafObj:
826 leafObj.vertex_groups.new(name=boneName)
827 elif (len(levelCount) == 1) and leafObj:
828 leafObj.vertex_groups.new(name=boneName)
830 # If this is first point of the spline then it must be parented to the level above it
833 b
.parent
= arm
.edit_bones
[parBone
]
834 # Otherwise, we need to attach it to the previous bone in the spline
838 # If there isn't a previous bone then it shouldn't be attached
840 b
.use_connect
= False
842 # Add the animation to the armature if required
844 # Define all the required parameters of the wind sway by the dimension of the spline
845 # a0 = 4 * splineL * (1 - n / (numPoints + 1)) / max(s.bezier_points[n].radius, 1e-6)
846 a0
= 2 * (splineL
/ numPoints
) * (1 - n
/ (numPoints
+ 1)) / max(s
.bezier_points
[n
].radius
, 1e-6)
847 a0
= a0
* min(step
, numPoints
)
848 # a0 = (splineL / numPoints) / max(s.bezier_points[n].radius, 1e-6)
849 a1
= (wind
/ 50) * a0
850 a2
= a1
* .65 # (windGust / 50) * a0 + a1 / 2
852 p
= s
.bezier_points
[nx
].co
- s
.bezier_points
[n
].co
854 ag
= (wind
* gust
/ 50) * a0
865 swayFreq
= gustF
* (tau
/ fps
) * frameRate
# animSpeed # .075 # 0.02
867 swayFreq
= 1 / (loopFrames
/ tau
)
869 # Prevent tree base from rotating
870 if (boneName
== "bone000.000") or (boneName
== "bone000.001"):
876 # Add new fcurves for each sway as well as the modifiers
877 swayX
= armOb
.animation_data
.action
.fcurves
.new(
878 'pose.bones["' + boneName
+ '"].rotation_euler', index
=0
880 swayY
= armOb
.animation_data
.action
.fcurves
.new(
881 'pose.bones["' + boneName
+ '"].rotation_euler', index
=2
883 swayXMod1
= swayX
.modifiers
.new(type='FNGENERATOR')
884 swayXMod2
= swayX
.modifiers
.new(type='FNGENERATOR')
886 swayYMod1
= swayY
.modifiers
.new(type='FNGENERATOR')
887 swayYMod2
= swayY
.modifiers
.new(type='FNGENERATOR')
889 # Set the parameters for each modifier
890 swayXMod1
.amplitude
= a1
891 swayXMod1
.phase_offset
= bxOffset
892 swayXMod1
.phase_multiplier
= windFreq1
894 swayXMod2
.amplitude
= a2
895 swayXMod2
.phase_offset
= 0.7 * bxOffset
896 swayXMod2
.phase_multiplier
= windFreq2
897 swayXMod2
.use_additive
= True
899 swayYMod1
.amplitude
= a1
900 swayYMod1
.phase_offset
= byOffset
901 swayYMod1
.phase_multiplier
= windFreq1
903 swayYMod2
.amplitude
= a2
904 swayYMod2
.phase_offset
= 0.7 * byOffset
905 swayYMod2
.phase_multiplier
= windFreq2
906 swayYMod2
.use_additive
= True
909 swayYMod3
= swayY
.modifiers
.new(type='FNGENERATOR')
910 swayYMod3
.amplitude
= a3
911 swayYMod3
.phase_multiplier
= swayFreq
912 swayYMod3
.value_offset
= .6 * a3
913 swayYMod3
.use_additive
= True
915 swayXMod3
= swayX
.modifiers
.new(type='FNGENERATOR')
916 swayXMod3
.amplitude
= a4
917 swayXMod3
.phase_multiplier
= swayFreq
918 swayXMod3
.value_offset
= .6 * a4
919 swayXMod3
.use_additive
= True
922 bonelist
= [b
.name
for b
in arm
.edit_bones
]
923 vertexGroups
= OrderedDict()
924 for i
, cp
in enumerate(leafP
):
925 # find leafs parent bone
926 leafParent
= roundBone(cp
.parBone
, boneStep
[armLevels
])
927 idx
= int(leafParent
[4:-4])
928 while leafParent
not in bonelist
:
929 # find parent bone of parent bone
930 leafParent
= splineToBone
[idx
]
931 idx
= int(leafParent
[4:-4])
934 bname
= "leaf" + str(i
)
935 b
= arm
.edit_bones
.new(bname
)
937 b
.tail
= cp
.co
+ Vector((0, 0, .02))
938 b
.envelope_distance
= 0.0
939 b
.parent
= arm
.edit_bones
[leafParent
]
941 vertexGroups
[bname
] = [
943 leafMesh
.vertices
[leafVertSize
* i
:(leafVertSize
* i
+ leafVertSize
)]
947 # Define all the required parameters of the wind sway by the dimension of the spline
951 bMult
= (1 / animSpeed
) * 6
952 bMult
*= 1 / max(af2
, .001)
955 bxOffset
= uniform(-ofstRand
, ofstRand
)
956 byOffset
= uniform(-ofstRand
, ofstRand
)
958 # Add new fcurves for each sway as well as the modifiers
959 swayX
= armOb
.animation_data
.action
.fcurves
.new(
960 'pose.bones["' + bname
+ '"].rotation_euler', index
=0
962 swayY
= armOb
.animation_data
.action
.fcurves
.new(
963 'pose.bones["' + bname
+ '"].rotation_euler', index
=2
965 # Add keyframe so noise works
966 swayX
.keyframe_points
.add(1)
967 swayY
.keyframe_points
.add(1)
968 swayX
.keyframe_points
[0].co
= (0, 0)
969 swayY
.keyframe_points
[0].co
= (0, 0)
971 # Add noise modifiers
972 swayXMod
= swayX
.modifiers
.new(type='NOISE')
973 swayYMod
= swayY
.modifiers
.new(type='NOISE')
976 swayXMod
.use_restricted_range
= True
977 swayXMod
.frame_end
= loopFrames
978 swayXMod
.blend_in
= 4
979 swayXMod
.blend_out
= 4
980 swayYMod
.use_restricted_range
= True
981 swayYMod
.frame_end
= loopFrames
982 swayYMod
.blend_in
= 4
983 swayYMod
.blend_out
= 4
985 swayXMod
.scale
= bMult
986 swayXMod
.strength
= a1
987 swayXMod
.offset
= bxOffset
989 swayYMod
.scale
= bMult
990 swayYMod
.strength
= a1
991 swayYMod
.offset
= byOffset
994 if leafParent
not in vertexGroups
:
995 vertexGroups
[leafParent
] = []
996 vertexGroups
[leafParent
].extend(
998 leafMesh
.vertices
[leafVertSize
* i
:(leafVertSize
* i
+ leafVertSize
)]]
1001 for group
in vertexGroups
:
1002 leafObj
.vertex_groups
.new(name
=group
)
1003 leafObj
.vertex_groups
[group
].add(vertexGroups
[group
], 1.0, 'ADD')
1005 # Now we need the rotation mode to be 'XYZ' to ensure correct rotation
1006 bpy
.ops
.object.mode_set(mode
='OBJECT')
1007 for p
in armOb
.pose
.bones
:
1008 p
.rotation_mode
= 'XYZ'
1009 treeOb
.parent
= armOb
1012 def kickstart_trunk(addstem
, levels
, leaves
, branches
, cu
, curve
, curveRes
,
1013 curveV
, attractUp
, length
, lengthV
, ratio
, ratioPower
,
1014 resU
, scale0
, scaleV0
, scaleVal
, taper
, minRadius
, rootFlare
):
1015 newSpline
= cu
.splines
.new('BEZIER')
1016 cu
.resolution_u
= resU
1017 newPoint
= newSpline
.bezier_points
[-1]
1018 newPoint
.co
= Vector((0, 0, 0))
1019 newPoint
.handle_right
= Vector((0, 0, 1))
1020 newPoint
.handle_left
= Vector((0, 0, -1))
1021 # (newPoint.handle_right_type, newPoint.handle_left_type) = ('VECTOR', 'VECTOR')
1022 branchL
= scaleVal
* length
[0]
1023 curveVal
= curve
[0] / curveRes
[0]
1024 # curveVal = curveVal * (branchL / scaleVal)
1028 childStems
= branches
[1]
1029 startRad
= scaleVal
* ratio
* scale0
* uniform(1 - scaleV0
, 1 + scaleV0
) # * (scale0 + uniform(-scaleV0, scaleV0))
1030 endRad
= (startRad
* (1 - taper
[0])) ** ratioPower
1031 startRad
= max(startRad
, minRadius
)
1032 endRad
= max(endRad
, minRadius
)
1033 newPoint
.radius
= startRad
* rootFlare
1036 newSpline
, curveVal
, curveV
[0] / curveRes
[0], attractUp
[0],
1037 0, curveRes
[0], branchL
/ curveRes
[0],
1038 childStems
, startRad
, endRad
, 0, 0, None
1043 def fabricate_stems(addsplinetobone
, addstem
, baseSize
, branches
, childP
, cu
, curve
, curveBack
,
1044 curveRes
, curveV
, attractUp
, downAngle
, downAngleV
, leafDist
, leaves
, length
,
1045 lengthV
, levels
, n
, ratioPower
, resU
, rotate
, rotateV
, scaleVal
, shape
, storeN
,
1046 taper
, shapeS
, minRadius
, radiusTweak
, customShape
, rMode
, segSplits
,
1047 useOldDownAngle
, useParentAngle
, boneStep
):
1049 # prevent baseSize from going to 1.0
1050 baseSize
= min(0.999, baseSize
)
1052 # Store the old rotation to allow new stems to be rotated away from the previous one.
1055 # use fancy child point selection / rotation
1056 if (n
== 1) and (rMode
!= "original"):
1057 childP_T
= OrderedDict()
1063 if p
.offset
not in childP_T
:
1064 childP_T
[p
.offset
] = [p
]
1066 childP_T
[p
.offset
].append(p
)
1068 childP_T
= [childP_T
[k
] for k
in sorted(childP_T
.keys())]
1073 if rMode
== "rotate":
1075 oldRotate
= -copysign(rotate
[n
], oldRotate
)
1077 oldRotate
+= rotate
[n
]
1078 bRotate
= oldRotate
+ uniform(-rotateV
[n
], rotateV
[n
])
1080 # choose start point whose angle is closest to the rotate angle
1084 a2
= atan2(a
.co
[0], -a
.co
[1])
1085 d
= min((a1
- a2
+ tau
) % tau
, (a2
- a1
+ tau
) % tau
)
1088 idx
= a_diff
.index(min(a_diff
))
1090 # find actual rotate angle from branch location
1095 v
= Vector((vx
, vy
))
1097 bD
= ((b
[0] * b
[0] + b
[1] * b
[1]) ** .5)
1098 bL
= br
.lengthPar
* length
[1] * shapeRatio(shape
, (1 - br
.offset
) / (1 - baseSize
), custom
=customShape
)
1100 # account for down angle
1101 if downAngleV
[1] > 0:
1102 downA
= downAngle
[n
] + (-downAngleV
[n
] * (1 - (1 - br
.offset
) / (1 - baseSize
)) ** 2)
1104 downA
= downAngle
[n
]
1105 if downA
< (.5 * pi
):
1106 downA
= sin(downA
) ** 2
1112 bv
= Vector((b
[0], -b
[1]))
1114 a
= atan2(cv
[0], cv
[1])
1117 # add fill points at top #experimental
1118 fillHeight = 1 - degrees(rotateV[3]) # 0.8
1120 w = (p[0].offset - fillHeight) / (1- fillHeight)
1121 prob_b = random() < w
1125 if (p[0].offset > fillHeight): # prob_b and (len(p) > 1): ##(p[0].offset > fillHeight) and
1126 childP.append(p[randint(0, len(p)-1)])
1127 rot_a.append(bRotate)# + pi)
1129 childP
.append(p
[idx
])
1133 idx
= randint(0, len(p
) - 1)
1134 childP
.append(p
[idx
])
1135 # childP.append(p[idx])
1137 childP
.extend(childP_L
)
1138 rot_a
.extend([0] * len(childP_L
))
1142 for i
, p
in enumerate(childP
):
1143 # Add a spline and set the coordinate of the first point.
1144 newSpline
= cu
.splines
.new('BEZIER')
1145 cu
.resolution_u
= resU
1146 newPoint
= newSpline
.bezier_points
[-1]
1148 tempPos
= zAxis
.copy()
1149 # If the -ve flag for downAngle is used we need a special formula to find it
1151 if downAngleV
[n
] < 0.0:
1152 downV
= downAngleV
[n
] * (1 - 2 * (.2 + .8 * ((1 - p
.offset
) / (1 - baseSize
))))
1153 # Otherwise just find a random value
1155 downV
= uniform(-downAngleV
[n
], downAngleV
[n
])
1157 if downAngleV
[n
] < 0.0:
1158 downV
= uniform(-downAngleV
[n
], downAngleV
[n
])
1160 downV
= -downAngleV
[n
] * (1 - (1 - p
.offset
) / (1 - baseSize
)) ** 2 # (110, 80) = (60, -50)
1163 downRotMat
= Matrix
.Rotation(0, 3, 'X')
1165 downRotMat
= Matrix
.Rotation(downAngle
[n
] + downV
, 3, 'X')
1167 # If the -ve flag for rotate is used we need to find which side of the stem
1168 # the last child point was and then grow in the opposite direction
1170 oldRotate
= -copysign(rotate
[n
], oldRotate
)
1171 # Otherwise just generate a random number in the specified range
1173 oldRotate
+= rotate
[n
]
1174 bRotate
= oldRotate
+ uniform(-rotateV
[n
], rotateV
[n
])
1176 if (n
== 1) and (rMode
== "rotate"):
1179 rotMat
= Matrix
.Rotation(bRotate
, 3, 'Z')
1181 # Rotate the direction of growth and set the new point coordinates
1182 tempPos
.rotate(downRotMat
)
1183 tempPos
.rotate(rotMat
)
1186 if (rMode
== "rotate") and (n
== 1) and (p
.offset
!= 1):
1188 edir
= p
.quat
.to_euler('XYZ', Euler((0, 0, bRotate
), 'XYZ'))
1193 tempPos
.rotate(edir
)
1195 dec
= declination(p
.quat
)
1196 tempPos
.rotate(Matrix
.Rotation(radians(dec
), 3, 'X'))
1199 tempPos
.rotate(edir
)
1201 tempPos
.rotate(p
.quat
)
1203 newPoint
.handle_right
= p
.co
+ tempPos
1205 # Make length variation inversely proportional to segSplits
1206 # lenV = (1 - min(segSplits[n], 1)) * lengthV[n]
1208 # Find branch length and the number of child stems.
1210 for l
in length
[:n
+ 1]:
1212 lMax
= length
[n
] # * uniform(1 - lenV, 1 + lenV)
1214 lShape
= shapeRatio(shape
, (1 - p
.stemOffset
) / (1 - baseSize
), custom
=customShape
)
1216 lShape
= shapeRatio(shapeS
, (1 - p
.stemOffset
) / (1 - baseSize
))
1217 branchL
= p
.lengthPar
* lMax
* lShape
1218 childStems
= branches
[min(3, n
+ 1)] * (0.1 + 0.9 * (branchL
/ maxbL
))
1220 # If this is the last level before leaves then we need to generate the child points differently
1221 if (storeN
== levels
- 1):
1225 childStems
= leaves
* (0.1 + 0.9 * (branchL
/ maxbL
)) * shapeRatio(leafDist
, (1 - p
.offset
))
1227 # print("n=%d, levels=%d, n'=%d, childStems=%s"%(n, levels, storeN, childStems))
1229 # Determine the starting and ending radii of the stem using the tapering of the stem
1230 startRad
= min((p
.radiusPar
[0] * ((branchL
/ p
.lengthPar
) ** ratioPower
)) * radiusTweak
[n
], p
.radiusPar
[1])
1232 startRad
= p
.radiusPar
[1]
1233 endRad
= (startRad
* (1 - taper
[n
])) ** ratioPower
1234 startRad
= max(startRad
, minRadius
)
1235 endRad
= max(endRad
, minRadius
)
1236 newPoint
.radius
= startRad
1239 curveVal
= curve
[n
] / curveRes
[n
]
1240 curveVar
= curveV
[n
] / curveRes
[n
]
1242 # curveVal = curveVal * (branchL / scaleVal)
1244 # Add the new stem to list of stems to grow and define which bone it will be parented to
1247 newSpline
, curveVal
, curveVar
, attractUp
[n
],
1248 0, curveRes
[n
], branchL
/ curveRes
[n
], childStems
,
1249 startRad
, endRad
, len(cu
.splines
) - 1, 0, p
.quat
1253 bone
= roundBone(p
.parBone
, boneStep
[n
- 1])
1258 addsplinetobone((bone
, isend
))
1261 def perform_pruning(baseSize
, baseSplits
, childP
, cu
, currentMax
, currentMin
, currentScale
, curve
,
1262 curveBack
, curveRes
, deleteSpline
, forceSprout
, handles
, n
, oldMax
, originalSplineToBone
,
1263 originalCo
, originalCurv
, originalCurvV
, originalHandleL
, originalHandleR
, originalLength
,
1264 originalSeg
, prune
, prunePowerHigh
, prunePowerLow
, pruneRatio
, pruneWidth
, pruneBase
,
1265 pruneWidthPeak
, randState
, ratio
, scaleVal
, segSplits
, splineToBone
, splitAngle
, splitAngleV
,
1266 st
, startPrune
, branchDist
, length
, splitByLen
, closeTip
, nrings
, splitBias
, splitHeight
,
1267 attractOut
, rMode
, lengthV
, taperCrown
, boneStep
, rotate
, rotateV
):
1268 while startPrune
and ((currentMax
- currentMin
) > 0.005):
1271 # If the search will halt after this iteration, then set the adjustment of stem
1272 # length to take into account the pruning ratio
1273 if (currentMax
- currentMin
) < 0.01:
1274 currentScale
= (currentScale
- 1) * pruneRatio
+ 1
1277 # Change the segment length of the stem by applying some scaling
1278 st
.segL
= originalLength
* currentScale
1279 # To prevent millions of splines being created we delete any old ones and
1280 # replace them with only their first points to begin the spline again
1282 for x
in splineList
:
1283 cu
.splines
.remove(x
.spline
)
1284 newSpline
= cu
.splines
.new('BEZIER')
1285 newPoint
= newSpline
.bezier_points
[-1]
1286 newPoint
.co
= originalCo
1287 newPoint
.handle_right
= originalHandleR
1288 newPoint
.handle_left
= originalHandleL
1289 (newPoint
.handle_left_type
, newPoint
.handle_right_type
) = ('VECTOR', 'VECTOR')
1290 st
.spline
= newSpline
1291 st
.curv
= originalCurv
1292 st
.curvV
= originalCurvV
1293 st
.seg
= originalSeg
1295 newPoint
.radius
= st
.radS
1296 splineToBone
= originalSplineToBone
1298 # Initialise the spline list for those contained in the current level of branching
1301 # split length variation
1302 stemsegL
= splineList
[0].segL
# initial segment length used for variation
1303 splineList
[0].segL
= stemsegL
* uniform(1 - lengthV
[n
], 1 + lengthV
[n
]) # variation for first stem
1305 # For each of the segments of the stem which must be grown we have to add to each spline in splineList
1306 for k
in range(curveRes
[n
]):
1307 # Make a copy of the current list to avoid continually adding to the list we're iterating over
1308 tempList
= splineList
[:]
1309 # print('Leng: ', len(tempList))
1311 # for curve variation
1313 kp
= (k
/ (curveRes
[n
] - 1)) # * 2
1318 splitValue
= segSplits
[n
]
1320 splitValue
= ((2 * splitBias
) * (kp
- .5) + 1) * splitValue
1321 splitValue
= max(splitValue
, 0.0)
1323 # For each of the splines in this list set the number of splits and then grow it
1324 for spl
in tempList
:
1326 lastsplit
= getattr(spl
, 'splitlast', 0)
1327 splitVal
= splitValue
1329 splitVal
= splitValue
* 1.33
1330 elif lastsplit
== 1:
1331 splitVal
= splitValue
* splitValue
1335 elif (n
== 0) and (k
< ((curveRes
[n
] - 1) * splitHeight
)) and (k
!= 1):
1337 elif (k
== 1) and (n
== 0):
1338 numSplit
= baseSplits
1339 # always split at splitHeight
1340 elif (n
== 0) and (k
== int((curveRes
[n
] - 1) * splitHeight
) + 1) and (splitVal
> 0):
1343 if (n
>= 1) and splitByLen
:
1344 L
= ((spl
.segL
* curveRes
[n
]) / scaleVal
)
1346 for l
in length
[:n
+ 1]:
1349 numSplit
= splits2(splitVal
* L
)
1351 numSplit
= splits2(splitVal
)
1353 if (k
== int(curveRes
[n
] / 2 + 0.5)) and (curveBack
[n
] != 0):
1354 spl
.curv
+= 2 * (curveBack
[n
] / curveRes
[n
]) # was -4 *
1357 n
, spl
, numSplit
, splitAngle
[n
], splitAngleV
[n
], splineList
,
1358 handles
, splineToBone
, closeTip
, kp
, splitHeight
, attractOut
[n
],
1359 stemsegL
, lengthV
[n
], taperCrown
, boneStep
, rotate
, rotateV
1362 # If pruning is enabled then we must to the check to see if the end of the spline is within the evelope
1364 # Check each endpoint to see if it is inside
1365 for s
in splineList
:
1366 coordMag
= (s
.spline
.bezier_points
[-1].co
.xy
).length
1367 ratio
= (scaleVal
- s
.spline
.bezier_points
[-1].co
.z
) / (scaleVal
* max(1 - pruneBase
, 1e-6))
1368 # Don't think this if part is needed
1369 if (n
== 0) and (s
.spline
.bezier_points
[-1].co
.z
< pruneBase
* scaleVal
):
1370 insideBool
= True # Init to avoid UnboundLocalError later
1373 (coordMag
/ scaleVal
) < pruneWidth
* shapeRatio(9, ratio
, pruneWidthPeak
, prunePowerHigh
,
1375 # If the point is not inside then we adjust the scale and current search bounds
1378 currentMax
= currentScale
1379 currentScale
= 0.5 * (currentMax
+ currentMin
)
1381 # If the scale is the original size and the point is inside then
1382 # we need to make sure it won't be pruned or extended to the edge of the envelope
1383 if insideBool
and (currentScale
!= 1):
1384 currentMin
= currentScale
1386 currentScale
= 0.5 * (currentMax
+ currentMin
)
1387 if insideBool
and ((currentMax
- currentMin
) == 1):
1390 # If the search will halt on the next iteration then we need
1391 # to make sure we sprout child points to grow the next splines or leaves
1392 if (((currentMax
- currentMin
) < 0.005) or not prune
) or forceSprout
:
1393 if (n
== 0) and (rMode
!= "original"):
1394 tVals
= findChildPoints2(splineList
, st
.children
)
1396 tVals
= findChildPoints(splineList
, st
.children
)
1397 # print("debug tvals[%d] , splineList[%d], %s" % ( len(tVals), len(splineList), st.children))
1398 # If leaves is -ve then we need to make sure the only point which sprouts is the end of the spline
1401 # remove some of the points because of baseSize
1402 trimNum
= int(baseSize
* (len(tVals
) + 1))
1403 tVals
= tVals
[trimNum
:]
1405 # grow branches in rings
1406 if (n
== 0) and (nrings
> 0):
1407 # tVals = [(floor(t * nrings)) / nrings for t in tVals[:-1]]
1408 tVals
= [(floor(t
* nrings
) / nrings
) * uniform(.995, 1.005) for t
in tVals
[:-1]]
1410 tVals
= [t
for t
in tVals
if t
> baseSize
]
1412 # branch distribution
1414 tVals
= [((t
- baseSize
) / (1 - baseSize
)) for t
in tVals
]
1415 if branchDist
< 1.0:
1416 tVals
= [t
** (1 / branchDist
) for t
in tVals
]
1418 tVals
= [1 - (1 - t
) ** branchDist
for t
in tVals
]
1419 tVals
= [t
* (1 - baseSize
) + baseSize
for t
in tVals
]
1421 # For all the splines, we interpolate them and add the new points to the list of child points
1422 maxOffset
= max([s
.offsetLen
+ (len(s
.spline
.bezier_points
) - 1) * s
.segL
for s
in splineList
])
1423 for s
in splineList
:
1424 # print(str(n)+'level: ', s.segMax*s.segL)
1425 childP
.extend(interpStem(s
, tVals
, s
.segMax
* s
.segL
, s
.radS
, maxOffset
, baseSize
))
1427 # Force the splines to be deleted
1429 # If pruning isn't enabled then make sure it doesn't loop
1432 return ratio
, splineToBone
1435 # calculate taper automatically
1436 def findtaper(length
, taper
, shape
, shapeS
, levels
, customShape
):
1438 for i
, t
in enumerate(length
):
1442 shp
= shapeRatio(shape
, 0, custom
=customShape
)
1444 shp
= shapeRatio(shapeS
, 0)
1449 for i
, t
in enumerate(taperS
):
1451 for x
in range(i
+ 1):
1456 for i
, t
in enumerate(taperP
):
1457 t
= sum(taperP
[i
:levels
])
1461 for i
, t
in enumerate(taperR
):
1463 t
= taperP
[i
] / taperR
[i
]
1464 except ZeroDivisionError:
1468 taperT
= [t
* taper
[i
] for i
, t
in enumerate(taperT
)]
1475 # startTime = time.time()
1476 # Set the seed for repeatable results
1479 # Set all other variables
1480 levels
= props
.levels
1481 length
= props
.length
1482 lengthV
= props
.lengthV
1483 taperCrown
= props
.taperCrown
1484 branches
= props
.branches
1485 curveRes
= props
.curveRes
1486 curve
= toRad(props
.curve
)
1487 curveV
= toRad(props
.curveV
)
1488 curveBack
= toRad(props
.curveBack
)
1489 baseSplits
= props
.baseSplits
1490 segSplits
= props
.segSplits
1491 splitByLen
= props
.splitByLen
1493 splitAngle
= toRad(props
.splitAngle
)
1494 splitAngleV
= toRad(props
.splitAngleV
)
1496 scaleV
= props
.scaleV
1497 attractUp
= props
.attractUp
1498 attractOut
= props
.attractOut
1499 shape
= int(props
.shape
)
1500 shapeS
= int(props
.shapeS
)
1501 customShape
= props
.customShape
1502 branchDist
= props
.branchDist
1503 nrings
= props
.nrings
1504 baseSize
= props
.baseSize
1505 baseSize_s
= props
.baseSize_s
1506 splitHeight
= props
.splitHeight
1507 splitBias
= props
.splitBias
1509 minRadius
= props
.minRadius
1510 closeTip
= props
.closeTip
1511 rootFlare
= props
.rootFlare
1512 autoTaper
= props
.autoTaper
1514 radiusTweak
= props
.radiusTweak
1515 ratioPower
= props
.ratioPower
1516 downAngle
= toRad(props
.downAngle
)
1517 downAngleV
= toRad(props
.downAngleV
)
1518 rotate
= toRad(props
.rotate
)
1519 rotateV
= toRad(props
.rotateV
)
1520 scale0
= props
.scale0
1521 scaleV0
= props
.scaleV0
1523 pruneWidth
= props
.pruneWidth
1524 pruneBase
= props
.pruneBase
1525 pruneWidthPeak
= props
.pruneWidthPeak
1526 prunePowerLow
= props
.prunePowerLow
1527 prunePowerHigh
= props
.prunePowerHigh
1528 pruneRatio
= props
.pruneRatio
1529 leafDownAngle
= radians(props
.leafDownAngle
)
1530 leafDownAngleV
= radians(props
.leafDownAngleV
)
1531 leafRotate
= radians(props
.leafRotate
)
1532 leafRotateV
= radians(props
.leafRotateV
)
1533 leafScale
= props
.leafScale
1534 leafScaleX
= props
.leafScaleX
1535 leafScaleT
= props
.leafScaleT
1536 leafScaleV
= props
.leafScaleV
1537 leafShape
= props
.leafShape
1538 leafDupliObj
= props
.leafDupliObj
1540 leafangle
= props
.leafangle
1541 horzLeaves
= props
.horzLeaves
1542 leafDist
= int(props
.leafDist
)
1543 bevelRes
= props
.bevelRes
1546 useArm
= props
.useArm
1547 previewArm
= props
.previewArm
1548 armAnim
= props
.armAnim
1549 leafAnim
= props
.leafAnim
1550 frameRate
= props
.frameRate
1551 loopFrames
= props
.loopFrames
1553 # windSpeed = props.windSpeed
1554 # windGust = props.windGust
1564 makeMesh
= props
.makeMesh
1565 armLevels
= props
.armLevels
1566 boneStep
= props
.boneStep
1568 useOldDownAngle
= props
.useOldDownAngle
1569 useParentAngle
= props
.useParentAngle
1572 boneStep
= [1, 1, 1, 1]
1576 taper
= findtaper(length
, taper
, shape
, shapeS
, levels
, customShape
)
1577 # pLevels = branches[0]
1578 # taper = findtaper(length, taper, shape, shapeS, pLevels, customShape)
1582 # Some effects can be turned ON and OFF, the necessary variables are changed here
1588 if not props
.showLeaves
:
1591 leaves
= props
.leaves
1593 if props
.handleType
== '0':
1598 for ob
in bpy
.data
.objects
:
1599 ob
.select_set(state
=False)
1601 # Initialise the tree object and curve and adjust the settings
1602 cu
= bpy
.data
.curves
.new('tree', 'CURVE')
1603 treeOb
= bpy
.data
.objects
.new('tree', cu
)
1604 bpy
.context
.scene
.collection
.objects
.link(treeOb
)
1606 # treeOb.location=bpy.context.scene.cursor.location attractUp
1608 cu
.dimensions
= '3D'
1609 cu
.fill_mode
= 'FULL'
1610 cu
.bevel_depth
= bevelDepth
1611 cu
.bevel_resolution
= bevelRes
1612 cu
.use_uv_as_generated
= True
1614 # Fix the scale of the tree now
1615 scaleVal
= scale
+ uniform(-scaleV
, scaleV
)
1616 scaleVal
+= copysign(1e-6, scaleVal
) # Move away from zero to avoid div by zero
1618 pruneBase
= min(pruneBase
, baseSize
)
1619 # If pruning is turned on we need to draw the pruning envelope
1623 enCu
= bpy
.data
.curves
.new('envelope', 'CURVE')
1624 enOb
= bpy
.data
.objects
.new('envelope', enCu
)
1625 enOb
.parent
= treeOb
1626 bpy
.context
.scene
.collection
.objects
.link(enOb
)
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 # Set the coordinates by varying the z value, envelope will be aligned to the x-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 scaleVal
* pruneWidth
*
1640 shapeRatio(9, ratioVal
, pruneWidthPeak
, prunePowerHigh
, prunePowerLow
),
1644 (newPoint
.handle_right_type
, newPoint
.handle_left_type
) = (enHandle
, enHandle
)
1645 newSpline
= enCu
.splines
.new('BEZIER')
1646 newPoint
= newSpline
.bezier_points
[-1]
1647 newPoint
.co
= Vector((0, 0, scaleVal
))
1648 (newPoint
.handle_right_type
, newPoint
.handle_left_type
) = (enHandle
, enHandle
)
1649 # Create a second envelope but this time on the y-axis
1650 for c
in range(enNum
):
1651 newSpline
.bezier_points
.add(1)
1652 newPoint
= newSpline
.bezier_points
[-1]
1653 ratioVal
= (c
+ 1) / (enNum
)
1654 zVal
= scaleVal
- scaleVal
* (1 - pruneBase
) * ratioVal
1655 newPoint
.co
= Vector(
1657 0, scaleVal
* pruneWidth
*
1658 shapeRatio(9, ratioVal
, pruneWidthPeak
, prunePowerHigh
, prunePowerLow
),
1662 (newPoint
.handle_right_type
, newPoint
.handle_left_type
) = (enHandle
, enHandle
)
1668 splineToBone
= deque([''])
1669 addsplinetobone
= splineToBone
.append
1671 # Each of the levels needed by the user we grow all the splines
1672 for n
in range(levels
):
1675 addstem
= stemList
.append
1676 # If n is used as an index to access parameters for the tree
1677 # it must be at most 3 or it will reference outside the array index
1681 # closeTip only on last level
1682 closeTipp
= all([(n
== levels
- 1), closeTip
])
1684 # If this is the first level of growth (the trunk) then we need some special work to begin the tree
1686 kickstart_trunk(addstem
, levels
, leaves
, branches
, cu
, curve
, curveRes
,
1687 curveV
, attractUp
, length
, lengthV
, ratio
, ratioPower
, resU
,
1688 scale0
, scaleV0
, scaleVal
, taper
, minRadius
, rootFlare
)
1689 # If this isn't the trunk then we may have multiple stem to initialise
1691 # For each of the points defined in the list of stem starting points we need to grow a stem.
1692 fabricate_stems(addsplinetobone
, addstem
, baseSize
, branches
, childP
, cu
, curve
, curveBack
,
1693 curveRes
, curveV
, attractUp
, downAngle
, downAngleV
, leafDist
, leaves
, length
, lengthV
,
1694 levels
, n
, ratioPower
, resU
, rotate
, rotateV
, scaleVal
, shape
, storeN
,
1695 taper
, shapeS
, minRadius
, radiusTweak
, customShape
, rMode
, segSplits
,
1696 useOldDownAngle
, useParentAngle
, boneStep
)
1698 # change base size for each level
1700 baseSize
*= baseSize_s
# decrease at each level
1701 if (n
== levels
- 1):
1705 # Now grow each of the stems in the list of those to be extended
1707 # When using pruning, we need to ensure that the random effects
1708 # will be the same for each iteration to make sure the problem is linear
1709 randState
= getstate()
1712 # Store all the original values for the stem to make sure
1713 # we have access after it has been modified by pruning
1714 originalLength
= st
.segL
1715 originalCurv
= st
.curv
1716 originalCurvV
= st
.curvV
1717 originalSeg
= st
.seg
1718 originalHandleR
= st
.p
.handle_right
.copy()
1719 originalHandleL
= st
.p
.handle_left
.copy()
1720 originalCo
= st
.p
.co
.copy()
1725 deleteSpline
= False
1726 originalSplineToBone
= copy
.copy(splineToBone
)
1728 # Now do the iterative pruning, this uses a binary search and halts once the difference
1729 # between upper and lower bounds of the search are less than 0.005
1730 ratio
, splineToBone
= perform_pruning(
1731 baseSize
, baseSplits
, childP
, cu
, currentMax
, currentMin
,
1732 currentScale
, curve
, curveBack
, curveRes
, deleteSpline
, forceSprout
,
1733 handles
, n
, oldMax
, originalSplineToBone
, originalCo
, originalCurv
,
1734 originalCurvV
, originalHandleL
, originalHandleR
, originalLength
,
1735 originalSeg
, prune
, prunePowerHigh
, prunePowerLow
, pruneRatio
,
1736 pruneWidth
, pruneBase
, pruneWidthPeak
, randState
, ratio
, scaleVal
,
1737 segSplits
, splineToBone
, splitAngle
, splitAngleV
, st
, startPrune
,
1738 branchDist
, length
, splitByLen
, closeTipp
, nrings
, splitBias
,
1739 splitHeight
, attractOut
, rMode
, lengthV
, taperCrown
, boneStep
,
1743 levelCount
.append(len(cu
.splines
))
1745 # If we need to add leaves, we do it here
1750 leafMesh
= None # in case we aren't creating leaves, we'll still have the variable
1756 # For each of the child points we add leaves
1758 # If the special flag is set then we need to add several leaves at the same location
1760 oldRot
= -leafRotate
/ 2
1761 for g
in range(abs(leaves
)):
1762 (vertTemp
, faceTemp
, normal
, oldRot
) = genLeafMesh(
1763 leafScale
, leafScaleX
, leafScaleT
,
1764 leafScaleV
, cp
.co
, cp
.quat
, cp
.offset
,
1765 len(leafVerts
), leafDownAngle
, leafDownAngleV
,
1766 leafRotate
, leafRotateV
,
1767 oldRot
, bend
, leaves
, leafShape
,
1768 leafangle
, horzLeaves
1770 leafVerts
.extend(vertTemp
)
1771 leafFaces
.extend(faceTemp
)
1772 leafNormals
.extend(normal
)
1774 # Otherwise just add the leaves like splines
1776 (vertTemp
, faceTemp
, normal
, oldRot
) = genLeafMesh(
1777 leafScale
, leafScaleX
, leafScaleT
, leafScaleV
,
1778 cp
.co
, cp
.quat
, cp
.offset
, len(leafVerts
),
1779 leafDownAngle
, leafDownAngleV
, leafRotate
,
1780 leafRotateV
, oldRot
, bend
, leaves
, leafShape
,
1781 leafangle
, horzLeaves
1783 leafVerts
.extend(vertTemp
)
1784 leafFaces
.extend(faceTemp
)
1785 leafNormals
.extend(normal
)
1788 # Create the leaf mesh and object, add geometry using from_pydata,
1789 # edges are currently added by validating the mesh which isn't great
1790 leafMesh
= bpy
.data
.meshes
.new('leaves')
1791 leafObj
= bpy
.data
.objects
.new('leaves', leafMesh
)
1792 bpy
.context
.scene
.collection
.objects
.link(leafObj
)
1793 leafObj
.parent
= treeOb
1794 leafMesh
.from_pydata(leafVerts
, (), leafFaces
)
1796 # set vertex normals for dupliVerts
1797 if leafShape
== 'dVert':
1798 leafMesh
.vertices
.foreach_set('normal', leafNormals
)
1800 # enable duplication
1801 if leafShape
== 'dFace':
1802 leafObj
.instance_type
= "FACES"
1803 leafObj
.use_instance_faces_scale
= True
1804 leafObj
.instance_faces_scale
= 10.0
1806 if leafDupliObj
not in "NONE":
1807 bpy
.data
.objects
[leafDupliObj
].parent
= leafObj
1810 elif leafShape
== 'dVert':
1811 leafObj
.instance_type
= "VERTS"
1812 leafObj
.use_instance_vertices_rotation
= True
1814 if leafDupliObj
not in "NONE":
1815 bpy
.data
.objects
[leafDupliObj
].parent
= leafObj
1820 if leafShape
== 'rect':
1821 leafMesh
.uv_layers
.new(name
='leafUV')
1822 uvlayer
= leafMesh
.uv_layers
.active
.data
1824 u1
= .5 * (1 - leafScaleX
)
1827 for i
in range(0, len(leafFaces
)):
1828 uvlayer
[i
* 4 + 0].uv
= Vector((u2
, 0))
1829 uvlayer
[i
* 4 + 1].uv
= Vector((u2
, 1))
1830 uvlayer
[i
* 4 + 2].uv
= Vector((u1
, 1))
1831 uvlayer
[i
* 4 + 3].uv
= Vector((u1
, 0))
1833 elif leafShape
== 'hex':
1834 leafMesh
.uv_layers
.new(name
='leafUV')
1835 uvlayer
= leafMesh
.uv_layers
.active
.data
1837 u1
= .5 * (1 - leafScaleX
)
1840 for i
in range(0, int(len(leafFaces
) / 2)):
1841 uvlayer
[i
* 8 + 0].uv
= Vector((.5, 0))
1842 uvlayer
[i
* 8 + 1].uv
= Vector((u1
, 1 / 3))
1843 uvlayer
[i
* 8 + 2].uv
= Vector((u1
, 2 / 3))
1844 uvlayer
[i
* 8 + 3].uv
= Vector((.5, 1))
1846 uvlayer
[i
* 8 + 4].uv
= Vector((.5, 0))
1847 uvlayer
[i
* 8 + 5].uv
= Vector((.5, 1))
1848 uvlayer
[i
* 8 + 6].uv
= Vector((u2
, 2 / 3))
1849 uvlayer
[i
* 8 + 7].uv
= Vector((u2
, 1 / 3))
1853 leafVertSize
= {'hex': 6, 'rect': 4, 'dFace': 4, 'dVert': 1}[leafShape
]
1855 armLevels
= min(armLevels
, levels
)
1858 # unpack vars from splineToBone
1859 splineToBone1
= splineToBone
1860 splineToBone
= [s
[0] if len(s
) > 1 else s
for s
in splineToBone1
]
1861 isend
= [s
[1] if len(s
) > 1 else False for s
in splineToBone1
]
1862 issplit
= [s
[2] if len(s
) > 2 else False for s
in splineToBone1
]
1863 splitPidx
= [s
[3] if len(s
) > 2 else 0 for s
in splineToBone1
]
1865 # If we need an armature we add it
1867 # Create the armature and objects
1869 armAnim
, leafP
, cu
, frameRate
, leafMesh
, leafObj
, leafVertSize
,
1870 leaves
, levelCount
, splineToBone
, treeOb
, wind
, gust
, gustF
, af1
,
1871 af2
, af3
, leafAnim
, loopFrames
, previewArm
, armLevels
, makeMesh
, boneStep
1874 # print(time.time()-startTime)
1880 treeMesh
= bpy
.data
.meshes
.new('treemesh')
1881 treeObj
= bpy
.data
.objects
.new('treemesh', treeMesh
)
1882 bpy
.context
.scene
.collection
.objects
.link(treeObj
)
1888 vertexGroups
= OrderedDict()
1891 for i
, curve
in enumerate(cu
.splines
):
1892 points
= curve
.bezier_points
1894 # find branching level
1896 for l
, c
in enumerate(levelCount
):
1900 level
= min(level
, 3)
1902 step
= boneStep
[level
]
1903 vindex
= len(treeVerts
)
1907 # add extra vertex for splits
1909 pb
= int(splineToBone
[i
][4:-4])
1910 pn
= splitPidx
[i
] # int(splineToBone[i][-3:])
1911 p_1
= cu
.splines
[pb
].bezier_points
[pn
]
1912 p_2
= cu
.splines
[pb
].bezier_points
[pn
+ 1]
1913 p
= evalBez(p_1
.co
, p_1
.handle_right
, p_2
.handle_left
, p_2
.co
, 1 - 1 / (resU
+ 1))
1916 root_vert
.append(False)
1917 vert_radius
.append((p1
.radius
* .75, p1
.radius
* .75))
1918 treeEdges
.append([vindex
, vindex
+ 1])
1922 parent
= lastVerts
[int(splineToBone
[i
][4:-4])]
1926 treeVerts
.append(p1
.co
)
1927 root_vert
.append(True)
1928 vert_radius
.append((p1
.radius
, p1
.radius
))
1930 # add extra vertex for splits
1933 p = evalBez(p1.co, p1.handle_right, p2.handle_left, p2.co, .001)
1935 root_vert.append(False)
1936 vert_radius.append((p1.radius, p1.radius)) #(p1.radius * .95, p1.radius * .95)
1937 treeEdges.append([vindex,vindex+1])
1940 # dont make vertex group if above armLevels
1941 if (i
>= levelCount
[armLevels
]):
1943 groupName
= splineToBone
[idx
]
1945 while groupName
not in vertexGroups
:
1946 # find parent bone of parent bone
1947 b
= splineToBone
[idx
]
1949 groupName
= splineToBone
[idx
]
1953 for n
, p2
in enumerate(points
[1:]):
1955 groupName
= 'bone' + (str(i
)).rjust(3, '0') + '.' + (str(n
)).rjust(3, '0')
1956 groupName
= roundBone(groupName
, step
)
1957 if groupName
not in vertexGroups
:
1958 vertexGroups
[groupName
] = []
1960 # parent first vert in split to parent branch bone
1961 if issplit
[i
] and n
== 0:
1963 vertexGroups
[groupName
].append(vindex
- 1)
1965 vertexGroups
[splineToBone
[i
]].append(vindex
- 1)
1967 for f
in range(1, resU
+ 1):
1969 p
= evalBez(p1
.co
, p1
.handle_right
, p2
.handle_left
, p2
.co
, pos
)
1970 radius
= p1
.radius
+ (p2
.radius
- p1
.radius
) * pos
1973 root_vert
.append(False)
1974 vert_radius
.append((radius
, radius
))
1976 if (isend
[i
]) and (n
== 0) and (f
== 1):
1977 edge
= [parent
, n
* resU
+ f
+ vindex
]
1979 edge
= [n
* resU
+ f
+ vindex
- 1, n
* resU
+ f
+ vindex
]
1981 vertexGroups
[groupName
].append(n
* resU
+ f
+ vindex
- 1)
1982 treeEdges
.append(edge
)
1984 vertexGroups
[groupName
].append(n
* resU
+ resU
+ vindex
)
1988 lastVerts
.append(len(treeVerts
) - 1)
1990 treeMesh
.from_pydata(treeVerts
, treeEdges
, ())
1992 for group
in vertexGroups
:
1993 treeObj
.vertex_groups
.new(name
=group
)
1994 treeObj
.vertex_groups
[group
].add(vertexGroups
[group
], 1.0, 'ADD')
1998 armMod
= treeObj
.modifiers
.new('windSway', 'ARMATURE')
2000 bpy
.data
.objects
['treeArm'].hide_viewport
= True
2001 bpy
.data
.armatures
['tree'].display_type
= 'STICK'
2002 armMod
.object = bpy
.data
.objects
['treeArm']
2003 armMod
.use_bone_envelopes
= False
2004 armMod
.use_vertex_groups
= True
2005 treeObj
.parent
= bpy
.data
.objects
['treeArm']
2007 # add skin modifier and set data
2008 skinMod
= treeObj
.modifiers
.new('Skin', 'SKIN')
2009 skinMod
.use_smooth_shade
= True
2011 skinMod
.show_viewport
= False
2012 skindata
= treeObj
.data
.skin_vertices
[0].data
2013 for i
, radius
in enumerate(vert_radius
):
2014 skindata
[i
].radius
= radius
2015 skindata
[i
].use_root
= root_vert
[i
]
2017 print("mesh time", time
.time() - t1
)