I'm completely new to blender and scripting and trying out some things:

I created a cube out of several smaller cubes (blue) and have one big cube called master (grey). The master cube overlaps with some of the smaller cubes.

blue cubes and grey master cube

I am now iterating through my small cubes and check with the help of this function (slightly updated):

def is_inside(p, obj):
    result, location, normal, index = obj.closest_point_on_mesh(p)
    p2 = location - p
    v = p2.dot(normal)

    return not(v <= 0.0)

If they are inside the master cube they turn green, of not they turn red. This works pretty well so far.

But now I want to move my master cube around to overlap with some other of the smaller cubes. If I run my script again it seems to check from the old position of the master and not the new one, because the same cubes as before are coloured green.

This is the situation after the second run of the script, where all visible small cubes should be red and the ones inside the grey cube should be green (FYI, they are not): situation after second run of script

How can I get the function to work with the updated postion of the master cube? Do I have to update some other values that I am missing?

This is my code:

master = bpy.data.objects['Master']

# select all smaller cubes

# create colors
green = bpy.data.materials.new("GREEN")
green.diffuse_color = (0.0, 1.0, 0.0)

red = bpy.data.materials.new("RED")
red.diffuse_color = (1.0, 0.0, 0.0)

for cube in bpy.context.selected_objects:
    if isInside(cube.location, master):
        cube.active_material = green

        cube.active_material = red

Thanks in advance for any helpful tips!


1 Answer 1


It's not working because closest_point_on_mesh expects its parameter p in Object Space, not in World Space. That is, the p's coordinates should be measured along the local axes of obj, taking into account obj's axes'location, rotation, and scale. closest_point_on_mesh also returns the location and normal of the closest point in the Object Space of obj.

So long as master is not transformed, its Object Space is the same as World Space, and isInside will work. But it will always compare the position of p to where master was before it was transformed. In order to measure p in master's space, we have to apply the inverse transformation to p as the one that was applied to master.. If you like, we pull master back to the world origin, and maintain the same relative position (rotation, scale) of p to master as it had before we pulled it back.

import bpy
import mathutils

def world_to_obj(point,obj):
    mat_obj_xform = obj.matrix_world
    mat_to_local = obj.matrix_world.inverted()
    return mat_to_local * point

def is_inside(point, obj):
    p = world_to_obj(point, obj)
    hit, hit_location, normal_at_location, face_index = obj.closest_point_on_mesh(p)
    ray = hit_location - p
    dotproduct = ray.dot(normal_at_location) 
    return (dotproduct > 0.0)

green = bpy.data.materials.get("GREEN")
if green is None:
    green = bpy.data.materials.new(name="GREEN")
    green.diffuse_color = (0.0, 1.0, 0.0)

red = bpy.data.materials.get("RED")
if red is None:
    red = bpy.data.materials.new(name="RED")   
    red.diffuse_color = (1.0, 0.0, 0.0)

master = bpy.data.objects['Master']

for cube in bpy.context.selected_objects:

    if is_inside(cube.location, master):
        if cube.data.materials:
            cube.data.materials[0] = green
        if cube.data.materials:
            cube.data.materials[0] = red

Forgive the long-winded variable names and grubby code.. I meant them to be a bit more explanatory. (I've also stopped the repeat creation of materials.)


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .