def size_l2l(self, layers, dx, dy=0, dz=0, mode=2, rh=True, mc=True): """ Change mask size in each layer by dx and dy. Size in z-direction remains unchanged. Parameters ---------- layers : list of MaterialLayer dx : int size increase in x-direction in [dbu] dy : int (optional) size increase in y-direction in [dbu] dz : int (optional) size increase in z-direction in [dbu] mode : int rh : boolean (optional) mc : boolean (optional) Returns ------- res : layers : list of MaterialLayer """ res = [] for l in layers: sized_polys = self.size_p2p(l.mask.data, dx, dy, mode, rh, mc) res.append( MaterialLayer(LayoutData(sized_polys, l.mask._xs), l.bottom - dz, l.thickness + 2 * dz)) # Join overlapping layers info(' res before normalize = {}'.format(res)) res = self.normalize(res) info(' res after normalize = {}'.format(res)) return res
def merge_layers_same_z(self, layers): """ Parameters ---------- layers : list of MaterialLayer Returns ------- res : list of MaterialLayer """ n_layers = len(layers) res_merged = [] for i, li in enumerate(layers): merged = MaterialLayer(li.mask, li.bottom, li.thickness) for j in range(i + 1, n_layers): lj = layers[j] if merged.is_z_same(lj): # perform OR operation on the LayoutData merged = MaterialLayer(merged.mask.or_(lj.mask), merged.bottom, merged.thickness) info(' Merged layers b = {}, t = {}'.format( merged.bottom, merged.top)) res_merged.append(merged) return res_merged
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 merge_layers_same_mask(self, layers): """ Parameters ---------- layers : list of MaterialLayer Returns ------- res : list of MaterialLayer """ _check_layer_list_sorted(layers) res_merged = [] while layers: la = layers.pop(0) ib = 0 while layers and (ib < len(layers)): lb = layers[ib] if la.top == lb.bottom: if la.mask.data == lb.mask.data: la = MaterialLayer(la.mask, la.bottom, lb.top - la.bottom) info(' Merged layers ({},{}) and ({}, {})'.format( la.bottom, la.top, lb.bottom, lb.top)) layers.pop(ib) elif la.top < lb.bottom: # all following lb will be higher res_merged.append(la) break # go to the next la ib += 1 else: res_merged.append(la) return res_merged
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 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 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_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_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 run(self): """ The basic generation method """ if not self._setup(): return None self._update_basic_regions() text = None with open(self._file_path) as file: text = file.read() if not text: MessageBox.critical("Error", "Error reading file #{self._file_path}", MessageBox.b_ok()) return None # prepare variables to be visible in the script locals_ = dir(self) locals_dict = {} for attr in locals_: if attr[0] != '_': locals_dict.update({attr: getattr(self, attr)}) try: exec(text, locals_dict) except Exception as e: # For development # print(e.__traceback__.) # print(dir(e)) MessageBox.critical("Error", str(e), MessageBox.b_ok()) # pass return None Application.instance().main_window().cm_lv_add_missing() # @@@ if self._lyp_file: self._target_view.load_layer_props(self._lyp_file) self._target_view.zoom_fit() self._target_layout.write(self._target_gds_file_name) info(' len(bulk.data) = {}'.format(len(self._bulk.data))) self._tech_str = '# This file was generated automatically by pyxs.\n\n'\ + layer_to_tech_str(255, self._bulk.data[0], 'Substrate') + self._tech_str with open(self._target_tech_file_name, 'w') as f: f.write(self._tech_str) return None
def output(self, layer_spec, material, color=None): """ Outputs a material object to the output layout Parameters ---------- layer_spec : str layer specification material : MaterialData3D """ if not isinstance(material, MaterialData3D): raise TypeError( "'output' method: second parameter must be " "a material object (MaterialData3D). {} is given".format( type(material))) # confine the shapes to the region of interest # info(' roi = {}'.format(self._roi)) # info(' material = {}'.format(material)) export_layers = self._lp.boolean_l2l(self._roi, material.data, LP.ModeAnd) info(' layers to export = {}'.format(export_layers)) l, data_type, name = string_to_layer_info_params(layer_spec, True) # info('{}, {}, {}'.format(l, data_type, name)) name = name if name else '' for i, layer in enumerate(export_layers): layer_not_empty = False layer_no = l + i if layer.thickness < MIN_EXPORT_LAYER_THICKNESS: continue # next layer in the material ls = LayerInfo(layer_no, data_type, '{} ({}-{})'.format(name, layer.bottom, layer.top)) li = self._target_layout.insert_layer(ls) shapes = self._target_layout.cell(self._target_cell).shapes(li) for polygon in layer.mask.data: # info('S = {}, S_box = {}' # .format(polygon.area(), polygon.bbox().area())) if polygon.area() > 0.001 * polygon.bbox().area(): layer_not_empty = True shapes.insert(polygon) if layer_not_empty: self._tech_str += layer_to_tech_str(layer_no, layer, name=name, color=color)
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 is_z_overlapping(self, other): """ Check two layers for overlap. Parameters ---------- other : MaterialLayer Returns ------- bool """ info(' self.b, self.t, other.b, other.t = {} {} {} {}'.format( self.bottom, self.top, other.bottom, other.top)) if (self._top <= other.bottom) or (self._bottom >= other.top): return False else: return True
def size_p2p(self, polygons, dx, dy=0, mode=2, rh=True, mc=True): """ Size the given polygons into polygons Parameters ---------- polygons : list of Polygon The input polygons dx : int The sizing value in x direction in dbu dy : int (optional) The sizing value in y direction in dbu mode : int (optional) The sizing mode. Allowed values from 1 to 5 rh : bool (optional) True, if holes should be resolved into the hull mc : bool (optional) True, if touching corners should be resolved into less connected contours Returns ------- res : list of Polygon The output polygons """ info(' polys = {}'.format(polygons)) info(' dx = {}, dy = {}'.format(dx, dy)) res = super(EdgeProcessor, self).size_p2p(polygons, dx, dy, mode, rh, mc) info(' EP.size_p2p().res = {}'.format(res)) return res
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 all(self): """ A pseudo-mask, covering the whole wafer Return ------ res : MaterialData """ e = self._extend info('e = {}'.format(e)) line_dbu = self._line_dbu info('line_dbu = {}'.format(line_dbu)) res = self._xpoints_to_mask([[-e, 1], [line_dbu.length() + e, -1]]) info(' all().res = {}'.format(res)) return res
def __init__(self, air_polygons, mask_polygons, xs): """ Parameters ---------- air_polygons : list of Polygon list of shapes constituting air in cross-section mask_polygons : list of Polygon list of shapes constituting material in cross-section xs: XSectionGenerator passed to LayoutData.__init__() delta : float the intrinsic height (required for mask data because there cannot be an infinitely small mask layer (in database units) """ super(MaskData, self).__init__([], xs) # LayoutData() self._air_polygons = air_polygons self._mask_polygons = mask_polygons info('air_polygons = {}'.format(air_polygons)) info('mask_polygons = {}'.format(mask_polygons)) info('Success!')
def delta(self, x): self._delta = int_floor(x / self._dbu + 0.5) info('XSG._delta set to {}'.format(self._delta))
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 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 boolean_l2l(self, la, lb, mode, rh=True, mc=True): """ Parameters ---------- la : list of MaterialLayer or empty list sorted list. layers must not overlap with each other lb : list of MaterialLayer or empty list sorted list. layers must not overlap with each other mode: int rh : bool (optional) resolve_holes mc : bool (optional) min_coherence Returns ------- list of MaterialLayer or [] """ n_la, n_lb = len(la), len(lb) # number of polygons in pa and pb info(' n_la = {}, n_lb = {}, mode = {}'.format(n_la, n_lb, mode)) ia, ib = 0, 0 a = la[ia] if la else None b = lb[ib] if lb else None la_res, lb_res, oa, ob = [], [], [], [] while a and b: info(' a = {}'.format(a)) info(' b = {}'.format(b)) if a.is_lower_s(b, 'bottom'): info(' a bottom is lower') top = min(a.top, b.bottom) info(' top = {}'.format(top)) if top == a.top: # no overlap info(' a top is lower than b bottom, no overlap') la_res += [a] ia += 1 a = None if ia >= len(la) else la[ia] continue else: info(' a top is higher than b bottom, overlap') # use part of a from a.bottom to top la_res += [MaterialLayer(a.mask, a.bottom, top - a.bottom)] # overlapping candidate a is a from top to a.top a = MaterialLayer(a.mask, top, a.top - top) continue elif b.is_lower_s(a, 'bottom'): info(' b is lower') top = min(b.top, a.bottom) if top == b.top: # no overlap lb_res += [b] ib += 1 b = None if ib >= len(lb) else lb[ib] continue else: # use part of b from b.bottom to top lb_res += [MaterialLayer(b.mask, b.bottom, top - b.bottom)] # overlapping candidate b is b from top to b.top b = MaterialLayer(b.mask, top, b.top - top) continue else: assert a.bottom == b.bottom, 'bottoms must be equal here' info(' same bottom') if a.is_lower_s(b, 'top') or b.is_lower_s(a, 'top'): top = min(a.top, b.top) if top < b.top: # a is in the overlap, b is higher info(' b is higher') oa += [a] ob += [MaterialLayer(b.mask, b.bottom, top - b.bottom)] b = MaterialLayer(b.mask, top, b.top - top) # remaining top ia += 1 a = None if ia >= len(la) else la[ia] continue elif top < a.top: # b is in the overlap, a is higher info(' a is higher') ob += [b] oa += [MaterialLayer(a.mask, a.bottom, top - a.bottom)] a = MaterialLayer(a.mask, top, a.top - top) # remaining top ib += 1 b = None if ib >= len(lb) else lb[ib] continue else: assert a.top == b.top, 'tops must be equal here' info(' same top') oa += [a] ob += [b] ia += 1 a = None if ia >= len(la) else la[ia] ib += 1 b = None if ib >= len(lb) else lb[ib] continue if a: la_res += [a] if b: lb_res += [b] # add remaining a's and b's while ia < len(la) - 1: ia += 1 la_res += [la[ia]] while ib < len(lb) - 1: ib += 1 lb_res += [lb[ib]] lo_res = [] for a, b in zip(oa, ob): o_polygons = self.boolean_p2p(a.mask.data, b.mask.data, mode, rh, mc) if o_polygons: lo_res += [ MaterialLayer(LayoutData(o_polygons, a.mask._xs), a.bottom, a.top - a.bottom) ] info(' la_res = {}'.format(la_res)) info(' lb_res = {}'.format(lb_res)) info(' lo_res = {}'.format(lo_res)) if mode == self.ModeAnd: # either la and lb is empty, mode AND info(' mode AND') res = lo_res # will be empty elif mode == self.ModeOr or mode == self.ModeXor: info(' mode OR/XOR') res = la_res + lo_res + lb_res elif mode == self.ModeANotB: info(' mode ANotB') res = la_res + lo_res elif mode == self.ModeBNotA: info(' mode BNotA') res = lo_res + lb_res else: res = [] res = self.normalize(res) # res = self.merge_layers_same_z(res) # res = self.merge_layers_same_mask(res) # res.sort() info(' boolean_l2l().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): """ 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 _mask_to_seed_material(self, mask): """ Convert mask to a seed material for growth / etch operations. Parameters ---------- mask: LayoutData top view of the region to be grown / etched Return ------ seed : MaterialData3D Thin seed material to be used in geometry generation. """ info(' mask = {}'.format(mask)) mask_material = [ MaterialLayer(mask, -(self._depth + self._below), self._depth + self._below + self._height) ] info(' mask material = {}'.format(mask)) air = self._air.data info(' air = {}'.format(air)) air_sized = self._lp.size_l2l(air, 0, 0, self._delta) info(' air sized = {}'.format(air_sized)) # extended air minus air air_border = self._lp.boolean_l2l(air_sized, air, LP.ModeANotB) info(' air_border = {}'.format(air_border)) # overlap of air border and mask layer seed_layers = self._lp.boolean_l2l(air_border, mask_material, EP.ModeAnd) info(' seed_layers= {}'.format(seed_layers)) seed = MaterialData3D(seed_layers, self, self._delta) return seed
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
from klayout_pyxs import Action from klayout_pyxs import FileDialog from klayout_pyxs import Box from klayout_pyxs import LayerInfo from klayout_pyxs import Point from klayout_pyxs import Polygon from klayout_pyxs import Trans from klayout_pyxs.utils import print_info, int_floor, make_iterable, info from klayout_pyxs.geometry_2d import EP, LayoutData, parse_grow_etch_args from klayout_pyxs.layer_parameters import string_to_layer_info_params from klayout_pyxs.layer_parameters import string_to_layer_info from klayout_pyxs.geometry_3d import MaterialLayer, LP, lp, layer_to_tech_str info('Module pyxs3D_lib.py reloaded') MIN_EXPORT_LAYER_THICKNESS = 5 class MaterialData3D(object): """ Class to operate 3D materials. 3D material is described by its top view (mask), vertical position (elevation), and thickness. """ def __init__(self, layers, xs, delta): """ Parameters ---------- layers : list of geometry_3d.MaterialLayer
def grow(self, *args, **kwargs): """ Same as deposit() """ all = self.all() info(all) return all.grow(*args, **kwargs)
def split_overlapping_z(self, layers): """ Parameters ---------- layers : list of MaterialLayer a list of non-sorted and / or overlapping layers Returns ------- res : list of MaterialLayer a sorted list of non-overlapping layers """ info(' layers = {}'.format(layers)) _check_layer_list_sorted(layers) res = [] while layers: la = layers.pop(0) # first element is the lowest in z info(' la = {}'.format(la)) if not layers: # la was the only element res += [la] continue lb = layers.pop(0) # take next element info(' lb = {}'.format(lb)) if la.is_lower(lb, levela='top', levelb='bottom'): # a is lower or touching # layers is sorted, la will not overlap with other lb res += [la] layers.insert(0, lb) info( ' la top < lb btm, la is moved to result, lb is returned' ) elif la.bottom == lb.bottom: info(' la btm == lb btm') if la.top < lb.top: lb_split = lb.split_by_layer(la) o = MaterialLayer(la.mask.or_(lb_split[0].mask), la.bottom, la.thickness) layers = [o] + layers # top part of b is inserted to layers, ensuring sorted order i = 0 while i < len(layers): if lb_split[1].bottom < layers[i].bottom: layers.insert(i, lb_split[1]) break elif lb_split[1].bottom == layers[i].bottom: if lb_split[1].top <= layers[i].top: layers.insert(i, lb_split[1]) break i += 1 else: layers.append(lb_split[1]) info(' la top < lb top, o calculated, added to layers') else: # la is the same height as lb # perform OR operation on the LayoutData o = MaterialLayer(la.mask.or_(lb.mask), la.bottom, la.thickness) layers.insert(0, o) info(' la top == lb top, o calculated, added to layers') else: info( ' la btm < lb btm, lower part of la is result, rest added to layers' ) # lb bottom splits a somewhere (maybe lb top too) la_split = la.split_by_layer(lb) # bottom sublayer is not overlapping with lb and others res.append(la_split[0]) layers = la_split[1:] + [lb] + layers info(' res = {}'.format(res)) return res