From 559fbf908ba8721f4c9e72b6dc7c3bfa0863479f Mon Sep 17 00:00:00 2001 From: Ryan Inch Date: Tue, 11 Aug 2020 00:19:34 -0400 Subject: Collection Manager: Add Holdout & Indirect Only. Task: T69577 Add support for the Holdout and Indirect Only RTOs. --- object_collection_manager/__init__.py | 8 +- object_collection_manager/internals.py | 22 ++- object_collection_manager/operator_utils.py | 130 ++++++++++++++--- object_collection_manager/operators.py | 209 ++++++++++++++++++++++++++++ object_collection_manager/ui.py | 68 ++++++++- 5 files changed, 413 insertions(+), 24 deletions(-) diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py index 49bd0de9..d7b312a5 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, 13, 0), + "version": (2, 14, 0), "blender": (2, 80, 0), "location": "View3D - Object Mode (Shortcut - M)", "warning": '', # used for warning icon and text in addons panel @@ -77,6 +77,8 @@ class CollectionManagerProperties(PropertyGroup): show_hide_viewport: BoolProperty(default=True, name="[VV] Hide in Viewport") show_disable_viewport: BoolProperty(default=False, name="[DV] Disable in Viewports") show_render: BoolProperty(default=False, name="[RR] Disable in Renders") + show_holdout: BoolProperty(default=False, name="[HH] Holdout") + show_indirect_only: BoolProperty(default=False, name="[IO] Indirect Only") align_local_ops: BoolProperty(default=False, name="Align Local Options", description="Align local options in a column to the right") @@ -108,6 +110,10 @@ classes = ( operators.CMUnDisableViewportAllOperator, operators.CMDisableRenderOperator, operators.CMUnDisableRenderAllOperator, + operators.CMHoldoutOperator, + operators.CMUnHoldoutAllOperator, + operators.CMIndirectOnlyOperator, + operators.CMUnIndirectOnlyAllOperator, operators.CMNewCollectionOperator, operators.CMRemoveCollectionOperator, operators.CMRemoveEmptyCollectionsOperator, diff --git a/object_collection_manager/internals.py b/object_collection_manager/internals.py index 857e73aa..163e9804 100644 --- a/object_collection_manager/internals.py +++ b/object_collection_manager/internals.py @@ -53,12 +53,16 @@ rto_history = { "disable": {}, "disable_all": {}, "render": {}, - "render_all": {} + "render_all": {}, + "holdout": {}, + "holdout_all": {}, + "indirect": {}, + "indirect_all": {}, } expand_history = { "target": "", - "history": [] + "history": [], } phantom_history = { @@ -70,12 +74,16 @@ phantom_history = { "hide_history": {}, "disable_history": {}, "render_history": {}, + "holdout_history": {}, + "indirect_history": {}, "exclude_all_history": [], "select_all_history": [], "hide_all_history": [], "disable_all_history": [], - "render_all_history": [] + "render_all_history": [], + "holdout_all_history": [], + "indirect_all_history": [], } copy_buffer = { @@ -317,7 +325,9 @@ def update_col_name(self, context): "select", "hide", "disable", - "render" + "render", + "holdout", + "indirect", ] orig_targets = { @@ -584,6 +594,8 @@ def generate_state(): "hide": [], "disable": [], "render": [], + "holdout": [], + "indirect": [], } for name, laycol in layer_collections.items(): @@ -593,6 +605,8 @@ def generate_state(): state["hide"].append(laycol["ptr"].hide_viewport) state["disable"].append(laycol["ptr"].collection.hide_viewport) state["render"].append(laycol["ptr"].collection.hide_render) + state["holdout"].append(laycol["ptr"].holdout) + state["indirect"].append(laycol["ptr"].indirect_only) return state diff --git a/object_collection_manager/operator_utils.py b/object_collection_manager/operator_utils.py index ed7fce09..6f7ee83b 100644 --- a/object_collection_manager/operator_utils.py +++ b/object_collection_manager/operator_utils.py @@ -36,12 +36,67 @@ rto_path = { "select": "collection.hide_select", "hide": "hide_viewport", "disable": "collection.hide_viewport", - "render": "collection.hide_render" + "render": "collection.hide_render", + "holdout": "holdout", + "indirect": "indirect_only", + } + +set_off_on = { + "exclude": { + "off": True, + "on": False + }, + "select": { + "off": True, + "on": False + }, + "hide": { + "off": True, + "on": False + }, + "disable": { + "off": True, + "on": False + }, + "render": { + "off": True, + "on": False + }, + "holdout": { + "off": False, + "on": True + }, + "indirect": { + "off": False, + "on": True + } + } + +get_off_on = { + False: { + "exclude": "on", + "select": "on", + "hide": "on", + "disable": "on", + "render": "on", + "holdout": "off", + "indirect": "off", + }, + + True: { + "exclude": "off", + "select": "off", + "hide": "off", + "disable": "off", + "render": "off", + "holdout": "on", + "indirect": "on", + } } def get_rto(layer_collection, rto): - if rto in ["exclude", "hide"]: + if rto in ["exclude", "hide", "holdout", "indirect"]: return getattr(layer_collection, rto_path[rto]) else: @@ -50,7 +105,7 @@ def get_rto(layer_collection, rto): def set_rto(layer_collection, rto, value): - if rto in ["exclude", "hide"]: + if rto in ["exclude", "hide", "holdout", "indirect"]: setattr(layer_collection, rto_path[rto], value) else: @@ -76,13 +131,16 @@ def apply_to_children(parent, apply_function): def isolate_rto(cls, self, view_layer, rto, *, children=False): + off = set_off_on[rto]["off"] + on = set_off_on[rto]["on"] + laycol_ptr = layer_collections[self.name]["ptr"] target = rto_history[rto][view_layer]["target"] history = rto_history[rto][view_layer]["history"] # get active collections active_layer_collections = [x["ptr"] for x in layer_collections.values() - if not get_rto(x["ptr"], rto)] + if get_rto(x["ptr"], rto) == on] # check if previous state should be restored if cls.isolated and self.name == target: @@ -100,7 +158,7 @@ def isolate_rto(cls, self, view_layer, rto, *, children=False): active_layer_collections[0].name == self.name): # activate all collections for item in layer_collections.values(): - set_rto(item["ptr"], rto, False) + set_rto(item["ptr"], rto, on) # reset target and history del rto_history[rto][view_layer] @@ -130,15 +188,15 @@ def isolate_rto(cls, self, view_layer, rto, *, children=False): # isolate collection for item in layer_collections.values(): if item["name"] != laycol_ptr.name: - set_rto(item["ptr"], rto, True) + set_rto(item["ptr"], rto, off) - set_rto(laycol_ptr, rto, False) + set_rto(laycol_ptr, rto, on) - if rto != "exclude": + if rto not in ["exclude", "holdout", "indirect"]: # activate all parents laycol = layer_collections[self.name] while laycol["id"] != 0: - set_rto(laycol["ptr"], rto, False) + set_rto(laycol["ptr"], rto, on) laycol = laycol["parent"] if children: @@ -156,7 +214,7 @@ def isolate_rto(cls, self, view_layer, rto, *, children=False): apply_to_children(laycol_ptr, restore_child_states) - else: + elif rto == "exclude": # deactivate all children def deactivate_all_children(layer_collection): set_rto(layer_collection, rto, True) @@ -183,6 +241,9 @@ def toggle_children(self, view_layer, rto): def activate_all_rtos(view_layer, rto): + off = set_off_on[rto]["off"] + on = set_off_on[rto]["on"] + history = rto_history[rto+"_all"][view_layer] # if not activated, activate all @@ -190,12 +251,12 @@ def activate_all_rtos(view_layer, rto): keep_history = False for item in reversed(list(layer_collections.values())): - if get_rto(item["ptr"], rto) == True: + if get_rto(item["ptr"], rto) == off: keep_history = True history.append(get_rto(item["ptr"], rto)) - set_rto(item["ptr"], rto, False) + set_rto(item["ptr"], rto, on) if not keep_history: history.clear() @@ -233,12 +294,22 @@ def copy_rtos(view_layer, rto): # copy copy_buffer["RTO"] = rto for laycol in layer_collections.values(): - copy_buffer["values"].append(get_rto(laycol["ptr"], rto)) + copy_buffer["values"].append(get_off_on[ + get_rto(laycol["ptr"], rto) + ][ + rto + ] + ) else: # paste for x, laycol in enumerate(layer_collections.values()): - set_rto(laycol["ptr"], rto, copy_buffer["values"][x]) + set_rto(laycol["ptr"], + rto, + set_off_on[rto][ + copy_buffer["values"][x] + ] + ) # clear rto history rto_history[rto].pop(view_layer, None) @@ -254,18 +325,41 @@ def swap_rtos(view_layer, rto): # get A swap_buffer["A"]["RTO"] = rto for laycol in layer_collections.values(): - swap_buffer["A"]["values"].append(get_rto(laycol["ptr"], rto)) + swap_buffer["A"]["values"].append(get_off_on[ + get_rto(laycol["ptr"], rto) + ][ + rto + ] + ) else: # get B swap_buffer["B"]["RTO"] = rto for laycol in layer_collections.values(): - swap_buffer["B"]["values"].append(get_rto(laycol["ptr"], rto)) + swap_buffer["B"]["values"].append(get_off_on[ + get_rto(laycol["ptr"], rto) + ][ + rto + ] + ) # swap A with B for x, laycol in enumerate(layer_collections.values()): - set_rto(laycol["ptr"], swap_buffer["A"]["RTO"], swap_buffer["B"]["values"][x]) - set_rto(laycol["ptr"], swap_buffer["B"]["RTO"], swap_buffer["A"]["values"][x]) + set_rto(laycol["ptr"], swap_buffer["A"]["RTO"], + set_off_on[ + swap_buffer["A"]["RTO"] + ][ + swap_buffer["B"]["values"][x] + ] + ) + + set_rto(laycol["ptr"], swap_buffer["B"]["RTO"], + set_off_on[ + swap_buffer["B"]["RTO"] + ][ + swap_buffer["A"]["values"][x] + ] + ) # clear rto history diff --git a/object_collection_manager/operators.py b/object_collection_manager/operators.py index 7509d3f2..ff1bbf95 100644 --- a/object_collection_manager/operators.py +++ b/object_collection_manager/operators.py @@ -909,6 +909,211 @@ class CMUnDisableRenderAllOperator(Operator): return {'FINISHED'} +class CMHoldoutOperator(Operator): + bl_label = "[HH] Holdout" + bl_description = ( + " * Shift+LMB - Isolate/Restore.\n" + " * Shift+Ctrl+LMB - Isolate nested/Restore.\n" + " * Ctrl+LMB - Toggle nested.\n" + " * Alt+LMB - Discard history" + ) + bl_idname = "view3d.holdout_collection" + bl_options = {'REGISTER', 'UNDO'} + + name: StringProperty() + + # static class var + isolated = False + + def invoke(self, context, event): + global rto_history + cls = CMHoldoutOperator + + modifiers = get_modifiers(event) + view_layer = context.view_layer.name + laycol_ptr = layer_collections[self.name]["ptr"] + + if not view_layer in rto_history["holdout"]: + rto_history["holdout"][view_layer] = {"target": "", "history": []} + + if modifiers == {"alt"}: + del rto_history["holdout"][view_layer] + cls.isolated = False + + elif modifiers == {"shift"}: + isolate_rto(cls, self, view_layer, "holdout") + + elif modifiers == {"ctrl"}: + toggle_children(self, view_layer, "holdout") + + cls.isolated = False + + elif modifiers == {"ctrl", "shift"}: + isolate_rto(cls, self, view_layer, "holdout", children=True) + + else: + # toggle holdout + + # reset holdout history + del rto_history["holdout"][view_layer] + + # toggle holdout of collection in viewport + laycol_ptr.holdout = not laycol_ptr.holdout + + cls.isolated = False + + # reset holdout all history + if view_layer in rto_history["holdout_all"]: + del rto_history["holdout_all"][view_layer] + + return {'FINISHED'} + + +class CMUnHoldoutAllOperator(Operator): + bl_label = "[HH Global] Holdout" + bl_description = ( + " * LMB - Enable all/Restore.\n" + " * Shift+LMB - Invert.\n" + " * Ctrl+LMB - Copy/Paste RTOs.\n" + " * Ctrl+Alt+LMB - Swap RTOs.\n" + " * Alt+LMB - Discard history" + ) + bl_idname = "view3d.un_holdout_all_collections" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + global rto_history + + view_layer = context.view_layer.name + modifiers = get_modifiers(event) + + if not view_layer in rto_history["holdout_all"]: + rto_history["holdout_all"][view_layer] = [] + + if modifiers == {"alt"}: + # clear all states + del rto_history["holdout_all"][view_layer] + clear_copy("holdout") + clear_swap("holdout") + + elif modifiers == {"ctrl"}: + copy_rtos(view_layer, "holdout") + + elif modifiers == {"ctrl", "alt"}: + swap_rtos(view_layer, "holdout") + + elif modifiers == {"shift"}: + invert_rtos(view_layer, "holdout") + + else: + activate_all_rtos(view_layer, "holdout") + + return {'FINISHED'} + + +class CMIndirectOnlyOperator(Operator): + bl_label = "[IO] Indirect Only" + bl_description = ( + " * Shift+LMB - Isolate/Restore.\n" + " * Shift+Ctrl+LMB - Isolate nested/Restore.\n" + " * Ctrl+LMB - Toggle nested.\n" + " * Alt+LMB - Discard history" + ) + bl_idname = "view3d.indirect_only_collection" + bl_options = {'REGISTER', 'UNDO'} + + name: StringProperty() + + # static class var + isolated = False + + def invoke(self, context, event): + global rto_history + cls = CMIndirectOnlyOperator + + modifiers = get_modifiers(event) + view_layer = context.view_layer.name + laycol_ptr = layer_collections[self.name]["ptr"] + + if not view_layer in rto_history["indirect"]: + rto_history["indirect"][view_layer] = {"target": "", "history": []} + + + if modifiers == {"alt"}: + del rto_history["indirect"][view_layer] + cls.isolated = False + + elif modifiers == {"shift"}: + isolate_rto(cls, self, view_layer, "indirect") + + elif modifiers == {"ctrl"}: + toggle_children(self, view_layer, "indirect") + + cls.isolated = False + + elif modifiers == {"ctrl", "shift"}: + isolate_rto(cls, self, view_layer, "indirect", children=True) + + else: + # toggle indirect only + + # reset indirect history + del rto_history["indirect"][view_layer] + + # toggle indirect only of collection + laycol_ptr.indirect_only = not laycol_ptr.indirect_only + + cls.isolated = False + + # reset indirect all history + if view_layer in rto_history["indirect_all"]: + del rto_history["indirect_all"][view_layer] + + return {'FINISHED'} + + +class CMUnIndirectOnlyAllOperator(Operator): + bl_label = "[IO Global] Indirect Only" + bl_description = ( + " * LMB - Enable all/Restore.\n" + " * Shift+LMB - Invert.\n" + " * Ctrl+LMB - Copy/Paste RTOs.\n" + " * Ctrl+Alt+LMB - Swap RTOs.\n" + " * Alt+LMB - Discard history" + ) + bl_idname = "view3d.un_indirect_only_all_collections" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + global rto_history + + view_layer = context.view_layer.name + modifiers = get_modifiers(event) + + if not view_layer in rto_history["indirect_all"]: + rto_history["indirect_all"][view_layer] = [] + + if modifiers == {"alt"}: + # clear all states + del rto_history["indirect_all"][view_layer] + clear_copy("indirect") + clear_swap("indirect") + + elif modifiers == {"ctrl"}: + copy_rtos(view_layer, "indirect") + + elif modifiers == {"ctrl", "alt"}: + swap_rtos(view_layer, "indirect") + + elif modifiers == {"shift"}: + invert_rtos(view_layer, "indirect") + + else: + activate_all_rtos(view_layer, "indirect") + + return {'FINISHED'} + + class CMRemoveCollectionOperator(Operator): '''Remove Collection''' bl_label = "Remove Collection" @@ -1112,6 +1317,8 @@ class CMPhantomModeOperator(Operator): "hide": layer_collection.hide_viewport, "disable": layer_collection.collection.hide_viewport, "render": layer_collection.collection.hide_render, + "holdout": layer_collection.holdout, + "indirect": layer_collection.indirect_only, } apply_to_children(view_layer.layer_collection, save_visibility_state) @@ -1132,6 +1339,8 @@ class CMPhantomModeOperator(Operator): layer_collection.hide_viewport = phantom_laycol["hide"] layer_collection.collection.hide_viewport = phantom_laycol["disable"] layer_collection.collection.hide_render = phantom_laycol["render"] + layer_collection.holdout = phantom_laycol["holdout"] + layer_collection.indirect_only = phantom_laycol["indirect"] apply_to_children(view_layer.layer_collection, restore_visibility_state) diff --git a/object_collection_manager/ui.py b/object_collection_manager/ui.py index 7858e5bf..0f2703cb 100644 --- a/object_collection_manager/ui.py +++ b/object_collection_manager/ui.py @@ -302,6 +302,44 @@ class CollectionManager(Operator): global_rto_row.operator("view3d.un_disable_render_all_collections", text="", icon=icon, depress=depress) + if cm.show_holdout: + holdout_all_history = rto_history["holdout_all"].get(view_layer.name, []) + depress = True if len(holdout_all_history) else False + icon = 'HOLDOUT_ON' + buffers = [False, False] + + if copy_buffer["RTO"] == "holdout": + icon = copy_icon + buffers[0] = True + + if swap_buffer["A"]["RTO"] == "holdout": + icon = swap_icon + buffers[1] = True + + if buffers[0] and buffers[1]: + icon = copy_swap_icon + + global_rto_row.operator("view3d.un_holdout_all_collections", text="", icon=icon, depress=depress) + + if cm.show_indirect_only: + indirect_all_history = rto_history["indirect_all"].get(view_layer.name, []) + depress = True if len(indirect_all_history) else False + icon = 'INDIRECT_ONLY_ON' + buffers = [False, False] + + if copy_buffer["RTO"] == "indirect": + icon = copy_icon + buffers[0] = True + + if swap_buffer["A"]["RTO"] == "indirect": + icon = swap_icon + buffers[1] = True + + if buffers[0] and buffers[1]: + icon = copy_swap_icon + + global_rto_row.operator("view3d.un_indirect_only_all_collections", text="", icon=icon, depress=depress) + # treeview layout.row().template_list("CM_UL_items", "", cm, "cm_list_collection", @@ -381,7 +419,7 @@ class CollectionManager(Operator): else: - for rto in ["exclude", "select", "hide", "disable", "render"]: + for rto in ["exclude", "select", "hide", "disable", "render", "holdout", "indirect"]: if new_state[rto] != collection_state[rto]: if view_layer.name in rto_history[rto]: del rto_history[rto][view_layer.name] @@ -628,6 +666,32 @@ class CM_UL_items(UIList): emboss=highlight, depress=highlight) prop.name = item.name + if cm.show_holdout: + holdout_history_base = rto_history["holdout"].get(view_layer.name, {}) + holdout_target = holdout_history_base.get("target", "") + holdout_history = holdout_history_base.get("history", []) + + highlight = bool(holdout_history and holdout_target == item.name) + icon = ('HOLDOUT_ON' if laycol["ptr"].holdout else + 'HOLDOUT_OFF') + + prop = row.operator("view3d.holdout_collection", text="", icon=icon, + emboss=highlight, depress=highlight) + prop.name = item.name + + if cm.show_indirect_only: + indirect_history_base = rto_history["indirect"].get(view_layer.name, {}) + indirect_target = indirect_history_base.get("target", "") + indirect_history = indirect_history_base.get("history", []) + + highlight = bool(indirect_history and indirect_target == item.name) + icon = ('INDIRECT_ONLY_ON' if laycol["ptr"].indirect_only else + 'INDIRECT_ONLY_OFF') + + prop = row.operator("view3d.indirect_only_collection", text="", icon=icon, + emboss=highlight, depress=highlight) + prop.name = item.name + row = s2 @@ -751,6 +815,8 @@ class CMDisplayOptionsPanel(Panel): row.prop(cm, "show_hide_viewport", icon='HIDE_OFF', icon_only=True) row.prop(cm, "show_disable_viewport", icon='RESTRICT_VIEW_OFF', icon_only=True) row.prop(cm, "show_render", icon='RESTRICT_RENDER_OFF', icon_only=True) + row.prop(cm, "show_holdout", icon='HOLDOUT_ON', icon_only=True) + row.prop(cm, "show_indirect_only", icon='INDIRECT_ONLY_ON', icon_only=True) layout.separator() -- cgit v1.2.3