I'm trying to use the boolean difference modifier to make a cut in my mesh. Instead the mesh looses all vertices and becomes empty, and in the terminal I get about 10 messages that look like:
group 0D0F8A30 is unclassified!
As far as I can tell, my normals are all consistent, I don't have any duplicate vertices, and both my meshes are manifolds. The really strange aspect is that all operations are being performed by a python script I wrote, but the script only fails some of the time. The script is entirely deterministic (as far as I can tell), but sometimes it will run correctly and sometimes it will fail, return an empty mesh and print some of the above messages into the terminal.
The highlighted mesh will have the boolean modifier applied to it and the small cube is the object for this modifier. The intersection of the meshes is nice and transverse.
The strangest aspect of all of this to me is that my apparently deterministic script succeeds some of the time and fails some of the time. When it fails, the point at which it fails will differ. Any insight would be appreciated.
Edit: I've included the code below. Hopefully it's understandable. When the error occurs, it occurs in the section that begins
if clip_edges
for edge in cut_edges(ADDR, n)
Note that this section is in many ways similar to the section that begins
for addr in ADDR_to_remove
However no error ever occurs in that section. The boolean operations in the latter mentioned section involve punching holes through solid faces of the mesh. The boolean operations in the former mentioned (error containing) section involve cutting solid edges of the mesh. I typically see both success and failure after running the code at most 10 times. Between runs of the code all I do is delete the one mesh that is produced by the code.
Also sorry if my code is hard to understand. I'm not used to writing code that others will look at or use.
import bpy
from time import localtime, time
from mathutils import Vector
start_time = time()
n = 3
final_size = 3.1 #makerware units are mm
final_wall_thickness = .1 #minimum 0.0275591 for wsf
clip_edges = True
export_save = False
export_name = "acyclic_surface"
#n=2 ADDR = [('x', (1, 0, 1)), ('z', (0, 1, 2)), ('x', (0, 0, 1)), ('y', (0, 0, 1)), ('z', (1, 0, 0)), ('z', (1, 1, 1)), ('x', (2, 0, 0)), ('x', (1, 1, 1)), ('z', (0, 0, 2)), ('y', (1, 0, 0)), ('y', (0, 2, 1)), ('x', (0, 1, 1)), ('y', (1, 1, 0)), ('x', (1, 0, 0))]
ADDR = [('z', (1, 1, 0)), ('z', (0, 0, 1)), ('x', (3, 0, 1)), ('y', (2, 0, 1)), ('y', (2, 1, 1)), ('z', (0, 1, 0)), ('x', (0, 1, 0)), ('z', (2, 0, 2)), ('z', (2, 2, 1)), ('x', (2, 0, 1)), ('y', (0, 1, 0)), ('y', (1, 2, 0)), ('y', (0, 2, 0)), ('z', (1, 2, 1)), ('x', (2, 1, 0)), ('z', (1, 0, 1)), ('y', (1, 1, 0)), ('z', (0, 2, 1)), ('z', (2, 1, 1))]
#n=4 ADDR = [('x', (4, 1, 1)), ('z', (3, 2, 1)), ('y', (1, 0, 1)), ('y', (3, 1, 1)), ('z', (3, 0, 2)), ('x', (3, 1, 1)), ('y', (0, 1, 0)), ('x', (1, 1, 0)), ('x', (3, 2, 1)), ('z', (0, 1, 0)), ('x', (0, 0, 1)), ('z', (2, 0, 2)), ('y', (0, 2, 0)), ('x', (2, 0, 1)), ('z', (2, 3, 2)), ('z', (1, 0, 1)), ('x', (0, 1, 1)), ('z', (0, 0, 1)), ('x', (4, 2, 1)), ('z', (1, 2, 2)), ('x', (3, 3, 1)), ('y', (1, 1, 1)), ('x', (1, 2, 1)), ('z', (1, 1, 2)), ('x', (4, 3, 1)), ('z', (0, 2, 1)), ('y', (0, 3, 1)), ('z', (3, 1, 1)), ('z', (0, 3, 2)), ('z', (2, 2, 2)), ('z', (1, 3, 2)), ('x', (0, 2, 1)), ('y', (0, 0, 1)), ('z', (3, 3, 1)), ('x', (1, 1, 1)), ('y', (3, 4, 1)), ('x', (0, 1, 0)), ('z', (2, 1, 2))]
#n=5 ADDR = [('z', (3, 2, 0)), ('x', (2, 4, 2)), ('y', (3, 3, 1)), ('y', (2, 2, 0)), ('y', (3, 2, 0)), ('x', (1, 0, 1)), ('x', (2, 4, 3)), ('x', (5, 0, 1)), ('y', (4, 3, 0)), ('z', (3, 0, 0)), ('y', (1, 0, 1)), ('x', (0, 2, 2)), ('z', (2, 1, 3)), ('x', (3, 1, 2)), ('x', (3, 1, 1)), ('z', (2, 2, 0)), ('z', (1, 3, 1)), ('y', (0, 1, 0)), ('x', (5, 1, 1)), ('z', (0, 0, 2)), ('z', (1, 4, 4)), ('z', (2, 0, 1)), ('z', (1, 2, 1)), ('z', (0, 4, 4)), ('x', (1, 1, 2)), ('z', (2, 3, 1)), ('y', (1, 4, 0)), ('z', (1, 1, 0)), ('z', (2, 4, 2)), ('z', (0, 1, 0)), ('y', (0, 5, 2)), ('z', (0, 3, 4)), ('y', (2, 0, 1)), ('y', (2, 1, 1)), ('z', (1, 3, 0)), ('z', (1, 1, 1)), ('x', (2, 3, 2)), ('z', (3, 3, 2)), ('x', (0, 3, 2)), ('x', (0, 3, 3)), ('y', (2, 1, 2)), ('y', (1, 5, 2)), ('z', (4, 3, 2)), ('y', (1, 3, 3)), ('x', (2, 3, 3)), ('y', (2, 2, 1)), ('z', (2, 3, 2)), ('z', (1, 0, 0)), ('x', (1, 0, 0)), ('z', (4, 4, 2)), ('y', (1, 0, 0)), ('y', (4, 0, 0)), ('z', (1, 2, 0)), ('y', (1, 3, 2)), ('y', (0, 3, 3)), ('z', (1, 2, 2)), ('z', (3, 4, 2)), ('y', (0, 5, 3)), ('y', (3, 0, 1)), ('z', (0, 1, 1)), ('y', (1, 1, 1)), ('x', (5, 2, 0)), ('x', (0, 3, 0)), ('x', (2, 0, 0)), ('z', (0, 2, 0)), ('z', (1, 1, 2)), ('y', (3, 3, 0)), ('y', (2, 4, 0)), ('x', (3, 3, 0)), ('x', (2, 1, 2)), ('y', (3, 0, 0)), ('x', (5, 1, 0)), ('x', (2, 1, 0)), ('y', (1, 5, 3)), ('z', (4, 0, 0)), ('y', (4, 3, 1)), ('z', (3, 1, 1)), ('z', (2, 3, 0)), ('y', (2, 2, 2)), ('y', (3, 1, 0)), ('z', (0, 2, 1)), ('z', (1, 3, 4)), ('x', (3, 0, 0)), ('y', (0, 4, 0)), ('z', (0, 3, 0)), ('y', (4, 0, 1)), ('x', (0, 1, 0)), ('z', (0, 3, 1)), ('x', (5, 2, 1)), ('x', (1, 2, 2)), ('y', (2, 3, 1)), ('z', (4, 1, 0)), ('z', (4, 2, 0)), ('x', (0, 1, 2)), ('x', (0, 4, 2)), ('x', (2, 2, 1)), ('x', (0, 4, 3)), ('y', (0, 1, 2)), ('z', (0, 1, 3)), ('x', (4, 1, 0)), ('z', (0, 2, 3)), ('x', (0, 2, 0)), ('x', (5, 0, 0))]
def approx(a, b, epsilon):
return abs(a - b) < epsilon
def t_add(t1, t2):
return tuple(map(lambda a,b:a+b, t1, t2))
def cycle_mode():
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.object.mode_set(mode = 'OBJECT')
def addresses(nx, ny=None, nz=None, diams = False):
if ny == None:
ny = nx
if nz == None:
nz = nx
if not diams:
diams = (1,1,1)
elif diams == 'big':
diams = (n1, n2, n3)
addr = []
for i in range(nx+1):
for j in range(ny+1-diams[1]):
for k in range(nz+1-diams[2]):
addr.append(('x', (i,j,k)))
for i in range(nx+1-diams[0]):
for j in range(ny+1):
for k in range(nz+1-diams[2]):
addr.append(('y', (i,j,k)))
for i in range(nx+1-diams[0]):
for j in range(ny+1-diams[1]):
for k in range(nz+1):
addr.append(('z', (i,j,k)))
return addr
def addr_complement(addr, dims):
addr_s = set(addr)
all_addr_s = set(addresses(dims))
return list(all_addr_s.difference(addr_s))
def hole_addresses(nx, ny=None, nz=None):
if ny == None:
ny = nx
if nz == None:
nz = nx
ADDR = []
for x in range(nx):
for y in range(ny):
for z in range(nz):
ADDR.append((x,y,z))
return ADDR
def cut_edges(addrs, nx, ny=None, nz=None):
#addresses to keep
if ny == None:
ny = nx
if nz == None:
nz = nx
edges = set()
for addr in addrs:
point = addr[1]
if addr[0] == 'x':
edges.update(set([('x', t_add(point, (i,j,k))) for i in (-1,0) for j in (0,1) for k in (0,1)]))
edges.update(set([('y', t_add(point, (0,j,k))) for j in (-1,1) for k in (0,1)]))
edges.update(set([('z', t_add(point, (0,j,k))) for j in (0,1) for k in (-1,1)]))
elif addr[0] == 'y':
edges.update(set([('y', t_add(point, (i,j,k))) for i in (0,1) for j in (-1,1) for k in (0,1)]))
edges.update(set([('x', t_add(point, (i,0,k))) for i in (-1,1) for k in (0,1)]))
edges.update(set([('z', t_add(point, (i,0,k))) for i in (0,1) for k in (-1,1)]))
elif addr[0] == 'z':
edges.update(set([('z', t_add(point, (i,j,k))) for i in (0,1) for j in (0,1) for k in (-1,0)]))
edges.update(set([('x', t_add(point, (i,j,0))) for i in (-1,1) for j in (0,1)]))
edges.update(set([('y', t_add(point, (i,j,0))) for i in (0,1) for j in (-1,1)]))
to_delete = set()
for edge in edges:
if min(edge[1])<0 or (edge[0]=='x' and edge[1][0]>=nx) or (edge[0]=='y' and edge[1][1]>=ny) or (edge[0]=='z' and edge[1][2]>=nz):
to_delete.add(edge)
edges.difference_update(to_delete)
for addr in addrs:
point = addr[1]
if addr[0] == 'x':
edges.discard(('y',point))
edges.discard(('z',point))
edges.discard(('y',t_add(point, (0,0,1))))
edges.discard(('z',t_add(point, (0,1,0))))
elif addr[0] == 'y':
edges.discard(('x',point))
edges.discard(('z',point))
edges.discard(('x',t_add(point, (0,0,1))))
edges.discard(('z',t_add(point, (1,0,0))))
elif addr[0] == 'z':
edges.discard(('x',point))
edges.discard(('y',point))
edges.discard(('x',t_add(point, (0,1,0))))
edges.discard(('y',t_add(point, (1,0,0))))
return edges
working_wall_thickness = (n*final_wall_thickness)/(final_size - final_wall_thickness)
wwt = working_wall_thickness
ADDR_to_remove = addr_complement(ADDR, n)
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
main = bpy.context.active_object
main.name = 'main'
bpy.ops.transform.resize(value = ((n+wwt)/2.0, (n+wwt)/2.0, (n+wwt)/2.0))
main.location = ((n+wwt)/2.0, (n+wwt)/2.0, (n+wwt)/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.subdivide(number_cuts = n-1)
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
hole = bpy.context.active_object
hole.name = 'hole'
bpy.ops.transform.resize(value = ((1-wwt)/2.0, (1-wwt)/2.0, (1-wwt)/2.0))
hole.location = ((1+wwt)/2.0, (1+wwt)/2.0, (1+wwt)/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_all(action = 'SELECT')
bpy.ops.mesh.flip_normals()
bpy.ops.object.mode_set(mode = 'OBJECT')
for addr in hole_addresses(n):
bpy.ops.object.select_all(action = 'DESELECT')
hole.select = True
bpy.ops.object.duplicate()
hole_temp = bpy.context.selectable_objects[0]
hole_temp.location = addr
bpy.ops.object.select_all(action = 'DESELECT')
main.select = True
hole_temp.select = True
bpy.context.scene.objects.active = main
bpy.ops.object.join()
bpy.ops.object.select_all(action = 'DESELECT')
hole.select = True
bpy.ops.object.delete()
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
punch_x = bpy.context.active_object
punch_x.name = 'punch_x'
bpy.ops.transform.resize(value = (wwt, .1, .1))
punch_x.location = (wwt/2.0, (1+wwt)/2.0, (1+wwt)/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
punch_y = bpy.context.active_object
punch_y.name = 'punch_y'
bpy.ops.transform.resize(value = (.1, wwt, .1))
punch_y.location = ((1+wwt)/2.0, wwt/2.0, (1+wwt)/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
punch_z = bpy.context.active_object
punch_z.name = 'punch_z'
bpy.ops.transform.resize(value = (.1, .1, wwt))
punch_z.location = ((1+wwt)/2.0, (1+wwt)/2.0, wwt/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
knife_x = bpy.context.active_object
knife_x.name = 'knife_x'
bpy.ops.transform.resize(value = (.1, wwt, wwt))
knife_x.location = ((1+wwt)/2.0, wwt/2.0, wwt/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
knife_y = bpy.context.active_object
knife_y.name = 'knife_y'
bpy.ops.transform.resize(value = (wwt, .1, wwt))
knife_y.location = (wwt/2.0, (1+wwt)/2.0, wwt/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
bpy.ops.mesh.primitive_cube_add(location = (0,0,0))
knife_z = bpy.context.active_object
knife_z.name = 'knife_z'
bpy.ops.transform.resize(value = (wwt, wwt, .1))
knife_z.location = (wwt/2.0, wwt/2.0, (1+wwt)/2.0)
bpy.ops.object.transform_apply(scale = True, location = True)
for addr in ADDR_to_remove:
x = addr[1][0]
y = addr[1][1]
z = addr[1][2]
if addr[0] == 'x':
punch_x.location = addr[1]
bpy.context.scene.objects.active = main
bpy.ops.object.modifier_add(type = 'BOOLEAN')
main.modifiers[0].object = punch_x
main.modifiers[0].operation = 'DIFFERENCE'
bpy.ops.object.modifier_apply(modifier = 'Boolean')
for vertex in main.data.vertices:
if approx(vertex.co[1], y + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[0], x, 2*wwt):
vertex.co[1] = y + 1
vertex.co[2] = z + 1
elif approx(vertex.co[1], y + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[0], x, 2*wwt):
vertex.co[1] = y + 1
vertex.co[2] = z + wwt
elif approx(vertex.co[1], y + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[0], x, 2*wwt):
vertex.co[1] = y + wwt
vertex.co[2] = z + 1
elif approx(vertex.co[1], y + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[0], x, 2*wwt):
vertex.co[1] = y + wwt
vertex.co[2] = z + wwt
elif addr[0] == 'y':
punch_y.location = addr[1]
bpy.context.scene.objects.active = main
bpy.ops.object.modifier_add(type = 'BOOLEAN')
main.modifiers[0].object = punch_y
main.modifiers[0].operation = 'DIFFERENCE'
bpy.ops.object.modifier_apply(modifier = 'Boolean')
for vertex in main.data.vertices:
if approx(vertex.co[0], x + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[1], y, 2*wwt):
vertex.co[0] = x + 1
vertex.co[2] = z + 1
elif approx(vertex.co[0], x + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[1], y, 2*wwt):
vertex.co[0] = x + 1
vertex.co[2] = z + wwt
elif approx(vertex.co[0], x + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[1], y, 2*wwt):
vertex.co[0] = x + wwt
vertex.co[2] = z + 1
elif approx(vertex.co[0], x + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[2], z + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[1], y, 2*wwt):
vertex.co[0] = x + wwt
vertex.co[2] = z + wwt
elif addr[0] == 'z':
punch_z.location = addr[1]
bpy.context.scene.objects.active = main
bpy.ops.object.modifier_add(type = 'BOOLEAN')
main.modifiers[0].object = punch_z
main.modifiers[0].operation = 'DIFFERENCE'
bpy.ops.object.modifier_apply(modifier = 'Boolean')
for vertex in main.data.vertices:
if approx(vertex.co[0], x + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[1], y + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[2], z, 2*wwt):
vertex.co[0] = x + 1
vertex.co[1] = y + 1
elif approx(vertex.co[0], x + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[1], y + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[2], z, 2*wwt):
vertex.co[0] = x + 1
vertex.co[1] = y + wwt
elif approx(vertex.co[0], x + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[1], y + (1+wwt)/2.0 + 0.1, .001) and approx(vertex.co[2], z, 2*wwt):
vertex.co[0] = x + wwt
vertex.co[1] = y + 1
elif approx(vertex.co[0], x + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[1], y + (1+wwt)/2.0 - 0.1, .001) and approx(vertex.co[2], z, 2*wwt):
vertex.co[0] = x + wwt
vertex.co[1] = y + wwt
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.remove_doubles()
bpy.ops.object.mode_set(mode = 'OBJECT')
if clip_edges:
for edge in cut_edges(ADDR, n):
print(edge)
x = edge[1][0]
y = edge[1][1]
z = edge[1][2]
if edge[0] == 'x':
knife_x.location = edge[1]
bpy.context.scene.objects.active = main
bpy.ops.object.modifier_add(type = 'BOOLEAN')
main.modifiers[0].object = knife_x
main.modifiers[0].operation = 'DIFFERENCE'
bpy.ops.object.modifier_apply(modifier = 'Boolean')
for vertex in main.data.vertices:
if approx(vertex.co[1], y, 2*wwt) and approx(vertex.co[2], z, 2*wwt) and approx(vertex.co[0], x + (wwt+1)/2.0 - 0.1, .001):
vertex.co[0] -= (1-wwt)/2.0 - 0.1
elif approx(vertex.co[1], y, 2*wwt) and approx(vertex.co[2], z, 2*wwt) and approx(vertex.co[0], x + (wwt+1)/2.0 + 0.1, .001):
vertex.co[0] += (1-wwt)/2.0 - 0.1
elif edge[0] == 'y':
knife_y.location = edge[1]
bpy.context.scene.objects.active = main
bpy.ops.object.modifier_add(type = 'BOOLEAN')
main.modifiers[0].object = knife_y
main.modifiers[0].operation = 'DIFFERENCE'
bpy.ops.object.modifier_apply(modifier = 'Boolean')
for vertex in main.data.vertices:
if approx(vertex.co[0], x, 2*wwt) and approx(vertex.co[2], z, 2*wwt) and approx(vertex.co[1], y + (wwt+1)/2.0 - 0.1, .001):
vertex.co[1] -= (1-wwt)/2.0 - 0.1
elif approx(vertex.co[0], x, 2*wwt) and approx(vertex.co[2], z, 2*wwt) and approx(vertex.co[1], y + (wwt+1)/2.0 + 0.1, .001):
vertex.co[1] += (1-wwt)/2.0 - 0.1
elif edge[0] == 'z':
knife_z.location = edge[1]
bpy.context.scene.objects.active = main
bpy.ops.object.modifier_add(type = 'BOOLEAN')
main.modifiers[0].object = knife_z
main.modifiers[0].operation = 'DIFFERENCE'
bpy.ops.object.modifier_apply(modifier = 'Boolean')
for vertex in main.data.vertices:
if approx(vertex.co[0], x, 2*wwt) and approx(vertex.co[1], y, 2*wwt) and approx(vertex.co[2], z + (wwt+1)/2.0 - 0.1, .001):
vertex.co[2] -= (1-wwt)/2.0 - 0.1
elif approx(vertex.co[0], x, 2*wwt) and approx(vertex.co[1], y, 2*wwt) and approx(vertex.co[2], z + (wwt+1)/2.0 + 0.1, .001):
vertex.co[2] += (1-wwt)/2.0 - 0.1
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.ops.object.select_all(action = 'DESELECT')
punch_x.select = True
punch_y.select = True
punch_z.select = True
knife_x.select = True
knife_y.select = True
knife_z.select = True
bpy.ops.object.delete()
c = final_wall_thickness/wwt
bpy.ops.object.select_all()
bpy.ops.transform.resize(value = (c, c, c))
bpy.ops.object.transform_apply(scale = True)
print(time() - start_time)