If this is not the correct place to post this, please let me know and I’ll move it.
Also: this is my first foray into coding in Blender, so I kind of have no idea what I am doing. This may be a mess. It hasn’t been heavily tested or vetted. So if anyone finds any bugs, has any improvements, or just wants to do some finger wagging, I am all ears!
The idea is to implement “grouping” in the outliner in a way that is analogous to how Maya does it. I know that this isn’t real grouping as defined by Blender. In reality, what this is is a shortcut to take a selection, create an empty at the same point in the transform hierarchy, and then to parent all the items in the selection to that new empty. I needed this because I am modeling in Blender, but rendering in Clarisse iFX and need to have a transform hierarchy that encodes my “grouping” intentions in the transform hierarchy and is preserved when saved in an alembic format.
The addon is based on the included Blender addon mesh - Parent_to_empty. I just modified it to work anywhere within the transform hierarchy, and to use world-space coordinates to make sure nothing moves during the parenting operation. I also made some changes to the original addon where it failed to work in world-space coordinates because it only ever worked on objects that originally had no parents.
Enjoy!
Edit: August 19th, 2018. I am fixing line 59 that had some weird, extra formatting characters in it from when I pasted in the original code.
# GPL # Original Author Liero, Modified by bvz2000 #
bl_info = {
"name": "Maya style grouping",
"category": "Object"
}
import bpy
from bpy.types import Operator
from bpy.props import (
StringProperty,
BoolProperty,
EnumProperty,
)
def averageCenter(sel):
print(len(sel))
x = sum([obj.matrix_world.to_translation().x for obj in sel]) / len(sel)
y = sum([obj.matrix_world.to_translation().y for obj in sel]) / len(sel)
z = sum([obj.matrix_world.to_translation().z for obj in sel]) / len(sel)
return (x, y, z)
class MayaStyleGrouping(Operator):
bl_idname = "object.maya_group"
bl_label = "Maya Style Grouping"
bl_description = "Parent selected objects to a new Empty, maya style"
bl_options = {"REGISTER", "UNDO"}
groupName = StringProperty(
name="",
default='group',
description='Give the empty / group a name'
)
alsoCreateGroup = BoolProperty(
name="Create Group",
default=False,
description="Also add objects to a group"
)
location = EnumProperty(
name='',
items=[('CURSOR', 'Cursor', 'Cursor'),
('ACTIVE', 'Active', 'Active'),
('CENTER', 'Center', 'Selection Center'),
('ORIGIN', 'Origin', 'World Origin')],
description='Transform Group location',
default='CENTER'
)
doRename = BoolProperty(
name="Add Prefix",
default=False,
description="Add prefix to objects name"
)
@classmethod
def poll(cls, context):
selectedItems = context.selected_objects
return len(selectedItems)
def draw(self, context):
layout = self.layout
layout.prop(self, "groupName")
column = layout.column(align=True)
column.prop(self, "location")
column.prop(self, "alsoCreateGroup")
column.prop(self, "doRename")
def getParentDepth(self, item):
parent = item.parent
depth = 0
while parent is not None:
depth += 1
parent = parent.parent
return depth
def execute(self, context):
# Build a list of the selected objects, the currently selected object,
# and the scene
selectedItems = context.selected_objects
act = context.object
sce = context.scene
# Do something here, I don't know what this does
try:
bpy.ops.object.mode_set()
except:
pass
# Identify the location for the new empty
if self.location == 'CURSOR':
loc = sce.cursor_location
elif self.location == 'ACTIVE':
loc = act.location
elif self.location == 'CENTER':
loc = averageCenter(selectedItems)
else:
loc = (0, 0, 0)
# Find the highest level parent. This is because if more than one object in different
# hierarchies are selected, the resulting "group" of objects will be
# parented under an empty that lives under the first, "highest" level
# parent.
parentDepthD = dict()
for item in selectedItems:
try:
parentDepthD[self.getParentDepth(item)].append(item)
except KeyError:
parentDepthD[self.getParentDepth(item)] = [item]
keys = list(parentDepthD.keys())
keys.sort()
highestLevelParent = parentDepthD[keys[0]][0].parent
# Create the new empty in its correct location
bpy.ops.object.add(type='EMPTY', location=(loc))
newParent = context.object
newParent.name = self.groupName
if highestLevelParent is not None:
savedMatrix = newParent.matrix_world
newParent.parent = highestLevelParent
newParent.matrix_world = savedMatrix
# Uncomment the next two lines if you want your empty to have its name
# automatically displayed in the 3DView, and it to have xray turned on.
# I don't like those features so I turn them off by default.
# newParent.show_name = True
# newParent.show_x_ray = True
if self.alsoCreateGroup:
bpy.ops.group.create(name=self.groupName)
bpy.ops.group.objects_add_active()
for item in selectedItems:
item.select = True
savedMatrix = item.matrix_world
item.parent = newParent
item.matrix_world = savedMatrix
if self.alsoCreateGroup:
bpy.ops.group.objects_add_active()
item.select = False
for item in selectedItems:
if self.doRename:
item.name = self.groupName + '_' + item.name
return {'FINISHED'}
def register():
bpy.utils.register_class(MayaStyleGrouping)
def unregister():
bpy.utils.unregister_class(MayaStyleGrouping)
# This allows you to run the script directly from blenders text editor
# to test the addon without having to install it.
if __name__ == "__main__":
register()