1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
24 from mathutils
import *
25 from math
import pi
,sin
,degrees
,radians
,atan2
,copysign
,cos
,acos
26 from random
import random
,uniform
,seed
,choice
,getstate
,setstate
27 from bpy
.props
import *
28 from collections
import deque
30 # Initialise the split error and axis vectors
32 zAxis
= Vector((0,0,1))
33 yAxis
= Vector((0,1,0))
34 xAxis
= Vector((1,0,0))
36 # This class will contain a part of the tree which needs to be extended and the required tree parameters
38 def __init__(self
,spline
,curvature
,curvatureV
,segments
,maxSegs
,segLength
,childStems
,stemRadStart
,stemRadEnd
,splineNum
):
40 self
.p
= spline
.bezier_points
[-1]
42 self
.curvV
= curvatureV
46 self
.children
= childStems
47 self
.radS
= stemRadStart
48 self
.radE
= stemRadEnd
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
- self
.spline
.bezier_points
[-1].co
).normalized()).to_track_quat('Z','Y')
55 return ((self
.spline
.bezier_points
[-1].co
- self
.spline
.bezier_points
[-2].co
).normalized()).to_track_quat('Z','Y')
56 # Determine the declination
58 tempVec
= zAxis
.copy()
59 tempVec
.rotate(self
.quat())
60 return zAxis
.angle(tempVec
)
61 # Update the end of the spline and increment the segment count
63 self
.p
= self
.spline
.bezier_points
[-1]
65 # Determine the spread angle for a split
67 return radians(choice([-1,1])*(20 + 0.75*(30 + abs(degrees(self
.dec()) - 90))*random()**2))
68 # Determine the splitting angle for a split
69 def splitAngle(self
,splitAng
,splitAngV
):
70 return max(0,splitAng
+uniform(-splitAngV
,splitAngV
)-self
.dec())
71 # This is used to change the the curvature per segment of the spline
72 def curvAdd(self
,curvD
):
75 # This class contains the data for a point where a new branch will sprout
77 def __init__(self
,coords
,quat
,radiusPar
,offset
,lengthPar
,parBone
):
80 self
.radiusPar
= radiusPar
82 self
.lengthPar
= lengthPar
83 self
.parBone
= parBone
86 # This function calculates the shape ratio as defined in the paper
87 def shapeRatio(shape
,ratio
,pruneWidthPeak
=0.0,prunePowerHigh
=0.0,prunePowerLow
=0.0):
89 return 0.2 + 0.8*ratio
91 return 0.2 + 0.8*sin(pi
*ratio
)
93 return 0.2 + 0.8*sin(0.5*pi
*ratio
)
97 return 0.5 + 0.5*ratio
102 return (1.0 - ratio
)/0.3
104 return 1.0 - 0.8*ratio
107 return 0.5 + 0.5*ratio
/0.7
109 return 0.5 + 0.5*(1.0 - ratio
)/0.3
111 if (ratio
< (1 - pruneWidthPeak
)) and (ratio
> 0.0):
112 return ((ratio
/(1 - pruneWidthPeak
))**prunePowerHigh
)
113 elif (ratio
>= (1 - pruneWidthPeak
)) and (ratio
< 1.0):
114 return (((1 - ratio
)/pruneWidthPeak
)**prunePowerLow
)
118 # This function determines the actual number of splits at a given point using the global error
121 nEff
= round(n
+ splitError
,0)
122 splitError
-= (nEff
- n
)
125 # Determine the declination from a given quaternion
126 def declination(quat
):
127 tempVec
= zAxis
.copy()
130 return degrees(acos(tempVec
.z
))
132 # Determine the length of a child stem
133 def lengthChild(lMax
,offset
,lPar
,shape
=False,lBase
=None):
135 return lPar
*lMax
*shapeRatio(shape
,(lPar
- offset
)/(lPar
- lBase
))
137 return lMax
*(lPar
- 0.6*offset
)
139 # Find the actual downAngle taking into account the special case
140 def downAngle(downAng
,downAngV
,lPar
=None,offset
=None,lBase
=None):
142 return downAng
+ (uniform(-downAngV
,downAngV
)*(1 - 2*shapeRatio(0,(lPar
- offset
)/(lPar
- lBase
))))
144 return downAng
+ uniform(-downAngV
,downAngV
)
146 # Returns the rotation matrix equivalent to i rotations by 2*pi/(n+1)
147 def splitRotMat(n
,i
):
148 return Matrix
.Rotation(2*i
*pi
/(n
+1),3,'Z')
150 # Returns the split angle
151 def angleSplit(splitAng
,splitAngV
,quat
):
152 return max(0,splitAng
+uniform(-splitAngV
,splitAngV
)-declination(quat
))
154 # Returns number of stems a stem will sprout
155 def stems(stemsMax
,lPar
,offset
,lChild
=False,lChildMax
=None):
157 return stemsMax
*(0.2 + 0.8*(lChild
/lPar
)/lChildMax
)
159 return stemsMax
*(1.0 - 0.5*offset
/lPar
)
161 # Returns the spreading angle
163 return radians(choice([-1,1])*(20 + 0.75*(30 + abs(dec
- 90))*random()**2))
165 # Determines the angle of upward rotation of a segment due to attractUp
166 def curveUp(attractUp
,quat
,curveRes
):
167 tempVec
= yAxis
.copy()
170 return attractUp
*radians(declination(quat
))*abs(tempVec
.z
)/curveRes
172 # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
173 def evalBez(p1
,h1
,h2
,p2
,t
):
174 return ((1-t
)**3)*p1
+ (3*t
*(1-t
)**2)*h1
+ (3*(t
**2)*(1-t
))*h2
+ (t
**3)*p2
176 # Evaluate the unit tangent on a bezier curve for t
177 def evalBezTan(p1
,h1
,h2
,p2
,t
):
178 return ((-3*(1-t
)**2)*p1
+ (-6*t
*(1-t
) + 3*(1-t
)**2)*h1
+ (-3*(t
**2) + 6*t
*(1-t
))*h2
+ (3*t
**2)*p2
).normalized()
180 # Determine the range of t values along a splines length where child stems are formed
181 def findChildPoints(stemList
,numChild
):
182 numPoints
= sum([len(n
.spline
.bezier_points
) for n
in stemList
])
183 numSplines
= len(stemList
)
184 numSegs
= numPoints
- numSplines
185 numPerSeg
= numChild
/numSegs
186 numMain
= round(numPerSeg
*stemList
[0].segMax
,0)
187 return [(a
+1)/(numMain
) for a
in range(int(numMain
))]
189 # Find the coordinates, quaternion and radius for each t on the stem
190 def interpStem(stem
,tVals
,lPar
,parRad
):
192 addpoint
= tempList
.append
193 checkVal
= (stem
.segMax
- len(stem
.spline
.bezier_points
) + 1)/stem
.segMax
194 points
= stem
.spline
.bezier_points
195 numPoints
= len(stem
.spline
.bezier_points
)
196 # Loop through all the parametric values to be determined
198 if (t
>= checkVal
) and (t
< 1.0):
199 scaledT
= (t
-checkVal
)/(tVals
[-1]-checkVal
)
200 length
= (numPoints
-1)*t
#scaledT
203 coord
= points
[-1].co
204 quat
= (points
[-1].handle_right
- points
[-1].co
).to_track_quat('Z','Y')
205 radius
= parRad
#points[-2].radius
207 tTemp
= length
- index
208 coord
= evalBez(points
[index
].co
,points
[index
].handle_right
,points
[index
+1].handle_left
,points
[index
+1].co
,tTemp
)
209 quat
= (evalBezTan(points
[index
].co
,points
[index
].handle_right
,points
[index
+1].handle_left
,points
[index
+1].co
,tTemp
)).to_track_quat('Z','Y')
210 radius
= (1-tTemp
)*points
[index
].radius
+ tTemp
*points
[index
+1].radius
# Not sure if this is the parent radius at the child point or parent start radius
211 addpoint(childPoint(coord
,quat
,(parRad
, radius
),t
*lPar
,lPar
,'bone'+(str(stem
.splN
).rjust(3,'0'))+'.'+(str(index
).rjust(3,'0'))))
214 # Convert a list of degrees to radians
216 return [radians(a
) for a
in list]
218 # This is the function which extends (or grows) a given stem.
219 def growSpline(stem
,numSplit
,splitAng
,splitAngV
,splineList
,attractUp
,hType
,splineToBone
):
220 # First find the current direction of the stem
222 # If the stem splits, we need to add new splines etc
225 cuData
= stem
.spline
.id_data
.name
226 cu
= bpy
.data
.curves
[cuData
]
227 # Now for each split add the new spline and adjust the growth direction
228 for i
in range(numSplit
):
229 newSpline
= cu
.splines
.new('BEZIER')
230 newPoint
= newSpline
.bezier_points
[-1]
231 (newPoint
.co
,newPoint
.handle_left_type
,newPoint
.handle_right_type
) = (stem
.p
.co
,'VECTOR','VECTOR')
232 newPoint
.radius
= stem
.radS
*(1 - stem
.seg
/stem
.segMax
) + stem
.radE
*(stem
.seg
/stem
.segMax
)
234 # Here we make the new "sprouting" stems diverge from the current direction
235 angle
= stem
.splitAngle(splitAng
,splitAngV
)
236 divRotMat
= Matrix
.Rotation(angle
+ stem
.curv
+ uniform(-stem
.curvV
,stem
.curvV
),3,'X')#CurveUP should go after curve is applied
237 dirVec
= zAxis
.copy()
238 dirVec
.rotate(divRotMat
)
239 dirVec
.rotate(splitRotMat(numSplit
,i
+1))
242 # if attractUp != 0.0: # Shouldn't have a special case as this will mess with random number generation
243 divRotMat
= Matrix
.Rotation(angle
+ stem
.curv
+ uniform(-stem
.curvV
,stem
.curvV
),3,'X')
244 dirVec
= zAxis
.copy()
245 dirVec
.rotate(divRotMat
)
246 dirVec
.rotate(splitRotMat(numSplit
,i
+1))
249 #Different version of the commented code above. We could use the inbuilt vector rotations but given this is a special case, it can be quicker to initialise the vector to the correct value.
250 # angle = stem.splitAngle(splitAng,splitAngV)
251 # curveUpAng = curveUp(attractUp,dir,stem.segMax)
252 # angleX = angle + stem.curv + uniform(-stem.curvV,stem.curvV) - curveUpAng
253 # angleZ = 2*i*pi/(numSplit+1)
254 # dirVec = Vector((sin(angleX)*sin(angleZ), -sin(angleX)*cos(angleZ), cos(angleX)))
257 # Spread the stem out in a random fashion
258 spreadMat
= Matrix
.Rotation(spreadAng(degrees(dirVec
.z
)),3,'Z')
259 dirVec
.rotate(spreadMat
)
260 # Introduce upward curvature
261 upRotAxis
= xAxis
.copy()
262 upRotAxis
.rotate(dirVec
.to_track_quat('Z','Y'))
263 curveUpAng
= curveUp(attractUp
,dirVec
.to_track_quat('Z','Y'),stem
.segMax
)
264 upRotMat
= Matrix
.Rotation(-curveUpAng
,3,upRotAxis
)
265 dirVec
.rotate(upRotMat
)
266 # Make the growth vec the length of a stem segment
270 # Get the end point position
271 end_co
= stem
.p
.co
.copy()
273 # Add the new point and adjust its coords, handles and radius
274 newSpline
.bezier_points
.add()
275 newPoint
= newSpline
.bezier_points
[-1]
276 (newPoint
.co
,newPoint
.handle_left_type
,newPoint
.handle_right_type
) = (end_co
+ dirVec
,hType
,hType
)
277 newPoint
.radius
= stem
.radS
*(1 - (stem
.seg
+ 1)/stem
.segMax
) + stem
.radE
*((stem
.seg
+ 1)/stem
.segMax
)
278 # If this isn't the last point on a stem, then we need to add it to the list of stems to continue growing
279 if stem
.seg
!= stem
.segMax
:
280 splineList
.append(stemSpline(newSpline
,stem
.curv
-angle
/(stem
.segMax
-stem
.seg
),stem
.curvV
,stem
.seg
+1,stem
.segMax
,stem
.segL
,stem
.children
,stem
.radS
,stem
.radE
,len(cu
.splines
)-1))
281 splineToBone
.append('bone'+(str(stem
.splN
)).rjust(3,'0')+'.'+(str(len(stem
.spline
.bezier_points
)-2)).rjust(3,'0'))
282 # The original spline also needs to keep growing so adjust its direction too
283 angle
= stem
.splitAngle(splitAng
,splitAngV
)
284 divRotMat
= Matrix
.Rotation(angle
+ stem
.curv
+ uniform(-stem
.curvV
,stem
.curvV
),3,'X')
285 dirVec
= zAxis
.copy()
286 dirVec
.rotate(divRotMat
)
288 spreadMat
= Matrix
.Rotation(spreadAng(degrees(dirVec
.z
)),3,'Z')
289 dirVec
.rotate(spreadMat
)
291 # If there are no splits then generate the growth direction without accounting for spreading of stems
292 dirVec
= zAxis
.copy()
293 #curveUpAng = curveUp(attractUp,dir,stem.segMax)
294 divRotMat
= Matrix
.Rotation(stem
.curv
+ uniform(-stem
.curvV
,stem
.curvV
),3,'X')
295 dirVec
.rotate(divRotMat
)
296 #dirVec = Vector((0,-sin(stem.curv - curveUpAng),cos(stem.curv - curveUpAng)))
298 upRotAxis
= xAxis
.copy()
299 upRotAxis
.rotate(dirVec
.to_track_quat('Z','Y'))
300 curveUpAng
= curveUp(attractUp
,dirVec
.to_track_quat('Z','Y'),stem
.segMax
)
301 upRotMat
= Matrix
.Rotation(-curveUpAng
,3,upRotAxis
)
302 dirVec
.rotate(upRotMat
)
306 # Get the end point position
307 end_co
= stem
.p
.co
.copy()
309 stem
.spline
.bezier_points
.add()
310 newPoint
= stem
.spline
.bezier_points
[-1]
311 (newPoint
.co
,newPoint
.handle_left_type
,newPoint
.handle_right_type
) = (end_co
+ dirVec
,hType
,hType
)
312 newPoint
.radius
= stem
.radS
*(1 - (stem
.seg
+ 1)/stem
.segMax
) + stem
.radE
*((stem
.seg
+ 1)/stem
.segMax
)
313 # There are some cases where a point cannot have handles as VECTOR straight away, set these now.
315 tempPoint
= stem
.spline
.bezier_points
[-2]
316 (tempPoint
.handle_left_type
,tempPoint
.handle_right_type
) = ('VECTOR','VECTOR')
317 if len(stem
.spline
.bezier_points
) == 2:
318 tempPoint
= stem
.spline
.bezier_points
[0]
319 (tempPoint
.handle_left_type
,tempPoint
.handle_right_type
) = ('VECTOR','VECTOR')
320 # Update the last point in the spline to be the newly added one
324 def genLeafMesh(leafScale
,leafScaleX
,loc
,quat
,index
,downAngle
,downAngleV
,rotate
,rotateV
,oldRot
,bend
,leaves
, leafShape
):
325 if leafShape
== 'hex':
326 verts
= [Vector((0,0,0)),Vector((0.5,0,1/3)),Vector((0.5,0,2/3)),Vector((0,0,1)),Vector((-0.5,0,2/3)),Vector((-0.5,0,1/3))]
327 edges
= [[0,1],[1,2],[2,3],[3,4],[4,5],[5,0],[0,3]]
328 faces
= [[0,1,2,3],[0,3,4,5]]
329 elif leafShape
== 'rect':
330 verts
= [Vector((1,0,0)),Vector((1,0,1)),Vector((-1,0,1)),Vector((-1,0,0))]
331 edges
= [[0,1],[1,2],[2,3],[3,0]]
333 #faces = [[0,1,5],[1,2,4,5],[2,3,4]]
338 # If the special -ve flag is used we need a different rotation of the leaf geometry
340 rotMat
= Matrix
.Rotation(oldRot
,3,'Y')
341 oldRot
+= rotate
/(abs(leaves
)-1)
343 oldRot
+= rotate
+uniform(-rotateV
,rotateV
)
344 downRotMat
= Matrix
.Rotation(downAngle
+uniform(-downAngleV
,downAngleV
),3,'X')
345 rotMat
= Matrix
.Rotation(oldRot
,3,'Z')
347 normal
= yAxis
.copy()
348 #dirVec = zAxis.copy()
349 orientationVec
= zAxis
.copy()
351 # If the bending of the leaves is used we need to rotated them differently
352 if (bend
!= 0.0) and (leaves
>= 0):
353 # normal.rotate(downRotMat)
354 # orientationVec.rotate(downRotMat)
356 # normal.rotate(rotMat)
357 # orientationVec.rotate(rotMat)
360 orientationVec
.rotate(quat
)
362 thetaPos
= atan2(loc
.y
,loc
.x
)
363 thetaBend
= thetaPos
- atan2(normal
.y
,normal
.x
)
364 rotateZ
= Matrix
.Rotation(bend
*thetaBend
,3,'Z')
365 normal
.rotate(rotateZ
)
366 orientationVec
.rotate(rotateZ
)
368 phiBend
= atan2((normal
.xy
).length
,normal
.z
)
369 orientation
= atan2(orientationVec
.y
,orientationVec
.x
)
370 rotateZOrien
= Matrix
.Rotation(orientation
,3,'X')
372 rotateX
= Matrix
.Rotation(bend
*phiBend
,3,'Z')
374 rotateZOrien2
= Matrix
.Rotation(-orientation
,3,'X')
376 # For each of the verts we now rotate and scale them, then append them to the list to be added to the mesh
380 v
.x
*= leafScaleX
*leafScale
388 if (bend
!= 0.0) and (leaves
> 0):
389 # Correct the rotation
391 v
.rotate(rotateZOrien
)
393 v
.rotate(rotateZOrien2
)
398 vertsList
.append([v
.x
,v
.y
,v
.z
])
401 facesList
.append([f
[0] + index
,f
[1] + index
,f
[2] + index
,f
[3] + index
])
402 return vertsList
,facesList
,oldRot
406 #startTime = time.time()
407 # Set the seed for repeatable results
410 # Set all other variables
411 levels
= props
.levels
#
412 length
= props
.length
#
413 lengthV
= props
.lengthV
#
414 branches
= props
.branches
#
415 curveRes
= props
.curveRes
#
416 curve
= toRad(props
.curve
)#
417 curveV
= toRad(props
.curveV
)#
418 curveBack
= toRad(props
.curveBack
)#
419 baseSplits
= props
.baseSplits
#
420 segSplits
= props
.segSplits
#
421 splitAngle
= toRad(props
.splitAngle
)#
422 splitAngleV
= toRad(props
.splitAngleV
)#
424 scaleV
= props
.scaleV
#
425 attractUp
= props
.attractUp
#
426 shape
= int(props
.shape
)#
427 baseSize
= props
.baseSize
430 ratioPower
= props
.ratioPower
#
431 downAngle
= toRad(props
.downAngle
)#
432 downAngleV
= toRad(props
.downAngleV
)#
433 rotate
= toRad(props
.rotate
)#
434 rotateV
= toRad(props
.rotateV
)#
435 scale0
= props
.scale0
#
436 scaleV0
= props
.scaleV0
#
438 pruneWidth
= props
.pruneWidth
#
439 pruneWidthPeak
= props
.pruneWidthPeak
#
440 prunePowerLow
= props
.prunePowerLow
#
441 prunePowerHigh
= props
.prunePowerHigh
#
442 pruneRatio
= props
.pruneRatio
#
443 leafScale
= props
.leafScale
#
444 leafScaleX
= props
.leafScaleX
#
445 leafShape
= props
.leafShape
447 leafDist
= int(props
.leafDist
)#
448 bevelRes
= props
.bevelRes
#
450 useArm
= props
.useArm
452 frameRate
= props
.frameRate
453 windSpeed
= props
.windSpeed
454 windGust
= props
.windGust
455 armAnim
= props
.armAnim
459 # Some effects can be turned ON and OFF, the necessary variables are changed here
465 if not props
.showLeaves
:
468 leaves
= props
.leaves
470 if props
.handleType
== '0':
475 for ob
in bpy
.data
.objects
:
481 # Initialise the tree object and curve and adjust the settings
482 cu
= bpy
.data
.curves
.new('tree','CURVE')
483 treeOb
= bpy
.data
.objects
.new('tree',cu
)
484 bpy
.context
.scene
.objects
.link(treeOb
)
486 treeOb
.location
=bpy
.context
.scene
.cursor_location
489 cu
.fill_mode
= 'FULL'
490 cu
.bevel_depth
= bevelDepth
491 cu
.bevel_resolution
= bevelRes
493 # Fix the scale of the tree now
494 scaleVal
= scale
+ uniform(-scaleV
,scaleV
)
496 # If pruning is turned on we need to draw the pruning envelope
500 enCu
= bpy
.data
.curves
.new('envelope','CURVE')
501 enOb
= bpy
.data
.objects
.new('envelope',enCu
)
503 bpy
.context
.scene
.objects
.link(enOb
)
504 newSpline
= enCu
.splines
.new('BEZIER')
505 newPoint
= newSpline
.bezier_points
[-1]
506 newPoint
.co
= Vector((0,0,scaleVal
))
507 (newPoint
.handle_right_type
,newPoint
.handle_left_type
) = (enHandle
,enHandle
)
508 # Set the coordinates by varying the z value, envelope will be aligned to the x-axis
509 for c
in range(enNum
):
510 newSpline
.bezier_points
.add()
511 newPoint
= newSpline
.bezier_points
[-1]
512 ratioVal
= (c
+1)/(enNum
)
513 zVal
= scaleVal
- scaleVal
*(1-baseSize
)*ratioVal
514 newPoint
.co
= Vector((scaleVal
*pruneWidth
*shapeRatio(8,ratioVal
,pruneWidthPeak
,prunePowerHigh
,prunePowerLow
),0,zVal
))
515 (newPoint
.handle_right_type
,newPoint
.handle_left_type
) = (enHandle
,enHandle
)
516 newSpline
= enCu
.splines
.new('BEZIER')
517 newPoint
= newSpline
.bezier_points
[-1]
518 newPoint
.co
= Vector((0,0,scaleVal
))
519 (newPoint
.handle_right_type
,newPoint
.handle_left_type
) = (enHandle
,enHandle
)
520 # Create a second envelope but this time on the y-axis
521 for c
in range(enNum
):
522 newSpline
.bezier_points
.add()
523 newPoint
= newSpline
.bezier_points
[-1]
524 ratioVal
= (c
+1)/(enNum
)
525 zVal
= scaleVal
- scaleVal
*(1-baseSize
)*ratioVal
526 newPoint
.co
= Vector((0,scaleVal
*pruneWidth
*shapeRatio(8,ratioVal
,pruneWidthPeak
,prunePowerHigh
,prunePowerLow
),zVal
))
527 (newPoint
.handle_right_type
,newPoint
.handle_left_type
) = (enHandle
,enHandle
)
533 splineToBone
= deque([''])
534 addsplinetobone
= splineToBone
.append
536 # Each of the levels needed by the user we grow all the splines
537 for n
in range(levels
):
540 addstem
= stemList
.append
541 # If n is used as an index to access parameters for the tree it must be at most 3 or it will reference outside the array index
545 # If this is the first level of growth (the trunk) then we need some special work to begin the tree
548 newSpline
= cu
.splines
.new('BEZIER')
549 cu
.resolution_u
= resU
550 newPoint
= newSpline
.bezier_points
[-1]
551 newPoint
.co
= Vector((0,0,0))
552 newPoint
.handle_right
= Vector((0,0,1))
553 newPoint
.handle_left
= Vector((0,0,-1))
554 #(newPoint.handle_right_type,newPoint.handle_left_type) = ('VECTOR','VECTOR')
555 branchL
= (scaleVal
)*(length
[0] + uniform(-lengthV
[0],lengthV
[0]))
556 childStems
= branches
[1]
557 startRad
= branchL
*ratio
*(scale0
+ uniform(-scaleV0
,scaleV0
))
558 endRad
= startRad
*(1 - taper
[0])
559 newPoint
.radius
= startRad
560 addstem(stemSpline(newSpline
,curve
[0]/curveRes
[0],curveV
[0]/curveRes
[0],0,curveRes
[0],branchL
/curveRes
[0],childStems
,startRad
,endRad
,0))
561 # If this isn't the trunk then we may have multiple stem to intialise
563 # Store the old rotation to allow new stems to be rotated away from the previous one.
565 # For each of the points defined in the list of stem starting points we need to grow a stem.
567 # Add a spline and set the coordinate of the first point.
568 newSpline
= cu
.splines
.new('BEZIER')
569 cu
.resolution_u
= resU
570 newPoint
= newSpline
.bezier_points
[-1]
572 tempPos
= zAxis
.copy()
573 # If the -ve flag for downAngle is used we need a special formula to find it
574 if downAngleV
[n
] < 0.0:
575 downV
= downAngleV
[n
]*(1 - 2*shapeRatio(0,(p
.lengthPar
- p
.offset
)/(p
.lengthPar
- baseSize
*scaleVal
)))
577 # Otherwise just find a random value
579 downV
= uniform(-downAngleV
[n
],downAngleV
[n
])
580 downRotMat
= Matrix
.Rotation(downAngle
[n
]+downV
,3,'X')
581 tempPos
.rotate(downRotMat
)
582 # If the -ve flag for rotate is used we need to find which side of the stem the last child point was and then grow in the opposite direction.
584 oldRotate
= -copysign(rotate
[n
] + uniform(-rotateV
[n
],rotateV
[n
]),oldRotate
)
585 # Otherwise just generate a random number in the specified range
587 oldRotate
+= rotate
[n
]+uniform(-rotateV
[n
],rotateV
[n
])
588 # Rotate the direction of growth and set the new point coordinates
589 rotMat
= Matrix
.Rotation(oldRotate
,3,'Z')
590 tempPos
.rotate(rotMat
)
591 tempPos
.rotate(p
.quat
)
592 newPoint
.handle_right
= p
.co
+ tempPos
593 # If this is the first level of branching then upward attraction has no effect and a special formula is used to find branch length and the number of child stems
596 lMax
= length
[1] + uniform(-lengthV
[1],lengthV
[1])
597 branchL
= p
.lengthPar
*lMax
*shapeRatio(shape
,(p
.lengthPar
- p
.offset
)/(p
.lengthPar
- baseSize
*scaleVal
))
598 childStems
= branches
[2]*(0.2 + 0.8*(branchL
/p
.lengthPar
)/lMax
)
599 elif storeN
<= levels
- 2:
600 branchL
= (length
[n
] + uniform(-lengthV
[n
],lengthV
[n
]))*(p
.lengthPar
- 0.6*p
.offset
)
601 childStems
= branches
[min(3,n
+1)]*(1.0 - 0.5*p
.offset
/p
.lengthPar
)
602 # If this is the last level before leaves then we need to generate the child points differently
604 branchL
= (length
[n
] + uniform(-lengthV
[n
],lengthV
[n
]))*(p
.lengthPar
- 0.6*p
.offset
)
608 childStems
= leaves
*shapeRatio(leafDist
,p
.offset
/p
.lengthPar
)
609 # Determine the starting and ending radii of the stem using the tapering of the stem
610 startRad
= min(p
.radiusPar
[0]*((branchL
/p
.lengthPar
)**ratioPower
), p
.radiusPar
[1])
611 endRad
= startRad
*(1 - taper
[n
])
612 newPoint
.radius
= startRad
613 # If curveBack is used then the curviness of the stem is different for the first half
614 if curveBack
[n
] == 0:
615 curveVal
= curve
[n
]/curveRes
[n
]
617 curveVal
= 2*curve
[n
]/curveRes
[n
]
618 # Add the new stem to list of stems to grow and define which bone it will be parented to
619 addstem(stemSpline(newSpline
,curveVal
,curveV
[n
]/curveRes
[n
],0,curveRes
[n
],branchL
/curveRes
[n
],childStems
,startRad
,endRad
,len(cu
.splines
)-1))
620 addsplinetobone(p
.parBone
)
623 # Now grow each of the stems in the list of those to be extended
625 # When using pruning, we need to ensure that the random effects will be the same for each iteration to make sure the problem is linear.
626 randState
= getstate()
629 # Store all the original values for the stem to make sure we have access after it has been modified by pruning
630 originalLength
= st
.segL
631 originalCurv
= st
.curv
632 originalCurvV
= st
.curvV
634 originalHandleR
= st
.p
.handle_right
.copy()
635 originalHandleL
= st
.p
.handle_left
.copy()
636 originalCo
= st
.p
.co
.copy()
642 orginalSplineToBone
= copy
.copy(splineToBone
)
644 # Now do the iterative pruning, this uses a binary search and halts once the difference between upper and lower bounds of the search are less than 0.005
645 while startPrune
and ((currentMax
- currentMin
) > 0.005):
648 # If the search will halt after this iteration, then set the adjustment of stem length to take into account the pruning ratio
649 if (currentMax
- currentMin
) < 0.01:
650 currentScale
= (currentScale
- 1)*pruneRatio
+ 1
653 # Change the segment length of the stem by applying some scaling
654 st
.segL
= originalLength
*currentScale
655 # To prevent millions of splines being created we delete any old ones and replace them with only their first points to begin the spline again
658 cu
.splines
.remove(x
.spline
)
659 newSpline
= cu
.splines
.new('BEZIER')
660 newPoint
= newSpline
.bezier_points
[-1]
661 newPoint
.co
= originalCo
662 newPoint
.handle_right
= originalHandleR
663 newPoint
.handle_left
= originalHandleL
664 (newPoint
.handle_left_type
,newPoint
.handle_right_type
) = ('VECTOR','VECTOR')
665 st
.spline
= newSpline
666 st
.curv
= originalCurv
667 st
.curvV
= originalCurvV
670 newPoint
.radius
= st
.radS
671 splineToBone
= orginalSplineToBone
673 # Initialise the spline list for those contained in the current level of branching
675 # For each of the segments of the stem which must be grown we have to add to each spline in splineList
676 for k
in range(curveRes
[n
]):
677 # Make a copy of the current list to avoid continually adding to the list we're iterating over
678 tempList
= splineList
[:]
679 #print('Leng: ',len(tempList))
680 # For each of the splines in this list set the number of splits and then grow it
684 elif (k
== 1) and (n
== 0):
685 numSplit
= baseSplits
687 numSplit
= splits(segSplits
[n
])
688 if (k
== int(curveRes
[n
]/2)) and (curveBack
[n
] != 0):
689 spl
.curvAdd(-2*curve
[n
]/curveRes
[n
] + 2*curveBack
[n
]/curveRes
[n
])
690 growSpline(spl
,numSplit
,splitAngle
[n
],splitAngleV
[n
],splineList
,vertAtt
,handles
,splineToBone
)# Add proper refs for radius and attractUp
692 # If pruning is enabled then we must to the check to see if the end of the spline is within the evelope
694 # Check each endpoint to see if it is inside
696 coordMag
= (s
.spline
.bezier_points
[-1].co
.xy
).length
697 ratio
= (scaleVal
- s
.spline
.bezier_points
[-1].co
.z
)/(scaleVal
*(1 - baseSize
))
698 # Don't think this if part is needed
699 if (n
== 0) and (s
.spline
.bezier_points
[-1].co
.z
< baseSize
*scaleVal
):
700 pass#insideBool = True
702 insideBool
= ((coordMag
/scaleVal
) < pruneWidth
*shapeRatio(8,ratio
,pruneWidthPeak
,prunePowerHigh
,prunePowerLow
))
703 # If the point is not inside then we adjust the scale and current search bounds
706 currentMax
= currentScale
707 currentScale
= 0.5*(currentMax
+ currentMin
)
709 # If the scale is the original size and the point is inside then we need to make sure it won't be pruned or extended to the edge of the envelope
710 if insideBool
and (currentScale
!= 1):
711 currentMin
= currentScale
713 currentScale
= 0.5*(currentMax
+ currentMin
)
714 if insideBool
and ((currentMax
- currentMin
) == 1):
716 # If the search will halt on the next iteration then we need to make sure we sprout child points to grow the next splines or leaves
717 if (((currentMax
- currentMin
) < 0.005) or not prune
) or forceSprout
:
718 tVals
= findChildPoints(splineList
,st
.children
)
719 # If leaves is -ve then we need to make sure the only point which sprouts is the end of the spline
723 # If this is the trunk then we need to remove some of the points because of baseSize
725 trimNum
= int(baseSize
*(len(tVals
)+1))
726 tVals
= tVals
[trimNum
:]
728 # For all the splines, we interpolate them and add the new points to the list of child points
730 #print(str(n)+'level: ',s.segMax*s.segL)
731 childP
.extend(interpStem(s
,tVals
,s
.segMax
*s
.segL
,s
.radS
))
733 # Force the splines to be deleted
735 # If pruning isn't enabled then make sure it doesn't loop
739 levelCount
.append(len(cu
.splines
))
740 # If we need to add leaves, we do it here
741 if (storeN
== levels
-1) and leaves
:
744 # For each of the child points we add leaves
746 # If the special flag is set then we need to add several leaves at the same location
748 oldRot
= -rotate
[n
]/2
749 for g
in range(abs(leaves
)):
750 (vertTemp
,faceTemp
,oldRot
) = genLeafMesh(leafScale
,leafScaleX
,cp
.co
,cp
.quat
,len(leafVerts
),downAngle
[n
],downAngleV
[n
],rotate
[n
],rotateV
[n
],oldRot
,bend
,leaves
, leafShape
)
751 leafVerts
.extend(vertTemp
)
752 leafFaces
.extend(faceTemp
)
753 # Otherwise just add the leaves like splines.
755 (vertTemp
,faceTemp
,oldRot
) = genLeafMesh(leafScale
,leafScaleX
,cp
.co
,cp
.quat
,len(leafVerts
),downAngle
[n
],downAngleV
[n
],rotate
[n
],rotateV
[n
],oldRot
,bend
,leaves
, leafShape
)
756 leafVerts
.extend(vertTemp
)
757 leafFaces
.extend(faceTemp
)
758 # Create the leaf mesh and object, add geometry using from_pydata, edges are currently added by validating the mesh which isn't great
759 leafMesh
= bpy
.data
.meshes
.new('leaves')
760 leafObj
= bpy
.data
.objects
.new('leaves',leafMesh
)
761 bpy
.context
.scene
.objects
.link(leafObj
)
762 leafObj
.parent
= treeOb
763 leafMesh
.from_pydata(leafVerts
,(),leafFaces
)
765 if leafShape
== 'rect':
766 leafMesh
.uv_textures
.new("leafUV")
767 uvlayer
= leafMesh
.uv_layers
.active
.data
769 for i
in range(0, len(leafFaces
)):
770 uvlayer
[i
*4 + 0].uv
= Vector((1, 0))
771 uvlayer
[i
*4 + 1].uv
= Vector((1, 1))
772 uvlayer
[i
*4 + 2].uv
= Vector((1 - leafScaleX
, 1))
773 uvlayer
[i
*4 + 3].uv
= Vector((1 - leafScaleX
, 0))
777 # This can be used if we need particle leaves
778 # if (storeN == levels-1) and leaves:
783 # # For each of the child points we add leaves
785 # # Here we make the new "sprouting" stems diverge from the current direction
786 # dirVec = zAxis.copy()
787 # oldRot += rotate[n]+uniform(-rotateV[n],rotateV[n])
788 # downRotMat = Matrix.Rotation(downAngle[n]+uniform(-downAngleV[n],downAngleV[n]),3,'X')
789 # rotMat = Matrix.Rotation(oldRot,3,'Z')
790 # dirVec.rotate(downRotMat)
791 # dirVec.rotate(rotMat)
792 # dirVec.rotate(cp.quat)
793 # normalList.extend([dirVec.x,dirVec.y,dirVec.z])
794 # leafVerts.append(cp.co)
795 # # Create the leaf mesh and object, add geometry using from_pydata, edges are currently added by validating the mesh which isn't great
796 # edgeList = [(a,a+1) for a in range(len(childP)-1)]
797 # leafMesh = bpy.data.meshes.new('leaves')
798 # leafObj = bpy.data.objects.new('leaves',leafMesh)
799 # bpy.context.scene.objects.link(leafObj)
800 # leafObj.parent = treeOb
801 # leafMesh.from_pydata(leafVerts,edgeList,())
802 # leafMesh.vertices.foreach_set('normal',normalList)
804 # If we need and armature we add it
806 # Create the armature and objects
807 arm
= bpy
.data
.armatures
.new('tree')
808 armOb
= bpy
.data
.objects
.new('treeArm',arm
)
809 bpy
.context
.scene
.objects
.link(armOb
)
811 # Create a new action to store all animation
812 newAction
= bpy
.data
.actions
.new(name
='windAction')
813 armOb
.animation_data_create()
814 armOb
.animation_data
.action
= newAction
816 arm
.draw_type
= 'STICK'
817 arm
.use_deform_delay
= True
819 # Add the armature modifier to the curve
820 armMod
= treeOb
.modifiers
.new('windSway','ARMATURE')
821 #armMod.use_apply_on_spline = True
822 armMod
.object = armOb
824 # If there are leaves then they need a modifier
826 armMod
= leafObj
.modifiers
.new('windSway','ARMATURE')
827 armMod
.object = armOb
829 # Make sure all objects are deselected (may not be required?)
830 for ob
in bpy
.data
.objects
:
833 # Set the armature as active and go to edit mode to add bones
834 bpy
.context
.scene
.objects
.active
= armOb
835 bpy
.ops
.object.mode_set(mode
='EDIT')
841 # For all the splines in the curve we need to add bones at each bezier point
842 for i
,parBone
in enumerate(splineToBone
):
845 # Get some data about the spline like length and number of points
846 numPoints
= len(s
.bezier_points
)-1
847 splineL
= numPoints
*((s
.bezier_points
[0].co
-s
.bezier_points
[1].co
).length
)
848 # Set the random phase difference of the animation
849 bxOffset
= uniform(0,2*pi
)
850 byOffset
= uniform(0,2*pi
)
851 # Set the phase multiplier for the spline
852 bMult
= (s
.bezier_points
[0].radius
/splineL
)*(1/15)*(1/frameRate
)
853 # For all the points in the curve (less the last) add a bone and name it by the spline it will affect
854 for n
in range(numPoints
):
856 boneName
= 'bone'+(str(i
)).rjust(3,'0')+'.'+(str(n
)).rjust(3,'0')
857 b
= arm
.edit_bones
.new(boneName
)
858 b
.head
= s
.bezier_points
[n
].co
859 b
.tail
= s
.bezier_points
[n
+1].co
861 b
.head_radius
= s
.bezier_points
[n
].radius
862 b
.tail_radius
= s
.bezier_points
[n
+1].radius
863 b
.envelope_distance
= 0.001#0.001
865 # If there are leaves then we need a new vertex group so they will attach to the bone
866 if (len(levelCount
) > 1) and (i
>= levelCount
[-2]) and leafObj
:
867 leafObj
.vertex_groups
.new(boneName
)
868 elif (len(levelCount
) == 1) and leafObj
:
869 leafObj
.vertex_groups
.new(boneName
)
870 # If this is first point of the spline then it must be parented to the level above it
873 b
.parent
= arm
.edit_bones
[parBone
]
874 # if len(parBone) > 11:
875 # b.use_connect = True
876 # Otherwise, we need to attach it to the previous bone in the spline
880 # If there isn't a previous bone then it shouldn't be attached
882 b
.use_connect
= False
885 # Add the animation to the armature if required
887 # Define all the required parameters of the wind sway by the dimension of the spline
888 a0
= 4*splineL
*(1-n
/(numPoints
+1))/s
.bezier_points
[n
].radius
889 a1
= (windSpeed
/50)*a0
890 a2
= (windGust
/50)*a0
+ a1
/2
892 # Add new fcurves for each sway as well as the modifiers
893 swayX
= armOb
.animation_data
.action
.fcurves
.new('pose.bones["' + boneName
+ '"].rotation_euler',0)
894 swayY
= armOb
.animation_data
.action
.fcurves
.new('pose.bones["' + boneName
+ '"].rotation_euler',2)
896 swayXMod1
= swayX
.modifiers
.new(type='FNGENERATOR')
897 swayXMod2
= swayX
.modifiers
.new(type='FNGENERATOR')
899 swayYMod1
= swayY
.modifiers
.new(type='FNGENERATOR')
900 swayYMod2
= swayY
.modifiers
.new(type='FNGENERATOR')
902 # Set the parameters for each modifier
903 swayXMod1
.amplitude
= radians(a1
)/numPoints
904 swayXMod1
.phase_offset
= bxOffset
905 swayXMod1
.phase_multiplier
= degrees(bMult
)
907 swayXMod2
.amplitude
= radians(a2
)/numPoints
908 swayXMod2
.phase_offset
= 0.7*bxOffset
909 swayXMod2
.phase_multiplier
= 0.7*degrees(bMult
) # This shouldn't have to be in degrees but it looks much better in animation
910 swayXMod2
.use_additive
= True
912 swayYMod1
.amplitude
= radians(a1
)/numPoints
913 swayYMod1
.phase_offset
= byOffset
914 swayYMod1
.phase_multiplier
= degrees(bMult
) # This shouldn't have to be in degrees but it looks much better in animation
916 swayYMod2
.amplitude
= radians(a2
)/numPoints
917 swayYMod2
.phase_offset
= 0.7*byOffset
918 swayYMod2
.phase_multiplier
= 0.7*degrees(bMult
) # This shouldn't have to be in degrees but it looks much better in animation
919 swayYMod2
.use_additive
= True
921 # If there are leaves we need to assign vertices to their vertex groups
925 if leafShape
== 'rect':
927 for i
,cp
in enumerate(childP
):
928 for v
in leafMesh
.vertices
[leafVertSize
*i
:(leafVertSize
*i
+leafVertSize
)]:
929 leafObj
.vertex_groups
[cp
.parBone
].add([v
.index
],1.0,'ADD')
931 # Now we need the rotation mode to be 'XYZ' to ensure correct rotation
932 bpy
.ops
.object.mode_set(mode
='OBJECT')
933 for p
in armOb
.pose
.bones
:
934 p
.rotation_mode
= 'XYZ'
935 treeOb
.parent
= armOb
936 #print(time.time()-startTime)