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",
32 from bpy
.types
import (
38 from bpy
.props
import (
44 # ------------------- Bool Tool FUNCTIONS -------------------------
47 # Hide boolean objects
48 def update_BoolHide(self
, context
):
49 ao
= context
.view_layer
.objects
.active
50 objs
= [i
.object for i
in ao
.modifiers
if i
.type == "BOOLEAN"]
51 hide_state
= context
.scene
.BoolHide
54 o
.hide_viewport
= hide_state
60 if _obj
["BoolToolRoot"]:
66 # Object is a Brush Tool Bool
69 if _obj
["BoolToolBrush"]:
75 # Object is a Poly Brush Tool Bool collection
76 def isPolyBrush(_obj
):
78 if _obj
["BoolToolPolyBrush"]:
84 def BT_ObjectByName(obj
):
85 for ob
in bpy
.context
.view_layer
.objects
:
86 if isCanvas(ob
) or isBrush(ob
):
92 for ob
in bpy
.context
.view_layer
.objects
:
94 for mod
in ob
.modifiers
:
95 if "BTool_" in mod
.name
:
96 if obj
.name
in mod
.name
:
101 preferences
= bpy
.context
.preferences
102 addons
= preferences
.addons
103 addon_prefs
= addons
[__name__
].preferences
104 if addon_prefs
.fast_transform
:
110 def ConvertToMesh(obj
):
111 act
= bpy
.context
.view_layer
.objects
.active
112 bpy
.context
.view_layer
.objects
.active
= obj
113 bpy
.ops
.object.convert(target
="MESH")
114 bpy
.context
.view_layer
.objects
.active
= act
117 # Do the Union, Difference and Intersection Operations with a Brush
118 def Operation(context
, _operation
):
119 prefs
= bpy
.context
.preferences
.addons
[__name__
].preferences
120 useWire
= prefs
.use_wire
122 for selObj
in bpy
.context
.selected_objects
:
124 selObj
!= context
.active_object
and
125 (selObj
.type == "MESH" or selObj
.type == "CURVE")
127 if selObj
.type == "CURVE":
128 ConvertToMesh(selObj
)
129 actObj
= context
.active_object
130 selObj
.hide_render
= True
131 cyclesVis
= selObj
.cycles_visibility
134 selObj
.display_type
= "WIRE"
136 selObj
.display_type
= "BOUNDS"
138 cyclesVis
.camera
= False
139 cyclesVis
.diffuse
= False
140 cyclesVis
.glossy
= False
141 cyclesVis
.shadow
= False
142 cyclesVis
.transmission
= False
143 if _operation
== "SLICE":
144 # copies instance_collection property(empty), but group property is empty (users_group = None)
145 clone
= context
.active_object
.copy()
146 # clone.select_set(state=True)
147 context
.collection
.objects
.link(clone
)
148 sliceMod
= clone
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN") # add mod to clone obj
149 sliceMod
.object = selObj
150 sliceMod
.operation
= "DIFFERENCE"
151 clone
["BoolToolRoot"] = True
152 newMod
= actObj
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN")
153 newMod
.object = selObj
154 if _operation
== "SLICE":
155 newMod
.operation
= "INTERSECT"
157 newMod
.operation
= _operation
159 actObj
["BoolToolRoot"] = True
160 selObj
["BoolToolBrush"] = _operation
161 selObj
["BoolTool_FTransform"] = "False"
164 # Remove Obejcts form the BoolTool System
165 def Remove(context
, thisObj_name
, Prop
):
166 # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
167 actObj
= context
.active_object
170 def RemoveThis(_thisObj_name
):
171 for obj
in bpy
.context
.view_layer
.objects
:
172 # if it's the brush object
173 if obj
.name
== _thisObj_name
:
174 cyclesVis
= obj
.cycles_visibility
175 obj
.display_type
= "TEXTURED"
176 del obj
["BoolToolBrush"]
177 del obj
["BoolTool_FTransform"]
178 cyclesVis
.camera
= True
179 cyclesVis
.diffuse
= True
180 cyclesVis
.glossy
= True
181 cyclesVis
.shadow
= True
182 cyclesVis
.transmission
= True
184 # Remove it from the Canvas
185 for mod
in actObj
.modifiers
:
186 if "BTool_" in mod
.name
:
187 if _thisObj_name
in mod
.name
:
188 actObj
.modifiers
.remove(mod
)
191 RemoveThis(thisObj_name
)
193 # If the remove was called from the Properties:
195 # Remove the Brush Property
197 Canvas
= FindCanvas(actObj
)
199 for mod
in Canvas
.modifiers
:
200 if "BTool_" in mod
.name
:
201 if actObj
.name
in mod
.name
:
202 Canvas
.modifiers
.remove(mod
)
203 cyclesVis
= actObj
.cycles_visibility
204 actObj
.display_type
= "TEXTURED"
205 del actObj
["BoolToolBrush"]
206 del actObj
["BoolTool_FTransform"]
207 cyclesVis
.camera
= True
208 cyclesVis
.diffuse
= True
209 cyclesVis
.glossy
= True
210 cyclesVis
.shadow
= True
211 cyclesVis
.transmission
= True
214 for mod
in actObj
.modifiers
:
215 if "BTool_" in mod
.name
:
216 RemoveThis(mod
.object.name
)
219 # Toggle the Enable the Brush Object Property
220 def EnableBrush(context
, objList
, canvas
):
222 for mod
in canvas
.modifiers
:
223 if "BTool_" in mod
.name
and mod
.object.name
== obj
:
225 if mod
.show_viewport
:
226 mod
.show_viewport
= False
227 mod
.show_render
= False
229 mod
.show_viewport
= True
230 mod
.show_render
= True
233 # Find the Canvas and Enable this Brush
234 def EnableThisBrush(context
, set):
236 for obj
in bpy
.context
.view_layer
.objects
:
237 if obj
!= bpy
.context
.active_object
:
239 for mod
in obj
.modifiers
:
240 if "BTool_" in mod
.name
:
241 if mod
.object == bpy
.context
.active_object
:
244 for mod
in canvas
.modifiers
:
245 if "BTool_" in mod
.name
:
246 if mod
.object == bpy
.context
.active_object
:
248 if mod
.show_viewport
:
249 mod
.show_viewport
= False
250 mod
.show_render
= False
252 mod
.show_viewport
= True
253 mod
.show_render
= True
256 mod
.show_viewport
= True
258 mod
.show_viewport
= False
262 # Toggle the Fast Transform Property of the Active Brush
263 def EnableFTransf(context
):
264 actObj
= bpy
.context
.active_object
266 if actObj
["BoolTool_FTransform"] == "True":
267 actObj
["BoolTool_FTransform"] = "False"
269 actObj
["BoolTool_FTransform"] = "True"
273 # Apply All Brushes to the Canvas
274 def ApplyAll(context
, list):
277 if isCanvas(selObj
) and selObj
== context
.active_object
:
278 for mod
in selObj
.modifiers
:
279 if "BTool_" in mod
.name
:
280 objDeleteList
.append(mod
.object)
282 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
283 except: # if fails the means it is multiuser data
284 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
285 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
286 del selObj
["BoolToolRoot"]
288 for obj
in context
.scene
.objects
:
290 for mod
in obj
.modifiers
:
291 # do not delete brush that is used by another canvas
292 if mod
.type == "BOOLEAN" and mod
.object in objDeleteList
:
293 objDeleteList
.remove(mod
.object) # remove it from deletion
295 bpy
.ops
.object.select_all(action
="DESELECT")
296 for obj
in objDeleteList
:
297 obj
.select_set(state
=True)
298 bpy
.ops
.object.delete()
301 # Apply This Brush to the Canvas
302 def ApplyThisBrush(context
, brush
):
303 for obj
in context
.scene
.objects
:
305 for mod
in obj
.modifiers
:
306 if "BTool_" + brush
.name
in mod
.name
:
308 context
.view_layer
.objects
.active
= obj
310 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
311 except: # if fails the means it is multiuser data
312 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
313 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
314 bpy
.ops
.object.select_all(action
="TOGGLE")
315 bpy
.ops
.object.select_all(action
="DESELECT")
318 brush
.select_set(state
=True)
319 # bpy.ops.object.delete()
322 # ------------------ Bool Tool OPERATORS --------------------------------------
325 class BTool_DrawPolyBrush(Operator
):
326 bl_idname
= "btool.draw_polybrush"
327 bl_label
= "Draw Poly Brush"
329 "Draw Polygonal Mask, can be applied to Canvas > Brush or Directly\n"
330 "Note: ESC to Cancel, Enter to Apply, Right Click to erase the Lines"
334 store_cont_draw
= False
337 def poll(cls
, context
):
338 return context
.active_object
is not None
340 def set_cont_draw(self
, context
, start
=False):
341 # store / restore GP continuous drawing (see T52321)
342 scene
= context
.scene
343 tool_settings
= scene
.tool_settings
344 continuous
= tool_settings
.use_gpencil_continuous_drawing
346 self
.store_cont_draw
= continuous
347 tool_settings
.use_gpencil_continuous_drawing
= True
349 tool_settings
.use_gpencil_continuous_drawing
= self
.store_cont_draw
351 def modal(self
, context
, event
):
353 actObj
= bpy
.context
.active_object
355 actObj
.select_set(state
=True)
356 bpy
.ops
.gpencil
.draw("INVOKE_DEFAULT", mode
="DRAW_POLY")
358 if event
.type == "RIGHTMOUSE":
359 # use this to pass to the Grease Pencil eraser (see T52321)
362 if event
.type in {"RET", "NUMPAD_ENTER"}:
364 bpy
.ops
.gpencil
.convert(type="POLY")
365 self
.set_cont_draw(context
)
367 for obj
in context
.selected_objects
:
368 if obj
.type == "CURVE":
369 obj
.name
= "PolyDraw"
370 bpy
.context
.view_layer
.objects
.active
= obj
371 bpy
.ops
.object.select_all(action
="DESELECT")
372 obj
.select_set(state
=True)
373 bpy
.ops
.object.convert(target
="MESH")
374 bpy
.ops
.object.mode_set(mode
="EDIT")
375 bpy
.ops
.mesh
.select_all(action
="SELECT")
376 bpy
.ops
.mesh
.edge_face_add()
377 bpy
.ops
.mesh
.flip_normals()
378 bpy
.ops
.object.mode_set(mode
="OBJECT")
379 bpy
.ops
.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
380 bpy
.ops
.object.modifier_add(type="SOLIDIFY")
381 for mod
in obj
.modifiers
:
382 if mod
.name
== "Solidify":
383 mod
.name
= "BTool_PolyBrush"
386 obj
["BoolToolPolyBrush"] = True
388 bpy
.ops
.object.select_all(action
="DESELECT")
389 bpy
.context
.view_layer
.objects
.active
= actObj
390 bpy
.context
.view_layer
.update()
391 actObj
.select_set(state
=True)
392 obj
.select_set(state
=True)
394 bpy
.context
.view_layer
.grease_pencil
.clear()
395 bpy
.ops
.gpencil
.data_unlink()
399 if event
.type == "ESC":
400 bpy
.ops
.ed
.undo() # remove o Grease Pencil
401 self
.set_cont_draw(context
)
403 self
.report({"INFO"}, "Draw Poly Brush: Operation Cancelled by User")
406 return {"RUNNING_MODAL"}
408 def invoke(self
, context
, event
):
410 self
.set_cont_draw(context
, start
=True)
411 context
.window_manager
.modal_handler_add(self
)
412 return {"RUNNING_MODAL"}
414 self
.report({"WARNING"}, "No active object, could not finish")
419 class BTool_FastTransform(Operator
):
420 bl_idname
= "btool.fast_transform"
421 bl_label
= "Fast Transform"
422 bl_description
= "Enable Fast Transform"
424 operator
: StringProperty("")
428 def modal(self
, context
, event
):
430 actObj
= bpy
.context
.active_object
431 useWire
= bpy
.context
.preferences
.addons
[__name__
].preferences
.use_wire
434 if isBrush(actObj
) and actObj
["BoolTool_FTransform"] == "True":
435 EnableThisBrush(bpy
.context
, "False")
437 actObj
.display_type
= "WIRE"
439 actObj
.display_type
= "BOUNDS"
441 if self
.operator
== "Translate":
442 bpy
.ops
.transform
.translate("INVOKE_DEFAULT")
443 if self
.operator
== "Rotate":
444 bpy
.ops
.transform
.rotate("INVOKE_DEFAULT")
445 if self
.operator
== "Scale":
446 bpy
.ops
.transform
.resize("INVOKE_DEFAULT")
448 if event
.type == "LEFTMOUSE":
450 EnableThisBrush(bpy
.context
, "True")
451 actObj
.display_type
= "WIRE"
454 if event
.type in {"RIGHTMOUSE", "ESC"}:
456 EnableThisBrush(bpy
.context
, "True")
457 actObj
.display_type
= "WIRE"
460 return {"RUNNING_MODAL"}
462 def invoke(self
, context
, event
):
464 context
.window_manager
.modal_handler_add(self
)
465 return {"RUNNING_MODAL"}
467 self
.report({"WARNING"}, "No active object, could not finish")
471 # ------------------- Bool Tool OPERATOR CLASSES --------------------------------------------------------
473 # Brush Operators --------------------------------------------
475 # Boolean Union Operator
476 class BTool_Union(Operator
):
477 bl_idname
= "btool.boolean_union"
478 bl_label
= "Brush Union"
479 bl_description
= "This operator add a union brush to a canvas"
480 bl_options
= {"REGISTER", "UNDO"}
483 def poll(cls
, context
):
484 return context
.active_object
is not None
486 def execute(self
, context
):
487 Operation(context
, "UNION")
491 # Boolean Intersection Operator
492 class BTool_Inters(Operator
):
493 bl_idname
= "btool.boolean_inters"
494 bl_label
= "Brush Intersection"
495 bl_description
= "This operator add a intersect brush to a canvas"
496 bl_options
= {"REGISTER", "UNDO"}
499 def poll(cls
, context
):
500 return context
.active_object
is not None
502 def execute(self
, context
):
503 Operation(context
, "INTERSECT")
507 # Boolean Difference Operator
508 class BTool_Diff(Operator
):
509 bl_idname
= "btool.boolean_diff"
510 bl_label
= "Brush Difference"
511 bl_description
= "This operator add a difference brush to a canvas"
512 bl_options
= {"REGISTER", "UNDO"}
515 def poll(cls
, context
):
516 return context
.active_object
is not None
518 def execute(self
, context
):
519 Operation(context
, "DIFFERENCE")
523 # Boolean Slices Operator
524 class BTool_Slice(Operator
):
525 bl_idname
= "btool.boolean_slice"
526 bl_label
= "Brush Slice"
527 bl_description
= "This operator add a intersect brush to a canvas"
528 bl_options
= {"REGISTER", "UNDO"}
531 def poll(cls
, context
):
532 return context
.active_object
is not None
534 def execute(self
, context
):
535 Operation(context
, "SLICE")
539 # Auto Boolean operators
540 # --------------------------------------------------------------------------------------
544 def objects_prepare(self
):
545 for ob
in bpy
.context
.selected_objects
:
546 if ob
.type != "MESH":
547 ob
.select_set(state
=False)
548 bpy
.ops
.object.make_single_user(object=True, obdata
=True)
549 bpy
.ops
.object.convert(target
="MESH")
551 def mesh_selection(self
, ob
, select_action
):
552 obj
= bpy
.context
.active_object
554 bpy
.context
.view_layer
.objects
.active
= ob
555 bpy
.ops
.object.mode_set(mode
="EDIT")
557 bpy
.ops
.mesh
.reveal()
558 bpy
.ops
.mesh
.select_all(action
=select_action
)
560 bpy
.ops
.object.mode_set(mode
="OBJECT")
561 bpy
.context
.view_layer
.objects
.active
= obj
563 def boolean_operation(self
):
564 obj
= bpy
.context
.active_object
565 obj
.select_set(state
=False)
566 obs
= bpy
.context
.selected_objects
568 self
.mesh_selection(obj
, "DESELECT")
570 self
.mesh_selection(ob
, "SELECT")
571 self
.boolean_mod(obj
, ob
, self
.mode
)
572 obj
.select_set(state
=True)
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 bpy
.ops
.object.modifier_apply(modifier
="Auto Boolean")
583 bpy
.data
.objects
.remove(ob
)
586 class OBJECT_OT_BoolTool_Auto_Union(Operator
, Auto_Boolean
):
587 bl_idname
= "object.booltool_auto_union"
588 bl_label
= "Bool Tool Union"
589 bl_description
= "Combine selected objects"
590 bl_options
= {"REGISTER", "UNDO"}
594 def execute(self
, context
):
595 self
.objects_prepare()
596 self
.boolean_operation()
600 class OBJECT_OT_BoolTool_Auto_Difference(Operator
, Auto_Boolean
):
601 bl_idname
= "object.booltool_auto_difference"
602 bl_label
= "Bool Tool Difference"
603 bl_description
= "Subtract selected objects from active object"
604 bl_options
= {"REGISTER", "UNDO"}
608 def execute(self
, context
):
609 self
.objects_prepare()
610 self
.boolean_operation()
614 class OBJECT_OT_BoolTool_Auto_Intersect(Operator
, Auto_Boolean
):
615 bl_idname
= "object.booltool_auto_intersect"
616 bl_label
= "Bool Tool Intersect"
617 bl_description
= "Keep only intersecting geometry"
618 bl_options
= {"REGISTER", "UNDO"}
622 def execute(self
, context
):
623 self
.objects_prepare()
624 self
.boolean_operation()
628 class OBJECT_OT_BoolTool_Auto_Slice(Operator
, Auto_Boolean
):
629 bl_idname
= "object.booltool_auto_slice"
630 bl_label
= "Bool Tool Slice"
631 bl_description
= "Slice active object along the selected object"
632 bl_options
= {"REGISTER", "UNDO"}
634 def execute(self
, context
):
635 self
.objects_prepare()
637 obj
= context
.active_object
638 obj
.select_set(state
=False)
639 ob
= context
.selected_objects
[0]
641 self
.mesh_selection(obj
, "DESELECT")
642 self
.mesh_selection(ob
, "SELECT")
644 obj_copy
= obj
.copy()
645 obj_copy
.data
= obj
.data
.copy()
646 context
.collection
.objects
.link(obj_copy
)
648 self
.boolean_mod(obj
, ob
, "DIFFERENCE", ob_delete
=False)
649 bpy
.context
.view_layer
.objects
.active
= obj_copy
650 self
.boolean_mod(obj_copy
, ob
, "INTERSECT")
651 obj_copy
.select_set(state
=True)
656 # Utils Class ---------------------------------------------------------------
658 # Find the Brush Selected in Three View
659 class BTool_FindBrush(Operator
):
660 bl_idname
= "btool.find_brush"
662 bl_description
= "Find the selected brush"
664 obj
: StringProperty("")
667 def poll(cls
, context
):
668 return context
.active_object
is not None
670 def execute(self
, context
):
671 for ob
in bpy
.context
.view_layer
.objects
:
672 if ob
.name
== self
.obj
:
673 bpy
.ops
.object.select_all(action
="TOGGLE")
674 bpy
.ops
.object.select_all(action
="DESELECT")
675 bpy
.context
.view_layer
.objects
.active
= ob
676 ob
.set_select(state
=True)
680 # Move The Modifier in The Stack Up or Down
681 class BTool_MoveStack(Operator
):
682 bl_idname
= "btool.move_stack"
684 bl_description
= "Move this Brush Up/Down in the Stack"
686 modif
: StringProperty("")
687 direction
: StringProperty("")
690 def poll(cls
, context
):
691 return context
.active_object
is not None
693 def execute(self
, context
):
694 if self
.direction
== "UP":
695 bpy
.ops
.object.modifier_move_up(modifier
=self
.modif
)
696 if self
.direction
== "DOWN":
697 bpy
.ops
.object.modifier_move_down(modifier
=self
.modif
)
701 # Enable or Disable a Brush in the Three View
702 class BTool_EnableBrush(Operator
):
703 bl_idname
= "btool.enable_brush"
705 bl_description
= "Removes all BoolTool config assigned to it"
707 thisObj
: StringProperty("")
710 def poll(cls
, context
):
711 return context
.active_object
is not None
713 def execute(self
, context
):
714 # in this case is just one object but the function accept more than one at once
715 EnableBrush(context
, [self
.thisObj
], context
.active_object
)
719 # Enable or Disable a Brush Directly
720 class BTool_EnableThisBrush(Operator
):
721 bl_idname
= "btool.enable_this_brush"
723 bl_description
= "Toggles this brush"
726 def poll(cls
, context
):
727 return context
.active_object
is not None
729 def execute(self
, context
):
730 EnableThisBrush(context
, "None")
734 # Enable or Disable a Brush Directly
735 class BTool_EnableFTransform(Operator
):
736 bl_idname
= "btool.enable_ftransf"
738 bl_description
= "Use Fast Transformations to improve speed"
741 def poll(cls
, context
):
742 return context
.active_object
is not None
744 def execute(self
, context
):
745 EnableFTransf(context
)
749 # Other Operations -------------------------------------------------------
751 # Remove a Brush or a Canvas
752 class BTool_Remove(Operator
):
753 bl_idname
= "btool.remove"
754 bl_label
= "Bool Tool Remove"
755 bl_description
= "Removes all BoolTool config assigned to it"
756 bl_options
= {"UNDO"}
758 thisObj
: StringProperty("")
759 Prop
: StringProperty("")
762 def poll(cls
, context
):
763 return context
.active_object
is not None
765 def execute(self
, context
):
766 Remove(context
, self
.thisObj
, self
.Prop
)
770 # Apply All to Canvas
771 class BTool_AllBrushToMesh(Operator
):
772 bl_idname
= "btool.to_mesh"
773 bl_label
= "Apply All Canvas"
774 bl_description
= "Apply all brushes of this canvas"
775 bl_options
= {"UNDO"}
778 def poll(cls
, context
):
779 return context
.active_object
is not None
781 def execute(self
, context
):
782 lists
= bpy
.context
.selected_objects
783 ApplyAll(context
, lists
)
787 # Apply This Brush to the Canvas
788 class BTool_BrushToMesh(Operator
):
789 bl_idname
= "btool.brush_to_mesh"
790 bl_label
= "Apply this Brush to Canvas"
791 bl_description
= "Apply this brush to the canvas"
792 bl_options
= {"UNDO"}
795 def poll(cls
, context
):
797 if isBrush(context
.active_object
):
802 def execute(self
, context
):
803 ApplyThisBrush(context
, bpy
.context
.active_object
)
808 # Apply This Brush To Mesh
811 # ------------------- MENU CLASSES ------------------------------
814 class VIEW3D_MT_booltool_menu(Menu
):
815 bl_label
= "BoolTool Operators"
816 bl_idname
= "VIEW3D_MT_booltool_menu"
818 def draw(self
, context
):
821 layout
.label(text
="Auto Boolean")
822 layout
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="PIVOT_ACTIVE")
823 layout
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="PIVOT_INDIVIDUAL")
824 layout
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="PIVOT_MEDIAN")
825 layout
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="PIVOT_MEDIAN")
829 layout
.label(text
="Brush Boolean")
830 layout
.operator(BTool_Diff
.bl_idname
, icon
="PIVOT_ACTIVE")
831 layout
.operator(BTool_Union
.bl_idname
, icon
="PIVOT_INDIVIDUAL")
832 layout
.operator(BTool_Inters
.bl_idname
, icon
="PIVOT_MEDIAN")
833 layout
.operator(BTool_Slice
.bl_idname
, icon
="PIVOT_MEDIAN")
835 if isCanvas(context
.active_object
):
837 layout
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
838 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove All")
842 if isBrush(context
.active_object
):
844 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
845 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove Brush")
850 def VIEW3D_BoolTool_Menu(self
, context
):
851 self
.layout
.menu(VIEW3D_MT_booltool_menu
.bl_idname
)
854 # ---------------- Toolshelf: Tools ---------------------
857 class VIEW3D_PT_booltool_tools(Panel
):
858 bl_category
= "objectmode"
859 bl_label
= "Bool Tool"
860 bl_space_type
= "VIEW_3D"
861 bl_region_type
= "UI"
862 bl_context
= "objectmode"
865 def poll(cls
, context
):
866 return context
.active_object
is not None
868 def draw(self
, context
):
870 obj
= context
.active_object
871 obs_len
= len(context
.selected_objects
)
874 row
.alignment
= "RIGHT"
876 row
.operator("wm.booltool_help", text
="", icon
="QUESTION")
878 main
= layout
.column(align
=True)
879 main
.enabled
= obj
.type == "MESH" and obs_len
> 0
881 col
= main
.column(align
=True)
882 col
.enabled
= obs_len
> 1
883 col
.label(text
="Auto Boolean", icon
="MODIFIER")
885 col
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="PIVOT_ACTIVE")
886 col
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="PIVOT_INDIVIDUAL")
887 col
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="PIVOT_MEDIAN")
888 sub
= col
.column(align
=True)
889 sub
.enabled
= obs_len
== 2
890 sub
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="PIVOT_MEDIAN")
894 col
= main
.column(align
=True)
895 col
.enabled
= obs_len
> 1
896 col
.label(text
="Brush Boolean", icon
="MODIFIER")
898 col
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="PIVOT_ACTIVE")
899 col
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="PIVOT_INDIVIDUAL")
900 col
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="PIVOT_MEDIAN")
901 col
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="PIVOT_MEDIAN")
903 # TODO Draw Poly Brush
906 # col = main.column(align=True)
907 # col.label(text="Draw:", icon="MESH_CUBE")
909 # col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
912 # ---------- Toolshelf: Properties --------------------------------------------------------
915 class VIEW3D_PT_booltool_config(Panel
):
916 bl_category
= "objectmode"
917 bl_label
= "Properties"
918 bl_space_type
= "VIEW_3D"
919 bl_region_type
= "UI"
920 bl_context
= "objectmode"
923 def poll(cls
, context
):
926 actObj
= bpy
.context
.active_object
927 if isCanvas(actObj
) or isBrush(actObj
) or isPolyBrush(actObj
):
931 def draw(self
, context
):
932 actObj
= bpy
.context
.active_object
936 row
= layout
.row(align
=True)
938 # CANVAS ---------------------------------------------------
940 row
.label(text
="CANVAS", icon
="MESH_GRID")
942 row
.prop(context
.scene
, "BoolHide", text
="Hide Bool objects")
943 row
= layout
.row(align
=True)
944 row
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
946 row
= layout
.row(align
=True)
947 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove All")
954 # BRUSH ------------------------------------------------------
957 if actObj
["BoolToolBrush"] == "UNION":
958 icon
= "PIVOT_INDIVIDUAL"
959 if actObj
["BoolToolBrush"] == "DIFFERENCE":
960 icon
= "PIVOT_MEDIAN"
961 if actObj
["BoolToolBrush"] == "INTERSECT":
962 icon
= "PIVOT_ACTIVE"
963 if actObj
["BoolToolBrush"] == "SLICE":
964 icon
= "PIVOT_MEDIAN"
966 row
= layout
.row(align
=True)
967 row
.label(text
="BRUSH", icon
=icon
)
970 if actObj
["BoolTool_FTransform"] == "True":
978 row
= layout
.row(align
=True)
979 row
.operator(BTool_EnableFTransform
.bl_idname
, text
="Fast Vis", icon
=icon
)
980 row
.operator(BTool_EnableThisBrush
.bl_idname
, text
="Enable", icon
="HIDE_OFF")
981 row
= layout
.row(align
=True)
983 row
.operator(BTool_EnableThisBrush
.bl_idname
, icon
="HIDE_OFF")
984 row
= layout
.row(align
=True)
986 if isPolyBrush(actObj
):
987 row
= layout
.row(align
=False)
988 row
.label(text
="POLY BRUSH", icon
="LINE_DATA")
989 mod
= actObj
.modifiers
["BTool_PolyBrush"]
990 row
= layout
.row(align
=False)
991 row
.prop(mod
, "thickness", text
="Size")
995 row
= layout
.row(align
=True)
996 row
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
997 row
= layout
.row(align
=True)
998 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove Brush")
1005 # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
1008 class VIEW3D_PT_booltool_bviewer(Panel
):
1009 bl_category
= "objectmode"
1010 bl_label
= "Brush Viewer"
1011 bl_space_type
= "VIEW_3D"
1012 bl_region_type
= "UI"
1013 bl_context
= "objectmode"
1016 def poll(cls
, context
):
1017 actObj
= bpy
.context
.active_object
1019 if isCanvas(actObj
):
1024 def draw(self
, context
):
1026 actObj
= bpy
.context
.active_object
1029 if isCanvas(actObj
):
1031 for mod
in actObj
.modifiers
:
1032 container
= self
.layout
.box()
1033 row
= container
.row(align
=True)
1035 if "BTool_" in mod
.name
:
1036 if mod
.operation
== "UNION":
1037 icon
= "PIVOT_INDIVIDUAL"
1038 if mod
.operation
== "DIFFERENCE":
1039 icon
= "PIVOT_MEDIAN"
1040 if mod
.operation
== "INTERSECT":
1041 icon
= "PIVOT_ACTIVE"
1042 if mod
.operation
== "SLICE":
1043 icon
= "PIVOT_MEDIAN"
1045 objSelect
= row
.operator("btool.find_brush", text
=mod
.object.name
, icon
=icon
, emboss
=False)
1046 objSelect
.obj
= mod
.object.name
1048 EnableIcon
= "RESTRICT_VIEW_ON"
1049 if mod
.show_viewport
:
1050 EnableIcon
= "RESTRICT_VIEW_OFF"
1051 Enable
= row
.operator(BTool_EnableBrush
.bl_idname
, icon
=EnableIcon
, emboss
=False)
1052 Enable
.thisObj
= mod
.object.name
1054 Remove
= row
.operator("btool.remove", icon
="CANCEL", emboss
=False)
1055 Remove
.thisObj
= mod
.object.name
1056 Remove
.Prop
= "THIS"
1059 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1063 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1065 Dw
.direction
= "DOWN"
1070 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1074 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1076 Dw
.direction
= "DOWN"
1079 # ------------------ BOOL TOOL Help ----------------------------
1082 class WM_OT_BoolTool_Help(Operator
):
1083 bl_idname
= "wm.booltool_help"
1084 bl_label
= "Bool Tool Help"
1085 bl_description
= "Help - click to read basic information"
1087 def draw(self
, context
):
1088 layout
= self
.layout
1090 layout
.label(text
="To use:")
1091 layout
.label(text
="Select two or more objects,")
1092 layout
.label(text
="choose one option from the panel")
1093 layout
.label(text
="or from the Ctrl + Shift + B menu")
1097 layout
.label(text
="Auto Boolean:")
1098 layout
.label(text
="Apply Boolean operation directly.")
1102 layout
.label(text
="Brush Boolean:")
1103 layout
.label(text
="Create a Boolean brush setup.")
1105 def execute(self
, context
):
1108 def invoke(self
, context
, event
):
1109 return context
.window_manager
.invoke_popup(self
, width
=220)
1112 # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
1115 def UpdateBoolTool_Pref(self
, context
):
1116 if self
.fast_transform
:
1122 # Add-ons Preferences Update Panel
1124 # Define Panel classes for updating
1126 VIEW3D_PT_booltool_tools
,
1127 VIEW3D_PT_booltool_config
,
1128 VIEW3D_PT_booltool_bviewer
,
1132 def update_panels(self
, context
):
1134 for panel
in panels
:
1135 if "bl_rna" in panel
.__dict
__:
1136 bpy
.utils
.unregister_class(panel
)
1138 for panel
in panels
:
1139 panel
.bl_category
= context
.preferences
.addons
[
1141 ].preferences
.category
1142 bpy
.utils
.register_class(panel
)
1144 except Exception as e
:
1145 message
= "Bool Tool: Updating Panel locations has failed"
1146 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1149 class PREFS_BoolTool_Props(AddonPreferences
):
1150 bl_idname
= __name__
1152 fast_transform
: BoolProperty(
1153 name
="Fast Transformations",
1155 update
=UpdateBoolTool_Pref
,
1156 description
="Replace the Transform HotKeys (G,R,S)\n"
1157 "for a custom version that can optimize the visualization of Brushes",
1159 make_vertex_groups
: BoolProperty(
1160 name
="Make Vertex Groups",
1162 description
="When Applying a Brush to the Object it will create\n"
1163 "a new vertex group for the new faces",
1165 make_boundary
: BoolProperty(
1166 name
="Make Boundary",
1168 description
="When Apply a Brush to the Object it will create a\n"
1169 "new vertex group of the boundary boolean area",
1171 use_wire
: BoolProperty(
1174 description
="Use The Wireframe Instead of Bounding Box for visualization",
1176 category
: StringProperty(
1177 name
="Tab Category",
1178 description
="Choose a name for the category of the panel",
1180 update
=update_panels
,
1182 Enable_Tab_01
: BoolProperty(default
=False)
1184 def draw(self
, context
):
1185 layout
= self
.layout
1188 split
= layout
.split(factor
=split_percent
)
1189 col
= split
.column()
1190 col
.label(text
="Tab Category:")
1191 col
= split
.column()
1192 col
.prop(self
, "category", text
="")
1194 split
= layout
.split(factor
=split_percent
)
1195 col
= split
.column()
1196 col
.label(text
="Experimental Features:")
1197 col
= split
.column()
1198 col
.prop(self
, "fast_transform")
1199 col
.prop(self
, "use_wire", text
="Use Wire Instead Of Bbox")
1203 layout
.prop(self
, "Enable_Tab_01", text
="Hot Keys", icon
="KEYINGSET")
1204 if self
.Enable_Tab_01
:
1208 col
.label(text
="Hotkey List:")
1209 col
.label(text
="Menu: Ctrl Shift B")
1213 col
.label(text
="Brush Operators:")
1214 col
.label(text
="Union: Ctrl Num +")
1215 col
.label(text
="Diff: Ctrl Num -")
1216 col
.label(text
="Intersect: Ctrl Num *")
1217 col
.label(text
="Slice: Ctrl Num /")
1221 col
.label(text
="Auto Operators:")
1222 col
.label(text
="Difference: Ctrl Shift Num -")
1223 col
.label(text
="Union: Ctrl Shift Num +")
1224 col
.label(text
="Intersect: Ctrl Shift Num *")
1225 col
.label(text
="Slice: Ctrl Shift Num /")
1226 col
.label(text
="BTool Brush To Mesh: Ctrl Num Enter")
1227 col
.label(text
="BTool All Brush To Mesh: Ctrl Shift Num Enter")
1230 # ------------------- Class List ------------------------------------------------
1233 PREFS_BoolTool_Props
,
1234 VIEW3D_MT_booltool_menu
,
1235 VIEW3D_PT_booltool_tools
,
1236 VIEW3D_PT_booltool_config
,
1237 VIEW3D_PT_booltool_bviewer
,
1238 OBJECT_OT_BoolTool_Auto_Union
,
1239 OBJECT_OT_BoolTool_Auto_Difference
,
1240 OBJECT_OT_BoolTool_Auto_Intersect
,
1241 OBJECT_OT_BoolTool_Auto_Slice
,
1246 # TODO Draw Poly Brush
1247 # BTool_DrawPolyBrush,
1249 BTool_AllBrushToMesh
,
1254 BTool_EnableThisBrush
,
1255 BTool_EnableFTransform
,
1256 BTool_FastTransform
,
1257 WM_OT_BoolTool_Help
,
1261 # ------------------- REGISTER ------------------------------------------------
1264 addon_keymapsFastT
= []
1267 # Fast Transform HotKeys Register
1268 def RegisterFastT():
1269 wm
= bpy
.context
.window_manager
1270 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
="Object Mode", space_type
="EMPTY")
1272 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "G", "PRESS")
1273 kmi
.properties
.operator
= "Translate"
1274 addon_keymapsFastT
.append((km
, kmi
))
1276 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "R", "PRESS")
1277 kmi
.properties
.operator
= "Rotate"
1278 addon_keymapsFastT
.append((km
, kmi
))
1280 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "S", "PRESS")
1281 kmi
.properties
.operator
= "Scale"
1282 addon_keymapsFastT
.append((km
, kmi
))
1285 # Fast Transform HotKeys UnRegister
1286 def UnRegisterFastT():
1287 wm
= bpy
.context
.window_manager
1288 kc
= wm
.keyconfigs
.addon
1290 for km
, kmi
in addon_keymapsFastT
:
1291 km
.keymap_items
.remove(kmi
)
1293 addon_keymapsFastT
.clear()
1298 bpy
.utils
.register_class(cls
)
1299 update_panels(None, bpy
.context
)
1302 bpy
.types
.Scene
.BoolHide
= BoolProperty(
1304 description
="Hide boolean objects",
1305 update
=update_BoolHide
,
1308 bpy
.types
.VIEW3D_MT_object
.append(VIEW3D_BoolTool_Menu
)
1310 bpy
.types
.VIEW3D_MT_Object
.prepend(VIEW3D_BoolTool_Menu
)
1314 wm
= bpy
.context
.window_manager
1315 kc
= wm
.keyconfigs
.addon
1317 # create the boolean menu hotkey
1319 km
= kc
.keymaps
.new(name
="Object Mode")
1321 kmi
= km
.keymap_items
.new("wm.call_menu", "B", "PRESS", ctrl
=True, shift
=True)
1322 kmi
.properties
.name
= "VIEW3D_MT_booltool_menu"
1323 addon_keymaps
.append((km
, kmi
))
1326 kmi
= km
.keymap_items
.new(BTool_Union
.bl_idname
, "NUMPAD_PLUS", "PRESS", ctrl
=True)
1327 addon_keymaps
.append((km
, kmi
))
1328 kmi
= km
.keymap_items
.new(BTool_Diff
.bl_idname
, "NUMPAD_MINUS", "PRESS", ctrl
=True)
1329 addon_keymaps
.append((km
, kmi
))
1330 kmi
= km
.keymap_items
.new(BTool_Inters
.bl_idname
, "NUMPAD_ASTERIX", "PRESS", ctrl
=True)
1331 addon_keymaps
.append((km
, kmi
))
1332 kmi
= km
.keymap_items
.new(BTool_Slice
.bl_idname
, "NUMPAD_SLASH", "PRESS", ctrl
=True)
1333 addon_keymaps
.append((km
, kmi
))
1334 kmi
= km
.keymap_items
.new(BTool_BrushToMesh
.bl_idname
, "NUMPAD_ENTER", "PRESS", ctrl
=True)
1335 addon_keymaps
.append((km
, kmi
))
1336 kmi
= km
.keymap_items
.new(
1337 BTool_AllBrushToMesh
.bl_idname
,
1343 addon_keymaps
.append((km
, kmi
))
1346 kmi
= km
.keymap_items
.new(
1347 OBJECT_OT_BoolTool_Auto_Union
.bl_idname
,
1353 addon_keymaps
.append((km
, kmi
))
1354 kmi
= km
.keymap_items
.new(
1355 OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
,
1361 addon_keymaps
.append((km
, kmi
))
1362 kmi
= km
.keymap_items
.new(
1363 OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
,
1369 addon_keymaps
.append((km
, kmi
))
1370 kmi
= km
.keymap_items
.new(
1371 OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
,
1377 addon_keymaps
.append((km
, kmi
))
1382 # remove keymaps when add-on is deactivated
1383 wm
= bpy
.context
.window_manager
1384 kc
= wm
.keyconfigs
.addon
1386 for km
, kmi
in addon_keymaps
:
1387 km
.keymap_items
.remove(kmi
)
1389 addon_keymaps
.clear()
1392 bpy
.types
.VIEW3D_MT_object
.remove(VIEW3D_BoolTool_Menu
)
1394 bpy
.types
.VIEW3D_MT_Object
.remove(VIEW3D_BoolTool_Menu
)
1398 del bpy
.types
.Scene
.BoolHide
1401 bpy
.utils
.unregister_class(cls
)
1404 if __name__
== "__main__":