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)