3
$\begingroup$

I'm learning bpy myself, and I don't have much knowledge of python。

I read the templates in text editor to learn scripting myself, but I found some thing weird in the Operator Mesh Add template:

import bpy
import bmesh


def add_box(width, height, depth):
    """
    This function takes inputs and returns vertex and face arrays.
    no actual mesh data creation is done here.
    """

    verts = [(+1.0, +1.0, -1.0),
             (+1.0, -1.0, -1.0),
             (-1.0, -1.0, -1.0),
             (-1.0, +1.0, -1.0),
             (+1.0, +1.0, +1.0),
             (+1.0, -1.0, +1.0),
             (-1.0, -1.0, +1.0),
             (-1.0, +1.0, +1.0),
             ]

    faces = [(0, 1, 2, 3),
             (4, 7, 6, 5),
             (0, 4, 5, 1),
             (1, 5, 6, 2),
             (2, 6, 7, 3),
             (4, 0, 3, 7),
            ]

    # apply size
    for i, v in enumerate(verts):
        verts[i] = v[0] * width, v[1] * depth, v[2] * height

    return verts, faces


from bpy.props import FloatProperty, BoolProperty, FloatVectorProperty


class AddBox(bpy.types.Operator):
    """Add a simple box mesh"""
    bl_idname = "mesh.primitive_box_add"
    bl_label = "Add Box"
    bl_options = {'REGISTER', 'UNDO'}

    width = FloatProperty(
            name="Width",
            description="Box Width",
            min=0.01, max=100.0,
            default=1.0,
            )
    height = FloatProperty(
            name="Height",
            description="Box Height",
            min=0.01, max=100.0,
            default=1.0,
            )
    depth = FloatProperty(
            name="Depth",
            description="Box Depth",
            min=0.01, max=100.0,
            default=1.0,
            )

    # generic transform props
    view_align = BoolProperty(
            name="Align to View",
            default=False,
            )
    location = FloatVectorProperty(
            name="Location",
            subtype='TRANSLATION',
            )
    rotation = FloatVectorProperty(
            name="Rotation",
            subtype='EULER',
            )

    def execute(self, context):

        verts_loc, faces = add_box(self.width,
                                   self.height,
                                   self.depth,
                                   )

        mesh = bpy.data.meshes.new("Box")

        bm = bmesh.new()

        for v_co in verts_loc:
            bm.verts.new(v_co)

        bm.verts.ensure_lookup_table()
        for f_idx in faces:
            bm.faces.new([bm.verts[i] for i in f_idx])

        bm.to_mesh(mesh)
        mesh.update()

        # add the mesh as an object into the scene with this utility module
        from bpy_extras import object_utils
        object_utils.object_data_add(context, mesh, operator=self)

        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(AddBox.bl_idname, icon='MESH_CUBE')


def register():
    bpy.utils.register_class(AddBox)
    bpy.types.INFO_MT_mesh_add.append(menu_func)


def unregister():
    bpy.utils.unregister_class(AddBox)
    bpy.types.INFO_MT_mesh_add.remove(menu_func)

if __name__ == "__main__":
    register()

    # test call
    bpy.ops.mesh.primitive_box_add()

self is used outside class indef menu_func(self, context):.

I was taught that self can only be used inside a class, so I'm confused.

Anyone can help me?

$\endgroup$
2
$\begingroup$

The answer lies in what happens in the register function, below the menu_func:

def register():
    bpy.utils.register_class(AddBox)
    bpy.types.INFO_MT_mesh_add.append(menu_func) # <<< This is the interesting bit

As you can see here, the menu_func is appended to the INFO_MT_mesh_add type as a new method. INFO_MT_mesh_add is the Add Mesh Menu in the blender UI.

This is the class where you will be adding the menu_func to, not the add_box operator.

This function is added to the Add Mesh Menu, and when you call it from the blender UI, it will be executed like any method. That means that it will get the class instance as the function's first parameter, "self".

The word "self" has no real meaning by itself, and is merely the first parameter this function receives. It is a convention in python (and some other languages) to call it "self", but you could call it "james" and it would work just fine.

When the function is a method in a class, it automatically gets the instance of the class as the first parameter when called from the instance (or the class itself if its a @classmethod, in which case we usually call the first parameter cls instead of self).

class someClass:
    def myMethod( self ):
        print( "I get the class instance as my first param 'self'" )
        print( self )

clsInstance = someClass()
clsInstance.myMethod() # myMethod will get clsInstance as the first parameter

We could write it this way though, and it would work just the same:

def to_be_method( self ):
    print( "I get the class instance as my first param 'self'" )
    print( self )

class someClass:
    # Reference to_be_method as our class' own myMethod
    myMethod = to_be_method 

clsInstance = someClass()
clsInstance.myMethod() # myMethod will get clsInstance as the first parameter

So as you can see in the example above, we define a function outside the class and used the "self" parameter, and then referenced this function inside the class.

Appending the menu_func into the INFO_MT_mesh_add class does something similar, which is why it's fine and works well.

I hope this explains the situation rather than confuse you even more! :)

$\endgroup$
2
$\begingroup$

The self argument in menu_func(self, context) could also have been written as:

def menu_func(caller, context):
    caller.layout.operator(AddBox.bl_idname, icon='MESH_CUBE')

The fact that it is written as self is a choice and a convention. The name isn't as important as the position of the arguments and the number of arguments in that function. Using self, context helps you and others see that it is being called from another place that does have a self and context.

Why a menu_func at all?

Using a function like menu_func() allows us to describe a portion of the layout from our own add-on. If you couldn't specify it externally you would be forced to always edit your menu additions to INFO_MT_mesh_add manually, and each time you upgrade to a new version of Blender.

prepend(menu_func) vs append(menu_func)

Let's say the draw function of INFO_MT_mesh_add looks like this:

def draw(self, context):
    layout = self.layout

    # menu_func is called here if you used INFO_MT_mesh_add.prepend(menu_func)
    # in the registration function

    ...existing menu items ...

    # menu_func is called here if you used INFO_MT_mesh_add.append(menu_func)

For example if you use .append, you'll get your item at the end of the draw function, you might imagine that the code that python executed is equivalent to

def draw(self, context):
    layout = self.layout

    ...existing menu items ...

    menu_func(self, context)

That means inside the draw function, the function menu_func is called with the self and context available to the drawing function at the time of calling.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.