Page MenuHome

2.8: Python API: bpy.context.object not available in Application Timers
Closed, ArchivedPublic

Description

System Information
Operating system: Windows 7, 64bit
Graphics card: N/A

Blender Version
Broken:
2.80, dd9cedddae69, win64

Short description of error

The property bpy.context.object is not available in Application Timers. It raises an AttributeError: 'Context' object has no attribute 'object'.

Exact steps for others to reproduce the error

Just run this minimal example:

import bpy


def add_cylinder():
    bpy.ops.mesh.primitive_cylinder_add(
        radius=1, depth=1,
        location=(1.5, 0.0, 1),
    )

    obj = bpy.context.object

    print(obj)
    
    
# add_cylinder()   # this works
bpy.app.timers.register(add_cylinder)

Details

Type
Bug

Event Timeline

Jacques Lucke (JacquesLucke) claimed this task.

Unfortunately the context is not really available within timers.
This is because timers are not bound to a window/scene/...

In 2.7 I used bpy.app.handlers.scene_update_pre to emulate such an application timer and there I could access bpy.context.object. Looks like scene_update_pre is gone in 2.8 (without any mention in the release notes AFAIS).

So what would be the alternative in 2.8? Or is there an alternative way to get the created object after bpy.ops.mesh.primitive_cylinder_add?

Read this: https://devtalk.blender.org/t/porting-my-addons-to-2-8-missing-scene-update-post-handler/2465

Not sure what you are doing exactly, but adding stuff like that can really mess with the undo system..

You can also use the more low level api to create new meshes, although then you probably don't have ready made code that creates a cylinder.

I have a rather special use case. I'm using Blender as a 3D live view controlled from an external application.

What I'm mainly doing:

  • creating objects
  • moving objects around (by setting its location)
  • joining objects
  • setting and unsetting parents (so I have a kinematics chain)

So I'm doing other high level stuff which is too much to refactor to use low level APIs.

Sadly, I couldn't find anything helpful in the linked discussion.

bpy.context.object gives you the active object, but that can be different depending on the window and view layer, so it's not accessible by something global like a timer.

With a controlled environment like you have, using scene.view_layers[0].objects.active will probably work.

Yes, bpy.context.scene.view_layers[0].objects.active works for the code above. But just hitting the next issue:

def join_objects(*objects):
    bpy.ops.object.select_all(action='DESELECT')
    for o in objects:
        o.select_set(True)
    bpy.context.scene.view_layers[0].objects.active = objects[0]
    bpy.ops.object.join()
    bpy.ops.object.select_all(action='DESELECT')

Fails with: RuntimeError: Operator bpy.ops.object.join.poll() failed, context is incorrect.

This works:

def get_default_context():
    window = bpy.context.window_manager.windows[0]
    return {'window': window, 'screen': window.screen}

def join_objects(*objects):
    ctx = get_default_context()
    ctx['object'] = objects[0]
    ctx['active_object'] = objects[0]
    ctx['selected_objects'] = objects
    ctx['selected_editable_objects'] = objects
    bpy.ops.object.join(ctx)

Using get_default_context seems to be working for other bpy.ops operators, too.

anyone here know how to override inside of a timer for drawing something in the bpy.context.window_manager ? i can't get my hand on a code working like you did with bpy.context.object
i think that def draw(self, context): pose also a problem
this is driving me crazy all i want is poping up a message, why does it need to be so complicated

import bpy 

def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): #Message function
    def draw(self, context):
        self.layout.label(text=message)
    bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)

def every_X_seconds_word():
    ShowMessageBox("Hello World", "Many Crash" ,"BLENDER")
    return 5.0
bpy.app.timers.register(every_X_seconds_word)