use f.area where possible over python function and use len(mface) over len(mface.v)
[blender.git] / release / scripts / bevel_center.py
1 #!BPY
2
3 """ Registration info for Blender menus
4 Name: 'Bevel Center'
5 Blender: 240
6 Group: 'Mesh'
7 Tip: 'Bevel selected faces, edges, and vertices'
8 """
9
10 __author__ = "Loic BERTHE"
11 __url__ = ("blender", "elysiun")
12 __version__ = "2.0"
13
14 __bpydoc__ = """\
15 This script implements vertex and edges bevelling in Blender.
16
17 Usage:
18
19 Select the mesh you want to work on, enter Edit Mode and select the edges
20 to bevel.  Then run this script from the 3d View's Mesh->Scripts menu.
21
22 You can control the thickness of the bevel with the slider -- redefine the
23 end points for bigger or smaller ranges.  The thickness can be changed even
24 after applying the bevel, as many times as needed.
25
26 For an extra smoothing after or instead of direct bevel, set the level of
27 recursiveness and use the "Recursive" button. 
28
29 This "Recursive" Button, won't work in face select mode, unless you choose
30 "faces" in the select mode menu.
31
32 Notes:<br>
33         You can undo and redo your steps just like with normal mesh operations in
34 Blender.
35 """
36
37 ######################################################################
38 # Bevel Center v2.0 for Blender
39
40 # This script lets you bevel the selected vertices or edges and control the
41 # thickness of the bevel
42
43 # (c) 2004-2006 Loïc Berthe (loic+blender@lilotux.net)
44 # released under Blender Artistic License
45
46 ######################################################################
47
48 import Blender
49 from Blender import NMesh, Window, Scene
50 from Blender.Draw import *
51 from Blender.Mathutils import *
52 from Blender.BGL import *
53
54 #PY23 NO SETS#
55 '''
56 try:
57         set()
58 except:
59         from sets import set    
60 '''
61
62 ######################################################################
63 # Functions to handle the global structures of the script NF, NE and NC
64 # which contain informations about faces and corners to be created
65
66 global E_selected
67 E_selected = NMesh.EdgeFlags['SELECT']
68
69 def make_sel_vert(*co):
70         v= NMesh.Vert(*co)
71         v.sel = 1
72         me.verts.append(v)
73         return v
74
75 def make_sel_face(verts):
76         f = NMesh.Face(verts)
77         f.sel = 1
78         me.addFace(f)
79
80 def add_to_NV(old,dir,new):
81         try:
82                 NV[old][dir] = new
83         except:
84                 NV[old] = {dir:new}        
85
86 def get_v(old, *neighbors):
87         # compute the direction of the new vert
88         if len(neighbors) == 1: dir = (neighbors[0].co - old.co).normalize()
89                 #dir
90         else: dir = (neighbors[0].co - old.co).normalize() + (neighbors[1].co-old.co).normalize()
91          
92         # look in NV if this vert already exists
93         key = tuple(dir)
94         if old in NV and key in NV[old] : return NV[old][key]
95         
96         # else, create it 
97         new = old.co + dist.val*dir
98         v = make_sel_vert(new.x,new.y,new.z)
99         add_to_NV(old,key,v)
100         return v
101
102 def make_faces():
103         """ Analyse the mesh, make the faces corresponding to selected faces and
104         fill the structures NE and NC """
105
106         # make the differents flags consistent
107         for e in me.edges:
108                 if e.flag & E_selected :
109                         e.v1.sel = 1
110                         e.v2.sel = 1
111         
112         NF =[]                    # NF : New faces
113         for f in me.faces:
114                 V = f.v
115                 nV = len(V)
116                 enumV = range(nV)
117                 E = [me.findEdge(V[i],V[(i+1) % nV]) for i in enumV]
118                 Esel = [x.flag & E_selected for x in E]
119                 
120                 # look for selected vertices and creates a list containing the new vertices
121                 newV = V[:] 
122                 changes = False
123                 for (i,v) in enumerate(V):
124                         if v.sel :
125                                 changes = True
126                                 if   Esel[i-1] == 0 and Esel[i] == 1 :  newV[i] = get_v(v,V[i-1])
127                                 elif Esel[i-1] == 1 and Esel[i] == 0 :  newV[i] = get_v(v,V[(i+1) % nV])
128                                 elif Esel[i-1] == 1 and Esel[i] == 1 :  newV[i] = get_v(v,V[i-1],V[(i+1) % nV])
129                                 else :                                                            newV[i] = [get_v(v,V[i-1]),get_v(v,V[(i+1) % nV])]
130                 
131                 if changes:
132                         # determine and store the face to be created
133
134                         lenV = [len(x) for x in newV]
135                         if 2 not in lenV :                        
136                                 new_f = NMesh.Face(newV)
137                                 if sum(Esel) == nV : new_f.sel = 1
138                                 NF.append(new_f)
139                                 
140                         else :
141                                 nb2 = lenV.count(2)
142                                 
143                                 if nV == 4 :                            # f is a quad
144                                         if nb2 == 1 :
145                                                 ind2 = lenV.index(2)
146                                                 NF.append(NMesh.Face([newV[ind2-1],newV[ind2][0],newV[ind2][1],newV[ind2-3]]))
147                                                 NF.append(NMesh.Face([newV[ind2-1],newV[ind2-2],newV[ind2-3]]))
148                                         
149                                         elif nb2 == 2 :
150                                                 # We must know if the tuples are neighbours
151                                                 ind2 = ''.join([str(x) for x in lenV+lenV[:1]]).find('22')
152                                                 
153                                                 if ind2 != -1 :  # They are 
154                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2][1],newV[ind2-3][0],newV[ind2-3][1]]))
155                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2-1],newV[ind2-2],newV[ind2-3][1]]))
156                                                 
157                                                 else:                      # They aren't
158                                                         ind2 = lenV.index(2)
159                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2][1],newV[ind2-2][0],newV[ind2-2][1]]))
160                                                         NF.append(NMesh.Face([newV[ind2][1],newV[ind2-3],newV[ind2-2][0]]))
161                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2-1],newV[ind2-2][1]]))
162                                         
163                                         elif nb2 == 3 :
164                                                 ind2 = lenV.index(3)
165                                                 NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2],newV[ind2-3][0]]))
166                                                 NF.append(NMesh.Face([newV[ind2-1][0],newV[ind2-1][1],newV[ind2-3][0],newV[ind2-3][1]]))
167                                                 NF.append(NMesh.Face([newV[ind2-3][1],newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0]]))
168                                         
169                                         else:
170                                                 if      (newV[0][1].co-newV[3][0].co).length + (newV[1][0].co-newV[2][1].co).length \
171                                                         < (newV[0][0].co-newV[1][1].co).length + (newV[2][0].co-newV[3][1].co).length :
172                                                         ind2 = 0
173                                                 else :
174                                                         ind2 = 1
175                                                 NF.append(NMesh.Face([newV[ind2-1][0],newV[ind2-1][1],newV[ind2][0],newV[ind2][1]]))
176                                                 NF.append(NMesh.Face([newV[ind2][1],newV[ind2-3][0],newV[ind2-2][1],newV[ind2-1][0]]))
177                                                 NF.append(NMesh.Face([newV[ind2-3][0],newV[ind2-3][1],newV[ind2-2][0],newV[ind2-2][1]]))
178                                 
179                                 else :                                    # f is a tri
180                                         if nb2 == 1:
181                                                 ind2 = lenV.index(2)
182                                                 NF.append(NMesh.Face([newV[ind2-2],newV[ind2-1],newV[ind2][0],newV[ind2][1]]))
183                                         
184                                         elif nb2 == 2:
185                                                 ind2 = lenV.index(3)
186                                                 NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2],newV[ind2-2][0]]))
187                                                 NF.append(NMesh.Face([newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0],newV[ind2-1][1]]))
188                                         
189                                         else:
190                                                 ind2 = min( [((newV[i][1].co-newV[i-1][0].co).length, i) for i in enumV] )
191                                                 NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2][0],newV[ind2][1],newV[ind2-2][0]]))
192                                                 NF.append(NMesh.Face([newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0],newV[ind2-1][1]]))
193                                 
194                                 # Preparing the corners
195                                 for i in enumV:
196                                         if lenV[i] == 2 :          NC.setdefault(V[i],[]).append(newV[i])
197                                 
198                         
199                         old_faces.append(f)
200                         
201                         # Preparing the Edges
202                         for i in enumV:
203                                 if Esel[i]:
204                                         verts = [newV[i],newV[(i+1) % nV]]
205                                         if V[i].index > V[(i+1) % nV].index : verts.reverse()
206                                         NE.setdefault(E[i],[]).append(verts)
207         
208         # Create the faces
209         for f in NF: me.addFace(f)
210
211 def make_edges():
212         """ Make the faces corresponding to selected edges """
213
214         for old,new in NE.iteritems() :
215                 if len(new) == 1 :                                        # This edge was on a border 
216                         oldv = [old.v1, old.v2]
217                         if old.v1.index < old.v2.index : oldv.reverse()
218                         
219                         make_sel_face(oldv+new[0])
220
221                         me.findEdge(*oldv).flag   |= E_selected
222                         me.findEdge(*new[0]).flag |= E_selected
223
224                         #PY23 NO SETS# for v in oldv : NV_ext.add(v)
225                         for v in oldv : NV_ext[v]= None
226                 
227                 else:
228                         make_sel_face(new[0] + new[1][::-1])
229
230                         me.findEdge(*new[0]).flag |= E_selected
231                         me.findEdge(*new[1]).flag |= E_selected
232
233 def make_corners():
234         """ Make the faces corresponding to corners """
235
236         for v in NV.iterkeys():
237                 V = NV[v].values()
238                 nV = len(V)
239                 
240                 if nV == 1:      pass
241                 
242                 elif nV == 2 :
243                         #PY23 NO SETS# if v in NV_ext:
244                         if v in NV_ext.iterkeys():
245                                 make_sel_face(V+[v])
246                                 me.findEdge(*V).flag   |= E_selected
247                                 
248                 else:
249                         #PY23 NO SETS# if nV == 3 and v not in NV_ext : make_sel_face(V)
250                         if nV == 3 and v not in NV_ext.iterkeys() : make_sel_face(V)
251                         
252                         
253                         else :
254                                 
255                                 # We need to know which are the edges around the corner.
256                                 # First, we look for the quads surrounding the corner.
257                                 eed = []
258                                 for old, new in NE.iteritems():
259                                         if v in (old.v1,old.v2) :
260                                                 if v.index == min(old.v1.index,old.v2.index) :   ind = 0
261                                                 else                                                                             :   ind = 1
262                                                 
263                                                 if len(new) == 1:          eed.append([v,new[0][ind]])
264                                                 else :                            eed.append([new[0][ind],new[1][ind]])
265                                 
266                                 # We will add the edges coming from faces where only one vertice is selected.
267                                 # They are stored in NC.
268                                 if v in NC:                                      eed = eed+NC[v]
269
270                                 # Now we have to sort these vertices
271                                 hc = {}
272                                 for (a,b) in eed :
273                                         hc.setdefault(a,[]).append(b)
274                                         hc.setdefault(b,[]).append(a)
275                                 
276                                 for x0,edges in hc.iteritems():
277                                         if len(edges) == 1 :            break
278                                 
279                                 b = [x0]                                                # b will contain the sorted list of vertices
280                                 
281                                 for i in range(len(hc)-1):
282                                         for x in hc[x0] :
283                                                 if x not in b :          break
284                                         b.append(x)
285                                         x0 = x
286
287                                 b.append(b[0])
288
289                                 # Now we can create the faces
290                                 if len(b) == 5:                          make_sel_face(b[:4])
291
292                                 else:
293                                         New_V = Vector(0.0, 0.0,0.0)
294                                         New_d = [0.0, 0.0,0.0]
295                 
296                                         for x in hc.iterkeys():          New_V += x.co
297                                         for dir in NV[v] :
298                                                 for i in xrange(3):      New_d[i] += dir[i]
299
300                                         New_V *= 1./len(hc)
301                                         for i in range(3) :              New_d[i] /= nV
302                                         
303                                         center = make_sel_vert(New_V.x,New_V.y,New_V.z)
304                                         add_to_NV(v,tuple(New_d),center)
305
306                                         for k in range(len(b)-1):   make_sel_face([center, b[k], b[k+1]])
307                                 
308                 if  2 < nV and v in NC :
309                         for edge in NC[v] :                              me.findEdge(*edge).flag   |= E_selected
310
311 def clear_old():
312         """ Erase old faces and vertices """
313
314         for f in old_faces: me.removeFace(f)
315         
316         for v in NV.iterkeys():
317                 #PY23 NO SETS# if v not in NV_ext :  me.verts.remove(v)
318                 if v not in NV_ext.iterkeys() :  me.verts.remove(v)
319
320         for e in me.edges:
321                 if e.flag & E_selected :
322                         e.v1.sel = 1
323                         e.v2.sel = 1
324         
325
326 ######################################################################
327 # Interface
328
329 global dist
330         
331 dist = Create(0.2)
332 left = Create(0.0)
333 right = Create(1.0)
334 num = Create(2)
335
336 # Events
337 EVENT_NOEVENT = 1
338 EVENT_BEVEL = 2
339 EVENT_UPDATE = 3
340 EVENT_RECURS = 4
341 EVENT_EXIT = 5
342
343 def draw():
344         global dist, left, right, num
345         global EVENT_NOEVENT, EVENT_BEVEL, EVENT_UPDATE, EVENT_RECURS, EVENT_EXIT
346
347         glClear(GL_COLOR_BUFFER_BIT)
348         Button("Bevel",EVENT_BEVEL,10,100,280,25)
349         left=Number('',  EVENT_NOEVENT,10,70,45, 20,left.val,0,right.val,'Set the minimum of the slider')
350         right = Number("",EVENT_NOEVENT,245,70,45,20,right.val,left.val,200,"Set the maximum of the slider")
351         dist=Slider("Thickness  ",EVENT_UPDATE,60,70,180,20,dist.val,left.val,right.val,0, \
352                         "Thickness of the bevel, can be changed even after bevelling")
353         glRasterPos2d(8,40)
354         Text('To finish, you can use recursive bevel to smooth it')
355         num=Number('',  EVENT_NOEVENT,10,10,40, 16,num.val,1,100,'Recursion level')
356         Button("Recursive",EVENT_RECURS,55,10,100,16)
357         Button("Exit",EVENT_EXIT,210,10,80,20)
358
359 def event(evt, val):
360         if ((evt == QKEY or evt == ESCKEY) and not val): Exit()
361
362 def bevent(evt):
363         if evt == EVENT_EXIT            : Exit()
364         elif evt == EVENT_BEVEL  : bevel()
365         elif evt == EVENT_UPDATE        :
366                 try: bevel_update()
367                 except NameError                : pass
368         elif evt == EVENT_RECURS        : recursive()
369
370 Register(draw, event, bevent)
371
372 ######################################################################
373 def bevel():
374         """ The main function, which creates the bevel """
375         global me,NV,NV_ext,NE,NC, old_faces,old_dist
376         t= Blender.sys.time()
377         scn = Scene.GetCurrent()
378         ob = scn.getActiveObject() 
379         if ob == None or ob.getType() != 'Mesh': 
380                 Draw.PupMenu('ERROR%t|Select a mesh object.')
381                 return
382         
383         Window.WaitCursor(1) # Change the Cursor
384         
385         is_editmode = Window.EditMode() 
386         if is_editmode: Window.EditMode(0)
387         
388         me = ob.getData()
389
390         NV = {}
391         #PY23 NO SETS# NV_ext = set()
392         NV_ext= {}
393         NE = {}
394         NC = {}
395         old_faces = []
396
397         make_faces()
398         make_edges()
399         make_corners()
400         clear_old()
401
402         old_dist = dist.val
403         print '\tbevel in %.6f sec' % (Blender.sys.time()-t)
404         me.update(1)
405         if is_editmode: Window.EditMode(1)
406         Window.WaitCursor(0)
407         Blender.Redraw()
408
409 def bevel_update():
410         """ Use NV to update the bevel """
411         global dist, old_dist
412         is_editmode = Window.EditMode()
413         if is_editmode: Window.EditMode(0)
414         fac = dist.val - old_dist
415         old_dist = dist.val
416
417         for old_v in NV.iterkeys():
418                 for dir in NV[old_v].iterkeys():
419                         for i in range(3):
420                                 NV[old_v][dir].co[i] += fac*dir[i]
421
422         me.update(1)
423         if is_editmode: Window.EditMode(1)
424         Blender.Redraw()
425
426 def recursive():
427         """ Make a recursive bevel... still experimental """
428         global dist
429         from math import pi, sin
430
431         if num.val > 1:
432                 a = pi/4
433                 ang = []
434                 for k in range(num.val):
435                         ang.append(a)
436                         a = (pi+2*a)/4
437
438                 l = [2*(1-sin(x))/sin(2*x) for x in ang]
439                 R = dist.val/sum(l)
440                 l = [x*R for x in l]
441
442                 dist.val = l[0]
443                 bevel_update()
444
445                 for x in l[1:]:
446                         dist.val = x
447                         bevel()
448