1 # SPDX-FileCopyrightText: 2011 Ryan Inch
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 from copy
import deepcopy
9 from bpy
.types
import (
13 from bpy
.props
import (
20 from . import internals
23 from .internals
import (
24 update_property_group
,
34 from .operator_utils
import (
44 link_child_collections_to_parent
,
46 select_collection_objects
,
48 isolate_sel_objs_collections
,
49 disable_sel_objs_collections
,
54 class SetActiveCollection(Operator
):
55 '''Set the active collection'''
56 bl_label
= "Set Active Collection"
57 bl_idname
= "view3d.set_active_collection"
60 is_master_collection
: BoolProperty()
61 collection_name
: StringProperty()
63 def execute(self
, context
):
64 if self
.is_master_collection
:
65 layer_collection
= context
.view_layer
.layer_collection
68 laycol
= internals
.layer_collections
[self
.collection_name
]
69 layer_collection
= laycol
["ptr"]
71 # set selection to this row
72 cm
= context
.scene
.collection_manager
73 cm
.cm_list_index
= laycol
["row_index"]
75 context
.view_layer
.active_layer_collection
= layer_collection
77 if context
.view_layer
.active_layer_collection
!= layer_collection
:
78 self
.report({'WARNING'}, "Can't set excluded collection as active")
83 class ExpandAllOperator(Operator
):
84 '''Expand/Collapse all collections'''
85 bl_label
= "Expand All Items"
86 bl_idname
= "view3d.expand_all_items"
88 def execute(self
, context
):
89 if len(internals
.expanded
) > 0:
90 internals
.expanded
.clear()
91 context
.scene
.collection_manager
.cm_list_index
= 0
93 for laycol
in internals
.layer_collections
.values():
94 if laycol
["ptr"].children
:
95 internals
.expanded
.add(laycol
["name"])
97 # clear expand history
98 internals
.expand_history
["target"] = ""
99 internals
.expand_history
["history"].clear()
102 update_property_group(context
)
107 class ExpandSublevelOperator(Operator
):
108 bl_label
= "Expand Sublevel Items"
110 " * Ctrl+LMB - Expand/Collapse all sublevels\n"
111 " * Shift+LMB - Isolate tree/Restore\n"
112 " * Alt+LMB - Discard history"
114 bl_idname
= "view3d.expand_sublevel"
116 expand
: BoolProperty()
117 name
: StringProperty()
120 def invoke(self
, context
, event
):
121 cls
= ExpandSublevelOperator
123 modifiers
= get_modifiers(event
)
125 if modifiers
== {"alt"}:
126 internals
.expand_history
["target"] = ""
127 internals
.expand_history
["history"].clear()
129 elif modifiers
== {"ctrl"}:
130 # expand/collapse all subcollections
133 # check whether to expand or collapse
134 if self
.name
in internals
.expanded
:
135 internals
.expanded
.remove(self
.name
)
138 internals
.expanded
.add(self
.name
)
141 # do expanding/collapsing
142 def set_expanded(layer_collection
):
144 internals
.expanded
.add(layer_collection
.name
)
146 internals
.expanded
.discard(layer_collection
.name
)
148 apply_to_children(internals
.layer_collections
[self
.name
]["ptr"], set_expanded
)
150 internals
.expand_history
["target"] = ""
151 internals
.expand_history
["history"].clear()
153 elif modifiers
== {"shift"}:
154 def isolate_tree(current_laycol
):
155 parent
= current_laycol
["parent"]
157 for laycol
in parent
["children"]:
158 if (laycol
["name"] != current_laycol
["name"]
159 and laycol
["name"] in internals
.expanded
):
160 internals
.expanded
.remove(laycol
["name"])
161 internals
.expand_history
["history"].append(laycol
["name"])
166 if self
.name
== internals
.expand_history
["target"]:
167 for item
in internals
.expand_history
["history"]:
168 internals
.expanded
.add(item
)
170 internals
.expand_history
["target"] = ""
171 internals
.expand_history
["history"].clear()
174 internals
.expand_history
["target"] = ""
175 internals
.expand_history
["history"].clear()
177 isolate_tree(internals
.layer_collections
[self
.name
])
178 internals
.expand_history
["target"] = self
.name
181 # expand/collapse collection
183 internals
.expanded
.add(self
.name
)
185 internals
.expanded
.remove(self
.name
)
187 internals
.expand_history
["target"] = ""
188 internals
.expand_history
["history"].clear()
190 # set the selected row to the collection you're expanding/collapsing to
191 # preserve the tree view's scrolling
192 context
.scene
.collection_manager
.cm_list_index
= self
.index
195 update_property_group(context
)
200 class CMSelectCollectionObjectsOperator(Operator
):
201 bl_label
= "Select All Objects in the Collection"
203 " * LMB - Select all objects in collection.\n"
204 " * Shift+LMB - Add/Remove collection objects from selection.\n"
205 " * Ctrl+LMB - Isolate nested selection.\n"
206 " * Ctrl+Shift+LMB - Add/Remove nested from selection"
208 bl_idname
= "view3d.select_collection_objects"
209 bl_options
= {'REGISTER', 'UNDO'}
211 is_master_collection
: BoolProperty()
212 collection_name
: StringProperty()
214 def invoke(self
, context
, event
):
215 modifiers
= get_modifiers(event
)
217 if modifiers
== {"shift"}:
218 select_collection_objects(
219 is_master_collection
=self
.is_master_collection
,
220 collection_name
=self
.collection_name
,
225 elif modifiers
== {"ctrl"}:
226 select_collection_objects(
227 is_master_collection
=self
.is_master_collection
,
228 collection_name
=self
.collection_name
,
233 elif modifiers
== {"ctrl", "shift"}:
234 select_collection_objects(
235 is_master_collection
=self
.is_master_collection
,
236 collection_name
=self
.collection_name
,
242 select_collection_objects(
243 is_master_collection
=self
.is_master_collection
,
244 collection_name
=self
.collection_name
,
252 class SelectAllCumulativeObjectsOperator(Operator
):
253 '''Select all objects that are present in more than one collection'''
254 bl_label
= "Select All Cumulative Objects"
255 bl_idname
= "view3d.select_all_cumulative_objects"
257 def execute(self
, context
):
258 selected_cumulative_objects
= 0
259 total_cumulative_objects
= 0
261 bpy
.ops
.object.select_all(action
='DESELECT')
263 for obj
in bpy
.data
.objects
:
264 if len(obj
.users_collection
) > 1:
265 if obj
.visible_get():
267 if obj
.select_get() == True: # needed because obj.select_set can fail silently
268 selected_cumulative_objects
+=1
270 total_cumulative_objects
+= 1
272 self
.report({'INFO'}, f
"{selected_cumulative_objects}/{total_cumulative_objects} Cumulative Objects Selected")
277 class CMSendObjectsToCollectionOperator(Operator
):
278 bl_label
= "Send Objects to Collection"
280 " * LMB - Move objects to collection.\n"
281 " * Shift+LMB - Add/Remove objects from collection"
283 bl_idname
= "view3d.send_objects_to_collection"
284 bl_options
= {'REGISTER', 'UNDO'}
286 is_master_collection
: BoolProperty()
287 collection_name
: StringProperty()
289 def invoke(self
, context
, event
):
290 if self
.is_master_collection
:
291 target_collection
= context
.view_layer
.layer_collection
.collection
294 laycol
= internals
.layer_collections
[self
.collection_name
]
295 target_collection
= laycol
["ptr"].collection
297 selected_objects
= get_move_selection()
298 active_object
= get_move_active()
300 internals
.move_triggered
= True
302 if not selected_objects
:
306 # add objects to collection
308 # make sure there is an active object
309 if not active_object
:
310 active_object
= tuple(selected_objects
)[0]
312 # check if in collection
313 if not active_object
.name
in target_collection
.objects
:
315 for obj
in selected_objects
:
316 if obj
.name
not in target_collection
.objects
:
317 target_collection
.objects
.link(obj
)
321 master_warning
= False
323 # remove from collections
324 for obj
in selected_objects
:
325 if obj
.name
in target_collection
.objects
:
327 # disallow removing if only one
328 if len(obj
.users_collection
) == 1:
330 master_laycol
= context
.view_layer
.layer_collection
331 master_collection
= master_laycol
.collection
333 if obj
.name
not in master_collection
.objects
:
334 master_collection
.objects
.link(obj
)
337 master_warning
= True
341 # remove from collection
342 target_collection
.objects
.unlink(obj
)
347 "Error removing 1 or more objects from the Scene Collection.\n"
348 "Objects would be left without a collection."
350 self
.report({"WARNING"},
351 "Error removing 1 or more objects from the Scene Collection."
352 " Objects would be left without a collection."
356 self
.report({"INFO"}, "1 or more objects moved to Scene Collection.")
360 # move objects to collection
361 for obj
in selected_objects
:
362 if obj
.name
not in target_collection
.objects
:
363 target_collection
.objects
.link(obj
)
365 # remove from all other collections
366 for collection
in obj
.users_collection
:
367 if collection
!= target_collection
:
368 collection
.objects
.unlink(obj
)
370 # update the active object if needed
371 if not context
.active_object
:
373 context
.view_layer
.objects
.active
= active_object
375 except RuntimeError: # object not in visible collection
378 # update qcd header UI
384 class CMExcludeOperator(Operator
):
385 bl_label
= "[EC] Exclude from View Layer"
387 " * Shift+LMB - Isolate/Restore.\n"
388 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
389 " * Ctrl+LMB - Toggle nested.\n"
390 " * Alt+LMB - Discard history"
392 bl_idname
= "view3d.exclude_collection"
393 bl_options
= {'REGISTER', 'UNDO'}
395 name
: StringProperty()
400 def invoke(self
, context
, event
):
401 cls
= CMExcludeOperator
403 modifiers
= get_modifiers(event
)
404 view_layer
= context
.view_layer
.name
405 orig_active_collection
= context
.view_layer
.active_layer_collection
406 orig_active_object
= context
.view_layer
.objects
.active
407 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
409 if not view_layer
in internals
.rto_history
["exclude"]:
410 internals
.rto_history
["exclude"][view_layer
] = {"target": "", "history": []}
412 if modifiers
== {"alt"}:
413 del internals
.rto_history
["exclude"][view_layer
]
416 elif modifiers
== {"shift"}:
417 isolate_rto(cls
, self
, view_layer
, "exclude")
419 elif modifiers
== {"ctrl"}:
420 toggle_children(self
, view_layer
, "exclude")
424 elif modifiers
== {"ctrl", "shift"}:
425 isolate_rto(cls
, self
, view_layer
, "exclude", children
=True)
430 # reset exclude history
431 del internals
.rto_history
["exclude"][view_layer
]
433 set_exclude_state(laycol_ptr
, not laycol_ptr
.exclude
)
437 # restore active collection
438 context
.view_layer
.active_layer_collection
= orig_active_collection
440 # restore active object if possible
441 if orig_active_object
:
442 if orig_active_object
.name
in context
.view_layer
.objects
:
443 context
.view_layer
.objects
.active
= orig_active_object
445 # reset exclude all history
446 if view_layer
in internals
.rto_history
["exclude_all"]:
447 del internals
.rto_history
["exclude_all"][view_layer
]
452 class CMUnExcludeAllOperator(Operator
):
453 bl_label
= "[EC Global] Exclude from View Layer"
455 " * LMB - Enable all/Restore.\n"
456 " * Shift+LMB - Invert.\n"
457 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
458 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
459 " * Ctrl+LMB - Copy/Paste RTOs.\n"
460 " * Ctrl+Alt+LMB - Swap RTOs.\n"
461 " * Alt+LMB - Discard history"
463 bl_idname
= "view3d.un_exclude_all_collections"
464 bl_options
= {'REGISTER', 'UNDO'}
466 def invoke(self
, context
, event
):
467 orig_active_collection
= context
.view_layer
.active_layer_collection
468 orig_active_object
= context
.view_layer
.objects
.active
469 view_layer
= context
.view_layer
.name
470 modifiers
= get_modifiers(event
)
472 if not view_layer
in internals
.rto_history
["exclude_all"]:
473 internals
.rto_history
["exclude_all"][view_layer
] = []
475 if modifiers
== {"alt"}:
477 del internals
.rto_history
["exclude_all"][view_layer
]
478 clear_copy("exclude")
479 clear_swap("exclude")
481 elif modifiers
== {"ctrl"}:
482 copy_rtos(view_layer
, "exclude")
484 elif modifiers
== {"ctrl", "alt"}:
485 swap_rtos(view_layer
, "exclude")
487 elif modifiers
== {"shift"}:
488 invert_rtos(view_layer
, "exclude")
490 elif modifiers
== {"shift", "ctrl"}:
491 error
= isolate_sel_objs_collections(view_layer
, "exclude", "CM")
494 self
.report({"WARNING"}, error
)
497 elif modifiers
== {"shift", "alt"}:
498 error
= disable_sel_objs_collections(view_layer
, "exclude", "CM")
501 self
.report({"WARNING"}, error
)
505 activate_all_rtos(view_layer
, "exclude")
507 # restore active collection
508 context
.view_layer
.active_layer_collection
= orig_active_collection
510 # restore active object if possible
511 if orig_active_object
:
512 if orig_active_object
.name
in context
.view_layer
.objects
:
513 context
.view_layer
.objects
.active
= orig_active_object
518 class CMRestrictSelectOperator(Operator
):
519 bl_label
= "[SS] Disable Selection"
521 " * Shift+LMB - Isolate/Restore.\n"
522 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
523 " * Ctrl+LMB - Toggle nested.\n"
524 " * Alt+LMB - Discard history"
526 bl_idname
= "view3d.restrict_select_collection"
527 bl_options
= {'REGISTER', 'UNDO'}
529 name
: StringProperty()
534 def invoke(self
, context
, event
):
535 cls
= CMRestrictSelectOperator
537 modifiers
= get_modifiers(event
)
538 view_layer
= context
.view_layer
.name
539 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
541 if not view_layer
in internals
.rto_history
["select"]:
542 internals
.rto_history
["select"][view_layer
] = {"target": "", "history": []}
544 if modifiers
== {"alt"}:
545 del internals
.rto_history
["select"][view_layer
]
548 elif modifiers
== {"shift"}:
549 isolate_rto(cls
, self
, view_layer
, "select")
551 elif modifiers
== {"ctrl"}:
552 toggle_children(self
, view_layer
, "select")
556 elif modifiers
== {"ctrl", "shift"}:
557 isolate_rto(cls
, self
, view_layer
, "select", children
=True)
562 # reset select history
563 del internals
.rto_history
["select"][view_layer
]
565 # toggle selectability of collection
566 laycol_ptr
.collection
.hide_select
= not laycol_ptr
.collection
.hide_select
570 # reset select all history
571 if view_layer
in internals
.rto_history
["select_all"]:
572 del internals
.rto_history
["select_all"][view_layer
]
577 class CMUnRestrictSelectAllOperator(Operator
):
578 bl_label
= "[SS Global] Disable Selection"
580 " * LMB - Enable all/Restore.\n"
581 " * Shift+LMB - Invert.\n"
582 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
583 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
584 " * Ctrl+LMB - Copy/Paste RTOs.\n"
585 " * Ctrl+Alt+LMB - Swap RTOs.\n"
586 " * Alt+LMB - Discard history"
588 bl_idname
= "view3d.un_restrict_select_all_collections"
589 bl_options
= {'REGISTER', 'UNDO'}
591 def invoke(self
, context
, event
):
592 view_layer
= context
.view_layer
.name
593 modifiers
= get_modifiers(event
)
595 if not view_layer
in internals
.rto_history
["select_all"]:
596 internals
.rto_history
["select_all"][view_layer
] = []
598 if modifiers
== {"alt"}:
600 del internals
.rto_history
["select_all"][view_layer
]
604 elif modifiers
== {"ctrl"}:
605 copy_rtos(view_layer
, "select")
607 elif modifiers
== {"ctrl", "alt"}:
608 swap_rtos(view_layer
, "select")
610 elif modifiers
== {"shift"}:
611 invert_rtos(view_layer
, "select")
613 elif modifiers
== {"shift", "ctrl"}:
614 error
= isolate_sel_objs_collections(view_layer
, "select", "CM")
617 self
.report({"WARNING"}, error
)
620 elif modifiers
== {"shift", "alt"}:
621 error
= disable_sel_objs_collections(view_layer
, "select", "CM")
624 self
.report({"WARNING"}, error
)
628 activate_all_rtos(view_layer
, "select")
633 class CMHideOperator(Operator
):
634 bl_label
= "[VV] Hide in Viewport"
636 " * Shift+LMB - Isolate/Restore.\n"
637 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
638 " * Ctrl+LMB - Toggle nested.\n"
639 " * Alt+LMB - Discard history"
641 bl_idname
= "view3d.hide_collection"
642 bl_options
= {'REGISTER', 'UNDO'}
644 name
: StringProperty()
649 def invoke(self
, context
, event
):
652 modifiers
= get_modifiers(event
)
653 view_layer
= context
.view_layer
.name
654 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
656 if not view_layer
in internals
.rto_history
["hide"]:
657 internals
.rto_history
["hide"][view_layer
] = {"target": "", "history": []}
659 if modifiers
== {"alt"}:
660 del internals
.rto_history
["hide"][view_layer
]
663 elif modifiers
== {"shift"}:
664 isolate_rto(cls
, self
, view_layer
, "hide")
666 elif modifiers
== {"ctrl"}:
667 toggle_children(self
, view_layer
, "hide")
671 elif modifiers
== {"ctrl", "shift"}:
672 isolate_rto(cls
, self
, view_layer
, "hide", children
=True)
678 del internals
.rto_history
["hide"][view_layer
]
680 # toggle view of collection
681 laycol_ptr
.hide_viewport
= not laycol_ptr
.hide_viewport
685 # reset hide all history
686 if view_layer
in internals
.rto_history
["hide_all"]:
687 del internals
.rto_history
["hide_all"][view_layer
]
692 class CMUnHideAllOperator(Operator
):
693 bl_label
= "[VV Global] Hide in Viewport"
695 " * LMB - Enable all/Restore.\n"
696 " * Shift+LMB - Invert.\n"
697 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
698 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
699 " * Ctrl+LMB - Copy/Paste RTOs.\n"
700 " * Ctrl+Alt+LMB - Swap RTOs.\n"
701 " * Alt+LMB - Discard history"
703 bl_idname
= "view3d.un_hide_all_collections"
704 bl_options
= {'REGISTER', 'UNDO'}
706 def invoke(self
, context
, event
):
707 view_layer
= context
.view_layer
.name
708 modifiers
= get_modifiers(event
)
710 if not view_layer
in internals
.rto_history
["hide_all"]:
711 internals
.rto_history
["hide_all"][view_layer
] = []
713 if modifiers
== {"alt"}:
715 del internals
.rto_history
["hide_all"][view_layer
]
719 elif modifiers
== {"ctrl"}:
720 copy_rtos(view_layer
, "hide")
722 elif modifiers
== {"ctrl", "alt"}:
723 swap_rtos(view_layer
, "hide")
725 elif modifiers
== {"shift"}:
726 invert_rtos(view_layer
, "hide")
728 elif modifiers
== {"shift", "ctrl"}:
729 error
= isolate_sel_objs_collections(view_layer
, "hide", "CM")
732 self
.report({"WARNING"}, error
)
735 elif modifiers
== {"shift", "alt"}:
736 error
= disable_sel_objs_collections(view_layer
, "hide", "CM")
739 self
.report({"WARNING"}, error
)
743 activate_all_rtos(view_layer
, "hide")
748 class CMDisableViewportOperator(Operator
):
749 bl_label
= "[DV] Disable in Viewports"
751 " * Shift+LMB - Isolate/Restore.\n"
752 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
753 " * Ctrl+LMB - Toggle nested.\n"
754 " * Alt+LMB - Discard history"
756 bl_idname
= "view3d.disable_viewport_collection"
757 bl_options
= {'REGISTER', 'UNDO'}
759 name
: StringProperty()
764 def invoke(self
, context
, event
):
765 cls
= CMDisableViewportOperator
767 modifiers
= get_modifiers(event
)
768 view_layer
= context
.view_layer
.name
769 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
771 if not view_layer
in internals
.rto_history
["disable"]:
772 internals
.rto_history
["disable"][view_layer
] = {"target": "", "history": []}
774 if modifiers
== {"alt"}:
775 del internals
.rto_history
["disable"][view_layer
]
778 elif modifiers
== {"shift"}:
779 isolate_rto(cls
, self
, view_layer
, "disable")
781 elif modifiers
== {"ctrl"}:
782 toggle_children(self
, view_layer
, "disable")
786 elif modifiers
== {"ctrl", "shift"}:
787 isolate_rto(cls
, self
, view_layer
, "disable", children
=True)
792 # reset disable history
793 del internals
.rto_history
["disable"][view_layer
]
795 # toggle disable of collection in viewport
796 laycol_ptr
.collection
.hide_viewport
= not laycol_ptr
.collection
.hide_viewport
800 # reset disable all history
801 if view_layer
in internals
.rto_history
["disable_all"]:
802 del internals
.rto_history
["disable_all"][view_layer
]
807 class CMUnDisableViewportAllOperator(Operator
):
808 bl_label
= "[DV Global] Disable in Viewports"
810 " * LMB - Enable all/Restore.\n"
811 " * Shift+LMB - Invert.\n"
812 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
813 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
814 " * Ctrl+LMB - Copy/Paste RTOs.\n"
815 " * Ctrl+Alt+LMB - Swap RTOs.\n"
816 " * Alt+LMB - Discard history"
818 bl_idname
= "view3d.un_disable_viewport_all_collections"
819 bl_options
= {'REGISTER', 'UNDO'}
821 def invoke(self
, context
, event
):
822 view_layer
= context
.view_layer
.name
823 modifiers
= get_modifiers(event
)
825 if not view_layer
in internals
.rto_history
["disable_all"]:
826 internals
.rto_history
["disable_all"][view_layer
] = []
828 if modifiers
== {"alt"}:
830 del internals
.rto_history
["disable_all"][view_layer
]
831 clear_copy("disable")
832 clear_swap("disable")
834 elif modifiers
== {"ctrl"}:
835 copy_rtos(view_layer
, "disable")
837 elif modifiers
== {"ctrl", "alt"}:
838 swap_rtos(view_layer
, "disable")
840 elif modifiers
== {"shift"}:
841 invert_rtos(view_layer
, "disable")
843 elif modifiers
== {"shift", "ctrl"}:
844 error
= isolate_sel_objs_collections(view_layer
, "disable", "CM")
847 self
.report({"WARNING"}, error
)
850 elif modifiers
== {"shift", "alt"}:
851 error
= disable_sel_objs_collections(view_layer
, "disable", "CM")
854 self
.report({"WARNING"}, error
)
858 activate_all_rtos(view_layer
, "disable")
863 class CMDisableRenderOperator(Operator
):
864 bl_label
= "[RR] Disable in Renders"
866 " * Shift+LMB - Isolate/Restore.\n"
867 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
868 " * Ctrl+LMB - Toggle nested.\n"
869 " * Alt+LMB - Discard history"
871 bl_idname
= "view3d.disable_render_collection"
872 bl_options
= {'REGISTER', 'UNDO'}
874 name
: StringProperty()
879 def invoke(self
, context
, event
):
880 cls
= CMDisableRenderOperator
882 modifiers
= get_modifiers(event
)
883 view_layer
= context
.view_layer
.name
884 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
886 if not view_layer
in internals
.rto_history
["render"]:
887 internals
.rto_history
["render"][view_layer
] = {"target": "", "history": []}
890 if modifiers
== {"alt"}:
891 del internals
.rto_history
["render"][view_layer
]
894 elif modifiers
== {"shift"}:
895 isolate_rto(cls
, self
, view_layer
, "render")
897 elif modifiers
== {"ctrl"}:
898 toggle_children(self
, view_layer
, "render")
902 elif modifiers
== {"ctrl", "shift"}:
903 isolate_rto(cls
, self
, view_layer
, "render", children
=True)
908 # reset render history
909 del internals
.rto_history
["render"][view_layer
]
911 # toggle renderability of collection
912 laycol_ptr
.collection
.hide_render
= not laycol_ptr
.collection
.hide_render
916 # reset render all history
917 if view_layer
in internals
.rto_history
["render_all"]:
918 del internals
.rto_history
["render_all"][view_layer
]
923 class CMUnDisableRenderAllOperator(Operator
):
924 bl_label
= "[RR Global] Disable in Renders"
926 " * LMB - Enable all/Restore.\n"
927 " * Shift+LMB - Invert.\n"
928 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
929 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
930 " * Ctrl+LMB - Copy/Paste RTOs.\n"
931 " * Ctrl+Alt+LMB - Swap RTOs.\n"
932 " * Alt+LMB - Discard history"
934 bl_idname
= "view3d.un_disable_render_all_collections"
935 bl_options
= {'REGISTER', 'UNDO'}
937 def invoke(self
, context
, event
):
938 view_layer
= context
.view_layer
.name
939 modifiers
= get_modifiers(event
)
941 if not view_layer
in internals
.rto_history
["render_all"]:
942 internals
.rto_history
["render_all"][view_layer
] = []
944 if modifiers
== {"alt"}:
946 del internals
.rto_history
["render_all"][view_layer
]
950 elif modifiers
== {"ctrl"}:
951 copy_rtos(view_layer
, "render")
953 elif modifiers
== {"ctrl", "alt"}:
954 swap_rtos(view_layer
, "render")
956 elif modifiers
== {"shift"}:
957 invert_rtos(view_layer
, "render")
959 elif modifiers
== {"shift", "ctrl"}:
960 error
= isolate_sel_objs_collections(view_layer
, "render", "CM")
963 self
.report({"WARNING"}, error
)
966 elif modifiers
== {"shift", "alt"}:
967 error
= disable_sel_objs_collections(view_layer
, "render", "CM")
970 self
.report({"WARNING"}, error
)
974 activate_all_rtos(view_layer
, "render")
979 class CMHoldoutOperator(Operator
):
980 bl_label
= "[HH] Holdout"
982 " * Shift+LMB - Isolate/Restore.\n"
983 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
984 " * Ctrl+LMB - Toggle nested.\n"
985 " * Alt+LMB - Discard history"
987 bl_idname
= "view3d.holdout_collection"
988 bl_options
= {'REGISTER', 'UNDO'}
990 name
: StringProperty()
995 def invoke(self
, context
, event
):
996 cls
= CMHoldoutOperator
998 modifiers
= get_modifiers(event
)
999 view_layer
= context
.view_layer
.name
1000 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
1002 if not view_layer
in internals
.rto_history
["holdout"]:
1003 internals
.rto_history
["holdout"][view_layer
] = {"target": "", "history": []}
1005 if modifiers
== {"alt"}:
1006 del internals
.rto_history
["holdout"][view_layer
]
1007 cls
.isolated
= False
1009 elif modifiers
== {"shift"}:
1010 isolate_rto(cls
, self
, view_layer
, "holdout")
1012 elif modifiers
== {"ctrl"}:
1013 toggle_children(self
, view_layer
, "holdout")
1015 cls
.isolated
= False
1017 elif modifiers
== {"ctrl", "shift"}:
1018 isolate_rto(cls
, self
, view_layer
, "holdout", children
=True)
1023 # reset holdout history
1024 del internals
.rto_history
["holdout"][view_layer
]
1026 # toggle holdout of collection in viewport
1027 laycol_ptr
.holdout
= not laycol_ptr
.holdout
1029 cls
.isolated
= False
1031 # reset holdout all history
1032 if view_layer
in internals
.rto_history
["holdout_all"]:
1033 del internals
.rto_history
["holdout_all"][view_layer
]
1038 class CMUnHoldoutAllOperator(Operator
):
1039 bl_label
= "[HH Global] Holdout"
1041 " * LMB - Enable all/Restore.\n"
1042 " * Shift+LMB - Invert.\n"
1043 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
1044 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
1045 " * Ctrl+LMB - Copy/Paste RTOs.\n"
1046 " * Ctrl+Alt+LMB - Swap RTOs.\n"
1047 " * Alt+LMB - Discard history"
1049 bl_idname
= "view3d.un_holdout_all_collections"
1050 bl_options
= {'REGISTER', 'UNDO'}
1052 def invoke(self
, context
, event
):
1053 view_layer
= context
.view_layer
.name
1054 modifiers
= get_modifiers(event
)
1056 if not view_layer
in internals
.rto_history
["holdout_all"]:
1057 internals
.rto_history
["holdout_all"][view_layer
] = []
1059 if modifiers
== {"alt"}:
1061 del internals
.rto_history
["holdout_all"][view_layer
]
1062 clear_copy("holdout")
1063 clear_swap("holdout")
1065 elif modifiers
== {"ctrl"}:
1066 copy_rtos(view_layer
, "holdout")
1068 elif modifiers
== {"ctrl", "alt"}:
1069 swap_rtos(view_layer
, "holdout")
1071 elif modifiers
== {"shift"}:
1072 invert_rtos(view_layer
, "holdout")
1074 elif modifiers
== {"shift", "ctrl"}:
1075 error
= isolate_sel_objs_collections(view_layer
, "holdout", "CM")
1078 self
.report({"WARNING"}, error
)
1079 return {'CANCELLED'}
1081 elif modifiers
== {"shift", "alt"}:
1082 error
= disable_sel_objs_collections(view_layer
, "holdout", "CM")
1085 self
.report({"WARNING"}, error
)
1086 return {'CANCELLED'}
1089 activate_all_rtos(view_layer
, "holdout")
1094 class CMIndirectOnlyOperator(Operator
):
1095 bl_label
= "[IO] Indirect Only"
1097 " * Shift+LMB - Isolate/Restore.\n"
1098 " * Shift+Ctrl+LMB - Isolate nested/Restore.\n"
1099 " * Ctrl+LMB - Toggle nested.\n"
1100 " * Alt+LMB - Discard history"
1102 bl_idname
= "view3d.indirect_only_collection"
1103 bl_options
= {'REGISTER', 'UNDO'}
1105 name
: StringProperty()
1110 def invoke(self
, context
, event
):
1111 cls
= CMIndirectOnlyOperator
1113 modifiers
= get_modifiers(event
)
1114 view_layer
= context
.view_layer
.name
1115 laycol_ptr
= internals
.layer_collections
[self
.name
]["ptr"]
1117 if not view_layer
in internals
.rto_history
["indirect"]:
1118 internals
.rto_history
["indirect"][view_layer
] = {"target": "", "history": []}
1121 if modifiers
== {"alt"}:
1122 del internals
.rto_history
["indirect"][view_layer
]
1123 cls
.isolated
= False
1125 elif modifiers
== {"shift"}:
1126 isolate_rto(cls
, self
, view_layer
, "indirect")
1128 elif modifiers
== {"ctrl"}:
1129 toggle_children(self
, view_layer
, "indirect")
1131 cls
.isolated
= False
1133 elif modifiers
== {"ctrl", "shift"}:
1134 isolate_rto(cls
, self
, view_layer
, "indirect", children
=True)
1137 # toggle indirect only
1139 # reset indirect history
1140 del internals
.rto_history
["indirect"][view_layer
]
1142 # toggle indirect only of collection
1143 laycol_ptr
.indirect_only
= not laycol_ptr
.indirect_only
1145 cls
.isolated
= False
1147 # reset indirect all history
1148 if view_layer
in internals
.rto_history
["indirect_all"]:
1149 del internals
.rto_history
["indirect_all"][view_layer
]
1154 class CMUnIndirectOnlyAllOperator(Operator
):
1155 bl_label
= "[IO Global] Indirect Only"
1157 " * LMB - Enable all/Restore.\n"
1158 " * Shift+LMB - Invert.\n"
1159 " * Shift+Ctrl+LMB - Isolate collections w/ selected objects.\n"
1160 " * Shift+Alt+LMB - Disable collections w/ selected objects.\n"
1161 " * Ctrl+LMB - Copy/Paste RTOs.\n"
1162 " * Ctrl+Alt+LMB - Swap RTOs.\n"
1163 " * Alt+LMB - Discard history"
1165 bl_idname
= "view3d.un_indirect_only_all_collections"
1166 bl_options
= {'REGISTER', 'UNDO'}
1168 def invoke(self
, context
, event
):
1169 view_layer
= context
.view_layer
.name
1170 modifiers
= get_modifiers(event
)
1172 if not view_layer
in internals
.rto_history
["indirect_all"]:
1173 internals
.rto_history
["indirect_all"][view_layer
] = []
1175 if modifiers
== {"alt"}:
1177 del internals
.rto_history
["indirect_all"][view_layer
]
1178 clear_copy("indirect")
1179 clear_swap("indirect")
1181 elif modifiers
== {"ctrl"}:
1182 copy_rtos(view_layer
, "indirect")
1184 elif modifiers
== {"ctrl", "alt"}:
1185 swap_rtos(view_layer
, "indirect")
1187 elif modifiers
== {"shift"}:
1188 invert_rtos(view_layer
, "indirect")
1190 elif modifiers
== {"shift", "ctrl"}:
1191 error
= isolate_sel_objs_collections(view_layer
, "indirect", "CM")
1194 self
.report({"WARNING"}, error
)
1195 return {'CANCELLED'}
1197 elif modifiers
== {"shift", "alt"}:
1198 error
= disable_sel_objs_collections(view_layer
, "indirect", "CM")
1201 self
.report({"WARNING"}, error
)
1202 return {'CANCELLED'}
1205 activate_all_rtos(view_layer
, "indirect")
1210 class CMRemoveCollectionOperator(Operator
):
1211 '''Remove Collection'''
1212 bl_label
= "Remove Collection"
1213 bl_idname
= "view3d.remove_collection"
1214 bl_options
= {'UNDO'}
1216 collection_name
: StringProperty()
1218 def execute(self
, context
):
1219 laycol
= internals
.layer_collections
[self
.collection_name
]
1220 collection
= laycol
["ptr"].collection
1221 parent_collection
= laycol
["parent"]["ptr"].collection
1224 # shift all objects in this collection to the parent collection
1225 for obj
in collection
.objects
:
1226 if obj
.name
not in parent_collection
.objects
:
1227 parent_collection
.objects
.link(obj
)
1230 # shift all child collections to the parent collection preserving view layer RTOs
1231 if collection
.children
:
1232 link_child_collections_to_parent(laycol
, collection
, parent_collection
)
1234 # remove collection, update references, and update tree view
1235 remove_collection(laycol
, collection
, context
)
1240 class CMRemoveEmptyCollectionsOperator(Operator
):
1241 bl_label
= "Remove Empty Collections"
1242 bl_idname
= "view3d.remove_empty_collections"
1243 bl_options
= {'UNDO'}
1245 without_objects
: BoolProperty()
1248 def description(cls
, context
, properties
):
1249 if properties
.without_objects
:
1251 "Purge All Collections Without Objects.\n"
1252 "Deletes all collections that don't contain objects even if they have subcollections"
1257 "Remove Empty Collections.\n"
1258 "Delete collections that don't have any subcollections or objects"
1263 def execute(self
, context
):
1264 if self
.without_objects
:
1265 empty_collections
= [laycol
["name"]
1266 for laycol
in internals
.layer_collections
.values()
1267 if not laycol
["ptr"].collection
.objects
]
1269 empty_collections
= [laycol
["name"]
1270 for laycol
in internals
.layer_collections
.values()
1271 if not laycol
["children"] and
1272 not laycol
["ptr"].collection
.objects
]
1274 for name
in empty_collections
:
1275 laycol
= internals
.layer_collections
[name
]
1276 collection
= laycol
["ptr"].collection
1277 parent_collection
= laycol
["parent"]["ptr"].collection
1279 # link all child collections to the parent collection preserving view layer RTOs
1280 if collection
.children
:
1281 link_child_collections_to_parent(laycol
, collection
, parent_collection
)
1283 # remove collection, update references, and update tree view
1284 remove_collection(laycol
, collection
, context
)
1286 self
.report({"INFO"}, f
"Removed {len(empty_collections)} collections")
1292 class CMNewCollectionOperator(Operator
):
1293 bl_label
= "Add New Collection"
1294 bl_idname
= "view3d.add_collection"
1295 bl_options
= {'UNDO'}
1297 child
: BoolProperty()
1300 def description(cls
, context
, properties
):
1301 if properties
.child
:
1303 "Add New SubCollection.\n"
1304 "Add a new subcollection to the currently selected collection"
1309 "Add New Collection.\n"
1310 "Add a new collection as a sibling of the currently selected collection"
1315 def execute(self
, context
):
1316 new_collection
= bpy
.data
.collections
.new("New Collection")
1317 cm
= context
.scene
.collection_manager
1319 # prevent adding collections when collections are filtered
1320 # and the selection is ambiguous
1321 if cm
.cm_list_index
== -1 and ui
.CM_UL_items
.filtering
:
1322 send_report("Cannot create new collection.\n"
1323 "No collection is selected and collections are filtered."
1325 return {'CANCELLED'}
1327 if cm
.cm_list_index
> -1 and not ui
.CM_UL_items
.visible_items
[cm
.cm_list_index
]:
1328 send_report("Cannot create new collection.\n"
1329 "The selected collection isn't visible."
1331 return {'CANCELLED'}
1334 # if there are collections
1335 if len(cm
.cm_list_collection
) > 0:
1336 if not cm
.cm_list_index
== -1:
1337 # get selected collection
1338 laycol
= internals
.layer_collections
[cm
.cm_list_collection
[cm
.cm_list_index
].name
]
1340 # add new collection
1342 laycol
["ptr"].collection
.children
.link(new_collection
)
1343 internals
.expanded
.add(laycol
["name"])
1345 # update tree view property
1346 update_property_group(context
)
1348 cm
.cm_list_index
= internals
.layer_collections
[new_collection
.name
]["row_index"]
1351 laycol
["parent"]["ptr"].collection
.children
.link(new_collection
)
1353 # update tree view property
1354 update_property_group(context
)
1356 cm
.cm_list_index
= internals
.layer_collections
[new_collection
.name
]["row_index"]
1359 context
.scene
.collection
.children
.link(new_collection
)
1361 # update tree view property
1362 update_property_group(context
)
1364 cm
.cm_list_index
= internals
.layer_collections
[new_collection
.name
]["row_index"]
1366 # if no collections add top level collection and select it
1368 context
.scene
.collection
.children
.link(new_collection
)
1370 # update tree view property
1371 update_property_group(context
)
1373 cm
.cm_list_index
= 0
1376 # set new collection to active
1377 layer_collection
= internals
.layer_collections
[new_collection
.name
]["ptr"]
1378 context
.view_layer
.active_layer_collection
= layer_collection
1380 # show the new collection when collections are filtered.
1381 ui
.CM_UL_items
.new_collections
.append(new_collection
.name
)
1387 for rto
in internals
.rto_history
.values():
1393 class CMPhantomModeOperator(Operator
):
1394 bl_label
= "Toggle Phantom Mode"
1395 bl_idname
= "view3d.toggle_phantom_mode"
1398 "Saves the state of all RTOs and only allows changes to them, on exit all RTOs are returned to their saved state.\n"
1399 "Note: modifying collections (except RTOs) externally will exit Phantom Mode and your initial state will be lost"
1402 def execute(self
, context
):
1403 cm
= context
.scene
.collection_manager
1404 view_layer
= context
.view_layer
1406 # enter Phantom Mode
1407 if not cm
.in_phantom_mode
:
1409 cm
.in_phantom_mode
= True
1411 # save current visibility state
1412 internals
.phantom_history
["view_layer"] = view_layer
.name
1414 def save_visibility_state(layer_collection
):
1415 internals
.phantom_history
["initial_state"][layer_collection
.name
] = {
1416 "exclude": layer_collection
.exclude
,
1417 "select": layer_collection
.collection
.hide_select
,
1418 "hide": layer_collection
.hide_viewport
,
1419 "disable": layer_collection
.collection
.hide_viewport
,
1420 "render": layer_collection
.collection
.hide_render
,
1421 "holdout": layer_collection
.holdout
,
1422 "indirect": layer_collection
.indirect_only
,
1425 apply_to_children(view_layer
.layer_collection
, save_visibility_state
)
1427 # save current rto history
1428 for rto
, history
, in internals
.rto_history
.items():
1429 if history
.get(view_layer
.name
, None):
1430 internals
.phantom_history
[rto
+"_history"] = deepcopy(history
[view_layer
.name
])
1434 else: # return to normal mode
1435 def restore_visibility_state(layer_collection
):
1436 phantom_laycol
= internals
.phantom_history
["initial_state"][layer_collection
.name
]
1438 layer_collection
.exclude
= phantom_laycol
["exclude"]
1439 layer_collection
.collection
.hide_select
= phantom_laycol
["select"]
1440 layer_collection
.hide_viewport
= phantom_laycol
["hide"]
1441 layer_collection
.collection
.hide_viewport
= phantom_laycol
["disable"]
1442 layer_collection
.collection
.hide_render
= phantom_laycol
["render"]
1443 layer_collection
.holdout
= phantom_laycol
["holdout"]
1444 layer_collection
.indirect_only
= phantom_laycol
["indirect"]
1446 apply_to_children(view_layer
.layer_collection
, restore_visibility_state
)
1449 # restore previous rto history
1450 for rto
, history
, in internals
.rto_history
.items():
1451 if view_layer
.name
in history
:
1452 del history
[view_layer
.name
]
1454 if internals
.phantom_history
[rto
+"_history"]:
1455 history
[view_layer
.name
] = deepcopy(internals
.phantom_history
[rto
+"_history"])
1457 internals
.phantom_history
[rto
+"_history"].clear()
1459 cm
.in_phantom_mode
= False
1465 class CMApplyPhantomModeOperator(Operator
):
1466 '''Apply changes and quit Phantom Mode'''
1467 bl_label
= "Apply Phantom Mode"
1468 bl_idname
= "view3d.apply_phantom_mode"
1470 def execute(self
, context
):
1471 cm
= context
.scene
.collection_manager
1472 cm
.in_phantom_mode
= False
1477 class CMDisableObjectsOperator(Operator
):
1478 '''Disable selected objects in viewports'''
1479 bl_label
= "Disable Selected"
1480 bl_idname
= "view3d.disable_selected_objects"
1481 bl_options
= {'REGISTER', 'UNDO'}
1483 def execute(self
, context
):
1484 for obj
in context
.selected_objects
:
1485 obj
.hide_viewport
= True
1490 class CMDisableUnSelectedObjectsOperator(Operator
):
1491 '''Disable unselected objects in viewports'''
1492 bl_label
= "Disable Unselected"
1493 bl_idname
= "view3d.disable_unselected_objects"
1494 bl_options
= {'REGISTER', 'UNDO'}
1496 def execute(self
, context
):
1497 for obj
in bpy
.data
.objects
:
1498 if obj
in context
.visible_objects
and not obj
in context
.selected_objects
:
1499 obj
.hide_viewport
= True
1504 class CMRestoreDisabledObjectsOperator(Operator
):
1505 '''Restore disabled objects in viewports'''
1506 bl_label
= "Restore Disabled Objects"
1507 bl_idname
= "view3d.restore_disabled_objects"
1508 bl_options
= {'REGISTER', 'UNDO'}
1510 def execute(self
, context
):
1511 for obj
in bpy
.data
.objects
:
1512 if obj
.name
in context
.view_layer
.objects
and obj
.hide_viewport
:
1513 obj
.hide_viewport
= False
1514 obj
.select_set(True)
1519 class CMUndoWrapper(Operator
):
1521 bl_description
= "Undo previous action"
1522 bl_idname
= "view3d.undo_wrapper"
1525 def poll(self
, context
):
1526 return bpy
.ops
.ed
.undo
.poll()
1528 def execute(self
, context
):
1529 internals
.collection_state
.clear()
1530 internals
.collection_state
.update(generate_state())
1532 update_property_group(context
)
1534 check_state(context
, cm_popup
=True)
1537 internals
.copy_buffer
["RTO"] = ""
1538 internals
.copy_buffer
["values"].clear()
1540 internals
.swap_buffer
["A"]["RTO"] = ""
1541 internals
.swap_buffer
["A"]["values"].clear()
1542 internals
.swap_buffer
["B"]["RTO"] = ""
1543 internals
.swap_buffer
["B"]["values"].clear()
1548 class CMRedoWrapper(Operator
):
1550 bl_description
= "Redo previous action"
1551 bl_idname
= "view3d.redo_wrapper"
1554 def poll(self
, context
):
1555 return bpy
.ops
.ed
.redo
.poll()
1557 def execute(self
, context
):
1559 update_property_group(context
)