Use URL icon, add Tip: prefix, increase lower margin
[blender-addons-contrib.git] / mesh_extra_tools / mesh_polyredux.py
1 # ***** BEGIN GPL LICENSE BLOCK *****
2 #
3 # Script copyright (C) Campbell J Barton
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 #
19 # ***** END GPL LICENCE BLOCK *****
20 # --------------------------------------------------------------------------
21 bl_info = {
22     "name": "PolyRedux",
23     "author": "Campbell J Barton - updated by Gert De Roost",
24     "version": (2, 0, 4),
25     "blender": (2, 63, 0),
26     "location": "View3D > Tools",
27     "description": "predictable mesh simplifaction maintaining face loops",
28     "warning": "",
29     "wiki_url": "",
30     "tracker_url": "",
31     "category": "Mesh"}
32
33
34 if "bpy" in locals():
35     import imp
36
37 import bpy
38 import bmesh
39 import time
40
41
42 class PolyRedux(bpy.types.Operator):
43         bl_idname = "mesh.polyredux"
44         bl_label = "PolyRedux"
45         bl_description = "predictable mesh simplifaction maintaining face loops"
46         bl_options = {"REGISTER", "UNDO"}
47         
48
49         @classmethod
50         def poll(cls, context):
51                 obj = context.active_object
52                 return (obj and obj.type == 'MESH')
53
54         def invoke(self, context, event):
55                 
56                 scn = bpy.context.scene
57                 
58                 self.save_global_undo = bpy.context.user_preferences.edit.use_global_undo
59                 bpy.context.user_preferences.edit.use_global_undo = False
60                 
61                 do_polyredux(self)
62                 
63                 return {'FINISHED'}
64
65 class redux_help(bpy.types.Operator):
66         bl_idname = 'help.polyredux'
67         bl_label = ''
68
69         def draw(self, context):
70                 layout = self.layout
71                 layout.label('To use:')
72                 layout.label('Make a selection of verts or polygons to reduce.')
73                 layout.label('works on whole mesh or selected')
74                 layout.label('To Help:')
75                 layout.label('Single operation, no parameters.')
76         
77         def execute(self, context):
78                 return {'FINISHED'}
79
80         def invoke(self, context, event):
81                 return context.window_manager.invoke_popup(self, width = 300)
82 '''
83 def panel_func(self, context):
84         
85         scn = bpy.context.scene
86         self.layout.label(text="PolyRedux:")
87         self.layout.operator("mesh.polyredux", text="Poly Redux")
88
89
90 def register():
91         bpy.utils.register_module(__name__)
92         bpy.types.VIEW3D_PT_tools_meshedit.append(panel_func)
93
94
95 def unregister():
96         bpy.utils.unregister_module(__name__)
97         bpy.types.VIEW3D_PT_tools_meshedit.remove(panel_func)
98
99
100 if __name__ == "__main__":
101         register()
102
103
104 '''
105
106 def my_mesh_util():
107         bm_verts = bm.verts
108         
109         vert_faces = [ [] for v in bm_verts]
110         vert_faces_corner = [ [] for v in bm_verts]
111         
112         
113         # Ignore topology where there are not 2 faces connected to an edge.
114         edge_count = {}
115         for f in bm.faces:
116                 for edge in f.edges:
117                         edkey = (edge.verts[0].index, edge.verts[1].index)
118                         try:
119                                 edge_count[edkey] += 1
120                         except:
121                                 edge_count[edkey]  = 1
122                                 
123         for edkey, count in edge_count.items():
124                 
125                 # Ignore verts that connect to edges with more than 2 faces.
126                 if count != 2:
127                         vert_faces[edkey[0]] = None
128                         vert_faces[edkey[1]] = None
129         # Done
130         
131         
132         
133         def faces_set_verts(face_ls):
134                 unique_verts = set()
135                 for f in face_ls:
136                         for v in f.verts:
137                                 unique_verts.add(v.index)
138                 return unique_verts
139         
140         for f in bm.faces:
141                 for corner, v in enumerate(f.verts):
142                         i = v.index
143                         if vert_faces[i] != None:
144                                 vert_faces[i].append(f)
145                                 vert_faces_corner[i].append(corner)
146         
147         grid_data_ls = []
148         
149         for vi, face_ls in enumerate(vert_faces):
150                 if face_ls != None:
151                         if len(face_ls) == 4:
152                                 if face_ls[0].select and face_ls[1].select and face_ls[2].select and face_ls[3].select:                                 
153                                         # Support triangles also
154                                         unique_vert_count = len(faces_set_verts(face_ls))
155                                         quads = 0
156                                         for f in face_ls:
157                                                 if len(f.verts) ==4:
158                                                         quads += 1
159                                         if unique_vert_count==5+quads: # yay we have a grid
160                                                 grid_data_ls.append( (vi, face_ls) )
161                         
162                         elif len(face_ls) == 3:
163                                 if face_ls[0].select and face_ls[1].select and face_ls[2].select:
164                                         unique_vert_count = len(faces_set_verts(face_ls))
165                                         if unique_vert_count==4: # yay we have 3 triangles to make into a bigger triangle
166                                                 grid_data_ls.append( (vi, face_ls) )
167                                 
168         
169         
170         # Now sort out which grid faces to use
171         
172         
173         # This list will be used for items we can convert, vertex is key, faces are values
174         grid_data_dict = {}
175         
176         if not grid_data_ls:
177                 print ("doing nothing")
178                 return
179         
180         # quick lookup for the opposing corner of a qiad
181         quad_diag_mapping = 2,3,0,1
182         
183         verts_used = [0] * len(bm_verts) # 0 == untouched, 1==should touch, 2==touched
184         verts_used[grid_data_ls[0][0]] = 1 # start touching 1!
185         
186         # From the corner vert, get the 2 edges that are not the corner or its opposing vert, this edge will make a new face
187         quad_edge_mapping = (1,3), (2,0), (1,3), (0,2) # hi-low, low-hi order is intended
188         tri_edge_mapping = (1,2), (0,2), (0,1)
189         
190         done_somthing = True
191         while done_somthing:
192                 done_somthing = False
193                 grid_data_ls_index = -1
194                 
195                 for vi, face_ls in grid_data_ls:
196                         grid_data_ls_index += 1
197                         if len(face_ls) == 3:
198                                 grid_data_dict[bm.verts[vi]] = face_ls
199                                 grid_data_ls.pop( grid_data_ls_index )
200                                 break
201                         elif len(face_ls) == 4:
202                                 # print vi
203                                 if verts_used[vi] == 1:
204                                         verts_used[vi] = 2 # dont look at this again.
205                                         done_somthing = True
206                                         
207                                         grid_data_dict[bm.verts[vi]] = face_ls
208                                         
209                                         # Tag all faces verts as used
210                                         
211                                         for i, f in enumerate(face_ls):
212                                                 # i == face index on vert, needed to recall which corner were on.
213                                                 v_corner = vert_faces_corner[vi][i]
214                                                 fv =f.verts
215                                                 
216                                                 if len(f.verts) == 4:
217                                                         v_other = quad_diag_mapping[v_corner]
218                                                         # get the 2 other corners
219                                                         corner1, corner2 = quad_edge_mapping[v_corner]
220                                                         if verts_used[fv[v_other].index] == 0:
221                                                                 verts_used[fv[v_other].index] = 1 # TAG for touching!
222                                                 else:
223                                                         corner1, corner2 = tri_edge_mapping[v_corner]
224                                                 
225                                                 verts_used[fv[corner1].index] = 2 # Dont use these, they are 
226                                                 verts_used[fv[corner2].index] = 2
227                                                 
228                                                 
229                                         # remove this since we have used it.
230                                         grid_data_ls.pop( grid_data_ls_index )
231                                         
232                                         break
233                 
234                 if done_somthing == False:
235                         # See if there are any that have not even been tagged, (probably on a different island), then tag them.
236                         
237                         for vi, face_ls in grid_data_ls:
238                                 if verts_used[vi] == 0:
239                                         verts_used[vi] = 1
240                                         done_somthing = True
241                                         break
242         
243         
244         # Now we have all the areas we will fill, calculate corner triangles we need to fill in.
245         new_faces = []
246         quad_del_vt_map = (1,2,3), (0,2,3), (0,1,3), (0,1,2)
247         for v, face_ls in grid_data_dict.items():
248                 for i, f in enumerate(face_ls):
249                         if len(f.verts) == 4:
250                                 # i == face index on vert, needed to recall which corner were on.
251                                 v_corner = vert_faces_corner[v.index][i]
252                                 v_other = quad_diag_mapping[v_corner]
253                                 fv =f.verts
254                                 
255                                 #print verts_used[fv[v_other].index]
256                                 #if verts_used[fv[v_other].index] != 2: # DOSNT WORK ALWAYS
257                                 
258                                 if 1: # THIS IS LAzY - some of these faces will be removed after adding.
259                                         # Ok we are removing half of this face, add the other half
260                                         
261                                         # This is probably slower
262                                         # new_faces.append( [fv[ii].index for ii in (0,1,2,3) if ii != v_corner ] )
263                                         
264                                         # do this instead
265                                         new_faces.append( (fv[quad_del_vt_map[v_corner][0]].index, fv[quad_del_vt_map[v_corner][1]].index, fv[quad_del_vt_map[v_corner][2]].index) )
266         
267         del grid_data_ls
268         
269         
270         # me.sel = 0
271         def faceCombine4(vi, face_ls):
272                 edges = []
273                 
274                 for i, f in enumerate(face_ls):
275                         fv = f.verts
276                         v_corner = vert_faces_corner[vi][i]
277                         if len(f.verts)==4:     ed = quad_edge_mapping[v_corner]
278                         else:                   ed = tri_edge_mapping[v_corner]
279                         
280                         edges.append( [fv[ed[0]].index, fv[ed[1]].index] )
281                 
282                 # get the face from the edges 
283                 face = edges.pop()
284                 while len(face) != 4:
285                         # print len(edges), edges, face
286                         for ed_idx, ed in enumerate(edges):
287                                 if face[-1] == ed[0] and (ed[1] != face[0]):
288                                         face.append(ed[1])
289                                 elif face[-1] == ed[1] and (ed[0] != face[0]):
290                                         face.append(ed[0])
291                                 else:
292                                         continue
293                                 
294                                 edges.pop(ed_idx) # we used the edge alredy
295                                 break
296                 
297                 return face     
298         
299         for v, face_ls in grid_data_dict.items():
300                 vi = v.index
301                 if len(face_ls) == 4:
302                         new_faces.append( faceCombine4(vi, face_ls) )
303                         #pass
304                 if len(face_ls) == 3: # 3 triangles
305                         face = list(faces_set_verts(face_ls))
306                         face.remove(vi)
307                         new_faces.append( face )
308                         
309         
310         # Now remove verts surounded by 3 triangles
311         
312
313                 
314         # print new_edges
315         # me.faces.extend(new_faces, ignoreDups=True)
316         
317         '''
318         faces_remove = []
319         for vi, face_ls in grid_data_dict.items():
320                 faces_remove.extend(face_ls)
321         '''
322         
323         orig_facelen = len(bm.faces)
324         
325         orig_faces = list(bm.faces)
326         made_faces = []
327         bpy.ops.mesh.select_all(action="DESELECT")
328         for vertidxs in new_faces:
329                 verts = []
330                 for idx in vertidxs:
331                         verts.append(bm.verts[idx])
332                 verts.append(verts[0])
333                 for idx in range(len(verts) - 1):
334                         verts.append(verts[0])
335                         v1 = verts[idx]
336                         v2 = verts[idx + 1]
337                         if bm.edges.get((v1, v2)) == None:
338                                 for f in v1.link_faces:
339                                         if f in v2.link_faces:
340                                                 bmesh.utils.face_split(f, v1, v2)
341                                                 break
342
343         for vert in grid_data_dict.keys():
344                 bmesh.utils.face_join(vert.link_faces[:])
345         
346         # me.faces.delete(1, faces_remove)
347         
348         bm.normal_update()
349
350 def do_polyredux(self):
351         
352         global bm, me
353         
354         # Gets the current scene, there can be many scenes in 1 blend file.
355         sce = bpy.context.scene
356         
357         # Get the active object, there can only ever be 1
358         # and the active object is always the editmode object.
359         mode = "EDIT"
360         if bpy.context.mode == "OBJECT":
361                 mode = "OBJECT"
362                 bpy.ops.object.editmode_toggle()
363         ob_act = bpy.context.active_object
364         if not ob_act or ob_act.type != 'MESH':
365                 return 
366         me = ob_act.data
367         bm = bmesh.from_edit_mesh(me)
368         
369         t = time.time()
370         
371         # Run the mesh editing function
372         my_mesh_util()
373         me.update(calc_edges=True, calc_tessface=True)
374         bm.free()
375         
376         # Restore editmode if it was enabled
377         if mode == "OBJECT":
378                 bpy.ops.object.editmode_toggle()
379         else:
380                 bpy.ops.object.editmode_toggle()
381                 bpy.ops.object.editmode_toggle()
382         
383         # Timing the script is a good way to be aware on any speed hits when scripting
384         print ('My Script finished in %.2f seconds' % (time.time()-t))
385         
386         
387