Newbie trying to automate joining objects after selecting them via search pattern

join_same_name_objects.py (746 Bytes) Hi Guys, sorry to bother you. Im really new with python scripting . I’m hopping you could give me some insight.
This is my situation: Im importing a model from Revit via IFC and it generates objects with long names.

This is an example of the object names: (the full list about 800 objects)

bpy.context.scene.objects[

‘IfcWallStandardCase/Muro básico:Generic - 150mm C:520337’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:520341’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:520345’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:520349’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:525707’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:525841’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:527695’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:527825’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:573925’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:637305’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:670420’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:670425’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:670557’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm C:670562’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:517485’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:517486’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:520390’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:520394’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:520398’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:520415’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:524163’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:615345’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:615384’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:615455’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:626248’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:650751’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:686053’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:686117’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:688973’]
‘IfcWallStandardCase/Muro básico:Generic - 150mm:689092’]
‘IfcWallStandardCase/Muro básico:Generic - 200mm:571706’]
‘IfcWallStandardCase/Muro básico:Generic - 200mm:571868’]
‘IfcWallStandardCase/Muro básico:LAmina F:541198’]
‘IfcWallStandardCase/Muro básico:LAmina F:563937’]
‘IfcWallStandardCase/Muro básico:LAmina F:563998’]
‘IfcWallStandardCase/Muro básico:LAmina F:564496’]
‘IfcWallStandardCase/Muro básico:Lamina:520472’]
‘IfcWallStandardCase/Muro básico:Lamina:520476’]
‘IfcWallStandardCase/Muro básico:Panel Fachada:562901’]
‘IfcWallStandardCase/Muro básico:Panel Fachada:563203’]
‘IfcWallStandardCase/Muro básico:Panel Fachada:563605’]
‘IfcWallStandardCase/Muro básico:Panel Fachada:565785’]
‘IfcWindow/Deslizante con cubrejuntas:1830 x 1220 mm 10:527089’]
‘IfcWindow/Deslizante con cubrejuntas:1830 x 1220 mm 11:650850’]
‘IfcWindow/Deslizante con cubrejuntas:600 x 600 mm 2:639533’]
‘IfcWindow/Deslizante con cubrejuntas:600 x 600 mm 2:639820’]
‘IfcWindow/Deslizante con cubrejuntas:600 x 600 mm 2:639940’]
‘IfcWindow/Window_4_pane_slider_10391:Window_4_pane_slider_10391’]

I need to join objects with similar names to be able to simplify the objects in the scene.
After a little study and watching some example scripts I manage to write this one:

import bpy

bpy.ops.object.select_all(action=‘DESELECT’) #This one Deselects all previous selections

for obs in bpy.context.scene.objects:

nocolon = obs.name.rsplit(sep=‘:’) # break down long names separating strings along the “:”
tname = nocolon[0].rsplit(sep=‘.’) # Removes the “.” and passes name variable “tname”
bpy.ops.object.select_pattern(pattern= tname[0]+‘*’, case_sensitive=False, extend=True)
obs.select_set(True) #Marks the selection
bpy.context.view_layer.objects.active = obs #Sets the active object
bpy.ops.object.join() #Joins the selection
bpy.ops.object.select_all(action=‘DESELECT’) #Deselects all for the next iteration

print(‘done’)

Im kinda lost here, because sometimes that I run the scripts I get diferent behaviour:

1.- It finishes selecting the objects and joining objects correctly but gives error RuntimeError: Error: Object ‘Cube.red’ can’t be selected because it is not in View Layer ‘View Layer’!

2.- Sometime it freezes blender for long time.

3.- Sometimes blender closes

4.- Sometimes gives me the error RuntimeError: Operator bpy.ops.object.join.poll() failed, context is incorrect

I hope you guys cold give me some insights of what could be causing this issue.
You could test the script by doing duplicates of different objects.
Example:
Cube
Cube.001
Cube.002
Sphere
Sphere.001
Sphere.002
Cone
Cone.001
Cone.002

It should be able to join the objects with the same name.

I hope you could advise me.

Please format your code so it is readable. You can use the editors code block.

Some operators want to work in certain modes (like Edit, or Object) some likes to be run inside a 3d View. Generally speaking, turning your code into a proper addon might resolve some of those issues. When you run your script , the script runs from the text editor which is a context itself, and some operators do not like that.

Thanks kkar, I will try to turn it into and addon. I’ll let you know how it goes. (I tried to do that, but I’m still trying to learn how to code, and I got a bunch of errors when trying to install it :laughing: )

In the meantime I mange reduce the list of errors to just one, and I’m not really sure how to solve it:

The script works with few objects in the scene:
Example:

But when I run it against a longer quantity of objects it crashes Blender with a EXCEPTION_ACCESS_VIOLATION error:

I’m not sure if this is correct but my guess is that it crashes when it tries to read objects that are no longer in the scene (Because they were joined).

Do you know if there is a way to maybe update the objects list on the fly. or how to Avoid the crash.
Or maybe this is not the right aproach to acomplish this.

Do you have a suggestion or tip that you could share.
Thanks a lot for your time.

This is the updated script JoinSameNameObjects.py (1.3 KB) I made it more “chatty” to see where it crashes, but it seems its random.

Try this:

import bpy, bmesh, re
context = bpy.context


def bmesh_join(list_of_bmeshes, normal_update=False):
    #https://blender.stackexchange.com/questions/50160/scripting-low-level-join-meshes-elements-hopefully-with-bmesh/80592#80592
    """ takes as input a list of bm references and outputs a single merged bmesh 
    allows an additional 'normal_update=True' to force _normal_ calculations.
    """

    bm = bmesh.new()
    add_vert = bm.verts.new
    add_face = bm.faces.new
    add_edge = bm.edges.new

    for bm_to_add in list_of_bmeshes:
        offset = len(bm.verts)

        for v in bm_to_add.verts:
            add_vert(v.co)

        bm.verts.index_update()
        bm.verts.ensure_lookup_table()

        if bm_to_add.faces:
            for face in bm_to_add.faces:
                add_face(tuple(bm.verts[i.index+offset] for i in face.verts))
            bm.faces.index_update()

        if bm_to_add.edges:
            for edge in bm_to_add.edges:
                edge_seq = tuple(bm.verts[i.index+offset] for i in edge.verts)
                try:
                    add_edge(edge_seq)
                except ValueError:
                    # edge exists!
                    pass
            bm.edges.index_update()

    if normal_update:
        bm.normal_update()

    return bm


def get_base_name(name):
    return re.match("([^.:]+)", name).group()


obs = [ob for ob in context.view_layer.objects if ob.type == 'MESH']
obs_left = obs.copy()
for ob_merge_to in obs:
    if ob_merge_to in obs_left:
        base_name = get_base_name(ob_merge_to.name)
        
        obs_to_merge = [ob for ob in obs_left if base_name in ob.name and ob != ob_merge_to]
        obs_left = [ob for ob in obs_left if ob not in obs_to_merge and ob != ob_merge_to]

        if obs_to_merge:
            # collect bmeshes
            list_of_bmeshes = []
            bmesh_objects = obs_to_merge.copy()
            bmesh_objects.append(ob_merge_to)
        
            for ob in bmesh_objects:
                bm = bmesh.new()
                bm.from_mesh(ob.data)
                bm.transform(ob.matrix_world)
                list_of_bmeshes.append(bm)  
               
            # join bmeshes 
            bm = bmesh_join(list_of_bmeshes)
            bm.transform(ob_merge_to.matrix_world.inverted())
            bm.to_mesh(ob_merge_to.data)
            ob_merge_to.name = base_name
            
            # clear bmeshes
            for bm in list_of_bmeshes:
                bm.free()
            
            # remove merged objects
            for ob in obs_to_merge:
                bpy.data.objects.remove(ob, do_unlink=True)
1 Like

Thanks a lot Cirno, this script works a lot faster that the one I endend up writing.
Great for study also. I was doing it by checking the commands that blender uses in the info editor.

For the script that you see above I ended up using the collection system as placeholders for the objects and then joining them outside the loop to avoid crashing blender.

But this one works great:

For joining the objects on a scene that I have it takes 21 seconds instead of 43 seconds that takes mine.
I have another scene that takes the first script around 15 minutes to join the objects. I can’t wait to test this one to see how much faster it is.

Thanks a lot. I’m saving a lot of time, It used to take hours to organize the scene. Scripting helps a lot for doing “monkey work”. :smiley:

Thanks again. Have a great day.

1 Like