glTF exporter: Manage all 4 types of Vertex Colors (corner/point - byte/float)
[blender-addons.git] / object_collection_manager / operators.py
blob0b2b9cbfd834dca7d4f06bc00012adbe1c7ab2bf
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Copyright 2011, Ryan Inch
5 import bpy
7 from copy import deepcopy
9 from bpy.types import (
10 Operator,
13 from bpy.props import (
14 BoolProperty,
15 StringProperty,
16 IntProperty
19 # For VARS
20 from . import internals
22 # For FUNCTIONS
23 from .internals import (
24 update_property_group,
25 generate_state,
26 check_state,
27 get_modifiers,
28 get_move_selection,
29 get_move_active,
30 update_qcd_header,
31 send_report,
34 from .operator_utils import (
35 apply_to_children,
36 isolate_rto,
37 toggle_children,
38 activate_all_rtos,
39 invert_rtos,
40 copy_rtos,
41 swap_rtos,
42 clear_copy,
43 clear_swap,
44 link_child_collections_to_parent,
45 remove_collection,
46 select_collection_objects,
47 set_exclude_state,
48 isolate_sel_objs_collections,
49 disable_sel_objs_collections,
52 from . import ui
54 class SetActiveCollection(Operator):
55 '''Set the active collection'''
56 bl_label = "Set Active Collection"
57 bl_idname = "view3d.set_active_collection"
58 bl_options = {'UNDO'}
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
67 else:
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")
80 return {'FINISHED'}
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
92 else:
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()
101 # update tree view
102 update_property_group(context)
104 return {'FINISHED'}
107 class ExpandSublevelOperator(Operator):
108 bl_label = "Expand Sublevel Items"
109 bl_description = (
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()
118 index: IntProperty()
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
131 expand = None
133 # check whether to expand or collapse
134 if self.name in internals.expanded:
135 internals.expanded.remove(self.name)
136 expand = False
137 else:
138 internals.expanded.add(self.name)
139 expand = True
141 # do expanding/collapsing
142 def set_expanded(layer_collection):
143 if expand:
144 internals.expanded.add(layer_collection.name)
145 else:
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"])
163 if parent["parent"]:
164 isolate_tree(parent)
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()
173 else:
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
180 else:
181 # expand/collapse collection
182 if self.expand:
183 internals.expanded.add(self.name)
184 else:
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
194 #update tree view
195 update_property_group(context)
197 return {'FINISHED'}
200 class CMSelectCollectionObjectsOperator(Operator):
201 bl_label = "Select All Objects in the Collection"
202 bl_description = (
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,
221 replace=False,
222 nested=False
225 elif modifiers == {"ctrl"}:
226 select_collection_objects(
227 is_master_collection=self.is_master_collection,
228 collection_name=self.collection_name,
229 replace=True,
230 nested=True
233 elif modifiers == {"ctrl", "shift"}:
234 select_collection_objects(
235 is_master_collection=self.is_master_collection,
236 collection_name=self.collection_name,
237 replace=False,
238 nested=True
241 else:
242 select_collection_objects(
243 is_master_collection=self.is_master_collection,
244 collection_name=self.collection_name,
245 replace=True,
246 nested=False
249 return {'FINISHED'}
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():
266 obj.select_set(True)
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")
274 return {'FINISHED'}
277 class CMSendObjectsToCollectionOperator(Operator):
278 bl_label = "Send Objects to Collection"
279 bl_description = (
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
293 else:
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:
303 return {'CANCELLED'}
305 if event.shift:
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:
314 # add to collection
315 for obj in selected_objects:
316 if obj.name not in target_collection.objects:
317 target_collection.objects.link(obj)
319 else:
320 warnings = False
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:
329 warnings = True
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)
336 else:
337 master_warning = True
338 continue
341 # remove from collection
342 target_collection.objects.unlink(obj)
344 if warnings:
345 if master_warning:
346 send_report(
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."
355 else:
356 self.report({"INFO"}, "1 or more objects moved to Scene Collection.")
359 else:
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:
372 try:
373 context.view_layer.objects.active = active_object
375 except RuntimeError: # object not in visible collection
376 pass
378 # update qcd header UI
379 update_qcd_header()
381 return {'FINISHED'}
384 class CMExcludeOperator(Operator):
385 bl_label = "[EC] Exclude from View Layer"
386 bl_description = (
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()
397 # static class var
398 isolated = False
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]
414 cls.isolated = False
416 elif modifiers == {"shift"}:
417 isolate_rto(cls, self, view_layer, "exclude")
419 elif modifiers == {"ctrl"}:
420 toggle_children(self, view_layer, "exclude")
422 cls.isolated = False
424 elif modifiers == {"ctrl", "shift"}:
425 isolate_rto(cls, self, view_layer, "exclude", children=True)
427 else:
428 # toggle exclusion
430 # reset exclude history
431 del internals.rto_history["exclude"][view_layer]
433 set_exclude_state(laycol_ptr, not laycol_ptr.exclude)
435 cls.isolated = False
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]
449 return {'FINISHED'}
452 class CMUnExcludeAllOperator(Operator):
453 bl_label = "[EC Global] Exclude from View Layer"
454 bl_description = (
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"}:
476 # clear all states
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")
493 if error:
494 self.report({"WARNING"}, error)
495 return {'CANCELLED'}
497 elif modifiers == {"shift", "alt"}:
498 error = disable_sel_objs_collections(view_layer, "exclude", "CM")
500 if error:
501 self.report({"WARNING"}, error)
502 return {'CANCELLED'}
504 else:
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
515 return {'FINISHED'}
518 class CMRestrictSelectOperator(Operator):
519 bl_label = "[SS] Disable Selection"
520 bl_description = (
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()
531 # static class var
532 isolated = False
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]
546 cls.isolated = False
548 elif modifiers == {"shift"}:
549 isolate_rto(cls, self, view_layer, "select")
551 elif modifiers == {"ctrl"}:
552 toggle_children(self, view_layer, "select")
554 cls.isolated = False
556 elif modifiers == {"ctrl", "shift"}:
557 isolate_rto(cls, self, view_layer, "select", children=True)
559 else:
560 # toggle selectable
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
568 cls.isolated = False
570 # reset select all history
571 if view_layer in internals.rto_history["select_all"]:
572 del internals.rto_history["select_all"][view_layer]
574 return {'FINISHED'}
577 class CMUnRestrictSelectAllOperator(Operator):
578 bl_label = "[SS Global] Disable Selection"
579 bl_description = (
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"}:
599 # clear all states
600 del internals.rto_history["select_all"][view_layer]
601 clear_copy("select")
602 clear_swap("select")
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")
616 if error:
617 self.report({"WARNING"}, error)
618 return {'CANCELLED'}
620 elif modifiers == {"shift", "alt"}:
621 error = disable_sel_objs_collections(view_layer, "select", "CM")
623 if error:
624 self.report({"WARNING"}, error)
625 return {'CANCELLED'}
627 else:
628 activate_all_rtos(view_layer, "select")
630 return {'FINISHED'}
633 class CMHideOperator(Operator):
634 bl_label = "[VV] Hide in Viewport"
635 bl_description = (
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()
646 # static class var
647 isolated = False
649 def invoke(self, context, event):
650 cls = CMHideOperator
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]
661 cls.isolated = False
663 elif modifiers == {"shift"}:
664 isolate_rto(cls, self, view_layer, "hide")
666 elif modifiers == {"ctrl"}:
667 toggle_children(self, view_layer, "hide")
669 cls.isolated = False
671 elif modifiers == {"ctrl", "shift"}:
672 isolate_rto(cls, self, view_layer, "hide", children=True)
674 else:
675 # toggle visible
677 # reset hide history
678 del internals.rto_history["hide"][view_layer]
680 # toggle view of collection
681 laycol_ptr.hide_viewport = not laycol_ptr.hide_viewport
683 cls.isolated = False
685 # reset hide all history
686 if view_layer in internals.rto_history["hide_all"]:
687 del internals.rto_history["hide_all"][view_layer]
689 return {'FINISHED'}
692 class CMUnHideAllOperator(Operator):
693 bl_label = "[VV Global] Hide in Viewport"
694 bl_description = (
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"}:
714 # clear all states
715 del internals.rto_history["hide_all"][view_layer]
716 clear_copy("hide")
717 clear_swap("hide")
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")
731 if error:
732 self.report({"WARNING"}, error)
733 return {'CANCELLED'}
735 elif modifiers == {"shift", "alt"}:
736 error = disable_sel_objs_collections(view_layer, "hide", "CM")
738 if error:
739 self.report({"WARNING"}, error)
740 return {'CANCELLED'}
742 else:
743 activate_all_rtos(view_layer, "hide")
745 return {'FINISHED'}
748 class CMDisableViewportOperator(Operator):
749 bl_label = "[DV] Disable in Viewports"
750 bl_description = (
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()
761 # static class var
762 isolated = False
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]
776 cls.isolated = False
778 elif modifiers == {"shift"}:
779 isolate_rto(cls, self, view_layer, "disable")
781 elif modifiers == {"ctrl"}:
782 toggle_children(self, view_layer, "disable")
784 cls.isolated = False
786 elif modifiers == {"ctrl", "shift"}:
787 isolate_rto(cls, self, view_layer, "disable", children=True)
789 else:
790 # toggle disable
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
798 cls.isolated = False
800 # reset disable all history
801 if view_layer in internals.rto_history["disable_all"]:
802 del internals.rto_history["disable_all"][view_layer]
804 return {'FINISHED'}
807 class CMUnDisableViewportAllOperator(Operator):
808 bl_label = "[DV Global] Disable in Viewports"
809 bl_description = (
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"}:
829 # clear all states
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")
846 if error:
847 self.report({"WARNING"}, error)
848 return {'CANCELLED'}
850 elif modifiers == {"shift", "alt"}:
851 error = disable_sel_objs_collections(view_layer, "disable", "CM")
853 if error:
854 self.report({"WARNING"}, error)
855 return {'CANCELLED'}
857 else:
858 activate_all_rtos(view_layer, "disable")
860 return {'FINISHED'}
863 class CMDisableRenderOperator(Operator):
864 bl_label = "[RR] Disable in Renders"
865 bl_description = (
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()
876 # static class var
877 isolated = False
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]
892 cls.isolated = False
894 elif modifiers == {"shift"}:
895 isolate_rto(cls, self, view_layer, "render")
897 elif modifiers == {"ctrl"}:
898 toggle_children(self, view_layer, "render")
900 cls.isolated = False
902 elif modifiers == {"ctrl", "shift"}:
903 isolate_rto(cls, self, view_layer, "render", children=True)
905 else:
906 # toggle renderable
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
914 cls.isolated = False
916 # reset render all history
917 if view_layer in internals.rto_history["render_all"]:
918 del internals.rto_history["render_all"][view_layer]
920 return {'FINISHED'}
923 class CMUnDisableRenderAllOperator(Operator):
924 bl_label = "[RR Global] Disable in Renders"
925 bl_description = (
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"}:
945 # clear all states
946 del internals.rto_history["render_all"][view_layer]
947 clear_copy("render")
948 clear_swap("render")
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")
962 if error:
963 self.report({"WARNING"}, error)
964 return {'CANCELLED'}
966 elif modifiers == {"shift", "alt"}:
967 error = disable_sel_objs_collections(view_layer, "render", "CM")
969 if error:
970 self.report({"WARNING"}, error)
971 return {'CANCELLED'}
973 else:
974 activate_all_rtos(view_layer, "render")
976 return {'FINISHED'}
979 class CMHoldoutOperator(Operator):
980 bl_label = "[HH] Holdout"
981 bl_description = (
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()
992 # static class var
993 isolated = False
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)
1020 else:
1021 # toggle holdout
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]
1035 return {'FINISHED'}
1038 class CMUnHoldoutAllOperator(Operator):
1039 bl_label = "[HH Global] Holdout"
1040 bl_description = (
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"}:
1060 # clear all states
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")
1077 if error:
1078 self.report({"WARNING"}, error)
1079 return {'CANCELLED'}
1081 elif modifiers == {"shift", "alt"}:
1082 error = disable_sel_objs_collections(view_layer, "holdout", "CM")
1084 if error:
1085 self.report({"WARNING"}, error)
1086 return {'CANCELLED'}
1088 else:
1089 activate_all_rtos(view_layer, "holdout")
1091 return {'FINISHED'}
1094 class CMIndirectOnlyOperator(Operator):
1095 bl_label = "[IO] Indirect Only"
1096 bl_description = (
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()
1107 # static class var
1108 isolated = False
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)
1136 else:
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]
1151 return {'FINISHED'}
1154 class CMUnIndirectOnlyAllOperator(Operator):
1155 bl_label = "[IO Global] Indirect Only"
1156 bl_description = (
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"}:
1176 # clear all states
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")
1193 if error:
1194 self.report({"WARNING"}, error)
1195 return {'CANCELLED'}
1197 elif modifiers == {"shift", "alt"}:
1198 error = disable_sel_objs_collections(view_layer, "indirect", "CM")
1200 if error:
1201 self.report({"WARNING"}, error)
1202 return {'CANCELLED'}
1204 else:
1205 activate_all_rtos(view_layer, "indirect")
1207 return {'FINISHED'}
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)
1237 return {'FINISHED'}
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()
1247 @classmethod
1248 def description(cls, context, properties):
1249 if properties.without_objects:
1250 tooltip = (
1251 "Purge All Collections Without Objects.\n"
1252 "Deletes all collections that don't contain objects even if they have subcollections"
1255 else:
1256 tooltip = (
1257 "Remove Empty Collections.\n"
1258 "Delete collections that don't have any subcollections or objects"
1261 return tooltip
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]
1268 else:
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")
1288 return {'FINISHED'}
1291 rename = [False]
1292 class CMNewCollectionOperator(Operator):
1293 bl_label = "Add New Collection"
1294 bl_idname = "view3d.add_collection"
1295 bl_options = {'UNDO'}
1297 child: BoolProperty()
1299 @classmethod
1300 def description(cls, context, properties):
1301 if properties.child:
1302 tooltip = (
1303 "Add New SubCollection.\n"
1304 "Add a new subcollection to the currently selected collection"
1307 else:
1308 tooltip = (
1309 "Add New Collection.\n"
1310 "Add a new collection as a sibling of the currently selected collection"
1313 return tooltip
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
1341 if self.child:
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"]
1350 else:
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"]
1358 else:
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
1367 else:
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)
1383 global rename
1384 rename[0] = True
1386 # reset history
1387 for rto in internals.rto_history.values():
1388 rto.clear()
1390 return {'FINISHED'}
1393 class CMPhantomModeOperator(Operator):
1394 bl_label = "Toggle Phantom Mode"
1395 bl_idname = "view3d.toggle_phantom_mode"
1396 bl_description = (
1397 "Phantom Mode\n"
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
1462 return {'FINISHED'}
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
1474 return {'FINISHED'}
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
1487 return {'FINISHED'}
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
1501 return {'FINISHED'}
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.hide_viewport:
1513 obj.hide_viewport = False
1514 obj.select_set(True)
1516 return {'FINISHED'}
1519 class CMUndoWrapper(Operator):
1520 bl_label = "Undo"
1521 bl_description = "Undo previous action"
1522 bl_idname = "view3d.undo_wrapper"
1524 @classmethod
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())
1531 bpy.ops.ed.undo()
1532 update_property_group(context)
1534 check_state(context, cm_popup=True)
1536 # clear buffers
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()
1545 return {'FINISHED'}
1548 class CMRedoWrapper(Operator):
1549 bl_label = "Redo"
1550 bl_description = "Redo previous action"
1551 bl_idname = "view3d.redo_wrapper"
1553 @classmethod
1554 def poll(self, context):
1555 return bpy.ops.ed.redo.poll()
1557 def execute(self, context):
1558 bpy.ops.ed.redo()
1559 update_property_group(context)
1561 return {'FINISHED'}