def meshNormalizedWeights(mesh): try: groupNames, vWeightList = BPyMesh.meshWeight2List(mesh) except: return [],[] if not groupNames: return [],[] for i, vWeights in enumerate(vWeightList): tot = 0.0 for w in vWeights: tot+=w #print 'i:%d tot:%f' % (i, tot) if tot: for j, w in enumerate(vWeights): vWeights[j] = w/tot #if w/tot > 0: #print 'i:%d j:%d w:%f w/tot:%f' % (i, j, w, vWeights[j]) return groupNames, vWeightList
def redux(ob, REDUX=0.5, BOUNDRY_WEIGHT=2.0, REMOVE_DOUBLES=False, FACE_AREA_WEIGHT=1.0, FACE_TRIANGULATE=True, DO_UV=True, DO_VCOL=True, DO_WEIGHTS=True, VGROUP_INF_REDUX=None, VGROUP_INF_WEIGHT=0.5): """ BOUNDRY_WEIGHT - 0 is no boundry weighting. 2.0 will make them twice as unlikely to collapse. FACE_AREA_WEIGHT - 0 is no weight. 1 is normal, 2.0 is higher. """ if REDUX < 0 or REDUX > 1.0: raise 'Error, factor must be between 0 and 1.0' elif not set: raise 'Error, this function requires Python 2.4 or a full install of Python 2.3' BOUNDRY_WEIGHT = 1 + BOUNDRY_WEIGHT """ # DEBUG! if Blender.Get('rt') == 1000: DEBUG=True else: DEBUG= False """ me = ob.getData(mesh=1) me.hide = False # unhide all data,. if len(me.faces) < 5: return if FACE_TRIANGULATE or REMOVE_DOUBLES: me.sel = True if FACE_TRIANGULATE: me.quadToTriangle() if REMOVE_DOUBLES: #me.remDoubles(0.0001) print "ERROR: No RemDoubles in background mode!!" vgroups = me.getVertGroupNames() if not me.getVertGroupNames(): DO_WEIGHTS = False if (VGROUP_INF_REDUX!= None and VGROUP_INF_REDUX not in vgroups) or\ VGROUP_INF_WEIGHT==0.0: VGROUP_INF_REDUX = None try: VGROUP_INF_REDUX_INDEX = vgroups.index(VGROUP_INF_REDUX) except: VGROUP_INF_REDUX_INDEX = -1 # del vgroups len_vgroups = len(vgroups) OLD_MESH_MODE = Blender.Mesh.Mode() Blender.Mesh.Mode(Blender.Mesh.SelectModes.VERTEX) if DO_UV and not me.faceUV: DO_UV = False if DO_VCOL and not me.vertexColors: DO_VCOL = False current_face_count = len(me.faces) target_face_count = int(current_face_count * REDUX) # % of the collapseable faces to collapse per pass. #collapse_per_pass= 0.333 # between 0.1 - lots of small nibbles, slow but high q. and 0.9 - big passes and faster. collapse_per_pass = 0.333 # between 0.1 - lots of small nibbles, slow but high q. and 0.9 - big passes and faster. """# DEBUG! if DEBUG: COUNT= [0] def rd(): if COUNT[0]< 330: COUNT[0]+=1 return me.update() Blender.Window.RedrawAll() print 'Press key for next, count "%s"' % COUNT[0] try: input() except KeyboardInterrupt: raise "Error" except: pass COUNT[0]+=1 """ class collapseEdge(object): __slots__ = 'length', 'key', 'faces', 'collapse_loc', 'v1', 'v2', 'uv1', 'uv2', 'col1', 'col2', 'collapse_weight' def __init__(self, ed): self.init_from_edge( ed) # So we can re-use the classes without using more memory. def init_from_edge(self, ed): self.key = ed.key self.length = ed.length self.faces = [] self.v1 = ed.v1 self.v2 = ed.v2 if DO_UV or DO_VCOL: self.uv1 = [] self.uv2 = [] self.col1 = [] self.col2 = [] # self.collapse_loc= None # new collapse location. # Basic weighting. #self.collapse_weight= self.length * (1+ ((ed.v1.no-ed.v2.no).length**2)) self.collapse_weight = 1.0 def collapse_locations(self, w1, w2): ''' Generate a smart location for this edge to collapse to w1 and w2 are vertex location bias ''' v1co = self.v1.co v2co = self.v2.co v1no = self.v1.no v2no = self.v2.no # Basic operation, works fine but not as good as predicting the best place. #between= ((v1co*w1) + (v2co*w2)) #self.collapse_loc= between # normalize the weights of each vert - se we can use them as scalers. wscale = w1 + w2 if not wscale: # no scale? w1 = w2 = 0.5 else: w1 /= wscale w2 /= wscale length = self.length between = MidpointVecs(v1co, v2co) # Collapse # new_location = between # Replace tricky code below. this code predicts the best collapse location. # Make lines at right angles to the normals- these 2 lines will intersect and be # the point of collapsing. # Enlarge so we know they intersect: self.length*2 cv1 = v1no.cross(v1no.cross(v1co - v2co)) cv2 = v2no.cross(v2no.cross(v2co - v1co)) # Scale to be less then the edge lengths. cv2.length = cv1.length = 1 cv1 = cv1 * (length * 0.4) cv2 = cv2 * (length * 0.4) smart_offset_loc = between + (cv1 + cv2) # Now we need to blend between smart_offset_loc and w1/w2 # you see were blending between a vert and the edges midpoint, so we cant use a normal weighted blend. if w1 > 0.5: # between v1 and smart_offset_loc #self.collapse_loc= v1co*(w2+0.5) + smart_offset_loc*(w1-0.5) w2 *= 2 w1 = 1 - w2 new_loc_smart = v1co * w1 + smart_offset_loc * w2 else: # w between v2 and smart_offset_loc w1 *= 2 w2 = 1 - w1 new_loc_smart = v2co * w2 + smart_offset_loc * w1 if new_loc_smart.x != new_loc_smart.x: # NAN LOCATION, revert to between new_loc_smart = None return new_loc_smart, between, v1co * 0.99999 + v2co * 0.00001, v1co * 0.00001 + v2co * 0.99999 class collapseFace(object): __slots__ = 'verts', 'normal', 'area', 'index', 'orig_uv', 'orig_col', 'uv', 'col' # , 'collapse_edge_count' def __init__(self, f): self.init_from_face(f) def init_from_face(self, f): self.verts = f.v self.normal = f.no self.area = f.area self.index = f.index if DO_UV: self.orig_uv = [uv_key(uv) for uv in f.uv] self.uv = f.uv if DO_VCOL: self.orig_col = [col_key(col) for col in f.col] self.col = f.col collapse_edges = collapse_faces = None # So meshCalcNormals can avoid making a new list all the time. reuse_vertNormals = [Vector() for v in xrange(len(me.verts))] while target_face_count <= len(me.faces): BPyMesh.meshCalcNormals(me, reuse_vertNormals) if DO_WEIGHTS: #groupNames, vWeightDict= BPyMesh.meshWeight2Dict(me) groupNames, vWeightList = BPyMesh.meshWeight2List(me) # THIS CRASHES? Not anymore. verts = list(me.verts) edges = list(me.edges) faces = list(me.faces) # THIS WORKS #verts= me.verts #edges= me.edges #faces= me.faces # if DEBUG: DOUBLE_CHECK= [0]*len(verts) me.sel = False if not collapse_faces: # Initialize the list. collapse_faces = [collapseFace(f) for f in faces] collapse_edges = [collapseEdge(ed) for ed in edges] else: for i, ed in enumerate(edges): collapse_edges[i].init_from_edge(ed) # Strip the unneeded end off the list collapse_edges[i + 1:] = [] for i, f in enumerate(faces): collapse_faces[i].init_from_face(f) # Strip the unneeded end off the list collapse_faces[i + 1:] = [] collapse_edges_dict = dict([(ced.key, ced) for ced in collapse_edges]) # Store verts edges. vert_ed_users = [[] for i in xrange(len(verts))] for ced in collapse_edges: vert_ed_users[ced.key[0]].append(ced) vert_ed_users[ced.key[1]].append(ced) # Store face users vert_face_users = [[] for i in xrange(len(verts))] # Have decieded not to use this. area is better. #face_perim= [0.0]* len(me.faces) for ii, cfa in enumerate(collapse_faces): for i, v1 in enumerate(cfa.verts): vert_face_users[v1.index].append((i, cfa)) # add the uv coord to the vert v2 = cfa.verts[i - 1] i1 = v1.index i2 = v2.index if i1 > i2: ced = collapse_edges_dict[i2, i1] else: ced = collapse_edges_dict[i1, i2] ced.faces.append(cfa) if DO_UV or DO_VCOL: # if the edge is flipped from its order in the face then we need to flip the order indicies. if cfa.verts[i] == ced.v1: i1, i2 = i, i - 1 else: i1, i2 = i - 1, i if DO_UV: ced.uv1.append(cfa.orig_uv[i1]) ced.uv2.append(cfa.orig_uv[i2]) if DO_VCOL: ced.col1.append(cfa.orig_col[i1]) ced.col2.append(cfa.orig_col[i2]) # PERIMITER #face_perim[ii]+= ced.length # How weight the verts by the area of their faces * the normal difference. # when the edge collapses, to vert weights are taken into account vert_weights = [0.5] * len(verts) for ii, vert_faces in enumerate(vert_face_users): for f in vert_faces: try: no_ang = (Ang(verts[ii].no, f[1].normal) / 180) * f[1].area except: no_ang = 1.0 vert_weights[ii] += no_ang # Use a vertex group as a weighting. if VGROUP_INF_REDUX != None: # Get Weights from a vgroup. """ vert_weights_map= [1.0] * len(verts) for i, wd in enumerate(vWeightDict): try: vert_weights_map[i]= 1+(wd[VGROUP_INF_REDUX] * VGROUP_INF_WEIGHT) except: pass """ vert_weights_map = [ 1 + (wl[VGROUP_INF_REDUX_INDEX] * VGROUP_INF_WEIGHT) for wl in vWeightList ] # BOUNDRY CHECKING AND WEIGHT EDGES. CAN REMOVE # Now we know how many faces link to an edge. lets get all the boundry verts if BOUNDRY_WEIGHT > 0: verts_boundry = [1] * len(verts) #for ed_idxs, faces_and_uvs in edge_faces_and_uvs.iteritems(): for ced in collapse_edges: if len(ced.faces) < 2: for key in ced.key: # only ever 2 key indicies. verts_boundry[key] = 2 for ced in collapse_edges: b1 = verts_boundry[ced.key[0]] b2 = verts_boundry[ced.key[1]] if b1 != b2: # Edge has 1 boundry and 1 non boundry vert. weight higher ced.collapse_weight = BOUNDRY_WEIGHT #elif b1==b2==2: # if both are on a seam then weigh half as bad. # ced.collapse_weight= ((BOUNDRY_WEIGHT-1)/2) +1 # weight the verts by their boundry status del b1 del b2 for ii, boundry in enumerate(verts_boundry): if boundry == 2: vert_weights[ii] *= BOUNDRY_WEIGHT vert_collapsed = verts_boundry del verts_boundry else: vert_collapsed = [1] * len(verts) # Best method, no quick hacks here, Correction. Should be the best but needs tweaks. def ed_set_collapse_error(ced): # Use the vertex weights to bias the new location. new_locs = ced.collapse_locations(vert_weights[ced.key[0]], vert_weights[ced.key[1]]) # Find the connecting faces of the 2 verts. i1, i2 = ced.key test_faces = set() for i in (i1, i2): # faster then LC's for f in vert_face_users[i]: test_faces.add(f[1].index) for f in ced.faces: test_faces.remove(f.index) v1_orig = Vector(ced.v1.co) v2_orig = Vector(ced.v2.co) def test_loc(new_loc): ''' Takes a location and tests the error without changing anything ''' new_weight = ced.collapse_weight ced.v1.co = ced.v2.co = new_loc new_nos = [faces[i].no for i in test_faces] # So we can compare the befire and after normals ced.v1.co = v1_orig ced.v2.co = v2_orig # now see how bad the normals are effected angle_diff = 1.0 for ii, i in enumerate( test_faces): # local face index, global face index cfa = collapse_faces[i] # this collapse face try: # can use perim, but area looks better. if FACE_AREA_WEIGHT: # Psudo code for wrighting # angle_diff= The before and after angle difference between the collapsed and un-collapsed face. # ... devide by 180 so the value will be between 0 and 1.0 # ... add 1 so we can use it as a multiplyer and not make the area have no eefect (below) # area_weight= The faces original area * the area weight # ... add 1.0 so a small area face dosent make the angle_diff have no effect. # # Now multiply - (angle_diff * area_weight) # ... The weight will be a minimum of 1.0 - we need to subtract this so more faces done give the collapse an uneven weighting. angle_diff += ( (1 + (Ang(cfa.normal, new_nos[ii]) / 180)) * (1 + (cfa.area * FACE_AREA_WEIGHT)) ) - 1 # 4 is how much to influence area else: angle_diff += (Ang(cfa.normal), new_nos[ii]) / 180 except: pass # This is very arbirary, feel free to modify try: no_ang = (Ang(ced.v1.no, ced.v2.no) / 180) + 1 except: no_ang = 2.0 # do *= because we face the boundry weight to initialize the weight. 1.0 default. new_weight *= ((no_ang * ced.length) * (1 - (1 / angle_diff)) ) # / max(len(test_faces), 1) return new_weight # End testloc # Test the collapse locatons collapse_loc_best = None collapse_weight_best = 1000000000 ii = 0 for collapse_loc in new_locs: if collapse_loc: # will only ever fail if smart loc is NAN test_weight = test_loc(collapse_loc) if test_weight < collapse_weight_best: iii = ii collapse_weight_best = test_weight collapse_loc_best = collapse_loc ii += 1 ced.collapse_loc = collapse_loc_best ced.collapse_weight = collapse_weight_best # are we using a weight map if VGROUP_INF_REDUX: v = vert_weights_map[i1] + vert_weights_map[i2] ced.collapse_weight *= v # End collapse Error # We can calculate the weights on __init__ but this is higher qualuity. for ced in collapse_edges: if ced.faces: # dont collapse faceless edges. ed_set_collapse_error(ced) # Wont use the function again. del ed_set_collapse_error # END BOUNDRY. Can remove # sort by collapse weight try: collapse_edges.sort(key=lambda ced: ced.collapse_weight ) # edges will be used for sorting except: collapse_edges.sort(lambda ced1, ced2: cmp(ced1.collapse_weight, ced2.collapse_weight) ) # edges will be used for sorting vert_collapsed = [0] * len(verts) collapse_edges_to_collapse = [] # Make a list of the first half edges we can collapse, # these will better edges to remove. collapse_count = 0 for ced in collapse_edges: if ced.faces: i1, i2 = ced.key # Use vert selections if vert_collapsed[i1] or vert_collapsed[i2]: pass else: # Now we know the verts havnyt been collapsed. vert_collapsed[i2] = vert_collapsed[ i1] = 1 # Dont collapse again. collapse_count += 1 collapse_edges_to_collapse.append(ced) # Get a subset of the entire list- the first "collapse_per_pass", that are best to collapse. if collapse_count > 4: collapse_count = int(collapse_count * collapse_per_pass) else: collapse_count = len(collapse_edges) # We know edge_container_list_collapse can be removed. for ced in collapse_edges_to_collapse: """# DEBUG! if DEBUG: if DOUBLE_CHECK[ced.v1.index] or\ DOUBLE_CHECK[ced.v2.index]: raise 'Error' else: DOUBLE_CHECK[ced.v1.index]=1 DOUBLE_CHECK[ced.v2.index]=1 tmp= (ced.v1.co+ced.v2.co)*0.5 Blender.Window.SetCursorPos(tmp.x, tmp.y, tmp.z) Blender.Window.RedrawAll() """ # Chech if we have collapsed our quota. collapse_count -= 1 if not collapse_count: break current_face_count -= len(ced.faces) # Find and assign the real weights based on collapse loc. # Find the weights from the collapse error if DO_WEIGHTS or DO_UV or DO_VCOL: i1, i2 = ced.key # Dont use these weights since they may not have been used to make the collapse loc. #w1= vert_weights[i1] #w2= vert_weights[i2] w1 = (ced.v2.co - ced.collapse_loc).length w2 = (ced.v1.co - ced.collapse_loc).length # Normalize weights wscale = w1 + w2 if not wscale: # no scale? w1 = w2 = 0.5 else: w1 /= wscale w2 /= wscale # Interpolate the bone weights. if DO_WEIGHTS: # add verts vgroups to eachother wl1 = vWeightList[i1] # v1 weight dict wl2 = vWeightList[i2] # v2 weight dict for group_index in xrange(len_vgroups): wl1[group_index] = wl2[group_index] = ( wl1[group_index] * w1) + (wl2[group_index] * w2) # Done finding weights. if DO_UV or DO_VCOL: # Handel UV's and vert Colors! for v, my_weight, other_weight, edge_my_uvs, edge_other_uvs, edge_my_cols, edge_other_cols in (\ (ced.v1, w1, w2, ced.uv1, ced.uv2, ced.col1, ced.col2),\ (ced.v2, w2, w1, ced.uv2, ced.uv1, ced.col2, ced.col1)\ ): uvs_mixed = [ uv_key_mix(edge_my_uvs[iii], edge_other_uvs[iii], my_weight, other_weight) for iii in xrange(len(edge_my_uvs)) ] cols_mixed = [ col_key_mix(edge_my_cols[iii], edge_other_cols[iii], my_weight, other_weight) for iii in xrange(len(edge_my_cols)) ] for face_vert_index, cfa in vert_face_users[v.index]: if len( cfa.verts ) == 3 and cfa not in ced.faces: # if the face is apart of this edge then dont bother finding the uvs since the face will be removed anyway. if DO_UV: # UV COORDS uvk = cfa.orig_uv[face_vert_index] try: tex_index = edge_my_uvs.index(uvk) except: tex_index = None """ # DEBUG! if DEBUG: print 'not found', uvk, 'in', edge_my_uvs, 'ed index', ii, '\nwhat about', edge_other_uvs """ if tex_index != None: # This face uses a uv in the collapsing face. - do a merge other_uv = edge_other_uvs[tex_index] uv_vec = cfa.uv[face_vert_index] uv_vec.x, uv_vec.y = uvs_mixed[ tex_index] # TEXFACE COLORS if DO_VCOL: colk = cfa.orig_col[face_vert_index] try: tex_index = edge_my_cols.index(colk) except: pass if tex_index != None: other_col = edge_other_cols[tex_index] col_ob = cfa.col[face_vert_index] col_ob.r, col_ob.g, col_ob.b = cols_mixed[ tex_index] # DEBUG! if DEBUG: rd() # Execute the collapse ced.v1.sel = ced.v2.sel = True # Select so remove doubles removed the edges and faces that use it ced.v1.co = ced.v2.co = ced.collapse_loc # DEBUG! if DEBUG: rd() if current_face_count <= target_face_count: break # Copy weights back to the mesh before we remove doubles. if DO_WEIGHTS: #BPyMesh.dict2MeshWeight(me, groupNames, vWeightDict) BPyMesh.list2MeshWeight(me, groupNames, vWeightList) #doubles= me.remDoubles(0.0001) doubles = 0 current_face_count = len(me.faces) if current_face_count <= target_face_count or not doubles: # not doubles shoule never happen. break me.update() Blender.Mesh.Mode(OLD_MESH_MODE)
def redux(ob, REDUX=0.5, BOUNDRY_WEIGHT=2.0, REMOVE_DOUBLES=False, FACE_AREA_WEIGHT=1.0, FACE_TRIANGULATE=True, DO_UV=True, DO_VCOL=True, DO_WEIGHTS=True, VGROUP_INF_REDUX= None, VGROUP_INF_WEIGHT=0.5): """ BOUNDRY_WEIGHT - 0 is no boundry weighting. 2.0 will make them twice as unlikely to collapse. FACE_AREA_WEIGHT - 0 is no weight. 1 is normal, 2.0 is higher. """ if REDUX<0 or REDUX>1.0: raise 'Error, factor must be between 0 and 1.0' elif not set: raise 'Error, this function requires Python 2.4 or a full install of Python 2.3' BOUNDRY_WEIGHT= 1+BOUNDRY_WEIGHT """ # DEBUG! if Blender.Get('rt') == 1000: DEBUG=True else: DEBUG= False """ me= ob.getData(mesh=1) me.hide= False # unhide all data,. if len(me.faces)<5: return if FACE_TRIANGULATE or REMOVE_DOUBLES: me.sel= True if FACE_TRIANGULATE: me.quadToTriangle() if REMOVE_DOUBLES: me.remDoubles(0.0001) vgroups= me.getVertGroupNames() if not me.getVertGroupNames(): DO_WEIGHTS= False if (VGROUP_INF_REDUX!= None and VGROUP_INF_REDUX not in vgroups) or\ VGROUP_INF_WEIGHT==0.0: VGROUP_INF_REDUX= None try: VGROUP_INF_REDUX_INDEX= vgroups.index(VGROUP_INF_REDUX) except: VGROUP_INF_REDUX_INDEX= -1 # del vgroups len_vgroups= len(vgroups) OLD_MESH_MODE= Blender.Mesh.Mode() Blender.Mesh.Mode(Blender.Mesh.SelectModes.VERTEX) if DO_UV and not me.faceUV: DO_UV= False if DO_VCOL and not me.vertexColors: DO_VCOL = False current_face_count= len(me.faces) target_face_count= int(current_face_count * REDUX) # % of the collapseable faces to collapse per pass. #collapse_per_pass= 0.333 # between 0.1 - lots of small nibbles, slow but high q. and 0.9 - big passes and faster. collapse_per_pass= 0.333 # between 0.1 - lots of small nibbles, slow but high q. and 0.9 - big passes and faster. """# DEBUG! if DEBUG: COUNT= [0] def rd(): if COUNT[0]< 330: COUNT[0]+=1 return me.update() Blender.Window.RedrawAll() print 'Press key for next, count "%s"' % COUNT[0] try: input() except KeyboardInterrupt: raise "Error" except: pass COUNT[0]+=1 """ class collapseEdge(object): __slots__ = 'length', 'key', 'faces', 'collapse_loc', 'v1', 'v2','uv1', 'uv2', 'col1', 'col2', 'collapse_weight' def __init__(self, ed): self.init_from_edge(ed) # So we can re-use the classes without using more memory. def init_from_edge(self, ed): self.key= ed.key self.length= ed.length self.faces= [] self.v1= ed.v1 self.v2= ed.v2 if DO_UV or DO_VCOL: self.uv1= [] self.uv2= [] self.col1= [] self.col2= [] # self.collapse_loc= None # new collapse location. # Basic weighting. #self.collapse_weight= self.length * (1+ ((ed.v1.no-ed.v2.no).length**2)) self.collapse_weight= 1.0 def collapse_locations(self, w1, w2): ''' Generate a smart location for this edge to collapse to w1 and w2 are vertex location bias ''' v1co= self.v1.co v2co= self.v2.co v1no= self.v1.no v2no= self.v2.no # Basic operation, works fine but not as good as predicting the best place. #between= ((v1co*w1) + (v2co*w2)) #self.collapse_loc= between # normalize the weights of each vert - se we can use them as scalers. wscale= w1+w2 if not wscale: # no scale? w1=w2= 0.5 else: w1/=wscale w2/=wscale length= self.length between= MidpointVecs(v1co, v2co) # Collapse # new_location = between # Replace tricky code below. this code predicts the best collapse location. # Make lines at right angles to the normals- these 2 lines will intersect and be # the point of collapsing. # Enlarge so we know they intersect: self.length*2 cv1= v1no.cross(v1no.cross(v1co-v2co)) cv2= v2no.cross(v2no.cross(v2co-v1co)) # Scale to be less then the edge lengths. cv2.length = cv1.length = 1 cv1 = cv1 * (length* 0.4) cv2 = cv2 * (length* 0.4) smart_offset_loc= between + (cv1 + cv2) # Now we need to blend between smart_offset_loc and w1/w2 # you see were blending between a vert and the edges midpoint, so we cant use a normal weighted blend. if w1 > 0.5: # between v1 and smart_offset_loc #self.collapse_loc= v1co*(w2+0.5) + smart_offset_loc*(w1-0.5) w2*=2 w1= 1-w2 new_loc_smart= v1co*w1 + smart_offset_loc*w2 else: # w between v2 and smart_offset_loc w1*=2 w2= 1-w1 new_loc_smart= v2co*w2 + smart_offset_loc*w1 if new_loc_smart.x != new_loc_smart.x: # NAN LOCATION, revert to between new_loc_smart= None return new_loc_smart, between, v1co*0.99999 + v2co*0.00001, v1co*0.00001 + v2co*0.99999 class collapseFace(object): __slots__ = 'verts', 'normal', 'area', 'index', 'orig_uv', 'orig_col', 'uv', 'col' # , 'collapse_edge_count' def __init__(self, f): self.init_from_face(f) def init_from_face(self, f): self.verts= f.v self.normal= f.no self.area= f.area self.index= f.index if DO_UV: self.orig_uv= [uv_key(uv) for uv in f.uv] self.uv= f.uv if DO_VCOL: self.orig_col= [col_key(col) for col in f.col] self.col= f.col collapse_edges= collapse_faces= None # So meshCalcNormals can avoid making a new list all the time. reuse_vertNormals= [ Vector() for v in xrange(len(me.verts)) ] while target_face_count <= len(me.faces): BPyMesh.meshCalcNormals(me, reuse_vertNormals) if DO_WEIGHTS: #groupNames, vWeightDict= BPyMesh.meshWeight2Dict(me) groupNames, vWeightList= BPyMesh.meshWeight2List(me) # THIS CRASHES? Not anymore. verts= list(me.verts) edges= list(me.edges) faces= list(me.faces) # THIS WORKS #verts= me.verts #edges= me.edges #faces= me.faces # if DEBUG: DOUBLE_CHECK= [0]*len(verts) me.sel= False if not collapse_faces: # Initialize the list. collapse_faces= [collapseFace(f) for f in faces] collapse_edges= [collapseEdge(ed) for ed in edges] else: for i, ed in enumerate(edges): collapse_edges[i].init_from_edge(ed) # Strip the unneeded end off the list collapse_edges[i+1:]= [] for i, f in enumerate(faces): collapse_faces[i].init_from_face(f) # Strip the unneeded end off the list collapse_faces[i+1:]= [] collapse_edges_dict= dict( [(ced.key, ced) for ced in collapse_edges] ) # Store verts edges. vert_ed_users= [[] for i in xrange(len(verts))] for ced in collapse_edges: vert_ed_users[ced.key[0]].append(ced) vert_ed_users[ced.key[1]].append(ced) # Store face users vert_face_users= [[] for i in xrange(len(verts))] # Have decieded not to use this. area is better. #face_perim= [0.0]* len(me.faces) for ii, cfa in enumerate(collapse_faces): for i, v1 in enumerate(cfa.verts): vert_face_users[v1.index].append( (i,cfa) ) # add the uv coord to the vert v2 = cfa.verts[i-1] i1= v1.index i2= v2.index if i1>i2: ced= collapse_edges_dict[i2,i1] else: ced= collapse_edges_dict[i1,i2] ced.faces.append(cfa) if DO_UV or DO_VCOL: # if the edge is flipped from its order in the face then we need to flip the order indicies. if cfa.verts[i]==ced.v1: i1,i2 = i, i-1 else: i1,i2 = i-1, i if DO_UV: ced.uv1.append( cfa.orig_uv[i1] ) ced.uv2.append( cfa.orig_uv[i2] ) if DO_VCOL: ced.col1.append( cfa.orig_col[i1] ) ced.col2.append( cfa.orig_col[i2] ) # PERIMITER #face_perim[ii]+= ced.length # How weight the verts by the area of their faces * the normal difference. # when the edge collapses, to vert weights are taken into account vert_weights= [0.5] * len(verts) for ii, vert_faces in enumerate(vert_face_users): for f in vert_faces: try: no_ang= (Ang(verts[ii].no, f[1].normal)/180) * f[1].area except: no_ang= 1.0 vert_weights[ii] += no_ang # Use a vertex group as a weighting. if VGROUP_INF_REDUX!=None: # Get Weights from a vgroup. """ vert_weights_map= [1.0] * len(verts) for i, wd in enumerate(vWeightDict): try: vert_weights_map[i]= 1+(wd[VGROUP_INF_REDUX] * VGROUP_INF_WEIGHT) except: pass """ vert_weights_map= [1+(wl[VGROUP_INF_REDUX_INDEX]*VGROUP_INF_WEIGHT) for wl in vWeightList ] # BOUNDRY CHECKING AND WEIGHT EDGES. CAN REMOVE # Now we know how many faces link to an edge. lets get all the boundry verts if BOUNDRY_WEIGHT > 0: verts_boundry= [1] * len(verts) #for ed_idxs, faces_and_uvs in edge_faces_and_uvs.iteritems(): for ced in collapse_edges: if len(ced.faces) < 2: for key in ced.key: # only ever 2 key indicies. verts_boundry[key]= 2 for ced in collapse_edges: b1= verts_boundry[ced.key[0]] b2= verts_boundry[ced.key[1]] if b1 != b2: # Edge has 1 boundry and 1 non boundry vert. weight higher ced.collapse_weight= BOUNDRY_WEIGHT #elif b1==b2==2: # if both are on a seam then weigh half as bad. # ced.collapse_weight= ((BOUNDRY_WEIGHT-1)/2) +1 # weight the verts by their boundry status del b1 del b2 for ii, boundry in enumerate(verts_boundry): if boundry==2: vert_weights[ii] *= BOUNDRY_WEIGHT vert_collapsed= verts_boundry del verts_boundry else: vert_collapsed= [1] * len(verts) # Best method, no quick hacks here, Correction. Should be the best but needs tweaks. def ed_set_collapse_error(ced): # Use the vertex weights to bias the new location. new_locs= ced.collapse_locations(vert_weights[ced.key[0]], vert_weights[ced.key[1]]) # Find the connecting faces of the 2 verts. i1, i2= ced.key test_faces= set() for i in (i1,i2): # faster then LC's for f in vert_face_users[i]: test_faces.add(f[1].index) for f in ced.faces: test_faces.remove(f.index) v1_orig= Vector(ced.v1.co) v2_orig= Vector(ced.v2.co) def test_loc(new_loc): ''' Takes a location and tests the error without changing anything ''' new_weight= ced.collapse_weight ced.v1.co= ced.v2.co= new_loc new_nos= [faces[i].no for i in test_faces] # So we can compare the befire and after normals ced.v1.co= v1_orig ced.v2.co= v2_orig # now see how bad the normals are effected angle_diff= 1.0 for ii, i in enumerate(test_faces): # local face index, global face index cfa= collapse_faces[i] # this collapse face try: # can use perim, but area looks better. if FACE_AREA_WEIGHT: # Psudo code for wrighting # angle_diff= The before and after angle difference between the collapsed and un-collapsed face. # ... devide by 180 so the value will be between 0 and 1.0 # ... add 1 so we can use it as a multiplyer and not make the area have no eefect (below) # area_weight= The faces original area * the area weight # ... add 1.0 so a small area face dosent make the angle_diff have no effect. # # Now multiply - (angle_diff * area_weight) # ... The weight will be a minimum of 1.0 - we need to subtract this so more faces done give the collapse an uneven weighting. angle_diff+= ((1+(Ang(cfa.normal, new_nos[ii])/180)) * (1+(cfa.area * FACE_AREA_WEIGHT))) -1 # 4 is how much to influence area else: angle_diff+= (Ang(cfa.normal), new_nos[ii])/180 except: pass # This is very arbirary, feel free to modify try: no_ang= (Ang(ced.v1.no, ced.v2.no)/180) + 1 except: no_ang= 2.0 # do *= because we face the boundry weight to initialize the weight. 1.0 default. new_weight *= ((no_ang * ced.length) * (1-(1/angle_diff)))# / max(len(test_faces), 1) return new_weight # End testloc # Test the collapse locatons collapse_loc_best= None collapse_weight_best= 1000000000 ii= 0 for collapse_loc in new_locs: if collapse_loc: # will only ever fail if smart loc is NAN test_weight= test_loc(collapse_loc) if test_weight < collapse_weight_best: iii= ii collapse_weight_best = test_weight collapse_loc_best= collapse_loc ii+=1 ced.collapse_loc= collapse_loc_best ced.collapse_weight= collapse_weight_best # are we using a weight map if VGROUP_INF_REDUX: v= vert_weights_map[i1]+vert_weights_map[i2] ced.collapse_weight*= v # End collapse Error # We can calculate the weights on __init__ but this is higher qualuity. for ced in collapse_edges: if ced.faces: # dont collapse faceless edges. ed_set_collapse_error(ced) # Wont use the function again. del ed_set_collapse_error # END BOUNDRY. Can remove # sort by collapse weight try: collapse_edges.sort(key = lambda ced: ced.collapse_weight) # edges will be used for sorting except: collapse_edges.sort(lambda ced1, ced2: cmp(ced1.collapse_weight, ced2.collapse_weight)) # edges will be used for sorting vert_collapsed= [0]*len(verts) collapse_edges_to_collapse= [] # Make a list of the first half edges we can collapse, # these will better edges to remove. collapse_count=0 for ced in collapse_edges: if ced.faces: i1, i2= ced.key # Use vert selections if vert_collapsed[i1] or vert_collapsed[i2]: pass else: # Now we know the verts havnyt been collapsed. vert_collapsed[i2]= vert_collapsed[i1]= 1 # Dont collapse again. collapse_count+=1 collapse_edges_to_collapse.append(ced) # Get a subset of the entire list- the first "collapse_per_pass", that are best to collapse. if collapse_count > 4: collapse_count = int(collapse_count*collapse_per_pass) else: collapse_count = len(collapse_edges) # We know edge_container_list_collapse can be removed. for ced in collapse_edges_to_collapse: """# DEBUG! if DEBUG: if DOUBLE_CHECK[ced.v1.index] or\ DOUBLE_CHECK[ced.v2.index]: raise 'Error' else: DOUBLE_CHECK[ced.v1.index]=1 DOUBLE_CHECK[ced.v2.index]=1 tmp= (ced.v1.co+ced.v2.co)*0.5 Blender.Window.SetCursorPos(tmp.x, tmp.y, tmp.z) Blender.Window.RedrawAll() """ # Chech if we have collapsed our quota. collapse_count-=1 if not collapse_count: break current_face_count -= len(ced.faces) # Find and assign the real weights based on collapse loc. # Find the weights from the collapse error if DO_WEIGHTS or DO_UV or DO_VCOL: i1, i2= ced.key # Dont use these weights since they may not have been used to make the collapse loc. #w1= vert_weights[i1] #w2= vert_weights[i2] w1= (ced.v2.co-ced.collapse_loc).length w2= (ced.v1.co-ced.collapse_loc).length # Normalize weights wscale= w1+w2 if not wscale: # no scale? w1=w2= 0.5 else: w1/= wscale w2/= wscale # Interpolate the bone weights. if DO_WEIGHTS: # add verts vgroups to eachother wl1= vWeightList[i1] # v1 weight dict wl2= vWeightList[i2] # v2 weight dict for group_index in xrange(len_vgroups): wl1[group_index]= wl2[group_index]= (wl1[group_index]*w1) + (wl2[group_index]*w2) # Done finding weights. if DO_UV or DO_VCOL: # Handel UV's and vert Colors! for v, my_weight, other_weight, edge_my_uvs, edge_other_uvs, edge_my_cols, edge_other_cols in (\ (ced.v1, w1, w2, ced.uv1, ced.uv2, ced.col1, ced.col2),\ (ced.v2, w2, w1, ced.uv2, ced.uv1, ced.col2, ced.col1)\ ): uvs_mixed= [ uv_key_mix(edge_my_uvs[iii], edge_other_uvs[iii], my_weight, other_weight) for iii in xrange(len(edge_my_uvs)) ] cols_mixed= [ col_key_mix(edge_my_cols[iii], edge_other_cols[iii], my_weight, other_weight) for iii in xrange(len(edge_my_cols)) ] for face_vert_index, cfa in vert_face_users[v.index]: if len(cfa.verts)==3 and cfa not in ced.faces: # if the face is apart of this edge then dont bother finding the uvs since the face will be removed anyway. if DO_UV: # UV COORDS uvk= cfa.orig_uv[face_vert_index] try: tex_index= edge_my_uvs.index(uvk) except: tex_index= None """ # DEBUG! if DEBUG: print 'not found', uvk, 'in', edge_my_uvs, 'ed index', ii, '\nwhat about', edge_other_uvs """ if tex_index != None: # This face uses a uv in the collapsing face. - do a merge other_uv= edge_other_uvs[tex_index] uv_vec= cfa.uv[face_vert_index] uv_vec.x, uv_vec.y= uvs_mixed[tex_index] # TEXFACE COLORS if DO_VCOL: colk= cfa.orig_col[face_vert_index] try: tex_index= edge_my_cols.index(colk) except: pass if tex_index != None: other_col= edge_other_cols[tex_index] col_ob= cfa.col[face_vert_index] col_ob.r, col_ob.g, col_ob.b= cols_mixed[tex_index] # DEBUG! if DEBUG: rd() # Execute the collapse ced.v1.sel= ced.v2.sel= True # Select so remove doubles removed the edges and faces that use it ced.v1.co= ced.v2.co= ced.collapse_loc # DEBUG! if DEBUG: rd() if current_face_count <= target_face_count: break # Copy weights back to the mesh before we remove doubles. if DO_WEIGHTS: #BPyMesh.dict2MeshWeight(me, groupNames, vWeightDict) BPyMesh.list2MeshWeight(me, groupNames, vWeightList) doubles= me.remDoubles(0.0001) current_face_count= len(me.faces) if current_face_count <= target_face_count or not doubles: # not doubles shoule never happen. break me.update() Blender.Mesh.Mode(OLD_MESH_MODE)
def env_from_group(ob_act, grp, PREF_UPDATE_ACT=True): me = ob_act.getData(mesh=1) if PREF_UPDATE_ACT: act_group = me.activeGroup if act_group == None: Draw.PupMenu('Error%t|No active vertex group.') return try: ob = Object.Get(act_group) except: Draw.PupMenu('Error%t|No object named "'+ act_group +'".') return group_isect = intersection_data(ob) else: # get intersection data # group_isect_data = [intersection_data(ob) for ob in group.objects] group_isect_data = [] for ob in grp.objects: if ob != ob_act: # in case we're in the group. gid = intersection_data(ob) if gid[1]: # has some triangles? group_isect_data.append( gid ) # we only need 1 for the active group if PREF_UPDATE_ACT: break # sort by name group_isect_data.sort() if PREF_UPDATE_ACT: group_names, vweight_list = BPyMesh.meshWeight2List(me) group_index = group_names.index(act_group) else: group_names = [gid[0] for gid in group_isect_data] vweight_list= [[0.0]* len(group_names) for i in xrange(len(me.verts))] ob_act_mat = ob_act.matrixWorld for vi, v in enumerate(me.verts): # Get all the groups for this vert co = v.co * ob_act_mat if PREF_UPDATE_ACT: # only update existing if point_in_data(co, group_isect): w = 1.0 else: w = 0.0 vweight_list[vi][group_index] = w else: # generate new vgroup weights. for group_index, group_isect in enumerate(group_isect_data): if point_in_data(co, group_isect): vweight_list[vi][group_index] = 1.0 BPyMesh.list2MeshWeight(me, group_names, vweight_list)
def env_from_group(ob_act, grp, PREF_UPDATE_ACT=True): me = ob_act.getData(mesh=1) if PREF_UPDATE_ACT: act_group = me.activeGroup if act_group == None: Draw.PupMenu('Error%t|No active vertex group.') return try: ob = Object.Get(act_group) except: Draw.PupMenu('Error%t|No object named "' + act_group + '".') return group_isect = intersection_data(ob) else: # get intersection data # group_isect_data = [intersection_data(ob) for ob in group.objects] group_isect_data = [] for ob in grp.objects: if ob != ob_act: # in case we're in the group. gid = intersection_data(ob) if gid[1]: # has some triangles? group_isect_data.append(gid) # we only need 1 for the active group if PREF_UPDATE_ACT: break # sort by name group_isect_data.sort() if PREF_UPDATE_ACT: group_names, vweight_list = BPyMesh.meshWeight2List(me) group_index = group_names.index(act_group) else: group_names = [gid[0] for gid in group_isect_data] vweight_list = [[0.0] * len(group_names) for i in xrange(len(me.verts))] ob_act_mat = ob_act.matrixWorld for vi, v in enumerate(me.verts): # Get all the groups for this vert co = v.co * ob_act_mat if PREF_UPDATE_ACT: # only update existing if point_in_data(co, group_isect): w = 1.0 else: w = 0.0 vweight_list[vi][group_index] = w else: # generate new vgroup weights. for group_index, group_isect in enumerate(group_isect_data): if point_in_data(co, group_isect): vweight_list[vi][group_index] = 1.0 BPyMesh.list2MeshWeight(me, group_names, vweight_list)