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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
lumicron_api-0.5.1.tar.gz
(1.7 MB
view details)
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
|