python template for doing mouse ray casts to pick objects in the 3d viewport.
authorCampbell Barton <ideasman42@gmail.com>
Sun, 1 Jul 2012 08:46:02 +0000 (08:46 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Sun, 1 Jul 2012 08:46:02 +0000 (08:46 +0000)
release/scripts/templates/operator_modal_view3d_raycast.py [new file with mode: 0644]

diff --git a/release/scripts/templates/operator_modal_view3d_raycast.py b/release/scripts/templates/operator_modal_view3d_raycast.py
new file mode 100644 (file)
index 0000000..5d6b66a
--- /dev/null
@@ -0,0 +1,107 @@
+import bpy
+from mathutils import Vector
+from bpy_extras import view3d_utils
+
+
+def main(context, event, ray_max=10000.0):
+    """Run this function on left mouse, execute the ray cast"""
+    # get the context arguments
+    scene = context.scene
+    region = context.region
+    rv3d = context.region_data
+    coord = event.mouse_x, event.mouse_y
+
+    # get the ray from the viewport and mouse
+    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
+    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
+    ray_target = ray_origin + (view_vector * ray_max)
+
+    scene.cursor_location = ray_target
+
+    def visible_objects_and_duplis():
+        """Loop over (object, matrix) pairs (mesh only)"""
+
+        for obj in context.visible_objects:
+            if obj.type == 'MESH':
+                yield (obj, obj.matrix_world.copy())
+
+        if obj.dupli_type != 'NONE':
+            obj.dupli_list_create(scene)
+            for dob in obj.dupli_list:
+                obj_dupli = dob.object
+                if obj_dupli.type == 'MESH':
+                    yield (obj_dupli, dob.matrix.copy())
+
+            obj.dupli_list_clear()
+
+    def obj_ray_cast(obj, matrix):
+        """Wrapper for ray casting that moves the ray into object space"""
+
+        # get the ray relative to the object
+        matrix_inv = matrix.inverted()
+        ray_origin_obj = matrix_inv * ray_origin
+        ray_target_obj = matrix_inv * ray_target
+
+        # cast the ray
+        hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
+
+        if face_index != -1:
+            return hit, normal, face_index
+        else:
+            return None, None, None
+
+    # cast rays and find the closest object
+    best_length_squared = ray_max * ray_max
+    best_obj = None
+
+    for obj, matrix in visible_objects_and_duplis():
+        if obj.type == 'MESH':
+            hit, normal, face_index = obj_ray_cast(obj, obj.matrix_world)
+            if hit is not None:
+                length_squared = (hit - ray_origin).length_squared
+                if length_squared < best_length_squared:
+                    best_length_squared = length_squared
+                    best_obj = obj
+
+    # now we have the object under the mouse cursor,
+    # we could do lots of stuff but for the example just select.
+    if best_obj is not None:
+        best_obj.select = True
+
+
+class ViewOperatorRayCast(bpy.types.Operator):
+    """Modal object selection with a ray cast"""
+    bl_idname = "view3d.modal_operator_raycast"
+    bl_label = "RayCast View Operator"
+
+    def modal(self, context, event):
+        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
+            # allow navigation
+            return {'PASS_THROUGH'}
+        elif event.type == 'LEFTMOUSE':
+            main(context, event)
+            return {'RUNNING_MODAL'}
+        elif event.type in {'RIGHTMOUSE', 'ESC'}:
+            return {'CANCELLED'}
+
+        return {'RUNNING_MODAL'}
+
+    def invoke(self, context, event):
+        if context.space_data.type == 'VIEW_3D':
+            context.window_manager.modal_handler_add(self)
+            return {'RUNNING_MODAL'}
+        else:
+            self.report({'WARNING'}, "Active space must be a View3d")
+            return {'CANCELLED'}
+
+
+def register():
+    bpy.utils.register_class(ViewOperatorRayCast)
+
+
+def unregister():
+    bpy.utils.unregister_class(ViewOperatorRayCast)
+
+
+if __name__ == "__main__":
+    register()