Files
blender-portable-repo/extensions/blender_org/bool_tool/functions/select.py
T
2026-03-17 14:58:51 -06:00

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