def pad_array( pad: ComponentOrFactory = pad, spacing: Tuple[float, float] = (150.0, 150.0), columns: int = 6, rows: int = 1, orientation: int = 270, ) -> Component: """Returns 2D array of pads Args: pad: pad element spacing: x, y pitch columns: rows: orientation: port orientation in deg """ c = Component() pad = pad() if callable(pad) else pad size = pad.info.full.size c.info.size = size c.add_array(pad, columns=columns, rows=rows, spacing=spacing) width = size[0] if orientation in [90, 270] else size[1] for col in range(columns): for row in range(rows): c.add_port( name=f"e{row+1}{col+1}", midpoint=(col * spacing[0], row * spacing[1]), width=width, orientation=orientation, port_type="electrical", layer=pad.info["layer"], ) return c
def fiber_array( n: int = 8, pitch: float = 127.0, core_diameter: float = 10, cladding_diameter: float = 125, layer_core: Tuple[int, int] = gf.LAYER.WG, layer_cladding: Tuple[int, int] = gf.LAYER.WGCLAD, ) -> Component: """Returns a fiber array .. code:: pitch <-> _________ | | lid | o o o o | | | base |_________| length """ c = Component() for i in range(n): core = c.add_ref(circle(radius=core_diameter / 2, layer=layer_core)) cladding = c.add_ref( circle(radius=cladding_diameter / 2, layer=layer_cladding)) core.movex(i * pitch) cladding.movex(i * pitch) c.add_port(name=f"F{i}", width=core_diameter, orientation=0) return c
def add_ports_from_labels( component: Component, port_width: float, port_layer: Layer, xcenter: Optional[float] = None, port_name_prefix: str = "o", port_type: str = "optical", ) -> Component: """Add ports from labels. Assumes that all ports have a label at the port center. """ xc = xcenter or component.x yc = component.y for i, label in enumerate(component.labels): x, y = label.position port_name = f"{port_name_prefix}{i+1}" if port_name_prefix else i if x > xc: # east orientation = 0 elif x < xc: # west orientation = 180 elif y > yc: # north orientation = 90 elif y < yc: # south orientation = 270 component.add_port( name=port_name, midpoint=(x, y), width=port_width, orientation=orientation, port_type=port_type, layer=port_layer, ) return component
def coupler_straight(length: float = 10.0, gap: float = 0.27, straight: ComponentFactory = straight_function, **kwargs) -> Component: """Coupler_straight with two parallel straights. Args: length: of straight gap: between straights straight: straight waveguide function kwargs: cross_section settings """ component = Component() straight_component = (straight(length=length, **kwargs) if callable(straight) else straight) top = component << straight_component bot = component << straight_component # bot.ymax = 0 # top.ymin = gap top.movey(straight_component.info.width + gap) component.add_port("o1", port=bot.ports["o1"]) component.add_port("o2", port=top.ports["o1"]) component.add_port("o3", port=bot.ports["o2"]) component.add_port("o4", port=top.ports["o2"]) component.auto_rename_ports() return component
def grating_coupler_array( grating_coupler: ComponentOrFactory = grating_coupler_circular, pitch: float = 127.0, n: int = 6, port_name: str = "o1", rotation: int = 0, ) -> Component: """Array of rectangular pads. Args: grating_coupler: ComponentOrFactory spacing: x spacing n: number of pads port_name: port name rotation: rotation angle for each reference """ c = Component() grating_coupler = (grating_coupler() if callable(grating_coupler) else grating_coupler) for i in range(n): gc = c << grating_coupler gc.rotate(rotation) gc.x = i * pitch port_name_new = f"o{i}" c.add_port(port=gc.ports[port_name], name=port_name_new) return c
def spiral_inner_io_fiber_single( cross_section: CrossSectionFactory = strip, cross_section_bend: Optional[CrossSectionFactory] = None, x_straight_inner_right: float = 40.0, x_straight_inner_left: float = 75.0, y_straight_inner_top: float = 10.0, y_straight_inner_bottom: float = 0.0, grating_spacing: float = 200.0, **kwargs): """Spiral with 0 and 270 degree ports.""" c = Component() ref = c << spiral_inner_io(cross_section=cross_section, cross_section_bend=cross_section_bend, x_straight_inner_right=x_straight_inner_right, x_straight_inner_left=x_straight_inner_left, y_straight_inner_top=y_straight_inner_top, y_straight_inner_bottom=y_straight_inner_bottom, grating_spacing=grating_spacing, **kwargs) ref.rotate(90) bend = bend_euler(cross_section=cross_section_bend or cross_section) btop = c << bend bbot = c << bend bbot.connect("o2", ref.ports["o1"]) btop.connect("o1", ref.ports["o2"]) c.add_port("o2", port=btop.ports["o2"]) c.add_port("o1", port=bbot.ports["o1"]) return c
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 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 extend_ports_list( ports: List[Port], extension_factory: ComponentOrFactory, extension_port_name: Optional[str] = None, ) -> Component: """Returns a component with the extensions for a list of ports. Args: ports: list of ports extension_factory: function for extension extension_port_name: to connect extension """ c = Component() extension = (extension_factory() if callable(extension_factory) else extension_factory) extension_port_name = extension_port_name or list( extension.ports.keys())[0] for i, port in enumerate(ports): extension_ref = c << extension extension_ref.connect(extension_port_name, port) for port_name, port in extension_ref.ports.items(): # if port_name not in extension_port_name: c.add_port(f"{i}_{port_name}", port=port) c.auto_rename_ports() return c
def grating_coupler_array( grating_coupler: ComponentOrFactory = grating_coupler_elliptical2, pitch: float = 127.0, n: int = 6, port_name: str = "o1", ) -> Component: """Array of rectangular pads. Args: grating_coupler: ComponentOrFactory spacing: x spacing n: number of pads port_list: list of port orientations (N, S, W, E) per pad """ c = Component() grating_coupler = (grating_coupler() if callable(grating_coupler) else grating_coupler) for i in range(n): gc = c << grating_coupler gc.x = i * pitch port_name_new = f"o{i}" c.add_port(port=gc.ports[port_name], name=port_name_new) 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 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 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 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 bend_euler_s(**kwargs) -> Component: """Sbend made of euler bends.""" c = Component() b = bend_euler(**kwargs) b1 = c.add_ref(b) b2 = c.add_ref(b) b2.mirror() b2.connect("o1", b1.ports["o2"]) c.add_port("o1", port=b1.ports["o1"]) c.add_port("o2", port=b2.ports["o2"]) return c
def spiral_inner_io_fiber_single( cross_section: CrossSectionFactory = strip, cross_section_bend: Optional[CrossSectionFactory] = None, cross_section_ports: Optional[CrossSectionFactory] = None, x_straight_inner_right: float = 40.0, x_straight_inner_left: float = 75.0, y_straight_inner_top: float = 10.0, y_straight_inner_bottom: float = 0.0, grating_spacing: float = 200.0, **kwargs): """Returns Spiral with 90 and 270 degree ports. You can add single fiber north and south grating couplers inside the spiral to save space Args: cross_section: for the straight sections in the spiral cross_section_bend: for the bends in the spiral cross_section_ports: for input/output ports x_straight_inner_right: x_straight_inner_left: y_straight_inner_top: y_straight_inner_bottom: grating_spacing: N: number of loops waveguide_spacing: center to center spacing bend90_function bend180_function straight: straight function length: computes spiral length from simple interpolation kwargs: cross_section settings """ c = Component() spiral = spiral_inner_io(cross_section=cross_section, cross_section_bend=cross_section_bend, x_straight_inner_right=x_straight_inner_right, x_straight_inner_left=x_straight_inner_left, y_straight_inner_top=y_straight_inner_top, y_straight_inner_bottom=y_straight_inner_bottom, grating_spacing=grating_spacing, **kwargs) ref = c << spiral ref.rotate(90) cross_section_ports = cross_section_ports or cross_section_bend or cross_section bend = bend_euler(cross_section=cross_section_ports) btop = c << bend bbot = c << bend c.copy_child_info(spiral) bbot.connect("o2", ref.ports["o1"]) btop.connect("o1", ref.ports["o2"]) c.add_port("o2", port=btop.ports["o2"]) c.add_port("o1", port=bbot.ports["o1"]) return c
def coupler90bend( radius: float = 10.0, gap: float = 0.2, bend: ComponentFactory = bend_euler, cross_section_inner: CrossSectionFactory = strip, cross_section_outer: CrossSectionFactory = strip, ) -> Component: r"""Returns 2 coupled bends. Args: radius: um gap: um bend: for bend cross_section_inner: cross_section_outer: .. code:: r 3 4 | | | | / / | / / 2____/ / 1_____/ """ c = Component() xi = cross_section_inner() xo = cross_section_outer() width = xo.info["width"] / 2 + xi.info["width"] / 2 spacing = gap + width bend90_inner = bend(radius=radius, cross_section=cross_section_inner) bend90_outer = bend(radius=radius + spacing, cross_section=cross_section_outer) bend_inner_ref = c << bend90_inner bend_outer_ref = c << bend90_outer pbw = bend_inner_ref.ports["o1"] bend_inner_ref.movey(pbw.midpoint[1] + spacing) # This component is a leaf cell => using absorb c.absorb(bend_outer_ref) c.absorb(bend_inner_ref) c.add_port("o1", port=bend_outer_ref.ports["o1"]) c.add_port("o2", port=bend_inner_ref.ports["o1"]) c.add_port("o3", port=bend_inner_ref.ports["o2"]) c.add_port("o4", port=bend_outer_ref.ports["o2"]) return c
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 fiber( core_diameter: float = 10, cladding_diameter: float = 125, layer_core: Tuple[int, int] = gf.LAYER.WG, layer_cladding: Tuple[int, int] = gf.LAYER.WGCLAD, ) -> Component: """Returns a fiber.""" c = Component() c.add_ref(circle(radius=core_diameter / 2, layer=layer_core)) c.add_ref(circle(radius=cladding_diameter / 2, layer=layer_cladding)) c.add_port(name="F0", width=core_diameter, orientation=0) return c
def fanout_component( component: ComponentOrFactory, port_names: Tuple[str, ...], pitch: Tuple[float, float] = (0.0, 20.0), dx: float = 20.0, sort_ports: bool = True, **kwargs, ) -> Component: """Returns component with Sbend fanout routes. Args: component: to fanout ports port_names: list of port names pitch: target port spacing for new component dx: how far the fanout in x direction kwargs: for get_route_sbend """ c = Component() comp = component() if callable(component) else component ref = c.add_ref(comp) ref.movey(-comp.y) for port_name in port_names: if port_name not in ref.ports: raise ValueError(f"{port_name} not in {list(ref.ports.keys())}") ports1 = [p for p in ref.ports.values() if p.name in port_names] port = ports1[0] port_extended_x = port.get_extended_midpoint(dx)[0] port_settings = port.settings.copy() port_settings.pop("name") port_settings.update(midpoint=(port_extended_x, 0)) port_settings.update(orientation=(port.angle + 180) % 360) ports2 = port_array(n=len(ports1), pitch=pitch, **port_settings) if sort_ports: ports1, ports2 = sort_ports_function(ports1, ports2) for i, (p1, p2) in enumerate(zip(ports1, ports2)): route = get_route_sbend(port1=p1, port2=p2, **kwargs) c.add(route.references) c.add_port(f"new_{i}", port=flip(p2)) for port in ref.ports.values(): if port.name not in port_names: c.add_port(port.name, port=port) return c
def _sample_route_sides() -> Component: c = Component() _dummy_t = _sample_route_side() sides = ["north", "south", "east", "west"] positions = [(0, 0), (400, 0), (400, 400), (0, 400)] for pos, side in zip(positions, sides): dummy_ref = _dummy_t.ref(position=pos) c.add(dummy_ref) routes, ports = route_ports_to_side(dummy_ref, side, layer=(2, 0)) for route in routes: c.add(route.references) for i, p in enumerate(ports): c.add_port(name=f"{side[0]}{i}", port=p) return c
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 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 coupler90(gap: float = 0.2, radius: float = 10.0, bend: ComponentFactory = bend_euler, cross_section: CrossSectionFactory = strip, **kwargs) -> Component: r"""straight coupled to a bend. Args: gap: um radius: um straight: for straight bend: for bend cross_section: kwargs: cross_section settings .. code:: 3 | / / 2_/ 1____4 """ c = Component() x = cross_section(radius=radius, **kwargs) bend90 = (bend(cross_section=cross_section, radius=radius, **kwargs) if callable(bend) else bend) bend_ref = c << bend90 straight_component = (straight( cross_section=cross_section, length=bend90.ports["o2"].midpoint[0] - bend90.ports["o1"].midpoint[0], **kwargs) if callable(straight) else straight) wg_ref = c << straight_component width = x.info["width"] pbw = bend_ref.ports["o1"] bend_ref.movey(pbw.midpoint[1] + gap + width) c.absorb(wg_ref) c.absorb(bend_ref) c.add_port("o1", port=wg_ref.ports["o1"]) c.add_port("o4", port=wg_ref.ports["o2"]) c.add_port("o2", port=bend_ref.ports["o1"]) c.add_port("o3", port=bend_ref.ports["o2"]) return c
def awg( arms: int = 10, outputs: int = 3, free_propagation_region_input_function=free_propagation_region_input, free_propagation_region_output_function=free_propagation_region_output, fpr_spacing: float = 50.0, ) -> Component: """Returns a basic Arrayed Waveguide grating. Args: arms: number of arms outputs: number of outputs free_propagation_region_input_function: for input free_propagation_region_output_function: for output fpr_spacing: x separation between input/output FPR """ c = Component() fpr_in = free_propagation_region_input_function( inputs=1, outputs=arms, ) fpr_out = free_propagation_region_output_function( inputs=outputs, outputs=arms, ) fpr_in_ref = c.add_ref(fpr_in) fpr_out_ref = c.add_ref(fpr_out) fpr_in_ref.rotate(90) fpr_out_ref.rotate(90) fpr_out_ref.x += fpr_spacing routes = gf.routing.get_bundle( fpr_in_ref.get_ports_list(prefix="E"), fpr_out_ref.get_ports_list(prefix="E") ) c.lengths = [] for route in routes: c.add(route.references) c.lengths.append(route.length) c.add_port("o1", port=fpr_in_ref.ports["o1"]) for i, port in enumerate(fpr_out_ref.get_ports_list(prefix="W")): c.add_port(f"E{i}", port=port) c.delta_length = np.mean(np.diff(c.lengths)) return c
def array( component: gf.types.ComponentOrFactory = straight, spacing: Tuple[float, float] = (150.0, 150.0), columns: int = 6, rows: int = 1, ) -> Component: """Returns an array of components. Args: component: to replicate spacing: x, y spacing columns: in x rows: in y Raises: ValueError: If columns > 1 and spacing[0] = 0 ValueError: If rows > 1 and spacing[1] = 0 .. code:: 2 rows x 4 columns ___ ___ ___ ___ | | | | | | | | |___| |___| |___| |___| ___ ___ ___ ___ | | | | | | | | |___| |___| |___| |___| """ if rows > 1 and spacing[1] == 0: raise ValueError(f"rows = {rows} > 1 require spacing[1] > 0") if columns > 1 and spacing[0] == 0: raise ValueError(f"columns = {columns} > 1 require spacing[0] > 0") c = Component() component = component() if callable(component) else component c.add_array(component, columns=columns, rows=rows, spacing=spacing) for col in range(columns): for row in range(rows): for port in component.ports.values(): name = f"{port.name}_{row+1}_{col+1}" c.add_port(name, port=port) c.ports[name].move((col * spacing[0], row * spacing[1])) return c
def crossing_from_taper(taper=lambda: taper(width2=2.5, length=3.0)): """ Crossing based on a taper. The default is a dummy taper """ taper = taper() if callable(taper) else taper c = Component() for i, a in enumerate([0, 90, 180, 270]): _taper = taper.ref(position=(0, 0), port_id="o2", rotation=a) c.add(_taper) c.add_port(name=i, port=_taper.ports["o1"]) c.absorb(_taper) c.auto_rename_ports() 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 crossing(arm: ComponentFactory = crossing_arm) -> Component: """Waveguide crossing""" cx = Component() arm = arm() if callable(arm) else arm arm_h = arm.ref() arm_v = arm.ref(rotation=90) port_id = 0 for c in [arm_h, arm_v]: cx.add(c) cx.absorb(c) for p in c.ports.values(): cx.add_port(name=port_id, port=p) port_id += 1 cx.auto_rename_ports() return cx
def add_ports_from_markers_square( component: Component, pin_layer: Layer = (69, 0), port_layer: Optional[Layer] = None, orientation: Optional[int] = 90, min_pin_area_um2: float = 0, max_pin_area_um2: float = 150 * 150, pin_extra_width: float = 0.0, port_names: Optional[Tuple[str, ...]] = None, port_name_prefix: str = "o", ) -> Component: """add ports from markers center in port_layer squared Args: component: to read polygons from and to write ports to pin_layer: for port markers port_layer: for the new created port orientation: in degrees 90: north, 0: east, 180: west, 270: south min_pin_area_um2: ignores pins with area smaller than min_pin_area_um2 max_pin_area_um2: ignore pins for area above certain size pin_extra_width: 2*offset from pin to straight port_names: names of the ports (defaults to {i}) """ port_markers = read_port_markers(component, [pin_layer]) port_names = port_names or [ f"{port_name_prefix}{i+1}" for i in range(len(port_markers.polygons)) ] layer = port_layer or pin_layer for port_name, p in zip(port_names, port_markers.polygons): dy = snap_to_grid(p.ymax - p.ymin) dx = snap_to_grid(p.xmax - p.xmin) x = p.x y = p.y if dx == dy and max_pin_area_um2 > dx * dy > min_pin_area_um2: component.add_port( port_name, midpoint=(x, y), width=dx - pin_extra_width, orientation=orientation, layer=layer, ) return component