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
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()
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), )
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()
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()
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
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()
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()
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()
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))
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()
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
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
def set_extend(self, x): """ Configures the computation margin """ self._extend = int_floor(x / self._dbu + 0.5) self._update_basic_regions()
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
def delta(self, x): self._delta = int_floor(x / self._dbu + 0.5) info('XSG._delta set to {}'.format(self._delta))
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)