1
$\begingroup$

I have a script which takes an SVG path, converts it to a mesh, joins all the edges together, extrudes it, and adds the missing faces. It then reads in a CNC drill file and drills holes in the mesh using a cylinder and the boolean DIFFERENCE operator.

As it stands, it works. However it starts out nice and quick, but rapidly each boolean operation starts to take longer and longer, ending up as a couple of seconds per operation.

The boolean section of the script I found somewhere online (I can't find where now), so I assume it's the right way to do it. But it may not be. So I'd like some pointers or ideas on how it can be sped up.

Here's the core of the script where the boolean work is done:

bpy.ops.mesh.primitive_cylinder_add()
bit = bpy.context.object
bit.name="Drill Bit"
bit.dimensions=[4,4,10]

def subtract(target, opObj):
   '''subtract opObj from the target'''

   # Deselect All
   bpy.ops.object.select_all(action='DESELECT')

   # Select the new object.
   target.select = True
   bpy.context.scene.objects.active = target

   # Add a modifier
   bpy.ops.object.modifier_add(type='BOOLEAN')

   mod = target.modifiers
   mod[0].name = "SubEmUp"
   mod[0].object = opObj
   mod[0].operation = 'DIFFERENCE'

   # Apply modifier - I assume this is the bit that is taking a long
   # time but I cannot be sure yet.
   bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod[0].name)


for line in content:
    if line == '%':
        inHeader = False
        continue
    if inHeader:
        # T61C0.300
        if line.startswith('T'):
            parts = line.split('C')
            drills[parts[0]] = float(parts[1])
    else:
        # T61
        if line.startswith('T'):
            drillWidth = drills[line]
            print("Selecting drill width " + str(drillWidth) + "mm")
            bit.dimensions=[drillWidth, drillWidth, 50]
            continue
        # X009050Y046150
        if line.startswith('X'):
            coords = line[1:].split('Y')
            x = float(coords[0])
            y = float(coords[1])
            x = x / 1000
            y = y / 1000
            print("Drilling at " + str(x) + "," + str(y))
            bit.location=[x, y, 0]
            subtract(pcb, bit)
            # I added this to see why the script was taking so long.
            bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

bpy.ops.object.select_all(action='DESELECT')
bit.select = True
bpy.ops.object.delete()

pcb is the mesh generated earlier.

Is there some way I can "batch up" all the boolean modifiers and perform them all in one operation? Would it be better to make one "drill bit" per hole and locate them all, then join them into one big drill object and subtract that from the main mesh? (would that even work?)

$\endgroup$
  • 1
    $\begingroup$ Try replacing the operators with low level API calls. Can replace modifier add and apply ops like shown here blender.stackexchange.com/a/48212/15543 $\endgroup$ – batFINGER Jan 5 '17 at 3:32
  • $\begingroup$ @batFINGER Interesting idea, but alas not - I am still seeing the same slowdown using that technique (though I think I will stick with doing it that way since it's nicer, unless something better and faster comes along). I am wondering if it is just taking longer and longer to regenerate the mesh each time as the mesh gets more and more complex - although it's not that complex on the scale of things... $\endgroup$ – Majenko Jan 5 '17 at 12:12
  • $\begingroup$ Possibly, could try stacking up the modifiers, (use bit.copy() as an object) and apply later. Very minor minor speedup print("blah", x, y) will be quicker than print("Drilling at " + str(x) + "," + str(y)) Best Practices $\endgroup$ – batFINGER Jan 5 '17 at 12:39
  • $\begingroup$ I don't actually need the prints at all. They were just while initially developing the file parser routine. Taking them out makes no difference. It is definitely the (newly added from where you linked) target.data = target.to_mesh(bpy.context.scene, True, 'PREVIEW') that is the slowness. Without that it is really fast but, of course, doesn't do anything. I will have a go at stacking the modifiers up. $\endgroup$ – Majenko Jan 5 '17 at 13:13
  • $\begingroup$ The fastest I have got so far is to 1) add new cylinders for all the drills (copy messed up with a later bit), 2) join all the drills into one single drill object, 3) subtract the drills object from the PCB. 1) and 2) are instantaneous, but 3) takes a while to execute. Still, it's faster than before. $\endgroup$ – Majenko Jan 5 '17 at 13:47
4
$\begingroup$

After some experimenting I found two things that can improve performance.

First is the mesh you are cutting into, cutting holes into a large face is slower than if it was subdivided into many faces, this would be making it easier for the boolean operation to find connecting edges.

For example the subdivided object on the right will give a faster boolean result than the single faced object on the left, by faster I mean it can cut the time almost in half.

compare meshes

In my testing I was starting with a cube so I could do a simple subdivide. With your extruded shape you may want to add loop cuts across your larger faces so you don't add unneeded cuts to smaller bevel edges etc.

For the second speedup, the really odd thing is that your approach of using one drill object that you move and reuse for each hole seems to be the slow way, I don't get why but creating and deleting a new drill object for each hole is faster.

Also using target.modifiers.new is faster than bpy.ops.object.modifier_add.

def drillIt(target, x, y, d):
    '''Drill a hole at x,y with d diameter - using a boolean modifier'''
    # this should get similar result as subtract()
    # for some reason this runs faster

    bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=d/2, depth=50,
                location=(x, y, 0))
    drill = bpy.context.object

    # Add a modifier
    mod = target.modifiers.new('Boolean',type='BOOLEAN')
    mod.object = drill
    mod.operation = 'DIFFERENCE'
    target.select = True
    bpy.context.scene.objects.active = target
    bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod.name)
    bpy.context.scene.objects.unlink(drill)

Remove the use of bit and instead of -

bit.location=[x, y, 0]
subtract(pcb, bit)

use -

drillIt(pcb, x, y, drillWidth)
$\endgroup$
  • $\begingroup$ I am doing similar at the moment. Flood with cylinders, then join them into a single object, and finally do one single boolean op. I will play with subdivision to see how that improves it. $\endgroup$ – Majenko Jan 5 '17 at 18:43
  • $\begingroup$ Hmmm... I'm not sure how I can do some subdivision of this mesh. I can subdivide the sides quite happily, and I can add more vertices around the loop, but I can't subdivide the top and bottom faces. I have uploaded the pre-extruded curve (2D mesh loop) here if you want to have a play. Maybe I'll go as far as to open another question for this aspect of it... $\endgroup$ – Majenko Jan 5 '17 at 20:55
  • $\begingroup$ First thought is - create a grid, delete the faces and knife project $\endgroup$ – sambler Jan 6 '17 at 19:25
  • $\begingroup$ I can run that sequence manually, but trying to script it (even typing the knife_project() function in the console) whinges about "expected a view3d region & editmesh" even though I am, at the time, in exactly the same state as when I press the "Knife Project" button to do it manually. $\endgroup$ – Majenko Jan 6 '17 at 20:01

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.