def dummy(): cmp = Component() xs = [0.0, 10.0, 25.0, 50.0] ys = [0.0, 10.0, 25.0, 50.0] a = 5 xl = min(xs) - a xr = max(xs) + a yb = min(ys) - a yt = max(ys) + a cmp.add_polygon([(xl, yb), (xl, yt), (xr, yt), (xr, yb)], LAYER.WG) for i, y in enumerate(ys): p0 = (xl, y) p1 = (xr, y) cmp.add_port(name="W{}".format(i), midpoint=p0, orientation=180, width=0.5) cmp.add_port(name="E{}".format(i), midpoint=p1, orientation=0, width=0.5) for i, x in enumerate(xs): p0 = (x, yb) p1 = (x, yt) cmp.add_port(name="S{}".format(i), midpoint=p0, orientation=270, width=0.5) cmp.add_port(name="N{}".format(i), midpoint=p1, orientation=90, width=0.5) return cmp
def corner(width=WIRE_WIDTH, radius=None, layer=LAYER.M3): """ 90 degrees electrical bend Args: width: wire width radius ignore (passed for consistency with other types of bends) layer: layer .. plot:: :include-source: import pp c = pp.c.corner(width=10., layer=pp.LAYER.M3) pp.plotgds(c) """ c = Component() a = width / 2 xpts = [-a, a, a, -a] ypts = [-a, -a, a, a] c.add_polygon([xpts, ypts], layer=layer) c.add_port(name="W0", midpoint=(-a, 0), width=width, orientation=180) c.add_port(name="N0", midpoint=(0, a), width=width, orientation=90) c.info["length"] = width return c
def L( width: Union[int, float] = 1, size: Tuple[int, int] = (10, 20), layer: Tuple[int, int] = LAYER.M3, ) -> Component: """Generates an 'L' geometry with ports on both ends. Based on phidl. Args: width: of the line size: length and height of the base layer: .. plot:: :include-source: import pp c = pp.c.L(width1=1, size=(10, 20), layer=pp.LAYER.M3) pp.plotgds(c) """ D = Component() w = width / 2 s1, s2 = size points = [(-w, -w), (s1, -w), (s1, w), (w, w), (w, s2), (-w, s2), (-w, -w)] D.add_polygon(points, layer=layer) D.add_port(name=1, midpoint=(0, s2), width=width, orientation=90) D.add_port(name=2, midpoint=(s1, 0), width=width, orientation=0) return D
def add_keepout(component, target_layers, keepout_layers, margin=2.0) -> Component: """Adds keepout after Looking up all polygons in a cell. You can also use add_padding Args: component target_layers: list of layers to read keepout_layers: list of layers to add keepout margin: offset from tareget to keepout_layers """ c = Component(f"{component.name}_ko") c << component for layer in target_layers: polygons = component.get_polygons(by_spec=layer) if polygons: for ko_layer in keepout_layers: ko_layer = _parse_layer(ko_layer) polygon_keepout = [ polygon_grow(polygon, margin) for polygon in polygons ] c.add_polygon(polygon_keepout, ko_layer) return c
def connect_electrical_shortest_path(port1, port2): """connects two ports with a polygon that takes the shortest path""" points = [port1.midpoint, port2.midpoint] name = f"zz_conn_{hash_points(points)}" c = Component(name=name) layer = port1.layer p1x0 = port1.endpoints[0][0] p1y0 = port1.endpoints[0][1] p1x1 = port1.endpoints[1][0] p1y1 = port1.endpoints[1][1] p2x0 = port2.endpoints[0][0] p2y0 = port2.endpoints[0][1] p2x1 = port2.endpoints[1][0] p2y1 = port2.endpoints[1][1] if port1.orientation in [90, 270]: c.add_polygon( ([(p1x1, p1y0), (p1x0, p1y1), (p2x1, p2y1), (p2x0, p2y0)]), layer=layer ) else: c.add_polygon( ([(p1x0, p1y1), (p1x1, p1y0), (p2x1, p2y1), (p2x0, p2y0)]), layer=layer ) return c.ref()
def _add_pin_square( component: Component, port: Port, pin_length: float = 0.1, layer: Tuple[int, int] = LAYER.PORT, label_layer: Optional[Tuple[int, int]] = LAYER.PORT, port_margin: float = 0.0, ) -> None: """Add half out pin to a component. Args: component: port: Port pin_length: length of the pin marker for the port layer: for the pin marker label_layer: for the label port_margin: margin to port edge .. code:: _______________ | | | | | | ||| | ||| | | | | __ | |_______________| __ """ p = port a = p.orientation ca = np.cos(a * np.pi / 180) sa = np.sin(a * np.pi / 180) rot_mat = np.array([[ca, -sa], [sa, ca]]) d = p.width / 2 + port_margin dbot = np.array([pin_length / 2, -d]) dtop = np.array([pin_length / 2, d]) dbotin = np.array([-pin_length / 2, -d]) dtopin = np.array([-pin_length / 2, +d]) p0 = p.position + _rotate(dbot, rot_mat) p1 = p.position + _rotate(dtop, rot_mat) ptopin = p.position + _rotate(dtopin, rot_mat) pbotin = p.position + _rotate(dbotin, rot_mat) polygon = [p0, p1, ptopin, pbotin] component.add_polygon(polygon, layer=layer) if label_layer: component.add_label( text=str(p.name), position=p.midpoint, layer=label_layer, )
def tlm( width: float = 11.0, height: float = 11.0, layers: List[Tuple[int, int]] = [LAYER.M1, LAYER.M2, LAYER.M3], vias: List[Any] = [via2, via3], ) -> Component: """ Rectangular transition thru metal layers Args: name: component name width, height: rectangle parameters layers: layers on which to draw rectangles vias: vias to use to fill the rectangles Returns <Component> """ # assert len(layers) - 1 == len(vias), "tlm: There should be N layers for N-1 vias" a = width / 2 b = height / 2 rect_pts = [(-a, -b), (a, -b), (a, b), (-a, b)] c = Component() # Add metal rectangles for layer in layers: c.add_polygon(rect_pts, layer=layer) # Add vias for via in vias: via = via() if callable(via) else via w = via.info["width"] h = via.info["height"] g = via.info["clearance"] period = via.info["period"] nb_vias_x = (width - w - 2 * g) / period + 1 nb_vias_y = (height - h - 2 * g) / period + 1 nb_vias_x = int(floor(nb_vias_x)) nb_vias_y = int(floor(nb_vias_y)) cw = (width - (nb_vias_x - 1) * period - w) / 2 ch = (height - (nb_vias_y - 1) * period - h) / 2 x0 = -a + cw + w / 2 y0 = -b + ch + h / 2 for i in range(nb_vias_x): for j in range(nb_vias_y): c.add(via.ref(position=(x0 + i * period, y0 + j * period))) return c
def wg3(length=3, width=0.5): from pp.component import Component c = Component("waveguide") w = width / 2 layer = (1, 0) c.add_polygon([(0, -w), (length, -w), (length, w), (0, w)], layer=layer) c.add_port(name="W0", midpoint=[0, 0], width=width, orientation=180, layer=layer) c.add_port(name="E0", midpoint=[length, 0], width=width, orientation=0, layer=layer) return c
def bbox(bbox=[(-1, -1), (3, 4)], layer=(1, 0)): """ Creates a bounding box rectangle from coordinates, to allow creation of a rectangle bounding box directly form another shape. Args: bbox: Coordinates of the box [(x1, y1), (x2, y2)]. layer: """ D = Component(name="bbox") (a, b), (c, d) = bbox points = ((a, b), (c, b), (c, d), (a, d)) D.add_polygon(points, layer=layer) return D
def waveguide( length: float = 10.0, width: float = 0.5, layer: Tuple[int, int] = pp.LAYER.WG, layers_cladding: Optional[Iterable[Tuple[int, int]]] = None, cladding_offset: float = pp.conf.tech.cladding_offset, ) -> Component: """Straight waveguide Args: length: in X direction width: in Y direction layer layers_cladding cladding_offset .. plot:: :include-source: import pp c = pp.c.waveguide(length=10, width=0.5) pp.plotgds(c) """ c = Component() w = width / 2 c.add_polygon([(0, -w), (length, -w), (length, w), (0, w)], layer=layer) wc = w + cladding_offset if layers_cladding: for layer_cladding in layers_cladding: c.add_polygon([(0, -wc), (length, -wc), (length, wc), (0, wc)], layer=layer_cladding) c.add_port(name="W0", midpoint=[0, 0], width=width, orientation=180, layer=layer) c.add_port(name="E0", midpoint=[length, 0], width=width, orientation=0, layer=layer) c.width = width c.length = length return c
def via(width=0.7, height=0.7, period=2.0, clearance=1.0, layer=LAYER.VIA1): """Rectangular via""" c = Component() c.info["period"] = period c.info["clearance"] = clearance c.info["width"] = width c.info["height"] = height a = width / 2 b = height / 2 c.add_polygon([(-a, -b), (a, -b), (a, b), (-a, b)], layer=layer) return c
def _add_pin_square_inside( component: Component, port: Port, pin_length: float = 0.1, layer: Tuple[int, int] = LAYER.PORT, label_layer: Tuple[int, int] = LAYER.TEXT, ) -> None: """Add square pin towards the inside of the port Args: component: port: Port pin_length: length of the pin marker for the port layer: for the pin marker label_layer: for the label port_margin: margin to port edge .. code:: _______________ | | | | | | || | || | | | | __ | |_______________| """ p = port a = p.orientation ca = np.cos(a * np.pi / 180) sa = np.sin(a * np.pi / 180) rot_mat = np.array([[ca, -sa], [sa, ca]]) d = p.width / 2 dbot = np.array([0, -d]) dtop = np.array([0, d]) dbotin = np.array([-pin_length, -d]) dtopin = np.array([-pin_length, +d]) p0 = p.position + _rotate(dbot, rot_mat) p1 = p.position + _rotate(dtop, rot_mat) ptopin = p.position + _rotate(dtopin, rot_mat) pbotin = p.position + _rotate(dbotin, rot_mat) polygon = [p0, p1, ptopin, pbotin] component.add_polygon(polygon, layer=layer)
def _add_padding(component, x=50, y=50, layers=(LAYER.PADDING), suffix="p"): """Adds padding layers to component. This is just an example. For the real function see pp.add_padding. """ c = Component(name=f"{component.name}_{suffix}") c << component points = [ [c.xmin - x, c.ymin - y], [c.xmax + x, c.ymin - y], [c.xmax + x, c.ymax + y], [c.xmin - x, c.ymax + y], ] for layer in layers: c.add_polygon(points, layer=layer) return c
def coupler_straight( length: float = 10.0, width: float = 0.5, gap: float = 0.27, layer: Tuple[int, int] = pp.LAYER.WG, layers_cladding: List[Tuple[int, int]] = [pp.LAYER.WGCLAD], cladding_offset: float = 3.0, ) -> Component: """ straight coupled waveguides. Two multimode ports .. plot:: :include-source: import pp c = pp.c.coupler_straight() pp.plotgds(c) """ c = Component() # Top path c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer) y = width + gap # Bottom path c.add_polygon([(0, y), (length, y), (length, width + y), (0, width + y)], layer=layer) # One multimode port on each side port_w = width * 2 + gap c.add_port(name="W0", midpoint=[0, port_w / 2], width=port_w, orientation=180) c.add_port(name="E0", midpoint=[length, port_w / 2], width=port_w, orientation=0) c.width = width c.length = length # cladding ymax = 2 * width + gap + cladding_offset for layer_cladding in layers_cladding: c.add_polygon( [ (0, -cladding_offset), (length, -cladding_offset), (length, ymax), (0, ymax), ], layer=layer_cladding, ) return c
def import_phidl_component(component: Device, **kwargs) -> Component: """ returns a gdsfactory Component from a phidl Device or function """ D = call_if_func(component, **kwargs) D_copy = Component(name=D._internal_name) D_copy.info = copy.deepcopy(D.info) for ref in D.references: new_ref = ComponentReference( device=ref.parent, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) new_ref.owner = D_copy D_copy.add(new_ref) for alias_name, alias_ref in D.aliases.items(): if alias_ref == ref: D_copy.aliases[alias_name] = new_ref for p in D.ports.values(): D_copy.add_port( port=Port( name=p.name, midpoint=p.midpoint, width=p.width, orientation=p.orientation, parent=p.parent, ) ) for poly in D.polygons: D_copy.add_polygon(poly) for label in D.labels: D_copy.add_label( text=label.text, position=label.position, layer=(label.layer, label.texttype), ) return D_copy
def _add_pin_triangle( component: Component, port: Port, layer: Tuple[int, int] = LAYER.PORT, label_layer: Tuple[int, int] = LAYER.TEXT, ) -> None: """Add triangle pin with a right angle, pointing out of the port Args: component: port: Port layer: for the pin marker label_layer: for the label """ p = port a = p.orientation ca = np.cos(a * np.pi / 180) sa = np.sin(a * np.pi / 180) rot_mat = np.array([[ca, -sa], [sa, ca]]) d = p.width / 2 dbot = np.array([0, -d]) dtop = np.array([0, d]) dtip = np.array([d, 0]) p0 = p.position + _rotate(dbot, rot_mat) p1 = p.position + _rotate(dtop, rot_mat) ptip = p.position + _rotate(dtip, rot_mat) polygon = [p0, p1, ptip] component.add_label( text=p.name, position=p.midpoint, layer=label_layer, ) component.add_polygon(polygon, layer=layer)
def connect_electrical_shortest_path(port1, port2): """connects two ports with a polygon that takes the shortest path""" c = Component(name="zz_conn_{}".format(uuid.uuid4())) layer = port1.layer p1x0 = port1.endpoints[0][0] p1y0 = port1.endpoints[0][1] p1x1 = port1.endpoints[1][0] p1y1 = port1.endpoints[1][1] p2x0 = port2.endpoints[0][0] p2y0 = port2.endpoints[0][1] p2x1 = port2.endpoints[1][0] p2y1 = port2.endpoints[1][1] if port1.orientation in [90, 270]: c.add_polygon(([(p1x1, p1y0), (p1x0, p1y1), (p2x1, p2y1), (p2x0, p2y0)]), layer=layer) else: c.add_polygon(([(p1x0, p1y1), (p1x1, p1y0), (p2x1, p2y1), (p2x0, p2y0)]), layer=layer) return c.ref()
def _add_outline( component: Component, reference: Optional[ComponentReference] = None, layer: Tuple[int, int] = LAYER.DEVREC, **kwargs, ) -> None: """Adds devices outline bounding box in layer. Args: component: where to add the markers reference: to read outline from layer: to add padding default: default padding top: North padding bottom right left """ c = reference or component if hasattr(component, "parent"): component = component.parent points = get_padding_points(component=c, default=0, **kwargs) component.add_polygon(points, layer=layer)
def waveguide_pin( length: float = 10.0, width: float = 0.5, width_i: float = 0.0, width_p: float = 1.0, width_n: float = 1.0, width_pp: float = 1.0, width_np: float = 1.0, width_ppp: float = 1.0, width_npp: float = 1.0, layer_p: Tuple[int, int] = LAYER.P, layer_n: Tuple[int, int] = LAYER.N, layer_pp: Tuple[int, int] = LAYER.Pp, layer_np: Tuple[int, int] = LAYER.Np, layer_ppp: Tuple[int, int] = LAYER.Ppp, layer_npp: Tuple[int, int] = LAYER.Npp, waveguide_factory: Callable = waveguide, ) -> Component: """PN doped waveguide .. code:: |<------width------>| ____________________ | | | | ___________________| | | |__________________________| | | | P++ P+ P | I | N N+ N++ | __________________________________________________________________| | |width_i| width_n | width_np | width_npp | 0 oi on onp onpp .. plot:: :include-source: import pp c = pp.c.waveguide_pin(length=10) pp.plotgds(c) """ c = Component() w = c << waveguide_factory(length=length, width=width) c.absorb(w) oi = width_i / 2 on = oi + width_n onp = oi + width_n + width_np onpp = oi + width_n + width_np + width_npp # N doping c.add_polygon([(0, oi), (length, oi), (length, onpp), (0, onpp)], layer=layer_n) if layer_np: c.add_polygon([(0, on), (length, on), (length, onpp), (0, onpp)], layer=layer_np) if layer_npp: c.add_polygon([(0, onp), (length, onp), (length, onpp), (0, onpp)], layer=layer_npp) oi = -width_i / 2 op = oi - width_p opp = oi - width_p - width_pp oppp = oi - width_p - width_pp - width_ppp # P doping c.add_polygon([(0, oi), (length, oi), (length, oppp), (0, oppp)], layer=layer_p) if layer_pp: c.add_polygon([(0, op), (length, op), (length, oppp), (0, oppp)], layer=layer_pp) if layer_ppp: c.add_polygon([(0, opp), (length, opp), (length, oppp), (0, oppp)], layer=layer_ppp) return c
def wg_heater_connector( heater_ports: List[Port], metal_width: float = 10.0, tlm_layers: List[Tuple[int, int]] = [ LAYER.VIA1, LAYER.M1, LAYER.VIA2, LAYER.M2, LAYER.VIA3, LAYER.M3, ], ) -> Component: """ Connects together a pair of wg heaters and connect to a M3 port """ cmp = Component() assert len(heater_ports) == 2 assert (heater_ports[0].orientation == heater_ports[1].orientation ), "both ports should be facing in the same direction" angle = heater_ports[0].orientation angle = angle % 360 assert angle in [0, 180], "angle should be 0 or 180, got {}".format(angle) dx = 0.0 dy = 0.0 angle_to_dps = {0: [(-dx, -dy), (-dx, dy)], 180: [(dx, -dy), (dx, dy)]} ports = heater_ports hw = heater_ports[0].width if angle in [0, 180]: ports.sort(key=lambda p: p.y) else: ports.sort(key=lambda p: p.x) _heater_to_metal = tlm(width=0.5, height=0.5, layers=tlm_layers, vias=[]) tlm_positions = [] for port, dp in zip(ports, angle_to_dps[angle]): # Extend heater p = port.midpoint # Add via/metal transitions tlm_pos = p + dp hm = _heater_to_metal.ref(position=tlm_pos) tlm_positions += [tlm_pos] cmp.add(hm) ss = 1 if angle == 0 else -1 # Connect both sides with top metal edge_metal_piece_width = 7.0 x = ss * edge_metal_piece_width / 2 top_metal_layer = tlm_layers[-1] cmp.add_polygon( line( tlm_positions[0] + (x, -hw / 2), tlm_positions[1] + (x, hw / 2), edge_metal_piece_width, ), layer=top_metal_layer, ) # Add metal port cmp.add_port( name="0", midpoint=0.5 * sum(tlm_positions) + (ss * edge_metal_piece_width / 2, 0), orientation=angle, width=metal_width, layer=top_metal_layer, port_type="dc", ) return cmp
def import_gds( gdspath: Union[str, Path], cellname: None = None, flatten: bool = False, snap_to_grid_nm: Optional[int] = None, ) -> Component: """Returns a Componenent from a GDS file. Adapted from phidl/geometry.py Args: gdspath: path of GDS file cellname: cell of the name to import (None) imports top cell flatten: if True returns flattened (no hierarchy) snap_to_grid_nm: snap to different nm grid (does not snap if False) """ gdsii_lib = gdspy.GdsLibrary() gdsii_lib.read_gds(gdspath) top_level_cells = gdsii_lib.top_level() cellnames = [c.name for c in top_level_cells] if cellname is not None: if cellname not in gdsii_lib.cells: raise ValueError( f"cell {cellname} is not in file {gdspath} with cells {cellnames}" ) topcell = gdsii_lib.cells[cellname] elif cellname is None and len(top_level_cells) == 1: topcell = top_level_cells[0] elif cellname is None and len(top_level_cells) > 1: raise ValueError( f"import_gds() There are multiple top-level cells in {gdspath}, " f"you must specify `cellname` to select of one of them among {cellnames}" ) if not flatten: D_list = [] c2dmap = {} for cell in gdsii_lib.cells.values(): D = Component(name=cell.name) D.polygons = cell.polygons D.references = cell.references D.name = cell.name for label in cell.labels: rotation = label.rotation if rotation is None: rotation = 0 label_ref = D.add_label( text=label.text, position=np.asfarray(label.position), magnification=label.magnification, rotation=rotation * 180 / np.pi, layer=(label.layer, label.texttype), ) label_ref.anchor = label.anchor c2dmap.update({cell: D}) D_list += [D] for D in D_list: # First convert each reference so it points to the right Device converted_references = [] for e in D.references: ref_device = c2dmap[e.ref_cell] if isinstance(e, gdspy.CellReference): dr = DeviceReference( device=ref_device, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) elif isinstance(e, gdspy.CellArray): dr = CellArray( device=ref_device, columns=e.columns, rows=e.rows, spacing=e.spacing, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) D.references = converted_references # Next convert each Polygon # temp_polygons = list(D.polygons) # D.polygons = [] # for p in temp_polygons: # D.add_polygon(p) # Next convert each Polygon temp_polygons = list(D.polygons) D.polygons = [] for p in temp_polygons: if snap_to_grid_nm: points_on_grid = pp.drc.snap_to_grid( p.polygons[0], nm=snap_to_grid_nm ) p = gdspy.Polygon( points_on_grid, layer=p.layers[0], datatype=p.datatypes[0] ) D.add_polygon(p) topdevice = c2dmap[topcell] return topdevice if flatten: D = pp.Component() polygons = topcell.get_polygons(by_spec=True) for layer_in_gds, polys in polygons.items(): D.add_polygon(polys, layer=layer_in_gds) return D
def gdsdiff(cellA, cellB): """Compare two Components. Args: CellA: Component or path to gds file CellB: Component or path to gds file Returns: Component with both cells (xor, common and diffs) """ if isinstance(cellA, pathlib.PosixPath): cellA = str(cellA) if isinstance(cellB, pathlib.PosixPath): cellB = str(cellB) if isinstance(cellA, str): cellA = import_gds(cellA, flatten=True) if isinstance(cellB, str): cellB = import_gds(cellB, flatten=True) layers = set() layers.update(cellA.get_layers()) layers.update(cellB.get_layers()) top = Component(name="TOP") diff = Component(name="xor") common = Component(name="common") old_only = Component(name="only_in_old") new_only = Component(name="only_in_new") cellA.name = "old" cellB.name = "new" top << cellA top << cellB for layer in layers: A = get_polygons_on_layer(cellA, layer) B = get_polygons_on_layer(cellB, layer) if A is None and B is None: continue elif B is None: diff.add_polygon(A, layer) continue elif A is None: diff.add_polygon(B, layer) continue # Common bits common_AB = boolean(A, B, operation="and", precision=0.001) # Bits in either A or B either_AB = boolean(A, B, operation="xor", precision=0.001) # Bits only in A only_in_A = boolean(A, either_AB, operation="and", precision=0.001) # Bits only in B only_in_B = boolean(B, either_AB, operation="and", precision=0.001) if common_AB is not None: common.add_polygon(common_AB, layer) if only_in_A is not None: old_only.add_polygon(only_in_A, layer) if only_in_B is not None: new_only.add_polygon(only_in_B, layer) top << diff top << common top << old_only top << new_only return top