├── .flake8 ├── .gitignore ├── .gitmodules ├── .pylintrc ├── .readthedocs.yaml ├── CHANGES.txt ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── docs ├── ArduinoUNO.png ├── Makefile ├── _ext │ └── doc_element.py ├── changes.rst ├── classes │ ├── drawing.rst │ ├── dsp.rst │ ├── electrical.rst │ ├── flow.rst │ ├── index.rst │ ├── logic.rst │ ├── pictorial.rst │ └── segments.rst ├── conf.py ├── contributing.rst ├── elements │ ├── compound.rst │ ├── connectors.rst │ ├── dsp.rst │ ├── electrical.rst │ ├── elements.rst │ ├── flow.rst │ ├── images.rst │ ├── intcircuits.rst │ ├── logic.rst │ ├── pictorial.rst │ └── timing.rst ├── gallery │ ├── analog.rst │ ├── flowcharting.rst │ ├── ic.rst │ ├── index.rst │ ├── logicgate.rst │ ├── opamp.rst │ ├── pictorial.rst │ ├── signalproc.rst │ ├── solidstate.rst │ ├── styles.rst │ └── timing.rst ├── index.rst ├── make.bat ├── requirements.txt └── usage │ ├── backends.rst │ ├── customizing.rst │ ├── fonts.svg │ ├── index.rst │ ├── labels.rst │ ├── mathfonts.svg │ ├── placement.rst │ ├── start.rst │ └── styles.rst ├── pyproject.toml ├── runtest.sh ├── schemdraw ├── __init__.py ├── backends │ ├── __init__.py │ ├── matrix.py │ ├── mpl.py │ ├── svg.py │ ├── svgtext.py │ └── svgunits.py ├── default_canvas.py ├── drawing_stack.py ├── dsp │ ├── __init__.py │ └── dsp.py ├── elements │ ├── __init__.py │ ├── cables.py │ ├── compound.py │ ├── connectors.py │ ├── container.py │ ├── elements.py │ ├── image.py │ ├── intcircuits.py │ ├── lines.py │ ├── misc.py │ ├── oneterm.py │ ├── opamp.py │ ├── outlets.py │ ├── sources.py │ ├── switches.py │ ├── transistors.py │ ├── twoports.py │ ├── twoterm.py │ └── xform.py ├── flow │ ├── __init__.py │ └── flow.py ├── logic │ ├── __init__.py │ ├── kmap.py │ ├── logic.py │ ├── table.py │ ├── timing.py │ └── timingwaves.py ├── parsing │ ├── __init__.py │ ├── buchheim.py │ └── logic_parser.py ├── pictorial │ ├── __init__.py │ ├── fritz.py │ └── pictorial.py ├── py.typed ├── schemdraw.py ├── segments.py ├── style.py ├── transform.py ├── types.py └── util.py ├── setup.cfg └── test ├── ArduinoUNO.png ├── ArduinoUno.svg ├── headless.py ├── schematic.py ├── test_backend.ipynb ├── test_canvas.ipynb ├── test_dsp.ipynb ├── test_elements.ipynb ├── test_elements2.ipynb ├── test_flow.ipynb ├── test_gallery.ipynb ├── test_intcircuits.ipynb ├── test_labels.ipynb ├── test_logic.ipynb ├── test_parselogic.ipynb ├── test_pictorial.ipynb ├── test_placement.ipynb ├── test_styles.ipynb └── test_timing.ipynb /.flake8: -------------------------------------------------------------------------------- 1 | 2 | [flake8] 3 | max-line-length = 120 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | docs/_build 4 | .idea/.name 5 | .idea/misc.xml 6 | .idea/modules.xml 7 | .idea/schemdraw.iml 8 | .idea/vcs.xml 9 | .idea/workspace.xml 10 | *.ipynb_checkpoints 11 | *.egg-info 12 | *__pycache__ 13 | /venv/ 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdelker/schemdraw/41589490f0de3ae89aed1718242d985d57eeab76/.gitmodules -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | extension-pkg-whitelist=PyQt5 3 | 4 | [FORMAT] 5 | max-line-length=120 6 | good-names=i,j,k,x,y,dx,dy,lw,ls,at,xy,ax -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: "ubuntu-20.04" 4 | tools: 5 | python: "3.9" 6 | sphinx: 7 | configuration: docs/conf.py 8 | fail_on_warning: false 9 | python: 10 | install: 11 | - requirements: docs/requirements.txt 12 | - method: pip 13 | path: . 14 | formats: all 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Report bugs and feature requests on the [Issue Tracker](https://github.com/cdelker/schemdraw/issues). 2 | 3 | Code contributions are welcome, especially with new circuit elements (and we'd be happy to expand beyond electrical elements too). To contribute code, please fork the [source code repository](https://github.com/cdelker/schemdraw/) and issue a pull request. Make sure to include any new elements somewhere in the test Jupyter notebooks and in the documentation. 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2025 Collin J. Delker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt CHANGES.txt readthedocs.yml 2 | recursive-include docs * 3 | recursive-include schemdraw *.py 4 | recursive-include test * 5 | prune docs/_build 6 | global-exclude *.ipynb_checkpoints* 7 | global-exclude *.pyc 8 | global-exclude *.pyo 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # schemdraw 2 | 3 | Schemdraw is a python package for producing high-quality electrical circuit schematic diagrams. Typical usage: 4 | 5 | ```python 6 | import schemdraw 7 | import schemdraw.elements as elm 8 | with schemdraw.Drawing(file='schematic.svg') as d: 9 | elm.Resistor().label('100KΩ') 10 | elm.Capacitor().down().label('0.1μF', loc='bottom') 11 | elm.Line().left() 12 | elm.Ground() 13 | elm.SourceV().up().label('10V') 14 | ``` 15 | 16 | Included are symbols for basic electrical components (resistors, capacitors, diodes, transistors, etc.), opamps and signal processing elements. Additionally, Schemdraw can produce digital timing diagrams, state machine diagrams, and flowcharts. 17 | 18 | Documentation is available at [readthedocs](https://schemdraw.readthedocs.io) 19 | 20 | The most current version can be found in the [source code git repository](https://github.com/cdelker/schemdraw). 21 | -------------------------------------------------------------------------------- /docs/ArduinoUNO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdelker/schemdraw/41589490f0de3ae89aed1718242d985d57eeab76/docs/ArduinoUNO.png -------------------------------------------------------------------------------- /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/_ext/doc_element.py: -------------------------------------------------------------------------------- 1 | ''' Sphinx directive for listing Schemdraw Elements 2 | 3 | Use with `.. element_list::` in rst files. 4 | ''' 5 | import re 6 | 7 | from docutils.parsers.rst import directives 8 | from docutils import nodes 9 | 10 | from sphinx.application import Sphinx 11 | from sphinx.util.docutils import SphinxDirective 12 | from sphinx.util.typing import ExtensionMetadata 13 | 14 | 15 | class SchemElement(SphinxDirective): 16 | option_spec = { 17 | 'ncols': directives.positive_int, 18 | 'nolabel': directives.flag, 19 | 'module': str, 20 | 'hide_anchors': str, 21 | } 22 | has_content = True 23 | 24 | def run(self) -> list[nodes.Node]: 25 | self.assert_has_content() 26 | ncols = self.options.get('ncols', 3) 27 | showlabel = not 'nolabel' in self.options 28 | module = self.options.get('module', 'elm') 29 | 30 | hide_anchors = [ 31 | 'center', 'start', 'end', 'istart', 'iend', 32 | 'isource', 'idrain', 'iemitter', 'icollector', 'xy'] 33 | 34 | hide_anchors.extend(self.options.get('hide_anchors', '').split()) 35 | 36 | names = [re.findall('(.*)\(', line)[0] for line in self.content] 37 | args = [re.findall('\((.*)\)', line)[0] for line in self.content] 38 | 39 | text = f''' 40 | .. grid:: {ncols} 41 | :gutter: 0 42 | 43 | ''' 44 | 45 | for name, arg in zip(names, args): 46 | 47 | text += f''' 48 | .. grid-item-card:: 49 | :class-body: sd-text-nowrap sd-fs-6 50 | 51 | {name}''' 52 | 53 | if arg: 54 | text += f'({arg})' 55 | 56 | text += f''' 57 | 58 | .. jupyter-execute:: 59 | :hide-code: 60 | 61 | e = {module}.{name}({arg}).right() 62 | ''' 63 | if showlabel: 64 | text += f''' 65 | for aname in e.anchors.keys(): 66 | if aname not in {hide_anchors}: 67 | e.label(aname, loc=aname, color='blue', fontsize=10) 68 | ''' 69 | text += ''' 70 | e 71 | ''' 72 | self.content = text 73 | nodes = self.parse_content_to_nodes() 74 | return nodes 75 | 76 | 77 | def setup(app: Sphinx) -> ExtensionMetadata: 78 | app.add_directive('element_list', SchemElement) 79 | 80 | return { 81 | 'version': '0.1', 82 | 'parallel_read_safe': True, 83 | 'parallel_write_safe': True, 84 | } 85 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | Change Log 2 | ---------- 3 | 4 | .. include:: ../CHANGES.txt -------------------------------------------------------------------------------- /docs/classes/drawing.rst: -------------------------------------------------------------------------------- 1 | 2 | Drawing 3 | ======= 4 | 5 | .. autoclass:: schemdraw.Drawing 6 | :members: 7 | :exclude-members: labelI, labelI_inline, loopI 8 | 9 | Element 10 | ======= 11 | 12 | 13 | .. autoclass:: schemdraw.elements.Element 14 | :members: 15 | :exclude-members: add_label 16 | 17 | 18 | Element2Term 19 | ============ 20 | 21 | .. autoclass:: schemdraw.elements.Element2Term 22 | :members: 23 | 24 | 25 | ElementDrawing 26 | ============== 27 | 28 | .. autoclass:: schemdraw.elements.ElementDrawing 29 | :members: 30 | 31 | ElementImage 32 | ============ 33 | 34 | .. autoclass:: schemdraw.elements.ElementImage 35 | :members: 36 | 37 | 38 | Element Style 39 | ============= 40 | 41 | .. py:function:: schemdraw.elements.style(style) 42 | 43 | Set global element style 44 | 45 | :param style: dictionary of {elementname: Element} to change the element module namespace. Use `elements.STYLE_US` or `elements.STYLE_IEC` to define U.S. or European/IEC element styles. 46 | 47 | 48 | .. autofunction:: schemdraw.config 49 | 50 | .. autofunction:: schemdraw.theme 51 | 52 | .. autofunction:: schemdraw.use -------------------------------------------------------------------------------- /docs/classes/dsp.rst: -------------------------------------------------------------------------------- 1 | 2 | Digital Signal Processing 3 | ========================= 4 | 5 | 6 | .. automodule:: schemdraw.dsp.dsp 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/classes/electrical.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _elecelements: 3 | 4 | Electrical Elements 5 | =================== 6 | 7 | 8 | Two-Terminal Elements 9 | --------------------- 10 | 11 | .. automodule:: schemdraw.elements.twoterm 12 | :members: 13 | :exclude-members: cycloid 14 | 15 | .. automodule:: schemdraw.elements.sources 16 | :members: 17 | 18 | 19 | One-terminal Elements 20 | --------------------- 21 | 22 | .. automodule:: schemdraw.elements.oneterm 23 | :members: 24 | 25 | 26 | Switches 27 | -------- 28 | 29 | .. automodule:: schemdraw.elements.switches 30 | :members: 31 | 32 | Lines 33 | ----- 34 | 35 | .. automodule:: schemdraw.elements.lines 36 | :members: 37 | 38 | 39 | Cables and Connectors 40 | --------------------- 41 | 42 | .. automodule:: schemdraw.elements.cables 43 | :members: 44 | 45 | .. automodule:: schemdraw.elements.connectors 46 | :members: 47 | 48 | 49 | Transistors 50 | ----------- 51 | 52 | .. automodule:: schemdraw.elements.transistors 53 | :members: 54 | 55 | 56 | Transformers 57 | ------------ 58 | 59 | .. automodule:: schemdraw.elements.xform 60 | :members: 61 | 62 | 63 | Opamp and Integrated Circuits 64 | ----------------------------- 65 | 66 | .. automodule:: schemdraw.elements.opamp 67 | :members: 68 | 69 | .. automodule:: schemdraw.elements.intcircuits 70 | :members: 71 | 72 | 73 | Other 74 | ----- 75 | 76 | .. automodule:: schemdraw.elements.misc 77 | :members: 78 | 79 | .. automodule:: schemdraw.elements.compound 80 | :members: 81 | 82 | .. automodule:: schemdraw.elements.twoports 83 | :members: 84 | -------------------------------------------------------------------------------- /docs/classes/flow.rst: -------------------------------------------------------------------------------- 1 | 2 | Flowcharting 3 | ============ 4 | 5 | 6 | .. automodule:: schemdraw.flow.flow 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/classes/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _api: 3 | 4 | Class Definitions 5 | ================= 6 | 7 | 8 | .. toctree:: 9 | 10 | drawing 11 | segments 12 | electrical 13 | logic 14 | dsp 15 | pictorial 16 | flow 17 | -------------------------------------------------------------------------------- /docs/classes/logic.rst: -------------------------------------------------------------------------------- 1 | 2 | Logic Gates 3 | =========== 4 | 5 | 6 | .. automodule:: schemdraw.logic.logic 7 | :members: 8 | 9 | .. automethod:: schemdraw.parsing.logic_parser.logicparse 10 | 11 | .. autoclass:: schemdraw.logic.table.Table 12 | 13 | .. autoclass:: schemdraw.logic.kmap.Kmap 14 | 15 | .. autoclass:: schemdraw.logic.timing.TimingDiagram -------------------------------------------------------------------------------- /docs/classes/pictorial.rst: -------------------------------------------------------------------------------- 1 | 2 | Pictorial Elements 3 | ================== 4 | 5 | 6 | .. automodule:: schemdraw.pictorial.pictorial 7 | :members: 8 | 9 | .. autoclass:: schemdraw.pictorial.fritz.FritzingPart 10 | -------------------------------------------------------------------------------- /docs/classes/segments.rst: -------------------------------------------------------------------------------- 1 | 2 | Segment Drawing Primitives 3 | ========================== 4 | 5 | .. automodule:: schemdraw.segments 6 | :members: 7 | :exclude-members: roundcorners -------------------------------------------------------------------------------- /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 | # http://www.sphinx-doc.org/en/master/config 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 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | import sys 18 | from pathlib import Path 19 | sys.path.append(str(Path('_ext').resolve())) 20 | 21 | import pkg_resources 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'Schemdraw' 26 | copyright = '2025, Collin J. Delker' 27 | author = 'Collin J. Delker' 28 | 29 | # The full version, including alpha/beta/rc tags 30 | 31 | release = pkg_resources.get_distribution(project).version 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | 'jupyter_sphinx', 40 | 'sphinx.ext.autodoc', 41 | 'sphinx.ext.autodoc.typehints', 42 | 'sphinx.ext.napoleon', 43 | 'sphinxcontrib.cairosvgconverter', 44 | 'sphinx_design', 45 | 'doc_element' 46 | ] 47 | 48 | # Add any paths that contain templates here, relative to this directory. 49 | #templates_path = ['_templates'] 50 | templates_path = [] 51 | 52 | # List of patterns, relative to source directory, that match files and 53 | # directories to ignore when looking for source files. 54 | # This pattern also affects html_static_path and html_extra_path. 55 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] 56 | 57 | 58 | # -- Options for HTML output ------------------------------------------------- 59 | 60 | # The theme to use for HTML and HTML Help pages. See the documentation for 61 | # a list of builtin themes. 62 | # 63 | html_theme = 'alabaster' 64 | 65 | # Add any paths that contain custom static files (such as style sheets) here, 66 | # relative to this directory. They are copied after the builtin static files, 67 | # so a file named "default.css" will overwrite the builtin "default.css". 68 | # html_static_path = ['_static'] 69 | html_static_path = [] 70 | 71 | master_doc = 'index' # See https://github.com/readthedocs/readthedocs.org/issues/2569 72 | 73 | latex_elements = { 74 | 'preamble': r'\DeclareUnicodeCharacter{03A9}{\ensuremath{\Omega}}' + 75 | r'\DeclareUnicodeCharacter{03BC}{\ensuremath{\mu}}' + 76 | r'\DeclareUnicodeCharacter{2184}{\ensuremath{\supset}}' + 77 | r'\DeclareUnicodeCharacter{2295}{\ensuremath{\oplus}}' + 78 | r'\DeclareUnicodeCharacter{2228}{\ensuremath{\vee}}' + 79 | r'\DeclareUnicodeCharacter{22BB}{\ensuremath{\veebar}}' + 80 | r'\DeclareUnicodeCharacter{01C1}{\ensuremath{\parallel}}' + 81 | r'\DeclareUnicodeCharacter{2220}{\ensuremath{\angle}}' + 82 | r'\DeclareUnicodeCharacter{2227}{\ensuremath{\wedge}}' + 83 | r'\DeclareUnicodeCharacter{2212}{\ensuremath{-}}' 84 | } 85 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | Report bugs and feature requests on the `Issue Tracker `_. 5 | 6 | Code contributions are welcome, especially with new circuit elements (and we'd be happy to expand beyond electrical elements too). To contribute code, please fork the `source code repository `_ and issue a pull request. Make sure to include any new elements somewhere in the test Jupyter notebooks and in the documentation. 7 | 8 | - `Source Code `_ 9 | 10 | | 11 | 12 | 13 | ---------- 14 | 15 | Want to support Schemdraw development? Need more circuit examples? Pick up the Schemdraw Examples Pack on buymeacoffee.com: 16 | 17 | .. raw:: html 18 | 19 | Buy Me A Coffee 20 | -------------------------------------------------------------------------------- /docs/elements/compound.rst: -------------------------------------------------------------------------------- 1 | Compound Elements 2 | ================= 3 | 4 | Several compound elements defined based on other basic elements. 5 | 6 | .. jupyter-execute:: 7 | :hide-code: 8 | 9 | import schemdraw 10 | from schemdraw import elements as elm 11 | schemdraw.use('svg') 12 | 13 | 14 | Optocoupler 15 | ----------- 16 | 17 | :py:class:`schemdraw.elements.compound.Optocoupler` can be drawn with or without a base contact. 18 | 19 | 20 | .. element_list:: 21 | :ncols: 2 22 | 23 | Optocoupler() 24 | Optocoupler(base=True) 25 | 26 | 27 | 28 | Relay 29 | ----- 30 | 31 | :py:class:`schemdraw.elements.compound.Relay` can be drawn with different options for switches and inductor solenoids. 32 | 33 | .. element_list:: 34 | :ncols: 2 35 | 36 | Relay() 37 | Relay(switch='spdt') 38 | Relay(swithc='dpst') 39 | Relay(switch='dpdt') 40 | 41 | 42 | Wheatstone 43 | ---------- 44 | 45 | :py:class:`schemdraw.elements.compound.Wheatstone` can be drawn with or without the output voltage taps. 46 | The `labels` argument specifies a list of labels for each resistor. 47 | 48 | .. element_list:: 49 | :ncols: 2 50 | 51 | Wheatstone() 52 | Wheatstone(vout=True) 53 | 54 | 55 | Rectifier 56 | ---------- 57 | 58 | :py:class:`schemdraw.elements.compound.Rectifier` draws four diodes at 45 degree angles. 59 | The `labels` argument specifies a list of labels for each diode. 60 | 61 | .. element_list:: 62 | :ncols: 2 63 | 64 | Rectifier() 65 | 66 | -------------------------------------------------------------------------------- /docs/elements/connectors.rst: -------------------------------------------------------------------------------- 1 | Connectors 2 | ========== 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | schemdraw.use('svg') 10 | 11 | All connectors are defined with a default pin spacing of 0.6, matching the default pin spacing of the :py:class:`schemdraw.elements.intcircuits.Ic` class, for easy connection of multiple signals. 12 | 13 | 14 | Headers 15 | ^^^^^^^ 16 | 17 | A :py:class:`schemdraw.elements.connectors.Header` is a generic Header block with any number of rows and columns. It can have round, square, or screw-head connection points. 18 | 19 | .. element_list:: 20 | :ncols: 2 21 | :nolabel: 22 | 23 | Header() 24 | Header(shownumber=True) 25 | Header(rows=3, cols=2) 26 | Header(style='square') 27 | Header(style='screw') 28 | Header(pinsleft=['A', 'B', 'C', 'D']) 29 | 30 | Header pins are given anchor names `pin1`, `pin2`, etc. 31 | Pin number labels and anchor names can be ordered left-to-right (`lr`), up-to-down (`ud`), or counterclockwise (`ccw`) like a traditional IC, depending on the `numbering` argument. 32 | The `flip` argument can be set True to put pin 1 at the bottom. 33 | 34 | .. jupyter-execute:: 35 | :hide-code: 36 | 37 | with schemdraw.Drawing(): 38 | elm.Header(shownumber=True, cols=2, numbering='lr', label="lr") 39 | elm.Header(at=[3, 0], shownumber=True, cols=2, numbering='ud', label="ud") 40 | elm.Header(at=[6, 0], shownumber=True, cols=2, numbering='ccw', label="ccw") 41 | 42 | A :py:class:`schemdraw.elements.connectors.Jumper` element is also defined, as a simple rectangle, for easy placing onto a header. 43 | 44 | .. jupyter-execute:: 45 | 46 | with schemdraw.Drawing(): 47 | J = elm.Header(cols=2, style='square') 48 | elm.Jumper().at(J.pin3).fill('lightgray') 49 | 50 | 51 | D-Sub Connectors 52 | ^^^^^^^^^^^^^^^^ 53 | 54 | Both :py:class:`schemdraw.elements.connectors.DB9` and :py:class:`schemdraw.elements.connectors.DB25` subminiature connectors are defined, with anchors `pin1` through `pin9` or `pin25`. 55 | 56 | .. element_list:: 57 | :nolabel: 58 | 59 | DB9() 60 | DB9(number=True) 61 | DB25() 62 | 63 | 64 | Multiple Lines 65 | ^^^^^^^^^^^^^^ 66 | 67 | The :py:class:`schemdraw.elements.connectors.RightLines` and :py:class:`schemdraw.elements.connectors.OrthoLines` elements are useful for connecting multiple pins of an integrated circuit or header all at once. Both need an `at` and `to` location specified, along with the `n` parameter for setting the number of lines to draw. Use RightLines when the Headers are perpindicular to each other. 68 | 69 | 70 | .. jupyter-execute:: 71 | :emphasize-lines: 7 72 | 73 | with schemdraw.Drawing(): 74 | D1 = elm.Ic(pins=[elm.IcPin(name='A', side='t', slot='1/4'), 75 | elm.IcPin(name='B', side='t', slot='2/4'), 76 | elm.IcPin(name='C', side='t', slot='3/4'), 77 | elm.IcPin(name='D', side='t', slot='4/4')]) 78 | D2 = elm.Header(rows=4).at((5,4)) 79 | elm.RightLines(n=4).at(D2.pin1).to(D1.D).label('RightLines') 80 | 81 | 82 | OrthoLines draw a z-shaped orthogonal connection. Use OrthoLines when the Headers are parallel but vertically offset. 83 | Use the `xstart` parameter, between 0 and 1, to specify the position where the first OrthoLine turns vertical. 84 | 85 | .. jupyter-execute:: 86 | :emphasize-lines: 7 87 | 88 | with schemdraw.Drawing(): 89 | D1 = elm.Ic(pins=[elm.IcPin(name='A', side='r', slot='1/4'), 90 | elm.IcPin(name='B', side='r', slot='2/4'), 91 | elm.IcPin(name='C', side='r', slot='3/4'), 92 | elm.IcPin(name='D', side='r', slot='4/4')]) 93 | D2 = elm.Header(rows=4).at((7, -3)) 94 | elm.OrthoLines(n=4).at(D1.D).to(D2.pin1).label('OrthoLines') 95 | 96 | 97 | Data Busses 98 | ^^^^^^^^^^^ 99 | 100 | Sometimes, multiple I/O pins to an integrated circuit are lumped together into a data bus. 101 | The connections to a bus can be drawn using the :py:class:`schemdraw.elements.connectors.BusConnect` element, which takes `n` the number of data lines and an argument. 102 | :py:class:`schemdraw.elements.connectors.BusLine` is simply a wider line used to extend the full bus to its destination. 103 | 104 | BusConnect elements define anchors `start`, `end` on the endpoints of the wide bus line, and `pin1`, `pin2`, etc. for the individual signals. 105 | 106 | 107 | .. jupyter-execute:: 108 | :emphasize-lines: 3-5 109 | 110 | with schemdraw.Drawing(): 111 | J = elm.Header(rows=6) 112 | B = elm.BusConnect(n=6).at(J.pin1) 113 | elm.BusLine().down().at(B.end).length(3) 114 | B2 = elm.BusConnect(n=6).anchor('start').reverse() 115 | elm.Header(rows=6).at(B2.pin1).anchor('pin1') 116 | 117 | 118 | 119 | Outlets 120 | ^^^^^^^ 121 | 122 | Power outlets and plugs are drawn using `OutletX` classes, with international styles A through L. Each has anchors 123 | `hot`, `neutral`, and `ground` (if applicable). 124 | The `plug` parameter fills the prongs to indicate a plug versus an outlet. 125 | 126 | .. element_list:: 127 | :nolabel: 128 | 129 | OutletA() 130 | OutletB() 131 | OutletC() 132 | OutletD() 133 | OutletE() 134 | OutletF() 135 | OutletG() 136 | OutletH() 137 | OutletI() 138 | OutletJ() 139 | OutletK() 140 | OutletL() 141 | -------------------------------------------------------------------------------- /docs/elements/dsp.rst: -------------------------------------------------------------------------------- 1 | Signal Processing 2 | ================= 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import dsp 9 | schemdraw.use('svg') 10 | 11 | Signal processing elements can be drawn by importing the :py:mod:`schemdraw.dsp.dsp` module: 12 | 13 | .. code-block:: python 14 | 15 | from schemdraw import dsp 16 | 17 | Because each element may have multiple connections in and out, these elements 18 | are not 2-terminal elements that extend "leads", so they must be manually connected with 19 | `Line` or `Arrow` elements. The square elements define anchors 'N', 'S', 'E', and 'W' for 20 | the four directions. Circle-based elements also includ 'NE', 'NW', 'SE', and 'SW' 21 | anchors. 22 | Directional elements, such as `Amp`, `Adc`, and `Dac` define anchors `input` and `out`. 23 | 24 | 25 | .. element_list:: 26 | :module: dsp 27 | :nolabel: 28 | 29 | Square() 30 | Circle() 31 | Sum() 32 | SumSigma() 33 | Mixer() 34 | Speaker() 35 | Amp() 36 | OscillatorBox() 37 | Oscillator() 38 | Filter() 39 | Filter(response='lp') 40 | Filter(response='bp') 41 | Filter(response='hp') 42 | Adc() 43 | Dac() 44 | Demod() 45 | Circulator() 46 | Isolator() 47 | VGA() 48 | 49 | 50 | 51 | Labels are placed in the center of the element. The generic `Square` and `Circle` elements can be used with a label to define other operations. For example, an integrator 52 | may be created using: 53 | 54 | .. jupyter-execute:: 55 | 56 | dsp.Square().label(r'$\int$') 57 | -------------------------------------------------------------------------------- /docs/elements/elements.rst: -------------------------------------------------------------------------------- 1 | Circuit Elements 2 | ================ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | electrical 8 | intcircuits 9 | connectors 10 | compound 11 | logic 12 | timing 13 | dsp 14 | images 15 | pictorial 16 | flow -------------------------------------------------------------------------------- /docs/elements/flow.rst: -------------------------------------------------------------------------------- 1 | Flowcharts and Diagrams 2 | ======================= 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import flow 9 | schemdraw.use('svg') 10 | 11 | 12 | Schemdraw provides basic symbols for flowcharting and state diagrams. 13 | The :py:mod:`schemdraw.flow.flow` module contains a set of functions for defining 14 | flowchart blocks and connecting lines that can be added to schemdraw Drawings. 15 | 16 | .. code-block:: python 17 | 18 | from schemdraw import flow 19 | 20 | Flowchart blocks: 21 | 22 | .. jupyter-execute:: 23 | :hide-code: 24 | 25 | d = schemdraw.Drawing(fontsize=10, unit=.5) 26 | d.add(flow.Start().label('Start').drop('E')) 27 | d.add(flow.Arrow()) 28 | d.add(flow.Ellipse().label('Ellipse')) 29 | d.add(flow.Arrow()) 30 | d.add(flow.Box(label='Box')) 31 | d.add(flow.Arrow()) 32 | d.add(flow.RoundBox(label='RoundBox').drop('S')) 33 | d.add(flow.Arrow().down()) 34 | d.add(flow.Subroutine(label='Subroutine').drop('W')) 35 | d.add(flow.Arrow().left()) 36 | d.add(flow.Data(label='Data')) 37 | d.add(flow.Arrow()) 38 | d.add(flow.Decision(label='Decision')) 39 | d.add(flow.Arrow()) 40 | d.add(flow.Connect(label='Connect')) 41 | d.draw() 42 | 43 | Some elements have been defined with multiple names, which can be used depending on the context or user preference: 44 | 45 | .. jupyter-execute:: 46 | :hide-code: 47 | 48 | d = schemdraw.Drawing(fontsize=10, unit=.5) 49 | d.add(flow.Terminal().label('Terminal').drop('E')) 50 | d.add(flow.Arrow()) 51 | d.add(flow.Process().label('Process')) 52 | d.add(flow.Arrow()) 53 | d.add(flow.RoundProcess().label('RoundProcess')) 54 | d.add(flow.Arrow()) 55 | d.add(flow.Circle(label='Circle')) 56 | d.add(flow.Arrow()) 57 | d.add(flow.State(label='State')) 58 | d.add(flow.Arrow()) 59 | d.add(flow.StateEnd(label='StateEnd')) 60 | d.draw() 61 | 62 | 63 | All flowchart symbols have 16 anchor positions named for the compass directions: 'N', 'S', 'E', 'W', 'NE', 'SE, 'NNE', etc., plus a 'center' anchor. 64 | 65 | The :py:class:`schemdraw.elements.intcircuits.Ic` element can be used with the flowchart elements to create blocks with other inputs/outputs per side if needed. 66 | 67 | The size of each block must be specified manually using `w` and `h` or `r` parameters to size each block to fit any labels. 68 | 69 | 70 | Connecting Lines 71 | ---------------- 72 | 73 | Typical flowcharts will use `Line` or `Arrow` elements to connect the boxes. The line and arrow elements have been included in the `flow` module for convenience. 74 | 75 | .. jupyter-execute:: 76 | 77 | with schemdraw.Drawing() as d: 78 | d.config(fontsize=10, unit=.5) 79 | flow.Terminal().label('Start') 80 | flow.Arrow() 81 | flow.Process().label('Do something').drop('E') 82 | flow.Arrow().right() 83 | flow.Process().label('Do something\nelse') 84 | 85 | 86 | Some flow diagrams, such as State Machine diagrams, often use curved connectors between states. Several Arc connectors are available. 87 | Each Arc element takes an `arrow` parameter, which may be '->', '<-', or '<->', to define the end(s) on which to draw arrowheads. 88 | 89 | Arc2 90 | ^^^^ 91 | 92 | `Arc2` draws a symmetric quadratic Bezier curve between the endpoints, with curvature controlled by parameter `k`. Endpoints of the arc should be specified using `at()` and `to()` methods. 93 | 94 | .. jupyter-execute:: 95 | 96 | with schemdraw.Drawing(fontsize=12, unit=1): 97 | a = flow.State().label('A') 98 | b = flow.State(arrow='->').label('B').at((4, 0)) 99 | flow.Arc2(arrow='->').at(a.NE).to(b.NW).color('deeppink').label('Arc2') 100 | flow.Arc2(k=.2, arrow='<->').at(b.SW).to(a.SE).color('mediumblue').label('Arc2') 101 | 102 | 103 | ArcZ and ArcN 104 | ^^^^^^^^^^^^^ 105 | 106 | These draw symmetric cubic Bezier curves between the endpoints. The `ArcZ` curve approaches the endpoints horizontally, and `ArcN` approaches them vertically. 107 | 108 | .. jupyter-execute:: 109 | 110 | with schemdraw.Drawing(fontsize=12, unit=1): 111 | a = flow.State().label('A') 112 | b = flow.State().label('B').at((4, 4)) 113 | c = flow.State().label('C').at((8, 0)) 114 | flow.ArcN(arrow='<->').at(a.N).to(b.S).color('deeppink').label('ArcN') 115 | flow.ArcZ(arrow='<->').at(b.E).to(c.W).color('mediumblue').label('ArcZ') 116 | 117 | 118 | Arc3 119 | ^^^^ 120 | 121 | The `Arc3` curve is an arbitrary cubic Bezier curve, defined by endpoints and angle of approach to each endpoint. `ArcZ` and `ArcN` are simply `Arc3` defined with the angles as 0 and 180, or 90 and 270, respectively. 122 | 123 | 124 | .. jupyter-execute:: 125 | 126 | with schemdraw.Drawing(fontsize=12, unit=1): 127 | a = flow.State().label('A') 128 | b = flow.State().label('B').at((3, 3)) 129 | flow.Arc3(th1=75, th2=-45, arrow='<->').at(a.N).to(b.SE).color('deeppink').label('Arc3') 130 | 131 | 132 | ArcLoop 133 | ^^^^^^^ 134 | 135 | The `ArcLoop` curve draws a partial circle that intersects the two endpoints, with the given radius. Often used in state machine diagrams to indicate cases where the state does not change. 136 | 137 | .. jupyter-execute:: 138 | 139 | with schemdraw.Drawing(fontsize=12, unit=1): 140 | a = flow.State().label('A') 141 | flow.ArcLoop(arrow='<-').at(a.NW).to(a.NNE).color('mediumblue').label('ArcLoop', halign='center') 142 | 143 | 144 | Decisions 145 | --------- 146 | 147 | To label the decision branches, the :py:class:`schemdraw.flow.flow.Decision` element takes keyword 148 | arguments for each cardinal direction. For example: 149 | 150 | 151 | .. jupyter-execute:: 152 | :hide-code: 153 | 154 | d = schemdraw.Drawing(fontsize=12, unit=1) 155 | 156 | .. jupyter-execute:: 157 | 158 | decision = flow.Decision(W='Yes', E='No', S='Maybe').label('Question?') 159 | 160 | 161 | .. jupyter-execute:: 162 | :hide-code: 163 | 164 | dec = d.add(decision) 165 | d.add(flow.Line().at(dec.W).left()) 166 | d.add(flow.Line().at(dec.E).right()) 167 | d.add(flow.Line().at(dec.S).down()) 168 | d.draw() 169 | 170 | 171 | Layout and Flow 172 | --------------- 173 | 174 | Without any directions specified, boxes flow top to bottom (see left image). 175 | If a direction is specified (right image), the flow will continue in that direction, starting the next arrow at an appropriate anchor. 176 | Otherwise, the `drop` method is useful for specifing where to begin the next arrow. 177 | 178 | .. jupyter-execute:: 179 | 180 | with schemdraw.Drawing() as d: 181 | d.config(fontsize=10, unit=.5) 182 | flow.Terminal().label('Start') 183 | flow.Arrow() 184 | flow.Process().label('Step 1') 185 | flow.Arrow() 186 | flow.Process().label('Step 2').drop('E') 187 | flow.Arrow().right() 188 | flow.Connect().label('Next') 189 | 190 | flow.Terminal().label('Start').at((4, 0)) 191 | flow.Arrow().theta(-45) 192 | flow.Process().label('Step 1') 193 | flow.Arrow() 194 | flow.Process().label('Step 2').drop('E') 195 | flow.Arrow().right() 196 | flow.Connect().label('Next') 197 | 198 | 199 | Containers 200 | ---------- 201 | 202 | Use :py:meth:`schemdraw.Drawing.container` as a context manager to add elements 203 | to be enclosed in a box. 204 | The elements in the container are added to the outer drawing too; the `container` 205 | just draws the box around them when it exits the `with`. 206 | 207 | 208 | .. jupyter-execute:: 209 | 210 | with schemdraw.Drawing(unit=1) as d: 211 | flow.Start().label('Start') 212 | flow.Arrow().down().length(1.5) 213 | with d.container() as c: 214 | flow.Box().label('Step 1').drop('E') 215 | flow.Arrow().right() 216 | flow.Box().label('Step 2') 217 | c.color('red') 218 | c.label('Subprocess', loc='N', halign='center', valign='top') 219 | flow.Arrow().right() 220 | flow.Start().label('End').anchor('W') 221 | 222 | Containers may be nested, calling `container()` on either a Drawing, or another Container. 223 | 224 | 225 | Examples 226 | -------- 227 | 228 | See the :ref:`galleryflow` Gallery for more examples. 229 | -------------------------------------------------------------------------------- /docs/elements/images.rst: -------------------------------------------------------------------------------- 1 | .. _images: 2 | 3 | Image-based Elements 4 | ==================== 5 | 6 | .. jupyter-execute:: 7 | :hide-code: 8 | 9 | import schemdraw 10 | from schemdraw import elements as elm 11 | schemdraw.use('svg') 12 | 13 | 14 | Elements can be made from image files, including PNG and SVG images, using :py:class:`schemdraw.elements.ElementImage`. 15 | SVG images are only supported in the SVG backend at this time (See :ref:`backends`). 16 | 17 | Common usage is to subclass `ElementImage` and provide the image file and anchor positions in the subclass's init method. 18 | For example, an Arduino Board element may be created from an image: 19 | 20 | .. jupyter-execute:: 21 | 22 | class ArduinoUno(elm.ElementImage): 23 | ''' Arduino Element ''' 24 | def __init__(self): 25 | # Dimensions based on image size to make it about the right 26 | # size relative to other components 27 | width = 10.3 28 | height = width/1.397 29 | pinspacing = .35 # Spacing between header pins 30 | 31 | super().__init__('ArduinoUNO.png', width=width, height=height, xy=(-.75, 0)) 32 | 33 | # Only defining the top header pins as anchors for now 34 | top = height * .956 35 | arefx = 3.4 36 | for i, pinname in enumerate(['aref', 'gnd', 'pin13', 'pin12', 'pin11', 37 | 'pin10', 'pin9', 'pin8']): 38 | self.anchors[pinname] = (arefx + i*pinspacing, top) 39 | 40 | 41 | The Arduino element is used like any other element: 42 | 43 | .. jupyter-execute:: 44 | 45 | with schemdraw.Drawing() as d: 46 | d.config(color='#dd2222', unit=2) 47 | arduino = ArduinoUno() 48 | elm.Dot().at(arduino.gnd) 49 | elm.Resistor().up().scale(.7) 50 | elm.Line().right().tox(arduino.pin8) 51 | elm.LED().down().reverse().toy(arduino.pin8).scale(.7) 52 | elm.Dot().at(arduino.pin8) 53 | 54 | 55 | `Arduino Image Source `_ , CC-BY-SA-3.0. 56 | 57 | 58 | See :ref:`pictorial` for using Image Elements with other graphical schematic components. -------------------------------------------------------------------------------- /docs/elements/intcircuits.rst: -------------------------------------------------------------------------------- 1 | 2 | .. jupyter-execute:: 3 | :hide-code: 4 | 5 | import schemdraw 6 | from schemdraw import elements as elm 7 | schemdraw.use('svg') 8 | 9 | 10 | .. _integratedcircuit: 11 | 12 | Integrated Circuits 13 | ------------------- 14 | 15 | The :py:class:`schemdraw.elements.intcircuits.Ic` class is used to make integrated circuits, multiplexers, and other black box elements. The :py:class:`schemdraw.elements.intcircuits.IcPin` class is used to define each input/output pin before adding it to the Ic. 16 | 17 | All pins will be given an anchor name of `inXY` where X is the side (L, R, T, B), and Y is the pin number along that side. 18 | Pins also define anchors based on the `name` parameter. 19 | If the `anchorname` parameter is provided for the pin, this name will be used, so that the pin `name` can be any string even if it cannot be used as a Python variable name. 20 | 21 | Here, a J-K flip flop, as part of an HC7476 integrated circuit, is drawn with input names and pin numbers. 22 | 23 | .. jupyter-execute:: 24 | 25 | JK = elm.Ic(pins=[elm.IcPin(name='>', pin='1', side='left'), 26 | elm.IcPin(name='K', pin='16', side='left'), 27 | elm.IcPin(name='J', pin='4', side='left'), 28 | elm.IcPin(name=r'$\overline{Q}$', pin='14', side='right', anchorname='QBAR'), 29 | elm.IcPin(name='Q', pin='15', side='right')], 30 | edgepadW = .5, # Make it a bit wider 31 | pinspacing=1).label('HC7476', 'bottom', fontsize=12) 32 | display(JK) 33 | 34 | 35 | Notice the use of `$\overline{Q}$` to acheive the label on the inverting output. 36 | The anchor positions can be accessed using attributes, such as `JK.Q` for the 37 | non-inverting output. However, inverting output is named `$\overline{Q}`, which is 38 | not accessible using the typical dot notation. It could be accessed using 39 | `getattr(JK, r'$\overline{Q}$')`, but to avoid this an alternative anchorname of `QBAR` 40 | was defined. 41 | 42 | 43 | Multiplexers 44 | ^^^^^^^^^^^^ 45 | 46 | Multiplexers and demultiplexers are drawn with the :py:class:`schemdraw.elements.intcircuits.Multiplexer` class which wraps the Ic class. 47 | 48 | 49 | .. jupyter-execute:: 50 | 51 | elm.Multiplexer( 52 | pins=[elm.IcPin(name='C', side='L'), 53 | elm.IcPin(name='B', side='L'), 54 | elm.IcPin(name='A', side='L'), 55 | elm.IcPin(name='Q', side='R'), 56 | elm.IcPin(name='T', side='B', invert=True)], 57 | edgepadH=-.5) 58 | 59 | See the :ref:`gallery` for more examples. 60 | 61 | 62 | Seven-Segment Display 63 | ^^^^^^^^^^^^^^^^^^^^^ 64 | 65 | A seven-segment display, in :py:class:`schemdraw.elements.intcircuits.SevenSegment`, provides a single digit 66 | with several options including decimal point and common anode or common cathode mode. The :py:meth:`schemdraw.elements.intcircuits.sevensegdigit` method generates a list of Segment objects that can be used to add 67 | a digit to another element, for example to make a multi-digit display. 68 | 69 | .. jupyter-execute:: 70 | :hide-code: 71 | 72 | elm.SevenSegment() 73 | 74 | 75 | DIP Integrated Circuits 76 | ^^^^^^^^^^^^^^^^^^^^^^^ 77 | 78 | Integrated circuits can be drawn in dual-inline package style with :py:class:`schemdraw.elements.intcircuits.IcDIP`. 79 | Anchors allow connecting elements externally to show the IC in a circuit, or interanally to show the internal 80 | configuration of the IC (see :ref:`dip741`.) 81 | 82 | .. jupyter-execute:: 83 | :hide-code: 84 | 85 | elm.IcDIP() 86 | 87 | 88 | Predefined ICs 89 | ^^^^^^^^^^^^^^ 90 | 91 | A few common integrated circuits are predefined as shown below. 92 | 93 | .. grid:: 2 94 | 95 | .. grid-item-card:: 96 | :class-body: sd-text-nowrap sd-fs-6 97 | 98 | VoltageRegulator 99 | 100 | .. jupyter-execute:: 101 | :hide-code: 102 | 103 | with schemdraw.Drawing(): 104 | j = elm.JKFlipFlop() 105 | j.label('J', loc='J', fontsize=10, color='blue') 106 | j.label('K', loc='K', fontsize=10, color='blue') 107 | j.label('Q', loc='Q', fontsize=10, color='blue') 108 | j.label('Qbar', loc='Qbar', fontsize=10, color='blue') 109 | j.label('CLK', loc='CLK', fontsize=10, color='blue') 110 | 111 | 112 | .. grid-item-card:: 113 | :class-body: sd-text-nowrap sd-fs-6 114 | 115 | DFlipFlop 116 | 117 | .. jupyter-execute:: 118 | :hide-code: 119 | 120 | with schemdraw.Drawing(): 121 | d = elm.DFlipFlop() 122 | d.label('D', loc='D', fontsize=10, color='blue') 123 | d.label('Q', loc='Q', fontsize=10, color='blue') 124 | d.label('Qbar', loc='Qbar', fontsize=10, color='blue') 125 | d.label('CLK', loc='CLK', fontsize=10, color='blue') 126 | 127 | 128 | .. grid-item-card:: 129 | :class-body: sd-text-nowrap sd-fs-6 130 | 131 | JKFlipFlop 132 | 133 | .. jupyter-execute:: 134 | :hide-code: 135 | 136 | with schemdraw.Drawing(): 137 | j = elm.JKFlipFlop() 138 | j.label('J', loc='J', fontsize=10, color='blue') 139 | j.label('K', loc='K', fontsize=10, color='blue') 140 | j.label('Q', loc='Q', fontsize=10, color='blue') 141 | j.label('Qbar', loc='Qbar', fontsize=10, color='blue') 142 | j.label('CLK', loc='CLK', fontsize=10, color='blue') 143 | 144 | 145 | .. grid-item-card:: 146 | :class-body: sd-text-nowrap sd-fs-6 147 | 148 | Ic555 149 | 150 | .. jupyter-execute:: 151 | :hide-code: 152 | 153 | with schemdraw.Drawing(): 154 | d = elm.Ic555() 155 | d.label('RST', loc='RST', fontsize=10, color='blue') 156 | d.label('DIS', loc='DIS', fontsize=10, color='blue') 157 | d.label('Vcc', loc='Vcc', fontsize=10, color='blue') 158 | d.label('THR', loc='THR', fontsize=10, color='blue') 159 | d.label('TRG', loc='TRG', fontsize=10, color='blue') 160 | d.label('CTL', loc='CTL', fontsize=10, color='blue') 161 | d.label('OUT', loc='OUT', fontsize=10, color='blue') 162 | d.label('GND', loc='GND', fontsize=10, color='blue') 163 | -------------------------------------------------------------------------------- /docs/elements/logic.rst: -------------------------------------------------------------------------------- 1 | Digital Logic 2 | ============= 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import logic 9 | schemdraw.use('svg') 10 | 11 | 12 | Logic gates can be drawn by importing the :py:mod:`schemdraw.logic.logic` module: 13 | 14 | .. code-block:: python 15 | 16 | from schemdraw import logic 17 | 18 | 19 | Logic gates are shown below. Gates define anchors for `out` and `in1`, `in2`, etc. 20 | `Buf`, `Not`, and `NotNot`, and their Schmitt-trigger counterparts, are two-terminal elements that extend leads. 21 | 22 | 23 | .. element_list:: 24 | :module: logic 25 | :hide_anchors: in1 in2 out 26 | 27 | And() 28 | Nand() 29 | Or() 30 | Nor() 31 | Xor() 32 | Xnor() 33 | Buf() 34 | Not() 35 | NotNot() 36 | Tgate() 37 | Tristate() 38 | Schmitt() 39 | SchmittNot() 40 | SchmittAnd() 41 | SchmittNand() 42 | 43 | 44 | 45 | Gates with more than 2 inputs can be created using the `inputs` parameter. With more than 3 inputs, the back of the gate will extend up and down. 46 | 47 | .. element_list:: 48 | :module: logic 49 | :hide_anchors: in1 in2 in3 in4 out 50 | 51 | Nand(inputs=3) 52 | Nor(inputs=4) 53 | 54 | Finally, any input can be pre-inverted (active low) using the `inputnots` keyword with a list of input numbers, starting at 1 to match the anchor names, on which to add an invert bubble. 55 | 56 | 57 | .. element_list:: 58 | :ncols: 2 59 | :module: logic 60 | :hide_anchors: in1 in2 in3 out 61 | 62 | Nand(inputs=3, inputnots=[1]) 63 | 64 | 65 | Logic Parser 66 | ------------ 67 | 68 | Logic trees can also be created from a string logic expression such as "(a and b) or c" using using :py:func:`schemdraw.parsing.logic_parser.logicparse`. 69 | The logic parser requires the `pyparsing `_ module. 70 | 71 | Examples: 72 | 73 | .. jupyter-execute:: 74 | 75 | from schemdraw.parsing import logicparse 76 | logicparse('not ((w and x) or (y and z))', outlabel=r'$\overline{Q}$') 77 | 78 | .. jupyter-execute:: 79 | 80 | logicparse('((a xor b) and (b or c) and (d or e)) or ((w and x) or (y and z))') 81 | 82 | 83 | Logicparse understands spelled-out logic functions "and", "or", "nand", "nor", "xor", "xnor", "not", but also common symbols such as "+", "&", "⊕" representing "or", "and", and "xor". 84 | 85 | .. jupyter-execute:: 86 | 87 | logicparse('¬ (a ∨ b) & (c ⊻ d)') # Using symbols 88 | 89 | 90 | Use the `gateH` and `gateW` parameters to adjust how gates line up: 91 | 92 | .. jupyter-execute:: 93 | 94 | logicparse('(not a) and b or c', gateH=.5) 95 | 96 | 97 | Truth Tables 98 | ------------ 99 | 100 | Simple tables can be drawn using the :py:class:`schemdraw.logic.table.Table` class. This class is included in the logic module as its primary purpose was for drawing logical truth tables. 101 | 102 | The tables are defined using typical Markdown syntax. The `colfmt` parameter works like the LaTeX tabular environment parameter for defining lines to draw between table columns: "cc|c" draws three centered columns, with a vertical line before the last column. 103 | Each column must be specified with a 'c', 'r', or 'l' for center, right, or left justification 104 | Two pipes (`||`), or a double pipe character (`ǁ`) draw a double bar between columns. 105 | Row lines are added to the table string itself, with either `---` or `===` in the row. 106 | 107 | .. jupyter-execute:: 108 | 109 | table = ''' 110 | A | B | C 111 | ---|---|--- 112 | 0 | 0 | 0 113 | 0 | 1 | 0 114 | 1 | 0 | 0 115 | 1 | 1 | 1 116 | ''' 117 | logic.Table(table, colfmt='cc||c') 118 | 119 | 120 | Karnaugh Maps 121 | ------------- 122 | 123 | Karnaugh Maps, or K-Maps, are useful for simplifying a logical truth table into the smallest number of gates. Schemdraw can draw K-Maps, with 2, 3, or 4 input variables, using the :py:class:`schemdraw.logic.kmap.Kmap` class. 124 | 125 | .. jupyter-execute:: 126 | 127 | logic.Kmap(names='ABCD') 128 | 129 | The `names` parameter must be a string with 2, 3, or 4 characters, each defining the name of one input variable. 130 | The `truthtable` parameter contains a list of tuples defining the logic values to display in the map. The first `len(names)` elements are 0's and 1's defining the position of the cell, and the last element is the string to display in that cell. 131 | The `default` parameter is a string to show in each cell of the K-Map when that cell is undefined in the `truthtable`. 132 | 133 | For example, this 2x2 K-Map has a '1' in the 01 position, and 0's elsewhere: 134 | 135 | .. jupyter-execute:: 136 | 137 | logic.Kmap(names='AB', truthtable=[('01', '1')]) 138 | 139 | K-Maps are typically used by grouping sets of 1's together. These groupings can be drawn using the `groups` parameter. The keys of the `groups` dictionary define which cells to group together, and the values of the dictionary define style parameters for the circle around the group. 140 | Each key must be a string of length `len(names)`, with either a `0`, `1`, or `.` in each position. As an example, with `names='ABCD'`, a group key of `"1..."` will place a circle around all cells where A=1. Or `".00."` draws a circle around all cells where B and C are both 0. Groups will automatically "wrap" around the edges. 141 | Parameters of the style dictionary include `color`, `fill`, `lw`, and `ls`. 142 | 143 | .. jupyter-execute:: 144 | 145 | logic.Kmap(names='ABCD', 146 | truthtable=[('1100', '1'), 147 | ('1101', '1'), 148 | ('1111', '1'), 149 | ('1110', '1'), 150 | ('0101', '1'), 151 | ('0111', 'X'), 152 | ('1101', '1'), 153 | ('1111', '1'), 154 | ('0000', '1'), 155 | ('1000', '1')], 156 | groups={'11..': {'color': 'red', 'fill': '#ff000033'}, 157 | '.1.1': {'color': 'blue', 'fill': '#0000ff33'}, 158 | '.000': {'color': 'green', 'fill': '#00ff0033'}}) 159 | 160 | .. note:: 161 | 162 | `Kmap` and `Table` are both Elements, meaning they may be added to a 163 | schemdraw `Drawing` with other schematic components. 164 | To save a standalone `Kmap` or `Table` to an image file, first add it to a drawing, and 165 | save the drawing: 166 | 167 | .. code-block:: python 168 | 169 | with schemdraw.Drawing(file='truthtable.svg'): 170 | logic.Table(table, colfmt='cc||c') 171 | -------------------------------------------------------------------------------- /docs/elements/pictorial.rst: -------------------------------------------------------------------------------- 1 | .. _pictorial: 2 | 3 | Pictorial Elements 4 | ================== 5 | 6 | .. jupyter-execute:: 7 | :hide-code: 8 | 9 | import schemdraw 10 | from schemdraw import elements as elm 11 | schemdraw.use('svg') 12 | 13 | Pictorial Schematics use pictures, rather than symbols, to represent circuit elements. 14 | Schemdraw provides a few common pictorial elements, and more may be added by loading in 15 | :ref:`images`. 16 | 17 | All the built-in pictorial elements are drawn below. 18 | 19 | .. jupyter-execute:: 20 | 21 | from schemdraw import pictorial 22 | 23 | 24 | .. element_list:: 25 | :module: pictorial 26 | 27 | CapacitorCeramic() 28 | CapacitorMylar() 29 | CapacitorElectrolytic() 30 | TO92() 31 | LED() 32 | LEDOrange() 33 | LEDYellow() 34 | LEDGreen() 35 | LEDBlue() 36 | LEDWhite() 37 | Diode() 38 | Resistor() 39 | DIP() 40 | 41 | 42 | 43 | Breadboard 44 | ---------- 45 | 46 | The :py:class:`schemdraw.pictorial.pictorial.Breadboard` element has anchors at each pin location. Anchor names for the center block of pins 47 | use the column letter and row number, such as `A1` for the top left pin. 48 | The power strip columns along each side are `L1_x` for the first column on the left side or `L2_x`, for 49 | the second column on the left side, with `x` designating the row number. Similarly, `R1_x` and `R2_x` 50 | designate anchors on the right power strip columns. Note the "missing" rows in the power strip columns, such as row 6, 51 | are not defined. Some examples are shown below. 52 | 53 | .. jupyter-execute:: 54 | 55 | with schemdraw.Drawing(): 56 | bb = pictorial.Breadboard() 57 | elm.Dot(radius=.15).at(bb.A1).color('red') 58 | elm.Dot(radius=.15).at(bb.H10).color('orange') 59 | elm.Dot(radius=.15).at(bb.L1_1).color('cyan') 60 | elm.Dot(radius=.15).at(bb.R2_7).color('magenta') 61 | elm.Dot(radius=.15).at(bb.L2_13).color('green') 62 | 63 | 64 | The following example shows the all elements drawn on a breadboard. 65 | 66 | .. jupyter-execute:: 67 | 68 | with schemdraw.Drawing(): 69 | bb = pictorial.Breadboard().up() 70 | pictorial.CapacitorCeramic().at(bb.J1) 71 | pictorial.CapacitorMylar().at(bb.J4) 72 | pictorial.CapacitorElectrolytic().at(bb.J7) 73 | pictorial.TO92().at(bb.J10) 74 | pictorial.LED().at(bb.J13) 75 | pictorial.LEDOrange().at(bb.J16) 76 | pictorial.LEDYellow().at(bb.J19) 77 | pictorial.LEDGreen().at(bb.J22) 78 | pictorial.LEDBlue().at(bb.J25) 79 | pictorial.LEDWhite().at(bb.J28) 80 | pictorial.Diode().at(bb.F9).to(bb.F14) 81 | pictorial.Resistor().at(bb.F2).to(bb.F7) 82 | pictorial.DIP().at(bb.E18).up() 83 | 84 | 85 | 86 | Resistors 87 | --------- 88 | 89 | Resistors and Diodes inherit from :py:class:`schemdraw.elements.Element2Term`, meaning they may be extended to any length. 90 | Resistors take `value` and `tolerance` arguments used to set the color bands. The colors 91 | will be the closest possible color code using 3 bands to represent the value. 92 | 93 | .. jupyter-execute:: 94 | 95 | with schemdraw.Drawing(): 96 | pictorial.Resistor(100) 97 | pictorial.Resistor(220) 98 | pictorial.Resistor(520) 99 | pictorial.Resistor(10000) 100 | 101 | 102 | Dual-inline Packages (DIP) 103 | -------------------------- 104 | 105 | Integrated circuits in DIP packages may be drawn with the :py:class:`schemdraw.pictorial.pictorial.DIP` element. The 106 | `npins` argument sets the total number of pins and `wide` argument specifies a wide-body (0.6 inch) 107 | versus the narrow-body (0.3 inch) package. 108 | 109 | DIPs have anchors `pinX`, where `X` is the pin number. 110 | 111 | .. jupyter-execute:: 112 | 113 | with schemdraw.Drawing(): 114 | pictorial.DIP() 115 | pictorial.DIP(npins=14).at((2, 0)) 116 | pictorial.DIP(npins=28, wide=True).at((4, 0)) 117 | 118 | 119 | Colors 120 | ------ 121 | 122 | The pictorial elements are drawn using solid shapes. As such, the `.fill()` method must be 123 | used to change their color, while the `.color()` method will set only the color of the outline, if the Element has one. 124 | For example, to create a custom-color LED: 125 | 126 | .. jupyter-execute:: 127 | 128 | pictorial.LED().fill('purple') 129 | 130 | 131 | Dimensions 132 | ---------- 133 | 134 | The pictorial elements are designed with spacing so they fit together in a breadboard with 0.1 inch spacing between pins. 135 | Some constants are defined to assist in creating other pictorial elements: 136 | `pictorial.INCH` and `pictorial.MILLIMETER` convert inches and millimeters to schemdraw's drawing units. 137 | `pictorial.PINSPACING` is equal to 0.1 inch, the standard spacing between breadboard and DIP pins. 138 | 139 | 140 | Example 141 | ------- 142 | 143 | This example combines an :py:class:`schemdraw.elements.ElementImage` of an Arduino Uno board 144 | with pictorial elements. 145 | 146 | .. jupyter-execute:: 147 | 148 | class ArduinoUno(elm.ElementImage): 149 | ''' Arduino Element ''' 150 | def __init__(self): 151 | width = 10.3 # Set the width to scale properly for 0.1 inch pin spacing on headers 152 | height = width/1.397 # Based on image dimensions 153 | super().__init__('ArduinoUNO.png', width=width, height=height, xy=(-.75, 0)) 154 | 155 | # Define all the anchors 156 | top = height * .956 157 | arefx = 3.4 158 | pinspace = pictorial.PINSPACING 159 | for i, pinname in enumerate(['aref', 'gnd_top', 'pin13', 'pin12', 'pin11', 160 | 'pin10', 'pin9', 'pin8']): 161 | self.anchors[pinname] = (arefx + i*pinspace, top) 162 | 163 | bot = .11*pictorial.INCH 164 | botx = 1.23*pictorial.INCH 165 | for i, pinname in enumerate(['ioref', 'reset', 'threev3', 166 | 'fivev', 'gnd1', 'gnd2', 'vin']): 167 | self.anchors[pinname] = (botx + i*pinspace, bot) 168 | 169 | botx += i*pinspace + pictorial.PINSPACING*2 170 | for i, pinname in enumerate(['A0', 'A1', 'A2', 'A3', 'A4', 'A5']): 171 | self.anchors[pinname] = (botx + i*pinspace, bot) 172 | 173 | 174 | with schemdraw.Drawing(): 175 | ard = ArduinoUno() 176 | bb = pictorial.Breadboard().at((0, 9)).up() 177 | elm.Wire('n', k=-1).at(ard.gnd2).to(bb.L2_29).linewidth(4) 178 | elm.Wire().at(ard.pin12).to(bb.A14).color('red').linewidth(4) 179 | pictorial.LED().at(bb.E14) 180 | pictorial.Resistor(330).at(bb.D15).to(bb.L2_15) 181 | 182 | `Arduino Image Source `_ , CC-BY-SA-3.0. 183 | 184 | 185 | Fritzing Part Files 186 | ------------------- 187 | 188 | Schemdraw can import part files in the `Fritzing `_ format and use them in pictorial schematics. 189 | Use :py:class:`schemdraw.pictorial.fritz.FritzingPart` and provide the file name of an `.fzpz` or `.fzbz` part file. 190 | Schemdraw's anchors will be set based on the part "connectors" defined in the part file. 191 | In this example, a part is downloaded from the `Adafruit Fritzing Library `_ and used in a drawing. 192 | 193 | Because Fritzing images are SVG format, `FritzingPart` only works in schemdraw's SVG backend (:ref:`backends`). 194 | 195 | .. jupyter-execute:: 196 | 197 | schemdraw.use('svg') 198 | from urllib.request import urlretrieve 199 | part = 'https://github.com/adafruit/Fritzing-Library/raw/master/parts/Adafruit%20OLED%20Monochrome%20128x32%20SPI.fzpz' 200 | fname, msg = urlretrieve(part) 201 | 202 | with schemdraw.Drawing() as d: 203 | oled = pictorial.FritzingPart(fname) 204 | elm.Line().down().at(oled.GND).length(.5) 205 | elm.Ground() 206 | elm.Line().down().at(oled.absanchors['3.3V']).color('red').length(1.5).label('3.3V', loc='left') 207 | elm.Button().at(oled.RESET) 208 | elm.Ground(lead=False) 209 | 210 | Note that occasionally anchor names defined in Fritzing parts are not valid as Python identifiers, such as the `3.3V` anchor above, and therefore cannot be used as attributes of the element instance (`f.3.3V` doesn't work, obviously). In these cases, the anchor must be accessed through the `absanchors` dictionary. 211 | 212 | -------------------------------------------------------------------------------- /docs/elements/timing.rst: -------------------------------------------------------------------------------- 1 | Timing Diagrams 2 | =============== 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import logic 9 | schemdraw.use('svg') 10 | 11 | 12 | Digital timing diagrams may be drawn using the :py:class:`schemdraw.logic.timing.TimingDiagram` Element in the :py:mod:`schemdraw.logic` module. 13 | 14 | Timing diagrams are set up using the WaveJSON syntax used by the `WaveDrom `_ JavaScript application. 15 | 16 | 17 | .. code-block:: python 18 | 19 | from schemdraw import logic 20 | 21 | 22 | .. jupyter-execute:: 23 | 24 | logic.TimingDiagram( 25 | {'signal': [ 26 | {'name': 'A', 'wave': '0..1..01.'}, 27 | {'name': 'B', 'wave': '101..0...'}]}) 28 | 29 | The input is a dictionary containing a `signal`, which is a list of each wave to show in the diagram. Each signal is a dictionary which must contain a `name` and `wave`. 30 | An empty dictionary leaves a blank row in the diagram. 31 | 32 | Every character in the `wave` specifies the state of the wave for one period. A dot `.` means the previous state is repeated. 33 | Wave characters 'n' and 'p' specify clock signals, and 'N', and 'P' draw clocks with arrows. 34 | '1' and '0' are used to define high and low signals. '2' draws a data block, and '3' through '9' draw data filled with a color. 'x' draws a don't-care or undefined data state. 35 | 36 | Data blocks can be labeled by adding a 'data' item to the wave's dictionary. 37 | 38 | This example shows the different wave sections: 39 | 40 | .. jupyter-execute:: 41 | 42 | logic.TimingDiagram( 43 | {'signal': [ 44 | {'name': 'clock n', 'wave': 'n......'}, 45 | {'name': 'clock p', 'wave': 'p......'}, 46 | {'name': 'clock N', 'wave': 'N......'}, 47 | {'name': 'clock P', 'wave': 'P......'}, 48 | {}, 49 | {'name': '1s and 0s', 'wave': '0.1.01.'}, 50 | {'name': 'data', 'wave': '2..=.2.'}, # '=' is the same as '2' 51 | {'name': 'data named', 'wave': '3.4.6..', 'data': ['A', 'B', 'C']}, 52 | {'name': 'dont care', 'wave': 'xx..x..'}, 53 | {}, 54 | {'name': 'high z', 'wave': 'z.10.z.'}, 55 | {'name': 'pull up/down', 'wave': '0u..d.1'}, 56 | ]}) 57 | 58 | 59 | Putting them together in a more realistic example: 60 | 61 | .. jupyter-execute:: 62 | 63 | logic.TimingDiagram( 64 | {'signal': [ 65 | {'name': 'clk', 'wave': 'P......'}, 66 | {'name': 'bus', 'wave': 'x.==.=x', 'data': ['head', 'body', 'tail']}, 67 | {'name': 'wire', 'wave': '0.1..0.'}]}) 68 | 69 | 70 | The `config` key, containing a dictionary with `hscale`, may be used to change the width of one period in the diagram: 71 | 72 | .. jupyter-execute:: 73 | :emphasize-lines: 6 74 | 75 | logic.TimingDiagram( 76 | {'signal': [ 77 | {'name': 'clk', 'wave': 'P......'}, 78 | {'name': 'bus', 'wave': 'x.==.=x', 'data': ['head', 'body', 'tail']}, 79 | {'name': 'wire', 'wave': '0.1..0.'}], 80 | 'config': {'hscale': 2}}) 81 | 82 | 83 | Signals may also be nested into different groups: 84 | 85 | .. jupyter-execute:: 86 | 87 | logic.TimingDiagram( 88 | {'signal': ['Group', 89 | ['Set 1', 90 | {'name': 'A', 'wave': '0..1..01.'}, 91 | {'name': 'B', 'wave': '101..0...'}], 92 | ['Set 2', 93 | {'name': 'C', 'wave': '0..1..01.'}, 94 | {'name': 'D', 'wave': '101..0...'}] 95 | ]}) 96 | 97 | 98 | Using the `node` key in a waveform, plus the `edge` key in the top-level dictionary, provides a way to show transitions between different edges. 99 | 100 | .. jupyter-execute:: 101 | :emphasize-lines: 5 102 | 103 | logic.TimingDiagram( 104 | {'signal': [ 105 | {'name': 'A', 'wave': '0..1..01.', 'node': '...a.....'}, 106 | {'name': 'B', 'wave': '101..0...', 'node': '.....b...'}], 107 | 'edge': ['a~>b'] 108 | }) 109 | 110 | 111 | Each string in the edge list must start and end with a node name (single character). The characters between them define the type of connecting line: '-' for straight line, '~' for curve, '-\|' for orthogonal lines, and \< or \> to include arrowheads. 112 | For example, 'a-~>b' draws a curved line with arrowhead between nodes a and b. 113 | 114 | 115 | Using JSON 116 | ---------- 117 | 118 | Because the examples from WaveDrom use JavaScript and JSON, they sometimes cannot be directly pasted into Python as dictionaries. 119 | The :py:meth:`schemdraw.logic.timing.TimingDiagram.from_json` method allows input of the WaveJSON as a string pasted directly from the Javascript/JSON examples without modification. 120 | 121 | Notice lack of quoting on the dictionary keys, requiring the `from_json` method to parse the string. 122 | 123 | .. jupyter-execute:: 124 | 125 | logic.TimingDiagram.from_json('''{ signal: [ 126 | { name: "clk", wave: "P......" }, 127 | { name: "bus", wave: "x.==.=x", data: ["head", "body", "tail", "data"] }, 128 | { name: "wire", wave: "0.1..0." } 129 | ]}''') 130 | 131 | 132 | 133 | Schemdraw's Customizations 134 | -------------------------- 135 | 136 | Schemdraw extends the WaveJSON spcification with a few additional options. 137 | 138 | Style Parameters 139 | **************** 140 | 141 | Each wave dictionary accpets a `color` and `lw` parameter. 142 | The rise/fall time for transitions can be set using the `risetime` parameter to TimingDiagram. Other colors and font sizes may be speficied using keyword arguments to :py:class:`schemdraw.logic.timing.TimingDiagram`. 143 | 144 | Asynchronous Signals 145 | ******************** 146 | 147 | WaveDrom does not have a means for defining asynchronous signals - all waves must transition on period boundaries. Schemdraw adds asyncrhonous signals using the `async` parameter, as a list of period multiples for each transition in the wave. Note the beginning and end time of the wave must also be specified, so the length of the `async` list must be one more than the length of `wave`. 148 | 149 | .. jupyter-execute:: 150 | :emphasize-lines: 4 151 | 152 | logic.TimingDiagram( 153 | {'signal': [ 154 | {'name': 'clk', 'wave': 'n......'}, 155 | {'name': 'B', 'wave': '010', 'async': [0, 1.6, 4.25, 7]}]}, 156 | risetime=.03) 157 | 158 | 159 | Extended Edge Notation 160 | ********************** 161 | 162 | Additional "edge" string notations are allowed for more complex labeling of edge timings, including asynchronous start and end times and labels just above or below a wave. 163 | 164 | Each edge string using this syntax takes the form 165 | 166 | .. code-block:: python 167 | 168 | '[WaveNum:Period]<->[WaveNum:Period]{color,ls} Label' 169 | 170 | Everything after the first space will be drawn as the label in the center of the line. 171 | The values in square brackets designate the start and end position of the line. 172 | `WaveNum` is the integer row number (starting at 0) of the wave, and `Period` is the possibly fractional number of periods in time for the node. `WaveNum` may be appended by a `^` or `v` to designate notations just above, or just below, the wave, respectively. 173 | 174 | Between the two square-bracket expressions is the standard line/arrow type designator. In optional curly braces, the line color and linestyle may be entered. 175 | 176 | Some examples are shown here: 177 | 178 | .. jupyter-execute:: 179 | :emphasize-lines: 5-7 180 | 181 | logic.TimingDiagram( 182 | {'signal': [ 183 | {'name': 'A', 'wave': 'x3...x'}, 184 | {'name': 'B', 'wave': 'x6.6.x'}], 185 | 'edge': ['[0^:1]+[0^:5] $t_1$', 186 | '[1^:1]<->[1^:3] $t_o$', 187 | '[0^:3]-[1v:3]{gray,:}', 188 | ]}, 189 | ygap=.5, grid=False) 190 | 191 | 192 | When placing edge labels above or below the wave, it can be useful to add the `ygap` parameter to TimingDiagram to increase the spacing between waves. 193 | 194 | 195 | See the :ref:`gallerytiming` Gallery for more examples. 196 | 197 | 198 | .. note:: 199 | 200 | `TimingDiagram` is an `Element`, meaning it may be added to a 201 | schemdraw `Drawing` with other schematic components. 202 | To save a standalone `TimingDiagram` to an image file, first add it to a drawing, and 203 | save the drawing: 204 | 205 | .. code-block:: python 206 | 207 | with schemdraw.Drawing(file='timing.svg'): 208 | logic.TimingDiagram( 209 | {'signal': [ 210 | {'name': 'A', 'wave': '0..1..01.'}, 211 | {'name': 'B', 'wave': '101..0...'}]}) 212 | -------------------------------------------------------------------------------- /docs/gallery/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _gallery: 3 | 4 | Circuit Gallery 5 | =============== 6 | 7 | 8 | .. toctree:: 9 | 10 | analog 11 | opamp 12 | logicgate 13 | timing 14 | solidstate 15 | ic 16 | signalproc 17 | pictorial 18 | flowcharting 19 | styles 20 | 21 | 22 | ------- 23 | 24 | Need more circuit examples? Check out the Schemdraw Examples Pack on buymeacoffee.com: 25 | 26 | .. raw:: html 27 | 28 | Buy Me A Coffee 29 | -------------------------------------------------------------------------------- /docs/gallery/logicgate.rst: -------------------------------------------------------------------------------- 1 | Digital Logic 2 | ------------- 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | from schemdraw import logic 10 | schemdraw.use('svg') 11 | 12 | 13 | Logic gate definitions are in the :py:mod:`schemdraw.logic.logic` module. Here it was imported with 14 | 15 | .. code-block:: python 16 | 17 | from schemdraw import logic 18 | 19 | 20 | Half Adder 21 | ^^^^^^^^^^ 22 | 23 | Notice the half and full adders set the drawing unit to 0.5 so the lines aren't quite as long and look better with logic gates. 24 | 25 | .. jupyter-execute:: 26 | :code-below: 27 | 28 | with schemdraw.Drawing() as d: 29 | d.config(unit=0.5) 30 | S = logic.Xor().label('S', 'right') 31 | logic.Line().left(d.unit*2).at(S.in1).idot().label('A', 'left') 32 | B = logic.Line().left().at(S.in2).dot() 33 | logic.Line().left().label('B', 'left') 34 | logic.Line().down(d.unit*3).at(S.in1) 35 | C = logic.And().right().anchor('in1').label('C', 'right') 36 | logic.Wire('|-').at(B.end).to(C.in2) 37 | 38 | 39 | Full Adder 40 | ^^^^^^^^^^ 41 | 42 | .. jupyter-execute:: 43 | :code-below: 44 | 45 | with schemdraw.Drawing() as d: 46 | d.config(unit=0.5) 47 | X1 = logic.Xor() 48 | A = logic.Line().left(d.unit*2).at(X1.in1).idot().label('A', 'left') 49 | B = logic.Line().left().at(X1.in2).dot() 50 | logic.Line().left().label('B', 'left') 51 | 52 | logic.Line().right().at(X1.out).idot() 53 | X2 = logic.Xor().anchor('in1') 54 | C = logic.Line().down(d.unit*2).at(X2.in2) 55 | d.push() 56 | logic.Dot().at(C.center) 57 | logic.Line().tox(A.end).label('C$_{in}$', 'left') 58 | d.pop() 59 | 60 | A1 = logic.And().right().anchor('in1') 61 | logic.Wire('-|').at(A1.in2).to(X1.out) 62 | d.move_from(A1.in2, dy=-d.unit*2) 63 | A2 = logic.And().right().anchor('in1') 64 | logic.Wire('-|').at(A2.in1).to(A.start) 65 | logic.Wire('-|').at(A2.in2).to(B.end) 66 | d.move_from(A1.out, dy=-(A1.out.y-A2.out.y)/2) 67 | O1 = logic.Or().right().label('C$_{out}$', 'right') 68 | logic.Line().at(A1.out).toy(O1.in1) 69 | logic.Line().at(A2.out).toy(O1.in2) 70 | logic.Line().at(X2.out).tox(O1.out).label('S', 'right') 71 | 72 | 73 | J-K Flip Flop 74 | ^^^^^^^^^^^^^ 75 | 76 | Note the use of the LaTeX command **overline{Q}** in the label to draw a bar over the inverting output label. 77 | 78 | .. jupyter-execute:: 79 | :code-below: 80 | 81 | with schemdraw.Drawing() as d: 82 | # Two front gates (SR latch) 83 | G1 = logic.Nand(leadout=.75).anchor('in1') 84 | logic.Line().length(d.unit/2).label('Q', 'right') 85 | d.move_from(G1.in1, dy=-2.5) 86 | G2 = logic.Nand(leadout=.75).anchor('in1') 87 | logic.Line().length(d.unit/2).label(r'$\overline{Q}$', 'right') 88 | logic.Wire('N', k=.5).at(G2.in1).to(G1.out).dot() 89 | logic.Wire('N', k=.5).at(G1.in2).to(G2.out).dot() 90 | 91 | # Two back gates 92 | logic.Line().left(d.unit/6).at(G1.in1) 93 | J = logic.Nand(inputs=3).anchor('out').right() 94 | logic.Wire('n', k=.5).at(J.in1).to(G2.out, dx=1).dot() 95 | logic.Line().left(d.unit/4).at(J.in2).label('J', 'left') 96 | logic.Line().left(d.unit/6).at(G2.in2) 97 | K = logic.Nand(inputs=3).right().anchor('out') 98 | logic.Wire('n', k=-.5).at(K.in3).to(G1.out, dx=.5).dot() 99 | logic.Line().left(d.unit/4).at(K.in2).label('K', 'left') 100 | C = logic.Line().at(J.in3).toy(K.in1) 101 | logic.Dot().at(C.center) 102 | logic.Line().left(d.unit/4).label('CLK', 'left') 103 | 104 | 105 | S-R Latch (Gates) 106 | ^^^^^^^^^^^^^^^^^ 107 | 108 | .. jupyter-execute:: 109 | :code-below: 110 | 111 | with schemdraw.Drawing() as d: 112 | g1 = logic.Nor() 113 | d.move_from(g1.in1, dy=-2.5) 114 | g2 = logic.Nor().anchor('in1') 115 | g1out = logic.Line().right(.25).at(g1.out) 116 | logic.Wire('N', k=.5).at(g2.in1).to(g1out.end).dot() 117 | g2out = logic.Line().right(.25).at(g2.out) 118 | logic.Wire('N', k=.5).at(g1.in2).to(g2out.end).dot() 119 | logic.Line().at(g1.in1).left(.5).label('R', 'left') 120 | logic.Line().at(g2.in2).left(.5).label('S', 'left') 121 | logic.Line().at(g1.out).right(.75).label('Q', 'right') 122 | logic.Line().at(g2.out).right(.75).label(r'$\overline{Q}$', 'right') 123 | -------------------------------------------------------------------------------- /docs/gallery/opamp.rst: -------------------------------------------------------------------------------- 1 | Opamp Circuits 2 | -------------- 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | schemdraw.use('svg') 10 | 11 | 12 | Inverting Opamp 13 | ^^^^^^^^^^^^^^^ 14 | 15 | .. jupyter-execute:: 16 | :code-below: 17 | 18 | with schemdraw.Drawing() as d: 19 | op = elm.Opamp(leads=True) 20 | elm.Line().down(d.unit/4).at(op.in2) 21 | elm.Ground(lead=False) 22 | Rin = elm.Resistor().at(op.in1).left().idot().label('$R_{in}$', loc='bot').label('$v_{in}$', loc='left') 23 | elm.Line().up(d.unit/2).at(op.in1) 24 | elm.Resistor().tox(op.out).label('$R_f$') 25 | elm.Line().toy(op.out).dot() 26 | elm.Line().right(d.unit/4).at(op.out).label('$v_{o}$', loc='right') 27 | 28 | 29 | Non-inverting Opamp 30 | ^^^^^^^^^^^^^^^^^^^ 31 | 32 | .. jupyter-execute:: 33 | :code-below: 34 | 35 | with schemdraw.Drawing() as d: 36 | op = elm.Opamp(leads=True) 37 | out = elm.Line().at(op.out).length(.75) 38 | elm.Line().up().at(op.in1).length(1.5).dot() 39 | d.push() 40 | elm.Resistor().left().label('$R_1$') 41 | elm.Ground() 42 | d.pop() 43 | elm.Resistor().tox(op.out).label('$R_f$') 44 | elm.Line().toy(op.out).dot() 45 | elm.Resistor().left().at(op.in2).idot().label('$R_2$') 46 | elm.SourceV().down().reverse().label('$v_{in}$') 47 | elm.Line().right().dot() 48 | elm.Resistor().up().label('$R_3$').hold() 49 | elm.Line().tox(out.end) 50 | elm.Gap().toy(op.out).label(['–','$v_o$','+']) 51 | 52 | 53 | Multi-stage amplifier 54 | ^^^^^^^^^^^^^^^^^^^^^ 55 | 56 | .. jupyter-execute:: 57 | :code-below: 58 | 59 | with schemdraw.Drawing() as d: 60 | elm.Ground(lead=False) 61 | elm.SourceV().label('500mV') 62 | elm.Resistor().right().label(r'20k$\Omega$').dot() 63 | O1 = elm.Opamp(leads=True).anchor('in1') 64 | elm.Ground().at(O1.in2) 65 | elm.Line().up(2).at(O1.in1) 66 | elm.Resistor().tox(O1.out).label(r'100k$\Omega$') 67 | elm.Line().toy(O1.out).dot() 68 | elm.Line().right(5).at(O1.out) 69 | O2 = elm.Opamp(leads=True).anchor('in2') 70 | elm.Resistor().left().at(O2.in1).idot().label(r'30k$\Omega$') 71 | elm.Ground() 72 | elm.Line().up(1.5).at(O2.in1) 73 | elm.Resistor().tox(O2.out).label(r'90k$\Omega$') 74 | elm.Line().toy(O2.out).dot() 75 | elm.Line().right(1).at(O2.out).label('$v_{out}$', loc='rgt') 76 | 77 | 78 | Opamp pin labeling 79 | ^^^^^^^^^^^^^^^^^^ 80 | 81 | This example shows how to label pin numbers on a 741 opamp, and connect to the offset anchors. 82 | Pin labels are somewhat manually placed; without the `ofst` and `align` keywords they 83 | will be drawn directly over the anchor position. 84 | 85 | .. jupyter-execute:: 86 | :code-below: 87 | 88 | with schemdraw.Drawing() as d: 89 | d.config(fontsize=12) 90 | op = (elm.Opamp().label('741', loc='center', ofst=0) 91 | .label('1', 'n1', fontsize=9, ofst=(-.1, -.25), halign='right', valign='top') 92 | .label('5', 'n1a', fontsize=9, ofst=(-.1, -.25), halign='right', valign='top') 93 | .label('4', 'vs', fontsize=9, ofst=(-.1, -.2), halign='right', valign='top') 94 | .label('7', 'vd', fontsize=9, ofst=(-.1, .2), halign='right', valign='bottom') 95 | .label('2', 'in1', fontsize=9, ofst=(-.1, .1), halign='right', valign='bottom') 96 | .label('3', 'in2', fontsize=9, ofst=(-.1, .1), halign='right', valign='bottom') 97 | .label('6', 'out', fontsize=9, ofst=(-.1, .1), halign='left', valign='bottom')) 98 | elm.Line().left(.5).at(op.in1) 99 | elm.Line().down(d.unit/2) 100 | elm.Ground(lead=False) 101 | elm.Line().left(.5).at(op.in2) 102 | elm.Line().right(.5).at(op.out).label('$V_o$', 'right') 103 | elm.Line().up(1).at(op.vd).label('$+V_s$', 'right') 104 | trim = elm.Potentiometer().down().at(op.n1).flip().scale(0.7) 105 | elm.Line().tox(op.n1a) 106 | elm.Line().up().to(op.n1a) 107 | elm.Line().at(trim.tap).tox(op.vs).dot() 108 | d.push() 109 | elm.Line().down(d.unit/3) 110 | elm.Ground() 111 | d.pop() 112 | elm.Line().toy(op.vs) 113 | 114 | 115 | Triaxial Cable Driver 116 | ^^^^^^^^^^^^^^^^^^^^^ 117 | 118 | .. jupyter-execute:: 119 | :code-below: 120 | 121 | with schemdraw.Drawing() as d: 122 | d.config(fontsize=10) 123 | elm.Line().length(d.unit/5).label('V', 'left') 124 | smu = (elm.Opamp(sign=False).anchor('in2') 125 | .label('SMU', 'center', ofst=[-.4, 0], halign='center', valign='center')) 126 | elm.Line().at(smu.out).length(.3) 127 | d.push() 128 | elm.Line().length(d.unit/4) 129 | triax = elm.Triax(length=5, shieldofststart=.75) 130 | d.pop() 131 | elm.Resistor().up().scale(0.6).idot() 132 | elm.Line().left().dot() 133 | elm.Wire('|-').to(smu.in1).hold() 134 | elm.Wire('|-').delta(d.unit/5, d.unit/5) 135 | buf = (elm.Opamp(sign=False).anchor('in2').scale(0.6) 136 | .label('BUF', 'center', ofst=(-.4, 0), halign='center', valign='center')) 137 | 138 | elm.Line().left(d.unit/5).at(buf.in1) 139 | elm.Wire('n').to(buf.out, dx=.5).dot() 140 | elm.Wire('-|').at(buf.out).to(triax.guardstart_top) 141 | elm.GroundChassis().at(triax.shieldcenter) 142 | -------------------------------------------------------------------------------- /docs/gallery/pictorial.rst: -------------------------------------------------------------------------------- 1 | Pictorial Schematics 2 | -------------------- 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | from schemdraw import pictorial 10 | schemdraw.use('svg') 11 | 12 | 13 | LED Blinker 14 | *********** 15 | 16 | .. jupyter-execute:: 17 | :code-below: 18 | 19 | elm.Line.defaults['lw'] = 4 20 | 21 | with schemdraw.Drawing(): 22 | bb = pictorial.Breadboard().up() 23 | pictorial.DIP().up().at(bb.E5).label('555', color='#DDD') 24 | elm.Line().at(bb.A8).to(bb.L1_7) 25 | elm.Line().at(bb.J5).to(bb.R1_4) 26 | elm.Line().at(bb.A5).to(bb.L2_4).color('black') 27 | pictorial.Resistor(330).at(bb.B7).to(bb.B12) 28 | pictorial.LED(lead_length=.3*pictorial.INCH).at(bb.C12) 29 | elm.Line().at(bb.A13).to(bb.L2_13).color('black') 30 | pictorial.Resistor(520).at(bb.G6).to(bb.G3) 31 | pictorial.Resistor(520).at(bb.J6).to(bb.R1_10) 32 | elm.Line().at(bb.H3).to(bb.H7).color('green') 33 | elm.Wire('c').at(bb.G7).to(bb.D6).linewidth(4).color('green') 34 | elm.Line().at(bb.H8).to(bb.H12).color('green') 35 | elm.Line().at(bb.J13).to(bb.R2_14).color('black') 36 | pictorial.CapacitorMylar(lead_length=.2*pictorial.INCH).at(bb.I12) 37 | elm.Line().at(bb.C6).to(bb.C3).color('green') 38 | pictorial.CapacitorMylar(lead_length=.2*pictorial.INCH).at(bb.D2) 39 | elm.Line().at(bb.A2).to(bb.L2_1).color('black') 40 | -------------------------------------------------------------------------------- /docs/gallery/signalproc.rst: -------------------------------------------------------------------------------- 1 | Signal Processing 2 | ----------------- 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | from schemdraw import dsp 10 | schemdraw.use('svg') 11 | 12 | 13 | Signal processing elements are in the :py:mod:`schemdraw.dsp.dsp` module. 14 | 15 | .. code-block:: python 16 | 17 | from schemdraw import dsp 18 | 19 | 20 | Various Networks 21 | ^^^^^^^^^^^^^^^^ 22 | 23 | .. jupyter-execute:: 24 | :code-below: 25 | 26 | with schemdraw.Drawing() as d: 27 | dsp.Line().length(d.unit/3).label('in') 28 | inpt = dsp.Dot() 29 | dsp.Arrow().length(d.unit/3) 30 | delay = dsp.Box(w=2, h=2).anchor('W').label('Delay\nT') 31 | dsp.Arrow().right(d.unit/2).at(delay.E) 32 | sm = dsp.SumSigma() 33 | dsp.Arrow().at(sm.E).length(d.unit/2) 34 | intg = dsp.Box(w=2, h=2).anchor('W').label(r'$\int$') 35 | dsp.Arrow().right(d.unit/2).at(intg.E).label('out', loc='right') 36 | dsp.Line().down(d.unit/2).at(inpt.center) 37 | dsp.Line().tox(sm.S) 38 | dsp.Arrow().toy(sm.S).label('+', loc='bot') 39 | 40 | 41 | .. jupyter-execute:: 42 | :code-below: 43 | 44 | with schemdraw.Drawing() as d: 45 | d.config(fontsize=14) 46 | dsp.Line().length(d.unit/2).label('F(s)').dot() 47 | d.push() 48 | dsp.Line().up(d.unit/2) 49 | dsp.Arrow().right(d.unit/2) 50 | h1 = dsp.Box(w=2, h=2).anchor('W').label('$H_1(s)$') 51 | d.pop() 52 | dsp.Line().down(d.unit/2) 53 | dsp.Arrow().right(d.unit/2) 54 | h2 = dsp.Box(w=2, h=2).anchor('W').label('$H_2(s)$') 55 | sm = dsp.SumSigma().right().at((h1.E[0] + d.unit/2, 0)).anchor('center') 56 | dsp.Line().at(h1.E).tox(sm.N) 57 | dsp.Arrow().toy(sm.N) 58 | dsp.Line().at(h2.E).tox(sm.S) 59 | dsp.Arrow().toy(sm.S) 60 | dsp.Arrow().right(d.unit/3).at(sm.E).label('Y(s)', 'right') 61 | 62 | 63 | Superheterodyne Receiver 64 | ^^^^^^^^^^^^^^^^^^^^^^^^ 65 | 66 | `Source `_. 67 | 68 | .. jupyter-execute:: 69 | :code-below: 70 | 71 | with schemdraw.Drawing() as d: 72 | d.config(fontsize=12) 73 | dsp.Antenna() 74 | dsp.Line().right(d.unit/4) 75 | dsp.Filter(response='bp').fill('thistle').anchor('W').label('RF filter\n#1', 'bottom', ofst=.2) 76 | dsp.Line().length(d.unit/4) 77 | dsp.Amp().fill('lightblue').label('LNA') 78 | dsp.Line().length(d.unit/4) 79 | dsp.Filter(response='bp').anchor('W').fill('thistle').label('RF filter\n#2', 'bottom', ofst=.2) 80 | dsp.Line().length(d.unit/3) 81 | mix = dsp.Mixer().fill('navajowhite').label('Mixer') 82 | dsp.Line().at(mix.S).down(d.unit/3) 83 | dsp.Oscillator().right().anchor('N').fill('navajowhite').label('Local\nOscillator', 'right', ofst=.2) 84 | dsp.Line().at(mix.E).right(d.unit/3) 85 | dsp.Filter(response='bp').anchor('W').fill('thistle').label('IF filter', 'bottom', ofst=.2) 86 | dsp.Line().right(d.unit/4) 87 | dsp.Amp().fill('lightblue').label('IF\namplifier') 88 | dsp.Line().length(d.unit/4) 89 | dsp.Demod().anchor('W').fill('navajowhite').label('Demodulator', 'bottom', ofst=.2) 90 | dsp.Arrow().right(d.unit/3) 91 | 92 | 93 | Direct Conversion Receiver 94 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 95 | 96 | .. jupyter-execute:: 97 | :code-below: 98 | 99 | with schemdraw.Drawing() as d: 100 | dsp.Antenna() 101 | dsp.Arrow().right(d.unit/2).label('$f_{RF}$', 'bot') 102 | dsp.Amp().label('LNA') 103 | dsp.Line().right(d.unit/5).dot() 104 | d.push() 105 | dsp.Line().length(d.unit/4) 106 | mix1 = dsp.Mixer().label('Mixer', ofst=0) 107 | dsp.Arrow().length(d.unit/2) 108 | lpf1 = dsp.Filter(response='lp').label('LPF', 'bot', ofst=.2) 109 | dsp.Line().length(d.unit/6) 110 | adc1 = dsp.Adc().label('ADC') 111 | dsp.Arrow().length(d.unit/3) 112 | dsp1 = dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], 113 | size=(2.75, 5), leadlen=0).anchor('inL2').label('DSP') 114 | dsp.Arrow().at(dsp1.inR1).length(d.unit/3) 115 | d.pop() 116 | 117 | dsp.Line().toy(dsp1.inL1) 118 | dsp.Arrow().tox(mix1.W) 119 | mix2 = dsp.Mixer().label('Mixer', ofst=0) 120 | dsp.Arrow().tox(lpf1.W) 121 | dsp.Filter(response='lp').label('LPF', 'bot', ofst=.2) 122 | dsp.Line().tox(adc1.W) 123 | dsp.Adc().label('ADC') 124 | dsp.Arrow().to(dsp1.inL1) 125 | 126 | dsp.Arrow().down(d.unit/6).reverse().at(mix1.S) 127 | dsp.Line().left(d.unit*1.25) 128 | dsp.Line().down(d.unit*.75) 129 | flo = dsp.Dot().label('$f_{LO}$', 'left') 130 | d.push() 131 | dsp.Line().down(d.unit/5) 132 | dsp.Oscillator().right().anchor('N').label('LO', 'left', ofst=.15) 133 | d.pop() 134 | dsp.Arrow().down(d.unit/4).reverse().at(mix2.S) 135 | b1 = dsp.Square().right().label('90°').anchor('N') 136 | dsp.Arrow().left(d.unit/4).reverse().at(b1.W) 137 | dsp.Line().toy(flo.center) 138 | dsp.Line().tox(flo.center) 139 | 140 | 141 | Digital Filter 142 | ^^^^^^^^^^^^^^ 143 | 144 | .. jupyter-execute:: 145 | :code-below: 146 | 147 | with schemdraw.Drawing() as d: 148 | d.config(unit=1, fontsize=14) 149 | dsp.Line().length(d.unit*2).label('x[n]', 'left').dot() 150 | 151 | d.push() 152 | dsp.Line().right() 153 | dsp.Amp().label('$b_0$', 'bottom') 154 | dsp.Arrow() 155 | s0 = dsp.Sum().anchor('W') 156 | d.pop() 157 | 158 | dsp.Arrow().down() 159 | z1 = dsp.Square(label='$z^{-1}$') 160 | dsp.Line().length(d.unit/2).dot() 161 | 162 | d.push() 163 | dsp.Line().right() 164 | dsp.Amp().label('$b_1$', 'bottom') 165 | dsp.Arrow() 166 | s1 = dsp.Sum().anchor('W') 167 | d.pop() 168 | 169 | dsp.Arrow().down(d.unit*.75) 170 | dsp.Square().label('$z^{-1}$') 171 | dsp.Line().length(d.unit*.75) 172 | dsp.Line().right() 173 | dsp.Amp().label('$b_2$', 'bottom') 174 | dsp.Arrow() 175 | s2 = dsp.Sum().anchor('W') 176 | 177 | dsp.Arrow().at(s2.N).toy(s1.S) 178 | dsp.Arrow().at(s1.N).toy(s0.S) 179 | 180 | dsp.Line().right(d.unit*2.75).at(s0.E).dot() 181 | dsp.Arrow().right().label('y[n]', 'right').hold() 182 | dsp.Arrow().down() 183 | dsp.Square().label('$z^{-1}$') 184 | dsp.Line().length(d.unit/2).dot() 185 | d.push() 186 | dsp.Line().left() 187 | a1 = dsp.Amp().label('$-a_1$', 'bottom') 188 | dsp.Arrow().at(a1.out).tox(s1.E) 189 | d.pop() 190 | 191 | dsp.Arrow().down(d.unit*.75) 192 | dsp.Square().label('$z^{-1}$') 193 | dsp.Line().length(d.unit*.75) 194 | dsp.Line().left() 195 | a2 = dsp.Amp().label('$-a_2$', 'bottom') 196 | dsp.Arrow().at(a2.out).tox(s2.E) 197 | -------------------------------------------------------------------------------- /docs/gallery/solidstate.rst: -------------------------------------------------------------------------------- 1 | Solid State 2 | ----------- 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | schemdraw.use('svg') 10 | 11 | 12 | S-R Latch (Transistors) 13 | ^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | .. jupyter-execute:: 16 | :code-below: 17 | 18 | with schemdraw.Drawing() as d: 19 | Q1 = elm.BjtNpn(circle=True).reverse().label('Q1', 'left') 20 | Q2 = elm.BjtNpn(circle=True).at((d.unit*2, 0)).label('Q2') 21 | elm.Line().up(d.unit/2).at(Q1.collector) 22 | 23 | R1 = elm.Resistor().up().label('R1').hold() 24 | elm.Dot().label('V1', 'left') 25 | elm.Resistor().right(d.unit*.75).label('R3', 'bottom').dot() 26 | elm.Line().up(d.unit/8).dot(open=True).label('Set', 'right').hold() 27 | elm.Line().to(Q2.base) 28 | 29 | elm.Line().up(d.unit/2).at(Q2.collector) 30 | elm.Dot().label('V2', 'right') 31 | R2 = elm.Resistor().up().label('R2', 'bottom').hold() 32 | elm.Resistor().left(d.unit*.75).label('R4', 'bottom').dot() 33 | elm.Line().up(d.unit/8).dot(open=True).label('Reset', 'right').hold() 34 | elm.Line().to(Q1.base) 35 | 36 | elm.Line().down(d.unit/4).at(Q1.emitter) 37 | BOT = elm.Line().tox(Q2.emitter) 38 | elm.Line().to(Q2.emitter) 39 | elm.Dot().at(BOT.center) 40 | elm.Ground().at(BOT.center) 41 | 42 | TOP = elm.Line().endpoints(R1.end, R2.end) 43 | elm.Dot().at(TOP.center) 44 | elm.Vdd().at(TOP.center).label('+Vcc') 45 | 46 | 47 | 741 Opamp Internal Schematic 48 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 49 | 50 | .. jupyter-execute:: 51 | :code-below: 52 | 53 | with schemdraw.Drawing() as d: 54 | d.config(fontsize=12, unit=2.5) 55 | Q1 = elm.BjtNpn().label('Q1').label('+IN', 'left') 56 | Q3 = elm.BjtPnp().left().at(Q1.emitter).anchor('emitter').flip().label('Q3', 'left') 57 | elm.Line().down().at(Q3.collector).dot() 58 | d.push() 59 | elm.Line().right(d.unit/4) 60 | Q7 = elm.BjtNpn().anchor('base').label('Q7') 61 | d.pop() 62 | elm.Line().down(d.unit*1.25) 63 | Q5 = elm.BjtNpn().left().flip().anchor('collector').label('Q5', 'left') 64 | elm.Line().left(d.unit/2).at(Q5.emitter).label('OFST\nNULL', 'left').flip() 65 | elm.Resistor().down().at(Q5.emitter).label('R1\n1K') 66 | elm.Line().right(d.unit*.75).dot() 67 | R3 = elm.Resistor().up().label('R3\n50K') 68 | elm.Line().toy(Q5.base).dot() 69 | d.push() 70 | elm.Line().left().to(Q5.base) 71 | elm.Line().at(Q7.emitter).toy(Q5.base).dot() 72 | d.pop() 73 | elm.Line().right(d.unit/4) 74 | Q6 = elm.BjtNpn().anchor('base').label('Q6') 75 | elm.Line().at(Q6.emitter).length(d.unit/3).label('\nOFST\nNULL', 'right').hold() 76 | elm.Resistor().down().at(Q6.emitter).label('R2\n1K').dot() 77 | 78 | elm.Line().at(Q6.collector).toy(Q3.collector) 79 | Q4 = elm.BjtPnp().right().anchor('collector').label('Q4') 80 | elm.Line().at(Q4.base).tox(Q3.base) 81 | elm.Line().at(Q4.emitter).toy(Q1.emitter) 82 | Q2 = elm.BjtNpn().left().flip().anchor('emitter').label('Q2', 'left').label('$-$IN', 'right') 83 | elm.Line().up(d.unit/3).at(Q2.collector).dot() 84 | Q8 = elm.BjtPnp().left().flip().anchor('base').label('Q8', 'left') 85 | elm.Line().at(Q8.collector).toy(Q2.collector).dot() 86 | elm.Line().at(Q2.collector).tox(Q1.collector) 87 | elm.Line().up(d.unit/4).at(Q8.emitter) 88 | top = elm.Line().tox(Q7.collector) 89 | elm.Line().toy(Q7.collector) 90 | 91 | elm.Line().right(d.unit*2).at(top.start) 92 | elm.Line().down(d.unit/4) 93 | Q9 = elm.BjtPnp().right().anchor('emitter').label('Q9', ofst=-.1) 94 | elm.Line().at(Q9.base).tox(Q8.base) 95 | elm.Dot().at(Q4.base) 96 | elm.Line().down(d.unit/2).at(Q4.base) 97 | elm.Line().tox(Q9.collector).dot() 98 | elm.Line().at(Q9.collector).toy(Q6.collector) 99 | Q10 = elm.BjtNpn().left().flip().anchor('collector').label('Q10', 'left') 100 | elm.Resistor().at(Q10.emitter).toy(R3.start).label('R4\n5K').dot() 101 | 102 | Q11 = elm.BjtNpn().right().at(Q10.base).anchor('base').label('Q11') 103 | elm.Dot().at(Q11.base) 104 | elm.Line().up(d.unit/2) 105 | elm.Line().tox(Q11.collector).dot() 106 | elm.Line().at(Q11.emitter).toy(R3.start).dot() 107 | elm.Line().up(d.unit*2).at(Q11.collector) 108 | elm.Resistor().toy(Q9.collector).label('R5\n39K') 109 | Q12 = elm.BjtPnp().left().flip().anchor('collector').label('Q12', 'left', ofst=-.1) 110 | elm.Line().up(d.unit/4).at(Q12.emitter).dot() 111 | elm.Line().tox(Q9.emitter).dot() 112 | elm.Line().right(d.unit/4).at(Q12.base).dot() 113 | elm.Wire('|-').to(Q12.collector).dot().hold() 114 | elm.Line().right(d.unit*1.5) 115 | Q13 = elm.BjtPnp().anchor('base').label('Q13') 116 | elm.Line().up(d.unit/4).dot() 117 | elm.Line().tox(Q12.emitter) 118 | K = elm.Line().down(d.unit/5).at(Q13.collector).dot() 119 | elm.Line().down() 120 | Q16 = elm.BjtNpn().right().anchor('collector').label('Q16', ofst=-.1) 121 | elm.Line().left(d.unit/3).at(Q16.base).dot() 122 | R7 = elm.Resistor().up().toy(K.end).label('R7\n4.5K').dot() 123 | elm.Line().tox(Q13.collector).hold() 124 | R8 = elm.Resistor().down().at(R7.start).label('R8\n7.5K').dot() 125 | elm.Line().tox(Q16.emitter) 126 | J = elm.Dot() 127 | elm.Line().toy(Q16.emitter) 128 | Q15 = elm.BjtNpn().right().at(R8.end).anchor('collector').label('Q15') 129 | elm.Line().left(d.unit/2).at(Q15.base).dot() 130 | C1 = elm.Capacitor().toy(R7.end).label('C1\n30pF') 131 | elm.Line().tox(Q13.collector) 132 | elm.Line().at(C1.start).tox(Q6.collector).dot() 133 | elm.Line().down(d.unit/2).at(J.center) 134 | Q19 = elm.BjtNpn().right().anchor('collector').label('Q19') 135 | elm.Line().at(Q19.base).tox(Q15.emitter).dot() 136 | elm.Line().toy(Q15.emitter).hold() 137 | elm.Line().down(d.unit/4).at(Q19.emitter).dot() 138 | elm.Line().left() 139 | Q22 = elm.BjtNpn().left().anchor('base').flip().label('Q22', 'left') 140 | elm.Line().at(Q22.collector).toy(Q15.base).dot() 141 | elm.Line().at(Q22.emitter).toy(R3.start).dot() 142 | elm.Line().tox(R3.start).hold() 143 | elm.Line().tox(Q15.emitter).dot() 144 | d.push() 145 | elm.Resistor().up().label('R12\n50K') 146 | elm.Line().toy(Q19.base) 147 | d.pop() 148 | elm.Line().tox(Q19.emitter).dot() 149 | R11 = elm.Resistor().up().label('R11\n50') 150 | elm.Line().toy(Q19.emitter) 151 | 152 | elm.Line().up(d.unit/4).at(Q13.emitter) 153 | elm.Line().right(d.unit*1.5).dot() 154 | elm.Line().length(d.unit/4).label('V+', 'right').hold() 155 | elm.Line().down(d.unit*.75) 156 | Q14 = elm.BjtNpn().right().anchor('collector').label('Q14') 157 | elm.Line().left(d.unit/2).at(Q14.base) 158 | d.push() 159 | elm.Line().down(d.unit/2).idot() 160 | Q17 = elm.BjtNpn().left().anchor('collector').flip().label('Q17', 'left', ofst=-.1) 161 | elm.Line().at(Q17.base).tox(Q14.emitter).dot() 162 | J = elm.Line().toy(Q14.emitter) 163 | d.pop() 164 | elm.Line().tox(Q13.collector).dot() 165 | elm.Resistor().down().at(J.start).label('R9\n25').dot() 166 | elm.Wire('-|').to(Q17.emitter).hold() 167 | elm.Line().down(d.unit/4).dot() 168 | elm.Line().right(d.unit/4).label('OUT', 'right').hold() 169 | elm.Resistor().down().label('R10\n50') 170 | Q20 = elm.BjtPnp().right().anchor('emitter').label('Q20') 171 | elm.Wire('c', k=-1).at(Q20.base).to(Q15.collector) 172 | elm.Line().at(Q20.collector).toy(R3.start).dot() 173 | elm.Line().right(d.unit/4).label('V-', 'right').hold() 174 | elm.Line().tox(R11.start) 175 | -------------------------------------------------------------------------------- /docs/gallery/styles.rst: -------------------------------------------------------------------------------- 1 | Styles 2 | ------ 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | schemdraw.use('svg') 10 | 11 | 12 | Circuit elements can be styled using Matplotlib colors, line-styles, and line widths. 13 | 14 | Resistor circle 15 | ^^^^^^^^^^^^^^^ 16 | 17 | Uses named colors in a loop. 18 | 19 | .. jupyter-execute:: 20 | :code-below: 21 | 22 | with schemdraw.Drawing() as d: 23 | for i, color in enumerate(['red', 'orange', 'yellow', 'yellowgreen', 'green', 'blue', 'indigo', 'violet']): 24 | elm.Resistor().theta(45*i+20).color(color).label('R{}'.format(i)) 25 | 26 | 27 | Hand-drawn 28 | ^^^^^^^^^^ 29 | 30 | And for a change of pace, activate Matplotlib's XKCD mode for "hand-drawn" look! 31 | 32 | .. jupyter-execute:: 33 | :code-below: 34 | 35 | import matplotlib.pyplot as plt 36 | plt.xkcd() 37 | schemdraw.use('matplotlib') 38 | 39 | with schemdraw.Drawing() as d: 40 | op = elm.Opamp(leads=True) 41 | elm.Line().down().at(op.in2).length(d.unit/4) 42 | elm.Ground(lead=False) 43 | Rin = elm.Resistor().at(op.in1).left().idot().label('$R_{in}$', loc='bot').label('$v_{in}$', loc='left') 44 | elm.Line().up().at(op.in1).length(d.unit/2) 45 | elm.Resistor().tox(op.out).label('$R_f$') 46 | elm.Line().toy(op.out).dot() 47 | elm.Line().right().at(op.out).length(d.unit/4).label('$v_{o}$', loc='right') -------------------------------------------------------------------------------- /docs/gallery/timing.rst: -------------------------------------------------------------------------------- 1 | .. _gallerytiming: 2 | 3 | Timing Diagrams 4 | --------------- 5 | 6 | .. jupyter-execute:: 7 | :hide-code: 8 | 9 | import schemdraw 10 | from schemdraw import elements as elm 11 | from schemdraw import logic 12 | schemdraw.use('svg') 13 | 14 | 15 | Timing diagrams, based on `WaveDrom `_, are drawn using the :py:class:`schemdraw.logic.timing.TimingDiagram` class. 16 | 17 | .. code-block:: python 18 | 19 | from schemdraw import logic 20 | 21 | 22 | 23 | SRAM read/write cycle 24 | ^^^^^^^^^^^^^^^^^^^^^ 25 | 26 | The SRAM examples make use of Schemdraw's extended 'edge' notation for labeling 27 | timings just above and below the wave. 28 | 29 | .. jupyter-execute:: 30 | :code-below: 31 | 32 | logic.TimingDiagram( 33 | {'signal': [ 34 | {'name': 'Address', 'wave': 'x4......x.', 'data': ['Valid address']}, 35 | {'name': 'Chip Select', 'wave': '1.0.....1.'}, 36 | {'name': 'Out Enable', 'wave': '1.0.....1.'}, 37 | {'name': 'Data Out', 'wave': 'z...x6...z', 'data': ['Valid data']}, 38 | ], 39 | 'edge': ['[0^:1.2]+[0^:8] $t_{WC}$', 40 | '[0v:1]+[0v:5] $t_{AQ}$', 41 | '[1:2]+[1:5] $t_{EQ}$', 42 | '[2:2]+[2:5] $t_{GQ}$', 43 | '[0^:5]-[3v:5]{lightgray,:}', 44 | ] 45 | }, ygap=.5, grid=False) 46 | 47 | 48 | .. jupyter-execute:: 49 | :code-below: 50 | 51 | logic.TimingDiagram( 52 | {'signal': [ 53 | {'name': 'Address', 'wave': 'x4......x.', 'data': ['Valid address']}, 54 | {'name': 'Chip Select', 'wave': '1.0......1'}, 55 | {'name': 'Write Enable', 'wave': '1..0...1..'}, 56 | {'name': 'Data In', 'wave': 'x...5....x', 'data': ['Valid data']}, 57 | ], 58 | 'edge': ['[0^:1]+[0^:8] $t_{WC}$', 59 | '[2:1]+[2:3] $t_{SA}$', 60 | '[3^:4]+[3^:7] $t_{WD}$', 61 | '[3^:7]+[3^:9] $t_{HD}$', 62 | '[0^:1]-[2:1]{lightgray,:}'], 63 | }, ygap=.4, grid=False) 64 | 65 | 66 | 67 | J-K Flip Flop 68 | ^^^^^^^^^^^^^ 69 | 70 | Timing diagram for a J-K flip flop taken from `here `_. 71 | Notice the use of the `async` dictionary parameter on the J and K signals, and the color parameters for the output signals. 72 | 73 | .. jupyter-execute:: 74 | :code-below: 75 | 76 | logic.TimingDiagram( 77 | {'signal': [ 78 | {'name': 'clk', 'wave': 'P......'}, 79 | {'name': 'J', 'wave': '0101', 'async': [0, .8, 1.3, 3.7, 7]}, 80 | {'name': 'K', 'wave': '010101', 'async': [0, 1.2, 2.3, 2.8, 3.2, 3.7, 7]}, 81 | {'name': 'Q', 'wave': '010.101', 'color': 'red', 'lw': 1.5}, 82 | {'name': r'$\overline{Q}$', 'wave': '101.010', 'color': 'blue', 'lw': 1.5}], 83 | 'config': {'hscale': 1.5}}, risetime=.05) 84 | 85 | 86 | Tutorial Examples 87 | ^^^^^^^^^^^^^^^^^ 88 | 89 | These examples were copied from `WaveDrom Tutorial `_. 90 | They use the `from_json` class method so the examples can be pasted directly as a string. Otherwise, the setup must be converted to a proper Python dictionary. 91 | 92 | .. jupyter-execute:: 93 | :code-below: 94 | 95 | logic.TimingDiagram.from_json('''{ signal: [{ name: "Alfa", wave: "01.zx=ud.23.456789" }] }''') 96 | 97 | 98 | .. jupyter-execute:: 99 | :code-below: 100 | 101 | logic.TimingDiagram.from_json('''{ signal: [ 102 | { name: "clk", wave: "p.....|..." }, 103 | { name: "Data", wave: "x.345x|=.x", data: ["head", "body", "tail", "data"] }, 104 | { name: "Request", wave: "0.1..0|1.0" }, 105 | {}, 106 | { name: "Acknowledge", wave: "1.....|01." } 107 | ]}''') 108 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Schemdraw documentation 2 | ======================= 3 | 4 | .. jupyter-execute:: 5 | :hide-code: 6 | 7 | import schemdraw 8 | from schemdraw import elements as elm 9 | 10 | Schemdraw is a Python package for producing high-quality electrical circuit schematic diagrams. 11 | Circuit elements are added, one at a time, similar to how you might draw them by hand, using Python methods. 12 | 13 | For example, 14 | 15 | .. code-block:: python 16 | 17 | with schemdraw.Drawing(): 18 | elm.Resistor().right().label('1Ω') 19 | 20 | creates a new schemdraw drawing with a resistor going to the right with a label of "1Ω". 21 | The next element added to the drawing will start at the endpoint of the resistor. 22 | 23 | .. jupyter-execute:: 24 | 25 | with schemdraw.Drawing(): 26 | elm.Resistor().right().label('1Ω') 27 | elm.Capacitor().down().label('10μF') 28 | elm.Line().left() 29 | elm.SourceSin().up().label('10V') 30 | 31 | 32 | 33 | | 34 | 35 | .. toctree:: 36 | :maxdepth: 1 37 | :caption: Contents: 38 | 39 | usage/start 40 | usage/index 41 | elements/elements 42 | gallery/index 43 | usage/customizing 44 | classes/index 45 | changes 46 | contributing 47 | 48 | 49 | ---------- 50 | 51 | Want to support Schemdraw development? Need more circuit examples? Pick up the Schemdraw Examples Pack on buymeacoffee.com: 52 | 53 | .. raw:: html 54 | 55 | Buy Me A Coffee 56 | -------------------------------------------------------------------------------- /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/requirements.txt: -------------------------------------------------------------------------------- 1 | pyparsing 2 | matplotlib 3 | ipykernel 4 | jupyter_sphinx 5 | cairosvg 6 | sphinx-design 7 | sphinxcontrib-svg2pdfconverter[cairosvg] -------------------------------------------------------------------------------- /docs/usage/backends.rst: -------------------------------------------------------------------------------- 1 | .. _backends: 2 | 3 | Backends 4 | -------- 5 | 6 | The backend is the "canvas" on which a schematic is drawn. Schemdraw supports two backends: Matplotlib, and SVG. 7 | 8 | 9 | Matplotlib Backend 10 | ****************** 11 | 12 | By default, all schematics are drawn on a Matplotlib axis if Matplotlib is installed and importable. 13 | A new Matplotlib Figure and Axis will be created, with no frame or borders. 14 | A schematic may be added to an existing Axis by using the :py:meth:`schemdraw.Drawing.draw` method and setting 15 | the `canvas` parameter to an existing Axis instance. 16 | 17 | The Matplotlib backend renders text labels as primative lines and arcs rather than text elements by default. 18 | This has the downside that SVG editors, such as Inkscape, cannot perform textual searches on the SVGs. 19 | The upside is that there is no dependence on installed fonts on the hosts that open the SVGs. 20 | 21 | To configure Matplotlib to render labels as SVG text elements: 22 | 23 | .. code-block:: python 24 | 25 | import matplotlib 26 | matplotlib.rcParams['svg.fonttype'] = 'none' 27 | 28 | 29 | SVG Backend 30 | *********** 31 | 32 | Schematics can also be drawn on directly to an SVG image backend. 33 | The SVG backend can be enabled for all drawings by calling: 34 | 35 | .. code-block:: python 36 | 37 | schemdraw.use('svg') 38 | 39 | The backend can be changed at any time. Alternatively, the backend can be set individually on each Drawing using the `canvas` parameter: 40 | 41 | .. code-block:: 42 | 43 | with schemdraw.Drawing(canvas='svg') as d: 44 | ... 45 | 46 | Use additional Python libraries, such as `pycairo `_, to convert the SVG output into other image formats. 47 | 48 | Math Text 49 | ^^^^^^^^^ 50 | 51 | The SVG backend has basic math text support, including greek symbols, subscripts, and superscripts. 52 | However, if `ziamath `_ and `latex2mathml `_ packages are installed, they will be used for full Latex math support. 53 | 54 | The SVG backend can produce searchable-text SVGs by setting: 55 | 56 | .. code-block:: python 57 | 58 | schemdraw.svgconfig.text = 'text' 59 | 60 | However, text mode does not support full Latex compatibility. 61 | To switch back to rendering text as SVG paths: 62 | 63 | .. code-block:: python 64 | 65 | schemdraw.svgconfig.text = 'path' 66 | 67 | Some SVG renderers are not fully compatible with SVG2.0. For better compatibility with SVG1.x, use 68 | 69 | .. code-block:: python 70 | 71 | schemdraw.svgconfig.svg2 = False 72 | 73 | The decimal precision of SVG elements can be set using 74 | 75 | .. code-block:: python 76 | 77 | schemdraw.svgconfig.precision = 2 78 | 79 | 80 | 81 | Backend Comparison 82 | ****************** 83 | 84 | Reasons to choose the SVG backend include: 85 | 86 | - No Matplotlib/Numpy dependency required (huge file size savings if bundling an executable). 87 | - Speed. The SVG backend draws 4-10x faster than Matplotlib, depending on the circuit complexity. 88 | 89 | Reasons to use Matplotlib backend: 90 | 91 | - To customize the schematic after drawing it by using other Matplotlib functionality. 92 | - To render directly in other, non-SVG, image formats, with no additional code. 93 | -------------------------------------------------------------------------------- /docs/usage/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _usage: 3 | 4 | Usage 5 | ===== 6 | 7 | .. toctree:: 8 | 9 | placement 10 | labels 11 | styles 12 | backends -------------------------------------------------------------------------------- /docs/usage/labels.rst: -------------------------------------------------------------------------------- 1 | .. jupyter-execute:: 2 | :hide-code: 3 | 4 | import schemdraw 5 | from schemdraw import elements as elm 6 | 7 | 8 | .. _labels: 9 | 10 | Labels 11 | ------ 12 | 13 | Labels are added to elements using the :py:meth:`schemdraw.elements.Element.label` method. 14 | Some unicode utf-8 characters are allowed, such as :code:`'1μF'` and :code:`'1MΩ'` if the character is included in your font set. 15 | Alternatively, full LaTeX math expressions can be rendered when enclosed in `$..$` 16 | For a description of supported math expressions, in the Matplotlib backend see `Matplotlib Mathtext `_, and the SVG backend refer to the `Ziamath `_ package. 17 | Subscripts and superscripts are also added using LaTeX math mode, enclosed in `$..$`: 18 | 19 | .. jupyter-execute:: 20 | 21 | with schemdraw.Drawing(): 22 | elm.Resistor().label('1MΩ') 23 | elm.Capacitor().label('1μF') 24 | elm.Capacitor().label(r'$v = \frac{1}{C} \int i dt$') 25 | elm.Resistor().at((0, -2)).label('$R_0$') 26 | elm.Capacitor().label('$x^2$') 27 | 28 | Location 29 | ******** 30 | 31 | The label location is specified with the `loc` parameter to the `label` method. 32 | It can be `left`, `right`, `top`, `bottom`, or the name of a defined anchor within the element. 33 | These directions do not depend on rotation. A label with `loc='left'` is always on the leftmost terminal of the element. 34 | 35 | .. jupyter-execute:: 36 | 37 | with schemdraw.Drawing(): 38 | (elm.Resistor() 39 | .label('Label') # 'top' is default 40 | .label('Bottom', loc='bottom') 41 | .label('Right', loc='right') 42 | .label('Left', loc='left')) 43 | 44 | Labels may also be placed near an element anchor by giving the anchor name as the `loc` parameter. 45 | 46 | .. jupyter-execute:: 47 | 48 | with schemdraw.Drawing(): 49 | (elm.BjtNpn() 50 | .label('b', loc='base') 51 | .label('c', loc='collector') 52 | .label('e', loc='emitter')) 53 | 54 | The :py:meth:`schemdraw.elements.Element.label` method also takes parameters that control the label's rotation, offset, font, alignment, and color. 55 | Label text stays horizontal by default, but may be rotated to the same angle as the element using `rotate=True`, or any angle `X` in degrees with `rotate=X`. 56 | Offsets apply vertically if a float value is given, or in both x and y if a tuple is given. 57 | 58 | .. jupyter-execute:: 59 | 60 | with schemdraw.Drawing(): 61 | elm.Resistor().label('no offset') 62 | elm.Resistor().label('offset', ofst=1) 63 | elm.Resistor().label('offset (x, y)', ofst=(-.6, .2)) 64 | elm.Resistor().theta(-45).label('no rotate') 65 | elm.Resistor().theta(-45).label('rotate', rotate=True) 66 | elm.Resistor().theta(45).label('90°', rotate=90) 67 | 68 | 69 | Labels may also be added anywhere using the :py:class:`schemdraw.elements.lines.Label` element. The element itself draws nothing, but labels can be added to it: 70 | 71 | .. code-block:: python 72 | 73 | elm.Label().label('Hello') 74 | 75 | 76 | Voltage Labels 77 | ************** 78 | 79 | A label may also be a list/tuple of strings, which will be evenly-spaced along the length of the element. 80 | This allows for labeling positive and negative along with a component name, for example: 81 | 82 | .. jupyter-execute:: 83 | 84 | elm.Resistor().label(('–','$V_1$','+')) # Note: using endash U+2013 character 85 | 86 | 87 | Use the `Gap` element to label voltage across a terminal: 88 | 89 | .. jupyter-execute:: 90 | 91 | with schemdraw.Drawing(): 92 | elm.Line().dot(open=True) 93 | elm.Gap().label(('–','$V_o$','+')) 94 | elm.Line().idot(open=True) 95 | 96 | 97 | Current Arrow Labels 98 | ******************** 99 | 100 | Current Arrow 101 | ^^^^^^^^^^^^^ 102 | 103 | To label the current through an element, the :py:class:`schemdraw.elements.lines.CurrentLabel` element can be added. 104 | The `at` method of this element can take an Element instance to label, and the 105 | arrow will be placed over the center of that Element. 106 | 107 | .. jupyter-execute:: 108 | 109 | with schemdraw.Drawing(): 110 | R1 = elm.Resistor() 111 | elm.CurrentLabel().at(R1).label('10 mA') 112 | 113 | For transistors, the label will follow sensible bias currents by default. 114 | 115 | .. jupyter-execute:: 116 | 117 | with schemdraw.Drawing(): 118 | Q1 = elm.AnalogNFet() 119 | elm.CurrentLabel().at(Q1).label('10 µA') 120 | 121 | Q2 = elm.AnalogNFet().at([4,0]).flip().reverse() 122 | elm.CurrentLabel().at(Q2).label('10 µA') 123 | 124 | 125 | Inline Current Arrow 126 | ^^^^^^^^^^^^^^^^^^^^ 127 | 128 | Alternatively, current labels can be drawn inline as arrowheads on the leads of 2-terminal elements using :py:class:`schemdraw.elements.lines.CurrentLabelInline`. Parameters `direction` and `start` control whether the arrow 129 | is shown pointing into or out of the element, and which end to place the arrowhead on. 130 | 131 | .. jupyter-execute:: 132 | 133 | with schemdraw.Drawing(): 134 | R1 = elm.Resistor() 135 | elm.CurrentLabelInline(direction='in').at(R1).label('10 mA') 136 | 137 | 138 | Loop Current 139 | ^^^^^^^^^^^^ 140 | 141 | Loop currents can be added using :py:class:`schemdraw.elements.lines.LoopCurrent`, given a list of 4 existing elements surrounding the loop. 142 | 143 | .. jupyter-execute:: 144 | 145 | with schemdraw.Drawing(): 146 | R1 = elm.Resistor() 147 | C1 = elm.Capacitor().down() 148 | D1 = elm.Diode().fill(True).left() 149 | L1 = elm.Inductor().up() 150 | elm.LoopCurrent([R1, C1, D1, L1], direction='cw').label('$I_1$') 151 | 152 | Alternatively, loop current arrows can be added anywhere with any size using :py:class:`schemdraw.elements.lines.LoopArrow`. 153 | 154 | .. jupyter-execute:: 155 | 156 | with schemdraw.Drawing(): 157 | a = elm.Line().dot() 158 | elm.LoopArrow(width=.75, height=.75).at(a.end) 159 | 160 | 161 | Impedance Arrow Label 162 | ^^^^^^^^^^^^^^^^^^^^^ 163 | 164 | A right-angle arrow label, often used to indicate impedance looking into a node, is added using :py:class:`schemdraw.elements.lines.ZLabel`. 165 | 166 | .. jupyter-execute:: 167 | 168 | with schemdraw.Drawing(): 169 | R = elm.RBox().right() 170 | elm.ZLabel().at(R).label('$Z_{in}$') 171 | 172 | 173 | Annotations 174 | *********** 175 | 176 | To make text and arrow annotations to a schematic, the :py:class:`schemdraw.elements.lines.Annotate` element draws a curvy arrow with label placed at it's end. It is based on the :py:class:`schemdraw.elements.lines.Arc3` element. 177 | 178 | The :py:class:`schemdraw.elements.lines.Encircle` and :py:class:`schemdraw.elements.lines.EncircleBox` elements draw an ellipse, or rounded rectangle, surrounding a list of elements. 179 | 180 | .. jupyter-input:: 181 | 182 | parallel = elm.Encircle([R1, R2], padx=.8).linestyle('--').linewidth(1).color('red') 183 | series = elm.Encircle([R3, R4], padx=.8).linestyle('--').linewidth(1).color('blue') 184 | 185 | elm.Annotate().at(parallel.NNE).delta(dx=1, dy=1).label('Parallel').color('red') 186 | elm.Annotate(th1=0).at(series.ENE).delta(dx=1.5, dy=1).label('Series').color('blue') 187 | 188 | 189 | .. jupyter-execute:: 190 | :hide-code: 191 | 192 | with schemdraw.Drawing(unit=2): 193 | R1 = elm.Resistor().down().label('R1') 194 | c = elm.Line().right().length(1) 195 | R2 = elm.Resistor().up().label('R2', loc='bottom') 196 | elm.Line().left().length(1) 197 | elm.Line().down().at(c.center).length(.75).idot() 198 | R3 = elm.Resistor().down().label('R3') 199 | R4 = elm.Resistor().down().label('R4') 200 | parallel = elm.Encircle([R1, R2], padx=.8).linestyle('--').linewidth(1).color('red') 201 | series = elm.Encircle([R3, R4], padx=.8).linestyle('--').linewidth(1).color('blue') 202 | 203 | elm.Annotate().at(parallel.NNE).delta(dx=1, dy=1).label('Parallel').color('red') 204 | elm.Annotate(th1=0).at(series.ENE).delta(dx=1.5, dy=1).label('Series').color('blue') 205 | 206 | -------------------------------------------------------------------------------- /docs/usage/start.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Installation 5 | ------------ 6 | 7 | schemdraw can be installed from pip using 8 | 9 | .. code-block:: bash 10 | 11 | pip install schemdraw 12 | 13 | or to include optional ``matplotlib`` backend dependencies: 14 | 15 | .. code-block:: bash 16 | 17 | pip install schemdraw[matplotlib] 18 | 19 | To allow the SVG drawing :ref:`backends` to render math expressions, 20 | install the optional `ziamath `_ dependency with: 21 | 22 | .. code-block:: bash 23 | 24 | pip install schemdraw[svgmath] 25 | 26 | 27 | Alternatively, schemdraw can be installed directly by downloading the source and running 28 | 29 | .. code-block:: bash 30 | 31 | pip install ./ 32 | 33 | Schemdraw requires Python 3.9 or higher. 34 | 35 | 36 | Overview 37 | --------- 38 | 39 | The :py:mod:`schemdraw` module allows for drawing circuit elements. 40 | :py:mod:`schemdraw.elements` contains :ref:`electrical` pre-defined for 41 | use in a drawing. A common import structure is: 42 | 43 | .. jupyter-execute:: 44 | 45 | import schemdraw 46 | import schemdraw.elements as elm 47 | 48 | 49 | .. jupyter-execute:: 50 | :hide-code: 51 | 52 | schemdraw.use('svg') 53 | 54 | 55 | To make a circuit diagram, use a context manager (`with` statement) on a :py:class:`schemdraw.Drawing`. Then any :py:class:`schemdraw.elements.Element` instances created within the `with` block added to the drawing: 56 | 57 | .. jupyter-execute:: 58 | 59 | with schemdraw.Drawing(): 60 | elm.Resistor() 61 | elm.Capacitor() 62 | elm.Diode() 63 | 64 | **New in version 0.18**: The context manager keeps track of the active drawing, so that using `drawing.add(element)` or `drawing += element` is no longer necessary. 65 | These operators are still functional and are needed if drawing outside a `with` context manager: 66 | 67 | .. code-block:: python 68 | 69 | with schemdraw.Drawing() as drawing: 70 | drawing += elm.Resistor() 71 | drawing += elm.Capacitor() 72 | drawing.add(elm.Diode()) # Same as `drawing +=` 73 | 74 | Element placement and other properties are set using a chained method interface, for example: 75 | 76 | .. jupyter-execute:: 77 | 78 | with schemdraw.Drawing(): 79 | elm.Resistor().label('100KΩ') 80 | elm.Capacitor().down().label('0.1μF', loc='bottom') 81 | elm.Line().left() 82 | elm.Ground() 83 | elm.SourceV().up().label('10V') 84 | 85 | Methods `up`, `down`, `left`, `right` specify the drawing direction, and `label` adds text to the element. 86 | If not specified, elements reuse the same direction from the previous element, and begin where 87 | the previous element ended. 88 | 89 | Using the `with` context manager is a convenience, letting the drawing be displayed and saved upon exiting the `with` block. Schematics may also be created simply by assinging a new Drawing instance, but this requires explicitly adding elements to the drawing with `d.add` or d +=`, and calling `draw()` and/or `save()` to show the drawing: 90 | 91 | .. code-block:: python 92 | 93 | d = schemdraw.Drawing() 94 | d += elm.Resistor() 95 | ... 96 | d.draw() 97 | d.save('my_circuit.svg') 98 | 99 | 100 | For full details of placing and stylizing elements, see :ref:`placement`. 101 | and :py:class:`schemdraw.elements.Element`. 102 | 103 | In general, parameters that control **what** is drawn are passed to the element itself, and parameters that control **how** things are drawn are set using chained Element methods. For example, to make a polarized Capacitor, pass `polar=True` as an argument to `Capacitor`, but to change the Capacitor's color, use the `.color()` method: `elm.Capacitor(polar=True).color('red')`. 104 | 105 | 106 | Viewing the Drawing 107 | ------------------- 108 | 109 | Jupyter 110 | ******* 111 | 112 | When run in a Jupyter notebook, the schematic will be drawn to the cell output after the `with` block is exited. 113 | If your schematics pop up in an external window, and you are using the Matplotlib backend, set Matplotlib to inline mode before importing schemdraw: 114 | 115 | .. code-block:: python 116 | 117 | %matplotlib inline 118 | 119 | For best results when viewing circuits in the notebook, use a vector figure format, such as svg before importing schemdraw: 120 | 121 | .. code-block:: python 122 | 123 | %config InlineBackend.figure_format = 'svg' 124 | 125 | 126 | Python Scripts and GUI/Web apps 127 | ******************************* 128 | 129 | If run as a Python script, the schematic will be opened in a pop-up window after the `with` block exits. 130 | Add the `show=False` option when creating the Drawing to suppress the window from appearing. 131 | 132 | .. code-block:: python 133 | 134 | with schemdraw.Drawing(show=False) as d: 135 | ... 136 | 137 | The raw image data as a bytes array can be obtained by calling `.get_imagedata()` with the after the `with` block exits. 138 | This can be useful for integrating schemdraw into an existing GUI or web application. 139 | 140 | .. code-block:: python 141 | 142 | with schemdraw.Drawing() as drawing: 143 | ... 144 | image_bytes = drawing.get_imagedata('svg') 145 | 146 | 147 | Headless Servers 148 | **************** 149 | 150 | When running on a server, sometimes there is no display available. 151 | The code may attempt to open the GUI preview window and fail. 152 | In these cases, try setting the Matplotlib backend to a non-GUI option. 153 | Before importing schemdraw, add these lines to use the Agg backend which does not have a GUI. 154 | Then get the drawing using `d.get_imagedata()`, or `d.save()` to get the image. 155 | 156 | .. code-block:: python 157 | 158 | import matplotlib 159 | matplotlib.use('Agg') # Set Matplotlib's backend here 160 | 161 | Alternatively, use Schemdraw's SVG backend (see :ref:`backends`). 162 | 163 | 164 | Saving Drawings 165 | --------------- 166 | 167 | To save the schematic to a file, add the `file` parameter when setting up the Drawing. 168 | The image type is determined from the file extension. 169 | Options include `svg`, `eps`, `png`, `pdf`, and `jpg` when using the Matplotlib backend, and `svg` when using the SVG backend. 170 | A vector format such as `svg` is recommended for best image quality. 171 | 172 | .. code-block:: python 173 | 174 | with schemdraw.Drawing(file='my_circuit.svg') as d: 175 | ... 176 | 177 | The Drawing may also be saved using with the :py:meth:`schemdraw.Drawing.save` method. 178 | 179 | -------------------------------------------------------------------------------- /docs/usage/styles.rst: -------------------------------------------------------------------------------- 1 | .. jupyter-execute:: 2 | :hide-code: 3 | 4 | import schemdraw 5 | from schemdraw import elements as elm 6 | 7 | 8 | .. _styles: 9 | 10 | 11 | Styling 12 | ------- 13 | 14 | Style options, such as color, line thickness, and fonts, may be set at the global level (all Schemdraw Drawings), at the Drawing level, or on individual Elements. 15 | 16 | Individual Elements 17 | ******************* 18 | 19 | Element styling methods include `color`, `fill`, `linewidth`, and `linestyle`. 20 | If a style method is not called when creating an Element, its value is obtained from from the drawing or global defaults. 21 | 22 | Color and fill parameters accept any named `SVG color `_ or a hex color string such as '#6A5ACD'. Linestyle parameters may be '-', '--', ':', or '-.'. 23 | 24 | .. jupyter-execute:: 25 | 26 | # All elements are blue with lightgray fill unless specified otherwise 27 | with schemdraw.Drawing(color='blue', fill='lightgray'): 28 | elm.Diode() 29 | elm.Diode().fill('red') # Fill overrides drawing color here 30 | elm.Resistor().fill('purple') # Fill has no effect on non-closed elements 31 | elm.RBox().linestyle('--').color('orange') 32 | elm.Resistor().linewidth(5) 33 | 34 | The `label` method also accepts color, font, and fontsize parameters, allowing labels with different style as their elements. 35 | 36 | 37 | Drawing style 38 | ************* 39 | 40 | Styles may be applied to an entire drawing using the :py:meth:`schemdraw.Drawing.config` method. 41 | These parameters include color, linewidth, font, fontsize, linestyle, fill, and background color. 42 | Additionally, the `config` method allows specification of the default 2-Terminal element length. 43 | 44 | 45 | Global style 46 | ************ 47 | 48 | Styles may be applied to every new drawing created by Schemdraw (during the Python session) using :py:meth:`schemdraw.config`, using the same arguments as the Drawing config method. 49 | 50 | .. jupyter-execute:: 51 | :emphasize-lines: 1 52 | 53 | schemdraw.config(lw=1, font='serif') 54 | with schemdraw.Drawing(): 55 | elm.Resistor().label('100KΩ') 56 | elm.Capacitor().down().label('0.1μF', loc='bottom') 57 | elm.Line().left() 58 | elm.Ground() 59 | elm.SourceV().up().label('10V') 60 | 61 | .. jupyter-execute:: 62 | :hide-code: 63 | 64 | schemdraw.config() 65 | 66 | 67 | Global Element Configuration 68 | **************************** 69 | 70 | Each Element has a `defaults` dictionary attribute containing default parameters for drawing the element. 71 | 72 | For example, to fill all Diode elements: 73 | 74 | .. jupyter-execute:: 75 | :emphasize-lines: 3 76 | 77 | elm.Diode.defaults['fill'] = True 78 | with schemdraw.Drawing(): 79 | elm.Diode() 80 | elm.Diode() 81 | elm.DiodeTunnel() 82 | 83 | Notice that the defaults apply to Diode and all elements subclassed from Diode, such as DiodeTunnel. 84 | In general, the docstring of each Element lists arguments with their default values, these arguments 85 | may be specified in the `defaults` dictionary. 86 | 87 | Styling Hierarchy 88 | ^^^^^^^^^^^^^^^^^ 89 | 90 | Element styles are applied in order of preference: 91 | 92 | 1) Setter methods like `.fill()` or `.color()` called after the Element is instantiated 93 | 2) Keyword arguments provided to Element instantiation 94 | 3) Defaults set by user in Element.defaults (inheriting from parent classes) 95 | 4) Parameters overridden by the Element definition 96 | 5) Parameters set in Drawing.config 97 | 6) Parameters set by Schemdraw.config 98 | 99 | 100 | U.S. versus European Style 101 | ************************** 102 | 103 | The :py:meth:`schemdraw.elements.Element.style` method will to reconfigure elements in IEEE/U.S. style or IEC/European style. 104 | The `schemdraw.elements.STYLE_IEC` and `schemdraw.elements.STYLE_IEEE` are dictionaries for use in the `style` method to change configuration of various elements that use different standard symbols (resistor, variable resistor, photo resistor, etc.) 105 | 106 | To configure IEC/European style, use the `style` method with the `elm.STYLE_IEC` dictionary. 107 | 108 | .. jupyter-execute:: 109 | :emphasize-lines: 1 110 | 111 | elm.style(elm.STYLE_IEC) 112 | elm.Resistor() 113 | 114 | .. jupyter-execute:: 115 | :emphasize-lines: 1 116 | 117 | elm.style(elm.STYLE_IEEE) 118 | elm.Resistor() 119 | 120 | To see all the elements that change between IEEE and IEC, see :ref:`styledelements`. 121 | 122 | Fonts 123 | ***** 124 | 125 | The font for label text may be set using the `font` parameter, either in the :py:meth:`schemdraw.elements.Element.label` method for a single label, or in :py:meth:`schemdraw.Drawing.config` to set the font for the entire drawing. 126 | The font parameter may be a string containing the name of a font installed in the system fonts path, a path to a TTF font file, or the name of a font family such as "serif" or "sans". 127 | These font options apply whether working in the Matplotlib or SVG backends. 128 | 129 | .. code-block:: python 130 | 131 | with schemdraw.Drawing(): 132 | # Default font 133 | elm.RBox().label('R1\n500K') 134 | 135 | # Named font in system fonts path 136 | elm.RBox().label('R1\n500K', font='Comic Sans MS') 137 | 138 | # Path to a TTF file 139 | elm.RBox().label('R1\n500K', font='Peralta-Regular.ttf') 140 | 141 | # Font family 142 | elm.RBox().label('R1\n500K', font='serif') 143 | 144 | .. image:: fonts.svg 145 | :alt: Font examples 146 | 147 | 148 | For typesetting math expressions, the `mathfont` parameter is used. 149 | In the Matplotlib backend, a limited `selection of math fonts `_ are available. 150 | With the SVG backend in the `path` text mode, the mathfont parameter may be the path to any TTF file that contains a MATH table (requires `Ziamath `_). 151 | 152 | .. code-block:: python 153 | 154 | with schemdraw.Drawing(canvas='svg'): 155 | # Default math font 156 | elm.RBox().label(r'$\sqrt{a^2+b^2}$').at((0, -2)) 157 | 158 | # Path to a TTF file with MATH font table (SVG backend only) 159 | elm.RBox().label(r'$\sqrt{a^2+b^2}$', mathfont='Asana-Math.ttf') 160 | 161 | .. image:: mathfonts.svg 162 | :alt: Math font examples 163 | 164 | 165 | 166 | Themes 167 | ****** 168 | 169 | Schemdraw also supports themeing, to enable dark mode, for example. 170 | The defined themes match those in the `Jupyter Themes `_ package: 171 | 172 | * default (black on white) 173 | * dark (white on black) 174 | * solarizedd 175 | * solarizedl 176 | * onedork 177 | * oceans16 178 | * monokai 179 | * gruvboxl 180 | * gruvboxd 181 | * grade3 182 | * chesterish 183 | 184 | They are enabled using :py:meth:`schemdraw.theme`: 185 | 186 | .. jupyter-execute:: 187 | :emphasize-lines: 1 188 | 189 | schemdraw.theme('monokai') 190 | with schemdraw.Drawing(): 191 | elm.Resistor().label('100KΩ') 192 | elm.Capacitor().down().label('0.1μF', loc='bottom') 193 | elm.Line().left() 194 | elm.Ground() 195 | elm.SourceV().up().label('10V') 196 | 197 | .. jupyter-execute:: 198 | :hide-code: 199 | 200 | schemdraw.theme('default') 201 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /runtest.sh: -------------------------------------------------------------------------------- 1 | # This runs the test notebooks in test folder and 2 | # verifies they complete without exceptions. 3 | # Also collects code coverage. 4 | # Notebooks should still be verified manually to 5 | # enusre things are drawn correctly. 6 | py.test --nbval-lax --cov=schemdraw --cov-report=html test -------------------------------------------------------------------------------- /schemdraw/__init__.py: -------------------------------------------------------------------------------- 1 | from .schemdraw import Drawing, use, config, theme, debug 2 | from .segments import Segment, SegmentCircle, SegmentArc, SegmentText, SegmentPoly, SegmentBezier, SegmentPath 3 | from .transform import Transform 4 | from .types import ImageFormat 5 | from .backends.svg import config as svgconfig 6 | from .backends.svg import settextmode 7 | 8 | __all__ = [ 9 | "Drawing", "use", "config", "theme", "debug", "Segment", "SegmentCircle", "SegmentArc", "SegmentText", 10 | "SegmentPath", 11 | "SegmentPoly", "SegmentBezier", "Transform", "ImageFormat", "settextmode", "svgconfig" 12 | ] 13 | 14 | __version__ = '0.20' 15 | -------------------------------------------------------------------------------- /schemdraw/backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdelker/schemdraw/41589490f0de3ae89aed1718242d985d57eeab76/schemdraw/backends/__init__.py -------------------------------------------------------------------------------- /schemdraw/backends/matrix.py: -------------------------------------------------------------------------------- 1 | ''' Affine transformation matrix operations for SVG `transform` attributes ''' 2 | from __future__ import annotations 3 | from typing import Sequence, Optional, Tuple 4 | import math 5 | 6 | from ..util import Point 7 | 8 | 9 | Matrix3x3 = Tuple[Tuple[float, float, float], Tuple[float, float, float], Tuple[float, float, float]] 10 | 11 | 12 | def matrix_translate(dx: float = 0, dy: float = 0) -> Matrix3x3: 13 | ''' Translation matrix ''' 14 | return ((1., 0., dx), 15 | (0., 1., dy), 16 | (0., 0., 1.)) 17 | 18 | 19 | def matrix_rotate(theta: float = 0, cx: float = 0, cy: float = 0) -> Matrix3x3: 20 | ''' Rotation matrix, theta in degrees, about center (cx, cy) ''' 21 | costh = math.cos(math.radians(theta)) 22 | sinth = math.sin(math.radians(theta)) 23 | return ((costh, -sinth, -cx*costh + cy*sinth + cx), 24 | (sinth, costh, -cx*sinth - cy*costh + cy), 25 | (0., 0., 1.)) 26 | 27 | 28 | def matrix_scale(kx: float = 1., ky: Optional[float] = None) -> Matrix3x3: 29 | ''' Scale matrix ''' 30 | if ky is None: 31 | ky = kx 32 | return ((kx, 0., 0.), 33 | (0., ky, 0.), 34 | (0., 0., 1.)) 35 | 36 | 37 | def matrix_skewx(k: float = 1.) -> Matrix3x3: 38 | ''' X-Skew matrix ''' 39 | return ((1., k, 0.), 40 | (0., 1., 0.), 41 | (0., 0., 1.)) 42 | 43 | 44 | def matrix_skewy(k: float = 1.) -> Matrix3x3: 45 | ''' Y-Skew matrix ''' 46 | return ((1., 0., 0.), 47 | (k, 1., 0.), 48 | (0., 0., 1.)) 49 | 50 | 51 | def matrix(a: float = 1., b: float = 0., c: float = 0., d: float = 1., e: float = 0, f: float = 0) -> Matrix3x3: 52 | ''' Full matrix as defined by SVG transform ''' 53 | return ((a, c, e), 54 | (b, d, f), 55 | (0., 0., 1.)) 56 | 57 | 58 | def transform(point: Point, xform: Matrix3x3) -> Point: 59 | ''' Apply transform to point (matrix multiply) ''' 60 | pt = [point[0], point[1], 1.] 61 | x = pt[0] * xform[0][0] + pt[1] * xform[0][1] + pt[2]*xform[0][2] 62 | y = pt[0] * xform[1][0] + pt[1] * xform[1][1] + pt[2]*xform[1][2] 63 | # z = pt[0] * xform[2][0] + pt[1] * xform[2][1] + pt[2]*xform[2][2] 64 | # assert math.isclose(z, 1) 65 | return Point((x, y)) 66 | 67 | 68 | def transform_all(pt, xform: Sequence[Matrix3x3]) -> Point: 69 | ''' Apply series of transforms to the point ''' 70 | for xf in xform: 71 | pt = transform(pt, xf) 72 | return pt 73 | -------------------------------------------------------------------------------- /schemdraw/backends/svgunits.py: -------------------------------------------------------------------------------- 1 | ''' CSS/SVG Unit Conversions per 2 | https://www.w3.org/TR/css-values/#absolute-lengths 3 | ''' 4 | PT_PER_IN = 72. 5 | PX_PER_IN = 96. 6 | CM_PER_IN = 2.54 7 | MM_PER_IN = CM_PER_IN * 10 8 | PC_PER_IN = 6. 9 | 10 | PX_PER_PT = PX_PER_IN / PT_PER_IN 11 | 12 | TO_PX = { 13 | 'in': PX_PER_IN, 14 | 'cm': PX_PER_IN / CM_PER_IN, 15 | 'mm': PX_PER_IN / MM_PER_IN, 16 | 'pt': PX_PER_IN / PT_PER_IN, 17 | 'pc': PX_PER_IN / PC_PER_IN, 18 | 'px': 1, 19 | } 20 | 21 | 22 | def parse_size_to_px(value: str) -> float: 23 | ''' Convert SVG size string (such as `2in`) to pixels ''' 24 | try: 25 | value_px = float(value) 26 | except ValueError: 27 | unit = value[-2:] 28 | value_px = float(value[:-2]) * TO_PX.get(unit, 1) 29 | return value_px 30 | -------------------------------------------------------------------------------- /schemdraw/default_canvas.py: -------------------------------------------------------------------------------- 1 | ''' The default canvas to draw on ''' 2 | 3 | default_canvas = 'matplotlib' 4 | -------------------------------------------------------------------------------- /schemdraw/drawing_stack.py: -------------------------------------------------------------------------------- 1 | ''' Drawing Stack 2 | 3 | This module tracks the active drawing and element for use in context 4 | managers, enabling of adding elements to a drawing without explicitly 5 | calling `d.add` or `d +=`. The stack is a dictionary (relying on being 6 | ordered) of drawing: element pairs. The last item in the dictionary 7 | is the last drawing or container to be opened by `with` block, and 8 | the element is the last element instantiated. 9 | 10 | Typical order of operations: 11 | 12 | 1) A drawing is added to the stack when the `with` block is opened 13 | 2) When an element is instantiated inside the block, it is set as 14 | the value of the drawing's dictionary item. 15 | 3) When a different element is instantiated, the element in the 16 | stack will then be added to its drawing if it hasn't been added already. 17 | This allows the chained methods (such as `.up`, `.down`) to affect 18 | the element before it is placed. 19 | 4) When the `with` block exits, the last element to be instantiated 20 | is added to the drawing, if it hasn't been already. The drawing 21 | is then popped from the stack. 22 | 23 | A `pause` attribute may be set True to prevent any stack operations. 24 | This may be used, for example, when adding elements to an ElementCompound. 25 | ''' 26 | from __future__ import annotations 27 | from typing import Union, Optional, TYPE_CHECKING 28 | 29 | if TYPE_CHECKING: 30 | from .schemdraw import Drawing 31 | from .elements import Element, Container 32 | 33 | DrawingType = Union['Drawing', 'Container'] 34 | 35 | drawing_stack: dict[DrawingType, Optional['Element']] = {} #{drawing: element} 36 | pause: bool = False 37 | 38 | 39 | def push_drawing(drawing: DrawingType) -> None: 40 | ''' Add a drawing to the stack ''' 41 | drawing_stack[drawing] = None 42 | 43 | def pop_drawing(drawing: DrawingType) -> None: 44 | ''' Remove the drawing from the stack ''' 45 | drawing_stack.pop(drawing) 46 | 47 | def push_element(element: Optional['Element']) -> None: 48 | ''' Add a new element to the stack, placing the existing 49 | one if not already placed by the user 50 | ''' 51 | if not pause and len(drawing_stack) > 0: 52 | drawing, prev_elm = list(drawing_stack.items())[-1] 53 | if prev_elm is not None and prev_elm not in drawing: 54 | drawing.add(prev_elm) 55 | drawing_stack[drawing] = element 56 | 57 | -------------------------------------------------------------------------------- /schemdraw/dsp/__init__.py: -------------------------------------------------------------------------------- 1 | from .dsp import (Square, Circle, Sum, SumSigma, Mixer, Speaker, Amp, OscillatorBox, Oscillator, Filter, Adc, 2 | Dac, Demod, Circulator, Isolator, VGA) 3 | from ..elements import Arrowhead, Antenna, Dot, Arrow, Line, Ic, IcPin, Multiplexer, Wire 4 | from ..flow import Box 5 | 6 | __all__ = ['Square', 'Circle', 'Sum', 'SumSigma', 'Mixer', 'Speaker', 'Amp', 'OscillatorBox', 'Oscillator', 7 | 'Filter', 'Adc', 'Dac', 'Demod', 'Circulator', 'Isolator', 'VGA', 'Arrowhead', 'Antenna', 'Dot', 8 | 'Arrow', 'Line', 'Ic', 'IcPin', 'Multiplexer', 'Wire', 'Box'] 9 | -------------------------------------------------------------------------------- /schemdraw/elements/cables.py: -------------------------------------------------------------------------------- 1 | ''' Cable elements, coaxial and triaxial ''' 2 | from __future__ import annotations 3 | from typing import Optional 4 | import math 5 | import warnings 6 | 7 | from ..segments import Segment, SegmentArc 8 | from .elements import Element2Term, gap 9 | 10 | 11 | class Coax(Element2Term): 12 | ''' Coaxial cable element. 13 | 14 | Args: 15 | length: Total length of the cable, excluding lead extensions. [default: 3] 16 | radius: Radius of shield [default: 0.3] 17 | leadlen: Distance (x) from start of center conductor to 18 | start of shield. [default: 0.6] 19 | 20 | Anchors: 21 | * shieldstart 22 | * shieldstart_top 23 | * shieldend 24 | * shieldend_top 25 | * shieldcenter 26 | * shieldcenter_top 27 | ''' 28 | _element_defaults = { 29 | 'length': 3, 30 | 'leadlen': 0.6, 31 | 'radius': 0.3, 32 | } 33 | def __init__(self, *, 34 | length: Optional[float] = None, 35 | radius: Optional[float] = None, 36 | leadlen: Optional[float] = None, 37 | **kwargs): 38 | super().__init__(**kwargs) 39 | _leadlen = self.params['leadlen'] 40 | _length = self.params['length'] 41 | _radius = self.params['radius'] 42 | self.segments.append(Segment( # Center conductor 43 | [(0, 0), (_leadlen, 0), gap, (_length-_leadlen+_radius/2, 0), 44 | (_length, 0)])) 45 | self.segments.append(Segment( 46 | [(_leadlen, _radius), (_length-_leadlen, _radius)])) # Top 47 | self.segments.append(Segment( 48 | [(_leadlen, -_radius), (_length-_leadlen, -_radius)])) # Bottom 49 | self.segments.append(SegmentArc( 50 | (_leadlen, 0), width=_radius, height=_radius*2, theta1=0, theta2=360)) 51 | self.segments.append(SegmentArc( 52 | (_length-_leadlen, 0), width=_radius, 53 | height=_radius*2, theta1=270, theta2=90)) 54 | self.anchors['shieldstart'] = (_leadlen, -_radius) 55 | self.anchors['shieldstart_top'] = (_leadlen, _radius) 56 | self.anchors['shieldend'] = (_length-_leadlen, -_radius) 57 | self.anchors['shieldend_top'] = (_length-_leadlen, _radius) 58 | self.anchors['shieldcenter'] = (_length/2, -_radius) 59 | self.anchors['shieldcenter_top'] = (_length/2, _radius) 60 | 61 | if _radius/2 > _leadlen: 62 | warnings.warn('Coax leadlen < radius/2. Coax may be malformed.') 63 | if _leadlen*2 > _length: 64 | warnings.warn('Coax length < 2*leadlen. Coax may be malformed.') 65 | 66 | 67 | class Triax(Element2Term): 68 | ''' Triaxial cable element. 69 | 70 | Args: 71 | length: Total length of the cable [default: 3] 72 | radiusinner: Radius of inner guard [default: 0.3] 73 | radiusouter: Radius of outer shield [default: 0.6] 74 | leadlen: Distance (x) from start of center conductor to 75 | start of guard. [default: 0.6] 76 | shieldofststart: Distance from start of inner guard to start 77 | of outer shield [default: 0.3] 78 | shieldofstend: Distance from end of outer shield to end 79 | of inner guard [default: 0.3] 80 | 81 | Anchors: 82 | * shieldstart 83 | * shieldstart_top 84 | * shieldend 85 | * shieldend_top 86 | * shieldcenter 87 | * shieldcenter_top 88 | * guardstart 89 | * guardstart_top 90 | * guardend 91 | * guardend_top 92 | ''' 93 | _element_defaults = { 94 | 'length': 3, 95 | 'leadlen': 0.6, 96 | 'radiusinner': 0.3, 97 | 'radiusouter': 0.6, 98 | 'shieldofststart': 0.3, 99 | 'shieldofstend': 0.3 100 | } 101 | def __init__(self, *, 102 | length: Optional[float] = None, 103 | leadlen: Optional[float] = None, 104 | radiusinner: Optional[float] = None, 105 | radiusouter: Optional[float] = None, 106 | shieldofststart: Optional[float] = None, 107 | shieldofstend: Optional[float] = None, 108 | **kwargs): 109 | super().__init__(**kwargs) 110 | r1 = self.params['radiusinner'] 111 | r2 = self.params['radiusouter'] 112 | _length = self.params['length'] 113 | _leadlen = self.params['leadlen'] 114 | _shieldofstend = self.params['shieldofstend'] 115 | _shieldofststart = self.params['shieldofststart'] 116 | 117 | if r2 < r1: 118 | raise ValueError('Triax inner radius > outer radius') 119 | 120 | xshield = r2/2 * math.sqrt(1 - r1**2/r2**2) 121 | if _shieldofststart - xshield > -r1/2: 122 | thetashield = 180 - math.degrees(math.atan2(r1, xshield)) 123 | else: 124 | thetashield = 180 125 | 126 | if _length - _leadlen - _shieldofstend + r2 < _length: 127 | # Include the inner guard on output side 128 | self.segments.append(Segment( # Center conductor (first) 129 | [(0, 0), (_leadlen, 0), gap, 130 | (_length-_leadlen+r1/2, 0), 131 | (_length, 0)])) 132 | self.segments.append(Segment( 133 | [(_length-_leadlen-_shieldofstend+xshield, r1), 134 | (_length-_leadlen, r1)])) # guard (inner) top/right 135 | self.segments.append(Segment( 136 | [(_length-_leadlen-_shieldofstend+xshield, -r1), 137 | (_length-_leadlen, -r1)])) # guard (inner) bottom/right 138 | self.segments.append(SegmentArc( 139 | (_length-_leadlen, 0), width=r1, height=r1*2, 140 | theta1=270, theta2=90)) 141 | else: 142 | # Don't include inner guard on output side 143 | self.segments.append(Segment( # Center conductor 144 | [(0, 0), (_leadlen, 0), gap, 145 | (_length-_leadlen-_shieldofstend+r2/2, 0)])) 146 | 147 | # Start with shapes that are always shown... 148 | self.segments.append(Segment( # guard (inner) top/left 149 | [(_leadlen, r1), (_leadlen+_shieldofststart+xshield, r1)])) 150 | self.segments.append(Segment( # guard (inner) bottom/left 151 | [(_leadlen, -r1), (_leadlen+_shieldofststart+xshield, -r1)])) 152 | self.segments.append(Segment( # shield (outer) top 153 | [(_leadlen+_shieldofststart, r2), 154 | (_length-_leadlen-_shieldofstend, r2)])) 155 | self.segments.append(Segment( # shield (outer) bottom 156 | [(_leadlen+_shieldofststart, -r2), 157 | (_length-_leadlen-_shieldofstend, -r2)])) 158 | 159 | self.segments.append(SegmentArc( 160 | (_leadlen, 0), width=r1, height=r1*2, 161 | theta1=0, theta2=360)) 162 | self.segments.append(SegmentArc( 163 | (_leadlen+_shieldofststart, 0), width=r2, 164 | height=r2*2, theta1=-thetashield, theta2=thetashield)) 165 | self.segments.append(SegmentArc( 166 | (_length-_leadlen-_shieldofstend, 0), width=r2, 167 | height=r2*2, theta1=270, theta2=90)) 168 | 169 | self.anchors['guardstart'] = (_leadlen, -r1) 170 | self.anchors['guardstart_top'] = (_leadlen, r1) 171 | self.anchors['guardend'] = (_length-_leadlen, -r1) 172 | self.anchors['guardend_top'] = (_length-_leadlen, r1) 173 | self.anchors['shieldstart'] = (_leadlen+_shieldofststart, -r2) 174 | self.anchors['shieldstart_top'] = (_leadlen+_shieldofststart, r2) 175 | self.anchors['shieldend'] = (_length-_leadlen-_shieldofstend, -r2) 176 | self.anchors['shieldend_top'] = (_length-_leadlen-_shieldofstend, r2) 177 | self.anchors['shieldcenter'] = (_length/2, -r2) 178 | self.anchors['shieldcenter_top'] = (_length/2, r2) 179 | 180 | if r2 <= r1: 181 | warnings.warn('Triax outer radius < inner radius') 182 | 183 | if _leadlen+_shieldofststart > _length-_leadlen-_shieldofstend: 184 | warnings.warn('Triax too short for outer radius') 185 | -------------------------------------------------------------------------------- /schemdraw/elements/container.py: -------------------------------------------------------------------------------- 1 | ''' Container Element ''' 2 | from __future__ import annotations 3 | import math 4 | from typing import Union, Optional, TYPE_CHECKING 5 | 6 | from .elements import Element 7 | from ..segments import Segment, SegmentPoly, BBox 8 | from .. import drawing_stack 9 | 10 | if TYPE_CHECKING: 11 | from ..schemdraw import Drawing 12 | 13 | 14 | class Container(Element): 15 | ''' Container element - a group of elements surrounded by a box 16 | 17 | Args: 18 | drawing: The schemdraw Drawing or another Container to contain 19 | cornerradius: Radius to round the corners of the box [default: 0.3] 20 | padx: Spacing from element to box in x direction [default: 0.75] 21 | pady: Spacing from element to box in y direction [default: 0.75] 22 | ''' 23 | _element_defaults = { 24 | 'cornerradius': 0.3, 25 | 'padx': 0.75, 26 | 'pady': 0.75, 27 | 'lblloc': 'NW', 28 | 'lblofst': (.1, -.1), 29 | 'lblalign': ('left', 'top'), 30 | 'theta': 0, 31 | 'anchor': 'boxNW' 32 | } 33 | def __init__(self, 34 | drawing: Union['Drawing', 'Container'], 35 | *, 36 | cornerradius: Optional[float] = None, 37 | padx: Optional[float] = None, 38 | pady: Optional[float] = None): 39 | super().__init__() 40 | self.drawing = drawing 41 | self.elements: list[Element] = [] 42 | 43 | def container(self, 44 | cornerradius: Optional[float] = None, 45 | padx: Optional[float] = None, 46 | pady: Optional[float] = None) -> 'Container': 47 | ''' Add a container to the container. Use as a context manager, 48 | such that elemnents inside the `with` are surrounded by 49 | the container. 50 | 51 | >>> with container.container(): 52 | >>> elm.Resistor() 53 | >>> ... 54 | 55 | Args: 56 | cornerradius: Radius to round the corners of the box [default: 0.3] 57 | padx: Spacing from element to box in x direction [default: 0.75] 58 | pady: Spacing from element to box in y direction [default: 0.75] 59 | ''' 60 | return Container(self, cornerradius=cornerradius, padx=padx, pady=pady) 61 | 62 | def __iadd__(self, element: Element) -> 'Container': 63 | return self.add(element) 64 | 65 | def add(self, element: Element) -> 'Container': 66 | ''' Add an element to the container ''' 67 | self.elements.append(element) 68 | self.drawing.add(element) 69 | return self 70 | 71 | def __enter__(self): 72 | drawing_stack.push_drawing(self) 73 | return self 74 | 75 | def __exit__(self, exc_type, exc_val, exc_tb): 76 | drawing_stack.push_element(self) 77 | drawing_stack.pop_drawing(self) 78 | 79 | def __contains__(self, element): 80 | return element in self.elements 81 | 82 | def container_bbox(self, transform: bool = True) -> BBox: 83 | ''' Bounding box of the contents only ''' 84 | xmin = math.inf 85 | xmax = -math.inf 86 | ymin = math.inf 87 | ymax = -math.inf 88 | for element in self.elements: 89 | bbox = element.get_bbox(transform=transform) 90 | xmin = min(bbox.xmin, xmin) 91 | xmax = max(bbox.xmax, xmax) 92 | ymin = min(bbox.ymin, ymin) 93 | ymax = max(bbox.ymax, ymax) 94 | return BBox(xmin, ymin, xmax, ymax) 95 | 96 | def get_bbox(self, transform: bool = False, includetext: bool = True) -> BBox: 97 | ''' Bounding box including border ''' 98 | bbox = self.container_bbox(transform=transform) 99 | return BBox(bbox.xmin-self.params['padx'], 100 | bbox.ymin-self.params['pady'], 101 | bbox.xmax+self.params['padx'], 102 | bbox.ymax+self.params['pady']) 103 | 104 | def _place(self, dwgxy, dwgtheta, **dwgparams): 105 | bbox = self.container_bbox() 106 | w = bbox.xmax - bbox.xmin + self.params['padx']*2 107 | h = bbox.ymax - bbox.ymin + self.params['pady']*2 108 | cornerradius = self.params['cornerradius'] 109 | bbox = BBox(bbox.xmin-self.params['padx'], 110 | bbox.ymin-self.params['pady'], 111 | bbox.xmax+self.params['padx'], 112 | bbox.ymax+self.params['pady']) 113 | self.at((bbox.xmin, bbox.ymax)) 114 | if cornerradius > 0: 115 | self.segments = [SegmentPoly( 116 | [(0, h/2), (w, h/2), (w, -h/2), (0, -h/2)], 117 | cornerradius=cornerradius)] 118 | else: 119 | self.segments.append(Segment([(0, 0), (0, h/2), (w, h/2), 120 | (w, -h/2), (0, -h/2), (0, 0)])) 121 | self.anchors['center'] = (w/2, 0) 122 | self.anchors['boxNW'] = (0, h/2) 123 | self.anchors['N'] = (w/2, h/2) 124 | self.anchors['E'] = (w, 0) 125 | self.anchors['S'] = (w/2, -h/2) 126 | self.anchors['W'] = (0, 0) 127 | k = cornerradius - cornerradius*math.sqrt(2)/2 128 | self.anchors['NE'] = (w-k, h/2-k) 129 | self.anchors['NW'] = (k, h/2-k) 130 | self.anchors['SE'] = (w-k, -h/2+k) 131 | self.anchors['SW'] = (k, -h/2+k) 132 | self.anchors['NNE'] = (3*w/4 if cornerradius < w/4 else w-cornerradius, h/2) 133 | self.anchors['NNW'] = (w/4 if cornerradius < w/4 else cornerradius, h/2) 134 | self.anchors['SSE'] = (3*w/4 if cornerradius < w/4 else w-cornerradius, -h/2) 135 | self.anchors['SSW'] = (w/4 if cornerradius < w/4 else cornerradius, -h/2) 136 | self.anchors['ENE'] = (w, h/4 if cornerradius < h/4 else h/2-cornerradius) 137 | self.anchors['ESE'] = (w, -h/4 if cornerradius < h/4 else -h/2+cornerradius) 138 | self.anchors['WNW'] = (0, h/4 if cornerradius < h/4 else h/2-cornerradius) 139 | self.anchors['WSW'] = (0, -h/4 if cornerradius < h/4 else -h/2+cornerradius) 140 | return super()._place(dwgxy, dwgtheta, **dwgparams) 141 | -------------------------------------------------------------------------------- /schemdraw/elements/image.py: -------------------------------------------------------------------------------- 1 | ''' Elements based on images ''' 2 | from __future__ import annotations 3 | from typing import Optional, BinaryIO 4 | 5 | from .elements import Element 6 | from ..segments import SegmentImage 7 | from ..util import Point 8 | 9 | 10 | class ElementImage(Element): 11 | ''' Element from an Image file 12 | 13 | Args: 14 | image: Image filename or open file pointer 15 | width: Width to draw image in Drawing 16 | height: Height to draw image in Drawing 17 | xy: Origin (lower left corner) 18 | ''' 19 | def __init__(self, image: str | BinaryIO, 20 | width: float, 21 | height: float, 22 | xy: Point = Point((0, 0)), 23 | imgfmt: Optional[str] = None, 24 | **kwargs): 25 | super().__init__(**kwargs) 26 | zorder = kwargs.get('zorder', 1) 27 | self.segments.append(SegmentImage(image=image, xy=xy, width=width, height=height, 28 | zorder=zorder, imgfmt=imgfmt)) 29 | self.elmparams['theta'] = 0 30 | -------------------------------------------------------------------------------- /schemdraw/elements/misc.py: -------------------------------------------------------------------------------- 1 | ''' Other elements ''' 2 | from __future__ import annotations 3 | from typing import Optional 4 | from .elements import Element, Element2Term, gap 5 | from .twoterm import resheight 6 | from ..segments import Segment, SegmentPoly, SegmentArc, SegmentCircle 7 | 8 | 9 | class Speaker(Element): 10 | ''' Speaker element with two inputs. 11 | 12 | Anchors: 13 | * in1 14 | * in2 15 | ''' 16 | def __init__(self, **kwargs): 17 | super().__init__(**kwargs) 18 | sph = .5 19 | self.segments.append(Segment([(0, 0), (resheight, 0)])) 20 | self.segments.append(Segment([(0, -sph), (resheight, -sph)])) 21 | self.segments.append(SegmentPoly( 22 | [(resheight, sph/2), (resheight, -sph*1.5), 23 | (resheight*2, -sph*1.5), (resheight*2, sph/2)])) 24 | self.segments.append(SegmentPoly( 25 | [(resheight*2, sph/2), (resheight*3.5, sph*1.25), 26 | (resheight*3.5, -sph*2.25), (resheight*2, -sph*1.5)], 27 | closed=False)) 28 | self.anchors['in1'] = (0, 0) 29 | self.anchors['in2'] = (0, -sph) 30 | self.params['drop'] = (0, -sph) 31 | 32 | 33 | class Mic(Element): 34 | ''' Microphone element with two inputs. 35 | 36 | Anchors: 37 | * in1 38 | * in2 39 | ''' 40 | def __init__(self, **kwargs): 41 | super().__init__(**kwargs) 42 | sph = .5 43 | self.segments.append(Segment( # Upper lead 44 | [(0, 0), (resheight, 0)])) 45 | self.segments.append(Segment( # Lower lead 46 | [(0, -sph), (resheight, -sph)])) 47 | self.segments.append(Segment( # Vertical flat 48 | [(-resheight*2, resheight), (-resheight*2, -resheight*3)])) 49 | self.segments.append(SegmentArc( 50 | (-resheight*2, -resheight), theta1=270, theta2=90, 51 | width=resheight*4, height=resheight*4)) 52 | self.anchors['in1'] = (resheight, 0) 53 | self.anchors['in2'] = (resheight, -sph) 54 | self.params['drop'] = (0, -sph) 55 | 56 | 57 | class Motor(Element2Term): 58 | ''' Motor ''' 59 | def __init__(self, **kwargs): 60 | super().__init__(**kwargs) 61 | mw = .22 62 | self.segments.append(Segment( 63 | [(-mw, 0), (-mw, 0), gap, (1+mw, 0), (1+mw, 0)])) 64 | self.segments.append(Segment( 65 | [(0, -mw), (0-mw, -mw), (0-mw, mw), (0, mw)])) 66 | self.segments.append(Segment( 67 | [(1, -mw), (1+mw, -mw), (1+mw, mw), (1, mw)])) 68 | self.segments.append(SegmentCircle((0.5, 0), 0.5)) 69 | 70 | 71 | class AudioJack(Element): 72 | ''' Audio Jack with 2 or 3 connectors and optional switches. 73 | 74 | Args: 75 | ring: Show ring (third conductor) contact 76 | switch: Show switch on tip contact 77 | ringswitch: Show switch on ring contact 78 | dots: Show connector dots 79 | radius: Radius of connector dots [default: 0.075] 80 | 81 | Anchors: 82 | * tip 83 | * sleeve 84 | * ring 85 | * ringswitch 86 | * tipswitch 87 | ''' 88 | _element_defaults = { 89 | 'radius': 0.075, 90 | 'open': True 91 | } 92 | def __init__(self, *, 93 | radius: Optional[float] = None, 94 | ring: bool = False, 95 | ringswitch: bool = False, 96 | dots: bool = True, 97 | switch: bool = False, 98 | open: Optional[bool] = None, 99 | **kwargs): 100 | super().__init__(**kwargs) 101 | fill = 'bg' if self.params['open'] else None 102 | r = self.params['radius'] 103 | 104 | length = 2.0 105 | ringlen = .75 106 | tiplen = .55 107 | swidth = .2 108 | sleeveheight = 1.0 109 | tipy = 1.0 110 | ringy = .1 111 | sleevey = .35 112 | swdy = .4 113 | swlen = .5 114 | 115 | if switch: 116 | tipy += .2 117 | 118 | if ring and ringswitch: 119 | sleevey += .2 120 | ringy -= .2 121 | 122 | if ring: 123 | if dots: 124 | self.segments.append(SegmentCircle((0, -sleevey), r, 125 | fill=fill, zorder=4)) 126 | self.segments.append(Segment( 127 | [(-r, -sleevey), (-length, -sleevey), 128 | (-length, 0), (-length, sleeveheight), 129 | (-length-swidth, sleeveheight), 130 | (-length-swidth, 0), (-length, 0)])) 131 | self.anchors['sleeve'] = (0, -sleevey) 132 | 133 | if dots: 134 | self.segments.append(SegmentCircle( 135 | (0, ringy), r, fill=fill, zorder=4)) 136 | self.segments.append(Segment( 137 | [(-r, ringy), (-length*.75, ringy), 138 | (-length*ringlen-2*r, ringy+2*r), 139 | (-length*ringlen-r*4, ringy)])) 140 | self.anchors['ring'] = (0, ringy) 141 | 142 | else: 143 | if dots: 144 | self.segments.append(SegmentCircle( 145 | (0, 0), r, fill=fill, zorder=4)) 146 | self.segments.append(Segment( 147 | [(-r, 0), (-length, 0), (-length, sleeveheight), 148 | (-length+swidth, sleeveheight), (-length+swidth, 0)])) 149 | self.anchors['sleeve'] = (0, 0) 150 | 151 | if dots: 152 | self.segments.append(SegmentCircle( 153 | (0, tipy), r, fill=fill, zorder=4)) 154 | self.segments.append(Segment( 155 | [(-r, tipy), (-length*.55, tipy), 156 | (-length*tiplen-2*r, tipy-2*r), 157 | (-length*tiplen-r*4, tipy)])) 158 | self.anchors['tip'] = (0, tipy) 159 | 160 | if switch: 161 | if dots: 162 | self.segments.append(SegmentCircle( 163 | (0, tipy-swdy), r, fill=fill, zorder=4)) 164 | self.segments.append(Segment( 165 | [(0, tipy-swdy), (-swlen, tipy-swdy)])) 166 | self.segments.append(Segment([(-swlen, tipy-swdy), (-swlen, tipy)], arrow='->')) 167 | self.anchors['tipswitch'] = (0, tipy-swdy) 168 | 169 | if ring and ringswitch: 170 | if dots: 171 | self.segments.append(SegmentCircle( 172 | (0, ringy+swdy), r, fill=fill, zorder=4)) 173 | self.segments.append(Segment( 174 | [(0, ringy+swdy), (-swlen, ringy+swdy)])) 175 | self.segments.append(Segment([(-swlen, ringy+swdy), (-swlen, ringy)], arrow='->')) 176 | self.anchors['ringswitch'] = (0, ringy+swdy) 177 | -------------------------------------------------------------------------------- /schemdraw/elements/oneterm.py: -------------------------------------------------------------------------------- 1 | ''' One terminal element definitions ''' 2 | from __future__ import annotations 3 | from typing import Optional 4 | 5 | from ..segments import Segment 6 | from .elements import Element 7 | from .twoterm import resheight, gap 8 | 9 | gndgap = 0.12 10 | _gnd_lead = 0.4 11 | 12 | 13 | class Ground(Element): 14 | ''' Ground connection 15 | 16 | Keyword Args: 17 | lead: Show lead wire [default: True] 18 | ''' 19 | _element_defaults = { 20 | 'lead': True, 21 | 'theta': 0, 22 | 'drop': (0, 0) 23 | } 24 | def __init__(self, *, 25 | lead: Optional[bool] = None, 26 | **kwargs): 27 | super().__init__(**kwargs) 28 | gnd_lead = _gnd_lead if self.params['lead'] else 0 29 | self.segments.append(Segment( 30 | [(0, 0), (0, -gnd_lead), (-resheight, -gnd_lead), 31 | (resheight, -gnd_lead), gap, (-resheight*.7, -gndgap-gnd_lead), 32 | (resheight*.7, -gndgap-gnd_lead), gap, 33 | (-resheight*.2, -gndgap*2-gnd_lead), 34 | (resheight*.2, -gndgap*2-gnd_lead)])) 35 | self.anchors['start'] = (0, 0) 36 | self.anchors['center'] = (0, 0) 37 | self.anchors['end'] = (0, 0) 38 | 39 | 40 | class GroundSignal(Element): 41 | ''' Signal ground 42 | 43 | Keyword Args: 44 | lead: Show lead wire 45 | ''' 46 | _element_defaults = { 47 | 'lead': True, 48 | 'theta': 0, 49 | 'drop': (0, 0) 50 | } 51 | def __init__(self, *, 52 | lead: bool = True, 53 | **kwargs): 54 | super().__init__(**kwargs) 55 | gnd_lead = _gnd_lead if self.params['lead'] else 0 56 | self.segments.append(Segment( 57 | [(0, 0), (0, -gnd_lead), (-resheight, -gnd_lead), (0, -gnd_lead-resheight), 58 | (resheight, -gnd_lead), (0, -gnd_lead)])) 59 | self.anchors['start'] = (0, 0) 60 | self.anchors['center'] = (0, 0) 61 | self.anchors['end'] = (0, 0) 62 | 63 | 64 | class GroundChassis(Element): 65 | ''' Chassis ground 66 | 67 | Keyword Args: 68 | lead: Show lead wire 69 | ''' 70 | _element_defaults = { 71 | 'lead': True, 72 | 'drop': (0, 0), 73 | 'theta': 0 74 | } 75 | def __init__(self, **kwargs): 76 | super().__init__(**kwargs) 77 | gnd_lead = _gnd_lead if self.params['lead'] else 0 78 | dx = resheight*.75 79 | dy = resheight 80 | self.segments.append(Segment( 81 | [(0, 0), (0, -gnd_lead), (-dx, -gnd_lead-dy)])) 82 | self.segments.append(Segment( 83 | [(0, -gnd_lead), (-dx, -gnd_lead), (-dx*2, -gnd_lead-dy)])) 84 | self.segments.append(Segment( 85 | [(0, -gnd_lead), (dx, -gnd_lead), (0, -gnd_lead-dy)])) 86 | self.elmparams['drop'] = (0, 0) 87 | self.elmparams['theta'] = 0 88 | self.anchors['start'] = (0, 0) 89 | self.anchors['center'] = (0, 0) 90 | self.anchors['end'] = (0, 0) 91 | 92 | 93 | class Antenna(Element): 94 | ''' Antenna ''' 95 | def __init__(self, **kwargs): 96 | super().__init__(**kwargs) 97 | lead = 0.6 98 | h = 0.6 99 | w = 0.38 100 | self.segments.append(Segment( 101 | [(0, 0), (0, lead), (-w, lead+h), (w, lead+h), (0, lead)])) 102 | self.elmparams['drop'] = (0, 0) 103 | self.elmparams['theta'] = 0 104 | self.anchors['start'] = (0, 0) 105 | self.anchors['center'] = (0, 0) 106 | self.anchors['end'] = (0, 0) 107 | 108 | 109 | class AntennaLoop(Element): 110 | ''' Loop antenna (diamond style) ''' 111 | def __init__(self, **kwargs): 112 | super().__init__(**kwargs) 113 | lead = 0.2 114 | h = 0.5 115 | self.segments.append(Segment( 116 | [(0, 0), (0, lead), (-h, h+lead), (lead/2, h*2+1.5*lead), 117 | (h+lead, h+lead), (lead, lead), (lead, 0)])) 118 | self.elmparams['drop'] = (0, 0) 119 | self.elmparams['theta'] = 0 120 | self.anchors['start'] = (0, 0) 121 | self.anchors['end'] = (lead, 0) 122 | 123 | 124 | class AntennaLoop2(Element): 125 | ''' Loop antenna (square style) ''' 126 | def __init__(self, **kwargs): 127 | super().__init__(**kwargs) 128 | lead = .25 129 | h = 1 130 | x1 = -h/2-lead/2 131 | x2 = x1 + lead 132 | x3 = h/2+lead/2 133 | x4 = x3 + lead 134 | y1 = lead 135 | y2 = lead*2 136 | y3 = h+y2 137 | y4 = y3+lead 138 | self.segments.append(Segment( 139 | [(0, 0), (0, y1), (x1, y1), (x1, y3), (x3, y3), (x3, y2), 140 | (x2, y2), (x2, y4), (x4, y4), (x4, y1), (lead, y1), (lead, 0)])) 141 | self.elmparams['drop'] = (lead, 0) 142 | self.elmparams['theta'] = 0 143 | self.anchors['start'] = (0, 0) 144 | self.anchors['end'] = (lead, 0) 145 | 146 | 147 | class Vss(Element): 148 | ''' Vss connection 149 | 150 | Keyword Args: 151 | lead: Show lead wire 152 | ''' 153 | _element_defaults = { 154 | 'lead': True, 155 | 'drop': (0, 0), 156 | 'theta': 0, 157 | 'lblloc': 'bottom' 158 | } 159 | def __init__(self, **kwargs): 160 | super().__init__(**kwargs) 161 | dx = resheight*.75 162 | gnd_lead = _gnd_lead if self.params['lead'] else 0 163 | self.segments.append(Segment([(0, 0), (0, -gnd_lead)])) 164 | self.segments.append(Segment([(0, -gnd_lead), (-dx, -gnd_lead)])) 165 | self.segments.append(Segment([(0, -gnd_lead), (dx, -gnd_lead)])) 166 | self.anchors['start'] = (0, 0) 167 | self.anchors['center'] = (0, 0) 168 | self.anchors['end'] = (0, 0) 169 | 170 | 171 | class Vdd(Element): 172 | ''' Vdd connection 173 | 174 | Keyword Args: 175 | lead: Show lead wire 176 | ''' 177 | _element_defaults = { 178 | 'lead': True, 179 | 'drop': (0, 0), 180 | 'theta': 0 181 | } 182 | def __init__(self, **kwargs): 183 | super().__init__(**kwargs) 184 | dx = resheight*.75 185 | gnd_lead = _gnd_lead if self.params['lead'] else 0 186 | self.segments.append(Segment([(0, 0), (0, gnd_lead)])) 187 | self.segments.append(Segment([(0, gnd_lead), (-dx, gnd_lead)])) 188 | self.segments.append(Segment([(0, gnd_lead), (dx, gnd_lead)])) 189 | self.anchors['start'] = (0, 0) 190 | self.anchors['center'] = (0, 0) 191 | self.anchors['end'] = (0, 0) 192 | 193 | 194 | class NoConnect(Element): 195 | ''' No Connection ''' 196 | def __init__(self, **kwargs): 197 | super().__init__(**kwargs) 198 | dx = resheight*.75 199 | dy = resheight*.75 200 | self.segments.append(Segment([(-dx, -dy), (dx, dy)])) 201 | self.segments.append(Segment([(-dx, dy), (dx, -dy)])) 202 | self.elmparams['drop'] = (0, 0) 203 | self.elmparams['theta'] = 0 204 | self.elmparams['lblloc'] = 'bottom' 205 | self.anchors['start'] = (0, 0) 206 | self.anchors['center'] = (0, 0) 207 | self.anchors['end'] = (0, 0) 208 | -------------------------------------------------------------------------------- /schemdraw/elements/opamp.py: -------------------------------------------------------------------------------- 1 | ''' Operation amplifier ''' 2 | from __future__ import annotations 3 | from typing import Optional 4 | import math 5 | 6 | from .elements import Element, gap 7 | from ..segments import Segment 8 | 9 | 10 | oa_back = 2.5 11 | oa_xlen = oa_back * math.sqrt(3)/2 12 | oa_lblx = oa_xlen/8 13 | oa_pluslen = .2 14 | 15 | 16 | class Opamp(Element): 17 | ''' Operational Amplifier. 18 | 19 | Keyword Args: 20 | sign: Draw +/- labels at each input 21 | leads: Draw short leads on input/output 22 | 23 | Anchors: 24 | * in1 25 | * in2 26 | * out 27 | * vd 28 | * vs 29 | * n1 30 | * n2 31 | * n1a 32 | * n2a 33 | ''' 34 | _element_defaults = { 35 | 'sign': True, 36 | 'leads': False 37 | } 38 | def __init__(self, *, 39 | sign: Optional[bool] = None, 40 | leads: Optional[bool] = None, 41 | **kwargs): 42 | super().__init__(**kwargs) 43 | leadlen = oa_back/4 44 | x = 0 if not self.params['leads'] else leadlen 45 | 46 | self.segments.append(Segment( 47 | [(x, 0), (x, oa_back/2), (x+oa_xlen, 0), (x, -oa_back/2), (x, 0), 48 | gap, (x+oa_xlen, 0)])) 49 | 50 | if self.params['sign']: 51 | self.segments.append(Segment( 52 | [(x+oa_lblx-oa_pluslen/2, oa_back/4), 53 | (x+oa_lblx+oa_pluslen/2, oa_back/4)])) 54 | self.segments.append(Segment( 55 | [(x+oa_lblx-oa_pluslen/2, -oa_back/4), 56 | (x+oa_lblx+oa_pluslen/2, -oa_back/4)])) 57 | self.segments.append(Segment( 58 | [(x+oa_lblx, -oa_back/4-oa_pluslen/2), 59 | (x+oa_lblx, -oa_back/4+oa_pluslen/2)])) 60 | 61 | if self.params['leads']: 62 | self.segments.append(Segment([(0, oa_back/4), (leadlen, oa_back/4)])) 63 | self.segments.append(Segment([(0, -oa_back/4), (leadlen, -oa_back/4)])) 64 | self.segments.append(Segment([(leadlen+oa_xlen, 0), (oa_xlen + 2*leadlen, 0)])) 65 | 66 | self.anchors['out'] = (oa_xlen+2*x, 0) 67 | self.anchors['in1'] = (0, oa_back/4) 68 | self.anchors['in2'] = (0, -oa_back/4) 69 | self.anchors['center'] = (x+oa_xlen/2, 0) 70 | self.anchors['vd'] = (x+oa_xlen/3, .84) 71 | self.anchors['vs'] = (x+oa_xlen/3, -.84) 72 | self.anchors['n1'] = (x+oa_xlen*2/3, -.42) 73 | self.anchors['n2'] = (x+oa_xlen*2/3, .42) 74 | self.anchors['n1a'] = (x+oa_xlen*.9, -.13) 75 | self.anchors['n2a'] = (x+oa_xlen*.9, .13) 76 | self.elmparams['drop'] = (2*x+oa_xlen, 0) 77 | -------------------------------------------------------------------------------- /schemdraw/elements/sources.py: -------------------------------------------------------------------------------- 1 | ''' Sources, meters, and lamp elements ''' 2 | 3 | from __future__ import annotations 4 | import math 5 | 6 | from .elements import Element2Term, gap 7 | from .twoterm import resheight 8 | from ..segments import Segment, SegmentCircle, SegmentText 9 | from .. import util 10 | 11 | 12 | class Source(Element2Term): 13 | ''' Generic source element ''' 14 | def __init__(self, **kwargs): 15 | super().__init__(**kwargs) 16 | self.segments.append(Segment([(0, 0), (0, 0), gap, (1, 0), (1, 0)])) 17 | self.segments.append(SegmentCircle((0.5, 0), 0.5,)) 18 | self.elmparams['theta'] = 90 19 | 20 | 21 | class SourceV(Source): 22 | ''' Voltage source ''' 23 | def __init__(self, **kwargs): 24 | super().__init__(**kwargs) 25 | plus_len = .2 26 | self.segments.append(Segment([(.25, -plus_len/2), 27 | (.25, plus_len/2)])) # '-' sign 28 | self.segments.append(Segment([(.75-plus_len/2, 0), 29 | (.75+plus_len/2, 0)])) # '+' sign 30 | self.segments.append(Segment([(.75, -plus_len/2), 31 | (.75, plus_len/2)])) # '+' sign 32 | 33 | 34 | class SourceI(Source): 35 | ''' Current source ''' 36 | def __init__(self, **kwargs): 37 | super().__init__(**kwargs) 38 | self.segments.append(Segment([(.25, 0), (.75, 0)], arrow='->')) 39 | 40 | 41 | class SourceSin(Source): 42 | ''' Source with sine ''' 43 | def __init__(self, **kwargs): 44 | super().__init__(**kwargs) 45 | sin_y = util.linspace(-.25, .25, num=25) 46 | sin_x = [.2 * math.sin((sy-.25)*math.pi*2/.5) + 0.5 for sy in sin_y] 47 | self.segments.append(Segment(list(zip(sin_x, sin_y)))) 48 | 49 | 50 | class SourcePulse(Source): 51 | ''' Pulse source ''' 52 | def __init__(self, **kwargs): 53 | super().__init__(**kwargs) 54 | sq = .15 55 | x = .4 56 | self.segments.append(Segment( 57 | [(x, sq*2), (x, sq), (x+sq, sq), (x+sq, -sq), 58 | (x, -sq), (x, -sq*2)])) 59 | 60 | 61 | class SourceTriangle(Source): 62 | ''' Triangle source ''' 63 | def __init__(self, **kwargs): 64 | super().__init__(**kwargs) 65 | self.segments.append(Segment([(.4, .25), (.7, 0), (.4, -.25)])) 66 | 67 | 68 | class SourceRamp(Source): 69 | ''' Ramp/sawtooth source ''' 70 | def __init__(self, **kwargs): 71 | super().__init__(**kwargs) 72 | self.segments.append(Segment([(.4, .25), (.8, -.2), (.4, -.2)])) 73 | 74 | 75 | class SourceSquare(Source): 76 | ''' Square wave source ''' 77 | def __init__(self, **kwargs): 78 | super().__init__(**kwargs) 79 | self.segments.append(Segment([(.5, .25), (.7, .25), (.7, 0), 80 | (.3, 0), (.3, -.25), (.5, -.25)])) 81 | 82 | 83 | class SourceControlled(Element2Term): 84 | ''' Generic controlled source ''' 85 | def __init__(self, **kwargs): 86 | super().__init__(**kwargs) 87 | self.segments.append(Segment([(0, 0), (.5, .5), (1, 0), 88 | (.5, -.5), (0, 0), gap, (1, 0)])) 89 | self.params['theta'] = 90 90 | 91 | 92 | class SourceControlledV(SourceControlled): 93 | ''' Controlled voltage source ''' 94 | def __init__(self, **kwargs): 95 | super().__init__(**kwargs) 96 | plus_len = .2 97 | self.segments.append(Segment([(.25, -plus_len/2), 98 | (.25, plus_len/2)])) # '-' sign 99 | self.segments.append(Segment([(.75-plus_len/2, 0), 100 | (.75+plus_len/2, 0)])) # '+' sign 101 | self.segments.append(Segment([(.75, -plus_len/2), 102 | (.75, plus_len/2)])) # '+' sign 103 | 104 | 105 | class SourceControlledI(SourceControlled): 106 | ''' Controlled current source ''' 107 | def __init__(self, **kwargs): 108 | super().__init__(**kwargs) 109 | self.segments.append(Segment([(.25, 0), (.75, 0)], arrow='->')) 110 | 111 | 112 | batw = resheight*.75 113 | bat1 = resheight*1.5 114 | bat2 = resheight*.75 115 | 116 | 117 | class BatteryCell(Element2Term): 118 | ''' Cell ''' 119 | def __init__(self, **kwargs): 120 | super().__init__(**kwargs) 121 | self.segments.append(Segment([(0, 0), gap, (batw, 0)])) 122 | self.segments.append(Segment([(0, bat1), (0, -bat1)])) 123 | self.segments.append(Segment([(batw, bat2), (batw, -bat2)])) 124 | 125 | 126 | class Battery(Element2Term): 127 | ''' Battery ''' 128 | def __init__(self, **kwargs): 129 | super().__init__(**kwargs) 130 | self.segments.append(Segment([(0, 0), gap, (batw*3, 0)])) 131 | self.segments.append(Segment([(0, bat1), (0, -bat1)])) 132 | self.segments.append(Segment([(batw, bat2), (batw, -bat2)])) 133 | self.segments.append(Segment([(batw*2, bat1), (batw*2, -bat1)])) 134 | self.segments.append(Segment([(batw*3, bat2), (batw*3, -bat2)])) 135 | 136 | 137 | class Solar(Source): 138 | ''' Solar source ''' 139 | def __init__(self, **kwargs): 140 | super().__init__(**kwargs) 141 | cellw = resheight*.5 142 | cellw2 = cellw + .15 143 | cellx = .4 144 | self.segments.append(Segment([(cellx, cellw), 145 | (cellx, -cellw)])) 146 | self.segments.append(Segment([(cellx+.2, cellw2), 147 | (cellx+.2, -cellw2)])) 148 | self.segments.append(Segment([(0, 0), (cellx, 0), gap, 149 | (cellx+.2, 0), (1, 0)])) 150 | self.segments.append(Segment([(1.1, .9), (.8, .6)], 151 | arrow='->', arrowwidth=.16, arrowlength=.2)) 152 | self.segments.append(Segment([(1.3, .7), (1, .4)], 153 | arrow='->', arrowwidth=.16, arrowlength=.2)) 154 | 155 | 156 | class MeterV(Source): 157 | ''' Volt meter ''' 158 | def __init__(self, **kwargs): 159 | super().__init__(**kwargs) 160 | self.segments.append(SegmentText((.5, 0), 'V')) 161 | 162 | 163 | class MeterI(Source): 164 | ''' Current Meter (I) ''' 165 | def __init__(self, **kwargs): 166 | super().__init__(**kwargs) 167 | self.segments.append(SegmentText((.5, 0), 'I')) 168 | 169 | 170 | class MeterA(Source): 171 | ''' Ammeter ''' 172 | def __init__(self, **kwargs): 173 | super().__init__(**kwargs) 174 | self.segments.append(SegmentText((.5, 0), 'A')) 175 | 176 | 177 | class MeterOhm(Source): 178 | ''' Ohm meter ''' 179 | def __init__(self, **kwargs): 180 | super().__init__(**kwargs) 181 | self.segments.append(SegmentText((.5, 0), r'$\Omega$')) 182 | 183 | 184 | class Lamp(Source): 185 | ''' Incandescent Lamp ''' 186 | def __init__(self, **kwargs): 187 | super().__init__(**kwargs) 188 | a = .25 189 | b = .7 190 | t = util.linspace(1.4, 3.6*math.pi, 100) 191 | x = [a*t0 - b*math.sin(t0) for t0 in t] 192 | y = [a - b * math.cos(t0) for t0 in t] 193 | x = [xx - x[0] for xx in x] # Scale to about the right size 194 | x = [xx / x[-1] for xx in x] 195 | y = [(yy - y[0]) * .25 for yy in y] 196 | self.segments.append(Segment(list(zip(x, y)))) 197 | 198 | 199 | class Lamp2(Source): 200 | ''' Incandescent Lamp (with X through a Source) ''' 201 | def __init__(self, **kwargs): 202 | super().__init__(**kwargs) 203 | r=0.5 204 | self.segments.append(Segment( 205 | [(r-r/2**.5, -r/2**.5), 206 | (r+r/2**.5, r/2**.5)])) 207 | self.segments.append(Segment( 208 | [(r-r/2**.5, r/2**.5), 209 | (r+r/2**.5, -r/2**.5)])) 210 | 211 | 212 | class Neon(Source): 213 | ''' Neon bulb ''' 214 | def __init__(self, **kwargs): 215 | super().__init__(**kwargs) 216 | cellw = resheight 217 | cellx = .4 218 | self.segments.append(Segment([(cellx, cellw), (cellx, -cellw)])) 219 | self.segments.append(Segment([(cellx+.2, cellw), (cellx+.2, -cellw)])) 220 | self.segments.append(Segment([(0, 0), (cellx, 0), gap, 221 | (cellx+.2, 0), (1, 0)])) 222 | self.segments.append(SegmentCircle((cellx-.15, .2), .05, fill=True)) 223 | -------------------------------------------------------------------------------- /schemdraw/elements/xform.py: -------------------------------------------------------------------------------- 1 | ''' Transformer element definitions ''' 2 | from __future__ import annotations 3 | from typing import Optional 4 | 5 | from ..segments import Segment, SegmentArc 6 | from .elements import Element 7 | from .twoterm import cycloid 8 | from ..types import XformTap 9 | 10 | 11 | class Transformer(Element): 12 | ''' Transformer 13 | 14 | Add taps to the windings on either side using 15 | the `.taps` method. 16 | 17 | Args: 18 | t1: Turns on primary (left) side 19 | t2: Turns on secondary (right) side 20 | core: Draw the core (parallel lines) [default: True] 21 | loop: Use spiral/cycloid (loopy) style [default: False] 22 | 23 | Anchors: 24 | * p1: primary side 1 25 | * p2: primary side 2 26 | * s1: secondary side 1 27 | * s2: secondary side 2 28 | * Other anchors defined by `taps` method 29 | ''' 30 | _element_defaults = { 31 | 'core': True, 32 | 'loop': False 33 | } 34 | def __init__(self, 35 | t1: int = 4, t2: int = 4, 36 | *, 37 | core: Optional[bool] = None, 38 | loop: Optional[bool] = None, 39 | **kwargs): 40 | super().__init__(**kwargs) 41 | ind_w = .4 42 | lbot = 0. 43 | ltop = t1*ind_w 44 | rtop = (ltop+lbot)/2 + t2*ind_w/2 45 | rbot = (ltop+lbot)/2 - t2*ind_w/2 46 | 47 | # Adjust for loops or core 48 | ind_gap = .75 49 | if self.params['loop']: 50 | ind_gap = ind_gap + .4 51 | if self.params['core']: 52 | ind_gap = ind_gap + .25 53 | 54 | ltapx = 0. 55 | rtapx = ind_gap 56 | 57 | # Draw coils 58 | if self.params['loop']: 59 | c1 = cycloid(loops=t1, ofst=(0, 0), norm=False, vertical=True) 60 | c2 = cycloid(loops=t2, ofst=(ind_gap, -rtop+ltop), norm=False, 61 | flip=True, vertical=True) 62 | ltapx = min([i[0] for i in c1]) 63 | rtapx = max([i[0] for i in c2]) 64 | ltop = c1[-1][1] 65 | rtop = c2[-1][1] 66 | self.segments.append(Segment(c1)) 67 | self.segments.append(Segment(c2)) 68 | else: 69 | for i in range(t1): 70 | self.segments.append(SegmentArc( 71 | (0, ltop-(i*ind_w+ind_w/2)), 72 | theta1=270, theta2=90, width=ind_w, height=ind_w)) 73 | for i in range(t2): 74 | self.segments.append(SegmentArc( 75 | (ind_gap, rtop-(i*ind_w+ind_w/2)), 76 | theta1=90, theta2=270, width=ind_w, height=ind_w)) 77 | # Add the core 78 | if self.params['core']: 79 | top = max(ltop, rtop) 80 | bot = min(lbot, rbot) 81 | center = ind_gap/2 82 | core_w = ind_gap/10 83 | self.segments.append(Segment( 84 | [(center-core_w, top), (center-core_w, bot)])) 85 | self.segments.append(Segment( 86 | [(center+core_w, top), (center+core_w, bot)])) 87 | 88 | self.anchors['p1'] = (0, ltop) 89 | self.anchors['p2'] = (0, lbot) 90 | self.anchors['s1'] = (ind_gap, rtop) 91 | self.anchors['s2'] = (ind_gap, rbot) 92 | 93 | self._ltapx = ltapx # Save these for adding taps 94 | self._rtapx = rtapx 95 | self._ltop = ltop 96 | self._rtop = rtop 97 | self._ind_w = ind_w 98 | 99 | if 'ltaps' in kwargs: 100 | for name, pos in kwargs['ltaps'].items(): 101 | self.tap(name, pos, 'primary') 102 | if 'rtaps' in kwargs: 103 | for name, pos in kwargs['rtaps'].items(): 104 | self.tap(name, pos, 'secondary') 105 | 106 | def tap(self, name: str, pos: int, side: XformTap = 'primary'): 107 | ''' Add a tap 108 | 109 | A tap is simply a named anchor definition along one side 110 | of the transformer. 111 | 112 | Args: 113 | name: Name of the tap/anchor 114 | pos: Turn number from the top of the tap 115 | side: Primary (left) or Secondary (right) side 116 | ''' 117 | if side in ['left', 'primary']: 118 | self.anchors[name] = (self._ltapx, self._ltop - pos * self._ind_w) 119 | elif side in ['right', 'secondary']: 120 | self.anchors[name] = (self._rtapx, self._rtop - pos * self._ind_w) 121 | else: 122 | raise ValueError(f'Undefined tap side {side}') 123 | return self 124 | -------------------------------------------------------------------------------- /schemdraw/flow/__init__.py: -------------------------------------------------------------------------------- 1 | from .flow import Box, RoundBox, Subroutine, Data, Start, Ellipse, Decision, Connect, Process, RoundProcess 2 | from .flow import Terminal, Circle, State, StateEnd 3 | from ..elements import Arrow, Arrowhead, Line, Dot, Wire, Arc2, Arc3, ArcZ, ArcN, ArcLoop 4 | 5 | __all__ = ['Box', 'RoundBox', 'Subroutine', 'Data', 'Start', 'Ellipse', 'Decision', 'Connect', 6 | 'Process', 'RoundProcess', 'Terminal', 'Circle', 'State', 'StateEnd', 'Arrow', 7 | 'Arrowhead', 'Line', 'Dot', 'Wire', 'Arc2', 'Arc3', 'ArcZ', 'ArcN', 'ArcLoop'] 8 | -------------------------------------------------------------------------------- /schemdraw/logic/__init__.py: -------------------------------------------------------------------------------- 1 | from .logic import And, Nand, Or, Nor, Xor, Xnor, Buf, Not, NotNot, Tristate, Tgate, Schmitt, SchmittNot, SchmittAnd, SchmittNand 2 | from .kmap import Kmap 3 | from .table import Table 4 | from .timing import TimingDiagram 5 | from ..elements import Arrow, Arrowhead, Dot, Line, Wire, Arc2, Arc3, ArcLoop 6 | 7 | __all__ = ['And', 'Nand', 'Or', 'Nor', 'Xor', 'Xnor', 'Buf', 'Not', 'NotNot', 'Tristate', 'Tgate', 'Schmitt', 'SchmittNot', 8 | 'SchmittAnd', 'SchmittNand', 'Kmap', 'Table', 'TimingDiagram', 'Arrow', 'Arrowhead', 'Dot', 'Line', 9 | 'Wire', 'Arc2', 'Arc3', 'ArcLoop'] 10 | -------------------------------------------------------------------------------- /schemdraw/logic/kmap.py: -------------------------------------------------------------------------------- 1 | ''' Karnaugh Map ''' 2 | from __future__ import annotations 3 | from typing import Optional, Sequence, Union 4 | import math 5 | 6 | from ..segments import Segment, SegmentText, SegmentPoly 7 | from ..elements import Element 8 | 9 | 10 | class Kmap(Element): 11 | ''' Karnaugh Map 12 | 13 | Draws a K-Map with 2, 3, or 4 variables. 14 | 15 | Args: 16 | names: 2, 3, or 4-character string defining names of 17 | the inputs 18 | truthtable: list defining values to display in each 19 | box of the K-Map. First element is string of 2, 3, 20 | or 4 logic 0's and 1's, and last element is the string 21 | to display for that input. Example: ('0000', '1') 22 | displays a '1' when all inputs are 0. 23 | groups: dictionary of style parameters for circling groups 24 | of inputs. Dictionary key must be same length as names, 25 | and defines which elements are circled using '0', '1', 26 | or '.' in each position. For example, '1...' circles 27 | every box where A=1, and '.11.' circles every box where 28 | both B and C are 1. Value of dictionary pair is another 29 | dictionary containing style of box (e.g. color, fill, lw, 30 | and ls). 31 | default: string to display in boxes that don't have a truthtable 32 | entry defined 33 | 34 | Anchors: 35 | * cellXXXX - Center of each cell in the grid, where X is 0 or 1 36 | ''' 37 | def __init__(self, 38 | names: str = 'ABCD', 39 | truthtable: Optional[Sequence[Sequence[Union[int, str]]]] = None, 40 | groups: Optional[dict] = None, 41 | default: str = '0', 42 | **kwargs): 43 | super().__init__(**kwargs) 44 | truthtable = [] if truthtable is None else truthtable 45 | groups = {} if groups is None else groups 46 | kwargs.setdefault('lw', 1.25) 47 | 48 | if len(names) == 4: 49 | rows = cols = 4 50 | elif len(names) == 3: 51 | rows, cols = 2, 4 52 | elif len(names) == 2: 53 | rows = cols = 2 54 | else: 55 | raise ValueError('Kmap requires 2 to 4 variable names') 56 | 57 | boxw = 1 58 | w = cols * boxw 59 | h = rows * boxw 60 | order = [0, 1, 3, 2] # 00, 01, 11, 10 61 | 62 | # Frame 63 | self.segments.append(Segment([(0, 0), (w, 0), (w, h), (0, h), (0, 0)], **kwargs)) 64 | for row in range(rows): 65 | self.segments.append(Segment([(0, row*boxw), (w, row*boxw)], **kwargs)) 66 | for col in range(cols): 67 | self.segments.append(Segment([(col*boxw, 0), (col*boxw, h)], **kwargs)) 68 | diag = boxw / math.sqrt(2) 69 | self.segments.append(Segment([(0, h), (-diag, h+diag)], **kwargs)) 70 | 71 | # Labels 72 | rowfmt = '02b' if rows > 2 else '01b' 73 | colfmt = '02b' if cols > 2 else '01b' 74 | topnames = names[:2] if len(names) > 2 else names[0] 75 | leftnames = names[2:] if len(names) > 2 else names[1] 76 | self.segments.append(SegmentText((0, h+3*diag/4), topnames, align=('right', 'bottom'), fontsize=12)) 77 | self.segments.append(SegmentText((-3*diag/4, h), leftnames, align=('right', 'bottom'), fontsize=12)) 78 | for i, col in enumerate(order[:cols]): 79 | self.segments.append(SegmentText((col*boxw+boxw/2, h+boxw/20), format(i, colfmt), 80 | align=('center', 'bottom'), fontsize=10)) 81 | for j, row in enumerate(order[:rows]): 82 | self.segments.append(SegmentText((-boxw/10, h-row*boxw-boxw/2), format(j, rowfmt), 83 | align=('right', 'center'), fontsize=10)) 84 | 85 | # Logic values 86 | ttable_in = [k[0] for k in truthtable] 87 | ttable_out = [k[1] for k in truthtable] 88 | for i, col in enumerate(order[:cols]): 89 | for j, row in enumerate(order[:rows]): 90 | invalue = ''.join([k for k in format(i, colfmt)] + [k for k in format(j, rowfmt)]) 91 | try: 92 | idx = ttable_in.index(invalue) 93 | except ValueError: 94 | valstr = default 95 | else: 96 | valstr = str(ttable_out[idx]) 97 | self.segments.append(SegmentText((col*boxw+boxw/2, h-row*boxw-boxw/2), valstr, 98 | align=('center', 'center'), fontsize=12)) 99 | self.anchors['cell'+''.join(str(k) for k in invalue)] = col*boxw+boxw/2, h-row*boxw-boxw/2 100 | 101 | # Group circles 102 | gpad = .1 103 | for group, style in groups.items(): 104 | tlen = len(topnames) 105 | llen = len(leftnames) 106 | gwidth = int(2**tlen / (2**(group[:tlen].count('0') + group[:tlen].count('1')))) 107 | gheight = int(2**llen / (2**(group[tlen:].count('0') + group[tlen:].count('1')))) 108 | rowlookup = {'0': 0, '1': 1, '.': 0, 109 | '..': 0, '00': 0, '01': 1, '10': 3, '11': 2, '.0': 3, '.1': 1, '0.': 0, '1.': 2} 110 | col = rowlookup.get(group[:tlen], 0) 111 | row = rowlookup.get(group[tlen:], 0) 112 | 113 | x1 = col*boxw 114 | x2 = x1 + gwidth*boxw 115 | y1 = h - row*boxw 116 | y2 = y1 - gheight*boxw 117 | verts = [] 118 | if col+gwidth <= cols and row+gheight <= rows: 119 | # No wrapping, just one rect 120 | verts = [[(x1+gpad, y1-gpad), (x2-gpad, y1-gpad), (x2-gpad, y2+gpad), (x1+gpad, y2+gpad)]] 121 | elif row+gheight <= rows: 122 | # Wrap left-right 123 | verts = [[(x1+gpad, y1-gpad), (x1+boxw+gpad, y1-gpad), (x1+boxw+gpad, y2+gpad), (x1+gpad, y2+gpad)], 124 | [(-gpad, y1-gpad), (boxw-gpad, y1-gpad), (boxw-gpad, y2+gpad), (-gpad, y2+gpad)]] 125 | elif col+gwidth <= cols: 126 | # Wrap top-bottom 127 | verts = [[(x1+gpad, y1-gpad), (x2-gpad, y1-gpad), (x2-gpad, y1-boxw-gpad), (x1+gpad, y1-boxw-gpad)], 128 | [(x1+gpad, h+gpad), (x2-gpad, h+gpad), (x2-gpad, h-boxw+gpad), (x1+gpad, h-boxw+gpad)]] 129 | else: 130 | # Wrap four corners 131 | verts = [[(x1+gpad, y1-gpad), (x1+boxw+gpad, y1-gpad), 132 | (x1+boxw+gpad, y1-boxw-gpad), (x1+gpad, y1-boxw-gpad)], 133 | [(-gpad, y1-gpad), (boxw-gpad, y1-gpad), (boxw-gpad, y1-boxw-gpad), 134 | (-gpad, y1-boxw-gpad)], 135 | [(-gpad, h+gpad), (boxw-gpad, h+gpad), (boxw-gpad, h-boxw+gpad), 136 | (-gpad, h-boxw+gpad)], 137 | [(x1+gpad, h+gpad), (x1+boxw+gpad, h+gpad), 138 | (x1+boxw+gpad, h-boxw+gpad), (x1+gpad, h-boxw+gpad)]] 139 | 140 | style.setdefault('lw', 1) 141 | for vert in verts: 142 | self.segments.append(SegmentPoly(vert, cornerradius=.25, **style)) 143 | -------------------------------------------------------------------------------- /schemdraw/logic/table.py: -------------------------------------------------------------------------------- 1 | ''' Markdown table. 2 | 3 | Put in the logic module since logic truth tables were the first 4 | use case for tables. 5 | ''' 6 | from __future__ import annotations 7 | 8 | import re 9 | 10 | from ..segments import Segment, SegmentText 11 | from ..elements import Element 12 | from ..backends import svg 13 | from typing import Optional 14 | 15 | 16 | def parse_colfmt(colfmt: str) -> tuple[str, str]: 17 | ''' Parse the column formatter string, using LaTeX style table formatter 18 | (e.g. "cc|c") 19 | 20 | Args: 21 | colfmt: column format string 22 | 23 | Returns: 24 | justifications: string of all justification characters, length = n 25 | bars: string of all separator characters, |, ǁ, or ., length = n+1 26 | ''' 27 | colfmt = re.sub(r'\|\|', 'ǁ', colfmt) 28 | while True: 29 | out = re.sub(r'([lcr])([lcr])', r'\1.\2', colfmt) 30 | if out == colfmt: 31 | break 32 | colfmt = out 33 | 34 | if not colfmt.startswith('|') or colfmt.startswith('ǁ'): 35 | colfmt = '.' + colfmt 36 | if not colfmt.endswith('|') or colfmt.endswith('ǁ'): 37 | colfmt += '.' 38 | 39 | bars = colfmt[::2] 40 | justs = colfmt[1::2] 41 | return justs, bars 42 | 43 | 44 | class Table(Element): 45 | r''' Table Element for drawing rudimentary Markdown formatted tables, such 46 | as logic truth tables. 47 | 48 | Args: 49 | table: Table definition, as markdown string. Columns separated by \|. 50 | Separator rows contain --- or === between column separators. 51 | colfmt: Justification and vertical separators to draw for each column, 52 | similar to LaTeX tabular environment parameter. Justification 53 | characters include 'c', 'r', and 'l' for center, left, and right 54 | justification. Separator characters may be '\|' for a single 55 | vertical bar, or '\|\|' or 'ǁ' for a double vertical bar, or omitted 56 | for no bar. Example: 'cc|c'. 57 | fontsize: Point size of table font 58 | font: Name of table font 59 | 60 | Example Table: 61 | \| A \| B \| Y \| 62 | \|---\|---\|---\| 63 | \| 0 \| 0 \| 1 \| 64 | \| 0 \| 1 \| 0 \| 65 | \| 1 \| 0 \| 0 \| 66 | \| 1 \| 1 \| 0 \| 67 | ''' 68 | def __init__(self, table: str, colfmt: Optional[str] = None, 69 | fontsize: float = 12, font: str = 'sans', **kwargs): 70 | super().__init__(**kwargs) 71 | self.table = table 72 | 73 | doublebarsep = .03 74 | rowpad = .3 75 | colpad = .4 76 | kwargs.setdefault('lw', 1) 77 | 78 | rows = self.table.strip().splitlines() 79 | rowfmt = '' 80 | for row in rows: 81 | if '---' in row: 82 | rowfmt += '|' 83 | elif '===' in row: 84 | rowfmt += 'ǁ' 85 | else: 86 | rowfmt += 'l' 87 | rowjusts, rowbars = parse_colfmt(rowfmt) 88 | 89 | if colfmt in [None, '']: 90 | colfmt = 'c' * len(rows[0].strip('| ').split('|')) 91 | coljusts, colbars = parse_colfmt(colfmt) # type: ignore 92 | 93 | ncols = len(coljusts) 94 | nrows = len(rowjusts) 95 | rows = [r for r in rows if '---' not in r and '===' not in r] 96 | if len(rows[0].strip('| ').split('|')) != ncols: 97 | raise ValueError('Number of columns in table does not match number of columns in colfmt string.') 98 | 99 | colwidths = [0.] * ncols 100 | rowheights = [0.] * nrows 101 | for k, row in enumerate(rows): 102 | cells = [c.strip() for c in row.strip('| ').split('|')] 103 | for i, cell in enumerate(cells): 104 | txtw, txth, _ = svg.text_size(cell, font=font, size=fontsize) 105 | colwidths[i] = max(colwidths[i], txtw/72*2+colpad) 106 | rowheights[k] = max(rowheights[k], txth/72*2+rowpad) 107 | 108 | totheight = sum(rowheights) 109 | totwidth = sum(colwidths) 110 | 111 | # Frame 112 | for i, colbar in enumerate(colbars): 113 | colx = sum(colwidths[:i]) 114 | if colbar == '|': 115 | self.segments.append(Segment([(colx, 0), (colx, -totheight)], **kwargs)) 116 | elif colbar == 'ǁ': 117 | self.segments.append(Segment([(colx-doublebarsep, 0), (colx-doublebarsep, -totheight)], **kwargs)) 118 | self.segments.append(Segment([(colx+doublebarsep, 0), (colx+doublebarsep, -totheight)], **kwargs)) 119 | for i, rowbar in enumerate(rowbars): 120 | rowy = -sum(rowheights[:i]) 121 | if rowbar == '|': 122 | self.segments.append(Segment([(0, rowy), (totwidth, rowy)], **kwargs)) 123 | elif rowbar == 'ǁ': 124 | self.segments.append(Segment([(0, rowy-doublebarsep), (totwidth, rowy-doublebarsep)], **kwargs)) 125 | self.segments.append(Segment([(0, rowy+doublebarsep), (totwidth, rowy+doublebarsep)], **kwargs)) 126 | 127 | # Text 128 | for r, row in enumerate(rows): 129 | cells = [c.strip() for c in row.strip('| ').split('|')] 130 | for c, cell in enumerate(cells): 131 | cellx = sum(colwidths[:c]) 132 | celly = -sum(rowheights[:r]) - rowheights[r] 133 | halign = {'c': 'center', 'l': 'left', 'r': 'right'}.get(coljusts[c]) 134 | if halign == 'center': 135 | cellx += colwidths[c]/2 136 | elif halign == 'right': 137 | cellx += colwidths[c] - colpad/2 138 | else: 139 | cellx += colpad/2 140 | self.segments.append( 141 | SegmentText((cellx, celly+rowpad/3), cell, font=font, fontsize=fontsize, 142 | align=(halign, 'bottom'))) # type: ignore 143 | -------------------------------------------------------------------------------- /schemdraw/parsing/__init__.py: -------------------------------------------------------------------------------- 1 | from .logic_parser import logicparse -------------------------------------------------------------------------------- /schemdraw/parsing/buchheim.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Layout of N-child Trees 3 | 4 | Source: Bill Mill 5 | https://llimllib.github.io/pymag-trees/ 6 | ''' 7 | 8 | class DrawTree(object): 9 | def __init__(self, tree, parent=None, depth=0, number=1): 10 | self.x = -1. 11 | self.y = depth 12 | self.tree = tree 13 | self.node = tree.node 14 | self.children = [DrawTree(c, self, depth+1, i+1) 15 | for i, c 16 | in enumerate(tree.children)] 17 | self.parent = parent 18 | self.thread = None 19 | self.mod = 0 20 | self.ancestor = self 21 | self.change = self.shift = 0 22 | self._lmost_sibling = None 23 | #this is the number of the node in its group of siblings 1..n 24 | self.number = number 25 | 26 | def left(self): 27 | return self.thread or len(self.children) and self.children[0] 28 | 29 | def right(self): 30 | return self.thread or len(self.children) and self.children[-1] 31 | 32 | def lbrother(self): 33 | n = None 34 | if self.parent: 35 | for node in self.parent.children: 36 | if node == self: return n 37 | else: n = node 38 | return n 39 | 40 | def get_lmost_sibling(self): 41 | if not self._lmost_sibling and self.parent and self != \ 42 | self.parent.children[0]: 43 | self._lmost_sibling = self.parent.children[0] 44 | return self._lmost_sibling 45 | lmost_sibling = property(get_lmost_sibling) 46 | 47 | def __str__(self): return "%s: x=%s mod=%s" % (self.tree, self.x, self.mod) 48 | def __repr__(self): return self.__str__() 49 | 50 | def buchheim(tree): 51 | dt = firstwalk(DrawTree(tree)) 52 | min = second_walk(dt) 53 | if min < 0: 54 | third_walk(dt, -min) 55 | return dt 56 | 57 | def third_walk(tree, n): 58 | tree.x += n 59 | for c in tree.children: 60 | third_walk(c, n) 61 | 62 | def firstwalk(v, distance=1.): 63 | if len(v.children) == 0: 64 | if v.lmost_sibling: 65 | v.x = v.lbrother().x + distance 66 | else: 67 | v.x = 0. 68 | else: 69 | default_ancestor = v.children[0] 70 | for w in v.children: 71 | firstwalk(w) 72 | default_ancestor = apportion(w, default_ancestor, distance) 73 | #print("finished v =", v.tree, "children") 74 | execute_shifts(v) 75 | 76 | midpoint = (v.children[0].x + v.children[-1].x) / 2 77 | 78 | ell = v.children[0] 79 | arr = v.children[-1] 80 | w = v.lbrother() 81 | if w: 82 | v.x = w.x + distance 83 | v.mod = v.x - midpoint 84 | else: 85 | v.x = midpoint 86 | return v 87 | 88 | def apportion(v, default_ancestor, distance): 89 | w = v.lbrother() 90 | if w is not None: 91 | #in buchheim notation: 92 | #i == inner; o == outer; r == right; l == left; r = +; l = - 93 | vir = vor = v 94 | vil = w 95 | vol = v.lmost_sibling 96 | sir = sor = v.mod 97 | sil = vil.mod 98 | sol = vol.mod 99 | while vil.right() and vir.left(): 100 | vil = vil.right() 101 | vir = vir.left() 102 | vol = vol.left() 103 | vor = vor.right() 104 | vor.ancestor = v 105 | shift = (vil.x + sil) - (vir.x + sir) + distance 106 | if shift > 0: 107 | move_subtree(ancestor(vil, v, default_ancestor), v, shift) 108 | sir = sir + shift 109 | sor = sor + shift 110 | sil += vil.mod 111 | sir += vir.mod 112 | sol += vol.mod 113 | sor += vor.mod 114 | if vil.right() and not vor.right(): 115 | vor.thread = vil.right() 116 | vor.mod += sil - sor 117 | else: 118 | if vir.left() and not vol.left(): 119 | vol.thread = vir.left() 120 | vol.mod += sir - sol 121 | default_ancestor = v 122 | return default_ancestor 123 | 124 | def move_subtree(wl, wr, shift): 125 | subtrees = wr.number - wl.number 126 | #print(wl.tree, "is conflicted with", wr.tree, 'moving', subtrees, 'shift', shift) 127 | #print wl, wr, wr.number, wl.number, shift, subtrees, shift/subtrees 128 | wr.change -= shift / subtrees 129 | wr.shift += shift 130 | wl.change += shift / subtrees 131 | wr.x += shift 132 | wr.mod += shift 133 | 134 | def execute_shifts(v): 135 | shift = change = 0 136 | for w in v.children[::-1]: 137 | #print("shift:", w, shift, w.change) 138 | w.x += shift 139 | w.mod += shift 140 | change += w.change 141 | shift += w.shift + change 142 | 143 | def ancestor(vil, v, default_ancestor): 144 | #the relevant text is at the bottom of page 7 of 145 | #"Improving Walker's Algorithm to Run in Linear Time" by Buchheim et al, (2002) 146 | #http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.16.8757&rep=rep1&type=pdf 147 | if vil.ancestor in v.parent.children: 148 | return vil.ancestor 149 | else: 150 | return default_ancestor 151 | 152 | def second_walk(v, m=0, depth=0, min=None): 153 | v.x += m 154 | v.y = depth 155 | 156 | if min is None or v.x < min: 157 | min = v.x 158 | 159 | for w in v.children: 160 | min = second_walk(w, m + v.mod, depth+1, min) 161 | 162 | return min -------------------------------------------------------------------------------- /schemdraw/parsing/logic_parser.py: -------------------------------------------------------------------------------- 1 | ''' Module for converting a logic string expression into a schemdraw.Drawing. 2 | 3 | Example: 4 | 5 | >>> logicparse("a and (b or c)") 6 | 7 | ''' 8 | from typing import Optional 9 | import pyparsing # type: ignore 10 | 11 | from .. import schemdraw 12 | from .. import logic 13 | from ..elements import RightLines 14 | from .buchheim import buchheim 15 | 16 | 17 | class LogicTree(): 18 | ''' Organize the logic gates into tree structure ''' 19 | def __init__(self, node, *children): 20 | self.node = node 21 | self.children = children if children else [] 22 | 23 | def __getitem__(self, key): 24 | if isinstance(key, (int, slice)): 25 | return self.children[key] 26 | 27 | def __iter__(self): 28 | return self.children.__iter__() 29 | 30 | def __len__(self): 31 | return len(self.children) 32 | 33 | 34 | def parse_string(logicstr): 35 | ''' Parse the logic string using pyparsing ''' 36 | and_ = pyparsing.Keyword('and') 37 | or_ = pyparsing.Keyword('or') 38 | nor_ = pyparsing.Keyword('nor') 39 | nand_ = pyparsing.Keyword('nand') 40 | xor_ = pyparsing.Keyword('xor') 41 | xnor_ = pyparsing.Keyword('xnor') 42 | not_ = pyparsing.Keyword('not') 43 | true_ = pyparsing.Keyword('true') 44 | false_ = pyparsing.Keyword('false') 45 | 46 | not_op = not_ | '~' | '¬' 47 | and_op = and_ | nand_ | '&' | '∧' 48 | xor_op = xor_ | xnor_ | '⊕' | '⊻' 49 | or_op = or_ | nor_ | '|' | '∨' | '+' 50 | 51 | expr = pyparsing.Forward() 52 | 53 | identifier = ~(and_ | or_ | nand_ | nor_ | not_ | true_ | false_) + \ 54 | pyparsing.Word('$' + pyparsing.alphas + '_', pyparsing.alphanums + '_' + '$') 55 | 56 | expr = pyparsing.infixNotation(true_ | false_ | identifier, 57 | [(not_op, 1, pyparsing.opAssoc.RIGHT), 58 | (and_op, 2, pyparsing.opAssoc.LEFT), 59 | (or_op, 2, pyparsing.opAssoc.LEFT), 60 | (xor_op, 2, pyparsing.opAssoc.LEFT)]) 61 | 62 | return expr.parseString(logicstr)[0] 63 | 64 | 65 | def to_tree(pres): 66 | ''' Convert the parsed logic expression into a LogicTree ''' 67 | invertfunc = False 68 | 69 | if pres[0] in ['not', '~', '¬']: 70 | if isinstance(pres[1], str): 71 | return LogicTree('not', to_tree(pres[1])) 72 | else: 73 | pres = pres[1] 74 | invertfunc = True 75 | 76 | if isinstance(pres, str): 77 | return LogicTree(pres) 78 | 79 | func = pres[1] 80 | inputs = pres[::2] 81 | 82 | func = {'&': 'and', '∧': 'and', 83 | '|': 'or', '∨': 'or', '+': 'or', 84 | '⊕': 'xor', '⊻': 'xor'}.get(func, func) 85 | 86 | if invertfunc: 87 | func = {'and': 'nand', 'or': 'nor', 'not': 'buf', 88 | 'nand': 'and', 'nor': 'or', 'buf': 'not', 89 | 'xor': 'xnor', 'xnor': 'xor'}.get(func) 90 | 91 | return LogicTree(func, *[to_tree(i) for i in inputs]) 92 | 93 | 94 | def drawlogic(tree, gateH=.7, gateW=2, outlabel=None): 95 | ''' Draw the LogicTree to a schemdraw Drawing 96 | 97 | Parameters 98 | ---------- 99 | tree: LogicTree 100 | The tree structure to draw 101 | gateH: float 102 | Height of one gate 103 | gateW: float 104 | Width of one gate 105 | outlabel: string 106 | Label for logic output 107 | 108 | Returns 109 | ------- 110 | schemdraw.Drawing 111 | ''' 112 | drawing = schemdraw.Drawing() 113 | drawing.unit = gateW # NOTs still use d.unit 114 | 115 | dtree = buchheim(tree) 116 | 117 | def drawit(root, depth=0, outlabel=None): 118 | ''' Recursive drawing function ''' 119 | elmdefs = {'and': logic.And, 120 | 'or': logic.Or, 121 | 'xor': logic.Xor, 122 | 'nand': logic.Nand, 123 | 'xnor': logic.Xnor, 124 | 'nor': logic.Nor, 125 | 'not': logic.Not} 126 | elm = elmdefs.get(root.node, logic.And) 127 | 128 | x = root.y * -gateW # buchheim draws vertical trees, so flip x-y. 129 | y = -root.x * gateH 130 | 131 | g = elm(d='r', at=(x, y), anchor='end', 132 | l=gateW, inputs=len(root.children)) 133 | if outlabel: 134 | g.label(outlabel, loc='end') 135 | 136 | for i, child in enumerate(root.children): 137 | anchorname = 'start' if elm in [logic.Not, logic.Buf] else f'in{i+1}' 138 | if child.node not in elmdefs: 139 | g.label(child.node, loc=anchorname) 140 | 141 | drawing.add(g) 142 | 143 | for i, child in enumerate(root.children): 144 | anchorname = 'start' if elm in [logic.Not, logic.Buf] else f'in{i+1}' 145 | if child.node in elmdefs: 146 | childelm = drawit(child, depth+1) # recursive 147 | drawing.add(RightLines(at=(g, anchorname), to=childelm.end)) 148 | return g 149 | 150 | drawit(dtree, outlabel=outlabel) 151 | return drawing 152 | 153 | 154 | def logicparse(expr: str, gateW: float = 2, gateH: float = .75, 155 | outlabel: Optional[str] = None) -> schemdraw.Drawing: 156 | ''' Parse a logic string expression and draw the gates in a schemdraw Drawing 157 | 158 | Logic expression is defined by string using 'and', 'or', 'not', etc. 159 | for example, "a or (b and c)". Parser recognizes several symbols and 160 | names for logic functions: 161 | [and, '&', '∧'] 162 | [or, '|', '∨', '+'] 163 | [xor, '⊕', '⊻'] 164 | [not, '~', '¬'] 165 | 166 | Args: 167 | expr: Logic expression 168 | gateH: Height of one gate 169 | gateW: Width of one gate 170 | outlabel: Label for logic output 171 | 172 | Returns: 173 | schemdraw.Drawing with logic tree 174 | ''' 175 | parsed = parse_string(expr) 176 | tree = to_tree(parsed) 177 | drawing = drawlogic(tree, gateH=gateH, gateW=gateW, outlabel=outlabel) 178 | return drawing 179 | -------------------------------------------------------------------------------- /schemdraw/pictorial/__init__.py: -------------------------------------------------------------------------------- 1 | from .pictorial import ( 2 | INCH, 3 | MILLIMETER, 4 | PINSPACING, 5 | ElementPictorial, 6 | Resistor, 7 | LED, 8 | LEDBlue, 9 | LEDGreen, 10 | LEDOrange, 11 | LEDYellow, 12 | LEDWhite, 13 | CapacitorElectrolytic, 14 | CapacitorMylar, 15 | CapacitorCeramic, 16 | Diode, 17 | TO92, 18 | DIP, 19 | Breadboard) 20 | from .fritz import FritzingPart, fritz_parts 21 | -------------------------------------------------------------------------------- /schemdraw/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdelker/schemdraw/41589490f0de3ae89aed1718242d985d57eeab76/schemdraw/py.typed -------------------------------------------------------------------------------- /schemdraw/style.py: -------------------------------------------------------------------------------- 1 | ''' Validate drawing style strings ''' 2 | from typing import Union 3 | import re 4 | 5 | from . import default_canvas 6 | 7 | 8 | # https://developer.mozilla.org/en-US/docs/Web/CSS/named-color 9 | NAMED_COLORS = [ 10 | 'black', 11 | 'silver', 12 | 'gray', 13 | 'white', 14 | 'maroon', 15 | 'red', 16 | 'purple', 17 | 'fuchsia', 18 | 'green', 19 | 'lime', 20 | 'olive', 21 | 'yellow', 22 | 'navy', 23 | 'blue', 24 | 'teal', 25 | 'aqua', 26 | 'aliceblue', 27 | 'antiquewhite', 28 | 'aqua', 29 | 'aquamarine', 30 | 'azure', 31 | 'beige', 32 | 'bisque', 33 | 'black', 34 | 'blanchedalmond', 35 | 'blue', 36 | 'blueviolet', 37 | 'brown', 38 | 'burlywood', 39 | 'cadetblue', 40 | 'chartreuse', 41 | 'chocolate', 42 | 'coral', 43 | 'cornflowerblue', 44 | 'cornsilk', 45 | 'crimson', 46 | 'cyan', 47 | 'darkblue', 48 | 'darkcyan', 49 | 'darkgoldenrod', 50 | 'darkgray', 51 | 'darkgreen', 52 | 'darkgrey', 53 | 'darkkhaki', 54 | 'darkmagenta', 55 | 'darkolivegreen', 56 | 'darkorange', 57 | 'darkorchid', 58 | 'darkred', 59 | 'darksalmon', 60 | 'darkseagreen', 61 | 'darkslateblue', 62 | 'darkslategray', 63 | 'darkslategrey', 64 | 'darkturquoise', 65 | 'darkviolet', 66 | 'deeppink', 67 | 'deepskyblue', 68 | 'dimgray', 69 | 'dimgrey', 70 | 'dodgerblue', 71 | 'firebrick', 72 | 'floralwhite', 73 | 'forestgreen', 74 | 'fuchsia', 75 | 'gainsboro', 76 | 'ghostwhite', 77 | 'gold', 78 | 'goldenrod', 79 | 'gray', 80 | 'green', 81 | 'greenyellow', 82 | 'grey', 83 | 'honeydew', 84 | 'hotpink', 85 | 'indianred', 86 | 'indigo', 87 | 'ivory', 88 | 'khaki', 89 | 'lavender', 90 | 'lavenderblush', 91 | 'lawngreen', 92 | 'lemonchiffon', 93 | 'lightblue', 94 | 'lightcoral', 95 | 'lightcyan', 96 | 'lightgoldenrodyellow', 97 | 'lightgray', 98 | 'lightgreen', 99 | 'lightgrey', 100 | 'lightpink', 101 | 'lightsalmon', 102 | 'lightseagreen', 103 | 'lightskyblue', 104 | 'lightslategray', 105 | 'lightslategrey', 106 | 'lightsteelblue', 107 | 'lightyellow', 108 | 'lime', 109 | 'limegreen', 110 | 'linen', 111 | 'magenta', 112 | 'maroon', 113 | 'mediumaquamarine', 114 | 'mediumblue', 115 | 'mediumorchid', 116 | 'mediumpurple', 117 | 'mediumseagreen', 118 | 'mediumslateblue', 119 | 'mediumspringgreen', 120 | 'mediumturquoise', 121 | 'mediumvioletred', 122 | 'midnightblue', 123 | 'mintcream', 124 | 'mistyrose', 125 | 'moccasin', 126 | 'navajowhite', 127 | 'navy', 128 | 'oldlace', 129 | 'olive', 130 | 'olivedrab', 131 | 'orange', 132 | 'orangered', 133 | 'orchid', 134 | 'palegoldenrod', 135 | 'palegreen', 136 | 'paleturquoise', 137 | 'palevioletred', 138 | 'papayawhip', 139 | 'peachpuff', 140 | 'peru', 141 | 'pink', 142 | 'plum', 143 | 'powderblue', 144 | 'purple', 145 | 'rebeccapurple', 146 | 'red', 147 | 'rosybrown', 148 | 'royalblue', 149 | 'saddlebrown', 150 | 'salmon', 151 | 'sandybrown', 152 | 'seagreen', 153 | 'seashell', 154 | 'sienna', 155 | 'silver', 156 | 'skyblue', 157 | 'slateblue', 158 | 'slategray', 159 | 'slategrey', 160 | 'snow', 161 | 'springgreen', 162 | 'steelblue', 163 | 'tan', 164 | 'teal', 165 | 'thistle', 166 | 'tomato', 167 | 'turquoise', 168 | 'violet', 169 | 'wheat', 170 | 'white', 171 | 'whitesmoke', 172 | 'yellow', 173 | 'yellowgreen' 174 | ] 175 | 176 | 177 | def color_hex(color: str) -> bool: 178 | ''' Is the color string a valid hex color? ''' 179 | match = re.match( 180 | r'#[A-Fa-f\d]{3}(?:[A-Fa-f\d]{3}|(?:[A-Fa-f\d]{5})?)\b', 181 | color, flags=re.IGNORECASE) 182 | return match is not None 183 | 184 | 185 | def color_rgb(color: str) -> bool: 186 | ''' Is the color string a valid rgb(...) color? ''' 187 | match = re.match( 188 | r'rgb(\s*)\((\s*)(?:\d*\.?\d*\%?)(\s*),(\s*)(?:\d*\.?\d*\%?)(\s*),(\s*)(?:\d*\.?\d*\%?)(\s*)\)', 189 | color, flags=re.IGNORECASE) 190 | return match is not None 191 | 192 | 193 | def color_rgba(color: str) -> bool: 194 | ''' Is the color string a valid rgba(...) color? ''' 195 | match = re.match( 196 | r'rgba(\s*)\((\s*)(?:\d*\.?\d*\%?)(\s*),(\s*)(?:\d*\.?\d*\%?)(\s*),(\s*)(?:\d*\.?\d*\%?)(\s*),(\s*)(?:\d*\.?\d*\%?)(\s*)\)', 197 | color, flags=re.IGNORECASE) 198 | return match is not None 199 | 200 | 201 | def color_hsl(color: str) -> bool: 202 | ''' Is the color string a valid hsl(...) color? ''' 203 | match = re.match( 204 | r'hsl(\s*)\((\s*)(?:-?\d*\.?\d*)(\s*),(\s*)(?:-?\d*\.?\d*\%)(\s*),(\s*)(?:-?\d*\.?\d*\%)(\s*)\)', 205 | color, flags=re.IGNORECASE) 206 | return match is not None 207 | 208 | 209 | def color_hsla(color: str) -> bool: 210 | ''' Is the color string a valid hsla(...) color? ''' 211 | match = re.match( 212 | r'hsla(\s*)\((\s*)(?:-?\d*\.?\d*)(\s*),(\s*)(?:-?\d*\.?\d*\%)(\s*),(\s*)(?:-?\d*\.?\d*\%)(\s*),(\s*)(?:-?\d*\.?\d*\%?)(\s*)\)', 213 | color, flags=re.IGNORECASE) 214 | return match is not None 215 | 216 | 217 | def dasharray(ls: str) -> bool: 218 | ''' Is the linestyle a valid dasharray? ''' 219 | match = re.match( 220 | r'(\d+\.?\d*)(,\s*\d+\.?\d*)*$', 221 | ls, flags=re.IGNORECASE) 222 | return match is not None 223 | 224 | 225 | def validate_color(color: Union[str, bool, tuple[int,int,int], None]) -> None: 226 | ''' Raise if not a valid CSS color ''' 227 | if color in [None, True, False]: 228 | return 229 | 230 | if isinstance(color, tuple): 231 | if (len(color) != 3 232 | or not isinstance(color[0], (int, float)) 233 | or not isinstance(color[1], (int, float)) 234 | or not isinstance(color[2], (int, float))): 235 | raise ValueError(f'Invalid color tuple {color}') 236 | return 237 | 238 | assert color is not None 239 | assert isinstance(color, str) 240 | 241 | if default_canvas.default_canvas == 'matplotlib': 242 | if (color not in NAMED_COLORS + ['bg'] 243 | and not color_hex(color)): 244 | raise ValueError(f'Invalid (matplotlib) color name {color}') 245 | 246 | elif (color not in NAMED_COLORS + ['bg'] 247 | and not color_hex(color) 248 | and not color_rgb(color) 249 | and not color_rgba(color) 250 | and not color_hsl(color) 251 | and not color_hsla(color)): 252 | raise ValueError(f'Invalid color name {color}') 253 | 254 | 255 | def validate_linestyle(ls: str) -> None: 256 | ''' Raise if ls is not a valid line style or dasharray ''' 257 | dashes = [None, '', ' ', '-', '--', ':', '-.', 'dashed', 'dotted', 'dashdot'] 258 | if ls not in dashes and not dasharray(ls): 259 | raise ValueError(f'Invalid line style {ls}') 260 | -------------------------------------------------------------------------------- /schemdraw/transform.py: -------------------------------------------------------------------------------- 1 | ''' Schemdraw transformations for converting local element definition to 2 | global position within the drawing 3 | ''' 4 | 5 | from __future__ import annotations 6 | from typing import Sequence 7 | 8 | from .util import Point 9 | from .types import XY 10 | 11 | 12 | class Transform: 13 | ''' Class defining transformation matrix 14 | 15 | Args: 16 | theta: Rotation angle in degrees 17 | globalshift: X-Y shift (applied after zoom and rotation) 18 | localshift: Local X-Y shift (applied before zoom and rotation) 19 | zoom: Zoom factor 20 | ''' 21 | def __init__(self, theta: float, globalshift: XY, 22 | localshift: XY = (0, 0), zoom: XY | float = Point((1, 1))): 23 | self.theta = theta 24 | self.shift = Point(globalshift) 25 | self.localshift = Point(localshift) 26 | if isinstance(zoom, (int, float)): 27 | zoom = Point((zoom, zoom)) 28 | self.zoom = zoom 29 | 30 | def __repr__(self): 31 | return f'Transform: xy={self.shift}; theta={self.theta}; scale={self.zoom}; lshift={self.localshift}' 32 | 33 | def transform(self, pt: XY) -> Point: 34 | ''' Apply the transform to the point 35 | 36 | Args: 37 | pt: Original (x, y) coordinates 38 | 39 | Returns: 40 | Transformed (x, y) coordinates 41 | ''' 42 | return ((Point(pt) + self.localshift) * self.zoom).rotate(self.theta) + self.shift 43 | 44 | def transform_array(self, pts: Sequence[XY]) -> list[Point]: 45 | ''' Apply the transform to multiple points 46 | 47 | Args: 48 | pts: List of (x,y) points to transform 49 | 50 | Returns: 51 | List of transformed (x, y) points 52 | ''' 53 | return [self.transform(pt) for pt in pts] 54 | -------------------------------------------------------------------------------- /schemdraw/types.py: -------------------------------------------------------------------------------- 1 | ''' Data types for schemdraw ''' 2 | from __future__ import annotations 3 | from typing import Union, Tuple, Optional, Literal 4 | from collections import namedtuple 5 | from enum import Enum, unique 6 | 7 | from .util import Point 8 | 9 | BBox = namedtuple('BBox', ['xmin', 'ymin', 'xmax', 'ymax']) 10 | 11 | # matplotlib uses 'projecting' to be the same as 'square' in svg. 12 | Capstyle = Literal['butt', 'round', 'square', 'projecting'] 13 | Joinstyle = Literal['bevel', 'miter', 'round'] 14 | Linestyle = Literal['-', ':', '--', '-.'] 15 | Direction = Union[Literal['up', 'down', 'left', 'right', 16 | 'u', 'd', 'l', 'r'], int] 17 | Halign = Literal['center', 'left', 'right'] 18 | Valign = Literal['center', 'top', 'bottom', 'base'] 19 | # Align = Tuple[Halign, Valign] 20 | Arcdirection = Literal['cw', 'ccw'] 21 | Side = Literal['top', 'bot', 'lft', 'rgt', 'bottom', 'left', 'right', 'L', 'R', 'T', 'B'] 22 | LabelLoc = Union[Side, str] 23 | XY = Union[Tuple[float, float], Point] 24 | RotationMode = Literal['anchor', 'default'] 25 | TextMode = Literal['path', 'text'] 26 | 27 | BilateralDirection = Literal['in', 'out'] 28 | EndRef = Literal['start', 'end'] 29 | ActionType = Optional[Literal['open', 'close']] 30 | HeaderStyle = Literal['round', 'square', 'screw'] 31 | HeaderNumbering = Literal['lr', 'ud', 'ccw'] 32 | XformTap = Literal['primary', 'secondary', 'left', 'right'] 33 | 34 | Backends = Literal['svg', 'matplotlib'] 35 | 36 | 37 | @unique 38 | class ImageFormat(str, Enum): 39 | ''' Known Matplotlib image formats ''' 40 | EPS = 'eps' 41 | JPG = 'jpg' 42 | PDF = 'pdf' 43 | PGF = 'pgf' 44 | PNG = 'png' 45 | PS = 'ps' 46 | RAW = 'raw' 47 | RGBA = 'rgba' 48 | SVG = 'svg' 49 | TIF = 'tif' 50 | 51 | 52 | ImageType = Literal['eps', 'jpg', 'pdf', 'pgf', 'png', 'ps', 53 | 'raw', 'rgba', 'svg', 'tif'] 54 | -------------------------------------------------------------------------------- /schemdraw/util.py: -------------------------------------------------------------------------------- 1 | ''' Utility functions for point geometry ''' 2 | from __future__ import annotations 3 | from typing import Union, Tuple 4 | 5 | import math 6 | from operator import mul 7 | from itertools import starmap 8 | 9 | XY = Union[Tuple[float, float], 'Point'] 10 | 11 | 12 | class Point(Tuple[float, float]): 13 | ''' An (x, y) tuple that can do math operations ''' 14 | @property 15 | def x(self) -> float: 16 | ''' X value of point ''' 17 | return self[0] 18 | 19 | @property 20 | def y(self) -> float: 21 | ''' Y value of point ''' 22 | return self[1] 23 | 24 | def __repr__(self): 25 | return f'Point({self.x},{self.y})' 26 | 27 | def __add__(self, a): 28 | try: 29 | return Point((self.x+a.x, self.y+a.y)) 30 | except AttributeError: 31 | return Point((self.x+a, self.y+a)) 32 | 33 | def __sub__(self, a): 34 | try: 35 | return Point((self.x-a.x, self.y-a.y)) 36 | except AttributeError: 37 | return Point((self.x-a, self.y-a)) 38 | 39 | def __rsub__(self, a): 40 | try: 41 | return Point((a.x-self.x, a.y-self.y)) 42 | except AttributeError: 43 | return Point((a-self.x, a-self.y)) 44 | 45 | def __mul__(self, a): 46 | try: 47 | return Point((a.x*self.x, a.y*self.y)) 48 | except AttributeError: 49 | return Point((a*self.x, a*self.y)) 50 | 51 | def __truediv__(self, a): 52 | try: 53 | return Point((self.x/a.x, self.y/a.y)) 54 | except AttributeError: 55 | return Point((self.x/a, self.y/a)) 56 | 57 | def __neg__(self): 58 | return Point((-self.x, -self.y)) 59 | 60 | __radd__ = __add__ 61 | __rmul__ = __mul__ 62 | 63 | def rotate(self, angle: float, center: XY = (0, 0)) -> 'Point': 64 | ''' Rotate the point by angle degrees about the center ''' 65 | return Point(rotate(self, angle, center=Point(center))) 66 | 67 | def mirrorx(self, centerx: float = 0) -> 'Point': 68 | ''' Mirror in x direction about the centerx point ''' 69 | return Point(mirrorx(self, centerx)) 70 | 71 | def flip(self) -> 'Point': 72 | ''' Flip the point vertically ''' 73 | return Point(flip(self)) 74 | 75 | 76 | def dot(a: XY, b: Tuple[Tuple[float, float], Tuple[float, float]]) -> Point: 77 | ''' Dot product of iterables a and b ''' 78 | return Point([sum(starmap(mul, zip(a, col))) for col in zip(*b)]) 79 | 80 | 81 | def linspace(start: float, stop: float, num: int = 50) -> list[float]: 82 | ''' List of evenly spaced numbers ''' 83 | step = (stop - start) / (num - 1) 84 | return [start+step*i for i in range(num)] 85 | 86 | 87 | def rotate(xy: XY, 88 | angle: float, 89 | center: XY = (0, 0)) -> Point: 90 | ''' Rotate the xy point by angle degrees ''' 91 | co = math.cos(math.radians(angle)) 92 | so = math.sin(math.radians(angle)) 93 | center = Point(center) 94 | m = ((co, so), (-so, co)) # rotation matrix 95 | b = Point(xy) - center 96 | b = dot(b, m) 97 | b = b + center 98 | return b 99 | 100 | 101 | def mirrorx(xy, centerx=0) -> Point: 102 | ''' Mirror the point horizontally ''' 103 | return Point((-(xy[0]-centerx)+centerx, xy[1])) 104 | 105 | 106 | def flip(xy: XY) -> Point: 107 | ''' Flip the point vertically ''' 108 | return Point((xy[0], -xy[1])) 109 | 110 | 111 | def delta(a: XY, b: XY) -> Point: 112 | ''' Delta between points a and b ''' 113 | return Point((b[0] - a[0], b[1] - a[1])) 114 | 115 | 116 | def angle(a: XY, b: XY) -> float: 117 | ''' Compute angle from point a to b ''' 118 | theta = math.degrees(math.atan2(b[1] - a[1], b[0] - a[0])) 119 | return theta 120 | 121 | 122 | def dist(a: XY, b: XY) -> float: 123 | ''' Get distance from point a to b. 124 | 125 | Same as math.dist in Python 3.8+. 126 | ''' 127 | try: 128 | # Python 3.8+ 129 | return math.dist(a, b) # type: ignore 130 | except AttributeError: 131 | return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2) 132 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = schemdraw 3 | version = attr: schemdraw.__version__ 4 | author = Collin J. Delker 5 | author_email = code@collindelker.com 6 | url = https://schemdraw.readthedocs.io 7 | description = Electrical circuit schematic drawing 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | keywords = circuit, schematic, electrical, flowchart, logic 11 | license = MIT 12 | project_urls = 13 | Documentation = https://schemdraw.readthedocs.io 14 | Source Code = https://github.com/cdelker/schemdraw 15 | classifiers = 16 | Development Status :: 4 - Beta 17 | Programming Language :: Python :: 3 18 | Programming Language :: Python :: 3.9 19 | Programming Language :: Python :: 3.10 20 | Programming Language :: Python :: 3.11 21 | Programming Language :: Python :: 3.12 22 | Programming Language :: Python :: 3.13 23 | License :: OSI Approved :: MIT License 24 | Operating System :: OS Independent 25 | Intended Audience :: Education 26 | Intended Audience :: Science/Research 27 | Intended Audience :: End Users/Desktop 28 | 29 | [options] 30 | packages = find: 31 | zip_safe = False 32 | python_requires = >= 3.9 33 | include_package_data = True 34 | 35 | [options.extras_require] 36 | matplotlib = matplotlib>=3.4 37 | svgmath = ziafont>=0.10; ziamath>=0.12; latex2mathml 38 | 39 | [options.package_data] 40 | schemdraw = py.typed 41 | -------------------------------------------------------------------------------- /test/ArduinoUNO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdelker/schemdraw/41589490f0de3ae89aed1718242d985d57eeab76/test/ArduinoUNO.png -------------------------------------------------------------------------------- /test/headless.py: -------------------------------------------------------------------------------- 1 | ''' Test for using schemdraw in a script WITHOUT interactive pyplot window ''' 2 | import schemdraw 3 | import schemdraw.elements as elm 4 | 5 | schemdraw.use('svg') 6 | 7 | with schemdraw.Drawing(file='testcircuit.svg', show=False) as d: 8 | d.add(elm.Resistor().label('1K')) 9 | d.add(elm.Capacitor().down()) 10 | print(d.get_imagedata('svg')) 11 | -------------------------------------------------------------------------------- /test/schematic.py: -------------------------------------------------------------------------------- 1 | ''' Test for using schemdraw in a script with interactive pyplot window ''' 2 | import schemdraw 3 | import schemdraw.elements as elm 4 | 5 | import matplotlib.pyplot as plt 6 | 7 | with schemdraw.Drawing(file='cap.svg') as d: 8 | d.add(elm.Resistor().label('1K')) 9 | d.add(elm.Capacitor().down()) 10 | 11 | with schemdraw.Drawing(file='res.svg', transparent=True) as d2: 12 | d2.add(elm.Diode().fill(True)) 13 | 14 | fig, ax = plt.subplots() 15 | ax.plot([0, 1, 2, 3], [1, 2, 1, 0], marker='o', ls='') 16 | with schemdraw.Drawing(canvas=ax) as d3: 17 | d3 += elm.Resistor().label('1M') 18 | plt.show() 19 | --------------------------------------------------------------------------------