Skip to content

Commit d7b2358

Browse files
committed
Snap - Refactor x-ray mode handling to prevent double raycasting
1 parent cafe5aa commit d7b2358

2 files changed

Lines changed: 98 additions & 51 deletions

File tree

src/bonsai/bonsai/tool/raycast.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,81 @@ def cast_rays_and_get_best_object(
676676
else:
677677
return None, None, None
678678

679+
@classmethod
680+
def ray_cast_and_get_closest_to_camera_snaps(
681+
cls,
682+
context: bpy.types.Context,
683+
event: bpy.types.Event,
684+
objs_to_raycast: list[bpy.types.Object],
685+
) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]:
686+
closest_length_squared = 1.0
687+
closest_obj = None
688+
closest_hit = None
689+
closest_face_index = None
690+
691+
ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event)
692+
693+
closest_snaps = []
694+
hit = None
695+
696+
for snap_obj in objs_to_raycast:
697+
if (snap_obj.obj.type in {"EMPTY", "CURVE"}
698+
or (hasattr(snap_obj.obj.data, "polygons") and len(snap_obj.obj.data.polygons) == 0)
699+
):
700+
# For wireframe objects we have to test all the snaps to see which is closer
701+
snap_points = tool.Raycast.ray_cast_by_proximity_2d(context, event, snap_obj)
702+
closest_wf_hit = None
703+
closest_wf_length_squared = 1.0
704+
closest_wf_point = None
705+
if snap_points:
706+
for point in snap_points:
707+
point["group"] = "Wireframe"
708+
closest_snaps.append(point)
709+
length = (point["point"] - ray_origin).length_squared
710+
if closest_wf_hit is None or length < closest_wf_length_squared:
711+
closest_wf_length_squared = length
712+
closest_wf_hit = point["point"]
713+
closest_wf_point = point
714+
715+
if closest_wf_point:
716+
hit_obj = closest_wf_point["object"]
717+
hit = closest_wf_point["point"]
718+
face_index = None
719+
720+
721+
else:
722+
# Solid objects
723+
hit_obj, hit, face_index = cls.cast_rays_to_single_object(context, event, snap_obj.obj)
724+
725+
if hit:
726+
snap_point = {
727+
"point": hit,
728+
"type": "Face",
729+
"group": "Object",
730+
"object": hit_obj,
731+
"face_index": face_index,
732+
"distance": 9, # High value so it has low priority
733+
}
734+
closest_snaps.append(snap_point)
735+
736+
737+
# Here we test which is closer, including wireframe and solid objects
738+
if hit is not None:
739+
length_squared = (hit - ray_origin).length_squared
740+
if closest_obj is None or length_squared < closest_length_squared:
741+
closest_length_squared = length_squared
742+
closest_obj = hit_obj
743+
closest_hit = hit
744+
closest_face_index = face_index
745+
746+
# Label snaps from the closest object
747+
if closest_obj is not None:
748+
for snap in closest_snaps:
749+
if snap["object"] == closest_obj:
750+
snap["is_closest_to_camera"] = True
751+
752+
return closest_snaps
753+
679754
@classmethod
680755
def calculate_snap_threshold(cls, view_distance):
681756
snap_threshold = view_distance / 100

src/bonsai/bonsai/tool/snap.py

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,16 @@ def select_plane_method():
360360
plane_normal = tool.Polyline.use_transform_orientations(plane_normal)
361361
return plane_origin, plane_normal
362362

363+
def handle_snap_points_in_xray_mode(closest_snaps, detected_snaps, snap_obj, is_xray):
364+
for snap in closest_snaps:
365+
if snap_obj.obj == snap["object"]:
366+
if "face_index" in snap and snap["face_index"]:
367+
points = tool.Raycast.ray_cast_by_proximity_2d(context, event, snap_obj)
368+
for point in points:
369+
point["group"] = "Object"
370+
detected_snaps.append(point)
371+
return detected_snaps
372+
363373
# Polyline
364374
polyline_props = tool.Model.get_polyline_props()
365375
try:
@@ -388,58 +398,20 @@ def select_plane_method():
388398

389399
# Objects
390400
objs_to_raycast = tool.Raycast.filter_objects_to_raycast(context, event, objs_2d_bbox)
391-
# Wireframes
392-
# For wireframe we have to get all the objects so we can further calculate edge intersection
393-
for snap_obj in objs_to_raycast:
394-
if snap_obj.obj.type in {"EMPTY", "CURVE"} or (snap_obj.obj.type == "MESH" and len(snap_obj.obj.data.polygons) == 0):
395-
snap_points = tool.Raycast.ray_cast_by_proximity_2d(context, event, snap_obj)
396-
if snap_points:
397-
for point in snap_points:
398-
point["group"] = "Wireframe"
399-
detected_snaps.append(point)
400-
401-
if (space.shading.type == "SOLID" and space.shading.show_xray) or (
402-
space.shading.type == "WIREFRAME" and space.shading.show_xray_wireframe
403-
):
404-
results = []
405-
for obj in objs_to_raycast:
406-
results.append(tool.Raycast.cast_rays_to_single_object(context, event, snap_obj.obj))
407-
else:
408-
results = []
409-
results.append(tool.Raycast.cast_rays_and_get_best_object(context, event, objs_to_raycast))
410-
411-
for result in results:
412-
snap_obj = result[0]
413-
hit = result[1]
414-
face_index = result[2]
415-
if hit is not None:
416-
# Wireframes
417-
if snap_obj.type in {"EMPTY", "CURVE"} or (
418-
snap_obj.type == "MESH" and len(snap_obj.data.polygons) == 0
419-
):
420-
continue
421-
# Meshes
422-
else:
423-
# Add face snap
424-
snap_point = {
425-
"point": hit,
426-
"type": "Face",
427-
"group": "Object",
428-
"object": snap_obj,
429-
"face_index": face_index,
430-
"distance": 9, # High value so it has low priority
431-
}
432-
detected_snaps.append(snap_point)
433-
434-
# Add vertex and edge snap
435-
snap_points = tool.Raycast.ray_cast_by_proximity(
436-
context, event, snap_obj, snap_obj.data.polygons[face_index]
437-
)
438-
if snap_points:
439-
for point in snap_points:
440-
point["group"] = "Object"
441-
detected_snaps.append(point)
401+
closest_snaps = tool.Raycast.ray_cast_and_get_closest_to_camera_snaps(context, event, objs_to_raycast)
402+
detected_snaps.extend(closest_snaps)
403+
404+
xray_conditions = (space.shading.type == "SOLID" and space.shading.show_xray) or (space.shading.type == "WIREFRAME" and space.shading.show_xray_wireframe)
442405

406+
if xray_conditions:
407+
for snap_obj in objs_to_raycast:
408+
detected_snaps = handle_snap_points_in_xray_mode(closest_snaps, detected_snaps, snap_obj, is_xray=True)
409+
else:
410+
for snap_obj in objs_to_raycast:
411+
for snap in closest_snaps:
412+
if "is_closest_to_camera" in snap and snap["is_closest_to_camera"]:
413+
detected_snaps = handle_snap_points_in_xray_mode([snap], detected_snaps, snap_obj, is_xray=False)
414+
443415
# snap to cut geometry (e.g. in plan view)
444416
if CutDecorator.installed:
445417
cut_snaps = []

0 commit comments

Comments
 (0)