def test_cache_metadata(): save_options = kdb.SaveLayoutOptions() save_options.gds2_write_file_properties = True layout = kdb.Layout() layout.set_property(CACHE_PROP_ID, "test1") layout.write("tests/tmp/test_cache_metadata.gds", save_options) layout2 = kdb.Layout() assert layout2.property(CACHE_PROP_ID) is None layout2.set_property(CACHE_PROP_ID, "test2") assert layout2.property(CACHE_PROP_ID) == "test2" layout2.read("tests/tmp/test_cache_metadata.gds") assert layout2.property(CACHE_PROP_ID) == "test1"
def find_labels(gdspath: Path, label_layer: Tuple[int, int] = LAYER.LABEL, prefix: str = "opt_") -> Iterator[Tuple[str, float, float]]: """finds labels and locations from a GDS file""" # Load the layout gdspath = str(gdspath) layout = pya.Layout() layout.read(gdspath) # Get the top cell and the units, and find out the index of the layer topcell = layout.top_cell() dbu = layout.dbu layer = pya.LayerInfo(label_layer[0], label_layer[1]) layer_index = layout.layer(layer) # Extract locations iterator = topcell.begin_shapes_rec(layer_index) while not (iterator.at_end()): shape, trans = iterator.shape(), iterator.trans() iterator.next() if shape.is_text(): text = shape.text if text.string.startswith(prefix): transformed = text.transformed(trans) yield text.string, transformed.x * dbu, transformed.y * dbu
def assemble_subdies( mask_name, dict_subdies, subdies_directory, mask_directory=None, um_to_grid=UM_TO_GRID, ): """ Args: dict_subdies: {subdie_name: (x, y, rotation) in (um, um, deg)} subdies_directory: directory where the subdies should be looked for """ top_level_layout = pya.Layout() top_level = top_level_layout.create_cell(mask_name) if mask_directory is None: mask_directory = subdies_directory for subdie_name, (x_um, y_um, R) in dict_subdies.items(): gdspath = os.path.join(subdies_directory, subdie_name + ".gds") subdie = load_gds(gdspath).top_cell() _subdie = import_cell(top_level_layout, subdie) t = pya.Trans(R / 2, 0, int(x_um * um_to_grid), int(y_um * um_to_grid)) # t = pya.Trans(0, 0) subdie_instance = pya.CellInstArray(_subdie.cell_index(), t) top_level.insert(subdie_instance) top_level.write(os.path.join(mask_directory, mask_name + ".gds")) return top_level
def import_oas(filename, cellname = None, flatten = False): if filename.lower().endswith('.gds'): # you are looking for import_gds retval = pg.import_gds(filename, cellname = cellname, flatten = flatten) return retval try: import klayout.db as pya except ImportError as err: err.args = ('[PHIDL] klayout package needed to import OASIS. pip install klayout\n' + err.args[0], ) + err.args[1:] raise if not filename.lower().endswith('.oas'): filename += '.oas' fileroot = os.path.splitext(filename)[0] tempfilename = fileroot + '-tmp.gds' layout = pya.Layout() layout.read(filename) # We want to end up with one Device. If the imported layout has multiple top cells, # a new toplevel is created, and they go into the second level if len(layout.top_cells()) > 1: topcell = layout.create_cell('toplevel') rot_DTrans = pya.DTrans.R0 origin = pya.DPoint(0, 0) for childcell in layout.top_cells(): if childcell == topcell: continue topcell.insert(pya.DCellInstArray(childcell.cell_index(), pya.DTrans(rot_DTrans, origin))) else: topcell = layout.top_cell() topcell.write(tempfilename) retval = pg.import_gds(tempfilename, cellname = cellname, flatten = flatten) os.remove(tempfilename) return retval
def test_metadata(): save_options = kdb.SaveLayoutOptions() save_options.gds2_write_file_properties = True save_options.gds2_write_cell_properties = True load_options = kdb.LoadLayoutOptions() load_options.properties_enabled = True layout = kdb.Layout() TOP = layout.create_cell("TOP") TOP.set_property("key", "test1") TOP.set_property(123, "test2") layout.write("tests/tmp/test_metadata.gds", save_options) layout2 = kdb.Layout() layout2.read("tests/tmp/test_metadata.gds", load_options) TOP = layout2.top_cell() assert TOP.property(123) == "test2" assert TOP.property("key") == "test1"
def load_gds(self, filename): """ Load a GDS and append it into self.cells """ layout = pya.Layout() WORKING_MEMORY[filename] = layout layout.read(str(filename)) self.cells[layout.top_cell().name] = layout.top_cell() self.cells[layout.top_cell().name].metadata = {}
def check_width(gdspath, layer: Tuple[int, int] = (1, 0), min_width: float = 0.150, dbu: float = 1e3): """Reads layer from top cell and returns a number of edges violating min width Args: gdspath: path to GDS or Component layer: tuple (int, int) min_width: in um dbu: database units (1000 um/nm) """ from pp.component import Component from pp.write_component import write_gds if isinstance(gdspath, Component): gdspath.flatten() gdspath = write_gds(gdspath) layout = pya.Layout() layout.read(str(gdspath)) cell = layout.top_cell() region = pya.Region(cell.begin_shapes_rec(layout.layer(layer[0], layer[1]))) # print(region) # print(min_width*1e3) return len(region.width_check(min_width * dbu))
def test_alphabet() -> None: ly = pya.Layout() top_cell = ly.create_cell("TOP") add_text(top_cell, "HELLO-WORLD_0123456789+_-") # add_text(top_cell, "HELLO - WORLD 0123456789 +_-") # Need to add spaces # to the font top_cell.write("hello_world.gds")
def change_grid_klayout( gdspath, gdspathout=None, precision_in=1e-9, precision_out=1e-9, ): """ This script allows you to change the design grid by reading a layout written with different precission (DBU database units) and scaling to for example 1nm grid Using this script here is a bit of a hack to ensure that this gdsfactory layout works with Calibre. Calibre has some issue when there are 2 cells defined in the GDS with the same name This script reads a GDS in klayout and writes it again. """ assert precision_in >= precision_out gdspathout = gdspathout or gdspath gdspath = str(gdspath) gdspathout = str(gdspathout) layout = kl.Layout() layout.read(gdspath) layout.top_cell() scale = int(precision_in / precision_out) layout.dbu = precision_out / 1e-6 if scale > 1: layout.scale_and_snap(layout.top_cell(), 1, scale, 1) layout.write(gdspathout) return gdspathout
def check_inclusion( gdspath: ComponentOrPath, layer_in: Tuple[int, int] = (1, 0), layer_out: Tuple[int, int] = (2, 0), min_inclusion: float = 0.150, dbu: float = 1e3, ignore_angle_deg: int = 80, whole_edges: bool = False, metrics: str = "Square", min_projection: None = None, max_projection: None = None, ) -> int: """reads layer from top cell and returns a the area that violates min inclusion if 0 no area violates exclusion Args: gdspath: path to GDS layer_in: tuple layer_out: tuple min_inclusion: in um dbu: database units (1000 um/nm) ignore_angle_deg: The angle above which no check is performed whole_edges: If true, deliver the whole edges metrics: Specify the metrics type min_projection: lower threshold of the projected length of one edge onto another max_projection: upper limit of the projected length of one edge onto another """ import klayout.db as pya from gdsfactory.component import Component if isinstance(gdspath, Component): gdspath.flatten() gdspath = gdspath.write_gds() layout = pya.Layout() layout.read(str(gdspath)) cell = layout.top_cell() a = pya.Region( cell.begin_shapes_rec(layout.layer(layer_in[0], layer_in[1]))) b = pya.Region( cell.begin_shapes_rec(layout.layer(layer_out[0], layer_out[1]))) valid_metrics = ["Square", "Euclidian"] if metrics not in valid_metrics: raise ValueError("metrics = {metrics!r} not in {valid_metrics}") metrics = getattr(pya.Region, metrics) d = b.inside_check( a, min_inclusion * dbu, whole_edges, metrics, ignore_angle_deg, min_projection, max_projection, ) return d.polygons().area()
def check_inclusion( gdspath, layer_in=(1, 0), layer_out=(2, 0), min_inclusion=0.150, dbu=1e3, ignore_angle_deg=80, whole_edges=False, metrics=None, min_projection=None, max_projection=None, ): """reads layer from top cell and returns a the area that violates min inclusion if 0 no area violates exclusion Args: gdspath: path to GDS layer_in: tuple layer_out: tuple min_inclusion: in um dbu: database units (1000 um/nm) ignore_angle_deg: The angle above which no check is performed other: The other region against which to check whole_edges: If true, deliver the whole edges metrics: Specify the metrics type min_projection The lower threshold of the projected length of one edge onto another max_projection The upper limit of the projected length of one edge onto another """ from pp.component import Component from pp.write_component import write_gds if isinstance(gdspath, Component): gdspath.flatten() gdspath = write_gds(gdspath) layout = pya.Layout() layout.read(str(gdspath)) cell = layout.top_cell() a = pya.Region( cell.begin_shapes_rec(layout.layer(layer_in[0], layer_in[1]))) b = pya.Region( cell.begin_shapes_rec(layout.layer(layer_out[0], layer_out[1]))) d = b.inside_check( a, min_inclusion * dbu, whole_edges, metrics, ignore_angle_deg, min_projection, max_projection, ) return d.polygons().area()
def layout_pgtext(cell, layer, x, y, text, mag, inv=False, angle=0): layout = kdb.Layout() lylayer = layout.layer(layer) for i, line in enumerate(text.splitlines()): pcell = layout.create_cell("TEXT", "Basic", { "text": line, "layer": layer, "mag": mag, "inverse": inv }) pcell.transform_into( kdb.DCplxTrans(1, angle, False, x, y - i * mag * 5 / 4)) lylayer_new = cell.layout().layer(layer) cell.shapes(lylayer_new).insert(pcell.shapes(lylayer))
def _demo(): import pp c = pp.c.waveguide() gdspath = pp.write_component(c) layout1 = load_gds(gdspath) cell1 = layout1.top_cell() cell1_instance1 = pya.CellInstArray(cell1.cell_index(), pya.Trans(10, 0)) layout2 = pya.Layout() cell2 = layout2.create_cell("TOP_LEVEL") layout2.cell("TOP_LEVEL").insert(cell1_instance1) layout2.write("test.gds")
def load_gds(filepath): filepath = str(filepath) layout = pya.Layout() try: layout.read(filepath) except: print(filepath) raise cell = layout.top_cell() cell.metadata = {} # To make sure the cell does not get destroyed global CELLS CELLS[cell.name] = layout return layout
def load_gds(filepath: Union[Path, str]) -> Layout: filepath = str(filepath) layout = pya.Layout() try: layout.read(filepath) except RuntimeError as e: print(f"Error reading {filepath}") raise e cell = layout.top_cell() cell.metadata = {} # To make sure the cell does not get destroyed global CELLS CELLS[cell.name] = layout return layout
def _demo(): import gdsfactory as gf c = gf.components.straight() gdspath = c.write_gds_with_metadata() layout1 = load_gds(gdspath) cell1 = layout1.top_cell() cell1_instance1 = pya.CellInstArray(cell1.cell_index(), pya.Trans(10, 0)) layout2 = pya.Layout() layout2.create_cell("TOP_LEVEL") layout2.cell("TOP_LEVEL").insert(cell1_instance1) layout2.write("test.gds")
def check_duplicated_cells(gdspath: Union[Path, str]): """Reads cell and checks for duplicated cells Args: gdspath: path to GDS or Component """ import klayout.db as pya from gdsfactory.component import Component if isinstance(gdspath, Component): gdspath.flatten() gdspath = gdspath.write_gds() layout = pya.Layout() layout.read(str(gdspath)) cell = layout.top_cell() return cell
def main(): layout = pya.Layout() TOP = layout.create_cell("TOP") layer = pya.LayerInfo(1, 0) # First layer origin = pya.DPoint(0, 0) ex = pya.DVector(1, 0) ey = pya.DVector(0, 1) angles = np.linspace(-170, 170, 13) for i, angle_0 in enumerate(angles): for j, angle_3 in enumerate(angles): curve = bezier_curve(origin + ey * i * 150 + ex * j * 150, angle_0, angle_3, ex, ey) layout_waveguide(TOP, layer, curve, width=0.5) layout.write("bezier_waveguides.gds")
def write_oas(device, filename, **write_kwargs): if filename.lower().endswith('.gds'): # you are looking for write_gds device.write_gds(filename, **write_kwargs) return try: import klayout.db as pya except ImportError as err: err.args = ('[PHIDL] klayout package needed to write OASIS. pip install klayout\n' + err.args[0], ) + err.args[1:] raise if not filename.lower().endswith('.oas'): filename += '.oas' fileroot = os.path.splitext(filename)[0] tempfilename = fileroot + '-tmp.gds' device.write_gds(tempfilename, **write_kwargs) layout = pya.Layout() layout.read(tempfilename) # there can only be one top_cell because we only wrote one device topcell = layout.top_cell() topcell.write(filename) os.remove(tempfilename) return filename
def find_labels(gdspath, label_layer=201, label_purpose=0): """ finds labels and locations from a GDS file """ # Load the layout gdspath = str(gdspath) layout = pya.Layout() layout.read(gdspath) # Get the top cell and the units, and find out the index of the layer topcell = layout.top_cell() dbu = layout.dbu layer = pya.LayerInfo(label_layer, label_purpose) layer_index = layout.layer(layer) # Extract locations iterator = topcell.begin_shapes_rec(layer_index) while not (iterator.at_end()): shape, trans = iterator.shape(), iterator.trans() iterator.next() if shape.is_text(): text = shape.text transformed = text.transformed(trans) yield text.string, transformed.x * dbu, transformed.y * dbu
def test_3(self): # db plugins loaded? v = db.Layout() v.read(os.path.join(os.path.dirname(__file__), "..", "gds", "t10.gds")) self.assertEqual(v.top_cell().name, "RINGO")
def check_space( gdspath: ComponentOrPath, layer: Tuple[int, int] = (1, 0), min_space: float = 0.150, dbu: float = 1e3, ignore_angle_deg: int = 80, whole_edges: bool = False, metrics: str = "Square", min_projection: None = None, max_projection: None = None, ) -> int: """Reads layer from top cell and returns a the area that violates min space. If "whole_edges" is true, the resulting EdgePairs collection will receive the whole edges which contribute in the space check. "metrics" can be one of the constants Euclidian, Square or Projection. See there for a description of these constants. Use nil for this value to select the default (Euclidian metrics). "ignore_angle" specifies the angle limit of two edges. If two edges form an angle equal or above the given value, they will not contribute in the check. Setting this value to 90 (the default) will exclude edges with an angle of 90 degree or more from the check. Use nil for this value to select the default. "min_projection" and "max_projection" allow selecting edges by their projected value upon each other. It is sufficient if the projection of on e edge on the other matches the specified condition. The projected length must be larger or equal to "min_projection" and less than "max_proje ction". If you don't want to specify one limit, pass nil to the respective value. Args: gdspath: path to GDS or Component layer: tuple min_space: in um dbu: database units (1000 um/nm) ignore_angle_deg: The angle above which no check is performed other: The other region against which to check whole_edges: If true, deliver the whole edges metrics: Specify the metrics type 'Euclidian, square' min_projection The lower threshold of the projected length of one edge onto another max_projection The upper limit of the projected length of one edge onto another """ if isinstance(gdspath, Component): gdspath.flatten() gdspath = gdspath.write_gds() layout = pya.Layout() layout.read(str(gdspath)) cell = layout.top_cell() region = pya.Region(cell.begin_shapes_rec(layout.layer(layer[0], layer[1]))) valid_metrics = ["Square", "Euclidian"] if metrics not in valid_metrics: raise ValueError("metrics = {metrics} not in {valid_metrics}") metrics = getattr(pya.Region, metrics) d = region.space_check( min_space * dbu, whole_edges, metrics, ignore_angle_deg, min_projection, max_projection, ) # print(d.polygons().area()) return d.polygons().area()
import inspect import sys import os import re import props from tools import Lazy import klayout.db as db import klayout.lib import tools import math import context import geom import uuid layout = db.Layout() layout.dbu = .001 ######## These are implementations of Skill standard library functions def stringp(s): return isinstance(s, basestring) def floatp(s): return isinstance(s, float) def fixp(s): return isinstance(s, int)
def wrapper_draw(self, cell): layout = cell.layout() try: layer_map_dict[layout] except KeyError: layer_map_dict[layout] = pya.LayerMap() # Adding the dbu of the layout in the hash (bit us in the butt last time) short_hash_pcell = produce_hash(self, extra=(layout.dbu, extra_hash)) # cache paths cache_fname = f"cache_{self.__class__.__qualname__}_{short_hash_pcell}" cache_fname_gds = cache_fname + ".gds" cache_fname_pkl = cache_fname + ".klayout.pkl" os.makedirs(cache_dir, mode=0o775, exist_ok=True) cache_fpath_gds = os.path.join(cache_dir, cache_fname_gds) cache_fpath_pkl = os.path.join(cache_dir, cache_fname_pkl) if os.path.isfile(cache_fpath_gds) and os.path.isfile( cache_fpath_pkl): with open(cache_fpath_pkl, "rb") as file: ports, read_short_hash_pcell, cellname = pickle.load(file) # pylint: disable=unused-variable logger.debug( f"Reading from cache: {cache_fname}: {cellname}, {ports}") print("r", end="", flush=True) if not layout.has_cell(cache_fname): read_layout(layout, cache_fpath_gds, disambiguation_name=cellname) retrieved_cell = layout.cell(cache_fname) cell.insert( pya.DCellInstArray( retrieved_cell.cell_index(), pya.DTrans(pya.DTrans.R0, pya.DPoint(0, 0)), )) # cell.move_tree(retrieved_cell) else: if layout.has_cell(cache_fname): logger.warning( f"WARNING: {cache_fname_gds} does not exist but {cache_fname} is in layout." ) # populating .gds and .pkl empty_layout = pya.Layout() empty_layout.dbu = layout.dbu empty_cell = empty_layout.create_cell(cell.name) filled_cell, ports = draw(self, empty_cell) logger.debug( f"Writing to cache: {cache_fname}: {filled_cell.name}, {ports}" ) print("w", end="", flush=True) cellname, filled_cell.name = filled_cell.name, cache_fname # There can be duplicate cell names in subcells here. # We are saving a list of them inside a property named CACHE_PROP_ID # So we need to allow the properties to be saved inside the gds file (incompatible with the GDS2 standard) save_options = pya.SaveLayoutOptions() save_options.gds2_write_file_properties = True empty_layout.write(cache_fpath_gds, save_options) with open(cache_fpath_pkl, "wb") as file: pickle.dump((ports, short_hash_pcell, cellname), file) # Make sure we delete the empty_layout to not grow # helps debug layer_map_dict.pop(empty_layout, None) del empty_layout assert not layout.has_cell(cache_fname) read_layout(layout, cache_fpath_gds, disambiguation_name=cellname) retrieved_cell = layout.cell(cache_fname) cell.insert( pya.DCellInstArray( retrieved_cell.cell_index(), pya.DTrans(pya.DTrans.R0, pya.DPoint(0, 0)), )) return cell, ports
def _top_cell(): layout = kdb.Layout() layout.dbu = 0.001 TOP = layout.create_cell("TOP") return TOP, layout
def wrapper_draw(self, cell): global layer_map_dict layout = cell.layout() try: layer_map_dict[layout] except KeyError: layer_map_dict[layout] = pya.LayerMap() # Adding the dbu of the layout in the hash (bit us in the butt last time) short_hash_pcell = produce_hash(self, extra=(layout.dbu, extra_hash)) # cache paths cache_fname = f"cache_{self.__class__.__qualname__}_{short_hash_pcell}" cache_fname_gds = cache_fname + ".gds" cache_fname_pkl = cache_fname + ".klayout.pkl" os.makedirs(cache_dir, mode=0o775, exist_ok=True) cache_fpath_gds = os.path.join(cache_dir, cache_fname_gds) cache_fpath_pkl = os.path.join(cache_dir, cache_fname_pkl) if os.path.isfile(cache_fpath_gds) and os.path.isfile(cache_fpath_pkl): with open(cache_fpath_pkl, "rb") as file: ports, read_short_hash_pcell, cellname = pickle.load(file) if debug: print(f"Reading from cache: {cache_fname}: {cellname}, {ports}") else: print("r", end="", flush=True) if not layout.has_cell(cache_fname): read_layout(layout, cache_fpath_gds) retrieved_cell = layout.cell(cache_fname) cell.insert( pya.DCellInstArray( retrieved_cell.cell_index(), pya.DTrans(pya.DTrans.R0, pya.DPoint(0, 0)), ) ) # cell.move_tree(retrieved_cell) else: if layout.has_cell(cache_fname): print( f"WARNING: {cache_fname_gds} does not exist but {cache_fname} is in layout." ) # populating .gds and .pkl empty_layout = pya.Layout() empty_layout.dbu = layout.dbu empty_cell = empty_layout.create_cell(cell.name) filled_cell, ports = draw(self, empty_cell) if debug: print( f"Writing to cache: {cache_fname}: {filled_cell.name}, {ports}" ) else: print("w", end="", flush=True) cellname, filled_cell.name = filled_cell.name, cache_fname filled_cell.write(cache_fpath_gds) with open(cache_fpath_pkl, "wb") as file: pickle.dump((ports, short_hash_pcell, cellname), file) # Make sure we delete the empty_layout to not grow # helps debug layer_map_dict.pop(empty_layout, None) del empty_layout assert not layout.has_cell(cache_fname) read_layout(layout, cache_fpath_gds) retrieved_cell = layout.cell(cache_fname) cell.insert( pya.DCellInstArray( retrieved_cell.cell_index(), pya.DTrans(pya.DTrans.R0, pya.DPoint(0, 0)), ) ) return cell, ports
def main(): def trace_rounded_path(cell, layer, rounded_path, width): points = [] for item in rounded_path: points.extend(item.get_points()) dpath = kdb.DPath(points, width, 0, 0) cell.shapes(layer).insert(dpath) def trace_reference_path(cell, layer, points, width): dpath = kdb.DPath(points, width, 0, 0) cell.shapes(layer).insert(dpath) layout = kdb.Layout() TOP = layout.create_cell("TOP") layer = kdb.LayerInfo(10, 0) layerRec = kdb.LayerInfo(1001, 0) ex, ey = kdb.DPoint(1, 0), kdb.DPoint(0, 1) points = [0 * ex, 10 * ex, 10 * (ex + ey), 30 * ex] origin = 0 * ey points = [origin + point for point in points] x = compute_rounded_path(points, 3) trace_rounded_path(TOP, layer, x, 0.5) trace_reference_path(TOP, layerRec, points, 0.5) points = [0 * ex, 10 * ex, 5 * (ex - ey), 17 * ex, 30 * ex] origin = 30 * ey points = [origin + point for point in points] x = compute_rounded_path(points, 3) trace_rounded_path(TOP, layer, x, 0.5) trace_reference_path(TOP, layerRec, points, 0.5) radius = 3 for ex2 in (ex, -ex): points = [2 * ex2] for d in np.arange(1, 10, 2.5): origin = points[-1] displacements = [ 4 * radius * ex2, 4 * radius * ex2 + d * ey - 1 * d * ex2, d * ey, (d + 2 * radius) * ey, ] points += [origin + displacement for displacement in displacements] origin = 15 * ex + 40 * ey points = [origin + point for point in points] x = compute_rounded_path(points, radius) trace_rounded_path(TOP, layer, x, 0.5) trace_reference_path(TOP, layerRec, points, 0.5) # Layout tapered waveguide points = [ 0 * ex, 100 * ex, 100 * ex + 20 * ey, 10 * ex + 5 * ey, 10 * ex + 25 * ey, 100 * ex + 30 * ey, ] # Untapered origin = 40 * ex points_ = [origin + point for point in points] layout_waveguide_from_points(TOP, layer, points_, 0.5, 5) # Tapered origin = 40 * ex + 40 * ey points_ = [origin + point for point in points] layout_waveguide_from_points( TOP, layer, points_, 0.5, 5, taper_width=3, taper_length=10 ) print("Wrote waveguide_rounding.gds") TOP.write("waveguide_rounding.gds")
#!/usr/bin/python3 import klayout.db as db import klayout.lib layout = db.Layout() layout.dbu = .001 layers = { "m1": layout.layer(68, 20), "m1.con": layout.layer(67, 44), "m1.pin": layout.layer(68, 16), "m1.label": layout.layer(68, 5), "m2": layout.layer(69, 20), "m2.con": layout.layer(68, 44), "m2.pin": layout.layer(69, 16), "m2.label": layout.layer(69, 5), "li": layout.layer(67, 20), "li.pin": layout.layer(67, 16), "li.label": layout.layer(67, 5), "li.con": layout.layer(66, 44), "poly": layout.layer(66, 20), "npc": layout.layer(95, 20) } def make_rect(cell, org, sz, layer): if isinstance(layer, list): for l in layer: make_rect(cell, org, sz, l) return
import klayout.db as pya from zeropdk.layout.polygons import layout_path from zeropdk.layout import layout_box import numpy as np from functions import * layout = pya.Layout() layout.dbu = 0.001 TOP = layout.create_cell("TOP") workingLayer = layout.layer(4, 0) chipBorder = layout.layer(7, 0) #computational layer maskBorder = layout.layer(10, 0) #nbtin layer outputLayer = layout.layer(1, 0) sourceLayer = layout.layer(3, 0) port1Layer = layout.layer(2, 0) port2Layer = layout.layer(5, 0) ex = pya.DVector(1, 0) ey = pya.DVector(0, 1) upperRegion = pya.Region() lowerRegion = pya.Region() feedlineRegion = pya.Region() lowZRegion = pya.Region() highZRegion = pya.Region() #insert chip/mask border chipWidth = 11000 chipLength = 19000 #layout_box(TOP, chipBorder, -chipWidth*ex-50000*ey, chipWidth*ex + 50000*ey, ex)
def place_from_yaml( filepath_yaml: Path, root_does: Path = CONFIG["cache_doe_directory"], precision: float = 1e-9, fontpath: Path = text.FONT_PATH, default_align_x: NSEW = "W", default_align_y: NSEW = "S", default_margin: int = 10, default_x0: NSEW = "E", default_y0: NSEW = "S", ) -> Cell: """Returns a gds cell composed of DOEs/components given in a yaml file allows for each DOE to have its own x and y spacing (more flexible than method1) Args: filepath_yaml: root_does: used for cache, requires content.txt """ transform_identity = pya.Trans(0, 0) dicts, mask_settings = load_yaml(filepath_yaml) does, templates = separate_does_from_templates(dicts) placed_doe = None placed_does = {} top_level_name = mask_settings.get("name", "TOP_LEVEL") layer_doe_label = mask_settings["layer_doe_label"] top_level_layout = pya.Layout() # Set database units according to precision top_level_layout.dbu = precision / 1e-6 dbu = top_level_layout.dbu um_to_grid = int(1 / dbu) top_level = top_level_layout.create_cell(top_level_name) global CELLS CELLS[top_level_name] = top_level_layout default_doe_settings = { "add_doe_label": False, "add_doe_visual_label": False, "dx_visual_label": 0, "dy_visual_label": 0, } for doe_name, doe in does.items(): # If a template is specified, apply it if "template" in doe: doe_templates = doe["template"] if type(doe_templates) != list: doe_templates = [doe_templates] for doe_template in doe_templates: try: doe = update_dicts_recurse(doe, templates[doe_template]) except BaseException: print(doe_template, "does not exist") raise doe = update_dicts_recurse(doe, default_doe_settings) # Get all the components components = load_doe(doe_name, root_does) # Check that the high level components are all unique # For now this is mostly to circumvent a bug # But the design manual also specifies that DOE components should have # unique names. So one instance per cell if components: if len(components) != len( set([_c.top_cell().name for _c in components])): __dict_component_debug = {} for _c in components: _name = _c.top_cell().name if _name not in __dict_component_debug: __dict_component_debug[_name] = 0 __dict_component_debug[_name] += 1 duplicates_components = [ _name for _name, _count in __dict_component_debug.items() if _count > 1 ] print( "Please remove duplicate components at DOE entry level: ") print(duplicates_components) components = [ import_cell(top_level_layout, _c.top_cell()) for _c in components ] default_placer_settings = { "align_x": default_align_x, "align_y": default_align_y, "margin": default_margin, "x0": default_x0, "y0": default_y0, } settings = default_placer_settings.copy() placer = doe.get("placer") if placer: placer_type = placer.pop("type", "pack_col") settings.update(doe["placer"]) else: placer_type = "pack_col" if placer_type not in PLACER_NAME2FUNC: raise ValueError( f"{placer_type} is not an available placer, Choose:" f" {list(PLACER_NAME2FUNC.keys())}") _placer = PLACER_NAME2FUNC[placer_type] # All other attributes are assumed to be settings for the placer # Check if the cell should be attached to a specific parent cell if "parent" in settings: parent_name = settings.pop("parent") if parent_name not in CELLS: # Create parent cell in layout and insert it under top level parent_cell = top_level_layout.create_cell(parent_name) CELLS[parent_name] = parent_cell parent_cell_instance = pya.CellInstArray( parent_cell.cell_index(), transform_identity) top_level.insert(parent_cell_instance) doe_parent_cell = CELLS[parent_name] else: # If no parent specified, insert the DOE at top level doe_parent_cell = top_level # Check if we should create a DOE cell which regroups the DOEs if "with_doe_cell" in settings: with_doe_cell = settings.pop("with_doe_cell") else: with_doe_cell = True # x0, y0 can either be float or string x0 = settings.pop("x0") y0 = settings.pop("y0") # Check whether we are doing relative or absolute placement # if (x0 in ["E", "W"] or y0 in ["N", "S"]) and not placed_doe: # raise ValueError( # "At least one DOE must be placed to use relative placement" # ) # For relative placement (to previous DOE) if "margin_x" not in settings: settings["margin_x"] = settings["margin"] if "margin_y" not in settings: settings["margin_y"] = settings["margin"] if "inter_margin_x" not in settings: inter_margin_x = settings["margin_x"] else: inter_margin_x = settings.pop("inter_margin_x") if "inter_margin_y" not in settings: inter_margin_y = settings["margin_y"] else: inter_margin_y = settings.pop("inter_margin_y") align_x = settings["align_x"] align_y = settings["align_y"] # Make sure that the alignment is sensible depending on how we stack # If we specify a DOE to place next to, use it if "next_to" in settings: placed_doe = placed_does[settings.pop("next_to")] # print(placed_doe) # print(placed_does) # Otherwise, use previously placed DOE as starting point doe_si = (SizeInfo(placed_doe, top_level_layout, um_to_grid=um_to_grid) if placed_doe is not None else None) if x0 == "E": x0 = doe_si.east if align_x == "W": x0 += inter_margin_x if x0 == "W": x0 = doe_si.west if align_x == "E": x0 -= inter_margin_x if y0 == "N": y0 = doe_si.north if align_y == "S": y0 += inter_margin_y if y0 == "S": y0 = doe_si.south if align_y == "N": y0 -= inter_margin_y # Add x0, y0 in settings as float settings["x0"] = x0 settings["y0"] = y0 settings["um_to_grid"] = um_to_grid placed_components = _placer(components, **settings) # Place components within a cell having the DOE name if with_doe_cell or len(placed_components) > 1: doe_cell = top_level_layout.create_cell(doe_name) CELLS[doe_name] = doe_cell for instance in placed_components: doe_cell.insert(instance) placed_does[doe_name] = doe_cell placed_doe = doe_cell doe_instance = pya.CellInstArray(doe_cell.cell_index(), transform_identity) else: # If only single cell and we want to skip the sweep cell doe_instance = placed_components[0] placed_does[doe_name] = doe_instance placed_doe = doe_instance add_doe_label = doe["add_doe_label"] add_doe_visual_label = doe["add_doe_visual_label"] if add_doe_label: layer_label_index, layer_label_datatype = layer_doe_label layer_index = top_level.layout().insert_layer( pya.LayerInfo(layer_label_index, layer_label_datatype)) # Add the name of the DOE at the center of the cell _p = doe_instance.bbox(top_level_layout).center() _text = pya.Text(doe_name, _p.x, _p.y) top_level.shapes(layer_index).insert(_text) if add_doe_visual_label: _bbox = doe_instance.bbox(top_level_layout) idbu = 1 / top_level.layout().dbu x_text = _bbox.center().x + doe["dx_visual_label"] * idbu y_text = _bbox.bottom + (15.0 + doe["dy_visual_label"]) * idbu _text = text.add_text(top_level, doe_name, position=(x_text, y_text), fontpath=fontpath) # _transform = pya.DTrans(x_text, y_text) # top_level.insert(pya.CellInstArray(_text.cell_index(), _transform)) doe_parent_cell.insert(doe_instance) return top_level