Remove Doubles script.

I want to be able to run Remove Doubles on a mesh from with in a script WITHOUT going into Edit mode.

I know that I can select the mesh and do a

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles()
bpy.ops.object.mode_set(mode='OBJECT')

but this has the disadvantage of not being able to interactively change the properties.

I know that Michel J. Anders (varkenvarken) has the same problem as I do with his add_mesh_gear.py

Before I write my own remove doubles function in python. Has any one already done this?

Quick exemple :


print('verts')
for i in ob.data.verts:
    print(i.co)
print('faces')
for i in ob.data.faces:
    print(tuple(i.verts))

new_verts = []
new_faces = []
dict_verts = {}

for face in ob.data.faces:
    new_face = []
    for vert in face.verts:
        co = tuple(ob.data.verts[vert].co)
        if co not in dict_verts:
            dict_verts[co] = len(dict_verts)
            new_verts.append(co)
        new_face.append(dict_verts[co])
    new_faces.append(new_face)

print('verts')
for i in new_verts:
    print(i)
print('faces')
for i in new_faces:
    print(i)

Note that you can really improve it with dict.setdefault (the code may be writed in only 3 lines and will be quicker)

how can i removed the double scripts i am rid out of it, please tell me more about it I have listen a lot about, it how can I get them, tell me some useful sites in this regards.

Thanks Guillaum.

Your example put me on the right path.
I have made a few changes and put it in a test script for Blender 2.5.

It doesn’t have the range function so it will only remove verts that are in exactly the same position but this is fine for what I want.
It seems to be fast enough for my needs.

I hope this is useful to other people.


# -------------------------------------------------------------------------- 
# Test Remove Doubles.py 
# -------------------------------------------------------------------------- 
# ***** BEGIN GPL LICENSE BLOCK ***** 
# 
# Copyright (C) 2010: Aaron Keith
# 
# 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 ***** 
# -------------------------------------------------------------------------- 


import bpy
 
# next two utility functions are stolen from import_obj.py

def unpack_list(list_of_tuples):
    l = []
    for t in list_of_tuples:
        l.extend(t)
    return l

def unpack_face_list(list_of_tuples):
    l = []
    for t in list_of_tuples:
        face = [i for i in t]

        if len(face) != 3 and len(face) != 4:
            raise RuntimeError("{0} vertices in face.".format(len(face)))
        
        # rotate indices if the 4th is 0
        if len(face) == 4 and face[3] == 0:
            face = [face[3], face[0], face[1], face[2]]

        if len(face) == 3:
            face.append(0)
            
        l.extend(face)

    return l



#Remove Doubles takes a list on Verts and a list of Faces and 
#removes the doubles, much like Blender does in edit mode.  
#It doesn’t have the range function so it will only remove 
#verts that are in exactly the same position.  The function 
#is useful because you can perform a “Remove Doubles” with out 
#having to enter Edit Mode. Having to enter edit mode has the 
#disadvantage of not being able to interactively change the properties.

def RemoveDoubles(verts,faces):
            
#        print('verts')
#        for i in verts:
#            print(i)
#        print('faces')
#        for i in faces:
#            print(i)

        
        new_verts = []
        new_faces = []
        dict_verts = {}

        for face in faces:
            new_face = []
            for face in face:
                co = tuple(verts[face])
                if co not in dict_verts:
                    dict_verts[co] = len(dict_verts)
                    new_verts.append(co)
                if dict_verts[co] not in new_face: 
                    new_face.append(dict_verts[co])
            if len(new_face) == 3 or len(new_face) == 4:
                new_faces.append(new_face)

        
#        print('new verts')
#        for i in new_verts:
#            print(i)
#        print('new faces')
#        for i in new_faces:
#            print(i)
        return new_verts,new_faces 

 
def Create_Mesh(context):
    
#    1------2
#    |\   / |    
#    | \ /  |  
#    |  4   |
#    | /  \ |
#    |/    \|
#    0------3
#    
#    0 =  [0.0, 0.0, 0.0]
#    1 =  [0.0, 1.0, 0.0] 
#    2 =  [1.0, 1.0, 0.0]
#    3 =  [1.0, 0.0, 0.0]
#    4 =  [0.5,0.5,1.0]   
     
  
    verts = []
    faces = []
    
    verts += [[0.0, 0.0, 0.0] , [0.0, 1.0, 0.0],[1.0, 1.0, 0.0],[1.0, 0.0, 0.0]]
    faces += [[0, 1, 2, 3]] #0 1 2 3
    
    offset =  len(verts)
    verts += [[0.0, 0.0, 0.0],[1.0, 0.0, 0.0],[0.5,0.5,1.0]]
    faces += [[offset+0,offset+1,offset+2]] #0 3 4
    
    offset =  len(verts)
    verts += [[1.0, 0.0, 0.0],[1.0, 1.0, 0.0],[0.5,0.5,1.0]]
    faces += [[offset+0,offset+1,offset+2]] # 3 2 4

    offset =  len(verts)
    verts += [[1.0, 1.0, 0.0],[0.0, 1.0, 0.0],[0.5,0.5,1.0]]
    faces += [[offset+0,offset+1,offset+2]] # 2 1 4 
    
    offset =  len(verts)
    verts += [[0.0, 1.0, 0.0],[0.0, 0.0, 0.0],[0.5,0.5,1.0],[0.5,0.5,1.0]]  #4 points with 2 of them are the same
    faces += [[offset+0,offset+1,offset+2,offset+3]] # 1 0 4 4
    
    offset =  len(verts)
    verts += [[0.5,0.5,1.0],[0.5,0.5,1.0],[0.5,0.5,1.0]]  #3 points all of them the same
    faces += [[offset+0,offset+1,offset+2]] # 4 4 4
    
    offset =  len(verts)
    verts += [[0.0, 0.0, 0.0],[0.0, 0.0, 0.0],[0.5,0.5,1.0],[0.5,0.5,1.0]]  #4 points,  2 pairs are the same
    faces += [[offset+0,offset+1,offset+2,offset+3]] # 0 0  4 4
    
    
    
    print('Mesh verts')
    for i in verts:
        print(i)
    print('Mesh faces')
    for i in faces:
        print(i)
 
        
    verts, faces = RemoveDoubles(verts,faces)

    print('Mesh verts after Remove Doubles')
    for i in verts:
        print(i)
    print('Mesh faces after Remove Doubles')
    for i in faces:
        print(i)
    
    mesh = bpy.data.meshes.new("Test")
    mesh.add_geometry((len(verts)), 0, int(len(faces)))
    
    mesh.verts.foreach_set("co", unpack_list(verts))
    mesh.faces.foreach_set("verts_raw", unpack_face_list(faces))
    scene = context.scene
    
    # ugh
    for ob in scene.objects:
        ob.selected = False

    mesh.update()
    ob_new = bpy.data.objects.new("Test_ob", mesh)
    scene.objects.link(ob_new)
    ob_new.selected = True
    obj_act = scene.objects.active




class ObjectButtonsPanel(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_label = "Remove Doubles Test V1.00"
    

    def draw_header(self, context):
        layout = self.layout
        layout.label(text="", icon='PLUGIN')


    def draw(self,context):
        layout = self.layout
        row = layout.row()
        row.operator("custom.Create_Button")
        




class CUSTOM_OT_Create_Button(bpy.types.Operator):
    bl_idname = "CUSTOM_OT_Create_Button"
    bl_label = "Create"
    __doc__ = "Create Test"
    
    
    def invoke(self, context, event):
        Create_Mesh(context)
        return('FINISHED')



def register():
    bpy.types.register(ObjectButtonsPanel)
    bpy.types.register(CUSTOM_OT_Create_Button)
    
def unregister():
    bpy.types.unregister(ObjectButtonsPanel)
    bpy.types.unregister(CUSTOM_OT_Create_Button)    
    
    

 
if __name__ == "__main__":
    register()
    


I think a better solution could be using the operator

 
#remove doubles    
    bm = bmesh.new()   # create an empty BMesh
    bm.from_mesh(me)   # fill it in from a Mesh
    
    bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.1)
    
    # Finish up, write the bmesh back to the mesh
    bm.to_mesh(me)
    bm.free()  # free and prevent further access
    
    me.validate()    
    me.update()
1 Like

Thnx,
This worked for me!
I’m working on a addin to import Civil3d Surfaces from a LandXML.