#!BPY

"""
Name: 'Slice'
Blender: 248
Group: 'Object'
Tooltip: 'Compute cross-section(s) from selected mesh-object(s)'
"""

__author__ = "Michael Schardt (M.Schardt@web.de)"

__version__ = "1.0.0 - 07.03.2007"

__bpydoc__ = """ """

# Id: Slice.py, v1.0 - 07.03.2007
#
# -------------------------------------------------------------------------- 
# Slice v1.0 (C) Michael Schardt
# -------------------------------------------------------------------------- 
#
#
# ***** 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 ***** 
#
# -------------------------------------------------------------------------- 

import Blender; from Blender import *

try:
	import psyco; psyco.full()
except:
	pass

epsilon = 1.0E-3

#####################################################################
#####################################################################

def Slice(object, splitplane, create = True):

	"""syntax: Slice(object, splitplane, create = True)
   
   return value: dictionary
   key 'vertices': slice vertices (list of vectors)
   key 'edges':    slice edges (list of index tuples to vertex list)
   key 'faces':    slice faces (list of index tuples to vertex list)\n
	"""

	# helpers:
	##########
	
	def safe_append(list, item):
		if item not in list:	list.append(item)
		
	##########

	slice_vertices = []
	slice_edges    = []
	slice_faces    = []

	Window.EditMode(0)

	Window.WaitCursor(1)

	# transform splitplane to object's local space
		
	local_splitplane_o = splitplane.getData(0, 1).verts[0].co
	local_splitplane_n = splitplane.getData(0, 1).faces[0].no
	
	global_splitplane_o = local_splitplane_o * splitplane.getMatrix('worldspace')
	global_splitplane_n = local_splitplane_n * splitplane.getMatrix('worldspace').rotationPart()
				
	object_matrix_i = object.getMatrix('worldspace').copy().invert()
	object_matrix_t = object.getMatrix('worldspace').copy().transpose()
	
	local_splitplane_o = global_splitplane_o * object_matrix_i
	local_splitplane_n = global_splitplane_n * object_matrix_t

	# calculate vertex classifications:
	###################################
	
	# vertex classification based on signed distance to splitplane:
	#  ( 0, distance): vertex on splitplane
	#	(+1, distance): vertex in positive halfspace
	#  (-1, distance): vertex in negative halfspace		
	
	# object flags:
	# object_class_z: at least one vertex on splitplane
	# object_class_p: at least one vertex in positive halfspace
	# object_class_n: at least one vertex in negative halfspace
	
	vertex_class = {} 

	object_class_z = False
	object_class_p = False
	object_class_n = False

	mesh = object.getData(0, 1)	
			
	for vertex in mesh.verts:
		
		distance = (vertex.co - local_splitplane_o) * local_splitplane_n
		
		if -epsilon <= distance <= +epsilon:
 			vertex_class[vertex] = (0, distance)
			object_class_z = True
			continue
		
		if distance > +epsilon:
			vertex_class[vertex] = (+1, distance)
			object_class_p = True
			continue
			
		if distance < -epsilon:
			vertex_class[vertex] = (-1, distance)
			object_class_n = True
			continue
			
	# object intersection:
	######################
	
	if (object_class_z or (object_class_p and object_class_n)) == True:
	
		intersectioncache = {}
							
		# face-loop:
		############
	
		for face in mesh.faces:
	
			# face flags:
			# face_class_z: at least one vertex on splitplane
			# face_class_p: at least one vertex in positive halfspace
			# face_class_n: at least one vertex in negative halfspace
			
			face_class = [vertex_class[vertex][0] for vertex in face.v]
			
			face_class_z =  0 in face_class
			face_class_p = +1 in face_class
			face_class_n = -1 in face_class
			
			# face on splitplane:
			#####################
	
			if (face_class_p or face_class_n) == False:
						
				for vertex in face.v:
					safe_append(slice_vertices, vertex.co)
						
				slice_faces.append([slice_vertices.index(vertex.co) for vertex in face.v])
				
				continue
		
			# face intersection:
			####################
	
			if (face_class_z or (face_class_p and face_class_n)) == True:
	
				edge_vertices = []
		
				# edge loop:
				############
				
				face_nv = len(face.v)
				
				for faceindex_1 in xrange(face_nv):
	
					faceindex_2 = (faceindex_1 + 1) % face_nv
	
					v1 = face.v[faceindex_1]
					v2 = face.v[faceindex_2]
																							
					# edge v1 to v2:
					################
	
					# v1 on splitplane
					##################
					
					if vertex_class[v1][0] == 0:
						safe_append(slice_vertices, v1.co)
						safe_append(edge_vertices, v1.co)
						
					# v2 on splitplane
					##################
					
					if vertex_class[v2][0] == 0:
						safe_append(slice_vertices, v2.co)
						safe_append(edge_vertices, v2.co)
	
					# v1, v2 on opposite sides of splitplane:
					#########################################
												
					if (vertex_class[v1][0] * vertex_class[v2][0] < 0):
	
						meshindex1 = v1.index
						meshindex2 = v2.index
						
						if (meshindex1 < meshindex2):	edgekey = (meshindex1, meshindex2)
						if (meshindex2 < meshindex1):	edgekey = (meshindex2, meshindex1)
	
						vi = intersectioncache.setdefault(edgekey, (v2.co * vertex_class[v1][1] - v1.co * vertex_class[v2][1]) / (vertex_class[v1][1] - vertex_class[v2][1]))
																		
						safe_append(slice_vertices, vi)
						safe_append(edge_vertices, vi)
	
				# create slice edges:
				#####################
			
				edge_nv = len(edge_vertices)
	
				if edge_nv > 1:
					
					for edgeindex_1 in xrange(edge_nv):
						
						edgeindex_2 = (edgeindex_1 + 1) % edge_nv
					
						safe_append(slice_edges, (slice_vertices.index(edge_vertices[edgeindex_1]),
											  			  slice_vertices.index(edge_vertices[edgeindex_2])))
						
															
		# create slice object:
		######################
	
		if (create):	
		
			new_mesh = Mesh.New("Slice("+mesh.name+")")		
			new_mesh.verts.extend(slice_vertices) 
			new_mesh.edges.extend(slice_edges)
			new_mesh.faces.extend(slice_faces)								
			new_mesh.mode = mesh.mode			
			new_object = Scene.GetCurrent().objects.new(new_mesh)
			new_object.name = "Slice("+object.name+")"
			new_object.LocX = object.LocX
			new_object.LocY = object.LocY
			new_object.LocZ = object.LocZ
			new_object.RotX = object.RotX
			new_object.RotY = object.RotY
			new_object.RotZ = object.RotZ
			new_object.SizeX = object.SizeX
			new_object.SizeY = object.SizeY
			new_object.SizeZ = object.SizeZ
			new_object.layers = object.layers
			new_object.makeDisplayList()
					
			new_object.select(1)		

	Window.WaitCursor(0)

	Window.RedrawAll()
	
	return {"vertices": slice_vertices, "edges": slice_edges, "faces": slice_faces}

#####################################################################
#####################################################################

def check_selection(selection):
	
	if len(selection) < 2:
		Draw.PupMenu("please select min 2 mesh-objects: object to be sliced and splitplane")
		return 0
	else:
		for object in selection:
			if object.getType() != "Mesh":
				Draw.PupMenu("object '"+object.name+"' is not a mesh-object - exit")
				return 0
	return 1

#####################################################################
#####################################################################

if __name__ == "__main__":
	
	selection = Object.GetSelected()
	
	if check_selection(selection):
	
		namestring = ""
		for index, object in enumerate(selection):
			namestring += object.name + "%x" + str(index) +"|"
			
		res = Draw.PupMenu("choose splitplane%t|"+namestring)
		
		if res == -1:

			print "\nAborted"

		else:

			splitplane = selection[res]; selection.remove(splitplane)
	
			for object in selection:
	
				print "\nCalculating Slice("+object.name+"):"
	
				object.select(0)
				splitplane.select(0)
	
				Slice(object, splitplane, True)
				
			print "\nDone"
