Boolean difference modifier erroneously switches to intersection one time

50 Views Asked by At

I'm trying to make a box with 4 walls (plates N,S,E,W) and a floor (plate_B). They should fit together for 3d printing, so I add circular tabs to some plates, and then I subtract (Boolean-Difference) each plate from its neighbors. I wrote a subtraction function to do this.

This subtraction function works fine for plate_N, plate_S, and plate_E. However, when plate_B is subtracted from plate_W, plate_W gets messed up, and blender-python appears to be doing the Boolean-Intersect modifier for that sole modification. plate_W should just look like a mirror of plate_E, but instead its just these two weird face-loops around the tabs on plate_B.

I have a full blender-python script that recreates this, you should just be able to copy and paste it into your script tab in blender, then run it as-is. Rerunning it should reset everything and start fresh. Could someone figure out why that one subtraction is going wrong?

import bpy
import time
from math import pi
deg2rad = pi/180
print()
print('~ begin ~~~~~~~~~~~~~')
print()

class ExitOK(Exception):
    pass
def clean_exit() :
    raise ExitOK

def clean_slate() :
    for obj in bpy.context.scene.objects :
        bpy.data.objects.remove( obj, do_unlink=True )
    for m in bpy.data.meshes :
        bpy.data.meshes.remove( m )
    for c in bpy.data.curves :
        bpy.data.curves.remove( c )
        
def cylinder( name='cylinder', vertices=32, radius=4, depth=3, location=[0,0,0] ) :
    kw = {}
    kw['vertices' ] = vertices
    kw['radius'   ] = radius
    kw['depth'    ] = depth
    kw['end_fill_type'] = 'NGON'
    kw['calc_uvs' ] = True
    kw['enter_editmode'] = False
    kw['align'] = 'WORLD'
    kw['location'] = location
    bpy.ops.mesh.primitive_cylinder_add( **kw )
    obj = bpy.data.objects.get('Cylinder')
    obj.name = name
    return obj

def rectangle( location=[1,1,1], dimensions=[3,2,1], scale=[1,1,1], name='rectangle' ) :

    bpy.ops.mesh.primitive_cube_add( location=location, scale=scale )
    plate = bpy.data.objects.get('Cube')
    plate.dimensions = dimensions
    plate.name = name
    return plate

def subtract( base, sub ) :
    # subtract mesh sub from mesh base
    # operates on base in place
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    bpy.context.view_layer.objects.active = base
    mod = base.modifiers.new( f'Boolean_{time.time()}', type='BOOLEAN' )
    mod.operation = 'DIFFERENCE'
    mod.object = sub
    print( f'subtracting {sub.name} from {base.name}' )
    #bpy.ops.object.modifier_apply( {'object':base}, modifier=mod.name )
    #base.modifier_apply( mod )
    with bpy.context.temp_override() :
        bpy.ops.object.modifier_apply( {'object':base}, modifier=mod.name )

def merge( objs, name='merged_object', inplace=False ) :
    # merge objects together
    # inplace : bool, if true, merge objs[1:] into obj[0]
    #           otherwise merge objs into new obj
    # name : str, only if inplace=False, set name of final merged object
    
    bpy.ops.object.select_all(action='DESELECT')
    for obj in objs :
        obj.select_set( True )
    
    if inplace :
        obj0 = objs[0]
    else :
        obj0 = simple_copy( objs[0], name=name )
    for obj in objs[1:] :
        mod = obj0.modifiers.new('Boolean_merge', type='BOOLEAN' )
        mod.operation = 'UNION'
        mod.object = obj
        bpy.ops.object.modifier_apply( {'object':obj0}, modifier=mod.name )
    bpy.ops.object.select_all(action='DESELECT')
    if not inplace :
        return obj0

def hide( objs ) :
    if not isinstance( objs, list ) :
        objs = [ objs ]
    for obj in objs :
        obj.hide_set( True )

def main() :

    clean_slate()
    
    T = 2   # plate thickness, mm
    X =  83/2
    Y = 137/2
    Z = 100/2
    
    fast = True
    
    N, S, E, W, B = 'N', 'S', 'E', 'W', 'B'
    plate_specs  = []
    plate_specs += [ [ B, [      2*X,2*Y,T],  0,  0, [ 0,              0,  T       ] ] ]
    plate_specs += [ [ N, [      2*X,2*Z,T],  0, 90, [ 0,        Y+(T/2),  Z-(T/2) ] ] ]
    plate_specs += [ [ S, [      2*X,2*Z,T],  0, 90, [ 0,       -Y-(T/2),  Z-(T/2) ] ] ]
    plate_specs += [ [ E, [2*(Y+T+T),2*Z,T], 90, 90, [ X+(T/2),        0,  Z-(T/2) ] ] ]
    plate_specs += [ [ W, [2*(Y+T+T),2*Z,T], 90, 90, [-X-(T/2),        0,  Z-(T/2) ] ] ]
    
    plate = {}
    for P, lwh, theta, phi, xyz in plate_specs :
        plate[P] = rectangle( name=f'plate_{P}', dimensions=lwh )
    
    def hide_plates() :
        hide( [plate[P] for P in [N,S,E,W,B] ] )
    def show_plates() :
        show( [plate[P] for P in [N,S,E,W,B] ] )
    
    def assembled_layout() :
        for P, lwh, theta, phi, xyz in plate_specs :
            plate[P].rotation_euler[2] = theta * deg2rad
            plate[P].rotation_euler[1] = 0
            plate[P].rotation_euler[0] = phi   * deg2rad
            plate[P].location = xyz
    
    def flat_layout() :
        for P, lwh, theta, phi, xyz in plate_specs :
            plate[P].rotation_euler = [0,0,0]
            plate[P].location       = [0,0,0]
    
        # add tabs to each plate
    flat_layout()
    tabs = []
    tabs += [ [ B, [      X, 0.4*Y], 3*T ] ]
    tabs += [ [ B, [      X,-0.4*Y], 3*T ] ]
    tabs += [ [ B, [     -X, 0.4*Y], 3*T ] ]
    tabs += [ [ B, [     -X,-0.4*Y], 3*T ] ]
    tabs += [ [ B, [  0.5*X,     Y], 3*T ] ]
    tabs += [ [ B, [ -0.5*X,     Y], 3*T ] ]
    tabs += [ [ B, [  0.5*X,    -Y], 3*T ] ]
    tabs += [ [ B, [ -0.5*X,    -Y], 3*T ] ]
    tabs += [ [ N, [      X,  Z/2 ], 3*T ] ]
    tabs += [ [ N, [      X, -Z/2 ], 3*T ] ]
    tabs += [ [ N, [     -X,  Z/2 ], 3*T ] ]
    tabs += [ [ N, [     -X, -Z/2 ], 3*T ] ]
    tabs += [ [ S, [      X,  Z/2 ], 3*T ] ]
    tabs += [ [ S, [      X, -Z/2 ], 3*T ] ]
    tabs += [ [ S, [     -X,  Z/2 ], 3*T ] ]
    tabs += [ [ S, [     -X, -Z/2 ], 3*T ] ]
    tabs += [ [ E, [ -7*Y/9, -Z   ], 3*T ] ]
    tabs += [ [ E, [  7*Y/9, -Z   ], 3*T ] ]
    tabs += [ [ E, [      0, -Z   ], 3*T ] ]
    tabs += [ [ W, [ -7*Y/9, -Z   ], 3*T ] ]
    tabs += [ [ W, [  7*Y/9, -Z   ], 3*T ] ]
    tabs += [ [ W, [      0, -Z   ], 3*T ] ]
    for P, [x,y], r in tabs :
        cyl = cylinder( name=f'tab_{P}_{x}_{y}', location=[x,y,0], radius=r, depth=T, vertices=100 )
        merge( [ plate[P], cyl ], inplace=True )
        hide( cyl )
    
    # cut tabs from each neighboring plate
    assembled_layout()
    plate_neighbors = []
    plate_neighbors += [ [ N, [B] ] ]
    plate_neighbors += [ [ S, [B] ] ]
    plate_neighbors += [ [ E, [B,N,S] ] ]
    plate_neighbors += [ [ W, [B,N,S] ] ]
    for P, neighbors in plate_neighbors :
        for neighbor in neighbors :
            
            # when P is 'W' and neighbor is 'B' is when this subtraction goes wrong
            subtract( plate[P], plate[neighbor] )
    
    bpy.context.view_layer.update()
           
    print()
    print('~ done ~~~~~~~~~~~~~~~')

if __name__ == '__main__' :
    try :
        main()
    except ExitOK :
        pass

I've tried setting the subtractee object as the active object, but that didn't fix it. I've tried deselecting everything at the beginning of the subtraction function, but that didn't fix it either.

I've tried google searches for better blender-python code, but none seem to do anything, or they results are for older versions of blender.

When I run the above script, I get warnings in the system console:

DeprecationWarning: Passing in context overrides is deprecated in favor of Context.temp_override(..), calling "object.modifier_apply"

But switching to a temporary context didn't seem to fix the problem, but I also couldn't find examples of how to do a boolean modifier in a temporary context, so I may not have set it up right.

Switching the order of the cuts doesn't seem to affect the issue, so maybe plate_W is subtly different from the other plates somehow.

0

There are 0 best solutions below