21

I try to do this:

bpy.ops.object.select_all()
bpy.ops.object.join()

in my script. It selects all the stuff like CtrlA but fails on join() (CtrlJ) with the following error message:

Traceback (most recent call last): File "", line 1,
in File "C:\Program Files\Blender
Foundation\Blender\2.71\scripts\modules\bpy\ops.py", line 188, in
__ call __
ret = op_call(self.idname_py(), None, kw) RuntimeError: Operator bpy.ops.object.join.poll() failed, context is incorrect

the message is from console.

How can I make this work? CtrlA, CtrlJ actually works.

CodeManX
  • 29,298
  • 3
  • 89
  • 128
Zéiksz
  • 1,959
  • 5
  • 23
  • 32
  • 1
    TO join an object you must have one object as the active object. If there is no active object selected it will not work. Try setting one object as the selected object. – Vader Jul 12 '14 at 13:07
  • Works nice, thank you. Would you put this as an answer so I can flag it? – Zéiksz Jul 12 '14 at 13:15

5 Answers5

26

If you don't want to change the real selection states, you may wanna use an override:

import bpy

scene = bpy.context.scene

obs = []
for ob in scene.objects:
    # whatever objects you want to join...
    if ob.type == 'MESH':
        obs.append(ob)

ctx = bpy.context.copy()

# one of the objects to join
ctx['active_object'] = obs[0]

ctx['selected_objects'] = obs
# In Blender 2.8x this needs to be the following instead:
#ctx['selected_editable_objects'] = obs

# We need the scene bases as well for joining.
# Remove this line in Blender >= 2.80!
ctx['selected_editable_bases'] = [scene.object_bases[ob.name] for ob in obs]

bpy.ops.object.join(ctx)
CodeManX
  • 29,298
  • 3
  • 89
  • 128
  • 1
    Your code is much much faster than the proposal of stacker when joining some hundred or thousand objects. Could you please explain the concept? Why do you make a copy of context? – Peter Hilgers Dec 05 '16 at 19:43
  • The performance difference can probably be explained like this: http://blender.stackexchange.com/a/7360/1363 The context override was only supposed to keep the original selection intact AFAIR. If you use a copy of the context, you can change some attributes and pass it to an operator without affecting the actual context. – CodeManX Dec 05 '16 at 19:48
  • 1
    Oh my God this is lightning fast! Thank you so much for this. Joining 400-ish objects was the main timesucker in my project, and it has been reduced to almost zero – Davy Herben Feb 08 '18 at 16:02
  • Any recommendation on making this work for 2.8: AttributeError: 'Scene' object has no attribute 'object_bases'? – oneiros Nov 22 '19 at 14:05
  • 1
    @oneiros Just delete that line, it's not required anymore (the collection system changed things). – CodeManX Nov 23 '19 at 15:02
  • I'm always getting this Warning: Active object is not a selected mesh {'CANCELLED'} error even after following these steps. I'm on Blender 2.82 – Neeraj Lagwankar Mar 17 '20 at 18:02
  • @NeerajLagwankar It works for me in 2.82a, using 3 mesh objects, a lamp and a camera, with the camera being the active object in the selection. What kind of objects do you have selected? – CodeManX Mar 18 '20 at 12:19
  • Here is the stripped down version of blend file: Blend Exchange – Neeraj Lagwankar Mar 18 '20 at 17:12
  • I checked the code and join_mesh_shapes_exec() iterates over selected_editable_objects instead of selected_objects https://github.com/dfelinto/blender/blob/87ebc4ef4f2029a8f4fb21ea433c4ca189b243d5/source/blender/editors/mesh/meshtools.c#L686 Appears to work with ctx['selected_editable_objects'] = obs (obj_list in your example) and without setting selected_objects in the context. Note that your operator call is incomplete in the .blend file, it should be bpy.ops.object.join(ctx) (it misses .object) – CodeManX Mar 18 '20 at 22:55
  • Any fast joining of many objects (1000+) in Blender >3.0? – Jan Kadeřábek Jun 13 '22 at 23:49
14

No need for bases in 2.8 join operator override.

Thought I would also post this here. It appears at 2.80 beta version, there is no need to have the selected editable bases context member, instead use selected editable objects

Testing this in python console. Have duped the default cube 3 times, the last dupe "Cube.003" is active and only object selected.

>>> C.object
bpy.data.objects['Cube.003']

>>> C.selected_objects [bpy.data.objects['Cube.003']]

A list of all the mesh objects in scene to join

>>> obs = [o for o in C.scene.objects if o.type == 'MESH']
>>> obs
[bpy.data.objects['Cube'], bpy.data.objects['Cube.001'], bpy.data.objects['Cube.002'], bpy.data.objects['Cube.003']]

Make a context override dictionary, with only object, active_object, selected_objects, selected_editable_objects as members

>>> c = {}

>>> c["object"] = c["active_object"] = C.object >>> c["selected_objects"] = c["selected_editable_objects"] = obs

Blender 2.8 - 3.1:

Run the operator with this override

>>> bpy.ops.object.join(c)
{'FINISHED'}

Blender 3.2+ :

with C.temp_override(active_object=C.active_object, selected_editable_objects=obs):
    bpy.ops.object.join()

Resulting scene after running. All four cubes are joined as one "Cube.003", The result desired

>>> C.scene.objects[:]
[bpy.data.objects['Lamp'], bpy.data.objects['Camera'], bpy.data.objects['Cube.003']]

Risky look at obs since three of the objects no longer exist.

>>> obs
[<bpy_struct, Object invalid>, <bpy_struct, Object invalid>, <bpy_struct, Object invalid>, bpy.data.objects['Cube.003']]

Further to this, can make object copies into the data collection and join them. Here i am copying the object 3 times, and then joining all mesh objects into file as one.

>> for i in range(3):
...     o.copy()
...

bpy.data.objects['Cube.000'] bpy.data.objects['Cube.001'] bpy.data.objects['Cube.002']

>>> obs = [o for o in D.objects if o.type == 'MESH']

"Array them to see result"

>> for  o in obs:
...     o.location.z += ob.dimensions.z
...     
>>> c = {}
>>> c["object"] = c["active_object"] = C.object
>>> c["selected_objects"] = c["selected_editable_objects"] = obs

>>> bpy.ops.object.join(c) {'FINISHED'}

the dupes will remain in data, the object "Cube.003" will be a mesh made of all others.

Gorgious
  • 30,723
  • 2
  • 44
  • 101
batFINGER
  • 84,216
  • 10
  • 108
  • 233
11

To join an object you must have one object as the active object. If there is no active object selected it will not work. Try setting one object as the selected object.

An example would be

bpy.context.scene.objects.active = bpy.data.objects["Cube"]

Vader
  • 14,680
  • 16
  • 74
  • 110
5

A full script for later reference:

import bpy

for ob in bpy.context.scene.objects:
    if ob.type == 'MESH':
        ob.select = True
        bpy.context.scene.objects.active = ob
    else:
        ob.select = False
bpy.ops.object.join()
stacker
  • 38,549
  • 31
  • 141
  • 243
1

here's a version of stacker's answer updated to work in 3.4.1

import bpy

for ob in bpy.context.scene.objects: if ob.type == 'MESH': ob.select_set(True) bpy.context.view_layer.objects.active = ob else: ob.select = False bpy.ops.object.join()

ddd
  • 116
  • 1
  • 4