-
lijenstina authored
Bumped version to 0.3.8 Allow passing of the Right Click to bpy.ops.gpencil.draw as it is used for the eraser functionality Add a message about user abort Temporary switch on the continuous drawing for GP as if it is False will stop as right click is pressed Correct the tooltip, so it is more clear
lijenstina authoredBumped version to 0.3.8 Allow passing of the Right Click to bpy.ops.gpencil.draw as it is used for the eraser functionality Add a message about user abort Temporary switch on the continuous drawing for GP as if it is False will stop as right click is pressed Correct the tooltip, so it is more clear
object_boolean_tools.py 48.06 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, 8),
"blender": (2, 78, 0),
"location": "View3D > Toolshelf",
"description": "Bool Tools Hotkey: Ctrl Shift B",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Object/BoolTool",
"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)
# 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.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
# 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 = 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 Collector
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):
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 = 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')
self.set_cont_draw(context)
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
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
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.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):
bl_idname = "btool.boolean_union"
bl_label = "Brush Union"
bl_description = "This operator add a union brush to a canvas"
@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):
bl_idname = "btool.boolean_inters"
bl_label = "Brush Intersection"
bl_description = "This operator add a intersect brush to a canvas"
@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):
bl_idname = "btool.boolean_diff"
bl_label = "Brush Difference"
bl_description = "This operator add a difference brush to a canvas"
@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):
bl_idname = "btool.boolean_slice"
bl_label = "Brush Slice"
bl_description = "This operator add a intersect brush to a canvas"
@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.make_single_user(object=True, obdata=True)
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):
bl_idname = "btool.auto_union"
bl_label = "Union"
bl_description = "Combine selected objects"
bl_options = {'REGISTER', 'UNDO'}
mode = 'UNION'
def execute(self, context):
self.objects_prepare()
self.boolean_operation()
return {'FINISHED'}
class Auto_Difference(AutoBoolean, Operator):
bl_idname = "btool.auto_difference"
bl_label = "Difference"
bl_description = "Subtract selected objects from active object"
bl_options = {'REGISTER', 'UNDO'}
mode = 'DIFFERENCE'
def execute(self, context):
self.objects_prepare()
self.boolean_operation()
return {'FINISHED'}
class Auto_Intersect(AutoBoolean, Operator):
bl_idname = "btool.auto_intersect"
bl_label = "Intersect"
bl_description = "Keep only intersecting geometry"
bl_options = {'REGISTER', 'UNDO'}
mode = 'INTERSECT'
def execute(self, context):
self.objects_prepare()
self.boolean_operation()
return {'FINISHED'}
class Auto_Slice(AutoBoolean, Operator):
bl_idname = "btool.auto_slice"
bl_label = "Slice"
bl_description = ("Slice active object along the selected object\n"
"(can handle only two objects at a time)")
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):
bl_idname = "btool.auto_subtract"
bl_label = "Subtract"
bl_description = ("Subtract selected object from active object, subtracted object not removed\n"
"(can handle only two objects at a time)")
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):
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.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'}
# 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 = ""
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 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")
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):
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
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
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_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'
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()