Node Wrangler: Add more specific poll methods
[blender-addons.git] / add_curve_ivygen.py
blobf10f13de8873d676c5864041574f052d2dcebc17
1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "IvyGen",
7 "author": "testscreenings, PKHG, TrumanBlending",
8 "version": (0, 1, 5),
9 "blender": (2, 80, 0),
10 "location": "View3D > Sidebar > Ivy Generator (Create Tab)",
11 "description": "Adds generated ivy to a mesh object starting "
12 "at the 3D cursor",
13 "warning": "",
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html",
15 "category": "Add Curve",
19 import bpy
20 from bpy.types import (
21 Operator,
22 Panel,
23 PropertyGroup,
25 from bpy.props import (
26 BoolProperty,
27 FloatProperty,
28 IntProperty,
29 PointerProperty,
31 from mathutils.bvhtree import BVHTree
32 from mathutils import (
33 Vector,
34 Matrix,
36 from collections import deque
37 from math import (
38 pow, cos,
39 pi, atan2,
41 from random import (
42 random as rand_val,
43 seed as rand_seed,
45 import time
48 def createIvyGeometry(IVY, growLeaves):
49 """Create the curve geometry for IVY"""
50 # Compute the local size and the gauss weight filter
51 # local_ivyBranchSize = IVY.ivyBranchSize # * radius * IVY.ivySize
52 gaussWeight = (1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0)
54 # Create a new curve and initialise it
55 curve = bpy.data.curves.new("IVY", type='CURVE')
56 curve.dimensions = '3D'
57 curve.bevel_depth = 1
58 curve.fill_mode = 'FULL'
59 curve.resolution_u = 4
61 if growLeaves:
62 # Create the ivy leaves
63 # Order location of the vertices
64 signList = ((-1.0, +1.0),
65 (+1.0, +1.0),
66 (+1.0, -1.0),
67 (-1.0, -1.0),
70 # Get the local size
71 # local_ivyLeafSize = IVY.ivyLeafSize # * radius * IVY.ivySize
73 # Initialise the vertex and face lists
74 vertList = deque()
76 # Store the methods for faster calling
77 addV = vertList.extend
78 rotMat = Matrix.Rotation
80 # Loop over all roots to generate its nodes
81 for root in IVY.ivyRoots:
82 # Only grow if more than one node
83 numNodes = len(root.ivyNodes)
84 if numNodes > 1:
85 # Calculate the local radius
86 local_ivyBranchRadius = 1.0 / (root.parents + 1) + 1.0
87 prevIvyLength = 1.0 / root.ivyNodes[-1].length
88 splineVerts = [ax for n in root.ivyNodes for ax in n.pos.to_4d()]
90 radiusConstant = local_ivyBranchRadius * IVY.ivyBranchSize
91 splineRadii = [radiusConstant * (1.3 - n.length * prevIvyLength)
92 for n in root.ivyNodes]
94 # Add the poly curve and set coords and radii
95 newSpline = curve.splines.new(type='POLY')
96 newSpline.points.add(len(splineVerts) // 4 - 1)
97 newSpline.points.foreach_set('co', splineVerts)
98 newSpline.points.foreach_set('radius', splineRadii)
100 # Loop over all nodes in the root
101 for i, n in enumerate(root.ivyNodes):
102 for k in range(len(gaussWeight)):
103 idx = max(0, min(i + k - 5, numNodes - 1))
104 n.smoothAdhesionVector += (gaussWeight[k] *
105 root.ivyNodes[idx].adhesionVector)
106 n.smoothAdhesionVector /= 56.0
107 n.adhesionLength = n.smoothAdhesionVector.length
108 n.smoothAdhesionVector.normalize()
110 if growLeaves and (i < numNodes - 1):
111 node = root.ivyNodes[i]
112 nodeNext = root.ivyNodes[i + 1]
114 # Find the weight and normalize the smooth adhesion vector
115 weight = pow(node.length * prevIvyLength, 0.7)
117 # Calculate the ground ivy and the new weight
118 groundIvy = max(0.0, -node.smoothAdhesionVector.z)
119 weight += groundIvy * pow(1 - node.length *
120 prevIvyLength, 2)
122 # Find the alignment weight
123 alignmentWeight = node.adhesionLength
125 # Calculate the needed angles
126 phi = atan2(node.smoothAdhesionVector.y,
127 node.smoothAdhesionVector.x) - pi / 2.0
129 theta = (0.5 *
130 node.smoothAdhesionVector.angle(Vector((0, 0, -1)), 0))
132 # Find the size weight
133 sizeWeight = 1.5 - (cos(2 * pi * weight) * 0.5 + 0.5)
135 # Randomise the angles
136 phi += (rand_val() - 0.5) * (1.3 - alignmentWeight)
137 theta += (rand_val() - 0.5) * (1.1 - alignmentWeight)
139 # Calculate the leaf size an append the face to the list
140 leafSize = IVY.ivyLeafSize * sizeWeight
142 for j in range(10):
143 # Generate the probability
144 probability = rand_val()
146 # If we need to grow a leaf, do so
147 if (probability * weight) > IVY.leafProbability:
149 # Generate the random vector
150 randomVector = Vector((rand_val() - 0.5,
151 rand_val() - 0.5,
152 rand_val() - 0.5,
155 # Find the leaf center
156 center = (node.pos.lerp(nodeNext.pos, j / 10.0) +
157 IVY.ivyLeafSize * randomVector)
159 # For each of the verts, rotate/scale and append
160 basisVecX = Vector((1, 0, 0))
161 basisVecY = Vector((0, 1, 0))
163 horiRot = rotMat(theta, 3, 'X')
164 vertRot = rotMat(phi, 3, 'Z')
166 basisVecX.rotate(horiRot)
167 basisVecY.rotate(horiRot)
169 basisVecX.rotate(vertRot)
170 basisVecY.rotate(vertRot)
172 basisVecX *= leafSize
173 basisVecY *= leafSize
175 addV([k1 * basisVecX + k2 * basisVecY + center for
176 k1, k2 in signList])
178 # Add the object and link to scene
179 newCurve = bpy.data.objects.new("IVY_Curve", curve)
180 bpy.context.collection.objects.link(newCurve)
182 if growLeaves:
183 faceList = [[4 * i + l for l in range(4)] for i in
184 range(len(vertList) // 4)]
186 # Generate the new leaf mesh and link
187 me = bpy.data.meshes.new('IvyLeaf')
188 me.from_pydata(vertList, [], faceList)
189 me.update(calc_edges=True)
190 ob = bpy.data.objects.new('IvyLeaf', me)
191 bpy.context.collection.objects.link(ob)
193 me.uv_layers.new(name="Leaves")
195 # Set the uv texture coords
196 # TODO, this is non-functional, default uvs are ok?
198 for d in tex.data:
199 uv1, uv2, uv3, uv4 = signList
202 ob.parent = newCurve
205 class IvyNode:
206 """ The basic class used for each point on the ivy which is grown."""
207 __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
208 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')
210 def __init__(self):
211 self.pos = Vector((0, 0, 0))
212 self.primaryDir = Vector((0, 0, 1))
213 self.adhesionVector = Vector((0, 0, 0))
214 self.smoothAdhesionVector = Vector((0, 0, 0))
215 self.length = 0.0001
216 self.floatingLength = 0.0
217 self.climb = True
220 class IvyRoot:
221 """ The class used to hold all ivy nodes growing from this root point."""
222 __slots__ = ('ivyNodes', 'alive', 'parents')
224 def __init__(self):
225 self.ivyNodes = deque()
226 self.alive = True
227 self.parents = 0
230 class Ivy:
231 """ The class holding all parameters and ivy roots."""
232 __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight',
233 'gravityWeight', 'adhesionWeight', 'branchingProbability',
234 'leafProbability', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
235 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
237 def __init__(self,
238 primaryWeight=0.5,
239 randomWeight=0.2,
240 gravityWeight=1.0,
241 adhesionWeight=0.1,
242 branchingProbability=0.05,
243 leafProbability=0.35,
244 ivySize=0.02,
245 ivyLeafSize=0.02,
246 ivyBranchSize=0.001,
247 maxFloatLength=0.5,
248 maxAdhesionDistance=1.0):
250 self.ivyRoots = deque()
251 self.primaryWeight = primaryWeight
252 self.randomWeight = randomWeight
253 self.gravityWeight = gravityWeight
254 self.adhesionWeight = adhesionWeight
255 self.branchingProbability = 1 - branchingProbability
256 self.leafProbability = 1 - leafProbability
257 self.ivySize = ivySize
258 self.ivyLeafSize = ivyLeafSize
259 self.ivyBranchSize = ivyBranchSize
260 self.maxFloatLength = maxFloatLength
261 self.maxAdhesionDistance = maxAdhesionDistance
262 self.maxLength = 0.0
264 # Normalize all the weights only on initialisation
265 sums = self.primaryWeight + self.randomWeight + self.adhesionWeight
266 self.primaryWeight /= sums
267 self.randomWeight /= sums
268 self.adhesionWeight /= sums
270 def seed(self, seedPos):
271 # Seed the Ivy by making a new root and first node
272 tmpRoot = IvyRoot()
273 tmpIvy = IvyNode()
274 tmpIvy.pos = seedPos
276 tmpRoot.ivyNodes.append(tmpIvy)
277 self.ivyRoots.append(tmpRoot)
279 def grow(self, ob, bvhtree):
280 # Determine the local sizes
281 # local_ivySize = self.ivySize # * radius
282 # local_maxFloatLength = self.maxFloatLength # * radius
283 # local_maxAdhesionDistance = self.maxAdhesionDistance # * radius
285 for root in self.ivyRoots:
286 # Make sure the root is alive, if not, skip
287 if not root.alive:
288 continue
290 # Get the last node in the current root
291 prevIvy = root.ivyNodes[-1]
293 # If the node is floating for too long, kill the root
294 if prevIvy.floatingLength > self.maxFloatLength:
295 root.alive = False
297 # Set the primary direction from the last node
298 primaryVector = prevIvy.primaryDir
300 # Make the random vector and normalize
301 randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5,
302 rand_val() - 0.5)) + Vector((0, 0, 0.2))
303 randomVector.normalize()
305 # Calculate the adhesion vector
306 adhesionVector = adhesion(
307 prevIvy.pos, bvhtree, self.maxAdhesionDistance)
309 # Calculate the growing vector
310 growVector = self.ivySize * (primaryVector * self.primaryWeight +
311 randomVector * self.randomWeight +
312 adhesionVector * self.adhesionWeight)
314 # Find the gravity vector
315 gravityVector = (self.ivySize * self.gravityWeight *
316 Vector((0, 0, -1)))
317 gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength,
318 0.7)
320 # Determine the new position vector
321 newPos = prevIvy.pos + growVector + gravityVector
323 # Check for collisions with the object
324 climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)
326 # Update the growing vector for any collisions
327 growVector = newPos - prevIvy.pos - gravityVector
328 growVector.normalize()
330 # Create a new IvyNode and set its properties
331 tmpNode = IvyNode()
332 tmpNode.climb = climbing
333 tmpNode.pos = newPos
334 tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5)
335 tmpNode.primaryDir.normalize()
336 tmpNode.adhesionVector = adhesionVector
337 tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length
339 if tmpNode.length > self.maxLength:
340 self.maxLength = tmpNode.length
342 # If the node isn't climbing, update it's floating length
343 # Otherwise set it to 0
344 if not climbing:
345 tmpNode.floatingLength = prevIvy.floatingLength + (newPos -
346 prevIvy.pos).length
347 else:
348 tmpNode.floatingLength = 0.0
350 root.ivyNodes.append(tmpNode)
352 # Loop through all roots to check if a new root is generated
353 for root in self.ivyRoots:
354 # Check the root is alive and isn't at high level of recursion
355 if (root.parents > 3) or (not root.alive):
356 continue
358 # Check to make sure there's more than 1 node
359 if len(root.ivyNodes) > 1:
360 # Loop through all nodes in root to check if new root is grown
361 for node in root.ivyNodes:
362 # Set the last node of the root and find the weighting
363 prevIvy = root.ivyNodes[-1]
364 weight = 1.0 - (cos(2.0 * pi * node.length /
365 prevIvy.length) * 0.5 + 0.5)
367 probability = rand_val()
369 # Check if a new root is grown and if so, set its values
370 if (probability * weight > self.branchingProbability):
371 tmpNode = IvyNode()
372 tmpNode.pos = node.pos
373 tmpNode.floatingLength = node.floatingLength
375 tmpRoot = IvyRoot()
376 tmpRoot.parents = root.parents + 1
378 tmpRoot.ivyNodes.append(tmpNode)
379 self.ivyRoots.append(tmpRoot)
380 return
383 def adhesion(loc, bvhtree, max_l):
384 # Compute the adhesion vector by finding the nearest point
385 nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
386 adhesion_vector = Vector((0.0, 0.0, 0.0))
387 if nearest_location is not None:
388 # Compute the distance to the nearest point
389 adhesion_vector = nearest_location - loc
390 distance = adhesion_vector.length
391 # If it's less than the maximum allowed and not 0, continue
392 if distance:
393 # Compute the direction vector between the closest point and loc
394 adhesion_vector.normalize()
395 adhesion_vector *= 1.0 - distance / max_l
396 # adhesion_vector *= getFaceWeight(ob.data, nearest_result[3])
397 return adhesion_vector
400 def collision(bvhtree, pos, new_pos):
401 # Check for collision with the object
402 climbing = False
404 corrected_new_pos = new_pos
405 direction = new_pos - pos
407 hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
408 # If there's a collision we need to check it
409 if hit_location is not None:
410 # Check whether the collision is going into the object
411 if direction.dot(hit_normal) < 0.0:
412 reflected_direction = (new_pos - hit_location).reflect(hit_normal)
414 corrected_new_pos = hit_location + reflected_direction
415 climbing = True
417 return climbing, corrected_new_pos
420 def bvhtree_from_object(ob):
421 import bmesh
422 bm = bmesh.new()
424 depsgraph = bpy.context.evaluated_depsgraph_get()
425 ob_eval = ob.evaluated_get(depsgraph)
426 mesh = ob_eval.to_mesh()
427 bm.from_mesh(mesh)
428 bm.transform(ob.matrix_world)
430 bvhtree = BVHTree.FromBMesh(bm)
431 ob_eval.to_mesh_clear()
432 return bvhtree
434 def check_mesh_faces(ob):
435 me = ob.data
436 if len(me.polygons) > 0:
437 return True
439 return False
442 class IvyGen(Operator):
443 bl_idname = "curve.ivy_gen"
444 bl_label = "IvyGen"
445 bl_description = "Generate Ivy on an Mesh Object"
446 bl_options = {'REGISTER', 'UNDO'}
448 updateIvy: BoolProperty(
449 name="Update Ivy",
450 description="Update the Ivy location based on the cursor and Panel settings",
451 default=False
453 defaultIvy: BoolProperty(
454 name="Default Ivy",
455 options={"HIDDEN", "SKIP_SAVE"},
456 default=False
459 @classmethod
460 def poll(self, context):
461 # Check if there's an object and whether it's a mesh
462 ob = context.active_object
463 return ((ob is not None) and
464 (ob.type == 'MESH') and
465 (context.mode == 'OBJECT'))
467 def invoke(self, context, event):
468 self.updateIvy = True
469 return self.execute(context)
471 def execute(self, context):
472 # scene = context.scene
473 ivyProps = context.window_manager.ivy_gen_props
475 if not self.updateIvy:
476 return {'PASS_THROUGH'}
478 # assign the variables, check if it is default
479 # Note: update the values if window_manager props defaults are changed
480 randomSeed = ivyProps.randomSeed if not self.defaultIvy else 0
481 maxTime = ivyProps.maxTime if not self.defaultIvy else 0
482 maxIvyLength = ivyProps.maxIvyLength if not self.defaultIvy else 1.0
483 ivySize = ivyProps.ivySize if not self.defaultIvy else 0.02
484 maxFloatLength = ivyProps.maxFloatLength if not self.defaultIvy else 0.5
485 maxAdhesionDistance = ivyProps.maxAdhesionDistance if not self.defaultIvy else 1.0
486 primaryWeight = ivyProps.primaryWeight if not self.defaultIvy else 0.5
487 randomWeight = ivyProps.randomWeight if not self.defaultIvy else 0.2
488 gravityWeight = ivyProps.gravityWeight if not self.defaultIvy else 1.0
489 adhesionWeight = ivyProps.adhesionWeight if not self.defaultIvy else 0.1
490 branchingProbability = ivyProps.branchingProbability if not self.defaultIvy else 0.05
491 leafProbability = ivyProps.leafProbability if not self.defaultIvy else 0.35
492 ivyBranchSize = ivyProps.ivyBranchSize if not self.defaultIvy else 0.001
493 ivyLeafSize = ivyProps.ivyLeafSize if not self.defaultIvy else 0.02
494 growLeaves = ivyProps.growLeaves if not self.defaultIvy else True
496 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
497 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
499 # Get the selected object
500 ob = context.active_object
501 bvhtree = bvhtree_from_object(ob)
503 # Check if the mesh has at least one polygon since some functions
504 # are expecting them in the object's data (see T51753)
505 check_face = check_mesh_faces(ob)
506 if check_face is False:
507 self.report({'WARNING'},
508 "Mesh Object doesn't have at least one Face. "
509 "Operation Cancelled")
510 return {"CANCELLED"}
512 # Compute bounding sphere radius
513 # radius = computeBoundingSphere(ob) # Not needed anymore
515 # Get the seeding point
516 seedPoint = context.scene.cursor.location
518 # Fix the random seed
519 rand_seed(randomSeed)
521 # Make the new ivy
522 IVY = Ivy(
523 primaryWeight=primaryWeight,
524 randomWeight=randomWeight,
525 gravityWeight=gravityWeight,
526 adhesionWeight=adhesionWeight,
527 branchingProbability=branchingProbability,
528 leafProbability=leafProbability,
529 ivySize=ivySize,
530 ivyLeafSize=ivyLeafSize,
531 ivyBranchSize=ivyBranchSize,
532 maxFloatLength=maxFloatLength,
533 maxAdhesionDistance=maxAdhesionDistance
535 # Generate first root and node
536 IVY.seed(seedPoint)
538 checkTime = False
539 maxLength = maxIvyLength # * radius
541 # If we need to check time set the flag
542 if maxTime != 0.0:
543 checkTime = True
545 t = time.time()
546 startPercent = 0.0
547 checkAliveIter = [True, ]
549 # Grow until 200 roots is reached or backup counter exceeds limit
550 while (any(checkAliveIter) and
551 (IVY.maxLength < maxLength) and
552 (not checkTime or (time.time() - t < maxTime))):
553 # Grow the ivy for this iteration
554 IVY.grow(ob, bvhtree)
556 # Print the proportion of ivy growth to console
557 if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10:
558 print('%0.2f%% of Ivy nodes have grown' %
559 (IVY.maxLength / maxLength * 100))
560 startPercent += 10
561 if IVY.maxLength / maxLength > 1:
562 print("Halting Growth")
564 # Make an iterator to check if all are alive
565 checkAliveIter = (r.alive for r in IVY.ivyRoots)
567 # Create the curve and leaf geometry
568 createIvyGeometry(IVY, growLeaves)
569 print("Geometry Generation Complete")
571 print("Ivy generated in %0.2f s" % (time.time() - t))
573 self.updateIvy = False
574 self.defaultIvy = False
576 return {'FINISHED'}
578 def draw(self, context):
579 layout = self.layout
581 layout.prop(self, "updateIvy", icon="FILE_REFRESH")
584 class CURVE_PT_IvyGenPanel(Panel):
585 bl_label = "Ivy Generator"
586 bl_idname = "CURVE_PT_IvyGenPanel"
587 bl_space_type = "VIEW_3D"
588 bl_region_type = "UI"
589 bl_category = "Create"
590 bl_context = "objectmode"
591 bl_options = {"DEFAULT_CLOSED"}
593 def draw(self, context):
594 layout = self.layout
595 wm = context.window_manager
596 col = layout.column(align=True)
598 prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
599 prop_new.defaultIvy = False
600 prop_new.updateIvy = True
602 prop_def = col.operator("curve.ivy_gen", text="Add New Default Ivy", icon="CURVE_DATA")
603 prop_def.defaultIvy = True
604 prop_def.updateIvy = True
606 col = layout.column(align=True)
607 col.label(text="Generation Settings:")
608 col.prop(wm.ivy_gen_props, "randomSeed")
609 col.prop(wm.ivy_gen_props, "maxTime")
611 col = layout.column(align=True)
612 col.label(text="Size Settings:")
613 col.prop(wm.ivy_gen_props, "maxIvyLength")
614 col.prop(wm.ivy_gen_props, "ivySize")
615 col.prop(wm.ivy_gen_props, "maxFloatLength")
616 col.prop(wm.ivy_gen_props, "maxAdhesionDistance")
618 col = layout.column(align=True)
619 col.label(text="Weight Settings:")
620 col.prop(wm.ivy_gen_props, "primaryWeight")
621 col.prop(wm.ivy_gen_props, "randomWeight")
622 col.prop(wm.ivy_gen_props, "gravityWeight")
623 col.prop(wm.ivy_gen_props, "adhesionWeight")
625 col = layout.column(align=True)
626 col.label(text="Branch Settings:")
627 col.prop(wm.ivy_gen_props, "branchingProbability")
628 col.prop(wm.ivy_gen_props, "ivyBranchSize")
630 col = layout.column(align=True)
631 col.prop(wm.ivy_gen_props, "growLeaves")
633 if wm.ivy_gen_props.growLeaves:
634 col = layout.column(align=True)
635 col.label(text="Leaf Settings:")
636 col.prop(wm.ivy_gen_props, "ivyLeafSize")
637 col.prop(wm.ivy_gen_props, "leafProbability")
640 class IvyGenProperties(PropertyGroup):
641 maxIvyLength: FloatProperty(
642 name="Max Ivy Length",
643 description="Maximum ivy length in Blender Units",
644 default=1.0,
645 min=0.0,
646 soft_max=3.0,
647 subtype='DISTANCE',
648 unit='LENGTH'
650 primaryWeight: FloatProperty(
651 name="Primary Weight",
652 description="Weighting given to the current direction",
653 default=0.5,
654 min=0.0,
655 soft_max=1.0
657 randomWeight: FloatProperty(
658 name="Random Weight",
659 description="Weighting given to the random direction",
660 default=0.2,
661 min=0.0,
662 soft_max=1.0
664 gravityWeight: FloatProperty(
665 name="Gravity Weight",
666 description="Weighting given to the gravity direction",
667 default=1.0,
668 min=0.0,
669 soft_max=1.0
671 adhesionWeight: FloatProperty(
672 name="Adhesion Weight",
673 description="Weighting given to the adhesion direction",
674 default=0.1,
675 min=0.0,
676 soft_max=1.0
678 branchingProbability: FloatProperty(
679 name="Branching Probability",
680 description="Probability of a new branch forming",
681 default=0.05,
682 min=0.0,
683 soft_max=1.0
685 leafProbability: FloatProperty(
686 name="Leaf Probability",
687 description="Probability of a leaf forming",
688 default=0.35,
689 min=0.0,
690 soft_max=1.0
692 ivySize: FloatProperty(
693 name="Ivy Size",
694 description="The length of an ivy segment in Blender"
695 " Units",
696 default=0.02,
697 min=0.0,
698 soft_max=1.0,
699 precision=3
701 ivyLeafSize: FloatProperty(
702 name="Ivy Leaf Size",
703 description="The size of the ivy leaves",
704 default=0.02,
705 min=0.0,
706 soft_max=0.5,
707 precision=3
709 ivyBranchSize: FloatProperty(
710 name="Ivy Branch Size",
711 description="The size of the ivy branches",
712 default=0.001,
713 min=0.0,
714 soft_max=0.1,
715 precision=4
717 maxFloatLength: FloatProperty(
718 name="Max Float Length",
719 description="The maximum distance that a branch "
720 "can live while floating",
721 default=0.5,
722 min=0.0,
723 soft_max=1.0
725 maxAdhesionDistance: FloatProperty(
726 name="Max Adhesion Length",
727 description="The maximum distance that a branch "
728 "will feel the effects of adhesion",
729 default=1.0,
730 min=0.0,
731 soft_max=2.0,
732 precision=2
734 randomSeed: IntProperty(
735 name="Random Seed",
736 description="The seed governing random generation",
737 default=0,
738 min=0
740 maxTime: FloatProperty(
741 name="Maximum Time",
742 description="The maximum time to run the generation for "
743 "in seconds generation (0.0 = Disabled)",
744 default=0.0,
745 min=0.0,
746 soft_max=10
748 growLeaves: BoolProperty(
749 name="Grow Leaves",
750 description="Grow leaves or not",
751 default=True
755 classes = (
756 IvyGen,
757 IvyGenProperties,
758 CURVE_PT_IvyGenPanel
762 def register():
763 for cls in classes:
764 bpy.utils.register_class(cls)
766 bpy.types.WindowManager.ivy_gen_props = PointerProperty(
767 type=IvyGenProperties
771 def unregister():
772 del bpy.types.WindowManager.ivy_gen_props
774 for cls in reversed(classes):
775 bpy.utils.unregister_class(cls)
778 if __name__ == "__main__":
779 register()