-
Mikhail Rachinskiy authored
Do not hide the panel when no selected objects, no good for user experience, greyout instead.
Mikhail Rachinskiy authoredDo not hide the panel when no selected objects, no good for user experience, greyout instead.
object_boolean_tools.py 46.29 KiB
# ##### 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",
"version": (0, 3, 6),
"blender": (2, 78, 0),
"location": "View3D > Toolshelf",
"description": "Bool Tools Hotkey: Ctrl Shift B",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Object/BoolTool",
"tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
"category": "Object",
}
import bpy
from bpy.app.handlers import persistent
from bpy.types import (
AddonPreferences,
Operator,
Panel,
Menu,
)
from bpy.props import (
BoolProperty,
StringProperty,
EnumProperty,
)
# ------------------- Bool Tool FUNCTIONS------------------------------
# Utils:
# Hide boolean objects
def update_BoolHide(self, context):
ao = context.scene.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 = 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.scene.objects:
if isCanvas(ob) or isBrush(ob):
if ob.name == obj:
return ob
def FindCanvas(obj):
for ob in bpy.context.scene.objects:
if isCanvas(ob):
for mod in ob.modifiers:
if ("BTool_" in mod.name):
if (obj.name in mod.name):
return ob
def isFTransf():
user_preferences = bpy.context.user_preferences
addons = user_preferences.addons
addon_prefs = addons[__name__].preferences
if addon_prefs.fast_transform:
return True
else:
return False
"""
# EXPERIMENTAL FEATURES
def isMakeVertexGroup():
user_preferences = bpy.context.user_preferences
addon_prefs = user_preferences.addons[__name__].preferences
if addon_prefs.make_vertex_groups:
return True
else:
return False
def isMakeBoundary():
user_preferences = bpy.context.user_preferences
addon_prefs = user_preferences.addons[__name__].preferences
if addon_prefs.make_boundary:
return True
else:
return False
"""
def ConvertToMesh(obj):
act = bpy.context.scene.objects.active
bpy.context.scene.objects.active = obj
bpy.ops.object.convert(target="MESH")
bpy.context.scene.objects.active = act
# Do the Union, Difference and Intersection Operations with a Brush
def Operation(context, _operation):
prefs = bpy.context.user_preferences.addons[__name__].preferences
useWire = prefs.use_wire
solver = prefs.solver
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.scene.objects:
# if isCanvas(obj):
# for mod in obj.modifiers:
# if(mod.name == "BTool_" + selObj.name):
# obj.modifiers.remove(mod)
if useWire:
selObj.draw_type = "WIRE"
else:
selObj.draw_type = "BOUNDS"
cyclesVis.camera = False
cyclesVis.diffuse = False
cyclesVis.glossy = False
cyclesVis.shadow = False
cyclesVis.transmission = False
if _operation == "SLICE":
# copies dupli_group property(empty), but group property is empty (users_group = None)
clone = context.active_object.copy()
# clone.select=True
context.scene.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
newMod.solver = solver
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.scene.objects:
# if it's the brush object
if obj.name == _thisObj_name:
cyclesVis = obj.cycles_visibility
obj.draw_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.draw_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)
# Tooble the Enable the Brush Object Propertie
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.scene.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
# Tooble the Fast Transform Propertie 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 = 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.scene.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.scene.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.scene.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
"""
# Garbage Colletor
brush.select = True
# bpy.ops.object.delete()
def GCollector(_obj):
if isCanvas(_obj):
BTRoot = False
for mod in _obj.modifiers:
if ("BTool_" in mod.name):
BTRoot = True
if mod.object is None:
_obj.modifiers.remove(mod)
if not BTRoot:
del _obj["BoolToolRoot"]
# Handle the callbacks when modifing things in the scene
@persistent
def HandleScene(scene):
if bpy.data.objects.is_updated:
for ob in bpy.data.objects:
if ob.is_updated:
GCollector(ob)
# ------------------ Bool Tool OPERATORS-----------------------------------------------------
class BTool_DrawPolyBrush(Operator):
"""Draw Polygonal Mask, can be applyied to Canvas > Brush or Directly. ESC to Exit"""
bl_idname = "btool.draw_polybrush"
bl_label = "Draw Poly Brush"
count = 0
@classmethod
def poll(cls, context):
return context.active_object is not None
def modal(self, context, event):
self.count += 1
actObj = bpy.context.active_object
if self.count == 1:
actObj.select = True
bpy.ops.gpencil.draw('INVOKE_DEFAULT', mode="DRAW_POLY")
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.scene.objects.active = obj
bpy.ops.object.select_all(action='DESELECT')
obj.select = 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.scene.objects.active = actObj
bpy.context.scene.update()
actObj.select = True
obj.select = True
# try:
bpy.context.scene.grease_pencil.clear()
bpy.ops.gpencil.data_unlink()
return {'FINISHED'}
if event.type in {'ESC'}:
bpy.ops.ed.undo() # remove o Grease Pencil
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'}
# Fast Transform
class BTool_FastTransform(Operator):
"""Enable Fast Transform"""
bl_idname = "btool.fast_transform"
bl_label = "Fast Transform"
operator = StringProperty("")
count = 0
def modal(self, context, event):
self.count += 1
actObj = bpy.context.active_object
useWire = bpy.context.user_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.draw_type = "WIRE"
else:
actObj.draw_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.draw_type = "WIRE"
return {'FINISHED'}
if event.type in {'RIGHTMOUSE', 'ESC'}:
if isBrush(actObj):
EnableThisBrush(bpy.context, "True")
actObj.draw_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 --------------------------------------------
# Boolean Union Operator
class BTool_Union(Operator):
"""This operator add a union brush to a canvas"""
bl_idname = "btool.boolean_union"
bl_label = "Brush Union"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
Operation(context, "UNION")
return {'FINISHED'}
# Boolean Intersection Operator
class BTool_Inters(Operator):
"""This operator add a intersect brush to a canvas"""
bl_idname = "btool.boolean_inters"
bl_label = "Brush Intersection"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
Operation(context, "INTERSECT")
return {'FINISHED'}
# Boolean Difference Operator
class BTool_Diff(Operator):
"""This operator add a difference brush to a canvas"""
bl_idname = "btool.boolean_diff"
bl_label = "Brush Difference"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
Operation(context, "DIFFERENCE")
return {'FINISHED'}
# Boolean Slices Operator
class BTool_Slice(Operator):
"""This operator add a intersect brush to a canvas"""
bl_idname = "btool.boolean_slice"
bl_label = "Brush Slice"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
Operation(context, "SLICE")
return {'FINISHED'}
# Auto Boolean operators (maintainer Mikhail Rachinskiy) -------------------------------
class AutoBoolean:
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 operation",
options={'SKIP_SAVE'},
)
def __init__(self):
self.solver = bpy.context.user_preferences.addons[__name__].preferences.solver
def objects_prepare(self):
for ob in bpy.context.selected_objects:
if ob.type != 'MESH':
ob.select = False
bpy.ops.object.convert(target='MESH')
def mesh_selection(self, ob, select_action):
scene = bpy.context.scene
obj = bpy.context.active_object
scene.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')
scene.objects.active = obj
def boolean_operation(self):
obj = bpy.context.active_object
obj.select = 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 = 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.solver = self.solver
md.object = ob
bpy.ops.object.modifier_apply(modifier="Auto Boolean")
if not ob_delete:
return
bpy.context.scene.objects.unlink(ob)
bpy.data.objects.remove(ob)
class Auto_Union(AutoBoolean, Operator):
"""Combine selected objects"""
bl_idname = "btool.auto_union"
bl_label = "Union"
bl_options = {'REGISTER', 'UNDO'}
mode = 'UNION'
def execute(self, context):
self.objects_prepare()
self.boolean_operation()
return {'FINISHED'}
class Auto_Difference(AutoBoolean, Operator):
"""Subtract selected objects from active object"""
bl_idname = "btool.auto_difference"
bl_label = "Difference"
bl_options = {'REGISTER', 'UNDO'}
mode = 'DIFFERENCE'
def execute(self, context):
self.objects_prepare()
self.boolean_operation()
return {'FINISHED'}
class Auto_Intersect(AutoBoolean, Operator):
"""Keep only intersecting geometry"""
bl_idname = "btool.auto_intersect"
bl_label = "Intersect"
bl_options = {'REGISTER', 'UNDO'}
mode = 'INTERSECT'
def execute(self, context):
self.objects_prepare()
self.boolean_operation()
return {'FINISHED'}
class Auto_Slice(AutoBoolean, Operator):
"""Slice active object along the selected object (can handle only two objects at a time)"""
bl_idname = "btool.auto_slice"
bl_label = "Slice"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
self.objects_prepare()
scene = context.scene
obj = context.active_object
obj.select = False
ob = context.selected_objects[0]
self.mesh_selection(obj, 'DESELECT')
self.mesh_selection(ob, 'SELECT')
obj_copy = obj.copy()
obj_copy.data = obj.data.copy()
scene.objects.link(obj_copy)
self.boolean_mod(obj, ob, 'DIFFERENCE', ob_delete=False)
scene.objects.active = obj_copy
self.boolean_mod(obj_copy, ob, 'INTERSECT')
obj_copy.select = True
return {'FINISHED'}
class Auto_Subtract(AutoBoolean, Operator):
"""Subtract selected object from active object, """ \
"""subtracted object not removed (can handle only two objects at a time)"""
bl_idname = "btool.auto_subtract"
bl_label = "Subtract"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
self.objects_prepare()
obj = context.active_object
obj.select = False
ob = context.selected_objects[0]
self.mesh_selection(obj, 'DESELECT')
self.mesh_selection(ob, 'SELECT')
self.boolean_mod(obj, ob, 'DIFFERENCE', ob_delete=False)
return {'FINISHED'}
# Utils Class ---------------------------------------------------------------
# Find the Brush Selected in Three View
class BTool_FindBrush(Operator):
"""Find the this brush"""
bl_idname = "btool.find_brush"
bl_label = ""
obj = StringProperty("")
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
for ob in bpy.context.scene.objects:
if (ob.name == self.obj):
bpy.ops.object.select_all(action='TOGGLE')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.scene.objects.active = ob
ob.select = True
return {'FINISHED'}
# Mode The Modifier in The Stack Up or Down
class BTool_MoveStack(Operator):
"""Move this Brush Up/Down in the Stack"""
bl_idname = "btool.move_stack"
bl_label = ""
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 th Three View
class BTool_EnableBrush(Operator):
"""Removes all BoolTool config assigned to it"""
bl_idname = "btool.enable_brush"
bl_label = ""
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 Disabel a Brush Directly
class BTool_EnableThisBrush(Operator):
""" Toggles this brush"""
bl_idname = "btool.enable_this_brush"
bl_label = ""
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
EnableThisBrush(context, "None")
return {'FINISHED'}
# Enable or Disabel a Brush Directly
class BTool_EnableFTransform(Operator):
"""Use Fast Transformations to improve speed"""
bl_idname = "btool.enable_ftransf"
bl_label = ""
@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):
"""Removes all BoolTool config assigned to it"""
bl_idname = "btool.remove"
bl_label = ""
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):
"""Apply all brushes of this canvas"""
bl_idname = "btool.to_mesh"
bl_label = "Apply All Canvas"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
list = bpy.context.selected_objects
ApplyAll(context, list)
return {'FINISHED'}
# Apply This Brush to the Canvas
class BTool_BrushToMesh(Operator):
"""Apply this brush to the canvas"""
bl_idname = "btool.brush_to_mesh"
bl_label = "Apply this Brush to 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 BoolTool_Menu(Menu):
bl_label = "BoolTool Operators"
bl_idname = "OBJECT_MT_BoolTool_Menu"
def draw(self, context):
layout = self.layout
layout.label("Auto Boolean:")
layout.operator(Auto_Difference.bl_idname, icon="ROTACTIVE")
layout.operator(Auto_Union.bl_idname, icon="ROTATECOLLECTION")
layout.operator(Auto_Intersect.bl_idname, icon="ROTATECENTER")
layout.operator(Auto_Slice.bl_idname, icon="ROTATECENTER")
layout.operator(Auto_Subtract.bl_idname, icon="ROTACTIVE")
layout.separator()
layout.label("Brush Boolean:")
layout.operator(BTool_Diff.bl_idname, icon="ROTACTIVE")
layout.operator(BTool_Union.bl_idname, icon="ROTATECOLLECTION")
layout.operator(BTool_Inters.bl_idname, icon="ROTATECENTER")
layout.operator(BTool_Slice.bl_idname, icon="ROTATECENTER")
layout.separator()
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="CANCEL", 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="CANCEL", text="Remove Brush")
Rem.thisObj = ""
Rem.Prop = "BRUSH"
def VIEW3D_BoolTool_Menu(self, context):
self.layout.menu(BoolTool_Menu.bl_idname)
# ---------------- Toolshelf: Tools ---------------------
class BoolTool_Tools(Panel):
bl_category = "Tools"
bl_label = "Bool Tools"
bl_idname = "BoolTool_Tools"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_context = 'objectmode'
@classmethod
def poll(cls, context):
return context.active_object is not None
def draw(self, context):
layout = self.layout
obj = context.active_object
obs_len = len(context.selected_objects)
row = layout.split(0.7)
row.label("Bool Tools:")
row.operator("help.bool_tool", text="", icon="QUESTION")
main = layout.column(align=True)
main.enabled = obj.type == 'MESH' and obs_len > 0
main.separator()
col = main.column(align=True)
col.enabled = obs_len > 1
col.label("Auto Boolean:", icon="MODIFIER")
col.separator()
col.operator(Auto_Difference.bl_idname, icon="ROTACTIVE")
col.operator(Auto_Union.bl_idname, icon="ROTATECOLLECTION")
col.operator(Auto_Intersect.bl_idname, icon="ROTATECENTER")
main.separator()
col = main.column(align=True)
col.enabled = obs_len == 2
col.operator(Auto_Slice.bl_idname, icon="ROTATECENTER")
col.operator(Auto_Subtract.bl_idname, icon="ROTACTIVE")
main.separator()
col = main.column(align=True)
col.enabled = obs_len > 1
col.label("Brush Boolean:", icon="MODIFIER")
col.separator()
col.operator(BTool_Diff.bl_idname, text="Difference", icon="ROTACTIVE")
col.operator(BTool_Union.bl_idname, text="Union", icon="ROTATECOLLECTION")
col.operator(BTool_Inters.bl_idname, text="Intersect", icon="ROTATECENTER")
col.operator(BTool_Slice.bl_idname, text="Slice", icon="ROTATECENTER")
main.separator()
col = main.column(align=True)
col.label("Draw:", icon="MESH_CUBE")
col.separator()
col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
# ---------- Toolshelf: Properties --------------------------------------------------------
class BoolTool_Config(Panel):
bl_category = "Tools"
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_category = "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):
"""Tip"""
bl_idname = "help.bool_tool"
bl_label = ""
def draw(self, context):
layout = self.layout
layout.label("To use:")
layout.label("Select two or more objects.")
layout.label("Auto Boolean:")
layout.label("Apply boolean operation directly.")
layout.label("Brush Boolean:")
layout.label("Create boolean brush set up.")
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
panels = [
BoolTool_Tools,
BoolTool_Config,
BoolTool_BViwer,
]
def update_panel(self, context):
message = "Bool Tool: Updating Panel locations has failed"
try:
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))
pass
class BoolTool_Pref(AddonPreferences):
bl_idname = __name__
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(
name="Make Boundary",
default=False,
description="When Apply a Brush to the Object it will create a\n"
"new vertex group of the bondary boolean area",
)
use_wire = BoolProperty(
name="Use Bmesh",
default=False,
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",
default="Tools",
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")),
default='BMESH',
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:")
col = split.column()
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()
"""
# EXPERIMENTAL
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:
row = layout.row()
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 ------------------------------------------------
classes = (
BoolTool_Pref,
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,
BoolTool_help
)
# ------------------- 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
km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
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_panel(None, bpy.context)
# Scene variables
bpy.types.Scene.BoolHide = BoolProperty(
default=False,
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'
# Brush Operators
kmi = km.keymap_items.new(BTool_Union.bl_idname, 'NUMPAD_PLUS', 'PRESS', ctrl=True)
kmi = km.keymap_items.new(BTool_Diff.bl_idname, 'NUMPAD_MINUS', 'PRESS', ctrl=True)
kmi = km.keymap_items.new(BTool_Inters.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', ctrl=True)
kmi = km.keymap_items.new(BTool_Slice.bl_idname, 'NUMPAD_SLASH', 'PRESS', ctrl=True)
kmi = km.keymap_items.new(BTool_BrushToMesh.bl_idname, 'NUMPAD_ENTER', 'PRESS', ctrl=True)
kmi = km.keymap_items.new(BTool_AllBrushToMesh.bl_idname, 'NUMPAD_ENTER', 'PRESS', ctrl=True, shift=True)
# Auto Operators
kmi = km.keymap_items.new(Auto_Union.bl_idname, 'NUMPAD_PLUS', 'PRESS', ctrl=True, shift=True)
kmi = km.keymap_items.new(Auto_Difference.bl_idname, 'NUMPAD_MINUS', 'PRESS', ctrl=True, shift=True)
kmi = km.keymap_items.new(Auto_Intersect.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', ctrl=True, shift=True)
kmi = km.keymap_items.new(Auto_Slice.bl_idname, 'NUMPAD_SLASH', 'PRESS', ctrl=True, shift=True)
addon_keymaps.append(km)
def unregister():
# Keymapping
# remove keymaps when add-on is deactivated
wm = bpy.context.window_manager
for km in addon_keymaps:
wm.keyconfigs.addon.keymaps.remove(km)
del addon_keymaps[:]
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()