226 lines
7.5 KiB
Python
226 lines
7.5 KiB
Python
import bpy, bmesh, mathutils
|
|
from .. import __package__ as base_package
|
|
|
|
|
|
#### ------------------------------ FUNCTIONS ------------------------------ ####
|
|
|
|
def add_boolean_modifier(self, context, canvas, cutter, mode, solver, apply=False, pin=False, redo=True, single_user=False):
|
|
"Adds boolean modifier with specified cutter and properties to a single object"
|
|
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
|
|
modifier = canvas.modifiers.new("boolean_" + cutter.name, 'BOOLEAN')
|
|
modifier.operation = mode
|
|
modifier.object = cutter
|
|
modifier.solver = solver
|
|
|
|
if redo:
|
|
modifier.material_mode = self.material_mode
|
|
modifier.use_self = self.use_self
|
|
modifier.use_hole_tolerant = self.use_hole_tolerant
|
|
modifier.double_threshold = self.double_threshold
|
|
|
|
if prefs.show_in_editmode:
|
|
modifier.show_in_editmode = True
|
|
|
|
if pin:
|
|
index = canvas.modifiers.find(modifier.name)
|
|
canvas.modifiers.move(index, 0)
|
|
|
|
if apply:
|
|
for face in cutter.data.polygons:
|
|
face.select = True
|
|
|
|
if context.mode == 'EDIT_MESH':
|
|
"""Applying boolean modifier in mesh edit mode:"""
|
|
"""1. Hiding other visible modifiers and creating new (temporary) mesh from evaluated object"""
|
|
"""2. Transfering temporary mesh to `bmesh` to update active mesh in edit mode"""
|
|
"""3. Removing boolean modifier and purging temporary mesh"""
|
|
"""4. Restoring visibility of other modifiers from (1)"""
|
|
|
|
visible_modifiers = []
|
|
for mod in canvas.modifiers:
|
|
if mod == modifier:
|
|
continue
|
|
if mod.show_viewport == True:
|
|
visible_modifiers.append(mod)
|
|
mod.show_viewport = False
|
|
|
|
evaluated_obj = canvas.evaluated_get(context.evaluated_depsgraph_get())
|
|
temp_data = bpy.data.meshes.new_from_object(evaluated_obj)
|
|
|
|
bm = bmesh.from_edit_mesh(canvas.data)
|
|
bm.clear()
|
|
bm.from_mesh(temp_data)
|
|
bmesh.update_edit_mesh(canvas.data)
|
|
evaluated_obj.to_mesh_clear()
|
|
|
|
canvas.modifiers.remove(modifier)
|
|
bpy.data.meshes.remove(temp_data)
|
|
|
|
for mod in visible_modifiers:
|
|
mod.show_viewport = True
|
|
|
|
else:
|
|
context_override = {'object': canvas, 'mode': 'OBJECT'}
|
|
with context.temp_override(**context_override):
|
|
apply_modifier(context, canvas, modifier, single_user=single_user)
|
|
|
|
|
|
def apply_modifier(context, obj, modifier, single_user=False):
|
|
"""Applies given modifier to object."""
|
|
|
|
context.view_layer.objects.active = obj
|
|
|
|
try:
|
|
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
|
except:
|
|
if single_user:
|
|
# Make Single User
|
|
context.active_object.data = context.active_object.data.copy()
|
|
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
|
|
|
|
|
def set_cutter_properties(context, canvas, cutter, mode, parent=True, hide=False, collection=True):
|
|
"""Ensures cutter is properly set: has right properties, is hidden, in a collection & parented"""
|
|
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
|
|
# Hide Cutters
|
|
cutter.hide_render = True
|
|
cutter.display_type = 'WIRE' if prefs.wireframe else 'BOUNDS'
|
|
cutter.lineart.usage = 'EXCLUDE'
|
|
object_visibility_set(cutter, value=False)
|
|
if hide:
|
|
cutter.hide_set(True)
|
|
|
|
# parent_to_active_canvas
|
|
if parent and cutter.parent == None:
|
|
cutter.parent = canvas
|
|
cutter.matrix_parent_inverse = canvas.matrix_world.inverted()
|
|
|
|
# Cutters Collection
|
|
if collection:
|
|
cutters_collection = ensure_collection(context)
|
|
if cutters_collection not in cutter.users_collection:
|
|
cutters_collection.objects.link(cutter)
|
|
if cutter.booleans.carver and parent == False:
|
|
context.collection.objects.unlink(cutter)
|
|
|
|
# add_boolean_property
|
|
cutter.booleans.cutter = mode.capitalize()
|
|
|
|
|
|
def object_visibility_set(obj, value=False):
|
|
"Sets object visibility properties to either True or False"
|
|
|
|
obj.visible_camera = value
|
|
obj.visible_diffuse = value
|
|
obj.visible_glossy = value
|
|
obj.visible_shadow = value
|
|
obj.visible_transmission = value
|
|
obj.visible_volume_scatter = value
|
|
|
|
|
|
def convert_to_mesh(context, obj):
|
|
"Converts active object into mesh (applying all modifiers and shape keys in process)"
|
|
|
|
# store_selection
|
|
stored_active = context.active_object
|
|
stored_selection = context.selected_objects
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
# Convert
|
|
obj.select_set(True)
|
|
context.view_layer.objects.active = obj
|
|
bpy.ops.object.convert(target='MESH')
|
|
|
|
# restore_selection
|
|
for obj in stored_selection:
|
|
obj.select_set(True)
|
|
context.view_layer.objects.active = stored_active
|
|
|
|
|
|
def ensure_collection(context):
|
|
"""Checks the existance of boolean cutters collection and creates it if it doesn't exist"""
|
|
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
|
|
collection_name = prefs.collection_name
|
|
cutters_collection = bpy.data.collections.get(collection_name)
|
|
|
|
if cutters_collection is None:
|
|
cutters_collection = bpy.data.collections.new(collection_name)
|
|
context.scene.collection.children.link(cutters_collection)
|
|
cutters_collection.hide_render = True
|
|
cutters_collection.color_tag = 'COLOR_01'
|
|
# cutters_collection.hide_viewport = True
|
|
# context.view_layer.layer_collection.children[collection_name].exclude = True
|
|
|
|
return cutters_collection
|
|
|
|
|
|
def delete_empty_collection():
|
|
"""Removes boolean cutters collection if it has no more objects in it"""
|
|
|
|
prefs = bpy.context.preferences.addons[base_package].preferences
|
|
|
|
collection = bpy.data.collections.get(prefs.collection_name)
|
|
if collection and not collection.objects:
|
|
bpy.data.collections.remove(collection)
|
|
|
|
|
|
def delete_cutter(cutter):
|
|
"""Deletes cutter object and purges it's mesh data"""
|
|
|
|
orphaned_mesh = cutter.data
|
|
bpy.data.objects.remove(cutter)
|
|
if orphaned_mesh.users == 0:
|
|
bpy.data.meshes.remove(orphaned_mesh)
|
|
|
|
|
|
def change_parent(object, parent):
|
|
"""Changes or removes parent from cutter object while keeping the transformation"""
|
|
|
|
matrix_copy = object.matrix_world.copy()
|
|
object.parent = parent
|
|
object.matrix_world = matrix_copy
|
|
|
|
|
|
def create_slice(context, canvas, modifier=False):
|
|
"""Creates copy of canvas to be used as slice"""
|
|
|
|
slice = canvas.copy()
|
|
slice.data = canvas.data.copy()
|
|
slice.name = slice.data.name = canvas.name + "_slice"
|
|
change_parent(slice, canvas)
|
|
|
|
# Set Boolean Properties
|
|
if modifier == True:
|
|
slice.booleans.canvas = True
|
|
slice.booleans.slice = True
|
|
slice.booleans.slice_of = canvas
|
|
|
|
# Add to Canvas Collections
|
|
for coll in canvas.users_collection:
|
|
coll.objects.link(slice)
|
|
|
|
# add_slices_to_local_view
|
|
if context.space_data.local_view:
|
|
slice.local_view_set(context.space_data, True)
|
|
|
|
return slice
|
|
|
|
|
|
def set_object_origin(obj, position=False):
|
|
"""Sets object origin to given position by shifting vertices"""
|
|
|
|
# default_to_center_of_bounding_box_if_no_position_provided
|
|
if position == False:
|
|
position = 0.125 * sum((mathutils.Vector(b) for b in obj.bound_box), mathutils.Vector())
|
|
|
|
mat = mathutils.Matrix.Translation(position - obj.location)
|
|
obj.location = position
|
|
obj.data.transform(mat.inverted())
|
|
obj.data.update()
|