Skip to content
Snippets Groups Projects
object_boolean_tools.py 47.2 KiB
Newer Older
  • Learn to ignore specific revisions
  •         col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
    
    
    
    # ---------- Toolshelf: Properties --------------------------------------------------------
    
    
    class BoolTool_Config(Panel):
    
        bl_label = "Properties"
        bl_idname = "BoolTool_BConfig"
        bl_space_type = "VIEW_3D"
        bl_region_type = "TOOLS"
        bl_context = "objectmode"
    
        @classmethod
        def poll(cls, context):
    
            result = False
            actObj = bpy.context.active_object
            if (isCanvas(actObj) or isBrush(actObj) or isPolyBrush(actObj)):
                result = True
            return result
    
        def draw(self, context):
            actObj = bpy.context.active_object
            icon = ""
    
            layout = self.layout
            row = layout.row(True)
    
            # CANVAS ---------------------------------------------------
            if isCanvas(actObj):
                row.label("CANVAS", icon="MESH_GRID")
                row = layout.row()
                row.prop(context.scene, 'BoolHide', text="Hide Bool objects")
                row = layout.row(True)
                row.operator(BTool_AllBrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply All")
    
                row = layout.row(True)
                Rem = row.operator(BTool_Remove.bl_idname, icon="CANCEL", text="Remove All")
                Rem.thisObj = ""
                Rem.Prop = "CANVAS"
    
                if isBrush(actObj):
                    layout.separator()
    
            # BRUSH ------------------------------------------------------
            if isBrush(actObj):
    
                if (actObj["BoolToolBrush"] == "UNION"):
                    icon = "ROTATECOLLECTION"
                if (actObj["BoolToolBrush"] == "DIFFERENCE"):
                    icon = "ROTATECENTER"
                if (actObj["BoolToolBrush"] == "INTERSECT"):
                    icon = "ROTACTIVE"
                if (actObj["BoolToolBrush"] == "SLICE"):
                    icon = "ROTATECENTER"
    
                row = layout.row(True)
                row.label("BRUSH", icon=icon)
    
                icon = ""
                if actObj["BoolTool_FTransform"] == "True":
                    icon = "PMARKER_ACT"
                else:
                    icon = "PMARKER"
                if isFTransf():
                    pass
    
                if isFTransf():
                    row = layout.row(True)
                    row.operator(BTool_EnableFTransform.bl_idname, text="Fast Vis", icon=icon)
                    row.operator(BTool_EnableThisBrush.bl_idname, text="Enable", icon="VISIBLE_IPO_ON")
                    row = layout.row(True)
                else:
                    row.operator(BTool_EnableThisBrush.bl_idname, icon="VISIBLE_IPO_ON")
                    row = layout.row(True)
    
            if isPolyBrush(actObj):
                row = layout.row(False)
                row.label("POLY BRUSH", icon="LINE_DATA")
                mod = actObj.modifiers["BTool_PolyBrush"]
                row = layout.row(False)
                row.prop(mod, "thickness", text="Size")
                layout.separator()
    
            if isBrush(actObj):
                row = layout.row(True)
                row.operator(BTool_BrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply Brush")
                row = layout.row(True)
                Rem = row.operator(BTool_Remove.bl_idname, icon="CANCEL", text="Remove Brush")
                Rem.thisObj = ""
                Rem.Prop = "BRUSH"
    
            layout.separator()
    
    
    
    # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
    
    
    class BoolTool_BViwer(Panel):
        bl_label = "Brush Viewer"
        bl_idname = "BoolTool_BViwer"
        bl_space_type = "VIEW_3D"
        bl_region_type = "TOOLS"
    
        bl_context = "objectmode"
    
        @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
            icon = ""
    
            if isCanvas(actObj):
    
                for mod in actObj.modifiers:
                    container = self.layout.box()
                    row = container.row(True)
                    icon = ""
                    if ("BTool_" in mod.name):
                        if (mod.operation == "UNION"):
                            icon = "ROTATECOLLECTION"
                        if (mod.operation == "DIFFERENCE"):
                            icon = "ROTATECENTER"
                        if (mod.operation == "INTERSECT"):
                            icon = "ROTACTIVE"
                        if (mod.operation == "SLICE"):
                            icon = "ROTATECENTER"
    
                        objSelect = row.operator("btool.find_brush", text=mod.object.name, icon=icon, emboss=False)
                        objSelect.obj = mod.object.name
    
                        EnableIcon = "RESTRICT_VIEW_ON"
                        if (mod.show_viewport):
                            EnableIcon = "RESTRICT_VIEW_OFF"
                        Enable = row.operator(BTool_EnableBrush.bl_idname, icon=EnableIcon, emboss=False)
                        Enable.thisObj = mod.object.name
    
                        Remove = row.operator("btool.remove", icon="CANCEL", emboss=False)
                        Remove.thisObj = mod.object.name
                        Remove.Prop = "THIS"
    
                        # Stack Changer
                        Up = row.operator("btool.move_stack", icon="TRIA_UP", emboss=False)
                        Up.modif = mod.name
                        Up.direction = "UP"
    
                        Dw = row.operator("btool.move_stack", icon="TRIA_DOWN", emboss=False)
                        Dw.modif = mod.name
                        Dw.direction = "DOWN"
    
                    else:
                        row.label(mod.name)
                        # Stack Changer
                        Up = row.operator("btool.move_stack", icon="TRIA_UP", emboss=False)
                        Up.modif = mod.name
                        Up.direction = "UP"
    
                        Dw = row.operator("btool.move_stack", icon="TRIA_DOWN", emboss=False)
                        Dw.modif = mod.name
                        Dw.direction = "DOWN"
    
    # ------------------ BOOL TOOL Help ----------------------------
    
    class BoolTool_help(Operator):
        bl_idname = "help.bool_tool"
    
        bl_label = "Help"
        bl_description = "Tool Help - click to read some basic information"
    
        def draw(self, context):
            layout = self.layout
    
            layout.label("To use:")
    
            layout.label("Select two or more objects,")
            layout.label("choose one option from the panel")
            layout.label("or from the Ctrl + Shift + B menu")
    
    
            layout.label("Auto Boolean:")
    
            layout.label("Apply Boolean operation directly.")
    
    
            layout.label("Brush Boolean:")
    
            layout.label("Create a Boolean brush setup.")
    
    
        def execute(self, context):
            return {'FINISHED'}
    
        def invoke(self, context, event):
    
            return context.window_manager.invoke_popup(self, width=220)
    
    # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
    
    def UpdateBoolTool_Pref(self, context):
        if self.fast_transform:
            RegisterFastT()
        else:
            UnRegisterFastT()
    
    
    
    # Add-ons Preferences Update Panel
    
    # Define Panel classes for updating
    
            BoolTool_Tools,
            BoolTool_Config,
            BoolTool_BViwer,
    
    def update_panel(self, context):
    
        message = "Bool Tool: Updating Panel locations has failed"
    
            for panel in panels:
                if "bl_rna" in panel.__dict__:
                    bpy.utils.unregister_class(panel)
    
            for panel in panels:
                panel.bl_category = context.user_preferences.addons[__name__].preferences.category
                bpy.utils.register_class(panel)
    
        except Exception as e:
            print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
    
    class BoolTool_Pref(AddonPreferences):
    
        fast_transform = BoolProperty(
    
                name="Fast Transformations",
                default=False,
                update=UpdateBoolTool_Pref,
    
                description="Replace the Transform HotKeys (G,R,S)\n"
    
                            "for a custom version that can optimize the visualization of Brushes",
    
        make_vertex_groups = BoolProperty(
    
                name="Make Vertex Groups",
                default=False,
    
                description="When Applying a Brush to the Object it will create\n"
                            "a new vertex group for the new faces",
    
        make_boundary = BoolProperty(
    
                description="When Apply a Brush to the Object it will create a\n"
                            "new vertex group of the bondary boolean area",
    
        use_wire = BoolProperty(
    
                description="Use The Wireframe Instead of Bounding Box for visualization",
    
        category = StringProperty(
    
                name="Tab Category",
                description="Choose a name for the category of the panel",
    
                update=update_panel,
    
        solver = EnumProperty(
    
                name="Boolean Solver",
                items=(('BMESH', "BMesh", "BMesh solver is faster, but less stable "
                                          "and cannot handle coplanar geometry"),
                       ('CARVE', "Carve", "Carve solver is slower, but more stable "
                                          "and can handle simple cases of coplanar geometry")),
    
                description="Specify solver for boolean operations",
    
        Enable_Tab_01 = BoolProperty(
                default=False
                )
    
        def draw(self, context):
            layout = self.layout
    
            split_percent = 0.3
    
            split = layout.split(percentage=split_percent)
            col = split.column()
    
            col.label(text="Tab Category:")
    
            col = split.column()
            colrow = col.row()
            colrow.prop(self, "category", text="")
    
            split = layout.split(percentage=split_percent)
            col = split.column()
    
            col.label("Boolean Solver:")
    
            colrow = col.row()
    
            colrow.prop(self, "solver", expand=True)
    
            split = layout.split(percentage=split_percent)
            col = split.column()
    
            col.label("Experimental Features:")
    
            col = split.column()
            colrow = col.row(align=True)
            colrow.prop(self, "fast_transform", toggle=True)
            colrow.prop(self, "use_wire", text="Use Wire Instead Of Bbox", toggle=True)
            layout.separator()
    
            col.prop(self, "make_vertex_groups")
            col.prop(self, "make_boundary")
    
            layout.prop(self, "Enable_Tab_01", text="Hot Keys", icon="KEYINGSET")
            if self.Enable_Tab_01:
    
                col = row.column()
                col.label("Hotkey List:")
                col.label("Menu: Ctrl Shift B")
    
                row = layout.row()
                col = row.column()
                col.label("Brush Operators:")
                col.label("Union: Ctrl Num +")
                col.label("Diff: Ctrl Num -")
                col.label("Intersect: Ctrl Num *")
                col.label("Slice: Ctrl Num /")
    
                row = layout.row()
                col = row.column()
    
                col.label("Auto Operators:")
                col.label("Difference: Ctrl Shift Num -")
                col.label("Union: Ctrl Shift Num +")
                col.label("Intersect: Ctrl Shift Num *")
                col.label("Slice: Ctrl Shift Num /")
    
                col.label("BTool Brush To Mesh: Ctrl Num Enter")
                col.label("BTool All Brush To Mesh: Ctrl Shift Num Enter")
    
    
    # ------------------- Class List ------------------------------------------------
    
        BoolTool_Menu,
        BoolTool_Tools,
        BoolTool_Config,
        BoolTool_BViwer,
    
    
        Auto_Union,
        Auto_Difference,
        Auto_Intersect,
        Auto_Slice,
        Auto_Subtract,
    
    
        BTool_Union,
        BTool_Diff,
        BTool_Inters,
        BTool_Slice,
        BTool_DrawPolyBrush,
        BTool_Remove,
        BTool_AllBrushToMesh,
        BTool_BrushToMesh,
        BTool_FindBrush,
        BTool_MoveStack,
        BTool_EnableBrush,
        BTool_EnableThisBrush,
        BTool_EnableFTransform,
        BTool_FastTransform,
    
    # ------------------- REGISTER ------------------------------------------------
    
    addon_keymaps = []
    addon_keymapsFastT = []
    
    
    # Fast Transform HotKeys Register
    def RegisterFastT():
        wm = bpy.context.window_manager
        km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
    
        kmi = km.keymap_items.new(BTool_FastTransform.bl_idname, 'G', 'PRESS')
        kmi.properties.operator = "Translate"
        addon_keymapsFastT.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_FastTransform.bl_idname, 'R', 'PRESS')
        kmi.properties.operator = "Rotate"
        addon_keymapsFastT.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_FastTransform.bl_idname, 'S', 'PRESS')
        kmi.properties.operator = "Scale"
        addon_keymapsFastT.append((km, kmi))
    
    
    # Fast Transform HotKeys UnRegister
    def UnRegisterFastT():
        wm = bpy.context.window_manager
    
        kc = wm.keyconfigs.addon
        if kc:
            for km, kmi in addon_keymapsFastT:
                km.keymap_items.remove(kmi)
    
    
        addon_keymapsFastT.clear()
    
    
    def register():
        for cls in classes:
            bpy.utils.register_class(cls)
    
        bpy.types.Scene.BoolHide = BoolProperty(
    
                description="Hide boolean objects",
    
                update=update_BoolHide,
                )
        # Handlers
        bpy.app.handlers.scene_update_post.append(HandleScene)
    
        bpy.types.VIEW3D_MT_object.append(VIEW3D_BoolTool_Menu)
    
        try:
            bpy.types.VIEW3D_MT_Object.prepend(VIEW3D_BoolTool_Menu)
        except:
            pass
    
        wm = bpy.context.window_manager
    
    
        # create the boolean menu hotkey
        km = wm.keyconfigs.addon.keymaps.new(name='Object Mode')
    
        kmi = km.keymap_items.new('wm.call_menu', 'B', 'PRESS', ctrl=True, shift=True)
        kmi.properties.name = 'OBJECT_MT_BoolTool_Menu'
    
        addon_keymaps.append((km, kmi))
    
    
        # Brush Operators
        kmi = km.keymap_items.new(BTool_Union.bl_idname, 'NUMPAD_PLUS', 'PRESS', ctrl=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_Diff.bl_idname, 'NUMPAD_MINUS', 'PRESS', ctrl=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_Inters.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', ctrl=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_Slice.bl_idname, 'NUMPAD_SLASH', 'PRESS', ctrl=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_BrushToMesh.bl_idname, 'NUMPAD_ENTER', 'PRESS', ctrl=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(BTool_AllBrushToMesh.bl_idname, 'NUMPAD_ENTER', 'PRESS', ctrl=True, shift=True)
    
        addon_keymaps.append((km, kmi))
    
        # Auto Operators
        kmi = km.keymap_items.new(Auto_Union.bl_idname, 'NUMPAD_PLUS', 'PRESS', ctrl=True, shift=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(Auto_Difference.bl_idname, 'NUMPAD_MINUS', 'PRESS', ctrl=True, shift=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(Auto_Intersect.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', ctrl=True, shift=True)
    
        addon_keymaps.append((km, kmi))
    
        kmi = km.keymap_items.new(Auto_Slice.bl_idname, 'NUMPAD_SLASH', 'PRESS', ctrl=True, shift=True)
    
        addon_keymaps.append((km, kmi))
    
    
    
    def unregister():
        # Keymapping
        # remove keymaps when add-on is deactivated
        wm = bpy.context.window_manager
    
        kc = wm.keyconfigs.addon
        if kc:
            for km, kmi in addon_keymaps:
                km.keymap_items.remove(kmi)
    
        addon_keymaps.clear()
        UnRegisterFastT()
    
    
        bpy.types.VIEW3D_MT_object.remove(VIEW3D_BoolTool_Menu)
    
        try:
            bpy.types.VIEW3D_MT_Object.remove(VIEW3D_BoolTool_Menu)
        except:
            pass
    
    
        del bpy.types.Scene.BoolHide
    
        for cls in classes:
            bpy.utils.unregister_class(cls)
    
    
    if __name__ == "__main__":
        register()