Script written for apricot, poly reduces based on known topologies, keeping edge...
authorCampbell Barton <ideasman42@gmail.com>
Thu, 2 Apr 2009 11:30:27 +0000 (11:30 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Thu, 2 Apr 2009 11:30:27 +0000 (11:30 +0000)
Example
http://www.graphicall.org/ftp/ideasman42/unsubsurf.gif

release/scripts/mesh_poly_reduce_grid.py [new file with mode: 0644]

diff --git a/release/scripts/mesh_poly_reduce_grid.py b/release/scripts/mesh_poly_reduce_grid.py
new file mode 100644 (file)
index 0000000..3741a47
--- /dev/null
@@ -0,0 +1,351 @@
+#!BPY
+"""
+Name: 'Poly Reduce Selection (Unsubsurf)'
+Blender: 245
+Group: 'Mesh'
+Tooltip: 'pradictable mesh simplifaction maintaining face loops'
+"""
+
+from Blender import Scene, Mesh, Window, sys
+import BPyMessages
+import bpy
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Script copyright (C) Campbell J Barton
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+# --------------------------------------------------------------------------
+
+
+def my_mesh_util(me):
+       me_verts = me.verts
+       
+       vert_faces = [ [] for v in me_verts]
+       vert_faces_corner = [ [] for v in me_verts]
+       
+       
+       # Ignore topology where there are not 2 faces connected to an edge.
+       edge_count = {}
+       for f in me.faces:
+               for edkey in f.edge_keys:
+                       try:
+                               edge_count[edkey] += 1
+                       except:
+                               edge_count[edkey]  = 1
+                               
+       for edkey, count in edge_count.iteritems():
+               
+               # Ignore verts that connect to edges with more then 2 faces.
+               if count != 2:
+                       vert_faces[edkey[0]] = None
+                       vert_faces[edkey[1]] = None
+       # Done
+       
+       
+       
+       def faces_set_verts(face_ls):
+               unique_verts = set()
+               for f in face_ls:
+                       for v in f:
+                               unique_verts.add(v.index)
+               return unique_verts
+       
+       for f in me.faces:
+               for corner, v in enumerate(f):
+                       i = v.index
+                       if vert_faces[i] != None:
+                               vert_faces[i].append(f)
+                               vert_faces_corner[i].append( corner )
+       
+       grid_data_ls = []
+       
+       for vi, face_ls in enumerate(vert_faces):
+               if face_ls != None:
+                       if len(face_ls) == 4:
+                               if face_ls[0].sel and face_ls[1].sel and face_ls[2].sel and face_ls[3].sel:                                     
+                                       # Support triangles also
+                                       unique_vert_count = len(faces_set_verts(face_ls))
+                                       quads = 0
+                                       for f in face_ls:
+                                               if len(f) ==4:
+                                                       quads += 1
+                                       if unique_vert_count==5+quads: # yay we have a grid
+                                               grid_data_ls.append( (vi, face_ls) )
+                       
+                       elif len(face_ls) == 3:
+                               if face_ls[0].sel and face_ls[1].sel and face_ls[2].sel:
+                                       unique_vert_count = len(faces_set_verts(face_ls))
+                                       if unique_vert_count==4: # yay we have 3 triangles to make into a bigger triangle
+                                               grid_data_ls.append( (vi, face_ls) )
+                               
+       
+       
+       # Now sort out which grid faces to use
+       
+       
+       # This list will be used for items we can convert, vertex is key, faces are values
+       grid_data_dict = {}
+       
+       if not grid_data_ls:
+               print "doing nothing"
+               return
+       
+       # quick lookup for the opposing corner of a qiad
+       quad_diag_mapping = 2,3,0,1
+       
+       verts_used = [0] * len(me_verts) # 0 == untouched, 1==should touch, 2==touched
+       verts_used[grid_data_ls[0][0]] = 1 # start touching 1!
+       
+       # From the corner vert, get the 2 edges that are not the corner or its opposing vert, this edge will make a new face
+       quad_edge_mapping = (1,3), (2,0), (1,3), (0,2) # hi-low, low-hi order is intended
+       tri_edge_mapping = (1,2), (0,2), (0,1)
+       
+       done_somthing = True
+       while done_somthing:
+               done_somthing = False
+               grid_data_ls_index = -1
+               
+               for vi, face_ls in grid_data_ls:
+                       grid_data_ls_index += 1
+                       if len(face_ls) == 3:
+                               grid_data_dict[vi] = face_ls
+                               grid_data_ls.pop( grid_data_ls_index )
+                               break
+                       elif len(face_ls) == 4:
+                               # print vi
+                               if verts_used[vi] == 1:
+                                       verts_used[vi] = 2 # dont look at this again.
+                                       done_somthing = True
+                                       
+                                       grid_data_dict[vi] = face_ls
+                                       
+                                       # Tag all faces verts as used
+                                       
+                                       for i, f in enumerate(face_ls):
+                                               # i == face index on vert, needed to recall which corner were on.
+                                               v_corner = vert_faces_corner[vi][i]
+                                               fv =f.v
+                                               
+                                               if len(f) == 4:
+                                                       v_other = quad_diag_mapping[v_corner]
+                                                       # get the 2 other corners
+                                                       corner1, corner2 = quad_edge_mapping[v_corner]
+                                                       if verts_used[fv[v_other].index] == 0:
+                                                               verts_used[fv[v_other].index] = 1 # TAG for touching!
+                                               else:
+                                                       corner1, corner2 = tri_edge_mapping[v_corner]
+                                               
+                                               verts_used[fv[corner1].index] = 2 # Dont use these, they are 
+                                               verts_used[fv[corner2].index] = 2
+                                               
+                                               
+                                       # remove this since we have used it.
+                                       grid_data_ls.pop( grid_data_ls_index )
+                                       
+                                       break
+               
+               if done_somthing == False:
+                       # See if there are any that have not even been tagged, (probably on a different island), then tag them.
+                       
+                       for vi, face_ls in grid_data_ls:
+                               if verts_used[vi] == 0:
+                                       verts_used[vi] = 1
+                                       done_somthing = True
+                                       break
+       
+       
+       # Now we have all the areas we will fill, calculate corner triangles we need to fill in.
+       new_faces = []
+       quad_del_vt_map = (1,2,3), (0,2,3), (0,1,3), (0,1,2)
+       for vi, face_ls in grid_data_dict.iteritems():
+               for i, f in enumerate(face_ls):
+                       if len(f) == 4:
+                               # i == face index on vert, needed to recall which corner were on.
+                               v_corner = vert_faces_corner[vi][i]
+                               v_other = quad_diag_mapping[v_corner]
+                               fv =f.v
+                               
+                               #print verts_used[fv[v_other].index]
+                               #if verts_used[fv[v_other].index] != 2: # DOSNT WORK ALWAYS
+                               
+                               if 1: # THIS IS LAzY - some of these faces will be removed after adding.
+                                       # Ok we are removing half of this face, add the other half
+                                       
+                                       # This is probably slower
+                                       # new_faces.append( [fv[ii].index for ii in (0,1,2,3) if ii != v_corner ] )
+                                       
+                                       # do this instead
+                                       new_faces.append( (fv[quad_del_vt_map[v_corner][0]], fv[quad_del_vt_map[v_corner][1]], fv[quad_del_vt_map[v_corner][2]]) )
+       
+       del grid_data_ls
+       
+       
+       # me.sel = 0
+       def faceCombine4(vi, face_ls):
+               edges = []
+               
+               for i, f in enumerate(face_ls):
+                       fv = f.v
+                       v_corner = vert_faces_corner[vi][i]
+                       if len(f)==4:   ed = quad_edge_mapping[v_corner]
+                       else:                   ed = tri_edge_mapping[v_corner]
+                       
+                       edges.append( [fv[ed[0]].index, fv[ed[1]].index] )
+               
+               # get the face from the edges 
+               face = edges.pop()
+               while len(face) != 4:
+                       # print len(edges), edges, face
+                       for ed_idx, ed in enumerate(edges):
+                               if face[-1] == ed[0] and (ed[1] != face[0]):
+                                       face.append(ed[1])
+                               elif face[-1] == ed[1] and (ed[0] != face[0]):
+                                       face.append(ed[0])
+                               else:
+                                       continue
+                               
+                               edges.pop(ed_idx) # we used the edge alredy
+                               break
+               
+               return face     
+       
+       for vi, face_ls in grid_data_dict.iteritems():
+               if len(face_ls) == 4:
+                       new_faces.append( faceCombine4(vi, face_ls) )
+                       #pass
+               if len(face_ls) == 3: # 3 triangles
+                       face = list(faces_set_verts(face_ls))
+                       face.remove(vi)
+                       new_faces.append( face )
+                       
+       
+       # Now remove verts surounded by 3 triangles
+       
+
+               
+       # print new_edges
+       # me.faces.extend(new_faces, ignoreDups=True)
+       
+       '''
+       faces_remove = []
+       for vi, face_ls in grid_data_dict.iteritems():
+               faces_remove.extend(face_ls)
+       '''
+       
+       orig_facelen = len(me.faces)
+       
+       orig_faces = list(me.faces)
+       me.faces.extend(new_faces, ignoreDups=True)
+       new_faces = list(me.faces)[len(orig_faces):]
+       
+       
+       
+       
+       
+       if me.faceUV:
+               uvnames = me.getUVLayerNames()
+               act_uvlay = me.activeUVLayer
+               
+               vert_faces_uvs =        []
+               vert_faces_images =     []
+                       
+                       
+               act_uvlay = me.activeUVLayer
+               
+               for uvlay in uvnames:
+                       me.activeUVLayer = uvlay
+                       vert_faces_uvs[:] = [None] * len(me.verts)
+                       vert_faces_images[:] = vert_faces_uvs[:]
+                       
+                       for i,f in enumerate(orig_faces):
+                               img = f.image
+                               fv = f.v
+                               uv = f.uv
+                               mat = f.mat
+                               for i,v in enumerate(fv):
+                                       vi = v.index
+                                       vert_faces_uvs[vi] = uv[i] # no nice averaging
+                                       vert_faces_images[vi] = img
+                                       
+                                       
+                       # Now copy UVs across
+                       for f in new_faces:     
+                               fi = [v.index for v in f.v]
+                               f.image = vert_faces_images[fi[0]]
+                               uv = f.uv
+                               for i,vi in enumerate(fi):
+                                       uv[i][:] = vert_faces_uvs[vi]
+               
+               if len(me.materials) > 1:
+                       vert_faces_mats = [None] * len(me.verts)
+                       for i,f in enumerate(orig_faces):
+                               mat = f.mat
+                               for i,v in enumerate(f.v):
+                                       vi = v.index
+                                       vert_faces_mats[vi] = mat
+                               
+                       # Now copy UVs across
+                       for f in new_faces:
+                               print vert_faces_mats[f.v[0].index]
+                               f.mat = vert_faces_mats[f.v[0].index]
+                               
+       
+       me.verts.delete(grid_data_dict.keys())
+       
+       # me.faces.delete(1, faces_remove)
+       
+       if me.faceUV:
+               me.activeUVLayer = act_uvlay
+       
+       me.calcNormals()
+
+def main():
+       
+       # Gets the current scene, there can be many scenes in 1 blend file.
+       sce = bpy.data.scenes.active
+       
+       # Get the active object, there can only ever be 1
+       # and the active object is always the editmode object.
+       ob_act = sce.objects.active
+       
+       if not ob_act or ob_act.type != 'Mesh':
+               BPyMessages.Error_NoMeshActive()
+               return 
+       
+       is_editmode = Window.EditMode()
+       if is_editmode: Window.EditMode(0)
+       
+       Window.WaitCursor(1)
+       me = ob_act.getData(mesh=1) # old NMesh api is default
+       t = sys.time()
+       
+       # Run the mesh editing function
+       my_mesh_util(me)
+       
+       # Restore editmode if it was enabled
+       if is_editmode: Window.EditMode(1)
+       
+       # Timing the script is a good way to be aware on any speed hits when scripting
+       print 'My Script finished in %.2f seconds' % (sys.time()-t)
+       Window.WaitCursor(0)
+       
+       
+# This lets you can import the script without running it
+if __name__ == '__main__':
+       main()
+