Campbell Barton
e8da6131fd
Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
1301 lines
40 KiB
Python
1301 lines
40 KiB
Python
# SPDX-FileCopyrightText: 2016-2023 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
bl_info = {
|
|
"name": "Bool Tool",
|
|
"author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto, Simon Appelt",
|
|
"version": (0, 4, 1),
|
|
"blender": (2, 80, 0),
|
|
"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:
|
|
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
|
|
|
|
|
|
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
|
|
|
|
if useWire:
|
|
selObj.display_type = "WIRE"
|
|
else:
|
|
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)
|
|
clone = actObj.copy()
|
|
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:
|
|
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:
|
|
# 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:
|
|
obj.select_set(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:
|
|
# 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")
|
|
|
|
# Garbage Collector
|
|
brush.select_set(True)
|
|
# 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:
|
|
# actObj.select_set(True)
|
|
# 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")
|
|
# obj.select_set(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(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"
|
|
else:
|
|
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"}
|
|
else:
|
|
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"}
|
|
|
|
mode = "UNION"
|
|
|
|
|
|
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"}
|
|
|
|
mode = "INTERSECT"
|
|
|
|
|
|
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"}
|
|
|
|
mode = "DIFFERENCE"
|
|
|
|
|
|
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"}
|
|
|
|
mode = "SLICE"
|
|
|
|
|
|
# Auto Boolean operators
|
|
# --------------------------------------------------------------------------------------
|
|
|
|
|
|
class Auto_Boolean:
|
|
|
|
def objects_prepare(self):
|
|
for ob in bpy.context.selected_objects:
|
|
if ob.type != "MESH":
|
|
ob.select_set(False)
|
|
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
|
|
obj.select_set(False)
|
|
obs = bpy.context.selected_objects
|
|
|
|
self.mesh_selection(obj, "DESELECT")
|
|
|
|
for ob in obs:
|
|
self.mesh_selection(ob, "SELECT")
|
|
self.boolean_mod(obj, ob, self.mode)
|
|
|
|
obj.select_set(True)
|
|
|
|
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
|
|
|
|
context_override = {'object': obj}
|
|
with bpy.context.temp_override(**context_override):
|
|
bpy.ops.object.modifier_apply(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"}
|
|
|
|
mode = "UNION"
|
|
|
|
|
|
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.select_set(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 ------------------------------
|
|
|
|
# 3Dview Header Menu
|
|
class VIEW3D_MT_booltool_menu(Menu):
|
|
bl_label = "Bool Tool"
|
|
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.separator()
|
|
|
|
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
|
|
actObj = context.active_object
|
|
|
|
row = layout.row(align=True)
|
|
|
|
if isCanvas(actObj):
|
|
|
|
row.label(text="CANVAS", icon="MESH_GRID")
|
|
row = layout.row()
|
|
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")
|
|
else:
|
|
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"
|
|
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", text="", icon="X", emboss=False)
|
|
Remove.thisObj = mod.object.name
|
|
Remove.Prop = "THIS"
|
|
|
|
else:
|
|
row.label(text=mod.name)
|
|
|
|
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 ADD-ON PREFERENCES ----------------------------
|
|
|
|
|
|
shortcut_list = (
|
|
("3D View", None),
|
|
("Menu", "Ctrl Shift B"),
|
|
|
|
("Auto Operators", None),
|
|
("Difference", "Ctrl Shift Num -"),
|
|
("Union", "Ctrl Shift Num +"),
|
|
("Intersect", "Ctrl Shift Num *"),
|
|
("Slice", "Ctrl Shift Num /"),
|
|
|
|
("Brush Operators", None),
|
|
("Difference", "Ctrl Num -"),
|
|
("Union", "Ctrl Num +"),
|
|
("Intersect", "Ctrl Num *"),
|
|
("Slice", "Ctrl Num /"),
|
|
("Brush To Mesh", "Ctrl Num Enter"),
|
|
("All Brushes To Mesh", "Ctrl Shift Num Enter"),
|
|
)
|
|
|
|
|
|
def UpdateBoolTool_Pref(self, context):
|
|
if self.fast_transform:
|
|
RegisterFastT()
|
|
else:
|
|
UnRegisterFastT()
|
|
|
|
|
|
# Define Panel classes for updating
|
|
panels = (
|
|
VIEW3D_PT_booltool_tools,
|
|
VIEW3D_PT_booltool_config,
|
|
VIEW3D_PT_booltool_bviewer,
|
|
)
|
|
|
|
|
|
def update_panels(self, context):
|
|
try:
|
|
for panel in panels:
|
|
if "bl_rna" in panel.__dict__:
|
|
bpy.utils.unregister_class(panel)
|
|
|
|
for panel in panels:
|
|
panel.bl_category = context.preferences.addons[
|
|
__name__
|
|
].preferences.category
|
|
bpy.utils.register_class(panel)
|
|
|
|
except Exception as e:
|
|
message = "Bool Tool: Updating Panel locations has failed"
|
|
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
|
|
|
|
|
|
def icon_tria(prop):
|
|
if prop:
|
|
return "TRIA_DOWN"
|
|
return "TRIA_RIGHT"
|
|
|
|
|
|
class PREFS_BoolTool_Props(AddonPreferences):
|
|
bl_idname = __name__
|
|
|
|
fast_transform: BoolProperty(
|
|
name="Fast Transformations",
|
|
update=UpdateBoolTool_Pref,
|
|
description="Replace the Transform HotKeys (G,R,S)\n"
|
|
"for a custom version that can optimize the visualization of Brushes",
|
|
)
|
|
use_wire: BoolProperty(
|
|
name="Display As Wireframe",
|
|
description="Display brush as wireframe instead of bounding box",
|
|
)
|
|
category: StringProperty(
|
|
name="Tab Name",
|
|
description="Set sidebar tab name",
|
|
default="Edit",
|
|
update=update_panels,
|
|
)
|
|
show_shortcuts: BoolProperty(name="Shortcuts")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
col = layout.column()
|
|
col.prop(self, "category")
|
|
col.prop(self, "fast_transform")
|
|
col.prop(self, "use_wire")
|
|
|
|
col = layout.column()
|
|
col.scale_y = 1.2
|
|
col.use_property_split = False
|
|
col.prop(self, "show_shortcuts", icon=icon_tria(self.show_shortcuts))
|
|
|
|
if self.show_shortcuts:
|
|
|
|
col = layout.column()
|
|
|
|
for key_name, key_comb in shortcut_list:
|
|
if key_comb is None:
|
|
col.separator()
|
|
col.label(text=key_name)
|
|
else:
|
|
row = col.row(align=True)
|
|
row.scale_y = 0.7
|
|
row.box().label(text=key_name)
|
|
row.box().label(text=key_comb)
|
|
|
|
|
|
# ------------------- Class List ------------------------------------------------
|
|
|
|
classes = (
|
|
PREFS_BoolTool_Props,
|
|
VIEW3D_MT_booltool_menu,
|
|
VIEW3D_PT_booltool_tools,
|
|
VIEW3D_PT_booltool_config,
|
|
VIEW3D_PT_booltool_bviewer,
|
|
OBJECT_OT_BoolTool_Auto_Union,
|
|
OBJECT_OT_BoolTool_Auto_Difference,
|
|
OBJECT_OT_BoolTool_Auto_Intersect,
|
|
OBJECT_OT_BoolTool_Auto_Slice,
|
|
BTool_Union,
|
|
BTool_Diff,
|
|
BTool_Inters,
|
|
BTool_Slice,
|
|
# TODO Draw Poly Brush
|
|
# 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)
|
|
update_panels(None, bpy.context)
|
|
|
|
# Scene variables
|
|
bpy.types.Scene.BoolHide = BoolProperty(
|
|
default=False,
|
|
description="Hide boolean objects",
|
|
update=update_BoolHide,
|
|
)
|
|
bpy.types.VIEW3D_MT_object.append(VIEW3D_BoolTool_Menu)
|
|
|
|
wm = bpy.context.window_manager
|
|
kc = wm.keyconfigs.addon
|
|
|
|
# create the boolean menu hotkey
|
|
if kc is not None:
|
|
km = kc.keymaps.new(name="Object Mode")
|
|
|
|
kmi = km.keymap_items.new("wm.call_menu", "B", "PRESS", ctrl=True, shift=True)
|
|
kmi.properties.name = "VIEW3D_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(
|
|
OBJECT_OT_BoolTool_Auto_Union.bl_idname,
|
|
"NUMPAD_PLUS",
|
|
"PRESS",
|
|
ctrl=True,
|
|
shift=True,
|
|
)
|
|
addon_keymaps.append((km, kmi))
|
|
kmi = km.keymap_items.new(
|
|
OBJECT_OT_BoolTool_Auto_Difference.bl_idname,
|
|
"NUMPAD_MINUS",
|
|
"PRESS",
|
|
ctrl=True,
|
|
shift=True,
|
|
)
|
|
addon_keymaps.append((km, kmi))
|
|
kmi = km.keymap_items.new(
|
|
OBJECT_OT_BoolTool_Auto_Intersect.bl_idname,
|
|
"NUMPAD_ASTERIX",
|
|
"PRESS",
|
|
ctrl=True,
|
|
shift=True,
|
|
)
|
|
addon_keymaps.append((km, kmi))
|
|
kmi = km.keymap_items.new(
|
|
OBJECT_OT_BoolTool_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 is not None:
|
|
for km, kmi in addon_keymaps:
|
|
km.keymap_items.remove(kmi)
|
|
|
|
addon_keymaps.clear()
|
|
UnRegisterFastT()
|
|
bpy.types.VIEW3D_MT_object.remove(VIEW3D_BoolTool_Menu)
|
|
del bpy.types.Scene.BoolHide
|
|
|
|
for cls in classes:
|
|
bpy.utils.unregister_class(cls)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|