Simple script request. Duplicating a group creates a new one

Good day; Maybe someone can help. I have a (seemingly) very small request which, to my inexperienced view, might be quite easy to implement for someone with experience, but is quite a problem for me due to my lack of experience with coding.
In essence: if there is a Group created and I duplicate it, right now Blender will add the duplicated objects to the same group. Is it possible to make a small script with a duplication option which will simply auto-assign the duplicated objects to a new group named (TheirPreviousGroupName)+(Number) and a) unlink them from their previous group + b) preserve their layer of origin (one group has objects on several layers)
The reason for this is that I’m working with a lot of branches with LODs (Hence several layers, one layer = one LOD. 4 LOD variants of a branch = 1 group. I allocate a branch group across several layers simultaneously so that all of its LODs share transforms, then isolate a specific layer and export it to a game-engine. So far it’s the best option I came up with.) and manually unlinking and renaming them every time is time-consuming because there are tons of them, each has to be positioned manually, and I start to confuse all the different group names accidentally adding duplicated object to already existing ones (“turns out there is already a group BranchBare Twisted i05; wait what was the last group with leafs I made” etc)
Blender 2.79
Thanks anyway
Regards

This duplicates selected objects, groups them, and places them on the original object’s layer.
The duplicate group’s name will always be the first group it finds in the selection plus an iterative number.

The script assumes you’ll only be working on one group - it doesn’t look for multiple groups.
Included some options as I wasn’t sure exactly how you want it to behave in some cases.

Default settings are:

  • make_linked_duplicate is probably self-explanatory
  • With select_duplicates set to True, duplicates are selected after running
  • With duplicate_all set to True, entire selection is duplicated regardless if any objects are ungrouped.

I also added an assertion to make the script halt if no objects in selection are grouped.

Run this in text editor with your group selected.

import bpy
from bpy import context as C, data as D


make_linked_duplicate = False

select_duplicates = True

duplicate_all = False


# duplication code
objs = C.selected_objects
g_objs = [o for o in C.selected_objects if o.users_group]
groups = set(g for o in g_objs for g in o.users_group)
assert groups, "No groups found in selection"
dupes = list()
found_group = False

for o in (g_objs if not duplicate_all else objs):
    if not found_group:
        grp = D.groups.new(o.users_group[0].name)
        found_group = True
            
    d = o.copy()
    if make_linked_duplicate:
        d.data = o.data.copy()
    dupes.append(d)
    C.scene.objects.link(d)
    grp.objects.link(d)
    d.layers = o.layers
    
if select_duplicates:
    for o in objs: o.select = False
else:
    for o in dupes: o.select = False
    
C.scene.update()
1 Like

Works wonderfully, thank you!

There are multiple “prime” groups but as long as it saves me from manually renaming and unlinking them every time I duplicate it’s more than enough!

I have tried to create a UI panel for the script following different tutorials; Predictably it went horribly wrong pretty fast, the best result achieved was a panel with check boxes operational but I wasn’t able to hook up the actual script to it. If it won’t take too much time could you please by any chance look into it?

import bpy

from bpy import context as C, data as D

from bpy.props import (BoolProperty,
                       )
from bpy.types import (Panel,
                       Operator,
                       AddonPreferences,
                       PropertyGroup,
                       )

# Bool values

class Values(PropertyGroup):

    make_linked_duplicate = BoolProperty(
        default = False
        )

    select_duplicates = BoolProperty(
        default = True
        )

    duplicate_all = BoolProperty(
        default = False
        )

# Trying to make an operator to call it from UI

class DupliGroupOP(operator):
    bl_idname = "object.dupligroup"
    bl_label = "Duplicate Group"
    
    objs = C.selected_objects
    g_objs = [o for o in C.selected_objects if o.users_group]
    groups = set(g for o in g_objs for g in o.users_group)
    assert groups, "No groups found in selection"
    dupes = list()
    found_group = False
    
    def execute(self, C):
        for o in (g_objs if not duplicate_all else objs):
            if not found_group:
                grp = D.groups.new(o.users_group[0].name)
                found_group = True
            
        d = o.copy()
        if make_linked_duplicate:
            d.data = o.data.copy()
        dupes.append(d)
        C.scene.objects.link(d)
        grp.objects.link(d)
        d.layers = o.layers
    
        if select_duplicates:
            for o in objs: o.select = False
        else:
            for o in dupes: o.select = False
    
        C.scene.update()

# UI

class CustomPanel(Panel):
    """A Group Duplication Panel"""
    bl_idname = "Group Duplication Panel"
    bl_label = "Group Duplication Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        mytool = scene.my_tool

        # display
        layout.prop(mytool, "make_linked_duplicate", text="Link duplicates")
        row = layout.row()
        layout.prop(mytool, "select_duplicates", text="Select duplicates")
        row = layout.row()
        layout.prop(mytool, "duplicate_all", text="Duplicate ungrouped")
        row = layout.row()
        row.operator("object.dupligroup")
        
       
# register/unregister

def register():
    bpy.utils.register_module(__name__)
    bpy.utils.register_class(DupliGroupOP)
    bpy.types.Scene.my_tool = PointerProperty(type=Values)

def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.utils.unregister_class(DupliGroupOP)
    del bpy.types.Scene.my_tool

if __name__ == "__main__":
    register()

Well you had the gist of it.

Usually operator properties are stored on the operator itself, however in this case it isn’t suitable because some operations cannot be undone properly (eg. changing between true and linked duplicate, Blender doesn’t know what to do if you swap the setting). So an external property group with predefined settings as you thought is a better approach.

This add a panel in Scene tab (Object tab is usually contextual and follows selection, so it fits more in Scene), with the properties. I’m sure you can build on this to have it do things more suitable to you at this point.

Only missing bl_info for making it into an addon. I’m sure you’ll manage :stuck_out_tongue:

import bpy
from bpy.props import (
                    BoolProperty,
                    PointerProperty,)
from bpy.types import (
                    PropertyGroup,
                    Operator,
                    Panel,
                    OperatorProperties,)


class SCENE_OT_DuplicateGroup(Operator):
    bl_idname = 'scene.duplicate_group'
    bl_label = 'Duplicate Group'
    bl_options = {'REGISTER', 'UNDO'}
    
    @classmethod
    def poll(self, context):
        a = context.scene
        b = context.mode == 'OBJECT'
        return a and b
    
    def execute(self, context):
        # Get the bools from scene.duplicate_group
        props = context.scene.duplicate_group
        make_linked_duplicate = props.linked
        select_duplicates = props.select
        duplicate_all = props.all
        
        C, D = context, context.blend_data
        objs = C.selected_objects
        g_objs = [o for o in C.selected_objects if o.users_group]
        groups = set(g for o in g_objs for g in o.users_group)
        assert groups, "No groups found in selection"
        dupes = list()
        found_group = False

        for o in (g_objs if not duplicate_all else objs):
            if not found_group:
                grp = D.groups.new(o.users_group[0].name)
                found_group = True
                    
            d = o.copy()
            if make_linked_duplicate:
                d.data = o.data
            else:
                d.data = o.data.copy()
            dupes.append(d)
            C.scene.objects.link(d)
            grp.objects.link(d)
            d.layers = o.layers

        if select_duplicates:
            for o in objs: o.select = False
        else:
            for o in dupes: o.select = False

        C.scene.update()
        return {'FINISHED'}

# Make some properties for the UI and operator to use
class SCENE_PG_DuplicateGroup(PropertyGroup):
    
    linked = BoolProperty(
        name="Make Linked Duplicate",
        default=False,)
                            
    select = BoolProperty(
        name="Select Duplicates",
        default=True,)
                            
    all = BoolProperty(
        name="Duplicate All",
        default=False,)


class SCENE_PT_DuplicateGroup(Panel):
    bl_label = 'Duplicate Group'
    bl_idname = 'SCENE_PT_duplicate_group'
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = 'scene'
    
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        props = scene.duplicate_group
        
        col = layout.column()
        col.label(text="Duplicate Group")
        
        col.prop(props, "linked")
        col.prop(props, "select")
        col.prop(props, "all")
        if not 'OBJECT' in context.mode:
            col.enabled = False
        layout.operator_context = "INVOKE_DEFAULT"
        op = layout.operator("scene.duplicate_group", "Duplicate Group")
                
        
classes = [
    SCENE_OT_DuplicateGroup,
    SCENE_PG_DuplicateGroup,
    SCENE_PT_DuplicateGroup,]


def register():
    for c in classes:
        bpy.utils.register_class(c)
        
    # Add the new property group to a namespace in scene
    pg = SCENE_PG_DuplicateGroup
    scene = bpy.types.Scene
    scene.duplicate_group = PointerProperty(type=pg)
    
def unregister():
    for c in reversed(classes):
        bpy.utils.unregister_class(c)
    
if __name__ == '__main__':
# live edit only
#    try:
#        for c in reversed(classes):
#            bpy.utils.unregister_class(c)
#    except:
#        None
    register()
2 Likes

I will:) Thank you for all the help! The script works like a charm and is a real time saver!

1 Like