Raycast and Snap refactor. Snap now works with objects with no faces

This commit is contained in:
Bruno Perdigão
2024-08-21 22:31:22 -03:00
parent 473d0ae888
commit b87e8136af
2 changed files with 99 additions and 30 deletions
+51
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bmesh
import bpy
from bpy_extras import view3d_utils
import bonsai.core.tool
@@ -105,6 +106,56 @@ class Raycast(bonsai.core.tool.Raycast):
else:
return None, None, None
@classmethod
def ray_cast_by_proximity(cls, context, event, obj, mesh=None):
region = context.region
rv3d = context.region_data
mouse_pos = event.mouse_region_x, event.mouse_region_y
ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event)
try:
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
except:
loc = Vector((0, 0, 0))
bm = bmesh.new()
if mesh is None:
bm.from_mesh(obj.data)
else:
verts = [bm.verts.new(obj.data.vertices[i].co) for i in mesh.vertices]
bm.faces.new(verts)
for edge in bm.edges:
v1 = edge.verts[0].co
v2 = edge.verts[1].co
world_v1 = obj.matrix_world @ v1
world_v2 = obj.matrix_world @ v2
division_point = (world_v1 + world_v2) / 2 # TODO Make it work for different divisions
intersection, _ = mathutils.geometry.intersect_point_line(division_point, ray_target, loc)
distance = (division_point - intersection).length
if distance < 0.2:
return division_point, "Edge Center"
for vertex in bm.verts:
world_vertex = obj.matrix_world @ vertex.co
intersection, _ = mathutils.geometry.intersect_point_line(world_vertex, ray_target, loc)
distance = (world_vertex - intersection).length
if distance < 0.2:
return world_vertex, "Vertex"
for edge in bm.edges:
v1 = edge.verts[0].co
v2 = edge.verts[1].co
world_v1 = obj.matrix_world @ v1
world_v2 = obj.matrix_world @ v2
intersection = mathutils.geometry.intersect_line_line(ray_target, loc, world_v1, world_v2)
distance = (intersection[0] - intersection[1]).length
if distance < 0.2:
return intersection[1], "Edge"
return None, None
@classmethod
def ray_cast_to_plane(cls, context, event, plane_origin, plane_normal):
region = context.region
+48 -30
View File
@@ -41,23 +41,19 @@ class Snap(bonsai.core.tool.Snap):
cls.snap_plane_method = value
@classmethod
def get_snap_points_on_raycasted_obj(cls, obj, face_index):
snap_points = {}
def get_snap_points_on_raycasted_face(cls, context, event, obj, face_index):
matrix = obj.matrix_world.copy()
face = obj.data.polygons[face_index]
vertices = []
verts = []
for i in face.vertices:
vertices.append(matrix @ obj.data.vertices[i].co)
verts.append(matrix @ obj.data.vertices[i].co)
edges_center = []
for v1, v2 in zip(vertices, vertices[1:] + [vertices[0]]):
middle_dist = (v2 - v1) / 2
edges_center.append(v1 + middle_dist)
hit, hit_type = tool.Raycast.ray_cast_by_proximity(context, event, obj, face)
snap_point = (hit, hit_type)
if hit is None:
return (None, None)
snap_points.update({tuple(vertex): "Vertices" for vertex in vertices})
snap_points.update({tuple(edge_center): "Edge Center" for edge_center in edges_center})
return snap_points
return snap_point
@classmethod
def get_snap_points_on_polyline(cls):
@@ -286,7 +282,8 @@ class Snap(bonsai.core.tool.Snap):
rv3d = context.region_data
cls.mouse_pos = event.mouse_region_x, event.mouse_region_y
offset = 5
offset = 15
snap_threshold = 0.3
mouse_offset = (
(-offset, offset),
(0, offset),
@@ -329,19 +326,12 @@ class Snap(bonsai.core.tool.Snap):
return plane_origin, plane_normal
def cast_rays_and_get_best_object():
def cast_rays_and_get_best_object(objs_to_raycast):
best_length_squared = 1.0
best_obj = None
best_hit = None
best_face_index = None
objs_to_raycast = []
for obj, bbox_2d in objs_2d_bbox:
if obj.type == "MESH" and bbox_2d:
if tool.Raycast.in_view_2d_bounding_box(cls.mouse_pos, bbox_2d):
objs_to_raycast.append(obj)
for obj in objs_to_raycast:
hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj)
if hit is None:
@@ -353,6 +343,7 @@ class Snap(bonsai.core.tool.Snap):
if hit:
break
cls.mouse_pos = original_mouse_pos
if hit is not None:
hit_world = obj.original.matrix_world @ hit
length_squared = (hit_world - ray_origin).length_squared
@@ -368,9 +359,6 @@ class Snap(bonsai.core.tool.Snap):
else:
return None, None, None
snap_threshold = 0.3
ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event)
obj, hit, face_index = cast_rays_and_get_best_object()
try:
polyline_data = bpy.context.scene.BIMModelProperties.polyline_point
@@ -396,26 +384,56 @@ class Snap(bonsai.core.tool.Snap):
else:
rot_intersection = None
if obj is not None:
ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event)
snap_points = cls.get_snap_points_on_raycasted_obj(obj, face_index)
snap_point = cls.select_snap_point(snap_points, hit, snap_threshold)
objs_to_raycast = []
for obj, bbox_2d in objs_2d_bbox:
if obj.type == "MESH" and bbox_2d:
if tool.Raycast.in_view_2d_bounding_box(cls.mouse_pos, bbox_2d):
objs_to_raycast.append(obj)
if not snap_point:
cls.update_snaping_point(hit, "Face")
# Try to get object using object raycast method
# If it fails use the raycast by proximity
snap_obj, hit, face_index = cast_rays_and_get_best_object(objs_to_raycast)
if snap_obj is None:
for obj in objs_to_raycast:
hit, hit_type = tool.Raycast.ray_cast_by_proximity(context, event, obj)
snap_obj = obj
if hit is not None:
break
if snap_obj is not None:
# Objects with faces
if face_index is not None:
snap_point = cls.get_snap_points_on_raycasted_face(context, event, snap_obj, face_index)
if snap_point[0] is None:
cls.update_snaping_point(hit, "Face")
return
# Objects with only edges or verts
else:
if hit is not None:
snap_point = (hit, hit_type)
else:
snap_point = None
# If there are no objects to snap to, it will try snaping in the polyline
else:
snap_points = cls.get_snap_points_on_polyline()
snap_point = cls.select_snap_point(snap_points, intersection, snap_threshold)
print("POLY", snap_points, snap_point)
if snap_point:
if event.shift and axis_start:
snap_result, snap_type = cls.mix_snap_and_axis(snap_point, axis_start, axis_end, elevation)
cls.update_snaping_point(snap_result, snap_type)
cls.update_snaping_ref(snap_point[0], snap_point[1])
return
else:
cls.update_snaping_point(snap_point[0], snap_point[1])
elif rot_intersection:
return
# If theres no snapping point from objects and polyline
# It will snap to the plane
if rot_intersection:
cls.update_snaping_point(rot_intersection, "Axis")
else:
cls.update_snaping_point(intersection, "Plane")