Scripts:
[blender.git] / release / scripts / sel_same.py
1 #!BPY
2
3 """
4 Name: 'Select Same Faces'
5 Blender: 232
6 Group: 'UV'
7 Tooltip: 'Select faces if attributes match the active.'
8 """
9
10 # $Id$
11 #
12 #===============================================#
13 # Sel Same script 1.0 by Campbell Barton        #
14 # email me ideasman@linuxmail.org               #
15 #===============================================#
16
17
18 # -------------------------------------------------------------------------- 
19 # Sel Same Face 1.0 By Campbell Barton (AKA Ideasman)
20 # -------------------------------------------------------------------------- 
21 # ***** BEGIN GPL LICENSE BLOCK ***** 
22
23 # This program is free software; you can redistribute it and/or 
24 # modify it under the terms of the GNU General Public License 
25 # as published by the Free Software Foundation; either version 2 
26 # of the License, or (at your option) any later version. 
27
28 # This program is distributed in the hope that it will be useful, 
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of 
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
31 # GNU General Public License for more details. 
32
33 # You should have received a copy of the GNU General Public License 
34 # along with this program; if not, write to the Free Software Foundation, 
35 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
36
37 # ***** END GPL LICENCE BLOCK ***** 
38 # -------------------------------------------------------------------------- 
39
40
41
42 from Blender import *
43 from Blender.Mathutils import DotVecs, Vector
44 from math import sqrt
45
46
47 #====================================#
48 # Sanity checks                      #
49 #====================================#
50 def error(str):
51         Draw.PupMenu('ERROR%t|'+str)
52 af = None 
53 selection = Object.GetSelected()
54 if len(selection) == 0:
55   error('No object selected')
56 else:
57   object = Object.GetSelected()[0]
58   if object.getType() != 'Mesh':
59     error('Not a mesh')
60   else:
61     mesh = object.getData()
62     
63     # We have a mesh so find AF.
64     af = mesh.getActiveFace()
65     if af: af = mesh.faces[af]
66
67 if af == None:
68   error('no active face')
69
70 else: # Okay everything seems sane
71   
72   #=====================================
73   # Popup menu to select the functions #
74   #====================================#
75   method = Draw.PupMenu(\
76   'Select Same as Active%t|\
77   Material|\
78   UV Image|\
79   Face Mode|\
80   Vertex Colours|\
81   UV CO-Ords|\
82   Area|\
83   Proportions|\
84   Normal|\
85   Co-Planer|')
86   
87   if method != -1:
88     #================================================#
89     # Do we add, seb or set to the existing face sel #
90     #================================================#
91     faceOp = Draw.PupMenu(\
92     'Active Face Match%t|\
93     Add to Selection|\
94     Subtract From Selection |\
95     Overwrite Selection|\
96     Overwrite Selection Inverse|')
97     
98     if faceOp != -1:
99       
100       def setFSel(f):
101         if faceOp == 1 or faceOp == 3:
102           f.flag |= NMesh.FaceFlags['SELECT'] # will set selection
103         elif faceOp == 2 or faceOp ==4:
104           f.flag &=~NMesh.FaceFlags['SELECT'] # will unselect, note the '~' to invert bitflags
105       
106       def setFUnSel(f):
107         if faceOp == 3:
108           f.flag &=~NMesh.FaceFlags['SELECT'] # will unselect, note the '~' to invert bitflags
109         elif faceOp == 4:
110           f.flag |= NMesh.FaceFlags['SELECT'] # will set selection
111       
112       
113       
114       #================#
115       # Math functions #
116       #================#
117       def compare(f1, f2, limit):
118         if f1 + limit > f2 and f1 - limit < f2:
119           return 1
120         return 0
121       
122       def compare2(v1, v2, limit):
123         if v1[0] + limit > v2[0] and v1[0] - limit < v2[0]:
124             if v1[1] + limit > v2[1] and v1[1] - limit < v2[1]:
125               return 1
126         return 0
127       
128       def compare3(v1, v2, limit):
129         if v1[0] + limit > v2[0] and v1[0] - limit < v2[0]:
130             if v1[1] + limit > v2[1] and v1[1] - limit < v2[1]:
131               if v1[2] + limit > v2[2] and v1[2] - limit < v2[2]:
132                 return 1
133         return 0
134       
135       def colCompare(v1, v2, limit):
136         # Simple test first
137         if v1.r == v2.r:
138           if v1.g == v2.g:
139             if v1.b == v2.b:
140               return 1  
141         # Now a test that uses the limit.
142         limit = int(limit * 255)
143         if v1.r + limit >= v2.r and v1.r - limit <= v2.r:
144           if v1.g + limit >= v2.g and v1.g - limit <= v2.g:
145             if v1.b + limit >= v2.b and v1.b - limit <= v2.b:
146               return 1
147         return 0
148       
149       # Makes sure face 2 has all the colours of face 1
150       def faceColCompare(f1, f2, limit):
151         avcolIdx = 0
152         while avcolIdx < len(f1.col):
153           match = 0
154           
155           vcolIdx = 0
156           while vcolIdx < len(f2.col):
157             if colCompare(f1.col[avcolIdx], f2.col[vcolIdx], limit):
158               match = 1
159               break
160             vcolIdx += 1
161           if match == 0: # premature exit if a motch not found
162             return 0
163           avcolIdx += 1
164         return 1
165       
166       # Makes sure face 2 has matching UVs within the limit.
167       def faceUvCompare(f1, f2, limit):
168         for auv in f1.uv:
169           match = 0
170           for uv in f2.uv:
171             if compare2(auv, uv, limit):
172               match = 1
173               break
174           if match == 0: # premature exit if a motch not found
175             return 0
176         return 1
177       
178       
179       def measure(v1, v2):
180         return Mathutils.Vector([v1[0]-v2[0], v1[1] - v2[1], v1[2] - v2[2]]).length
181       
182       def triArea2D(v1, v2, v3):
183         e1 = measure(v1, v2)  
184         e2 = measure(v2, v3)  
185         e3 = measure(v3, v1)  
186         p = e1+e2+e3
187         return 0.25 * sqrt(p*(p-2*e1)*(p-2*e2)*(p-2*e3))
188       #====================#
189       # End Math Functions #
190       #====================#
191       
192       
193       #=============================#
194       # Blender functions/shortcuts #
195       #=============================#
196       def getLimit(text):
197         return Draw.PupFloatInput(text, 0.1, 0.0, 1.0, 0.1, 3)
198       
199       def faceArea(f):
200         if len(f.v) == 4:
201           return triArea2D(f.v[0].co, f.v[1].co, f.v[2].co) + triArea2D(f.v[0].co, f.v[2].co, f.v[3].co)
202         elif len(f.v) == 3:
203           return triArea2D(f.v[0].co, f.v[1].co, f.v[2].co)
204         
205       def getEdgeLengths(f):
206         if len(f.v) == 4:
207           return (measure(f.v[0].co, f.v[1].co), measure(f.v[1].co, f.v[2].co), measure(f.v[2].co, f.v[3].co) , measure(f.v[3].co, f.v[0].co) )
208         elif len(f.v) == 3:
209           return (measure(f.v[0].co, f.v[1].co), measure(f.v[1].co, f.v[2].co), measure(f.v[2].co, f.v[0].co) )
210       
211       def faceCent(f):
212         x = y = z = 0
213         for v in f.v:
214           x += v.co[0]
215           y += v.co[1]
216           z += v.co[2]
217         x = x/len(f.v)
218         y = y/len(f.v)
219         z = z/len(f.v)
220         return Vector([x, y, z])
221       
222       #========================================#
223       # Should we bother computing this faces  #
224       #========================================#
225       def fShouldCompare(f):
226         # Only calculate for faces that will be affected.
227         if len(f.v) < 3: # cant be an edge
228           return 0
229         elif faceOp == 1 and f.flag == 1:
230           return 0
231         elif faceOp == 0 and f.flag == 0:
232           return 0
233         elif f.flag == 64: # Ignore hidden
234           return 0
235         return 1
236         
237       #=======================================#
238       # Sel same funcs as called by the menus #
239       #=======================================#
240       def get_same_mat():
241         for f in mesh.faces:
242           if fShouldCompare(f):
243             if af.mat == f.mat: setFSel(f)
244             else:               setFUnSel(f)
245       
246       def get_same_image():
247         if mesh.hasFaceUV() == 0:
248           error('mesh has no uv image')
249         else:
250           for f in mesh.faces:
251             if fShouldCompare(f):
252               if af.image == f.image: setFSel(f)
253               else:                   setFUnSel(f)
254       
255       def get_same_mode():
256         for f in mesh.faces:
257           if fShouldCompare(f):
258             if af.mode == f.mode: setFSel(f)
259             else:                 setFUnSel(f)
260       
261       def get_same_vcol(limit):
262         for f in mesh.faces:
263           if fShouldCompare(f):
264             if faceColCompare(f, af, limit) and faceColCompare(af, f, limit) :
265               setFSel(f)
266             else:
267               setFUnSel(f)
268       
269       def get_same_uvco(limit):
270         for f in mesh.faces:
271           if fShouldCompare(f):
272             if faceUvCompare(af, f, limit): setFSel(f)
273             else:                           setFUnSel(f)
274       
275       def get_same_area(limit):
276         afArea = faceArea(af)
277         limit = limit * afArea # Make the lomot proportinal to the 
278         for f in mesh.faces:
279           if fShouldCompare(f):
280             if compare(afArea, faceArea(f), limit): setFSel(f)
281             else:                                   setFUnSel(f)
282       
283       def get_same_prop(limit):
284         # Here we get the perimeter and use it for a proportional limit modifier.
285         afEdgeLens = getEdgeLengths(af)
286         perim = 0
287         for e in afEdgeLens:
288           perim += e
289       
290         limit = limit * perim
291         for f in mesh.faces:
292           if fShouldCompare(f):
293             for ae in afEdgeLens:
294               match = 0
295               for e in getEdgeLengths(f):
296                 if compare(ae, e, limit):
297                   match = 1
298                   break
299               if not match:
300                 break
301            
302             if match: setFSel(f)
303             else:     setFUnSel(f)
304       
305       def get_same_normal(limit):    
306         limit = limit * 2
307         for f in mesh.faces:
308           if fShouldCompare(f):
309             if compare3(af.no, f.no, limit): setFSel(f)
310             else:                            setFUnSel(f)
311       
312       def get_same_coplaner(limit):
313         nlimit = limit * 2 # * 1 # limit for normal test
314         climit = limit * 3 # limit for coplaner test.
315         afCent = faceCent(af)
316         for f in mesh.faces:
317           if fShouldCompare(f):
318             match = 0
319             if compare3(af.no, f.no, nlimit):
320               fCent = faceCent(f)
321               if abs(DotVecs(Vector([af.no[0], af.no[1], af.no[2]]), afCent ) - DotVecs(Vector([af.no[0], af.no[1], af.no[2]]), fCent )) <= climit:
322                 match = 1
323             if match:
324               setFSel(f)
325             else:
326               setFUnSel(f)
327       
328       #=====================#
329       # End Sel same funcs  #
330       #=====================#
331       limit = 1 # some of these dont use the limit so it needs to be set, to somthing.
332       # act on the menu item selected
333       if method == 1: # Material
334         get_same_mat()
335       elif method == 2: # UV Image
336         get_same_image()
337       elif method == 3: # mode
338         get_same_mode()
339       elif method == 4: # vertex colours
340         limit = getLimit('vert col limit: ')
341         if limit != None:
342           get_same_vcol(limit)
343       elif method == 5: # UV-coords
344         limit = getLimit('uv-coord limit: ')
345         if limit != None:
346           get_same_uvco(limit)
347       elif method == 6: # area
348         limit = getLimit('area limit: ')
349         if limit != None:
350           get_same_area(limit)
351       elif method == 7: # proportions
352         limit = getLimit('proportion limit: ')
353         if limit != None:
354           get_same_prop(limit)
355       elif method == 8: # normal
356         limit = getLimit('normal limit: ')
357         if limit != None:
358           get_same_normal(limit)
359       elif method == 9: # coplaner
360         limit = getLimit('coplaner limit: ')
361         if limit != None:
362           get_same_coplaner(limit)
363       
364       # If limit is not set then dont bother
365       if limit != None:
366         mesh.update(0)