Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2019-12-09 12:57:40 +0300
committerCampbell Barton <ideasman42@gmail.com>2019-12-09 12:57:40 +0300
commitec6873e90f2d1e2765f85b58dfa6addeb2a592dd (patch)
tree3fc32258b929da3b8ec31e8e39b1eaf9d3ab08a0 /object_collection_manager
parent14420434d2aa4c0c1d7897440e40f1656ea57de0 (diff)
Use "object" prefix for collection manager
This was defining it's own category, which should be avoided and isn't needed in this case since it manages object collections.
Diffstat (limited to 'object_collection_manager')
-rw-r--r--object_collection_manager/__init__.py121
-rw-r--r--object_collection_manager/internals.py107
-rw-r--r--object_collection_manager/operators.py1210
-rw-r--r--object_collection_manager/ui.py412
4 files changed, 1850 insertions, 0 deletions
diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py
new file mode 100644
index 00000000..75142a35
--- /dev/null
+++ b/object_collection_manager/__init__.py
@@ -0,0 +1,121 @@
+# ##### 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 #####
+
+
+""" Copyright 2011 GPL licence applies"""
+
+bl_info = {
+ "name": "Collection Manager",
+ "description": "Manage collections and their objects",
+ "author": "Ryan Inch",
+ "version": (1,8,0),
+ "blender": (2, 80, 0),
+ "location": "View3D - Object Mode (Shortcut - M)",
+ "warning": '', # used for warning icon and text in addons panel
+ "wiki_url": "",
+ "category": "User Interface"}
+
+
+if "bpy" in locals():
+ import importlib
+
+ importlib.reload(internals)
+ importlib.reload(operators)
+ importlib.reload(ui)
+
+else:
+ from . import internals
+ from . import operators
+ from . import ui
+
+import bpy
+from bpy.props import (
+ CollectionProperty,
+ IntProperty,
+ BoolProperty,
+ )
+
+addon_keymaps = []
+
+classes = (
+ internals.CMListCollection,
+ operators.ExpandAllOperator,
+ operators.ExpandSublevelOperator,
+ operators.CMExcludeOperator,
+ operators.CMUnExcludeAllOperator,
+ operators.CMRestrictSelectOperator,
+ operators.CMUnRestrictSelectAllOperator,
+ operators.CMHideOperator,
+ operators.CMUnHideAllOperator,
+ operators.CMDisableViewportOperator,
+ operators.CMUnDisableViewportAllOperator,
+ operators.CMDisableRenderOperator,
+ operators.CMUnDisableRenderAllOperator,
+ operators.CMNewCollectionOperator,
+ operators.CMRemoveCollectionOperator,
+ operators.CMSetCollectionOperator,
+ operators.CMPhantomModeOperator,
+ ui.CM_UL_items,
+ ui.CollectionManager,
+ ui.CMRestrictionTogglesPanel,
+ )
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+ bpy.types.Scene.CMListCollection = CollectionProperty(type=internals.CMListCollection)
+ bpy.types.Scene.CMListIndex = IntProperty(update=ui.update_selection)
+
+ bpy.types.Scene.show_exclude = BoolProperty(default=True, name="Exclude from View Layer")
+ bpy.types.Scene.show_selectable = BoolProperty(default=True, name="Selectable")
+ bpy.types.Scene.show_hideviewport = BoolProperty(default=True, name="Hide in Viewport")
+ bpy.types.Scene.show_disableviewport = BoolProperty(default=False, name="Disable in Viewports")
+ bpy.types.Scene.show_render = BoolProperty(default=False, name="Disable in Renders")
+
+ bpy.types.Scene.CM_Phantom_Mode = BoolProperty(default=False)
+
+
+ # create the global menu hotkey
+ wm = bpy.context.window_manager
+ km = wm.keyconfigs.addon.keymaps.new(name='Object Mode')
+ kmi = km.keymap_items.new('view3d.collection_manager', 'M', 'PRESS')
+ addon_keymaps.append((km, kmi))
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+
+ del bpy.types.Scene.CMListCollection
+ del bpy.types.Scene.CMListIndex
+
+ del bpy.types.Scene.show_exclude
+ del bpy.types.Scene.show_selectable
+ del bpy.types.Scene.show_hideviewport
+ del bpy.types.Scene.show_disableviewport
+ del bpy.types.Scene.show_render
+
+ del bpy.types.Scene.CM_Phantom_Mode
+
+ # remove keymaps when add-on is deactivated
+ for km, kmi in addon_keymaps:
+ km.keymap_items.remove(kmi)
+ addon_keymaps.clear()
+
+if __name__ == "__main__":
+ register()
diff --git a/object_collection_manager/internals.py b/object_collection_manager/internals.py
new file mode 100644
index 00000000..e898de1c
--- /dev/null
+++ b/object_collection_manager/internals.py
@@ -0,0 +1,107 @@
+from bpy.types import PropertyGroup
+from bpy.props import StringProperty
+
+layer_collections = {}
+
+collection_tree = []
+
+expanded = []
+
+max_lvl = 0
+row_index = 0
+
+def get_max_lvl():
+ return max_lvl
+
+def update_col_name(self, context):
+ if self.name != self.last_name:
+ if self.name == '':
+ self.name = self.last_name
+ return
+
+ if self.last_name != '':
+ layer_collections[self.last_name]["ptr"].collection.name = self.name
+
+ update_property_group(context)
+
+ self.last_name = self.name
+
+class CMListCollection(PropertyGroup):
+ name: StringProperty(update=update_col_name)
+ last_name: StringProperty()
+
+
+def update_collection_tree(context):
+ global max_lvl
+ global row_index
+ collection_tree.clear()
+ layer_collections.clear()
+ max_lvl = 0
+ row_index = 0
+
+ init_laycol_list = context.view_layer.layer_collection.children
+
+ master_laycol = {"id": 0,
+ "name": context.view_layer.layer_collection.name,
+ "lvl": -1,
+ "row_index": -1,
+ "visible": True,
+ "has_children": True,
+ "expanded": True,
+ "parent": None,
+ "children": [],
+ "ptr": context.view_layer.layer_collection
+ }
+
+ get_all_collections(context, init_laycol_list, master_laycol, collection_tree, visible=True)
+
+
+def get_all_collections(context, collections, parent, tree, level=0, visible=False):
+ global row_index
+
+ for item in collections:
+ laycol = {"id": len(layer_collections) +1,
+ "name": item.name,
+ "lvl": level,
+ "row_index": row_index,
+ "visible": visible,
+ "has_children": False,
+ "expanded": False,
+ "parent": parent,
+ "children": [],
+ "ptr": item
+ }
+
+ row_index += 1
+
+ layer_collections[item.name] = laycol
+ tree.append(laycol)
+
+ if len(item.children) > 0:
+ global max_lvl
+ max_lvl += 1
+ laycol["has_children"] = True
+
+ if item.name in expanded and laycol["visible"]:
+ laycol["expanded"] = True
+ get_all_collections(context, item.children, laycol, laycol["children"], level+1, visible=True)
+
+ else:
+ get_all_collections(context, item.children, laycol, laycol["children"], level+1)
+
+
+def update_property_group(context):
+ update_collection_tree(context)
+ context.scene.CMListCollection.clear()
+ create_property_group(context, collection_tree)
+
+
+def create_property_group(context, tree):
+ global in_filter
+
+ for laycol in tree:
+ new_cm_listitem = context.scene.CMListCollection.add()
+ new_cm_listitem.name = laycol["name"]
+
+ if laycol["has_children"]:
+ create_property_group(context, laycol["children"])
diff --git a/object_collection_manager/operators.py b/object_collection_manager/operators.py
new file mode 100644
index 00000000..84396e7e
--- /dev/null
+++ b/object_collection_manager/operators.py
@@ -0,0 +1,1210 @@
+import bpy
+from bpy.types import Operator
+from bpy.props import (
+ BoolProperty,
+ StringProperty,
+ IntProperty
+ )
+
+from .internals import *
+
+rto_history = {"exclude": {},
+ "exclude_all": {},
+ "select": {},
+ "select_all": {},
+ "hide": {},
+ "hide_all": {},
+ "disable": {},
+ "disable_all": {},
+ "render": {},
+ "render_all": {}
+ }
+
+class ExpandAllOperator(bpy.types.Operator):
+ '''Expand/Collapse all collections'''
+ bl_label = "Expand All Items"
+ bl_idname = "view3d.expand_all_items"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ if len(expanded) > 0:
+ expanded.clear()
+ else:
+ for laycol in layer_collections.values():
+ if laycol["ptr"].children:
+ expanded.append(laycol["name"])
+
+ # update tree view
+ update_property_group(context)
+
+ return {'FINISHED'}
+
+
+class ExpandSublevelOperator(bpy.types.Operator):
+ ''' * Shift-Click to expand/collapse all sublevels'''
+ bl_label = "Expand Sublevel Items"
+ bl_idname = "view3d.expand_sublevel"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ expand: BoolProperty()
+ name: StringProperty()
+ index: IntProperty()
+
+ def invoke(self, context, event):
+ if event.shift:
+ # expand/collapse all subcollections
+ expand = None
+
+ # check whether to expand or collapse
+ if self.name in expanded:
+ expanded.remove(self.name)
+ expand = False
+ else:
+ expanded.append(self.name)
+ expand = True
+
+ # do expanding/collapsing
+ def loop(laycol):
+ for item in laycol.children:
+ if expand:
+ if not item.name in expanded:
+ expanded.append(item.name)
+ else:
+ if item.name in expanded:
+ expanded.remove(item.name)
+
+ if len(item.children) > 0:
+ loop(item)
+
+ loop(layer_collections[self.name]["ptr"])
+
+ else:
+ # expand/collapse collection
+ if self.expand:
+ expanded.append(self.name)
+ else:
+ expanded.remove(self.name)
+
+
+ # set selected row to the collection you're expanding/collapsing and update tree view
+ context.scene.CMListIndex = self.index
+ update_property_group(context)
+
+ return {'FINISHED'}
+
+
+class CMSetCollectionOperator(bpy.types.Operator):
+ ''' * Click to move object to collection.\n * Shift-Click to add/remove object from collection'''
+ bl_label = "Set Object Collection"
+ bl_idname = "view3d.set_collection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ collection_index: IntProperty()
+ collection_name: StringProperty()
+
+ def invoke(self, context, event):
+ collection = layer_collections[self.collection_name]["ptr"].collection
+
+ if event.shift:
+ # add object to collection
+
+ # check if in collection
+ in_collection = True
+
+ for obj in context.selected_objects:
+ if obj.name not in collection.objects:
+ in_collection = False
+
+ if not in_collection:
+ # add to collection
+ bpy.ops.object.link_to_collection(collection_index=self.collection_index)
+
+ else:
+ # check and disallow removing from all collections
+ for obj in context.selected_objects:
+ if len(obj.users_collection) == 1:
+ send_report("Error removing 1 or more objects from this collection.\nObjects would be left without a collection")
+
+ return {'FINISHED'}
+
+ # remove from collection
+ bpy.ops.collection.objects_remove(collection=collection.name)
+
+ else:
+ # move object to collection
+ bpy.ops.object.move_to_collection(collection_index=self.collection_index)
+
+ return {'FINISHED'}
+
+
+class CMExcludeOperator(bpy.types.Operator):
+ ''' * Shift-Click to isolate/restore previous state\n * Ctrl-Click to toggle children'''
+ bl_label = "Exclude Collection from View Layer"
+ bl_idname = "view3d.exclude_collection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ name: StringProperty()
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+ laycol_ptr = layer_collections[self.name]["ptr"]
+
+ if not view_layer in rto_history["exclude"]:
+ rto_history["exclude"][view_layer] = {"target": "", "history": []}
+
+ rto_history["exclude"][view_layer]["target"] = self.name
+ exclude_history = rto_history["exclude"][view_layer]["history"]
+
+ if event.shift:
+ # isolate/de-isolate exclusion of collections
+
+ # get active layer collections
+ active_layer_collections = [x for x in layer_collections.values() \
+ if x["ptr"].exclude == False]
+
+ # check if collection isolated
+ if len(active_layer_collections) == 1 and active_layer_collections[0]["name"] == self.name:
+ if len(exclude_history) > 1:
+ # restore previous state
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].exclude = exclude_history[x]
+
+ else:
+ # enable all collections
+ for item in layer_collections.values():
+ item["ptr"].exclude = False
+
+ # reset exclude history
+ del rto_history["exclude"][view_layer]
+
+ else:
+ # isolate collection
+
+ # reset exclude history
+ exclude_history.clear()
+
+ # save state
+ keep_history = -1
+ for item in layer_collections.values():
+ exclude_history.append(item["ptr"].exclude)
+
+ if item["ptr"].exclude == False:
+ keep_history += 1
+
+ if not keep_history:
+ del rto_history["exclude"][view_layer]
+
+
+ # isolate collection
+ for item in layer_collections.values():
+ if item["name"] != laycol_ptr.name:
+ item["ptr"].exclude = True
+
+ laycol_ptr.exclude = False
+
+ # exclude all children
+ laycol_iter_list = [laycol_ptr.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ layer_collection.exclude = True
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+
+ elif event.ctrl:
+ # toggle children
+
+ # reset exclude history
+ del rto_history["exclude"][view_layer]
+
+ # toggle exclusion of collection (this propagates to children)
+ laycol_ptr.exclude = not laycol_ptr.exclude
+
+ else:
+ # toggle exclusion
+
+ # reset exclude history
+ del rto_history["exclude"][view_layer]
+
+
+ # get current child exclusion state
+ child_exclusion = []
+
+ laycol_iter_list = [laycol_ptr.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ child_exclusion.append([layer_collection, layer_collection.exclude])
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+
+ # toggle exclusion of collection
+ laycol_ptr.exclude = not laycol_ptr.exclude
+
+
+ # set correct state for all children
+ for laycol in child_exclusion:
+ laycol[0].exclude = laycol[1]
+
+
+ # reset exclude all history
+ if view_layer in rto_history["exclude_all"]:
+ del rto_history["exclude_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMUnExcludeAllOperator(bpy.types.Operator):
+ ''' * Click to toggle between current excluded state and all included.\n * Shift-Click to invert excluded status of all collections'''
+ bl_label = "Toggle Excluded Status Of All Collections"
+ bl_idname = "view3d.un_exclude_all_collections"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+
+ if not view_layer in rto_history["exclude_all"]:
+ rto_history["exclude_all"][view_layer] = []
+
+ exclude_all_history = rto_history["exclude_all"][view_layer]
+
+ if len(exclude_all_history) == 0:
+ exclude_all_history.clear()
+ keep_history = False
+
+ if event.shift:
+ for item in layer_collections.values():
+ keep_history = True
+ exclude_all_history.append(item["ptr"].exclude)
+
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].exclude = not exclude_all_history[x]
+
+ print([not i for i in exclude_all_history])
+
+ else:
+ for item in reversed(list(layer_collections.values())):
+ if item["ptr"].exclude:
+ keep_history = True
+
+ exclude_all_history.append(item["ptr"].exclude)
+
+ item["ptr"].exclude = False
+
+ exclude_all_history.reverse()
+
+ if not keep_history:
+ del rto_history["exclude_all"][view_layer]
+
+ else:
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].exclude = exclude_all_history[x]
+
+ del rto_history["exclude_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMRestrictSelectOperator(bpy.types.Operator):
+ ''' * Shift-Click to isolate/restore previous state\n * Ctrl-Click to toggle children'''
+ bl_label = "Disable Selection of Collection"
+ bl_idname = "view3d.restrict_select_collection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ name: StringProperty()
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+ laycol_ptr = layer_collections[self.name]["ptr"]
+
+ if not view_layer in rto_history["select"]:
+ rto_history["select"][view_layer] = {"target": "", "history": []}
+
+ rto_history["select"][view_layer]["target"] = self.name
+ select_history = rto_history["select"][view_layer]["history"]
+
+ if event.shift:
+ # isolate/de-isolate selectability of collections
+
+ # get active collections
+ active_layer_collections = [x for x in layer_collections.values() \
+ if x["ptr"].collection.hide_select == False]
+
+ layerchain = []
+ laycol = layer_collections[self.name]
+
+ # get chain of parents up to top level collection
+ while laycol["id"] != 0:
+ layerchain.append(laycol)
+ laycol = laycol["parent"]
+
+ # check if reversed layerchain matches active collections
+ if layerchain[::-1] == active_layer_collections:
+ if len(select_history) > 1:
+ # restore previous state
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].collection.hide_select = select_history[x]
+
+ else:
+ # make all collections selectable
+ for item in layer_collections.values():
+ item["ptr"].collection.hide_select = False
+
+ # reset select history
+ del rto_history["select"][view_layer]
+
+ else:
+ # reset select history
+ select_history.clear()
+
+ # save state
+ keep_history = -1
+ for item in layer_collections.values():
+ select_history.append(item["ptr"].collection.hide_select)
+
+ if item["ptr"].collection.hide_select == False:
+ keep_history += 1
+
+ if not keep_history:
+ del rto_history["select"][view_layer]
+
+ # make all collections unselectable
+ for item in layer_collections.values():
+ item["ptr"].collection.hide_select = True
+
+ # allow selection of active collection plus parents
+ laycol_ptr.collection.hide_select = False
+
+ laycol = layer_collections[self.name]
+ while laycol["id"] != 0:
+ laycol["ptr"].collection.hide_select = False
+ laycol = laycol["parent"]
+
+
+ elif event.ctrl:
+ # toggle children
+
+ # reset select history
+ del rto_history["select"][view_layer]
+
+ # toggle selectability of collection
+ state = not laycol_ptr.collection.hide_select
+ laycol_ptr.collection.hide_select = state
+
+ # pass state to children
+ laycol_iter_list = [laycol_ptr.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ layer_collection.collection.hide_select = state
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+ else:
+ # reset select history
+ del rto_history["select"][view_layer]
+
+ # toggle selectability of collection
+ laycol_ptr.collection.hide_select = not laycol_ptr.collection.hide_select
+
+
+ # reset select all history
+ if view_layer in rto_history["select_all"]:
+ del rto_history["select_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMUnRestrictSelectAllOperator(bpy.types.Operator):
+ ''' * Click to toggle between current selectable state and all selectable.\n * Shift-Click to invert selectable status of all collections'''
+ bl_label = "Toggle Selectable Status Of All Collections"
+ bl_idname = "view3d.un_restrict_select_all_collections"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+
+ if not view_layer in rto_history["select_all"]:
+ rto_history["select_all"][view_layer] = []
+
+ select_all_history = rto_history["select_all"][view_layer]
+
+ if len(select_all_history) == 0:
+ select_all_history.clear()
+ keep_history = False
+
+ for item in layer_collections.values():
+ if event.shift:
+ keep_history = True
+ select_all_history.append(item["ptr"].collection.hide_select)
+ item["ptr"].collection.hide_select = not item["ptr"].collection.hide_select
+
+ else:
+ if item["ptr"].collection.hide_select:
+ keep_history = True
+
+ select_all_history.append(item["ptr"].collection.hide_select)
+ item["ptr"].collection.hide_select = False
+
+ if not keep_history:
+ del rto_history["select_all"][view_layer]
+
+ else:
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].collection.hide_select = select_all_history[x]
+
+ del rto_history["select_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMHideOperator(bpy.types.Operator):
+ ''' * Shift-Click to isolate/restore previous state\n * Ctrl-Click to toggle children'''
+ bl_label = "Hide Collection"
+ bl_idname = "view3d.hide_collection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ name: StringProperty()
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+ laycol_ptr = layer_collections[self.name]["ptr"]
+
+ if not view_layer in rto_history["hide"]:
+ rto_history["hide"][view_layer] = {"target": "", "history": []}
+
+ rto_history["hide"][view_layer]["target"] = self.name
+ hide_history = rto_history["hide"][view_layer]["history"]
+
+ if event.shift:
+ # isolate/de-isolate view of collections
+
+ # get active collections
+ active_layer_collections = [x for x in layer_collections.values() \
+ if x["ptr"].hide_viewport == False]
+
+ layerchain = []
+ laycol = layer_collections[self.name]
+
+ # get chain of parents up to top level collection
+ while laycol["id"] != 0:
+ layerchain.append(laycol)
+ laycol = laycol["parent"]
+
+ # check if reversed layerchain matches active collections
+ if layerchain[::-1] == active_layer_collections:
+ if len(hide_history) > 1:
+ # restore previous state
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].hide_viewport = hide_history[x]
+
+ else:
+ # show all collections
+ for laycol in layer_collections.values():
+ laycol["ptr"].hide_viewport = False
+
+ # reset hide history
+ del rto_history["hide"][view_layer]
+
+ else:
+ # reset hide history
+ hide_history.clear()
+
+ # save state
+ keep_history = -1
+ for item in layer_collections.values():
+ hide_history.append(item["ptr"].hide_viewport)
+
+ if item["ptr"].hide_viewport == False:
+ keep_history += 1
+
+ if not keep_history:
+ del rto_history["hide"][view_layer]
+
+ # hide all collections
+ for laycol in layer_collections.values():
+ laycol["ptr"].hide_viewport = True
+
+ # show active collection plus parents
+ laycol_ptr.hide_viewport = False
+
+ laycol = layer_collections[self.name]
+ while laycol["id"] != 0:
+ laycol["ptr"].hide_viewport = False
+ laycol = laycol["parent"]
+
+ elif event.ctrl:
+ # toggle children
+
+ # reset hide history
+ del rto_history["hide"][view_layer]
+
+ # toggle view of collection
+ state = not laycol_ptr.hide_viewport
+ laycol_ptr.hide_viewport = state
+
+ # pass state to children
+ laycol_iter_list = [laycol_ptr.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ layer_collection.hide_viewport = state
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+ else:
+ # reset hide history
+ del rto_history["hide"][view_layer]
+
+ # toggle view of collection
+ laycol_ptr.hide_viewport = not laycol_ptr.hide_viewport
+
+
+ # reset hide all history
+ if view_layer in rto_history["hide_all"]:
+ del rto_history["hide_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMUnHideAllOperator(bpy.types.Operator):
+ ''' * Click to toggle between current visibility state and all visible.\n * Shift-Click to invert visibility status of all collections'''
+ bl_label = "Toggle Hidden Status Of All Collections"
+ bl_idname = "view3d.un_hide_all_collections"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+
+ if not view_layer in rto_history["hide_all"]:
+ rto_history["hide_all"][view_layer] = []
+
+ hide_all_history = rto_history["hide_all"][view_layer]
+
+ if len(hide_all_history) == 0:
+ hide_all_history.clear()
+ keep_history = False
+
+ for item in layer_collections.values():
+ if event.shift:
+ keep_history = True
+ hide_all_history.append(item["ptr"].hide_viewport)
+ item["ptr"].hide_viewport = not item["ptr"].hide_viewport
+
+ else:
+ if item["ptr"].hide_viewport:
+ keep_history = True
+
+ hide_all_history.append(item["ptr"].hide_viewport)
+ item["ptr"].hide_viewport = False
+
+ if not keep_history:
+ del rto_history["hide_all"][view_layer]
+
+ else:
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].hide_viewport = hide_all_history[x]
+
+ del rto_history["hide_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMDisableViewportOperator(bpy.types.Operator):
+ ''' * Shift-Click to isolate/restore previous state\n * Ctrl-Click to toggle children'''
+ bl_label = "Disable Collection in Viewport"
+ bl_idname = "view3d.disable_viewport_collection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ name: StringProperty()
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+ laycol_ptr = layer_collections[self.name]["ptr"]
+
+ if not view_layer in rto_history["disable"]:
+ rto_history["disable"][view_layer] = {"target": "", "history": []}
+
+ rto_history["disable"][view_layer]["target"] = self.name
+ disable_history = rto_history["disable"][view_layer]["history"]
+
+ if event.shift:
+ # isolate/de-isolate disablement of collections in viewport
+
+ # get active collections
+ active_layer_collections = [x for x in layer_collections.values() \
+ if x["ptr"].collection.hide_viewport == False]
+
+ layerchain = []
+ laycol = layer_collections[self.name]
+
+ # get chain of parents up to top level collection
+ while laycol["id"] != 0:
+ layerchain.append(laycol)
+ laycol = laycol["parent"]
+
+ # check if reversed layerchain matches active collections
+ if layerchain[::-1] == active_layer_collections:
+ if len(disable_history) > 1:
+ # restore previous state
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].collection.hide_viewport = disable_history[x]
+
+ else:
+ # enable all collections in viewport
+ for laycol in layer_collections.values():
+ laycol["ptr"].collection.hide_viewport = False
+
+ # reset disable history
+ del rto_history["disable"][view_layer]
+
+ else:
+ # reset disable history
+ disable_history.clear()
+
+ # save state
+ keep_history = -1
+ for item in layer_collections.values():
+ disable_history.append(item["ptr"].collection.hide_viewport)
+
+ if item["ptr"].collection.hide_viewport == False:
+ keep_history += 1
+
+ if not keep_history:
+ del rto_history["disable"][view_layer]
+
+ # disable all collections in viewport
+ for laycol in layer_collections.values():
+ laycol["ptr"].collection.hide_viewport = True
+
+ # enable active collection plus parents in viewport
+ laycol_ptr.collection.hide_viewport = False
+
+ laycol = layer_collections[self.name]
+ while laycol["id"] != 0:
+ laycol["ptr"].collection.hide_viewport = False
+ laycol = laycol["parent"]
+
+ elif event.ctrl:
+ # toggle children
+
+ # reset disable history
+ del rto_history["disable"][view_layer]
+
+ # toggle view of collection
+ state = not laycol_ptr.collection.hide_viewport
+ laycol_ptr.collection.hide_viewport = state
+
+ # pass state to children
+ laycol_iter_list = [laycol_ptr.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ layer_collection.collection.hide_viewport = state
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+ else:
+ # reset disable history
+ del rto_history["disable"][view_layer]
+
+ # toggle disable of collection in viewport
+ laycol_ptr.collection.hide_viewport = not laycol_ptr.collection.hide_viewport
+
+
+ # reset disable all history
+ if view_layer in rto_history["disable_all"]:
+ del rto_history["disable_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMUnDisableViewportAllOperator(bpy.types.Operator):
+ ''' * Click to toggle between current viewport display and all enabled.\n * Shift-Click to invert viewport display of all collections'''
+ bl_label = "Toggle Viewport Display of All Collections"
+ bl_idname = "view3d.un_disable_viewport_all_collections"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+
+ if not view_layer in rto_history["disable_all"]:
+ rto_history["disable_all"][view_layer] = []
+
+ disable_all_history = rto_history["disable_all"][view_layer]
+
+ if len(disable_all_history) == 0:
+ disable_all_history.clear()
+ keep_history = False
+
+ for item in layer_collections.values():
+ if event.shift:
+ keep_history = True
+ disable_all_history.append(item["ptr"].collection.hide_viewport)
+ item["ptr"].collection.hide_viewport = not \
+ item["ptr"].collection.hide_viewport
+
+ else:
+ if item["ptr"].collection.hide_viewport:
+ keep_history = True
+
+ disable_all_history.append(item["ptr"].collection.hide_viewport)
+ item["ptr"].collection.hide_viewport = False
+
+ if not keep_history:
+ del rto_history["disable_all"][view_layer]
+
+ else:
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].collection.hide_viewport = disable_all_history[x]
+
+ del rto_history["disable_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMDisableRenderOperator(bpy.types.Operator):
+ ''' * Shift-Click to isolate/restore previous state\n * Shift-Click to invert viewport display of all collections'''
+ bl_label = "Disable Collection in Render"
+ bl_idname = "view3d.disable_render_collection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ name: StringProperty()
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+ laycol_ptr = layer_collections[self.name]["ptr"]
+
+ if not view_layer in rto_history["render"]:
+ rto_history["render"][view_layer] = {"target": "", "history": []}
+
+ rto_history["render"][view_layer]["target"] = self.name
+ render_history = rto_history["render"][view_layer]["history"]
+
+ if event.shift:
+ # isolate/de-isolate render of collections
+
+ # get active collections
+ active_layer_collections = [x for x in layer_collections.values() \
+ if x["ptr"].collection.hide_render == False]
+
+ layerchain = []
+ laycol = layer_collections[self.name]
+
+ # get chain of parents up to top level collection
+ while laycol["id"] != 0:
+ layerchain.append(laycol)
+ laycol = laycol["parent"]
+
+ # check if reversed layerchain matches active collections
+ if layerchain[::-1] == active_layer_collections:
+ if len(render_history) > 1:
+ # restore previous state
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].collection.hide_render = render_history[x]
+
+ else:
+ # allow render of all collections
+ for laycol in layer_collections.values():
+ laycol["ptr"].collection.hide_render = False
+
+ # reset render history
+ del rto_history["render"][view_layer]
+
+ else:
+ # reset render history
+ render_history.clear()
+
+ # save state
+ keep_history = -1
+ for item in layer_collections.values():
+ render_history.append(item["ptr"].collection.hide_render)
+
+ if item["ptr"].collection.hide_render == False:
+ keep_history += 1
+
+ if not keep_history:
+ del rto_history["render"][view_layer]
+
+ # disallow render of all collections
+ for laycol in layer_collections.values():
+ laycol["ptr"].collection.hide_render = True
+
+ # allow render of active collection plus parents
+ laycol_ptr.collection.hide_render = False
+
+ laycol = layer_collections[self.name]
+ while laycol["id"] != 0:
+ laycol["ptr"].collection.hide_render = False
+ laycol = laycol["parent"]
+
+ elif event.ctrl:
+ # toggle children
+
+ # reset render history
+ del rto_history["render"][view_layer]
+
+ # toggle view of collection
+ state = not laycol_ptr.collection.hide_render
+ laycol_ptr.collection.hide_render = state
+
+ # pass state to children
+ laycol_iter_list = [laycol_ptr.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ layer_collection.collection.hide_render = state
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+ else:
+ # reset render history
+ del rto_history["render"][view_layer]
+
+ # toggle renderability of collection
+ laycol_ptr.collection.hide_render = not laycol_ptr.collection.hide_render
+
+
+ # reset render all history
+ if view_layer in rto_history["render_all"]:
+ del rto_history["render_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMUnDisableRenderAllOperator(bpy.types.Operator):
+ ''' * Click to toggle between current render status and all rendered.\n * Shift-Click to invert render status of all collections'''
+ bl_label = "Toggle Render Status of All Collections"
+ bl_idname = "view3d.un_disable_render_all_collections"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ global rto_history
+
+ view_layer = context.view_layer.name
+
+ if not view_layer in rto_history["render_all"]:
+ rto_history["render_all"][view_layer] = []
+
+ render_all_history = rto_history["render_all"][view_layer]
+
+ if len(render_all_history) == 0:
+ render_all_history.clear()
+ keep_history = False
+
+ for item in layer_collections.values():
+ if event.shift:
+ keep_history = True
+ render_all_history.append(item["ptr"].collection.hide_render)
+ item["ptr"].collection.hide_render = not \
+ item["ptr"].collection.hide_render
+
+ else:
+ if item["ptr"].collection.hide_render:
+ keep_history = True
+
+ render_all_history.append(item["ptr"].collection.hide_render)
+ item["ptr"].collection.hide_render = False
+
+ if not keep_history:
+ del rto_history["render_all"][view_layer]
+
+ else:
+ for x, item in enumerate(layer_collections.values()):
+ item["ptr"].collection.hide_render = render_all_history[x]
+
+ del rto_history["render_all"][view_layer]
+
+ return {'FINISHED'}
+
+
+class CMRemoveCollectionOperator(bpy.types.Operator):
+ '''Remove Collection'''
+ bl_label = "Remove Collection"
+ bl_idname = "view3d.remove_collection"
+ bl_options = {'UNDO'}
+
+ collection_name: StringProperty()
+
+ def execute(self, context):
+ global rto_history
+
+ laycol = layer_collections[self.collection_name]
+ collection = laycol["ptr"].collection
+ laycol_parent = laycol["parent"]
+
+ # save state and remove all hiding properties of parent collection
+ orig_parent_hide_select = False
+ orig_parent_exclude = False
+ orig_parent_hide_viewport = False
+
+ if laycol_parent["ptr"].collection.hide_select:
+ orig_parent_hide_select = True
+
+ if laycol_parent["ptr"].exclude:
+ orig_parent_exclude = True
+
+ if laycol_parent["ptr"].hide_viewport:
+ orig_parent_hide_viewport = True
+
+ laycol_parent["ptr"].collection.hide_select = False
+ laycol_parent["ptr"].exclude = False
+ laycol_parent["ptr"].hide_viewport = False
+
+
+ # remove all hiding properties of this collection
+ collection.hide_select = False
+ laycol["ptr"].exclude = False
+ laycol["ptr"].hide_viewport = False
+
+
+ # shift all objects in this collection to the parent collection
+ if collection.objects:
+ orig_selected_objs = context.selected_objects
+ orig_active_obj = context.active_object
+
+ # select all objects in collection
+ bpy.ops.object.select_same_collection(collection=collection.name)
+ context.view_layer.objects.active = context.selected_objects[0]
+
+ # remove any objects already in parent collection from selection
+ for obj in context.selected_objects:
+ if obj in laycol["parent"]["ptr"].collection.objects.values():
+ obj.select_set(False)
+
+ # link selected objects to parent collection
+ bpy.ops.object.link_to_collection(collection_index=laycol_parent["id"])
+
+ # remove objects from collection
+ bpy.ops.collection.objects_remove(collection=collection.name)
+
+ # reset selection original values
+ bpy.ops.object.select_all(action='DESELECT')
+
+ for obj in orig_selected_objs:
+ obj.select_set(True)
+ context.view_layer.objects.active = orig_active_obj
+
+
+ # shift all child collections to the parent collection
+ if collection.children:
+ for subcollection in collection.children:
+ laycol_parent["ptr"].collection.children.link(subcollection)
+
+ # reset hiding properties of parent collection
+ laycol_parent["ptr"].collection.hide_select = orig_parent_hide_select
+ laycol_parent["ptr"].exclude = orig_parent_exclude
+ laycol_parent["ptr"].hide_viewport = orig_parent_hide_viewport
+
+
+ # remove collection and update tree view
+ bpy.data.collections.remove(collection)
+
+
+ update_property_group(context)
+
+ if len(context.scene.CMListCollection) == context.scene.CMListIndex:
+ context.scene.CMListIndex = len(context.scene.CMListCollection) - 1
+ update_property_group(context)
+
+
+ # reset history
+ for rto in rto_history.values():
+ rto.clear()
+
+ return {'FINISHED'}
+
+rename = [False]
+class CMNewCollectionOperator(bpy.types.Operator):
+ '''Add New Collection'''
+ bl_label = "Add New Collection"
+ bl_idname = "view3d.add_collection"
+ bl_options = {'UNDO'}
+
+ child: BoolProperty()
+
+ def execute(self, context):
+ global rto_history
+
+ new_collection = bpy.data.collections.new('Collection')
+ scn = context.scene
+
+ # if there are collections
+ if len(scn.CMListCollection) > 0:
+ # get selected collection
+ laycol = layer_collections[scn.CMListCollection[scn.CMListIndex].name]
+
+ # add new collection
+ if self.child:
+ laycol["ptr"].collection.children.link(new_collection)
+ expanded.append(laycol["name"])
+
+ # update tree view property
+ update_property_group(context)
+
+ scn.CMListIndex = layer_collections[new_collection.name]["row_index"]
+
+ else:
+ laycol["parent"]["ptr"].collection.children.link(new_collection)
+
+ # update tree view property
+ update_property_group(context)
+
+ scn.CMListIndex = layer_collections[new_collection.name]["row_index"]
+
+ # if no collections add top level collection and select it
+ else:
+ scn.collection.children.link(new_collection)
+
+ # update tree view property
+ update_property_group(context)
+
+ scn.CMListIndex = 0
+
+ global rename
+ rename[0] = True
+
+ # reset history
+ for rto in rto_history.values():
+ rto.clear()
+
+ return {'FINISHED'}
+
+
+phantom_history = {"view_layer": "",
+ "initial_state": {},
+
+ "exclude_history": [],
+ "select_history": [],
+ "hide_history": [],
+ "disable_history": [],
+ "render_history": [],
+
+ "exclude_all_history": [],
+ "select_all_history": [],
+ "hide_all_history": [],
+ "disable_all_history": [],
+ "render_all_history": []
+ }
+
+class CMPhantomModeOperator(bpy.types.Operator):
+ '''Toggle Phantom Mode'''
+ bl_label = "Toggle Phantom Mode"
+ bl_idname = "view3d.toggle_phantom_mode"
+
+ def execute(self, context):
+ global phantom_history
+ global rto_history
+
+ scn = context.scene
+ view_layer = context.view_layer.name
+
+ # enter Phantom Mode
+ if not scn.CM_Phantom_Mode:
+
+ scn.CM_Phantom_Mode = True
+
+ # save current visibility state
+ phantom_history["view_layer"] = view_layer
+
+ laycol_iter_list = [context.view_layer.layer_collection.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ phantom_history["initial_state"][layer_collection.name] = {
+ "exclude": layer_collection.exclude,
+ "select": layer_collection.collection.hide_select,
+ "hide": layer_collection.hide_viewport,
+ "disable": layer_collection.collection.hide_viewport,
+ "render": layer_collection.collection.hide_render,
+ }
+
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+
+ # save current rto history
+ for rto, history, in rto_history.items():
+ phantom_history[rto+"_history"] = history.get(view_layer, {}).copy()
+
+
+ # return to normal mode
+ else:
+ laycol_iter_list = [context.view_layer.layer_collection.children]
+ while len(laycol_iter_list) > 0:
+ new_laycol_iter_list = []
+ for laycol_iter in laycol_iter_list:
+ for layer_collection in laycol_iter:
+ phantom_laycol = phantom_history["initial_state"][layer_collection.name]
+
+ layer_collection.exclude = \
+ phantom_laycol["exclude"]
+
+ layer_collection.collection.hide_select = \
+ phantom_laycol["select"]
+
+ layer_collection.hide_viewport = \
+ phantom_laycol["hide"]
+
+ layer_collection.collection.hide_viewport = \
+ phantom_laycol["disable"]
+
+ layer_collection.collection.hide_render = \
+ phantom_laycol["render"]
+
+
+ if len(layer_collection.children) > 0:
+ new_laycol_iter_list.append(layer_collection.children)
+
+ laycol_iter_list = new_laycol_iter_list
+
+
+ # restore previous rto history
+ for rto, history, in rto_history.items():
+ history[view_layer] = phantom_history[rto+"_history"].copy()
+
+ scn.CM_Phantom_Mode = False
+
+
+ return {'FINISHED'}
diff --git a/object_collection_manager/ui.py b/object_collection_manager/ui.py
new file mode 100644
index 00000000..4c0e4597
--- /dev/null
+++ b/object_collection_manager/ui.py
@@ -0,0 +1,412 @@
+from bpy.types import (
+ Operator,
+ Panel,
+ UIList,
+ UI_UL_list,
+ )
+
+from bpy.props import BoolProperty
+
+from .internals import *
+from .operators import (
+ rto_history,
+ rename,
+ phantom_history,
+ )
+
+
+class CollectionManager(Operator):
+ bl_label = "Collection Manager"
+ bl_idname = "view3d.collection_manager"
+
+ last_view_layer = ""
+
+ def draw(self, context):
+ layout = self.layout
+ scn = context.scene
+ view_layer = context.view_layer.name
+
+ if view_layer != self.last_view_layer:
+ update_collection_tree(context)
+ self.last_view_layer = view_layer
+
+ title_row = layout.split(factor=0.5)
+ main = title_row.row()
+ view = title_row.row(align=True)
+ view.alignment = 'RIGHT'
+
+ main.label(text="Collection Manager")
+
+ view.prop(context.view_layer, "use", text="")
+ view.separator()
+
+ window = context.window
+ scene = window.scene
+ view.template_search(
+ window, "view_layer",
+ scene, "view_layers",
+ new="scene.view_layer_add",
+ unlink="scene.view_layer_remove")
+
+ layout.row().separator()
+ layout.row().separator()
+
+ filter_row = layout.row()
+ filter_row.alignment = 'RIGHT'
+
+ filter_row.popover(panel="COLLECTIONMANAGER_PT_restriction_toggles", text="", icon='FILTER')
+
+ toggle_row = layout.split(factor=0.3)
+ toggle_row.alignment = 'LEFT'
+
+ sec1 = toggle_row.row()
+ sec1.alignment = 'LEFT'
+ sec1.enabled = False
+
+ if len(expanded) > 0:
+ text = "Collapse All Items"
+ else:
+ text = "Expand All Items"
+
+ sec1.operator("view3d.expand_all_items", text=text)
+
+ for laycol in collection_tree:
+ if laycol["has_children"]:
+ sec1.enabled = True
+ break
+
+ sec2 = toggle_row.row()
+ sec2.alignment = 'RIGHT'
+
+ if scn.show_exclude:
+ exclude_all_history = rto_history["exclude_all"].get(view_layer, [])
+ depress = True if len(exclude_all_history) else False
+
+ sec2.operator("view3d.un_exclude_all_collections", text="", icon='CHECKBOX_HLT', depress=depress)
+
+ if scn.show_selectable:
+ select_all_history = rto_history["select_all"].get(view_layer, [])
+ depress = True if len(select_all_history) else False
+
+ sec2.operator("view3d.un_restrict_select_all_collections", text="", icon='RESTRICT_SELECT_OFF', depress=depress)
+
+ if scn.show_hideviewport:
+ hide_all_history = rto_history["hide_all"].get(view_layer, [])
+ depress = True if len(hide_all_history) else False
+
+ sec2.operator("view3d.un_hide_all_collections", text="", icon='HIDE_OFF', depress=depress)
+
+ if scn.show_disableviewport:
+ disable_all_history = rto_history["disable_all"].get(view_layer, [])
+ depress = True if len(disable_all_history) else False
+
+ sec2.operator("view3d.un_disable_viewport_all_collections", text="", icon='RESTRICT_VIEW_OFF', depress=depress)
+
+ if scn.show_render:
+ render_all_history = rto_history["render_all"].get(view_layer, [])
+ depress = True if len(render_all_history) else False
+
+ sec2.operator("view3d.un_disable_render_all_collections", text="", icon='RESTRICT_RENDER_OFF', depress=depress)
+
+ layout.row().template_list("CM_UL_items", "", context.scene, "CMListCollection", context.scene, "CMListIndex", rows=15, sort_lock=True)
+
+ addcollec_row = layout.row()
+ addcollec_row.operator("view3d.add_collection", text="Add Collection", icon='COLLECTION_NEW').child = False
+
+ addcollec_row.operator("view3d.add_collection", text="Add SubCollection", icon='COLLECTION_NEW').child = True
+
+ phantom_row = layout.row()
+ toggle_text = "Disable " if scn.CM_Phantom_Mode else "Enable "
+ phantom_row.operator("view3d.toggle_phantom_mode", text=toggle_text+"Phantom Mode")
+
+ if scn.CM_Phantom_Mode:
+ view.enabled = False
+ addcollec_row.enabled = False
+
+
+ def execute(self, context):
+ wm = context.window_manager
+ lvl = 0
+
+ #expanded.clear()
+
+ #excludeall_history.clear()
+ #restrictselectall_history.clear()
+ #hideall_history.clear()
+ #disableviewall_history.clear()
+ #disablerenderall_history.clear()
+
+ update_property_group(context)
+
+ lvl = get_max_lvl()
+
+ if lvl > 25:
+ lvl = 25
+
+ self.view_layer = context.view_layer.name
+
+ # sync selection in ui list with active layer collection
+ try:
+ active_laycol_name = context.view_layer.active_layer_collection.name
+ active_laycol_row_index = layer_collections[active_laycol_name]["row_index"]
+ context.scene.CMListIndex = active_laycol_row_index
+ except:
+ context.scene.CMListIndex = 0
+
+ if context.scene.CM_Phantom_Mode:
+ if set(layer_collections.keys()) != set(phantom_history["initial_state"].keys()):
+ context.scene.CM_Phantom_Mode = False
+
+ if context.view_layer.name != phantom_history["view_layer"]:
+ context.scene.CM_Phantom_Mode = False
+
+ return wm.invoke_popup(self, width=(400+(lvl*20)))
+
+
+def update_selection(self, context):
+ selected_item = context.scene.CMListCollection[context.scene.CMListIndex]
+ layer_collection = layer_collections[selected_item.name]["ptr"]
+
+ context.view_layer.active_layer_collection = layer_collection
+
+
+def filter_items_by_name_insensitive(pattern, bitflag, items, propname="name", flags=None, reverse=False):
+ """
+ Set FILTER_ITEM for items which name matches filter_name one (case-insensitive).
+ pattern is the filtering pattern.
+ propname is the name of the string property to use for filtering.
+ flags must be a list of integers the same length as items, or None!
+ return a list of flags (based on given flags if not None),
+ or an empty list if no flags were given and no filtering has been done.
+ """
+ import fnmatch
+
+ if not pattern or not items: # Empty pattern or list = no filtering!
+ return flags or []
+
+ if flags is None:
+ flags = [0] * len(items)
+
+ # Make pattern case-insensitive
+ pattern = pattern.lower()
+
+ # Implicitly add heading/trailing wildcards.
+ pattern = "*" + pattern + "*"
+
+ for i, item in enumerate(items):
+ name = getattr(item, propname, None)
+
+ # Make name case-insensitive
+ name = name.lower()
+
+ # This is similar to a logical xor
+ if bool(name and fnmatch.fnmatch(name, pattern)) is not bool(reverse):
+ flags[i] |= bitflag
+
+ return flags
+
+
+class CM_UL_items(UIList):
+ last_filter_value = ""
+
+ filter_by_selected: BoolProperty(
+ name="Filter By Selected",
+ default=False,
+ description="Filter collections by selected items"
+ )
+
+ def draw_item(self, context, layout, data, item, icon, active_data,active_propname, index):
+ self.use_filter_show = True
+
+ scn = context.scene
+ view_layer = context.view_layer.name
+ laycol = layer_collections[item.name]
+ collection = laycol["ptr"].collection
+
+ split = layout.split(factor=0.96)
+ row = split.row(align=True)
+ row.alignment = 'LEFT'
+
+ # indent child items
+ if laycol["lvl"] > 0:
+ for x in range(laycol["lvl"]):
+ row.label(icon='BLANK1')
+
+ # add expander if collection has children to make UIList act like tree view
+ if laycol["has_children"]:
+ if laycol["expanded"]:
+ prop = row.operator("view3d.expand_sublevel", text="", icon='DISCLOSURE_TRI_DOWN', emboss=False)
+ prop.expand = False
+ prop.name = item.name
+ prop.index = index
+
+ else:
+ prop = row.operator("view3d.expand_sublevel", text="", icon='DISCLOSURE_TRI_RIGHT', emboss=False)
+ prop.expand = True
+ prop.name = item.name
+ prop.index = index
+
+ else:
+ row.label(icon='BLANK1')
+
+
+ row.label(icon='GROUP')
+
+ name_row = row.row()
+
+ #if rename[0] and index == scn.CMListIndex:
+ #name_row.activate_init = True
+ #rename[0] = False
+
+ name_row.prop(item, "name", text="", expand=True)
+
+ # used as a separator (actual separator not wide enough)
+ row.label()
+
+ # add set_collection op
+ row_setcol = row.row()
+ row_setcol.operator_context = 'INVOKE_DEFAULT'
+
+ icon = 'MESH_CUBE'
+
+ if len(context.selected_objects) > 0 and context.active_object:
+ if context.active_object.name in collection.objects:
+ icon = 'SNAP_VOLUME'
+ else:
+ row_setcol.enabled = False
+
+
+ prop = row_setcol.operator("view3d.set_collection", text="", icon=icon, emboss=False)
+ prop.collection_index = laycol["id"]
+ prop.collection_name = item.name
+
+
+ if scn.show_exclude:
+ exclude_history_base = rto_history["exclude"].get(view_layer, {})
+ exclude_target = exclude_history_base.get("target", "")
+ exclude_history = exclude_history_base.get("history", [])
+
+ depress = True if len(exclude_history) and exclude_target == item.name else False
+ emboss = True if len(exclude_history) and exclude_target == item.name else False
+ icon = 'CHECKBOX_DEHLT' if laycol["ptr"].exclude else 'CHECKBOX_HLT'
+
+ row.operator("view3d.exclude_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name
+
+ if scn.show_selectable:
+ select_history_base = rto_history["select"].get(view_layer, {})
+ select_target = select_history_base.get("target", "")
+ select_history = select_history_base.get("history", [])
+
+ depress = True if len(select_history) and select_target == item.name else False
+ emboss = True if len(select_history) and select_target == item.name else False
+ icon = 'RESTRICT_SELECT_ON' if laycol["ptr"].collection.hide_select else 'RESTRICT_SELECT_OFF'
+
+ row.operator("view3d.restrict_select_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name
+
+ if scn.show_hideviewport:
+ hide_history_base = rto_history["hide"].get(view_layer, {})
+ hide_target = hide_history_base.get("target", "")
+ hide_history = hide_history_base.get("history", [])
+
+ depress = True if len(hide_history) and hide_target == item.name else False
+ emboss = True if len(hide_history) and hide_target == item.name else False
+ icon = 'HIDE_ON' if laycol["ptr"].hide_viewport else 'HIDE_OFF'
+
+ row.operator("view3d.hide_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name
+
+ if scn.show_disableviewport:
+ disable_history_base = rto_history["disable"].get(view_layer, {})
+ disable_target = disable_history_base.get("target", "")
+ disable_history = disable_history_base.get("history", [])
+
+ depress = True if len(disable_history) and disable_target == item.name else False
+ emboss = True if len(disable_history) and disable_target == item.name else False
+ icon = 'RESTRICT_VIEW_ON' if laycol["ptr"].collection.hide_viewport else 'RESTRICT_VIEW_OFF'
+
+ row.operator("view3d.disable_viewport_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name
+
+ if scn.show_render:
+ render_history_base = rto_history["render"].get(view_layer, {})
+ render_target = render_history_base.get("target", "")
+ render_history = render_history_base.get("history", [])
+
+ depress = True if len(render_history) and render_target == item.name else False
+ emboss = True if len(render_history) and render_target == item.name else False
+ icon = 'RESTRICT_RENDER_ON' if laycol["ptr"].collection.hide_render else 'RESTRICT_RENDER_OFF'
+
+ row.operator("view3d.disable_render_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name
+
+
+ rm_op = split.row()
+ rm_op.alignment = 'RIGHT'
+ rm_op.operator("view3d.remove_collection", text="", icon='X', emboss=False).collection_name = item.name
+
+ if scn.CM_Phantom_Mode:
+ name_row.enabled = False
+ row_setcol.enabled = False
+ rm_op.enabled = False
+
+
+ def draw_filter(self, context, layout):
+ row = layout.row()
+
+ subrow = row.row(align=True)
+ subrow.prop(self, "filter_name", text="")
+
+ icon = 'ZOOM_OUT' if self.use_filter_invert else 'ZOOM_IN'
+ subrow.prop(self, "use_filter_invert", text="", icon=icon)
+
+ subrow = row.row(align=True)
+ subrow.prop(self, "filter_by_selected", text="", icon='SNAP_VOLUME')
+
+ def filter_items(self, context, data, propname):
+ flt_flags = []
+ flt_neworder = []
+
+ list_items = getattr(data, propname)
+
+ if self.filter_name:
+ flt_flags = filter_items_by_name_insensitive(self.filter_name, self.bitflag_filter_item, list_items)
+
+ elif self.filter_by_selected:
+ flt_flags = [0] * len(list_items)
+
+ for idx, item in enumerate(list_items):
+ collection = layer_collections[item.name]["ptr"].collection
+
+ # check if any of the selected objects are in the collection
+ if not set(context.selected_objects).isdisjoint(collection.objects):
+ flt_flags[idx] |= self.bitflag_filter_item
+
+ else: # display as treeview
+ flt_flags = [self.bitflag_filter_item] * len(list_items)
+
+ for idx, item in enumerate(list_items):
+ if not layer_collections[item.name]["visible"]:
+ flt_flags[idx] = 0
+
+ return flt_flags, flt_neworder
+
+
+
+ def invoke(self, context, event):
+ pass
+
+
+class CMRestrictionTogglesPanel(Panel):
+ bl_label = "Restriction Toggles"
+ bl_idname = "COLLECTIONMANAGER_PT_restriction_toggles"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'HEADER'
+
+ def draw(self, context):
+
+ layout = self.layout
+
+ row = layout.row()
+
+ row.prop(context.scene, "show_exclude", icon='CHECKBOX_HLT', icon_only=True)
+ row.prop(context.scene, "show_selectable", icon='RESTRICT_SELECT_OFF', icon_only=True)
+ row.prop(context.scene, "show_hideviewport", icon='HIDE_OFF', icon_only=True)
+ row.prop(context.scene, "show_disableviewport", icon='RESTRICT_VIEW_OFF', icon_only=True)
+ row.prop(context.scene, "show_render", icon='RESTRICT_RENDER_OFF', icon_only=True)