Fix [#32135] FRAMERATE: Framerate display is truncated in selection box.
[blender.git] / release / scripts / templates / operator_modal_view3d_raycast.py
1 import bpy
2 from mathutils import Vector
3 from bpy_extras import view3d_utils
4
5
6 def main(context, event, ray_max=10000.0):
7     """Run this function on left mouse, execute the ray cast"""
8     # get the context arguments
9     scene = context.scene
10     region = context.region
11     rv3d = context.region_data
12     coord = event.mouse_region_x, event.mouse_region_y
13
14     # get the ray from the viewport and mouse
15     view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
16     ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
17     ray_target = ray_origin + (view_vector * ray_max)
18
19     scene.cursor_location = ray_target
20
21     def visible_objects_and_duplis():
22         """Loop over (object, matrix) pairs (mesh only)"""
23
24         for obj in context.visible_objects:
25             if obj.type == 'MESH':
26                 yield (obj, obj.matrix_world.copy())
27
28             if obj.dupli_type != 'NONE':
29                 obj.dupli_list_create(scene)
30                 for dob in obj.dupli_list:
31                     obj_dupli = dob.object
32                     if obj_dupli.type == 'MESH':
33                         yield (obj_dupli, dob.matrix.copy())
34
35             obj.dupli_list_clear()
36
37     def obj_ray_cast(obj, matrix):
38         """Wrapper for ray casting that moves the ray into object space"""
39
40         # get the ray relative to the object
41         matrix_inv = matrix.inverted()
42         ray_origin_obj = matrix_inv * ray_origin
43         ray_target_obj = matrix_inv * ray_target
44
45         # cast the ray
46         hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
47
48         if face_index != -1:
49             return hit, normal, face_index
50         else:
51             return None, None, None
52
53     # cast rays and find the closest object
54     best_length_squared = ray_max * ray_max
55     best_obj = None
56
57     for obj, matrix in visible_objects_and_duplis():
58         if obj.type == 'MESH':
59             hit, normal, face_index = obj_ray_cast(obj, matrix)
60             if hit is not None:
61                 length_squared = (hit - ray_origin).length_squared
62                 if length_squared < best_length_squared:
63                     best_length_squared = length_squared
64                     best_obj = obj
65
66     # now we have the object under the mouse cursor,
67     # we could do lots of stuff but for the example just select.
68     if best_obj is not None:
69         best_obj.select = True
70
71
72 class ViewOperatorRayCast(bpy.types.Operator):
73     """Modal object selection with a ray cast"""
74     bl_idname = "view3d.modal_operator_raycast"
75     bl_label = "RayCast View Operator"
76
77     def modal(self, context, event):
78         if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
79             # allow navigation
80             return {'PASS_THROUGH'}
81         elif event.type == 'LEFTMOUSE':
82             main(context, event)
83             return {'RUNNING_MODAL'}
84         elif event.type in {'RIGHTMOUSE', 'ESC'}:
85             return {'CANCELLED'}
86
87         return {'RUNNING_MODAL'}
88
89     def invoke(self, context, event):
90         if context.space_data.type == 'VIEW_3D':
91             context.window_manager.modal_handler_add(self)
92             return {'RUNNING_MODAL'}
93         else:
94             self.report({'WARNING'}, "Active space must be a View3d")
95             return {'CANCELLED'}
96
97
98 def register():
99     bpy.utils.register_class(ViewOperatorRayCast)
100
101
102 def unregister():
103     bpy.utils.unregister_class(ViewOperatorRayCast)
104
105
106 if __name__ == "__main__":
107     register()