1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
23 "author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto, Simon Appelt",
25 "blender": (2, 80, 0),
26 "location": "View3D > Sidebar > Edit Tab",
27 "description": "Bool Tool Hotkey: Ctrl Shift B",
28 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/bool_tools.html",
33 from bpy
.types
import (
39 from bpy
.props
import (
45 # ------------------- Bool Tool FUNCTIONS -------------------------
48 # Hide boolean objects
49 def update_BoolHide(self
, context
):
50 ao
= context
.view_layer
.objects
.active
51 objs
= [i
.object for i
in ao
.modifiers
if i
.type == "BOOLEAN"]
52 hide_state
= context
.scene
.BoolHide
55 o
.hide_viewport
= hide_state
60 if _obj
["BoolToolRoot"]:
68 if _obj
["BoolToolBrush"]:
75 # def isPolyBrush(_obj):
77 # if _obj["BoolToolPolyBrush"]:
83 def cycles_visibility_set(ob
, value
=False):
84 if not hasattr(ob
, "cycles_visibility"):
87 vis
= ob
.cycles_visibility
93 vis
.transmission
= value
97 def BT_ObjectByName(obj
):
98 for ob
in bpy
.context
.view_layer
.objects
:
99 if isCanvas(ob
) or isBrush(ob
):
105 for ob
in bpy
.context
.view_layer
.objects
:
107 for mod
in ob
.modifiers
:
108 if "BTool_" in mod
.name
:
109 if obj
.name
in mod
.name
:
114 preferences
= bpy
.context
.preferences
115 addons
= preferences
.addons
116 addon_prefs
= addons
[__name__
].preferences
117 if addon_prefs
.fast_transform
:
123 def ConvertToMesh(obj
):
124 act
= bpy
.context
.view_layer
.objects
.active
125 bpy
.context
.view_layer
.objects
.active
= obj
126 bpy
.ops
.object.convert(target
="MESH")
127 bpy
.context
.view_layer
.objects
.active
= act
130 # Do the Union, Difference and Intersection Operations with a Brush
131 def Operation(context
, _operation
):
132 prefs
= context
.preferences
.addons
[__name__
].preferences
133 useWire
= prefs
.use_wire
135 for selObj
in context
.selected_objects
:
137 selObj
!= context
.active_object
and
138 (selObj
.type == "MESH" or selObj
.type == "CURVE")
140 if selObj
.type == "CURVE":
141 ConvertToMesh(selObj
)
142 actObj
= context
.active_object
143 selObj
.hide_render
= True
146 selObj
.display_type
= "WIRE"
148 selObj
.display_type
= "BOUNDS"
150 cycles_visibility_set(selObj
, value
=False)
152 if _operation
== "SLICE":
153 # copies instance_collection property(empty), but group property is empty (users_group = None)
154 clone
= actObj
.copy()
155 context
.collection
.objects
.link(clone
)
157 space_data
= context
.space_data
158 is_local_view
= bool(space_data
.local_view
)
161 clone
.local_view_set(space_data
, True)
163 sliceMod
= clone
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN") # add mod to clone obj
164 sliceMod
.object = selObj
165 sliceMod
.operation
= "DIFFERENCE"
166 clone
["BoolToolRoot"] = True
168 newMod
= actObj
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN")
169 newMod
.object = selObj
171 if _operation
== "SLICE":
172 newMod
.operation
= "INTERSECT"
174 newMod
.operation
= _operation
176 actObj
["BoolToolRoot"] = True
177 selObj
["BoolToolBrush"] = _operation
178 selObj
["BoolTool_FTransform"] = "False"
181 # Remove Objects form the BoolTool System
182 def Remove(context
, thisObj_name
, Prop
):
183 # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
184 actObj
= context
.active_object
187 def RemoveThis(_thisObj_name
):
188 for obj
in bpy
.context
.view_layer
.objects
:
189 # if it's the brush object
190 if obj
.name
== _thisObj_name
:
191 obj
.display_type
= "TEXTURED"
192 del obj
["BoolToolBrush"]
193 del obj
["BoolTool_FTransform"]
194 cycles_visibility_set(obj
, value
=True)
196 # Remove it from the Canvas
197 for mod
in actObj
.modifiers
:
198 if "BTool_" in mod
.name
:
199 if _thisObj_name
in mod
.name
:
200 actObj
.modifiers
.remove(mod
)
203 RemoveThis(thisObj_name
)
205 # If the remove was called from the Properties:
207 # Remove the Brush Property
209 Canvas
= FindCanvas(actObj
)
212 for mod
in Canvas
.modifiers
:
213 if "BTool_" in mod
.name
and actObj
.name
in mod
.name
:
214 Canvas
.modifiers
.remove(mod
)
216 actObj
.display_type
= "TEXTURED"
217 del actObj
["BoolToolBrush"]
218 del actObj
["BoolTool_FTransform"]
219 cycles_visibility_set(actObj
, value
=True)
222 for mod
in actObj
.modifiers
:
223 if "BTool_" in mod
.name
:
224 RemoveThis(mod
.object.name
)
227 # Toggle the Enable the Brush Object Property
228 def EnableBrush(context
, objList
, canvas
):
230 for mod
in canvas
.modifiers
:
231 if "BTool_" in mod
.name
and mod
.object.name
== obj
:
233 if mod
.show_viewport
:
234 mod
.show_viewport
= False
235 mod
.show_render
= False
237 mod
.show_viewport
= True
238 mod
.show_render
= True
241 # Find the Canvas and Enable this Brush
242 def EnableThisBrush(context
, set):
244 for obj
in bpy
.context
.view_layer
.objects
:
245 if obj
!= bpy
.context
.active_object
:
247 for mod
in obj
.modifiers
:
248 if "BTool_" in mod
.name
:
249 if mod
.object == bpy
.context
.active_object
:
252 for mod
in canvas
.modifiers
:
253 if "BTool_" in mod
.name
:
254 if mod
.object == bpy
.context
.active_object
:
256 if mod
.show_viewport
:
257 mod
.show_viewport
= False
258 mod
.show_render
= False
260 mod
.show_viewport
= True
261 mod
.show_render
= True
264 mod
.show_viewport
= True
266 mod
.show_viewport
= False
270 # Toggle the Fast Transform Property of the Active Brush
271 def EnableFTransf(context
):
272 actObj
= bpy
.context
.active_object
274 if actObj
["BoolTool_FTransform"] == "True":
275 actObj
["BoolTool_FTransform"] = "False"
277 actObj
["BoolTool_FTransform"] = "True"
281 # Apply All Brushes to the Canvas
282 def ApplyAll(context
, list):
285 if isCanvas(selObj
) and selObj
== context
.active_object
:
286 for mod
in selObj
.modifiers
:
287 if "BTool_" in mod
.name
:
288 objDeleteList
.append(mod
.object)
290 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
291 except: # if fails the means it is multiuser data
292 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
293 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
294 del selObj
["BoolToolRoot"]
296 for obj
in context
.scene
.objects
:
298 for mod
in obj
.modifiers
:
299 # do not delete brush that is used by another canvas
300 if mod
.type == "BOOLEAN" and mod
.object in objDeleteList
:
301 objDeleteList
.remove(mod
.object) # remove it from deletion
303 bpy
.ops
.object.select_all(action
="DESELECT")
304 for obj
in objDeleteList
:
306 bpy
.ops
.object.delete()
309 # Apply This Brush to the Canvas
310 def ApplyThisBrush(context
, brush
):
311 for obj
in context
.scene
.objects
:
313 for mod
in obj
.modifiers
:
314 if "BTool_" + brush
.name
in mod
.name
:
316 context
.view_layer
.objects
.active
= obj
318 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
319 except: # if fails the means it is multiuser data
320 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
321 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
322 bpy
.ops
.object.select_all(action
="TOGGLE")
323 bpy
.ops
.object.select_all(action
="DESELECT")
326 brush
.select_set(True)
327 # bpy.ops.object.delete()
330 # ------------------ Bool Tool OPERATORS --------------------------------------
333 # class BTool_DrawPolyBrush(Operator):
334 # bl_idname = "btool.draw_polybrush"
335 # bl_label = "Draw Poly Brush"
337 # "Draw Polygonal Mask, can be applied to Canvas > Brush or Directly\n"
338 # "Note: ESC to Cancel, Enter to Apply, Right Click to erase the Lines"
342 # store_cont_draw = False
345 # def poll(cls, context):
346 # return context.active_object is not None
348 # def set_cont_draw(self, context, start=False):
349 # # store / restore GP continuous drawing (see T52321)
350 # scene = context.scene
351 # tool_settings = scene.tool_settings
352 # continuous = tool_settings.use_gpencil_continuous_drawing
354 # self.store_cont_draw = continuous
355 # tool_settings.use_gpencil_continuous_drawing = True
357 # tool_settings.use_gpencil_continuous_drawing = self.store_cont_draw
359 # def modal(self, context, event):
361 # actObj = bpy.context.active_object
362 # if self.count == 1:
363 # actObj.select_set(True)
364 # bpy.ops.gpencil.draw("INVOKE_DEFAULT", mode="DRAW_POLY")
366 # if event.type == "RIGHTMOUSE":
367 # # use this to pass to the Grease Pencil eraser (see T52321)
370 # if event.type in {"RET", "NUMPAD_ENTER"}:
372 # bpy.ops.gpencil.convert(type="POLY")
373 # self.set_cont_draw(context)
375 # for obj in context.selected_objects:
376 # if obj.type == "CURVE":
377 # obj.name = "PolyDraw"
378 # bpy.context.view_layer.objects.active = obj
379 # bpy.ops.object.select_all(action="DESELECT")
380 # obj.select_set(True)
381 # bpy.ops.object.convert(target="MESH")
382 # bpy.ops.object.mode_set(mode="EDIT")
383 # bpy.ops.mesh.select_all(action="SELECT")
384 # bpy.ops.mesh.edge_face_add()
385 # bpy.ops.mesh.flip_normals()
386 # bpy.ops.object.mode_set(mode="OBJECT")
387 # bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
388 # bpy.ops.object.modifier_add(type="SOLIDIFY")
389 # for mod in obj.modifiers:
390 # if mod.name == "Solidify":
391 # mod.name = "BTool_PolyBrush"
394 # obj["BoolToolPolyBrush"] = True
396 # bpy.ops.object.select_all(action="DESELECT")
397 # bpy.context.view_layer.objects.active = actObj
398 # bpy.context.view_layer.update()
399 # actObj.select_set(True)
400 # obj.select_set(True)
402 # bpy.context.view_layer.grease_pencil.clear()
403 # bpy.ops.gpencil.data_unlink()
405 # return {"FINISHED"}
407 # if event.type == "ESC":
408 # bpy.ops.ed.undo() # remove o Grease Pencil
409 # self.set_cont_draw(context)
411 # self.report({"INFO"}, "Draw Poly Brush: Operation Cancelled by User")
412 # return {"CANCELLED"}
414 # return {"RUNNING_MODAL"}
416 # def invoke(self, context, event):
418 # self.set_cont_draw(context, start=True)
419 # context.window_manager.modal_handler_add(self)
420 # return {"RUNNING_MODAL"}
422 # self.report({"WARNING"}, "No active object, could not finish")
423 # return {"CANCELLED"}
427 class BTool_FastTransform(Operator
):
428 bl_idname
= "btool.fast_transform"
429 bl_label
= "Fast Transform"
430 bl_description
= "Enable Fast Transform"
432 operator
: StringProperty("")
436 def modal(self
, context
, event
):
438 actObj
= bpy
.context
.active_object
439 useWire
= bpy
.context
.preferences
.addons
[__name__
].preferences
.use_wire
442 if isBrush(actObj
) and actObj
["BoolTool_FTransform"] == "True":
443 EnableThisBrush(bpy
.context
, "False")
445 actObj
.display_type
= "WIRE"
447 actObj
.display_type
= "BOUNDS"
449 if self
.operator
== "Translate":
450 bpy
.ops
.transform
.translate("INVOKE_DEFAULT")
451 if self
.operator
== "Rotate":
452 bpy
.ops
.transform
.rotate("INVOKE_DEFAULT")
453 if self
.operator
== "Scale":
454 bpy
.ops
.transform
.resize("INVOKE_DEFAULT")
456 if event
.type == "LEFTMOUSE":
458 EnableThisBrush(bpy
.context
, "True")
459 actObj
.display_type
= "WIRE"
462 if event
.type in {"RIGHTMOUSE", "ESC"}:
464 EnableThisBrush(bpy
.context
, "True")
465 actObj
.display_type
= "WIRE"
468 return {"RUNNING_MODAL"}
470 def invoke(self
, context
, event
):
472 context
.window_manager
.modal_handler_add(self
)
473 return {"RUNNING_MODAL"}
475 self
.report({"WARNING"}, "No active object, could not finish")
479 # ------------------- Bool Tool OPERATOR CLASSES --------------------------------------------------------
483 # --------------------------------------------------------------------------------------
488 def execute(self
, context
):
489 Operation(context
, self
.mode
)
492 def invoke(self
, context
, event
):
493 if len(context
.selected_objects
) < 2:
494 self
.report({"ERROR"}, "At least two objects must be selected")
497 return self
.execute(context
)
500 class BTool_Union(Operator
, BToolSetup
):
501 bl_idname
= "btool.boolean_union"
502 bl_label
= "Brush Union"
503 bl_description
= "This operator add a union brush to a canvas"
504 bl_options
= {"REGISTER", "UNDO"}
509 class BTool_Inters(Operator
, BToolSetup
):
510 bl_idname
= "btool.boolean_inters"
511 bl_label
= "Brush Intersection"
512 bl_description
= "This operator add a intersect brush to a canvas"
513 bl_options
= {"REGISTER", "UNDO"}
518 class BTool_Diff(Operator
, BToolSetup
):
519 bl_idname
= "btool.boolean_diff"
520 bl_label
= "Brush Difference"
521 bl_description
= "This operator add a difference brush to a canvas"
522 bl_options
= {"REGISTER", "UNDO"}
527 class BTool_Slice(Operator
, BToolSetup
):
528 bl_idname
= "btool.boolean_slice"
529 bl_label
= "Brush Slice"
530 bl_description
= "This operator add a intersect brush to a canvas"
531 bl_options
= {"REGISTER", "UNDO"}
536 # Auto Boolean operators
537 # --------------------------------------------------------------------------------------
542 def objects_prepare(self
):
543 for ob
in bpy
.context
.selected_objects
:
544 if ob
.type != "MESH":
546 bpy
.ops
.object.make_single_user(object=True, obdata
=True)
547 bpy
.ops
.object.convert(target
="MESH")
549 def mesh_selection(self
, ob
, select_action
):
550 obj
= bpy
.context
.active_object
552 bpy
.context
.view_layer
.objects
.active
= ob
553 bpy
.ops
.object.mode_set(mode
="EDIT")
555 bpy
.ops
.mesh
.reveal()
556 bpy
.ops
.mesh
.select_all(action
=select_action
)
558 bpy
.ops
.object.mode_set(mode
="OBJECT")
559 bpy
.context
.view_layer
.objects
.active
= obj
561 def boolean_operation(self
):
562 obj
= bpy
.context
.active_object
563 obj
.select_set(False)
564 obs
= bpy
.context
.selected_objects
566 self
.mesh_selection(obj
, "DESELECT")
569 self
.mesh_selection(ob
, "SELECT")
570 self
.boolean_mod(obj
, ob
, self
.mode
)
574 def boolean_mod(self
, obj
, ob
, mode
, ob_delete
=True):
575 md
= obj
.modifiers
.new("Auto Boolean", "BOOLEAN")
576 md
.show_viewport
= False
580 override
= {"object": obj
}
581 bpy
.ops
.object.modifier_apply(override
, modifier
=md
.name
)
584 bpy
.data
.objects
.remove(ob
)
586 def execute(self
, context
):
587 self
.objects_prepare()
588 self
.boolean_operation()
591 def invoke(self
, context
, event
):
592 if len(context
.selected_objects
) < 2:
593 self
.report({"ERROR"}, "At least two objects must be selected")
596 return self
.execute(context
)
599 class OBJECT_OT_BoolTool_Auto_Union(Operator
, Auto_Boolean
):
600 bl_idname
= "object.booltool_auto_union"
601 bl_label
= "Bool Tool Union"
602 bl_description
= "Combine selected objects"
603 bl_options
= {"REGISTER", "UNDO"}
608 class OBJECT_OT_BoolTool_Auto_Difference(Operator
, Auto_Boolean
):
609 bl_idname
= "object.booltool_auto_difference"
610 bl_label
= "Bool Tool Difference"
611 bl_description
= "Subtract selected objects from active object"
612 bl_options
= {"REGISTER", "UNDO"}
617 class OBJECT_OT_BoolTool_Auto_Intersect(Operator
, Auto_Boolean
):
618 bl_idname
= "object.booltool_auto_intersect"
619 bl_label
= "Bool Tool Intersect"
620 bl_description
= "Keep only intersecting geometry"
621 bl_options
= {"REGISTER", "UNDO"}
626 class OBJECT_OT_BoolTool_Auto_Slice(Operator
, Auto_Boolean
):
627 bl_idname
= "object.booltool_auto_slice"
628 bl_label
= "Bool Tool Slice"
629 bl_description
= "Slice active object along the selected objects"
630 bl_options
= {"REGISTER", "UNDO"}
632 def execute(self
, context
):
633 space_data
= context
.space_data
634 is_local_view
= bool(space_data
.local_view
)
635 self
.objects_prepare()
637 ob1
= context
.active_object
638 ob1
.select_set(False)
639 self
.mesh_selection(ob1
, "DESELECT")
641 for ob2
in context
.selected_objects
:
643 self
.mesh_selection(ob2
, "SELECT")
645 ob1_copy
= ob1
.copy()
646 ob1_copy
.data
= ob1
.data
.copy()
648 for coll
in ob1
.users_collection
:
649 coll
.objects
.link(ob1_copy
)
652 ob1_copy
.local_view_set(space_data
, True)
654 self
.boolean_mod(ob1
, ob2
, "DIFFERENCE", ob_delete
=False)
655 self
.boolean_mod(ob1_copy
, ob2
, "INTERSECT")
656 ob1_copy
.select_set(True)
658 context
.view_layer
.objects
.active
= ob1_copy
663 # Utils Class ---------------------------------------------------------------
665 # Find the Brush Selected in Three View
666 class BTool_FindBrush(Operator
):
667 bl_idname
= "btool.find_brush"
669 bl_description
= "Find the selected brush"
671 obj
: StringProperty("")
674 def poll(cls
, context
):
675 return context
.active_object
is not None
677 def execute(self
, context
):
678 for ob
in bpy
.context
.view_layer
.objects
:
679 if ob
.name
== self
.obj
:
680 bpy
.ops
.object.select_all(action
="TOGGLE")
681 bpy
.ops
.object.select_all(action
="DESELECT")
682 bpy
.context
.view_layer
.objects
.active
= ob
683 ob
.set_select(state
=True)
687 # Move The Modifier in The Stack Up or Down
688 class BTool_MoveStack(Operator
):
689 bl_idname
= "btool.move_stack"
691 bl_description
= "Move this Brush Up/Down in the Stack"
693 modif
: StringProperty("")
694 direction
: StringProperty("")
697 def poll(cls
, context
):
698 return context
.active_object
is not None
700 def execute(self
, context
):
701 if self
.direction
== "UP":
702 bpy
.ops
.object.modifier_move_up(modifier
=self
.modif
)
703 if self
.direction
== "DOWN":
704 bpy
.ops
.object.modifier_move_down(modifier
=self
.modif
)
708 # Enable or Disable a Brush in the Three View
709 class BTool_EnableBrush(Operator
):
710 bl_idname
= "btool.enable_brush"
712 bl_description
= "Removes all BoolTool config assigned to it"
714 thisObj
: StringProperty("")
717 def poll(cls
, context
):
718 return context
.active_object
is not None
720 def execute(self
, context
):
721 # in this case is just one object but the function accept more than one at once
722 EnableBrush(context
, [self
.thisObj
], context
.active_object
)
726 # Enable or Disable a Brush Directly
727 class BTool_EnableThisBrush(Operator
):
728 bl_idname
= "btool.enable_this_brush"
730 bl_description
= "Toggles this brush"
733 def poll(cls
, context
):
734 return context
.active_object
is not None
736 def execute(self
, context
):
737 EnableThisBrush(context
, "None")
741 # Enable or Disable a Brush Directly
742 class BTool_EnableFTransform(Operator
):
743 bl_idname
= "btool.enable_ftransf"
745 bl_description
= "Use Fast Transformations to improve speed"
748 def poll(cls
, context
):
749 return context
.active_object
is not None
751 def execute(self
, context
):
752 EnableFTransf(context
)
756 # Other Operations -------------------------------------------------------
758 # Remove a Brush or a Canvas
759 class BTool_Remove(Operator
):
760 bl_idname
= "btool.remove"
761 bl_label
= "Bool Tool Remove"
762 bl_description
= "Removes all BoolTool config assigned to it"
763 bl_options
= {"UNDO"}
765 thisObj
: StringProperty("")
766 Prop
: StringProperty("")
769 def poll(cls
, context
):
770 return context
.active_object
is not None
772 def execute(self
, context
):
773 Remove(context
, self
.thisObj
, self
.Prop
)
777 # Apply All to Canvas
778 class BTool_AllBrushToMesh(Operator
):
779 bl_idname
= "btool.to_mesh"
780 bl_label
= "Apply All Canvas"
781 bl_description
= "Apply all brushes of this canvas"
782 bl_options
= {"UNDO"}
785 def poll(cls
, context
):
786 return context
.active_object
is not None
788 def execute(self
, context
):
789 lists
= bpy
.context
.selected_objects
790 ApplyAll(context
, lists
)
794 # Apply This Brush to the Canvas
795 class BTool_BrushToMesh(Operator
):
796 bl_idname
= "btool.brush_to_mesh"
797 bl_label
= "Apply this Brush to Canvas"
798 bl_description
= "Apply this brush to the canvas"
799 bl_options
= {"UNDO"}
802 def poll(cls
, context
):
804 if isBrush(context
.active_object
):
809 def execute(self
, context
):
810 ApplyThisBrush(context
, bpy
.context
.active_object
)
815 # Apply This Brush To Mesh
818 # ------------------- MENU CLASSES ------------------------------
821 class VIEW3D_MT_booltool_menu(Menu
):
822 bl_label
= "Bool Tool"
823 bl_idname
= "VIEW3D_MT_booltool_menu"
825 def draw(self
, context
):
828 layout
.label(text
="Auto Boolean")
829 layout
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
830 layout
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
831 layout
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
832 layout
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
836 layout
.label(text
="Brush Boolean")
837 layout
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
838 layout
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
839 layout
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
840 layout
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
842 if isCanvas(context
.active_object
):
844 layout
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
845 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove All")
849 if isBrush(context
.active_object
):
851 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
852 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove Brush")
857 def VIEW3D_BoolTool_Menu(self
, context
):
858 self
.layout
.menu(VIEW3D_MT_booltool_menu
.bl_idname
)
861 # ---------------- Toolshelf: Tools ---------------------
864 class VIEW3D_PT_booltool_tools(Panel
):
865 bl_category
= "objectmode"
866 bl_label
= "Bool Tool"
867 bl_space_type
= "VIEW_3D"
868 bl_region_type
= "UI"
869 bl_context
= "objectmode"
870 bl_options
= {'DEFAULT_CLOSED'}
873 def poll(cls
, context
):
874 return context
.active_object
is not None
876 def draw(self
, context
):
879 col
= layout
.column(align
=True)
880 col
.label(text
="Auto Boolean")
881 col
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
882 col
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
883 col
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
884 col
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
886 col
= layout
.column(align
=True)
887 col
.label(text
="Brush Boolean")
888 col
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
889 col
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
890 col
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
891 col
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
893 # TODO Draw Poly Brush
896 # col = main.column(align=True)
897 # col.label(text="Draw:", icon="MESH_CUBE")
899 # col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
902 # ---------- Toolshelf: Properties --------------------------------------------------------
905 class VIEW3D_PT_booltool_config(Panel
):
906 bl_category
= "objectmode"
907 bl_label
= "Properties"
908 bl_space_type
= "VIEW_3D"
909 bl_region_type
= "UI"
910 bl_context
= "objectmode"
911 bl_parent_id
= "VIEW3D_PT_booltool_tools"
914 def poll(cls
, context
):
915 actObj
= context
.active_object
916 return isCanvas(actObj
) or isBrush(actObj
) # or isPolyBrush(actObj)
918 def draw(self
, context
):
920 actObj
= context
.active_object
922 row
= layout
.row(align
=True)
926 row
.label(text
="CANVAS", icon
="MESH_GRID")
928 row
.prop(context
.scene
, "BoolHide", text
="Hide Bool objects")
929 row
= layout
.row(align
=True)
930 row
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
932 row
= layout
.row(align
=True)
933 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove All")
942 if actObj
["BoolToolBrush"] == "DIFFERENCE":
943 icon
= "SELECT_SUBTRACT"
944 elif actObj
["BoolToolBrush"] == "UNION":
945 icon
= "SELECT_EXTEND"
946 elif actObj
["BoolToolBrush"] == "INTERSECT":
947 icon
= "SELECT_INTERSECT"
948 elif actObj
["BoolToolBrush"] == "SLICE":
949 icon
= "SELECT_DIFFERENCE"
951 row
.label(text
="BRUSH", icon
=icon
)
953 if actObj
["BoolTool_FTransform"] == "True":
961 row
= layout
.row(align
=True)
962 row
.operator(BTool_EnableFTransform
.bl_idname
, text
="Fast Vis", icon
=icon
)
963 row
.operator(BTool_EnableThisBrush
.bl_idname
, text
="Enable", icon
="HIDE_OFF")
965 row
.operator(BTool_EnableThisBrush
.bl_idname
, icon
="HIDE_OFF")
967 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
968 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove Brush")
973 # if isPolyBrush(actObj):
974 # layout.label(text="POLY BRUSH", icon="LINE_DATA")
975 # mod = actObj.modifiers["BTool_PolyBrush"]
976 # layout.prop(mod, "thickness", text="Size")
979 # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
982 class VIEW3D_PT_booltool_bviewer(Panel
):
983 bl_category
= "objectmode"
984 bl_label
= "Brush Viewer"
985 bl_space_type
= "VIEW_3D"
986 bl_region_type
= "UI"
987 bl_context
= "objectmode"
988 bl_parent_id
= "VIEW3D_PT_booltool_tools"
991 def poll(cls
, context
):
992 actObj
= bpy
.context
.active_object
999 def draw(self
, context
):
1001 actObj
= bpy
.context
.active_object
1003 if isCanvas(actObj
):
1005 for mod
in actObj
.modifiers
:
1006 container
= self
.layout
.box()
1007 row
= container
.row(align
=True)
1009 if "BTool_" in mod
.name
:
1011 if mod
.operation
== "DIFFERENCE":
1012 icon
= "SELECT_SUBTRACT"
1013 elif mod
.operation
== "UNION":
1014 icon
= "SELECT_EXTEND"
1015 elif mod
.operation
== "INTERSECT":
1016 icon
= "SELECT_INTERSECT"
1017 elif mod
.operation
== "SLICE":
1018 icon
= "SELECT_DIFFERENCE"
1020 objSelect
= row
.operator("btool.find_brush", text
=mod
.object.name
, icon
=icon
, emboss
=False)
1021 objSelect
.obj
= mod
.object.name
1023 EnableIcon
= "RESTRICT_VIEW_ON"
1024 if mod
.show_viewport
:
1025 EnableIcon
= "RESTRICT_VIEW_OFF"
1026 Enable
= row
.operator(BTool_EnableBrush
.bl_idname
, icon
=EnableIcon
, emboss
=False)
1027 Enable
.thisObj
= mod
.object.name
1029 Remove
= row
.operator("btool.remove", text
="", icon
="X", emboss
=False)
1030 Remove
.thisObj
= mod
.object.name
1031 Remove
.Prop
= "THIS"
1034 row
.label(text
=mod
.name
)
1036 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1040 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1042 Dw
.direction
= "DOWN"
1045 # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
1050 ("Menu", "Ctrl Shift B"),
1052 ("Auto Operators", None),
1053 ("Difference", "Ctrl Shift Num -"),
1054 ("Union", "Ctrl Shift Num +"),
1055 ("Intersect", "Ctrl Shift Num *"),
1056 ("Slice", "Ctrl Shift Num /"),
1058 ("Brush Operators", None),
1059 ("Difference", "Ctrl Num -"),
1060 ("Union", "Ctrl Num +"),
1061 ("Intersect", "Ctrl Num *"),
1062 ("Slice", "Ctrl Num /"),
1063 ("Brush To Mesh", "Ctrl Num Enter"),
1064 ("All Brushes To Mesh", "Ctrl Shift Num Enter"),
1068 def UpdateBoolTool_Pref(self
, context
):
1069 if self
.fast_transform
:
1075 # Define Panel classes for updating
1077 VIEW3D_PT_booltool_tools
,
1078 VIEW3D_PT_booltool_config
,
1079 VIEW3D_PT_booltool_bviewer
,
1083 def update_panels(self
, context
):
1085 for panel
in panels
:
1086 if "bl_rna" in panel
.__dict
__:
1087 bpy
.utils
.unregister_class(panel
)
1089 for panel
in panels
:
1090 panel
.bl_category
= context
.preferences
.addons
[
1092 ].preferences
.category
1093 bpy
.utils
.register_class(panel
)
1095 except Exception as e
:
1096 message
= "Bool Tool: Updating Panel locations has failed"
1097 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1100 def icon_tria(prop
):
1106 class PREFS_BoolTool_Props(AddonPreferences
):
1107 bl_idname
= __name__
1109 fast_transform
: BoolProperty(
1110 name
="Fast Transformations",
1111 update
=UpdateBoolTool_Pref
,
1112 description
="Replace the Transform HotKeys (G,R,S)\n"
1113 "for a custom version that can optimize the visualization of Brushes",
1115 use_wire
: BoolProperty(
1116 name
="Display As Wirewrame",
1117 description
="Display brush as wireframe instead of bounding box",
1119 category
: StringProperty(
1121 description
="Set sidebar tab name",
1123 update
=update_panels
,
1125 show_shortcuts
: BoolProperty(name
="Shortcuts")
1127 def draw(self
, context
):
1128 layout
= self
.layout
1129 layout
.use_property_split
= True
1130 layout
.use_property_decorate
= False
1132 col
= layout
.column()
1133 col
.prop(self
, "category")
1134 col
.prop(self
, "fast_transform")
1135 col
.prop(self
, "use_wire")
1137 col
= layout
.column()
1139 col
.use_property_split
= False
1140 col
.prop(self
, "show_shortcuts", icon
=icon_tria(self
.show_shortcuts
))
1142 if self
.show_shortcuts
:
1144 col
= layout
.column()
1146 for key_name
, key_comb
in shortcut_list
:
1147 if key_comb
is None:
1149 col
.label(text
=key_name
)
1151 row
= col
.row(align
=True)
1153 row
.box().label(text
=key_name
)
1154 row
.box().label(text
=key_comb
)
1157 # ------------------- Class List ------------------------------------------------
1160 PREFS_BoolTool_Props
,
1161 VIEW3D_MT_booltool_menu
,
1162 VIEW3D_PT_booltool_tools
,
1163 VIEW3D_PT_booltool_config
,
1164 VIEW3D_PT_booltool_bviewer
,
1165 OBJECT_OT_BoolTool_Auto_Union
,
1166 OBJECT_OT_BoolTool_Auto_Difference
,
1167 OBJECT_OT_BoolTool_Auto_Intersect
,
1168 OBJECT_OT_BoolTool_Auto_Slice
,
1173 # TODO Draw Poly Brush
1174 # BTool_DrawPolyBrush,
1176 BTool_AllBrushToMesh
,
1181 BTool_EnableThisBrush
,
1182 BTool_EnableFTransform
,
1183 BTool_FastTransform
,
1187 # ------------------- REGISTER ------------------------------------------------
1190 addon_keymapsFastT
= []
1193 # Fast Transform HotKeys Register
1194 def RegisterFastT():
1195 wm
= bpy
.context
.window_manager
1196 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
="Object Mode", space_type
="EMPTY")
1198 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "G", "PRESS")
1199 kmi
.properties
.operator
= "Translate"
1200 addon_keymapsFastT
.append((km
, kmi
))
1202 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "R", "PRESS")
1203 kmi
.properties
.operator
= "Rotate"
1204 addon_keymapsFastT
.append((km
, kmi
))
1206 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "S", "PRESS")
1207 kmi
.properties
.operator
= "Scale"
1208 addon_keymapsFastT
.append((km
, kmi
))
1211 # Fast Transform HotKeys UnRegister
1212 def UnRegisterFastT():
1213 wm
= bpy
.context
.window_manager
1214 kc
= wm
.keyconfigs
.addon
1216 for km
, kmi
in addon_keymapsFastT
:
1217 km
.keymap_items
.remove(kmi
)
1219 addon_keymapsFastT
.clear()
1224 bpy
.utils
.register_class(cls
)
1225 update_panels(None, bpy
.context
)
1228 bpy
.types
.Scene
.BoolHide
= BoolProperty(
1230 description
="Hide boolean objects",
1231 update
=update_BoolHide
,
1233 bpy
.types
.VIEW3D_MT_object
.append(VIEW3D_BoolTool_Menu
)
1235 wm
= bpy
.context
.window_manager
1236 kc
= wm
.keyconfigs
.addon
1238 # create the boolean menu hotkey
1240 km
= kc
.keymaps
.new(name
="Object Mode")
1242 kmi
= km
.keymap_items
.new("wm.call_menu", "B", "PRESS", ctrl
=True, shift
=True)
1243 kmi
.properties
.name
= "VIEW3D_MT_booltool_menu"
1244 addon_keymaps
.append((km
, kmi
))
1247 kmi
= km
.keymap_items
.new(BTool_Union
.bl_idname
, "NUMPAD_PLUS", "PRESS", ctrl
=True)
1248 addon_keymaps
.append((km
, kmi
))
1249 kmi
= km
.keymap_items
.new(BTool_Diff
.bl_idname
, "NUMPAD_MINUS", "PRESS", ctrl
=True)
1250 addon_keymaps
.append((km
, kmi
))
1251 kmi
= km
.keymap_items
.new(BTool_Inters
.bl_idname
, "NUMPAD_ASTERIX", "PRESS", ctrl
=True)
1252 addon_keymaps
.append((km
, kmi
))
1253 kmi
= km
.keymap_items
.new(BTool_Slice
.bl_idname
, "NUMPAD_SLASH", "PRESS", ctrl
=True)
1254 addon_keymaps
.append((km
, kmi
))
1255 kmi
= km
.keymap_items
.new(BTool_BrushToMesh
.bl_idname
, "NUMPAD_ENTER", "PRESS", ctrl
=True)
1256 addon_keymaps
.append((km
, kmi
))
1257 kmi
= km
.keymap_items
.new(
1258 BTool_AllBrushToMesh
.bl_idname
,
1264 addon_keymaps
.append((km
, kmi
))
1267 kmi
= km
.keymap_items
.new(
1268 OBJECT_OT_BoolTool_Auto_Union
.bl_idname
,
1274 addon_keymaps
.append((km
, kmi
))
1275 kmi
= km
.keymap_items
.new(
1276 OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
,
1282 addon_keymaps
.append((km
, kmi
))
1283 kmi
= km
.keymap_items
.new(
1284 OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
,
1290 addon_keymaps
.append((km
, kmi
))
1291 kmi
= km
.keymap_items
.new(
1292 OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
,
1298 addon_keymaps
.append((km
, kmi
))
1303 # remove keymaps when add-on is deactivated
1304 wm
= bpy
.context
.window_manager
1305 kc
= wm
.keyconfigs
.addon
1307 for km
, kmi
in addon_keymaps
:
1308 km
.keymap_items
.remove(kmi
)
1310 addon_keymaps
.clear()
1312 bpy
.types
.VIEW3D_MT_object
.remove(VIEW3D_BoolTool_Menu
)
1313 del bpy
.types
.Scene
.BoolHide
1316 bpy
.utils
.unregister_class(cls
)
1319 if __name__
== "__main__":