Skip to content
Snippets Groups Projects
object_boolean_tools.py 40.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
    
    bl_info = {
        "name": "Bool Tool",
    
        "author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto, Simon Appelt",
    
        "blender": (2, 80, 0),
    
    meta-androcto's avatar
    meta-androcto committed
        "location": "View3D > Sidebar > Edit Tab",
    
        "description": "Bool Tool Hotkey: Ctrl Shift B",
    
        "doc_url": "{BLENDER_MANUAL_URL}/addons/object/bool_tools.html",
    
        "category": "Object",
    
    
    import bpy
    from bpy.types import (
    
        AddonPreferences,
        Operator,
        Panel,
        Menu,
    )
    
    from bpy.props import (
    
        BoolProperty,
        StringProperty,
    )
    
    # -------------------  Bool Tool FUNCTIONS -------------------------
    
    # Utils:
    
    # Hide boolean objects
    def update_BoolHide(self, context):
    
        ao = context.view_layer.objects.active
    
        objs = [i.object for i in ao.modifiers if i.type == "BOOLEAN"]
    
        hide_state = context.scene.BoolHide
    
        for o in objs:
    
            o.hide_viewport = hide_state
    
    
    
    def isCanvas(_obj):
        try:
            if _obj["BoolToolRoot"]:
                return True
        except:
            return False
    
    
    def isBrush(_obj):
        try:
            if _obj["BoolToolBrush"]:
                return True
        except:
            return False
    
    
    
    # TODO
    # def isPolyBrush(_obj):
    #     try:
    #         if _obj["BoolToolPolyBrush"]:
    #             return True
    #     except:
    #         return False
    
    def object_visibility_set(ob, value=False):
        ob.visible_camera = value
        ob.visible_diffuse = value
        ob.visible_glossy = value
        ob.visible_shadow = value
        ob.visible_transmission = value
        ob.visible_volume_scatter = value
    
    def BT_ObjectByName(obj):
    
        for ob in bpy.context.view_layer.objects:
    
            if isCanvas(ob) or isBrush(ob):
                if ob.name == obj:
                    return ob
    
    
    def FindCanvas(obj):
    
        for ob in bpy.context.view_layer.objects:
    
            if isCanvas(ob):
                for mod in ob.modifiers:
    
                    if "BTool_" in mod.name:
                        if obj.name in mod.name:
    
        preferences = bpy.context.preferences
        addons = preferences.addons
    
        addon_prefs = addons[__name__].preferences
        if addon_prefs.fast_transform:
            return True
        else:
            return False
    
    
    def ConvertToMesh(obj):
    
        act = bpy.context.view_layer.objects.active
        bpy.context.view_layer.objects.active = obj
    
        bpy.ops.object.convert(target="MESH")
    
        bpy.context.view_layer.objects.active = act
    
    
    
    # Do the Union, Difference and Intersection Operations with a Brush
    def Operation(context, _operation):
    
        prefs = context.preferences.addons[__name__].preferences
    
        useWire = prefs.use_wire
    
        for selObj in context.selected_objects:
    
            if (
                selObj != context.active_object and
                (selObj.type == "MESH" or selObj.type == "CURVE")
            ):
    
                if selObj.type == "CURVE":
                    ConvertToMesh(selObj)
                actObj = context.active_object
                selObj.hide_render = True
    
                    selObj.display_type = "WIRE"
    
                    selObj.display_type = "BOUNDS"
    
                object_visibility_set(selObj, value=False)
    
                if _operation == "SLICE":
    
                    # copies instance_collection property(empty), but group property is empty (users_group = None)
    
                    context.collection.objects.link(clone)
    
    
                    space_data = context.space_data
                    is_local_view = bool(space_data.local_view)
    
                    if is_local_view:
                        clone.local_view_set(space_data, True)
    
    
                    sliceMod = clone.modifiers.new("BTool_" + selObj.name, "BOOLEAN")  # add mod to clone obj
                    sliceMod.object = selObj
                    sliceMod.operation = "DIFFERENCE"
                    clone["BoolToolRoot"] = True
    
                newMod = actObj.modifiers.new("BTool_" + selObj.name, "BOOLEAN")
                newMod.object = selObj
    
                if _operation == "SLICE":
                    newMod.operation = "INTERSECT"
                else:
                    newMod.operation = _operation
    
                actObj["BoolToolRoot"] = True
                selObj["BoolToolBrush"] = _operation
                selObj["BoolTool_FTransform"] = "False"
    
    
    
    # Remove Objects form the BoolTool System
    
    def Remove(context, thisObj_name, Prop):
        # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
        actObj = context.active_object
    
        # Restore the Brush
        def RemoveThis(_thisObj_name):
    
            for obj in bpy.context.view_layer.objects:
    
                # if it's the brush object
                if obj.name == _thisObj_name:
    
                    obj.display_type = "TEXTURED"
    
                    del obj["BoolToolBrush"]
                    del obj["BoolTool_FTransform"]
    
                    object_visibility_set(obj, value=True)
    
    
                    # Remove it from the Canvas
                    for mod in actObj.modifiers:
    
                        if "BTool_" in mod.name:
                            if _thisObj_name in mod.name:
    
                                actObj.modifiers.remove(mod)
    
        if Prop == "THIS":
            RemoveThis(thisObj_name)
    
        # If the remove was called from the Properties:
        else:
            # Remove the Brush Property
            if Prop == "BRUSH":
                Canvas = FindCanvas(actObj)
    
                if Canvas:
                    for mod in Canvas.modifiers:
    
                        if "BTool_" in mod.name and actObj.name in mod.name:
                            Canvas.modifiers.remove(mod)
    
    
                actObj.display_type = "TEXTURED"
    
                del actObj["BoolToolBrush"]
                del actObj["BoolTool_FTransform"]
    
                object_visibility_set(actObj, value=True)
    
    
            if Prop == "CANVAS":
                for mod in actObj.modifiers:
    
                    if "BTool_" in mod.name:
    
    # Toggle the Enable the Brush Object Property
    
    def EnableBrush(context, objList, canvas):
        for obj in objList:
            for mod in canvas.modifiers:
    
                if "BTool_" in mod.name and mod.object.name == obj:
    
                    if mod.show_viewport:
    
                        mod.show_viewport = False
                        mod.show_render = False
                    else:
                        mod.show_viewport = True
                        mod.show_render = True
    
    
    # Find the Canvas and Enable this Brush
    def EnableThisBrush(context, set):
        canvas = None
    
        for obj in bpy.context.view_layer.objects:
    
            if obj != bpy.context.active_object:
                if isCanvas(obj):
                    for mod in obj.modifiers:
    
                        if "BTool_" in mod.name:
    
                            if mod.object == bpy.context.active_object:
                                canvas = obj
    
        for mod in canvas.modifiers:
    
            if "BTool_" in mod.name:
    
                if mod.object == bpy.context.active_object:
                    if set == "None":
    
                        if mod.show_viewport:
    
                            mod.show_viewport = False
                            mod.show_render = False
                        else:
                            mod.show_viewport = True
                            mod.show_render = True
                    else:
    
                        if set == "True":
    
                            mod.show_viewport = True
                        else:
                            mod.show_viewport = False
                    return
    
    
    
    # Toggle the Fast Transform Property of the Active Brush
    
    def EnableFTransf(context):
        actObj = bpy.context.active_object
    
        if actObj["BoolTool_FTransform"] == "True":
            actObj["BoolTool_FTransform"] = "False"
        else:
            actObj["BoolTool_FTransform"] = "True"
        return
    
    
    # Apply All Brushes to the Canvas
    def ApplyAll(context, list):
        objDeleteList = []
        for selObj in list:
            if isCanvas(selObj) and selObj == context.active_object:
                for mod in selObj.modifiers:
    
                    if "BTool_" in mod.name:
    
                        objDeleteList.append(mod.object)
                    try:
                        bpy.ops.object.modifier_apply(modifier=mod.name)
                    except:  # if fails the means it is multiuser data
                        context.active_object.data = context.active_object.data.copy()  # so just make data unique
                        bpy.ops.object.modifier_apply(modifier=mod.name)
    
                del selObj["BoolToolRoot"]
    
    
        for obj in context.scene.objects:
            if isCanvas(obj):
                for mod in obj.modifiers:
    
                    # do not delete brush that is used by another canvas
                    if mod.type == "BOOLEAN" and mod.object in objDeleteList:
                        objDeleteList.remove(mod.object)  # remove it from deletion
    
        bpy.ops.object.select_all(action="DESELECT")
    
        for obj in objDeleteList:
    
        bpy.ops.object.delete()
    
    
    
    # Apply This Brush to the Canvas
    def ApplyThisBrush(context, brush):
        for obj in context.scene.objects:
            if isCanvas(obj):
                for mod in obj.modifiers:
    
                    if "BTool_" + brush.name in mod.name:
    
                        context.view_layer.objects.active = obj
    
                        try:
                            bpy.ops.object.modifier_apply(modifier=mod.name)
                        except:  # if fails the means it is multiuser data
                            context.active_object.data = context.active_object.data.copy()  # so just make data unique
                            bpy.ops.object.modifier_apply(modifier=mod.name)
    
                        bpy.ops.object.select_all(action="TOGGLE")
                        bpy.ops.object.select_all(action="DESELECT")
    
        # bpy.ops.object.delete()
    
    
    # ------------------ Bool Tool OPERATORS --------------------------------------
    
    # TODO
    # class BTool_DrawPolyBrush(Operator):
    #     bl_idname = "btool.draw_polybrush"
    #     bl_label = "Draw Poly Brush"
    #     bl_description = (
    #         "Draw Polygonal Mask, can be applied to Canvas > Brush or Directly\n"
    #         "Note: ESC to Cancel, Enter to Apply, Right Click to erase the Lines"
    #     )
    
    #     count = 0
    #     store_cont_draw = False
    
    #     @classmethod
    #     def poll(cls, context):
    #         return context.active_object is not None
    
    #     def set_cont_draw(self, context, start=False):
    #         # store / restore GP continuous drawing (see T52321)
    #         scene = context.scene
    #         tool_settings = scene.tool_settings
    #         continuous = tool_settings.use_gpencil_continuous_drawing
    #         if start:
    #             self.store_cont_draw = continuous
    #             tool_settings.use_gpencil_continuous_drawing = True
    #         else:
    #             tool_settings.use_gpencil_continuous_drawing = self.store_cont_draw
    
    #     def modal(self, context, event):
    #         self.count += 1
    #         actObj = bpy.context.active_object
    #         if self.count == 1:
    
    #             bpy.ops.gpencil.draw("INVOKE_DEFAULT", mode="DRAW_POLY")
    
    #         if event.type == "RIGHTMOUSE":
    #             # use this to pass to the Grease Pencil eraser (see T52321)
    #             pass
    
    #         if event.type in {"RET", "NUMPAD_ENTER"}:
    
    #             bpy.ops.gpencil.convert(type="POLY")
    #             self.set_cont_draw(context)
    
    #             for obj in context.selected_objects:
    #                 if obj.type == "CURVE":
    #                     obj.name = "PolyDraw"
    #                     bpy.context.view_layer.objects.active = obj
    #                     bpy.ops.object.select_all(action="DESELECT")
    
    #                     bpy.ops.object.convert(target="MESH")
    #                     bpy.ops.object.mode_set(mode="EDIT")
    #                     bpy.ops.mesh.select_all(action="SELECT")
    #                     bpy.ops.mesh.edge_face_add()
    #                     bpy.ops.mesh.flip_normals()
    #                     bpy.ops.object.mode_set(mode="OBJECT")
    #                     bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
    #                     bpy.ops.object.modifier_add(type="SOLIDIFY")
    #                     for mod in obj.modifiers:
    #                         if mod.name == "Solidify":
    #                             mod.name = "BTool_PolyBrush"
    #                             mod.thickness = 1
    #                             mod.offset = 0
    #                     obj["BoolToolPolyBrush"] = True
    
    #                     bpy.ops.object.select_all(action="DESELECT")
    #                     bpy.context.view_layer.objects.active = actObj
    #                     bpy.context.view_layer.update()
    
    #                     actObj.select_set(True)
    #                     obj.select_set(True)
    
    
    #                     bpy.context.view_layer.grease_pencil.clear()
    #                     bpy.ops.gpencil.data_unlink()
    
    #             return {"FINISHED"}
    
    #         if event.type == "ESC":
    #             bpy.ops.ed.undo()  # remove o Grease Pencil
    #             self.set_cont_draw(context)
    
    #             self.report({"INFO"}, "Draw Poly Brush: Operation Cancelled by User")
    #             return {"CANCELLED"}
    
    #         return {"RUNNING_MODAL"}
    
    #     def invoke(self, context, event):
    #         if context.object:
    #             self.set_cont_draw(context, start=True)
    #             context.window_manager.modal_handler_add(self)
    #             return {"RUNNING_MODAL"}
    #         else:
    #             self.report({"WARNING"}, "No active object, could not finish")
    #             return {"CANCELLED"}
    
    
    
    # Fast Transform
    class BTool_FastTransform(Operator):
        bl_idname = "btool.fast_transform"
        bl_label = "Fast Transform"
    
        bl_description = "Enable Fast Transform"
    
        operator: StringProperty("")
    
    
        count = 0
    
        def modal(self, context, event):
            self.count += 1
            actObj = bpy.context.active_object
    
            useWire = bpy.context.preferences.addons[__name__].preferences.use_wire
    
            if self.count == 1:
    
                if isBrush(actObj) and actObj["BoolTool_FTransform"] == "True":
                    EnableThisBrush(bpy.context, "False")
                    if useWire:
    
                        actObj.display_type = "WIRE"
    
                        actObj.display_type = "BOUNDS"
    
    
                if self.operator == "Translate":
    
                    bpy.ops.transform.translate("INVOKE_DEFAULT")
    
                if self.operator == "Rotate":
    
                    bpy.ops.transform.rotate("INVOKE_DEFAULT")
    
                if self.operator == "Scale":
    
                    bpy.ops.transform.resize("INVOKE_DEFAULT")
    
            if event.type == "LEFTMOUSE":
    
                if isBrush(actObj):
                    EnableThisBrush(bpy.context, "True")
    
                    actObj.display_type = "WIRE"
    
                return {"FINISHED"}
    
            if event.type in {"RIGHTMOUSE", "ESC"}:
    
                if isBrush(actObj):
                    EnableThisBrush(bpy.context, "True")
    
                    actObj.display_type = "WIRE"
    
                return {"CANCELLED"}
    
            return {"RUNNING_MODAL"}
    
    
        def invoke(self, context, event):
            if context.object:
                context.window_manager.modal_handler_add(self)
    
                return {"RUNNING_MODAL"}
    
                self.report({"WARNING"}, "No active object, could not finish")
                return {"CANCELLED"}
    
    
    
    # -------------------  Bool Tool OPERATOR CLASSES --------------------------------------------------------
    
    
    
    # Brush operators
    # --------------------------------------------------------------------------------------
    
    
    class BToolSetup():
    
        def execute(self, context):
            Operation(context, self.mode)
            return {"FINISHED"}
    
        def invoke(self, context, event):
            if len(context.selected_objects) < 2:
                self.report({"ERROR"}, "At least two objects must be selected")
                return {"CANCELLED"}
    
            return self.execute(context)
    
    
    class BTool_Union(Operator, BToolSetup):
    
        bl_idname = "btool.boolean_union"
        bl_label = "Brush Union"
    
        bl_description = "This operator add a union brush to a canvas"
    
        bl_options = {"REGISTER", "UNDO"}
    
    class BTool_Inters(Operator, BToolSetup):
    
        bl_idname = "btool.boolean_inters"
        bl_label = "Brush Intersection"
    
        bl_description = "This operator add a intersect brush to a canvas"
    
        bl_options = {"REGISTER", "UNDO"}
    
    class BTool_Diff(Operator, BToolSetup):
    
        bl_idname = "btool.boolean_diff"
        bl_label = "Brush Difference"
    
        bl_description = "This operator add a difference brush to a canvas"
    
        bl_options = {"REGISTER", "UNDO"}
    
    class BTool_Slice(Operator, BToolSetup):
    
        bl_idname = "btool.boolean_slice"
        bl_label = "Brush Slice"
    
        bl_description = "This operator add a intersect brush to a canvas"
    
        bl_options = {"REGISTER", "UNDO"}
    
    # Auto Boolean operators
    
    # --------------------------------------------------------------------------------------
    
        def objects_prepare(self):
    
            for ob in bpy.context.selected_objects:
    
                if ob.type != "MESH":
    
            bpy.ops.object.make_single_user(object=True, obdata=True)
    
            bpy.ops.object.convert(target="MESH")
    
    
        def mesh_selection(self, ob, select_action):
    
            obj = bpy.context.active_object
    
            bpy.context.view_layer.objects.active = ob
    
            bpy.ops.object.mode_set(mode="EDIT")
    
            bpy.ops.mesh.reveal()
    
            bpy.ops.mesh.select_all(action=select_action)
    
            bpy.ops.object.mode_set(mode="OBJECT")
    
            bpy.context.view_layer.objects.active = obj
    
        def boolean_operation(self):
    
            obj = bpy.context.active_object
    
            obs = bpy.context.selected_objects
    
            self.mesh_selection(obj, "DESELECT")
    
                self.mesh_selection(ob, "SELECT")
    
                self.boolean_mod(obj, ob, self.mode)
    
        def boolean_mod(self, obj, ob, mode, ob_delete=True):
    
            md = obj.modifiers.new("Auto Boolean", "BOOLEAN")
    
            md.show_viewport = False
            md.operation = mode
            md.object = ob
    
    
            override = {"object": obj}
            bpy.ops.object.modifier_apply(override, modifier=md.name)
    
            if ob_delete:
                bpy.data.objects.remove(ob)
    
        def execute(self, context):
            self.objects_prepare()
            self.boolean_operation()
            return {"FINISHED"}
    
        def invoke(self, context, event):
            if len(context.selected_objects) < 2:
                self.report({"ERROR"}, "At least two objects must be selected")
                return {"CANCELLED"}
    
            return self.execute(context)
    
    class OBJECT_OT_BoolTool_Auto_Union(Operator, Auto_Boolean):
        bl_idname = "object.booltool_auto_union"
        bl_label = "Bool Tool Union"
    
        bl_description = "Combine selected objects"
    
        bl_options = {"REGISTER", "UNDO"}
    
    class OBJECT_OT_BoolTool_Auto_Difference(Operator, Auto_Boolean):
        bl_idname = "object.booltool_auto_difference"
        bl_label = "Bool Tool Difference"
    
        bl_description = "Subtract selected objects from active object"
    
        bl_options = {"REGISTER", "UNDO"}
    
        mode = "DIFFERENCE"
    
    class OBJECT_OT_BoolTool_Auto_Intersect(Operator, Auto_Boolean):
        bl_idname = "object.booltool_auto_intersect"
        bl_label = "Bool Tool Intersect"
    
        bl_description = "Keep only intersecting geometry"
    
        bl_options = {"REGISTER", "UNDO"}
    
        mode = "INTERSECT"
    
    class OBJECT_OT_BoolTool_Auto_Slice(Operator, Auto_Boolean):
        bl_idname = "object.booltool_auto_slice"
        bl_label = "Bool Tool Slice"
    
        bl_description = "Slice active object along the selected objects"
    
        bl_options = {"REGISTER", "UNDO"}
    
    
        def execute(self, context):
    
            space_data = context.space_data
            is_local_view = bool(space_data.local_view)
    
            self.objects_prepare()
    
    
            ob1 = context.active_object
            ob1.select_set(False)
            self.mesh_selection(ob1, "DESELECT")
    
            for ob2 in context.selected_objects:
    
                self.mesh_selection(ob2, "SELECT")
    
                ob1_copy = ob1.copy()
                ob1_copy.data = ob1.data.copy()
    
                for coll in ob1.users_collection:
                    coll.objects.link(ob1_copy)
    
                if is_local_view:
                    ob1_copy.local_view_set(space_data, True)
    
                self.boolean_mod(ob1, ob2, "DIFFERENCE", ob_delete=False)
                self.boolean_mod(ob1_copy, ob2, "INTERSECT")
                ob1_copy.select_set(True)
    
            context.view_layer.objects.active = ob1_copy
    
            return {"FINISHED"}
    
    
    
    # Utils Class ---------------------------------------------------------------
    
    # Find the Brush Selected in Three View
    class BTool_FindBrush(Operator):
        bl_idname = "btool.find_brush"
        bl_label = ""
    
        bl_description = "Find the selected brush"
    
        obj: StringProperty("")
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
    
            for ob in bpy.context.view_layer.objects:
    
                if ob.name == self.obj:
                    bpy.ops.object.select_all(action="TOGGLE")
                    bpy.ops.object.select_all(action="DESELECT")
    
                    bpy.context.view_layer.objects.active = ob
                    ob.set_select(state=True)
    
            return {"FINISHED"}
    
    # Move The Modifier in The Stack Up or Down
    
    class BTool_MoveStack(Operator):
        bl_idname = "btool.move_stack"
        bl_label = ""
    
        bl_description = "Move this Brush Up/Down in the Stack"
    
    
        modif: StringProperty("")
        direction: StringProperty("")
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
    
            if self.direction == "UP":
    
                bpy.ops.object.modifier_move_up(modifier=self.modif)
    
            if self.direction == "DOWN":
    
                bpy.ops.object.modifier_move_down(modifier=self.modif)
    
            return {"FINISHED"}
    
    # Enable or Disable a Brush in the Three View
    
    class BTool_EnableBrush(Operator):
        bl_idname = "btool.enable_brush"
        bl_label = ""
    
        bl_description = "Removes all BoolTool config assigned to it"
    
        thisObj: StringProperty("")
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
            # in this case is just one object but the function accept more than one at once
            EnableBrush(context, [self.thisObj], context.active_object)
    
            return {"FINISHED"}
    
    # Enable or Disable a Brush Directly
    
    class BTool_EnableThisBrush(Operator):
        bl_idname = "btool.enable_this_brush"
        bl_label = ""
    
        bl_description = "Toggles this brush"
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
            EnableThisBrush(context, "None")
    
            return {"FINISHED"}
    
    # Enable or Disable a Brush Directly
    
    class BTool_EnableFTransform(Operator):
        bl_idname = "btool.enable_ftransf"
        bl_label = ""
    
        bl_description = "Use Fast Transformations to improve speed"
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
            EnableFTransf(context)
    
            return {"FINISHED"}
    
    
    
    # Other Operations -------------------------------------------------------
    
    # Remove a Brush or a Canvas
    class BTool_Remove(Operator):
        bl_idname = "btool.remove"
    
        bl_label = "Bool Tool Remove"
    
        bl_description = "Removes all BoolTool config assigned to it"
    
        bl_options = {"UNDO"}
    
        thisObj: StringProperty("")
        Prop: StringProperty("")
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
            Remove(context, self.thisObj, self.Prop)
    
            return {"FINISHED"}
    
    
    
    # Apply All to Canvas
    class BTool_AllBrushToMesh(Operator):
        bl_idname = "btool.to_mesh"
        bl_label = "Apply All Canvas"
    
        bl_description = "Apply all brushes of this canvas"
    
        bl_options = {"UNDO"}
    
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
    
            lists = bpy.context.selected_objects
            ApplyAll(context, lists)
    
            return {"FINISHED"}
    
    
    
    # Apply This Brush to the Canvas
    class BTool_BrushToMesh(Operator):
        bl_idname = "btool.brush_to_mesh"
        bl_label = "Apply this Brush to Canvas"
    
        bl_description = "Apply this brush to the canvas"
    
        bl_options = {"UNDO"}
    
    
        @classmethod
        def poll(cls, context):
    
            if isBrush(context.active_object):
                return True
            else:
                return False
    
        def execute(self, context):
            ApplyThisBrush(context, bpy.context.active_object)
    
            return {"FINISHED"}
    
    
    
    # TODO
    # Apply This Brush To Mesh
    
    
    # ------------------- MENU CLASSES ------------------------------
    
    class VIEW3D_MT_booltool_menu(Menu):
    
        bl_idname = "VIEW3D_MT_booltool_menu"
    
    
        def draw(self, context):
            layout = self.layout
    
    
            layout.label(text="Auto Boolean")
    
            layout.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
            layout.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
            layout.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
            layout.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
    
            layout.label(text="Brush Boolean")
    
            layout.operator(BTool_Diff.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
            layout.operator(BTool_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
            layout.operator(BTool_Inters.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
            layout.operator(BTool_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
    
            if isCanvas(context.active_object):
    
                layout.separator()
                layout.operator(BTool_AllBrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply All")
    
                Rem = layout.operator(BTool_Remove.bl_idname, icon="X", text="Remove All")
    
                Rem.thisObj = ""
                Rem.Prop = "CANVAS"
    
    
            if isBrush(context.active_object):
    
                layout.separator()
                layout.operator(BTool_BrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply Brush")
    
                Rem = layout.operator(BTool_Remove.bl_idname, icon="X", text="Remove Brush")
    
                Rem.thisObj = ""
                Rem.Prop = "BRUSH"
    
    
    def VIEW3D_BoolTool_Menu(self, context):
    
        self.layout.menu(VIEW3D_MT_booltool_menu.bl_idname)
    
    # ---------------- Toolshelf: Tools ---------------------
    
    class VIEW3D_PT_booltool_tools(Panel):
    
        bl_category = "objectmode"
    
        bl_label = "Bool Tool"
    
        bl_space_type = "VIEW_3D"
        bl_region_type = "UI"
        bl_context = "objectmode"
    
        bl_options = {'DEFAULT_CLOSED'}
    
    
        @classmethod
        def poll(cls, context):
    
            return context.active_object is not None
    
        def draw(self, context):
            layout = self.layout
    
            col = layout.column(align=True)
    
            col.label(text="Auto Boolean")
            col.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
            col.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
            col.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
    
            col.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
    
            col = layout.column(align=True)
    
            col.label(text="Brush Boolean")
            col.operator(BTool_Diff.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
            col.operator(BTool_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
            col.operator(BTool_Inters.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
            col.operator(BTool_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
    
            # TODO Draw Poly Brush
    
            # main.separator()
    
            # col = main.column(align=True)
            # col.label(text="Draw:", icon="MESH_CUBE")
            # col.separator()
            # col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
    
    # ---------- Toolshelf: Properties --------------------------------------------------------
    
    
    class VIEW3D_PT_booltool_config(Panel):
    
        bl_category = "objectmode"
    
        bl_label = "Properties"
    
        bl_space_type = "VIEW_3D"
        bl_region_type = "UI"
        bl_context = "objectmode"
    
        bl_parent_id = "VIEW3D_PT_booltool_tools"
    
    
        @classmethod
        def poll(cls, context):
    
            actObj = context.active_object
    
            return isCanvas(actObj) or isBrush(actObj)  # or isPolyBrush(actObj)
    
    
        def draw(self, context):
            layout = self.layout
    
            row = layout.row(align=True)
    
                row.label(text="CANVAS", icon="MESH_GRID")
    
                row.prop(context.scene, "BoolHide", text="Hide Bool objects")
    
                row = layout.row(align=True)
    
                row.operator(BTool_AllBrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply All")
    
    
                row = layout.row(align=True)
    
                Rem = row.operator(BTool_Remove.bl_idname, icon="X", text="Remove All")
    
                Rem.thisObj = ""
                Rem.Prop = "CANVAS"
    
                if isBrush(actObj):
                    layout.separator()
    
            if isBrush(actObj):
    
    
                if actObj["BoolToolBrush"] == "DIFFERENCE":
    
                    icon = "SELECT_SUBTRACT"
                elif actObj["BoolToolBrush"] == "UNION":
                    icon = "SELECT_EXTEND"
                elif actObj["BoolToolBrush"] == "INTERSECT":
                    icon = "SELECT_INTERSECT"
                elif actObj["BoolToolBrush"] == "SLICE":
                    icon = "SELECT_DIFFERENCE"
    
                row.label(text="BRUSH", icon=icon)
    
    
                if actObj["BoolTool_FTransform"] == "True":
                    icon = "PMARKER_ACT"
                else:
                    icon = "PMARKER"
                if isFTransf():
                    pass
    
                if isFTransf():
    
                    row = layout.row(align=True)
    
                    row.operator(BTool_EnableFTransform.bl_idname, text="Fast Vis", icon=icon)
    
                    row.operator(BTool_EnableThisBrush.bl_idname, text="Enable", icon="HIDE_OFF")
    
                    row.operator(BTool_EnableThisBrush.bl_idname, icon="HIDE_OFF")
    
                layout.operator(BTool_BrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply Brush")
                Rem = layout.operator(BTool_Remove.bl_idname, icon="X", text="Remove Brush")
    
                Rem.thisObj = ""
                Rem.Prop = "BRUSH"
    
    
            # TODO
            # if isPolyBrush(actObj):
            #     layout.label(text="POLY BRUSH", icon="LINE_DATA")
            #     mod = actObj.modifiers["BTool_PolyBrush"]
            #     layout.prop(mod, "thickness", text="Size")
    
    # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
    
    
    class VIEW3D_PT_booltool_bviewer(Panel):
    
        bl_category = "objectmode"
    
        bl_label = "Brush Viewer"
    
        bl_space_type = "VIEW_3D"
        bl_region_type = "UI"
        bl_context = "objectmode"
    
        bl_parent_id = "VIEW3D_PT_booltool_tools"
    
    
        @classmethod
        def poll(cls, context):
            actObj = bpy.context.active_object
    
            if isCanvas(actObj):
                return True
            else:
                return False
    
        def draw(self, context):
    
            actObj = bpy.context.active_object
    
            if isCanvas(actObj):
    
                for mod in actObj.modifiers:
                    container = self.layout.box()
    
                    row = container.row(align=True)
    
                    if "BTool_" in mod.name:
    
                        if mod.operation == "DIFFERENCE":
    
                            icon = "SELECT_SUBTRACT"
                        elif mod.operation == "UNION":
                            icon = "SELECT_EXTEND"
                        elif mod.operation == "INTERSECT":
                            icon = "SELECT_INTERSECT"
                        elif mod.operation == "SLICE":
                            icon = "SELECT_DIFFERENCE"
    
    
                        objSelect = row.operator("btool.find_brush", text=mod.object.name, icon=icon, emboss=False)
                        objSelect.obj = mod.object.name
    
                        EnableIcon = "RESTRICT_VIEW_ON"