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()
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
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()
I will:) Thank you for all the help! The script works like a charm and is a real time saver!