#!BPY

""" Registration info for Blender menus
Name: 'Bevel Center'
Blender: 234
Group: 'Mesh'
Tip: 'Bevel selected vertices'
"""

# $Id: bevel_center.py,v 1.4 2004/07/30 05:18:13 ianwill Exp $
#
######################################################################
# Bevel Center v1 for Blender
#
# This script lets you bevel the selected vertices and control the
# thickness of the bevel
#
# (c) 2004 Loïc Berthe (loic.berthe@lilotux.net)
# released under Blender Artistic License
#
######################################################################

import Blender
from Blender import NMesh, Window
from Blender.Draw import *
from Blender.BGL import *

from math import pi, sin, sqrt

######################################################################
# Functions to handle the global structures of the script NV, NE and NC
# which contain informations about the vertices, faces and corners to be
# created

class Dir:
	def __init__(self, co):
		self.co = co

def add_to_NV(old,co,new):
	dir = Dir(co)
	#
	if old in NV.keys():
		NV[old][dir] = new
	else:
		NV[old] = {dir:new}

def is_in_NV(old,co):
	if old in NV.keys():
		for dir in NV[old]:
			if dir.co == co : return NV[old][dir]
	#
	return False

def add_to_NE(old, new):
	ind1 = old[0].index
	ind2 = old[1].index
	if ind1 > ind2:
		new.reverse()
		ind1,ind2 = ind2,ind1
	id = str(ind1)+"_"+str(ind2)
	if id in NE.keys():
		[NE[id].append(v) for v in new]
	else:
		NE[id] = new

def add_to_NC(old,edge):
	if old in NC.keys():
		NC[old].append(edge)
	else:
		NC[old] = [edge]
		
######################################################################
# Geometric functions
		
def norm(vec):
	n = sqrt(vec[0]**2+vec[1]**2+vec[2]**2)
	return [vec[0]/n,vec[1]/n,vec[2]/n]

def parall_coord(old, dir):
	co = old.co
	vec = [0.0,0.0,0.0]
	nco = [0.0,0.0,0.0]
	#
	if len(dir) == 1:
		for i in range(3): vec[i] = dir[0].co[i] - co[i] 
		vec = norm(vec)
	#
	elif len(dir) == 2:
		vec1 = [0.0,0.0,0.0]
		vec2 = [0.0,0.0,0.0]
		for i in range(3):
			vec1[i] = dir[0].co[i] - co[i] 
			vec2[i] = dir[1].co[i] - co[i] 
		vec1 = norm(vec1)
		vec2 = norm(vec2)
		for i in range(3) : vec[i] = vec1[i]+vec2[i]
	#
	for i in range(3): nco[i] = co[i] + dist.val*vec[i]
	return (nco,vec)

def get_vert(old, dir):
	""" Look in NV if a vertice corresponding to the vertex old and the
	direction dir already exists, and create one otherwise""" 
	(nco, vec) = parall_coord(old, dir)
	v = is_in_NV(old,vec)
	if v: return v
	#
	v = NMesh.Vert(nco[0],nco[1],nco[2])
	v.sel = 1
	me.verts.append(v)
	add_to_NV(old,vec,v)
	return v
			
######################################################################
# Functions to create the differents faces
	
def make_NF():
	""" Analyse the mesh, sort the faces containing selected vertices and
	create a liste NF : NF = [[flag, vertlist, old_face]]. Flag describes the
	topology of the face.""" 
	#
	for f in me.faces:
		V = f.v
		v_sel = [x.sel for x in V]
		nb_sel = sum(v_sel)
		if nb_sel == 0 :
			pass
		else:
			nb_v = len(V)
			#
			if nb_v == 4:
				#
				if nb_sel == 4:
					NF.append([1,V,f])
				#
				elif nb_sel == 3:
					if v_sel == [0,1,1,1]: V = [V[1],V[2],V[3],V[0]]
					elif v_sel == [1,0,1,1]: V = [V[2],V[3],V[0],V[1]]
					elif v_sel == [1,1,0,1]: V = [V[3],V[0],V[1],V[2]]
					NF.append([2,V,f])
				#		
				elif nb_sel == 2:
					if v_sel == [1,0,1,0] or v_sel == [0,1,0,1]:
						if	v_sel == [0,1,0,1]: V = [V[1],V[2],V[3],V[0]]
						NF.append([5,[V[0],V[1],V[3]],f])
						NF.append([5,[V[2],V[1],V[3]]])
					else:
						if v_sel == [0,1,1,0]: V = [V[1],V[2],V[3],V[0]]
						elif v_sel == [0,0,1,1]: V = [V[2],V[3],V[0],V[1]]
						elif v_sel == [1,0,0,1]: V = [V[3],V[0],V[1],V[2]]
						NF.append([3,V,f])
				#
				else:
					if v_sel == [0,1,0,0]: V = [V[1],V[2],V[3],V[0]]
					elif v_sel == [0,0,1,0]: V = [V[2],V[3],V[0],V[1]]
					elif v_sel == [0,0,0,1]: V = [V[3],V[0],V[1],V[2]]
					NF.append([4,V,f])
			#
			elif nb_v == 3:
				#
				if nb_sel == 3:
					NF.append([6,V,f])
				#
				elif nb_sel == 2:
					if v_sel == [0,1,1]: V = [V[1],V[2],V[0]]
					elif v_sel == [1,0,1]: V = [V[2],V[0],V[1]]
					NF.append([7,V,f])
				#
				else:
					if v_sel == [0,1,0]: V = [V[1],V[2],V[0]]
					elif v_sel == [0,0,1]: V = [V[2],V[0],V[1]]
					NF.append([5,V,f])

def make_faces():
	""" Make the new faces according to NF """
	#
	for N in NF:
		cas = N[0]
		V = N[1]
		#
		if cas < 6:
			new_v = [0,0,0,0]
			if cas == 1:				# v_sel = [1,1,1,1]
				for i in range(-1,3):
					new_v[i] = get_vert(V[i],[V[i-1],V[i+1]])
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				for i in range(-1,3):
					add_to_NE([V[i],V[i+1]],[new_v[i],new_v[i+1]])
			#
			elif cas == 2:				# v_sel = [1,1,1,0]
				new_v[0] = get_vert(V[0],[V[3]])
				new_v[1] = get_vert(V[1],[V[0],V[2]])
				new_v[2] = get_vert(V[2],[V[3]])
				new_v[3] = V[3]
				#
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				#
				add_to_NE([V[0],V[1]],[new_v[0],new_v[1]])
				add_to_NE([V[1],V[2]],[new_v[1],new_v[2]])
			#		
			elif cas == 3:				# v_sel = [1,1,0,0]
				new_v[0] = get_vert(V[0],[V[3]])
				new_v[1] = get_vert(V[1],[V[2]])
				new_v[2] = V[2]
				new_v[3] = V[3]
				#
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				#
				add_to_NE([V[0],V[1]],[new_v[0],new_v[1]])
			#
			elif cas == 4:				# v_sel = [1,0,0,0]
				new_v[0] = get_vert(V[0],[V[3]])
				new_v[1] = get_vert(V[0],[V[1]])
				new_v[2] = V[1]
				new_v[3] = V[3]
				#
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				#
				add_to_NC(V[0], new_v[0:2])
				#
				new_v[0] = V[1]
				new_v[1] = V[2]
				new_v[2] = V[3]
				#
				new_f = NMesh.Face(new_v[:3])
				me.faces.append(new_f)
			#
			else:				# v_sel = [1,0,0]
				new_v[0] = get_vert(V[0],[V[2]])
				new_v[1] = get_vert(V[0],[V[1]])
				new_v[2] = V[1]
				new_v[3] = V[2]
				#
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				#
				add_to_NC(V[0], new_v[0:2])
		#
		else:
			new_v = [0,0,0]
			#
			if cas == 6:				# v_sel = [1,1,1]
				for i in range(-1,2):
					new_v[i] = get_vert(V[i],[V[i-1],V[i+1]])
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				for i in range(-1,2):
					add_to_NE([V[i],V[i+1]],[new_v[i],new_v[i+1]])
			#
			elif cas == 7:				# v_sel = [1,1,0]
				new_v[0] = get_vert(V[0],[V[2]])
				new_v[1] = get_vert(V[1],[V[2]])
				new_v[2] = V[2]
				#
				new_f = NMesh.Face(new_v)
				me.faces.append(new_f)
				add_to_NE([V[0],V[1]],[new_v[0],new_v[1]])

def make_edges():
	""" Make the faces corresponding to selected edges """
	#
	for l in NE.values():
		if len(l) == 4:
			f = NMesh.Face([l[0],l[1],l[3],l[2]])
			me.faces.append(f)

def make_corners():
	""" Make the faces corresponding to selected corners """
	#
	for v in NV.keys():
		V = NV[v].values()
		nb_v = len(V)
		#
		if nb_v < 3:
			pass
		#
		elif nb_v == 3:
			new_f = NMesh.Face(V)
			me.faces.append(new_f)
		#
		else:
			# We need to know which are the edges around the corner.
			# First, we look for the quads surrounding the corner.
			q = [NE[id] for id in NE.keys() if str(v.index) in id.split('_')]
			#
			# We will put the associated edges in the list eed
			is_in_v = lambda x:x in V
			eed =  [filter(is_in_v, l) for l in q]
			#
			# We will add the edges coming from faces where only one vertice is selected.
			# They are stocked in NC.
			if v in NC.keys():
				eed = eed+NC[v]
			b = eed.pop()
			# b will contain the sorted list of vertices
			#
			while  eed:
				for l in eed:
					if l[0] == b[-1]:
						b.append(l[1])
						eed.remove(l)
						break
					elif l[1] == b[-1]:
						b.append(l[0])
						eed.remove(l)
						break
			# Now we can create the faces
			if nb_v == 4:
				new_f = NMesh.Face(b[:4])
				me.faces.append(new_f)
			#
			else:
				co = [0.0, 0.0,0.0]
				vec = [0.0, 0.0,0.0]
				for x in V:
					co[0] += x[0]
					co[1] += x[1]
					co[2] += x[2]
				#
				for dir in NV[v]:
					vec[0] += dir.co[0]
					vec[1] += dir.co[1]
					vec[2] += dir.co[2]
				#
				co = [x/nb_v for x in co]
				vec = [x/nb_v for x in vec]
				center = NMesh.Vert(co[0],co[1],co[2])
				center.sel = 1
				me.verts.append(center)
				add_to_NV(v,vec,center)
				#
				for k in range(nb_v):
					new_f = NMesh.Face([center, b[k], b[k+1]])
					me.faces.append(new_f)
		#

def clear_old():
	""" Erase old faces and vertices """
	for F in NF:
		if len(F) == 3:
			me.faces.remove(F[2])
	#
	for v in NV.keys():
		me.verts.remove(v)

######################################################################
# Interface
#
global dist
dist = Create(0.2)
left = Create(0.0)
right = Create(1.0)
num = Create(2)

# Events
EVENT_NOEVENT = 1
EVENT_BEVEL = 2
EVENT_UPDATE = 3
EVENT_RECURS = 4
EVENT_EXIT = 5

def draw():
	global dist, left, right, num
	global EVENT_NOEVENT, EVENT_BEVEL, EVENT_UPDATE, EVENT_RECURS, EVENT_EXIT

	glClear(GL_COLOR_BUFFER_BIT)
	Button("Bevel",EVENT_BEVEL,10,100,280,25)
	left=Number('',  EVENT_NOEVENT,10,70,45, 20,left.val,0,right.val,'Set the minimum of the slider')
	right = Number("",EVENT_NOEVENT,245,70,45,20,right.val,left.val,200,"Set the maximum of the slider")
	dist=Slider("Thickness	",EVENT_UPDATE,60,70,180,20,dist.val,left.val,right.val,0,"Thickness of the bevel, can be changed even after bevelling")
	glRasterPos2d(8,40)
	Text('To finish, you can use recursive bevel to smooth it')
	num=Number('',	EVENT_NOEVENT,10,10,40, 16,num.val,1,100,'Recursion level')
	Button("Recursive",EVENT_RECURS,55,10,100,16)
	Button("Exit",EVENT_EXIT,210,10,80,20)

def event(evt, val):
	if ((evt == QKEY or evt == ESCKEY) and not val):
		Exit()

def bevent(evt):
	if evt == EVENT_EXIT :
		Exit()
	#
	elif evt == EVENT_BEVEL:
		bevel()
	#
	elif evt == EVENT_UPDATE:
		try:
			bevel_update()
		except NameError:
			pass
	#
	elif evt == EVENT_RECURS:
		recursive()

Register(draw, event, bevent)

######################################################################
def bevel():
	""" The main function, which creates the bevel """
	global me,NF,NV,NE,NC, old_dist
	#
	is_editmode = Window.EditMode()
	if is_editmode: Window.EditMode(0)
	objects = Blender.Object.GetSelected() 
	me = NMesh.GetRaw(objects[0].data.name)
	#
	NF = []
	NV = {}
	NE = {}
	NC = {}
	#
	make_NF()
	make_faces()
	make_edges()
	make_corners()
	clear_old()
	#
	old_dist = dist.val
	#
	me.update(1)
	if is_editmode: Window.EditMode(1)
	Blender.Redraw()

def bevel_update():
	""" Use NV to update the bevel """
	global dist, old_dist
	is_editmode = Window.EditMode()
	if is_editmode: Window.EditMode(0)
	fac = dist.val - old_dist
	old_dist = dist.val
	#
	for old_v in NV.keys():
		for dir in NV[old_v].keys():
			for i in range(3):
				NV[old_v][dir].co[i] += fac*dir.co[i]
	#
	me.update(1)
	if is_editmode: Window.EditMode(1)
	Blender.Redraw()

def recursive():
	""" Make a recursive bevel... still experimental """
	global dist
	#
	if num.val > 1:
		a = pi/4
		ang = []
		for k in range(num.val):
			ang.append(a)
			a = (pi+2*a)/4
		#
		l = [2*(1-sin(x))/sin(2*x) for x in ang]
		R = dist.val/sum(l)
		l = [x*R for x in l]
		#
		dist.val = l[0]
		bevel_update()
		#
		for x in l[1:]:
			dist.val = x
			bevel()
	
# vim:set ts=4 sw=4:

