How to programmatically call bpy.ops.xyz operators in Python

Scripting in Blender with Python, and working on the API

Moderators: jesterKing, stiv

Post Reply
dvochin
Posts: 0
Joined: Mon Aug 20, 2012 9:39 pm

How to programmatically call bpy.ops.xyz operators in Python

Post by dvochin » Mon Jul 22, 2013 2:19 am

Hi, I am following very closely the tutorial at http://en.wikibooks.org/wiki/Blender_3D ... m_Property on how to create a panel and operations that I can invoke through the tool shelf GUI (to left of 3dView).

The provided tutorial works great if I click the 3dView button to make a tetrahedra, but if I attempt to run the same operator that the GUI button gives me (bpy.ops.mesh.make_tetrahedron()) in the Python command window, it spits out "{PASS_THROUGH}" and I get an error in the log saying "wm_operator_invoke: invalid operator call 'MESH_OT_make_tetrahedron'"

Presumably the operator needs to be called with the 3D View having the focus, but this is not possible in my case.

How can I call bpy.ops.mesh.make_tetrahedron() and have it work from within Python code??

Many thanks!

Dan

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Mon Jul 22, 2013 11:23 am

some notes on the code:

One should use
- no whitespace between parathesis of class inheritance and the colon
- no CamelCase for variables (should only be used for class names) such as "TheCol", better comply to stock scripts and use "col"
- proper operator naming (primitive_tetrahedron_add instead of make_tetrahedron)
- bl_options = {'REGISTER', 'UNDO'} for such an operator, I see no reason for omitting the 'REGISTER'
- execute() for the actual operator code, not invoke() - this is what makes it PASS_THROUGH if called from commandline, since 'EXECUTE_DEFAULT' is the default here, in viewport it is 'INVOKE_DEFAULT'
- bpy.utils.register_module(__name__) over several register_class() calls IMO, so there's no chance you forget a class
- operator properties if needed. I would remove the "Update Down" checkbox from the panel and instead leave it up for the redo panel, but I left it it and forward the state to the operator
- from xxx import yyy here, so you can i.e. ommit "mathutils" in "mathutils.Vector"
- code that works similar to similar operators from user perspective, it should IMO clear current selection and make the new object selected/active
- a poll() function to prevent operator execution if it's not available (in this case, the operator has no code to add geometry to an existing mesh object if currently in editmode - like the stock operators do)
- scene.update() somewhere after scene.objects.link(...)
- PEP8 says there should be spaces around python operators (e.g. "="), but not inside a function call!

Here's the improved version, that can be called via command line:

Code: Select all

import bpy
from math import sqrt
from mathutils import Vector
 
class TetrahedronMakerPanel(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_context = "objectmode"
    bl_label = "Add Tetrahedron"
 
    def draw(self, context):
        col = self.layout.column(align=True)
        col.prop(context.scene, "make_tetrahedron_inverted")
        props = col.operator("mesh.make_tetrahedron", text="Add Tetrahedron")
        props.inverted = context.scene.make_tetrahedron_inverted
    #end draw
 
#end TetrahedronMakerPanel
 
class MakeTetrahedron(bpy.types.Operator):
    bl_idname = "mesh.primitive_tetrahedron_add"
    bl_label = "Add Tetrahedron_"
    bl_options = {'REGISTER', 'UNDO'}
 
    inverted = bpy.props.BoolProperty(
        name="Upside Down",
        description="Generate the tetrahedron upside down",
        default=False)
 
    @classmethod
    def poll(cls, context):
        return context.mode == 'OBJECT'

    def execute(self, context):
        scale = -1 if self.inverted else 1
        vertices = \
          [
            Vector((0, -1 / sqrt(3),0)),
            Vector((0.5, 1 / (2 * sqrt(3)), 0)),
            Vector((-0.5, 1 / (2 * sqrt(3)), 0)),
            Vector((0, 0, scale * sqrt(2 / 3))),
          ]
        me = bpy.data.meshes.new("Tetrahedron")
        me.from_pydata \
          (
            vertices,
            [],
            [[0, 1, 2], [0, 1, 3], [1, 2, 3], [2, 0, 3]]
          )
        me.update()
        ob = bpy.data.objects.new("Tetrahedron", me)
        context.scene.objects.link(ob)
        ob.location = context.scene.cursor_location
        
        bpy.ops.object.select_all(action='DESELECT')
        ob.select = True
        context.scene.objects.active = ob
        context.scene.update()
        
        return {"FINISHED"}
    #end invoke
 
#end MakeTetrahedron
 
def register() :
    bpy.utils.register_module(__name__)
    bpy.types.Scene.make_tetrahedron_inverted = bpy.props.BoolProperty \
      (
        name = "Upside Down",
        description = "Generate the tetrahedron upside down",
        default = False
      )
#end register
 
def unregister() :
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.make_tetrahedron_inverted
#end unregister
 
if __name__ == "__main__" :
    register()
#end if
I'm sitting, waiting, wishing, building Blender in superstition...

dvochin
Posts: 0
Joined: Mon Aug 20, 2012 9:39 pm

Post by dvochin » Mon Jul 22, 2013 7:32 pm

Hi Codemanx, many thanks for taking the time with these important corrections.

It works! I'm now able to call the operator from Python and a tetrahedron is created!

Unfortunately for me however, my attempt to call a blender function that requires to be called from the context of the 3dView from python is not working. (As it's a different topic I've rephrased my questions at this thread http://www.blender.org/forum/viewtopic. ... 749#105749)

Many thanks!

P.S. The code complains about the 'inverted' property when you run it...

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Tue Jul 23, 2013 3:12 am

you might wanna look into context overriding:

http://www.blender.org/documentation/bl ... ng-context

also check out the API docs about poll fail
I'm sitting, waiting, wishing, building Blender in superstition...

dvochin
Posts: 0
Joined: Mon Aug 20, 2012 9:39 pm

Post by dvochin » Tue Jul 23, 2013 4:33 am

Hi CodeManx, I'm trying with context overriding and changing execution context but after much reading resources like http://blenderartists.org/forum/showthr ... -incorrect don't seam to point to any answer.

Doing the exercise of running the following code is yielding some insight, but the problem I'm having is how to obtain from within a python script the proper objects to stuff into the context!

Code: Select all

def execute(self,context):
    print(self)
    print(dir(self))
    print(context)
    print(dir(context))
    return {'FINISHED'}
In other words, the following code seem to have the right approach but getting the proper objects to stuff into is like finding a needle in a haystack...

Code: Select all

import bpy
for window in bpy.context.window_manager.windows:
    screen = window.screen
    for area in screen.areas:
        if area.type == 'VIEW_3D':
            override = {'window': window, 'screen': screen, 'area': area}
            bpy.ops.screen.screen_full_area(override)
            break
Am I on the right track by calling bpy.ops.mesh.knife_project() with my override and stuffing the right data in there?

The question now is how to stuff in the right data... Am poring through .py code that comes with blender for strings like INVOKE_ and EXEC_ to see what they do...

Any hint?

Dan,

dvochin
Posts: 0
Joined: Mon Aug 20, 2012 9:39 pm

Post by dvochin » Wed Jul 24, 2013 1:59 am

For those following this I found a solution and I posted it at http://www.blender.org/forum/viewtopic. ... 783#105783

:)

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Wed Jul 24, 2013 2:24 am

what context member an operator requires is hard to tell, to be sure what it actually checks and uses, you need to dive into the C-code.

But you can also try to call an operator with an empty context and see the system console for PyContext messages, like

bpy.ops.object.select_all({})

it should list some of the required members. If you provide them, it might complain about others missing (and didn't tell you before). Beware of scene bases, they are often needed and if missing, blender might crash - and it rarely tells you about that they are required.
I'm sitting, waiting, wishing, building Blender in superstition...

dvochin
Posts: 0
Joined: Mon Aug 20, 2012 9:39 pm

Post by dvochin » Wed Jul 24, 2013 2:33 am

Yup... agreed there... had I not been tracing through blender's code for bpy.ops.mesh.knife_project() and its complex contexts checks it would have taken longer!

But you're right, the very helpful 'PyContext: 'xyz' not found log at least told me what was missing and it was a breeze to fix.

I recommend posting the code somewhere in blender's docs near the section about operator context override... tough nut for noobs to crack and documentation sure could use a code snippet...

:)

Post Reply