├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── _static │ ├── annotations_custom.png │ ├── annotations_enhanced.png │ ├── annotations_simple.png │ ├── demo_pinout_diagram.png │ ├── huzzah32_pinout.png │ ├── kicad_export.png │ ├── kicad_screenshot.png │ ├── label_dimensions.png │ ├── quick_start_measurements_labels.png │ ├── quick_start_measurements_pins.png │ ├── quick_start_measurements_scale.png │ └── quick_start_pinout_diagram.png ├── conf.py ├── index.rst ├── make.bat ├── pages │ ├── KiCad_integration.rst │ ├── annotation.rst │ ├── config.rst │ ├── core.rst │ ├── customise.rst │ ├── install.rst │ ├── integrated_circuits.rst │ ├── layout.rst │ ├── leaderline.rst │ ├── legend.rst │ ├── manager.rst │ ├── modules.rst │ ├── pinlabel.rst │ ├── resources.rst │ ├── text.rst │ └── tutorial.rst └── requirements.txt ├── pinout.toml ├── pinout ├── __init__.py ├── components │ ├── __init__.py │ ├── annotation.py │ ├── integrated_circuits.py │ ├── layout.py │ ├── leaderline.py │ ├── legend.py │ ├── pinlabel.py │ └── text.py ├── config.py ├── core.py ├── kicad2pinout.py ├── manager.py ├── resources │ ├── config.py │ ├── pinout_kicad_example.zip │ └── quick_start │ │ ├── data.py │ │ ├── hardware.png │ │ ├── pinout_diagram.py │ │ └── styles.css ├── style_tools.py ├── templates.py └── templates │ ├── circle.svg │ ├── clippath.svg │ ├── component_common.svg │ ├── group.svg │ ├── image.svg │ ├── kicad_footprint_lib │ ├── Annotation.j2 │ ├── Origin.j2 │ └── PinLabel.j2 │ ├── label.svg │ ├── path.svg │ ├── rect.svg │ ├── style.svg │ ├── stylesheet.j2 │ ├── svg.svg │ ├── text.svg │ ├── textblock.svg │ └── use.svg ├── samples ├── arduino │ ├── arduino │ │ ├── __init__.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ ├── arduino_components.py │ │ │ ├── patterns.xml │ │ │ ├── preprocessor.py │ │ │ └── styles.css │ │ ├── rp2040 │ │ │ ├── __init__.py │ │ │ ├── arduino_nano_rp2040_connect.py │ │ │ ├── hardware.png │ │ │ └── rp2040_data.py │ │ └── uno │ │ │ ├── __init__.py │ │ │ ├── arduino_uno.py │ │ │ ├── delete │ │ │ ├── arduino_components.py │ │ │ ├── patterns.xml │ │ │ ├── pinout_arduino_uno_rev3.svg │ │ │ └── styles.css │ │ │ ├── hardware.png │ │ │ └── uno_data.py │ ├── arduino_notes.md │ ├── pinout_arduino_nano_rp2040_connect.svg │ └── pinout_arduino_uno_rev3.svg ├── attiny85 │ ├── attiny85.py │ ├── attiny_styles.css │ └── pinout_attiny85.svg ├── clip_path │ ├── diagram.svg │ ├── hardware_18pin.png │ ├── pinout_diagram.py │ └── styles.css ├── full_sample │ ├── __init__.py │ ├── components.py │ ├── full_sample_data.py │ ├── hardware.png │ ├── pinout_diagram.py │ ├── pinout_diagram.svg │ ├── styles.css │ └── styles_auto.css ├── panel_layout │ ├── __init__.py │ ├── auto_styles.css │ ├── output │ │ ├── panel_layout.svg │ │ ├── populated_layout.png │ │ └── populated_layout.svg │ ├── panel_layout.py │ ├── populated_layout.py │ ├── readme.md │ └── styles.css ├── pci-express │ ├── autostyles.css │ ├── pci-express_data.xlsx │ ├── pci-express_x1.svg │ ├── pci_data.py │ ├── pinout_x1.py │ └── pinout_x1.svg ├── section_pullout │ ├── diagram.svg │ ├── hardware_18pin.png │ ├── pinout_diagram.py │ ├── section_pullout_data.py │ └── styles.css └── teensy_4.0 │ ├── hardware_teensy_4.0_front.svg │ ├── pinout_diagram.py │ ├── styles.css │ ├── styles.scss │ ├── teensy_4.0_front_pinout_diagram.svg │ └── teensy_4_data.py ├── setup.py └── tests ├── resources ├── 200x200.png ├── 200x200.svg ├── __init__.py ├── diagram_export.py ├── diagram_export.svg ├── diagram_image.py ├── diagram_image.svg └── styles.css └── test_samples.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 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 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | tests/temp/ 54 | temp_pytest_*.svg 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # Visual studio code project settings 123 | .vscode/ 124 | *.code-workspace 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 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: 1 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | builder: html 11 | configuration: docs/conf.py 12 | 13 | # Optionally build your docs in additional formats such as PDF 14 | formats: 15 | - epub 16 | - pdf 17 | 18 | # Optionally set the version of Python and requirements required to build your docs 19 | python: 20 | version: 3.6 21 | install: 22 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 John Newall (aka j0ono0) 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 | recursive-include pinout/templates * 2 | recursive-include pinout/resources * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pinout 2 | 3 | ![GitHub](https://img.shields.io/github/license/j0ono0/pinout) ![GitHub last commit](https://img.shields.io/github/last-commit/j0ono0/pinout) ![Read the Docs](https://img.shields.io/readthedocs/pinout) 4 | 5 | SVG diagram creation from Python code - **pinout** provides an easy method of creating pinout diagrams for electronic hardware. 6 | 7 |

8 | Example diagram created from pinout. 9 |

10 | 11 | Please visit [pinout.readthedocs.io](https://pinout.readthedocs.io) for the full *quick start* tutorial and detailed documentation on all options provided by the *pinout* package. 12 | 13 | ## Quick start 14 | 15 | *pinout* can be easily installed with pip and provides some sample files that demonstrate key features. 16 | 17 | ### Install 18 | 19 | Using a virtual environment is recommended; Start by installing the *pinout* package. Either clone this repo and pip install it or install from PyPi: 20 | ``` 21 | pip install pinout 22 | 23 | # Or upgrade to the latest version 24 | pip install --upgrade pinout 25 | ``` 26 | 27 | ### Duplicate sample files 28 | 29 | A normal pinout diagram will include a hardware image, stylesheet, data file, and a Python script. Sample files are included with the package and can be duplicated for your use. Open a command line (with enabled virtual environment if you are using one) in the location you plan to work and enter the following: 30 | ```python 31 | py -m pinout.manager --duplicate quick_start 32 | 33 | # expected output: 34 | # >>> data.py duplicated. 35 | # >>> hardware.png duplicated. 36 | # >>> pinout_diagram.py duplicated. 37 | # >>> styles.css duplicated. 38 | ``` 39 | 40 | Once you have these file a finished diagram can be generated from a command line `py -m pinout.manager --export pinout_diagram diagram.svg`. An SVG file is created and can be conveniently view in a browser. 41 | 42 | ![SVG diagram ](docs/_static/quick_start_pinout_diagram.png) 43 | 44 | For a detailed walk through *pinout_diagram.py* and more information on *pinout* please visit [pinout.readthedocs.io](https://pinout.readthedocs.io). 45 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/annotations_custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/annotations_custom.png -------------------------------------------------------------------------------- /docs/_static/annotations_enhanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/annotations_enhanced.png -------------------------------------------------------------------------------- /docs/_static/annotations_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/annotations_simple.png -------------------------------------------------------------------------------- /docs/_static/demo_pinout_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/demo_pinout_diagram.png -------------------------------------------------------------------------------- /docs/_static/huzzah32_pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/huzzah32_pinout.png -------------------------------------------------------------------------------- /docs/_static/kicad_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/kicad_export.png -------------------------------------------------------------------------------- /docs/_static/kicad_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/kicad_screenshot.png -------------------------------------------------------------------------------- /docs/_static/label_dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/label_dimensions.png -------------------------------------------------------------------------------- /docs/_static/quick_start_measurements_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/quick_start_measurements_labels.png -------------------------------------------------------------------------------- /docs/_static/quick_start_measurements_pins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/quick_start_measurements_pins.png -------------------------------------------------------------------------------- /docs/_static/quick_start_measurements_scale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/quick_start_measurements_scale.png -------------------------------------------------------------------------------- /docs/_static/quick_start_pinout_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/docs/_static/quick_start_pinout_diagram.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../../pinout/")) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = "pinout" 22 | copyright = "2021, John Newall" 23 | author = "John Newall" 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = "0.0.20" 27 | 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | "sphinx.ext.autodoc", 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ["_templates"] 40 | 41 | # List of patterns, relative to source directory, that match files and 42 | # directories to ignore when looking for source files. 43 | # This pattern also affects html_static_path and html_extra_path. 44 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 45 | 46 | 47 | # -- Options for HTML output ------------------------------------------------- 48 | 49 | # The theme to use for HTML and HTML Help pages. See the documentation for 50 | # a list of builtin themes. 51 | # 52 | html_theme = "sphinx_rtd_theme" 53 | 54 | # Add any paths that contain custom static files (such as style sheets) here, 55 | # relative to this directory. They are copied after the builtin static files, 56 | # so a file named "default.css" will overwrite the builtin "default.css". 57 | html_static_path = ["_static"] 58 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pinout documentation master file, created by 2 | sphinx-quickstart on Tue Mar 16 00:16:18 2021. 3 | 4 | **pinout** 5 | ========== 6 | 7 | SVG diagram creation from Python code - **pinout** provides an easy method to create pin-out diagrams for electronic hardware. 8 | 9 | .. figure:: _static/demo_pinout_diagram.* 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Contents: 14 | 15 | pages/install 16 | pages/tutorial 17 | pages/KiCad_integration 18 | pages/modules 19 | pages/customise 20 | pages/resources 21 | 22 | 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | -------------------------------------------------------------------------------- /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% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/pages/annotation.rst: -------------------------------------------------------------------------------- 1 | Annotations 2 | =========== 3 | 4 | .. currentmodule:: pinout.components.annotation 5 | 6 | Annotation 7 | ---------- 8 | 9 | .. autoclass:: AnnotationLabel 10 | :show-inheritance: 11 | 12 | An alternative method to 'label' a diagram, suitable for highlighting hardware details. 13 | 14 | It is likely the body, leaderline, and target will all require customisation to best suit specific usages. Several methods of customisation are possible: 15 | 16 | **diagram-wide customisations**: 17 | 18 | - Over-ride dictionary settings in pinout.config.annotation 19 | - Over-ride default annotation body, leaderline, and target classes 20 | 21 | **instance specific customisations**: 22 | 23 | - Supply a dictionary of arguments to body, content, leaderline, and target attributes. These override config.annotation settings 24 | - provide an alternative component instance to body, content, leaderline, and target attributes. 25 | 26 | :param content: [description] 27 | :type content: [type] 28 | :param body: [description], defaults to None 29 | :type body: [type], optional 30 | :param leaderline: [description], defaults to None 31 | :type leaderline: [type], optional 32 | :param target: [description], defaults to None 33 | :type target: [type], optional 34 | 35 | 36 | Body 37 | ---- 38 | .. autoclass:: Body 39 | :show-inheritance: 40 | 41 | 42 | Content 43 | ------- 44 | .. autoclass:: Content 45 | :show-inheritance: 46 | 47 | Content can be provided as a string, list, dictionary, or component instance. Strings are presented as a single line. Entries of a list present as lines of text. If a dictionary is provided it updates the default config settings and expects the 'content' attribute to be a list. 48 | 49 | 50 | Leaderline 51 | ---------- 52 | 53 | .. autoclass:: Leaderline 54 | :show-inheritance: 55 | 56 | Target 57 | ------ 58 | 59 | .. autoclass:: Target 60 | :show-inheritance: -------------------------------------------------------------------------------- /docs/pages/config.rst: -------------------------------------------------------------------------------- 1 | .. _Config: 2 | 3 | Config 4 | ====== 5 | 6 | Components with a graphical representation have a variety of configuration attributes that affect their appearance. These attributes can be modified at several location whilst scripting. 7 | 8 | 9 | Default values 10 | -------------- 11 | 12 | These attributes are stored as Python dictionaries in the **config** module. 13 | 14 | A complete set of all default configurations can be duplicated for reference from the command line:: 15 | 16 | py -m pinout.manager --duplicate config 17 | 18 | # expected response: 19 | # >>> config.py duplicated. 20 | 21 | Amending the default configurations can be done by replacing or updating any of the dictionaries with plain Python:: 22 | 23 | from pinout import config 24 | config.pinlabel["body"].update({"width": 120}) 25 | 26 | # All pin-label bodies will now default to 120 wide 27 | 28 | 29 | Instance attributes 30 | ------------------- 31 | 32 | PinLabels and Annotations accept a dictionary of configurations for some attributes. These values are used to update the default settings for that single instance. This is ideal when small alterations are required for a low number items:: 33 | 34 | from pinout.core import Diagram 35 | from pinout.components.pinlabel import PinLabel 36 | 37 | diagram = Diagram(1200, 675, "pinout") 38 | diagram.add( 39 | PinLabel( 40 | x=30, 41 | y=30, 42 | tag="sm-label", 43 | body={"width": 40}, 44 | ) 45 | ) 46 | 47 | -------------------------------------------------------------------------------- /docs/pages/customise.rst: -------------------------------------------------------------------------------- 1 | Customisation 2 | ============= 3 | 4 | Documentation conveys not just information about its subject but also the personality of the owner. In the context of product/electronics documentation this 'personality' may be of the hardware itself, the creator of the hardware, or company that creates/distributes/sells the hardware. 5 | 6 | Many *pinout* components have facility for customisation and easy integration into a diagram. 7 | 8 | 9 | Stylesheet 10 | ---------- 11 | 12 | The first stop for altering a diagram's appearance is to edit its stylesheet. Presentation styles are all controlled here. If you are coming with some knowlege of CSS for web, be aware SVG has some different names for rules! 13 | 14 | 15 | Component config 16 | ---------------- 17 | 18 | Altering the geometry of default components can be done by changing, or providing new, config values. See the :ref:`Config` section for more details 19 | 20 | Building components 21 | ------------------- 22 | 23 | It is possible to build new components and integrate them into *pinout*. 24 | 25 | Existing components are split into parts to allow easier overriding. Where a universal change is desired this maybe the best approach - until a guide is written for this, reviewing the package code (hosted on `github `_) is recommended. 26 | 27 | Insertion of customised elements into some component instances is also possible and suitable where only small changes, or multiple variants, of a component are required in a single diagram. 28 | 29 | **PinLabel** has 'leaderline' and 'body' attributes. These accept either a dictionary of values (see :ref:`Config`) or an instance that will be used in preference to the equivalent default component. 30 | 31 | **Annotation** has 'leaderline', 'body', and 'target' atttributes that accept new component instances. 32 | 33 | **An example**: The following code can be added to the quick_start script ('pinout_diagram.py') for quick and easy testing:: 34 | 35 | # Import required modules and class at top of the script 36 | from pinout.components import pinlabel 37 | from pinout.core import Path 38 | 39 | # Create a new pin-label body class 40 | # and override the render function 41 | class SkewLabelBody(pinlabel.Body): 42 | def render(self): 43 | skew = 3 44 | path_def = " ".join( 45 | [ 46 | f"M {self.x + skew} {self.y -self.height/2}", 47 | f"l {self.width} 0", 48 | f"l {-skew*2} {self.height}", 49 | f"l {-self.width} 0" "Z", 50 | ] 51 | ) 52 | body = Path(path_definition=path_def, tag="label__body") 53 | return body.render() 54 | 55 | 56 | # Insert the following before the export statement 57 | # Add an instance of the custom pin-label body to the diagram 58 | diagram.add( 59 | pinlabel.PinLabel( 60 | content="SKEWED", 61 | x=50, 62 | y=50, 63 | body=SkewLabelBody(70, 0, 100, 30), 64 | ) 65 | ) 66 | -------------------------------------------------------------------------------- /docs/pages/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Install and Quickstart 4 | ====================== 5 | 6 | 7 | Install 8 | ------- 9 | 10 | Using a virtual environment is recommended; Start by installing the *pinout* package from PyPi:: 11 | 12 | pip install pinout 13 | 14 | # Or upgrade to the latest version 15 | pip install --upgrade pinout 16 | 17 | *pinout* exports diagrams in SVG format and can be used with with no further package installations. With the additional installation of CairoSVG, diagrams can also be exported in PNG, PDF, and PS formats:: 18 | 19 | pip install cairosvg 20 | 21 | .. warning:: 22 | CairoSVG has non-Python dependencies that will require installing if not present. Installation varies depending on platform and may feel like quite a journey for non-technical users. Information regarding installation requirements can be found in the `CairoSVG `_ and `Cairo Graphics Library `_ websites. 23 | 24 | For Windows users `installing GTK3 via MSYS2 `_ may be the most reliable method to install all requirements (Don't forget to add the correct GTK bin folder to the system PATH environmental variable!) 25 | 26 | 27 | .. _quickstart: 28 | 29 | Quickstart 30 | ---------- 31 | 32 | .. image:: /_static/quick_start_pinout_diagram.* 33 | 34 | This guide makes use of a hardware image, stylesheet, data file, and a Python script. Sample files are included with the package and can be duplicated for your use. Open a command line (with enabled virtual environment if you are using one) in the location you plan to work and enter the following 35 | 36 | .. note:: 37 | Depending on your operating system the command to invoke Python may differ. This guide uses Windows default method. Exchanging 'py' for 'python' or similar may be required for examples to work on other systems. 38 | 39 | :: 40 | 41 | py -m pinout.manager --duplicate quick_start 42 | 43 | # expected output: 44 | # >>> data.py duplicated. 45 | # >>> hardware.png duplicated. 46 | # >>> pinout_diagram.py duplicated. 47 | # >>> styles.css duplicated. 48 | 49 | 50 | Generating the final SVG graphic is done from the command line:: 51 | 52 | py -m pinout.manager --export pinout_diagram.py diagram.svg 53 | 54 | If everything is correctly configured the newly created file 'diagram.svg' can be viewed in a browser and should look identical to the diagram pictured here. 55 | 56 | .. warning:: 57 | **Not all SVG viewers are build equal!** 58 | *pinout* uses SVG format 'under-the-hood' and can also output diagrams in this format. SVG is well supported by modern browsers and applications that *specialize* in rendering SVG such as InkScape. If a *pinout* diagram displays unexpected results (eg. mis-aligned text) cross-check by viewing the diagram in an up-to-date browser (eg. Firefox or Chrome) as an initial trouble-shooting step. 59 | 60 | Once you have installed the *pinout* package explore its main features in the :ref:`tutorial`. -------------------------------------------------------------------------------- /docs/pages/integrated_circuits.rst: -------------------------------------------------------------------------------- 1 | Integrated circuits 2 | =================== 3 | 4 | .. currentmodule:: pinout.components.integrated_circuits 5 | 6 | *pinout* can generate simple integrated circuit (IC) graphics - Ideal for documenting stand-alone IC components. 7 | 8 | DIP and QFP components can be utilised in a diagram in the same way as an image. However helper functions also exists for easy application of labels to these component. 9 | 10 | 11 | Labelled QFP graphic 12 | -------------------- 13 | 14 | .. autofunction:: labelled_qfn 15 | 16 | :param labels: List of label data 17 | :type labels: list 18 | :param length: length of the IC sides (including legs), defaults to 160 19 | :type length: int, optional 20 | :param label_start: Offset of the first label from the first pin, defaults to (100, 20) 21 | :type label_start: tuple, optional 22 | :param label_pitch: Offest between each label row, defaults to (0, 30) 23 | :type label_pitch: tuple, optional 24 | :return: IC graphic with pinlabels applied 25 | :rtype: SVG markup 26 | 27 | 28 | Labelled DIP graphic 29 | -------------------- 30 | 31 | .. autofunction:: labelled_dip 32 | 33 | :param labels: List of label data 34 | :type labels: list 35 | :param width: Width of IC (includes legs), defaults to 100 36 | :type width: int, optional 37 | :param height: Height of IC (includes inset), defaults to 160 38 | :type height: int, optional 39 | :param label_start_x: Offset in x-axis of first label from first pin, defaults to 100 40 | :type label_start_x: int, optional 41 | :param label_pitch: Offest between each label row, defaults to (0, 30) 42 | :type label_pitch: tuple, optional 43 | :return: IC graphic with pinlabels applied 44 | :rtype: SVG markup 45 | 46 | 47 | Dual in-line package (DIP) 48 | -------------------------- 49 | 50 | .. autoclass:: DIP 51 | :show-inheritance: 52 | 53 | :param pin_count: Total number of pins on the integrated circuit 54 | :type pin_count: int 55 | :param width: width of the graphic, including body and legs 56 | :type width: int 57 | :param height: height of the graphic, including body and legs 58 | :type height: int 59 | 60 | Dimensions can be modified to depict a variety of IC types, eg SOIC and TSOP. 61 | 62 | .. autoproperty:: pin_coords 63 | 64 | :param index: Pin number (starts at 1) 65 | :type index: int 66 | :param rotate: If true, includes component rotation in the calculation, defaults to True 67 | :type rotate: bool, optional 68 | :return: coordinates of the pin relative to the IC's origin 69 | :rtype: namedtuple (x,y) 70 | 71 | 72 | Quad flat package (QFP) 73 | ----------------------- 74 | 75 | .. autoclass:: QFP 76 | :show-inheritance: 77 | 78 | :param pin_count: Total number of pins on the integrated circuit 79 | :type pin_count: int 80 | :param length: length of the QFP sides 81 | :type length: int 82 | 83 | Dimensions can be modified to depict a variety of 'quad' IC types. 84 | 85 | .. autoproperty:: pin_coords 86 | 87 | :param index: Pin number (starts at 1) 88 | :type index: int 89 | :param rotate: If true, includes component rotation in the calculation, defaults to True 90 | :type rotate: bool, optional 91 | :return: coordinates of the pin relative to the IC's origin 92 | :rtype: namedtuple (x,y) -------------------------------------------------------------------------------- /docs/pages/layout.rst: -------------------------------------------------------------------------------- 1 | Layout 2 | ====== 3 | 4 | .. currentmodule:: pinout.components.layout 5 | 6 | Diagram 7 | -------- 8 | 9 | .. autoclass:: Diagram 10 | :show-inheritance: 11 | 12 | :param width: width of diagram 13 | :type width: int 14 | :param height: height of diagram 15 | :type height: int 16 | :param tag: CSS class applied to diagram, defaults to None 17 | :type tag: string (must comply to CSS naming rules), optional 18 | 19 | .. automethod:: Diagram.add_stylesheet 20 | 21 | Pinout relies on cascading-style-sheet (CSS) rules to control presentation attributes of components. 22 | 23 | The path attribute is dependent on whether the styles are linked or embedded. When linked, the path is relative to the exported file. When embedded the path is relative to the diagram script file. 24 | 25 | :param path: Path to stylesheet file 26 | :type path: string 27 | :param embed: embed stylesheet in exported file, defaults to True 28 | :type embed: bool, optional 29 | 30 | 31 | .. automethod:: Diagram.render 32 | 33 | :return: SVG markup 34 | :rtype: string 35 | 36 | Panel 37 | ----- 38 | .. autoclass:: Panel 39 | :show-inheritance: 40 | 41 | The basic building block to control layout (grouping and location) of components that make up a complete diagram document. The Panel component renders two rectangles - and outer and inner rectangle - behind all child components to assist with graphical styling. 42 | 43 | The inset attribute controls dimensions of the 'inner rectangle'. All children are aligned relative to the inset coordinate (x1, y1). 44 | 45 | The inner dimensions can be accessed via the properties ``Panel.inset_width`` and ``Panel.inset_height``. 46 | 47 | :param width: Width of component 48 | :type width: int 49 | :param height: Height of component 50 | :type height: int 51 | :param inset: Inset of inner dimensions, defaults to None 52 | :type inset: Tuple (x1, y1, x2, y2), optional 53 | -------------------------------------------------------------------------------- /docs/pages/leaderline.rst: -------------------------------------------------------------------------------- 1 | Leaderlines 2 | =========== 3 | 4 | .. currentmodule:: pinout.components.leaderline 5 | 6 | 7 | Leaderline 8 | ---------- 9 | .. autoclass:: Leaderline 10 | :show-inheritance: 11 | 12 | :param direction: 2 letter code, defaults to "hh" 13 | :type direction: str, optional 14 | 15 | The leaderline connects an origin and destination point. Route taken is controlled with a *direction* argument where the first character dictates the start direction and the second character the end direction: 16 | 17 | - **vh**: vertical , horizontal 18 | - **hv**: horizontal , vertical 19 | - **hh**: horizontal , horizontal 20 | - **vv**: vertical , vertical 21 | 22 | 23 | .. automethod:: Leaderline.end_points 24 | 25 | The end_point method takes two components as arguments and returns coordinates that are aligned with the centre coordinates of the relevant side. 26 | 27 | :param origin: origin component 28 | :type origin: component with width and height attributes and bounding_coords method 29 | :param destination: destination component 30 | :type destination: component with width and height attributes and bounding_coords method 31 | :return: coordinates of start and end points 32 | :rtype: Tuple ((ox, oy), (dx, dy)) 33 | 34 | Curved 35 | ------ 36 | .. autoclass:: Curved 37 | :show-inheritance: 38 | 39 | 40 | Angled 41 | ------ 42 | .. autoclass:: Angled 43 | :show-inheritance: 44 | 45 | 46 | Straight 47 | -------- 48 | .. autoclass:: Straight 49 | :show-inheritance: 50 | -------------------------------------------------------------------------------- /docs/pages/legend.rst: -------------------------------------------------------------------------------- 1 | Legend 2 | ====== 3 | 4 | .. currentmodule:: pinout.components.legend 5 | 6 | Legend 7 | ------ 8 | 9 | .. autoclass:: Legend 10 | 11 | *Note*: *pinout* does not calculate text widths. A manually provided width may be required to ensure text remains enclosed within the legend. 12 | 13 | :param data: [description] 14 | :type data: [type] 15 | :param max_height: [description], defaults to None 16 | :type max_height: [type], optional 17 | 18 | 19 | Swatch 20 | ------ 21 | 22 | .. autoclass:: Swatch 23 | 24 | :param width: Width of swatch, defaults to None 25 | :type width: int, optional 26 | :param height: Height of swatch, defaults to None 27 | :type height: int, optional 28 | 29 | 30 | Entry 31 | ----- 32 | 33 | .. autoclass:: LegendEntry 34 | 35 | The swatch attribute accepts either a dictionary of Swatch attributes or a Swatch instance. Swatch styling (ie filling with color) is done via CSS and should reference the LegendEntry class(es). 36 | 37 | :param content: Text displayed in entry 38 | :type content: [type] 39 | :param width: Width of entry, defaults to None 40 | :type width: int, optional 41 | :param height: height of entry, defaults to None 42 | :type height: int, optional 43 | :param swatch: Graphical icon included in entry, defaults to None 44 | :type swatch: dict or Swatch, optional 45 | 46 | -------------------------------------------------------------------------------- /docs/pages/manager.rst: -------------------------------------------------------------------------------- 1 | Manager 2 | ======= 3 | 4 | The manager module provides various functions to assist *pinout* create diagrams. For users, Manager is primarily accessed via the command-line for the following. 5 | 6 | Duplicate quick_start files 7 | --------------------------- 8 | 9 | A fast way to get started exploring *pinout* is by trying out the quick_start diagram that is featured in the tutorial. Required files can be duplicated from the *pinout* package via command line:: 10 | 11 | py -m pinout.manager --duplicate quick_start 12 | 13 | # expected output: 14 | # >>> data.py duplicated. 15 | # >>> hardware.png duplicated. 16 | # >>> pinout_diagram.py duplicated. 17 | # >>> styles.css duplicated. 18 | 19 | *-d* works as a short-hand version of *--duplicate* 20 | 21 | Export an SVG diagram 22 | --------------------- 23 | 24 | Once a diagram has been documented it can be exported to SVG format via the command-line. Two arguments must be supplied. A path to the diagram python script and destination path including filename:: 25 | 26 | >>> py pinout.manager --export pinout_diagram.py my_diagram.svg 27 | 28 | # expected response: 29 | # 'my_diagram.svg' exported successfully. 30 | 31 | # Example where pinout.Diagram instance is named 'board_x_diagram' 32 | >>> py pinout.manager --export pinout_diagram.py my_diagram.svg board_x_diagram 33 | 34 | Details to note: 35 | 36 | - *--export* can be expressed as a single letter *-e* 37 | - An *--overwrite* (*-o*) can also be included to overwrite an existing file 38 | - if the instance name is not 'diagram' the alternative name can be added as as third argument 39 | 40 | Export in other formats 41 | ----------------------- 42 | 43 | With the addition of CairoSVG *pinout* is able to export to PNG, PDF, and PS formats. Installation is done via pip:: 44 | 45 | pip install cairosvg 46 | 47 | .. note:: 48 | 49 | CairoSVG has it's own (non-Python) dependencies. See :ref:`Install` for more details. 50 | 51 | Once these dependencies have been installed replace the filename suffix to export in the desired format:: 52 | 53 | # Export as png 54 | >>> py pinout.manager --export pinout_diagram.py my_diagram.png 55 | 56 | # Export as pdf 57 | >>> py pinout.manager --export pinout_diagram.py my_diagram.pdf 58 | 59 | # Export as ps 60 | >>> py pinout.manager --export pinout_diagram.py my_diagram.ps 61 | 62 | 63 | Generate a cascading stylesheet 64 | ------------------------------- 65 | 66 | Provided with a diagram file, the manager can extract components and tags, then export a stylesheet based on this data to assist with styling. The resulting stylesheet can then be further edited or a second stylesheet created to supplement the default styles:: 67 | 68 | >>> py pinout.manager --css pinout_diagram.py diagram_styles.css 69 | 70 | # expected response: 71 | # Stylesheet created: 'diagram_styles.css' 72 | 73 | As with exporting an SVG, the *-o* flag can be used to overwrite and existing file. Note, there is no short-hand for the *-css* flag. -------------------------------------------------------------------------------- /docs/pages/modules.rst: -------------------------------------------------------------------------------- 1 | .. _modules: 2 | 3 | Modules 4 | ======= 5 | 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :caption: Contents: 10 | 11 | manager 12 | config 13 | core 14 | layout 15 | pinlabel 16 | leaderline 17 | annotation 18 | legend 19 | text 20 | integrated_circuits 21 | -------------------------------------------------------------------------------- /docs/pages/pinlabel.rst: -------------------------------------------------------------------------------- 1 | Pin Labels 2 | ========== 3 | 4 | .. currentmodule:: pinout.components.pinlabel 5 | 6 | Base 7 | ---- 8 | .. autoclass:: Base 9 | :show-inheritance: 10 | 11 | :param content: Text displayed in label, defaults to "" 12 | :type content: str, optional 13 | :param x: position of label on x-axis , defaults to 0 14 | :type x: int, optional 15 | :param y: position of label on y-axis, defaults to 0 16 | :type y: int, optional 17 | :param tag: categorise the label - applied as a CSS class, defaults to None 18 | :type tag: str (CSS name compliant), optional 19 | :param body: replace or configure the default body component, defaults to None 20 | :type body: dict or pinlabel.Body instance, optional 21 | :param leaderline: replace or configure the default leaderline component, defaults to None 22 | :type leaderline: dict or pinlabel.Leaderline, optional 23 | 24 | 25 | 26 | PinLabel 27 | -------- 28 | .. autoclass:: PinLabel 29 | :show-inheritance: 30 | 31 | See Base for details of this component. 32 | 33 | Body 34 | ---- 35 | .. autoclass:: Body 36 | :show-inheritance: 37 | 38 | :param x: position of label on x-axis 39 | :type x: int 40 | :param y: position of label on y-axis 41 | :type y: int 42 | :param width: Width of label body 43 | :type width: int 44 | :param height: Height of label body 45 | :type height: int 46 | :param corner_radius: Corner radius of label body, defaults to 0 47 | :type corner_radius: int, optional 48 | 49 | 50 | Leaderline 51 | ---------- 52 | .. autoclass:: Leaderline 53 | :show-inheritance: 54 | 55 | :param lline: Override configuration or replace the pinlabel's leaderline. 56 | :type lline: dict of leaderline attributes or replacement Leaderline instance 57 | 58 | PinLabelGroup 59 | ------------- 60 | .. autoclass:: PinLabelGroup 61 | :show-inheritance: 62 | 63 | This is the recommended method of adding pin labels to a diagram. Locate the PinLabelSet by setting *x* and *y* attributes. 64 | 65 | Pitch is the distance, in pixels, between each pin of the header. (0, 30) steps 0px right and 30px down for each pin. (30, 0) creates a horizontal header. (-30, 0) creates a horizontal header in the reverse direction. This can be useful for 'stacking' rows in reversed order to avoid leader-lines overlapping. 66 | 67 | :param x: x-coordinate of the first pin in the header 68 | :type x: int 69 | :param y: y-coordinate of the first pin in the header 70 | :type y: int 71 | :param pin_pitch: Distance between pins in the header 72 | :type pin_pitch: tuple: (x,y) 73 | :param label_start: Offset of the first label from the first pin 74 | :type label_start: tuple: (x,y) 75 | :param label_pitch: Distance between each row of labels 76 | :type label_pitch: tuple: (x,y) 77 | :param labels: Label data 78 | :type labels: List 79 | :param leaderline: Leaderline customisations, defaults to None 80 | :type leaderline: dict or Leaderline object, optional 81 | :param body: Label body customisations, defaults to None 82 | :type body: dict or LabelBody object, optional 83 | -------------------------------------------------------------------------------- /docs/pages/resources.rst: -------------------------------------------------------------------------------- 1 | Resources 2 | ========= 3 | 4 | Every *pinout* diagram has the fundamental requirement of an image to enhance with graphical additions. This prerequisite can be sizable barrier to creating the clearest diagram possible. 5 | 6 | During *pinout* development several image create methods have been investigated. Community members have also reached out and generously provided feedback and further options to tryout. 7 | 8 | Documented here is a list of what was tried during *pinout* development and some community input (some tried, some on my list to try out). The intent is to revisit all option and write-up some reviews and process notes. 9 | 10 | + Export from KiCad 11 | + Create from scratch 12 | + InkScape 13 | + Illustrator 14 | + With exported elements from KiCad 15 | + Photograph 16 | + https://github.com/yaqwsx/PcbDraw 17 | + fritzing 18 | -------------------------------------------------------------------------------- /docs/pages/text.rst: -------------------------------------------------------------------------------- 1 | Text 2 | ==== 3 | 4 | .. currentmodule:: pinout.components.text 5 | 6 | TextBlock 7 | --------- 8 | 9 | .. autoclass:: TextBlock 10 | :show-inheritance: 11 | 12 | The TextBlock accepts either a string or list for content. Each list entry is presented as a line of text. Where a string is provided, it is converted to a list by splitting on new-line characters ('\\n') and stripping whitespace from start and end of each line created. 13 | 14 | .. note:: 15 | 16 | *pinout* cannot detect text character size! Consequently care should be taken to ensure text does not render outside expected boundaries. 17 | 18 | :param content: Text to be displayed 19 | :type content: String or List 20 | :param line_height: Distance between lines, defaults to None 21 | :type line_height: int, optional -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2 -------------------------------------------------------------------------------- /pinout.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /pinout/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/pinout/__init__.py -------------------------------------------------------------------------------- /pinout/components/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/pinout/components/__init__.py -------------------------------------------------------------------------------- /pinout/components/annotation.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from pinout import core 3 | from pinout.components.text import TextBlock 4 | from pinout.components import leaderline as lline 5 | from pinout.components.layout import Group 6 | 7 | from pinout import config 8 | 9 | 10 | class Leaderline(lline.Curved): 11 | pass 12 | 13 | 14 | class Target(core.Rect): 15 | pass 16 | 17 | 18 | class Body(core.Rect): 19 | pass 20 | 21 | 22 | class Content(TextBlock): 23 | pass 24 | 25 | 26 | class AnnotationLabel(Group): 27 | """Annotation style label.""" 28 | 29 | def __init__( 30 | self, 31 | content=None, 32 | body=None, 33 | leaderline=None, 34 | target=None, 35 | **kwargs, 36 | ): 37 | self._content = None 38 | self._body = None 39 | self._leaderline = None 40 | self._target = None 41 | 42 | super().__init__(**kwargs) 43 | self.update_config(config.annotation) 44 | self.add_tag(self.config["tag"]) 45 | 46 | self.leaderline = leaderline 47 | self.body = body 48 | self.target = target 49 | # content relied on body - must come after 50 | self.content = content 51 | 52 | # Route leaderline once other elements exist 53 | self._leaderline.route(self.target, self.body) 54 | 55 | self.add(self.leaderline) 56 | self.add(self.target) 57 | self.add(self.body) 58 | self.add(self.content) 59 | 60 | @property 61 | def content(self): 62 | return self._content 63 | 64 | @content.setter 65 | def content(self, content): 66 | content = copy.deepcopy(content or {}) 67 | config = self.config["content"] 68 | # Parse content: str > list > dict > TextBlock 69 | if type(content) is str: 70 | content = [content] 71 | if type(content) is list: 72 | content = { 73 | "content": content, 74 | "x": self.body.x + self.body.width / 2, 75 | "y": (self.body.y + self.body.height / 2) 76 | - (config["line_height"] * (len(content) - 1) / 2 * self.scale.y), 77 | } 78 | if isinstance(content, dict): 79 | config.update(content) 80 | content = Content(**config) 81 | content.add_tag(self.config["content"]["tag"]) 82 | content.scale = self.scale 83 | self._content = content 84 | 85 | @property 86 | def leaderline(self): 87 | return self._leaderline 88 | 89 | @leaderline.setter 90 | def leaderline(self, leaderline): 91 | leaderline = copy.deepcopy(leaderline or {}) 92 | if isinstance(leaderline, dict): 93 | leaderline_config = self.config["leaderline"] 94 | leaderline_config.update(leaderline) 95 | leaderline = Leaderline(**leaderline_config) 96 | leaderline.add_tag(self.config["leaderline"]["tag"]) 97 | self._leaderline = leaderline 98 | 99 | @property 100 | def target(self): 101 | return self._target 102 | 103 | @target.setter 104 | def target(self, target): 105 | target = copy.deepcopy(target or {}) 106 | if isinstance(target, dict): 107 | target_config = self.config["target"] 108 | target_config.update(target) 109 | target = Target(**target_config) 110 | target.add_tag(self.config["target"]["tag"]) 111 | self._target = target 112 | 113 | @property 114 | def body(self): 115 | return self._body 116 | 117 | @body.setter 118 | def body(self, body): 119 | body = copy.deepcopy(body or {}) 120 | if isinstance(body, dict): 121 | body_config = self.config["body"] 122 | body_config.update(body) 123 | body = Body(**body_config) 124 | body.add_tag(self.config["body"]["tag"]) 125 | self._body = body 126 | -------------------------------------------------------------------------------- /pinout/components/layout.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from pinout import templates, config 3 | from pinout.core import ( 4 | Layout, 5 | StyleSheet, 6 | Group, 7 | SvgShape, 8 | Rect, 9 | BoundingCoords, 10 | ) 11 | 12 | 13 | class Diagram(Layout): 14 | """Basis of a pinout diagram""" 15 | 16 | def __init__(self, width, height, tag=None, **kwargs): 17 | self.width = width 18 | self.height = height 19 | super().__init__(tag=tag, **kwargs) 20 | self.add(SvgShape(width=width, height=height)) 21 | 22 | def add_stylesheet(self, path, embed=False): 23 | """Add a stylesheet to the diagram""" 24 | self.children.insert(0, StyleSheet(path, embed)) 25 | 26 | def render(self): 27 | """Render children into an tag.""" 28 | 29 | # Warn user if no styles have been added 30 | stylesheets = self.find_children_by_type(self, StyleSheet) 31 | if not stylesheets: 32 | print( 33 | """ 34 | ********************* 35 | No stylesheet is attached, the diagram may not appear as expected! 36 | Generate one automatically with: 37 | >>> py -m pinout.manager --css styles.css 38 | 39 | More info at: 40 | https://pinout.readthedocs.io/en/latest/pages/manager.html#generate-a-cascading-stylesheet 41 | ********************* 42 | """ 43 | ) 44 | 45 | tplt = templates.get("svg.svg") 46 | return tplt.render(svg=self) 47 | 48 | 49 | class Panel(Layout): 50 | def __init__(self, width, height, inset=None, **kwargs): 51 | """Assist with content grouping and positioning""" 52 | 53 | self.width = width 54 | self.height = height 55 | 56 | kwargs["config"] = kwargs.get("config", config.panel) 57 | 58 | super().__init__(**kwargs) 59 | 60 | inset = inset or self.config["inset"] 61 | self.inset = BoundingCoords(*inset) 62 | self.add_tag(config.panel["tag"]) 63 | 64 | # add a non-rendering shape so component 65 | # reports user set coordinates and dimensions 66 | self.add( 67 | SvgShape( 68 | x=-self.inset.x1, 69 | y=-self.inset.y1, 70 | width=width, 71 | height=height, 72 | ) 73 | ) 74 | 75 | # Offset component to align children with inner dimensions 76 | self.x += self.inset.x1 77 | self.y += self.inset.y1 78 | 79 | @property 80 | def inset_width(self): 81 | return self.width - (self.inset.x1 + self.inset.x2) 82 | 83 | @property 84 | def inset_height(self): 85 | return self.height - (self.inset.y1 + self.inset.y2) 86 | 87 | def render(self): 88 | """Panel renders children into a tag.""" 89 | 90 | self.children.insert( 91 | 0, 92 | Rect( 93 | width=self.width - (self.inset.x1 + self.inset.x2), 94 | height=self.height - (self.inset.y1 + self.inset.y2), 95 | tag=config.panel["inner"]["tag"], 96 | ), 97 | ) 98 | # Insert a rect filling the outer component dimensions 99 | self.children.insert( 100 | 0, 101 | Rect( 102 | x=-self.inset.x1, 103 | y=-self.inset.y1, 104 | width=self.width, 105 | height=self.height, 106 | tag=config.panel["outer"]["tag"], 107 | ), 108 | ) 109 | 110 | tplt = templates.get("group.svg") 111 | return tplt.render(group=self) 112 | 113 | 114 | class Diagram_2Columns(Diagram): 115 | def __init__(self, width, height, gutter, tag, **kwargs): 116 | self.gutter = gutter 117 | super().__init__(width, height, tag, **kwargs) 118 | 119 | # Add/override config 120 | self.config = config.diagram_presets 121 | self.update_config(kwargs.get("config", {})) 122 | 123 | self.panel_00 = self.add( 124 | Panel( 125 | x=0, 126 | y=0, 127 | width=width, 128 | height=height, 129 | tag=self.config["panel_00"]["tag"], 130 | config=self.config["panel_00"], 131 | ) 132 | ) 133 | self.panel_01 = self.panel_00.add( 134 | Panel( 135 | x=0, 136 | y=0, 137 | width=self.gutter, 138 | height=self.panel_00.inset_height, 139 | tag=self.config["panel_01"]["tag"], 140 | config=self.config["panel_01"], 141 | ) 142 | ) 143 | self.panel_02 = self.panel_00.add( 144 | Panel( 145 | x=self.gutter, 146 | y=0, 147 | width=self.panel_00.inset_width - self.gutter, 148 | height=self.panel_00.inset_height, 149 | tag=self.config["panel_02"]["tag"], 150 | config=self.config["panel_02"], 151 | ) 152 | ) 153 | 154 | 155 | class Diagram_2Rows(Diagram): 156 | def __init__(self, width, height, gutter, tag, **kwargs): 157 | self.gutter = gutter 158 | super().__init__(width, height, tag, **kwargs) 159 | 160 | # Add/override config 161 | self.config = config.diagram_presets 162 | self.update_config(kwargs.get("config", {})) 163 | 164 | self.add_tag(self.config["tag"]) 165 | 166 | self.panel_00 = self.add( 167 | Panel( 168 | x=0, 169 | y=0, 170 | width=width, 171 | height=height, 172 | tag=self.config["panel_00"]["tag"], 173 | config=self.config["panel_00"], 174 | ) 175 | ) 176 | self.panel_01 = self.panel_00.add( 177 | Panel( 178 | x=0, 179 | y=0, 180 | width=self.panel_00.inset_width, 181 | height=self.gutter, 182 | tag=self.config["panel_01"]["tag"], 183 | config=self.config["panel_01"], 184 | ) 185 | ) 186 | self.panel_02 = self.panel_00.add( 187 | Panel( 188 | x=0, 189 | y=self.gutter, 190 | width=self.panel_00.inset_width, 191 | height=self.panel_00.inset_height - self.gutter, 192 | tag=self.config["panel_02"]["tag"], 193 | config=self.config["panel_02"], 194 | ) 195 | ) 196 | -------------------------------------------------------------------------------- /pinout/components/legend.py: -------------------------------------------------------------------------------- 1 | from pinout import core, config 2 | from pinout.components.layout import Group 3 | 4 | 5 | class Swatch(Group): 6 | """Graphical icon for display in LegendEntry""" 7 | 8 | def __init__(self, width=None, height=None, **kwargs): 9 | super().__init__(**kwargs) 10 | self.update_config(config.legend["entry"]["swatch"]) 11 | width = width or self.config["width"] 12 | height = height or self.config["height"] 13 | 14 | # Rect aligned left hand edge, vertically centered around origin. 15 | shape = self.add(core.Rect(y=-height / 2, width=width, height=height)) 16 | self.add_tag("swatch") 17 | shape.add_tag("swatch__body") 18 | 19 | 20 | class LegendEntry(Group): 21 | """Legend entry comprised of a swatch and single line of text.""" 22 | 23 | def __init__( 24 | self, 25 | content, 26 | width=None, 27 | height=None, 28 | swatch=None, 29 | **kwargs, 30 | ): 31 | super().__init__(**kwargs) 32 | self.update_config(config.legend["entry"]) 33 | self.add_tag(self.config["tag"]) 34 | 35 | width = width or self.config["width"] 36 | height = height or self.config["height"] 37 | swatch = swatch or {} 38 | 39 | if isinstance(swatch, dict): 40 | swatch = Swatch(**swatch) 41 | 42 | self.add( 43 | core.SvgShape( 44 | width=width, 45 | height=height, 46 | ), 47 | ) 48 | 49 | swatch.y = height / 2 50 | swatch.x = (height - swatch.height) / 2 51 | self.add(swatch) 52 | 53 | self.add( 54 | core.Text( 55 | content, 56 | x=swatch.bounding_coords().x2 + swatch.x, 57 | y=self.height / 2, 58 | ) 59 | ) 60 | 61 | 62 | class Legend(Group): 63 | """Auto generate a legend component""" 64 | 65 | def __init__( 66 | self, 67 | data, 68 | max_height=None, 69 | **kwargs, 70 | ): 71 | super().__init__(**kwargs) 72 | self.update_config(config.legend) 73 | self.add_tag(self.config["tag"]) 74 | 75 | max_height = max_height or self.config["max_height"] 76 | 77 | entry_x = 0 78 | entry_y = 0 79 | for entry in data: 80 | 81 | if type(entry) is tuple: 82 | content, tag, *args = entry 83 | attrs = args[0] if len(args) > 0 else {} 84 | entry = LegendEntry(content, tag=tag, **attrs, scale=self.scale) 85 | 86 | self.add(entry) 87 | 88 | # Position entry in legend 89 | if max_height and entry_y + entry.height > max_height: 90 | entry_x = self.width 91 | entry_y = 0 92 | entry.x = entry_x 93 | entry.y = entry_y 94 | entry_y += entry.height 95 | -------------------------------------------------------------------------------- /pinout/components/pinlabel.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from pinout.core import SvgShape, Group, Rect, Text, BoundingCoords, Coords 3 | from pinout.components import leaderline as lline 4 | from pinout import config 5 | 6 | 7 | def pitch_generator(start, pitch): 8 | x = start[0] 9 | y = start[1] 10 | while True: 11 | yield (x, y) 12 | x += pitch[0] 13 | y += pitch[1] 14 | 15 | 16 | class Body(SvgShape): 17 | """Graphical shape that makes up the body of a pinlabel.""" 18 | 19 | def __init__(self, x, y, width, height, corner_radius=0, **kwargs): 20 | self.corner_radius = corner_radius 21 | super().__init__(x=x, y=y, width=width, height=height, **kwargs) 22 | 23 | def bounding_coords(self): 24 | # PinLabelBody origin is vertically centered 25 | return BoundingCoords( 26 | self.x, 27 | self.y - (self.height / 2), 28 | self.x + self.width, 29 | self.y + (self.height / 2), 30 | ) 31 | 32 | def render(self): 33 | body = Rect( 34 | x=self.x, 35 | y=self.y - (self.height / 2), 36 | width=self.width, 37 | height=self.height, 38 | corner_radius=self.corner_radius, 39 | ) 40 | body.add_tag(config.pinlabel["body"]["tag"]) 41 | return body.render() 42 | 43 | 44 | class Leaderline(lline.Curved): 45 | """Graphical line joining the label origin coordinates to the label body.""" 46 | 47 | pass 48 | 49 | 50 | class Base(Group): 51 | """Label component designed specifically for labelling pins.""" 52 | 53 | def __init__( 54 | self, 55 | content="", 56 | x=0, 57 | y=0, 58 | tag=None, 59 | body=None, 60 | leaderline=None, 61 | **kwargs, 62 | ): 63 | self.content = content 64 | self._leaderline = None 65 | self._body = None 66 | super().__init__(x, y, tag=tag, **kwargs) 67 | self.update_config(config.pinlabel) 68 | 69 | self.body = body 70 | self.leaderline = leaderline 71 | 72 | # Add leaderline and body reference into children 73 | self.add(self._body) 74 | 75 | # Add leaderline at render as it is replaced by pinlabelGroup!!! 76 | # Add SvgShape so pin label reports correct dimensions. 77 | self.add(SvgShape(x=self.leaderline.x, y=self.leaderline.x)) 78 | 79 | self.add_tag(config.pinlabel["tag"]) 80 | 81 | @property 82 | def body(self): 83 | return self._body 84 | 85 | @body.setter 86 | def body(self, body): 87 | # ensure instance data is unique 88 | body = copy.deepcopy(body or self.config["body"]) 89 | # Convert dict into body object 90 | if isinstance(body, dict): 91 | body_config = self.config["body"] 92 | body_config.update(body) 93 | body = Body(**body_config) 94 | # Add body config tag if not there 95 | body.add_tag(self.config["body"]["tag"]) 96 | self._body = body 97 | 98 | @property 99 | def leaderline(self): 100 | return self._leaderline 101 | 102 | @leaderline.setter 103 | def leaderline(self, leaderline): 104 | # ensure instance data is unique 105 | leaderline = copy.deepcopy(leaderline or self.config["leaderline"]) 106 | # Convert dict into leaderline object 107 | if isinstance(leaderline, dict): 108 | leaderline_config = self.config["leaderline"] 109 | leaderline_config.update(leaderline) 110 | leaderline = Leaderline(**leaderline_config) 111 | # Add leaderline config tag if not there 112 | leaderline.add_tag(self.config["leaderline"]["tag"]) 113 | self._leaderline = leaderline 114 | 115 | def render(self): 116 | # Add text content 117 | x = self.body.width / 2 + self.body.x 118 | y = self.body.y 119 | self.add( 120 | Text( 121 | self.content, 122 | x=x, 123 | y=y, 124 | tag=config.pinlabel["text"]["tag"], 125 | scale=self.scale, 126 | ) 127 | ) 128 | # Route leaderline 129 | self.leaderline.route(Rect(), self._body) 130 | self.add(self.leaderline) 131 | return super().render() 132 | 133 | 134 | class PinLabel(Base): 135 | pass 136 | 137 | 138 | class PinLabelGroup(Group): 139 | """Convenience class to place multiple rows of pin-labels on a pin-header.""" 140 | 141 | def __init__( 142 | self, 143 | x, 144 | y, 145 | pin_pitch, 146 | label_start, 147 | label_pitch, 148 | labels, 149 | leaderline=None, 150 | body=None, 151 | **kwargs, 152 | ): 153 | scale = Coords(*kwargs.pop("scale", (1, 1))) 154 | super().__init__(x=x, y=y, **kwargs) 155 | 156 | # Setup generators for row locations 157 | pin_coords = pitch_generator((0, 0), pin_pitch) 158 | label_coords = pitch_generator(label_start, label_pitch) 159 | 160 | for row in labels: 161 | row_group = self.add(Group()) 162 | for label in row: 163 | 164 | # If data is supplied convert to Label 165 | if type(label) is tuple: 166 | content, tag, *args = label 167 | attrs = args[0] if len(args) > 0 else {} 168 | 169 | # Set leaderline and body in attrs if supplied in either: 170 | # 1. data 171 | # 2. PinlabelGroup 172 | attrs["leaderline"] = attrs.get("leaderline", None) or leaderline 173 | attrs["body"] = attrs.get("body", None) or body 174 | 175 | label = PinLabel( 176 | content=content, 177 | scale=scale, 178 | **attrs, 179 | ) 180 | 181 | # -- label now exists -- # 182 | label.add_tag(tag) 183 | 184 | # Label follows another label in the row 185 | try: 186 | prev_label = row_group.children[-1] 187 | label.x = prev_label.x + prev_label.width * scale.x 188 | label.y = prev_label.y + prev_label.body.y * scale.y 189 | label.leaderline = lline.Straight(direction="hh") 190 | # Start of a new row 191 | except IndexError: 192 | label.x, label.y = next(pin_coords) 193 | x, y = next(label_coords) 194 | 195 | label.body.x += x - label.x * scale.x 196 | label.body.y += y - label.y * scale.y 197 | 198 | row_group.add(label) 199 | -------------------------------------------------------------------------------- /pinout/components/text.py: -------------------------------------------------------------------------------- 1 | from pinout import core, config 2 | from pinout.components.layout import Group 3 | 4 | 5 | class TextBlock(Group): 6 | """Multiline text component.""" 7 | 8 | def __init__(self, content, line_height=None, **kwargs): 9 | # initialise module attrs 10 | self._scale = core.Coords(*kwargs.pop("scale", (1, 1))) 11 | self._content = None 12 | super().__init__(**kwargs) 13 | self.update_config(config.textblock) 14 | self.line_height = line_height or self.config["line_height"] 15 | self.add_tag(self.config["tag"]) 16 | self.content = content 17 | 18 | @property 19 | def content(self): 20 | return self._content 21 | 22 | @content.setter 23 | def content(self, content): 24 | # Convert string to list 25 | if isinstance(content, str): 26 | content = [line.strip() for line in content.split("\n")] 27 | self._content = content 28 | 29 | def render(self): 30 | self.add_tag(self.config["tag"]) 31 | y = 0 32 | for text in self.content: 33 | self.add( 34 | core.Text(content=text, x=0, y=y, scale=self._scale, **self.config) 35 | ) 36 | y += self.line_height * self._scale.y 37 | 38 | return super().render() 39 | -------------------------------------------------------------------------------- /pinout/config.py: -------------------------------------------------------------------------------- 1 | ################################ 2 | # 3 | # Default component settings 4 | # 5 | ################################ 6 | 7 | # Pinlabel 8 | pinlabel = { 9 | "tag": "pinlabel", 10 | "body": { 11 | "x": 6, 12 | "y": 0, 13 | "width": 80, 14 | "height": 26, 15 | "corner_radius": 3, 16 | "tag": "pinlabel__body", 17 | }, 18 | "leaderline": { 19 | "direction": "hh", 20 | "tag": "pinlabel__leader", 21 | }, 22 | "text": { 23 | "tag": "pinlabel__text", 24 | }, 25 | } 26 | 27 | # Legend 28 | legend = { 29 | "max_height": None, 30 | "inset": (10, 10, 10, 10), 31 | "tag": "legend", 32 | "entry": { 33 | "width": 159, 34 | "height": 28, 35 | "swatch": { 36 | "width": 20, 37 | "height": 20, 38 | "tag": "swatch", 39 | }, 40 | "tag": "legendentry", 41 | }, 42 | } 43 | 44 | # TextBlock 45 | textblock = { 46 | "line_height": 22, 47 | "width": None, 48 | "height": None, 49 | "offset": (0, 0), 50 | "tag": "textblock", 51 | } 52 | 53 | # Annotation 54 | annotation = { 55 | "tag": "annotation", 56 | "content": { 57 | "tag": "annotation__text", 58 | "x": 28, 59 | "y": 17, 60 | "line_height": 16, 61 | }, 62 | "body": { 63 | "x": 40, 64 | "y": 29, 65 | "width": 250, 66 | "height": 50, 67 | "corner_radius": 25, 68 | "tag": "annotation__body", 69 | }, 70 | "target": { 71 | "x": -10, 72 | "y": -10, 73 | "width": 20, 74 | "height": 20, 75 | "corner_radius": 10, 76 | "tag": "annotation__target", 77 | }, 78 | "leaderline": { 79 | "direction": "vh", 80 | "tag": "annotation__leaderline", 81 | }, 82 | } 83 | 84 | # Panel 85 | panel = { 86 | "inset": (2, 2, 2, 2), 87 | "tag": "panel", 88 | "inner": {"tag": "panel__inner"}, 89 | "outer": {"tag": "panel__outer"}, 90 | } 91 | 92 | 93 | # Integrated circuit 94 | ic_dip = { 95 | "inset": (15, 0, 15, 0), 96 | "tag": "ic ic--dip", 97 | "body": { 98 | "x": 15, 99 | "y": 0, 100 | "corner_radius": 3, 101 | "tag": "ic__body", 102 | }, 103 | "leg": { 104 | "tag": "ic__leg", 105 | }, 106 | "polarity_mark": { 107 | "radius": 5, 108 | "tag": "polarity", 109 | }, 110 | } 111 | ic_qfp = { 112 | "inset": (15, 15, 15, 15), 113 | "pin_pitch": 30, 114 | "tag": "ic ic--qfp", 115 | "body": { 116 | "x": 15, 117 | "y": 15, 118 | "corner_radius": 3, 119 | "tag": "ic__body", 120 | }, 121 | "leg": { 122 | "tag": "ic__leg", 123 | }, 124 | "polarity_mark": { 125 | "radius": 5, 126 | "tag": "polarity", 127 | }, 128 | } 129 | 130 | # Diagram layout template presets 131 | diagram_presets = { 132 | "tag": "layout", 133 | "panel_00": { 134 | "inset": (2, 2, 2, 2), 135 | "tag": "panel", 136 | "inner": {"tag": "panel__inner"}, 137 | "outer": {"tag": "panel__outer"}, 138 | }, 139 | "panel_01": { 140 | "inset": (0.5, 0.5, 0.5, 0.5), 141 | "tag": "panel--main", 142 | "inner": {"tag": "panel__inner"}, 143 | "outer": {"tag": "panel__outer"}, 144 | }, 145 | "panel_02": { 146 | "inset": (0.5, 0.5, 0.5, 0.5), 147 | "tag": "panel--info", 148 | "inner": {"tag": "panel__inner"}, 149 | "outer": {"tag": "panel__outer"}, 150 | }, 151 | } 152 | 153 | 154 | ################################ 155 | # 156 | # KiCad footprint settings 157 | # 158 | ################################ 159 | kicad_6_footprints = { 160 | "version": 6, 161 | "layer": "User.1", 162 | "pinlabel": { 163 | "hide_fp_text_reference": True, 164 | "hide_fp_text_user": True, 165 | "value_offset": (0, 25), # (mm dimensions) 166 | }, 167 | "annotation": { 168 | "hide_fp_text_reference": True, 169 | "hide_fp_text_user": True, 170 | "value_offset": (25, 25), # (mm dimensions) 171 | }, 172 | "textblock": { 173 | "hide_fp_text_reference": True, 174 | "hide_fp_text_user": True, 175 | }, 176 | } 177 | kicad_5_footprints = { 178 | "version": 5, 179 | "layer": "Eco1.User", 180 | "pinlabel": { 181 | "hide_fp_text_reference": True, 182 | "hide_fp_text_user": True, 183 | "value_offset": (0, 25), # (mm dimensions) 184 | }, 185 | "annotation": { 186 | "hide_fp_text_reference": True, 187 | "hide_fp_text_user": True, 188 | "value_offset": (25, 25), # (mm dimensions) 189 | }, 190 | "textblock": { 191 | "hide_fp_text_reference": True, 192 | "hide_fp_text_user": True, 193 | }, 194 | } 195 | -------------------------------------------------------------------------------- /pinout/resources/config.py: -------------------------------------------------------------------------------- 1 | ################################ 2 | # 3 | # Default component settings 4 | # 5 | ################################ 6 | 7 | # Pinlabel 8 | pinlabel = { 9 | "tag": "pinlabel", 10 | "body": { 11 | "x": 6, 12 | "y": 0, 13 | "width": 80, 14 | "height": 26, 15 | "corner_radius": 3, 16 | "tag": "pinlabel__body", 17 | }, 18 | "leaderline": { 19 | "direction": "hh", 20 | "tag": "pinlabel__leader", 21 | }, 22 | "text": { 23 | "tag": "pinlabel__text", 24 | }, 25 | } 26 | 27 | # Legend 28 | legend = { 29 | "max_height": None, 30 | "inset": (10, 10, 10, 10), 31 | "tag": "legend", 32 | "entry": { 33 | "width": 159, 34 | "height": 28, 35 | "swatch": { 36 | "width": 20, 37 | "height": 20, 38 | "tag": "swatch", 39 | }, 40 | "tag": "legendentry", 41 | }, 42 | } 43 | 44 | # TextBlock 45 | textblock = { 46 | "line_height": 22, 47 | "width": None, 48 | "height": None, 49 | "offset": (0, 0), 50 | "tag": "textblock", 51 | } 52 | 53 | # Annotation 54 | annotation = { 55 | "tag": "annotation", 56 | "content": { 57 | "tag": "annotation__text", 58 | "x": 28, 59 | "y": 17, 60 | "line_height": 16, 61 | }, 62 | "body": { 63 | "x": 40, 64 | "y": 29, 65 | "width": 250, 66 | "height": 50, 67 | "corner_radius": 25, 68 | "tag": "annotation__body", 69 | }, 70 | "target": { 71 | "x": -10, 72 | "y": -10, 73 | "width": 20, 74 | "height": 20, 75 | "corner_radius": 10, 76 | "tag": "annotation__target", 77 | }, 78 | "leaderline": { 79 | "direction": "vh", 80 | "tag": "annotation__leaderline", 81 | }, 82 | } 83 | 84 | # Panel 85 | panel = { 86 | "inset": (2, 2, 2, 2), 87 | "tag": "panel", 88 | "inner": {"tag": "panel__inner"}, 89 | "outer": {"tag": "panel__outer"}, 90 | } 91 | 92 | 93 | # Integrated circuit 94 | ic_dip = { 95 | "inset": (15, 0, 15, 0), 96 | "tag": "ic ic--dip", 97 | "body": { 98 | "x": 15, 99 | "y": 0, 100 | "corner_radius": 3, 101 | "tag": "ic__body", 102 | }, 103 | "leg": { 104 | "tag": "ic__leg", 105 | }, 106 | "polarity_mark": { 107 | "radius": 5, 108 | "tag": "polarity", 109 | }, 110 | } 111 | ic_qfp = { 112 | "inset": (15, 15, 15, 15), 113 | "pin_pitch": 30, 114 | "tag": "ic ic--qfp", 115 | "body": { 116 | "x": 15, 117 | "y": 15, 118 | "corner_radius": 3, 119 | "tag": "ic__body", 120 | }, 121 | "leg": { 122 | "tag": "ic__leg", 123 | }, 124 | "polarity_mark": { 125 | "radius": 5, 126 | "tag": "polarity", 127 | }, 128 | } 129 | 130 | # Diagram layout template presets 131 | diagram_presets = { 132 | "tag": "layout", 133 | "panel_00": { 134 | "inset": (2, 2, 2, 2), 135 | "tag": "panel", 136 | "inner": {"tag": "panel__inner"}, 137 | "outer": {"tag": "panel__outer"}, 138 | }, 139 | "panel_01": { 140 | "inset": (0.5, 0.5, 0.5, 0.5), 141 | "tag": "panel--main", 142 | "inner": {"tag": "panel__inner"}, 143 | "outer": {"tag": "panel__outer"}, 144 | }, 145 | "panel_02": { 146 | "inset": (0.5, 0.5, 0.5, 0.5), 147 | "tag": "panel--info", 148 | "inner": {"tag": "panel__inner"}, 149 | "outer": {"tag": "panel__outer"}, 150 | }, 151 | } 152 | 153 | 154 | ################################ 155 | # 156 | # KiCad footprint settings 157 | # 158 | ################################ 159 | kicad_6_footprints = { 160 | "version": 6, 161 | "layer": "User.1", 162 | "pinlabel": { 163 | "hide_fp_text_reference": True, 164 | "hide_fp_text_user": True, 165 | "value_offset": (0, 25), # (mm dimensions) 166 | }, 167 | "annotation": { 168 | "hide_fp_text_reference": True, 169 | "hide_fp_text_user": True, 170 | "value_offset": (25, 25), # (mm dimensions) 171 | }, 172 | "textblock": { 173 | "hide_fp_text_reference": True, 174 | "hide_fp_text_user": True, 175 | }, 176 | } 177 | kicad_5_footprints = { 178 | "version": 5, 179 | "layer": "Eco1.User", 180 | "pinlabel": { 181 | "hide_fp_text_reference": True, 182 | "hide_fp_text_user": True, 183 | "value_offset": (0, 25), # (mm dimensions) 184 | }, 185 | "annotation": { 186 | "hide_fp_text_reference": True, 187 | "hide_fp_text_user": True, 188 | "value_offset": (25, 25), # (mm dimensions) 189 | }, 190 | "textblock": { 191 | "hide_fp_text_reference": True, 192 | "hide_fp_text_user": True, 193 | }, 194 | } 195 | -------------------------------------------------------------------------------- /pinout/resources/pinout_kicad_example.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/pinout/resources/pinout_kicad_example.zip -------------------------------------------------------------------------------- /pinout/resources/quick_start/data.py: -------------------------------------------------------------------------------- 1 | legend = [ 2 | ("Analog", "analog"), 3 | ("Communication", "comms"), 4 | ("Ground", "gnd"), 5 | ("GPIO", "gpio"), 6 | ("Touch", "touch"), 7 | ("Power", "pwr"), 8 | ("PWM", "pwm"), 9 | ] 10 | 11 | # Pinlabels 12 | 13 | left_header = [ 14 | [ 15 | ("0", "gpio"), 16 | ("A0", "analog"), 17 | ("MISO", "comms"), 18 | ], 19 | [ 20 | ("1", "gpio"), 21 | ("MOSI", "comms", {"body": {"x": 92}}), 22 | ], 23 | [ 24 | ("2", "gpio"), 25 | ("A1", "analog"), 26 | ("SCLK", "comms"), 27 | ], 28 | ] 29 | 30 | lower_header = [ 31 | [ 32 | ("3", "gpio"), 33 | ("PWM", "pwm"), 34 | ], 35 | [ 36 | ("4", "gpio"), 37 | ("A2", "analog"), 38 | ("TOUCH", "touch"), 39 | ], 40 | [ 41 | ("5", "gpio"), 42 | ("A3", "analog"), 43 | ], 44 | ] 45 | 46 | right_header = [ 47 | [ 48 | ("Vcc", "pwr"), 49 | ], 50 | [ 51 | ("GND", "gnd"), 52 | ], 53 | [ 54 | ("6", "gpio"), 55 | ("A4", "analog"), 56 | ("TOUCH", "touch"), 57 | ], 58 | ] 59 | 60 | 61 | # Text 62 | 63 | title = "Pinout Quick start" 64 | 65 | description = """Python tool kit to assist with 66 | documentation of electronic hardware. 67 | More info at pinout.readthedocs.io""" 68 | -------------------------------------------------------------------------------- /pinout/resources/quick_start/hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/pinout/resources/quick_start/hardware.png -------------------------------------------------------------------------------- /pinout/resources/quick_start/pinout_diagram.py: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # 3 | # Example script to build a 4 | # pinout diagram. Includes basic 5 | # features and convenience classes. 6 | # 7 | ########################################### 8 | 9 | from pinout.core import Group, Image 10 | from pinout.components.layout import Diagram_2Rows 11 | from pinout.components.pinlabel import PinLabelGroup, PinLabel 12 | from pinout.components.text import TextBlock 13 | from pinout.components import leaderline as lline 14 | from pinout.components.legend import Legend 15 | 16 | 17 | # Import data for the diagram 18 | import data 19 | 20 | # Create a new diagram 21 | # The Diagram_2Rows class provides 2 panels, 22 | # 'panel_01' and 'panel_02', to insert components into. 23 | diagram = Diagram_2Rows(1024, 576, 440, "diagram") 24 | 25 | # Add a stylesheet 26 | diagram.add_stylesheet("styles.css", embed=True) 27 | 28 | # Create a group to hold the pinout-diagram components. 29 | graphic = diagram.panel_01.add(Group(400, 42)) 30 | 31 | # Add and embed an image 32 | hardware = graphic.add(Image("hardware.png", embed=True)) 33 | 34 | # Measure and record key locations with the hardware Image instance 35 | hardware.add_coord("gpio0", 16, 100) 36 | hardware.add_coord("gpio3", 65, 244) 37 | hardware.add_coord("reset", 155, 244) 38 | hardware.add_coord("vcc", 206, 100) 39 | # Other (x,y) pairs can also be stored here 40 | hardware.add_coord("pin_pitch_v", 0, 30) 41 | hardware.add_coord("pin_pitch_h", 30, 0) 42 | 43 | # Create a single pin label 44 | graphic.add( 45 | PinLabel( 46 | content="RESET", 47 | x=hardware.coord("reset").x, 48 | y=hardware.coord("reset").y, 49 | tag="pwr", 50 | body={"x": 117, "y": 30}, 51 | leaderline={"direction": "vh"}, 52 | ) 53 | ) 54 | 55 | # Create pinlabels on the right header 56 | graphic.add( 57 | PinLabelGroup( 58 | x=hardware.coord("vcc").x, 59 | y=hardware.coord("vcc").y, 60 | pin_pitch=hardware.coord("pin_pitch_v", raw=True), 61 | label_start=(60, 0), 62 | label_pitch=(0, 30), 63 | labels=data.right_header, 64 | ) 65 | ) 66 | 67 | # Create pinlabels on the left header 68 | graphic.add( 69 | PinLabelGroup( 70 | x=hardware.coord("gpio0").x, 71 | y=hardware.coord("gpio0").y, 72 | pin_pitch=hardware.coord("pin_pitch_v", raw=True), 73 | label_start=(60, 0), 74 | label_pitch=(0, 30), 75 | scale=(-1, 1), 76 | labels=data.left_header, 77 | ) 78 | ) 79 | 80 | # Create pinlabels on the lower header 81 | graphic.add( 82 | PinLabelGroup( 83 | x=hardware.coord("gpio3").x, 84 | y=hardware.coord("gpio3").y, 85 | scale=(-1, 1), 86 | pin_pitch=hardware.coord("pin_pitch_h", raw=True), 87 | label_start=(110, 30), 88 | label_pitch=(0, 30), 89 | labels=data.lower_header, 90 | leaderline=lline.Curved(direction="vh"), 91 | ) 92 | ) 93 | 94 | # Create a title and description text-blocks 95 | title_block = diagram.panel_02.add( 96 | TextBlock( 97 | data.title, 98 | x=20, 99 | y=30, 100 | line_height=18, 101 | tag="panel title_block", 102 | ) 103 | ) 104 | diagram.panel_02.add( 105 | TextBlock( 106 | data.description, 107 | x=20, 108 | y=60, 109 | width=title_block.width, 110 | height=diagram.panel_02.height - title_block.height, 111 | line_height=18, 112 | tag="panel text_block", 113 | ) 114 | ) 115 | 116 | # Create a legend 117 | legend = diagram.panel_02.add( 118 | Legend( 119 | data.legend, 120 | x=340, 121 | y=8, 122 | max_height=132, 123 | ) 124 | ) 125 | 126 | # Export the diagram via commandline: 127 | # >>> py -m pinout.manager --export pinout_diagram.py diagram.svg 128 | -------------------------------------------------------------------------------- /pinout/resources/quick_start/styles.css: -------------------------------------------------------------------------------- 1 | text { 2 | font-family: Verdana, Georgia, sans-serif; 3 | font-size: 14px; 4 | font-weight: normal; 5 | } 6 | 7 | .pinlabel__leader{ 8 | stroke-width: 2; 9 | fill: none; 10 | } 11 | 12 | .pinlabel__text{ 13 | dominant-baseline: central; 14 | fill: #fff; 15 | font-weight: bold; 16 | stroke-width: 0; 17 | text-anchor: middle; 18 | } 19 | 20 | .pwr .pinlabel__body{ 21 | fill: rgb(173, 0, 0); 22 | } 23 | .pwr .pinlabel__leader{ 24 | stroke: rgb(173, 0, 0); 25 | } 26 | .pwr .swatch__body { 27 | fill: rgb(173, 0, 0); 28 | } 29 | .comms .pinlabel__body{ 30 | fill: rgb(182, 69, 176); 31 | } 32 | .comms .pinlabel__leader{ 33 | stroke: rgb(182, 69, 176); 34 | } 35 | .comms .swatch__body { 36 | fill: rgb(182, 69, 176); 37 | } 38 | .gpio .pinlabel__body{ 39 | fill: rgb(160, 133, 26); 40 | } 41 | .gpio .pinlabel__leader{ 42 | stroke: rgb(160, 133, 26); 43 | } 44 | .gpio .swatch__body { 45 | fill: rgb(160, 133, 26); 46 | } 47 | .analog .pinlabel__body{ 48 | fill: rgb(32, 150, 165); 49 | } 50 | .analog .pinlabel__leader{ 51 | stroke: rgb(32, 150, 165); 52 | } 53 | .analog .swatch__body { 54 | fill: rgb(32, 150, 165); 55 | } 56 | .gnd .pinlabel__body{ 57 | fill: rgb(0, 0, 0); 58 | } 59 | .gnd .pinlabel__leader{ 60 | stroke: rgb(0, 0, 0); 61 | } 62 | .gnd .swatch__body { 63 | fill: rgb(0, 0, 0); 64 | } 65 | .pwm .pinlabel__body{ 66 | fill: rgb(151, 76, 23); 67 | } 68 | .pwm .pinlabel__leader{ 69 | stroke: rgb(151, 76, 23); 70 | } 71 | .pwm .swatch__body { 72 | fill: rgb(151, 76, 23); 73 | } 74 | .touch .pinlabel__body{ 75 | fill: rgb(230, 87, 10); 76 | } 77 | .touch .pinlabel__leader{ 78 | stroke: rgb(230, 87, 10); 79 | } 80 | .touch .swatch__body { 81 | fill: rgb(230, 87, 10); 82 | } 83 | 84 | .panel__inner { 85 | fill: #fff; 86 | } 87 | .panel__outer { 88 | fill: #333; 89 | } 90 | 91 | .legendentry text { 92 | dominant-baseline: central; 93 | } 94 | 95 | .h1 { 96 | font-size: 26px; 97 | font-weight: bold; 98 | font-style: italic; 99 | } 100 | .italic{ 101 | font-style: italic; 102 | } 103 | .strong{ 104 | font-weight: bold; 105 | } 106 | 107 | .panel--info .panel__inner{ 108 | fill: #f4f4f4; 109 | } -------------------------------------------------------------------------------- /pinout/style_tools.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def luminace(color_component): 5 | """Luminance of an individual Red, Green, or Blue, color component. 6 | 7 | :param color_component: Value between 0 and 255 (inclusive) 8 | :type color_component: int 9 | :return: Luminance value of the color component 10 | :rtype: float 11 | """ 12 | i = color_component / 255 13 | 14 | if i <= 0.03928: 15 | return i / 12.92 16 | else: 17 | return ((i + 0.055) / 1.055) ** 2.4 18 | 19 | 20 | def relative_luminance(rgb): 21 | """Normalised luminance value of an RGB color. 22 | 23 | :param rgb: Tuple (or List) representing RGB value. 24 | :type rgb: tuple 25 | :return: Value between 0 and 1. 26 | :rtype: float 27 | """ 28 | return ( 29 | 0.2126 * luminace(rgb[0]) 30 | + 0.7152 * luminace(rgb[1]) 31 | + 0.0722 * luminace(rgb[2]) 32 | ) 33 | 34 | 35 | def unique_contrasting_rgb(ref_color): 36 | """Generate a psudo-random color that, compared to 'ref_color', has a contrast ratio greater than 3. This contrast value is the minimum value to pass WCAG AA contrast recommendation for UI components. 37 | 38 | :param ref_color: Tuple (or List) representing RGB value. 39 | :type ref_color: tuple 40 | :return: Tuple representing an RGB color. 41 | :rtype: tuple 42 | """ 43 | contrast = 0 44 | unique = False 45 | while contrast < 3 or not unique: 46 | rgb = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 47 | 48 | color_one_luminance = relative_luminance(ref_color) 49 | color_two_luminance = relative_luminance(rgb) 50 | 51 | if color_one_luminance > color_two_luminance: 52 | light = color_one_luminance 53 | dark = color_two_luminance 54 | else: 55 | dark = color_one_luminance 56 | light = color_two_luminance 57 | 58 | contrast = (light + 0.05) / (dark + 0.05) 59 | unique = is_distinct_rbg(rgb) 60 | palette.append(rgb) 61 | return rgb 62 | 63 | 64 | def is_distinct_rbg(rgb_color, threshold=60): 65 | r, g, b = rgb_color 66 | for (pr, pg, pb) in palette: 67 | diff = abs(r - pr) + abs(g - pg) + abs(b - pb) 68 | if diff < threshold: 69 | return False 70 | return True 71 | 72 | 73 | def assign_color(tag_list, ref_color=(255, 255, 255)): 74 | """Generate a stylesheet using metrics from a diagram. Various styles are tailored by making a *best-guess* based on diagram component dimensions or a *lucky-guess* filtered by preset criteria. The output should be considered a boot-strapping step to styling a diagram ...unless you feel lucky! 75 | 76 | :param diagram: The Diagram object requiring styling 77 | :type diagram: Diagram 78 | :return: content of a css stylesheet with all required styles to display a diagram. 79 | :rtype: str 80 | """ 81 | 82 | # Assign random-ish color to each tag 83 | return [(tag, unique_contrasting_rgb(ref_color)) for tag in tag_list] 84 | 85 | 86 | # Store created color for reference 87 | palette = [] -------------------------------------------------------------------------------- /pinout/templates.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, PackageLoader, select_autoescape 2 | 3 | 4 | env = Environment( 5 | loader=PackageLoader("pinout", "templates"), 6 | autoescape=select_autoescape(["html", "xml"]), 7 | trim_blocks=True, 8 | lstrip_blocks=True, 9 | ) 10 | 11 | 12 | def get(template_name): 13 | return env.get_template(template_name) 14 | -------------------------------------------------------------------------------- /pinout/templates/circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pinout/templates/clippath.svg: -------------------------------------------------------------------------------- 1 | 16 | {{ path.render_children() }} 17 | -------------------------------------------------------------------------------- /pinout/templates/component_common.svg: -------------------------------------------------------------------------------- 1 | {% if params.tag %} 2 | class="{{ params.tag }}" 3 | {% endif %} 4 | {% if params.clip %} 5 | clip-path="url(#{{ params.clip.id }})" 6 | {% endif %} 7 | transform=" 8 | translate({{ params.x }} {{ params.y }}) 9 | scale({{ params.scale.x }} {{ params.scale.y }}) 10 | rotate({{ params.rotate }}) 11 | " 12 | id="{{ params.id }}" 13 | clipPathUnits="userSpaceOnUse" 14 | shape-rendering="geometricPrecision" 15 | -------------------------------------------------------------------------------- /pinout/templates/group.svg: -------------------------------------------------------------------------------- 1 | 6 | {% block content %} 7 | {{ group.render_children()|safe }} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /pinout/templates/image.svg: -------------------------------------------------------------------------------- 1 | {% if image.svg_data is not none %} 2 | 7 | {{ image.svg_data }} 8 | 9 | {% else %} 10 | 20 | {% endif %} -------------------------------------------------------------------------------- /pinout/templates/kicad_footprint_lib/Annotation.j2: -------------------------------------------------------------------------------- 1 | ({{ component_type }} "Annotation" 2 | {% if version >= 6 %} 3 | (version {{ timestamp }}) (generator pinout) 4 | {% endif %} 5 | (layer "{{ layer }}") 6 | {% if version >= 6 %} 7 | (tedit 0) 8 | {% endif %} 9 | (descr "Add an annotation") 10 | (attr {% if version >= 6 %}exclude_from_pos_files exclude_from_bom{%else%}virtual{%endif%}) 11 | (fp_text reference "pinout_annotation" (at 9.525 6.985) (layer "{{ layer }}") {% if annotation.hide_fp_text_reference %}hide{% endif %} 12 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 13 | ) 14 | (fp_text value "Annotation_content_here" (at 23.495 6.985 unlocked) (layer "{{ layer }}") 15 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 16 | ) 17 | (fp_text user "vh" (at 9.525 8.89) (layer "{{ layer }}") {% if annotation.hide_fp_text_user %}hide{% endif %} 18 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 19 | ) 20 | (fp_line (start 1 0) (end -1 0) (layer "{{ layer }}") (width 0.12)) 21 | (fp_line (start 0 -1) (end 0 1) (layer "{{ layer }}") (width 0.12)) 22 | (fp_circle (center 0 0) (end 0 -0.635) (layer "{{ layer }}") (width 0.12){% if version >= 6 %} (fill none){% endif %} ) 23 | ) 24 | -------------------------------------------------------------------------------- /pinout/templates/kicad_footprint_lib/Origin.j2: -------------------------------------------------------------------------------- 1 | ({{ component_type }} "Origin" 2 | {% if version >= 6 %} 3 | (version {{ timestamp }}) (generator pinout) 4 | {% endif %} 5 | (layer "{{ layer }}") 6 | {% if version >= 6 %} 7 | (tedit 0) 8 | {%endif%} 9 | (descr "Set origin coordinates for pinout") 10 | (attr {% if version >= 6 %}exclude_from_pos_files exclude_from_bom{%else%}virtual{%endif%}) 11 | (fp_text reference "pinout_origin" (at 1.5 1.5) (layer "{{ layer }}") hide 12 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 13 | ) 14 | (fp_text value "Pinout Origin: Place at image top left." (at 1.5 3.5) (layer "{{ layer }}") 15 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 16 | ) 17 | (fp_line (start 0.1 0.1) (end 4 0.1) (layer "{{ layer }}") (width 0.2)) 18 | (fp_line (start 0.1 0.1) (end 0.1 4) (layer "{{ layer }}") (width 0.2)) 19 | ) 20 | -------------------------------------------------------------------------------- /pinout/templates/kicad_footprint_lib/PinLabel.j2: -------------------------------------------------------------------------------- 1 | ({{ component_type }} "PinLabel" 2 | {% if version >= 6 %} 3 | (version {{ timestamp }}) (generator pinout) 4 | {% endif %} 5 | (layer "{{ layer }}") 6 | {% if version >= 6 %} 7 | (tedit 0) 8 | {% endif %} 9 | (descr "Add one or more labels in a row to a single location.") 10 | (attr {% if version >= 6 %}exclude_from_pos_files exclude_from_bom{%else%}virtual{%endif%}) 11 | (fp_text reference "pinout_pinlabel" (at 9.525 7.62) (layer "{{ layer }}") {% if pinlabel.hide_fp_text_reference %}hide{% endif %} 12 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 13 | 14 | ) 15 | (fp_text value "LBL {{tag}}" (at 21.59 7.62) (layer "{{ layer }}") 16 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 17 | 18 | ) 19 | (fp_text user "hh" (at 9.525 9.525) (layer "{{ layer }}") {% if pinlabel.hide_fp_text_user %}hide{% endif %} 20 | (effects (font (size 1 1) (thickness 0.15)) (justify left)) 21 | 22 | ) 23 | (fp_line (start 1 0) (end -1 0) (layer "{{ layer }}") (width 0.12)) 24 | (fp_line (start 0 -1) (end 0 1) (layer "{{ layer }}") (width 0.12)) 25 | (fp_circle (center 0 0) (end 0 -0.5) (layer "{{ layer }}") (width 0.12) {% if version >= 6 %} (fill none){%endif%}) 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /pinout/templates/label.svg: -------------------------------------------------------------------------------- 1 | 8 | 18 | 31 | {{ text_content }} 32 | 33 | 34 | -------------------------------------------------------------------------------- /pinout/templates/path.svg: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /pinout/templates/rect.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pinout/templates/style.svg: -------------------------------------------------------------------------------- 1 | {% if stylesheet %} 2 | 3 | {% else %} 4 | 9 | {% endif %} -------------------------------------------------------------------------------- /pinout/templates/stylesheet.j2: -------------------------------------------------------------------------------- 1 | 2 | {# Diagram settings #} 3 | text { 4 | font-family: Verdana, Georgia, sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | } 8 | 9 | {# Pinlabel component #} 10 | {% if css.pinlabel %} 11 | .{{ css.pinlabel.leaderline.tag }}{ 12 | stroke-width: 2; 13 | fill: none; 14 | } 15 | 16 | .{{ css.pinlabel.text.tag }}{ 17 | dominant-baseline: central; 18 | fill: #fff; 19 | 20 | font-weight: bold; 21 | stroke-width: 0; 22 | text-anchor: middle; 23 | } 24 | 25 | {% endif -%} 26 | 27 | {# Color styling derived from pinlabel classes #} 28 | {% if css.tags %} 29 | {% for (name, color) in css.tags %} 30 | .{{name}} .{{ css.pinlabel.body.tag }}{ 31 | fill: rgb{{color}}; 32 | } 33 | .{{name}} .{{ css.pinlabel.leaderline.tag }}{ 34 | stroke: rgb{{color}}; 35 | } 36 | .{{name}} .swatch__body { 37 | fill: rgb{{color}}; 38 | } 39 | {% endfor %} 40 | 41 | {% endif -%} 42 | 43 | {# Panel component #} 44 | {% if css.panel %} 45 | .{{ css.panel.inner.tag }} { 46 | fill: #fff; 47 | } 48 | .{{ css.panel.outer.tag }} { 49 | fill: #333; 50 | } 51 | 52 | {% endif -%} 53 | 54 | {# Legend component #} 55 | {% if css.legend %} 56 | .{{ css.legend.entry.tag }} text { 57 | dominant-baseline: central; 58 | } 59 | {% endif -%} 60 | 61 | {# Annotation component #} 62 | {% if css.annotation %} 63 | .{{ css.annotation.body.tag }} { 64 | fill: rgb(253, 203, 36); 65 | stroke-width: 0; 66 | } 67 | .{{ css.annotation.content.tag }} { 68 | dominant-baseline: central; 69 | font-size: 14px; 70 | font-weight: bold; 71 | fill: rgb(63, 40, 6); 72 | text-anchor: middle; 73 | } 74 | .{{ css.annotation.target.tag }} { 75 | fill: none; 76 | stroke: rgb(253, 203, 36); 77 | stroke-width: 4px; 78 | } 79 | .{{ css.annotation.leaderline.tag }} { 80 | stroke: rgb(253, 203, 36); 81 | stroke-width: 4px; 82 | fill: none; 83 | } 84 | 85 | {% endif -%} 86 | 87 | {# Integrated circuits #} 88 | {% if css.ic_dip %} 89 | .{{ css.ic_dip.body.tag }}{ 90 | fill: rgb(85, 85, 85); 91 | stroke-width: 1px; 92 | stroke: #000; 93 | } 94 | .{{ css.ic_dip.leg.tag }} .{{ css.ic_dip.polarity_mark.tag }}{ 95 | fill: rgb(85, 85, 85); 96 | stroke-width: 2px; 97 | stroke: rgb(138, 138, 138); 98 | 99 | } 100 | .{{ css.ic_dip.leg.tag }}{ 101 | fill: rgb(255, 255, 255); 102 | stroke-width: 1px; 103 | stroke: #000; 104 | } 105 | 106 | {% endif -%} 107 | 108 | {% if css.ic_qfp %} 109 | .{{ css.ic_qfp.body.tag }}{ 110 | fill: rgb(85, 85, 85); 111 | stroke-width: 1px; 112 | stroke: #000; 113 | } 114 | .{{ css.ic_qfp.leg.tag }} .{{ css.ic_qfp.polarity_mark.tag }}{ 115 | fill: rgb(85, 85, 85); 116 | stroke-width: 2px; 117 | stroke: rgb(138, 138, 138); 118 | 119 | } 120 | .{{ css.ic_qfp.leg.tag }}{ 121 | fill: rgb(255, 255, 255); 122 | stroke-width: 1px; 123 | stroke: #000; 124 | } 125 | 126 | {% endif -%} 127 | 128 | {# Diagram preset templates #} 129 | {% if css.diagram_presets %} 130 | .{{ css.diagram_presets.tag }} .h1{ 131 | font-size: 26px; 132 | font-weight: bold; 133 | } 134 | .{{ css.diagram_presets.panel_02.tag }} .panel__inner{ 135 | fill: #ededed; 136 | } 137 | 138 | {% endif -%} -------------------------------------------------------------------------------- /pinout/templates/svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | {{ svg.render_defs() }} 20 | 21 | 22 | {% block body %} 23 | 24 | {{ svg.render_children() }} 25 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /pinout/templates/text.svg: -------------------------------------------------------------------------------- 1 | 7 | {{ text.content }} 8 | 9 | -------------------------------------------------------------------------------- /pinout/templates/textblock.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | {% for content in text_content %} 8 | 18 | {{ content }} 19 | 20 | {% endfor %} 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pinout/templates/use.svg: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /samples/arduino/arduino/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/arduino/arduino/__init__.py -------------------------------------------------------------------------------- /samples/arduino/arduino/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/arduino/arduino/common/__init__.py -------------------------------------------------------------------------------- /samples/arduino/arduino/common/arduino_components.py: -------------------------------------------------------------------------------- 1 | # Customised components for Arduino pinout diagram 2 | from pinout.core import Group, Path, Rect 3 | from pinout.components import pinlabel 4 | 5 | 6 | # PinBodyStart and PinBody include an inset shape 7 | INSET = 2 8 | 9 | 10 | class PlbStart(pinlabel.Body): 11 | def render(self): 12 | 13 | output = Group() 14 | 15 | # Label body 16 | radius = self.height / 2 17 | path_def = " ".join( 18 | [ 19 | f"M {radius} 0", 20 | f"l {self.width - radius} 0", 21 | f"l 0 {self.height}", 22 | f"l {-(self.width - radius)} 0", 23 | f"a {-radius} {-radius} 0 0 1 0 {-self.height}", 24 | "z", 25 | ] 26 | ) 27 | output.add( 28 | Path( 29 | path_definition=path_def, 30 | x=self.x, 31 | y=self.y - (self.height / 2), 32 | width=self.width, 33 | height=self.height, 34 | tag="pinlabel__body", 35 | ) 36 | ) 37 | 38 | # SVG does not support stroke alignment. 39 | # To achive an 'inner stroke' effect another 40 | # component has been added with the desired inset. 41 | 42 | h = self.height - INSET 43 | w = self.width - INSET 44 | radius = h / 2 45 | path_def = " ".join( 46 | [ 47 | f"M {radius + INSET/2} {INSET/2}", 48 | f"l {w - radius} 0", 49 | f"l 0 {h}", 50 | f"l {-(w - radius)} 0", 51 | f"a {-radius} {-radius} 0 0 1 0 {-h}", 52 | "z", 53 | ] 54 | ) 55 | output.add( 56 | Path( 57 | path_definition=path_def, 58 | x=self.x, 59 | y=self.y - (self.height / 2), 60 | width=self.width, 61 | height=self.height, 62 | tag="pinlabel__bodyinner", 63 | ) 64 | ) 65 | return output.render() 66 | 67 | 68 | class PlbEnd(pinlabel.Body): 69 | def render(self): 70 | 71 | output = Group() 72 | 73 | radius = self.height / 2 74 | path_def = " ".join( 75 | [ 76 | f"M 0 0", 77 | f"L {self.width - radius} 0", 78 | f"a {radius} {radius} 0 0 1 0 {self.height}", 79 | f"L 0 {self.height}", 80 | "Z", 81 | ] 82 | ) 83 | output.add( 84 | Path( 85 | path_definition=path_def, 86 | x=self.x, 87 | y=self.y - (self.height / 2), 88 | width=self.width, 89 | height=self.height, 90 | tag="pinlabel__body", 91 | ) 92 | ) 93 | 94 | return output.render() 95 | 96 | 97 | class Plb(pinlabel.Body): 98 | # this class differs from the default version as it include an 99 | # # 'inner rect' for custom styling 100 | def render(self): 101 | 102 | output = Group() 103 | 104 | output.add( 105 | Rect( 106 | x=self.x, 107 | y=self.y - (self.height / 2), 108 | width=self.width, 109 | height=self.height, 110 | corner_radius=self.corner_radius, 111 | tag="block pinlabel__body", 112 | ) 113 | ) 114 | 115 | # Add an inner body for 'inner-stroke' styling 116 | output.add( 117 | Rect( 118 | x=self.x + INSET / 2, 119 | y=self.y - (self.height / 2) + INSET / 2, 120 | width=self.width - INSET, 121 | height=self.height - INSET, 122 | corner_radius=self.corner_radius, 123 | tag="block pinlabel__bodyinner", 124 | ) 125 | ) 126 | return output.render() 127 | -------------------------------------------------------------------------------- /samples/arduino/arduino/common/patterns.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/arduino/arduino/common/preprocessor.py: -------------------------------------------------------------------------------- 1 | # An attempt to keep style/config separate from content 2 | from copy import deepcopy 3 | from .arduino_components import PlbStart, PlbEnd, Plb 4 | 5 | 6 | # Generator function that applies pinlabel configurations to label data 7 | def pinlabel_preprocessor(pinlabel_set): 8 | for row in pinlabel_set: 9 | 10 | if len(row) == 1: 11 | # Single label row config 12 | row[0] = ( 13 | *row[0], 14 | {"body": Plb(width=80, height=20, x=0, y=0, corner_radius=10)}, 15 | ) 16 | else: 17 | # Default label config 18 | row = [ 19 | (name, tag, {"body": Plb(width=80, height=20, x=2, y=0)}) 20 | for (name, tag) in row 21 | ] 22 | 23 | # Row start and end labels 24 | row[0] = ( 25 | row[0][0], 26 | row[0][1], 27 | {"body": PlbStart(x=0, y=0, width=80, height=20)}, 28 | ) 29 | row[-1] = ( 30 | row[-1][0], 31 | row[-1][1], 32 | {"body": PlbEnd(x=2, y=0, width=80, height=20)}, 33 | ) 34 | 35 | # Order dependent labels 36 | if row[0][1] == "digital" and row[1][1] == "analog": 37 | row[0] = ( 38 | row[0][0], 39 | row[0][1], 40 | {"body": PlbStart(x=0, y=0, width=50, height=20)}, 41 | ) 42 | row[1] = ( 43 | row[1][0], 44 | row[1][1], 45 | {"body": Plb(width=30, height=20, x=0, y=0)}, 46 | ) 47 | 48 | # Tag specific labels 49 | for i, label in enumerate(row): 50 | name, tag = label[:2] 51 | if "show-leader" in tag: 52 | row[i] = ( 53 | name, 54 | tag, 55 | {"body": PlbEnd(x=84, y=0, width=80, height=20)}, 56 | ) 57 | 58 | yield row 59 | -------------------------------------------------------------------------------- /samples/arduino/arduino/common/styles.css: -------------------------------------------------------------------------------- 1 | 2 | svg { 3 | fill-opacity: 1; 4 | font-family: 'Roboto', Helvetica, Verdana, monospace; 5 | font-size: 13px; 6 | font-weight: 300; 7 | opacity: 1; 8 | 9 | } 10 | 11 | path{ 12 | fill: none; 13 | } 14 | rect { 15 | fill: rgba(0, 0, 0, 0); 16 | } 17 | 18 | text { 19 | dominant-baseline: auto; 20 | } 21 | 22 | .pinlabel__body{ 23 | fill: rgb(36, 36, 36); 24 | } 25 | .pinlabel__bodyinner{ 26 | fill: none; 27 | stroke-linejoin: "miter"; 28 | } 29 | .pinlabel .pinlabel__leader{ 30 | stroke-width: 0; 31 | } 32 | .pinlabel__text{ 33 | dominant-baseline: central; 34 | fill: #fff; 35 | font-size: 12px; 36 | font-weight: 600; 37 | stroke-width: 0; 38 | text-anchor: middle; 39 | } 40 | 41 | .legend rect{ 42 | fill: none; 43 | } 44 | .legend .swatch__body{ 45 | fill: rgba(0,0,0,0); 46 | } 47 | .legendentry text{ 48 | dominant-baseline: central; 49 | fill: #000; 50 | } 51 | 52 | .diagram__bg{ 53 | fill: #333; 54 | } 55 | .panel--main .panel__bg{ 56 | fill: #fff; 57 | } 58 | .panel--notes .panel__bg{ 59 | fill: #e4e4e2; 60 | } 61 | 62 | .pinheader g .pinlabel:first-child .pinlabel__leader, 63 | .pinlabel.show-leader .pinlabel__leader{ 64 | stroke-width: 2px; 65 | stroke: #bcc6c6; 66 | } 67 | 68 | .led .pinlabel__body, 69 | .led .swatch__body{ 70 | fill: #1da086; 71 | } 72 | .mu-port .pinlabel__body, 73 | .mu-port .swatch__body{ 74 | fill: #f39c12; 75 | } 76 | .default .pinlabel__body, 77 | .default .swatch__body{ 78 | fill: #f1c40f; 79 | } 80 | .default .pinlabel__text{ 81 | fill: #000; 82 | } 83 | .digital .pinlabel__body, 84 | .digital .swatch__body{ 85 | fill: url("#stripe-a"); 86 | } 87 | 88 | #stripe-a rect{ 89 | fill: #d35400; 90 | } 91 | 92 | #stripe-a path{ 93 | fill: #de7f40; 94 | } 95 | 96 | .analog .pinlabel__body{ 97 | fill: url("#stripe-b"); 98 | } 99 | 100 | #stripe-b rect{ 101 | fill: #ffffff; 102 | } 103 | #stripe-b path{ 104 | fill: #f8e6d9; 105 | } 106 | 107 | .analog .pinlabel__bodyinner{ 108 | fill: none; 109 | stroke: #d35400; 110 | stroke-width: 2; 111 | } 112 | .analog .pinlabel__text{ 113 | fill: #d35400; 114 | } 115 | .analog .swatch__body{ 116 | fill: url("#stripe-b"); 117 | stroke: #d35400; 118 | stroke-width: 2; 119 | } 120 | .pwr .pinlabel__body, 121 | .pwr .swatch__body{ 122 | fill: #c11f09; 123 | } 124 | 125 | .other .pinlabel__body, 126 | .other .swatch__body{ 127 | fill: #fff; 128 | } 129 | .other .pinlabel__bodyinner, 130 | .other .swatch__body{ 131 | stroke: #d86e29; 132 | stroke-width: 2; 133 | } 134 | .other .pinlabel__text{ 135 | fill: #d86e29; 136 | } 137 | .gnd .pinlabel__body, 138 | .gnd .swatch__body{ 139 | fill: #000; 140 | } 141 | .internal .pinlabel__body, 142 | .internal .swatch__body{ 143 | fill: #94a3a6; 144 | } 145 | .swd .pinlabel__body, 146 | .swd .swatch__body{ 147 | fill: #9e856e; 148 | } 149 | .nc .pinlabel__body, 150 | .nc .swatch__body{ 151 | fill: #fff; 152 | } 153 | .nc .pinlabel__text{ 154 | fill: #ccc; 155 | } 156 | .nc .pinlabel__bodyinner, 157 | .nc .swatch__body{ 158 | stroke: #ccc; 159 | stroke-width: 2; 160 | } 161 | 162 | .h1{ 163 | font-size: 28px; 164 | font-weight: 900; 165 | } 166 | .p{ 167 | fill: #333; 168 | } 169 | .strong{ font-weight: bold; } 170 | .italic{ font-style: italic; } -------------------------------------------------------------------------------- /samples/arduino/arduino/rp2040/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/arduino/arduino/rp2040/__init__.py -------------------------------------------------------------------------------- /samples/arduino/arduino/rp2040/arduino_nano_rp2040_connect.py: -------------------------------------------------------------------------------- 1 | from pinout.core import Group, Image, Raw, Rect 2 | from pinout.components.layout import Diagram, Panel 3 | from pinout.components import pinlabel, legend, leaderline 4 | from pinout.components.text import TextBlock 5 | import sys 6 | 7 | 8 | # Import data and custom function 9 | # Slightly inelegant method to amend module search path 10 | sys.path.append("..") 11 | import rp2040_data as data 12 | from common.preprocessor import pinlabel_preprocessor as prep 13 | 14 | 15 | # Create a new diagram 16 | diagram = Diagram(1200, 675, tag="arduino-rp2040-connect") 17 | 18 | # Add a stylesheet and some custom patterns 19 | diagram.add_stylesheet("../common/styles.css", True) 20 | 21 | # Load some svg markup to be used as patterns 22 | # The Raw class allows arbitary code to be inserted 23 | # into the diagram. 24 | with open("../common/patterns.xml") as f: 25 | patterns = f.read() 26 | diagram.add_def(Raw(patterns)) 27 | 28 | # Construct a layout and add some backgrounds 29 | diagram.add(Rect(x=0, y=0, width=1200, height=675, tag="diagram__bg")) 30 | group_main = diagram.add(Group(2, 2, tag="panel panel--main")) 31 | group_main.add(Rect(x=0, y=0, width=1196, height=548, tag="panel__bg")) 32 | 33 | # Keeping elements in a group allows for easier measuring and moving 34 | # Create a group for the main pinout graphic 35 | pinout_graphic = group_main.add(Group(600, 80, tag="pinout-graphic")) 36 | 37 | group_notes = diagram.add(Group(2, 552, tag="panel panel--notes")) 38 | group_notes.add(Rect(x=0, y=0, width=1196, height=121, tag="panel__bg")) 39 | group_notes.add( 40 | legend.Legend(data.legend, max_height=112, x=10, y=5, inset=(0, 0, 0, 0)) 41 | ) 42 | group_notes.add(TextBlock(data.title_1, 22, x=580, y=30)) 43 | group_notes.add(TextBlock(data.para_1, 17, x=580, y=74)) 44 | group_notes.add(TextBlock(data.para_2, 17, x=900, y=74)) 45 | 46 | # Add a hardware image 47 | # Note its coordinates are relative to the group it is within 48 | pinout_graphic.add( 49 | Image( 50 | "hardware.png", 51 | x=-176 / 2, 52 | y=0, 53 | width=176, 54 | height=449, 55 | embed=True, 56 | ) 57 | ) 58 | # Right hand side pin header 59 | pinout_graphic.add( 60 | pinlabel.PinLabelGroup( 61 | x=86, 62 | y=58, 63 | pin_pitch=(0, 24.6), 64 | label_start=(80, 0), 65 | label_pitch=(0, 24.6), 66 | labels=prep(data.header_rhs), 67 | tag="pinheader", 68 | ) 69 | ) 70 | 71 | # Left hand side pin header 72 | pinout_graphic.add( 73 | pinlabel.PinLabelGroup( 74 | x=-86, 75 | y=58, 76 | pin_pitch=(0, 24.6), 77 | label_start=(80, 0), 78 | label_pitch=(0, 24.6), 79 | scale=(-1, 1), 80 | labels=prep(data.header_lhs), 81 | tag="pinheader", 82 | ) 83 | ) 84 | 85 | # LED labels 86 | pinout_graphic.add( 87 | pinlabel.PinLabelGroup( 88 | x=-56, 89 | y=28, 90 | pin_pitch=(112, 0), 91 | label_start=(104, 60), 92 | label_pitch=(0, 22), 93 | scale=(-1, -1), 94 | labels=prep(data.leds), 95 | leaderline=leaderline.Curved(direction="vh"), 96 | tag="pinheader", 97 | ) 98 | ) 99 | -------------------------------------------------------------------------------- /samples/arduino/arduino/rp2040/hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/arduino/arduino/rp2040/hardware.png -------------------------------------------------------------------------------- /samples/arduino/arduino/rp2040/rp2040_data.py: -------------------------------------------------------------------------------- 1 | ######################################################### 2 | # 3 | # Legend 4 | # 5 | ######################################################### 6 | 7 | legend = [ 8 | ("Ground", "gnd"), 9 | ("Power", "pwr"), 10 | ("LED", "led"), 11 | ("Internal Pin", "internal"), 12 | ("SWD Pin", "swd"), 13 | ("Digital Pin", "digital"), 14 | ("Analog Pin", "analog"), 15 | ("Other Pin", "other"), 16 | ("Microcontroller's Port", "mu-port"), 17 | ("Default", "default"), 18 | ] 19 | 20 | 21 | ######################################################### 22 | # 23 | # Header: Right-hand-side 24 | # 25 | ######################################################### 26 | 27 | header_rhs = [ 28 | [ 29 | ("D12", "digital"), 30 | ("GPIO4", "mu-port"), 31 | ("CIPO", "default"), 32 | ], 33 | [ 34 | ("D11", "digital"), 35 | ("GPIO7", "mu-port"), 36 | ("CIPI", "default"), 37 | ], 38 | [ 39 | ("D10", "digital"), 40 | ("GPIO5", "mu-port"), 41 | ], 42 | [ 43 | ("D9", "digital"), 44 | ("GPIO21", "mu-port"), 45 | ], 46 | [ 47 | ("D8", "digital"), 48 | ("GPIO20", "mu-port"), 49 | ], 50 | [ 51 | ("D7", "digital"), 52 | ("GPIO19", "mu-port"), 53 | ], 54 | [ 55 | ("D6", "digital"), 56 | ("GPIO18", "mu-port"), 57 | ], 58 | [ 59 | ("D5", "digital"), 60 | ("GPIO17", "mu-port"), 61 | ], 62 | [ 63 | ("D4", "digital"), 64 | ("GPIO16", "mu-port"), 65 | ], 66 | [ 67 | ("D3", "digital"), 68 | ("GPIO15", "mu-port"), 69 | ], 70 | [ 71 | ("D2", "digital"), 72 | ("GPIO25", "mu-port"), 73 | ], 74 | [ 75 | ("GND", "gnd"), 76 | ], 77 | [ 78 | ("RESET", "other"), 79 | ("RESET", "mu-port"), 80 | ], 81 | [ 82 | ("RX", "digital"), 83 | ("GPIO1", "mu-port"), 84 | ], 85 | [ 86 | ("TX", "digital"), 87 | ("GPIO0", "mu-port"), 88 | ], 89 | ] 90 | 91 | ######################################################### 92 | # 93 | # Header: Left-hand-side 94 | # 95 | ######################################################### 96 | 97 | header_lhs = [ 98 | [ 99 | ("D13", "digital"), 100 | ("GPIO6", "mu-port"), 101 | ("SCK", "default"), 102 | ], 103 | [ 104 | ("+3V3", "pwr"), 105 | ], 106 | [ 107 | ("AREF", "other"), 108 | ("PA03", "mu-port"), 109 | ], 110 | [ 111 | ("D14", "digital"), 112 | ("A0", "analog"), 113 | ("GPIO26", "mu-port"), 114 | ("A0/DAC0", "default"), 115 | ], 116 | [ 117 | ("D15", "digital"), 118 | ("A1", "analog"), 119 | ("GPIO27", "mu-port"), 120 | ("A1", "default"), 121 | ], 122 | [ 123 | ("D16", "digital"), 124 | ("A2", "analog"), 125 | ("GPIO28", "mu-port"), 126 | ("A2", "default"), 127 | ], 128 | [ 129 | ("D17", "digital"), 130 | ("A3", "analog"), 131 | ("GPIO29", "mu-port"), 132 | ("A3", "default"), 133 | ], 134 | [ 135 | ("D18", "digital"), 136 | ("A4", "analog"), 137 | ("GPIO12", "mu-port"), 138 | ("A4", "default"), 139 | ], 140 | [ 141 | ("D19", "digital"), 142 | ("A5", "analog"), 143 | ("GPIO13", "mu-port"), 144 | ("A5", "default"), 145 | ], 146 | [ 147 | ("D20", "digital"), 148 | ("A6", "analog"), 149 | ("A6", "default show-leader"), 150 | ], 151 | [ 152 | ("D21", "digital"), 153 | ("A7", "analog"), 154 | ("A7", "default show-leader"), 155 | ], 156 | [ 157 | ("+5V", "pwr"), 158 | ], 159 | [ 160 | ("RESET", "other"), 161 | ("QSPI_CSn", "default"), 162 | ], 163 | [ 164 | ("GND", "gnd"), 165 | ], 166 | [ 167 | ("VIN", "pwr"), 168 | ], 169 | ] 170 | 171 | 172 | ######################################################### 173 | # 174 | # LED labels 175 | # 176 | ######################################################### 177 | 178 | leds = [ 179 | [("Power", "led")], 180 | [("LED_BUILTIN", "led")], 181 | ] 182 | 183 | 184 | ######################################################### 185 | # 186 | # Text blocks 187 | # 188 | ######################################################### 189 | 190 | title_1 = [ 191 | 'Arduino', 192 | 'Nano RP2024 Connect', 193 | ] 194 | para_1 = [ 195 | 'Pinout diagram created with pinout (v0.0.10)', 196 | 'A Python package for creating pinout diagrams.', 197 | 'pinout.readthedocs.io', 198 | ] 199 | para_2 = [ 200 | 'NOTE: This is not official documentation.', 201 | 'Diagram aesthetics from Arduino docs.', 202 | 'https://www.arduino.cc/', 203 | ] 204 | -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/arduino/arduino/uno/__init__.py -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/arduino_uno.py: -------------------------------------------------------------------------------- 1 | from pinout.core import Group, Image, Raw, Rect 2 | from pinout.components.layout import Diagram, Panel 3 | from pinout.components import pinlabel, legend, leaderline 4 | from pinout.components.text import TextBlock 5 | import sys 6 | 7 | # Import data and custom function 8 | # Slightly inelegant method to amend module search path 9 | sys.path.append("..") 10 | import uno_data as data 11 | from common.preprocessor import pinlabel_preprocessor as prep 12 | 13 | # Create a new digram 14 | diagram = Diagram(1200, 675, tag="arduino-rp2040-connect") 15 | 16 | # Add a stylesheet and some custom patterns 17 | diagram.add_stylesheet("../common/styles.css", True) 18 | 19 | # Load some svg markup to be used as patterns 20 | # The Raw class allows arbitary code to be inserted 21 | # into the diagram. 22 | with open("../common/patterns.xml") as f: 23 | patterns = f.read() 24 | diagram.add_def(Raw(patterns)) 25 | 26 | # Construct a layout and add some backgrounds 27 | diagram.add(Rect(x=0, y=0, width=1200, height=675, tag="diagram__bg")) 28 | group_main = diagram.add(Group(2, 2, tag="panel panel--main")) 29 | group_main.add(Rect(x=0, y=0, width=1196, height=548, tag="panel__bg")) 30 | 31 | # Keeping elements in a group allows for easier measuring and moving 32 | # Create a group for the main pinout graphic 33 | pinout_graphic = group_main.add(Group(600, 10, tag="pinout-graphic")) 34 | 35 | group_notes = diagram.add(Group(2, 552, tag="panel panel--notes")) 36 | group_notes.add(Rect(x=0, y=0, width=1196, height=121, tag="panel__bg")) 37 | group_notes.add( 38 | legend.Legend(data.legend, max_height=112, x=10, y=5, inset=(0, 0, 0, 0)) 39 | ) 40 | group_notes.add(TextBlock(data.title_1, 22, x=580, y=30)) 41 | group_notes.add(TextBlock(data.para_1, 17, x=580, y=74)) 42 | group_notes.add(TextBlock(data.para_2, 17, x=900, y=74)) 43 | 44 | # Add a hardware image 45 | # Note its coordinates are relative to the group it is within 46 | pinout_graphic.add( 47 | Image( 48 | "hardware.png", 49 | x=-326 / 2, 50 | y=0, 51 | width=326, 52 | height=455, 53 | embed=True, 54 | ) 55 | ) 56 | # Right hand side pin headers 57 | pinout_graphic.add( 58 | pinlabel.PinLabelGroup( 59 | x=147, 60 | y=153, 61 | pin_pitch=(0, 15.35), 62 | label_start=(90, -84), 63 | label_pitch=(0, 23.35), 64 | labels=prep(data.header_rhs_a), 65 | tag="pinheader", 66 | ) 67 | ) 68 | 69 | pinout_graphic.add( 70 | pinlabel.PinLabelGroup( 71 | x=147, 72 | y=316, 73 | pin_pitch=(0, 15.35), 74 | label_start=(90, 8), 75 | label_pitch=(0, 23.35), 76 | labels=prep(data.header_rhs_b), 77 | tag="pinheader", 78 | ) 79 | ) 80 | 81 | # Left hand side pin header 82 | pinout_graphic.add( 83 | pinlabel.PinLabelGroup( 84 | x=-147, 85 | y=208, 86 | pin_pitch=(0, 15.35), 87 | label_start=(90, -64), 88 | label_pitch=(0, 23.35), 89 | scale=(-1, 1), 90 | labels=prep(data.header_lhs_a), 91 | tag="pinheader", 92 | ) 93 | ) 94 | pinout_graphic.add( 95 | pinlabel.PinLabelGroup( 96 | x=-147, 97 | y=347, 98 | pin_pitch=(0, 15.35), 99 | label_start=(90, 12), 100 | label_pitch=(0, 23.35), 101 | scale=(-1, 1), 102 | labels=prep(data.header_lhs_b), 103 | tag="pinheader", 104 | ) 105 | ) 106 | 107 | 108 | # LEDs RX & TX 109 | pinout_graphic.add( 110 | pinlabel.PinLabelGroup( 111 | x=46, 112 | y=206, 113 | pin_pitch=(17, 0), 114 | label_start=(284, 120), 115 | label_pitch=(0, 23), 116 | scale=(-1, -1), 117 | labels=prep(data.leds_a), 118 | leaderline=leaderline.Curved(direction="vh"), 119 | tag="pinheader", 120 | ) 121 | ) 122 | 123 | # LEDs pwr 124 | pinout_graphic.add( 125 | pinlabel.PinLabelGroup( 126 | x=62, 127 | y=392, 128 | pin_pitch=(39, -185), 129 | label_start=(38, 90), 130 | label_pitch=(0, 23.35), 131 | scale=(-1, 1), 132 | labels=prep(data.leds_b), 133 | leaderline=leaderline.Curved(direction="vh"), 134 | tag="pinheader", 135 | ) 136 | ) 137 | -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/delete/arduino_components.py: -------------------------------------------------------------------------------- 1 | # Customised components for Arduino pinout diagram 2 | from pinout import core 3 | from pinout.components import pinlabel 4 | 5 | 6 | # PinBodyStart and PinBody include an inset shape 7 | INSET = 2 8 | 9 | 10 | class PlbStart(pinlabel.Body): 11 | def render(self): 12 | 13 | output = Group() 14 | 15 | # Label body 16 | radius = self.height / 2 17 | path_def = " ".join( 18 | [ 19 | f"M {radius} 0", 20 | f"l {self.width - radius} 0", 21 | f"l 0 {self.height}", 22 | f"l {-(self.width - radius)} 0", 23 | f"a {-radius} {-radius} 0 0 1 0 {-self.height}", 24 | "z", 25 | ] 26 | ) 27 | output.add( 28 | core.Path( 29 | path_definition=path_def, 30 | x=self.x, 31 | y=self.y - (self.height / 2), 32 | width=self.width, 33 | height=self.height, 34 | tag="label__body", 35 | ) 36 | ) 37 | 38 | # SVG does not support stroke alignment. 39 | # To achive an 'inner stroke' effect another 40 | # component has been added with the desired inset. 41 | 42 | h = self.height - INSET 43 | w = self.width - INSET 44 | radius = h / 2 45 | path_def = " ".join( 46 | [ 47 | f"M {radius + INSET/2} {INSET/2}", 48 | f"l {w - radius} 0", 49 | f"l 0 {h}", 50 | f"l {-(w - radius)} 0", 51 | f"a {-radius} {-radius} 0 0 1 0 {-h}", 52 | "z", 53 | ] 54 | ) 55 | output.add( 56 | core.Path( 57 | path_definition=path_def, 58 | x=self.x, 59 | y=self.y - (self.height / 2), 60 | width=self.width, 61 | height=self.height, 62 | tag="label__bodyinner", 63 | ) 64 | ) 65 | return output.render() 66 | 67 | 68 | class PlbEnd(pinlabel.Body): 69 | def render(self): 70 | 71 | output = Group() 72 | 73 | radius = self.height / 2 74 | path_def = " ".join( 75 | [ 76 | f"M 0 0", 77 | f"L {self.width - radius} 0", 78 | f"a {radius} {radius} 0 0 1 0 {self.height}", 79 | f"L 0 {self.height}", 80 | "Z", 81 | ] 82 | ) 83 | output.add( 84 | core.Path( 85 | path_definition=path_def, 86 | x=self.x, 87 | y=self.y - (self.height / 2), 88 | width=self.width, 89 | height=self.height, 90 | tag="label__body", 91 | ) 92 | ) 93 | 94 | return output.render() 95 | 96 | 97 | class Plb(pinlabel.Body): 98 | # this class differs from the default version as it include an 99 | # # 'inner rect' for custom styling 100 | def render(self): 101 | 102 | output = core.Group() 103 | 104 | output.add( 105 | core.Rect( 106 | x=self.x, 107 | y=self.y - (self.height / 2), 108 | width=self.width, 109 | height=self.height, 110 | corner_radius=self.corner_radius, 111 | tag="block label__body", 112 | ) 113 | ) 114 | 115 | # Add an inner body for 'inner-stroke' styling 116 | output.add( 117 | core.Rect( 118 | x=self.x + INSET / 2, 119 | y=self.y - (self.height / 2) + INSET / 2, 120 | width=self.width - INSET, 121 | height=self.height - INSET, 122 | corner_radius=self.corner_radius, 123 | tag="block label__bodyinner", 124 | ) 125 | ) 126 | return output.render() 127 | -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/delete/patterns.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/delete/styles.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | svg { 6 | fill-opacity: 1; 7 | font-family: 'Roboto', Helvetica, Verdana, monospace; 8 | font-size: 13px; 9 | font-weight: 300; 10 | opacity: 1; 11 | 12 | } 13 | path, rect { 14 | stroke: rgb(0, 0, 0); 15 | stroke-dasharray: 0; 16 | stroke-dashoffset: 0; 17 | stroke-linecap: round; 18 | stroke-linejoin: miter; 19 | stroke-miterlimit: inherit; 20 | stroke-opacity: 1; 21 | stroke-width: 0; 22 | } 23 | path{ 24 | fill: none; 25 | } 26 | rect { 27 | fill: rgba(0, 0, 0, 0); 28 | } 29 | 30 | text { 31 | dominant-baseline: auto; 32 | } 33 | 34 | 35 | 36 | 37 | 38 | .label__body{ 39 | fill: rgb(36, 36, 36); 40 | } 41 | .label__bodyinner{ 42 | fill: none; 43 | stroke-linejoin: "miter"; 44 | } 45 | .label .lline{ 46 | stroke-width: 0; 47 | } 48 | .label__text{ 49 | dominant-baseline: central; 50 | fill: #fff; 51 | font-size: 12px; 52 | font-weight: 600; 53 | stroke-width: 0; 54 | text-anchor: middle; 55 | } 56 | 57 | .legend rect{ 58 | fill: none; 59 | } 60 | .legend .swatch__body{ 61 | fill: rgba(0,0,0,0); 62 | } 63 | .legend-entry__text{ 64 | dominant-baseline: central; 65 | fill: #000; 66 | } 67 | 68 | 69 | 70 | 71 | .diagram__bg{ 72 | fill: #333; 73 | } 74 | 75 | .panel--main .panel__bg{ 76 | fill: #fff; 77 | } 78 | 79 | .panel--notes .panel__bg{ 80 | fill: #e4e4e2; 81 | } 82 | 83 | .led .label__body, 84 | .gnd .label__body, 85 | .pwr .label__body, 86 | .nc .label__body, 87 | .nc .label__bodyinner, 88 | .other .label__body, 89 | .other .label__bodyinner{ 90 | rx: 10px; 91 | } 92 | 93 | .label__row .label:first-child .lline, 94 | .label.show-leader .lline{ 95 | stroke-width: 2px; 96 | stroke: #bcc6c6; 97 | } 98 | 99 | .led .label__body, 100 | .led .swatch__body{ 101 | fill: #1da086; 102 | } 103 | 104 | .mu-port .label__body, 105 | .mu-port .swatch__body{ 106 | fill: #f39c12; 107 | } 108 | .default .label__body, 109 | .default .swatch__body{ 110 | fill: #f1c40f; 111 | } 112 | .default .label__text{ 113 | fill: #000; 114 | } 115 | .digital .label__body, 116 | .digital .swatch__body{ 117 | fill: url("#stripe-a"); 118 | } 119 | 120 | #stripe-a rect{ 121 | fill: #d35400; 122 | } 123 | 124 | #stripe-a path{ 125 | fill: #de7f40; 126 | } 127 | 128 | .analog .label__body{ 129 | fill: url("#stripe-b"); 130 | } 131 | 132 | #stripe-b rect{ 133 | fill: #ffffff; 134 | } 135 | #stripe-b path{ 136 | fill: #f8e6d9; 137 | } 138 | 139 | .analog .label__bodyinner{ 140 | fill: none; 141 | stroke: #d35400; 142 | stroke-width: 2; 143 | } 144 | 145 | .analog .label__text{ 146 | fill: #d35400; 147 | } 148 | 149 | .analog .swatch__body{ 150 | fill: url("#stripe-b"); 151 | stroke: #d35400; 152 | stroke-width: 2; 153 | } 154 | .pwr .label__body, 155 | .pwr .swatch__body{ 156 | fill: #c11f09; 157 | } 158 | 159 | .other .label__body, 160 | .other .swatch__body{ 161 | fill: #fff; 162 | } 163 | 164 | .other .label__bodyinner, 165 | .other .swatch__body{ 166 | stroke: #d86e29; 167 | stroke-width: 2; 168 | } 169 | 170 | .other .label__text{ 171 | fill: #d86e29; 172 | } 173 | 174 | .gnd .label__body, 175 | .gnd .swatch__body{ 176 | fill: #000; 177 | } 178 | 179 | .internal .label__body, 180 | .internal .swatch__body{ 181 | fill: #94a3a6; 182 | } 183 | 184 | .swd .label__body, 185 | .swd .swatch__body{ 186 | fill: #9e856e; 187 | } 188 | 189 | .nc .label__body, 190 | .nc .swatch__body{ 191 | fill: #fff; 192 | } 193 | .nc .label__text{ 194 | fill: #ccc; 195 | } 196 | 197 | .nc .label__bodyinner, 198 | .nc .swatch__body{ 199 | stroke: #ccc; 200 | stroke-width: 2; 201 | } 202 | 203 | 204 | 205 | .h1{ 206 | font-size: 28px; 207 | font-weight: 900; 208 | } 209 | .p{ 210 | fill: #333; 211 | } 212 | .strong{ font-weight: bold; } 213 | .italic{ font-style: italic; } -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/arduino/arduino/uno/hardware.png -------------------------------------------------------------------------------- /samples/arduino/arduino/uno/uno_data.py: -------------------------------------------------------------------------------- 1 | ######################################################### 2 | # 3 | # Legend 4 | # 5 | ######################################################### 6 | 7 | legend = [ 8 | ("Ground", "gnd"), 9 | ("Power", "pwr"), 10 | ("LED", "led"), 11 | ("Internal Pin", "internal"), 12 | ("SWD Pin", "swd"), 13 | ("Digital Pin", "digital"), 14 | ("Analog Pin", "analog"), 15 | ("Other Pin", "other"), 16 | ("Microcontroller's Port", "mu-port"), 17 | ("Default", "default"), 18 | ] 19 | 20 | ######################################################### 21 | # 22 | # Header: Right-hand-side 23 | # 24 | ######################################################### 25 | 26 | 27 | header_rhs_a = [ 28 | [ 29 | ("D19/SCL", "digital"), 30 | ("PC5", "mu-port"), 31 | ("SCL", "default"), 32 | ], 33 | [ 34 | ("D18/SDA", "digital"), 35 | ("PC4", "mu-port"), 36 | ("SDA", "default"), 37 | ], 38 | [ 39 | ("AREF", "other"), 40 | ], 41 | [ 42 | ("GND", "gnd"), 43 | ], 44 | [ 45 | ("D13", "digital"), 46 | ("PB5", "mu-port"), 47 | ("SCK", "default"), 48 | ], 49 | [ 50 | ("D12", "digital"), 51 | ("PB4", "mu-port"), 52 | ("MISO", "default"), 53 | ], 54 | [ 55 | ("~D11", "digital"), 56 | ("PB3", "mu-port"), 57 | ("MOSI", "default"), 58 | ], 59 | [ 60 | ("~D10", "digital"), 61 | ("PB2", "mu-port"), 62 | ("SS", "default"), 63 | ], 64 | [ 65 | ("~D9", "digital"), 66 | ("PB1", "mu-port"), 67 | ], 68 | [ 69 | ("D8", "digital"), 70 | ("PB0", "mu-port"), 71 | ], 72 | ] 73 | 74 | header_rhs_b = [ 75 | [ 76 | ("D7", "digital"), 77 | ("PD7", "default"), 78 | ], 79 | [ 80 | ("~D6", "digital"), 81 | ("PD6", "default"), 82 | ], 83 | [ 84 | ("~D5", "digital"), 85 | ("PD5", "mu-port"), 86 | ], 87 | [ 88 | ("D4", "digital"), 89 | ("PD4", "mu-port"), 90 | ], 91 | [ 92 | ("~D3", "digital"), 93 | ("PD3", "mu-port"), 94 | ], 95 | [ 96 | ("D2", "digital"), 97 | ("PD2", "mu-port"), 98 | ], 99 | [ 100 | ("D1/TX", "digital"), 101 | ("PD1", "mu-port"), 102 | ], 103 | [ 104 | ("D0/RX", "digital"), 105 | ("PD0", "mu-port"), 106 | ], 107 | ] 108 | 109 | ######################################################### 110 | # 111 | # Header: Left-hand-side 112 | # 113 | ######################################################### 114 | 115 | header_lhs_a = [ 116 | [("NC", "nc")], 117 | [("IOREF", "other")], 118 | [ 119 | ("RESET", "other"), 120 | ("PC6", "default"), 121 | ], 122 | [("+3V3", "pwr")], 123 | [("+5", "pwr")], 124 | [("GND", "gnd")], 125 | [("GND", "gnd")], 126 | [("VIN", "pwr")], 127 | ] 128 | 129 | # Example of using a list comprehension to generate pin data 130 | 131 | 132 | header_lhs_b = [ 133 | [ 134 | (f"D{14 + i}", "digital"), 135 | (f"A{i}", "analog"), 136 | (f"PC{i}", "mu-port"), 137 | (f"ADC[{i}]", "default"), 138 | ] 139 | for i in range(6) 140 | ] 141 | 142 | ######################################################### 143 | # 144 | # LED labels 145 | # 146 | ######################################################### 147 | 148 | leds_a = [ 149 | [ 150 | ("RX LED", "led"), 151 | ("PD4", "default"), 152 | ], 153 | [ 154 | ("TX LED", "led"), 155 | ("PD5", "default"), 156 | ], 157 | ] 158 | 159 | 160 | leds_b = [ 161 | [ 162 | ("POWER", "led"), 163 | ], 164 | [ 165 | ("LED_BUILTIN", "led"), 166 | ("PB5", "default"), 167 | ], 168 | ] 169 | 170 | ######################################################### 171 | # 172 | # Text blocks 173 | # 174 | ######################################################### 175 | 176 | title_1 = [ 177 | 'ARDUINO', 178 | 'UNO REV3', 179 | ] 180 | para_1 = [ 181 | 'Pinout diagram created with pinout (v0.0.10)', 182 | 'A Python package for creating pinout diagrams.', 183 | 'pinout.readthedocs.io', 184 | ] 185 | para_2 = [ 186 | 'NOTE: This is not official documentation.', 187 | 'Diagram aesthetics from Arduino docs.', 188 | 'https://www.arduino.cc/', 189 | ] -------------------------------------------------------------------------------- /samples/arduino/arduino_notes.md: -------------------------------------------------------------------------------- 1 | # Arduino pinout 2 | 3 | The Arduino samples endeavour to maximise common components and styles across different diagrams. 4 | This has been done by converting files into a Python package with repeated resources grouped into a 'common' folder. 5 | Consequently some aspects of the code deviate from the official docs: 6 | 7 | + Paths are relative to the package 8 | + Use of a preprocessing function to better seperate data and configuration 9 | 10 | There are likely to be alternative ways to organise pinout to cater for mulitple related diagrams - use this example as a launching point for ideas rather than a best-practice example. 11 | 12 | Build Uno diagram, open command-line at the location of this file and enter: 13 | ```py -m pinout.manager --export arduino/uno/arduino_uno.py pinout_arduino_uno_rev3.svg -o``` 14 | 15 | 16 | Build RP2024 diagram, open command-line at the location of this file and enter: 17 | ```py -m pinout.manager --export arduino/rp2040/arduino_nano_rp2040_connect.py pinout_arduino_nano_rp2040_connect.svg -o``` 18 | 19 | -------------------------------------------------------------------------------- /samples/attiny85/attiny85.py: -------------------------------------------------------------------------------- 1 | from pinout import config 2 | from pinout.components import integrated_circuits as ic 3 | from pinout.components.legend import Legend 4 | from pinout.components.layout import Diagram, Group, Panel 5 | from pinout.components.text import TextBlock 6 | 7 | legend_data = [ 8 | ("Port", "port"), 9 | ("Analog", "adc"), 10 | ("Analog Comparator", "comparator"), 11 | ("Ground", "gnd"), 12 | ("Interrupt", "interrupt"), 13 | ("Timer-counter", "timer-counter"), 14 | ("Communications", "comms"), 15 | ("Oscillator", "oscillator"), 16 | ("Power", "pwr"), 17 | ] 18 | 19 | attiny85 = [ 20 | [ 21 | ("PB5", "port"), 22 | ("dW", "port"), 23 | ("ADC0", "adc"), 24 | ("RESET", "gnd"), 25 | ("PCINT5", "interrupt"), 26 | ], 27 | [ 28 | ("PB3", "port"), 29 | ("ADC3", "adc"), 30 | ("OC1B", "timer-counter"), 31 | ("CLKI", "comms"), 32 | ("XTAL1", "oscillator"), 33 | ("PCINT3", "interrupt"), 34 | ], 35 | [ 36 | ("PB4", "port"), 37 | ("ADC2", "adc"), 38 | ("OC1B", "timer-counter"), 39 | ("CLKO", "port"), 40 | ("XTAL2", "oscillator"), 41 | ("PCINT4", "interrupt"), 42 | ], 43 | [ 44 | ("GND", "gnd"), 45 | ], 46 | [ 47 | ("PB0", "port"), 48 | ("MOSI", "comms"), 49 | ("DI", "comms"), 50 | ("SDA", "comms"), 51 | ("AIN0", "comparator"), 52 | ("OC0A", "timer-counter"), 53 | ("OC1A", "timer-counter"), 54 | ("AREF", "pwr"), 55 | ("PCINT0", "interrupt"), 56 | ], 57 | [ 58 | ("PB1", "port"), 59 | ("MISO", "comms"), 60 | ("DO", "comms"), 61 | ("AIN1", "comparator"), 62 | ("OC0B", "timer-counter"), 63 | ("OC1A", "timer-counter"), 64 | ("PCINT1", "interrupt"), 65 | ], 66 | [ 67 | ("PB2", "port"), 68 | ("SCK", "comms"), 69 | ("USCK", "comms"), 70 | ("SCL", "comms"), 71 | ("ADC1", "adc"), 72 | ("T0", "timer-counter"), 73 | ("INT0", "interrupt"), 74 | ("PCINT2", "interrupt"), 75 | ], 76 | [ 77 | ("VCC", "pwr"), 78 | ], 79 | ] 80 | 81 | attiny85_QFN = ( 82 | attiny85[0:2] 83 | + [[("DNC", "dnc")]] * 2 84 | + attiny85[2:3] 85 | + [[("DNC", "dnc")]] * 2 86 | + attiny85[3:4] 87 | + [[("DNC", "dnc")]] * 2 88 | + attiny85[4:6] 89 | + [[("DNC", "dnc")]] 90 | + attiny85[6:] 91 | + [[("DNC", "dnc")]] * 5 92 | ) 93 | 94 | # Modify default config 95 | config.pinlabel["body"]["width"] = 50 96 | config.ic_qfp["inset"] = (3, 3, 3, 3) 97 | config.panel["inset"] = (1.5, 1.5, 1.5, 1.5) 98 | config.legend["entry"]["width"] = 200 99 | config.legend["entry"]["inset"] = 0 100 | 101 | # Add pin numbers in a list comprehension 102 | attiny85_numbered = [ 103 | [(f"{i+1}", "pin_id", {"body": {"width": 20, "height": 15}})] + row 104 | for i, row in enumerate(attiny85) 105 | ] 106 | 107 | 108 | diagram = Diagram(1200, 675) 109 | diagram.add_stylesheet("attiny_styles.css") 110 | content = diagram.add(Panel(width=1200, height=675)) 111 | dip_panel = content.add(Panel(x=0, y=0, width=content.inset_width, height=200)) 112 | qfn_panel = content.add(Panel(x=0, y=200, width=content.inset_width, height=360)) 113 | title_panel = content.add( 114 | Panel( 115 | x=0, 116 | y=qfn_panel.bounding_coords().y2, 117 | width=content.inset_width, 118 | height=content.inset_height - qfn_panel.bounding_coords().y2, 119 | tag="panel__title", 120 | ) 121 | ) 122 | 123 | dip_panel.add( 124 | TextBlock("PDIP / SOIC / TSSOP", x=10, y=30) 125 | ) 126 | dip_graphic = dip_panel.add(Group(450, 50)) 127 | dip_graphic.add( 128 | ic.labelled_dip(labels=attiny85_numbered, width=110, height=120, label_start_x=50) 129 | ) 130 | 131 | qfn_numbered = [ 132 | [(f"{i+1}", "pin_id", {"body": {"width": 20, "height": 15}})] + row 133 | for i, row in enumerate(attiny85_QFN) 134 | ] 135 | qfn_panel.add(TextBlock("QFN / MLF", x=10, y=30)) 136 | qfn_graphic = qfn_panel.add(Group(500, 110)) 137 | qfn_graphic.add(ic.labelled_qfn(labels=qfn_numbered, length=100, label_start=(50, 10))) 138 | 139 | 140 | title_panel.add( 141 | TextBlock( 142 | [ 143 | "Pinout ATtiny25/45/85", 144 | "Created with pinout Python package.", 145 | "pinout.readthedocs.io", 146 | ], 147 | x=10, 148 | y=30, 149 | ) 150 | ) 151 | title_panel.add( 152 | Legend( 153 | legend_data, 154 | max_height=96, 155 | x=title_panel.width - 614, 156 | y=14, 157 | ) 158 | ) 159 | -------------------------------------------------------------------------------- /samples/attiny85/attiny_styles.css: -------------------------------------------------------------------------------- 1 | 2 | text { 3 | font-family: Verdana, Georgia, sans-serif; 4 | font-size: 14px; 5 | font-weight: normal; 6 | } 7 | 8 | .panel__title{ 9 | font-size: 16px; 10 | } 11 | .diagram__title{ 12 | font-size: 22px; 13 | font-weight: bold; 14 | } 15 | .italic{ 16 | font-style:italic; 17 | font-weight: bold; 18 | } 19 | 20 | .panel__inner { 21 | fill: #fff; 22 | } 23 | .panel__outer { 24 | fill: #99a5b2; 25 | } 26 | .panel__title .panel__inner{ 27 | fill: #ededed; 28 | } 29 | 30 | .legendentry text { 31 | dominant-baseline: central; 32 | } 33 | 34 | .pinlabel__leader{ 35 | stroke-width: 1; 36 | fill: none; 37 | } 38 | 39 | .pinlabel__text{ 40 | dominant-baseline: central; 41 | fill: #fff; 42 | font-size: 12px; 43 | stroke-width: 0; 44 | text-anchor: middle; 45 | } 46 | 47 | .dnc .pinlabel__body{ 48 | fill: rgb(220,220,220); 49 | } 50 | .dnc .pinlabel__leader{ 51 | stroke: rgb(220,220,220); 52 | } 53 | .dnc text{ 54 | fill: #999; 55 | } 56 | .pin_id .pinlabel__body{ 57 | fill: rgb(85, 85, 85); 58 | } 59 | .pin_id .pinlabel__leader{ 60 | stroke: rgb(85, 85, 85); 61 | } 62 | .pin_id text{ 63 | font-size: 9px; 64 | } 65 | .comparator .pinlabel__body{ 66 | fill: rgb(243, 85, 191); 67 | } 68 | .comparator .pinlabel__leader{ 69 | stroke: rgb(243, 85, 191); 70 | } 71 | .comparator .swatch__body { 72 | fill: rgb(243, 85, 191); 73 | } 74 | .adc .pinlabel__body{ 75 | fill: rgb(83, 93, 222); 76 | } 77 | .adc .pinlabel__leader{ 78 | stroke: rgb(83, 93, 222); 79 | } 80 | .adc .swatch__body { 81 | fill: rgb(83, 93, 222); 82 | } 83 | .interrupt .pinlabel__body{ 84 | fill: rgb(3, 130, 92); 85 | } 86 | .interrupt .pinlabel__leader{ 87 | stroke: rgb(3, 130, 92); 88 | } 89 | .interrupt .swatch__body { 90 | fill: rgb(3, 130, 92); 91 | } 92 | .pwr .pinlabel__body{ 93 | fill: rgb(255, 53, 53); 94 | } 95 | .pwr .pinlabel__leader{ 96 | stroke: rgb(255, 53, 53); 97 | } 98 | .pwr .swatch__body { 99 | fill: rgb(255, 53, 53); 100 | } 101 | .port .pinlabel__body{ 102 | fill: rgb(177, 30, 236); 103 | } 104 | .port .pinlabel__leader{ 105 | stroke: rgb(177, 30, 236); 106 | } 107 | .port .swatch__body { 108 | fill: rgb(177, 30, 236); 109 | } 110 | .timer-counter .pinlabel__body{ 111 | fill: rgb(160, 79, 27); 112 | } 113 | .timer-counter .pinlabel__leader{ 114 | stroke: rgb(160, 79, 27); 115 | } 116 | .timer-counter .swatch__body { 117 | fill: rgb(160, 79, 27); 118 | } 119 | .comms .pinlabel__body{ 120 | fill: rgb(59, 145, 130); 121 | } 122 | .comms .pinlabel__leader{ 123 | stroke: rgb(59, 145, 130); 124 | } 125 | .comms .swatch__body { 126 | fill: rgb(59, 145, 130); 127 | } 128 | .gnd .pinlabel__body{ 129 | fill: rgb(0, 0, 0); 130 | } 131 | .gnd .pinlabel__leader{ 132 | stroke: rgb(0, 0, 0); 133 | } 134 | .gnd .swatch__body { 135 | fill: rgb(0, 0, 0); 136 | } 137 | .oscillator .pinlabel__body{ 138 | fill: rgb(124, 21, 205); 139 | } 140 | .oscillator .pinlabel__leader{ 141 | stroke: rgb(124, 21, 205); 142 | } 143 | .oscillator .swatch__body { 144 | fill: rgb(124, 21, 205); 145 | } 146 | 147 | .ic__body{ 148 | fill: rgb(85, 85, 85); 149 | stroke-width: 1px; 150 | stroke: #000; 151 | } 152 | .ic__leg .polarity{ 153 | fill: rgb(85, 85, 85); 154 | stroke-width: 2px; 155 | stroke: rgb(138, 138, 138); 156 | 157 | } 158 | .ic__leg{ 159 | fill: rgb(255, 255, 255); 160 | stroke-width: 1px; 161 | stroke: #000; 162 | } 163 | 164 | -------------------------------------------------------------------------------- /samples/clip_path/hardware_18pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/clip_path/hardware_18pin.png -------------------------------------------------------------------------------- /samples/clip_path/pinout_diagram.py: -------------------------------------------------------------------------------- 1 | ############################################################## 2 | # 3 | # Example script demonstrating clipping paths 4 | # 5 | # Export the diagram via commandline: 6 | # >>> py -m pinout.manager -e pinout_diagram.py diagram.svg 7 | # 8 | ############################################################## 9 | 10 | 11 | from pinout.core import Circle, Group, Image, Rect, ClipPath 12 | from pinout.components.layout import Diagram_2Rows 13 | from pinout.components.text import TextBlock 14 | 15 | 16 | ############################################################## 17 | # 18 | # Diagram setup 19 | # Create a layout and add an image component 20 | # 21 | # Diagram_2Rows provides 2 panels to add components, 22 | # 'panel_01' and 'panel_02'. 23 | # 24 | ############################################################## 25 | 26 | diagram = Diagram_2Rows(1200, 675, 500, tag="clip-demo") 27 | diagram.add_stylesheet("styles.css") 28 | 29 | # Components added to do not render but can be referenced 30 | # by other components. 31 | hardware_def = diagram.add_def(Image("hardware_18pin.png")) 32 | 33 | # Coordinates and sizes of intended ClipPaths can be measured 34 | # against the reference image and stored here. These coordinates 35 | # are automatically adjusted if the image is transformed. 36 | hardware_def.add_coord("led_loc", 32, 63) 37 | hardware_def.add_coord("led_size", 74, 45) 38 | hardware_def.add_coord("ic_center", 110, 176) 39 | 40 | 41 | ############################################################## 42 | # 43 | # All components have a 'clip' attribute. 44 | # Clip arguments can be an instance of a ClipPath, SvgShape, 45 | # or a list of SvgShapes. 46 | # 47 | ############################################################## 48 | 49 | # Example of applying a simple clip-path 50 | box01 = diagram.panel_01.add( 51 | Rect( 52 | x=0, 53 | y=0, 54 | width=1195, 55 | height=499, 56 | tag="box01", 57 | clip=Rect(x=10, y=10, width=589, height=489), 58 | ) 59 | ) 60 | 61 | 62 | ############################################################## 63 | # 64 | # Apply clipping to an Image 65 | # 66 | ############################################################## 67 | 68 | # Grouping components and clipping paths makes positioning easier 69 | group_overlay = diagram.panel_01.add(Group()) 70 | 71 | # Add a semi-transparent image as a base 72 | group_overlay.add(Image(hardware_def, tag="opacity_40")) 73 | 74 | # Create a clip-path 75 | image_clip_path = ClipPath(Rect(x=0, y=230, width=220, height=70)) 76 | 77 | # Create an image, applying the clip-path to it 78 | overlay_image = group_overlay.add(Image(hardware_def, clip=image_clip_path)) 79 | 80 | # Now 'group_overlay' is populated its dimensions can be calculated and 81 | # centered over 'box01'. 82 | group_overlay.x = (box01.width - group_overlay.width) / 2 83 | group_overlay.y = (box01.height - group_overlay.height) / 2 84 | 85 | 86 | ############################################################## 87 | # 88 | # Apply clipping to a Group 89 | # 90 | # Aligning the visible section of a clipped component 91 | # at at (0,0) origin can make for more intuative 92 | # layout calculations. 93 | # 94 | ############################################################## 95 | 96 | # Create a group 97 | led_detail = diagram.panel_01.add(Group()) 98 | 99 | # Create an image, adding it to 'led_detail' 100 | led_image = led_detail.add(Image(hardware_def)) 101 | 102 | # Access relevant coordinates form 'led_image'. 103 | led_x, led_y = led_image.coord("led_loc") 104 | led_w, led_h = led_image.coord("led_size", True) 105 | 106 | # Realign 'led_image' so the clipped section's top-left 107 | # aligns with its parent's origin. 108 | led_image.x = -led_x 109 | led_image.y = -led_y 110 | 111 | # Add a clip-path to 'led_detail'. 112 | led_detail.clip = Rect( 113 | width=led_w, 114 | height=led_h, 115 | ) 116 | 117 | # Locating 'led_detail' is now more intuative as 118 | # its (x,y) location matches with the visible portion 119 | # of its children 120 | 121 | led_detail.x = 837 122 | led_detail.y = 150 123 | 124 | ############################################################## 125 | # 126 | # Succinct version of previous example 127 | # 128 | ############################################################## 129 | 130 | circle_group = diagram.panel_01.add( 131 | Group( 132 | x=874, 133 | y=300, 134 | clip=Circle(cx=0, cy=0, r=68), 135 | children=[ 136 | Image( 137 | hardware_def, 138 | x=-hardware_def.coord("ic_center").x, 139 | y=-hardware_def.coord("ic_center").y, 140 | ) 141 | ], 142 | ) 143 | ) 144 | 145 | ############################################################## 146 | # 147 | # Additional non ClipPath diagram components 148 | # 149 | ############################################################## 150 | diagram.panel_02.add( 151 | TextBlock( 152 | """Pinout: ClipPath examples 153 | pinout provides an easy method to create pinout diagrams 154 | for electronic hardware. 155 | 156 | pinout.readthedocs.io""", 157 | x=20, 158 | y=30, 159 | ) 160 | ) 161 | -------------------------------------------------------------------------------- /samples/clip_path/styles.css: -------------------------------------------------------------------------------- 1 | 2 | text { 3 | font-family: Verdana, Georgia, sans-serif; 4 | font-size: 14px; 5 | font-weight: normal; 6 | } 7 | 8 | .panel__inner { 9 | fill: #fff; 10 | } 11 | .panel__outer { 12 | fill: #333; 13 | } 14 | 15 | .layout .h1{ 16 | font-size: 26px; 17 | font-weight: bold; 18 | } 19 | .layout .strong{ 20 | font-weight: bold; 21 | } 22 | .panel--info .panel__inner{ 23 | fill: #ededed; 24 | } 25 | 26 | .box01{ 27 | fill:rgb(198, 220, 233); 28 | } 29 | 30 | .opacity_40 { 31 | opacity: 0.40; 32 | } -------------------------------------------------------------------------------- /samples/full_sample/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/full_sample/__init__.py -------------------------------------------------------------------------------- /samples/full_sample/components.py: -------------------------------------------------------------------------------- 1 | # User defined components 2 | 3 | from pinout.components.pinlabel import PinLabelBody 4 | from pinout import core 5 | 6 | 7 | class LabelIn(PinLabelBody): 8 | def render(self): 9 | skew = 3 10 | body = core.Path( 11 | path_definition=" ".join( 12 | [ 13 | f"M {self.x + skew} {self.y - self.height/2}", 14 | f"l {self.width} 0", 15 | f"l {-skew*2} {self.height}", 16 | f"{-self.width} 0", 17 | "z", 18 | ] 19 | ) 20 | ) 21 | body.add_tag("label__body") 22 | return body.render() -------------------------------------------------------------------------------- /samples/full_sample/full_sample_data.py: -------------------------------------------------------------------------------- 1 | legend = [ 2 | ("Analog", "analog"), 3 | ("Communication", "comms"), 4 | ("Ground", "gnd"), 5 | ("GPIO", "gpio"), 6 | ("Touch", "touch"), 7 | ("Power", "pwr"), 8 | ("PWM", "pwm"), 9 | ] 10 | 11 | # Pinlabels 12 | 13 | lhs = [ 14 | [ 15 | ("0", "gpio"), 16 | ("A0", "analog"), 17 | ("MISO", "comms"), 18 | ], 19 | [ 20 | ("1", "gpio"), 21 | ("MOSI", "comms", {"body": {"x": 92}}), 22 | ], 23 | [ 24 | ("2", "gpio"), 25 | ("A1", "analog"), 26 | ("SCLK", "comms"), 27 | ], 28 | [ 29 | ("RESET", "pwr"), 30 | ], 31 | ] 32 | 33 | btm_lhs = [ 34 | [ 35 | ("3", "gpio"), 36 | ("A2", "analog"), 37 | ("PWM", "pwm"), 38 | ], 39 | [ 40 | ("4", "gpio"), 41 | ("A3", "analog"), 42 | ], 43 | ] 44 | 45 | btm_rhs = [ 46 | [ 47 | ("6", "gpio"), 48 | ("PWM", "pwm"), 49 | ], 50 | [ 51 | ("5", "gpio"), 52 | ("A4", "analog"), 53 | ("PWM", "pwm"), 54 | ], 55 | ] 56 | rhs = [ 57 | [ 58 | ("Vcc", "pwr"), 59 | ], 60 | [ 61 | ("GND", "gnd"), 62 | ], 63 | [ 64 | ("3", "gpio"), 65 | ("A6", "analog"), 66 | ("TOUCH", "touch"), 67 | ], 68 | [ 69 | ("4", "gpio"), 70 | ("A5", "analog"), 71 | ], 72 | ] 73 | 74 | 75 | aux = [ 76 | [ 77 | ("TOUCH", "touch"), 78 | ("A7", "analog"), 79 | ], 80 | [ 81 | ("TOUCH", "touch"), 82 | ], 83 | ] 84 | 85 | 86 | # Text 87 | 88 | annotation_usb = ["USB-C", "port"] 89 | annotation_led = ["Status", "LED"] 90 | 91 | title = "pinout" 92 | 93 | desc = """Description 94 | Demonstration diagram displaying pin-out 95 | information of non-existent hardware. 96 | Created with version 0.0.10 97 | """ 98 | 99 | notes = """Notes 100 | pinout is a Python application to assist 101 | with documentation of electronic hardware. 102 | Development is active with a goal to convert 103 | a promising idea into a useful tool. 104 | 105 | Current release: 106 | pinout.readthedocs.io""" 107 | -------------------------------------------------------------------------------- /samples/full_sample/hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/full_sample/hardware.png -------------------------------------------------------------------------------- /samples/full_sample/pinout_diagram.py: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # 3 | # Example script to build a pinout diagram 4 | # Includes examples of all basic features 5 | # 6 | # Export SVG diagram via command-line: 7 | # >>> py -m pinout.manager --export pinout_diagram.py pinout_diagram.svg -o 8 | # 9 | ########################################### 10 | from pinout import config 11 | from pinout.core import Group, Image 12 | from pinout.components.layout import Diagram, Panel 13 | from pinout.components.pinlabel import PinLabelGroup 14 | from pinout.components.annotation import AnnotationLabel 15 | from pinout.components.text import TextBlock 16 | from pinout.components import leaderline as lline 17 | from pinout.components.legend import Legend 18 | 19 | import full_sample_data as data 20 | 21 | # Edit some component default config 22 | config.panel["inset"] = (2, 2, 2, 2) 23 | 24 | # Create a new diagram, add styles and a base panel 25 | diagram = Diagram(1200, 675, "diagram") 26 | diagram.add_stylesheet("styles_auto.css", True) 27 | diagram.add_stylesheet("styles.css", True) 28 | content = diagram.add(Panel(width=1200, height=675, tag="panel__content")) 29 | 30 | 31 | # Create panels to from a graphical layout 32 | 33 | panel_graphic = content.add( 34 | Panel( 35 | width=860, 36 | height=content.inset_height, 37 | tag="panel__graphic", 38 | ) 39 | ) 40 | panel_title = content.add( 41 | Panel( 42 | x=panel_graphic.width, 43 | y=0, 44 | width=content.inset_width - panel_graphic.width, 45 | height=60, 46 | tag="panel__title", 47 | ) 48 | ) 49 | panel_legend = content.add( 50 | Panel( 51 | x=panel_graphic.width, 52 | y=panel_title.bounding_coords().y2, 53 | width=panel_title.width, 54 | height=135, 55 | tag="panel__legend", 56 | ) 57 | ) 58 | panel_description = content.add( 59 | Panel( 60 | x=panel_graphic.width, 61 | y=panel_legend.bounding_coords().y2, 62 | width=panel_title.width, 63 | height=120, 64 | tag="panel__description", 65 | ) 66 | ) 67 | panel_notes = content.add( 68 | Panel( 69 | x=panel_graphic.width, 70 | y=panel_description.bounding_coords().y2, 71 | width=panel_title.width, 72 | height=content.inset_height - panel_description.bounding_coords().y2, 73 | tag="panel__notes", 74 | ) 75 | ) 76 | 77 | # Create a group to hold the actual diagram components. 78 | graphic = panel_graphic.add(Group(318, 200)) 79 | 80 | # Add and embed an image 81 | graphic.add(Image("hardware.png", width=220, height=300, embed=True)) 82 | 83 | 84 | # Add a pinlabels to the right header 85 | graphic.add( 86 | PinLabelGroup( 87 | x=204, 88 | y=106, 89 | pin_pitch=(0, 30), 90 | label_start=(60, 0), 91 | label_pitch=(0, 30), 92 | labels=data.rhs, 93 | ) 94 | ) 95 | 96 | # Add a pinlabels to the left header 97 | graphic.add( 98 | PinLabelGroup( 99 | x=16, 100 | y=106, 101 | pin_pitch=(0, 30), 102 | label_start=(60, 0), 103 | label_pitch=(0, 30), 104 | scale=(-1, 1), 105 | labels=data.lhs, 106 | ) 107 | ) 108 | 109 | # Add a pinlabels to the left header 110 | graphic.add( 111 | PinLabelGroup( 112 | x=65, 113 | y=284, 114 | pin_pitch=(30, 0), 115 | label_start=(109, 60), 116 | label_pitch=(0, 30), 117 | scale=(-1, 1), 118 | labels=data.btm_lhs, 119 | leaderline=lline.Curved(direction="vh"), 120 | ) 121 | ) 122 | 123 | # Add a pinlabels to the left header 124 | graphic.add( 125 | PinLabelGroup( 126 | x=155, 127 | y=284, 128 | pin_pitch=(-30, 0), 129 | label_start=(109, 60), 130 | label_pitch=(0, 30), 131 | labels=data.btm_rhs, 132 | leaderline=lline.Curved(direction="vh"), 133 | ) 134 | ) 135 | 136 | graphic.add( 137 | PinLabelGroup( 138 | x=47, 139 | y=80, 140 | scale=(-1, -1), 141 | pin_pitch=(15, 15), 142 | label_start=(91, 110), 143 | label_pitch=(0, 30), 144 | labels=data.aux, 145 | leaderline=lline.Curved(direction="vh"), 146 | ) 147 | ) 148 | 149 | 150 | graphic.add( 151 | AnnotationLabel( 152 | content={"x": 102, "y": 55, "content": data.annotation_usb}, 153 | x=110, 154 | y=0, 155 | scale=(1, -1), 156 | body={"width": 125}, 157 | ) 158 | ) 159 | 160 | graphic.add( 161 | AnnotationLabel( 162 | x=87, 163 | y=85, 164 | scale=(1, -1), 165 | content={"x": 102, "y": 196, "content": data.annotation_led}, 166 | body={"y": 168, "width": 125}, 167 | target={"x": -20, "y": -20, "width": 40, "height": 40, "corner_radius": 20}, 168 | ) 169 | ) 170 | 171 | title_block = panel_title.add( 172 | TextBlock( 173 | [data.title], 174 | x=10, 175 | y=40, 176 | line_height=18, 177 | tag="panel title_block", 178 | ) 179 | ) 180 | 181 | legend = panel_legend.add( 182 | Legend( 183 | data.legend, 184 | x=10, 185 | y=5, 186 | max_height=132, 187 | ) 188 | ) 189 | 190 | description = panel_description.add( 191 | TextBlock( 192 | data.desc, 193 | x=10, 194 | y=20, 195 | line_height=18, 196 | tag="panel text_block", 197 | ) 198 | ) 199 | 200 | panel_notes.add( 201 | TextBlock( 202 | data.notes, 203 | x=10, 204 | y=20, 205 | line_height=18, 206 | tag="panel text_block", 207 | ) 208 | ) 209 | -------------------------------------------------------------------------------- /samples/full_sample/styles.css: -------------------------------------------------------------------------------- 1 | .comments__{ 2 | /* NOTE: comments in a css rule to work around an InkScape bug! */ 3 | /* 4 | 5 | Just for fun css for this diagram has been auto-generated, then enhanced with this second file. 6 | A new auto-generated CSS file can be created from the command-line: 7 | >>> py -m pinout.manager -o --css pinout_diagram styles_auto.css 8 | 9 | */ 10 | 11 | } 12 | 13 | 14 | text { 15 | font-family: Verdana, Georgia, sans-serif; 16 | font-size: 14px; 17 | font-weight: 300; 18 | dominant-baseline: auto; 19 | } 20 | .h1 { 21 | font-size: 30px; 22 | font-weight: bold; 23 | font-style: italic; 24 | } 25 | .italic{ 26 | font-style: italic; 27 | } 28 | .strong{ 29 | font-weight: bold; 30 | } 31 | .panel__title .panel__inner{ 32 | fill: rgb(253, 203, 36); 33 | font-weight: bold; 34 | font-style: italic; 35 | } -------------------------------------------------------------------------------- /samples/full_sample/styles_auto.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | text { 4 | font-family: Verdana, Georgia, sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | } 8 | 9 | 10 | 11 | .pinlabel__leader{ 12 | stroke-width: 2; 13 | fill: none; 14 | } 15 | 16 | .pinlabel__text{ 17 | dominant-baseline: central; 18 | fill: #fff; 19 | 20 | font-weight: bold; 21 | stroke-width: 0; 22 | text-anchor: middle; 23 | } 24 | 25 | 26 | 27 | .gpio .pinlabel__body{ 28 | fill: rgb(183, 76, 231); 29 | } 30 | .gpio .pinlabel__leader{ 31 | stroke: rgb(183, 76, 231); 32 | } 33 | .gpio .swatch__body { 34 | fill: rgb(183, 76, 231); 35 | } 36 | .pwr .pinlabel__body{ 37 | fill: rgb(109, 99, 25); 38 | } 39 | .pwr .pinlabel__leader{ 40 | stroke: rgb(109, 99, 25); 41 | } 42 | .pwr .swatch__body { 43 | fill: rgb(109, 99, 25); 44 | } 45 | .pwm .pinlabel__body{ 46 | fill: rgb(218, 96, 195); 47 | } 48 | .pwm .pinlabel__leader{ 49 | stroke: rgb(218, 96, 195); 50 | } 51 | .pwm .swatch__body { 52 | fill: rgb(218, 96, 195); 53 | } 54 | .gnd .pinlabel__body{ 55 | fill: rgb(25, 90, 5); 56 | } 57 | .gnd .pinlabel__leader{ 58 | stroke: rgb(25, 90, 5); 59 | } 60 | .gnd .swatch__body { 61 | fill: rgb(25, 90, 5); 62 | } 63 | .touch .pinlabel__body{ 64 | fill: rgb(200, 9, 45); 65 | } 66 | .touch .pinlabel__leader{ 67 | stroke: rgb(200, 9, 45); 68 | } 69 | .touch .swatch__body { 70 | fill: rgb(200, 9, 45); 71 | } 72 | .comms .pinlabel__body{ 73 | fill: rgb(57, 138, 188); 74 | } 75 | .comms .pinlabel__leader{ 76 | stroke: rgb(57, 138, 188); 77 | } 78 | .comms .swatch__body { 79 | fill: rgb(57, 138, 188); 80 | } 81 | .analog .pinlabel__body{ 82 | fill: rgb(96, 42, 122); 83 | } 84 | .analog .pinlabel__leader{ 85 | stroke: rgb(96, 42, 122); 86 | } 87 | .analog .swatch__body { 88 | fill: rgb(96, 42, 122); 89 | } 90 | 91 | 92 | .panel__inner { 93 | fill: #fff; 94 | } 95 | .panel__outer { 96 | fill: #333; 97 | } 98 | 99 | 100 | .legendentry text { 101 | dominant-baseline: central; 102 | } 103 | 104 | 105 | .annotation__body { 106 | fill: rgb(253, 203, 36); 107 | stroke-width: 0; 108 | } 109 | .annotation__text { 110 | dominant-baseline: central; 111 | font-size: 14px; 112 | font-weight: bold; 113 | fill: rgb(63, 40, 6); 114 | text-anchor: middle; 115 | } 116 | .annotation__target { 117 | fill: none; 118 | stroke: rgb(253, 203, 36); 119 | stroke-width: 4px; 120 | } 121 | .annotation__leaderline { 122 | stroke: rgb(253, 203, 36); 123 | stroke-width: 4px; 124 | fill: none; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /samples/panel_layout/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/panel_layout/__init__.py -------------------------------------------------------------------------------- /samples/panel_layout/auto_styles.css: -------------------------------------------------------------------------------- 1 | 2 | text { 3 | font-family: Verdana, Georgia, sans-serif; 4 | font-size: 14px; 5 | font-weight: normal; 6 | } 7 | 8 | .panel__inner { 9 | fill: #fff; 10 | } 11 | .panel__outer { 12 | fill: #333; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /samples/panel_layout/output/populated_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/panel_layout/output/populated_layout.png -------------------------------------------------------------------------------- /samples/panel_layout/panel_layout.py: -------------------------------------------------------------------------------- 1 | # Example diagram layout using Diagram and Panel classes 2 | # 3 | # export this sample via the command line: 4 | # >>> py -m pinout.manager --export panel_layout.py output/panel_layout.svg 5 | # 6 | 7 | from pinout.components.layout import Diagram, Panel 8 | 9 | # Create a blank diagram 10 | diagram = Diagram(1200, 675, tag="panel_layout") 11 | 12 | # 'auto_styles.css' was auto-generated and inserted here 13 | # *AFTER* all components were added to this script. 14 | # >>> py -m pinout.manager --css panel_layout styles.css 15 | diagram.add_stylesheet("auto_styles.css") 16 | 17 | # User defined styles can be added to the auto-generated 18 | # file or included as an additional asset. 19 | diagram.add_stylesheet("styles.css") 20 | 21 | # Panel fills entire diagram. 22 | # All other panels will be added to this one. 23 | panel_00 = diagram.add(Panel(1200, 675, (2, 2, 2, 2))) 24 | 25 | # Banner panel 26 | panel_banner = panel_00.add( 27 | Panel( 28 | width=panel_00.inset_width, 29 | height=50, 30 | tag="panel--banner", 31 | ) 32 | ) 33 | 34 | # Main panel 35 | panel_main = panel_00.add( 36 | Panel( 37 | width=panel_00.inset_width * 2 / 3, 38 | height=500, 39 | x=0, 40 | y=panel_banner.height, 41 | tag="panel--main", 42 | ) 43 | ) 44 | 45 | # Detail panel 46 | # This component is a wrapper for easier alignment 47 | panel_details = panel_00.add( 48 | Panel( 49 | x=panel_main.width, 50 | y=panel_banner.height, 51 | width=panel_00.inset_width - panel_main.width, 52 | height=panel_main.height, 53 | inset=(0, 0, 0, 0), 54 | tag="panel--detail", 55 | ) 56 | ) 57 | 58 | # x3 'sub' panels 59 | panel_detail_01 = panel_details.add( 60 | Panel( 61 | x=0, 62 | y=0, 63 | width=panel_details.width, 64 | height=panel_details.height / 3, 65 | tag="panel--detail", 66 | ) 67 | ) 68 | panel_detail_02 = panel_details.add( 69 | Panel( 70 | x=0, 71 | y=panel_detail_01.bounding_coords().y2, 72 | width=panel_details.width, 73 | height=panel_details.height / 3, 74 | tag="panel--detail", 75 | ) 76 | ) 77 | 78 | panel_detail_03 = panel_details.add( 79 | Panel( 80 | x=0, 81 | y=panel_detail_02.bounding_coords().y2, 82 | width=panel_details.width, 83 | height=panel_details.height / 3, 84 | tag="panel--detail", 85 | ) 86 | ) 87 | 88 | # Footer panel 89 | panel_footer = panel_00.add( 90 | Panel( 91 | x=0, 92 | y=panel_main.bounding_coords().y2, 93 | width=panel_00.inset_width, 94 | height=panel_00.inset_height - panel_main.bounding_coords().y2, 95 | tag="panel--footer", 96 | ) 97 | ) 98 | -------------------------------------------------------------------------------- /samples/panel_layout/populated_layout.py: -------------------------------------------------------------------------------- 1 | # 2 | # Example: Inserting content into 'panel_layout' 3 | # 4 | # export this sample via the command line: 5 | # >>> py -m pinout.manager --export populated_layout.py output/populated_layout.svg 6 | # 7 | 8 | from pinout.components.text import TextBlock 9 | import panel_layout as layout 10 | 11 | # pinout expects to see 'diagram' here when exporting 12 | from panel_layout import diagram 13 | 14 | # All of the content panels are now available for content to be added 15 | layout.panel_banner.add(TextBlock(content="Banner", x=20, y=40)) 16 | layout.panel_main.add(TextBlock(content="Main", x=20, y=40)) 17 | layout.panel_detail_01.add(TextBlock(content="Detail 01", x=20, y=40)) 18 | layout.panel_detail_02.add(TextBlock(content="Detail 02", x=20, y=40)) 19 | layout.panel_detail_03.add(TextBlock(content="Detail 03", x=20, y=40)) 20 | layout.panel_footer.add(TextBlock(content="Footer", x=20, y=40)) 21 | -------------------------------------------------------------------------------- /samples/panel_layout/readme.md: -------------------------------------------------------------------------------- 1 | # Example: Panel layout 2 | 3 | The Diagram and Panel classes can be directly used to create custom layouts or build upon diagram sub-classes. 4 | 5 | Dimensions and position of each panel can be manually calculated and entered. Alternatively, as done in this example, many of these details reference other panels. This setup allows for easier layout adjustments. As critical layout dimensions (*panel_banner.height*, *panel_main.width* and *panel_main.height*) are altered other panels automatically resize to fit. 6 | 7 | ## Output 8 | 9 | Now the layout has been completed further content can in added to the Panel instances creating a finished diagram featuring multiple sections (output from 'populated_layout.py'). 10 | 11 | ![diagram featuring customised panel layout](output/populated_layout.png) 12 | -------------------------------------------------------------------------------- /samples/panel_layout/styles.css: -------------------------------------------------------------------------------- 1 | .panel--banner .panel__inner, 2 | .panel--footer .panel__inner{ 3 | fill: #333; 4 | } 5 | 6 | .panel--detail .panel__inner{ 7 | fill: #ededed; 8 | } -------------------------------------------------------------------------------- /samples/pci-express/autostyles.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | text { 4 | font-family: Verdana, Georgia, sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | } 8 | 9 | 10 | 11 | .pinlabel__leader{ 12 | stroke-width: 1; 13 | fill: none; 14 | } 15 | 16 | .pinlabel__text{ 17 | dominant-baseline: central; 18 | fill: #000; 19 | font-size: 12px; 20 | 21 | font-weight: normal; 22 | stroke-width: 0; 23 | text-anchor: middle; 24 | } 25 | 26 | 27 | 28 | .pinid .pinlabel__body, 29 | .pinid .swatch__body{ 30 | fill: rgb(0, 0, 0); 31 | } 32 | .pinid .pinlabel__leader{ 33 | stroke: rgb(0, 0, 0); 34 | } 35 | .pinid .pinlabel__text{ 36 | fill: #fff; 37 | } 38 | 39 | .host-to-card .pinlabel__body, 40 | .host-to-card .swatch__body{ 41 | fill: rgb(83, 102, 165); 42 | } 43 | .host-to-card .pinlabel__leader{ 44 | stroke: rgb(83, 102, 165); 45 | } 46 | 47 | 48 | .card-to-host .pinlabel__body, 49 | .card-to-host .swatch__body{ 50 | fill: rgb(145, 79, 158); 51 | } 52 | .card-to-host .pinlabel__leader{ 53 | stroke: rgb(145, 79, 158); 54 | } 55 | 56 | .open-drain .pinlabel__body, 57 | .open-drain .swatch__body{ 58 | fill: rgb(158, 121, 79); 59 | } 60 | .open-drain .pinlabel__leader{ 61 | stroke: rgb(158, 121, 79); 62 | } 63 | 64 | .sense-pin .pinlabel__body, 65 | .sense-pin .swatch__body{ 66 | fill: rgb(75, 185, 31); 67 | } 68 | .sense-pin .pinlabel__leader{ 69 | stroke: rgb(75, 185, 31); 70 | } 71 | 72 | 73 | .gnd .pinlabel__body, 74 | .gnd .swatch__body{ 75 | fill: rgb(100, 100, 100); 76 | } 77 | .gnd .pinlabel__leader{ 78 | stroke: rgb(100, 100, 100); 79 | } 80 | 81 | 82 | .pwr .pinlabel__body{ 83 | fill: rgb(163, 34, 34); 84 | } 85 | .pwr .pinlabel__leader{ 86 | stroke: rgb(163, 34, 34); 87 | } 88 | .pwr .swatch__body { 89 | fill: rgb(163, 34, 34); 90 | } 91 | .pinb .pinlabel__body{ 92 | fill: rgb(99, 88, 162); 93 | } 94 | .pinb .pinlabel__leader{ 95 | stroke: rgb(99, 88, 162); 96 | } 97 | .pinb .swatch__body { 98 | fill: rgb(99, 88, 162); 99 | } 100 | 101 | 102 | .panel__inner { 103 | fill: #fff; 104 | } 105 | .panel__outer { 106 | fill: #333; 107 | } 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /samples/pci-express/pci-express_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/pci-express/pci-express_data.xlsx -------------------------------------------------------------------------------- /samples/pci-express/pci-express_x1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /samples/pci-express/pci_data.py: -------------------------------------------------------------------------------- 1 | # Example of importing data from a spreadsheet 2 | # 3 | # Python has multiple options for reading popular spreadsheet formats. 4 | # This example uses Pandas in conjunction with openpyxl. 5 | # Installation of these packages can be done via the command line. 6 | # (**If you are using a virtual environment, ensure it is activated): 7 | # >>> pip install pandas 8 | # >>> pip install openpyxl 9 | import pandas as pd 10 | 11 | 12 | def assign_css(text): 13 | # Rather than alter then source data css classes can be 14 | # figured out and assigned here 15 | if text == "Ground": 16 | return "gnd" 17 | elif text in ["SMCLK", "SMDAT", "WAKE#", "PERST#", "CLKREQ#", "PWRBRK#"]: 18 | return "open-drain" 19 | elif text.startswith("+"): 20 | return "pwr" 21 | elif text.startswith("HSO") or text.startswith("REFCLK"): 22 | return "host-to-card" 23 | elif text.startswith("HSI") or text == "TDO": 24 | return "card-to-host" 25 | elif text.startswith("PRSNT"): 26 | return "sense-pin" 27 | return "reserved" 28 | 29 | 30 | def get_from_xlsx(filepath): 31 | # read the excel spreadsheet into a 'dataframe' 32 | df = pd.read_excel(filepath, engine="openpyxl") 33 | 34 | # Remove the Description column as we are not using it 35 | df.drop("Description", axis=1, inplace=True) 36 | 37 | # Remove the rows that have notes (rather than data). 38 | # Luckily, those rows all have 'None' in at least one column 39 | df.dropna(inplace=True) 40 | 41 | # Problem characters are easy to replace with Pandas. 42 | # In this instance a minus sign is causing grief. 43 | df["Side A"] = df["Side A"].str.replace("−", "−") 44 | 45 | # Parse the data for use with pinout 46 | data = df.values.tolist() 47 | data = [ 48 | [ 49 | (f"{i}", "pinid", {"body": {"width": 30}}), 50 | (f"{b}", assign_css(b)), 51 | (f"{a}", assign_css(a)), 52 | ] 53 | for (i, b, a) in data 54 | ] 55 | 56 | return data 57 | 58 | 59 | if __name__ == "__main__": 60 | get_from_xlsx("pci-express_data.xlsx") 61 | -------------------------------------------------------------------------------- /samples/pci-express/pinout_x1.py: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | # 3 | # 4 | # This sample is a WORK IN PROGRESS! 5 | # 6 | # ...It should work but might be a bit 7 | # rough around the edges. 8 | # 9 | ##################################################### 10 | 11 | from pinout.components.layout import Diagram, Panel 12 | from pinout.core import Group, Image 13 | from pinout.components.pinlabel import PinLabelGroup 14 | from pinout import config 15 | 16 | # Python has multiple options for reading popular spreadsheet formats. 17 | # This example uses Pandas in conjunction with openpyxl. 18 | # Installation of these packages can be done via the command line. 19 | # (**If you are using a virtual environment, ensure it is activated): 20 | # >>> pip install pandas 21 | # >>> pip install openpyxl 22 | 23 | # Export: 24 | # >>> py -m pinout.manager -e pinout_x1.py pinout_x1.svg 25 | 26 | import pci_data as data 27 | 28 | # configuration customsations 29 | config.pinlabel["body"]["corner_radius"] = 0 30 | config.pinlabel["body"]["height"] = 28 31 | 32 | diagram = Diagram(1200, 675, "diagram") 33 | diagram.add_stylesheet("autostyles.css") 34 | content = diagram.add(Panel(width=1200, height=675, tag="panel__content")) 35 | 36 | graphic = content.add(Group(30, 10)) 37 | graphic.add(Image("pci-express_x1.svg", width=94, height=264, embed=True, scale=(2, 2))) 38 | graphic.add( 39 | PinLabelGroup( 40 | x=80 * 2, 41 | y=64.5 * 2, 42 | pin_pitch=(0, 9 * 2), 43 | label_start=(100, -40), 44 | label_pitch=(0, 30), 45 | labels=data.get_from_xlsx("pci-express_data.xlsx")[0:11], 46 | ) 47 | ) 48 | graphic.add( 49 | PinLabelGroup( 50 | x=80 * 2, 51 | y=181 * 2, 52 | pin_pitch=(0, 9 * 2), 53 | label_start=(100, -181 * 2 + 64.5 * 2 + 30 * 10), 54 | label_pitch=(0, 30), 55 | labels=data.get_from_xlsx("pci-express_data.xlsx")[11:18], 56 | ) 57 | ) 58 | -------------------------------------------------------------------------------- /samples/section_pullout/hardware_18pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/samples/section_pullout/hardware_18pin.png -------------------------------------------------------------------------------- /samples/section_pullout/pinout_diagram.py: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # 3 | # Example script to build a pinout diagram 4 | # featuring a detail 'pull-out'. 5 | # 6 | # Export the diagram via commandline: 7 | # >>> py -m pinout.manager --export pinout_diagram.py diagram.svg 8 | # 9 | ########################################### 10 | 11 | from pinout.core import Group, Image, Rect, ClipPath 12 | from pinout.components.annotation import AnnotationLabel 13 | from pinout.components.layout import Diagram, Panel 14 | from pinout.components.pinlabel import PinLabelGroup 15 | from pinout.components.text import TextBlock 16 | from pinout.components.legend import Legend 17 | 18 | 19 | # Import data for the diagram 20 | import section_pullout_data as data 21 | 22 | 23 | ############################################################## 24 | # 25 | # Create a diagram and layout 26 | # 27 | ############################################################## 28 | 29 | diagram = Diagram(1024, 576, "diagram") 30 | 31 | # Add stylesheet 32 | diagram.add_stylesheet("styles.css") 33 | 34 | # Add hardware image 35 | # This will be referenced multiple times so 36 | # storing a reference copy in diagram.defs 37 | hardware_img = diagram.add_def(Image("hardware_18pin.png")) 38 | 39 | # Create the layout 40 | content = diagram.add( 41 | Panel( 42 | width=1024, 43 | height=576, 44 | inset=(2, 2, 2, 2), 45 | ) 46 | ) 47 | panel_main = content.add( 48 | Panel( 49 | x=0, 50 | y=0, 51 | width=content.inset_width // 3, 52 | height=440, 53 | inset=(2, 2, 2, 2), 54 | tag="panel--main", 55 | ) 56 | ) 57 | panel_detail = content.add( 58 | Panel( 59 | x=panel_main.width, 60 | y=0, 61 | width=content.inset_width - panel_main.width, 62 | height=440, 63 | inset=(2, 2, 2, 2), 64 | tag="panel--detail", 65 | ) 66 | ) 67 | panel_info = content.add( 68 | Panel( 69 | x=0, 70 | y=panel_main.height, 71 | width=content.inset_width, 72 | height=content.inset_height - panel_main.height, 73 | inset=(2, 2, 2, 2), 74 | tag="panel--info", 75 | ) 76 | ) 77 | 78 | 79 | ############################################################## 80 | # 81 | # Measuring key coordinates on hardware image 82 | # 83 | ############################################################## 84 | 85 | hardware_img.add_coord("pin5", 65, 253) 86 | hardware_img.add_coord("pin9", 65, 284) 87 | hardware_img.add_coord("annotation", 110, 268) 88 | hardware_img.add_coord("pin_pitch_v", 0, 30) 89 | 90 | ############################################################## 91 | # 92 | # Construct pinout graphic with annotation label 93 | # 94 | ############################################################## 95 | 96 | # Create a group to hold the annotated image. 97 | group_annotations = panel_main.add( 98 | Group( 99 | x=panel_main.width // 2, 100 | y=20, 101 | ) 102 | ) 103 | 104 | # Add an image to 'group_annotations' 105 | # It has been offset here for easy center alignment 106 | hardware = group_annotations.add(Image(hardware_img, x=-110)) 107 | 108 | # Add an annotation to 'group_annotations' 109 | # Its positioning uses transformed coordinates from 'hardware' 110 | # A custom body and target have been supplied to suit requirements. 111 | group_annotations.add( 112 | AnnotationLabel( 113 | x=hardware.coord("annotation").x, 114 | y=hardware.coord("annotation").y, 115 | content="Section A", 116 | body={"x": 20, "y": 60, "width": 120}, 117 | target={"x": -120, "y": -40, "width": 240, "height": 80}, 118 | scale=(-1, 1), 119 | ) 120 | ) 121 | 122 | 123 | ############################################################## 124 | # 125 | # Construct a 'pull-out' graphic with pin-labels 126 | # 127 | ############################################################## 128 | 129 | # Create a title and text for the diagram section 130 | panel_detail.add(TextBlock(content=data.section_a_text, x=20, y=40)) 131 | 132 | 133 | # Create a group to hold components for a second graphic 134 | group_detail = panel_detail.add(Group(tag="graphic-detail")) 135 | 136 | # Add a rotated instance of 'hardware_img'. 137 | # It has been offset (x=300) so its origin 138 | # is still at the components top-left. 139 | hardware_sm = group_detail.add( 140 | Image( 141 | hardware_img, 142 | x=300, 143 | rotate=90, 144 | ) 145 | ) 146 | 147 | # Create and apply a clipping path to 'hardware_sm' 148 | hardware_sm.clip = ClipPath(Rect(x=0, y=0, width=80, height=220)) 149 | 150 | 151 | # Add pin-labels to the x2 rows of pin headers 152 | group_detail.add( 153 | PinLabelGroup( 154 | x=hardware_sm.coord("pin5").x, 155 | y=hardware_sm.coord("pin5").y, 156 | pin_pitch=hardware_sm.coord("pin_pitch_v", True), 157 | label_start=(60, 0), 158 | label_pitch=(0, 30), 159 | scale=(1, 1), 160 | labels=data.lower_header_in, 161 | ) 162 | ) 163 | group_detail.add( 164 | PinLabelGroup( 165 | x=hardware_sm.coord("pin9").x, 166 | y=hardware_sm.coord("pin9").y, 167 | scale=(-1, 1), 168 | pin_pitch=hardware_sm.coord("pin_pitch_v", True), 169 | label_start=(60, 0), 170 | label_pitch=(0, 30), 171 | labels=data.lower_header_out, 172 | ) 173 | ) 174 | 175 | 176 | # With content added the width of 'group_detail' can be 177 | # calculated and the component aligned in its panel. 178 | group_detail.x = ( 179 | panel_detail.inset_width - group_detail.width 180 | ) / 2 - group_detail.bounding_rect().x 181 | 182 | group_detail.y = (panel_detail.inset_height - group_detail.height) / 2 183 | 184 | 185 | ############################################################## 186 | # 187 | # Add content to the title bar 188 | # 189 | ############################################################## 190 | 191 | # Create a title and a text-block 192 | title_block = panel_info.add( 193 | TextBlock( 194 | data.title, 195 | x=20, 196 | y=30, 197 | line_height=18, 198 | tag="panel title_block", 199 | ) 200 | ) 201 | panel_info.add( 202 | TextBlock( 203 | data.description, 204 | x=20, 205 | y=60, 206 | width=title_block.width, 207 | height=panel_info.height - title_block.height, 208 | line_height=18, 209 | tag="panel text_block", 210 | ) 211 | ) 212 | 213 | # Create a legend 214 | legend = panel_info.add( 215 | Legend( 216 | data.legend, 217 | x=640, 218 | y=8, 219 | max_height=132, 220 | ) 221 | ) 222 | -------------------------------------------------------------------------------- /samples/section_pullout/section_pullout_data.py: -------------------------------------------------------------------------------- 1 | legend = [ 2 | ("Analog", "analog"), 3 | ("I2C", "i2c"), 4 | ("GPIO", "gpio"), 5 | ("Touch", "touch"), 6 | ("PWM", "pwm"), 7 | ("Power", "pwr"), 8 | ("Ground", "gnd"), 9 | ] 10 | 11 | # Pinlabels 12 | 13 | left_header = [ 14 | [ 15 | ("0", "gpio"), 16 | ("A0", "analog"), 17 | ("MISO", "comms"), 18 | ], 19 | [ 20 | ("1", "gpio"), 21 | ("MOSI", "comms", {"body": {"x": 92}}), 22 | ], 23 | [ 24 | ("2", "gpio"), 25 | ("A1", "analog"), 26 | ("SCLK", "comms"), 27 | ], 28 | [ 29 | ("3", "gpio"), 30 | ("PWM", "pwm"), 31 | ], 32 | ] 33 | 34 | lower_header_out = [ 35 | [ 36 | ("VBAT", "pwr"), 37 | ], 38 | [ 39 | ("3.3V", "pwr"), 40 | ], 41 | [ 42 | ("GND", "gnd"), 43 | ], 44 | [ 45 | ("AREF", "pwr"), 46 | ], 47 | ] 48 | lower_header_in = [ 49 | [ 50 | ("A2", "analog"), 51 | ("TOUCH", "touch"), 52 | ], 53 | [ 54 | ("PWM", "pwm"), 55 | ], 56 | [ 57 | ("6", "gpio"), 58 | ("SDA1", "i2c"), 59 | ], 60 | [ 61 | ("A5", "analog"), 62 | ("SCL1", "i2c"), 63 | ], 64 | ] 65 | 66 | right_header = [ 67 | [ 68 | ("Vcc", "pwr"), 69 | ], 70 | [ 71 | ("GND", "gnd"), 72 | ], 73 | [ 74 | ("RESET", "reset"), 75 | ], 76 | [ 77 | ("8", "gpio"), 78 | ("A6", "analog"), 79 | ], 80 | ] 81 | 82 | 83 | # Text 84 | 85 | title = "Pinout sample: Section pull-out" 86 | 87 | description = """Pinout is a Python tool kit to assist with documentation of electronic hardware. 88 | This sample demonstrates documenting a section of hardware. 89 | More info at pinout.readthedocs.io""" 90 | 91 | section_a_text = """Section A 92 | Detail of double row header.""" -------------------------------------------------------------------------------- /samples/section_pullout/styles.css: -------------------------------------------------------------------------------- 1 | text { 2 | font-family: Verdana, Georgia, sans-serif; 3 | font-size: 14px; 4 | font-weight: normal; 5 | } 6 | 7 | .pinlabel__leader{ 8 | stroke-width: 2; 9 | fill: none; 10 | } 11 | 12 | .pinlabel__text{ 13 | dominant-baseline: central; 14 | fill: #fff; 15 | font-weight: bold; 16 | stroke-width: 0; 17 | text-anchor: middle; 18 | } 19 | 20 | .pwr .pinlabel__body{ 21 | fill: rgb(173, 0, 0); 22 | } 23 | .pwr .pinlabel__leader{ 24 | stroke: rgb(173, 0, 0); 25 | } 26 | .pwr .swatch__body { 27 | fill: rgb(173, 0, 0); 28 | } 29 | .i2c .pinlabel__body{ 30 | fill: rgb(182, 69, 176); 31 | } 32 | .i2c .pinlabel__leader{ 33 | stroke: rgb(182, 69, 176); 34 | } 35 | .i2c .swatch__body { 36 | fill: rgb(182, 69, 176); 37 | } 38 | .gpio .pinlabel__body{ 39 | fill: rgb(160, 133, 26); 40 | } 41 | .gpio .pinlabel__leader{ 42 | stroke: rgb(160, 133, 26); 43 | } 44 | .gpio .swatch__body { 45 | fill: rgb(160, 133, 26); 46 | } 47 | .analog .pinlabel__body{ 48 | fill: rgb(32, 150, 165); 49 | } 50 | .analog .pinlabel__leader{ 51 | stroke: rgb(32, 150, 165); 52 | } 53 | .analog .swatch__body { 54 | fill: rgb(32, 150, 165); 55 | } 56 | .gnd .pinlabel__body{ 57 | fill: rgb(0, 0, 0); 58 | } 59 | .gnd .pinlabel__leader{ 60 | stroke: rgb(0, 0, 0); 61 | } 62 | .gnd .swatch__body { 63 | fill: rgb(0, 0, 0); 64 | } 65 | .pwm .pinlabel__body{ 66 | fill: rgb(151, 76, 23); 67 | } 68 | .pwm .pinlabel__leader{ 69 | stroke: rgb(151, 76, 23); 70 | } 71 | .pwm .swatch__body { 72 | fill: rgb(151, 76, 23); 73 | } 74 | .touch .pinlabel__body{ 75 | fill: rgb(230, 87, 10); 76 | } 77 | .touch .pinlabel__leader{ 78 | stroke: rgb(230, 87, 10); 79 | } 80 | .touch .swatch__body { 81 | fill: rgb(230, 87, 10); 82 | } 83 | 84 | .panel__inner { 85 | fill: #fff; 86 | } 87 | .panel__outer { 88 | fill: #333; 89 | } 90 | 91 | .legendentry text { 92 | dominant-baseline: central; 93 | } 94 | 95 | .h1 { 96 | font-size: 26px; 97 | font-weight: bold; 98 | font-style: italic; 99 | } 100 | .italic{ 101 | font-style: italic; 102 | } 103 | .strong{ 104 | font-weight: bold; 105 | } 106 | 107 | .panel--info .panel__inner{ 108 | 109 | fill: #f4f4f4; 110 | } 111 | 112 | 113 | .annotation__body { 114 | fill: rgb(253, 203, 36); 115 | } 116 | .annotation__text text { 117 | dominant-baseline: central; 118 | font-size: 18px; 119 | font-weight: bold; 120 | text-anchor: middle; 121 | } 122 | .annotation__target { 123 | fill: none; 124 | stroke: rgb(253, 203, 36); 125 | stroke-width: 4px; 126 | } 127 | .annotation__leaderline { 128 | stroke: rgb(253, 203, 36); 129 | stroke-width: 4px; 130 | fill: none; 131 | } 132 | .section_title{ 133 | font-size: 20px; 134 | font-weight: bold; 135 | } -------------------------------------------------------------------------------- /samples/teensy_4.0/pinout_diagram.py: -------------------------------------------------------------------------------- 1 | # Teensy 4.0 Pinout 2 | # https://www.pjrc.com/store/teensy40.html 3 | 4 | 5 | # Export final SVG diagram from command-line 6 | # py -m pinout.manager -e pinout_diagram.py teensy_4.0_front_pinout_diagram.svg 7 | 8 | # NOTE: this sample requires the python package libsass. 9 | # install via pip: 10 | # >>> pip install libsass 11 | 12 | from pinout.core import Group, Image 13 | from pinout import config 14 | from pinout.components.layout import Diagram, Panel 15 | from pinout.components.pinlabel import PinLabelGroup 16 | from pinout.components.text import TextBlock 17 | from pinout.components import leaderline as lline 18 | 19 | 20 | import teensy_4_data as data 21 | 22 | # Override default config settings 23 | config.pinlabel["body"]["x"] = 0 24 | config.pinlabel["body"]["height"] = 28 25 | config.pinlabel["body"]["corner_radius"] = 0 26 | 27 | ################################################ 28 | # EXPERIMENTAL: DATA PREPROCESSOR 29 | # modify data based on its tag name 30 | # to further minimise config in data 31 | 32 | 33 | def update_config(source, update): 34 | for k, v in update.items(): 35 | if type(v) is dict: 36 | source[k] = source.get(k, {}) 37 | update_config(source[k], v) 38 | else: 39 | source[k] = v 40 | return source 41 | 42 | 43 | def data_preprocessor(data_list): 44 | for row in data_list: 45 | for i, label in enumerate(row): 46 | try: 47 | data_config = label[2] 48 | except IndexError: 49 | data_config = {} 50 | 51 | config = {} 52 | if label[1] in ["digital", "analog"]: 53 | config = {"body": {"width": 40}} 54 | elif label[1] in ["pwm"]: 55 | config = {"body": {"width": 60}} 56 | 57 | update_config(config, data_config) 58 | 59 | row[i] = (label[0], label[1], config) 60 | yield row 61 | 62 | 63 | ################################################ 64 | # Using SASS here to try out some 65 | # more succinct style documentation. 66 | # It is not required for pinout but 67 | # might suit some workflows. 68 | 69 | # build css from scss 70 | import sass 71 | 72 | with open("styles.scss", "r") as f: 73 | styles = sass.compile(string=f.read()) 74 | 75 | with open("styles.css", "w") as f: 76 | f.write(styles) 77 | 78 | ################################################ 79 | 80 | # Create a new diagram and add a background 81 | # Official Teensy pinout cards are 1.41 ratio (or very close) 82 | 83 | diagram = Diagram(1128, 800, "diagram") 84 | 85 | # Add a stylesheet 86 | diagram.add_stylesheet("styles.css", True) 87 | 88 | # Create a layout 89 | diagram_inner = diagram.add( 90 | Panel( 91 | x=0, 92 | y=0, 93 | width=1128, 94 | height=800, 95 | inset=(10, 10, 10, 10), 96 | ) 97 | ) 98 | 99 | panel_main = diagram_inner.add( 100 | Panel( 101 | x=0, 102 | y=0, 103 | width=960, 104 | height=640, 105 | inset=(1, 1, 1, 1), 106 | tag="mainpanel", 107 | ) 108 | ) 109 | 110 | # Create a group to hold the actual diagram components. 111 | graphic = panel_main.add(Group(340, 25, scale=(1.1, 1.1))) 112 | 113 | # Custom legend 114 | legend = diagram_inner.add( 115 | Panel( 116 | x=panel_main.bounding_coords().x2, 117 | y=0, 118 | width=diagram_inner.inset_width - panel_main.width, 119 | height=diagram_inner.inset_height, 120 | inset=(1, 1, 1, 1), 121 | tag="legend", 122 | ) 123 | ) 124 | 125 | # Title bar 126 | titlebar = diagram_inner.add( 127 | Panel( 128 | x=0, 129 | y=panel_main.height, 130 | width=panel_main.width, 131 | height=diagram_inner.inset_height - panel_main.height, 132 | inset=(1, 1, 1, 1), 133 | tag="titlebar", 134 | ) 135 | ) 136 | 137 | # Add and embed an image 138 | graphic.add( 139 | Image( 140 | "hardware_teensy_4.0_front.svg", 141 | x=-8.5, 142 | y=-7.5, 143 | width=210, 144 | height=340, 145 | embed=True, 146 | ) 147 | ) 148 | 149 | # Pinlabels: Right header 150 | graphic.add( 151 | PinLabelGroup( 152 | x=195, 153 | y=32, 154 | pin_pitch=(0, 30), 155 | label_start=(40, 0), 156 | label_pitch=(0, 30), 157 | labels=data_preprocessor(data.header_rhs), 158 | tag="pingroup", 159 | ) 160 | ) 161 | 162 | # Pinlabels: Left header 163 | graphic.add( 164 | PinLabelGroup( 165 | x=15, 166 | y=32, 167 | scale=(-1, 1), 168 | pin_pitch=(0, 30), 169 | label_start=(40, 0), 170 | label_pitch=(0, 30), 171 | labels=data_preprocessor(data.header_lhs), 172 | tag="pingroup", 173 | ) 174 | ) 175 | 176 | # Pinlabels: End, left side 177 | graphic.add( 178 | PinLabelGroup( 179 | x=45, 180 | y=422, 181 | scale=(-1, 1), 182 | pin_pitch=(30, 0), 183 | label_start=(70, 30), 184 | label_pitch=(0, 30), 185 | labels=data.header_end_lhs, 186 | leaderline=lline.Curved(direction="vh"), 187 | tag="pingroup", 188 | ) 189 | ) 190 | 191 | # Pinlabels: End, right side 192 | graphic.add( 193 | PinLabelGroup( 194 | x=135, 195 | y=422, 196 | pin_pitch=(30, 0), 197 | label_start=(100, 90), 198 | label_pitch=(0, -30), 199 | labels=data.header_end_rhs, 200 | leaderline=lline.Curved(direction="vh"), 201 | tag="pingroup", 202 | ) 203 | ) 204 | 205 | # Legend entries 206 | entry_count = len(data.legend_content) 207 | entry_height = legend.inset_height / entry_count 208 | for index, (entry_text, tag) in enumerate(data.legend_content): 209 | legend_entry = legend.add( 210 | Panel( 211 | x=0, 212 | y=entry_height * index, 213 | width=legend.inset_width, 214 | height=entry_height, 215 | inset=(1, 1, 1, 1), 216 | tag=f"{tag} legend__entry", 217 | ) 218 | ) 219 | legend_entry.add( 220 | TextBlock( 221 | entry_text, 222 | x=10, 223 | y=22, 224 | line_height=19, 225 | ) 226 | ) 227 | 228 | titlebar.add( 229 | TextBlock( 230 | data.title, 231 | x=20, 232 | y=38, 233 | line_height=16, 234 | tag="h1", 235 | ) 236 | ) 237 | titlebar.add( 238 | TextBlock( 239 | data.title_2, 240 | x=20, 241 | y=60, 242 | line_height=18, 243 | tag="h2", 244 | ) 245 | ) 246 | titlebar.add( 247 | TextBlock( 248 | data.instructions, 249 | x=20, 250 | y=100, 251 | line_height=18, 252 | tag="p", 253 | ) 254 | ) 255 | titlebar.add( 256 | TextBlock( 257 | data.notes, 258 | x=395, 259 | y=60, 260 | line_height=18, 261 | tag="p", 262 | ) 263 | ) 264 | -------------------------------------------------------------------------------- /samples/teensy_4.0/styles.css: -------------------------------------------------------------------------------- 1 | .gnd .pinlabel__body, 2 | .gnd .textblock__bg { 3 | fill: #000; } 4 | 5 | .gnd .pinlabel__leader { 6 | stroke: #000; } 7 | 8 | .gnd.legend__entry .panel__inner { 9 | fill: #000; } 10 | 11 | .pwr .pinlabel__body, 12 | .pwr .textblock__bg { 13 | fill: #ad0000; } 14 | 15 | .pwr .pinlabel__leader { 16 | stroke: #ad0000; } 17 | 18 | .pwr.legend__entry .panel__inner { 19 | fill: #ad0000; } 20 | 21 | .digital .pinlabel__body, 22 | .digital .textblock__bg { 23 | fill: #cfd5d5; } 24 | 25 | .digital .pinlabel__leader { 26 | stroke: #cfd5d5; } 27 | 28 | .digital.legend__entry .panel__inner { 29 | fill: #cfd5d5; } 30 | 31 | .analog .pinlabel__body, 32 | .analog .textblock__bg { 33 | fill: #ffd7ad; } 34 | 35 | .analog .pinlabel__leader { 36 | stroke: #ffd7ad; } 37 | 38 | .analog.legend__entry .panel__inner { 39 | fill: #ffd7ad; } 40 | 41 | .pwm .pinlabel__body, 42 | .pwm .textblock__bg { 43 | fill: #ffb3aa; } 44 | 45 | .pwm .pinlabel__leader { 46 | stroke: #ffb3aa; } 47 | 48 | .pwm.legend__entry .panel__inner { 49 | fill: #ffb3aa; } 50 | 51 | .audio .pinlabel__body, 52 | .audio .textblock__bg { 53 | fill: #f7f09d; } 54 | 55 | .audio .pinlabel__leader { 56 | stroke: #f7f09d; } 57 | 58 | .audio.legend__entry .panel__inner { 59 | fill: #f7f09d; } 60 | 61 | .serial .pinlabel__body, 62 | .serial .textblock__bg { 63 | fill: #c9daed; } 64 | 65 | .serial .pinlabel__leader { 66 | stroke: #c9daed; } 67 | 68 | .serial.legend__entry .panel__inner { 69 | fill: #c9daed; } 70 | 71 | .i2c .pinlabel__body, 72 | .i2c .textblock__bg { 73 | fill: #b1add7; } 74 | 75 | .i2c .pinlabel__leader { 76 | stroke: #b1add7; } 77 | 78 | .i2c.legend__entry .panel__inner { 79 | fill: #b1add7; } 80 | 81 | .spi .pinlabel__body, 82 | .spi .textblock__bg { 83 | fill: #c2e6b9; } 84 | 85 | .spi .pinlabel__leader { 86 | stroke: #c2e6b9; } 87 | 88 | .spi.legend__entry .panel__inner { 89 | fill: #c2e6b9; } 90 | 91 | .canbus .pinlabel__body, 92 | .canbus .textblock__bg { 93 | fill: #fedae2; } 94 | 95 | .canbus .pinlabel__leader { 96 | stroke: #fedae2; } 97 | 98 | .canbus.legend__entry .panel__inner { 99 | fill: #fedae2; } 100 | 101 | .note .pinlabel__body, 102 | .note .textblock__bg { 103 | fill: #ddd; } 104 | 105 | .note .pinlabel__leader { 106 | stroke: #ddd; } 107 | 108 | .note.legend__entry .panel__inner { 109 | fill: #ddd; } 110 | 111 | .pinlabel__leader { 112 | fill: none; 113 | stroke-width: 0; } 114 | 115 | .pingroup g .pinlabel:first-child .pinlabel__leader, 116 | .lline--visible .pinlabel__leader { 117 | stroke-width: 2; } 118 | 119 | .pinlabel__text { 120 | dominant-baseline: central; 121 | fill: #000; 122 | font-size: 20px; 123 | font-weight: normal; 124 | letter-spacing: -1px; 125 | stroke-width: 0; 126 | text-anchor: middle; } 127 | 128 | .gnd .pinlabel__text, 129 | .pwr .pinlabel__text { 130 | fill: #fff; } 131 | 132 | .tight text { 133 | letter-spacing: -2px; } 134 | 135 | .titlebar__bg { 136 | fill: white; } 137 | 138 | .h1 text { 139 | font-size: 36px; 140 | font-weight: bold; } 141 | 142 | .h2 text { 143 | font-size: 18px; 144 | font-weight: bold; 145 | font-style: italic; } 146 | 147 | .italic { 148 | font-style: italic; } 149 | 150 | .strong { 151 | font-weight: bold; } 152 | 153 | .url { 154 | fill: #0084ff; 155 | font-weight: bold; } 156 | 157 | text { 158 | fill: #000; 159 | font-family: Arial, Georgia, sans-serif; 160 | font-size: 16px; 161 | font-weight: 300; 162 | dominant-baseline: auto; } 163 | 164 | .legend__bg { 165 | fill: black; } 166 | 167 | .legend__entry .panel__outer { 168 | fill: black; } 169 | 170 | .legend__title { 171 | font-size: 18px; 172 | font-weight: bold; 173 | dominant-baseline: auto; } 174 | 175 | .legend text { 176 | fill: #000; 177 | font-family: Arial, Georgia, sans-serif; 178 | font-size: 16px; 179 | font-weight: 300; 180 | dominant-baseline: auto; } 181 | 182 | .diagram__bg { 183 | fill: #000; } 184 | 185 | .panel__inner { 186 | fill: white; } 187 | -------------------------------------------------------------------------------- /samples/teensy_4.0/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | $panel_bg: rgb(255,255,255); 3 | $diagram_bg: rgb(0,0,0); 4 | 5 | @mixin paragraph_text { 6 | fill: #000; 7 | font-family: Arial, Georgia, sans-serif; 8 | font-size: 16px; 9 | font-weight: 300; 10 | dominant-baseline: auto; 11 | } 12 | 13 | // pinlabels ///////////////////////////////////////// 14 | 15 | $pinlabels: ( 16 | "gnd": #000, 17 | "pwr": #ad0000, 18 | "digital": #cfd5d5, 19 | "analog": #ffd7ad, 20 | "pwm": #ffb3aa, 21 | "audio": #f7f09d, 22 | "serial": #c9daed, 23 | "i2c": #b1add7, 24 | "spi": #c2e6b9, 25 | "canbus": #fedae2, 26 | "note": #ddd 27 | ); 28 | @each $name, $color in $pinlabels { 29 | .#{$name} .pinlabel__body, 30 | .#{$name} .textblock__bg{ 31 | fill: #{$color} 32 | } 33 | .#{$name} .pinlabel__leader{ 34 | stroke: #{$color} 35 | } 36 | .#{$name}.legend__entry .panel__inner{ 37 | fill: #{$color} 38 | } 39 | } 40 | 41 | .pinlabel__leader{ 42 | fill: none; 43 | stroke-width:0; 44 | } 45 | .pingroup g .pinlabel:first-child .pinlabel__leader, 46 | .lline--visible .pinlabel__leader{ 47 | stroke-width: 2; 48 | } 49 | 50 | .pinlabel__text{ 51 | dominant-baseline: central; 52 | fill: #000; 53 | font-size: 20px; 54 | font-weight: normal; 55 | letter-spacing: -1px; 56 | stroke-width: 0; 57 | text-anchor: middle; 58 | } 59 | // Power and ground labels have dark background and need light text 60 | .gnd .pinlabel__text, 61 | .pwr .pinlabel__text{ 62 | fill:#fff; 63 | } 64 | .tight text{ 65 | letter-spacing: -2px; 66 | } 67 | 68 | 69 | // Title block ///////////////////////////////////////// 70 | 71 | .titlebar__bg{ 72 | fill:$panel_bg; 73 | } 74 | .h1 text { 75 | font-size: 36px; 76 | font-weight: bold; 77 | } 78 | .h2 text { 79 | font-size: 18px; 80 | font-weight: bold; 81 | font-style: italic; 82 | } 83 | .italic{ 84 | font-style: italic; 85 | } 86 | .strong{ 87 | font-weight: bold; 88 | } 89 | .url{ 90 | fill: #0084ff; 91 | font-weight: bold; 92 | } 93 | text { 94 | @include paragraph_text; 95 | } 96 | 97 | 98 | // legend ///////////////////////////////////////// 99 | 100 | .legend__bg{ 101 | fill: $diagram_bg; 102 | } 103 | .legend__entry .panel__outer{ 104 | fill: $diagram_bg; 105 | } 106 | .legend__title{ 107 | font-size: 18px; 108 | font-weight: bold; 109 | dominant-baseline: auto; 110 | } 111 | .legend text{ 112 | @include paragraph_text; 113 | } 114 | 115 | // diagram layout ///////////////////////////////// 116 | 117 | .diagram__bg{ 118 | fill: #000; 119 | } 120 | .panel__inner{ 121 | fill:$panel_bg; 122 | } -------------------------------------------------------------------------------- /samples/teensy_4.0/teensy_4_data.py: -------------------------------------------------------------------------------- 1 | lline_84 = {"body": {"x": 80}, "tag": "lline--visible"} 2 | lline_53 = {"body": {"x": 60}, "tag": "lline--visible"} 3 | lline_40 = {"body": {"x": 40}, "tag": "lline--visible"} 4 | lg_body = {"body": {"width": 170}} 5 | 6 | 7 | # Pinlabels 8 | 9 | header_rhs = [ 10 | [("Vin", "pwr"), ("(3.6 to 5.5 volts)", "note", lg_body)], 11 | [("GND", "gnd")], 12 | [("3.3V", "pwr"), ("(250 mA max)", "note", lg_body)], 13 | [ 14 | ("23", "digital"), 15 | ("A9", "analog"), 16 | ("PWM", "pwm"), 17 | ("CRX1", "canbus"), 18 | ("MCLK1", "audio"), 19 | ], 20 | [ 21 | ("22", "digital"), 22 | ("A8", "analog"), 23 | ("PWM", "pwm"), 24 | ("CTX1", "canbus"), 25 | ("MCLK1", "audio"), 26 | ], 27 | [ 28 | ("21", "digital"), 29 | ("A7", "analog"), 30 | ("RX5", "serial", lline_53), 31 | ("BCLK1", "audio"), 32 | ], 33 | [ 34 | ("20", "digital"), 35 | ("A6", "analog"), 36 | ("TX5", "serial", lline_53), 37 | ("LRCLK1", "audio"), 38 | ], 39 | [("19", "digital"), ("A5", "analog"), ("PWM", "pwm"), ("SCL0", "i2c", lline_84)], 40 | [("18", "digital"), ("A4", "analog"), ("PWM", "pwm"), ("SDA0", "i2c", lline_84)], 41 | [("17", "digital"), ("A3", "analog"), ("TX4", "serial", lline_53), ("SDA1", "i2c")], 42 | [("16", "digital"), ("A2", "analog"), ("RX4", "serial", lline_53), ("SCL1", "i2c")], 43 | [ 44 | ("15", "digital"), 45 | ("A1", "analog"), 46 | ("PWM", "pwm"), 47 | ("RX3", "serial"), 48 | ("DIF IN", "audio tight"), 49 | ], 50 | [ 51 | ("14", "digital"), 52 | ("A0", "analog"), 53 | ("PWM", "pwm"), 54 | ("TX3", "serial"), 55 | ("DIF OUT", "audio tight"), 56 | ], 57 | [ 58 | ("13", "digital"), 59 | ("PWM", "pwm", lline_40), 60 | ("CRX1", "canbus"), 61 | ("SCK", "spi"), 62 | ], 63 | ] 64 | 65 | header_lhs = [ 66 | [("GND", "gnd")], 67 | [("0", "digital"), ("PWM", "pwm"), ("RX1", "serial"), ("CRX2", "canbus")], 68 | [("1", "digital"), ("PWM", "pwm"), ("TX1", "serial"), ("CTX2", "canbus")], 69 | [("2", "digital"), ("PWM", "pwm"), ("OUT2", "audio", lline_84)], 70 | [("3", "digital"), ("PWM", "pwm"), ("LRCLK2", "audio", lline_84)], 71 | [("4", "digital"), ("PWM", "pwm"), ("BCLK2", "audio", lline_84)], 72 | [("5", "digital"), ("PWM", "pwm"), ("IN2", "audio", lline_84)], 73 | [("6", "digital"), ("PWM", "pwm"), ("OUT1D", "audio", lline_84)], 74 | [("7", "digital"), ("PWM", "pwm"), ("RX2", "serial"), ("OUT1A", "audio")], 75 | [("8", "digital"), ("PWM", "pwm"), ("TX2", "serial"), ("IN1", "audio")], 76 | [("9", "digital"), ("PWM", "pwm"), ("OUT1C", "audio", lline_84)], 77 | [("10", "digital"), ("PWM", "pwm"), ("CS", "spi"), ("MQSR", "audio")], 78 | [("11", "digital"), ("PWM", "pwm"), ("MOSI", "spi"), ("CTX1", "canbus")], 79 | [("12", "digital"), ("PWM", "pwm"), ("MISO", "spi"), ("MQSL", "audio")], 80 | ] 81 | 82 | 83 | header_end_lhs = [ 84 | [("VBat", "pwr")], 85 | [("3.3v", "pwr")], 86 | [("GND", "gnd")], 87 | ] 88 | header_end_rhs = [ 89 | [("Prog", "pwr")], 90 | [("On/Off", "gnd")], 91 | ] 92 | 93 | 94 | # Legend 95 | legend_digital = """Digital Pins 96 | digitalRead 97 | digitalWrite 98 | pinMode""" 99 | 100 | legend_analog = """Analog Pins 101 | analogRead""" 102 | 103 | legend_pwm = """PWM Pins 104 | analogWrite""" 105 | 106 | legend_audio = """Digital Audio 107 | Audio library""" 108 | 109 | legend_serial = """Serial Ports 110 | Serial1 - Serial7""" 111 | 112 | legend_i2c = """I2C Port 113 | Wire library""" 114 | 115 | legend_spi = """SPI Port 116 | SPI library""" 117 | 118 | legend_canbus = """CAN Bus 119 | FlexCAN_t4 120 | library""" 121 | 122 | legend_content = [ 123 | (legend_digital, "digital"), 124 | (legend_analog, "analog"), 125 | (legend_pwm, "pwm"), 126 | (legend_audio, "audio"), 127 | (legend_serial, "serial"), 128 | (legend_i2c, "i2c"), 129 | (legend_spi, "spi"), 130 | (legend_canbus, "canbus"), 131 | ] 132 | 133 | # Text 134 | 135 | title = "Teensy 4 pinout" 136 | 137 | title_2 = """32 Bit Arduino Compatible 138 | Microcontroller""" 139 | 140 | instructions = """To begin please visit 'Getting Started' 141 | at www.pjrc.com/teensy""" 142 | 143 | notes = """• All digital pins have interrupt capability. 144 | • Loading status (Red LED): dim: Ready | bright: Writing | blink: No USB 145 | • LED on pin-13""" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | 7 | setuptools.setup( 8 | name="pinout", 9 | version="0.0.20", 10 | author="John Newall", 11 | author_email="john@johnnewall.com", 12 | description="Generate graphical pinout references for electronic hardware.", 13 | license="MIT", 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | url="https://github.com/j0ono0/pinout", 17 | project_urls={ 18 | "Bug Tracker": "https://github.com/j0ono0/pinout/issues", 19 | }, 20 | classifiers=[ 21 | "Development Status :: 3 - Alpha", 22 | "Programming Language :: Python :: 3", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | ], 26 | packages=setuptools.find_packages(include=["pinout", "pinout.*"]), 27 | python_requires=">=3.6", 28 | install_requires=["Jinja2", "Pillow"], 29 | include_package_data=True, 30 | ) 31 | -------------------------------------------------------------------------------- /tests/resources/200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/tests/resources/200x200.png -------------------------------------------------------------------------------- /tests/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j0ono0/pinout/8a718a4699468d93175624f0683d2dc9a628de78/tests/resources/__init__.py -------------------------------------------------------------------------------- /tests/resources/diagram_export.py: -------------------------------------------------------------------------------- 1 | from typing import Text 2 | from pinout.components.layout import Diagram 3 | from pinout.components.text import TextBlock 4 | 5 | diagram = Diagram(800, 400, tag="pinout") 6 | diagram.add_stylesheet("styles.css") 7 | diagram.add(TextBlock("Pinout export test.", x=100, y=100)) 8 | -------------------------------------------------------------------------------- /tests/resources/diagram_export.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 40 | Pinout export test. 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/resources/diagram_image.py: -------------------------------------------------------------------------------- 1 | # Build with: 2 | # py -m pinout.manager -e diagram_image.py diagram_image.svg -o 3 | 4 | from pinout.core import Image 5 | from pinout.components.layout import Diagram 6 | 7 | 8 | diagram = Diagram(800, 800, tag="pinout") 9 | 10 | # PNG image 11 | # --------- 12 | 13 | # PNG def, linked 14 | png_def_linked = diagram.add_def(Image("200x200.png")) 15 | 16 | # PNG def, embedded 17 | png_def_embedded = diagram.add_def(Image("200x200.png", embed=True)) 18 | 19 | # PNG linked 20 | diagram.add(Image("200x200.png", x=0, y=0)) 21 | 22 | # PNG embedded 23 | diagram.add(Image("200x200.png", x=200, y=0, embed=True)) 24 | 25 | # Referenced linked 26 | diagram.add(Image(png_def_linked, x=400, y=0)) 27 | 28 | # Referenced embedded 29 | diagram.add(Image(png_def_embedded, x=600, y=0)) 30 | 31 | # PNG linked, resized 32 | diagram.add(Image("200x200.png", x=400, y=400, width=400, height=400)) 33 | 34 | # SVG image 35 | # --------- 36 | 37 | 38 | # SVG linked 39 | diagram.add(Image("200x200.svg", x=0, y=200)) 40 | 41 | # SVG embedded 42 | diagram.add(Image("200x200.svg", x=200, y=200, width=200, height=200, embed=True)) 43 | 44 | # Referenced linked 45 | svg_def_linked = diagram.add_def(Image("200x200.svg")) 46 | diagram.add(Image(svg_def_linked, x=400, y=200)) 47 | 48 | # Referenced embedded 49 | svg_def_embedded = diagram.add_def(Image("200x200.svg", embed=True)) 50 | diagram.add(Image(svg_def_embedded, x=600, y=200)) 51 | 52 | # SVG linked rezised 53 | diagram.add(Image("200x200.svg", x=0, y=400, width=400, height=400)) 54 | -------------------------------------------------------------------------------- /tests/resources/styles.css: -------------------------------------------------------------------------------- 1 | 2 | text { 3 | font-family: Verdana, Georgia, sans-serif; 4 | font-size: 14px; 5 | font-weight: normal; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tests/test_samples.py: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # 3 | # pinout tests (with coverage reporting) 4 | # 5 | # Use a user-defined temporary directory if 6 | # you have problems with multiple harddrives (like I do): 7 | # 8 | # >>> coverage run -m pytest --basetemp=temp 9 | # 10 | # Build coverage html report 11 | # 12 | # >>> coverage html 13 | # 14 | ########################################################## 15 | import filecmp 16 | import pytest 17 | import re 18 | import shutil 19 | import uuid 20 | from pathlib import Path 21 | from importlib import reload 22 | from pinout import manager 23 | from pinout import config 24 | 25 | 26 | def re_sub_ids(re_m): 27 | id = re_m.group(0).split("_") 28 | id = "unique_id_replaced-for-testing_" + id[-1] 29 | return id 30 | 31 | 32 | def mk_test_file(src, dest): 33 | 34 | shutil.copyfile(src, dest) 35 | with src.open() as f: 36 | data = f.read() 37 | # sub ids 38 | id = re.compile(r"(?<=id=\").+(?=\")") 39 | data = re.sub(id, re_sub_ids, data) 40 | # sub hrefs 41 | id = re.compile(r"(?<=href=\"#).+(?=\")") 42 | data = re.sub(id, re_sub_ids, data) 43 | # sub clip-path urls 44 | id = re.compile(r"(?<=clip-path=\"url\(#).+(?=\")") 45 | data = re.sub(id, re_sub_ids, data) 46 | # write modified file data to testfile 47 | dest.write_text(data) 48 | return dest 49 | 50 | 51 | @pytest.mark.parametrize( 52 | "module_path, ref_path", 53 | [ 54 | # Components 55 | ( 56 | "./resources/diagram_export.py", 57 | "./resources/diagram_export.svg", 58 | ), 59 | ( 60 | "./resources/diagram_image.py", 61 | "./resources/diagram_image.svg", 62 | ), 63 | # Samples 64 | ( 65 | "../samples/arduino/arduino/uno/arduino_uno.py", 66 | "../samples/arduino/pinout_arduino_uno_rev3.svg", 67 | ), 68 | ( 69 | "../samples/arduino/arduino/rp2040/arduino_nano_rp2040_connect.py", 70 | "../samples/arduino/pinout_arduino_nano_rp2040_connect.svg", 71 | ), 72 | ( 73 | "../samples/attiny85/attiny85.py", 74 | "../samples/attiny85/pinout_attiny85.svg", 75 | ), 76 | ( 77 | "../samples/clip_path/pinout_diagram.py", 78 | "../samples/clip_path/diagram.svg", 79 | ), 80 | ( 81 | "../samples/full_sample/pinout_diagram.py", 82 | "../samples/full_sample/pinout_diagram.svg", 83 | ), 84 | ( 85 | "../samples/panel_layout/panel_layout.py", 86 | "../samples/panel_layout/output/panel_layout.svg", 87 | ), 88 | ( 89 | "../samples/panel_layout/populated_layout.py", 90 | "../samples/panel_layout/output/populated_layout.svg", 91 | ), 92 | ( 93 | "../samples/pci-express/pinout_x1.py", 94 | "../samples/pci-express/pinout_x1.svg", 95 | ), 96 | ( 97 | "../samples/section_pullout/pinout_diagram.py", 98 | "../samples/section_pullout/diagram.svg", 99 | ), 100 | ( 101 | "../samples/teensy_4.0/pinout_diagram.py", 102 | "../samples/teensy_4.0/teensy_4.0_front_pinout_diagram.svg", 103 | ), 104 | ], 105 | ) 106 | def test_output_against_reference(tmp_path, module_path, ref_path): 107 | # Config requires reloading between tests to to ensure 108 | # is in default state. 109 | reload(config) 110 | 111 | module_path = Path(module_path) 112 | ref_path = Path(ref_path) 113 | 114 | # Export a temp file in same location as reference: 115 | # Required for relative links to be identical. 116 | tempsvg = ref_path.parent / f"temp_pytest_{str(uuid.uuid4())}.svg" 117 | manager.export_diagram( 118 | module_path, 119 | tempsvg, 120 | overwrite=True, 121 | ) 122 | 123 | # Create files for comparison. Unique ids are converted to match 124 | file1 = mk_test_file(tempsvg, tmp_path / f"test_file.svg") 125 | file2 = mk_test_file(ref_path, tmp_path / f"ref_file.svg") 126 | 127 | # Remove temp file 128 | tempsvg.unlink() 129 | 130 | # Test files are identical 131 | assert filecmp.cmp(file1, file2, shallow=False) 132 | --------------------------------------------------------------------------------