Python Library

KLayout Integration

Wrap Laylight devices as KLayout PCells, register a library, and instantiate the result inside a KLayout layout flow.

This guide shows how to take a laylight device, expose it as a KLayout PCell, register it inside a kdb.Library, and instantiate it from a normal KLayout layout flow.

It focuses on the workflow, not the implementation details. In short: when KLayout asks for a PCell variant, the device is built in a laylight Layout, compiled to GDSII by the laylight command line, and the result is copied into the variant's cell, without the marker texts laylight writes for ports.

What you can do

With the current integration, you can:

  • wrap an AbstractDevice subclass as a KLayout PCell
  • register one or more wrapped devices in a kdb.Library
  • instantiate those PCells from another layout using KLayout's external-library PCell flow
  • validate the result with the repository tests

Prerequisites

This guide assumes:

  • you are working inside this repository
  • you already know the basics of klayout.db
  • laylight with its KLayout extra is available in your Python environment: pip install "laylight[klayout]"

KLayout objects stay on the KLayout side: the target layout is a kdb.Layout and instances use kdb.DCplxTrans. The laylight.Layout and laylight.Trans types belong to laylight's own layouts.

Step 1: Wrap a Laylight device as a PCell

Use register_abstract_device_pcell(...) to expose a laylight device class as a KLayout PCell.

The most common case is a device with mostly simple scalar parameters. For example, WGStraight can be wrapped like this:

from klayout import db as kdb

from laylight import PCellParameterSpec, register_abstract_device_pcell
from laylight.structure.wg import WGStraight


layout = kdb.Layout()
layout.dbu = 0.001

pcell_id = register_abstract_device_pcell(
    layout,
    "WGStraightPCell",
    WGStraight,
    parameter_specs={
        "length": PCellParameterSpec(default=20.0, description="Length"),
    },
)

Why does length need PCellParameterSpec here?

Because WGStraight.length defaults to None, and the helper only exposes parameters that have a concrete PCell default value. That means:

  • layout and tech are never exposed as PCell parameters
  • default=None parameters are skipped unless you explicitly provide a default
  • common scalar parameters such as float, int, bool, str, and LayerInfo can be mapped automatically

If you want a single-file smoke test for this step, run:

uv run --package laylight python -m pytest -q --import-mode=importlib pypkg/laylight/tests/test_pcell.py

Step 2: Register a KLayout library

Once you can register one PCell into a Layout, the next step is to register a kdb.Library.

This is the minimal pattern:

from klayout import db as kdb

from laylight import PCellParameterSpec, register_abstract_device_pcell
from laylight.structure.ring import Ring
from laylight.structure.wg import WGStraight


class DemoLibrary(kdb.Library):
    def __init__(self) -> None:
        super().__init__()

        layout = self.layout()

        register_abstract_device_pcell(
            layout,
            "WGStraightPCell",
            WGStraight,
            parameter_specs={
                "length": PCellParameterSpec(default=40.0),
            },
        )
        register_abstract_device_pcell(
            layout,
            "RingPCell",
            Ring,
            parameter_specs={
                "width": PCellParameterSpec(default=0.5),
            },
        )

        self.description = "Demo laylight PCell library"
        self.register("Laylight Demo Library")


library = DemoLibrary()

After self.register(...) runs, the library is available through KLayout's library registry.

Step 3: Instantiate the PCell from a normal KLayout flow

The wrapped PCell is used through KLayout's external-library PCell API.

This is the flow:

  1. Look up the registered library
  2. Resolve the PCell ID from the library layout
  3. Create a variant in a target layout
  4. Insert that variant into a top cell

Example:

from klayout import db as kdb


registered_library = kdb.Library.library_by_name("Laylight Demo Library", "*")
assert registered_library is not None

target_layout = kdb.Layout()
target_layout.dbu = 0.001
top = target_layout.create_cell("TOP")

pcell_id = registered_library.layout().pcell_id("WGStraightPCell")
variant_index = target_layout.add_pcell_variant(
    registered_library,
    pcell_id,
    {
        "length": 60.0,
        "width": 0.8,
        "slab_enable": True,
    },
)

variant = target_layout.cell(variant_index)
top.insert(kdb.DCellInstArray(variant.cell_index(), kdb.DCplxTrans()))

target_layout.write("demo_library_usage.gds")

At this point:

  • variant is the external-library proxy cell
  • the underlying Laylight geometry is still generated by the wrapped device
  • the resulting layout can be written to GDS or used in a larger assembly flow

Step 4: Run the repository example in KLayout Macro Editor

The repository includes a ready-to-run script for KLayout Macro Editor:

pypkg/laylight-examples/laylight_examples/klayout_library.py

That script:

  • bootstraps pypkg/laylight into PYTHONPATH when needed
  • selects a seeded subset of AbstractDevice classes
  • wraps them with register_abstract_device_pcell(...)
  • registers a test library named Laylight Test Library

Try it in Macro Editor

  1. Open KLayout
  2. Open Macro Editor
  3. Open pypkg/laylight-examples/laylight_examples/klayout_library.py
  4. Run the script

If registration succeeds, the script prints the registered library name and the selected PCells.

If you prefer to run it from the command line first:

uv run --package laylight-examples python pypkg/laylight-examples/laylight_examples/klayout_library.py

Step 5: Verify the geometry

There are two repository-level validation paths for this integration.

Validate the helper itself

This checks parameter exposure and single-PCell variant generation:

uv run --package laylight python -m pytest -q --import-mode=importlib pypkg/laylight/tests/test_pcell.py

Validate the library round trip

This checks the larger flow:

  • register a library
  • instantiate external-library PCell variants in a target layout
  • compare the flattened geometry with a GDS baseline

Run:

uv run --package laylight python -m pytest -q --import-mode=importlib pypkg/laylight/tests/test_pcell_library.py

The committed reference image for that test is:

pypkg/laylight/tests/baseline/pcell_library_shape.gds

If this test passes, the current repository has verified:

  • library registration works
  • external-library PCell instantiation works
  • the generated layout still matches the expected geometry

Complete example

The following script puts the full workflow in one place:

from klayout import db as kdb

from laylight import PCellParameterSpec, register_abstract_device_pcell
from laylight.structure.wg import WGStraight


class WaveguideLibrary(kdb.Library):
    def __init__(self) -> None:
        super().__init__()
        self.description = "Waveguide-only laylight library"

        register_abstract_device_pcell(
            self.layout(),
            "WGStraightPCell",
            WGStraight,
            parameter_specs={
                "length": PCellParameterSpec(default=20.0),
            },
        )

        self.register("Waveguide Library")


_library = WaveguideLibrary()

registered_library = kdb.Library.library_by_name("Waveguide Library", "*")
assert registered_library is not None

layout = kdb.Layout()
layout.dbu = 0.001
top = layout.create_cell("TOP")

pcell_id = registered_library.layout().pcell_id("WGStraightPCell")
variant_index = layout.add_pcell_variant(
    registered_library,
    pcell_id,
    {
        "length": 80.0,
        "width": 0.6,
        "slab_enable": False,
    },
)

variant = layout.cell(variant_index)
top.insert(kdb.DCellInstArray(variant.cell_index(), kdb.DCplxTrans()))

layout.write("waveguide_library_demo.gds")

Common limits

This integration is intentionally narrow. Keep these limits in mind:

  • parameters with default=None are not exposed automatically
  • object-valued or custom parameters may need an explicit PCellParameterSpec, or may need to stay out of the PCell interface entirely
  • this guide covers Python-driven KLayout flows, not a full KLayout GUI tutorial

If you want a more complex example, start from:

  • pypkg/laylight-examples/laylight_examples/klayout_library.py
  • pypkg/laylight/tests/test_pcell.py
  • pypkg/laylight/tests/test_pcell_library.py

On this page