130 lines
5.4 KiB
Python
130 lines
5.4 KiB
Python
import bpy
|
|
import mathutils
|
|
from bpy_extras import view3d_utils
|
|
|
|
from .math import get_bounding_box
|
|
from .poll import is_linked, is_instanced_data
|
|
|
|
|
|
#### ------------------------------ FUNCTIONS ------------------------------ ####
|
|
|
|
def cursor_snap(self, context, event, mouse_pos):
|
|
"""Find the closest position on the overlay grid and snap the mouse on it"""
|
|
|
|
region = context.region
|
|
rv3d = context.region_data
|
|
|
|
for i, a in enumerate(context.screen.areas):
|
|
if a.type == 'VIEW_3D':
|
|
space = context.screen.areas[i].spaces.active
|
|
|
|
# get_the_grid_overlay
|
|
grid_scale = space.overlay.grid_scale
|
|
grid_subdivisions = space.overlay.grid_subdivisions
|
|
|
|
# use_grid_scale_and_subdivision_to_get_the_increment
|
|
increment = (grid_scale / grid_subdivisions)
|
|
half_increment = increment / 2
|
|
|
|
# convert_2d_location_of_the_mouse_in_3d
|
|
for index, loc in enumerate(reversed(mouse_pos)):
|
|
mouse_loc_3d = view3d_utils.region_2d_to_location_3d(region, rv3d, loc, (0, 0, 0))
|
|
|
|
# get_the_remainder_from_the_mouse_location_and_the_ratio (test_if_the_remainder_>_to_the_half_of_the_increment)
|
|
for i in range(3):
|
|
modulo = mouse_loc_3d[i] % increment
|
|
if modulo < half_increment:
|
|
modulo = -modulo
|
|
else:
|
|
modulo = increment - modulo
|
|
|
|
# add_the_remainder_to_get_the_closest_location_on_the_grid
|
|
mouse_loc_3d[i] = mouse_loc_3d[i] + modulo
|
|
|
|
snap_loc_2d = view3d_utils.location_3d_to_region_2d(region, rv3d, mouse_loc_3d)
|
|
|
|
# replace_the_last_mouse_location_by_the_snapped_location
|
|
if len(self.mouse_path) > 0:
|
|
self.mouse_path[len(self.mouse_path) - (index + 1) ] = tuple(snap_loc_2d)
|
|
|
|
|
|
def is_inside_selection(context, obj, rect_min, rect_max):
|
|
"""Checks if the bounding box of an object intersects with the selection bounding box"""
|
|
|
|
region = context.region
|
|
rv3d = context.space_data.region_3d
|
|
|
|
bound_corners = [obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box]
|
|
bound_corners_2d = [view3d_utils.location_3d_to_region_2d(region, rv3d, corner) for corner in bound_corners]
|
|
|
|
# check_if_2d_point_is_inside_rectangle_(defined_by_min_and_max_points)
|
|
for corner_2d in bound_corners_2d:
|
|
if corner_2d and (rect_min.x <= corner_2d.x <= rect_max.x and rect_min.y <= corner_2d.y <= rect_max.y):
|
|
return True
|
|
|
|
# check_if_any_part_of_the_bounding_box_intersects_the_selection_rectangle
|
|
min_x = min(corner_2d.x for corner_2d in bound_corners_2d if corner_2d)
|
|
max_x = max(corner_2d.x for corner_2d in bound_corners_2d if corner_2d)
|
|
min_y = min(corner_2d.y for corner_2d in bound_corners_2d if corner_2d)
|
|
max_y = max(corner_2d.y for corner_2d in bound_corners_2d if corner_2d)
|
|
|
|
return not (max_x < rect_min.x or min_x > rect_max.x or max_y < rect_min.y or min_y > rect_max.y)
|
|
|
|
|
|
def selection_fallback(self, context, objects, shape='BOX', include_cutters=False):
|
|
"""Returns mesh objects that fall inside given 2d rectangle (bounding box of the shape) coordinates"""
|
|
"""Needed to know exactly which objects should be carved, to avoid adding and applying unnecessary modifiers"""
|
|
"""NOTE: bounding box isn't always returning correct results, but checking full shape would be too expensive"""
|
|
|
|
if shape == 'POLYLINE':
|
|
x_values = [point[0] for point in self.mouse_path]
|
|
y_values = [point[1] for point in self.mouse_path]
|
|
rect_min = mathutils.Vector((min(x_values), min(y_values)))
|
|
rect_max = mathutils.Vector((max(x_values), max(y_values)))
|
|
|
|
elif shape == 'BOX':
|
|
if self.origin == 'EDGE':
|
|
rect_min = mathutils.Vector((min(self.mouse_path[0][0], self.mouse_path[1][0]),
|
|
min(self.mouse_path[0][1], self.mouse_path[1][1])))
|
|
rect_max = mathutils.Vector((max(self.mouse_path[0][0], self.mouse_path[1][0]),
|
|
max(self.mouse_path[0][1], self.mouse_path[1][1])))
|
|
|
|
elif self.origin == 'CENTER':
|
|
# get_bounding_box_of_the_shape
|
|
min_x, min_y, max_x, max_y = get_bounding_box(self.verts)
|
|
|
|
rect_min = mathutils.Vector((min(min_x, max_x), min(min_y, max_y)))
|
|
rect_max = mathutils.Vector((max(min_x, max_x), max(min_y, max_y)))
|
|
|
|
# ARRAY
|
|
if self.rows > 1:
|
|
rect_max.x = rect_min.x + (rect_max.x - rect_min.x) * self.rows + (self.rows_gap * (self.rows - 1))
|
|
if self.columns > 1:
|
|
rect_min.y = rect_max.y - (rect_max.y - rect_min.y) * self.columns - (self.columns_gap * (self.columns - 1))
|
|
|
|
|
|
intersecting_objects = []
|
|
for obj in objects:
|
|
if obj.type != 'MESH':
|
|
continue
|
|
if obj == self.cutter:
|
|
continue
|
|
if tuple(round(v, 4) for v in obj.dimensions) == (0.0, 0.0, 0.0):
|
|
continue
|
|
if include_cutters == False and obj.booleans.cutter != "":
|
|
continue
|
|
|
|
if is_inside_selection(context, obj, rect_min, rect_max):
|
|
if is_linked(context, obj):
|
|
self.report({'ERROR'}, f"{obj.name} is linked and can not be carved")
|
|
continue
|
|
|
|
if self.mode == 'DESTRUCTIVE':
|
|
if is_instanced_data(obj):
|
|
self.report({'ERROR'}, f"Modifiers cannot be applied to {obj.name} because it has instanced object data")
|
|
continue
|
|
|
|
intersecting_objects.append(obj)
|
|
|
|
return intersecting_objects
|