import bpy from bpy_extras import view3d_utils def main(context, event, ray_max=1000.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_region_x, event.mouse_region_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) if rv3d.view_perspective == 'ORTHO': # move ortho origin back ray_origin = ray_origin - (view_vector * (ray_max / 2.0)) ray_target = ray_origin + (view_vector * ray_max) 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, matrix) if hit is not None: hit_world = matrix * hit scene.cursor_location = hit_world length_squared = (hit_world - 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 context.scene.objects.active = best_obj 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()