14

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.

Gorgious
  • 30,723
  • 2
  • 44
  • 101

2 Answers2

11
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.


Gorgious
  • 30,723
  • 2
  • 44
  • 101
  • have you asked about this in devtalk? perhaps someone has a script that they can run over the source to generate such a table. – Marty Fouts Dec 27 '21 at 22:28
  • 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. – Gorgious Dec 27 '21 at 22:39
  • 2
    @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. – p2or Jan 23 '22 at 21:52
  • @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 :) – Gorgious Jan 23 '22 at 22:49
  • @Gorgious how about bpy.ops.uv.smart_project? it doesn't work with selected_objects or selected_editable_objects. i can't tell it which objects are selected using: with bpy.context.temp_override(selected_objects=selected_objects): bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.uv.smart_project(). i have to manually select the objects with a for loop before executing smart_project – Harry McKenzie Jul 28 '23 at 06:53
  • @HarryMcKenzie you can try either context.object or context.active_object ? – Gorgious Jul 28 '23 at 15:11
  • Hi @Gorgious Sorry for my heavy delay :D ... I tried to make a table out of your answer and tested how to provide links to the manual and the Q&A's as well. IMHO this makes it easier to read, but I imagine that this makes maintenance of this answer even more complicated. After a few tests, I guess that's the best we can get out of markdown What do you think? Cheers! – p2or Jan 22 '24 at 16:33
  • @p2or you can get a hell lot more if you use mathjax, but mathjax is not so portable... – Markus von Broady Jan 22 '24 at 17:00
  • @MarkusvonBroady Thank you, great idea! Tried it immediately, but unfortunately mathjax doesn't necessarily make it more readable. Also it seems there is no way highlighting the code properly and setting up the links. Do you have more experience with this than I do? – p2or Jan 22 '24 at 18:57
  • @p2or I have some experience with mathjax, keep in mind both markdown and mathjax will render differently on BSE and on github, also some people might read the content without e.g. javascript and they might see quirky syntax instead of readable plain text... – Markus von Broady Jan 22 '24 at 20:36
  • 1
    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? – p2or Jan 22 '24 at 21:05
  • @p2or I once explored the idea of using a table but without a dedicated editor I found it very cumbersome. If you want to modify the answer, go for it I don't mind, on the contrary ! I just don't understand the Q&A's column, could you elaborate on its purpose ? Anyways I've made it a community wiki so anyone can chime in. If it renders badly we can always rollback. Cheers – Gorgious Jan 23 '24 at 13:50
  • @Gorgious Thanks for your feedback. As mentioned in my first comment, I would personally find it useful to provide links to the answers already given. Don't you? ... as the links to the Q&A's would take up quite a lot of space, I have tried to shorten them. Do you have a better idea? – p2or Jan 23 '24 at 14:48
  • @p2or ah, right I understand, yes it would be a nice reference indeed ! – Gorgious Jan 24 '24 at 07:27
  • @Gorgious Cool, done! In its full glory it would look like this. Did I forget something? Re maintenance: The table is generated from a json file within this blend. I first tried to work with a spreadsheets, but that quickly became illegible so I decided to write a quick and dirty script that generates the table for us. When you run 'Generate-Markdown' a new text block called '_Markdown' is created which you can copy and paste here. What do you think? – p2or Jan 27 '24 at 21:59
  • @p2or Nice ! Yeah go for it :) – Gorgious Jan 29 '24 at 07:02
6

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']]}

Markus von Broady
  • 36,563
  • 3
  • 30
  • 99
  • 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 – Gorgious Feb 09 '22 at 06:25
  • @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? – Markus von Broady Feb 09 '22 at 09:58