Improved performance. Objects are first select by their 2d bounding box related to the view. Then, just the objects close to the mouse use ray cast.

This commit is contained in:
Bruno Perdigão
2024-07-29 22:46:02 -03:00
committed by Bruno Perdigão
parent 0522adac63
commit 056df7758b
2 changed files with 140 additions and 81 deletions
@@ -339,7 +339,7 @@ class WallPolylineDecorator:
snap_prop = context.scene.BIMModelProperties.snap_vertex[0]
points = [Vector((snap_prop.x, snap_prop.y, snap_prop.z))]
if snap_prop.snap_type != "Face":
self.draw_batch("POINTS", points, decorator_color_selected)
else:
if snap_prop.snap_type in ["Face", "Plane"]:
self.draw_batch("POINTS", points, decorator_color_special)
else:
self.draw_batch("POINTS", points, decorator_color_selected)
@@ -287,13 +287,57 @@ class DrawPolylineWall(bpy.types.Operator):
bl_idname = "bim.draw_polyline_wall"
bl_label = "Draw Polyline Wall"
bl_options = {"REGISTER", "UNDO"}
ray_cast_type = bpy.props.StringProperty()
objs_bvhs = []
viewport_box = []
@classmethod
def poll(cls, context):
return context.space_data.type == 'VIEW_3D'
def __init__(self):
self.mousemove_count = 0
self.visible_objs = None
def get_visible_objects(self, context):
depsgraph = context.evaluated_depsgraph_get()
all_objs = []
for dup in depsgraph.object_instances:
if dup.is_instance: # Real dupli instance
obj = dup.instance_object
all_objs.append(obj)
else: # Usual object
obj = dup.object
all_objs.append(obj)
return all_objs
def get_objects_2d_bounding_boxes(self, context, obj):
obj_matrix = obj.matrix_world.copy()
bbox = [obj_matrix @ Vector(v) for v in obj.bound_box]
transposed_bbox = []
bbox_2d = []
screen_size = (context.region.width, context.region.height)
for v in bbox:
coord_2d = view3d_utils.location_3d_to_region_2d(context.region, context.space_data.region_3d, v)
if coord_2d is not None:
transposed_bbox.append(coord_2d)
region = context.region
borders = (region.width, region.height)
for i, axis in enumerate(zip(*transposed_bbox)):
if all(ax < 0 or ax > borders[i] for ax in axis): # Filter only objects in viewport
return (obj, None)
min_point = min(axis)
max_point = max(axis)
bbox_2d.extend([min_point, max_point])
return (obj, bbox_2d)
def main(self, context, event):
scene = context.scene
region = context.region
@@ -321,8 +365,6 @@ class DrawPolylineWall(bpy.types.Operator):
return ray_origin, ray_target, ray_direction
def get_object_ray_data(obj_matrix):
ray_origin, ray_target, ray_direction = get_viewport_ray_data()
@@ -334,17 +376,15 @@ class DrawPolylineWall(bpy.types.Operator):
return ray_origin_obj, ray_target_obj, ray_direction_obj
def get_visible_objects():
depsgraph = context.evaluated_depsgraph_get()
for dup in depsgraph.object_instances:
if dup.is_instance: # Real dupli instance
obj = dup.instance_object
yield (obj, dup.matrix_world.copy())
else: # Usual object
obj = dup.object
yield (obj, obj.matrix_world.copy())
def in_view_2d_bounding_box(obj, bbox):
x, y = self.mouse_pos
xmin, xmax, ymin, ymax = bbox
if xmin < x < xmax and ymin < y < ymax:
return True
else:
return False
def obj_ray_cast(obj):
ray_origin_obj, _, ray_direction_obj = get_object_ray_data(obj.matrix_world.copy())
success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
@@ -354,19 +394,6 @@ class DrawPolylineWall(bpy.types.Operator):
return None, None, None
def bvh_ray_cast(obj):
depsgraph = context.evaluated_depsgraph_get()
bvh = BVHTree.FromObject(obj, depsgraph)
#BVH uses objects local matrix
ray_origin_obj, _, ray_direction_obj = get_object_ray_data(obj.matrix_local.copy())
location, normal, face_index, distance = bvh.ray_cast(ray_origin_obj, ray_direction_obj)
if location:
return location, normal, face_index
else:
return None, None, None
pass
def ray_cast_to_plane(context, plane_origin, plane_normal):
intersection = Vector ((0,0,0,))
try:
@@ -389,27 +416,44 @@ class DrawPolylineWall(bpy.types.Operator):
best_hit = None
best_face_index = None
for obj, matrix in get_visible_objects():
if obj.type == 'MESH':
hit, normal, face_index = obj_ray_cast(obj) if self.ray_cast_type == 'OBJ' else bvh_ray_cast(obj)
if hit is None:
# Tried original mouse position. Now it will try the offsets.
original_mouse_pos = self.mouse_pos
for value in mouse_offset:
self.mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value))
hit, normal, face_index = obj_ray_cast(obj) if self.ray_cast_type == 'OBJ' else bvh_ray_cast(obj)
if hit:
break
self.mouse_pos = original_mouse_pos
if hit is not None:
hit_world = matrix @ hit
length_squared = (hit_world - ray_origin).length_squared
if best_obj is None or length_squared < best_length_squared:
best_length_squared = length_squared
best_obj = obj
best_hit = hit_world
best_face_index = face_index
in_viewport = []
objs_to_raycast = []
start_time = time()
for obj, bbox_2d in self.objs_2d_bbox:
if obj.type == 'MESH' and bbox_2d:
if in_view_2d_bounding_box(obj, bbox_2d):
objs_to_raycast.append(obj)
operator_time = time() - start_time
print(f"raycast bound box was finished in {operator_time:.2f} seconds")
print("Len to raycast: ", len(objs_to_raycast))
start_time = time()
for obj in objs_to_raycast:
hit, normal, face_index = obj_ray_cast(obj)
if hit is None:
# Tried original mouse position. Now it will try the offsets.
original_mouse_pos = self.mouse_pos
for value in mouse_offset:
self.mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value))
hit, normal, face_index = obj_ray_cast(obj)
if hit:
break
self.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
if best_obj is None or length_squared < best_length_squared:
best_length_squared = length_squared
best_obj = obj
best_hit = hit_world
best_face_index = face_index
operator_time = time() - start_time
print(f"raycast objects was finished in {operator_time:.2f} seconds")
if best_obj is not None:
return best_obj, best_hit, best_face_index
@@ -462,57 +506,63 @@ class DrawPolylineWall(bpy.types.Operator):
obj, hit, face_index = cast_rays_and_get_best_object()
intersection = ray_cast_to_plane(context, plane_origin, plane_normal)
try:
snap_vertex = context.scene.BIMModelProperties.snap_vertex[0]
except:
snap_vertex = context.scene.BIMModelProperties.snap_vertex.add()
if obj is not None:
original_obj = obj.original
bpy.ops.object.select_all(action="DESELECT")
original_obj.select_set(True)
context.view_layer.objects.active = original_obj
snap_points = get_snap_points(obj, face_index)
snap_point = select_snap_point(snap_points, hit, snap_threshold)
try:
snap_vextex = context.scene.BIMModelProperties.snap_vertex[0]
except:
snap_vextex = context.scene.BIMModelProperties.snap_vertex.add()
if snap_point:
snap_vextex.x = snap_point[0][0]
snap_vextex.y = snap_point[0][1]
snap_vextex.z = snap_point[0][2]
snap_vextex.snap_type = snap_point[1]
scene.cursor.location = snap_point[0]
snap_vertex.x = snap_point[0][0]
snap_vertex.y = snap_point[0][1]
snap_vertex.z = snap_point[0][2]
snap_vertex.snap_type = snap_point[1]
else:
snap_vextex.x = hit[0]
snap_vextex.y = hit[1]
snap_vextex.z = hit[2]
snap_vextex.snap_type = "Face"
scene.cursor.location = hit
snap_vertex.x = hit[0]
snap_vertex.y = hit[1]
snap_vertex.z = hit[2]
snap_vertex.snap_type = "Face"
else:
WallPolylineDecorator.uninstall()
bpy.ops.object.select_all(action="DESELECT")
scene.cursor.location = intersection
snap_vertex.x = intersection[0]
snap_vertex.y = intersection[1]
snap_vertex.z = intersection[2]
snap_vertex.snap_type = "Plane"
def modal(self, context, event):
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
return {'PASS_THROUGH'}
elif event.type == 'MOUSEMOVE':
if event.type == 'MOUSEMOVE':
self.mousemove_count += 1
tool.Blender.update_viewport()
else:
self.mousemove_count = 0
if self.mousemove_count == 2:
self.objs_2d_bbox = []
for obj in self.visible_objs:
self.objs_2d_bbox.append(self.get_objects_2d_bounding_boxes(context, obj))
if self.mousemove_count > 3:
start_time = time()
WallPolylineDecorator.install(context)
self.main(context, event)
operator_time = time() - start_time
print(f"main function was finished in {operator_time:.2f} seconds")
return {'RUNNING_MODAL'}
elif event.type == 'O':
self.ray_cast_type = 'OBJ'
print("OBJ selected.")
elif event.type == 'B':
self.ray_cast_type = 'BVH'
print("BVH selected.")
elif event.type in {'RIGHTMOUSE', 'ESC'}:
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
return {'PASS_THROUGH'}
if event.type in {'RIGHTMOUSE', 'ESC'}:
WallPolylineDecorator.uninstall()
print("CANCELLLL")
tool.Blender.update_viewport()
print("CANCELLED")
return {'CANCELLED'}
return {'RUNNING_MODAL'}
@@ -520,7 +570,16 @@ class DrawPolylineWall(bpy.types.Operator):
def invoke(self, context, event):
if context.space_data.type == 'VIEW_3D':
WallPolylineDecorator.install(context)
start_time = time()
self.objs_2d_bbox = []
self.visible_objs = self.get_visible_objects(context)
for obj in self.visible_objs: # TODO check if matrix is needed here
self.objs_2d_bbox.append(self.get_objects_2d_bounding_boxes(context, obj))
# self.objs_bvhs.append(self.get_bvh_objects_bounding_boxes(obj))
context.window_manager.modal_handler_add(self)
operator_time = time() - start_time
print(f"invoke was finished in {operator_time:.2f} seconds")
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "Active space must be a View3d")