operator_modal_view3d_raycast.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import bpy
  2. from bpy_extras import view3d_utils
  3. def main(context, event):
  4. """Run this function on left mouse, execute the ray cast"""
  5. # get the context arguments
  6. scene = context.scene
  7. region = context.region
  8. rv3d = context.region_data
  9. coord = event.mouse_region_x, event.mouse_region_y
  10. # get the ray from the viewport and mouse
  11. view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
  12. ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
  13. ray_target = ray_origin + view_vector
  14. def visible_objects_and_duplis():
  15. """Loop over (object, matrix) pairs (mesh only)"""
  16. depsgraph = context.evaluated_depsgraph_get()
  17. for dup in depsgraph.object_instances:
  18. if dup.is_instance: # Real dupli instance
  19. obj = dup.instance_object
  20. yield (obj, dup.matrix_world.copy())
  21. else: # Usual object
  22. obj = dup.object
  23. yield (obj, obj.matrix_world.copy())
  24. def obj_ray_cast(obj, matrix):
  25. """Wrapper for ray casting that moves the ray into object space"""
  26. # get the ray relative to the object
  27. matrix_inv = matrix.inverted()
  28. ray_origin_obj = matrix_inv @ ray_origin
  29. ray_target_obj = matrix_inv @ ray_target
  30. ray_direction_obj = ray_target_obj - ray_origin_obj
  31. # cast the ray
  32. success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
  33. if success:
  34. return location, normal, face_index
  35. else:
  36. return None, None, None
  37. # cast rays and find the closest object
  38. best_length_squared = -1.0
  39. best_obj = None
  40. for obj, matrix in visible_objects_and_duplis():
  41. if obj.type == 'MESH':
  42. hit, normal, face_index = obj_ray_cast(obj, matrix)
  43. if hit is not None:
  44. hit_world = matrix @ hit
  45. scene.cursor.location = hit_world
  46. length_squared = (hit_world - ray_origin).length_squared
  47. if best_obj is None or length_squared < best_length_squared:
  48. best_length_squared = length_squared
  49. best_obj = obj
  50. # now we have the object under the mouse cursor,
  51. # we could do lots of stuff but for the example just select.
  52. if best_obj is not None:
  53. # for selection etc. we need the original object,
  54. # evaluated objects are not in viewlayer
  55. best_original = best_obj.original
  56. best_original.select_set(True)
  57. context.view_layer.objects.active = best_original
  58. class ViewOperatorRayCast(bpy.types.Operator):
  59. """Modal object selection with a ray cast"""
  60. bl_idname = "view3d.modal_operator_raycast"
  61. bl_label = "RayCast View Operator"
  62. def modal(self, context, event):
  63. if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
  64. # allow navigation
  65. return {'PASS_THROUGH'}
  66. elif event.type == 'LEFTMOUSE':
  67. main(context, event)
  68. return {'RUNNING_MODAL'}
  69. elif event.type in {'RIGHTMOUSE', 'ESC'}:
  70. return {'CANCELLED'}
  71. return {'RUNNING_MODAL'}
  72. def invoke(self, context, event):
  73. if context.space_data.type == 'VIEW_3D':
  74. context.window_manager.modal_handler_add(self)
  75. return {'RUNNING_MODAL'}
  76. else:
  77. self.report({'WARNING'}, "Active space must be a View3d")
  78. return {'CANCELLED'}
  79. def register():
  80. bpy.utils.register_class(ViewOperatorRayCast)
  81. def unregister():
  82. bpy.utils.unregister_class(ViewOperatorRayCast)
  83. if __name__ == "__main__":
  84. register()