Skip to content
Snippets Groups Projects
operators.py 49.6 KiB
Newer Older
  • Learn to ignore specific revisions
  •         if not view_layer in internals.rto_history["holdout"]:
                internals.rto_history["holdout"][view_layer] = {"target": "", "history": []}
    
                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'}
    
    
            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": []}
    
                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
    
            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)
    
            return {'FINISHED'}
    
    class CMRemoveEmptyCollectionsOperator(Operator):
        bl_label = "Remove Empty Collections"
        bl_idname = "view3d.remove_empty_collections"
        bl_options = {'UNDO'}
    
        without_objects: BoolProperty()
    
        @classmethod
        def description(cls, context, properties):
            if properties.without_objects:
                tooltip = (
                    "Purge All Collections Without Objects.\n"
                    "Deletes all collections that don't contain objects even if they have subcollections"
                    )
    
            else:
                tooltip = (
                    "Remove Empty Collections.\n"
                    "Delete collections that don't have any subcollections or objects"
                    )
    
        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'}
    
        child: BoolProperty()
    
        @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)
    
                cm.cm_list_index = 0
    
    
            # 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():
    
            return {'FINISHED'}
    
    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
    
            # enter Phantom Mode
    
            if not cm.in_phantom_mode:
    
                cm.in_phantom_mode = True
    
                # 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()
    
                cm.in_phantom_mode = False
    
            return {'FINISHED'}
    
    
    
    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'}
    
    
    
    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'}
    
    
    
    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'}