Metadata-Version: 2.4
Name: lumicron_api
Version: 0.1.0
Summary: Layout scripting for photonic and electronic chip design
Author: Elyon Semiconductor
License-Expression: MIT
Project-URL: Homepage, https://pypi.org/project/lumicron-api/
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: gdstk>=0.9
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Dynamic: license-file

# 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

```bash
pip install lumicron_api
```

Requires Python 3.12+.

## Quick Start

```python
import lumicron_api.all as lm
import lumicron_api.pdks.elyon_demo.all as pdk

@lm.cell_params
def MZI(arm_length: float = 100, arm_spacing: float = 10):
    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["o1"], radius=10)
    c.Route(s.port["o2"], k.port["o2"], radius=10)

    return c

mzi = MZI()
mzi.to_layout().to_gds("mzi.gds")
```

## Features

- **Hierarchical cells** — build complex designs from reusable parametric components
- **Placement system** — anchor-based positioning with directional helpers (`.above()`, `.below()`, `.right_of()`)
- **Routing** — auto-router with manual override via `.go()`, bend radius validation, tapers with positional placement
- **Bend types** — circular (default), euler (clothoid), and custom bend PCells with adjustable `p` parameter
- **Custom bend PCells** — use your own 90° bend cells in routes: `c.Route(a, b, bend=my_bend)`
- **Waveguide** — standalone path primitive for spirals, delay lines, and tapered structures
- **Arrays** — 1D and 2D arrays with per-element rotation
- **Port promotion** — access deeply nested ports from any level in the hierarchy
- **Port types** — optical and electrical ports for future auto layer transitions
- **Shapes** — Rectangle, Circle, Ring, Oval, Arc, Triangle, Parallelogram, Trapezoid
- **PDK support** — ships with Elyon Demo PDK (17 layers, 11 components, 3 route profiles)
- **Export** — GDS-II, OASIS, and JSON formats

## Placement

```python
# 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

```python
# 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

# 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)

# 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.RPROF.siln_strip)
```

## Waveguide

```python
# 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,
)

ref = c.add(wg)
c.Route(some_port, ref.port["o1"], radius=10)
```

## Arrays

```python
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

```python
@lm.cell_params
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"
```

## Documentation

See [`docs/user_guide.md`](docs/user_guide.md) for the full API reference.

## License

MIT
