1
$\begingroup$

I've been trying to reuse certain parts of code as functions in my bpy scripts. But that means that the same bmesh will be created as 'my_bmesh.001' the next time I run that function. To deal with this, I join 'my_bmesh' with a different object, 'main_object' and then run the function again, hoping that it will notice that 'my_bmesh' doesn't exist anymore, because it was joined into 'main_object' thereby allowing me to avoid situations with .001 names. In one current case, where I make stairs using an add_stairs() function, that function makes 2 objects, 'stairs' and 'stairs_support'. At the end of the function, I join 'stairs_support' into 'stairs'. A different function runs that add_stairs() function twice, and here is where the problem happens. Since 'stairs_support' has already been joined to 'stairs', it no longer exists. Still, upon the next call to add_stairs(), instead of the new bmesh being named 'stairs_support', it would be named 'stairs_support.001' so when the script looks for 'stairs_support' to join it with 'stairs', it returns the error:

"KeyError: bpy_prop_collection[key]: key 'stairs_support' not found"

For now, I just have to check myself that the original object already exists, so it knows to go with the 'stairs_support.001' name, but I don't want to have to make something like a for-loop that goes until it figures out what number mesh the counter has gone up to. I know that the counter restarts when Blender starts up again, but I can't just restart Blender mid-script. How do I refresh this counter, after I join 'stairs_support' to 'stairs'? Or perhaps there is some other means of combating this issue without dealing with the naming counter?

My original script is full of 1AM math that somehow just works, so to keep the code as readable as possible, I created a new script in under 200 lines of code that demonstrates the issue I'm having.

Here is the script:

import bpy
import bmesh
from bpy_extras import object_utils

def add_box(width, height, depth, self, context):

    verts = [(+(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), +(height / 2.0), +(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), +(depth / 2.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),
            ]

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

    bm = bmesh.new()

    for v_co in verts:
        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
    object_utils.object_data_add(context, mesh, operator=self)

    width += 1.0
    verts = [(+(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), +(height / 2.0), +(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), +(depth / 2.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),
            ]

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

    bm = bmesh.new()

    for v_co in verts:
        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
    object_utils.object_data_add(context, mesh, operator=self)

    #THE WAY IT CURRENTLY FAILS:
    bpy.data.objects['Object1'].select=True
    bpy.data.objects['Object2'].select=True
    bpy.context.scene.objects.active = bpy.data.objects['Object1']
    bpy.ops.object.join()
    bpy.data.objects['Object1'].name = 'Final_object'

    #TO MAKE IT WORK, COMMENT OUT THE ABOVE (ALL LINES FROM 79 TO 83) AND UNCOMMENT THE SECTION BELOW:
    """
    #Join them and rename them
    bpy.ops.object.select_all(action='DESELECT')
    if bpy.data.objects.get('Object1') is not None:
        bpy.data.objects['Object1'].select=True
    else:
        bpy.data.objects['Object1.001'].select=True
    if bpy.data.objects.get('Object2') is not None:
        bpy.data.objects['Object2'].select=True
    else:
        bpy.data.objects['Object2.001'].select=True
    if bpy.data.objects.get('Object1') is not None:
        bpy.context.scene.objects.active = bpy.data.objects['Object1']
    else:
        bpy.context.scene.objects.active = bpy.data.objects['Object1.001']

    bpy.ops.object.join()

    if bpy.data.objects.get('Object1') is not None:
        bpy.data.objects['Object1'].name = 'Final_object'
    else:
        bpy.data.objects['Object1.001'].name = 'Final_object'
    """


from bpy.props import (
        BoolProperty,
        BoolVectorProperty,
        FloatProperty,
        FloatVectorProperty,
        )


class AddBox(bpy.types.Operator):
    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,
            )
    layers = BoolVectorProperty(
            name="Layers",
            description="Object Layers",
            size=20,
            options={'HIDDEN', 'SKIP_SAVE'},
            )

    # 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):

        add_box(self.width, self.height, self.depth, self, context)
        add_box(self.width, self.height, self.depth, self, context)

        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()

And a test .blend file:

$\endgroup$
  • $\begingroup$ I'm using Blender 2.79, if that makes a difference. $\endgroup$ – Perregrinne Jan 20 at 6:45
  • 2
    $\begingroup$ It would be far easier to help if you included your script rather than a description of your script. $\endgroup$ – batFINGER Jan 20 at 8:09
  • $\begingroup$ Let me know if the link to the script doesn't work. I've never really had to do this before. $\endgroup$ – Perregrinne Jan 21 at 15:59
1
$\begingroup$

Based on your original script the following changes 4 lines to store your newly created mesh data in variables that can be accessed instead of the name. The other changes are primarily with regards to pep8 compliance and removal of alternative method from your original code. It is likely that this can be improved upon. Furthermore the use of join will create orphan data that will be removed on save/reload of file may become excessive after multiple uses of the command. Also it was unclear as to whether or not you intended to have the execute duplicate the command so that was left as found.

import bpy
import bmesh
from bpy_extras import object_utils
from bpy.props import (
        BoolProperty,
        BoolVectorProperty,
        FloatProperty,
        FloatVectorProperty,
        )


def add_box(width, height, depth, self, context):

    verts = [(+(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), +(height / 2.0), +(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), +(depth / 2.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),
        ]

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

    bm = bmesh.new()

    for v_co in verts:
        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
    # and assign to variable
    obj1 = object_utils.object_data_add(context, mesh, operator=self)

    width += 1.0
    verts = [(+(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), -(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), -(depth / 2.0)),
             (+(width / 2.0), +(height / 2.0), +(depth / 2.0)),
             (+(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), -(height / 2.0), +(depth / 2.0)),
             (-(width / 2.0), +(height / 2.0), +(depth / 2.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),
        ]

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

    bm = bmesh.new()

    for v_co in verts:
        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
    # and assign to variable
    obj2 = object_utils.object_data_add(context, mesh, operator=self)
    # by default obj2 is both selected and active

    obj1.select = True

    bpy.ops.object.join()
    context.active_object.name = 'Final_object'


class AddBox(bpy.types.Operator):
    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,
            )
    layers = BoolVectorProperty(
            name="Layers",
            description="Object Layers",
            size=20,
            options={'HIDDEN', 'SKIP_SAVE'},
            )

    # 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):

        add_box(self.width, self.height, self.depth, self, context)
        add_box(self.width, self.height, self.depth, self, context)

        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()
$\endgroup$
  • $\begingroup$ I checked it out, and your script gives exactly the output I was hoping to find, thank you! $\endgroup$ – Perregrinne Jan 26 at 2:08

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.