#!BPY
"""
Name: 'Spawn_XYZ'
Blender: 248
Group: 'Object'
Tip: 'This script distributes objects on another object's surface'
"""

__author__ = "Jimmy Hazevoet"
__url__ = ("")
__version__ = "03-2007"
__bpydoc__ = """\

SpawnXXX.py

This script distributes objects on another object's (mesh) surface.

Usage:

0: Start this script from the scripts menu (Scripts>Object>Spawn).

1: Select and assign one target Object (mesh),
	or one target Object with 'DupliObjects' (DupliVerts, DupliFaces, DupliFrames).

2: Select some objects from the 3D view and append them to the objects list. (see list of objects/groups in console)

If duplication mode is 'Group Instance': press the 'Update' button to update the groups menu,
select a group from the menu and 'Append' the group to the groups list, append all the groups you want to instance.

3: Set the options, and hit the Generate button.
"""


# --------------------------------------------------------------------------
# Spawn.py by Jimmy Hazevoet
# --------------------------------------------------------------------------
# ***** 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------


# Alt+P to run script


import Blender
from Blender import *
from Blender.Mathutils import RotationMatrix
from random import random, choice
from Blender.Draw import *
from Blender.BGL import *
import sys



#--------------------
phi = 3.14159265359/180


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


#------------------------------------------------------------
## Make Object Duplicates

#--------------------
# duplication mode: Simple copy
def SimpleCopy( ob, scn ):
	return scn.objects.new( ob.data )

##--------------------
# duplication mode: Linked Duplicate
def MakeLinkedDuplicate( ob, scn ):
	type = ob.getType()
	if type != 'Empty':
		newOb = scn.objects.new( ob.data )
		newOb.shareFrom( ob )
	else:
		newOb = scn.objects.new( 'Empty' )
	newOb.setMatrix(ob.getMatrix())
	newOb.setDrawMode(ob.getDrawMode())
	newOb.setDrawType(ob.getDrawType())
	if newOb.type == ob.type:
		newOb.modifiers = ob.modifiers
	return newOb

###--------------------
# duplication mode: Object Duplicate
def MakeDuplicate( ob, scn ):
	ob.select( 1 )
	Object.Duplicate()
	newOb = scn.objects.active
	newOb.select( 0 )
	return newOb

####--------------------
# duplication mode: Instance group at an empty using dupligroups
def MakeGroupInstance( grp, scn ):
	gp = Group.Get( grp.name )
	newOb = scn.objects.new('Empty')
	newOb.name = grp.name + '_Instance'
	newOb.enableDupGroup = True
	newOb.DupGroup = gp
	return newOb


#####--------------------
# object DUPLICATIONMODES:

DUPLICATIONMODES = SimpleCopy, MakeLinkedDuplicate, MakeDuplicate, MakeGroupInstance


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


#--------------------------------------------------------------------
#--------------------------------------------------------------------
## Duplicate objects to: Vertices
###------------------------------------------------------------------
def ObsToVerts( targetname='', objectnames=[], duplimode=1, variate=(0.0,0.0), axis=('z','y'), holes=0 ):
	"""
	
	"""
	
	#--------------------
	# get objects or groups from list with names
	if duplimode == 3:
		dobjs = [ Group.Get(name) for name in objectnames ]
	else:
		dobjs = [ Object.Get(name) for name in objectnames ]

	#--------------------
	rotaxis = axis[0]
	if rotaxis == '-x': rotaxis = 'x'
	if rotaxis == '-y': rotaxis = 'y'
	if rotaxis == '-z': rotaxis = 'z'

	#--------------------
	scene = Scene.GetCurrent()
	scene.objects.selected = []

	#--------------------
	# get target
	target = Object.Get( targetname )
	if target.getType() == 'Mesh':
		me = Mesh.New()
		me.getFromObject(target.name)
		me.transform(target.matrix)

		for v in me.verts:
			if v.sel !=0:
				if random()*100 >= holes:

					# make duplicates
					new_ob = DUPLICATIONMODES[ duplimode ] ( choice( dobjs ), scene )

					# location
					new_ob.loc = v.co

					# rotation
					vno = v.no.toTrackQuat( axis[0], axis[1] ).toEuler()
					new_ob.rot = vno[0]*phi, vno[1]*phi, vno[2]*phi
					# variate[0] rot
					if variate[0] > 0.0:
						new_ob.setMatrix( RotationMatrix( (random()-0.5)*360*variate[0], 4, rotaxis ) * new_ob.matrix )

					# variate[1] size
					if variate[1] > 0.0:
						rs = 1+(random()-0.5)*variate[1]
						new_ob.size = new_ob.SizeX*rs, new_ob.SizeY*rs, new_ob.SizeZ*rs
		
		me=[]
		#--------------------



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

 # ------------------------------------------------------------------------ #
 # --- Function to get a random location on a face --- # The Beast 0.5 Beta #
def GetRandFacePos( obj, face ):

   tvert = []
   for vert in face.v:
      tvert.append( vert.co )

   nverts = len(face.v)
   vertns = []
   rone = random()
   rtwo = random()
   rthree = random()
   pone   = [0.0, 0.0, 0.0]
   ptwo   = [0.0, 0.0, 0.0]
   pthree = [0.0, 0.0, 0.0]

   for i in range(0, 4):
      rn = int(random() * nverts)
      j = 0
      while vertns.count(rn) > 0 and j < 100:
         rn = int(random() * nverts)
         j += 1
      vertns.append(rn)

   pone[0] = ((1 - rone) * tvert[vertns[0]][0]) + (rone * tvert[vertns[1]][0])
   pone[1] = ((1 - rone) * tvert[vertns[0]][1]) + (rone * tvert[vertns[1]][1])
   pone[2] = ((1 - rone) * tvert[vertns[0]][2]) + (rone * tvert[vertns[1]][2])

   ptwo[0] = ((1 - rtwo) * tvert[vertns[2]][0]) + (rtwo * tvert[vertns[3]][0])
   ptwo[1] = ((1 - rtwo) * tvert[vertns[2]][1]) + (rtwo * tvert[vertns[3]][1])
   ptwo[2] = ((1 - rtwo) * tvert[vertns[2]][2]) + (rtwo * tvert[vertns[3]][2])

   pthree[0] = ((1 - rthree) * ptwo[0]) + (rthree * pone[0])
   pthree[1] = ((1 - rthree) * ptwo[1]) + (rthree * pone[1])
   pthree[2] = ((1 - rthree) * ptwo[2]) + (rthree * pone[2])

   return pthree
 

#--------------------------------------------------------------------
#--------------------------------------------------------------------
## Duplicate objects to: Faces
###------------------------------------------------------------------
def ObsToFaces( targetname='', objectnames=[], duplimode=1, variate=(0.0,0.0), axis=('z','y'), rescale=1, density=1, holes=0 ):
	"""
	
	"""

	#--------------------
	# get objects or groups from list with names
	if duplimode == 3:
		dobjs = [ Group.Get(name) for name in objectnames ]
	else:
		dobjs = [ Object.Get(name) for name in objectnames ]

	#--------------------
	rotaxis = axis[0]
	if rotaxis == '-x': rotaxis = 'x'
	if rotaxis == '-y': rotaxis = 'y'
	if rotaxis == '-z': rotaxis = 'z'

	#--------------------
	scene = Scene.GetCurrent()
	scene.objects.selected = []

	#--------------------
	# get target
	target = Object.Get( targetname )
	if target.getType() == 'Mesh':
		me = Mesh.New()
		me.getFromObject(target.name)
		me.transform(target.matrix)

		randpos = 0
		if density ==0:
			density = 1
			randpos = 1

		for f in me.faces:

			i=0
			while i < density:
				i+=1
				if f.sel !=0:
					if random()*100 >= holes:

						# make duplicates
						new_ob = DUPLICATIONMODES[ duplimode ] ( choice( dobjs ), scene )

						# location
						if density > 1:
							new_ob.loc = GetRandFacePos( target, f )
						else:
							if randpos !=0:
								new_ob.loc = GetRandFacePos( target, f )
							else:
								new_ob.loc = f.cent
	
						# rotation
						fno = f.no.toTrackQuat( axis[0], axis[1] ).toEuler()
						new_ob.rot = fno[0]*phi, fno[1]*phi, fno[2]*phi
						# variate[0] rot
						if variate[0] > 0.0:
							new_ob.setMatrix( RotationMatrix( (random()-0.5)*360*variate[0], 4, rotaxis ) * new_ob.matrix )

						# size
						if rescale !=0:
							scale = f.area, f.area, f.area
						else:
							scale = new_ob.SizeX, new_ob.SizeY, new_ob.SizeZ
						# variate[1] size
						if variate[1] > 0.0:
							rs = 1+(random()-0.5)*variate[1]
							new_ob.size = ( scale[0]*rs, scale[1]*rs, scale[2]*rs )
						else:
							if rescale !=0:
								new_ob.size = ( scale[0], scale[1], scale[2] )

		me=[]
		#--------------------



#--------------------------------------------------------------------
#--------------------------------------------------------------------
## Duplicate objects to: DupliObjects
###------------------------------------------------------------------
def ObsToDupObs( targetname='', objectnames=[], duplimode=1, variate=(0.0,0.0), rescale=0, holes=33 ):
	"""
	
	"""
	
	#--------------------
	# get objects or groups
	if duplimode == 3:
		dobjs = [ Group.Get(name) for name in objectnames ]
	else:
		dobjs = [ Object.Get(name) for name in objectnames ]

	#--------------------
	scene = Scene.GetCurrent()
	scene.objects.selected=[]

	#--------------------
	# get the "DupliVert/Face/Frame" object
	try:
		target = Object.Get( targetname )
		dupe_obs = target.DupObjects
	except:
		return

	#--------------------
	for dupe_ob, dupe_matrix in dupe_obs:
		if random()*100 >= holes:

			# make duplicates
			source = choice( dobjs )
			new_ob = DUPLICATIONMODES[ duplimode ] ( source, scene )

			# rotation, matrix
			if variate[0] >0.0:
				# variate[0] rot
				new_ob.setMatrix( RotationMatrix( (random()-0.5)*360*variate[0], 4, 'z' ) * dupe_matrix )
			else:
				# set new object matrix
				new_ob.setMatrix(dupe_matrix)

			# size
			if rescale ==0:
				new_ob.size = source.size
			# variate[1] size
			if variate[1] > 0.0:
				rs = 1+(random()-0.5)*variate[1]
				new_ob.size = ( new_ob.SizeX*rs, new_ob.SizeY*rs, new_ob.SizeZ*rs )



#--------------------------------------------------------------------
#--------------------------------------------------------------------
## Duplicate objects to: (Static) Particle location
###------------------------------------------------------------------
def ObsToParticleS( targetname='', objectnames=[], duplimode=1, variate=(0.0,0.0), holes=33 ):
	"""
	
	"""

	#--------------------
	# get objects or groups
	if duplimode == 3:
		dobjs = [ Group.Get(name) for name in objectnames ]
	else:
		dobjs = [ Object.Get(name) for name in objectnames ]

	#--------------------
	scene = Scene.GetCurrent()
	scene.objects.selected=[]

	#--------------------
	# get target
	target = Object.Get( targetname )
	eff = Effect.Get( target.name )[0]
	particleloc = eff.getParticlesLoc()
	for pl in particleloc:
		if random()*100 >= holes:

			# make duplicates
			source = choice( dobjs )
			new_ob = DUPLICATIONMODES[ duplimode ] ( source, scene )

			# particle location
			new_ob.loc = ( pl[0] )

			# variate[1] size
			if variate[1] > 0.0:
				rs = 1+(random()-0.5)*variate[1]
				new_ob.size = ( new_ob.SizeX*rs, new_ob.SizeY*rs, new_ob.SizeZ*rs )

			# variate[0] rot
			if variate[0] > 0.0:
				#new_ob.rot = ( (random()-0.5)*variate[0], (random()-0.5)*variate[0], (random()-0.5)*variate[0] )
				new_ob.RotZ = (random()-0.5)*variate[0]


#------------------------------------------------------------
#------------------------------------------------------------
#ObsToVerts( targetname='Sphere', objectnames=['Cube','Suzanne','Cone'], duplimode=1, variate=(1.0,0.5), axis=('z','y') )
#ObsToFaces( targetname='Sphere', objectnames=['Cube','Suzanne','Cone'], duplimode=1, variate=(1.0,0.5), axis=('z','y'), rescale=1 )
#ObsToDupObs( targetname='Sphere', objectnames=['Cube','Suzanne','Cone'], duplimode=1, variate=(1.0,0.5), rescale=1, holes=33 )
#ObsToParticleS( targetname='Sphere', objectnames=['Cube','Suzanne','Cone'], duplimode=1, variate=(1.0,0.5), holes=33 )
#------------------------------------------------------------
#------------------------------------------------------------


#------------------------------------------------------------
##------------------------------------------------------------
### G.U.I.
##------------------------------------------------------------
#------------------------------------------------------------


#------------------------------------------------------------
## Initialize
###------------------------------------------------------------

# buttons
VAR_DUPLIMODE = Create(1)
VAR_DUPLITARGET = Create(1)
VAR_VARIATE = [ Create(0.0), Create(0.0) ]
VAR_AXIS = [ Create(2), Create(1) ]
VAR_RESCALE = Create(0)
VAR_DENSITY = Create(1)
VAR_HOLES = Create(0)
VAR_GROUPSMENU = Create(1)
VAR_GUIPANEL = Create(0)

# bevents
Assign_Evt=11
Append_Evt=22
Grp_Evt=21
Del_Evt=23
Gen_Evt=31
Btn_Evt=42
End_Evt=99

# globals
TargetOb = []
ObsList = []
AllGroups = []
for gp in Group.Get():
	AllGroups.append( gp.name )
axisList = [ 'x','y','z','-x','-y','-z' ]


#------------------------------------------------------------
##
###------------------------------------------------------------
def draw_Text( ( x, y ), text, color, size ):
	glColor3f( color[0],color[1],color[2] )
	glRasterPos2d(x,y)
	txtsize = 'small', 'normal', 'large'
	Text( text, txtsize[ size ] )
def draw_BackPanel( text, x, y, w, h, colors ):
	glColor3f( colors[0]*0.8, colors[1]*0.8, colors[2]*0.8 )
	glRecti( x, h, w, h+20 )
	glColor3f( colors[0], colors[1], colors[2] )
	glRecti( x, y, w, h )
	glColor3f( colors[0], colors[1], colors[2] )
	glRasterPos2d( x+8, h+5 )
	Text( text )


#------------------------------------------------------------
## Draw
###------------------------------------------------------------

def main():
	global VAR_DUPLIMODE, VAR_DUPLITARGET, VAR_VARIATE, VAR_AXIS, VAR_RESCALE, VAR_DENSITY, VAR_HOLES, VAR_GROUPSMENU
	global TargetOb, ObsList, AllGroups, VAR_GUIPANEL
	
	glClearColor(0.68,0.68,0.68,1.0)
	glClear(GL_COLOR_BUFFER_BIT)

	if VAR_GUIPANEL.val !=0:
		w = 150
		h = 80
	else:
		w = 0
		h = 0
		
	VAR_GUIPANEL = Toggle( '-', Btn_Evt, 123+w, 242-h,  15, 15, VAR_GUIPANEL.val, 'G.U.I.' )
	draw_BackPanel( 'Spawn XYZ', 8, 8, 160+w, 240-h, [0.73,0.73,0.73] )
	PushButton( 'X', End_Evt, 140+w, 242-h, 15, 15, 'Exit' )

	draw_Text( ( 16, 227-h ), 'Target :', [0.0,0.0,0.0], 0 )
	PushButton( 'Assign target', Assign_Evt, 16, 205-h, 138, 16, 'Assign selected target (mesh, or object with DupliObjects)' )
	if TargetOb !=[]:
		draw_Text( ( 56, 227-h ), TargetOb[0].name, [1.0,1.0,1.0], 0 )

	if VAR_DUPLIMODE.val == 3:
		draw_Text( ( 16, 192-h ), 'Groups :', [0.0,0.0,0.0], 0 )
		PushButton( 'Upd', Grp_Evt, 16, 150-h , 30, 16, 'Update groups menu' )
		VAR_GROUPSMENU = Menu( 'Select Group and Append to list %t|' + '|'.join( AllGroups ), Btn_Evt, 48, 150-h, 106, 16, VAR_GROUPSMENU.val, 'Select Group and Append to list' )
		apptxt = ' group'
	else:
		draw_Text( ( 16, 192-h ), 'Objects :', [0.0,0.0,0.0], 0 )
		apptxt = ' objects'
	PushButton( 'Append'+apptxt, Append_Evt,  48, 170-h,  106, 16, 'Append selected'+apptxt+' to list for duplication' )
	PushButton( 'Del',       Del_Evt,  16, 170-h,  30, 16, 'Clear list' )
	draw_Text( ( 64, 192-h ), str(len(ObsList)), [1.0,1.0,1.0], 0 )

	VAR_DUPLIMODE = Menu( 'Object duplication mode: %t|Simple Copy %x0|Linked Duplicate %x1|Duplicate %x2|Group Instance %x3', Btn_Evt, 16+w, 130, 68, 16, VAR_DUPLIMODE.val, 'Object duplication type' )
	min16=0
	if VAR_DUPLITARGET.val ==1:
		min16 = 16
		VAR_DENSITY = Number( '', Btn_Evt, 138+w, 130, 16, 16, VAR_DENSITY.val, 0, 9, '0=Random face position, 1=Face center, 2+=Density, number of objects on a face' )
	VAR_DUPLITARGET = Menu( 'Duplicate objects to: %t|Vertices %x0|Faces %x1|DupliObjects %x2', Btn_Evt, 86+w, 130, 68-min16, 16, VAR_DUPLITARGET.val, 'Duplicate objects to: selected vertices, selected faces, or DupliObjects' )

	min16 = 0	
	if VAR_DUPLITARGET.val !=0:
		min16 = 16
		VAR_RESCALE = Toggle( 'S',     Btn_Evt, 138+w, 85,  16, 16, VAR_RESCALE.val, 'Rescale new objects' )

	VAR_VARIATE[1] = Slider( 'rSize:', Btn_Evt, 16+w, 85, 138-min16, 16, VAR_VARIATE[1].val, 0.0, 2.0, 0, 'Random size amount' )
	VAR_VARIATE[0] = Slider( 'rRot:',  Btn_Evt, 16+w,  65, 138, 16, VAR_VARIATE[0].val, 0.0, 2.0, 0, 'Random rotation amount' )

	if VAR_DUPLITARGET.val in [0,1]:
		VAR_AXIS[0] = Menu( 'Track axis %t|Track X %x0|Track Y %x1|Track Z %x2|Track -X %x3|Track -Y %x4|Track -Z %x5', Btn_Evt, 16+w, 45, 68, 16, VAR_AXIS[0].val, 'Specify the axis that points to another object. (Track and Up axis can not be the same)' )
		VAR_AXIS[1] = Menu( 'Up axis %t|Up X %x0|Up Y %x1|Up Z %x2', Btn_Evt, 86+w, 45, 68, 16, VAR_AXIS[1].val, 'Specify the axis that points up. (Track and Up axis can not be the same)' )

	VAR_HOLES = Slider('Holes%', Btn_Evt, 16+w, 105, 138, 16, VAR_HOLES.val, 0, 99, 0, 'Random holes amount' )

	PushButton( 'Generate', Gen_Evt,  16, 18, 138+w, 16, 'Generate duplicates' )



#------------------------------------------------------------
## Events, Bevents
###------------------------------------------------------------

def events(evt, val):
	if evt in [ QKEY ] and not val:
		Exit()

###------------------
def bevents(evt):
	global VAR_DUPLIMODE, VAR_DUPLITARGET, VAR_VARIATE, VAR_AXIS, VAR_RESCALE, VAR_DENSITY, VAR_HOLES, VAR_GROUPSMENU
	global TargetOb, ObsList, AllGroups, axisList

	if evt == End_Evt:
		#name = "OK ?%t|Quit %x1"
		#result = PupMenu(name)
		#if result==1:
		Exit()

	if evt == Btn_Evt:
		Draw()

	if evt == Assign_Evt: # assign target
		scn=Scene.GetCurrent()
		targetobj = scn.objects.active
		if targetobj:
			TargetOb=[]
			TargetOb.append( targetobj )
			print '\n Assigned target: ', targetobj.name
			targetobj.sel=0
			Window.RedrawAll()

	if evt == Grp_Evt: # update groups menu
		AllGroups = []
		for gp in Group.Get():
			AllGroups.append( gp.name )
		Draw()

	if evt == Append_Evt: # append objects/groups
		if VAR_DUPLIMODE.val ==3:
			thisgroup = AllGroups[ VAR_GROUPSMENU.val-1 ]
			ObsList.append( thisgroup )
			print '\n Selected groups to duplicate: ', ObsList
			Draw()
		else:
			scn=Scene.GetCurrent()
			appobjs = [ ob.name for ob in scn.objects.selected ]
			if appobjs:
				for objs in appobjs:
					ObsList.append( objs )
				print '\n Selected objects to duplicate: ', ObsList
				scn.objects.selected=[]
				Window.RedrawAll()

	if evt == Del_Evt: # clear objects list
		ObsList=[]
		Draw()

	if evt == Gen_Evt: # generate
		if TargetOb !=[]:
			if ObsList !=[]:
				#try:
					t=Blender.sys.time()
					Window.WaitCursor(1)

					if VAR_DUPLITARGET.val ==0: # objects to vertices
							ObsToVerts( TargetOb[0].name, ObsList,  VAR_DUPLIMODE.val, (VAR_VARIATE[0].val,VAR_VARIATE[1].val), (axisList[VAR_AXIS[0].val],axisList[VAR_AXIS[1].val]), VAR_HOLES.val )

					elif VAR_DUPLITARGET.val ==1: # objects to faces
							ObsToFaces( TargetOb[0].name, ObsList,  VAR_DUPLIMODE.val, (VAR_VARIATE[0].val,VAR_VARIATE[1].val), (axisList[VAR_AXIS[0].val],axisList[VAR_AXIS[1].val]), VAR_RESCALE.val, VAR_DENSITY.val, VAR_HOLES.val )

					elif VAR_DUPLITARGET.val ==2: # objects to dupobjects
						if TargetOb[0].DupObjects:
							ObsToDupObs( TargetOb[0].name, ObsList, VAR_DUPLIMODE.val, (VAR_VARIATE[0].val,VAR_VARIATE[1].val), VAR_RESCALE.val, VAR_HOLES.val )
						else:
							PupMenu( TargetOb[0].name + " has no DupliObjects")

					Window.WaitCursor(0)
					Window.RedrawAll()
					print " Duplicate: done in %.6f" % (Blender.sys.time()-t)
				#except:
				#	PupMenu( "Unexpected error: " + str(sys.exc_info()[0]) )

###------------------
if __name__ == '__main__':
	Register( main, events, bevents )

