heitzmann/gdstk

gdstk Boolean too slow but works fast in Klayout??

GaN-T opened this issue ยท 14 comments

I am trying to cut out several holes in a rectangle. When I do this in Klayout using "Subtract others from first", the result is computed almost instantly. However, when I try to do it using gdstk, it is very slow! Am i doing something wrong?

GDSTK code:

'box' is the rectangle i want to cut the holes in
'pols' is a list of 2713 polygons i want to cut out of 'box'

res = box
for i in range(len(pols)):
    if res!= []:
        res = gdstk.boolean(res, pols[i], 'not')[0]

The Klayout GUI boolean operation works very fast somehow as I am selecting all the polygons to be cut at once with multiple selection. I suspect this is different from the sequential for loop update for each cut operation im doing using gdstk. Is there a smarter + faster way to do this in gdstk? Many thanks for your help!

Yes: res = gdstk.boolean(box, pols, 'not')

this is what i tried initially, but the result is an empty list

Then there is probably something wrong with your inputs. If you can share the example I can take a look.

This is not a small example. I'll have to take a look when I have more time.

i can try to make a more minimal working example, give me a moment

EDIT: The line of code causing the problem has been commented out.

I removed my earlier code. The code below is the relevant bit. I get the structures I want to perform the boolean "not" on and saved them in the cell "clip" to test in Klayout. This works as expected. However, in the last part where I do the inversion in gdstk, it produces an empty list. I was also having the same problem when creating the 'clip' cell and the only way to get it to work I found was to iterate through the list of polygons...

import gdstk

# parameters for the design

dc =5.8
eps = 1/20
cW=2000
d = 70
cL = 10000
g = dc/(1.4*(eps)**0.48) 

# create gds library and cells

lib = gdstk.Library()
pD = lib.new_cell("post")
pRow = lib.new_cell("pArray")
pRef = lib.new_cell("pRef")
clip = lib.new_cell("clip")
inv = lib.new_cell("invDld")

# construct geometries 

pD.add(gdstk.ellipse((0,0), d/2, tolerance=0.01))   # unit cell for array
box = gdstk.rectangle((-cL/2,-cW/2),(cL/2,cW/2))    # box to cut from

ny = int(cL / (g + d)) + 1                  # ncols
nx = int(cW / (g + d)) + 1                #  nrows
spc = d + g                                      # array spacing
extras = int(ny*eps) + 1

# create array 

for i in range(ny):
    arr = gdstk.Reference(pD, origin = (i*spc,i*eps*spc - extras*spc), columns = 1, rows= nx + extras, spacing = (0,spc))
    pRow.add(arr) 
rowRef = gdstk.Reference(pRow, origin = (-cL/2,-cW/2), columns = 1, rows= 1, spacing = (0,0))
pRef.add(rowRef)

 # mask to trim off edges

mask = gdstk.boolean(gdstk.offset(box, (extras+2)*spc)[0], box, 'not')[0]

# remove posts that fall outside box

pols = pRef.flatten().polygons
for i in range(len(pols)):
    res = gdstk.boolean(pols[i], mask, 'not')
    if res != []:
        if res[0].area() > 25:
            clip.add(res[0])
## clip.add(box)                                                               # this line was causing problems

lib.remove(pRef, pRow, pD)                                              # remove unwanted cells

# flatten structures to perform boolean cut

clipFlat = clip.flatten()
pols = clipFlat.polygons

# perform inversion! Now it works after the problematic line is removed

res = gdstk.boolean(box,pols, "not")[0]
inv.add(res)

lib.write_gds("minimalExample.gds")

@heitzmann have you had the chance to try to run the minimal example? Do you get the same problem as me?

I'm a bit confused here. You add the box to the clip and then subtract it from the box in your boolean, don't you? That means you end up with no polygons at the end because you've cut the box from itself....

@philstopford Thank you so much. That was exactly what I was doing wrong! It works now, and its fast as expected.

@philstopford I also tried to modify this block of the code (which works but is slow due to the for loop):

pols = pRef.flatten().polygons
for i in range(len(pols)):
    res = gdstk.boolean(pols[i], mask, 'not')
    if res != []:
        if res[0].area() > 25:
            clip.add(res[0])

to

pols = pRef.polygons
res = gdstk.boolean(pols, mask, 'not')
clip.add(res[0])

but this returns an empty list too. It only works as I expect when I first flatten the cell. Any idea why?

@GaN-T Any chance you could post your modified simple test case? My brain is tired and being able to just run/poke something immediately would help.

@philstopford I edited and updated my previously provided simplified example to get rid of the problematic line of code. You should be able to run it. My question is rather about the underlying principle of boolean "not" with large arrays of polygons. I first need to flatten the cell in order to get the result I want. It doesn't work without this, when I use the reference/ cell containing the polygons directly. You can see this in my example where I do the boolean "not" on two occasions. In both cases, it is preceded by the flatten operation. Without this, I get an empty list, which is not what i expected...

I'm not exactly sure what the output should be here. Is it a plane full of holes or a trimmed set of holes? If it's the latter, I get that quite nicely. The long delay I see is in writing the GDSII file, not the boolean operation itself. OASIS writes out much more quickly.

the output in the cell called "clip" is the plane of trimmed polygons. The output in the cell called "inv" is the plate with holes cut using the polygons from the "clip". I see your point. The slow step is the file writing.. My last question was just about why its necessary to flatten cells before doing the "not" in each case. But its not a big problem in the end.. I will close the issue. Thanks very much for your help!