4
$\begingroup$

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()
$\endgroup$
10
  • $\begingroup$ Does that message show in system console or in blender ui? It probably occurs if the input meshes are "bad". Does it work if you use something more simple? Perhaps run remove_doubles() or Mesh.validate(True) beforehand? $\endgroup$
    – CodeManX
    Commented Nov 24, 2013 at 21:27
  • $\begingroup$ @CoDEmanX Message shows up in blender UI (see image that's part of linked Blender documentation). Calling these methods before the offending difference() does not seem to help so far (in fact if I call remove_doubles() I get RuntimeError: Operator bpy.ops.mesh.remove_doubles.poll() failed, context is incorrect). $\endgroup$
    – Drux
    Commented Nov 24, 2013 at 21:53
  • $\begingroup$ Mesh needs to be in editmode for remove_doubles, or use bmesh.ops (from_mesh / to_mesh required) $\endgroup$
    – CodeManX
    Commented Nov 25, 2013 at 0:35
  • $\begingroup$ @CoDEmanX So no, switching to edit mode and then removing doubles before the offending call to difference() does not help either. From the evidence available so far I also don't see why it would. The problem now seems to be related to the order of adding/applying boolean operators (such as DIFFERENCE) to a mesh when targets are overlapping. The quoted advise from the documentation seems to say: ignore the error and use/apply the respective modifiers at a later time ("for subsequent operations"): but what does that amount to in practice (esp. in the context of a script)? $\endgroup$
    – Drux
    Commented Nov 25, 2013 at 8:50
  • $\begingroup$ The error message has nothing to do with the script, but rather what it'd being executed on - if you add a boolean modifier to your geometry without using this script, it should show the same error $\endgroup$
    – Greg Zaal
    Commented Nov 26, 2013 at 12:38

2 Answers 2

1
$\begingroup$

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”

$\endgroup$
1
$\begingroup$

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.

$\endgroup$

You must log in to answer this question.

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