3
$\begingroup$

I have an operator that creates a certain mesh but I want to keep things modular so I separated each operation into its own operator. Like for example, I have an operator that removes vertices that don't have any edges linked to them, and I can reuse that in other operators. My main operator calls these 5 operators:

class MyTaskOperator(bpy.types.Operator):
    def execute(self, context):
        bpy.ops.mesh.do_something_1()
        bpy.ops.mesh.do_something_2()
        bpy.ops.mesh.do_something_3()
        bpy.ops.mesh.do_something_4()
        bpy.ops.mesh.do_something_5()

The thing is each operator instantiates a bmesh and then does a bm.free(). Is that okay? Or should I instantiate the bm beforehand and pass it as reference/pointer to each of these operators and then do the bm.free() at the end of the main operator? like this:

class MyTaskOperator(bpy.types.Operator):
    def execute(self, context):
        bm = bmesh()
        bpy.ops.mesh.do_something_1(bm)
        bpy.ops.mesh.do_something_2(bm)
        bpy.ops.mesh.do_something_3(bm)
        bpy.ops.mesh.do_something_4(bm)
        bpy.ops.mesh.do_something_5(bm)
        bm.free()

But to do this I would need a bpy.props.BMeshProperty? But that doesn't exist so it's not possible to pass a bmesh property. You can only pass StringProperty, FloatProperty, BoolProperty, and PointerProperty (which are just used to organize properties and minimize namespace pollution)

which brings me also to another point why I want to avoid instantiating the object in the execute function because for example I have

def execute(self, context: bpy_types.Context) -> set[str]:
        # function gets called every time I tweak something
        # in the Adjust Last Operation panel so it keeps 
        # instantiating these objects multiple times.
        image = bpy.data.images.get(self.image_name)
        mesh = bpy.data.meshes.new(image.name)
        plane = bpy.data.objects.new(image.name, mesh)
        context.collection.objects.link(plane)
        mesh = plane.data
        bm = bmesh.new()

and whenever I change or tweak something in the Adjust Last Operation panel, doesn't this keep calling the execute function which keeps instantiating several new objects into memory every time I, for example, slide a FloatProperty scroller and it updates the mesh on the fly? Isn't that not memory friendly than if I first instantiated all these objects only once before calling the operator and passed these objects into the operator?

$\endgroup$

1 Answer 1

3
+100
$\begingroup$

Blender operator designed for shortcuts, and not all variable types can be passed.
Avoid running bpy.ops inside bpy.ops.
Here's an example to reused the 3 operator:

import bpy, bmesh
from bpy.types import Operator

def poll_edit_mesh(context):
    if context.area.type != "VIEW_3D": return False
    ob = context.object
    if not hasattr(ob, "mode") or ob.mode != "EDIT": False
    if ob.type != "MESH": return False
    return True

def get_bm(ob): return bmesh.from_edit_mesh(ob.data)
def bm_free_and_update(bm, ob):
    bm.free()
    ob.data.update()

def ops_A(bm):
    ..
def ops_B(bm):
    ..
def ops_main(bm):
    ops_A(bm)
    ops_B(bm)
    ..


class OpsMain(Operator):
    bl_idname = "mesh.ops_main"
    bl_label = "Ops Main"

    @classmethod
    def poll(cls, context): return poll_edit_mesh(context)

    def execute(self, context):
        bm = get_bm(context.object)
        ops_main(bm)
        bm_free_and_update(bm, context.object)
        return {'FINISHED'}


class OpsA(Operator):
    bl_idname = "mesh.ops_A"
    bl_label = "Ops A"

    @classmethod
    def poll(cls, context): return poll_edit_mesh(context)

    def execute(self, context):
        bm = get_bm(context.object)
        ops_A(bm)
        bm_free_and_update(bm, context.object)
        return {'FINISHED'}


class OpsB(Operator):
    bl_idname = "mesh.ops_B"
    bl_label = "Ops B"

    @classmethod
    def poll(cls, context): return poll_edit_mesh(context)

    def execute(self, context):
        bm = get_bm(context.object)
        ops_B(bm)
        bm_free_and_update(bm, context.object)
        return {'FINISHED'}



def register():
    ..

def unregister():
    ..


if __name__ == "__main__":
    register()

    # test call
    with bpy.context.temp_override(active_object=bpy.data.objects["Cube"]):
        bpy.ops.mesh.ops_main()

Use inheritance

import bpy, bmesh
from bpy.types import Operator


def get_bm(ob): return bmesh.from_edit_mesh(ob.data)
def bm_free_and_update(bm, ob):
    bm.free()
    ob.data.update()

def ops_A(bm):
    ..
def ops_B(bm):
    ..
def ops_main(bm):
    ops_A(bm)
    ops_B(bm)
    ..


class PollEditMesh(Operator):
    __slots__ = ()

    func = None

    @classmethod
    def poll(cls, context):
        if context.area.type != "VIEW_3D": return False
        ob = context.object
        if not hasattr(ob, "mode") or ob.mode != "EDIT": False
        if ob.type != "MESH": return False
        return True

    def execute(self, context):
        bm = get_bm(context.object)
        self.__class__.func(bm)
        bm_free_and_update(bm, context.object)
        return {'FINISHED'}

class OpsMain(PollEditMesh):
    bl_idname = "mesh.ops_main"
    bl_label = "Ops Main"

    func = ops_main


class OpsA(PollEditMesh):
    bl_idname = "mesh.ops_A"
    bl_label = "Ops A"

    func = ops_A


class OpsB(PollEditMesh):
    bl_idname = "mesh.ops_B"
    bl_label = "Ops B"

    func = ops_B



def register():
    ..

def unregister():
    ..


if __name__ == "__main__":
    register()

    # test call
    with bpy.context.temp_override(active_object=bpy.data.objects["Cube"]):
        bpy.ops.mesh.ops_main()
$\endgroup$
1
  • $\begingroup$ Hi thank you for sharing your answer! I will check it out $\endgroup$
    – Megan Love
    Commented May 8 at 6:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .