Overriding

Hi,

I have seen quite a few questions regarding this issue, and I have not been able to wrap my head around it.

So here is a concrete example.

I create a cube and in edit mode I add loopcuts. No sweat!

I copy the code from the info window and I try it out in a script and I get the ominous RuntimeError: Operator bpy.ops.mesh.loopcut_slide.poll() expected a view3d region & editmesh error message.

I understand now that it is about first giving python the right context by overriding. And alas, I do not know how to do it!

Here is the code:

import bpy
import os

os.system("cls")

# remove the default cube...
objs = bpy.data.objects
for obj in objs:
    if obj.name.find("Cube") == 0:
        bpy.data.objects.remove(obj, do_unlink=True)

# add a cube!
bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=True, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
# do something to it to be sure that we have it...
bpy.data.objects['Cube'].scale[0] = 10

# THE CODE BELOW GIVES THE RuntimeError: Operator bpy.ops.mesh.loopcut_slide.poll() expected a view3d region & editmesh error message.

# What is the override code I have to use to fix it????????

bpy.ops.mesh.loopcut_slide(MESH_OT_loopcut={"number_cuts":16, "smoothness":0, "falloff":'INVERSE_SQUARE', "object_index":0, "edge_index":4, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":0, "single_side":False, "use_even":False, "flipped":False, "use_clamp":True, "mirror":True, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":True, "release_confirm":False, "use_accurate":False})

first you’ll need to find a reference to the 3d area in your context, then copy the current context and assign it to the area key. After that you can pass it in to an operator.

ctx_override = bpy.context.copy()
ctx_override['area'] = the_area
bpy.ops.mesh.loopcut_slide(ctx_override, ...)

Now, that is a good start.

And, as mentioned, I am so new at this that I need to know more about what you stick in "ctx_override[‘area’] = the_area!

What should “the_area” be in the simple case I mention: adding loopcuts to a cube I just created?

I tried the code you mention in the console, and although I can see the ‘area’, I don’t know what to set it to!

And then, when I am done, I suppose that I have to restore what I have overridden, or does it not make sense?

You’ll need to find a valid area in the current context. I’ll give you a function that will return the first 3d viewport area it finds, but first let me explain how you would do it manually so you understand how it works.

As you know, in blender the UI is made up of a series of areas. all of the areas in the current context (that is, the workspace/tab/layout you are currently viewing) can be found in bpy.context.screen.areas. If you were to loop through this property collection and print out the type in the default Scripting tab of a fresh blender install, you’d see this:

Some operators might even require a specific region in addition to an area. Doing the same loop/print on the VIEW_3D area’s areas would give us this:

So as you can see there’s a bit of an onion effect happening here- the best way to get a handle on the relationships each of these objects have with one another is to start somewhere in the official API docs and just drill down until you’re saturated. At some point it will just “click”.

as promised, here’s a handy script to help you create an overridden context, and an example of how to use it:

import bpy

def find_3d_viewport_area(ctx = None):
    # allow the user to pass in a context, but handle it for them if there isn't one.
    # it is always best to use a local context (ie: from an operator or callback) rather than
    # the global context, but sometimes that's all you've got.
    if ctx is None:
        ctx = bpy.context

    for a in ctx.screen.areas:
        if a.type == 'VIEW_3D':
            return a
    return None

def main():    
    v3d = find_3d_viewport_area()
    if v3d is None:
        raise Exception("There was no 3d viewport! Stopping!")

    # bpy.types.Context's copy() function will return a dictionary. If you're curious what's in it, try printing it out!
    # you could actually build this dictionary manually and pass it in- Blender doesn't care! It's just easier to start from
    # one that already exists since you don't ever really know what it is that an operator's Poll function cares about (this
    # generally means some trial and error is involved while you "guess")

    ctx = bpy.context.copy()

    # uncomment the following two lines to see what's in the context dictionary
    # from pprint import pprint as pp
    # pp(ctx)

    ctx['area'] = v3d
    ctx['region'] = v3d.regions[5] # I happen to know that a VIEW_3D area's fifth region is the window, which is what the view3d.view_all operator is checking for. But we could always create a "find region of type" function similar to the one that found the area.
    
    # and finally, run the built in operator with our context override passed in as the first parameter. Normally, this operator would fail when running from the
    # text editor because the operator's poll would throw an exception since we aren't in the correct area to run it. With this override it's happy.
    bpy.ops.view3d.view_all(ctx)    
    
main()

And then, when I am done, I suppose that I have to restore what I have overridden, or does it not make sense?

there’s nothing to restore since you’re making a copy- bpy operators are fire and forget, so you’re passing in an overridden context and Blender doesn’t care what you do with it afterward, it’s just a dictionary after all.

1 Like

First, thank you so much for your very detailed exposé. You have no idea how much I am learning (for instance I did not know about writing a main routine that way…)

I religiously ran your code one step at a time from the console (after I found out a bug: the fifth element has index 4, not 5, lucky for me that it was the last element).

I end up knowing how to do all that, and yet, I am still missing what I hope to be the last step, namely how the heck do I use it to give my poor cube in the original post some loopcuts!

I know, that shows the extend of my newbieness, which I do not deny, and yet, with the kind of patience people like you show, I am learning a lot…

ah, yes- if the HUD region has not been created yet there will be one less region in the 3d view area, i forgot it’s initialized on demand.

Please…

I suppose that for you all these things are very simple, and for me, not.

Will you take the few minutes it would require to run the code I originally posted (I repeat it here for your convenience) and add the necessary code so that the loopcuts can be added in python!

import bpy
import os

os.system("cls")

# remove the default cube...
objs = bpy.data.objects
for obj in objs:
    if obj.name.find("Cube") == 0:
        bpy.data.objects.remove(obj, do_unlink=True)

# add a cube!
bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=True, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
# do something to it to be sure that we have it...
bpy.data.objects['Cube'].scale[0] = 10

# THE CODE BELOW GIVES THE RuntimeError: Operator bpy.ops.mesh.loopcut_slide.poll() expected a view3d region & editmesh error message.

# What is the override code I have to use to fix it????????

bpy.ops.mesh.loopcut_slide(MESH_OT_loopcut={"number_cuts":16, "smoothness":0, "falloff":'INVERSE_SQUARE', "object_index":0, "edge_index":4, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":0, "single_side":False, "use_even":False, "flipped":False, "use_clamp":True, "mirror":True, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":True, "release_confirm":False, "use_accurate":False})

Thank you in advance…