From 09e37c1f7048648952f3445b2e2e12445a653c8b Mon Sep 17 00:00:00 2001 From: Andrew Hale Date: Sat, 30 Jul 2011 09:59:19 +0000 Subject: Moved to Trunk [[Split portion of a mixed commit.]] --- add_curve_ivygen.py | 687 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 add_curve_ivygen.py diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py new file mode 100644 index 00000000..d46503b1 --- /dev/null +++ b/add_curve_ivygen.py @@ -0,0 +1,687 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +bl_info = { + "name": "IvyGen", + "author": "testscreenings, PKHG, TrumanBlending", + "version": (0, 1, 0), + "blender": (2, 5, 8), + "api": 38479, + "location": "View3D > Add > Curve", + "description": "Adds generated ivy to a mesh object starting at the 3D"\ + " cursor.", + "warning": "", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\ + "Scripts/Curve/Ivy_Gen", + "tracker_url": "http://projects.blender.org/tracker/index.php?"\ + "func=detail&aid=27234", + "category": "Add Curve"} + + +import bpy +from bpy.props import FloatProperty, IntProperty, BoolProperty +from mathutils import Vector, Matrix +from collections import deque +from math import pow, cos, pi, atan2 +from random import random as rand_val, seed as rand_seed +import time + + +def createIvyGeometry(IVY, growLeaves): + '''Create the curve geometry for IVY''' + # Compute the local size and the gauss weight filter + #local_ivyBranchSize = IVY.ivyBranchSize # * radius * IVY.ivySize + gaussWeight = [1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0] + + # Create a new curve and intialise it + curve = bpy.data.curves.new("IVY", type='CURVE') + curve.dimensions = '3D' + curve.bevel_depth = 1 + curve.use_fill_front = curve.use_fill_back = False + + if growLeaves: + # Create the ivy leaves + # Order location of the vertices + signList = [(-1, 1), (1, 1), (1, -1), (-1, -1)] + + # Get the local size + #local_ivyLeafSize = IVY.ivyLeafSize # * radius * IVY.ivySize + + # Initialise the vertex and face lists + vertList = deque() + + # Store the methods for faster calling + addV = vertList.extend + rotMat = Matrix.Rotation + + # Loop over all roots to generate its nodes + for root in IVY.ivyRoots: + # Only grow if more than one node + numNodes = len(root.ivyNodes) + if numNodes > 1: + # Calculate the local radius + local_ivyBranchRadius = 1 / (root.parents + 1) + 1 + prevIvyLength = 1 / root.ivyNodes[-1].length + splineVerts = [ax for n in root.ivyNodes for ax in n.pos.to_4d()] + + radiusConstant = local_ivyBranchRadius * IVY.ivyBranchSize + splineRadii = [radiusConstant * (1.3 - n.length * prevIvyLength) + for n in root.ivyNodes] + + # Add the poly curve and set coords and radii + newSpline = curve.splines.new(type='POLY') + newSpline.points.add(len(splineVerts) // 4 - 1) + newSpline.points.foreach_set('co', splineVerts) + newSpline.points.foreach_set('radius', splineRadii) + + # Loop over all nodes in the root + for i, n in enumerate(root.ivyNodes): + for k in range(len(gaussWeight)): + idx = max(0, min(i + k - 5, numNodes - 1)) + n.smoothAdhesionVector += (gaussWeight[k] * + root.ivyNodes[idx].adhesionVector) + n.smoothAdhesionVector /= 56.0 + n.adhesionLength = n.smoothAdhesionVector.length + n.smoothAdhesionVector.normalize() + + if growLeaves and (i < numNodes - 1): + node = root.ivyNodes[i] + nodeNext = root.ivyNodes[i + 1] + + # Find the weight and normalise the smooth adhesion vector + weight = pow(node.length * prevIvyLength, 0.7) + + # Calculate the ground ivy and the new weight + groundIvy = max(0.0, -node.smoothAdhesionVector.z) + weight += groundIvy * pow(1 - node.length * + prevIvyLength, 2) + + # Find the alignment weight + alignmentWeight = node.adhesionLength + + # Calculate the needed angles + phi = atan2(node.smoothAdhesionVector.y, + node.smoothAdhesionVector.x) - pi / 2 + + theta = (0.5 * + node.smoothAdhesionVector.angle(Vector((0, 0, -1)), 0)) + + # Find the size weight + sizeWeight = 1.5 - (cos(2 * pi * weight) * 0.5 + 0.5) + + # Randomise the angles + phi += (rand_val() - 0.5) * (1.3 - alignmentWeight) + theta += (rand_val() - 0.5) * (1.1 - alignmentWeight) + + # Calculate the leaf size an append the face to the list + leafSize = IVY.ivyLeafSize * sizeWeight + + for j in range(10): + # Generate the probability + probability = rand_val() + + # If we need to grow a leaf, do so + if (probability * weight) > IVY.leafProbability: + + # Generate the random vector + randomVector = Vector((rand_val() - 0.5, + rand_val() - 0.5, rand_val() - 0.5)) + + # Find the leaf center + center = node.pos.lerp(nodeNext.pos, j / 10) +\ + IVY.ivyLeafSize * randomVector + + # For each of the verts, rotate/scale and append + basisVecX = Vector((1, 0, 0)) + basisVecY = Vector((0, 1, 0)) + + horiRot = rotMat(theta, 3, 'X') + vertRot = rotMat(phi, 3, 'Z') + + basisVecX.rotate(horiRot) + basisVecY.rotate(horiRot) + + basisVecX.rotate(vertRot) + basisVecY.rotate(vertRot) + + basisVecX *= leafSize + basisVecY *= leafSize + + addV([k1 * basisVecX + k2 * basisVecY + center for + k1, k2 in signList]) + + # Add the object and link to scene + newCurve = bpy.data.objects.new("IVY_Curve", curve) + bpy.context.scene.objects.link(newCurve) + + if growLeaves: + faceList = [[4 * i + l for l in range(4)] for i in + range(len(vertList) // 4)] + + # Generate the new leaf mesh and link + me = bpy.data.meshes.new('IvyLeaf') + me.from_pydata(vertList, [], faceList) + me.update(calc_edges=True) + ob = bpy.data.objects.new('IvyLeaf', me) + bpy.context.scene.objects.link(ob) + + tex = me.uv_textures.new("Leaves") + + # Set the uv texture coords + for d in tex.data: + uv1, uv2, uv3, uv4 = signList + + ob.parent = newCurve + + +def computeBoundingSphere(ob): + # Get the mesh data + me = ob.data + # Intialise the center + center = Vector((0, 0, 0)) + # Add all vertex coords + for v in me.vertices: + center += v.co + # Average over all verts + center /= len(me.vertices) + # Create the iterator and find its max + length_iter = ((center - v.co).length for v in me.vertices) + radius = max(length_iter) + return radius + + +class IvyNode: + """ The basic class used for each point on the ivy which is grown.""" + __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength', + 'smoothAdhesionVector', 'length', 'floatingLength', 'climb') + + def __init__(self): + self.pos = Vector((0, 0, 0)) + self.primaryDir = Vector((0, 0, 1)) + self.adhesionVector = Vector((0, 0, 0)) + self.smoothAdhesionVector = Vector((0, 0, 0)) + self.length = 0.0001 + self.floatingLength = 0.0 + self.climb = True + + +class IvyRoot: + """ The class used to hold all ivy nodes growing from this root point.""" + __slots__ = ('ivyNodes', 'alive', 'parents') + + def __init__(self): + self.ivyNodes = deque() + self.alive = True + self.parents = 0 + + +class Ivy: + """ The class holding all parameters and ivy roots.""" + __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight', + 'gravityWeight', 'adhesionWeight', 'branchingProbability', + 'leafProbability', 'ivySize', 'ivyLeafSize', 'ivyBranchSize', + 'maxFloatLength', 'maxAdhesionDistance', 'maxLength') + + def __init__(self, + primaryWeight=0.5, + randomWeight=0.2, + gravityWeight=1.0, + adhesionWeight=0.1, + branchingProbability=0.05, + leafProbability=0.35, + ivySize=0.02, + ivyLeafSize=0.02, + ivyBranchSize=0.001, + maxFloatLength=0.5, + maxAdhesionDistance=1.0): + + self.ivyRoots = deque() + self.primaryWeight = primaryWeight + self.randomWeight = randomWeight + self.gravityWeight = gravityWeight + self.adhesionWeight = adhesionWeight + self.branchingProbability = 1 - branchingProbability + self.leafProbability = 1 - leafProbability + self.ivySize = ivySize + self.ivyLeafSize = ivyLeafSize + self.ivyBranchSize = ivyBranchSize + self.maxFloatLength = maxFloatLength + self.maxAdhesionDistance = maxAdhesionDistance + self.maxLength = 0.0 + + # Normalise all the weights only on intialisation + sum = self.primaryWeight + self.randomWeight + self.adhesionWeight + self.primaryWeight /= sum + self.randomWeight /= sum + self.adhesionWeight /= sum + + def seed(self, seedPos): + # Seed the Ivy by making a new root and first node + tmpRoot = IvyRoot() + tmpIvy = IvyNode() + tmpIvy.pos = seedPos + + tmpRoot.ivyNodes.append(tmpIvy) + self.ivyRoots.append(tmpRoot) + + def grow(self, ob): + # Determine the local sizes + #local_ivySize = self.ivySize # * radius + #local_maxFloatLength = self.maxFloatLength # * radius + #local_maxAdhesionDistance = self.maxAdhesionDistance # * radius + + for root in self.ivyRoots: + # Make sure the root is alive, if not, skip + if not root.alive: + continue + + # Get the last node in the current root + prevIvy = root.ivyNodes[-1] + + # If the node is floating for too long, kill the root + if prevIvy.floatingLength > self.maxFloatLength: + root.alive = False + + # Set the primary direction from the last node + primaryVector = prevIvy.primaryDir + + # Make the random vector and normalise + randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5, + rand_val() - 0.5)) + Vector((0, 0, 0.2)) + randomVector.normalize() + + # Calculate the adhesion vector + adhesionVector = adhesion(prevIvy.pos, ob, + self.maxAdhesionDistance) + + # Calculate the growing vector + growVector = self.ivySize * (primaryVector * self.primaryWeight + + randomVector * self.randomWeight + + adhesionVector * self.adhesionWeight) + + # Find the gravity vector + gravityVector = (self.ivySize * self.gravityWeight * + Vector((0, 0, -1))) + gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength, + 0.7) + + # Determine the new position vector + newPos = prevIvy.pos + growVector + gravityVector + + # Check for collisions with the object + climbing = collision(ob, prevIvy.pos, newPos) + + # Update the growing vector for any collisions + growVector = newPos - prevIvy.pos - gravityVector + growVector.normalize() + + # Create a new IvyNode and set its properties + tmpNode = IvyNode() + tmpNode.climb = climbing + tmpNode.pos = newPos + tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5) + tmpNode.primaryDir.normalize() + tmpNode.adhesionVector = adhesionVector + tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length + + if tmpNode.length > self.maxLength: + self.maxLength = tmpNode.length + + # If the node isn't climbing, update it's floating length + # Otherwise set it to 0 + if not climbing: + tmpNode.floatingLength = prevIvy.floatingLength + (newPos - + prevIvy.pos).length + else: + tmpNode.floatingLength = 0.0 + + root.ivyNodes.append(tmpNode) + + # Loop through all roots to check if a new root is generated + for root in self.ivyRoots: + # Check the root is alive and isn't at high level of recursion + if (root.parents > 3) or (not root.alive): + continue + + # Check to make sure there's more than 1 node + if len(root.ivyNodes) > 1: + # Loop through all nodes in root to check if new root is grown + for node in root.ivyNodes: + # Set the last node of the root and find the weighting + prevIvy = root.ivyNodes[-1] + weight = 1.0 - (cos(2.0 * pi * node.length / + prevIvy.length) * 0.5 + 0.5) + + probability = rand_val() + + # Check if a new root is grown and if so, set its values + if (probability * weight > self.branchingProbability): + tmpNode = IvyNode() + tmpNode.pos = node.pos + tmpNode.floatingLength = node.floatingLength + + tmpRoot = IvyRoot() + tmpRoot.parents = root.parents + 1 + + tmpRoot.ivyNodes.append(tmpNode) + self.ivyRoots.append(tmpRoot) + return + + +def adhesion(loc, ob, max_l): + # Get transfor vector and transformed loc + tran_mat = ob.matrix_world.inverted() + tran_loc = tran_mat * loc + + # Compute the adhesion vector by finding the nearest point + nearest_result = ob.closest_point_on_mesh(tran_loc, max_l) + adhesion_vector = Vector((0, 0, 0)) + if nearest_result[2] != -1: + # Compute the distance to the nearest point + adhesion_vector = ob.matrix_world * nearest_result[0] - loc + distance = adhesion_vector.length + # If it's less than the maximum allowed and not 0, continue + if distance: + # Compute the direction vector between the closest point and loc + adhesion_vector.normalize() + adhesion_vector *= 1.0 - distance / max_l + #adhesion_vector *= getFaceWeight(ob.data, nearest_result[2]) + return adhesion_vector + + +def collision(ob, pos, new_pos): + # Check for collision with the object + climbing = False + + # Transform vecs + tran_mat = ob.matrix_world.inverted() + tran_pos = tran_mat * pos + tran_new_pos = tran_mat * new_pos + + ray_result = ob.ray_cast(tran_pos, tran_new_pos) + # If there's a collision we need to check it + if ray_result[2] != -1: + # Check whether the collision is going into the object + if (tran_new_pos - tran_pos).dot(ray_result[1]) < 0.0: + # Find projection of the piont onto the plane + p0 = tran_new_pos - (tran_new_pos - + ray_result[0]).project(ray_result[1]) + # Reflect in the plane + tran_new_pos += 2 * (p0 - tran_new_pos) + new_pos *= 0 + new_pos += ob.matrix_world * tran_new_pos + climbing = True + return climbing + + +class IvyGen(bpy.types.Operator): + bl_idname = "curve.ivy_gen" + bl_label = "IvyGen" + bl_options = {'REGISTER', 'UNDO'} + + maxIvyLength = FloatProperty(name="Max Ivy Length", + description="Maximum ivy length in Blender Units.", + default=1.0, + min=0.0, + soft_max=3.0, + subtype='DISTANCE', + unit='LENGTH') + primaryWeight = FloatProperty(name="Primary Weight", + description="Weighting given to the current direction.", + default=0.5, + min=0.0, + soft_max=1.0) + randomWeight = FloatProperty(name="Random Weight", + description="Weighting given to the random direction.", + default=0.2, + min=0.0, + soft_max=1.0) + gravityWeight = FloatProperty(name="Gravity Weight", + description="Weighting given to the gravity direction.", + default=1.0, + min=0.0, + soft_max=1.0) + adhesionWeight = FloatProperty(name="Adhesion Weight", + description="Weighting given to the adhesion direction.", + default=0.1, + min=0.0, + soft_max=1.0) + branchingProbability = FloatProperty(name="Branching Probability", + description="Probability of a new branch forming.", + default=0.05, + min=0.0, + soft_max=1.0) + leafProbability = FloatProperty(name="Leaf Probability", + description="Probability of a leaf forming.", + default=0.35, + min=0.0, + soft_max=1.0) + ivySize = FloatProperty(name="Ivy Size", + description="The length of an ivy segment in Blender"\ + " Units.", + default=0.02, + min=0.0, + soft_max=1.0, + precision=3) + ivyLeafSize = FloatProperty(name="Ivy Leaf Size", + description="The size of the ivy leaves", + default=0.02, + min=0.0, + soft_max=0.5, + precision=3) + ivyBranchSize = FloatProperty(name="Ivy Branch Size", + description="The size of the ivy branches", + default=0.001, + min=0.0, + soft_max=0.1, + precision=4) + maxFloatLength = FloatProperty(name="Max Float Length", + description="The maximum distance that a branch"\ + "can live while floating.", + default=0.5, + min=0.0, + soft_max=1.0) + maxAdhesionDistance = FloatProperty(name="Max Adhesion Length", + description="The maximum distance that a branch"\ + "will feel the effects of adhesion.", + default=1.0, + min=0.0, + soft_max=2.0, + precision=2) + randomSeed = FloatProperty(name="Random Seed", + description="The seed governing random generation.", + default=0, + min=0.0, + soft_max=10) + maxTime = FloatProperty(name="Maximum Time", + description="The maximum time to run the generation for"\ + "in seconds generation (0.0 = Disabled)", + default=0.0, + min=0.0, + soft_max=10) + growLeaves = BoolProperty(name="Grow Leaves", + description="Grow leaves or not.", + default=True) + updateIvy = BoolProperty(name="Update Ivy", default=False) + + @classmethod + def poll(self, context): + # Check if there's an object and whether it's a mesh + ob = context.active_object + if (ob is not None) and\ + (ob.type == 'MESH') and\ + (context.mode == 'OBJECT'): + return True + return False + + def execute(self, context): + if self.updateIvy: + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + # Get the selected object + ob = context.active_object + + # Compute bounding sphere radius + #radius = computeBoundingSphere(ob) # Not needed anymore + + # Get the seeding point + seedPoint = context.scene.cursor_location + + # Fix the random seed + rand_seed(int(self.randomSeed)) + + # Make the new ivy + IVY = Ivy(**self.as_keywords(ignore=('randomSeed', 'growLeaves', + 'maxIvyLength', 'maxTime', 'updateIvy'))) + + # Generate first root and node + IVY.seed(seedPoint) + + checkAlive = True + checkTime = False + maxLength = self.maxIvyLength # * radius + + # If we need to check time set the flag + if self.maxTime != 0.0: + checkTime = True + + t = time.time() + startPercent = 0.0 + checkAliveIter = [True, ] + + # Grow until 200 roots is reached or backup counter exceeds limit + while any(checkAliveIter) and\ + (IVY.maxLength < maxLength) and\ + (not checkTime or (time.time() - t < self.maxTime)): + # Grow the ivy for this iteration + IVY.grow(ob) + + # Print the proportion of ivy growth to console + if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10: + print('%0.2f%% of Ivy nodes have grown' %\ + (IVY.maxLength / maxLength * 100)) + startPercent += 10 + if IVY.maxLength / maxLength > 1: + print("Halting Growth") + + # Make an iterator to check if all are alive + checkAliveIter = (r.alive for r in IVY.ivyRoots) + + # Create the curve and leaf geometry + createIvyGeometry(IVY, self.growLeaves) + print("Geometry Generation Complete") + + print("Ivy generated in %0.2f s" % (time.time() - t)) + + self.updateIvy = False + + return {'FINISHED'} + + return {'PASS_THROUGH'} + + def draw(self, context): + layout = self.layout + row = layout.row() + row.alignment = 'EXPAND' + row.prop(self, 'updateIvy', icon='CURVE_DATA') + + row = layout.row() + properties = row.operator('curve.ivy_gen', text="Add New Ivy") + properties.randomSeed = self.randomSeed + properties.maxTime = self.maxTime + properties.maxIvyLength = self.maxIvyLength + properties.ivySize = self.ivySize + properties.maxFloatLength = self.maxFloatLength + properties.maxAdhesionDistance = self.maxAdhesionDistance + properties.primaryWeight = self.primaryWeight + properties.randomWeight = self.randomWeight + properties.gravityWeight = self.gravityWeight + properties.adhesionWeight = self.adhesionWeight + properties.branchingProbability = self.branchingProbability + properties.leafProbability = self.leafProbability + properties.ivyBranchSize = self.ivyBranchSize + properties.ivyLeafSize = self.ivyLeafSize + + row = layout.row() + row.operator('curve.ivy_gen', text="Add New Default Ivy") + + row = layout.row() + row.prop(self, 'growLeaves') + + box = layout.box() + box.label("Generation Settings") + row = box.row() + row.prop(self, 'randomSeed') + row = box.row() + row.prop(self, 'maxTime') + box = layout.box() + box.label("Size Settings") + row = box.row() + row.prop(self, 'maxIvyLength') + row = box.row() + row.prop(self, 'ivySize') + row = box.row() + row.prop(self, 'maxFloatLength') + row = box.row() + row.prop(self, 'maxAdhesionDistance') + box = layout.box() + box.label("Weight Settings") + row = box.row() + row.prop(self, 'primaryWeight') + row = box.row() + row.prop(self, 'randomWeight') + row = box.row() + row.prop(self, 'gravityWeight') + row = box.row() + row.prop(self, 'adhesionWeight') + box = layout.box() + box.label("Branch Settings") + row = box.row() + row.prop(self, 'branchingProbability') + row = box.row() + row.prop(self, 'ivyBranchSize') + + if self.growLeaves: + box = layout.box() + box.label("Leaf Settings") + row = box.row() + row.prop(self, 'ivyLeafSize') + row = box.row() + row.prop(self, 'leafProbability') + + +def menu_func(self, context): + self.layout.operator(IvyGen.bl_idname, text="Add Ivy to Mesh", + icon='PLUGIN').updateIvy = True + + +def register(): + bpy.utils.register_module(__name__) + bpy.types.INFO_MT_curve_add.append(menu_func) + + +def unregister(): + bpy.types.INFO_MT_curve_add.remove(menu_func) + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() -- cgit v1.2.3