1
$\begingroup$

I'm trying to create some automated test that will launch blender open a specific file and call an operator from an addon.

The operator is using bpy.context.active_object and it works perfectly when calling this operator from blender window.

The test are launched with GUI to respect the user environement as much as possible. Unfortunately this is not enough, i got the error:

'Context' object has no attribute 'active_object'

This problem doesn't occur if I don't open a file before calling the operator in the script.

If I call the operator manually (in the exact same blender instance opened automatically for the test) everythings works fine.

What can I do to call this operator from an automated script without making any modification to the initial addon?

A generic solution would be usefull since I have a lot of operator to test and most of them are using bpy.context.active_object or bpy.context.selected_object

Here is a minimal example: (Tested on Blender 2.73)

The addon: (CANNOT BE CHANGED)

bl_info = {
    "name": "test addon",
    "description": "test addon",
    "author": "Pyros",
    "version": (1, 0, 0),
    "blender": (2, 73, 0),
    "location": "3D View",
    "warning": "development",
    "category": "3D View"
}
import bpy

class OBJECT_OT_addon_operator_to_test(bpy.types.Operator):
    bl_idname =  "mesh.operator_to_test"
    bl_label = bl_description = "Operator to test"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        ob = bpy.context.active_object
        print(ob.name)#do stuff with object
        return {'FINISHED'}   
def register():
    bpy.utils.register_module(__name__)
def unregister():
    bpy.utils.unregister_module(__name__)

The script:

import os
import sys
import bpy

base = sys.argv.index("--")
blendFile = sys.argv[base+1]
bpy.ops.wm.open_mainfile(filepath=blendFile)#without this line everything looks to work fine
bpy.ops.mesh.operator_to_test()
exit()

And a little python file to launch the test

import os
testfolder = os.path.split(os.path.realpath(__file__))[0]
scriptPath = testfolder+"\\script.py"
blendFile = testfolder+"\\base.blend"
os.system("blender -P \""+scriptPath+"\" -- \""+blendFile+"\"")

EDIT: A solution would be to launch blender with a file to open instead of opening it in the script:

os.system("blender \""+blendFile+"\" -P \""+scriptPath)

This solution would be acceptable but I'd prefer to open the file in the script to have more control. More than that I want to understand why this errors occurs.

$\endgroup$
  • $\begingroup$ I'll try to create a minimal example to reproduce this problem. $\endgroup$ – Pyros Nov 23 '17 at 15:27
  • $\begingroup$ Consider using a persistent onload post handler. Also (not a solution) please use context.active_object ie context is a parameter of the execute method. $\endgroup$ – batFINGER Nov 23 '17 at 16:05
  • $\begingroup$ I'll try that thanks. I agree with you but again this is a simplified example to reproduce the problem, the addons are more complex and can't be edited easily. $\endgroup$ – Pyros Nov 23 '17 at 16:15
1
$\begingroup$

When calling blender from the CLI or script with arguments, it will "perform" each argument in the order it is presented, this can be important with many tasks.

What that means, is that blender -P script.py file.blend will run the given script and then open the blend file, while blender file.blend -P script.py will open the blend file and then run the script. In the second example you don't need to run bpy.ops.wm.open_mainfile and won't have the issue you are trying to solve.

That also means, your addon test example can be reduced to a single line and passed to blender without using a script.

blender file.blend --python-expr 'import bpy; bpy.ops.mesh.operator_to_test()'

Rather than combining a string to pass to os.system() I would suggest using subprocess.call() which uses a list for the arguments, preventing the need to quote and escape... effectively the list becomes sys.argv

As you are doing an automated addon test, consider using --factory-startup and --addon to control what addons are enabled. You could do one run with your normal startup file and another with factory and your addon or simply ensure your addon is enabled without having to change your startup file.

To test an addon with multiple files -

from glob import glob
from subprocess import call

for blendFile in glob('*.blend'):
    arglist = [
    'blender',
    '--factory-startup',
    '--addons',
    'addon_test',
    blendFile,
    '--python-expr',
    'import bpy; bpy.ops.mesh.operator_to_test()'
    ]
    call(arglist)

Or you can loop through multiple addons and test one at a time -

from subprocess import call

for addon in ['addon_test1', 'addon_test2']:
    arglist = [
    'blender',
    '--factory-startup',
    '--addons',
    addon,
    'test.blend',
    '--python',
    addon + '_test.py'
    ]
    call(arglist)
$\endgroup$
  • $\begingroup$ Thanks for your answer. I gave the possibiliy to open the blend like you propose at the end of my question. In my example the blend file is behind the -- so it is ignored by blender, the order of execution of the argument has no impact in this case since the only real argument is the script. At the begining I wanted to be able to open the same file multiple time in the same script to make multiple tests without restarting blender but I finaly decided to use your solution. Thanks for the --factory-startup and especially for the --addon tricks I think its will be usefull to use that. $\endgroup$ – Pyros Nov 24 '17 at 11:30
  • $\begingroup$ @Pyros I get that blender doesn't open the file, your script does, but if you tell blender to open it as a cli argument, the context gets updated as you want before your script runs (you wanted to know why). Opening the same file multiple times would mean reverting to saved version - does bpy.ops.wm.revert_mainfile work better than open_mainfile? Also for testing it is better to have a clean start each time to eliminate accumulating errors from previous runs. $\endgroup$ – sambler Nov 24 '17 at 17:50
0
$\begingroup$

Thanks to batFINGER I found the reason of the failure.

It isn't linked to the way script is called but to the way that blender open a file with bpy.ops.wm.open_mainfile.

This post explains a little the reason of the problem. I'm not sure it explains everything since the code is availlable after the execution of open_mainfile (the operator is called and the operator is still in memory)

So with the problem I gave a solution would be:

import sys
import bpy
from bpy.app.handlers import persistent
@persistent
def load_handler(dummy):
    bpy.ops.mesh.operator_to_test()
base = sys.argv.index("--")
blendFile = sys.argv[base+1]
bpy.app.handlers.load_post.append(load_handler)
bpy.ops.wm.open_mainfile(filepath=blendFile)
exit()

I still wonder if there is a way to "refresh" the context after opening a file to avoid this kind of manipulation.

$\endgroup$
  • $\begingroup$ The context is related to the UI. The UI doesn't get refreshed while your script is running, so context doesn't get updated. Compare this to running bpy.ops.render.render() by script. $\endgroup$ – sambler Nov 24 '17 at 1:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.