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",
25 "blender": (2, 78, 0),
26 "location": "View3D > Toolshelf",
27 "description": "Bool Tools Hotkey: Ctrl Shift B",
28 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Object/BoolTool",
29 "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
34 from bpy
.app
.handlers
import persistent
35 from bpy
.types
import (
41 from bpy
.props
import (
48 # ------------------- Bool Tool FUNCTIONS------------------------------
51 # Hide boolean objects
52 def update_BoolHide(self
, context
):
53 ao
= context
.scene
.objects
.active
54 objs
= [i
.object for i
in ao
.modifiers
if i
.type == 'BOOLEAN']
55 hide_state
= context
.scene
.BoolHide
64 if _obj
["BoolToolRoot"]:
70 # Object is a Brush Tool Bool
73 if _obj
["BoolToolBrush"]:
79 # Object is a Poly Brush Tool Bool collection
80 def isPolyBrush(_obj
):
82 if _obj
["BoolToolPolyBrush"]:
88 def BT_ObjectByName(obj
):
89 for ob
in bpy
.context
.scene
.objects
:
90 if isCanvas(ob
) or isBrush(ob
):
96 for ob
in bpy
.context
.scene
.objects
:
98 for mod
in ob
.modifiers
:
99 if ("BTool_" in mod
.name
):
100 if (obj
.name
in mod
.name
):
105 user_preferences
= bpy
.context
.user_preferences
106 addons
= user_preferences
.addons
107 addon_prefs
= addons
[__name__
].preferences
108 if addon_prefs
.fast_transform
:
115 # EXPERIMENTAL FEATURES
116 def isMakeVertexGroup():
117 user_preferences = bpy.context.user_preferences
118 addon_prefs = user_preferences.addons[__name__].preferences
119 if addon_prefs.make_vertex_groups:
124 def isMakeBoundary():
125 user_preferences = bpy.context.user_preferences
126 addon_prefs = user_preferences.addons[__name__].preferences
127 if addon_prefs.make_boundary:
134 def ConvertToMesh(obj
):
135 act
= bpy
.context
.scene
.objects
.active
136 bpy
.context
.scene
.objects
.active
= obj
137 bpy
.ops
.object.convert(target
="MESH")
138 bpy
.context
.scene
.objects
.active
= act
141 # Do the Union, Difference and Intersection Operations with a Brush
142 def Operation(context
, _operation
):
144 prefs
= bpy
.context
.user_preferences
.addons
[__name__
].preferences
145 useWire
= prefs
.use_wire
146 solver
= prefs
.solver
148 for selObj
in bpy
.context
.selected_objects
:
149 if selObj
!= context
.active_object
and (selObj
.type == "MESH" or selObj
.type == "CURVE"):
150 if selObj
.type == "CURVE":
151 ConvertToMesh(selObj
)
152 actObj
= context
.active_object
153 selObj
.hide_render
= True
154 cyclesVis
= selObj
.cycles_visibility
155 # for obj in bpy.context.scene.objects:
157 # for mod in obj.modifiers:
158 # if(mod.name == "BTool_" + selObj.name):
159 # obj.modifiers.remove(mod)
162 selObj
.draw_type
= "WIRE"
164 selObj
.draw_type
= "BOUNDS"
166 cyclesVis
.camera
= False
167 cyclesVis
.diffuse
= False
168 cyclesVis
.glossy
= False
169 cyclesVis
.shadow
= False
170 cyclesVis
.transmission
= False
171 if _operation
== "SLICE":
172 # copies dupli_group property(empty), but group property is empty (users_group = None)
173 clone
= context
.active_object
.copy()
175 context
.scene
.objects
.link(clone
)
176 sliceMod
= clone
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN") # add mod to clone obj
177 sliceMod
.object = selObj
178 sliceMod
.operation
= "DIFFERENCE"
179 clone
["BoolToolRoot"] = True
180 newMod
= actObj
.modifiers
.new("BTool_" + selObj
.name
, "BOOLEAN")
181 newMod
.object = selObj
182 newMod
.solver
= solver
183 if _operation
== "SLICE":
184 newMod
.operation
= "INTERSECT"
186 newMod
.operation
= _operation
188 actObj
["BoolToolRoot"] = True
189 selObj
["BoolToolBrush"] = _operation
190 selObj
["BoolTool_FTransform"] = "False"
193 # Remove Obejcts form the BoolTool System
194 def Remove(context
, thisObj_name
, Prop
):
195 # Find the Brush pointed in the Tree View and Restore it, active is the Canvas
196 actObj
= context
.active_object
199 def RemoveThis(_thisObj_name
):
200 for obj
in bpy
.context
.scene
.objects
:
201 # if it's the brush object
202 if obj
.name
== _thisObj_name
:
203 cyclesVis
= obj
.cycles_visibility
204 obj
.draw_type
= "TEXTURED"
205 del obj
["BoolToolBrush"]
206 del obj
["BoolTool_FTransform"]
207 cyclesVis
.camera
= True
208 cyclesVis
.diffuse
= True
209 cyclesVis
.glossy
= True
210 cyclesVis
.shadow
= True
211 cyclesVis
.transmission
= True
213 # Remove it from the Canvas
214 for mod
in actObj
.modifiers
:
215 if ("BTool_" in mod
.name
):
216 if (_thisObj_name
in mod
.name
):
217 actObj
.modifiers
.remove(mod
)
220 RemoveThis(thisObj_name
)
222 # If the remove was called from the Properties:
224 # Remove the Brush Property
226 Canvas
= FindCanvas(actObj
)
228 for mod
in Canvas
.modifiers
:
229 if ("BTool_" in mod
.name
):
230 if (actObj
.name
in mod
.name
):
231 Canvas
.modifiers
.remove(mod
)
232 cyclesVis
= actObj
.cycles_visibility
233 actObj
.draw_type
= "TEXTURED"
234 del actObj
["BoolToolBrush"]
235 del actObj
["BoolTool_FTransform"]
236 cyclesVis
.camera
= True
237 cyclesVis
.diffuse
= True
238 cyclesVis
.glossy
= True
239 cyclesVis
.shadow
= True
240 cyclesVis
.transmission
= True
243 for mod
in actObj
.modifiers
:
244 if ("BTool_" in mod
.name
):
245 RemoveThis(mod
.object.name
)
248 # Tooble the Enable the Brush Object Propertie
249 def EnableBrush(context
, objList
, canvas
):
251 for mod
in canvas
.modifiers
:
252 if ("BTool_" in mod
.name
and mod
.object.name
== obj
):
254 if (mod
.show_viewport
):
255 mod
.show_viewport
= False
256 mod
.show_render
= False
258 mod
.show_viewport
= True
259 mod
.show_render
= True
262 # Find the Canvas and Enable this Brush
263 def EnableThisBrush(context
, set):
265 for obj
in bpy
.context
.scene
.objects
:
266 if obj
!= bpy
.context
.active_object
:
268 for mod
in obj
.modifiers
:
269 if ("BTool_" in mod
.name
):
270 if mod
.object == bpy
.context
.active_object
:
273 for mod
in canvas
.modifiers
:
274 if ("BTool_" in mod
.name
):
275 if mod
.object == bpy
.context
.active_object
:
277 if (mod
.show_viewport
):
278 mod
.show_viewport
= False
279 mod
.show_render
= False
281 mod
.show_viewport
= True
282 mod
.show_render
= True
285 mod
.show_viewport
= True
287 mod
.show_viewport
= False
291 # Tooble the Fast Transform Propertie of the Active Brush
292 def EnableFTransf(context
):
293 actObj
= bpy
.context
.active_object
295 if actObj
["BoolTool_FTransform"] == "True":
296 actObj
["BoolTool_FTransform"] = "False"
298 actObj
["BoolTool_FTransform"] = "True"
302 # Apply All Brushes to the Canvas
303 def ApplyAll(context
, list):
306 if isCanvas(selObj
) and selObj
== context
.active_object
:
307 for mod
in selObj
.modifiers
:
308 if ("BTool_" in mod
.name
):
309 objDeleteList
.append(mod
.object)
311 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
312 except: # if fails the means it is multiuser data
313 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
314 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
315 del selObj
['BoolToolRoot']
317 for obj
in context
.scene
.objects
:
319 for mod
in obj
.modifiers
:
320 if mod
.type == 'BOOLEAN':
321 if mod
.object in objDeleteList
: # do not delete brush that is used by another canvas
322 objDeleteList
.remove(mod
.object) # remove it from deletion
324 bpy
.ops
.object.select_all(action
='DESELECT')
325 for obj
in objDeleteList
:
327 bpy
.ops
.object.delete()
330 # Apply This Brush to the Canvas
331 def ApplyThisBrush(context
, brush
):
332 for obj
in context
.scene
.objects
:
334 for mod
in obj
.modifiers
:
335 if ("BTool_" + brush
.name
in mod
.name
):
338 if isMakeVertexGroup():
339 # Turn all faces of the Brush selected
340 bpy.context.scene.objects.active = brush
341 bpy.ops.object.mode_set(mode='EDIT')
342 bpy.ops.mesh.select_all(action='SELECT')
343 bpy.ops.object.mode_set(mode='OBJECT')
345 # Turn off al faces of the Canvas selected
346 bpy.context.scene.objects.active = canvas
347 bpy.ops.object.mode_set(mode='EDIT')
348 bpy.ops.mesh.select_all(action='DESELECT')
349 bpy.ops.object.mode_set(mode='OBJECT')
353 context
.scene
.objects
.active
= obj
355 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
356 except: # if fails the means it is multiuser data
357 context
.active_object
.data
= context
.active_object
.data
.copy() # so just make data unique
358 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
359 bpy
.ops
.object.select_all(action
='TOGGLE')
360 bpy
.ops
.object.select_all(action
='DESELECT')
364 if isMakeVertexGroup():
366 bpy.ops.object.mode_set(mode='EDIT')
367 bpy.ops.object.vertex_group_assign_new()
368 bpy.ops.mesh.select_all(action='DESELECT')
369 bpy.ops.object.mode_set(mode='OBJECT')
371 canvas.vertex_groups.active.name = "BTool_" + brush.name
376 # bpy.ops.object.delete()
379 def GCollector(_obj
):
382 for mod
in _obj
.modifiers
:
383 if ("BTool_" in mod
.name
):
385 if mod
.object is None:
386 _obj
.modifiers
.remove(mod
)
388 del _obj
["BoolToolRoot"]
391 # Handle the callbacks when modifing things in the scene
393 def HandleScene(scene
):
394 if bpy
.data
.objects
.is_updated
:
395 for ob
in bpy
.data
.objects
:
400 # ------------------ Bool Tool OPERATORS-----------------------------------------------------
402 class BTool_DrawPolyBrush(Operator
):
403 """Draw Polygonal Mask, can be applyied to Canvas > Brush or Directly. ESC to Exit"""
404 bl_idname
= "btool.draw_polybrush"
405 bl_label
= "Draw Poly Brush"
410 def poll(cls
, context
):
411 return context
.active_object
is not None
413 def modal(self
, context
, event
):
415 actObj
= bpy
.context
.active_object
418 bpy
.ops
.gpencil
.draw('INVOKE_DEFAULT', mode
="DRAW_POLY")
420 if event
.type in {'RET', 'NUMPAD_ENTER'}:
422 bpy
.ops
.gpencil
.convert(type='POLY')
423 for obj
in context
.selected_objects
:
424 if obj
.type == "CURVE":
425 obj
.name
= "PolyDraw"
426 bpy
.context
.scene
.objects
.active
= obj
427 bpy
.ops
.object.select_all(action
='DESELECT')
429 bpy
.ops
.object.convert(target
="MESH")
430 bpy
.ops
.object.mode_set(mode
='EDIT')
431 bpy
.ops
.mesh
.select_all(action
='SELECT')
432 bpy
.ops
.mesh
.edge_face_add()
433 bpy
.ops
.mesh
.flip_normals()
434 bpy
.ops
.object.mode_set(mode
='OBJECT')
435 bpy
.ops
.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
436 bpy
.ops
.object.modifier_add(type="SOLIDIFY")
437 for mod
in obj
.modifiers
:
438 if mod
.name
== "Solidify":
439 mod
.name
= "BTool_PolyBrush"
442 obj
["BoolToolPolyBrush"] = True
444 bpy
.ops
.object.select_all(action
='DESELECT')
445 bpy
.context
.scene
.objects
.active
= actObj
446 bpy
.context
.scene
.update()
450 bpy
.context
.scene
.grease_pencil
.clear()
451 bpy
.ops
.gpencil
.data_unlink()
455 if event
.type in {'ESC'}:
456 bpy
.ops
.ed
.undo() # remove o Grease Pencil
459 return {'RUNNING_MODAL'}
461 def invoke(self
, context
, event
):
463 context
.window_manager
.modal_handler_add(self
)
464 return {'RUNNING_MODAL'}
466 self
.report({'WARNING'}, "No active object, could not finish")
471 class BTool_FastTransform(Operator
):
472 """Enable Fast Transform"""
473 bl_idname
= "btool.fast_transform"
474 bl_label
= "Fast Transform"
476 operator
= StringProperty("")
480 def modal(self
, context
, event
):
482 actObj
= bpy
.context
.active_object
483 useWire
= bpy
.context
.user_preferences
.addons
[__name__
].preferences
.use_wire
486 if isBrush(actObj
) and actObj
["BoolTool_FTransform"] == "True":
487 EnableThisBrush(bpy
.context
, "False")
489 actObj
.draw_type
= "WIRE"
491 actObj
.draw_type
= "BOUNDS"
493 if self
.operator
== "Translate":
494 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
495 if self
.operator
== "Rotate":
496 bpy
.ops
.transform
.rotate('INVOKE_DEFAULT')
497 if self
.operator
== "Scale":
498 bpy
.ops
.transform
.resize('INVOKE_DEFAULT')
500 if event
.type == 'LEFTMOUSE':
502 EnableThisBrush(bpy
.context
, "True")
503 actObj
.draw_type
= "WIRE"
506 if event
.type in {'RIGHTMOUSE', 'ESC'}:
508 EnableThisBrush(bpy
.context
, "True")
509 actObj
.draw_type
= "WIRE"
512 return {'RUNNING_MODAL'}
514 def invoke(self
, context
, event
):
516 context
.window_manager
.modal_handler_add(self
)
517 return {'RUNNING_MODAL'}
519 self
.report({'WARNING'}, "No active object, could not finish")
523 # ------------------- Bool Tool OPERATOR CLASSES --------------------------------------------------------
525 # Brush Operators --------------------------------------------
527 # Boolean Union Operator
528 class BTool_Union(Operator
):
529 """This operator add a union brush to a canvas"""
530 bl_idname
= "btool.boolean_union"
531 bl_label
= "Brush Union"
534 def poll(cls
, context
):
535 return context
.active_object
is not None
537 def execute(self
, context
):
538 Operation(context
, "UNION")
542 # Boolean Intersection Operator
543 class BTool_Inters(Operator
):
544 """This operator add a intersect brush to a canvas"""
545 bl_idname
= "btool.boolean_inters"
546 bl_label
= "Brush Intersection"
549 def poll(cls
, context
):
550 return context
.active_object
is not None
552 def execute(self
, context
):
553 Operation(context
, "INTERSECT")
557 # Boolean Difference Operator
558 class BTool_Diff(Operator
):
559 """This operator add a difference brush to a canvas"""
560 bl_idname
= "btool.boolean_diff"
561 bl_label
= "Brush Difference"
564 def poll(cls
, context
):
565 return context
.active_object
is not None
567 def execute(self
, context
):
568 Operation(context
, "DIFFERENCE")
572 # Boolean Slices Operator
573 class BTool_Slice(Operator
):
574 """This operator add a intersect brush to a canvas"""
575 bl_idname
= "btool.boolean_slice"
576 bl_label
= "Brush Slice"
579 def poll(cls
, context
):
580 return context
.active_object
is not None
582 def execute(self
, context
):
583 Operation(context
, "SLICE")
587 # Auto Boolean operators (maintainer Mikhail Rachinskiy) -------------------------------
591 solver
= EnumProperty(
592 name
="Boolean Solver",
593 items
=(('BMESH', "BMesh", "BMesh solver is faster, but less stable "
594 "and cannot handle coplanar geometry"),
595 ('CARVE', "Carve", "Carve solver is slower, but more stable "
596 "and can handle simple cases of coplanar geometry")),
597 description
="Specify solver for boolean operation",
598 options
={'SKIP_SAVE'},
602 self
.solver
= bpy
.context
.user_preferences
.addons
[__name__
].preferences
.solver
604 def objects_prepare(self
):
605 for ob
in bpy
.context
.selected_objects
:
606 if ob
.type != 'MESH':
608 bpy
.ops
.object.make_single_user(object=True, obdata
=True)
609 bpy
.ops
.object.convert(target
='MESH')
611 def mesh_selection(self
, ob
, select_action
):
612 scene
= bpy
.context
.scene
613 obj
= bpy
.context
.active_object
615 scene
.objects
.active
= ob
616 bpy
.ops
.object.mode_set(mode
='EDIT')
618 bpy
.ops
.mesh
.reveal()
619 bpy
.ops
.mesh
.select_all(action
=select_action
)
621 bpy
.ops
.object.mode_set(mode
='OBJECT')
622 scene
.objects
.active
= obj
624 def boolean_operation(self
):
625 obj
= bpy
.context
.active_object
627 obs
= bpy
.context
.selected_objects
629 self
.mesh_selection(obj
, 'DESELECT')
631 self
.mesh_selection(ob
, 'SELECT')
632 self
.boolean_mod(obj
, ob
, self
.mode
)
635 def boolean_mod(self
, obj
, ob
, mode
, ob_delete
=True):
636 md
= obj
.modifiers
.new("Auto Boolean", 'BOOLEAN')
637 md
.show_viewport
= False
639 md
.solver
= self
.solver
642 bpy
.ops
.object.modifier_apply(modifier
="Auto Boolean")
645 bpy
.context
.scene
.objects
.unlink(ob
)
646 bpy
.data
.objects
.remove(ob
)
649 class Auto_Union(AutoBoolean
, Operator
):
650 """Combine selected objects"""
651 bl_idname
= "btool.auto_union"
653 bl_options
= {'REGISTER', 'UNDO'}
657 def execute(self
, context
):
658 self
.objects_prepare()
659 self
.boolean_operation()
663 class Auto_Difference(AutoBoolean
, Operator
):
664 """Subtract selected objects from active object"""
665 bl_idname
= "btool.auto_difference"
666 bl_label
= "Difference"
667 bl_options
= {'REGISTER', 'UNDO'}
671 def execute(self
, context
):
672 self
.objects_prepare()
673 self
.boolean_operation()
677 class Auto_Intersect(AutoBoolean
, Operator
):
678 """Keep only intersecting geometry"""
679 bl_idname
= "btool.auto_intersect"
680 bl_label
= "Intersect"
681 bl_options
= {'REGISTER', 'UNDO'}
685 def execute(self
, context
):
686 self
.objects_prepare()
687 self
.boolean_operation()
691 class Auto_Slice(AutoBoolean
, Operator
):
692 """Slice active object along the selected object (can handle only two objects at a time)"""
693 bl_idname
= "btool.auto_slice"
695 bl_options
= {'REGISTER', 'UNDO'}
697 def execute(self
, context
):
698 self
.objects_prepare()
700 scene
= context
.scene
701 obj
= context
.active_object
703 ob
= context
.selected_objects
[0]
705 self
.mesh_selection(obj
, 'DESELECT')
706 self
.mesh_selection(ob
, 'SELECT')
708 obj_copy
= obj
.copy()
709 obj_copy
.data
= obj
.data
.copy()
710 scene
.objects
.link(obj_copy
)
712 self
.boolean_mod(obj
, ob
, 'DIFFERENCE', ob_delete
=False)
713 scene
.objects
.active
= obj_copy
714 self
.boolean_mod(obj_copy
, ob
, 'INTERSECT')
715 obj_copy
.select
= True
720 class Auto_Subtract(AutoBoolean
, Operator
):
721 """Subtract selected object from active object, """ \
722 """subtracted object not removed (can handle only two objects at a time)"""
723 bl_idname
= "btool.auto_subtract"
724 bl_label
= "Subtract"
725 bl_options
= {'REGISTER', 'UNDO'}
727 def execute(self
, context
):
728 self
.objects_prepare()
730 obj
= context
.active_object
732 ob
= context
.selected_objects
[0]
734 self
.mesh_selection(obj
, 'DESELECT')
735 self
.mesh_selection(ob
, 'SELECT')
736 self
.boolean_mod(obj
, ob
, 'DIFFERENCE', ob_delete
=False)
741 # Utils Class ---------------------------------------------------------------
743 # Find the Brush Selected in Three View
744 class BTool_FindBrush(Operator
):
745 """Find the this brush"""
746 bl_idname
= "btool.find_brush"
748 obj
= StringProperty("")
751 def poll(cls
, context
):
752 return context
.active_object
is not None
754 def execute(self
, context
):
755 for ob
in bpy
.context
.scene
.objects
:
756 if (ob
.name
== self
.obj
):
757 bpy
.ops
.object.select_all(action
='TOGGLE')
758 bpy
.ops
.object.select_all(action
='DESELECT')
759 bpy
.context
.scene
.objects
.active
= ob
764 # Mode The Modifier in The Stack Up or Down
765 class BTool_MoveStack(Operator
):
766 """Move this Brush Up/Down in the Stack"""
767 bl_idname
= "btool.move_stack"
769 modif
= StringProperty("")
770 direction
= StringProperty("")
773 def poll(cls
, context
):
774 return context
.active_object
is not None
776 def execute(self
, context
):
777 if (self
.direction
== "UP"):
778 bpy
.ops
.object.modifier_move_up(modifier
=self
.modif
)
779 if (self
.direction
== "DOWN"):
780 bpy
.ops
.object.modifier_move_down(modifier
=self
.modif
)
784 # Enable or Disable a Brush in th Three View
785 class BTool_EnableBrush(Operator
):
786 """Removes all BoolTool config assigned to it"""
787 bl_idname
= "btool.enable_brush"
790 thisObj
= StringProperty("")
793 def poll(cls
, context
):
794 return context
.active_object
is not None
796 def execute(self
, context
):
797 # in this case is just one object but the function accept more than one at once
798 EnableBrush(context
, [self
.thisObj
], context
.active_object
)
802 # Enable or Disabel a Brush Directly
803 class BTool_EnableThisBrush(Operator
):
804 """ Toggles this brush"""
805 bl_idname
= "btool.enable_this_brush"
809 def poll(cls
, context
):
810 return context
.active_object
is not None
812 def execute(self
, context
):
813 EnableThisBrush(context
, "None")
817 # Enable or Disabel a Brush Directly
818 class BTool_EnableFTransform(Operator
):
819 """Use Fast Transformations to improve speed"""
820 bl_idname
= "btool.enable_ftransf"
824 def poll(cls
, context
):
825 return context
.active_object
is not None
827 def execute(self
, context
):
828 EnableFTransf(context
)
832 # Other Operations -------------------------------------------------------
834 # Remove a Brush or a Canvas
835 class BTool_Remove(Operator
):
836 """Removes all BoolTool config assigned to it"""
837 bl_idname
= "btool.remove"
839 bl_options
= {'UNDO'}
840 thisObj
= StringProperty("")
841 Prop
= StringProperty("")
844 def poll(cls
, context
):
845 return context
.active_object
is not None
847 def execute(self
, context
):
848 Remove(context
, self
.thisObj
, self
.Prop
)
852 # Apply All to Canvas
853 class BTool_AllBrushToMesh(Operator
):
854 """Apply all brushes of this canvas"""
855 bl_idname
= "btool.to_mesh"
856 bl_label
= "Apply All Canvas"
857 bl_options
= {'UNDO'}
860 def poll(cls
, context
):
861 return context
.active_object
is not None
863 def execute(self
, context
):
864 list = bpy
.context
.selected_objects
865 ApplyAll(context
, list)
869 # Apply This Brush to the Canvas
870 class BTool_BrushToMesh(Operator
):
871 """Apply this brush to the canvas"""
872 bl_idname
= "btool.brush_to_mesh"
873 bl_label
= "Apply this Brush to Canvas"
874 bl_options
= {'UNDO'}
877 def poll(cls
, context
):
879 if isBrush(context
.active_object
):
884 def execute(self
, context
):
885 ApplyThisBrush(context
, bpy
.context
.active_object
)
890 # Apply This Brush To Mesh
893 # ------------------- MENU CLASSES ------------------------------
896 class BoolTool_Menu(Menu
):
897 bl_label
= "BoolTool Operators"
898 bl_idname
= "OBJECT_MT_BoolTool_Menu"
900 def draw(self
, context
):
903 layout
.label("Auto Boolean:")
904 layout
.operator(Auto_Difference
.bl_idname
, icon
="ROTACTIVE")
905 layout
.operator(Auto_Union
.bl_idname
, icon
="ROTATECOLLECTION")
906 layout
.operator(Auto_Intersect
.bl_idname
, icon
="ROTATECENTER")
907 layout
.operator(Auto_Slice
.bl_idname
, icon
="ROTATECENTER")
908 layout
.operator(Auto_Subtract
.bl_idname
, icon
="ROTACTIVE")
911 layout
.label("Brush Boolean:")
912 layout
.operator(BTool_Diff
.bl_idname
, icon
="ROTACTIVE")
913 layout
.operator(BTool_Union
.bl_idname
, icon
="ROTATECOLLECTION")
914 layout
.operator(BTool_Inters
.bl_idname
, icon
="ROTATECENTER")
915 layout
.operator(BTool_Slice
.bl_idname
, icon
="ROTATECENTER")
918 if (isCanvas(context
.active_object
)):
920 layout
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
921 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove All")
925 if (isBrush(context
.active_object
)):
927 layout
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
928 Rem
= layout
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove Brush")
933 def VIEW3D_BoolTool_Menu(self
, context
):
934 self
.layout
.menu(BoolTool_Menu
.bl_idname
)
937 # ---------------- Toolshelf: Tools ---------------------
939 class BoolTool_Tools(Panel
):
940 bl_category
= "Tools"
941 bl_label
= "Bool Tools"
942 bl_idname
= "BoolTool_Tools"
943 bl_space_type
= "VIEW_3D"
944 bl_region_type
= "TOOLS"
945 bl_context
= 'objectmode'
948 def poll(cls
, context
):
949 return context
.active_object
is not None
951 def draw(self
, context
):
953 obj
= context
.active_object
954 obs_len
= len(context
.selected_objects
)
956 row
= layout
.split(0.7)
957 row
.label("Bool Tools:")
958 row
.operator("help.bool_tool", text
="", icon
="QUESTION")
960 main
= layout
.column(align
=True)
961 main
.enabled
= obj
.type == 'MESH' and obs_len
> 0
965 col
= main
.column(align
=True)
966 col
.enabled
= obs_len
> 1
967 col
.label("Auto Boolean:", icon
="MODIFIER")
969 col
.operator(Auto_Difference
.bl_idname
, icon
="ROTACTIVE")
970 col
.operator(Auto_Union
.bl_idname
, icon
="ROTATECOLLECTION")
971 col
.operator(Auto_Intersect
.bl_idname
, icon
="ROTATECENTER")
975 col
= main
.column(align
=True)
976 col
.enabled
= obs_len
== 2
977 col
.operator(Auto_Slice
.bl_idname
, icon
="ROTATECENTER")
978 col
.operator(Auto_Subtract
.bl_idname
, icon
="ROTACTIVE")
982 col
= main
.column(align
=True)
983 col
.enabled
= obs_len
> 1
984 col
.label("Brush Boolean:", icon
="MODIFIER")
986 col
.operator(BTool_Diff
.bl_idname
, text
="Difference", icon
="ROTACTIVE")
987 col
.operator(BTool_Union
.bl_idname
, text
="Union", icon
="ROTATECOLLECTION")
988 col
.operator(BTool_Inters
.bl_idname
, text
="Intersect", icon
="ROTATECENTER")
989 col
.operator(BTool_Slice
.bl_idname
, text
="Slice", icon
="ROTATECENTER")
993 col
= main
.column(align
=True)
994 col
.label("Draw:", icon
="MESH_CUBE")
996 col
.operator(BTool_DrawPolyBrush
.bl_idname
, icon
="LINE_DATA")
999 # ---------- Toolshelf: Properties --------------------------------------------------------
1001 class BoolTool_Config(Panel
):
1002 bl_category
= "Tools"
1003 bl_label
= "Properties"
1004 bl_idname
= "BoolTool_BConfig"
1005 bl_space_type
= "VIEW_3D"
1006 bl_region_type
= "TOOLS"
1007 bl_context
= "objectmode"
1010 def poll(cls
, context
):
1013 actObj
= bpy
.context
.active_object
1014 if (isCanvas(actObj
) or isBrush(actObj
) or isPolyBrush(actObj
)):
1018 def draw(self
, context
):
1019 actObj
= bpy
.context
.active_object
1022 layout
= self
.layout
1023 row
= layout
.row(True)
1025 # CANVAS ---------------------------------------------------
1026 if isCanvas(actObj
):
1027 row
.label("CANVAS", icon
="MESH_GRID")
1029 row
.prop(context
.scene
, 'BoolHide', text
="Hide Bool objects")
1030 row
= layout
.row(True)
1031 row
.operator(BTool_AllBrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply All")
1033 row
= layout
.row(True)
1034 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove All")
1041 # BRUSH ------------------------------------------------------
1044 if (actObj
["BoolToolBrush"] == "UNION"):
1045 icon
= "ROTATECOLLECTION"
1046 if (actObj
["BoolToolBrush"] == "DIFFERENCE"):
1047 icon
= "ROTATECENTER"
1048 if (actObj
["BoolToolBrush"] == "INTERSECT"):
1050 if (actObj
["BoolToolBrush"] == "SLICE"):
1051 icon
= "ROTATECENTER"
1053 row
= layout
.row(True)
1054 row
.label("BRUSH", icon
=icon
)
1057 if actObj
["BoolTool_FTransform"] == "True":
1058 icon
= "PMARKER_ACT"
1065 row
= layout
.row(True)
1066 row
.operator(BTool_EnableFTransform
.bl_idname
, text
="Fast Vis", icon
=icon
)
1067 row
.operator(BTool_EnableThisBrush
.bl_idname
, text
="Enable", icon
="VISIBLE_IPO_ON")
1068 row
= layout
.row(True)
1070 row
.operator(BTool_EnableThisBrush
.bl_idname
, icon
="VISIBLE_IPO_ON")
1071 row
= layout
.row(True)
1073 if isPolyBrush(actObj
):
1074 row
= layout
.row(False)
1075 row
.label("POLY BRUSH", icon
="LINE_DATA")
1076 mod
= actObj
.modifiers
["BTool_PolyBrush"]
1077 row
= layout
.row(False)
1078 row
.prop(mod
, "thickness", text
="Size")
1082 row
= layout
.row(True)
1083 row
.operator(BTool_BrushToMesh
.bl_idname
, icon
="MOD_LATTICE", text
="Apply Brush")
1084 row
= layout
.row(True)
1085 Rem
= row
.operator(BTool_Remove
.bl_idname
, icon
="CANCEL", text
="Remove Brush")
1092 # ---------- Toolshelf: Brush Viewer -------------------------------------------------------
1094 class BoolTool_BViwer(Panel
):
1095 bl_label
= "Brush Viewer"
1096 bl_idname
= "BoolTool_BViwer"
1097 bl_space_type
= "VIEW_3D"
1098 bl_region_type
= "TOOLS"
1099 bl_category
= "Tools"
1100 bl_context
= "objectmode"
1103 def poll(cls
, context
):
1104 actObj
= bpy
.context
.active_object
1106 if isCanvas(actObj
):
1111 def draw(self
, context
):
1113 actObj
= bpy
.context
.active_object
1116 if isCanvas(actObj
):
1118 for mod
in actObj
.modifiers
:
1119 container
= self
.layout
.box()
1120 row
= container
.row(True)
1122 if ("BTool_" in mod
.name
):
1123 if (mod
.operation
== "UNION"):
1124 icon
= "ROTATECOLLECTION"
1125 if (mod
.operation
== "DIFFERENCE"):
1126 icon
= "ROTATECENTER"
1127 if (mod
.operation
== "INTERSECT"):
1129 if (mod
.operation
== "SLICE"):
1130 icon
= "ROTATECENTER"
1132 objSelect
= row
.operator("btool.find_brush", text
=mod
.object.name
, icon
=icon
, emboss
=False)
1133 objSelect
.obj
= mod
.object.name
1135 EnableIcon
= "RESTRICT_VIEW_ON"
1136 if (mod
.show_viewport
):
1137 EnableIcon
= "RESTRICT_VIEW_OFF"
1138 Enable
= row
.operator(BTool_EnableBrush
.bl_idname
, icon
=EnableIcon
, emboss
=False)
1139 Enable
.thisObj
= mod
.object.name
1141 Remove
= row
.operator("btool.remove", icon
="CANCEL", emboss
=False)
1142 Remove
.thisObj
= mod
.object.name
1143 Remove
.Prop
= "THIS"
1146 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1150 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1152 Dw
.direction
= "DOWN"
1157 Up
= row
.operator("btool.move_stack", icon
="TRIA_UP", emboss
=False)
1161 Dw
= row
.operator("btool.move_stack", icon
="TRIA_DOWN", emboss
=False)
1163 Dw
.direction
= "DOWN"
1166 # ------------------ BOOL TOOL Help ----------------------------
1167 class BoolTool_help(Operator
):
1169 bl_idname
= "help.bool_tool"
1172 def draw(self
, context
):
1173 layout
= self
.layout
1174 layout
.label("To use:")
1175 layout
.label("Select two or more objects.")
1176 layout
.label("Auto Boolean:")
1177 layout
.label("Apply boolean operation directly.")
1178 layout
.label("Brush Boolean:")
1179 layout
.label("Create boolean brush set up.")
1181 def execute(self
, context
):
1184 def invoke(self
, context
, event
):
1185 return context
.window_manager
.invoke_popup(self
, width
=220)
1188 # ------------------ BOOL TOOL ADD-ON PREFERENCES ----------------------------
1190 def UpdateBoolTool_Pref(self
, context
):
1191 if self
.fast_transform
:
1197 # Add-ons Preferences Update Panel
1199 # Define Panel classes for updating
1207 def update_panel(self
, context
):
1208 message
= "Bool Tool: Updating Panel locations has failed"
1210 for panel
in panels
:
1211 if "bl_rna" in panel
.__dict
__:
1212 bpy
.utils
.unregister_class(panel
)
1214 for panel
in panels
:
1215 panel
.bl_category
= context
.user_preferences
.addons
[__name__
].preferences
.category
1216 bpy
.utils
.register_class(panel
)
1218 except Exception as e
:
1219 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1223 class BoolTool_Pref(AddonPreferences
):
1224 bl_idname
= __name__
1226 fast_transform
= BoolProperty(
1227 name
="Fast Transformations",
1229 update
=UpdateBoolTool_Pref
,
1230 description
="Replace the Transform HotKeys (G,R,S)\n"
1231 "for a custom version that can optimize the visualization of Brushes",
1233 make_vertex_groups
= BoolProperty(
1234 name
="Make Vertex Groups",
1236 description
="When Applying a Brush to the Object it will create\n"
1237 "a new vertex group for the new faces",
1239 make_boundary
= BoolProperty(
1240 name
="Make Boundary",
1242 description
="When Apply a Brush to the Object it will create a\n"
1243 "new vertex group of the bondary boolean area",
1245 use_wire
= BoolProperty(
1248 description
="Use The Wireframe Instead of Bounding Box for visualization",
1250 category
= StringProperty(
1251 name
="Tab Category",
1252 description
="Choose a name for the category of the panel",
1254 update
=update_panel
,
1256 solver
= EnumProperty(
1257 name
="Boolean Solver",
1258 items
=(('BMESH', "BMesh", "BMesh solver is faster, but less stable "
1259 "and cannot handle coplanar geometry"),
1260 ('CARVE', "Carve", "Carve solver is slower, but more stable "
1261 "and can handle simple cases of coplanar geometry")),
1263 description
="Specify solver for boolean operations",
1265 Enable_Tab_01
= BoolProperty(
1269 def draw(self
, context
):
1270 layout
= self
.layout
1273 split
= layout
.split(percentage
=split_percent
)
1274 col
= split
.column()
1275 col
.label(text
="Tab Category:")
1276 col
= split
.column()
1278 colrow
.prop(self
, "category", text
="")
1280 split
= layout
.split(percentage
=split_percent
)
1281 col
= split
.column()
1282 col
.label("Boolean Solver:")
1283 col
= split
.column()
1285 colrow
.prop(self
, "solver", expand
=True)
1287 split
= layout
.split(percentage
=split_percent
)
1288 col
= split
.column()
1289 col
.label("Experimental Features:")
1290 col
= split
.column()
1291 colrow
= col
.row(align
=True)
1292 colrow
.prop(self
, "fast_transform", toggle
=True)
1293 colrow
.prop(self
, "use_wire", text
="Use Wire Instead Of Bbox", toggle
=True)
1297 col.prop(self, "make_vertex_groups")
1298 col.prop(self, "make_boundary")
1300 layout
.prop(self
, "Enable_Tab_01", text
="Hot Keys", icon
="KEYINGSET")
1301 if self
.Enable_Tab_01
:
1305 col
.label("Hotkey List:")
1306 col
.label("Menu: Ctrl Shift B")
1310 col
.label("Brush Operators:")
1311 col
.label("Union: Ctrl Num +")
1312 col
.label("Diff: Ctrl Num -")
1313 col
.label("Intersect: Ctrl Num *")
1314 col
.label("Slice: Ctrl Num /")
1318 col
.label("Auto Operators:")
1319 col
.label("Difference: Ctrl Shift Num -")
1320 col
.label("Union: Ctrl Shift Num +")
1321 col
.label("Intersect: Ctrl Shift Num *")
1322 col
.label("Slice: Ctrl Shift Num /")
1323 col
.label("BTool Brush To Mesh: Ctrl Num Enter")
1324 col
.label("BTool All Brush To Mesh: Ctrl Shift Num Enter")
1327 # ------------------- Class List ------------------------------------------------
1346 BTool_DrawPolyBrush
,
1348 BTool_AllBrushToMesh
,
1353 BTool_EnableThisBrush
,
1354 BTool_EnableFTransform
,
1355 BTool_FastTransform
,
1361 # ------------------- REGISTER ------------------------------------------------
1364 addon_keymapsFastT
= []
1367 # Fast Transform HotKeys Register
1368 def RegisterFastT():
1369 wm
= bpy
.context
.window_manager
1370 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
='Object Mode', space_type
='EMPTY')
1372 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, 'G', 'PRESS')
1373 kmi
.properties
.operator
= "Translate"
1374 addon_keymapsFastT
.append((km
, kmi
))
1376 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, 'R', 'PRESS')
1377 kmi
.properties
.operator
= "Rotate"
1378 addon_keymapsFastT
.append((km
, kmi
))
1380 kmi
= km
.keymap_items
.new(BTool_FastTransform
.bl_idname
, 'S', 'PRESS')
1381 kmi
.properties
.operator
= "Scale"
1382 addon_keymapsFastT
.append((km
, kmi
))
1385 # Fast Transform HotKeys UnRegister
1386 def UnRegisterFastT():
1387 wm
= bpy
.context
.window_manager
1388 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
='Object Mode', space_type
='EMPTY')
1390 for km
, kmi
in addon_keymapsFastT
:
1391 km
.keymap_items
.remove(kmi
)
1392 addon_keymapsFastT
.clear()
1397 bpy
.utils
.register_class(cls
)
1398 update_panel(None, bpy
.context
)
1401 bpy
.types
.Scene
.BoolHide
= BoolProperty(
1403 description
="Hide boolean objects",
1404 update
=update_BoolHide
,
1407 bpy
.app
.handlers
.scene_update_post
.append(HandleScene
)
1409 bpy
.types
.VIEW3D_MT_object
.append(VIEW3D_BoolTool_Menu
)
1411 bpy
.types
.VIEW3D_MT_Object
.prepend(VIEW3D_BoolTool_Menu
)
1415 wm
= bpy
.context
.window_manager
1417 # create the boolean menu hotkey
1418 km
= wm
.keyconfigs
.addon
.keymaps
.new(name
='Object Mode')
1419 kmi
= km
.keymap_items
.new('wm.call_menu', 'B', 'PRESS', ctrl
=True, shift
=True)
1420 kmi
.properties
.name
= 'OBJECT_MT_BoolTool_Menu'
1423 kmi
= km
.keymap_items
.new(BTool_Union
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', ctrl
=True)
1424 kmi
= km
.keymap_items
.new(BTool_Diff
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', ctrl
=True)
1425 kmi
= km
.keymap_items
.new(BTool_Inters
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', ctrl
=True)
1426 kmi
= km
.keymap_items
.new(BTool_Slice
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', ctrl
=True)
1427 kmi
= km
.keymap_items
.new(BTool_BrushToMesh
.bl_idname
, 'NUMPAD_ENTER', 'PRESS', ctrl
=True)
1428 kmi
= km
.keymap_items
.new(BTool_AllBrushToMesh
.bl_idname
, 'NUMPAD_ENTER', 'PRESS', ctrl
=True, shift
=True)
1431 kmi
= km
.keymap_items
.new(Auto_Union
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', ctrl
=True, shift
=True)
1432 kmi
= km
.keymap_items
.new(Auto_Difference
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', ctrl
=True, shift
=True)
1433 kmi
= km
.keymap_items
.new(Auto_Intersect
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', ctrl
=True, shift
=True)
1434 kmi
= km
.keymap_items
.new(Auto_Slice
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', ctrl
=True, shift
=True)
1436 addon_keymaps
.append(km
)
1441 # remove keymaps when add-on is deactivated
1442 wm
= bpy
.context
.window_manager
1443 for km
in addon_keymaps
:
1444 wm
.keyconfigs
.addon
.keymaps
.remove(km
)
1445 del addon_keymaps
[:]
1447 bpy
.types
.VIEW3D_MT_object
.remove(VIEW3D_BoolTool_Menu
)
1449 bpy
.types
.VIEW3D_MT_Object
.remove(VIEW3D_BoolTool_Menu
)
1453 del bpy
.types
.Scene
.BoolHide
1456 bpy
.utils
.unregister_class(cls
)
1459 if __name__
== "__main__":