Following a BIG CLUE given by @rob I've slapped together a script.
Give each mesh object in selection a key faces_edges_verts
, for instance the default cube would have a key "6_12_8", 6 faces, 12 edges, 8 verts. Then check whether the coordinates of global bounding box, of any other objects that have same counts, match.
If they do select those objects to mark them as possible dupes.
To run suggest: Select all in scene, run script from text editor, and see if those objects that remain selected are dupes.... or close enough to... and then if all good hit x
import bpy
from mathutils import Vector
context = bpy.context
scene = context.scene
TOL = 1.0e-4 # reduced tolerance.
# routine to text matches on bound box.
def test(ob1, ob2):
# test if ob1 and ob2 which have same face/edge/vert count are same
bbox1 = [ob1.matrix_world * Vector(b) for b in ob1.bound_box]
bbox2 = [ob2.matrix_world * Vector(b) for b in ob2.bound_box]
diffs = [v2 - v1 for v1, v2 in zip(bbox1, bbox2)]
return all(d.length < TOL for d in diffs)
#return sum(diffs, Vector()).length < TOL
obs = {}
keys = []
for ob in context.selected_objects:
ob.select = False
if ob.type != 'MESH':
continue
# make a key from faces, edges, verts
mesh = ob.data
key = "%d_%d_%d" % (len(mesh.polygons), len(mesh.edges), len(mesh.vertices))
obs[ob.name] = key
keys.append(key)
possible_dupes = set([x for x in keys if keys.count(x) > 1])
print("-" * 40)
while possible_dupes:
p = possible_dupes.pop()
dupes = sorted(name for name, value in obs.items() if value == p)
while dupes:
original = dupes.pop(0)
original_ob = scene.objects.get(original)
for dupe in dupes:
dupe_ob = scene.objects.get(dupe)
if dupe_ob.select:
continue
if test(original_ob, dupe_ob):
dupe_ob.select = True
scene.objects.active = dupe_ob
print("possible dupe: ", dupe, " of original: ", original)
Result on your file. Dupe objects moved to layer 3 on left. Reduced file size from 91 to 47 meg on removal of dupes.

Updated for version 2.8
Some minor changes for the new API, notably ob.select_set()
and ob.select_get()
for setting and getting selection, and `@' for matrix / vector multiplication
import bpy
from mathutils import Vector
context = bpy.context
scene = context.scene
view_layer = context.view_layer
TOL = 1.0e-3 # reduced tolerance.
# routine to text matches on bound box.
def test(ob1, ob2):
# test if ob1 and ob2 which have same face/edge/vert count are same
bbox1 = [ob1.matrix_world @ Vector(b) for b in ob1.bound_box]
bbox2 = [ob2.matrix_world @ Vector(b) for b in ob2.bound_box]
diffs = [v1 - v2 for v1, v2 in zip(bbox1, bbox2)]
return all(d.length < TOL for d in diffs)
#return sum(diffs, Vector()).length < TOL
obs = {}
keys = []
for ob in context.selected_objects:
ob.select_set(False)
if ob.type != 'MESH':
continue
# make a key from faces, edges, verts
mesh = ob.data
key = "%d_%d_%d" % (len(mesh.polygons), len(mesh.edges), len(mesh.vertices))
obs[ob.name] = key
keys.append(key)
possible_dupes = set([x for x in keys if keys.count(x) > 1])
print("-" * 50)
while possible_dupes:
p = possible_dupes.pop()
dupes = sorted(name for name, value in obs.items() if value == p)
while dupes:
original_ob = scene.objects.get(dupes.pop(0))
for dupe in dupes:
dupe_ob = scene.objects.get(dupe)
if dupe_ob.select_get():
continue
if test(original_ob, dupe_ob):
dupe_ob.select_set(True)
view_layer.objects.active = dupe_ob
print("possible dupe: ", dupe, " of original: ", original_ob.name)