import bpy
import bmesh
from bpy_extras import view3d_utils
from mathutils.bvhtree import BVHTree
from mathutils import Vector, Matrix
def get_ray_plane_intersection(ray_origin, ray_direction, plane_point, plane_normal):
d = ray_direction.dot(plane_normal)
if abs(ray_direction.dot(plane_normal)) <= 0.00000001: return float('inf')
return (plane_point-ray_origin).dot(plane_normal) / d
def get_ray_origin(ray_origin, ray_direction, ob):
mx = ob.matrix_world
q = ob.rotation_quaternion
bbox = [Vector(v) for v in ob.bound_box]
bm = Vector((min(v.x for v in bbox),min(v.y for v in bbox),min(v.z for v in bbox)))
bM = Vector((max(v.x for v in bbox),max(v.y for v in bbox),max(v.z for v in bbox)))
x,y,z = Vector((1,0,0)),Vector((0,1,0)),Vector((0,0,1))
planes = []
if abs(ray_direction.x)>0.0001: planes += [(bm,x), (bM,-x)]
if abs(ray_direction.y)>0.0001: planes += [(bm,y), (bM,-y)]
if abs(ray_direction.z)>0.0001: planes += [(bm,z), (bM,-z)]
dists = [get_ray_plane_intersection(ray_origin,ray_direction,mx*p0,q*no) for p0,no in planes]
return ray_origin + ray_direction * min(dists)
def main(self, 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
ray_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord).normalized()
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
if rv3d.is_perspective:
#ray_target = ray_origin + ray_vector * 100
r1 = get_ray_origin(ray_origin, -ray_vector, context.object)
ray_target = r1
else:
# need to back up the ray's origin, because ortho projection has front and back
# projection planes at inf
r0 = get_ray_origin(ray_origin, ray_vector, context.object)
r1 = get_ray_origin(ray_origin, -ray_vector, context.object)
ray_origin = r0
ray_target = r1
def ray_cast_obj_bvh(obj, bvh, 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_dir_obj = ray_target_obj - ray_origin_obj
d = ray_dir_obj.length
ray_dir_obj.normalize()
# cast the ray
hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
#cast it again with BVH
hit_b, normal_b, face_index_b, distance = bvh.ray_cast(ray_origin_obj, ray_dir_obj)
if face_index != -1:
return [(hit, normal, face_index),(hit_b, normal_b, face_index_b)]
else:
return [(None, None, None)]
matrix = context.object.matrix_world
results = ray_cast_obj_bvh(context.object, self.BVH, matrix)
loc, no, f_ind = results[0]
if loc:
loc_b, no_b, f_indb = results[1]
print((loc, loc_b))
print((no, no_b))
print((f_ind, f_indb))
context.scene.cursor_location = matrix * loc
if loc_b:
bpy.data.objects['Lamp'].location = matrix * loc_b
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(self, 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)
ob = context.object
if ob.hide or ob.type != 'MESH':
self.report({'WARNING'}, "Active object must be visible and a mesh")
return {'CANCELLED'}
self.BVH = BVHTree.FromObject(ob, context.scene)
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()
http://www.pasteall.org/blend/37368