2009-04-02 11:30:27 +00:00
|
|
|
#!BPY
|
|
|
|
"""
|
|
|
|
Name: 'Poly Reduce Selection (Unsubsurf)'
|
|
|
|
Blender: 245
|
|
|
|
Group: 'Mesh'
|
2009-05-15 21:27:13 +00:00
|
|
|
Tooltip: 'predictable mesh simplifaction maintaining face loops'
|
2009-04-02 11:30:27 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
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():
|
|
|
|
|
2009-05-15 21:27:13 +00:00
|
|
|
# Ignore verts that connect to edges with more than 2 faces.
|
2009-04-02 11:30:27 +00:00
|
|
|
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()
|
|
|
|
|