Apply-modifiers-and-join-to-new-object operator

For my modeling work, I found that I often need to do the following things before exporting:

  • select objects
  • for each object apply the modifiers
  • join the objects
  • triangulate the objects

Now I created an operator that does exactly that. It creates a copy of the selected objects, applies the modifiers for each, joins the objects and triangulates them.

Here it is:


import bpy

bl_info = {
    "name": "Merge objects and apply modifiers",
    "description": "Creates a copy of the selected objects, applies their modifiers, and joins them into a single mesh",
    "author": "Jonatan Bijl",
    "version": (0, 1, 0),
    "blender": (2, 6, 4),
    "api": 44136,
    "location": "context menu",
    "category": "Object"}

def main(context):
    #make a list of the selected objects of type 'mesh'
    objs = [obj for obj in context.selected_objects if obj.type == 'MESH']
    active_obj = context.object
    new_active_obj = None
    
    bpy.ops.object.select_all(action='DESELECT')
    
    new_objs = []
    for obj in objs:
        new_mesh = obj.to_mesh(context.scene, True, 'PREVIEW')
        new_obj = bpy.data.objects.new('{}_tmp'.format(new_mesh.name), new_mesh)
        context.scene.objects.link(new_obj)
        #triangulate
        context.scene.objects.active = new_obj
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.quads_convert_to_tris()
        bpy.ops.object.mode_set(mode='OBJECT')
        new_obj.location = obj.location
        new_obj.rotation_euler = obj.rotation_euler
        new_obj.scale = obj.scale
        new_objs.append(new_obj)
        new_obj.select = True
        if obj == active_obj:
            new_active_obj = new_obj
    
    #make sure we have a new active object selected
    if new_active_obj:
        context.scene.objects.active = new_active_obj
    else:
        new_mesh = bpy.data.meshes.new('joined')
        new_obj = bpy.data.objects.new('joined', new_mesh)
        context.scene.objects.link(new_obj)
        new_obj.select = True
        context.scene.objects.active = new_obj
    
    bpy.ops.object.join()

class MergeApplyOperator(bpy.types.Operator):
    """Create a joined copy of selected meshes, with modifiers applied if needed"""
    bl_idname = "object.merge_apply"
    bl_label = "Merge objects and apply modifiers"

    @classmethod
    def poll(cls, context):
        return len(context.selected_objects) > 0

    def execute(self, context):
        main(context)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(MergeApplyOperator)


def unregister():
    bpy.utils.unregister_class(MergeApplyOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.merge_apply()


coool! exept the triangulation, I think is very useful! thankyou!

I added an extra feature. If the input objects contain group instances, they are also made real and joined into the result.


import bpy

bl_info = {
    "name": "Merge objects and apply modifiers",
    "description": "Creates a copy of the selected objects, applies their modifiers, and joins them into a single mesh",
    "author": "Jonatan Bijl",
    "version": (0, 1, 2),
    "blender": (2, 6, 4),
    "api": 44136,
    "location": "context menu",
    "category": "Object"}

import bmesh, bpy, mathutils
from bpy.props import StringProperty

def main(context, name='joined'):
    #First store references to the objects we want to work on, because in the process we will do a deselect
    objs = [obj for obj in context.selected_objects if obj.type == 'MESH']
    group_instances = [obj for obj in context.selected_objects if obj.dupli_group]
    
    bpy.ops.object.select_all(action='DESELECT')
    
    #list of objects that we will need to delete afterwards
    group_duplicates = []
    for group_instance in group_instances:
        clone = group_instance.copy()
        context.scene.objects.link(clone)
        clone.select = True
        
    #by now we have only the clones of the dupligroups selected
    bpy.ops.object.duplicates_make_real()
    
    #the newly created duplicate objects are all selected
    for obj in context.selected_objects:
        print('{} {}'.format(group_instance, obj))
        group_duplicates.append(obj)
        if obj.type == 'MESH':
            objs.append(obj)
    
    bpy.ops.object.select_all(action='DESELECT')
    
    new_objs = []
    for obj in objs:
        #create a mesh with the operators applied
        new_mesh = obj.to_mesh(context.scene, True, 'PREVIEW')
        #Create a new object which has the same parent
        new_obj = bpy.data.objects.new('{}_tmp'.format(new_mesh.name), new_mesh)
        new_obj.parent = obj.parent
        context.scene.objects.link(new_obj)
        #apply object-linked materials to the mesh itself
        for mat_slot in new_obj.material_slots.values():
            if  mat_slot.link == 'OBJECT':
                mat = mat_slot.material = material
                mat_slot.link = 'DATA'
                mat_slot.material = mat
        #triangulate
        context.scene.objects.active = new_obj
        bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.quads_convert_to_tris()
        bpy.ops.object.mode_set(mode='OBJECT')
        new_obj.location = obj.location
        new_obj.rotation_euler = obj.rotation_euler
        new_obj.scale = obj.scale
        new_objs.append(new_obj)
        new_obj.select = True

    new_mesh = bpy.data.meshes.new(name)
    new_obj = bpy.data.objects.new(name, new_mesh)
    context.scene.objects.link(new_obj)
    new_obj.select = True
    context.scene.objects.active = new_obj

    bpy.ops.object.join()
    
    #delete the temporary objects we generated when handling the dupligroups
    bpy.ops.object.select_all(action='DESELECT')
    for obj in group_duplicates:
        obj.select = True
    bpy.ops.object.delete()
    
    #now select the result
    new_obj.select = True
    context.scene.objects.active = new_obj
    
    new_obj.data.show_double_sided = False

from bpy.props import StringProperty

class MergeApplyOperator(bpy.types.Operator):
    """Create a joined copy of selected meshes, with modifiers applied if needed"""
    bl_idname = "object.merge_apply"
    bl_label = "Merge objects and apply modifiers"
    bl_options = {'REGISTER', 'UNDO'}

    object_name = StringProperty(name='object_name', description='The name for the new mesh and object', default='joined')
    @classmethod
    def poll(cls, context):
        return len(context.selected_objects) > 0

    def execute(self, context):
        main(context, self.object_name)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(MergeApplyOperator)


def unregister():
    bpy.utils.unregister_class(MergeApplyOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.merge_apply()

thx for sharing!

i change it a bit to have the appility to let the mesh as quads:


class MergeApplyOperator(bpy.types.Operator):
    """Create a joined copy of selected meshes, with modifiers applied if needed"""
    bl_idname = "object.merge_apply"
    bl_label = "Merge objects and apply modifiers"
    bl_options = {'REGISTER', 'UNDO'}

    object_name = StringProperty(name='object_name', description='The name for the new mesh and object', default='joined')
    tri = bpy.props.BoolProperty(name="Triangulation",  description="Triangulation", default=False) 

    @classmethod
    def poll(cls, context):
        return len(context.selected_objects) > 0

    def execute(self, context):
        main(context, self.object_name)
        
        for i in range(self.tri):
            bpy.ops.object.editmode_toggle()
            bpy.ops.mesh.select_all(action='SELECT')        
            bpy.ops.mesh.quads_convert_to_tris()        
            bpy.ops.object.editmode_toggle()
        
        return {'FINISHED'}