Paste Code
Paste Blends
Paste Images
# ##### 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 #####

# Josh Wedlake, Tube Project

import random
import bpy
import math
import mathutils
import bpy


def auto_detect_prefixes(mushroom_object):
keys_list=list(mushroom_object.data.shape_keys.keys)
dict_prefixes=dict()
for index,each_key in enumerate(keys_list):
name=each_key.name
prefix=''
for b in [[name[(len(name)-(a+1)):],name[:(len(name)-(a+1))]] for a in range(0,len(name))]:
if b[0].isdigit():
prefix=b[1]
available_value=int(b[0])
else:
break
if prefix !='':
if prefix not in dict_prefixes:
dict_prefixes[prefix]=[[available_value,index]]
else:
dict_prefixes[prefix].append([available_value,index])
list_dict_keys=list(dict_prefixes.keys())
for d_key in list_dict_keys:
dict_prefixes[d_key]=sorted(dict_prefixes[d_key],key=lambda list_item: list_item[0])
return dict_prefixes

def build_face_limit_enum(target_object):
vertex_group_list=[]
vertex_group_list.append(('TOVGM','Target Object Vertex Group Mask','Target Object Vertex Group Mask'))
for vertex_group in target_object.vertex_groups:
vertex_group_list.append((vertex_group.name, vertex_group.name, vertex_group.name))
vertex_group_list.append(('-1','(Do not use: Mushrooms everywhere!)','no limits'))
target_object.EnumProperty(attr="mushroomer_limit_group",items=vertex_group_list,default='-1')

def build_vertex_group_enum(mushroom_list,target_object):
set_valid_groups=set()
for mushroom_object in mushroom_list:
for vertex_group in mushroom_object.vertex_groups:
set_valid_groups.add(vertex_group.name)
vertex_group_list=[]
vertex_group_list.append(('VGID','Mushroom Vertex Group for Shrinkwrap','Mushroom Vertex Group for Shrinkwrap'))
vertex_group_list.append(('-1','(Do not use shrinkwrapping)','no shrinkwrapping'))
for vertex_group_name in list(set_valid_groups):
vertex_group_list.append((vertex_group_name, vertex_group_name, vertex_group_name))
target_object.EnumProperty(attr="mushroomer_shrinkwrap_vg",items=vertex_group_list,default='-1')

def build_timing_group_enum(target_object):
vertex_group_list=[]
vertex_group_list.append(('TVG','Vertex Group for Retiming','Vertex Group for Retiming'))
for vertex_group in target_object.vertex_groups:
if vertex_group.name.startswith('MT'):
if vertex_group.name[2:] in bpy.data.groups:
vertex_group_list.append((vertex_group.name, vertex_group.name, vertex_group.name))
vertex_group_list.append(('-1','(Do not use: Mushrooms everywhere!)','no limits'))
target_object.EnumProperty(attr="mushroomer_timing_vg",items=vertex_group_list,default='-1')

def build_group_name_text(target_object):
#find out if there are any groups named mushroomer_group_x, where x is an int. Find the lowest free int
lowest_free_id=-1
for group in bpy.data.groups:
if group.name.startswith('mushroomer_group_'):
this_id=group.name.lstrip('mushroomer_group_')
if this_id.isdigit():
this_id_int=int(this_id)
if this_id_int>lowest_free_id:
lowest_free_id=this_id_int
group_name='mushroomer_group_'+str(lowest_free_id+1)
target_object.StringProperty(attr="mushroomer_grouping", description="Group To Add Mushrooms To",default=group_name)

def build_prefixes_enum(mushroom_list,target_object):
#builds a list of valid shape prefixes for the enum
#builds a dictionary > mushroom name > prefixes > valid numerical values
dict_all_prefixes=dict()
set_valid_prefixes=set()
for mushroom_object in mushroom_list:
mushroom_dict=auto_detect_prefixes(mushroom_object)
dict_all_prefixes[mushroom_object.name]=mushroom_dict
set_valid_prefixes|=set(mushroom_dict.keys())
prefixes_list=[]
prefixes_list.append(('LSK','Life cycle shape key set','Life cycle shape key set'))
prefixes_list.append(("IGNORE",'Don\'t Use','Don\'t Use'))
for prefix in list(set_valid_prefixes):
prefixes_list.append((prefix,prefix+'... [as available]',prefix))
random_prefixes_list=prefixes_list[:]
bend_prefixes_list=prefixes_list[:]
random_prefixes_list[0]=('RSK','Random variation shape key set','Random variation shape key set')
bend_prefixes_list[0]=('BSK','Bend variation shape key set','Random variation shape key set')
prefixes_list.append(("USEEXIST",'Use Pre-existing Shape-Key & Material Actions as Meta-Strips','Use Pre-existing Shape-Key, Material & Texture Actions'))
target_object.EnumProperty(attr="mushroomer_lifecycle_key_set",items=prefixes_list,default='USEEXIST')
target_object.EnumProperty(attr="mushroomer_random_key_set",items=random_prefixes_list,default='IGNORE')
target_object.EnumProperty(attr="mushroomer_bend_key_set",items=bend_prefixes_list,default='IGNORE')
return dict_all_prefixes

def get_number_for_generation(generation,generations_max,mode,min,max,jitter):
import random,math
#gen is input
#pass a minimum and a maximum number
#jitter is % random error
#modes 0-constant,1-linear,2-square,3-fibbonacci,4-root
jitter_amount=(((random.random()*2)-1)*jitter)+1
if mode==0:
value=(jitter_amount*0.5*(max-min))+min
elif mode==1:
value=((generation/generations_max)*(max-min)*jitter_amount)+min
elif mode==2:
value=(((generation**2)/(generations_max**2))*(max-min)*jitter_amount)+min
elif mode==3:
root5 = math.sqrt(5)
fib_c=(0.5 + root5/2)
fib_g=fib_c**generation
fib_c=int(0.5+fib_g/root5)
fib_g=fib_c**generations_max
fib_max=int(0.5+fib_g/root5)
if fib_max==0:
fib_max=1
value=((fib_c/fib_max)*(max-min)*jitter_amount)+min
elif mode==4:
root_max=generations_max**0.5
root_c=generation**0.5
value=((root_c/root_max)*(max-min)*jitter_amount)+min
return int(value)

def make_vertex_parent(target_object,ob_to_distribute,faceid):
#find the closest 3 verts
face_verts=target_object.data.faces[faceid].vertices
vert_list=[]
for each_vert in face_verts:
vert_list.append([(ob_to_distribute.location-(target_object.matrix_world*target_object.data.vertices[each_vert].co)).length,each_vert])
sorted_verts=sorted(vert_list,key=lambda vertinfo: vertinfo[0])
verts_to_parent=[sorted_verts[0][1],sorted_verts[1][1],sorted_verts[2][1]]
#only select the target
bpy.ops.object.select_all(action='DESELECT')
target_object.select=True
if bpy.context.mode!='EDIT_MESH':
bpy.ops.object.editmode_toggle()
toggle=True
else: toggle=False
#in edit mode: deselect verts
existing_sel_mode=bpy.context.tool_settings.mesh_select_mode
bpy.context.tool_settings.mesh_select_mode=[True,False,False]
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.editmode_toggle()
#not in edit mode:select verts
for each_vert in verts_to_parent:
target_object.data.vertices[each_vert].select=True
#now select the mushroom aswell
ob_to_distribute.select=True
bpy.context.scene.objects.active=target_object
#get into edit mode for the active object
bpy.ops.object.editmode_toggle()
#now in edit mode with mushroom selected and appropriate verts selected
bpy.ops.object.vertex_parent_set()
bpy.context.tool_settings.mesh_select_mode=existing_sel_mode
bpy.ops.object.editmode_toggle()
#out of edit mode
bpy.ops.object.select_all(action='DESELECT')
if not toggle:
bpy.ops.object.editmode_toggle()

def place_on_face(target_object, faceid, mushroom_object, pc, sc):
if bpy.context.mode=='EDIT_MESH':
bpy.ops.object.editmode_toggle()
#where pc is center point
if len(bpy.context.selected_objects)>0:
bpy.ops.object.select_all()
mushroom_object.select=True
#change this line if keyframe path fails
bpy.ops.object.duplicate(linked=False)
ob_to_distribute_handle=bpy.context.selected_editable_objects[0]
ob_to_distribute_handle.location=pc
sc_x=sc.x*ob_to_distribute_handle.scale.x
sc_y=sc.y*ob_to_distribute_handle.scale.y
sc_z=sc.z*ob_to_distribute_handle.scale.z
ob_to_distribute_handle.scale=mathutils.Vector((sc_x,sc_y,sc_z))
d_start=target_object.matrix_world.copy().invert()*mathutils.Vector((0,0,0))
d_end=target_object.matrix_world.copy().invert()*target_object.data.faces[faceid].normal.copy()
d=d_end-d_start
d=d.normalize()
e = mathutils.Vector((0,0,1))
f=e.copy().cross(d)
f=f.normalize()
e=d.copy().cross(f)
e=e.normalize()
mat = (mathutils.Matrix(f,e,d)).rotation_part().to_euler()
ob_to_distribute_handle.rotation_euler=mat
make_vertex_parent(target_object,ob_to_distribute_handle,faceid)
return [ob_to_distribute_handle,mushroom_object.name]

def get_face_dimensions(target_object, faceid):
v1=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[0]].co
v2=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[1]].co
v3=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[2]].co
fx=v2-v1
fy=v3-v2
return [fx.length,fy.length]

def convert_dec_to_pt(target_object,faceid,dec):
v1=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[0]].co
v2=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[1]].co
v3=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[2]].co
if len(target_object.data.faces[faceid].vertices)>3:
v4=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[3]].co
else:
v4=v3
va=((v2-v1)*dec[0])+v1
vb=((v3-v4)*dec[0])+v4
vp=((vb-va)*dec[1])+va
return vp

def get_points_random(target_object,faceid,n):
pl=[]
pl_xyz=[]
for tmp in range(0,n):
pl.append([random.random(),random.random()])
for index in range(0,len(pl)):
pl_xyz.append(convert_dec_to_pt(target_object,faceid,pl[index]))
return pl_xyz

def get_points_jittered(target_object,faceid,n,jitter):
#jitter is a decimal 0 - 1
#returns a list of XYZ vectors referring to points jitter distrib on face
fdim=get_face_dimensions(target_object,faceid)
ndim=propose_dimensions(fdim,n)
pl=[]
pl_xyz=[]
for x in range(0,ndim[0]):
for y in range(0,ndim[1]):
rx=(jitter*random.random())-0.5
ry=(jitter*random.random())-0.5
pl.append([((x+0.5)/ndim[0]),((y+0.5)/ndim[1])])
for index in range(0,len(pl)):
pl_xyz.append(convert_dec_to_pt(target_object,faceid,pl[index]))
return pl_xyz

def get_points_scales(target_object,faceid,pl_xyz):
pl_xyz_s=[]
pl_xyz_l=list(pl_xyz)
if len(pl_xyz)>1:
for pa in pl_xyz_l:
closest_dist=-1
for pb in pl_xyz_l:
if pb!=pa:
dist_vec=pb-pa
dist=dist_vec.length
if closest_dist==-1:
closest_dist=dist
elif dist<closest_dist:
closest_dist=dist
pl_xyz_s.append([pa,closest_dist/2])
else:
fdim=get_face_dimensions(target_object, faceid)
if fdim[0]<fdim[1]:
psc=fdim[0]/2
else:
psc=fdim[1]/2
pl_xyz_s.append([pl_xyz[0],psc])
return pl_xyz_s

def get_points_scales_tightpack(target_object,faceid,pl_xyz):
pl_xyz_l=list(pl_xyz)
#point data has a list of [pid,dist] sorted by dist
point_data=[]
if len(pl_xyz)>1:
for index,pa in enumerate(pl_xyz_l):
point_data.append([])
for neighbour_index,pb in enumerate(pl_xyz_l):
if pb!=pa:
dist_vec=pb-pa
dist=dist_vec.length
point_data[index].append((neighbour_index,dist))
point_data[index]=sorted(point_data[index], key=lambda dp: dp[1])
#data point is now sorted.
#priority list is a list ordered by pids' closest neighbour dists
priority_list=[]
for index,point_data_item in enumerate(point_data):
priority_list.append((index,point_data_item[0][1]))
priority_list=sorted(priority_list,key=lambda dp: dp[1])
#ringed points[dict]=ringsize
ringed_points=dict()
#starting with the first item in priority list
for point_id in priority_list:
#find the closest ring
ring_size=-1
for neighbour_point in point_data[point_id[0]]:
if neighbour_point[0] in ringed_points.keys():
ring_size=neighbour_point[1]-ringed_points[neighbour_point[0]]
break
for neighbour_point in point_data[point_id[0]]:
if neighbour_point[0] not in ringed_points.keys():
if ring_size==-1:
ring_size=neighbour_point[1]/2
elif ring_size>(neighbour_point[1]/2):
ring_size=neighbour_point[1]/2
break
ringed_points[point_id[0]]=ring_size
pl_xyz_l[point_id[0]]=[pl_xyz[point_id[0]],ring_size]
#pl_xyz is a list of [point, ringsize]
else:
#if there is only one point, use the face dimensions
fdim=get_face_dimensions(target_object, faceid)
if fdim[0]<fdim[1]:
psc=fdim[0]/2
else:
psc=fdim[1]/2
pl_xyz_l=[pl_xyz[0],psc]
return pl_xyz_l

def propose_dimensions(f=[],n=1):
#where fx,fy are face dimensions
#n is the number needed
#fx should be longer than fy
fx=f[0]
fy=f[1]
if fx>fy:
swap=0
else:
t=fy
fy=fx
fx=t
swap=1
nx=math.floor(fx*((n/(fx*fy))**0.5))
ny=math.ceil(fy*((n/(fx*fy))**0.5))
# so nx and ny are approximately the number in x and y directions
closest_pair=[math.fabs((n**0.5)-(nx*ny)),[nx,ny]]
count=0.5
tnx=tny=0
while (tnx*tny<n):
tnx=round(nx+count)
tny=round(ny+((ny/nx)*count))
t_cp=math.fabs((n**0.5)-(tnx*tny))
if t_cp<closest_pair[0]:
closest_pair=[t_cp,[tnx,tny]]
count+=0.5
if swap==1:
t=closest_pair[1][1]
closest_pair[1][1]=closest_pair[1][0]
closest_pair[1][0]=t
return closest_pair[1]

def getneighbouringfaces(faces,faceid,vertshare,set_allowed_faces):
#vertshare is the number of vertices which must be in common
cset=set(faces[faceid].vertices)
neighbourfaces=set()
for count in range(0,len(faces)):
vset=set(faces[count].vertices)
if len(cset&vset)>(vertshare-1):
neighbourfaces.add(count)
neighbourfaces=neighbourfaces&set_allowed_faces
return neighbourfaces

def build_neighbour_faces_dict(target_object,set_allowed_faces):
print('building a neighbour face lookup...')
neighbour_faces=dict()
for index,each_face in enumerate(target_object.data.faces):
neighbour_faces[index]=getneighbouringfaces(target_object.data.faces,index,1,set_allowed_faces)
print('done.')
return neighbour_faces

def dofacesneighbour(faces,faceset1,faceset2,set_allowed_faces):
for iface in faceset2:
neighbouringfaceset=getneighbouringfaces(faces,iface,1,set_allowed_faces)
if len(neighbouringfaceset&faceset1)>0:
return True
return False

def doesfaceneighbour(faces,faceset1,sface,set_allowed_faces):
neighbouringfaceset=getneighbouringfaces(faces,sface,1,set_allowed_faces)
if len(neighbouringfaceset&faceset1)>0:
return True
return False

def sortfacelistbyz(faces,facelist):
nfacelist=[]
sfacelist=[]
for ifaceref in facelist:
nfacelist.append((ifaceref,faces[ifaceref].center.z))
nfacelist=sorted(nfacelist, key=lambda nfaces: -nfaces[1])
for nface in nfacelist:
sfacelist.append(nface[0])
return sfacelist

def sortfacelistbyrandom(faces,facelist):
nfacelist=[]
sfacelist=[]
for ifaceref in facelist:
nfacelist.append((ifaceref,random.random()))
nfacelist=sorted(nfacelist, key=lambda nfaces: nfaces[1])
for nface in nfacelist:
sfacelist.append(nface[0])
return sfacelist

def getallneighbours(livefaces,neighbour_face_dict,set_allowed_faces):
neighbour_set=set()
for each_face in livefaces:
neighbour_set|=neighbour_face_dict[each_face]
neighbour_set-=livefaces
neighbour_set&=set_allowed_faces
return neighbour_set

#-------------------choose next algorithms----------------

def choosenextfaces_simpleincrease(target_object,faces,livefaces,includedfaces,set_allowed_faces):
#always arrives in object mode
#deselect everything
bpy.ops.object.select_all(action='DESELECT')
target_object.select=True
bpy.context.scene.objects.active=target_object

bpy.ops.object.editmode_toggle()
#make sure faces select is on - in edit mode
bpy.context.tool_settings.mesh_select_mode=[False,False,True]
#set the selection - in edit mode
selectset(faces,set(livefaces.keys()))
#increase the selection - in edit mode
bpy.ops.mesh.select_more()
bpy.ops.object.editmode_toggle()

#get the selection - out of edit mode
selection_list=list((selectget(faces)&set_allowed_faces)-includedfaces)

new_livefaces=dict()
for selected_item in selection_list:
new_livefaces[selected_item]=-1
return new_livefaces

def choosenextfaces_conways(faces,livefaces,neighbour_face_dict,set_allowed_faces):
new_live_faces=dict()
for each_face in list(livefaces.keys()):
n_neighbours=len(neighbour_face_dict[each_face]&set(livefaces.keys()))
print(n_neighbours)
if n_neighbours>1 and n_neighbours<4:
new_live_faces[each_face]=-1
list_neighbours=list(getallneighbours(livefaces.keys(),neighbour_face_dict,set_allowed_faces))
print(list_neighbours)
for each_face in list_neighbours:
n_neighbours=len(neighbour_face_dict[each_face]&set(livefaces.keys()))
if n_neighbours==3:
new_live_faces[each_face]=-1
return new_live_faces

def choosenextfaces(faces,livefaces,includedfaces,fork_chance,upward_chance,death_chance,growth_mode,set_allowed_faces):
tnlivefaces=dict()
livefaceslist=list(livefaces.keys())
for iface in livefaceslist:
nlivefaces=dict()
if random.random()<fork_chance:
willfork=1
else:
willfork=0
if random.random()<upward_chance:
willclimb=1
else:
willclimb=0
if random.random()<death_chance and len(tnlivefaces)>0:
print('dead end')
else:
#not dieing out
neighbourfacesset=getneighbouringfaces(faces,iface,2,set_allowed_faces)
if iface in neighbourfacesset:
neighbourfacesset.remove(iface)
neighbourfaceslist=list(neighbourfacesset)
acceptedneighbours=[]
#remove unsuitable options
if growth_mode==0 or growth_mode==1:
for neighbourface in neighbourfaceslist:
#so if the value()=-1 then remove all of the neighbours of the liveface
if doesfaceneighbour(faces,(includedfaces-set([iface]))-set([livefaces[iface]]),neighbourface,set_allowed_faces)==False:
acceptedneighbours.append(neighbourface)
elif growth_mode==2:
acceptedneighbours=neighbourfaceslist
if growth_mode==0:
#need a list >= length 1+willfork
while len(acceptedneighbours)<(willfork+1) and len(neighbourfacesset)>0:
#pop a random item from the neighbourfacesset into the acceptedneighbours list
tmpset=set(acceptedneighbours)
tmpset.add(neighbourfacesset.pop())
acceptedneighbours=list(tmpset)
#now acceptedneighbours contains a list of possible options
#if willclimb==1 then sort them by z
#else sort them randomly
if willclimb==1:
acceptedneighbours=sortfacelistbyz(faces,acceptedneighbours)
else:
acceptedneighbours=sortfacelistbyrandom(faces,acceptedneighbours)
#if willfork==1 then select first two
#if willfork==0 then select first one
if len(acceptedneighbours)>0:
nlivefaces[acceptedneighbours[0]]=iface
if willfork==1 and len(acceptedneighbours)>1:
nlivefaces[acceptedneighbours[1]]=iface
elif (growth_mode==1 or growth_mode==2) and len(acceptedneighbours)>0:
if willclimb==1:
acceptedneighbours=sortfacelistbyz(faces,acceptedneighbours)
else:
acceptedneighbours=sortfacelistbyrandom(faces,acceptedneighbours)
if len(acceptedneighbours)<(willfork+1):
willfork=0
nlivefaces[acceptedneighbours[0]]=iface
if willfork==1 and len(acceptedneighbours)>1:
nlivefaces[acceptedneighbours[1]]=iface
else:
print('could not fork')
tnlivefaces.update(nlivefaces)
return tnlivefaces

#-------------------end choose next algorithms----------------

def selectset(faces,selset):
#for index,each_face in enumerate(faces):
# if index in selset:
# each_face.select=1
# else:
# each_face.select=0
#slightly faster method
#call this in edit mode
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.editmode_toggle()
list_selset=list(selset)
for each_face in selset:
faces[each_face].select=True
bpy.ops.object.editmode_toggle()
return faces

def selectget(faces):
select_set=set()
for index,each_face in enumerate(faces):
if each_face.select==1:
select_set.add(index)
return select_set

def find_corresponding_mushroom_vertex_groups(mushrooms_list,target_object,prefix):
#prefix is OB for objects
#prefix is MA for materials, mushrooms_list=materials_list
vertex_group_lookup=dict()
mushroom_names=[each_mushroom.name for each_mushroom in mushrooms_list]
for index,vertex_group in enumerate(target_object.vertex_groups):
if vertex_group.name[:len(prefix)]==prefix and vertex_group.name[len(prefix):] in mushroom_names:
vertex_group_lookup[index]=vertex_group.name
return vertex_group_lookup

def get_face_groupchance_dict(target_object,faceid,mushrooms_list,lookup):
group_chance=dict()
for each_vertex in [target_object.data.vertices[vertex_index] for vertex_index in target_object.data.faces[faceid].vertices]:
for vertex_group in each_vertex.groups:
if vertex_group.group in lookup.keys():
if vertex_group.group in group_chance.keys():
group_chance[vertex_group.group]+=vertex_group.weight
else:
group_chance[vertex_group.group]=vertex_group.weight
return group_chance

def choose_mushroom_from_vertex_weights(mushrooms_list,lookup,return_random,group_chance):
#mushrooms list can be a list of materials
#lookup can be a material lookup
#returns a name only not a data path
#if return_random==True then a random object from the list is returned if no valid vert weights exist
#if return_random!=True then -1 is returned is [ditto]
list_group_chance=list(group_chance.keys())
sum_weights=0
for index in list_group_chance:
sum_weights+=group_chance[index]
if sum_weights>0:
result=random.random()*sum_weights
sum_weights=0
for index in list_group_chance:
sum_weights+=group_chance[index]
if sum_weights>result:
return lookup[index][2:]
return lookup[list(group_chance.keys())[index]][2:]
elif return_random:
return random.choice(mushrooms_list).name
else:
return -1

def place_many_on_face(target_object, faceid, mushrooms_list, number_to_distribute, distribute_method, distribute_jitter,pack_mode,vertex_group_lookup):
#distribute method
#0=uniform
#1=random
#2=jitter
if distribute_method==0:
place_points=get_points_jittered(target_object,faceid,number_to_distribute,distribute_jitter)
elif distribute_method==1:
place_points=get_points_random(target_object,faceid,number_to_distribute)
else:
print('incorrect jitter mode!')
return -1
if pack_mode=='TIGHT':
place_points_with_scales=get_points_scales_tightpack(target_object,faceid,place_points)
else:
place_points_with_scales=get_points_scales(target_object,faceid,place_points)
placed_data=[]
groupchance=get_face_groupchance_dict(target_object,faceid,mushrooms_list,vertex_group_lookup)
for point in place_points_with_scales:
mushroom_choice=bpy.data.objects[choose_mushroom_from_vertex_weights(mushrooms_list,vertex_group_lookup,True,groupchance)]
placed_data.append(place_on_face(target_object, faceid, mushroom_choice, point[0],mathutils.Vector((point[1],point[1],point[1]))))
return placed_data

def insert_keyframes(target_object,start_frame,shapes_lifecycle,frames_per_shape,jitter):
#target_object=target object name
#start frame = start frame number
#shapes available is an int referring to the max number of shapes s+str(a+1) for a in range(0,shapes_available)
#frames_per_shape - how much to advace between each shape
#jitter - 0>1 ... affects frames_per_shape
last_shape_name=''
#set frame
bpy.context.scene.frame_current=int(start_frame)
actual_start_frame=bpy.context.scene.frame_current=int(bpy.context.scene.frame_current+(frames_per_shape*(1+(2*random.random()*jitter))))
shapes_available=len(shapes_lifecycle)
for shape_id,current_shape_name in enumerate(shapes_lifecycle):
if shape_id<(shapes_available-1):
next_shape_name=shapes_lifecycle[shape_id+1]
target_object.data.shape_keys.keys[next_shape_name].value=0
target_object.data.shape_keys.keys[next_shape_name].keyframe_insert('value')
#target_object.keyframe_insert('data.shape_keys.keys[\"'+next_shape_name+'\"].value')
if last_shape_name!='':
target_object.data.shape_keys.keys[last_shape_name].value=0
target_object.data.shape_keys.keys[last_shape_name].keyframe_insert('value')
#target_object.keyframe_insert('data.shape_keys.keys[\"'+last_shape_name+'\"].value')
target_object.data.shape_keys.keys[current_shape_name].value=1
target_object.data.shape_keys.keys[current_shape_name].keyframe_insert('value')
#target_object.keyframe_insert('data.shape_keys.keys[\"'+current_shape_name+'\"].value')
#advance frames
bpy.context.scene.frame_current=int(bpy.context.scene.frame_current+(frames_per_shape*(1+(2*random.random()*jitter))))
last_shape_name=current_shape_name
return [actual_start_frame,int(bpy.context.scene.frame_current)]

def add_z_up_driver(mushroom_object,bend_key_name,start_frame,end_frame):
driver_curve=mushroom_object.data.shape_keys.keys[bend_key_name].driver_add('value')
#driver_curve=mushroom_object.driver_add('data.shape_keys.keys[\"'+bend_key_name+'\"].value')
z_var=driver_curve.driver.variables.new()
z_var.type='SINGLE_PROP'
z_var.targets[0].id_type='OBJECT'
z_var.targets[0].id=mushroom_object
#this line may require editing for better results - [0] is y axis rot, [11] is combined xy
z_var.targets[0].data_path='matrix_world[0]'
if start_frame!=-1 and end_frame!=-1:
current_frame_var=driver_curve.driver.variables.new()
current_frame_var.type='SINGLE_PROP'
current_frame_var.targets[0].id_type='SCENE'
current_frame_var.targets[0].id=bpy.context.scene
current_frame_var.targets[0].data_path='frame_current'
mid_frame=(start_frame+end_frame)/2
range_frame=(end_frame-start_frame)
driver_curve.driver.expression='(1-'+z_var.name+')*(2.7182**(-1*((('+current_frame_var.name+'-'+str(mid_frame)+')/('+str(range_frame)+'*0.2))**2)))'
else:
driver_curve.driver.expression='1-'+z_var.name

def insert_keyframes_multi(mushrooms_list,start_frame,mushrooms_shapes_database,lifetime,lifetime_jitter,start_frame_variation,start_frame_jitter):
#try '["blah"]' or "['dfdf']"
target_frame=start_frame
for mushroom_object in mushrooms_list:
#load shapes_lifecycle etc. based on the mushroom.name
shapes_lifecycle=mushrooms_shapes_database[0][mushroom_object[1]]
shapes_random=mushrooms_shapes_database[1][mushroom_object[1]]
shapes_bend=mushrooms_shapes_database[2][mushroom_object[1]]
frame_spread=[]
if shapes_lifecycle==['USEEXIST']:
frame_spread=reanimate_actions(mushroom_object[0],target_frame,target_frame+lifetime,lifetime_jitter)
elif shapes_lifecycle!=[]:
frame_spread=insert_keyframes(mushroom_object[0],target_frame,shapes_lifecycle,lifetime/len(shapes_lifecycle),lifetime_jitter)
if frame_spread!=[]:
#if keyframes have been inserted/edited
#add the random shape key data
halfway_frame=int((frame_spread[0]+frame_spread[1])/2)
if len(shapes_random)>0:
random_shape=shapes_random[int(random.random()*(len(shapes_random)-1))]
bpy.context.scene.frame_current=frame_spread[0]
mushroom_object[0].data.shape_keys.keys[random_shape].value=0
mushroom_object[0].data.shape_keys.keys[random_shape].keyframe_insert('value')
#mushroom_object[0].keyframe_insert('data.shape_keys.keys[\"'+random_shape+'\"].value')
bpy.context.scene.frame_current=halfway_frame
mushroom_object[0].data.shape_keys.keys[random_shape].value=random.random()
mushroom_object[0].data.shape_keys.keys[random_shape].keyframe_insert('value')
#mushroom_object[0].keyframe_insert('data.shape_keys.keys[\"'+random_shape+'\"].value')
bpy.context.scene.frame_current=frame_spread[1]
mushroom_object[0].data.shape_keys.keys[random_shape].value=0
mushroom_object[0].data.shape_keys.keys[random_shape].keyframe_insert('value')
#mushroom_object[0].keyframe_insert('data.shape_keys.keys[\"'+random_shape+'\"].value')
if len(shapes_bend)>0:
bend_shape=shapes_bend[int(random.random()*(len(shapes_bend)-1))]
#add a gaussion rise and fall with y rotation driven shape key
add_z_up_driver(mushroom_object[0],bend_shape,frame_spread[0],frame_spread[1])
else:
#basically theres no animation on the mushrooms
#just set a shape key value for randome
if len(shapes_random)>0:
random_shape=shapes_random[int(random.random()*(len(shapes_random)-1))]
mushroom_object[0].data.shape_keys.keys[random_shape].value=random.random()
if len(shapes_bend)>0:
bend_shape=shapes_bend[int(random.random()*(len(shapes_bend)-1))]
#add a gaussion rise and fall with y rotation driven shape key
#calling the z driver adder with -1 and -1 tells it to skip the gaussian curve falloff and just add a simple driver
add_z_up_driver(mushroom_object[0],bend_shape,-1,-1)
target_frame+=start_frame_variation*(1+(2*random.random()*start_frame_jitter))

def add_mushroom_shrinkwrap(target_object,mushroom_object_list,vertex_group):
#this is only called if vertex group!=-1
for mushroom_object in mushroom_object_list:
mushroom_shrinkwrap_modifier=mushroom_object[0].modifiers.new(type='SHRINKWRAP', name='mushroom_shrinkwrap')
mushroom_shrinkwrap_modifier.target=target_object
mushroom_shrinkwrap_modifier.vertex_group=vertex_group

def add_mushrooms_to_group(mushroom_object_list,group_name):
for mushroom_object in mushroom_object_list:
bpy.data.groups[group_name].objects.link(mushroom_object[0])

def build_materials_list(mushrooms_list):
materials_list=[]
for each_mushroom in mushrooms_list:
for each_material in each_mushroom.material_slots:
materials_list.append(each_material.material)
return materials_list

def set_materials(mushrooms_list,target_object,faceid,randomize_material,materials_list,vertex_group_lookup_materials):
groupchance=get_face_groupchance_dict(target_object,faceid,materials_list,vertex_group_lookup_materials)
for mushroom_object in mushrooms_list:
for material_slot in mushroom_object[0].material_slots:
if 'material' in dir(material_slot):
material_slot_new=choose_mushroom_from_vertex_weights(materials_list,vertex_group_lookup_materials,False,groupchance)
if material_slot_new!=-1:
material_slot.material=bpy.data.materials[material_slot_new]
elif randomize_material:
material_slot.material=random.choice(materials_list)

def get_allowed_faces(target_object,vertex_group_name):
if bpy.context.mode!='EDIT_MESH':
bpy.context.scene.objects.active=target_object
bpy.ops.object.editmode_toggle()
#now in edit mode
bpy.context.tool_settings.mesh_select_mode=[True,False,False]
if vertex_group_name!='-1':
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.vertex_group_set_active(group=vertex_group_name)
bpy.ops.object.vertex_group_select()
#leave edit mode
bpy.ops.object.editmode_toggle()
set_allowed_faces=selectget(target_object.data.faces)
else:
#leave edit mode
bpy.ops.object.editmode_toggle()
set_allowed_faces=set(range(0,len(target_object.data.faces)))
return set_allowed_faces

def find_live_faces(selected_faces,set_allowed_faces):
#returns a dict
#keys are the live faces keys, values are -1 (indicating initial state)
livefaces=dict()
for index in selected_faces:
if index in set_allowed_faces:
livefaces[index]=-1
if len(livefaces.keys())==0:
print('Nothing in the masking group was selected... randomizing... ')
livefaces[random.choice(list(set_allowed_faces))]=-1
print ('Selection is: '+str(livefaces))
return livefaces

def save_timing(target_object,faces,livefaces,timing_group_name,value):
if bpy.context.mode=='EDIT_MESH':
bpy.ops.object.editmode_toggle()
was_in_edit_mode=True
else:
was_in_edit_mode=False
bpy.context.scene.objects.active=target_object
bpy.ops.object.vertex_group_set_active(group=timing_group_name)
selectset(faces,set(livefaces.keys()))
bpy.ops.object.editmode_toggle()
#now in edit mode
bpy.context.tool_settings.vertex_group_weight=value
bpy.ops.object.vertex_group_assign(new=False)
if was_in_edit_mode and bpy.context.mode!='EDIT_MESH':
bpy.ops.object.editmode_toggle()
elif (not was_in_edit_mode) and bpy.context.mode=='EDIT_MESH':
bpy.ops.object.editmode_toggle()

#-------------new animation defs---------------

def prepend_data_path(list_fcurve,data_path_prefix):
#data path prefix does not include the dot
for each_fcurve in list_fcurve:
each_fcurve.data_path=data_path_prefix+'.'+each_fcurve.data_path

def copy_fcurves(mushroom_object,host_action,data_path_prefix,time_scale,time_shift,time_jitter):
#this def is for when blender gets fixed
#time_scale is time multiplier about zero, followed by adding time_shift
time_scale+=time_jitter*(time_scale*random.uniform(-1,1))
mushroom_object.animation_data_create()
mushroom_object.animation_data.action=bpy.data.actions.new('mushroom_AC')
for each_fcurve in host_action.fcurves:
copy_fcurve=mushroom_object.animation_data.action.fcurves.new(data_path_prefix+'.'+each_fcurve.data_path)
copy_fcurve.data_path=(data_path_prefix+'.'+each_fcurve.data_path)
#adjust the keyframe on the newly created fcurve
last_time=(time_scale*each_fcurve.keyframe_points[0].co.x)+time_shift
for o_kp in each_fcurve.keyframe_points:
new_time=(time_scale*o_kp.co.x)+time_shift
new_time=(new_time*(1-time_jitter))+((new_time-((new_time-last_time)*random.random()))*time_jitter)
print(copy_fcurve.keyframe_points.add(0,0))
c_kp=copy_fcurve.keyframe_points.add(new_time, o_kp.co.y, True,False,True)
c_kp.interpolation=o_kp.interpolation
c_kp.handle_left=o_kp.handle_left
c_kp.handle_left_type=o_kp.handle_left_type
c_kp.handle_right=o_kp.handle_right
c_kp.handle_right_type=o_kp.handle_right_type
last_time=new_time

def adjust_fcurves(host_action,time_scale,time_shift,time_jitter):
for each_fcurve in host_action.fcurves:
last_time=(time_scale*each_fcurve.keyframe_points[0].co.x)+time_shift
for o_kp in each_fcurve.keyframe_points:
new_time=(time_scale*o_kp.co.x)+time_shift
new_time=(new_time*(1-time_jitter))+((new_time-((new_time-last_time)*random.random()))*time_jitter)
o_kp.co.x=new_time
last_time=new_time

def reanimate_actions(mushroom_object,start_frame,end_frame,time_jitter):
#assuming the duplicate is linked, transfer the animation data to object level
if 'action' in dir(mushroom_object.data.shape_keys.animation_data):
#copy_fcurves(mushroom_object,mushroom_object.data.shape_keys.animation_data.action,'data.shape_keys',(end_frame-start_frame)/100,start_frame,time_jitter)
adjust_fcurves(mushroom_object.data.shape_keys.animation_data.action,(end_frame-start_frame)/100,start_frame,time_jitter)
for mat_index,material_slot in enumerate(mushroom_object.material_slots):
if 'material' in dir(material_slot):
if 'animation_data' in dir(material_slot.material):
if 'action' in dir(material_slot.material.animation_data):
#copy_fcurves(mushroom_object,material_slot.material.animation_data.action,'material_slot['+str(mat_index)+'].material',(end_frame-start_frame)/100,start_frame,time_jitter)
material_slot.material=material_slot.material.copy()
adjust_fcurves(material_slot.material.animation_data.action,(end_frame-start_frame)/100,start_frame,time_jitter)
for tex_index,texture_slot in enumerate(material_slot.material.texture_slots):
if 'texture' in dir(texture_slot):
if 'animation_data' in dir(texture_slot.texture):
if 'action' in dir(texture_slot.texture.animation_data):
texture_slot.texture=texture_slot.texture.copy()
#copy_fcurves(mushroom_object,texture_slot.texture.animation_data.action,'material_slot['+str(mat_index)+'].material.texture_slot['+str(tex_index)+'].texture',(end_frame-start_frame)/100,start_frame,time_jitter)
adjust_fcurves(texture_slot.texture.animation_data.action,(end_frame-start_frame)/100,start_frame,time_jitter)
return [int(start_frame),int(end_frame)]


#-----------------------------------------------

def generate_mushrooms_shapes_database(mushrooms_list,target_object,shape_key_dict_prefixes):
#list[0=lifecycle,1=random,2=bend]=dict[mushroom_object.name]=[shapes...]
mushroom_shapes_database=[dict(),dict(),dict()]
for mushroom_object in mushrooms_list:
if target_object.mushroomer_lifecycle_key_set!="IGNORE" and target_object.mushroomer_lifecycle_key_set!="USEEXIST":
mushroom_shapes_database[0][mushroom_object.name]=[target_object.mushroomer_lifecycle_key_set+str(dict_list_item[0]) for dict_list_item in shape_key_dict_prefixes[mushroom_object.name][target_object.mushroomer_lifecycle_key_set]]
elif target_object.mushroomer_lifecycle_key_set=="USEEXIST":
mushroom_shapes_database[0][mushroom_object.name]=['USEEXIST']
else: mushroom_shapes_database[0][mushroom_object.name]=[]
if target_object.mushroomer_random_key_set!="IGNORE":
mushroom_shapes_database[1][mushroom_object.name]=[target_object.mushroomer_random_key_set+str(dict_list_item[0]) for dict_list_item in shape_key_dict_prefixes[mushroom_object.name][target_object.mushroomer_random_key_set]]
else: mushroom_shapes_database[1][mushroom_object.name]=[]
if target_object.mushroomer_bend_key_set!="IGNORE":
mushroom_shapes_database[2][mushroom_object.name]=[target_object.mushroomer_bend_key_set+str(dict_list_item[0]) for dict_list_item in shape_key_dict_prefixes[mushroom_object.name][target_object.mushroomer_bend_key_set]]
else: mushroom_shapes_database[2][mushroom_object.name]=[]
print(mushroom_shapes_database)
return mushroom_shapes_database

#begin UI code

def find_mushrooms(context):
mushrooms_list=[]
for try_object in context.selected_objects:
if try_object!=context.active_object:
mushrooms_list.append(try_object)
return mushrooms_list


class Mushroomer(bpy.types.Operator):
'''plants mushrooms on your mesh'''
bl_idname = "mesh.mushroom_maker"
bl_label = "Mushroomer"
bl_options = {'REGISTER', 'UNDO'}
dialog_width=400

title_label_text=''
mushrooms_list=[]
shape_key_dict_prefixes=dict()

target_object_name=bpy.props.StringProperty(name="Target Object", description="Object receiving the mushrooms",default="")
iterations=bpy.props.IntProperty(name="Generations of Mushrooms", description="Number of generations",default=5,min=0,max=200)
global_start_frame=bpy.props.IntProperty(name="Start Frame", description="Start Frame",default=1,min=0)
children_first_generation=bpy.props.IntProperty(name="1st Generation Mushroom Family Size", description="1st Generation Mushroom Family Size",default=1,min=0)
children_last_generation=bpy.props.IntProperty(name="Last Generation Mushroom Family Size", description="Last Generation Mushroom Family Size",default=5,min=0)
children_mode_list=[("CHM","Family Size Growth Mode","Family Size Growth Mode"),
("0","Constant","Constant"),
("1","Linear","Linear"),
("2","Square","Square"),
("3","Fibonacci","Fibonacci"),
("4","Square Root","Square Root")]
children_mode=bpy.props.EnumProperty(attr="children_mode",items=children_mode_list,default="1")
children_jitter=bpy.props.FloatProperty(name="Family Size Jitter", description="Number of generations",default=0.1,min=0,max=1,step=0.1,precision=2)
generational_frame_advance=bpy.props.IntProperty(name="Child Bearing Age", description="Child Bearing Age",default=10,min=2)
generational_frame_advance_jitter=bpy.props.FloatProperty(name="Child Bearing Age Jitter", description="Child Bearing Age Jitter",default=0.1,min=0,max=1,step=0.1,precision=2)
instance_lifetime=bpy.props.IntProperty(name="Death Age", description="Death Age",default=20,min=2)
instance_lifetime_jitter=bpy.props.FloatProperty(name="Death Age Jitter", description="Death Age Jitter",default=0.1,min=0,max=1,step=0.1,precision=2)
sub_generational_frame_spread=bpy.props.IntProperty(name="Generational Spread (frames)", description="The maximum length of time from the first birth to the last birth",default=10,min=0)
sub_generational_frame_spread_jitter=bpy.props.FloatProperty(name="Generational Spread Jitter", description="Generational Spread Jitter",default=0.1,min=0,max=1,step=0.1,precision=2)
distribute_method_list=[("DM","Distribution Method","How the mushrooms are distributed on each face"),
("0","Uniform","Uniform"),
("1","Random","Random")]
distribute_method=bpy.props.EnumProperty(attr="distribute_method",items=distribute_method_list,default="1")
distribute_jitter=bpy.props.FloatProperty(name="Distribution Jitter", description="Distribution Jitter",default=0.5,min=0,max=1,step=0.1,precision=2)
growth_mode_list=[("GM","Growth Method","How the mushrooms spread out from their start face"),
("0","Crossed Paths","Crossed Paths"),
("1","Forked Paths","Forked Paths"),
("2","Splurge","Splurge"),
("3","Conway's Game of Life","Game of Life"),
("4","Simple Selection Growth","Simple Selection Growth")]
growth_mode=bpy.props.EnumProperty(attr="growth_mode",items=growth_mode_list,default="1")
death_chance=bpy.props.FloatProperty(name="Family Wipeout Chance", description="Family Wipeout Chance",default=0.1,min=0,max=1,step=0.1,precision=2)
upward_chance=bpy.props.FloatProperty(name="Upwards Attraction (climb chance)", description="Probability children will choose to move up",default=0.7,min=0,max=1,step=0.1,precision=2)
fork_chance=bpy.props.FloatProperty(name="Probability of Family/Fork Split", description="Probability the family will split into two different groups of children",default=0.5,min=0,max=1,step=0.1,precision=2)
pack_mode_list=[("PM","Packing Method","Algorithm for Packing/Sizing Mushrooms"),
("LAZY","Lazy Pack (faster)","Lazy Pack - Half Nearest Neighbour Distance"),
("TIGHT","Tight Pack","Tight Pack")]
pack_mode=bpy.props.EnumProperty(attr="pack_mode",items=pack_mode_list,default="LAZY")
randomize_materials=bpy.props.BoolProperty(name="Randomize Materials",description="if ticked materials from any slot on any object an be shuffled between objects: beware UV people!",default=False)


#in invoke to do automatically...
#get the first selected but not active mushroom -> name -> self.properties.ob_to_distribute....
def invoke(self, context, event):
#all the generator functions stoe their result on the target object
#but take info from the list of mushrooms
self.properties.target_object_name=context.active_object.name
self.mushrooms_list=find_mushrooms(context)
self.title_label_text='Mushrooming: '
for mushroom_object in self.mushrooms_list:
self.title_label_text=self.title_label_text+mushroom_object.name+', '
self.title_label_text=self.title_label_text.rstrip(', ')+' all over '+self.properties.target_object_name
self.shape_key_dict_prefixes=build_prefixes_enum(self.mushrooms_list,bpy.data.objects[self.properties.target_object_name])
build_vertex_group_enum(self.mushrooms_list,bpy.data.objects[self.properties.target_object_name])
build_group_name_text(bpy.data.objects[self.properties.target_object_name])
build_face_limit_enum(bpy.data.objects[self.properties.target_object_name])
self.properties.global_start_frame=bpy.context.scene.frame_current
wm = context.manager
wm.invoke_props_dialog(self, self.dialog_width)
return {'RUNNING_MODAL'}

def draw(self, context):
layout = self.layout
props = self.properties

layout.label(text=self.title_label_text)
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_grouping", text="Group To Append Mushrooms To")
layout.prop(props, "iterations")
layout.prop(props, "global_start_frame")

layout.label(text="Family Settings")
row = layout.row()
split = row.split(percentage=0.5)
colL = split.column()
colR = split.column()
colL.prop(props, "children_first_generation")
colR.prop(props, "children_last_generation")
colL.prop(props, "children_mode")
colR.prop(props, "children_jitter")
colL.prop(props, "generational_frame_advance")
colR.prop(props, "generational_frame_advance_jitter")
colL.prop(props, "instance_lifetime")
colR.prop(props, "instance_lifetime_jitter")
colL.prop(props, "sub_generational_frame_spread")
colR.prop(props, "sub_generational_frame_spread_jitter")
colL.prop(props, "distribute_method")
colR.prop(props, "distribute_jitter")
colL.prop(props, "pack_mode")
colR.prop(props, "randomize_materials")

layout.label(text="Spread & Conquer Settings")
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_limit_group", text="Limit to Vertex Group")
row = layout.row()
split = row.split(percentage=0.5)
colL = split.column()
colR = split.column()
colL.prop(props, "growth_mode")
colR.prop(props, "fork_chance")
colL.prop(props, "upward_chance")
colR.prop(props, "death_chance")

layout.label(text="Shape Key Settings")
layout.label(text="NB. Pre-animated mushrooms must have actions from frames 0-100")
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_lifecycle_key_set", text="Animation: Manual / Lifecycle Shape Set")
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_random_key_set", text="Random Shape Set")
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_bend_key_set", text="Bend Upwards Shape Set")
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_shrinkwrap_vg", text="Vertex Group for Shrinkwrap")

def execute(self,context):
if len(self.mushrooms_list)==0:
print('Select a mushroom first, you monkey.')
return {'FINISHED'}
target_object=bpy.data.objects[self.properties.target_object_name]
mushroom_group_name=target_object.mushroomer_grouping
if mushroom_group_name!='':
if (mushroom_group_name not in bpy.data.groups):
bpy.data.groups.new(mushroom_group_name)
if (("MT"+mushroom_group_name) not in target_object.vertex_groups):
timing_group_name=(target_object.vertex_groups.new("MT"+mushroom_group_name)).name
mushrooms_shapes_database=generate_mushrooms_shapes_database(self.mushrooms_list,target_object,self.shape_key_dict_prefixes)
generation_frame=self.properties.global_start_frame
vertex_group_lookup=find_corresponding_mushroom_vertex_groups(self.mushrooms_list,target_object,'OB')
materials_list=build_materials_list(self.mushrooms_list)
vertex_group_lookup_materials=find_corresponding_mushroom_vertex_groups(materials_list,target_object,'MA')
includedfaces=set()
#save the selection before the other defs mess with it
faces=target_object.data.faces
selected_faces=list(selectget(faces))
set_allowed_faces=get_allowed_faces(target_object,str(target_object.mushroomer_limit_group))
#if nothing is selected then randomise the selection
#livefaces is a dict... livefaces[faceid]=
livefaces=find_live_faces(selected_faces,set_allowed_faces)
if self.properties.growth_mode=="3":
neighbour_face_dict=build_neighbour_faces_dict(target_object,set_allowed_faces)
if len(livefaces)>0:
includedfaces|=set(livefaces.keys())
for count in range(0,self.properties.iterations):
print ('iteration:'+str(count))

if self.properties.growth_mode=="3":
livefaces=choosenextfaces_conways(faces,livefaces,neighbour_face_dict,set_allowed_faces)
populate_list=livefaces.keys()
elif self.properties.growth_mode=="4":
livefaces=choosenextfaces_simpleincrease(target_object,faces,livefaces,includedfaces,set_allowed_faces)
populate_list=list(set(livefaces.keys())-includedfaces)
else:
livefaces=choosenextfaces(faces,livefaces,includedfaces,self.properties.fork_chance,self.properties.upward_chance,self.properties.death_chance,int(self.properties.growth_mode),set_allowed_faces)
populate_list=list(set(livefaces.keys())-includedfaces)

for face_to_populate in populate_list:
children_this_generation=get_number_for_generation(count,self.properties.iterations,int(self.properties.children_mode),self.properties.children_first_generation,self.properties.children_last_generation,self.properties.children_jitter)
placed_objects=place_many_on_face(target_object, face_to_populate, self.mushrooms_list,children_this_generation, int(self.properties.distribute_method), self.properties.distribute_jitter,self.properties.pack_mode,vertex_group_lookup)
set_materials(placed_objects,target_object,face_to_populate,self.properties.randomize_materials,materials_list,vertex_group_lookup_materials)
#placed objects is a list of [object structures (not names),names of types]
insert_keyframes_multi(placed_objects,generation_frame,mushrooms_shapes_database,self.properties.instance_lifetime,self.properties.instance_lifetime_jitter,self.properties.sub_generational_frame_spread/children_this_generation,self.properties.sub_generational_frame_spread_jitter)
if target_object.mushroomer_shrinkwrap_vg!='-1':
add_mushroom_shrinkwrap(target_object,placed_objects,target_object.mushroomer_shrinkwrap_vg)
if mushroom_group_name!='':
add_mushrooms_to_group(placed_objects,mushroom_group_name)

if mushroom_group_name!='':
save_timing(target_object,faces,livefaces,timing_group_name,count/self.properties.iterations)
#if everything has died out then regenerate...
print('livefaces',livefaces)
if len(livefaces)<1:
if self.properties.growth_mode!="3":
print('regenerating...')
tmplist=list(includedfaces)
livefaces[random.choice(tmplist)]=-1
#so if the value()=-1 then all of the neighbouring faces can be considered for expansion onto
else:
print('everything died, Conway\'s game over.')
break
includedfaces|=set(livefaces.keys())
generation_frame+=self.properties.generational_frame_advance*(1+(2*random.random()*self.properties.generational_frame_advance_jitter))
#display the last set of included faces
if bpy.context.mode!='EDIT_MESH':
bpy.ops.object.editmode_toggle()
bpy.context.tool_settings.mesh_select_mode=[False,False,True]
faces=selectset(faces,includedfaces)
else:
print('The masking group is empty - can\'t contnue!')
return {'FINISHED'}


#end UI code

class MushroomerRetime(bpy.types.Operator):
'''retimes mushrooms in a given group from group weights'''
bl_idname = "mesh.mushroom_retimer"
bl_label = "Mushroomer Retimer"
bl_options = {'REGISTER', 'UNDO'}
dialog_width=250

target_object_name=bpy.props.StringProperty(name="Target Object", description="Object receiving the mushrooms",default="")
global_start_frame=bpy.props.IntProperty(name="Start Frame", description="Start Frame",default=1,min=0)

def invoke(self, context, event):
#all the generator functions stoe their result on the target object
#but take info from the list of mushrooms
self.properties.target_object_name=context.active_object.name
build_timing_group_enum(bpy.data.objects[self.properties.target_object_name])
self.properties.global_start_frame=bpy.context.scene.frame_current
wm = context.manager
wm.invoke_props_dialog(self, self.dialog_width)
return {'RUNNING_MODAL'}

def draw(self, context):
layout = self.layout
props = self.properties
layout.label(text="Retiming "+self.properties.target_object_name)
layout.prop(props, "global_start_frame")
layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_timing_vg", text="Vertex Group for Retiming")

def execute(self,context):

return {'FINISHED'}
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. #  This program is free software; you can redistribute it and/or
  4. #  modify it under the terms of the GNU General Public License
  5. #  as published by the Free Software Foundation; either version 2
  6. #  of the License, or (at your option) any later version.
  7. #
  8. #  This program is distributed in the hope that it will be useful,
  9. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. #  GNU General Public License for more details.
  12. #
  13. #  You should have received a copy of the GNU General Public License
  14. #  along with this program; if not, write to the Free Software Foundation,
  15. #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18.  
  19. # Josh Wedlake, Tube Project
  20.  
  21. import random
  22. import bpy
  23. import math
  24. import mathutils
  25. import bpy
  26.  
  27.  
  28. def auto_detect_prefixes(mushroom_object):
  29.     keys_list=list(mushroom_object.data.shape_keys.keys)
  30.     dict_prefixes=dict()
  31.     for index,each_key in enumerate(keys_list):
  32.         name=each_key.name
  33.         prefix=''
  34.         for b in [[name[(len(name)-(a+1)):],name[:(len(name)-(a+1))]] for a in range(0,len(name))]:
  35.             if b[0].isdigit():
  36.                 prefix=b[1]
  37.                 available_value=int(b[0])
  38.             else:
  39.                 break
  40.         if prefix !='':
  41.             if prefix not in dict_prefixes:
  42.                 dict_prefixes[prefix]=[[available_value,index]]
  43.             else:
  44.                 dict_prefixes[prefix].append([available_value,index])
  45.     list_dict_keys=list(dict_prefixes.keys())
  46.     for d_key in list_dict_keys:
  47.         dict_prefixes[d_key]=sorted(dict_prefixes[d_key],key=lambda list_item: list_item[0])
  48.     return dict_prefixes
  49.  
  50. def build_face_limit_enum(target_object):
  51.     vertex_group_list=[]
  52.     vertex_group_list.append(('TOVGM','Target Object Vertex Group Mask','Target Object Vertex Group Mask'))
  53.     for vertex_group in target_object.vertex_groups:
  54.         vertex_group_list.append((vertex_group.name, vertex_group.name, vertex_group.name))
  55.     vertex_group_list.append(('-1','(Do not use: Mushrooms everywhere!)','no limits'))
  56.     target_object.EnumProperty(attr="mushroomer_limit_group",items=vertex_group_list,default='-1')
  57.  
  58. def build_vertex_group_enum(mushroom_list,target_object):
  59.     set_valid_groups=set()
  60.     for mushroom_object in mushroom_list:
  61.         for vertex_group in mushroom_object.vertex_groups:
  62.             set_valid_groups.add(vertex_group.name)
  63.     vertex_group_list=[]
  64.     vertex_group_list.append(('VGID','Mushroom Vertex Group for Shrinkwrap','Mushroom Vertex Group for Shrinkwrap'))
  65.     vertex_group_list.append(('-1','(Do not use shrinkwrapping)','no shrinkwrapping'))
  66.     for vertex_group_name in list(set_valid_groups):
  67.         vertex_group_list.append((vertex_group_name, vertex_group_name, vertex_group_name))
  68.     target_object.EnumProperty(attr="mushroomer_shrinkwrap_vg",items=vertex_group_list,default='-1')
  69.  
  70. def build_timing_group_enum(target_object):
  71.     vertex_group_list=[]
  72.     vertex_group_list.append(('TVG','Vertex Group for Retiming','Vertex Group for Retiming'))
  73.     for vertex_group in target_object.vertex_groups:
  74.         if vertex_group.name.startswith('MT'):
  75.             if vertex_group.name[2:] in bpy.data.groups:
  76.                 vertex_group_list.append((vertex_group.name, vertex_group.name, vertex_group.name))
  77.     vertex_group_list.append(('-1','(Do not use: Mushrooms everywhere!)','no limits'))
  78.     target_object.EnumProperty(attr="mushroomer_timing_vg",items=vertex_group_list,default='-1')
  79.    
  80. def build_group_name_text(target_object):
  81.     #find out if there are any groups named mushroomer_group_x, where x is an int.  Find the lowest free int
  82.     lowest_free_id=-1
  83.     for group in bpy.data.groups:
  84.         if group.name.startswith('mushroomer_group_'):
  85.             this_id=group.name.lstrip('mushroomer_group_')
  86.             if this_id.isdigit():
  87.                 this_id_int=int(this_id)
  88.                 if this_id_int>lowest_free_id:
  89.                     lowest_free_id=this_id_int
  90.     group_name='mushroomer_group_'+str(lowest_free_id+1)
  91.     target_object.StringProperty(attr="mushroomer_grouping", description="Group To Add Mushrooms To",default=group_name)
  92.  
  93. def build_prefixes_enum(mushroom_list,target_object):
  94.     #builds a list of valid shape prefixes for the enum
  95.     #builds a dictionary > mushroom name > prefixes > valid numerical values
  96.     dict_all_prefixes=dict()
  97.     set_valid_prefixes=set()
  98.     for mushroom_object in mushroom_list:
  99.         mushroom_dict=auto_detect_prefixes(mushroom_object)
  100.         dict_all_prefixes[mushroom_object.name]=mushroom_dict
  101.         set_valid_prefixes|=set(mushroom_dict.keys())
  102.     prefixes_list=[]
  103.     prefixes_list.append(('LSK','Life cycle shape key set','Life cycle shape key set'))
  104.     prefixes_list.append(("IGNORE",'Don\'t Use','Don\'t Use'))
  105.     for prefix in list(set_valid_prefixes):
  106.         prefixes_list.append((prefix,prefix+'... [as available]',prefix))
  107.     random_prefixes_list=prefixes_list[:]
  108.     bend_prefixes_list=prefixes_list[:]
  109.     random_prefixes_list[0]=('RSK','Random variation shape key set','Random variation shape key set')
  110.     bend_prefixes_list[0]=('BSK','Bend variation shape key set','Random variation shape key set')
  111.     prefixes_list.append(("USEEXIST",'Use Pre-existing Shape-Key & Material Actions as Meta-Strips','Use Pre-existing Shape-Key, Material & Texture Actions'))
  112.     target_object.EnumProperty(attr="mushroomer_lifecycle_key_set",items=prefixes_list,default='USEEXIST')
  113.     target_object.EnumProperty(attr="mushroomer_random_key_set",items=random_prefixes_list,default='IGNORE')
  114.     target_object.EnumProperty(attr="mushroomer_bend_key_set",items=bend_prefixes_list,default='IGNORE')
  115.     return dict_all_prefixes
  116.  
  117. def get_number_for_generation(generation,generations_max,mode,min,max,jitter):
  118.     import random,math
  119.     #gen is input
  120.     #pass a minimum and a maximum number
  121.     #jitter is % random error
  122.     #modes 0-constant,1-linear,2-square,3-fibbonacci,4-root
  123.     jitter_amount=(((random.random()*2)-1)*jitter)+1
  124.     if mode==0:
  125.         value=(jitter_amount*0.5*(max-min))+min
  126.     elif mode==1:
  127.         value=((generation/generations_max)*(max-min)*jitter_amount)+min
  128.     elif mode==2:
  129.         value=(((generation**2)/(generations_max**2))*(max-min)*jitter_amount)+min
  130.     elif mode==3:
  131.         root5 = math.sqrt(5)
  132.         fib_c=(0.5 + root5/2)
  133.         fib_g=fib_c**generation
  134.         fib_c=int(0.5+fib_g/root5)
  135.         fib_g=fib_c**generations_max
  136.         fib_max=int(0.5+fib_g/root5)
  137.         if fib_max==0:
  138.             fib_max=1
  139.         value=((fib_c/fib_max)*(max-min)*jitter_amount)+min
  140.     elif mode==4:
  141.         root_max=generations_max**0.5
  142.         root_c=generation**0.5
  143.         value=((root_c/root_max)*(max-min)*jitter_amount)+min
  144.     return int(value)
  145.        
  146. def make_vertex_parent(target_object,ob_to_distribute,faceid):
  147.     #find the closest 3 verts
  148.     face_verts=target_object.data.faces[faceid].vertices
  149.     vert_list=[]
  150.     for each_vert in face_verts:
  151.         vert_list.append([(ob_to_distribute.location-(target_object.matrix_world*target_object.data.vertices[each_vert].co)).length,each_vert])
  152.     sorted_verts=sorted(vert_list,key=lambda vertinfo: vertinfo[0])
  153.     verts_to_parent=[sorted_verts[0][1],sorted_verts[1][1],sorted_verts[2][1]]
  154.     #only select the target
  155.     bpy.ops.object.select_all(action='DESELECT')
  156.     target_object.select=True
  157.     if bpy.context.mode!='EDIT_MESH':
  158.         bpy.ops.object.editmode_toggle()
  159.         toggle=True
  160.     else: toggle=False
  161.     #in edit mode: deselect verts
  162.     existing_sel_mode=bpy.context.tool_settings.mesh_select_mode
  163.     bpy.context.tool_settings.mesh_select_mode=[True,False,False]
  164.     bpy.ops.mesh.select_all(action='DESELECT')
  165.     bpy.ops.object.editmode_toggle()
  166.     #not in edit mode:select verts
  167.     for each_vert in verts_to_parent:
  168.         target_object.data.vertices[each_vert].select=True
  169.     #now select the mushroom aswell
  170.     ob_to_distribute.select=True
  171.     bpy.context.scene.objects.active=target_object
  172.     #get into edit mode for the active object
  173.     bpy.ops.object.editmode_toggle()
  174.     #now in edit mode with mushroom selected and appropriate verts selected
  175.     bpy.ops.object.vertex_parent_set()
  176.     bpy.context.tool_settings.mesh_select_mode=existing_sel_mode
  177.     bpy.ops.object.editmode_toggle()
  178.     #out of edit mode
  179.     bpy.ops.object.select_all(action='DESELECT')
  180.     if not toggle:
  181.         bpy.ops.object.editmode_toggle()  
  182.  
  183. def place_on_face(target_object, faceid, mushroom_object, pc, sc):
  184.     if bpy.context.mode=='EDIT_MESH':
  185.         bpy.ops.object.editmode_toggle()
  186.     #where pc is center point
  187.     if len(bpy.context.selected_objects)>0:
  188.         bpy.ops.object.select_all()
  189.     mushroom_object.select=True
  190.     #change this line if keyframe path fails
  191.     bpy.ops.object.duplicate(linked=False)
  192.     ob_to_distribute_handle=bpy.context.selected_editable_objects[0]
  193.     ob_to_distribute_handle.location=pc
  194.     sc_x=sc.x*ob_to_distribute_handle.scale.x
  195.     sc_y=sc.y*ob_to_distribute_handle.scale.y
  196.     sc_z=sc.z*ob_to_distribute_handle.scale.z
  197.     ob_to_distribute_handle.scale=mathutils.Vector((sc_x,sc_y,sc_z))
  198.     d_start=target_object.matrix_world.copy().invert()*mathutils.Vector((0,0,0))
  199.     d_end=target_object.matrix_world.copy().invert()*target_object.data.faces[faceid].normal.copy()
  200.     d=d_end-d_start
  201.     d=d.normalize()
  202.     e = mathutils.Vector((0,0,1))
  203.     f=e.copy().cross(d)
  204.     f=f.normalize()
  205.     e=d.copy().cross(f)
  206.     e=e.normalize()
  207.     mat = (mathutils.Matrix(f,e,d)).rotation_part().to_euler()
  208.     ob_to_distribute_handle.rotation_euler=mat
  209.     make_vertex_parent(target_object,ob_to_distribute_handle,faceid)
  210.     return [ob_to_distribute_handle,mushroom_object.name]
  211.  
  212. def get_face_dimensions(target_object, faceid):
  213.     v1=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[0]].co
  214.     v2=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[1]].co
  215.     v3=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[2]].co
  216.     fx=v2-v1
  217.     fy=v3-v2
  218.     return [fx.length,fy.length]
  219.  
  220. def convert_dec_to_pt(target_object,faceid,dec):
  221.     v1=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[0]].co
  222.     v2=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[1]].co
  223.     v3=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[2]].co
  224.     if len(target_object.data.faces[faceid].vertices)>3:
  225.         v4=target_object.matrix_world*target_object.data.vertices[target_object.data.faces[faceid].vertices[3]].co
  226.     else:
  227.         v4=v3
  228.     va=((v2-v1)*dec[0])+v1
  229.     vb=((v3-v4)*dec[0])+v4
  230.     vp=((vb-va)*dec[1])+va
  231.     return vp
  232.  
  233. def get_points_random(target_object,faceid,n):
  234.     pl=[]
  235.     pl_xyz=[]
  236.     for tmp in range(0,n):
  237.         pl.append([random.random(),random.random()])
  238.     for index in range(0,len(pl)):
  239.         pl_xyz.append(convert_dec_to_pt(target_object,faceid,pl[index]))
  240.     return pl_xyz
  241.    
  242. def get_points_jittered(target_object,faceid,n,jitter):
  243.     #jitter is a decimal 0 - 1
  244.     #returns a list of XYZ vectors referring to points jitter distrib on face
  245.     fdim=get_face_dimensions(target_object,faceid)
  246.     ndim=propose_dimensions(fdim,n)
  247.     pl=[]
  248.     pl_xyz=[]
  249.     for x in range(0,ndim[0]):
  250.         for y in range(0,ndim[1]):
  251.             rx=(jitter*random.random())-0.5
  252.             ry=(jitter*random.random())-0.5
  253.             pl.append([((x+0.5)/ndim[0]),((y+0.5)/ndim[1])])
  254.     for index in range(0,len(pl)):
  255.         pl_xyz.append(convert_dec_to_pt(target_object,faceid,pl[index]))
  256.     return pl_xyz
  257.  
  258. def get_points_scales(target_object,faceid,pl_xyz):
  259.     pl_xyz_s=[]
  260.     pl_xyz_l=list(pl_xyz)
  261.     if len(pl_xyz)>1:
  262.         for pa in pl_xyz_l:
  263.             closest_dist=-1
  264.             for pb in pl_xyz_l:
  265.                 if pb!=pa:
  266.                     dist_vec=pb-pa
  267.                     dist=dist_vec.length
  268.                     if closest_dist==-1:
  269.                         closest_dist=dist
  270.                     elif dist<closest_dist:
  271.                         closest_dist=dist
  272.             pl_xyz_s.append([pa,closest_dist/2])
  273.     else:
  274.         fdim=get_face_dimensions(target_object, faceid)
  275.         if fdim[0]<fdim[1]:
  276.             psc=fdim[0]/2
  277.         else:
  278.             psc=fdim[1]/2
  279.         pl_xyz_s.append([pl_xyz[0],psc])
  280.     return pl_xyz_s
  281.  
  282. def get_points_scales_tightpack(target_object,faceid,pl_xyz):
  283.     pl_xyz_l=list(pl_xyz)
  284.     #point data has a list of [pid,dist] sorted by dist
  285.     point_data=[]
  286.     if len(pl_xyz)>1:
  287.         for index,pa in enumerate(pl_xyz_l):
  288.             point_data.append([])
  289.             for neighbour_index,pb in enumerate(pl_xyz_l):
  290.                 if pb!=pa:
  291.                     dist_vec=pb-pa
  292.                     dist=dist_vec.length
  293.                     point_data[index].append((neighbour_index,dist))
  294.             point_data[index]=sorted(point_data[index], key=lambda dp: dp[1])
  295.         #data point is now sorted.
  296.         #priority list is a list ordered by pids' closest neighbour dists
  297.         priority_list=[]
  298.         for index,point_data_item in enumerate(point_data):
  299.             priority_list.append((index,point_data_item[0][1]))
  300.         priority_list=sorted(priority_list,key=lambda dp: dp[1])
  301.         #ringed points[dict]=ringsize
  302.         ringed_points=dict()
  303.         #starting with the first item in priority list
  304.         for point_id in priority_list:
  305.             #find the closest ring
  306.             ring_size=-1
  307.             for neighbour_point in point_data[point_id[0]]:
  308.                 if neighbour_point[0] in ringed_points.keys():
  309.                     ring_size=neighbour_point[1]-ringed_points[neighbour_point[0]]
  310.                     break
  311.             for neighbour_point in point_data[point_id[0]]:
  312.                 if neighbour_point[0] not in ringed_points.keys():
  313.                     if ring_size==-1:
  314.                         ring_size=neighbour_point[1]/2
  315.                     elif ring_size>(neighbour_point[1]/2):
  316.                         ring_size=neighbour_point[1]/2
  317.                     break
  318.             ringed_points[point_id[0]]=ring_size
  319.             pl_xyz_l[point_id[0]]=[pl_xyz[point_id[0]],ring_size]
  320.         #pl_xyz is a list of [point, ringsize]
  321.     else:
  322.         #if there is only one point, use the face dimensions
  323.         fdim=get_face_dimensions(target_object, faceid)
  324.         if fdim[0]<fdim[1]:
  325.             psc=fdim[0]/2
  326.         else:
  327.             psc=fdim[1]/2
  328.         pl_xyz_l=[pl_xyz[0],psc]
  329.     return pl_xyz_l
  330.  
  331. def propose_dimensions(f=[],n=1):
  332.     #where fx,fy are face dimensions
  333.     #n is the number needed
  334.     #fx should be longer than fy
  335.     fx=f[0]
  336.     fy=f[1]
  337.     if fx>fy:
  338.         swap=0
  339.     else:
  340.         t=fy
  341.         fy=fx
  342.         fx=t
  343.         swap=1
  344.     nx=math.floor(fx*((n/(fx*fy))**0.5))
  345.     ny=math.ceil(fy*((n/(fx*fy))**0.5))
  346.     # so nx and ny are approximately the number in x and y directions
  347.     closest_pair=[math.fabs((n**0.5)-(nx*ny)),[nx,ny]]
  348.     count=0.5
  349.     tnx=tny=0
  350.     while (tnx*tny<n):
  351.         tnx=round(nx+count)
  352.         tny=round(ny+((ny/nx)*count))
  353.         t_cp=math.fabs((n**0.5)-(tnx*tny))
  354.         if t_cp<closest_pair[0]:
  355.             closest_pair=[t_cp,[tnx,tny]]
  356.         count+=0.5
  357.     if swap==1:
  358.         t=closest_pair[1][1]
  359.         closest_pair[1][1]=closest_pair[1][0]
  360.         closest_pair[1][0]=t
  361.     return closest_pair[1]
  362.  
  363. def getneighbouringfaces(faces,faceid,vertshare,set_allowed_faces):
  364.     #vertshare is the number of vertices which must be in common
  365.     cset=set(faces[faceid].vertices)
  366.     neighbourfaces=set()
  367.     for count in range(0,len(faces)):
  368.         vset=set(faces[count].vertices)
  369.         if len(cset&vset)>(vertshare-1):
  370.             neighbourfaces.add(count)
  371.     neighbourfaces=neighbourfaces&set_allowed_faces
  372.     return neighbourfaces
  373.  
  374. def build_neighbour_faces_dict(target_object,set_allowed_faces):
  375.     print('building a neighbour face lookup...')
  376.     neighbour_faces=dict()
  377.     for index,each_face in enumerate(target_object.data.faces):
  378.         neighbour_faces[index]=getneighbouringfaces(target_object.data.faces,index,1,set_allowed_faces)
  379.     print('done.')
  380.     return neighbour_faces
  381.  
  382. def dofacesneighbour(faces,faceset1,faceset2,set_allowed_faces):
  383.     for iface in faceset2:
  384.         neighbouringfaceset=getneighbouringfaces(faces,iface,1,set_allowed_faces)
  385.         if len(neighbouringfaceset&faceset1)>0:
  386.             return True
  387.     return False
  388.  
  389. def doesfaceneighbour(faces,faceset1,sface,set_allowed_faces):
  390.     neighbouringfaceset=getneighbouringfaces(faces,sface,1,set_allowed_faces)
  391.     if len(neighbouringfaceset&faceset1)>0:
  392.         return True
  393.     return False
  394.  
  395. def sortfacelistbyz(faces,facelist):
  396.     nfacelist=[]
  397.     sfacelist=[]
  398.     for ifaceref in facelist:
  399.         nfacelist.append((ifaceref,faces[ifaceref].center.z))
  400.     nfacelist=sorted(nfacelist, key=lambda nfaces: -nfaces[1])
  401.     for nface in nfacelist:
  402.         sfacelist.append(nface[0])
  403.     return sfacelist
  404.  
  405. def sortfacelistbyrandom(faces,facelist):
  406.     nfacelist=[]
  407.     sfacelist=[]
  408.     for ifaceref in facelist:
  409.         nfacelist.append((ifaceref,random.random()))
  410.     nfacelist=sorted(nfacelist, key=lambda nfaces: nfaces[1])
  411.     for nface in nfacelist:
  412.         sfacelist.append(nface[0])
  413.     return sfacelist
  414.  
  415. def getallneighbours(livefaces,neighbour_face_dict,set_allowed_faces):
  416.     neighbour_set=set()
  417.     for each_face in livefaces:
  418.         neighbour_set|=neighbour_face_dict[each_face]
  419.     neighbour_set-=livefaces
  420.     neighbour_set&=set_allowed_faces
  421.     return neighbour_set
  422.  
  423. #-------------------choose next algorithms----------------
  424.  
  425. def choosenextfaces_simpleincrease(target_object,faces,livefaces,includedfaces,set_allowed_faces):
  426.     #always arrives in object mode
  427.     #deselect everything
  428.     bpy.ops.object.select_all(action='DESELECT')
  429.     target_object.select=True
  430.     bpy.context.scene.objects.active=target_object
  431.    
  432.     bpy.ops.object.editmode_toggle()
  433.     #make sure faces select is on - in edit mode
  434.     bpy.context.tool_settings.mesh_select_mode=[False,False,True]
  435.     #set the selection - in edit mode
  436.     selectset(faces,set(livefaces.keys()))
  437.     #increase the selection - in edit mode
  438.     bpy.ops.mesh.select_more()
  439.     bpy.ops.object.editmode_toggle()
  440.    
  441.     #get the selection - out of edit mode
  442.     selection_list=list((selectget(faces)&set_allowed_faces)-includedfaces)
  443.    
  444.     new_livefaces=dict()
  445.     for selected_item in selection_list:
  446.         new_livefaces[selected_item]=-1
  447.     return new_livefaces
  448.  
  449. def choosenextfaces_conways(faces,livefaces,neighbour_face_dict,set_allowed_faces):
  450.     new_live_faces=dict()
  451.     for each_face in list(livefaces.keys()):
  452.         n_neighbours=len(neighbour_face_dict[each_face]&set(livefaces.keys()))
  453.         print(n_neighbours)
  454.         if n_neighbours>1 and n_neighbours<4:
  455.             new_live_faces[each_face]=-1
  456.     list_neighbours=list(getallneighbours(livefaces.keys(),neighbour_face_dict,set_allowed_faces))
  457.     print(list_neighbours)
  458.     for each_face in list_neighbours:
  459.         n_neighbours=len(neighbour_face_dict[each_face]&set(livefaces.keys()))
  460.         if n_neighbours==3:
  461.             new_live_faces[each_face]=-1
  462.     return new_live_faces
  463.    
  464. def choosenextfaces(faces,livefaces,includedfaces,fork_chance,upward_chance,death_chance,growth_mode,set_allowed_faces):
  465.     tnlivefaces=dict()
  466.     livefaceslist=list(livefaces.keys())
  467.     for iface in livefaceslist:
  468.         nlivefaces=dict()
  469.         if random.random()<fork_chance:
  470.             willfork=1
  471.         else:
  472.             willfork=0
  473.         if random.random()<upward_chance:
  474.             willclimb=1
  475.         else:
  476.             willclimb=0
  477.         if random.random()<death_chance and len(tnlivefaces)>0:
  478.             print('dead end')
  479.         else:
  480.             #not dieing out
  481.             neighbourfacesset=getneighbouringfaces(faces,iface,2,set_allowed_faces)
  482.             if iface in neighbourfacesset:
  483.                 neighbourfacesset.remove(iface)
  484.             neighbourfaceslist=list(neighbourfacesset)
  485.             acceptedneighbours=[]
  486.             #remove unsuitable options
  487.             if growth_mode==0 or growth_mode==1:
  488.                 for neighbourface in neighbourfaceslist:
  489.                     #so if the value()=-1 then remove all of the neighbours of the liveface
  490.                     if doesfaceneighbour(faces,(includedfaces-set([iface]))-set([livefaces[iface]]),neighbourface,set_allowed_faces)==False:
  491.                         acceptedneighbours.append(neighbourface)
  492.             elif growth_mode==2:
  493.                 acceptedneighbours=neighbourfaceslist
  494.             if growth_mode==0:
  495.                 #need a list >= length 1+willfork
  496.                 while len(acceptedneighbours)<(willfork+1) and len(neighbourfacesset)>0:
  497.                     #pop a random item from the neighbourfacesset into the acceptedneighbours list
  498.                     tmpset=set(acceptedneighbours)
  499.                     tmpset.add(neighbourfacesset.pop())
  500.                     acceptedneighbours=list(tmpset)        
  501.                 #now acceptedneighbours contains a list of possible options
  502.                 #if willclimb==1 then sort them by z
  503.                 #else sort them randomly
  504.                 if willclimb==1:
  505.                     acceptedneighbours=sortfacelistbyz(faces,acceptedneighbours)
  506.                 else:
  507.                     acceptedneighbours=sortfacelistbyrandom(faces,acceptedneighbours)
  508.                 #if willfork==1 then select first two
  509.                 #if willfork==0 then select first one
  510.                 if len(acceptedneighbours)>0:
  511.                     nlivefaces[acceptedneighbours[0]]=iface
  512.                 if willfork==1 and len(acceptedneighbours)>1:
  513.                     nlivefaces[acceptedneighbours[1]]=iface
  514.             elif (growth_mode==1 or growth_mode==2) and len(acceptedneighbours)>0:
  515.                 if willclimb==1:
  516.                     acceptedneighbours=sortfacelistbyz(faces,acceptedneighbours)
  517.                 else:
  518.                     acceptedneighbours=sortfacelistbyrandom(faces,acceptedneighbours)
  519.                 if len(acceptedneighbours)<(willfork+1):
  520.                     willfork=0
  521.                 nlivefaces[acceptedneighbours[0]]=iface
  522.                 if willfork==1 and len(acceptedneighbours)>1:
  523.                     nlivefaces[acceptedneighbours[1]]=iface
  524.                 else:
  525.                     print('could not fork')        
  526.             tnlivefaces.update(nlivefaces)
  527.     return tnlivefaces
  528.  
  529. #-------------------end choose next algorithms----------------
  530.  
  531. def selectset(faces,selset):
  532.     #for index,each_face in enumerate(faces):
  533.     #    if index in selset:
  534.     #        each_face.select=1
  535.     #    else:
  536.     #        each_face.select=0
  537.     #slightly faster method        
  538.     #call this in edit mode
  539.     bpy.ops.object.select_all(action='DESELECT')
  540.     bpy.ops.object.editmode_toggle()
  541.     list_selset=list(selset)
  542.     for each_face in selset:
  543.         faces[each_face].select=True
  544.     bpy.ops.object.editmode_toggle()
  545.     return faces
  546.  
  547. def selectget(faces):
  548.     select_set=set()
  549.     for index,each_face in enumerate(faces):
  550.         if each_face.select==1:
  551.             select_set.add(index)
  552.     return select_set
  553.  
  554. def find_corresponding_mushroom_vertex_groups(mushrooms_list,target_object,prefix):
  555.     #prefix is OB for objects
  556.     #prefix is MA for materials, mushrooms_list=materials_list
  557.     vertex_group_lookup=dict()
  558.     mushroom_names=[each_mushroom.name for each_mushroom in mushrooms_list]
  559.     for index,vertex_group in enumerate(target_object.vertex_groups):
  560.         if vertex_group.name[:len(prefix)]==prefix and vertex_group.name[len(prefix):] in mushroom_names:
  561.             vertex_group_lookup[index]=vertex_group.name
  562.     return vertex_group_lookup
  563.  
  564. def get_face_groupchance_dict(target_object,faceid,mushrooms_list,lookup):
  565.     group_chance=dict()
  566.     for each_vertex in [target_object.data.vertices[vertex_index] for vertex_index in target_object.data.faces[faceid].vertices]:
  567.         for vertex_group in each_vertex.groups:
  568.             if vertex_group.group in lookup.keys():
  569.                 if vertex_group.group in group_chance.keys():
  570.                     group_chance[vertex_group.group]+=vertex_group.weight
  571.                 else:
  572.                     group_chance[vertex_group.group]=vertex_group.weight
  573.     return group_chance
  574.  
  575. def choose_mushroom_from_vertex_weights(mushrooms_list,lookup,return_random,group_chance):
  576.     #mushrooms list can be a list of materials
  577.     #lookup can be a material lookup
  578.     #returns a name only not a data path
  579.     #if return_random==True then a random object from the list is returned if no valid vert weights exist
  580.     #if return_random!=True then -1 is returned is [ditto]
  581.     list_group_chance=list(group_chance.keys())
  582.     sum_weights=0
  583.     for index in list_group_chance:
  584.         sum_weights+=group_chance[index]
  585.     if sum_weights>0:
  586.         result=random.random()*sum_weights
  587.         sum_weights=0
  588.         for index in list_group_chance:
  589.             sum_weights+=group_chance[index]
  590.             if sum_weights>result:
  591.                 return lookup[index][2:]
  592.         return lookup[list(group_chance.keys())[index]][2:]
  593.     elif return_random:
  594.         return random.choice(mushrooms_list).name
  595.     else:
  596.         return -1
  597.            
  598. def place_many_on_face(target_object, faceid, mushrooms_list, number_to_distribute, distribute_method, distribute_jitter,pack_mode,vertex_group_lookup):
  599.     #distribute method
  600.     #0=uniform
  601.     #1=random
  602.     #2=jitter
  603.     if distribute_method==0:
  604.         place_points=get_points_jittered(target_object,faceid,number_to_distribute,distribute_jitter)
  605.     elif distribute_method==1:
  606.         place_points=get_points_random(target_object,faceid,number_to_distribute)
  607.     else:
  608.         print('incorrect jitter mode!')
  609.         return -1
  610.     if pack_mode=='TIGHT':
  611.         place_points_with_scales=get_points_scales_tightpack(target_object,faceid,place_points)
  612.     else:
  613.         place_points_with_scales=get_points_scales(target_object,faceid,place_points)
  614.     placed_data=[]
  615.     groupchance=get_face_groupchance_dict(target_object,faceid,mushrooms_list,vertex_group_lookup)
  616.     for point in place_points_with_scales:
  617.         mushroom_choice=bpy.data.objects[choose_mushroom_from_vertex_weights(mushrooms_list,vertex_group_lookup,True,groupchance)]
  618.         placed_data.append(place_on_face(target_object, faceid, mushroom_choice, point[0],mathutils.Vector((point[1],point[1],point[1]))))
  619.     return placed_data
  620.  
  621. def insert_keyframes(target_object,start_frame,shapes_lifecycle,frames_per_shape,jitter):
  622.     #target_object=target object name
  623.     #start frame = start frame number
  624.     #shapes available is an int referring to the max number of shapes s+str(a+1) for a in range(0,shapes_available)
  625.     #frames_per_shape - how much to advace between each shape
  626.     #jitter - 0>1 ... affects frames_per_shape
  627.     last_shape_name=''
  628.     #set frame
  629.     bpy.context.scene.frame_current=int(start_frame)
  630.     actual_start_frame=bpy.context.scene.frame_current=int(bpy.context.scene.frame_current+(frames_per_shape*(1+(2*random.random()*jitter))))
  631.     shapes_available=len(shapes_lifecycle)
  632.     for shape_id,current_shape_name in enumerate(shapes_lifecycle):
  633.         if shape_id<(shapes_available-1):
  634.             next_shape_name=shapes_lifecycle[shape_id+1]
  635.             target_object.data.shape_keys.keys[next_shape_name].value=0
  636.             target_object.data.shape_keys.keys[next_shape_name].keyframe_insert('value')
  637.             #target_object.keyframe_insert('data.shape_keys.keys[\"'+next_shape_name+'\"].value')
  638.         if last_shape_name!='':
  639.             target_object.data.shape_keys.keys[last_shape_name].value=0
  640.             target_object.data.shape_keys.keys[last_shape_name].keyframe_insert('value')
  641.             #target_object.keyframe_insert('data.shape_keys.keys[\"'+last_shape_name+'\"].value')
  642.         target_object.data.shape_keys.keys[current_shape_name].value=1
  643.         target_object.data.shape_keys.keys[current_shape_name].keyframe_insert('value')
  644.         #target_object.keyframe_insert('data.shape_keys.keys[\"'+current_shape_name+'\"].value')
  645.         #advance frames
  646.         bpy.context.scene.frame_current=int(bpy.context.scene.frame_current+(frames_per_shape*(1+(2*random.random()*jitter))))
  647.         last_shape_name=current_shape_name
  648.     return [actual_start_frame,int(bpy.context.scene.frame_current)]
  649.  
  650. def add_z_up_driver(mushroom_object,bend_key_name,start_frame,end_frame):
  651.     driver_curve=mushroom_object.data.shape_keys.keys[bend_key_name].driver_add('value')
  652.     #driver_curve=mushroom_object.driver_add('data.shape_keys.keys[\"'+bend_key_name+'\"].value')
  653.     z_var=driver_curve.driver.variables.new()
  654.     z_var.type='SINGLE_PROP'
  655.     z_var.targets[0].id_type='OBJECT'
  656.     z_var.targets[0].id=mushroom_object
  657.     #this line may require editing for better results - [0] is y axis rot, [11] is combined xy
  658.     z_var.targets[0].data_path='matrix_world[0]'
  659.     if start_frame!=-1 and end_frame!=-1:
  660.         current_frame_var=driver_curve.driver.variables.new()
  661.         current_frame_var.type='SINGLE_PROP'
  662.         current_frame_var.targets[0].id_type='SCENE'
  663.         current_frame_var.targets[0].id=bpy.context.scene
  664.         current_frame_var.targets[0].data_path='frame_current'
  665.         mid_frame=(start_frame+end_frame)/2
  666.         range_frame=(end_frame-start_frame)
  667.         driver_curve.driver.expression='(1-'+z_var.name+')*(2.7182**(-1*((('+current_frame_var.name+'-'+str(mid_frame)+')/('+str(range_frame)+'*0.2))**2)))'
  668.     else:
  669.         driver_curve.driver.expression='1-'+z_var.name
  670.  
  671. def insert_keyframes_multi(mushrooms_list,start_frame,mushrooms_shapes_database,lifetime,lifetime_jitter,start_frame_variation,start_frame_jitter):
  672.     #try '["blah"]' or "['dfdf']"
  673.     target_frame=start_frame
  674.     for mushroom_object in mushrooms_list:
  675.         #load shapes_lifecycle etc. based on the mushroom.name
  676.         shapes_lifecycle=mushrooms_shapes_database[0][mushroom_object[1]]
  677.         shapes_random=mushrooms_shapes_database[1][mushroom_object[1]]
  678.         shapes_bend=mushrooms_shapes_database[2][mushroom_object[1]]
  679.         frame_spread=[]
  680.         if shapes_lifecycle==['USEEXIST']:
  681.             frame_spread=reanimate_actions(mushroom_object[0],target_frame,target_frame+lifetime,lifetime_jitter)
  682.         elif shapes_lifecycle!=[]:
  683.             frame_spread=insert_keyframes(mushroom_object[0],target_frame,shapes_lifecycle,lifetime/len(shapes_lifecycle),lifetime_jitter)
  684.         if frame_spread!=[]:
  685.             #if keyframes have been inserted/edited
  686.             #add the random shape key data
  687.             halfway_frame=int((frame_spread[0]+frame_spread[1])/2)
  688.             if len(shapes_random)>0:
  689.                 random_shape=shapes_random[int(random.random()*(len(shapes_random)-1))]
  690.                 bpy.context.scene.frame_current=frame_spread[0]
  691.                 mushroom_object[0].data.shape_keys.keys[random_shape].value=0
  692.                 mushroom_object[0].data.shape_keys.keys[random_shape].keyframe_insert('value')
  693.                 #mushroom_object[0].keyframe_insert('data.shape_keys.keys[\"'+random_shape+'\"].value')
  694.                 bpy.context.scene.frame_current=halfway_frame
  695.                 mushroom_object[0].data.shape_keys.keys[random_shape].value=random.random()
  696.                 mushroom_object[0].data.shape_keys.keys[random_shape].keyframe_insert('value')
  697.                 #mushroom_object[0].keyframe_insert('data.shape_keys.keys[\"'+random_shape+'\"].value')
  698.                 bpy.context.scene.frame_current=frame_spread[1]
  699.                 mushroom_object[0].data.shape_keys.keys[random_shape].value=0
  700.                 mushroom_object[0].data.shape_keys.keys[random_shape].keyframe_insert('value')
  701.                 #mushroom_object[0].keyframe_insert('data.shape_keys.keys[\"'+random_shape+'\"].value')
  702.             if len(shapes_bend)>0:
  703.                 bend_shape=shapes_bend[int(random.random()*(len(shapes_bend)-1))]
  704.                 #add a gaussion rise and fall with y rotation driven shape key
  705.                 add_z_up_driver(mushroom_object[0],bend_shape,frame_spread[0],frame_spread[1])
  706.         else:
  707.             #basically theres no animation on the mushrooms
  708.             #just set a shape key value for randome
  709.             if len(shapes_random)>0:
  710.                 random_shape=shapes_random[int(random.random()*(len(shapes_random)-1))]
  711.                 mushroom_object[0].data.shape_keys.keys[random_shape].value=random.random()
  712.             if len(shapes_bend)>0:
  713.                 bend_shape=shapes_bend[int(random.random()*(len(shapes_bend)-1))]
  714.                 #add a gaussion rise and fall with y rotation driven shape key
  715.                 #calling the z driver adder with -1 and -1 tells it to skip the gaussian curve falloff and just add a simple driver
  716.                 add_z_up_driver(mushroom_object[0],bend_shape,-1,-1)
  717.         target_frame+=start_frame_variation*(1+(2*random.random()*start_frame_jitter))
  718.        
  719. def add_mushroom_shrinkwrap(target_object,mushroom_object_list,vertex_group):
  720.     #this is only called if vertex group!=-1
  721.     for mushroom_object in mushroom_object_list:
  722.         mushroom_shrinkwrap_modifier=mushroom_object[0].modifiers.new(type='SHRINKWRAP', name='mushroom_shrinkwrap')
  723.         mushroom_shrinkwrap_modifier.target=target_object
  724.         mushroom_shrinkwrap_modifier.vertex_group=vertex_group
  725.        
  726. def add_mushrooms_to_group(mushroom_object_list,group_name):
  727.     for mushroom_object in mushroom_object_list:
  728.         bpy.data.groups[group_name].objects.link(mushroom_object[0])
  729.        
  730. def build_materials_list(mushrooms_list):
  731.     materials_list=[]
  732.     for each_mushroom in mushrooms_list:
  733.         for each_material in each_mushroom.material_slots:
  734.             materials_list.append(each_material.material)
  735.     return materials_list
  736.  
  737. def set_materials(mushrooms_list,target_object,faceid,randomize_material,materials_list,vertex_group_lookup_materials):
  738.     groupchance=get_face_groupchance_dict(target_object,faceid,materials_list,vertex_group_lookup_materials)
  739.     for mushroom_object in mushrooms_list:
  740.         for material_slot in mushroom_object[0].material_slots:
  741.             if 'material' in dir(material_slot):
  742.                 material_slot_new=choose_mushroom_from_vertex_weights(materials_list,vertex_group_lookup_materials,False,groupchance)
  743.                 if material_slot_new!=-1:
  744.                     material_slot.material=bpy.data.materials[material_slot_new]
  745.                 elif randomize_material:
  746.                     material_slot.material=random.choice(materials_list)
  747.  
  748. def get_allowed_faces(target_object,vertex_group_name):
  749.     if bpy.context.mode!='EDIT_MESH':
  750.         bpy.context.scene.objects.active=target_object
  751.         bpy.ops.object.editmode_toggle()
  752.     #now in edit mode    
  753.     bpy.context.tool_settings.mesh_select_mode=[True,False,False]
  754.     if vertex_group_name!='-1':
  755.         bpy.ops.object.select_all(action='DESELECT')
  756.         bpy.ops.object.vertex_group_set_active(group=vertex_group_name)
  757.         bpy.ops.object.vertex_group_select()
  758.         #leave edit mode
  759.         bpy.ops.object.editmode_toggle()
  760.         set_allowed_faces=selectget(target_object.data.faces)
  761.     else:
  762.         #leave edit mode
  763.         bpy.ops.object.editmode_toggle()
  764.         set_allowed_faces=set(range(0,len(target_object.data.faces)))
  765.     return set_allowed_faces
  766.  
  767. def find_live_faces(selected_faces,set_allowed_faces):
  768.     #returns a dict
  769.     #keys are the live faces keys, values are -1 (indicating initial state)
  770.     livefaces=dict()
  771.     for index in selected_faces:
  772.         if index in set_allowed_faces:
  773.             livefaces[index]=-1
  774.     if len(livefaces.keys())==0:
  775.         print('Nothing in the masking group was selected... randomizing... ')
  776.         livefaces[random.choice(list(set_allowed_faces))]=-1
  777.     print ('Selection is: '+str(livefaces))
  778.     return livefaces
  779.  
  780. def save_timing(target_object,faces,livefaces,timing_group_name,value):
  781.     if bpy.context.mode=='EDIT_MESH':
  782.         bpy.ops.object.editmode_toggle()
  783.         was_in_edit_mode=True
  784.     else:
  785.         was_in_edit_mode=False
  786.     bpy.context.scene.objects.active=target_object
  787.     bpy.ops.object.vertex_group_set_active(group=timing_group_name)
  788.     selectset(faces,set(livefaces.keys()))
  789.     bpy.ops.object.editmode_toggle()
  790.     #now in edit mode
  791.     bpy.context.tool_settings.vertex_group_weight=value
  792.     bpy.ops.object.vertex_group_assign(new=False)
  793.     if was_in_edit_mode and bpy.context.mode!='EDIT_MESH':
  794.         bpy.ops.object.editmode_toggle()
  795.     elif (not was_in_edit_mode) and bpy.context.mode=='EDIT_MESH':
  796.         bpy.ops.object.editmode_toggle()
  797.  
  798. #-------------new animation defs---------------
  799.  
  800. def prepend_data_path(list_fcurve,data_path_prefix):
  801.     #data path prefix does not include the dot
  802.     for each_fcurve in list_fcurve:
  803.         each_fcurve.data_path=data_path_prefix+'.'+each_fcurve.data_path
  804.        
  805. def copy_fcurves(mushroom_object,host_action,data_path_prefix,time_scale,time_shift,time_jitter):
  806.     #this def is for when blender gets fixed
  807.     #time_scale is time multiplier about zero, followed by adding time_shift
  808.     time_scale+=time_jitter*(time_scale*random.uniform(-1,1))
  809.     mushroom_object.animation_data_create()
  810.     mushroom_object.animation_data.action=bpy.data.actions.new('mushroom_AC')
  811.     for each_fcurve in host_action.fcurves:
  812.         copy_fcurve=mushroom_object.animation_data.action.fcurves.new(data_path_prefix+'.'+each_fcurve.data_path)
  813.         copy_fcurve.data_path=(data_path_prefix+'.'+each_fcurve.data_path)
  814.         #adjust the keyframe on the newly created fcurve
  815.         last_time=(time_scale*each_fcurve.keyframe_points[0].co.x)+time_shift
  816.         for o_kp in each_fcurve.keyframe_points:
  817.             new_time=(time_scale*o_kp.co.x)+time_shift
  818.             new_time=(new_time*(1-time_jitter))+((new_time-((new_time-last_time)*random.random()))*time_jitter)
  819.             print(copy_fcurve.keyframe_points.add(0,0))
  820.             c_kp=copy_fcurve.keyframe_points.add(new_time, o_kp.co.y, True,False,True)
  821.             c_kp.interpolation=o_kp.interpolation
  822.             c_kp.handle_left=o_kp.handle_left
  823.             c_kp.handle_left_type=o_kp.handle_left_type
  824.             c_kp.handle_right=o_kp.handle_right
  825.             c_kp.handle_right_type=o_kp.handle_right_type
  826.             last_time=new_time
  827.            
  828. def adjust_fcurves(host_action,time_scale,time_shift,time_jitter):
  829.     for each_fcurve in host_action.fcurves:
  830.         last_time=(time_scale*each_fcurve.keyframe_points[0].co.x)+time_shift
  831.         for o_kp in each_fcurve.keyframe_points:
  832.             new_time=(time_scale*o_kp.co.x)+time_shift
  833.             new_time=(new_time*(1-time_jitter))+((new_time-((new_time-last_time)*random.random()))*time_jitter)
  834.             o_kp.co.x=new_time
  835.             last_time=new_time
  836.  
  837. def reanimate_actions(mushroom_object,start_frame,end_frame,time_jitter):
  838.     #assuming the duplicate is linked, transfer the animation data to object level
  839.     if 'action' in dir(mushroom_object.data.shape_keys.animation_data):
  840.         #copy_fcurves(mushroom_object,mushroom_object.data.shape_keys.animation_data.action,'data.shape_keys',(end_frame-start_frame)/100,start_frame,time_jitter)
  841.         adjust_fcurves(mushroom_object.data.shape_keys.animation_data.action,(end_frame-start_frame)/100,start_frame,time_jitter)
  842.     for mat_index,material_slot in enumerate(mushroom_object.material_slots):
  843.         if 'material' in dir(material_slot):
  844.             if 'animation_data' in dir(material_slot.material):
  845.                 if 'action' in dir(material_slot.material.animation_data):
  846.                     #copy_fcurves(mushroom_object,material_slot.material.animation_data.action,'material_slot['+str(mat_index)+'].material',(end_frame-start_frame)/100,start_frame,time_jitter)
  847.                     material_slot.material=material_slot.material.copy()
  848.                     adjust_fcurves(material_slot.material.animation_data.action,(end_frame-start_frame)/100,start_frame,time_jitter)
  849.             for tex_index,texture_slot in enumerate(material_slot.material.texture_slots):
  850.                 if 'texture' in dir(texture_slot):
  851.                     if 'animation_data' in dir(texture_slot.texture):
  852.                         if 'action' in dir(texture_slot.texture.animation_data):
  853.                             texture_slot.texture=texture_slot.texture.copy()
  854.                             #copy_fcurves(mushroom_object,texture_slot.texture.animation_data.action,'material_slot['+str(mat_index)+'].material.texture_slot['+str(tex_index)+'].texture',(end_frame-start_frame)/100,start_frame,time_jitter)
  855.                             adjust_fcurves(texture_slot.texture.animation_data.action,(end_frame-start_frame)/100,start_frame,time_jitter)
  856.     return [int(start_frame),int(end_frame)]
  857.    
  858.  
  859. #-----------------------------------------------
  860.  
  861. def generate_mushrooms_shapes_database(mushrooms_list,target_object,shape_key_dict_prefixes):
  862.     #list[0=lifecycle,1=random,2=bend]=dict[mushroom_object.name]=[shapes...]
  863.     mushroom_shapes_database=[dict(),dict(),dict()]
  864.     for mushroom_object in mushrooms_list:
  865.         if target_object.mushroomer_lifecycle_key_set!="IGNORE" and target_object.mushroomer_lifecycle_key_set!="USEEXIST":
  866.             mushroom_shapes_database[0][mushroom_object.name]=[target_object.mushroomer_lifecycle_key_set+str(dict_list_item[0]) for dict_list_item in shape_key_dict_prefixes[mushroom_object.name][target_object.mushroomer_lifecycle_key_set]]
  867.         elif target_object.mushroomer_lifecycle_key_set=="USEEXIST":
  868.             mushroom_shapes_database[0][mushroom_object.name]=['USEEXIST']
  869.         else: mushroom_shapes_database[0][mushroom_object.name]=[]
  870.         if target_object.mushroomer_random_key_set!="IGNORE":
  871.             mushroom_shapes_database[1][mushroom_object.name]=[target_object.mushroomer_random_key_set+str(dict_list_item[0]) for dict_list_item in shape_key_dict_prefixes[mushroom_object.name][target_object.mushroomer_random_key_set]]
  872.         else: mushroom_shapes_database[1][mushroom_object.name]=[]
  873.         if target_object.mushroomer_bend_key_set!="IGNORE":
  874.             mushroom_shapes_database[2][mushroom_object.name]=[target_object.mushroomer_bend_key_set+str(dict_list_item[0]) for dict_list_item in shape_key_dict_prefixes[mushroom_object.name][target_object.mushroomer_bend_key_set]]
  875.         else: mushroom_shapes_database[2][mushroom_object.name]=[]
  876.     print(mushroom_shapes_database)
  877.     return mushroom_shapes_database
  878.    
  879. #begin UI code
  880.  
  881. def find_mushrooms(context):
  882.     mushrooms_list=[]
  883.     for try_object in context.selected_objects:
  884.         if try_object!=context.active_object:
  885.             mushrooms_list.append(try_object)
  886.     return mushrooms_list
  887.    
  888.  
  889. class Mushroomer(bpy.types.Operator):
  890.     '''plants mushrooms on your mesh'''
  891.     bl_idname = "mesh.mushroom_maker"
  892.     bl_label = "Mushroomer"
  893.     bl_options = {'REGISTER', 'UNDO'}
  894.     dialog_width=400
  895.    
  896.     title_label_text=''    
  897.     mushrooms_list=[]
  898.     shape_key_dict_prefixes=dict()
  899.  
  900.     target_object_name=bpy.props.StringProperty(name="Target Object", description="Object receiving the mushrooms",default="")
  901.     iterations=bpy.props.IntProperty(name="Generations of Mushrooms", description="Number of generations",default=5,min=0,max=200)
  902.     global_start_frame=bpy.props.IntProperty(name="Start Frame", description="Start Frame",default=1,min=0)
  903.     children_first_generation=bpy.props.IntProperty(name="1st Generation Mushroom Family Size", description="1st Generation Mushroom Family Size",default=1,min=0)
  904.     children_last_generation=bpy.props.IntProperty(name="Last Generation Mushroom Family Size", description="Last Generation Mushroom Family Size",default=5,min=0)
  905.     children_mode_list=[("CHM","Family Size Growth Mode","Family Size Growth Mode"),
  906.                         ("0","Constant","Constant"),
  907.                         ("1","Linear","Linear"),
  908.                         ("2","Square","Square"),
  909.                         ("3","Fibonacci","Fibonacci"),
  910.                         ("4","Square Root","Square Root")]
  911.     children_mode=bpy.props.EnumProperty(attr="children_mode",items=children_mode_list,default="1")
  912.     children_jitter=bpy.props.FloatProperty(name="Family Size Jitter", description="Number of generations",default=0.1,min=0,max=1,step=0.1,precision=2)
  913.     generational_frame_advance=bpy.props.IntProperty(name="Child Bearing Age", description="Child Bearing Age",default=10,min=2)
  914.     generational_frame_advance_jitter=bpy.props.FloatProperty(name="Child Bearing Age Jitter", description="Child Bearing Age Jitter",default=0.1,min=0,max=1,step=0.1,precision=2)
  915.     instance_lifetime=bpy.props.IntProperty(name="Death Age", description="Death Age",default=20,min=2)
  916.     instance_lifetime_jitter=bpy.props.FloatProperty(name="Death Age Jitter", description="Death Age Jitter",default=0.1,min=0,max=1,step=0.1,precision=2)
  917.     sub_generational_frame_spread=bpy.props.IntProperty(name="Generational Spread (frames)", description="The maximum length of time from the first birth to the last birth",default=10,min=0)
  918.     sub_generational_frame_spread_jitter=bpy.props.FloatProperty(name="Generational Spread Jitter", description="Generational Spread Jitter",default=0.1,min=0,max=1,step=0.1,precision=2)
  919.     distribute_method_list=[("DM","Distribution Method","How the mushrooms are distributed on each face"),
  920.                             ("0","Uniform","Uniform"),
  921.                             ("1","Random","Random")]
  922.     distribute_method=bpy.props.EnumProperty(attr="distribute_method",items=distribute_method_list,default="1")
  923.     distribute_jitter=bpy.props.FloatProperty(name="Distribution Jitter", description="Distribution Jitter",default=0.5,min=0,max=1,step=0.1,precision=2)
  924.     growth_mode_list=[("GM","Growth Method","How the mushrooms spread out from their start face"),
  925.                             ("0","Crossed Paths","Crossed Paths"),
  926.                             ("1","Forked Paths","Forked Paths"),
  927.                             ("2","Splurge","Splurge"),
  928.                             ("3","Conway's Game of Life","Game of Life"),
  929.                             ("4","Simple Selection Growth","Simple Selection Growth")]
  930.     growth_mode=bpy.props.EnumProperty(attr="growth_mode",items=growth_mode_list,default="1")
  931.     death_chance=bpy.props.FloatProperty(name="Family Wipeout Chance", description="Family Wipeout Chance",default=0.1,min=0,max=1,step=0.1,precision=2)
  932.     upward_chance=bpy.props.FloatProperty(name="Upwards Attraction (climb chance)", description="Probability children will choose to move up",default=0.7,min=0,max=1,step=0.1,precision=2)
  933.     fork_chance=bpy.props.FloatProperty(name="Probability of Family/Fork Split", description="Probability the family will split into two different groups of children",default=0.5,min=0,max=1,step=0.1,precision=2)
  934.     pack_mode_list=[("PM","Packing Method","Algorithm for Packing/Sizing Mushrooms"),
  935.                             ("LAZY","Lazy Pack (faster)","Lazy Pack - Half Nearest Neighbour Distance"),
  936.                             ("TIGHT","Tight Pack","Tight Pack")]
  937.     pack_mode=bpy.props.EnumProperty(attr="pack_mode",items=pack_mode_list,default="LAZY")
  938.     randomize_materials=bpy.props.BoolProperty(name="Randomize Materials",description="if ticked materials from any slot on any object an be shuffled between objects: beware UV people!",default=False)
  939.    
  940.    
  941.     #in invoke to do automatically...
  942.     #get the first selected but not active mushroom -> name -> self.properties.ob_to_distribute....
  943.     def invoke(self, context, event):
  944.         #all the generator functions stoe their result on the target object
  945.         #but take info from the list of mushrooms
  946.         self.properties.target_object_name=context.active_object.name
  947.         self.mushrooms_list=find_mushrooms(context)
  948.         self.title_label_text='Mushrooming: '
  949.         for mushroom_object in self.mushrooms_list:
  950.             self.title_label_text=self.title_label_text+mushroom_object.name+', '
  951.         self.title_label_text=self.title_label_text.rstrip(', ')+' all over '+self.properties.target_object_name
  952.         self.shape_key_dict_prefixes=build_prefixes_enum(self.mushrooms_list,bpy.data.objects[self.properties.target_object_name])
  953.         build_vertex_group_enum(self.mushrooms_list,bpy.data.objects[self.properties.target_object_name])
  954.         build_group_name_text(bpy.data.objects[self.properties.target_object_name])
  955.         build_face_limit_enum(bpy.data.objects[self.properties.target_object_name])
  956.         self.properties.global_start_frame=bpy.context.scene.frame_current
  957.         wm = context.manager
  958.         wm.invoke_props_dialog(self, self.dialog_width)
  959.         return {'RUNNING_MODAL'}
  960.  
  961.     def draw(self, context):
  962.         layout = self.layout
  963.         props = self.properties
  964.  
  965.         layout.label(text=self.title_label_text)
  966.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_grouping", text="Group To Append Mushrooms To")
  967.         layout.prop(props, "iterations")
  968.         layout.prop(props, "global_start_frame")
  969.  
  970.         layout.label(text="Family Settings")
  971.         row = layout.row()
  972.         split = row.split(percentage=0.5)
  973.         colL = split.column()
  974.         colR = split.column()
  975.         colL.prop(props, "children_first_generation")
  976.         colR.prop(props, "children_last_generation")
  977.         colL.prop(props, "children_mode")
  978.         colR.prop(props, "children_jitter")
  979.         colL.prop(props, "generational_frame_advance")
  980.         colR.prop(props, "generational_frame_advance_jitter")
  981.         colL.prop(props, "instance_lifetime")
  982.         colR.prop(props, "instance_lifetime_jitter")
  983.         colL.prop(props, "sub_generational_frame_spread")
  984.         colR.prop(props, "sub_generational_frame_spread_jitter")
  985.         colL.prop(props, "distribute_method")
  986.         colR.prop(props, "distribute_jitter")
  987.         colL.prop(props, "pack_mode")
  988.         colR.prop(props, "randomize_materials")
  989.  
  990.         layout.label(text="Spread & Conquer Settings")
  991.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_limit_group", text="Limit to Vertex Group")
  992.         row = layout.row()
  993.         split = row.split(percentage=0.5)
  994.         colL = split.column()
  995.         colR = split.column()
  996.         colL.prop(props, "growth_mode")
  997.         colR.prop(props, "fork_chance")
  998.         colL.prop(props, "upward_chance")
  999.         colR.prop(props, "death_chance")
  1000.        
  1001.         layout.label(text="Shape Key Settings")
  1002.         layout.label(text="NB. Pre-animated mushrooms must have actions from frames 0-100")
  1003.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_lifecycle_key_set", text="Animation: Manual / Lifecycle Shape Set")
  1004.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_random_key_set", text="Random Shape Set")
  1005.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_bend_key_set", text="Bend Upwards Shape Set")
  1006.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_shrinkwrap_vg", text="Vertex Group for Shrinkwrap")
  1007.        
  1008.     def execute(self,context):
  1009.         if len(self.mushrooms_list)==0:
  1010.             print('Select a mushroom first, you monkey.')
  1011.             return {'FINISHED'}
  1012.         target_object=bpy.data.objects[self.properties.target_object_name]
  1013.         mushroom_group_name=target_object.mushroomer_grouping
  1014.         if mushroom_group_name!='':
  1015.             if (mushroom_group_name not in bpy.data.groups):
  1016.                 bpy.data.groups.new(mushroom_group_name)
  1017.             if (("MT"+mushroom_group_name) not in target_object.vertex_groups):
  1018.                 timing_group_name=(target_object.vertex_groups.new("MT"+mushroom_group_name)).name
  1019.         mushrooms_shapes_database=generate_mushrooms_shapes_database(self.mushrooms_list,target_object,self.shape_key_dict_prefixes)
  1020.         generation_frame=self.properties.global_start_frame
  1021.         vertex_group_lookup=find_corresponding_mushroom_vertex_groups(self.mushrooms_list,target_object,'OB')
  1022.         materials_list=build_materials_list(self.mushrooms_list)
  1023.         vertex_group_lookup_materials=find_corresponding_mushroom_vertex_groups(materials_list,target_object,'MA')
  1024.         includedfaces=set()
  1025.         #save the selection before the other defs mess with it
  1026.         faces=target_object.data.faces
  1027.         selected_faces=list(selectget(faces))
  1028.         set_allowed_faces=get_allowed_faces(target_object,str(target_object.mushroomer_limit_group))
  1029.         #if nothing is selected then randomise the selection
  1030.         #livefaces is a dict... livefaces[faceid]=
  1031.         livefaces=find_live_faces(selected_faces,set_allowed_faces)
  1032.         if self.properties.growth_mode=="3":
  1033.             neighbour_face_dict=build_neighbour_faces_dict(target_object,set_allowed_faces)
  1034.         if len(livefaces)>0:
  1035.             includedfaces|=set(livefaces.keys())
  1036.             for count in range(0,self.properties.iterations):
  1037.                 print ('iteration:'+str(count))
  1038.                
  1039.                 if self.properties.growth_mode=="3":
  1040.                     livefaces=choosenextfaces_conways(faces,livefaces,neighbour_face_dict,set_allowed_faces)
  1041.                     populate_list=livefaces.keys()
  1042.                 elif self.properties.growth_mode=="4":
  1043.                     livefaces=choosenextfaces_simpleincrease(target_object,faces,livefaces,includedfaces,set_allowed_faces)
  1044.                     populate_list=list(set(livefaces.keys())-includedfaces)
  1045.                 else:
  1046.                     livefaces=choosenextfaces(faces,livefaces,includedfaces,self.properties.fork_chance,self.properties.upward_chance,self.properties.death_chance,int(self.properties.growth_mode),set_allowed_faces)
  1047.                     populate_list=list(set(livefaces.keys())-includedfaces)
  1048.                
  1049.                 for face_to_populate in populate_list:
  1050.                     children_this_generation=get_number_for_generation(count,self.properties.iterations,int(self.properties.children_mode),self.properties.children_first_generation,self.properties.children_last_generation,self.properties.children_jitter)
  1051.                     placed_objects=place_many_on_face(target_object, face_to_populate, self.mushrooms_list,children_this_generation, int(self.properties.distribute_method), self.properties.distribute_jitter,self.properties.pack_mode,vertex_group_lookup)
  1052.                     set_materials(placed_objects,target_object,face_to_populate,self.properties.randomize_materials,materials_list,vertex_group_lookup_materials)
  1053.                     #placed objects is a list of [object structures (not names),names of types]
  1054.                     insert_keyframes_multi(placed_objects,generation_frame,mushrooms_shapes_database,self.properties.instance_lifetime,self.properties.instance_lifetime_jitter,self.properties.sub_generational_frame_spread/children_this_generation,self.properties.sub_generational_frame_spread_jitter)
  1055.                     if target_object.mushroomer_shrinkwrap_vg!='-1':
  1056.                         add_mushroom_shrinkwrap(target_object,placed_objects,target_object.mushroomer_shrinkwrap_vg)
  1057.                     if mushroom_group_name!='':
  1058.                         add_mushrooms_to_group(placed_objects,mushroom_group_name)
  1059.                
  1060.                 if mushroom_group_name!='':
  1061.                     save_timing(target_object,faces,livefaces,timing_group_name,count/self.properties.iterations)
  1062.                 #if everything has died out then regenerate...
  1063.                 print('livefaces',livefaces)
  1064.                 if len(livefaces)<1:
  1065.                     if self.properties.growth_mode!="3":
  1066.                         print('regenerating...')
  1067.                         tmplist=list(includedfaces)
  1068.                         livefaces[random.choice(tmplist)]=-1
  1069.                         #so if the value()=-1 then all of the neighbouring faces can be considered for expansion onto
  1070.                     else:
  1071.                         print('everything died, Conway\'s game over.')
  1072.                         break  
  1073.                 includedfaces|=set(livefaces.keys())
  1074.                 generation_frame+=self.properties.generational_frame_advance*(1+(2*random.random()*self.properties.generational_frame_advance_jitter))
  1075.             #display the last set of included faces
  1076.             if bpy.context.mode!='EDIT_MESH':
  1077.                 bpy.ops.object.editmode_toggle()
  1078.             bpy.context.tool_settings.mesh_select_mode=[False,False,True]
  1079.             faces=selectset(faces,includedfaces)
  1080.         else:
  1081.             print('The masking group is empty - can\'t contnue!')
  1082.         return {'FINISHED'}
  1083.        
  1084.        
  1085. #end UI code
  1086.  
  1087. class MushroomerRetime(bpy.types.Operator):
  1088.     '''retimes mushrooms in a given group from group weights'''
  1089.     bl_idname = "mesh.mushroom_retimer"
  1090.     bl_label = "Mushroomer Retimer"
  1091.     bl_options = {'REGISTER', 'UNDO'}
  1092.     dialog_width=250
  1093.    
  1094.     target_object_name=bpy.props.StringProperty(name="Target Object", description="Object receiving the mushrooms",default="")
  1095.     global_start_frame=bpy.props.IntProperty(name="Start Frame", description="Start Frame",default=1,min=0)
  1096.    
  1097.     def invoke(self, context, event):
  1098.         #all the generator functions stoe their result on the target object
  1099.         #but take info from the list of mushrooms
  1100.         self.properties.target_object_name=context.active_object.name
  1101.         build_timing_group_enum(bpy.data.objects[self.properties.target_object_name])
  1102.         self.properties.global_start_frame=bpy.context.scene.frame_current
  1103.         wm = context.manager
  1104.         wm.invoke_props_dialog(self, self.dialog_width)
  1105.         return {'RUNNING_MODAL'}
  1106.  
  1107.     def draw(self, context):
  1108.         layout = self.layout
  1109.         props = self.properties
  1110.         layout.label(text="Retiming "+self.properties.target_object_name)
  1111.         layout.prop(props, "global_start_frame")
  1112.         layout.prop(bpy.data.objects[self.properties.target_object_name], "mushroomer_timing_vg", text="Vertex Group for Retiming")
  1113.        
  1114.     def execute(self,context):
  1115.  
  1116.         return {'FINISHED'}
go to heaven