├── img
└── flayout_gf_mzi.png
├── docs
├── assets
│ └── images
│ │ ├── favicon.ico
│ │ ├── company_logo.png
│ │ └── favicon.svg
├── Gemfile
├── _data
│ └── topnav.yml
├── sidebar.json
├── _config.yml
├── _includes
│ └── head.html
└── index.html
├── Dockerfile
├── .bumpversion.cfg
├── pyproject.toml
├── environment.yml
├── .pre-commit-config.yaml
├── settings.ini
├── flayout
├── __init__.py
├── io.py
├── example_lib.py
├── gui.py
├── notebook.py
├── _nbdev.py
├── factories.py
├── pcell.py
├── cell.py
└── bokeh.py
├── setup.cfg
├── Makefile
├── .gitignore
├── source
├── 04_io.ipynb
├── 92_notebook.ipynb
├── 99_example_lib.ipynb
├── 50_gui.ipynb
├── 03_pcell.ipynb
├── 01_factories.ipynb
├── 02_cell.ipynb
└── 91_bokeh.ipynb
├── README.md
├── .github
└── workflows
│ └── main.yml
└── index.ipynb
/img/flayout_gf_mzi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaport/flayout/HEAD/img/flayout_gf_mzi.png
--------------------------------------------------------------------------------
/docs/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaport/flayout/HEAD/docs/assets/images/favicon.ico
--------------------------------------------------------------------------------
/docs/assets/images/company_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flaport/flayout/HEAD/docs/assets/images/company_logo.png
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM condaforge/mambaforge
2 | ADD . /flayout
3 | RUN pip install /flayout[dev]
4 | RUN pip install gdsfactory
5 |
--------------------------------------------------------------------------------
/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | gem 'github-pages', group: :jekyll_plugins
3 | gem "jekyll", ">= 3.7"
4 | gem "kramdown", ">= 2.3.1"
5 | gem "jekyll-remote-theme"
6 |
--------------------------------------------------------------------------------
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 0.0.1
3 | commit = True
4 | tag = True
5 | tag_name = {new_version}
6 |
7 | [bumpversion:file:setup.cfg]
8 |
9 | [bumpversion:file:settings.ini]
10 |
--------------------------------------------------------------------------------
/docs/_data/topnav.yml:
--------------------------------------------------------------------------------
1 | topnav:
2 | - title: Topnav
3 | items:
4 | - title: github
5 | external_url: https://github.com/flaport/flayout/
6 |
7 | #Topnav dropdowns
8 | topnav_dropdowns:
9 | - title: Topnav dropdowns
10 | folders:
11 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "pip", "build", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [tool.black]
6 | line-length = 88
7 | target-version = ['py38']
8 | include = '\.pyi?$'
9 |
10 | [tool.pyright]
11 | reportPrivateImportUsage = false
12 |
--------------------------------------------------------------------------------
/docs/sidebar.json:
--------------------------------------------------------------------------------
1 | {
2 | "FLayout": {
3 | "Overview": "index",
4 | "Factories": "factories",
5 | "Cell Utilities": "cell",
6 | "PCell Decorator": "pcell",
7 | "Read & Write": "io"
8 | },
9 | "Extensions": {
10 | "Bokeh Visualization": "bokeh",
11 | "Notebook": "notebook"
12 | },
13 | "Libraries": {
14 | "Example Library": "example_lib"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: fl
2 | channels:
3 | - conda-forge
4 | - fastai
5 | - flaport
6 | dependencies:
7 | - build
8 | - python=3.8
9 | - pip
10 | - klayout
11 | - klayout-gui
12 | - black
13 | - bokeh
14 | - bump2version
15 | - ipykernel
16 | - isort
17 | - lxml
18 | - nbdev<2
19 | - nbstripout
20 | - numpy
21 | - papermill
22 | - pre-commit
23 | - pytest
24 | - pyyaml
25 | - tqdm
26 | - gdsfactory # optional dependency
27 | prefix: /home/flaport/.anaconda/envs/mb
28 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: local
3 | hooks:
4 | - id: nbdev_clean_nbs
5 | name: nbdev_clean_nbs
6 | entry: nbdev_clean_nbs
7 | language: python
8 | pass_filenames: false
9 | additional_dependencies:
10 | - nbdev
11 | - repo: local
12 | hooks:
13 | - id: nbstripout
14 | name: nbstripout
15 | entry: nbstripout
16 | language: python
17 | pass_filenames: true
18 | types: [jupyter]
19 | - repo: local
20 | hooks:
21 | - id: nbdev_diff_nbs
22 | name: nbdev_diff_nbs
23 | entry: bash -c 'if [ ! -z "$(nbdev_diff_nbs)" ]; then nbdev_build_lib; fi'
24 | language: python
25 | pass_filenames: false
26 | additional_dependencies:
27 | - nbdev
28 |
--------------------------------------------------------------------------------
/settings.ini:
--------------------------------------------------------------------------------
1 | [DEFAULT]
2 | audience = Developers
3 | author = Floris Laporte
4 | author_email = floris.laporte@gmail.com
5 | branch = master
6 | copyright = Floris Laporte
7 | custom_sidebar = True
8 | description = FLayout
9 | doc_baseurl = /flayout/
10 | doc_host = https://flaport.github.io/flaport
11 | doc_path = docs
12 | git_url = https://github.com/flaport/flayout/tree/master/
13 | host = github
14 | jekyll_styles = note,warning,tip,important
15 | keywords = flayout
16 | language = English
17 | lib_name = flayout
18 | lib_path = flayout
19 | license = GPLv3
20 | min_python = 3.9
21 | monospace_docstrings = True
22 | nbs_path = .
23 | recursive = True
24 | repo_name = flayout
25 | status = 1
26 | title = FLayout
27 | user = flaport
28 | version = 0.0.1
29 |
--------------------------------------------------------------------------------
/flayout/__init__.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: index.ipynb (unless otherwise specified).
2 |
3 | __all__ = []
4 |
5 | # Internal Cell
6 | __version__ = "0.0.0"
7 | __author__ = "Floris Laporte"
8 |
9 | from .cell import copy_tree as copy_tree
10 | from .cell import reference as reference
11 | from .factories import box as box
12 | from .factories import cell as cell
13 | from .factories import layer as layer
14 | from .factories import layout as layout
15 | from .factories import library as library
16 | from .factories import path as path
17 | from .factories import point as point
18 | from .factories import polygon as polygon
19 | from .factories import transform as transform
20 | from .factories import vector as vector
21 | from .io import read_gds as read_gds
22 | from .io import save_gds as save_gds
23 | from .pcell import pcell as pcell
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = flayout
3 | version = 0.0.1
4 | description = Thin functional/declarative wrapper around the KLayout Python API
5 | author = Floris Laporte
6 | author_email = floris.laporte@gmail.com
7 | long_description = file: README.md
8 | long_description_content_type = text/markdown
9 | url = https://github.com/flaport/flayout
10 | classifiers =
11 | Development Status :: 3 - Alpha
12 | License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13 | Operating System :: OS Independent
14 | Programming Language :: Python :: 3
15 | Topic :: Scientific/Engineering
16 |
17 | [options]
18 | packages =
19 | flayout
20 | install_requires =
21 | klayout
22 |
23 | [options.extras_require]
24 | dev =
25 | black
26 | bokeh
27 | bump2version
28 | ipykernel
29 | isort
30 | lxml
31 | nbdev
32 | nbstripout
33 | numpy
34 | papermill
35 | pre-commit
36 | pytest
37 | pyyaml
38 | tqdm
39 |
40 | [options.package_data]
41 | * =
42 | README.md
43 | LICENSE
44 | flayout =
45 | layers.lyp
46 |
--------------------------------------------------------------------------------
/flayout/io.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/04_io.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['read_gds', 'save_gds']
4 |
5 | # Internal Cell
6 | import os
7 |
8 | import pya
9 |
10 | # Cell
11 | def read_gds(fp: str) -> pya.Layout:
12 | """load a layout
13 |
14 | Args:
15 | fp: the path to the layout to load
16 |
17 | Returns:
18 | the loaded layout
19 | """
20 | if isinstance(fp, str):
21 | fp = os.path.abspath(os.path.expanduser(fp))
22 | layout = pya.Layout()
23 | layout.read(fp)
24 | return layout
25 |
26 | # Cell
27 | def save_gds(layout: pya.Layout, fp: str):
28 | """save a layout
29 |
30 | Args:
31 | layout: the layout to save
32 | fp: the path where to save the layout
33 | """
34 | if isinstance(fp, str):
35 | fp = os.path.abspath(os.path.expanduser(fp))
36 | if isinstance(layout, pya.Layout):
37 | layout.write(fp)
38 | else:
39 | raise ValueError(f"Don't know how to save type {layout.__class__.__name__}.")
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | repository: https://github.com/flaport/flayout
2 | output: web
3 | topnav_title: FLayout
4 | site_title: FLayout
5 | company_name: Floris Laporte
6 | description: FLayout
7 | use_math: true
8 | google_analytics:
9 | google_search:
10 | host: 127.0.0.1
11 | port: 4000
12 |
13 | exclude:
14 | - .idea/
15 | - .vscode/
16 | - .gitignore
17 | - vendor
18 |
19 | highlighter: rouge
20 | markdown: kramdown
21 | kramdown:
22 | input: GFM
23 | auto_ids: true
24 | hard_wrap: false
25 | syntax_highlighter: rouge
26 |
27 | collections:
28 | tooltips:
29 | output: false
30 |
31 | defaults:
32 | -
33 | scope:
34 | path: ""
35 | type: "pages"
36 | values:
37 | layout: "page"
38 | comments: true
39 | search: true
40 | sidebar: home_sidebar
41 | topnav: topnav
42 | -
43 | scope:
44 | path: ""
45 | type: "tooltips"
46 | values:
47 | layout: "page"
48 | comments: true
49 | search: true
50 | tooltip: true
51 |
52 | sidebars:
53 | - home_sidebar
54 |
55 | plugins:
56 | - jekyll-remote-theme
57 |
58 | remote_theme: fastai/nbdev-jekyll-theme
59 | baseurl: /flayout/
60 |
--------------------------------------------------------------------------------
/flayout/example_lib.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/99_example_lib.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['rectangle', 'square', 'gf_pcells', 'example_lib']
4 |
5 | # Internal Cell
6 | import pya
7 | import flayout as fl
8 |
9 | # Cell
10 | @fl.pcell
11 | def rectangle(
12 | name="rectangle",
13 | width: float = 1.0,
14 | height: float = 0.5,
15 | layer: pya.LayerInfo = pya.LayerInfo(1, 0),
16 | ):
17 | rect = fl.polygon(hull=[(-width/2, -height/2), (width/2, -height/2), (width/2, height/2), (-width/2, height/2)])
18 | cell = fl.cell(name, shapes={layer: [rect]})
19 | return cell
20 |
21 | # Cell
22 | @fl.pcell
23 | def square(
24 | name="square",
25 | width: float = 0.5,
26 | layer: pya.LayerInfo = pya.LayerInfo(1, 0),
27 | ):
28 | square = fl.polygon(hull=[(-width/2, -width/2), (width/2, -width/2), (width/2, width/2), (-width/2, width/2)])
29 | cell = fl.cell(name, shapes={layer: [square]})
30 | return cell
31 |
32 | # Cell
33 | gf_pcells = []
34 |
35 | import flayout as fl
36 | import gdsfactory.components as gfc
37 |
38 | gf_pcells += [
39 | mzi := fl.pcell(gfc.mzi, on_error="ignore"),
40 | bend_euler := fl.pcell(gfc.bend_euler, on_error="ignore")
41 | ]
42 |
43 | # Cell
44 | example_lib = fl.library(
45 | "F. E. L.",
46 | pcells=[rectangle, square, *gf_pcells],
47 | cells=[],
48 | description="FLayout Example Library",
49 | )
--------------------------------------------------------------------------------
/flayout/gui.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/50_gui.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['instance', 'example_lib']
4 |
5 | # Internal Cell
6 | import pya
7 | import flayout as fl
8 | import numpy as np
9 |
10 | # Cell
11 | @fl.pcell
12 | def instance(
13 | name="instance",
14 | width: float = 1.0,
15 | height: float = 1.0,
16 | num_inputs: int = 3,
17 | num_outputs: int = 1,
18 | port_size=0.1,
19 | ):
20 | if (width < 2 * port_size) or (height < 2 * port_size):
21 | raise ValueError(f"width/height should be bigger than port size. Got: {width:.2f} < {port_size:.2f}")
22 |
23 | rect = np.array([(-width/2, -height/2), (width/2, -height/2), (width/2, height/2), (-width/2, height/2)])
24 | port = np.array([(-port_size/2, -port_size/2), (port_size/2, -port_size/2), (port_size/2, port_size/2), (-port_size/2, port_size/2)])
25 | input_ports = np.array([port + np.array([-width/2, y]) for i, y in enumerate(np.linspace(-width/2, width/2, num_inputs + 2)[1:-1])])
26 | output_ports = np.array([port + np.array([width/2, y]) for i, y in enumerate(np.linspace(-width/2, width/2, num_inputs + 2)[1:-1])])
27 |
28 |
29 | cell = fl.cell(
30 | name,
31 | shapes={
32 | (1, 0): [rect],
33 | (1, 1): [*input_ports, *output_ports],
34 | }
35 | )
36 | return cell
37 |
38 | # Cell
39 | example_lib = fl.library(
40 | "F. E. L.",
41 | pcells=[instance],
42 | cells=[],
43 | description="FLayout Example Library",
44 | )
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .ONESHELL:
2 | SHELL := /bin/bash
3 | SRC = $(wildcard nbs/*.ipynb)
4 |
5 | all: lib docs
6 |
7 | .SILENT: docker
8 | docker:
9 | docker build . -t fl
10 |
11 | lib:
12 | nbdev_build_lib
13 |
14 | sync:
15 | nbdev_update_lib
16 |
17 | serve:
18 | cd docs && bundle-2.7 exec jekyll serve
19 |
20 | .PHONY: docs
21 | docs:
22 | papermill index.ipynb index.ipynb --cwd . --kernel fl
23 | nbdev_build_docs
24 |
25 | run:
26 | find source -name "*.ipynb" | grep -v .ipynb_checkpoints | xargs -I {} papermill {} {} --cwd source --kernel fl
27 |
28 | test:
29 | nbdev_test_nbs
30 |
31 | release: pypi conda_release
32 | nbdev_bump_version
33 |
34 | conda_release:
35 | fastrelease_conda_package
36 |
37 | pypi: dist
38 | twine upload --repository pypi dist/*
39 |
40 | dist: clean
41 | python -m build --sdist --wheel
42 |
43 | install: clean lib clean
44 | python -m pip uninstall -y flayout
45 | python -m pip install --upgrade -e .
46 |
47 | user_install: clean lib clean
48 | python -m pip uninstall -y flayout
49 | python -m pip install --user --upgrade .
50 |
51 | fast_user_install: lib
52 | python -m pip uninstall -y flayout
53 | python -m pip install --user --upgrade --no-deps .
54 |
55 | clean:
56 | nbdev_clean_nbs
57 | find . -name "*.ipynb" | xargs nbstripout
58 | find . -name "dist" | xargs rm -rf
59 | find . -name "build" | xargs rm -rf
60 | find . -name "builds" | xargs rm -rf
61 | find . -name "__pycache__" | xargs rm -rf
62 | find . -name "*.so" | xargs rm -rf
63 | find . -name "*.egg-info" | xargs rm -rf
64 | find . -name ".ipynb_checkpoints" | xargs rm -rf
65 | find . -name ".pytest_cache" | xargs rm -rf
66 |
67 | reset:
68 | rm -rf flayout
69 | rm -rf docs
70 | git checkout -- docs
71 | nbdev_build_lib
72 | make clean
73 |
--------------------------------------------------------------------------------
/flayout/notebook.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/92_notebook.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['html_repr', 'auto_expand']
4 |
5 | # Internal Cell
6 | from functools import wraps
7 |
8 | import bokeh.plotting as bp
9 | import flayout.bokeh as flbk
10 | import pya
11 | from bokeh.embed import file_html
12 | from bokeh.io import output_notebook
13 | from bokeh.resources import CDN
14 |
15 | try:
16 | IPYTHON = get_ipython() # type: ignore
17 | output_notebook(hide_banner=True)
18 | except NameError:
19 | IPYTHON = None
20 |
21 | # Cell
22 | def html_repr(func, types):
23 | for cls in types:
24 |
25 | def registered(obj):
26 | plot = func(None, obj)
27 | html = file_html(plot, CDN)
28 | return html
29 |
30 | if IPYTHON is not None:
31 | fmtr = IPYTHON.display_formatter.formatters["text/html"]
32 | fmtr.for_type(cls, registered)
33 |
34 | # Cell
35 | def _expand_fig(fig):
36 | try:
37 | w = (fig.x_range.end - fig.x_range.start)
38 | h = (fig.y_range.end - fig.y_range.start)
39 | fig.x_range.start -= w/10
40 | fig.x_range.end += w/10
41 | fig.y_range.start -= h/10
42 | fig.y_range.end += h/10
43 | except Exception as e:
44 | pass
45 | return fig
46 |
47 | def auto_expand(func):
48 | @wraps(func)
49 | def new_func(*args, **kwargs):
50 | fig = func(*args, **kwargs)
51 | if isinstance(fig, bp.Column):
52 | fig = bp.Column(*[_expand_fig(f) for f in fig.children])
53 | else:
54 | fig = _expand_fig(fig)
55 | return fig
56 | return new_func
57 |
58 | # Cell
59 | html_repr(auto_expand(flbk.draw_point), [pya.DPoint, pya.Point])
60 | html_repr(auto_expand(flbk.draw_vector), [pya.DVector, pya.Vector])
61 | html_repr(auto_expand(flbk.draw_path), [pya.Path, pya.DPath])
62 | html_repr(auto_expand(flbk.draw_poly), [pya.DSimplePolygon, pya.SimplePolygon, pya.DPolygon, pya.Polygon])
63 | html_repr(auto_expand(flbk.draw_box), [pya.Box, pya.DBox])
64 | html_repr(auto_expand(flbk.draw_cell), [pya.Cell])
65 | html_repr(auto_expand(flbk.draw_inst), [pya.Instance])
66 | html_repr(auto_expand(flbk.draw_layout), [pya.Layout])
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # repo specific
2 | docs
3 |
4 | # IDE settings
5 | .vscode/
6 | .idea/
7 |
8 | # nbdev
9 | *.bak
10 | *.log
11 | *~
12 | .gitattributes
13 | .gitconfig
14 | .jekyll-cache/
15 | .last_checked
16 | Gemfile.lock
17 | _tmp*
18 | docs/
19 | tags
20 | tmp*
21 | ~*
22 |
23 | # Byte-compiled / optimized / DLL files
24 | __pycache__/
25 | *.py[cod]
26 | *$py.class
27 |
28 | # C extensions
29 | *.so
30 |
31 | # Distribution / packaging
32 | .Python
33 | env/
34 | build/
35 | develop-eggs/
36 | dist/
37 | downloads/
38 | eggs/
39 | .eggs/
40 | lib/
41 | lib64/
42 | parts/
43 | sdist/
44 | var/
45 | wheels/
46 | *.egg-info/
47 | .installed.cfg
48 | *.egg
49 |
50 | # PyInstaller
51 | # Usually these files are written by a python script from a template
52 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
53 | *.manifest
54 | *.spec
55 |
56 | # Installer logs
57 | pip-log.txt
58 | pip-delete-this-directory.txt
59 |
60 | # Unit test / coverage reports
61 | htmlcov/
62 | .tox/
63 | .coverage
64 | .coverage.*
65 | .cache
66 | nosetests.xml
67 | coverage.xml
68 | *.cover
69 | .hypothesis/
70 |
71 | # Translations
72 | *.mo
73 | *.pot
74 |
75 | # Django stuff:
76 | *.log
77 | local_settings.py
78 |
79 | # Flask stuff:
80 | instance/
81 | .webassets-cache
82 |
83 | # Scrapy stuff:
84 | .scrapy
85 |
86 | # Sphinx documentation
87 | docs/_build/
88 |
89 | # PyBuilder
90 | target/
91 |
92 | # Jupyter Notebook
93 | .ipynb_checkpoints
94 |
95 | # pyenv
96 | .python-version
97 |
98 | # celery beat schedule file
99 | celerybeat-schedule
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # dotenv
105 | .env
106 |
107 | # virtualenv
108 | .venv
109 | venv/
110 | ENV/
111 |
112 | # Spyder project settings
113 | .spyderproject
114 | .spyproject
115 |
116 | # Rope project settings
117 | .ropeproject
118 |
119 | # mkdocs documentation
120 | /site
121 |
122 | # mypy
123 | .mypy_cache/
124 |
125 | .vscode
126 | *.swp
127 |
128 | # osx generated files
129 | .DS_Store
130 | .DS_Store?
131 | .Trashes
132 | ehthumbs.db
133 | Thumbs.db
134 | .idea
135 |
136 | # pytest
137 | .pytest_cache
138 |
139 | # tools/trust-doc-nbs
140 | docs_src/.last_checked
141 |
142 | # symlinks to fastai
143 | docs_src/fastai
144 | tools/fastai
145 |
146 | # link checker
147 | checklink/cookies.txt
148 |
149 | # .gitconfig is now autogenerated
150 | .gitconfig
151 |
152 |
--------------------------------------------------------------------------------
/source/04_io.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "893fd908-aa0b-4daa-ac08-16c7782101f6",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp io"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "d816dbc2-a09f-4e7a-b173-680c9fb05d38",
16 | "metadata": {},
17 | "source": [
18 | "# FLayout IO\n",
19 | "> read and save KLayout layouts"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "2a7e3589-2b55-4791-838b-2798b4a7648d",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "import os\n",
31 | "\n",
32 | "import pya"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "e8bfc909-072a-4fef-83a2-14ecc564181a",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "# export\n",
43 | "def read_gds(fp: str) -> pya.Layout:\n",
44 | " \"\"\"load a layout\n",
45 | "\n",
46 | " Args:\n",
47 | " fp: the path to the layout to load\n",
48 | "\n",
49 | " Returns:\n",
50 | " the loaded layout\n",
51 | " \"\"\"\n",
52 | " if isinstance(fp, str):\n",
53 | " fp = os.path.abspath(os.path.expanduser(fp))\n",
54 | " layout = pya.Layout()\n",
55 | " layout.read(fp)\n",
56 | " return layout"
57 | ]
58 | },
59 | {
60 | "cell_type": "code",
61 | "execution_count": null,
62 | "id": "54754b17-afb8-4e2c-9256-db991e74a161",
63 | "metadata": {},
64 | "outputs": [],
65 | "source": [
66 | "# export\n",
67 | "def save_gds(layout: pya.Layout, fp: str):\n",
68 | " \"\"\"save a layout\n",
69 | "\n",
70 | " Args:\n",
71 | " layout: the layout to save\n",
72 | " fp: the path where to save the layout\n",
73 | " \"\"\"\n",
74 | " if isinstance(fp, str):\n",
75 | " fp = os.path.abspath(os.path.expanduser(fp))\n",
76 | " if isinstance(layout, pya.Layout):\n",
77 | " layout.write(fp)\n",
78 | " else:\n",
79 | " raise ValueError(f\"Don't know how to save type {layout.__class__.__name__}.\")"
80 | ]
81 | }
82 | ],
83 | "metadata": {
84 | "kernelspec": {
85 | "display_name": "fl",
86 | "language": "python",
87 | "name": "fl"
88 | }
89 | },
90 | "nbformat": 4,
91 | "nbformat_minor": 5
92 | }
93 |
--------------------------------------------------------------------------------
/docs/assets/images/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
40 |
42 |
46 |
51 |
55 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FLayout
2 | > My personal KLayout wrappers and utility functions
3 |
4 |
5 | ## Archived
6 |
7 | **Consider this project archived as I don't have time to maintain it. Feel free to use whatever you deem useful in your own klayout-based projects.**
8 |
9 | ## Installation
10 |
11 | ```sh
12 | pip install flayout
13 | ```
14 |
15 | ### Development installation
16 |
17 | ```sh
18 | git clone https://github.com/flaport/flayout
19 | cd flayout
20 | conda env update # create conda environment 'fl' with klayout (python library) and klayout-gui (GUI)
21 | pip install -e .
22 | ```
23 |
24 | It would be cool if a [salt](https://www.klayout.de/package_cookbook.html) package for this library would exist.
25 |
26 | Note that this package might not work on Windwos... I never took the time to test it there...
27 |
28 | ## Easy PCells
29 |
30 | This library offers a klayout [PCell decorator](https://flaport.github.io/flayout/pcell), which is much easier to use than the default PCell offered by the KLayout api. Moreover, the PCell decorator can be with a [GDSFactory](https://github.com/gdsfactory/gdsfactory) component function (or any function that produces a [gdspy](https://github.com/heitzmann/gdspy) cell) as well!
31 |
32 | ## Use with KLayout
33 |
34 | First install flayout as a system python package:
35 |
36 | ```sh
37 | cd /path/to/flayout
38 | pip install --user .
39 | ```
40 |
41 | Alternatively, you can also open the klayout gui from *within* the `fl` python environment (linux only)
42 |
43 | Then within klayout add a simple macro to import the flayout example library
44 |
45 | ```python
46 | from flayout.example_lib import *
47 | ```
48 |
49 | Note that running this macro takes a while in KLayout (about 10 seconds).
50 |
51 | After running the macro, open a new gdsfile and find the Flayout PCells in the "F.E.L - Flayout Example Library", which supplies two gdsfactory-defined components: the mzi and euler bend. These components are now imported as PCells!
52 |
53 | 
54 |
55 | You can have a look at how the [example library](flayout/example_lib.py) is implemented and try something similar for yourself!
56 |
57 | ## Quick Docs
58 |
59 |
60 |
61 |
62 | - **FLayout**
63 | - Overview
64 | - [Factories](https://flaport.github.io/flayout/factories) ⬅ start here.
65 | - [Cell Utilities](https://flaport.github.io/flayout/cell)
66 | - [PCell Decorator](https://flaport.github.io/flayout/pcell)
67 | - [Read & Write](https://flaport.github.io/flayout/io)
68 | - **Extensions**
69 | - [Bokeh Visualization](https://flaport.github.io/flayout/bokeh)
70 | - [Notebook](https://flaport.github.io/flayout/notebook)
71 | - **Libraries**
72 | - [Example Library](https://flaport.github.io/flayout/example_lib)
73 |
74 |
75 |
76 |
77 | ## NBDev
78 |
79 | This project was created using [nbdev1](https://nbdev1.fast.ai/). An awesome [literate programming](https://en.wikipedia.org/wiki/Literate_programming) environment for python.
80 |
81 | ## License
82 |
83 | As this library can be considered a light wrapper for the KLayout python api, I chose license it under [GPLv3](LICENSE).
84 |
--------------------------------------------------------------------------------
/flayout/_nbdev.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED BY NBDEV! DO NOT EDIT!
2 |
3 | __all__ = ["index", "modules", "custom_doc_links", "git_url"]
4 |
5 | index = {"__version__": "index.ipynb",
6 | "__author__": "index.ipynb",
7 | "layer": "01_factories.ipynb",
8 | "point": "01_factories.ipynb",
9 | "vector": "01_factories.ipynb",
10 | "box": "01_factories.ipynb",
11 | "polygon": "01_factories.ipynb",
12 | "Point": "01_factories.ipynb",
13 | "Points": "01_factories.ipynb",
14 | "path": "01_factories.ipynb",
15 | "cell": "01_factories.ipynb",
16 | "Shape": "01_factories.ipynb",
17 | "transform": "01_factories.ipynb",
18 | "layout": "01_factories.ipynb",
19 | "library": "01_factories.ipynb",
20 | "DoesNotExist": "02_cell.ipynb",
21 | "COPY_IMPLEMENTATIONS": "02_cell.ipynb",
22 | "MAX_DEPTH": "02_cell.ipynb",
23 | "ON_SAME_NAME_DOC": "02_cell.ipynb",
24 | "Reference": "02_cell.ipynb",
25 | "LibReference": "02_cell.ipynb",
26 | "PCellReference": "02_cell.ipynb",
27 | "PCellLibReference": "02_cell.ipynb",
28 | "reference": "02_cell.ipynb",
29 | "copy_tree": "02_cell.ipynb",
30 | "add_cells_to_layout": "02_cell.ipynb",
31 | "add_pcells_to_layout": "02_cell.ipynb",
32 | "CELL_CONVERTERS": "03_pcell.ipynb",
33 | "pcell": "03_pcell.ipynb",
34 | "read_gds": "04_io.ipynb",
35 | "save_gds": "04_io.ipynb",
36 | "instance": "50_gui.ipynb",
37 | "example_lib": "99_example_lib.ipynb",
38 | "ALPHA": "91_bokeh.ipynb",
39 | "RED": "91_bokeh.ipynb",
40 | "GREEN": "91_bokeh.ipynb",
41 | "BLUE": "91_bokeh.ipynb",
42 | "C0": "91_bokeh.ipynb",
43 | "C1": "91_bokeh.ipynb",
44 | "C2": "91_bokeh.ipynb",
45 | "C3": "91_bokeh.ipynb",
46 | "C4": "91_bokeh.ipynb",
47 | "C5": "91_bokeh.ipynb",
48 | "C6": "91_bokeh.ipynb",
49 | "C7": "91_bokeh.ipynb",
50 | "C8": "91_bokeh.ipynb",
51 | "C9": "91_bokeh.ipynb",
52 | "get_lyp_path": "91_bokeh.ipynb",
53 | "LayerProperty": "91_bokeh.ipynb",
54 | "LayerProperties": "91_bokeh.ipynb",
55 | "read_lyp": "91_bokeh.ipynb",
56 | "get_lyp": "91_bokeh.ipynb",
57 | "new_plot": "91_bokeh.ipynb",
58 | "adjust_plot": "91_bokeh.ipynb",
59 | "draw_polys": "91_bokeh.ipynb",
60 | "draw_poly": "91_bokeh.ipynb",
61 | "draw_path": "91_bokeh.ipynb",
62 | "draw_point": "91_bokeh.ipynb",
63 | "draw_vector": "91_bokeh.ipynb",
64 | "draw_box": "91_bokeh.ipynb",
65 | "draw_cell": "91_bokeh.ipynb",
66 | "draw_inst": "91_bokeh.ipynb",
67 | "draw_layout": "91_bokeh.ipynb",
68 | "html_repr": "92_notebook.ipynb",
69 | "auto_expand": "92_notebook.ipynb",
70 | "rectangle": "99_example_lib.ipynb",
71 | "square": "99_example_lib.ipynb",
72 | "gf_pcells": "99_example_lib.ipynb"}
73 |
74 | modules = ["__init__.py",
75 | "factories.py",
76 | "cell.py",
77 | "pcell.py",
78 | "io.py",
79 | "gui.py",
80 | "bokeh.py",
81 | "notebook.py",
82 | "example_lib.py"]
83 |
84 | doc_url = "https://flaport.github.io/flaport/flayout/"
85 |
86 | git_url = "https://github.com/flaport/flayout/tree/master/"
87 |
88 | def custom_doc_links(name): return None
89 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 | on:
3 | workflow_dispatch:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 | paths-ignore:
9 | - ".github/**"
10 | - "!.github/workflows/main.yml"
11 | - "docs/**"
12 | - "*.md"
13 | - ".git*"
14 |
15 | jobs:
16 | # clean:
17 | # runs-on: ubuntu-latest
18 | # timeout-minutes: 5
19 | # container:
20 | # image: condaforge/mambaforge:4.10.3-10
21 | # volumes:
22 | # - ${{ github.workspace }}:/github/workspace
23 | # steps:
24 | # - name: Checkout
25 | # uses: actions/checkout@master
26 | # - name: Install dependencies
27 | # run: mamba install -c fastai -c conda-forge nbdev\<2 nbstripout
28 | # - name: Clean notebooks
29 | # run: nbdev_clean_nbs
30 | # - name: Strip notebook outputs
31 | # run: find . -name "*.ipynb" | xargs nbstripout
32 | # - name: Check if git status is clean
33 | # run: |
34 | # if [ ! -z "$(git status --porcelain)" ]; then
35 | # echo "Notebooks are not cleaned! They still contain outputs and/or metadata."
36 | # echo "You probably forgot to clear the notebook outputs before pushing."
37 | # echo "Please make sure all pre-commit hooks are properly installed to prevent this issue."
38 | # false;
39 | # fi
40 |
41 | # diff:
42 | # runs-on: ubuntu-latest
43 | # timeout-minutes: 5
44 | # container:
45 | # image: condaforge/mambaforge:4.10.3-10
46 | # volumes:
47 | # - ${{ github.workspace }}:/github/workspace
48 | # steps:
49 | # - name: Checkout
50 | # uses: actions/checkout@master
51 | # - name: Install dependencies
52 | # run: mamba install -c fastai -c conda-forge nbdev\<2
53 | # - name: Check for no diff between notebooks and library
54 | # run: |
55 | # if [ ! -z "$(nbdev_diff_nbs)" ]; then
56 | # echo "Python library does not match backing notebooks (diff between notebooks and library detected). "
57 | # echo "You probably forgot to re-generate the library before pushing."
58 | # echo "Please make sure all pre-commit hooks are properly installed to prevent this issue."
59 | # false;
60 | # fi
61 |
62 | docs:
63 | # needs:
64 | # - clean
65 | # - diff
66 | runs-on: ubuntu-latest
67 | timeout-minutes: 5
68 | container:
69 | image: condaforge/mambaforge:4.10.3-10
70 | volumes:
71 | - ${{ github.workspace }}:/github/workspace
72 | steps:
73 | - name: Checkout
74 | uses: actions/checkout@master
75 | - name: Install dependencies
76 | run: mamba env update -n base -f environment.yml
77 | - name: Create kernel
78 | run: python -m ipykernel install --user --name fl
79 | - name: Run notebooks
80 | run: find source -name "*.ipynb" | grep -v '.ipynb_checkpoints' | xargs -I {} papermill {} {} -k fl
81 | - name: Run index.ipynb
82 | run: papermill index.ipynb index.ipynb --cwd . --kernel fl
83 | - name: Build docs
84 | run: nbdev_build_docs
85 | - name: Store artifacts
86 | uses: actions/upload-artifact@master
87 | with:
88 | name: docs
89 | path: /github/workspace/docs
90 |
91 | pages:
92 | needs:
93 | - docs
94 | runs-on: ubuntu-latest
95 | timeout-minutes: 5
96 | container:
97 | image: condaforge/mambaforge:4.10.3-10
98 | volumes:
99 | - ${{ github.workspace }}:/github/workspace
100 | steps:
101 | - name: Checkout
102 | uses: actions/checkout@master
103 | - name: Install dependencies
104 | run: mamba install -c fastai -c conda-forge rsync
105 | - name: Download 'docs' artifact
106 | uses: actions/download-artifact@master
107 | with:
108 | name: docs
109 | path: /github/workspace/docs
110 | - name: Deploy
111 | uses: JamesIves/github-pages-deploy-action@4.1.7
112 | with:
113 | branch: pages
114 | folder: docs
115 |
--------------------------------------------------------------------------------
/source/92_notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "1ebbed71-340c-4363-ad7c-4d852c3658e9",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp notebook"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "94d088e4-fbfd-400c-84de-3c5531be520a",
16 | "metadata": {},
17 | "source": [
18 | "# Notebook\n",
19 | "> Importing this library yields nice bokeh-powered notebook previews of your cells and polygons."
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "e79cbfb8-1dfb-4e9e-9566-732677ce222f",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "from functools import wraps\n",
31 | "\n",
32 | "import bokeh.plotting as bp\n",
33 | "import flayout.bokeh as flbk\n",
34 | "import pya\n",
35 | "from bokeh.embed import file_html\n",
36 | "from bokeh.io import output_notebook\n",
37 | "from bokeh.resources import CDN\n",
38 | "\n",
39 | "try:\n",
40 | " IPYTHON = get_ipython() # type: ignore\n",
41 | " output_notebook(hide_banner=True)\n",
42 | "except NameError:\n",
43 | " IPYTHON = None"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "id": "5bce77e1-0f3f-46ae-b8ae-062960719d69",
50 | "metadata": {},
51 | "outputs": [],
52 | "source": [
53 | "# export\n",
54 | "def html_repr(func, types):\n",
55 | " for cls in types:\n",
56 | "\n",
57 | " def registered(obj):\n",
58 | " plot = func(None, obj)\n",
59 | " html = file_html(plot, CDN)\n",
60 | " return html\n",
61 | "\n",
62 | " if IPYTHON is not None:\n",
63 | " fmtr = IPYTHON.display_formatter.formatters[\"text/html\"]\n",
64 | " fmtr.for_type(cls, registered)"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "id": "d8f360fe-e397-478d-ada6-608b2731c5b2",
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "# export\n",
75 | "def _expand_fig(fig):\n",
76 | " try:\n",
77 | " w = (fig.x_range.end - fig.x_range.start)\n",
78 | " h = (fig.y_range.end - fig.y_range.start)\n",
79 | " fig.x_range.start -= w/10\n",
80 | " fig.x_range.end += w/10\n",
81 | " fig.y_range.start -= h/10\n",
82 | " fig.y_range.end += h/10\n",
83 | " except Exception as e:\n",
84 | " pass\n",
85 | " return fig\n",
86 | "\n",
87 | "def auto_expand(func):\n",
88 | " @wraps(func)\n",
89 | " def new_func(*args, **kwargs):\n",
90 | " fig = func(*args, **kwargs)\n",
91 | " if isinstance(fig, bp.Column):\n",
92 | " fig = bp.Column(*[_expand_fig(f) for f in fig.children])\n",
93 | " else:\n",
94 | " fig = _expand_fig(fig)\n",
95 | " return fig\n",
96 | " return new_func"
97 | ]
98 | },
99 | {
100 | "cell_type": "code",
101 | "execution_count": null,
102 | "id": "de00a860-be44-42a4-9e21-200c0e7997db",
103 | "metadata": {},
104 | "outputs": [],
105 | "source": [
106 | "# export\n",
107 | "html_repr(auto_expand(flbk.draw_point), [pya.DPoint, pya.Point])\n",
108 | "html_repr(auto_expand(flbk.draw_vector), [pya.DVector, pya.Vector])\n",
109 | "html_repr(auto_expand(flbk.draw_path), [pya.Path, pya.DPath])\n",
110 | "html_repr(auto_expand(flbk.draw_poly), [pya.DSimplePolygon, pya.SimplePolygon, pya.DPolygon, pya.Polygon])\n",
111 | "html_repr(auto_expand(flbk.draw_box), [pya.Box, pya.DBox])\n",
112 | "html_repr(auto_expand(flbk.draw_cell), [pya.Cell])\n",
113 | "html_repr(auto_expand(flbk.draw_inst), [pya.Instance])\n",
114 | "html_repr(auto_expand(flbk.draw_layout), [pya.Layout])"
115 | ]
116 | }
117 | ],
118 | "metadata": {
119 | "kernelspec": {
120 | "display_name": "fl",
121 | "language": "python",
122 | "name": "fl"
123 | }
124 | },
125 | "nbformat": 4,
126 | "nbformat_minor": 5
127 | }
128 |
--------------------------------------------------------------------------------
/source/99_example_lib.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "831fd803-f817-4b13-94b9-c97d5fd9b667",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp example_lib"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "a8f0486c-2290-4ec4-b63f-63987110c7a1",
16 | "metadata": {},
17 | "source": [
18 | "# Example Library\n",
19 | "> An example library of PCells importable in klayout."
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "055bfd2c-4968-4b16-b4e4-65a7ee48381e",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "import pya\n",
31 | "import flayout as fl"
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": null,
37 | "id": "60d0fcaf-79fd-41ab-a658-52f101b384ef",
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "# hide\n",
42 | "import flayout.notebook"
43 | ]
44 | },
45 | {
46 | "cell_type": "code",
47 | "execution_count": null,
48 | "id": "8f9ece43-492a-4f59-b16b-0eecc60b002f",
49 | "metadata": {},
50 | "outputs": [],
51 | "source": [
52 | "# export\n",
53 | "@fl.pcell\n",
54 | "def rectangle(\n",
55 | " name=\"rectangle\",\n",
56 | " width: float = 1.0,\n",
57 | " height: float = 0.5,\n",
58 | " layer: pya.LayerInfo = pya.LayerInfo(1, 0),\n",
59 | "):\n",
60 | " rect = fl.polygon(hull=[(-width/2, -height/2), (width/2, -height/2), (width/2, height/2), (-width/2, height/2)])\n",
61 | " cell = fl.cell(name, shapes={layer: [rect]})\n",
62 | " return cell"
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "id": "56580594-751c-453f-918c-5241c681bda8",
69 | "metadata": {},
70 | "outputs": [],
71 | "source": [
72 | "rectangle()"
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": null,
78 | "id": "a61f7b10-44c4-4599-8f85-b13925bcb612",
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "# export\n",
83 | "@fl.pcell\n",
84 | "def square(\n",
85 | " name=\"square\",\n",
86 | " width: float = 0.5,\n",
87 | " layer: pya.LayerInfo = pya.LayerInfo(1, 0),\n",
88 | "):\n",
89 | " square = fl.polygon(hull=[(-width/2, -width/2), (width/2, -width/2), (width/2, width/2), (-width/2, width/2)])\n",
90 | " cell = fl.cell(name, shapes={layer: [square]})\n",
91 | " return cell"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": null,
97 | "id": "73b1fc34-fc98-4b63-acb7-6cacb5d96145",
98 | "metadata": {},
99 | "outputs": [],
100 | "source": [
101 | "square()"
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": null,
107 | "id": "e724cad8-ad67-473f-9482-60b09202ac4d",
108 | "metadata": {},
109 | "outputs": [],
110 | "source": [
111 | "# export\n",
112 | "gf_pcells = []\n",
113 | "\n",
114 | "import flayout as fl\n",
115 | "import gdsfactory.components as gfc\n",
116 | "\n",
117 | "gf_pcells += [\n",
118 | " mzi := fl.pcell(gfc.mzi, on_error=\"ignore\"),\n",
119 | " bend_euler := fl.pcell(gfc.bend_euler, on_error=\"ignore\")\n",
120 | "]"
121 | ]
122 | },
123 | {
124 | "cell_type": "code",
125 | "execution_count": null,
126 | "id": "8271e1ed-a6b3-469f-9ee9-e2f2431a03f4",
127 | "metadata": {},
128 | "outputs": [],
129 | "source": [
130 | "display(mzi())\n",
131 | "display(bend_euler())"
132 | ]
133 | },
134 | {
135 | "cell_type": "markdown",
136 | "id": "c51c0904-300a-4490-b640-42cdde4a2aa6",
137 | "metadata": {},
138 | "source": [
139 | "## Library"
140 | ]
141 | },
142 | {
143 | "cell_type": "code",
144 | "execution_count": null,
145 | "id": "71b00ca4-04de-449c-882d-6760ffdde495",
146 | "metadata": {},
147 | "outputs": [],
148 | "source": [
149 | "# exports\n",
150 | "example_lib = fl.library(\n",
151 | " \"F. E. L.\",\n",
152 | " pcells=[rectangle, square, *gf_pcells],\n",
153 | " cells=[],\n",
154 | " description=\"FLayout Example Library\",\n",
155 | ")"
156 | ]
157 | }
158 | ],
159 | "metadata": {
160 | "kernelspec": {
161 | "display_name": "fl",
162 | "language": "python",
163 | "name": "fl"
164 | }
165 | },
166 | "nbformat": 4,
167 | "nbformat_minor": 5
168 | }
169 |
--------------------------------------------------------------------------------
/source/50_gui.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "831fd803-f817-4b13-94b9-c97d5fd9b667",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp gui"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "a8f0486c-2290-4ec4-b63f-63987110c7a1",
16 | "metadata": {},
17 | "source": [
18 | "# GUI\n",
19 | "> An example KLayout-base GUI"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "055bfd2c-4968-4b16-b4e4-65a7ee48381e",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "import pya\n",
31 | "import flayout as fl\n",
32 | "import numpy as np"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "60d0fcaf-79fd-41ab-a658-52f101b384ef",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "# hide\n",
43 | "import flayout.notebook"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "id": "8f9ece43-492a-4f59-b16b-0eecc60b002f",
50 | "metadata": {},
51 | "outputs": [],
52 | "source": [
53 | "# export\n",
54 | "@fl.pcell\n",
55 | "def instance(\n",
56 | " name=\"instance\",\n",
57 | " width: float = 1.0,\n",
58 | " height: float = 1.0,\n",
59 | " num_inputs: int = 3,\n",
60 | " num_outputs: int = 1,\n",
61 | " port_size=0.1,\n",
62 | "):\n",
63 | " if (width < 2 * port_size) or (height < 2 * port_size):\n",
64 | " raise ValueError(f\"width/height should be bigger than port size. Got: {width:.2f} < {port_size:.2f}\")\n",
65 | " \n",
66 | " rect = np.array([(-width/2, -height/2), (width/2, -height/2), (width/2, height/2), (-width/2, height/2)])\n",
67 | " port = np.array([(-port_size/2, -port_size/2), (port_size/2, -port_size/2), (port_size/2, port_size/2), (-port_size/2, port_size/2)])\n",
68 | " input_ports = np.array([port + np.array([-width/2, y]) for i, y in enumerate(np.linspace(-width/2, width/2, num_inputs + 2)[1:-1])])\n",
69 | " output_ports = np.array([port + np.array([width/2, y]) for i, y in enumerate(np.linspace(-width/2, width/2, num_inputs + 2)[1:-1])])\n",
70 | " \n",
71 | " \n",
72 | " cell = fl.cell(\n",
73 | " name, \n",
74 | " shapes={\n",
75 | " (1, 0): [rect],\n",
76 | " (1, 1): [*input_ports, *output_ports],\n",
77 | " }\n",
78 | " )\n",
79 | " return cell"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "id": "56580594-751c-453f-918c-5241c681bda8",
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "instance()"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "id": "d448a5b3-b1c2-4310-99ed-33cd50681440",
96 | "metadata": {},
97 | "outputs": [],
98 | "source": [
99 | "@fl.pcell\n",
100 | "def connection(\n",
101 | " name=\"connection\",\n",
102 | " input=\"\",\n",
103 | " output=\"\",\n",
104 | "):\n",
105 | " rect = np.array([(-width/2, -height/2), (width/2, -height/2), (width/2, height/2), (-width/2, height/2)])\n",
106 | " port = np.array([(-port_size/2, -port_size/2), (port_size/2, -port_size/2), (port_size/2, port_size/2), (-port_size/2, port_size/2)])\n",
107 | " input_ports = np.array([port + np.array([-width/2, y]) for i, y in enumerate(np.linspace(-width/2, width/2, num_inputs + 2)[1:-1])])\n",
108 | " output_ports = np.array([port + np.array([width/2, y]) for i, y in enumerate(np.linspace(-width/2, width/2, num_outputs + 2)[1:-1])])\n",
109 | " \n",
110 | " \n",
111 | " cell = fl.cell(\n",
112 | " name, \n",
113 | " shapes={\n",
114 | " (1, 0): [rect],\n",
115 | " (1, 1): [*input_ports, *output_ports],\n",
116 | " }\n",
117 | " )\n",
118 | " return cell"
119 | ]
120 | },
121 | {
122 | "cell_type": "code",
123 | "execution_count": null,
124 | "id": "71b00ca4-04de-449c-882d-6760ffdde495",
125 | "metadata": {},
126 | "outputs": [],
127 | "source": [
128 | "# exports\n",
129 | "example_lib = fl.library(\n",
130 | " \"F. E. L.\",\n",
131 | " pcells=[instance],\n",
132 | " cells=[],\n",
133 | " description=\"FLayout Example Library\",\n",
134 | ")"
135 | ]
136 | }
137 | ],
138 | "metadata": {
139 | "kernelspec": {
140 | "display_name": "fl",
141 | "language": "python",
142 | "name": "fl"
143 | }
144 | },
145 | "nbformat": 4,
146 | "nbformat_minor": 5
147 | }
148 |
--------------------------------------------------------------------------------
/docs/_includes/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ page.title }} | {{ site.site_title }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {% if site.use_math %}
25 |
26 |
27 |
28 |
29 |
40 | {% endif %}
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
63 |
64 |
65 |
66 |
67 | {% if site.twitter_username %}
68 |
69 |
70 |
71 | {% endif %}
72 |
73 | {% if page.summary %}
74 |
75 | {% else %}
76 |
77 | {% endif %}
78 |
79 | {% if page.image %}
80 |
81 |
82 | {% else %}
83 |
84 | {% if site.title_image %}
85 |
86 | {% endif %}
87 | {% endif %}
88 |
89 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | title: FLayout
4 |
5 |
6 | keywords: fastai
7 | sidebar: home_sidebar
8 |
9 | summary: "My personal KLayout wrappers and utility functions"
10 | description: "My personal KLayout wrappers and utility functions"
11 | nb_path: "index.ipynb"
12 | ---
13 |
22 |
23 |
24 |
25 | {% raw %}
26 |
27 |
28 |
29 |
30 | {% endraw %}
31 |
32 |
33 |
34 |
Archived Consider this project archived as I don't have time to maintain it. Feel free to use whatever you deem useful in your own klayout-based projects.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Installation git clone https://github.com/flaport/flayout
42 | cd flayout
43 | conda env update # create conda environment 'fl' with klayout (python library) and klayout-gui (GUI)
44 | pip install -e .
45 |
46 |
It would be cool if a salt package for this library would exist.
47 |
Note that this package might not work on Windwos... I never took the time to test it there...
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Easy PCells This library offers a klayout PCell decorator , which is much easier to use than the default PCell offered by the KLayout api. Moreover, the PCell decorator can be with a GDSFactory component function (or any function that produces a gdspy cell) as well!
55 |
56 |
57 |
58 |
59 |
60 |
61 |
Use with KLayout First install flayout as a system python package:
62 |
cd /path/to/flayout
63 | pip install --user .
64 |
65 |
Alternatively, you can also open the klayout gui from within the fl python environment (linux only)
66 |
Then within klayout add a simple macro to import the flayout example library
67 |
from flayout.example_lib import *
68 |
69 |
Note that running this macro takes a while in KLayout (about 10 seconds).
70 |
After running the macro, open a new gdsfile and find the Flayout PCells in the "F.E.L - Flayout Example Library", which supplies two gdsfactory-defined components: the mzi and euler bend. These components are now imported as PCells!
71 |
72 |
You can have a look at how the example library is implemented and try something similar for yourself!
73 |
74 |
75 |
76 |
77 |
83 | {% raw %}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | FLayout
102 |
103 | Extensions
107 |
108 | Libraries
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | {% endraw %}
123 |
124 |
125 |
126 |
NBDev This project was created using nbdev1 . An awesome literate programming environment for python.
127 |
128 |
129 |
130 |
131 |
132 |
133 |
License As this library can be considered a light wrapper for the KLayout python api, I chose license it under GPLv3 .
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/index.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "d2084e67-8453-43e2-b864-007987072521",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp __init__"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "a059ef95",
16 | "metadata": {},
17 | "source": [
18 | "# FLayout\n",
19 | "> My personal KLayout wrappers and utility functions"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "45ada385-89ae-4efd-9f4f-4553d75dd231",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "__version__ = \"0.0.0\"\n",
31 | "__author__ = \"Floris Laporte\"\n",
32 | "\n",
33 | "from flayout.cell import copy_tree as copy_tree\n",
34 | "from flayout.cell import reference as reference\n",
35 | "from flayout.factories import box as box\n",
36 | "from flayout.factories import cell as cell\n",
37 | "from flayout.factories import layer as layer\n",
38 | "from flayout.factories import layout as layout\n",
39 | "from flayout.factories import library as library\n",
40 | "from flayout.factories import path as path\n",
41 | "from flayout.factories import point as point\n",
42 | "from flayout.factories import polygon as polygon\n",
43 | "from flayout.factories import transform as transform\n",
44 | "from flayout.factories import vector as vector\n",
45 | "from flayout.io import read_gds as read_gds\n",
46 | "from flayout.io import save_gds as save_gds\n",
47 | "from flayout.pcell import pcell as pcell"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": null,
53 | "id": "0813fb22",
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "# hide\n",
58 | "import json\n",
59 | "from nbdev.showdoc import Markdown"
60 | ]
61 | },
62 | {
63 | "cell_type": "markdown",
64 | "id": "165ba04d-d5b2-477b-a289-22a672044011",
65 | "metadata": {},
66 | "source": [
67 | "## Archived\n",
68 | "\n",
69 | "**Consider this project archived as I don't have time to maintain it. Feel free to use whatever you deem useful in your own klayout-based projects.**"
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "id": "cc6cb4d6-d3a9-4f98-9612-6f9ce302f4c8",
75 | "metadata": {},
76 | "source": [
77 | "## Installation\n",
78 | "\n",
79 | "```sh\n",
80 | "git clone https://github.com/flaport/flayout\n",
81 | "cd flayout\n",
82 | "conda env update # create conda environment 'fl' with klayout (python library) and klayout-gui (GUI)\n",
83 | "pip install -e .\n",
84 | "```\n",
85 | "\n",
86 | "It would be cool if a [salt](https://www.klayout.de/package_cookbook.html) package for this library would exist.\n",
87 | "\n",
88 | "Note that this package might not work on Windwos... I never took the time to test it there..."
89 | ]
90 | },
91 | {
92 | "cell_type": "markdown",
93 | "id": "9c6e3721-5933-4c40-9c3c-1b657cbd7cd9",
94 | "metadata": {},
95 | "source": [
96 | "## Easy PCells\n",
97 | "\n",
98 | "This library offers a klayout [PCell decorator](https://flaport.github.io/flayout/pcell), which is much easier to use than the default PCell offered by the KLayout api. Moreover, the PCell decorator can be with a [GDSFactory](https://github.com/gdsfactory/gdsfactory) component function (or any function that produces a [gdspy](https://github.com/heitzmann/gdspy) cell) as well!"
99 | ]
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "id": "eed1770d-ceb9-41fc-b9d0-f6cea14746b0",
104 | "metadata": {},
105 | "source": [
106 | "## Use with KLayout\n",
107 | "\n",
108 | "First install flayout as a system python package:\n",
109 | "\n",
110 | "```sh\n",
111 | "cd /path/to/flayout\n",
112 | "pip install --user .\n",
113 | "```\n",
114 | "\n",
115 | "Alternatively, you can also open the klayout gui from *within* the `fl` python environment (linux only)\n",
116 | "\n",
117 | "Then within klayout add a simple macro to import the flayout example library\n",
118 | "\n",
119 | "```python\n",
120 | "from flayout.example_lib import *\n",
121 | "```\n",
122 | "\n",
123 | "Note that running this macro takes a while in KLayout (about 10 seconds).\n",
124 | "\n",
125 | "After running the macro, open a new gdsfile and find the Flayout PCells in the \"F.E.L - Flayout Example Library\", which supplies two gdsfactory-defined components: the mzi and euler bend. These components are now imported as PCells!\n",
126 | "\n",
127 | "\n",
128 | "\n",
129 | "You can have a look at how the [example library](flayout/example_lib.py) is implemented and try something similar for yourself!"
130 | ]
131 | },
132 | {
133 | "cell_type": "markdown",
134 | "id": "cd2bd927",
135 | "metadata": {},
136 | "source": [
137 | "## Quick Docs"
138 | ]
139 | },
140 | {
141 | "cell_type": "code",
142 | "execution_count": null,
143 | "id": "3db036f5",
144 | "metadata": {},
145 | "outputs": [],
146 | "source": [
147 | "# hide_input\n",
148 | "\n",
149 | "md = \"\"\n",
150 | "sidebar = json.load(open(\"docs/sidebar.json\"))\n",
151 | "for key, section in sidebar.items():\n",
152 | " md += f\"- **{key}**\\n\"\n",
153 | " for name, url in section.items():\n",
154 | " if url == \"index\":\n",
155 | " md += f\" - {name}\\n\"\n",
156 | " else:\n",
157 | " md += f\" - [{name}](https://flaport.github.io/flayout/{url})\\n\"\n",
158 | " if url == \"factories\":\n",
159 | " md = md[:-1] + \" ⬅ start here.\\n\"\n",
160 | " \n",
161 | "Markdown(md)"
162 | ]
163 | },
164 | {
165 | "cell_type": "markdown",
166 | "id": "5577e5ff-9423-4346-bb21-7fbfb257f2d7",
167 | "metadata": {},
168 | "source": [
169 | "## NBDev\n",
170 | "\n",
171 | "This project was created using [nbdev1](https://nbdev1.fast.ai/). An awesome [literate programming](https://en.wikipedia.org/wiki/Literate_programming) environment for python."
172 | ]
173 | },
174 | {
175 | "cell_type": "markdown",
176 | "id": "d53d3c2a-2a56-4f61-b543-c22c0da82b52",
177 | "metadata": {},
178 | "source": [
179 | "## License\n",
180 | "\n",
181 | "As this library can be considered a light wrapper for the KLayout python api, I chose license it under [GPLv3](LICENSE)."
182 | ]
183 | }
184 | ],
185 | "metadata": {
186 | "kernelspec": {
187 | "display_name": "fl",
188 | "language": "python",
189 | "name": "fl"
190 | }
191 | },
192 | "nbformat": 4,
193 | "nbformat_minor": 5
194 | }
195 |
--------------------------------------------------------------------------------
/flayout/factories.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/01_factories.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['layer', 'point', 'vector', 'box', 'polygon', 'polygon', 'polygon', 'Point', 'Points', 'path', 'cell',
4 | 'Shape', 'transform', 'layout', 'library']
5 |
6 | # Internal Cell
7 | from typing import Dict, List, Optional, Tuple, Union, overload
8 |
9 | import pya
10 | from .cell import (
11 | LibReference,
12 | PCellLibReference,
13 | PCellReference,
14 | Reference,
15 | _add_cell_to_layout,
16 | _add_lib_cell_to_layout,
17 | _add_lib_pcell_to_layout,
18 | add_cells_to_layout,
19 | add_pcells_to_layout,
20 | reference,
21 | )
22 |
23 | # Cell
24 |
25 |
26 | # Cell
27 | def layer(lr: int, dt: int, name: str = "") -> pya.LayerInfo:
28 | layer = pya.LayerInfo(int(lr), int(dt))
29 | if name:
30 | layer.name = name
31 | return layer
32 |
33 | # Cell
34 | def point(x: Union[float, int], y: Union[float, int]) -> pya.DPoint:
35 | """create a KLayout DPoint
36 |
37 | Args:
38 | x: the x-value of the point
39 | y: the y-value of the point
40 |
41 | Returns:
42 | the point
43 | """
44 | return pya.DPoint(float(x), float(y))
45 |
46 | # Cell
47 | def vector(x: Union[float, int], y: Union[float, int]) -> pya.DVector:
48 | """create a KLayout DVector
49 |
50 | Args:
51 | x: the x-value of the vector
52 | y: the y-value of the vector
53 |
54 | Returns:
55 | the vector
56 | """
57 | return pya.DVector(float(x), float(y))
58 |
59 | # Cell
60 |
61 | def _validate_point(p):
62 | if isinstance(p, pya.Point):
63 | p = p.to_dtype()
64 | if not isinstance(p, pya.DPoint):
65 | p = pya.DPoint(*(float(x) for x in p))
66 | return p
67 |
68 | def box(p1: Union[pya.Point, pya.DPoint], p2: Union[pya.Point, pya.DPoint]) -> pya.DBox:
69 | p1 = _validate_point(p1)
70 | p2 = _validate_point(p2)
71 | return pya.DBox(p1, p2)
72 |
73 | # Cell
74 |
75 | Point = Union[pya.DPoint, Tuple[int, int], Tuple[float, float], 'np.ndarray']
76 | Points = Union[List[Point], 'np.ndarray']
77 |
78 | @overload
79 | def polygon(
80 | hull: Points,
81 | *,
82 | holes: None = None,
83 | raw: bool = False,
84 | ) -> pya.DSimplePolygon:
85 | ...
86 |
87 |
88 | @overload
89 | def polygon(
90 | hull: Points,
91 | *,
92 | holes: List[Points] = None,
93 | raw: bool = False,
94 | ) -> pya.DPolygon:
95 | ...
96 |
97 |
98 | def polygon(
99 | hull: Points,
100 | *,
101 | holes: Optional[List[Points]] = None,
102 | raw: bool = False,
103 | ) -> Union[pya.DSimplePolygon, pya.DPolygon]:
104 | """create a KLayout polygon
105 |
106 | Args:
107 | hull: the points that make the polygon hull
108 | holes: the collection of points that make the polygon holes
109 | raw: if true, the points won't be compressed.
110 |
111 | Returns:
112 | the polygon
113 | """
114 | hull = [(p if isinstance(p, (pya.Point, pya.DPoint)) else point(*p)) for p in hull]
115 | if holes is None:
116 | poly = pya.DSimplePolygon(hull, raw)
117 | else:
118 | poly = pya.DPolygon(hull, raw)
119 | for hole in holes:
120 | hole = [(p if isinstance(p, pya.DPoint) else point(*p)) for p in hole]
121 | poly.insert_hole(hole)
122 | return poly
123 |
124 | # Cell
125 | def path(
126 | pts: Points,
127 | width: float,
128 | *,
129 | bgn_ext: float = 0.0,
130 | end_ext: float = 0.0,
131 | round: bool = False,
132 | ) -> pya.DPath:
133 | """create a KLayout path
134 |
135 | Args:
136 | pts: the points that make the path
137 | width: the width of the path
138 | bgn_ext: the begin extension of the path
139 | end_ext: the end extension of the path
140 | round: round the ends of the path
141 |
142 | Returns:
143 | the path
144 | """
145 | pts = [(p if isinstance(p, (pya.Point, pya.DPoint)) else point(*p)) for p in pts]
146 | print(pts)
147 | return pya.DPath(pts, width, bgn_ext, end_ext, round)
148 |
149 | # Cell
150 | Shape = Union[pya.DBox, pya.DSimplePolygon, pya.DPolygon, pya.DPath, pya.Box, pya.SimplePolygon, pya.Polygon, pya.Path]
151 | def cell(
152 | name: str,
153 | *,
154 | shapes: Optional[Dict[pya.LayerInfo, Shape]] = None,
155 | child_cells: Optional[List[Union[pya.Cell, Tuple[pya.Cell, pya.CplxTrans]]]] = None,
156 | ) -> pya.Cell:
157 | """create a KLayout cell
158 |
159 | Args:
160 | name: the name of the cell
161 | shapes: the shapes to add to the cell
162 | child_cells: the child cells to add to the cell
163 |
164 | Returns:
165 | the cell
166 | """
167 | layout = pya.Layout()
168 | cell = layout.create_cell(name)
169 | cell._layout = layout # make sure layout does not get destroyed
170 | if shapes:
171 | for lr, lrshapes in shapes.items():
172 | if not isinstance(lr, pya.LayerInfo):
173 | lr = layer(*lr)
174 | lr = layout.layer(lr)
175 | for i, shape in enumerate(lrshapes):
176 |
177 | # if isinstance(shape, np.ndarray):
178 | if type(shape).__name__ == 'ndarray': # yeah... I don't want to import numpy...
179 | lrshapes[i] = polygon(shape)
180 |
181 | for i, shape in enumerate(lrshapes): # TODO: insert all at once?
182 | cell.shapes(lr).insert(shape)
183 | if child_cells:
184 | cell_idxs = {}
185 | for ref in child_cells:
186 | if isinstance(ref, (str, pya.Cell, pya.PCellDeclaration)):
187 | ref = reference(ref)
188 | else:
189 | ref = reference(*ref)
190 | if isinstance(ref, Reference):
191 | if ref.cell not in cell_idxs:
192 | cell_idxs[ref.cell] = _add_cell_to_layout(layout, ref.cell)
193 | cell.insert(pya.CellInstArray(cell_idxs[ref.cell], ref.trans))
194 | elif isinstance(ref, LibReference):
195 | # don't store index in cell_idxs... Cell belongs to library and will not be copied.
196 | idx = _add_lib_cell_to_layout(layout, ref.lib, ref.cell)
197 | cell.insert(pya.CellInstArray(idx, ref.trans))
198 | elif isinstance(ref, PCellReference):
199 | raise ValueError(
200 | f"One can only add pcells belonging to a library to to a new cell. "
201 | f"Add a pcell reference using the following string format to "
202 | f"represent the cell: '.'."
203 | )
204 | elif isinstance(ref, PCellLibReference):
205 | # don't store index in cell_idxs... PCell belongs to library and will not be copied.
206 | idx = _add_lib_pcell_to_layout(layout, ref.lib, ref.cell, ref.params)
207 | cell.insert(pya.CellInstArray(idx, ref.trans))
208 |
209 | return cell
210 |
211 | # Cell
212 | def transform(
213 | mag: float = 1.0,
214 | rot: float = 0.0,
215 | mirrx: bool = False,
216 | x: float = 0.0,
217 | y: float = 0.0,
218 | ) -> pya.CplxTrans:
219 | """create a KLayout Transformation
220 |
221 | Args:
222 | mag: the magnitude of the transformation
223 | rot: the rotation of the transformation
224 | mirrx: mirror over the x-axis (x=0 line).
225 | x: translation distance in the x-direction
226 | y: translation distance in the y-direction
227 |
228 | Returns:
229 | the cell reference
230 | """
231 | return pya.CplxTrans(mag, rot, mirrx, int(1000 * x), int(1000 * y))
232 |
233 | # Cell
234 | def layout(*, cells: Optional[List[pya.Cell]] = None) -> pya.Layout:
235 | """create a KLayout Layout
236 |
237 | Args:
238 | cells: the cells to add to the layout
239 |
240 | Returns:
241 | the KLayout layout
242 | """
243 | layout = pya.Layout()
244 | layout.dbu = 0.001 # hard coded for now
245 | if cells:
246 | add_cells_to_layout(layout, cells)
247 | return layout
248 |
249 | # Cell
250 | def library(
251 | name: str,
252 | *,
253 | cells: Optional[List[pya.Cell]] = None,
254 | pcells: Optional[List[pya.Cell]] = None,
255 | description: str = "",
256 | ) -> pya.Library:
257 | """create a KLayout Library
258 |
259 | Args:
260 | name: the name of the library
261 | cells: the cells to add to the library
262 | pcells: the pcells to add to the library
263 |
264 | Returns:
265 | the KLayout library
266 | """
267 | lib = pya.Library()
268 | if description:
269 | lib.description = description
270 | layout = lib.layout()
271 | layout.dbu = 0.001 # hard coded for now
272 | if cells:
273 | add_cells_to_layout(layout, cells)
274 | if pcells:
275 | add_pcells_to_layout(layout, pcells)
276 | lib.register(name)
277 | return lib
--------------------------------------------------------------------------------
/flayout/pcell.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/03_pcell.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['pcell']
4 |
5 | # Internal Cell
6 | from functools import partial
7 | from inspect import Parameter, Signature, signature
8 | from typing import Callable, Optional
9 |
10 | import pya
11 | from .cell import copy_tree
12 |
13 | CELL_CONVERTERS = {}
14 |
15 | # Internal Cell
16 | def _klayout_type(param: Parameter):
17 | type_map = {
18 | pya.PCellDeclarationHelper.TypeInt: pya.PCellDeclarationHelper.TypeInt,
19 | "TypeInt": pya.PCellDeclarationHelper.TypeInt,
20 | "int": pya.PCellDeclarationHelper.TypeInt,
21 | int: pya.PCellDeclarationHelper.TypeInt,
22 | Optional[int]: pya.PCellDeclarationHelper.TypeInt,
23 | pya.PCellDeclarationHelper.TypeDouble: pya.PCellDeclarationHelper.TypeDouble,
24 | "TypeDouble": pya.PCellDeclarationHelper.TypeDouble,
25 | "float": pya.PCellDeclarationHelper.TypeDouble,
26 | float: pya.PCellDeclarationHelper.TypeDouble,
27 | Optional[float]: pya.PCellDeclarationHelper.TypeDouble,
28 | pya.PCellDeclarationHelper.TypeString: pya.PCellDeclarationHelper.TypeString,
29 | "TypeString": pya.PCellDeclarationHelper.TypeString,
30 | "str": pya.PCellDeclarationHelper.TypeString,
31 | str: pya.PCellDeclarationHelper.TypeString,
32 | Optional[str]: pya.PCellDeclarationHelper.TypeString,
33 | pya.PCellDeclarationHelper.TypeBoolean: pya.PCellDeclarationHelper.TypeBoolean,
34 | "TypeBoolean": pya.PCellDeclarationHelper.TypeBoolean,
35 | "bool": pya.PCellDeclarationHelper.TypeBoolean,
36 | bool: pya.PCellDeclarationHelper.TypeBoolean,
37 | Optional[bool]: pya.PCellDeclarationHelper.TypeBoolean,
38 | pya.PCellDeclarationHelper.TypeLayer: pya.PCellDeclarationHelper.TypeLayer,
39 | "TypeLayer": pya.PCellDeclarationHelper.TypeLayer,
40 | "LayerInfo": pya.PCellDeclarationHelper.TypeLayer,
41 | pya.LayerInfo: pya.PCellDeclarationHelper.TypeLayer,
42 | pya.PCellDeclarationHelper.TypeShape: pya.PCellDeclarationHelper.TypeShape,
43 | "TypeShape": pya.PCellDeclarationHelper.TypeShape,
44 | "Shape": pya.PCellDeclarationHelper.TypeShape,
45 | pya.Shape: pya.PCellDeclarationHelper.TypeShape,
46 | pya.PCellDeclarationHelper.TypeList: pya.PCellDeclarationHelper.TypeList,
47 | "TypeList": pya.PCellDeclarationHelper.TypeList,
48 | "list": pya.PCellDeclarationHelper.TypeList,
49 | list: pya.PCellDeclarationHelper.TypeList,
50 | Optional[list]: pya.PCellDeclarationHelper.TypeList,
51 | }
52 | try:
53 | annotation = param.annotation
54 | if annotation is Parameter.empty:
55 | annotation = type(param.default)
56 | except AttributeError:
57 | annotation = param
58 | if not annotation in type_map:
59 | raise ValueError(
60 | f"Cannot create pcell. Parameter {param.name!r} has unsupported type: {annotation!r}"
61 | )
62 | return type_map[annotation]
63 |
64 | # Internal Cell
65 | def _python_type(param: Parameter):
66 | type_map = {
67 | pya.PCellDeclarationHelper.TypeInt: int,
68 | "TypeInt": int,
69 | "int": int,
70 | int: int,
71 | pya.PCellDeclarationHelper.TypeDouble: float,
72 | "TypeDouble": float,
73 | "float": float,
74 | float: float,
75 | pya.PCellDeclarationHelper.TypeString: str,
76 | "TypeString": str,
77 | "str": str,
78 | str: str,
79 | pya.PCellDeclarationHelper.TypeBoolean: bool,
80 | "TypeBoolean": bool,
81 | "bool": bool,
82 | bool: bool,
83 | pya.PCellDeclarationHelper.TypeLayer: pya.LayerInfo,
84 | "TypeLayer": pya.LayerInfo,
85 | "LayerInfo": pya.LayerInfo,
86 | pya.LayerInfo: pya.LayerInfo,
87 | pya.PCellDeclarationHelper.TypeShape: pya.Shape,
88 | "TypeShape": pya.Shape,
89 | "Shape": pya.Shape,
90 | pya.Shape: pya.Shape,
91 | pya.PCellDeclarationHelper.TypeList: list,
92 | "TypeList": list,
93 | "list": list,
94 | list: list,
95 | }
96 | try:
97 | annotation = param.annotation
98 | if annotation is Parameter.empty:
99 | annotation = type(param.default)
100 | except AttributeError:
101 | annotation = param
102 | if not annotation in type_map:
103 | raise ValueError(
104 | f"Cannot create pcell. Parameter {param.name!r} has unsupported type: {annotation!r}"
105 | )
106 | return type_map[annotation]
107 |
108 | # Internal Cell
109 |
110 | def _validate_on_error(on_error):
111 | on_error = on_error.lower()
112 | if not on_error in ["raise", "ignore"]:
113 | raise ValueError("on_error should be 'raise' or 'ignore'.")
114 | return on_error
115 |
116 | def _validate_parameter(name, param):
117 | if param.kind == Parameter.VAR_POSITIONAL:
118 | raise ValueError(
119 | f"Cannot create pcell from functions with var positional [*args] arguments."
120 | )
121 | elif param.kind == Parameter.VAR_KEYWORD:
122 | raise ValueError(
123 | f"Cannot create pcell from functions with var keyword [**kwargs] arguments."
124 | )
125 | elif param.kind == Parameter.POSITIONAL_ONLY:
126 | raise ValueError(
127 | f"Cannot create pcell from functions with positional arguments. Please use keyword arguments."
128 | )
129 | elif (param.kind == Parameter.POSITIONAL_OR_KEYWORD) and (param.default is Parameter.empty):
130 | raise ValueError(
131 | f"Cannot create pcell from functions with positional arguments. Please use keyword arguments."
132 | )
133 | annotation = _python_type(_klayout_type(_python_type(param)))
134 | default = param.default
135 | try:
136 | default = annotation(default)
137 | except Exception:
138 | pass
139 | return Parameter(
140 | name,
141 | kind=Parameter.KEYWORD_ONLY,
142 | default=default,
143 | annotation=annotation,
144 | )
145 |
146 | def _pcell_parameters(func: Callable, on_error="raise"):
147 | sig = signature(func)
148 | params = sig.parameters
149 | on_error = _validate_on_error(on_error)
150 | new_params = {
151 | "name": Parameter(
152 | "name", kind=Parameter.KEYWORD_ONLY, default=func.__name__, annotation=str
153 | )
154 | }
155 | for name, param in params.items():
156 | try:
157 | new_params[name] = _validate_parameter(name, param)
158 | except ValueError:
159 | if on_error == "raise":
160 | raise
161 | return new_params
162 |
163 | # Cell
164 |
165 | def pcell(func=None, on_error="raise"):
166 | """create a KLayout PCell from a native python function
167 |
168 | Args:
169 | func: the function creating a KLayout cell
170 |
171 | Returns:
172 | the Klayout PCell
173 | """
174 | if func is None:
175 | return partial(pcell, on_error=on_error)
176 |
177 | params = _pcell_parameters(func, on_error=on_error)
178 |
179 |
180 | def init(self):
181 | pya.PCellDeclarationHelper.__init__(self)
182 | self._params = {}
183 | for name, param in params.items():
184 | self.param(
185 | name=name,
186 | value_type=_klayout_type(param),
187 | description=name.replace("_", " "),
188 | default=param.default,
189 | )
190 | self._params[name] = param
191 | self.func = func
192 |
193 | def call(self, **kwargs):
194 | name = kwargs.pop("name", (self.name or func.__name__))
195 | try:
196 | obj = self.func(**kwargs, name=name)
197 | except TypeError:
198 | obj = self.func(**kwargs)
199 | obj.name = name
200 | return obj
201 |
202 | def produce_impl(self):
203 | kwargs = {}
204 | for name, param in self._params.items():
205 | v = getattr(self, name)
206 | if v is None:
207 | v = param.default
208 | if v is Parameter.empty:
209 | continue
210 | kwargs[name] = v
211 | print(kwargs)
212 | cell = self(**kwargs)
213 | copy_tree(cell, self.cell, on_same_name="replace")
214 |
215 | def display_text_impl(self):
216 | return f"{self.name}<{self.__class__.__name__}>"
217 |
218 | DynamicPCell = type(
219 | func.__name__,
220 | (pya.PCellDeclarationHelper,),
221 | {
222 | "__init__": init,
223 | "__call__": call,
224 | "__doc__": func.__doc__
225 | if func.__doc__ is not None
226 | else f"a {func.__name__} PCell.",
227 | "produce_impl": produce_impl,
228 | "display_text_impl": display_text_impl,
229 | },
230 | )
231 | pcell = DynamicPCell()
232 | pcell.__signature__ = Signature(list(params.values()), return_annotation=pya.Cell)
233 | pcell.name = func.__name__
234 | return pcell
--------------------------------------------------------------------------------
/flayout/cell.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/02_cell.ipynb (unless otherwise specified).
2 |
3 | __all__ = ['reference', 'copy_tree', 'add_cells_to_layout', 'add_pcells_to_layout']
4 |
5 | # Internal Cell
6 | import sys
7 | from typing import Any, Dict, List, NamedTuple, Union
8 |
9 | import flayout as fl
10 | import pya
11 |
12 |
13 | class DoesNotExist:
14 | pass
15 |
16 | try:
17 | from gdspy import Cell as GdsPyCell
18 | except ImportError:
19 | GdsPyCell = DoesNotExist
20 |
21 | # Internal Cell
22 | COPY_IMPLEMENTATIONS = {}
23 | MAX_DEPTH = 2 * sys.getrecursionlimit()
24 | ON_SAME_NAME_DOC = """what to do when the layout containing the destination cell
25 | already contains a cell with the same name. 'skip' (default): don't
26 | add the new child cell, use the one already present in the layout
27 | in stead. This will make the instances in the new cell point to the
28 | cell already in the layout. 'replace': replace the cell
29 | already present in the layout. This will make the instances in all
30 | cells alread present in the target cell's layout point to the newly
31 | added cell. 'add_suffix': add the new cell, but change its name by
32 | adding a suffix (this is the default behavior of KLayout's native
33 | cell.copy_tree method.)"""
34 |
35 | # Internal Cell
36 | class Reference(NamedTuple):
37 | cell: pya.Cell
38 | trans: pya.CplxTrans
39 |
40 |
41 | class LibReference(NamedTuple):
42 | lib: pya.Library
43 | cell: pya.Cell
44 | trans: pya.CplxTrans
45 |
46 |
47 | class PCellReference(NamedTuple):
48 | cell: pya.PCellDeclaration
49 | trans: pya.CplxTrans
50 | params: Dict
51 |
52 |
53 | class PCellLibReference(NamedTuple):
54 | lib: pya.Library
55 | cell: pya.PCellDeclaration
56 | trans: pya.CplxTrans
57 | params: Dict
58 |
59 | # Cell
60 | def reference(*args) -> Union[Reference, LibReference, PCellReference, PCellLibReference]:
61 | """create a cell reference
62 |
63 | Note:
64 | this is not a native KLayout datastructure,
65 | but rather a convenience wrapper to ease the cell constructor API.
66 |
67 | Args:
68 | cell: the cell to create a reference for
69 | trans: the transformation to apply to the cell upon adding
70 |
71 | Returns:
72 | the cell reference
73 | """
74 | cell_str = _get_object_from_type(args, str)
75 | if cell_str is not None:
76 | num_dots = cell_str.count(".")
77 | if num_dots != 1:
78 | raise ValueError(
79 | "Reference to cell by string name should have format '.'."
80 | )
81 | lib_name, cell_name = cell_str.split(".")
82 | lib = pya.Library.library_by_name(lib_name)
83 | if lib is None:
84 | raise ValueError(
85 | f"Cannot use cell {cell_str!r}. Library {lib_name!r} does not exist (or is not registered)."
86 | )
87 |
88 | # search for pcell in library
89 | cell = lib.layout().pcell_declaration(cell_name)
90 |
91 | # search for normal cell in library
92 | if cell is None:
93 | try:
94 | cell_idx = lib.layout().cell_by_name(cell_name)
95 | cell = lib.layout().cell(cell_idx)
96 | except RuntimeError:
97 | raise ValueError(
98 | f"Cannot use cell {cell_str!r}. Cell {cell_name!r} does not exist "
99 | f"in library {lib_name!r}."
100 | )
101 |
102 | args = (
103 | arg
104 | for arg in args
105 | if not (str(arg) == cell_str or arg is cell or arg is lib)
106 | )
107 | return reference(cell, lib, *args)
108 |
109 | lib = _get_object_from_type(args, pya.Library)
110 | cell = _get_object_from_type(args, (pya.Cell, pya.PCellDeclaration))
111 | trans = _get_object_from_type(
112 | args,
113 | (pya.CplxTrans, pya.ICplxTrans, pya.DCplxTrans),
114 | default=pya.CplxTrans(1.0, 0.0, False, 0, 0),
115 | )
116 |
117 | if isinstance(cell, pya.Cell):
118 | if lib is None:
119 | return Reference(cell, trans)
120 | else:
121 | return LibReference(lib, cell, trans)
122 | elif isinstance(cell, pya.PCellDeclaration):
123 | params = _get_object_from_type(args, dict, default={})
124 | if lib is None:
125 | return PCellReference(cell, trans, params)
126 | else:
127 | return PCellLibReference(lib, cell, trans, params)
128 | else:
129 | raise ValueError(f"No cell found in reference tuple: {tuple(args)}.")
130 |
131 | def _get_object_from_type(objs, cls, default=None):
132 | selected = [obj for obj in objs if isinstance(obj, cls)]
133 | if len(selected) > 1:
134 | raise ValueError(
135 | f"Only one argument of type {cls.__name__!r} expected. "
136 | f"Got: {', '.join(repr(s) for s in selected)}."
137 | )
138 | if not selected:
139 | return default
140 | return selected[0]
141 |
142 | # Internal Cell
143 | def _copy_klayout(source, dest, on_same_name, depth=0):
144 | if depth > MAX_DEPTH:
145 | return # basically just to make type checker happy
146 |
147 | # if on_same_name == "add_suffix":
148 | # return dest.copy_tree(source)
149 | # # default klayout behavior. Not used because our implementation is
150 | # # slightly different in the order cells are added
151 |
152 | dest_layout = dest.layout()
153 | source_layout = source.layout()
154 | for layer in source_layout.layer_infos():
155 | source_idx = source_layout.layer(layer)
156 | dest_idx = dest_layout.layer(layer)
157 | for shape in source.each_shape(source_idx):
158 | dest.shapes(dest_idx).insert(shape)
159 |
160 | source_cells_map = {}
161 | for idx in source.each_child_cell():
162 | current_cell = source_layout.cell(idx)
163 | source_cells_map[idx] = _add_cell_to_layout(
164 | dest_layout, current_cell, on_same_name, depth
165 | )
166 |
167 | for inst in source.each_inst():
168 | dest_ref = pya.CellInstArray(source_cells_map[inst.cell_index], inst.cplx_trans)
169 | dest.insert(dest_ref)
170 |
171 | COPY_IMPLEMENTATIONS[pya.Cell] = _copy_klayout
172 |
173 | # Internal Cell
174 | def _copy_gdspy(source, dest, on_same_name, depth=0):
175 | if depth > MAX_DEPTH:
176 | return # basically just to make type checker happy
177 | dest_layout = dest.layout()
178 | for lr, dt in source.get_layers():
179 | dest_idx = dest_layout.layer(pya.LayerInfo(lr, dt))
180 | for arr in source.get_polygons(by_spec=(lr, dt), depth=1):
181 | dest.shapes(dest_idx).insert(
182 | pya.DPolygon([pya.DPoint(x, y) for x, y in arr])
183 | )
184 | for arr in source.get_paths(depth=1):
185 | raise NotImplementedError("Cannot convert native gdspy paths (yet).")
186 |
187 | source_cells_map = {}
188 | child_cells = sorted(
189 | set(ref.ref_cell for ref in source.references), key=lambda c: c.name
190 | )
191 | for current_cell in child_cells:
192 | if on_same_name == "skip" and dest_layout.has_cell(current_cell.name):
193 | source_cells_map[current_cell.name] = dest_layout.cell_by_name(
194 | current_cell.name
195 | )
196 | continue
197 | elif on_same_name == "replace" and dest_layout.has_cell(current_cell.name):
198 | dest_layout.delete_cell(dest_layout.cell_by_name(current_cell.name))
199 | new_cell = dest_layout.create_cell(current_cell.name)
200 | _copy_gdspy(current_cell, new_cell, on_same_name, depth=depth + 1)
201 | new_cell._layout = dest_layout
202 | source_cells_map[current_cell.name] = dest_layout.cell_by_name(
203 | current_cell.name
204 | )
205 |
206 | for ref in source.references:
207 | dest_cell = source_cells_map[ref.ref_cell.name]
208 | mag = ref.magnification if ref.magnification is not None else 1.0
209 | rot = ref.rotation if ref.rotation is not None else 0.0
210 | x, y = ref.origin
211 | dest_trans = pya.CplxTrans(
212 | mag, rot, ref.x_reflection, int(1000.0 * x), int(1000.0 * y)
213 | )
214 | dest_ref = pya.CellInstArray(dest_cell, dest_trans)
215 | dest.insert(dest_ref)
216 |
217 | if GdsPyCell is not DoesNotExist:
218 | COPY_IMPLEMENTATIONS[GdsPyCell] = _copy_gdspy
219 |
220 | # Cell
221 |
222 | def copy_tree(source: pya.Cell, dest: pya.Cell, on_same_name: str = "skip"):
223 | f"""Copy the contents of a cell into another cell
224 |
225 | Args:
226 | source: the source cell to copy the contents from
227 | dest: the destination cell to copy the contents into
228 | on_same_name: {ON_SAME_NAME_DOC}
229 | """
230 | on_same_name = _validate_on_same_name(on_same_name)
231 | for cls, _copy_impl in COPY_IMPLEMENTATIONS.items():
232 | if isinstance(source, cls):
233 | break
234 | else:
235 | raise TypeError(
236 | f"Error in copy_tree: source is not a supported cell type. "
237 | f"Got: {type(source)}. Expected: {', '.join(t.__name__ for t in COPY_IMPLEMENTATIONS)}."
238 | )
239 |
240 | _copy_impl(source, dest, on_same_name)
241 | return dest
242 |
243 | def _validate_on_same_name(on_same_name):
244 | on_same_name = on_same_name.lower()
245 | allowed_on_same_name = ["skip", "replace", "add_suffix"]
246 | if not on_same_name in allowed_on_same_name:
247 | raise ValueError(
248 | "on_same_name should be one of the following: "
249 | f"{', '.join(repr(key) for key in allowed_on_same_name)}."
250 | )
251 | return on_same_name
252 |
253 | # Cell
254 | def add_cells_to_layout(
255 | layout: pya.Layout,
256 | cells: List[pya.Cell],
257 | on_same_name: str = "skip",
258 | depth: int = 0,
259 | ):
260 | f"""Add multiple cells to a layout
261 |
262 | Args:
263 | layout: The layout to add the cell into
264 | cells: the cells to add into the layout
265 | on_same_name: {ON_SAME_NAME_DOC}
266 | """
267 | cells = sorted(cells, key=lambda c: c.hierarchy_levels())
268 | for cell in cells:
269 | _add_cell_to_layout(layout, cell, on_same_name, depth)
270 | return layout
271 |
272 | def _add_lib_cell_to_layout(layout: pya.Layout, lib: pya.Library, cell: pya.Cell):
273 | """Add a library Cell to a layout
274 |
275 | Args:
276 | layout: The layout to add the cell into
277 | lib: The library to which the cell belongs
278 | cell: the cell to add into the layout
279 | """
280 | pcell = cell.pcell_declaration()
281 | if pcell is not None:
282 | return _add_lib_pcell_to_layout(layout, lib, pcell, cell.pcell_parameters())
283 | else:
284 | cell_idx = lib.layout().cell_by_name(cell.name)
285 | return layout.add_lib_cell(lib, cell_idx)
286 |
287 |
288 | def _add_cell_to_layout(
289 | layout: pya.Layout, cell: pya.Cell, on_same_name: str = "skip", depth: int = 0
290 | ):
291 | f"""Add a cell to a layout
292 |
293 | Args:
294 | layout: The layout to add the cell into
295 | cell: the cell to add into the layout
296 | on_same_name: {ON_SAME_NAME_DOC}
297 | """
298 | if cell.is_proxy():
299 | _add_lib_cell_to_layout(layout, cell.library(), cell)
300 | if on_same_name == "skip" and layout.has_cell(cell.name):
301 | return layout.cell_by_name(cell.name)
302 | elif on_same_name == "replace" and layout.has_cell(cell.name):
303 | layout.delete_cell(layout.cell_by_name(cell.name))
304 | new_cell = layout.create_cell(cell.name)
305 | _copy_klayout(cell, new_cell, on_same_name, depth=depth + 1)
306 | new_cell._layout = layout
307 | return layout.cell_by_name(new_cell.name)
308 |
309 | # Cell
310 |
311 | def add_pcells_to_layout(layout, pcells):
312 | for pcell in pcells:
313 | func = pcell
314 | while hasattr(func, "func"):
315 | func = func.func
316 | layout.register_pcell(func.__name__, pcell)
317 |
318 | def _get_pcell_param_value(params, param):
319 | value = params.get(param.name, param.default)
320 | if param.type == pya.PCellDeclarationHelper.TypeLayer:
321 | if not isinstance(value, pya.LayerInfo):
322 | value = pya.LayerInfo(*value)
323 | return value
324 |
325 | def _add_lib_pcell_to_layout(
326 | layout: pya.Layout,
327 | lib: pya.Library,
328 | pcell: pya.PCellDeclaration,
329 | params: Dict[str, Any],
330 | ):
331 | """Add a library PCell to a layout
332 |
333 | Args:
334 | layout: The layout to add the cell into
335 | lib: The library to which the cell belongs
336 | cell: the cell to add into the layout
337 | params: the parameters to instantiate the pcell with
338 | """
339 | if isinstance(params, dict):
340 | params_list = [
341 | _get_pcell_param_value(params, p) for p in pcell.get_parameters()
342 | ]
343 | else:
344 | params_list = params
345 | return layout.add_pcell_variant(lib, pcell.id(), params_list)
--------------------------------------------------------------------------------
/source/03_pcell.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "43b948a8-2cf4-4b94-87e6-875151816a83",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp pcell"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "5a690dd2-e96d-4a34-be54-56a11e25cbf1",
16 | "metadata": {},
17 | "source": [
18 | "# PCells\n",
19 | "> Friendly PCell definitions"
20 | ]
21 | },
22 | {
23 | "cell_type": "markdown",
24 | "id": "b8de1094-781a-4529-ae23-374bf46b063b",
25 | "metadata": {},
26 | "source": [
27 | "This library offers a klayout [PCell decorator](https://flaport.github.io/flayout/pcell), which is much easier to use than the default PCell offered by the KLayout api. Moreover, the PCell decorator can be with a [GDSFactory](https://github.com/gdsfactory/gdsfactory) component function (or any function that produces a [gdspy](https://github.com/heitzmann/gdspy) cell) as well!\n",
28 | "\n",
29 | "To use FLayout PCells, first install flayout as a system python package (so that you can access it through the klayout gui):\n",
30 | "\n",
31 | "```sh\n",
32 | "cd /path/to/flayout\n",
33 | "pip install --user .\n",
34 | "```\n",
35 | "\n",
36 | "Alternatively, you can also open the klayout gui from *within* the `fl` python environment (linux only)\n",
37 | "\n",
38 | "Then within klayout add a simple macro to import the flayout example library\n",
39 | "\n",
40 | "```python\n",
41 | "from flayout.example_lib import *\n",
42 | "```\n",
43 | "\n",
44 | "Note that running this macro takes a while in KLayout (about 10 seconds).\n",
45 | "\n",
46 | "After running the macro, open a new gdsfile and find the Flayout PCells in the \"F.E.L - Flayout Example Library\", which supplies two gdsfactory-defined components: the mzi and euler bend. These components are now imported as PCells!\n",
47 | "\n",
48 | "You can have a look at how the [example library](example_lib.py) is implemented and try something similar for yourself!"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": null,
54 | "id": "420feb72-c2b2-4b99-be1f-b3ce33c36d24",
55 | "metadata": {},
56 | "outputs": [],
57 | "source": [
58 | "# exporti\n",
59 | "from functools import partial\n",
60 | "from inspect import Parameter, Signature, signature\n",
61 | "from typing import Callable, Optional\n",
62 | "\n",
63 | "import pya\n",
64 | "from flayout.cell import copy_tree\n",
65 | "\n",
66 | "CELL_CONVERTERS = {}"
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": null,
72 | "id": "51fc2ddf-b7bd-44c5-9fe6-157134a03950",
73 | "metadata": {},
74 | "outputs": [],
75 | "source": [
76 | "# exporti\n",
77 | "def _klayout_type(param: Parameter):\n",
78 | " type_map = {\n",
79 | " pya.PCellDeclarationHelper.TypeInt: pya.PCellDeclarationHelper.TypeInt,\n",
80 | " \"TypeInt\": pya.PCellDeclarationHelper.TypeInt,\n",
81 | " \"int\": pya.PCellDeclarationHelper.TypeInt,\n",
82 | " int: pya.PCellDeclarationHelper.TypeInt,\n",
83 | " Optional[int]: pya.PCellDeclarationHelper.TypeInt,\n",
84 | " pya.PCellDeclarationHelper.TypeDouble: pya.PCellDeclarationHelper.TypeDouble,\n",
85 | " \"TypeDouble\": pya.PCellDeclarationHelper.TypeDouble,\n",
86 | " \"float\": pya.PCellDeclarationHelper.TypeDouble,\n",
87 | " float: pya.PCellDeclarationHelper.TypeDouble,\n",
88 | " Optional[float]: pya.PCellDeclarationHelper.TypeDouble,\n",
89 | " pya.PCellDeclarationHelper.TypeString: pya.PCellDeclarationHelper.TypeString,\n",
90 | " \"TypeString\": pya.PCellDeclarationHelper.TypeString,\n",
91 | " \"str\": pya.PCellDeclarationHelper.TypeString,\n",
92 | " str: pya.PCellDeclarationHelper.TypeString,\n",
93 | " Optional[str]: pya.PCellDeclarationHelper.TypeString,\n",
94 | " pya.PCellDeclarationHelper.TypeBoolean: pya.PCellDeclarationHelper.TypeBoolean,\n",
95 | " \"TypeBoolean\": pya.PCellDeclarationHelper.TypeBoolean,\n",
96 | " \"bool\": pya.PCellDeclarationHelper.TypeBoolean,\n",
97 | " bool: pya.PCellDeclarationHelper.TypeBoolean,\n",
98 | " Optional[bool]: pya.PCellDeclarationHelper.TypeBoolean,\n",
99 | " pya.PCellDeclarationHelper.TypeLayer: pya.PCellDeclarationHelper.TypeLayer,\n",
100 | " \"TypeLayer\": pya.PCellDeclarationHelper.TypeLayer,\n",
101 | " \"LayerInfo\": pya.PCellDeclarationHelper.TypeLayer,\n",
102 | " pya.LayerInfo: pya.PCellDeclarationHelper.TypeLayer,\n",
103 | " pya.PCellDeclarationHelper.TypeShape: pya.PCellDeclarationHelper.TypeShape,\n",
104 | " \"TypeShape\": pya.PCellDeclarationHelper.TypeShape,\n",
105 | " \"Shape\": pya.PCellDeclarationHelper.TypeShape,\n",
106 | " pya.Shape: pya.PCellDeclarationHelper.TypeShape,\n",
107 | " pya.PCellDeclarationHelper.TypeList: pya.PCellDeclarationHelper.TypeList,\n",
108 | " \"TypeList\": pya.PCellDeclarationHelper.TypeList,\n",
109 | " \"list\": pya.PCellDeclarationHelper.TypeList,\n",
110 | " list: pya.PCellDeclarationHelper.TypeList,\n",
111 | " Optional[list]: pya.PCellDeclarationHelper.TypeList,\n",
112 | " }\n",
113 | " try:\n",
114 | " annotation = param.annotation\n",
115 | " if annotation is Parameter.empty:\n",
116 | " annotation = type(param.default)\n",
117 | " except AttributeError:\n",
118 | " annotation = param\n",
119 | " if not annotation in type_map:\n",
120 | " raise ValueError(\n",
121 | " f\"Cannot create pcell. Parameter {param.name!r} has unsupported type: {annotation!r}\"\n",
122 | " )\n",
123 | " return type_map[annotation]"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": null,
129 | "id": "99fddcfe-a532-4b5c-9ddb-b4f8016d515f",
130 | "metadata": {},
131 | "outputs": [],
132 | "source": [
133 | "# exporti\n",
134 | "def _python_type(param: Parameter):\n",
135 | " type_map = {\n",
136 | " pya.PCellDeclarationHelper.TypeInt: int,\n",
137 | " \"TypeInt\": int,\n",
138 | " \"int\": int,\n",
139 | " int: int,\n",
140 | " pya.PCellDeclarationHelper.TypeDouble: float,\n",
141 | " \"TypeDouble\": float,\n",
142 | " \"float\": float,\n",
143 | " float: float,\n",
144 | " pya.PCellDeclarationHelper.TypeString: str,\n",
145 | " \"TypeString\": str,\n",
146 | " \"str\": str,\n",
147 | " str: str,\n",
148 | " pya.PCellDeclarationHelper.TypeBoolean: bool,\n",
149 | " \"TypeBoolean\": bool,\n",
150 | " \"bool\": bool,\n",
151 | " bool: bool,\n",
152 | " pya.PCellDeclarationHelper.TypeLayer: pya.LayerInfo,\n",
153 | " \"TypeLayer\": pya.LayerInfo,\n",
154 | " \"LayerInfo\": pya.LayerInfo,\n",
155 | " pya.LayerInfo: pya.LayerInfo,\n",
156 | " pya.PCellDeclarationHelper.TypeShape: pya.Shape,\n",
157 | " \"TypeShape\": pya.Shape,\n",
158 | " \"Shape\": pya.Shape,\n",
159 | " pya.Shape: pya.Shape,\n",
160 | " pya.PCellDeclarationHelper.TypeList: list,\n",
161 | " \"TypeList\": list,\n",
162 | " \"list\": list,\n",
163 | " list: list,\n",
164 | " }\n",
165 | " try:\n",
166 | " annotation = param.annotation\n",
167 | " if annotation is Parameter.empty:\n",
168 | " annotation = type(param.default)\n",
169 | " except AttributeError:\n",
170 | " annotation = param\n",
171 | " if not annotation in type_map:\n",
172 | " raise ValueError(\n",
173 | " f\"Cannot create pcell. Parameter {param.name!r} has unsupported type: {annotation!r}\"\n",
174 | " )\n",
175 | " return type_map[annotation]"
176 | ]
177 | },
178 | {
179 | "cell_type": "code",
180 | "execution_count": null,
181 | "id": "79e106f8-d68c-479c-82de-918edacca56b",
182 | "metadata": {},
183 | "outputs": [],
184 | "source": [
185 | "# exporti\n",
186 | "\n",
187 | "def _validate_on_error(on_error):\n",
188 | " on_error = on_error.lower()\n",
189 | " if not on_error in [\"raise\", \"ignore\"]:\n",
190 | " raise ValueError(\"on_error should be 'raise' or 'ignore'.\")\n",
191 | " return on_error\n",
192 | "\n",
193 | "def _validate_parameter(name, param):\n",
194 | " if param.kind == Parameter.VAR_POSITIONAL:\n",
195 | " raise ValueError(\n",
196 | " f\"Cannot create pcell from functions with var positional [*args] arguments.\"\n",
197 | " )\n",
198 | " elif param.kind == Parameter.VAR_KEYWORD:\n",
199 | " raise ValueError(\n",
200 | " f\"Cannot create pcell from functions with var keyword [**kwargs] arguments.\"\n",
201 | " )\n",
202 | " elif param.kind == Parameter.POSITIONAL_ONLY:\n",
203 | " raise ValueError(\n",
204 | " f\"Cannot create pcell from functions with positional arguments. Please use keyword arguments.\"\n",
205 | " )\n",
206 | " elif (param.kind == Parameter.POSITIONAL_OR_KEYWORD) and (param.default is Parameter.empty):\n",
207 | " raise ValueError(\n",
208 | " f\"Cannot create pcell from functions with positional arguments. Please use keyword arguments.\"\n",
209 | " )\n",
210 | " annotation = _python_type(_klayout_type(_python_type(param)))\n",
211 | " default = param.default\n",
212 | " try:\n",
213 | " default = annotation(default)\n",
214 | " except Exception:\n",
215 | " pass\n",
216 | " return Parameter(\n",
217 | " name,\n",
218 | " kind=Parameter.KEYWORD_ONLY,\n",
219 | " default=default,\n",
220 | " annotation=annotation,\n",
221 | " )\n",
222 | "\n",
223 | "def _pcell_parameters(func: Callable, on_error=\"raise\"):\n",
224 | " sig = signature(func)\n",
225 | " params = sig.parameters\n",
226 | " on_error = _validate_on_error(on_error)\n",
227 | " new_params = {\n",
228 | " \"name\": Parameter(\n",
229 | " \"name\", kind=Parameter.KEYWORD_ONLY, default=func.__name__, annotation=str\n",
230 | " )\n",
231 | " }\n",
232 | " for name, param in params.items():\n",
233 | " try:\n",
234 | " new_params[name] = _validate_parameter(name, param)\n",
235 | " except ValueError:\n",
236 | " if on_error == \"raise\":\n",
237 | " raise\n",
238 | " return new_params"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "id": "51c6e9e0-f791-4de6-a072-8efb6a1e09db",
245 | "metadata": {},
246 | "outputs": [],
247 | "source": [
248 | "# hide\n",
249 | "def func(x: int = 3, y: float = 5.0, z=6.0, d: pya.Point = pya.Point(3, 4)):\n",
250 | " return (x, y)\n",
251 | "_pcell_parameters(func, on_error=\"ignore\")"
252 | ]
253 | },
254 | {
255 | "cell_type": "code",
256 | "execution_count": null,
257 | "id": "f3d1f3fd-e5a7-4523-aabd-66bc6d51834a",
258 | "metadata": {},
259 | "outputs": [],
260 | "source": [
261 | "# export\n",
262 | "\n",
263 | "def pcell(func=None, on_error=\"raise\"):\n",
264 | " \"\"\"create a KLayout PCell from a native python function\n",
265 | "\n",
266 | " Args:\n",
267 | " func: the function creating a KLayout cell\n",
268 | "\n",
269 | " Returns:\n",
270 | " the Klayout PCell\n",
271 | " \"\"\"\n",
272 | " if func is None:\n",
273 | " return partial(pcell, on_error=on_error)\n",
274 | " \n",
275 | " params = _pcell_parameters(func, on_error=on_error)\n",
276 | " \n",
277 | "\n",
278 | " def init(self):\n",
279 | " pya.PCellDeclarationHelper.__init__(self)\n",
280 | " self._params = {}\n",
281 | " for name, param in params.items():\n",
282 | " self.param(\n",
283 | " name=name,\n",
284 | " value_type=_klayout_type(param),\n",
285 | " description=name.replace(\"_\", \" \"),\n",
286 | " default=param.default,\n",
287 | " )\n",
288 | " self._params[name] = param\n",
289 | " self.func = func\n",
290 | "\n",
291 | " def call(self, **kwargs):\n",
292 | " name = kwargs.pop(\"name\", (self.name or func.__name__))\n",
293 | " try:\n",
294 | " obj = self.func(**kwargs, name=name)\n",
295 | " except TypeError:\n",
296 | " obj = self.func(**kwargs)\n",
297 | " obj.name = name\n",
298 | " return obj\n",
299 | "\n",
300 | " def produce_impl(self):\n",
301 | " kwargs = {}\n",
302 | " for name, param in self._params.items():\n",
303 | " v = getattr(self, name)\n",
304 | " if v is None:\n",
305 | " v = param.default\n",
306 | " if v is Parameter.empty:\n",
307 | " continue\n",
308 | " kwargs[name] = v\n",
309 | " print(kwargs)\n",
310 | " cell = self(**kwargs)\n",
311 | " copy_tree(cell, self.cell, on_same_name=\"replace\")\n",
312 | "\n",
313 | " def display_text_impl(self):\n",
314 | " return f\"{self.name}<{self.__class__.__name__}>\"\n",
315 | "\n",
316 | " DynamicPCell = type(\n",
317 | " func.__name__,\n",
318 | " (pya.PCellDeclarationHelper,),\n",
319 | " {\n",
320 | " \"__init__\": init,\n",
321 | " \"__call__\": call,\n",
322 | " \"__doc__\": func.__doc__\n",
323 | " if func.__doc__ is not None\n",
324 | " else f\"a {func.__name__} PCell.\",\n",
325 | " \"produce_impl\": produce_impl,\n",
326 | " \"display_text_impl\": display_text_impl,\n",
327 | " },\n",
328 | " )\n",
329 | " pcell = DynamicPCell()\n",
330 | " pcell.__signature__ = Signature(list(params.values()), return_annotation=pya.Cell)\n",
331 | " pcell.name = func.__name__\n",
332 | " return pcell"
333 | ]
334 | }
335 | ],
336 | "metadata": {
337 | "kernelspec": {
338 | "display_name": "fl",
339 | "language": "python",
340 | "name": "fl"
341 | }
342 | },
343 | "nbformat": 4,
344 | "nbformat_minor": 5
345 | }
346 |
--------------------------------------------------------------------------------
/flayout/bokeh.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: source/91_bokeh.ipynb (unless otherwise specified).
2 |
3 |
4 | from __future__ import annotations
5 |
6 |
7 | __all__ = ['ALPHA', 'RED', 'GREEN', 'BLUE', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'get_lyp_path',
8 | 'LayerProperty', 'LayerProperties', 'read_lyp', 'get_lyp', 'new_plot', 'adjust_plot', 'draw_polys',
9 | 'draw_poly', 'draw_path', 'draw_point', 'draw_vector', 'draw_box', 'draw_cell', 'draw_inst', 'draw_layout']
10 |
11 | # Internal Cell
12 | #nbdev_comment from __future__ import annotations
13 |
14 | import glob
15 | import os
16 | from functools import lru_cache
17 | from typing import Dict, List, Optional, Tuple, Union
18 |
19 | import bokeh.io as bio
20 | import bokeh.models as bm
21 | import bokeh.plotting as bp
22 | import numpy as np
23 | import pkg_resources
24 | import pya
25 | from lxml import etree
26 |
27 | # Cell
28 | ALPHA = 0.3
29 | RED = "#FF0000"
30 | GREEN = "#00FF00"
31 | BLUE = "#0000FF"
32 | C0 = '#1f77b4'
33 | C1 = '#ff7f0e'
34 | C2 = '#2ca02c'
35 | C3 = '#d62728'
36 | C4 = '#9467bd'
37 | C5 = '#8c564b'
38 | C6 = '#e377c2'
39 | C7 = '#7f7f7f'
40 | C8 = '#bcbd22'
41 | C9 = '#17becf'
42 |
43 | # Cell
44 | LayerProperty = Dict[str, Union[str, bool]]
45 | LayerProperties = Dict[Tuple[int, int], LayerProperty]
46 |
47 | def get_lyp_path(path: Optional[str] = None):
48 | # first, let's try "~/.klayout"
49 | if path is None:
50 | path = ""
51 | path = os.path.abspath(os.path.expanduser(path))
52 | if os.path.isdir(path):
53 | possible_paths = glob.glob(f"{path}/*.lyp")
54 | if not possible_paths:
55 | path = get_lyp_path(pkg_resources.resource_filename("flayout", "layers.lyp"))
56 | else:
57 | path = possible_paths[0]
58 | return path
59 |
60 | # Cell
61 | @lru_cache
62 | def read_lyp(path: Optional[str] = None) -> LayerProperties:
63 | """Load layer properties from a file
64 |
65 | Args:
66 | path: the path where to load the layer properties from
67 |
68 | Returns:
69 | a dictionary of layer property dictionaries
70 | """
71 | path = get_lyp_path(path)
72 | xml = etree.parse(path)
73 | root = xml.getroot()
74 | parsed: LayerProperties = {
75 | (0, 0): {
76 | "name": "",
77 | "frame-color": "#000000",
78 | "fill-color": "#000000",
79 | "visible": True,
80 | }
81 | }
82 | for properties in root.iter("properties"):
83 | name = properties.find("name")
84 | if name is not None:
85 | name, *_ = name.text.split("(")
86 | name = name.strip()
87 | else:
88 | name = ""
89 |
90 | layerstr = properties.find("source")
91 | if layerstr is not None:
92 | layerstr, *_ = layerstr.text.split("@")
93 | lr, dt = layerstr.strip().split("/")
94 | lr, dt = int(lr), int(dt)
95 | parsed[lr, dt] = {
96 | "name": name,
97 | "frame-color": properties.find("frame-color").text,
98 | "fill-color": properties.find("fill-color").text,
99 | "visible": bool(properties.find("visible").text),
100 | }
101 |
102 | return parsed
103 |
104 | # Cell
105 | @lru_cache
106 | def get_lyp(
107 | layer_info: Union[pya.LayerInfo, Tuple[int, int]], path: Optional[str] = None
108 | ) -> LayerProperty:
109 | """Load layer properties for a specific layer from a file
110 |
111 | Args:
112 | layer_info: the layer info tuple to load the layer properties for
113 | path: the path where to load the layer properties from
114 |
115 | Returns:
116 | a layer property dictionary
117 | """
118 | if isinstance(layer_info, pya.LayerInfo):
119 | layer, datatype = layer_info.layer, layer_info.datatype # type: ignore
120 | else:
121 | layer, datatype, *_ = layer_info
122 | lyps = read_lyp(path=path)
123 | lyp = lyps.get((layer, datatype), lyps[0, 0])
124 | return lyp
125 |
126 | # Internal Cell
127 | def _get_range(box: Union[pya.Box, pya.DBox]) -> Tuple[float, float, float, float, float, float]:
128 | """Get the plotting bbox for a klayout box
129 |
130 | Args:
131 | poly: the polygon to create the bbox for
132 |
133 | returns:
134 | x1, x2, y1, y2, w, h
135 | """
136 | if isinstance(box, pya.Box):
137 | box = box.to_dtype()
138 | x1, x2 = min(box.p1.x, box.p2.x), max(box.p1.x, box.p2.x)
139 | y1, y2 = min(box.p1.y, box.p2.y), max(box.p1.y, box.p2.y)
140 | w, h = x2 - x1, y2 - y1
141 | if w > 5 * h:
142 | y, h = 0.5 * (y1 + y2), w / 5
143 | y1, y2 = y - h / 2, y + h / 2
144 | if h > 5 * w:
145 | x, w = 0.5 * (x1 + x2), h / 5
146 | x1, x2 = x - w / 2, x + w / 2
147 | if w < 2.0:
148 | x1, x2 = (x2 + x1) / 2 - 1.0, (x2 + x1) / 2 + 1.0
149 | w = 2.0
150 | if h < 2.0:
151 | y1, y2 = (y2 + y1) / 2 - 1.0, (y2 + y1) / 2 + 1.0
152 | h = 2.0
153 | return x1, x2, y1, y2, w, h
154 |
155 | # Cell
156 | def new_plot(box: Union[pya.Box, pya.DBox], max_dim: Optional[float] = None) -> bp.Figure:
157 | """Create a new plot with limits determined by a bbox
158 |
159 | Args:
160 | box: the bbox of the polygon or cell to create the figure for
161 |
162 | Returns:
163 | a bokeh Figure.
164 | """
165 | if max_dim is None:
166 | max_dim = 500
167 | x0, x1, y0, y1, w, h = _get_range(box)
168 | if w > h:
169 | plot_width = max_dim
170 | plot_height = max_dim * h / w
171 | else:
172 | plot_height = max_dim
173 | plot_width = max_dim * w / h
174 | plot = bp.figure(
175 | plot_width=round(plot_width),
176 | plot_height=round(plot_height),
177 | x_range=(x0, x1),
178 | y_range=(y0, y1),
179 | match_aspect=True,
180 | toolbar_location=None,
181 | tools=[bm.PanTool(), zoom := bm.WheelZoomTool()],
182 | )
183 | plot.toolbar.active_scroll = zoom # type: ignore
184 | return plot
185 |
186 | # Cell
187 | def adjust_plot(plot: bp.Figure, box: Union[pya.Box, pya.DBox], max_dim: Optional[float] = None) -> bp.Figure:
188 | """Adjust a plot with limits determined by a bbox
189 |
190 | Args:
191 | plot: the plot to adjust the limits for
192 | box: the bbox of the polygon or cell to create the figure for
193 |
194 | Returns:
195 | a bokeh Figure.
196 | """
197 | if plot is None:
198 | return new_plot(box, max_dim=max_dim)
199 | if max_dim is None:
200 | max_dim = max(plot.height, plot.width)
201 | assert max_dim is not None
202 | x0, x1, y0, y1, w, h = _get_range(box)
203 | if w > h:
204 | plot_width = max_dim
205 | plot_height = max_dim * h / w
206 | else:
207 | plot_height = max_dim
208 | plot_width = max_dim * w / h
209 | plot.plot_width = round(plot_width)
210 | plot.plot_height = round(plot_height)
211 | plot.x_range = bm.Range1d(start=x0, end=x1)
212 | plot.y_range = bm.Range1d(start=y0, end=y1)
213 | return plot
214 |
215 | # Cell
216 | def draw_polys(
217 | plot: bp.Figure,
218 | polys: List[Union[pya.Polygon, pya.SimplePolygon, pya.DPolygon, pya.DSimplePolygon]],
219 | layer=(0, 0),
220 | fill_color=None,
221 | line_color=None,
222 | fill_alpha=ALPHA,
223 | ):
224 | """draw polygons with bokeh
225 |
226 | Args:
227 | plot: the plot to draw the polygon in
228 | polys: the polygons to draw
229 |
230 | Returns:
231 | the (inplace) modified plot containing the polygons
232 | """
233 | # some typing definitions for our own sanity...
234 |
235 | # Array of single coordinates (x OR y): ndim==1, shape=(N,).
236 | SimplePolygon = np.ndarray
237 |
238 | # List of coordinate arrays (first element: hull, other elements: holes)
239 | Polygon = List[SimplePolygon]
240 |
241 | # List of individual polygons (bokeh will XOR those polygons -> usually 1 Polygon per MultiPolygon)
242 | MultiPolygon = List[Polygon]
243 |
244 | # List of multi polygons
245 | MultiPolygons = List[MultiPolygon]
246 |
247 | xs: MultiPolygons = []
248 | ys: MultiPolygons = []
249 |
250 | for poly in polys:
251 | if isinstance(poly, (pya.Polygon, pya.SimplePolygon)):
252 | poly = poly.to_dtype()
253 |
254 | if isinstance(poly, pya.DPolygon):
255 | hull = np.asarray([(p.x, p.y) for p in poly.each_point_hull()])
256 | holes = [
257 | np.asarray([(p.x, p.y) for p in poly.each_point_hole(i)])
258 | for i in range(poly.holes())
259 | ]
260 | elif isinstance(poly, pya.DSimplePolygon):
261 | hull = np.asarray([(p.x, p.y) for p in poly.each_point()])
262 | holes = []
263 | else:
264 | raise ValueError(
265 | f"Invalid polygon type. Got: {type(poly)}. "
266 | f"Expected 'DPolygon' or 'DSimplePolygon'"
267 | )
268 |
269 | if hull.shape[0] < 3:
270 | continue
271 |
272 | plot = adjust_plot(
273 | plot, pya.Polygon([pya.DPoint(x, y) for x, y in hull]).bbox()
274 | )
275 |
276 | xs_: MultiPolygon = [[hull[:, 0], *(h[:, 0] for h in holes)]]
277 | ys_: MultiPolygon = [[hull[:, 1], *(h[:, 1] for h in holes)]]
278 | xs.append(xs_)
279 | ys.append(ys_)
280 |
281 | source = bm.ColumnDataSource({"xs": xs, "ys": ys})
282 | lyp = get_lyp(layer)
283 | patch = bm.MultiPolygons(
284 | xs="xs",
285 | ys="ys",
286 | fill_color=fill_color or lyp["fill-color"],
287 | fill_alpha=fill_alpha,
288 | line_color=line_color or lyp["frame-color"],
289 | )
290 | plot.add_glyph(source, patch)
291 | return plot
292 |
293 | # Cell
294 | def draw_poly(
295 | plot: bp.Figure,
296 | poly: Union[pya.Polygon, pya.SimplePolygon, pya.DPolygon, pya.DSimplePolygon],
297 | layer=(0, 0),
298 | fill_color=None,
299 | line_color=None,
300 | fill_alpha=ALPHA,
301 | ):
302 | """draw a polygon with bokeh
303 |
304 | Args:
305 | plot: the plot to draw the polygon in
306 | poly: the polygon to draw
307 |
308 | Returns:
309 | the (inplace) modified plot containing the polygon
310 | """
311 | plot = adjust_plot(plot, poly.bbox())
312 | return draw_polys(
313 | plot,
314 | [poly],
315 | layer=layer,
316 | fill_color=fill_color,
317 | line_color=line_color,
318 | fill_alpha=fill_alpha,
319 | )
320 |
321 | # Cell
322 | def draw_path(
323 | plot: bp.Figure,
324 | path: Union[pya.Path, pya.DPath],
325 | layer=(0, 0),
326 | fill_color=None,
327 | line_color=None,
328 | fill_alpha=ALPHA,
329 | ):
330 | """draw a path with bokeh
331 |
332 | Args:
333 | plot: the plot to draw the path in
334 | poly: the path to draw
335 |
336 | Returns:
337 | the (inplace) modified plot containing the path
338 | """
339 | return draw_polys(
340 | plot,
341 | [path.polygon()],
342 | layer=layer,
343 | fill_color=fill_color,
344 | line_color=line_color,
345 | fill_alpha=fill_alpha,
346 | )
347 |
348 | # Cell
349 | def draw_point(
350 | plot: bp.Figure,
351 | p: Union[pya.Point, pya.DPoint],
352 | layer=(0, 0),
353 | fill_color=None,
354 | line_color=None,
355 | fill_alpha=ALPHA,
356 | ):
357 | """draw a point with bokeh
358 |
359 | Args:
360 | plot: the plot to draw the point in
361 | p: the point to draw
362 |
363 | Returns:
364 | the (inplace) modified plot containing the point
365 | """
366 | if isinstance(p, pya.Point):
367 | p = p.to_dtype()
368 | v = pya.DVector(1.0, 1.0)
369 | box = pya.DBox(p - v, p + v)
370 | plot = adjust_plot(plot, box)
371 | *_, w, h = _get_range(box)
372 | radius = max(w, h) / 30
373 | lyp = get_lyp(layer)
374 | plot.circle(
375 | p.x,
376 | p.y,
377 | fill_alpha=fill_alpha,
378 | fill_color=fill_color or lyp["fill-color"],
379 | line_color=line_color or ["frame-color"],
380 | radius=radius,
381 | )
382 | return plot
383 |
384 | # Cell
385 | def draw_vector(
386 | plot: bp.Figure,
387 | v: Union[pya.Vector, pya.DVector],
388 | layer=(0, 0),
389 | fill_color=None,
390 | line_color=None,
391 | fill_alpha=ALPHA,
392 | ):
393 | """draw a vector as an arrow with bokeh
394 |
395 | Args:
396 | plot: the plot to draw the vector in
397 | v: the vector to draw
398 |
399 | Returns:
400 | the (inplace) modified plot containing the vector
401 | """
402 | if isinstance(v, pya.Vector):
403 | v = pya.DVector(v.x / 1000.0, v.y / 1000.0)
404 | box = pya.DBox(0, 0, v.x, v.y)
405 | plot = adjust_plot(plot, box, max_dim=250)
406 | lyp = get_lyp(layer)
407 | arrow_head = bm.VeeHead(
408 | fill_alpha=fill_alpha,
409 | fill_color=fill_color or lyp["fill-color"],
410 | line_color=line_color or lyp["frame-color"],
411 | )
412 | arrow = bm.Arrow(
413 | end=arrow_head,
414 | x_start=0,
415 | y_start=0,
416 | x_end=v.x,
417 | y_end=v.y,
418 | line_color=line_color or lyp["frame-color"],
419 | )
420 | plot.add_layout(arrow)
421 | return plot
422 |
423 | # Cell
424 | def _box_to_poly(box: Union[pya.Box, pya.DBox]):
425 | """convert a box into a polygon
426 |
427 | Args:
428 | box: the box to convert into a polygon
429 |
430 | Returns:
431 | the polygon
432 | """
433 | if isinstance(box, pya.Box):
434 | box = box.to_dtype()
435 | x0, y0 = box.p1.x, box.p1.y
436 | x1, y1 = box.p2.x, box.p2.y
437 | return pya.DPolygon(
438 | [pya.DPoint(x0, y0), pya.DPoint(x1, y0), pya.DPoint(x1, y1), pya.DPoint(x0, y1)]
439 | )
440 |
441 |
442 | def draw_box(
443 | plot: bp.Figure,
444 | box: Union[pya.Box, pya.DBox],
445 | fill_alpha=0.0,
446 | fill_color="#000000",
447 | line_color=GREEN,
448 | ):
449 | """draw a box with bokeh
450 |
451 | Args:
452 | plot: the plot to draw the box in
453 | box: the box to draw
454 |
455 | Returns:
456 | the (inplace) modified plot containing the box
457 | """
458 | plot = adjust_plot(plot, box)
459 | poly = _box_to_poly(box)
460 | return draw_poly(
461 | plot, poly, fill_alpha=fill_alpha, fill_color=fill_color, line_color=line_color
462 | )
463 |
464 | # Cell
465 | def _draw_shapes(plot, shapes, layer=(0, 0)):
466 | """draw shapes with bokeh
467 |
468 | Args:
469 | plot: the plot to draw the shape in
470 | shapes: the shapes to draw
471 |
472 | Returns:
473 | the (inplace) modified plot containing the shape
474 | """
475 | polys = []
476 | for shape in shapes:
477 | if shape.is_box():
478 | polys.append(_box_to_poly(shape.dbbox()))
479 | elif shape.is_path():
480 | polys.append(shape.dpath.polygon())
481 | elif shape.is_polygon():
482 | polys.append(shape.dpolygon)
483 | elif shape.is_simple_polygon():
484 | polys.append(shape.dsimple_polygon)
485 | #for poly in polys:
486 | # plot = draw_poly(plot, poly, layer=layer)
487 | return draw_polys(plot, polys, layer=layer)
488 |
489 | def draw_cell(plot, cell, draw_bbox=True):
490 | """draw a cell with bokeh
491 |
492 | Args:
493 | plot: the plot to draw the cell in
494 | cell: the cell to draw
495 |
496 | Returns:
497 | the (inplace) modified plot containing the cell
498 | """
499 | layout = pya.Layout()
500 | new_cell = layout.create_cell(cell.name)
501 | new_cell.copy_tree(cell)
502 |
503 | cell = new_cell
504 | cell = cell.flatten(-1, True)
505 | box = cell.dbbox()
506 | plot = adjust_plot(plot, box, max_dim=500)
507 | for lr in layout.layer_infos():
508 | shapes = [*cell.shapes(layout.layer(lr)).each()]
509 | plot = _draw_shapes(plot, shapes, layer=(lr.layer, lr.datatype))
510 | if draw_bbox:
511 | draw_box(plot, box)
512 | return plot
513 |
514 | # Cell
515 | def draw_inst(plot, inst, draw_bbox=True, draw_arrow=True):
516 | """draw a instance with bokeh
517 |
518 | Args:
519 | plot: the plot to draw the instance in
520 | inst: the instance to draw
521 |
522 | Returns:
523 | the (inplace) modified plot with containing the instance
524 | """
525 | _layout = pya.Layout()
526 | _cell = _layout.create_cell(inst.cell.name)
527 | _cell.copy_tree(inst.cell)
528 | _refcell = _layout.create_cell(f"ref_{inst.cell.name}")
529 | _refcell.insert(pya.CellInstArray(_layout.cell_by_name(inst.cell.name), inst.trans))
530 |
531 | plot = new_plot(_refcell.dbbox() + pya.Point(0, 0))
532 | plot = draw_vector(plot, inst.trans.disp)
533 | plot = draw_cell(plot, _refcell)
534 | plot = adjust_plot(plot, _refcell.dbbox() + pya.Point(0, 0))
535 | return plot
536 |
537 | # Cell
538 | def draw_layout(plot, layout):
539 | """draw a layout with bokeh
540 |
541 | Args:
542 | plot: the plot to draw the layout in
543 | layout: the layout to draw
544 |
545 | Returns:
546 | the (inplace) modified plot with containing the layout
547 | """
548 | plots = bp.Column(*[draw_cell(plot, cell) for cell in layout.top_cells()])
549 | return plots
--------------------------------------------------------------------------------
/source/01_factories.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "893fd908-aa0b-4daa-ac08-16c7782101f6",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp factories"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "d816dbc2-a09f-4e7a-b173-680c9fb05d38",
16 | "metadata": {},
17 | "source": [
18 | "# FLayout Factories\n",
19 | "> FLayout core factories yielding native klayout datastructures."
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "582867a3-6517-48a2-9557-7e9591a9988c",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "from typing import Dict, List, Optional, Tuple, Union, overload\n",
31 | "\n",
32 | "import pya\n",
33 | "from flayout.cell import (\n",
34 | " LibReference,\n",
35 | " PCellLibReference,\n",
36 | " PCellReference,\n",
37 | " Reference,\n",
38 | " _add_cell_to_layout,\n",
39 | " _add_lib_cell_to_layout,\n",
40 | " _add_lib_pcell_to_layout,\n",
41 | " add_cells_to_layout,\n",
42 | " add_pcells_to_layout,\n",
43 | " reference,\n",
44 | ")"
45 | ]
46 | },
47 | {
48 | "cell_type": "code",
49 | "execution_count": null,
50 | "id": "8216abd4-8a99-4f14-a11e-d2adfc0235c0",
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "# hide\n",
55 | "import flayout.notebook"
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "id": "5ad0137d-fa1d-4c1d-b63c-62a22fbadce8",
61 | "metadata": {},
62 | "source": [
63 | "#### Shape"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "id": "3b5f97a2-db22-42dc-8958-643eb256bf2d",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "# exports"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "id": "5bedd58b-748a-42c7-a38b-57fe4037ffc9",
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "# export\n",
84 | "def layer(lr: int, dt: int, name: str = \"\") -> pya.LayerInfo:\n",
85 | " layer = pya.LayerInfo(int(lr), int(dt))\n",
86 | " if name:\n",
87 | " layer.name = name\n",
88 | " return layer"
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "id": "95901d00-caee-4521-a206-158b89996575",
95 | "metadata": {},
96 | "outputs": [],
97 | "source": [
98 | "layer(19, 0)"
99 | ]
100 | },
101 | {
102 | "cell_type": "code",
103 | "execution_count": null,
104 | "id": "01e5a82c-23d4-47e4-b911-9209560499af",
105 | "metadata": {},
106 | "outputs": [],
107 | "source": [
108 | "# export\n",
109 | "def point(x: Union[float, int], y: Union[float, int]) -> pya.DPoint:\n",
110 | " \"\"\"create a KLayout DPoint\n",
111 | "\n",
112 | " Args:\n",
113 | " x: the x-value of the point\n",
114 | " y: the y-value of the point\n",
115 | "\n",
116 | " Returns:\n",
117 | " the point\n",
118 | " \"\"\"\n",
119 | " return pya.DPoint(float(x), float(y))"
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": null,
125 | "id": "29c197ac-e305-4771-ba7b-2981d5eebcac",
126 | "metadata": {},
127 | "outputs": [],
128 | "source": [
129 | "my_point = point(3, 5)\n",
130 | "my_point"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "id": "da742702-0d6d-4653-9ec6-af873a17a8e9",
137 | "metadata": {},
138 | "outputs": [],
139 | "source": [
140 | "# export\n",
141 | "def vector(x: Union[float, int], y: Union[float, int]) -> pya.DVector:\n",
142 | " \"\"\"create a KLayout DVector\n",
143 | "\n",
144 | " Args:\n",
145 | " x: the x-value of the vector\n",
146 | " y: the y-value of the vector\n",
147 | "\n",
148 | " Returns:\n",
149 | " the vector\n",
150 | " \"\"\"\n",
151 | " return pya.DVector(float(x), float(y))"
152 | ]
153 | },
154 | {
155 | "cell_type": "code",
156 | "execution_count": null,
157 | "id": "f6b92404-e19b-4331-88c2-02e27ed17008",
158 | "metadata": {},
159 | "outputs": [],
160 | "source": [
161 | "my_vector = vector(3, 5)\n",
162 | "my_vector"
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "id": "c30dea4b-0dfd-4226-b7aa-6669a8c6c43e",
169 | "metadata": {},
170 | "outputs": [],
171 | "source": [
172 | "# export\n",
173 | "\n",
174 | "def _validate_point(p):\n",
175 | " if isinstance(p, pya.Point):\n",
176 | " p = p.to_dtype()\n",
177 | " if not isinstance(p, pya.DPoint):\n",
178 | " p = pya.DPoint(*(float(x) for x in p))\n",
179 | " return p\n",
180 | "\n",
181 | "def box(p1: Union[pya.Point, pya.DPoint], p2: Union[pya.Point, pya.DPoint]) -> pya.DBox:\n",
182 | " p1 = _validate_point(p1)\n",
183 | " p2 = _validate_point(p2)\n",
184 | " return pya.DBox(p1, p2)"
185 | ]
186 | },
187 | {
188 | "cell_type": "code",
189 | "execution_count": null,
190 | "id": "321746cc-5c32-48f1-97b5-c250fcd2df7a",
191 | "metadata": {},
192 | "outputs": [],
193 | "source": [
194 | "my_box = box((0, 0), (5, 10))\n",
195 | "my_box"
196 | ]
197 | },
198 | {
199 | "cell_type": "code",
200 | "execution_count": null,
201 | "id": "1214fc0f-6d0f-4d60-b8ac-e5f1abf3e0d1",
202 | "metadata": {},
203 | "outputs": [],
204 | "source": [
205 | "# export\n",
206 | "\n",
207 | "Point = Union[pya.DPoint, Tuple[int, int], Tuple[float, float], 'np.ndarray']\n",
208 | "Points = Union[List[Point], 'np.ndarray']\n",
209 | "\n",
210 | "@overload\n",
211 | "def polygon(\n",
212 | " hull: Points,\n",
213 | " *,\n",
214 | " holes: None = None,\n",
215 | " raw: bool = False,\n",
216 | ") -> pya.DSimplePolygon:\n",
217 | " ...\n",
218 | "\n",
219 | "\n",
220 | "@overload\n",
221 | "def polygon(\n",
222 | " hull: Points,\n",
223 | " *,\n",
224 | " holes: List[Points] = None,\n",
225 | " raw: bool = False,\n",
226 | ") -> pya.DPolygon:\n",
227 | " ...\n",
228 | "\n",
229 | "\n",
230 | "def polygon(\n",
231 | " hull: Points,\n",
232 | " *,\n",
233 | " holes: Optional[List[Points]] = None,\n",
234 | " raw: bool = False,\n",
235 | ") -> Union[pya.DSimplePolygon, pya.DPolygon]:\n",
236 | " \"\"\"create a KLayout polygon\n",
237 | "\n",
238 | " Args:\n",
239 | " hull: the points that make the polygon hull\n",
240 | " holes: the collection of points that make the polygon holes\n",
241 | " raw: if true, the points won't be compressed.\n",
242 | "\n",
243 | " Returns:\n",
244 | " the polygon\n",
245 | " \"\"\"\n",
246 | " hull = [(p if isinstance(p, (pya.Point, pya.DPoint)) else point(*p)) for p in hull]\n",
247 | " if holes is None:\n",
248 | " poly = pya.DSimplePolygon(hull, raw)\n",
249 | " else:\n",
250 | " poly = pya.DPolygon(hull, raw)\n",
251 | " for hole in holes:\n",
252 | " hole = [(p if isinstance(p, pya.DPoint) else point(*p)) for p in hole]\n",
253 | " poly.insert_hole(hole)\n",
254 | " return poly"
255 | ]
256 | },
257 | {
258 | "cell_type": "markdown",
259 | "id": "83f841c1-74c1-4562-806a-cf9fdad66d1a",
260 | "metadata": {},
261 | "source": [
262 | "A simple polygon is a polygon without holes. You can create this by using `fl.polygon` and only specifying a hull."
263 | ]
264 | },
265 | {
266 | "cell_type": "code",
267 | "execution_count": null,
268 | "id": "28f151ec-2636-4810-9d3c-b648a5fda60f",
269 | "metadata": {},
270 | "outputs": [],
271 | "source": [
272 | "my_simple_poly = polygon(\n",
273 | " hull=[(0, -2), (5, -2), (1, 2)],\n",
274 | ")\n",
275 | "my_simple_poly"
276 | ]
277 | },
278 | {
279 | "cell_type": "markdown",
280 | "id": "49a271df-65a2-4e1a-be39-17a14549ce20",
281 | "metadata": {},
282 | "source": [
283 | "General Polygons (with holes) can be specified by also specifying a `holes` keyword in `fl.polygon`:"
284 | ]
285 | },
286 | {
287 | "cell_type": "code",
288 | "execution_count": null,
289 | "id": "788a1bfb-99ad-4a09-afaf-624a761e7010",
290 | "metadata": {},
291 | "outputs": [],
292 | "source": [
293 | "my_poly = polygon(\n",
294 | " hull=[(0, 0), (6, 0), (6, 4), (0, 3)],\n",
295 | " holes=[[(3, 1), (4, 1), (4, 2), (3, 2)]]\n",
296 | ")\n",
297 | "my_poly"
298 | ]
299 | },
300 | {
301 | "cell_type": "code",
302 | "execution_count": null,
303 | "id": "06b9af6d-d592-455c-8ee4-a750038e137d",
304 | "metadata": {},
305 | "outputs": [],
306 | "source": [
307 | "# export\n",
308 | "def path(\n",
309 | " pts: Points,\n",
310 | " width: float,\n",
311 | " *,\n",
312 | " bgn_ext: float = 0.0,\n",
313 | " end_ext: float = 0.0,\n",
314 | " round: bool = False,\n",
315 | ") -> pya.DPath:\n",
316 | " \"\"\"create a KLayout path\n",
317 | "\n",
318 | " Args:\n",
319 | " pts: the points that make the path\n",
320 | " width: the width of the path\n",
321 | " bgn_ext: the begin extension of the path\n",
322 | " end_ext: the end extension of the path\n",
323 | " round: round the ends of the path\n",
324 | "\n",
325 | " Returns:\n",
326 | " the path\n",
327 | " \"\"\"\n",
328 | " pts = [(p if isinstance(p, (pya.Point, pya.DPoint)) else point(*p)) for p in pts]\n",
329 | " print(pts)\n",
330 | " return pya.DPath(pts, width, bgn_ext, end_ext, round)"
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": null,
336 | "id": "7db9a373-c41c-4c9c-be7e-b50e8321175b",
337 | "metadata": {},
338 | "outputs": [],
339 | "source": [
340 | "my_path = path([(3.0, 4.0), (6.0, -1.0)], 1.2)\n",
341 | "my_path"
342 | ]
343 | },
344 | {
345 | "cell_type": "code",
346 | "execution_count": null,
347 | "id": "fa0d71f5-bd2b-4fc4-9a12-caeabc0f4fa9",
348 | "metadata": {},
349 | "outputs": [],
350 | "source": [
351 | "# export\n",
352 | "Shape = Union[pya.DBox, pya.DSimplePolygon, pya.DPolygon, pya.DPath, pya.Box, pya.SimplePolygon, pya.Polygon, pya.Path]\n",
353 | "def cell(\n",
354 | " name: str,\n",
355 | " *,\n",
356 | " shapes: Optional[Dict[pya.LayerInfo, Shape]] = None,\n",
357 | " child_cells: Optional[List[Union[pya.Cell, Tuple[pya.Cell, pya.CplxTrans]]]] = None,\n",
358 | ") -> pya.Cell:\n",
359 | " \"\"\"create a KLayout cell\n",
360 | "\n",
361 | " Args:\n",
362 | " name: the name of the cell\n",
363 | " shapes: the shapes to add to the cell\n",
364 | " child_cells: the child cells to add to the cell\n",
365 | "\n",
366 | " Returns:\n",
367 | " the cell\n",
368 | " \"\"\"\n",
369 | " layout = pya.Layout()\n",
370 | " cell = layout.create_cell(name)\n",
371 | " cell._layout = layout # make sure layout does not get destroyed\n",
372 | " if shapes:\n",
373 | " for lr, lrshapes in shapes.items():\n",
374 | " if not isinstance(lr, pya.LayerInfo):\n",
375 | " lr = layer(*lr)\n",
376 | " lr = layout.layer(lr)\n",
377 | " for i, shape in enumerate(lrshapes):\n",
378 | " \n",
379 | " # if isinstance(shape, np.ndarray):\n",
380 | " if type(shape).__name__ == 'ndarray': # yeah... I don't want to import numpy...\n",
381 | " lrshapes[i] = polygon(shape)\n",
382 | " \n",
383 | " for i, shape in enumerate(lrshapes): # TODO: insert all at once?\n",
384 | " cell.shapes(lr).insert(shape)\n",
385 | " if child_cells:\n",
386 | " cell_idxs = {}\n",
387 | " for ref in child_cells:\n",
388 | " if isinstance(ref, (str, pya.Cell, pya.PCellDeclaration)):\n",
389 | " ref = reference(ref)\n",
390 | " else:\n",
391 | " ref = reference(*ref)\n",
392 | " if isinstance(ref, Reference):\n",
393 | " if ref.cell not in cell_idxs:\n",
394 | " cell_idxs[ref.cell] = _add_cell_to_layout(layout, ref.cell)\n",
395 | " cell.insert(pya.CellInstArray(cell_idxs[ref.cell], ref.trans))\n",
396 | " elif isinstance(ref, LibReference):\n",
397 | " # don't store index in cell_idxs... Cell belongs to library and will not be copied.\n",
398 | " idx = _add_lib_cell_to_layout(layout, ref.lib, ref.cell)\n",
399 | " cell.insert(pya.CellInstArray(idx, ref.trans))\n",
400 | " elif isinstance(ref, PCellReference):\n",
401 | " raise ValueError(\n",
402 | " f\"One can only add pcells belonging to a library to to a new cell. \"\n",
403 | " f\"Add a pcell reference using the following string format to \"\n",
404 | " f\"represent the cell: '.'.\"\n",
405 | " )\n",
406 | " elif isinstance(ref, PCellLibReference):\n",
407 | " # don't store index in cell_idxs... PCell belongs to library and will not be copied.\n",
408 | " idx = _add_lib_pcell_to_layout(layout, ref.lib, ref.cell, ref.params)\n",
409 | " cell.insert(pya.CellInstArray(idx, ref.trans))\n",
410 | "\n",
411 | " return cell"
412 | ]
413 | },
414 | {
415 | "cell_type": "code",
416 | "execution_count": null,
417 | "id": "82a1aa01-bdf6-4441-ba2b-7eb3173c5501",
418 | "metadata": {},
419 | "outputs": [],
420 | "source": [
421 | "my_cell = cell(\n",
422 | " name=\"cell0\", \n",
423 | " shapes={\n",
424 | " (10, 0): [my_simple_poly, my_poly],\n",
425 | " (19, 0): [my_path],\n",
426 | " },\n",
427 | ")\n",
428 | "my_cell"
429 | ]
430 | },
431 | {
432 | "cell_type": "markdown",
433 | "id": "6c2c7979-4a0b-4414-b805-7375149a2cf4",
434 | "metadata": {},
435 | "source": [
436 | "Child cells can also be added to a cell:"
437 | ]
438 | },
439 | {
440 | "cell_type": "code",
441 | "execution_count": null,
442 | "id": "161fb58d-5112-44ff-938c-1a2653c15ca9",
443 | "metadata": {},
444 | "outputs": [],
445 | "source": [
446 | "my_other_poly = polygon([(10, 0), (11, 0), (11, 0), (10, 5)])\n",
447 | "my_other_cell = cell(\n",
448 | " name=\"cell1\", \n",
449 | " shapes={(0, 0): [my_other_poly]},\n",
450 | " child_cells=[my_cell],\n",
451 | ")\n",
452 | "my_other_cell"
453 | ]
454 | },
455 | {
456 | "cell_type": "code",
457 | "execution_count": null,
458 | "id": "690a1844-22cb-4f66-873d-5172063ff224",
459 | "metadata": {},
460 | "outputs": [],
461 | "source": [
462 | "# export\n",
463 | "def transform(\n",
464 | " mag: float = 1.0,\n",
465 | " rot: float = 0.0,\n",
466 | " mirrx: bool = False,\n",
467 | " x: float = 0.0,\n",
468 | " y: float = 0.0,\n",
469 | ") -> pya.CplxTrans:\n",
470 | " \"\"\"create a KLayout Transformation\n",
471 | "\n",
472 | " Args:\n",
473 | " mag: the magnitude of the transformation\n",
474 | " rot: the rotation of the transformation\n",
475 | " mirrx: mirror over the x-axis (x=0 line).\n",
476 | " x: translation distance in the x-direction\n",
477 | " y: translation distance in the y-direction\n",
478 | "\n",
479 | " Returns:\n",
480 | " the cell reference\n",
481 | " \"\"\"\n",
482 | " return pya.CplxTrans(mag, rot, mirrx, int(1000 * x), int(1000 * y))"
483 | ]
484 | },
485 | {
486 | "cell_type": "markdown",
487 | "id": "a3ac6d8d-b744-424a-a51c-aaea60f888c9",
488 | "metadata": {},
489 | "source": [
490 | "One can apply transformations to the child cell as well:"
491 | ]
492 | },
493 | {
494 | "cell_type": "code",
495 | "execution_count": null,
496 | "id": "d0402f6e-84cc-4927-b7c9-c14de6a39971",
497 | "metadata": {},
498 | "outputs": [],
499 | "source": [
500 | "my_third_poly = polygon([(0, 0), (6, 0), (6, 4), (0, 5)])\n",
501 | "my_third_cell = cell(\n",
502 | " name=\"cell2\", \n",
503 | " shapes={(0, 0): [my_third_poly]},\n",
504 | " child_cells=[\n",
505 | " (my_other_cell, transform(mirrx=True, x=12.0, y=3.0)),\n",
506 | " ],\n",
507 | ")\n",
508 | "my_third_cell"
509 | ]
510 | },
511 | {
512 | "cell_type": "markdown",
513 | "id": "8db53b55-5fda-4265-9e15-7c18ea5e6aed",
514 | "metadata": {},
515 | "source": [
516 | "The combination of a child cell and a transformation is called an `instance` of a cell (sometimes also called a reference):"
517 | ]
518 | },
519 | {
520 | "cell_type": "code",
521 | "execution_count": null,
522 | "id": "58c43740-7686-45b4-ac4d-d15d23d9de11",
523 | "metadata": {},
524 | "outputs": [],
525 | "source": [
526 | "inst = next(my_third_cell.each_inst()) # get the only instance\n",
527 | "inst"
528 | ]
529 | },
530 | {
531 | "cell_type": "code",
532 | "execution_count": null,
533 | "id": "957503e0-ce66-4b7b-86a9-07ae6b44d4c7",
534 | "metadata": {},
535 | "outputs": [],
536 | "source": [
537 | "# export\n",
538 | "def layout(*, cells: Optional[List[pya.Cell]] = None) -> pya.Layout:\n",
539 | " \"\"\"create a KLayout Layout\n",
540 | "\n",
541 | " Args:\n",
542 | " cells: the cells to add to the layout\n",
543 | "\n",
544 | " Returns:\n",
545 | " the KLayout layout\n",
546 | " \"\"\"\n",
547 | " layout = pya.Layout()\n",
548 | " layout.dbu = 0.001 # hard coded for now\n",
549 | " if cells:\n",
550 | " add_cells_to_layout(layout, cells)\n",
551 | " return layout"
552 | ]
553 | },
554 | {
555 | "cell_type": "markdown",
556 | "id": "818065f9-6f08-42f8-a30f-63ba38a66543",
557 | "metadata": {},
558 | "source": [
559 | "Finally, a layout is a collection of multiple cells. If a cell is not a sub cell of another cell, it will be considered a top-level cell. Moreover, one cannot have cyclic references within a layout."
560 | ]
561 | },
562 | {
563 | "cell_type": "markdown",
564 | "id": "231cf13d-2ff6-4db0-8724-3d82ac305fd9",
565 | "metadata": {},
566 | "source": [
567 | "Let's create a layout with two top-level cells:"
568 | ]
569 | },
570 | {
571 | "cell_type": "code",
572 | "execution_count": null,
573 | "id": "bcf1a948-5ab5-4005-9c88-0cd218ad8745",
574 | "metadata": {},
575 | "outputs": [],
576 | "source": [
577 | "my_independent_cell = cell(\n",
578 | " name='independent_cell',\n",
579 | " shapes={(19, 0): [polygon([(6, 0), (10, -2), (10, 0)])]}\n",
580 | ")\n",
581 | " \n",
582 | "# even though we specify four cells for our layout, only two of them are independent\n",
583 | "# i.e. some of the cells we are added are actually child cells of other cells\n",
584 | "# top level cells in this case are 'my_independent_cell' and 'my_third_cell'\n",
585 | "my_layout = layout(cells=[my_independent_cell, my_cell, my_other_cell, my_third_cell])\n",
586 | "# my_layout = layout(cells=[my_independent_cell, my_third_cell]) # equivalent\n",
587 | "\n",
588 | "my_layout"
589 | ]
590 | },
591 | {
592 | "cell_type": "code",
593 | "execution_count": null,
594 | "id": "9ffec107-ac98-4317-aa7f-062289267ab6",
595 | "metadata": {},
596 | "outputs": [],
597 | "source": [
598 | "# export\n",
599 | "def library(\n",
600 | " name: str,\n",
601 | " *,\n",
602 | " cells: Optional[List[pya.Cell]] = None,\n",
603 | " pcells: Optional[List[pya.Cell]] = None,\n",
604 | " description: str = \"\",\n",
605 | ") -> pya.Library:\n",
606 | " \"\"\"create a KLayout Library\n",
607 | "\n",
608 | " Args:\n",
609 | " name: the name of the library\n",
610 | " cells: the cells to add to the library\n",
611 | " pcells: the pcells to add to the library\n",
612 | "\n",
613 | " Returns:\n",
614 | " the KLayout library\n",
615 | " \"\"\"\n",
616 | " lib = pya.Library()\n",
617 | " if description:\n",
618 | " lib.description = description\n",
619 | " layout = lib.layout()\n",
620 | " layout.dbu = 0.001 # hard coded for now\n",
621 | " if cells:\n",
622 | " add_cells_to_layout(layout, cells)\n",
623 | " if pcells:\n",
624 | " add_pcells_to_layout(layout, pcells)\n",
625 | " lib.register(name)\n",
626 | " return lib"
627 | ]
628 | }
629 | ],
630 | "metadata": {
631 | "kernelspec": {
632 | "display_name": "fl",
633 | "language": "python",
634 | "name": "fl"
635 | }
636 | },
637 | "nbformat": 4,
638 | "nbformat_minor": 5
639 | }
640 |
--------------------------------------------------------------------------------
/source/02_cell.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "38ee6b54-23c7-4577-a8ba-67abffa6678e",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp cell"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "d816dbc2-a09f-4e7a-b173-680c9fb05d38",
16 | "metadata": {},
17 | "source": [
18 | "# FLayout Cell\n",
19 | "> FLayout cell functionality"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "582867a3-6517-48a2-9557-7e9591a9988c",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "import sys\n",
31 | "from typing import Any, Dict, List, NamedTuple, Union\n",
32 | "\n",
33 | "import flayout as fl\n",
34 | "import pya\n",
35 | "\n",
36 | "\n",
37 | "class DoesNotExist:\n",
38 | " pass\n",
39 | "\n",
40 | "try:\n",
41 | " from gdspy import Cell as GdsPyCell\n",
42 | "except ImportError:\n",
43 | " GdsPyCell = DoesNotExist"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "id": "a4b7930a-be8d-4814-875b-ead652c8b9f8",
50 | "metadata": {},
51 | "outputs": [],
52 | "source": [
53 | "# hide\n",
54 | "import flayout as fl\n",
55 | "import bokeh.io as bio\n",
56 | "import flayout.bokeh as flbk\n",
57 | "import flayout.notebook"
58 | ]
59 | },
60 | {
61 | "cell_type": "code",
62 | "execution_count": null,
63 | "id": "14e1e034-d255-4b09-bf47-01271ad0fe8b",
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "# exporti\n",
68 | "COPY_IMPLEMENTATIONS = {}\n",
69 | "MAX_DEPTH = 2 * sys.getrecursionlimit()\n",
70 | "ON_SAME_NAME_DOC = \"\"\"what to do when the layout containing the destination cell\n",
71 | " already contains a cell with the same name. 'skip' (default): don't\n",
72 | " add the new child cell, use the one already present in the layout\n",
73 | " in stead. This will make the instances in the new cell point to the\n",
74 | " cell already in the layout. 'replace': replace the cell\n",
75 | " already present in the layout. This will make the instances in all\n",
76 | " cells alread present in the target cell's layout point to the newly\n",
77 | " added cell. 'add_suffix': add the new cell, but change its name by\n",
78 | " adding a suffix (this is the default behavior of KLayout's native\n",
79 | " cell.copy_tree method.)\"\"\""
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "id": "416e429c-0f4f-4344-b2ef-2a4794a03ada",
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "# exporti\n",
90 | "class Reference(NamedTuple):\n",
91 | " cell: pya.Cell\n",
92 | " trans: pya.CplxTrans\n",
93 | "\n",
94 | "\n",
95 | "class LibReference(NamedTuple):\n",
96 | " lib: pya.Library\n",
97 | " cell: pya.Cell\n",
98 | " trans: pya.CplxTrans\n",
99 | "\n",
100 | "\n",
101 | "class PCellReference(NamedTuple):\n",
102 | " cell: pya.PCellDeclaration\n",
103 | " trans: pya.CplxTrans\n",
104 | " params: Dict\n",
105 | "\n",
106 | "\n",
107 | "class PCellLibReference(NamedTuple):\n",
108 | " lib: pya.Library\n",
109 | " cell: pya.PCellDeclaration\n",
110 | " trans: pya.CplxTrans\n",
111 | " params: Dict"
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "execution_count": null,
117 | "id": "addd12cc-ed01-4bbe-9209-c4c60300c12f",
118 | "metadata": {},
119 | "outputs": [],
120 | "source": [
121 | "# export\n",
122 | "def reference(*args) -> Union[Reference, LibReference, PCellReference, PCellLibReference]:\n",
123 | " \"\"\"create a cell reference\n",
124 | "\n",
125 | " Note:\n",
126 | " this is not a native KLayout datastructure,\n",
127 | " but rather a convenience wrapper to ease the cell constructor API.\n",
128 | "\n",
129 | " Args:\n",
130 | " cell: the cell to create a reference for\n",
131 | " trans: the transformation to apply to the cell upon adding\n",
132 | "\n",
133 | " Returns:\n",
134 | " the cell reference\n",
135 | " \"\"\"\n",
136 | " cell_str = _get_object_from_type(args, str)\n",
137 | " if cell_str is not None:\n",
138 | " num_dots = cell_str.count(\".\")\n",
139 | " if num_dots != 1:\n",
140 | " raise ValueError(\n",
141 | " \"Reference to cell by string name should have format '.'.\"\n",
142 | " )\n",
143 | " lib_name, cell_name = cell_str.split(\".\")\n",
144 | " lib = pya.Library.library_by_name(lib_name)\n",
145 | " if lib is None:\n",
146 | " raise ValueError(\n",
147 | " f\"Cannot use cell {cell_str!r}. Library {lib_name!r} does not exist (or is not registered).\"\n",
148 | " )\n",
149 | "\n",
150 | " # search for pcell in library\n",
151 | " cell = lib.layout().pcell_declaration(cell_name)\n",
152 | "\n",
153 | " # search for normal cell in library\n",
154 | " if cell is None:\n",
155 | " try:\n",
156 | " cell_idx = lib.layout().cell_by_name(cell_name)\n",
157 | " cell = lib.layout().cell(cell_idx)\n",
158 | " except RuntimeError:\n",
159 | " raise ValueError(\n",
160 | " f\"Cannot use cell {cell_str!r}. Cell {cell_name!r} does not exist \"\n",
161 | " f\"in library {lib_name!r}.\"\n",
162 | " )\n",
163 | "\n",
164 | " args = (\n",
165 | " arg\n",
166 | " for arg in args\n",
167 | " if not (str(arg) == cell_str or arg is cell or arg is lib)\n",
168 | " )\n",
169 | " return reference(cell, lib, *args)\n",
170 | "\n",
171 | " lib = _get_object_from_type(args, pya.Library)\n",
172 | " cell = _get_object_from_type(args, (pya.Cell, pya.PCellDeclaration))\n",
173 | " trans = _get_object_from_type(\n",
174 | " args,\n",
175 | " (pya.CplxTrans, pya.ICplxTrans, pya.DCplxTrans),\n",
176 | " default=pya.CplxTrans(1.0, 0.0, False, 0, 0),\n",
177 | " )\n",
178 | "\n",
179 | " if isinstance(cell, pya.Cell):\n",
180 | " if lib is None:\n",
181 | " return Reference(cell, trans)\n",
182 | " else:\n",
183 | " return LibReference(lib, cell, trans)\n",
184 | " elif isinstance(cell, pya.PCellDeclaration):\n",
185 | " params = _get_object_from_type(args, dict, default={})\n",
186 | " if lib is None:\n",
187 | " return PCellReference(cell, trans, params)\n",
188 | " else:\n",
189 | " return PCellLibReference(lib, cell, trans, params)\n",
190 | " else:\n",
191 | " raise ValueError(f\"No cell found in reference tuple: {tuple(args)}.\")\n",
192 | " \n",
193 | "def _get_object_from_type(objs, cls, default=None):\n",
194 | " selected = [obj for obj in objs if isinstance(obj, cls)]\n",
195 | " if len(selected) > 1:\n",
196 | " raise ValueError(\n",
197 | " f\"Only one argument of type {cls.__name__!r} expected. \"\n",
198 | " f\"Got: {', '.join(repr(s) for s in selected)}.\"\n",
199 | " )\n",
200 | " if not selected:\n",
201 | " return default\n",
202 | " return selected[0]"
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": null,
208 | "id": "4ec35518-ce94-40f0-9010-db9910207e9a",
209 | "metadata": {},
210 | "outputs": [],
211 | "source": [
212 | "from numpy import linspace, pi, stack, cos, sin\n",
213 | "triangle = fl.cell(\n",
214 | " name='triangle',\n",
215 | " shapes={(19, 0): [fl.polygon([(6, 0), (10, -2), (10, 1)])]}\n",
216 | ")\n",
217 | "\n",
218 | "@fl.pcell\n",
219 | "def circle(\n",
220 | " name=\"circle\",\n",
221 | " radius: float = 0.1,\n",
222 | " layer: pya.LayerInfo = pya.LayerInfo(1, 0),\n",
223 | " num_points=64,\n",
224 | "):\n",
225 | " t = linspace(0, 2 * pi, num_points, endpoint=False)\n",
226 | " xy = stack([radius * cos(t), radius * sin(t)], -1)\n",
227 | " cell = fl.cell(\n",
228 | " name,\n",
229 | " shapes={layer: [xy]},\n",
230 | " )\n",
231 | " return cell\n",
232 | "\n",
233 | "lib = fl.library(\n",
234 | " \"lib\",\n",
235 | " pcells=[circle],\n",
236 | " cells=[triangle],\n",
237 | " description=\"my library\",\n",
238 | ")"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "id": "5dc060db-12cb-4806-8de8-345e6005164a",
245 | "metadata": {},
246 | "outputs": [],
247 | "source": [
248 | "ref = reference(triangle)\n",
249 | "print(f\"{type(ref).__name__}(cell={ref.cell.name}, trans={ref.trans})\")"
250 | ]
251 | },
252 | {
253 | "cell_type": "code",
254 | "execution_count": null,
255 | "id": "53de01d2-af92-44bb-a211-c68e0fd7e110",
256 | "metadata": {},
257 | "outputs": [],
258 | "source": [
259 | "ref = reference(triangle, fl.transform(mag=2.0, rot=90.0, x=3, y=3))\n",
260 | "print(f\"{type(ref).__name__}(cell={ref.cell.name}, trans={ref.trans})\")"
261 | ]
262 | },
263 | {
264 | "cell_type": "code",
265 | "execution_count": null,
266 | "id": "5381fe91-3a44-4dc3-b0ab-4e3111e78ed6",
267 | "metadata": {},
268 | "outputs": [],
269 | "source": [
270 | "ref = reference(lib, triangle, fl.transform(mag=2.0, rot=90.0, x=3, y=3))\n",
271 | "print(f\"{type(ref).__name__}(lib={ref.lib.name()}, cell={ref.cell.name}, trans={ref.trans})\")"
272 | ]
273 | },
274 | {
275 | "cell_type": "code",
276 | "execution_count": null,
277 | "id": "38841188-da75-462b-9578-91e7dfc35d00",
278 | "metadata": {},
279 | "outputs": [],
280 | "source": [
281 | "ref = reference(lib, triangle, fl.transform(mag=2.0, rot=90.0, x=3, y=3))\n",
282 | "print(f\"{type(ref).__name__}(lib={ref.lib.name()}, cell={ref.cell.name}, trans={ref.trans})\")"
283 | ]
284 | },
285 | {
286 | "cell_type": "code",
287 | "execution_count": null,
288 | "id": "69b78f35-3c7e-4d8b-aae1-9fd9f083e5eb",
289 | "metadata": {},
290 | "outputs": [],
291 | "source": [
292 | "ref = reference(circle, fl.transform(mag=2.0, rot=90.0, x=3, y=3))\n",
293 | "print(f\"{type(ref).__name__}(cell={ref.cell.name}, trans={ref.trans})\")"
294 | ]
295 | },
296 | {
297 | "cell_type": "code",
298 | "execution_count": null,
299 | "id": "598d3122-9aff-445c-b5e0-bbe37bb64aed",
300 | "metadata": {},
301 | "outputs": [],
302 | "source": [
303 | "ref = reference(lib, circle, fl.transform(mag=2.0, rot=90.0, x=3, y=3))\n",
304 | "print(f\"{type(ref).__name__}(lib={ref.lib.name()}, cell={ref.cell.name}, trans={ref.trans})\")"
305 | ]
306 | },
307 | {
308 | "cell_type": "code",
309 | "execution_count": null,
310 | "id": "98cc610a-6649-4b7b-adb4-848548c228e1",
311 | "metadata": {},
312 | "outputs": [],
313 | "source": [
314 | "# exporti\n",
315 | "def _copy_klayout(source, dest, on_same_name, depth=0):\n",
316 | " if depth > MAX_DEPTH:\n",
317 | " return # basically just to make type checker happy\n",
318 | "\n",
319 | " # if on_same_name == \"add_suffix\":\n",
320 | " # return dest.copy_tree(source)\n",
321 | " # # default klayout behavior. Not used because our implementation is\n",
322 | " # # slightly different in the order cells are added\n",
323 | "\n",
324 | " dest_layout = dest.layout()\n",
325 | " source_layout = source.layout()\n",
326 | " for layer in source_layout.layer_infos():\n",
327 | " source_idx = source_layout.layer(layer)\n",
328 | " dest_idx = dest_layout.layer(layer)\n",
329 | " for shape in source.each_shape(source_idx):\n",
330 | " dest.shapes(dest_idx).insert(shape)\n",
331 | "\n",
332 | " source_cells_map = {}\n",
333 | " for idx in source.each_child_cell():\n",
334 | " current_cell = source_layout.cell(idx)\n",
335 | " source_cells_map[idx] = _add_cell_to_layout(\n",
336 | " dest_layout, current_cell, on_same_name, depth\n",
337 | " )\n",
338 | "\n",
339 | " for inst in source.each_inst():\n",
340 | " dest_ref = pya.CellInstArray(source_cells_map[inst.cell_index], inst.cplx_trans)\n",
341 | " dest.insert(dest_ref)\n",
342 | " \n",
343 | "COPY_IMPLEMENTATIONS[pya.Cell] = _copy_klayout"
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": null,
349 | "id": "9e85461a-a930-4313-b0e4-d16e8b446da3",
350 | "metadata": {},
351 | "outputs": [],
352 | "source": [
353 | "# exporti\n",
354 | "def _copy_gdspy(source, dest, on_same_name, depth=0):\n",
355 | " if depth > MAX_DEPTH:\n",
356 | " return # basically just to make type checker happy\n",
357 | " dest_layout = dest.layout()\n",
358 | " for lr, dt in source.get_layers():\n",
359 | " dest_idx = dest_layout.layer(pya.LayerInfo(lr, dt))\n",
360 | " for arr in source.get_polygons(by_spec=(lr, dt), depth=1):\n",
361 | " dest.shapes(dest_idx).insert(\n",
362 | " pya.DPolygon([pya.DPoint(x, y) for x, y in arr])\n",
363 | " )\n",
364 | " for arr in source.get_paths(depth=1):\n",
365 | " raise NotImplementedError(\"Cannot convert native gdspy paths (yet).\")\n",
366 | "\n",
367 | " source_cells_map = {}\n",
368 | " child_cells = sorted(\n",
369 | " set(ref.ref_cell for ref in source.references), key=lambda c: c.name\n",
370 | " )\n",
371 | " for current_cell in child_cells:\n",
372 | " if on_same_name == \"skip\" and dest_layout.has_cell(current_cell.name):\n",
373 | " source_cells_map[current_cell.name] = dest_layout.cell_by_name(\n",
374 | " current_cell.name\n",
375 | " )\n",
376 | " continue\n",
377 | " elif on_same_name == \"replace\" and dest_layout.has_cell(current_cell.name):\n",
378 | " dest_layout.delete_cell(dest_layout.cell_by_name(current_cell.name))\n",
379 | " new_cell = dest_layout.create_cell(current_cell.name)\n",
380 | " _copy_gdspy(current_cell, new_cell, on_same_name, depth=depth + 1)\n",
381 | " new_cell._layout = dest_layout\n",
382 | " source_cells_map[current_cell.name] = dest_layout.cell_by_name(\n",
383 | " current_cell.name\n",
384 | " )\n",
385 | "\n",
386 | " for ref in source.references:\n",
387 | " dest_cell = source_cells_map[ref.ref_cell.name]\n",
388 | " mag = ref.magnification if ref.magnification is not None else 1.0\n",
389 | " rot = ref.rotation if ref.rotation is not None else 0.0\n",
390 | " x, y = ref.origin\n",
391 | " dest_trans = pya.CplxTrans(\n",
392 | " mag, rot, ref.x_reflection, int(1000.0 * x), int(1000.0 * y)\n",
393 | " )\n",
394 | " dest_ref = pya.CellInstArray(dest_cell, dest_trans)\n",
395 | " dest.insert(dest_ref)\n",
396 | " \n",
397 | "if GdsPyCell is not DoesNotExist:\n",
398 | " COPY_IMPLEMENTATIONS[GdsPyCell] = _copy_gdspy"
399 | ]
400 | },
401 | {
402 | "cell_type": "code",
403 | "execution_count": null,
404 | "id": "ec99e453-8e9d-4bc2-9fbd-ad2d61f273f0",
405 | "metadata": {},
406 | "outputs": [],
407 | "source": [
408 | "# export\n",
409 | "\n",
410 | "def copy_tree(source: pya.Cell, dest: pya.Cell, on_same_name: str = \"skip\"):\n",
411 | " f\"\"\"Copy the contents of a cell into another cell\n",
412 | "\n",
413 | " Args:\n",
414 | " source: the source cell to copy the contents from\n",
415 | " dest: the destination cell to copy the contents into\n",
416 | " on_same_name: {ON_SAME_NAME_DOC}\n",
417 | " \"\"\"\n",
418 | " on_same_name = _validate_on_same_name(on_same_name)\n",
419 | " for cls, _copy_impl in COPY_IMPLEMENTATIONS.items():\n",
420 | " if isinstance(source, cls):\n",
421 | " break\n",
422 | " else:\n",
423 | " raise TypeError(\n",
424 | " f\"Error in copy_tree: source is not a supported cell type. \"\n",
425 | " f\"Got: {type(source)}. Expected: {', '.join(t.__name__ for t in COPY_IMPLEMENTATIONS)}.\"\n",
426 | " )\n",
427 | " \n",
428 | " _copy_impl(source, dest, on_same_name)\n",
429 | " return dest\n",
430 | "\n",
431 | "def _validate_on_same_name(on_same_name):\n",
432 | " on_same_name = on_same_name.lower()\n",
433 | " allowed_on_same_name = [\"skip\", \"replace\", \"add_suffix\"]\n",
434 | " if not on_same_name in allowed_on_same_name:\n",
435 | " raise ValueError(\n",
436 | " \"on_same_name should be one of the following: \"\n",
437 | " f\"{', '.join(repr(key) for key in allowed_on_same_name)}.\"\n",
438 | " )\n",
439 | " return on_same_name"
440 | ]
441 | },
442 | {
443 | "cell_type": "code",
444 | "execution_count": null,
445 | "id": "078ac576-523a-493c-abfa-31f9cd998b48",
446 | "metadata": {},
447 | "outputs": [],
448 | "source": [
449 | "my_cell = fl.cell(\n",
450 | " name='my_cell',\n",
451 | " shapes={(19, 0): [fl.polygon([(0, 0), (5, 0), (5, 5), (0, 5)])]}\n",
452 | ")\n",
453 | "my_cell"
454 | ]
455 | },
456 | {
457 | "cell_type": "code",
458 | "execution_count": null,
459 | "id": "b1a6c584-c7bb-4d75-b0a4-339973ccce25",
460 | "metadata": {},
461 | "outputs": [],
462 | "source": [
463 | "copy_tree(triangle, my_cell)\n",
464 | "my_cell"
465 | ]
466 | },
467 | {
468 | "cell_type": "code",
469 | "execution_count": null,
470 | "id": "fe299bab-2055-4b7c-b577-3e41913dd7a3",
471 | "metadata": {},
472 | "outputs": [],
473 | "source": [
474 | "# export\n",
475 | "def add_cells_to_layout(\n",
476 | " layout: pya.Layout,\n",
477 | " cells: List[pya.Cell],\n",
478 | " on_same_name: str = \"skip\",\n",
479 | " depth: int = 0,\n",
480 | "):\n",
481 | " f\"\"\"Add multiple cells to a layout\n",
482 | "\n",
483 | " Args:\n",
484 | " layout: The layout to add the cell into\n",
485 | " cells: the cells to add into the layout\n",
486 | " on_same_name: {ON_SAME_NAME_DOC}\n",
487 | " \"\"\"\n",
488 | " cells = sorted(cells, key=lambda c: c.hierarchy_levels())\n",
489 | " for cell in cells:\n",
490 | " _add_cell_to_layout(layout, cell, on_same_name, depth)\n",
491 | " return layout\n",
492 | " \n",
493 | "def _add_lib_cell_to_layout(layout: pya.Layout, lib: pya.Library, cell: pya.Cell):\n",
494 | " \"\"\"Add a library Cell to a layout\n",
495 | "\n",
496 | " Args:\n",
497 | " layout: The layout to add the cell into\n",
498 | " lib: The library to which the cell belongs\n",
499 | " cell: the cell to add into the layout\n",
500 | " \"\"\"\n",
501 | " pcell = cell.pcell_declaration()\n",
502 | " if pcell is not None:\n",
503 | " return _add_lib_pcell_to_layout(layout, lib, pcell, cell.pcell_parameters())\n",
504 | " else:\n",
505 | " cell_idx = lib.layout().cell_by_name(cell.name)\n",
506 | " return layout.add_lib_cell(lib, cell_idx)\n",
507 | " \n",
508 | "\n",
509 | "def _add_cell_to_layout(\n",
510 | " layout: pya.Layout, cell: pya.Cell, on_same_name: str = \"skip\", depth: int = 0\n",
511 | "):\n",
512 | " f\"\"\"Add a cell to a layout\n",
513 | "\n",
514 | " Args:\n",
515 | " layout: The layout to add the cell into\n",
516 | " cell: the cell to add into the layout\n",
517 | " on_same_name: {ON_SAME_NAME_DOC}\n",
518 | " \"\"\"\n",
519 | " if cell.is_proxy():\n",
520 | " _add_lib_cell_to_layout(layout, cell.library(), cell)\n",
521 | " if on_same_name == \"skip\" and layout.has_cell(cell.name):\n",
522 | " return layout.cell_by_name(cell.name)\n",
523 | " elif on_same_name == \"replace\" and layout.has_cell(cell.name):\n",
524 | " layout.delete_cell(layout.cell_by_name(cell.name))\n",
525 | " new_cell = layout.create_cell(cell.name)\n",
526 | " _copy_klayout(cell, new_cell, on_same_name, depth=depth + 1)\n",
527 | " new_cell._layout = layout\n",
528 | " return layout.cell_by_name(new_cell.name)"
529 | ]
530 | },
531 | {
532 | "cell_type": "code",
533 | "execution_count": null,
534 | "id": "7b7bf0aa-b75a-4aed-ae6b-42e7f6b03987",
535 | "metadata": {},
536 | "outputs": [],
537 | "source": [
538 | "layout = fl.layout()\n",
539 | "add_cells_to_layout(layout, cells=[my_cell, triangle, circle()])\n",
540 | "fig =flbk.draw_layout(None, layout)\n",
541 | "bio.show(fig)"
542 | ]
543 | },
544 | {
545 | "cell_type": "code",
546 | "execution_count": null,
547 | "id": "2c799eaf-534c-4284-96a0-82f835e0818f",
548 | "metadata": {},
549 | "outputs": [],
550 | "source": [
551 | "# export\n",
552 | "\n",
553 | "def add_pcells_to_layout(layout, pcells):\n",
554 | " for pcell in pcells:\n",
555 | " func = pcell\n",
556 | " while hasattr(func, \"func\"):\n",
557 | " func = func.func\n",
558 | " layout.register_pcell(func.__name__, pcell)\n",
559 | "\n",
560 | "def _get_pcell_param_value(params, param):\n",
561 | " value = params.get(param.name, param.default)\n",
562 | " if param.type == pya.PCellDeclarationHelper.TypeLayer:\n",
563 | " if not isinstance(value, pya.LayerInfo):\n",
564 | " value = pya.LayerInfo(*value)\n",
565 | " return value\n",
566 | "\n",
567 | "def _add_lib_pcell_to_layout(\n",
568 | " layout: pya.Layout,\n",
569 | " lib: pya.Library,\n",
570 | " pcell: pya.PCellDeclaration,\n",
571 | " params: Dict[str, Any],\n",
572 | "):\n",
573 | " \"\"\"Add a library PCell to a layout\n",
574 | "\n",
575 | " Args:\n",
576 | " layout: The layout to add the cell into\n",
577 | " lib: The library to which the cell belongs\n",
578 | " cell: the cell to add into the layout\n",
579 | " params: the parameters to instantiate the pcell with\n",
580 | " \"\"\"\n",
581 | " if isinstance(params, dict):\n",
582 | " params_list = [\n",
583 | " _get_pcell_param_value(params, p) for p in pcell.get_parameters()\n",
584 | " ]\n",
585 | " else:\n",
586 | " params_list = params\n",
587 | " return layout.add_pcell_variant(lib, pcell.id(), params_list)"
588 | ]
589 | },
590 | {
591 | "cell_type": "code",
592 | "execution_count": null,
593 | "id": "a8ec2686-f720-4f5e-867d-13ca856c9497",
594 | "metadata": {},
595 | "outputs": [],
596 | "source": [
597 | "add_pcells_to_layout(layout, pcells=[circle])\n",
598 | "layout"
599 | ]
600 | }
601 | ],
602 | "metadata": {
603 | "kernelspec": {
604 | "display_name": "fl",
605 | "language": "python",
606 | "name": "fl"
607 | }
608 | },
609 | "nbformat": 4,
610 | "nbformat_minor": 5
611 | }
612 |
--------------------------------------------------------------------------------
/source/91_bokeh.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "e575b56f-0caf-49cf-a935-fe5b931395b2",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# default_exp bokeh"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "7031f6c1-8422-4092-85a3-ebd76b410b10",
16 | "metadata": {},
17 | "source": [
18 | "# Extension - Bokeh\n",
19 | "> Bokeh visualization extension"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "a7f7e0ea-b6b2-44ce-a2d9-20b0af0bef8f",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# exporti\n",
30 | "from __future__ import annotations\n",
31 | "\n",
32 | "import glob\n",
33 | "import os\n",
34 | "from functools import lru_cache\n",
35 | "from typing import Dict, List, Optional, Tuple, Union\n",
36 | "\n",
37 | "import bokeh.io as bio\n",
38 | "import bokeh.models as bm\n",
39 | "import bokeh.plotting as bp\n",
40 | "import numpy as np\n",
41 | "import pkg_resources\n",
42 | "import pya\n",
43 | "from lxml import etree"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "id": "ccaeabe7-3260-45ca-a2d6-5c27873e0f15",
50 | "metadata": {},
51 | "outputs": [],
52 | "source": [
53 | "# hide\n",
54 | "bio.output_notebook(hide_banner=True)"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "id": "8829c0cd-68a1-430e-ac98-e3417b748364",
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "# export\n",
65 | "ALPHA = 0.3\n",
66 | "RED = \"#FF0000\"\n",
67 | "GREEN = \"#00FF00\"\n",
68 | "BLUE = \"#0000FF\"\n",
69 | "C0 = '#1f77b4'\n",
70 | "C1 = '#ff7f0e'\n",
71 | "C2 = '#2ca02c'\n",
72 | "C3 = '#d62728'\n",
73 | "C4 = '#9467bd'\n",
74 | "C5 = '#8c564b'\n",
75 | "C6 = '#e377c2'\n",
76 | "C7 = '#7f7f7f'\n",
77 | "C8 = '#bcbd22'\n",
78 | "C9 = '#17becf'"
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": null,
84 | "id": "cd18eb7b-4b16-421a-8a2b-150b94401585",
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "# export\n",
89 | "LayerProperty = Dict[str, Union[str, bool]]\n",
90 | "LayerProperties = Dict[Tuple[int, int], LayerProperty]\n",
91 | "\n",
92 | "def get_lyp_path(path: Optional[str] = None):\n",
93 | " # first, let's try \"~/.klayout\"\n",
94 | " if path is None:\n",
95 | " path = \"\"\n",
96 | " path = os.path.abspath(os.path.expanduser(path))\n",
97 | " if os.path.isdir(path):\n",
98 | " possible_paths = glob.glob(f\"{path}/*.lyp\")\n",
99 | " if not possible_paths:\n",
100 | " path = get_lyp_path(pkg_resources.resource_filename(\"flayout\", \"layers.lyp\"))\n",
101 | " else:\n",
102 | " path = possible_paths[0]\n",
103 | " return path"
104 | ]
105 | },
106 | {
107 | "cell_type": "code",
108 | "execution_count": null,
109 | "id": "2d3a2c3c-4625-4304-8a4c-6bce298028a6",
110 | "metadata": {},
111 | "outputs": [],
112 | "source": [
113 | "get_lyp_path()"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": null,
119 | "id": "d04f6281-3c09-4350-8a98-a7a52429f9fe",
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "# export\n",
124 | "@lru_cache\n",
125 | "def read_lyp(path: Optional[str] = None) -> LayerProperties:\n",
126 | " \"\"\"Load layer properties from a file\n",
127 | "\n",
128 | " Args:\n",
129 | " path: the path where to load the layer properties from\n",
130 | "\n",
131 | " Returns:\n",
132 | " a dictionary of layer property dictionaries\n",
133 | " \"\"\"\n",
134 | " path = get_lyp_path(path)\n",
135 | " xml = etree.parse(path)\n",
136 | " root = xml.getroot()\n",
137 | " parsed: LayerProperties = {\n",
138 | " (0, 0): {\n",
139 | " \"name\": \"\",\n",
140 | " \"frame-color\": \"#000000\",\n",
141 | " \"fill-color\": \"#000000\",\n",
142 | " \"visible\": True,\n",
143 | " }\n",
144 | " }\n",
145 | " for properties in root.iter(\"properties\"):\n",
146 | " name = properties.find(\"name\")\n",
147 | " if name is not None:\n",
148 | " name, *_ = name.text.split(\"(\")\n",
149 | " name = name.strip()\n",
150 | " else:\n",
151 | " name = \"\"\n",
152 | " \n",
153 | " layerstr = properties.find(\"source\")\n",
154 | " if layerstr is not None:\n",
155 | " layerstr, *_ = layerstr.text.split(\"@\")\n",
156 | " lr, dt = layerstr.strip().split(\"/\")\n",
157 | " lr, dt = int(lr), int(dt)\n",
158 | " parsed[lr, dt] = {\n",
159 | " \"name\": name,\n",
160 | " \"frame-color\": properties.find(\"frame-color\").text,\n",
161 | " \"fill-color\": properties.find(\"fill-color\").text,\n",
162 | " \"visible\": bool(properties.find(\"visible\").text),\n",
163 | " }\n",
164 | " \n",
165 | " return parsed"
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": null,
171 | "id": "6f47525e-0a04-431b-ab77-4068eda87a7d",
172 | "metadata": {},
173 | "outputs": [],
174 | "source": [
175 | "lyp = read_lyp()\n",
176 | "{k: v for k, v in lyp.items() if k in [(19, 0), (10001, 0)]}"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "id": "579d7d05-e00b-4b4b-b102-bfb2229a5c4f",
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "# export\n",
187 | "@lru_cache\n",
188 | "def get_lyp(\n",
189 | " layer_info: Union[pya.LayerInfo, Tuple[int, int]], path: Optional[str] = None\n",
190 | ") -> LayerProperty:\n",
191 | " \"\"\"Load layer properties for a specific layer from a file\n",
192 | "\n",
193 | " Args:\n",
194 | " layer_info: the layer info tuple to load the layer properties for\n",
195 | " path: the path where to load the layer properties from\n",
196 | "\n",
197 | " Returns:\n",
198 | " a layer property dictionary\n",
199 | " \"\"\"\n",
200 | " if isinstance(layer_info, pya.LayerInfo):\n",
201 | " layer, datatype = layer_info.layer, layer_info.datatype # type: ignore\n",
202 | " else:\n",
203 | " layer, datatype, *_ = layer_info\n",
204 | " lyps = read_lyp(path=path)\n",
205 | " lyp = lyps.get((layer, datatype), lyps[0, 0])\n",
206 | " return lyp"
207 | ]
208 | },
209 | {
210 | "cell_type": "code",
211 | "execution_count": null,
212 | "id": "a739a021-45b8-4860-9d8b-6aff145374d2",
213 | "metadata": {},
214 | "outputs": [],
215 | "source": [
216 | "get_lyp((19, 0))"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "id": "19e329c2-9900-4745-8c5e-e9f247043b49",
223 | "metadata": {},
224 | "outputs": [],
225 | "source": [
226 | "# exporti\n",
227 | "def _get_range(box: Union[pya.Box, pya.DBox]) -> Tuple[float, float, float, float, float, float]:\n",
228 | " \"\"\"Get the plotting bbox for a klayout box\n",
229 | "\n",
230 | " Args:\n",
231 | " poly: the polygon to create the bbox for\n",
232 | "\n",
233 | " returns:\n",
234 | " x1, x2, y1, y2, w, h\n",
235 | " \"\"\"\n",
236 | " if isinstance(box, pya.Box):\n",
237 | " box = box.to_dtype()\n",
238 | " x1, x2 = min(box.p1.x, box.p2.x), max(box.p1.x, box.p2.x)\n",
239 | " y1, y2 = min(box.p1.y, box.p2.y), max(box.p1.y, box.p2.y)\n",
240 | " w, h = x2 - x1, y2 - y1\n",
241 | " if w > 5 * h:\n",
242 | " y, h = 0.5 * (y1 + y2), w / 5\n",
243 | " y1, y2 = y - h / 2, y + h / 2\n",
244 | " if h > 5 * w:\n",
245 | " x, w = 0.5 * (x1 + x2), h / 5\n",
246 | " x1, x2 = x - w / 2, x + w / 2\n",
247 | " if w < 2.0:\n",
248 | " x1, x2 = (x2 + x1) / 2 - 1.0, (x2 + x1) / 2 + 1.0\n",
249 | " w = 2.0\n",
250 | " if h < 2.0:\n",
251 | " y1, y2 = (y2 + y1) / 2 - 1.0, (y2 + y1) / 2 + 1.0\n",
252 | " h = 2.0\n",
253 | " return x1, x2, y1, y2, w, h"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": null,
259 | "id": "064d8dcf-f3dc-4e77-a6ef-83de2a585638",
260 | "metadata": {},
261 | "outputs": [],
262 | "source": [
263 | "box = pya.Box(0.0, 0.0, 5.0, 2.5)\n",
264 | "_get_range(box)"
265 | ]
266 | },
267 | {
268 | "cell_type": "code",
269 | "execution_count": null,
270 | "id": "2e786a55-fca5-4cf3-8932-41cb7f7044cf",
271 | "metadata": {},
272 | "outputs": [],
273 | "source": [
274 | "box = pya.DBox(0.0, 0.0, 5.0, 2.5)\n",
275 | "#box = box.enlarge(pya.DVector(0.5, 0.5))\n",
276 | "_get_range(box)"
277 | ]
278 | },
279 | {
280 | "cell_type": "code",
281 | "execution_count": null,
282 | "id": "9eac0379-4bc4-4ec4-a5da-55a6b7cc4974",
283 | "metadata": {},
284 | "outputs": [],
285 | "source": [
286 | "# export\n",
287 | "def new_plot(box: Union[pya.Box, pya.DBox], max_dim: Optional[float] = None) -> bp.Figure:\n",
288 | " \"\"\"Create a new plot with limits determined by a bbox\n",
289 | "\n",
290 | " Args:\n",
291 | " box: the bbox of the polygon or cell to create the figure for\n",
292 | "\n",
293 | " Returns:\n",
294 | " a bokeh Figure.\n",
295 | " \"\"\"\n",
296 | " if max_dim is None:\n",
297 | " max_dim = 500\n",
298 | " x0, x1, y0, y1, w, h = _get_range(box)\n",
299 | " if w > h:\n",
300 | " plot_width = max_dim\n",
301 | " plot_height = max_dim * h / w\n",
302 | " else:\n",
303 | " plot_height = max_dim\n",
304 | " plot_width = max_dim * w / h\n",
305 | " plot = bp.figure(\n",
306 | " plot_width=round(plot_width),\n",
307 | " plot_height=round(plot_height),\n",
308 | " x_range=(x0, x1),\n",
309 | " y_range=(y0, y1),\n",
310 | " match_aspect=True,\n",
311 | " toolbar_location=None,\n",
312 | " tools=[bm.PanTool(), zoom := bm.WheelZoomTool()],\n",
313 | " )\n",
314 | " plot.toolbar.active_scroll = zoom # type: ignore\n",
315 | " return plot"
316 | ]
317 | },
318 | {
319 | "cell_type": "code",
320 | "execution_count": null,
321 | "id": "a0735379-fc00-4f7b-a03a-644e06fc080d",
322 | "metadata": {},
323 | "outputs": [],
324 | "source": [
325 | "p = new_plot(box)\n",
326 | "p.line([0, 1], [0, 1])\n",
327 | "bio.show(p)"
328 | ]
329 | },
330 | {
331 | "cell_type": "code",
332 | "execution_count": null,
333 | "id": "5cc20edd-4ea8-488a-bdf3-444f2ca92301",
334 | "metadata": {},
335 | "outputs": [],
336 | "source": [
337 | "# export\n",
338 | "def adjust_plot(plot: bp.Figure, box: Union[pya.Box, pya.DBox], max_dim: Optional[float] = None) -> bp.Figure:\n",
339 | " \"\"\"Adjust a plot with limits determined by a bbox\n",
340 | "\n",
341 | " Args:\n",
342 | " plot: the plot to adjust the limits for\n",
343 | " box: the bbox of the polygon or cell to create the figure for\n",
344 | "\n",
345 | " Returns:\n",
346 | " a bokeh Figure.\n",
347 | " \"\"\"\n",
348 | " if plot is None:\n",
349 | " return new_plot(box, max_dim=max_dim)\n",
350 | " if max_dim is None:\n",
351 | " max_dim = max(plot.height, plot.width)\n",
352 | " assert max_dim is not None\n",
353 | " x0, x1, y0, y1, w, h = _get_range(box)\n",
354 | " if w > h:\n",
355 | " plot_width = max_dim\n",
356 | " plot_height = max_dim * h / w\n",
357 | " else:\n",
358 | " plot_height = max_dim\n",
359 | " plot_width = max_dim * w / h\n",
360 | " plot.plot_width = round(plot_width)\n",
361 | " plot.plot_height = round(plot_height)\n",
362 | " plot.x_range = bm.Range1d(start=x0, end=x1)\n",
363 | " plot.y_range = bm.Range1d(start=y0, end=y1)\n",
364 | " return plot"
365 | ]
366 | },
367 | {
368 | "cell_type": "code",
369 | "execution_count": null,
370 | "id": "7955840b-27d4-4dd1-bec3-05fdfd75abfc",
371 | "metadata": {},
372 | "outputs": [],
373 | "source": [
374 | "fig = adjust_plot(p, pya.DBox(-1.0, -1.0, 5.0, 3.0))\n",
375 | "bio.show(fig)"
376 | ]
377 | },
378 | {
379 | "cell_type": "code",
380 | "execution_count": null,
381 | "id": "557b15f1-983c-4a98-9a18-8b2f3c27536a",
382 | "metadata": {},
383 | "outputs": [],
384 | "source": [
385 | "# export\n",
386 | "def draw_polys(\n",
387 | " plot: bp.Figure,\n",
388 | " polys: List[Union[pya.Polygon, pya.SimplePolygon, pya.DPolygon, pya.DSimplePolygon]],\n",
389 | " layer=(0, 0),\n",
390 | " fill_color=None,\n",
391 | " line_color=None,\n",
392 | " fill_alpha=ALPHA,\n",
393 | "):\n",
394 | " \"\"\"draw polygons with bokeh\n",
395 | "\n",
396 | " Args:\n",
397 | " plot: the plot to draw the polygon in\n",
398 | " polys: the polygons to draw\n",
399 | "\n",
400 | " Returns:\n",
401 | " the (inplace) modified plot containing the polygons\n",
402 | " \"\"\"\n",
403 | " # some typing definitions for our own sanity...\n",
404 | "\n",
405 | " # Array of single coordinates (x OR y): ndim==1, shape=(N,).\n",
406 | " SimplePolygon = np.ndarray\n",
407 | "\n",
408 | " # List of coordinate arrays (first element: hull, other elements: holes)\n",
409 | " Polygon = List[SimplePolygon]\n",
410 | "\n",
411 | " # List of individual polygons (bokeh will XOR those polygons -> usually 1 Polygon per MultiPolygon)\n",
412 | " MultiPolygon = List[Polygon]\n",
413 | "\n",
414 | " # List of multi polygons\n",
415 | " MultiPolygons = List[MultiPolygon]\n",
416 | "\n",
417 | " xs: MultiPolygons = []\n",
418 | " ys: MultiPolygons = []\n",
419 | "\n",
420 | " for poly in polys:\n",
421 | " if isinstance(poly, (pya.Polygon, pya.SimplePolygon)):\n",
422 | " poly = poly.to_dtype()\n",
423 | "\n",
424 | " if isinstance(poly, pya.DPolygon):\n",
425 | " hull = np.asarray([(p.x, p.y) for p in poly.each_point_hull()])\n",
426 | " holes = [\n",
427 | " np.asarray([(p.x, p.y) for p in poly.each_point_hole(i)])\n",
428 | " for i in range(poly.holes())\n",
429 | " ]\n",
430 | " elif isinstance(poly, pya.DSimplePolygon):\n",
431 | " hull = np.asarray([(p.x, p.y) for p in poly.each_point()])\n",
432 | " holes = []\n",
433 | " else:\n",
434 | " raise ValueError(\n",
435 | " f\"Invalid polygon type. Got: {type(poly)}. \"\n",
436 | " f\"Expected 'DPolygon' or 'DSimplePolygon'\"\n",
437 | " )\n",
438 | " \n",
439 | " if hull.shape[0] < 3:\n",
440 | " continue\n",
441 | "\n",
442 | " plot = adjust_plot(\n",
443 | " plot, pya.Polygon([pya.DPoint(x, y) for x, y in hull]).bbox()\n",
444 | " )\n",
445 | "\n",
446 | " xs_: MultiPolygon = [[hull[:, 0], *(h[:, 0] for h in holes)]]\n",
447 | " ys_: MultiPolygon = [[hull[:, 1], *(h[:, 1] for h in holes)]]\n",
448 | " xs.append(xs_)\n",
449 | " ys.append(ys_)\n",
450 | "\n",
451 | " source = bm.ColumnDataSource({\"xs\": xs, \"ys\": ys})\n",
452 | " lyp = get_lyp(layer)\n",
453 | " patch = bm.MultiPolygons(\n",
454 | " xs=\"xs\",\n",
455 | " ys=\"ys\",\n",
456 | " fill_color=fill_color or lyp[\"fill-color\"],\n",
457 | " fill_alpha=fill_alpha,\n",
458 | " line_color=line_color or lyp[\"frame-color\"],\n",
459 | " )\n",
460 | " plot.add_glyph(source, patch)\n",
461 | " return plot"
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "execution_count": null,
467 | "id": "9c949c3c-ed3f-44b1-a0e1-c6f44a075b3e",
468 | "metadata": {},
469 | "outputs": [],
470 | "source": [
471 | "fig = new_plot(box)\n",
472 | "draw_polys(fig, [pya.DPolygon([pya.DPoint(0, 0), pya.DPoint(2, 0), pya.DPoint(3, 1)])])\n",
473 | "bio.show(fig)"
474 | ]
475 | },
476 | {
477 | "cell_type": "code",
478 | "execution_count": null,
479 | "id": "a54d3152-b0eb-46f7-8e32-276af7f848cb",
480 | "metadata": {},
481 | "outputs": [],
482 | "source": [
483 | "# export\n",
484 | "def draw_poly(\n",
485 | " plot: bp.Figure,\n",
486 | " poly: Union[pya.Polygon, pya.SimplePolygon, pya.DPolygon, pya.DSimplePolygon],\n",
487 | " layer=(0, 0),\n",
488 | " fill_color=None,\n",
489 | " line_color=None,\n",
490 | " fill_alpha=ALPHA,\n",
491 | "):\n",
492 | " \"\"\"draw a polygon with bokeh\n",
493 | "\n",
494 | " Args:\n",
495 | " plot: the plot to draw the polygon in\n",
496 | " poly: the polygon to draw\n",
497 | "\n",
498 | " Returns:\n",
499 | " the (inplace) modified plot containing the polygon\n",
500 | " \"\"\"\n",
501 | " plot = adjust_plot(plot, poly.bbox())\n",
502 | " return draw_polys(\n",
503 | " plot,\n",
504 | " [poly],\n",
505 | " layer=layer,\n",
506 | " fill_color=fill_color,\n",
507 | " line_color=line_color,\n",
508 | " fill_alpha=fill_alpha,\n",
509 | " )"
510 | ]
511 | },
512 | {
513 | "cell_type": "code",
514 | "execution_count": null,
515 | "id": "8a51ba82-24f3-4be9-a3a4-93eb2f30625c",
516 | "metadata": {},
517 | "outputs": [],
518 | "source": [
519 | "fig = new_plot(box)\n",
520 | "draw_poly(fig, pya.DPolygon([pya.DPoint(0, 0), pya.DPoint(2, 0), pya.DPoint(3, 1)]), layer=(19, 0))\n",
521 | "bio.show(fig)"
522 | ]
523 | },
524 | {
525 | "cell_type": "code",
526 | "execution_count": null,
527 | "id": "d1adf85b-b520-4638-810d-466cd9d2e504",
528 | "metadata": {},
529 | "outputs": [],
530 | "source": [
531 | "# export\n",
532 | "def draw_path(\n",
533 | " plot: bp.Figure,\n",
534 | " path: Union[pya.Path, pya.DPath],\n",
535 | " layer=(0, 0),\n",
536 | " fill_color=None,\n",
537 | " line_color=None,\n",
538 | " fill_alpha=ALPHA,\n",
539 | "):\n",
540 | " \"\"\"draw a path with bokeh\n",
541 | "\n",
542 | " Args:\n",
543 | " plot: the plot to draw the path in\n",
544 | " poly: the path to draw\n",
545 | "\n",
546 | " Returns:\n",
547 | " the (inplace) modified plot containing the path\n",
548 | " \"\"\"\n",
549 | " return draw_polys(\n",
550 | " plot,\n",
551 | " [path.polygon()],\n",
552 | " layer=layer,\n",
553 | " fill_color=fill_color,\n",
554 | " line_color=line_color,\n",
555 | " fill_alpha=fill_alpha,\n",
556 | " )"
557 | ]
558 | },
559 | {
560 | "cell_type": "code",
561 | "execution_count": null,
562 | "id": "829d9970-a1ca-44e6-8345-b53542f09188",
563 | "metadata": {},
564 | "outputs": [],
565 | "source": [
566 | "fig = new_plot(box)\n",
567 | "path = pya.DPath([pya.Point(0, 0), pya.Point(10, 0)], 3.0)\n",
568 | "draw_path(fig, path)\n",
569 | "bio.show(fig)"
570 | ]
571 | },
572 | {
573 | "cell_type": "code",
574 | "execution_count": null,
575 | "id": "f0878503-a639-4402-ae92-49e242f2bd73",
576 | "metadata": {},
577 | "outputs": [],
578 | "source": [
579 | "# export\n",
580 | "def draw_point(\n",
581 | " plot: bp.Figure,\n",
582 | " p: Union[pya.Point, pya.DPoint],\n",
583 | " layer=(0, 0),\n",
584 | " fill_color=None,\n",
585 | " line_color=None,\n",
586 | " fill_alpha=ALPHA,\n",
587 | "):\n",
588 | " \"\"\"draw a point with bokeh\n",
589 | "\n",
590 | " Args:\n",
591 | " plot: the plot to draw the point in\n",
592 | " p: the point to draw\n",
593 | "\n",
594 | " Returns:\n",
595 | " the (inplace) modified plot containing the point\n",
596 | " \"\"\"\n",
597 | " if isinstance(p, pya.Point):\n",
598 | " p = p.to_dtype()\n",
599 | " v = pya.DVector(1.0, 1.0)\n",
600 | " box = pya.DBox(p - v, p + v)\n",
601 | " plot = adjust_plot(plot, box)\n",
602 | " *_, w, h = _get_range(box)\n",
603 | " radius = max(w, h) / 30\n",
604 | " lyp = get_lyp(layer)\n",
605 | " plot.circle(\n",
606 | " p.x,\n",
607 | " p.y,\n",
608 | " fill_alpha=fill_alpha,\n",
609 | " fill_color=fill_color or lyp[\"fill-color\"],\n",
610 | " line_color=line_color or [\"frame-color\"],\n",
611 | " radius=radius,\n",
612 | " )\n",
613 | " return plot"
614 | ]
615 | },
616 | {
617 | "cell_type": "code",
618 | "execution_count": null,
619 | "id": "e2592aa9-990f-4d5e-9d70-7e3f8f677290",
620 | "metadata": {},
621 | "outputs": [],
622 | "source": [
623 | "fig = new_plot(box)\n",
624 | "draw_point(fig, pya.Point(0, 0))\n",
625 | "bio.show(fig)"
626 | ]
627 | },
628 | {
629 | "cell_type": "code",
630 | "execution_count": null,
631 | "id": "4d710d58-f8e3-44a8-b0d1-eea2c794a3ac",
632 | "metadata": {},
633 | "outputs": [],
634 | "source": [
635 | "# export\n",
636 | "def draw_vector(\n",
637 | " plot: bp.Figure,\n",
638 | " v: Union[pya.Vector, pya.DVector],\n",
639 | " layer=(0, 0),\n",
640 | " fill_color=None,\n",
641 | " line_color=None,\n",
642 | " fill_alpha=ALPHA,\n",
643 | "):\n",
644 | " \"\"\"draw a vector as an arrow with bokeh\n",
645 | "\n",
646 | " Args:\n",
647 | " plot: the plot to draw the vector in\n",
648 | " v: the vector to draw\n",
649 | "\n",
650 | " Returns:\n",
651 | " the (inplace) modified plot containing the vector\n",
652 | " \"\"\"\n",
653 | " if isinstance(v, pya.Vector):\n",
654 | " v = pya.DVector(v.x / 1000.0, v.y / 1000.0)\n",
655 | " box = pya.DBox(0, 0, v.x, v.y)\n",
656 | " plot = adjust_plot(plot, box, max_dim=250)\n",
657 | " lyp = get_lyp(layer)\n",
658 | " arrow_head = bm.VeeHead(\n",
659 | " fill_alpha=fill_alpha,\n",
660 | " fill_color=fill_color or lyp[\"fill-color\"],\n",
661 | " line_color=line_color or lyp[\"frame-color\"],\n",
662 | " )\n",
663 | " arrow = bm.Arrow(\n",
664 | " end=arrow_head,\n",
665 | " x_start=0,\n",
666 | " y_start=0,\n",
667 | " x_end=v.x,\n",
668 | " y_end=v.y,\n",
669 | " line_color=line_color or lyp[\"frame-color\"],\n",
670 | " )\n",
671 | " plot.add_layout(arrow)\n",
672 | " return plot"
673 | ]
674 | },
675 | {
676 | "cell_type": "code",
677 | "execution_count": null,
678 | "id": "665ca0da-05f9-4d8a-b1a8-dbbdd17ff64e",
679 | "metadata": {},
680 | "outputs": [],
681 | "source": [
682 | "fig = new_plot(box)\n",
683 | "draw_vector(fig, pya.DVector(3, 4))\n",
684 | "bio.show(fig)"
685 | ]
686 | },
687 | {
688 | "cell_type": "code",
689 | "execution_count": null,
690 | "id": "95f5d1c6-b4c3-4e0e-940d-4d0569d27340",
691 | "metadata": {},
692 | "outputs": [],
693 | "source": [
694 | "# export\n",
695 | "def _box_to_poly(box: Union[pya.Box, pya.DBox]):\n",
696 | " \"\"\"convert a box into a polygon\n",
697 | "\n",
698 | " Args:\n",
699 | " box: the box to convert into a polygon\n",
700 | "\n",
701 | " Returns:\n",
702 | " the polygon\n",
703 | " \"\"\"\n",
704 | " if isinstance(box, pya.Box):\n",
705 | " box = box.to_dtype()\n",
706 | " x0, y0 = box.p1.x, box.p1.y\n",
707 | " x1, y1 = box.p2.x, box.p2.y\n",
708 | " return pya.DPolygon(\n",
709 | " [pya.DPoint(x0, y0), pya.DPoint(x1, y0), pya.DPoint(x1, y1), pya.DPoint(x0, y1)]\n",
710 | " )\n",
711 | "\n",
712 | "\n",
713 | "def draw_box(\n",
714 | " plot: bp.Figure,\n",
715 | " box: Union[pya.Box, pya.DBox],\n",
716 | " fill_alpha=0.0,\n",
717 | " fill_color=\"#000000\",\n",
718 | " line_color=GREEN,\n",
719 | "):\n",
720 | " \"\"\"draw a box with bokeh\n",
721 | "\n",
722 | " Args:\n",
723 | " plot: the plot to draw the box in\n",
724 | " box: the box to draw\n",
725 | "\n",
726 | " Returns:\n",
727 | " the (inplace) modified plot containing the box\n",
728 | " \"\"\"\n",
729 | " plot = adjust_plot(plot, box)\n",
730 | " poly = _box_to_poly(box)\n",
731 | " return draw_poly(\n",
732 | " plot, poly, fill_alpha=fill_alpha, fill_color=fill_color, line_color=line_color\n",
733 | " )"
734 | ]
735 | },
736 | {
737 | "cell_type": "code",
738 | "execution_count": null,
739 | "id": "7ddc16b7-d188-4d4c-9667-dd08c37069dd",
740 | "metadata": {},
741 | "outputs": [],
742 | "source": [
743 | "fig = new_plot(box)\n",
744 | "draw_box(fig, pya.DBox(1.0, 2.0, 4.0, 3.0))\n",
745 | "bio.show(fig)"
746 | ]
747 | },
748 | {
749 | "cell_type": "code",
750 | "execution_count": null,
751 | "id": "b141005a-0e36-491a-b387-e3df71069d2e",
752 | "metadata": {},
753 | "outputs": [],
754 | "source": [
755 | "# export\n",
756 | "def _draw_shapes(plot, shapes, layer=(0, 0)):\n",
757 | " \"\"\"draw shapes with bokeh\n",
758 | "\n",
759 | " Args:\n",
760 | " plot: the plot to draw the shape in\n",
761 | " shapes: the shapes to draw\n",
762 | "\n",
763 | " Returns:\n",
764 | " the (inplace) modified plot containing the shape\n",
765 | " \"\"\"\n",
766 | " polys = []\n",
767 | " for shape in shapes:\n",
768 | " if shape.is_box():\n",
769 | " polys.append(_box_to_poly(shape.dbbox()))\n",
770 | " elif shape.is_path():\n",
771 | " polys.append(shape.dpath.polygon())\n",
772 | " elif shape.is_polygon():\n",
773 | " polys.append(shape.dpolygon)\n",
774 | " elif shape.is_simple_polygon():\n",
775 | " polys.append(shape.dsimple_polygon)\n",
776 | " #for poly in polys:\n",
777 | " # plot = draw_poly(plot, poly, layer=layer)\n",
778 | " return draw_polys(plot, polys, layer=layer)\n",
779 | "\n",
780 | "def draw_cell(plot, cell, draw_bbox=True):\n",
781 | " \"\"\"draw a cell with bokeh\n",
782 | "\n",
783 | " Args:\n",
784 | " plot: the plot to draw the cell in\n",
785 | " cell: the cell to draw\n",
786 | "\n",
787 | " Returns:\n",
788 | " the (inplace) modified plot containing the cell\n",
789 | " \"\"\"\n",
790 | " layout = pya.Layout()\n",
791 | " new_cell = layout.create_cell(cell.name)\n",
792 | " new_cell.copy_tree(cell)\n",
793 | " \n",
794 | " cell = new_cell\n",
795 | " cell = cell.flatten(-1, True)\n",
796 | " box = cell.dbbox()\n",
797 | " plot = adjust_plot(plot, box, max_dim=500)\n",
798 | " for lr in layout.layer_infos():\n",
799 | " shapes = [*cell.shapes(layout.layer(lr)).each()]\n",
800 | " plot = _draw_shapes(plot, shapes, layer=(lr.layer, lr.datatype))\n",
801 | " if draw_bbox:\n",
802 | " draw_box(plot, box)\n",
803 | " return plot"
804 | ]
805 | },
806 | {
807 | "cell_type": "code",
808 | "execution_count": null,
809 | "id": "d6b5788c-17e6-4946-bbe6-86c53022edca",
810 | "metadata": {},
811 | "outputs": [],
812 | "source": [
813 | "# export\n",
814 | "def draw_inst(plot, inst, draw_bbox=True, draw_arrow=True):\n",
815 | " \"\"\"draw a instance with bokeh\n",
816 | "\n",
817 | " Args:\n",
818 | " plot: the plot to draw the instance in\n",
819 | " inst: the instance to draw\n",
820 | "\n",
821 | " Returns:\n",
822 | " the (inplace) modified plot with containing the instance\n",
823 | " \"\"\"\n",
824 | " _layout = pya.Layout()\n",
825 | " _cell = _layout.create_cell(inst.cell.name)\n",
826 | " _cell.copy_tree(inst.cell)\n",
827 | " _refcell = _layout.create_cell(f\"ref_{inst.cell.name}\")\n",
828 | " _refcell.insert(pya.CellInstArray(_layout.cell_by_name(inst.cell.name), inst.trans))\n",
829 | "\n",
830 | " plot = new_plot(_refcell.dbbox() + pya.Point(0, 0))\n",
831 | " plot = draw_vector(plot, inst.trans.disp)\n",
832 | " plot = draw_cell(plot, _refcell)\n",
833 | " plot = adjust_plot(plot, _refcell.dbbox() + pya.Point(0, 0))\n",
834 | " return plot"
835 | ]
836 | },
837 | {
838 | "cell_type": "code",
839 | "execution_count": null,
840 | "id": "af153c3d-d908-48a9-9f20-ab2ba7c697bb",
841 | "metadata": {},
842 | "outputs": [],
843 | "source": [
844 | "# export\n",
845 | "def draw_layout(plot, layout):\n",
846 | " \"\"\"draw a layout with bokeh\n",
847 | "\n",
848 | " Args:\n",
849 | " plot: the plot to draw the layout in\n",
850 | " layout: the layout to draw\n",
851 | "\n",
852 | " Returns:\n",
853 | " the (inplace) modified plot with containing the layout\n",
854 | " \"\"\"\n",
855 | " plots = bp.Column(*[draw_cell(plot, cell) for cell in layout.top_cells()])\n",
856 | " return plots"
857 | ]
858 | }
859 | ],
860 | "metadata": {
861 | "kernelspec": {
862 | "display_name": "fl",
863 | "language": "python",
864 | "name": "fl"
865 | }
866 | },
867 | "nbformat": 4,
868 | "nbformat_minor": 5
869 | }
870 |
--------------------------------------------------------------------------------