diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py index a208fd6019..6a7af3eba5 100644 --- a/src/bonsai/bonsai/tool/raycast.py +++ b/src/bonsai/bonsai/tool/raycast.py @@ -750,6 +750,11 @@ class Raycast(bonsai.core.tool.Raycast): gpu.state.blend_set("NONE") gpu.state.face_culling_set("NONE") + read_size = 2 * _SNAP_RADIUS_PX + 1 + read_x = max(0, min(mx - _SNAP_RADIUS_PX, w - read_size)) + read_y = max(0, min(my - _SNAP_RADIUS_PX, h - read_size)) + + buffers_list = [] with _offscreen.bind(): fb = gpu.state.active_framebuffer_get() fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0) @@ -762,35 +767,44 @@ class Raycast(bonsai.core.tool.Raycast): gpu.matrix.load_matrix(Matrix.Identity(4)) batch.draw(_encoding_shader) - read_size = 2 * _SNAP_RADIUS_PX + 1 - read_x = max(0, min(mx - _SNAP_RADIUS_PX, w - read_size)) - read_y = max(0, min(my - _SNAP_RADIUS_PX, h - read_size)) - buf = fb.read_color(int(read_x), int(read_y), + buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE") + if xray_mode: # gets all buffers + buffers_list.append(buf) + last_buf = buf # gets only the closest buffer # Restore state gpu.state.depth_mask_set(True) gpu.state.depth_test_set("LESS") - pixel_data = buf.to_list() - if not pixel_data or not pixel_data[0]: - return [], None if tris: # Decode hits - hits: set[tuple[int, int]] = set() + hits: set[int] = set() if xray_mode: - for row in pixel_data: - for px in row: + vals_read: set[int] = set() + for buf in buffers_list: + pixel_data = buf.to_list() + if not pixel_data or not pixel_data[0]: + return [], None + centre_x = mx - int(read_x) + centre_y = my - int(read_y) + if 0 <= centre_y < len(pixel_data) and 0 <= centre_x < len(pixel_data[0]): + px = pixel_data[centre_y][centre_x] val = _decode_wireframe_pixel(px[0], px[1], px[2], px[3]) + if val in vals_read: # avoid getting all the tris from the same object + continue + vals_read.add(val) if val > 0: val -= 1 obj_index = int(val) >> _TRI_OBJ_SHIFT - face_index = int(val) & _TRI_FACE_MASK if obj_index < len(_obj_list): - hits.add((obj_index, face_index)) + hits.add(obj_index) else: + pixel_data = last_buf.to_list() + if not pixel_data or not pixel_data[0]: + return [], None centre_x = mx - int(read_x) centre_y = my - int(read_y) if 0 <= centre_y < len(pixel_data) and 0 <= centre_x < len(pixel_data[0]): @@ -799,42 +813,43 @@ class Raycast(bonsai.core.tool.Raycast): if val > 0: val -= 1 obj_index = int(val) >> _TRI_OBJ_SHIFT - face_index = int(val) & _TRI_FACE_MASK if obj_index < len(_obj_list): - hits.add((obj_index, face_index)) + hits.add(obj_index) if not hits: - return [] + return [], None snaps: list[dict] = [] closest_obj = None closest_dist = float("inf") ray_origin, _, _ = cls.get_viewport_ray_data(context, event) - - objs_to_raycast = [] - for obj_index, face_index in hits: + for obj_index in hits: obj = _obj_list[obj_index] - objs_to_raycast.append(obj) - hit_obj, hit, face_index = cls.cast_rays_and_get_best_object(context, event, objs_to_raycast) + hit_obj, hit, face_index = cls.cast_rays_to_single_object(context, event, obj) + if hit: + snap: dict = { + "point": hit, + "type": "Face", + "group": "Object", + "object": hit_obj, + "face_index": face_index, + "distance": 9, # High value so it has low priority + } + dist = (hit - ray_origin).length + if dist < closest_dist: + closest_dist = dist + closest_obj = obj - snap: dict = { - "point": hit, - "type": "Face", - "group": "Object", - "object": obj, - "face_index": face_index, - "distance": 9, # High value so it has low priority - } - - snaps.append(snap) + snaps.append(snap) return snaps, closest_obj else: centre = (mx - int(read_x), my - int(read_y)) + pixel_data = last_buf.to_list() best = _find_closest_wireframe_pixel(pixel_data, *centre) if best is None: - return [] + return [], None encoded, dx, dy = best # Decode and build snap dicts @@ -931,7 +946,7 @@ class Raycast(bonsai.core.tool.Raycast): }) break - return snaps + return snaps, None @classmethod def get_gpu_solid_snaps(cls, context, event, objs_to_raycast): diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index 31b71d3288..9166930384 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -390,30 +390,40 @@ 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) - obj_face_gpu_raycast = tool.Raycast.get_gpu_solid_snaps(context, event, objs_to_raycast) - obj_wireframe_gpu_raycast = tool.Raycast.get_gpu_wireframe_snaps(context, event, objs_to_raycast) - print("FACES", obj_face_gpu_raycast) - print("WIREFRAME", obj_wireframe_gpu_raycast) - closest_snaps = tool.Raycast.ray_cast_and_get_closest_to_camera_snaps(context, event, objs_to_raycast) - detected_snaps.extend(closest_snaps) - xray_mode = (space.shading.type == "SOLID" and space.shading.show_xray) or ( space.shading.type == "WIREFRAME" and space.shading.show_xray_wireframe ) + objs_to_raycast = tool.Raycast.filter_objects_to_raycast(context, event, objs_2d_bbox) + snap_faces, closest_obj = tool.Raycast.get_gpu_solid_snaps(context, event, objs_to_raycast) + wireframe_snaps, _ = tool.Raycast.get_gpu_wireframe_snaps(context, event, objs_to_raycast) - for snap_obj in objs_to_raycast: - for snap in closest_snaps: - if snap_obj == snap["object"]: - if not xray_mode: - # If it is a solid object that is closest to camera it ignores all the rest - if ( - "is_closest_to_camera" in snap - and snap["is_closest_to_camera"] - and snap["group"] == "Object" - ): - closest_snap = [snap] # discards objects that aren't the closest - detected_snaps = closest_snap + if not xray_mode: + for snap in snap_faces: + if snap["object"] == closest_obj: + detected_snaps.append(snap) + snap_points = tool.Raycast.ray_cast_by_proximity(context, event, snap["object"], snap["object"].data.polygons[snap["face_index"]]) + for point in snap_points: + point["group"] = "Object" + detected_snaps.append(point) + + # Get wireframe snaps that are not occluded by face snap + ray_origin = tool.Raycast.get_viewport_ray_data(context, event)[0] + occl_dist = (snap["point"] - ray_origin).length + 1e-4 + visible_wireframe_snaps = [ + w for w in wireframe_snaps + if (w["point"] - ray_origin).length <= occl_dist + ] + detected_snaps.extend(visible_wireframe_snaps) + continue + else: + # Doesn't include face snaps, only their edges and vertices + for snap in snap_faces: + snap_points = tool.Raycast.ray_cast_by_proximity(context, event, snap["object"], snap["object"].data.polygons[snap["face_index"]]) + for point in snap_points: + point["group"] = "Object" + detected_snaps.append(point) + + detected_snaps.extend(wireframe_snaps) # snap to cut geometry (e.g. in plan view) if CutDecorator.installed: