Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2011, Ryan Inch
from copy import deepcopy
from bpy.types import (
Operator,
)
from bpy.props import (
BoolProperty,
StringProperty,
IntProperty
from . import internals
from .internals import (
update_property_group,
generate_state,
check_state,
get_move_selection,
get_move_active,
from .operator_utils import (
apply_to_children,
isolate_rto,
toggle_children,
activate_all_rtos,
invert_rtos,
copy_rtos,
swap_rtos,
clear_copy,
clear_swap,
link_child_collections_to_parent,
remove_collection,
select_collection_objects,
isolate_sel_objs_collections,
disable_sel_objs_collections,
class SetActiveCollection(Operator):
'''Set the active collection'''
bl_label = "Set Active Collection"
bl_idname = "view3d.set_active_collection"
bl_options = {'UNDO'}
is_master_collection: BoolProperty()
collection_name: StringProperty()
def execute(self, context):
layer_collection = context.view_layer.layer_collection
else:
laycol = internals.layer_collections[self.collection_name]
layer_collection = laycol["ptr"]
# set selection to this row
cm = context.scene.collection_manager
cm.cm_list_index = laycol["row_index"]
context.view_layer.active_layer_collection = layer_collection
if context.view_layer.active_layer_collection != layer_collection:
self.report({'WARNING'}, "Can't set excluded collection as active")
return {'FINISHED'}
class ExpandAllOperator(Operator):
'''Expand/Collapse all collections'''
bl_label = "Expand All Items"
bl_idname = "view3d.expand_all_items"
if len(internals.expanded) > 0:
internals.expanded.clear()
context.scene.collection_manager.cm_list_index = 0
for laycol in internals.layer_collections.values():
internals.expanded.add(laycol["name"])
internals.expand_history["target"] = ""
internals.expand_history["history"].clear()
# update tree view
update_property_group(context)
return {'FINISHED'}
class ExpandSublevelOperator(Operator):
bl_label = "Expand Sublevel Items"
bl_description = (
" * Ctrl+LMB - Expand/Collapse all sublevels\n"
" * Shift+LMB - Isolate tree/Restore\n"
" * Alt+LMB - Discard history"
)
bl_idname = "view3d.expand_sublevel"
expand: BoolProperty()
name: StringProperty()
index: IntProperty()
def invoke(self, context, event):
cls = ExpandSublevelOperator
modifiers = get_modifiers(event)
if modifiers == {"alt"}:
internals.expand_history["target"] = ""
internals.expand_history["history"].clear()
elif modifiers == {"ctrl"}:
# expand/collapse all subcollections
expand = None
# check whether to expand or collapse
if self.name in internals.expanded:
internals.expanded.remove(self.name)
internals.expanded.add(self.name)
def set_expanded(layer_collection):
if expand:
internals.expanded.add(layer_collection.name)
internals.expanded.discard(layer_collection.name)
apply_to_children(internals.layer_collections[self.name]["ptr"], set_expanded)
internals.expand_history["target"] = ""
internals.expand_history["history"].clear()
elif modifiers == {"shift"}:
def isolate_tree(current_laycol):
parent = current_laycol["parent"]
for laycol in parent["children"]:
if (laycol["name"] != current_laycol["name"]
and laycol["name"] in internals.expanded):
internals.expanded.remove(laycol["name"])
internals.expand_history["history"].append(laycol["name"])
if parent["parent"]:
isolate_tree(parent)
if self.name == internals.expand_history["target"]:
for item in internals.expand_history["history"]:
internals.expanded.add(item)
internals.expand_history["target"] = ""
internals.expand_history["history"].clear()
internals.expand_history["target"] = ""
internals.expand_history["history"].clear()
isolate_tree(internals.layer_collections[self.name])
internals.expand_history["target"] = self.name
else:
# expand/collapse collection
if self.expand:
internals.expanded.add(self.name)
internals.expanded.remove(self.name)
internals.expand_history["target"] = ""
internals.expand_history["history"].clear()
# set the selected row to the collection you're expanding/collapsing to
# preserve the tree view's scrolling
context.scene.collection_manager.cm_list_index = self.index
update_property_group(context)
return {'FINISHED'}
class CMSelectCollectionObjectsOperator(Operator):
bl_label = "Select All Objects in the Collection"
bl_description = (
" * LMB - Select all objects in collection.\n"
" * Shift+LMB - Add/Remove collection objects from selection.\n"
" * Ctrl+LMB - Isolate nested selection.\n"
" * Ctrl+Shift+LMB - Add/Remove nested from selection"
)
bl_idname = "view3d.select_collection_objects"
bl_options = {'REGISTER', 'UNDO'}
is_master_collection: BoolProperty()
collection_name: StringProperty()
def invoke(self, context, event):
modifiers = get_modifiers(event)
if modifiers == {"shift"}:
select_collection_objects(
is_master_collection=self.is_master_collection,
collection_name=self.collection_name,
replace=False,
nested=False
)
elif modifiers == {"ctrl"}:
select_collection_objects(
is_master_collection=self.is_master_collection,
collection_name=self.collection_name,
replace=True,
nested=True
)
elif modifiers == {"ctrl", "shift"}:
select_collection_objects(
is_master_collection=self.is_master_collection,
collection_name=self.collection_name,
replace=False,
nested=True
)
else:
select_collection_objects(
is_master_collection=self.is_master_collection,
collection_name=self.collection_name,
replace=True,
nested=False
)
return {'FINISHED'}
class SelectAllCumulativeObjectsOperator(Operator):
'''Select all objects that are present in more than one collection'''
bl_label = "Select All Cumulative Objects"
bl_idname = "view3d.select_all_cumulative_objects"
def execute(self, context):
selected_cumulative_objects = 0
total_cumulative_objects = 0
bpy.ops.object.select_all(action='DESELECT')
for obj in bpy.data.objects:
if len(obj.users_collection) > 1:
if obj.visible_get():
obj.select_set(True)
if obj.select_get() == True: # needed because obj.select_set can fail silently
selected_cumulative_objects +=1
total_cumulative_objects += 1
self.report({'INFO'}, f"{selected_cumulative_objects}/{total_cumulative_objects} Cumulative Objects Selected")
return {'FINISHED'}
class CMSendObjectsToCollectionOperator(Operator):
bl_label = "Send Objects to Collection"
" * LMB - Move objects to collection.\n"
" * Shift+LMB - Add/Remove objects from collection"
bl_idname = "view3d.send_objects_to_collection"
bl_options = {'REGISTER', 'UNDO'}
is_master_collection: BoolProperty()
collection_name: StringProperty()
def invoke(self, context, event):
target_collection = context.view_layer.layer_collection.collection
else:
laycol = internals.layer_collections[self.collection_name]
target_collection = laycol["ptr"].collection
selected_objects = get_move_selection()
active_object = get_move_active()
internals.move_triggered = True
if not selected_objects:
return {'CANCELLED'}
# add objects to collection
# make sure there is an active object
if not active_object:
active_object = tuple(selected_objects)[0]
if not active_object.name in target_collection.objects:
for obj in selected_objects:
if obj.name not in target_collection.objects:
target_collection.objects.link(obj)
warnings = False
master_warning = False
# remove from collections
for obj in selected_objects:
if obj.name in target_collection.objects:
# disallow removing if only one
if len(obj.users_collection) == 1:
warnings = True
master_laycol = context.view_layer.layer_collection
master_collection = master_laycol.collection
if obj.name not in master_collection.objects:
master_collection.objects.link(obj)
else:
master_warning = True
continue
# remove from collection
target_collection.objects.unlink(obj)
if warnings:
if master_warning:
send_report(
"Error removing 1 or more objects from the Scene Collection.\n"
"Objects would be left without a collection."
)
self.report({"WARNING"},
"Error removing 1 or more objects from the Scene Collection."
" Objects would be left without a collection."
)
else:
self.report({"INFO"}, "1 or more objects moved to Scene Collection.")
# move objects to collection
for obj in selected_objects:
if obj.name not in target_collection.objects:
target_collection.objects.link(obj)
# remove from all other collections
for collection in obj.users_collection:
if collection != target_collection:
collection.objects.unlink(obj)
# update the active object if needed
if not context.active_object:
try:
context.view_layer.objects.active = active_object
except RuntimeError: # object not in visible collection
pass
# update qcd header UI
return {'FINISHED'}
class CMExcludeOperator(Operator):
bl_label = "[EC] Exclude from View Layer"
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.exclude_collection"
bl_options = {'REGISTER', 'UNDO'}
# static class var
isolated = False
def invoke(self, context, event):
cls = CMExcludeOperator
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
orig_active_collection = context.view_layer.active_layer_collection
orig_active_object = context.view_layer.objects.active
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["exclude"]:
internals.rto_history["exclude"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.rto_history["exclude"][view_layer]
cls.isolated = False
elif modifiers == {"shift"}:
isolate_rto(cls, self, view_layer, "exclude")
elif modifiers == {"ctrl"}:
toggle_children(self, view_layer, "exclude")
cls.isolated = False
elif modifiers == {"ctrl", "shift"}:
isolate_rto(cls, self, view_layer, "exclude", children=True)
else:
# toggle exclusion
del internals.rto_history["exclude"][view_layer]
set_exclude_state(laycol_ptr, not laycol_ptr.exclude)
# restore active collection
context.view_layer.active_layer_collection = orig_active_collection
# restore active object if possible
if orig_active_object:
if orig_active_object.name in context.view_layer.objects:
context.view_layer.objects.active = orig_active_object
# reset exclude all history
if view_layer in internals.rto_history["exclude_all"]:
del internals.rto_history["exclude_all"][view_layer]
return {'FINISHED'}
class CMUnExcludeAllOperator(Operator):
bl_label = "[EC Global] Exclude from View Layer"
bl_description = (
" * LMB - Enable all/Restore.\n"
" * Shift+LMB - Invert.\n"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
" * Ctrl+LMB - Copy/Paste RTOs.\n"
" * Ctrl+Alt+LMB - Swap RTOs.\n"
" * Alt+LMB - Discard history"
)
bl_idname = "view3d.un_exclude_all_collections"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
orig_active_collection = context.view_layer.active_layer_collection
orig_active_object = context.view_layer.objects.active
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["exclude_all"]:
internals.rto_history["exclude_all"][view_layer] = []
if modifiers == {"alt"}:
del internals.rto_history["exclude_all"][view_layer]
clear_copy("exclude")
clear_swap("exclude")
copy_rtos(view_layer, "exclude")
elif modifiers == {"ctrl", "alt"}:
swap_rtos(view_layer, "exclude")
invert_rtos(view_layer, "exclude")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "exclude", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "exclude", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
activate_all_rtos(view_layer, "exclude")
# restore active collection
context.view_layer.active_layer_collection = orig_active_collection
# restore active object if possible
if orig_active_object:
if orig_active_object.name in context.view_layer.objects:
context.view_layer.objects.active = orig_active_object
return {'FINISHED'}
class CMRestrictSelectOperator(Operator):
bl_label = "[SS] Disable Selection"
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.restrict_select_collection"
bl_options = {'REGISTER', 'UNDO'}
# static class var
isolated = False
def invoke(self, context, event):
cls = CMRestrictSelectOperator
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["select"]:
internals.rto_history["select"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.rto_history["select"][view_layer]
cls.isolated = False
elif modifiers == {"shift"}:
isolate_rto(cls, self, view_layer, "select")
elif modifiers == {"ctrl"}:
toggle_children(self, view_layer, "select")
cls.isolated = False
elif modifiers == {"ctrl", "shift"}:
isolate_rto(cls, self, view_layer, "select", children=True)
# toggle selectable
del internals.rto_history["select"][view_layer]
# toggle selectability of collection
laycol_ptr.collection.hide_select = not laycol_ptr.collection.hide_select
if view_layer in internals.rto_history["select_all"]:
del internals.rto_history["select_all"][view_layer]
return {'FINISHED'}
class CMUnRestrictSelectAllOperator(Operator):
bl_label = "[SS Global] Disable Selection"
bl_description = (
" * LMB - Enable all/Restore.\n"
" * Shift+LMB - Invert.\n"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
" * Ctrl+LMB - Copy/Paste RTOs.\n"
" * Ctrl+Alt+LMB - Swap RTOs.\n"
" * Alt+LMB - Discard history"
)
bl_idname = "view3d.un_restrict_select_all_collections"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["select_all"]:
internals.rto_history["select_all"][view_layer] = []
if modifiers == {"alt"}:
del internals.rto_history["select_all"][view_layer]
clear_copy("select")
clear_swap("select")
copy_rtos(view_layer, "select")
elif modifiers == {"ctrl", "alt"}:
swap_rtos(view_layer, "select")
invert_rtos(view_layer, "select")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "select", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "select", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
activate_all_rtos(view_layer, "select")
return {'FINISHED'}
class CMHideOperator(Operator):
bl_label = "[VV] Hide in Viewport"
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.hide_collection"
bl_options = {'REGISTER', 'UNDO'}
# static class var
isolated = False
def invoke(self, context, event):
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["hide"]:
internals.rto_history["hide"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.rto_history["hide"][view_layer]
cls.isolated = False
elif modifiers == {"shift"}:
isolate_rto(cls, self, view_layer, "hide")
elif modifiers == {"ctrl"}:
toggle_children(self, view_layer, "hide")
cls.isolated = False
elif modifiers == {"ctrl", "shift"}:
isolate_rto(cls, self, view_layer, "hide", children=True)
del internals.rto_history["hide"][view_layer]
# toggle view of collection
laycol_ptr.hide_viewport = not laycol_ptr.hide_viewport
if view_layer in internals.rto_history["hide_all"]:
del internals.rto_history["hide_all"][view_layer]
return {'FINISHED'}
class CMUnHideAllOperator(Operator):
bl_label = "[VV Global] Hide in Viewport"
bl_description = (
" * LMB - Enable all/Restore.\n"
" * Shift+LMB - Invert.\n"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
" * Ctrl+LMB - Copy/Paste RTOs.\n"
" * Ctrl+Alt+LMB - Swap RTOs.\n"
" * Alt+LMB - Discard history"
)
bl_idname = "view3d.un_hide_all_collections"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["hide_all"]:
internals.rto_history["hide_all"][view_layer] = []
if modifiers == {"alt"}:
del internals.rto_history["hide_all"][view_layer]
clear_copy("hide")
clear_swap("hide")
copy_rtos(view_layer, "hide")
elif modifiers == {"ctrl", "alt"}:
swap_rtos(view_layer, "hide")
invert_rtos(view_layer, "hide")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "hide", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "hide", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
activate_all_rtos(view_layer, "hide")
return {'FINISHED'}
class CMDisableViewportOperator(Operator):
bl_label = "[DV] Disable in Viewports"
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.disable_viewport_collection"
bl_options = {'REGISTER', 'UNDO'}
# static class var
isolated = False
def invoke(self, context, event):
cls = CMDisableViewportOperator
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["disable"]:
internals.rto_history["disable"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.rto_history["disable"][view_layer]
cls.isolated = False
elif modifiers == {"shift"}:
isolate_rto(cls, self, view_layer, "disable")
elif modifiers == {"ctrl"}:
toggle_children(self, view_layer, "disable")
cls.isolated = False
elif modifiers == {"ctrl", "shift"}:
isolate_rto(cls, self, view_layer, "disable", children=True)
del internals.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 internals.rto_history["disable_all"]:
del internals.rto_history["disable_all"][view_layer]
return {'FINISHED'}
class CMUnDisableViewportAllOperator(Operator):
bl_label = "[DV Global] Disable in Viewports"
bl_description = (
" * LMB - Enable all/Restore.\n"
" * Shift+LMB - Invert.\n"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
" * Ctrl+LMB - Copy/Paste RTOs.\n"
" * Ctrl+Alt+LMB - Swap RTOs.\n"
" * Alt+LMB - Discard history"
)
bl_idname = "view3d.un_disable_viewport_all_collections"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["disable_all"]:
internals.rto_history["disable_all"][view_layer] = []
if modifiers == {"alt"}:
del internals.rto_history["disable_all"][view_layer]
clear_copy("disable")
clear_swap("disable")
copy_rtos(view_layer, "disable")
elif modifiers == {"ctrl", "alt"}:
swap_rtos(view_layer, "disable")
invert_rtos(view_layer, "disable")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "disable", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "disable", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
activate_all_rtos(view_layer, "disable")
return {'FINISHED'}
class CMDisableRenderOperator(Operator):
bl_label = "[RR] Disable in Renders"
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.disable_render_collection"
bl_options = {'REGISTER', 'UNDO'}
# static class var
isolated = False
def invoke(self, context, event):
cls = CMDisableRenderOperator
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["render"]:
internals.rto_history["render"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.rto_history["render"][view_layer]
cls.isolated = False
elif modifiers == {"shift"}:
isolate_rto(cls, self, view_layer, "render")
elif modifiers == {"ctrl"}:
toggle_children(self, view_layer, "render")
cls.isolated = False
elif modifiers == {"ctrl", "shift"}:
isolate_rto(cls, self, view_layer, "render", children=True)
# toggle renderable
del internals.rto_history["render"][view_layer]
# toggle renderability of collection
laycol_ptr.collection.hide_render = not laycol_ptr.collection.hide_render
if view_layer in internals.rto_history["render_all"]:
del internals.rto_history["render_all"][view_layer]
return {'FINISHED'}
class CMUnDisableRenderAllOperator(Operator):
bl_label = "[RR Global] Disable in Renders"
bl_description = (
" * LMB - Enable all/Restore.\n"
" * Shift+LMB - Invert.\n"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
" * Ctrl+LMB - Copy/Paste RTOs.\n"
" * Ctrl+Alt+LMB - Swap RTOs.\n"
" * Alt+LMB - Discard history"
)
bl_idname = "view3d.un_disable_render_all_collections"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["render_all"]:
internals.rto_history["render_all"][view_layer] = []
if modifiers == {"alt"}:
del internals.rto_history["render_all"][view_layer]
clear_copy("render")
clear_swap("render")
copy_rtos(view_layer, "render")
elif modifiers == {"ctrl", "alt"}:
swap_rtos(view_layer, "render")
invert_rtos(view_layer, "render")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "render", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "render", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
activate_all_rtos(view_layer, "render")
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):
cls = CMHoldoutOperator
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["holdout"]:
internals.rto_history["holdout"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.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 internals.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 internals.rto_history["holdout_all"]:
del internals.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"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\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):
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["holdout_all"]:
internals.rto_history["holdout_all"][view_layer] = []
if modifiers == {"alt"}:
# clear all states
del internals.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")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "holdout", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "holdout", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
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):
cls = CMIndirectOnlyOperator
modifiers = get_modifiers(event)
view_layer = context.view_layer.name
laycol_ptr = internals.layer_collections[self.name]["ptr"]
if not view_layer in internals.rto_history["indirect"]:
internals.rto_history["indirect"][view_layer] = {"target": "", "history": []}
if modifiers == {"alt"}:
del internals.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 internals.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 internals.rto_history["indirect_all"]:
del internals.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"
" * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
" * Shift+Alt+LMB - Disable collections w/ selected objects.\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):
view_layer = context.view_layer.name
modifiers = get_modifiers(event)
if not view_layer in internals.rto_history["indirect_all"]:
internals.rto_history["indirect_all"][view_layer] = []
if modifiers == {"alt"}:
# clear all states
del internals.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")
elif modifiers == {"shift", "ctrl"}:
error = isolate_sel_objs_collections(view_layer, "indirect", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
elif modifiers == {"shift", "alt"}:
error = disable_sel_objs_collections(view_layer, "indirect", "CM")
if error:
self.report({"WARNING"}, error)
return {'CANCELLED'}
else:
activate_all_rtos(view_layer, "indirect")
return {'FINISHED'}
class CMRemoveCollectionOperator(Operator):
'''Remove Collection'''
bl_label = "Remove Collection"
bl_idname = "view3d.remove_collection"
bl_options = {'UNDO'}
collection_name: StringProperty()
def execute(self, context):
laycol = internals.layer_collections[self.collection_name]
collection = laycol["ptr"].collection
parent_collection = laycol["parent"]["ptr"].collection
# shift all objects in this collection to the parent collection
for obj in collection.objects:
if obj.name not in parent_collection.objects:
parent_collection.objects.link(obj)
# shift all child collections to the parent collection preserving view layer RTOs
link_child_collections_to_parent(laycol, collection, parent_collection)
# remove collection, update references, and update tree view
remove_collection(laycol, collection, context)
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"
)
def execute(self, context):
if self.without_objects:
empty_collections = [laycol["name"]
for laycol in internals.layer_collections.values()
if not laycol["ptr"].collection.objects]
else:
empty_collections = [laycol["name"]
for laycol in internals.layer_collections.values()
if not laycol["children"] and
not laycol["ptr"].collection.objects]
for name in empty_collections:
laycol = internals.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")
class CMNewCollectionOperator(Operator):
bl_label = "Add New Collection"
bl_idname = "view3d.add_collection"
bl_options = {'UNDO'}
@classmethod
def description(cls, context, properties):
if properties.child:
tooltip = (
"Add New SubCollection.\n"
"Add a new subcollection to the currently selected collection"
)
else:
tooltip = (
"Add New Collection.\n"
"Add a new collection as a sibling of the currently selected collection"
)
return tooltip
def execute(self, context):
new_collection = bpy.data.collections.new("New Collection")
cm = context.scene.collection_manager
# prevent adding collections when collections are filtered
# and the selection is ambiguous
if cm.cm_list_index == -1 and ui.CM_UL_items.filtering:
send_report("Cannot create new collection.\n"
"No collection is selected and collections are filtered."
)
return {'CANCELLED'}
if cm.cm_list_index > -1 and not ui.CM_UL_items.visible_items[cm.cm_list_index]:
send_report("Cannot create new collection.\n"
"The selected collection isn't visible."
)
return {'CANCELLED'}
# if there are collections
if len(cm.cm_list_collection) > 0:
if not cm.cm_list_index == -1:
# get selected collection
laycol = internals.layer_collections[cm.cm_list_collection[cm.cm_list_index].name]
# add new collection
if self.child:
laycol["ptr"].collection.children.link(new_collection)
internals.expanded.add(laycol["name"])
# update tree view property
update_property_group(context)
cm.cm_list_index = internals.layer_collections[new_collection.name]["row_index"]
else:
laycol["parent"]["ptr"].collection.children.link(new_collection)
# update tree view property
update_property_group(context)
cm.cm_list_index = internals.layer_collections[new_collection.name]["row_index"]
context.scene.collection.children.link(new_collection)
# update tree view property
update_property_group(context)
cm.cm_list_index = internals.layer_collections[new_collection.name]["row_index"]
# if no collections add top level collection and select it
else:
context.scene.collection.children.link(new_collection)
# update tree view property
update_property_group(context)
# set new collection to active
layer_collection = internals.layer_collections[new_collection.name]["ptr"]
context.view_layer.active_layer_collection = layer_collection
# show the new collection when collections are filtered.
ui.CM_UL_items.new_collections.append(new_collection.name)
global rename
rename[0] = True
for rto in internals.rto_history.values():
class CMPhantomModeOperator(Operator):
bl_label = "Toggle Phantom Mode"
bl_idname = "view3d.toggle_phantom_mode"
bl_description = (
"Phantom Mode\n"
"Saves the state of all RTOs and only allows changes to them, on exit all RTOs are returned to their saved state.\n"
"Note: modifying collections (except RTOs) externally will exit Phantom Mode and your initial state will be lost"
)
def execute(self, context):
cm = context.scene.collection_manager
view_layer = context.view_layer
# save current visibility state
internals.phantom_history["view_layer"] = view_layer.name
def save_visibility_state(layer_collection):
internals.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,
"holdout": layer_collection.holdout,
"indirect": layer_collection.indirect_only,
apply_to_children(view_layer.layer_collection, save_visibility_state)
# save current rto history
for rto, history, in internals.rto_history.items():
if history.get(view_layer.name, None):
internals.phantom_history[rto+"_history"] = deepcopy(history[view_layer.name])
else: # return to normal mode
def restore_visibility_state(layer_collection):
phantom_laycol = internals.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"]
layer_collection.holdout = phantom_laycol["holdout"]
layer_collection.indirect_only = phantom_laycol["indirect"]
apply_to_children(view_layer.layer_collection, restore_visibility_state)
# restore previous rto history
for rto, history, in internals.rto_history.items():
if view_layer.name in history:
del history[view_layer.name]
if internals.phantom_history[rto+"_history"]:
history[view_layer.name] = deepcopy(internals.phantom_history[rto+"_history"])
internals.phantom_history[rto+"_history"].clear()
class CMApplyPhantomModeOperator(Operator):
'''Apply changes and quit Phantom Mode'''
bl_label = "Apply Phantom Mode"
bl_idname = "view3d.apply_phantom_mode"
def execute(self, context):
cm = context.scene.collection_manager
cm.in_phantom_mode = False
return {'FINISHED'}
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
class CMDisableObjectsOperator(Operator):
'''Disable selected objects in viewports'''
bl_label = "Disable Selected"
bl_idname = "view3d.disable_selected_objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj in context.selected_objects:
obj.hide_viewport = True
return {'FINISHED'}
class CMDisableUnSelectedObjectsOperator(Operator):
'''Disable unselected objects in viewports'''
bl_label = "Disable Unselected"
bl_idname = "view3d.disable_unselected_objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj in bpy.data.objects:
if obj in context.visible_objects and not obj in context.selected_objects:
obj.hide_viewport = True
return {'FINISHED'}
class CMRestoreDisabledObjectsOperator(Operator):
'''Restore disabled objects in viewports'''
bl_label = "Restore Disabled Objects"
bl_idname = "view3d.restore_disabled_objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj in bpy.data.objects:
if obj.hide_viewport:
obj.hide_viewport = False
obj.select_set(True)
return {'FINISHED'}
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
class CMUndoWrapper(Operator):
bl_label = "Undo"
bl_description = "Undo previous action"
bl_idname = "view3d.undo_wrapper"
@classmethod
def poll(self, context):
return bpy.ops.ed.undo.poll()
def execute(self, context):
internals.collection_state.clear()
internals.collection_state.update(generate_state())
bpy.ops.ed.undo()
update_property_group(context)
check_state(context, cm_popup=True)
# clear buffers
internals.copy_buffer["RTO"] = ""
internals.copy_buffer["values"].clear()
internals.swap_buffer["A"]["RTO"] = ""
internals.swap_buffer["A"]["values"].clear()
internals.swap_buffer["B"]["RTO"] = ""
internals.swap_buffer["B"]["values"].clear()
return {'FINISHED'}
class CMRedoWrapper(Operator):
bl_label = "Redo"
bl_description = "Redo previous action"
bl_idname = "view3d.redo_wrapper"
@classmethod
def poll(self, context):
return bpy.ops.ed.redo.poll()
def execute(self, context):
bpy.ops.ed.redo()
update_property_group(context)
return {'FINISHED'}