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