21
$\begingroup$

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.

$\endgroup$
2
  • 1
    $\begingroup$ 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. $\endgroup$
    – Vader
    Jul 12, 2014 at 13:07
  • $\begingroup$ Works nice, thank you. Would you put this as an answer so I can flag it? $\endgroup$
    – Zéiksz
    Jul 12, 2014 at 13:15

5 Answers 5

11
$\begingroup$

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"]

$\endgroup$
26
$\begingroup$

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)
$\endgroup$
10
  • 1
    $\begingroup$ 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? $\endgroup$ Dec 5, 2016 at 19:43
  • $\begingroup$ The performance difference can probably be explained like this: 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. $\endgroup$
    – CodeManX
    Dec 5, 2016 at 19:48
  • 1
    $\begingroup$ 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 $\endgroup$ Feb 8, 2018 at 16:02
  • $\begingroup$ Any recommendation on making this work for 2.8: AttributeError: 'Scene' object has no attribute 'object_bases'? $\endgroup$
    – oneiros
    Nov 22, 2019 at 14:05
  • 1
    $\begingroup$ @oneiros Just delete that line, it's not required anymore (the collection system changed things). $\endgroup$
    – CodeManX
    Nov 23, 2019 at 15:02
14
$\begingroup$

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.

$\endgroup$
5
$\begingroup$

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()
$\endgroup$
1
  • 1
    $\begingroup$ Given the comments below CoDEmanX 's answer re speed, perhaps set active outside loop. $\endgroup$
    – batFINGER
    Feb 27, 2019 at 14:50
1
$\begingroup$

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()
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .