Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / bevel_center.py
1 #!BPY
2 # -*- coding: utf-8 -*-
3 """ Registration info for Blender menus
4 Name: 'Bevel Center'
5 Blender: 243
6 Group: 'Mesh'
7 Tip: 'Bevel selected faces, edges, and vertices'
8 """
9
10 __author__ = "Loic BERTHE"
11 __url__ = ("blender", "blenderartists.org")
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 import BPyMessages
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 old_dist = None
70
71 def act_mesh_ob():
72         scn = Scene.GetCurrent()
73         ob = scn.objects.active
74         if ob == None or ob.type != 'Mesh': 
75                 BPyMessages.Error_NoMeshActive()
76                 return
77         
78         if ob.getData(mesh=1).multires:
79                 BPyMessages.Error_NoMeshMultiresEdit()
80                 return
81         
82         return ob
83
84 def make_sel_vert(*co):
85         v= NMesh.Vert(*co)
86         v.sel = 1
87         me.verts.append(v)
88         return v
89
90 def make_sel_face(verts):
91         f = NMesh.Face(verts)
92         f.sel = 1
93         me.addFace(f)
94
95 def add_to_NV(old,dir,new):
96         try:
97                 NV[old][dir] = new
98         except:
99                 NV[old] = {dir:new}        
100
101 def get_v(old, *neighbors):
102         # compute the direction of the new vert
103         if len(neighbors) == 1: dir = (neighbors[0].co - old.co).normalize()
104                 #dir
105         else: dir = (neighbors[0].co - old.co).normalize() + (neighbors[1].co-old.co).normalize()
106          
107         # look in NV if this vert already exists
108         key = tuple(dir)
109         if old in NV and key in NV[old] : return NV[old][key]
110         
111         # else, create it 
112         new = old.co + dist.val*dir
113         v = make_sel_vert(new.x,new.y,new.z)
114         add_to_NV(old,key,v)
115         return v
116
117 def make_faces():
118         """ Analyse the mesh, make the faces corresponding to selected faces and
119         fill the structures NE and NC """
120
121         # make the differents flags consistent
122         for e in me.edges:
123                 if e.flag & E_selected :
124                         e.v1.sel = 1
125                         e.v2.sel = 1
126         
127         NF =[]                    # NF : New faces
128         for f in me.faces:
129                 V = f.v
130                 nV = len(V)
131                 enumV = range(nV)
132                 E = [me.findEdge(V[i],V[(i+1) % nV]) for i in enumV]
133                 Esel = [x.flag & E_selected for x in E]
134                 
135                 # look for selected vertices and creates a list containing the new vertices
136                 newV = V[:] 
137                 changes = False
138                 for (i,v) in enumerate(V):
139                         if v.sel :
140                                 changes = True
141                                 if   Esel[i-1] == 0 and Esel[i] == 1 :  newV[i] = get_v(v,V[i-1])
142                                 elif Esel[i-1] == 1 and Esel[i] == 0 :  newV[i] = get_v(v,V[(i+1) % nV])
143                                 elif Esel[i-1] == 1 and Esel[i] == 1 :  newV[i] = get_v(v,V[i-1],V[(i+1) % nV])
144                                 else :                                                            newV[i] = [get_v(v,V[i-1]),get_v(v,V[(i+1) % nV])]
145                 
146                 if changes:
147                         # determine and store the face to be created
148
149                         lenV = [len(x) for x in newV]
150                         if 2 not in lenV :                        
151                                 new_f = NMesh.Face(newV)
152                                 if sum(Esel) == nV : new_f.sel = 1
153                                 NF.append(new_f)
154                                 
155                         else :
156                                 nb2 = lenV.count(2)
157                                 
158                                 if nV == 4 :                            # f is a quad
159                                         if nb2 == 1 :
160                                                 ind2 = lenV.index(2)
161                                                 NF.append(NMesh.Face([newV[ind2-1],newV[ind2][0],newV[ind2][1],newV[ind2-3]]))
162                                                 NF.append(NMesh.Face([newV[ind2-1],newV[ind2-2],newV[ind2-3]]))
163                                         
164                                         elif nb2 == 2 :
165                                                 # We must know if the tuples are neighbours
166                                                 ind2 = ''.join([str(x) for x in lenV+lenV[:1]]).find('22')
167                                                 
168                                                 if ind2 != -1 :  # They are 
169                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2][1],newV[ind2-3][0],newV[ind2-3][1]]))
170                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2-1],newV[ind2-2],newV[ind2-3][1]]))
171                                                 
172                                                 else:                      # They aren't
173                                                         ind2 = lenV.index(2)
174                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2][1],newV[ind2-2][0],newV[ind2-2][1]]))
175                                                         NF.append(NMesh.Face([newV[ind2][1],newV[ind2-3],newV[ind2-2][0]]))
176                                                         NF.append(NMesh.Face([newV[ind2][0],newV[ind2-1],newV[ind2-2][1]]))
177                                         
178                                         elif nb2 == 3 :
179                                                 ind2 = lenV.index(3)
180                                                 NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2],newV[ind2-3][0]]))
181                                                 NF.append(NMesh.Face([newV[ind2-1][0],newV[ind2-1][1],newV[ind2-3][0],newV[ind2-3][1]]))
182                                                 NF.append(NMesh.Face([newV[ind2-3][1],newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0]]))
183                                         
184                                         else:
185                                                 if      (newV[0][1].co-newV[3][0].co).length + (newV[1][0].co-newV[2][1].co).length \
186                                                         < (newV[0][0].co-newV[1][1].co).length + (newV[2][0].co-newV[3][1].co).length :
187                                                         ind2 = 0
188                                                 else :
189                                                         ind2 = 1
190                                                 NF.append(NMesh.Face([newV[ind2-1][0],newV[ind2-1][1],newV[ind2][0],newV[ind2][1]]))
191                                                 NF.append(NMesh.Face([newV[ind2][1],newV[ind2-3][0],newV[ind2-2][1],newV[ind2-1][0]]))
192                                                 NF.append(NMesh.Face([newV[ind2-3][0],newV[ind2-3][1],newV[ind2-2][0],newV[ind2-2][1]]))
193                                 
194                                 else :                                    # f is a tri
195                                         if nb2 == 1:
196                                                 ind2 = lenV.index(2)
197                                                 NF.append(NMesh.Face([newV[ind2-2],newV[ind2-1],newV[ind2][0],newV[ind2][1]]))
198                                         
199                                         elif nb2 == 2:
200                                                 ind2 = lenV.index(3)
201                                                 NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2],newV[ind2-2][0]]))
202                                                 NF.append(NMesh.Face([newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0],newV[ind2-1][1]]))
203                                         
204                                         else:
205                                                 ind2 = min( [((newV[i][1].co-newV[i-1][0].co).length, i) for i in enumV] )[1]
206                                                 NF.append(NMesh.Face([newV[ind2-1][1],newV[ind2][0],newV[ind2][1],newV[ind2-2][0]]))
207                                                 NF.append(NMesh.Face([newV[ind2-2][0],newV[ind2-2][1],newV[ind2-1][0],newV[ind2-1][1]]))
208                                 
209                                 # Preparing the corners
210                                 for i in enumV:
211                                         if lenV[i] == 2 :          NC.setdefault(V[i],[]).append(newV[i])
212                                 
213                         
214                         old_faces.append(f)
215                         
216                         # Preparing the Edges
217                         for i in enumV:
218                                 if Esel[i]:
219                                         verts = [newV[i],newV[(i+1) % nV]]
220                                         if V[i].index > V[(i+1) % nV].index : verts.reverse()
221                                         NE.setdefault(E[i],[]).append(verts)
222         
223         # Create the faces
224         for f in NF: me.addFace(f)
225
226 def make_edges():
227         """ Make the faces corresponding to selected edges """
228
229         for old,new in NE.iteritems() :
230                 if len(new) == 1 :                                        # This edge was on a border 
231                         oldv = [old.v1, old.v2]
232                         if old.v1.index < old.v2.index : oldv.reverse()
233                         
234                         make_sel_face(oldv+new[0])
235
236                         me.findEdge(*oldv).flag   |= E_selected
237                         me.findEdge(*new[0]).flag |= E_selected
238
239                         #PY23 NO SETS# for v in oldv : NV_ext.add(v)
240                         for v in oldv : NV_ext[v]= None
241                 
242                 else:
243                         make_sel_face(new[0] + new[1][::-1])
244
245                         me.findEdge(*new[0]).flag |= E_selected
246                         me.findEdge(*new[1]).flag |= E_selected
247
248 def make_corners():
249         """ Make the faces corresponding to corners """
250
251         for v in NV.iterkeys():
252                 V = NV[v].values()
253                 nV = len(V)
254                 
255                 if nV == 1:      pass
256                 
257                 elif nV == 2 :
258                         #PY23 NO SETS# if v in NV_ext:
259                         if v in NV_ext.iterkeys():
260                                 make_sel_face(V+[v])
261                                 me.findEdge(*V).flag   |= E_selected
262                                 
263                 else:
264                         #PY23 NO SETS# if nV == 3 and v not in NV_ext : make_sel_face(V)
265                         if nV == 3 and v not in NV_ext.iterkeys() : make_sel_face(V)
266                         
267                         
268                         else :
269                                 
270                                 # We need to know which are the edges around the corner.
271                                 # First, we look for the quads surrounding the corner.
272                                 eed = []
273                                 for old, new in NE.iteritems():
274                                         if v in (old.v1,old.v2) :
275                                                 if v.index == min(old.v1.index,old.v2.index) :   ind = 0
276                                                 else                                                                             :   ind = 1
277                                                 
278                                                 if len(new) == 1:          eed.append([v,new[0][ind]])
279                                                 else :                            eed.append([new[0][ind],new[1][ind]])
280                                 
281                                 # We will add the edges coming from faces where only one vertice is selected.
282                                 # They are stored in NC.
283                                 if v in NC:                                      eed = eed+NC[v]
284
285                                 # Now we have to sort these vertices
286                                 hc = {}
287                                 for (a,b) in eed :
288                                         hc.setdefault(a,[]).append(b)
289                                         hc.setdefault(b,[]).append(a)
290                                 
291                                 for x0,edges in hc.iteritems():
292                                         if len(edges) == 1 :            break
293                                 
294                                 b = [x0]                                                # b will contain the sorted list of vertices
295                                 
296                                 for i in xrange(len(hc)-1):
297                                         for x in hc[x0] :
298                                                 if x not in b :          break
299                                         b.append(x)
300                                         x0 = x
301
302                                 b.append(b[0])
303
304                                 # Now we can create the faces
305                                 if len(b) == 5:                          make_sel_face(b[:4])
306
307                                 else:
308                                         New_V = Vector(0.0, 0.0,0.0)
309                                         New_d = [0.0, 0.0,0.0]
310                 
311                                         for x in hc.iterkeys():          New_V += x.co
312                                         for dir in NV[v] :
313                                                 for i in xrange(3):      New_d[i] += dir[i]
314
315                                         New_V *= 1./len(hc)
316                                         for i in xrange(3) :             New_d[i] /= nV
317                                         
318                                         center = make_sel_vert(New_V.x,New_V.y,New_V.z)
319                                         add_to_NV(v,tuple(New_d),center)
320
321                                         for k in xrange(len(b)-1):   make_sel_face([center, b[k], b[k+1]])
322                                 
323                 if  2 < nV and v in NC :
324                         for edge in NC[v] :                              me.findEdge(*edge).flag   |= E_selected
325
326 def clear_old():
327         """ Erase old faces and vertices """
328
329         for f in old_faces: me.removeFace(f)
330         
331         for v in NV.iterkeys():
332                 #PY23 NO SETS# if v not in NV_ext :  me.verts.remove(v)
333                 if v not in NV_ext.iterkeys() :  me.verts.remove(v)
334
335         for e in me.edges:
336                 if e.flag & E_selected :
337                         e.v1.sel = 1
338                         e.v2.sel = 1
339         
340
341 ######################################################################
342 # Interface
343
344 global dist
345         
346 dist = Create(0.2)
347 left = Create(0.0)
348 right = Create(1.0)
349 num = Create(2)
350
351 # Events
352 EVENT_NOEVENT = 1
353 EVENT_BEVEL = 2
354 EVENT_UPDATE = 3
355 EVENT_RECURS = 4
356 EVENT_EXIT = 5
357
358 def draw():
359         global dist, left, right, num, old_dist
360         global EVENT_NOEVENT, EVENT_BEVEL, EVENT_UPDATE, EVENT_RECURS, EVENT_EXIT
361
362         glClear(GL_COLOR_BUFFER_BIT)
363         Button("Bevel",EVENT_BEVEL,10,100,280,25)
364         
365         BeginAlign()
366         left=Number('',  EVENT_NOEVENT,10,70,45, 20,left.val,0,right.val,'Set the minimum of the slider')
367         dist=Slider("Thickness  ",EVENT_UPDATE,60,70,180,20,dist.val,left.val,right.val,0, \
368                         "Thickness of the bevel, can be changed even after bevelling")
369         right = Number("",EVENT_NOEVENT,245,70,45,20,right.val,left.val,200,"Set the maximum of the slider")
370
371         EndAlign()
372         glRasterPos2d(8,40)
373         Text('To finish, you can use recursive bevel to smooth it')
374         
375         
376         if old_dist != None:
377                 num=Number('',  EVENT_NOEVENT,10,10,40, 16,num.val,1,100,'Recursion level')
378                 Button("Recursive",EVENT_RECURS,55,10,100,16)
379         
380         Button("Exit",EVENT_EXIT,210,10,80,20)
381
382 def event(evt, val):
383         if ((evt == QKEY or evt == ESCKEY) and not val): Exit()
384
385 def bevent(evt):
386         if evt == EVENT_EXIT            : Exit()
387         elif evt == EVENT_BEVEL  : bevel()
388         elif evt == EVENT_UPDATE        :
389                 try: bevel_update()
390                 except NameError                : pass
391         elif evt == EVENT_RECURS        : recursive()
392
393 Register(draw, event, bevent)
394
395 ######################################################################
396 def bevel():
397         """ The main function, which creates the bevel """
398         global me,NV,NV_ext,NE,NC, old_faces,old_dist
399         
400         ob = act_mesh_ob()
401         if not ob: return
402         
403         Window.WaitCursor(1) # Change the Cursor
404         t= Blender.sys.time()
405         is_editmode = Window.EditMode() 
406         if is_editmode: Window.EditMode(0)
407         
408         me = ob.data
409
410         NV = {}
411         #PY23 NO SETS# NV_ext = set()
412         NV_ext= {}
413         NE = {}
414         NC = {}
415         old_faces = []
416
417         make_faces()
418         make_edges()
419         make_corners()
420         clear_old()
421
422         old_dist = dist.val
423         print '\tbevel in %.6f sec' % (Blender.sys.time()-t)
424         me.update(1)
425         if is_editmode: Window.EditMode(1)
426         Window.WaitCursor(0)
427         Blender.Redraw()
428
429 def bevel_update():
430         """ Use NV to update the bevel """
431         global dist, old_dist
432         
433         if old_dist == None:
434                 # PupMenu('Error%t|Must bevel first.')
435                 return
436         
437         is_editmode = Window.EditMode()
438         if is_editmode: Window.EditMode(0)
439         
440         fac = dist.val - old_dist
441         old_dist = dist.val
442
443         for old_v in NV.iterkeys():
444                 for dir in NV[old_v].iterkeys():
445                         for i in xrange(3):
446                                 NV[old_v][dir].co[i] += fac*dir[i]
447
448         me.update(1)
449         if is_editmode: Window.EditMode(1)
450         Blender.Redraw()
451
452 def recursive():
453         """ Make a recursive bevel... still experimental """
454         global dist
455         from math import pi, sin
456         
457         if num.val > 1:
458                 a = pi/4
459                 ang = []
460                 for k in xrange(num.val):
461                         ang.append(a)
462                         a = (pi+2*a)/4
463
464                 l = [2*(1-sin(x))/sin(2*x) for x in ang]
465                 R = dist.val/sum(l)
466                 l = [x*R for x in l]
467
468                 dist.val = l[0]
469                 bevel_update()
470
471                 for x in l[1:]:
472                         dist.val = x
473                         bevel()
474