def L( width: Union[int, float] = 1, size: Tuple[int, int] = (10, 20), layer: Tuple[int, int] = LAYER.M3, port_type: str = "electrical", ) -> 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: """ 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="e1", midpoint=(0, s2), width=width, orientation=90, port_type=port_type) D.add_port(name="e2", midpoint=(s1, 0), width=width, orientation=0, port_type=port_type) return D
def extend_port(port: Port, length: float, layer: Optional[Layer] = None) -> Component: """Returns a straight extension component out of a port. Args: port: port to extend length: extension length layer: for the straight section """ c = Component() layer = layer or port.layer # Generate a port extension p_start = port.midpoint angle = port.angle p_end = move_polar_rad_copy(p_start, angle * DEG2RAD, length) w = port.width _line = line(p_start, p_end, w) c.add_polygon(_line, layer=layer) c.add_port(name="original", port=port) port_settings = port.settings.copy() port_settings.update(midpoint=p_end) c.add_port(**port_settings) return c
def add_keepout( component: Component, target_layers: Layers, keepout_layers: Layers, margin: float = 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 ramp( length: float = 10.0, width1: float = 5.0, width2: Optional[float] = 8.0, layer: Layer = (1, 0), ) -> Component: """Return a ramp component. Based on phidl. Args: length: Length of the ramp section. width1: Width of the start of the ramp section. width2: Width of the end of the ramp section (defaults to width1). layer: Specific layer to put polygon geometry on. """ if width2 is None: width2 = width1 xpts = [0, length, length, 0] ypts = [width1, width2, 0, 0] c = Component() c.add_polygon([xpts, ypts], layer=layer) c.add_port(name="o1", midpoint=[0, width1 / 2], width=width1, orientation=180) c.add_port(name="o2", midpoint=[length, width2 / 2], width=width2, orientation=0) return c
def C( width: float = 1.0, size: Tuple[float, float] = (10.0, 20.0), layer: Tuple[int, int] = LAYER.M3, ) -> Component: """Generates a 'C' geometry with ports on both ends. Adapted from phidl Args: width: of the line size: length and height of the base layer: """ D = Component() w = width / 2 s1, s2 = size points = [ (-w, -w), (s1, -w), (s1, w), (w, w), (w, s2 - w), (s1, s2 - w), (s1, s2 + w), (-w, s2 + w), (-w, -w), ] D.add_polygon(points, layer=layer) D.add_port(name="o1", midpoint=(s1, s2), width=width, orientation=0) D.add_port(name="o2", midpoint=(s1, 0), width=width, orientation=0) return D
def _sample_route_side() -> Component: c = 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 c.add_polygon([(xl, yb), (xl, yt), (xr, yt), (xr, yb)], LAYER.WG) for i, y in enumerate(ys): p0 = (xl, y) p1 = (xr, y) c.add_port(name="W{}".format(i), midpoint=p0, orientation=180, width=0.5) c.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) c.add_port(name="S{}".format(i), midpoint=p0, orientation=270, width=0.5) c.add_port(name="N{}".format(i), midpoint=p1, orientation=90, width=0.5) return c
def from_np( ndarray: np.ndarray, nm_per_pixel: int = 20, layer: Tuple[int, int] = (1, 0), threshold: float = 0.99, ) -> Component: """Returns Component from a np.ndarray. Extracts contours skimage.measure.find_contours using `threshold`. Args: ndarray: 2D ndarray representing the device layout nm_per_pixel: scale_factor layer: layer tuple to output gds threshold: value along which to find contours in the array """ c = Component() d = Component() ndarray = np.pad(ndarray, 2) contours = measure.find_contours(ndarray, threshold) assert len(contours) > 0, ( f"no contours found for threshold = {threshold}, maybe you can reduce the" " threshold") for contour in contours: area = compute_area_signed(contour) points = contour * 1e-3 * nm_per_pixel if area < 0: c.add_polygon(points, layer=layer) else: d.add_polygon(points, layer=layer) c = boolean(c, d, operation="not", layer=layer) return c
def crossing_arm( width: float = 0.5, r1: float = 3.0, r2: float = 1.1, w: float = 1.2, L: float = 3.4, ) -> Component: """arm of a crossing""" c = Component() _ellipse = ellipse(radii=(r1, r2), layer=LAYER.SLAB150).ref() c.add(_ellipse) c.absorb(_ellipse) a = np.round(L + w / 2, 3) h = width / 2 taper_pts = [ (-a, h), (-w / 2, w / 2), (w / 2, w / 2), (a, h), (a, -h), (w / 2, -w / 2), (-w / 2, -w / 2), (-a, -h), ] c.add_polygon(taper_pts, layer=LAYER.WG) c.add_port( name="o1", midpoint=(-a, 0), orientation=180, width=width, layer=LAYER.WG ) c.add_port(name="o2", midpoint=(a, 0), orientation=0, width=width, layer=LAYER.WG) return c
def add_padding_to_size( component: Component, layers: Tuple[Layer, ...] = (LAYER.PADDING, ), xsize: Optional[float] = None, ysize: Optional[float] = None, left: float = 0, bottom: float = 0, ) -> Component: """Returns component with padding layers on each side. New size is multiple of grid size """ c = component top = abs(ysize - component.ysize) if ysize else 0 right = abs(xsize - component.xsize) if xsize else 0 points = [ [c.xmin - left, c.ymin - bottom], [c.xmax + right, c.ymin - bottom], [c.xmax + right, c.ymax + top], [c.xmin - left, c.ymax + top], ] for layer in layers: component.add_polygon(points, layer=layer) return component
def triangle( x: float = 10, xtop: float = 0, y: float = 20, ybot: float = 0, layer: Layer = (1, 0), ) -> Component: r""" Args: x: base xsize xtop: top xsize y: ysize ybot: bottom ysize layer: .. code:: xtop _ | \ | \ | \ y| \ | \ | \ |______|ybot x """ c = Component() points = [[0, 0], [x, 0], [x, ybot], [xtop, y], [0, y]] c.add_polygon(points, layer=layer) return c
def add_padding( component: Component, layers: Tuple[Layer, ...] = (LAYER.PADDING, ), **kwargs, ) -> Component: """Adds padding layers to a component inside a container. Returns the same ports as the component. Args: component layers: list of layers new_component: returns a new component if True keyword Args: default: default padding top: north padding bottom: south padding right: east padding left: west padding """ points = get_padding_points(component, **kwargs) for layer in layers: component.add_polygon(points, layer=layer) return component
def add_padding_container( component: Component, layers: Tuple[Layer, ...] = (LAYER.PADDING, ), **kwargs, ) -> Component: """Returns new component with padding added. Args: component layers: list of layers default: default padding top: north padding bottom: south padding right: east padding left: west padding """ c = Component() c.component = component cref = c << component points = get_padding_points(component, **kwargs) for layer in layers: c.add_polygon(points, layer=layer) c.ports = cref.ports c.copy_child_info(component) return c
def pixel_array( pixels: str = character_a, pixel_size: float = 10.0, layer: Tuple[int, int] = LAYER.M1, ) -> Component: """Returns a pixel component from a string representing the pixels. Args: pixels: string representing the pixels pixel_size: widht/height for each pixel layer: layer for each pixel """ component = Component() lines = [line for line in pixels.split("\n") if len(line) > 0] lines.reverse() j = 0 i = 0 i_max = 0 a = pixel_size for line in lines: i = 0 for c in line: if c in ["X", "1"]: p0 = np.array([i * a, j * a]) pixel = [p0 + p for p in [(0, 0), (a, 0), (a, a), (0, a)]] component.add_polygon(pixel, layer=layer) i += 1 i_max = max(i_max, i) j += 1 return component
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 wg(length: int = 3, width: float = 0.5) -> Component: from gdsfactory.component import Component c = Component("straight") w = width / 2 layer = (1, 0) c.add_polygon([(0, -w), (length, -w), (length, w), (0, w)], layer=layer) c.add_port(name="o1", midpoint=[0, 0], width=width, orientation=180, layer=layer) c.add_port(name="o2", midpoint=[length, 0], width=width, orientation=0, layer=layer) return c
def add_pin_square_inside( component: Component, port: Port, pin_length: float = 0.1, layer: Tuple[int, int] = LAYER.PORT, layer_label: Optional[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 layer_label: for the label .. 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) if layer_label: component.add_label( text=str(p.name), position=p.midpoint, layer=layer_label, )
def wg(length: int = 3, layer: Tuple[int, int] = (1, 0)) -> Component: """Dummy component for testing.""" from gdsfactory.component import Component c = Component("straight") width = 0.5 w = width / 2 c.add_polygon([(0, -w), (length, -w), (length, w), (0, w)], layer=layer) c.add_port(name="o1", midpoint=[0, 0], width=width, orientation=180, layer=layer) c.add_port(name="o2", midpoint=[length, 0], width=width, orientation=0, layer=layer) return c
def bend_s(size: Float2 = (10.0, 2.0), nb_points: int = 99, with_cladding_box: bool = True, cross_section: CrossSectionFactory = strip, **kwargs) -> Component: """S bend with bezier curve stores min_bend_radius property in self.info['min_bend_radius'] min_bend_radius depends on height and length Args: size: in x and y direction nb_points: number of points with_cladding_box: square bounding box to avoid DRC errors cross_section: function kwargs: cross_section settings """ dx, dy = size x = cross_section(**kwargs) width = x.info["width"] layer = x.info["layer"] c = Component() bend = bezier( width=width, control_points=[(0, 0), (dx / 2, 0), (dx / 2, dy), (dx, dy)], npoints=nb_points, layer=layer, ) bend_ref = c << bend c.add_ports(bend_ref.ports) c.copy_child_info(bend) c.info.start_angle = bend.info.start_angle c.info.end_angle = bend.info.end_angle c.info.length = bend.info.length c.info.min_bend_radius = bend.info.min_bend_radius if with_cladding_box and x.info["layers_cladding"]: layers_cladding = x.info["layers_cladding"] cladding_offset = x.info["cladding_offset"] points = get_padding_points( component=c, default=0, bottom=cladding_offset, top=cladding_offset, ) for layer in layers_cladding or []: c.add_polygon(points, layer=layer) auto_rename_ports(c) return c
def bend_circular(angle: int = 90, npoints: int = 720, with_cladding_box: bool = True, cross_section: CrossSectionOrFactory = strip, **kwargs) -> Component: """Returns a radial arc. Args: angle: angle of arc (degrees) npoints: number of points with_cladding_box: square in layers_cladding to remove DRC cross_section: kwargs: cross_section settings .. code:: o2 | / / / o1_____/ """ x = cross_section(**kwargs) if callable(cross_section) else cross_section radius = x.info["radius"] p = arc(radius=radius, angle=angle, npoints=npoints) c = Component() path = extrude(p, x) ref = c << path c.add_ports(ref.ports) c.info.length = snap_to_grid(p.length()) c.info.dy = float(abs(p.points[0][0] - p.points[-1][0])) c.info.radius = float(radius) if with_cladding_box and x.info["layers_cladding"]: layers_cladding = x.info["layers_cladding"] cladding_offset = x.info["cladding_offset"] top = cladding_offset if angle == 180 else 0 points = get_padding_points( component=c, default=0, bottom=cladding_offset, right=cladding_offset, top=top, ) for layer in layers_cladding or []: c.add_polygon(points, layer=layer) c.absorb(ref) return c
def demo(length: int = 3, wg_width: float = 0.5) -> Component: """Demo Dummy cell""" c = Component() w = length h = wg_width points = [ [-w / 2.0, -h / 2.0], [-w / 2.0, h / 2], [w / 2, h / 2], [w / 2, -h / 2.0], ] c.add_polygon(points) return c
def via( size: Tuple[float, float] = (0.7, 0.7), spacing: Tuple[float, float] = (2.0, 2.0), enclosure: float = 1.0, layer: Tuple[int, int] = LAYER.VIAC, layers_cladding: Optional[Tuple[Tuple[int, int], ...]] = None, cladding_offset: float = 0, ) -> Component: """Rectangular via. Defaults to a square via. Args: size: in x, y direction spacing: pitch_x, pitch_y enclosure: inclusion of via layer: via layer layers_cladding: cladding_offset .. code:: enclosure _________________________________________ |<---> | | size[0] | | <-----> | | ______ ______ | | | | | | | | | | | | size[1] | | |______| |______| | | <-------------> | | spacing[0] | |_______________________________________| """ c = Component() c.info["spacing"] = spacing c.info["enclosure"] = enclosure c.info["size"] = size width, height = size a = width / 2 b = height / 2 c.add_polygon([(-a, -b), (a, -b), (a, b), (-a, b)], layer=layer) layers_cladding = layers_cladding or [] a = (width + cladding_offset) / 2 b = (height + cladding_offset) / 2 for layer in layers_cladding: c.add_polygon([(-a, -b), (a, -b), (a, b), (-a, b)], layer=layer) return c
def crossing_arm( width: float = 0.5, r1: float = 3.0, r2: float = 1.1, w: float = 1.2, L: float = 3.4, layer_wg: Layer = LAYER.WG, layer_slab: Layer = LAYER.SLAB150, cross_section: CrossSectionFactory = strip, ) -> Component: """arm of a crossing""" c = Component() c << ellipse(radii=(r1, r2), layer=layer_slab) xs = cross_section() a = np.round(L + w / 2, 3) h = width / 2 taper_pts = [ (-a, h), (-w / 2, w / 2), (w / 2, w / 2), (a, h), (a, -h), (w / 2, -w / 2), (-w / 2, -w / 2), (-a, -h), ] c.add_polygon(taper_pts, layer=layer_wg) c.add_port( name="o1", midpoint=(-a, 0), orientation=180, width=width, layer=layer_wg, cross_section=xs, ) c.add_port( name="o2", midpoint=(a, 0), orientation=0, width=width, layer=layer_wg, cross_section=xs, ) return c
def copy(D: Component, prefix: str = "", suffix: str = "_copy", cache: bool = True) -> Component: """returns a deep copy of a Component. based on phidl.geometry with CellArray support """ D_copy = Component(name=f"{prefix}{D.name}{suffix}") D_copy.info = python_copy.deepcopy(D.info) for ref in D.references: if isinstance(ref, DeviceReference): new_ref = ComponentReference( ref.parent, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) new_ref.owner = D_copy elif isinstance(ref, gdspy.CellArray): new_ref = CellArray( device=ref.parent, columns=ref.columns, rows=ref.rows, spacing=ref.spacing, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) 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 port in D.ports.values(): D_copy.add_port(port=port) 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), ) if cache: D_copy = avoid_duplicated_cells(D_copy) return D_copy
def union( component: Component, by_layer: bool = False, precision: float = 1e-4, join_first: bool = True, max_points: int = 4000, layer: Layer = (1, 0), ) -> Component: """Creates an inverted version of the input shapes with an additional border around the edges. adapted from phidl.geometry.invert Args: D: Component(/Reference), list of Component(/Reference), or Polygon A Component containing the polygons to perform union on. by_Layer: performs the union operation layer-wise so each layer can be individually combined. precision: Desired precision for rounding vertex coordinates. join_first: before offsetting to avoid unnecessary joins in adjacent polygon max_points: The maximum number of vertices within the resulting polygon. layer : Specific layer to put polygon geometry on. Returns Component containing the union of the polygons """ U = Component() if by_layer: all_polygons = component.get_polygons(by_spec=True) for layer, polygons in all_polygons.items(): unioned_polygons = pg._union_polygons(polygons, precision=precision, max_points=max_points) U.add_polygon(unioned_polygons, layer=layer) else: all_polygons = component.get_polygons(by_spec=False) unioned_polygons = pg._union_polygons(all_polygons, precision=precision, max_points=max_points) U.add_polygon(unioned_polygons, layer=layer) return U
def circle( radius: float = 10.0, angle_resolution: float = 2.5, layer: Tuple[int, int] = gf.LAYER.WG, ) -> Component: """Generate a circle geometry. Args: radius: of the circle. angle_resolution: number of degrees per point. layer: layer. """ c = Component() t = np.linspace(0, 360, int(360 / angle_resolution) + 1) * pi / 180 xpts = (radius * cos(t)).tolist() ypts = (radius * sin(t)).tolist() c.add_polygon(points=(xpts, ypts), layer=layer) return c
def add_pin_triangle( component: Component, port: Port, layer: Tuple[int, int] = LAYER.PORT, label_layer: Optional[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_polygon(polygon, layer=layer) if label_layer: component.add_label( text=str(p.name), position=ptip, layer=label_layer, )
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 add_pin_triangle( component: Component, port: Port, layer: Tuple[int, int] = LAYER.PORT, layer_label: Optional[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 layer_label: for the label """ polygon, ptip = get_pin_triangle_polygon_tip(port=port) component.add_polygon(polygon, layer=layer) if layer_label: component.add_label( text=str(port.name), position=ptip, layer=layer_label, )
def add_padding_to_size_container( component: Component, layers: Tuple[Layer, ...] = (LAYER.PADDING, ), xsize: Optional[float] = None, ysize: Optional[float] = None, left: float = 0, bottom: float = 0, ) -> Component: """Returns new component with padding layers on each side. New size is multiple of grid size Args: component layers: list of layers xsize: ysize: left: bottom: """ c = Component() cref = c << component top = abs(ysize - component.ysize) if ysize else 0 right = abs(xsize - component.xsize) if xsize else 0 points = [ [cref.xmin - left, cref.ymin - bottom], [cref.xmax + right, cref.ymin - bottom], [cref.xmax + right, cref.ymax + top], [cref.xmin - left, cref.ymax + top], ] for layer in layers: c.add_polygon(points, layer=layer) c.ports = cref.ports c.copy_child_info(component) return c
def wire_corner(cross_section: CrossSectionFactory = metal3, **kwargs) -> Component: """90 degrees electrical corner Args: waveguide: kwargs: cross_section settings """ x = cross_section(**kwargs) layer = x.info["layer"] width = x.info["width"] 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="e1", midpoint=(-a, 0), width=width, orientation=180, layer=layer, port_type="electrical", ) c.add_port( name="e2", midpoint=(0, a), width=width, orientation=90, layer=layer, port_type="electrical", ) c.info.length = width return c