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.
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.
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.bake() |
selected_objects - Sequence of Object references |
#1 |
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.join() |
active_object - Object reference selected_editable_objects - Sequence of Object references |
#1, #2, #3 |
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.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
object.modifier_*()
, additional parameters must be passed (in this case the name of the modifier), see the documentation of the respective operator for reference.bpy.ops.object.convert
cannot be overwritten, see T93188.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.
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.
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...
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()
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)
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)
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']]}
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$
Context.temp_override
which should be used instead. $\endgroup$