14
$\begingroup$

I feel like I'm always spending far too much time playing scripting whack-a-mole whenever I want to override an operator in one of my scripts. I'd like to build a comprehensive one-stop-shop list for the most used operator overrides.

$\endgroup$
1
  • 2
    $\begingroup$ Since Blender 3.2 passing the context to operators as a dictionary has been deprecated as part of the inclusion of Context.temp_override which should be used instead. $\endgroup$ Jun 8, 2022 at 19:10

2 Answers 2

11
$\begingroup$
Operator Required context members Q&A's
ed.lib_id_generate_preview() id - Object reference #1
ed.lib_id_load_custom_preview() id - Object reference #1
file.execute() window - Window reference
area - Area reference
fluid.bake_data() scene - Scene reference
active_object - Object reference
#1
image.save_sequence() area - Area reference #1
info.report_copy() area - Area reference
info.select_all() area - Area reference
mesh.customdata_custom_
splitnormals_clear()
mesh - Mesh reference
object.delete() selected_objects - Sequence of Object references #1, #2
object.duplicates_make_real() selected_objects - Sequence of Object references
object.geometry_nodes_input_
attribute_toggle()
object - Sequence of Object references
selected_editable_objects - Sequence of Object references
object.make_single_user() selected_objects - Sequence of Object references
object.material_slot_remove() object - Object reference #1, #2, #3
object.modifier_apply() object - Object reference
view_layer - Viewlayer reference (Optional)
scene - Scene reference (Optional)
#1, #2, #3, #4, #5
object.modifier_copy_
to_selected()
object - Object reference
selected_objects - Sequence of Object references
#1, #2
object.modifier_move_down() object - Object reference #1, #2, #3
object.modifier_move_to_index() object - Object reference
object.modifier_move_up() object - Object reference #1, #2, #3
object.object.join() active_object - Object reference
selected_editable_objects - Sequence of Object references
#1, #2
object.origin_set() selected_editable_objects - Sequence of Object references #1, #2
object.parent_clear() selected_editable_objects - Sequence of Object references
object.parent_set() object - Object reference
selected_editable_objects - Sequence of Object references
object.posemode_toggle() active_object - Object reference
object.select_all() area - Area reference
object.shade_smooth() selected_editable_objects - Sequence of Object references #1, #2
object.transform_apply() selected_editable_objects - Sequence of Object references #1
palette.extract_from_image() area - Area reference #1
ptcache.bake() scene - Scene reference
active_object - Object reference
pointcache - Pointcache reference
#1, #2, #3, #4, #5
render.opengl() area - Area reference #1, #2
screen.area_close() area - Area reference #1
screen.area_dupli() area - Area reference #1
screen.area_split() area - Area reference #1
script.execute_preset() window - Window reference
area - Area reference
#1
sequencer.sound_strip_add() area - Area reference #1
text.reload() edit_text - Text reference
text.run_script() edit_text - Text reference #1
view3d.view_axis() area - Area reference
region - Region reference
#1
view3d.walk area - Area reference
region - Region reference
#1, #2
workspace.delete() workspace - Workspace reference

Notes

  • For a few operators, e.g. object.modifier_*(), additional parameters must be passed (in this case the name of the modifier), see the documentation of the respective operator for reference.
  • The context members of bpy.ops.object.convert cannot be overwritten, see T93188.

Appendix

In Blender 3.2, a dedicated context manager has been added to the Python API. Note that the example given in the documentation can be simplified to:

import bpy
from bpy import context

objs = list(context.scene.objects)
with context.temp_override(selected_objects=objs):
    bpy.ops.object.delete()

If you'd like to pass additional arguments, see the following example for reference:

import bpy
from bpy import context

for area in context.screen.areas:
    if area.type == 'VIEW_3D':
        with context.temp_override(area=area):
            bpy.ops.render.opengl('INVOKE_DEFAULT', write_still=True)
        break

For versions prior to Blender 3.2, you always had to pass the context members to each operator as a dictionary, please see the revisions of this answer for reference.


Background

Most of the usual suggestions involve overwriting area, screen, space or window which is very cumbersome and prevents headless usage of operators, as these members are not required in many cases. To determine the required members, it is unfortunately necessary to either take a look at the source code and deduce them from the way they are coded internally (whether in C or Python) or by manual testing the most likely possibilities.

If you'd like to contribute to this answer, please download this blend file, add the operator you tested and its context members to the DataJson text-block and run GenerateMarkdown, which will generate the markdown table above from scratch.


$\endgroup$
16
  • $\begingroup$ have you asked about this in devtalk? perhaps someone has a script that they can run over the source to generate such a table. $\endgroup$ Dec 27, 2021 at 22:28
  • $\begingroup$ That's a great idea ! I'll make sure to ask there, although I think since there are quite intricate dependencies and edge cases it might be a bit hard to get a reliable table. $\endgroup$
    – Gorgious
    Dec 27, 2021 at 22:39
  • 2
    $\begingroup$ @Gorgious Good initiative! Too bad that the only two upvotes are mine from a month ago... I'd propose linking the Q&A's as well. What do you think? I would take care of it, if there is some time next week. $\endgroup$
    – p2or
    Jan 23, 2022 at 21:52
  • $\begingroup$ @p2or Yeah I think all the efforts we make in this direction will bear fruits in the long run even if it is not payed in upvotes !! Thanks for the interest :) $\endgroup$
    – Gorgious
    Jan 23, 2022 at 22:49
  • 1
    $\begingroup$ Thanks @MarkusvonBroady. Bummer, if javasript is a requirement to render properly, I'd go for the table, if that is even an option for Gorgious. Otherwise we will drive away people we actually want to help. Do you agree? $\endgroup$
    – p2or
    Jan 22 at 21:05
6
$\begingroup$

I'm still working on my script that helps me to detect what elements a context override requires, but I guess I might as well share it now and update it later...

Making context copy

First, prepare a situation, in which an operator works. If you can simply run it from a console, then you can type:

c = {k:v for k, v in C.copy().items() if v is not None}

This makes a copy of a current context, as well as removes empty (None) values for readability. Theoretically a context could require some value to be in the context (override) and be None, but I just assume this is not the case.

If you can't make he operator work from console, perhaps you can do this little trick, that changes area type, and then makes context copy, and changes it back:

C.area.type = 'NODE_EDITOR'; c = {k:v for k, v in C.copy().items() if v is not None};

(you can add ; C.area.type = 'CONSOLE' after, but you can also just press ShiftF4 to return to the console)

(typing C.area.type = 0 will error with a list of available area types)

If your valid context appears in more exotic circumstances: a timer, msgbus/draw/property callback, an operator or whatever else - you can just save the context copy assigned to some type (I recommend any except bpy.types.Context), for example:

bpy.types.WindowManager.context_copy = C.copy()

Context Override Checker:

import bpy
from bpy import data as D, context as C

log_path = "E:/log.txt"


'''
Usage:
    
c = {k:v for k, v in C.copy().items() if v is not None}
D.texts['Text'].as_module().test(c)
'''


def log(text):
    with open(file=log_path, mode="a") as f:
        f.write(text)


def test(context_override):
    cpy = context_override.copy()
    
    # overriding those 3 keys with `None` tends to crash Blender, so I just skip checking those
    cpy.pop('scene')
    cpy.pop('view_layer')
    cpy.pop('blend_data')
    
    # you can skip removing those keys if you know they're needed
    skip = [
        'object',
        'selected_objects'
    ]
    to_remove = {k:v for k,v in cpy.items() if k not in skip}
    
    for k, v in to_remove.items():
        #prepare the environment here...
        log(k)
        cpy[k] = None  # override context value with None
        try:
            '''
                IMPORTANT!
                REMEMBER TO PASS CONTEXT OVERRIDE TO THE OPERATOR
                                 |
                                 |
            '''  #               |
                 #               v
            result = bpy.ops...(cpy, arguments...)            
            C.view_layer.update()
            if ... : # find a way to check if the operator failed
                log(f"  {result=}")
                raise ValueError
            else:
                log(" - success\n")
                # now undo all changes a successful operator has caused
                C.view_layer.update()
        except ValueError:
            log(" - ERROR\n") 
            cpy[k] = v  # restore required value
            
        # perhaps some cleanup regardless of the operator's success or failure
    
    what_is_left = {k:v for k,v in cpy.items() if v is not None}
    log(f"\n\n\n{what_is_left}") # show what's left in the context override

Then in console:

D.texts['Text'].as_module().test(c)

Example

import bpy
from bpy import data as D, context as C

log_path = "E:/log.txt"


def log(text):
    with open(file=log_path, mode="a") as f:
        f.write(text)


def test(context_override):
    cpy = context_override.copy()
    cpy.pop('scene')
    cpy.pop('view_layer')
    cpy.pop('blend_data')
    
    skip = [
#        'object',
#        'selected_objects'
    ]
    to_remove = {k:v for k,v in cpy.items() if k not in skip}
    
    for k, v in to_remove.items():
        #prepare the environment here...
        cube = D.objects['Cube']
        log(k)
        cpy[k] = None  # override context value with None
        try:
            cube.location = 0,0,0
            result = bpy.ops.transform.translate(cpy, value=(0, 3, 0))     
            C.view_layer.update()
            if cube.location.y < 1:  # should be 3
                log(f"  {result=}")
                raise ValueError
            else:
                log(" - success\n")
                # no need to undo anything here, as the location is set to 0 after `try`
        except ValueError:
            log(" - ERROR\n") 
            cpy[k] = v  # restore required value
    
    what_is_left = {k:v for k,v in cpy.items() if v is not None}
    log(f"\n\n\n{what_is_left}") # show what's left in the context override

Then in console:

>>> c = {k:v for k, v in C.copy().items() if v is not None}
>>> D.texts['Text'].as_module().test(c)

Resulting log.txt file

active_object - success
active_operator - success
area - success
asset_library_ref - success
collection - success
editable_objects - success
engine - success
evaluated_depsgraph_get - success
layer_collection - success
mode - success
object - success
objects_in_mode - success
objects_in_mode_unique_data - success
preferences - success
region - success
screen - success
selectable_objects - success
selected_editable_objects - success
selected_editable_sequences - success
selected_objects  result={'CANCELLED'} - ERROR
selected_sequences - success
sequences - success
space_data - success
tool_settings - success
visible_objects - success
window - success
window_manager - success
workspace - success



{'selected_objects': [bpy.data.objects['Cube']]}
$\endgroup$
2
  • $\begingroup$ Ooh nice I do like the bulldozer approach, test every possible combination and selectively remove keys. Smart ! Would need another recursion of tests I guess if there are more than one mandatory key, like bpy.ops.object.join which might send the computation through the roof but I like the approach. Thankfully there aren't that many. Cheers $\endgroup$
    – Gorgious
    Feb 9, 2022 at 6:25
  • $\begingroup$ @Gorgious the assumption is that breaking any link breaks the chain, i.e. if an operator uses multiple values, not providing any will fail the operator. But indeed I find it quite likely an operator might use one value and fall back to another if it's not provided, in which case perhaps the script could try again in reversed order? 🤔 $\endgroup$ Feb 9, 2022 at 9:58

You must log in to answer this question.

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