Pose Library: update for rename of asset_library to asset_library_ref
[blender-addons.git] / object_boolean_tools.py
blob3000f58851c3967b0f24999d0e347a9f4dd17f53
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 #####
19 # <pep8 compliant>
21 bl_info = {
22 "name": "Bool Tool",
23 "author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto, Simon Appelt",
24 "version": (0, 4, 1),
25 "blender": (2, 80, 0),
26 "location": "View3D > Sidebar > Edit Tab",
27 "description": "Bool Tool Hotkey: Ctrl Shift B",
28 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/bool_tools.html",
29 "category": "Object",
32 import bpy
33 from bpy.types import (
34 AddonPreferences,
35 Operator,
36 Panel,
37 Menu,
39 from bpy.props import (
40 BoolProperty,
41 StringProperty,
45 # ------------------- Bool Tool FUNCTIONS -------------------------
46 # Utils:
48 # Hide boolean objects
49 def update_BoolHide(self, context):
50 ao = context.view_layer.objects.active
51 objs = [i.object for i in ao.modifiers if i.type == "BOOLEAN"]
52 hide_state = context.scene.BoolHide
54 for o in objs:
55 o.hide_viewport = hide_state
58 def isCanvas(_obj):
59 try:
60 if _obj["BoolToolRoot"]:
61 return True
62 except:
63 return False
66 def isBrush(_obj):
67 try:
68 if _obj["BoolToolBrush"]:
69 return True
70 except:
71 return False
74 # TODO
75 # def isPolyBrush(_obj):
76 # try:
77 # if _obj["BoolToolPolyBrush"]:
78 # return True
79 # except:
80 # return False
83 def cycles_visibility_set(ob, value=False):
84 if not hasattr(ob, "cycles_visibility"):
85 return
87 vis = ob.cycles_visibility
89 vis.camera = value
90 vis.diffuse = value
91 vis.glossy = value
92 vis.shadow = value
93 vis.transmission = value
94 vis.scatter = value
97 def BT_ObjectByName(obj):
98 for ob in bpy.context.view_layer.objects:
99 if isCanvas(ob) or isBrush(ob):
100 if ob.name == obj:
101 return ob
104 def FindCanvas(obj):
105 for ob in bpy.context.view_layer.objects:
106 if isCanvas(ob):
107 for mod in ob.modifiers:
108 if "BTool_" in mod.name:
109 if obj.name in mod.name:
110 return ob
113 def isFTransf():
114 preferences = bpy.context.preferences
115 addons = preferences.addons
116 addon_prefs = addons[__name__].preferences
117 if addon_prefs.fast_transform:
118 return True
119 else:
120 return False
123 def ConvertToMesh(obj):
124 act = bpy.context.view_layer.objects.active
125 bpy.context.view_layer.objects.active = obj
126 bpy.ops.object.convert(target="MESH")
127 bpy.context.view_layer.objects.active = act
130 # Do the Union, Difference and Intersection Operations with a Brush
131 def Operation(context, _operation):
132 prefs = context.preferences.addons[__name__].preferences
133 useWire = prefs.use_wire
135 for selObj in context.selected_objects:
136 if (
137 selObj != context.active_object and
138 (selObj.type == "MESH" or selObj.type == "CURVE")
140 if selObj.type == "CURVE":
141 ConvertToMesh(selObj)
142 actObj = context.active_object
143 selObj.hide_render = True
145 if useWire:
146 selObj.display_type = "WIRE"
147 else:
148 selObj.display_type = "BOUNDS"
150 cycles_visibility_set(selObj, value=False)
152 if _operation == "SLICE":
153 # copies instance_collection property(empty), but group property is empty (users_group = None)
154 clone = actObj.copy()
155 context.collection.objects.link(clone)
157 space_data = context.space_data
158 is_local_view = bool(space_data.local_view)
160 if is_local_view:
161 clone.local_view_set(space_data, True)
163 sliceMod = clone.modifiers.new("BTool_" + selObj.name, "BOOLEAN") # add mod to clone obj
164 sliceMod.object = selObj
165 sliceMod.operation = "DIFFERENCE"
166 clone["BoolToolRoot"] = True
168 newMod = actObj.modifiers.new("BTool_" + selObj.name, "BOOLEAN")
169 newMod.object = selObj
171 if _operation == "SLICE":
172 newMod.operation = "INTERSECT"
173 else:
174 newMod.operation = _operation
176 actObj["BoolToolRoot"] = True
177 selObj["BoolToolBrush"] = _operation
178 selObj["BoolTool_FTransform"] = "False"
181 # Remove Objects form the BoolTool System
182 def Remove(context, thisObj_name, Prop):
183 # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
184 actObj = context.active_object
186 # Restore the Brush
187 def RemoveThis(_thisObj_name):
188 for obj in bpy.context.view_layer.objects:
189 # if it's the brush object
190 if obj.name == _thisObj_name:
191 obj.display_type = "TEXTURED"
192 del obj["BoolToolBrush"]
193 del obj["BoolTool_FTransform"]
194 cycles_visibility_set(obj, value=True)
196 # Remove it from the Canvas
197 for mod in actObj.modifiers:
198 if "BTool_" in mod.name:
199 if _thisObj_name in mod.name:
200 actObj.modifiers.remove(mod)
202 if Prop == "THIS":
203 RemoveThis(thisObj_name)
205 # If the remove was called from the Properties:
206 else:
207 # Remove the Brush Property
208 if Prop == "BRUSH":
209 Canvas = FindCanvas(actObj)
211 if Canvas:
212 for mod in Canvas.modifiers:
213 if "BTool_" in mod.name and actObj.name in mod.name:
214 Canvas.modifiers.remove(mod)
216 actObj.display_type = "TEXTURED"
217 del actObj["BoolToolBrush"]
218 del actObj["BoolTool_FTransform"]
219 cycles_visibility_set(actObj, value=True)
221 if Prop == "CANVAS":
222 for mod in actObj.modifiers:
223 if "BTool_" in mod.name:
224 RemoveThis(mod.object.name)
227 # Toggle the Enable the Brush Object Property
228 def EnableBrush(context, objList, canvas):
229 for obj in objList:
230 for mod in canvas.modifiers:
231 if "BTool_" in mod.name and mod.object.name == obj:
233 if mod.show_viewport:
234 mod.show_viewport = False
235 mod.show_render = False
236 else:
237 mod.show_viewport = True
238 mod.show_render = True
241 # Find the Canvas and Enable this Brush
242 def EnableThisBrush(context, set):
243 canvas = None
244 for obj in bpy.context.view_layer.objects:
245 if obj != bpy.context.active_object:
246 if isCanvas(obj):
247 for mod in obj.modifiers:
248 if "BTool_" in mod.name:
249 if mod.object == bpy.context.active_object:
250 canvas = obj
252 for mod in canvas.modifiers:
253 if "BTool_" in mod.name:
254 if mod.object == bpy.context.active_object:
255 if set == "None":
256 if mod.show_viewport:
257 mod.show_viewport = False
258 mod.show_render = False
259 else:
260 mod.show_viewport = True
261 mod.show_render = True
262 else:
263 if set == "True":
264 mod.show_viewport = True
265 else:
266 mod.show_viewport = False
267 return
270 # Toggle the Fast Transform Property of the Active Brush
271 def EnableFTransf(context):
272 actObj = bpy.context.active_object
274 if actObj["BoolTool_FTransform"] == "True":
275 actObj["BoolTool_FTransform"] = "False"
276 else:
277 actObj["BoolTool_FTransform"] = "True"
278 return
281 # Apply All Brushes to the Canvas
282 def ApplyAll(context, list):
283 objDeleteList = []
284 for selObj in list:
285 if isCanvas(selObj) and selObj == context.active_object:
286 for mod in selObj.modifiers:
287 if "BTool_" in mod.name:
288 objDeleteList.append(mod.object)
289 try:
290 bpy.ops.object.modifier_apply(modifier=mod.name)
291 except: # if fails the means it is multiuser data
292 context.active_object.data = context.active_object.data.copy() # so just make data unique
293 bpy.ops.object.modifier_apply(modifier=mod.name)
294 del selObj["BoolToolRoot"]
296 for obj in context.scene.objects:
297 if isCanvas(obj):
298 for mod in obj.modifiers:
299 # do not delete brush that is used by another canvas
300 if mod.type == "BOOLEAN" and mod.object in objDeleteList:
301 objDeleteList.remove(mod.object) # remove it from deletion
303 bpy.ops.object.select_all(action="DESELECT")
304 for obj in objDeleteList:
305 obj.select_set(True)
306 bpy.ops.object.delete()
309 # Apply This Brush to the Canvas
310 def ApplyThisBrush(context, brush):
311 for obj in context.scene.objects:
312 if isCanvas(obj):
313 for mod in obj.modifiers:
314 if "BTool_" + brush.name in mod.name:
315 # Apply This Brush
316 context.view_layer.objects.active = obj
317 try:
318 bpy.ops.object.modifier_apply(modifier=mod.name)
319 except: # if fails the means it is multiuser data
320 context.active_object.data = context.active_object.data.copy() # so just make data unique
321 bpy.ops.object.modifier_apply(modifier=mod.name)
322 bpy.ops.object.select_all(action="TOGGLE")
323 bpy.ops.object.select_all(action="DESELECT")
325 # Garbage Collector
326 brush.select_set(True)
327 # bpy.ops.object.delete()
330 # ------------------ Bool Tool OPERATORS --------------------------------------
332 # TODO
333 # class BTool_DrawPolyBrush(Operator):
334 # bl_idname = "btool.draw_polybrush"
335 # bl_label = "Draw Poly Brush"
336 # bl_description = (
337 # "Draw Polygonal Mask, can be applied to Canvas > Brush or Directly\n"
338 # "Note: ESC to Cancel, Enter to Apply, Right Click to erase the Lines"
341 # count = 0
342 # store_cont_draw = False
344 # @classmethod
345 # def poll(cls, context):
346 # return context.active_object is not None
348 # def set_cont_draw(self, context, start=False):
349 # # store / restore GP continuous drawing (see T52321)
350 # scene = context.scene
351 # tool_settings = scene.tool_settings
352 # continuous = tool_settings.use_gpencil_continuous_drawing
353 # if start:
354 # self.store_cont_draw = continuous
355 # tool_settings.use_gpencil_continuous_drawing = True
356 # else:
357 # tool_settings.use_gpencil_continuous_drawing = self.store_cont_draw
359 # def modal(self, context, event):
360 # self.count += 1
361 # actObj = bpy.context.active_object
362 # if self.count == 1:
363 # actObj.select_set(True)
364 # bpy.ops.gpencil.draw("INVOKE_DEFAULT", mode="DRAW_POLY")
366 # if event.type == "RIGHTMOUSE":
367 # # use this to pass to the Grease Pencil eraser (see T52321)
368 # pass
370 # if event.type in {"RET", "NUMPAD_ENTER"}:
372 # bpy.ops.gpencil.convert(type="POLY")
373 # self.set_cont_draw(context)
375 # for obj in context.selected_objects:
376 # if obj.type == "CURVE":
377 # obj.name = "PolyDraw"
378 # bpy.context.view_layer.objects.active = obj
379 # bpy.ops.object.select_all(action="DESELECT")
380 # obj.select_set(True)
381 # bpy.ops.object.convert(target="MESH")
382 # bpy.ops.object.mode_set(mode="EDIT")
383 # bpy.ops.mesh.select_all(action="SELECT")
384 # bpy.ops.mesh.edge_face_add()
385 # bpy.ops.mesh.flip_normals()
386 # bpy.ops.object.mode_set(mode="OBJECT")
387 # bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
388 # bpy.ops.object.modifier_add(type="SOLIDIFY")
389 # for mod in obj.modifiers:
390 # if mod.name == "Solidify":
391 # mod.name = "BTool_PolyBrush"
392 # mod.thickness = 1
393 # mod.offset = 0
394 # obj["BoolToolPolyBrush"] = True
396 # bpy.ops.object.select_all(action="DESELECT")
397 # bpy.context.view_layer.objects.active = actObj
398 # bpy.context.view_layer.update()
399 # actObj.select_set(True)
400 # obj.select_set(True)
402 # bpy.context.view_layer.grease_pencil.clear()
403 # bpy.ops.gpencil.data_unlink()
405 # return {"FINISHED"}
407 # if event.type == "ESC":
408 # bpy.ops.ed.undo() # remove o Grease Pencil
409 # self.set_cont_draw(context)
411 # self.report({"INFO"}, "Draw Poly Brush: Operation Cancelled by User")
412 # return {"CANCELLED"}
414 # return {"RUNNING_MODAL"}
416 # def invoke(self, context, event):
417 # if context.object:
418 # self.set_cont_draw(context, start=True)
419 # context.window_manager.modal_handler_add(self)
420 # return {"RUNNING_MODAL"}
421 # else:
422 # self.report({"WARNING"}, "No active object, could not finish")
423 # return {"CANCELLED"}
426 # Fast Transform
427 class BTool_FastTransform(Operator):
428 bl_idname = "btool.fast_transform"
429 bl_label = "Fast Transform"
430 bl_description = "Enable Fast Transform"
432 operator: StringProperty("")
434 count = 0
436 def modal(self, context, event):
437 self.count += 1
438 actObj = bpy.context.active_object
439 useWire = bpy.context.preferences.addons[__name__].preferences.use_wire
440 if self.count == 1:
442 if isBrush(actObj) and actObj["BoolTool_FTransform"] == "True":
443 EnableThisBrush(bpy.context, "False")
444 if useWire:
445 actObj.display_type = "WIRE"
446 else:
447 actObj.display_type = "BOUNDS"
449 if self.operator == "Translate":
450 bpy.ops.transform.translate("INVOKE_DEFAULT")
451 if self.operator == "Rotate":
452 bpy.ops.transform.rotate("INVOKE_DEFAULT")
453 if self.operator == "Scale":
454 bpy.ops.transform.resize("INVOKE_DEFAULT")
456 if event.type == "LEFTMOUSE":
457 if isBrush(actObj):
458 EnableThisBrush(bpy.context, "True")
459 actObj.display_type = "WIRE"
460 return {"FINISHED"}
462 if event.type in {"RIGHTMOUSE", "ESC"}:
463 if isBrush(actObj):
464 EnableThisBrush(bpy.context, "True")
465 actObj.display_type = "WIRE"
466 return {"CANCELLED"}
468 return {"RUNNING_MODAL"}
470 def invoke(self, context, event):
471 if context.object:
472 context.window_manager.modal_handler_add(self)
473 return {"RUNNING_MODAL"}
474 else:
475 self.report({"WARNING"}, "No active object, could not finish")
476 return {"CANCELLED"}
479 # ------------------- Bool Tool OPERATOR CLASSES --------------------------------------------------------
482 # Brush operators
483 # --------------------------------------------------------------------------------------
486 class BToolSetup():
488 def execute(self, context):
489 Operation(context, self.mode)
490 return {"FINISHED"}
492 def invoke(self, context, event):
493 if len(context.selected_objects) < 2:
494 self.report({"ERROR"}, "At least two objects must be selected")
495 return {"CANCELLED"}
497 return self.execute(context)
500 class BTool_Union(Operator, BToolSetup):
501 bl_idname = "btool.boolean_union"
502 bl_label = "Brush Union"
503 bl_description = "This operator add a union brush to a canvas"
504 bl_options = {"REGISTER", "UNDO"}
506 mode = "UNION"
509 class BTool_Inters(Operator, BToolSetup):
510 bl_idname = "btool.boolean_inters"
511 bl_label = "Brush Intersection"
512 bl_description = "This operator add a intersect brush to a canvas"
513 bl_options = {"REGISTER", "UNDO"}
515 mode = "INTERSECT"
518 class BTool_Diff(Operator, BToolSetup):
519 bl_idname = "btool.boolean_diff"
520 bl_label = "Brush Difference"
521 bl_description = "This operator add a difference brush to a canvas"
522 bl_options = {"REGISTER", "UNDO"}
524 mode = "DIFFERENCE"
527 class BTool_Slice(Operator, BToolSetup):
528 bl_idname = "btool.boolean_slice"
529 bl_label = "Brush Slice"
530 bl_description = "This operator add a intersect brush to a canvas"
531 bl_options = {"REGISTER", "UNDO"}
533 mode = "SLICE"
536 # Auto Boolean operators
537 # --------------------------------------------------------------------------------------
540 class Auto_Boolean:
542 def objects_prepare(self):
543 for ob in bpy.context.selected_objects:
544 if ob.type != "MESH":
545 ob.select_set(False)
546 bpy.ops.object.make_single_user(object=True, obdata=True)
547 bpy.ops.object.convert(target="MESH")
549 def mesh_selection(self, ob, select_action):
550 obj = bpy.context.active_object
552 bpy.context.view_layer.objects.active = ob
553 bpy.ops.object.mode_set(mode="EDIT")
555 bpy.ops.mesh.reveal()
556 bpy.ops.mesh.select_all(action=select_action)
558 bpy.ops.object.mode_set(mode="OBJECT")
559 bpy.context.view_layer.objects.active = obj
561 def boolean_operation(self):
562 obj = bpy.context.active_object
563 obj.select_set(False)
564 obs = bpy.context.selected_objects
566 self.mesh_selection(obj, "DESELECT")
568 for ob in obs:
569 self.mesh_selection(ob, "SELECT")
570 self.boolean_mod(obj, ob, self.mode)
572 obj.select_set(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
577 md.operation = mode
578 md.object = ob
580 override = {"object": obj}
581 bpy.ops.object.modifier_apply(override, modifier=md.name)
583 if ob_delete:
584 bpy.data.objects.remove(ob)
586 def execute(self, context):
587 self.objects_prepare()
588 self.boolean_operation()
589 return {"FINISHED"}
591 def invoke(self, context, event):
592 if len(context.selected_objects) < 2:
593 self.report({"ERROR"}, "At least two objects must be selected")
594 return {"CANCELLED"}
596 return self.execute(context)
599 class OBJECT_OT_BoolTool_Auto_Union(Operator, Auto_Boolean):
600 bl_idname = "object.booltool_auto_union"
601 bl_label = "Bool Tool Union"
602 bl_description = "Combine selected objects"
603 bl_options = {"REGISTER", "UNDO"}
605 mode = "UNION"
608 class OBJECT_OT_BoolTool_Auto_Difference(Operator, Auto_Boolean):
609 bl_idname = "object.booltool_auto_difference"
610 bl_label = "Bool Tool Difference"
611 bl_description = "Subtract selected objects from active object"
612 bl_options = {"REGISTER", "UNDO"}
614 mode = "DIFFERENCE"
617 class OBJECT_OT_BoolTool_Auto_Intersect(Operator, Auto_Boolean):
618 bl_idname = "object.booltool_auto_intersect"
619 bl_label = "Bool Tool Intersect"
620 bl_description = "Keep only intersecting geometry"
621 bl_options = {"REGISTER", "UNDO"}
623 mode = "INTERSECT"
626 class OBJECT_OT_BoolTool_Auto_Slice(Operator, Auto_Boolean):
627 bl_idname = "object.booltool_auto_slice"
628 bl_label = "Bool Tool Slice"
629 bl_description = "Slice active object along the selected objects"
630 bl_options = {"REGISTER", "UNDO"}
632 def execute(self, context):
633 space_data = context.space_data
634 is_local_view = bool(space_data.local_view)
635 self.objects_prepare()
637 ob1 = context.active_object
638 ob1.select_set(False)
639 self.mesh_selection(ob1, "DESELECT")
641 for ob2 in context.selected_objects:
643 self.mesh_selection(ob2, "SELECT")
645 ob1_copy = ob1.copy()
646 ob1_copy.data = ob1.data.copy()
648 for coll in ob1.users_collection:
649 coll.objects.link(ob1_copy)
651 if is_local_view:
652 ob1_copy.local_view_set(space_data, True)
654 self.boolean_mod(ob1, ob2, "DIFFERENCE", ob_delete=False)
655 self.boolean_mod(ob1_copy, ob2, "INTERSECT")
656 ob1_copy.select_set(True)
658 context.view_layer.objects.active = ob1_copy
660 return {"FINISHED"}
663 # Utils Class ---------------------------------------------------------------
665 # Find the Brush Selected in Three View
666 class BTool_FindBrush(Operator):
667 bl_idname = "btool.find_brush"
668 bl_label = ""
669 bl_description = "Find the selected brush"
671 obj: StringProperty("")
673 @classmethod
674 def poll(cls, context):
675 return context.active_object is not None
677 def execute(self, context):
678 for ob in bpy.context.view_layer.objects:
679 if ob.name == self.obj:
680 bpy.ops.object.select_all(action="TOGGLE")
681 bpy.ops.object.select_all(action="DESELECT")
682 bpy.context.view_layer.objects.active = ob
683 ob.set_select(state=True)
684 return {"FINISHED"}
687 # Move The Modifier in The Stack Up or Down
688 class BTool_MoveStack(Operator):
689 bl_idname = "btool.move_stack"
690 bl_label = ""
691 bl_description = "Move this Brush Up/Down in the Stack"
693 modif: StringProperty("")
694 direction: StringProperty("")
696 @classmethod
697 def poll(cls, context):
698 return context.active_object is not None
700 def execute(self, context):
701 if self.direction == "UP":
702 bpy.ops.object.modifier_move_up(modifier=self.modif)
703 if self.direction == "DOWN":
704 bpy.ops.object.modifier_move_down(modifier=self.modif)
705 return {"FINISHED"}
708 # Enable or Disable a Brush in the Three View
709 class BTool_EnableBrush(Operator):
710 bl_idname = "btool.enable_brush"
711 bl_label = ""
712 bl_description = "Removes all BoolTool config assigned to it"
714 thisObj: StringProperty("")
716 @classmethod
717 def poll(cls, context):
718 return context.active_object is not None
720 def execute(self, context):
721 # in this case is just one object but the function accept more than one at once
722 EnableBrush(context, [self.thisObj], context.active_object)
723 return {"FINISHED"}
726 # Enable or Disable a Brush Directly
727 class BTool_EnableThisBrush(Operator):
728 bl_idname = "btool.enable_this_brush"
729 bl_label = ""
730 bl_description = "Toggles this brush"
732 @classmethod
733 def poll(cls, context):
734 return context.active_object is not None
736 def execute(self, context):
737 EnableThisBrush(context, "None")
738 return {"FINISHED"}
741 # Enable or Disable a Brush Directly
742 class BTool_EnableFTransform(Operator):
743 bl_idname = "btool.enable_ftransf"
744 bl_label = ""
745 bl_description = "Use Fast Transformations to improve speed"
747 @classmethod
748 def poll(cls, context):
749 return context.active_object is not None
751 def execute(self, context):
752 EnableFTransf(context)
753 return {"FINISHED"}
756 # Other Operations -------------------------------------------------------
758 # Remove a Brush or a Canvas
759 class BTool_Remove(Operator):
760 bl_idname = "btool.remove"
761 bl_label = "Bool Tool Remove"
762 bl_description = "Removes all BoolTool config assigned to it"
763 bl_options = {"UNDO"}
765 thisObj: StringProperty("")
766 Prop: StringProperty("")
768 @classmethod
769 def poll(cls, context):
770 return context.active_object is not None
772 def execute(self, context):
773 Remove(context, self.thisObj, self.Prop)
774 return {"FINISHED"}
777 # Apply All to Canvas
778 class BTool_AllBrushToMesh(Operator):
779 bl_idname = "btool.to_mesh"
780 bl_label = "Apply All Canvas"
781 bl_description = "Apply all brushes of this canvas"
782 bl_options = {"UNDO"}
784 @classmethod
785 def poll(cls, context):
786 return context.active_object is not None
788 def execute(self, context):
789 lists = bpy.context.selected_objects
790 ApplyAll(context, lists)
791 return {"FINISHED"}
794 # Apply This Brush to the Canvas
795 class BTool_BrushToMesh(Operator):
796 bl_idname = "btool.brush_to_mesh"
797 bl_label = "Apply this Brush to Canvas"
798 bl_description = "Apply this brush to the canvas"
799 bl_options = {"UNDO"}
801 @classmethod
802 def poll(cls, context):
804 if isBrush(context.active_object):
805 return True
806 else:
807 return False
809 def execute(self, context):
810 ApplyThisBrush(context, bpy.context.active_object)
811 return {"FINISHED"}
814 # TODO
815 # Apply This Brush To Mesh
818 # ------------------- MENU CLASSES ------------------------------
820 # 3Dview Header Menu
821 class VIEW3D_MT_booltool_menu(Menu):
822 bl_label = "Bool Tool"
823 bl_idname = "VIEW3D_MT_booltool_menu"
825 def draw(self, context):
826 layout = self.layout
828 layout.label(text="Auto Boolean")
829 layout.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
830 layout.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
831 layout.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
832 layout.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
834 layout.separator()
836 layout.label(text="Brush Boolean")
837 layout.operator(BTool_Diff.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
838 layout.operator(BTool_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
839 layout.operator(BTool_Inters.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
840 layout.operator(BTool_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
842 if isCanvas(context.active_object):
843 layout.separator()
844 layout.operator(BTool_AllBrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply All")
845 Rem = layout.operator(BTool_Remove.bl_idname, icon="X", text="Remove All")
846 Rem.thisObj = ""
847 Rem.Prop = "CANVAS"
849 if isBrush(context.active_object):
850 layout.separator()
851 layout.operator(BTool_BrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply Brush")
852 Rem = layout.operator(BTool_Remove.bl_idname, icon="X", text="Remove Brush")
853 Rem.thisObj = ""
854 Rem.Prop = "BRUSH"
857 def VIEW3D_BoolTool_Menu(self, context):
858 self.layout.menu(VIEW3D_MT_booltool_menu.bl_idname)
861 # ---------------- Toolshelf: Tools ---------------------
864 class VIEW3D_PT_booltool_tools(Panel):
865 bl_category = "objectmode"
866 bl_label = "Bool Tool"
867 bl_space_type = "VIEW_3D"
868 bl_region_type = "UI"
869 bl_context = "objectmode"
870 bl_options = {'DEFAULT_CLOSED'}
872 @classmethod
873 def poll(cls, context):
874 return context.active_object is not None
876 def draw(self, context):
877 layout = self.layout
879 col = layout.column(align=True)
880 col.label(text="Auto Boolean")
881 col.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
882 col.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
883 col.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
884 col.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
886 col = layout.column(align=True)
887 col.label(text="Brush Boolean")
888 col.operator(BTool_Diff.bl_idname, text="Difference", icon="SELECT_SUBTRACT")
889 col.operator(BTool_Union.bl_idname, text="Union", icon="SELECT_EXTEND")
890 col.operator(BTool_Inters.bl_idname, text="Intersect", icon="SELECT_INTERSECT")
891 col.operator(BTool_Slice.bl_idname, text="Slice", icon="SELECT_DIFFERENCE")
893 # TODO Draw Poly Brush
894 # main.separator()
896 # col = main.column(align=True)
897 # col.label(text="Draw:", icon="MESH_CUBE")
898 # col.separator()
899 # col.operator(BTool_DrawPolyBrush.bl_idname, icon="LINE_DATA")
902 # ---------- Toolshelf: Properties --------------------------------------------------------
905 class VIEW3D_PT_booltool_config(Panel):
906 bl_category = "objectmode"
907 bl_label = "Properties"
908 bl_space_type = "VIEW_3D"
909 bl_region_type = "UI"
910 bl_context = "objectmode"
911 bl_parent_id = "VIEW3D_PT_booltool_tools"
913 @classmethod
914 def poll(cls, context):
915 actObj = context.active_object
916 return isCanvas(actObj) or isBrush(actObj) # or isPolyBrush(actObj)
918 def draw(self, context):
919 layout = self.layout
920 actObj = context.active_object
922 row = layout.row(align=True)
924 if isCanvas(actObj):
926 row.label(text="CANVAS", icon="MESH_GRID")
927 row = layout.row()
928 row.prop(context.scene, "BoolHide", text="Hide Bool objects")
929 row = layout.row(align=True)
930 row.operator(BTool_AllBrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply All")
932 row = layout.row(align=True)
933 Rem = row.operator(BTool_Remove.bl_idname, icon="X", text="Remove All")
934 Rem.thisObj = ""
935 Rem.Prop = "CANVAS"
937 if isBrush(actObj):
938 layout.separator()
940 if isBrush(actObj):
942 if actObj["BoolToolBrush"] == "DIFFERENCE":
943 icon = "SELECT_SUBTRACT"
944 elif actObj["BoolToolBrush"] == "UNION":
945 icon = "SELECT_EXTEND"
946 elif actObj["BoolToolBrush"] == "INTERSECT":
947 icon = "SELECT_INTERSECT"
948 elif actObj["BoolToolBrush"] == "SLICE":
949 icon = "SELECT_DIFFERENCE"
951 row.label(text="BRUSH", icon=icon)
953 if actObj["BoolTool_FTransform"] == "True":
954 icon = "PMARKER_ACT"
955 else:
956 icon = "PMARKER"
957 if isFTransf():
958 pass
960 if isFTransf():
961 row = layout.row(align=True)
962 row.operator(BTool_EnableFTransform.bl_idname, text="Fast Vis", icon=icon)
963 row.operator(BTool_EnableThisBrush.bl_idname, text="Enable", icon="HIDE_OFF")
964 else:
965 row.operator(BTool_EnableThisBrush.bl_idname, icon="HIDE_OFF")
967 layout.operator(BTool_BrushToMesh.bl_idname, icon="MOD_LATTICE", text="Apply Brush")
968 Rem = layout.operator(BTool_Remove.bl_idname, icon="X", text="Remove Brush")
969 Rem.thisObj = ""
970 Rem.Prop = "BRUSH"
972 # TODO
973 # if isPolyBrush(actObj):
974 # layout.label(text="POLY BRUSH", icon="LINE_DATA")
975 # mod = actObj.modifiers["BTool_PolyBrush"]
976 # layout.prop(mod, "thickness", text="Size")
979 # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
982 class VIEW3D_PT_booltool_bviewer(Panel):
983 bl_category = "objectmode"
984 bl_label = "Brush Viewer"
985 bl_space_type = "VIEW_3D"
986 bl_region_type = "UI"
987 bl_context = "objectmode"
988 bl_parent_id = "VIEW3D_PT_booltool_tools"
990 @classmethod
991 def poll(cls, context):
992 actObj = bpy.context.active_object
994 if isCanvas(actObj):
995 return True
996 else:
997 return False
999 def draw(self, context):
1001 actObj = bpy.context.active_object
1003 if isCanvas(actObj):
1005 for mod in actObj.modifiers:
1006 container = self.layout.box()
1007 row = container.row(align=True)
1009 if "BTool_" in mod.name:
1011 if mod.operation == "DIFFERENCE":
1012 icon = "SELECT_SUBTRACT"
1013 elif mod.operation == "UNION":
1014 icon = "SELECT_EXTEND"
1015 elif mod.operation == "INTERSECT":
1016 icon = "SELECT_INTERSECT"
1017 elif mod.operation == "SLICE":
1018 icon = "SELECT_DIFFERENCE"
1020 objSelect = row.operator("btool.find_brush", text=mod.object.name, icon=icon, emboss=False)
1021 objSelect.obj = mod.object.name
1023 EnableIcon = "RESTRICT_VIEW_ON"
1024 if mod.show_viewport:
1025 EnableIcon = "RESTRICT_VIEW_OFF"
1026 Enable = row.operator(BTool_EnableBrush.bl_idname, icon=EnableIcon, emboss=False)
1027 Enable.thisObj = mod.object.name
1029 Remove = row.operator("btool.remove", text="", icon="X", emboss=False)
1030 Remove.thisObj = mod.object.name
1031 Remove.Prop = "THIS"
1033 else:
1034 row.label(text=mod.name)
1036 Up = row.operator("btool.move_stack", icon="TRIA_UP", emboss=False)
1037 Up.modif = mod.name
1038 Up.direction = "UP"
1040 Dw = row.operator("btool.move_stack", icon="TRIA_DOWN", emboss=False)
1041 Dw.modif = mod.name
1042 Dw.direction = "DOWN"
1045 # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
1048 shortcut_list = (
1049 ("3D View", None),
1050 ("Menu", "Ctrl Shift B"),
1052 ("Auto Operators", None),
1053 ("Difference", "Ctrl Shift Num -"),
1054 ("Union", "Ctrl Shift Num +"),
1055 ("Intersect", "Ctrl Shift Num *"),
1056 ("Slice", "Ctrl Shift Num /"),
1058 ("Brush Operators", None),
1059 ("Difference", "Ctrl Num -"),
1060 ("Union", "Ctrl Num +"),
1061 ("Intersect", "Ctrl Num *"),
1062 ("Slice", "Ctrl Num /"),
1063 ("Brush To Mesh", "Ctrl Num Enter"),
1064 ("All Brushes To Mesh", "Ctrl Shift Num Enter"),
1068 def UpdateBoolTool_Pref(self, context):
1069 if self.fast_transform:
1070 RegisterFastT()
1071 else:
1072 UnRegisterFastT()
1075 # Define Panel classes for updating
1076 panels = (
1077 VIEW3D_PT_booltool_tools,
1078 VIEW3D_PT_booltool_config,
1079 VIEW3D_PT_booltool_bviewer,
1083 def update_panels(self, context):
1084 try:
1085 for panel in panels:
1086 if "bl_rna" in panel.__dict__:
1087 bpy.utils.unregister_class(panel)
1089 for panel in panels:
1090 panel.bl_category = context.preferences.addons[
1091 __name__
1092 ].preferences.category
1093 bpy.utils.register_class(panel)
1095 except Exception as e:
1096 message = "Bool Tool: Updating Panel locations has failed"
1097 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
1100 def icon_tria(prop):
1101 if prop:
1102 return "TRIA_DOWN"
1103 return "TRIA_RIGHT"
1106 class PREFS_BoolTool_Props(AddonPreferences):
1107 bl_idname = __name__
1109 fast_transform: BoolProperty(
1110 name="Fast Transformations",
1111 update=UpdateBoolTool_Pref,
1112 description="Replace the Transform HotKeys (G,R,S)\n"
1113 "for a custom version that can optimize the visualization of Brushes",
1115 use_wire: BoolProperty(
1116 name="Display As Wirewrame",
1117 description="Display brush as wireframe instead of bounding box",
1119 category: StringProperty(
1120 name="Tab Name",
1121 description="Set sidebar tab name",
1122 default="Edit",
1123 update=update_panels,
1125 show_shortcuts: BoolProperty(name="Shortcuts")
1127 def draw(self, context):
1128 layout = self.layout
1129 layout.use_property_split = True
1130 layout.use_property_decorate = False
1132 col = layout.column()
1133 col.prop(self, "category")
1134 col.prop(self, "fast_transform")
1135 col.prop(self, "use_wire")
1137 col = layout.column()
1138 col.scale_y = 1.2
1139 col.use_property_split = False
1140 col.prop(self, "show_shortcuts", icon=icon_tria(self.show_shortcuts))
1142 if self.show_shortcuts:
1144 col = layout.column()
1146 for key_name, key_comb in shortcut_list:
1147 if key_comb is None:
1148 col.separator()
1149 col.label(text=key_name)
1150 else:
1151 row = col.row(align=True)
1152 row.scale_y = 0.7
1153 row.box().label(text=key_name)
1154 row.box().label(text=key_comb)
1157 # ------------------- Class List ------------------------------------------------
1159 classes = (
1160 PREFS_BoolTool_Props,
1161 VIEW3D_MT_booltool_menu,
1162 VIEW3D_PT_booltool_tools,
1163 VIEW3D_PT_booltool_config,
1164 VIEW3D_PT_booltool_bviewer,
1165 OBJECT_OT_BoolTool_Auto_Union,
1166 OBJECT_OT_BoolTool_Auto_Difference,
1167 OBJECT_OT_BoolTool_Auto_Intersect,
1168 OBJECT_OT_BoolTool_Auto_Slice,
1169 BTool_Union,
1170 BTool_Diff,
1171 BTool_Inters,
1172 BTool_Slice,
1173 # TODO Draw Poly Brush
1174 # BTool_DrawPolyBrush,
1175 BTool_Remove,
1176 BTool_AllBrushToMesh,
1177 BTool_BrushToMesh,
1178 BTool_FindBrush,
1179 BTool_MoveStack,
1180 BTool_EnableBrush,
1181 BTool_EnableThisBrush,
1182 BTool_EnableFTransform,
1183 BTool_FastTransform,
1187 # ------------------- REGISTER ------------------------------------------------
1189 addon_keymaps = []
1190 addon_keymapsFastT = []
1193 # Fast Transform HotKeys Register
1194 def RegisterFastT():
1195 wm = bpy.context.window_manager
1196 km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
1198 kmi = km.keymap_items.new(BTool_FastTransform.bl_idname, "G", "PRESS")
1199 kmi.properties.operator = "Translate"
1200 addon_keymapsFastT.append((km, kmi))
1202 kmi = km.keymap_items.new(BTool_FastTransform.bl_idname, "R", "PRESS")
1203 kmi.properties.operator = "Rotate"
1204 addon_keymapsFastT.append((km, kmi))
1206 kmi = km.keymap_items.new(BTool_FastTransform.bl_idname, "S", "PRESS")
1207 kmi.properties.operator = "Scale"
1208 addon_keymapsFastT.append((km, kmi))
1211 # Fast Transform HotKeys UnRegister
1212 def UnRegisterFastT():
1213 wm = bpy.context.window_manager
1214 kc = wm.keyconfigs.addon
1215 if kc:
1216 for km, kmi in addon_keymapsFastT:
1217 km.keymap_items.remove(kmi)
1219 addon_keymapsFastT.clear()
1222 def register():
1223 for cls in classes:
1224 bpy.utils.register_class(cls)
1225 update_panels(None, bpy.context)
1227 # Scene variables
1228 bpy.types.Scene.BoolHide = BoolProperty(
1229 default=False,
1230 description="Hide boolean objects",
1231 update=update_BoolHide,
1233 bpy.types.VIEW3D_MT_object.append(VIEW3D_BoolTool_Menu)
1235 wm = bpy.context.window_manager
1236 kc = wm.keyconfigs.addon
1238 # create the boolean menu hotkey
1239 if kc is not None:
1240 km = kc.keymaps.new(name="Object Mode")
1242 kmi = km.keymap_items.new("wm.call_menu", "B", "PRESS", ctrl=True, shift=True)
1243 kmi.properties.name = "VIEW3D_MT_booltool_menu"
1244 addon_keymaps.append((km, kmi))
1246 # Brush Operators
1247 kmi = km.keymap_items.new(BTool_Union.bl_idname, "NUMPAD_PLUS", "PRESS", ctrl=True)
1248 addon_keymaps.append((km, kmi))
1249 kmi = km.keymap_items.new(BTool_Diff.bl_idname, "NUMPAD_MINUS", "PRESS", ctrl=True)
1250 addon_keymaps.append((km, kmi))
1251 kmi = km.keymap_items.new(BTool_Inters.bl_idname, "NUMPAD_ASTERIX", "PRESS", ctrl=True)
1252 addon_keymaps.append((km, kmi))
1253 kmi = km.keymap_items.new(BTool_Slice.bl_idname, "NUMPAD_SLASH", "PRESS", ctrl=True)
1254 addon_keymaps.append((km, kmi))
1255 kmi = km.keymap_items.new(BTool_BrushToMesh.bl_idname, "NUMPAD_ENTER", "PRESS", ctrl=True)
1256 addon_keymaps.append((km, kmi))
1257 kmi = km.keymap_items.new(
1258 BTool_AllBrushToMesh.bl_idname,
1259 "NUMPAD_ENTER",
1260 "PRESS",
1261 ctrl=True,
1262 shift=True,
1264 addon_keymaps.append((km, kmi))
1266 # Auto Operators
1267 kmi = km.keymap_items.new(
1268 OBJECT_OT_BoolTool_Auto_Union.bl_idname,
1269 "NUMPAD_PLUS",
1270 "PRESS",
1271 ctrl=True,
1272 shift=True,
1274 addon_keymaps.append((km, kmi))
1275 kmi = km.keymap_items.new(
1276 OBJECT_OT_BoolTool_Auto_Difference.bl_idname,
1277 "NUMPAD_MINUS",
1278 "PRESS",
1279 ctrl=True,
1280 shift=True,
1282 addon_keymaps.append((km, kmi))
1283 kmi = km.keymap_items.new(
1284 OBJECT_OT_BoolTool_Auto_Intersect.bl_idname,
1285 "NUMPAD_ASTERIX",
1286 "PRESS",
1287 ctrl=True,
1288 shift=True,
1290 addon_keymaps.append((km, kmi))
1291 kmi = km.keymap_items.new(
1292 OBJECT_OT_BoolTool_Auto_Slice.bl_idname,
1293 "NUMPAD_SLASH",
1294 "PRESS",
1295 ctrl=True,
1296 shift=True,
1298 addon_keymaps.append((km, kmi))
1301 def unregister():
1302 # Keymapping
1303 # remove keymaps when add-on is deactivated
1304 wm = bpy.context.window_manager
1305 kc = wm.keyconfigs.addon
1306 if kc is not None:
1307 for km, kmi in addon_keymaps:
1308 km.keymap_items.remove(kmi)
1310 addon_keymaps.clear()
1311 UnRegisterFastT()
1312 bpy.types.VIEW3D_MT_object.remove(VIEW3D_BoolTool_Menu)
1313 del bpy.types.Scene.BoolHide
1315 for cls in classes:
1316 bpy.utils.unregister_class(cls)
1319 if __name__ == "__main__":
1320 register()