mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Raycast and Snap refactor. Snap now works with objects with no faces
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user