4

I am trying to make a script that works kind of the same as Merge by Distance but to be used in Object mode. Basically, what I am trying to do is join all the objects that have their origin at the same location. This is the script I tried to use :

import bpy;

visible_objects=[ob for ob in bpy.context.view_layer.objects if ob.visible_get()]; #get all the visible objects in a list

for o in visible_objects: #iterate the objects bpy.context.view_layer.objects.active= o; #set the current one as active for j in visible_objects: #iterate again if j== o: continue; if j.location== o.location: #join the objects sharing location with the current one j.select_set(True); bpy.ops.object.join();

There are 2 problems with this code.

The first one is a logical one, once an object is joined to another, it "disappears" but the visible_objects list is still referencing it so I get the obvious error : "ReferenceError: StructRNA of type Object has been removed".

The second problem is that I keep receiving the warning "Warning: Active object is not a selected mesh" after each join even though I already set an object as active with bpy.context.view_layer.objects.active= o and all the other objects are meshes.

Is there a way to get rid of the warning and how do I fix the algorithm so that the inexistant objects are not anymore referenced by the list?

mqbaka mqbaka
  • 3,036
  • 7
  • 22

2 Answers2

5

Another solution which uses a context override which will prevent blender from freezing too much because the selection states takes a lot of time to process when there are a lot of objects in the scene :

import bpy
from collections import defaultdict
dic = defaultdict(set)
for obj in bpy.data.objects: 
    dic[obj.location.to_tuple()].add(obj)

for objects in dic.values(): if len(objects) <= 1: continue objects = list(objects) with bpy.context.temp_override( active_object=objects[0], selected_editable_objects=objects ): bpy.ops.object.join()

Link to the operator override arguments

Gorgious
  • 30,723
  • 2
  • 44
  • 101
4
import bpy

dic_obj_loc = {ob : tuple(ob.location) for ob in bpy.context.view_layer.objects if ob.visible_get() and ob.type == 'MESH'}

# get same location dict
rev_multidict = {}
for k, v in dic_obj_loc.items():
    rev_multidict.setdefault(v, set()).add(k)

for k, objs in rev_multidict.items():
    print(k, objs)
    print()
    if len(objs) == 1: continue
    bpy.ops.object.select_all(action='DESELECT')

    for obj in objs:
        obj.select_set(True)

    bpy.context.view_layer.objects.active = bpy.context.selected_objects[0]
    bpy.ops.object.join()
X Y
  • 5,234
  • 1
  • 6
  • 20
  • Thank you for the answer. Can you explain what this line does tho? rev_multidict.setdefault(v, set()).add(k). I know it adds k to rev_multidict but I don't understrand what setdefault(v, set()) does. – mqbaka mqbaka Sep 21 '22 at 10:28
  • 1
    @mqbakamqbaka it's better to use defaultdict instead, setdefault reads a value from a dictionary. If no value is found, it sets the value to the - in this case - set(), then it reads the value again, so now it has either a reference to the new set just created, or a reference to a set that was already there. – Markus von Broady Sep 21 '22 at 10:30
  • 1
    also, vectors have a method to_tuple - I don't know if it's faster, but should be otherwise there's no reason for it to be there – Markus von Broady Sep 21 '22 at 10:32
  • Thanks for the explanation. My blender froze after I tried the script tho :'D – mqbaka mqbaka Sep 21 '22 at 10:37
  • So it seems like Blender is still processing the script now, I just checked system console and the prints are still going on but the main UI turned unresponsive. – mqbaka mqbaka Sep 21 '22 at 10:42
  • Thanks for the answers again. The script has done running, it took around 13 minutes to process 9600 objects :D – mqbaka mqbaka Sep 21 '22 at 10:53
  • I think you can remove one step from your algorithm. from collections import defaultdict; dic = defaultdict(set); for obj in bpy.data.objects: dic[obj.location.to_tuple()].add(obj) then for objects in dic.values(): ... – Gorgious Sep 21 '22 at 11:50