Skip to content
Snippets Groups Projects
object_boolean_tools.py 47.1 KiB
Newer Older
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

bl_info = {
    "name": "Bool Tool",
    "author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto, Simon Appelt",
    "version": (0, 4, 0),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar",
    "description": "Bool Tool Hotkey: Ctrl Shift B",
    "category": "Object",

import bpy
from bpy.types import (
        AddonPreferences,
        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
# Object is a Canvas
def isCanvas(_obj):
    try:
        if _obj["BoolToolRoot"]:
            return True
    except:
        return False


# Object is a Brush Tool Bool
def isBrush(_obj):
    try:
        if _obj["BoolToolBrush"]:
            return True
    except:
        return False


# Object is a Poly Brush Tool Bool collection
def isPolyBrush(_obj):
    try:
        if _obj["BoolToolPolyBrush"]:
            return True
    except:
        return False


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):
                        return ob


def isFTransf():
    preferences = bpy.context.preferences
    addons = preferences.addons
    addon_prefs = addons[__name__].preferences
    if addon_prefs.fast_transform:
        return True
    else:
        return False


"""
# EXPERIMENTAL FEATURES
def isMakeVertexGroup():
    preferences = bpy.context.preferences
    addon_prefs = preferences.addons[__name__].preferences
    if addon_prefs.make_vertex_groups:
        return True
    else:
        return False

def isMakeBoundary():
    preferences = bpy.context.preferences
    addon_prefs = preferences.addons[__name__].preferences
    if addon_prefs.make_boundary:
        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 = bpy.context.preferences.addons[__name__].preferences
    useWire = prefs.use_wire

    for selObj in bpy.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
            cyclesVis = selObj.cycles_visibility
            for obj in bpy.context.view_layer.objects:
                if isCanvas(obj):
                    for mod in obj.modifiers:
                        if(mod.name == "BTool_" + selObj.name):
                            obj.modifiers.remove(mod)
            """
                selObj.display_type = "WIRE"
                selObj.display_type = "BOUNDS"

            cyclesVis.camera = False
            cyclesVis.diffuse = False
            cyclesVis.glossy = False
            cyclesVis.shadow = False
            cyclesVis.transmission = False
            if _operation == "SLICE":
                # copies instance_collection property(empty), but group property is empty (users_group = None)
                clone = context.active_object.copy()
                # clone.select_set(state=True)
                context.collection.objects.link(clone)
                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 Obejcts 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:
                cyclesVis = obj.cycles_visibility
                obj.display_type = "TEXTURED"
                del obj["BoolToolBrush"]
                del obj["BoolTool_FTransform"]
                cyclesVis.camera = True
                cyclesVis.diffuse = True
                cyclesVis.glossy = True
                cyclesVis.shadow = True
                cyclesVis.transmission = 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):
                        if (actObj.name in mod.name):
                            Canvas.modifiers.remove(mod)
            cyclesVis = actObj.cycles_visibility
            actObj.display_type = "TEXTURED"
            del actObj["BoolToolBrush"]
            del actObj["BoolTool_FTransform"]
            cyclesVis.camera = True
            cyclesVis.diffuse = True
            cyclesVis.glossy = True
            cyclesVis.shadow = True
            cyclesVis.transmission = True

        if Prop == "CANVAS":
            for mod in actObj.modifiers:
                if ("BTool_" in mod.name):
                    RemoveThis(mod.object.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:
                if mod.type == 'BOOLEAN':
                    if mod.object in objDeleteList:  # do not delete brush that is used by another canvas
                        objDeleteList.remove(mod.object)  # remove it from deletion
    bpy.ops.object.select_all(action='DESELECT')
    for obj in objDeleteList:
        obj.select_set(state=True)
    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):
                    """
                    # EXPERIMENTAL
                    if isMakeVertexGroup():
                        # Turn all faces of the Brush selected
                        bpy.context.view_layer.objects.active = brush
                        bpy.ops.object.mode_set(mode='EDIT')
                        bpy.ops.mesh.select_all(action='SELECT')
                        bpy.ops.object.mode_set(mode='OBJECT')

                        # Turn off al faces of the Canvas selected
                        bpy.context.view_layer.objects.active = canvas
                        bpy.ops.object.mode_set(mode='EDIT')
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT')
                    """

                    # Apply This Brush
                    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')

                    """
                    # EXPERIMENTAL
                    if isMakeVertexGroup():
                        # Make Vertex Group
                        bpy.ops.object.mode_set(mode='EDIT')
                        bpy.ops.object.vertex_group_assign_new()
                        bpy.ops.mesh.select_all(action='DESELECT')
                        bpy.ops.object.mode_set(mode='OBJECT')

                        canvas.vertex_groups.active.name = "BTool_" + brush.name
                    """

    brush.select_set(state=True)
    # bpy.ops.object.delete()

# ------------------ Bool Tool OPERATORS --------------------------------------
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")

    @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:
            actObj.select_set(state=True)
            bpy.ops.gpencil.draw('INVOKE_DEFAULT', mode="DRAW_POLY")

        if event.type in {'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')
            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')
                    obj.select_set(state=True)
                    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(state=True)
                    obj.select_set(state=True)
                    bpy.context.view_layer.grease_pencil.clear()
                    bpy.ops.gpencil.data_unlink()

            return {'FINISHED'}

        if event.type in {'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"
lijenstina's avatar
Loading
Loading full blame...