glTF exporter: fix division by zero in some specular approximation
[blender-addons.git] / add_curve_ivygen.py
blobb00794e3ca5d69b2446200bd5eaaf99b43c79f22
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "IvyGen",
5 "author": "testscreenings, PKHG, TrumanBlending",
6 "version": (0, 1, 5),
7 "blender": (2, 80, 0),
8 "location": "View3D > Sidebar > Ivy Generator (Create Tab)",
9 "description": "Adds generated ivy to a mesh object starting "
10 "at the 3D cursor",
11 "warning": "",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html",
13 "category": "Add Curve",
17 import bpy
18 from bpy.types import (
19 Operator,
20 Panel,
21 PropertyGroup,
23 from bpy.props import (
24 BoolProperty,
25 FloatProperty,
26 IntProperty,
27 PointerProperty,
29 from mathutils.bvhtree import BVHTree
30 from mathutils import (
31 Vector,
32 Matrix,
34 from collections import deque
35 from math import (
36 pow, cos,
37 pi, atan2,
39 from random import (
40 random as rand_val,
41 seed as rand_seed,
43 import time
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 initialise it
53 curve = bpy.data.curves.new("IVY", type='CURVE')
54 curve.dimensions = '3D'
55 curve.bevel_depth = 1
56 curve.fill_mode = 'FULL'
57 curve.resolution_u = 4
59 if growLeaves:
60 # Create the ivy leaves
61 # Order location of the vertices
62 signList = ((-1.0, +1.0),
63 (+1.0, +1.0),
64 (+1.0, -1.0),
65 (-1.0, -1.0),
68 # Get the local size
69 # local_ivyLeafSize = IVY.ivyLeafSize # * radius * IVY.ivySize
71 # Initialise the vertex and face lists
72 vertList = deque()
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)
82 if numNodes > 1:
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 *
118 prevIvyLength, 2)
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
127 theta = (0.5 *
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
140 for j in range(10):
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,
149 rand_val() - 0.5,
150 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
174 k1, k2 in signList])
176 # Add the object and link to scene
177 newCurve = bpy.data.objects.new("IVY_Curve", curve)
178 bpy.context.collection.objects.link(newCurve)
180 if growLeaves:
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.collection.objects.link(ob)
191 me.uv_layers.new(name="Leaves")
193 # Set the uv texture coords
194 # TODO, this is non-functional, default uvs are ok?
196 for d in tex.data:
197 uv1, uv2, uv3, uv4 = signList
200 ob.parent = newCurve
203 class IvyNode:
204 """ The basic class used for each point on the ivy which is grown."""
205 __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
206 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')
208 def __init__(self):
209 self.pos = Vector((0, 0, 0))
210 self.primaryDir = Vector((0, 0, 1))
211 self.adhesionVector = Vector((0, 0, 0))
212 self.smoothAdhesionVector = Vector((0, 0, 0))
213 self.length = 0.0001
214 self.floatingLength = 0.0
215 self.climb = True
218 class IvyRoot:
219 """ The class used to hold all ivy nodes growing from this root point."""
220 __slots__ = ('ivyNodes', 'alive', 'parents')
222 def __init__(self):
223 self.ivyNodes = deque()
224 self.alive = True
225 self.parents = 0
228 class Ivy:
229 """ The class holding all parameters and ivy roots."""
230 __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight',
231 'gravityWeight', 'adhesionWeight', 'branchingProbability',
232 'leafProbability', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
233 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
235 def __init__(self,
236 primaryWeight=0.5,
237 randomWeight=0.2,
238 gravityWeight=1.0,
239 adhesionWeight=0.1,
240 branchingProbability=0.05,
241 leafProbability=0.35,
242 ivySize=0.02,
243 ivyLeafSize=0.02,
244 ivyBranchSize=0.001,
245 maxFloatLength=0.5,
246 maxAdhesionDistance=1.0):
248 self.ivyRoots = deque()
249 self.primaryWeight = primaryWeight
250 self.randomWeight = randomWeight
251 self.gravityWeight = gravityWeight
252 self.adhesionWeight = adhesionWeight
253 self.branchingProbability = 1 - branchingProbability
254 self.leafProbability = 1 - leafProbability
255 self.ivySize = ivySize
256 self.ivyLeafSize = ivyLeafSize
257 self.ivyBranchSize = ivyBranchSize
258 self.maxFloatLength = maxFloatLength
259 self.maxAdhesionDistance = maxAdhesionDistance
260 self.maxLength = 0.0
262 # Normalize all the weights only on initialisation
263 sums = self.primaryWeight + self.randomWeight + self.adhesionWeight
264 self.primaryWeight /= sums
265 self.randomWeight /= sums
266 self.adhesionWeight /= sums
268 def seed(self, seedPos):
269 # Seed the Ivy by making a new root and first node
270 tmpRoot = IvyRoot()
271 tmpIvy = IvyNode()
272 tmpIvy.pos = seedPos
274 tmpRoot.ivyNodes.append(tmpIvy)
275 self.ivyRoots.append(tmpRoot)
277 def grow(self, ob, bvhtree):
278 # Determine the local sizes
279 # local_ivySize = self.ivySize # * radius
280 # local_maxFloatLength = self.maxFloatLength # * radius
281 # local_maxAdhesionDistance = self.maxAdhesionDistance # * radius
283 for root in self.ivyRoots:
284 # Make sure the root is alive, if not, skip
285 if not root.alive:
286 continue
288 # Get the last node in the current root
289 prevIvy = root.ivyNodes[-1]
291 # If the node is floating for too long, kill the root
292 if prevIvy.floatingLength > self.maxFloatLength:
293 root.alive = False
295 # Set the primary direction from the last node
296 primaryVector = prevIvy.primaryDir
298 # Make the random vector and normalize
299 randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5,
300 rand_val() - 0.5)) + Vector((0, 0, 0.2))
301 randomVector.normalize()
303 # Calculate the adhesion vector
304 adhesionVector = adhesion(
305 prevIvy.pos, bvhtree, self.maxAdhesionDistance)
307 # Calculate the growing vector
308 growVector = self.ivySize * (primaryVector * self.primaryWeight +
309 randomVector * self.randomWeight +
310 adhesionVector * self.adhesionWeight)
312 # Find the gravity vector
313 gravityVector = (self.ivySize * self.gravityWeight *
314 Vector((0, 0, -1)))
315 gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength,
316 0.7)
318 # Determine the new position vector
319 newPos = prevIvy.pos + growVector + gravityVector
321 # Check for collisions with the object
322 climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)
324 # Update the growing vector for any collisions
325 growVector = newPos - prevIvy.pos - gravityVector
326 growVector.normalize()
328 # Create a new IvyNode and set its properties
329 tmpNode = IvyNode()
330 tmpNode.climb = climbing
331 tmpNode.pos = newPos
332 tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5)
333 tmpNode.primaryDir.normalize()
334 tmpNode.adhesionVector = adhesionVector
335 tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length
337 if tmpNode.length > self.maxLength:
338 self.maxLength = tmpNode.length
340 # If the node isn't climbing, update it's floating length
341 # Otherwise set it to 0
342 if not climbing:
343 tmpNode.floatingLength = prevIvy.floatingLength + (newPos -
344 prevIvy.pos).length
345 else:
346 tmpNode.floatingLength = 0.0
348 root.ivyNodes.append(tmpNode)
350 # Loop through all roots to check if a new root is generated
351 for root in self.ivyRoots:
352 # Check the root is alive and isn't at high level of recursion
353 if (root.parents > 3) or (not root.alive):
354 continue
356 # Check to make sure there's more than 1 node
357 if len(root.ivyNodes) > 1:
358 # Loop through all nodes in root to check if new root is grown
359 for node in root.ivyNodes:
360 # Set the last node of the root and find the weighting
361 prevIvy = root.ivyNodes[-1]
362 weight = 1.0 - (cos(2.0 * pi * node.length /
363 prevIvy.length) * 0.5 + 0.5)
365 probability = rand_val()
367 # Check if a new root is grown and if so, set its values
368 if (probability * weight > self.branchingProbability):
369 tmpNode = IvyNode()
370 tmpNode.pos = node.pos
371 tmpNode.floatingLength = node.floatingLength
373 tmpRoot = IvyRoot()
374 tmpRoot.parents = root.parents + 1
376 tmpRoot.ivyNodes.append(tmpNode)
377 self.ivyRoots.append(tmpRoot)
378 return
381 def adhesion(loc, bvhtree, max_l):
382 # Compute the adhesion vector by finding the nearest point
383 nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
384 adhesion_vector = Vector((0.0, 0.0, 0.0))
385 if nearest_location is not None:
386 # Compute the distance to the nearest point
387 adhesion_vector = nearest_location - loc
388 distance = adhesion_vector.length
389 # If it's less than the maximum allowed and not 0, continue
390 if distance:
391 # Compute the direction vector between the closest point and loc
392 adhesion_vector.normalize()
393 adhesion_vector *= 1.0 - distance / max_l
394 # adhesion_vector *= getFaceWeight(ob.data, nearest_result[3])
395 return adhesion_vector
398 def collision(bvhtree, pos, new_pos):
399 # Check for collision with the object
400 climbing = False
402 corrected_new_pos = new_pos
403 direction = new_pos - pos
405 hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
406 # If there's a collision we need to check it
407 if hit_location is not None:
408 # Check whether the collision is going into the object
409 if direction.dot(hit_normal) < 0.0:
410 reflected_direction = (new_pos - hit_location).reflect(hit_normal)
412 corrected_new_pos = hit_location + reflected_direction
413 climbing = True
415 return climbing, corrected_new_pos
418 def bvhtree_from_object(ob):
419 import bmesh
420 bm = bmesh.new()
422 depsgraph = bpy.context.evaluated_depsgraph_get()
423 ob_eval = ob.evaluated_get(depsgraph)
424 mesh = ob_eval.to_mesh()
425 bm.from_mesh(mesh)
426 bm.transform(ob.matrix_world)
428 bvhtree = BVHTree.FromBMesh(bm)
429 ob_eval.to_mesh_clear()
430 return bvhtree
432 def check_mesh_faces(ob):
433 me = ob.data
434 if len(me.polygons) > 0:
435 return True
437 return False
440 class IvyGen(Operator):
441 bl_idname = "curve.ivy_gen"
442 bl_label = "IvyGen"
443 bl_description = "Generate Ivy on an Mesh Object"
444 bl_options = {'REGISTER', 'UNDO'}
446 updateIvy: BoolProperty(
447 name="Update Ivy",
448 description="Update the Ivy location based on the cursor and Panel settings",
449 default=False
451 defaultIvy: BoolProperty(
452 name="Default Ivy",
453 options={"HIDDEN", "SKIP_SAVE"},
454 default=False
457 @classmethod
458 def poll(self, context):
459 # Check if there's an object and whether it's a mesh
460 ob = context.active_object
461 return ((ob is not None) and
462 (ob.type == 'MESH') and
463 (context.mode == 'OBJECT'))
465 def invoke(self, context, event):
466 self.updateIvy = True
467 return self.execute(context)
469 def execute(self, context):
470 # scene = context.scene
471 ivyProps = context.window_manager.ivy_gen_props
473 if not self.updateIvy:
474 return {'PASS_THROUGH'}
476 # assign the variables, check if it is default
477 # Note: update the values if window_manager props defaults are changed
478 randomSeed = ivyProps.randomSeed if not self.defaultIvy else 0
479 maxTime = ivyProps.maxTime if not self.defaultIvy else 0
480 maxIvyLength = ivyProps.maxIvyLength if not self.defaultIvy else 1.0
481 ivySize = ivyProps.ivySize if not self.defaultIvy else 0.02
482 maxFloatLength = ivyProps.maxFloatLength if not self.defaultIvy else 0.5
483 maxAdhesionDistance = ivyProps.maxAdhesionDistance if not self.defaultIvy else 1.0
484 primaryWeight = ivyProps.primaryWeight if not self.defaultIvy else 0.5
485 randomWeight = ivyProps.randomWeight if not self.defaultIvy else 0.2
486 gravityWeight = ivyProps.gravityWeight if not self.defaultIvy else 1.0
487 adhesionWeight = ivyProps.adhesionWeight if not self.defaultIvy else 0.1
488 branchingProbability = ivyProps.branchingProbability if not self.defaultIvy else 0.05
489 leafProbability = ivyProps.leafProbability if not self.defaultIvy else 0.35
490 ivyBranchSize = ivyProps.ivyBranchSize if not self.defaultIvy else 0.001
491 ivyLeafSize = ivyProps.ivyLeafSize if not self.defaultIvy else 0.02
492 growLeaves = ivyProps.growLeaves if not self.defaultIvy else True
494 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
495 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
497 # Get the selected object
498 ob = context.active_object
499 bvhtree = bvhtree_from_object(ob)
501 # Check if the mesh has at least one polygon since some functions
502 # are expecting them in the object's data (see T51753)
503 check_face = check_mesh_faces(ob)
504 if check_face is False:
505 self.report({'WARNING'},
506 "Mesh Object doesn't have at least one Face. "
507 "Operation Cancelled")
508 return {"CANCELLED"}
510 # Compute bounding sphere radius
511 # radius = computeBoundingSphere(ob) # Not needed anymore
513 # Get the seeding point
514 seedPoint = context.scene.cursor.location
516 # Fix the random seed
517 rand_seed(randomSeed)
519 # Make the new ivy
520 IVY = Ivy(
521 primaryWeight=primaryWeight,
522 randomWeight=randomWeight,
523 gravityWeight=gravityWeight,
524 adhesionWeight=adhesionWeight,
525 branchingProbability=branchingProbability,
526 leafProbability=leafProbability,
527 ivySize=ivySize,
528 ivyLeafSize=ivyLeafSize,
529 ivyBranchSize=ivyBranchSize,
530 maxFloatLength=maxFloatLength,
531 maxAdhesionDistance=maxAdhesionDistance
533 # Generate first root and node
534 IVY.seed(seedPoint)
536 checkTime = False
537 maxLength = maxIvyLength # * radius
539 # If we need to check time set the flag
540 if maxTime != 0.0:
541 checkTime = True
543 t = time.time()
544 startPercent = 0.0
545 checkAliveIter = [True, ]
547 # Grow until 200 roots is reached or backup counter exceeds limit
548 while (any(checkAliveIter) and
549 (IVY.maxLength < maxLength) and
550 (not checkTime or (time.time() - t < maxTime))):
551 # Grow the ivy for this iteration
552 IVY.grow(ob, bvhtree)
554 # Print the proportion of ivy growth to console
555 if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10:
556 print('%0.2f%% of Ivy nodes have grown' %
557 (IVY.maxLength / maxLength * 100))
558 startPercent += 10
559 if IVY.maxLength / maxLength > 1:
560 print("Halting Growth")
562 # Make an iterator to check if all are alive
563 checkAliveIter = (r.alive for r in IVY.ivyRoots)
565 # Create the curve and leaf geometry
566 createIvyGeometry(IVY, growLeaves)
567 print("Geometry Generation Complete")
569 print("Ivy generated in %0.2f s" % (time.time() - t))
571 self.updateIvy = False
572 self.defaultIvy = False
574 return {'FINISHED'}
576 def draw(self, context):
577 layout = self.layout
579 layout.prop(self, "updateIvy", icon="FILE_REFRESH")
582 class CURVE_PT_IvyGenPanel(Panel):
583 bl_label = "Ivy Generator"
584 bl_idname = "CURVE_PT_IvyGenPanel"
585 bl_space_type = "VIEW_3D"
586 bl_region_type = "UI"
587 bl_category = "Create"
588 bl_context = "objectmode"
589 bl_options = {"DEFAULT_CLOSED"}
591 def draw(self, context):
592 layout = self.layout
593 wm = context.window_manager
594 col = layout.column(align=True)
596 prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
597 prop_new.defaultIvy = False
598 prop_new.updateIvy = True
600 prop_def = col.operator("curve.ivy_gen", text="Add New Default Ivy", icon="CURVE_DATA")
601 prop_def.defaultIvy = True
602 prop_def.updateIvy = True
604 col = layout.column(align=True)
605 col.label(text="Generation Settings:")
606 col.prop(wm.ivy_gen_props, "randomSeed")
607 col.prop(wm.ivy_gen_props, "maxTime")
609 col = layout.column(align=True)
610 col.label(text="Size Settings:")
611 col.prop(wm.ivy_gen_props, "maxIvyLength")
612 col.prop(wm.ivy_gen_props, "ivySize")
613 col.prop(wm.ivy_gen_props, "maxFloatLength")
614 col.prop(wm.ivy_gen_props, "maxAdhesionDistance")
616 col = layout.column(align=True)
617 col.label(text="Weight Settings:")
618 col.prop(wm.ivy_gen_props, "primaryWeight")
619 col.prop(wm.ivy_gen_props, "randomWeight")
620 col.prop(wm.ivy_gen_props, "gravityWeight")
621 col.prop(wm.ivy_gen_props, "adhesionWeight")
623 col = layout.column(align=True)
624 col.label(text="Branch Settings:")
625 col.prop(wm.ivy_gen_props, "branchingProbability")
626 col.prop(wm.ivy_gen_props, "ivyBranchSize")
628 col = layout.column(align=True)
629 col.prop(wm.ivy_gen_props, "growLeaves")
631 if wm.ivy_gen_props.growLeaves:
632 col = layout.column(align=True)
633 col.label(text="Leaf Settings:")
634 col.prop(wm.ivy_gen_props, "ivyLeafSize")
635 col.prop(wm.ivy_gen_props, "leafProbability")
638 class IvyGenProperties(PropertyGroup):
639 maxIvyLength: FloatProperty(
640 name="Max Ivy Length",
641 description="Maximum ivy length in Blender Units",
642 default=1.0,
643 min=0.0,
644 soft_max=3.0,
645 subtype='DISTANCE',
646 unit='LENGTH'
648 primaryWeight: FloatProperty(
649 name="Primary Weight",
650 description="Weighting given to the current direction",
651 default=0.5,
652 min=0.0,
653 soft_max=1.0
655 randomWeight: FloatProperty(
656 name="Random Weight",
657 description="Weighting given to the random direction",
658 default=0.2,
659 min=0.0,
660 soft_max=1.0
662 gravityWeight: FloatProperty(
663 name="Gravity Weight",
664 description="Weighting given to the gravity direction",
665 default=1.0,
666 min=0.0,
667 soft_max=1.0
669 adhesionWeight: FloatProperty(
670 name="Adhesion Weight",
671 description="Weighting given to the adhesion direction",
672 default=0.1,
673 min=0.0,
674 soft_max=1.0
676 branchingProbability: FloatProperty(
677 name="Branching Probability",
678 description="Probability of a new branch forming",
679 default=0.05,
680 min=0.0,
681 soft_max=1.0
683 leafProbability: FloatProperty(
684 name="Leaf Probability",
685 description="Probability of a leaf forming",
686 default=0.35,
687 min=0.0,
688 soft_max=1.0
690 ivySize: FloatProperty(
691 name="Ivy Size",
692 description="The length of an ivy segment in Blender"
693 " Units",
694 default=0.02,
695 min=0.0,
696 soft_max=1.0,
697 precision=3
699 ivyLeafSize: FloatProperty(
700 name="Ivy Leaf Size",
701 description="The size of the ivy leaves",
702 default=0.02,
703 min=0.0,
704 soft_max=0.5,
705 precision=3
707 ivyBranchSize: FloatProperty(
708 name="Ivy Branch Size",
709 description="The size of the ivy branches",
710 default=0.001,
711 min=0.0,
712 soft_max=0.1,
713 precision=4
715 maxFloatLength: FloatProperty(
716 name="Max Float Length",
717 description="The maximum distance that a branch "
718 "can live while floating",
719 default=0.5,
720 min=0.0,
721 soft_max=1.0
723 maxAdhesionDistance: FloatProperty(
724 name="Max Adhesion Length",
725 description="The maximum distance that a branch "
726 "will feel the effects of adhesion",
727 default=1.0,
728 min=0.0,
729 soft_max=2.0,
730 precision=2
732 randomSeed: IntProperty(
733 name="Random Seed",
734 description="The seed governing random generation",
735 default=0,
736 min=0
738 maxTime: FloatProperty(
739 name="Maximum Time",
740 description="The maximum time to run the generation for "
741 "in seconds generation (0.0 = Disabled)",
742 default=0.0,
743 min=0.0,
744 soft_max=10
746 growLeaves: BoolProperty(
747 name="Grow Leaves",
748 description="Grow leaves or not",
749 default=True
753 classes = (
754 IvyGen,
755 IvyGenProperties,
756 CURVE_PT_IvyGenPanel
760 def register():
761 for cls in classes:
762 bpy.utils.register_class(cls)
764 bpy.types.WindowManager.ivy_gen_props = PointerProperty(
765 type=IvyGenProperties
769 def unregister():
770 del bpy.types.WindowManager.ivy_gen_props
772 for cls in reversed(classes):
773 bpy.utils.unregister_class(cls)
776 if __name__ == "__main__":
777 register()