Layers
In GDS-based photonic and electronic design, a layer is an integer pair
(layer_number, datatype) that identifies a fabrication process step — for example
waveguide core, metal trace, or doping implant. kfactory provides three abstractions
for working with layers:
| Class | Best for |
|---|---|
LayerInfos |
Defining your process layer palette (the primary approach) |
LayerEnum |
When you need layers to behave as plain integers (KLayout-native style) |
LayerStack |
3-D simulation / cross-section metadata (thickness, material, z-position) |
This page walks through each.
import kfactory as kf
from kfactory.layer import LayerLevel, layerenum_from_dict
LayerInfos — define your layer palette
LayerInfos is a Pydantic model. Subclass it and declare
each layer as a class attribute typed kf.kdb.LayerInfo:
class LAYER(kf.LayerInfos):
WG: kf.kdb.LayerInfo = kf.kdb.LayerInfo(1, 0) # waveguide core
WGEX: kf.kdb.LayerInfo = kf.kdb.LayerInfo(2, 0) # waveguide exclusion zone
CLAD: kf.kdb.LayerInfo = kf.kdb.LayerInfo(4, 0) # cladding
FLOORPLAN: kf.kdb.LayerInfo = kf.kdb.LayerInfo(10, 0)
L = LAYER()
Each field is a kdb.LayerInfo(layer_number, datatype). The field name is
automatically stored as the layer's .name attribute, which is useful for DRC
reports and technology files.
Instantiating the class runs Pydantic validation — it checks that every field is a
kdb.LayerInfo with valid layer and datatype numbers.
print(L.WG) # LayerInfo(1/0) — KLayout's string representation
print(L.WG.layer) # 1
print(L.WG.datatype) # 0
print(L.WG.name) # "WG" — auto-set from the field name
WG (1/0)
1
0
WG
Registering layers with a layout
A KCLayout (the global kf.kcl by default) must know about your layers so that
find_layer and other helpers work correctly. Assign your LayerInfos instance to
kcl.infos:
kf.kcl.infos = L
Looking up the integer layer index
KLayout stores shapes using an integer layer index (not the (layer, datatype) pair
directly). Use kcl.find_layer to convert a LayerInfo to this index:
idx_wg = kf.kcl.find_layer(L.WG)
print(f"WG layer index: {idx_wg}")
# You can also look up by number/datatype directly:
idx_wg2 = kf.kcl.find_layer(1, 0)
print(f"Same index via (layer, datatype): {idx_wg2}")
print(f"Indices match: {idx_wg == idx_wg2}")
WG layer index: WG
Same index via (layer, datatype): WG
Indices match: True
Accessing layers by name
LayerInfos supports dict-style access, which is useful in generic code:
print(L["CLAD"]) # same as L.CLAD
CLAD (4/0)
Iterating over all layers
Because LayerInfos is a Pydantic model, model_fields gives you the declared
layers and model_dump() serialises them:
for name in L.model_fields:
li = getattr(L, name)
print(f" {name:12s} layer={li.layer} datatype={li.datatype}")
WG layer=1 datatype=0
WGEX layer=2 datatype=0
CLAD layer=4 datatype=0
FLOORPLAN layer=10 datatype=0
/tmp/ipykernel_4136/971822332.py:1: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.
for name in L.model_fields:
LayerEnum — integer-style layer access
LayerEnum is an alternative that maps layer names to KLayout layer indices
(integers). It is useful when interfacing with older KLayout APIs that expect an
integer directly, or when you want to use a layer as a dict key with O(1) lookup.
Use layerenum_from_dict to convert a LayerInfos into a LayerEnum:
LE = layerenum_from_dict(L)
print(type(LE.WG)) # <enum 'LAYER'>
print(int(LE.WG)) # integer layer index in kf.kcl.layout
print(LE.WG.layer) # 1 — original layer number
print(LE.WG.datatype) # 0 — original datatype
print(LE.WG[0], LE.WG[1]) # tuple-style access: (layer, datatype)
<aenum 'LAYER'>
0
1
0
1 0
Both LayerInfos and LayerEnum are valid everywhere kfactory expects a layer —
kf.kcl.find_layer accepts a LayerInfo, a LayerEnum, or an (int, int) tuple.
# LayerInfos → find_layer gives the integer index:
print(kf.kcl.find_layer(L.WG)) # from LayerInfos
print(kf.kcl.find_layer(1, 0)) # from (layer, datatype) pair
# LayerEnum members *are* layer indices already:
print(int(LE.WG)) # same integer, no find_layer needed
WG
WG
0
LayerStack — 3-D process metadata
LayerStack stores per-layer physical properties needed for 3-D simulation, cross-
section rendering, or fabrication export. Each entry is a LayerLevel:
stack = kf.LayerStack(
wg_core=LayerLevel(
layer=L.WG,
zmin=0.0,
thickness=0.22,
material="Si",
sidewall_angle=85.0,
),
cladding=LayerLevel(
layer=L.CLAD,
zmin=-3.0,
thickness=3.22,
material="SiO2",
),
)
LayerLevel fields:
| Field | Type | Meaning |
|---|---|---|
layer |
(int, int) or kdb.LayerInfo |
GDS layer |
zmin |
float µm | Bottom of the material |
thickness |
float µm | Material thickness |
material |
str | None | Material name (for simulation) |
sidewall_angle |
float degrees | Etch sidewall angle (90° = vertical) |
info |
Info |
Free-form simulation metadata |
# Access individual levels by attribute or dict key
print(stack["wg_core"].thickness) # 0.22
print(stack.cladding.material) # SiO2
# Convenience helpers for simulation
print(stack.get_layer_to_thickness()) # {(1,0): 0.22, (4,0): 3.22}
print(stack.get_layer_to_material()) # {(1,0): 'Si', (4,0): 'SiO2'}
0.22
SiO2
{(1, 0): 0.22, (4, 0): 3.22}
{(1, 0): 'Si', (4, 0): 'SiO2'}
Putting it all together: a minimal PDK layer set
A typical PDK definition combines LayerInfos and LayerStack in one module:
class PDK_LAYER(kf.LayerInfos):
WG: kf.kdb.LayerInfo = kf.kdb.LayerInfo(1, 0)
WG_TRENCH: kf.kdb.LayerInfo = kf.kdb.LayerInfo(2, 0)
METAL1: kf.kdb.LayerInfo = kf.kdb.LayerInfo(11, 0)
METAL2: kf.kdb.LayerInfo = kf.kdb.LayerInfo(12, 0)
FLOORPLAN: kf.kdb.LayerInfo = kf.kdb.LayerInfo(99, 0)
pdk_layers = PDK_LAYER()
pdk_stack = kf.LayerStack(
wg=LayerLevel(layer=pdk_layers.WG, zmin=0.0, thickness=0.22, material="Si"),
m1=LayerLevel(layer=pdk_layers.METAL1, zmin=0.5, thickness=0.5, material="Al"),
m2=LayerLevel(layer=pdk_layers.METAL2, zmin=1.2, thickness=0.5, material="Al"),
)
print("Layer palette:")
for name in pdk_layers.model_fields:
li = getattr(pdk_layers, name)
print(f" {name:12s} ({li.layer}/{li.datatype})")
print("\n3-D stack:")
for name, level in pdk_stack.layers.items():
print(
f" {name:6s} z={level.zmin:.1f}…{level.zmin + level.thickness:.2f} µm {level.material}"
)
Layer palette:
WG (1/0)
WG_TRENCH (2/0)
METAL1 (11/0)
METAL2 (12/0)
FLOORPLAN (99/0)
3-D stack:
wg z=0.0…0.22 µm Si
m1 z=0.5…1.00 µm Al
m2 z=1.2…1.70 µm Al
/tmp/ipykernel_4136/3302201102.py:18: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.
for name in pdk_layers.model_fields:
See Also
| Topic | Where |
|---|---|
| KCLayout — the layout registry that owns layers | Core Concepts: KCLayout |
| Cross-sections built on top of layers | Cross-Sections |
| LayerLevel and full 3-D stack in a PDK | PDK: Technology & Layer Stack |
| Assembling a full PDK with layers | PDK: Creating a PDK |