123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- import bpy
- from bpy_extras import view3d_utils
- def main(context, event):
- """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)
- ray_target = ray_origin + view_vector
- def visible_objects_and_duplis():
- """Loop over (object, matrix) pairs (mesh only)"""
- depsgraph = context.evaluated_depsgraph_get()
- for dup in depsgraph.object_instances:
- if dup.is_instance: # Real dupli instance
- obj = dup.instance_object
- yield (obj, dup.matrix_world.copy())
- else: # Usual object
- obj = dup.object
- yield (obj, obj.matrix_world.copy())
- 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
- ray_direction_obj = ray_target_obj - ray_origin_obj
- # cast the ray
- success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
- if success:
- return location, normal, face_index
- else:
- return None, None, None
- # cast rays and find the closest object
- best_length_squared = -1.0
- 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 best_obj is None or 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:
- # for selection etc. we need the original object,
- # evaluated objects are not in viewlayer
- best_original = best_obj.original
- best_original.select_set(True)
- context.view_layer.objects.active = best_original
- 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()
|