def output(self, layer_spec=None, layer_data=None, *args): """Outputs a material object to the output layout Can be used for a single material (layer_spec and layer_data pair), or for a list of materials passed through an output_layers dictionary. Parameters ---------- layer_spec : str layer specification layer_data : LayoutData """ # process layer_spec / layer_data pair if not isinstance(layer_data, LayoutData): raise TypeError("'output()': layer_data parameter must be " "a geometry object. {} is given".format( type(layer_data))) if not self._is_target_layout_created: self._create_new_layout() ls = string_to_layer_info(layer_spec) li = self._target_layout.insert_layer(ls) shapes = self._target_layout.cell(self._target_cell).shapes(li) # confine the shapes to the region of interest for polygon in self._ep.boolean_to_polygon([Polygon(self._roi)], layer_data.data, EP.ModeAnd, True, True): shapes.insert(polygon)
def all(self): """ A pseudo-mask, covering the whole wafer Return ------ res : MaterialData3D """ res = self._mask_to_seed_material( LayoutData([Polygon(self._box_dbu)], self)) info(' result: {}'.format(res)) return res
def inverted(self): """ Calculate inversion of the mask. Total region is determined by self._xs.background(). Returns ------- ld : LayoutData """ return self.upcast( self._ep.boolean_p2p(self._polygons, [Polygon(self._xs.background())], EP.ModeXor))
def _update_basic_regions(self): h = self._height # height above the wafer d = self._depth # thickness of the wafer b = self._below # distance below the wafer w = self._line_dbu.length() # length of the ruler e = self._extend # extend to the sides self._area = Box(-e, -(d + b), w + e, h) self._air = MaterialData([Polygon(Box(-e, 0, w + e, h))], self) self._air_below = MaterialData([Polygon(Box(-e, -(d + b), w + e, -d))], self) self._bulk = MaterialData([Polygon(Box(-e, -d, w + e, 0))], self) self._roi = Box(0, -(d + b), w, h) info(' XSG._area: {}'.format(self._area)) info(' XSG._roi: {}'.format(self._roi)) info(' XSG._air: {}'.format(self._air)) info(' XSG._bulk: {}'.format(self._bulk)) info(' XSG._air_below: {}'.format(self._air_below))
def _update_basic_regions(self): h = self._height # height above the wafer d = self._depth # thickness of the wafer b = self._below # distance below the wafer # w = self._line_dbu.length() # length of the ruler e = self._extend # extend to the sides # TODO: add extend to the basic regions self._area = [ MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -(b + d), (b + d + h)) ] # Box(-e, -(d+b), w+e, h) self._roi = [ MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -(b + d), (b + d + h)) ] # Box(0, -(d + b), w, h) self._air = MaterialData3D( [MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), 0, h)], self, 0) self._air_below = MaterialData3D([ MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -(d + b), b) ], self, 0) self._bulk = MaterialData3D( [MaterialLayer(LayoutData([Polygon(self._box_dbu)], self), -d, d)], self, 0) info(' XSG._area: {}'.format(self._area)) info(' XSG._roi: {}'.format(self._roi)) info(' XSG._air: {}'.format(self._air)) info(' XSG._bulk: {}'.format(self._bulk)) info(' XSG._air_below: {}'.format(self._air_below))
def mask(self, layer_data): """ Designates the layout_data object as a litho pattern (mask). This is the starting point for structured grow or etch operations. Parameters ---------- layer_data : LayoutData Returns ------- MaskData """ info(' layer_data = {}'.format(layer_data)) mask = layer_data.and_([Polygon(self._box_dbu)]) info(' mask = {}'.format(mask)) return self._mask_to_seed_material(mask)
def inverted(self): """ Calculate inversion of the material. Total region is determined by self._xs.background(). Returns ------- res : MaterialData3D """ return MaterialData3D( self._lp.boolean_l2l(self._layers, [ MaterialLayer( LayoutData([Polygon(self._xs.background())], self._xs), -(self._xs.depth_dbu + self._xs.below_dbu), self._xs.depth_dbu + self._xs.below_dbu + self._xs.height_dbu) ], EP.ModeXor), self._xs, 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 _xpoints_to_mask(self, iv): """ Convert crossing points to a mask Parameters ---------- iv : list of lists or list of tuple each list / tuple represents two coordinates. Return ------ res : MaterialData Top ot the surface for deposition """ info(' iv = {})'.format(iv)) s = 0 last_s = 0 p1 = 0 p2 = 0 mask_polygons = [] for i in iv: z = i[0] # first coordinate s += i[1] # increase second coordinate if last_s <= 0 < s: # s increased and became > 0 p1 = z elif last_s > 0 >= s: # s decreased and became < 0 p2 = z poly = Polygon( Box(p1, -self._depth - self._below, p2, self._height)) info(' Appending poly {}'.format(poly)) mask_polygons.append(poly) last_s = s info(' mask_polys = {}'.format(mask_polygons)) ''' air = self._air.data info(' air = {}'.format(air)) # Sizing is needed only in vertical direction, it seems # air_sized = self._ep.size_p2p(air, self._delta, self._delta) air_sized = self._ep.size_p2p(air, self._delta, self._delta) info(' air_sized = {}'.format(air_sized)) # extended air minus air air_border = self._ep.boolean_p2p(air_sized, air, EP.ModeANotB) info(' air_border = {}'.format(air_border)) # overlap of air border and mask polygons mask_data = self._ep.boolean_p2p( air_border, mask_polygons, EP.ModeAnd) info(' mask_data = {}'.format(mask_data)) # info('____Creating MD from {}'.format([str(p) for p in mask_data])) return MaterialData(mask_data, self) ''' info('Before MaskData creation') res = MaskData(self._air.data, mask_polygons, self) info('res = {}'.format(res)) return res
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)
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 invert(self): self._polygons = self._ep.boolean_p2p(self._polygons, [Polygon(self._xs.background())], EP.ModeXor)