How to use ray_cast

I’m trying to write a script that scans an object from a particular perspective and creates a copy of it with no occluded geometry. Mostly I’m planning to use the ray_cast function for this, but documentation on it seems to be a bit lacking, and I was only able to find one other support thread on the subject which was a bit out of date.

  • The ray_cast function takes three parameters: origin, direction and distance. Distance is optional, so I did away with that. I assumed direction was a vector, but the other support thread suggested it was a destination point, which is a bit confusing. The documentation doesn’t give any detail on this, anyone know which it’s supposed to be?

  • It seems to return four values: result (boolean, did it hit or not), location (point at which it hit), normal (not useful to me), face index (also not useful). How exactly do I access the value I want? Currently I’m using:

cast = ray_cast(someparameters)

then looking at cast[0], cast[1] etc. to see the individual return values, but I’m not sure if that’s how it’s supposed to be used.

  • I’m also running into the following error when I run my script on the default cube: “runtime error: Cube has no mesh data to be used for ray casting.” It’s in edit mode if that makes a difference.

  • ray_cast works in object space, but my points are in global space. What’s the easiest way to convert between the two? I tried a couple of suggestions from another support thread but they didn’t quite work. Possibly outdated.

Thanks in advance for any and all replies. Here’s the relevant section of my current code:


def createMatrix (direction, rows, columns, center, pitch, height, width):
    #Opening Variables
    target = bpy.context.scene.objects.active
    matrix = []
    ceiling = findZBoundary("Top")
    floor = findZBoundary("Bottom")
    if (direction == "Top"):
        startPoint  = [center[0] - (width / 2), center[1] - (height / 2), ceiling]
        scanStart   = [startPoint[0], startPoint[0], ceiling]
        scanEnd     = [startPoint[0], startPoint[0], floor]
    else:
        startPoint  = [center[0] - (width / 2), center[1] - (height / 2), floor]
        scanStart   = [startPoint[0], startPoint[0], floor]
        scanEnd     = [startPoint[0], startPoint[0], ceiling]
    
    #Convert to object space
    #I commented these out because they weren't working
    #startPoint  = target.matrix_local * startPoint
    #scanStart   = target.matrix_local * scanStart
    #scanEnd     = target.matrix_local * scanEnd
    
    #Populate Matrix
    for i in range(rows):
        #Create new row
        row = []
        
        #Populate row
        for j in range(columns):
            #Update scanStart and ScanEnd
            scanStart [0] = startPoint[0] + (j * pitch)
            scanStart [1] = startPoint[1] + (i * pitch)
            scanEnd   [0] = startPoint[0] + (j * pitch)
            scanEnd   [1] = startPoint[1] + (i * pitch)
            
            #Cast Ray
            scan = target.ray_cast(scanStart, scanEnd)
            
            #Find new point
            if (scan[0] == True):
                #Ray hit object, set newPoint to it's value
                newPoint = scan[1]
            else:
                #Ray missed object, create newPoint on parting plane
                newPoint = scanPoint
                newPoint [2] = center [2]
            
            #Convert to global space
            #newpoint = target.matrix_world * newpoint
            
            #Add result to row
            row.append(newPoint)
        
        #Append row to matrix
        matrix.append(row)
    
    #Deleteme
    #print("Here's the matrix")
    #print(matrix)
    
    #Return result
    return matrix

Alternately, can anyone name a plugin that uses ray_cast successfully? I could probably work off that.

Why are you assigning values to scanStart and scanEnd if you’re going to immediately overwrite them in a for loop?

Also, I think there’s multiple variations of ray cast available through the Python API and they all work different. That could be why you got conflicting info on whether it was destination or direction. The simplest way I’ve found to check on documentation is to type the command into Blender’s built-in Python console and use the “Autocomplete” button. For the object method version of ray_cast this was the result:

>>> bpy.context.scene.objects.active.ray_cast(
ray_cast()
Object.ray_cast(origin, direction, distance=1.70141e+038)
Cast a ray onto in object space

No info on the return value though :confused: Looking at a quick test and going from memory I think this is what is in the 4 item tuple that ray_cast returns:

  1. bool indicating whether the “cast” ray hit a face
  2. an “object space” coordinate indicating where on the face the ray hit
  3. the normal value of the face that was hit (not sure on this one)
  4. the index value of the face that was hit

My general workflow for learning how any function / method works is to start with a super basic example before trying to integrate it into a program. To keep it simple I’ll use this mesh object version of ray_cast from your code:

import bpy
from mathutils import Vector

# create a plane at "world" location 0, 0, 3
bpy.ops.mesh.primitive_plane_add(radius=1, view_align=False, enter_editmode=False, location=(0, 0, 3))
# new objects are always added at scene index 0
plane_ob = bpy.context.scene.objects[0]

ray_begin = Vector((0.5, 0, 2))
ray_end = Vector((0.5, 0, 4))
ray_direction = ray_end - ray_begin
ray_direction.normalize()
# covert ray_begin to "plane_ob" local space
ray_begin_local = plane_ob.matrix_world.inverted() * ray_begin

# do a ray cast on newly created plane
cast_result = plane_ob.ray_cast(ray_begin_local, ray_direction)
print("cast_result:", cast_result)
1 Like

Thanks for your reply :slight_smile:

Why are you assigning values to scanStart and scanEnd if you’re going to immediately overwrite them in a for loop?

No good reason. My code is in development, so there’s probably multiple bugs and inefficiencies in there. Right now I’m just trying to get ray_cast working.

Also, I think there’s multiple variations of ray cast available through the Python API and they all work different. That could be why you got conflicting info on whether it was destination or direction. The simplest way I’ve found to check on documentation is to type the command into Blender’s built-in Python console and use the “Autocomplete” button. For the object method version of ray_cast this was the result:

It’s direction, but the only example code I could find was treating it more like a destination (my words). I can only find one example by searching the API. My guess is that the example code was outdated.

Thanks for the tip about the console, I didn’t know it could do that. I was using docs.blender.org/api/current, it has a little more information but still not enough. For example it didn’t say that the return type is a tuple or what datatype “direction” is expecting.

No info on the return value though :confused: Looking at a quick test and going from memory I think this is what is in the 4 item tuple that ray_cast returns

Your memory is excellent, that’s exactly what it says.

My general workflow for learning how any function / method works is to start with a super basic example before trying to integrate it into a program. To keep it simple I’ll use this mesh object version of ray_cast from your code:

Thanks for the help, I’ll try to work from that. Any idea why the default cube would trigger that error message? “runtime error: Cube has no mesh data to be used for ray casting.”

I was accessing it by “bpy.context.scene.objects.active”

Also, is there a best practice for converting points between global and object space, then back again? I found a few old threads, but their solutions seemed outdated.

1 Like

Yeah, it’s probably outdated code. I just did a quick check and it looks like there’s 3 different versions of ray_cast: the mesh object method used in your code, another one in mathutils.bvhtree.BVHTree, and one in bpy.context.scene. Each one has different return values, but they all take the same arguments (origin, direction, distance).

Thanks for the tip about the console, I didn’t know it could do that. I was using docs.blender.org/api/current, it has a little more information but still not enough. For example it didn’t say that the return type is a tuple or what datatype “direction” is expecting.

Yeah, it should. It looks like an omission in the documentation in the Python console which is weird because the return value shows correctly on docs.blender.org.

Your memory is excellent, that’s exactly what it says.

Thanks.

Thanks for the help, I’ll try to work from that. Any idea why the default cube would trigger that error message? “runtime error: Cube has no mesh data to be used for ray casting.”

I was accessing it by “bpy.context.scene.objects.active”

I’m not sure what would cause that. My guess is a problem with the conditions ray_cast is being called in (wrong mode, bad selection, bad mesh data, etc) or an issue with one of the arguments ray_cast is being supplied.

Also, is there a best practice for converting points between global and object space, then back again? I found a few old threads, but their solutions seemed outdated.

Not that I am aware of. I’ve always used “.matrix_world * local_co” for conversion to global and “.matrix_world.inverted() * global_co” for conversion to local.

1 Like

Thanks so much, that’s all really helpful, I’ll give it a try this weekend.

In case someone stumbles into this in 2020, since Blender 2.8 the above code needs to look like this:

import bpy
from mathutils import Vector

# create a plane at "world" location 0, 0, 3
bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, location=(0, 0, 3))
# new objects are always added at scene index 0

plane_ob = bpy.context.scene.objects[0]

ray_begin = Vector((0.5, 0, 2))
ray_end = Vector((0.5, 0, 4))
ray_direction = ray_end - ray_begin
ray_direction.normalize()
# covert ray_begin to "plane_ob" local space
ray_begin_local = plane_ob.matrix_world.inverted() @ ray_begin

# do a ray cast on newly created plane
cast_result = plane_ob.ray_cast(ray_begin_local, ray_direction)
print("cast_result:", cast_result)

The result is not very informative though, unfortunately, but thanks for providing this example though ! Here is what I got, maybe it helps to understand ray casting though:

cast_result: (False, Vector((0.0, 0.0, 0.0)), Vector((0.0, 0.0, 0.0)), -1)

2 Likes