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$
3
  • $\begingroup$ I'll try to create a minimal example to reproduce this problem. $\endgroup$ Commented Nov 23, 2017 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$ Commented Nov 23, 2017 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$ Commented Nov 23, 2017 at 16:15

2 Answers 2

2
$\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$
2
  • $\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$ Commented Nov 24, 2017 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$ Commented Nov 24, 2017 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$
1
  • $\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$ Commented Nov 24, 2017 at 1:02

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.