# Preserving sub-group instances when making duplicates real

I'm writing some Python scripts for refactoring, one of which will be used for relocating a group to another file. The workflow will be to make the new file, open it, link the group, select the group instance, then run a 'Make Source' script.

That script will involve making the top-level group duplicates real, but not the sub-groups, then creating a new group with the same name as the old one, then going through all '.blend' files within a project directory and relinking the group instances within them to the new group, then maybe deleting the old original.

The first part of the script is where I am have problems: making the top-level group duplicates real, but not the sub-groups.

bpy.ops.object.duplicates_make_real() does not seem to do what I need because it just breaks down the sub-groups, though it just throws an error when I run it with Python with arguments.

There's also dupli_group and dupli_list object operators, butdupli_group just gives the top-level group and dupli-list seems to be empty.

object.dupli_list_create(context.scene) just given 'None', though I'm probably using it wrong.

Edit 1: I have been able access dupli-list, but I'm not sure how useful the information held in the DupliObject struct will be.

Edit 2: I think Blender holds as little information on group instances as possible, as such I think what I will need to do is access the source '.blend' file, then directly link the objects that are not in sub-groups and the sub-group instances with their offset.

The following script works with the workflow in the question:

make the new file, open it, link the group, select the group instance, then run a 'Make Source' script.

What the script does:

• Takes the active object, which should be an externally linked DupliGroup.
• Checks if the name of the DupliGroup already exists as a local group and stops the script if it does.
• Stores DupliGroup name, and external library path.
• Deletes the active object as it is not necessary anymore.
• Links the objects from the library '.blend', then links them, makes a proxy object, then deletes the non-proxy objects.
• Adds the proxy objects to a group with the same name as the DupliGroup.

Of course the script doesn't actually make this group the source, more would need to be added to do that. It just appends the contents of a group to a local group of the same name.

Forgive lack of convention in the code.

make_source.py, just run the script from Blender's internal text editor after selecting an externally linked DupliGroup:

# A class just for the purposes of stopping the script early without closing Blender.
class ExitWrongInput(Exception):
def __init__(self, to_print):
print("ExitWrongInput: %s" % to_print)

# In this with statement, the data_to is just a list of strings, but after the with statement, it is actual data, which is what we return.
data_to.groups = [group_name]

return data_to.groups[0]

def make_source():
# Variable setup.
# ----------
scene = bpy.context.scene
obj = scene.objects.active
obj_dupligroup_name = obj.dupli_group.name

# TODO Add a proper check to determine if the active is an externally linked DupliGroup object.

# Check if the group already exists and exit the script if it does.
for group_iter in bpy.data.groups:
if (group_iter.name == obj_dupligroup_name) and (group_iter.library is None):
raise ExitWrongInput('Group "%s" already exists.' % obj_dupligroup_name)

libraries = bpy.data.libraries

# Find library which the DupliGroup is in and store the path.
obj_library_path = ""
for library in libraries:
for user in library.users_id:
if user.name == obj_dupligroup_name:
obj_library_path = library.filepath
break
# ----------

# Delete obj.
bpy.ops.object.delete()

# Link the old source group to the scene.
# ----------

# No objects can be selected for the next part, so deselect all objects just in case.
for obj_iter in bpy.data.objects:
obj_iter.select = False

# Link the objects to the scene then make proxies of them by making duplicate linked, and deleting the old ones.
for obj_iter in group_data.objects:
# Link the objects to the scene, it will not be possible to move them.
# Duplicate to a variable, not linked yet.
# Delete the old object that could not be moved.
# Link the new proxy object.
# Add to selection for grouping.

# Create new group from the selected objects.
bpy.ops.group.create(name=obj_dupligroup_name)
# ----------

# Update the scene just in case.
bpy.context.scene.update()

try:
make_source()
except ExitWrongInput:
pass