Layout scripting for photonic and electronic chip design
Project description
Lumicron API
Layout scripting for photonic and electronic chip design.
Define cells, place components, route waveguides, and export to GDS/OASIS — all in Python.
Install
pip install lumicron_api
Requires Python 3.12+.
Documentation
The User Guide and PDK Authoring Guide PDFs are bundled inside the wheel — no internet required after install.
lumicron-docs # opens the User Guide
lumicron-docs --pdk # opens the PDK Authoring Guide
lumicron-docs --where # prints both filesystem paths
From Python:
import lumicron_api as lm
lm.open_user_guide() # opens the User Guide in your default PDF viewer
lm.user_guide_path() # → Path to the bundled PDF
Quick Start
import lumicron_api as lm
import lumicron_api.pdks.elyon_demo.all as pdk
@lm.pcell
def MZI(arm_length: float = 300,
delay_y: float = 50):
c = lm.CELL("MZI")
# Components
splitter = pdk.YSplitter(layer=pdk.LAYER.SILC)
combiner = pdk.YSplitter(layer=pdk.LAYER.SILC)
# Add and place
s = c.add(splitter)
k = c.add(combiner)
c.Place(s).at((0, 0))
c.Place(k).using("i1").at((arm_length + 50, 0)).rotate_by(180)
# Route
c.Route(s.port["o1"], k.port["o2"], radius=10)
c.Route(s.port["o2"], k.port["o1"], radius=10).jog("S", by=delay_y)
return c
if __name__ == "__main__":
MZI().to_layout().to_gds("mzi.gds")
Features
- Hierarchical Cells — build complex designs from reusable parametric components
- Fluid Chain-based Syntax - chain verbs and descriptors to your place and route commands using intuitive commands to "speak" your layout into existence.
c.Place(mmi).using("in1").relative_to(heater.port["in"]).move_by(dx=-50, dy=-50).rotate_by(90) - Intuitive Placement System - anchor-based positioning with directional helpers (
.above(),.below(),.right_of(),.left_of()) and.move_by()offset helper - Smart Routing — auto-router that automatically selects route style based on port position, overridable by
style="manhattan","sbend", or"direct". Automatically validates bend radius and automatically tapers routes with flexible positional placement - Robust routing — routing supported for ports at arbitrary angles. Contains intuitive route management via
.go()commands:c.Route(coupler.port["out"], mod_array.port["m0_in"], bend_radius=25).go("N", to=mod_array.S, dy=-100).go("W", to=mod_array.port["m0_in"]). Allows custom bend and taper PCell to be auto-placed into routes - Route shaping —
.jog(direction, by)inserts a U-shaped manhattan detour at the route's bend radius for delay lines, MZI arms, and pad fanout - Bend types — circular (default), euler (clothoid), and custom bend PCells with adjustable
pparameter - Bundle routing —
c.RouteBundle(ports_a, ports_b, spacing=10)routes multiple port pairs with a single call. Chainable.go()steers the whole bundle through 90° turns with pitch preserved; pitch-matching fanout is auto-inserted..match_lengths()matches lengths in a bundle automatically - Layout-aware routing —
c.Route(a, b, clearance=5)auto-detours routes around placed cell bounding boxes, bend-radius-aware with path simplification - Waveguide — standalone path primitive for spirals, delay lines, and tapered structures with custom bend PCell support
- Arrays — 1D and 2D arrays with per-element rotation
- Port promotion — access deeply nested ports from any level in the hierarchy
- Shapes — Rectangle, Circle, Ring, Oval, Arc, Triangle, Parallelogram, Trapezoid
- PDK support — ships with Elyon Demo PDK (17 layers, 11 components, 3 route profiles)
- Export — GDSII, OASIS with auto-filename from cell name
Placement
# Absolute
c.Place(shape).at((100, 200))
# Relative
c.Place(pad).right_of(coupler)
c.Place(stalk).below(segment).move_by(dy=5)
# Anchor + rotate
c.Place(splitter).using("i1").at(frame.NW).rotate_by(90)
Routing
# Auto-route with circular bends
c.Route(port_a, port_b, radius=10)
# Euler bends (smooth curvature transition)
c.Route(port_a, port_b, radius=10, bend="euler")
c.Route(port_a, port_b, radius=10, bend="euler", p=0.3) # adjust euler fraction
# Force a routing style
c.Route(port_a, port_b, radius=10, style="manhattan")
c.Route(port_a, port_b, radius=10, style="sbend")
c.Route(port_a, port_b, radius=10, style="direct")
# Custom bend PCell (user-defined 90° bend geometry)
c.Route(port_a, port_b, bend=my_euler_bend_cell)
# Manual with GPS-style directions
c.Route(port_a, port_b, radius=15) \
.go("E", by=50) \
.go("N", to=port_b, dy=-20)
# Route jog — U-shaped detour for delay lines, MZI arms, pad fanout
c.Route(port_a, port_b, radius=10).jog("S", by=50)
# Positional taper — place taper 50µm from port A
c.Route(port_a, port_b, radius=10).start_taper(length=10, at=50)
# With route profile
c.Route(port_a, port_b, profile=pdk.RP.siln_strip)
# Layout-aware routing — detour around placed cells
c.Route(port_a, port_b, radius=10, clearance=5)
# Control distance from port to first/last bend (direct paths)
c.Route(port_a, port_b, radius=10, start_straight=20, end_straight=15)
# Bundle routing — route multiple port pairs at once
c.RouteBundle(
ports_a=[ref_a.port["o1"], ref_a.port["o2"]],
ports_b=[ref_b.port["i1"], ref_b.port["i2"]],
spacing=10, radius=10,
)
# Manhattan bundle — steer the whole bus with .go(), pitch preserved
# through each corner. Pitch-matching fanout is auto-inserted.
(c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20)
.go("E", by=150).go("N", by=80).go("E", by=150))
# Length-matched bundle
(c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20)
.go("E", by=200).go("N", by=100).go("E", by=300)
.match_lengths())
c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20,
length_match=True).go("E", by=500)
Waveguide
# Standalone path from points — auto-generates i1/o1 ports
wg = lm.Waveguide(
points=[(0, 0), (50, 0), (50, 100), (100, 100)],
layer=pdk.LAYER.SILC, width=0.5, radius=10,
)
# Tapered waveguide (width interpolates linearly)
wg = lm.Waveguide(
points=spiral_points,
layer=pdk.LAYER.SILC, width=0.2, width_end=1.0, radius=10,
)
# Custom bend PCell in waveguide
wg = lm.Waveguide(
points=[(0, 0), (50, 0), (50, 50)],
layer=pdk.LAYER.SILC, width=0.5, bend=my_bend_cell,
)
ref = c.add(wg)
c.Route(some_port, ref.port["o1"], radius=10)
Arrays
couplers = lm.ARRAY(coupler, count=8, pitch=(0, 127), rotation=90)
arr = c.add(couplers)
c.Place(arr).at((0, 0))
# Access individual element ports
arr[0].port["o1"]
arr[3].C
Parametric Cells
@lm.pcell
def Ring(radius: float = 50, width: float = 5):
c = lm.CELL("Ring")
c.add(lm.Ring(outer_radius=radius, width=width, layer=pdk.LAYER.SILC))
return c
Ring() # cell name: "Ring"
Ring(radius=100) # cell name: "Ring_radius=100"
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lumicron_api-0.5.1.tar.gz.
File metadata
- Download URL: lumicron_api-0.5.1.tar.gz
- Upload date:
- Size: 1.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
592f04cf2dce9703b51ded89047e7e581b66fd6b5c47a2123de7d5c394ebc6f2
|
|
| MD5 |
be5ff024562b7b3147473a164f1a5a1f
|
|
| BLAKE2b-256 |
f6568efdfa08dbb2b747ad827f3747d599545d67560799196d8a437c186f465f
|
File details
Details for the file lumicron_api-0.5.1-py3-none-any.whl.
File metadata
- Download URL: lumicron_api-0.5.1-py3-none-any.whl
- Upload date:
- Size: 1.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
902f52edce5335f71eecf74d89681c76aa30c1d62f8e1260bc7f72c8db6fabb0
|
|
| MD5 |
74d97d8ab815892ea9ce141cee7e9bfd
|
|
| BLAKE2b-256 |
abb79b9abb21ae00d91805e86e815de8a9850236e009e00c69e724a3c47507de
|