1 # SPDX-License-Identifier: GPL-2.0-or-later
5 "author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto, Simon Appelt",
8 "location": "View3D > Sidebar > Edit Tab",
9 "description": "Bool Tool Hotkey: Ctrl Shift B",
10 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/bool_tools.html",
15 from bpy
.types
import (
21 from bpy
.props
import (
27 # ------------------- Bool Tool FUNCTIONS -------------------------
30 # Hide boolean objects
31 def update_BoolHide(self
, context
):
32 ao
= context
.view_layer
.objects
.active
33 objs
= [i
.object for i
in ao
.modifiers
if i
.type == "BOOLEAN"]
34 hide_state
= context
.scene
.BoolHide
37 o
.hide_viewport
= hide_state
42 if _obj
["BoolToolRoot"]:
50 if _obj
["BoolToolBrush"]:
57 # def isPolyBrush(_obj):
59 # if _obj["BoolToolPolyBrush"]:
65 def object_visibility_set(ob
, value
=False):
66 ob
.visible_camera
= value
67 ob
.visible_diffuse
= value
68 ob
.visible_glossy
= value
69 ob
.visible_shadow
= value
70 ob
.visible_transmission
= value
71 ob
.visible_volume_scatter
= value
74 def BT_ObjectByName(obj
):
75 for ob
in bpy
.context
.view_layer
.objects
:
76 if isCanvas(ob
) or isBrush(ob
):
82 for ob
in bpy
.context
.view_layer
.objects
:
84 for mod
in ob
.modifiers
:
85 if "BTool_" in mod
.name
:
86 if obj
.name
in mod
.name
:
91 preferences
= bpy
.context
.preferences
92 addons
= preferences
.addons
93 addon_prefs
= addons
[__name__
].preferences
94 if addon_prefs
.fast_transform
:
100 def ConvertToMesh(obj
):
101 act
= bpy
.context
.view_layer
.objects
.active
102 bpy
.context
.view_layer
.objects
.active
= obj
103 bpy
.ops
.object.convert(target
="MESH")
104 bpy
.context
.view_layer
.objects
.active
= act
107 # Do the Union, Difference and Intersection Operations with a Brush
108 def Operation(context
, _operation
):
109 prefs
= context
.preferences
.addons
[__name__
].preferences
110 useWire
= prefs
.use_wire
112 for selObj
in context
.selected_objects
:
114 selObj
!= context
.active_object
and
115 (selObj
.type == "MESH" or selObj
.type == "CURVE")
117 if selObj
.type == "CURVE":
118 ConvertToMesh(selObj
)
119 actObj
= context
.active_object
120 selObj
.hide_render
= True
123 selObj
.display_type
= "WIRE"
125 selObj
.display_type
= "BOUNDS"
127 object_visibility_set(selObj
, value
=False)
129 if _operation
== "SLICE":
130 # copies instance_collection property(empty), but group property is empty (users_group = None)
131 clone
= actObj
.copy()
132 context
.collection
.objects
.link(clone
)
134 space_data
= context
.space_data
135 is_local_view
= bool(space_data
.local_view
)
138 clone
.local_view_set(space_data
, True)
140 sliceMod
= clone
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN") # add mod to clone obj
141 sliceMod
.object = selObj
142 sliceMod
.operation
= "DIFFERENCE"
143 clone
["BoolToolRoot"] = True
145 newMod
= actObj
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN")
146 newMod
.object = selObj
148 if _operation
== "SLICE":
149 newMod
.operation
= "INTERSECT"
151 newMod
.operation
= _operation
153 actObj
["BoolToolRoot"] = True
154 selObj
["BoolToolBrush"] = _operation
155 selObj
["BoolTool_FTransform"] = "False"
158 # Remove Objects form the BoolTool System
159 def Remove(context
, thisObj_name
, Prop
):
160 # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
161 actObj
= context
.active_object
164 def RemoveThis(_thisObj_name
):
165 for obj
in bpy
.context
.view_layer
.objects
:
166 # if it's the brush object
167 if obj
.name
== _thisObj_name
:
168 obj
.display_type
= "TEXTURED"
169 del obj
["BoolToolBrush"]
170 del obj
["BoolTool_FTransform"]
171 object_visibility_set(obj
, value
=True)
173 # Remove it from the Canvas
174 for mod
in actObj
.modifiers
:
175 if "BTool_" in mod
.name
:
176 if _thisObj_name
in mod
.name
:
177 actObj
.modifiers
.remove(mod
)
180 RemoveThis(thisObj_name
)
182 # If the remove was called from the Properties:
184 # Remove the Brush Property
186 Canvas
= FindCanvas(actObj
)
189 for mod
in Canvas
.modifiers
:
190 if "BTool_" in mod
.name
and actObj
.name
in mod
.name
:
191 Canvas
.modifiers
.remove(mod
)
193 actObj
.display_type
= "TEXTURED"
194 del actObj
["BoolToolBrush"]
195 del actObj
["BoolTool_FTransform"]
196 object_visibility_set(actObj
, value
=True)
199 for mod
in actObj
.modifiers
:
200 if "BTool_" in mod
.name
:
201 RemoveThis(mod
.object.name
)
204 # Toggle the Enable the Brush Object Property
205 def EnableBrush(context
, objList
, canvas
):
207 for mod
in canvas
.modifiers
:
208 if "BTool_" in mod
.name
and mod
.object.name
== obj
:
210 if mod
.show_viewport
:
211 mod
.show_viewport
= False
212 mod
.show_render
= False
214 mod
.show_viewport
= True
215 mod
.show_render
= True
218 # Find the Canvas and Enable this Brush
219 def EnableThisBrush(context
, set):
221 for obj
in bpy
.context
.view_layer
.objects
:
222 if obj
!= bpy
.context
.active_object
:
224 for mod
in obj
.modifiers
:
225 if "BTool_" in mod
.name
:
226 if mod
.object == bpy
.context
.active_object
:
229 for mod
in canvas
.modifiers
:
230 if "BTool_" in mod
.name
:
231 if mod
.object == bpy
.context
.active_object
:
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 mod
.show_viewport
= True
243 mod
.show_viewport
= False
247 # Toggle the Fast Transform Property of the Active Brush
248 def EnableFTransf(context
):
249 actObj
= bpy
.context
.active_object
251 if actObj
["BoolTool_FTransform"] == "True":
252 actObj
["BoolTool_FTransform"] = "False"
254 actObj
["BoolTool_FTransform"] = "True"
258 # Apply All Brushes to the Canvas
259 def ApplyAll(context
, list):
262 if isCanvas(selObj
) and selObj
== context
.active_object
:
263 for mod
in selObj
.modifiers
:
264 if "BTool_" in mod
.name
:
265 objDeleteList
.append(mod
.object)
267 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
268 except: # if fails the means it is multiuser data
269 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
270 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
271 del selObj
["BoolToolRoot"]
273 for obj
in context
.scene
.objects
:
275 for mod
in obj
.modifiers
:
276 # do not delete brush that is used by another canvas
277 if mod
.type == "BOOLEAN" and mod
.object in objDeleteList
:
278 objDeleteList
.remove(mod
.object) # remove it from deletion
280 bpy
.ops
.object.select_all(action
="DESELECT")
281 for obj
in objDeleteList
:
283 bpy
.ops
.object.delete()
286 # Apply This Brush to the Canvas
287 def ApplyThisBrush(context
, brush
):
288 for obj
in context
.scene
.objects
:
290 for mod
in obj
.modifiers
:
291 if "BTool_" + brush
.name
in mod
.name
:
293 context
.view_layer
.objects
.active
= obj
295 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
296 except: # if fails the means it is multiuser data
297 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
298 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
299 bpy
.ops
.object.select_all(action
="TOGGLE")
300 bpy
.ops
.object.select_all(action
="DESELECT")
303 brush
.select_set(True)
304 # bpy.ops.object.delete()
307 # ------------------ Bool Tool OPERATORS --------------------------------------
310 # class BTool_DrawPolyBrush(Operator):
311 # bl_idname = "btool.draw_polybrush"
312 # bl_label = "Draw Poly Brush"
314 # "Draw Polygonal Mask, can be applied to Canvas > Brush or Directly\n"
315 # "Note: ESC to Cancel, Enter to Apply, Right Click to erase the Lines"
319 # store_cont_draw = False
322 # def poll(cls, context):
323 # return context.active_object is not None
325 # def set_cont_draw(self, context, start=False):
326 # # store / restore GP continuous drawing (see T52321)
327 # scene = context.scene
328 # tool_settings = scene.tool_settings
329 # continuous = tool_settings.use_gpencil_continuous_drawing
331 # self.store_cont_draw = continuous
332 # tool_settings.use_gpencil_continuous_drawing = True
334 # tool_settings.use_gpencil_continuous_drawing = self.store_cont_draw
336 # def modal(self, context, event):
338 # actObj = bpy.context.active_object
339 # if self.count == 1:
340 # actObj.select_set(True)
341 # bpy.ops.gpencil.draw("INVOKE_DEFAULT", mode="DRAW_POLY")
343 # if event.type == "RIGHTMOUSE":
344 # # use this to pass to the Grease Pencil eraser (see T52321)
347 # if event.type in {"RET", "NUMPAD_ENTER"}:
349 # bpy.ops.gpencil.convert(type="POLY")
350 # self.set_cont_draw(context)
352 # for obj in context.selected_objects:
353 # if obj.type == "CURVE":
354 # obj.name = "PolyDraw"
355 # bpy.context.view_layer.objects.active = obj
356 # bpy.ops.object.select_all(action="DESELECT")
357 # obj.select_set(True)
358 # bpy.ops.object.convert(target="MESH")
359 # bpy.ops.object.mode_set(mode="EDIT")
360 # bpy.ops.mesh.select_all(action="SELECT")
361 # bpy.ops.mesh.edge_face_add()
362 # bpy.ops.mesh.flip_normals()
363 # bpy.ops.object.mode_set(mode="OBJECT")
364 # bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
365 # bpy.ops.object.modifier_add(type="SOLIDIFY")
366 # for mod in obj.modifiers:
367 # if mod.name == "Solidify":
368 # mod.name = "BTool_PolyBrush"
371 # obj["BoolToolPolyBrush"] = True
373 # bpy.ops.object.select_all(action="DESELECT")
374 # bpy.context.view_layer.objects.active = actObj
375 # bpy.context.view_layer.update()
376 # actObj.select_set(True)
377 # obj.select_set(True)
379 # bpy.context.view_layer.grease_pencil.clear()
380 # bpy.ops.gpencil.data_unlink()
382 # return {"FINISHED"}
384 # if event.type == "ESC":
385 # bpy.ops.ed.undo() # remove o Grease Pencil
386 # self.set_cont_draw(context)
388 # self.report({"INFO"}, "Draw Poly Brush: Operation Cancelled by User")
389 # return {"CANCELLED"}
391 # return {"RUNNING_MODAL"}
393 # def invoke(self, context, event):
395 # self.set_cont_draw(context, start=True)
396 # context.window_manager.modal_handler_add(self)
397 # return {"RUNNING_MODAL"}
399 # self.report({"WARNING"}, "No active object, could not finish")
400 # return {"CANCELLED"}
404 class BTool_FastTransform(Operator
):
405 bl_idname
= "btool.fast_transform"
406 bl_label
= "Fast Transform"
407 bl_description
= "Enable Fast Transform"
409 operator
: StringProperty("")
413 def modal(self
, context
, event
):
415 actObj
= bpy
.context
.active_object
416 useWire
= bpy
.context
.preferences
.addons
[__name__
].preferences
.use_wire
419 if isBrush(actObj
) and actObj
["BoolTool_FTransform"] == "True":
420 EnableThisBrush(bpy
.context
, "False")
422 actObj
.display_type
= "WIRE"
424 actObj
.display_type
= "BOUNDS"
426 if self
.operator
== "Translate":
427 bpy
.ops
.transform
.translate("INVOKE_DEFAULT")
428 if self
.operator
== "Rotate":
429 bpy
.ops
.transform
.rotate("INVOKE_DEFAULT")
430 if self
.operator
== "Scale":
431 bpy
.ops
.transform
.resize("INVOKE_DEFAULT")
433 if event
.type == "LEFTMOUSE":
435 EnableThisBrush(bpy
.context
, "True")
436 actObj
.display_type
= "WIRE"
439 if event
.type in {"RIGHTMOUSE", "ESC"}:
441 EnableThisBrush(bpy
.context
, "True")
442 actObj
.display_type
= "WIRE"
445 return {"RUNNING_MODAL"}
447 def invoke(self
, context
, event
):
449 context
.window_manager
.modal_handler_add(self
)
450 return {"RUNNING_MODAL"}
452 self
.report({"WARNING"}, "No active object, could not finish")
456 # ------------------- Bool Tool OPERATOR CLASSES --------------------------------------------------------
460 # --------------------------------------------------------------------------------------
465 def execute(self
, context
):
466 Operation(context
, self
.mode
)
469 def invoke(self
, context
, event
):
470 if len(context
.selected_objects
) < 2:
471 self
.report({"ERROR"}, "At least two objects must be selected")
474 return self
.execute(context
)
477 class BTool_Union(Operator
, BToolSetup
):
478 bl_idname
= "btool.boolean_union"
479 bl_label
= "Brush Union"
480 bl_description
= "This operator add a union brush to a canvas"
481 bl_options
= {"REGISTER", "UNDO"}
486 class BTool_Inters(Operator
, BToolSetup
):
487 bl_idname
= "btool.boolean_inters"
488 bl_label
= "Brush Intersection"
489 bl_description
= "This operator add a intersect brush to a canvas"
490 bl_options
= {"REGISTER", "UNDO"}
495 class BTool_Diff(Operator
, BToolSetup
):
496 bl_idname
= "btool.boolean_diff"
497 bl_label
= "Brush Difference"
498 bl_description
= "This operator add a difference brush to a canvas"
499 bl_options
= {"REGISTER", "UNDO"}
504 class BTool_Slice(Operator
, BToolSetup
):
505 bl_idname
= "btool.boolean_slice"
506 bl_label
= "Brush Slice"
507 bl_description
= "This operator add a intersect brush to a canvas"
508 bl_options
= {"REGISTER", "UNDO"}
513 # Auto Boolean operators
514 # --------------------------------------------------------------------------------------
519 def objects_prepare(self
):
520 for ob
in bpy
.context
.selected_objects
:
521 if ob
.type != "MESH":
523 bpy
.ops
.object.make_single_user(object=True, obdata
=True)
524 bpy
.ops
.object.convert(target
="MESH")
526 def mesh_selection(self
, ob
, select_action
):
527 obj
= bpy
.context
.active_object
529 bpy
.context
.view_layer
.objects
.active
= ob
530 bpy
.ops
.object.mode_set(mode
="EDIT")
532 bpy
.ops
.mesh
.reveal()
533 bpy
.ops
.mesh
.select_all(action
=select_action
)
535 bpy
.ops
.object.mode_set(mode
="OBJECT")
536 bpy
.context
.view_layer
.objects
.active
= obj
538 def boolean_operation(self
):
539 obj
= bpy
.context
.active_object
540 obj
.select_set(False)
541 obs
= bpy
.context
.selected_objects
543 self
.mesh_selection(obj
, "DESELECT")
546 self
.mesh_selection(ob
, "SELECT")
547 self
.boolean_mod(obj
, ob
, self
.mode
)
551 def boolean_mod(self
, obj
, ob
, mode
, ob_delete
=True):
552 md
= obj
.modifiers
.new("Auto Boolean", "BOOLEAN")
553 md
.show_viewport
= False
557 override
= {"object": obj
}
558 bpy
.ops
.object.modifier_apply(override
, modifier
=md
.name
)
561 bpy
.data
.objects
.remove(ob
)
563 def execute(self
, context
):
564 self
.objects_prepare()
565 self
.boolean_operation()
568 def invoke(self
, context
, event
):
569 if len(context
.selected_objects
) < 2:
570 self
.report({"ERROR"}, "At least two objects must be selected")
573 return self
.execute(context
)
576 class OBJECT_OT_BoolTool_Auto_Union(Operator
, Auto_Boolean
):
577 bl_idname
= "object.booltool_auto_union"
578 bl_label
= "Bool Tool Union"
579 bl_description
= "Combine selected objects"
580 bl_options
= {"REGISTER", "UNDO"}
585 class OBJECT_OT_BoolTool_Auto_Difference(Operator
, Auto_Boolean
):
586 bl_idname
= "object.booltool_auto_difference"
587 bl_label
= "Bool Tool Difference"
588 bl_description
= "Subtract selected objects from active object"
589 bl_options
= {"REGISTER", "UNDO"}
594 class OBJECT_OT_BoolTool_Auto_Intersect(Operator
, Auto_Boolean
):
595 bl_idname
= "object.booltool_auto_intersect"
596 bl_label
= "Bool Tool Intersect"
597 bl_description
= "Keep only intersecting geometry"
598 bl_options
= {"REGISTER", "UNDO"}
603 class OBJECT_OT_BoolTool_Auto_Slice(Operator
, Auto_Boolean
):
604 bl_idname
= "object.booltool_auto_slice"
605 bl_label
= "Bool Tool Slice"
606 bl_description
= "Slice active object along the selected objects"
607 bl_options
= {"REGISTER", "UNDO"}
609 def execute(self
, context
):
610 space_data
= context
.space_data
611 is_local_view
= bool(space_data
.local_view
)
612 self
.objects_prepare()
614 ob1
= context
.active_object
615 ob1
.select_set(False)
616 self
.mesh_selection(ob1
, "DESELECT")
618 for ob2
in context
.selected_objects
:
620 self
.mesh_selection(ob2
, "SELECT")
622 ob1_copy
= ob1
.copy()
623 ob1_copy
.data
= ob1
.data
.copy()
625 for coll
in ob1
.users_collection
:
626 coll
.objects
.link(ob1_copy
)
629 ob1_copy
.local_view_set(space_data
, True)
631 self
.boolean_mod(ob1
, ob2
, "DIFFERENCE", ob_delete
=False)
632 self
.boolean_mod(ob1_copy
, ob2
, "INTERSECT")
633 ob1_copy
.select_set(True)
635 context
.view_layer
.objects
.active
= ob1_copy
640 # Utils Class ---------------------------------------------------------------
642 # Find the Brush Selected in Three View
643 class BTool_FindBrush(Operator
):
644 bl_idname
= "btool.find_brush"
646 bl_description
= "Find the selected brush"
648 obj
: StringProperty("")
651 def poll(cls
, context
):
652 return context
.active_object
is not None
654 def execute(self
, context
):
655 for ob
in bpy
.context
.view_layer
.objects
:
656 if ob
.name
== self
.obj
:
657 bpy
.ops
.object.select_all(action
="TOGGLE")
658 bpy
.ops
.object.select_all(action
="DESELECT")
659 bpy
.context
.view_layer
.objects
.active
= ob
660 ob
.set_select(state
=True)
664 # Move The Modifier in The Stack Up or Down
665 class BTool_MoveStack(Operator
):
666 bl_idname
= "btool.move_stack"
668 bl_description
= "Move this Brush Up/Down in the Stack"
670 modif
: StringProperty("")
671 direction
: StringProperty("")
674 def poll(cls
, context
):
675 return context
.active_object
is not None
677 def execute(self
, context
):
678 if self
.direction
== "UP":
679 bpy
.ops
.object.modifier_move_up(modifier
=self
.modif
)
680 if self
.direction
== "DOWN":
681 bpy
.ops
.object.modifier_move_down(modifier
=self
.modif
)
685 # Enable or Disable a Brush in the Three View
686 class BTool_EnableBrush(Operator
):
687 bl_idname
= "btool.enable_brush"
689 bl_description
= "Removes all BoolTool config assigned to it"
691 thisObj
: StringProperty("")
694 def poll(cls
, context
):
695 return context
.active_object
is not None
697 def execute(self
, context
):
698 # in this case is just one object but the function accept more than one at once
699 EnableBrush(context
, [self
.thisObj
], context
.active_object
)
703 # Enable or Disable a Brush Directly
704 class BTool_EnableThisBrush(Operator
):
705 bl_idname
= "btool.enable_this_brush"
707 bl_description
= "Toggles this brush"
710 def poll(cls
, context
):
711 return context
.active_object
is not None
713 def execute(self
, context
):
714 EnableThisBrush(context
, "None")
718 # Enable or Disable a Brush Directly
719 class BTool_EnableFTransform(Operator
):
720 bl_idname
= "btool.enable_ftransf"
722 bl_description
= "Use Fast Transformations to improve speed"
725 def poll(cls
, context
):
726 return context
.active_object
is not None
728 def execute(self
, context
):
729 EnableFTransf(context
)
733 # Other Operations -------------------------------------------------------
735 # Remove a Brush or a Canvas
736 class BTool_Remove(Operator
):
737 bl_idname
= "btool.remove"
738 bl_label
= "Bool Tool Remove"
739 bl_description
= "Removes all BoolTool config assigned to it"
740 bl_options
= {"UNDO"}
742 thisObj
: StringProperty("")
743 Prop
: StringProperty("")
746 def poll(cls
, context
):
747 return context
.active_object
is not None
749 def execute(self
, context
):
750 Remove(context
, self
.thisObj
, self
.Prop
)
754 # Apply All to Canvas
755 class BTool_AllBrushToMesh(Operator
):
756 bl_idname
= "btool.to_mesh"
757 bl_label
= "Apply All Canvas"
758 bl_description
= "Apply all brushes of this canvas"
759 bl_options
= {"UNDO"}
762 def poll(cls
, context
):
763 return context
.active_object
is not None
765 def execute(self
, context
):
766 lists
= bpy
.context
.selected_objects
767 ApplyAll(context
, lists
)
771 # Apply This Brush to the Canvas
772 class BTool_BrushToMesh(Operator
):
773 bl_idname
= "btool.brush_to_mesh"
774 bl_label
= "Apply this Brush to Canvas"
775 bl_description
= "Apply this brush to the canvas"
776 bl_options
= {"UNDO"}
779 def poll(cls
, context
):
781 if isBrush(context
.active_object
):
786 def execute(self
, context
):
787 ApplyThisBrush(context
, bpy
.context
.active_object
)
792 # Apply This Brush To Mesh
795 # ------------------- MENU CLASSES ------------------------------
798 class VIEW3D_MT_booltool_menu(Menu
):
799 bl_label
= "Bool Tool"
800 bl_idname
= "VIEW3D_MT_booltool_menu"
802 def draw(self
, context
):
805 layout
.label(text
="Auto Boolean")
806 layout
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
807 layout
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
808 layout
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
809 layout
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
813 layout
.label(text
="Brush Boolean")
814 layout
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
815 layout
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
816 layout
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
817 layout
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
819 if isCanvas(context
.active_object
):
821 layout
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
822 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove All")
826 if isBrush(context
.active_object
):
828 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
829 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove Brush")
834 def VIEW3D_BoolTool_Menu(self
, context
):
835 self
.layout
.menu(VIEW3D_MT_booltool_menu
.bl_idname
)
838 # ---------------- Toolshelf: Tools ---------------------
841 class VIEW3D_PT_booltool_tools(Panel
):
842 bl_category
= "objectmode"
843 bl_label
= "Bool Tool"
844 bl_space_type
= "VIEW_3D"
845 bl_region_type
= "UI"
846 bl_context
= "objectmode"
847 bl_options
= {'DEFAULT_CLOSED'}
850 def poll(cls
, context
):
851 return context
.active_object
is not None
853 def draw(self
, context
):
856 col
= layout
.column(align
=True)
857 col
.label(text
="Auto Boolean")
858 col
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
859 col
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
860 col
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
861 col
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
863 col
= layout
.column(align
=True)
864 col
.label(text
="Brush Boolean")
865 col
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
866 col
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
867 col
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
868 col
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
870 # TODO Draw Poly Brush
873 # col = main.column(align=True)
874 # col.label(text="Draw:", icon="MESH_CUBE")
876 # col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
879 # ---------- Toolshelf: Properties --------------------------------------------------------
882 class VIEW3D_PT_booltool_config(Panel
):
883 bl_category
= "objectmode"
884 bl_label
= "Properties"
885 bl_space_type
= "VIEW_3D"
886 bl_region_type
= "UI"
887 bl_context
= "objectmode"
888 bl_parent_id
= "VIEW3D_PT_booltool_tools"
891 def poll(cls
, context
):
892 actObj
= context
.active_object
893 return isCanvas(actObj
) or isBrush(actObj
) # or isPolyBrush(actObj)
895 def draw(self
, context
):
897 actObj
= context
.active_object
899 row
= layout
.row(align
=True)
903 row
.label(text
="CANVAS", icon
="MESH_GRID")
905 row
.prop(context
.scene
, "BoolHide", text
="Hide Bool objects")
906 row
= layout
.row(align
=True)
907 row
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
909 row
= layout
.row(align
=True)
910 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove All")
919 if actObj
["BoolToolBrush"] == "DIFFERENCE":
920 icon
= "SELECT_SUBTRACT"
921 elif actObj
["BoolToolBrush"] == "UNION":
922 icon
= "SELECT_EXTEND"
923 elif actObj
["BoolToolBrush"] == "INTERSECT":
924 icon
= "SELECT_INTERSECT"
925 elif actObj
["BoolToolBrush"] == "SLICE":
926 icon
= "SELECT_DIFFERENCE"
928 row
.label(text
="BRUSH", icon
=icon
)
930 if actObj
["BoolTool_FTransform"] == "True":
938 row
= layout
.row(align
=True)
939 row
.operator(BTool_EnableFTransform
.bl_idname
, text
="Fast Vis", icon
=icon
)
940 row
.operator(BTool_EnableThisBrush
.bl_idname
, text
="Enable", icon
="HIDE_OFF")
942 row
.operator(BTool_EnableThisBrush
.bl_idname
, icon
="HIDE_OFF")
944 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
945 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove Brush")
950 # if isPolyBrush(actObj):
951 # layout.label(text="POLY BRUSH", icon="LINE_DATA")
952 # mod = actObj.modifiers["BTool_PolyBrush"]
953 # layout.prop(mod, "thickness", text="Size")
956 # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
959 class VIEW3D_PT_booltool_bviewer(Panel
):
960 bl_category
= "objectmode"
961 bl_label
= "Brush Viewer"
962 bl_space_type
= "VIEW_3D"
963 bl_region_type
= "UI"
964 bl_context
= "objectmode"
965 bl_parent_id
= "VIEW3D_PT_booltool_tools"
968 def poll(cls
, context
):
969 actObj
= bpy
.context
.active_object
976 def draw(self
, context
):
978 actObj
= bpy
.context
.active_object
982 for mod
in actObj
.modifiers
:
983 container
= self
.layout
.box()
984 row
= container
.row(align
=True)
986 if "BTool_" in mod
.name
:
988 if mod
.operation
== "DIFFERENCE":
989 icon
= "SELECT_SUBTRACT"
990 elif mod
.operation
== "UNION":
991 icon
= "SELECT_EXTEND"
992 elif mod
.operation
== "INTERSECT":
993 icon
= "SELECT_INTERSECT"
994 elif mod
.operation
== "SLICE":
995 icon
= "SELECT_DIFFERENCE"
997 objSelect
= row
.operator("btool.find_brush", text
=mod
.object.name
, icon
=icon
, emboss
=False)
998 objSelect
.obj
= mod
.object.name
1000 EnableIcon
= "RESTRICT_VIEW_ON"
1001 if mod
.show_viewport
:
1002 EnableIcon
= "RESTRICT_VIEW_OFF"
1003 Enable
= row
.operator(BTool_EnableBrush
.bl_idname
, icon
=EnableIcon
, emboss
=False)
1004 Enable
.thisObj
= mod
.object.name
1006 Remove
= row
.operator("btool.remove", text
="", icon
="X", emboss
=False)
1007 Remove
.thisObj
= mod
.object.name
1008 Remove
.Prop
= "THIS"
1011 row
.label(text
=mod
.name
)
1013 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1017 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1019 Dw
.direction
= "DOWN"
1022 # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
1027 ("Menu", "Ctrl Shift B"),
1029 ("Auto Operators", None),
1030 ("Difference", "Ctrl Shift Num -"),
1031 ("Union", "Ctrl Shift Num +"),
1032 ("Intersect", "Ctrl Shift Num *"),
1033 ("Slice", "Ctrl Shift Num /"),
1035 ("Brush Operators", None),
1036 ("Difference", "Ctrl Num -"),
1037 ("Union", "Ctrl Num +"),
1038 ("Intersect", "Ctrl Num *"),
1039 ("Slice", "Ctrl Num /"),
1040 ("Brush To Mesh", "Ctrl Num Enter"),
1041 ("All Brushes To Mesh", "Ctrl Shift Num Enter"),
1045 def UpdateBoolTool_Pref(self
, context
):
1046 if self
.fast_transform
:
1052 # Define Panel classes for updating
1054 VIEW3D_PT_booltool_tools
,
1055 VIEW3D_PT_booltool_config
,
1056 VIEW3D_PT_booltool_bviewer
,
1060 def update_panels(self
, context
):
1062 for panel
in panels
:
1063 if "bl_rna" in panel
.__dict
__:
1064 bpy
.utils
.unregister_class(panel
)
1066 for panel
in panels
:
1067 panel
.bl_category
= context
.preferences
.addons
[
1069 ].preferences
.category
1070 bpy
.utils
.register_class(panel
)
1072 except Exception as e
:
1073 message
= "Bool Tool: Updating Panel locations has failed"
1074 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1077 def icon_tria(prop
):
1083 class PREFS_BoolTool_Props(AddonPreferences
):
1084 bl_idname
= __name__
1086 fast_transform
: BoolProperty(
1087 name
="Fast Transformations",
1088 update
=UpdateBoolTool_Pref
,
1089 description
="Replace the Transform HotKeys (G,R,S)\n"
1090 "for a custom version that can optimize the visualization of Brushes",
1092 use_wire
: BoolProperty(
1093 name
="Display As Wireframe",
1094 description
="Display brush as wireframe instead of bounding box",
1096 category
: StringProperty(
1098 description
="Set sidebar tab name",
1100 update
=update_panels
,
1102 show_shortcuts
: BoolProperty(name
="Shortcuts")
1104 def draw(self
, context
):
1105 layout
= self
.layout
1106 layout
.use_property_split
= True
1107 layout
.use_property_decorate
= False
1109 col
= layout
.column()
1110 col
.prop(self
, "category")
1111 col
.prop(self
, "fast_transform")
1112 col
.prop(self
, "use_wire")
1114 col
= layout
.column()
1116 col
.use_property_split
= False
1117 col
.prop(self
, "show_shortcuts", icon
=icon_tria(self
.show_shortcuts
))
1119 if self
.show_shortcuts
:
1121 col
= layout
.column()
1123 for key_name
, key_comb
in shortcut_list
:
1124 if key_comb
is None:
1126 col
.label(text
=key_name
)
1128 row
= col
.row(align
=True)
1130 row
.box().label(text
=key_name
)
1131 row
.box().label(text
=key_comb
)
1134 # ------------------- Class List ------------------------------------------------
1137 PREFS_BoolTool_Props
,
1138 VIEW3D_MT_booltool_menu
,
1139 VIEW3D_PT_booltool_tools
,
1140 VIEW3D_PT_booltool_config
,
1141 VIEW3D_PT_booltool_bviewer
,
1142 OBJECT_OT_BoolTool_Auto_Union
,
1143 OBJECT_OT_BoolTool_Auto_Difference
,
1144 OBJECT_OT_BoolTool_Auto_Intersect
,
1145 OBJECT_OT_BoolTool_Auto_Slice
,
1150 # TODO Draw Poly Brush
1151 # BTool_DrawPolyBrush,
1153 BTool_AllBrushToMesh
,
1158 BTool_EnableThisBrush
,
1159 BTool_EnableFTransform
,
1160 BTool_FastTransform
,
1164 # ------------------- REGISTER ------------------------------------------------
1167 addon_keymapsFastT
= []
1170 # Fast Transform HotKeys Register
1171 def RegisterFastT():
1172 wm
= bpy
.context
.window_manager
1173 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
="Object Mode", space_type
="EMPTY")
1175 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "G", "PRESS")
1176 kmi
.properties
.operator
= "Translate"
1177 addon_keymapsFastT
.append((km
, kmi
))
1179 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "R", "PRESS")
1180 kmi
.properties
.operator
= "Rotate"
1181 addon_keymapsFastT
.append((km
, kmi
))
1183 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "S", "PRESS")
1184 kmi
.properties
.operator
= "Scale"
1185 addon_keymapsFastT
.append((km
, kmi
))
1188 # Fast Transform HotKeys UnRegister
1189 def UnRegisterFastT():
1190 wm
= bpy
.context
.window_manager
1191 kc
= wm
.keyconfigs
.addon
1193 for km
, kmi
in addon_keymapsFastT
:
1194 km
.keymap_items
.remove(kmi
)
1196 addon_keymapsFastT
.clear()
1201 bpy
.utils
.register_class(cls
)
1202 update_panels(None, bpy
.context
)
1205 bpy
.types
.Scene
.BoolHide
= BoolProperty(
1207 description
="Hide boolean objects",
1208 update
=update_BoolHide
,
1210 bpy
.types
.VIEW3D_MT_object
.append(VIEW3D_BoolTool_Menu
)
1212 wm
= bpy
.context
.window_manager
1213 kc
= wm
.keyconfigs
.addon
1215 # create the boolean menu hotkey
1217 km
= kc
.keymaps
.new(name
="Object Mode")
1219 kmi
= km
.keymap_items
.new("wm.call_menu", "B", "PRESS", ctrl
=True, shift
=True)
1220 kmi
.properties
.name
= "VIEW3D_MT_booltool_menu"
1221 addon_keymaps
.append((km
, kmi
))
1224 kmi
= km
.keymap_items
.new(BTool_Union
.bl_idname
, "NUMPAD_PLUS", "PRESS", ctrl
=True)
1225 addon_keymaps
.append((km
, kmi
))
1226 kmi
= km
.keymap_items
.new(BTool_Diff
.bl_idname
, "NUMPAD_MINUS", "PRESS", ctrl
=True)
1227 addon_keymaps
.append((km
, kmi
))
1228 kmi
= km
.keymap_items
.new(BTool_Inters
.bl_idname
, "NUMPAD_ASTERIX", "PRESS", ctrl
=True)
1229 addon_keymaps
.append((km
, kmi
))
1230 kmi
= km
.keymap_items
.new(BTool_Slice
.bl_idname
, "NUMPAD_SLASH", "PRESS", ctrl
=True)
1231 addon_keymaps
.append((km
, kmi
))
1232 kmi
= km
.keymap_items
.new(BTool_BrushToMesh
.bl_idname
, "NUMPAD_ENTER", "PRESS", ctrl
=True)
1233 addon_keymaps
.append((km
, kmi
))
1234 kmi
= km
.keymap_items
.new(
1235 BTool_AllBrushToMesh
.bl_idname
,
1241 addon_keymaps
.append((km
, kmi
))
1244 kmi
= km
.keymap_items
.new(
1245 OBJECT_OT_BoolTool_Auto_Union
.bl_idname
,
1251 addon_keymaps
.append((km
, kmi
))
1252 kmi
= km
.keymap_items
.new(
1253 OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
,
1259 addon_keymaps
.append((km
, kmi
))
1260 kmi
= km
.keymap_items
.new(
1261 OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
,
1267 addon_keymaps
.append((km
, kmi
))
1268 kmi
= km
.keymap_items
.new(
1269 OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
,
1275 addon_keymaps
.append((km
, kmi
))
1280 # remove keymaps when add-on is deactivated
1281 wm
= bpy
.context
.window_manager
1282 kc
= wm
.keyconfigs
.addon
1284 for km
, kmi
in addon_keymaps
:
1285 km
.keymap_items
.remove(kmi
)
1287 addon_keymaps
.clear()
1289 bpy
.types
.VIEW3D_MT_object
.remove(VIEW3D_BoolTool_Menu
)
1290 del bpy
.types
.Scene
.BoolHide
1293 bpy
.utils
.unregister_class(cls
)
1296 if __name__
== "__main__":