Why does attached Python script produce error “Cannot execute boolean operation”?

Loading...

Why does attached Python script produce error “Cannot execute boolean operation”?

I am trying to create a 3D model using Python scripting with Blender 2.69. I start from a cube and subtract several spheres from it to make indentations. I then form a ring out of four cylinders and four spheres, which I then also subtract from the sphere. However, the result of this step (as marked in the script) is unexpected (the ring isn't subtracted, only one cylinder is deleted/unlinked from the scene). When I inspect the cube in the Blender UI, it only says:

Cannot execute boolean operation.

There is also something else that seems odd and that could be related: If I uncomment the two lines at the end of union(), it will lead to this error message:

RuntimeError: Operator bpy.ops.object.modifier_apply.poll() failed, context is incorrect

How can I dig deeper and find out more about the root cause, and how does the script have to be patched?
I've seen this previous answer and a mentioning of the error message in the Blender documentation, however neither seemed to provide sufficient information. I get the sense that the problem may be that several of the objects which I want to subtract are overlapping and it may not be possible to subtract them all at once, but I don't (yet) know what specific changes to the script are  needed to obey this piece of advice from the documentation:

In this case, you can apply the first Boolean modifier of the stack for the target and then use the other Boolean modifier(s) in the stack for subsequent operations. 

Now here is the current script:
import bpy
import math

def deleteAll():
    bpy.ops.object.select_all(action = 'SELECT')
    bpy.ops.object.delete()

def createBoard():
    # prepare some constants
    BOARD_WIDTH  = 22.7
    BOARD_HEIGHT = 22.7
    BOARD_DEPTH  = 1.5
    SPHERE_DIAMETER = 1.9
    SPHERE_PADDING  = 0.5
    DITCH_DIAMETER = 2.5
    DITCH_PADDING  = 0.8
    # create platform
    origin = (0,0,0)
    bpy.ops.mesh.primitive_cube_add(location=origin)
    platform = bpy.context.active_object
    platform.scale = (BOARD_WIDTH/2,BOARD_HEIGHT/2,BOARD_DEPTH/2)
    # create spheres
    for i in [-2,-1,1,2]:
        for j in [-2,-1,1,2]:
            x = (SPHERE_DIAMETER + SPHERE_PADDING)*(i - copysign(.5,i));
            y = (SPHERE_DIAMETER + SPHERE_PADDING)*(j - copysign(.5,j));
            z = BOARD_DEPTH/2
            difference(platform,createSphere(x,y,z,SPHERE_DIAMETER/2))
    # create ditch
    xMax = BOARD_WIDTH/2  - DITCH_PADDING - DITCH_DIAMETER/2
    xMin = -xMax
    yMax = BOARD_HEIGHT/2 - DITCH_PADDING - DITCH_DIAMETER/2
    yMin = -yMax
    z = BOARD_DEPTH/2
    r = DITCH_DIAMETER/2
    ring = createCylinder(xMin,0,z,r,xMax,[math.pi/2,0,0])
    union(ring,createSphere(xMin,yMin,z,r))
    union(ring,createCylinder(0,yMin,z,r,xMax,[0,math.pi/2,0]))
    union(ring,createSphere(xMax,yMin,z,r))
    union(ring,createCylinder(xMax,0,z,r,yMax,[math.pi/2,0,0]))
    union(ring,createSphere(xMax,yMax,z,r))
    union(ring,createCylinder(0,yMax,z,r,yMax,[0,math.pi/2,0]))
    union(ring,createSphere(xMin,yMax,z,r))
    difference(platform,ring) # this leads to unexpected result

def createSphere(x, y, z, r):
    origin = (x,y,z)
    bpy.ops.mesh.primitive_uv_sphere_add(location=origin)
    sphere = bpy.context.active_object
    sphere.scale = (r,r,r)
    return sphere

def createCylinder(x, y, z, r, height, rotation):
    origin = (x,y,z)
    bpy.ops.mesh.primitive_cylinder_add(location=origin)
    cylinder = bpy.context.active_object
    cylinder.scale = (r,r,height)
    cylinder.rotation_mode = 'XYZ'
    cylinder.rotation_euler = rotation
    return cylinder

def difference(first,second):
    modifier = first.modifiers.new('Modifier', 'BOOLEAN')
    modifier.object = second
    modifier.operation = 'DIFFERENCE'
    bpy.ops.object.modifier_apply(apply_as='DATA', modifier=modifier.name)
    scene = bpy.context.scene
    scene.objects.unlink(second)

def union(first,second):
    modifier = first.modifiers.new('Modifier', 'BOOLEAN')
    modifier.object = second
    modifier.operation = 'UNION'
    bpy.ops.object.modifier_apply(apply_as='DATA', modifier=modifier.name)
    #scene = bpy.context.scene
    #scene.objects.unlink(second)

if __name__ == "__main__":
    deleteAll()
    createBoard()

Solutions/Answers:

Answer 1:

It reminds me of an error I had during a manual difference, like this one: http://blenderartists.org/forum/archive/index.php/t-263716.html

It seemed to me that blender 2.63 cannot make the difference of two objects sharing the same plane frontier. I just moved one for a very short distance and got rid of the message “Cannot execute boolean operation”

Answer 2:

To help find the source of the error, starting blender from a console window provides more information on error messages, in this case the output was

SG failed, exception degenerate edge
Unknown internal error in boolean

So, as mentioned in ebt’s answer, the error probably has something to do with vertices or faces sharing the same position in the different objects.

To fix this, I modified line 57 of your script (in the function createCylinder)

cylinder.scale = (r,r,height+0.01)

cylinder.scale = (r,r,height)

This allows the ring to be applied as a boolean to the cube, however inspecting the ring shows that it too has several problems with booleans. It may be possible to resolve it with a similar approach, however I wouldn’t recommend it. In my experience blender’s booleans utterly fail when faces or vertices of the objects share the same positions.
Unless doing this as a python script is essential, it would be easiest to model the ring separately, then subtract that from the platform.

References

Loading...