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:
-rw-r--r--object_collection_manager/__init__.py4
-rw-r--r--object_collection_manager/operator_utils.py82
-rw-r--r--object_collection_manager/operators.py106
-rw-r--r--object_collection_manager/ui.py39
4 files changed, 161 insertions, 70 deletions
diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py
index f6a31695..90783e7e 100644
--- a/object_collection_manager/__init__.py
+++ b/object_collection_manager/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "Collection Manager",
"description": "Manage collections and their objects",
"author": "Ryan Inch",
- "version": (2, 9, 5),
+ "version": (2, 10, 0),
"blender": (2, 80, 0),
"location": "View3D - Object Mode (Shortcut - M)",
"warning": '', # used for warning icon and text in addons panel
@@ -110,12 +110,14 @@ classes = (
operators.CMUnDisableRenderAllOperator,
operators.CMNewCollectionOperator,
operators.CMRemoveCollectionOperator,
+ operators.CMRemoveEmptyCollectionsOperator,
operators.CMSetCollectionOperator,
operators.CMPhantomModeOperator,
preferences.CMPreferences,
ui.CM_UL_items,
ui.CollectionManager,
ui.CMDisplayOptionsPanel,
+ ui.SpecialsMenu,
CollectionManagerProperties,
)
diff --git a/object_collection_manager/operator_utils.py b/object_collection_manager/operator_utils.py
index f99d870b..d86a534f 100644
--- a/object_collection_manager/operator_utils.py
+++ b/object_collection_manager/operator_utils.py
@@ -17,12 +17,17 @@
# ##### END GPL LICENSE BLOCK #####
# Copyright 2011, Ryan Inch
+import bpy
from .internals import (
layer_collections,
+ qcd_slots,
+ expanded,
+ expand_history,
rto_history,
copy_buffer,
swap_buffer,
+ update_property_group,
)
rto_path = {
@@ -289,3 +294,80 @@ def clear_swap(rto):
swap_buffer["A"]["values"].clear()
swap_buffer["B"]["RTO"] = ""
swap_buffer["B"]["values"].clear()
+
+
+def link_child_collections_to_parent(laycol, collection, parent_collection):
+ # store view layer RTOs for all children of the to be deleted collection
+ child_states = {}
+ def get_child_states(layer_collection):
+ child_states[layer_collection.name] = (layer_collection.exclude,
+ layer_collection.hide_viewport,
+ layer_collection.holdout,
+ layer_collection.indirect_only)
+
+ apply_to_children(laycol["ptr"], get_child_states)
+
+ # link any subcollections of the to be deleted collection to it's parent
+ for subcollection in collection.children:
+ if not subcollection.name in parent_collection.children:
+ parent_collection.children.link(subcollection)
+
+ # apply the stored view layer RTOs to the newly linked collections and their
+ # children
+ def restore_child_states(layer_collection):
+ state = child_states.get(layer_collection.name)
+
+ if state:
+ layer_collection.exclude = state[0]
+ layer_collection.hide_viewport = state[1]
+ layer_collection.holdout = state[2]
+ layer_collection.indirect_only = state[3]
+
+ apply_to_children(laycol["parent"]["ptr"], restore_child_states)
+
+
+def remove_collection(laycol, collection, context):
+ # get selected row
+ cm = context.scene.collection_manager
+ selected_row_name = cm.cm_list_collection[cm.cm_list_index].name
+
+ # delete collection
+ bpy.data.collections.remove(collection)
+
+ # update references
+ expanded.discard(laycol["name"])
+
+ if expand_history["target"] == laycol["name"]:
+ expand_history["target"] = ""
+
+ if laycol["name"] in expand_history["history"]:
+ expand_history["history"].remove(laycol["name"])
+
+ if qcd_slots.contains(name=laycol["name"]):
+ qcd_slots.del_slot(name=laycol["name"])
+
+ if laycol["name"] in qcd_slots.overrides:
+ qcd_slots.overrides.remove(laycol["name"])
+
+ # reset history
+ for rto in rto_history.values():
+ rto.clear()
+
+ # update tree view
+ update_property_group(context)
+
+ # update selected row
+ laycol = layer_collections.get(selected_row_name, None)
+ if laycol:
+ cm.cm_list_index = laycol["row_index"]
+
+ elif len(cm.cm_list_collection) <= cm.cm_list_index:
+ cm.cm_list_index = len(cm.cm_list_collection) - 1
+
+ if cm.cm_list_index > -1:
+ name = cm.cm_list_collection[cm.cm_list_index].name
+ laycol = layer_collections[name]
+ while not laycol["visible"]:
+ laycol = laycol["parent"]
+
+ cm.cm_list_index = laycol["row_index"]
diff --git a/object_collection_manager/operators.py b/object_collection_manager/operators.py
index 77882c97..642860fa 100644
--- a/object_collection_manager/operators.py
+++ b/object_collection_manager/operators.py
@@ -63,6 +63,8 @@ from .operator_utils import (
swap_rtos,
clear_copy,
clear_swap,
+ link_child_collections_to_parent,
+ remove_collection,
)
class SetActiveCollection(Operator):
@@ -869,12 +871,9 @@ class CMRemoveCollectionOperator(Operator):
global expand_history
global qcd_slots
- cm = context.scene.collection_manager
-
laycol = layer_collections[self.collection_name]
collection = laycol["ptr"].collection
parent_collection = laycol["parent"]["ptr"].collection
- selected_row_name = cm.cm_list_collection[cm.cm_list_index].name
# shift all objects in this collection to the parent collection
@@ -885,78 +884,69 @@ class CMRemoveCollectionOperator(Operator):
# shift all child collections to the parent collection preserving view layer RTOs
if collection.children:
- # store view layer RTOs for all children of the to be deleted collection
- child_states = {}
- def get_child_states(layer_collection):
- child_states[layer_collection.name] = (layer_collection.exclude,
- layer_collection.hide_viewport,
- layer_collection.holdout,
- layer_collection.indirect_only)
-
- apply_to_children(laycol["ptr"], get_child_states)
-
- # link any subcollections of the to be deleted collection to it's parent
- for subcollection in collection.children:
- if not subcollection.name in parent_collection.children:
- parent_collection.children.link(subcollection)
+ link_child_collections_to_parent(laycol, collection, parent_collection)
- # apply the stored view layer RTOs to the newly linked collections and their
- # children
- def restore_child_states(layer_collection):
- state = child_states.get(layer_collection.name)
-
- if state:
- layer_collection.exclude = state[0]
- layer_collection.hide_viewport = state[1]
- layer_collection.holdout = state[2]
- layer_collection.indirect_only = state[3]
-
- apply_to_children(laycol["parent"]["ptr"], restore_child_states)
+ # remove collection, update references, and update tree view
+ remove_collection(laycol, collection, context)
+ return {'FINISHED'}
- # remove collection, update expanded, and update tree view
- bpy.data.collections.remove(collection)
- expanded.discard(self.collection_name)
- if expand_history["target"] == self.collection_name:
- expand_history["target"] = ""
-
- if self.collection_name in expand_history["history"]:
- expand_history["history"].remove(self.collection_name)
+class CMRemoveEmptyCollectionsOperator(Operator):
+ bl_label = "Remove Empty Collections"
+ bl_idname = "view3d.remove_empty_collections"
+ bl_options = {'UNDO'}
- update_property_group(context)
+ without_objects: BoolProperty()
+ @classmethod
+ def description(cls, context, properties):
+ if properties.without_objects:
+ tooltip = (
+ "Purge All Collections Without Objects.\n"
+ "Deletes all collections that don't contain objects even if they have subcollections"
+ )
- # update selected row
- laycol = layer_collections.get(selected_row_name, None)
- if laycol:
- cm.cm_list_index = laycol["row_index"]
+ else:
+ tooltip = (
+ "Remove Empty Collections.\n"
+ "Delete collections that don't have any subcollections or objects"
+ )
- elif len(cm.cm_list_collection) == cm.cm_list_index:
- cm.cm_list_index -= 1
+ return tooltip
- if cm.cm_list_index > -1:
- name = cm.cm_list_collection[cm.cm_list_index].name
- laycol = layer_collections[name]
- while not laycol["visible"]:
- laycol = laycol["parent"]
+ def execute(self, context):
+ global rto_history
+ global expand_history
+ global qcd_slots
- cm.cm_list_index = laycol["row_index"]
+ if self.without_objects:
+ empty_collections = [laycol["name"]
+ for laycol in layer_collections.values()
+ if not laycol["ptr"].collection.objects]
+ else:
+ empty_collections = [laycol["name"]
+ for laycol in layer_collections.values()
+ if not laycol["children"] and
+ not laycol["ptr"].collection.objects]
+ for name in empty_collections:
+ laycol = layer_collections[name]
+ collection = laycol["ptr"].collection
+ parent_collection = laycol["parent"]["ptr"].collection
- # update qcd
- if qcd_slots.contains(name=self.collection_name):
- qcd_slots.del_slot(name=self.collection_name)
+ # link all child collections to the parent collection preserving view layer RTOs
+ if collection.children:
+ link_child_collections_to_parent(laycol, collection, parent_collection)
- if self.collection_name in qcd_slots.overrides:
- qcd_slots.overrides.remove(self.collection_name)
+ # remove collection, update references, and update tree view
+ remove_collection(laycol, collection, context)
- # reset history
- for rto in rto_history.values():
- rto.clear()
+ self.report({"INFO"}, f"Removed {len(empty_collections)} collections")
return {'FINISHED'}
+
rename = [False]
class CMNewCollectionOperator(Operator):
bl_label = "Add New Collection"
diff --git a/object_collection_manager/ui.py b/object_collection_manager/ui.py
index 838c5d18..fab65c67 100644
--- a/object_collection_manager/ui.py
+++ b/object_collection_manager/ui.py
@@ -21,6 +21,7 @@
import bpy
from bpy.types import (
+ Menu,
Operator,
Panel,
UIList,
@@ -112,9 +113,9 @@ class CollectionManager(Operator):
layout.row().separator()
# buttons
- button_row = layout.row()
+ button_row_1 = layout.row()
- op_sec = button_row.row()
+ op_sec = button_row_1.row()
op_sec.alignment = 'LEFT'
collapse_sec = op_sec.row()
@@ -138,11 +139,12 @@ class CollectionManager(Operator):
renum_sec.alignment = 'LEFT'
renum_sec.operator("view3d.renumerate_qcd_slots")
- # filter
- filter_sec = button_row.row()
- filter_sec.alignment = 'RIGHT'
+ # menu & filter
+ right_sec = button_row_1.row()
+ right_sec.alignment = 'RIGHT'
- filter_sec.popover(panel="COLLECTIONMANAGER_PT_display_options",
+ right_sec.menu("VIEW3D_MT_CM_specials_menu")
+ right_sec.popover(panel="COLLECTIONMANAGER_PT_display_options",
text="", icon='FILTER')
mc_box = layout.box()
@@ -304,19 +306,19 @@ class CollectionManager(Operator):
sort_lock=True)
# add collections
- addcollec_row = layout.row()
- prop = addcollec_row.operator("view3d.add_collection", text="Add Collection",
+ button_row_2 = layout.row()
+ prop = button_row_2.operator("view3d.add_collection", text="Add Collection",
icon='COLLECTION_NEW')
prop.child = False
- prop = addcollec_row.operator("view3d.add_collection", text="Add SubCollection",
+ prop = button_row_2.operator("view3d.add_collection", text="Add SubCollection",
icon='COLLECTION_NEW')
prop.child = True
# phantom mode
- phantom_row = layout.row()
+ button_row_3 = layout.row()
toggle_text = "Disable " if cm.in_phantom_mode else "Enable "
- phantom_row.operator("view3d.toggle_phantom_mode", text=toggle_text+"Phantom Mode")
+ button_row_3.operator("view3d.toggle_phantom_mode", text=toggle_text+"Phantom Mode")
if cm.in_phantom_mode:
view.enabled = False
@@ -748,6 +750,21 @@ class CMDisplayOptionsPanel(Panel):
row.prop(cm, "align_local_ops")
+class SpecialsMenu(Menu):
+ bl_label = "Specials"
+ bl_idname = "VIEW3D_MT_CM_specials_menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ prop = layout.operator("view3d.remove_empty_collections")
+ prop.without_objects = False
+
+ prop = layout.operator("view3d.remove_empty_collections",
+ text="Purge All Collections Without Objects")
+ prop.without_objects = True
+
+
def view3d_header_qcd_slots(self, context):
layout = self.layout