-
Brendon Murphy authored
finished
Brendon Murphy authoredfinished
__init__.py 21.06 KiB
#====================== 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": "Sapling",
"author": "Andrew Hale (TrumanBlending)",
"version": (0, 2, 5),
"blender": (2, 6, 4),
"location": "View3D > Add > Curve",
"description": ("Adds a parametric tree. The method is presented by "
"Jason Weber & Joseph Penn in their paper 'Creation and Rendering of "
"Realistic Trees'."),
"warning": "", # used for warning icon and text in addons panel
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
"Scripts/Curve/Sapling_Tree",
"tracker_url": "http://projects.blender.org/tracker/"\
"?func=detail&atid=469&aid=27226&group_id=153",
"category": "Add Curve"}
if "bpy" in locals():
import imp
imp.reload(utils)
else:
from add_curve_sapling import utils
import bpy
import time
import os
#from utils import *
from mathutils import *
from math import pi, sin, degrees, radians, atan2, copysign
from random import random, uniform, seed, choice, getstate, setstate
from bpy.props import *
from add_curve_sapling.utils import *
#global splitError
useSet = False
shapeList = [('0', 'Conical (0)', 'Shape = 0'),
('1', 'Spherical (1)', 'Shape = 1'),
('2', 'Hemispherical (2)', 'Shape = 2'),
('3', 'Cylindrical (3)', 'Shape = 3'),
('4', 'Tapered Cylindrical (4)', 'Shape = 4'),
('5', 'Flame (5)', 'Shape = 5'),
('6', 'Inverse Conical (6)', 'Shape = 6'),
('7', 'Tend Flame (7)', 'Shape = 7')]
handleList = [('0', 'Auto', 'Auto'),
('1', 'Vector', 'Vector')]
settings = [('0', 'Geometry', 'Geometry'),
('1', 'Branch Splitting', 'Branch Splitting'),
('2', 'Branch Growth', 'Branch Growth'),
('3', 'Pruning', 'Pruning'),
('4', 'Leaves', 'Leaves'),
('5', 'Armature', 'Armature')]
def getPresetpath():
'''Support user defined scripts directory
Find the first ocurrence of add_curve_sapling/presets in possible script paths
and return it as preset path'''
presetpath = ""
for p in bpy.utils.script_paths():
presetpath = os.path.join(p, 'addons', 'add_curve_sapling', 'presets')
if os.path.exists(presetpath):
break
return presetpath
class ExportData(bpy.types.Operator):
'''This operator handles writing presets to file'''
bl_idname = 'sapling.exportdata'
bl_label = 'Export Preset'
data = StringProperty()
def execute(self, context):
# Unpack some data from the input
data, filename = eval(self.data)
try:
# Check whether the file exists by trying to open it.
f = open(os.path.join(getPresetpath(), filename + '.py'), 'r')
f.close()
# If it exists then report an error
self.report({'ERROR_INVALID_INPUT'}, 'Preset Already Exists')
return {'CANCELLED'}
except IOError:
if data:
# If it doesn't exist, create the file with the required data
f = open(os.path.join(getPresetpath(), filename + '.py'), 'w')
f.write(data)
f.close()
return {'FINISHED'}
else:
return {'CANCELLED'}
class ImportData(bpy.types.Operator):
'''This operator handles importing existing presets'''
bl_idname = 'sapling.importdata'
bl_label = 'Import Preset'
filename = StringProperty()
def execute(self, context):
# Make sure the operator knows about the global variables
global settings, useSet
# Read the preset data into the global settings
f = open(os.path.join(getPresetpath(), self.filename), 'r')
settings = f.readline()
f.close()
#print(settings)
settings = eval(settings)
# Set the flag to use the settings
useSet = True
return {'FINISHED'}
class PresetMenu(bpy.types.Menu):
'''Create the preset menu by finding all preset files
in the preset directory
'''
bl_idname = "sapling.presetmenu"
bl_label = "Presets"
def draw(self, context):
# Get all the sapling presets
presets = [a for a in os.listdir(getPresetpath()) if a[-3:] == '.py']
layout = self.layout
# Append all to the menu
for p in presets:
layout.operator("sapling.importdata", text=p[:-3]).filename = p
class AddTree(bpy.types.Operator):
bl_idname = "curve.tree_add"
bl_label = "Sapling: Add Tree"
bl_options = {'REGISTER', 'UNDO'}
def update_tree(self, context):
self.do_update = True
def no_update_tree(self, context):
self.do_update = False
do_update = BoolProperty(name='Do Update',
default=True, options={'HIDDEN'})
chooseSet = EnumProperty(name='Settings',
description='Choose the settings to modify',
items=settings,
default='0', update=no_update_tree)
bevel = BoolProperty(name='Bevel',
description='Whether the curve is bevelled',
default=False, update=update_tree)
prune = BoolProperty(name='Prune',
description='Whether the tree is pruned',
default=False, update=update_tree)
showLeaves = BoolProperty(name='Show Leaves',
description='Whether the leaves are shown',
default=False, update=update_tree)
useArm = BoolProperty(name='Use Armature',
description='Whether the armature is generated',
default=False, update=update_tree)
seed = IntProperty(name='Random Seed',
description='The seed of the random number generator',
default=0, update=update_tree)
handleType = IntProperty(name='Handle Type',
description='The type of curve handles',
min=0,
max=1,
default=0, update=update_tree)
levels = IntProperty(name='Levels',
description='Number of recursive branches (Levels)',
min=1,
max=6,
default=3, update=update_tree)
length = FloatVectorProperty(name='Length',
description='The relative lengths of each branch level (nLength)',
min=0.000001,
default=[1, 0.3, 0.6, 0.45],
size=4, update=update_tree)
lengthV = FloatVectorProperty(name='Length Variation',
description='The relative length variations of each level (nLengthV)',
min=0.0,
default=[0, 0, 0, 0],
size=4, update=update_tree)
branches = IntVectorProperty(name='Branches',
description='The number of branches grown at each level (nBranches)',
min=0,
default=[50, 30, 10, 10],
size=4, update=update_tree)
curveRes = IntVectorProperty(name='Curve Resolution',
description='The number of segments on each branch (nCurveRes)',
min=1,
default=[3, 5, 3, 1],
size=4, update=update_tree)
curve = FloatVectorProperty(name='Curvature',
description='The angle of the end of the branch (nCurve)',
default=[0, -40, -40, 0],
size=4, update=update_tree)
curveV = FloatVectorProperty(name='Curvature Variation',
description='Variation of the curvature (nCurveV)',
default=[20, 50, 75, 0],
size=4, update=update_tree)
curveBack = FloatVectorProperty(name='Back Curvature',
description='Curvature for the second half of a branch (nCurveBack)',
default=[0, 0, 0, 0],
size=4, update=update_tree)
baseSplits = IntProperty(name='Base Splits',
description='Number of trunk splits at its base (nBaseSplits)',
min=0,
default=0, update=update_tree)
segSplits = FloatVectorProperty(name='Segment Splits',
description='Number of splits per segment (nSegSplits)',
min=0,
default=[0, 0, 0, 0],
size=4, update=update_tree)
splitAngle = FloatVectorProperty(name='Split Angle',
description='Angle of branch splitting (nSplitAngle)',
default=[0, 0, 0, 0],
size=4, update=update_tree)
splitAngleV = FloatVectorProperty(name='Split Angle Variation',
description='Variation in the split angle (nSplitAngleV)',
default=[0, 0, 0, 0],
size=4, update=update_tree)
scale = FloatProperty(name='Scale',
description='The tree scale (Scale)',
min=0.0,
default=13.0, update=update_tree)
scaleV = FloatProperty(name='Scale Variation',
description='The variation in the tree scale (ScaleV)',
default=3.0, update=update_tree)
attractUp = FloatProperty(name='Vertical Attraction',
description='Branch upward attraction',
default=0.0, update=update_tree)
shape = EnumProperty(name='Shape',
description='The overall shape of the tree (Shape)',
items=shapeList,
default='7', update=update_tree)
baseSize = FloatProperty(name='Base Size',
description='Fraction of tree height with no branches (BaseSize)',
min=0.0,
max=1.0,
default=0.4, update=update_tree)
ratio = FloatProperty(name='Ratio',
description='Base radius size (Ratio)',
min=0.0,
default=0.015, update=update_tree)
taper = FloatVectorProperty(name='Taper',
description='The fraction of tapering on each branch (nTaper)',
min=0.0,
max=1.0,
default=[1, 1, 1, 1],
size=4, update=update_tree)
ratioPower = FloatProperty(name='Branch Radius Ratio',
description=('Power which defines the radius of a branch compared to '
'the radius of the branch it grew from (RatioPower)'),
min=0.0,
default=1.2, update=update_tree)
downAngle = FloatVectorProperty(name='Down Angle',
description=('The angle between a new branch and the one it grew '
'from (nDownAngle)'),
default=[90, 60, 45, 45],
size=4, update=update_tree)
downAngleV = FloatVectorProperty(name='Down Angle Variation',
description='Variation in the down angle (nDownAngleV)',
default=[0, -50, 10, 10],
size=4, update=update_tree)
rotate = FloatVectorProperty(name='Rotate Angle',
description=('The angle of a new branch around the one it grew from '
'(nRotate)'),
default=[140, 140, 140, 77],
size=4, update=update_tree)
rotateV = FloatVectorProperty(name='Rotate Angle Variation',
description='Variation in the rotate angle (nRotateV)',
default=[0, 0, 0, 0],
size=4, update=update_tree)
scale0 = FloatProperty(name='Radius Scale',
description='The scale of the trunk radius (0Scale)',
min=0.0,
default=1.0, update=update_tree)
scaleV0 = FloatProperty(name='Radius Scale Variation',
description='Variation in the radius scale (0ScaleV)',
default=0.2, update=update_tree)
pruneWidth = FloatProperty(name='Prune Width',
description='The width of the envelope (PruneWidth)',
min=0.0,
default=0.4, update=update_tree)
pruneWidthPeak = FloatProperty(name='Prune Width Peak',
description=('Fraction of envelope height where the maximum width '
'occurs (PruneWidthPeak)'),
min=0.0,
default=0.6, update=update_tree)
prunePowerHigh = FloatProperty(name='Prune Power High',
description=('Power which determines the shape of the upper portion '
'of the envelope (PrunePowerHigh)'),
default=0.5, update=update_tree)
prunePowerLow = FloatProperty(name='Prune Power Low',
description=('Power which determines the shape of the lower portion '
'of the envelope (PrunePowerLow)'),
default=0.001, update=update_tree)
pruneRatio = FloatProperty(name='Prune Ratio',
description='Proportion of pruned length (PruneRatio)',
min=0.0,
max=1.0,
default=1.0, update=update_tree)
leaves = IntProperty(name='Leaves',
description='Maximum number of leaves per branch (Leaves)',
default=25, update=update_tree)
leafScale = FloatProperty(name='Leaf Scale',
description='The scaling applied to the whole leaf (LeafScale)',
min=0.0,
default=0.17, update=update_tree)
leafScaleX = FloatProperty(name='Leaf Scale X',
description=('The scaling applied to the x direction of the leaf '
'(LeafScaleX)'),
min=0.0,
default=1.0, update=update_tree)
leafShape = leafDist = EnumProperty(name='Leaf Shape',
description='The shape of the leaves, rectangular are UV mapped',
items=(('hex', 'Hexagonal', '0'), ('rect', 'Rectangular', '1')),
default='hex', update=update_tree)
bend = FloatProperty(name='Leaf Bend',
description='The proportion of bending applied to the leaf (Bend)',
min=0.0,
max=1.0,
default=0.0, update=update_tree)
leafDist = EnumProperty(name='Leaf Distribution',
description='The way leaves are distributed on branches',
items=shapeList,
default='4', update=update_tree)
bevelRes = IntProperty(name='Bevel Resolution',
description='The bevel resolution of the curves',
min=0,
default=0, update=update_tree)
resU = IntProperty(name='Curve Resolution',
description='The resolution along the curves',
min=1,
default=4, update=update_tree)
handleType = EnumProperty(name='Handle Type',
description='The type of handles used in the spline',
items=handleList,
default='1', update=update_tree)
frameRate = FloatProperty(name='Frame Rate',
description=('The number of frames per second which can be used to '
'adjust the speed of animation'),
min=0.001,
default=1, update=update_tree)
windSpeed = FloatProperty(name='Wind Speed',
description='The wind speed to apply to the armature (WindSpeed)',
default=2.0, update=update_tree)
windGust = FloatProperty(name='Wind Gust',
description='The greatest increase over Wind Speed (WindGust)',
default=0.0, update=update_tree)
armAnim = BoolProperty(name='Armature Animation',
description='Whether animation is added to the armature',
default=False, update=update_tree)
presetName = StringProperty(name='Preset Name',
description='The name of the preset to be saved',
default='',
subtype='FILENAME', update=no_update_tree)
limitImport = BoolProperty(name='Limit Import',
description='Limited imported tree to 2 levels & no leaves for speed',
default=True, update=no_update_tree)
startCurv = FloatProperty(name='Trunk Starting Angle',
description=('The angle between vertical and the starting direction '
'of the trunk'),
min=0.0,
max=360,
default=0.0, update=update_tree)
@classmethod
def poll(cls, context):
return context.mode == 'OBJECT'
def draw(self, context):
layout = self.layout
# Branch specs
#layout.label('Tree Definition')
layout.prop(self, 'chooseSet')
if self.chooseSet == '0':
box = layout.box()
box.label("Geometry:")
box.prop(self, 'bevel')
row = box.row()
row.prop(self, 'bevelRes')
row.prop(self, 'resU')
box.prop(self, 'handleType')
box.prop(self, 'shape')
box.prop(self, 'seed')
box.prop(self, 'ratio')
row = box.row()
row.prop(self, 'scale')
row.prop(self, 'scaleV')
row = box.row()
row.prop(self, 'scale0')
row.prop(self, 'scaleV0')
# Here we create a dict of all the properties.
# Unfortunately as_keyword doesn't work with vector properties,
# so we need something custom. This is it
data = []
for a, b in (self.as_keywords(ignore=("chooseSet", "presetName", "limitImport", "do_update"))).items():
# If the property is a vector property then add the slice to the list
try:
len(b)
data.append((a, b[:]))
# Otherwise, it is fine so just add it
except:
data.append((a, b))
# Create the dict from the list
data = dict(data)
row = box.row()
row.prop(self, 'presetName')
# Send the data dict and the file name to the exporter
row.operator('sapling.exportdata').data = repr([repr(data),
self.presetName])
row = box.row()
row.menu('sapling.presetmenu', text='Load Preset')
row.prop(self, 'limitImport')
elif self.chooseSet == '1':
box = layout.box()
box.label("Branch Splitting:")
box.prop(self, 'levels')
box.prop(self, 'baseSplits')
box.prop(self, 'baseSize')
split = box.split()
col = split.column()
col.prop(self, 'branches')
col.prop(self, 'splitAngle')
col.prop(self, 'downAngle')
col.prop(self, 'rotate')
col = split.column()
col.prop(self, 'segSplits')
col.prop(self, 'splitAngleV')
col.prop(self, 'downAngleV')
col.prop(self, 'rotateV')
box.prop(self, 'ratioPower')
elif self.chooseSet == '2':
box = layout.box()
box.label("Branch Growth:")
box.prop(self, 'startCurv')
box.prop(self, 'attractUp')
split = box.split()
col = split.column()
col.prop(self, 'length')
col.prop(self, 'curve')
col.prop(self, 'curveBack')
col = split.column()
col.prop(self, 'lengthV')
col.prop(self, 'curveV')
col.prop(self, 'taper')
box.column().prop(self, 'curveRes')
elif self.chooseSet == '3':
box = layout.box()
box.label("Pruning:")
box.prop(self, 'prune')
box.prop(self, 'pruneRatio')
box.prop(self, 'pruneWidth')
box.prop(self, 'pruneWidthPeak')
row = box.row()
row.prop(self, 'prunePowerHigh')
row.prop(self, 'prunePowerLow')
elif self.chooseSet == '4':
box = layout.box()
box.label("Leaves:")
box.prop(self, 'showLeaves')
box.prop(self, 'leafShape')
box.prop(self, 'leaves')
box.prop(self, 'leafDist')
row = box.row()
row.prop(self, 'leafScale')
row.prop(self, 'leafScaleX')
box.prop(self, 'bend')
elif self.chooseSet == '5':
box = layout.box()
box.label("Armature and Animation:")
row = box.row()
row.prop(self, 'useArm')
row.prop(self, 'armAnim')
row = box.row()
row.prop(self, 'windSpeed')
row.prop(self, 'windGust')
box.prop(self, 'frameRate')
def execute(self, context):
# Ensure the use of the global variables
global settings, useSet
start_time = time.time()
#bpy.ops.ImportData.filename = "quaking_aspen"
# If we need to set the properties from a preset then do it here
if useSet:
for a, b in settings.items():
setattr(self, a, b)
if self.limitImport:
setattr(self, 'levels', 2)
setattr(self, 'showLeaves', False)
useSet = False
if not self.do_update:
return {'PASS_THROUGH'}
addTree(self)
print("Tree creation in %0.1fs" %(time.time()-start_time))
return {'FINISHED'}
def invoke(self, context, event):
# global settings, useSet
# useSet = True
bpy.ops.sapling.importdata(filename = "quaking_aspen.py")
return self.execute(context)
def menu_func(self, context):
self.layout.operator(AddTree.bl_idname, text="Add Tree", icon='PLUGIN')
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_curve_add.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_curve_add.remove(menu_func)
if __name__ == "__main__":
register()