Skip to content

API

Geometry Construction

Classes and functions for construction and manipulation of geometric objects.

Component definition

Component

Bases: ComponentBase, DKCell

Canvas where you add polygons, instances and ports.

  • stores settings that you use to build the component
  • stores info that you want to use
  • can return ports by type (optical, electrical ...)
  • can return netlist for circuit simulation
  • can write to GDS, OASIS
  • can show in KLayout, matplotlib or 3D
Properties

info: dictionary that includes derived properties, simulation_settings, settings (test_protocol, docs, ...)

Source code in gdsfactory/component.py
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
class Component(ComponentBase, kf.DKCell):
    """Canvas where you add polygons, instances and ports.

    - stores settings that you use to build the component
    - stores info that you want to use
    - can return ports by type (optical, electrical ...)
    - can return netlist for circuit simulation
    - can write to GDS, OASIS
    - can show in KLayout, matplotlib or 3D

    Properties:
        info: dictionary that includes derived properties, simulation_settings, settings (test_protocol, docs, ...)
    """

    routes: dict[str, Route] = Field(default_factory=dict)

    def dup(self, new_name: str | None = None) -> Self:
        """Copy the full cell.

        Overrides kfactory's dup() to fix pin port mapping during copy.
        kfactory builds port_mapping from ephemeral Port wrappers, but pin
        ports are BasePort objects — the id() values never match.
        """
        saved_pins = self._base.pins
        self._base.pins = []
        try:
            c = super().dup(new_name=new_name)
        finally:
            self._base.pins = saved_pins
        if saved_pins:
            port_mapping = {id(b): i for i, b in enumerate(self.ports._bases)}
            c._base.pins = [
                BasePin(
                    name=p.name,
                    kcl=self.kcl,
                    ports=[c.base.ports[port_mapping[id(port)]] for port in p.ports],
                    pin_type=p.pin_type,
                    info=p.info,
                )
                for p in saved_pins
            ]
        return c

    @override
    def write(
        self,
        filename: str | pathlib.Path,
        save_options: kdb.SaveLayoutOptions | None = None,
        convert_external_cells: bool = False,
        set_meta_data: bool = True,
        autoformat_from_file_extension: bool = True,
        deduplicate_cell_names: bool = True,
    ) -> None:
        """Write component to GDS, fixing pin metadata for kfactory compat."""
        if set_meta_data:
            self.insert_vinsts()
            self.kcl.set_meta_data()
            for ci in self.called_cells():
                kcell = self.kcl[ci]
                if not kcell._destroyed():
                    if convert_external_cells and kcell.is_library_cell():
                        kcell.convert_to_static(recursive=True)
                    kcell.set_meta_data()
                    _fix_pin_metadata(kcell)
            if convert_external_cells and self.is_library_cell():
                self.convert_to_static(recursive=True)
            self.set_meta_data()
            _fix_pin_metadata(self)
        super().write(
            filename,
            save_options=save_options,
            convert_external_cells=False,
            set_meta_data=False,
            autoformat_from_file_extension=autoformat_from_file_extension,
            deduplicate_cell_names=deduplicate_cell_names,
        )

    @property
    def layers(self) -> list[Layer]:
        return [
            (info.layer, info.datatype)
            for info in self.kcl.layout.layer_infos()
            if not self.bbox(self.kcl.layout.layer(info)).empty()
        ]

    def add(self, instances: Iterable[ComponentReference] | ComponentReference) -> None:
        if self.locked:
            raise LockedError(self)

        if not isinstance(instances, Iterable):
            instance_list = [instances]
        else:
            instance_list = list(instances)

        for instance in instance_list:
            self.kdb_cell.insert(instance.instance)

    def absorb(self, reference: ComponentReference) -> Self:
        """Absorbs polygons from ComponentReference into Component.

        Destroys the reference in the process but keeping the polygon geometry.

        Args:
            reference: Instance to be absorbed into the Component.

        """
        if self.locked:
            raise LockedError(self)
        if reference not in self.insts:
            raise ValueError(
                "The reference you asked to absorb does not exist in this Component."
            )
        reference.flatten()
        return self

    def trim(
        self,
        left: float,
        bottom: float,
        right: float,
        top: float,
        flatten: bool = False,
    ) -> None:
        """Trims the Component to a bounding box.

        Args:
            left: left coordinate of the bounding box.
            bottom: bottom coordinate of the bounding box.
            right: right coordinate of the bounding box.
            top: top coordinate of the bounding box.
            flatten: if True, flattens the Component.
        """
        if self.locked:
            raise LockedError(self)

        c = self

        domain_box = kdb.DBox(left, bottom, right, top)
        if not c.dbbox().inside(domain_box):
            kdb_cell = c.kcl.layout.clip(c.kdb_cell, kdb.DBox(left, bottom, right, top))
            c.kdb_cell.clear()
            c.kdb_cell.copy_tree(kdb_cell)
            kdb_cell.delete()
            if flatten:
                c.flatten()

    def __lshift__(self, cell: kf.ProtoTKCell[Any]) -> ComponentReference:
        """Convenience function for adding instances/references to a Component.

        Args:
            cell: The cell to be added as an instance
        """
        return self.add_ref(cell)

    def add_ref(
        self,
        component: kf.ProtoTKCell[Any],
        name: str | None = None,
        columns: int = 1,
        rows: int = 1,
        column_pitch: float = 0.0,
        row_pitch: float = 0.0,
    ) -> ComponentReference:
        """Adds a component instance reference to a Component.

        Args:
            component: The referenced component.
            name: Name of the reference.
            columns: Number of columns in the array.
            rows: Number of rows in the array.
            column_pitch: column pitch.
            row_pitch: row pitch.
        """
        if isinstance(component, ComponentAllAngle):
            raise ValueError(
                f"Use Component.add_ref_off_grid() for all angle {component.name!r}"
            )
        if not isinstance(component, kf.ProtoTKCell):
            raise ValueError(f"Expected a Component, got {type(component)}")

        if self.locked:
            raise LockedError(self)

        if rows > 1 or columns > 1:
            if rows > 1 and row_pitch == 0:
                raise ValueError(f"rows = {rows} > 1 require {row_pitch=} > 0")

            if columns > 1 and column_pitch == 0:
                raise ValueError(f"columns = {columns} > 1 require {column_pitch} > 0")

            a = kf.kdb.DVector(column_pitch, 0)
            b = kf.kdb.DVector(0, row_pitch)

            inst = self.create_inst(component, na=columns, nb=rows, a=a, b=b)
        else:
            inst = self.create_inst(component)
        if name is not None:
            inst.name = name
        return ComponentReference(kcl=self.kcl, instance=inst.instance)

    def get_paths(self, layer: LayerSpec, recursive: bool = True) -> list[kf.kdb.DPath]:
        """Returns a list of paths.

        Args:
            layer: layer to get paths from.
            recursive: if True, gets paths recursively.
        """
        from gdsfactory import get_layer

        paths: list[kf.kdb.DPath] = []

        layer = get_layer(layer)

        if recursive:
            iterator = self.kdb_cell.begin_shapes_rec(layer)
            iterator.shape_flags = kdb.Shapes.SPaths
            paths.extend(
                it.shape().dpath.transformed(it.dtrans()) for it in iterator.each()
            )
        else:
            paths.extend(
                shape.dpath
                for shape in self.kdb_cell.shapes(layer).each(kdb.Shapes.SPaths)
            )
        return paths

    def get_boxes(self, layer: LayerSpec, recursive: bool = True) -> list[kf.kdb.DBox]:
        """Returns a list of boxes.

        Args:
            layer: layer to get boxes from.
            recursive: if True, gets boxes recursively.
        """
        from gdsfactory import get_layer

        boxes: list[kf.kdb.DBox] = []

        layer = get_layer(layer)

        if recursive:
            iterator = self.kdb_cell.begin_shapes_rec(layer)
            iterator.shape_flags = kdb.Shapes.SBoxes
            boxes.extend(
                it.shape().dbox.transformed(it.dtrans()) for it in iterator.each()
            )
        else:
            boxes.extend(
                shape.dbox
                for shape in self.kdb_cell.shapes(layer).each(kdb.Shapes.SBoxes)
            )
        return boxes

    def get_labels(
        self, layer: LayerSpec, recursive: bool = True
    ) -> list[kf.kdb.DText]:
        """Returns a list of labels from the Component.

        Args:
            layer: layer to get labels from.
            recursive: if True, gets labels recursively.
        """
        from gdsfactory import get_layer

        texts: list[kf.kdb.DText] = []
        layer_enum = get_layer(layer)

        if recursive:
            iterator = self.kdb_cell.begin_shapes_rec(layer_enum)
            iterator.shape_flags = kdb.Shapes.STexts
            texts.extend(
                it.shape().dtext.transformed(it.dtrans()) for it in iterator.each()
            )
        else:
            texts.extend(
                shape.dtext
                for shape in self.kdb_cell.shapes(layer_enum).each(kdb.Shapes.STexts)
            )
        return texts

    def area(self, layer: LayerSpec) -> float:
        """Returns the area of the Component in um2."""
        from gdsfactory import get_layer

        layer_index = get_layer(layer)
        r = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
        r.merge()
        return float(sum(p.area2() / 2 * self.kcl.dbu**2 for p in r.each()))

    def get_polygons(
        self,
        merge: bool = False,
        by: Literal["index", "name", "tuple"] = "index",
        layers: LayerSpecs | None = None,
        smooth: float | None = None,
    ) -> dict[tuple[int, int] | str | int, list[kf.kdb.Polygon]]:
        """Returns a dict of Polygons per layer.

        Args:
            merge: if True, merges the polygons.
            by: the format of the resulting keys in the dictionary ('index', 'name', 'tuple')
            layers: list of layers to get polygons from. Defaults to all layers.
            smooth: if True, smooths the polygons.
        """
        if merge and self.locked:
            raise LockedError(self)

        from gdsfactory.functions import get_polygons

        return get_polygons(self, merge=merge, by=by, layers=layers, smooth=smooth)

    def get_region(
        self, layer: LayerSpec, merge: bool = False, smooth: float | None = None
    ) -> kdb.Region:
        """Returns a Region of the Component.

        Note that all operations that you do with the Region will be done in the database units.

        Where for most processes 1 dbu = 1 nm.

        Args:
            layer: layer to get region from.
            merge: if True, merges the region.
            smooth: if True, smooths the region by the specified amount (in um).
        """
        from gdsfactory import get_layer

        layer_index = get_layer(layer)
        r = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
        if smooth:
            r.smooth(self.kcl.to_dbu(smooth))
        if merge:
            r.merge()
        return r

    def get_polygons_points(
        self,
        merge: bool = False,
        scale: float | None = None,
        by: Literal["index", "name", "tuple"] = "index",
        layers: LayerSpecs | None = None,
    ) -> dict[int | str | tuple[int, int], list[npt.NDArray[np.floating[Any]]]]:
        """Returns a dict with list of points per layer.

        Args:
            merge: if True, merges the polygons.
            scale: if True, scales the points.
            by: the format of the resulting keys in the dictionary ('index', 'name', 'tuple')
            layers: list of layers to get polygons from. Defaults to all layers.
        """
        if merge and self.locked:
            raise LockedError(self)

        from gdsfactory.functions import get_polygons_points

        return get_polygons_points(self, merge=merge, scale=scale, by=by, layers=layers)

    def extract(
        self,
        layers: LayerSpecs,
        recursive: bool = True,
    ) -> Component:
        """Extracts a list of layers and adds them to a new Component.

        Args:
            layers: list of layers to extract.
            recursive: if True, extracts layers recursively and returns a flattened Component.
        """
        from gdsfactory.functions import extract

        return extract(self, layers=layers, recursive=recursive)

    def copy_layers(
        self,
        layer_map: dict[LayerSpec, LayerSpec],
        recursive: bool = False,
    ) -> Self:
        """Remaps a list of layers and returns the same Component.

        Args:
            layer_map: dictionary of layers to copy.
            recursive: if True, remaps layers recursively.
        """
        from gdsfactory import get_layer

        if recursive:
            self.locked = False

        if self.locked:
            raise LockedError(self)

        layer_index_pairs = [
            (get_layer(layer), get_layer(new_layer))
            for layer, new_layer in layer_map.items()
        ]
        kdb_cell = self.kdb_cell
        for src_layer_index, dst_layer_index in layer_index_pairs:
            kdb_cell.copy(src_layer_index, dst_layer_index)

        if recursive:
            for ci in kdb_cell.called_cells():
                child = self.kcl[ci]
                child_cell = child.kdb_cell
                was_locked = child.locked
                child.locked = False
                try:
                    for src_layer_index, dst_layer_index in layer_index_pairs:
                        child_cell.copy(src_layer_index, dst_layer_index)
                finally:
                    if was_locked:
                        child.locked = True
        return self

    def remove_layers(
        self,
        layers: LayerSpecs,
        recursive: bool = True,
    ) -> Self:
        """Removes a list of layers and returns the same Component.

        Args:
            layers: list of layers to remove.
            recursive: if True, removes layers recursively and temporarily unlocks components.
        """
        from gdsfactory import get_layer

        if recursive:
            self.locked = False

        if self.locked:
            raise LockedError(self)

        layer_indexes = self.kcl.layer_indexes()
        layer_indexes_to_remove = [get_layer(layer) for layer in layers]
        layer_indices = [
            layer for layer in layer_indexes_to_remove if layer in layer_indexes
        ]
        if not layer_indices:
            return self

        kdb_cell = self.kdb_cell
        for layer_index in layer_indices:
            kdb_cell.shapes(layer_index).clear()

        if recursive:
            for ci in kdb_cell.called_cells():
                child = self.kcl[ci]
                child_cell = child.kdb_cell
                was_locked = child.locked
                child.locked = False
                try:
                    for layer_idx in layer_indices:
                        child_cell.shapes(layer_idx).clear()
                finally:
                    if was_locked:
                        child.locked = True
        return self

    def remap_layers(
        self, layer_map: dict[LayerSpec, LayerSpec], recursive: bool = False
    ) -> Self:
        """Remaps a list of layers and returns the same Component.

        Args:
            layer_map: dictionary of layers to remap.
            recursive: if True, remaps layers recursively.
        """
        from gdsfactory import get_layer

        if self.locked:
            raise LockedError(self)

        layer_index_pairs = [
            (get_layer(layer), get_layer(new_layer))
            for layer, new_layer in layer_map.items()
        ]
        kdb_cell = self.kdb_cell
        for src_layer_index, dst_layer_index in layer_index_pairs:
            kdb_cell.move(src_layer_index, dst_layer_index)

        if recursive:
            for ci in kdb_cell.called_cells():
                child_cell = self.kcl[ci].kdb_cell
                for src_layer_index, dst_layer_index in layer_index_pairs:
                    child_cell.move(src_layer_index, dst_layer_index)
        return self

    def to_3d(
        self,
        layer_views: LayerViews | None = None,
        layer_stack: LayerStack | None = None,
        exclude_layers: Sequence[Layer] | None = None,
    ) -> Scene:
        """Return Component 3D trimesh Scene.

        Args:
            component: to extrude in 3D.
            layer_views: layer colors from Klayout Layer Properties file.
                Defaults to active PDK.layer_views.
            layer_stack: contains thickness and zmin for each layer.
                Defaults to active PDK.layer_stack.
            exclude_layers: layers to exclude.

        """
        from gdsfactory.export.to_3d import to_3d

        return to_3d(
            self,
            layer_views=layer_views,
            layer_stack=layer_stack,
            exclude_layers=exclude_layers,
        )

    def over_under(
        self,
        layer: LayerSpec,
        distance: float = 0.001,
        remove_old_layer: bool = True,
        corner_mode: int | CornerMode = 2,
    ) -> None:
        """Returns a Component over-under on a layer in the Component.

        For big components use tiled version.

        Args:
            layer: layer to perform over-under on.
            distance: distance to perform over-under in um.
            remove_old_layer: if True, removes the old layer.
            corner_mode: determines behavior around corners
        """
        from gdsfactory import get_layer

        if self.locked:
            raise LockedError(self)

        distance_dbu = self.kcl.to_dbu(distance)

        layer_index = get_layer(layer)
        region = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
        region.size(+distance_dbu, +distance_dbu, corner_mode).size(
            -distance_dbu, -distance_dbu, corner_mode
        )

        if remove_old_layer:
            self.remove_layers([layer])
        self.kdb_cell.shapes(layer_index).insert(region)
        self.kcl.layout.end_changes()

    def fix_spacing(
        self,
        layer: LayerSpec,
        min_space: float = 0.2,
        size_bias: float = 0.0,
    ) -> None:
        """Fixes layer spacing in the Component.

        Args:
            layer: layer to fix spacing on.
            min_space: minimum space in um.
            size_bias: optional geometry bias applied after spacing fix (um).
        """
        import gdsfactory as gf
        from gdsfactory.pdk import get_layer

        layer = get_layer(layer)
        layer_info = gf.kcl.get_info(layer)
        fix = fix_spacing_tiled(
            self.to_itype(), min_space=self.kcl.to_dbu(min_space), layer=layer_info
        )
        if size_bias:
            size_offset_dbu = self.kcl.to_dbu(size_bias)
            fix = fix.sized(+size_offset_dbu)
            fix = fix.sized(-size_offset_dbu)

        self.shapes(layer).insert(fix)

    def fix_width(
        self,
        layer: LayerSpec,
        min_width: float = 0.2,
        n_threads: int | None = None,
        tile_size: tuple[float, float] | None = None,
        overlap: int = 1,
        smooth: int | None = None,
        flatten: bool = True,
    ) -> None:
        """Fixes layer min width in the Component.

        Args:
            layer: layer to fix width on.
            min_width: minimum width in um.
            n_threads: number of threads to use for processing.
            tile_size: size of the tiles to use for processing.
            overlap: overlap between tiles.
            smooth: smooth the polygons by this amount in um.
            flatten: if True, flattens the Component before fixing width.
        """
        import gdsfactory as gf
        from gdsfactory.pdk import get_layer

        if flatten:
            self.flatten()
        layer = get_layer(layer)
        layer_info = gf.kcl.get_info(layer)

        fix = fix_width_minkowski_tiled(
            self.to_itype(),
            min_width=self.kcl.to_dbu(min_width),
            ref=layer_info,
            n_threads=n_threads,
            tile_size=tile_size,
            overlap=overlap,
            smooth=smooth,
        )
        cast(kdb.Shapes, self.shapes(layer)).clear()  # type: ignore[redundant-cast]
        self.shapes(layer).insert(fix)

    def offset(
        self,
        layer: LayerSpec,
        distance: float,
        flatten: bool = False,
        corner_mode: int | CornerMode = 2,
    ) -> None:
        """Offsets a Component layer by a distance in um.

        Args:
            layer: layer to offset the Component on.
            distance: distance to offset the Component in um.
            flatten: if True, flattens the Component before offsetting.
            corner_mode: determines behavior around corners
        """
        from gdsfactory import get_layer

        if self.locked:
            raise LockedError(self)

        if flatten:
            self.flatten()

        distance_dbu = self.kcl.to_dbu(distance)

        layer_index = get_layer(layer)
        region = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
        region.size(distance_dbu, distance_dbu, corner_mode)
        self.remove_layers([layer])
        self.kdb_cell.shapes(layer_index).insert(region)

        self.kcl.layout.end_changes()

    def add_polygon(self, points: _PolygonPoints, layer: LayerSpec) -> kdb.Shape | None:
        """Adds a Polygon to the Component and returns a klayout Shape.

        Args:
            points: Coordinates of the vertices of the Polygon.
            layer: layer spec to add polygon on.
        """
        from gdsfactory.pdk import get_layer

        if self.locked:
            raise LockedError(self)

        _layer = get_layer(layer)

        polygon = points_to_polygon(points)
        if isinstance(polygon, kdb.DPolygon | kdb.DSimplePolygon):
            polygon = polygon.to_itype(self.kcl.dbu)  # type: ignore[assignment]

        return self.kdb_cell.shapes(_layer).insert(polygon)

    @overload
    def plot(
        self,
        lyrdb: pathlib.Path | str | None = None,
        display_type: Literal["image", "widget"] | None = None,
        *,
        show_labels: bool = True,
        show_ruler: bool = True,
        pixel_buffer_options: PixelBufferOptions | None = None,
        return_fig: Literal[True] = True,
        ax: Axes | None = None,
    ) -> Figure: ...

    @overload
    def plot(
        self,
        lyrdb: pathlib.Path | str | None = None,
        display_type: Literal["image", "widget"] | None = None,
        *,
        show_labels: bool = True,
        show_ruler: bool = True,
        pixel_buffer_options: PixelBufferOptions | None = None,
        return_fig: Literal[False] = False,
        ax: Axes | None = None,
    ) -> None: ...

    def plot(
        self,
        lyrdb: pathlib.Path | str | None = None,
        display_type: Literal["image", "widget"] | None = None,
        *,
        show_labels: bool = True,
        show_ruler: bool = True,
        pixel_buffer_options: PixelBufferOptions | None = None,
        return_fig: bool = False,
        ax: Axes | None = None,
    ) -> Figure | None:
        """Plots the Component using klayout.

        Args:
            lyrdb: path to layer properties file.
            display_type: if "image", displays the image.
            show_labels: if True, shows labels.
            show_ruler: if True, shows ruler.
            pixel_buffer_options: options for KLayout's get_pixels_with_options.
                If None, uses default values (width=800, height=600, linewidth=0,
                oversampling=0, resolution=0).
            return_fig: if True, returns the figure.
            ax: Optional matplotlib Axes to plot on. If None, creates a new figure and axes. When specified, fig_size and dpi are determined by the provided axes' figure.
        """
        from io import BytesIO

        import matplotlib.pyplot as plt

        from gdsfactory.pdk import get_layer_views

        self.insert_vinsts()

        lyp_path = GDSDIR_TEMP / "layer_properties.lyp"
        layer_views = get_layer_views()
        layer_views.to_lyp(filepath=lyp_path)

        layout_view = lay.LayoutView()
        cell_view_index = layout_view.create_layout(True)
        layout_view.active_cellview_index = cell_view_index
        cell_view = layout_view.cellview(cell_view_index)
        layout = cell_view.layout()
        layout.assign(kf.kcl.layout)

        assert self.name is not None, "Component name is None"

        cell_view.cell = layout.cell(self.name)

        layout_view.max_hier()
        layout_view.load_layer_props(str(lyp_path))

        layout_view.add_missing_layers()
        layout_view.zoom_fit()

        layout_view.set_config("text-visible", "true" if show_labels else "false")
        layout_view.set_config("grid-show-ruler", "true" if show_ruler else "false")

        pixel_buffer = layout_view.get_pixels_with_options(
            **cast(
                dict[str, Any],
                ({"width": 800, "height": 600} | (pixel_buffer_options or {})),
            )
        )
        png_data = pixel_buffer.to_png_data()

        # Convert PNG data to NumPy array and display with matplotlib
        with BytesIO(png_data) as f:
            img_array = plt.imread(f)

        # Compute the figure dimensions based on the image size and desired DPI
        dpi = 80
        fig_width = img_array.shape[1] / dpi
        fig_height = img_array.shape[0] / dpi

        if ax is not None:
            fig = plt.gcf()  # Get the current figure (global figure, not subfigure)
        else:
            fig, ax = plt.subplots(figsize=(fig_width, fig_height), dpi=dpi)

        # Remove margins and display the image
        ax.imshow(img_array)
        ax.axis("off")  # Hide axes
        ax.set_position((0, 0, 1, 1))  # Set axes to occupy the full figure space

        plt.subplots_adjust(
            left=0, right=1, top=1, bottom=0, wspace=0, hspace=0
        )  # Remove any padding
        plt.tight_layout(pad=0)  # Ensure no space is wasted
        return fig if return_fig else None

    def plot_netlist(
        self,
        recursive: bool = False,
        with_labels: bool = True,
        font_weight: str = "normal",
        **kwargs: Any,
    ) -> nx.Graph:
        """Plots a netlist graph with networkx.

        Args:
            recursive: if True, returns a recursive netlist.
            with_labels: add label to each node.
            font_weight: normal, bold.
            kwargs: keyword arguments to get_netlist.

        Keyword Args:
            tolerance: tolerance in grid_factor to consider two ports connected.
            exclude_port_types: optional list of port types to exclude from netlisting.
            get_instance_name: function to get instance name.
            allow_multiple: False to raise an error if more than two ports share the same connection. \
                    if True, will return key: [value] pairs with [value] a list of all connected instances.
        """
        import matplotlib.pyplot as plt
        import networkx as nx

        plt.figure()
        netlist = self.get_netlist(recursive=recursive, **kwargs)
        G = nx.Graph()

        if recursive:
            pos: dict[str, tuple[float, float]] = {}
            labels: dict[str, str] = {}
            for net in netlist.values():
                nets = net.get("nets", [])
                connections = net.get("connections", {})
                connections = nets_to_connections(nets, connections)
                placements = net["placements"]
                G.add_edges_from(
                    [
                        (",".join(k.split(",")[:-1]), ",".join(v.split(",")[:-1]))
                        for k, v in connections.items()
                    ]
                )
                pos |= {k: (v["x"], v["y"]) for k, v in placements.items()}
                labels |= {k: ",".join(k.split(",")[:1]) for k in placements}

        else:
            nets = netlist.get("nets", [])
            connections = netlist.get("connections", {})
            connections = nets_to_connections(nets, connections)
            placements = netlist["placements"]
            G.add_edges_from(
                [
                    (",".join(k.split(",")[:-1]), ",".join(v.split(",")[:-1]))
                    for k, v in connections.items()
                ]
            )
            pos = {k: (v["x"], v["y"]) for k, v in placements.items()}
            labels = {k: ",".join(k.split(",")[:1]) for k in placements}

        nx.draw(
            G,
            with_labels=with_labels,
            font_weight=font_weight,
            labels=labels,
            pos=pos,
        )
        return G

    def plot_netlist_graphviz(
        self, recursive: bool = False, interactive: bool = False, splines: str = "ortho"
    ) -> None:
        """Plots a netlist graph with graphviz.

        Args:
            recursive: if True, returns a recursive netlist.
            interactive: if True, opens the graph in a browser.
            splines: ortho, spline, polyline, line, curved.
        """
        from gdsfactory.schematic import plot_graphviz

        n = self.to_graphviz(
            recursive=recursive,
        )
        plot_graphviz(n, splines=splines, interactive=interactive)

    def to_graphviz(self, recursive: bool = False) -> Digraph:
        """Returns a netlist graph with graphviz.

        Args:
            recursive: if True, returns a recursive netlist.
        """
        from gdsfactory.schematic import to_graphviz

        netlist = self.get_netlist(recursive=recursive)
        return to_graphviz(
            netlist["instances"],
            placements=netlist["placements"],
            nets=netlist["nets"],
        )

    def fill(
        self,
        fill_cell: ComponentSpec,
        fill_layers: Iterable[tuple[LayerSpec, float]] = [],
        fill_regions: Iterable[tuple[kdb.Region, float]] = [],
        exclude_layers: Iterable[tuple[LayerSpec, float]] = [],
        exclude_regions: Iterable[tuple[kdb.Region, float]] = [],
        n_threads: int | None = None,
        tile_size: tuple[float, float] | None = None,
        row_step: kdb.DVector | None = None,
        col_step: kdb.DVector | None = None,
        x_space: float = 0.0,
        y_space: float = 0.0,
        tile_border: tuple[float, float] = (20, 20),
        multi: bool = False,
    ) -> None:
        """Fill a [KCell][kfactory.kcell.KCell].

        Args:
            fill_cell: The cell used as a cell to fill the regions.
            fill_layers: Tuples of layer and keepout in um.
            fill_regions: Specific regions to fill. Also tuples like the layers.
            exclude_layers: Layers to ignore. Tuples like the fill layers
            exclude_regions: Specific regions to ignore. Tuples like the fill layers.
            n_threads: Max number of threads used. Defaults to number of cores of the
                machine.
            tile_size: Size of the tiles in um.
            row_step: DVector for steping to the next instance position in the row.
                x-coordinate must be >= 0.
            col_step: DVector for steping to the next instance position in the column.
                y-coordinate must be >= 0.
            x_space: Spacing between the fill cell bounding boxes in x-direction.
            y_space: Spacing between the fill cell bounding boxes in y-direction.
            tile_border: The tile border to consider for excludes
            multi: Use the region_fill_multi strategy instead of single fill.
        """
        from gdsfactory.pdk import get_component, get_layer_info

        fill_cell = get_component(fill_cell)
        fill_layers_converted = [
            (get_layer_info(layer), int(spacing)) for layer, spacing in fill_layers
        ]
        fill_regions_converted = [
            (region, int(spacing)) for region, spacing in fill_regions
        ]
        exclude_layers_converted = [
            (get_layer_info(layer), int(spacing)) for layer, spacing in exclude_layers
        ]
        exclude_regions_converted = [
            (region, int(spacing)) for region, spacing in exclude_regions
        ]

        fill_tiled(
            self,
            fill_cell=fill_cell,
            fill_layers=fill_layers_converted,
            fill_regions=fill_regions_converted,
            exclude_layers=exclude_layers_converted,
            exclude_regions=exclude_regions_converted,
            n_threads=n_threads,
            tile_size=tile_size,
            row_step=row_step,
            col_step=col_step,
            x_space=x_space,
            y_space=y_space,
            tile_border=tile_border,
            multi=multi,
        )

__lshift__

__lshift__(cell: ProtoTKCell[Any]) -> ComponentReference

Convenience function for adding instances/references to a Component.

Parameters:

Name Type Description Default
cell ProtoTKCell[Any]

The cell to be added as an instance

required
Source code in gdsfactory/component.py
796
797
798
799
800
801
802
def __lshift__(self, cell: kf.ProtoTKCell[Any]) -> ComponentReference:
    """Convenience function for adding instances/references to a Component.

    Args:
        cell: The cell to be added as an instance
    """
    return self.add_ref(cell)

absorb

absorb(reference: ComponentReference) -> Self

Absorbs polygons from ComponentReference into Component.

Destroys the reference in the process but keeping the polygon geometry.

Parameters:

Name Type Description Default
reference ComponentReference

Instance to be absorbed into the Component.

required
Source code in gdsfactory/component.py
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
def absorb(self, reference: ComponentReference) -> Self:
    """Absorbs polygons from ComponentReference into Component.

    Destroys the reference in the process but keeping the polygon geometry.

    Args:
        reference: Instance to be absorbed into the Component.

    """
    if self.locked:
        raise LockedError(self)
    if reference not in self.insts:
        raise ValueError(
            "The reference you asked to absorb does not exist in this Component."
        )
    reference.flatten()
    return self

add_polygon

add_polygon(
    points: _PolygonPoints, layer: LayerSpec
) -> kdb.Shape | None

Adds a Polygon to the Component and returns a klayout Shape.

Parameters:

Name Type Description Default
points _PolygonPoints

Coordinates of the vertices of the Polygon.

required
layer LayerSpec

layer spec to add polygon on.

required
Source code in gdsfactory/component.py
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
def add_polygon(self, points: _PolygonPoints, layer: LayerSpec) -> kdb.Shape | None:
    """Adds a Polygon to the Component and returns a klayout Shape.

    Args:
        points: Coordinates of the vertices of the Polygon.
        layer: layer spec to add polygon on.
    """
    from gdsfactory.pdk import get_layer

    if self.locked:
        raise LockedError(self)

    _layer = get_layer(layer)

    polygon = points_to_polygon(points)
    if isinstance(polygon, kdb.DPolygon | kdb.DSimplePolygon):
        polygon = polygon.to_itype(self.kcl.dbu)  # type: ignore[assignment]

    return self.kdb_cell.shapes(_layer).insert(polygon)

add_ref

add_ref(
    component: ProtoTKCell[Any],
    name: str | None = None,
    columns: int = 1,
    rows: int = 1,
    column_pitch: float = 0.0,
    row_pitch: float = 0.0,
) -> ComponentReference

Adds a component instance reference to a Component.

Parameters:

Name Type Description Default
component ProtoTKCell[Any]

The referenced component.

required
name str | None

Name of the reference.

None
columns int

Number of columns in the array.

1
rows int

Number of rows in the array.

1
column_pitch float

column pitch.

0.0
row_pitch float

row pitch.

0.0
Source code in gdsfactory/component.py
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
def add_ref(
    self,
    component: kf.ProtoTKCell[Any],
    name: str | None = None,
    columns: int = 1,
    rows: int = 1,
    column_pitch: float = 0.0,
    row_pitch: float = 0.0,
) -> ComponentReference:
    """Adds a component instance reference to a Component.

    Args:
        component: The referenced component.
        name: Name of the reference.
        columns: Number of columns in the array.
        rows: Number of rows in the array.
        column_pitch: column pitch.
        row_pitch: row pitch.
    """
    if isinstance(component, ComponentAllAngle):
        raise ValueError(
            f"Use Component.add_ref_off_grid() for all angle {component.name!r}"
        )
    if not isinstance(component, kf.ProtoTKCell):
        raise ValueError(f"Expected a Component, got {type(component)}")

    if self.locked:
        raise LockedError(self)

    if rows > 1 or columns > 1:
        if rows > 1 and row_pitch == 0:
            raise ValueError(f"rows = {rows} > 1 require {row_pitch=} > 0")

        if columns > 1 and column_pitch == 0:
            raise ValueError(f"columns = {columns} > 1 require {column_pitch} > 0")

        a = kf.kdb.DVector(column_pitch, 0)
        b = kf.kdb.DVector(0, row_pitch)

        inst = self.create_inst(component, na=columns, nb=rows, a=a, b=b)
    else:
        inst = self.create_inst(component)
    if name is not None:
        inst.name = name
    return ComponentReference(kcl=self.kcl, instance=inst.instance)

area

area(layer: LayerSpec) -> float

Returns the area of the Component in um2.

Source code in gdsfactory/component.py
929
930
931
932
933
934
935
936
def area(self, layer: LayerSpec) -> float:
    """Returns the area of the Component in um2."""
    from gdsfactory import get_layer

    layer_index = get_layer(layer)
    r = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
    r.merge()
    return float(sum(p.area2() / 2 * self.kcl.dbu**2 for p in r.each()))

copy_layers

copy_layers(
    layer_map: dict[LayerSpec, LayerSpec],
    recursive: bool = False,
) -> Self

Remaps a list of layers and returns the same Component.

Parameters:

Name Type Description Default
layer_map dict[LayerSpec, LayerSpec]

dictionary of layers to copy.

required
recursive bool

if True, remaps layers recursively.

False
Source code in gdsfactory/component.py
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
def copy_layers(
    self,
    layer_map: dict[LayerSpec, LayerSpec],
    recursive: bool = False,
) -> Self:
    """Remaps a list of layers and returns the same Component.

    Args:
        layer_map: dictionary of layers to copy.
        recursive: if True, remaps layers recursively.
    """
    from gdsfactory import get_layer

    if recursive:
        self.locked = False

    if self.locked:
        raise LockedError(self)

    layer_index_pairs = [
        (get_layer(layer), get_layer(new_layer))
        for layer, new_layer in layer_map.items()
    ]
    kdb_cell = self.kdb_cell
    for src_layer_index, dst_layer_index in layer_index_pairs:
        kdb_cell.copy(src_layer_index, dst_layer_index)

    if recursive:
        for ci in kdb_cell.called_cells():
            child = self.kcl[ci]
            child_cell = child.kdb_cell
            was_locked = child.locked
            child.locked = False
            try:
                for src_layer_index, dst_layer_index in layer_index_pairs:
                    child_cell.copy(src_layer_index, dst_layer_index)
            finally:
                if was_locked:
                    child.locked = True
    return self

dup

dup(new_name: str | None = None) -> Self

Copy the full cell.

Overrides kfactory's dup() to fix pin port mapping during copy. kfactory builds port_mapping from ephemeral Port wrappers, but pin ports are BasePort objects — the id() values never match.

Source code in gdsfactory/component.py
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
def dup(self, new_name: str | None = None) -> Self:
    """Copy the full cell.

    Overrides kfactory's dup() to fix pin port mapping during copy.
    kfactory builds port_mapping from ephemeral Port wrappers, but pin
    ports are BasePort objects — the id() values never match.
    """
    saved_pins = self._base.pins
    self._base.pins = []
    try:
        c = super().dup(new_name=new_name)
    finally:
        self._base.pins = saved_pins
    if saved_pins:
        port_mapping = {id(b): i for i, b in enumerate(self.ports._bases)}
        c._base.pins = [
            BasePin(
                name=p.name,
                kcl=self.kcl,
                ports=[c.base.ports[port_mapping[id(port)]] for port in p.ports],
                pin_type=p.pin_type,
                info=p.info,
            )
            for p in saved_pins
        ]
    return c

extract

extract(
    layers: LayerSpecs, recursive: bool = True
) -> Component

Extracts a list of layers and adds them to a new Component.

Parameters:

Name Type Description Default
layers LayerSpecs

list of layers to extract.

required
recursive bool

if True, extracts layers recursively and returns a flattened Component.

True
Source code in gdsfactory/component.py
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
def extract(
    self,
    layers: LayerSpecs,
    recursive: bool = True,
) -> Component:
    """Extracts a list of layers and adds them to a new Component.

    Args:
        layers: list of layers to extract.
        recursive: if True, extracts layers recursively and returns a flattened Component.
    """
    from gdsfactory.functions import extract

    return extract(self, layers=layers, recursive=recursive)

fill

fill(
    fill_cell: ComponentSpec,
    fill_layers: Iterable[tuple[LayerSpec, float]] = [],
    fill_regions: Iterable[tuple[Region, float]] = [],
    exclude_layers: Iterable[tuple[LayerSpec, float]] = [],
    exclude_regions: Iterable[tuple[Region, float]] = [],
    n_threads: int | None = None,
    tile_size: tuple[float, float] | None = None,
    row_step: DVector | None = None,
    col_step: DVector | None = None,
    x_space: float = 0.0,
    y_space: float = 0.0,
    tile_border: tuple[float, float] = (20, 20),
    multi: bool = False,
) -> None

Fill a [KCell][kfactory.kcell.KCell].

Parameters:

Name Type Description Default
fill_cell ComponentSpec

The cell used as a cell to fill the regions.

required
fill_layers Iterable[tuple[LayerSpec, float]]

Tuples of layer and keepout in um.

[]
fill_regions Iterable[tuple[Region, float]]

Specific regions to fill. Also tuples like the layers.

[]
exclude_layers Iterable[tuple[LayerSpec, float]]

Layers to ignore. Tuples like the fill layers

[]
exclude_regions Iterable[tuple[Region, float]]

Specific regions to ignore. Tuples like the fill layers.

[]
n_threads int | None

Max number of threads used. Defaults to number of cores of the machine.

None
tile_size tuple[float, float] | None

Size of the tiles in um.

None
row_step DVector | None

DVector for steping to the next instance position in the row. x-coordinate must be >= 0.

None
col_step DVector | None

DVector for steping to the next instance position in the column. y-coordinate must be >= 0.

None
x_space float

Spacing between the fill cell bounding boxes in x-direction.

0.0
y_space float

Spacing between the fill cell bounding boxes in y-direction.

0.0
tile_border tuple[float, float]

The tile border to consider for excludes

(20, 20)
multi bool

Use the region_fill_multi strategy instead of single fill.

False
Source code in gdsfactory/component.py
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
def fill(
    self,
    fill_cell: ComponentSpec,
    fill_layers: Iterable[tuple[LayerSpec, float]] = [],
    fill_regions: Iterable[tuple[kdb.Region, float]] = [],
    exclude_layers: Iterable[tuple[LayerSpec, float]] = [],
    exclude_regions: Iterable[tuple[kdb.Region, float]] = [],
    n_threads: int | None = None,
    tile_size: tuple[float, float] | None = None,
    row_step: kdb.DVector | None = None,
    col_step: kdb.DVector | None = None,
    x_space: float = 0.0,
    y_space: float = 0.0,
    tile_border: tuple[float, float] = (20, 20),
    multi: bool = False,
) -> None:
    """Fill a [KCell][kfactory.kcell.KCell].

    Args:
        fill_cell: The cell used as a cell to fill the regions.
        fill_layers: Tuples of layer and keepout in um.
        fill_regions: Specific regions to fill. Also tuples like the layers.
        exclude_layers: Layers to ignore. Tuples like the fill layers
        exclude_regions: Specific regions to ignore. Tuples like the fill layers.
        n_threads: Max number of threads used. Defaults to number of cores of the
            machine.
        tile_size: Size of the tiles in um.
        row_step: DVector for steping to the next instance position in the row.
            x-coordinate must be >= 0.
        col_step: DVector for steping to the next instance position in the column.
            y-coordinate must be >= 0.
        x_space: Spacing between the fill cell bounding boxes in x-direction.
        y_space: Spacing between the fill cell bounding boxes in y-direction.
        tile_border: The tile border to consider for excludes
        multi: Use the region_fill_multi strategy instead of single fill.
    """
    from gdsfactory.pdk import get_component, get_layer_info

    fill_cell = get_component(fill_cell)
    fill_layers_converted = [
        (get_layer_info(layer), int(spacing)) for layer, spacing in fill_layers
    ]
    fill_regions_converted = [
        (region, int(spacing)) for region, spacing in fill_regions
    ]
    exclude_layers_converted = [
        (get_layer_info(layer), int(spacing)) for layer, spacing in exclude_layers
    ]
    exclude_regions_converted = [
        (region, int(spacing)) for region, spacing in exclude_regions
    ]

    fill_tiled(
        self,
        fill_cell=fill_cell,
        fill_layers=fill_layers_converted,
        fill_regions=fill_regions_converted,
        exclude_layers=exclude_layers_converted,
        exclude_regions=exclude_regions_converted,
        n_threads=n_threads,
        tile_size=tile_size,
        row_step=row_step,
        col_step=col_step,
        x_space=x_space,
        y_space=y_space,
        tile_border=tile_border,
        multi=multi,
    )

fix_spacing

fix_spacing(
    layer: LayerSpec,
    min_space: float = 0.2,
    size_bias: float = 0.0,
) -> None

Fixes layer spacing in the Component.

Parameters:

Name Type Description Default
layer LayerSpec

layer to fix spacing on.

required
min_space float

minimum space in um.

0.2
size_bias float

optional geometry bias applied after spacing fix (um).

0.0
Source code in gdsfactory/component.py
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
def fix_spacing(
    self,
    layer: LayerSpec,
    min_space: float = 0.2,
    size_bias: float = 0.0,
) -> None:
    """Fixes layer spacing in the Component.

    Args:
        layer: layer to fix spacing on.
        min_space: minimum space in um.
        size_bias: optional geometry bias applied after spacing fix (um).
    """
    import gdsfactory as gf
    from gdsfactory.pdk import get_layer

    layer = get_layer(layer)
    layer_info = gf.kcl.get_info(layer)
    fix = fix_spacing_tiled(
        self.to_itype(), min_space=self.kcl.to_dbu(min_space), layer=layer_info
    )
    if size_bias:
        size_offset_dbu = self.kcl.to_dbu(size_bias)
        fix = fix.sized(+size_offset_dbu)
        fix = fix.sized(-size_offset_dbu)

    self.shapes(layer).insert(fix)

fix_width

fix_width(
    layer: LayerSpec,
    min_width: float = 0.2,
    n_threads: int | None = None,
    tile_size: tuple[float, float] | None = None,
    overlap: int = 1,
    smooth: int | None = None,
    flatten: bool = True,
) -> None

Fixes layer min width in the Component.

Parameters:

Name Type Description Default
layer LayerSpec

layer to fix width on.

required
min_width float

minimum width in um.

0.2
n_threads int | None

number of threads to use for processing.

None
tile_size tuple[float, float] | None

size of the tiles to use for processing.

None
overlap int

overlap between tiles.

1
smooth int | None

smooth the polygons by this amount in um.

None
flatten bool

if True, flattens the Component before fixing width.

True
Source code in gdsfactory/component.py
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
def fix_width(
    self,
    layer: LayerSpec,
    min_width: float = 0.2,
    n_threads: int | None = None,
    tile_size: tuple[float, float] | None = None,
    overlap: int = 1,
    smooth: int | None = None,
    flatten: bool = True,
) -> None:
    """Fixes layer min width in the Component.

    Args:
        layer: layer to fix width on.
        min_width: minimum width in um.
        n_threads: number of threads to use for processing.
        tile_size: size of the tiles to use for processing.
        overlap: overlap between tiles.
        smooth: smooth the polygons by this amount in um.
        flatten: if True, flattens the Component before fixing width.
    """
    import gdsfactory as gf
    from gdsfactory.pdk import get_layer

    if flatten:
        self.flatten()
    layer = get_layer(layer)
    layer_info = gf.kcl.get_info(layer)

    fix = fix_width_minkowski_tiled(
        self.to_itype(),
        min_width=self.kcl.to_dbu(min_width),
        ref=layer_info,
        n_threads=n_threads,
        tile_size=tile_size,
        overlap=overlap,
        smooth=smooth,
    )
    cast(kdb.Shapes, self.shapes(layer)).clear()  # type: ignore[redundant-cast]
    self.shapes(layer).insert(fix)

get_boxes

get_boxes(
    layer: LayerSpec, recursive: bool = True
) -> list[kf.kdb.DBox]

Returns a list of boxes.

Parameters:

Name Type Description Default
layer LayerSpec

layer to get boxes from.

required
recursive bool

if True, gets boxes recursively.

True
Source code in gdsfactory/component.py
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
def get_boxes(self, layer: LayerSpec, recursive: bool = True) -> list[kf.kdb.DBox]:
    """Returns a list of boxes.

    Args:
        layer: layer to get boxes from.
        recursive: if True, gets boxes recursively.
    """
    from gdsfactory import get_layer

    boxes: list[kf.kdb.DBox] = []

    layer = get_layer(layer)

    if recursive:
        iterator = self.kdb_cell.begin_shapes_rec(layer)
        iterator.shape_flags = kdb.Shapes.SBoxes
        boxes.extend(
            it.shape().dbox.transformed(it.dtrans()) for it in iterator.each()
        )
    else:
        boxes.extend(
            shape.dbox
            for shape in self.kdb_cell.shapes(layer).each(kdb.Shapes.SBoxes)
        )
    return boxes

get_labels

get_labels(
    layer: LayerSpec, recursive: bool = True
) -> list[kf.kdb.DText]

Returns a list of labels from the Component.

Parameters:

Name Type Description Default
layer LayerSpec

layer to get labels from.

required
recursive bool

if True, gets labels recursively.

True
Source code in gdsfactory/component.py
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
def get_labels(
    self, layer: LayerSpec, recursive: bool = True
) -> list[kf.kdb.DText]:
    """Returns a list of labels from the Component.

    Args:
        layer: layer to get labels from.
        recursive: if True, gets labels recursively.
    """
    from gdsfactory import get_layer

    texts: list[kf.kdb.DText] = []
    layer_enum = get_layer(layer)

    if recursive:
        iterator = self.kdb_cell.begin_shapes_rec(layer_enum)
        iterator.shape_flags = kdb.Shapes.STexts
        texts.extend(
            it.shape().dtext.transformed(it.dtrans()) for it in iterator.each()
        )
    else:
        texts.extend(
            shape.dtext
            for shape in self.kdb_cell.shapes(layer_enum).each(kdb.Shapes.STexts)
        )
    return texts

get_paths

get_paths(
    layer: LayerSpec, recursive: bool = True
) -> list[kf.kdb.DPath]

Returns a list of paths.

Parameters:

Name Type Description Default
layer LayerSpec

layer to get paths from.

required
recursive bool

if True, gets paths recursively.

True
Source code in gdsfactory/component.py
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
def get_paths(self, layer: LayerSpec, recursive: bool = True) -> list[kf.kdb.DPath]:
    """Returns a list of paths.

    Args:
        layer: layer to get paths from.
        recursive: if True, gets paths recursively.
    """
    from gdsfactory import get_layer

    paths: list[kf.kdb.DPath] = []

    layer = get_layer(layer)

    if recursive:
        iterator = self.kdb_cell.begin_shapes_rec(layer)
        iterator.shape_flags = kdb.Shapes.SPaths
        paths.extend(
            it.shape().dpath.transformed(it.dtrans()) for it in iterator.each()
        )
    else:
        paths.extend(
            shape.dpath
            for shape in self.kdb_cell.shapes(layer).each(kdb.Shapes.SPaths)
        )
    return paths

get_polygons

get_polygons(
    merge: bool = False,
    by: Literal["index", "name", "tuple"] = "index",
    layers: LayerSpecs | None = None,
    smooth: float | None = None,
) -> dict[
    tuple[int, int] | str | int, list[kf.kdb.Polygon]
]

Returns a dict of Polygons per layer.

Parameters:

Name Type Description Default
merge bool

if True, merges the polygons.

False
by Literal['index', 'name', 'tuple']

the format of the resulting keys in the dictionary ('index', 'name', 'tuple')

'index'
layers LayerSpecs | None

list of layers to get polygons from. Defaults to all layers.

None
smooth float | None

if True, smooths the polygons.

None
Source code in gdsfactory/component.py
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
def get_polygons(
    self,
    merge: bool = False,
    by: Literal["index", "name", "tuple"] = "index",
    layers: LayerSpecs | None = None,
    smooth: float | None = None,
) -> dict[tuple[int, int] | str | int, list[kf.kdb.Polygon]]:
    """Returns a dict of Polygons per layer.

    Args:
        merge: if True, merges the polygons.
        by: the format of the resulting keys in the dictionary ('index', 'name', 'tuple')
        layers: list of layers to get polygons from. Defaults to all layers.
        smooth: if True, smooths the polygons.
    """
    if merge and self.locked:
        raise LockedError(self)

    from gdsfactory.functions import get_polygons

    return get_polygons(self, merge=merge, by=by, layers=layers, smooth=smooth)

get_polygons_points

get_polygons_points(
    merge: bool = False,
    scale: float | None = None,
    by: Literal["index", "name", "tuple"] = "index",
    layers: LayerSpecs | None = None,
) -> dict[
    int | str | tuple[int, int],
    list[npt.NDArray[np.floating[Any]]],
]

Returns a dict with list of points per layer.

Parameters:

Name Type Description Default
merge bool

if True, merges the polygons.

False
scale float | None

if True, scales the points.

None
by Literal['index', 'name', 'tuple']

the format of the resulting keys in the dictionary ('index', 'name', 'tuple')

'index'
layers LayerSpecs | None

list of layers to get polygons from. Defaults to all layers.

None
Source code in gdsfactory/component.py
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
def get_polygons_points(
    self,
    merge: bool = False,
    scale: float | None = None,
    by: Literal["index", "name", "tuple"] = "index",
    layers: LayerSpecs | None = None,
) -> dict[int | str | tuple[int, int], list[npt.NDArray[np.floating[Any]]]]:
    """Returns a dict with list of points per layer.

    Args:
        merge: if True, merges the polygons.
        scale: if True, scales the points.
        by: the format of the resulting keys in the dictionary ('index', 'name', 'tuple')
        layers: list of layers to get polygons from. Defaults to all layers.
    """
    if merge and self.locked:
        raise LockedError(self)

    from gdsfactory.functions import get_polygons_points

    return get_polygons_points(self, merge=merge, scale=scale, by=by, layers=layers)

get_region

get_region(
    layer: LayerSpec,
    merge: bool = False,
    smooth: float | None = None,
) -> kdb.Region

Returns a Region of the Component.

Note that all operations that you do with the Region will be done in the database units.

Where for most processes 1 dbu = 1 nm.

Parameters:

Name Type Description Default
layer LayerSpec

layer to get region from.

required
merge bool

if True, merges the region.

False
smooth float | None

if True, smooths the region by the specified amount (in um).

None
Source code in gdsfactory/component.py
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
def get_region(
    self, layer: LayerSpec, merge: bool = False, smooth: float | None = None
) -> kdb.Region:
    """Returns a Region of the Component.

    Note that all operations that you do with the Region will be done in the database units.

    Where for most processes 1 dbu = 1 nm.

    Args:
        layer: layer to get region from.
        merge: if True, merges the region.
        smooth: if True, smooths the region by the specified amount (in um).
    """
    from gdsfactory import get_layer

    layer_index = get_layer(layer)
    r = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
    if smooth:
        r.smooth(self.kcl.to_dbu(smooth))
    if merge:
        r.merge()
    return r

offset

offset(
    layer: LayerSpec,
    distance: float,
    flatten: bool = False,
    corner_mode: int | CornerMode = 2,
) -> None

Offsets a Component layer by a distance in um.

Parameters:

Name Type Description Default
layer LayerSpec

layer to offset the Component on.

required
distance float

distance to offset the Component in um.

required
flatten bool

if True, flattens the Component before offsetting.

False
corner_mode int | CornerMode

determines behavior around corners

2
Source code in gdsfactory/component.py
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
def offset(
    self,
    layer: LayerSpec,
    distance: float,
    flatten: bool = False,
    corner_mode: int | CornerMode = 2,
) -> None:
    """Offsets a Component layer by a distance in um.

    Args:
        layer: layer to offset the Component on.
        distance: distance to offset the Component in um.
        flatten: if True, flattens the Component before offsetting.
        corner_mode: determines behavior around corners
    """
    from gdsfactory import get_layer

    if self.locked:
        raise LockedError(self)

    if flatten:
        self.flatten()

    distance_dbu = self.kcl.to_dbu(distance)

    layer_index = get_layer(layer)
    region = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
    region.size(distance_dbu, distance_dbu, corner_mode)
    self.remove_layers([layer])
    self.kdb_cell.shapes(layer_index).insert(region)

    self.kcl.layout.end_changes()

over_under

over_under(
    layer: LayerSpec,
    distance: float = 0.001,
    remove_old_layer: bool = True,
    corner_mode: int | CornerMode = 2,
) -> None

Returns a Component over-under on a layer in the Component.

For big components use tiled version.

Parameters:

Name Type Description Default
layer LayerSpec

layer to perform over-under on.

required
distance float

distance to perform over-under in um.

0.001
remove_old_layer bool

if True, removes the old layer.

True
corner_mode int | CornerMode

determines behavior around corners

2
Source code in gdsfactory/component.py
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
def over_under(
    self,
    layer: LayerSpec,
    distance: float = 0.001,
    remove_old_layer: bool = True,
    corner_mode: int | CornerMode = 2,
) -> None:
    """Returns a Component over-under on a layer in the Component.

    For big components use tiled version.

    Args:
        layer: layer to perform over-under on.
        distance: distance to perform over-under in um.
        remove_old_layer: if True, removes the old layer.
        corner_mode: determines behavior around corners
    """
    from gdsfactory import get_layer

    if self.locked:
        raise LockedError(self)

    distance_dbu = self.kcl.to_dbu(distance)

    layer_index = get_layer(layer)
    region = kdb.Region(self.kdb_cell.begin_shapes_rec(layer_index))
    region.size(+distance_dbu, +distance_dbu, corner_mode).size(
        -distance_dbu, -distance_dbu, corner_mode
    )

    if remove_old_layer:
        self.remove_layers([layer])
    self.kdb_cell.shapes(layer_index).insert(region)
    self.kcl.layout.end_changes()

plot

plot(
    lyrdb: Path | str | None = None,
    display_type: Literal["image", "widget"] | None = None,
    *,
    show_labels: bool = True,
    show_ruler: bool = True,
    pixel_buffer_options: PixelBufferOptions | None = None,
    return_fig: Literal[True] = True,
    ax: Axes | None = None
) -> Figure
plot(
    lyrdb: Path | str | None = None,
    display_type: Literal["image", "widget"] | None = None,
    *,
    show_labels: bool = True,
    show_ruler: bool = True,
    pixel_buffer_options: PixelBufferOptions | None = None,
    return_fig: Literal[False] = False,
    ax: Axes | None = None
) -> None
plot(
    lyrdb: Path | str | None = None,
    display_type: Literal["image", "widget"] | None = None,
    *,
    show_labels: bool = True,
    show_ruler: bool = True,
    pixel_buffer_options: PixelBufferOptions | None = None,
    return_fig: bool = False,
    ax: Axes | None = None
) -> Figure | None

Plots the Component using klayout.

Parameters:

Name Type Description Default
lyrdb Path | str | None

path to layer properties file.

None
display_type Literal['image', 'widget'] | None

if "image", displays the image.

None
show_labels bool

if True, shows labels.

True
show_ruler bool

if True, shows ruler.

True
pixel_buffer_options PixelBufferOptions | None

options for KLayout's get_pixels_with_options. If None, uses default values (width=800, height=600, linewidth=0, oversampling=0, resolution=0).

None
return_fig bool

if True, returns the figure.

False
ax Axes | None

Optional matplotlib Axes to plot on. If None, creates a new figure and axes. When specified, fig_size and dpi are determined by the provided axes' figure.

None
Source code in gdsfactory/component.py
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
def plot(
    self,
    lyrdb: pathlib.Path | str | None = None,
    display_type: Literal["image", "widget"] | None = None,
    *,
    show_labels: bool = True,
    show_ruler: bool = True,
    pixel_buffer_options: PixelBufferOptions | None = None,
    return_fig: bool = False,
    ax: Axes | None = None,
) -> Figure | None:
    """Plots the Component using klayout.

    Args:
        lyrdb: path to layer properties file.
        display_type: if "image", displays the image.
        show_labels: if True, shows labels.
        show_ruler: if True, shows ruler.
        pixel_buffer_options: options for KLayout's get_pixels_with_options.
            If None, uses default values (width=800, height=600, linewidth=0,
            oversampling=0, resolution=0).
        return_fig: if True, returns the figure.
        ax: Optional matplotlib Axes to plot on. If None, creates a new figure and axes. When specified, fig_size and dpi are determined by the provided axes' figure.
    """
    from io import BytesIO

    import matplotlib.pyplot as plt

    from gdsfactory.pdk import get_layer_views

    self.insert_vinsts()

    lyp_path = GDSDIR_TEMP / "layer_properties.lyp"
    layer_views = get_layer_views()
    layer_views.to_lyp(filepath=lyp_path)

    layout_view = lay.LayoutView()
    cell_view_index = layout_view.create_layout(True)
    layout_view.active_cellview_index = cell_view_index
    cell_view = layout_view.cellview(cell_view_index)
    layout = cell_view.layout()
    layout.assign(kf.kcl.layout)

    assert self.name is not None, "Component name is None"

    cell_view.cell = layout.cell(self.name)

    layout_view.max_hier()
    layout_view.load_layer_props(str(lyp_path))

    layout_view.add_missing_layers()
    layout_view.zoom_fit()

    layout_view.set_config("text-visible", "true" if show_labels else "false")
    layout_view.set_config("grid-show-ruler", "true" if show_ruler else "false")

    pixel_buffer = layout_view.get_pixels_with_options(
        **cast(
            dict[str, Any],
            ({"width": 800, "height": 600} | (pixel_buffer_options or {})),
        )
    )
    png_data = pixel_buffer.to_png_data()

    # Convert PNG data to NumPy array and display with matplotlib
    with BytesIO(png_data) as f:
        img_array = plt.imread(f)

    # Compute the figure dimensions based on the image size and desired DPI
    dpi = 80
    fig_width = img_array.shape[1] / dpi
    fig_height = img_array.shape[0] / dpi

    if ax is not None:
        fig = plt.gcf()  # Get the current figure (global figure, not subfigure)
    else:
        fig, ax = plt.subplots(figsize=(fig_width, fig_height), dpi=dpi)

    # Remove margins and display the image
    ax.imshow(img_array)
    ax.axis("off")  # Hide axes
    ax.set_position((0, 0, 1, 1))  # Set axes to occupy the full figure space

    plt.subplots_adjust(
        left=0, right=1, top=1, bottom=0, wspace=0, hspace=0
    )  # Remove any padding
    plt.tight_layout(pad=0)  # Ensure no space is wasted
    return fig if return_fig else None

plot_netlist

plot_netlist(
    recursive: bool = False,
    with_labels: bool = True,
    font_weight: str = "normal",
    **kwargs: Any
) -> nx.Graph

Plots a netlist graph with networkx.

Parameters:

Name Type Description Default
recursive bool

if True, returns a recursive netlist.

False
with_labels bool

add label to each node.

True
font_weight str

normal, bold.

'normal'
kwargs Any

keyword arguments to get_netlist.

{}

Other Parameters:

Name Type Description
tolerance

tolerance in grid_factor to consider two ports connected.

exclude_port_types

optional list of port types to exclude from netlisting.

get_instance_name

function to get instance name.

allow_multiple

False to raise an error if more than two ports share the same connection. if True, will return key: [value] pairs with [value] a list of all connected instances.

Source code in gdsfactory/component.py
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
def plot_netlist(
    self,
    recursive: bool = False,
    with_labels: bool = True,
    font_weight: str = "normal",
    **kwargs: Any,
) -> nx.Graph:
    """Plots a netlist graph with networkx.

    Args:
        recursive: if True, returns a recursive netlist.
        with_labels: add label to each node.
        font_weight: normal, bold.
        kwargs: keyword arguments to get_netlist.

    Keyword Args:
        tolerance: tolerance in grid_factor to consider two ports connected.
        exclude_port_types: optional list of port types to exclude from netlisting.
        get_instance_name: function to get instance name.
        allow_multiple: False to raise an error if more than two ports share the same connection. \
                if True, will return key: [value] pairs with [value] a list of all connected instances.
    """
    import matplotlib.pyplot as plt
    import networkx as nx

    plt.figure()
    netlist = self.get_netlist(recursive=recursive, **kwargs)
    G = nx.Graph()

    if recursive:
        pos: dict[str, tuple[float, float]] = {}
        labels: dict[str, str] = {}
        for net in netlist.values():
            nets = net.get("nets", [])
            connections = net.get("connections", {})
            connections = nets_to_connections(nets, connections)
            placements = net["placements"]
            G.add_edges_from(
                [
                    (",".join(k.split(",")[:-1]), ",".join(v.split(",")[:-1]))
                    for k, v in connections.items()
                ]
            )
            pos |= {k: (v["x"], v["y"]) for k, v in placements.items()}
            labels |= {k: ",".join(k.split(",")[:1]) for k in placements}

    else:
        nets = netlist.get("nets", [])
        connections = netlist.get("connections", {})
        connections = nets_to_connections(nets, connections)
        placements = netlist["placements"]
        G.add_edges_from(
            [
                (",".join(k.split(",")[:-1]), ",".join(v.split(",")[:-1]))
                for k, v in connections.items()
            ]
        )
        pos = {k: (v["x"], v["y"]) for k, v in placements.items()}
        labels = {k: ",".join(k.split(",")[:1]) for k in placements}

    nx.draw(
        G,
        with_labels=with_labels,
        font_weight=font_weight,
        labels=labels,
        pos=pos,
    )
    return G

plot_netlist_graphviz

plot_netlist_graphviz(
    recursive: bool = False,
    interactive: bool = False,
    splines: str = "ortho",
) -> None

Plots a netlist graph with graphviz.

Parameters:

Name Type Description Default
recursive bool

if True, returns a recursive netlist.

False
interactive bool

if True, opens the graph in a browser.

False
splines str

ortho, spline, polyline, line, curved.

'ortho'
Source code in gdsfactory/component.py
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
def plot_netlist_graphviz(
    self, recursive: bool = False, interactive: bool = False, splines: str = "ortho"
) -> None:
    """Plots a netlist graph with graphviz.

    Args:
        recursive: if True, returns a recursive netlist.
        interactive: if True, opens the graph in a browser.
        splines: ortho, spline, polyline, line, curved.
    """
    from gdsfactory.schematic import plot_graphviz

    n = self.to_graphviz(
        recursive=recursive,
    )
    plot_graphviz(n, splines=splines, interactive=interactive)

remap_layers

remap_layers(
    layer_map: dict[LayerSpec, LayerSpec],
    recursive: bool = False,
) -> Self

Remaps a list of layers and returns the same Component.

Parameters:

Name Type Description Default
layer_map dict[LayerSpec, LayerSpec]

dictionary of layers to remap.

required
recursive bool

if True, remaps layers recursively.

False
Source code in gdsfactory/component.py
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
def remap_layers(
    self, layer_map: dict[LayerSpec, LayerSpec], recursive: bool = False
) -> Self:
    """Remaps a list of layers and returns the same Component.

    Args:
        layer_map: dictionary of layers to remap.
        recursive: if True, remaps layers recursively.
    """
    from gdsfactory import get_layer

    if self.locked:
        raise LockedError(self)

    layer_index_pairs = [
        (get_layer(layer), get_layer(new_layer))
        for layer, new_layer in layer_map.items()
    ]
    kdb_cell = self.kdb_cell
    for src_layer_index, dst_layer_index in layer_index_pairs:
        kdb_cell.move(src_layer_index, dst_layer_index)

    if recursive:
        for ci in kdb_cell.called_cells():
            child_cell = self.kcl[ci].kdb_cell
            for src_layer_index, dst_layer_index in layer_index_pairs:
                child_cell.move(src_layer_index, dst_layer_index)
    return self

remove_layers

remove_layers(
    layers: LayerSpecs, recursive: bool = True
) -> Self

Removes a list of layers and returns the same Component.

Parameters:

Name Type Description Default
layers LayerSpecs

list of layers to remove.

required
recursive bool

if True, removes layers recursively and temporarily unlocks components.

True
Source code in gdsfactory/component.py
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
def remove_layers(
    self,
    layers: LayerSpecs,
    recursive: bool = True,
) -> Self:
    """Removes a list of layers and returns the same Component.

    Args:
        layers: list of layers to remove.
        recursive: if True, removes layers recursively and temporarily unlocks components.
    """
    from gdsfactory import get_layer

    if recursive:
        self.locked = False

    if self.locked:
        raise LockedError(self)

    layer_indexes = self.kcl.layer_indexes()
    layer_indexes_to_remove = [get_layer(layer) for layer in layers]
    layer_indices = [
        layer for layer in layer_indexes_to_remove if layer in layer_indexes
    ]
    if not layer_indices:
        return self

    kdb_cell = self.kdb_cell
    for layer_index in layer_indices:
        kdb_cell.shapes(layer_index).clear()

    if recursive:
        for ci in kdb_cell.called_cells():
            child = self.kcl[ci]
            child_cell = child.kdb_cell
            was_locked = child.locked
            child.locked = False
            try:
                for layer_idx in layer_indices:
                    child_cell.shapes(layer_idx).clear()
            finally:
                if was_locked:
                    child.locked = True
    return self

to_3d

to_3d(
    layer_views: LayerViews | None = None,
    layer_stack: LayerStack | None = None,
    exclude_layers: Sequence[Layer] | None = None,
) -> Scene

Return Component 3D trimesh Scene.

Parameters:

Name Type Description Default
component

to extrude in 3D.

required
layer_views LayerViews | None

layer colors from Klayout Layer Properties file. Defaults to active PDK.layer_views.

None
layer_stack LayerStack | None

contains thickness and zmin for each layer. Defaults to active PDK.layer_stack.

None
exclude_layers Sequence[Layer] | None

layers to exclude.

None
Source code in gdsfactory/component.py
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
def to_3d(
    self,
    layer_views: LayerViews | None = None,
    layer_stack: LayerStack | None = None,
    exclude_layers: Sequence[Layer] | None = None,
) -> Scene:
    """Return Component 3D trimesh Scene.

    Args:
        component: to extrude in 3D.
        layer_views: layer colors from Klayout Layer Properties file.
            Defaults to active PDK.layer_views.
        layer_stack: contains thickness and zmin for each layer.
            Defaults to active PDK.layer_stack.
        exclude_layers: layers to exclude.

    """
    from gdsfactory.export.to_3d import to_3d

    return to_3d(
        self,
        layer_views=layer_views,
        layer_stack=layer_stack,
        exclude_layers=exclude_layers,
    )

to_graphviz

to_graphviz(recursive: bool = False) -> Digraph

Returns a netlist graph with graphviz.

Parameters:

Name Type Description Default
recursive bool

if True, returns a recursive netlist.

False
Source code in gdsfactory/component.py
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
def to_graphviz(self, recursive: bool = False) -> Digraph:
    """Returns a netlist graph with graphviz.

    Args:
        recursive: if True, returns a recursive netlist.
    """
    from gdsfactory.schematic import to_graphviz

    netlist = self.get_netlist(recursive=recursive)
    return to_graphviz(
        netlist["instances"],
        placements=netlist["placements"],
        nets=netlist["nets"],
    )

trim

trim(
    left: float,
    bottom: float,
    right: float,
    top: float,
    flatten: bool = False,
) -> None

Trims the Component to a bounding box.

Parameters:

Name Type Description Default
left float

left coordinate of the bounding box.

required
bottom float

bottom coordinate of the bounding box.

required
right float

right coordinate of the bounding box.

required
top float

top coordinate of the bounding box.

required
flatten bool

if True, flattens the Component.

False
Source code in gdsfactory/component.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
def trim(
    self,
    left: float,
    bottom: float,
    right: float,
    top: float,
    flatten: bool = False,
) -> None:
    """Trims the Component to a bounding box.

    Args:
        left: left coordinate of the bounding box.
        bottom: bottom coordinate of the bounding box.
        right: right coordinate of the bounding box.
        top: top coordinate of the bounding box.
        flatten: if True, flattens the Component.
    """
    if self.locked:
        raise LockedError(self)

    c = self

    domain_box = kdb.DBox(left, bottom, right, top)
    if not c.dbbox().inside(domain_box):
        kdb_cell = c.kcl.layout.clip(c.kdb_cell, kdb.DBox(left, bottom, right, top))
        c.kdb_cell.clear()
        c.kdb_cell.copy_tree(kdb_cell)
        kdb_cell.delete()
        if flatten:
            c.flatten()

write

write(
    filename: str | Path,
    save_options: SaveLayoutOptions | None = None,
    convert_external_cells: bool = False,
    set_meta_data: bool = True,
    autoformat_from_file_extension: bool = True,
    deduplicate_cell_names: bool = True,
) -> None

Write component to GDS, fixing pin metadata for kfactory compat.

Source code in gdsfactory/component.py
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
@override
def write(
    self,
    filename: str | pathlib.Path,
    save_options: kdb.SaveLayoutOptions | None = None,
    convert_external_cells: bool = False,
    set_meta_data: bool = True,
    autoformat_from_file_extension: bool = True,
    deduplicate_cell_names: bool = True,
) -> None:
    """Write component to GDS, fixing pin metadata for kfactory compat."""
    if set_meta_data:
        self.insert_vinsts()
        self.kcl.set_meta_data()
        for ci in self.called_cells():
            kcell = self.kcl[ci]
            if not kcell._destroyed():
                if convert_external_cells and kcell.is_library_cell():
                    kcell.convert_to_static(recursive=True)
                kcell.set_meta_data()
                _fix_pin_metadata(kcell)
        if convert_external_cells and self.is_library_cell():
            self.convert_to_static(recursive=True)
        self.set_meta_data()
        _fix_pin_metadata(self)
    super().write(
        filename,
        save_options=save_options,
        convert_external_cells=False,
        set_meta_data=False,
        autoformat_from_file_extension=autoformat_from_file_extension,
        deduplicate_cell_names=deduplicate_cell_names,
    )

ComponentAllAngle

Bases: ComponentBase, VKCell

Source code in gdsfactory/component.py
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
class ComponentAllAngle(ComponentBase, kf.VKCell):
    def plot(self, **kwargs: Any) -> None:
        """Plots the Component using klayout."""
        c = Component()
        if self.name is not None:
            c.name = self.name

        VInstance(self).insert_into_flat(c, levels=0)
        c.plot(**kwargs)

    def dup(self, new_name: str | None = None) -> ComponentAllAngle:
        """Copy the full cell."""
        c = self.__class__(
            kcl=self.kcl, name=new_name or self.name + "$1" if self.name else None
        )
        c.ports = self.ports.copy()

        c.settings = self.settings.model_copy()
        c.settings_units = self.settings_units.model_copy()
        c.info = self.info.model_copy()
        for layer, shapes in self.shapes().items():
            for shape in shapes:
                c.shapes(layer).insert(shape)
        c._base.vinsts = self.vinsts.dup()

        return c

    def add_polygon(self, points: _PolygonPoints, layer: LayerSpec) -> kdb.Shape | None:
        """Adds a Polygon to the Component and returns a klayout Shape.

        Args:
            points: Coordinates of the vertices of the Polygon.
            layer: layer spec to add polygon on.
        """
        from gdsfactory.pdk import get_layer

        if self.locked:
            raise LockedError(self)

        _layer = get_layer(layer)

        polygon = points_to_polygon(points)

        res = self.shapes(_layer).insert(polygon)  # type: ignore[func-returns-value]
        return res

    def get_polygons(self, layer: LayerSpec) -> list[kf.kdb.DPolygon]:
        """Returns a list of polygons from the Component."""
        from gdsfactory import get_layer

        return [x for x in self.shapes(get_layer(layer)) if isinstance(x, kdb.DPolygon)]

add_polygon

add_polygon(
    points: _PolygonPoints, layer: LayerSpec
) -> kdb.Shape | None

Adds a Polygon to the Component and returns a klayout Shape.

Parameters:

Name Type Description Default
points _PolygonPoints

Coordinates of the vertices of the Polygon.

required
layer LayerSpec

layer spec to add polygon on.

required
Source code in gdsfactory/component.py
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
def add_polygon(self, points: _PolygonPoints, layer: LayerSpec) -> kdb.Shape | None:
    """Adds a Polygon to the Component and returns a klayout Shape.

    Args:
        points: Coordinates of the vertices of the Polygon.
        layer: layer spec to add polygon on.
    """
    from gdsfactory.pdk import get_layer

    if self.locked:
        raise LockedError(self)

    _layer = get_layer(layer)

    polygon = points_to_polygon(points)

    res = self.shapes(_layer).insert(polygon)  # type: ignore[func-returns-value]
    return res

dup

dup(new_name: str | None = None) -> ComponentAllAngle

Copy the full cell.

Source code in gdsfactory/component.py
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
def dup(self, new_name: str | None = None) -> ComponentAllAngle:
    """Copy the full cell."""
    c = self.__class__(
        kcl=self.kcl, name=new_name or self.name + "$1" if self.name else None
    )
    c.ports = self.ports.copy()

    c.settings = self.settings.model_copy()
    c.settings_units = self.settings_units.model_copy()
    c.info = self.info.model_copy()
    for layer, shapes in self.shapes().items():
        for shape in shapes:
            c.shapes(layer).insert(shape)
    c._base.vinsts = self.vinsts.dup()

    return c

get_polygons

get_polygons(layer: LayerSpec) -> list[kf.kdb.DPolygon]

Returns a list of polygons from the Component.

Source code in gdsfactory/component.py
1651
1652
1653
1654
1655
def get_polygons(self, layer: LayerSpec) -> list[kf.kdb.DPolygon]:
    """Returns a list of polygons from the Component."""
    from gdsfactory import get_layer

    return [x for x in self.shapes(get_layer(layer)) if isinstance(x, kdb.DPolygon)]

plot

plot(**kwargs: Any) -> None

Plots the Component using klayout.

Source code in gdsfactory/component.py
1606
1607
1608
1609
1610
1611
1612
1613
def plot(self, **kwargs: Any) -> None:
    """Plots the Component using klayout."""
    c = Component()
    if self.name is not None:
        c.name = self.name

    VInstance(self).insert_into_flat(c, levels=0)
    c.plot(**kwargs)

ComponentReference module-attribute

ComponentReference: TypeAlias = DInstance

import_gds

import_gds

import_gds(
    gdspath: str | Path,
    cellname: str | None = None,
    post_process: PostProcesses | None = None,
    rename_duplicated_cells: bool = False,
    skip_new_cells: bool = False,
) -> Component

Reads a GDS file and returns a Component.

Parameters:

Name Type Description Default
gdspath str | Path

path to GDS file.

required
cellname str | None

name of the cell to return. Defaults to top cell.

None
post_process PostProcesses | None

function to run after reading the GDS file.

None
rename_duplicated_cells bool

if True, rename duplicated cells. By default appends $n to the cell name.

False
skip_new_cells bool

if True, skip new cells that conflict with existing ones.

False
Source code in gdsfactory/read/import_gds.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def import_gds(
    gdspath: str | Path,
    cellname: str | None = None,
    post_process: PostProcesses | None = None,
    rename_duplicated_cells: bool = False,
    skip_new_cells: bool = False,
) -> Component:
    """Reads a GDS file and returns a Component.

    Args:
        gdspath: path to GDS file.
        cellname: name of the cell to return. Defaults to top cell.
        post_process: function to run after reading the GDS file.
        rename_duplicated_cells: if True, rename duplicated cells. By default appends $n to the cell name.
        skip_new_cells: if True, skip new cells that conflict with existing ones.

    """
    options = utilities.load_layout_options()
    options.warn_level = 0

    if skip_new_cells:
        options.cell_conflict_resolution = (
            kf.kdb.LoadLayoutOptions.CellConflictResolution.SkipNewCell
        )
    elif rename_duplicated_cells:
        options.cell_conflict_resolution = (
            kf.kdb.LoadLayoutOptions.CellConflictResolution.RenameCell
        )

    with temporary_kcl(str(gdspath)) as temp_kcl:
        temp_kcl.read(gdspath, options=options)

        if cellname is None:
            if len(temp_kcl.layout.top_cells()) > 1:
                raise ValueError(
                    "GDS file has multiple top cells. Use cellname to select a specific one, or use gf.read.import_gds_multiple_top_cells instead.\n"
                    + f"Top cells: {[c.name for c in temp_kcl.layout.top_cells()]}"
                )
            cellname = temp_kcl.layout.top_cell().name

        kcell = temp_kcl[cellname]

        if hasattr(temp_kcl, "cross_sections"):
            for cross_section in temp_kcl.cross_sections.cross_sections.values():
                try:
                    kf.kcl.get_symmetrical_cross_section(cross_section)
                except CrossSectionNamingConflictError:
                    canonical = kf.kcl.cross_sections.cross_sections.get(
                        cross_section.auto_name()
                    )
                    radius_conflict = canonical is not None and (
                        (
                            cross_section.radius is not None
                            and cross_section.radius != canonical.radius
                        )
                        or (
                            cross_section.radius_min is not None
                            and cross_section.radius_min != canonical.radius_min
                        )
                    )
                    if canonical is None or radius_conflict:
                        raise
                    warn(
                        f"Cross section {cross_section.name!r} in {gdspath} matches "
                        f"already-registered {canonical.name!r}; imported ports will "
                        f"use {canonical.name!r}.",
                        stacklevel=2,
                    )
                    kf.kcl.cross_sections.cross_sections[cross_section.name] = canonical

        c = kcell_to_component(kcell)
        for pp in post_process or []:
            pp(c)

        return c

import_gds_multiple_top_cells

import_gds_multiple_top_cells(
    gdspath: str | Path,
    cellnames: list[str] | None = None,
    post_process: PostProcesses | None = None,
    rename_duplicated_cells: bool = False,
    skip_new_cells: bool = False,
) -> dict[str, Component]

Reads a GDS file and returns a dictionary of its top cells as Components.

Parameters:

Name Type Description Default
gdspath str | Path

path to GDS file.

required
cellnames list[str] | None

list of names of the cells to return. Defaults to all top cells.

None
post_process PostProcesses | None

list of post-processing functions to apply to each cell.

None
rename_duplicated_cells bool

if True, renames duplicated cells instead of raising an error.

False
skip_new_cells bool

if True, skips new cells instead of raising an error.

False

Returns:

Type Description
dict[str, Component]

dict of cellname to Component.

Raises:

Type Description
ValueError

if any of the provided cellnames are not found among the top cells.

Source code in gdsfactory/read/import_gds.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def import_gds_multiple_top_cells(
    gdspath: str | Path,
    cellnames: list[str] | None = None,
    post_process: PostProcesses | None = None,
    rename_duplicated_cells: bool = False,
    skip_new_cells: bool = False,
) -> dict[str, Component]:
    """Reads a GDS file and returns a dictionary of its top cells as Components.

    Args:
        gdspath: path to GDS file.
        cellnames: list of names of the cells to return. Defaults to all top cells.
        post_process: list of post-processing functions to apply to each cell.
        rename_duplicated_cells: if True, renames duplicated cells instead of raising an error.
        skip_new_cells: if True, skips new cells instead of raising an error.

    Returns:
        dict of cellname to Component.

    Raises:
        ValueError: if any of the provided cellnames are not found among the top cells.

    """
    options = utilities.load_layout_options()
    options.warn_level = 0

    if skip_new_cells:
        options.cell_conflict_resolution = (
            kf.kdb.LoadLayoutOptions.CellConflictResolution.SkipNewCell
        )
    elif rename_duplicated_cells:
        options.cell_conflict_resolution = (
            kf.kdb.LoadLayoutOptions.CellConflictResolution.RenameCell
        )

    with temporary_kcl(str(gdspath)) as temp_kcl:
        temp_kcl.read(gdspath, options=options)

        components = {}

        kcells = temp_kcl.layout.top_cells()

        if cellnames is not None:
            # Validate provided cellnames and surface all invalid names at once
            available_cellnames = {kcell.name for kcell in kcells}
            missing = set(cellnames) - available_cellnames
            if missing:
                raise ValueError(
                    "Unknown cellnames requested. These names are not present in the GDS top cells: "
                    + ", ".join(sorted(missing))
                    + ".\n"
                    + f"Available top cells: {sorted(available_cellnames)}"
                )
            # Filter kcells to include only those specified in cellnames
            kcells = [kcell for kcell in kcells if kcell.name in cellnames]

        for kcell in kcells:
            components[kcell.name] = kcell_to_component(
                temp_kcl[kcell.name]
            )  # Convert each kcell to Component class and store in dictionary using its name as the key

        for pp in post_process or []:
            for c in components.values():
                pp(c)

        return components

import_gds_with_conflicts

import_gds_with_conflicts(
    gdspath: str | Path, cellname: str | None = None
) -> Component

Reads a GDS file and returns a Component.

Parameters:

Name Type Description Default
gdspath str | Path

path to GDS file.

required
cellname str | None

name of the cell to return. Defaults to top cell.

None
Modes

AddToCell: Add content to existing cell. Content of new cells is simply added to existing cells with the same name. OverwriteCell: The old cell is overwritten entirely (including child cells which are not used otherwise) RenameCell: The new cell will be renamed to become unique SkipNewCell: The new cell is skipped entirely (including child cells which are not used otherwise)

Source code in gdsfactory/read/import_gds.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def import_gds_with_conflicts(
    gdspath: str | Path,
    cellname: str | None = None,
) -> Component:
    """Reads a GDS file and returns a Component.

    Args:
        gdspath: path to GDS file.
        cellname: name of the cell to return. Defaults to top cell.

    Modes:
        AddToCell: Add content to existing cell. Content of new cells is simply added to existing cells with the same name.
        OverwriteCell: The old cell is overwritten entirely (including child cells which are not used otherwise)
        RenameCell: The new cell will be renamed to become unique
        SkipNewCell: The new cell is skipped entirely (including child cells which are not used otherwise)
    """
    return import_gds(gdspath, cellname=cellname, rename_duplicated_cells=True)

from_yaml

Returns Component from YAML syntax.

name: myComponent settings: length: 3

info

description: just a demo polarization: TE ...

instances

mzi: component: mzi_phase_shifter settings: delta_length: ${settings.length} length_x: 50

pads: component: pad_array settings: n: 2 port_names: - e4

placements

mzi: x: 0 pads: y: 200 x: mzi,cc

ports: o1: mzi,o1 o2: mzi,o2

routes

electrical: links: mzi,etop_e1: pads,e4_0 mzi,etop_e2: pads,e4_1

settings:
    layer: [31, 0]
    width: 10
    radius: 10

cell_from_yaml

cell_from_yaml(
    yaml_str: str | Path | IO[Any] | dict[str, Any],
    routing_strategies: RoutingStrategies | None = None,
    label_instance_function: LabelInstanceFunction = add_instance_label,
    name: str | None = None,
) -> Callable[[], Component]

Returns Component factory from YAML string or file.

YAML includes instances, placements, routes, ports and connections.

Parameters:

Name Type Description Default
yaml_str str | Path | IO[Any] | dict[str, Any]

YAML string or file.

required
routing_strategies RoutingStrategies | None

for each route.

None
label_instance_function LabelInstanceFunction

to label each instance.

add_instance_label
name str | None

Optional name.

None
kwargs

function settings for creating YAML PCells.

required
valid variables
required
name str | None

Optional Component name

None
settings

Optional variables

required
pdk

overrides

required
info

Optional component info description: just a demo polarization: TE ...

required
instances

name: component: (ComponentSpec) settings (Optional) length: 10 ...

required
placements

x: float, str | None str can be instanceName,portName y: float, str | None rotation: float | None mirror: bool, float | None float is x mirror axis port: str | None port anchor

required
connections Optional

between instances

required
ports Optional

ports to expose

required
routes Optional

bundles of routes routeName: library: optical links: instance1,port1: instance2,port2

required
settings

length_mmi: 5

required
instances

mmi_bot: component: mmi1x2 settings: width_mmi: 4.5 length_mmi: 10 mmi_top: component: mmi1x2 settings: width_mmi: 4.5 length_mmi: ${settings.length_mmi}

required
placements

mmi_top: port: o1 x: 0 y: 0 mmi_bot: port: o1 x: mmi_top,o2 y: mmi_top,o2 dx: 30 dy: -30

required
routes

optical: library: optical links: mmi_top,o3: mmi_bot,o1

required
Source code in gdsfactory/read/from_yaml.py
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
def cell_from_yaml(
    yaml_str: str | pathlib.Path | IO[Any] | dict[str, Any],
    routing_strategies: RoutingStrategies | None = None,
    label_instance_function: LabelInstanceFunction = add_instance_label,
    name: str | None = None,
) -> Callable[[], Component]:
    """Returns Component factory from YAML string or file.

    YAML includes instances, placements, routes, ports and connections.

    Args:
        yaml_str: YAML string or file.
        routing_strategies: for each route.
        label_instance_function: to label each instance.
        name: Optional name.
        kwargs: function settings for creating YAML PCells.

        valid variables:

        name: Optional Component name
        settings: Optional variables
        pdk: overrides
        info: Optional component info
            description: just a demo
            polarization: TE
            ...
        instances:
            name:
                component: (ComponentSpec)
                settings (Optional)
                    length: 10
                    ...
        placements:
            x: float, str | None  str can be instanceName,portName
            y: float, str | None
            rotation: float | None
            mirror: bool, float | None float is x mirror axis
            port: str | None port anchor
        connections (Optional): between instances
        ports (Optional): ports to expose
        routes (Optional): bundles of routes
            routeName:
            library: optical
            links:
                instance1,port1: instance2,port2

        settings:
            length_mmi: 5

        instances:
            mmi_bot:
              component: mmi1x2
              settings:
                width_mmi: 4.5
                length_mmi: 10
            mmi_top:
              component: mmi1x2
              settings:
                width_mmi: 4.5
                length_mmi: ${settings.length_mmi}

        placements:
            mmi_top:
                port: o1
                x: 0
                y: 0
            mmi_bot:
                port: o1
                x: mmi_top,o2
                y: mmi_top,o2
                dx: 30
                dy: -30
        routes:
            optical:
                library: optical
                links:
                    mmi_top,o3: mmi_bot,o1

    """
    routing_strategies = routing_strategies or {}

    return partial(
        from_yaml,
        yaml_str=yaml_str,
        routing_strategies=routing_strategies,
        label_instance_function=label_instance_function,
        name=name,
    )

from_yaml

from_yaml(
    yaml_str: str | Path | IO[Any] | dict[str, Any],
    routing_strategies: RoutingStrategies | None = None,
    label_instance_function: LabelInstanceFunction = add_instance_label,
    name: str | None = None,
) -> Component

Returns Component from YAML string or file.

YAML includes instances, placements, routes, ports and connections.

Parameters:

Name Type Description Default
yaml_str str | Path | IO[Any] | dict[str, Any]

YAML string or file.

required
routing_strategies RoutingStrategies | None

for each route.

None
label_instance_function LabelInstanceFunction

to label each instance.

add_instance_label
name str | None

Optional name.

None
valid variables
required
name str | None

Optional Component name

None
settings

Optional variables

required
pdk

overrides

required
info

Optional component info description: just a demo polarization: TE ...

required
instances

name: component: (ComponentSpec) settings (Optional) length: 10 ...

required
placements

x: float, str | None str can be instanceName,portName y: float, str | None rotation: float | None mirror: bool, float | None float is x mirror axis port: str | None port anchor

required
connections Optional

between instances

required
ports Optional

ports to expose

required
routes Optional

bundles of routes routeName: library: optical links: instance1,port1: instance2,port2

required
settings

length_mmi: 5

required
instances

mmi_bot: component: mmi1x2 settings: width_mmi: 4.5 length_mmi: 10 mmi_top: component: mmi1x2 settings: width_mmi: 4.5 length_mmi: ${settings.length_mmi}

required
placements

mmi_top: port: o1 x: 0 y: 0 mmi_bot: port: o1 x: mmi_top,o2 y: mmi_top,o2 dx: 30 dy: -30

required
routes

optical: library: optical links: mmi_top,o3: mmi_bot,o1

required
Source code in gdsfactory/read/from_yaml.py
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
def from_yaml(
    yaml_str: str | pathlib.Path | IO[Any] | dict[str, Any],
    routing_strategies: RoutingStrategies | None = None,
    label_instance_function: LabelInstanceFunction = add_instance_label,
    name: str | None = None,
) -> Component:
    """Returns Component from YAML string or file.

    YAML includes instances, placements, routes, ports and connections.

    Args:
        yaml_str: YAML string or file.
        routing_strategies: for each route.
        label_instance_function: to label each instance.
        name: Optional name.

        valid variables:

        name: Optional Component name
        settings: Optional variables
        pdk: overrides
        info: Optional component info
            description: just a demo
            polarization: TE
            ...
        instances:
            name:
                component: (ComponentSpec)
                settings (Optional)
                    length: 10
                    ...
        placements:
            x: float, str | None  str can be instanceName,portName
            y: float, str | None
            rotation: float | None
            mirror: bool, float | None float is x mirror axis
            port: str | None port anchor
        connections (Optional): between instances
        ports (Optional): ports to expose
        routes (Optional): bundles of routes
            routeName:
            library: optical
            links:
                instance1,port1: instance2,port2

        settings:
            length_mmi: 5

        instances:
            mmi_bot:
              component: mmi1x2
              settings:
                width_mmi: 4.5
                length_mmi: 10
            mmi_top:
              component: mmi1x2
              settings:
                width_mmi: 4.5
                length_mmi: ${settings.length_mmi}

        placements:
            mmi_top:
                port: o1
                x: 0
                y: 0
            mmi_bot:
                port: o1
                x: mmi_top,o2
                y: mmi_top,o2
                dx: 30
                dy: -30
        routes:
            optical:
                library: optical
                links:
                    mmi_top,o3: mmi_bot,o1

    """
    from gdsfactory.pdk import get_active_pdk

    routing_strategies = routing_strategies or {}

    c = Component()
    dct = _load_yaml_str(yaml_str)
    pdk = get_active_pdk()
    net = Netlist.model_validate(dct)
    g = _get_dependency_graph(net)
    refs = _get_references(c, pdk, net.instances)
    _place_and_connect(g, refs, net.connections, net.placements)
    c = _add_routes(c, refs, net.routes, routing_strategies)
    c = _add_ports(c, refs, net.ports)
    c = _add_labels(c, refs, label_instance_function)
    c.name = name or net.name or c.name  # pyright: ignore
    return c

make_connection

make_connection(
    instance_src_name: str,
    port_src_name: str,
    instance_dst_name: str,
    port_dst_name: str,
    instances: dict[str, InstanceOrVInstance],
    src_ia: int | None = None,
    src_ib: int | None = None,
    dst_ia: int | None = None,
    dst_ib: int | None = None,
) -> None

Connect instance_src_name,port to instance_dst_name,port.

Parameters:

Name Type Description Default
instance_src_name str

source instance name.

required
port_src_name str

from instance_src_name.

required
instance_dst_name str

destination instance name.

required
port_dst_name str

from instance_dst_name.

required
instances dict[str, InstanceOrVInstance]

dict of instances.

required
src_ia int | None

the a-index of the source instance, if it is an arrayed instance

None
src_ib int | None

the b-index of the source instance, if it is an arrayed instance

None
dst_ia int | None

the a-index of the destination instance, if it is an arrayed instance

None
dst_ib int | None

the b-index of the destination instance, if it is an arrayed instance

None
Source code in gdsfactory/read/from_yaml.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
def make_connection(
    instance_src_name: str,
    port_src_name: str,
    instance_dst_name: str,
    port_dst_name: str,
    instances: dict[str, InstanceOrVInstance],
    src_ia: int | None = None,
    src_ib: int | None = None,
    dst_ia: int | None = None,
    dst_ib: int | None = None,
) -> None:
    """Connect instance_src_name,port to instance_dst_name,port.

    Args:
        instance_src_name: source instance name.
        port_src_name: from instance_src_name.
        instance_dst_name: destination instance name.
        port_dst_name: from instance_dst_name.
        instances: dict of instances.
        src_ia: the a-index of the source instance, if it is an arrayed instance
        src_ib: the b-index of the source instance, if it is an arrayed instance
        dst_ia: the a-index of the destination instance, if it is an arrayed instance
        dst_ib: the b-index of the destination instance, if it is an arrayed instance

    """
    instance_src_name = instance_src_name.strip()
    instance_dst_name = instance_dst_name.strip()
    port_src_name = port_src_name.strip()
    port_dst_name = port_dst_name.strip()

    if instance_src_name not in instances:
        raise ValueError(f"{instance_src_name!r} not in {list(instances.keys())}")
    if instance_dst_name not in instances:
        raise ValueError(f"{instance_dst_name!r} not in {list(instances.keys())}")
    instance_src = instances[instance_src_name]
    instance_dst = instances[instance_dst_name]

    if port_src_name not in instance_src.ports:
        instance_src_port_names = [p.name for p in instance_src.ports]
        raise ValueError(
            f"{port_src_name!r} not in {instance_src_port_names} for"
            f" {instance_src_name!r} "
        )
    if port_dst_name not in instance_dst.ports:
        instance_dst_port_names = [p.name for p in instance_dst.ports]
        raise ValueError(
            f"{port_dst_name!r} not in {instance_dst_port_names} for"
            f" {instance_dst_name!r}"
        )

    if src_ia is None or src_ib is None:
        src_port = instance_src.ports[port_src_name]
    else:
        src_port = instance_src.ports[port_src_name, src_ia, src_ib]

    if dst_ia is None or dst_ib is None:
        dst_port = instance_dst.ports[port_dst_name]
    else:
        dst_port = instance_dst.ports[port_dst_name, dst_ia, dst_ib]
    instance_src.connect(port=src_port, other=dst_port, use_mirror=True, mirror=False)

place

place(
    placements_conf: dict[
        str, dict[str, int | float | str]
    ],
    connections_by_transformed_inst: dict[
        str, dict[str, str]
    ],
    instances: dict[str, InstanceOrVInstance],
    encountered_insts: list[str],
    instance_name: str | None = None,
    all_remaining_insts: list[str] | None = None,
) -> None

Place instance_name based on placements_conf config.

Parameters:

Name Type Description Default
placements_conf dict[str, dict[str, int | float | str]]

Dict of instance_name to placement (x, y, rotation ...).

required
connections_by_transformed_inst dict[str, dict[str, str]]

Dict of connection attributes. keyed by the name of the instance which should be transformed.

required
instances dict[str, InstanceOrVInstance]

Dict of references.

required
encountered_insts list[str]

list of encountered_instances.

required
instance_name str | None

instance_name to place.

None
all_remaining_insts list[str] | None

list of all the remaining instances to place instances pop from this instance as they are placed.

None
Source code in gdsfactory/read/from_yaml.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
def place(
    placements_conf: dict[str, dict[str, int | float | str]],
    connections_by_transformed_inst: dict[str, dict[str, str]],
    instances: dict[str, InstanceOrVInstance],
    encountered_insts: list[str],
    instance_name: str | None = None,
    all_remaining_insts: list[str] | None = None,
) -> None:
    """Place instance_name based on placements_conf config.

    Args:
        placements_conf: Dict of instance_name to placement (x, y, rotation ...).
        connections_by_transformed_inst: Dict of connection attributes.
            keyed by the name of the instance which should be transformed.
        instances: Dict of references.
        encountered_insts: list of encountered_instances.
        instance_name: instance_name to place.
        all_remaining_insts: list of all the remaining instances to place
            instances pop from this instance as they are placed.

    """
    if not all_remaining_insts:
        return
    if instance_name is None:
        instance_name = all_remaining_insts.pop(0)
    else:
        all_remaining_insts.remove(instance_name)

    if instance_name in encountered_insts:
        encountered_insts.append(instance_name)
        loop_str = " -> ".join(encountered_insts)
        raise ValueError(
            f"circular reference in placement for {instance_name}! Loop: {loop_str}"
        )
    encountered_insts.append(instance_name)
    if instance_name not in instances:
        raise ValueError(f"{instance_name!r} not in {list(instances.keys())}")
    ref = instances[instance_name]

    if instance_name in placements_conf:
        placement_settings = placements_conf[instance_name] or {}
        if not isinstance(placement_settings, dict):
            raise ValueError(
                f"Invalid placement {placement_settings} from {valid_placement_keys}"
            )
        for k in placement_settings:
            if k not in valid_placement_keys:
                raise ValueError(f"Invalid placement {k} from {valid_placement_keys}")

        x = placement_settings.get("x")
        xmin = placement_settings.get("xmin")
        xmax = placement_settings.get("xmax")

        y = placement_settings.get("y")
        ymin = placement_settings.get("ymin")
        ymax = placement_settings.get("ymax")

        dx = placement_settings.get("dx")
        dy = placement_settings.get("dy")
        port = placement_settings.get("port")
        rotation = placement_settings.get("rotation")
        mirror = placement_settings.get("mirror")

        assert isinstance(rotation, int | float | None), "rotation must be a number"
        assert isinstance(port, str | None), "port must be a string or None"

        if rotation:
            if port:
                ref.rotate(rotation, center=_get_anchor_point_from_name(ref, port))
            else:
                ref.rotate(rotation)

        if mirror:
            if mirror is True and port:
                ref.dmirror_x(x=_get_anchor_value_from_name(ref, port, "x") or 0)
            elif mirror is True:
                ref.dcplx_trans *= kf.kdb.DCplxTrans(1, 0, True, 0, 0)
            elif mirror is False:
                pass
            elif isinstance(mirror, str):
                x_mirror = ref.ports[mirror].x
                ref.dmirror_x(x_mirror)
            elif isinstance(mirror, int | float):
                ref.dmirror_x(x=ref.x)
            else:
                port_names = [port.name for port in ref.ports]
                raise ValueError(
                    f"{mirror!r} can only be a port name {port_names}, "
                    "x value or True/False"
                )

        if port:
            a = _get_anchor_point_from_name(ref, port)
            if a is None:
                port_names = [port.name for port in ref.ports]
                raise ValueError(
                    f"Port {port!r} is neither a valid port on {ref.cell.name!r}"
                    " nor a recognized anchor keyword.\n"
                    "Valid ports: \n"
                    f"{port_names}. \n"
                    "Valid keywords: \n"
                    f"{valid_anchor_point_keywords}",
                )
            ref.x -= a[0]
            ref.y -= a[1]

        if x is not None:
            _dx = _move_ref(
                x,
                x_or_y="x",
                placements_conf=placements_conf,
                connections_by_transformed_inst=connections_by_transformed_inst,
                instances=instances,
                encountered_insts=encountered_insts,
                all_remaining_insts=all_remaining_insts,
            )
            assert _dx is not None
            ref.x += _dx

        if y is not None:
            _dy = _move_ref(
                y,
                x_or_y="y",
                placements_conf=placements_conf,
                connections_by_transformed_inst=connections_by_transformed_inst,
                instances=instances,
                encountered_insts=encountered_insts,
                all_remaining_insts=all_remaining_insts,
            )
            assert _dy is not None
            ref.y += _dy

        if ymin is not None and ymax is not None:
            raise ValueError("You cannot set ymin and ymax")
        if ymax is not None:
            dymax = _move_ref(
                ymax,
                x_or_y="y",
                placements_conf=placements_conf,
                connections_by_transformed_inst=connections_by_transformed_inst,
                instances=instances,
                encountered_insts=encountered_insts,
                all_remaining_insts=all_remaining_insts,
            )
            assert dymax is not None
            ref.ymax = dymax
        elif ymin is not None:
            dymin = _move_ref(
                ymin,
                x_or_y="y",
                placements_conf=placements_conf,
                connections_by_transformed_inst=connections_by_transformed_inst,
                instances=instances,
                encountered_insts=encountered_insts,
                all_remaining_insts=all_remaining_insts,
            )
            assert dymin is not None
            ref.ymin = dymin

        if xmin is not None and xmax is not None:
            raise ValueError("You cannot set xmin and xmax")
        if xmin is not None:
            dxmin = _move_ref(
                xmin,
                x_or_y="x",
                placements_conf=placements_conf,
                connections_by_transformed_inst=connections_by_transformed_inst,
                instances=instances,
                encountered_insts=encountered_insts,
                all_remaining_insts=all_remaining_insts,
            )
            assert dxmin is not None
            ref.xmin = dxmin
        elif xmax is not None:
            dxmax = _move_ref(
                xmax,
                x_or_y="x",
                placements_conf=placements_conf,
                connections_by_transformed_inst=connections_by_transformed_inst,
                instances=instances,
                encountered_insts=encountered_insts,
                all_remaining_insts=all_remaining_insts,
            )
            assert dxmax is not None
            ref.xmax = dxmax
        if dx:
            ref.x += float(dx)

        if dy:
            ref.y += float(dy)

    if instance_name in connections_by_transformed_inst:
        conn_info = connections_by_transformed_inst[instance_name]
        instance_dst_name = conn_info["instance_dst_name"]
        if instance_dst_name in all_remaining_insts:
            place(
                placements_conf,
                connections_by_transformed_inst,
                instances,
                encountered_insts,
                instance_dst_name,
                all_remaining_insts,
            )

        make_connection(instances=instances, **conn_info)  # type: ignore[arg-type]

transform_connections_dict

transform_connections_dict(
    connections_conf: dict[str, str],
) -> dict[str, dict[str, str | int | None]]

Returns Dict with source_instance_name key and connection properties.

Source code in gdsfactory/read/from_yaml.py
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
def transform_connections_dict(
    connections_conf: dict[str, str],
) -> dict[str, dict[str, str | int | None]]:
    """Returns Dict with source_instance_name key and connection properties."""
    if not connections_conf:
        return {}
    attrs_by_src_inst: dict[str, dict[str, str | int | None]] = {}
    for port_src_string, port_dst_string in connections_conf.items():
        instance_src_name, port_src_name = port_src_string.split(",")
        instance_dst_name, port_dst_name = port_dst_string.split(",")
        instance_src_name, src_ia, src_ib = _parse_maybe_arrayed_instance(
            instance_src_name
        )
        instance_dst_name, dst_ia, dst_ib = _parse_maybe_arrayed_instance(
            instance_dst_name
        )
        attrs_by_src_inst[instance_src_name] = {
            "instance_src_name": instance_src_name,
            "port_src_name": port_src_name,
            "instance_dst_name": instance_dst_name,
            "port_dst_name": port_dst_name,
        }
        src_dict = attrs_by_src_inst[instance_src_name]
        if src_ia is not None:
            src_dict["src_ia"] = src_ia
            src_dict["src_ib"] = src_ib
        if dst_ia is not None:
            src_dict["dst_ia"] = dst_ia
            src_dict["dst_ib"] = dst_ib
    return attrs_by_src_inst

from_np

Read component from a numpy.ndarray.

compute_area_signed

compute_area_signed(pr: NDArray[floating[Any]]) -> float

Return the signed area enclosed by a ring using the linear time.

algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0 indicates a counter-clockwise oriented ring.

Source code in gdsfactory/read/from_np.py
16
17
18
19
20
21
22
23
24
25
26
27
28
def compute_area_signed(pr: npt.NDArray[np.floating[Any]]) -> float:
    """Return the signed area enclosed by a ring using the linear time.

    algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0
    indicates a counter-clockwise oriented ring.

    """
    xs, ys = map(list, zip(*pr, strict=False))
    xs.append(xs[1])
    ys.append(ys[1])
    xs_ = cast("list[float]", xs)
    ys_ = cast("list[float]", ys)
    return sum(xs_[i] * (ys_[i + 1] - ys_[i - 1]) for i in range(1, len(pr))) / 2.0

from_image

from_image(
    image_path: PathType, **kwargs: Any
) -> Component

Returns Component from a png image.

Parameters:

Name Type Description Default
image_path PathType

png file path.

required
kwargs Any

for from_np.

{}

Other Parameters:

Name Type Description
nm_per_pixel

scale_factor.

layer

layer tuple to output gds.

threshold

value along which to find contours in the array.

Source code in gdsfactory/read/from_np.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@gf.cell
def from_image(image_path: PathType, **kwargs: Any) -> Component:
    """Returns Component from a png image.

    Args:
        image_path: png file path.
        kwargs: for from_np.

    Keyword Args:
        nm_per_pixel: scale_factor.
        layer: layer tuple to output gds.
        threshold: value along which to find contours in the array.

    """
    import matplotlib.pyplot as plt

    # Load the image using matplotlib
    img = plt.imread(image_path)

    if len(img.shape) == 3:
        img = 0.2989 * img[:, :, 0] + 0.5870 * img[:, :, 1] + 0.1140 * img[:, :, 2]

    # Convert image to numpy array (in fact, plt.imread already returns a numpy array)
    img_array = np.array(img)

    return from_np(img_array, **kwargs)

from_np

from_np(
    ndarray: NDArray[floating[Any]],
    nm_per_pixel: int = 20,
    layer: tuple[int, int] = (1, 0),
    threshold: float = 0.99,
    invert: bool = True,
) -> Component

Returns Component from a np.ndarray.

Extracts contours skimage.measure.find_contours using threshold.

Parameters:

Name Type Description Default
ndarray NDArray[floating[Any]]

2D ndarray representing the device layout.

required
nm_per_pixel int

scale_factor.

20
layer tuple[int, int]

layer tuple to output gds.

(1, 0)
threshold float

value along which to find contours in the array.

0.99
invert bool

invert the mask.

True
Source code in gdsfactory/read/from_np.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def from_np(
    ndarray: npt.NDArray[np.floating[Any]],
    nm_per_pixel: int = 20,
    layer: tuple[int, int] = (1, 0),
    threshold: float = 0.99,
    invert: bool = True,
) -> 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.
        invert: invert the mask.
    """
    from skimage import measure

    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)

    return boolean(c, d, operation="not", layer=layer) if invert else d

Cell decorators

cell

cell(
    _func: ComponentFunc[ComponentParams],
) -> ComponentFunc[ComponentParams]
cell(
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: (
        Iterable[Callable[[Component], None]] | None
    ) = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    schematic_function: Callable[ComponentParams, Schematic]
) -> Callable[
    [ComponentFunc[ComponentParams]],
    ComponentFunc[ComponentParams],
]
cell(
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: (
        Iterable[Callable[[Component], None]] | None
    ) = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    schematic_function: None = None
) -> Callable[
    [ComponentFunc[ComponentParams]],
    ComponentFunc[ComponentParams],
]
cell(
    _func: ComponentFunc[ComponentParams] | None = None,
    /,
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: (
        Iterable[Callable[[Component], None]] | None
    ) = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    ports: PortsDefinition | None = None,
    schematic_function: (
        Callable[ComponentParams, Schematic] | None
    ) = None,
) -> (
    ComponentFunc[ComponentParams]
    | Callable[
        [ComponentFunc[ComponentParams]],
        ComponentFunc[ComponentParams],
    ]
)

Decorator to convert a function into a Component.

Source code in gdsfactory/_cell.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def cell(
    _func: ComponentFunc[ComponentParams] | None = None,
    /,
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: Iterable[Callable[[Component], None]] | None = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    ports: PortsDefinition | None = None,
    schematic_function: Callable[ComponentParams, Schematic] | None = None,
) -> (
    ComponentFunc[ComponentParams]
    | Callable[[ComponentFunc[ComponentParams]], ComponentFunc[ComponentParams]]
):
    """Decorator to convert a function into a Component."""
    from gdsfactory.component import Component

    if with_module_name and _func is not None:
        basename = basename or _module_basename(_func)

    if drop_params is None:
        drop_params = ["self", "cls"]
    if post_process is None:
        post_process = []
    cell_kwargs: dict[str, Any] = dict(
        output_type=Component,
        set_settings=set_settings,
        set_name=set_name,
        check_ports=check_ports,
        check_instances=check_instances,
        snap_ports=snap_ports,
        add_port_layers=add_port_layers,
        cache=cache,
        basename=basename,
        drop_params=drop_params,
        register_factory=register_factory,
        overwrite_existing=overwrite_existing,
        layout_cache=layout_cache,
        info=info,
        post_process=post_process,
        debug_names=debug_names,
        tags=tags,
        lvs_equivalent_ports=lvs_equivalent_ports,
        ports=ports,
        schematic_function=schematic_function,
    )
    c: Any = _cell(_func, **cell_kwargs)  # type: ignore[arg-type]

    if _func is not None:
        c.is_gf_cell = True
        return cast(ComponentFunc[ComponentParams], c)

    @wraps(c)
    def wrapper(
        func: ComponentFunc[ComponentParams],
    ) -> ComponentFunc[ComponentParams]:
        decorated: Any
        if with_module_name and basename is None:
            decorated = _cell(
                func, **{**cell_kwargs, "basename": _module_basename(func)}
            )
        else:
            decorated = c(func)
        decorated.is_gf_cell = True
        return cast(ComponentFunc[ComponentParams], decorated)

    return wrapper

vcell

vcell(
    _func: ComponentAllAngleFunc[ComponentParams],
) -> ComponentAllAngleFunc[ComponentParams]
vcell(
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    basename: str | None = None,
    drop_params: tuple[str, ...] = ("self", "cls"),
    register_factory: bool = True,
    ports: PortsDefinition | None = None,
    lvs_equivalent_ports: list[list[str]] | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False
) -> Callable[
    [ComponentAllAngleFunc[ComponentParams]],
    ComponentAllAngleFunc[ComponentParams],
]
vcell(
    _func: (
        ComponentAllAngleFunc[ComponentParams] | None
    ) = None,
    /,
    *,
    set_settings: bool = True,
    set_name: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: tuple[str, ...] = ("self", "cls"),
    register_factory: bool = True,
    check_ports: bool = True,
    ports: PortsDefinition | None = None,
    lvs_equivalent_ports: list[list[str]] | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
) -> (
    ComponentAllAngleFunc[ComponentParams]
    | Callable[
        [ComponentAllAngleFunc[ComponentParams]],
        ComponentAllAngleFunc[ComponentParams],
    ]
)
Source code in gdsfactory/_cell.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
def vcell[**ComponentParams](
    _func: ComponentAllAngleFunc[ComponentParams] | None = None,
    /,
    *,
    set_settings: bool = True,
    set_name: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: tuple[str, ...] = ("self", "cls"),
    register_factory: bool = True,
    check_ports: bool = True,
    ports: PortsDefinition | None = None,
    lvs_equivalent_ports: list[list[str]] | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
) -> (
    ComponentAllAngleFunc[ComponentParams]
    | Callable[
        [ComponentAllAngleFunc[ComponentParams]], ComponentAllAngleFunc[ComponentParams]
    ]
):
    from gdsfactory.component import ComponentAllAngle

    if with_module_name and _func is not None:
        basename = basename or _module_basename(_func)

    vcell_kwargs: dict[str, Any] = dict(
        output_type=ComponentAllAngle,
        set_settings=set_settings,
        set_name=set_name,
        add_port_layers=add_port_layers,
        cache=cache,
        basename=basename,
        drop_params=list(drop_params),
        register_factory=register_factory,
        check_ports=check_ports,
        ports=ports,
        lvs_equivalent_ports=lvs_equivalent_ports,
        tags=tags,
    )
    vc: Any = _vcell(_func, **vcell_kwargs)  # type: ignore[arg-type]

    if _func is not None:
        vc.is_gf_vcell = True
        return cast(ComponentAllAngleFunc[ComponentParams], vc)

    @wraps(vc)
    def wrapper(
        func: ComponentAllAngleFunc[ComponentParams],
    ) -> ComponentAllAngleFunc[ComponentParams]:
        if with_module_name and basename is None:
            decorated: Any = _vcell(
                func, **{**vcell_kwargs, "basename": _module_basename(func)}
            )
        else:
            decorated = vc(func)
        decorated.is_gf_vcell = True
        return cast(ComponentAllAngleFunc[ComponentParams], decorated)

    return wrapper

Paths

Path

Bases: UMGeometricObject

You can extrude a Path with a CrossSection to create a Component.

Parameters:

Name Type Description Default
path NDArray[floating[Any]] | Path | list[tuple[float, float]] | None

array-like[N][2], Path, or list of Paths.

None
Source code in gdsfactory/path.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
class Path(UMGeometricObject):
    """You can extrude a Path with a CrossSection to create a Component.

    Parameters:
        path: array-like[N][2], Path, or list of Paths.

    """

    def __init__(
        self,
        path: npt.NDArray[np.floating[Any]]
        | Path
        | list[tuple[float, float]]
        | None = None,
        start_angle: float | None = None,
        end_angle: float | None = None,
    ) -> None:
        """Initializes a Path.

        Args:
            path: array-like[N][2], Path, or list of Paths.
            start_angle: optional angle in degrees at the start of the path.
                Overrides the angle inferred from the points.
            end_angle: optional angle in degrees at the end of the path.
                Overrides the angle inferred from the points.
        """
        self.points: npt.NDArray[np.floating[Any]] = np.array(
            [[0, 0]], dtype=np.float64
        )
        self.start_angle: float = 0
        self.end_angle: float = 0
        self.info: dict[str, Any] = {}
        if path is not None:
            if isinstance(path, Path):
                self.points = np.array(path.points, dtype=np.float64)
                self.start_angle = path.start_angle
                self.end_angle = path.end_angle
                self.info = {}
            elif (
                (np.asarray(path, dtype=object).ndim == 2)
                and np.issubdtype(np.array(path).dtype, np.number)
                and (np.shape(path)[1] == 2)
            ):
                self.points = np.array(path, dtype=np.float64)
                if len(self.points) > 1:
                    nx1, ny1 = self.points[1] - self.points[0]
                    self.start_angle = np.arctan2(ny1, nx1) / np.pi * 180
                    nx2, ny2 = self.points[-1] - self.points[-2]
                    self.end_angle = np.arctan2(ny2, nx2) / np.pi * 180
            elif np.asarray(path, dtype=object).size > 1:
                self.append(path)
            else:
                raise ValueError(
                    "Path() the `path` argument must be either blank, a path Object, "
                    "an array-like[N][2] list of points, or a list of these"
                )
        if start_angle is not None:
            self.start_angle = mod(start_angle, 360)
        if end_angle is not None:
            self.end_angle = mod(end_angle, 360)

    def __repr__(self) -> str:
        """Returns path points."""
        return (
            f"Path(start_angle={self.start_angle}, "
            f"end_angle={self.end_angle}, "
            f"points={self.points})"
        )

    def __len__(self) -> int:
        """Returns path points."""
        return len(self.points)

    def __iadd__(self, path_or_points: npt.NDArray[np.floating[Any]] | Path) -> Path:
        """Adds points to current path."""
        return self.append(path_or_points)

    def __add__(self, path: npt.NDArray[np.floating[Any]] | Path) -> Path:
        """Returns new path concatenating current and new path."""
        new = self.copy()
        return new.append(path)

    @property
    def kcl(self) -> kf.KCLayout:
        return gf.kcl

    def transform(
        self,
        trans: kdb.Trans | kdb.DTrans | kdb.ICplxTrans | kdb.DCplxTrans,
        /,
    ) -> Any:
        if isinstance(trans, kdb.DCplxTrans):
            trans_ = trans
        elif isinstance(trans, kdb.DTrans):
            trans_ = kdb.DCplxTrans(trans)
        elif isinstance(trans, kdb.Trans):
            trans_ = kdb.DCplxTrans(trans.to_dtype(gf.kcl.dbu))
        else:
            trans_ = trans.to_itrans(gf.kcl.dbu)

        new_points = self.points

        if trans_.angle != 0:
            angle_rad = np.radians(trans_.angle)
            cos_angle = np.cos(angle_rad)
            sin_angle = np.sin(angle_rad)

            rotation_matrix = np.array(
                [
                    [cos_angle, sin_angle],
                    [-sin_angle, cos_angle],
                ]
            )
            new_points = np.dot(self.points, rotation_matrix)

        new_points = new_points + np.array([trans_.disp.x, trans_.disp.y])

        if trans_.mirror:
            new_points[:, 1] = -new_points[:, 1]

        self.points = new_points
        if len(self.points) > 1:
            nx1, ny1 = self.points[1] - self.points[0]
            self.start_angle = np.arctan2(ny1, nx1) / np.pi * 180
            nx2, ny2 = self.points[-1] - self.points[-2]
            self.end_angle = np.arctan2(ny2, nx2) / np.pi * 180

    def dbbox(self, layer: int | None = None) -> kdb.DBox:
        return kdb.DBox(*self.bbox_np().flatten())

    def ibbox(self, layer: int | None = None) -> kdb.Box:
        return kdb.Box(*map(gf.kcl.to_dbu, self.bbox_np().flatten()))

    def bbox_np(self) -> npt.NDArray[np.float64]:
        """Returns the bounding box of the Path as a numpy array."""
        return np.array(
            [
                (np.min(self.points[:, 0]), np.min(self.points[:, 1])),
                (np.max(self.points[:, 0]), np.max(self.points[:, 1])),
            ],
            dtype=np.float64,
        )

    def append(
        self,
        path: npt.NDArray[np.floating[Any]]
        | Path
        | list[Path]
        | list[tuple[float, float]],
    ) -> Path:
        """Attach Path to the end of this Path.

        The input path automatically rotates and translates such that it continues
        smoothly from the previous segment.

        Args:
            path: Path, array-like[N][2], or list of Paths. The input path that will be appended.
        """
        # If appending another Path, load relevant variables
        if isinstance(path, Path):
            start_angle = path.start_angle
            end_angle = path.end_angle
            points = path.points
        # If array[N][2]
        elif (
            (np.asarray(path, dtype=object).ndim == 2)
            and not isinstance(path[0], Path)
            and np.issubdtype(np.array(path).dtype, np.number)
            and (np.shape(path)[1] == 2)  # type: ignore[arg-type]
        ):
            points = np.asarray(path, dtype=np.float64)
            start_angle, end_angle = 0, 0
            if len(points) > 1:
                nx1, ny1 = points[1] - points[0]
                start_angle = np.arctan2(ny1, nx1) / np.pi * 180
                nx2, ny2 = points[-1] - points[-2]
                end_angle = np.arctan2(ny2, nx2) / np.pi * 180
        elif isinstance(path, list):
            for p in path:
                self.append(p)  # type: ignore[arg-type]
            return self
        else:
            raise ValueError(
                "Path.append() the `path` argument must be either "
                "a Path object, an array-like[N][2] list of points, or a list of these"
            )

        # Connect beginning of new points with old points
        points = rotate_points(points, angle=self.end_angle - start_angle)
        points += self.points[-1, :] - points[0, :]

        # Update end angle
        self.end_angle = mod(end_angle + self.end_angle - start_angle, 360)

        # Concatenate old points + new points
        self.points = np.vstack([self.points, points[1:]])

        return self

    def offset(self, offset: float | Callable[[float], float] = 0) -> Path:
        """Offsets Path so that it follows the Path centerline plus an offset.

        The offset can either be a fixed value, or a function
        of the form my_offset(t) where t goes from 0->1

        Args:
            offset: int or float, callable. Magnitude of the offset
        """
        if offset == 0:
            points = self.points
            start_angle = self.start_angle
            end_angle = self.end_angle
        elif callable(offset):
            # Compute lengths
            dx = np.diff(self.points[:, 0])
            dy = np.diff(self.points[:, 1])
            lengths = np.cumsum(np.sqrt((dx) ** 2 + (dy) ** 2))
            lengths = np.concatenate([[0], lengths])
            # Create list of offset points and perform offset
            points = self.centerpoint_offset_curve(
                self.points,
                offset_distance=offset(lengths / lengths[-1]),
                start_angle=self.start_angle,
                end_angle=self.end_angle,
            )
            # Numerically compute start and end angles
            tol = 1e-6
            ds = tol / lengths[-1]
            ny1 = offset(ds) - offset(0)
            start_angle = np.arctan2(-ny1, tol) / np.pi * 180 + self.start_angle
            ny2 = offset(1) - offset(1 - ds)
            end_angle = np.arctan2(-ny2, tol) / np.pi * 180 + self.end_angle
        else:
            points = self.centerpoint_offset_curve(
                self.points,
                offset_distance=cast(float, offset),  # type: ignore[redundant-cast]
                start_angle=self.start_angle,
                end_angle=self.end_angle,
            )
            start_angle = self.start_angle
            end_angle = self.end_angle

        self.points = points
        self.start_angle = start_angle
        self.end_angle = end_angle
        return self

    def centerpoint_offset_curve(
        self,
        points: npt.NDArray[np.floating[Any]],
        offset_distance: float | Sequence[float] | npt.NDArray[np.floating[Any]],
        start_angle: float | None = None,
        end_angle: float | None = None,
    ) -> npt.NDArray[np.floating[Any]]:
        """Creates a offset curve computing the centerpoint offset of x and y points.

        Args:
            points: array-like[N][2] The points to be offset.
            offset_distance: array-like[N] The distance to offset the points.
            start_angle: float or None The angle at the start of the path.
            end_angle: float or None The angle at the end of the path.

        """
        cos_mid, sin_mid, sin_half = _compute_offset_directions(points)
        return _offset_curve_from_directions(
            points,
            offset_distance,
            cos_mid,
            sin_mid,
            sin_half,
            start_angle=start_angle,
            end_angle=end_angle,
        )

    def _parametric_offset_curve(
        self,
        points: npt.NDArray[np.floating[Any]],
        offset_distance: npt.NDArray[np.floating[Any]],
        start_angle: float | None = None,
        end_angle: float | None = None,
    ) -> npt.NDArray[np.floating[Any]]:
        """Creates a parametric offset by using gradient of the supplied x and y points.

        Args:
            points: array-like[N][2] The points to be offset.
            offset_distance: array-like[N] The distance to offset the points.
            start_angle: float or None The angle at the start of the path.
            end_angle: float or None The angle at the end of the path.


        """
        x = points[:, 0]
        y = points[:, 1]
        dxdt = np.gradient(x)
        dydt = np.gradient(y)
        if start_angle is not None:
            dxdt[0] = np.cos(start_angle * np.pi / 180)
            dydt[0] = np.sin(start_angle * np.pi / 180)
        if end_angle is not None:
            dxdt[-1] = np.cos(end_angle * np.pi / 180)
            dydt[-1] = np.sin(end_angle * np.pi / 180)
        x_offset = x + offset_distance * dydt / np.sqrt(dxdt**2 + dydt**2)
        y_offset = y - offset_distance * dxdt / np.sqrt(dydt**2 + dxdt**2)
        return np.array([x_offset, y_offset]).T

    def length(self) -> float:
        """Return cumulative length."""
        x = self.points[:, 0]
        y = self.points[:, 1]
        dx: npt.NDArray[np.floating[Any]] = np.diff(x)
        dy: npt.NDArray[np.floating[Any]] = np.diff(y)
        return float(np.round(np.sum(np.sqrt((dx) ** 2 + (dy) ** 2)), 3))

    def curvature(
        self,
    ) -> tuple[npt.NDArray[np.floating[Any]], npt.NDArray[np.floating[Any]]]:
        """Calculates Path curvature.

        The curvature is numerically computed so areas where the curvature
        jumps instantaneously (such as between an arc and a straight segment)
        will be slightly interpolated, and sudden changes in point density
        along the curve can cause discontinuities.

        Returns:
            s: array-like[N] The arc-length of the Path
            K: array-like[N] The curvature of the Path
        """
        x = self.points[:, 0]
        y = self.points[:, 1]
        dx = np.diff(x)
        dy = np.diff(y)
        ds = np.sqrt((dx) ** 2 + (dy) ** 2)
        s = np.cumsum(ds)
        theta = np.arctan2(dy, dx)

        # Fix discontinuities arising from np.arctan2
        dtheta = np.diff(theta)
        dtheta[np.where(dtheta > np.pi)] += -2 * np.pi
        dtheta[np.where(dtheta < -np.pi)] += 2 * np.pi
        theta = np.concatenate([[0], np.cumsum(dtheta)]) + theta[0]

        match len(ds):
            case 0 | 1:
                k = np.array([np.inf])
            case 2:
                k = np.nan_to_num(np.gradient(theta, s, edge_order=1), nan=np.inf)
            case _:
                k = np.gradient(theta, s, edge_order=2)

        return s, k

    def __hash__(self) -> int:
        """Computes a hash of the Path."""
        return self.hash_geometry()

    def __eq__(self, other: object) -> bool:
        """Check if two Path instances are equal."""
        if not isinstance(other, Path):
            return False
        return (
            np.array_equal(self.points, other.points)
            and self.start_angle == other.start_angle
            and self.end_angle == other.end_angle
        )

    def hash_geometry(self, precision: float = 1e-4) -> int:
        """Computes an SHA1 hash of the points in the Path and the start_angle and end_angle.

        Args:
            precision: Rounding precision for the the objects in the Component. For instance, \
                    a precision of 1e-2 will round a point at (0.124, 1.748) to (0.12, 1.75)

        Returns:
            str Hash result in the form of an SHA1 hex digest string.

            hash(
                hash(First layer information: [layer1, datatype1]),
                hash(Polygon 1 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
                hash(Polygon 2 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
                hash(Polygon 3 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
                hash(Second layer information: [layer2, datatype2]),
                hash(Polygon 1 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
                hash(Polygon 2 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3)] ),
            )
        """
        magic_offset = 0.17048614

        # Create a SHA1 hash object
        final_hash = hashlib.sha1()

        # Adjust points by precision and add the magic offset, then convert to bytes
        adjusted_points = (
            ((self.points / precision) + magic_offset).round().astype(np.int64)
        )
        final_hash.update(adjusted_points.tobytes())

        # Adjust angles by precision, round and convert to bytes
        adjusted_angles = np.array([self.start_angle, self.end_angle])
        adjusted_angles = (
            ((adjusted_angles / precision) + magic_offset).round().astype(np.int64)
        )
        final_hash.update(adjusted_angles.tobytes())
        hash_bytes = final_hash.digest()
        return int.from_bytes(hash_bytes, byteorder="big")

    def plot(self) -> None:
        """Plot path in matplotlib.

        Example:
            ```python
            import gdsfactory as gf

            p = gf.path.euler(radius=10)
            p.plot()
            ```
        """
        import matplotlib.pyplot as plt

        plt.plot(self.points[:, 0], self.points[:, 1])
        plt.axis("equal")
        plt.grid(True)
        plt.show()

    @overload
    def extrude(
        self,
        cross_section: CrossSectionSpec | None = None,
        layer: LayerSpec | None = None,
        width: float | None = None,
        simplify: float | None = None,
        all_angle: Literal[False] = False,
        register_cross_section: bool = False,
    ) -> Component: ...

    @overload
    def extrude(
        self,
        cross_section: CrossSectionSpec | None = None,
        layer: LayerSpec | None = None,
        width: float | None = None,
        simplify: float | None = None,
        all_angle: Literal[True] = True,
        register_cross_section: bool = False,
    ) -> ComponentAllAngle: ...

    @overload
    def extrude(
        self,
        cross_section: CrossSectionSpec | None = None,
        layer: LayerSpec | None = None,
        width: float | None = None,
        simplify: float | None = None,
        all_angle: bool = True,
        register_cross_section: bool = False,
    ) -> AnyComponent: ...

    def extrude(
        self,
        cross_section: CrossSectionSpec | None = None,
        layer: LayerSpec | None = None,
        width: float | None = None,
        simplify: float | None = None,
        all_angle: bool = False,
        register_cross_section: bool = False,
    ) -> AnyComponent:
        """Returns Component by extruding a Path with a CrossSection.

        A path can be extruded using any CrossSection returning a Component
        The CrossSection defines the layer numbers, widths and offsets.

        Args:
            cross_section: to extrude.
            layer: optional layer.
            width: optional width in um.
            simplify: Tolerance value for the simplification algorithm. \
                    All points that can be removed without changing the resulting polygon\
                    by more than the value listed here will be removed.

            all_angle: if True, the bend is drawn with a single euler curve.
            register_cross_section: if True, the cross_section factory is registered in the active PDK.

        Example:
            ```python
            import gdsfactory as gf

            p = gf.path.euler(radius=10)
            c = p.extrude(layer=(1, 0), width=0.5)
            c.plot()
            ```
        """
        return extrude(
            p=self,
            cross_section=cross_section,
            layer=layer,
            width=width,
            simplify=simplify,
            all_angle=all_angle,
            register_cross_section=register_cross_section,
        )

    @overload
    def extrude_transition(
        self,
        transition: Transition | TransitionAsymmetric,
        all_angle: Literal[False] = False,
    ) -> Component: ...

    @overload
    def extrude_transition(
        self,
        transition: Transition | TransitionAsymmetric,
        all_angle: Literal[True] = True,
    ) -> ComponentAllAngle: ...

    @overload
    def extrude_transition(
        self,
        transition: Transition | TransitionAsymmetric,
        all_angle: bool = True,
    ) -> AnyComponent: ...

    def extrude_transition(
        self,
        transition: Transition | TransitionAsymmetric,
        all_angle: bool = False,
    ) -> AnyComponent:
        """Extrudes a path along a transition.

        Allows different transition methods for the upper and lower edges.

        Args:
            transition: Transition or TransitionAsymmetric object describing the
                cross-sections and default transition types.
            all_angle: if True, returns a ComponentAllAngle.

        Returns:
            AnyComponent: The extruded component with the specified transition methods
                for each edge.
        """
        return extrude_transition(p=self, transition=transition, all_angle=all_angle)

    def copy(self) -> Path:
        """Returns a copy of the Path."""
        p = Path()
        p.info = self.info.copy()
        p.points = np.array(self.points)
        p.start_angle = self.start_angle
        p.end_angle = self.end_angle
        return p

    def mirror(
        self, p1: tuple[float, float] = (0, 1), p2: tuple[float, float] = (0, 0)
    ) -> Path:
        """Mirrors the Path across the line formed between the two specified points.

        ``points`` may be input as either single points [1,2]
        or array-like[N][2], and will return in kind.

        Args:
            p1: First point of the line.
            p2: Second point of the line.
        """
        self.points = reflect_points(self.points, p1, p2)
        angle = np.arctan2((p2[1] - p1[1]), (p2[0] - p1[0])) * 180 / np.pi
        if self.start_angle is not None:
            self.start_angle = mod(2 * angle - self.start_angle, 360)
        if self.end_angle is not None:
            self.end_angle = mod(2 * angle - self.end_angle, 360)
        return self

__add__

__add__(path: NDArray[floating[Any]] | Path) -> Path

Returns new path concatenating current and new path.

Source code in gdsfactory/path.py
173
174
175
176
def __add__(self, path: npt.NDArray[np.floating[Any]] | Path) -> Path:
    """Returns new path concatenating current and new path."""
    new = self.copy()
    return new.append(path)

__eq__

__eq__(other: object) -> bool

Check if two Path instances are equal.

Source code in gdsfactory/path.py
451
452
453
454
455
456
457
458
459
def __eq__(self, other: object) -> bool:
    """Check if two Path instances are equal."""
    if not isinstance(other, Path):
        return False
    return (
        np.array_equal(self.points, other.points)
        and self.start_angle == other.start_angle
        and self.end_angle == other.end_angle
    )

__hash__

__hash__() -> int

Computes a hash of the Path.

Source code in gdsfactory/path.py
447
448
449
def __hash__(self) -> int:
    """Computes a hash of the Path."""
    return self.hash_geometry()

__iadd__

__iadd__(
    path_or_points: NDArray[floating[Any]] | Path,
) -> Path

Adds points to current path.

Source code in gdsfactory/path.py
169
170
171
def __iadd__(self, path_or_points: npt.NDArray[np.floating[Any]] | Path) -> Path:
    """Adds points to current path."""
    return self.append(path_or_points)

__init__

__init__(
    path: (
        NDArray[floating[Any]]
        | Path
        | list[tuple[float, float]]
        | None
    ) = None,
    start_angle: float | None = None,
    end_angle: float | None = None,
) -> None

Initializes a Path.

Parameters:

Name Type Description Default
path NDArray[floating[Any]] | Path | list[tuple[float, float]] | None

array-like[N][2], Path, or list of Paths.

None
start_angle float | None

optional angle in degrees at the start of the path. Overrides the angle inferred from the points.

None
end_angle float | None

optional angle in degrees at the end of the path. Overrides the angle inferred from the points.

None
Source code in gdsfactory/path.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def __init__(
    self,
    path: npt.NDArray[np.floating[Any]]
    | Path
    | list[tuple[float, float]]
    | None = None,
    start_angle: float | None = None,
    end_angle: float | None = None,
) -> None:
    """Initializes a Path.

    Args:
        path: array-like[N][2], Path, or list of Paths.
        start_angle: optional angle in degrees at the start of the path.
            Overrides the angle inferred from the points.
        end_angle: optional angle in degrees at the end of the path.
            Overrides the angle inferred from the points.
    """
    self.points: npt.NDArray[np.floating[Any]] = np.array(
        [[0, 0]], dtype=np.float64
    )
    self.start_angle: float = 0
    self.end_angle: float = 0
    self.info: dict[str, Any] = {}
    if path is not None:
        if isinstance(path, Path):
            self.points = np.array(path.points, dtype=np.float64)
            self.start_angle = path.start_angle
            self.end_angle = path.end_angle
            self.info = {}
        elif (
            (np.asarray(path, dtype=object).ndim == 2)
            and np.issubdtype(np.array(path).dtype, np.number)
            and (np.shape(path)[1] == 2)
        ):
            self.points = np.array(path, dtype=np.float64)
            if len(self.points) > 1:
                nx1, ny1 = self.points[1] - self.points[0]
                self.start_angle = np.arctan2(ny1, nx1) / np.pi * 180
                nx2, ny2 = self.points[-1] - self.points[-2]
                self.end_angle = np.arctan2(ny2, nx2) / np.pi * 180
        elif np.asarray(path, dtype=object).size > 1:
            self.append(path)
        else:
            raise ValueError(
                "Path() the `path` argument must be either blank, a path Object, "
                "an array-like[N][2] list of points, or a list of these"
            )
    if start_angle is not None:
        self.start_angle = mod(start_angle, 360)
    if end_angle is not None:
        self.end_angle = mod(end_angle, 360)

__len__

__len__() -> int

Returns path points.

Source code in gdsfactory/path.py
165
166
167
def __len__(self) -> int:
    """Returns path points."""
    return len(self.points)

__repr__

__repr__() -> str

Returns path points.

Source code in gdsfactory/path.py
157
158
159
160
161
162
163
def __repr__(self) -> str:
    """Returns path points."""
    return (
        f"Path(start_angle={self.start_angle}, "
        f"end_angle={self.end_angle}, "
        f"points={self.points})"
    )

append

append(
    path: (
        NDArray[floating[Any]]
        | Path
        | list[Path]
        | list[tuple[float, float]]
    ),
) -> Path

Attach Path to the end of this Path.

The input path automatically rotates and translates such that it continues smoothly from the previous segment.

Parameters:

Name Type Description Default
path NDArray[floating[Any]] | Path | list[Path] | list[tuple[float, float]]

Path, array-like[N][2], or list of Paths. The input path that will be appended.

required
Source code in gdsfactory/path.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def append(
    self,
    path: npt.NDArray[np.floating[Any]]
    | Path
    | list[Path]
    | list[tuple[float, float]],
) -> Path:
    """Attach Path to the end of this Path.

    The input path automatically rotates and translates such that it continues
    smoothly from the previous segment.

    Args:
        path: Path, array-like[N][2], or list of Paths. The input path that will be appended.
    """
    # If appending another Path, load relevant variables
    if isinstance(path, Path):
        start_angle = path.start_angle
        end_angle = path.end_angle
        points = path.points
    # If array[N][2]
    elif (
        (np.asarray(path, dtype=object).ndim == 2)
        and not isinstance(path[0], Path)
        and np.issubdtype(np.array(path).dtype, np.number)
        and (np.shape(path)[1] == 2)  # type: ignore[arg-type]
    ):
        points = np.asarray(path, dtype=np.float64)
        start_angle, end_angle = 0, 0
        if len(points) > 1:
            nx1, ny1 = points[1] - points[0]
            start_angle = np.arctan2(ny1, nx1) / np.pi * 180
            nx2, ny2 = points[-1] - points[-2]
            end_angle = np.arctan2(ny2, nx2) / np.pi * 180
    elif isinstance(path, list):
        for p in path:
            self.append(p)  # type: ignore[arg-type]
        return self
    else:
        raise ValueError(
            "Path.append() the `path` argument must be either "
            "a Path object, an array-like[N][2] list of points, or a list of these"
        )

    # Connect beginning of new points with old points
    points = rotate_points(points, angle=self.end_angle - start_angle)
    points += self.points[-1, :] - points[0, :]

    # Update end angle
    self.end_angle = mod(end_angle + self.end_angle - start_angle, 360)

    # Concatenate old points + new points
    self.points = np.vstack([self.points, points[1:]])

    return self

bbox_np

bbox_np() -> npt.NDArray[np.float64]

Returns the bounding box of the Path as a numpy array.

Source code in gdsfactory/path.py
229
230
231
232
233
234
235
236
237
def bbox_np(self) -> npt.NDArray[np.float64]:
    """Returns the bounding box of the Path as a numpy array."""
    return np.array(
        [
            (np.min(self.points[:, 0]), np.min(self.points[:, 1])),
            (np.max(self.points[:, 0]), np.max(self.points[:, 1])),
        ],
        dtype=np.float64,
    )

centerpoint_offset_curve

centerpoint_offset_curve(
    points: NDArray[floating[Any]],
    offset_distance: (
        float | Sequence[float] | NDArray[floating[Any]]
    ),
    start_angle: float | None = None,
    end_angle: float | None = None,
) -> npt.NDArray[np.floating[Any]]

Creates a offset curve computing the centerpoint offset of x and y points.

Parameters:

Name Type Description Default
points NDArray[floating[Any]]

array-like[N][2] The points to be offset.

required
offset_distance float | Sequence[float] | NDArray[floating[Any]]

array-like[N] The distance to offset the points.

required
start_angle float | None

float or None The angle at the start of the path.

None
end_angle float | None

float or None The angle at the end of the path.

None
Source code in gdsfactory/path.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def centerpoint_offset_curve(
    self,
    points: npt.NDArray[np.floating[Any]],
    offset_distance: float | Sequence[float] | npt.NDArray[np.floating[Any]],
    start_angle: float | None = None,
    end_angle: float | None = None,
) -> npt.NDArray[np.floating[Any]]:
    """Creates a offset curve computing the centerpoint offset of x and y points.

    Args:
        points: array-like[N][2] The points to be offset.
        offset_distance: array-like[N] The distance to offset the points.
        start_angle: float or None The angle at the start of the path.
        end_angle: float or None The angle at the end of the path.

    """
    cos_mid, sin_mid, sin_half = _compute_offset_directions(points)
    return _offset_curve_from_directions(
        points,
        offset_distance,
        cos_mid,
        sin_mid,
        sin_half,
        start_angle=start_angle,
        end_angle=end_angle,
    )

copy

copy() -> Path

Returns a copy of the Path.

Source code in gdsfactory/path.py
637
638
639
640
641
642
643
644
def copy(self) -> Path:
    """Returns a copy of the Path."""
    p = Path()
    p.info = self.info.copy()
    p.points = np.array(self.points)
    p.start_angle = self.start_angle
    p.end_angle = self.end_angle
    return p

curvature

curvature() -> tuple[
    npt.NDArray[np.floating[Any]],
    npt.NDArray[np.floating[Any]],
]

Calculates Path curvature.

The curvature is numerically computed so areas where the curvature jumps instantaneously (such as between an arc and a straight segment) will be slightly interpolated, and sudden changes in point density along the curve can cause discontinuities.

Returns:

Name Type Description
s NDArray[floating[Any]]

array-like[N] The arc-length of the Path

K NDArray[floating[Any]]

array-like[N] The curvature of the Path

Source code in gdsfactory/path.py
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
def curvature(
    self,
) -> tuple[npt.NDArray[np.floating[Any]], npt.NDArray[np.floating[Any]]]:
    """Calculates Path curvature.

    The curvature is numerically computed so areas where the curvature
    jumps instantaneously (such as between an arc and a straight segment)
    will be slightly interpolated, and sudden changes in point density
    along the curve can cause discontinuities.

    Returns:
        s: array-like[N] The arc-length of the Path
        K: array-like[N] The curvature of the Path
    """
    x = self.points[:, 0]
    y = self.points[:, 1]
    dx = np.diff(x)
    dy = np.diff(y)
    ds = np.sqrt((dx) ** 2 + (dy) ** 2)
    s = np.cumsum(ds)
    theta = np.arctan2(dy, dx)

    # Fix discontinuities arising from np.arctan2
    dtheta = np.diff(theta)
    dtheta[np.where(dtheta > np.pi)] += -2 * np.pi
    dtheta[np.where(dtheta < -np.pi)] += 2 * np.pi
    theta = np.concatenate([[0], np.cumsum(dtheta)]) + theta[0]

    match len(ds):
        case 0 | 1:
            k = np.array([np.inf])
        case 2:
            k = np.nan_to_num(np.gradient(theta, s, edge_order=1), nan=np.inf)
        case _:
            k = np.gradient(theta, s, edge_order=2)

    return s, k

extrude

extrude(
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    simplify: float | None = None,
    all_angle: Literal[False] = False,
    register_cross_section: bool = False,
) -> Component
extrude(
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    simplify: float | None = None,
    all_angle: Literal[True] = True,
    register_cross_section: bool = False,
) -> ComponentAllAngle
extrude(
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    simplify: float | None = None,
    all_angle: bool = True,
    register_cross_section: bool = False,
) -> AnyComponent
extrude(
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    simplify: float | None = None,
    all_angle: bool = False,
    register_cross_section: bool = False,
) -> AnyComponent

Returns Component by extruding a Path with a CrossSection.

A path can be extruded using any CrossSection returning a Component The CrossSection defines the layer numbers, widths and offsets.

Parameters:

Name Type Description Default
cross_section CrossSectionSpec | None

to extrude.

None
layer LayerSpec | None

optional layer.

None
width float | None

optional width in um.

None
simplify float | None

Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting polygon by more than the value listed here will be removed.

None
all_angle bool

if True, the bend is drawn with a single euler curve.

False
register_cross_section bool

if True, the cross_section factory is registered in the active PDK.

False
Example
import gdsfactory as gf

p = gf.path.euler(radius=10)
c = p.extrude(layer=(1, 0), width=0.5)
c.plot()
Source code in gdsfactory/path.py
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
def extrude(
    self,
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    simplify: float | None = None,
    all_angle: bool = False,
    register_cross_section: bool = False,
) -> AnyComponent:
    """Returns Component by extruding a Path with a CrossSection.

    A path can be extruded using any CrossSection returning a Component
    The CrossSection defines the layer numbers, widths and offsets.

    Args:
        cross_section: to extrude.
        layer: optional layer.
        width: optional width in um.
        simplify: Tolerance value for the simplification algorithm. \
                All points that can be removed without changing the resulting polygon\
                by more than the value listed here will be removed.

        all_angle: if True, the bend is drawn with a single euler curve.
        register_cross_section: if True, the cross_section factory is registered in the active PDK.

    Example:
        ```python
        import gdsfactory as gf

        p = gf.path.euler(radius=10)
        c = p.extrude(layer=(1, 0), width=0.5)
        c.plot()
        ```
    """
    return extrude(
        p=self,
        cross_section=cross_section,
        layer=layer,
        width=width,
        simplify=simplify,
        all_angle=all_angle,
        register_cross_section=register_cross_section,
    )

extrude_transition

extrude_transition(
    transition: Transition | TransitionAsymmetric,
    all_angle: Literal[False] = False,
) -> Component
extrude_transition(
    transition: Transition | TransitionAsymmetric,
    all_angle: Literal[True] = True,
) -> ComponentAllAngle
extrude_transition(
    transition: Transition | TransitionAsymmetric,
    all_angle: bool = True,
) -> AnyComponent
extrude_transition(
    transition: Transition | TransitionAsymmetric,
    all_angle: bool = False,
) -> AnyComponent

Extrudes a path along a transition.

Allows different transition methods for the upper and lower edges.

Parameters:

Name Type Description Default
transition Transition | TransitionAsymmetric

Transition or TransitionAsymmetric object describing the cross-sections and default transition types.

required
all_angle bool

if True, returns a ComponentAllAngle.

False

Returns:

Name Type Description
AnyComponent AnyComponent

The extruded component with the specified transition methods for each edge.

Source code in gdsfactory/path.py
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
def extrude_transition(
    self,
    transition: Transition | TransitionAsymmetric,
    all_angle: bool = False,
) -> AnyComponent:
    """Extrudes a path along a transition.

    Allows different transition methods for the upper and lower edges.

    Args:
        transition: Transition or TransitionAsymmetric object describing the
            cross-sections and default transition types.
        all_angle: if True, returns a ComponentAllAngle.

    Returns:
        AnyComponent: The extruded component with the specified transition methods
            for each edge.
    """
    return extrude_transition(p=self, transition=transition, all_angle=all_angle)

hash_geometry

hash_geometry(precision: float = 0.0001) -> int

Computes an SHA1 hash of the points in the Path and the start_angle and end_angle.

Parameters:

Name Type Description Default
precision float

Rounding precision for the the objects in the Component. For instance, a precision of 1e-2 will round a point at (0.124, 1.748) to (0.12, 1.75)

0.0001

Returns:

Type Description
int

str Hash result in the form of an SHA1 hex digest string.

int

hash( hash(First layer information: [layer1, datatype1]), hash(Polygon 1 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ), hash(Polygon 2 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ), hash(Polygon 3 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ), hash(Second layer information: [layer2, datatype2]), hash(Polygon 1 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ), hash(Polygon 2 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3)] ),

int

)

Source code in gdsfactory/path.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
def hash_geometry(self, precision: float = 1e-4) -> int:
    """Computes an SHA1 hash of the points in the Path and the start_angle and end_angle.

    Args:
        precision: Rounding precision for the the objects in the Component. For instance, \
                a precision of 1e-2 will round a point at (0.124, 1.748) to (0.12, 1.75)

    Returns:
        str Hash result in the form of an SHA1 hex digest string.

        hash(
            hash(First layer information: [layer1, datatype1]),
            hash(Polygon 1 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
            hash(Polygon 2 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
            hash(Polygon 3 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
            hash(Second layer information: [layer2, datatype2]),
            hash(Polygon 1 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
            hash(Polygon 2 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3)] ),
        )
    """
    magic_offset = 0.17048614

    # Create a SHA1 hash object
    final_hash = hashlib.sha1()

    # Adjust points by precision and add the magic offset, then convert to bytes
    adjusted_points = (
        ((self.points / precision) + magic_offset).round().astype(np.int64)
    )
    final_hash.update(adjusted_points.tobytes())

    # Adjust angles by precision, round and convert to bytes
    adjusted_angles = np.array([self.start_angle, self.end_angle])
    adjusted_angles = (
        ((adjusted_angles / precision) + magic_offset).round().astype(np.int64)
    )
    final_hash.update(adjusted_angles.tobytes())
    hash_bytes = final_hash.digest()
    return int.from_bytes(hash_bytes, byteorder="big")

length

length() -> float

Return cumulative length.

Source code in gdsfactory/path.py
401
402
403
404
405
406
407
def length(self) -> float:
    """Return cumulative length."""
    x = self.points[:, 0]
    y = self.points[:, 1]
    dx: npt.NDArray[np.floating[Any]] = np.diff(x)
    dy: npt.NDArray[np.floating[Any]] = np.diff(y)
    return float(np.round(np.sum(np.sqrt((dx) ** 2 + (dy) ** 2)), 3))

mirror

mirror(
    p1: tuple[float, float] = (0, 1),
    p2: tuple[float, float] = (0, 0),
) -> Path

Mirrors the Path across the line formed between the two specified points.

points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:

Name Type Description Default
p1 tuple[float, float]

First point of the line.

(0, 1)
p2 tuple[float, float]

Second point of the line.

(0, 0)
Source code in gdsfactory/path.py
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
def mirror(
    self, p1: tuple[float, float] = (0, 1), p2: tuple[float, float] = (0, 0)
) -> Path:
    """Mirrors the Path across the line formed between the two specified points.

    ``points`` may be input as either single points [1,2]
    or array-like[N][2], and will return in kind.

    Args:
        p1: First point of the line.
        p2: Second point of the line.
    """
    self.points = reflect_points(self.points, p1, p2)
    angle = np.arctan2((p2[1] - p1[1]), (p2[0] - p1[0])) * 180 / np.pi
    if self.start_angle is not None:
        self.start_angle = mod(2 * angle - self.start_angle, 360)
    if self.end_angle is not None:
        self.end_angle = mod(2 * angle - self.end_angle, 360)
    return self

offset

offset(
    offset: float | Callable[[float], float] = 0,
) -> Path

Offsets Path so that it follows the Path centerline plus an offset.

The offset can either be a fixed value, or a function of the form my_offset(t) where t goes from 0->1

Parameters:

Name Type Description Default
offset float | Callable[[float], float]

int or float, callable. Magnitude of the offset

0
Source code in gdsfactory/path.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
def offset(self, offset: float | Callable[[float], float] = 0) -> Path:
    """Offsets Path so that it follows the Path centerline plus an offset.

    The offset can either be a fixed value, or a function
    of the form my_offset(t) where t goes from 0->1

    Args:
        offset: int or float, callable. Magnitude of the offset
    """
    if offset == 0:
        points = self.points
        start_angle = self.start_angle
        end_angle = self.end_angle
    elif callable(offset):
        # Compute lengths
        dx = np.diff(self.points[:, 0])
        dy = np.diff(self.points[:, 1])
        lengths = np.cumsum(np.sqrt((dx) ** 2 + (dy) ** 2))
        lengths = np.concatenate([[0], lengths])
        # Create list of offset points and perform offset
        points = self.centerpoint_offset_curve(
            self.points,
            offset_distance=offset(lengths / lengths[-1]),
            start_angle=self.start_angle,
            end_angle=self.end_angle,
        )
        # Numerically compute start and end angles
        tol = 1e-6
        ds = tol / lengths[-1]
        ny1 = offset(ds) - offset(0)
        start_angle = np.arctan2(-ny1, tol) / np.pi * 180 + self.start_angle
        ny2 = offset(1) - offset(1 - ds)
        end_angle = np.arctan2(-ny2, tol) / np.pi * 180 + self.end_angle
    else:
        points = self.centerpoint_offset_curve(
            self.points,
            offset_distance=cast(float, offset),  # type: ignore[redundant-cast]
            start_angle=self.start_angle,
            end_angle=self.end_angle,
        )
        start_angle = self.start_angle
        end_angle = self.end_angle

    self.points = points
    self.start_angle = start_angle
    self.end_angle = end_angle
    return self

plot

plot() -> None

Plot path in matplotlib.

Example
import gdsfactory as gf

p = gf.path.euler(radius=10)
p.plot()
Source code in gdsfactory/path.py
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
def plot(self) -> None:
    """Plot path in matplotlib.

    Example:
        ```python
        import gdsfactory as gf

        p = gf.path.euler(radius=10)
        p.plot()
        ```
    """
    import matplotlib.pyplot as plt

    plt.plot(self.points[:, 0], self.points[:, 1])
    plt.axis("equal")
    plt.grid(True)
    plt.show()

straight

straight(length: float = 10.0, npoints: int = 2) -> Path

Returns a straight path.

For transitions you should increase have at least 100 points

Parameters:

Name Type Description Default
length float

of straight.

10.0
npoints int

number of points.

2
Source code in gdsfactory/path.py
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
def straight(length: float = 10.0, npoints: int = 2) -> Path:
    """Returns a straight path.

    For transitions you should increase have at least 100 points

    Args:
        length: of straight.
        npoints: number of points.

    """
    if length < 0:
        raise ValueError(f"length = {length} needs to be > 0")
    x = np.linspace(0, length, npoints)
    y = x * 0
    points = np.array((x, y)).T

    p = Path()
    p.append(points)
    return p

euler

euler(
    radius: float = 10,
    angle: float = 90,
    p: float = 0.5,
    use_eff: bool = False,
    npoints: int | None = None,
    angular_step: float | None = None,
) -> Path

Returns an euler bend that adiabatically transitions from straight to curved.

radius is the minimum radius of curvature of the bend. However, if use_eff is set to True, radius corresponds to the effective radius of curvature (making the curve a drop-in replacement for an arc). If p < 1.0, will create a "partial euler" curve as described in Vogelbacher et. al. https://dx.doi.org/10.1364/oe.27.031394

Parameters:

Name Type Description Default
radius float

minimum radius of curvature.

10
angle float

total angle of the curve.

90
p float

Proportion of the curve that is an Euler curve.

0.5
use_eff bool

If False: radius is the minimum radius of curvature of the bend. If True: The curve will be scaled such that the endpoints match an arc with parameters radius and angle.

False
npoints int | None

Number of points used per 360 degrees.

None
angular_step float | None

If provided, determines the angular step (in degrees) between points. This overrides npoints calculation.

None
Example
import gdsfactory as gf

p = gf.path.euler(radius=10, angle=45, p=1, use_eff=True, npoints=720)
p.plot()
Source code in gdsfactory/path.py
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
def euler(
    radius: float = 10,
    angle: float = 90,
    p: float = 0.5,
    use_eff: bool = False,
    npoints: int | None = None,
    angular_step: float | None = None,
) -> Path:
    """Returns an euler bend that adiabatically transitions from straight to curved.

    `radius` is the minimum radius of curvature of the bend.
    However, if `use_eff` is set to True, `radius` corresponds to the effective
    radius of curvature (making the curve a drop-in replacement for an arc).
    If p < 1.0, will create a "partial euler" curve as described in Vogelbacher et. al.
    https://dx.doi.org/10.1364/oe.27.031394

    Args:
        radius: minimum radius of curvature.
        angle: total angle of the curve.
        p: Proportion of the curve that is an Euler curve.
        use_eff: If False: `radius` is the minimum radius of curvature of the bend. \
                If True: The curve will be scaled such that the endpoints match an \
                arc with parameters `radius` and `angle`.
        npoints: Number of points used per 360 degrees.
        angular_step: If provided, determines the angular step (in degrees) between points. \
                This overrides npoints calculation.

    Example:
        ```python
        import gdsfactory as gf

        p = gf.path.euler(radius=10, angle=45, p=1, use_eff=True, npoints=720)
        p.plot()
        ```
    """
    from gdsfactory.pdk import get_active_pdk

    if angular_step is not None and npoints is not None:
        raise ValueError(
            "euler() requires either npoints or angular_step, not both. "
            "Use angular_step for angular discretization."
        )

    if not radius:
        raise ValueError("euler() requires a radius argument")

    if (p < 0) or (p > 1):
        raise ValueError(f"euler requires argument `p` be between 0 and 1. Got {p}")
    if p == 0:
        path = arc(radius, angle, npoints=npoints, angular_step=angular_step)
        path.info["Reff"] = radius
        path.info["Rmin"] = radius
        return path

    if angle < 0:
        mirror = True
        angle = np.abs(angle)
    else:
        mirror = False

    R0 = 1
    alpha = np.radians(angle)
    sp = float(R0 * np.sqrt(p * alpha))
    # Rp = inf at alpha=0, but all usages are guarded by is_small_angle
    with np.errstate(divide="ignore"):
        Rp = R0 / np.sqrt(p * alpha)

    pdk = get_active_pdk()
    if angular_step is not None:
        npoints = math.ceil(abs(angle / angular_step)) + 1
        # For angular discretization, distribute points based on angle proportion
        euler_angle = p * angle / 2  # Angle covered by each Euler section
        arc_angle = (
            (1 - p) * angle / 2
        )  # Angle covered by arc section (half, then mirrored)
        num_pts_euler = max(2, math.ceil(euler_angle / angular_step))
        num_pts_arc = max(2, math.ceil(arc_angle / angular_step) + 1)
        npoints = (
            2 * num_pts_euler + num_pts_arc - 2
        )  # Total points (avoiding duplicates)
    else:
        if not npoints:
            npoints = abs(int(angle / 360 * radius / pdk.bend_points_distance / 2))
            npoints = max(npoints, _bend_npoints_floor(angle), 2)
        else:
            npoints = max(int(npoints), 2)
        # Use simplified form: sp/(s0/2) = 2p/(p+1), avoids 0/0 at alpha=0
        num_pts_euler = int(np.round(2 * p / (p + 1) * npoints))
        num_pts_arc = npoints - num_pts_euler

    # Ensure a minimum of 2 points for each euler/arc section
    if npoints <= 2:
        num_pts_euler = 0
        num_pts_arc = 2

    # Small angle threshold in degrees (matches arc() convention)
    is_small_angle = abs(angle) <= 1e-6

    if num_pts_euler > 0:
        if angular_step is not None:
            xbend1, ybend1 = _fresnel_angular(R0, sp, num_pts_euler)
        else:
            xbend1, ybend1 = _fresnel(R0, sp, num_pts_euler)
        xp, yp = xbend1[-1], ybend1[-1]
        # Sinc-based formulas avoid Rp*sin (inf*0 at alpha=0)
        # Rp * sin(p*a/2) = sp/2 * sinc(p*a/(2*pi))
        # Rp * (1-cos(p*a/2)) = (sp^3/8) * sinc^2(p*a/(4*pi))  [using 1-cos=2sin^2]
        # Using sinc(2x) = sinc(x)*cos(pi*x) to compute both from one sinc call
        sinc_quarter = np.sinc(p * alpha / (4 * np.pi))
        dx = xp - sp / 2 * sinc_quarter * np.cos(p * alpha / 4)
        dy = yp - (sp**3 / 8) * sinc_quarter**2
    else:
        xbend1 = ybend1 = np.asarray([], dtype=float)
        dx = 0
        dy = 0

    if not is_small_angle:
        if angular_step is not None:
            # Original angular_step behavior (different from npoints mode)
            arc_angle_section = alpha * (1 - p) / 2
            theta = np.linspace(0, arc_angle_section, num_pts_arc)
            arc_angles = theta + p * alpha / 2
        else:
            # Direct linspace replaces: s = linspace(sp, s0/2), arc_angles = (s-sp)*sqrt(p*a)/R0 + p*a/2
            arc_angles = np.linspace(p * alpha / 2, alpha / 2, num_pts_arc)
        xbend2 = Rp * np.sin(arc_angles) + dx
        ybend2 = Rp * (1 - np.cos(arc_angles)) + dy
    else:
        # Limit is 0 by L'Hopital: Rp*sin(t) ~ sin(a)/sqrt(a) -> 0
        xbend2 = np.zeros(num_pts_arc) + dx
        # Limit is 0: Rp*(1-cos(t)) ~ (1/sqrt(a))*(a^2/2) = a^(3/2)/2 -> 0
        ybend2 = np.zeros(num_pts_arc) + dy

    x = np.concatenate([xbend1, xbend2[1:]])
    y = np.concatenate([ybend1, ybend2[1:]])
    points1 = np.array([x, y]).T
    points2 = np.flipud(np.array([x, -y]).T)

    points2 = rotate_points(points2, angle - 180)
    points2 += -points2[0, :] + points1[-1, :]

    # Use [:-1] to remove duplicate junction point, but [:None] if only 1 point
    points = np.concatenate([points1[: -1 if len(points1) > 1 else None], points2])

    # Find y-axis intersection point to compute Reff
    start_angle = 180 * (angle < 0)
    end_angle = start_angle + angle

    if is_small_angle:
        # Degenerate case: curve collapses to a point, radius is infinite
        Reff = np.inf
        Rmin = np.inf
        scale = 0.0
    else:
        dy = np.tan(np.radians(end_angle - 90)) * points[-1][0]
        Reff = points[-1][1] - dy
        Rmin = Rp
        # Fix degenerate condition at angle == 180
        if np.abs(180 - angle) < 1e-3:
            Reff = points[-1][1] / 2
        scale = radius / Reff if use_eff else radius / Rmin

    points *= scale

    path = Path()

    # Manually add points & adjust start and end angles
    path.points = points
    path.start_angle = start_angle
    path.end_angle = end_angle
    path.info["Reff"] = Reff * scale if not is_small_angle else np.inf
    path.info["Rmin"] = Rmin * scale if not is_small_angle else np.inf
    if mirror:
        path.mirror((1, 0))
    return path

arc

arc(
    radius: float | None = 10.0,
    angle: float = 90,
    npoints: int | None = None,
    start_angle: float = -90,
    angular_step: float | None = None,
) -> Path

Returns a radial arc.

Parameters:

Name Type Description Default
radius float | None

minimum radius of curvature.

10.0
angle float

total angle of the curve.

90
npoints int | None

Number of points used per 360 degrees. Defaults to pdk.bend_points_distance.

None
start_angle float

initial angle of the curve for drawing, default -90 degrees.

-90
angular_step float | None

If provided, determines the angular step (in degrees) between points. This overrides npoints calculation.

None
Example
import gdsfactory as gf

p = gf.path.arc(radius=10, angle=45)
p.plot()
Source code in gdsfactory/path.py
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
def arc(
    radius: float | None = 10.0,
    angle: float = 90,
    npoints: int | None = None,
    start_angle: float = -90,
    angular_step: float | None = None,
) -> Path:
    """Returns a radial arc.

    Args:
        radius: minimum radius of curvature.
        angle: total angle of the curve.
        npoints: Number of points used per 360 degrees. Defaults to pdk.bend_points_distance.
        start_angle: initial angle of the curve for drawing, default -90 degrees.
        angular_step: If provided, determines the angular step (in degrees) between points. \
                This overrides npoints calculation.

    Example:
        ```python
        import gdsfactory as gf

        p = gf.path.arc(radius=10, angle=45)
        p.plot()
        ```
    """
    from gdsfactory.pdk import get_active_pdk

    PDK = get_active_pdk()

    if not radius:
        raise ValueError("arc() requires a radius argument")

    if npoints is not None and angular_step is not None:
        raise ValueError(
            "arc() requires either npoints or angular_step, not both. "
            "Use angular_step for angular discretization."
        )

    if angular_step is not None:
        npoints = math.ceil(abs(angle / angular_step)) + 1
    elif not npoints:
        npoints = int(abs(angle) / 360 * radius / PDK.bend_points_distance / 2)
        npoints = max(npoints, _bend_npoints_floor(angle), 2)
    else:
        npoints = max(int(npoints), 2)

    t = np.linspace(
        start_angle * np.pi / 180, (angle + start_angle) * np.pi / 180, npoints
    )
    x = radius * np.cos(t)
    y = radius * (np.sin(t) + 1)
    points = np.array((x, y)).T * np.sign(angle)

    path = Path()
    # Manually add points & adjust start and end angles
    path.points = points
    path.start_angle = start_angle + 90
    path.end_angle = start_angle + angle + 90
    return path

spiral_archimedean

spiral_archimedean(
    min_bend_radius: float,
    separation: float,
    number_of_loops: float,
    npoints: int,
) -> Path

Returns an Archimedean spiral.

Parameters:

Name Type Description Default
min_bend_radius float

Inner radius of the spiral.

required
separation float

Half the radial separation between loops in um. The current formula is retained for compatibility with existing layouts, so adjacent turns are separated by 2 * separation.

required
number_of_loops float

number of loops.

required
npoints int

number of Points.

required
Example
import gdsfactory as gf

p = gf.path.spiral_archimedean(min_bend_radius=5, separation=2, number_of_loops=3, npoints=200)
p.plot()
Source code in gdsfactory/path.py
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
def spiral_archimedean(
    min_bend_radius: float, separation: float, number_of_loops: float, npoints: int
) -> Path:
    """Returns an Archimedean spiral.

    Args:
        min_bend_radius: Inner radius of the spiral.
        separation: Half the radial separation between loops in um. The
            current formula is retained for compatibility with existing
            layouts, so adjacent turns are separated by ``2 * separation``.
        number_of_loops: number of loops.
        npoints: number of Points.

    Example:
        ```python
        import gdsfactory as gf

        p = gf.path.spiral_archimedean(min_bend_radius=5, separation=2, number_of_loops=3, npoints=200)
        p.plot()
        ```
    """
    theta = np.linspace(0, number_of_loops * 2 * np.pi, int(npoints))
    points = (separation / np.pi * theta + min_bend_radius)[:, None] * np.column_stack(
        (np.sin(theta), np.cos(theta))
    )
    return Path(points)

smooth

smooth(
    points: NDArray[floating[Any]] | Path,
    radius: float = 4.0,
    bend: PathFactory = euler,
    **kwargs: Any
) -> Path

Returns a smooth Path from a series of waypoints.

Parameters:

Name Type Description Default
points NDArray[floating[Any]] | Path

array-like[N][2] List of waypoints for the path to follow.

required
radius float

radius of curvature, passed to bend.

4.0
bend PathFactory

bend function that returns a path that round corners.

euler
kwargs Any

Extra keyword arguments that will be passed to bend.

{}
Example
import gdsfactory as gf

p = gf.path.smooth(([0, 0], [0, 10], [10, 10]))
p.plot()
Source code in gdsfactory/path.py
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
def smooth(
    points: npt.NDArray[np.floating[Any]] | Path,
    radius: float = 4.0,
    bend: PathFactory = euler,
    **kwargs: Any,
) -> Path:
    """Returns a smooth Path from a series of waypoints.

    Args:
        points: array-like[N][2] List of waypoints for the path to follow.
        radius: radius of curvature, passed to `bend`.
        bend: bend function that returns a path that round corners.
        kwargs: Extra keyword arguments that will be passed to `bend`.

    Example:
        ```python
        import gdsfactory as gf

        p = gf.path.smooth(([0, 0], [0, 10], [10, 10]))
        p.plot()
        ```
    """
    if isinstance(points, Path):
        points = points.points

    points, normals, ds, theta, dtheta = _compute_segments(points)
    colinear_elements = np.concatenate([[False], np.abs(dtheta) < 1e-6, [False]])
    if np.any(colinear_elements):
        points, normals, ds, theta, dtheta = _compute_segments(
            points[~colinear_elements, :]
        )

    if np.any(np.abs(np.abs(dtheta) - 180) < 1e-6):
        raise ValueError(
            "smooth() received points which double-back on themselves"
            "--turns cannot be computed when going forwards then exactly backwards."
        )

    # FIXME add caching
    # Create arcs
    paths: list[Path] = []
    radii: list[float] = []
    for dt in dtheta:
        P = bend(radius=radius, angle=dt, **kwargs)
        chord = np.linalg.norm(P.points[-1, :] - P.points[0, :])
        r = (chord / 2) / np.sin(np.radians(dt / 2))
        r = np.abs(r)
        radii.append(r)
        paths.append(P)

    d = np.abs(np.array(radii) / np.tan(np.radians(180 - dtheta) / 2))
    encroachment = np.concatenate([[0], d]) + np.concatenate([d, [0]])
    if np.any(encroachment > ds):
        raise ValueError(
            "smooth(): Not enough distance between points to to fit curves."
            "Try reducing the radius or spacing the points out farther"
        )
    p1 = points[1:-1, :] - normals[:-1, :] * d[:, np.newaxis]

    # Move arcs into position
    new_points: list[npt.NDArray[np.floating[Any]]] = []
    new_points.append(np.array([points[0, :]]))
    for n in range(len(dtheta)):
        p = paths[n]
        p.rotate(theta[n] - 0)
        p.move(p1[n])
        new_points.append(p.points)
    new_points.append(np.array([points[-1, :]]))
    new_points_np = np.concatenate(new_points)

    path = Path()
    path.append(new_points_np)
    path.rotate(float(theta[0]))
    path.move(cast("tuple[float, float]", points[0, :]))
    path.start_angle = theta[0]
    path.end_angle = theta[-1]
    return path

Cross-section functions

CrossSection

Bases: BaseModel

Waveguide information to extrude a path.

Parameters:

Name Type Description Default
sections

tuple of Sections(width, offset, layer, ports).

required
components_along_path

tuple of ComponentAlongPaths.

required
radius

default bend radius for routing (um).

required
radius_min

minimum acceptable bend radius.

required
bbox_layers

layer to add as bounding box.

required
bbox_offsets

offset to add to the bounding box.

┌────────────────────────────────────────────────────────────┐ │ │ │ │ │ boox_layer │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ ▲ │bbox_offset│ │ │ │ ├──────────►│ │ │ cladding_offset │ │ │ │ │ │ │ │ │ ├─────────────────────────▲──┴─────────┤ │ │ │ │ │ │

required
Source code in gdsfactory/cross_section/base.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
class CrossSection(BaseModel):
    """Waveguide information to extrude a path.

    Parameters:
        sections: tuple of Sections(width, offset, layer, ports).
        components_along_path: tuple of ComponentAlongPaths.
        radius: default bend radius for routing (um).
        radius_min: minimum acceptable bend radius.
        bbox_layers: layer to add as bounding box.
        bbox_offsets: offset to add to the bounding box.


           ┌────────────────────────────────────────────────────────────┐
           │                                                            │
           │                                                            │
           │                   boox_layer                               │
           │                                                            │
           │         ┌──────────────────────────────────────┐           │
           │         │                            ▲         │bbox_offset│
           │         │                            │         ├──────────►│
           │         │           cladding_offset  │         │           │
           │         │                            │         │           │
           │         ├─────────────────────────▲──┴─────────┤           │
           │         │                         │            │           │
        ─ ─┤         │           core   width  │            │           ├─ ─ center
           │         │                         │            │           │
           │         ├─────────────────────────▼────────────┤           │
           │         │                                      │           │
           │         │                                      │           │
           │         │                                      │           │
           │         │                                      │           │
           │         └──────────────────────────────────────┘           │
           │                                                            │
           │                                                            │
           │                                                            │
           └────────────────────────────────────────────────────────────┘

    """

    sections: Sections = Field(default_factory=tuple)
    components_along_path: tuple[ComponentAlongPath, ...] = Field(default_factory=tuple)
    radius: float | None = None
    radius_min: float | None = None
    bbox_layers: typings.LayerSpecs | None = None
    bbox_offsets: typings.Floats | None = None

    model_config = ConfigDict(extra="forbid", frozen=True)
    _name: str = PrivateAttr("")
    _dcross_section: DCrossSection | None = PrivateAttr()

    def validate_radius(
        self, radius: float, error_type: ErrorType | None = None
    ) -> None:
        radius_min = self.radius_min or self.radius

        if radius_min and radius < radius_min:
            message = (
                f"min_bend_radius {radius} < CrossSection.radius_min {radius_min}. "
            )

            error_type = error_type or CONF.bend_radius_error_type

            if error_type == ErrorType.ERROR:
                raise ValueError(message)

            if error_type == ErrorType.WARNING:
                warnings.warn(message, stacklevel=3)

    @property
    def name(self) -> str:
        if self._name:
            return self._name
        h = hashlib.md5(str(self).encode()).hexdigest()[:8]
        return f"xs_{h}"

    @property
    def width(self) -> float:
        return self.sections[0].width

    @property
    def layer(self) -> typings.LayerSpec:
        return self.sections[0].layer

    def append_sections(self, sections: Sections) -> Self:
        """Append sections to the cross_section."""
        new_sections = list(self.sections) + list(sections)
        return self.model_copy(update={"sections": tuple(new_sections)})

    def __getitem__(self, key: str) -> Section:
        """Returns the section with the given name."""
        key_to_section = {s.name: s for s in self.sections}
        if key in key_to_section:
            return key_to_section[key]
        raise KeyError(f"{key} not in {list(key_to_section.keys())}")

    @property
    def hash(self) -> str:
        """Returns a hash of the cross_section."""
        return hashlib.md5(str(self).encode()).hexdigest()

    def copy(
        self,
        width: float | None = None,
        layer: typings.LayerSpec | None = None,
        width_function: typings.WidthFunction | None = None,
        offset_function: typings.OffsetFunction | None = None,
        sections: Sections | None = None,
        **kwargs: Any,
    ) -> CrossSection:
        """Returns copy of the cross_section with new parameters.

        Args:
            width: of the section (um). Defaults to current width.
            layer: layer spec. Defaults to current layer.
            width_function: parameterized function from 0 to 1.
            offset_function: parameterized function from 0 to 1.
            sections: a tuple of Sections, to replace the original sections
            kwargs: additional parameters to update.

        Keyword Args:
            sections: tuple of Sections(width, offset, layer, ports).
            components_along_path: tuple of ComponentAlongPaths.
            radius: route bend radius (um).
            bbox_layers: layer to add as bounding box.
            bbox_offsets: offset to add to the bounding box.
            _name: name of the cross_section.

        """
        for kwarg in kwargs:
            if kwarg not in dict(self):
                raise ValueError(f"{kwarg!r} not in CrossSection")

        xs_original = self

        if width_function or offset_function or width or layer or sections:
            if sections is None:
                section_list = list(self.sections)
            else:
                section_list = list(sections)

            section_list = [s.model_copy() for s in section_list]
            section_list[0] = section_list[0].model_copy(
                update={
                    "width_function": width_function,
                    "offset_function": offset_function,
                    "width": width or self.width,
                    "layer": layer or self.layer,
                }
            )
            xs = self.model_copy(update={"sections": tuple(section_list), **kwargs})
            if xs != xs_original:
                xs._name = f"xs_{xs.hash}"
            return xs

        xs = self.model_copy(update=kwargs)
        if xs != xs_original:
            xs._name = f"xs_{xs.hash}"
        return xs

    def mirror(self) -> CrossSection:
        """Returns a mirrored copy of the cross_section."""
        sections = [s.model_copy(update=dict(offset=-s.offset)) for s in self.sections]
        return self.model_copy(update={"sections": tuple(sections)})

    def add_bbox(
        self,
        component: typings.AnyComponentT,
        top: float | None = None,
        bottom: float | None = None,
        right: float | None = None,
        left: float | None = None,
    ) -> typings.AnyComponentT:
        """Add bounding box layers to a component.

        Args:
            component: to add layers.
            top: top padding.
            bottom: bottom padding.
            right: right padding.
            left: left padding.
        """
        from gdsfactory.add_padding import get_padding_points

        c = component
        if self.bbox_layers and self.bbox_offsets:
            padding: list[list[typings.Coordinate]] = []
            for offset in self.bbox_offsets:
                points = get_padding_points(
                    component=c,
                    default=0,
                    top=top if top is not None else offset,
                    bottom=bottom if bottom is not None else offset,
                    right=right if right is not None else offset,
                    left=left if left is not None else offset,
                )
                padding.append(points)

            for layer, points in zip(self.bbox_layers, padding, strict=False):
                c.add_polygon(points, layer=layer)
        return c

    def get_xmin_xmax(self) -> tuple[float, float]:
        """Returns the min and max extent of the cross_section across all sections."""
        main_width = self.width
        main_offset = self.sections[0].offset
        xmin = main_offset - main_width / 2
        xmax = main_offset + main_width / 2
        for section in self.sections:
            width = section.width
            offset = section.offset
            xmin = min(xmin, offset - width / 2)
            xmax = max(xmax, offset + width / 2)

        return xmin, xmax

hash property

hash: str

Returns a hash of the cross_section.

__getitem__

__getitem__(key: str) -> Section

Returns the section with the given name.

Source code in gdsfactory/cross_section/base.py
268
269
270
271
272
273
def __getitem__(self, key: str) -> Section:
    """Returns the section with the given name."""
    key_to_section = {s.name: s for s in self.sections}
    if key in key_to_section:
        return key_to_section[key]
    raise KeyError(f"{key} not in {list(key_to_section.keys())}")

add_bbox

add_bbox(
    component: AnyComponentT,
    top: float | None = None,
    bottom: float | None = None,
    right: float | None = None,
    left: float | None = None,
) -> typings.AnyComponentT

Add bounding box layers to a component.

Parameters:

Name Type Description Default
component AnyComponentT

to add layers.

required
top float | None

top padding.

None
bottom float | None

bottom padding.

None
right float | None

right padding.

None
left float | None

left padding.

None
Source code in gdsfactory/cross_section/base.py
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
def add_bbox(
    self,
    component: typings.AnyComponentT,
    top: float | None = None,
    bottom: float | None = None,
    right: float | None = None,
    left: float | None = None,
) -> typings.AnyComponentT:
    """Add bounding box layers to a component.

    Args:
        component: to add layers.
        top: top padding.
        bottom: bottom padding.
        right: right padding.
        left: left padding.
    """
    from gdsfactory.add_padding import get_padding_points

    c = component
    if self.bbox_layers and self.bbox_offsets:
        padding: list[list[typings.Coordinate]] = []
        for offset in self.bbox_offsets:
            points = get_padding_points(
                component=c,
                default=0,
                top=top if top is not None else offset,
                bottom=bottom if bottom is not None else offset,
                right=right if right is not None else offset,
                left=left if left is not None else offset,
            )
            padding.append(points)

        for layer, points in zip(self.bbox_layers, padding, strict=False):
            c.add_polygon(points, layer=layer)
    return c

append_sections

append_sections(sections: Sections) -> Self

Append sections to the cross_section.

Source code in gdsfactory/cross_section/base.py
263
264
265
266
def append_sections(self, sections: Sections) -> Self:
    """Append sections to the cross_section."""
    new_sections = list(self.sections) + list(sections)
    return self.model_copy(update={"sections": tuple(new_sections)})

copy

copy(
    width: float | None = None,
    layer: LayerSpec | None = None,
    width_function: WidthFunction | None = None,
    offset_function: OffsetFunction | None = None,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Returns copy of the cross_section with new parameters.

Parameters:

Name Type Description Default
width float | None

of the section (um). Defaults to current width.

None
layer LayerSpec | None

layer spec. Defaults to current layer.

None
width_function WidthFunction | None

parameterized function from 0 to 1.

None
offset_function OffsetFunction | None

parameterized function from 0 to 1.

None
sections Sections | None

a tuple of Sections, to replace the original sections

None
kwargs Any

additional parameters to update.

{}

Other Parameters:

Name Type Description
sections Sections | None

tuple of Sections(width, offset, layer, ports).

components_along_path

tuple of ComponentAlongPaths.

radius

route bend radius (um).

bbox_layers

layer to add as bounding box.

bbox_offsets

offset to add to the bounding box.

_name

name of the cross_section.

Source code in gdsfactory/cross_section/base.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
def copy(
    self,
    width: float | None = None,
    layer: typings.LayerSpec | None = None,
    width_function: typings.WidthFunction | None = None,
    offset_function: typings.OffsetFunction | None = None,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Returns copy of the cross_section with new parameters.

    Args:
        width: of the section (um). Defaults to current width.
        layer: layer spec. Defaults to current layer.
        width_function: parameterized function from 0 to 1.
        offset_function: parameterized function from 0 to 1.
        sections: a tuple of Sections, to replace the original sections
        kwargs: additional parameters to update.

    Keyword Args:
        sections: tuple of Sections(width, offset, layer, ports).
        components_along_path: tuple of ComponentAlongPaths.
        radius: route bend radius (um).
        bbox_layers: layer to add as bounding box.
        bbox_offsets: offset to add to the bounding box.
        _name: name of the cross_section.

    """
    for kwarg in kwargs:
        if kwarg not in dict(self):
            raise ValueError(f"{kwarg!r} not in CrossSection")

    xs_original = self

    if width_function or offset_function or width or layer or sections:
        if sections is None:
            section_list = list(self.sections)
        else:
            section_list = list(sections)

        section_list = [s.model_copy() for s in section_list]
        section_list[0] = section_list[0].model_copy(
            update={
                "width_function": width_function,
                "offset_function": offset_function,
                "width": width or self.width,
                "layer": layer or self.layer,
            }
        )
        xs = self.model_copy(update={"sections": tuple(section_list), **kwargs})
        if xs != xs_original:
            xs._name = f"xs_{xs.hash}"
        return xs

    xs = self.model_copy(update=kwargs)
    if xs != xs_original:
        xs._name = f"xs_{xs.hash}"
    return xs

get_xmin_xmax

get_xmin_xmax() -> tuple[float, float]

Returns the min and max extent of the cross_section across all sections.

Source code in gdsfactory/cross_section/base.py
381
382
383
384
385
386
387
388
389
390
391
392
393
def get_xmin_xmax(self) -> tuple[float, float]:
    """Returns the min and max extent of the cross_section across all sections."""
    main_width = self.width
    main_offset = self.sections[0].offset
    xmin = main_offset - main_width / 2
    xmax = main_offset + main_width / 2
    for section in self.sections:
        width = section.width
        offset = section.offset
        xmin = min(xmin, offset - width / 2)
        xmax = max(xmax, offset + width / 2)

    return xmin, xmax

mirror

mirror() -> CrossSection

Returns a mirrored copy of the cross_section.

Source code in gdsfactory/cross_section/base.py
339
340
341
342
def mirror(self) -> CrossSection:
    """Returns a mirrored copy of the cross_section."""
    sections = [s.model_copy(update=dict(offset=-s.offset)) for s in self.sections]
    return self.model_copy(update={"sections": tuple(sections)})

Transition

Bases: BaseModel

Waveguide information to extrude a path between two CrossSection.

cladding_layers follow path shape

Parameters:

Name Type Description Default
cross_section1

input cross_section.

required
cross_section2

output cross_section.

required
width_type

'sine', 'linear', 'parabolic' or Callable. Sets the type of width transition used if widths are different between the two input CrossSections.

required
offset_type

'sine', 'linear', 'parabolic' or Callable. Sets the type of offset transition used if offsets are different between the two input CrossSections.

required
Source code in gdsfactory/cross_section/base.py
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
class Transition(BaseModel, arbitrary_types_allowed=True):
    """Waveguide information to extrude a path between two CrossSection.

    cladding_layers follow path shape

    Parameters:
        cross_section1: input cross_section.
        cross_section2: output cross_section.
        width_type: 'sine', 'linear', 'parabolic' or Callable. Sets the type of width \
                transition used if widths are different between the two input CrossSections.
        offset_type: 'sine', 'linear', 'parabolic' or Callable. Sets the type of offset \
                transition used if offsets are different between the two input CrossSections.
    """

    cross_section1: CrossSectionSpec
    cross_section2: CrossSectionSpec
    width_type: typings.WidthTypes | Callable[[float, float, float], float] = "sine"
    offset_type: typings.WidthTypes | Callable[[float, float, float], float] = "sine"

    @field_serializer("width_type")
    def serialize_width(
        self,
        width_type: typings.WidthTypes | Callable[[float, float, float], float],
    ) -> str:
        if isinstance(width_type, str):
            return width_type
        # TODO: implement callable serialization for width_type.
        raise NotImplementedError(
            "Serialization of callable width_type is not yet supported. "
            "Use a string value ('sine', 'linear', or 'parabolic') instead."
        )

Section

Bases: BaseModel

CrossSection to extrude a path with a waveguide.

Parameters:

Name Type Description Default
width

of the section (um). When width_function is set it takes precedence during extrusion, so width acts as a nominal value.

required
offset

center offset (um). When offset_function is set it takes precedence during extrusion, so offset acts as a nominal value.

required
insets

distance (um) in x to inset section relative to end of the Path (i.e. (start inset, stop_inset)).

required
layer

layer spec. If None does not draw the main section.

required
port_names

Optional port names.

required
port_types

optical, electrical, ...

required
name

Optional Section name.

required
hidden

hide layer.

required
simplify

Optional Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting. polygon by more than the value listed here will be removed.

required
skip_transition

if True, this section is excluded from cross-section transitions (will not be tapered between two CrossSections).

required
width_function

parameterized function from 0 to 1.

required
offset_function

parameterized function from 0 to 1.

0

│ ┌───────┐ │ │ │ │ layer │ │◄─────►│ │ │ │ │ width │ │ └───────┘ | │ | ◄────────────► +offset

required
Source code in gdsfactory/cross_section/base.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class Section(BaseModel):
    """CrossSection to extrude a path with a waveguide.

    Parameters:
        width: of the section (um). When `width_function` is set it takes \
                precedence during extrusion, so `width` acts as a nominal value.
        offset: center offset (um). When `offset_function` is set it takes \
                precedence during extrusion, so `offset` acts as a nominal value.
        insets: distance (um) in x to inset section relative to end of the Path \
                (i.e. (start inset, stop_inset)).
        layer: layer spec. If None does not draw the main section.
        port_names: Optional port names.
        port_types: optical, electrical, ...
        name: Optional Section name.
        hidden: hide layer.
        simplify: Optional Tolerance value for the simplification algorithm. \
                All points that can be removed without changing the resulting. \
                polygon by more than the value listed here will be removed.
        skip_transition: if True, this section is excluded from cross-section \
                transitions (will not be tapered between two CrossSections).
        width_function: parameterized function from 0 to 1.
        offset_function: parameterized function from 0 to 1.

         0

         │        ┌───────┐
                  │       │
         │        │ layer │
                  │◄─────►│
         │        │       │
                  │ width │
         │        └───────┘
                      |

                      |
         ◄────────────►
            +offset
    """

    width: NonNegativeFloat = 0
    offset: float = 0
    insets: tuple[float, float] | None = None
    layer: typings.LayerSpec
    port_names: tuple[str | None, str | None] = (None, None)
    port_types: tuple[str, str] = ("optical", "optical")
    name: str | None = None
    hidden: bool = False
    simplify: float | None = None
    skip_transition: bool = False

    width_function: typings.WidthFunction | None = None
    offset_function: typings.OffsetFunction | None = None

    model_config = ConfigDict(extra="forbid", frozen=True)

    @model_validator(mode="before")
    @classmethod
    def generate_default_name(cls, data: Any) -> Any:
        if not data.get("name"):
            h = hashlib.md5(str(data).encode()).hexdigest()[:8]
            data["name"] = f"s_{h}"
        return data

    @model_validator(mode="after")
    def _require_width_value_or_function(self) -> Self:
        if self.width == 0 and self.width_function is None:
            raise ValueError("Section requires `width > 0` or a `width_function`.")
        return self

    @field_serializer("width_function")
    def serialize_width_function(
        self, func: typings.WidthFunction | None
    ) -> str | None:
        if func is None:
            return None
        t_values = np.linspace(0, 1, 11)
        return ",".join([str(round(width, 3)) for width in func(t_values)])

    @field_serializer("offset_function")
    def serialize_offset_function(
        self, func: typings.OffsetFunction | None
    ) -> str | None:
        if func is None:
            return None
        t_values = np.linspace(0, 1, 11)
        return ",".join([str(round(func(offset), 3)) for offset in t_values])

cross_section

cross_section(
    width: float | WidthFunction = 0.5,
    offset: float | OffsetFunction = 0,
    layer: LayerSpec = "WG",
    sections: Sections | None = None,
    port_names: IOPorts = ("o1", "o2"),
    port_types: IOPorts = ("optical", "optical"),
    bbox_layers: LayerSpecs | None = None,
    bbox_offsets: Floats | None = None,
    cladding_layers: LayerSpecs | None = None,
    cladding_offsets: float | Floats | None = None,
    cladding_simplify: float | Floats | None = None,
    cladding_centers: float | Floats | None = None,
    radius: float | None = 10.0,
    radius_min: float | None = 7.0,
    main_section_name: str = "_default",
) -> CrossSection

Return CrossSection.

Parameters:

Name Type Description Default
width float | WidthFunction

main Section width (um) or parameterized function from 0 to 1.

0.5
offset float | OffsetFunction

main Section center offset (um) or parameterized function from 0 to 1.

0
layer LayerSpec

main section layer.

'WG'
sections Sections | None

list of Sections(width, offset, layer, ports).

None
port_names IOPorts

for input and output ('o1', 'o2').

('o1', 'o2')
port_types IOPorts

for input and output: electrical, optical, vertical_te ...

('optical', 'optical')
bbox_layers LayerSpecs | None

list of layers bounding boxes to extrude.

None
bbox_offsets Floats | None

list of offset from bounding box edge.

None
cladding_layers LayerSpecs | None

list of layers to extrude.

None
cladding_offsets float | Floats | None

offset from main Section edge. Single float is broadcast to all cladding layers.

None
cladding_simplify float | Floats | None

Optional Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting. polygon by more than the value listed here will be removed. Single float is broadcast to all cladding layers.

None
cladding_centers float | Floats | None

center offset for each cladding layer. Defaults to 0. Single float is broadcast to all cladding layers.

None
radius float | None

routing bend radius (um).

10.0
radius_min float | None

min acceptable bend radius.

7.0
main_section_name str

name of the main section. Defaults to _default

'_default'
Example
import gdsfactory as gf

xs = gf.cross_section.cross_section(width=0.5, offset=0, layer='WG')
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()


┌────────────────────────────────────────────────────────────┐
                                                            
                                                            
                   boox_layer                               
                                                            
         ┌──────────────────────────────────────┐           
                                              bbox_offset
                                              ├──────────►│
                    cladding_offset                      
                                                         
         ├─────────────────────────▲──┴─────────┤           
                                                         
 ─┤                    core   width                         ├─  center
                                                         
         ├─────────────────────────▼────────────┤           
                                                          
                                                          
                                                          
                                                          
         └──────────────────────────────────────┘           
                                                            
                                                            
                                                            
└────────────────────────────────────────────────────────────┘
Source code in gdsfactory/cross_section/utils.py
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def cross_section(
    width: float | typings.WidthFunction = 0.5,
    offset: float | typings.OffsetFunction = 0,
    layer: typings.LayerSpec = "WG",
    sections: Sections | None = None,
    port_names: typings.IOPorts = ("o1", "o2"),
    port_types: typings.IOPorts = ("optical", "optical"),
    bbox_layers: typings.LayerSpecs | None = None,
    bbox_offsets: typings.Floats | None = None,
    cladding_layers: typings.LayerSpecs | None = None,
    cladding_offsets: float | typings.Floats | None = None,
    cladding_simplify: float | typings.Floats | None = None,
    cladding_centers: float | typings.Floats | None = None,
    radius: float | None = 10.0,
    radius_min: float | None = 7.0,
    main_section_name: str = "_default",
) -> CrossSection:
    """Return CrossSection.

    Args:
        width: main Section width (um) or parameterized function from 0 to 1.
        offset: main Section center offset (um) or parameterized function from 0 to 1.
        layer: main section layer.
        sections: list of Sections(width, offset, layer, ports).
        port_names: for input and output ('o1', 'o2').
        port_types: for input and output: electrical, optical, vertical_te ...
        bbox_layers: list of layers bounding boxes to extrude.
        bbox_offsets: list of offset from bounding box edge.
        cladding_layers: list of layers to extrude.
        cladding_offsets: offset from main Section edge. Single float is
            broadcast to all cladding layers.
        cladding_simplify: Optional Tolerance value for the simplification algorithm. \
                All points that can be removed without changing the resulting. \
                polygon by more than the value listed here will be removed. \
                Single float is broadcast to all cladding layers.
        cladding_centers: center offset for each cladding layer. Defaults to 0. \
                Single float is broadcast to all cladding layers.
        radius: routing bend radius (um).
        radius_min: min acceptable bend radius.
        main_section_name: name of the main section. Defaults to _default

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.cross_section(width=0.5, offset=0, layer='WG')
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()


        ┌────────────────────────────────────────────────────────────┐
        │                                                            │
        │                                                            │
        │                   boox_layer                               │
        │                                                            │
        │         ┌──────────────────────────────────────┐           │
        │         │                            ▲         │bbox_offset│
        │         │                            │         ├──────────►│
        │         │           cladding_offset  │         │           │
        │         │                            │         │           │
        │         ├─────────────────────────▲──┴─────────┤           │
        │         │                         │            │           │
        ─ ─┤         │           core   width  │            │           ├─ ─ center
        │         │                         │            │           │
        │         ├─────────────────────────▼────────────┤           │
        │         │                                      │           │
        │         │                                      │           │
        │         │                                      │           │
        │         │                                      │           │
        │         └──────────────────────────────────────┘           │
        │                                                            │
        │                                                            │
        │                                                            │
        └────────────────────────────────────────────────────────────┘
        ```
    """
    section_list: list[Section] = list(sections or [])
    cladding_simplify_not_none: list[float | None] | None = None
    cladding_offsets_not_none: list[float] | None = None
    cladding_centers_not_none: list[float] | None = None
    if cladding_layers:

        def _broadcast(
            value: float | typings.Floats | None, default: float | None
        ) -> list[Any]:
            if isinstance(value, (int, float, np.number)):
                return [float(value)] * len(cladding_layers)
            if value is None or len(value) == 0:
                return [default] * len(cladding_layers)
            return list(value)

        cladding_simplify_not_none = _broadcast(cladding_simplify, None)
        cladding_offsets_not_none = _broadcast(cladding_offsets, 0)
        cladding_centers_not_none = _broadcast(cladding_centers, 0)

        if (
            len(
                {
                    len(x)
                    for x in (
                        cladding_layers,
                        cladding_offsets_not_none,
                        cladding_simplify_not_none,
                        cladding_centers_not_none,
                    )
                }
            )
            > 1
        ):
            raise ValueError(
                f"{len(cladding_layers)=}, "
                f"{len(cladding_offsets_not_none)=}, "
                f"{len(cladding_simplify_not_none)=}, "
                f"{len(cladding_centers_not_none)=} must have same length"
            )
    s = [
        Section(
            width=0 if callable(width) else cast(Any, width),
            width_function=cast(Callable[..., Any], width) if callable(width) else None,
            offset=0 if callable(offset) else cast(Any, offset),
            offset_function=cast(Callable[..., Any], offset)
            if callable(offset)
            else None,
            layer=layer,
            port_names=port_names,
            port_types=port_types,
            name=main_section_name,
        )
    ] + section_list

    if (
        cladding_layers
        and cladding_offsets_not_none
        and cladding_simplify_not_none
        and cladding_centers_not_none
    ):

        def _cladding_width_kwargs(offset: float) -> dict[str, Any]:
            if callable(width):
                return {
                    "width_function": lambda t: cast(Callable[..., Any], width)(t)
                    + 2 * offset
                }
            return {"width": cast(Any, width) + 2 * offset}

        s += [
            Section(
                **_cladding_width_kwargs(cladding_offset),
                layer=cladding_layer,
                simplify=cladding_simplify,
                offset=cladding_center,
                name=f"cladding_{i}",
            )
            for i, (
                cladding_layer,
                cladding_offset,
                cladding_simplify,
                cladding_center,
            ) in enumerate(
                zip(
                    cladding_layers,
                    cladding_offsets_not_none,
                    cladding_simplify_not_none,
                    cladding_centers_not_none,
                    strict=False,
                )
            )
        ]
    return CrossSection(
        sections=tuple(s),
        radius=radius,
        radius_min=radius_min,
        bbox_layers=bbox_layers,
        bbox_offsets=bbox_offsets,
    )

strip

strip(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    radius: float = 10.0,
    radius_min: float = 3.5,
    **kwargs: Any
) -> CrossSection

Return Strip cross_section.

Parameters:

Name Type Description Default
width

main Section width (um).

required
layer

main section layer.

required
radius

routing bend radius (um).

required
radius_min

min acceptable bend radius.

required
kwargs

cross_section settings.

required
Source code in gdsfactory/cross_section/presets.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@xsection
def strip(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    radius: float = 10.0,
    radius_min: float = 3.5,
    **kwargs: Any,
) -> CrossSection:
    """Return Strip cross_section.

    Args:
        width: main Section width (um).
        layer: main section layer.
        radius: routing bend radius (um).
        radius_min: min acceptable bend radius.
        kwargs: cross_section settings.
    """
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        **kwargs,
    )

strip_no_ports

strip_no_ports(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    radius: float = 10.0,
    radius_min: float = 5,
    port_names: IOPorts = ("", ""),
    **kwargs: Any
) -> CrossSection

Return Strip cross_section without ports.

Parameters:

Name Type Description Default
width float

main Section width (um).

0.5
layer LayerSpec

main section layer.

'WG'
radius float

routing bend radius (um).

10.0
radius_min float

min acceptable bend radius.

5
port_names IOPorts

for input and output ('o1', 'o2').

('', '')
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/cross_section/presets.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@xsection
def strip_no_ports(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    radius: float = 10.0,
    radius_min: float = 5,
    port_names: typings.IOPorts = ("", ""),
    **kwargs: Any,
) -> CrossSection:
    """Return Strip cross_section without ports.

    Args:
        width: main Section width (um).
        layer: main section layer.
        radius: routing bend radius (um).
        radius_min: min acceptable bend radius.
        port_names: for input and output ('o1', 'o2').
        kwargs: cross_section settings.
    """
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        port_names=port_names,
        **kwargs,
    )

slot

slot(
    width: float = 0.5,
    layer: LayerSpec = "WG_ABSTRACT",
    slot_width: float = 0.04,
    rail_layer: LayerSpec = "WG",
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Return CrossSection Slot (with an etched region in the center).

Parameters:

Name Type Description Default
width float

main Section width (um) or function parameterized from 0 to 1. the width at t==0 is the width at the beginning of the Path. the width at t==1 is the width at the end.

0.5
layer LayerSpec

main section layer.

'WG_ABSTRACT'
slot_width float

in um.

0.04
rail_layer LayerSpec

rail layer.

'WG'
sections Sections | None

list of Sections(width, offset, layer, ports).

None
kwargs Any

other cross section parameters.

{}
Example
import gdsfactory as gf

xs = gf.cross_section.slot(width=0.5, slot_width=0.05, layer='WG')
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/presets.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
@xsection
def slot(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG_ABSTRACT",
    slot_width: float = 0.04,
    rail_layer: typings.LayerSpec = "WG",
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Return CrossSection Slot (with an etched region in the center).

    Args:
        width: main Section width (um) or function parameterized from 0 to 1. \
                the width at t==0 is the width at the beginning of the Path. \
                the width at t==1 is the width at the end.
        layer: main section layer.
        slot_width: in um.
        rail_layer: rail layer.
        sections: list of Sections(width, offset, layer, ports).
        kwargs: other cross section parameters.

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.slot(width=0.5, slot_width=0.05, layer='WG')
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    if slot_width >= width:
        raise ValueError(f"{width=} must be greater than {slot_width=}")

    rail_width = (width - slot_width) / 2
    rail_offset = (rail_width + slot_width) / 2

    section_list: list[Section] = list(sections or [])
    section_list.extend(
        [
            Section(
                width=rail_width,
                offset=+rail_offset,
                layer=rail_layer,
                port_names=("o3", "o4"),
                name="left_rail",
            ),
            Section(
                width=rail_width,
                offset=-rail_offset,
                layer=rail_layer,
                port_names=("o5", "o6"),
                name="right_rail",
            ),
        ]
    )

    return strip(
        width=width,
        layer=layer,
        sections=section_list,
        **kwargs,
    )

rib

rib(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    radius: float = radius_rib,
    radius_min: float | None = 7,
    cladding_layers: LayerSpecs = ("SLAB90",),
    cladding_offsets: Floats = (3,),
    cladding_simplify: Floats = (50 * nm,),
    **kwargs: Any
) -> CrossSection

Return Rib cross_section.

Source code in gdsfactory/cross_section/presets.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
@xsection
def rib(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    radius: float = radius_rib,
    radius_min: float | None = 7,
    cladding_layers: typings.LayerSpecs = ("SLAB90",),
    cladding_offsets: typings.Floats = (3,),
    cladding_simplify: typings.Floats = (50 * nm,),
    **kwargs: Any,
) -> CrossSection:
    """Return Rib cross_section."""
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        cladding_layers=cladding_layers,
        cladding_offsets=cladding_offsets,
        cladding_simplify=cladding_simplify,
        **kwargs,
    )

rib_bbox

rib_bbox(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    radius: float = radius_rib,
    radius_min: float | None = None,
    bbox_layers: LayerSpecs = ("SLAB90",),
    bbox_offsets: Floats = (3,),
    **kwargs: Any
) -> CrossSection

Return Rib cross_section.

Source code in gdsfactory/cross_section/presets.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@xsection
def rib_bbox(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    radius: float = radius_rib,
    radius_min: float | None = None,
    bbox_layers: typings.LayerSpecs = ("SLAB90",),
    bbox_offsets: typings.Floats = (3,),
    **kwargs: Any,
) -> CrossSection:
    """Return Rib cross_section."""
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        bbox_layers=bbox_layers,
        bbox_offsets=bbox_offsets,
        **kwargs,
    )

rib2

rib2(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    radius: float = radius_rib,
    radius_min: float | None = None,
    width_slab: float = 6,
    **kwargs: Any
) -> CrossSection

Return Rib cross_section.

Source code in gdsfactory/cross_section/presets.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
@xsection
def rib2(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    layer_slab: typings.LayerSpec = "SLAB90",
    radius: float = radius_rib,
    radius_min: float | None = None,
    width_slab: float = 6,
    **kwargs: Any,
) -> CrossSection:
    """Return Rib cross_section."""
    sections = (
        Section(width=width_slab, layer=layer_slab, name="slab", simplify=50 * nm),
    )
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        sections=sections,
        **kwargs,
    )

rib_with_trenches

rib_with_trenches(
    width: float = 0.5,
    width_trench: float = 2.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    simplify_slab: float | None = None,
    layer: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    wg_marking_layer: LayerSpec = "WG_ABSTRACT",
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Return CrossSection of rib waveguide defined by trenches.

Parameters:

Name Type Description Default
width float

main Section width (um) or function parameterized from 0 to 1. the width at t==0 is the width at the beginning of the Path. the width at t==1 is the width at the end.

0.5
width_trench float

in um.

2.0
slab_offset float | None

from the edge of the trench to the edge of the slab.

0.3
width_slab float | None

in um.

None
simplify_slab float | None

Optional Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting polygon by more than the value listed here will be removed.

None
layer LayerSpec

slab layer.

'WG'
layer_trench LayerSpec

layer to etch trenches.

'DEEP_ETCH'
wg_marking_layer LayerSpec

layer to draw over the actual waveguide. This can be useful for booleans, routing, placement ...

'WG_ABSTRACT'
sections Sections | None

list of Sections(width, offset, layer, ports).

None
kwargs Any

cross_section settings.

        ┌─────────┐
        │         │ wg_marking_layer
        └─────────┘

┌────────┐ ┌────────┐ │ │ │ │layer_trench └────────┘ └────────┘

┌─────────────────────────────────────────┐ │ layer │ │ │ └─────────────────────────────────────────┘ ◄─────────► width ┌─────┐ ┌────────┐ ┌───────┐ │ │ │ │ │ │ │ └─────────┘ └────────┘ │ │ ◄---------► ◄-------► │ └─────────────────────────────────────────┘ slab_offset width_trench ──────► | ◄────────────────────────────────────────► width_slab

{}
Example
import gdsfactory as gf

xs = gf.cross_section.rib_with_trenches(width=0.5)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/presets.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
@xsection
def rib_with_trenches(
    width: float = 0.5,
    width_trench: float = 2.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    simplify_slab: float | None = None,
    layer: typings.LayerSpec = "WG",
    layer_trench: typings.LayerSpec = "DEEP_ETCH",
    wg_marking_layer: typings.LayerSpec = "WG_ABSTRACT",
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Return CrossSection of rib waveguide defined by trenches.

    Args:
        width: main Section width (um) or function parameterized from 0 to 1. \
                the width at t==0 is the width at the beginning of the Path. \
                the width at t==1 is the width at the end.
        width_trench: in um.
        slab_offset: from the edge of the trench to the edge of the slab.
        width_slab: in um.
        simplify_slab: Optional Tolerance value for the simplification algorithm. \
                All points that can be removed without changing the resulting\
                polygon by more than the value listed here will be removed.
        layer: slab layer.
        layer_trench: layer to etch trenches.
        wg_marking_layer: layer to draw over the actual waveguide. \
                This can be useful for booleans, routing, placement ...
        sections: list of Sections(width, offset, layer, ports).
        kwargs: cross_section settings.

                        ┌─────────┐
                        │         │ wg_marking_layer
                        └─────────┘

               ┌────────┐         ┌────────┐
               │        │         │        │layer_trench
               └────────┘         └────────┘

         ┌─────────────────────────────────────────┐
         │                                  layer  │
         │                                         │
         └─────────────────────────────────────────┘
                        ◄─────────►
                           width
         ┌─────┐         ┌────────┐        ┌───────┐
         │     │         │        │        │       │
         │     └─────────┘        └────────┘       │
         │     ◄---------►         ◄-------►       │
         └─────────────────────────────────────────┘
                                            slab_offset
              width_trench                  ──────►
                                                   |
         ◄────────────────────────────────────────►
                      width_slab


    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.rib_with_trenches(width=0.5)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    if slab_offset is None and width_slab is None:
        raise ValueError("Must specify either slab_offset or width_slab")

    if slab_offset is not None and width_slab is not None:
        raise ValueError("Cannot specify both slab_offset and width_slab")

    if slab_offset is not None:
        width_slab = width + 2 * width_trench + 2 * slab_offset

    trench_offset = width / 2 + width_trench / 2
    section_list: list[Section] = list(sections or ())
    assert width_slab is not None
    section_list.append(
        Section(width=width_slab, layer=layer, name="slab", simplify=simplify_slab)
    )
    section_list += [
        Section(
            width=width_trench, offset=offset, layer=layer_trench, name=f"trench_{i}"
        )
        for i, offset in enumerate([+trench_offset, -trench_offset])
    ]

    return cross_section(
        layer=wg_marking_layer,
        width=width,
        sections=tuple(section_list),
        **kwargs,
    )

nitride

nitride(
    width: float = 1.0,
    layer: LayerSpec = "WGN",
    radius: float = radius_nitride,
    radius_min: float | None = None,
    **kwargs: Any
) -> CrossSection

Return Strip cross_section.

Source code in gdsfactory/cross_section/presets.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@xsection
def nitride(
    width: float = 1.0,
    layer: typings.LayerSpec = "WGN",
    radius: float = radius_nitride,
    radius_min: float | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Return Strip cross_section."""
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        **kwargs,
    )

strip_rib_tip

strip_rib_tip(
    width: float = 0.5,
    width_tip: float = 0.2,
    layer: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    radius: float = 10.0,
    radius_min: float | None = 5,
    **kwargs: Any
) -> CrossSection

Return Rib tip cross_section.

Source code in gdsfactory/cross_section/presets.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@xsection
def strip_rib_tip(
    width: float = 0.5,
    width_tip: float = 0.2,
    layer: typings.LayerSpec = "WG",
    layer_slab: typings.LayerSpec = "SLAB90",
    radius: float = 10.0,
    radius_min: float | None = 5,
    **kwargs: Any,
) -> CrossSection:
    """Return Rib tip cross_section."""
    sections = (Section(width=width_tip, layer=layer_slab, name="slab"),)
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        sections=sections,
        **kwargs,
    )

strip_nitride_tip

strip_nitride_tip(
    width: float = 1.0,
    layer: LayerSpec = "WGN",
    layer_silicon: LayerSpec = "WG",
    width_tip_nitride: float = 0.2,
    width_tip_silicon: float = 0.1,
    radius: float = radius_nitride,
    radius_min: float | None = None,
    **kwargs: Any
) -> CrossSection

Return the end of the nitride tip.

Parameters:

Name Type Description Default
width float

main Section width (um).

1.0
layer LayerSpec

main section layer.

'WGN'
layer_silicon LayerSpec

silicon layer.

'WG'
width_tip_nitride float

in um.

0.2
width_tip_silicon float

in um.

0.1
radius float

routing bend radius (um).

radius_nitride
radius_min float | None

min acceptable bend radius.

None
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/cross_section/presets.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
@xsection
def strip_nitride_tip(
    width: float = 1.0,
    layer: typings.LayerSpec = "WGN",
    layer_silicon: typings.LayerSpec = "WG",
    width_tip_nitride: float = 0.2,
    width_tip_silicon: float = 0.1,
    radius: float = radius_nitride,
    radius_min: float | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Return the end of the nitride tip.

    Args:
        width: main Section width (um).
        layer: main section layer.
        layer_silicon: silicon layer.
        width_tip_nitride: in um.
        width_tip_silicon: in um.
        radius: routing bend radius (um).
        radius_min: min acceptable bend radius.
        kwargs: cross_section settings.

    """
    sections = (
        Section(width=width_tip_nitride, layer=layer, name="tip_nitride"),
        Section(width=width_tip_silicon, layer=layer_silicon, name="tip_silicon"),
    )
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        radius_min=radius_min,
        sections=sections,
        **kwargs,
    )

l_with_trenches

l_with_trenches(
    width: float = 0.5,
    width_trench: float = 2.0,
    width_slab: float = 7.0,
    layer: LayerSpec = "WG",
    layer_slab: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    mirror: bool = False,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Return CrossSection of l waveguide defined by trenches.

Parameters:

Name Type Description Default
width float

main Section width (um) or function parameterized from 0 to 1. the width at t==0 is the width at the beginning of the Path. the width at t==1 is the width at the end.

0.5
width_trench float

in um.

2.0
width_slab float

in um.

7.0
layer LayerSpec

ridge layer. None adds only ridge.

'WG'
layer_slab LayerSpec

slab layer.

'WG'
layer_trench LayerSpec

layer to etch trenches.

'DEEP_ETCH'
mirror bool

this cross section is not symmetric and you can switch orientation.

False
sections Sections | None

list of Sections(width, offset, layer, ports).

None
kwargs Any

cross_section settings. x = 0 | |

{}
   _________________________
         <------->          |
        width_trench
                   <-------->
                      width
                            |
   <------------------------>
        width_slab
Example
import gdsfactory as gf

xs = gf.cross_section.l_with_trenches(width=0.5)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/presets.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
@xsection
def l_with_trenches(
    width: float = 0.5,
    width_trench: float = 2.0,
    width_slab: float = 7.0,
    layer: typings.LayerSpec = "WG",
    layer_slab: typings.LayerSpec = "WG",
    layer_trench: typings.LayerSpec = "DEEP_ETCH",
    mirror: bool = False,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Return CrossSection of l waveguide defined by trenches.

    Args:
        width: main Section width (um) or function parameterized from 0 to 1. \
                the width at t==0 is the width at the beginning of the Path. \
                the width at t==1 is the width at the end.
        width_trench: in um.
        width_slab: in um.
        layer: ridge layer. None adds only ridge.
        layer_slab: slab layer.
        layer_trench: layer to etch trenches.
        mirror: this cross section is not symmetric and you can switch orientation.
        sections: list of Sections(width, offset, layer, ports).
        kwargs: cross_section settings.
                          x = 0
                           |
                           |
        _____         __________
             |        |         |
             |________|         |

    ```text
       _________________________
             <------->          |
            width_trench
                       <-------->
                          width
                                |
       <------------------------>
            width_slab
    ```



    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.l_with_trenches(width=0.5)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    mult = 1 if mirror else -1
    trench_offset = mult * (width / 2 + width_trench / 2)
    section_list: list[Section] = list(sections or ())
    section_list += [
        Section(
            width=width_slab,
            layer=layer_slab,
            offset=mult * (width_slab / 2 - width / 2),
        )
    ]
    section_list += [
        Section(width=width_trench, offset=trench_offset, layer=layer_trench)
    ]

    return cross_section(
        width=width,
        layer=layer,
        sections=tuple(section_list),
        **kwargs,
    )

metal1

metal1(
    width: float = 10,
    layer: LayerSpec = "M1",
    radius: float | None = None,
    port_names: IOPorts = port_names_electrical,
    port_types: IOPorts = port_types_electrical,
    **kwargs: Any
) -> CrossSection

Return Metal Strip cross_section.

Source code in gdsfactory/cross_section/presets.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
@xsection
def metal1(
    width: float = 10,
    layer: typings.LayerSpec = "M1",
    radius: float | None = None,
    port_names: typings.IOPorts = port_names_electrical,
    port_types: typings.IOPorts = port_types_electrical,
    **kwargs: Any,
) -> CrossSection:
    """Return Metal Strip cross_section."""
    radius = radius or width
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        port_names=port_names,
        port_types=port_types,
        **kwargs,
    )

metal2

metal2(
    width: float = 10,
    layer: LayerSpec = "M2",
    radius: float | None = None,
    port_names: IOPorts = port_names_electrical,
    port_types: IOPorts = port_types_electrical,
    **kwargs: Any
) -> CrossSection

Return Metal Strip cross_section.

Source code in gdsfactory/cross_section/presets.py
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
@xsection
def metal2(
    width: float = 10,
    layer: typings.LayerSpec = "M2",
    radius: float | None = None,
    port_names: typings.IOPorts = port_names_electrical,
    port_types: typings.IOPorts = port_types_electrical,
    **kwargs: Any,
) -> CrossSection:
    """Return Metal Strip cross_section."""
    radius = radius or width
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        port_names=port_names,
        port_types=port_types,
        **kwargs,
    )

metal3

metal3(
    width: float = 10,
    layer: LayerSpec = "M3",
    radius: float | None = None,
    port_names: IOPorts = port_names_electrical,
    port_types: IOPorts = port_types_electrical,
    **kwargs: Any
) -> CrossSection

Return Metal Strip cross_section.

Source code in gdsfactory/cross_section/presets.py
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
@xsection
def metal3(
    width: float = 10,
    layer: typings.LayerSpec = "M3",
    radius: float | None = None,
    port_names: typings.IOPorts = port_names_electrical,
    port_types: typings.IOPorts = port_types_electrical,
    **kwargs: Any,
) -> CrossSection:
    """Return Metal Strip cross_section."""
    radius = radius or width
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        port_names=port_names,
        port_types=port_types,
        **kwargs,
    )

metal_routing

metal_routing(
    width: float = 10,
    layer: LayerSpec = "M3",
    radius: float | None = None,
    port_names: IOPorts = port_names_electrical,
    port_types: IOPorts = port_types_electrical,
    **kwargs: Any
) -> CrossSection

Return Metal Strip cross_section.

Source code in gdsfactory/cross_section/presets.py
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
@xsection
def metal_routing(
    width: float = 10,
    layer: typings.LayerSpec = "M3",
    radius: float | None = None,
    port_names: typings.IOPorts = port_names_electrical,
    port_types: typings.IOPorts = port_types_electrical,
    **kwargs: Any,
) -> CrossSection:
    """Return Metal Strip cross_section."""
    radius = radius or width

    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        port_names=port_names,
        port_types=port_types,
        **kwargs,
    )

gs

gs(
    trace_width: float = 140,
    layer: LayerSpec = "M3",
    gap: float = 120,
    layer_port: LayerSpec = "M3_ABSTRACT",
    radius: float | None = None,
    **kwargs: Any
) -> CrossSection

Return Ground-Signal-Ground cross_section.

Parameters:

Name Type Description Default
trace_width float

in um.

140
layer LayerSpec

metal layer.

'M3'
gap float

between metal lines in um.

120
layer_port LayerSpec

port layer.

'M3_ABSTRACT'
radius float | None

bend radius. Optional, defaults to 2*width+gap.

None
kwargs Any

cross_section settings. (ignored)

{}
Source code in gdsfactory/cross_section/presets.py
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
@xsection
def gs(
    trace_width: float = 140,
    layer: typings.LayerSpec = "M3",
    gap: float = 120,
    layer_port: typings.LayerSpec = "M3_ABSTRACT",
    radius: float | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Return Ground-Signal-Ground cross_section.

    Args:
        trace_width: in um.
        layer: metal layer.
        gap: between metal lines in um.
        layer_port: port layer.
        radius: bend radius. Optional, defaults to 2*width+gap.
        kwargs: cross_section settings. (ignored)
    """
    width = trace_width
    sections = [
        Section(
            width=gap,
            layer=layer_port,
            offset=0,
            port_names=port_names_electrical,
            port_types=port_types_electrical,
        ),
        Section(width=width, layer=layer, offset=+gap / 2 + width / 2),
        Section(width=width, layer=layer, offset=-gap / 2 - width / 2),
    ]
    return CrossSection(sections=tuple(sections), radius=radius or 2 * width + gap)

gsg

gsg(
    trace_width: float = 140,
    layer: LayerSpec = "M3",
    gap: float = 100,
    radius: float | None = None,
) -> CrossSection

Return Ground-Signal-Ground cross_section.

Parameters:

Name Type Description Default
trace_width float

in um.

140
layer LayerSpec

metal layer.

'M3'
gap float

between metal lines in um.

100
layer_port

port layer.

required
radius float | None

bend radius. Optional, defaults to 3width+2gap.

None
Source code in gdsfactory/cross_section/presets.py
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
@xsection
def gsg(
    trace_width: float = 140,
    layer: typings.LayerSpec = "M3",
    gap: float = 100,
    radius: float | None = None,
) -> CrossSection:
    """Return Ground-Signal-Ground cross_section.

    Args:
        trace_width: in um.
        layer: metal layer.
        gap: between metal lines in um.
        layer_port: port layer.
        radius: bend radius. Optional, defaults to 3*width+2*gap.
    """
    width = trace_width
    sections = [
        Section(
            width=width,
            layer=layer,
            offset=0,
            port_names=port_names_electrical,
            port_types=port_types_electrical,
        ),
        Section(width=width, layer=layer, offset=-gap - width),
        Section(width=width, layer=layer, offset=+gap + width),
    ]
    return CrossSection(sections=tuple(sections), radius=radius or 3 * width + 2 * gap)

heater_metal

heater_metal(
    width: float = 2.5,
    layer: LayerSpec = "HEATER",
    radius: float | None = None,
    port_names: IOPorts = port_names_electrical,
    port_types: IOPorts = port_types_electrical,
    **kwargs: Any
) -> CrossSection

Return Metal Strip cross_section.

Source code in gdsfactory/cross_section/presets.py
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
@xsection
def heater_metal(
    width: float = 2.5,
    layer: typings.LayerSpec = "HEATER",
    radius: float | None = None,
    port_names: typings.IOPorts = port_names_electrical,
    port_types: typings.IOPorts = port_types_electrical,
    **kwargs: Any,
) -> CrossSection:
    """Return Metal Strip cross_section."""
    radius = radius or width
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        port_names=port_names,
        port_types=port_types,
        **kwargs,
    )

npp

npp(
    width: float = 0.5,
    layer: LayerSpec = "NPP",
    radius: float | None = None,
    port_names: IOPorts = port_names_electrical,
    port_types: IOPorts = port_types_electrical,
    **kwargs: Any
) -> CrossSection

Return Doped NPP cross_section.

Source code in gdsfactory/cross_section/presets.py
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
@xsection
def npp(
    width: float = 0.5,
    layer: typings.LayerSpec = "NPP",
    radius: float | None = None,
    port_names: typings.IOPorts = port_names_electrical,
    port_types: typings.IOPorts = port_types_electrical,
    **kwargs: Any,
) -> CrossSection:
    """Return Doped NPP cross_section."""
    return cross_section(
        width=width,
        layer=layer,
        radius=radius,
        port_names=port_names,
        port_types=port_types,
        **kwargs,
    )

pin

pin(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    layers_via_stack1: LayerSpecs = ("PPP",),
    layers_via_stack2: LayerSpecs = ("NPP",),
    bbox_offsets_via_stack1: tuple[float, ...] = (0, -0.2),
    bbox_offsets_via_stack2: tuple[float, ...] = (0, -0.2),
    via_stack_width: float = 9.0,
    via_stack_gap: float = 0.55,
    slab_gap: float = -0.2,
    layer_via: LayerSpec | None = None,
    via_width: float = 1,
    via_offsets: tuple[float, ...] | None = None,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Rib PIN doped cross_section.

Parameters:

Name Type Description Default
width float

ridge width.

0.5
layer LayerSpec

ridge layer.

'WG'
layer_slab LayerSpec

slab layer.

'SLAB90'
layers_via_stack1 LayerSpecs

list of bot layer.

('PPP',)
layers_via_stack2 LayerSpecs

list of top layer.

('NPP',)
bbox_offsets_via_stack1 tuple[float, ...]

for bot.

(0, -0.2)
bbox_offsets_via_stack2 tuple[float, ...]

for top.

(0, -0.2)
via_stack_width float

in um.

9.0
via_stack_gap float

offset from via_stack to ridge edge.

0.55
slab_gap float

extra slab gap (negative: via_stack goes beyond slab).

-0.2
layer_via LayerSpec | None

for via.

None
via_width float

in um.

1
via_offsets tuple[float, ...] | None

in um.

None
sections Sections | None

cross_section sections.

None
kwargs Any

cross_section settings.

{}

https://doi.org/10.1364/OE.26.029983

                                  layer
                            |<----width--->|
                             _______________ via_stack_gap           slab_gap
                            |              |<----------->|             <-->
    ___ ____________________|              |__________________________|___
   |   |         |                                       |            |   |
   |   |    P++  |         undoped silicon               |     N++    |   |
   |___|_________|_______________________________________|____________|___|
                                                          <----------->
                                                          via_stack_width
   <---------------------------------------------------------------------->
                               slab_width
Example
import gdsfactory as gf

xs = gf.cross_section.pin(width=0.5, via_stack_gap=1, via_stack_width=1)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/pn_junction.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@xsection
def pin(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    layer_slab: typings.LayerSpec = "SLAB90",
    layers_via_stack1: typings.LayerSpecs = ("PPP",),
    layers_via_stack2: typings.LayerSpecs = ("NPP",),
    bbox_offsets_via_stack1: tuple[float, ...] = (0, -0.2),
    bbox_offsets_via_stack2: tuple[float, ...] = (0, -0.2),
    via_stack_width: float = 9.0,
    via_stack_gap: float = 0.55,
    slab_gap: float = -0.2,
    layer_via: typings.LayerSpec | None = None,
    via_width: float = 1,
    via_offsets: tuple[float, ...] | None = None,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Rib PIN doped cross_section.

    Args:
        width: ridge width.
        layer: ridge layer.
        layer_slab: slab layer.
        layers_via_stack1: list of bot layer.
        layers_via_stack2: list of top layer.
        bbox_offsets_via_stack1: for bot.
        bbox_offsets_via_stack2: for top.
        via_stack_width: in um.
        via_stack_gap: offset from via_stack to ridge edge.
        slab_gap: extra slab gap (negative: via_stack goes beyond slab).
        layer_via: for via.
        via_width: in um.
        via_offsets: in um.
        sections: cross_section sections.
        kwargs: cross_section settings.


    https://doi.org/10.1364/OE.26.029983

    ```text
                                      layer
                                |<----width--->|
                                 _______________ via_stack_gap           slab_gap
                                |              |<----------->|             <-->
        ___ ____________________|              |__________________________|___
       |   |         |                                       |            |   |
       |   |    P++  |         undoped silicon               |     N++    |   |
       |___|_________|_______________________________________|____________|___|
                                                              <----------->
                                                              via_stack_width
       <---------------------------------------------------------------------->
                                   slab_width
    ```

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.pin(width=0.5, via_stack_gap=1, via_stack_width=1)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    section_list: list[Section] = list(sections or [])
    slab_width = width + 2 * via_stack_gap + 2 * via_stack_width - 2 * slab_gap
    via_stack_offset = width / 2 + via_stack_gap + via_stack_width / 2

    section_list += [Section(width=slab_width, layer=layer_slab, name="slab")]
    section_list += [
        Section(
            layer=layer,
            width=via_stack_width + 2 * cladding_offset,
            offset=+via_stack_offset,
        )
        for layer, cladding_offset in zip(
            layers_via_stack1, bbox_offsets_via_stack1, strict=False
        )
    ]
    section_list += [
        Section(
            layer=layer,
            width=via_stack_width + 2 * cladding_offset,
            offset=-via_stack_offset,
        )
        for layer, cladding_offset in zip(
            layers_via_stack2, bbox_offsets_via_stack2, strict=False
        )
    ]
    if layer_via and via_width and via_offsets:
        section_list += [
            Section(
                layer=layer_via,
                width=via_width,
                offset=offset,
            )
            for offset in via_offsets
        ]

    return strip(
        width=width,
        layer=layer,
        sections=tuple(section_list),
        **kwargs,
    )

pn

pn(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    gap_low_doping: float = 0.0,
    gap_medium_doping: float = 0.5,
    gap_high_doping: float = 1.0,
    offset_low_doping: float = 0.0,
    width_doping: float = 8.0,
    width_slab: float = 7.0,
    layer_p: LayerSpec | None = "P",
    layer_pp: LayerSpec | None = "PP",
    layer_ppp: LayerSpec | None = "PPP",
    layer_n: LayerSpec | None = "N",
    layer_np: LayerSpec | None = "NP",
    layer_npp: LayerSpec | None = "NPP",
    layer_via: LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: tuple[str, str] = ("o1", "o2"),
    sections: Sections | None = None,
    cladding_layers: LayerSpecs | None = None,
    cladding_offsets: Floats | None = None,
    cladding_simplify: Floats | None = None,
    slab_inset: float | None = None,
    **kwargs: Any
) -> CrossSection

Rib PN doped cross_section.

Parameters:

Name Type Description Default
width float

width of the ridge in um.

0.5
layer LayerSpec

ridge layer.

'WG'
layer_slab LayerSpec

slab layer.

'SLAB90'
gap_low_doping float

from waveguide center to low doping. Only used for PIN.

0.0
gap_medium_doping float

from waveguide center to medium doping. None removes it.

0.5
gap_high_doping float

from center to high doping. None removes it.

1.0
offset_low_doping float

from center to junction center.

0.0
width_doping float

in um.

8.0
width_slab float

in um.

7.0
layer_p LayerSpec | None

p doping layer.

'P'
layer_pp LayerSpec | None

p+ doping layer.

'PP'
layer_ppp LayerSpec | None

p++ doping layer.

'PPP'
layer_n LayerSpec | None

n doping layer.

'N'
layer_np LayerSpec | None

n+ doping layer.

'NP'
layer_npp LayerSpec | None

n++ doping layer.

'NPP'
layer_via LayerSpec | None

via layer.

None
width_via float

via width in um.

1.0
layer_metal LayerSpec | None

metal layer.

None
width_metal float

metal width in um.

1.0
port_names tuple[str, str]

input and output port names.

('o1', 'o2')
sections Sections | None

optional list of sections.

None
cladding_layers LayerSpecs | None

optional list of cladding layers.

None
cladding_offsets Floats | None

optional list of cladding offsets.

None
cladding_simplify Floats | None

Optional Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting polygon by more than the value listed here will be removed.

None
slab_inset float | None

slab inset in um.

None
kwargs Any

cross_section settings.

              offset_low_doping
                <------>
               |       |
              wg     junction
            center   center
               |       |
 ______________|_______|______
 |             |       |     |
{}
Example
import gdsfactory as gf

xs = gf.cross_section.pn(width=0.5, gap_low_doping=0, width_doping=2.)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/pn_junction.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
@xsection
def pn(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    layer_slab: typings.LayerSpec = "SLAB90",
    gap_low_doping: float = 0.0,
    gap_medium_doping: float = 0.5,
    gap_high_doping: float = 1.0,
    offset_low_doping: float = 0.0,
    width_doping: float = 8.0,
    width_slab: float = 7.0,
    layer_p: typings.LayerSpec | None = "P",
    layer_pp: typings.LayerSpec | None = "PP",
    layer_ppp: typings.LayerSpec | None = "PPP",
    layer_n: typings.LayerSpec | None = "N",
    layer_np: typings.LayerSpec | None = "NP",
    layer_npp: typings.LayerSpec | None = "NPP",
    layer_via: typings.LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: typings.LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: tuple[str, str] = ("o1", "o2"),
    sections: Sections | None = None,
    cladding_layers: typings.LayerSpecs | None = None,
    cladding_offsets: typings.Floats | None = None,
    cladding_simplify: typings.Floats | None = None,
    slab_inset: float | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Rib PN doped cross_section.

    Args:
        width: width of the ridge in um.
        layer: ridge layer.
        layer_slab: slab layer.
        gap_low_doping: from waveguide center to low doping. Only used for PIN.
        gap_medium_doping: from waveguide center to medium doping. None removes it.
        gap_high_doping: from center to high doping. None removes it.
        offset_low_doping: from center to junction center.
        width_doping: in um.
        width_slab: in um.
        layer_p: p doping layer.
        layer_pp: p+ doping layer.
        layer_ppp: p++ doping layer.
        layer_n: n doping layer.
        layer_np: n+ doping layer.
        layer_npp: n++ doping layer.
        layer_via: via layer.
        width_via: via width in um.
        layer_metal: metal layer.
        width_metal: metal width in um.
        port_names: input and output port names.
        sections: optional list of sections.
        cladding_layers: optional list of cladding layers.
        cladding_offsets: optional list of cladding offsets.
        cladding_simplify: Optional Tolerance value for the simplification algorithm. \
                All points that can be removed without changing the resulting\
                polygon by more than the value listed here will be removed.
        slab_inset: slab inset in um.
        kwargs: cross_section settings.

                              offset_low_doping
                                <------>
                               |       |
                              wg     junction
                            center   center
                               |       |
                 ______________|_______|______
                 |             |       |     |
        _________|             |       |     |_________________|
              P                |       |               N       |
          width_p              |       |            width_n    |
        <----------------------------->|<--------------------->|
                               |               |       N+      |
                               |               |    width_n    |
                               |               |<------------->|
                               |<------------->|
                               gap_medium_doping

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.pn(width=0.5, gap_low_doping=0, width_doping=2.)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    slab_insets_valid = (slab_inset, slab_inset) if slab_inset else None

    slab = Section(
        width=width_slab, offset=0, layer=layer_slab, insets=slab_insets_valid
    )

    section_list: list[Section] = list(sections or [])
    section_list += [slab]
    base_offset_low_doping = width_doping / 2 + gap_low_doping / 4
    width_low_doping = width_doping - gap_low_doping / 2

    if layer_n:
        n = Section(
            width=width_low_doping + offset_low_doping,
            offset=+base_offset_low_doping - offset_low_doping / 2,
            layer=layer_n,
        )
        section_list.append(n)
    if layer_p:
        p = Section(
            width=width_low_doping - offset_low_doping,
            offset=-base_offset_low_doping - offset_low_doping / 2,
            layer=layer_p,
        )
        section_list.append(p)

    width_medium_doping = width_doping - gap_medium_doping
    offset_medium_doping = width_medium_doping / 2 + gap_medium_doping

    if layer_np is not None:
        np = Section(
            width=width_medium_doping,
            offset=+offset_medium_doping,
            layer=layer_np,
        )
        section_list.append(np)
    if layer_pp is not None:
        pp = Section(
            width=width_medium_doping,
            offset=-offset_medium_doping,
            layer=layer_pp,
        )
        section_list.append(pp)

    width_high_doping = width_doping - gap_high_doping
    offset_high_doping = width_high_doping / 2 + gap_high_doping
    if layer_npp is not None:
        npp = Section(
            width=width_high_doping, offset=+offset_high_doping, layer=layer_npp
        )
        section_list.append(npp)
    if layer_ppp is not None:
        ppp = Section(
            width=width_high_doping, offset=-offset_high_doping, layer=layer_ppp
        )
        section_list.append(ppp)

    if layer_via is not None:
        offset = width_high_doping + gap_high_doping - width_via / 2
        via_top = Section(width=width_via, offset=+offset, layer=layer_via)
        via_bot = Section(width=width_via, offset=-offset, layer=layer_via)
        section_list.append(via_top)
        section_list.append(via_bot)

    if layer_metal is not None:
        offset = width_high_doping + gap_high_doping - width_metal / 2
        port_types = ("electrical", "electrical")
        metal_top = Section(
            width=width_via,
            offset=+offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_top", "e2_top"),
        )
        metal_bot = Section(
            width=width_via,
            offset=-offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_bot", "e2_bot"),
        )
        section_list.append(metal_top)
        section_list.append(metal_bot)

    return cross_section(
        width=width,
        offset=0,
        layer=layer,
        port_names=port_names,
        sections=tuple(section_list),
        cladding_offsets=cladding_offsets,
        cladding_layers=cladding_layers,
        cladding_simplify=cladding_simplify,
        **kwargs,
    )

pn_with_trenches

pn_with_trenches(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    gap_low_doping: float = 0.0,
    gap_medium_doping: float | None = 0.5,
    gap_high_doping: float | None = 1.0,
    offset_low_doping: float = 0.0,
    width_doping: float = 8.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    width_trench: float = 2.0,
    layer_p: LayerSpec | None = "P",
    layer_pp: LayerSpec | None = "PP",
    layer_ppp: LayerSpec | None = "PPP",
    layer_n: LayerSpec | None = "N",
    layer_np: LayerSpec | None = "NP",
    layer_npp: LayerSpec | None = "NPP",
    layer_via: LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: IOPorts = ("o1", "o2"),
    cladding_layers: (
        Layers | None
    ) = cladding_layers_optical,
    cladding_offsets: (
        Floats | None
    ) = cladding_offsets_optical,
    cladding_simplify: (
        Floats | None
    ) = cladding_simplify_optical,
    wg_marking_layer: LayerSpec | None = None,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Rib PN doped cross_section.

Parameters:

Name Type Description Default
width float

width of the ridge in um.

0.5
layer LayerSpec

ridge layer. None adds only ridge.

'WG'
layer_trench LayerSpec

layer to etch trenches.

'DEEP_ETCH'
gap_low_doping float

from waveguide center to low doping. Only used for PIN.

0.0
gap_medium_doping float | None

from waveguide center to medium doping. None removes it.

0.5
gap_high_doping float | None

from center to high doping. None removes it.

1.0
offset_low_doping float

from center to junction center.

0.0
width_doping float

in um.

8.0
slab_offset float | None

from the edge of the trench to the edge of the slab.

0.3
width_slab float | None

in um.

None
width_trench float

in um.

2.0
layer_p LayerSpec | None

p doping layer.

'P'
layer_pp LayerSpec | None

p+ doping layer.

'PP'
layer_ppp LayerSpec | None

p++ doping layer.

'PPP'
layer_n LayerSpec | None

n doping layer.

'N'
layer_np LayerSpec | None

n+ doping layer.

'NP'
layer_npp LayerSpec | None

n++ doping layer.

'NPP'
layer_via LayerSpec | None

via layer.

None
width_via float

via width in um.

1.0
layer_metal LayerSpec | None

metal layer.

None
width_metal float

metal width in um.

1.0
port_names IOPorts

input and output port names.

('o1', 'o2')
cladding_layers Layers | None

optional list of cladding layers.

cladding_layers_optical
cladding_offsets Floats | None

optional list of cladding offsets.

cladding_offsets_optical
cladding_simplify Floats | None

Optional Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting. polygon by more than the value listed here will be removed.

cladding_simplify_optical
wg_marking_layer LayerSpec | None

layer to draw over the actual waveguide.

None
sections Sections | None

optional list of sections.

None
kwargs Any

cross_section settings.

{}
                               offset_low_doping
                                 <------>
                                |       |
                               wg     junction
                             center   center             slab_offset
                                |       |               <------>
    _____         ______________|_______ ______         ________
         |        |             |       |     |         |       |
         |________|             |             |_________|       |
               P                |       |               N       |
           width_p              |                    width_n    |
      <-------------------------------->|<--------------------->|
         <------->              |               |       N+      |
        width_trench            |               |    width_n    |
                                |               |<------------->|
                                |<------------->|
                                gap_medium_doping
   <------------------------------------------------------------>
                            width_slab
Example
import gdsfactory as gf

xs = gf.cross_section.pn_with_trenches(width=0.5, gap_low_doping=0, width_doping=2.)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/pn_junction.py
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
@xsection
def pn_with_trenches(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    layer_trench: typings.LayerSpec = "DEEP_ETCH",
    gap_low_doping: float = 0.0,
    gap_medium_doping: float | None = 0.5,
    gap_high_doping: float | None = 1.0,
    offset_low_doping: float = 0.0,
    width_doping: float = 8.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    width_trench: float = 2.0,
    layer_p: typings.LayerSpec | None = "P",
    layer_pp: typings.LayerSpec | None = "PP",
    layer_ppp: typings.LayerSpec | None = "PPP",
    layer_n: typings.LayerSpec | None = "N",
    layer_np: typings.LayerSpec | None = "NP",
    layer_npp: typings.LayerSpec | None = "NPP",
    layer_via: typings.LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: typings.LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: typings.IOPorts = ("o1", "o2"),
    cladding_layers: typings.Layers | None = cladding_layers_optical,
    cladding_offsets: typings.Floats | None = cladding_offsets_optical,
    cladding_simplify: typings.Floats | None = cladding_simplify_optical,
    wg_marking_layer: typings.LayerSpec | None = None,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Rib PN doped cross_section.

    Args:
        width: width of the ridge in um.
        layer: ridge layer. None adds only ridge.
        layer_trench: layer to etch trenches.
        gap_low_doping: from waveguide center to low doping. Only used for PIN.
        gap_medium_doping: from waveguide center to medium doping. None removes it.
        gap_high_doping: from center to high doping. None removes it.
        offset_low_doping: from center to junction center.
        width_doping: in um.
        slab_offset: from the edge of the trench to the edge of the slab.
        width_slab: in um.
        width_trench: in um.
        layer_p: p doping layer.
        layer_pp: p+ doping layer.
        layer_ppp: p++ doping layer.
        layer_n: n doping layer.
        layer_np: n+ doping layer.
        layer_npp: n++ doping layer.
        layer_via: via layer.
        width_via: via width in um.
        layer_metal: metal layer.
        width_metal: metal width in um.
        port_names: input and output port names.
        cladding_layers: optional list of cladding layers.
        cladding_offsets: optional list of cladding offsets.
        cladding_simplify: Optional Tolerance value for the simplification algorithm.\
                All points that can be removed without changing the resulting. \
                polygon by more than the value listed here will be removed.
        wg_marking_layer: layer to draw over the actual waveguide.
        sections: optional list of sections.
        kwargs: cross_section settings.

    ```text
                                   offset_low_doping
                                     <------>
                                    |       |
                                   wg     junction
                                 center   center             slab_offset
                                    |       |               <------>
        _____         ______________|_______ ______         ________
             |        |             |       |     |         |       |
             |________|             |             |_________|       |
                   P                |       |               N       |
               width_p              |                    width_n    |
          <-------------------------------->|<--------------------->|
             <------->              |               |       N+      |
            width_trench            |               |    width_n    |
                                    |               |<------------->|
                                    |<------------->|
                                    gap_medium_doping
       <------------------------------------------------------------>
                                width_slab
    ```

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.pn_with_trenches(width=0.5, gap_low_doping=0, width_doping=2.)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    if slab_offset is None and width_slab is None:
        raise ValueError("Must specify either slab_offset or width_slab")

    if slab_offset is not None and width_slab is not None:
        raise ValueError("Cannot specify both slab_offset and width_slab")

    if slab_offset is not None:
        width_slab = width + 2 * width_trench + 2 * slab_offset

    trench_offset = width / 2 + width_trench / 2
    section_list: list[Section] = list(sections or [])
    assert width_slab is not None
    section_list += [Section(width=width_slab, layer=layer)]
    section_list += [
        Section(width=width_trench, offset=offset, layer=layer_trench)
        for offset in [+trench_offset, -trench_offset]
    ]

    if wg_marking_layer is not None:
        section_list += [Section(width=width, offset=0, layer=wg_marking_layer)]

    base_offset_low_doping = width_doping / 2 + gap_low_doping / 4
    width_low_doping = width_doping - gap_low_doping / 2

    if layer_n:
        n = Section(
            width=width_low_doping + offset_low_doping,
            offset=+base_offset_low_doping - offset_low_doping / 2,
            layer=layer_n,
        )
        section_list.append(n)
    if layer_p:
        p = Section(
            width=width_low_doping - offset_low_doping,
            offset=-base_offset_low_doping - offset_low_doping / 2,
            layer=layer_p,
        )
        section_list.append(p)

    if gap_medium_doping is not None:
        width_medium_doping = width_doping - gap_medium_doping
        offset_medium_doping = width_medium_doping / 2 + gap_medium_doping

        if layer_np:
            np = Section(
                width=width_medium_doping,
                offset=+offset_medium_doping,
                layer=layer_np,
            )
            section_list.append(np)
        if layer_pp:
            pp = Section(
                width=width_medium_doping,
                offset=-offset_medium_doping,
                layer=layer_pp,
            )
            section_list.append(pp)

    width_high_doping: float | None = None
    if gap_high_doping is not None:
        width_high_doping = width_doping - gap_high_doping
        offset_high_doping = width_high_doping / 2 + gap_high_doping
        if layer_npp:
            npp = Section(
                width=width_high_doping, offset=+offset_high_doping, layer=layer_npp
            )
            section_list.append(npp)
        if layer_ppp:
            ppp = Section(
                width=width_high_doping, offset=-offset_high_doping, layer=layer_ppp
            )
            section_list.append(ppp)

    if (
        layer_via is not None
        and gap_high_doping is not None
        and width_high_doping is not None
    ):
        offset = width_high_doping + gap_high_doping - width_via / 2
        via_top = Section(width=width_via, offset=+offset, layer=layer_via)
        via_bot = Section(width=width_via, offset=-offset, layer=layer_via)
        section_list.append(via_top)
        section_list.append(via_bot)

    if (
        layer_metal is not None
        and width_high_doping is not None
        and gap_high_doping is not None
    ):
        offset = width_high_doping + gap_high_doping - width_metal / 2
        port_types = ("electrical", "electrical")
        metal_top = Section(
            width=width_via,
            offset=+offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_top", "e2_top"),
        )
        metal_bot = Section(
            width=width_via,
            offset=-offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_bot", "e2_bot"),
        )
        section_list.append(metal_top)
        section_list.append(metal_bot)

    return cross_section(
        width=width,
        offset=0,
        layer=layer,
        port_names=port_names,
        sections=tuple(section_list),
        cladding_offsets=cladding_offsets,
        cladding_simplify=cladding_simplify,
        cladding_layers=cladding_layers,
        **kwargs,
    )

pn_with_trenches_asymmetric

pn_with_trenches_asymmetric(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    gap_low_doping: float | tuple[float, float] = (
        0.0,
        0.0,
    ),
    gap_medium_doping: (
        float | tuple[float, float] | None
    ) = (0.5, 0.2),
    gap_high_doping: float | tuple[float, float] | None = (
        1.0,
        0.8,
    ),
    width_doping: float = 8.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    width_trench: float = 2.0,
    layer_p: LayerSpec | None = "P",
    layer_pp: LayerSpec | None = "PP",
    layer_ppp: LayerSpec | None = "PPP",
    layer_n: LayerSpec | None = "N",
    layer_np: LayerSpec | None = "NP",
    layer_npp: LayerSpec | None = "NPP",
    layer_via: LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: tuple[str, str] = ("o1", "o2"),
    cladding_layers: (
        Layers | None
    ) = cladding_layers_optical,
    cladding_offsets: (
        Floats | None
    ) = cladding_offsets_optical,
    wg_marking_layer: LayerSpec | None = None,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Rib PN doped cross_section with asymmetric dimensions left and right.

Parameters:

Name Type Description Default
width float

width of the ridge in um.

0.5
layer LayerSpec

ridge layer. None adds only ridge.

'WG'
layer_trench LayerSpec

layer to etch trenches.

'DEEP_ETCH'
gap_low_doping float | tuple[float, float]

from waveguide center to low doping. Only used for PIN. If a list, it considers the first element is [p_side, n_side]. If a number, it assumes the same for both sides.

(0.0, 0.0)
gap_medium_doping float | tuple[float, float] | None

from waveguide center to medium doping. None removes it. If a list, it considers the first element is [p_side, n_side]. If a number, it assumes the same for both sides.

(0.5, 0.2)
gap_high_doping float | tuple[float, float] | None

from center to high doping. None removes it. If a list, it considers the first element is [p_side, n_side]. If a number, it assumes the same for both sides.

(1.0, 0.8)
width_doping float

in um.

8.0
slab_offset float | None

from the edge of the trench to the edge of the slab.

0.3
width_slab float | None

in um.

None
width_trench float

in um.

2.0
layer_p LayerSpec | None

p doping layer.

'P'
layer_pp LayerSpec | None

p+ doping layer.

'PP'
layer_ppp LayerSpec | None

p++ doping layer.

'PPP'
layer_n LayerSpec | None

n doping layer.

'N'
layer_np LayerSpec | None

n+ doping layer.

'NP'
layer_npp LayerSpec | None

n++ doping layer.

'NPP'
layer_via LayerSpec | None

via layer.

None
width_via float

via width in um.

1.0
layer_metal LayerSpec | None

metal layer.

None
width_metal float

metal width in um.

1.0
port_names tuple[str, str]

input and output port names.

('o1', 'o2')
cladding_layers Layers | None

optional list of cladding layers.

cladding_layers_optical
cladding_offsets Floats | None

optional list of cladding offsets.

cladding_offsets_optical
wg_marking_layer LayerSpec | None

layer to draw over the actual waveguide.

None
sections Sections | None

optional list of sections.

None
kwargs Any

cross_section settings.

{}
                               gap_low_doping[1]
                                 <------>
                                |       |
                               wg     junction
                             center   center           slab_offset
                                |       |               <------>
    _____         ______________|_______ ______         ________
         |        |             |       |     |         |       |
         |________|             |             |_________|       |
               P                |       |               N       |
           width_p              |                    width_n    |
      <-------------------------------->|<--------------------->|
         <------->              |               |       N+      |
        width_trench            |               |    width_n    |
                                |               |<------------->|
                                |<------------->|
                                gap_medium_doping[1]
   <------------------------------------------------------------>
                            width_slab
Example
import gdsfactory as gf

xs = gf.cross_section.pn_with_trenches_assymmetric(width=0.5, gap_low_doping=0, width_doping=2.)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/pn_junction.py
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
@xsection
def pn_with_trenches_asymmetric(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    layer_trench: typings.LayerSpec = "DEEP_ETCH",
    gap_low_doping: float | tuple[float, float] = (0.0, 0.0),
    gap_medium_doping: float | tuple[float, float] | None = (0.5, 0.2),
    gap_high_doping: float | tuple[float, float] | None = (1.0, 0.8),
    width_doping: float = 8.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    width_trench: float = 2.0,
    layer_p: typings.LayerSpec | None = "P",
    layer_pp: typings.LayerSpec | None = "PP",
    layer_ppp: typings.LayerSpec | None = "PPP",
    layer_n: typings.LayerSpec | None = "N",
    layer_np: typings.LayerSpec | None = "NP",
    layer_npp: typings.LayerSpec | None = "NPP",
    layer_via: typings.LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: typings.LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: tuple[str, str] = ("o1", "o2"),
    cladding_layers: typings.Layers | None = cladding_layers_optical,
    cladding_offsets: typings.Floats | None = cladding_offsets_optical,
    wg_marking_layer: typings.LayerSpec | None = None,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Rib PN doped cross_section with asymmetric dimensions left and right.

    Args:
        width: width of the ridge in um.
        layer: ridge layer. None adds only ridge.
        layer_trench: layer to etch trenches.
        gap_low_doping: from waveguide center to low doping. Only used for PIN. \
                If a list, it considers the first element is [p_side, n_side]. If a number, \
                it assumes the same for both sides.
        gap_medium_doping: from waveguide center to medium doping. None removes it. \
                If a list, it considers the first element is [p_side, n_side]. \
                If a number, it assumes the same for both sides.
        gap_high_doping: from center to high doping. None removes it. \
                If a list, it considers the first element is [p_side, n_side].\
                If a number, it assumes the same for both sides.
        width_doping: in um.
        slab_offset: from the edge of the trench to the edge of the slab.
        width_slab: in um.
        width_trench: in um.
        layer_p: p doping layer.
        layer_pp: p+ doping layer.
        layer_ppp: p++ doping layer.
        layer_n: n doping layer.
        layer_np: n+ doping layer.
        layer_npp: n++ doping layer.
        layer_via: via layer.
        width_via: via width in um.
        layer_metal: metal layer.
        width_metal: metal width in um.
        port_names: input and output port names.
        cladding_layers: optional list of cladding layers.
        cladding_offsets: optional list of cladding offsets.
        wg_marking_layer: layer to draw over the actual waveguide.
        sections: optional list of sections.
        kwargs: cross_section settings.

    ```text
                                   gap_low_doping[1]
                                     <------>
                                    |       |
                                   wg     junction
                                 center   center           slab_offset
                                    |       |               <------>
        _____         ______________|_______ ______         ________
             |        |             |       |     |         |       |
             |________|             |             |_________|       |
                   P                |       |               N       |
               width_p              |                    width_n    |
          <-------------------------------->|<--------------------->|
             <------->              |               |       N+      |
            width_trench            |               |    width_n    |
                                    |               |<------------->|
                                    |<------------->|
                                    gap_medium_doping[1]
       <------------------------------------------------------------>
                                width_slab
    ```

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.pn_with_trenches_assymmetric(width=0.5, gap_low_doping=0, width_doping=2.)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    if slab_offset is None and width_slab is None:
        raise ValueError("Must specify either slab_offset or width_slab")

    if slab_offset is not None and width_slab is not None:
        raise ValueError("Cannot specify both slab_offset and width_slab")

    if slab_offset is not None:
        width_slab = width + 2 * width_trench + 2 * slab_offset

    # Trenches
    trench_offset = width / 2 + width_trench / 2
    section_list: list[Section] = list(sections or [])
    assert width_slab is not None
    section_list += [Section(width=width_slab, layer=layer)]
    section_list += [
        Section(width=width_trench, offset=offset, layer=layer_trench)
        for offset in [+trench_offset, -trench_offset]
    ]

    if wg_marking_layer is not None:
        section_list += [Section(width=width, offset=0, layer=wg_marking_layer)]

    # Low doping

    if not isinstance(gap_low_doping, list | tuple):
        gap_low_doping_list = [gap_low_doping] * 2
    else:
        gap_low_doping_list = list(gap_low_doping)

    if layer_n:
        width_low_doping_n = width_doping - gap_low_doping_list[1]
        n = Section(
            width=width_low_doping_n,
            offset=width_low_doping_n / 2 + gap_low_doping_list[1],
            layer=layer_n,
        )
        section_list.append(n)
    if layer_p:
        width_low_doping_p = width_doping - gap_low_doping_list[0]
        p = Section(
            width=width_low_doping_p,
            offset=-(width_low_doping_p / 2 + gap_low_doping_list[0]),
            layer=layer_p,
        )
        section_list.append(p)

    if gap_medium_doping is not None:
        if not isinstance(gap_medium_doping, list | tuple):
            gap_medium_doping_list = [gap_medium_doping] * 2
        else:
            gap_medium_doping_list = list(gap_medium_doping)

        if layer_np:
            width_np = width_doping - gap_medium_doping_list[1]
            np = Section(
                width=width_np,
                offset=width_np / 2 + gap_medium_doping_list[1],
                layer=layer_np,
            )
            section_list.append(np)
        if layer_pp:
            width_pp = width_doping - gap_medium_doping_list[0]
            pp = Section(
                width=width_pp,
                offset=-(width_pp / 2 + gap_medium_doping_list[0]),
                layer=layer_pp,
            )
            section_list.append(pp)
    gap_high_doping_list: list[float] | None = None
    width_npp: float | None = None
    width_ppp: float | None = None
    if gap_high_doping is not None:
        if not isinstance(gap_high_doping, list | tuple):
            gap_high_doping_list = [float(gap_high_doping)] * 2
        else:
            gap_high_doping_list = list(gap_high_doping)

        if layer_npp:
            width_npp = width_doping - gap_high_doping_list[1]
            npp = Section(
                width=width_npp,
                offset=width_npp / 2 + gap_high_doping_list[1],
                layer=layer_npp,
            )
            section_list.append(npp)
        if layer_ppp:
            width_ppp = width_doping - gap_high_doping_list[0]
            ppp = Section(
                width=width_ppp,
                offset=-(width_ppp / 2 + gap_high_doping_list[0]),
                layer=layer_ppp,
            )
            section_list.append(ppp)

    if (
        layer_via is not None
        and gap_high_doping_list is not None
        and width_npp is not None
        and width_ppp is not None
    ):
        offset_top = width_npp + gap_high_doping_list[1] - width_via / 2
        offset_bot = width_ppp + gap_high_doping_list[0] - width_via / 2
        via_top = Section(width=width_via, offset=+offset_top, layer=layer_via)
        via_bot = Section(width=width_via, offset=-offset_bot, layer=layer_via)
        section_list.append(via_top)
        section_list.append(via_bot)

    if (
        layer_metal is not None
        and gap_high_doping_list is not None
        and width_npp is not None
        and width_ppp is not None
    ):
        offset_top = width_npp + gap_high_doping_list[1] - width_metal / 2
        offset_bot = width_ppp + gap_high_doping_list[0] - width_metal / 2
        port_types = ("electrical", "electrical")
        metal_top = Section(
            width=width_via,
            offset=offset_top,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_top", "e2_top"),
        )
        metal_bot = Section(
            width=width_via,
            offset=-offset_bot,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_bot", "e2_bot"),
        )
        section_list.append(metal_top)
        section_list.append(metal_bot)

    return cross_section(
        width=width,
        offset=0,
        layer=layer,
        port_names=port_names,
        sections=tuple(section_list),
        cladding_offsets=cladding_offsets,
        cladding_layers=cladding_layers,
        **kwargs,
    )

l_wg_doped_with_trenches

l_wg_doped_with_trenches(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    gap_low_doping: float = 0.0,
    gap_medium_doping: float | None = 0.5,
    gap_high_doping: float | None = 1.0,
    width_doping: float = 8.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    width_trench: float = 2.0,
    layer_low: LayerSpec = "P",
    layer_mid: LayerSpec = "PP",
    layer_high: LayerSpec = "PPP",
    layer_via: LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: tuple[str, str] = ("o1", "o2"),
    cladding_layers: (
        Layers | None
    ) = cladding_layers_optical,
    cladding_offsets: (
        Floats | None
    ) = cladding_offsets_optical,
    wg_marking_layer: LayerSpec | None = None,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

L waveguide PN doped cross_section.

Parameters:

Name Type Description Default
width float

width of the ridge in um.

0.5
layer LayerSpec

ridge layer. None adds only ridge.

'WG'
layer_trench LayerSpec

layer to etch trenches.

'DEEP_ETCH'
gap_low_doping float

from waveguide outer edge to low doping. Only used for PIN.

0.0
gap_medium_doping float | None

from waveguide edge to medium doping. None removes it.

0.5
gap_high_doping float | None

from edge to high doping. None removes it.

1.0
width_doping float

in um.

8.0
slab_offset float | None

from the edge of the trench to the edge of the slab.

0.3
width_slab float | None

in um.

None
width_trench float

in um.

2.0
layer_low LayerSpec

low doping layer.

'P'
layer_mid LayerSpec

mid doping layer.

'PP'
layer_high LayerSpec

high doping layer.

'PPP'
layer_via LayerSpec | None

via layer.

None
width_via float

via width in um.

1.0
layer_metal LayerSpec | None

metal layer.

None
width_metal float

metal width in um.

1.0
port_names tuple[str, str]

input and output port names.

('o1', 'o2')
cladding_layers Layers | None

optional list of cladding layers.

cladding_layers_optical
cladding_offsets Floats | None

optional list of cladding offsets.

cladding_offsets_optical
wg_marking_layer LayerSpec | None

layer to mark where the actual guiding section is.

None
sections Sections | None

optional list of sections.

None
kwargs Any

cross_section settings.

{}
                                      gap_low_doping
                                       <------>
                                              |
                                              wg
                                             edge
                                              |
    _____                       _______ ______
         |                     |              |
         |_____________________|              |
                                              |
                                              |
                                <------------>
                                       width
         <--------------------->               |
        width_trench       |                   |
                           |                   |
                           |<----------------->|
                              gap_medium_doping
                 |<--------------------------->|
                         gap_high_doping
   <------------------------------------------->
                    width_slab
Example
import gdsfactory as gf

xs = gf.cross_section.pn_with_trenches(width=0.5, gap_low_doping=0, width_doping=2.)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/pn_junction.py
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
@xsection
def l_wg_doped_with_trenches(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    layer_trench: typings.LayerSpec = "DEEP_ETCH",
    gap_low_doping: float = 0.0,
    gap_medium_doping: float | None = 0.5,
    gap_high_doping: float | None = 1.0,
    width_doping: float = 8.0,
    slab_offset: float | None = 0.3,
    width_slab: float | None = None,
    width_trench: float = 2.0,
    layer_low: typings.LayerSpec = "P",
    layer_mid: typings.LayerSpec = "PP",
    layer_high: typings.LayerSpec = "PPP",
    layer_via: typings.LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: typings.LayerSpec | None = None,
    width_metal: float = 1.0,
    port_names: tuple[str, str] = ("o1", "o2"),
    cladding_layers: typings.Layers | None = cladding_layers_optical,
    cladding_offsets: typings.Floats | None = cladding_offsets_optical,
    wg_marking_layer: typings.LayerSpec | None = None,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """L waveguide PN doped cross_section.

    Args:
        width: width of the ridge in um.
        layer: ridge layer. None adds only ridge.
        layer_trench: layer to etch trenches.
        gap_low_doping: from waveguide outer edge to low doping. Only used for PIN.
        gap_medium_doping: from waveguide edge to medium doping. None removes it.
        gap_high_doping: from edge to high doping. None removes it.
        width_doping: in um.
        slab_offset: from the edge of the trench to the edge of the slab.
        width_slab: in um.
        width_trench: in um.
        layer_low: low doping layer.
        layer_mid: mid doping layer.
        layer_high: high doping layer.
        layer_via: via layer.
        width_via: via width in um.
        layer_metal: metal layer.
        width_metal: metal width in um.
        port_names: input and output port names.
        cladding_layers: optional list of cladding layers.
        cladding_offsets: optional list of cladding offsets.
        wg_marking_layer: layer to mark where the actual guiding section is.
        sections: optional list of sections.
        kwargs: cross_section settings.

    ```text
                                          gap_low_doping
                                           <------>
                                                  |
                                                  wg
                                                 edge
                                                  |
        _____                       _______ ______
             |                     |              |
             |_____________________|              |
                                                  |
                                                  |
                                    <------------>
                                           width
             <--------------------->               |
            width_trench       |                   |
                               |                   |
                               |<----------------->|
                                  gap_medium_doping
                     |<--------------------------->|
                             gap_high_doping
       <------------------------------------------->
                        width_slab
    ```

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.pn_with_trenches(width=0.5, gap_low_doping=0, width_doping=2.)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    if slab_offset is None and width_slab is None:
        raise ValueError("Must specify either slab_offset or width_slab")

    if slab_offset is not None and width_slab is not None:
        raise ValueError("Cannot specify both slab_offset and width_slab")

    if slab_offset is not None:
        width_slab = width + 2 * width_trench + 2 * slab_offset

    trench_offset = -1 * (width / 2 + width_trench / 2)
    section_list: list[Section] = list(sections or [])
    assert width_slab is not None
    section_list.append(
        Section(width=width_slab, layer=layer, offset=-1 * (width_slab / 2 - width / 2))
    )
    section_list += [
        Section(width=width_trench, offset=trench_offset, layer=layer_trench)
    ]

    if wg_marking_layer is not None:
        section_list += [Section(width=width, offset=0, layer=wg_marking_layer)]

    offset_low_doping = width / 2 - gap_low_doping - width_doping / 2

    low_doping = Section(
        width=width_doping,
        offset=offset_low_doping,
        layer=layer_low,
    )

    section_list.append(low_doping)

    if gap_medium_doping is not None:
        width_medium_doping = width_doping - gap_medium_doping
        offset_medium_doping = width / 2 - gap_medium_doping - width_medium_doping / 2

        mid_doping = Section(
            width=width_medium_doping,
            offset=offset_medium_doping,
            layer=layer_mid,
        )
        section_list.append(mid_doping)

    offset_high_doping: float | None = None
    width_high_doping: float | None = None

    if gap_high_doping is not None:
        width_high_doping = width_doping - gap_high_doping
        offset_high_doping = width / 2 - gap_high_doping - width_high_doping / 2

        high_doping = Section(
            width=width_high_doping, offset=+offset_high_doping, layer=layer_high
        )

        section_list.append(high_doping)

    if (
        layer_via is not None
        and offset_high_doping is not None
        and width_high_doping is not None
    ):
        offset = offset_high_doping - width_high_doping / 2 + width_via / 2
        via = Section(width=width_via, offset=+offset, layer=layer_via)
        section_list.append(via)

    if (
        layer_metal is not None
        and offset_high_doping is not None
        and width_high_doping is not None
    ):
        offset = offset_high_doping - width_high_doping / 2 + width_metal / 2
        port_types = ("electrical", "electrical")
        metal = Section(
            width=width_via,
            offset=+offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_top", "e2_top"),
        )
        section_list.append(metal)

    return cross_section(
        width=width,
        offset=0,
        layer=layer,
        port_names=port_names,
        sections=tuple(section_list),
        cladding_offsets=cladding_offsets,
        cladding_layers=cladding_layers,
        **kwargs,
    )

strip_heater_metal_undercut

strip_heater_metal_undercut(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    heater_width: float = 2.5,
    trench_width: float = 6.5,
    trench_gap: float = 2.0,
    layer_heater: LayerSpec = "HEATER",
    layer_trench: LayerSpec = "DEEPTRENCH",
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Returns strip cross_section with top metal and undercut trenches on both.

sides.

dimensions from https://doi.org/10.1364/OE.18.020298

Parameters:

Name Type Description Default
width float

waveguide width.

0.5
layer LayerSpec

waveguide layer.

'WG'
heater_width float

of metal heater.

2.5
trench_width float

in um.

6.5
trench_gap float

from waveguide edge to trench edge.

2.0
layer_heater LayerSpec

heater layer.

'HEATER'
layer_trench LayerSpec

tench layer.

'DEEPTRENCH'
sections Sections | None

cross_section sections.

None
kwargs Any

cross_section settings.

|<-------heater_width--------->|


| | | layer_heater | |______|

   |<------width------>|
    ____________________ trench_gap
   |                   |<----------->|              |
   |                   |             |   undercut   |
   |       width       |             |              |
   |                   |             |<------------>|
   |___________________|             | trench_width |
                                     |              |
                                     |              |
{}
Example
import gdsfactory as gf

xs = gf.cross_section.strip_heater_metal_undercut(width=0.5, heater_width=2, trench_width=4, trench_gap=4)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/heater.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@xsection
def strip_heater_metal_undercut(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    heater_width: float = 2.5,
    trench_width: float = 6.5,
    trench_gap: float = 2.0,
    layer_heater: typings.LayerSpec = "HEATER",
    layer_trench: typings.LayerSpec = "DEEPTRENCH",
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Returns strip cross_section with top metal and undercut trenches on both.

    sides.

    dimensions from https://doi.org/10.1364/OE.18.020298

    Args:
        width: waveguide width.
        layer: waveguide layer.
        heater_width: of metal heater.
        trench_width: in um.
        trench_gap: from waveguide edge to trench edge.
        layer_heater: heater layer.
        layer_trench: tench layer.
        sections: cross_section sections.
        kwargs: cross_section settings.

              |<-------heater_width--------->|
               ______________________________
              |                              |
              |         layer_heater         |
              |______________________________|

                   |<------width------>|
                    ____________________ trench_gap
                   |                   |<----------->|              |
                   |                   |             |   undercut   |
                   |       width       |             |              |
                   |                   |             |<------------>|
                   |___________________|             | trench_width |
                                                     |              |
                                                     |              |

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.strip_heater_metal_undercut(width=0.5, heater_width=2, trench_width=4, trench_gap=4)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    trench_offset = trench_gap + trench_width / 2 + width / 2
    section_list: list[Section] = list(sections or [])
    section_list += [
        Section(
            layer=layer_heater,
            width=heater_width,
            port_names=port_names_electrical,
            port_types=port_types_electrical,
        ),
        Section(layer=layer_trench, width=trench_width, offset=+trench_offset),
        Section(layer=layer_trench, width=trench_width, offset=-trench_offset),
    ]

    return strip(
        width=width,
        layer=layer,
        sections=tuple(section_list),
        **kwargs,
    )

strip_heater_metal

strip_heater_metal(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    heater_width: float = 2.5,
    layer_heater: LayerSpec = "HEATER",
    sections: Sections | None = None,
    insets: tuple[float, float] | None = None,
    **kwargs: Any
) -> CrossSection

Returns strip cross_section with top heater metal.

dimensions from https://doi.org/10.1364/OE.18.020298

Parameters:

Name Type Description Default
width float

waveguide width (um).

0.5
layer LayerSpec

waveguide layer.

'WG'
heater_width float

of metal heater.

2.5
layer_heater LayerSpec

for the metal.

'HEATER'
sections Sections | None

cross_section sections.

None
insets tuple[float, float] | None

for the heater.

None
kwargs Any

cross_section settings.

{}
Example
import gdsfactory as gf

xs = gf.cross_section.strip_heater_metal(width=0.5, heater_width=2)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/heater.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
@xsection
def strip_heater_metal(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    heater_width: float = 2.5,
    layer_heater: typings.LayerSpec = "HEATER",
    sections: Sections | None = None,
    insets: tuple[float, float] | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Returns strip cross_section with top heater metal.

    dimensions from https://doi.org/10.1364/OE.18.020298

    Args:
        width: waveguide width (um).
        layer: waveguide layer.
        heater_width: of metal heater.
        layer_heater: for the metal.
        sections: cross_section sections.
        insets: for the heater.
        kwargs: cross_section settings.

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.strip_heater_metal(width=0.5, heater_width=2)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    section_list: list[Section] = list(sections or [])
    section_list += [
        Section(
            layer=layer_heater,
            width=heater_width,
            port_names=port_names_electrical,
            port_types=port_types_electrical,
            insets=insets,
        )
    ]

    return strip(
        width=width,
        layer=layer,
        sections=tuple(section_list),
        **kwargs,
    )

strip_heater_doped

strip_heater_doped(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    layers_heater: LayerSpecs = ("WG", "NPP"),
    bbox_offsets_heater: tuple[float, ...] = (0, 0.1),
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Returns strip cross_section with N++ doped heaters on both sides.

Parameters:

Name Type Description Default
width float

in um.

0.5
layer LayerSpec

waveguide spec.

'WG'
heater_width float

in um.

2.0
heater_gap float

in um.

0.8
layers_heater LayerSpecs

for doped heater.

('WG', 'NPP')
bbox_offsets_heater tuple[float, ...]

for each layers_heater.

(0, 0.1)
sections Sections | None

cross_section sections.

None
kwargs Any

cross_section settings.

                  |<------width------>|

__ __ __ | | | undoped Si | | | |layerheater| | intrinsic region |<----------->| layer_heater | |_| |____| |__| <------------> heater_gap heater_width

{}
Example
import gdsfactory as gf

xs = gf.cross_section.strip_heater_doped(width=0.5, heater_width=2, heater_gap=0.5)
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/heater.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
@xsection
def strip_heater_doped(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    layers_heater: typings.LayerSpecs = ("WG", "NPP"),
    bbox_offsets_heater: tuple[float, ...] = (0, 0.1),
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Returns strip cross_section with N++ doped heaters on both sides.

    Args:
        width: in um.
        layer: waveguide spec.
        heater_width: in um.
        heater_gap: in um.
        layers_heater: for doped heater.
        bbox_offsets_heater: for each layers_heater.
        sections: cross_section sections.
        kwargs: cross_section settings.

                                  |<------width------>|
          ____________             ___________________               ______________
         |            |           |     undoped Si    |             |              |
         |layer_heater|           |  intrinsic region |<----------->| layer_heater |
         |____________|           |___________________|             |______________|
                                                                     <------------>
                                                        heater_gap     heater_width

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.strip_heater_doped(width=0.5, heater_width=2, heater_gap=0.5)
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    heater_offset = width / 2 + heater_gap + heater_width / 2

    section_list: list[Section] = list(sections or [])
    section_list += [
        Section(
            layer=layer,
            width=heater_width + 2 * cladding_offset,
            offset=+heater_offset,
            name=f"heater_upper_{layer}",
        )
        for layer, cladding_offset in zip(
            layers_heater, bbox_offsets_heater, strict=False
        )
    ]

    section_list += [
        Section(
            layer=layer,
            width=heater_width + 2 * cladding_offset,
            offset=-heater_offset,
            name=f"heater_lower_{layer}",
        )
        for layer, cladding_offset in zip(
            layers_heater, bbox_offsets_heater, strict=False
        )
    ]

    return strip(
        width=width,
        layer=layer,
        sections=tuple(section_list),
        **kwargs,
    )

rib_heater_doped

rib_heater_doped(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    layer_heater: LayerSpec = "NPP",
    layer_slab: LayerSpec = "SLAB90",
    slab_gap: float = 0.2,
    with_top_heater: bool = True,
    with_bot_heater: bool = True,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Returns rib cross_section with N++ doped heaters on both sides.

dimensions from https://doi.org/10.1364/OE.27.010456

                            |<------width------>|
                             ____________________  heater_gap           slab_gap
                            |                   |<----------->|             <-->
 ___ _______________________|                   |__________________________|___
|   |            |                undoped Si                  |            |   |
|   |layer_heater|                intrinsic region            |layer_heater|   |
|___|____________|____________________________________________|____________|___|
                                                               <---------->
                                                                heater_width
<------------------------------------------------------------------------------>
                                slab_width
Example
import gdsfactory as gf

xs = gf.cross_section.rib_heater_doped(width=0.5, heater_width=2, heater_gap=0.5, layer_heater='NPP')
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/heater.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
@xsection
def rib_heater_doped(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    layer_heater: typings.LayerSpec = "NPP",
    layer_slab: typings.LayerSpec = "SLAB90",
    slab_gap: float = 0.2,
    with_top_heater: bool = True,
    with_bot_heater: bool = True,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Returns rib cross_section with N++ doped heaters on both sides.

    dimensions from https://doi.org/10.1364/OE.27.010456

                                    |<------width------>|
                                     ____________________  heater_gap           slab_gap
                                    |                   |<----------->|             <-->
         ___ _______________________|                   |__________________________|___
        |   |            |                undoped Si                  |            |   |
        |   |layer_heater|                intrinsic region            |layer_heater|   |
        |___|____________|____________________________________________|____________|___|
                                                                       <---------->
                                                                        heater_width
        <------------------------------------------------------------------------------>
                                        slab_width

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.rib_heater_doped(width=0.5, heater_width=2, heater_gap=0.5, layer_heater='NPP')
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    heater_offset = width / 2 + heater_gap + heater_width / 2

    if with_bot_heater and with_top_heater:
        slab_width = width + 2 * heater_gap + 2 * heater_width + 2 * slab_gap
        slab_offset = 0.0
    elif with_top_heater:
        slab_width = width + heater_gap + heater_width + slab_gap
        slab_offset = -slab_width / 2
    elif with_bot_heater:
        slab_width = width + heater_gap + heater_width + slab_gap
        slab_offset = +slab_width / 2
    else:
        raise ValueError("At least one heater must be True")

    section_list: list[Section] = list(sections or [])
    section_list += [
        Section(width=slab_width, layer=layer_slab, offset=slab_offset, name="slab")
    ]

    if with_bot_heater:
        section_list += [
            Section(
                layer=layer_heater,
                width=heater_width,
                offset=+heater_offset,
                name="heater_upper",
            )
        ]
    if with_top_heater:
        section_list += [
            Section(
                layer=layer_heater,
                width=heater_width,
                offset=-heater_offset,
                name="heater_lower",
            )
        ]
    return strip(
        width=width,
        layer=layer,
        sections=tuple(section_list),
        **kwargs,
    )

rib_heater_doped_via_stack

rib_heater_doped_via_stack(
    width: float = 0.5,
    layer: LayerSpec = "WG",
    heater_width: float = 1.0,
    heater_gap: float = 0.8,
    layer_slab: LayerSpec = "SLAB90",
    layer_heater: LayerSpec = "NPP",
    via_stack_width: float = 2.0,
    via_stack_gap: float = 0.8,
    layers_via_stack: LayerSpecs = ("NPP", "VIAC"),
    bbox_offsets_via_stack: tuple[float, ...] = (0, -0.2),
    slab_gap: float = 0.2,
    slab_offset: float = 0,
    with_top_heater: bool = True,
    with_bot_heater: bool = True,
    sections: Sections | None = None,
    **kwargs: Any
) -> CrossSection

Returns rib cross_section with N++ doped heaters on both sides.

dimensions from https://doi.org/10.1364/OE.27.010456

Parameters:

Name Type Description Default
width float

in um.

0.5
layer LayerSpec

for main waveguide section.

'WG'
heater_width float

in um.

1.0
heater_gap float

in um.

0.8
layer_slab LayerSpec

for pedestal.

'SLAB90'
layer_heater LayerSpec

for doped heater.

'NPP'
via_stack_width float

for the contact.

2.0
via_stack_gap float

in um.

0.8
layers_via_stack LayerSpecs

for the contact.

('NPP', 'VIAC')
bbox_offsets_via_stack tuple[float, ...]

for the contact.

(0, -0.2)
slab_gap float

from heater edge.

0.2
slab_offset float

over the center of the slab.

0
with_top_heater bool

adds top/left heater.

True
with_bot_heater bool

adds bottom/right heater.

True
sections Sections | None

list of sections to add to the cross_section.

None
kwargs Any

cross_section settings.

{}
                               |<----width------>|
   slab_gap                     __________________ via_stack_gap     via_stack width
   <-->                        |                 |<------------>|<--------------->
                               |                 | heater_gap |
                               |                 |<---------->|
    ___ _______________________|                 |___________________________ ____
   |   |            |              undoped Si                 |              |    |
   |   |layer_heater|              intrinsic region           |layer_heater  |    |
   |___|____________|_________________________________________|______________|____|
                                                               <------------>
                                                                heater_width
   <------------------------------------------------------------------------------>
                                   slab_width
Example
import gdsfactory as gf

xs = gf.cross_section.rib_heater_doped_via_stack(width=0.5, heater_width=2, heater_gap=0.5, layer_heater='NPP')
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/heater.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
@xsection
def rib_heater_doped_via_stack(
    width: float = 0.5,
    layer: typings.LayerSpec = "WG",
    heater_width: float = 1.0,
    heater_gap: float = 0.8,
    layer_slab: typings.LayerSpec = "SLAB90",
    layer_heater: typings.LayerSpec = "NPP",
    via_stack_width: float = 2.0,
    via_stack_gap: float = 0.8,
    layers_via_stack: typings.LayerSpecs = ("NPP", "VIAC"),
    bbox_offsets_via_stack: tuple[float, ...] = (0, -0.2),
    slab_gap: float = 0.2,
    slab_offset: float = 0,
    with_top_heater: bool = True,
    with_bot_heater: bool = True,
    sections: Sections | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Returns rib cross_section with N++ doped heaters on both sides.

    dimensions from https://doi.org/10.1364/OE.27.010456

    Args:
        width: in um.
        layer: for main waveguide section.
        heater_width: in um.
        heater_gap: in um.
        layer_slab: for pedestal.
        layer_heater: for doped heater.
        via_stack_width: for the contact.
        via_stack_gap: in um.
        layers_via_stack: for the contact.
        bbox_offsets_via_stack: for the contact.
        slab_gap: from heater edge.
        slab_offset: over the center of the slab.
        with_top_heater: adds top/left heater.
        with_bot_heater: adds bottom/right heater.
        sections: list of sections to add to the cross_section.
        kwargs: cross_section settings.

    ```text
                                   |<----width------>|
       slab_gap                     __________________ via_stack_gap     via_stack width
       <-->                        |                 |<------------>|<--------------->
                                   |                 | heater_gap |
                                   |                 |<---------->|
        ___ _______________________|                 |___________________________ ____
       |   |            |              undoped Si                 |              |    |
       |   |layer_heater|              intrinsic region           |layer_heater  |    |
       |___|____________|_________________________________________|______________|____|
                                                                   <------------>
                                                                    heater_width
       <------------------------------------------------------------------------------>
                                       slab_width
    ```

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.rib_heater_doped_via_stack(width=0.5, heater_width=2, heater_gap=0.5, layer_heater='NPP')
        p = gf.path.arc(radius=10, angle=45)
        c = p.extrude(xs)
        c.plot()
        ```
    """
    if with_bot_heater and with_top_heater:
        slab_width = width + 2 * heater_gap + 2 * heater_width + 2 * slab_gap
    elif with_top_heater:
        slab_width = width + heater_gap + heater_width + slab_gap
        slab_offset -= slab_width / 2
    elif with_bot_heater:
        slab_width = width + heater_gap + heater_width + slab_gap
        slab_offset += slab_width / 2
    else:
        raise ValueError("At least one heater must be True")

    heater_offset = width / 2 + heater_gap + heater_width / 2
    via_stack_offset = width / 2 + via_stack_gap + via_stack_width / 2
    section_list: list[Section] = list(sections or [])
    section_list += [
        Section(width=slab_width, layer=layer_slab, offset=slab_offset, name="slab"),
    ]
    if with_bot_heater:
        section_list += [
            Section(
                layer=layer_heater,
                width=heater_width,
                offset=+heater_offset,
            )
        ]

    if with_top_heater:
        section_list += [
            Section(
                layer=layer_heater,
                width=heater_width,
                offset=-heater_offset,
            )
        ]

    if with_bot_heater:
        section_list += [
            Section(
                layer=layer,
                width=heater_width + 2 * cladding_offset,
                offset=+via_stack_offset,
            )
            for layer, cladding_offset in zip(
                layers_via_stack, bbox_offsets_via_stack, strict=False
            )
        ]

    if with_top_heater:
        section_list += [
            Section(
                layer=layer,
                width=heater_width + 2 * cladding_offset,
                offset=-via_stack_offset,
            )
            for layer, cladding_offset in zip(
                layers_via_stack, bbox_offsets_via_stack, strict=False
            )
        ]

    return strip(
        sections=tuple(section_list),
        width=width,
        layer=layer,
        **kwargs,
    )

pn_ge_detector_si_contacts

pn_ge_detector_si_contacts(
    width_si: float = 6.0,
    layer_si: LayerSpec = "WG",
    width_ge: float = 3.0,
    layer_ge: LayerSpec = "GE",
    gap_low_doping: float = 0.6,
    gap_medium_doping: float = 0.9,
    gap_high_doping: float = 1.1,
    width_doping: float = 8.0,
    layer_p: LayerSpec = "P",
    layer_pp: LayerSpec = "PP",
    layer_ppp: LayerSpec = "PPP",
    layer_n: LayerSpec = "N",
    layer_np: LayerSpec = "NP",
    layer_npp: LayerSpec = "NPP",
    layer_via: LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: LayerSpec | None = None,
    port_names: tuple[str, str] = ("o1", "o2"),
    cladding_layers: (
        Layers | None
    ) = cladding_layers_optical,
    cladding_offsets: (
        Floats | None
    ) = cladding_offsets_optical,
    cladding_simplify: Floats | None = None,
    **kwargs: Any
) -> CrossSection

Linear Ge detector cross section based on a lateral p(i)n junction.

It has silicon contacts (no contact on the Ge). The contacts need to be created in the component generating function (they can't be created here).

See Chen et al., "High-Responsivity Low-Voltage 28-Gb/s Ge p-i-n Photodetector With Silicon Contacts", Journal of Lightwave Technology 33(4), 2015.

Notice it is possible to have dopings going beyond the ridge waveguide. This is fine, and it is to account for the presence of the contacts. Such contacts can be subwavelength or not.

Parameters:

Name Type Description Default
width_si float

width of the full etch si in um.

6.0
layer_si LayerSpec

si ridge layer.

'WG'
width_ge float

width of the ge in um.

3.0
layer_ge LayerSpec

ge layer.

'GE'
gap_low_doping float

from waveguide center to low doping.

0.6
gap_medium_doping float

from waveguide center to medium doping. None removes it.

0.9
gap_high_doping float

from center to high doping. None removes it.

1.1
width_doping float

distance from waveguide center to doping edge in um.

8.0
layer_p LayerSpec

p doping layer.

'P'
layer_pp LayerSpec

p+ doping layer.

'PP'
layer_ppp LayerSpec

p++ doping layer.

'PPP'
layer_n LayerSpec

n doping layer.

'N'
layer_np LayerSpec

n+ doping layer.

'NP'
layer_npp LayerSpec

n++ doping layer.

'NPP'
layer_via LayerSpec | None

via layer.

None
width_via float

via width in um.

1.0
layer_metal LayerSpec | None

metal layer.

None
port_names tuple[str, str]

for input and output ('o1', 'o2').

('o1', 'o2')
cladding_layers Layers | None

list of layers to extrude.

cladding_layers_optical
cladding_offsets Floats | None

list of offset from main Section edge.

cladding_offsets_optical
cladding_simplify Floats | None

Optional Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting. polygon by more than the value listed here will be removed.

None
kwargs Any

cross_section settings.

                   layer_si
           |<------width_si---->|

                  layer_ge
              |<--width_ge->|
               ______________
              |             |
            __|_____________|___
           |     |       |     |
           |     |       |     |
    P      |     |       |     |         N                |
 width_p   |_____|_______|_____|           width_n        |
{}
Example
import gdsfactory as gf

xs = gf.cross_section.pn()
p = gf.path.straight()
c = p.extrude(xs)
c.plot()
Source code in gdsfactory/cross_section/pn_junction.py
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
@xsection
def pn_ge_detector_si_contacts(
    width_si: float = 6.0,
    layer_si: typings.LayerSpec = "WG",
    width_ge: float = 3.0,
    layer_ge: typings.LayerSpec = "GE",
    gap_low_doping: float = 0.6,
    gap_medium_doping: float = 0.9,
    gap_high_doping: float = 1.1,
    width_doping: float = 8.0,
    layer_p: typings.LayerSpec = "P",
    layer_pp: typings.LayerSpec = "PP",
    layer_ppp: typings.LayerSpec = "PPP",
    layer_n: typings.LayerSpec = "N",
    layer_np: typings.LayerSpec = "NP",
    layer_npp: typings.LayerSpec = "NPP",
    layer_via: typings.LayerSpec | None = None,
    width_via: float = 1.0,
    layer_metal: typings.LayerSpec | None = None,
    port_names: tuple[str, str] = ("o1", "o2"),
    cladding_layers: typings.Layers | None = cladding_layers_optical,
    cladding_offsets: typings.Floats | None = cladding_offsets_optical,
    cladding_simplify: typings.Floats | None = None,
    **kwargs: Any,
) -> CrossSection:
    """Linear Ge detector cross section based on a lateral p(i)n junction.

    It has silicon contacts (no contact on the Ge). The contacts need to be
    created in the component generating function (they can't be created here).

    See Chen et al., "High-Responsivity Low-Voltage 28-Gb/s Ge p-i-n Photodetector
    With Silicon Contacts", Journal of Lightwave Technology 33(4), 2015.

    Notice it is possible to have dopings going beyond the ridge waveguide. This
    is fine, and it is to account for the
    presence of the contacts. Such contacts can be subwavelength or not.

    Args:
        width_si: width of the full etch si in um.
        layer_si: si ridge layer.
        width_ge: width of the ge in um.
        layer_ge: ge layer.
        gap_low_doping: from waveguide center to low doping.
        gap_medium_doping: from waveguide center to medium doping. None removes it.
        gap_high_doping: from center to high doping. None removes it.
        width_doping: distance from waveguide center to doping edge in um.
        layer_p: p doping layer.
        layer_pp: p+ doping layer.
        layer_ppp: p++ doping layer.
        layer_n: n doping layer.
        layer_np: n+ doping layer.
        layer_npp: n++ doping layer.
        layer_via: via layer.
        width_via: via width in um.
        layer_metal: metal layer.
        port_names: for input and output ('o1', 'o2').
        cladding_layers: list of layers to extrude.
        cladding_offsets: list of offset from main Section edge.
        cladding_simplify: Optional Tolerance value for the simplification algorithm. \
                All points that can be removed without changing the resulting. \
                polygon by more than the value listed here will be removed.
        kwargs: cross_section settings.

                                   layer_si
                           |<------width_si---->|

                                  layer_ge
                              |<--width_ge->|
                               ______________
                              |             |
                            __|_____________|___
                           |     |       |     |
                           |     |       |     |
                    P      |     |       |     |         N                |
                 width_p   |_____|_______|_____|           width_n        |
        <----------------------->|       |<------------------------------>|
                                     |<->|
                                     gap_low_doping
                                     |         |        N+                |
                                     |         |     width_np             |
                                     |         |<------------------------>|
                                     |<------->|
                                     |     gap_medium_doping
                                     |
                                     |<---------------------------------->|
                                                width_doping

    Example:
        ```python
        import gdsfactory as gf

        xs = gf.cross_section.pn()
        p = gf.path.straight()
        c = p.extrude(xs)
        c.plot()
        ```
    """
    width_low_doping = width_doping - gap_low_doping
    offset_low_doping = width_low_doping / 2 + gap_low_doping

    s = Section(width=width_si, offset=0, layer=layer_si, port_names=port_names)
    n = Section(width=width_low_doping, offset=+offset_low_doping, layer=layer_n)
    p = Section(width=width_low_doping, offset=-offset_low_doping, layer=layer_p)

    section_list = [s, n, p]

    cladding_layers = cladding_layers or ()
    cladding_offsets = cladding_offsets or ()
    cladding_simplify_not_none = cladding_simplify or (None,) * len(cladding_layers)
    section_list += [
        Section(width=width_si + 2 * offset, layer=layer, simplify=simplify)
        for layer, offset, simplify in zip(
            cladding_layers, cladding_offsets, cladding_simplify_not_none, strict=False
        )
    ]

    width_medium_doping = width_doping - gap_medium_doping
    offset_medium_doping = width_medium_doping / 2 + gap_medium_doping

    np = Section(
        width=width_medium_doping,
        offset=+offset_medium_doping,
        layer=layer_np,
    )
    pp = Section(
        width=width_medium_doping,
        offset=-offset_medium_doping,
        layer=layer_pp,
    )
    section_list.extend((np, pp))
    width_high_doping = width_doping - gap_high_doping
    offset_high_doping = width_high_doping / 2 + gap_high_doping
    npp = Section(width=width_high_doping, offset=+offset_high_doping, layer=layer_npp)
    ppp = Section(width=width_high_doping, offset=-offset_high_doping, layer=layer_ppp)
    section_list.extend((npp, ppp))
    if layer_via is not None:
        offset = width_high_doping / 2 + gap_high_doping
        via_top = Section(width=width_via, offset=+offset, layer=layer_via)
        via_bot = Section(width=width_via, offset=-offset, layer=layer_via)
        section_list.extend((via_top, via_bot))
    if layer_metal is not None:
        offset = width_high_doping / 2 + gap_high_doping
        port_types = ("electrical", "electrical")
        metal_top = Section(
            width=width_via,
            offset=+offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_top", "e2_top"),
        )
        metal_bot = Section(
            width=width_via,
            offset=-offset,
            layer=layer_metal,
            port_types=port_types,
            port_names=("e1_bot", "e2_bot"),
        )
        section_list.extend((metal_top, metal_bot))

    # Add the Ge
    s = Section(width=width_ge, offset=0, layer=layer_ge)
    section_list.append(s)

    return CrossSection(
        sections=tuple(section_list),
        **kwargs,
    )

CrossSectionFactory

CrossSectionFactory = Callable[..., 'CrossSection']

CrossSectionSpec

CrossSectionSpec = (
    CrossSection
    | str
    | dict[str, Any]
    | CrossSectionFactory
    | SymmetricalCrossSection
    | DCrossSection
)

Transitions

transition

transition(
    cross_section1: CrossSectionSpec,
    cross_section2: CrossSectionSpec,
    width_type: (
        WidthTypes | Callable[[float, float, float], float]
    ) = "sine",
    offset_type: (
        WidthTypes | Callable[[float, float, float], float]
    ) = "sine",
) -> Transition

Returns a smoothly-transitioning between two CrossSections.

Only cross-sectional elements that have the name (as in X.add(..., name = 'wg') ) parameter specified in both input CrosSections will be created. Port names will be cloned from the input CrossSections in reverse.

Parameters:

Name Type Description Default
cross_section1 CrossSectionSpec

First CrossSection.

required
cross_section2 CrossSectionSpec

Second CrossSection.

required
width_type WidthTypes | Callable[[float, float, float], float]

'sine', 'parabolic', 'linear' or Callable. type of width transition used if any widths are different between the two input CrossSections.

'sine'
offset_type WidthTypes | Callable[[float, float, float], float]

'sine', 'parabolic', 'linear' or Callable. type of width transition used if any widths are different between the two input CrossSections.

'sine'
Source code in gdsfactory/path.py
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
def transition(
    cross_section1: CrossSectionSpec,
    cross_section2: CrossSectionSpec,
    width_type: WidthTypes | Callable[[float, float, float], float] = "sine",
    offset_type: WidthTypes | Callable[[float, float, float], float] = "sine",
) -> Transition:
    """Returns a smoothly-transitioning between two CrossSections.

    Only cross-sectional elements that have the `name` (as in X.add(..., name = 'wg') )
    parameter specified in both input CrosSections will be created.
    Port names will be cloned from the input CrossSections in reverse.

    Args:
        cross_section1: First CrossSection.
        cross_section2: Second CrossSection.
        width_type: 'sine', 'parabolic', 'linear' or Callable. type of width transition used \
                if any widths are different between the two input CrossSections.
        offset_type: 'sine', 'parabolic', 'linear' or Callable. type of width transition used \
                if any widths are different between the two input CrossSections. \

    """
    from gdsfactory.pdk import get_cross_section, get_layer

    X1 = get_cross_section(cross_section1)
    X2 = get_cross_section(cross_section2)

    layers1 = {get_layer(section.layer) for section in X1.sections}
    layers2 = {get_layer(section.layer) for section in X2.sections}
    layers1.add(get_layer(X1.layer))
    layers2.add(get_layer(X2.layer))

    has_common_layers = bool(layers1.intersection(layers2))
    if not has_common_layers:
        raise ValueError(
            f"transition() found no common layers X1 {layers1} and X2 {layers2}"
        )

    return Transition(
        cross_section1=X1,
        cross_section2=X2,
        width_type=width_type,
        offset_type=offset_type,
    )

Boolean

boolean

boolean

boolean(
    A: ComponentOrReference,
    B: ComponentOrReference,
    operation: Literal[
        "or", "|", "not", "-", "^", "xor", "&", "and", "A-B"
    ],
    layer: LayerSpec,
    layer1: LayerSpec | None = None,
    layer2: LayerSpec | None = None,
) -> Component

Performs boolean operations between 2 Component or Instance objects.

The operation parameter specifies the type of boolean operation to perform. Supported operations include {'not', 'and', 'or', 'xor', '-', '&', '|', '^'}:

  • '|' is equivalent to 'or'
  • '-' is equivalent to 'not'
  • '&' is equivalent to 'and'
  • '^' is equivalent to 'xor'

Parameters:

Name Type Description Default
A ComponentOrReference

Component(/Reference) or list of Component(/References).

required
B ComponentOrReference

Component(/Reference) or list of Component(/References).

required
operation Literal['or', '|', 'not', '-', '^', 'xor', '&', 'and', 'A-B']

{'not', 'and', 'or', 'xor', '-', '&', '|', '^'}.

required
layer LayerSpec

Specific layer to put polygon geometry on.

required
layer1 LayerSpec | None

Specific layer to get polygons.

None
layer2 LayerSpec | None

Specific layer to get polygons.

None

Component with polygon(s) of the boolean operations between

Type Description
Component

the 2 input Components performed.

Example
import gdsfactory as gf

c = gf.Component()
c1 = c << gf.components.circle(radius=10)
c2 = c << gf.components.circle(radius=9)
c2.movex(5)

c = gf.boolean(c1, c2, operation="xor")
c.plot()
Source code in gdsfactory/boolean.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def boolean(
    A: ComponentOrReference,
    B: ComponentOrReference,
    operation: Literal["or", "|", "not", "-", "^", "xor", "&", "and", "A-B"],
    layer: LayerSpec,
    layer1: LayerSpec | None = None,
    layer2: LayerSpec | None = None,
) -> Component:
    """Performs boolean operations between 2 Component or Instance objects.

    The `operation` parameter specifies the type of boolean operation to perform.
    Supported operations include {'not', 'and', 'or', 'xor', '-', '&', '|', '^'}:

    - `'|'` is equivalent to `'or'`
    - `'-'` is equivalent to `'not'`
    - `'&'` is equivalent to `'and'`
    - `'^'` is equivalent to `'xor'`

    Args:
        A: Component(/Reference) or list of Component(/References).
        B: Component(/Reference) or list of Component(/References).
        operation: {'not', 'and', 'or', 'xor', '-', '&', '|', '^'}.
        layer: Specific layer to put polygon geometry on.
        layer1: Specific layer to get polygons.
        layer2: Specific layer to get polygons.

    Returns: Component with polygon(s) of the boolean operations between
      the 2 input Components performed.

    Example:
        ```python
        import gdsfactory as gf

        c = gf.Component()
        c1 = c << gf.components.circle(radius=10)
        c2 = c << gf.components.circle(radius=9)
        c2.movex(5)

        c = gf.boolean(c1, c2, operation="xor")
        c.plot()
        ```
    """
    from gdsfactory import get_layer

    if operation not in boolean_operations:
        raise ValueError(
            f"Boolean operation {operation} not supported. Choose from {list(boolean_operations.keys())}"
        )

    c = Component()
    layer1 = layer1 or layer
    layer2 = layer2 or layer

    layer_index1 = get_layer(layer1)
    layer_index2 = get_layer(layer2)
    layer_index = get_layer(layer)

    if isinstance(A, kf.DKCell):
        ar = kf.kdb.Region(A.begin_shapes_rec(layer_index1))
    else:
        ar = get_ref_shapes(A, layer_index1)
    if isinstance(B, kf.DKCell):
        br = kf.kdb.Region(B.begin_shapes_rec(layer_index2))
    else:
        br = get_ref_shapes(B, layer_index2)
    c.shapes(layer_index).insert(boolean_operations[operation](ar, br))

    return c

Decorators

cell

cell(
    _func: ComponentFunc[ComponentParams],
) -> ComponentFunc[ComponentParams]
cell(
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: (
        Iterable[Callable[[Component], None]] | None
    ) = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    schematic_function: Callable[ComponentParams, Schematic]
) -> Callable[
    [ComponentFunc[ComponentParams]],
    ComponentFunc[ComponentParams],
]
cell(
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: (
        Iterable[Callable[[Component], None]] | None
    ) = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    schematic_function: None = None
) -> Callable[
    [ComponentFunc[ComponentParams]],
    ComponentFunc[ComponentParams],
]
cell(
    _func: ComponentFunc[ComponentParams] | None = None,
    /,
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: (
        Iterable[Callable[[Component], None]] | None
    ) = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    ports: PortsDefinition | None = None,
    schematic_function: (
        Callable[ComponentParams, Schematic] | None
    ) = None,
) -> (
    ComponentFunc[ComponentParams]
    | Callable[
        [ComponentFunc[ComponentParams]],
        ComponentFunc[ComponentParams],
    ]
)

Decorator to convert a function into a Component.

Source code in gdsfactory/_cell.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def cell(
    _func: ComponentFunc[ComponentParams] | None = None,
    /,
    *,
    set_settings: bool = True,
    set_name: bool = True,
    check_ports: bool = True,
    check_instances: CheckInstances | None = None,
    snap_ports: bool = True,
    add_port_layers: bool = True,
    cache: Cache[int, Any] | dict[int, Any] | None = None,
    basename: str | None = None,
    drop_params: list[str] | None = None,
    register_factory: bool = True,
    overwrite_existing: bool | None = None,
    layout_cache: bool | None = None,
    info: dict[str, MetaData] | None = None,
    post_process: Iterable[Callable[[Component], None]] | None = None,
    debug_names: bool | None = None,
    tags: list[str] | None = None,
    with_module_name: bool = False,
    lvs_equivalent_ports: list[list[str]] | None = None,
    ports: PortsDefinition | None = None,
    schematic_function: Callable[ComponentParams, Schematic] | None = None,
) -> (
    ComponentFunc[ComponentParams]
    | Callable[[ComponentFunc[ComponentParams]], ComponentFunc[ComponentParams]]
):
    """Decorator to convert a function into a Component."""
    from gdsfactory.component import Component

    if with_module_name and _func is not None:
        basename = basename or _module_basename(_func)

    if drop_params is None:
        drop_params = ["self", "cls"]
    if post_process is None:
        post_process = []
    cell_kwargs: dict[str, Any] = dict(
        output_type=Component,
        set_settings=set_settings,
        set_name=set_name,
        check_ports=check_ports,
        check_instances=check_instances,
        snap_ports=snap_ports,
        add_port_layers=add_port_layers,
        cache=cache,
        basename=basename,
        drop_params=drop_params,
        register_factory=register_factory,
        overwrite_existing=overwrite_existing,
        layout_cache=layout_cache,
        info=info,
        post_process=post_process,
        debug_names=debug_names,
        tags=tags,
        lvs_equivalent_ports=lvs_equivalent_ports,
        ports=ports,
        schematic_function=schematic_function,
    )
    c: Any = _cell(_func, **cell_kwargs)  # type: ignore[arg-type]

    if _func is not None:
        c.is_gf_cell = True
        return cast(ComponentFunc[ComponentParams], c)

    @wraps(c)
    def wrapper(
        func: ComponentFunc[ComponentParams],
    ) -> ComponentFunc[ComponentParams]:
        decorated: Any
        if with_module_name and basename is None:
            decorated = _cell(
                func, **{**cell_kwargs, "basename": _module_basename(func)}
            )
        else:
            decorated = c(func)
        decorated.is_gf_cell = True
        return cast(ComponentFunc[ComponentParams], decorated)

    return wrapper

Typings

Anchor

Anchor = Literal[
    "ce",
    "cw",
    "nc",
    "ne",
    "nw",
    "sc",
    "se",
    "sw",
    "center",
    "cc",
]

ComponentSpec

ComponentSpec = (
    str
    | ComponentFactory
    | dict[str, Any]
    | DKCell
    | partial[Component]
)

Layer

Layer = tuple[int, int]

LayerSpec

LayerSpec = Layer | str | int | LayerEnum

LayerSpecs

LayerSpecs = Sequence[LayerSpec]

MaterialSpec

MaterialSpec = (
    str
    | float
    | tuple[float, float]
    | Callable[..., Any]
    | NDArray[float64]
)

PathType

PathType = str | Path

Step

Bases: TypedDict

Manhattan Step.

Parameters:

Name Type Description Default
x

set the absolute x coordinate of the next waypoint.

required
y

set the absolute y coordinate of the next waypoint.

required
dx

relative x-displacement from the current position.

required
dy

relative y-displacement from the current position.

required

You can combine absolute and relative in a single step, e.g. {"x": 100, "dy": 20} sets x to 100 and shifts y by 20 from the current position.

Source code in gdsfactory/typings.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class Step(TypedDict, total=False):
    """Manhattan Step.

    Parameters:
        x: set the absolute x coordinate of the next waypoint.
        y: set the absolute y coordinate of the next waypoint.
        dx: relative x-displacement from the current position.
        dy: relative y-displacement from the current position.

    You can combine absolute and relative in a single step, e.g. {"x": 100, "dy": 20}
    sets x to 100 and shifts y by 20 from the current position.

    """

    x: float
    y: float
    dx: Delta
    dy: Delta

MultiCrossSectionAngleSpec

MultiCrossSectionAngleSpec = Sequence[
    tuple[CrossSectionSpec, tuple[int, ...]]
]

Technology

AbstractLayer

Bases: BaseModel

Generic design layer.

Attributes:

Name Type Description
sizings_xoffsets Sequence[int]

sequence of xoffset sizings to apply to this Logical or Derived layer.

sizings_yoffsets Sequence[int]

sequence of yoffset sizings to apply to this Logical or Derived layer.

sizings_modes Sequence[int]

sequence of sizing modes to apply to this Logical or Derived layer.

Source code in gdsfactory/technology/layer_stack.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
class AbstractLayer(BaseModel):
    """Generic design layer.

    Attributes:
        sizings_xoffsets: sequence of xoffset sizings to apply to this Logical or Derived layer.
        sizings_yoffsets: sequence of yoffset sizings to apply to this Logical or Derived layer.
        sizings_modes: sequence of sizing modes to apply to this Logical or Derived layer.
    """

    sizings_xoffsets: Sequence[int] = (0,)
    sizings_yoffsets: Sequence[int] = (0,)
    sizings_modes: Sequence[int] = (2,)

    def _perform_operation(
        self, other: AbstractLayer, operation: Literal["and", "or", "xor", "not"]
    ) -> DerivedLayer:
        if isinstance(other, DerivedLayer | LogicalLayer) and isinstance(
            self, DerivedLayer | LogicalLayer
        ):
            return DerivedLayer(layer1=self, layer2=other, operation=operation)
        raise ValueError(f"{other} is not a DerivedLayer or LogicalLayer")

    # Boolean AND (&)
    def __and__(self, other: AbstractLayer) -> DerivedLayer:
        """Represents boolean AND (&) operation between two layers.

        Args:
            other (AbstractLayer): Another Layer object to perform AND operation.

        Returns:
            A new DerivedLayer with the AND operation logged.
        """
        return self._perform_operation(other, "and")

    # Boolean OR (|, +)
    def __or__(self, other: AbstractLayer) -> DerivedLayer:
        """Represents boolean OR (|) operation between two layers.

        Args:
            other (AbstractLayer): Another Layer object to perform OR operation.

        Returns:
            A new DerivedLayer with the OR operation logged.
        """
        return self._perform_operation(other, "or")

    def __add__(self, other: AbstractLayer) -> DerivedLayer:
        """Represents boolean OR (+) operation between two derived layers.

        Args:
            other (AbstractLayer): Another Layer object to perform OR operation.

        Returns:
            A new DerivedLayer with the AND operation logged.
        """
        return self._perform_operation(other, "or")

    # Boolean XOR (^)
    def __xor__(self, other: AbstractLayer) -> DerivedLayer:
        """Represents boolean XOR (^) operation between two derived layers.

        Args:
            other (AbstractLayer): Another Layer object to perform XOR operation.

        Returns:
            A new DerivedLayer with the XOR operation logged.
        """
        return self._perform_operation(other, "xor")

    # Boolean NOT (-)
    def __sub__(self, other: AbstractLayer) -> DerivedLayer:
        """Represents boolean NOT (-) operation on a derived layer.

        Args:
            other (AbstractLayer): Another Layer object to perform NOT operation.

        Returns:
            A new DerivedLayer with the NOT operation logged.
        """
        return self._perform_operation(other, "not")

    def sized(
        self: T,
        xoffset: int | tuple[int, ...],
        yoffset: int | tuple[int, ...] | None = None,
        mode: int | tuple[int, ...] | None = None,
    ) -> T:
        """Accumulates a list of sizing operations for the layer by the provided offset (in dbu).

        Args:
            xoffset (int | tuple): number of dbu units to buffer by. Can be a tuple for sequential sizing operations.
            yoffset (int | tuple): number of dbu units to buffer by in the y-direction. If not specified, uses xfactor. Can be a tuple for sequential sizing operations.
            mode (int | tuple): mode of the sizing operation(s). Can be a tuple for sequential sizing operations.
        """
        #  Validate inputs
        xoffset_list: list[int]
        if isinstance(xoffset, int):
            xoffset_list = [xoffset]
        else:
            xoffset_list = list(xoffset)
        yoffset_list: list[int]
        if isinstance(yoffset, tuple):
            if len(yoffset) != len(xoffset_list):
                raise ValueError(
                    "If yoffset is provided as a tuple, length must be equal to xoffset!"
                )
            yoffset_list = list(yoffset)
        elif yoffset is None:
            yoffset_list = xoffset_list
        else:
            yoffset_list = [yoffset] * len(xoffset_list)

        mode_list: list[int]
        if isinstance(mode, tuple):
            if len(mode) != len(xoffset_list):
                raise ValueError(
                    "If mode is provided as a tuple, length must be equal to xoffset!"
                )
            mode_list = list(mode)
        elif mode is None:
            mode_list = [2] * len(xoffset_list)
        else:
            mode_list = [mode] * len(xoffset_list)

        # Accumulate
        sizings_xoffsets = list(self.sizings_xoffsets) + xoffset_list
        sizings_yoffsets = list(self.sizings_yoffsets) + yoffset_list
        sizings_modes = list(self.sizings_modes) + mode_list

        # Return a copy of the layer with updated sizings
        current_layer_attributes = self.__dict__.copy()
        current_layer_attributes["sizings_xoffsets"] = sizings_xoffsets
        current_layer_attributes["sizings_yoffsets"] = sizings_yoffsets
        current_layer_attributes["sizings_modes"] = sizings_modes
        return self.__class__(**current_layer_attributes)

__add__

__add__(other: AbstractLayer) -> DerivedLayer

Represents boolean OR (+) operation between two derived layers.

Parameters:

Name Type Description Default
other AbstractLayer

Another Layer object to perform OR operation.

required

Returns:

Type Description
DerivedLayer

A new DerivedLayer with the AND operation logged.

Source code in gdsfactory/technology/layer_stack.py
68
69
70
71
72
73
74
75
76
77
def __add__(self, other: AbstractLayer) -> DerivedLayer:
    """Represents boolean OR (+) operation between two derived layers.

    Args:
        other (AbstractLayer): Another Layer object to perform OR operation.

    Returns:
        A new DerivedLayer with the AND operation logged.
    """
    return self._perform_operation(other, "or")

__and__

__and__(other: AbstractLayer) -> DerivedLayer

Represents boolean AND (&) operation between two layers.

Parameters:

Name Type Description Default
other AbstractLayer

Another Layer object to perform AND operation.

required

Returns:

Type Description
DerivedLayer

A new DerivedLayer with the AND operation logged.

Source code in gdsfactory/technology/layer_stack.py
45
46
47
48
49
50
51
52
53
54
def __and__(self, other: AbstractLayer) -> DerivedLayer:
    """Represents boolean AND (&) operation between two layers.

    Args:
        other (AbstractLayer): Another Layer object to perform AND operation.

    Returns:
        A new DerivedLayer with the AND operation logged.
    """
    return self._perform_operation(other, "and")

__or__

__or__(other: AbstractLayer) -> DerivedLayer

Represents boolean OR (|) operation between two layers.

Parameters:

Name Type Description Default
other AbstractLayer

Another Layer object to perform OR operation.

required

Returns:

Type Description
DerivedLayer

A new DerivedLayer with the OR operation logged.

Source code in gdsfactory/technology/layer_stack.py
57
58
59
60
61
62
63
64
65
66
def __or__(self, other: AbstractLayer) -> DerivedLayer:
    """Represents boolean OR (|) operation between two layers.

    Args:
        other (AbstractLayer): Another Layer object to perform OR operation.

    Returns:
        A new DerivedLayer with the OR operation logged.
    """
    return self._perform_operation(other, "or")

__sub__

__sub__(other: AbstractLayer) -> DerivedLayer

Represents boolean NOT (-) operation on a derived layer.

Parameters:

Name Type Description Default
other AbstractLayer

Another Layer object to perform NOT operation.

required

Returns:

Type Description
DerivedLayer

A new DerivedLayer with the NOT operation logged.

Source code in gdsfactory/technology/layer_stack.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def __sub__(self, other: AbstractLayer) -> DerivedLayer:
    """Represents boolean NOT (-) operation on a derived layer.

    Args:
        other (AbstractLayer): Another Layer object to perform NOT operation.

    Returns:
        A new DerivedLayer with the NOT operation logged.
    """
    return self._perform_operation(other, "not")

__xor__

__xor__(other: AbstractLayer) -> DerivedLayer

Represents boolean XOR (^) operation between two derived layers.

Parameters:

Name Type Description Default
other AbstractLayer

Another Layer object to perform XOR operation.

required

Returns:

Type Description
DerivedLayer

A new DerivedLayer with the XOR operation logged.

Source code in gdsfactory/technology/layer_stack.py
80
81
82
83
84
85
86
87
88
89
def __xor__(self, other: AbstractLayer) -> DerivedLayer:
    """Represents boolean XOR (^) operation between two derived layers.

    Args:
        other (AbstractLayer): Another Layer object to perform XOR operation.

    Returns:
        A new DerivedLayer with the XOR operation logged.
    """
    return self._perform_operation(other, "xor")

sized

sized(
    xoffset: int | tuple[int, ...],
    yoffset: int | tuple[int, ...] | None = None,
    mode: int | tuple[int, ...] | None = None,
) -> T

Accumulates a list of sizing operations for the layer by the provided offset (in dbu).

Parameters:

Name Type Description Default
xoffset int | tuple

number of dbu units to buffer by. Can be a tuple for sequential sizing operations.

required
yoffset int | tuple

number of dbu units to buffer by in the y-direction. If not specified, uses xfactor. Can be a tuple for sequential sizing operations.

None
mode int | tuple

mode of the sizing operation(s). Can be a tuple for sequential sizing operations.

None
Source code in gdsfactory/technology/layer_stack.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def sized(
    self: T,
    xoffset: int | tuple[int, ...],
    yoffset: int | tuple[int, ...] | None = None,
    mode: int | tuple[int, ...] | None = None,
) -> T:
    """Accumulates a list of sizing operations for the layer by the provided offset (in dbu).

    Args:
        xoffset (int | tuple): number of dbu units to buffer by. Can be a tuple for sequential sizing operations.
        yoffset (int | tuple): number of dbu units to buffer by in the y-direction. If not specified, uses xfactor. Can be a tuple for sequential sizing operations.
        mode (int | tuple): mode of the sizing operation(s). Can be a tuple for sequential sizing operations.
    """
    #  Validate inputs
    xoffset_list: list[int]
    if isinstance(xoffset, int):
        xoffset_list = [xoffset]
    else:
        xoffset_list = list(xoffset)
    yoffset_list: list[int]
    if isinstance(yoffset, tuple):
        if len(yoffset) != len(xoffset_list):
            raise ValueError(
                "If yoffset is provided as a tuple, length must be equal to xoffset!"
            )
        yoffset_list = list(yoffset)
    elif yoffset is None:
        yoffset_list = xoffset_list
    else:
        yoffset_list = [yoffset] * len(xoffset_list)

    mode_list: list[int]
    if isinstance(mode, tuple):
        if len(mode) != len(xoffset_list):
            raise ValueError(
                "If mode is provided as a tuple, length must be equal to xoffset!"
            )
        mode_list = list(mode)
    elif mode is None:
        mode_list = [2] * len(xoffset_list)
    else:
        mode_list = [mode] * len(xoffset_list)

    # Accumulate
    sizings_xoffsets = list(self.sizings_xoffsets) + xoffset_list
    sizings_yoffsets = list(self.sizings_yoffsets) + yoffset_list
    sizings_modes = list(self.sizings_modes) + mode_list

    # Return a copy of the layer with updated sizings
    current_layer_attributes = self.__dict__.copy()
    current_layer_attributes["sizings_xoffsets"] = sizings_xoffsets
    current_layer_attributes["sizings_yoffsets"] = sizings_yoffsets
    current_layer_attributes["sizings_modes"] = sizings_modes
    return self.__class__(**current_layer_attributes)

DerivedLayer

Bases: AbstractLayer

Physical "derived layer", resulting from a combination of GDS design layers. Can be used by renderers and simulators.

Overloads operators for simpler expressions.

Attributes:

Name Type Description
input_layer1

primary layer comprising the derived layer. Can be a GDS design layer (kf.kcell.LayerEnum , tuple[int, int]), or another derived layer.

input_layer2

secondary layer comprising the derived layer. Can be a GDS design layer (kf.kcell.LayerEnum , tuple[int, int]), or another derived layer.

operation Literal['and', '&', 'or', '|', 'xor', '^', 'not', '-']

operation to perform between layer1 and layer2. One of "and", "or", "xor", or "not" or associated symbols.

Source code in gdsfactory/technology/layer_stack.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
class DerivedLayer(AbstractLayer):
    """Physical "derived layer", resulting from a combination of GDS design layers. Can be used by renderers and simulators.

    Overloads operators for simpler expressions.

    Attributes:
        input_layer1: primary layer comprising the derived layer. Can be a GDS design layer (kf.kcell.LayerEnum , tuple[int, int]), or another derived layer.
        input_layer2: secondary layer comprising the derived layer. Can be a GDS design layer (kf.kcell.LayerEnum , tuple[int, int]), or another derived layer.
        operation: operation to perform between layer1 and layer2. One of "and", "or", "xor", or "not" or associated symbols.
    """

    layer1: DerivedLayer | LogicalLayer
    layer2: DerivedLayer | LogicalLayer
    operation: Literal["and", "&", "or", "|", "xor", "^", "not", "-"]

    def __hash__(self) -> int:
        """Generates a hash value for a LogicalLayer instance.

        This method allows LogicalLayer instances to be used in hash-based data structures such as sets and dictionaries.

        Returns:
            int: The hash value of the layer attribute.
        """
        return hash((self.layer1.__hash__(), self.layer2.__hash__(), self.operation))

    def __eq__(self, other: object) -> bool:
        """Check if two DerivedLayer instances are equal."""
        if not isinstance(other, DerivedLayer):
            return False
        return (
            self.layer1 == other.layer1
            and self.layer2 == other.layer2
            and self.operation == other.operation
        )

    @property
    def keyword_to_symbol(self) -> dict[str, str]:
        return {
            "and": "&",
            "or": "|",
            "xor": "^",
            "not": "-",
        }

    @property
    def symbol_to_keyword(self) -> dict[str, str]:
        return {
            "&": "and",
            "|": "or",
            "^": "xor",
            "-": "not",
        }

    def get_symbol(self) -> str:
        if self.operation in self.keyword_to_symbol:
            return self.keyword_to_symbol[self.operation]
        return self.operation

    def get_shapes(self, component: Component) -> kf.kdb.Region:
        """Return the shapes of the component argument corresponding to this layer.

        Arguments:
            component: Component from which to extract shapes on this layer.

        Returns:
            kf.kdb.Region: A region of polygons on this layer.
        """
        from gdsfactory.component import boolean_operations

        r1 = self.layer1.get_shapes(component)
        r2 = self.layer2.get_shapes(component)
        region = boolean_operations[self.operation](r1, r2)
        if not (
            all(v == 0 for v in self.sizings_xoffsets)
            and all(v == 0 for v in self.sizings_yoffsets)
        ):
            for xoffset, yoffset, mode in zip(
                self.sizings_xoffsets,
                self.sizings_yoffsets,
                self.sizings_modes,
                strict=False,
            ):
                region = region.sized(xoffset, yoffset, mode)
        return region

    def __repr__(self) -> str:
        """Print text representation."""
        return f"({self.layer1} {self.get_symbol()} {self.layer2})"

    __str__ = __repr__

__eq__

__eq__(other: object) -> bool

Check if two DerivedLayer instances are equal.

Source code in gdsfactory/technology/layer_stack.py
252
253
254
255
256
257
258
259
260
def __eq__(self, other: object) -> bool:
    """Check if two DerivedLayer instances are equal."""
    if not isinstance(other, DerivedLayer):
        return False
    return (
        self.layer1 == other.layer1
        and self.layer2 == other.layer2
        and self.operation == other.operation
    )

__hash__

__hash__() -> int

Generates a hash value for a LogicalLayer instance.

This method allows LogicalLayer instances to be used in hash-based data structures such as sets and dictionaries.

Returns:

Name Type Description
int int

The hash value of the layer attribute.

Source code in gdsfactory/technology/layer_stack.py
242
243
244
245
246
247
248
249
250
def __hash__(self) -> int:
    """Generates a hash value for a LogicalLayer instance.

    This method allows LogicalLayer instances to be used in hash-based data structures such as sets and dictionaries.

    Returns:
        int: The hash value of the layer attribute.
    """
    return hash((self.layer1.__hash__(), self.layer2.__hash__(), self.operation))

__repr__

__repr__() -> str

Print text representation.

Source code in gdsfactory/technology/layer_stack.py
312
313
314
def __repr__(self) -> str:
    """Print text representation."""
    return f"({self.layer1} {self.get_symbol()} {self.layer2})"

get_shapes

get_shapes(component: Component) -> kf.kdb.Region

Return the shapes of the component argument corresponding to this layer.

Parameters:

Name Type Description Default
component Component

Component from which to extract shapes on this layer.

required

Returns:

Type Description
Region

kf.kdb.Region: A region of polygons on this layer.

Source code in gdsfactory/technology/layer_stack.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
def get_shapes(self, component: Component) -> kf.kdb.Region:
    """Return the shapes of the component argument corresponding to this layer.

    Arguments:
        component: Component from which to extract shapes on this layer.

    Returns:
        kf.kdb.Region: A region of polygons on this layer.
    """
    from gdsfactory.component import boolean_operations

    r1 = self.layer1.get_shapes(component)
    r2 = self.layer2.get_shapes(component)
    region = boolean_operations[self.operation](r1, r2)
    if not (
        all(v == 0 for v in self.sizings_xoffsets)
        and all(v == 0 for v in self.sizings_yoffsets)
    ):
        for xoffset, yoffset, mode in zip(
            self.sizings_xoffsets,
            self.sizings_yoffsets,
            self.sizings_modes,
            strict=False,
        ):
            region = region.sized(xoffset, yoffset, mode)
    return region

LayerLevel

Bases: BaseModel

Level for 3D LayerStack.

Parameters:

Name Type Description Default
name

str

required
layer

LogicalLayer or DerivedLayer. DerivedLayers can be composed of operations consisting of multiple other GDSLayers or other DerivedLayers.

required
derived_layer

if the layer is derived, LogicalLayer to assign to the derived layer.

required
thickness

layer thickness in um.

required
thickness_tolerance

layer thickness tolerance in um.

required
width_tolerance

layer width tolerance in um.

required
zmin

height position where material starts in um.

required
zmin_tolerance

layer height tolerance in um.

required
sidewall_angle

in degrees with respect to normal.

required
sidewall_angle_tolerance

in degrees.

required
width_to_z

if sidewall_angle, reference z-position (0 --> zmin, 1 --> zmin + thickness, 0.5 in the middle).

required
bias

shrink/grow of the level compared to the mask

required
z_to_bias

most generic way to specify an extrusion. Two tuples of the same length specifying the shrink/grow (float) to apply between zmin (0) and zmin + thickness (1) I.e. [[z1, z2, ..., zN], [bias1, bias2, ..., biasN]] Defaults no buffering [[0, 1], [0, 0]]. NOTE: A dict might be more expressive.

required
mesh_order

lower mesh order (e.g. 1) will have priority over higher mesh order (e.g. 2) in the regions where materials overlap.

required
material

used in the klayout script

required
info

all other rendering and simulation metadata should go here.

required
Source code in gdsfactory/technology/layer_stack.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
class LayerLevel(BaseModel):
    """Level for 3D LayerStack.

    Parameters:
        name: str
        layer: LogicalLayer or DerivedLayer. DerivedLayers can be composed of operations consisting of multiple other GDSLayers or other DerivedLayers.
        derived_layer: if the layer is derived, LogicalLayer to assign to the derived layer.
        thickness: layer thickness in um.
        thickness_tolerance: layer thickness tolerance in um.
        width_tolerance: layer width tolerance in um.
        zmin: height position where material starts in um.
        zmin_tolerance: layer height tolerance in um.
        sidewall_angle: in degrees with respect to normal.
        sidewall_angle_tolerance: in degrees.
        width_to_z: if sidewall_angle, reference z-position (0 --> zmin, 1 --> zmin + thickness, 0.5 in the middle).
        bias: shrink/grow of the level compared to the mask
        z_to_bias: most generic way to specify an extrusion.\
            Two tuples of the same length specifying the shrink/grow (float) to apply between zmin (0) and zmin + thickness (1)\
            I.e. [[z1, z2, ..., zN], [bias1, bias2, ..., biasN]]\
                    Defaults no buffering [[0, 1], [0, 0]].
                    NOTE: A dict might be more expressive.
        mesh_order: lower mesh order (e.g. 1) will have priority over higher mesh order (e.g. 2) in the regions where materials overlap.
        material: used in the klayout script
        info: all other rendering and simulation metadata should go here.
    """

    # ID
    name: str | None = None
    layer: BroadLayer
    derived_layer: LogicalLayer | None = None

    # Extrusion rules
    thickness: float
    thickness_tolerance: float | None = None
    width_tolerance: float | None = None
    zmin: float
    zmin_tolerance: float | None = None
    sidewall_angle: float = 0.0
    sidewall_angle_tolerance: float | None = None
    width_to_z: float = 0.0
    z_to_bias: tuple[list[float], list[float]] | None = None
    bias: tuple[float, float] | float | None = None

    # Rendering
    mesh_order: int = 3
    material: str | None = None

    # Other
    info: dict[str, Any] = Field(default_factory=dict)

    @field_validator("layer")
    @classmethod
    def check_layer(cls, layer: BroadLayer) -> LogicalLayer | DerivedLayer:
        if isinstance(layer, LogicalLayer | DerivedLayer):
            return layer
        return LogicalLayer(layer=layer)

    @model_validator(mode="after")
    def check_derived_layer(self) -> LayerLevel:
        if isinstance(self.layer, DerivedLayer) and self.derived_layer is None:
            raise ValueError("derived_layer is required when layer is a DerivedLayer")
        return self

    @property
    def bounds(self) -> tuple[float, float]:
        """Calculates and returns the bounds of the layer level in the z-direction.

        Returns:
            tuple: A tuple containing the minimum and maximum z-values of the layer level.
        """
        z_values = [self.zmin, self.zmin + self.thickness]
        z_values.sort()
        return z_values[0], z_values[1]

bounds property

bounds: tuple[float, float]

Calculates and returns the bounds of the layer level in the z-direction.

Returns:

Name Type Description
tuple tuple[float, float]

A tuple containing the minimum and maximum z-values of the layer level.

LayerMap

Bases: LayerEnum

You will need to create a new LayerMap with your specific foundry layers.

Source code in gdsfactory/technology/layer_map.py
 7
 8
 9
10
class LayerMap(gf.LayerEnum):
    """You will need to create a new LayerMap with your specific foundry layers."""

    layout = gf.constant(gf.kcl.layout)

LayerStack

Bases: BaseModel

For simulation and 3D rendering. Captures design intent of the chip layers after fabrication.

Parameters:

Name Type Description Default
layers

dict of layer_levels.

required
Source code in gdsfactory/technology/layer_stack.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
class LayerStack(BaseModel):
    """For simulation and 3D rendering. Captures design intent of the chip layers after fabrication.

    Parameters:
        layers: dict of layer_levels.
    """

    layers: dict[str, LayerLevel] = Field(
        default_factory=dict,
        description="dict of layer_levels",
    )

    def model_copy(
        self, *, update: Mapping[str, Any] | None = None, deep: bool = False
    ) -> LayerStack:
        """Returns a copy of the LayerStack."""
        return super().model_copy(update=update, deep=True)

    def __init__(self, **data: Any) -> None:
        """Add LayerLevels automatically for subclassed LayerStacks."""
        super().__init__(**data)

        for field in self.model_dump():
            val = getattr(self, field)
            if isinstance(val, LayerLevel):
                self.layers[field] = val

    def pprint(self) -> None:
        console = Console()
        table = Table(show_header=True, header_style="bold")
        keys = ["layer", "thickness", "material", "sidewall_angle"]

        for key in ["name", *keys]:
            table.add_column(key)

        for layer_name, layer in self.layers.items():
            port_dict = dict(layer)
            row = [layer_name] + [str(port_dict.get(key, "")) for key in keys]
            table.add_row(*row)

        console.print(table)

    def get_layer_to_thickness(self) -> dict[BroadLayer, float]:
        """Returns layer tuple to thickness (um)."""
        layer_to_thickness: dict[BroadLayer, float] = {}

        for level in self.layers.values():
            layer = level.layer

            if (layer and level.thickness) or hasattr(level, "operator"):
                layer_to_thickness[layer] = level.thickness

        return layer_to_thickness

    def get_component_with_derived_layers(
        self, component: Component, **kwargs: Any
    ) -> Component:
        """Returns component with derived layers."""
        return get_component_with_derived_layers(
            component=component, layer_stack=self, **kwargs
        )

    def get_layer_to_zmin(self) -> dict[BroadLayer, float]:
        """Returns layer tuple to z min position (um)."""
        return {
            level.layer: level.zmin for level in self.layers.values() if level.thickness
        }

    def get_layer_to_material(self) -> dict[BroadLayer, str | None]:
        """Returns layer tuple to material name."""
        return {
            level.layer: level.material
            for level in self.layers.values()
            if level.thickness
        }

    def get_layer_to_sidewall_angle(self) -> dict[BroadLayer, float]:
        """Returns layer tuple to material name."""
        return {
            level.layer: level.sidewall_angle
            for level in self.layers.values()
            if level.thickness
        }

    def get_layer_to_info(self) -> dict[BroadLayer, dict[str, Any]]:
        """Returns layer tuple to info dict."""
        return {level.layer: level.info for level in self.layers.values()}

    def get_layer_to_layername(self) -> dict[BroadLayer, list[str]]:
        """Returns layer tuple to layername."""
        d: dict[BroadLayer, list[str]] = defaultdict(list)
        for level_name, level in self.layers.items():
            d[level.layer].append(level_name)

        return d

    def get_layer_to_mesh_order(
        self,
    ) -> dict[BroadLayer, int]:
        """Returns layer tuple to mesh order."""
        d: dict[BroadLayer, int] = defaultdict(int)
        for level in self.layers.values():
            if level.info is not None and "mesh_order" in level.info:
                # cspdk LayerStack has the mesh_order in the info dict, override default mesh_order if specified there
                d[level.layer] = level.info["mesh_order"]
            else:
                d[level.layer] = level.mesh_order
        return d

    def to_dict(self) -> dict[str, dict[str, Any]]:
        return {level_name: dict(level) for level_name, level in self.layers.items()}

    def __getitem__(self, key: str) -> LayerLevel:
        """Access layer stack elements."""
        if key not in self.layers:
            layers = list(self.layers.keys())
            raise KeyError(f"{key!r} not in {layers}")

        return self.layers[key]

    def get_klayout_3d_script(
        self,
        layer_views: LayerViews | None = None,
        dbu: float | None = 0.001,
    ) -> str:
        """Returns script for 2.5D view in KLayout.

        You can include this information in your tech.lyt

        Args:
            layer_views: optional layer_views.
            dbu: Optional database unit. Defaults to 1nm.
        """
        if self.layers is None:
            return ""
        layers = self.layers

        # Collect etch layers
        etch_layers = {
            layer_name: str(level.layer)
            for layer_name, level in layers.items()
            if isinstance(level.layer, DerivedLayer)
        }

        from gdsfactory.pdk import get_layer_tuple

        def get_base_layers(layer: BroadLayer) -> dict[str, tuple[int, int]]:
            base_layers = {}
            if isinstance(layer, DerivedLayer):
                base_layers.update(get_base_layers(layer.layer1))
                base_layers.update(get_base_layers(layer.layer2))
            elif isinstance(layer, LogicalLayer):
                base_layers[str(layer)] = get_layer_tuple(layer.layer)
            return base_layers

        base_layers = {
            k: v
            for layer_name in etch_layers
            for k, v in get_base_layers(layers[layer_name].layer).items()
        }

        unetched_layers = {
            layer_name: get_layer_tuple(level.layer.layer)
            for layer_name, level in layers.items()
            if isinstance(level.layer, LogicalLayer)
        }

        # Define base layers
        out = "# base layers\n"
        out += "\n".join(
            [
                f"{layer_name} = input({layer[0]}, {layer[1]})"
                for layer_name, layer in base_layers.items()
            ]
        )
        out += "\n\n"

        # Define unetched layers
        out += "# unetched layers\n"
        out += "\n".join(
            [
                f"{layer_name} = input({layer[0]}, {layer[1]})"
                for layer_name, layer in unetched_layers.items()
            ]
        )
        out += "\n\n"

        # Define etch layers
        out += "# etch layers\n"
        out += "\n".join(
            [
                f"{layer_name} = {layer_expr}"
                for layer_name, layer_expr in etch_layers.items()
            ]
        )
        out += "\n\n"

        if layer_views is None:
            from gdsfactory.pdk import get_layer_views

            layer_views = get_layer_views()
        layers_in_layer_views = layer_views.get_layer_tuples() if layer_views else set()

        for layer_name, level in layers.items():
            zmin = level.zmin
            zmax = zmin + level.thickness
            if dbu:
                rnd_pl = len(str(dbu).split(".")[-1])
                zmin = round(zmin, rnd_pl)
                zmax = round(zmax, rnd_pl)

            if layer_name in etch_layers:
                layer = level.derived_layer
            elif layer_name in unetched_layers:
                assert isinstance(level.layer, LogicalLayer)
                layer = level.layer

            layer_tuple = get_layer_tuple(layer.layer)  # type: ignore[union-attr]

            name = f"{layer_name}: {level.material} {layer_tuple[0]}/{layer_tuple[1]}"
            txt = f"z({layer_name}, zstart: {zmin}, zstop: {zmax}, name: '{name}'"

            if layer_views:
                if layer_tuple in layers_in_layer_views:
                    props = layer_views.get_from_tuple(layer_tuple)
                    if (
                        hasattr(props, "color")
                        and hasattr(props.color, "fill")
                        and hasattr(props.color, "frame")
                    ):
                        txt += ", "
                        if props.color.fill == props.color.frame:
                            txt += f"color: {props.color.fill}"
                        else:
                            txt += (
                                f"fill: {props.color.fill}, frame: {props.color.frame}"
                            )
            txt += ")"
            out += f"{txt}\n"

        return out

    def filtered(self, layers: list[str]) -> LayerStack:
        """Returns filtered layerstack, given layer specs."""
        return LayerStack(
            layers={k: self.layers[k] for k in layers if k in self.layers}
        )

    def z_offset(self, dz: float) -> LayerStack:
        """Translates the z-coordinates of the layerstack."""
        layers = self.layers or {}
        for layer in layers.values():
            layer.zmin += dz

        return self

    def invert_zaxis(self) -> LayerStack:
        """Flips the zmin values about the origin."""
        layers = self.layers or {}
        for layer in layers.values():
            layer.zmin *= -1

        return self

__getitem__

__getitem__(key: str) -> LayerLevel

Access layer stack elements.

Source code in gdsfactory/technology/layer_stack.py
509
510
511
512
513
514
515
def __getitem__(self, key: str) -> LayerLevel:
    """Access layer stack elements."""
    if key not in self.layers:
        layers = list(self.layers.keys())
        raise KeyError(f"{key!r} not in {layers}")

    return self.layers[key]

__init__

__init__(**data: Any) -> None

Add LayerLevels automatically for subclassed LayerStacks.

Source code in gdsfactory/technology/layer_stack.py
415
416
417
418
419
420
421
422
def __init__(self, **data: Any) -> None:
    """Add LayerLevels automatically for subclassed LayerStacks."""
    super().__init__(**data)

    for field in self.model_dump():
        val = getattr(self, field)
        if isinstance(val, LayerLevel):
            self.layers[field] = val

filtered

filtered(layers: list[str]) -> LayerStack

Returns filtered layerstack, given layer specs.

Source code in gdsfactory/technology/layer_stack.py
639
640
641
642
643
def filtered(self, layers: list[str]) -> LayerStack:
    """Returns filtered layerstack, given layer specs."""
    return LayerStack(
        layers={k: self.layers[k] for k in layers if k in self.layers}
    )

get_component_with_derived_layers

get_component_with_derived_layers(
    component: Component, **kwargs: Any
) -> Component

Returns component with derived layers.

Source code in gdsfactory/technology/layer_stack.py
451
452
453
454
455
456
457
def get_component_with_derived_layers(
    self, component: Component, **kwargs: Any
) -> Component:
    """Returns component with derived layers."""
    return get_component_with_derived_layers(
        component=component, layer_stack=self, **kwargs
    )

get_klayout_3d_script

get_klayout_3d_script(
    layer_views: LayerViews | None = None,
    dbu: float | None = 0.001,
) -> str

Returns script for 2.5D view in KLayout.

You can include this information in your tech.lyt

Parameters:

Name Type Description Default
layer_views LayerViews | None

optional layer_views.

None
dbu float | None

Optional database unit. Defaults to 1nm.

0.001
Source code in gdsfactory/technology/layer_stack.py
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
def get_klayout_3d_script(
    self,
    layer_views: LayerViews | None = None,
    dbu: float | None = 0.001,
) -> str:
    """Returns script for 2.5D view in KLayout.

    You can include this information in your tech.lyt

    Args:
        layer_views: optional layer_views.
        dbu: Optional database unit. Defaults to 1nm.
    """
    if self.layers is None:
        return ""
    layers = self.layers

    # Collect etch layers
    etch_layers = {
        layer_name: str(level.layer)
        for layer_name, level in layers.items()
        if isinstance(level.layer, DerivedLayer)
    }

    from gdsfactory.pdk import get_layer_tuple

    def get_base_layers(layer: BroadLayer) -> dict[str, tuple[int, int]]:
        base_layers = {}
        if isinstance(layer, DerivedLayer):
            base_layers.update(get_base_layers(layer.layer1))
            base_layers.update(get_base_layers(layer.layer2))
        elif isinstance(layer, LogicalLayer):
            base_layers[str(layer)] = get_layer_tuple(layer.layer)
        return base_layers

    base_layers = {
        k: v
        for layer_name in etch_layers
        for k, v in get_base_layers(layers[layer_name].layer).items()
    }

    unetched_layers = {
        layer_name: get_layer_tuple(level.layer.layer)
        for layer_name, level in layers.items()
        if isinstance(level.layer, LogicalLayer)
    }

    # Define base layers
    out = "# base layers\n"
    out += "\n".join(
        [
            f"{layer_name} = input({layer[0]}, {layer[1]})"
            for layer_name, layer in base_layers.items()
        ]
    )
    out += "\n\n"

    # Define unetched layers
    out += "# unetched layers\n"
    out += "\n".join(
        [
            f"{layer_name} = input({layer[0]}, {layer[1]})"
            for layer_name, layer in unetched_layers.items()
        ]
    )
    out += "\n\n"

    # Define etch layers
    out += "# etch layers\n"
    out += "\n".join(
        [
            f"{layer_name} = {layer_expr}"
            for layer_name, layer_expr in etch_layers.items()
        ]
    )
    out += "\n\n"

    if layer_views is None:
        from gdsfactory.pdk import get_layer_views

        layer_views = get_layer_views()
    layers_in_layer_views = layer_views.get_layer_tuples() if layer_views else set()

    for layer_name, level in layers.items():
        zmin = level.zmin
        zmax = zmin + level.thickness
        if dbu:
            rnd_pl = len(str(dbu).split(".")[-1])
            zmin = round(zmin, rnd_pl)
            zmax = round(zmax, rnd_pl)

        if layer_name in etch_layers:
            layer = level.derived_layer
        elif layer_name in unetched_layers:
            assert isinstance(level.layer, LogicalLayer)
            layer = level.layer

        layer_tuple = get_layer_tuple(layer.layer)  # type: ignore[union-attr]

        name = f"{layer_name}: {level.material} {layer_tuple[0]}/{layer_tuple[1]}"
        txt = f"z({layer_name}, zstart: {zmin}, zstop: {zmax}, name: '{name}'"

        if layer_views:
            if layer_tuple in layers_in_layer_views:
                props = layer_views.get_from_tuple(layer_tuple)
                if (
                    hasattr(props, "color")
                    and hasattr(props.color, "fill")
                    and hasattr(props.color, "frame")
                ):
                    txt += ", "
                    if props.color.fill == props.color.frame:
                        txt += f"color: {props.color.fill}"
                    else:
                        txt += (
                            f"fill: {props.color.fill}, frame: {props.color.frame}"
                        )
        txt += ")"
        out += f"{txt}\n"

    return out

get_layer_to_info

get_layer_to_info() -> dict[BroadLayer, dict[str, Any]]

Returns layer tuple to info dict.

Source code in gdsfactory/technology/layer_stack.py
481
482
483
def get_layer_to_info(self) -> dict[BroadLayer, dict[str, Any]]:
    """Returns layer tuple to info dict."""
    return {level.layer: level.info for level in self.layers.values()}

get_layer_to_layername

get_layer_to_layername() -> dict[BroadLayer, list[str]]

Returns layer tuple to layername.

Source code in gdsfactory/technology/layer_stack.py
485
486
487
488
489
490
491
def get_layer_to_layername(self) -> dict[BroadLayer, list[str]]:
    """Returns layer tuple to layername."""
    d: dict[BroadLayer, list[str]] = defaultdict(list)
    for level_name, level in self.layers.items():
        d[level.layer].append(level_name)

    return d

get_layer_to_material

get_layer_to_material() -> dict[BroadLayer, str | None]

Returns layer tuple to material name.

Source code in gdsfactory/technology/layer_stack.py
465
466
467
468
469
470
471
def get_layer_to_material(self) -> dict[BroadLayer, str | None]:
    """Returns layer tuple to material name."""
    return {
        level.layer: level.material
        for level in self.layers.values()
        if level.thickness
    }

get_layer_to_mesh_order

get_layer_to_mesh_order() -> dict[BroadLayer, int]

Returns layer tuple to mesh order.

Source code in gdsfactory/technology/layer_stack.py
493
494
495
496
497
498
499
500
501
502
503
504
def get_layer_to_mesh_order(
    self,
) -> dict[BroadLayer, int]:
    """Returns layer tuple to mesh order."""
    d: dict[BroadLayer, int] = defaultdict(int)
    for level in self.layers.values():
        if level.info is not None and "mesh_order" in level.info:
            # cspdk LayerStack has the mesh_order in the info dict, override default mesh_order if specified there
            d[level.layer] = level.info["mesh_order"]
        else:
            d[level.layer] = level.mesh_order
    return d

get_layer_to_sidewall_angle

get_layer_to_sidewall_angle() -> dict[BroadLayer, float]

Returns layer tuple to material name.

Source code in gdsfactory/technology/layer_stack.py
473
474
475
476
477
478
479
def get_layer_to_sidewall_angle(self) -> dict[BroadLayer, float]:
    """Returns layer tuple to material name."""
    return {
        level.layer: level.sidewall_angle
        for level in self.layers.values()
        if level.thickness
    }

get_layer_to_thickness

get_layer_to_thickness() -> dict[BroadLayer, float]

Returns layer tuple to thickness (um).

Source code in gdsfactory/technology/layer_stack.py
439
440
441
442
443
444
445
446
447
448
449
def get_layer_to_thickness(self) -> dict[BroadLayer, float]:
    """Returns layer tuple to thickness (um)."""
    layer_to_thickness: dict[BroadLayer, float] = {}

    for level in self.layers.values():
        layer = level.layer

        if (layer and level.thickness) or hasattr(level, "operator"):
            layer_to_thickness[layer] = level.thickness

    return layer_to_thickness

get_layer_to_zmin

get_layer_to_zmin() -> dict[BroadLayer, float]

Returns layer tuple to z min position (um).

Source code in gdsfactory/technology/layer_stack.py
459
460
461
462
463
def get_layer_to_zmin(self) -> dict[BroadLayer, float]:
    """Returns layer tuple to z min position (um)."""
    return {
        level.layer: level.zmin for level in self.layers.values() if level.thickness
    }

invert_zaxis

invert_zaxis() -> LayerStack

Flips the zmin values about the origin.

Source code in gdsfactory/technology/layer_stack.py
653
654
655
656
657
658
659
def invert_zaxis(self) -> LayerStack:
    """Flips the zmin values about the origin."""
    layers = self.layers or {}
    for layer in layers.values():
        layer.zmin *= -1

    return self

model_copy

model_copy(
    *,
    update: Mapping[str, Any] | None = None,
    deep: bool = False
) -> LayerStack

Returns a copy of the LayerStack.

Source code in gdsfactory/technology/layer_stack.py
409
410
411
412
413
def model_copy(
    self, *, update: Mapping[str, Any] | None = None, deep: bool = False
) -> LayerStack:
    """Returns a copy of the LayerStack."""
    return super().model_copy(update=update, deep=True)

z_offset

z_offset(dz: float) -> LayerStack

Translates the z-coordinates of the layerstack.

Source code in gdsfactory/technology/layer_stack.py
645
646
647
648
649
650
651
def z_offset(self, dz: float) -> LayerStack:
    """Translates the z-coordinates of the layerstack."""
    layers = self.layers or {}
    for layer in layers.values():
        layer.zmin += dz

    return self

LayerView

Bases: BaseModel

KLayout layer properties.

Docstrings adapted from KLayout documentation: https://www.klayout.de/lyp_format.html

Attributes:

Name Type Description
name str | None

Layer name.

info str | None

Extra info to include in the LayerView.

layer Layer | None

GDSII layer.

layer_in_name bool

Whether to display the name as 'name layer/datatype' rather than just the layer.

width int | None

This is the line width of the frame in pixels (or empty for the default which is 1).

line_style str | LineStyle | None

This is the number of the line style used to draw the shape boundaries. An empty string is "solid line". The values are "Ix" for one of the built-in styles where "I0" is "solid", "I1" is "dotted" etc.

hatch_pattern str | HatchPattern | None

This is the number of the dither pattern used to fill the shapes. The values are "Ix" for one of the built-in pattern where "I0" is "solid" and "I1" is "clear".

fill_color Color | None

Display color of the layer fill.

frame_color Color | None

Display color of the layer frame. Accepts Pydantic Color types. See: https://docs.pydantic.dev/usage/types/#color-type for more info.

fill_brightness int

Brightness of the fill.

frame_brightness int

Brightness of the frame.

animation int

This is a value indicating the animation mode. 0 is "none", 1 is "scrolling", 2 is "blinking" and 3 is "inverse blinking". (Only applies to KLayout layer properties)

xfill bool

Whether boxes are drawn with a diagonal cross. (Only applies to KLayout layer properties)

marked bool

Whether the entry is marked (drawn with small crosses). (Only applies to KLayout layer properties)

transparent bool

Whether the entry is transparent.

visible bool

Whether the entry is visible.

valid bool

Whether the entry is valid. Invalid layers are drawn but you can't select shapes on those layers. (Only applies to KLayout layer properties)

group_members dict[str, LayerView]

Add a list of group members to the LayerView.

Source code in gdsfactory/technology/layer_views.py
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
class LayerView(BaseModel):
    """KLayout layer properties.

    Docstrings adapted from KLayout documentation:
    https://www.klayout.de/lyp_format.html

    Attributes:
        name: Layer name.
        info: Extra info to include in the LayerView.
        layer: GDSII layer.
        layer_in_name: Whether to display the name as 'name layer/datatype' rather than just the layer.
        width: This is the line width of the frame in pixels (or empty for the default which is 1).
        line_style: This is the number of the line style used to draw the shape boundaries.
            An empty string is "solid line". The values are "Ix" for one of the built-in styles
            where "I0" is "solid", "I1" is "dotted" etc.
        hatch_pattern: This is the number of the dither pattern used to fill the shapes.
            The values are "Ix" for one of the built-in pattern where "I0" is "solid" and "I1" is "clear".
        fill_color: Display color of the layer fill.
        frame_color: Display color of the layer frame.
            Accepts Pydantic Color types. See: https://docs.pydantic.dev/usage/types/#color-type for more info.
        fill_brightness: Brightness of the fill.
        frame_brightness: Brightness of the frame.
        animation: This is a value indicating the animation mode.
            0 is "none", 1 is "scrolling", 2 is "blinking" and 3 is "inverse blinking".
            (Only applies to KLayout layer properties)
        xfill: Whether boxes are drawn with a diagonal cross. (Only applies to KLayout layer properties)
        marked: Whether the entry is marked (drawn with small crosses). (Only applies to KLayout layer properties)
        transparent: Whether the entry is transparent.
        visible: Whether the entry is visible.
        valid: Whether the entry is valid. Invalid layers are drawn but you can't select shapes on those layers.
            (Only applies to KLayout layer properties)
        group_members: Add a list of group members to the LayerView.
    """

    name: str | None = Field(default=None, exclude=True)
    info: str | None = Field(default=None)
    layer: Layer | None = None
    layer_in_name: bool = False
    frame_color: Color | None = None
    fill_color: Color | None = None
    frame_brightness: int = 0
    fill_brightness: int = 0
    hatch_pattern: str | HatchPattern | None = None
    line_style: str | LineStyle | None = None
    valid: bool = True
    visible: bool = True
    transparent: bool = False
    width: int | None = None
    marked: bool = False
    xfill: bool = False
    animation: int = 0
    group_members: builtins.dict[str, LayerView] = Field(default_factory=builtins.dict)

    def __init__(
        self,
        gds_layer: int | None = None,
        gds_datatype: int | None = None,
        color: ColorType | None = None,
        brightness: int | None = None,
        **data: Any,
    ) -> None:
        """Initialize LayerView object."""
        if (gds_layer is not None) and (gds_datatype is not None):
            if "layer" in data and data["layer"] is not None:
                raise KeyError(
                    "Specify either 'layer' or both 'gds_layer' and 'gds_datatype'."
                )
            data["layer"] = (gds_layer, gds_datatype)

        if color is not None:
            if "fill_color" in data or "frame_color" in data:
                raise KeyError(
                    "Specify either a single 'color' or both 'frame_color' and 'fill_color'."
                )
            data["fill_color"] = data["frame_color"] = color
        if brightness is not None:
            if "fill_brightness" in data or "frame_brightness" in data:
                raise KeyError(
                    "Specify either a single 'brightness' or both 'frame_brightness' and 'fill_brightness'."
                )
            data["fill_brightness"] = data["frame_brightness"] = brightness

        super().__init__(**data)

    def dict(
        self,
        *,
        include: IncEx | None = None,
        exclude: IncEx | None = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        simplify: bool = True,
    ) -> dict[str, Any]:
        """Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

        Specify "simplify" to consolidate fill and frame color/brightness if they are the same.

        Args:
            mode: The mode in which `to_python` should run.
                If mode is 'json', the dictionary will only contain JSON serializable types.
                If mode is 'python', the dictionary may contain any Python objects.
            include: A list of fields to include in the output.
            exclude: A list of fields to exclude from the output.
            by_alias: Whether to use the field's alias in the dictionary key if defined.
            exclude_unset: Whether to exclude fields that are unset or None from the output.
            exclude_defaults: Whether to exclude fields that are set to their default value from the output.
            exclude_none: Whether to exclude fields that have a value of `None` from the output.
            simplify: Whether to consolidate fill and frame color/brightness if they are the same.

        Returns:
            A dictionary representation of the model.

        """
        _dict = super().model_dump(
            include=include,
            exclude=exclude,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
        )

        if simplify:
            replace_keys = ["color", "brightness"]
            for key in replace_keys:
                fill = f"fill_{key}"
                frame = f"frame_{key}"

                if (fill in _dict and frame in _dict) and (_dict[fill] == _dict[frame]):
                    _dict[key] = _dict.pop(f"frame_{key}")
                    _dict.pop(f"fill_{key}")
        return _dict

    def __str__(self) -> str:
        """Returns a formatted view of properties and their values."""
        return "LayerView:\n\t" + "\n\t".join(
            [f"{k}: {v}" for k, v in self.model_dump().items()]
        )

    def __repr__(self) -> str:
        """Returns a formatted view of properties and their values."""
        return self.__str__()

    def get_alpha(self) -> float:
        if not self.visible:
            return 0.0
        if not self.transparent:
            dither_name = getattr(self.hatch_pattern, "name", self.hatch_pattern)
            if dither_name in ["I0", "solid"]:
                return 0.6
            if dither_name in ["I1", "hollow"]:
                return 0.1
            return 0.4
        return 0.3

    def get_color_dict(self) -> builtins.dict[str, str | None]:
        if self.fill_color is not None and self.frame_color is not None:
            return {
                "fill_color": ensure_six_digit_hex_color(self.fill_color.as_hex()),
                "frame_color": ensure_six_digit_hex_color(self.frame_color.as_hex()),
            }
        # Colors generated from here: http://phrogz.net/css/distinct-colors.html
        layer_colors = [
            "#3dcc5c",
            "#2b0fff",
            "#cc3d3d",
            "#e5dd45",
            "#7b3dcc",
            "#cc860c",
            "#73ff0f",
            "#2dccb4",
            "#ff0fa3",
            "#0ec2e6",
            "#3d87cc",
            "#e5520e",
        ]
        if self.layer is not None:
            color = layer_colors[int(np.mod(self.layer[0], len(layer_colors)))]
        else:
            color = None
        return {"fill_color": color, "frame_color": color}

    def _build_klayout_xml_element(
        self,
        tag: str,
        name: str,
        custom_hatch_patterns: builtins.dict[str, HatchPattern],
        custom_line_styles: builtins.dict[str, LineStyle],
    ) -> ET.Element:
        """Get XML Element from attributes."""
        # If hatch pattern name matches a named (built-in) KLayout pattern, use 'I<idx>' notation
        hatch_name = getattr(self.hatch_pattern, "name", self.hatch_pattern)
        if hatch_name is None:
            dither_pattern = None
        elif hatch_name in _klayout_dither_patterns:
            dither_pattern = f"I{list(_klayout_dither_patterns).index(str(hatch_name))}"
        elif hatch_name in custom_hatch_patterns:
            dither_pattern = f"C{list(custom_hatch_patterns).index(str(hatch_name))}"
        else:
            warnings.warn(
                f"Dither pattern {hatch_name!r} does not correspond to any KLayout built-in or custom pattern! Using 'I3' instead.",
                stacklevel=3,
            )
            dither_pattern = "I3"

        # If line style name matches a named (built-in) KLayout pattern, use 'I<idx>' notation
        ls_name = getattr(self.line_style, "name", self.line_style)
        if ls_name is None:
            line_style = None
        elif ls_name in _klayout_line_styles:
            line_style = f"I{list(_klayout_line_styles).index(str(ls_name))}"
        elif ls_name in custom_line_styles:
            line_style = f"C{list(custom_line_styles).index(str(ls_name))}"
        else:
            warnings.warn(
                f"Line style {ls_name!r} does not correspond to any KLayout built-in or custom pattern! Using 'I3' instead.",
                stacklevel=3,
            )
            line_style = "I3"

        frame_color = (
            ensure_six_digit_hex_color(self.frame_color.as_hex())
            if isinstance(self.frame_color, Color)
            else self.frame_color
        )
        fill_color = (
            ensure_six_digit_hex_color(self.fill_color.as_hex())
            if isinstance(self.fill_color, Color)
            else self.fill_color
        )
        prop_dict = {
            "frame-color": frame_color,
            "fill-color": fill_color,
            "frame-brightness": self.frame_brightness,
            "fill-brightness": self.fill_brightness,
            "dither-pattern": dither_pattern,
            "line-style": line_style,
            "valid": str(self.valid).lower(),
            "visible": str(self.visible).lower(),
            "transparent": str(self.transparent).lower(),
            "width": self.width,
            "marked": str(self.marked).lower(),
            "xfill": str(self.xfill).lower(),
            "animation": self.animation,
            "name": (
                f"{name} {self.layer[0]}/{self.layer[1]}"
                if self.layer_in_name and self.layer is not None
                else name
            ),
            "source": (
                f"{self.layer[0]}/{self.layer[1]}@1"
                if self.layer is not None
                else "*/*@*"
            ),
        }
        el = ET.Element(tag)
        for key, value in prop_dict.items():
            subel = ET.SubElement(el, key)

            if value is None:
                continue

            if isinstance(value, bool):
                value = str(value).lower()

            subel.text = str(value)
        return el

    def to_klayout_xml(
        self,
        custom_hatch_patterns: builtins.dict[str, HatchPattern],
        custom_line_styles: builtins.dict[str, LineStyle],
    ) -> ET.Element:
        """Return an XML representation of the LayerView."""
        props = self._build_klayout_xml_element(
            "properties",
            name=self.name or "",
            custom_hatch_patterns=custom_hatch_patterns,
            custom_line_styles=custom_line_styles,
        )
        for member_name, member in self.group_members.items():
            props.append(
                member._build_klayout_xml_element(
                    "group-members",
                    name=member_name,
                    custom_hatch_patterns=custom_hatch_patterns,
                    custom_line_styles=custom_line_styles,
                )
            )
        return props

    @staticmethod
    def _process_name(
        name: str, layer_pattern: str | re.Pattern[str], source: str | None = None
    ) -> tuple[str | None, bool | None]:
        """Strip layer info from name if it exists.

        Args:
            name: XML-formatted name entry.
            layer_pattern: Regex pattern to match layers with.
            source: Source field from XML that may contain layer name.
        """
        # If name is empty but source exists, try to extract name from source
        if not name and source:
            # Extract name from source like "WG_COR 37/4@1"
            match = re.search(layer_pattern, source)
            if match:
                # Get the part before the layer pattern
                name = source[: match.start()].strip()
                if name:
                    return clean_name(name, remove_dots=True), False
            return None, None

        if not name:
            return None, None

        layer_in_name = False
        match = re.search(layer_pattern, name)
        if match:
            name = (name[: match.start()] + name[match.end() :]).strip()
            layer_in_name = True
        return clean_name(name, remove_dots=True), layer_in_name

    @staticmethod
    def _process_layer(
        layer: str, layer_pattern: str | re.Pattern[str]
    ) -> Layer | None:
        """Convert .lyp XML layer entry to a Layer.

        Args:
            layer: XML-formatted layer entry.
            layer_pattern: Regex pattern to match layers with.
        """
        match = re.search(layer_pattern, layer)
        if not match:
            raise OSError(f"Could not read layer {layer}!")
        v = match.group().split("/")
        return None if "*" in v else (int(v[0]), int(v[1]))

    @classmethod
    def from_xml_element(
        cls, element: ET.Element, layer_pattern: str | re.Pattern[str]
    ) -> LayerView | None:
        """Read properties from .lyp XML and generate LayerViews from them.

        Args:
            element: XML Element to iterate over.
            layer_pattern: Regex pattern to match layers with.
        """
        element_name = element.find("name")
        element_source = element.find("source")
        source_text = element_source.text if element_source is not None else None

        name, layer_in_name = cls._process_name(
            element_name.text or "" if element_name is not None else "",
            layer_pattern,
            source_text,
        )
        if name is None:
            return None

        hatch_pattern = element.find("dither-pattern").text  # type: ignore[union-attr]
        # Translate KLayout index to hatch name
        if hatch_pattern and re.match(r"I\d+", hatch_pattern):
            hatch_pattern = list(_klayout_dither_patterns.keys())[
                int(hatch_pattern[1:])
            ]

        # Translate KLayout index to line style name
        line_style = element.find("line-style")
        if (
            line_style is not None
            and line_style.text is not None
            and re.match(r"I\d+", line_style.text)
        ):
            line_style = list(_klayout_line_styles.keys())[int(line_style.text[1:])]  # type: ignore[assignment]

        lv = LayerView(
            name=name,
            layer=cls._process_layer(element.find("source").text, layer_pattern),  # type: ignore[union-attr,arg-type]
            fill_color=getattr(element.find("fill-color"), "text", None),
            frame_color=getattr(element.find("frame-color"), "text", None),
            fill_brightness=element.find("fill-brightness").text or 0,  # type: ignore[union-attr]
            frame_brightness=element.find("frame-brightness").text or 0,  # type: ignore[union-attr]
            hatch_pattern=hatch_pattern or None,
            line_style=line_style
            if line_style is not None and len(line_style) > 0
            else None,
            valid=getattr(element.find("valid"), "text", True),
            visible=getattr(element.find("visible"), "text", True),
            transparent=getattr(element.find("transparent"), "text", False),
            width=getattr(element.find("width"), "text", None),
            marked=getattr(element.find("marked"), "text", False),
            xfill=getattr(element.find("xfill"), "text", False),
            animation=getattr(element.find("animation"), "text", False),
            layer_in_name=layer_in_name,
        )

        # Add only if needed, so we can filter by defaults when dumping to yaml
        group_members: builtins.dict[str, LayerView] = {}
        for member in element.iterfind("group-members"):
            member_lv = cls.from_xml_element(member, layer_pattern)
            if member_lv and member_lv.name is not None:
                group_members[member_lv.name] = member_lv

        if group_members != {}:
            lv.group_members = group_members

        return lv

__init__

__init__(
    gds_layer: int | None = None,
    gds_datatype: int | None = None,
    color: ColorType | None = None,
    brightness: int | None = None,
    **data: Any
) -> None

Initialize LayerView object.

Source code in gdsfactory/technology/layer_views.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
def __init__(
    self,
    gds_layer: int | None = None,
    gds_datatype: int | None = None,
    color: ColorType | None = None,
    brightness: int | None = None,
    **data: Any,
) -> None:
    """Initialize LayerView object."""
    if (gds_layer is not None) and (gds_datatype is not None):
        if "layer" in data and data["layer"] is not None:
            raise KeyError(
                "Specify either 'layer' or both 'gds_layer' and 'gds_datatype'."
            )
        data["layer"] = (gds_layer, gds_datatype)

    if color is not None:
        if "fill_color" in data or "frame_color" in data:
            raise KeyError(
                "Specify either a single 'color' or both 'frame_color' and 'fill_color'."
            )
        data["fill_color"] = data["frame_color"] = color
    if brightness is not None:
        if "fill_brightness" in data or "frame_brightness" in data:
            raise KeyError(
                "Specify either a single 'brightness' or both 'frame_brightness' and 'fill_brightness'."
            )
        data["fill_brightness"] = data["frame_brightness"] = brightness

    super().__init__(**data)

__repr__

__repr__() -> str

Returns a formatted view of properties and their values.

Source code in gdsfactory/technology/layer_views.py
515
516
517
def __repr__(self) -> str:
    """Returns a formatted view of properties and their values."""
    return self.__str__()

__str__

__str__() -> str

Returns a formatted view of properties and their values.

Source code in gdsfactory/technology/layer_views.py
509
510
511
512
513
def __str__(self) -> str:
    """Returns a formatted view of properties and their values."""
    return "LayerView:\n\t" + "\n\t".join(
        [f"{k}: {v}" for k, v in self.model_dump().items()]
    )

dict

dict(
    *,
    include: IncEx | None = None,
    exclude: IncEx | None = None,
    by_alias: bool = False,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    exclude_none: bool = False,
    simplify: bool = True
) -> dict[str, Any]

Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

Specify "simplify" to consolidate fill and frame color/brightness if they are the same.

Parameters:

Name Type Description Default
mode

The mode in which to_python should run. If mode is 'json', the dictionary will only contain JSON serializable types. If mode is 'python', the dictionary may contain any Python objects.

required
include IncEx | None

A list of fields to include in the output.

None
exclude IncEx | None

A list of fields to exclude from the output.

None
by_alias bool

Whether to use the field's alias in the dictionary key if defined.

False
exclude_unset bool

Whether to exclude fields that are unset or None from the output.

False
exclude_defaults bool

Whether to exclude fields that are set to their default value from the output.

False
exclude_none bool

Whether to exclude fields that have a value of None from the output.

False
simplify bool

Whether to consolidate fill and frame color/brightness if they are the same.

True

Returns:

Type Description
dict[str, Any]

A dictionary representation of the model.

Source code in gdsfactory/technology/layer_views.py
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
def dict(
    self,
    *,
    include: IncEx | None = None,
    exclude: IncEx | None = None,
    by_alias: bool = False,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    exclude_none: bool = False,
    simplify: bool = True,
) -> dict[str, Any]:
    """Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

    Specify "simplify" to consolidate fill and frame color/brightness if they are the same.

    Args:
        mode: The mode in which `to_python` should run.
            If mode is 'json', the dictionary will only contain JSON serializable types.
            If mode is 'python', the dictionary may contain any Python objects.
        include: A list of fields to include in the output.
        exclude: A list of fields to exclude from the output.
        by_alias: Whether to use the field's alias in the dictionary key if defined.
        exclude_unset: Whether to exclude fields that are unset or None from the output.
        exclude_defaults: Whether to exclude fields that are set to their default value from the output.
        exclude_none: Whether to exclude fields that have a value of `None` from the output.
        simplify: Whether to consolidate fill and frame color/brightness if they are the same.

    Returns:
        A dictionary representation of the model.

    """
    _dict = super().model_dump(
        include=include,
        exclude=exclude,
        by_alias=by_alias,
        exclude_unset=exclude_unset,
        exclude_defaults=exclude_defaults,
        exclude_none=exclude_none,
    )

    if simplify:
        replace_keys = ["color", "brightness"]
        for key in replace_keys:
            fill = f"fill_{key}"
            frame = f"frame_{key}"

            if (fill in _dict and frame in _dict) and (_dict[fill] == _dict[frame]):
                _dict[key] = _dict.pop(f"frame_{key}")
                _dict.pop(f"fill_{key}")
    return _dict

from_xml_element classmethod

from_xml_element(
    element: Element, layer_pattern: str | Pattern[str]
) -> LayerView | None

Read properties from .lyp XML and generate LayerViews from them.

Parameters:

Name Type Description Default
element Element

XML Element to iterate over.

required
layer_pattern str | Pattern[str]

Regex pattern to match layers with.

required
Source code in gdsfactory/technology/layer_views.py
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
@classmethod
def from_xml_element(
    cls, element: ET.Element, layer_pattern: str | re.Pattern[str]
) -> LayerView | None:
    """Read properties from .lyp XML and generate LayerViews from them.

    Args:
        element: XML Element to iterate over.
        layer_pattern: Regex pattern to match layers with.
    """
    element_name = element.find("name")
    element_source = element.find("source")
    source_text = element_source.text if element_source is not None else None

    name, layer_in_name = cls._process_name(
        element_name.text or "" if element_name is not None else "",
        layer_pattern,
        source_text,
    )
    if name is None:
        return None

    hatch_pattern = element.find("dither-pattern").text  # type: ignore[union-attr]
    # Translate KLayout index to hatch name
    if hatch_pattern and re.match(r"I\d+", hatch_pattern):
        hatch_pattern = list(_klayout_dither_patterns.keys())[
            int(hatch_pattern[1:])
        ]

    # Translate KLayout index to line style name
    line_style = element.find("line-style")
    if (
        line_style is not None
        and line_style.text is not None
        and re.match(r"I\d+", line_style.text)
    ):
        line_style = list(_klayout_line_styles.keys())[int(line_style.text[1:])]  # type: ignore[assignment]

    lv = LayerView(
        name=name,
        layer=cls._process_layer(element.find("source").text, layer_pattern),  # type: ignore[union-attr,arg-type]
        fill_color=getattr(element.find("fill-color"), "text", None),
        frame_color=getattr(element.find("frame-color"), "text", None),
        fill_brightness=element.find("fill-brightness").text or 0,  # type: ignore[union-attr]
        frame_brightness=element.find("frame-brightness").text or 0,  # type: ignore[union-attr]
        hatch_pattern=hatch_pattern or None,
        line_style=line_style
        if line_style is not None and len(line_style) > 0
        else None,
        valid=getattr(element.find("valid"), "text", True),
        visible=getattr(element.find("visible"), "text", True),
        transparent=getattr(element.find("transparent"), "text", False),
        width=getattr(element.find("width"), "text", None),
        marked=getattr(element.find("marked"), "text", False),
        xfill=getattr(element.find("xfill"), "text", False),
        animation=getattr(element.find("animation"), "text", False),
        layer_in_name=layer_in_name,
    )

    # Add only if needed, so we can filter by defaults when dumping to yaml
    group_members: builtins.dict[str, LayerView] = {}
    for member in element.iterfind("group-members"):
        member_lv = cls.from_xml_element(member, layer_pattern)
        if member_lv and member_lv.name is not None:
            group_members[member_lv.name] = member_lv

    if group_members != {}:
        lv.group_members = group_members

    return lv

to_klayout_xml

to_klayout_xml(
    custom_hatch_patterns: dict[str, HatchPattern],
    custom_line_styles: dict[str, LineStyle],
) -> ET.Element

Return an XML representation of the LayerView.

Source code in gdsfactory/technology/layer_views.py
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
def to_klayout_xml(
    self,
    custom_hatch_patterns: builtins.dict[str, HatchPattern],
    custom_line_styles: builtins.dict[str, LineStyle],
) -> ET.Element:
    """Return an XML representation of the LayerView."""
    props = self._build_klayout_xml_element(
        "properties",
        name=self.name or "",
        custom_hatch_patterns=custom_hatch_patterns,
        custom_line_styles=custom_line_styles,
    )
    for member_name, member in self.group_members.items():
        props.append(
            member._build_klayout_xml_element(
                "group-members",
                name=member_name,
                custom_hatch_patterns=custom_hatch_patterns,
                custom_line_styles=custom_line_styles,
            )
        )
    return props

LayerViews

Bases: BaseModel

A container for layer properties for KLayout layer property (.lyp) files.

Attributes:

Name Type Description
layer_views dict[str, LayerView]

Dictionary of LayerViews describing how to display gds layers.

custom_dither_patterns dict[str, HatchPattern]

Custom dither patterns.

custom_line_styles dict[str, LineStyle]

Custom line styles.

layers type[LayerEnum] | None

Specify a layer_map to get the layer tuple based on the name of the LayerView, rather than the 'layer' argument.

Source code in gdsfactory/technology/layer_views.py
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
class LayerViews(BaseModel):
    """A container for layer properties for KLayout layer property (.lyp) files.

    Attributes:
        layer_views: Dictionary of LayerViews describing how to display gds layers.
        custom_dither_patterns: Custom dither patterns.
        custom_line_styles: Custom line styles.
        layers: Specify a layer_map to get the layer tuple based on the name of the LayerView, rather than the 'layer' argument.
    """

    layer_views: dict[str, LayerView] = Field(default_factory=dict)
    custom_dither_patterns: dict[str, HatchPattern] = Field(default_factory=dict)
    custom_line_styles: dict[str, LineStyle] = Field(default_factory=dict)
    layers: type[LayerEnum] | None = None

    model_config = ConfigDict(extra="forbid", frozen=True, revalidate_instances="never")

    def __init__(
        self,
        filepath: PathLike | None = None,
        layers: type[LayerEnum] | None = None,
        **data: Any,
    ) -> None:
        """Initialize LayerViews object.

        Args:
            filepath: can be YAML or LYP.
            layers: Optional layermap.
            data: Additional data to add to the LayerViews object.
        """
        if filepath is not None:
            filepath = pathlib.Path(filepath)
            if filepath.suffix == ".lyp":
                lvs = LayerViews.from_lyp(filepath=filepath)
                logger.debug(
                    f"Importing LayerViews from KLayout layer properties file: {str(filepath)!r}."
                )
            elif filepath.suffix in {".yaml", ".yml"}:
                lvs = LayerViews.from_yaml(layer_file=filepath)
                logger.debug(f"Importing LayerViews from YAML file: {str(filepath)!r}.")
            else:
                raise ValueError(f"Unable to load LayerViews from {str(filepath)!r}.")

            data["layer_views"] = lvs.layer_views
            data["custom_line_styles"] = lvs.custom_line_styles
            data["custom_dither_patterns"] = lvs.custom_dither_patterns
        layer_names: builtins.dict[str, LayerEnum] | None = None
        if layers:
            layer_names = {layer.name: layer for layer in layers if layer is not None}  # type: ignore[attr-defined]
        else:
            layer_names = None

        # Use model_construct to skip Pydantic re-validation of LayerView instances.
        # This avoids "Input should be a valid dictionary or instance of LayerView"
        # errors in long-running processes where class identity can drift.
        cls = type(self)
        construct_data: builtins.dict[str, Any] = {
            "layer_views": data.get("layer_views", {}),
            "custom_dither_patterns": data.get("custom_dither_patterns", {}),
            "custom_line_styles": data.get("custom_line_styles", {}),
            "layers": layers,
        }
        # Include subclass fields (e.g. extra LayerView fields) with their defaults
        for field_name, field_info in cls.model_fields.items():
            if field_name not in construct_data:
                construct_data[field_name] = data.get(field_name, field_info.default)
        constructed = cls.model_construct(**construct_data)
        object.__setattr__(self, "__dict__", constructed.__dict__)
        object.__setattr__(
            self, "__pydantic_fields_set__", constructed.__pydantic_fields_set__
        )

        for name in self.model_dump():
            lv = getattr(self, name)
            if isinstance(lv, LayerView):
                # Auto-populate group_members from LayerView subclass fields
                if type(lv) is not LayerView and not lv.group_members:
                    for field_name in type(lv).model_fields:
                        field_val = getattr(lv, field_name)
                        if isinstance(field_val, LayerView):
                            lv.group_members[field_name] = field_val
                if (
                    layers is not None
                    and layer_names is not None
                    and name in layer_names
                ):
                    lv_dict = lv.dict(exclude={"layer", "name"})
                    lv = LayerView(layer=layer_names[name], name=name, **lv_dict)
                self.add_layer_view(name=name, layer_view=lv)

    def add_layer_view(
        self, name: str, layer_view: LayerView | None = None, **kwargs: Any
    ) -> None:
        """Adds a layer to LayerViews.

        Args:
            name: Name of the LayerView.
            layer_view: LayerView to add.
            kwargs: Additional arguments to pass to LayerView.
        """
        if name in self.layer_views:
            raise ValueError(
                f"Adding {name!r} already defined {list(self.layer_views.keys())}"
            )
        if layer_view is None:
            layer_view = LayerView(name=name, **kwargs)
        if layer_view.name is None:
            layer_view.name = name
        self.layer_views[name] = layer_view

        # If the dither pattern is a CustomDitherPattern, add it to custom_patterns
        dither_pattern = layer_view.hatch_pattern
        if isinstance(dither_pattern, HatchPattern) and (
            dither_pattern.name is not None
            and dither_pattern.name not in self.custom_dither_patterns
        ):
            self.custom_dither_patterns[dither_pattern.name] = dither_pattern

        # If hatch_pattern is the name of a custom pattern, replace string with the CustomDitherPattern
        elif (
            isinstance(dither_pattern, str)
            and dither_pattern in self.custom_dither_patterns
        ):
            layer_view.hatch_pattern = self.custom_dither_patterns[dither_pattern]

        line_style = layer_view.line_style
        if isinstance(line_style, LineStyle) and (
            line_style.name is not None
            and line_style.name not in self.custom_line_styles
        ):
            self.custom_line_styles[line_style.name] = line_style
        elif isinstance(line_style, str) and line_style in self.custom_line_styles:
            layer_view.line_style = self.custom_line_styles[line_style]

    def get_layer_views(self, exclude_groups: bool = False) -> dict[str, LayerView]:
        """Return all LayerViews.

        Args:
            exclude_groups: Whether to exclude LayerViews that contain other LayerViews.
        """
        layers: dict[str, LayerView] = {}
        for name, view in self.layer_views.items():
            if view.group_members and not exclude_groups:
                layers.update(view.group_members.items())
            layers[name] = view
        return layers

    def get_layer_view_groups(self) -> dict[str, LayerView]:
        """Return the LayerViews that contain other LayerViews."""
        return {name: lv for name, lv in self.layer_views.items() if lv.group_members}

    def __str__(self) -> str:
        """Prints the number of LayerView objects in the LayerViews object."""
        lvs = self.get_layer_views()
        groups = self.get_layer_view_groups()
        return (
            f"LayerViews: {len(lvs)} layers ({len(groups)} groups)\n"
            f"\tCustomDitherPatterns: {list(self.custom_dither_patterns.keys())}\n"
            f"\tCustomLineStyles: {list(self.custom_line_styles.keys())}\n"
        )

    def get(self, name: str) -> LayerView:
        """Returns Layer from name.

        Args:
            name: Name of layer.
        """
        if name not in self.layer_views:
            raise ValueError(f"Layer {name!r} not in {list(self.layer_views.keys())}")
        return self.layer_views[name]

    def __getitem__(self, val: str) -> LayerView:
        """Allows accessing to the layer names like ls['gold2'].

        Args:
            val: Layer name to access within the LayerViews.

        Returns:
            self.layers[val]: LayerView in the LayerViews.

        """
        try:
            return self.get_layer_views()[val]
        except Exception as error:
            raise KeyError(
                f"LayerView {val!r} not in LayerViews {list(self.layer_views.keys())}"
            ) from error

    def get_from_tuple(self, layer_tuple: tuple[int, int]) -> LayerView:
        """Returns LayerView from layer tuple.

        Args:
            layer_tuple: Tuple of (gds_layer, gds_datatype).

        Returns:
            LayerView.
        """
        tuple_to_name = {v.layer: k for k, v in self.get_layer_views().items()}
        if layer_tuple not in tuple_to_name:
            raise ValueError(
                f"LayerView {layer_tuple} not in {list(tuple_to_name.keys())}"
            )

        name = tuple_to_name[layer_tuple]
        return self.get_layer_views()[name]

    def get_layer_tuples(self) -> set[Layer]:
        """Returns a tuple for each layer."""
        return {
            layer.layer
            for layer in self.get_layer_views().values()
            if layer.layer is not None
        }

    def clear(self) -> None:
        """Deletes all layers in the LayerViews."""
        self.layer_views = {}

    def preview_layerset(
        self, size: float = 100.0, spacing: float = 100.0
    ) -> Component:
        """Generates a Component with all the layers.

        Args:
            size: square size in um.
            spacing: spacing between each square in um.

        """
        import gdsfactory as gf

        component = gf.Component()
        scale = size / 100
        num_layers = len(self.get_layer_views())
        matrix_size = int(np.ceil(np.sqrt(num_layers)))
        layer_views = self.get_layer_views()

        non_empty_layers = [v for v in layer_views.values() if v.layer is not None]

        sorted_layers = sorted(
            non_empty_layers,
            key=lambda x: (x.layer[0], x.layer[1]),  # type: ignore[index]
        )

        for n, layer in enumerate(sorted_layers):
            layer_tuple = layer.layer
            if layer_tuple is None:
                continue
            rectangle = gf.components.rectangle(
                size=(100 * scale, 100 * scale), layer=layer_tuple
            )
            text = gf.components.text(
                text=f"{layer.name}\n{layer_tuple[0]} / {layer_tuple[1]}"
                if layer_tuple is not None
                else layer.name,
                size=20 * scale,
                position=(50 * scale, -20 * scale),
                justify="center",
                layer=layer_tuple,
            )

            xloc = n % matrix_size
            yloc = int(n // matrix_size)
            ref = component.add_ref(rectangle)
            ref.movex((100 + spacing) * xloc * scale)
            ref.movey(-(100 + spacing) * yloc * scale)
            ref = component.add_ref(text)
            ref.movex((100 + spacing) * xloc * scale)
            ref.movey(-(100 + spacing) * yloc * scale)
        return component

    def to_lyp(
        self, filepath: str | pathlib.Path, overwrite: bool = True
    ) -> pathlib.Path:
        """Write all layer properties to a KLayout .lyp file.

        Args:
            filepath: to write the .lyp file to (appends .lyp extension if not present).
            overwrite: Whether to overwrite an existing file located at the filepath.
        """
        filepath = pathlib.Path(filepath)
        dirpath = filepath.parent
        dirpath.mkdir(exist_ok=True, parents=True)

        if filepath.exists() and not overwrite:
            raise OSError(f"File {str(filepath)!r} exists, cannot write.")

        root = ET.Element("layer-properties")

        for lv in self.layer_views.values():
            root.append(
                lv.to_klayout_xml(
                    custom_hatch_patterns=self.custom_dither_patterns,
                    custom_line_styles=self.custom_line_styles,
                )
            )

        for dp in self.custom_dither_patterns.values():
            root.append(dp.to_klayout_xml())

        for ls in self.custom_line_styles.values():
            root.append(ls.to_klayout_xml())

        filepath.write_bytes(make_pretty_xml(root))
        return filepath

    @staticmethod
    def from_lyp(
        filepath: str | pathlib.Path,
        layer_pattern: str | re.Pattern[str] | None = None,
    ) -> LayerViews:
        r"""Write all layer properties to a KLayout .lyp file.

        Args:
            filepath: to write the .lyp file to (appends .lyp extension if not present).
            layer_pattern: Regex pattern to match layers with. Defaults to r'(\d+|\*)/(\d+|\*)'.
        """
        layer_pattern = re.compile(layer_pattern or r"(\d+|\*)/(\d+|\*)")

        filepath = pathlib.Path(filepath)

        if not filepath.exists():
            raise FileNotFoundError(
                f"File {str(filepath)!r} does not exist, cannot read."
            )

        tree = ET.parse(filepath)
        root = tree.getroot()
        if root.tag != "layer-properties":
            raise OSError("Layer properties file incorrectly formatted, cannot read.")

        dither_patterns: dict[str, HatchPattern] = {}
        pattern_counter = 0
        for dither_block in root.iter("custom-dither-pattern"):
            name_element = dither_block.find("name")
            name = name_element.text if name_element is not None else None
            order_element = dither_block.find("order")
            order = order_element.text if order_element is not None else None

            if order is None:
                continue

            # Generate a name if none exists
            if not name:
                name = f"custom_pattern_{pattern_counter}"
                pattern_counter += 1

            assert name is not None  # Type assertion for mypy
            pattern = "\n".join(
                [line.text for line in dither_block.find("pattern").iter()]  # type: ignore[misc,union-attr]
            )

            if name in dither_patterns:
                warnings.warn(
                    f"Dither pattern named {name!r} already exists. Keeping only the first defined.",
                    stacklevel=3,
                )
                continue

            dither_patterns[name] = HatchPattern(
                name=name,
                order=int(order),
                custom_pattern=pattern.lstrip(),
            )
        line_styles: dict[str, LineStyle] = {}
        style_counter = 0
        for line_block in root.iter("custom-line-style"):
            name_element = line_block.find("name")
            name = name_element.text if name_element is not None else None
            order_element = line_block.find("order")
            order = order_element.text if order_element is not None else None

            if order is None:
                continue

            # Generate a name if none exists
            if not name:
                name = f"custom_style_{style_counter}"
                style_counter += 1

            assert name is not None  # Type assertion for mypy
            if name in line_styles:
                warnings.warn(
                    f"Line style named {name!r} already exists. Keeping only the first defined.",
                    stacklevel=3,
                )
                continue

            pattern_element = line_block.find("pattern")
            line_pattern = pattern_element.text if pattern_element is not None else None

            line_styles[name] = LineStyle(
                name=name,
                order=int(order),
                custom_style=line_pattern,
            )

        layer_views = {}
        for properties_element in root.iter("properties"):
            lv = LayerView.from_xml_element(
                properties_element, layer_pattern=layer_pattern
            )
            if lv:
                hp = lv.hatch_pattern
                if isinstance(hp, str) and re.match(r"C\d+", hp):
                    lv.hatch_pattern = list(dither_patterns.keys())[int(hp[1:])]
                layer_views[lv.name] = lv

        return LayerViews.model_construct(
            layer_views=layer_views,
            custom_dither_patterns=dither_patterns,
            custom_line_styles=line_styles,
            layers=None,
        )

    def to_yaml(self, layer_file: str | pathlib.Path) -> None:
        """Export layer properties to a YAML file.

        Args:
            layer_file: Name of the file to write LayerViews to.
        """
        lf_path = pathlib.Path(layer_file)
        dirpath = lf_path.parent
        dirpath.mkdir(exist_ok=True, parents=True)

        lvs = {
            name: lv.dict(exclude_none=True, exclude_defaults=True, exclude_unset=True)
            for name, lv in self.layer_views.items()
        }

        out_dict = {"LayerViews": lvs}
        if self.custom_dither_patterns:
            out_dict["CustomDitherPatterns"] = {
                name: dp.model_dump(
                    exclude_none=True, exclude_defaults=True, exclude_unset=True
                )
                for name, dp in self.custom_dither_patterns.items()
            }
        if self.custom_line_styles:
            out_dict["CustomLineStyles"] = {
                name: ls.model_dump(
                    exclude_none=True, exclude_defaults=True, exclude_unset=True
                )
                for name, ls in self.custom_line_styles.items()
            }
        lf_path.write_bytes(
            yaml.dump(
                out_dict,
                indent=2,
                sort_keys=False,
                default_flow_style=False,
                encoding="utf-8",
                Dumper=TechnologyDumper,
            )
        )

    @staticmethod
    def from_yaml(layer_file: str | pathlib.Path) -> LayerViews:
        """Import layer properties from two yaml files.

        Args:
            layer_file: Name of the file to read LayerViews, CustomDitherPatterns, and CustomLineStyles from.
        """
        layer_file = pathlib.Path(layer_file)

        properties = yaml.safe_load(layer_file.read_text())
        lvs = {}
        for name, lv in properties["LayerViews"].items():
            if "group_members" in lv:
                lv["group_members"] = {
                    member_name: LayerView(name=member_name, **member_view)
                    for member_name, member_view in lv["group_members"].items()
                }
            lvs[name] = LayerView(name=name, **lv)

        custom_dither_patterns = (
            {
                name: HatchPattern(name=name, **dp)
                for name, dp in properties["CustomDitherPatterns"].items()
            }
            if "CustomDitherPatterns" in properties
            else {}
        )

        custom_line_styles = (
            {
                name: LineStyle(name=name, **ls)
                for name, ls in properties["CustomLineStyles"].items()
            }
            if "CustomLineStyles" in properties
            else {}
        )

        return LayerViews.model_construct(
            layer_views=lvs,
            custom_dither_patterns=custom_dither_patterns,
            custom_line_styles=custom_line_styles,
            layers=None,
        )

__getitem__

__getitem__(val: str) -> LayerView

Allows accessing to the layer names like ls['gold2'].

Parameters:

Name Type Description Default
val str

Layer name to access within the LayerViews.

required

Returns:

Type Description
LayerView

self.layers[val]: LayerView in the LayerViews.

Source code in gdsfactory/technology/layer_views.py
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
def __getitem__(self, val: str) -> LayerView:
    """Allows accessing to the layer names like ls['gold2'].

    Args:
        val: Layer name to access within the LayerViews.

    Returns:
        self.layers[val]: LayerView in the LayerViews.

    """
    try:
        return self.get_layer_views()[val]
    except Exception as error:
        raise KeyError(
            f"LayerView {val!r} not in LayerViews {list(self.layer_views.keys())}"
        ) from error

__init__

__init__(
    filepath: PathLike | None = None,
    layers: type[LayerEnum] | None = None,
    **data: Any
) -> None

Initialize LayerViews object.

Parameters:

Name Type Description Default
filepath PathLike | None

can be YAML or LYP.

None
layers type[LayerEnum] | None

Optional layermap.

None
data Any

Additional data to add to the LayerViews object.

{}
Source code in gdsfactory/technology/layer_views.py
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
def __init__(
    self,
    filepath: PathLike | None = None,
    layers: type[LayerEnum] | None = None,
    **data: Any,
) -> None:
    """Initialize LayerViews object.

    Args:
        filepath: can be YAML or LYP.
        layers: Optional layermap.
        data: Additional data to add to the LayerViews object.
    """
    if filepath is not None:
        filepath = pathlib.Path(filepath)
        if filepath.suffix == ".lyp":
            lvs = LayerViews.from_lyp(filepath=filepath)
            logger.debug(
                f"Importing LayerViews from KLayout layer properties file: {str(filepath)!r}."
            )
        elif filepath.suffix in {".yaml", ".yml"}:
            lvs = LayerViews.from_yaml(layer_file=filepath)
            logger.debug(f"Importing LayerViews from YAML file: {str(filepath)!r}.")
        else:
            raise ValueError(f"Unable to load LayerViews from {str(filepath)!r}.")

        data["layer_views"] = lvs.layer_views
        data["custom_line_styles"] = lvs.custom_line_styles
        data["custom_dither_patterns"] = lvs.custom_dither_patterns
    layer_names: builtins.dict[str, LayerEnum] | None = None
    if layers:
        layer_names = {layer.name: layer for layer in layers if layer is not None}  # type: ignore[attr-defined]
    else:
        layer_names = None

    # Use model_construct to skip Pydantic re-validation of LayerView instances.
    # This avoids "Input should be a valid dictionary or instance of LayerView"
    # errors in long-running processes where class identity can drift.
    cls = type(self)
    construct_data: builtins.dict[str, Any] = {
        "layer_views": data.get("layer_views", {}),
        "custom_dither_patterns": data.get("custom_dither_patterns", {}),
        "custom_line_styles": data.get("custom_line_styles", {}),
        "layers": layers,
    }
    # Include subclass fields (e.g. extra LayerView fields) with their defaults
    for field_name, field_info in cls.model_fields.items():
        if field_name not in construct_data:
            construct_data[field_name] = data.get(field_name, field_info.default)
    constructed = cls.model_construct(**construct_data)
    object.__setattr__(self, "__dict__", constructed.__dict__)
    object.__setattr__(
        self, "__pydantic_fields_set__", constructed.__pydantic_fields_set__
    )

    for name in self.model_dump():
        lv = getattr(self, name)
        if isinstance(lv, LayerView):
            # Auto-populate group_members from LayerView subclass fields
            if type(lv) is not LayerView and not lv.group_members:
                for field_name in type(lv).model_fields:
                    field_val = getattr(lv, field_name)
                    if isinstance(field_val, LayerView):
                        lv.group_members[field_name] = field_val
            if (
                layers is not None
                and layer_names is not None
                and name in layer_names
            ):
                lv_dict = lv.dict(exclude={"layer", "name"})
                lv = LayerView(layer=layer_names[name], name=name, **lv_dict)
            self.add_layer_view(name=name, layer_view=lv)

__str__

__str__() -> str

Prints the number of LayerView objects in the LayerViews object.

Source code in gdsfactory/technology/layer_views.py
941
942
943
944
945
946
947
948
949
def __str__(self) -> str:
    """Prints the number of LayerView objects in the LayerViews object."""
    lvs = self.get_layer_views()
    groups = self.get_layer_view_groups()
    return (
        f"LayerViews: {len(lvs)} layers ({len(groups)} groups)\n"
        f"\tCustomDitherPatterns: {list(self.custom_dither_patterns.keys())}\n"
        f"\tCustomLineStyles: {list(self.custom_line_styles.keys())}\n"
    )

add_layer_view

add_layer_view(
    name: str,
    layer_view: LayerView | None = None,
    **kwargs: Any
) -> None

Adds a layer to LayerViews.

Parameters:

Name Type Description Default
name str

Name of the LayerView.

required
layer_view LayerView | None

LayerView to add.

None
kwargs Any

Additional arguments to pass to LayerView.

{}
Source code in gdsfactory/technology/layer_views.py
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
def add_layer_view(
    self, name: str, layer_view: LayerView | None = None, **kwargs: Any
) -> None:
    """Adds a layer to LayerViews.

    Args:
        name: Name of the LayerView.
        layer_view: LayerView to add.
        kwargs: Additional arguments to pass to LayerView.
    """
    if name in self.layer_views:
        raise ValueError(
            f"Adding {name!r} already defined {list(self.layer_views.keys())}"
        )
    if layer_view is None:
        layer_view = LayerView(name=name, **kwargs)
    if layer_view.name is None:
        layer_view.name = name
    self.layer_views[name] = layer_view

    # If the dither pattern is a CustomDitherPattern, add it to custom_patterns
    dither_pattern = layer_view.hatch_pattern
    if isinstance(dither_pattern, HatchPattern) and (
        dither_pattern.name is not None
        and dither_pattern.name not in self.custom_dither_patterns
    ):
        self.custom_dither_patterns[dither_pattern.name] = dither_pattern

    # If hatch_pattern is the name of a custom pattern, replace string with the CustomDitherPattern
    elif (
        isinstance(dither_pattern, str)
        and dither_pattern in self.custom_dither_patterns
    ):
        layer_view.hatch_pattern = self.custom_dither_patterns[dither_pattern]

    line_style = layer_view.line_style
    if isinstance(line_style, LineStyle) and (
        line_style.name is not None
        and line_style.name not in self.custom_line_styles
    ):
        self.custom_line_styles[line_style.name] = line_style
    elif isinstance(line_style, str) and line_style in self.custom_line_styles:
        layer_view.line_style = self.custom_line_styles[line_style]

clear

clear() -> None

Deletes all layers in the LayerViews.

Source code in gdsfactory/technology/layer_views.py
1004
1005
1006
def clear(self) -> None:
    """Deletes all layers in the LayerViews."""
    self.layer_views = {}

from_lyp staticmethod

from_lyp(
    filepath: str | Path,
    layer_pattern: str | Pattern[str] | None = None,
) -> LayerViews

Write all layer properties to a KLayout .lyp file.

Parameters:

Name Type Description Default
filepath str | Path

to write the .lyp file to (appends .lyp extension if not present).

required
layer_pattern str | Pattern[str] | None

Regex pattern to match layers with. Defaults to r'(\d+|*)/(\d+|*)'.

None
Source code in gdsfactory/technology/layer_views.py
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
@staticmethod
def from_lyp(
    filepath: str | pathlib.Path,
    layer_pattern: str | re.Pattern[str] | None = None,
) -> LayerViews:
    r"""Write all layer properties to a KLayout .lyp file.

    Args:
        filepath: to write the .lyp file to (appends .lyp extension if not present).
        layer_pattern: Regex pattern to match layers with. Defaults to r'(\d+|\*)/(\d+|\*)'.
    """
    layer_pattern = re.compile(layer_pattern or r"(\d+|\*)/(\d+|\*)")

    filepath = pathlib.Path(filepath)

    if not filepath.exists():
        raise FileNotFoundError(
            f"File {str(filepath)!r} does not exist, cannot read."
        )

    tree = ET.parse(filepath)
    root = tree.getroot()
    if root.tag != "layer-properties":
        raise OSError("Layer properties file incorrectly formatted, cannot read.")

    dither_patterns: dict[str, HatchPattern] = {}
    pattern_counter = 0
    for dither_block in root.iter("custom-dither-pattern"):
        name_element = dither_block.find("name")
        name = name_element.text if name_element is not None else None
        order_element = dither_block.find("order")
        order = order_element.text if order_element is not None else None

        if order is None:
            continue

        # Generate a name if none exists
        if not name:
            name = f"custom_pattern_{pattern_counter}"
            pattern_counter += 1

        assert name is not None  # Type assertion for mypy
        pattern = "\n".join(
            [line.text for line in dither_block.find("pattern").iter()]  # type: ignore[misc,union-attr]
        )

        if name in dither_patterns:
            warnings.warn(
                f"Dither pattern named {name!r} already exists. Keeping only the first defined.",
                stacklevel=3,
            )
            continue

        dither_patterns[name] = HatchPattern(
            name=name,
            order=int(order),
            custom_pattern=pattern.lstrip(),
        )
    line_styles: dict[str, LineStyle] = {}
    style_counter = 0
    for line_block in root.iter("custom-line-style"):
        name_element = line_block.find("name")
        name = name_element.text if name_element is not None else None
        order_element = line_block.find("order")
        order = order_element.text if order_element is not None else None

        if order is None:
            continue

        # Generate a name if none exists
        if not name:
            name = f"custom_style_{style_counter}"
            style_counter += 1

        assert name is not None  # Type assertion for mypy
        if name in line_styles:
            warnings.warn(
                f"Line style named {name!r} already exists. Keeping only the first defined.",
                stacklevel=3,
            )
            continue

        pattern_element = line_block.find("pattern")
        line_pattern = pattern_element.text if pattern_element is not None else None

        line_styles[name] = LineStyle(
            name=name,
            order=int(order),
            custom_style=line_pattern,
        )

    layer_views = {}
    for properties_element in root.iter("properties"):
        lv = LayerView.from_xml_element(
            properties_element, layer_pattern=layer_pattern
        )
        if lv:
            hp = lv.hatch_pattern
            if isinstance(hp, str) and re.match(r"C\d+", hp):
                lv.hatch_pattern = list(dither_patterns.keys())[int(hp[1:])]
            layer_views[lv.name] = lv

    return LayerViews.model_construct(
        layer_views=layer_views,
        custom_dither_patterns=dither_patterns,
        custom_line_styles=line_styles,
        layers=None,
    )

from_yaml staticmethod

from_yaml(layer_file: str | Path) -> LayerViews

Import layer properties from two yaml files.

Parameters:

Name Type Description Default
layer_file str | Path

Name of the file to read LayerViews, CustomDitherPatterns, and CustomLineStyles from.

required
Source code in gdsfactory/technology/layer_views.py
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
@staticmethod
def from_yaml(layer_file: str | pathlib.Path) -> LayerViews:
    """Import layer properties from two yaml files.

    Args:
        layer_file: Name of the file to read LayerViews, CustomDitherPatterns, and CustomLineStyles from.
    """
    layer_file = pathlib.Path(layer_file)

    properties = yaml.safe_load(layer_file.read_text())
    lvs = {}
    for name, lv in properties["LayerViews"].items():
        if "group_members" in lv:
            lv["group_members"] = {
                member_name: LayerView(name=member_name, **member_view)
                for member_name, member_view in lv["group_members"].items()
            }
        lvs[name] = LayerView(name=name, **lv)

    custom_dither_patterns = (
        {
            name: HatchPattern(name=name, **dp)
            for name, dp in properties["CustomDitherPatterns"].items()
        }
        if "CustomDitherPatterns" in properties
        else {}
    )

    custom_line_styles = (
        {
            name: LineStyle(name=name, **ls)
            for name, ls in properties["CustomLineStyles"].items()
        }
        if "CustomLineStyles" in properties
        else {}
    )

    return LayerViews.model_construct(
        layer_views=lvs,
        custom_dither_patterns=custom_dither_patterns,
        custom_line_styles=custom_line_styles,
        layers=None,
    )

get

get(name: str) -> LayerView

Returns Layer from name.

Parameters:

Name Type Description Default
name str

Name of layer.

required
Source code in gdsfactory/technology/layer_views.py
951
952
953
954
955
956
957
958
959
def get(self, name: str) -> LayerView:
    """Returns Layer from name.

    Args:
        name: Name of layer.
    """
    if name not in self.layer_views:
        raise ValueError(f"Layer {name!r} not in {list(self.layer_views.keys())}")
    return self.layer_views[name]

get_from_tuple

get_from_tuple(layer_tuple: tuple[int, int]) -> LayerView

Returns LayerView from layer tuple.

Parameters:

Name Type Description Default
layer_tuple tuple[int, int]

Tuple of (gds_layer, gds_datatype).

required

Returns:

Type Description
LayerView

LayerView.

Source code in gdsfactory/technology/layer_views.py
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
def get_from_tuple(self, layer_tuple: tuple[int, int]) -> LayerView:
    """Returns LayerView from layer tuple.

    Args:
        layer_tuple: Tuple of (gds_layer, gds_datatype).

    Returns:
        LayerView.
    """
    tuple_to_name = {v.layer: k for k, v in self.get_layer_views().items()}
    if layer_tuple not in tuple_to_name:
        raise ValueError(
            f"LayerView {layer_tuple} not in {list(tuple_to_name.keys())}"
        )

    name = tuple_to_name[layer_tuple]
    return self.get_layer_views()[name]

get_layer_tuples

get_layer_tuples() -> set[Layer]

Returns a tuple for each layer.

Source code in gdsfactory/technology/layer_views.py
 996
 997
 998
 999
1000
1001
1002
def get_layer_tuples(self) -> set[Layer]:
    """Returns a tuple for each layer."""
    return {
        layer.layer
        for layer in self.get_layer_views().values()
        if layer.layer is not None
    }

get_layer_view_groups

get_layer_view_groups() -> dict[str, LayerView]

Return the LayerViews that contain other LayerViews.

Source code in gdsfactory/technology/layer_views.py
937
938
939
def get_layer_view_groups(self) -> dict[str, LayerView]:
    """Return the LayerViews that contain other LayerViews."""
    return {name: lv for name, lv in self.layer_views.items() if lv.group_members}

get_layer_views

get_layer_views(
    exclude_groups: bool = False,
) -> dict[str, LayerView]

Return all LayerViews.

Parameters:

Name Type Description Default
exclude_groups bool

Whether to exclude LayerViews that contain other LayerViews.

False
Source code in gdsfactory/technology/layer_views.py
924
925
926
927
928
929
930
931
932
933
934
935
def get_layer_views(self, exclude_groups: bool = False) -> dict[str, LayerView]:
    """Return all LayerViews.

    Args:
        exclude_groups: Whether to exclude LayerViews that contain other LayerViews.
    """
    layers: dict[str, LayerView] = {}
    for name, view in self.layer_views.items():
        if view.group_members and not exclude_groups:
            layers.update(view.group_members.items())
        layers[name] = view
    return layers

preview_layerset

preview_layerset(
    size: float = 100.0, spacing: float = 100.0
) -> Component

Generates a Component with all the layers.

Parameters:

Name Type Description Default
size float

square size in um.

100.0
spacing float

spacing between each square in um.

100.0
Source code in gdsfactory/technology/layer_views.py
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
def preview_layerset(
    self, size: float = 100.0, spacing: float = 100.0
) -> Component:
    """Generates a Component with all the layers.

    Args:
        size: square size in um.
        spacing: spacing between each square in um.

    """
    import gdsfactory as gf

    component = gf.Component()
    scale = size / 100
    num_layers = len(self.get_layer_views())
    matrix_size = int(np.ceil(np.sqrt(num_layers)))
    layer_views = self.get_layer_views()

    non_empty_layers = [v for v in layer_views.values() if v.layer is not None]

    sorted_layers = sorted(
        non_empty_layers,
        key=lambda x: (x.layer[0], x.layer[1]),  # type: ignore[index]
    )

    for n, layer in enumerate(sorted_layers):
        layer_tuple = layer.layer
        if layer_tuple is None:
            continue
        rectangle = gf.components.rectangle(
            size=(100 * scale, 100 * scale), layer=layer_tuple
        )
        text = gf.components.text(
            text=f"{layer.name}\n{layer_tuple[0]} / {layer_tuple[1]}"
            if layer_tuple is not None
            else layer.name,
            size=20 * scale,
            position=(50 * scale, -20 * scale),
            justify="center",
            layer=layer_tuple,
        )

        xloc = n % matrix_size
        yloc = int(n // matrix_size)
        ref = component.add_ref(rectangle)
        ref.movex((100 + spacing) * xloc * scale)
        ref.movey(-(100 + spacing) * yloc * scale)
        ref = component.add_ref(text)
        ref.movex((100 + spacing) * xloc * scale)
        ref.movey(-(100 + spacing) * yloc * scale)
    return component

to_lyp

to_lyp(
    filepath: str | Path, overwrite: bool = True
) -> pathlib.Path

Write all layer properties to a KLayout .lyp file.

Parameters:

Name Type Description Default
filepath str | Path

to write the .lyp file to (appends .lyp extension if not present).

required
overwrite bool

Whether to overwrite an existing file located at the filepath.

True
Source code in gdsfactory/technology/layer_views.py
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
def to_lyp(
    self, filepath: str | pathlib.Path, overwrite: bool = True
) -> pathlib.Path:
    """Write all layer properties to a KLayout .lyp file.

    Args:
        filepath: to write the .lyp file to (appends .lyp extension if not present).
        overwrite: Whether to overwrite an existing file located at the filepath.
    """
    filepath = pathlib.Path(filepath)
    dirpath = filepath.parent
    dirpath.mkdir(exist_ok=True, parents=True)

    if filepath.exists() and not overwrite:
        raise OSError(f"File {str(filepath)!r} exists, cannot write.")

    root = ET.Element("layer-properties")

    for lv in self.layer_views.values():
        root.append(
            lv.to_klayout_xml(
                custom_hatch_patterns=self.custom_dither_patterns,
                custom_line_styles=self.custom_line_styles,
            )
        )

    for dp in self.custom_dither_patterns.values():
        root.append(dp.to_klayout_xml())

    for ls in self.custom_line_styles.values():
        root.append(ls.to_klayout_xml())

    filepath.write_bytes(make_pretty_xml(root))
    return filepath

to_yaml

to_yaml(layer_file: str | Path) -> None

Export layer properties to a YAML file.

Parameters:

Name Type Description Default
layer_file str | Path

Name of the file to write LayerViews to.

required
Source code in gdsfactory/technology/layer_views.py
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
def to_yaml(self, layer_file: str | pathlib.Path) -> None:
    """Export layer properties to a YAML file.

    Args:
        layer_file: Name of the file to write LayerViews to.
    """
    lf_path = pathlib.Path(layer_file)
    dirpath = lf_path.parent
    dirpath.mkdir(exist_ok=True, parents=True)

    lvs = {
        name: lv.dict(exclude_none=True, exclude_defaults=True, exclude_unset=True)
        for name, lv in self.layer_views.items()
    }

    out_dict = {"LayerViews": lvs}
    if self.custom_dither_patterns:
        out_dict["CustomDitherPatterns"] = {
            name: dp.model_dump(
                exclude_none=True, exclude_defaults=True, exclude_unset=True
            )
            for name, dp in self.custom_dither_patterns.items()
        }
    if self.custom_line_styles:
        out_dict["CustomLineStyles"] = {
            name: ls.model_dump(
                exclude_none=True, exclude_defaults=True, exclude_unset=True
            )
            for name, ls in self.custom_line_styles.items()
        }
    lf_path.write_bytes(
        yaml.dump(
            out_dict,
            indent=2,
            sort_keys=False,
            default_flow_style=False,
            encoding="utf-8",
            Dumper=TechnologyDumper,
        )
    )

LogicalLayer

Bases: AbstractLayer

GDS design layer.

Source code in gdsfactory/technology/layer_stack.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
class LogicalLayer(AbstractLayer):
    """GDS design layer."""

    layer: LayerSpec

    def __eq__(self, other: object) -> bool:
        """Check if two LogicalLayer instances are equal.

        This method compares the 'layer' attribute of the two LogicalLayer instances.

        Args:
            other (LogicalLayer): The other LogicalLayer instance to compare with.

        Returns:
            bool: True if the 'layer' attributes are equal, False otherwise.

        Raises:
            NotImplementedError: If 'other' is not an instance of LogicalLayer.
        """
        if not isinstance(other, type(self)):
            raise NotImplementedError(f"{other} is not a {type(self)}")
        return self.layer == other.layer

    def __hash__(self) -> int:
        """Generates a hash value for a LogicalLayer instance.

        This method allows LogicalLayer instances to be used in hash-based data structures such as sets and dictionaries.

        Returns:
            int: The hash value of the layer attribute.
        """
        return hash(self.layer)

    def get_shapes(self, component: Component) -> kf.kdb.Region:
        """Return the shapes of the component argument corresponding to this layer.

        Arguments:
            component: Component from which to extract shapes on this layer.

        Returns:
            kf.kdb.Region: A region of polygons on this layer.
        """
        from gdsfactory.pdk import get_layer

        polygons_per_layer = component.get_polygons()
        layer_index = get_layer(self.layer)
        polygons = polygons_per_layer.get(layer_index, [])
        region = kf.kdb.Region(polygons)
        if not (
            all(v == 0 for v in self.sizings_xoffsets)
            and all(v == 0 for v in self.sizings_yoffsets)
        ):
            for xoffset, yoffset, mode in zip(
                self.sizings_xoffsets,
                self.sizings_yoffsets,
                self.sizings_modes,
                strict=False,
            ):
                region = region.sized(xoffset, yoffset, mode)
        return region

    def __repr__(self) -> str:
        """Print text representation."""
        return f"{self.layer}"

    __str__ = __repr__

__eq__

__eq__(other: object) -> bool

Check if two LogicalLayer instances are equal.

This method compares the 'layer' attribute of the two LogicalLayer instances.

Parameters:

Name Type Description Default
other LogicalLayer

The other LogicalLayer instance to compare with.

required

Returns:

Name Type Description
bool bool

True if the 'layer' attributes are equal, False otherwise.

Raises:

Type Description
NotImplementedError

If 'other' is not an instance of LogicalLayer.

Source code in gdsfactory/technology/layer_stack.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def __eq__(self, other: object) -> bool:
    """Check if two LogicalLayer instances are equal.

    This method compares the 'layer' attribute of the two LogicalLayer instances.

    Args:
        other (LogicalLayer): The other LogicalLayer instance to compare with.

    Returns:
        bool: True if the 'layer' attributes are equal, False otherwise.

    Raises:
        NotImplementedError: If 'other' is not an instance of LogicalLayer.
    """
    if not isinstance(other, type(self)):
        raise NotImplementedError(f"{other} is not a {type(self)}")
    return self.layer == other.layer

__hash__

__hash__() -> int

Generates a hash value for a LogicalLayer instance.

This method allows LogicalLayer instances to be used in hash-based data structures such as sets and dictionaries.

Returns:

Name Type Description
int int

The hash value of the layer attribute.

Source code in gdsfactory/technology/layer_stack.py
182
183
184
185
186
187
188
189
190
def __hash__(self) -> int:
    """Generates a hash value for a LogicalLayer instance.

    This method allows LogicalLayer instances to be used in hash-based data structures such as sets and dictionaries.

    Returns:
        int: The hash value of the layer attribute.
    """
    return hash(self.layer)

__repr__

__repr__() -> str

Print text representation.

Source code in gdsfactory/technology/layer_stack.py
220
221
222
def __repr__(self) -> str:
    """Print text representation."""
    return f"{self.layer}"

get_shapes

get_shapes(component: Component) -> kf.kdb.Region

Return the shapes of the component argument corresponding to this layer.

Parameters:

Name Type Description Default
component Component

Component from which to extract shapes on this layer.

required

Returns:

Type Description
Region

kf.kdb.Region: A region of polygons on this layer.

Source code in gdsfactory/technology/layer_stack.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def get_shapes(self, component: Component) -> kf.kdb.Region:
    """Return the shapes of the component argument corresponding to this layer.

    Arguments:
        component: Component from which to extract shapes on this layer.

    Returns:
        kf.kdb.Region: A region of polygons on this layer.
    """
    from gdsfactory.pdk import get_layer

    polygons_per_layer = component.get_polygons()
    layer_index = get_layer(self.layer)
    polygons = polygons_per_layer.get(layer_index, [])
    region = kf.kdb.Region(polygons)
    if not (
        all(v == 0 for v in self.sizings_xoffsets)
        and all(v == 0 for v in self.sizings_yoffsets)
    ):
        for xoffset, yoffset, mode in zip(
            self.sizings_xoffsets,
            self.sizings_yoffsets,
            self.sizings_modes,
            strict=False,
        ):
            region = region.sized(xoffset, yoffset, mode)
    return region

lyp_to_dataclass

lyp_to_dataclass(
    lyp_filepath: str | Path,
    overwrite: bool = True,
    sort_by_name: bool = True,
    map_name: str = "LayerMapFab",
    output_filepath: Path | str | None = None,
) -> str

Returns python LayerMap script from a klayout layer properties file lyp.

Source code in gdsfactory/technology/layer_map.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def lyp_to_dataclass(
    lyp_filepath: str | pathlib.Path,
    overwrite: bool = True,
    sort_by_name: bool = True,  # sort_by_name=False means same order as in lyp file
    map_name: str = "LayerMapFab",
    output_filepath: pathlib.Path | str | None = None,
) -> str:
    """Returns python LayerMap script from a klayout layer properties file lyp."""
    filepathin = pathlib.Path(lyp_filepath)

    if output_filepath is None:
        filepathout = filepathin.with_suffix(".py")
    else:
        filepathout = pathlib.Path(output_filepath)
    filepathout.parent.mkdir(parents=True, exist_ok=True)

    if filepathout.exists() and not overwrite:
        raise FileExistsError(f"You can delete {filepathout}")

    if not map_name.isidentifier():
        raise ValueError(
            f"Argument 'map_name' must be a valid python identifier, but {map_name} is not."
        )

    script = f"""from gdsfactory.typings import Layer
from gdsfactory.technology.layer_map import LayerMap


class {map_name}(LayerMap):
"""
    lys = LayerViews.from_lyp(filepathin)
    maybe_sort = sorted if sort_by_name else lambda seq: seq
    for layer_name, layer in maybe_sort(lys.get_layer_views().items()):
        if layer.layer is not None:
            script += (
                f"    {layer_name}: Layer = ({layer.layer[0]}, {layer.layer[1]})\n"
            )

    script += f"""

LAYER = {map_name}
"""

    filepathout.write_text(script)
    return script

Pack

pack

pack a list of components into as few components as possible.

Adapted from PHIDL https://github.com/amccaugh/phidl/ by Adam McCaughan

pack

pack(
    component_list: Sequence[ComponentSpec],
    spacing: float = 10.0,
    aspect_ratio: Float2 = (1.0, 1.0),
    max_size: tuple[float | None, float | None] = (
        None,
        None,
    ),
    sort_by_area: bool = True,
    density: float = 1.1,
    precision: float = 0.01,
    text: TextFunction | None = None,
    text_prefix: str = "",
    text_mirror: bool = False,
    text_rotation: int = 0,
    text_offsets: tuple[Float2, ...] = ((0, 0),),
    text_anchors: tuple[Anchor, ...] = ("cc",),
    name_prefix: str | None = None,
    rotation: int = 0,
    h_mirror: bool = False,
    v_mirror: bool = False,
    add_ports_prefix: bool = True,
    add_ports_suffix: bool = False,
    csvpath: str | None = None,
) -> list[Component]

Pack a list of components into as few Components as possible.

Parameters:

Name Type Description Default
component_list Sequence[ComponentSpec]

list or tuple.

required
spacing float

Minimum distance between adjacent shapes.

10.0
aspect_ratio Float2

(width, height) ratio of the rectangular bin.

(1.0, 1.0)
max_size tuple[float | None, float | None]

Limits the size into which the shapes will be packed.

(None, None)
sort_by_area bool

Pre-sorts the shapes by area.

True
density float

Values closer to 1 pack tighter but require more computation.

1.1
precision float

Desired precision for rounding vertex coordinates.

0.01
text TextFunction | None

Optional function to add text labels.

None
text_prefix str

for labels. For example. 'A' will produce 'A1', 'A2', ...

''
text_mirror bool

if True mirrors text.

False
text_rotation int

Optional text rotation.

0
text_offsets tuple[Float2, ...]

relative to component size info anchor. Defaults to center.

((0, 0),)
text_anchors tuple[Anchor, ...]

relative to component (ce cw nc ne nw sc se sw center cc).

('cc',)
name_prefix str | None

for each packed component (avoids the Unnamed cells warning). Note that the suffix contains a uuid so the name will not be deterministic.

None
rotation int

optional component rotation in degrees.

0
h_mirror bool

horizontal mirror in y axis (x, 1) (1, 0). This is the most common.

False
v_mirror bool

vertical mirror using x axis (1, y) (0, y).

False
add_ports_prefix bool

adds port names with prefix.

True
add_ports_suffix bool

adds port names with suffix.

False
csvpath str | None

optional path to save the packed component list as a CSV file.

None
Example
import gdsfactory as gf
from functools import partial

components = [gf.components.triangle(x=i) for i in range(1, 10)]
c = gf.pack(
components,
spacing=20.0,
max_size=(100, 100),
text=partial(gf.components.text, justify="center"),
text_prefix="R",
name_prefix="demo",
text_anchors=["nc"],
text_offsets=[(-10, 0)],
v_mirror=True,
)
c[0].plot()
Source code in gdsfactory/pack.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def pack(
    component_list: Sequence[ComponentSpec],
    spacing: float = 10.0,
    aspect_ratio: Float2 = (1.0, 1.0),
    max_size: tuple[float | None, float | None] = (None, None),
    sort_by_area: bool = True,
    density: float = 1.1,
    precision: float = 1e-2,
    text: TextFunction | None = None,
    text_prefix: str = "",
    text_mirror: bool = False,
    text_rotation: int = 0,
    text_offsets: tuple[Float2, ...] = ((0, 0),),
    text_anchors: tuple[Anchor, ...] = ("cc",),
    name_prefix: str | None = None,
    rotation: int = 0,
    h_mirror: bool = False,
    v_mirror: bool = False,
    add_ports_prefix: bool = True,
    add_ports_suffix: bool = False,
    csvpath: str | None = None,
) -> list[Component]:
    """Pack a list of components into as few Components as possible.

    Args:
        component_list: list or tuple.
        spacing: Minimum distance between adjacent shapes.
        aspect_ratio: (width, height) ratio of the rectangular bin.
        max_size: Limits the size into which the shapes will be packed.
        sort_by_area: Pre-sorts the shapes by area.
        density: Values closer to 1 pack tighter but require more computation.
        precision: Desired precision for rounding vertex coordinates.
        text: Optional function to add text labels.
        text_prefix: for labels. For example. 'A' will produce 'A1', 'A2', ...
        text_mirror: if True mirrors text.
        text_rotation: Optional text rotation.
        text_offsets: relative to component size info anchor. Defaults to center.
        text_anchors: relative to component (ce cw nc ne nw sc se sw center cc).
        name_prefix: for each packed component (avoids the Unnamed cells warning). \
                Note that the suffix contains a uuid so the name will not be deterministic.
        rotation: optional component rotation in degrees.
        h_mirror: horizontal mirror in y axis (x, 1) (1, 0). This is the most common.
        v_mirror: vertical mirror using x axis (1, y) (0, y).
        add_ports_prefix: adds port names with prefix.
        add_ports_suffix: adds port names with suffix.
        csvpath: optional path to save the packed component list as a CSV file.

    Example:
        ```python
        import gdsfactory as gf
        from functools import partial

        components = [gf.components.triangle(x=i) for i in range(1, 10)]
        c = gf.pack(
        components,
        spacing=20.0,
        max_size=(100, 100),
        text=partial(gf.components.text, justify="center"),
        text_prefix="R",
        name_prefix="demo",
        text_anchors=["nc"],
        text_offsets=[(-10, 0)],
        v_mirror=True,
        )
        c[0].plot()
        ```
    """
    import pandas as pd

    if csvpath:
        if os.path.exists(csvpath):
            df = pd.read_csv(csvpath)
        else:
            df = pd.DataFrame(columns=["name", "x", "y", "w", "h"])
    else:
        df = pd.DataFrame(columns=["name", "x", "y", "w", "h"])

    df.set_index("name", inplace=True)

    if density < 1.01:
        raise ValueError(
            "pack() `density` argument is too small. "
            "The density argument must be >= 1.01"
        )

    # Sanitize max_size variable
    max_size_filtered = tuple(np.inf if v is None else v for v in max_size)
    max_size_array: npt.NDArray[np.floating[Any]] = np.asarray(
        max_size_filtered, dtype=np.float64
    )  # In case it's integers
    max_size_array = max_size_array / precision
    max_size_tuple = cast("tuple[float, float]", tuple(max_size_array))

    components = [gf.get_component(component) for component in component_list]

    # Convert Components to rectangles
    rect_dict: dict[int, tuple[float, float]] = {}
    for n, _component in enumerate(components):
        size = np.array([_component.xsize, _component.ysize])
        w: float = int((size[0] + spacing) / precision)
        h: float = int((size[1] + spacing) / precision)
        if w > max_size_tuple[0]:
            raise ValueError(
                f"pack() failed because Component {_component.name!r} has x dimension "
                "larger than `max_size` and cannot be packed.\n"
                f"xsize = {size[0]}, max_xsize = {int(precision * w)}"
            )
        if h > max_size_tuple[1]:
            raise ValueError(
                f"pack() failed because Component {_component.name!r} has y dimension "
                "larger than `max_size` and cannot be packed.\n"
                f"ysize = {size[1]}, max_ysize = {int(precision * h)}"
            )
        rect_dict[n] = (w, h)

    packed_list: list[dict[int, tuple[float, float, float, float]]] = []
    while rect_dict:
        (packed_rect_dict, rect_dict) = _pack_single_bin(
            rect_dict,
            aspect_ratio=aspect_ratio,
            max_size=max_size_tuple,
            sort_by_area=sort_by_area,
            density=density,
        )
        packed_list.append(packed_rect_dict)

    components_packed_list: list[Component] = []
    index = 0
    for rect_dict_ in packed_list:
        packed = Component()
        for i, (n, rect) in enumerate(rect_dict_.items()):
            component = components[n]
            x, y, w, h = rect
            name = f"{component.name}_{i}"

            if name in df.index:
                row = df.loc[name]
                x, y, w, h = row["x"], row["y"], row["w"], row["h"]
            else:
                # fallback values if name is not found
                x, y, w, h = rect
                df.loc[name] = [x, y, w, h]

            xcenter = x + w / 2 + spacing / 2
            ycenter = y + h / 2 + spacing / 2
            if isinstance(component, gf.ComponentAllAngle):
                d = packed.add_ref_off_grid(component)
            else:
                d = packed << component
            if rotation:
                d.rotate(rotation)
            if h_mirror:
                d.mirror_x()
            if v_mirror:
                d.mirror_y()
            d.center = cast(
                "tuple[float, float]",
                tuple(snap_to_grid((xcenter * precision, ycenter * precision))),
            )
            if add_ports_prefix:
                packed.add_ports(d.ports, prefix=f"{index}_")
            elif add_ports_suffix:
                packed.add_ports(d.ports, suffix=f"_{index}")
            else:
                try:
                    packed.add_ports(d.ports)
                except ValueError:
                    packed.add_ports(d.ports, suffix=f"_{index}")

            index += 1

            if text:
                for text_offset, text_anchor in zip(
                    text_offsets, text_anchors, strict=False
                ):
                    label = packed << text(f"{text_prefix}{index}")
                    if text_mirror:
                        label.dmirror()
                    if text_rotation:
                        label.rotate(text_rotation)
                    label.move(
                        np.array(text_offset) + getattr(d.dsize_info, text_anchor)
                    )
                    component.info["text_label"] = f"{text_prefix}{index}"

        components_packed_list.append(packed)

    if csvpath:
        dirpath = pathlib.Path(csvpath).parent
        dirpath.mkdir(parents=True, exist_ok=True)
        df.to_csv(csvpath, index=True)

    return components_packed_list

grid

pack a list of components into a grid.

Adapted from PHIDL https://github.com/amccaugh/phidl/ by Adam McCaughan

grid

grid(
    components: ComponentSpecs = ("rectangle", "triangle"),
    spacing: Spacing | float = (5.0, 5.0),
    shape: tuple[int, int] | None = None,
    align_x: Literal[
        "origin", "xmin", "xmax", "center"
    ] = "center",
    align_y: Literal[
        "origin", "ymin", "ymax", "center"
    ] = "center",
    rotation: int = 0,
    mirror: bool = False,
    flex: bool = False,
) -> Component

Returns Component with a 1D or 2D grid of components.

Parameters:

Name Type Description Default
components ComponentSpecs

Iterable to be placed onto a grid. (can be 1D or 2D).

('rectangle', 'triangle')
spacing Spacing | float

between adjacent elements on the grid, can be a tuple for different distances in height and width or a single float.

(5.0, 5.0)
shape tuple[int, int] | None

x, y shape of the grid (see np.reshape). If no shape and the list is 1D, if np.reshape were run with (1, -1).

None
align_x Literal['origin', 'xmin', 'xmax', 'center']

x alignment along (origin, xmin, xmax, center).

'center'
align_y Literal['origin', 'ymin', 'ymax', 'center']

y alignment along (origin, ymin, ymax, center).

'center'
rotation int

for each component in degrees.

0
mirror bool

horizontal mirror y axis (x, 1) (1, 0). most common mirror.

False
flex bool

use minimal row height and column width where possible.

False

Returns:

Type Description
Component

Component containing components grid.

Example
import gdsfactory as gf

components = [gf.components.triangle(x=i) for i in range(1, 10)]
c = gf.grid(
components,
shape=(1, len(components)),
rotation=0,
mirror=False,
spacing=(100, 100),
)
c.plot()
Source code in gdsfactory/grid.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def grid(
    components: ComponentSpecs = ("rectangle", "triangle"),
    spacing: Spacing | float = (5.0, 5.0),
    shape: tuple[int, int] | None = None,
    align_x: Literal["origin", "xmin", "xmax", "center"] = "center",
    align_y: Literal["origin", "ymin", "ymax", "center"] = "center",
    rotation: int = 0,
    mirror: bool = False,
    flex: bool = False,
) -> Component:
    """Returns Component with a 1D or 2D grid of components.

    Args:
        components: Iterable to be placed onto a grid. (can be 1D or 2D).
        spacing: between adjacent elements on the grid, can be a tuple for \
                different distances in height and width or a single float.
        shape: x, y shape of the grid (see np.reshape). \
                If no shape and the list is 1D, if np.reshape were run with (1, -1).
        align_x: x alignment along (origin, xmin, xmax, center).
        align_y: y alignment along (origin, ymin, ymax, center).
        rotation: for each component in degrees.
        mirror: horizontal mirror y axis (x, 1) (1, 0). most common mirror.
        flex: use minimal row height and column width where possible.

    Returns:
        Component containing components grid.

    Example:
        ```python
        import gdsfactory as gf

        components = [gf.components.triangle(x=i) for i in range(1, 10)]
        c = gf.grid(
        components,
        shape=(1, len(components)),
        rotation=0,
        mirror=False,
        spacing=(100, 100),
        )
        c.plot()
        ```
    """
    c = gf.Component()
    grid_func = kf.flexgrid if flex else kf.grid
    instances = grid_func(
        c,
        kcells=[gf.get_component(component) for component in components],
        shape=shape,
        spacing=(
            (float(spacing[0]), float(spacing[1]))
            if isinstance(spacing, tuple | list)
            else float(spacing)
        ),
        align_x=align_x,
        align_y=align_y,
        rotation=rotation,
        mirror=mirror,
    )
    for i, instance in enumerate(instances):
        c.add_ports(instance.ports, prefix=f"{i}_")
    return c

grid_with_text

grid_with_text(
    components: Sequence[ComponentSpec] = (
        "rectangle",
        "triangle",
    ),
    text_prefix: str = "",
    text_offsets: Sequence[Float2] | None = None,
    text_anchors: Sequence[Anchor] | None = None,
    text_mirror: bool = False,
    text_rotation: int = 0,
    text: ComponentSpec | None = "text_rectangular",
    spacing: Spacing | float = (5.0, 5.0),
    shape: tuple[int, int] | None = None,
    align_x: Literal[
        "origin", "xmin", "xmax", "center"
    ] = "center",
    align_y: Literal[
        "origin", "ymin", "ymax", "center"
    ] = "center",
    rotation: int = 0,
    mirror: bool = False,
    labels: Sequence[str] | None = None,
    flex: bool = False,
) -> Component

Returns Component with 1D or 2D grid of components with text labels.

Parameters:

Name Type Description Default
components Sequence[ComponentSpec]

Iterable to be placed onto a grid. (can be 1D or 2D).

('rectangle', 'triangle')
text_prefix str

for labels. For example. 'A' will produce 'A1', 'A2', ...

''
text_offsets Sequence[Float2] | None

relative to component anchor. Defaults to center.

None
text_anchors Sequence[Anchor] | None

relative to component (ce cw nc ne nw sc se sw center cc).

None
text_mirror bool

if True mirrors text.

False
text_rotation int

Optional text rotation.

0
text ComponentSpec | None

function to add text labels.

'text_rectangular'
spacing Spacing | float

between adjacent elements on the grid, can be a tuple for different distances in height and width.

(5.0, 5.0)
shape tuple[int, int] | None

x, y shape of the grid (see np.reshape).

None
align_x Literal['origin', 'xmin', 'xmax', 'center']

x alignment along (origin, xmin, xmax, center).

'center'
align_y Literal['origin', 'ymin', 'ymax', 'center']

y alignment along (origin, ymin, ymax, center).

'center'
rotation int

for each component in degrees.

0
mirror bool

horizontal mirror y axis (x, 1) (1, 0). most common mirror.

False
labels Sequence[str] | None

list of labels for each component.

None
flex bool

use minimal row height and column width where possible.

False
Example
import gdsfactory as gf

components = [gf.components.triangle(x=i) for i in range(1, 10)]
c = gf.grid_with_text(
components,
shape=(1, len(components)),
rotation=0,
mirror=False,
spacing=(100, 100),
text_offsets=((0, 100), (0, -100)),
text_anchors=("nc", "sc"),
)
c.plot()
Source code in gdsfactory/grid.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def grid_with_text(
    components: Sequence[ComponentSpec] = ("rectangle", "triangle"),
    text_prefix: str = "",
    text_offsets: Sequence[Float2] | None = None,
    text_anchors: Sequence[Anchor] | None = None,
    text_mirror: bool = False,
    text_rotation: int = 0,
    text: ComponentSpec | None = "text_rectangular",
    spacing: Spacing | float = (5.0, 5.0),
    shape: tuple[int, int] | None = None,
    align_x: Literal["origin", "xmin", "xmax", "center"] = "center",
    align_y: Literal["origin", "ymin", "ymax", "center"] = "center",
    rotation: int = 0,
    mirror: bool = False,
    labels: Sequence[str] | None = None,
    flex: bool = False,
) -> Component:
    """Returns Component with 1D or 2D grid of components with text labels.

    Args:
        components: Iterable to be placed onto a grid. (can be 1D or 2D).
        text_prefix: for labels. For example. 'A' will produce 'A1', 'A2', ...
        text_offsets: relative to component anchor. Defaults to center.
        text_anchors: relative to component (ce cw nc ne nw sc se sw center cc).
        text_mirror: if True mirrors text.
        text_rotation: Optional text rotation.
        text: function to add text labels.
        spacing: between adjacent elements on the grid, can be a tuple for \
                different distances in height and width.
        shape: x, y shape of the grid (see np.reshape).
        align_x: x alignment along (origin, xmin, xmax, center).
        align_y: y alignment along (origin, ymin, ymax, center).
        rotation: for each component in degrees.
        mirror: horizontal mirror y axis (x, 1) (1, 0). most common mirror.
        labels: list of labels for each component.
        flex: use minimal row height and column width where possible.


    Example:
        ```python
        import gdsfactory as gf

        components = [gf.components.triangle(x=i) for i in range(1, 10)]
        c = gf.grid_with_text(
        components,
        shape=(1, len(components)),
        rotation=0,
        mirror=False,
        spacing=(100, 100),
        text_offsets=((0, 100), (0, -100)),
        text_anchors=("nc", "sc"),
        )
        c.plot()
        ```
    """
    component_list = [gf.get_component(component) for component in components]
    text_offsets = text_offsets or ((0, 0),)
    text_anchors = text_anchors or ("center",)
    labels_not_none: list[str | None] = (
        list(labels) if labels else [None] * len(component_list)
    )

    if len(labels_not_none) != len(component_list):
        raise ValueError(
            f"Number of labels {len(labels_not_none)} must match number of components {len(component_list)}"
        )

    c = gf.Component()
    grid_func = kf.flexgrid if flex else kf.grid
    instances = grid_func(
        c,
        kcells=component_list,
        shape=shape,
        spacing=(
            (float(spacing[0]), float(spacing[1]))
            if isinstance(spacing, tuple | list)
            else float(spacing)
        ),
        align_x=align_x,
        align_y=align_y,
        rotation=rotation,
        mirror=mirror,
    )
    for i, instance in enumerate(instances):
        c.add_ports(instance.ports, prefix=f"{i}_")
        text_string = labels_not_none[i] or f"{text_prefix}_{i}"

        if text:
            for text_offset, text_anchor in zip_longest(text_offsets, text_anchors):
                t = c << gf.get_component(text, text=text_string)
                size_info = instance.dsize_info
                text_offset = text_offset or (0, 0)
                text_anchor = text_anchor or "center"
                o = np.array(text_offset)
                d = np.array(getattr(size_info, text_anchor))
                t.move(tuple(o + d))
                if text_mirror:
                    t.dmirror()
                if text_rotation:
                    t.rotate(text_rotation)
    return c

grid_with_text

grid_with_text(
    components: Sequence[ComponentSpec] = (
        "rectangle",
        "triangle",
    ),
    text_prefix: str = "",
    text_offsets: Sequence[Float2] | None = None,
    text_anchors: Sequence[Anchor] | None = None,
    text_mirror: bool = False,
    text_rotation: int = 0,
    text: ComponentSpec | None = "text_rectangular",
    spacing: Spacing | float = (5.0, 5.0),
    shape: tuple[int, int] | None = None,
    align_x: Literal[
        "origin", "xmin", "xmax", "center"
    ] = "center",
    align_y: Literal[
        "origin", "ymin", "ymax", "center"
    ] = "center",
    rotation: int = 0,
    mirror: bool = False,
    labels: Sequence[str] | None = None,
    flex: bool = False,
) -> Component

Returns Component with 1D or 2D grid of components with text labels.

Parameters:

Name Type Description Default
components Sequence[ComponentSpec]

Iterable to be placed onto a grid. (can be 1D or 2D).

('rectangle', 'triangle')
text_prefix str

for labels. For example. 'A' will produce 'A1', 'A2', ...

''
text_offsets Sequence[Float2] | None

relative to component anchor. Defaults to center.

None
text_anchors Sequence[Anchor] | None

relative to component (ce cw nc ne nw sc se sw center cc).

None
text_mirror bool

if True mirrors text.

False
text_rotation int

Optional text rotation.

0
text ComponentSpec | None

function to add text labels.

'text_rectangular'
spacing Spacing | float

between adjacent elements on the grid, can be a tuple for different distances in height and width.

(5.0, 5.0)
shape tuple[int, int] | None

x, y shape of the grid (see np.reshape).

None
align_x Literal['origin', 'xmin', 'xmax', 'center']

x alignment along (origin, xmin, xmax, center).

'center'
align_y Literal['origin', 'ymin', 'ymax', 'center']

y alignment along (origin, ymin, ymax, center).

'center'
rotation int

for each component in degrees.

0
mirror bool

horizontal mirror y axis (x, 1) (1, 0). most common mirror.

False
labels Sequence[str] | None

list of labels for each component.

None
flex bool

use minimal row height and column width where possible.

False
Example
import gdsfactory as gf

components = [gf.components.triangle(x=i) for i in range(1, 10)]
c = gf.grid_with_text(
components,
shape=(1, len(components)),
rotation=0,
mirror=False,
spacing=(100, 100),
text_offsets=((0, 100), (0, -100)),
text_anchors=("nc", "sc"),
)
c.plot()
Source code in gdsfactory/grid.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def grid_with_text(
    components: Sequence[ComponentSpec] = ("rectangle", "triangle"),
    text_prefix: str = "",
    text_offsets: Sequence[Float2] | None = None,
    text_anchors: Sequence[Anchor] | None = None,
    text_mirror: bool = False,
    text_rotation: int = 0,
    text: ComponentSpec | None = "text_rectangular",
    spacing: Spacing | float = (5.0, 5.0),
    shape: tuple[int, int] | None = None,
    align_x: Literal["origin", "xmin", "xmax", "center"] = "center",
    align_y: Literal["origin", "ymin", "ymax", "center"] = "center",
    rotation: int = 0,
    mirror: bool = False,
    labels: Sequence[str] | None = None,
    flex: bool = False,
) -> Component:
    """Returns Component with 1D or 2D grid of components with text labels.

    Args:
        components: Iterable to be placed onto a grid. (can be 1D or 2D).
        text_prefix: for labels. For example. 'A' will produce 'A1', 'A2', ...
        text_offsets: relative to component anchor. Defaults to center.
        text_anchors: relative to component (ce cw nc ne nw sc se sw center cc).
        text_mirror: if True mirrors text.
        text_rotation: Optional text rotation.
        text: function to add text labels.
        spacing: between adjacent elements on the grid, can be a tuple for \
                different distances in height and width.
        shape: x, y shape of the grid (see np.reshape).
        align_x: x alignment along (origin, xmin, xmax, center).
        align_y: y alignment along (origin, ymin, ymax, center).
        rotation: for each component in degrees.
        mirror: horizontal mirror y axis (x, 1) (1, 0). most common mirror.
        labels: list of labels for each component.
        flex: use minimal row height and column width where possible.


    Example:
        ```python
        import gdsfactory as gf

        components = [gf.components.triangle(x=i) for i in range(1, 10)]
        c = gf.grid_with_text(
        components,
        shape=(1, len(components)),
        rotation=0,
        mirror=False,
        spacing=(100, 100),
        text_offsets=((0, 100), (0, -100)),
        text_anchors=("nc", "sc"),
        )
        c.plot()
        ```
    """
    component_list = [gf.get_component(component) for component in components]
    text_offsets = text_offsets or ((0, 0),)
    text_anchors = text_anchors or ("center",)
    labels_not_none: list[str | None] = (
        list(labels) if labels else [None] * len(component_list)
    )

    if len(labels_not_none) != len(component_list):
        raise ValueError(
            f"Number of labels {len(labels_not_none)} must match number of components {len(component_list)}"
        )

    c = gf.Component()
    grid_func = kf.flexgrid if flex else kf.grid
    instances = grid_func(
        c,
        kcells=component_list,
        shape=shape,
        spacing=(
            (float(spacing[0]), float(spacing[1]))
            if isinstance(spacing, tuple | list)
            else float(spacing)
        ),
        align_x=align_x,
        align_y=align_y,
        rotation=rotation,
        mirror=mirror,
    )
    for i, instance in enumerate(instances):
        c.add_ports(instance.ports, prefix=f"{i}_")
        text_string = labels_not_none[i] or f"{text_prefix}_{i}"

        if text:
            for text_offset, text_anchor in zip_longest(text_offsets, text_anchors):
                t = c << gf.get_component(text, text=text_string)
                size_info = instance.dsize_info
                text_offset = text_offset or (0, 0)
                text_anchor = text_anchor or "center"
                o = np.array(text_offset)
                d = np.array(getattr(size_info, text_anchor))
                t.move(tuple(o + d))
                if text_mirror:
                    t.dmirror()
                if text_rotation:
                    t.rotate(text_rotation)
    return c

Netlist

get_netlist

get_netlist(
    cell: ProtoTKCell[Any],
    *,
    on_multi_connect: ErrorBehavior = "error",
    on_dangling_port: ErrorBehavior = "warn",
    instance_namer: InstanceNamer | None = None,
    component_namer: ComponentNamer = function_namer,
    port_matcher: PortMatcher | None = None,
    serialization_max_digits: int = DEFAULT_SERIALIZATION_MAX_DIGITS
) -> dict[str, Any]

Extract netlist from a cell's port connectivity.

Parameters:

Name Type Description Default
cell ProtoTKCell[Any]

The cell to extract the netlist from.

required
on_multi_connect ErrorBehavior

What to do when more than two ports overlap. "ignore": silently allow, "warn": allow with warning, "error": raise.

'error'
on_dangling_port ErrorBehavior

What to do when an instance port is not connected. "ignore": silently allow, "warn": allow with warning, "error": raise.

'warn'
instance_namer InstanceNamer | None

Callable to name instances. Defaults to SmartNamer(component_namer).

None
component_namer ComponentNamer

Callable to name components. Defaults to function_namer.

function_namer
port_matcher PortMatcher | None

Callable to determine if two ports are connected. Defaults to SmartPortMatcher().

None
serialization_max_digits int

How many float digits to preserve. Defaults to DEFAULT_SERIALIZATION_MAX_DIGITS

DEFAULT_SERIALIZATION_MAX_DIGITS

Returns:

Type Description
dict[str, Any]

A dictionary containing instances, placements, ports, and nets.

Source code in gdsfactory/get_netlist.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def get_netlist(
    cell: kf.ProtoTKCell[Any],
    *,
    on_multi_connect: ErrorBehavior = "error",
    on_dangling_port: ErrorBehavior = "warn",
    instance_namer: InstanceNamer | None = None,
    component_namer: ComponentNamer = function_namer,
    port_matcher: PortMatcher | None = None,
    serialization_max_digits: int = DEFAULT_SERIALIZATION_MAX_DIGITS,
) -> dict[str, Any]:
    """Extract netlist from a cell's port connectivity.

    Args:
        cell: The cell to extract the netlist from.
        on_multi_connect: What to do when more than two ports overlap.
            "ignore": silently allow, "warn": allow with warning, "error": raise.
        on_dangling_port: What to do when an instance port is not connected.
            "ignore": silently allow, "warn": allow with warning, "error": raise.
        instance_namer: Callable to name instances.
            Defaults to SmartNamer(component_namer).
        component_namer: Callable to name components.
            Defaults to function_namer.
        port_matcher: Callable to determine if two ports are connected.
            Defaults to SmartPortMatcher().
        serialization_max_digits: How many float digits to preserve.
            Defaults to DEFAULT_SERIALIZATION_MAX_DIGITS

    Returns:
        A dictionary containing instances, placements, ports, and nets.
    """
    recnet: dict[str, dict[str, Any]] = {}
    _insert_netlist(
        recnet,
        cell,
        on_multi_connect,
        on_dangling_port,
        instance_namer or SmartNamer(component_namer),
        component_namer,
        component_namer,  # netlist_namer: use component_namer for non-recursive
        port_matcher or _default_port_matcher,
        recursive=False,
    )
    return cast(
        dict[str, Any],
        clean_value_json(
            recnet[next(iter(recnet))],
            serialization_max_digits=serialization_max_digits,
        ),
    )