Collection Manager: Add Operator. Task: T69577
Adds a Remove Empty Collections operator in a new specials menu in the main Collection Manager popup. This operator has two modes: Mode one only removes collections if they don't have subcollections or objects. Mode two removes all collections that don't contain objects. Both of these modes are accessible via the new specials menu.
This commit is contained in:
parent
39b8dbb572
commit
711efc3e2c
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)
|
||||
link_child_collections_to_parent(laycol, collection, parent_collection)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
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 -= 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"]
|
||||
|
||||
|
||||
# update qcd
|
||||
if qcd_slots.contains(name=self.collection_name):
|
||||
qcd_slots.del_slot(name=self.collection_name)
|
||||
|
||||
if self.collection_name in qcd_slots.overrides:
|
||||
qcd_slots.overrides.remove(self.collection_name)
|
||||
|
||||
# reset history
|
||||
for rto in rto_history.values():
|
||||
rto.clear()
|
||||
# remove collection, update references, and update tree view
|
||||
remove_collection(laycol, collection, context)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CMRemoveEmptyCollectionsOperator(Operator):
|
||||
bl_label = "Remove Empty Collections"
|
||||
bl_idname = "view3d.remove_empty_collections"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
else:
|
||||
tooltip = (
|
||||
"Remove Empty Collections.\n"
|
||||
"Delete collections that don't have any subcollections or objects"
|
||||
)
|
||||
|
||||
return tooltip
|
||||
|
||||
def execute(self, context):
|
||||
global rto_history
|
||||
global expand_history
|
||||
global qcd_slots
|
||||
|
||||
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
|
||||
|
||||
# link all child collections to the parent collection preserving view layer RTOs
|
||||
if collection.children:
|
||||
link_child_collections_to_parent(laycol, collection, parent_collection)
|
||||
|
||||
# remove collection, update references, and update tree view
|
||||
remove_collection(laycol, collection, context)
|
||||
|
||||
self.report({"INFO"}, f"Removed {len(empty_collections)} collections")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
rename = [False]
|
||||
class CMNewCollectionOperator(Operator):
|
||||
bl_label = "Add New Collection"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue