475 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			475 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!BPY
 | |
| # -*- coding: utf-8 -*-
 | |
| """ Registration info for Blender menus
 | |
| Name: 'Bevel Center'
 | |
| Blender: 243
 | |
| Group: 'Mesh'
 | |
| Tip: 'Bevel selected faces, edges, and vertices'
 | |
| """
 | |
| 
 | |
| __author__ = "Loic BERTHE"
 | |
| __url__ = ("blender", "blenderartists.org")
 | |
| __version__ = "2.0"
 | |
| 
 | |
| __bpydoc__ = """\
 | |
| This script implements vertex and edges bevelling in Blender.
 | |
| 
 | |
| Usage:
 | |
| 
 | |
| Select the mesh you want to work on, enter Edit Mode and select the edges
 | |
| to bevel.  Then run this script from the 3d View's Mesh->Scripts menu.
 | |
| 
 | |
| You can control the thickness of the bevel with the slider -- redefine the
 | |
| end points for bigger or smaller ranges.  The thickness can be changed even
 | |
| after applying the bevel, as many times as needed.
 | |
| 
 | |
| For an extra smoothing after or instead of direct bevel, set the level of
 | |
| recursiveness and use the "Recursive" button. 
 | |
| 
 | |
| This "Recursive" Button, won't work in face select mode, unless you choose
 | |
| "faces" in the select mode menu.
 | |
| 
 | |
| Notes:<br>
 | |
| 	You can undo and redo your steps just like with normal mesh operations in
 | |
| Blender.
 | |
| """
 | |
| 
 | |
| ######################################################################
 | |
| # Bevel Center v2.0 for Blender
 | |
| 
 | |
| # This script lets you bevel the selected vertices or edges and control the
 | |
| # thickness of the bevel
 | |
| 
 | |
| # (c) 2004-2006 Loïc Berthe (loic+blender@lilotux.net)
 | |
| # released under Blender Artistic License
 | |
| 
 | |
| ######################################################################
 | |
| 
 | |
| import Blender
 | |
| from Blender import NMesh, Window, Scene
 | |
| from Blender.Draw import *
 | |
| from Blender.Mathutils import *
 | |
| from Blender.BGL import *
 | |
| import BPyMessages
 | |
| #PY23 NO SETS#
 | |
| '''
 | |
| try:
 | |
| 	set()
 | |
| except:
 | |
| 	from sets import set	
 | |
| '''
 | |
| 
 | |
| ######################################################################
 | |
| # Functions to handle the global structures of the script NF, NE and NC
 | |
| # which contain informations about faces and corners to be created
 | |
| 
 | |
| global E_selected
 | |
| E_selected = NMesh.EdgeFlags['SELECT']
 | |
| 
 | |
| old_dist = None
 | |
| 
 | |
| def act_mesh_ob():
 | |
| 	scn = Scene.GetCurrent()
 | |
| 	ob = scn.objects.active
 | |
| 	if ob == None or ob.type != 'Mesh': 
 | |
| 		BPyMessages.Error_NoMeshActive()
 | |
| 		return
 | |
| 	
 | |
| 	if ob.getData(mesh=1).multires:
 | |
| 		BPyMessages.Error_NoMeshMultiresEdit()
 | |
| 		return
 | |
| 	
 | |
| 	return ob
 | |
| 
 | |
| def make_sel_vert(*co):
 | |
| 	v= NMesh.Vert(*co)
 | |
| 	v.sel = 1
 | |
| 	me.verts.append(v)
 | |
| 	return v
 | |
| 
 | |
| def make_sel_face(verts):
 | |
| 	f = NMesh.Face(verts)
 | |
| 	f.sel = 1
 | |
| 	me.addFace(f)
 | |
| 
 | |
| def add_to_NV(old,dir,new):
 | |
| 	try:
 | |
| 		NV[old][dir] = new
 | |
| 	except:
 | |
| 		NV[old] = {dir:new}	   
 | |
| 
 | |
| def get_v(old, *neighbors):
 | |
| 	# compute the direction of the new vert
 | |
| 	if len(neighbors) == 1: dir = (neighbors[0].co - old.co).normalize()
 | |
| 		#dir
 | |
| 	else: dir = (neighbors[0].co - old.co).normalize() + (neighbors[1].co-old.co).normalize()
 | |
| 	 
 | |
| 	# look in NV if this vert already exists
 | |
| 	key = tuple(dir)
 | |
| 	if old in NV and key in NV[old] : return NV[old][key]
 | |
| 	
 | |
| 	# else, create it 
 | |
| 	new = old.co + dist.val*dir
 | |
| 	v = make_sel_vert(new.x,new.y,new.z)
 | |
| 	add_to_NV(old,key,v)
 | |
| 	return v
 | |
| 
 | |
| def make_faces():
 | |
| 	""" Analyse the mesh, make the faces corresponding to selected faces and
 | |
| 	fill the structures NE and NC """
 | |
| 
 | |
| 	# make the differents flags consistent
 | |
| 	for e in me.edges:
 | |
| 		if e.flag & E_selected :
 | |
| 			e.v1.sel = 1
 | |
| 			e.v2.sel = 1
 | |
| 	
 | |
| 	NF =[]			  # NF : New faces
 | |
| 	for f in me.faces:
 | |
| 		V = f.v
 | |
| 		nV = len(V)
 | |
| 		enumV = range(nV)
 | |
| 		E = [me.findEdge(V[i],V[(i+1) % nV]) for i in enumV]
 | |
| 		Esel = [x.flag & E_selected for x in E]
 | |
| 		
 | |
| 		# look for selected vertices and creates a list containing the new vertices
 | |
| 		newV = V[:] 
 | |
| 		changes = False
 | |
| 		for (i,v) in enumerate(V):
 | |
| 			if v.sel :
 | |
| 				changes = True
 | |
| 				if   Esel[i-1] == 0 and Esel[i] == 1 :  newV[i] = get_v(v,V[i-1])
 | |
| 				elif Esel[i-1] == 1 and Esel[i] == 0 :  newV[i] = get_v(v,V[(i+1) % nV])
 | |
| 				elif Esel[i-1] == 1 and Esel[i] == 1 :  newV[i] = get_v(v,V[i-1],V[(i+1) % nV])
 | |
| 				else :								  newV[i] = [get_v(v,V[i-1]),get_v(v,V[(i+1) % nV])]
 | |
| 		
 | |
| 		if changes:
 | |
| 			# determine and store the face to be created
 | |
| 
 | |
| 			lenV = [len(x) for x in newV]
 | |
| 			if 2 not in lenV :			  
 | |
| 				new_f = NMesh.Face(newV)
 | |
| 				if sum(Esel) == nV : new_f.sel = 1
 | |
| 				NF.append(new_f)
 | |
| 				
 | |
| 			else :
 | |
| 				nb2 = lenV.count(2)
 | |
| 				
 | |
| 				if nV == 4 :				# f is a quad
 | |
| 					if nb2 == 1 :
 | |
| 						ind2 = lenV.index(2)
 | |
| 						NF.append(NMesh.Face([newV[ind2-1],newV[ind2][0],newV[ind2][1],newV[ind2-3]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2-1],newV[ind2-2],newV[ind2-3]]))
 | |
| 					
 | |
| 					elif nb2 == 2 :
 | |
| 						# We must know if the tuples are neighbours
 | |
| 						ind2 = ''.join([str(x) for x in lenV+lenV[:1]]).find('22')
 | |
| 						
 | |
| 						if ind2 != -1 :	 # They are 
 | |
| 							NF.append(NMesh.Face([newV[ind2][0],newV[ind2][1],newV[ind2-3][0],newV[ind2-3][1]]))
 | |
| 							NF.append(NMesh.Face([newV[ind2][0],newV[ind2-1],newV[ind2-2],newV[ind2-3][1]]))
 | |
| 						
 | |
| 						else:			   # They aren't
 | |
| 							ind2 = lenV.index(2)
 | |
| 							NF.append(NMesh.Face([newV[ind2][0],newV[ind2][1],newV[ind2-2][0],newV[ind2-2][1]]))
 | |
| 							NF.append(NMesh.Face([newV[ind2][1],newV[ind2-3],newV[ind2-2][0]]))
 | |
| 							NF.append(NMesh.Face([newV[ind2][0],newV[ind2-1],newV[ind2-2][1]]))
 | |
| 					
 | |
| 					elif nb2 == 3 :
 | |
| 						ind2 = lenV.index(3)
 | |
| 						NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2],newV[ind2-3][0]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2-1][0],newV[ind2-1][1],newV[ind2-3][0],newV[ind2-3][1]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2-3][1],newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0]]))
 | |
| 					
 | |
| 					else:
 | |
| 						if	(newV[0][1].co-newV[3][0].co).length + (newV[1][0].co-newV[2][1].co).length \
 | |
| 							< (newV[0][0].co-newV[1][1].co).length + (newV[2][0].co-newV[3][1].co).length :
 | |
| 							ind2 = 0
 | |
| 						else :
 | |
| 							ind2 = 1
 | |
| 						NF.append(NMesh.Face([newV[ind2-1][0],newV[ind2-1][1],newV[ind2][0],newV[ind2][1]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2][1],newV[ind2-3][0],newV[ind2-2][1],newV[ind2-1][0]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2-3][0],newV[ind2-3][1],newV[ind2-2][0],newV[ind2-2][1]]))
 | |
| 				
 | |
| 				else :					  # f is a tri
 | |
| 					if nb2 == 1:
 | |
| 						ind2 = lenV.index(2)
 | |
| 						NF.append(NMesh.Face([newV[ind2-2],newV[ind2-1],newV[ind2][0],newV[ind2][1]]))
 | |
| 					
 | |
| 					elif nb2 == 2:
 | |
| 						ind2 = lenV.index(3)
 | |
| 						NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2],newV[ind2-2][0]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0],newV[ind2-1][1]]))
 | |
| 					
 | |
| 					else:
 | |
| 						ind2 = min( [((newV[i][1].co-newV[i-1][0].co).length, i) for i in enumV] )[1]
 | |
| 						NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2][0],newV[ind2][1],newV[ind2-2][0]]))
 | |
| 						NF.append(NMesh.Face([newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0],newV[ind2-1][1]]))
 | |
| 				
 | |
| 				# Preparing the corners
 | |
| 				for i in enumV:
 | |
| 					if lenV[i] == 2 :	   NC.setdefault(V[i],[]).append(newV[i])
 | |
| 				
 | |
| 			
 | |
| 			old_faces.append(f)
 | |
| 			
 | |
| 			# Preparing the Edges
 | |
| 			for i in enumV:
 | |
| 				if Esel[i]:
 | |
| 					verts = [newV[i],newV[(i+1) % nV]]
 | |
| 					if V[i].index > V[(i+1) % nV].index : verts.reverse()
 | |
| 					NE.setdefault(E[i],[]).append(verts)
 | |
| 	
 | |
| 	# Create the faces
 | |
| 	for f in NF: me.addFace(f)
 | |
| 
 | |
| def make_edges():
 | |
| 	""" Make the faces corresponding to selected edges """
 | |
| 
 | |
| 	for old,new in NE.iteritems() :
 | |
| 		if len(new) == 1 :					  # This edge was on a border 
 | |
| 			oldv = [old.v1, old.v2]
 | |
| 			if old.v1.index < old.v2.index : oldv.reverse()
 | |
| 			
 | |
| 			make_sel_face(oldv+new[0])
 | |
| 
 | |
| 			me.findEdge(*oldv).flag   |= E_selected
 | |
| 			me.findEdge(*new[0]).flag |= E_selected
 | |
| 
 | |
| 			#PY23 NO SETS# for v in oldv : NV_ext.add(v)
 | |
| 			for v in oldv : NV_ext[v]= None
 | |
| 		
 | |
| 		else:
 | |
| 			make_sel_face(new[0] + new[1][::-1])
 | |
| 
 | |
| 			me.findEdge(*new[0]).flag |= E_selected
 | |
| 			me.findEdge(*new[1]).flag |= E_selected
 | |
| 
 | |
| def make_corners():
 | |
| 	""" Make the faces corresponding to corners """
 | |
| 
 | |
| 	for v in NV.iterkeys():
 | |
| 		V = NV[v].values()
 | |
| 		nV = len(V)
 | |
| 		
 | |
| 		if nV == 1:	 pass
 | |
| 		
 | |
| 		elif nV == 2 :
 | |
| 			#PY23 NO SETS# if v in NV_ext:
 | |
| 			if v in NV_ext.iterkeys():
 | |
| 				make_sel_face(V+[v])
 | |
| 				me.findEdge(*V).flag   |= E_selected
 | |
| 				
 | |
| 		else:
 | |
| 			#PY23 NO SETS# if nV == 3 and v not in NV_ext : make_sel_face(V)
 | |
| 			if nV == 3 and v not in NV_ext.iterkeys() : make_sel_face(V)
 | |
| 			
 | |
| 			
 | |
| 			else :
 | |
| 				
 | |
| 				# We need to know which are the edges around the corner.
 | |
| 				# First, we look for the quads surrounding the corner.
 | |
| 				eed = []
 | |
| 				for old, new in NE.iteritems():
 | |
| 					if v in (old.v1,old.v2) :
 | |
| 						if v.index == min(old.v1.index,old.v2.index) :   ind = 0
 | |
| 						else										 :   ind = 1
 | |
| 						
 | |
| 						if len(new) == 1:	   eed.append([v,new[0][ind]])
 | |
| 						else :				  eed.append([new[0][ind],new[1][ind]])
 | |
| 				
 | |
| 				# We will add the edges coming from faces where only one vertice is selected.
 | |
| 				# They are stored in NC.
 | |
| 				if v in NC:					 eed = eed+NC[v]
 | |
| 
 | |
| 				# Now we have to sort these vertices
 | |
| 				hc = {}
 | |
| 				for (a,b) in eed :
 | |
| 					hc.setdefault(a,[]).append(b)
 | |
| 					hc.setdefault(b,[]).append(a)
 | |
| 				
 | |
| 				for x0,edges in hc.iteritems():
 | |
| 					if len(edges) == 1 :		break
 | |
| 				
 | |
| 				b = [x0]						# b will contain the sorted list of vertices
 | |
| 				
 | |
| 				for i in xrange(len(hc)-1):
 | |
| 					for x in hc[x0] :
 | |
| 						if x not in b :		 break
 | |
| 					b.append(x)
 | |
| 					x0 = x
 | |
| 
 | |
| 				b.append(b[0])
 | |
| 
 | |
| 				# Now we can create the faces
 | |
| 				if len(b) == 5:				 make_sel_face(b[:4])
 | |
| 
 | |
| 				else:
 | |
| 					New_V = Vector(0.0, 0.0,0.0)
 | |
| 					New_d = [0.0, 0.0,0.0]
 | |
| 		
 | |
| 					for x in hc.iterkeys():		 New_V += x.co
 | |
| 					for dir in NV[v] :
 | |
| 						for i in xrange(3):	 New_d[i] += dir[i]
 | |
| 
 | |
| 					New_V *= 1./len(hc)
 | |
| 					for i in xrange(3) :		 New_d[i] /= nV
 | |
| 					
 | |
| 					center = make_sel_vert(New_V.x,New_V.y,New_V.z)
 | |
| 					add_to_NV(v,tuple(New_d),center)
 | |
| 
 | |
| 					for k in xrange(len(b)-1):   make_sel_face([center, b[k], b[k+1]])
 | |
| 				
 | |
| 		if  2 < nV and v in NC :
 | |
| 			for edge in NC[v] :				 me.findEdge(*edge).flag   |= E_selected
 | |
| 
 | |
| def clear_old():
 | |
| 	""" Erase old faces and vertices """
 | |
| 
 | |
| 	for f in old_faces: me.removeFace(f)
 | |
| 	
 | |
| 	for v in NV.iterkeys():
 | |
| 		#PY23 NO SETS# if v not in NV_ext :  me.verts.remove(v)
 | |
| 		if v not in NV_ext.iterkeys() :  me.verts.remove(v)
 | |
| 
 | |
| 	for e in me.edges:
 | |
| 		if e.flag & E_selected :
 | |
| 			e.v1.sel = 1
 | |
| 			e.v2.sel = 1
 | |
| 	
 | |
| 
 | |
| ######################################################################
 | |
| # 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, old_dist
 | |
| 	global EVENT_NOEVENT, EVENT_BEVEL, EVENT_UPDATE, EVENT_RECURS, EVENT_EXIT
 | |
| 
 | |
| 	glClear(GL_COLOR_BUFFER_BIT)
 | |
| 	Button("Bevel",EVENT_BEVEL,10,100,280,25)
 | |
| 	
 | |
| 	BeginAlign()
 | |
| 	left=Number('',  EVENT_NOEVENT,10,70,45, 20,left.val,0,right.val,'Set the minimum 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")
 | |
| 	right = Number("",EVENT_NOEVENT,245,70,45,20,right.val,left.val,200,"Set the maximum of the slider")
 | |
| 
 | |
| 	EndAlign()
 | |
| 	glRasterPos2d(8,40)
 | |
| 	Text('To finish, you can use recursive bevel to smooth it')
 | |
| 	
 | |
| 	
 | |
| 	if old_dist != None:
 | |
| 		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,NV,NV_ext,NE,NC, old_faces,old_dist
 | |
| 	
 | |
| 	ob = act_mesh_ob()
 | |
| 	if not ob: return
 | |
| 	
 | |
| 	Window.WaitCursor(1) # Change the Cursor
 | |
| 	t= Blender.sys.time()
 | |
| 	is_editmode = Window.EditMode() 
 | |
| 	if is_editmode: Window.EditMode(0)
 | |
| 	
 | |
| 	me = ob.data
 | |
| 
 | |
| 	NV = {}
 | |
| 	#PY23 NO SETS# NV_ext = set()
 | |
| 	NV_ext= {}
 | |
| 	NE = {}
 | |
| 	NC = {}
 | |
| 	old_faces = []
 | |
| 
 | |
| 	make_faces()
 | |
| 	make_edges()
 | |
| 	make_corners()
 | |
| 	clear_old()
 | |
| 
 | |
| 	old_dist = dist.val
 | |
| 	print '\tbevel in %.6f sec' % (Blender.sys.time()-t)
 | |
| 	me.update(1)
 | |
| 	if is_editmode: Window.EditMode(1)
 | |
| 	Window.WaitCursor(0)
 | |
| 	Blender.Redraw()
 | |
| 
 | |
| def bevel_update():
 | |
| 	""" Use NV to update the bevel """
 | |
| 	global dist, old_dist
 | |
| 	
 | |
| 	if old_dist == None:
 | |
| 		# PupMenu('Error%t|Must bevel first.')
 | |
| 		return
 | |
| 	
 | |
| 	is_editmode = Window.EditMode()
 | |
| 	if is_editmode: Window.EditMode(0)
 | |
| 	
 | |
| 	fac = dist.val - old_dist
 | |
| 	old_dist = dist.val
 | |
| 
 | |
| 	for old_v in NV.iterkeys():
 | |
| 		for dir in NV[old_v].iterkeys():
 | |
| 			for i in xrange(3):
 | |
| 				NV[old_v][dir].co[i] += fac*dir[i]
 | |
| 
 | |
| 	me.update(1)
 | |
| 	if is_editmode: Window.EditMode(1)
 | |
| 	Blender.Redraw()
 | |
| 
 | |
| def recursive():
 | |
| 	""" Make a recursive bevel... still experimental """
 | |
| 	global dist
 | |
| 	from math import pi, sin
 | |
| 	
 | |
| 	if num.val > 1:
 | |
| 		a = pi/4
 | |
| 		ang = []
 | |
| 		for k in xrange(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()
 | |
| 
 |