diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py index dd52310207cabc2840a7085eab22bb35fd927005..7a90b3b9d2e84255bb7fd7af04c267855e4aecd2 100644 --- a/add_curve_ivygen.py +++ b/add_curve_ivygen.py @@ -21,9 +21,9 @@ bl_info = { "name": "IvyGen", "author": "testscreenings, PKHG, TrumanBlending", - "version": (0, 1, 2), + "version": (0, 1, 4), "blender": (2, 59, 0), - "location": "View3D > Add > Curve", + "location": "View3D > Tool Shelf > Create > Ivy Generator", "description": "Adds generated ivy to a mesh object starting " "at the 3D cursor", "warning": "", @@ -34,10 +34,16 @@ bl_info = { import bpy +from bpy.types import ( + Operator, + Panel, + PropertyGroup, + ) from bpy.props import ( + BoolProperty, FloatProperty, IntProperty, - BoolProperty, + PointerProperty, ) from mathutils import ( Vector, @@ -191,7 +197,7 @@ def createIvyGeometry(IVY, growLeaves): if growLeaves: faceList = [[4 * i + l for l in range(4)] for i in - range(len(vertList) // 4)] + range(len(vertList) // 4)] # Generate the new leaf mesh and link me = bpy.data.meshes.new('IvyLeaf') @@ -212,24 +218,6 @@ def createIvyGeometry(IVY, growLeaves): ob.parent = newCurve -''' -def computeBoundingSphere(ob): - # Get the mesh data - me = ob.data - # Intialise the center - center = Vector((0.0, 0.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', @@ -463,12 +451,204 @@ def check_mesh_faces(ob): return False -class IvyGen(bpy.types.Operator): +class IvyGen(Operator): bl_idname = "curve.ivy_gen" bl_label = "IvyGen" bl_description = "Generate Ivy on an Mesh Object" bl_options = {'REGISTER', 'UNDO'} + updateIvy = BoolProperty( + name="Update Ivy", + description="Update the Ivy location based on the cursor and Panel settings", + default=False + ) + defaultIvy = BoolProperty( + name="Default Ivy", + options={"HIDDEN", "SKIP_SAVE"}, + default=False + ) + + @classmethod + def poll(self, context): + # Check if there's an object and whether it's a mesh + ob = context.active_object + return ((ob is not None) and + (ob.type == 'MESH') and + (context.mode == 'OBJECT')) + + def invoke(self, context, event): + self.updateIvy = True + return self.execute(context) + + def execute(self, context): + # scene = context.scene + ivyProps = context.window_manager.ivy_gen_props + + if not self.updateIvy: + return {'PASS_THROUGH'} + + # assign the variables, check if it is default + # Note: update the values if window_manager props defaults are changed + randomSeed = ivyProps.randomSeed if not self.defaultIvy else 0 + maxTime = ivyProps.maxTime if not self.defaultIvy else 0 + maxIvyLength = ivyProps.maxIvyLength if not self.defaultIvy else 1.0 + ivySize = ivyProps.ivySize if not self.defaultIvy else 0.02 + maxFloatLength = ivyProps.maxFloatLength if not self.defaultIvy else 0.5 + maxAdhesionDistance = ivyProps.maxAdhesionDistance if not self.defaultIvy else 1.0 + primaryWeight = ivyProps.primaryWeight if not self.defaultIvy else 0.5 + randomWeight = ivyProps.randomWeight if not self.defaultIvy else 0.2 + gravityWeight = ivyProps.gravityWeight if not self.defaultIvy else 1.0 + adhesionWeight = ivyProps.adhesionWeight if not self.defaultIvy else 0.1 + branchingProbability = ivyProps.branchingProbability if not self.defaultIvy else 0.05 + leafProbability = ivyProps.leafProbability if not self.defaultIvy else 0.35 + ivyBranchSize = ivyProps.ivyBranchSize if not self.defaultIvy else 0.001 + ivyLeafSize = ivyProps.ivyLeafSize if not self.defaultIvy else 0.02 + growLeaves = ivyProps.growLeaves if not self.defaultIvy else True + + 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 + + # Check if the mesh has at least one polygon since some functions + # are expecting them in the object's data (see T51753) + check_face = check_mesh_faces(ob) + if check_face is False: + self.report({'WARNING'}, + "Mesh Object doesn't have at least one Face. " + "Operation Cancelled") + return {"CANCELLED"} + + # 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(randomSeed) + + # Make the new ivy + IVY = Ivy( + primaryWeight=primaryWeight, + randomWeight=randomWeight, + gravityWeight=gravityWeight, + adhesionWeight=adhesionWeight, + branchingProbability=branchingProbability, + leafProbability=leafProbability, + ivySize=ivySize, + ivyLeafSize=ivyLeafSize, + ivyBranchSize=ivyBranchSize, + maxFloatLength=maxFloatLength, + maxAdhesionDistance=maxAdhesionDistance + ) + # Generate first root and node + IVY.seed(seedPoint) + + checkTime = False + maxLength = maxIvyLength # * radius + + # If we need to check time set the flag + if 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 < 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, growLeaves) + print("Geometry Generation Complete") + + print("Ivy generated in %0.2f s" % (time.time() - t)) + + self.updateIvy = False + self.defaultIvy = False + + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + + layout.prop(self, "updateIvy", icon="FILE_REFRESH") + + +class CURVE_PT_IvyGenPanel(Panel): + bl_label = "Ivy Generator" + bl_idname = "CURVE_PT_IvyGenPanel" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Create" + bl_context = 'objectmode' + bl_options = {"DEFAULT_CLOSED"} + + def draw(self, context): + layout = self.layout + wm = context.window_manager + col = layout.column(align=True) + + prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE") + prop_new.defaultIvy = False + prop_new.updateIvy = True + + prop_def = col.operator("curve.ivy_gen", text="Add New Default Ivy", icon="CURVE_DATA") + prop_def.defaultIvy = True + prop_def.updateIvy = True + + col = layout.column(align=True) + col.label("Generation Settings:") + col.prop(wm.ivy_gen_props, "randomSeed") + col.prop(wm.ivy_gen_props, "maxTime") + + col = layout.column(align=True) + col.label("Size Settings:") + col.prop(wm.ivy_gen_props, "maxIvyLength") + col.prop(wm.ivy_gen_props, "ivySize") + col.prop(wm.ivy_gen_props, "maxFloatLength") + col.prop(wm.ivy_gen_props, "maxAdhesionDistance") + + col = layout.column(align=True) + col.label("Weight Settings:") + col.prop(wm.ivy_gen_props, "primaryWeight") + col.prop(wm.ivy_gen_props, "randomWeight") + col.prop(wm.ivy_gen_props, "gravityWeight") + col.prop(wm.ivy_gen_props, "adhesionWeight") + + col = layout.column(align=True) + col.label("Branch Settings:") + col.prop(wm.ivy_gen_props, "branchingProbability") + col.prop(wm.ivy_gen_props, "ivyBranchSize") + + col = layout.column(align=True) + col.prop(wm.ivy_gen_props, "growLeaves") + + if wm.ivy_gen_props.growLeaves: + col = layout.column(align=True) + col.label("Leaf Settings:") + col.prop(wm.ivy_gen_props, "ivyLeafSize") + col.prop(wm.ivy_gen_props, "leafProbability") + + +class IvyGenProperties(PropertyGroup): maxIvyLength = FloatProperty( name="Max Ivy Length", description="Maximum ivy length in Blender Units", @@ -551,7 +731,8 @@ class IvyGen(bpy.types.Operator): "can live while floating", default=0.5, min=0.0, - soft_max=1.0) + soft_max=1.0 + ) maxAdhesionDistance = FloatProperty( name="Max Adhesion Length", description="The maximum distance that a branch " @@ -580,168 +761,29 @@ class IvyGen(bpy.types.Operator): 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 - return ((ob is not None) and - (ob.type == 'MESH') and - (context.mode == 'OBJECT')) - - def invoke(self, context, event): - self.updateIvy = True - return self.execute(context) - - def execute(self, context): - if not self.updateIvy: - return {'PASS_THROUGH'} - - 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 - - # Check if the mesh has at least one polygon since some functions - # are expecting them in the object's data (see T51753) - check_face = check_mesh_faces(ob) - if check_face is False: - self.report({'WARNING'}, - "Mesh Object doesn't have at least one Face. " - "Operation Cancelled") - return {"CANCELLED"} - - # 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(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) - - 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'} - - def draw(self, context): - layout = self.layout - - layout.prop(self, 'updateIvy', icon='CURVE_DATA') - - properties = layout.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 - properties.updateIvy = True - - prop_def = layout.operator('curve.ivy_gen', text="Add New Default Ivy") - prop_def.updateIvy = True - - layout.prop(self, 'growLeaves') - - box = layout.box() - box.label("Generation Settings:") - box.prop(self, 'randomSeed') - box.prop(self, 'maxTime') - - box = layout.box() - box.label("Size Settings:") - box.prop(self, 'maxIvyLength') - box.prop(self, 'ivySize') - box.prop(self, 'maxFloatLength') - box.prop(self, 'maxAdhesionDistance') - - box = layout.box() - box.label("Weight Settings:") - box.prop(self, 'primaryWeight') - box.prop(self, 'randomWeight') - box.prop(self, 'gravityWeight') - box.prop(self, 'adhesionWeight') - - box = layout.box() - box.label("Branch Settings:") - box.prop(self, 'branchingProbability') - box.prop(self, 'ivyBranchSize') - - if self.growLeaves: - box = layout.box() - box.label("Leaf Settings:") - box.prop(self, 'ivyLeafSize') - box.prop(self, 'leafProbability') - - -def menu_func(self, context): - self.layout.operator(IvyGen.bl_idname, text="Add Ivy to Mesh", - icon='OUTLINER_DATA_CURVE').updateIvy = True +classes = ( + IvyGen, + IvyGenProperties, + CURVE_PT_IvyGenPanel +) def register(): - bpy.utils.register_module(__name__) - bpy.types.INFO_MT_curve_add.append(menu_func) + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.WindowManager.ivy_gen_props = PointerProperty( + type=IvyGenProperties + ) def unregister(): - bpy.types.INFO_MT_curve_add.remove(menu_func) - bpy.utils.unregister_module(__name__) + del bpy.types.WindowManager.ivy_gen_props + + for cls in reversed(classes): + bpy.utils.unregister_class(cls) if __name__ == "__main__":