Refactor snapping functions and allow to choose snapping point.

By pressing "M", the user can now cycle through active snapping points when
there are many options close to each other. Still lacks a text to give
information about the active point.
This commit is contained in:
Bruno Perdigão
2024-08-25 18:23:22 -03:00
parent fcaac3787b
commit 6bbf3d3917
4 changed files with 148 additions and 68 deletions
+10 -3
View File
@@ -324,6 +324,7 @@ class DrawPolylineWall(bpy.types.Operator):
self.input_value_xy = [None, None] self.input_value_xy = [None, None]
self.input_panel = {"D": "", "A": "", "X": "", "Y": ""} self.input_panel = {"D": "", "A": "", "X": "", "Y": ""}
self.snap_angle = None self.snap_angle = None
self.snapping_points = []
def recalculate_inputs(self, context): def recalculate_inputs(self, context):
if self.number_input: if self.number_input:
@@ -378,7 +379,7 @@ class DrawPolylineWall(bpy.types.Operator):
self.is_input_on = False self.is_input_on = False
self.input_type = "OFF" self.input_type = "OFF"
PolylineDecorator.set_input_panel(self.input_panel, self.input_type) PolylineDecorator.set_input_panel(self.input_panel, self.input_type)
tool.Snap.clear_snaping_ref() tool.Snap.clear_snapping_ref()
tool.Blender.update_viewport() tool.Blender.update_viewport()
else: else:
self.mousemove_count = 0 self.mousemove_count = 0
@@ -389,7 +390,8 @@ class DrawPolylineWall(bpy.types.Operator):
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj)) self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
if self.mousemove_count > 3: if self.mousemove_count > 3:
tool.Snap.snaping_movement(context, event, self.objs_2d_bbox) detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox)
self.snapping_points = tool.Snap.select_snapping_points(context, event, detected_snaps)
PolylineDecorator.set_mouse_position(event) PolylineDecorator.set_mouse_position(event)
self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on) self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
tool.Blender.update_viewport() tool.Blender.update_viewport()
@@ -480,6 +482,10 @@ class DrawPolylineWall(bpy.types.Operator):
PolylineDecorator.set_input_panel(self.input_panel, self.input_type) PolylineDecorator.set_input_panel(self.input_panel, self.input_type)
tool.Blender.update_viewport() tool.Blender.update_viewport()
if event.value == "PRESS" and event.type == "M":
self.snapping_points = tool.Snap.modify_snapping_point_selection(self.snapping_points)
tool.Blender.update_viewport()
if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}:
return {"PASS_THROUGH"} return {"PASS_THROUGH"}
@@ -508,7 +514,8 @@ class DrawPolylineWall(bpy.types.Operator):
self.visible_objs = tool.Raycast.get_visible_objects(context) self.visible_objs = tool.Raycast.get_visible_objects(context)
for obj in self.visible_objs: for obj in self.visible_objs:
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj)) self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
tool.Snap.snaping_movement(context, event, self.objs_2d_bbox) detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox)
self.snapping_points = tool.Snap.select_snapping_points(context, event, detected_snaps)
PolylineDecorator.set_mouse_position(event) PolylineDecorator.set_mouse_position(event)
self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on) self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
tool.Blender.update_viewport() tool.Blender.update_viewport()
@@ -2333,6 +2333,7 @@ class MeasureTool(bpy.types.Operator):
self.input_value_xy = [None, None] self.input_value_xy = [None, None]
self.input_panel = {"D": "", "A": "", "X": "", "Y": "", "Z": ""} self.input_panel = {"D": "", "A": "", "X": "", "Y": "", "Z": ""}
self.snap_angle = None self.snap_angle = None
self.snapping_points = []
def recalculate_inputs(self, context): def recalculate_inputs(self, context):
if self.number_input: if self.number_input:
@@ -2361,7 +2362,7 @@ class MeasureTool(bpy.types.Operator):
self.is_input_on = False self.is_input_on = False
self.input_type = "OFF" self.input_type = "OFF"
PolylineDecorator.set_input_panel(self.input_panel, self.input_type) PolylineDecorator.set_input_panel(self.input_panel, self.input_type)
tool.Snap.clear_snaping_ref() tool.Snap.clear_snapping_ref()
tool.Blender.update_viewport() tool.Blender.update_viewport()
else: else:
self.mousemove_count = 0 self.mousemove_count = 0
@@ -2372,7 +2373,8 @@ class MeasureTool(bpy.types.Operator):
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj)) self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
if self.mousemove_count > 3: if self.mousemove_count > 3:
tool.Snap.snaping_movement(context, event, self.objs_2d_bbox) detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox)
self.snapping_points = tool.Snap.select_snapping_points(context, event, detected_snaps)
PolylineDecorator.set_mouse_position(event) PolylineDecorator.set_mouse_position(event)
self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on) self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
PolylineDecorator.set_input_panel(self.input_panel, self.input_type) PolylineDecorator.set_input_panel(self.input_panel, self.input_type)
@@ -2456,6 +2458,10 @@ class MeasureTool(bpy.types.Operator):
PolylineDecorator.set_input_panel(self.input_panel, self.input_type) PolylineDecorator.set_input_panel(self.input_panel, self.input_type)
tool.Blender.update_viewport() tool.Blender.update_viewport()
if event.value == "PRESS" and event.type == "M":
self.snapping_points = tool.Snap.modify_snapping_point_selection(self.snapping_points)
tool.Blender.update_viewport()
if event.shift and event.value == "PRESS" and event.type == "X": if event.shift and event.value == "PRESS" and event.type == "X":
tool.Snap.set_use_default_container(False) tool.Snap.set_use_default_container(False)
PolylineDecorator.set_use_default_container(False) PolylineDecorator.set_use_default_container(False)
@@ -2499,7 +2505,8 @@ class MeasureTool(bpy.types.Operator):
self.visible_objs = tool.Raycast.get_visible_objects(context) self.visible_objs = tool.Raycast.get_visible_objects(context)
for obj in self.visible_objs: for obj in self.visible_objs:
self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj)) self.objs_2d_bbox.append(tool.Raycast.get_objects_2d_bounding_boxes(context, obj))
tool.Snap.snaping_movement(context, event, self.objs_2d_bbox) detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox)
self.snapping_points = tool.Snap.select_snapping_points(context, event, detected_snaps)
PolylineDecorator.set_mouse_position(event) PolylineDecorator.set_mouse_position(event)
self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on) self.input_panel = PolylineDecorator.calculate_distance_and_angle(context, self.is_input_on)
tool.Blender.update_viewport() tool.Blender.update_viewport()
+33 -6
View File
@@ -106,12 +106,14 @@ class Raycast(bonsai.core.tool.Raycast):
else: else:
return None, None, None return None, None, None
@classmethod @classmethod
def ray_cast_by_proximity(cls, context, event, obj, mesh=None): def ray_cast_by_proximity(cls, context, event, obj, mesh=None):
region = context.region region = context.region
rv3d = context.region_data rv3d = context.region_data
mouse_pos = event.mouse_region_x, event.mouse_region_y mouse_pos = event.mouse_region_x, event.mouse_region_y
ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event) ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event)
points = []
try: try:
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction) loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
@@ -119,9 +121,9 @@ class Raycast(bonsai.core.tool.Raycast):
loc = Vector((0, 0, 0)) loc = Vector((0, 0, 0))
bm = bmesh.new() bm = bmesh.new()
if mesh is None: if mesh is None: # Object with faces
bm.from_mesh(obj.data) bm.from_mesh(obj.data)
else: else: # Object without faces
verts = [bm.verts.new(obj.data.vertices[i].co) for i in mesh.vertices] verts = [bm.verts.new(obj.data.vertices[i].co) for i in mesh.vertices]
bm.faces.new(verts) bm.faces.new(verts)
@@ -134,14 +136,14 @@ class Raycast(bonsai.core.tool.Raycast):
intersection, _ = mathutils.geometry.intersect_point_line(division_point, ray_target, loc) intersection, _ = mathutils.geometry.intersect_point_line(division_point, ray_target, loc)
distance = (division_point - intersection).length distance = (division_point - intersection).length
if distance < 0.2: if distance < 0.2:
return division_point, "Edge Center" points.append((division_point, "Edge Center"))
for vertex in bm.verts: for vertex in bm.verts:
world_vertex = obj.matrix_world @ vertex.co world_vertex = obj.matrix_world @ vertex.co
intersection, _ = mathutils.geometry.intersect_point_line(world_vertex, ray_target, loc) intersection, _ = mathutils.geometry.intersect_point_line(world_vertex, ray_target, loc)
distance = (world_vertex - intersection).length distance = (world_vertex - intersection).length
if distance < 0.2: if distance < 0.2:
return world_vertex, "Vertex" points.append((world_vertex, "Vertex"))
for edge in bm.edges: for edge in bm.edges:
v1 = edge.verts[0].co v1 = edge.verts[0].co
@@ -151,11 +153,36 @@ class Raycast(bonsai.core.tool.Raycast):
intersection = mathutils.geometry.intersect_line_line(ray_target, loc, world_v1, world_v2) intersection = mathutils.geometry.intersect_line_line(ray_target, loc, world_v1, world_v2)
distance = (intersection[0] - intersection[1]).length distance = (intersection[0] - intersection[1]).length
if distance < 0.2: if distance < 0.2:
return intersection[1], "Edge" points.append((intersection[1], "Edge"))
return None, None return points
@classmethod
def ray_cast_to_polyline(cls, context, event):
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))
polyline_data = bpy.context.scene.BIMModelProperties.polyline_point
polyline_points = []
for point_data in polyline_data:
point = Vector((point_data.x, point_data.y, point_data.z))
intersection, _ = mathutils.geometry.intersect_point_line(point, ray_target, loc)
distance = (point - intersection).length
if distance < 0.2:
polyline_points.append((point, "Vertex"))
return polyline_points
@classmethod @classmethod
def ray_cast_to_plane(cls, context, event, plane_origin, plane_normal): def ray_cast_to_plane(cls, context, event, plane_origin, plane_normal):
region = context.region region = context.region
+95 -56
View File
@@ -113,7 +113,7 @@ class Snap(bonsai.core.tool.Snap):
snap_vertex.snap_type = snap_type snap_vertex.snap_type = snap_type
@classmethod @classmethod
def clear_snaping_ref(cls): def clear_snapping_ref(cls):
bpy.context.scene.BIMModelProperties.snap_mouse_ref.clear() bpy.context.scene.BIMModelProperties.snap_mouse_ref.clear()
@classmethod @classmethod
@@ -294,10 +294,11 @@ class Snap(bonsai.core.tool.Snap):
# cls.update_snaping_point(snap_point[0], snap_point[1]) # cls.update_snaping_point(snap_point[0], snap_point[1])
@classmethod @classmethod
def snaping_movement(cls, context, event, objs_2d_bbox): def detect_snapping_points(cls, context, event, objs_2d_bbox):
region = context.region region = context.region
rv3d = context.region_data rv3d = context.region_data
cls.mouse_pos = event.mouse_region_x, event.mouse_region_y cls.mouse_pos = event.mouse_region_x, event.mouse_region_y
detected_snaps = []
offset = 15 offset = 15
snap_threshold = 0.3 snap_threshold = 0.3
@@ -376,12 +377,38 @@ class Snap(bonsai.core.tool.Snap):
else: else:
return None, None, None return None, None, None
ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event)
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)
# Obj
snap_obj, hit, face_index = cast_rays_and_get_best_object(objs_to_raycast)
if hit is not None:
detected_snaps.append({"Object": (snap_obj, hit, face_index)})
# Edge-Vertex
for obj in objs_to_raycast:
options = tool.Raycast.ray_cast_by_proximity(context, event, obj)
snap_obj = obj
if options:
detected_snaps.append({"Edge-Vertex": (snap_obj, options)})
break
# Polyline
try: try:
polyline_data = bpy.context.scene.BIMModelProperties.polyline_point polyline_data = bpy.context.scene.BIMModelProperties.polyline_point
last_polyline_point = polyline_data[len(polyline_data) - 1] last_polyline_point = polyline_data[len(polyline_data) - 1]
except: except:
last_polyline_point = None last_polyline_point = None
snap_points = tool.Raycast.ray_cast_to_polyline(context, event)
# snap_point = cls.select_snap_point(snap_points, intersection, snap_threshold)
if snap_points:
detected_snaps.append({"Polyline": snap_points})
# Axis and Plane
elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z
plane_origin, plane_normal = select_plane_method() plane_origin, plane_normal = select_plane_method()
@@ -391,67 +418,79 @@ class Snap(bonsai.core.tool.Snap):
axis_start = None axis_start = None
axis_end = None axis_end = None
if cls.snap_plane_method in {"XY", "XZ", "YZ"}: if cls.snap_plane_method in {"XY", "XZ", "YZ"}:
# Locks snap into an angle axis rot_intersection, snap_angle, axis_start, axis_end = cls.snap_on_axis(intersection, cls.snap_angle)
if event.shift: if rot_intersection:
rot_intersection, _, axis_start, axis_end = cls.snap_on_axis(intersection, cls.snap_angle) detected_snaps.append({"Axis": rot_intersection})
else:
cls.snap_angle = None
rot_intersection, cls.snap_angle, _, _ = cls.snap_on_axis(intersection)
else:
rot_intersection = None
ray_origin, ray_target, ray_direction = tool.Raycast.get_viewport_ray_data(context, event) detected_snaps.append({"Plane": intersection})
objs_to_raycast = [] for item in detected_snaps:
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)
# Try to get object using object raycast method return detected_snaps
# 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 @classmethod
if face_index is not None: def select_snapping_points(cls, context, event, detected_snaps):
snap_point = cls.get_snap_points_on_raycasted_face(context, event, snap_obj, face_index) snapping_points = []
if snap_point[0] is None: for origin in detected_snaps:
cls.update_snaping_point(hit, "Face")
return if "Object" in list(origin.keys()):
# Objects with only edges or verts snap_obj, hit, face_index = origin['Object']
else:
if hit is not None: matrix = snap_obj.matrix_world.copy()
snap_point = (hit, hit_type) face = snap_obj.data.polygons[face_index]
verts = []
for i in face.vertices:
verts.append(matrix @ snap_obj.data.vertices[i].co)
options = tool.Raycast.ray_cast_by_proximity(context, event, snap_obj, face)
if not options:
snapping_points.append((hit, "Face"))
else: else:
snap_point = None for op in options:
snapping_points.append(op)
# If there are no objects to snap to, it will try snaping in the polyline cls.update_snaping_point(snapping_points[0][0], snapping_points[0][1])
else:
snap_points = cls.get_snap_points_on_polyline() return snapping_points
snap_point = cls.select_snap_point(snap_points, intersection, snap_threshold)
elif "Edge-Vertex" in list(origin.keys()):
snap_obj, options = origin['Edge-Vertex']
for op in options:
snapping_points.append(op)
cls.update_snaping_point(snapping_points[0][0], snapping_points[0][1])
return snapping_points
elif "Polyline" in list(origin.keys()):
options = origin['Polyline']
for op in options:
snapping_points.append(op)
cls.update_snaping_point(snapping_points[0][0], snapping_points[0][1])
return snapping_points
elif "Axis" in list(origin.keys()):
intersection = origin['Axis']
snapping_points.append((intersection, "Axis"))
cls.update_snaping_point(snapping_points[0][0], snapping_points[0][1])
return snapping_points
elif "Plane" in list(origin.keys()):
intersection = origin['Plane']
snapping_points.append((intersection, "Plane"))
cls.update_snaping_point(snapping_points[0][0], snapping_points[0][1])
return snapping_points
@classmethod
def modify_snapping_point_selection(cls, snapping_points):
shifted_list = snapping_points[1:] + snapping_points[:1]
cls.update_snaping_point(shifted_list[0][0], shifted_list[0][1])
return shifted_list
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])
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")
@classmethod @classmethod
def validate_input(cls, input_number, input_type): def validate_input(cls, input_number, input_type):