diff --git a/object_boolean_tools.py b/object_boolean_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..6e857ce387d65ce1a7f61bcbfd1ecc5d1edec716 --- /dev/null +++ b/object_boolean_tools.py @@ -0,0 +1,1371 @@ +# ##### 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, 3), + "blender": (2, 77, 0), + "location": "View3D > Toolshelf > BoolTool", + "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 ( + Operator, + Panel, + ) + + +# ------------------- 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): + + useWire = bpy.context.user_preferences.addons[__name__].preferences.use_wire + + for selObj in bpy.context.selected_objects: + if selObj != context.active_object and (selObj.type == "MESH" or selObj.type == "CURVE"): + if selObj.type == "CURVE": + ConvertToMesh(selObj) + actObj = context.active_object + selObj.hide_render = True + cyclesVis = selObj.cycles_visibility + # for obj in bpy.context.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 + if _operation == "SLICE": + newMod.operation = "INTERSECT" + else: + newMod.operation = _operation + + actObj["BoolToolRoot"] = True + selObj["BoolToolBrush"] = _operation + selObj["BoolTool_FTransform"] = "False" + + +# Do Direct Union, Difference and Intersection Operations +def Operation_Direct(context, _operation): + actObj = context.active_object + + useWire = bpy.context.user_preferences.addons[__name__].preferences.use_wire + for selObj in bpy.context.selected_objects: + if selObj != context.active_object and (selObj.type == "MESH" or selObj.type == "CURVE"): + if selObj.type == "CURVE": + ConvertToMesh(selObj) + actObj = context.active_object + if useWire: + selObj.draw_type = "WIRE" + else: + selObj.draw_type = "BOUNDS" + if _operation == "SLICE": + # copies dupli_group property(empty), but group property is empty (users_group = None) + clone = context.active_object.copy() + # clone.select=True + clone.data = context.active_object.data.copy() + context.scene.objects.link(clone) + sliceMod = clone.modifiers.new("BTool_" + selObj.name, "BOOLEAN") # add mod to clone obj + sliceMod.object = selObj + sliceMod.operation = "DIFFERENCE" + + bpy.ops.object.modifier_apply(modifier=sliceMod.name) + + newMod = actObj.modifiers.new("BTool_" + selObj.name, "BOOLEAN") + if _operation == "SLICE": + newMod.operation = "INTERSECT" + else: + newMod.operation = _operation + newMod.object = selObj + bpy.ops.object.modifier_apply(modifier=newMod.name) + bpy.ops.object.select_all(action='DESELECT') + # selObj.select = True + # bpy.ops.object.delete() + + +# 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) + 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) + + # bpy.ops.object.select_all(action='TOGGLE') + # bpy.ops.object.select_all(action='DESELECT') + # for obj in deleteList: + # obj.select = True + # bpy.ops.object.delete() + 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 = bpy.props.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'} + +# Booltron Direct FUNCTIONS --------------------------------------------------- + + +def mesh_selection(ob, select_action): + context = bpy.context + scene = context.scene + obj = context.active_object + ops_me = bpy.ops.mesh + ops_ob = bpy.ops.object + + scene.objects.active = ob + ops_ob.mode_set(mode="EDIT") + ops_me.select_all(action=select_action) + ops_ob.mode_set(mode="OBJECT") + scene.objects.active = obj + + +def modifier_boolean(obj, ob, mode, delete_not=False): + md = obj.modifiers.new("BoolTool Direct", 'BOOLEAN') + md.show_viewport = False + md.show_render = False + md.operation = mode + md.object = ob + + bpy.ops.object.modifier_apply(modifier="BoolTool Direct") + if delete_not is True: + return + bpy.context.scene.objects.unlink(ob) + bpy.data.objects.remove(ob) + + +def boolean_each(mode): + context = bpy.context + obj = context.active_object + + obj.select = False + obs = context.selected_objects + + mesh_selection(obj, 'DESELECT') + for ob in obs: + mesh_selection(ob, 'SELECT') + modifier_boolean(obj, ob, mode) + obj.select = True + + +def objects_get(): + context = bpy.context + obj = context.active_object + + obj.select = False + ob = context.selected_objects[0] + + mesh_selection(obj, 'DESELECT') + mesh_selection(ob, 'SELECT') + + return obj, ob + +# Booltron Direct Operators --------------------------------------------------- + + +class Direct_Union(Operator): + """Combine selected objects""" + bl_idname = "btool.direct_union" + bl_label = "Union" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + boolean_each('UNION') + return {'FINISHED'} + + +class Direct_Difference(Operator): + """Subtract selected objects from active object""" + bl_idname = "btool.direct_difference" + bl_label = "Difference" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + boolean_each('DIFFERENCE') + return {'FINISHED'} + + +class Direct_Intersect(Operator): + """Keep only intersecting geometry""" + bl_idname = "btool.direct_intersect" + bl_label = "Intersect" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + boolean_each('INTERSECT') + return {'FINISHED'} + + +class Direct_Slice(Operator): + """Slice active object along the selected object (can handle only two objects at a time)""" + bl_idname = "btool.direct_slice" + bl_label = "Slice" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return len(context.selected_objects) == 2 + + def execute(self, context): + scene = context.scene + obj, ob = objects_get() + + def object_duplicate(ob): + ops_ob = bpy.ops.object + ops_ob.select_all(action="DESELECT") + ops_ob.select_pattern(pattern=ob.name) + ops_ob.duplicate() + scene.objects.active = obj + return context.selected_objects[0] + + obj_copy = object_duplicate(obj) + modifier_boolean(obj, ob, 'DIFFERENCE', delete_not=True) + scene.objects.active = obj_copy + modifier_boolean(obj_copy, ob, 'INTERSECT') + return {'FINISHED'} + + +class Direct_Subtract(Operator): + """Subtract selected object from active object, """ \ + """subtracted object not removed (can handle only two objects at a time))""" + bl_idname = "btool.direct_subtract" + bl_label = "Subtract" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return len(context.selected_objects) == 2 + + def execute(self, context): + obj, ob = objects_get() + modifier_boolean(obj, ob, 'DIFFERENCE', delete_not=True) + 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 = bpy.props.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 = bpy.props.StringProperty("") + direction = bpy.props.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 = bpy.props.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 = bpy.props.StringProperty("") + Prop = bpy.props.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(bpy.types.Menu): + bl_label = "BoolTool Operators" + bl_idname = "OBJECT_MT_BoolTool_Menu" + + def draw(self, context): + layout = self.layout + + layout.label("Auto Boolean:") + layout.operator(Direct_Difference.bl_idname, icon="ROTACTIVE") + layout.operator(Direct_Union.bl_idname, icon="ROTATECOLLECTION") + layout.operator(Direct_Intersect.bl_idname, icon="ROTATECENTER") + layout.operator(Direct_Slice.bl_idname, icon="ROTATECENTER") + layout.operator(Direct_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) + +# ---------------- Bool Tools --------------------- + + +class BoolTool_Tools(Panel): + bl_label = "Tools" + bl_idname = "BoolTool_Tools" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Bool Tool" + bl_context = "objectmode" + + def draw(self, context): + layout = self.layout + + row = layout.row(True) + col = row.column(True) + col.label("Auto Boolean:", icon="MODIFIER") + col.separator() + col.operator(Direct_Difference.bl_idname, icon="ROTACTIVE") + col.operator(Direct_Union.bl_idname, icon="ROTATECOLLECTION") + col.operator(Direct_Intersect.bl_idname, icon="ROTATECENTER") + col.separator() + col.operator(Direct_Slice.bl_idname, icon="ROTATECENTER") + col.operator(Direct_Subtract.bl_idname, icon="ROTACTIVE") + + layout.separator() + row = layout.row(True) + col = row.column(True) + 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") + + layout.separator() + row = layout.row(True) + col = row.column(True) + col.label("Draw:", icon="MESH_CUBE") + col.separator() + col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA") + col.separator() + + +# ---------- Properties -------------------------------------------------------- +class BoolTool_Config(Panel): + bl_label = "Properties" + bl_idname = "BoolTool_BConfig" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Bool Tool" + 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) + # layout.separator() + + 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() + + +# ---------- Tree Viewer------------------------------------------------------- +class BoolTool_BViwer(Panel): + bl_label = "Brush Viewer" + bl_idname = "BoolTool_BViwer" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Bool Tool" + 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 ADD-ON PREFERENCES ---------------------------- + +def UpdateBoolTool_Pref(self, context): + if self.fast_transform: + RegisterFastT() + else: + UnRegisterFastT() + +## Addons Preferences Update Panel +def update_panel(self, context): + try: + bpy.utils.unregister_class(BoolTool_Tools) + bpy.utils.unregister_class(BoolTool_Config) + bpy.utils.unregister_class(BoolTool_BViwer) + except: + pass + BoolTool_Tools.bl_category = context.user_preferences.addons[__name__].preferences.category + bpy.utils.register_class(BoolTool_Tools) + BoolTool_Config.bl_category = context.user_preferences.addons[__name__].preferences.category + bpy.utils.register_class(BoolTool_Config) + BoolTool_BViwer.bl_category = context.user_preferences.addons[__name__].preferences.category + bpy.utils.register_class(BoolTool_BViwer) + + +class BoolTool_Pref(bpy.types.AddonPreferences): + bl_idname = __name__ + + fast_transform = bpy.props.BoolProperty( + name="Fast Transformations", + default=False, + update=UpdateBoolTool_Pref, + description=("Replace the Transform HotKeys (G,R,S) " + "for a custom version that can optimize the visualization of Brushes") + ) + + make_vertex_groups = bpy.props.BoolProperty( + name="Make Vertex Groups", + default=False, + description="When Apply a Brush to de Object it will create a new vertex group of the new faces" + ) + make_boundary = bpy.props.BoolProperty( + name="Make Boundary", + default=False, + description="When Apply a Brush to de Object it will create a new vertex group of the bondary boolean area" + ) + use_wire = bpy.props.BoolProperty( + name="Use Bmesh", + default=False, + description="Use The Wireframe Instead Of Boolean" + ) + + category = bpy.props.StringProperty( + name="Tab Category", + description="Choose a name for the category of the panel", + default="Bool Tools", + update=update_panel) + + def draw(self, context): + + layout = self.layout + row = layout.row() + col = row.column() + col.label(text="Category:") + col.prop(self, "category", text="") + layout.separator() + layout = self.layout + layout.label("Experimental Features:") + layout.prop(self, "fast_transform") + layout.prop(self, "use_wire", text="Use Wire Instead Of Bbox") + """ + # EXPERIMENTAL + layout.prop(self, "make_vertex_groups") + layout.prop(self, "make_boundary") + """ + layout = self.layout + layout.separator() + layout.label("Hotkey List:") + layout.separator() + layout.label("Menu: 'B', 'PRESS', ctrl=True, shift=True") + layout.label("Brush Operators:") + layout.label("Union: 'NUMPAD_PLUS', 'PRESS', ctrl=True") + layout.label("Diff: 'NUMPAD_MINUS', 'PRESS', ctrl=True") + layout.label("Intersect: 'NUMPAD_ASTERIX', 'PRESS', ctrl=True") + layout.label("Slice: 'NUMPAD_SLASH', 'PRESS', ctrl=True") + layout.label("Direct Operators:") + layout.label("Direct_Union: 'NUMPAD_PLUS', 'PRESS', ctrl=True, shift=True") + layout.label("Direct_Difference: 'NUMPAD_MINUS', 'PRESS', ctrl=True, shift=True") + layout.label("Direct_Intersect: 'NUMPAD_ASTERIX', 'PRESS', ctrl=True, shift=True") + layout.label("Direct_Slice: 'NUMPAD_SLASH', 'PRESS', ctrl=True, shift=True") + layout.label("BTool_BrushToMesh: 'NUMPAD_ENTER', 'PRESS', ctrl=True") + layout.label("BTool_AllBrushToMesh: 'NUMPAD_ENTER', 'PRESS', ctrl=True, shift=True") + +# ------------------- Class List ------------------------------------------------ +classes = ( + # Booltron + Direct_Union, + Direct_Difference, + Direct_Intersect, + Direct_Slice, + Direct_Subtract, + # Bool Tools + 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, + BoolTool_Menu, + BoolTool_Tools, + BoolTool_Config, + BoolTool_BViwer, + BTool_FastTransform, + BoolTool_Pref, + ) + +# ------------------- 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) + + wm = bpy.context.window_manager + # Scene variables + bpy.types.Scene.BoolHide = bpy.props.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) + + # 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) + + # Direct Operators + kmi = km.keymap_items.new(Direct_Union.bl_idname, 'NUMPAD_PLUS', 'PRESS', ctrl=True, shift=True) + kmi = km.keymap_items.new(Direct_Difference.bl_idname, 'NUMPAD_MINUS', 'PRESS', ctrl=True, shift=True) + kmi = km.keymap_items.new(Direct_Intersect.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', ctrl=True, shift=True) + kmi = km.keymap_items.new(Direct_Slice.bl_idname, 'NUMPAD_SLASH', 'PRESS', ctrl=True, shift=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) + + 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) + + del bpy.types.Scene.BoolHide + + for cls in classes: + bpy.utils.unregister_class(cls) + +if __name__ == "__main__": + register()