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