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 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
29 "object/bool_tools.html",
34 from bpy
.types
import (
40 from bpy
.props
import (
46 # ------------------- Bool Tool FUNCTIONS -------------------------
49 # Hide boolean objects
50 def update_BoolHide(self
, context
):
51 ao
= context
.view_layer
.objects
.active
52 objs
= [i
.object for i
in ao
.modifiers
if i
.type == "BOOLEAN"]
53 hide_state
= context
.scene
.BoolHide
56 o
.hide_viewport
= hide_state
61 if _obj
["BoolToolRoot"]:
69 if _obj
["BoolToolBrush"]:
76 # def isPolyBrush(_obj):
78 # if _obj["BoolToolPolyBrush"]:
84 def cycles_visibility_set(ob
, value
=False):
85 if not hasattr(ob
, "cycles_visibility"):
88 vis
= ob
.cycles_visibility
94 vis
.transmission
= value
98 def BT_ObjectByName(obj
):
99 for ob
in bpy
.context
.view_layer
.objects
:
100 if isCanvas(ob
) or isBrush(ob
):
106 for ob
in bpy
.context
.view_layer
.objects
:
108 for mod
in ob
.modifiers
:
109 if "BTool_" in mod
.name
:
110 if obj
.name
in mod
.name
:
115 preferences
= bpy
.context
.preferences
116 addons
= preferences
.addons
117 addon_prefs
= addons
[__name__
].preferences
118 if addon_prefs
.fast_transform
:
124 def ConvertToMesh(obj
):
125 act
= bpy
.context
.view_layer
.objects
.active
126 bpy
.context
.view_layer
.objects
.active
= obj
127 bpy
.ops
.object.convert(target
="MESH")
128 bpy
.context
.view_layer
.objects
.active
= act
131 # Do the Union, Difference and Intersection Operations with a Brush
132 def Operation(context
, _operation
):
133 prefs
= context
.preferences
.addons
[__name__
].preferences
134 useWire
= prefs
.use_wire
136 for selObj
in context
.selected_objects
:
138 selObj
!= context
.active_object
and
139 (selObj
.type == "MESH" or selObj
.type == "CURVE")
141 if selObj
.type == "CURVE":
142 ConvertToMesh(selObj
)
143 actObj
= context
.active_object
144 selObj
.hide_render
= True
147 selObj
.display_type
= "WIRE"
149 selObj
.display_type
= "BOUNDS"
151 cycles_visibility_set(selObj
, value
=False)
153 if _operation
== "SLICE":
154 # copies instance_collection property(empty), but group property is empty (users_group = None)
155 clone
= actObj
.copy()
156 context
.collection
.objects
.link(clone
)
158 space_data
= context
.space_data
159 is_local_view
= bool(space_data
.local_view
)
162 clone
.local_view_set(space_data
, True)
164 sliceMod
= clone
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN") # add mod to clone obj
165 sliceMod
.object = selObj
166 sliceMod
.operation
= "DIFFERENCE"
167 clone
["BoolToolRoot"] = True
169 newMod
= actObj
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN")
170 newMod
.object = selObj
172 if _operation
== "SLICE":
173 newMod
.operation
= "INTERSECT"
175 newMod
.operation
= _operation
177 actObj
["BoolToolRoot"] = True
178 selObj
["BoolToolBrush"] = _operation
179 selObj
["BoolTool_FTransform"] = "False"
182 # Remove Objects form the BoolTool System
183 def Remove(context
, thisObj_name
, Prop
):
184 # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
185 actObj
= context
.active_object
188 def RemoveThis(_thisObj_name
):
189 for obj
in bpy
.context
.view_layer
.objects
:
190 # if it's the brush object
191 if obj
.name
== _thisObj_name
:
192 obj
.display_type
= "TEXTURED"
193 del obj
["BoolToolBrush"]
194 del obj
["BoolTool_FTransform"]
195 cycles_visibility_set(obj
, value
=True)
197 # Remove it from the Canvas
198 for mod
in actObj
.modifiers
:
199 if "BTool_" in mod
.name
:
200 if _thisObj_name
in mod
.name
:
201 actObj
.modifiers
.remove(mod
)
204 RemoveThis(thisObj_name
)
206 # If the remove was called from the Properties:
208 # Remove the Brush Property
210 Canvas
= FindCanvas(actObj
)
213 for mod
in Canvas
.modifiers
:
214 if "BTool_" in mod
.name
and actObj
.name
in mod
.name
:
215 Canvas
.modifiers
.remove(mod
)
217 actObj
.display_type
= "TEXTURED"
218 del actObj
["BoolToolBrush"]
219 del actObj
["BoolTool_FTransform"]
220 cycles_visibility_set(actObj
, value
=True)
223 for mod
in actObj
.modifiers
:
224 if "BTool_" in mod
.name
:
225 RemoveThis(mod
.object.name
)
228 # Toggle the Enable the Brush Object Property
229 def EnableBrush(context
, objList
, canvas
):
231 for mod
in canvas
.modifiers
:
232 if "BTool_" in mod
.name
and mod
.object.name
== obj
:
234 if mod
.show_viewport
:
235 mod
.show_viewport
= False
236 mod
.show_render
= False
238 mod
.show_viewport
= True
239 mod
.show_render
= True
242 # Find the Canvas and Enable this Brush
243 def EnableThisBrush(context
, set):
245 for obj
in bpy
.context
.view_layer
.objects
:
246 if obj
!= bpy
.context
.active_object
:
248 for mod
in obj
.modifiers
:
249 if "BTool_" in mod
.name
:
250 if mod
.object == bpy
.context
.active_object
:
253 for mod
in canvas
.modifiers
:
254 if "BTool_" in mod
.name
:
255 if mod
.object == bpy
.context
.active_object
:
257 if mod
.show_viewport
:
258 mod
.show_viewport
= False
259 mod
.show_render
= False
261 mod
.show_viewport
= True
262 mod
.show_render
= True
265 mod
.show_viewport
= True
267 mod
.show_viewport
= False
271 # Toggle the Fast Transform Property of the Active Brush
272 def EnableFTransf(context
):
273 actObj
= bpy
.context
.active_object
275 if actObj
["BoolTool_FTransform"] == "True":
276 actObj
["BoolTool_FTransform"] = "False"
278 actObj
["BoolTool_FTransform"] = "True"
282 # Apply All Brushes to the Canvas
283 def ApplyAll(context
, list):
286 if isCanvas(selObj
) and selObj
== context
.active_object
:
287 for mod
in selObj
.modifiers
:
288 if "BTool_" in mod
.name
:
289 objDeleteList
.append(mod
.object)
291 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
292 except: # if fails the means it is multiuser data
293 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
294 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
295 del selObj
["BoolToolRoot"]
297 for obj
in context
.scene
.objects
:
299 for mod
in obj
.modifiers
:
300 # do not delete brush that is used by another canvas
301 if mod
.type == "BOOLEAN" and mod
.object in objDeleteList
:
302 objDeleteList
.remove(mod
.object) # remove it from deletion
304 bpy
.ops
.object.select_all(action
="DESELECT")
305 for obj
in objDeleteList
:
307 bpy
.ops
.object.delete()
310 # Apply This Brush to the Canvas
311 def ApplyThisBrush(context
, brush
):
312 for obj
in context
.scene
.objects
:
314 for mod
in obj
.modifiers
:
315 if "BTool_" + brush
.name
in mod
.name
:
317 context
.view_layer
.objects
.active
= obj
319 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
320 except: # if fails the means it is multiuser data
321 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
322 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
323 bpy
.ops
.object.select_all(action
="TOGGLE")
324 bpy
.ops
.object.select_all(action
="DESELECT")
327 brush
.select_set(True)
328 # bpy.ops.object.delete()
331 # ------------------ Bool Tool OPERATORS --------------------------------------
334 # class BTool_DrawPolyBrush(Operator):
335 # bl_idname = "btool.draw_polybrush"
336 # bl_label = "Draw Poly Brush"
338 # "Draw Polygonal Mask, can be applied to Canvas > Brush or Directly\n"
339 # "Note: ESC to Cancel, Enter to Apply, Right Click to erase the Lines"
343 # store_cont_draw = False
346 # def poll(cls, context):
347 # return context.active_object is not None
349 # def set_cont_draw(self, context, start=False):
350 # # store / restore GP continuous drawing (see T52321)
351 # scene = context.scene
352 # tool_settings = scene.tool_settings
353 # continuous = tool_settings.use_gpencil_continuous_drawing
355 # self.store_cont_draw = continuous
356 # tool_settings.use_gpencil_continuous_drawing = True
358 # tool_settings.use_gpencil_continuous_drawing = self.store_cont_draw
360 # def modal(self, context, event):
362 # actObj = bpy.context.active_object
363 # if self.count == 1:
364 # actObj.select_set(True)
365 # bpy.ops.gpencil.draw("INVOKE_DEFAULT", mode="DRAW_POLY")
367 # if event.type == "RIGHTMOUSE":
368 # # use this to pass to the Grease Pencil eraser (see T52321)
371 # if event.type in {"RET", "NUMPAD_ENTER"}:
373 # bpy.ops.gpencil.convert(type="POLY")
374 # self.set_cont_draw(context)
376 # for obj in context.selected_objects:
377 # if obj.type == "CURVE":
378 # obj.name = "PolyDraw"
379 # bpy.context.view_layer.objects.active = obj
380 # bpy.ops.object.select_all(action="DESELECT")
381 # obj.select_set(True)
382 # bpy.ops.object.convert(target="MESH")
383 # bpy.ops.object.mode_set(mode="EDIT")
384 # bpy.ops.mesh.select_all(action="SELECT")
385 # bpy.ops.mesh.edge_face_add()
386 # bpy.ops.mesh.flip_normals()
387 # bpy.ops.object.mode_set(mode="OBJECT")
388 # bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
389 # bpy.ops.object.modifier_add(type="SOLIDIFY")
390 # for mod in obj.modifiers:
391 # if mod.name == "Solidify":
392 # mod.name = "BTool_PolyBrush"
395 # obj["BoolToolPolyBrush"] = True
397 # bpy.ops.object.select_all(action="DESELECT")
398 # bpy.context.view_layer.objects.active = actObj
399 # bpy.context.view_layer.update()
400 # actObj.select_set(True)
401 # obj.select_set(True)
403 # bpy.context.view_layer.grease_pencil.clear()
404 # bpy.ops.gpencil.data_unlink()
406 # return {"FINISHED"}
408 # if event.type == "ESC":
409 # bpy.ops.ed.undo() # remove o Grease Pencil
410 # self.set_cont_draw(context)
412 # self.report({"INFO"}, "Draw Poly Brush: Operation Cancelled by User")
413 # return {"CANCELLED"}
415 # return {"RUNNING_MODAL"}
417 # def invoke(self, context, event):
419 # self.set_cont_draw(context, start=True)
420 # context.window_manager.modal_handler_add(self)
421 # return {"RUNNING_MODAL"}
423 # self.report({"WARNING"}, "No active object, could not finish")
424 # return {"CANCELLED"}
428 class BTool_FastTransform(Operator
):
429 bl_idname
= "btool.fast_transform"
430 bl_label
= "Fast Transform"
431 bl_description
= "Enable Fast Transform"
433 operator
: StringProperty("")
437 def modal(self
, context
, event
):
439 actObj
= bpy
.context
.active_object
440 useWire
= bpy
.context
.preferences
.addons
[__name__
].preferences
.use_wire
443 if isBrush(actObj
) and actObj
["BoolTool_FTransform"] == "True":
444 EnableThisBrush(bpy
.context
, "False")
446 actObj
.display_type
= "WIRE"
448 actObj
.display_type
= "BOUNDS"
450 if self
.operator
== "Translate":
451 bpy
.ops
.transform
.translate("INVOKE_DEFAULT")
452 if self
.operator
== "Rotate":
453 bpy
.ops
.transform
.rotate("INVOKE_DEFAULT")
454 if self
.operator
== "Scale":
455 bpy
.ops
.transform
.resize("INVOKE_DEFAULT")
457 if event
.type == "LEFTMOUSE":
459 EnableThisBrush(bpy
.context
, "True")
460 actObj
.display_type
= "WIRE"
463 if event
.type in {"RIGHTMOUSE", "ESC"}:
465 EnableThisBrush(bpy
.context
, "True")
466 actObj
.display_type
= "WIRE"
469 return {"RUNNING_MODAL"}
471 def invoke(self
, context
, event
):
473 context
.window_manager
.modal_handler_add(self
)
474 return {"RUNNING_MODAL"}
476 self
.report({"WARNING"}, "No active object, could not finish")
480 # ------------------- Bool Tool OPERATOR CLASSES --------------------------------------------------------
484 # --------------------------------------------------------------------------------------
489 def execute(self
, context
):
490 Operation(context
, self
.mode
)
493 def invoke(self
, context
, event
):
494 if len(context
.selected_objects
) < 2:
495 self
.report({"ERROR"}, "At least two objects must be selected")
498 return self
.execute(context
)
501 class BTool_Union(Operator
, BToolSetup
):
502 bl_idname
= "btool.boolean_union"
503 bl_label
= "Brush Union"
504 bl_description
= "This operator add a union brush to a canvas"
505 bl_options
= {"REGISTER", "UNDO"}
510 class BTool_Inters(Operator
, BToolSetup
):
511 bl_idname
= "btool.boolean_inters"
512 bl_label
= "Brush Intersection"
513 bl_description
= "This operator add a intersect brush to a canvas"
514 bl_options
= {"REGISTER", "UNDO"}
519 class BTool_Diff(Operator
, BToolSetup
):
520 bl_idname
= "btool.boolean_diff"
521 bl_label
= "Brush Difference"
522 bl_description
= "This operator add a difference brush to a canvas"
523 bl_options
= {"REGISTER", "UNDO"}
528 class BTool_Slice(Operator
, BToolSetup
):
529 bl_idname
= "btool.boolean_slice"
530 bl_label
= "Brush Slice"
531 bl_description
= "This operator add a intersect brush to a canvas"
532 bl_options
= {"REGISTER", "UNDO"}
537 # Auto Boolean operators
538 # --------------------------------------------------------------------------------------
543 def objects_prepare(self
):
544 for ob
in bpy
.context
.selected_objects
:
545 if ob
.type != "MESH":
547 bpy
.ops
.object.make_single_user(object=True, obdata
=True)
548 bpy
.ops
.object.convert(target
="MESH")
550 def mesh_selection(self
, ob
, select_action
):
551 obj
= bpy
.context
.active_object
553 bpy
.context
.view_layer
.objects
.active
= ob
554 bpy
.ops
.object.mode_set(mode
="EDIT")
556 bpy
.ops
.mesh
.reveal()
557 bpy
.ops
.mesh
.select_all(action
=select_action
)
559 bpy
.ops
.object.mode_set(mode
="OBJECT")
560 bpy
.context
.view_layer
.objects
.active
= obj
562 def boolean_operation(self
):
563 obj
= bpy
.context
.active_object
564 obj
.select_set(False)
565 obs
= bpy
.context
.selected_objects
567 self
.mesh_selection(obj
, "DESELECT")
570 self
.mesh_selection(ob
, "SELECT")
571 self
.boolean_mod(obj
, ob
, self
.mode
)
575 def boolean_mod(self
, obj
, ob
, mode
, ob_delete
=True):
576 md
= obj
.modifiers
.new("Auto Boolean", "BOOLEAN")
577 md
.show_viewport
= False
581 override
= {"object": obj
}
582 bpy
.ops
.object.modifier_apply(override
, modifier
=md
.name
)
585 bpy
.data
.objects
.remove(ob
)
587 def execute(self
, context
):
588 self
.objects_prepare()
589 self
.boolean_operation()
592 def invoke(self
, context
, event
):
593 if len(context
.selected_objects
) < 2:
594 self
.report({"ERROR"}, "At least two objects must be selected")
597 return self
.execute(context
)
600 class OBJECT_OT_BoolTool_Auto_Union(Operator
, Auto_Boolean
):
601 bl_idname
= "object.booltool_auto_union"
602 bl_label
= "Bool Tool Union"
603 bl_description
= "Combine selected objects"
604 bl_options
= {"REGISTER", "UNDO"}
609 class OBJECT_OT_BoolTool_Auto_Difference(Operator
, Auto_Boolean
):
610 bl_idname
= "object.booltool_auto_difference"
611 bl_label
= "Bool Tool Difference"
612 bl_description
= "Subtract selected objects from active object"
613 bl_options
= {"REGISTER", "UNDO"}
618 class OBJECT_OT_BoolTool_Auto_Intersect(Operator
, Auto_Boolean
):
619 bl_idname
= "object.booltool_auto_intersect"
620 bl_label
= "Bool Tool Intersect"
621 bl_description
= "Keep only intersecting geometry"
622 bl_options
= {"REGISTER", "UNDO"}
627 class OBJECT_OT_BoolTool_Auto_Slice(Operator
, Auto_Boolean
):
628 bl_idname
= "object.booltool_auto_slice"
629 bl_label
= "Bool Tool Slice"
630 bl_description
= "Slice active object along the selected objects"
631 bl_options
= {"REGISTER", "UNDO"}
633 def execute(self
, context
):
634 space_data
= context
.space_data
635 is_local_view
= bool(space_data
.local_view
)
636 self
.objects_prepare()
638 ob1
= context
.active_object
639 ob1
.select_set(False)
640 self
.mesh_selection(ob1
, "DESELECT")
642 for ob2
in context
.selected_objects
:
644 self
.mesh_selection(ob2
, "SELECT")
646 ob1_copy
= ob1
.copy()
647 ob1_copy
.data
= ob1
.data
.copy()
649 for coll
in ob1
.users_collection
:
650 coll
.objects
.link(ob1_copy
)
653 ob1_copy
.local_view_set(space_data
, True)
655 self
.boolean_mod(ob1
, ob2
, "DIFFERENCE", ob_delete
=False)
656 self
.boolean_mod(ob1_copy
, ob2
, "INTERSECT")
657 ob1_copy
.select_set(True)
659 context
.view_layer
.objects
.active
= ob1_copy
664 # Utils Class ---------------------------------------------------------------
666 # Find the Brush Selected in Three View
667 class BTool_FindBrush(Operator
):
668 bl_idname
= "btool.find_brush"
670 bl_description
= "Find the selected brush"
672 obj
: StringProperty("")
675 def poll(cls
, context
):
676 return context
.active_object
is not None
678 def execute(self
, context
):
679 for ob
in bpy
.context
.view_layer
.objects
:
680 if ob
.name
== self
.obj
:
681 bpy
.ops
.object.select_all(action
="TOGGLE")
682 bpy
.ops
.object.select_all(action
="DESELECT")
683 bpy
.context
.view_layer
.objects
.active
= ob
684 ob
.set_select(state
=True)
688 # Move The Modifier in The Stack Up or Down
689 class BTool_MoveStack(Operator
):
690 bl_idname
= "btool.move_stack"
692 bl_description
= "Move this Brush Up/Down in the Stack"
694 modif
: StringProperty("")
695 direction
: StringProperty("")
698 def poll(cls
, context
):
699 return context
.active_object
is not None
701 def execute(self
, context
):
702 if self
.direction
== "UP":
703 bpy
.ops
.object.modifier_move_up(modifier
=self
.modif
)
704 if self
.direction
== "DOWN":
705 bpy
.ops
.object.modifier_move_down(modifier
=self
.modif
)
709 # Enable or Disable a Brush in the Three View
710 class BTool_EnableBrush(Operator
):
711 bl_idname
= "btool.enable_brush"
713 bl_description
= "Removes all BoolTool config assigned to it"
715 thisObj
: StringProperty("")
718 def poll(cls
, context
):
719 return context
.active_object
is not None
721 def execute(self
, context
):
722 # in this case is just one object but the function accept more than one at once
723 EnableBrush(context
, [self
.thisObj
], context
.active_object
)
727 # Enable or Disable a Brush Directly
728 class BTool_EnableThisBrush(Operator
):
729 bl_idname
= "btool.enable_this_brush"
731 bl_description
= "Toggles this brush"
734 def poll(cls
, context
):
735 return context
.active_object
is not None
737 def execute(self
, context
):
738 EnableThisBrush(context
, "None")
742 # Enable or Disable a Brush Directly
743 class BTool_EnableFTransform(Operator
):
744 bl_idname
= "btool.enable_ftransf"
746 bl_description
= "Use Fast Transformations to improve speed"
749 def poll(cls
, context
):
750 return context
.active_object
is not None
752 def execute(self
, context
):
753 EnableFTransf(context
)
757 # Other Operations -------------------------------------------------------
759 # Remove a Brush or a Canvas
760 class BTool_Remove(Operator
):
761 bl_idname
= "btool.remove"
762 bl_label
= "Bool Tool Remove"
763 bl_description
= "Removes all BoolTool config assigned to it"
764 bl_options
= {"UNDO"}
766 thisObj
: StringProperty("")
767 Prop
: StringProperty("")
770 def poll(cls
, context
):
771 return context
.active_object
is not None
773 def execute(self
, context
):
774 Remove(context
, self
.thisObj
, self
.Prop
)
778 # Apply All to Canvas
779 class BTool_AllBrushToMesh(Operator
):
780 bl_idname
= "btool.to_mesh"
781 bl_label
= "Apply All Canvas"
782 bl_description
= "Apply all brushes of this canvas"
783 bl_options
= {"UNDO"}
786 def poll(cls
, context
):
787 return context
.active_object
is not None
789 def execute(self
, context
):
790 lists
= bpy
.context
.selected_objects
791 ApplyAll(context
, lists
)
795 # Apply This Brush to the Canvas
796 class BTool_BrushToMesh(Operator
):
797 bl_idname
= "btool.brush_to_mesh"
798 bl_label
= "Apply this Brush to Canvas"
799 bl_description
= "Apply this brush to the canvas"
800 bl_options
= {"UNDO"}
803 def poll(cls
, context
):
805 if isBrush(context
.active_object
):
810 def execute(self
, context
):
811 ApplyThisBrush(context
, bpy
.context
.active_object
)
816 # Apply This Brush To Mesh
819 # ------------------- MENU CLASSES ------------------------------
822 class VIEW3D_MT_booltool_menu(Menu
):
823 bl_label
= "Bool Tool"
824 bl_idname
= "VIEW3D_MT_booltool_menu"
826 def draw(self
, context
):
829 layout
.label(text
="Auto Boolean")
830 layout
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
831 layout
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
832 layout
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
833 layout
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
837 layout
.label(text
="Brush Boolean")
838 layout
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
839 layout
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
840 layout
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
841 layout
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
843 if isCanvas(context
.active_object
):
845 layout
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
846 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove All")
850 if isBrush(context
.active_object
):
852 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
853 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove Brush")
858 def VIEW3D_BoolTool_Menu(self
, context
):
859 self
.layout
.menu(VIEW3D_MT_booltool_menu
.bl_idname
)
862 # ---------------- Toolshelf: Tools ---------------------
865 class VIEW3D_PT_booltool_tools(Panel
):
866 bl_category
= "objectmode"
867 bl_label
= "Bool Tool"
868 bl_space_type
= "VIEW_3D"
869 bl_region_type
= "UI"
870 bl_context
= "objectmode"
871 bl_options
= {'DEFAULT_CLOSED'}
874 def poll(cls
, context
):
875 return context
.active_object
is not None
877 def draw(self
, context
):
880 col
= layout
.column(align
=True)
881 col
.label(text
="Auto Boolean")
882 col
.operator(OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
883 col
.operator(OBJECT_OT_BoolTool_Auto_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
884 col
.operator(OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
885 col
.operator(OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
887 col
= layout
.column(align
=True)
888 col
.label(text
="Brush Boolean")
889 col
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="SELECT_SUBTRACT")
890 col
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="SELECT_EXTEND")
891 col
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="SELECT_INTERSECT")
892 col
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="SELECT_DIFFERENCE")
894 # TODO Draw Poly Brush
897 # col = main.column(align=True)
898 # col.label(text="Draw:", icon="MESH_CUBE")
900 # col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
903 # ---------- Toolshelf: Properties --------------------------------------------------------
906 class VIEW3D_PT_booltool_config(Panel
):
907 bl_category
= "objectmode"
908 bl_label
= "Properties"
909 bl_space_type
= "VIEW_3D"
910 bl_region_type
= "UI"
911 bl_context
= "objectmode"
912 bl_parent_id
= "VIEW3D_PT_booltool_tools"
915 def poll(cls
, context
):
916 actObj
= context
.active_object
917 return isCanvas(actObj
) or isBrush(actObj
) # or isPolyBrush(actObj)
919 def draw(self
, context
):
921 actObj
= context
.active_object
923 row
= layout
.row(align
=True)
927 row
.label(text
="CANVAS", icon
="MESH_GRID")
929 row
.prop(context
.scene
, "BoolHide", text
="Hide Bool objects")
930 row
= layout
.row(align
=True)
931 row
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
933 row
= layout
.row(align
=True)
934 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove All")
943 if actObj
["BoolToolBrush"] == "DIFFERENCE":
944 icon
= "SELECT_SUBTRACT"
945 elif actObj
["BoolToolBrush"] == "UNION":
946 icon
= "SELECT_EXTEND"
947 elif actObj
["BoolToolBrush"] == "INTERSECT":
948 icon
= "SELECT_INTERSECT"
949 elif actObj
["BoolToolBrush"] == "SLICE":
950 icon
= "SELECT_DIFFERENCE"
952 row
.label(text
="BRUSH", icon
=icon
)
954 if actObj
["BoolTool_FTransform"] == "True":
962 row
= layout
.row(align
=True)
963 row
.operator(BTool_EnableFTransform
.bl_idname
, text
="Fast Vis", icon
=icon
)
964 row
.operator(BTool_EnableThisBrush
.bl_idname
, text
="Enable", icon
="HIDE_OFF")
966 row
.operator(BTool_EnableThisBrush
.bl_idname
, icon
="HIDE_OFF")
968 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
969 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="X", text
="Remove Brush")
974 # if isPolyBrush(actObj):
975 # layout.label(text="POLY BRUSH", icon="LINE_DATA")
976 # mod = actObj.modifiers["BTool_PolyBrush"]
977 # layout.prop(mod, "thickness", text="Size")
980 # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
983 class VIEW3D_PT_booltool_bviewer(Panel
):
984 bl_category
= "objectmode"
985 bl_label
= "Brush Viewer"
986 bl_space_type
= "VIEW_3D"
987 bl_region_type
= "UI"
988 bl_context
= "objectmode"
989 bl_parent_id
= "VIEW3D_PT_booltool_tools"
992 def poll(cls
, context
):
993 actObj
= bpy
.context
.active_object
1000 def draw(self
, context
):
1002 actObj
= bpy
.context
.active_object
1004 if isCanvas(actObj
):
1006 for mod
in actObj
.modifiers
:
1007 container
= self
.layout
.box()
1008 row
= container
.row(align
=True)
1010 if "BTool_" in mod
.name
:
1012 if mod
.operation
== "DIFFERENCE":
1013 icon
= "SELECT_SUBTRACT"
1014 elif mod
.operation
== "UNION":
1015 icon
= "SELECT_EXTEND"
1016 elif mod
.operation
== "INTERSECT":
1017 icon
= "SELECT_INTERSECT"
1018 elif mod
.operation
== "SLICE":
1019 icon
= "SELECT_DIFFERENCE"
1021 objSelect
= row
.operator("btool.find_brush", text
=mod
.object.name
, icon
=icon
, emboss
=False)
1022 objSelect
.obj
= mod
.object.name
1024 EnableIcon
= "RESTRICT_VIEW_ON"
1025 if mod
.show_viewport
:
1026 EnableIcon
= "RESTRICT_VIEW_OFF"
1027 Enable
= row
.operator(BTool_EnableBrush
.bl_idname
, icon
=EnableIcon
, emboss
=False)
1028 Enable
.thisObj
= mod
.object.name
1030 Remove
= row
.operator("btool.remove", text
="", icon
="X", emboss
=False)
1031 Remove
.thisObj
= mod
.object.name
1032 Remove
.Prop
= "THIS"
1035 row
.label(text
=mod
.name
)
1037 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1041 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1043 Dw
.direction
= "DOWN"
1046 # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
1051 ("Menu", "Ctrl Shift B"),
1053 ("Auto Operators", None),
1054 ("Difference", "Ctrl Shift Num -"),
1055 ("Union", "Ctrl Shift Num +"),
1056 ("Intersect", "Ctrl Shift Num *"),
1057 ("Slice", "Ctrl Shift Num /"),
1059 ("Brush Operators", None),
1060 ("Difference", "Ctrl Num -"),
1061 ("Union", "Ctrl Num +"),
1062 ("Intersect", "Ctrl Num *"),
1063 ("Slice", "Ctrl Num /"),
1064 ("Brush To Mesh", "Ctrl Num Enter"),
1065 ("All Brushes To Mesh", "Ctrl Shift Num Enter"),
1069 def UpdateBoolTool_Pref(self
, context
):
1070 if self
.fast_transform
:
1076 # Define Panel classes for updating
1078 VIEW3D_PT_booltool_tools
,
1079 VIEW3D_PT_booltool_config
,
1080 VIEW3D_PT_booltool_bviewer
,
1084 def update_panels(self
, context
):
1086 for panel
in panels
:
1087 if "bl_rna" in panel
.__dict
__:
1088 bpy
.utils
.unregister_class(panel
)
1090 for panel
in panels
:
1091 panel
.bl_category
= context
.preferences
.addons
[
1093 ].preferences
.category
1094 bpy
.utils
.register_class(panel
)
1096 except Exception as e
:
1097 message
= "Bool Tool: Updating Panel locations has failed"
1098 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1101 def icon_tria(prop
):
1107 class PREFS_BoolTool_Props(AddonPreferences
):
1108 bl_idname
= __name__
1110 fast_transform
: BoolProperty(
1111 name
="Fast Transformations",
1112 update
=UpdateBoolTool_Pref
,
1113 description
="Replace the Transform HotKeys (G,R,S)\n"
1114 "for a custom version that can optimize the visualization of Brushes",
1116 use_wire
: BoolProperty(
1117 name
="Display As Wirewrame",
1118 description
="Display brush as wireframe instead of bounding box",
1120 category
: StringProperty(
1122 description
="Set sidebar tab name",
1124 update
=update_panels
,
1126 show_shortcuts
: BoolProperty(name
="Shortcuts")
1128 def draw(self
, context
):
1129 layout
= self
.layout
1130 layout
.use_property_split
= True
1131 layout
.use_property_decorate
= False
1133 col
= layout
.column()
1134 col
.prop(self
, "category")
1135 col
.prop(self
, "fast_transform")
1136 col
.prop(self
, "use_wire")
1138 col
= layout
.column()
1140 col
.use_property_split
= False
1141 col
.prop(self
, "show_shortcuts", icon
=icon_tria(self
.show_shortcuts
))
1143 if self
.show_shortcuts
:
1145 col
= layout
.column()
1147 for key_name
, key_comb
in shortcut_list
:
1148 if key_comb
is None:
1150 col
.label(text
=key_name
)
1152 row
= col
.row(align
=True)
1154 row
.box().label(text
=key_name
)
1155 row
.box().label(text
=key_comb
)
1158 # ------------------- Class List ------------------------------------------------
1161 PREFS_BoolTool_Props
,
1162 VIEW3D_MT_booltool_menu
,
1163 VIEW3D_PT_booltool_tools
,
1164 VIEW3D_PT_booltool_config
,
1165 VIEW3D_PT_booltool_bviewer
,
1166 OBJECT_OT_BoolTool_Auto_Union
,
1167 OBJECT_OT_BoolTool_Auto_Difference
,
1168 OBJECT_OT_BoolTool_Auto_Intersect
,
1169 OBJECT_OT_BoolTool_Auto_Slice
,
1174 # TODO Draw Poly Brush
1175 # BTool_DrawPolyBrush,
1177 BTool_AllBrushToMesh
,
1182 BTool_EnableThisBrush
,
1183 BTool_EnableFTransform
,
1184 BTool_FastTransform
,
1188 # ------------------- REGISTER ------------------------------------------------
1191 addon_keymapsFastT
= []
1194 # Fast Transform HotKeys Register
1195 def RegisterFastT():
1196 wm
= bpy
.context
.window_manager
1197 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
="Object Mode", space_type
="EMPTY")
1199 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "G", "PRESS")
1200 kmi
.properties
.operator
= "Translate"
1201 addon_keymapsFastT
.append((km
, kmi
))
1203 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "R", "PRESS")
1204 kmi
.properties
.operator
= "Rotate"
1205 addon_keymapsFastT
.append((km
, kmi
))
1207 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, "S", "PRESS")
1208 kmi
.properties
.operator
= "Scale"
1209 addon_keymapsFastT
.append((km
, kmi
))
1212 # Fast Transform HotKeys UnRegister
1213 def UnRegisterFastT():
1214 wm
= bpy
.context
.window_manager
1215 kc
= wm
.keyconfigs
.addon
1217 for km
, kmi
in addon_keymapsFastT
:
1218 km
.keymap_items
.remove(kmi
)
1220 addon_keymapsFastT
.clear()
1225 bpy
.utils
.register_class(cls
)
1226 update_panels(None, bpy
.context
)
1229 bpy
.types
.Scene
.BoolHide
= BoolProperty(
1231 description
="Hide boolean objects",
1232 update
=update_BoolHide
,
1234 bpy
.types
.VIEW3D_MT_object
.append(VIEW3D_BoolTool_Menu
)
1236 wm
= bpy
.context
.window_manager
1237 kc
= wm
.keyconfigs
.addon
1239 # create the boolean menu hotkey
1241 km
= kc
.keymaps
.new(name
="Object Mode")
1243 kmi
= km
.keymap_items
.new("wm.call_menu", "B", "PRESS", ctrl
=True, shift
=True)
1244 kmi
.properties
.name
= "VIEW3D_MT_booltool_menu"
1245 addon_keymaps
.append((km
, kmi
))
1248 kmi
= km
.keymap_items
.new(BTool_Union
.bl_idname
, "NUMPAD_PLUS", "PRESS", ctrl
=True)
1249 addon_keymaps
.append((km
, kmi
))
1250 kmi
= km
.keymap_items
.new(BTool_Diff
.bl_idname
, "NUMPAD_MINUS", "PRESS", ctrl
=True)
1251 addon_keymaps
.append((km
, kmi
))
1252 kmi
= km
.keymap_items
.new(BTool_Inters
.bl_idname
, "NUMPAD_ASTERIX", "PRESS", ctrl
=True)
1253 addon_keymaps
.append((km
, kmi
))
1254 kmi
= km
.keymap_items
.new(BTool_Slice
.bl_idname
, "NUMPAD_SLASH", "PRESS", ctrl
=True)
1255 addon_keymaps
.append((km
, kmi
))
1256 kmi
= km
.keymap_items
.new(BTool_BrushToMesh
.bl_idname
, "NUMPAD_ENTER", "PRESS", ctrl
=True)
1257 addon_keymaps
.append((km
, kmi
))
1258 kmi
= km
.keymap_items
.new(
1259 BTool_AllBrushToMesh
.bl_idname
,
1265 addon_keymaps
.append((km
, kmi
))
1268 kmi
= km
.keymap_items
.new(
1269 OBJECT_OT_BoolTool_Auto_Union
.bl_idname
,
1275 addon_keymaps
.append((km
, kmi
))
1276 kmi
= km
.keymap_items
.new(
1277 OBJECT_OT_BoolTool_Auto_Difference
.bl_idname
,
1283 addon_keymaps
.append((km
, kmi
))
1284 kmi
= km
.keymap_items
.new(
1285 OBJECT_OT_BoolTool_Auto_Intersect
.bl_idname
,
1291 addon_keymaps
.append((km
, kmi
))
1292 kmi
= km
.keymap_items
.new(
1293 OBJECT_OT_BoolTool_Auto_Slice
.bl_idname
,
1299 addon_keymaps
.append((km
, kmi
))
1304 # remove keymaps when add-on is deactivated
1305 wm
= bpy
.context
.window_manager
1306 kc
= wm
.keyconfigs
.addon
1308 for km
, kmi
in addon_keymaps
:
1309 km
.keymap_items
.remove(kmi
)
1311 addon_keymaps
.clear()
1313 bpy
.types
.VIEW3D_MT_object
.remove(VIEW3D_BoolTool_Menu
)
1314 del bpy
.types
.Scene
.BoolHide
1317 bpy
.utils
.unregister_class(cls
)
1320 if __name__
== "__main__":