Files
blender-portable-repo/extensions/blender_org/bool_tool/functions/cutter.py
T
2026-07-14 14:44:58 -06:00

189 lines
6.0 KiB
Python

import bpy
from .. import __package__ as base_package
from ..constants import (
CONVERTABLE_TYPES,
)
from .modifier import (
is_boolean_modifier,
)
from .object import (
is_linked,
has_evaluated_mesh,
object_visibility_set,
convert_to_mesh,
change_parent,
delete_object,
)
from .scene import (
ensure_collection,
)
#### ------------------------------ /list/ ------------------------------ ####
def list_selected_cutters(context):
"""Returns the list of cutters in the selection."""
cutters = []
active_object = context.active_object
selected_objects = context.selected_objects
if selected_objects:
for obj in selected_objects:
if obj == active_object:
continue
if obj.type != 'MESH':
continue
if obj.booleans.cutter:
cutters.append(obj)
if active_object:
if active_object.booleans.cutter:
cutters.append(active_object)
return cutters
def list_cutter_users(cutters: list, exclude: list=None) -> dict:
"""
List canvases that use specified cutters.
Canvases that should be excluded from the search can be specified with the `exclude` arg.
Returns a dict of canvases (keys) and set of their Boolean modifiers that use cutters (values).
"""
cutter_users = {}
for key, values in bpy.data.user_map(subset=cutters).items():
for value in values:
if value.id_type != 'OBJECT':
continue
if exclude and value in exclude:
continue
if len(value.modifiers) == 0:
continue
for mod in value.modifiers:
if not is_boolean_modifier(mod):
continue
if mod.object not in cutters:
continue
cutter_users.setdefault(value, set()).add(mod)
return cutter_users
#### ------------------------------ /filter/ ------------------------------ ####
def filter_cutters(self, context, cutters: list, canvases: list) -> list:
"""
Filter out objects from the given list if they can't be used as a cutter.
If non-mesh type object has evaluated mesh, and can be converted to mesh it will be.
"""
usable_cutters = []
for cutter in cutters:
# Exclude object if it is in both lists.
if cutter in canvases:
continue
# Exclude linked objects.
if is_linked(context, cutter):
self.report({'WARNING'}, f"{cutter.name} is linked and can not be used as a cutter")
continue
if cutter.type == 'MESH':
# Exclude if object is already a cutter for canvas.
users = list_cutter_users([cutter]).keys()
if any(canvas in users for canvas in canvases):
continue
# Exclude if canvas is cutting the object (avoid dependancy loop).
users = list_cutter_users(canvases).keys()
if cutter in users:
self.report({'WARNING'}, f"{cutter.name} can not cut its own cutter (dependancy loop)")
continue
usable_cutters.append(cutter)
elif cutter.type in CONVERTABLE_TYPES:
if not has_evaluated_mesh(context, cutter):
continue
convert_to_mesh(context, cutter)
usable_cutters.append(cutter)
return usable_cutters
#### ------------------------------ /operate/ ------------------------------ ####
def make_cutter(context, cutter, mode: str, display='BOUNDS', collection=True):
"""Ensures the cutter has the correct properties."""
# Hide Cutters
cutter.hide_render = True
cutter.display_type = display
cutter.lineart.usage = 'EXCLUDE'
object_visibility_set(cutter, value=False)
# Cutters Collection
if collection:
cutters_collection = ensure_collection(context)
if cutters_collection not in cutter.users_collection:
cutters_collection.objects.link(cutter)
# Set Boolean Property
cutter.booleans.cutter = mode.capitalize()
def restore_cutter(context, cutter, unparent=True, unlink_collection=True):
"""Remove Boolean properties from a cutter object to restore it to a normal state."""
prefs = context.preferences.addons[base_package].preferences
# Restore Unused Cutters
cutter.hide_render = False
cutter.display_type = 'TEXTURED'
cutter.lineart.usage = 'INHERIT'
object_visibility_set(cutter, value=True)
cutter.booleans.cutter = ""
# Remove Parent & Collection
if unparent:
change_parent(context, cutter, None)
if unlink_collection:
cutters_collection = bpy.data.collections.get(prefs.collection_name)
if cutters_collection in cutter.users_collection:
cutters_collection.objects.unlink(cutter)
def handle_unused_cutters(context, cutters: list, canvases: list, delete=True):
"""Deletes or restores cutters with no remaining users (besides given list of canvases)."""
prefs = context.preferences.addons[base_package].preferences
for cutter in cutters:
other_canvases = list_cutter_users([cutter], exclude=canvases).keys()
if len(other_canvases) == 0:
if delete:
# Delete unused cutters & transfer their children to canvas.
for child in cutter.children:
change_parent(context, child, cutter.parent, inverse=True)
delete_object(cutter)
else:
# Restore unused cutters.
restore_cutter(context, cutter,
unparent=prefs.parent and cutter.parent in canvases,
unlink_collection=prefs.use_collection)
else:
# Cutter has other users, parent it to one of them.
if prefs.parent and cutter.parent in canvases:
new_parent = next(c for c in other_canvases if not c.booleans.slice)
change_parent(context, cutter, new_parent, inverse=True)