├── .bumpversion.cfg
├── .flake8
├── .github
├── dependabot.yml
└── workflows
│ ├── pages.yml
│ ├── pre-commit.yml
│ ├── pythonpublish.yml
│ ├── test_code.yml
│ └── test_docs.yml
├── .gitignore
├── .isort.cfg
├── .pre-commit-config.yaml
├── .readthedocs.yml
├── .sourcery.yaml
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── docs
├── CHANGELOG.md
├── DocEtch.rst
├── DocGrow.rst
├── DocIntro.rst
├── DocReference.rst
├── Makefile
├── README.md
├── conf.py
├── img
│ ├── e1.png
│ ├── e10.png
│ ├── e10_xs.png
│ ├── e12.png
│ ├── e12_xs.png
│ ├── e13.png
│ ├── e13_xs.png
│ ├── e14.png
│ ├── e14_xs.png
│ ├── e1_xs.png
│ ├── e2.png
│ ├── e2_xs.png
│ ├── e3.png
│ ├── e3_xs.png
│ ├── e4.png
│ ├── e4_xs.png
│ ├── e5.png
│ ├── e5_xs.png
│ ├── e6.png
│ ├── e6_xs.png
│ ├── e7.png
│ ├── e7_xs.png
│ ├── e8.png
│ ├── e8_xs.png
│ ├── g1.png
│ ├── g10.png
│ ├── g10_xs.png
│ ├── g12.png
│ ├── g12_xs.png
│ ├── g13.png
│ ├── g13_xs.png
│ ├── g14.png
│ ├── g14_xs.png
│ ├── g15.png
│ ├── g15_xs.png
│ ├── g1_xs.png
│ ├── g2.png
│ ├── g2_xs.png
│ ├── g3.png
│ ├── g3_xs.png
│ ├── g4.png
│ ├── g4_xs.png
│ ├── g5.png
│ ├── g5_xs.png
│ ├── g6.png
│ ├── g6_xs.png
│ ├── g7.png
│ ├── g7_xs.png
│ ├── g8.png
│ ├── g8_xs.png
│ ├── s1.png
│ ├── s1_xs.png
│ ├── s2.png
│ ├── s2_xs.png
│ ├── s3.png
│ ├── s3_xs.png
│ └── xsection_70p.png
├── index.rst
└── make.bat
├── klayout_package
├── grain.xml
├── pymacros
│ ├── pyxs.lym
│ └── pyxs_dev.lym
├── python
│ └── klayout_pyxs
│ │ ├── __init__.py
│ │ ├── compat.py
│ │ ├── geometry_2d.py
│ │ ├── geometry_3d.py
│ │ ├── layer_parameters.py
│ │ ├── pyxs3D_lib.py
│ │ ├── pyxs_lib.py
│ │ └── utils.py
├── xs_128x128.png
└── xs_64x64.png
├── klayout_pyxs
├── requirements.txt
├── requirements_dev.txt
├── samples
├── cmos.pyxs
├── makedoc.rb
├── makedoc.sh
├── pure_python.py
└── sample.gds
├── setup.py
├── tests
├── au
│ ├── xs_bug11.gds
│ ├── xs_bug4.gds
│ ├── xs_bug8.gds
│ ├── xs_etch1.gds
│ ├── xs_etch10.gds
│ ├── xs_etch2.gds
│ ├── xs_etch3.gds
│ ├── xs_etch4.gds
│ ├── xs_etch5.gds
│ ├── xs_etch6.gds
│ ├── xs_etch7.gds
│ ├── xs_etch8.gds
│ ├── xs_etch9.gds
│ ├── xs_flow1.gds
│ ├── xs_flow2.gds
│ ├── xs_flow3.gds
│ ├── xs_flow4.gds
│ ├── xs_flow5.gds
│ ├── xs_flow6.gds
│ ├── xs_flow7.gds
│ ├── xs_flow8.gds
│ ├── xs_grow1.gds
│ ├── xs_grow10.gds
│ ├── xs_grow11.gds
│ ├── xs_grow12.gds
│ ├── xs_grow2.gds
│ ├── xs_grow3.gds
│ ├── xs_grow4.gds
│ ├── xs_grow5.gds
│ ├── xs_grow6.gds
│ ├── xs_grow7.gds
│ ├── xs_grow8.gds
│ ├── xs_grow9.gds
│ ├── xs_misc1.gds
│ ├── xs_misc2.gds
│ ├── xs_misc3.gds
│ ├── xs_misc4.gds
│ ├── xs_misc5.gds
│ ├── xs_planarize1.gds
│ ├── xs_planarize2.gds
│ └── xs_planarize3.gds
├── readme.rst
├── run_tests.sh
├── run_tests_windows.sh
├── run_xor.rb
├── xs_bug11.gds
├── xs_bug11.pyxs
├── xs_bug4.gds
├── xs_bug4.pyxs
├── xs_bug8.gds
├── xs_bug8.pyxs
├── xs_etch1.pyxs
├── xs_etch10.pyxs
├── xs_etch2.pyxs
├── xs_etch3.pyxs
├── xs_etch4.pyxs
├── xs_etch5.pyxs
├── xs_etch6.pyxs
├── xs_etch7.pyxs
├── xs_etch8.pyxs
├── xs_etch9.pyxs
├── xs_flow1.pyxs
├── xs_flow2.pyxs
├── xs_flow3.pyxs
├── xs_flow4.pyxs
├── xs_flow5.pyxs
├── xs_flow6.pyxs
├── xs_flow7.pyxs
├── xs_flow8.pyxs
├── xs_grow1.pyxs
├── xs_grow10.pyxs
├── xs_grow11.pyxs
├── xs_grow12.pyxs
├── xs_grow2.pyxs
├── xs_grow3.pyxs
├── xs_grow4.pyxs
├── xs_grow5.pyxs
├── xs_grow6.pyxs
├── xs_grow7.pyxs
├── xs_grow8.pyxs
├── xs_grow9.pyxs
├── xs_misc1.pyxs
├── xs_misc2.pyxs
├── xs_misc3.pyxs
├── xs_misc4.pyxs
├── xs_misc5.pyxs
├── xs_planarize1.pyxs
├── xs_planarize2.pyxs
├── xs_planarize3.pyxs
└── xs_test.gds
└── xs2pyxs
├── xs2pyxs.sh
├── xs2pyxs_patterns.txt
└── xs_bug11.xs
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 0.1.13
3 | commit = True
4 | tag = True
5 |
6 | [bumpversion:file:./setup.py]
7 |
8 | [bumpversion:file:./docs/conf.py]
9 |
10 | [bumpversion:file:./README.md]
11 |
12 | [bumpversion:file:klayout_package/python/klayout_pyxs/__init__.py]
13 |
14 | [bumpversion:file:klayout_package/grain.xml]
15 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | max-complexity = 18
4 | select = B,C,E,F,W,T4,B9
5 | ignore = E203, E266, E501, W503, F403, F401
6 |
7 | exclude =
8 | # No need to traverse our git directory
9 | .git,
10 | # There's no value in checking cache directories
11 | __pycache__,
12 | # The conf file is mostly autogenerated, ignore it
13 | docs/source/conf.py,
14 | # The old directory contains Flake8 2.0
15 | old,
16 | # This contains our built documentation
17 | build,
18 | # This contains builds of flake8 that we don't want to check
19 | dist,
20 | .ipynb_checkpoints,
21 | .tox,
22 | extra,
23 | deprecated,
24 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "pip"
4 | directory: "/" # Location of package manifests
5 | schedule:
6 | interval: "daily"
7 |
8 | - package-ecosystem: github-actions
9 | directory: /
10 | schedule:
11 | interval: monthly
12 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | name: Sphinx docs to gh-pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | workflow_dispatch:
8 |
9 | jobs:
10 | sphinx_docs_to_gh-pages:
11 | runs-on: ubuntu-latest
12 | name: Sphinx docs to gh-pages
13 | steps:
14 | - name: Cancel Workflow Action
15 | uses: styfle/cancel-workflow-action@0.10.0
16 | - uses: actions/checkout@v3
17 | - uses: conda-incubator/setup-miniconda@v2
18 | with:
19 | python-version: 3.9
20 | mamba-version: "*"
21 | channels: conda-forge,defaults
22 | channel-priority: true
23 | activate-environment: anaconda-client-env
24 | - name: Add conda to system path
25 | run: |
26 | echo $CONDA/bin >> $GITHUB_PATH
27 | - name: Installing the library
28 | shell: bash -l {0}
29 | run: |
30 | pip install -e .
31 | pip install -r requirements_dev.txt
32 | sudo wget https://github.com/jgm/pandoc/releases/download/1.16.0.2/pandoc-1.16.0.2-1-amd64.deb
33 | sudo dpkg -i pandoc-1.16.0.2-1-amd64.deb
34 | #sudo apt install pandoc
35 | - name: Running the Sphinx to gh-pages Action
36 | uses: uibcdf/action-sphinx-docs-to-gh-pages@v1.0-beta.2
37 | with:
38 | branch: master
39 | dir_docs: docs
40 | sphinxopts: ""
41 |
--------------------------------------------------------------------------------
/.github/workflows/pre-commit.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: pre-commit
3 |
4 | on:
5 | pull_request:
6 | push:
7 |
8 | jobs:
9 | pre-commit:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-python@v4
14 | with:
15 | python-version: 3.9
16 | - uses: pre-commit/action@v3.0.0
17 |
--------------------------------------------------------------------------------
/.github/workflows/pythonpublish.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Upload Python Package
3 |
4 | on:
5 | release:
6 | types: [created, published]
7 | push:
8 | branches: [master]
9 | tags: [v*]
10 |
11 | jobs:
12 | deploy:
13 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Set up Python
18 | uses: actions/setup-python@v4
19 | with:
20 | python-version: 3.x
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install setuptools wheel twine
25 | - name: Build and publish
26 | env:
27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29 | run: |
30 | python setup.py sdist bdist_wheel
31 | twine upload dist/*
32 |
--------------------------------------------------------------------------------
/.github/workflows/test_code.yml:
--------------------------------------------------------------------------------
1 | name: Test code and lint
2 |
3 | on:
4 | pull_request:
5 | push:
6 | schedule:
7 | - cron: 0 2 * * * # run at 2 AM UTC
8 |
9 | jobs:
10 | build:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | max-parallel: 12
14 | matrix:
15 | python-version: [3.7]
16 | os: [ubuntu-latest]
17 |
18 | steps:
19 | - name: Cancel Workflow Action
20 | uses: styfle/cancel-workflow-action@0.10.0
21 | - uses: actions/checkout@v3
22 | - name: Set up Python ${{ matrix.python-version }}
23 | uses: actions/setup-python@v4
24 | with:
25 | python-version: ${{ matrix.python-version }}
26 | - name: run tests
27 | run: |
28 | pip install .
29 | sudo apt install klayout
30 | cd tests
31 | bash run_tests.sh
32 |
--------------------------------------------------------------------------------
/.github/workflows/test_docs.yml:
--------------------------------------------------------------------------------
1 | name: Test documentation
2 |
3 | on:
4 | pull_request:
5 | push:
6 | schedule:
7 | - cron: "0 2 * * *" # run at 2 AM UTC
8 | -
9 |
10 | jobs:
11 | build-linux:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Cancel Workflow Action
16 | uses: styfle/cancel-workflow-action@0.10.0
17 | - uses: actions/checkout@v3
18 | - name: Set up Python 3.9
19 | uses: actions/setup-python@v4
20 | with:
21 | python-version: 3.9
22 | - name: Install dependencies
23 | run: |
24 | pip install -r requirements_dev.txt
25 | pip install .
26 | sudo apt install pandoc
27 | - name: Test documentation
28 | run: |
29 | cd docs
30 | make html
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ---> Python
2 | # Byte-compiled / optimized / DLL files
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 | extra/
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *,cover
49 | .hypothesis/
50 | .pytest_cache
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # Jupyter Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # SageMath parsed files
83 | *.sage.py
84 |
85 | # dotenv
86 | .env
87 |
88 | # virtualenv
89 | .venv
90 | venv/
91 | ENV/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # Pycharm project settings
101 | .idea
102 | .settings
103 |
104 | # VS code project settings
105 | .vscode
106 |
107 | # LaTeX compilation files
108 | *.aux
109 | *.log
110 | *.synctex.gz
111 | *.toc
112 | *.out
113 |
114 | # Backup files
115 | *.bak
116 | *~
117 |
118 | # Archives
119 | *.tar
120 | *.zip
121 | *.rar
122 |
123 | # GDS generated while testing
124 | tests/run_dir
125 |
--------------------------------------------------------------------------------
/.isort.cfg:
--------------------------------------------------------------------------------
1 | [settings]
2 | line_length = 88
3 | multi_line_output = 3
4 | include_trailing_comma = True
5 | known_third_party = celery,django,environ,pyquery,pytz,redis,requests,rest_framework
6 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: "b0f06dc9f2260909f3423243de180edfc823ec5a"
4 | hooks:
5 | - id: check-yaml
6 | - id: end-of-file-fixer
7 | - id: trailing-whitespace
8 |
9 | - repo: https://github.com/hakancelik96/unimport
10 | rev: df8eb1a4c91acb84da197828af8157708968b596
11 | hooks:
12 | - id: unimport
13 | args: [--remove, --include-star-import]
14 | - repo: https://github.com/pycqa/isort
15 | rev: "12cc5fbd67eebf92eb2213b03c07b138ae1fb448"
16 | hooks:
17 | - id: isort
18 | files: "klayout_pyxs/.*"
19 | args: [--profile, black, --filter-files]
20 |
21 | - repo: https://github.com/psf/black
22 | rev: "6debce63bc2429b1680f8838592f2e56e3df6b27"
23 | hooks:
24 | - id: black
25 |
26 | # - repo: https://gitlab.com/pycqa/flake8
27 | # rev: "21d3c70d676007470908d39b73f0521d39b3b997"
28 | # hooks:
29 | # - id: flake8
30 |
31 | - repo: https://github.com/kynan/nbstripout
32 | rev: 8cafdcc393232045208137698dbeb42d6e0dd9e8
33 | hooks:
34 | - id: nbstripout
35 | files: ".ipynb"
36 |
37 | - repo: https://github.com/asottile/pyupgrade
38 | rev: 85f02f45f0d2889f3826f16b60f27188ea84c1ae
39 | hooks:
40 | - id: pyupgrade
41 | args: [--py37-plus, --keep-runtime-typing]
42 |
43 | # - repo: https://github.com/codespell-project/codespell
44 | # rev: 4d782511b3999c243feb3858cd7062270eb13291
45 | # hooks:
46 | # - id: codespell
47 | # args: ["-L TE,TE/TM,te,ba,FPR,fpr_spacing,ro"]
48 |
49 | # - repo: https://github.com/shellcheck-py/shellcheck-py
50 | # rev: f87a493c6596a5338d69395905a4e13ed65584f6
51 | # hooks:
52 | # - id: shellcheck
53 |
54 | # - repo: https://github.com/pre-commit/pygrep-hooks
55 | # rev: 8e68d79b05355c9e107f81fc267f2bfc3e9e5a04 # Use the ref you want to point at
56 | # hooks:
57 | # - id: python-use-type-annotations
58 |
59 | - repo: https://github.com/PyCQA/bandit
60 | rev: 44c05fcac53de3a39589173177c931b38de5bb0f
61 | hooks:
62 | - id: bandit
63 | args: [--exit-zero]
64 | # ignore all tests, not just tests data
65 | exclude: ^tests/
66 | # - repo: https://github.com/pre-commit/mirrors-mypy
67 | # rev: "214c33306afe17f1cc7d2d55e4da705b6ebe0627"
68 | # hooks:
69 | # - id: mypy
70 | # exclude: ^(docs/|example-plugin/|tests/fixtures)
71 | # additional_dependencies:
72 | # - "pydantic"
73 | # - repo: https://github.com/pycqa/pydocstyle
74 | # rev: ""
75 | # hooks:
76 | # - id: pydocstyle
77 | # - repo: https://github.com/asottile/reorder_python_imports
78 | # rev: 2b2f0c74acdb3de316e23ceb7dd0d7945c354050
79 | # hooks:
80 | # - id: reorder-python-imports
81 | # - repo: https://github.com/terrencepreilly/darglint
82 | # rev: master
83 | # hooks:
84 | # - id: darglint
85 | # - repo: https://github.com/PyCQA/pylint
86 | # rev: v2.14.1
87 | # hooks:
88 | # - id: pylint
89 | # args: [--exit-zero]
90 | # - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
91 | # rev: 6565d773ca281682d7062d4c0be74538cc474cc9
92 | # hooks:
93 | # - id: pretty-format-java
94 | # args: [--autofix]
95 | # - id: pretty-format-kotlin
96 | # args: [--autofix]
97 | # - id: pretty-format-yaml
98 | # args: [--autofix, --indent, "2"]
99 | # - repo: https://github.com/adrienverge/yamllint.git
100 | # rev: v1.21.0 # or higher tag
101 | # hooks:
102 | # - id: yamllint
103 | # args: [--format, parsable, --strict]
104 | # - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
105 | # rev: 0.1.0 # or specific tag
106 | # hooks:
107 | # - id: yamlfmt
108 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | configuration: docs/source/conf.py
11 |
12 | # Build documentation with MkDocs
13 | #mkdocs:
14 | # configuration: mkdocs.yml
15 |
16 | # Optionally build your docs in additional formats such as PDF and ePub
17 | # formats: all
18 |
19 | # Optionally set the version of Python and requirements required to build your docs
20 | python:
21 | version: 3.9
22 | install:
23 | - requirements: requirements.txt
24 | - requirements: requirements_dev.txt
25 |
--------------------------------------------------------------------------------
/.sourcery.yaml:
--------------------------------------------------------------------------------
1 | refactor:
2 | python_version: '3.7'
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## [0.1.10](https://github.com/dimapu/klayout_pyxs/pull/4)
4 |
5 | - fix python package
6 |
7 | ## [0.1.9](https://github.com/dimapu/klayout_pyxs/pull/16)
8 |
9 | - make klayout package
10 | - upload to pypi
11 | - add CI/CD
12 |
13 |
14 | ## 0.1.5
15 |
16 | - original version
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dima Pustakhod
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include requirements.txt
2 | include README.md
3 | include LICENSE
4 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | help:
2 | @echo 'make install: Install package, hook, notebooks and gdslib'
3 | @echo 'make test: Run tests with pytest'
4 | @echo 'make test-force: Rebuilds regression test'
5 |
6 | install:
7 | cp -r klayout_pyxs ~/.klayout/python/
8 | cp pymacros/pyxs.lym ~/.klayout/pymacros/
9 | # ln -s $(PWD)/klayout_pyxs ~/.klayout/python/
10 |
11 |
12 | release:
13 | git push
14 | git push origin --tags
15 |
16 | upload-twine: build
17 | pip install twine
18 | twine upload dist/*
19 |
20 | lint:
21 | flake8
22 |
23 | pylint:
24 | pylint --rcfile .pylintrc klayout_pyxs/
25 |
26 | lintdocs:
27 | flake8 --select RST
28 |
29 | pydocstyle:
30 | pydocstyle klayout_pyxs
31 |
32 | doc8:
33 | doc8 docs/
34 |
35 | autopep8:
36 | autopep8 --in-place --aggressive --aggressive **/*.py
37 |
38 | codestyle:
39 | pycodestyle --max-line-length=88
40 |
41 | release:
42 | git push
43 | git push origin --tags
44 |
45 | git-rm-merged:
46 | git branch -D `git branch --merged | grep -v \* | xargs`
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # klayout_pyxs 0.1.13
2 |
3 | [](https://gdsfactory.github.io/klayout_pyxs/)
4 | [](https://pypi.org/project/klayout_pyxs/)
5 | [](https://choosealicense.com/licenses/mit/)
6 | [](https://github.com/psf/black)
7 | [](https://pepy.tech/project/klayout_pyxs)
8 | [](https://pepy.tech/project/klayout_pyxs)
9 | [](https://pepy.tech/project/klayout_pyxs)
10 |
11 | This is a python port of the XSection project
12 | (https://github.com/klayoutmatthias/xsection).
13 |
14 | The goal of this project is to provide an add-on to KLayout (www.klayout.de) to
15 | create and visualize a realistic cross-section view for VLSI designs
16 | supporting a wide range of technology options.
17 |
18 | ## User Documentation
19 |
20 | For the project description see [klayout_pyxs Project Home Page](https://gdsfactory.github.io/klayout_pyxs).
21 |
22 |
23 | ## Project Files
24 |
25 | The basic structure is:
26 |
27 | * `docs` The documentation
28 | * `klayout_pyxs` The python package sources
29 | * `pymacros` The python .lym macros files for KLayout
30 | * `samples` Some sample files
31 | * `tests` Test sources and golden data
32 | * `xs2pyxs` xs to pyxs conversion scripts
33 |
34 | The `docs` folder contains the .rst files and images for the documentation
35 | pages. See rendered version [here](https://klayout-pyxs.readthedocs.io/en/latest).
36 |
37 | The `klayout_pyxs` folder contains the python package which includes
38 | the cross-section generation engine.
39 |
40 | The `pymacros` folder contains with the actual KLayout macros code,
41 | `pyxs.lym`.
42 |
43 | The `samples` folder holds a few files for playing around.
44 |
45 | The `tests` folder contains some regression tests for the package.
46 | To run the tests, make sure "klayout" or "klayout_app" (in Windows)
47 | is in your path and use
48 |
49 | ```sh
50 | $ cd tests
51 | $ ./run_tests.sh
52 | ```
53 |
54 | or (from e.g. git bash console on Windows)
55 |
56 | ```bash
57 | $ cd tests
58 | $ bash run_tests_windows.sh
59 | ```
60 |
61 | The `xs2pyxs` folder contains a shell script which helps converting
62 | Ruby-based .xs scripts to .pyxs scripts. It performs necessary but not
63 | sufficient string replacements. Depending on the .xs script complexity,
64 | more changes are likely to be needed.
65 |
66 |
67 | ## Installation for users
68 |
69 | You can install the module
70 |
71 | ```
72 | pip install klayout_pyxs
73 | ```
74 |
75 | And the klayout macro from klayout package manager.
76 |
77 | 
78 |
79 | ## Installation for developers
80 |
81 | To run .pyxs scripts from the KLayout menu, klayout_pyxs package and
82 | python macros file have to be installed to the KLayout folders.
83 | According to [KLayout documentation](https://www.klayout.de/doc-qt4/about/macro_editor.html),
84 | they should go to the "pymacros" and "python" folders in KLayout's user
85 | specific application folder. In Windows, it is $USERPROFILE/KLayout.
86 |
87 | If you are using Python 2.7 in your KLayout distribution, you need
88 | `six` package installed.
89 |
90 | ### Windows
91 |
92 | In Windows, do the following (the commands should be run from e.g.
93 | git bash console). Tested on KLayout 0.25.3 and 0.25.7.
94 |
95 | 0. Check if $USERPROFILE/KLayout exists and is used by the KLayout to
96 | store macros. Run
97 |
98 | ```bash
99 | $ ls $USERPROFILE/KLayout
100 | ```
101 |
102 | If no error reported, continue with 1. If there is an error, you need to
103 | find a location of KLayout's user specific application folder
104 | with pymacros, python folders and use it in further commands.
105 |
106 | 1. Clone klayout_pyxs repository into any source folder:
107 |
108 | ```bash
109 | $ git clone https://github.com/dimapu/klayout_pyxs.git klayout_pyxs_repo
110 | ```
111 |
112 | 2. Copy klayout_pyxs_repo/pymacros/pyxs.lym to $USERPROFILE/KLayout/pymacros/pyxs.lym
113 |
114 | ```bash
115 | $ cp klayout_pyxs_repo/pymacros/pyxs.lym $USERPROFILE/KLayout/pymacros/pyxs.lym
116 | ```
117 |
118 | 3. Copy klayout_pyxs_repo/klayout_pyxs/*.* to $USERPROFILE/KLayout/python/klayout_pyxs
119 |
120 | ```bash
121 | $ mkdir $USERPROFILE/KLayout/python/klayout_pyxs
122 | $ cp klayout_pyxs_repo/klayout_pyxs/*.py $USERPROFILE/KLayout/python/klayout_pyxs
123 | ```
124 |
125 | Now, run Klayout. In the Tools menu, you should see pyxs > Load pyxs script.
126 |
127 | ### Linux / Mac OS
128 |
129 | Run
130 |
131 | ```bash
132 | $ make install
133 | ```
134 |
135 | Now, run Klayout. In the Tools menu, you should see pyxs > Load pyxs script.
136 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ../CHANGELOG.md
--------------------------------------------------------------------------------
/docs/DocEtch.rst:
--------------------------------------------------------------------------------
1 | .. _DocEtch:
2 |
3 | The ``etch()`` Method
4 | =====================
5 |
6 | The ``etch()`` method is one of the basic methods for describing a process.
7 | It is called on a mask data object. The basic use case is:
8 |
9 | .. code-block:: python
10 |
11 | l1 = layer("1/0")
12 | substrate = bulk()
13 | mask(l1).etch(0.3, into='substrate')
14 | output("1/0", substrate)
15 |
16 | This simple case removes material from the substrate and leaves a hole
17 | where the mask is drawn..
18 |
19 | The ``etch()`` method has up to two positional arguments and a couple of
20 | options which have to be put after the arguments in the usual Python
21 | keyword notation ``name=value``:
22 |
23 | ``etch(height, lateral=0, option=value, ...)``
24 |
25 | The ``height`` argument is mandatory and specifies the depth of the etch.
26 | The ``lateral`` parameter specifies the lateral extension (underetch).
27 | The lateral extension is optional and defaults to 0. The lateral
28 | extension can be negative. In that case, the profile will be aligned
29 | with the mask at the top. Otherwise it is aligned at the bottom.
30 |
31 | There are several options:
32 |
33 | .. list-table::
34 | :widths: 10 60
35 | :header-rows: 1
36 |
37 | * - Option
38 | - Value
39 | * - ``mode``
40 | - | The profile mode. Can be ``'square'`` (default), ``'round'``,
41 | | and ``'octagon'``.
42 | * - ``taper``
43 | - | The taper angle. This option specifies tapered mode and cannot be
44 | | combined with ``mode``.
45 | * - ``bias``
46 | - | Adjusts the profile by shifting it to the interior of the figure.
47 | | Positive values will reduce the line width by twice the value.
48 | * - ``into``
49 | - | A material or an array of materials into which the etch is
50 | | performed. This specification is mandatory.
51 | * - ``through``
52 | - | A material or an array of materials which form the selective
53 | | material of the etch. The etch will happen only where this
54 | | material interfaces with air and pass through this material
55 | | (hence the name).
56 | * - ``buried``
57 | - | Applies the etching at the given depth below the surface. This
58 | | option allows to create cavities. It specifies the vertical
59 | | displacement of the etch seed and there may be more applications
60 | | for this feature.
61 |
62 | ``mode``, ``taper`` and ``bias``
63 | --------------------------------
64 |
65 | The effect of the mode and bias interaction is best illustrated with
66 | some examples.
67 |
68 | The initial layout is always this in all following examples:
69 |
70 | .. image:: ./img/e1.png
71 |
72 | The first example if the effect of the plain etch with a thickness of
73 | 0.3 um. It will remove a rectangular part at the mask:
74 |
75 | .. code-block:: python
76 |
77 | etch(0.3, into=substrate)
78 |
79 | .. image:: ./img/e1_xs.png
80 |
81 | The next example illustrates the effect of a lateral extension on a
82 | square profile. The 0.1 um extension will remove material left and right
83 | of the main trench:
84 |
85 | .. code-block:: python
86 |
87 | etch(0.3, 0.1, into=substrate)
88 |
89 | .. image:: ./img/e2_xs.png
90 |
91 | In ``'round'`` mode, the material will be removed with an elliptical
92 | profile. The vertical axis will be 0.3 um, the horizontal 0.1 um
93 | representing the lateral extension. The trench will become bigger
94 | than the mask by the lateral extension at the bottom:
95 |
96 | .. code-block:: python
97 |
98 | etch(0.3, 0.1, mode='round', into=substrate)
99 |
100 | .. image:: ./img/e3_xs.png
101 |
102 | To avoid overetch, a negative lateral extension can be specified,
103 | resulting in a alignment of patch and mask at the top:
104 |
105 | .. code-block:: python
106 |
107 | etch(0.3, -0.1, mode='round', into=substrate)
108 |
109 | .. image:: ./img/e4_xs.png
110 |
111 | Another mode is ``'octagon'`` which is basically a coarse approximation
112 | of the ellipse and computationally less expensive:
113 |
114 | .. code-block:: python
115 |
116 | etch(0.3, 0.1, mode='octagon', into=substrate)
117 |
118 | .. image:: ./img/e5_xs.png
119 |
120 | A bias value can be specified to fine-tune the position of the top
121 | edge of the trench. A *positive* bias value will *shrink* the figure:
122 |
123 | .. code-block:: python
124 |
125 | etch(0.3, 0.1, mode='round', bias=0.05, into=substrate)
126 |
127 | .. image:: ./img/e6_xs.png
128 |
129 | A special profile can be specified with the ``taper`` option. This option
130 | specifies a taper angle and a conical trench will be created. The taper
131 | angle will be the sidewall angle of the trench. This option cannot be
132 | combined with ``mode`` and the lateral extension should be omitted. It can
133 | be combined with ``bias`` however:
134 |
135 | .. code-block:: python
136 |
137 | etch(0.3, taper=10, into=substrate)
138 |
139 | .. image:: ./img/e7_xs.png
140 |
141 | .. code-block:: python
142 |
143 | etch(0.3, taper=10, bias=-0.1, into=substrate)
144 |
145 | .. image:: ./img/e8_xs.png
146 |
147 | Step etch profile
148 | -----------------
149 |
150 | The following image shows the etch profile of a 30° slope and a
151 | vertical step by an etch in round mode with thickness of 0.3 um and
152 | lateral extension of 0.1 um. The sidewall of the step will be removed
153 | with a thickness of 0.1 um corresponding to the lateral extension.
154 |
155 | The solid gray line shows the profile before the etch:
156 |
157 | .. code-block:: python
158 |
159 | etch(0.3, 0.1, mode='round', into=substrate)
160 |
161 | .. image:: ./img/e10_xs.png
162 |
163 | ``through`` - selective etch
164 | ----------------------------
165 |
166 | Normally the etch will happen only at the interface between air and
167 | the ``into`` material, as the following example demonstrates:
168 |
169 | .. code-block:: python
170 |
171 | # Prepare input layers
172 | m1 = layer("1/0")
173 | m2 = layer("2/0")
174 |
175 | substrate = bulk()
176 |
177 | # Grow a stop layer
178 | stop = mask(m2).grow(0.05, into=substrate)
179 |
180 | # Grow with mask m1, but only where there is a substrate surface
181 | mask(m1).etch(0.3, 0.1, mode='round', into=substrate)
182 |
183 | # output the material data to the target layout
184 | output("0/0", substrate)
185 | output("2/0", stop)
186 |
187 | With the following input:
188 |
189 | .. image:: ./img/e12.png
190 |
191 | This script will produce the following result:
192 |
193 | .. image:: ./img/e12_xs.png
194 |
195 | The blue material will prevent etching as it blocks the air/substrate
196 | interface. The ``through`` option reverses that scheme: giving this
197 | ``stop`` material as an argument to ``through`` will make the etch happen
198 | at places where this material interfaces with air:
199 |
200 | .. code-block:: python
201 |
202 | # Prepare input layers
203 | m1 = layer("1/0")
204 | m2 = layer("2/0")
205 |
206 | substrate = bulk()
207 |
208 | # Grow a stop layer
209 | stop = mask(m2).grow(0.05, into=substrate)
210 |
211 | # Grow with mask m1, but only where there is a substrate surface
212 | mask(m1).etch(0.3, 0.1, mode='round', into=substrate, through=stop)
213 |
214 | # output the material data to the target layout
215 | output("0/0", substrate)
216 | output("2/0", stop)
217 |
218 | This script will produce the following result:
219 |
220 | .. image:: ./img/e13_xs.png
221 |
222 | ``buried`` - vertically displaced etch
223 | --------------------------------------
224 |
225 | This option shifts the seed of the etch operation into the material.
226 | Without this option, the etch will start at the surface. If a positive
227 | value is given, the etch starts below the surface in a depth given by
228 | this value. The etch will proceed upwards and downwards with the given
229 | features. In the extreme case (below the surface by more than the etch
230 | depth), this feature creates cavities:
231 |
232 | .. code-block:: python
233 |
234 | # Prepare input layers
235 | m1 = layer("1/0")
236 | m2 = layer("2/0")
237 |
238 | substrate = bulk()
239 |
240 | # Grow with mask m1 into the substrate
241 | mask(m1).etch(0.3, 0.1, mode='round', into=substrate, buried=0.4)
242 |
243 | # output the material data to the target layout
244 | output("0/0", substrate)
245 |
246 | With the following input:
247 |
248 | .. image:: ./img/e14.png
249 |
250 | This script will produce the following result:
251 |
252 | .. image:: ./img/e14_xs.png
253 |
--------------------------------------------------------------------------------
/docs/DocGrow.rst:
--------------------------------------------------------------------------------
1 | .. _DocGrow:
2 |
3 | The ``grow()`` Method
4 | =====================
5 |
6 | The ``grow()`` method is one of the basic methods for describing a process.
7 | It is called on a mask data object. The basic use case is:
8 |
9 | .. code-block:: python
10 |
11 | l1 = layer("1/0")
12 | metal = mask(l1).grow(0.3)
13 | output("1/0", metal)
14 |
15 | This simple case deposits a material where the layer is drawn with a
16 | rectangular profile.
17 |
18 | The ``grow()`` method has up to two arguments and a couple of options which
19 | have to be put after the arguments in the usual Python keyword notation
20 | ``name=value``:
21 |
22 | ``grow(height, lateral=0, option=value...)``
23 |
24 | The ``height`` argument is mandatory and specifies the thickness of the
25 | layer grown. The ``lateral`` parameter specifies the lateral extension
26 | (overgrow, diffusion). The lateral extension is optional and defaults
27 | to 0. The lateral extension can be negative. In that case, the profile
28 | will be aligned with the mask at the bottom. Otherwise it is aligned
29 | at the top.
30 |
31 | There are several options:
32 |
33 | .. list-table::
34 | :widths: 10 60
35 | :header-rows: 1
36 |
37 | * - Option
38 | - Value
39 | * - ``mode``
40 | - | The profile mode. Can be ``'round'``, ``'square'`` and
41 | | ``'octagon'``. The default is 'square'.
42 | * - ``taper``
43 | - | The taper angle. This option specifies tapered mode and cannot
44 | | be combined with ``'mode'``.
45 | * - ``bias``
46 | - | Adjusts the profile by shifting it to the interior of the
47 | | figure. Positive values will reduce the line width by twice
48 | | the value.
49 | * - ``on``
50 | - | A material or an array of materials onto which the material
51 | | is deposited (selective grow). The default is ``'all'``. This
52 | | option cannot be combined with ``"into"``. With ``"into"``,
53 | | ``"through"`` has the same effect than ``"on"``.
54 | * - ``into``
55 | - | Specifies a material or an array of materials that the new
56 | | material should consume instead of growing upwards. This
57 | | will make "grow" a "conversion" process like an implant step.
58 | * - ``through``
59 | - | To be used together with "into". Specifies a material or an
60 | | array of materials to be used for selecting grow. Grow will
61 | | happen starting on the interface of that material with air,
62 | | pass through the ``through`` material (hence the name) and
63 | | consume and convert the ``into`` material below.
64 | * - ``buried``
65 | - | Applies the conversion of material at the given depth below
66 | | the mask level. This is intended to be used together with
67 | | ``into`` and allows modeling of deep implants. The value is
68 | | the depth below the surface.
69 |
70 | ``mode``, ``taper`` and ``bias``
71 | --------------------------------
72 |
73 | The effect of the ``mode`` and ``bias`` interaction is best illustrated with
74 | some examples.
75 |
76 | The initial layout is always this in all following examples:
77 |
78 | .. image:: ./img/g1.png
79 |
80 | The first example if the effect of the plain grow with a thickness of
81 | 0.3 um. It will deposit a rectangular material profile at the mask:
82 |
83 | .. code-block:: python
84 |
85 | grow(0.3)
86 |
87 | .. image:: ./img/g1_xs.png
88 |
89 | The next example illustrates the effect of a lateral extension on a
90 | square profile. The 0.1 um extension will add material left and right of
91 | the main patch:
92 |
93 | .. code-block:: python
94 |
95 | grow(0.3, 0.1)
96 |
97 | .. image:: ./img/g2_xs.png
98 |
99 | In ``'round'`` mode, the material will be deposited with an elliptical
100 | profile. The vertical axis will be 0.3 um, the horizontal 0.1 um
101 | representing the laternal extension. The patch will become bigger than
102 | the mask by the lateral extension at the bottom:
103 |
104 | .. code-block:: python
105 |
106 | grow(0.3, 0.1, mode='round')
107 |
108 | .. image:: ./img/g3_xs.png
109 |
110 | To avoid overgrow, a negative lateral extension can be specified,
111 | resulting in a alignment of patch and mask at the bottom:
112 |
113 | .. code-block:: python
114 |
115 | grow(0.3, -0.1, mode='round')
116 |
117 | .. image:: ./img/g4_xs.png
118 |
119 | Another mode is ``'octagon'`` which is basically a coarse approximation
120 | of the ellipse and computationally less expensive:
121 |
122 | .. code-block:: python
123 |
124 | grow(0.3, 0.1, mode='octagon')
125 |
126 | .. image:: ./img/g5_xs.png
127 |
128 | A ``bias`` value can be specified to fine-tune the position of the bottom
129 | edge of the patch. A *positive* bias value will *shrink* the figure:
130 |
131 | .. code-block:: python
132 |
133 | grow(0.3, 0.1, mode='round', bias=0.05)
134 |
135 | .. image:: ./img/g6_xs.png
136 |
137 | A special profile can be specified with the ``taper`` option. This option
138 | specifies a taper angle and a conical patch will be created. The taper
139 | angle will be the sidewall angle of the patch. This option cannot be
140 | combined with ``mode`` and the lateral extension should be omitted. It can
141 | be combined with ``bias`` however:
142 |
143 | .. code-block:: python
144 |
145 | grow(0.3, taper=10)
146 |
147 | .. image:: ./img/g7_xs.png
148 |
149 | .. code-block:: python
150 |
151 | grow(0.3, taper=10, bias=-0.1)
152 |
153 | .. image:: ./img/g8_xs.png
154 |
155 | Step coverage
156 | -------------
157 |
158 | The following image shows the step coverage of a 30° slope and a
159 | vertical step by a material deposited in round mode with thickness of
160 | 0.3 um and lateral extension of 0.1 um. The sidewall of the step will be
161 | covered with a thickness of 0.1 um corresponding to the lateral
162 | extension:
163 |
164 | .. code-block:: python
165 |
166 | grow(0.3, 0.1, mode='round')
167 |
168 | .. image:: ./img/g10_xs.png
169 |
170 | ``on`` - growing on specific material
171 | -------------------------------------
172 |
173 | The ``on`` option allows to select growth on a material surface in
174 | addition to selection by a mask. To do so, specify the array of
175 | materials or the single material on which the new material will be
176 | deposited. The surface of these substrate materials will form the seed
177 | of the growth process.
178 |
179 | An array of materials is written as a list of material data objects in
180 | square brackets.
181 |
182 | .. code-block:: python
183 |
184 | # Prepare input layers
185 | m1 = layer("1/0")
186 | m2 = layer("2/0")
187 |
188 | # Grow a stop layer
189 | stop = mask(m2).grow(0.05)
190 |
191 | # Grow with mask m1, but only where there is a substrate surface
192 | metal = mask(m1).grow(0.3, 0.1, mode='round', on=bulk())
193 |
194 | # output the material data to the target layout
195 | output("0/0", bulk())
196 | output("1/0", metal)
197 | output("2/0", stop)
198 |
199 | Here is the input data:
200 |
201 | .. image:: ./img/g12.png
202 |
203 | And this is the result:
204 |
205 | .. image:: ./img/g12_xs.png
206 |
207 |
208 | ``into`` - converting material
209 | ------------------------------
210 |
211 | With the ``into`` option it is possible to convert material below the
212 | mask rather than growing upwards. ``into`` specifies a single material
213 | or an array of materials in square brackets. In effect, the direction
214 | is reversed and the material given by ``into`` is consumed and replaced
215 | by the new material. Note: the ``etch`` operation is basically doing the
216 | same, replacing the material by "air".
217 |
218 | .. code-block:: python
219 |
220 | # Prepare input layers
221 | m1 = layer("1/0")
222 | m2 = layer("2/0")
223 |
224 | substrate = bulk()
225 |
226 | # Grow with mask m1 into the substrate
227 | metal = mask(m1).grow(0.3, 0.1, mode='round', into=substrate)
228 |
229 | # output the material data to the target layout
230 | output("0/0", substrate)
231 | output("1/0", metal)
232 |
233 | This script gives the following result:
234 |
235 | .. image:: ./img/g13_xs.png
236 |
237 | ``through`` - selective conversion
238 | ----------------------------------
239 |
240 | The same way that ``on`` will make the grow selective on the chosen
241 | materials, ``through`` will select seed materials for conversion with
242 | ``into``. Conversion will start at the interface between ``through`` and
243 | air and consume the materials of ``into``. It will not consume the
244 | ``through`` materials:
245 |
246 | .. code-block:: python
247 |
248 | # Prepare input layers
249 | m1 = layer("1/0")
250 | m2 = layer("2/0")
251 |
252 | substrate = bulk()
253 |
254 | stop = mask(m2).grow(0.05, into=substrate)
255 |
256 | # Grow with mask m1 into the substrate
257 | metal = mask(m1).grow(0.3, 0.1, mode='round', through=stop, into=substrate)
258 |
259 | # output the material data to the target layout
260 | output("0/0", substrate)
261 | output("1/0", metal)
262 | output("2/0", stop)
263 |
264 | With the following input:
265 |
266 | .. image:: ./img/g14.png
267 |
268 | This script gives the following result:
269 |
270 | .. image:: ./img/g14_xs.png
271 |
272 | ``buried`` - applies a conversion in a region below the surface
273 | ---------------------------------------------------------------
274 |
275 | If ``buried`` parameter is given, the process is not applied on the
276 | surface, but at the given depth below the surface. The main application
277 | is to model deep implants. In that case, ``into`` can be given to specify
278 | the material to convert and ``buried`` will specify the depth at which the
279 | material is converted. The region of conversion extends below and above
280 | that depth:
281 |
282 | .. code-block:: python
283 |
284 | # Prepare input layers
285 | m1 = layer("1/0")
286 | m2 = layer("2/0")
287 |
288 | substrate = bulk()
289 |
290 | # Grow with mask m1 into the substrate
291 | metal = mask(m1).grow(0.3, 0.1, mode='round', into=substrate, buried=0.4)
292 |
293 | # output the material data to the target layout
294 | output("0/0", substrate)
295 | output("1/0", metal)
296 |
297 | With the following input:
298 |
299 | .. image:: ./img/g15.png
300 |
301 | This script gives the following result:
302 |
303 | .. image:: ./img/g15_xs.png
304 |
--------------------------------------------------------------------------------
/docs/DocIntro.rst:
--------------------------------------------------------------------------------
1 | .. _DocIntro:
2 |
3 | Writing PYXS files - an Introduction
4 | ====================================
5 |
6 | Cross section files are simple to write. They provide a description how
7 | to convert a planar layout into a vertical material stack. PYXS scripts
8 | are really "scripts" - they are small programs which are executed by
9 | KLayout's internal Python interpreter. Simple scripts can be written
10 | without much knowledge of the Python language by following a simple
11 | recipe. Complex scripts can utilize the full power of that scheme by
12 | using loops, procedures, if statements and so on.
13 |
14 | This document is an introduction into PYXS files. A reference with more
15 | details about the functions, methods and their parameters is provided
16 | here: :doc:`DocReference`.
17 |
18 | Let us start with a simple example for a PYXS file:
19 |
20 | .. code-block:: python
21 |
22 | # Prepare input layers
23 | m1 = layer("1/0")
24 |
25 | # "grow" metal on mask m1 with thickness 0.3 and lateral extension 0.1
26 | # with elliptical edge contours
27 | metal1 = mask(m1).grow(0.3, 0.1, mode='round')
28 |
29 | # output the material data to the target layout
30 | output("0/0", bulk)
31 | output("1/0", metal1)
32 |
33 | With the following input:
34 |
35 | .. image:: ./img/s1.png
36 |
37 | This script will produce a simple cross section:
38 |
39 | .. image:: ./img/s1_xs.png
40 |
41 | PYXS files are basically Python scripts, so the syntax rules for the
42 | Python language apply. Specifically:
43 |
44 | * Comments start with a hash symbol (``#``)
45 | * Functions and methods should list their parameters in round brackets
46 | after the function/method name (i.e. ``method(p1, p2)``)
47 | * Layers and materials are variables and should start with a lower
48 | case letter (according to Python style guide
49 | `PEP-8 `_)
50 | * Symbolic values (strings) are included in single or double quotes (i.e.
51 | ``'mode'`` or ``"round"``)
52 | * Some names are reserved and cannot be used as layer or material
53 | names, i.e. ``or``, ``and``, ``if``, ``while``, ``def``, ``class`` etc.
54 | See full list here: `Built-in Functions `_.
55 |
56 | PYXS files typically consist of three sections: the layer preparation
57 | step, the process description and the output section.
58 |
59 | In the layer preparation step, mask layers are computed from design
60 | layers. Often, mask data does not exactly correspond to design layers.
61 | Mask data can be derived from design layers by:
62 |
63 | * Sizing (shifting of the edges to the outside or inside of figures)
64 | * Boolean operations between two design layers (AND, OR, NOT, XOR)
65 | * Inversion of a layer
66 |
67 | The design layer to mask conversion still happens in the xy plane,
68 | looking at the chip layout from above. Once the computation has been
69 | finished, the layout data now represents the mask data and is converted
70 | to a litho pattern by using the "mask" function.
71 | In our example, no conversion takes place, and the input layer
72 | (layer 1, datatype 0) is directly taken to be mask data:
73 |
74 | .. code-block:: python
75 |
76 | mask(m1)
77 |
78 | The mask function will basically create the cut along the ruler and
79 | prepare the mask to become a "seed" for subsequent grow and etch
80 | operations. Note that using a mask seed is not mandatory. There are
81 | also maskless operations (uniform deposit, epitaxy or planarization
82 | steps) which do not require mask data.
83 |
84 | In this example, there is just a single, mask-driven deposition step
85 | which is performed on that mask. It uses the ``grow()`` method which is
86 | applied to the litho pattern object using the ``.`` notation:
87 |
88 | .. code-block:: python
89 |
90 | metal1 = mask(m1).grow(0.3, 0.1, mode='round')
91 |
92 | ``grow()`` is the method and the result of this is a "material" object.
93 | In that case, we specify an "overgrow" with an elliptical profile. The
94 | first argument of the ``grow()`` method is the thickness of the deposited
95 | material in micrometers. The second argument is the lateral extension
96 | (diffusion) in micrometer units. ``mode='round'`` is an option (a keyword
97 | argument in Python) which specifies round or elliptical mode. In the
98 | end, this specification will widen the original line of 0.4 micrometers
99 | to 0.6 micrometers, because it will add 0.1 micrometers to each side
100 | and produce a sheet thickness of 0.3 micrometers.
101 |
102 | The ``grow()`` method is one of the two basic methods for the process
103 | description. It can not only grow some material atop of the current
104 | stack but also convert material below the surface to another material.
105 | Find more about this method here: :doc:`DocGrow`.
106 |
107 | A material object can be used later as a target of etch processes for
108 | example. In our case, we simply output the material to an arbitrary
109 | output layer (here layer 1, datatype 0):
110 |
111 | .. code-block:: python
112 |
113 | output("0/0", bulk())
114 | output("1/0", metal1)
115 |
116 | ``bulk()`` is a pseudo-material which denotes the substrate. It is
117 | written to layer 0, datatype 0 for illustration. In the screenshot,
118 | the ``metal1`` material has been colored light red, the "bulk" material
119 | is colored gray.
120 |
121 | Variables and notation
122 | ----------------------
123 |
124 | The above form of the script is not mandatory. ``m1`` and ``metal1`` are
125 | variables which carry certain information. It is not required to store
126 | this information in variables, except if the information is to be reused
127 | in other places. Hence the computation of the ``metal1`` output can be
128 | reduced to:
129 |
130 | .. code-block:: python
131 |
132 | output("1/0", mask(layer("1/0")).grow(0.3, 0.1, mode='round'))
133 |
134 | But obviously readability suffers, so the expanded notation used before
135 | is recommended. But this example illustrates that there is no magic
136 | behind the material names - they're just variables carrying certain
137 | information. For the interested readers: instances of Python objects
138 | of various kinds representing layout data or material data.
139 |
140 | Input preparation
141 | -----------------
142 |
143 | As stated before, input data can be combined to produce mask data.
144 | PYXS scripts use Python methods on layout data objects to implement
145 | these operations:
146 |
147 | .. code-block:: python
148 |
149 | l1 = layer("1/0")
150 | l2 = layer("2/0")
151 | # Boolean NOT between layer 1, datatype 0 and layer 2, datatype 0
152 | p = l1.not_(l2)
153 |
154 | The same can be written more compactly if required. But again,
155 | readability suffers:
156 |
157 | .. code-block:: python
158 |
159 | p = layer("1/0").not_(layer("2/0"))
160 |
161 | Other Boolean operations available are:
162 |
163 | .. code-block:: python
164 |
165 | l1 = layer("1/0")
166 | l2 = layer("2/0")
167 |
168 | # Boolean OR between layer 1, datatype 0 and layer 2, datatype 0:
169 | p1 = l1.or_(l2)
170 |
171 | # Boolean AND between layer 1, datatype 0 and layer 2, datatype 0:
172 | p2 = l1.and_(l2)
173 |
174 | # Boolean XOR between layer 1, datatype 0 and layer 2, datatype 0:
175 | p3 = l1.xor(l2)
176 |
177 | Layers can be sized (biased). Sizing will shift the edges by the
178 | specified value in micrometer units. Positive values will shift the
179 | edges to the outside of the figures, negative values to the inside.
180 | Hence a positive value with increase the width of a line by twice that
181 | value, a negative value will reduce the width by twice the value.
182 | Negative values may make figures vanish fully or partially, positive
183 | values may remove holes or gaps in the layout.
184 |
185 | Sizing is available in two flavors: a modifying (in-place) version and
186 | version delivering a sized copy (out-of-place). Both methods accept one
187 | or two values. If one value is given, the bias will be applied in x
188 | and y direction, with two values, the first bias will be applied in
189 | horizontal direction, the other one in vertical direction.
190 |
191 | .. code-block:: python
192 |
193 | l1 = layer("1/0")
194 |
195 | # p will be a copy of layer 1, datatype 0, sized by 0.2 micrometers:
196 | p = l1.sized(0.2)
197 |
198 | # this will modify l1 by sizing it with a value of 0.1 in x direction only:
199 | l1.size(0.1, 0)
200 |
201 | Layers can be inverted. Again there is a in-place and out-of-place
202 | version of that method:
203 |
204 | .. code-block:: python
205 |
206 | l1 = layer("1/0")
207 |
208 | # Inverts the layer (modifies l1):
209 | l1.invert()
210 |
211 | # Returns an inverted copy (which is identical with layer 1, datatype 0 again):
212 | p = l1.inverted()
213 |
214 | Caveat: Python and object references
215 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
216 |
217 | When assigning something to another variable it is easy to make a common
218 | mistake: when Python assigns something to another variable, it will
219 | (except for basic types) create another **reference** to the object,
220 | not a copy. That has a strange consequence:
221 |
222 | .. code-block:: python
223 |
224 | a = layer("1/0")
225 | b = a
226 |
227 | # this will invert "b" too, since b references the same object as a:
228 | a.invert()
229 |
230 | To create a real copy, either use the out-of-place methods or use the
231 | ``dup()`` method which creates a copy:
232 |
233 | .. code-block:: python
234 |
235 | # Solution 1:
236 | b = layer("1/0")
237 | a = b.inverted()
238 |
239 | # Solution 2:
240 | a = layer("1/0")
241 | b = a.dup()
242 | a.invert()
243 |
244 | Material data is layout data too
245 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
246 |
247 | Material data derived from deposition methods is layout data too,
248 | although not related to design layers. That allows to apply boolean
249 | operations and sizing to material data as well:
250 |
251 | .. code-block:: python
252 |
253 | l1 = layer("1/0")
254 | l2 = layer("2/0")
255 |
256 | metal1a = mask(l1).grow(0.1, 0.1, mode='round')
257 | metal1b = mask(l2).grow(0.1, 0.1, mode='round')
258 |
259 | output("1/0", metal1a.or_(metal1b))
260 |
261 | Etch operations
262 | ---------------
263 |
264 | The next sample script demonstrates the etch operation and mask-less
265 | deposit methods. It is closer to a real process, which does not use
266 | "mask-driven" grow but rather deposition and etch. It uses a layer
267 | inversion to turn the polarity of the mask: etching has to happen where
268 | no structure is drawn in order to produce the structure where it was
269 | drawn.
270 |
271 | .. code-block:: python
272 |
273 | # Prepare input layers
274 | m1 = layer("1/0")
275 | m1i = m1.inverted()
276 |
277 | # deposit metal with width 0.25 micron
278 | metal1 = deposit(0.25)
279 |
280 | substrate = bulk()
281 |
282 | # etch metal on mask m1 with thickness 0.3 and lateral extension 0.1
283 | # with elliptical edge contours
284 | mask(m1i).etch(0.3, 0.1, mode='round', into=[metal1, substrate])
285 |
286 | # output the material data to the target layout
287 | output("0/0", substrate)
288 | output("1/0", metal1)
289 |
290 | With the following input:
291 |
292 | .. image:: ./img/s2.png
293 |
294 | This script will produce the following cross-section:
295 |
296 | .. image:: ./img/s2_xs.png
297 |
298 | The layer preparation step performs the inversion using the ``inverted()``
299 | method:
300 |
301 | .. code-block:: python
302 |
303 | m1 = layer("1/0")
304 | m1i = m1.inverted()
305 |
306 | ``m1i`` will be the inverted ``m1`` mask and will be used as the "seed" for
307 | the etch.
308 |
309 | Because we etch into the substrate we have to provide substrate as a
310 | material too. This is achieved with the following assignment:
311 |
312 | .. code-block:: python
313 |
314 | substrate = bulk()
315 |
316 | ``bulk()`` is a pseudo-material describing the wafer substrate. Initially
317 | it is the wafer material below the surface. ``bulk()`` is a function,
318 | so we have to create a material we can modify by storing the material
319 | data object in a variable we call ``substrate``.
320 |
321 | The etch step will now start from the inverted metal1 mask. Etch depth
322 | will be 0.3 micrometers, and we specify an underetch of 0.1 micrometers
323 | with elliptical profile. For the etch method we have to specify the
324 | material our etch process will remove. There is no differentiation in
325 | etch rate for these materials - they are assumed to behave the same way.
326 | Materials not listed will effectively form an etch stop.
327 |
328 | The materials the etch method will remove are listed with the ``into``
329 | parameter. This named parameter is mandatory for the etch method.
330 | The argument of that parameter is a material object or an array of
331 | objects, if multiple materials are to be removed. Arrays are formed by
332 | enclosing the list in square brackets:
333 |
334 | .. code-block:: python
335 |
336 | mask(m1i).etch(0.3, 0.1, mode='round', into=[metal1, substrate])
337 |
338 | Because we etch deeper (0.3) than the sheet we have deposited before
339 | (0.25), we will remove a little bit of substrate as well. Note also,
340 | that we reduce the line with from 0.8 (drawn) to 0.6 at the top edge of
341 | the metal line. This bias will typically be compensated by a sizing
342 | operation when the mask data is prepared in a real process.
343 |
344 | Find details about the ``etch()`` method here: :doc:`DocEtch`.
345 |
346 | Backside processing
347 | -------------------
348 |
349 | The last sample script demonstrates combinations of process steps and
350 | backside processing. Here is the script:
351 |
352 | .. code-block:: python
353 |
354 | # Specify wafer thickness
355 | depth(1)
356 |
357 | # Prepare input layers
358 | m1 = layer("1/0").inverted()
359 | m2 = layer("2/0")
360 |
361 | # deposit metal with width 0.25 micron
362 | metal1 = deposit(0.25)
363 |
364 | substrate = bulk()
365 |
366 | # etch metal on mask m1 with thickness 0.3 and lateral extension 0.1
367 | # with elliptical edge contours
368 | mask(m1).etch(0.3, 0.1, mode='round', into=[metal1, substrate])
369 |
370 | # process from the backside
371 | flip()
372 |
373 | # backside etch, taper angle 4 degree
374 | mask(m2).etch(1, taper=4, into=substrate)
375 |
376 | # fill with metal and polish
377 | metal2 = deposit(1.1)
378 | planarize(downto=substrate, into=metal2)
379 |
380 | # output the material data to the target layout
381 | output("0/0", substrate)
382 | output("1/0", metal1.or_(metal2))
383 |
384 | With the following input:
385 |
386 | .. image:: ./img/s3.png
387 |
388 | This script will produce the following cross section:
389 |
390 | .. image:: ./img/s3_xs.png
391 |
392 | The topside processing part is the same than the previous sample and
393 | produces a metal structure at the top of the wafer. For backside
394 | processing, it is important to specify the wafer thickness. For
395 | illustration we use an unreasonably small value of 1 micrometer:
396 |
397 | .. code-block:: python
398 |
399 | depth(1)
400 |
401 | The interesting part starts with this line:
402 |
403 | .. code-block:: python
404 |
405 | flip()
406 |
407 | This will basically turn the wafer and processing now happens from the
408 | backside. You can flip again to return to top side processing. We use a
409 | sequence of operations to create a filled through-silicon via:
410 |
411 | .. code-block:: python
412 |
413 | # backside etch, taper angle 4 degree
414 | mask(m2).etch(1, taper=4, into=substrate)
415 |
416 | # fill with metal and polish
417 | metal2 = deposit(0.3, 0.3, mode='square')
418 | planarize(downto=substrate, into=metal2)
419 |
420 |
421 | The etch step is configured to produce a tapered hole with a taper angle
422 | of 4 degree. The initial (now bottom) dimension of the hole is defined
423 | by the ``m2`` mask which is shown in blue color in the layout and the
424 | structure has a dimension of 0.4 micrometers.
425 |
426 | After the hole has been etched, we deposit a metal layer thick enough
427 | to fill the hole. 0.3 micrometer layer thickness is sufficient. The
428 | deposition will cover the side walls of the hole and fill the hole
429 | completely. Square mode is creating a square profile which is not
430 | realistic, but computationally simple.
431 |
432 | The deposition will also create metal at the bottom side of the wafer
433 | which we polish away with a planarization step. The planarization step
434 | is similar to a maskless etch, but it will remove the specified material
435 | down to a certain depth. This stop condition is defined either by a
436 | planarization stop material (here: substrate) or by a threshold value.
437 |
438 | On output we merge both metal types (front and backside deposited
439 | material) to form a joined metal structure:
440 |
441 | .. code-block:: python
442 |
443 | output("1/0", metal1.or_(metal2))
444 |
--------------------------------------------------------------------------------
/docs/DocReference.rst:
--------------------------------------------------------------------------------
1 | .. _DocReference:
2 |
3 | PYXS File Reference
4 | ===================
5 |
6 |
7 | This document details the functions available in PYXS scripts. An
8 | introduction is available as a separate document:
9 | :doc:`DocIntro`.
10 |
11 | In PYXS scripts, there are basically three kind of functions and
12 | methods:
13 |
14 | * Standalone functions which don't require an object. For example
15 | ``input()`` and ``deposit()``.
16 | * Methods on original layout layers (and in some weaker sense on
17 | material data objects), i.e. ``invert()`` or ``not_()``.
18 | * Methods on mask data objects, i.e. ``grow()`` and ``etch()``.
19 |
20 | Functions
21 | ---------
22 |
23 | The following standalone functions are available:
24 |
25 | .. list-table::
26 | :widths: 15 70
27 | :header-rows: 1
28 |
29 | * - Function
30 | - Description
31 | * - ``all()``
32 | - Return a pseudo-mask, covering the whole wafer
33 | * - ``below(b)``
34 | - | Configure the lower height of the processing window for
35 | | backside processing (see below)
36 | * - ``bulk()``
37 | - Return a pseudo-material describing the wafer body
38 | * - ``delta(d)``
39 | - Configure the accuracy parameter (see ``below()``)
40 | * - | ``deposit(...)``
41 | | ``grow()``
42 | | ``diffuse()``
43 | - | Deposit material as a uniform sheet. Equivalent to
44 | | ``all().grow(...)``. Return a material data object
45 | * - ``depth(d)``
46 | - | Configure the depth of the processing window or the wafer
47 | | thickness for backside processing (see below)
48 | * - ``etch(...)``
49 | - Uniform etching. Equivalent to ``all.etch(...)``
50 | * - ``extend(x)``
51 | - Configure the computation margin (see below)
52 | * - ``flip()``
53 | - Start or end backside processing
54 | * - ``height(h)``
55 | - Configure the height of the processing window (see below)
56 | * - ``layer(layer_spec)``
57 | - | Fetche an input layer from the original layout. Return a
58 | | layer data object.
59 | * - ``layers_file(lyp_filename)``
60 | - | Configure a ``.lyp`` layer properties file to be used on the
61 | | cross-section layout
62 | * - ``mask(layout_data)``
63 | - | Designate the ``layout_data`` object as a litho pattern (mask).
64 | | This is the starting point for structured grow or etch
65 | | operations. Return a mask data object.
66 | * - ``output(layer_spec, material)``
67 | - Output a material object to the output layout
68 | * - ``planarize(...)``
69 | - Planarization
70 |
71 | ``all()`` method
72 | ^^^^^^^^^^^^^^^^
73 |
74 | This method delivers a mask data object which covers the whole wafer.
75 | It's used as seed for the global etch and grow function only.
76 |
77 | ``below()``, ``depth()`` and ``height()`` methods
78 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
79 |
80 | The material operations a performed in a limited processing window,
81 | which extends a certain height over the wafer top surface (``height``),
82 | covers the wafer with a certain depth (``depth``) and extends below the
83 | wafer for backside processing (``below`` parameter). Material cannot grow
84 | outside the space above or below the wafer. Etching cannot happen
85 | deeper than ``depth``. For backside processing, ``depth`` also defines the
86 | wafer thickness.
87 |
88 | The parameters can be modified with the respective functions. All
89 | functions accept a value in micrometer units. The default value is
90 | 2 micrometers.
91 |
92 | ``bulk()`` method
93 | ^^^^^^^^^^^^^^^^^
94 |
95 | This methods returns a material data object which represents the wafer
96 | at it's initial state. This object can be used to represent the
97 | unmodified wafer substrate and can be target of etch operations. Every
98 | call of ``bulk()`` will return a fresh object, so the object needs to be
99 | stored in a variable for later use:
100 |
101 | .. code-block:: python
102 |
103 | substrate = bulk()
104 | mask(layer).etch(0.5, into='substrate')
105 | output("1/0", substrate)
106 |
107 | ``delta()`` method
108 | ^^^^^^^^^^^^^^^^^^
109 |
110 | Due to limitations of the underlying processor which cannot handle
111 | infinitely thin polygons, there is an accuracy limit for the creation
112 | or modification or geometrical regions. The delta parameter will
113 | basically determine that accuracy level and in some cases, for example
114 | the sheet thickness will only be accurate to that level. In addition,
115 | healing or small gaps and slivers during the processing uses the delta
116 | value as a dimension threshold, so shapes or gaps smaller than that
117 | value cannot be produced.
118 |
119 | The default value of ``delta`` is 10 database units. To modify the value,
120 | call the ``delta()`` function with the desired delta value in micrometer
121 | units. The minimum value recommended is 2 database unit. That implies
122 | that the accuracy can be increased by using a smaller database unit for
123 | the input layout.
124 |
125 | ``deposit()`` (``grow()``, ``diffuse()``) methods
126 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127 |
128 | This function will deposit material uniformly. ``grow()`` and ``diffuse()``
129 | are just synonyms. It is equivalent to ``all.grow(...)``. For a
130 | description of the parameters see the ``grow()`` method on the mask data
131 | object.
132 |
133 | The ``deposit()`` function will return a material object representing the
134 | deposited material.
135 |
136 | ``etch()`` method
137 | ^^^^^^^^^^^^^^^^^
138 |
139 | This function will perform a uniform etch and is equivalent to
140 | ``all().etch(...)``. For a description of the parameter see the
141 | "etch()" function on the mask data object.
142 |
143 | ``extend()`` method
144 | ^^^^^^^^^^^^^^^^^^^
145 |
146 | To reduce the likelihood of missing important features, the cross
147 | section script will sample the layout in a window around the cut line.
148 | The dimensions of that window are controlled by the ``extend`` parameter.
149 | The window extends the specified value to the left, the right, the start
150 | and end of the cut line.
151 |
152 | The default value is 2 micrometers. To catch all relevant input data in
153 | cases where positive sizing values larger than the extend parameter are
154 | used, increase the extend value by calling ``extend(e)`` with the desired
155 | value in micrometer units.
156 |
157 | In addition, the ``extend`` parameter determines the extension of an
158 | invisible part left and right of the cross section, which is included
159 | in the processing to reduce border effects. If deposition or etching
160 | happens with dimensions bigger than the extend value, artifacts start
161 | to appear at the borders of the simulation window. The extend value can
162 | then be increased to hide these effects.
163 |
164 | ``flip()`` method
165 | ^^^^^^^^^^^^^^^^^
166 |
167 | This function will start backside processing. After this function,
168 | modifications will be applied on the back side of the wafer. Calling
169 | ``flip()`` again, will continue processing on the front side.
170 |
171 | ``layer()`` method
172 | ^^^^^^^^^^^^^^^^^^
173 |
174 | The layer method fetches a layout layer and prepares a layout data
175 | object for further processing. The ``layer()`` function expects a single
176 | string parameter which encodes the source of the layout data.
177 |
178 | The function understands the following variants:
179 |
180 | * ``layer("17")``: Layer 17, datatype 0
181 | * ``layer("17/6")``: Layer 17, datatype 6
182 | * ``layer("METAL1")``: layer "METAL1" for formats that support
183 | named layers (DXF, CIF)
184 | * ``layer("METAL1 (17/0)")``: hybrid specification for GDS
185 | (layer 17, datatype 0) and "METAL1" for named-layer formats like DXF
186 | and CIF.
187 |
188 | ``layers_file()`` method
189 | ^^^^^^^^^^^^^^^^^^^^^^^^
190 |
191 | This function specifies a layer properties file which will be loaded
192 | when the cross section has been generated. This file specifies colors,
193 | fill pattern and other parameters of the display:
194 |
195 | .. code-block:: python
196 |
197 | layers_file("/home/matthias/xsection/lyp_files/cmos1.lyp")
198 |
199 | ``mask()`` method
200 | ^^^^^^^^^^^^^^^^^
201 |
202 | The ``mask()`` function designates the given layout data object as a litho
203 | mask. It returns a mask data object which is the starting point for
204 | further ``etch()`` or ``grow()`` operations:
205 |
206 | .. code-block:: python
207 |
208 | l1 = layer("1/0")
209 | metal = mask(l1).grow(0.3)
210 | output("1/0", metal)
211 |
212 | ``output()`` method
213 | ^^^^^^^^^^^^^^^^^^^
214 |
215 | The ``output()`` function will write the given material to the output
216 | layout. The function expects two parameters: an output layer
217 | specification and a material object:
218 |
219 | .. code-block:: python
220 |
221 | output("1/0", metal)
222 |
223 | The layer specifications follow the same rules than for the ``layer()``
224 | function described above.
225 |
226 | ``planarize()`` method
227 | ^^^^^^^^^^^^^^^^^^^^^^
228 |
229 | The ``planarize()`` function removes material of the given kind (``into``
230 | argument) down to a certain level. The level can be determined
231 | numerically or by a stop layer.
232 |
233 | The function takes a couple of keyword parameters in the Python notation
234 | (``name=value``), for example:
235 |
236 | .. code-block:: python
237 |
238 | planarize(downto=substrate, into=metal)
239 | planarize(less=0.5, into=[metal, substrate])
240 |
241 | The keyword parameters are:
242 |
243 | .. list-table::
244 | :widths: 10 70
245 | :header-rows: 1
246 |
247 | * - Name
248 | - Description
249 | * - ``into``
250 | - | (mandatory) A single material or an array or materials. The
251 | | planarization will remove these materials selectively.
252 | * - ``downto``
253 | - | Value is a material. Planarization stops at the topmost point
254 | | of that material. Cannot be used together with ``less`` or ``to``.
255 | * - ``less``
256 | - | Value is a micrometer distance. Planarization will remove a
257 | | horizontal alice of the given material, stopping ``less``
258 | | micrometers measured from the topmost point of that material
259 | | before the planarization. Cannot be used together with ``downto``
260 | | or ``to``.
261 | * - ``to``
262 | - | Value is micrometer z value. Planarization stops when reaching
263 | | that value. The z value is measured from the initial wafer
264 | | surface. Cannot be used together with ``downto`` or ``less``.
265 |
266 |
267 | Methods on original layout layers or material data objects
268 | ----------------------------------------------------------
269 |
270 | The following methods are available for these objects:
271 |
272 | .. list-table::
273 | :widths: 15 60
274 | :header-rows: 1
275 |
276 | * - Method
277 | - Description
278 | * - ``size(s)`` or ``size(x, y)``
279 | - Isotropic or anisotropic sizing
280 | * - ``sized(s)`` or ``sized(x, y)``
281 | - Out-of-place version of ``size()``
282 | * - ``invert()``
283 | - Invert a layer
284 | * - ``inverted()``
285 | - Out-of-place version of ``invert()``
286 | * - ``or_(other)``
287 | - Boolean OR (merging) with another layer
288 | * - ``and_(other)``
289 | - Boolean AND (intersection) with another layer
290 | * - ``xor(other)``
291 | - Boolean XOR (symmetric difference) with another layer
292 | * - ``not_(other)``
293 | - Boolean NOT (difference) with another layer
294 |
295 | ``size()`` method
296 | ^^^^^^^^^^^^^^^^^^^^^^
297 |
298 | This method will apply a bias to the layout data. A bias is applied by
299 | shifting the edges to the outside (for positive bias) or the inside
300 | (for negative bias) of the figure.
301 |
302 | Applying a bias will increase or reduce the dimension of a figure by
303 | twice the value.
304 |
305 | Two versions are available: isotropic or anisotropic sizing. The first
306 | version takes one single value in micrometer units and applies this value
307 | in x and y direction. The second version takes two values for x and y
308 | direction.
309 |
310 | The ``size()`` method will modify the layer object (in-place). A
311 | non-modifying version (out-of-place) is ``sized()``.
312 |
313 | .. code-block:: python
314 |
315 | l1 = layer("1/0")
316 | l1.size(0.3)
317 | metal = mask(l1).grow(0.3)
318 |
319 | ``sized()`` method
320 | ^^^^^^^^^^^^^^^^^^
321 |
322 | Same as ``size()``, but returns a new layout data object rather than
323 | modifying it:
324 |
325 | .. code-block:: python
326 |
327 | l1 = layer("1/0")
328 | l1_sized = l1.sized(0.3)
329 | metal = mask(l1_sized).grow(0.3)
330 | # l1 can still be used in the original form
331 |
332 | ``invert()`` method
333 | ^^^^^^^^^^^^^^^^^^^
334 |
335 | Inverts a layer (creates layout where nothing is drawn and vice versa).
336 | This method modifies the layout data object (in-place):
337 |
338 | .. code-block:: python
339 |
340 | l1 = layer("1/0")
341 | l1.invert()
342 | metal = mask(l1).grow(0.3)
343 |
344 | A non-modifying version (out-of-place) is ``inverted()``.
345 |
346 | ``inverted()`` method
347 | ^^^^^^^^^^^^^^^^^^^^^
348 |
349 | Returns a new layout data object representing the inverted source
350 | layout:
351 |
352 | .. code-block:: python
353 |
354 | l1 = layer("1/0")
355 | l1_inv = l1.inverted()
356 | metal = mask(l1_inv).grow(0.3)
357 | # l1 can still be used in the original form
358 |
359 | ``or_()``, ``and_()``, ``xor()``, ``not_()`` methods
360 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
361 |
362 | These methods perform boolean operations. Their notation is somewhat
363 | unusual but follows the method notation of Python:
364 |
365 | .. code-block:: python
366 |
367 | l1 = layer("1/0")
368 | l2 = layer("2/0")
369 | one_of_them = l1.xor(l2)
370 |
371 | Here is the output of the operations:
372 |
373 | .. list-table::
374 | :widths: 10 10 15 15 15 15
375 | :header-rows: 1
376 |
377 | * - layer ``a``
378 | - layer ``b``
379 | - ``a.or_(b)``
380 | - ``a.and_(b)``
381 | - ``a.xor(b)``
382 | - ``a.not_(b)``
383 | * - clear
384 | - clear
385 | - clear
386 | - clear
387 | - clear
388 | - clear
389 | * - drawn
390 | - clear
391 | - drawn
392 | - clear
393 | - drawn
394 | - drawn
395 | * - clear
396 | - drawn
397 | - drawn
398 | - clear
399 | - drawn
400 | - clear
401 | * - drawn
402 | - drawn
403 | - drawn
404 | - drawn
405 | - clear
406 | - clear
407 |
408 |
409 | Methods on mask data objects: ``grow()`` and ``etch()``
410 | -------------------------------------------------------
411 |
412 | The following methods are available for mask data objects:
413 |
414 | .. list-table::
415 | :widths: 15 60
416 | :header-rows: 1
417 |
418 | * - Method
419 | - Description
420 | * - ``grow(...)``
421 | - Deposition of material where this mask is present
422 | * - ``etch(...)``
423 | - Removal of material where this mask is present
424 |
425 | ``grow()`` method
426 | ^^^^^^^^^^^^^^^^^
427 |
428 | This method is important and has a rich parameter set, so it is
429 | described in an individual document here: :doc:`DocGrow`.
430 |
431 | ``etch()`` method
432 | ^^^^^^^^^^^^^^^^^
433 |
434 | This method is important and has a rich parameter set, so it is
435 | described in an individual document here: :doc:`DocEtch`.
436 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SOURCEDIR = .
8 | BUILDDIR = build
9 |
10 | # Put it first so that "make" without argument is like "make help".
11 | help:
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13 |
14 | .PHONY: help Makefile
15 |
16 | # Catch-all target: route all unknown targets to Sphinx using the new
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 | %: Makefile
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
20 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | project = "klayout_pyxs"
2 | copyright = "2019, Dzmitry Pustakhod"
3 | author = "Dzmitry Pustakhod"
4 | release = "0.1.13"
5 |
6 | extensions = [
7 | "matplotlib.sphinxext.plot_directive",
8 | "myst_parser",
9 | "nbsphinx",
10 | "sphinx.ext.autodoc",
11 | "sphinx.ext.doctest",
12 | "sphinx.ext.mathjax",
13 | "sphinx.ext.napoleon",
14 | "sphinx.ext.todo",
15 | "sphinx.ext.viewcode",
16 | "sphinx_autodoc_typehints",
17 | "sphinx_click",
18 | # "sphinx_markdown_tables",
19 | "sphinx_copybutton",
20 | "sphinxcontrib.autodoc_pydantic",
21 | "sphinx.ext.autosummary",
22 | "sphinx.ext.extlinks",
23 | ]
24 |
25 | templates_path = ["_templates"]
26 | source_suffix = [".rst"]
27 | master_doc = "index"
28 | language = None
29 | exclude_patterns = []
30 | html_theme = "sphinx_book_theme"
31 |
32 | html_static_path = ["_static"]
33 |
34 | # htmlhelp_basename = "klayout_pyxsdoc"
35 |
36 |
37 | # -- Options for LaTeX output ------------------------------------------------
38 |
39 | latex_elements = {
40 | # The paper size ('letterpaper' or 'a4paper').
41 | #
42 | # 'papersize': 'letterpaper',
43 | # The font size ('10pt', '11pt' or '12pt').
44 | #
45 | # 'pointsize': '10pt',
46 | # Additional stuff for the LaTeX preamble.
47 | #
48 | # 'preamble': '',
49 | # Latex figure (float) alignment
50 | #
51 | # 'figure_align': 'htbp',
52 | }
53 |
54 | # Grouping the document tree into LaTeX files. List of tuples
55 | # (source start file, target name, title,
56 | # author, documentclass [howto, manual, or own class]).
57 | latex_documents = [
58 | (
59 | master_doc,
60 | "klayout_pyxs.tex",
61 | "klayout\\_pyxs Documentation",
62 | "Dzmitry Pustakhod",
63 | "manual",
64 | ),
65 | ]
66 |
67 |
68 | # -- Options for manual page output ------------------------------------------
69 |
70 | # One entry per manual page. List of tuples
71 | # (source start file, name, description, authors, manual section).
72 | man_pages = [(master_doc, "klayout_pyxs", "klayout_pyxs Documentation", [author], 1)]
73 |
74 |
75 | # -- Options for Texinfo output ----------------------------------------------
76 |
77 | # Grouping the document tree into Texinfo files. List of tuples
78 | # (source start file, target name, title, author,
79 | # dir menu entry, description, category)
80 | texinfo_documents = [
81 | (
82 | master_doc,
83 | "klayout_pyxs",
84 | "klayout_pyxs Documentation",
85 | author,
86 | "klayout_pyxs",
87 | "One line description of project.",
88 | "Miscellaneous",
89 | ),
90 | ]
91 |
92 |
93 | # -- Options for Epub output -------------------------------------------------
94 |
95 | # Bibliographic Dublin Core info.
96 | epub_title = project
97 |
98 | # The unique identifier of the text. This can be a ISBN number
99 | # or the project homepage.
100 | #
101 | # epub_identifier = ''
102 |
103 | # A unique identification for the text.
104 | #
105 | # epub_uid = ''
106 |
107 | # A list of files that should not be packed into the epub file.
108 | epub_exclude_files = ["search.html"]
109 |
110 |
111 | # -- Extension configuration -------------------------------------------------
112 |
113 | # -- Options for todo extension ----------------------------------------------
114 |
115 | # If true, `todo` and `todoList` produce output, else they produce nothing.
116 | todo_include_todos = True
117 |
118 | html_theme_options = {
119 | "path_to_docs": "docs",
120 | "repository_url": "https://github.com/gdsfactory/klayout_pyxs",
121 | "repository_branch": "master",
122 | "use_edit_page_button": True,
123 | "use_issues_button": True,
124 | "use_repository_button": True,
125 | "use_download_button": True,
126 | }
127 |
--------------------------------------------------------------------------------
/docs/img/e1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e1.png
--------------------------------------------------------------------------------
/docs/img/e10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e10.png
--------------------------------------------------------------------------------
/docs/img/e10_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e10_xs.png
--------------------------------------------------------------------------------
/docs/img/e12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e12.png
--------------------------------------------------------------------------------
/docs/img/e12_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e12_xs.png
--------------------------------------------------------------------------------
/docs/img/e13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e13.png
--------------------------------------------------------------------------------
/docs/img/e13_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e13_xs.png
--------------------------------------------------------------------------------
/docs/img/e14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e14.png
--------------------------------------------------------------------------------
/docs/img/e14_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e14_xs.png
--------------------------------------------------------------------------------
/docs/img/e1_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e1_xs.png
--------------------------------------------------------------------------------
/docs/img/e2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e2.png
--------------------------------------------------------------------------------
/docs/img/e2_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e2_xs.png
--------------------------------------------------------------------------------
/docs/img/e3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e3.png
--------------------------------------------------------------------------------
/docs/img/e3_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e3_xs.png
--------------------------------------------------------------------------------
/docs/img/e4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e4.png
--------------------------------------------------------------------------------
/docs/img/e4_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e4_xs.png
--------------------------------------------------------------------------------
/docs/img/e5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e5.png
--------------------------------------------------------------------------------
/docs/img/e5_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e5_xs.png
--------------------------------------------------------------------------------
/docs/img/e6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e6.png
--------------------------------------------------------------------------------
/docs/img/e6_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e6_xs.png
--------------------------------------------------------------------------------
/docs/img/e7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e7.png
--------------------------------------------------------------------------------
/docs/img/e7_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e7_xs.png
--------------------------------------------------------------------------------
/docs/img/e8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e8.png
--------------------------------------------------------------------------------
/docs/img/e8_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/e8_xs.png
--------------------------------------------------------------------------------
/docs/img/g1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g1.png
--------------------------------------------------------------------------------
/docs/img/g10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g10.png
--------------------------------------------------------------------------------
/docs/img/g10_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g10_xs.png
--------------------------------------------------------------------------------
/docs/img/g12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g12.png
--------------------------------------------------------------------------------
/docs/img/g12_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g12_xs.png
--------------------------------------------------------------------------------
/docs/img/g13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g13.png
--------------------------------------------------------------------------------
/docs/img/g13_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g13_xs.png
--------------------------------------------------------------------------------
/docs/img/g14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g14.png
--------------------------------------------------------------------------------
/docs/img/g14_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g14_xs.png
--------------------------------------------------------------------------------
/docs/img/g15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g15.png
--------------------------------------------------------------------------------
/docs/img/g15_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g15_xs.png
--------------------------------------------------------------------------------
/docs/img/g1_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g1_xs.png
--------------------------------------------------------------------------------
/docs/img/g2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g2.png
--------------------------------------------------------------------------------
/docs/img/g2_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g2_xs.png
--------------------------------------------------------------------------------
/docs/img/g3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g3.png
--------------------------------------------------------------------------------
/docs/img/g3_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g3_xs.png
--------------------------------------------------------------------------------
/docs/img/g4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g4.png
--------------------------------------------------------------------------------
/docs/img/g4_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g4_xs.png
--------------------------------------------------------------------------------
/docs/img/g5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g5.png
--------------------------------------------------------------------------------
/docs/img/g5_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g5_xs.png
--------------------------------------------------------------------------------
/docs/img/g6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g6.png
--------------------------------------------------------------------------------
/docs/img/g6_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g6_xs.png
--------------------------------------------------------------------------------
/docs/img/g7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g7.png
--------------------------------------------------------------------------------
/docs/img/g7_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g7_xs.png
--------------------------------------------------------------------------------
/docs/img/g8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g8.png
--------------------------------------------------------------------------------
/docs/img/g8_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/g8_xs.png
--------------------------------------------------------------------------------
/docs/img/s1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/s1.png
--------------------------------------------------------------------------------
/docs/img/s1_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/s1_xs.png
--------------------------------------------------------------------------------
/docs/img/s2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/s2.png
--------------------------------------------------------------------------------
/docs/img/s2_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/s2_xs.png
--------------------------------------------------------------------------------
/docs/img/s3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/s3.png
--------------------------------------------------------------------------------
/docs/img/s3_xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/s3_xs.png
--------------------------------------------------------------------------------
/docs/img/xsection_70p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/docs/img/xsection_70p.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to klayout_pyxs's documentation!
2 | ========================================
3 |
4 | `klayout_pyxs `_
5 | is a package that implements a cross-section generator for the KLayout
6 | VLSI layout viewer and editor
7 | `http://www.klayout.de `_.
8 |
9 | It is a port of Ruby script
10 | `XSection `_.
11 | Most of the algorithms / tests / documentation are taken with minor
12 | adaptations from XSection project.
13 |
14 | In order to use the cross section generator, a description of the
15 | process must be provided. Such a description is stored in files with
16 | extension ``.pyxs``. They contain a step-by-step recipe how the layer
17 | stack is formed. Statements will describe individual process steps
18 | such as etching, deposition and material conversion (i.e. implant).
19 |
20 | The source tree contains one example for such a file in
21 | "samples/cmos.pyxs". This example illustrates how to create a ".pyxs"
22 | and has a lot of documentation inside. Have a look at this file here:
23 | [cmos.pyxs](cmos.pyxs).
24 |
25 | Using The KLayout PYXS Module
26 | -----------------------------
27 |
28 | Start KLayout after you have installed the script. You will find a new entry in the "Tools" menu.
29 | Choosing "Tools/pyxs/Load pyxs script" opens a file browser and you are prompted for the ``.pyxs`` file.
30 |
31 | To create a cross section, draw a ruler into the layout indicating the line along which the
32 | cross section is created. Choose "Tools/pyxs/Load pyxs script" to select
33 | the ".pyxs" file and to generate the cross section in a new layout window. Once you have
34 | used a ``.pyxs`` file, it is available in the recently used files list below the "Tools/pyxs"
35 | menu entry for quick access.
36 |
37 | An introduction into writing PYXS files can be found here: :doc:`DocIntro`.
38 |
39 | A function reference is also available here: :doc:`DocReference`.
40 |
41 | Example
42 | -------
43 |
44 | The following screenshot shows a sample cross section taken from the
45 | [cmos.pyxs](cmos.pyxs) sample file and the [sample.gds](sample.gds) layout found in the samples folder:
46 |
47 | .. image:: ./img/xsection_70p.png
48 |
49 | .. toctree::
50 | :caption: Contents
51 | :maxdepth: 2
52 |
53 | README
54 | DocIntro
55 | DocGrow
56 | DocEtch
57 | DocReference
58 | CHANGELOG
59 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/klayout_package/grain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | klayout_pyxs
4 |
5 | false
6 | 0.1.13
7 |
8 | klayout_pyxs
9 | python port of the Klayout xsection project
10 | https://gdsfactory.github.io/klayout_pyxs/
11 | https://github.com/gdsfactory/klayout_pyxs
12 | MIT
13 | Dima Pustakhod
14 | D.Pustakhod@tue.nl>
15 | 2022
16 | iVBORw0KGgoAAAANSUhEUgAAAIAAAAB5CAIAAAB+wb5NAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH5gcJDToFnvp4AAAAFKZJREFUeNrtXXuYVMWV/52qe28/pgcG5CmCKKBBBQEFlKAQDYorrsnuRlS+rCKamFXXKJ/RzX6bxCSYbDS+PnUT3TVAkjUSlQhR44PgKxiNguILCYkPFCMMODM9/biPOmf/uN1NzwzDNNDDZWb79/Hx3Z6uvvfU+VXVOVV16lwSEQACIRAjzfiAoFDDvkHABEfhcIIOddtZyZquI0aNgIhRIyBi1AiIGFUmoGDSy7D/q1QuQ+njAYtqEiAiRMTMxjARiYCZy3XRrSipm5nLH0dEe3/T7odVxfoTkQgrxGwrEXCTpjiRbSQNqJCF8vKhXtppqvQxvC7/f5e/IiIIBLJTBpCtUgZZRQoCBhe/bHP/dk+JkICq9QACMbNF8Y8++vjC+ZfPOfPSWbPmLfv1QxalRDhUDQBm1qQgMMaUPpIQs4iIsFikw1Zc0g6X/s6F1s3MmjQExjAIzEwgBRKRIDAPLl9hUfL66+94eMUqC/W+H+iye0IAgYIKb9WRlZ5KgEAspbP5zBe+cOkRRx72v7/68Q0/XHjtN258/KlVtkqROCIQgaP7ZvN5YW3rlIg4OpXJ5QJDjk4p0poSLZm0RlKruEiBA02WonhLJq0pqVUcIFv3ac1lhC1bJ1iMo+uDQPJuYKsBH2zecvNNP0tndlxw4RdPnHYs4MWsvplcRiOhKS5ilHIgtuf7jq4nQKIeo6o2BBnDltVw//1LBg0a8M3rvhUEf5t6/PRfLbtj6NC+Dy5f8bN7H9qxo+mee25YuXLVM8+8pBRdddVFnz/11FtuvfMPf1jX1NRy2eXz5pw18+qvf2/Llm2e59940zWfOWJU1s05jt3U3Pztb92+5aOtvu//+Jbrxhw+6pv/vmjjxve1Vtdd99WJE6Yue2DZ0iXL3bx30cX/mE5nXnjh1QceeHzr1h1jx46aM2fQgouvaWxschzrttv+g8UsvPqHQ4YM+NOf3jh/3px/vXyBZ7JKUYQcVNEIC6A2bHh33LgxQCYQ3zPbJk4Yf+jwKZs2vffxlq1PPHXvmjUvr3pqzWOP3rPwmgXXXnvjn//61j13L/vRTdfcfMt1vu8/8ttn1q/f+OADP/nnC87+4IMtQH+LBlg08pHfPv3qurcffODuuef+XTqduXfxstdee+fnv/zRrNM+e+WV3/94619uWPRft9z2b3f99NuPPfrsyTMmz5w5Zf4F8za8/RdAFi2603HslSt+ceKJE6+44ruWrZ977pWLLv6nxUt+cPdP7t/RvM3RdlSqrzoBAKR//z47tqcBOwh8W8e2bfukNfdnpdTnZk5PJSa+/PKbZ545G+h7yoyTBg0a2NqSvWrh/LPnfO2GRT8de9SoOXNmjh49YuKkM//4wmvTph33y/sWX3zJZd/57rVnnHHyEUeMnDjpjHVr35pw7Ng1f1jb1JS+9CvffurJNccdd/Tzz788ZszoMYcfM2L4IUuXLE0mE4YZEMuyPM9/841N5593DiDnnfcPruu/9+5HkyYdPXH8pMNHjhw4cEBraxZQ0bqp1TPCpID8abOmv/jS2h1NH6YShxAGXHHFt37x8xX9+zWQDoD8gAENmz98H+ifTuebm5uy+dy48Ue+/vqjc86a+eXzv7F+/YYrr5y/bu3D+Xz+sn+5ftSoEYePGj5q1PB339t89cIF69Y+3NjYdOWVi4YOHXjqqScuWfzrm2/+zkkzjht+yNDGxq2AtlTq5ltu2Lq1MR6LA/UAbNuuSyU2b34fOPijjz5x3Xx9fR0bA3g+u6REHQAeatVsgFLkmczECZPmzp1z8klz58z53Pr17+Ry7qVfuej7N/yYRYDsgku+dP65C6//7sLXX984efL4ycePO2Hq3NNnTzeBOXHahGzOXTB/4devuqCxsWnq1HEnTPn8pAnTHafuqd8v//K8b1x11YXpdOu0z048Y/bJs09fEItf88zTfxo9esRdd944ZMiSS756hVZq69btX/3auZs2vbd46d1KKc/zLrt83kUXfnNr47blDz15ySXn1NUl864LEBFls3nDHLX+QdVajhYBkTDDVnWvrHvlpZdeHzp0wOzTT4nHkm9ueMMEPP6YzwD0t0+2/faRVcOGDT5t1kyt9Lbt21eufDKRiJ111qxUst+bb7+xevWaIz8zatYpJ3kmxyyAxO0+b7z11urVLxx99JjPzZxGUO9v/vCxx1aPHDnstFmnEAWuGzy84gljzNlnn1GXSKz544stLZlBg/v36ZMafdhRb7712jPPvnjcccdMnXzCjqaP169/Z8bJU3wTPP/8K1OmjKtLJIxw1Y1w5cvRVSSg4FOLsEV1QIIoCDgtZDQlIMSUFxZFtqZ6IPxKFMKPbCTNEliUJEqKeEbSUAoCIjCzRQmipIhrkBERTY6ieogfSBoECFmqD0ABN4NYUx1EAYEQG85rSipKiuQDaVXKVhI3lCGQQpKRE/ButNOTCGjLQWHWo5QKFycAKKUAMIdfIfxKRIxhAFoXSpb/MJSt4987Fiu/STjFC/17pVSxsNJaFSZxujAvK/28N/SAGspR2xHrMagREDEslBYXo/eJeyPCWR6hZM/aXViFDzigdy16MIrNumTn210ohD0g7AQ1FqqIUKMipYuOW3XMrAA0NzfncrmdP6qhGhAIQbe0pLPZLIDm5ubSRWtrKwBmbmxspBIhRMSSZqq5oVWAgEkcRYdDNCAdh/5ObEDNDlcRBADhFhw6twE1L6ibUfOCIkYlXlANEaK4HyAAASKA7FFvOBAiO/Ydex0ese/Vt4o3KtxOQ1fiBYV8GbCgx2sfxUGYiBQUFWvXZfUZwtjXvYQCASIggu8H6UyaSKPLtkAQQV1dzLatXtMDmKWltZVZiLqaERGEEYvbiYRTMqp79+gSAUJE77279b77HnEcp8vOSESeF5x7/vQjx4zwxevpBABQoGzeXfw/qzIZN9w52F1hRdms99mTxp4+63hf3CoMQSGYxXV9EVUBAfDcwJhe5TuJiOsGrutr3UWohFLkun4QmH1/aPtNeaVIKep6BCIi1QvafXuE1e9SA2GZqtS/vb2t2BHoVW2/nQa6VEKhTDV0UJsHRIwaARGjRkDEqBEQMWoERIw2bihRpd4VKVKKet8SdqiBit3QKjyxDQHGmGzWNUGHA13tpARIkZv3jeFexYEgl/OyWbeSiVg243p+sO/PbLMY179//fSTjrIsu5KZcBCY/v1TgOkd8zEBLFtPmTradYMua0REnucfdthgYF+bYGkNNlzgawW2VGIYisuBAXdPcOv+RxhAqGBXvhoqYINgl9WvPDSxzWooCxtxAV3hJI+Ietl+QCBu5b8iEKkq7QcUGAqXeLBn1rWna79UhbA97d1v9xo1NzRi1AiIGDUCIkaNgIix61OSoVPUTUkUylJwdONT9lG2oh5k9wX2HW0j46Q8oC6MEqj64SmUIgnCpyhSB4gXJRAuC/LoKBtD9iAKRAqhC0DlkXHFO5cy/2RzfjWD5gSWrWOxnTNtIsrl3fCIXcQQ2I7lOFa5bNmcy6WzxAInZtu2rrQHVB4bms/ntVa27UCkxIGG2tGUXvqz1cZw6S67lHuXD+5YgBTlst6UqWNmn358KZBCgX7z0IvvvftJLGZz1ceiPZTt5BlHzZwxoRTlQMBDD6z5cPN2x7EA5HLenL+fPPHYMb64XY0LYVIjEFE+7ypFjuPk83mlVHhBRLFYjJlzuZwFIAgCwLLttpKFcTItuZCAzmpIHVZPw9PVHQsoRdms63kBChO9wtwvm3VbmnOxeFBlY7Br2XiXBcLFNd837WTLZNzm5mwsZhOQzXp+UfgKVl8oHFKMCQAdKtmyCtoOT+wC8H3fApBKpcqGIBZASAAmQrLONkGnwV9E8H3jeW1Wr+JxS2lVigr2vcD3DRGJkAgHQQCY8jWseNxK1jnl41J1QPB947eXzValUZ3guUEQFGUD+3472Sget+rqHMexiAgklk2AEbB0TYCARMB1dXWhblOpVPhF6UIp1dDQUDqgIUSKZZtPrxJ02H+M4Uxrvrjw1B5suL5PYuVvXrnz9sdTqTizhE3s+kXnHDP+kFzWF0gqFb/r9idW/OaV+vq4iPi+GTio7x0/nd9QnwyEQ0Ocac0XtFA95Yey/XLpc0vvfba+TyJMIqcUFv3nuaPHDMnnfUCSdbFbbnz0yd+tT4WyeWbI0IbbfzK/TyphirK1tuZNYEp50hLJWMyxWKTL1VKAIQmbpkKsCg9oAIWGHwAiBABao1/feGcPMYYdnXBsymSyWhd6tzGcSlr1sWTcdkXE0UlFnM1kbRvMHATc0qJM4ANGxEARgPqUU3VPyzA7OmFpZLJZ2yFjOCQglbL7JBKJmBaBo+OAyWSz2oYw+z43N6eN8QgOYEId92krG0MYwe4sYllZogAVGuEO7BEVXagAnfonRtgBC6CVLg1qIjAQgANhETgwQqS0CgsoBUtrIiosfAmBYKT6LlBJNqW0UioM41EKhguyQQAwAK2UUkpASom2wsDknaN8R9nCrHNdOkFS8YJmJ+lqii7Ubu4S6hFh2sKyNhGKXqI5FCcc5wqDXfunVLsDFNxEKnwobniUvMfyJixlZaRDsNWuZauqvLWliIhRIyBi1AiIGDUCIkaNgIhRIyBi1AiIGDUCIkaNgIhRIyBi1AiIGDUCIkaNgIhRIyBi1AiIGDUCIsaevT9gD/bNuyxY9vqpAxCFnVt0++mTyggI30IlBIEUd+nYMLQR5lI0F8I0LsyAYWaBQBsIg2TnLp+IsBEY5m482xTKBhGicG+uIIAwC5jDODBtCn8vpUsCmJlh2JjCWYFw/1Eq2ITsXgIozKAjRJajYgQKIwyBpKWSgSgWzWEoC8ix6oC+cZ0XAZBUKm5YGWgWNgImK6bqLdRr3V0EiBRk01Y8EGVC2QQEOHaK0CeuPREBEqRjgYSyiRFh0jGr3kZKa6OIfPgsXncfANqDHqBA7ssbVq170peAiJiRiKkNb7celHTjyhcSAKzwwvLFm9c4XiAQxGLqb283HVTnxVUgBCaJ+dknlt5Wl9CGuwzu2HuEsr37asuApBdXQZhagwjPLfvvjQfZfiAAHJu2b/z0oKQbJ180mMRxc08svjUZ1ywwHEweOXnojBO8hFbd2QPavD/AYGuAtQS9SwIsUeklT9563025MOEuIALLgRUrG/EJXhZsCgKLwI5D22XZGxlerjwEsrsgAisGy2ljjbwcmAuP7kw2hCGFvlw0/YvHXL0g18/Re06AgAkJG9MIVkWH9Lq8n5Cmj5tUPkj0i5OXU6R25kRuGygWT7XxrETaRJKRQqJ+f7leXcrGXH4isiQbEWljtGPhw0+p35BCapru6QS7Dk/fNVwfnmE2bATK7MyziDatTIqxpF0W6EbNV/bozgsQG4AZOX/vHbV9CU/fNRRBk9JaaVJKFR21TkHo+pbdhAof3VkBRUohIEXQ+9BUKg9P932fCJZl774HkBHk3Fyzl3VFq96VLq4tFCRvEGQ8+GbvrZWAiHw/IIJlWb7vE1F4AcC2bRHxPM8CkMlkbNuyrM7fqkggiOmXtKYfNXP4PN/4veBs8G5AoECCAUPHyfAGAu8dBQIBVDbbYlmWZVmtra22badSqdbWVq21bdvM3NTUVJEXVBzNFEHbKMwDeisDxUEbPjyGT+C9sL975gVJycp3otfioToWMe6eHObv0SgkYqC9nQlXboTDp4XlOpWl7P+oNbM/sQ9V7hAI3G7cDjnYw/T1vXvs70509ILC69pydMSoERAx2r0/oHDWgsLTSgcSpKqHyPYfulpfaJMvSBFsCEEMFJM6kPITk0agKk+sHLW0KD81tFuz2aYHeIaaPBtk1XmZmHG7f8lyD9Acqw+U7im9QMCa7P4xUFc9oOCNGhat6HebzHm/dpFK3nf/3NlvPxTE+2muQob2fa0MkQryZ897ZPVhM+q9LJPe93t2K4jgBnJoX3p2vpNyutjRKeaMAwHI+6qpKQbGyrrps3csI9oO4Wh7AYMUZEO/0U9b49Pb0cpxOeA9NwIkQFyBC++H3J3vXtoPkIKHqgUW/jpkDBBOQCLu9EIKbBrrB7c09FMBi6rslG6kCEXUGrud3BZQIqB8mRw6CIBCmvwIe0DpBaMvDpuK0vudDvy5oIQncysq2647Fzjr+hUa+68uBOCF4SfCghLuAdrfQ3QcTwkGH/Qd0ezUa6kkK0X3whLjamdLaigMZOfiVu9BBwJIaV/WDzv2nQFHIurpT0j/+w0jXjnkePhgUv8fegAIDIVnRs6IVrIwgwuAxsTAYA8j+HoQdjEEkQg03hp8dNSyFXrA46NP56TWHEQ9HHYLOhIgTAo+nh4x89N4Q2gGohqFtBgArw45tmgAIlVV96BD1w6TeDG21g/5JDWkX74JFA0DAlKQv/QftXrUqTCAUj0lTbsiGKrUWrXpASxAADZQvsk6ia/MvkOArl+r1w0QgEkBeGLkqelEvXYDNpAe8o8NYGBMRT22+AIHAEB/B8cMRJ1NYOQc9D90mK8dx3ioIKV+dbVfSis6oM4eOxB9MzAH+gLEThDBCzCsL3QFU+E2BBwcxzkHw7EFTKwRSw379OAjBm9+g7VNwvuzHzCRZYJA2+kzLjh/MOw8SQ8iAAgYDXWwKmizbd8hI8gaGEUioMC09Kn//dzvfenWc63A3c89QAFCtOLSezeNnexkjK8UTM8wACgSEK9sHdlq91NFYeUhWsdz/Oa0L2wfunbK43c5+VZWev84IkLKdjMbJ815eda8eMZA9ZzGD6B4FKVCI7ybCY6IUk6ePzl07PLL7tjfVoBIBZLIsJAqRubsr4fvX3RGQKHOQmR5YrtRbMsQlWm/l6q/TXh6uzZWyjAISESbUNIbQsEKuu0qPH13722LrP49WvHtKtFZeLoCwGFqX+BACoPoBQj3kzg8V8rMYXLh0oWIGGMUgJaWllw+V/pJDdWBQGuk0+lcLodQycWLTCYDgJm3b99uAWhoaCikde/Zo+0BBiJjUN+nbzi4NDQ0hH8uXWitBw0a1M4I9+K4//2P8rzznRrh/wMkUwyyb7COxQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMi0wNy0wOVQxMzo1NDozOSswMDowMPbn6VcAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDctMDlUMTM6NTQ6MzkrMDA6MDCHulHrAAAAAElFTkSuQmCC
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/klayout_package/pymacros/pyxs.lym:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | pymacros
6 |
7 |
8 |
9 | true
10 | false
11 |
12 | false
13 |
14 |
15 | python
16 |
17 | # import sys
18 |
19 | # fpath = 'D:/Docs/TUe/Projects/!!python_projects/klayout_prj/klayout_pyxs_repo/'
20 | # if not fpath in sys.path:
21 | # sys.path.insert(0, fpath)
22 |
23 | # from pprint import pprint
24 | # pprint(sys.path)
25 |
26 | # import klayout_pyxs.pyxs_lib as pyxs_lib
27 |
28 | from __future__ import print_function
29 | from __future__ import absolute_import
30 | from klayout_pyxs import XSectionScriptEnvironment
31 |
32 | # import importlib
33 | # importlib.reload(klayout.pyxs_lib)
34 |
35 | pyxs_processing_environment = XSectionScriptEnvironment('pyxs')
36 |
37 | DEBUG = False
38 |
39 | if DEBUG:
40 | print('xs_run:', xs_run)
41 | print('xs_cut:', xs_cut)
42 | print('xs_out:', xs_out)
43 | print('RBA::LayoutView::current', pya.LayoutView.current())
44 |
45 | if pya.LayoutView.current() and xs_run and xs_cut and xs_out:
46 | x1, x2 = xs_cut.split(';')[0].split(',')
47 | x3, x4 = xs_cut.split(';')[1].split(',')
48 |
49 | if DEBUG:
50 | print(x1, x2, x3, x4)
51 |
52 | p1 = pya.DPoint(float(x1), float(x2))
53 | p2 = pya.DPoint(float(x3), float(x4))
54 |
55 | print("Running pyxs script {} with {} to {} ...".format(xs_run, p1, p2))
56 | target_view = pyxs_processing_environment.run_script(xs_run, p1, p2)[-1]
57 |
58 | l = target_view.active_cellview().layout()
59 |
60 | print("Writing {} ...".format(xs_out))
61 | l.write(xs_out)
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/klayout_package/pymacros/pyxs_dev.lym:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | pymacros
6 |
7 |
8 |
9 | true
10 | false
11 |
12 | false
13 |
14 |
15 | python
16 |
17 | import sys
18 |
19 | fpath = 'D:/Docs/TUe/Projects/!!python_projects/klayout_prj/klayout_pyxs_repo/'
20 | if not fpath in sys.path:
21 | sys.path.insert(0, fpath)
22 |
23 | # from pprint import pprint
24 | # pprint(sys.path)
25 |
26 | # import klayout_pyxs.pyxs_lib as pyxs_lib
27 |
28 | from klayout_pyxs import XSectionScriptEnvironment
29 |
30 | # import importlib
31 | # importlib.reload(klayout.pyxs_lib)
32 |
33 | pyxs_processing_environment_dev = XSectionScriptEnvironment('pyxs_dev')
34 |
35 | DEBUG = False
36 |
37 | if DEBUG:
38 | print('xs_run:', xs_run)
39 | print('xs_cut:', xs_cut)
40 | print('xs_out:', xs_out)
41 | print('RBA::LayoutView::current', pya.LayoutView.current())
42 |
43 | if pya.LayoutView.current() and xs_run and xs_cut and xs_out:
44 | x1, x2 = xs_cut.split(';')[0].split(',')
45 | x3, x4 = xs_cut.split(';')[1].split(',')
46 |
47 | if DEBUG:
48 | print(x1, x2, x3, x4)
49 |
50 | p1 = pya.DPoint(float(x1), float(x2))
51 | p2 = pya.DPoint(float(x3), float(x4))
52 |
53 | print("Running pyxs script {} with {} to {} ...".format(xs_run, p1, p2))
54 | target_view = pyxs_processing_environment_dev.run_script(xs_run, p1, p2)[-1]
55 |
56 | l = target_view.active_cellview().layout()
57 |
58 | print("Writing {} ...".format(xs_out))
59 | l.write(xs_out)
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/klayout_package/python/klayout_pyxs/__init__.py:
--------------------------------------------------------------------------------
1 | """klayout_pyxs.__init__.py
2 |
3 | Copyright 2017-2019 Dima Pustakhod
4 |
5 | """
6 |
7 | DEBUG = False
8 | HAS_KLAYOUT = False
9 | HAS_PYA = False
10 |
11 | try:
12 | if DEBUG:
13 | print("Trying to import klayout module... ", end="")
14 | import importlib
15 |
16 | importlib.import_module("klayout")
17 | HAS_KLAYOUT = True
18 |
19 | from klayout.db import Box, DPoint, Edge
20 | from klayout.db import EdgeProcessor as EP_
21 | from klayout.db import (
22 | Edges,
23 | LayerInfo,
24 | Point,
25 | Polygon,
26 | Region,
27 | SimplePolygon,
28 | Trans,
29 | )
30 |
31 | if DEBUG:
32 | print("found!")
33 | except:
34 | if DEBUG:
35 | print("not found!")
36 | try:
37 | if DEBUG:
38 | print("Trying to import pya module... ", end="")
39 | import pya as klayout
40 |
41 | # For plugin only
42 | from pya import Action, Application, Box, DPoint, Edge
43 | from pya import EdgeProcessor as EP_
44 | from pya import (
45 | Edges,
46 | FileDialog,
47 | LayerInfo,
48 | MessageBox,
49 | Point,
50 | Polygon,
51 | Region,
52 | SimplePolygon,
53 | Trans,
54 | )
55 |
56 | HAS_PYA = True
57 | if DEBUG:
58 | print("found!")
59 | except:
60 | if DEBUG:
61 | print("not found!")
62 | raise ModuleNotFoundError(
63 | "Neither pya nor klayout module are not "
64 | "installed in the current python distribution."
65 | )
66 |
67 |
68 | # from .misc import info
69 | # reload(pyxs.misc)
70 | # reload(pyxs.geometry_2d)
71 | # reload(pyxs.geometry_3d)
72 |
73 |
74 | def _poly_repr(self):
75 | """Return nice representation of the Polygon instance
76 |
77 | This is useful when printing a list of Polygons
78 | """
79 | return f"{self.num_points()} pts: {self.__str__()}"
80 |
81 |
82 | Polygon.__repr__ = _poly_repr
83 |
84 | # info('pyxs.__init__.py loaded')
85 |
86 | from klayout_pyxs.pyxs_lib import XSectionScriptEnvironment
87 |
88 | __version__ = "0.1.13"
89 |
90 | __all__ = [
91 | "XSectionScriptEnvironment",
92 | "__version__",
93 | ]
94 |
--------------------------------------------------------------------------------
/klayout_package/python/klayout_pyxs/compat.py:
--------------------------------------------------------------------------------
1 | """klayout_pyxs.compat.py
2 |
3 | This module imports functions necessary to ensure compatibility with
4 | both 2.7 and 3.7 Python versions.
5 |
6 | Copyright 2017-2019 Dima Pustakhod
7 |
8 | """
9 | import sys
10 |
11 | major, middle, minor, _, _ = sys.version_info
12 |
13 | if major == 2:
14 | from six.moces import zip
15 | from six.moves import range
16 | elif major == 3:
17 | range = range
18 | zip = zip
19 | else:
20 | raise OSError("Unsupported python version")
21 |
22 | __all__ = [
23 | "range",
24 | "zip",
25 | ]
26 |
--------------------------------------------------------------------------------
/klayout_package/python/klayout_pyxs/geometry_2d.py:
--------------------------------------------------------------------------------
1 | """ pyxs.geometry_2d.py
2 |
3 | (C) 2017-2019 Dima Pustakhod and contributors
4 | """
5 | import math
6 |
7 | from klayout_pyxs import (
8 | EP_,
9 | DPoint,
10 | Edges,
11 | Point,
12 | Polygon,
13 | Region,
14 | SimplePolygon,
15 | Trans,
16 | )
17 | from klayout_pyxs.compat import range
18 | from klayout_pyxs.layer_parameters import string_to_layer_info
19 | from klayout_pyxs.utils import info, int_floor, make_iterable, print_info
20 |
21 |
22 | class EdgeProcessor(EP_):
23 | """
24 | Problems: empty polygon arrays produce errors with boolean_to_polygon
25 | because RBA does not recognize the empty array as an array of polygons
26 | and then there is an ambiguity between the edge-input and polygon input
27 | variants. Thus this extension which checks for empty input and performs
28 | some default operation
29 | """
30 |
31 | def boolean_p2p(self, pa, pb, mode, rh=True, mc=True):
32 | """Boolean operation for a set of given polygons, creating polygons
33 |
34 | This method computes the result for the given boolean operation on
35 | two sets of polygons. This method produces polygons on output and
36 | allows to fine-tune the parameters for that purpose.
37 |
38 | This is a convenience method that bundles filling of the edges,
39 | processing with a Boolean operator and puts the result into an output
40 | vector.
41 |
42 | Parameters
43 | ----------
44 | pa : list of Polygon
45 | the input polygons (first operand)
46 | pb : list of Polygon
47 | the input polygons (second operand)
48 | mode : int
49 | one of self.ModeANotB, self.ModeAnd, self.ModeBNotA,
50 | self.ModeOr, self.ModeXor
51 | rh : bool (optional)
52 | True, if holes should be resolved into the hull
53 | mc : bool (optional)
54 | True, if touching corners should be resolved into less connected
55 | contours
56 |
57 | Returns
58 | -------
59 | res : list of Polygon
60 | The output polygons
61 |
62 | """
63 | return super().boolean_p2p(pa, pb, mode, rh, mc)
64 |
65 | def safe_boolean_to_polygon(self, pa, pb, mode, rh=True, mc=True):
66 | """Applies boolean operation to two lists of polygons.
67 |
68 | Use of this method is deprecated. Use boolean_p2p instead
69 |
70 | Works safe in case any of input arrays is empty.
71 |
72 | Parameters
73 | ----------
74 | pa : list of Polygon
75 | pb : list of Polygon
76 | mode : int
77 | rh : bool (optional)
78 | resolve_holes
79 | mc : bool (optional)
80 | min_coherence
81 |
82 | Returns
83 | -------
84 | list of Polygon or []
85 | """
86 | n_pa, n_pb = len(pa), len(pb) # number of polygons in pa and pb
87 |
88 | if n_pa > 0 and n_pb > 0: # both pa and pb are not empty
89 | return self.boolean_to_polygon(pa, pb, mode, rh, mc)
90 | elif mode == self.ModeAnd: # either pa and pb is empty, mode AND
91 | return [] # will be empty
92 | elif mode == self.ModeOr:
93 | return pa if n_pa > 0 else pb
94 | elif mode == self.ModeXor:
95 | return pa if n_pa > 0 else pb
96 | elif mode == self.ModeANotB:
97 | return pa
98 | elif mode == self.ModeBNotA:
99 | return pb
100 | else:
101 | return []
102 |
103 | def size_to_polygon(self, polygons, dx, dy, mode=2, rh=True, mc=True):
104 | """Size the given polygons into polygons
105 |
106 | Use of this method is deprecated. Use size_p2p instead
107 | """
108 | return super().size_to_polygon(polygons, dx, dy, mode, rh, mc)
109 |
110 | @print_info(False)
111 | def size_p2p(self, polygons, dx, dy=0, mode=2, rh=True, mc=True):
112 | """Size the given polygons into polygons
113 |
114 | Parameters
115 | ----------
116 | polygons : list of Polygon
117 | The input polygons
118 | dx : int
119 | The sizing value in x direction in dbu
120 | dy : int (optional)
121 | The sizing value in y direction in dbu
122 | mode : int (optional)
123 | The sizing mode. Allowed values from 1 to 5
124 | rh : bool (optional)
125 | True, if holes should be resolved into the hull
126 | mc : bool (optional)
127 | True, if touching corners should be resolved into less connected
128 | contours
129 |
130 | Returns
131 | -------
132 | res : list of Polygon
133 | The output polygons
134 |
135 | """
136 | info(f" polys = {polygons}")
137 | info(f" dx = {dx}, dy = {dy}")
138 | res = super().size_p2p(polygons, dx, dy, mode, rh, mc)
139 | info(f" EP.size_p2p().res = {res}")
140 | return res
141 |
142 |
143 | EP = EdgeProcessor
144 | ep = EdgeProcessor()
145 |
146 |
147 | def parse_grow_etch_args(
148 | method, material_cls, into=(), through=(), on=(), mode="square"
149 | ):
150 | """
151 | Parameters
152 | ----------
153 | method : str
154 | 'etch|grow': calling method, used for debug messages.
155 | material_cls : type
156 | into, through, and on lists must contain instances of this type.
157 | into : None or list (optional)
158 | on : None or list (optional)
159 | through : None or list (optional)
160 | mode : str (optional)
161 | 'square|round|octagon'
162 |
163 |
164 | Returns
165 | -------
166 | res : tuple
167 | into : None or list
168 | through : None or list
169 | on : None or list
170 | mode : str
171 | 'square|round|octagon'
172 |
173 | """
174 | if into:
175 | into = make_iterable(into)
176 | for i in into:
177 | # should be MaterialData @@@
178 | if not isinstance(i, material_cls):
179 | raise TypeError(
180 | f"'{method}' method: 'into' expects a material parameter or an array of such. {type(i)} is given"
181 | )
182 |
183 | if on:
184 | on = make_iterable(on)
185 | for i in on:
186 | # should be MaterialData @@@
187 | if not isinstance(i, material_cls):
188 | raise TypeError(
189 | f"'{method}' method: 'on' expects a material parameter or an array of such"
190 | )
191 |
192 | if through:
193 | through = make_iterable(through)
194 | for i in through:
195 | # should be MaterialData @@@
196 | if not isinstance(i, material_cls):
197 | raise TypeError(
198 | f"'{method}' method: 'through' expects a material parameter or an array of such"
199 | )
200 |
201 | if on and (through or into):
202 | raise ValueError(
203 | "'on' option cannot be combined with 'into' or " "'through' option"
204 | )
205 |
206 | if mode not in ("round", "square", "octagon"):
207 | raise ValueError(
208 | f"'{method}' method: 'mode' should be 'round', 'square' or 'octagon'"
209 | )
210 |
211 | return into, through, on, mode
212 |
213 |
214 | class LayoutData:
215 | """Class to manipulate masks, which is a 2d view.
216 |
217 | Layout data is a list of polygons.
218 |
219 | Attributes
220 | ----------
221 | self._polygons : list of Polygon
222 | In case of XSectionGenerator.layer() object, self._polygons
223 | contains shapes touching the ruler, top view of the mask
224 | """
225 |
226 | def __init__(self, polygons, xs):
227 | """LayoutData constructor.
228 |
229 | Parameters
230 | ----------
231 | polygons : list of Polygon
232 | list of shapes contained in this LayoutData
233 | xs : XSectionGenerator
234 |
235 | """
236 | self._polygons = polygons
237 | self._xs = xs
238 | self._ep = ep
239 |
240 | def upcast(self, polygons):
241 | return self.__class__(polygons, self._xs)
242 |
243 | def dup(self):
244 | return self.__class__(self._polygons, self._xs)
245 |
246 | def __str__(self):
247 | n_poly = self.n_poly
248 |
249 | s = f"LayoutData (n_polygons = {n_poly})"
250 |
251 | if n_poly > 0:
252 | s += ":"
253 |
254 | for pi in range(min(2, n_poly)):
255 | s += f"\n {self._polygons[pi]}"
256 | return s
257 |
258 | def __repr__(self):
259 | return f""
260 |
261 | @property
262 | def data(self):
263 | """
264 | Return
265 | ------
266 | data: list of Polygon
267 | polygons which constitute the mask
268 | """
269 | return self._polygons
270 |
271 | @data.setter
272 | def data(self, polygons):
273 | """
274 | Parameters
275 | ----------
276 | polygons: list of Polygon
277 | polygons to be saved in the mask
278 | """
279 | self._polygons = polygons
280 |
281 | def add(self, other):
282 | """Add more polygons to the layout (OR).
283 |
284 | Parameters
285 | ----------
286 | other : LayoutData or list of Polygon
287 |
288 | """
289 | other_polygons = self._get_polygons(other)
290 | self._polygons = self._ep.boolean_p2p(self._polygons, other_polygons, EP.ModeOr)
291 |
292 | def and_(self, other):
293 | """Calculate overlap of the mask with a list of polygons (AND).
294 |
295 | Parameters
296 | ----------
297 | other : LayoutData or list of Polygon
298 |
299 | Returns
300 | -------
301 | ld : LayoutData
302 | """
303 | other_polygons = self._get_polygons(other)
304 | return self.upcast(
305 | self._ep.boolean_p2p(self._polygons, other_polygons, EP.ModeAnd)
306 | )
307 |
308 | def invert(self):
309 | self._polygons = self._ep.boolean_p2p(
310 | self._polygons, [Polygon(self._xs.background())], EP.ModeXor
311 | )
312 |
313 | def inverted(self):
314 | """Calculate inversion of the mask.
315 |
316 | Total region is determined by self._xs.background().
317 |
318 | Returns
319 | -------
320 | ld : LayoutData
321 | """
322 | return self.upcast(
323 | self._ep.boolean_p2p(
324 | self._polygons, [Polygon(self._xs.background())], EP.ModeXor
325 | )
326 | )
327 |
328 | @print_info(False)
329 | def load(self, layout, cell, box, layer_spec):
330 | """Load all shapes from the layer into self._polygons.
331 |
332 | The shapes are collected from layer defined by layer_spec. Only
333 | shapes touching the box are loaded. Box is effectively a ruler region.
334 |
335 | Parameters
336 | ----------
337 | layout : Layout
338 | layout
339 | cell : int
340 | cell's index
341 | box : Box
342 | The box of the ruler, enlarged in both directions.
343 | Only shapes touching this box will be collected
344 | layer_spec : str
345 | layer to be used
346 | """
347 | info(f"LD.load(..., box={box}, layer_spec={layer_spec})")
348 |
349 | ls = string_to_layer_info(layer_spec)
350 |
351 | # look up the layer index with a given layer_spec in the current layout
352 | layer_index = None
353 | for li in layout.layer_indices():
354 | info(f" li = {li}")
355 | if layout.get_info(li).is_equivalent(ls):
356 | info(f" layer_index = {li}")
357 | layer_index = li
358 | break
359 |
360 | # collect polygons from the specified layer
361 | # all the shapes from the layout will be saved in self._polygons
362 | if layer_index is not None:
363 | info(" iterations:")
364 | shape_iter = layout.begin_shapes_touching(cell, layer_index, box)
365 |
366 | while not shape_iter.at_end():
367 | shape = shape_iter.shape()
368 | if shape.is_polygon() or shape.is_path() or shape.is_box():
369 | self._polygons.append(
370 | shape.polygon.transformed(shape_iter.itrans())
371 | )
372 | shape_iter.next()
373 |
374 | n_poly = self.n_poly
375 | info(f" loaded polygon count: {n_poly}")
376 | if n_poly > 0:
377 | info(" loaded polygons:")
378 | for pi in range(min(2, n_poly)):
379 | info(f" {self._polygons[pi]}")
380 |
381 | info("LD.load()\n")
382 |
383 | def mask(self, other):
384 | """Mask current layout with external list of polygons (AND).
385 |
386 | Parameters
387 | ----------
388 | other : LayoutData or list of Polygon
389 |
390 | """
391 | other_polygons = self._get_polygons(other)
392 | self._polygons = self._ep.boolean_p2p(
393 | self._polygons, other_polygons, EP.ModeAnd
394 | )
395 |
396 | @property
397 | def n_poly(self):
398 | """
399 | Returns
400 | -------
401 | n_poly : int
402 | number of polygons contained in the mask
403 |
404 | """
405 | return len(self._polygons)
406 |
407 | def not_(self, other):
408 | """Calculate difference with another list of polygons.
409 |
410 | Parameters
411 | ----------
412 | other : LayoutData or list of Polygon
413 |
414 | Returns
415 | -------
416 | ld : LayoutData
417 | """
418 | other_polygons = self._get_polygons(other)
419 | return self.upcast(
420 | self._ep.boolean_p2p(self._polygons, other_polygons, EP.ModeANotB)
421 | )
422 |
423 | __sub__ = not_
424 |
425 | def or_(self, other):
426 | """Calculate sum with another list of polygons (OR).
427 |
428 | Parameters
429 | ----------
430 | other : LayoutData or list of Polygon
431 |
432 | Returns
433 | -------
434 | ld : LayoutData
435 | """
436 | other_polygons = self._get_polygons(other)
437 | return self.upcast(
438 | self._ep.boolean_p2p(self._polygons, other_polygons, EP.ModeOr)
439 | )
440 |
441 | __add__ = or_
442 | __iadd__ = or_
443 |
444 | def size(self, dx, dy=None):
445 | """Resize the layout mask.
446 |
447 | Parameters
448 | ----------
449 | dx : float
450 | size change in x-direction in [um]
451 | dy : float (optional)
452 | size change in y-direction in [um]. Equals to dx by default.
453 |
454 | """
455 | dy = dx if dy is None else dy
456 | self._polygons = self._ep.size_p2p(
457 | self._polygons,
458 | int_floor(dx / self._xs.dbu + 0.5),
459 | int_floor(dy / self._xs.dbu + 0.5),
460 | )
461 |
462 | def sized(self, dx, dy=None):
463 | """Calculate a sized mask.
464 |
465 | Parameters
466 | ----------
467 | dx : float
468 | size change in x-direction in [um]
469 | dy : float (optional)
470 | size change in y-direction in [um]. Equals to dx by default.
471 |
472 | Returns
473 | -------
474 | ld : LayoutData
475 | """
476 | dy = dx if dy is None else dy
477 | return self.upcast(
478 | self._ep.size_p2p(
479 | self._polygons,
480 | int_floor(dx / self._xs.dbu + 0.5),
481 | int_floor(dy / self._xs.dbu + 0.5),
482 | )
483 | )
484 |
485 | def sub(self, other):
486 | """Subtract another list of polygons.
487 |
488 | Parameters
489 | ----------
490 | other : LayoutData or list of Polygon
491 |
492 | """
493 | other_polygons = self._get_polygons(other)
494 | self._polygons = self._ep.boolean_p2p(
495 | self._polygons, other_polygons, EP.ModeANotB
496 | )
497 |
498 | def transform(self, t):
499 | """Transform mask with a transformation.
500 |
501 | Parameters
502 | ----------
503 | t : Trans
504 | transformation to be applied
505 | """
506 | self._polygons = [p.transformed(t) for p in self._polygons]
507 |
508 | def xor(self, other):
509 | """Calculate XOR with another list of polygons.
510 |
511 | Parameters
512 | ----------
513 | other : LayoutData or list of Polygon
514 |
515 | Returns
516 | -------
517 | ld : LayoutData
518 | """
519 | other_polygons = self._get_polygons(other)
520 | return self.upcast(
521 | self._ep.boolean_p2p(self._polygons, other_polygons, EP.ModeXor)
522 | )
523 |
524 | def close_gaps(self):
525 | """Close gaps in self._polygons.
526 |
527 | Increase size of all polygons by 1 dbu in all directions.
528 | """
529 | sz = 1
530 | d = self._polygons
531 | d = self._ep.size_p2p(d, 0, sz)
532 | d = self._ep.size_p2p(d, 0, -sz)
533 | d = self._ep.size_p2p(d, sz, 0)
534 | d = self._ep.size_p2p(d, -sz, 0)
535 | self._polygons = d
536 |
537 | def remove_slivers(self):
538 | """Remove slivers in self._polygons."""
539 | sz = 1
540 | d = self._polygons
541 | d = self._ep.size_p2p(d, 0, -sz)
542 | d = self._ep.size_p2p(d, 0, sz)
543 | d = self._ep.size_p2p(d, -sz, 0)
544 | d = self._ep.size_p2p(d, sz, 0)
545 | self._polygons = d
546 |
547 | @staticmethod
548 | def _get_polygons(l):
549 | if isinstance(l, LayoutData):
550 | return l.data
551 | elif isinstance(l, (tuple, list)):
552 | return l
553 | else:
554 | raise TypeError(
555 | f"l should be either an instance of LayoutData or a list of Polygon. {type(l)} is given."
556 | )
557 |
558 |
559 | class MaskData(LayoutData):
560 | """Class to operate 2D cross-sections.
561 |
562 | Material data is a list of single
563 |
564 | """
565 |
566 | @print_info(False)
567 | def __init__(self, air_polygons, mask_polygons, xs):
568 | """
569 | Parameters
570 | ----------
571 | air_polygons : list of Polygon
572 | list of shapes constituting air in cross-section
573 | mask_polygons : list of Polygon
574 | list of shapes constituting material in cross-section
575 | xs: XSectionGenerator
576 | passed to LayoutData.__init__()
577 | delta : float
578 | the intrinsic height (required for mask data because there
579 | cannot be an infinitely small mask layer (in database units)
580 | """
581 | super().__init__([], xs) # LayoutData()
582 | self._air_polygons = air_polygons
583 | self._mask_polygons = mask_polygons
584 |
585 | info(f"air_polygons = {air_polygons}")
586 | info(f"mask_polygons = {mask_polygons}")
587 | info("Success!")
588 |
589 | def upcast(self, polygons):
590 | return MaskData(self._air_polygons, polygons, self._xs)
591 |
592 | def dup(self):
593 | return MaskData(self._air_polygons, self._mask_polygons, self._xs)
594 |
595 | def __str__(self):
596 | n_air_poly = self.n_air_poly
597 | n_mask_poly = self.n_mask_poly
598 |
599 | s = f"{self.__class__.__name__} (n_air_polygons={n_air_poly}, n_mask_polygons={n_mask_poly})"
600 |
601 | if n_mask_poly > 0:
602 | s += ":"
603 |
604 | for pi in range(min(2, n_mask_poly)):
605 | s += f"\n {self._mask_polygons[pi]}"
606 | return s
607 |
608 | @property
609 | def n_air_poly(self):
610 | """
611 | Returns
612 | -------
613 | int
614 | number of polygons describing air
615 |
616 | """
617 | return len(self._air_polygons)
618 |
619 | @property
620 | def n_mask_poly(self):
621 | """
622 | Returns
623 | -------
624 | int
625 | number of polygons describing mask
626 |
627 | """
628 | return len(self._mask_polygons)
629 |
630 | def __repr__(self):
631 | return f""
632 |
633 | @print_info(False)
634 | def grow(
635 | self,
636 | z,
637 | xy=0.0,
638 | into=(),
639 | through=(),
640 | on=(),
641 | mode="square",
642 | taper=None,
643 | bias=None,
644 | buried=None,
645 | ):
646 | """
647 | Parameters
648 | ----------
649 | z : float
650 | height
651 | xy : float
652 | lateral
653 | mode : str
654 | 'round|square|octagon'. The profile mode.
655 | taper : float
656 | The taper angle. This option specifies tapered mode and cannot
657 | be combined with :mode.
658 | bias : float
659 | Adjusts the profile by shifting it to the interior of the figure.
660 | Positive values will reduce the line width by twice the value.
661 | on : list of MaterialData (optional)
662 | A material or an array of materials onto which the material is
663 | deposited (selective grow). The default is "all". This option
664 | cannot be combined with ":into". With ":into", ":through" has the
665 | same effect than ":on".
666 | into : list of MaterialData (optional)
667 | Specifies a material or an array of materials that the new
668 | material should consume instead of growing upwards. This will
669 | make "grow" a "conversion" process like an implant step.
670 | through : list of MaterialData (optional)
671 | To be used together with ":into". Specifies a material or an array
672 | of materials to be used for selecting grow. Grow will happen
673 | starting on the interface of that material with air, pass
674 | through the "through" material (hence the name) and consume and
675 | convert the ":into" material below.
676 | buried : float
677 | Applies the conversion of material at the given depth below the
678 | mask level. This is intended to be used together with :into
679 | and allows modeling of deep implants. The value is the depth
680 | below the surface.
681 |
682 | """
683 | # parse the arguments
684 | info(f" into={into}")
685 | into, through, on, mode = parse_grow_etch_args(
686 | "grow", MaterialData, into=into, through=through, on=on, mode=mode
687 | )
688 |
689 | info(f" into={into}")
690 | # produce the geometry of the new material
691 | d = self.produce_geom(
692 | "grow", xy, z, into, through, on, taper, bias, mode, buried
693 | )
694 |
695 | # prepare the result
696 | # list of Polygon which are removed
697 | res = MaterialData(d, self._xs)
698 |
699 | # consume material
700 | if into:
701 | for i in into: # for each MaterialData
702 | i.sub(res)
703 | else:
704 | self._xs.air().sub(res) # remove air where material was added
705 | return res
706 |
707 | def etch(
708 | self,
709 | z,
710 | xy=0.0,
711 | into=(),
712 | through=(),
713 | mode="square",
714 | taper=None,
715 | bias=None,
716 | buried=None,
717 | ):
718 | """
719 |
720 | Parameters
721 | ----------
722 | z : float
723 | etch depth
724 | xy : float (optional)
725 | mask extension, lateral
726 | mode : str
727 | 'round|square|octagon'. The profile mode.
728 | taper : float
729 | The taper angle. This option specifies tapered mode and cannot
730 | be combined with mode.
731 | bias : float
732 | Adjusts the profile by shifting it to the interior of the
733 | figure. Positive values will reduce the line width by twice
734 | the value.
735 | into : list of MaterialData (optional)
736 | A material or an array of materials into which the etch is
737 | performed. This specification is mandatory.
738 | through : list of MaterialData (optional)
739 | A material or an array of materials which form the selective
740 | material of the etch. The etch will happen only where this
741 | material interfaces with air and pass through this material
742 | (hence the name).
743 | buried : float
744 | Applies the etching at the given depth below the surface. This
745 | option allows to create cavities. It specifies the vertical
746 | displacement of the etch seed and there may be more applications
747 | for this feature.
748 |
749 | """
750 | # parse the arguments
751 |
752 | into, through, on, mode = parse_grow_etch_args(
753 | "etch", MaterialData, into=into, through=through, on=(), mode=mode
754 | )
755 |
756 | if not into:
757 | raise ValueError("'etch' method: requires an 'into' specification")
758 |
759 | # prepare the result
760 | d = self.produce_geom(
761 | "etch", xy, z, into, through, on, taper, bias, mode, buried
762 | ) # list of Polygon
763 |
764 | # produce the geometry of the etched material
765 | # list of Polygon which are removed
766 | res = MaterialData(d, self._xs)
767 |
768 | # consume material and add to air
769 | for i in into: # for each MaterialData
770 | j = LayoutData(i.data, self._xs)
771 | i.sub(res)
772 | j.sub(i)
773 | self._xs.air().add(j)
774 |
775 | # Add air in place of the etched materials
776 | # self._xs.air().add(res)
777 | # self._xs.air().close_gaps()
778 |
779 | @print_info(False)
780 | def produce_geom(self, method, xy, z, into, through, on, taper, bias, mode, buried):
781 | """
782 |
783 | Parameters
784 | ----------
785 | method : str
786 | xy : float
787 | extension
788 | z : float
789 | height
790 | into : list of MaterialData
791 | through : list of MaterialData
792 | on : list of MaterialData
793 | taper : float
794 | bias : float
795 | mode : str
796 | 'round|square|octagon'
797 | buried :
798 |
799 | Returns
800 | -------
801 | d : list of Polygon
802 | """
803 | info(f" method={method}, xy={xy}, z={z},")
804 | info(f" into={into}, through={through}, on={on},")
805 | info(
806 | " taper={}, bias={}, mode={}, buried={})".format(
807 | taper, bias, mode, buried
808 | )
809 | )
810 |
811 | prebias = bias or 0.0
812 |
813 | if xy < 0.0: # if size to be reduced,
814 | xy = -xy #
815 | prebias += xy # positive prebias
816 |
817 | if taper:
818 | d = z * math.tan(math.pi / 180.0 * taper)
819 | prebias += d - xy
820 | xy = d
821 |
822 | # determine the "into" material by joining the data of all "into" specs
823 | # or taking "air" if required.
824 | # into_data is a list of polygons from all `into` MaterialData
825 | # Finally we get a into_data, which is a list of Polygons
826 | if into:
827 | into_data = []
828 | for i in into:
829 | if len(into_data) == 0:
830 | into_data = i.data
831 | else:
832 | into_data = self._ep.boolean_p2p(i.data, into_data, EP.ModeOr)
833 | else:
834 | # when deposit or grow is selected, into_data is self.air()
835 | into_data = self._xs.air().data
836 |
837 | info(f" into_data = {into_data}")
838 |
839 | # determine the "through" material by joining the data of all
840 | # "through" specs
841 | # through_data is a list of polygons from all `through` MaterialData
842 | # Finally we get a through_data, which is a list of Polygons
843 | if through:
844 | through_data = []
845 | for i in through:
846 | if len(through_data) == 0:
847 | through_data = i.data
848 | else:
849 | through_data = self._ep.boolean_p2p(i.data, through_data, EP.ModeOr)
850 | info(f" through_data = {through_data}")
851 |
852 | # determine the "on" material by joining the data of all "on" specs
853 | # on_data is a list of polygons from all `on` MaterialData
854 | # Finally we get an on_data, which is a list of Polygons
855 | if on:
856 | on_data = []
857 | for i in on:
858 | if len(on_data) == 0:
859 | on_data = i.data
860 | else:
861 | on_data = self._ep.boolean_p2p(i.data, on_data, EP.ModeOr)
862 | info(f" on_data = {on_data}")
863 |
864 | pi = int_floor(prebias / self._xs.dbu + 0.5)
865 | xyi = int_floor(xy / self._xs.dbu + 0.5)
866 | zi = int_floor(z / self._xs.dbu + 0.5)
867 |
868 | # calculate all edges without prebias and check if prebias
869 | # would remove edges if so reduce it
870 | mp = self._ep.size_p2p(self._mask_polygons, 0, 0, 2)
871 |
872 | for p in mp:
873 | box = p.bbox()
874 | if box.width() <= 2 * pi:
875 | pi = int_floor(box.width() / 2.0) - 1
876 | xyi = pi
877 |
878 | mp = self._ep.size_p2p(self._mask_polygons, -pi, 0, 2)
879 | air_masked = self._ep.boolean_p2p(self._air_polygons, mp, EP.ModeAnd)
880 | me = (Edges(air_masked) if air_masked else Edges()) - (
881 | Edges(mp) if mp else Edges()
882 | )
883 | info(f"me after creation: {me}")
884 |
885 | # in the "into" case determine the interface region between
886 | # self and into
887 | if into or through or on:
888 | if on:
889 | data = on_data
890 | elif through:
891 | data = through_data
892 | else:
893 | data = into_data
894 |
895 | info(f"data = {data}")
896 | me = (me & Edges(data)) if data else list()
897 |
898 | # if len(data) == 0:
899 | # me = []
900 | # else:
901 | # me += Edges(data)
902 | info(f"type(me): {type(me)}") # list of Edge
903 | info(f"me before operation: {me}")
904 |
905 | d = Region()
906 |
907 | if taper and xyi > 0:
908 | info(" case taper and xyi > 0")
909 | kernel_pts = list()
910 | kernel_pts.append(Point(-xyi, 0))
911 | kernel_pts.append(Point(0, zi))
912 | kernel_pts.append(Point(xyi, 0))
913 | kernel_pts.append(Point(0, -zi))
914 | kp = Polygon(kernel_pts)
915 | for e in me:
916 | d.insert(kp.minkowsky_sum(e, False))
917 |
918 | elif xyi <= 0:
919 | info(" case xyi <= 0")
920 | # TODO: there is no way to do that with a Minkowsky sum currently
921 | # since polygons cannot be lines except through dirty tricks
922 | dz = Point(0, zi)
923 | for e in me:
924 | d.insert(Polygon([e.p1 - dz, e.p2 - dz, e.p2 + dz, e.p1 + dz]))
925 | elif mode in ("round", "octagon"):
926 | info(" case round / octagon")
927 | # approximate round corners by 64 points for "round" and
928 | # 8 for "octagon"
929 | n = 64 if mode == "round" else 8
930 | da = 2.0 * math.pi / n
931 | rf = 1.0 / math.cos(da * 0.5)
932 |
933 | info(f" n = {n}, da = {da}, rf = {rf}")
934 | kernel_pts = list()
935 | for i in range(n):
936 | kernel_pts.append(
937 | Point.from_dpoint(
938 | DPoint(
939 | xyi * rf * math.cos(da * (i + 0.5)),
940 | zi * rf * math.sin(da * (i + 0.5)),
941 | )
942 | )
943 | )
944 | info(f" n kernel_pts: {len(kernel_pts)}")
945 | info(f" kernel_pts: {kernel_pts}")
946 |
947 | kp = Polygon(kernel_pts)
948 | for n, e in enumerate(me):
949 | d.insert(kp.minkowsky_sum(e, False))
950 | if n > 0 and n % 10 == 0:
951 | d.merge()
952 |
953 | elif mode == "square":
954 | kernel_pts = list()
955 | kernel_pts.append(Point(-xyi, -zi))
956 | kernel_pts.append(Point(-xyi, zi))
957 | kernel_pts.append(Point(xyi, zi))
958 | kernel_pts.append(Point(xyi, -zi))
959 | kp = SimplePolygon()
960 | kp.set_points(kernel_pts, True) # "raw" - don't optimize away
961 | for e in me:
962 | d.insert(kp.minkowsky_sum(e, False))
963 |
964 | d.merge()
965 | info(f"d after merge: {d}")
966 |
967 | if abs(buried or 0.0) > 1e-6:
968 | t = Trans(Point(0, -int_floor(buried / self._xs.dbu + 0.5)))
969 | d.transform(t)
970 | if through:
971 | d -= Region(through_data)
972 | d &= Region(into_data)
973 |
974 | poly = [p for p in d]
975 | return poly
976 |
977 |
978 | class MaterialData(LayoutData):
979 | def __init__(self, polygons, xs):
980 | super().__init__(polygons, xs)
981 |
982 | def discard(self):
983 | self._xs.air().add(self)
984 |
985 | def keep(self):
986 | self._xs.air().sub(self)
987 |
988 | def __repr__(self):
989 | n_poly = self.n_poly
990 |
991 | s = f"{self.__class__.__name__} (n_polygons = {n_poly})"
992 |
993 | if n_poly > 0:
994 | s += ":"
995 |
996 | for pi in range(min(2, n_poly)):
997 | s += f"\n {self._polygons[pi]}"
998 | return s
999 |
1000 | def __str__(self):
1001 | s = f"<{self.__class__.__name__} (n_polygons = {self.n_poly})>"
1002 | return s
1003 |
--------------------------------------------------------------------------------
/klayout_package/python/klayout_pyxs/geometry_3d.py:
--------------------------------------------------------------------------------
1 | """ pyxs.geometry_2d.py
2 |
3 | (C) 2017 Dima Pustakhod and contributors
4 | """
5 | from random import random
6 |
7 | from klayout_pyxs.compat import range, zip
8 | from klayout_pyxs.geometry_2d import EdgeProcessor, LayoutData
9 | from klayout_pyxs.utils import info, print_info
10 |
11 |
12 | class LayerProcessor(EdgeProcessor):
13 | """Class implementing operations on MaterialLayer lists"""
14 |
15 | def normalize(self, layers):
16 | """
17 | Parameters
18 | ----------
19 | layers : list of MaterialLayer or empty list
20 | a list of non-sorted and / or overlapping layers
21 |
22 | Returns
23 | -------
24 | res : list of MaterialLayer
25 | a sorted list of non-overlapping layers
26 | """
27 | layers.sort()
28 | res = self.split_overlapping_z(layers)
29 | res = self.merge_layers_same_mask(res)
30 |
31 | return res
32 |
33 | @print_info(False)
34 | def split_overlapping_z(self, layers):
35 | """
36 | Parameters
37 | ----------
38 | layers : list of MaterialLayer
39 | a list of non-sorted and / or overlapping layers
40 |
41 | Returns
42 | -------
43 | res : list of MaterialLayer
44 | a sorted list of non-overlapping layers
45 | """
46 | info(f" layers = {layers}")
47 | _check_layer_list_sorted(layers)
48 |
49 | res = []
50 |
51 | while layers:
52 | la = layers.pop(0) # first element is the lowest in z
53 | info(f" la = {la}")
54 | if not layers: # la was the only element
55 | res += [la]
56 | continue
57 |
58 | lb = layers.pop(0) # take next element
59 | info(f" lb = {lb}")
60 | if la.is_lower(lb, levela="top", levelb="bottom"):
61 | # a is lower or touching
62 | # layers is sorted, la will not overlap with other lb
63 | res += [la]
64 | layers.insert(0, lb)
65 | info(" la top < lb btm, la is moved to result, lb is returned")
66 | elif la.bottom == lb.bottom:
67 | info(" la btm == lb btm")
68 | if la.top < lb.top:
69 | lb_split = lb.split_by_layer(la)
70 | o = MaterialLayer(
71 | la.mask.or_(lb_split[0].mask), la.bottom, la.thickness
72 | )
73 | layers = [o] + layers
74 |
75 | # top part of b is inserted to layers, ensuring sorted order
76 | i = 0
77 | while i < len(layers):
78 | if lb_split[1].bottom < layers[i].bottom:
79 | layers.insert(i, lb_split[1])
80 | break
81 | elif lb_split[1].bottom == layers[i].bottom:
82 | if lb_split[1].top <= layers[i].top:
83 | layers.insert(i, lb_split[1])
84 | break
85 | i += 1
86 | else:
87 | layers.append(lb_split[1])
88 | info(" la top < lb top, o calculated, added to layers")
89 | else:
90 | # la is the same height as lb
91 | # perform OR operation on the LayoutData
92 | o = MaterialLayer(la.mask.or_(lb.mask), la.bottom, la.thickness)
93 | layers.insert(0, o)
94 | info(" la top == lb top, o calculated, added to layers")
95 | else:
96 | info(
97 | " la btm < lb btm, lower part of la is result, rest added to layers"
98 | )
99 | # lb bottom splits a somewhere (maybe lb top too)
100 | la_split = la.split_by_layer(lb)
101 |
102 | # bottom sublayer is not overlapping with lb and others
103 | res.append(la_split[0])
104 | layers = la_split[1:] + [lb] + layers
105 |
106 | info(f" res = {res}")
107 | return res
108 |
109 | @print_info(False)
110 | def boolean_l2l(self, la, lb, mode, rh=True, mc=True):
111 | """
112 | Parameters
113 | ----------
114 | la : list of MaterialLayer or empty list
115 | sorted list. layers must not overlap with each other
116 | lb : list of MaterialLayer or empty list
117 | sorted list. layers must not overlap with each other
118 | mode: int
119 | rh : bool (optional)
120 | resolve_holes
121 | mc : bool (optional)
122 | min_coherence
123 |
124 | Returns
125 | -------
126 | list of MaterialLayer or []
127 | """
128 | n_la, n_lb = len(la), len(lb) # number of polygons in pa and pb
129 |
130 | info(f" n_la = {n_la}, n_lb = {n_lb}, mode = {mode}")
131 |
132 | ia, ib = 0, 0
133 | a = la[ia] if la else None
134 | b = lb[ib] if lb else None
135 | la_res, lb_res, oa, ob = [], [], [], []
136 | while a and b:
137 | info(f" a = {a}")
138 | info(f" b = {b}")
139 | if a.is_lower_s(b, "bottom"):
140 | info(" a bottom is lower")
141 | top = min(a.top, b.bottom)
142 | info(f" top = {top}")
143 | if top == a.top: # no overlap
144 | info(" a top is lower than b bottom, no overlap")
145 | la_res += [a]
146 | ia += 1
147 | a = None if ia >= len(la) else la[ia]
148 | else:
149 | info(" a top is higher than b bottom, overlap")
150 | # use part of a from a.bottom to top
151 | la_res += [MaterialLayer(a.mask, a.bottom, top - a.bottom)]
152 | # overlapping candidate a is a from top to a.top
153 | a = MaterialLayer(a.mask, top, a.top - top)
154 | continue
155 | elif b.is_lower_s(a, "bottom"):
156 | info(" b is lower")
157 | top = min(b.top, a.bottom)
158 | if top == b.top: # no overlap
159 | lb_res += [b]
160 | ib += 1
161 | b = None if ib >= len(lb) else lb[ib]
162 | else:
163 | # use part of b from b.bottom to top
164 | lb_res += [MaterialLayer(b.mask, b.bottom, top - b.bottom)]
165 | # overlapping candidate b is b from top to b.top
166 | b = MaterialLayer(b.mask, top, b.top - top)
167 | continue
168 | else:
169 | assert a.bottom == b.bottom, "bottoms must be equal here"
170 | info(" same bottom")
171 | if a.is_lower_s(b, "top") or b.is_lower_s(a, "top"):
172 | top = min(a.top, b.top)
173 | if top < b.top: # a is in the overlap, b is higher
174 | info(" b is higher")
175 | oa += [a]
176 | ob += [MaterialLayer(b.mask, b.bottom, top - b.bottom)]
177 | b = MaterialLayer(b.mask, top, b.top - top) # remaining top
178 | ia += 1
179 | a = None if ia >= len(la) else la[ia]
180 | continue
181 | elif top < a.top: # b is in the overlap, a is higher
182 | info(" a is higher")
183 | ob += [b]
184 | oa += [MaterialLayer(a.mask, a.bottom, top - a.bottom)]
185 | a = MaterialLayer(a.mask, top, a.top - top) # remaining top
186 | ib += 1
187 | b = None if ib >= len(lb) else lb[ib]
188 | continue
189 | else:
190 | assert a.top == b.top, "tops must be equal here"
191 | info(" same top")
192 | oa += [a]
193 | ob += [b]
194 | ia += 1
195 | a = None if ia >= len(la) else la[ia]
196 | ib += 1
197 | b = None if ib >= len(lb) else lb[ib]
198 | continue
199 |
200 | if a:
201 | la_res += [a]
202 | if b:
203 | lb_res += [b]
204 |
205 | # add remaining a's and b's
206 | while ia < len(la) - 1:
207 | ia += 1
208 | la_res += [la[ia]]
209 |
210 | while ib < len(lb) - 1:
211 | ib += 1
212 | lb_res += [lb[ib]]
213 |
214 | lo_res = []
215 | for a, b in zip(oa, ob):
216 | if o_polygons := self.boolean_p2p(a.mask.data, b.mask.data, mode, rh, mc):
217 | lo_res += [
218 | MaterialLayer(
219 | LayoutData(o_polygons, a.mask._xs), a.bottom, a.top - a.bottom
220 | )
221 | ]
222 |
223 | info(f" la_res = {la_res}")
224 | info(f" lb_res = {lb_res}")
225 | info(f" lo_res = {lo_res}")
226 |
227 | if mode == self.ModeAnd: # either la and lb is empty, mode AND
228 | info(" mode AND")
229 | res = lo_res # will be empty
230 | elif mode in [self.ModeOr, self.ModeXor]:
231 | info(" mode OR/XOR")
232 | res = la_res + lo_res + lb_res
233 | elif mode == self.ModeANotB:
234 | info(" mode ANotB")
235 | res = la_res + lo_res
236 | elif mode == self.ModeBNotA:
237 | info(" mode BNotA")
238 | res = lo_res + lb_res
239 | else:
240 | res = []
241 |
242 | res = self.normalize(res)
243 | # res = self.merge_layers_same_z(res)
244 | # res = self.merge_layers_same_mask(res)
245 | # res.sort()
246 | info(f" boolean_l2l().res = {res}")
247 | return res
248 |
249 | def split_layers_z(self, a, b):
250 | """Split two layers if they overlap in z-direction
251 |
252 | Parameters
253 | ----------
254 | a : MaterialLayer
255 | b : MaterialLayer
256 |
257 | Return
258 | ------
259 | res : tuple of MaterialLayer
260 | ab, ao, at, bb, bo, bt. bottom, overlapping and top part of each
261 | initial layer.
262 |
263 | """
264 | if not a.is_z_overlapping(b):
265 | if a.is_lower(b):
266 | return a, None, None, None, None, b
267 | else:
268 | return None, None, a, b, None, None
269 |
270 | overlap_bottom, overlap_top = a.z_overlap(b)
271 | if a.bottom < overlap_bottom:
272 | ab_bottom, ab_top = a.bottom, overlap_bottom
273 | else:
274 | ab_bottom, ab_top = None, None
275 |
276 | if a.top > overlap_top:
277 | at_bottom, at_top = overlap_top, a.top
278 | else:
279 | at_bottom, at_top = None, None
280 |
281 | if b.bottom < overlap_bottom:
282 | bb_bottom, bb_top = b.bottom, overlap_bottom
283 | else:
284 | bb_bottom, bb_top = None, None
285 |
286 | if b.top > overlap_top:
287 | bt_bottom, bt_top = overlap_top, b.top
288 | else:
289 | bt_bottom, bt_top = None, None
290 |
291 | ab = MaterialLayer(a.mask, ab_bottom, ab_top - ab_bottom) if ab_bottom else None
292 | at = MaterialLayer(a.mask, at_bottom, at_top - at_bottom) if at_bottom else None
293 | ao = MaterialLayer(a.mask, overlap_bottom, overlap_top - overlap_bottom)
294 |
295 | bb = MaterialLayer(b.mask, bb_bottom, bb_top - bb_bottom) if bb_bottom else None
296 | bt = MaterialLayer(b.mask, bt_bottom, bt_top - bt_bottom) if bt_bottom else None
297 | bo = MaterialLayer(b.mask, overlap_bottom, overlap_top - overlap_bottom)
298 |
299 | return ab, ao, at, bb, bo, bt
300 |
301 | @print_info(False)
302 | def size_l2l(self, layers, dx, dy=0, dz=0, mode=2, rh=True, mc=True):
303 | """Change mask size in each layer by dx and dy.
304 |
305 | Size in z-direction remains unchanged.
306 |
307 | Parameters
308 | ----------
309 | layers : list of MaterialLayer
310 | dx : int
311 | size increase in x-direction in [dbu]
312 | dy : int (optional)
313 | size increase in y-direction in [dbu]
314 | dz : int (optional)
315 | size increase in z-direction in [dbu]
316 | mode : int
317 | rh : boolean (optional)
318 | mc : boolean (optional)
319 |
320 | Returns
321 | -------
322 | res : layers : list of MaterialLayer
323 | """
324 | res = []
325 | for l in layers:
326 | sized_polys = self.size_p2p(l.mask.data, dx, dy, mode, rh, mc)
327 | res.append(
328 | MaterialLayer(
329 | LayoutData(sized_polys, l.mask._xs),
330 | l.bottom - dz,
331 | l.thickness + 2 * dz,
332 | )
333 | )
334 |
335 | # Join overlapping layers
336 | info(f" res before normalize = {res}")
337 | res = self.normalize(res)
338 | info(f" res after normalize = {res}")
339 |
340 | return res
341 |
342 | @print_info(False)
343 | def merge_layers_same_z(self, layers):
344 | """
345 | Parameters
346 | ----------
347 | layers : list of MaterialLayer
348 |
349 | Returns
350 | -------
351 | res : list of MaterialLayer
352 | """
353 | n_layers = len(layers)
354 | res_merged = []
355 |
356 | for i, li in enumerate(layers):
357 | merged = MaterialLayer(li.mask, li.bottom, li.thickness)
358 | for j in range(i + 1, n_layers):
359 | lj = layers[j]
360 | if merged.is_z_same(lj):
361 |
362 | # perform OR operation on the LayoutData
363 | merged = MaterialLayer(
364 | merged.mask.or_(lj.mask), merged.bottom, merged.thickness
365 | )
366 | info(f" Merged layers b = {merged.bottom}, t = {merged.top}")
367 | res_merged.append(merged)
368 | return res_merged
369 |
370 | @print_info(False)
371 | def merge_layers_same_mask(self, layers):
372 | """
373 | Parameters
374 | ----------
375 | layers : list of MaterialLayer
376 |
377 | Returns
378 | -------
379 | res : list of MaterialLayer
380 | """
381 | _check_layer_list_sorted(layers)
382 |
383 | res_merged = []
384 |
385 | while layers:
386 | la = layers.pop(0)
387 | ib = 0
388 | while layers and (ib < len(layers)):
389 | lb = layers[ib]
390 | if la.top == lb.bottom:
391 | if la.mask.data == lb.mask.data:
392 | la = MaterialLayer(la.mask, la.bottom, lb.top - la.bottom)
393 | info(
394 | f" Merged layers ({la.bottom},{la.top}) and ({lb.bottom}, {lb.top})"
395 | )
396 | layers.pop(ib)
397 | elif la.top < lb.bottom:
398 | # all following lb will be higher
399 | res_merged.append(la)
400 | break # go to the next la
401 | ib += 1
402 | else:
403 | res_merged.append(la)
404 | return res_merged
405 |
406 |
407 | class MaterialLayer:
408 | def __init__(self, mask, elevation, thickness):
409 | """
410 | Parameters
411 | ----------
412 | mask : LayoutData
413 | elevation : int
414 | z-coordinate of the layer bottom in [dbu]
415 | thickness : float
416 | thickness of the layer in [dbu]
417 | """
418 | self.mask = mask
419 | self._bottom = elevation
420 | self._top = self._bottom
421 | self.thickness = thickness
422 |
423 | def __lt__(self, other):
424 | """
425 | Parameters
426 | ----------
427 | other : MaterialLayer
428 | """
429 | if self._bottom < other.bottom:
430 | return True
431 | elif self._bottom > other.bottom:
432 | return False
433 | else:
434 | return self._top < other.top
435 |
436 | def __str__(self):
437 | n_edges = "".join(f"{poly.num_points()}, " for poly in self.mask.data)
438 | return f""
439 |
440 | def __repr__(self):
441 | n_edges = "".join(f"{poly.num_points()}, " for poly in self.mask.data)
442 | return f""
443 |
444 | @property
445 | def bottom(self):
446 | return self._bottom
447 |
448 | @property
449 | def top(self):
450 | return self._top
451 |
452 | @property
453 | def thickness(self):
454 | return self._top - self._bottom
455 |
456 | @thickness.setter
457 | def thickness(self, t):
458 | """
459 | Parameters
460 | ----------
461 | t : int
462 | thickness in [dbu]
463 | """
464 | if t <= 0:
465 | raise ValueError(
466 | f"Material layer thickness must be positive. {t} is given."
467 | )
468 | else:
469 | self._top = self._bottom + t
470 |
471 | @print_info(False)
472 | def is_z_overlapping(self, other):
473 | """Check two layers for overlap.
474 |
475 | Parameters
476 | ----------
477 | other : MaterialLayer
478 |
479 | Returns
480 | -------
481 | bool
482 | """
483 | info(
484 | f" self.b, self.t, other.b, other.t = {self.bottom} {self.top} {other.bottom} {other.top}"
485 | )
486 |
487 | return self._top > other.bottom and self._bottom < other.top
488 |
489 | def is_z_same(self, other):
490 | return self._bottom == other.bottom and self._top == other.top
491 |
492 | def split(self, z_coords):
493 | """Split layer into several layers.
494 |
495 | Parameters
496 | ----------
497 | z_coords : list of int
498 |
499 | Returns
500 | -------
501 | res : list of MaterialLayer
502 | """
503 | z_coords_all = [self.bottom] + z_coords + [self.top]
504 | return [
505 | MaterialLayer(self.mask, b, t - b)
506 | for b, t in zip(z_coords_all[:-1], z_coords_all[1:])
507 | ]
508 |
509 | def split_by_layer(self, other):
510 |
511 | z_split = []
512 | if self.bottom < other.bottom < self.top:
513 | z_split.append(other.bottom)
514 |
515 | if self.bottom < other.top < self.top:
516 | z_split.append(other.top)
517 |
518 | return self.split(z_split)
519 |
520 | def z_overlap(self, other):
521 | """Return overlap points with the other layer.
522 |
523 | self and other must be overlapping.
524 |
525 | Parameters
526 | ----------
527 | other : MaterialLayer
528 |
529 | Return
530 | ------
531 | res : tuple
532 | bottom : int or None
533 | top : int or None
534 | """
535 | bottom = max(self._bottom, other.bottom)
536 | top = min(self._top, other.top)
537 | return bottom, top
538 |
539 | def is_lower_s(self, other, levela="bottom", levelb=None):
540 | """Compares the location of two layers strictly.
541 |
542 | Parameters
543 | ----------
544 | other : MaterialLayer
545 | levela : str
546 | 'bottom|top'
547 | levelb : str or None
548 | 'bottom|top'. If None, levela will be used
549 |
550 | Returns
551 | -------
552 | bool
553 | """
554 | if not levelb:
555 | levelb = levela
556 |
557 | return getattr(self, levela) < getattr(other, levelb)
558 |
559 | def is_lower(self, other, levela="bottom", levelb=None):
560 | """Compares the location of two layers nonstrictly.
561 |
562 | Parameters
563 | ----------
564 | other : MaterialLayer
565 | levela : str
566 | 'bottom|top'
567 | levelb : str or None
568 | 'bottom|top'. If None, levela will be used
569 |
570 | Returns
571 | -------
572 | bool
573 | """
574 | if not levelb:
575 | levelb = levela
576 |
577 | return getattr(self, levela) <= getattr(other, levelb)
578 |
579 | def is_higher(self, other, levela="bottom", levelb=None):
580 | """Compares the tops of two layers strictly.
581 |
582 | Parameters
583 | ----------
584 | other : MaterialLayer
585 |
586 | Returns
587 | -------
588 | bool
589 | """
590 | if not levelb:
591 | levelb = levela
592 |
593 | return getattr(self, levela) > getattr(other, levelb)
594 |
595 |
596 | def _check_layer_list_sorted(layers):
597 | for la, lb in zip(layers[:-1], layers[1:]):
598 | if (la.bottom > lb.bottom) or ((la.bottom == lb.bottom) and (la.top > lb.top)):
599 | raise ValueError(
600 | f"layers must be a sorted list of MaterialLayer. Layers {la} and {lb} are not sorted."
601 | )
602 |
603 |
604 | @print_info(False)
605 | def layer_to_tech_str(
606 | layer_no_gds,
607 | layer,
608 | name="",
609 | color=None,
610 | filter=0.0,
611 | metal=0,
612 | shortcut="",
613 | show=True,
614 | ):
615 | """
616 | Parameters
617 | ----------
618 | layer_no_gds : int
619 | layer number in the gds file
620 | layer : MaterialLayer
621 | layer to be exported
622 | name : str
623 | name to be displayed in the legend. If empty, layer number is used.
624 | color : tuple of float
625 | r, g, b components of the color in the range [0, 1] each
626 | filter : float
627 | layer transparency, from 0 to 1
628 | metal : float
629 | Not used at the moment
630 | shortcut : str
631 | A digit from 0 to 9. Defines a shortcut from 0 to 9 to toggle the layer
632 | visibility. Can be prepended with any combination of , ,
633 | and as modifiers (eg. ' 0')
634 | show : bool
635 | Whether to show layer during rendering
636 |
637 | Returns
638 | -------
639 | s : str
640 | A layer record for the tech file of GDS3D software.
641 |
642 | """
643 | name = f"{name} ({layer_no_gds})" if name else f"-- ({layer_no_gds})"
644 | if (color is None) or (len(color) not in (3, 4)):
645 | r, g, b = random(), random(), random()
646 | a = filter
647 | elif len(color) == 3:
648 | r, g, b = color
649 | a = filter
650 | elif len(color) == 4:
651 | r, g, b, a = color
652 |
653 | if not (0 <= r <= 1 and 0 <= g <= 1 and 0 <= b <= 1):
654 | raise ValueError(
655 | f"Color components must be from 0 to 1. ({r}, {g}, {b}) is given"
656 | )
657 |
658 | if not (0 <= a <= 1):
659 | raise ValueError(
660 | f"Filter / transparency value must be from 0 to 1. {a} is given"
661 | )
662 |
663 | s = f"LayerStart: {name}\n"
664 | s += f"Layer: {layer_no_gds}\n"
665 | s += f"Height: {layer.bottom}\n"
666 | s += f"Thickness: {layer.thickness}\n"
667 |
668 | s += f"Red: {r}\nGreen: {g}\nBlue: {b}\nFilter: {a}\n"
669 | s += f"Metal: {metal}\n"
670 | s += f"Shortkey: {shortcut}\n" if shortcut else ""
671 | s += f"Show: {int(show)}\n"
672 | s += "LayerEnd\n\n"
673 | return s
674 |
675 |
676 | LP = LayerProcessor
677 | lp = LayerProcessor()
678 |
--------------------------------------------------------------------------------
/klayout_package/python/klayout_pyxs/layer_parameters.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Copyright 2017-2019 Dima Pustakhod
4 |
5 |
6 | Changelog
7 | ---------
8 | 2019.10.01
9 | Fix imports from pya/klayout
10 | Add doctests
11 | 2017.xx.xx
12 | Initial commit
13 | """
14 | import re
15 |
16 | from klayout_pyxs import LayerInfo
17 |
18 |
19 | def string_to_layer_info_params(layer_spec, return_None=False):
20 | """Convert the layer specification into a LayerInfo parameters
21 |
22 | Parameters
23 | ----------
24 | layer_spec : str
25 | format: "l", "l/d", "n(l/d)" or "n".
26 |
27 | Returns
28 | -------
29 | res : tuple
30 | layer (int), data type (int), name (str)
31 |
32 | Examples
33 | --------
34 | >>> print(string_to_layer_info_params('1'))
35 | (1, 0)
36 | >>> print(string_to_layer_info_params('1/2'))
37 | (1, 2)
38 | >>> print(string_to_layer_info_params('a(1/2)'))
39 | (1, 2, 'a')
40 | >>> print(string_to_layer_info_params('a'))
41 | ('a',)
42 | """
43 | if re.match(r"^(\d+)$", layer_spec):
44 | match = re.match(r"^(\d+)$", layer_spec)
45 | ls = int(match[0]), 0
46 | elif re.match(r"^(\d+)/(\d+)$", layer_spec):
47 | match = re.match(r"^(\d+)/(\d+)$", layer_spec)
48 | ls = int(match[1]), int(match[2])
49 | elif re.match(r"^(.*)\s*\((\d+)/(\d+)\)$", layer_spec):
50 | match = re.match(r"^(.*)\s*\((\d+)/(\d+)\)$", layer_spec)
51 | ls = int(match[2]), int(match[3]), match[1]
52 | else:
53 | ls = (layer_spec,)
54 |
55 | if return_None:
56 | if len(ls) == 1:
57 | ls = (None, None, ls[0])
58 | elif len(ls) == 2:
59 | ls = (ls[0], ls[1], None)
60 |
61 | return ls
62 |
63 |
64 | def string_to_layer_info(layer_spec):
65 | """Convert the layer specification into a LayerInfo structure
66 |
67 | Parameters
68 | ----------
69 | layer_spec : str
70 | format: "l", "l/d", "n(l/d)" or "n".
71 |
72 | Returns
73 | -------
74 | ls : LayerInfo
75 | layer parameters are given by the `layer_spec`.
76 |
77 | Examples
78 | --------
79 | >>> string_to_layer_info('1')
80 | 1/0
81 | >>> string_to_layer_info('1/2')
82 | 1/2
83 | >>> string_to_layer_info('a(1/2)')
84 | a (1/2)
85 | >>> string_to_layer_info('a')
86 | a
87 | """
88 | ls_param = string_to_layer_info_params(layer_spec)
89 | return LayerInfo(*ls_param)
90 |
91 |
92 | def main():
93 | import doctest
94 |
95 | doctest.testmod()
96 |
97 |
98 | if __name__ == "__main__":
99 | main()
100 |
--------------------------------------------------------------------------------
/klayout_package/python/klayout_pyxs/utils.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 | VERBOSE = True
4 | OFFSET = 0
5 |
6 |
7 | def info(*s):
8 | """Print information with offset.
9 |
10 | Parameters
11 | ----------
12 | s : str
13 | string to be printed
14 | """
15 | if VERBOSE:
16 | print(" " * OFFSET, *s)
17 |
18 |
19 | def print_info(v=True):
20 | """Decorator to show / disable function output to the console.
21 |
22 | Parameters
23 | ----------
24 | v : bool
25 | it False, all info() inside the function will be disabled.
26 |
27 | """
28 |
29 | def decorator(f):
30 | def wrapper(*args, **kwargs):
31 | global VERBOSE
32 | global OFFSET
33 | old_v = VERBOSE
34 | VERBOSE = v
35 | if v:
36 | OFFSET += 4
37 | info(f"{f.__name__}():")
38 | res = f(*args, **kwargs)
39 | info(f"end of {f.__name__}()\n")
40 | if v:
41 | OFFSET -= 4
42 | VERBOSE = old_v
43 | return res
44 |
45 | return wrapper
46 |
47 | return decorator
48 |
49 |
50 | def int_floor(x):
51 | """Floor a float value and return int
52 |
53 | Returns
54 | -------
55 | res : int
56 | int(math.floor(x))
57 |
58 | Examples
59 | --------
60 | >>> int_floor(1.5)
61 | 1
62 | >>> int_floor(1.2)
63 | 1
64 | >>> int_floor(1.8)
65 | 1
66 | >>> int_floor(-1.2)
67 | -2
68 | >>> int_floor(-1.5)
69 | -2
70 | >>> int_floor(-1.8)
71 | -2
72 | """
73 | return int(math.floor(x))
74 |
75 |
76 | def _check_type(instance, typ, caller=""):
77 | """Check type of an object
78 |
79 | Parameters
80 | ----------
81 | caller : str
82 | caller name. Used for more informative error messages.
83 | """
84 |
85 | if not isinstance(instance, typ):
86 | caller_str = f"'{caller}': " if caller != "" else ""
87 | raise TypeError(
88 | f"{caller_str}Argument must be an instance of {typ}. {type(instance)} is given"
89 | )
90 |
91 |
92 | def make_iterable(v):
93 | return v if (v is None) or (type(v) in (list, tuple)) else [v]
94 |
95 |
96 | def main():
97 | import doctest
98 |
99 | doctest.testmod()
100 |
101 |
102 | if __name__ == "__main__":
103 | main()
104 |
--------------------------------------------------------------------------------
/klayout_package/xs_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/klayout_package/xs_128x128.png
--------------------------------------------------------------------------------
/klayout_package/xs_64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/klayout_package/xs_64x64.png
--------------------------------------------------------------------------------
/klayout_pyxs:
--------------------------------------------------------------------------------
1 | klayout_package/python/klayout_pyxs/
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/requirements.txt
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | autodoc_pydantic
2 | autotyping
3 | doc8
4 | docutils
5 | flake8
6 | flake8-bugbear
7 | ipykernel
8 | mypy
9 | myst-parser
10 | nbsphinx
11 | nbval
12 | pre-commit
13 | pur
14 | pydocstyle
15 | pytest
16 | pytest-cov
17 | pytest-regressions
18 | qrcode
19 | recommonmark
20 | sphinx>=4.4.0
21 | sphinx-autodoc-typehints
22 | sphinx-book-theme
23 | sphinx-click
24 | sphinx-copybutton
25 | sphinx-markdown-tables
26 | tox
27 | types-PyYAML
28 | types-waitress
29 | xdoctest
30 | matplotlib
31 |
--------------------------------------------------------------------------------
/samples/cmos.pyxs:
--------------------------------------------------------------------------------
1 | # A simple CMOS process description demonstrating:
2 | # - Well formation
3 | # - Field oxide formation
4 | # - Gate formation with LDD spacers/implant
5 | # - W plug creating and W CMP
6 | # - First metal layer formation
7 |
8 | # Basic options: declare the depth of the simulation and the height.
9 | # These are the defaults:
10 | # depth(2.0)
11 | # height(2.0)
12 | # Declare the basic accuracy used to remove artefacts for example:
13 | delta(5 * dbu)
14 |
15 | # Declaration the layout layers.
16 | # Possible operations are (l1 = layer(..); l2 = layer(..))
17 | # "or_" l1.or_(l2)
18 | # "and_" l1.and_(l2)
19 | # "xor" l1.xor(l2)
20 | # "not_" l1.not_(l2)
21 | # "size" l1.sized(s) (s in micron units)
22 | # or l1.sized(x, y) (x, y in micron units)
23 | # "invert" l1.inverted()
24 |
25 | lpoly = layer("3/0")
26 | lactive = layer("2/0")
27 | lfox = lactive.inverted()
28 | lwn = layer("1/0")
29 | lcg = layer("4/0")
30 | m1 = layer("6/0")
31 |
32 | # Process steps:
33 | # Now we move to cross section view: from the layout geometry we create
34 | # a material stack by simulating the process step by step.
35 | # The basic idea is that all activity happens at the surface. We can
36 | # deposit material (over existing or at a mask), etch material and
37 | # planarize.
38 | # A material is a 2D geometry as seen in the cross section along the
39 | # measurement line.
40 | # The following steps mimic a simple process.
41 |
42 | # Start with the p doped bulk and assign that to material "pbulk"
43 | # "bulk" delivers the wafer's cross section.
44 | pbulk = bulk()
45 |
46 | # create a n-well by growing the mask into the pbulk material. The
47 | # pbulk material is consumed by this step. We grep 0.5 in depth and
48 | # 0.05 to the inside using a round approximation.
49 | nwell = mask(lwn).grow(0.5, -0.05, mode='round', into=pbulk)
50 |
51 | # field oxide formation: we use the mask twice, once to grow upwards and
52 | # once to grow into the existing material. We use round approximation to
53 | # build a "hill", although that is not showing the typical beak.
54 | # Note, that we first derive the mask and use it twice. That ensures we
55 | # use the same seed for both contributions. Afterwards we join the
56 | # contributions to form the field oxide.
57 | # "bias" will shrink the resulting area.
58 | mfox = mask(lfox)
59 | fox1 = mfox.grow(0.2, 0.2, bias=0.1, mode='round')
60 | fox2 = mfox.grow(0.2, 0.2, bias=0.1, mode='round', into=[pbulk, nwell])
61 | fox = fox1.or_(fox2)
62 |
63 | # deposit 20nm gate oxide.
64 | # "deposit" is an alias for "all.grow" where "all" is a special mask covering "everything".
65 | gox = deposit(0.02)
66 |
67 | # create poly and put silicide atop of that
68 | poly = mask(lpoly).grow(0.15, -0.05, mode='round')
69 | silicide = grow(0.10, on=poly)
70 |
71 | # implant the LDD areas. Note the "through" specification which allows to grow into the
72 | # pbulk/nwell even if covered by GOX (normally that would prevent).
73 | # Also note, that "diffuse" is actually an alias for "all.grow".
74 | pldd = diffuse(0.05, 0.02, into=nwell, through=gox, bias=0.01, mode='round')
75 | nldd = diffuse(0.05, 0.02, into=pbulk, through=gox, bias=0.01, mode='round')
76 |
77 | # deposit and etch the spacer. Deposition is conformal while etch is anisotropic
78 | # (conformal: 0.05, 0.05, anisotropic: 0.05). Note that "etch" is an alias for "all.etch".
79 | ox1 = deposit(0.05, 0.05)
80 | etch(0.05, into=ox1)
81 |
82 | # implant the p+ and n+ source drain regions
83 | pd = diffuse(0.1, -0.05, into=[pldd, nwell], through=gox, mode='round')
84 | nd = diffuse(0.1, -0.05, into=[nldd, pbulk], through=gox, mode='round')
85 |
86 | # remove gate oxide where not covered
87 | etch(0.02, into=[gox, ox1])
88 |
89 | # deposit isolation
90 | iso = deposit(0.5, 0.5, mode='round')
91 | output("400/0", iso) # for demonstration
92 |
93 | # etch the gate and source/drain contacts
94 | # "taper" will make the holes conical with a sidewall angle of 5 degree.
95 | mask(lcg).etch(0.8, into=iso, taper=5)
96 |
97 | # fill with tungsten to form the plugs
98 | w = deposit(0.15, 0.15)
99 |
100 | # tungsten CMP: take off 0.45 micron measured from the top level of the
101 | # w, iso materials from w and iso.
102 | # Alternative specifications are:
103 | # downto=[material(s)] planarize down to these materials
104 | # to=z planarize to the given z position measured from 0 (the initial wafer surface)
105 | planarize(into=[w, iso], less=0.45)
106 |
107 | # m1 isolation and etch, metal deposition and CMP
108 | iso2 = deposit(0.2)
109 | mask(m1).etch(0.3, into=iso2, taper=5)
110 | alu1 = deposit(0.2, 0.2)
111 | planarize(into=[alu1], less=0.2)
112 |
113 | # finally output all result material.
114 | # output specification can be scattered throughout the script but it is
115 | # easier to read when put at the end.
116 | output("300/0", nwell)
117 | output("301/0", fox)
118 | output("301/1", gox)
119 | output("302/0", poly)
120 | output("302/1", silicide)
121 | output("303/0", pldd)
122 | output("304/0", nldd)
123 | output("305/0", ox1)
124 | output("306/0", pd)
125 | output("307/0", nd)
126 | output("308/0", iso)
127 | output("309/0", w)
128 | output("310/0", iso2)
129 | output("311/0", alu1)
130 |
--------------------------------------------------------------------------------
/samples/makedoc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | klayout -z -rm ../src/xsection.rbm -r makedoc.rb
4 |
--------------------------------------------------------------------------------
/samples/pure_python.py:
--------------------------------------------------------------------------------
1 | """Pure python example, where we don't use the klayout GUI.
2 |
3 | FIXME! it does not work yet
4 | """
5 |
6 | import pathlib
7 | from klayout_pyxs.pyxs_lib import XSectionGenerator
8 |
9 |
10 | gdspath = pathlib.Path(__file__).parent.absolute() / "sample.gds"
11 | xg = XSectionGenerator(file_name=gdspath)
12 |
13 | lpoly = xg.layer("3/0")
14 | lactive = xg.layer("2/0")
15 | lfox = lactive.inverted()
16 | lwn = xg.layer("1/0")
17 | lcg = xg.layer("4/0")
18 | m1 = xg.layer("6/0")
19 |
--------------------------------------------------------------------------------
/samples/sample.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/samples/sample.gds
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import find_packages
2 | from setuptools import setup
3 |
4 |
5 | with open("README.md") as f:
6 | LONG_DESCRIPTION = f.read()
7 |
8 |
9 | # def get_install_requires():
10 | # with open("requirements.txt") as f:
11 | # return [line.strip() for line in f.readlines() if not line.startswith("-")]
12 |
13 |
14 | setup(
15 | name="klayout_pyxs",
16 | version="0.1.13",
17 | url="https://github.com/dimapu/klayout_pyxs",
18 | license="MIT",
19 | author="Dima Pustakhod",
20 | description="python port of the Klayout xsection project",
21 | long_description=LONG_DESCRIPTION,
22 | long_description_content_type="text/markdown",
23 | packages=find_packages(exclude=("tests",)),
24 | # packages=['python/klayout_pyxs'],
25 | # packages=find_packages(exclude=("tests",), where="./python/klayout_pyxs"),
26 | # install_requires=get_install_requires(),
27 | python_requires=">=3.6",
28 | classifiers=[
29 | "Programming Language :: Python",
30 | ],
31 | )
32 |
--------------------------------------------------------------------------------
/tests/au/xs_bug11.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_bug11.gds
--------------------------------------------------------------------------------
/tests/au/xs_bug4.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_bug4.gds
--------------------------------------------------------------------------------
/tests/au/xs_bug8.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_bug8.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch1.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch1.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch10.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch10.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch2.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch2.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch3.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch3.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch4.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch4.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch5.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch5.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch6.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch6.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch7.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch7.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch8.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch8.gds
--------------------------------------------------------------------------------
/tests/au/xs_etch9.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_etch9.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow1.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow1.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow2.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow2.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow3.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow3.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow4.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow4.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow5.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow5.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow6.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow6.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow7.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow7.gds
--------------------------------------------------------------------------------
/tests/au/xs_flow8.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_flow8.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow1.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow1.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow10.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow10.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow11.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow11.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow12.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow12.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow2.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow2.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow3.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow3.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow4.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow4.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow5.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow5.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow6.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow6.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow7.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow7.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow8.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow8.gds
--------------------------------------------------------------------------------
/tests/au/xs_grow9.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_grow9.gds
--------------------------------------------------------------------------------
/tests/au/xs_misc1.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_misc1.gds
--------------------------------------------------------------------------------
/tests/au/xs_misc2.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_misc2.gds
--------------------------------------------------------------------------------
/tests/au/xs_misc3.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_misc3.gds
--------------------------------------------------------------------------------
/tests/au/xs_misc4.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_misc4.gds
--------------------------------------------------------------------------------
/tests/au/xs_misc5.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_misc5.gds
--------------------------------------------------------------------------------
/tests/au/xs_planarize1.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_planarize1.gds
--------------------------------------------------------------------------------
/tests/au/xs_planarize2.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_planarize2.gds
--------------------------------------------------------------------------------
/tests/au/xs_planarize3.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/au/xs_planarize3.gds
--------------------------------------------------------------------------------
/tests/readme.rst:
--------------------------------------------------------------------------------
1 | To run tests in Windows, use::
2 |
3 | $ bash run_tests_windows.sh
4 |
--------------------------------------------------------------------------------
/tests/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | export KLAYOUT_HOME=/dev/null
4 |
5 | echo "Using KLayout:"
6 | klayout -v
7 | echo ""
8 |
9 | rm -rf run_dir
10 | mkdir -p run_dir
11 |
12 | failed=""
13 |
14 | bin=../pymacros/pyxs.lym
15 |
16 | if [ "$1" == "" ]; then
17 | all_xs=( *.pyxs )
18 | tc_files=${all_xs[@]}
19 | else
20 | tc_files=$*
21 | fi
22 |
23 | for tc_file in $tc_files; do
24 |
25 | tc=$(echo "$tc_file" | sed 's/\.pyxs$//')
26 |
27 | echo "---------------------------------------------------"
28 | echo "Running testcase $tc .."
29 |
30 | xs_input=$(grep XS_INPUT $tc.pyxs | sed 's/.*XS_INPUT *= *//')
31 | if [ "$xs_input" = "" ]; then
32 | xs_input="xs_test.gds"
33 | fi
34 | xs_cut=$(grep XS_CUT $tc.pyxs | sed 's/.*XS_CUT *= *//')
35 | if [ "$xs_cut" = "" ]; then
36 | xs_cut="-1,0;1,0"
37 | fi
38 |
39 | klayout -rx -z -rd xs_run=$tc.pyxs -rd xs_cut="$xs_cut" -rd xs_out=run_dir/$tc.gds "$xs_input" -r $bin
40 |
41 | if klayout -b -rd a=au/"$tc".gds -rd b=run_dir/"$tc".gds -rd tol=10 -r run_xor.rb; then
42 | echo "No differences found."
43 | else
44 | failed="$failed $tc"
45 | fi
46 |
47 | done
48 |
49 | echo "---------------------------------------------------"
50 | if [ "$failed" = "" ]; then
51 | echo "All tests successful."
52 | else
53 | echo "*** TESTS FAILED:$failed"
54 | fi
55 |
--------------------------------------------------------------------------------
/tests/run_tests_windows.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | #
4 |
5 | # Add klayout install folder to path
6 | export PATH=$PATH:"/C/Program Files (x86)/KLayout"
7 |
8 | # Check klayout version
9 | echo "Using KLayout:"
10 | klayout_app -v
11 | echo ""
12 |
13 | rm -rf run_dir
14 | mkdir -p run_dir
15 |
16 | failed=""
17 |
18 | # Location of the python macros pyxs.lym (dev version)
19 | bin=../klayout_pyxs/pymacros/pyxs.lym
20 |
21 | if [ "$1" == "" ]; then
22 | all_xs=( *.pyxs ) # will test all .pyxs files in the folder
23 | tc_files=${all_xs[@]}
24 | else
25 | tc_files=$* # will test only specified file
26 | fi
27 |
28 | for tc_file in $tc_files; do
29 |
30 | tc=$(echo "$tc_file" | sed 's/\.pyxs$//')
31 |
32 | echo "---------------------------------------------------"
33 | echo "Running testcase $tc .."
34 |
35 | # Check which gds file to use
36 | xs_input=$(grep XS_INPUT "$tc".pyxs | sed 's/.*XS_INPUT *= *//')
37 | if [ "$xs_input" = "" ]; then
38 | xs_input="xs_test.gds"
39 | fi
40 |
41 | # Check which ruler to use for a cross-section
42 | xs_cut=$(grep XS_CUT "$tc".pyxs | sed 's/.*XS_CUT *= *//')
43 | if [ "$xs_cut" = "" ]; then
44 | xs_cut="-1,0;1,0"
45 | fi
46 |
47 | # echo $tc.pyxs
48 | # echo $xs_cut
49 | # echo $tc.gds
50 | # echo $xs_input
51 | # echo $bin
52 |
53 | klayout_app -rx -z -rd xs_run="$tc".pyxs -rd xs_cut="$xs_cut" -rd xs_out=run_dir/"$tc".gds "$xs_input" -r $bin
54 |
55 | if klayout_app -b -rd a=au/"$tc".gds -rd b=run_dir/"$tc".gds -rd tol=10 -r run_xor.rb; then
56 | echo "No differences found."
57 | else
58 | failed="$failed $tc"
59 | fi
60 |
61 | done
62 |
63 | echo "---------------------------------------------------"
64 | if [ "$failed" = "" ]; then
65 | echo "All tests successful."
66 | else
67 | echo "*** TESTS FAILED:$failed"
68 | fi
69 |
--------------------------------------------------------------------------------
/tests/run_xor.rb:
--------------------------------------------------------------------------------
1 |
2 | l1 = RBA::Layout::new
3 | l1.read($a)
4 |
5 | l2 = RBA::Layout::new
6 | l2.read($b)
7 |
8 | layer_pairs = []
9 |
10 | l1.layer_indices.each do |ll1|
11 |
12 | li1 = l1.get_info(ll1)
13 |
14 | ll2 = l2.find_layer(l1.get_info(ll1))
15 | if !ll2
16 | raise "Layer #{li1.to_s} of layout #{$a} now present in layout #{$b}"
17 | end
18 |
19 | layer_pairs << [ ll1, ll2 ]
20 |
21 | end
22 |
23 | l2.layer_indices.each do |ll2|
24 | ll1 = l1.find_layer(l2.get_info(ll2))
25 | if !ll1
26 | raise "Layer #{li2.to_s} of layout #{$b} now present in layout #{$a}"
27 | end
28 | end
29 |
30 | if l1.top_cell.name != l2.top_cell.name
31 | raise "Top cell name of layout #{$a} (#{l1.top_cell.name} differs from that of layout #{$b} (#{l2.top_cell.name})"
32 | end
33 |
34 | if (l1.dbu - l2.dbu) > 1e-6
35 | raise "Database unit of layout #{$a} (#{l1.dbu} differs from that of layout #{$b} (#{l2.dbu})"
36 | end
37 |
38 | diff = false
39 |
40 | layer_pairs.each do |ll1,ll2|
41 |
42 | r1 = RBA::Region::new(l1.top_cell.begin_shapes_rec(ll1))
43 | r2 = RBA::Region::new(l2.top_cell.begin_shapes_rec(ll2))
44 |
45 | rxor = r1 ^ r2
46 |
47 | if $tol.to_i > 0
48 | rxor.size(-$tol.to_i)
49 | end
50 |
51 | if !rxor.is_empty?
52 | diff = true
53 | puts "#{rxor.size} differences found on layer #{l1.get_info(ll1).to_s}"
54 | else
55 | puts "No differences found on layer #{l1.get_info(ll1).to_s}"
56 | end
57 |
58 | end
59 |
60 | if diff
61 | raise "Differences found between layouts #{$a} and #{$b}"
62 | end
63 |
--------------------------------------------------------------------------------
/tests/xs_bug11.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/xs_bug11.gds
--------------------------------------------------------------------------------
/tests/xs_bug11.pyxs:
--------------------------------------------------------------------------------
1 | # XS_INPUT=xs_bug11.gds
2 | # XS_CUT=-10,40;60,40
3 |
4 | depth(100)
5 | height(100)
6 |
7 | # Prepare input layers
8 | layer_IN = layer("1/0")
9 |
10 | substrate = bulk()
11 |
12 | # Grow some bars
13 | bars = mask(layer_IN).grow(2.0)
14 |
15 | # First epitaxial layer
16 | epi1a = deposit(1.0, 0.1, mode='round')
17 | epi1b = deposit(1.0, 0.2, mode='round')
18 | epi1c = deposit(1.0, 0.3, mode='round')
19 | epi1d = deposit(1.0, 0.4, mode='round')
20 |
21 | # Second epitaxial layer
22 | epi2 = deposit(3.0, 3.0, mode='round')
23 |
24 | # finally output all result material to the target layout
25 | output("1/0", substrate)
26 | output("2/0", bars)
27 | output("3/1", epi1a)
28 | output("3/2", epi1b)
29 | output("3/3", epi1c)
30 | output("3/4", epi1d)
31 | output("4/0", epi2)
32 |
--------------------------------------------------------------------------------
/tests/xs_bug4.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/xs_bug4.gds
--------------------------------------------------------------------------------
/tests/xs_bug4.pyxs:
--------------------------------------------------------------------------------
1 | # XS_INPUT=xs_bug4.gds
2 | # XS_CUT=-8,16;12,16
3 |
4 | # Prepare input layers
5 | layer_TRENCH = layer("2/0")
6 | layer_IMPLANT = layer("3/0")
7 |
8 | substrate = bulk()
9 |
10 | # First epitaxial layer
11 | epi = deposit(0.5)
12 |
13 | # Second epitaxial layer
14 | epi2 = deposit(0.5)
15 |
16 | # TRENCH
17 | # etch substrate on mask with thickness 0.7µm and angle 30°
18 | mask(layer_TRENCH).etch(0.7, taper=30, into=[substrate, epi, epi2])
19 |
20 | # IMPLANT
21 | # create an implant by growing the mask into the substrate material and both epitaxial layers.
22 | implant=mask(layer_IMPLANT).grow(0.2, 0.05, mode='round', into=[substrate, epi, epi2])
23 |
24 | # finally output all result material to the target layout
25 | output("1/0", substrate)
26 | output("2/0", epi)
27 | output("3/0", epi2)
28 | output("6/0", implant)
29 |
--------------------------------------------------------------------------------
/tests/xs_bug8.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/xs_bug8.gds
--------------------------------------------------------------------------------
/tests/xs_bug8.pyxs:
--------------------------------------------------------------------------------
1 | # XS_INPUT=xs_bug8.gds
2 | # XS_CUT=-3,0;7,0
3 |
4 | depth(20.0)
5 | height(20.0)
6 |
7 | l1 = layer("1/0")
8 |
9 | substrate = bulk()
10 |
11 | m1 = deposit(1.0)
12 | mask(l1).etch(2.0, 0.0, into=[m1, substrate])
13 | mask(l1.sized(0.2)).etch(0.0, 0.5, into=m1)
14 |
15 | output("1/0", substrate)
16 | output("2/0", m1)
17 |
--------------------------------------------------------------------------------
/tests/xs_etch1.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch10.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1.sized(0.1)).etch(0.3, taper=30, into=substrate)
6 | step1 = substrate.dup()
7 |
8 | mask(l1.inverted()).etch(0.2, into=substrate)
9 | step2 = substrate.dup()
10 |
11 | mask(l1.inverted()).etch(0.2, 0.1, mode='round', into=substrate)
12 | step3 = substrate.dup()
13 |
14 | mask(l1.inverted()).etch(0.2, into=substrate)
15 |
16 | output("100/0", bulk())
17 | output("101/0", step1)
18 | output("102/0", step2)
19 | output("103/0", step3)
20 | output("104/0", substrate)
21 |
--------------------------------------------------------------------------------
/tests/xs_etch2.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, 0.1, mode='square', into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch3.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, bias=0.1, into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch4.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, 0.1, taper=20, into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch5.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, bias=-0.1, taper=20, into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch6.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, 0.1, mode='round', into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch7.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, 0.1, bias=0.05, mode='round', into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch8.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | mask(l1).etch(0.3, 0.1, bias=0.05, mode='octagon', into=substrate)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 |
--------------------------------------------------------------------------------
/tests/xs_etch9.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | m1 = mask(l1).grow(0.3, 0.3, taper=45)
6 | mask(l1.sized(0.1)).etch(0.2, 0.1, mode='round', into=[m1, substrate])
7 |
8 | output("100/0", bulk())
9 | output("101/0", substrate)
10 | output("102/0", m1)
11 |
--------------------------------------------------------------------------------
/tests/xs_flow1.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: mask data preprocessing, out-of-place operations
2 |
3 | l1 = layer("1/0")
4 | l2 = layer("2/0")
5 | l3 = layer("3/0")
6 | l4 = layer("4/0")
7 |
8 | m1 = mask(l1).grow(0.05)
9 | m2 = mask(l1.or_(l3)).grow(0.05)
10 | m3 = mask(l1.or_(l3).and_(l4)).grow(0.05)
11 | m4 = mask(l2.not_(l4)).grow(0.05)
12 | m5 = mask(l1.xor(l4)).grow(0.05)
13 | m6 = mask(l2.sized(0.1)).grow(0.05)
14 | m7 = mask(l3.inverted()).grow(0.05)
15 |
16 | output("100/0", bulk())
17 | output("101/0", m1)
18 | output("102/0", m2)
19 | output("103/0", m3)
20 | output("104/0", m4)
21 | output("105/0", m5)
22 | output("106/0", m6)
23 | output("107/0", m7)
24 |
--------------------------------------------------------------------------------
/tests/xs_flow2.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: mask data preprocessing, in-place operations
2 |
3 | l1 = layer("1/0")
4 | l2 = layer("2/0")
5 | l3 = layer("3/0")
6 | l4 = layer("4/0")
7 |
8 | m1 = mask(l1).grow(0.05)
9 | l = l1.dup()
10 | l.add(l3)
11 | m2 = mask(l).grow(0.05)
12 | l.mask(l4)
13 | m3 = mask(l).grow(0.05)
14 | l = l2.dup()
15 | l.sub(l4)
16 | m4 = mask(l).grow(0.05)
17 | l = l2.dup()
18 | l.size(0.1)
19 | m5 = mask(l).grow(0.05)
20 | l = l3.dup()
21 | l.invert()
22 | m6 = mask(l).grow(0.05)
23 |
24 | output("100/0", bulk())
25 | output("101/0", m1)
26 | output("102/0", m2)
27 | output("103/0", m3)
28 | output("104/0", m4)
29 | output("105/0", m5)
30 | output("106/0", m6)
31 |
--------------------------------------------------------------------------------
/tests/xs_flow3.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: deposit
2 |
3 | l1 = layer("1/0")
4 | l2 = layer("2/0")
5 | l3 = layer("3/0")
6 | l4 = layer("4/0")
7 |
8 | m1 = mask(l1).grow(0.3)
9 | m2 = mask(l3).grow(0.3, 0.2, mode='round')
10 | m3 = deposit(0.3, 0.4, mode='round')
11 |
12 | output("100/0", bulk())
13 |
14 | output("101/0", m1)
15 | output("102/0", m2)
16 | output("103/0", m3)
17 |
18 | # delete all material above bulk
19 | planarize(into=[m1, m2, m3], downto=bulk())
20 |
21 | m1 = mask(l1).grow(0.3)
22 | m2 = mask(l3).grow(0.3, 0.1, bias=0.05, mode='round')
23 | m3 = deposit(0.3, taper=15)
24 |
25 | output("111/0", m1)
26 | output("112/0", m2)
27 | output("113/0", m3)
28 |
29 | # delete all material above bulk
30 | planarize(into=[m1, m2, m3], downto=bulk())
31 |
32 | m1 = mask(l1).grow(0.3)
33 | m2 = mask(l3).grow(0.3, taper=10)
34 | m3 = deposit(0.2, 0.2, mode='round')
35 |
36 | output("121/0", m1)
37 | output("122/0", m2)
38 | output("123/0", m3)
39 |
--------------------------------------------------------------------------------
/tests/xs_flow4.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: etch
2 |
3 | l1 = layer("1/0")
4 | l2 = layer("2/0")
5 | l3 = layer("3/0")
6 | l4 = layer("4/0")
7 |
8 | b = bulk()
9 |
10 | mask(l1).etch(0.3, into=b)
11 | m1 = grow(0.3, 0.3, mode='round')
12 |
13 | mask(l2).etch(0.1, into=m1)
14 |
15 | mask(l3).etch(0.3, 0.1, mode='round', into=[b, m1])
16 |
17 | m2 = deposit(0.5)
18 | planarize(into=m2, downto=m1)
19 |
20 | mask(l3).etch(0.2, taper=10, into=[m2])
21 |
22 | output("100/0", b)
23 | output("101/0", m1)
24 | output("102/0", m2)
25 |
--------------------------------------------------------------------------------
/tests/xs_flow5.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: flip
2 |
3 | depth(1)
4 |
5 | l1 = layer("1/0")
6 | l2 = layer("2/0")
7 | l3 = layer("3/0")
8 | l4 = layer("4/0")
9 |
10 | b = bulk()
11 |
12 | mask(l1).etch(0.3, into=b)
13 | m1 = grow(0.3, 0.3, mode='round')
14 |
15 | mask(l2).etch(0.1, into=m1)
16 |
17 | mask(l3).etch(0.3, 0.1, mode='round', into=[b, m1])
18 |
19 | m2 = deposit(0.5)
20 | planarize(into=m2, downto=m1)
21 |
22 | flip()
23 |
24 | mask(l1).etch(0.3, into=b)
25 | m1b = grow(0.3, 0.3, mode='round')
26 |
27 | mask(l2).etch(0.1, into=m1b)
28 |
29 | mask(l3).etch(0.3, 0.1, mode='round', into=[b, m1b])
30 |
31 | m2b = deposit(0.5)
32 | planarize(into=m2b, downto=m1b)
33 |
34 | mask(l3).etch(0.2, taper=10, into=[m2b])
35 |
36 | flip()
37 |
38 | mask(l3).etch(0.2, taper=10, into=[m2])
39 |
40 | output("100/0", b)
41 | output("101/0", m1.or_(m1b))
42 | output("102/0", m2.or_(m2b))
43 |
--------------------------------------------------------------------------------
/tests/xs_flow6.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: combine materials into new one
2 |
3 | depth(1)
4 |
5 | l1 = layer("1/0")
6 | l2 = layer("2/0")
7 | l3 = layer("3/0")
8 | l4 = layer("4/0")
9 |
10 | b = bulk()
11 |
12 | m1 = grow(0.3, 0)
13 | m2 = mask(l1).grow(0.3, 0)
14 |
15 | # new material built from two ones
16 | m12 = m1.or_(m2)
17 |
18 | output("100/0", b)
19 | output("101/0", m1)
20 | output("102/0", m2)
21 | output("103/0", m12)
22 |
23 | mask(l4).etch(0.1, into=m12)
24 | output("104/0", m12)
25 |
--------------------------------------------------------------------------------
/tests/xs_flow7.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: combine materials into new one
2 |
3 | depth(1)
4 |
5 | l1 = layer("1/0")
6 | l2 = layer("2/0")
7 | l3 = layer("3/0")
8 | l4 = layer("4/0")
9 |
10 | b = bulk()
11 |
12 | m1 = grow(0.3, 0)
13 | m2 = mask(l1).grow(0.3, 0)
14 |
15 | # new material built from two ones by subtracting and sizing
16 | m1.discard() # NEEDED: m1 and m2 must be removed. Otherwise they block the etch step. MUST be there before the "and" operation.
17 | m2.discard()
18 | m12 = m1.not_(m2.sized(0.1))
19 | m12.keep() # NEEDED: m12 is kept finally
20 |
21 | output("100/0", b)
22 | output("101/0", m1)
23 | output("102/0", m2)
24 | output("103/0", m12)
25 |
26 | mask(l4).etch(0.1, into=m12)
27 | output("104/0", m12)
28 |
--------------------------------------------------------------------------------
/tests/xs_flow8.pyxs:
--------------------------------------------------------------------------------
1 | # Basic functionality: combine materials into new one
2 |
3 | depth(1)
4 |
5 | l1 = layer("1/0")
6 | l2 = layer("2/0")
7 | l3 = layer("3/0")
8 | l4 = layer("4/0")
9 |
10 | b = bulk()
11 |
12 | m1 = grow(0.3, 0)
13 | m2 = mask(l1).grow(0.3, 0)
14 |
15 | # new material built from two ones by subtracting and sizing
16 | m1.discard() # NEEDED: m1 and m2 must be removed. Otherwise they block the etch step. MUST be there before the "and" operation.
17 | m2.discard()
18 | m12 = m1.and_(m2.sized(0.3))
19 | m12.keep() # NEEDED: m12 is kept finally
20 |
21 | output("100/0", b)
22 | output("101/0", m1)
23 | output("102/0", m2)
24 | output("103/0", m12)
25 |
26 | mask(l4).etch(0.1, into=m12)
27 | output("104/0", m12)
28 |
--------------------------------------------------------------------------------
/tests/xs_grow1.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3)
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow10.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 | l2 = layer("2/0")
3 | l3 = layer("3/0")
4 | l4 = layer("4/0")
5 |
6 | substrate = bulk()
7 |
8 | below = mask(l4).grow(0.2, into=substrate)
9 | atop1 = mask(l1.or_(l3)).grow(0.1, 0.05, on=below)
10 | atop2 = mask(l1.or_(l3)).grow(0.1, 0.15)
11 |
12 | output("100/0", bulk())
13 | output("101/0", substrate)
14 | output("102/0", below)
15 | output("103/0", atop1)
16 | output("104/0", atop2)
17 |
--------------------------------------------------------------------------------
/tests/xs_grow11.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 | l2 = layer("2/0")
3 | l3 = layer("3/0")
4 | l4 = layer("4/0")
5 |
6 | substrate = bulk()
7 |
8 | below1 = mask(l4).grow(0.05, into=substrate)
9 | below2 = mask(l1.or_(l3)).grow(0.3, 0.2, mode='round', through=below1, into=substrate)
10 |
11 | output("100/0", bulk())
12 | output("101/0", substrate)
13 | output("102/0", below1)
14 | output("103/0", below2)
15 |
--------------------------------------------------------------------------------
/tests/xs_grow12.pyxs:
--------------------------------------------------------------------------------
1 | # tests the ability to work with empty data
2 |
3 | l1 = layer("1/0")
4 |
5 | # Those layers don't exist
6 | l2 = layer("12/0")
7 | l3 = layer("13/0")
8 | l4 = layer("14/0")
9 |
10 | substrate = bulk()
11 |
12 | below1 = mask(l4).grow(0.05, into=substrate)
13 | below2 = mask(l1.or_(l3)).grow(0.3, 0.2, mode='round', through=below1, into=substrate)
14 | below3 = mask(l1.or_(l3)).grow(0.3, 0.2, into=below1)
15 | below4 = mask(l1.or_(l3)).grow(0.3, 0.2, mode='square', on=below1)
16 |
17 | output("100/0", bulk())
18 | output("101/0", substrate)
19 | output("102/0", below1)
20 | output("103/0", below2)
21 | output("104/0", below3)
22 | output("105/0", below4)
23 |
--------------------------------------------------------------------------------
/tests/xs_grow2.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, 0.1, mode='square')
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow3.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, bias=0.1)
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow4.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, 0.1, taper=20)
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow5.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, bias=-0.1, taper=20)
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow6.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, 0.1, mode='round')
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow7.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, 0.1, bias=0.05, mode='round')
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow8.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.3, 0.1, bias=0.05, mode='octagon')
4 | output("100/0", bulk())
5 | output("101/0", m1)
6 |
--------------------------------------------------------------------------------
/tests/xs_grow9.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | m1 = mask(l1).grow(0.3, 0.1, mode='round', into=substrate, buried=0.4)
6 | output("100/0", bulk())
7 | output("101/0", substrate)
8 | output("102/0", m1)
9 |
--------------------------------------------------------------------------------
/tests/xs_misc1.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | depth(1.0)
4 |
5 | substrate = bulk()
6 |
7 | m1 = mask(l1).grow(0.2, bias=-0.2, taper=20)
8 |
9 | flip()
10 |
11 | mask(l1).etch(0.2, into=substrate, bias=-0.2, taper=20)
12 |
13 | output("100/0", bulk())
14 | output("101/0", substrate)
15 | output("102/0", m1)
16 |
--------------------------------------------------------------------------------
/tests/xs_misc2.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1 = mask(l1).grow(0.2, bias=-0.2, taper=20)
4 | m2 = deposit(0.1)
5 |
6 | output("100/0", bulk())
7 | output("101/0", m1)
8 | output("102/0", m2)
9 |
--------------------------------------------------------------------------------
/tests/xs_misc3.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | substrate = bulk()
4 |
5 | m1 = mask(l1).grow(0.2, bias=-0.2, taper=20)
6 | etch(0.1, into=[substrate, m1])
7 |
8 | output("100/0", bulk())
9 | output("101/0", substrate)
10 | output("102/0", m1)
11 |
--------------------------------------------------------------------------------
/tests/xs_misc4.pyxs:
--------------------------------------------------------------------------------
1 | # This script tests output_all() function.
2 | # It accepts a dictionary as an output_layers parameter.
3 | # Keys are layer specifications, and values are LayoutData instances.
4 |
5 | l1 = layer("1/0")
6 |
7 | substrate = bulk()
8 |
9 | m1 = mask(l1).grow(0.2, bias=-0.2, taper=20)
10 | etch(0.1, into=[substrate, m1])
11 |
12 | output_layers = {
13 | "100/0": bulk(),
14 | "101/0": substrate,
15 | "102/0": m1,
16 | }
17 |
18 | output_all(output_layers=output_layers)
19 |
--------------------------------------------------------------------------------
/tests/xs_misc5.pyxs:
--------------------------------------------------------------------------------
1 | # This script tests new format of output function.
2 | # It accepts a dictionary as an output_layers parameter.
3 | # Keys are layer specifications, and values are LayoutData instances.
4 |
5 | l1 = layer("1/0")
6 |
7 | output_layers = {
8 | "100/0": 'blk',
9 | "101/0": 'substrate',
10 | "102/0": 'm1',
11 | }
12 |
13 | blk = bulk()
14 | substrate = bulk()
15 |
16 | m1 = mask(l1).grow(0.2, bias=-0.2, taper=20)
17 | etch(0.1, into=[substrate, m1])
18 |
19 | output_all(output_layers=output_layers, script_globals=globals())
20 |
--------------------------------------------------------------------------------
/tests/xs_planarize1.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1a = mask(l1).grow(0.2)
4 | m1b = mask(l1).grow(0.2, 0.2)
5 | planarize(into=[m1a, m1b], to=0.15)
6 | output("100/0", bulk())
7 | output("101/0", m1a)
8 | output("102/0", m1b)
9 |
--------------------------------------------------------------------------------
/tests/xs_planarize2.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1a = mask(l1).grow(0.2)
4 | m1b = mask(l1).grow(0.2, 0.2)
5 | planarize(into=m1b, downto=m1a)
6 | output("100/0", bulk())
7 | output("101/0", m1a)
8 | output("102/0", m1b)
9 |
--------------------------------------------------------------------------------
/tests/xs_planarize3.pyxs:
--------------------------------------------------------------------------------
1 | l1 = layer("1/0")
2 |
3 | m1a = mask(l1).grow(0.2)
4 | m1b = mask(l1).grow(0.2, 0.2)
5 | planarize(into=[m1a, m1b], less=0.25)
6 | output("100/0", bulk())
7 | output("101/0", m1a)
8 | output("102/0", m1b)
9 |
--------------------------------------------------------------------------------
/tests/xs_test.gds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdsfactory/klayout_pyxs/4b5845c0525cbb905a4b32c571f234163ac4e2e8/tests/xs_test.gds
--------------------------------------------------------------------------------
/xs2pyxs/xs2pyxs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | # This script contains replacement patterns to convert XS to PYXS
4 | # It implements some of the required changes, however more changes
5 | # might be needed.
6 |
7 | if [ "$1" == "" ]; then
8 | all_xs=( *.xs ) # will convert all .xs files in the folder
9 | xs_files=${all_xs[@]}
10 | else
11 | xs_files=$* # will convert only specified file(s)
12 | fi
13 |
14 | for xs_file in $xs_files; do
15 | pyxs_file=$(echo "$xs_file" | sed 's/\.xs$/\.pyxs/')
16 | sed -r --file=xs2pyxs_patterns.txt < "$xs_file" > "$pyxs_file"
17 | done
18 |
--------------------------------------------------------------------------------
/xs2pyxs/xs2pyxs_patterns.txt:
--------------------------------------------------------------------------------
1 | # This sed script contains replacement patterns to convert XS to PYXS
2 | # It implements some of the required changes, however more changes
3 | # might be needed.
4 |
5 | /^#.*/! s/([^A-Za-z_0-9]|^)(all|bulk|flip|)([^A-Za-z_0-9]|$)/\1\2()\3/g
6 | /^#.*/! s/([^A-Za-z_0-9]|^)(inverted|invert)([^A-Za-z_0-9]|$)/\1\2()\3/g
7 | s/:mode[ ]*=>[ ]*/mode=/g
8 | s/:taper[ ]*=>[ ]*/taper=/g
9 | s/:bias[ ]*=>[ ]*/bias=/g
10 | s/:into[ ]*=>[ ]*/into=/g
11 | s/:through[ ]*=>[ ]*/through=/g
12 | s/:buried[ ]*=>[ ]*/buried=/g
13 | s/:to[ ]*=>[ ]*/to=/g
14 | s/:mode/mode/g
15 | s/:taper/taper/g
16 | s/:bias/bias/g
17 | s/:into/into/g
18 | s/:through/through/g
19 | s/:buried/buried/g
20 | s/:to/buried/g
21 | s/:round/'round'/g
22 | s/:square/'square'/g
23 | s/:octagon/'octagon'/g
24 | s/.and\(/.and_\(/g
25 | s/.or\(/.or_\(/g
26 | s/.not\(/.not_\(/g
27 |
--------------------------------------------------------------------------------
/xs2pyxs/xs_bug11.xs:
--------------------------------------------------------------------------------
1 | # XS_INPUT=xs_bug11.gds
2 | # XS_CUT=-10,40;60,40
3 |
4 | depth(100)
5 | height(100)
6 |
7 | # Prepare input layers
8 | layer_IN = layer("1/0")
9 |
10 | substrate = bulk
11 |
12 | substrate =all
13 |
14 | # Grow some bars
15 | bars = mask(layer_IN).grow(2.0)
16 |
17 | # First epitaxial layer
18 | epi1a = deposit(1.0, 0.1, :mode => :round)
19 | epi1b = deposit(1.0, 0.2, :mode => :round)
20 | epi1c = deposit(1.0, 0.3, :mode => :round)
21 | epi1d = deposit(1.0, 0.4, :mode => :round) # :mode
22 |
23 | # Second epitaxial layer
24 | epi2 = deposit(3.0, 3.0, :mode => :round)
25 |
26 | # finally output all result material to the target layout finall allo all
27 | output("1/0", substrate)
28 | output("2/0", bars)
29 | output("2/0", all) # boom
30 | output("2/0", all) # all
31 | output("3/1", epi1a)
32 | output("3/2", epi1b)
33 | output("3/3", epi1c)
34 | output("3/4", epi1d)
35 | output("4/0", epi2)
36 |
37 | shallow = all + (all) + none + all
38 |
39 | marshmall = all
40 |
--------------------------------------------------------------------------------