From b40f6307838248899d687f5592d7ccf0c7d709ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Thu, 13 Mar 2025 16:55:37 -0300 Subject: [PATCH] Refactor - move raycast functions from snap.py to raycast.py --- src/bonsai/bonsai/tool/raycast.py | 91 +++++++++++++++++++++++++++++++ src/bonsai/bonsai/tool/snap.py | 25 ++------- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py index ca531b7be1..7ab648bc5b 100644 --- a/src/bonsai/bonsai/tool/raycast.py +++ b/src/bonsai/bonsai/tool/raycast.py @@ -24,6 +24,7 @@ import bonsai.core.tool import bonsai.tool as tool import mathutils from mathutils import Vector +from typing import Union class Raycast(bonsai.core.tool.Raycast): @@ -336,3 +337,93 @@ class Raycast(bonsai.core.tool.Raycast): "distance": distance, } return snap_point + + @classmethod + def filter_objects_to_raycast( + cls, + context: bpy.types.Context, + event: bpy.types.Event, + objs_2d_bbox: Union[tuple[bpy.types.Object, list[float]]], + offset: int = None, + ) -> list[bpy.types.Object]: + mouse_pos = event.mouse_region_x, event.mouse_region_y + objs_to_raycast = [] + for obj, bbox_2d in objs_2d_bbox: + if obj.type in {"MESH", "EMPTY", "CURVE"} and bbox_2d: + if tool.Raycast.intersect_mouse_2d_bounding_box(mouse_pos, bbox_2d, offset): + if ( + obj.visible_in_viewport_get(bpy.context.space_data) or obj.library + ): # Check for local view and local collections for this viewport and object + objs_to_raycast.append(obj) + return objs_to_raycast + + @classmethod + def cast_rays_to_single_object( + cls, + context: bpy.types.Context, + event: bpy.types.Event, + obj: bpy.types.Object, + mouse_offset: tuple[tuple[int, int]] = None, + ) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]: + + mouse_pos = event.mouse_region_x, event.mouse_region_y + hit = None + face_index = None + # Wireframes + if obj.type in {"EMPTY", "CURVE"} or (hasattr(obj.data, "polygons") and len(obj.data.polygons) == 0): + snap_points = tool.Raycast.ray_cast_by_proximity(context, event, obj) + if snap_points: + hit = sorted(snap_points, key=lambda x: x["distance"])[0]["point"] + if hit: + hit_world = obj.original.matrix_world @ hit + return obj, hit_world, face_index + return None, None, None + # Meshes + else: + hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj) + if hit is None: + # Tried original mouse position. Now it will try the offsets. + original_mouse_pos = mouse_pos + for value in mouse_offset: + mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value)) + hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj, mouse_pos) + if hit: + break + mouse_pos = original_mouse_pos + if hit: + hit_world = obj.original.matrix_world @ hit + return obj, hit_world, face_index + else: + return None, None, None + + @classmethod + def cast_rays_and_get_best_object( + cls, + context: bpy.types.Context, + event: bpy.types.Event, + objs_to_raycast: list[bpy.types.Object], + mouse_offset: tuple[tuple[int, int]] = None, + ) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]: + best_length_squared = 1.0 + best_obj = None + best_hit = None + best_face_index = None + + ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event) + + for obj in objs_to_raycast: + snap_obj, hit, face_index = cls.cast_rays_to_single_object(context, event, obj, mouse_offset) + + if hit is not None: + length_squared = (hit - ray_origin).length_squared + if best_obj is None or length_squared < best_length_squared: + best_length_squared = length_squared + best_obj = snap_obj + best_hit = hit + best_face_index = face_index + + if best_obj is not None: + return best_obj, best_hit, best_face_index + + else: + return None, None, None diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index bb815b1ce3..4adf4f1fd3 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -269,24 +269,6 @@ class Snap(bonsai.core.tool.Snap): sorted_intersections = sorted(valid_intersections, key=lambda x: (x - last_point).length, reverse=True) return sorted_intersections - @classmethod - def filter_objects_to_raycast( - cls, - context: bpy.types.Context, - objs_2d_bbox: Union[tuple[bpy.types.Object, list[float]]], - mouse_pos: tuple[int, int], - offset: int = None, - ) -> list[bpy.types.Object]: - objs_to_raycast = [] - for obj, bbox_2d in objs_2d_bbox: - if obj.type in {"MESH", "EMPTY", "CURVE"} and bbox_2d: - if tool.Raycast.intersect_mouse_2d_bounding_box(mouse_pos, bbox_2d, offset): - if ( - obj.visible_in_viewport_get(bpy.context.space_data) or obj.library - ): # Check for local view and local collections for this viewport and object - objs_to_raycast.append(obj) - return objs_to_raycast - @classmethod def detect_snapping_points(cls, context: bpy.types.Context, event: bpy.types.Event, objs_2d_bbox, tool_state): rv3d = context.region_data @@ -349,7 +331,7 @@ class Snap(bonsai.core.tool.Snap): hit = None face_index = None # Wireframes - if obj.type in {"EMPTY", "CURVE"} or (hasattr(obj.data, "polygons") and len(obj.data.polygons) == 0): + if obj.type in {"EMPTY", "CURVE"} or (hasattr(obj.data, "polygons") and len(obj.data.polygons) == 0) : snap_points = tool.Raycast.ray_cast_by_proximity(context, event, obj) if snap_points: hit = sorted(snap_points, key=lambda x: x["distance"])[0]["point"] @@ -430,15 +412,16 @@ class Snap(bonsai.core.tool.Snap): detected_snaps.append(point) # Objects + objs_to_raycast = tool.Raycast.filter_objects_to_raycast(context, event, objs_2d_bbox, offset) if (space.shading.type == "SOLID" and space.shading.show_xray) or ( space.shading.type == "WIREFRAME" and space.shading.show_xray_wireframe ): results = [] for obj in objs_to_raycast: - results.append(cast_rays_to_single_object(obj, mouse_pos)) + results.append(tool.Raycast.cast_rays_to_single_object(context, event, obj, mouse_offset)) else: results = [] - results.append(cast_rays_and_get_best_object(objs_to_raycast, mouse_pos)) + results.append(tool.Raycast.cast_rays_and_get_best_object(context, event, objs_to_raycast, mouse_offset)) for result in results: snap_obj = result[0]