Object collections manager addon

This is an idea presented at T53495. I'm not sure if we should have this as a
built-in UI functionality, but the addon allows us to test and evaluate it.
This commit is contained in:
Dalai Felinto 2017-12-28 13:03:32 -02:00
parent 05688be3be
commit 8471942e21
1 changed files with 309 additions and 0 deletions

309
object_collections.py Normal file
View File

@ -0,0 +1,309 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from bpy.types import (
Operator,
Panel,
Menu,
)
from bpy.props import (
EnumProperty,
IntProperty,
)
bl_info = {
"name": "Collections",
"author": "Dalai Felinto",
"version": (1, 0),
"blender": (2, 80, 0),
"description": "Panel to set/unset object collections",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Object"}
# #####################################################################################
# Operators
# #####################################################################################
class OBJECT_OT_collection_add(Operator):
"""Add an object to a new collection"""
bl_idname = "object.collection_add"
bl_label = "Add to New Collection"
def execute(self, context):
scene = context.scene
collection = scene.master_collection.collections.new()
collection.objects.link(context.object)
return {'FINISHED'}
class OBJECT_OT_collection_remove(Operator):
"""Remove the active object from this collection"""
bl_idname = "object.collection_remove"
bl_label = "Remove from Collection"
def execute(self, context):
collection = context.scene_collection
collection.objects.unlink(context.object)
return {'FINISHED'}
def get_collection_from_id_recursive(collection, collection_id, current_id):
"""Return len of collection and the collection if it was a match"""
if collection_id == current_id:
return collection, 0
current_id += 1
for collection_nested in collection.collections:
matched_collection, current_id = get_collection_from_id_recursive(
collection_nested,
collection_id,
current_id)
if matched_collection is not None:
return matched_collection, 0
return None, current_id
def get_collection_from_id(scene, collection_id):
master_collection = scene.master_collection
return get_collection_from_id_recursive(master_collection, collection_id, 0)[0]
def collection_items_recursive(path, collection, items, current_id, object_name):
name = collection.name
current_id += 1
if object_name not in collection.objects:
items.append((str(current_id), path + name, ""))
path += name + " / "
for collection_nested in collection.collections:
current_id = collection_items_recursive(path, collection_nested, items, current_id, object_name)
return current_id
def collection_items(self, context):
items = []
master_collection = context.scene.master_collection
object_name = context.object.name
if object_name not in master_collection.objects:
items.append(('0', "Master Collection", "", 'COLLAPSEMENU', 0))
current_id = 0
for collection in master_collection.collections:
current_id = collection_items_recursive("", collection, items, current_id, object_name)
return items
class OBJECT_OT_collection_link(Operator):
"""Add an object to an existing collection"""
bl_idname = "object.collection_link"
bl_label = "Link to Collection"
collection_index = IntProperty(
name = "Collection Index",
default = -1,
options = {'SKIP_SAVE'},
)
type = EnumProperty(
name = "",
description = "Dynamic enum for collections",
items=collection_items,
)
def execute(self, context):
if self.collection_index == -1:
self.collection_index = int(self.type)
collection = get_collection_from_id(context.scene, self.collection_index)
if collection is None:
# It should never ever happen!
self.report({'ERROR'}, "Unexpected error: collection {0} is invalid".format(
self.collection_index))
return {'CANCELLED'}
collection.objects.link(context.object)
return {'FINISHED'}
def invoke(self, context, events):
if self.collection_index != -1:
return self.execute(context)
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}
def find_collection_parent(collection, collection_parent):
for collection_nested in collection_parent.collections:
if collection_nested == collection:
return collection_parent
found_collection = find_collection_parent(collection, collection_nested)
if found_collection:
return found_collection
return None
class OBJECT_OT_collection_unlink(Operator):
"""Unlink the collection from all objects"""
bl_idname = "object.collection_unlink"
bl_label = "Unlink Collection"
def execute(self, context):
collection = context.scene_collection
master_collection = context.scene.master_collection
collection_parent = find_collection_parent(collection, master_collection)
if collection_parent is None:
self.report({'ERROR'}, "Cannot find {0}'s parent".format(collection.name))
return {'CANCELLED'}
collection_parent.collections.remove(collection)
return {'CANCELLED'}
def select_collection_objects(collection):
for ob in collection.objects:
ob.select_set('SELECT')
for collection_nested in collection.collections:
select_collection_objects(collection_nested)
class OBJECT_OT_collection_select(Operator):
"""Select all objects in collection"""
bl_idname = "object.collection_select"
bl_label = "Select Collection"
def execute(self, context):
collection = context.scene_collection
select_collection_objects(collection)
return {'FINISHED'}
# #####################################################################################
# Interface
# #####################################################################################
class COLLECTION_MT_specials(Menu):
bl_label = "Collection Specials"
def draw(self, context):
layout = self.layout
col = layout.column()
col.active = context.scene_collection != context.scene.master_collection
col.operator("object.collection_unlink", icon='X', text="Unlink Collection")
layout.operator("object.collection_select", text="Select Collection")
def all_collections_get(context):
"""Iterator over all scene collections
"""
def all_collections_recursive_get(collection_parent, collections):
collections.append(collection_parent)
for collection_nested in collection_parent.collections:
all_collections_recursive_get(collection_nested, collections)
scene = context.scene
master_collection = scene.master_collection
collections = []
all_collections_recursive_get(master_collection, collections)
return collections
class OBJECT_PT_collections(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
bl_label = "Collections"
def draw(self, context):
layout = self.layout
row = layout.row(align=True)
obj = context.object
master_collection = bpy.context.scene.master_collection
if master_collection.collections:
row.operator("object.collection_link", text="Add to Collection")
else:
row.operator("object.collection_link", text="Add to Collection").collection_index = 0
row.operator("object.collection_add", text="", icon='ZOOMIN')
obj_name = obj.name
for collection in all_collections_get(context):
collection_objects = collection.objects
if obj_name in collection.objects:
col = layout.column(align=True)
col.context_pointer_set("scene_collection", collection)
row = col.box().row()
if collection == master_collection:
row.label(text=collection.name)
else:
row.prop(collection, "name", text="")
row.operator("object.collection_remove", text="", icon='X', emboss=False)
row.menu("COLLECTION_MT_specials", icon='DOWNARROW_HLT', text="")
# #####################################################################################
# Register/Unregister
# #####################################################################################
classes = (
COLLECTION_MT_specials,
OBJECT_PT_collections,
OBJECT_OT_collection_add,
OBJECT_OT_collection_remove,
OBJECT_OT_collection_link,
OBJECT_OT_collection_unlink,
OBJECT_OT_collection_select,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()