Home Forums Nazca bbox bug

Tagged: ,

Viewing 6 posts - 1 through 6 (of 6 total)
  • Author
  • #6127

    I believe there is a bug in the calculation of the bounding box. Curves that start with a non-90 angle will result in a bbox larger than the element. The minimal way I’ve found to recreate this bug is below


    # third-party imports
    import nazca as nd
    from nazca import cp, Cell
    from nazca.interconnects import Interconnect as IC
    #  Interconnect settings
    XS = 'xs1'
    nd.add_layer2xsection(xsection=XS, layer=1)
    IC_1 = IC(width=1, radius=80, xs=XS)
    # main
    with Cell('main', autobbox=True) as main:
    nd.export_gds(topcells=main, filename='bbox_test.gds')

    Dear Joe,

    Thank you for the example. This may look surprising, but it is expected behaviour.

    A bbox of a cell is a rectangular shape in the native orientation of the block. Let’s say you draw a thin diagonal line from (0, 0) to (1, 1), then the bbox is bottom-left (0, 0) to top right (1, 1), but the upper left and bottom right corner of the bbox (square) are empty. If you rotate the bbox by -45 degree, the diagonal is a vertical line and the bbox a diamond shape. If you place this in another cell it will see the diamond left and right tips “pushing” the parent bbox.

    The bend example is similar; Interconnects in Nazca by default (by choice) are not instantiated when exported to gds (they are nazca internally cells, actually a stack of cells, each with a bbox), so you do not see that diamond bbox anymore. But you do see it pushing the bbox of cell main. The bbox is visualized in the example below.

    Nazca does also have a convex hull option as boundary. Such hull will always make the structure “touch” the parent bbox. But also that touching is optional, because you can still ask a parent cell to either use the hull (if present) or the rectangular bbox of the instance to calculate the bounding box. Calculating a hull costs (much) more time, so it is not used in mask elements or interconnects.

    import nazca as nd
    from nazca import cp, Cell
    from nazca.interconnects import Interconnect as IC
    #  Interconnect settings
    XS = 'xs1'
    nd.add_layer2xsection(xsection=XS, layer=1)
    IC_1 = IC(width=1, radius=80, xs=XS)
    with Cell('bend', autobbox=True) as bend:
    # main
    with Cell('main', autobbox=True) as main:
        bend.put(0, 0, 45)
    nd.export_gds(topcells=main, filename='bbox_test.gds')



    I understand a little better.  I was hoping to use the bbox (or hull) to extract tight bounding box dimensions of a cell containing many elements, ignoring such rotated bounding boxes of child cells. As you say, the hull of basic elements intentionally matches its bounding box, leading to a similar issue in this case. I thought to try something like “instantiate=False” in the child cell, and/or “hull=True, hull_based_bbox=True”, but no luck. Do you see a way to do this?




    Hey Joe,

    There are some pro’s and con’s of a hull vs. bbox. A convex hull is like a rubber band around the shapes. It can not be assigned a top-right etc corner like a rectangular bbox. The bbox is not supposed to be the tightest shape under rotation, it serves another purpose, like placement convenience, vizualization or finding overlaps with other boxes efficiently. Hulls take an order longer to calculate, so default they are off. Note that for your topcell of interest you do want a width and a height, according to a rectangular bbox, if I understand correctly, which depends on the orientation of that topcell.

    A way to get the tight bbox of your top cell is to flatten it and get the bbox for that flattened cell. This can be done with the cell_iter(). I also switched the hull on for the flattened cell. The hull has a lot more points than a bbox in this case, due to the outside of the bend being part of the hull.

    import nazca as nd
    from nazca.interconnects import Interconnect as IC
    #  Interconnect settings
    XS = 'xs1'
    nd.add_layer2xsection(xsection=XS, layer=1)
    IC_1 = IC(width=1, radius=80, xs=XS)
    # main
    with nd.Cell('main') as main:
        IC_1.bend(angle=45).put(0, 0, 45)
    nd.cfg.use_hull = True # switch on hull calculations.
    lyo = nd.layout()
    for P in nd.cell_iter(main, flat=True):
        if P.cell_start:
            if P.level == 0:
        if P.cell_close and P.level == 0:
    # output
    (-0.35355339059327373, -0.3535533905932738, 23.931457505076203, 56.56854249492379)
    [[-3.53553391e-01  3.53553391e-01]
     [ 3.53553391e-01 -3.53553391e-01]
     [ 7.07106781e-01 -3.88578059e-16]
     [ 1.19628836e+00  5.01371256e-01]
     [ 1.75083025e+00  1.07841601e+00]
     [ 2.29960800e+00  1.66094527e+00]
     [ 2.84256736e+00  2.24890147e+00]
     [ 3.37965467e+00  2.84222649e+00]
     [ 3.91081685e+00  3.44086168e+00]
     [ 4.43600139e+00  4.04474789e+00]
     [ 4.95515640e+00  4.65382541e+00]
     [ 5.46823055e+00  5.26803407e+00]
     [ 5.97517314e+00  5.88731314e+00]
     [ 6.47593406e+00  6.51160141e+00]
     [ 6.97046382e+00  7.14083720e+00]
     [ 7.45871354e+00  7.77495830e+00]
     [ 7.94063497e+00  8.41390203e+00]
     [ 8.41618046e+00  9.05760525e+00]
     [ 8.88530302e+00  9.70600434e+00]
     [ 9.34795629e+00  1.03590352e+01]
     [ 9.80409452e+00  1.10166333e+01]
     [ 1.02536727e+01  1.16787337e+01]
     [ 1.06966462e+01  1.23452708e+01]
     [ 1.11329715e+01  1.30161789e+01]
     [ 1.15626053e+01  1.36913915e+01]
     [ 1.19855052e+01  1.43708421e+01]
     [ 1.24016294e+01  1.50544633e+01]
     [ 1.28109367e+01  1.57421877e+01]
     [ 1.32133867e+01  1.64339473e+01]
     [ 1.36089397e+01  1.71296737e+01]
     [ 1.39975565e+01  1.78292981e+01]
     [ 1.43791987e+01  1.85327515e+01]
     [ 1.47538286e+01  1.92399642e+01]
     [ 1.51214092e+01  1.99508663e+01]
     [ 1.54819041e+01  2.06653877e+01]
     [ 1.58352777e+01  2.13834576e+01]
     [ 1.61814951e+01  2.21050052e+01]
     [ 1.65205221e+01  2.28299590e+01]
     [ 1.68523251e+01  2.35582475e+01]
     [ 1.71768714e+01  2.42897986e+01]
     [ 1.74941289e+01  2.50245401e+01]
     [ 1.78040662e+01  2.57623993e+01]
     [ 1.81066526e+01  2.65033033e+01]
     [ 1.84018584e+01  2.72471789e+01]
     [ 1.86896543e+01  2.79939526e+01]
     [ 1.89700119e+01  2.87435505e+01]
     [ 1.92429034e+01  2.94958985e+01]
     [ 1.95083019e+01  3.02509224e+01]
     [ 1.97661811e+01  3.10085474e+01]
     [ 2.00165156e+01  3.17686987e+01]
     [ 2.02592806e+01  3.25313011e+01]
     [ 2.04944522e+01  3.32962794e+01]
     [ 2.07220070e+01  3.40635578e+01]
     [ 2.09419226e+01  3.48330605e+01]
     [ 2.11541773e+01  3.56047115e+01]
     [ 2.13587500e+01  3.63784346e+01]
     [ 2.15556206e+01  3.71541532e+01]
     [ 2.17447696e+01  3.79317906e+01]
     [ 2.19261783e+01  3.87112701e+01]
     [ 2.20998288e+01  3.94925145e+01]
     [ 2.22657039e+01  4.02754467e+01]
     [ 2.24237872e+01  4.10599893e+01]
     [ 2.25740631e+01  4.18460647e+01]
     [ 2.27165167e+01  4.26335952e+01]
     [ 2.28511340e+01  4.34225030e+01]
     [ 2.29779017e+01  4.42127101e+01]
     [ 2.30968072e+01  4.50041385e+01]
     [ 2.32078387e+01  4.57967098e+01]
     [ 2.33109853e+01  4.65903459e+01]
     [ 2.34062369e+01  4.73849681e+01]
     [ 2.34935839e+01  4.81804980e+01]
     [ 2.35730178e+01  4.89768570e+01]
     [ 2.36445307e+01  4.97739663e+01]
     [ 2.37081155e+01  5.05717472e+01]
     [ 2.37637660e+01  5.13701208e+01]
     [ 2.38114766e+01  5.21690081e+01]
     [ 2.38512427e+01  5.29683304e+01]
     [ 2.38830602e+01  5.37680084e+01]
     [ 2.39069262e+01  5.45679633e+01]
     [ 2.39228381e+01  5.53681159e+01]
     [ 2.39314575e+01  5.60685425e+01]
     [ 2.39314575e+01  5.65685425e+01]
     [ 2.29314575e+01  5.65685425e+01]]



    another way is:

    import nazca as nd
    from nazca.interconnects import Interconnect as IC
    #  Interconnect settings
    XS = 'xs1'
    nd.add_layer2xsection(xsection=XS, layer=1)
    IC_1 = IC(width=1, radius=80, xs=XS)
    nd.cfg.use_hull = True  # switch hull calculation on for all cells
    nd.cfg.hull_based_bbox = True  # use the hull for the next level hull/bbox, not the bbox, for all cells.
    # main
    with nd.Cell('main', autobbox=False) as main:
        IC_1.bend(angle=45).put(0, 0, 45)



    Thanks Ronald. I found your second method to work the best for me. I wrote a helper function in a separate module, then used the with block in my function that returns a cell. In this way, the expensive hull calculation is contained.

    def temp_attributes(obj, **kwargs):
        prev_vals = {k: getattr(obj, k) for k in kwargs}
        for k, v in kwargs.items():
            setattr(obj, k, v)
            for k, v in prev_vals.items():
                setattr(obj, k, v)
    with temp_attributes(nd.cfg, use_hull=True, hull_based_bbox=True):
        with Cell('main') as main:
Viewing 6 posts - 1 through 6 (of 6 total)
  • You must be logged in to reply to this topic.