├── .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 | [![docs](https://github.com/gdsfactory/klayout_pyxs/actions/workflows/pages.yml/badge.svg)](https://gdsfactory.github.io/klayout_pyxs/) 4 | [![pypi](https://img.shields.io/pypi/v/klayout_pyxs)](https://pypi.org/project/klayout_pyxs/) 5 | [![MIT](https://img.shields.io/github/license/gdsfactory/gdsfactory)](https://choosealicense.com/licenses/mit/) 6 | [![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 7 | [![Downloads](https://pepy.tech/badge/klayout_pyxs)](https://pepy.tech/project/klayout_pyxs) 8 | [![Downloads](https://pepy.tech/badge/klayout_pyxs/month)](https://pepy.tech/project/klayout_pyxs) 9 | [![Downloads](https://pepy.tech/badge/klayout_pyxs/week)](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 | ![](https://i.imgur.com/0e1vAqW.png) 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 | --------------------------------------------------------------------------------