def layout_box(cell, layer, point1, point3, ex): """ Lays out a box Args: point1: bottom-left point point3: top-right point """ ey = rotate90(ex) polygon = box(point1, point3, ex, ey) insert_shape(cell, layer, polygon) return polygon
def origin_ex_ey(self, multiple_of_90=False): # pylint: disable=unused-argument EX = kdb.DVector(1, 0) cp = self.get_cell_params() origin = kdb.DPoint(0, 0) # if 'angle_ex' not in cp.__dict__: # cp.angle_ex = 0 if multiple_of_90: if cp.angle_ex % 90 != 0: raise RuntimeError("Specify an angle multiple of 90 degrees") from math import pi ex = rotate(EX, cp.angle_ex * pi / 180) ey = rotate90(ex) return origin, ex, ey
def layout_rectangle(cell, layer, center, width, height, ex): """ Lays out a rectangle Args: center: pya.DPoint (um units) width: float (um units) height: float (um unit) ex: orientation """ ey = rotate90(ex) shape = rectangle(center, width, height, ex, ey) insert_shape(cell, layer, shape) return shape
def draw(self, cell, layer): """ Draws this port on cell's layer using klayout.db""" if self.name.startswith("el"): pin_length = self.width else: # port is optical pin_length = max(2, self.width / 10) ex = self.direction # Place a Path around the port pointing towards its exit port_path = kdb.DPath( [ self.position - 0.5 * pin_length * ex, self.position + 0.5 * pin_length * ex, ], self.width, ) cell.shapes(layer).insert(port_path) # Place a small arrow around the tip of the port from zeropdk.layout.geometry import rotate90 ey = rotate90(ex) port_tip = kdb.DSimplePolygon( [ self.position + 0.5 * pin_length * ex, self.position + 0.4 * pin_length * ex + 0.1 * pin_length * ey, self.position + 0.4 * pin_length * ex - 0.1 * pin_length * ey, ] ) cell.shapes(layer).insert(port_tip) # pin_rectangle = rectangle(self.position, self.width, # pin_length, ex, ey) # cell.shapes(layer).insert(pin_rectangle) # Place a text object annotating the name of the port cell.shapes(layer).insert( kdb.DText( self.name, kdb.DTrans(kdb.DTrans.R0, self.position.x, self.position.y), min(pin_length, 20), 0, ) ) return self
def transform_and_rotate(self, center, ex=None): """Translates the polygon by 'center' and rotates by the 'ex' orientation. Example: if current polygon is a unit square with bottom-left corner at (0,0), then square.transform_and_rotate(DPoint(0, 1), DVector(0, 1)) will rotate the square by 90 degrees and translate it by 1 y-unit. The new square's bottom-left corner will be at (-1, 1). """ if ex is None: ex = backend.DVector(1, 0) ey = rotate90(ex) polygon_dpoints_transformed = [ center + p.x * ex + p.y * ey for p in self.each_point() ] self.assign(_SimplePolygon(polygon_dpoints_transformed)) return self
def layout_square(cell, layer, center, width, ex=None): """ Lays out a square in a layer Args: center: pya.DPoint (um units) width: float (um units) ex: orientation """ if ex is None: ex = pya.DPoint(1, 0) ey = rotate90(ex) shape = square(center, width, ex, ey) insert_shape(cell, layer, shape) return shape
def connect_ports_L(cell, cplayer, ports_from, ports_to, ex): """ Connects ports ports_from to ports_to, always leaving vertically""" ey = rotate90(ex) for port_from, port_to in zip(ports_from, ports_to): assert port_from.direction == ey or port_from.direction == -ey o_y = ey if port_to.position * ey > port_from.position * ey else -ey o_x = ex if port_to.position * ex > port_from.position * ex else -ex middle_point = manhattan_intersection(port_from.position, port_to.position, ex) layout_waveguide( cell, ensure_layer(cell.layout(), cplayer), [port_from.position, middle_point + port_to.width * 0.5 * o_y], port_from.width, ) layout_waveguide( cell, ensure_layer(cell.layout(), cplayer), [middle_point - port_from.width * 0.5 * o_x, port_to.position], port_to.width, )
def common_layout_manhattan_traces(cell, layer1, layer2, layervia, via_cell_placer, path, ex, initiate_with_via=False): """Lays out a manhattan trace, potentially with vias Args: layer1 and layer2 are given to layout.LayerInfo(layer), generally layer2 is on top via_cell_placer: returns a cell when called with via_cell_placer(parent_cell, pya.DPoint origin, width, layer1, layer2, layervia, ex) path: list of tuples containing necessary info ((x, y) or pya.DPoint, layer, width) Returns: path Algorithm places a via when there is a change of layers. To terminate with a via, have the last layer be different than the penultimate one. """ assert isinstance(ex, (pya.DPoint, pya.DVector)) ey = rotate90(ex) first_point, _, first_width = path[0] if initiate_with_via: via_cell_placer(cell, first_point, first_width, layer1, layer2, layervia, ex) points_list = list() widths_list = list() _, previous_layer, _ = path[0] layout = cell.layout() for point, layer, width in path: if isinstance(point, tuple): # point are (x, y) coordinates x, y = point point = x * ex + y * ey else: assert isinstance(point, (pya.DPoint, pya.DVector)) if isinstance(point, pya.DVector): point = pya.DPoint(point) if layer == previous_layer: points_list.append(point) # store points widths_list.append(width) else: # time to place a via and layout points_list.append(point) widths_list.append(width) layout_waveguide( cell, ensure_layer(layout, previous_layer), points_list, widths_list, smooth=True, ) via_cell_placer(cell, point, width, layer1, layer2, layervia, ex) # delete all but the last point del points_list[:-1] del widths_list[:-1] previous_layer = layer # layout last trace if len(points_list) >= 2: layout_waveguide( cell, ensure_layer(layout, previous_layer), points_list, widths_list, smooth=True, ) return path
def append_Z_trace_vertical(path, new_point, height, ex, middle_layer=None, middle_taper=False): """Adds new_point to the path list plus TWO Z or S manhattan interesections. Args: path: list of tuples containing necessary info (pya.DPoint, layer, width) new_point: tuple ((x, y) or pya.DPoint, layer, width) height: y-coordinate of where to place the inner point, from 0 to abs(new_point.y - path.y) ex: orientation of ports middle_layer (optional): layer of middle trace middle_taper (default False): Adds a middle point in the Z-shaped trace attempting to avoid collisions and DRC errors. """ assert len(path) > 0 ey = rotate90(ex) P0, l0, w0 = path[-1] P3, l3, w3 = new_point height = abs(height) # assert height <= abs(P0 * ey - P3 * ey) # Invert sign of height if P3 is below P0 if P3 * ey < P0 * ey: height = -height P1 = P0 + height * ey P2 = P1 * ey * ey + P3 * ex * ex # selecting middle_layer if middle_layer is None: l1, l2 = l0, l3 else: l1 = l2 = middle_layer # lmid defined below # selecting middle widths w1, w2 = w0, w3 if (P2 - P1).norm() <= w1 + w2: # w1 = w2 = min(w1, w2) middle_taper = False # middle taper when points are that close looks weird if w1 < w2: wmid = w1 lmid = l1 else: wmid = w2 lmid = l2 path.append((P1, l1, w1)) # move P2 a little bit to avoid acute corners delta_w = abs(w2 - w1) / 2 if P3 * ey < P0 * ey: delta_w = -delta_w P2 += delta_w * ey Pmid = (P1 + P2) / 2 if (P1 - P2).norm() <= max(w1, w2): if (P3 - P2) * ey > max(w1, w2) * 3: path.append((P2 + ey * max(w1, w2) * 3, l2, w2)) else: path.append((P3 + ey * max(w1, w2) * 0.2, l3, w3)) else: if middle_taper: path.append((Pmid, lmid, wmid)) path.append((P2, l2, w2)) path.append(new_point) return path
def compute_paths_from_clusters(ports_clusters, layer, ex, pitch=None, middle_taper=False, initial_height=0): """ Args: - middle_taper: Adds a middle point in the Z-shaped trace attempting to avoid collisions and DRC errors. provide a pitch for optical waveguides. electrical waveguides are figured out automatically. path: list of tuples containing necessary info (pya.DPoint, layer, width) """ Z = 0 S = 1 ey = rotate90(ex) paths = [] for ports_cluster, orientation in ports_clusters: assert orientation in (Z, S) # start from the lowest height Z trace height = initial_height if orientation == S: ports_iterator = list(iter(ports_cluster)) elif orientation == Z: ports_iterator = list(reversed(ports_cluster)) is_to_top = is_to_bottom = False # check which row is on the top: for port_from, port_to in ports_iterator: if (port_to.position - port_from.position) * ey > 0: is_to_top = True or is_to_top else: is_to_bottom = True or is_to_bottom assert not ( is_to_bottom and is_to_top ), "There must be a line dividing the top and bottom port rows. Maybe you are using the wrong ex argument?" if is_to_top: offset_port_from = max( [port_from.position * ey for port_from, _ in ports_iterator]) else: offset_port_from = min( [port_from.position * ey for port_from, _ in ports_iterator]) paths_cluster = [] for port_from, port_to in ports_iterator: # # Make port_from be the one with largest width # if port_from.width < port_to.width: # port_from, port_to = port_to, port_from P0 = port_from.position # + port_from.direction * port_from.width / 2 P3 = port_to.position # + port_to.direction * port_to.width / 2 if pitch is None: new_pitch = max(port_from.width, port_to.width) * 1.5 else: new_pitch = max(max(port_from.width, port_to.width), pitch) height += new_pitch new_height = height + abs(offset_port_from - P0 * ey) paths_cluster.append( append_Z_trace_vertical( [(P0, layer, port_from.width)], (P3, layer, port_to.width), new_height, ex, middle_taper=middle_taper, )) if orientation == S: paths.extend(paths_cluster) elif orientation == Z: paths.extend(reversed(paths_cluster)) return paths