Check if object is on surface, remove those that aren't with raycast?

I’ve been looking for ways to detect objects that are not on a surface, & remove them. I am developing an addon that works with blender rigid body & some objects fall through sometimes, It is just a clean up option.


Easiest way would be to calculate a bounding box for each object and raycast downward from various points below it (the more raycasts, the more accurate your results will be).

I wrote some code to check if objects are intersecting in world space. If by “on a surface”, you mean that the geometry is crashing, then you could loop through your objects and anything that returns an empty tuple (no intersections), you delete.

from collections import namedtuple
import bpy
from mathutils import Matrix, Vector

Hit = namedtuple('Hit', ('obj', 'location', 'normal', 'face_index'))

def get_intersections(obj1, obj2, get_all=False):
    Cast rays between all edges of `obj1` on `obj2` an return the hit info. Then do the same
    in reverse.
    :param Object obj1: First object to cast rays with.
    :param Object obj2: Second object to cast rays with.
    :param bool get_all: If False, will return the moment a ray hits, instead of collecting all hits.
    def a_to_b(_obj1, _obj2):
        _result = []
        for edge in
            # Convert spatial relationship from _obj1 local space to _obj2 local space for ray cast.
            v1, v2 = edge.vertices
            inv_mat = _obj2.matrix_world.inverted()
            start = inv_mat @ _obj1.matrix_world @[v1].co
            end = inv_mat @ _obj1.matrix_world @[v2].co

            # Cast ray
            delta = end - start
            norm_delta = Vector(delta)
            hit, loc, normal, face_index = _obj2.ray_cast(start, norm_delta, distance=delta.magnitude)
            if hit:
                _result.append(Hit(_obj2, loc, normal, face_index))
                if not get_all:
                    return _result
        return _result

    result = a_to_b(obj1, obj2)
    if result and not get_all:
        return result

    result += a_to_b(obj2, obj1)
    return tuple(result)

Note: This won’t be very efficient for a lot of dense meshes that may not be near each other. I intend to update this when I have time to first check if the bounding boxes are intersecting and only do a full geometry check if they are. That will speed this up for geometry not close to the collider considerably.

1 Like

Nice! How would I make use of this? Do I select the objects and then the surface(plane)?

I think you can start with bounding box or bounding sphere tests to see if that is generally enough for you.

if you have a list of your objects (e.g. a selection), you can just loop through and run this.

if objects is a list of objects you want to check and surface is the surface you want to check against:

intersecting = [obj for obj in objects if get_intersections(obj, surface)]

Then intersecting will be a list of just the objects that intersect with the surface.

I’m sorry, im confused. Where would I include this & how could I execute it?. I have my objects & then my surface selected.

If you select everything and your surface last, then you could put this below the code I posted originally:

surface = bpy.context.active_object
objects = [x for x in bpy.context.selected_objects if x is not surface]
not_intersecting = [obj for obj in objects if not get_intersections(obj, surface)]
bpy.ops.object.delete({'selected_objects': not_intersecting})   # Delete non-intersecting objects.

You can try it out by opening the text editor and pasting this all in there and running the script.


Awesome!, for some reason, after running the script it removes some extra objects. I was wondering if this can be avoided?

As I mentioned, this works if you define an object as being “on the surface” if it is intersecting the surface. I suspect the extra objects being deleted in your case are not actually intersecting the surface. Hard to tell from this distance though. I would be interested to know if you have a case where an object that is intersecting is being deleted.

For a case like that, you could just cast a single ray down in the Z axis to see if it is “above” the surface.