diff --git a/blenderpack.py b/blenderpack.py old mode 100644 new mode 100755 index b582533..c8244af --- a/blenderpack.py +++ b/blenderpack.py @@ -3,10 +3,109 @@ # HACK: seems 'requests' module bundled with blender isn't bundled with 'idna' module. So force system python for now import sys sys.path.insert(0, '/usr/lib/python3.6/site-packages') + import requests +import json +import os +import ast +import argparse +import zipfile +import logging + +log = logging.getLogger(__name__) + +def report(msg): + # if __name__ == '__main__': + print("blenderpack:", msg) + +def fatal_report(msg, code=1): + report(msg) + exit(code) def fetch(url): # TODO: do conditional request re = requests.get(url) print(re.json()) +def parse_blinfo(source, addon_name="unknown"): + """Parse a python file and return its bl_info dict, if there is one (else return None)""" + + try: + tree = ast.parse(source) + except SyntaxError as ex: + log.warning('Skipping addon: SyntaxError in %s: %s', addon_name, ex) + return None + + for body in tree.body: + if body.__class__ != ast.Assign: + continue + if len(body.targets) != 1: + continue + if getattr(body.targets[0], 'id', '') != 'bl_info': + continue + + return ast.literal_eval(body.value) + + log.warning('Unable to find bl_info dict in %s', addon_name) + return None + + +def extract_blinfo(path): + """Extract bl_info dict from addon at path (can be single file, module, or zip)""" + + source = None + # get last component of path, even with trailing slash + addon_name = os.path.split(path.rstrip(os.path.sep))[1] + + if os.path.isdir(path): + with open(os.path.join(path, '__init__.py' 'r')) as f: + source = f.read() + else: + + # HACK: perhaps not the best approach determining filetype..? + try: + with zipfile.ZipFile(path, 'r') as z: + for fname in z.namelist(): + # HACK: this seems potentially fragile; depends on zipfile listing root contents first + if fname.endswith('__init__.py'): + source = z.read(fname) + break + except zipfile.BadZipFile: + with open(path, 'r') as f: + source = f.read() + + if source == None: + raise RuntimeError("Could not read addon '%s'" % addon_name) + + return parse_blinfo(source, addon_name) + + + +def make_repo(path): + """Make repo.json for files in directory 'path'""" + # try: + for addon in os.listdir(path): + extract_blinfo(os.path.join(path, addon)) + # except FileNotFoundError: + # fatal_report("No such file or directory: '%s'" % path) + + + +def main(): + pass + # print(args) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Search, install, and manage packages for Blender') + subparsers = parser.add_subparsers() + + make = subparsers.add_parser('make') + make.add_argument('path') + make.set_defaults(func=lambda args: make_repo(args.path)) + + args = parser.parse_args() + args.func(args) + + main() + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_blenderpack.py b/tests/test_blenderpack.py new file mode 100644 index 0000000..5146eaa --- /dev/null +++ b/tests/test_blenderpack.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import unittest +import os.path +import blenderpack + +class test_blenderpack_make_repo(unittest.TestCase): + + helper_path = os.path.join('tests', 'test_helpers') + + def test_extract_blinfo_from_file(self): + with open(os.path.join(self.helper_path, 'ivy_gen_blinfo.txt'), 'r') as f: + expectation = f.read() + + reality = str(blenderpack.extract_blinfo(os.path.join(self.helper_path, 'addons', 'add_curve_ivygen.py'))) + self.assertEqual(expectation, reality) + + def test_extract_blinfo_from_zip(self): + with open(os.path.join(self.helper_path, 'extra_objects_blinfo.txt'), 'r') as f: + expectation = f.read() + + reality = str(blenderpack.extract_blinfo(os.path.join(self.helper_path, 'addons', 'add_curve_extra_objects.zip'))) + self.assertEqual(expectation, reality) + + # def test_validpath(self): + # blenderpack.make_repo(os.path.join('test_helpers', 'addons')) + + diff --git a/tests/test_helpers/addons/add_curve_extra_objects.zip b/tests/test_helpers/addons/add_curve_extra_objects.zip new file mode 100644 index 0000000..c08089b Binary files /dev/null and b/tests/test_helpers/addons/add_curve_extra_objects.zip differ diff --git a/tests/test_helpers/addons/add_curve_ivygen.py b/tests/test_helpers/addons/add_curve_ivygen.py new file mode 100644 index 0000000..dd52310 --- /dev/null +++ b/tests/test_helpers/addons/add_curve_ivygen.py @@ -0,0 +1,748 @@ +# ##### 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, 2), + "blender": (2, 59, 0), + "location": "View3D > Add > Curve", + "description": "Adds generated ivy to a mesh object starting " + "at the 3D cursor", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Curve/Ivy_Gen", + "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.fill_mode = 'FULL' + curve.resolution_u = 4 + + if growLeaves: + # Create the ivy leaves + # Order location of the vertices + signList = ((-1.0, +1.0), + (+1.0, +1.0), + (+1.0, -1.0), + (-1.0, -1.0), + ) + + # 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.0 / (root.parents + 1) + 1.0 + prevIvyLength = 1.0 / 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 normalize 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.0 + + 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.0) + + 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) + + me.uv_textures.new("Leaves") + + # Set the uv texture coords + # TODO, this is non-functional, default uvs are ok? + ''' + 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.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 + + # Normalize all the weights only on intialisation + sums = self.primaryWeight + self.randomWeight + self.adhesionWeight + self.primaryWeight /= sums + self.randomWeight /= sums + self.adhesionWeight /= sums + + 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 normalize + 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.0, 0.0)) + if nearest_result[0]: + # Compute the distance to the nearest point + adhesion_vector = ob.matrix_world * nearest_result[1] - 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[3]) + 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 + tran_dir = tran_new_pos - tran_pos + + ray_result = ob.ray_cast(tran_pos, tran_dir, tran_dir.length) + # If there's a collision we need to check it + if ray_result[0]: + # Check whether the collision is going into the object + if tran_dir.dot(ray_result[2]) < 0.0: + # Find projection of the piont onto the plane + p0 = tran_new_pos - (tran_new_pos - + ray_result[1]).project(ray_result[2]) + # 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 + + +def check_mesh_faces(ob): + me = ob.data + if len(me.polygons) > 0: + return True + + return False + + +class IvyGen(bpy.types.Operator): + bl_idname = "curve.ivy_gen" + bl_label = "IvyGen" + bl_description = "Generate Ivy on an Mesh Object" + 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 = IntProperty( + name="Random Seed", + description="The seed governing random generation", + default=0, + min=0 + ) + 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 + 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 + + +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() diff --git a/tests/test_helpers/extra_objects_blinfo.txt b/tests/test_helpers/extra_objects_blinfo.txt new file mode 100644 index 0000000..f7a28bd --- /dev/null +++ b/tests/test_helpers/extra_objects_blinfo.txt @@ -0,0 +1 @@ +{'name': 'Extra Objects', 'author': 'Multiple Authors', 'version': (0, 1, 2), 'blender': (2, 76, 0), 'location': 'View3D > Add > Curve > Extra Objects', 'description': 'Add extra curve object types', 'warning': '', 'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Curve_Objects', 'category': 'Add Curve'} \ No newline at end of file diff --git a/tests/test_helpers/ivy_gen_blinfo.txt b/tests/test_helpers/ivy_gen_blinfo.txt new file mode 100644 index 0000000..7543bc5 --- /dev/null +++ b/tests/test_helpers/ivy_gen_blinfo.txt @@ -0,0 +1 @@ +{'name': 'IvyGen', 'author': 'testscreenings, PKHG, TrumanBlending', 'version': (0, 1, 2), 'blender': (2, 59, 0), 'location': 'View3D > Add > Curve', 'description': 'Adds generated ivy to a mesh object starting at the 3D cursor', 'warning': '', 'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Ivy_Gen', 'category': 'Add Curve'} \ No newline at end of file