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 #####
23 "author": "testscreenings, PKHG, TrumanBlending",
25 "blender": (2, 59, 0),
26 "location": "View3D > Add > Curve",
27 "description": "Adds generated ivy to a mesh object starting at the 3D"\
30 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
31 "Scripts/Curve/Ivy_Gen",
32 "tracker_url": "http://projects.blender.org/tracker/index.php?"\
33 "func=detail&aid=27234",
34 "category": "Add Curve"}
38 from bpy
.props
import FloatProperty
, IntProperty
, BoolProperty
39 from mathutils
import Vector
, Matrix
40 from collections
import deque
41 from math
import pow, cos
, pi
, atan2
42 from random
import random
as rand_val
, seed
as rand_seed
46 def createIvyGeometry(IVY
, growLeaves
):
47 """Create the curve geometry for IVY"""
48 # Compute the local size and the gauss weight filter
49 #local_ivyBranchSize = IVY.ivyBranchSize # * radius * IVY.ivySize
50 gaussWeight
= (1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0)
52 # Create a new curve and intialise it
53 curve
= bpy
.data
.curves
.new("IVY", type='CURVE')
54 curve
.dimensions
= '3D'
56 curve
.fill_mode
= 'FULL'
57 curve
.resolution_u
= 4
60 # Create the ivy leaves
61 # Order location of the vertices
62 signList
= ((-1.0, +1.0),
69 #local_ivyLeafSize = IVY.ivyLeafSize # * radius * IVY.ivySize
71 # Initialise the vertex and face lists
74 # Store the methods for faster calling
75 addV
= vertList
.extend
76 rotMat
= Matrix
.Rotation
78 # Loop over all roots to generate its nodes
79 for root
in IVY
.ivyRoots
:
80 # Only grow if more than one node
81 numNodes
= len(root
.ivyNodes
)
83 # Calculate the local radius
84 local_ivyBranchRadius
= 1.0 / (root
.parents
+ 1) + 1.0
85 prevIvyLength
= 1.0 / root
.ivyNodes
[-1].length
86 splineVerts
= [ax
for n
in root
.ivyNodes
for ax
in n
.pos
.to_4d()]
88 radiusConstant
= local_ivyBranchRadius
* IVY
.ivyBranchSize
89 splineRadii
= [radiusConstant
* (1.3 - n
.length
* prevIvyLength
)
90 for n
in root
.ivyNodes
]
92 # Add the poly curve and set coords and radii
93 newSpline
= curve
.splines
.new(type='POLY')
94 newSpline
.points
.add(len(splineVerts
) // 4 - 1)
95 newSpline
.points
.foreach_set('co', splineVerts
)
96 newSpline
.points
.foreach_set('radius', splineRadii
)
98 # Loop over all nodes in the root
99 for i
, n
in enumerate(root
.ivyNodes
):
100 for k
in range(len(gaussWeight
)):
101 idx
= max(0, min(i
+ k
- 5, numNodes
- 1))
102 n
.smoothAdhesionVector
+= (gaussWeight
[k
] *
103 root
.ivyNodes
[idx
].adhesionVector
)
104 n
.smoothAdhesionVector
/= 56.0
105 n
.adhesionLength
= n
.smoothAdhesionVector
.length
106 n
.smoothAdhesionVector
.normalize()
108 if growLeaves
and (i
< numNodes
- 1):
109 node
= root
.ivyNodes
[i
]
110 nodeNext
= root
.ivyNodes
[i
+ 1]
112 # Find the weight and normalize the smooth adhesion vector
113 weight
= pow(node
.length
* prevIvyLength
, 0.7)
115 # Calculate the ground ivy and the new weight
116 groundIvy
= max(0.0, -node
.smoothAdhesionVector
.z
)
117 weight
+= groundIvy
* pow(1 - node
.length
*
120 # Find the alignment weight
121 alignmentWeight
= node
.adhesionLength
123 # Calculate the needed angles
124 phi
= atan2(node
.smoothAdhesionVector
.y
,
125 node
.smoothAdhesionVector
.x
) - pi
/ 2.0
128 node
.smoothAdhesionVector
.angle(Vector((0, 0, -1)), 0))
130 # Find the size weight
131 sizeWeight
= 1.5 - (cos(2 * pi
* weight
) * 0.5 + 0.5)
133 # Randomise the angles
134 phi
+= (rand_val() - 0.5) * (1.3 - alignmentWeight
)
135 theta
+= (rand_val() - 0.5) * (1.1 - alignmentWeight
)
137 # Calculate the leaf size an append the face to the list
138 leafSize
= IVY
.ivyLeafSize
* sizeWeight
141 # Generate the probability
142 probability
= rand_val()
144 # If we need to grow a leaf, do so
145 if (probability
* weight
) > IVY
.leafProbability
:
147 # Generate the random vector
148 randomVector
= Vector((rand_val() - 0.5,
153 # Find the leaf center
154 center
= (node
.pos
.lerp(nodeNext
.pos
, j
/ 10.0) +
155 IVY
.ivyLeafSize
* randomVector
)
157 # For each of the verts, rotate/scale and append
158 basisVecX
= Vector((1, 0, 0))
159 basisVecY
= Vector((0, 1, 0))
161 horiRot
= rotMat(theta
, 3, 'X')
162 vertRot
= rotMat(phi
, 3, 'Z')
164 basisVecX
.rotate(horiRot
)
165 basisVecY
.rotate(horiRot
)
167 basisVecX
.rotate(vertRot
)
168 basisVecY
.rotate(vertRot
)
170 basisVecX
*= leafSize
171 basisVecY
*= leafSize
173 addV([k1
* basisVecX
+ k2
* basisVecY
+ center
for
176 # Add the object and link to scene
177 newCurve
= bpy
.data
.objects
.new("IVY_Curve", curve
)
178 bpy
.context
.scene
.objects
.link(newCurve
)
181 faceList
= [[4 * i
+ l
for l
in range(4)] for i
in
182 range(len(vertList
) // 4)]
184 # Generate the new leaf mesh and link
185 me
= bpy
.data
.meshes
.new('IvyLeaf')
186 me
.from_pydata(vertList
, [], faceList
)
187 me
.update(calc_edges
=True)
188 ob
= bpy
.data
.objects
.new('IvyLeaf', me
)
189 bpy
.context
.scene
.objects
.link(ob
)
191 me
.uv_textures
.new("Leaves")
193 # Set the uv texture coords
194 # TODO, this is non-functional, default uvs are ok?
197 uv1, uv2, uv3, uv4 = signList
203 def computeBoundingSphere(ob):
206 # Intialise the center
207 center = Vector((0.0, 0.0, 0.0))
208 # Add all vertex coords
209 for v in me.vertices:
211 # Average over all verts
212 center /= len(me.vertices)
213 # Create the iterator and find its max
214 length_iter = ((center - v.co).length for v in me.vertices)
215 radius = max(length_iter)
221 """ The basic class used for each point on the ivy which is grown."""
222 __slots__
= ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
223 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')
226 self
.pos
= Vector((0, 0, 0))
227 self
.primaryDir
= Vector((0, 0, 1))
228 self
.adhesionVector
= Vector((0, 0, 0))
229 self
.smoothAdhesionVector
= Vector((0, 0, 0))
231 self
.floatingLength
= 0.0
236 """ The class used to hold all ivy nodes growing from this root point."""
237 __slots__
= ('ivyNodes', 'alive', 'parents')
240 self
.ivyNodes
= deque()
246 """ The class holding all parameters and ivy roots."""
247 __slots__
= ('ivyRoots', 'primaryWeight', 'randomWeight',
248 'gravityWeight', 'adhesionWeight', 'branchingProbability',
249 'leafProbability', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
250 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
257 branchingProbability
=0.05,
258 leafProbability
=0.35,
263 maxAdhesionDistance
=1.0):
265 self
.ivyRoots
= deque()
266 self
.primaryWeight
= primaryWeight
267 self
.randomWeight
= randomWeight
268 self
.gravityWeight
= gravityWeight
269 self
.adhesionWeight
= adhesionWeight
270 self
.branchingProbability
= 1 - branchingProbability
271 self
.leafProbability
= 1 - leafProbability
272 self
.ivySize
= ivySize
273 self
.ivyLeafSize
= ivyLeafSize
274 self
.ivyBranchSize
= ivyBranchSize
275 self
.maxFloatLength
= maxFloatLength
276 self
.maxAdhesionDistance
= maxAdhesionDistance
279 # Normalize all the weights only on intialisation
280 sum = self
.primaryWeight
+ self
.randomWeight
+ self
.adhesionWeight
281 self
.primaryWeight
/= sum
282 self
.randomWeight
/= sum
283 self
.adhesionWeight
/= sum
285 def seed(self
, seedPos
):
286 # Seed the Ivy by making a new root and first node
291 tmpRoot
.ivyNodes
.append(tmpIvy
)
292 self
.ivyRoots
.append(tmpRoot
)
295 # Determine the local sizes
296 #local_ivySize = self.ivySize # * radius
297 #local_maxFloatLength = self.maxFloatLength # * radius
298 #local_maxAdhesionDistance = self.maxAdhesionDistance # * radius
300 for root
in self
.ivyRoots
:
301 # Make sure the root is alive, if not, skip
305 # Get the last node in the current root
306 prevIvy
= root
.ivyNodes
[-1]
308 # If the node is floating for too long, kill the root
309 if prevIvy
.floatingLength
> self
.maxFloatLength
:
312 # Set the primary direction from the last node
313 primaryVector
= prevIvy
.primaryDir
315 # Make the random vector and normalize
316 randomVector
= Vector((rand_val() - 0.5, rand_val() - 0.5,
317 rand_val() - 0.5)) + Vector((0, 0, 0.2))
318 randomVector
.normalize()
320 # Calculate the adhesion vector
321 adhesionVector
= adhesion(prevIvy
.pos
, ob
,
322 self
.maxAdhesionDistance
)
324 # Calculate the growing vector
325 growVector
= self
.ivySize
* (primaryVector
* self
.primaryWeight
+
326 randomVector
* self
.randomWeight
+
327 adhesionVector
* self
.adhesionWeight
)
329 # Find the gravity vector
330 gravityVector
= (self
.ivySize
* self
.gravityWeight
*
332 gravityVector
*= pow(prevIvy
.floatingLength
/ self
.maxFloatLength
,
335 # Determine the new position vector
336 newPos
= prevIvy
.pos
+ growVector
+ gravityVector
338 # Check for collisions with the object
339 climbing
= collision(ob
, prevIvy
.pos
, newPos
)
341 # Update the growing vector for any collisions
342 growVector
= newPos
- prevIvy
.pos
- gravityVector
343 growVector
.normalize()
345 # Create a new IvyNode and set its properties
347 tmpNode
.climb
= climbing
349 tmpNode
.primaryDir
= prevIvy
.primaryDir
.lerp(growVector
, 0.5)
350 tmpNode
.primaryDir
.normalize()
351 tmpNode
.adhesionVector
= adhesionVector
352 tmpNode
.length
= prevIvy
.length
+ (newPos
- prevIvy
.pos
).length
354 if tmpNode
.length
> self
.maxLength
:
355 self
.maxLength
= tmpNode
.length
357 # If the node isn't climbing, update it's floating length
358 # Otherwise set it to 0
360 tmpNode
.floatingLength
= prevIvy
.floatingLength
+ (newPos
-
363 tmpNode
.floatingLength
= 0.0
365 root
.ivyNodes
.append(tmpNode
)
367 # Loop through all roots to check if a new root is generated
368 for root
in self
.ivyRoots
:
369 # Check the root is alive and isn't at high level of recursion
370 if (root
.parents
> 3) or (not root
.alive
):
373 # Check to make sure there's more than 1 node
374 if len(root
.ivyNodes
) > 1:
375 # Loop through all nodes in root to check if new root is grown
376 for node
in root
.ivyNodes
:
377 # Set the last node of the root and find the weighting
378 prevIvy
= root
.ivyNodes
[-1]
379 weight
= 1.0 - (cos(2.0 * pi
* node
.length
/
380 prevIvy
.length
) * 0.5 + 0.5)
382 probability
= rand_val()
384 # Check if a new root is grown and if so, set its values
385 if (probability
* weight
> self
.branchingProbability
):
387 tmpNode
.pos
= node
.pos
388 tmpNode
.floatingLength
= node
.floatingLength
391 tmpRoot
.parents
= root
.parents
+ 1
393 tmpRoot
.ivyNodes
.append(tmpNode
)
394 self
.ivyRoots
.append(tmpRoot
)
398 def adhesion(loc
, ob
, max_l
):
399 # Get transfor vector and transformed loc
400 tran_mat
= ob
.matrix_world
.inverted()
401 tran_loc
= tran_mat
* loc
403 # Compute the adhesion vector by finding the nearest point
404 nearest_result
= ob
.closest_point_on_mesh(tran_loc
, max_l
)
405 adhesion_vector
= Vector((0.0, 0.0, 0.0))
406 if nearest_result
[2] != -1:
407 # Compute the distance to the nearest point
408 adhesion_vector
= ob
.matrix_world
* nearest_result
[0] - loc
409 distance
= adhesion_vector
.length
410 # If it's less than the maximum allowed and not 0, continue
412 # Compute the direction vector between the closest point and loc
413 adhesion_vector
.normalize()
414 adhesion_vector
*= 1.0 - distance
/ max_l
415 #adhesion_vector *= getFaceWeight(ob.data, nearest_result[2])
416 return adhesion_vector
419 def collision(ob
, pos
, new_pos
):
420 # Check for collision with the object
424 tran_mat
= ob
.matrix_world
.inverted()
425 tran_pos
= tran_mat
* pos
426 tran_new_pos
= tran_mat
* new_pos
428 ray_result
= ob
.ray_cast(tran_pos
, tran_new_pos
)
429 # If there's a collision we need to check it
430 if ray_result
[2] != -1:
431 # Check whether the collision is going into the object
432 if (tran_new_pos
- tran_pos
).dot(ray_result
[1]) < 0.0:
433 # Find projection of the piont onto the plane
434 p0
= tran_new_pos
- (tran_new_pos
-
435 ray_result
[0]).project(ray_result
[1])
436 # Reflect in the plane
437 tran_new_pos
+= 2 * (p0
- tran_new_pos
)
439 new_pos
+= ob
.matrix_world
* tran_new_pos
444 class IvyGen(bpy
.types
.Operator
):
445 bl_idname
= "curve.ivy_gen"
447 bl_options
= {'REGISTER', 'UNDO'}
449 maxIvyLength
= FloatProperty(name
="Max Ivy Length",
450 description
="Maximum ivy length in Blender Units",
456 primaryWeight
= FloatProperty(name
="Primary Weight",
457 description
="Weighting given to the current direction",
461 randomWeight
= FloatProperty(name
="Random Weight",
462 description
="Weighting given to the random direction",
466 gravityWeight
= FloatProperty(name
="Gravity Weight",
467 description
="Weighting given to the gravity direction",
471 adhesionWeight
= FloatProperty(name
="Adhesion Weight",
472 description
="Weighting given to the adhesion direction",
476 branchingProbability
= FloatProperty(name
="Branching Probability",
477 description
="Probability of a new branch forming",
481 leafProbability
= FloatProperty(name
="Leaf Probability",
482 description
="Probability of a leaf forming",
486 ivySize
= FloatProperty(name
="Ivy Size",
487 description
=("The length of an ivy segment in Blender"
493 ivyLeafSize
= FloatProperty(name
="Ivy Leaf Size",
494 description
="The size of the ivy leaves",
499 ivyBranchSize
= FloatProperty(name
="Ivy Branch Size",
500 description
="The size of the ivy branches",
505 maxFloatLength
= FloatProperty(name
="Max Float Length",
506 description
=("The maximum distance that a branch "
507 "can live while floating"),
511 maxAdhesionDistance
= FloatProperty(name
="Max Adhesion Length",
512 description
=("The maximum distance that a branch "
513 "will feel the effects of adhesion"),
518 randomSeed
= IntProperty(name
="Random Seed",
519 description
="The seed governing random generation",
522 maxTime
= FloatProperty(name
="Maximum Time",
523 description
=("The maximum time to run the generation for "
524 "in seconds generation (0.0 = Disabled)"),
528 growLeaves
= BoolProperty(name
="Grow Leaves",
529 description
="Grow leaves or not",
531 updateIvy
= BoolProperty(name
="Update Ivy", default
=False)
534 def poll(self
, context
):
535 # Check if there's an object and whether it's a mesh
536 ob
= context
.active_object
537 return ((ob
is not None) and
538 (ob
.type == 'MESH') and
539 (context
.mode
== 'OBJECT'))
541 def execute(self
, context
):
542 if not self
.updateIvy
:
543 return {'PASS_THROUGH'}
545 bpy
.ops
.object.mode_set(mode
='EDIT', toggle
=False)
546 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
548 # Get the selected object
549 ob
= context
.active_object
551 # Compute bounding sphere radius
552 #radius = computeBoundingSphere(ob) # Not needed anymore
554 # Get the seeding point
555 seedPoint
= context
.scene
.cursor_location
557 # Fix the random seed
558 rand_seed(self
.randomSeed
)
561 IVY
= Ivy(**self
.as_keywords(ignore
=('randomSeed', 'growLeaves',
562 'maxIvyLength', 'maxTime', 'updateIvy')))
564 # Generate first root and node
568 maxLength
= self
.maxIvyLength
# * radius
570 # If we need to check time set the flag
571 if self
.maxTime
!= 0.0:
576 checkAliveIter
= [True, ]
578 # Grow until 200 roots is reached or backup counter exceeds limit
579 while (any(checkAliveIter
) and
580 (IVY
.maxLength
< maxLength
) and
581 (not checkTime
or (time
.time() - t
< self
.maxTime
))):
582 # Grow the ivy for this iteration
585 # Print the proportion of ivy growth to console
586 if (IVY
.maxLength
/ maxLength
* 100) > 10 * startPercent
// 10:
587 print('%0.2f%% of Ivy nodes have grown' %
588 (IVY
.maxLength
/ maxLength
* 100))
590 if IVY
.maxLength
/ maxLength
> 1:
591 print("Halting Growth")
593 # Make an iterator to check if all are alive
594 checkAliveIter
= (r
.alive
for r
in IVY
.ivyRoots
)
596 # Create the curve and leaf geometry
597 createIvyGeometry(IVY
, self
.growLeaves
)
598 print("Geometry Generation Complete")
600 print("Ivy generated in %0.2f s" % (time
.time() - t
))
602 self
.updateIvy
= False
606 def draw(self
, context
):
609 layout
.prop(self
, 'updateIvy', icon
='CURVE_DATA')
611 properties
= layout
.operator('curve.ivy_gen', text
="Add New Ivy")
612 properties
.randomSeed
= self
.randomSeed
613 properties
.maxTime
= self
.maxTime
614 properties
.maxIvyLength
= self
.maxIvyLength
615 properties
.ivySize
= self
.ivySize
616 properties
.maxFloatLength
= self
.maxFloatLength
617 properties
.maxAdhesionDistance
= self
.maxAdhesionDistance
618 properties
.primaryWeight
= self
.primaryWeight
619 properties
.randomWeight
= self
.randomWeight
620 properties
.gravityWeight
= self
.gravityWeight
621 properties
.adhesionWeight
= self
.adhesionWeight
622 properties
.branchingProbability
= self
.branchingProbability
623 properties
.leafProbability
= self
.leafProbability
624 properties
.ivyBranchSize
= self
.ivyBranchSize
625 properties
.ivyLeafSize
= self
.ivyLeafSize
626 properties
.updateIvy
= True
628 prop_def
= layout
.operator('curve.ivy_gen', text
="Add New Default Ivy")
629 prop_def
.updateIvy
= True
631 layout
.prop(self
, 'growLeaves')
634 box
.label("Generation Settings:")
635 box
.prop(self
, 'randomSeed')
636 box
.prop(self
, 'maxTime')
639 box
.label("Size Settings:")
640 box
.prop(self
, 'maxIvyLength')
641 box
.prop(self
, 'ivySize')
642 box
.prop(self
, 'maxFloatLength')
643 box
.prop(self
, 'maxAdhesionDistance')
646 box
.label("Weight Settings:")
647 box
.prop(self
, 'primaryWeight')
648 box
.prop(self
, 'randomWeight')
649 box
.prop(self
, 'gravityWeight')
650 box
.prop(self
, 'adhesionWeight')
653 box
.label("Branch Settings:")
654 box
.prop(self
, 'branchingProbability')
655 box
.prop(self
, 'ivyBranchSize')
659 box
.label("Leaf Settings:")
660 box
.prop(self
, 'ivyLeafSize')
661 box
.prop(self
, 'leafProbability')
664 def menu_func(self
, context
):
665 self
.layout
.operator(IvyGen
.bl_idname
, text
="Add Ivy to Mesh",
666 icon
='PLUGIN').updateIvy
= True
670 bpy
.utils
.register_module(__name__
)
671 bpy
.types
.INFO_MT_curve_add
.append(menu_func
)
675 bpy
.types
.INFO_MT_curve_add
.remove(menu_func
)
676 bpy
.utils
.unregister_module(__name__
)
679 if __name__
== "__main__":