2025-12-01
This commit is contained in:
@@ -1,22 +1,38 @@
|
||||
import bpy
|
||||
from .list import list_canvas_cutters
|
||||
|
||||
from .list import (
|
||||
list_canvas_cutters,
|
||||
list_cutter_users,
|
||||
)
|
||||
from .object import (
|
||||
convert_to_mesh,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ FUNCTIONS ------------------------------ ####
|
||||
|
||||
def basic_poll(context, check_linked=False):
|
||||
if context.mode == 'OBJECT':
|
||||
if context.active_object is not None:
|
||||
if context.active_object.type == 'MESH':
|
||||
if check_linked and is_linked(context) == True:
|
||||
return False
|
||||
def basic_poll(cls, context, check_linked=False):
|
||||
"""Basic poll for boolean operators."""
|
||||
|
||||
return True
|
||||
if context.mode != 'OBJECT':
|
||||
return False
|
||||
if context.active_object is None:
|
||||
return False
|
||||
|
||||
obj = context.active_object
|
||||
if obj.type != 'MESH':
|
||||
cls.poll_message_set("Boolean operators can only be used for mesh objects")
|
||||
return False
|
||||
|
||||
if check_linked and is_linked(context, obj) == True:
|
||||
cls.poll_message_set("Boolean operators can not be executed on linked objects")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_linked(context, obj=None):
|
||||
if not obj:
|
||||
obj = context.active_object
|
||||
def is_linked(context, obj):
|
||||
"""Checks whether the object is linked from an external .blend file (including library-overrides)."""
|
||||
|
||||
if obj not in context.editable_objects:
|
||||
if obj.library:
|
||||
@@ -31,19 +47,22 @@ def is_linked(context, obj=None):
|
||||
|
||||
|
||||
def is_canvas(obj):
|
||||
"""Checks whether the object is a boolean canvas (i.e. has boolean cutters)."""
|
||||
|
||||
if obj.booleans.canvas == False:
|
||||
return False
|
||||
else:
|
||||
# Even if object is marked as canvas, check if it actually has any cutters
|
||||
cutters, __ = list_canvas_cutters([obj])
|
||||
if len(cutters) != 0:
|
||||
if len(cutters) > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def is_instanced_data(obj):
|
||||
"""Checks if obj.data has more than one users, i.e. is instanced"""
|
||||
"""Function only considers object types as users, and excludes pointers"""
|
||||
"""Checks if `obj.data` has more than one users, i.e. is instanced."""
|
||||
"""Function only considers object types as users, and excludes pointers."""
|
||||
|
||||
data = bpy.data.meshes.get(obj.data.name)
|
||||
users = 0
|
||||
@@ -59,18 +78,103 @@ def is_instanced_data(obj):
|
||||
return False
|
||||
|
||||
|
||||
def active_modifier_poll(context):
|
||||
"""Checks whether the active modifier for active object is a boolean"""
|
||||
def active_modifier_poll(obj):
|
||||
"""Checks whether the active modifier for active object is a boolean."""
|
||||
|
||||
if context.object:
|
||||
if len(context.object.modifiers) == 0:
|
||||
return False
|
||||
# Check if active modifier exists.
|
||||
if len(obj.modifiers) == 0:
|
||||
return False
|
||||
if obj.modifiers.active is None:
|
||||
return False
|
||||
|
||||
modifier = context.object.modifiers.active
|
||||
if modifier and modifier.type == "BOOLEAN":
|
||||
if modifier.object == None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
# Check if active modifier is a boolean with a valid object.
|
||||
modifier = obj.modifiers.active
|
||||
if modifier.type != "BOOLEAN":
|
||||
return False
|
||||
if modifier.object is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def has_evaluated_mesh(context, obj):
|
||||
"""Checks if an object (non-mesh type) has an evaluated mesh created by Geometry Nodes modifiers."""
|
||||
|
||||
depsgraph = context.view_layer.depsgraph
|
||||
obj_eval = depsgraph.id_eval_get(obj)
|
||||
geometry = obj_eval.evaluated_geometry()
|
||||
|
||||
if geometry.mesh:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def list_candidate_objects(self, context, canvas):
|
||||
"""Filter out objects from the selection that can't be used as a cutter."""
|
||||
|
||||
cutters = []
|
||||
for obj in context.selected_objects:
|
||||
if obj == context.active_object:
|
||||
continue
|
||||
if is_linked(context, obj):
|
||||
self.report({'WARNING'}, f"{obj.name} is linked and can not be used as a cutter")
|
||||
continue
|
||||
|
||||
if obj.type == 'MESH':
|
||||
# Exclude if object is already a cutter for canvas.
|
||||
if canvas in list_cutter_users([obj]):
|
||||
continue
|
||||
# Exclude if canvas is cutting the object (avoid dependancy loop).
|
||||
if obj in list_cutter_users([canvas]):
|
||||
self.report({'WARNING'}, f"{obj.name} can not cut its own cutter (dependancy loop)")
|
||||
continue
|
||||
|
||||
cutters.append(obj)
|
||||
|
||||
elif obj.type in ('CURVE', 'FONT'):
|
||||
if has_evaluated_mesh(context, obj):
|
||||
convert_to_mesh(context, obj)
|
||||
cutters.append(obj)
|
||||
|
||||
return cutters
|
||||
|
||||
|
||||
def destructive_op_confirmation(self, context, event, canvases: list, title="Boolean Operation"):
|
||||
"""
|
||||
Creates & returns the confirmation pop-up window for destructive boolean operators.\n
|
||||
Confirmation window is triggered by canvas objects that have instanced object data or shape keys.\n
|
||||
If none of the canvas objects have them the operator is executed without any confirmation.
|
||||
"""
|
||||
|
||||
has_instanced_data = any(obj for obj in canvases if is_instanced_data(obj))
|
||||
has_shape_keys = any(obj for obj in canvases if obj.data.shape_keys)
|
||||
|
||||
if has_instanced_data or has_shape_keys:
|
||||
# Instanced data message.
|
||||
if has_instanced_data and not has_shape_keys:
|
||||
message = ("Object(s) you're trying to cut have instanced object data.\n"
|
||||
"In order to apply modifiers, they need to be made single-user.\n"
|
||||
"Do you proceed?")
|
||||
|
||||
# Shape keys message.
|
||||
if has_shape_keys and not has_instanced_data:
|
||||
message = ("Object(s) you're trying to cut have shape keys.\n"
|
||||
"In order to apply modifiers shape keys need to be applied as well.\n"
|
||||
"Do you proceed?")
|
||||
|
||||
# Combined message.
|
||||
if has_instanced_data and has_shape_keys:
|
||||
message = ("Object(s) you're trying to cut have shape keys and instanced object data.\n"
|
||||
"In order to apply modifiers shape keys need to be applied, and object data made single user.\n"
|
||||
"Do you proceed?")
|
||||
|
||||
popup = context.window_manager.invoke_confirm(self, event, title=title,
|
||||
confirm_text="Yes", icon='WARNING',
|
||||
message=message)
|
||||
|
||||
return popup
|
||||
|
||||
# Execute without confirmation window.
|
||||
else:
|
||||
return self.execute(context)
|
||||
|
||||
Reference in New Issue
Block a user