示例#1
0
    def _setup(self, p1, p2):
        """
        Parameters
        ----------
        p1 : Point
            first point of the ruler
        p2 : Point
            second point of the ruler

        """
        # locate the layout
        app = Application.instance()
        view = app.main_window().current_view()  # LayoutView
        if not view:
            MessageBox.critical(
                "Error",
                "No view open for creating the cross-"
                "section from",
                MessageBox.b_ok(),
            )
            return False

        cv = view.cellview(view.active_cellview_index())  # CellView
        if not cv.is_valid():
            MessageBox.critical("Error", "The selected layout is not valid",
                                MessageBox.b_ok())
            return False

        self._cv = cv  # CellView
        self._layout = cv.layout()  # Layout
        self._dbu = self._layout.dbu
        self._cell = cv.cell_index  # int

        # get the start and end points in database units and micron
        p1_dbu = Point.from_dpoint(p1 * (1.0 / self._dbu))
        p2_dbu = Point.from_dpoint(p2 * (1.0 / self._dbu))
        self._line_dbu = Edge(p1_dbu, p2_dbu)  # Edge describing the ruler

        # initialize height and depth
        self._extend = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu
        self._delta = 10
        self._height = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu
        self._depth = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu
        self._below = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu

        info('    XSG._dbu is:    {}'.format(self._dbu))
        info('    XSG._extend is: {}'.format(self._extend))
        info('    XSG._delta is:  {}'.format(self._delta))
        info('    XSG._height is: {}'.format(self._height))
        info('    XSG._depth is:  {}'.format(self._depth))
        info('    XSG._below is:  {}'.format(self._below))

        return True
示例#2
0
    def height(self, x):
        """ Configures the height of the processing window

        """
        self._height = int_floor(x / self._dbu + 0.5)
        info('XSG._height set to {}'.format(self._height))
        self._update_basic_regions()
示例#3
0
    def size(self, dx, dy=None):
        """ Resize the layout mask.

        Parameters
        ----------
        dx : float
            size change in x-direction in [um]
        dy : float (optional)
            size change in y-direction in [um]. Equals to dx by default.

        """
        dy = dx if dy is None else dy
        self._polygons = self._ep.size_p2p(
            self._polygons,
            int_floor(dx / self._xs.dbu + 0.5),
            int_floor(dy / self._xs.dbu + 0.5),
        )
示例#4
0
    def set_depth(self, x):
        """Set the depth of the processing window
        or the wafer thickness for backside processing (see below)

        """
        self._depth = int_floor(x / self._dbu + 0.5)
        info('XSG._depth set to {}'.format(self._depth))
        self._update_basic_regions()
示例#5
0
    def set_extend(self, x):
        """ Set the extend of the computation region

        Parameters
        ----------
        x : float
            extend in [um]
        """
        self._extend = int_floor(x / self._dbu + 0.5)
        self._update_basic_regions()
示例#6
0
    def sized(self, dx, dy=None):
        """ Calculate a sized mask.

        Parameters
        ----------
        dx : float
            size change in x-direction in [um]
        dy : float (optional)
            size change in y-direction in [um]. Equals to dx by default.

        Returns
        -------
        ld : LayoutData
        """
        dy = dx if dy is None else dy
        ld = self.upcast(
            self._ep.size_p2p(self._polygons,
                              int_floor(dx / self._xs.dbu + 0.5),
                              int_floor(dy / self._xs.dbu + 0.5)))
        return ld
示例#7
0
    def set_below(self, x):
        """ Configures the lower height of the processing window for backside processing

        Parameters
        ----------
        x : float
            depth below the wafer in [um]

        """
        self._below = int_floor(x / self._dbu + 0.5)
        info('XSG._below set to {}'.format(self._below))
        self._update_basic_regions()
示例#8
0
    def set_depth(self, x):
        """ Configures the depth of the processing window
        or the wafer thickness for backside processing (see `below`)

        Parameters
        ----------
        x : float
            depth of the wafer in [um]
        """
        self._depth = int_floor(x / self._dbu + 0.5)
        info('XSG._depth set to {}'.format(self._depth))
        self._update_basic_regions()
示例#9
0
    def set_height(self, x):
        """ Configures the height of the processing window

        Parameters
        ----------
        x : float
            height in [um]

        """
        self._height = int_floor(x / self._dbu + 0.5)
        info('XSG._height set to {}'.format(self._height))
        self._update_basic_regions()
示例#10
0
 def set_delta(self, x):
     """Configures the accuracy parameter
     """
     self._delta = int_floor(x / self._dbu + 0.5)
     info('XSG._delta set to {}'.format(self._delta))
示例#11
0
    def planarize(self, into=[], downto=[], less=None, to=None, **kwargs):
        """Planarization
        """

        if not into:
            raise ValueError("'planarize' requires an 'into' argument")

        into = make_iterable(into)
        for i in into:
            # should be MaterialData @@@
            if not isinstance(i, MaterialData3D):
                raise TypeError("'planarize' method: 'into' expects "
                                "a material parameter or an array "
                                "of such")

        downto = make_iterable(downto)
        for i in downto:
            # should be MaterialData @@@
            if not isinstance(i, MaterialData3D):
                raise TypeError("'planarize' method: 'downto' expects "
                                "a material parameter or an array "
                                "of such")

        if less is not None:
            less = int_floor(0.5 + float(less) / self.dbu)

        if to is not None:
            to = int_floor(0.5 + float(to) / self.dbu)

        if downto:
            downto_data = []
            for d in downto:
                if len(downto_data) == 0:
                    downto_data = d.data
                else:
                    downto_data = self._lp.boolean_p2p(d.data, downto_data,
                                                       LP.ModeOr)

            # determine upper bound of material
            if downto_data:
                raise NotImplementedError('downto not implemented yet')
                for p in downto_data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])

        elif into and not to:
            raise NotImplementedError('into and not to not implemented yet')
            # determine upper bound of our material
            for i in into:

                for p in i.data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])

        if to:
            less = less or 0
            if self._flipped:
                removed_box = MaterialLayer(
                    LayoutData([
                        Polygon(
                            self._box_dbu.enlarged(
                                Point(self._extend, self._extend)))
                    ], self), -(self.depth_dbu + self.below_dbu),
                    (to + less) + self.depth_dbu + self.below_dbu)

            else:
                removed_box = MaterialLayer(
                    LayoutData([
                        Polygon(
                            self._box_dbu.enlarged(
                                Point(self._extend, self._extend)))
                    ], self), to - less, self.height_dbu - (to - less))

            rem = MaterialData3D([], self, self._delta)
            for i in into:
                rem.add(i.and_([removed_box]))
                i.sub([removed_box])

            self.air().add(rem)
            self.air().close_gaps()
示例#12
0
    def produce_geom(self, method, xy, z, into, through, on, taper, bias, mode,
                     buried):
        """

        method : str
        xy : float
            mask extension, lateral in [um]
        z : float
            vertical material size in [um]
        into : list of MaterialData3D
        through : list of MaterialData3D
        on : list of MaterialData3D
        taper : float
        bias : float
        mode : str
            'round|square|octagon'
        buried : float

        Returns
        -------
        layers : list of MaterialLayer
        """
        info('    method={}, xy={}, z={}, \n'
             '    into={}, through={}, on={}, \n'
             '    taper={}, bias={}, mode={}, buried={})'.format(
                 method, xy, z, into, through, on, taper, bias, mode, buried))

        prebias = bias or 0.0

        if xy < 0.0:
            xy = -xy
            prebias += xy
        '''
        if taper:
            d = z * math.tan(math.pi / 180.0 * taper)
            prebias += d - xy
            xy = d
        '''
        # determine the "into" material by joining the layers of all "into"
        # materials or taking air's layers if required.
        # Finally we get a into_layers : list of MaterialLayer
        if into:
            into_layers = []
            for i in into:
                info('    i = {}'.format(i))
                if len(into_layers) == 0:
                    into_layers = i.data
                else:
                    into_layers = self._lp.boolean_l2l(i.data, into_layers,
                                                       LP.ModeOr)
        else:
            # when deposit or grow is selected, into_layers is self._xs.air()
            into_layers = self._xs.air().data

        info('    into_layers = {}'.format(into_layers))

        # determine the "through" material by joining the layers of all
        # "through" materials
        # Finally we get a thru_layers : list of MaterialLayer
        if through:
            thru_layers = []
            for t in through:
                if len(thru_layers) == 0:
                    thru_layers = t.data
                else:
                    thru_layers = self._lp.boolean_l2l(t.data, thru_layers,
                                                       LP.ModeOr)
            info('    thru_layers = {}'.format(thru_layers))

        # determine the "on" material by joining the data of all "on" materials
        # Finally we get an on_layers : list of MaterialLayer
        if on:
            on_layers = []
            for o in on:
                if len(on_layers) == 0:
                    on_layers = o.data
                else:
                    on_layers = self._lp.boolean_l2l(o.data, on_layers,
                                                     LP.ModeOr)
            info('    on_layers = {}'.format(on_layers))

        offset = self._delta
        layers = self._layers
        info('    Seed material to be grown: {}'.format(self))
        '''
        if abs(buried or 0.0) > 1e-6:
            t = Trans(Point(
                    0, -_int_floor(buried / self._xs.dbu + 0.5)))
            d = [p.transformed(t) for p in d]
        '''

        # in the "into" case determine the interface region between
        # self and into
        if into or through or on:
            # apply an artificial sizing to create an overlap before
            if offset == 0:
                offset = self._xs.delta_dbu / 2
                layers = self._lp.size_l2l(layers, 0, dz=offset)

            if on:
                layers = self._lp.boolean_l2l(layers, on_layers, EP.ModeAnd)
            elif through:
                layers = self._lp.boolean_l2l(layers, thru_layers, EP.ModeAnd)
            else:
                layers = self._lp.boolean_l2l(layers, into_layers, EP.ModeAnd)
        info('    overlap layers = {}'.format(layers))

        pi = int_floor(prebias / self._xs.dbu + 0.5)
        info('    pi = {}'.format(pi))
        if pi < 0:
            layers = self._lp.size_l2l(layers, -pi, dy=-pi, dz=0)
        elif pi > 0:
            raise NotImplementedError('pi > 0 not implemented yet')
            # apply a positive prebias by filtering with a sized box
            dd = []
            for p in d:
                box = p.bbox()
                if box.width > 2 * pi:
                    box = Box(box.left + pi, box.bottom, box.right - pi,
                              box.top)

                    for pp in self._ep.boolean_p2p([Polygon(box)], [p],
                                                   EP.ModeAnd):
                        dd.append(pp)
            d = dd

        xyi = int_floor(xy / self._xs.dbu + 0.5)  # size change in [dbu]
        zi = int_floor(z / self._xs.dbu + 0.5) - offset  # height in [dbu]
        info('    xyi = {}, zi = {}'.format(xyi, zi))

        if taper:
            raise NotImplementedError('taper option is not supported yet')
            # d = self._ep.size_p2p(d, xyi, zi, 0)
        elif xyi <= 0:
            layers = self._lp.size_l2l(layers, 0, dy=0, dz=zi)
            # d = self._ep.size_p2p(d, 0, zi)
        elif mode == 'round':
            # same as square for now
            layers = self._lp.size_l2l(layers, xyi, dy=xyi, dz=zi)

            # raise NotImplementedError('round option is not supported yet')
            # emulate "rounding" of corners by performing soft-edged sizes
            # d = self._ep.size_p2p(d, xyi / 3, zi / 3, 1)
            # d = self._ep.size_p2p(d, xyi / 3, zi / 3, 0)
            # d = self._ep.size_p2p(d, xyi - 2 * (xyi / 3), zi - 2 * (zi / 3), 0)
        elif mode == 'square':
            layers = self._lp.size_l2l(layers, xyi, dy=xyi, dz=zi)
        elif mode == 'octagon':
            raise NotImplementedError('octagon option is not supported yet')
            # d = self._ep.size_p2p(d, xyi, zi, 1)

        if through:
            layers = self._lp.boolean_l2l(layers, thru_layers, LP.ModeANotB)

        info('    layers before and with into:'.format(layers))
        layers = self._lp.boolean_l2l(layers, into_layers, LP.ModeAnd)
        info('    layers after and with into:'.format(layers))

        info('    final layers = {}'.format(layers))
        if None:
            # remove small features
            # Hint: this is done separately in x and y direction since that is
            # more robust against snapping distortions
            layers = self._lp.size_p2p(layers, 0, self._xs.delta_dbu / 2)
            layers = self._lp.size_p2p(layers, 0, -self._xs.delta_dbu)
            layers = self._lp.size_p2p(layers, 0, self._xs.delta_dbu / 2)
            layers = self._lp.size_p2p(layers, self._xs.delta_dbu / 2, 0)
            layers = self._lp.size_p2p(layers, -self._xs.delta_dbu, 0)
            layers = self._lp.size_p2p(layers, self._xs.delta_dbu / 2, 0)

        return layers
示例#13
0
    def _setup(self):

        # locate the layout
        app = Application.instance()
        view = app.main_window().current_view()  # LayoutView
        if not view:
            MessageBox.critical(
                "Error", "No view open for creating the cross "
                "section from", MessageBox.b_ok())
            return False

        # locate the (single) ruler
        rulers = []
        n_rulers = 0
        for a in view.each_annotation():
            # Use only rulers with "plain line" style
            # print(a.style)
            # print(Annotation.StyleLine)
            # if a.style == Annotation.StyleLine:
            rulers.append(a)
            n_rulers += 1

        # if n_rulers == 0 or n_rulers >= 2:
        #     MessageBox.info("No rulers",
        #                         "Number of rulers is not equal to one. "
        #                         "Will be exporting the whole layout",
        #                         pya.MessageBox.b_ok())

        # if n_rulers == 1:
        #     MessageBox.info(
        #             "Box export", "One ruler is present for the cross "
        #             "section line. Will be exporting only shapes in the box",
        #             pya.MessageBox.b_ok())

        cv = view.cellview(view.active_cellview_index())  # CellView
        if not cv.is_valid():
            MessageBox.critical("Error", "The selected layout is not valid",
                                MessageBox.b_ok())
            return False

        self._cv = cv  # CellView
        self._layout = cv.layout()  # Layout
        self._dbu = self._layout.dbu
        self._cell = cv.cell_index  # int

        if n_rulers == 1:
            # get the start and end points in database units and micron
            p1_dbu = Point.from_dpoint(rulers[0].p1 * (1.0 / self._dbu))
            p2_dbu = Point.from_dpoint(rulers[0].p2 * (1.0 / self._dbu))
            self._box_dbu = Box(p1_dbu, p2_dbu)  # box describing the ruler
        else:
            # TODO: choose current cell, not top cell
            top_cell = self._layout.top_cell()
            p1_dbu = (top_cell.bbox().p1 * (1.0 / self._dbu)).dup()
            p1_dbu = top_cell.bbox().p1.dup()
            p2_dbu = (top_cell.bbox().p2 * (1.0 / self._dbu)).dup()
            p2_dbu = top_cell.bbox().p2.dup()
            self._box_dbu = Box(p1_dbu, p2_dbu)  # box describing the top cell

        info('XSG._box_dbu to be used is: {}'.format(self._box_dbu))

        # create a new layout for the output
        cv = app.main_window().create_layout(1)
        cell = cv.layout().add_cell("XSECTION")
        self._target_view = app.main_window().current_view()
        self._target_view.select_cell(cell, 0)
        self._target_layout = cv.layout()
        self._target_layout.dbu = self._dbu
        self._target_cell = cell

        # initialize height and depth
        self._extend = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu
        self._delta = 10
        self._height = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu
        self._depth = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu
        self._below = int_floor(2.0 / self._dbu + 0.5)  # 2 um in dbu

        info('    XSG._dbu is:    {}'.format(self._dbu))
        info('    XSG._extend is: {}'.format(self._extend))
        info('    XSG._delta is:  {}'.format(self._delta))
        info('    XSG._height is: {}'.format(self._height))
        info('    XSG._depth is:  {}'.format(self._depth))
        info('    XSG._below is:  {}'.format(self._below))

        return True
示例#14
0
    def set_extend(self, x):
        """ Configures the computation margin

        """
        self._extend = int_floor(x / self._dbu + 0.5)
        self._update_basic_regions()
示例#15
0
    def produce_geom(self, method, xy, z, into, through, on, taper, bias, mode,
                     buried):
        """

        Parameters
        ----------
        method : str
        xy : float
            extension
        z : float
            height
        into : list of MaterialData
        through : list of MaterialData
        on : list of MaterialData
        taper : float
        bias : float
        mode : str
            'round|square|octagon'
        buried :

        Returns
        -------
        d : list of Polygon
        """
        info('    method={}, xy={}, z={},'.format(method, xy, z))
        info('    into={}, through={}, on={},'.format(into, through, on))
        info('    taper={}, bias={}, mode={}, buried={})'.format(
            taper, bias, mode, buried))

        prebias = bias or 0.0

        if xy < 0.0:  # if size to be reduced,
            xy = -xy  #
            prebias += xy  # positive prebias

        if taper:
            d = z * math.tan(math.pi / 180.0 * taper)
            prebias += d - xy
            xy = d

        # determine the "into" material by joining the data of all "into" specs
        # or taking "air" if required.
        # into_data is a list of polygons from all `into` MaterialData
        # Finally we get a into_data, which is a list of Polygons
        if into:
            into_data = []
            for i in into:
                if len(into_data) == 0:
                    into_data = i.data
                else:
                    into_data = self._ep.boolean_p2p(i.data, into_data,
                                                     EP.ModeOr)
        else:
            # when deposit or grow is selected, into_data is self.air()
            into_data = self._xs.air().data

        info('    into_data = {}'.format(into_data))

        # determine the "through" material by joining the data of all
        # "through" specs
        # through_data is a list of polygons from all `through` MaterialData
        # Finally we get a through_data, which is a list of Polygons
        if through:
            through_data = []
            for i in through:
                if len(through_data) == 0:
                    through_data = i.data
                else:
                    through_data = self._ep.boolean_p2p(
                        i.data, through_data, EP.ModeOr)
            info('    through_data = {}'.format(through_data))

        # determine the "on" material by joining the data of all "on" specs
        # on_data is a list of polygons from all `on` MaterialData
        # Finally we get an on_data, which is a list of Polygons
        if on:
            on_data = []
            for i in on:
                if len(on_data) == 0:
                    on_data = i.data
                else:
                    on_data = self._ep.boolean_p2p(i.data, on_data, EP.ModeOr)
            info('    on_data = {}'.format(on_data))

        pi = int_floor(prebias / self._xs.dbu + 0.5)
        xyi = int_floor(xy / self._xs.dbu + 0.5)
        zi = int_floor(z / self._xs.dbu + 0.5)

        # calculate all edges without prebias and check if prebias
        # would remove edges if so reduce it
        mp = self._ep.size_p2p(self._mask_polygons, 0, 0, 2)

        for p in mp:
            box = p.bbox()
            if box.width() <= 2 * pi:
                pi = int_floor(box.width() / 2.0) - 1
                xyi = pi

        mp = self._ep.size_p2p(self._mask_polygons, -pi, 0, 2)
        air_masked = self._ep.boolean_p2p(self._air_polygons, mp, EP.ModeAnd)
        me = (Edges(air_masked) if air_masked else Edges()) - \
             (Edges(mp) if mp else Edges())
        info('me after creation: {}'.format(me))

        # in the "into" case determine the interface region between
        # self and into
        if into or through or on:
            if on:
                data = on_data
            elif through:
                data = through_data
            else:
                data = into_data

            info("data = {}".format(data))
            me = (me & Edges(data)) if data else list()

            # if len(data) == 0:
            #     me = []
            # else:
            #     me += Edges(data)
        info('type(me): {}'.format(type(me)))  # list of Edge
        info('me before operation: {}'.format(me))

        d = Region()

        if taper and xyi > 0:
            info('    case taper and xyi > 0')
            kernel_pts = list()
            kernel_pts.append(Point(-xyi, 0))
            kernel_pts.append(Point(0, zi))
            kernel_pts.append(Point(xyi, 0))
            kernel_pts.append(Point(0, -zi))
            kp = Polygon(kernel_pts)
            for e in me:
                d.insert(kp.minkowsky_sum(e, False))

        elif xyi <= 0:
            info('    case xyi <= 0')
            # TODO: there is no way to do that with a Minkowsky sum currently
            # since polygons cannot be lines except through dirty tricks
            dz = Point(0, zi)
            for e in me:
                d.insert(Polygon([e.p1 - dz, e.p2 - dz, e.p2 + dz, e.p1 + dz]))
        elif mode in ('round', 'octagon'):
            info('    case round / octagon')
            # approximate round corners by 64 points for "round" and
            # 8 for "octagon"
            n = 64 if mode == 'round' else 8
            da = 2.0 * math.pi / n
            rf = 1.0 / math.cos(da * 0.5)

            info("    n = {}, da = {}, rf = {}".format(n, da, rf))
            kernel_pts = list()
            for i in range(n):
                kernel_pts.append(
                    Point.from_dpoint(
                        DPoint(xyi * rf * math.cos(da * (i + 0.5)),
                               zi * rf * math.sin(da * (i + 0.5)))))
            info('    n kernel_pts: {}'.format(len(kernel_pts)))
            info('    kernel_pts: {}'.format(kernel_pts))

            kp = Polygon(kernel_pts)
            for n, e in enumerate(me):
                d.insert(kp.minkowsky_sum(e, False))
                if n > 0 and n % 10 == 0:
                    d.merge()

        elif mode == 'square':
            kernel_pts = list()
            kernel_pts.append(Point(-xyi, -zi))
            kernel_pts.append(Point(-xyi, zi))
            kernel_pts.append(Point(xyi, zi))
            kernel_pts.append(Point(xyi, -zi))
            kp = SimplePolygon()
            kp.set_points(kernel_pts, True)  # "raw" - don't optimize away
            for e in me:
                d.insert(kp.minkowsky_sum(e, False))

        d.merge()
        info('d after merge: {}'.format(d))

        if abs(buried or 0.0) > 1e-6:
            t = Trans(Point(0, -int_floor(buried / self._xs.dbu + 0.5)))
            d.transform(t)
        if through:
            d -= Region(through_data)
        d &= Region(into_data)

        poly = [p for p in d]
        return poly
示例#16
0
 def delta(self, x):
     self._delta = int_floor(x / self._dbu + 0.5)
     info('XSG._delta set to {}'.format(self._delta))
示例#17
0
    def planarize(self, *args, **kwargs):
        """ Planarization
        """
        downto = None
        less = None
        to = None
        into = []

        for k, v in kwargs.items():
            if k == 'downto':
                downto = make_iterable(v)
                for i in downto:
                    if not isinstance(i, MaterialData):
                        raise TypeError("'planarize' method: 'downto' expects "
                                        "a material parameter or an array "
                                        "of such")

            elif k == 'into':
                into = make_iterable(v)
                for i in into:
                    if not isinstance(i, MaterialData):
                        raise TypeError("'planarize' method: 'into' expects "
                                        "a material parameter or an array "
                                        "of such")
            elif k == 'less':
                less = int_floor(0.5 + float(v) / self.dbu)
            elif k == 'to':
                to = int_floor(0.5 + float(v) / self.dbu)

        if not into:
            raise ValueError("'planarize' requires an 'into' argument")

        info('   downto = {}'.format(downto))
        info('   less = {}'.format(less))
        info('   to = {}'.format(to))
        info('   into = {}'.format(into))

        if downto:
            downto_data = None
            if len(downto) == 1:
                downto_data = downto[0].data
            else:
                for i in downto:
                    if len(downto_data) == 0:
                        downto_data = i.data
                    else:
                        downto_data = self._ep.boolean_p2p(
                            i.data, downto_data, EP.ModeOr)

            # determine upper bound of material
            if downto_data:
                for p in downto_data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])
                info('    to = {}'.format(to))

        elif into and not to:

            # determine upper bound of our material
            for i in into:
                for p in i.data:
                    yt = p.bbox().top
                    yb = p.bbox().bottom
                    to = to or yt
                    if not self._flipped:
                        to = max([to, yt, yb])
                    else:
                        to = min([to, yt, yb])

        if to is not None:
            info('    to is true')
            less = less or 0
            if self._flipped:
                removed_box = Box(
                    -self._extend,
                    -self.depth_dbu - self.below_dbu,
                    self._line_dbu.length() + self._extend,
                    to + less,
                )
            else:
                removed_box = Box(
                    -self._extend,
                    to - less,
                    self._line_dbu.length() + self._extend,
                    self.height_dbu,
                )

            rem = LayoutData([], self)
            for i in into:
                rem.add(i.and_([Polygon(removed_box)]))
                i.sub([Polygon(removed_box)])

            self.air().add(rem)