2
$\begingroup$

How do I 'Apply' the Boolean Difference operation? I've written some Python that creates a hollow object visually, but in fact is not hollow. Visually I get the right thing: enter image description here

Exporting to STL still has both objects (not an empty shell). I can manually complete the application of the Boolean operation by "Applying" enter image description here

enter image description here

and deleting the inner object. Then the export to STL file is correct.

Here is the code I wrote, can someone let me know what calls I can make to apply the boolean modifier action and delete the object I used to cut into the other object with?

import sys
import bpy
D = bpy.data
C = bpy.context
from mathutils import *
import math
bpy.app.debug=True

########################################################################
# Stitch together vertices to create a face in the arc 
def face(column, row, columns, rows):
   v3 = ((column + 1) * rows) + row
   v3 = (((v3 // 4) * 4) + (v3 + 1) % 4)
   v4 = (column * rows + 1) + row
   v4a =  (((v4 // 4) * 4) + (v4) % 4)
   if v4a == v3:
      v4 = v4a - 4
   return (column* rows + row,
           (column + 1) * rows + row,
           v3,
           v4)

########################################################################
# Create an arc, start and eng angle, a scale to convert units, inner/outer size, height and the number of segments
def arc_segment(nme, start_ang, end_ang, scale, outer_size, inner_size, height, segs):
   # Assuming arguments are in centimeters and in degrees, adjust o meters and radians.
   # Change from degrees to radians
   start_ang = (math.tau / 360.0) * start_ang
   end_ang = (math.tau / 360.0) * end_ang
   if start_ang > end_ang:
      start_ang, end_ang = end_ang, start_ang

   # Change from meters (unit of measure in Blender) to centimeters
   outer_size = scale * outer_size
   inner_size = scale * inner_size

   # We're creating the arc, beginning with the newmericly smaller angle.
   if outer_size < inner_size:
      outer_size, inner_size = inner_size, outer_size

   # Arc will be created withe center at 0, 1/2 infront and 1/2 behing X/Y plane
   height = scale * height
   # Use z for "height", center is 0, so 1/2 height away (plus), and 1/2 height close (minus)
   p_height = (height * 0.5)
   m_height = (height * -0.5)

   # Think of the arc as a tube. This loop connects the faces to create the tube.
   verts = []
   for seg in range(0, segs + 1):
      verts.append((math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,
                    math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,    p_height))
      verts.append((math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,
                    math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,    m_height))
      verts.append((math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,
                    math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,    m_height))
      verts.append((math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,
                    math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,    p_height))

   sides = 4
   args = (segs, sides)
   faces = [face(x, y, segs, sides) for x in range(segs) for y in range(sides)]

   # Now cap the ends of the tube
   faces.append((0, 1, 2, 3))
   vbase = segs * sides
   faces.append((vbase + 3, vbase + 2, vbase + 1, vbase))

   # Create object, link it into the scene, make it 'active'.
   mesh_data = bpy.data.meshes.new(nme)  
   mesh_data.from_pydata(verts, [], faces)  
   mesh_data.update() # (calc_edges=True) not needed here  
   
   cube_object = bpy.data.objects.new(nme, mesh_data)  
   
   scene = bpy.context.scene    
   bpy.context.collection.objects.link(cube_object)
   cube_object.select_set(state=True)

########################################################################
# Solids modeling (CSG) take away coliding parts of one shape from another.
def csg(prim, cut, new_name, objects, operation):
   o_prim = objects[prim]
   o_cut = objects[cut]
   cut_work = o_prim.modifiers.new(type="BOOLEAN", name=new_name)
   cut_work.object = o_cut
   cut_work.operation = 'DIFFERENCE'
   o_cut.hide_set(True)                 # Hidden--But will still exist in an STL export.
   # --  --  --  --  --  --  --  --  --  --  --  --  --
   # What calls do I add here to "apply" the "difference" and delete the o_cut object?

   # --  --  --  --  --  --  --  --  --  --  --  --  --
   
########################################################################
#
objects = bpy.data.objects
set_scale = 2.54 * 2
#
# Create some arcs
arc_segment("big_display_hull", 5.0, 90.0, set_scale, 11.5, 8.0, 4.5, 34)
arc_segment("big_hollow_hull", 6.5, 88.5, set_scale, 11.6, 7.7, 4.1, 34)

csg("big_display_hull", "big_hollow_hull", "o1", objects, 'DIFFERENCE')

arc_segment("attach_hulls",        360 + 110, 360 - 110.0, set_scale, 9.0, 8.0, 4.9, 120)
arc_segment("attach_hollow_hulls", 360 + 113, 360 - 113.0, set_scale, 9.2, 7.9, 4.1, 120)
csg("attach_hulls", "attach_hollow_hulls", "o3", objects, 'DIFFERENCE')

arc_segment("small_display_hull", 360 - 10, 360 - 65, set_scale, 11.5, 8.0, 4.5, 22)
arc_segment("small_hollow_hull", 360 - 11.5, 360 - 63.5, set_scale, 11.6, 7.7, 4.1, 22)
csg("small_display_hull", "small_hollow_hull", "o3", objects, 'DIFFERENCE')

I did try a suggested solution Scripting:How to correctly add a boolean modifer to an object? , after fixing a "'apply_as' unrecognized" issue, it ran. However I see that the boolean is still not applied. Given that blender 2.90.1 is so different than v 2.79, can someone post a complete python Boolean (CSG) example where the resulting meshes are the 'finished' objects? -- Also, I saw that 2.91 (beta) has something called 'exact solver', could someone post similar code to use the "exact solver"?

$\endgroup$
3
  • $\begingroup$ Hi and welcome! I think that's well explained in the linked answer. If you run into other issues, just let us know. $\endgroup$ – brockmann Oct 6 '20 at 7:02
  • $\begingroup$ Tried the referenced, but after fixing "'apply_as' unrecognized" issue, I see that the boolean is still not applied. given that 2.90.1 is so different, can someone post a complete python Boolean (CSG) example where the model only has the 'finished' objects? - Also, I saw that 2.91 (beta) has something called 'exact solver', could someone post the code to use the "exact solver"? $\endgroup$ – Toby Baden Nov 1 '20 at 2:10
  • $\begingroup$ Very borderline, have re-opened and answered. Main issue was assigning an active object for the operator. $\endgroup$ – batFINGER Nov 2 '20 at 5:54
2
$\begingroup$

Turn on developer extras / use autocomplete in the console.

  • Please use 4 space indent (not 3).

  • As noted the apply modifier no longer has an "apply_as" property.

  • For the exact solver set the solver attribute to exact, which is the default value anyway. Turn on developer extras in user prefs to get a tooltip displaying this, or use auto-complete in the console.

     >>> C.object.modifiers['o1']
     bpy.data.objects['big_display_hull'].modifiers["o1"]
    
     >>> C.object.modifiers['o1'].solver
     'EXACT'
    
     >>> C.object.modifiers['o1'].solver = 'FOO'
     Traceback (most recent call last):
       File "<blender_console>", line 1, in <module>
     TypeError: bpy_struct: item.attr = val: enum "FOO" not found in ('FAST', 'EXACT')
    
  • Operators work on the active and selected object(s). Have overridden the operator such that the active object is the prim. Can set the active object with context.view_layer.objects.active = ob (You were only selecting)

  • The apply modifier operator will fail silently, test run active object has no modifiers

     >>> bpy.ops.object.modifier_apply()
     {'CANCELLED'}
    
  • Remove any object with bpy.data.objects.remove(ob) This has been well documented and answered here

Changes for 2.91 using exact solver. There is no need to set as it is the default when modifier added

def csg(prim, cut, new_name, objects, operation):
   o_prim = objects[prim]
   o_cut = objects[cut]
   cut_work = o_prim.modifiers.new(type="BOOLEAN", name=new_name)
   cut_work.object = o_cut
   cut_work.operation = 'DIFFERENCE'
   o_cut.hide_set(True)                 # Hidden--But will still exist in an STL export.
   # --  --  --  --  --  --  --  --  --  --  --  --  --
   # What calls do I add here to "apply" the "difference" and delete the o_cut object?
   #cut_work.solver = 'EXACT' # it's the default anyhow.
   bpy.ops.object.modifier_apply(
      {"object" : o_prim},
      modifier=cut_work.name,
      )
   # --  -

Result of running with changes above.

enter image description here High orange area is z-fighting between "big display hull" and "attach hulls"

$\endgroup$
0
1
$\begingroup$

Got it!

The main goal is now solved: an STL file that looks the same as in Blender. The corrected code is below.

  • The Boolean DIFFERENCE modifier runs successfully--Apparently it can "quietly" fail. <Any insight on getting more info on this?>
  • Once an object is used to cut away "material", the code deletes it successfully now.
  • Code now uses the implication that a modifier is created "as a child" of the currently active object
import sys
import bpy

D = bpy.data
C = bpy.context
from mathutils import *
import math

bpy.app.debug = True

########################################################################
# Stitch together vertices to create a face in the arc
def face(column, row, columns, rows):
    v3 = ((column + 1) * rows) + row
    v3 = ((v3 // 4) * 4) + (v3 + 1) % 4
    v4 = (column * rows + 1) + row
    v4a = ((v4 // 4) * 4) + (v4) % 4
    if v4a == v3:
        v4 = v4a - 4
    return (column * rows + row, (column + 1) * rows + row, v3, v4)


########################################################################
# Create an arc, start and eng angle, a scale to convert units, inner/outer size, height and the number of segments
def arc_segment(nme, start_ang, end_ang, scale, outer_size, inner_size, height, segs):
    # Assuming arguments are in centimeters and in degrees, adjust o meters and radians.
    # Change from degrees to radians
    start_ang = (math.tau / 360.0) * start_ang
    end_ang = (math.tau / 360.0) * end_ang
    if start_ang > end_ang:
        start_ang, end_ang = end_ang, start_ang

    # Change from meters (unit of measure in Blender) to centimeters
    outer_size = scale * outer_size
    inner_size = scale * inner_size

    # We're creating the arc, beginning with the newmericly smaller angle.
    if outer_size < inner_size:
        outer_size, inner_size = inner_size, outer_size

    # Arc will be created withe center at 0, 1/2 infront and 1/2 behing X/Y plane
    height = scale * height
    # Use z for "height", center is 0, so 1/2 height away (plus), and 1/2 height close (minus)
    p_height = height * 0.5
    m_height = height * -0.5

    # Think of the arc as a tube. This loop connects the faces to create the tube.
    verts = []
    for seg in range(0, segs + 1):
        verts.append(
            (
                math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,
                math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,
                p_height,
            )
        )
        verts.append(
            (
                math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,
                math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * inner_size,
                m_height,
            )
        )
        verts.append(
            (
                math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,
                math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,
                m_height,
            )
        )
        verts.append(
            (
                math.cos(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,
                math.sin(start_ang + (end_ang - start_ang) * (seg / segs)) * outer_size,
                p_height,
            )
        )

    sides = 4
    args = (segs, sides)
    faces = [face(x, y, segs, sides) for x in range(segs) for y in range(sides)]

    # Now cap the ends of the tube
    faces.append((0, 1, 2, 3))
    vbase = segs * sides
    faces.append((vbase + 3, vbase + 2, vbase + 1, vbase))

    # Create object, link it into the scene, make it 'active'.
    mesh_data = bpy.data.meshes.new(nme)
    mesh_data.from_pydata(verts, [], faces)
    mesh_data.update()  # (calc_edges=True) not needed here

    cube_object = bpy.data.objects.new(nme, mesh_data)

    scene = bpy.context.scene
    bpy.context.collection.objects.link(cube_object)
    cube_object.select_set(state=True)


########################################################################
# Solids modeling (CSG) take away coliding parts of one shape from another.
# The main corrections (the "answer") are in this method here:
#
def csg(prim, cut, new_name, objects, operation):
    o_prim = objects[prim]
    o_cut = objects[cut]
    bpy.context.view_layer.objects.active = o_prim   #Setting the primary object as active.
    cut_work = o_prim.modifiers.new(type="BOOLEAN", name=new_name)
    cut_work.object = o_cut
    cut_work.operation = "DIFFERENCE"
    o_cut.hide_set(True)  # Hidden--But will still exist in an STL export.
    rs = bpy.ops.object.modifier_apply({"object": cut_work.object}, modifier=cut_work.name)
    print(rs)   # If this quietly fails, this prints {'CANCELLED'}. How can one know what to fix? Maybe logs or something that can be looked at to find out why?


########################################################################
#
objects = bpy.data.objects
set_scale = 2.54 * 2
#
# Create some arcs
arc_segment("big_display_hull", 5.0, 90.0, set_scale, 11.5, 8.0, 4.5, 34)
arc_segment("big_hollow_hull", 6.5, 88.5, set_scale, 11.6, 7.7, 4.1, 34)

csg("big_display_hull", "big_hollow_hull", "o1", objects, "DIFFERENCE")

arc_segment("attach_hulls", 360 + 110, 360 - 110.0, set_scale, 9.0, 8.0, 4.9, 120)
arc_segment(
    "attach_hollow_hulls", 360 + 113, 360 - 113.0, set_scale, 9.2, 7.9, 4.1, 120
)
csg("attach_hulls", "attach_hollow_hulls", "o2", objects, "DIFFERENCE")

arc_segment("small_display_hull", 360 - 10, 360 - 65, set_scale, 11.5, 8.0, 4.5, 22)
arc_segment("small_hollow_hull", 360 - 11.5, 360 - 63.5, set_scale, 11.6, 7.7, 4.1, 22)
csg("small_display_hull", "small_hollow_hull", "o3", objects, "DIFFERENCE")

```
$\endgroup$
1
  • $\begingroup$ Thanks to all who helped me connect the dots :-) $\endgroup$ – Toby Baden Nov 4 '20 at 22:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.