2025-12-01
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import bpy
|
||||
from . import (
|
||||
boolean,
|
||||
canvas,
|
||||
cutter,
|
||||
select,
|
||||
)
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
for mod in [boolean,
|
||||
canvas,
|
||||
cutter,
|
||||
select,
|
||||
]:
|
||||
importlib.reload(mod)
|
||||
else:
|
||||
import bpy
|
||||
from . import (
|
||||
boolean,
|
||||
canvas,
|
||||
cutter,
|
||||
select,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ REGISTRATION ------------------------------ ####
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
import bpy
|
||||
from collections import defaultdict
|
||||
from .. import __package__ as base_package
|
||||
|
||||
from ..functions.poll import (
|
||||
basic_poll,
|
||||
is_linked,
|
||||
is_instanced_data,
|
||||
list_candidate_objects,
|
||||
destructive_op_confirmation,
|
||||
)
|
||||
from ..functions.modifier import (
|
||||
add_boolean_modifier,
|
||||
apply_modifiers,
|
||||
)
|
||||
from ..functions.object import (
|
||||
apply_modifier,
|
||||
convert_to_mesh,
|
||||
add_boolean_modifier,
|
||||
set_cutter_properties,
|
||||
change_parent,
|
||||
create_slice,
|
||||
delete_cutter,
|
||||
)
|
||||
from ..functions.list import (
|
||||
list_candidate_objects,
|
||||
list_cutter_users,
|
||||
list_pre_boolean_modifiers,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ PROPERTIES ------------------------------ ####
|
||||
|
||||
class ModifierProperties():
|
||||
material_mode: bpy.props.EnumProperty(
|
||||
name = "Materials",
|
||||
description = "Method for setting materials on the new faces",
|
||||
items = (('INDEX', "Index Based", "Set the material on new faces based on the order of the material slot lists. If a material doesn’t exist on the\n"
|
||||
"modifier object, the face will use the same material slot or the first if the object doesn’t have enough slots."),
|
||||
('TRANSFER', "Transfer", "Transfer materials from non-empty slots to the result mesh, adding new materials as necessary.\n"
|
||||
"For empty slots, fall back to using the same material index as the operand mesh.")),
|
||||
items = (('INDEX', "Index Based", ("Set the material on new faces based on the order of the material slot lists. If a material doesn't exist on the\n"
|
||||
"modifier object, the face will use the same material slot or the first if the object doesn't have enough slots.")),
|
||||
('TRANSFER', "Transfer", ("Transfer materials from non-empty slots to the result mesh, adding new materials as necessary.\n"
|
||||
"For empty slots, fall back to using the same material index as the operand mesh."))),
|
||||
default = 'INDEX',
|
||||
)
|
||||
use_self: bpy.props.BoolProperty(
|
||||
@@ -60,7 +64,7 @@ class ModifierProperties():
|
||||
layout.prop(self, "material_mode")
|
||||
layout.prop(self, "use_self")
|
||||
layout.prop(self, "use_hole_tolerant")
|
||||
elif prefs.solver == 'FAST':
|
||||
elif prefs.solver == 'FLOAT':
|
||||
layout.prop(self, "double_threshold")
|
||||
|
||||
|
||||
@@ -68,20 +72,20 @@ class ModifierProperties():
|
||||
#### ------------------------------ /brush_boolean/ ------------------------------ ####
|
||||
|
||||
class BrushBoolean(ModifierProperties):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(cls, context)
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
# abort_when_no_selected_objects
|
||||
# Abort if there are less than 2 selected objects.
|
||||
if len(context.selected_objects) < 2:
|
||||
self.report({'WARNING'}, "Boolean operator needs at least two selected objects")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# abort_when_linked
|
||||
# Abort if active object is linked.
|
||||
if is_linked(context, context.active_object):
|
||||
self.report({'WARNING'}, "Booleans can not be performed on linked objects")
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.cutters = list_candidate_objects(self, context, context.active_object)
|
||||
if len(self.cutters) == 0:
|
||||
self.report({'WARNING'}, "Boolean operators cannot be performed on linked objects")
|
||||
return {'CANCELLED'}
|
||||
|
||||
return self.execute(context)
|
||||
@@ -90,20 +94,23 @@ class BrushBoolean(ModifierProperties):
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
canvas = context.active_object
|
||||
cutters = list_candidate_objects(self, context, context.active_object)
|
||||
|
||||
# Create Slices
|
||||
if len(cutters) == 0:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Create slices.
|
||||
if self.mode == "SLICE":
|
||||
for cutter in self.cutters:
|
||||
"""NOTE: Slices need to be created in separate loop to avoid inheriting boolean modifiers that operator adds"""
|
||||
for cutter in cutters:
|
||||
"""NOTE: Slices need to be created in a separate loop to avoid inheriting boolean modifiers that the operator adds."""
|
||||
slice = create_slice(context, canvas, modifier=True)
|
||||
add_boolean_modifier(self, context, slice, cutter, "INTERSECT", prefs.solver)
|
||||
add_boolean_modifier(self, context, slice, cutter, "INTERSECT", prefs.solver, pin=prefs.pin)
|
||||
|
||||
for cutter in self.cutters:
|
||||
for cutter in cutters:
|
||||
mode = "DIFFERENCE" if self.mode == "SLICE" else self.mode
|
||||
set_cutter_properties(context, canvas, cutter, self.mode, parent=prefs.parent, collection=prefs.use_collection)
|
||||
add_boolean_modifier(self, context, canvas, cutter, "DIFFERENCE" if self.mode == "SLICE" else self.mode, prefs.solver, pin=prefs.pin)
|
||||
add_boolean_modifier(self, context, canvas, cutter, mode, prefs.solver, pin=prefs.pin)
|
||||
|
||||
|
||||
context.view_layer.objects.active = canvas
|
||||
canvas.booleans.canvas = True
|
||||
|
||||
return {'FINISHED'}
|
||||
@@ -115,10 +122,6 @@ class OBJECT_OT_boolean_brush_union(bpy.types.Operator, BrushBoolean):
|
||||
bl_description = "Merge selected objects into active one"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "UNION"
|
||||
|
||||
|
||||
@@ -128,10 +131,6 @@ class OBJECT_OT_boolean_brush_intersect(bpy.types.Operator, BrushBoolean):
|
||||
bl_description = "Only keep the parts of the active object that are interesecting selected objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "INTERSECT"
|
||||
|
||||
|
||||
@@ -141,10 +140,6 @@ class OBJECT_OT_boolean_brush_difference(bpy.types.Operator, BrushBoolean):
|
||||
bl_description = "Subtract selected objects from active one"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "DIFFERENCE"
|
||||
|
||||
|
||||
@@ -154,10 +149,6 @@ class OBJECT_OT_boolean_brush_slice(bpy.types.Operator, BrushBoolean):
|
||||
bl_description = "Slice active object along the selected ones. Will create slices as separate objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "SLICE"
|
||||
|
||||
|
||||
@@ -165,90 +156,89 @@ class OBJECT_OT_boolean_brush_slice(bpy.types.Operator, BrushBoolean):
|
||||
#### ------------------------------ /auto_boolean/ ------------------------------ ####
|
||||
|
||||
class AutoBoolean(ModifierProperties):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(cls, context)
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
# abort_when_no_selected_objects
|
||||
# Abort if there are less than 2 selected objects.
|
||||
if len(context.selected_objects) < 2:
|
||||
self.report({'WARNING'}, "Boolean operator needs at least two selected objects")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# abort_when_linked
|
||||
# Abort if active object is linked.
|
||||
if is_linked(context, context.active_object):
|
||||
self.report({'ERROR'}, "Modifiers can't be applied to linked object")
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.cutters = list_candidate_objects(self, context, context.active_object)
|
||||
if len(self.cutters) == 0:
|
||||
self.report({'ERROR'}, "Modifiers cannot be applied to linked object")
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
if is_instanced_data(context.active_object):
|
||||
return context.window_manager.invoke_confirm(self, event,
|
||||
title="Auto Boolean", confirm_text="Yes", icon='WARNING',
|
||||
message=("Canvas object has instanced object data.\n"
|
||||
"In order to apply modifiers, it needs to be made single-user.\n"
|
||||
"Do you proceed?"))
|
||||
else:
|
||||
return self.execute(context)
|
||||
return destructive_op_confirmation(self, context, event, [context.active_object], title="Auto Boolean")
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
canvas = context.active_object
|
||||
cutters = list_candidate_objects(self, context, context.active_object)
|
||||
new_modifiers = defaultdict(list)
|
||||
|
||||
# apply_modifiers
|
||||
if (prefs.apply_order == 'ALL') or (prefs.apply_order == 'BEFORE' and prefs.pin == False):
|
||||
convert_to_mesh(context, canvas)
|
||||
else:
|
||||
if canvas.data.shape_keys:
|
||||
self.report({'ERROR'}, "Modifiers can't be applied to object with shape keys")
|
||||
return {'CANCELLED'}
|
||||
if len(cutters) == 0:
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
# Create Slices
|
||||
# Create slices.
|
||||
if self.mode == "SLICE":
|
||||
for cutter in self.cutters:
|
||||
"""NOTE: Slices need to be created in separate loop to avoid inheriting boolean modifiers that operator adds"""
|
||||
for cutter in cutters:
|
||||
"""NOTE: Slices need to be created in a separate loop to avoid inheriting boolean modifiers that the operator adds."""
|
||||
slice = create_slice(context, canvas)
|
||||
add_boolean_modifier(self, context, slice, cutter, "INTERSECT", prefs.solver, apply=True, single_user=True)
|
||||
modifier = add_boolean_modifier(self, context, slice, cutter, "INTERSECT", prefs.solver, pin=prefs.pin)
|
||||
new_modifiers[slice].append(modifier)
|
||||
slice.select_set(True)
|
||||
|
||||
|
||||
for cutter in self.cutters:
|
||||
# Add Modifier (& Apply)
|
||||
for cutter in cutters:
|
||||
# Add boolean modifier on canvas.
|
||||
mode = "DIFFERENCE" if self.mode == "SLICE" else self.mode
|
||||
add_boolean_modifier(self, context, canvas, cutter, mode, prefs.solver, apply=True, pin=prefs.pin, single_user=True)
|
||||
modifier = add_boolean_modifier(self, context, canvas, cutter, mode, prefs.solver, pin=prefs.pin)
|
||||
new_modifiers[canvas].append(modifier)
|
||||
|
||||
# Transfer Children
|
||||
# Transfer cutters children to canvas.
|
||||
for child in cutter.children:
|
||||
change_parent(child, canvas)
|
||||
|
||||
# Delete Cutter
|
||||
# Select all faces of the cutter so that newly created faces in canvas
|
||||
# are also selected after applying the modifier.
|
||||
for face in cutter.data.polygons:
|
||||
face.select = True
|
||||
|
||||
# Apply modifiers on canvas & slices.
|
||||
for obj, modifiers in new_modifiers.items():
|
||||
modifiers = self._get_modifiers_to_apply(prefs, obj, modifiers)
|
||||
apply_modifiers(context, obj, modifiers)
|
||||
|
||||
# Delete cutters.
|
||||
for cutter in cutters:
|
||||
delete_cutter(cutter)
|
||||
|
||||
if self.mode == "SLICE":
|
||||
slice.select_set(True)
|
||||
context.view_layer.objects.active = slice
|
||||
|
||||
|
||||
# apply_modifiers_before_final_boolean
|
||||
if prefs.apply_order == 'BEFORE' and prefs.pin:
|
||||
modifiers = list_pre_boolean_modifiers(canvas)
|
||||
for mod in modifiers:
|
||||
apply_modifier(context, canvas, mod, single_user=True)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def _get_modifiers_to_apply(self, prefs, obj, new_modifiers) -> list:
|
||||
"""Returns a list of modifiers that need to be applied based on add-on preferences."""
|
||||
|
||||
if prefs.apply_order == 'ALL':
|
||||
modifiers = [mod for mod in obj.modifiers]
|
||||
elif prefs.apply_order == 'BOOLEANS':
|
||||
modifiers = new_modifiers
|
||||
elif prefs.apply_order == 'BEFORE':
|
||||
modifiers = list_pre_boolean_modifiers(obj)
|
||||
|
||||
return modifiers
|
||||
|
||||
|
||||
class OBJECT_OT_boolean_auto_union(bpy.types.Operator, AutoBoolean):
|
||||
bl_idname = "object.boolean_auto_union"
|
||||
bl_label = "Boolean Union (Auto)"
|
||||
bl_description = "Merge selected objects into active one"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "UNION"
|
||||
|
||||
|
||||
@@ -258,10 +248,6 @@ class OBJECT_OT_boolean_auto_difference(bpy.types.Operator, AutoBoolean):
|
||||
bl_description = "Subtract selected objects from active one"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "DIFFERENCE"
|
||||
|
||||
|
||||
@@ -271,10 +257,6 @@ class OBJECT_OT_boolean_auto_intersect(bpy.types.Operator, AutoBoolean):
|
||||
bl_description = "Only keep the parts of the active object that are interesecting selected objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "INTERSECT"
|
||||
|
||||
|
||||
@@ -284,10 +266,6 @@ class OBJECT_OT_boolean_auto_slice(bpy.types.Operator, AutoBoolean):
|
||||
bl_description = "Slice active object along the selected ones. Will create slices as separate objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context)
|
||||
|
||||
mode = "SLICE"
|
||||
|
||||
|
||||
@@ -317,7 +295,7 @@ def register():
|
||||
addon = bpy.context.window_manager.keyconfigs.addon
|
||||
km = addon.keymaps.new(name="Object Mode")
|
||||
|
||||
# brush_operators
|
||||
# Brush Operators
|
||||
kmi = km.keymap_items.new("object.boolean_brush_union", 'NUMPAD_PLUS', 'PRESS', ctrl=True)
|
||||
kmi.active = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
@@ -334,7 +312,7 @@ def register():
|
||||
kmi.active = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
# auto_operators
|
||||
# Auto Operators
|
||||
kmi = km.keymap_items.new("object.boolean_auto_union", 'NUMPAD_PLUS', 'PRESS', ctrl=True, shift=True)
|
||||
kmi.active = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import bpy, itertools
|
||||
import bpy
|
||||
import itertools
|
||||
from .. import __package__ as base_package
|
||||
|
||||
from ..functions.poll import (
|
||||
basic_poll,
|
||||
is_canvas,
|
||||
is_instanced_data,
|
||||
destructive_op_confirmation,
|
||||
)
|
||||
from ..functions.modifier import (
|
||||
apply_modifiers,
|
||||
)
|
||||
from ..functions.object import (
|
||||
apply_modifier,
|
||||
convert_to_mesh,
|
||||
object_visibility_set,
|
||||
delete_empty_collection,
|
||||
delete_cutter,
|
||||
@@ -36,7 +39,7 @@ class OBJECT_OT_boolean_toggle_all(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context, check_linked=True) and is_canvas(context.active_object)
|
||||
return basic_poll(cls, context, check_linked=True) and is_canvas(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
canvases = list_selected_canvases(context)
|
||||
@@ -79,7 +82,7 @@ class OBJECT_OT_boolean_remove_all(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context, check_linked=True) and is_canvas(context.active_object)
|
||||
return basic_poll(cls, context, check_linked=True) and is_canvas(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
@@ -153,29 +156,12 @@ class OBJECT_OT_boolean_apply_all(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context, check_linked=True) and is_canvas(context.active_object)
|
||||
return basic_poll(cls, context, check_linked=True) and is_canvas(context.active_object)
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
# Filter Objects
|
||||
self.canvases = []
|
||||
for obj in list_selected_canvases(context):
|
||||
# excude_canvases_with_shape_keys
|
||||
if obj.data.shape_keys:
|
||||
self.report({'ERROR'}, f"Modifiers can't be applied to {obj.name} because it has shape keys")
|
||||
continue
|
||||
|
||||
self.canvases.append(obj)
|
||||
|
||||
|
||||
if any(obj for obj in self.canvases if is_instanced_data(obj)):
|
||||
return context.window_manager.invoke_confirm(self, event,
|
||||
title="Apply Boolean Cutters", confirm_text="Yes", icon='WARNING',
|
||||
message=("Canvas object(s) have instanced object data.\n"
|
||||
"In order to apply modifiers, they need to be made single-user.\n"
|
||||
"Do you proceed?"))
|
||||
else:
|
||||
return self.execute(context)
|
||||
self.canvases = list_selected_canvases(context)
|
||||
return destructive_op_confirmation(self, context, event, self.canvases, title="Apply Boolean Cutters")
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
@@ -184,6 +170,8 @@ class OBJECT_OT_boolean_apply_all(bpy.types.Operator):
|
||||
cutters, __ = list_canvas_cutters(self.canvases)
|
||||
slices = list_canvas_slices(self.canvases)
|
||||
|
||||
# Select all faces of the cutter so that newly created faces in canvas
|
||||
# are also selected after applying the modifier.
|
||||
for cutter in cutters:
|
||||
for face in cutter.data.polygons:
|
||||
face.select = True
|
||||
@@ -193,17 +181,13 @@ class OBJECT_OT_boolean_apply_all(bpy.types.Operator):
|
||||
|
||||
# Apply Modifiers
|
||||
if prefs.apply_order == 'ALL':
|
||||
convert_to_mesh(context, canvas)
|
||||
|
||||
modifiers = [mod for mod in canvas.modifiers]
|
||||
elif prefs.apply_order == 'BEFORE':
|
||||
modifiers = list_pre_boolean_modifiers(canvas)
|
||||
for mod in modifiers:
|
||||
apply_modifier(context, canvas, mod, single_user=True)
|
||||
|
||||
elif prefs.apply_order == 'BOOLEANS':
|
||||
for mod in canvas.modifiers:
|
||||
if mod.type == 'BOOLEAN' and "boolean_" in mod.name:
|
||||
apply_modifier(context, canvas, mod, single_user=True)
|
||||
modifiers = [mod for mod in canvas.modifiers if mod.type == 'BOOLEAN' and "boolean_" in mod.name]
|
||||
|
||||
apply_modifiers(context, canvas, modifiers)
|
||||
|
||||
# remove_boolean_properties
|
||||
canvas.booleans.canvas = False
|
||||
|
||||
@@ -4,9 +4,12 @@ from .. import __package__ as base_package
|
||||
from ..functions.poll import (
|
||||
basic_poll,
|
||||
is_instanced_data,
|
||||
destructive_op_confirmation,
|
||||
)
|
||||
from ..functions.modifier import (
|
||||
apply_modifiers,
|
||||
)
|
||||
from ..functions.object import (
|
||||
apply_modifier,
|
||||
object_visibility_set,
|
||||
delete_empty_collection,
|
||||
delete_cutter,
|
||||
@@ -45,7 +48,7 @@ class OBJECT_OT_boolean_toggle_cutter(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context, check_linked=True)
|
||||
return basic_poll(cls, context, check_linked=True)
|
||||
|
||||
def execute(self, context):
|
||||
if self.method == 'SPECIFIED':
|
||||
@@ -118,7 +121,7 @@ class OBJECT_OT_boolean_remove_cutter(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context, check_linked=True)
|
||||
return basic_poll(cls, context, check_linked=True)
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
@@ -220,7 +223,7 @@ class OBJECT_OT_boolean_apply_cutter(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context, check_linked=True)
|
||||
return basic_poll(cls, context, check_linked=True)
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -232,25 +235,9 @@ class OBJECT_OT_boolean_apply_cutter(bpy.types.Operator):
|
||||
|
||||
elif self.method == 'ALL':
|
||||
self.cutters = list_selected_cutters(context)
|
||||
self.canvases = []
|
||||
self.canvases = list_cutter_users(self.cutters)
|
||||
|
||||
for obj in list_cutter_users(self.cutters):
|
||||
# excude_canvases_with_shape_keys
|
||||
if obj.data.shape_keys:
|
||||
self.report({'ERROR'}, f"Modifiers can't be applied to {obj.name} because it has shape keys")
|
||||
continue
|
||||
|
||||
self.canvases.append(obj)
|
||||
|
||||
|
||||
if any(obj for obj in self.canvases if is_instanced_data(obj)):
|
||||
return context.window_manager.invoke_confirm(self, event,
|
||||
title="Apply Boolean Cutter", confirm_text="Yes", icon='WARNING',
|
||||
message=("Canvas object(s) have instanced object data.\n"
|
||||
"In order to apply modifiers, they need to be made single-user.\n"
|
||||
"Do you proceed?"))
|
||||
else:
|
||||
return self.execute(context)
|
||||
return destructive_op_confirmation(self, context, event, self.canvases, title="Apply Boolean Cutter")
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
@@ -258,6 +245,8 @@ class OBJECT_OT_boolean_apply_cutter(bpy.types.Operator):
|
||||
leftovers = []
|
||||
|
||||
if self.cutters:
|
||||
# Select all faces of the cutter so that newly created faces in canvas
|
||||
# are also selected after applying the modifier.
|
||||
for cutter in self.cutters:
|
||||
for face in cutter.data.polygons:
|
||||
face.select = True
|
||||
@@ -266,10 +255,12 @@ class OBJECT_OT_boolean_apply_cutter(bpy.types.Operator):
|
||||
for canvas in self.canvases:
|
||||
context.view_layer.objects.active = canvas
|
||||
|
||||
boolean_mods = []
|
||||
for mod in canvas.modifiers:
|
||||
if "boolean_" in mod.name:
|
||||
if mod.object in self.cutters:
|
||||
apply_modifier(context, canvas, mod, single_user=True)
|
||||
boolean_mods.append(mod)
|
||||
apply_modifiers(context, canvas, boolean_mods)
|
||||
|
||||
# remove_canvas_property_if_needed
|
||||
other_cutters, __ = list_canvas_cutters([canvas])
|
||||
@@ -281,9 +272,11 @@ class OBJECT_OT_boolean_apply_cutter(bpy.types.Operator):
|
||||
if self.method == 'SPECIFIED':
|
||||
# Apply Modifier for Slices (for_specified_method)
|
||||
for slice in self.slices:
|
||||
boolean_mods = []
|
||||
for mod in slice.modifiers:
|
||||
if mod.type == 'BOOLEAN' and mod.object in self.cutters:
|
||||
apply_modifier(context, slice, mod, single_user=True)
|
||||
boolean_mods.append(mod)
|
||||
apply_modifiers(context, slice, boolean_mods)
|
||||
|
||||
|
||||
unused_cutters, leftovers = list_unused_cutters(self.cutters, self.canvases, do_leftovers=True)
|
||||
|
||||
@@ -25,7 +25,7 @@ class OBJECT_OT_select_cutter_canvas(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context) and context.active_object.booleans.cutter
|
||||
return basic_poll(cls, context) and context.active_object.booleans.cutter
|
||||
|
||||
def execute(self, context):
|
||||
cutters = list_selected_cutters(context)
|
||||
@@ -48,7 +48,7 @@ class OBJECT_OT_boolean_select_all(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return basic_poll(context) and is_canvas(context.active_object)
|
||||
return basic_poll(cls, context) and is_canvas(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
canvases = list_selected_canvases(context)
|
||||
@@ -72,7 +72,7 @@ class OBJECT_OT_boolean_select_cutter(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
return (basic_poll(context) and active_modifier_poll(context) and
|
||||
return (basic_poll(cls, context) and active_modifier_poll(context.active_object) and
|
||||
context.area.type == 'PROPERTIES' and context.space_data.context == 'MODIFIER' and
|
||||
prefs.double_click)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user