fracture_cell_setup: get particles from evaluated object
[blender-addons.git] / object_boolean_tools.py
blob56c11aec1423d8015d07493e7b78c5afa4414551
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, 0),
25 "blender": (2, 80, 0),
26 "location": "View3D > Sidebar > Edit Tab",
27 "description": "Bool Tool Hotkey: Ctrl Shift B",
28 "category": "Object",
31 import bpy
32 from bpy.types import (
33 AddonPreferences,
34 Operator,
35 Panel,
36 Menu,
38 from bpy.props import (
39 BoolProperty,
40 StringProperty,
44 # ------------------- Bool Tool FUNCTIONS -------------------------
45 # Utils:
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
53 for o in objs:
54 o.hide_viewport = hide_state
57 # Object is a Canvas
58 def isCanvas(_obj):
59 try:
60 if _obj["BoolToolRoot"]:
61 return True
62 except:
63 return False
66 # Object is a Brush Tool Bool
67 def isBrush(_obj):
68 try:
69 if _obj["BoolToolBrush"]:
70 return True
71 except:
72 return False
75 # Object is a Poly Brush Tool Bool collection
76 def isPolyBrush(_obj):
77 try:
78 if _obj["BoolToolPolyBrush"]:
79 return True
80 except:
81 return False
84 def BT_ObjectByName(obj):
85 for ob in bpy.context.view_layer.objects:
86 if isCanvas(ob) or isBrush(ob):
87 if ob.name == obj:
88 return ob
91 def FindCanvas(obj):
92 for ob in bpy.context.view_layer.objects:
93 if isCanvas(ob):
94 for mod in ob.modifiers:
95 if "BTool_" in mod.name:
96 if obj.name in mod.name:
97 return ob
100 def isFTransf():
101 preferences = bpy.context.preferences
102 addons = preferences.addons
103 addon_prefs = addons[__name__].preferences
104 if addon_prefs.fast_transform:
105 return True
106 else:
107 return False
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:
123 if (
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
133 if useWire:
134 selObj.display_type = "WIRE"
135 else:
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"
156 else:
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
169 # Restore the Brush
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)
190 if Prop == "THIS":
191 RemoveThis(thisObj_name)
193 # If the remove was called from the Properties:
194 else:
195 # Remove the Brush Property
196 if Prop == "BRUSH":
197 Canvas = FindCanvas(actObj)
198 if Canvas:
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
213 if Prop == "CANVAS":
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):
221 for obj in objList:
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
228 else:
229 mod.show_viewport = True
230 mod.show_render = True
233 # Find the Canvas and Enable this Brush
234 def EnableThisBrush(context, set):
235 canvas = None
236 for obj in bpy.context.view_layer.objects:
237 if obj != bpy.context.active_object:
238 if isCanvas(obj):
239 for mod in obj.modifiers:
240 if "BTool_" in mod.name:
241 if mod.object == bpy.context.active_object:
242 canvas = obj
244 for mod in canvas.modifiers:
245 if "BTool_" in mod.name:
246 if mod.object == bpy.context.active_object:
247 if set == "None":
248 if mod.show_viewport:
249 mod.show_viewport = False
250 mod.show_render = False
251 else:
252 mod.show_viewport = True
253 mod.show_render = True
254 else:
255 if set == "True":
256 mod.show_viewport = True
257 else:
258 mod.show_viewport = False
259 return
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"
268 else:
269 actObj["BoolTool_FTransform"] = "True"
270 return
273 # Apply All Brushes to the Canvas
274 def ApplyAll(context, list):
275 objDeleteList = []
276 for selObj in 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)
281 try:
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:
289 if isCanvas(obj):
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:
304 if isCanvas(obj):
305 for mod in obj.modifiers:
306 if "BTool_" + brush.name in mod.name:
307 # Apply This Brush
308 context.view_layer.objects.active = obj
309 try:
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")
317 # Garbage Collector
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"
328 bl_description = (
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"
333 count = 0
334 store_cont_draw = False
336 @classmethod
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
345 if start:
346 self.store_cont_draw = continuous
347 tool_settings.use_gpencil_continuous_drawing = True
348 else:
349 tool_settings.use_gpencil_continuous_drawing = self.store_cont_draw
351 def modal(self, context, event):
352 self.count += 1
353 actObj = bpy.context.active_object
354 if self.count == 1:
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)
360 pass
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"
384 mod.thickness = 1
385 mod.offset = 0
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()
397 return {"FINISHED"}
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")
404 return {"CANCELLED"}
406 return {"RUNNING_MODAL"}
408 def invoke(self, context, event):
409 if context.object:
410 self.set_cont_draw(context, start=True)
411 context.window_manager.modal_handler_add(self)
412 return {"RUNNING_MODAL"}
413 else:
414 self.report({"WARNING"}, "No active object, could not finish")
415 return {"CANCELLED"}
418 # Fast Transform
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("")
426 count = 0
428 def modal(self, context, event):
429 self.count += 1
430 actObj = bpy.context.active_object
431 useWire = bpy.context.preferences.addons[__name__].preferences.use_wire
432 if self.count == 1:
434 if isBrush(actObj) and actObj["BoolTool_FTransform"] == "True":
435 EnableThisBrush(bpy.context, "False")
436 if useWire:
437 actObj.display_type = "WIRE"
438 else:
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":
449 if isBrush(actObj):
450 EnableThisBrush(bpy.context, "True")
451 actObj.display_type = "WIRE"
452 return {"FINISHED"}
454 if event.type in {"RIGHTMOUSE", "ESC"}:
455 if isBrush(actObj):
456 EnableThisBrush(bpy.context, "True")
457 actObj.display_type = "WIRE"
458 return {"CANCELLED"}
460 return {"RUNNING_MODAL"}
462 def invoke(self, context, event):
463 if context.object:
464 context.window_manager.modal_handler_add(self)
465 return {"RUNNING_MODAL"}
466 else:
467 self.report({"WARNING"}, "No active object, could not finish")
468 return {"CANCELLED"}
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"}
482 @classmethod
483 def poll(cls, context):
484 return context.active_object is not None
486 def execute(self, context):
487 Operation(context, "UNION")
488 return {"FINISHED"}
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"}
498 @classmethod
499 def poll(cls, context):
500 return context.active_object is not None
502 def execute(self, context):
503 Operation(context, "INTERSECT")
504 return {"FINISHED"}
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"}
514 @classmethod
515 def poll(cls, context):
516 return context.active_object is not None
518 def execute(self, context):
519 Operation(context, "DIFFERENCE")
520 return {"FINISHED"}
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"}
530 @classmethod
531 def poll(cls, context):
532 return context.active_object is not None
534 def execute(self, context):
535 Operation(context, "SLICE")
536 return {"FINISHED"}
539 # Auto Boolean operators
540 # --------------------------------------------------------------------------------------
543 class Auto_Boolean:
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")
569 for ob in obs:
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
577 md.operation = mode
578 md.object = ob
580 bpy.ops.object.modifier_apply(modifier="Auto Boolean")
581 if not ob_delete:
582 return
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"}
592 mode = "UNION"
594 def execute(self, context):
595 self.objects_prepare()
596 self.boolean_operation()
597 return {"FINISHED"}
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"}
606 mode = "DIFFERENCE"
608 def execute(self, context):
609 self.objects_prepare()
610 self.boolean_operation()
611 return {"FINISHED"}
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"}
620 mode = "INTERSECT"
622 def execute(self, context):
623 self.objects_prepare()
624 self.boolean_operation()
625 return {"FINISHED"}
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)
653 return {"FINISHED"}
656 # Utils Class ---------------------------------------------------------------
658 # Find the Brush Selected in Three View
659 class BTool_FindBrush(Operator):
660 bl_idname = "btool.find_brush"
661 bl_label = ""
662 bl_description = "Find the selected brush"
664 obj: StringProperty("")
666 @classmethod
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)
677 return {"FINISHED"}
680 # Move The Modifier in The Stack Up or Down
681 class BTool_MoveStack(Operator):
682 bl_idname = "btool.move_stack"
683 bl_label = ""
684 bl_description = "Move this Brush Up/Down in the Stack"
686 modif: StringProperty("")
687 direction: StringProperty("")
689 @classmethod
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)
698 return {"FINISHED"}
701 # Enable or Disable a Brush in the Three View
702 class BTool_EnableBrush(Operator):
703 bl_idname = "btool.enable_brush"
704 bl_label = ""
705 bl_description = "Removes all BoolTool config assigned to it"
707 thisObj: StringProperty("")
709 @classmethod
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)
716 return {"FINISHED"}
719 # Enable or Disable a Brush Directly
720 class BTool_EnableThisBrush(Operator):
721 bl_idname = "btool.enable_this_brush"
722 bl_label = ""
723 bl_description = "Toggles this brush"
725 @classmethod
726 def poll(cls, context):
727 return context.active_object is not None
729 def execute(self, context):
730 EnableThisBrush(context, "None")
731 return {"FINISHED"}
734 # Enable or Disable a Brush Directly
735 class BTool_EnableFTransform(Operator):
736 bl_idname = "btool.enable_ftransf"
737 bl_label = ""
738 bl_description = "Use Fast Transformations to improve speed"
740 @classmethod
741 def poll(cls, context):
742 return context.active_object is not None
744 def execute(self, context):
745 EnableFTransf(context)
746 return {"FINISHED"}
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("")
761 @classmethod
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)
767 return {"FINISHED"}
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"}
777 @classmethod
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)
784 return {"FINISHED"}
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"}
794 @classmethod
795 def poll(cls, context):
797 if isBrush(context.active_object):
798 return True
799 else:
800 return False
802 def execute(self, context):
803 ApplyThisBrush(context, bpy.context.active_object)
804 return {"FINISHED"}
807 # TODO
808 # Apply This Brush To Mesh
811 # ------------------- MENU CLASSES ------------------------------
813 # 3Dview Header Menu
814 class VIEW3D_MT_booltool_menu(Menu):
815 bl_label = "BoolTool Operators"
816 bl_idname = "VIEW3D_MT_booltool_menu"
818 def draw(self, context):
819 layout = self.layout
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")
827 layout.separator()
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):
836 layout.separator()
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")
839 Rem.thisObj = ""
840 Rem.Prop = "CANVAS"
842 if isBrush(context.active_object):
843 layout.separator()
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")
846 Rem.thisObj = ""
847 Rem.Prop = "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"
864 @classmethod
865 def poll(cls, context):
866 return context.active_object is not None
868 def draw(self, context):
869 layout = self.layout
870 obj = context.active_object
871 obs_len = len(context.selected_objects)
873 row = layout.row()
874 row.alignment = "RIGHT"
875 row.scale_x = 1.5
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")
884 col.separator()
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")
892 main.separator()
894 col = main.column(align=True)
895 col.enabled = obs_len > 1
896 col.label(text="Brush Boolean", icon="MODIFIER")
897 col.separator()
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
904 # main.separator()
906 # col = main.column(align=True)
907 # col.label(text="Draw:", icon="MESH_CUBE")
908 # col.separator()
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"
922 @classmethod
923 def poll(cls, context):
925 result = False
926 actObj = bpy.context.active_object
927 if isCanvas(actObj) or isBrush(actObj) or isPolyBrush(actObj):
928 result = True
929 return result
931 def draw(self, context):
932 actObj = bpy.context.active_object
933 icon = ""
935 layout = self.layout
936 row = layout.row(align=True)
938 # CANVAS ---------------------------------------------------
939 if isCanvas(actObj):
940 row.label(text="CANVAS", icon="MESH_GRID")
941 row = layout.row()
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")
948 Rem.thisObj = ""
949 Rem.Prop = "CANVAS"
951 if isBrush(actObj):
952 layout.separator()
954 # BRUSH ------------------------------------------------------
955 if isBrush(actObj):
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)
969 icon = ""
970 if actObj["BoolTool_FTransform"] == "True":
971 icon = "PMARKER_ACT"
972 else:
973 icon = "PMARKER"
974 if isFTransf():
975 pass
977 if isFTransf():
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)
982 else:
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")
992 layout.separator()
994 if isBrush(actObj):
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")
999 Rem.thisObj = ""
1000 Rem.Prop = "BRUSH"
1002 layout.separator()
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"
1015 @classmethod
1016 def poll(cls, context):
1017 actObj = bpy.context.active_object
1019 if isCanvas(actObj):
1020 return True
1021 else:
1022 return False
1024 def draw(self, context):
1026 actObj = bpy.context.active_object
1027 icon = ""
1029 if isCanvas(actObj):
1031 for mod in actObj.modifiers:
1032 container = self.layout.box()
1033 row = container.row(align=True)
1034 icon = ""
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"
1058 # Stack Changer
1059 Up = row.operator("btool.move_stack", icon="TRIA_UP", emboss=False)
1060 Up.modif = mod.name
1061 Up.direction = "UP"
1063 Dw = row.operator("btool.move_stack", icon="TRIA_DOWN", emboss=False)
1064 Dw.modif = mod.name
1065 Dw.direction = "DOWN"
1067 else:
1068 row.label(mod.name)
1069 # Stack Changer
1070 Up = row.operator("btool.move_stack", icon="TRIA_UP", emboss=False)
1071 Up.modif = mod.name
1072 Up.direction = "UP"
1074 Dw = row.operator("btool.move_stack", icon="TRIA_DOWN", emboss=False)
1075 Dw.modif = mod.name
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")
1095 layout.separator()
1097 layout.label(text="Auto Boolean:")
1098 layout.label(text="Apply Boolean operation directly.")
1100 layout.separator()
1102 layout.label(text="Brush Boolean:")
1103 layout.label(text="Create a Boolean brush setup.")
1105 def execute(self, context):
1106 return {"FINISHED"}
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:
1117 RegisterFastT()
1118 else:
1119 UnRegisterFastT()
1122 # Add-ons Preferences Update Panel
1124 # Define Panel classes for updating
1125 panels = (
1126 VIEW3D_PT_booltool_tools,
1127 VIEW3D_PT_booltool_config,
1128 VIEW3D_PT_booltool_bviewer,
1132 def update_panels(self, context):
1133 try:
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[
1140 __name__
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",
1154 default=False,
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",
1161 default=False,
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",
1167 default=False,
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(
1172 name="Use Bmesh",
1173 default=False,
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",
1179 default="Edit",
1180 update=update_panels,
1182 Enable_Tab_01: BoolProperty(default=False)
1184 def draw(self, context):
1185 layout = self.layout
1186 split_percent = 0.3
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")
1201 layout.separator()
1203 layout.prop(self, "Enable_Tab_01", text="Hot Keys", icon="KEYINGSET")
1204 if self.Enable_Tab_01:
1205 row = layout.row()
1207 col = row.column()
1208 col.label(text="Hotkey List:")
1209 col.label(text="Menu: Ctrl Shift B")
1211 row = layout.row()
1212 col = row.column()
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 /")
1219 row = layout.row()
1220 col = row.column()
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 ------------------------------------------------
1232 classes = (
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,
1242 BTool_Union,
1243 BTool_Diff,
1244 BTool_Inters,
1245 BTool_Slice,
1246 # TODO Draw Poly Brush
1247 # BTool_DrawPolyBrush,
1248 BTool_Remove,
1249 BTool_AllBrushToMesh,
1250 BTool_BrushToMesh,
1251 BTool_FindBrush,
1252 BTool_MoveStack,
1253 BTool_EnableBrush,
1254 BTool_EnableThisBrush,
1255 BTool_EnableFTransform,
1256 BTool_FastTransform,
1257 WM_OT_BoolTool_Help,
1261 # ------------------- REGISTER ------------------------------------------------
1263 addon_keymaps = []
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
1289 if kc:
1290 for km, kmi in addon_keymapsFastT:
1291 km.keymap_items.remove(kmi)
1293 addon_keymapsFastT.clear()
1296 def register():
1297 for cls in classes:
1298 bpy.utils.register_class(cls)
1299 update_panels(None, bpy.context)
1301 # Scene variables
1302 bpy.types.Scene.BoolHide = BoolProperty(
1303 default=False,
1304 description="Hide boolean objects",
1305 update=update_BoolHide,
1308 bpy.types.VIEW3D_MT_object.append(VIEW3D_BoolTool_Menu)
1309 try:
1310 bpy.types.VIEW3D_MT_Object.prepend(VIEW3D_BoolTool_Menu)
1311 except:
1312 pass
1314 wm = bpy.context.window_manager
1315 kc = wm.keyconfigs.addon
1317 # create the boolean menu hotkey
1318 if kc is not None:
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))
1325 # Brush Operators
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,
1338 "NUMPAD_ENTER",
1339 "PRESS",
1340 ctrl=True,
1341 shift=True,
1343 addon_keymaps.append((km, kmi))
1345 # Auto Operators
1346 kmi = km.keymap_items.new(
1347 OBJECT_OT_BoolTool_Auto_Union.bl_idname,
1348 "NUMPAD_PLUS",
1349 "PRESS",
1350 ctrl=True,
1351 shift=True,
1353 addon_keymaps.append((km, kmi))
1354 kmi = km.keymap_items.new(
1355 OBJECT_OT_BoolTool_Auto_Difference.bl_idname,
1356 "NUMPAD_MINUS",
1357 "PRESS",
1358 ctrl=True,
1359 shift=True,
1361 addon_keymaps.append((km, kmi))
1362 kmi = km.keymap_items.new(
1363 OBJECT_OT_BoolTool_Auto_Intersect.bl_idname,
1364 "NUMPAD_ASTERIX",
1365 "PRESS",
1366 ctrl=True,
1367 shift=True,
1369 addon_keymaps.append((km, kmi))
1370 kmi = km.keymap_items.new(
1371 OBJECT_OT_BoolTool_Auto_Slice.bl_idname,
1372 "NUMPAD_SLASH",
1373 "PRESS",
1374 ctrl=True,
1375 shift=True,
1377 addon_keymaps.append((km, kmi))
1380 def unregister():
1381 # Keymapping
1382 # remove keymaps when add-on is deactivated
1383 wm = bpy.context.window_manager
1384 kc = wm.keyconfigs.addon
1385 if kc is not None:
1386 for km, kmi in addon_keymaps:
1387 km.keymap_items.remove(kmi)
1389 addon_keymaps.clear()
1390 UnRegisterFastT()
1392 bpy.types.VIEW3D_MT_object.remove(VIEW3D_BoolTool_Menu)
1393 try:
1394 bpy.types.VIEW3D_MT_Object.remove(VIEW3D_BoolTool_Menu)
1395 except:
1396 pass
1398 del bpy.types.Scene.BoolHide
1400 for cls in classes:
1401 bpy.utils.unregister_class(cls)
1404 if __name__ == "__main__":
1405 register()