├── kicad ├── __init__.py ├── util │ ├── __init__.py │ └── point.py ├── primitives │ ├── __init__.py │ └── polygon.py ├── scripting │ ├── __init__.py │ ├── parameter.py │ └── footprint_wizard.py ├── _native.py ├── pcbnew │ ├── zone.py │ ├── __init__.py │ ├── net.py │ ├── pcbtarget.py │ ├── pad.py │ ├── via.py │ ├── track.py │ ├── dimension.py │ ├── text.py │ ├── module.py │ ├── layer.py │ ├── boarditem.py │ ├── board.py │ └── drawsegment.py └── plotter.py ├── tests ├── __init__.py ├── util │ ├── __init__.py │ └── test_point.py └── pcbnew │ ├── __init__.py │ ├── testproject │ ├── sym-lib-table │ ├── testproject.pro │ └── testproject.sch │ └── test_board.py ├── setup.cfg ├── docs ├── kicad │ ├── util │ │ ├── point.rst │ │ └── index.rst │ ├── pcbnew │ │ ├── pad.rst │ │ ├── via.rst │ │ ├── text.rst │ │ ├── zone.rst │ │ ├── track.rst │ │ ├── module.rst │ │ ├── net.rst │ │ ├── board.rst │ │ ├── dimension.rst │ │ ├── pcbtarget.rst │ │ ├── layer.rst │ │ ├── index.rst │ │ └── drawsegment.rst │ ├── plotter.rst │ └── primitives │ │ ├── index.rst │ │ └── polygon.rst ├── Makefile ├── examples │ └── list_pcb.rst ├── make.bat ├── index.rst └── conf.py ├── examples ├── footprint_wizard │ ├── definitions.yaml │ └── simple_footprint_wizard.py ├── list_pcb.py ├── pad_resizer.py └── pcbnew_diff │ └── pcbnew_diff.py ├── README.md ├── setup.py ├── .travis.yml ├── .gitignore └── LICENSE /kicad/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/pcbnew/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length=120 3 | -------------------------------------------------------------------------------- /tests/pcbnew/testproject/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | ) 3 | -------------------------------------------------------------------------------- /docs/kicad/util/point.rst: -------------------------------------------------------------------------------- 1 | Point2D 2 | ======= 3 | 4 | .. automodule:: kicad.util.point 5 | 6 | .. autoclass:: kicad.util.Point2D 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/pad.rst: -------------------------------------------------------------------------------- 1 | Pad 2 | === 3 | 4 | .. automodule:: kicad.pcbnew.pad 5 | 6 | .. autoclass:: kicad.pcbnew.Pad 7 | :members: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/via.rst: -------------------------------------------------------------------------------- 1 | Via 2 | === 3 | 4 | .. automodule:: kicad.pcbnew.via 5 | 6 | .. autoclass:: kicad.pcbnew.Via 7 | :members: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/kicad/plotter.rst: -------------------------------------------------------------------------------- 1 | kicad.plotter 2 | ============= 3 | 4 | .. automodule:: kicad.plotter 5 | 6 | .. autoclass:: kicad.plotter.Plotter 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/text.rst: -------------------------------------------------------------------------------- 1 | Text 2 | ==== 3 | 4 | .. automodule:: kicad.pcbnew.text 5 | 6 | .. autoclass:: kicad.pcbnew.Text 7 | :members: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/zone.rst: -------------------------------------------------------------------------------- 1 | Zone 2 | ==== 3 | 4 | .. automodule:: kicad.pcbnew.zone 5 | 6 | .. autoclass:: kicad.pcbnew.Zone 7 | :members: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/track.rst: -------------------------------------------------------------------------------- 1 | Track 2 | ===== 3 | 4 | .. automodule:: kicad.pcbnew.track 5 | 6 | .. autoclass:: kicad.pcbnew.Track 7 | :members: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/kicad/util/index.rst: -------------------------------------------------------------------------------- 1 | kicad.util 2 | ========== 3 | 4 | .. automodule:: kicad.util 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | point 11 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/module.rst: -------------------------------------------------------------------------------- 1 | Module 2 | ====== 3 | 4 | .. automodule:: kicad.pcbnew.module 5 | 6 | .. autoclass:: kicad.pcbnew.Module 7 | :members: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/net.rst: -------------------------------------------------------------------------------- 1 | Net 2 | === 3 | 4 | .. automodule:: kicad.pcbnew.net 5 | 6 | .. autoclass:: kicad.pcbnew.Net 7 | :members: 8 | :inherited-members: 9 | 10 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/board.rst: -------------------------------------------------------------------------------- 1 | Board 2 | ===== 3 | 4 | .. automodule:: kicad.pcbnew.board 5 | 6 | .. autoclass:: kicad.pcbnew.Board 7 | :members: 8 | :inherited-members: 9 | 10 | -------------------------------------------------------------------------------- /docs/kicad/primitives/index.rst: -------------------------------------------------------------------------------- 1 | kicad.primitives 2 | ================ 3 | 4 | .. automodule:: kicad.primitives 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | polygon 11 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/dimension.rst: -------------------------------------------------------------------------------- 1 | Dimension 2 | ========= 3 | 4 | .. automodule:: kicad.pcbnew.dimension 5 | 6 | .. autoclass:: kicad.pcbnew.Dimension 7 | :members: 8 | :inherited-members: 9 | 10 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/pcbtarget.rst: -------------------------------------------------------------------------------- 1 | PcbTarget 2 | ========= 3 | 4 | .. automodule:: kicad.pcbnew.pcbtarget 5 | 6 | .. autoclass:: kicad.pcbnew.PcbTarget 7 | :members: 8 | :inherited-members: 9 | 10 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/layer.rst: -------------------------------------------------------------------------------- 1 | Layer 2 | ===== 3 | 4 | .. automodule:: kicad.pcbnew.layer 5 | 6 | .. autoclass:: kicad.pcbnew.Layer 7 | :members: 8 | :inherited-members: 9 | 10 | .. autoclass:: kicad.pcbnew.LayerSet 11 | :members: 12 | -------------------------------------------------------------------------------- /docs/kicad/primitives/polygon.rst: -------------------------------------------------------------------------------- 1 | Polygon 2 | ======= 3 | 4 | .. automodule:: kicad.primitives.polygon 5 | 6 | .. autoclass:: kicad.primitives.Polygon 7 | :members: 8 | 9 | .. autoclass:: kicad.primitives.PolygonSet 10 | :members: 11 | -------------------------------------------------------------------------------- /examples/footprint_wizard/definitions.yaml: -------------------------------------------------------------------------------- 1 | - name: "test_1" 2 | width: 1 3 | height: 2 4 | pad_width: 0.2 5 | pad_height: 0.5 6 | pad_spacing: 0.5 7 | 8 | - name: "test_2" 9 | width: 2 10 | height: 4 11 | pad_width: 0.6 12 | pad_height: 0.7 13 | pad_spacing: 0.4 -------------------------------------------------------------------------------- /docs/kicad/pcbnew/index.rst: -------------------------------------------------------------------------------- 1 | kicad.pcbnew 2 | ============ 3 | 4 | .. automodule:: kicad.pcbnew 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | board 11 | dimension 12 | drawsegment 13 | layer 14 | module 15 | net 16 | pad 17 | pcbtarget 18 | text 19 | track 20 | via 21 | zone 22 | -------------------------------------------------------------------------------- /docs/kicad/pcbnew/drawsegment.rst: -------------------------------------------------------------------------------- 1 | Drawsegment 2 | =========== 3 | 4 | .. automodule:: kicad.pcbnew.drawsegment 5 | 6 | .. autoclass:: kicad.pcbnew.Drawsegment 7 | :members: 8 | :inherited-members: 9 | 10 | Arc 11 | === 12 | 13 | .. autoclass:: kicad.pcbnew.Arc 14 | :members: 15 | :inherited-members: 16 | 17 | Circle 18 | ====== 19 | 20 | .. autoclass:: kicad.pcbnew.Circle 21 | :members: 22 | :inherited-members: 23 | 24 | Line 25 | ==== 26 | 27 | .. autoclass:: kicad.pcbnew.Line 28 | :members: 29 | :inherited-members: 30 | 31 | Polygon 32 | ======= 33 | 34 | .. autoclass:: kicad.pcbnew.Polygon 35 | :members: 36 | :inherited-members: 37 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build2 7 | SPHINXPROJ = KiCad 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/examples/list_pcb.rst: -------------------------------------------------------------------------------- 1 | List PCB Entities 2 | ================= 3 | 4 | This examples is a reimplementation of the `listPcb.py `_ 5 | script found in the official KiCad repository. It basically loads a board and then prints a short representation of all vias, tracks, drawings modules and zones. 6 | 7 | 8 | .. literalinclude:: ./../../examples/list_pcb.py 9 | 10 | This script can now simply be executed from the commandline, and outputs some nice 11 | informations about the board file: 12 | 13 | .. code-block:: bash 14 | 15 | $ python ./examples/list_pcb.py ./tests/pcbnew/testproject/testproject.kicad_pcb 16 | -------------------------------------------------------------------------------- /kicad/util/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.util.point import Point2D # noqa: F401 17 | -------------------------------------------------------------------------------- /kicad/primitives/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.primitives.polygon import Polygon, PolygonSet # noqa: F401 17 | -------------------------------------------------------------------------------- /tests/pcbnew/testproject/testproject.pro: -------------------------------------------------------------------------------- 1 | update=22/05/2015 07:44:53 2 | version=1 3 | last_client=kicad 4 | [general] 5 | version=1 6 | RootSch= 7 | BoardNm= 8 | [pcbnew] 9 | version=1 10 | LastNetListRead= 11 | UseCmpFile=1 12 | PadDrill=0.600000000000 13 | PadDrillOvalY=0.600000000000 14 | PadSizeH=1.500000000000 15 | PadSizeV=1.500000000000 16 | PcbTextSizeV=1.500000000000 17 | PcbTextSizeH=1.500000000000 18 | PcbTextThickness=0.300000000000 19 | ModuleTextSizeV=1.000000000000 20 | ModuleTextSizeH=1.000000000000 21 | ModuleTextSizeThickness=0.150000000000 22 | SolderMaskClearance=0.000000000000 23 | SolderMaskMinWidth=0.000000000000 24 | DrawSegmentWidth=0.200000000000 25 | BoardOutlineThickness=0.100000000000 26 | ModuleOutlineThickness=0.150000000000 27 | [cvpcb] 28 | version=1 29 | NetIExt=net 30 | [eeschema] 31 | version=1 32 | LibDir= 33 | [eeschema/libraries] 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kicad-python 2 | 3 | Abstraction layer for the KiCad python interface. 4 | 5 | Be aware this is in initial development and the interface can change anytime! 6 | 7 | Inspired by https://github.com/pierstitus/kicad-python 8 | 9 | [![Build Status](https://travis-ci.org/pointhi/kicad-python.svg?branch=master)](https://travis-ci.org/pointhi/kicad-python) 10 | [![Documentation Status](https://readthedocs.org/projects/kicad-python-python/badge/?version=latest)](http://kicad-python-python.readthedocs.io/en/latest/?badge=latest) 11 | ![Python 2.7](https://img.shields.io/badge/python-2.7-blue.svg) 12 | 13 | ## Example usage 14 | 15 | ```python 16 | from kicad.pcbnew import Board 17 | 18 | b = Board.from_file('path/to/file.kicad_pcb') 19 | 20 | for module in b.modules: 21 | print(module) 22 | ``` 23 | 24 | ## Run Tests 25 | 26 | ```bash 27 | ./setup.py test 28 | ``` -------------------------------------------------------------------------------- /kicad/scripting/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.scripting.footprint_wizard import FootprintWizard, register_footprint_wizard # noqa: F401 17 | -------------------------------------------------------------------------------- /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 | set SPHINXPROJ=kicad 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import os 4 | import sys 5 | 6 | from setuptools import setup 7 | from setuptools import find_packages 8 | 9 | long_description = open(os.path.join(sys.path[0], 'README.md')).read() 10 | 11 | setup( 12 | name='kicad-python', 13 | version='0.0.2', 14 | author='Thomas Pointhuber', 15 | author_email='thomas.pointhuber@gmx.at', 16 | url='https://github.com/pointhi/kicad-python', 17 | description="unofficial abstraction layer for the KiCad API", 18 | long_description=long_description, 19 | long_description_content_type='text/markdown', 20 | license="GPL3+", 21 | 22 | install_requires=[], 23 | packages=find_packages('.', exclude=['tests*']), 24 | test_suite='tests', 25 | 26 | classifiers=[ 27 | 'Development Status :: 2 - Pre-Alpha', 28 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 29 | 'Programming Language :: Python :: 2 :: Only', # not because of our implementation, but because of KiCad 30 | 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)' 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. KiCad documentation master file, created by 2 | sphinx-quickstart on Wed Feb 7 17:26:12 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to KiCad's Python API documentation! 7 | ============================================ 8 | 9 | This is the initial proposal for a high level `KiCad `_. Python API. 10 | 11 | Main goals are: 12 | 13 | * easy to understand 14 | * everything is documented and tested 15 | * stable 16 | 17 | Please note this in initial development. Everything can change, and this is at the moment an inofficial realisation 18 | of this idea (thus unstable 3rd-party software). 19 | 20 | .. toctree:: 21 | :caption: User Documentation 22 | :maxdepth: 2 23 | 24 | kicad/pcbnew/index 25 | kicad/primitives/index 26 | kicad/util/index 27 | kicad/plotter 28 | 29 | .. toctree:: 30 | :caption: Examples 31 | :maxdepth: 2 32 | 33 | examples/list_pcb 34 | 35 | Indices and tables 36 | ================== 37 | 38 | * :ref:`genindex` 39 | * :ref:`modindex` 40 | * :ref:`search` 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | cache: 4 | - pip 5 | - directories: 6 | - $HOME/.cache/pip 7 | addons: 8 | apt: 9 | packages: 10 | - python-flake8 11 | - python3-pip 12 | language: python 13 | python: 14 | - "2.7" 15 | before_install: 16 | # we need KiCad for testing 17 | #- sudo add-apt-repository --yes ppa:js-reynaud/kicad-4 -y 18 | - sudo add-apt-repository --yes ppa:js-reynaud/kicad-dev-nightly -y 19 | - sudo apt update -q 20 | - sudo apt install --no-install-recommends kicad -y 21 | install: 22 | - pip install sphinx sphinx_rtd_theme 23 | - sudo pip3 install mypy 24 | script: 25 | # otherwise our scripts do not find pcbnew 26 | - export PYTHONPATH=/usr/lib/python2.7/dist-packages 27 | 28 | # test code formatting 29 | - flake8 30 | 31 | # static type checking 32 | - python3 -m mypy --py2 kicad examples # tests 33 | 34 | # run unittests 35 | - ./setup.py test 36 | 37 | # test sphinx documentation 38 | - (cd docs && mkdir -p _static) # otherwise `make html` will return an error 39 | - (cd docs && make SPHINXBUILD=sphinx-build SPHINXOPTS=-W html) 40 | - (cd docs && make SPHINXBUILD=sphinx-build SPHINXOPTS=-W doctest) 41 | - (cd docs && make SPHINXBUILD=sphinx-build SPHINXOPTS=-W linkcheck) 42 | -------------------------------------------------------------------------------- /kicad/_native.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | import sys 17 | 18 | try: 19 | _pcbnew = __import__('pcbnew') # We need to import the pcbnew module this way 20 | except ImportError as e: 21 | if 'sphinx' not in sys.modules: 22 | raise e 23 | else: 24 | class PcbnewDummy(object): 25 | PCB_LAYER_ID_COUNT = 0 26 | PLOT_FORMAT_HPGL = None 27 | PLOT_FORMAT_GERBER = None 28 | PLOT_FORMAT_POST = None 29 | PLOT_FORMAT_DXF = None 30 | PLOT_FORMAT_PDF = None 31 | PLOT_FORMAT_SVG = None 32 | 33 | _pcbnew = PcbnewDummy() 34 | -------------------------------------------------------------------------------- /examples/footprint_wizard/simple_footprint_wizard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from kicad.scripting import FootprintWizard, register_footprint_wizard 4 | 5 | 6 | class ExampleWizard(FootprintWizard): 7 | def __init__(self): 8 | super(ExampleWizard, self).__init__() # TODO: simpler design without super on current classname? 9 | 10 | self.name = 'Example Wizard' 11 | self.description = 'Example Wizard description' 12 | self.value = 'some_value' # TODO: should be set in build_footprint 13 | 14 | def generate_parameter_list(self, params): 15 | params.add_parameter("Basic", "name", str, "Example_Footprint") # TODO: float -> distance in mm 16 | params.add_parameter("Package", "width", float, 1) # TODO: float -> distance in mm 17 | params.add_parameter("Package", "height", float, 2, designator='D') 18 | params.add_parameter("Package", "pad_width", float, 0.5) 19 | params.add_parameter("Package", "pad_height", float, 1) 20 | params.add_parameter("Package", "pad_spacing", float, 0.25, minValue=0.2) 21 | 22 | def build_footprint(self, module, params): # TODO: create module in this method? 23 | # TODO: create some footprint 24 | return module 25 | 26 | 27 | wizard = register_footprint_wizard(ExampleWizard) 28 | if __name__ == '__main__': 29 | wizard.run_as_script() 30 | -------------------------------------------------------------------------------- /examples/list_pcb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import argparse 6 | 7 | from kicad.pcbnew import Board 8 | from kicad.pcbnew import Text 9 | 10 | 11 | def list_pcb(board): 12 | print() 13 | print("LIST VIAS:") 14 | for via in board.vias: 15 | print(" * Via: {} - {}/{}".format(via.position, via.drill, via.width)) 16 | 17 | print() 18 | print("LIST TRACKS:") 19 | for track in board.tracks: 20 | print(" * Track: {} to {}, width {}".format(track.start, track.end, track.width)) 21 | 22 | print() 23 | print("LIST DRAWINGS:") 24 | for drawing in board.drawings: 25 | if type(drawing) is Text: 26 | print("* Text: '{}' at {}".format(drawing.text, drawing.position)) 27 | else: 28 | print("* Drawing: {}".format(drawing)) 29 | 30 | print() 31 | print("LIST MODULES:") 32 | for module in board.modules: 33 | print("* Module: {} at {}".format(module.reference, module.position)) 34 | 35 | print() 36 | print("LIST ZONES:") 37 | for zone in board.zones: 38 | print("* Zone: '{}' with priority {}".format(zone.net.name, zone.priority)) 39 | 40 | 41 | # reimplementation of pcbnew/python/examples/listPcb.py script using our abstraction layer 42 | if __name__ == "__main__": 43 | parser = argparse.ArgumentParser() 44 | 45 | parser.add_argument('board', help='board file to list elements', action='store') 46 | 47 | args = parser.parse_args() 48 | 49 | board = Board.from_file(args.board) 50 | 51 | list_pcb(board) 52 | -------------------------------------------------------------------------------- /kicad/pcbnew/zone.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.net import Net 18 | 19 | from kicad._native import _pcbnew 20 | 21 | 22 | class Zone(BoardItem): 23 | def __init__(self, zone): 24 | assert isinstance(zone, _pcbnew.ZONE_CONTAINER) 25 | super(Zone, self).__init__(zone) 26 | 27 | def get_native(self): 28 | """Get native object from the low level API 29 | 30 | :return: :class:`pcbnew.ZONE` 31 | """ 32 | return self._obj 33 | 34 | @property 35 | def priority(self): 36 | """Priority of the Zone 37 | 38 | :return: ``int`` 39 | """ 40 | return self._obj.GetPriority() 41 | 42 | @priority.setter 43 | def priority(self, priority): 44 | self._obj.SetPriority(priority) 45 | 46 | @property 47 | def net(self): 48 | """Net of the Zone 49 | 50 | :return: :class:`kicad.pcbnew.Net` 51 | """ 52 | return Net(self._obj.GetNet()) 53 | 54 | def __repr__(self): 55 | return "kicad.pcbnew.Zone({})".format(self._obj) 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | .static_storage/ 57 | .media/ 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | # IDE excludes 108 | .idea/ 109 | -------------------------------------------------------------------------------- /kicad/pcbnew/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.board import Board # noqa: F401 17 | 18 | from kicad.pcbnew.dimension import Dimension # noqa: F401 19 | 20 | from kicad.pcbnew.drawsegment import Drawsegment # noqa: F401 21 | from kicad.pcbnew.drawsegment import Arc # noqa: F401 22 | from kicad.pcbnew.drawsegment import Circle # noqa: F401 23 | from kicad.pcbnew.drawsegment import Line # noqa: F401 24 | from kicad.pcbnew.drawsegment import Polygon # noqa: F401 25 | 26 | from kicad.pcbnew.layer import Layer, LayerSet # noqa: F401 27 | 28 | from kicad.pcbnew.module import Module # noqa: F401 29 | 30 | from kicad.pcbnew.net import Net # noqa: F401 31 | 32 | from kicad.pcbnew.pad import Pad # noqa: F401 33 | 34 | from kicad.pcbnew.pcbtarget import PcbTarget # noqa: F401 35 | 36 | from kicad.pcbnew.text import Text # noqa: F401 37 | 38 | from kicad.pcbnew.track import Track # noqa: F401 39 | 40 | from kicad.pcbnew.via import Via # noqa: F401 41 | 42 | from kicad.pcbnew.zone import Zone # noqa: F401 43 | -------------------------------------------------------------------------------- /kicad/pcbnew/net.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad._native import _pcbnew 17 | 18 | 19 | class Net(object): 20 | def __init__(self, netinfo): 21 | assert isinstance(netinfo, _pcbnew.NETINFO_ITEM) 22 | self._obj = netinfo 23 | 24 | def get_native(self): 25 | """Get native object from the low level API 26 | 27 | :return: :class:`pcbnew.NETINFO_ITEM` 28 | """ 29 | return self._obj 30 | 31 | @property 32 | def name(self): 33 | """Name of Net 34 | 35 | :return: ``unicode`` 36 | """ 37 | return self._obj.GetNetname() 38 | 39 | def __eq__(self, other): 40 | if not isinstance(self, other.__class__): 41 | return False 42 | 43 | if not isinstance(self._obj, other._obj.__class__): 44 | return False 45 | 46 | if self._obj == other._obj: 47 | return True 48 | 49 | return self.name == other.name 50 | 51 | def __ne__(self, other): 52 | return not self.__eq__(other) 53 | 54 | def __hash__(self): 55 | return hash(self.name) 56 | 57 | def __repr__(self): 58 | return "kicad.pcbnew.Net({})".format(self._obj) 59 | 60 | def __str__(self): 61 | return "kicad.pcbnew.Net(\"{}\")".format(self.name) 62 | -------------------------------------------------------------------------------- /kicad/pcbnew/pcbtarget.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | 18 | from kicad.util.point import Point2D 19 | 20 | from kicad._native import _pcbnew 21 | 22 | 23 | class PcbTarget(BoardItem): # TODO: better name?, is used for layer alignment 24 | def __init__(self, target): 25 | assert isinstance(target, _pcbnew.PCB_TARGET) 26 | super(PcbTarget, self).__init__(target) 27 | 28 | def get_native(self): 29 | """Get native object from the low level API 30 | 31 | :return: :class:`pcbnew.EDA_TEXT` 32 | """ 33 | return self._obj 34 | 35 | @property 36 | def position(self): 37 | """position of the PcbTarget 38 | 39 | :return: :class:`kicad.util.Point2D` 40 | """ 41 | return Point2D.from_wxPoint(self._obj.GetPosition()) 42 | 43 | @position.setter 44 | def position(self, center): 45 | self._obj.SetPosition(Point2D(center).to_wxPoint()) 46 | 47 | @property 48 | def width(self): 49 | """Width of line in mm 50 | 51 | :return: ``float`` 52 | """ 53 | return _pcbnew.ToMM(self._obj.GetWidth()) 54 | 55 | @width.setter 56 | def width(self, width): 57 | self._obj.SetWidth(_pcbnew.FromMM(width)) 58 | 59 | def __repr__(self): 60 | return "kicad.pcbnew.PcbTarget({})".format(self._obj) 61 | 62 | def __str__(self): 63 | return "kicad.pcbnew.PcbTarget(position={}, width={})".format(self.position, self.width) 64 | -------------------------------------------------------------------------------- /examples/pad_resizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import argparse 6 | 7 | from kicad.pcbnew import Board 8 | 9 | from kicad.util.point import Point2D 10 | 11 | 12 | def resize_pads(board, minimal_drill, minimal_annular_ring): 13 | for module in board.modules: 14 | print("Resize pads of: {}".format(module)) 15 | for pad in module.pads: 16 | assert pad.drill_size.x == pad.drill_size.y # we assume round holes 17 | if pad.drill_size.x < minimal_drill: 18 | new_size = Point2D([minimal_drill, minimal_drill]) 19 | size_difference = new_size - pad.drill_size 20 | 21 | print("* modify drill size from {} to {}".format(pad.drill_size, new_size)) 22 | pad.size += size_difference # modify annular ring 23 | pad.drill_size = new_size # set to minimal size 24 | 25 | annular_size = pad.size - pad.drill_size 26 | if annular_size.x < minimal_annular_ring: 27 | new_size = pad.size # currently required because kicad-python cannot do pad.size.x = ... 28 | new_size.x = pad.drill_size.x + minimal_annular_ring 29 | 30 | print("* modify size (x) from {} to {}".format(pad.size, new_size)) 31 | pad.size = new_size 32 | 33 | if annular_size.y < minimal_annular_ring: 34 | new_size = pad.size # currently required because kicad-python cannot do pad.size.x = ... 35 | new_size.y = pad.drill_size.y + minimal_annular_ring 36 | 37 | print("* modify size (y) from {} to {}".format(pad.size, new_size)) 38 | pad.size = new_size 39 | 40 | 41 | if __name__ == "__main__": 42 | parser = argparse.ArgumentParser() 43 | 44 | parser.add_argument('board', help='board file to list elements', action='store') 45 | parser.add_argument('minimal_drill', help='minimal drill size', type=float, default=0., action='store') 46 | parser.add_argument('minimal_copper', help='minimal copper size to solder', type=float, default=0., action='store') 47 | 48 | args = parser.parse_args() 49 | 50 | board = Board.from_file(args.board) 51 | 52 | resize_pads(board, args.minimal_drill, args.minimal_copper*2) 53 | 54 | print() 55 | save_path = board.filepath + ".new" # save with new name 56 | print("save board in: {}".format(save_path)) 57 | board.to_file(save_path) 58 | -------------------------------------------------------------------------------- /kicad/pcbnew/pad.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.net import Net 18 | 19 | from kicad.util.point import Point2D 20 | 21 | from kicad._native import _pcbnew 22 | 23 | 24 | class Pad(BoardItem): 25 | def __init__(self, pad): 26 | assert isinstance(pad, _pcbnew.D_PAD) 27 | super(Pad, self).__init__(pad) 28 | 29 | def get_native(self): 30 | """Get native object from the low level API 31 | 32 | :return: :class:`pcbnew.PAD` 33 | """ 34 | return self._obj 35 | 36 | @property 37 | def name(self): 38 | """Name of the Pad 39 | 40 | :return: ``unicode`` 41 | """ 42 | return self._obj.GetName() 43 | 44 | @name.setter 45 | def name(self, name): 46 | self._obj.SetName(name) 47 | 48 | @property 49 | def drill_size(self): 50 | """Drill size of the Pad 51 | 52 | :return: :class:`kicad.util.Point2D` 53 | """ 54 | return Point2D.from_wxPoint(self._obj.GetDrillSize()) 55 | 56 | @drill_size.setter 57 | def drill_size(self, drill_size): 58 | self._obj.SetDrillSize(Point2D(drill_size).to_wxSize()) 59 | 60 | @property 61 | def size(self): 62 | """Size of the Pad 63 | 64 | :return: :class:`kicad.util.Point2D` 65 | """ 66 | return Point2D.from_wxPoint(self._obj.GetSize()) 67 | 68 | @size.setter 69 | def size(self, size): 70 | self._obj.SetSize(Point2D(size).to_wxSize()) 71 | 72 | @property 73 | def net(self): 74 | """Net of the Zone 75 | 76 | :return: :class:`kicad.pcbnew.Net` 77 | """ 78 | return Net(self._obj.GetNet()) 79 | 80 | def __repr__(self): 81 | return "kicad.pcbnew.Pad({})".format(self._obj) 82 | 83 | def __str__(self): 84 | return "kicad.pcbnew.Pad(\"{}\")".format(self.name) 85 | -------------------------------------------------------------------------------- /kicad/pcbnew/via.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.net import Net 18 | 19 | from kicad.util.point import Point2D 20 | 21 | from kicad._native import _pcbnew 22 | 23 | 24 | class Via(BoardItem): 25 | def __init__(self, via): 26 | assert type(via) is _pcbnew.VIA 27 | super(Via, self).__init__(via) 28 | 29 | def get_native(self): 30 | """Get native object from the low level API 31 | 32 | :return: :class:`pcbnew.VIA` 33 | """ 34 | return self._obj 35 | 36 | @property 37 | def position(self): 38 | """Position of the Via 39 | 40 | :return: :class:`kicad.util.Point2D` 41 | """ 42 | return Point2D.from_wxPoint(self._obj.GetPosition()) 43 | 44 | @position.setter 45 | def position(self, pos): 46 | self._obj.SetPosition(Point2D(pos).to_wxPoint()) 47 | 48 | @property 49 | def drill(self): 50 | """Drill size of Via in mm 51 | 52 | :return: ``float`` 53 | """ 54 | return _pcbnew.ToMM(self._obj.GetDrillValue()) 55 | 56 | @drill.setter 57 | def drill(self, drill): 58 | self._obj.SetDrill(_pcbnew.FromMM(drill)) 59 | 60 | @property 61 | def width(self): 62 | """Width of Via in mm 63 | 64 | :return: ``float`` 65 | """ 66 | return _pcbnew.ToMM(self._obj.GetWidth()) 67 | 68 | @width.setter 69 | def width(self, width): 70 | self._obj.SetWidth(_pcbnew.FromMM(width)) 71 | 72 | @property 73 | def net(self): 74 | """Net of the Via 75 | 76 | :return: :class:`kicad.pcbnew.Net` 77 | """ 78 | return Net(self._obj.GetNet()) 79 | 80 | def __repr__(self): 81 | return "kicad.pcbnew.Via({})".format(self._obj) 82 | 83 | def __str__(self): 84 | return "kicad.pcbnew.Via(\"{}\")".format(self.net.name) 85 | -------------------------------------------------------------------------------- /kicad/pcbnew/track.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.net import Net 18 | 19 | from kicad.util.point import Point2D 20 | 21 | from kicad._native import _pcbnew 22 | 23 | 24 | class Track(BoardItem): 25 | def __init__(self, track): 26 | assert type(track) is _pcbnew.TRACK 27 | super(Track, self).__init__(track) 28 | 29 | def get_native(self): 30 | """Get native object from the low level API 31 | 32 | :return: :class:`pcbnew.TRACK` 33 | """ 34 | return self._obj 35 | 36 | @property 37 | def start(self): 38 | """Start of the Track 39 | 40 | :return: :class:`kicad.util.Point2D` 41 | """ 42 | return Point2D.from_wxPoint(self._obj.GetStart()) 43 | 44 | @start.setter 45 | def start(self, start): 46 | self._obj.SetStart(Point2D(start).to_wxPoint()) 47 | 48 | @property 49 | def end(self): 50 | """End of the Track 51 | 52 | :return: :class:`kicad.util.Point2D` 53 | """ 54 | return Point2D.from_wxPoint(self._obj.GetEnd()) 55 | 56 | @end.setter 57 | def end(self, end): 58 | self._obj.SetEnd(Point2D(end).to_wxPoint()) 59 | 60 | @property 61 | def width(self): 62 | """Width of Track in mm 63 | 64 | :return: ``float`` 65 | """ 66 | return _pcbnew.ToMM(self._obj.GetWidth()) 67 | 68 | @width.setter 69 | def width(self, width): 70 | self._obj.SetWidth(_pcbnew.FromMM(width)) 71 | 72 | @property 73 | def net(self): 74 | """Net of the Track 75 | 76 | :return: :class:`kicad.pcbnew.Net` 77 | """ 78 | return Net(self._obj.GetNet()) 79 | 80 | def __repr__(self): 81 | return "kicad.pcbnew.Track({})".format(self._obj) 82 | 83 | def __str__(self): 84 | return "kicad.pcbnew.Track(\"{}\")".format(self.net.name) 85 | -------------------------------------------------------------------------------- /kicad/pcbnew/dimension.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.layer import Layer, BoardItemLayer 18 | 19 | from kicad._native import _pcbnew 20 | 21 | 22 | class Dimension(BoardItem): 23 | def __init__(self, dimension): 24 | # type: (_pcbnew.DIMENSION) -> None 25 | assert isinstance(dimension, _pcbnew.DIMENSION) 26 | super(Dimension, self).__init__(dimension) 27 | 28 | def get_native(self): 29 | # type: () -> _pcbnew.DIMENSION 30 | """Get native object from the low level API 31 | 32 | :return: :class:`pcbnew.DIMENSION` 33 | """ 34 | return self._obj 35 | 36 | @property 37 | def layer(self): 38 | # type: () -> Layer 39 | """layer of the drawsegment 40 | 41 | :return: :class:`kicad.pcbnew.Layer` 42 | """ 43 | return BoardItemLayer(self._obj) 44 | 45 | @layer.setter 46 | def layer(self, layer): 47 | # type: (Layer) -> None 48 | assert type(layer) is Layer 49 | self._obj.SetLayer(layer.id) 50 | 51 | @property 52 | def text(self): 53 | # type: () -> str 54 | """Text 55 | 56 | :return: ``unicode`` 57 | """ 58 | return self._obj.GetText() 59 | 60 | @text.setter 61 | def text(self, text): 62 | # type: (str) -> None 63 | self._obj.SetText(text) 64 | 65 | @property 66 | def value(self): 67 | # type: () -> float 68 | """value in mm 69 | 70 | :return: ``float`` 71 | """ 72 | return _pcbnew.ToMM(self._obj.GetValue()) 73 | 74 | @value.setter 75 | def value(self, value): 76 | self._obj.SetValue(_pcbnew.FromMM(value)) 77 | 78 | def __repr__(self): 79 | # type: () -> str 80 | return "kicad.pcbnew.Dimension({})".format(self._obj) 81 | 82 | def __str__(self): 83 | # type: () -> str 84 | return "kicad.pcbnew.Dimension({}, layer=\"{}\")".format(repr(self.text), self.layer.name) 85 | -------------------------------------------------------------------------------- /tests/pcbnew/test_board.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | import unittest 17 | import os 18 | 19 | import pcbnew as _pcbnew 20 | from kicad.pcbnew import Board 21 | 22 | 23 | TEST_PROJECT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'testproject') 24 | TEST_PROJECT_FILE = os.path.join(TEST_PROJECT_DIR, 'testproject.kicad_pcb') 25 | 26 | 27 | class BoardTests(unittest.TestCase): 28 | 29 | def test_init(self): 30 | b = Board() 31 | self.assertEqual(_pcbnew.BOARD, type(b.get_native())) 32 | 33 | def test_from_file(self): 34 | b = Board.from_file(TEST_PROJECT_FILE) 35 | self.assertEqual(TEST_PROJECT_FILE, b.filepath) 36 | 37 | def test_from_file_not_existing(self): 38 | self.assertRaises(IOError, Board.from_file, os.path.join(TEST_PROJECT_DIR, 'not_existing.kicad_pcb')) 39 | 40 | def test_from_file_invalid(self): 41 | self.assertRaises(IOError, Board.from_file, os.path.join(TEST_PROJECT_DIR, 'testproject.pro')) 42 | 43 | def test_init_param(self): 44 | b_native = _pcbnew.BOARD() 45 | b = Board(b_native) 46 | 47 | self.assertIs(b_native, b.get_native()) 48 | self.assertEqual(_pcbnew.BOARD, type(b.get_native())) 49 | 50 | def test_filepath(self): 51 | b = Board() 52 | self.assertEqual("", b.filepath) 53 | b.filepath = "path/to/board.kicad_mod" 54 | self.assertEqual("path/to/board.kicad_mod", b.filepath) 55 | 56 | def test_eq(self): 57 | bp = _pcbnew.BOARD() 58 | b1 = Board(bp) 59 | b2 = Board(bp.GetBoard()) 60 | self.assertEqual(b1, b2) 61 | 62 | @unittest.skip("Board.from_editor() seems to return a new BOARD object") 63 | def test_eq_from_editor(self): 64 | b1 = Board.from_editor() 65 | b2 = Board.from_editor() 66 | self.assertEqual(b1, b2) 67 | 68 | def test_neq(self): 69 | b1 = Board() 70 | b2 = Board() 71 | self.assertNotEqual(b1, b2) 72 | 73 | def test_neq_other_types(self): 74 | b1 = Board() 75 | self.assertNotEqual(b1, None) 76 | self.assertNotEqual(b1, 1) 77 | self.assertNotEqual(b1, "foo") 78 | -------------------------------------------------------------------------------- /kicad/pcbnew/text.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.layer import Layer, BoardItemLayer 18 | 19 | from kicad.util.point import Point2D 20 | 21 | from kicad._native import _pcbnew 22 | 23 | 24 | class Text(BoardItem): 25 | def __init__(self, text): 26 | assert isinstance(text, _pcbnew.EDA_TEXT) 27 | super(Text, self).__init__(text) 28 | 29 | def get_native(self): 30 | """Get native object from the low level API 31 | 32 | :return: :class:`pcbnew.EDA_TEXT` 33 | """ 34 | return self._obj 35 | 36 | @property 37 | def layer(self): 38 | """layer of the drawsegment 39 | 40 | :return: :class:`kicad.pcbnew.Layer` 41 | """ 42 | return BoardItemLayer(self._obj) 43 | 44 | @layer.setter 45 | def layer(self, layer): 46 | assert type(layer) is Layer 47 | self._obj.SetLayer(layer.id) 48 | 49 | @property 50 | def position(self): 51 | """Position of the Text 52 | 53 | :return: :class:`kicad.util.Point2D` 54 | """ 55 | return Point2D.from_wxPoint(self._obj.GetPosition()) 56 | 57 | @position.setter 58 | def position(self, pos): 59 | self._obj.SetPosition(Point2D(pos).to_wxPoint()) 60 | 61 | @property 62 | def text(self): 63 | """Text 64 | 65 | :return: ``unicode`` 66 | """ 67 | return self._obj.GetText() 68 | 69 | @text.setter 70 | def text(self, text): 71 | self._obj.SetText(text) 72 | 73 | @property 74 | def text_size(self): 75 | """Text Size 76 | 77 | :return: :class:`kicad.util.Point2D` 78 | """ 79 | return Point2D.from_wxSize(self._obj.GetTextSize()) 80 | 81 | @text_size.setter 82 | def text_size(self, text_size): 83 | self._obj.SetTextSize(Point2D(text_size).to_wxSize()) 84 | 85 | @property 86 | def thickness(self): 87 | """Thickness 88 | 89 | :return: ``float`` 90 | """ 91 | return _pcbnew.ToMM(self._obj.GetThickness()) 92 | 93 | @thickness.setter 94 | def thickness(self, thickness): 95 | self._obj.SetThickness(_pcbnew.FromMM(thickness)) 96 | 97 | def __repr__(self): 98 | return "kicad.pcbnew.Text({})".format(self._obj) 99 | 100 | def __str__(self): 101 | return "kicad.pcbnew.Text({}, layer=\"{}\")".format(repr(self.text), self.layer.name) 102 | -------------------------------------------------------------------------------- /kicad/scripting/parameter.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | 17 | class Parameter(object): 18 | def __init__(self, page, name, type, default, **kwargs): 19 | """A Parameter specifies how a specific parameter has to look like and what constrains are attached""" 20 | self.page = page 21 | self.name = name 22 | 23 | self.type = type 24 | self.default = default 25 | 26 | self.hint = kwargs.get('hint') # Parameter hint (shown as mouse-over text) 27 | self.designator = kwargs.get('designator') # Parameter designator such as "e, D, p" (etc) 28 | 29 | self.multiple = int(kwargs.get('multiple', 1)) # Check integer values are multiples of this number 30 | self.min_value = kwargs.get('min_value') # Check numeric values are above or equal to this number 31 | self.max_value = kwargs.get('max_value') # Check numeric values are below or equal to this number 32 | 33 | # TODO: custom validator 34 | 35 | def parse(self, value): 36 | parsed_value = self.type(value) 37 | # TODO: rangecheck 38 | # TODO: validation 39 | return parsed_value 40 | 41 | def __repr__(self): 42 | return "kicad.scripting.parameter.Parameter('{}', '{}', {}, {})".format(self.page, self.name, 43 | self.type, self.default) 44 | 45 | 46 | class ParsedParameter(object): 47 | def __init__(self, parameter, value=None): 48 | self.parameter = parameter 49 | self._value = None 50 | 51 | if value is not None: 52 | self.value = value 53 | 54 | @property 55 | def name(self): 56 | return self.parameter.name 57 | 58 | @property 59 | def value(self): 60 | return self._value 61 | 62 | @value.setter 63 | def value(self, raw_value): 64 | self._value = self.parameter.parse(raw_value) 65 | 66 | def __repr__(self): 67 | return "kicad.scripting.parameter.ParsedParameter('{}': '{}')".format(self.name, self.value) 68 | 69 | 70 | class ParameterMap(object): 71 | def __init__(self): 72 | self._params = [] # sorted store 73 | self._params_lookup = {} # fast lookup 74 | 75 | def add(self, param): 76 | # type: (Parameter) -> None 77 | if param.name in self._params_lookup: 78 | raise AttributeError("Parameter with name '{}' already in ParameterMap!".format(param.name)) 79 | self._params.append(param) 80 | self._params_lookup[param.name] = param 81 | 82 | def add_parameter(self, *args, **kwargs): 83 | self.add(Parameter(*args, **kwargs)) 84 | 85 | def __iter__(self): 86 | return self._params.__iter__() 87 | 88 | def __repr__(self): 89 | return "kicad.scripting.parameter.ParameterMap({})".format(self._params) 90 | 91 | 92 | class ParsedParameterMap(object): 93 | def __init__(self, parameter_map): 94 | self._parameter_map = parameter_map 95 | self._parsed_parameters = {} 96 | 97 | def add(self, key, value): 98 | param = self._parameter_map._params_lookup[key] # TODO 99 | self._parsed_parameters[key] = ParsedParameter(param, value) 100 | 101 | def __repr__(self): 102 | entries = [] 103 | for key in self._parsed_parameters: 104 | value = self._parsed_parameters[key] 105 | entries.append('{}: {}'.format(key, repr(value.value))) 106 | return "kicad.scripting.parameter.ParsedParameterMap({})".format(', '.join(entries)) 107 | -------------------------------------------------------------------------------- /kicad/pcbnew/module.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from typing import Generator # noqa: F401 17 | 18 | from kicad.pcbnew.boarditem import BoardItem 19 | 20 | from kicad.pcbnew.pad import Pad 21 | 22 | from kicad.util.point import Point2D 23 | 24 | from kicad._native import _pcbnew 25 | 26 | 27 | class Module(BoardItem): 28 | def __init__(self, module): 29 | # type: (_pcbnew.MODULE) -> None 30 | assert isinstance(module, _pcbnew.MODULE) 31 | super(Module, self).__init__(module) 32 | 33 | def get_native(self): 34 | # type: () -> _pcbnew.MODULE 35 | """Get native object from the low level API 36 | 37 | :return: :class:`pcbnew.MODULE` 38 | """ 39 | return self._obj 40 | 41 | @staticmethod 42 | def from_library(lib_path, name): 43 | # type: (str, str) -> Module 44 | """Load Module from library 45 | 46 | :param lib_path: library path 47 | :type lib_path: ``str``, ``unicode`` 48 | :param name: name of the footprin to load 49 | :type name: ``str``, ``unicode`` 50 | 51 | :return: :class:`pcbnew.MODULE` 52 | """ 53 | io = _pcbnew.PCB_IO() 54 | return Module(io.FootprintLoad(lib_path, name)) 55 | 56 | def to_library(self, lib_path): 57 | # type: (str) -> None 58 | """Save Module to library 59 | 60 | :param lib_path: library path where to save the footprint 61 | :type lib_path: ``str``, ``unicode`` 62 | """ 63 | io = _pcbnew.PCB_IO() 64 | io.FootprintSave(lib_path, self.get_native()) # TODO: uses FPID().GetLibItemName(), what to do when not set? 65 | 66 | @property 67 | def description(self): 68 | # type: () -> str 69 | """Description of the Module 70 | 71 | :return: ``unicode`` 72 | """ 73 | return self._obj.GetDescription() 74 | 75 | @description.setter 76 | def description(self, description): 77 | # type: (str) -> None 78 | self._obj.SetDescription(description) 79 | 80 | @property 81 | def keywords(self): 82 | # type: () -> str 83 | """Keywords of the Module 84 | 85 | :return: ``unicode`` 86 | """ 87 | return self._obj.GetKeywords() 88 | 89 | @keywords.setter 90 | def keywords(self, keywords): 91 | # type: (str) -> None 92 | self._obj.SetKeywords(keywords) 93 | 94 | @property 95 | def pads(self): 96 | # type: () -> Generator[Pad, None, None] 97 | """List of Pads present in the Module 98 | 99 | :return: Iterator over :class:`kicad.pcbnew.Pad` 100 | """ 101 | for p in self._obj.Pads(): 102 | yield Pad(p) 103 | 104 | @property 105 | def position(self): 106 | # type: () -> Point2D 107 | """Position of the Module 108 | 109 | :return: :class:`kicad.util.Point2D` 110 | """ 111 | return Point2D.from_wxPoint(self._obj.GetPosition()) 112 | 113 | @position.setter 114 | def position(self, pos): 115 | self._obj.SetPosition(Point2D(pos).to_wxPoint()) 116 | 117 | @property 118 | def reference(self): 119 | # type: () -> str 120 | """Reference of the Module 121 | 122 | :return: ``unicode`` 123 | """ 124 | return self._obj.GetReference() 125 | 126 | @reference.setter 127 | def reference(self, reference): 128 | # type: (str) -> None 129 | self._obj.SetReference(reference) 130 | 131 | @property 132 | def value(self): 133 | # type: () -> str 134 | """Value of the Module 135 | 136 | :return: ``unicode`` 137 | """ 138 | return self._obj.GetValue() 139 | 140 | @value.setter 141 | def value(self, value): 142 | # type: (str) -> None 143 | self._obj.SetValue(value) 144 | 145 | def __repr__(self): 146 | # type: () -> str 147 | return "kicad.pcbnew.Module({})".format(self._obj) 148 | 149 | def __str__(self): 150 | # type: () -> str 151 | return "kicad.pcbnew.Module(\"{}\")".format(self.reference) 152 | -------------------------------------------------------------------------------- /kicad/plotter.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | import sys 17 | 18 | from kicad.pcbnew.board import Board 19 | from kicad.pcbnew.layer import Layer 20 | 21 | from kicad._native import _pcbnew 22 | 23 | 24 | # https://stackoverflow.com/questions/43645628/how-to-plot-colored-output-from-pcbnew-via-python-scripting 25 | # https://scottbezek.blogspot.co.at/2016/04/scripting-kicad-pcbnew-exports.html 26 | 27 | class Plotter(object): 28 | PLOT_FORMAT_HPGL = _pcbnew.PLOT_FORMAT_HPGL 29 | PLOT_FORMAT_GERBER = _pcbnew.PLOT_FORMAT_GERBER 30 | PLOT_FORMAT_POSTSCRIPT = _pcbnew.PLOT_FORMAT_POST 31 | PLOT_FORMAT_DXF = _pcbnew.PLOT_FORMAT_DXF 32 | PLOT_FORMAT_PDF = _pcbnew.PLOT_FORMAT_PDF 33 | PLOT_FORMAT_SVG = _pcbnew.PLOT_FORMAT_SVG 34 | 35 | def __init__(self, board, layer=None, color_mode=None): 36 | assert type(board) is Board 37 | 38 | self._board = board 39 | self._pctl = _pcbnew.PLOT_CONTROLLER(self._board.get_native()) 40 | self._popt = self._pctl.GetPlotOptions() 41 | 42 | if layer is not None: 43 | self.layer = layer 44 | 45 | if color_mode is not None: 46 | self.color_mode = color_mode 47 | 48 | def open(self, filename, format, sheet_description=None): 49 | """Open a new plotfile for writing 50 | 51 | :param filename: Name of the file to plot 52 | :type filename: ``str`` 53 | :param format: format of the output file 54 | :param sheet_description: some description 55 | 56 | :return: :class:`kicad.plotter.Plotter` 57 | 58 | :Example: 59 | 60 | >>> from kicad.pcbnew import Board, Layer 61 | >>> from kicad.plotter import Plotter 62 | >>> b = Board.from_editor() 63 | >>> p = Plotter(b, layer=Layer.from_id(0)) 64 | >>> with p.open('test', Plotter.PLOT_FORMAT_SVG): 65 | ... p.plot_layer()# doctest: +SKIP 66 | ... 67 | """ 68 | if sys.version_info >= (3,): 69 | assert type(filename) is str 70 | assert sheet_description is None or type(sheet_description) is str 71 | else: 72 | assert type(filename) in [str, unicode] 73 | assert sheet_description is None or type(sheet_description) in [str, unicode] 74 | 75 | self._pctl.OpenPlotfile(filename, format, sheet_description) 76 | 77 | return self # to support "__enter__" 78 | 79 | def close(self): 80 | """Close a plotfile after writing 81 | 82 | :Example: 83 | 84 | >>> from kicad.pcbnew import Board, Layer 85 | >>> from kicad.plotter import Plotter 86 | >>> b = Board.from_editor() 87 | >>> p = Plotter(b, layer=Layer.from_id(0)) 88 | >>> p.open('test', Plotter.PLOT_FORMAT_SVG) 89 | kicad.plotter.Plotter(board="") 90 | >>> p.plot_layer()# doctest: +SKIP 91 | >>> p.close() 92 | """ 93 | self._pctl.ClosePlot() 94 | 95 | @property 96 | def color_mode(self): 97 | """is color mode enabled? 98 | 99 | :return: ``bool`` 100 | """ 101 | return self._pctl.GetColorMode() 102 | 103 | @color_mode.setter 104 | def color_mode(self, color_mode): 105 | assert type(color_mode) is bool 106 | self._pctl.SetColorMode(color_mode) 107 | 108 | @property 109 | def is_open(self): 110 | """is plotfile open? 111 | 112 | :return: ``bool`` 113 | """ 114 | return self._pctl.IsPlotOpen() 115 | 116 | @property 117 | def layer(self): 118 | """layer to plot on 119 | 120 | :return: :class:`kicad.pcbnew.Layer` 121 | """ 122 | return Layer.from_id(self._pctl.GetLayer()) 123 | 124 | @layer.setter 125 | def layer(self, layer): 126 | assert type(layer) is Layer 127 | self._pctl.SetLayer(layer.id) 128 | 129 | def plot_layer(self): 130 | """plot layer to opened file""" 131 | self._pctl.PlotLayer() 132 | 133 | def __enter__(self): 134 | # TODO: this only works with the open statement 135 | if not self.is_open: 136 | raise RuntimeError("You have to use \"Plot.open(...)\" as defined in the documentation") 137 | return self 138 | 139 | def __exit__(self, type, value, traceback): 140 | self.close() 141 | 142 | def __repr__(self): 143 | return "kicad.plotter.Plotter(board=\"{}\")".format(self._board.filepath) 144 | 145 | def __str__(self): 146 | return "kicad.plotter.Plotter(layer=\"{}\")".format(self.layer.name) 147 | -------------------------------------------------------------------------------- /kicad/pcbnew/layer.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | import sys 17 | 18 | from kicad._native import _pcbnew 19 | 20 | 21 | # dict for default layers to convert layer names to id and back 22 | _LAYERS = {_pcbnew.BOARD_GetStandardLayerName(n): n for n in range(_pcbnew.PCB_LAYER_ID_COUNT)} 23 | if sys.version_info >= (3,): 24 | _LAYERS_NAME_LOOKUP = {s: n for n, s in _LAYERS.items()} 25 | else: 26 | _LAYERS_NAME_LOOKUP = {s: n for n, s in _LAYERS.iteritems()} 27 | 28 | 29 | class Layer(object): 30 | def __init__(self, id): 31 | # type: (int) -> None 32 | assert type(id) is int 33 | self._id = id 34 | 35 | @staticmethod 36 | def from_id(id): 37 | # type: (int) -> Layer 38 | """Get Layer object from id 39 | 40 | :param id: 41 | :type id: ``int`` 42 | 43 | :return: :class:`kicad.pcbnew.Layer` 44 | """ 45 | return Layer(id) 46 | 47 | @staticmethod 48 | def from_name(name): 49 | # type: (str) -> Layer 50 | """Get Layer object from name 51 | 52 | :param name: 53 | :type id: ``str`` 54 | 55 | :return: :class:`kicad.pcbnew.Layer` 56 | """ 57 | if sys.version_info >= (3,): 58 | assert type(name) is str 59 | else: 60 | assert type(name) in [str, unicode] 61 | return Layer(_LAYERS[name]) 62 | 63 | @property 64 | def id(self): 65 | # type () -> int 66 | """internal ID of the layer 67 | 68 | :return: ``int`` 69 | """ 70 | return self._id 71 | 72 | @id.setter 73 | def id(self, id): 74 | # type: (int) -> None 75 | assert type(id) is int 76 | self._id = id 77 | 78 | @property 79 | def name(self): 80 | # type: () -> str 81 | """name of the layer 82 | 83 | :return: ``unicode`` 84 | """ 85 | return _LAYERS_NAME_LOOKUP.get(self._id, "") 86 | 87 | def __eq__(self, other): 88 | if not isinstance(self, other.__class__): 89 | return False 90 | 91 | return self.id == other.id 92 | 93 | def __ne__(self, other): 94 | return not self.__eq__(other) 95 | 96 | def __hash__(self): 97 | return hash(self.id) 98 | 99 | def __repr__(self): 100 | return "kicad.pcbnew.Layer(id={})".format(self.id) 101 | 102 | def __str__(self): 103 | return "kicad.pcbnew.Layer(id={}, name=\"{}\")".format(self.id, self.name) 104 | 105 | 106 | class BoardItemLayer(Layer): 107 | def __init__(self, board_item): 108 | # type: (_pcbnew.BOARD_ITEM) -> None 109 | assert isinstance(board_item, _pcbnew.BOARD_ITEM) 110 | self._obj = board_item 111 | 112 | @property 113 | def id(self): 114 | # type: () -> int 115 | """internal ID of the layer 116 | 117 | :return: ``int`` 118 | """ 119 | return self._obj.GetLayer() 120 | 121 | @id.setter 122 | def id(self, id): 123 | # type: (int) -> None 124 | assert type(id) is int 125 | self._obj.SetLayer(id) 126 | 127 | @property 128 | def name(self): 129 | # type: () -> str 130 | """name of the layer 131 | 132 | :return: ``unicode`` 133 | """ 134 | return self._obj.GetLayerName() 135 | 136 | 137 | class LayerSet(object): 138 | def __init__(self, layer_set): 139 | # type: (_pcbnew.LSET) -> None 140 | assert isinstance(layer_set, _pcbnew.LSET) 141 | self._obj = layer_set 142 | 143 | def get_native(self): 144 | # type: () -> _pcbnew.LSET 145 | """Get native object from the low level API 146 | :return: :class:`pcbnew.LSET` 147 | """ 148 | return self._obj 149 | 150 | def __iter__(self): 151 | for id in self._obj.SeqStackupBottom2Top(): 152 | yield Layer.from_id(id) 153 | 154 | def __eq__(self, other): 155 | if not isinstance(self, other.__class__): 156 | return False 157 | 158 | return set(iter(self)) == set(iter(other)) # TODO: verify 159 | 160 | def __ne__(self, other): 161 | return not self.__eq__(other) 162 | 163 | def __hash__(self): 164 | # type: () -> int 165 | return hash(self._obj.FmtHex()) 166 | 167 | def __repr__(self): 168 | # type: () -> str 169 | return "kicad.pcbnew.LayerSet({})".format(self._obj) 170 | 171 | def __str__(self): 172 | # type: () -> str 173 | return "kicad.pcbnew.LayerSet({{{}}})".format(', '.join([str(i.name) for i in iter(self)])) 174 | -------------------------------------------------------------------------------- /tests/util/test_point.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | import unittest 17 | 18 | from kicad.util.point import Point2D 19 | 20 | 21 | class Point2DTests(unittest.TestCase): 22 | 23 | def test_init(self): 24 | p1 = Point2D([1, 2]) 25 | self.assertEqual(p1.x, 1) 26 | self.assertEqual(p1.y, 2) 27 | 28 | p2 = Point2D((4, 5)) 29 | self.assertEqual(p2.x, 4) 30 | self.assertEqual(p2.y, 5) 31 | 32 | p3 = Point2D({'x': 7, 'y': 8}) 33 | self.assertEqual(p3.x, 7) 34 | self.assertEqual(p3.y, 8) 35 | 36 | p3_empty = Point2D({}) 37 | self.assertEqual(p3_empty.x, 0) 38 | self.assertEqual(p3_empty.y, 0) 39 | 40 | p4 = Point2D(p1) 41 | self.assertEqual(p4.x, 1) 42 | self.assertEqual(p4.y, 2) 43 | 44 | p5 = Point2D(1, 2) 45 | self.assertEqual(p5.x, 1) 46 | self.assertEqual(p5.y, 2) 47 | 48 | # TODO: test float datatype 49 | # TODO: invalid type tests 50 | # TODO: tests if int is always converted to float 51 | 52 | def test_round_to(self): 53 | p1 = Point2D([1.234, 5.678]).round_to(0) 54 | self.assertAlmostEqual(p1.x, 1.234) 55 | self.assertAlmostEqual(p1.y, 5.678) 56 | 57 | p2 = Point2D([1.234, 5.678]).round_to(0.1) 58 | self.assertAlmostEqual(p2.x, 1.2) 59 | self.assertAlmostEqual(p2.y, 5.7) 60 | 61 | p3 = Point2D([1.234, 5.678]).round_to(0.01) 62 | self.assertAlmostEqual(p3.x, 1.23) 63 | self.assertAlmostEqual(p3.y, 5.68) 64 | 65 | p4 = Point2D([1.234, 5.678]).round_to(0.001) 66 | self.assertAlmostEqual(p4.x, 1.234) 67 | self.assertAlmostEqual(p4.y, 5.678) 68 | 69 | p5 = Point2D([1.234, 5.678]).round_to(0.0001) 70 | self.assertAlmostEqual(p5.x, 1.234) 71 | self.assertAlmostEqual(p5.y, 5.678) 72 | 73 | def test_eq(self): 74 | p1a = Point2D([1, 2]) 75 | p1b = Point2D([1, 2]) 76 | self.assertEqual(p1a, p1b) 77 | 78 | def test_neq(self): 79 | p1a = Point2D([1, 2]) 80 | p1b = Point2D([2, 1]) 81 | self.assertNotEqual(p1a, p1b) 82 | 83 | def test_add(self): 84 | p1 = Point2D([1, 2]) 85 | self.assertEqual(p1.x, 1) 86 | self.assertEqual(p1.y, 2) 87 | 88 | p2 = p1 + 5 89 | self.assertEqual(p2.x, 6) 90 | self.assertEqual(p2.y, 7) 91 | 92 | p3 = p1 + (-5) 93 | self.assertEqual(p3.x, -4) 94 | self.assertEqual(p3.y, -3) 95 | 96 | p4 = p1 + [4, 2] 97 | self.assertEqual(p4.x, 5) 98 | self.assertEqual(p4.y, 4) 99 | 100 | p5 = p1 + [-5, -3] 101 | self.assertEqual(p5.x, -4) 102 | self.assertEqual(p5.y, -1) 103 | 104 | # TODO: invalid type tests 105 | 106 | def test_sub(self): 107 | p1 = Point2D([1, 2]) 108 | self.assertEqual(p1.x, 1) 109 | self.assertEqual(p1.y, 2) 110 | 111 | p2 = p1 - 5 112 | self.assertEqual(p2.x, -4) 113 | self.assertEqual(p2.y, -3) 114 | 115 | p3 = p1 - (-5) 116 | self.assertEqual(p3.x, 6) 117 | self.assertEqual(p3.y, 7) 118 | 119 | p4 = p1 - [4, 2] 120 | self.assertEqual(p4.x, -3) 121 | self.assertEqual(p4.y, 0) 122 | 123 | p5 = p1 - [-5, -3] 124 | self.assertEqual(p5.x, 6) 125 | self.assertEqual(p5.y, 5) 126 | 127 | # TODO: invalid type tests 128 | 129 | def test_mul(self): 130 | p1 = Point2D([1, 2]) 131 | self.assertEqual(p1.x, 1) 132 | self.assertEqual(p1.y, 2) 133 | 134 | p2 = p1 * 5 135 | self.assertEqual(p2.x, 5) 136 | self.assertEqual(p2.y, 10) 137 | 138 | p3 = p1 * (-5) 139 | self.assertEqual(p3.x, -5) 140 | self.assertEqual(p3.y, -10) 141 | 142 | p4 = p1 * [4, 5] 143 | self.assertEqual(p4.x, 4) 144 | self.assertEqual(p4.y, 10) 145 | 146 | p5 = p1 * [-5, -3] 147 | self.assertEqual(p5.x, -5) 148 | self.assertEqual(p5.y, -6) 149 | 150 | # TODO: invalid type tests 151 | 152 | def test_div(self): 153 | p1 = Point2D([1, 2]) 154 | self.assertEqual(p1.x, 1) 155 | self.assertEqual(p1.y, 2) 156 | 157 | p2 = p1 / 5 158 | self.assertEqual(p2.x, 0.2) 159 | self.assertEqual(p2.y, 0.4) 160 | 161 | p3 = p1 / (-5) 162 | self.assertEqual(p3.x, -0.2) 163 | self.assertEqual(p3.y, -0.4) 164 | 165 | p4 = p1 / [4, 5] 166 | self.assertEqual(p4.x, 0.25) 167 | self.assertEqual(p4.y, 0.4) 168 | 169 | p5 = p1 / [-5, -2] 170 | self.assertEqual(p5.x, -0.2) 171 | self.assertEqual(p5.y, -1) 172 | 173 | # TODO: division by zero tests 174 | # TODO: invalid type tests 175 | -------------------------------------------------------------------------------- /kicad/primitives/polygon.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad._native import _pcbnew 17 | 18 | 19 | class Polygon(object): 20 | def __init__(self): 21 | self._outline = [] 22 | self._holes = [] 23 | 24 | @property 25 | def outline(self): 26 | return self._outline 27 | 28 | @property 29 | def holes(self): 30 | return self._holes 31 | 32 | def __eq__(self, other): 33 | if not isinstance(self, other.__class__): 34 | return False 35 | 36 | if not isinstance(self._obj, other._obj.__class__): 37 | return False 38 | 39 | if self._obj == other._obj: 40 | return True 41 | 42 | return False # TODO: implement real equal check 43 | 44 | def __ne__(self, other): 45 | return not self.__eq__(other) 46 | 47 | def __repr__(self): 48 | return "kicad.primitives.Polygon(outline={})".format(self._outline) 49 | 50 | 51 | class PolygonSet(object): 52 | def __init__(self, poly_set=None): 53 | if poly_set is None: 54 | self._obj = _pcbnew.SHAPE_POLY_SET() 55 | else: 56 | assert isinstance(poly_set, _pcbnew.SHAPE_POLY_SET) 57 | self._obj = poly_set 58 | 59 | def get_native(self): 60 | """Get native object from the low level API 61 | :return: :class:`pcbnew.SHAPE_POLY_SET` 62 | """ 63 | return self._obj 64 | 65 | def fracture(self): 66 | """Converts a set of polygons with holes to a singe outline with 67 | slits/fractures connecting the outer ring to the inner holes""" 68 | self._obj.Fracture(_pcbnew.SHAPE_POLY_SET.PM_FAST) 69 | 70 | def unfracture(self): 71 | """Converts a single outline slitted ('fractured') polygon into a set of outlines with holes""" 72 | self._obj.Unfracture(_pcbnew.SHAPE_POLY_SET.PM_FAST) 73 | 74 | def union(self, other): 75 | """Performs boolean PolygonSet union 76 | 77 | :param other: second operand of union operation 78 | :type other: :class:`kicad.primitives.PolygonSet` 79 | 80 | """ 81 | assert isinstance(other, PolygonSet) 82 | self._obj.BooleanAdd(other._obj, _pcbnew.SHAPE_POLY_SET.PM_FAST) 83 | 84 | def difference(self, other): 85 | """Performs boolean PolygonSet difference 86 | 87 | :param other: second operand of difference operation 88 | :type other: :class:`kicad.primitives.PolygonSet` 89 | 90 | """ 91 | assert isinstance(other, PolygonSet) 92 | self._obj.BooleanSubtract(other._obj, _pcbnew.SHAPE_POLY_SET.PM_FAST) 93 | 94 | def intersection(self, other): 95 | """Performs boolean PolygonSet intersection 96 | 97 | :param other: second operand of intersection operation 98 | :type other: :class:`kicad.primitives.PolygonSet` 99 | 100 | """ 101 | assert isinstance(other, PolygonSet) 102 | self._obj.BooleanIntersection(other._obj, _pcbnew.SHAPE_POLY_SET.PM_FAST) 103 | 104 | def __iter__(self): 105 | # TODO: for the moment, we need to parse the polygon from a string, needs to be improved!! 106 | # Reason: it seems we cannot access the polygons using std::vector using python 107 | lines = iter(self._obj.Format().split('\n')) 108 | 109 | polygons = list() 110 | for _ in range(int(lines.next()[len('polyset '):])): 111 | new_polygon = Polygon() 112 | for poly in range(int(lines.next()[len('poly '):])): 113 | xy_coord = [] # TODO: correct type 114 | for _ in range(int(lines.next())): 115 | xy_coord.append([_pcbnew.ToMM(int(v)) for v in lines.next().split(' ')]) 116 | if poly == 0: 117 | new_polygon._outline = xy_coord 118 | else: 119 | new_polygon._holes.append(xy_coord) 120 | polygons.append(new_polygon) 121 | lines.next() 122 | 123 | # generate all polygons beforehand to prevent possible race-conditions 124 | for polygon in polygons: 125 | yield polygon 126 | 127 | def __eq__(self, other): 128 | if not isinstance(self, other.__class__): 129 | return False 130 | 131 | if not isinstance(self._obj, other._obj.__class__): 132 | return False 133 | 134 | if self._obj == other._obj: # equal op works? 135 | return True 136 | 137 | if self._obj.checksum() != other._obj.checksum(): 138 | return False 139 | 140 | return False # TODO: implement real equal check 141 | 142 | def __ne__(self, other): 143 | return not self.__eq__(other) 144 | 145 | def __repr__(self): 146 | return "kicad.primitives.PolygonSet({})".format(self._obj) 147 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # KiCad documentation build configuration file, created by 5 | # sphinx-quickstart on Wed Feb 7 17:26:12 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | 21 | import sphinx_rtd_theme 22 | 23 | import os 24 | import sys 25 | sys.path.insert(0, os.path.abspath('..')) 26 | 27 | # -- General configuration ------------------------------------------------ 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = ['sphinx.ext.autodoc', 37 | 'sphinx.ext.doctest', 38 | 'sphinx.ext.coverage'] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'KiCad' 54 | copyright = '2018, Thomas Pointhuber' 55 | author = 'Thomas Pointhuber' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '0.1' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '0.1' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | # If true, `todo` and `todoList` produce output, else they produce nothing. 82 | todo_include_todos = False 83 | 84 | 85 | # -- Options for HTML output ---------------------------------------------- 86 | 87 | # The theme to use for HTML and HTML Help pages. See the documentation for 88 | # a list of builtin themes. 89 | # 90 | html_theme = "sphinx_rtd_theme" 91 | 92 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 93 | 94 | # Theme options are theme-specific and customize the look and feel of a theme 95 | # further. For a list of options available for each theme, see the 96 | # documentation. 97 | # 98 | # html_theme_options = {} 99 | 100 | # Add any paths that contain custom static files (such as style sheets) here, 101 | # relative to this directory. They are copied after the builtin static files, 102 | # so a file named "default.css" will overwrite the builtin "default.css". 103 | html_static_path = ['_static'] 104 | 105 | # Custom sidebar templates, must be a dictionary that maps document names 106 | # to template names. 107 | # 108 | # This is required for the alabaster theme 109 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 110 | html_sidebars = { 111 | '**': [ 112 | 'relations.html', # needs 'show_related': True theme option to display 113 | 'searchbox.html', 114 | ] 115 | } 116 | 117 | 118 | # -- Options for HTMLHelp output ------------------------------------------ 119 | 120 | # Output file base name for HTML help builder. 121 | htmlhelp_basename = 'kicaddoc' 122 | 123 | 124 | # -- Options for LaTeX output --------------------------------------------- 125 | 126 | latex_elements = { 127 | # The paper size ('letterpaper' or 'a4paper'). 128 | # 129 | # 'papersize': 'letterpaper', 130 | 131 | # The font size ('10pt', '11pt' or '12pt'). 132 | # 133 | # 'pointsize': '10pt', 134 | 135 | # Additional stuff for the LaTeX preamble. 136 | # 137 | # 'preamble': '', 138 | 139 | # Latex figure (float) alignment 140 | # 141 | # 'figure_align': 'htbp', 142 | } 143 | 144 | # Grouping the document tree into LaTeX files. List of tuples 145 | # (source start file, target name, title, 146 | # author, documentclass [howto, manual, or own class]). 147 | latex_documents = [ 148 | (master_doc, 'kicad.tex', 'KiCad Documentation', 149 | 'Thomas Pointhuber', 'manual'), 150 | ] 151 | 152 | 153 | # -- Options for manual page output --------------------------------------- 154 | 155 | # One entry per manual page. List of tuples 156 | # (source start file, name, description, authors, manual section). 157 | man_pages = [ 158 | (master_doc, 'kicad', 'KiCad Documentation', 159 | [author], 1) 160 | ] 161 | 162 | 163 | # -- Options for Texinfo output ------------------------------------------- 164 | 165 | # Grouping the document tree into Texinfo files. List of tuples 166 | # (source start file, target name, title, author, 167 | # dir menu entry, description, category) 168 | texinfo_documents = [ 169 | (master_doc, 'kicad', 'KiCad Documentation', 170 | author, 'KiCad', 'One line description of project.', 171 | 'Miscellaneous'), 172 | ] 173 | -------------------------------------------------------------------------------- /tests/pcbnew/testproject/testproject.sch: -------------------------------------------------------------------------------- 1 | EESchema Schematic File Version 4 2 | EELAYER 26 0 3 | EELAYER END 4 | $Descr A4 11693 8268 5 | encoding utf-8 6 | Sheet 1 1 7 | Title "" 8 | Date "" 9 | Rev "" 10 | Comp "" 11 | Comment1 "" 12 | Comment2 "" 13 | Comment3 "" 14 | Comment4 "" 15 | $EndDescr 16 | $Comp 17 | L Device:LED D1 18 | U 1 1 5A9ADD59 19 | P 6300 3650 20 | F 0 "D1" V 6338 3533 50 0000 R CNN 21 | F 1 "LED" V 6247 3533 50 0000 R CNN 22 | F 2 "LEDs:LED_0805" H 6300 3650 50 0001 C CNN 23 | F 3 "~" H 6300 3650 50 0001 C CNN 24 | 1 6300 3650 25 | 0 -1 -1 0 26 | $EndComp 27 | $Comp 28 | L Device:LED D2 29 | U 1 1 5A9ADEBF 30 | P 6650 3650 31 | F 0 "D2" V 6688 3533 50 0000 R CNN 32 | F 1 "LED" V 6597 3533 50 0000 R CNN 33 | F 2 "LEDs:LED_0805" H 6650 3650 50 0001 C CNN 34 | F 3 "~" H 6650 3650 50 0001 C CNN 35 | 1 6650 3650 36 | 0 -1 -1 0 37 | $EndComp 38 | $Comp 39 | L Device:LED D3 40 | U 1 1 5A9ADEDD 41 | P 7000 3650 42 | F 0 "D3" V 7038 3533 50 0000 R CNN 43 | F 1 "LED" V 6947 3533 50 0000 R CNN 44 | F 2 "LEDs:LED_0805" H 7000 3650 50 0001 C CNN 45 | F 3 "~" H 7000 3650 50 0001 C CNN 46 | 1 7000 3650 47 | 0 -1 -1 0 48 | $EndComp 49 | $Comp 50 | L Device:LED D4 51 | U 1 1 5A9AE595 52 | P 7350 3650 53 | F 0 "D4" V 7388 3533 50 0000 R CNN 54 | F 1 "LED" V 7297 3533 50 0000 R CNN 55 | F 2 "LEDs:LED_0805" H 7350 3650 50 0001 C CNN 56 | F 3 "~" H 7350 3650 50 0001 C CNN 57 | 1 7350 3650 58 | 0 -1 -1 0 59 | $EndComp 60 | $Comp 61 | L power:GND #PWR0101 62 | U 1 1 5A9AE879 63 | P 6300 3900 64 | F 0 "#PWR0101" H 6300 3650 50 0001 C CNN 65 | F 1 "GND" H 6305 3727 50 0000 C CNN 66 | F 2 "" H 6300 3900 50 0001 C CNN 67 | F 3 "" H 6300 3900 50 0001 C CNN 68 | 1 6300 3900 69 | 1 0 0 -1 70 | $EndComp 71 | $Comp 72 | L power:GND #PWR0102 73 | U 1 1 5A9AE8BD 74 | P 6650 3900 75 | F 0 "#PWR0102" H 6650 3650 50 0001 C CNN 76 | F 1 "GND" H 6655 3727 50 0000 C CNN 77 | F 2 "" H 6650 3900 50 0001 C CNN 78 | F 3 "" H 6650 3900 50 0001 C CNN 79 | 1 6650 3900 80 | 1 0 0 -1 81 | $EndComp 82 | $Comp 83 | L power:GND #PWR0103 84 | U 1 1 5A9AE8D2 85 | P 7000 3900 86 | F 0 "#PWR0103" H 7000 3650 50 0001 C CNN 87 | F 1 "GND" H 7005 3727 50 0000 C CNN 88 | F 2 "" H 7000 3900 50 0001 C CNN 89 | F 3 "" H 7000 3900 50 0001 C CNN 90 | 1 7000 3900 91 | 1 0 0 -1 92 | $EndComp 93 | $Comp 94 | L power:GND #PWR0104 95 | U 1 1 5A9AE8E7 96 | P 7350 3900 97 | F 0 "#PWR0104" H 7350 3650 50 0001 C CNN 98 | F 1 "GND" H 7355 3727 50 0000 C CNN 99 | F 2 "" H 7350 3900 50 0001 C CNN 100 | F 3 "" H 7350 3900 50 0001 C CNN 101 | 1 7350 3900 102 | 1 0 0 -1 103 | $EndComp 104 | Wire Wire Line 105 | 7350 3800 7350 3900 106 | Wire Wire Line 107 | 7000 3800 7000 3900 108 | Wire Wire Line 109 | 6650 3800 6650 3900 110 | Wire Wire Line 111 | 6300 3800 6300 3900 112 | $Comp 113 | L power:VDD #PWR0105 114 | U 1 1 5A9AEFED 115 | P 4900 3000 116 | F 0 "#PWR0105" H 4900 2850 50 0001 C CNN 117 | F 1 "VDD" H 4917 3173 50 0000 C CNN 118 | F 2 "" H 4900 3000 50 0001 C CNN 119 | F 3 "" H 4900 3000 50 0001 C CNN 120 | 1 4900 3000 121 | 1 0 0 -1 122 | $EndComp 123 | $Comp 124 | L Device:R_Pack04 RN1 125 | U 1 1 5A9AF3CE 126 | P 6000 3300 127 | F 0 "RN1" V 5583 3300 50 0000 C CNN 128 | F 1 "R_Pack04" V 5674 3300 50 0000 C CNN 129 | F 2 "Resistors_SMD:R_Array_Convex_4x0603" V 6275 3300 50 0001 C CNN 130 | F 3 "~" H 6000 3300 50 0001 C CNN 131 | 1 6000 3300 132 | 0 1 1 0 133 | $EndComp 134 | Wire Wire Line 135 | 5600 3100 5800 3100 136 | Wire Wire Line 137 | 5600 3200 5800 3200 138 | Wire Wire Line 139 | 5600 3300 5800 3300 140 | Wire Wire Line 141 | 5800 3400 5600 3400 142 | Wire Wire Line 143 | 6200 3400 6300 3400 144 | Wire Wire Line 145 | 6300 3400 6300 3500 146 | Wire Wire Line 147 | 6200 3300 6650 3300 148 | Wire Wire Line 149 | 6650 3300 6650 3500 150 | Wire Wire Line 151 | 7000 3500 7000 3200 152 | Wire Wire Line 153 | 7000 3200 6200 3200 154 | Wire Wire Line 155 | 6200 3100 7350 3100 156 | Wire Wire Line 157 | 7350 3100 7350 3500 158 | $Comp 159 | L Connector_Generic:Conn_01x02 J1 160 | U 1 1 5A9B0CE2 161 | P 3700 3700 162 | F 0 "J1" H 3780 3692 50 0000 L CNN 163 | F 1 "Conn_01x02" H 3780 3601 50 0000 L CNN 164 | F 2 "Connectors_Molex:Molex_KK-6410-02_02x2.54mm_Straight" H 3700 3700 50 0001 C CNN 165 | F 3 "~" H 3700 3700 50 0001 C CNN 166 | 1 3700 3700 167 | 1 0 0 -1 168 | $EndComp 169 | $Comp 170 | L power:VDD #PWR0106 171 | U 1 1 5A9B1B98 172 | P 3400 3600 173 | F 0 "#PWR0106" H 3400 3450 50 0001 C CNN 174 | F 1 "VDD" H 3417 3773 50 0000 C CNN 175 | F 2 "" H 3400 3600 50 0001 C CNN 176 | F 3 "" H 3400 3600 50 0001 C CNN 177 | 1 3400 3600 178 | 1 0 0 -1 179 | $EndComp 180 | $Comp 181 | L power:GND #PWR0107 182 | U 1 1 5A9B1C8A 183 | P 3400 3900 184 | F 0 "#PWR0107" H 3400 3650 50 0001 C CNN 185 | F 1 "GND" H 3405 3727 50 0000 C CNN 186 | F 2 "" H 3400 3900 50 0001 C CNN 187 | F 3 "" H 3400 3900 50 0001 C CNN 188 | 1 3400 3900 189 | 1 0 0 -1 190 | $EndComp 191 | Wire Wire Line 192 | 3500 3700 3400 3700 193 | Wire Wire Line 194 | 3400 3700 3400 3600 195 | Wire Wire Line 196 | 3500 3800 3400 3800 197 | Wire Wire Line 198 | 3400 3800 3400 3900 199 | $Comp 200 | L Switch:SW_DIP_x04 SW1 201 | U 1 1 5A9B35E1 202 | P 5300 3300 203 | F 0 "SW1" H 5300 3767 50 0000 C CNN 204 | F 1 "SW_DIP_x04" H 5300 3676 50 0000 C CNN 205 | F 2 "Buttons_Switches_SMD:SW_DIP_x4_W6.15mm_Slide_Omron_A6H" H 5300 3300 50 0001 C CNN 206 | F 3 "" H 5300 3300 50 0001 C CNN 207 | 1 5300 3300 208 | 1 0 0 -1 209 | $EndComp 210 | Wire Wire Line 211 | 4900 3000 4900 3100 212 | Wire Wire Line 213 | 4900 3400 5000 3400 214 | Wire Wire Line 215 | 5000 3300 4900 3300 216 | Connection ~ 4900 3300 217 | Wire Wire Line 218 | 4900 3300 4900 3400 219 | Wire Wire Line 220 | 5000 3200 4900 3200 221 | Connection ~ 4900 3200 222 | Wire Wire Line 223 | 4900 3200 4900 3300 224 | Wire Wire Line 225 | 5000 3100 4900 3100 226 | Connection ~ 4900 3100 227 | Wire Wire Line 228 | 4900 3100 4900 3200 229 | $EndSCHEMATC 230 | -------------------------------------------------------------------------------- /kicad/util/point.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad._native import _pcbnew 17 | 18 | 19 | class Point2D(object): 20 | """Representation of a 2D Point in space 21 | 22 | :Example: 23 | 24 | >>> from kicad.util.point import Point2D 25 | >>> Point2D(0, 1) 26 | kicad.util.point.Point2D(0.0, 1.0) 27 | >>> Point2D([2, 3]) 28 | kicad.util.point.Point2D(2.0, 3.0) 29 | >>> Point2D((4, 5)) 30 | kicad.util.point.Point2D(4.0, 5.0) 31 | >>> Point2D({'x': 6, 'y':7}) 32 | kicad.util.point.Point2D(6.0, 7.0) 33 | >>> Point2D(Point2D(8, 9)) 34 | kicad.util.point.Point2D(8.0, 9.0) 35 | """ 36 | def __init__(self, coordinates=None, y=None): 37 | # parse constructor 38 | if coordinates is None: 39 | coordinates = {} 40 | elif type(coordinates) in [int, float]: 41 | if y is not None: 42 | coordinates = [coordinates, y] 43 | else: 44 | raise TypeError('you have to give x and y coordinate') 45 | elif isinstance(coordinates, Point2D): 46 | coordinates = coordinates.__dict__() 47 | 48 | # parse points with format: Point2D({'x':0, 'y':0}) 49 | if type(coordinates) is dict: 50 | self.x = float(coordinates.get('x', 0.)) 51 | self.y = float(coordinates.get('y', 0.)) 52 | return 53 | 54 | # parse points with format: Point2D([0, 0]) or Point2D((0, 0)) 55 | if type(coordinates) in [list, tuple]: 56 | if len(coordinates) == 2: 57 | self.x = float(coordinates[0]) 58 | self.y = float(coordinates[1]) 59 | return 60 | else: 61 | raise TypeError('invalid list size (2 elements expected)') 62 | 63 | raise TypeError('invalid parameters given') 64 | 65 | @staticmethod 66 | def from_wxPoint(wxobj): 67 | """Convert a wxPoint to a Point2D 68 | 69 | :param wxobj: point to convert 70 | :type wxobj: :class:`pcbnew.wxPoint` 71 | 72 | :return: :class:`kicad.util.Point2D` 73 | """ 74 | return Point2D(_pcbnew.ToMM(wxobj)) 75 | 76 | @staticmethod 77 | def from_wxSize(wxobj): 78 | """Convert a wxSize to a Point2D 79 | 80 | :param wxobj: point to convert 81 | :type wxobj: :class:`pcbnew.wxSize` 82 | 83 | :return: :class:`kicad.util.Point2D` 84 | """ 85 | return Point2D(_pcbnew.ToMM(wxobj)) 86 | 87 | def to_wxPoint(self): 88 | """Convert coordinate to internal coordinate 89 | 90 | :return: :class:`pcbnew.wxPoint` 91 | """ 92 | return _pcbnew.wxPointMM(float(self.x), float(self.y)) 93 | 94 | def to_wxSize(self): 95 | """Convert size given as Point2D to internal size 96 | 97 | :return: :class:`pcbnew.wxSize` 98 | """ 99 | return _pcbnew.wxSizeMM(float(self.x), float(self.y)) 100 | 101 | def round_to(self, base, prec=10): 102 | """Round to a specific base (like it's required for a grid) 103 | 104 | :param base: base we want to round to 105 | :type base: ``float`` 106 | :param prec: precision of rounding operation 107 | :type prec: ``int`` 108 | 109 | :return: :class:`kicad.util.Point2D` 110 | 111 | :Example: 112 | 113 | >>> from kicad.util.point import Point2D 114 | >>> Point2D(0.1234, 0.5678).round_to(0.01) 115 | kicad.util.point.Point2D(0.12, 0.57) 116 | """ 117 | if base == 0: 118 | return self 119 | 120 | return Point2D({'x': round(base * round(self.x / base), prec), 121 | 'y': round(base * round(self.y / base), prec)}) 122 | 123 | def __eq__(self, other): 124 | if not isinstance(self, other.__class__): 125 | return False 126 | return self.x == other.x and self.y == other.y 127 | 128 | def __ne__(self, other): 129 | return not self.__eq__(other) 130 | 131 | @staticmethod 132 | def __arithmetic_parse(value): 133 | if isinstance(value, Point2D): 134 | return value 135 | elif type(value) in [int, float]: 136 | return Point2D([value, value]) 137 | else: 138 | return Point2D(value) 139 | 140 | def __add__(self, value): 141 | other = Point2D.__arithmetic_parse(value) 142 | 143 | return Point2D({'x': self.x + other.x, 144 | 'y': self.y + other.y}) 145 | 146 | def __sub__(self, value): 147 | other = Point2D.__arithmetic_parse(value) 148 | 149 | return Point2D({'x': self.x - other.x, 150 | 'y': self.y - other.y}) 151 | 152 | def __mul__(self, value): 153 | other = Point2D.__arithmetic_parse(value) 154 | 155 | return Point2D({'x': self.x * other.x, 156 | 'y': self.y * other.y}) 157 | 158 | def __div__(self, value): 159 | other = Point2D.__arithmetic_parse(value) 160 | 161 | return Point2D({'x': self.x / other.x, 162 | 'y': self.y / other.y}) 163 | 164 | def __truediv__(self, obj): 165 | return self.__div__(obj) 166 | 167 | def __dict__(self): 168 | return {'x': self.x, 'y': self.y} 169 | 170 | def __hash__(self): 171 | return hash((self.x, self.y)) 172 | 173 | def __repr__(self): 174 | return "kicad.util.point.Point2D({x}, {y})".format(**self.__dict__()) 175 | 176 | def __str__(self): 177 | return "[{x}, {y}]".format(**self.__dict__()) 178 | -------------------------------------------------------------------------------- /kicad/pcbnew/boarditem.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.layer import Layer, LayerSet 17 | 18 | from kicad._native import _pcbnew 19 | 20 | 21 | def from_board_item(board_item): 22 | # type: (_pcbnew.BOARD_ITEM) -> BoardItem 23 | item = board_item.Cast() 24 | item_type = type(item) 25 | 26 | if item_type is _pcbnew.TEXTE_PCB: 27 | from kicad.pcbnew.text import Text 28 | return Text(item) 29 | 30 | elif item_type is _pcbnew.BOARD: 31 | from kicad.pcbnew.board import Board 32 | return Board(item) 33 | 34 | elif item_type is _pcbnew.DIMENSION: 35 | from kicad.pcbnew.dimension import Dimension 36 | return Dimension(item) 37 | 38 | elif item_type is _pcbnew.DRAWSEGMENT: 39 | from kicad.pcbnew.drawsegment import Drawsegment 40 | return Drawsegment.from_drawsegment(item) 41 | 42 | elif item_type is _pcbnew.EDGE_MODULE: 43 | raise NotImplementedError(item_type) 44 | 45 | elif item_type is _pcbnew.MODULE: 46 | from kicad.pcbnew.module import Module 47 | return Module(item) 48 | 49 | elif item_type is _pcbnew.D_PAD: 50 | from kicad.pcbnew.pad import Pad 51 | return Pad(item) 52 | 53 | elif item_type is _pcbnew.TEXTE_MODULE: 54 | from kicad.pcbnew.text import Text # type: ignore 55 | return Text(item) 56 | 57 | elif item_type is _pcbnew.VIA: 58 | from kicad.pcbnew.via import Via 59 | return Via(item) 60 | 61 | elif item_type is _pcbnew.TRACK: 62 | from kicad.pcbnew.track import Track 63 | return Track(item) 64 | 65 | elif item_type is _pcbnew.PCB_TARGET: 66 | from kicad.pcbnew.pcbtarget import PcbTarget 67 | return PcbTarget(item) 68 | 69 | elif item_type is _pcbnew.ZONE_CONTAINER: 70 | from kicad.pcbnew.zone import Zone 71 | return Zone(item) 72 | 73 | else: 74 | raise NotImplementedError(item_type) 75 | 76 | 77 | class BoardItem(object): 78 | """Create a new BoardItem object 79 | 80 | :param board_item: already existing board_item object 81 | :type board_item: :class:`pcbnew.BOARD_ITEM` 82 | """ 83 | 84 | def __init__(self, board_item): 85 | # type: (_pcbnew.BOARD_ITEM) -> None 86 | assert isinstance(board_item, _pcbnew.BOARD_ITEM) 87 | self._obj = board_item # type: _pcbnew.BOARD_ITEM 88 | 89 | def get_native(self): 90 | # type: () -> _pcbnew.BOARD_ITEM 91 | """Get native object from the low level API 92 | 93 | :return: :class:`pcbnew.BOARD_ITEM` 94 | """ 95 | return self._obj 96 | 97 | @property 98 | def is_highlighted(self): 99 | # type: () -> bool 100 | """is highlighted? 101 | 102 | :return: ``bool`` 103 | """ 104 | return self._obj.IsHighlighted() 105 | 106 | @is_highlighted.setter 107 | def is_highlighted(self, is_highlighted): 108 | # type: (bool) -> None 109 | assert type(is_highlighted) is bool 110 | if is_highlighted: 111 | self._obj.SetHighlighted() 112 | else: 113 | self._obj.ClearHighlighted() 114 | 115 | @property 116 | def is_locked(self): 117 | # type: () -> bool 118 | """is locked? 119 | 120 | :return: ``bool`` 121 | """ 122 | return self._obj.IsLocked() 123 | 124 | @is_locked.setter 125 | def is_locked(self, is_locked): 126 | # type: (bool) -> None 127 | assert type(is_locked) is bool 128 | self._obj.SetLocked(is_locked) 129 | 130 | @property 131 | def is_selected(self): 132 | # type: () -> bool 133 | """is selected? 134 | 135 | :return: ``bool`` 136 | """ 137 | return self._obj.IsSelected() 138 | 139 | @is_selected.setter 140 | def is_selected(self, is_selected): 141 | # type: (bool) -> None 142 | assert type(is_selected) is bool 143 | if is_selected: 144 | self._obj.SetSelected() 145 | else: 146 | self._obj.ClearSelected() 147 | 148 | @property 149 | def layer(self): 150 | # type: () -> Layer 151 | """primary layer of the item 152 | 153 | :return: ``kicad.pcbnew.Layer`` 154 | """ 155 | return Layer.from_id(self._obj.GetLayer()) 156 | 157 | @property 158 | def layers(self): 159 | # type: () -> LayerSet 160 | """All layers where the item is present on 161 | 162 | :return: ``kicad.pcbnew.LayerSet`` 163 | """ 164 | return LayerSet(self._obj.GetLayerSet()) 165 | 166 | def __eq__(self, other): 167 | if not isinstance(self, other.__class__): 168 | return False 169 | 170 | if not isinstance(self._obj, other._obj.__class__): 171 | return False 172 | 173 | if self._obj == other._obj: 174 | return True 175 | 176 | if self.is_highlighted != other.is_highlighted: 177 | return False 178 | 179 | # now we will do some hack to check if the other object is actually the same. We know is_highlighted is the same 180 | old_value = self.is_highlighted 181 | self.is_highlighted = not self.is_highlighted 182 | is_still_same = self.is_highlighted == other.is_highlighted # TODO: replace with something better 183 | self.is_highlighted = old_value 184 | return is_still_same 185 | 186 | def __ne__(self, other): 187 | return not self.__eq__(other) 188 | 189 | def __hash__(self): 190 | # type: () -> int 191 | return hash(self.layers) 192 | 193 | def __repr__(self): 194 | # type: () -> str 195 | return "kicad.pcbnew.boarditem.BoardItem({})".format(self._obj) 196 | -------------------------------------------------------------------------------- /examples/pcbnew_diff/pcbnew_diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | # 16 | # (C) 2018 by Thomas Pointhuber, 17 | 18 | import argparse 19 | import math 20 | 21 | from kicad.pcbnew import Board, Layer 22 | from kicad.primitives import PolygonSet 23 | 24 | # Dependencies required for plotting 25 | import matplotlib.pyplot as plt 26 | from matplotlib.patches import Polygon 27 | from matplotlib.collections import PatchCollection 28 | 29 | 30 | def create_polyset_from_item(board_item): 31 | """Create PolygonSet from a BoardItem""" 32 | p = PolygonSet() 33 | 34 | # @see: https://github.com/twlostow/kicad-dev/blob/tom-polygon-gen/qa/polygon_generator/test_polygon_generator.cpp 35 | segsPerCircle = 64 36 | correctionFactor = 1.0 / math.cos(math.pi / segsPerCircle) 37 | # TODO: method not exported in kicad-python by now, which means we need to access it native 38 | board_item.get_native().TransformShapeWithClearanceToPolygon(p.get_native(), 1, segsPerCircle, correctionFactor) 39 | 40 | return p 41 | 42 | 43 | def create_polysets_per_net(board, layer): 44 | """Create PolgonSet for specific layer seperated per net""" 45 | nets = {} 46 | 47 | def add_polyset(net, polyset): 48 | if net not in nets: 49 | nets[net] = [] 50 | nets[net].append(polyset) 51 | 52 | for track in board.tracks: 53 | if layer not in track.layers: 54 | continue 55 | add_polyset(track.net, create_polyset_from_item(track)) 56 | 57 | for via in board.vias: 58 | if layer not in via.layers: 59 | continue 60 | add_polyset(via.net, create_polyset_from_item(via)) 61 | 62 | for zone in board.zones: 63 | if layer not in zone.layers: 64 | continue 65 | add_polyset(zone.net, create_polyset_from_item(zone)) 66 | 67 | for module in board.modules: 68 | for pad in module.pads: 69 | if layer not in pad.layers: 70 | continue 71 | add_polyset(pad.net, create_polyset_from_item(pad)) 72 | 73 | # TODO: drawings 74 | return nets 75 | 76 | 77 | def merge_polysets(polysets): 78 | """Merge multiple PolygonSet into one PolygonSet""" 79 | p = PolygonSet() 80 | for ps in polysets: 81 | p.union(ps) 82 | return p 83 | 84 | 85 | def unify_polysets(nets): 86 | """Merge multiple PolygonSet per Net into one PolygonSet and remove holes""" 87 | merged_nets = {} 88 | for net in nets.keys(): 89 | print("* Unify polyset for \"{}\" with {} subpolygons".format(net, len(nets[net]))) 90 | merged_nets[net] = merge_polysets(nets[net]) 91 | merged_nets[net].fracture() # only for plotting required, just to be sure 92 | return merged_nets 93 | 94 | 95 | def plot_polygon_from_polyset(polyset): 96 | patches = [] 97 | for poly in polyset: 98 | assert len(poly.holes) == 0 # because of fracture() no holes are present 99 | polygon = Polygon(poly.outline, True) 100 | patches.append(polygon) 101 | return patches 102 | 103 | 104 | def patch_collection_from_nets(merged_nets): 105 | """create matplotlib Polygons for PatchCollection""" 106 | patches = [] 107 | for net in merged_nets.keys(): 108 | patches += plot_polygon_from_polyset(merged_nets[net]) 109 | return patches 110 | 111 | 112 | def create_diff_polygons(old_merged_nets, new_merged_nets): 113 | all_nets = set(old_merged_nets.keys()) 114 | all_nets.union(new_merged_nets.keys()) 115 | 116 | nodiff_poly = PolygonSet() 117 | add_poly = PolygonSet() 118 | sub_poly = PolygonSet() 119 | 120 | for net in all_nets: 121 | print('* Calculate diff for "{}"'.format(net)) 122 | old_net = old_merged_nets.get(net) 123 | new_net = new_merged_nets.get(net) 124 | 125 | if old_net and new_net: 126 | nodiff_poly.union(old_net) 127 | nodiff_poly.union(new_net) 128 | 129 | tmp_sub = PolygonSet() 130 | tmp_sub.union(old_net) 131 | tmp_sub.difference(new_net) 132 | sub_poly.union(tmp_sub) 133 | 134 | tmp_add = PolygonSet() 135 | tmp_add.union(new_net) 136 | tmp_add.difference(old_net) 137 | add_poly.union(tmp_add) 138 | elif old_net: 139 | nodiff_poly.union(old_net) 140 | sub_poly.union(old_net) 141 | elif new_net: 142 | nodiff_poly.union(new_net) 143 | add_poly.union(new_net) 144 | 145 | nodiff_poly.fracture() 146 | add_poly.fracture() 147 | sub_poly.fracture() 148 | 149 | return nodiff_poly, add_poly, sub_poly 150 | 151 | 152 | if __name__ == "__main__": 153 | parser = argparse.ArgumentParser() 154 | 155 | parser.add_argument('old_board', help='old board file as original board', action='store') 156 | parser.add_argument('new_board', help='new board file to compare to', action='store') # TODO: implement 157 | 158 | parser.add_argument('--layer', help='layer which should be diffed', action='store', default='F.Cu') 159 | 160 | args = parser.parse_args() 161 | 162 | old_board = Board.from_file(args.old_board) 163 | new_board = Board.from_file(args.new_board) 164 | 165 | layer = Layer.from_name(args.layer) 166 | 167 | # Create Polygons for boards 168 | old_nets = create_polysets_per_net(old_board, layer) 169 | old_merged_nets = unify_polysets(old_nets) 170 | 171 | new_nets = create_polysets_per_net(new_board, layer) 172 | new_merged_nets = unify_polysets(new_nets) 173 | 174 | # Create diff polygons 175 | nodiff_poly, add_poly, sub_poly = create_diff_polygons(old_merged_nets, new_merged_nets) 176 | 177 | # Start Plotting code 178 | fig, ax = plt.subplots() 179 | 180 | # Plot all layers 181 | nodiff_p = PatchCollection(plot_polygon_from_polyset(nodiff_poly), alpha=0.4, facecolors='lightgrey') 182 | ax.add_collection(nodiff_p) 183 | 184 | sub_p = PatchCollection(plot_polygon_from_polyset(sub_poly), alpha=0.6, facecolors='red') 185 | ax.add_collection(sub_p) 186 | 187 | add_p = PatchCollection(plot_polygon_from_polyset(add_poly), alpha=0.6, facecolors='green') 188 | ax.add_collection(add_p) 189 | 190 | # output diff 191 | plt.gca().set_aspect('equal', adjustable='box') 192 | plt.gca().invert_yaxis() 193 | ax.autoscale() 194 | 195 | plt.show() 196 | -------------------------------------------------------------------------------- /kicad/pcbnew/board.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from typing import Generator # noqa: F401 17 | 18 | from kicad.pcbnew.boarditem import BoardItem, from_board_item 19 | 20 | from kicad.pcbnew.module import Module 21 | from kicad.pcbnew.track import Track 22 | from kicad.pcbnew.via import Via 23 | from kicad.pcbnew.zone import Zone 24 | 25 | from kicad.util.point import Point2D 26 | 27 | from kicad._native import _pcbnew 28 | 29 | 30 | class Board(BoardItem): 31 | """Create a new Board object 32 | 33 | :param board: already existing board object 34 | :type board: :class:`pcbnew.BOARD` 35 | 36 | :Example: 37 | 38 | >>> from kicad.pcbnew import Board 39 | >>> b = Board() 40 | """ 41 | def __init__(self, board=None): 42 | # type: (_pcbnew.BOARD) -> None 43 | if board is None: 44 | board = _pcbnew.BOARD() 45 | assert isinstance(board, _pcbnew.BOARD) 46 | super(Board, self).__init__(board) 47 | 48 | def get_native(self): 49 | # type: () -> _pcbnew.BOARD 50 | """Get native object from the low level API 51 | 52 | :return: :class:`pcbnew.BOARD` 53 | """ 54 | return self._obj 55 | 56 | @staticmethod 57 | def from_editor(): 58 | # type: () -> Board 59 | """Get the current board visible in pcbnew 60 | 61 | :return: :class:`kicad.pcbnew.Board` 62 | 63 | :Example: 64 | 65 | >>> from kicad.pcbnew import Board 66 | >>> b = Board.from_editor() 67 | """ 68 | return Board(_pcbnew.GetBoard()) 69 | 70 | @staticmethod 71 | def from_file(path): 72 | # type: (str) -> Board 73 | """Load a board from a given filepath 74 | 75 | :param path: path to the ".kicad_pcb" file 76 | :type path: ``str``, ``unicode`` 77 | 78 | :return: :class:`kicad.pcbnew.Board` 79 | 80 | :Example: 81 | 82 | >>> from kicad.pcbnew import Board 83 | >>> b = Board.from_file("path/to/board.kicad_pcb")# doctest: +SKIP 84 | """ 85 | return Board(_pcbnew.LoadBoard(path)) 86 | 87 | def to_file(self, path): 88 | # type: (str) -> None 89 | """Save a board to a given filepath 90 | 91 | :param path: path for the ".kicad_pcb" file 92 | :type path: ``str``, ``unicode`` 93 | """ 94 | _pcbnew.SaveBoard(path, self.get_native()) 95 | 96 | @property 97 | def filepath(self): 98 | # type: () -> str 99 | """Filepath of the Board 100 | 101 | :return: ``unicode`` 102 | 103 | :Example: 104 | 105 | >>> from kicad.pcbnew import Board 106 | >>> b = Board() 107 | >>> b.filepath = "path/to/board.kicad_mod" 108 | >>> print(b.filepath) 109 | path/to/board.kicad_mod 110 | """ 111 | return self._obj.GetFileName() 112 | 113 | @filepath.setter 114 | def filepath(self, filepath): 115 | # type: (str) -> None 116 | self._obj.SetFileName(filepath) 117 | 118 | @property 119 | def aux_origin(self): 120 | # type: () -> Point2D 121 | """Aux origin of Board 122 | 123 | :return: :class:`kicad.util.Point2D` 124 | 125 | :Example: 126 | 127 | >>> from kicad.pcbnew import Board 128 | >>> b = Board() 129 | >>> b.aux_origin = [1, 2] 130 | >>> b.aux_origin 131 | kicad.util.point.Point2D(1.0, 2.0) 132 | """ 133 | return Point2D.from_wxPoint(self._obj.GetAuxOrigin()) 134 | 135 | @aux_origin.setter 136 | def aux_origin(self, origin): 137 | self._obj.SetAuxOrigin(Point2D(origin).to_wxPoint()) 138 | 139 | @property 140 | def grid_origin(self): 141 | # type: () -> Point2D 142 | """Grid origin of Board 143 | 144 | :return: :class:`kicad.util.Point2D` 145 | 146 | :Example: 147 | 148 | >>> from kicad.pcbnew import Board 149 | >>> b = Board() 150 | >>> b.grid_origin = [1, 2] 151 | >>> b.grid_origin 152 | kicad.util.point.Point2D(1.0, 2.0) 153 | """ 154 | return Point2D.from_wxPoint(self._obj.GetGridOrigin()) 155 | 156 | @grid_origin.setter 157 | def grid_origin(self, origin): 158 | self._obj.SetGridOrigin(Point2D(origin).to_wxPoint()) 159 | 160 | @property 161 | def modules(self): 162 | # type: () -> Generator[Module, None, None] 163 | """List of Modules present in the Board 164 | 165 | :return: Iterator over :class:`kicad.pcbnew.Module` 166 | """ 167 | for item in self._obj.GetModules(): 168 | yield Module(item) 169 | 170 | @property 171 | def tracks(self): 172 | # type: () -> Generator[Track, None, None] 173 | """List of Tracks present in the Board 174 | 175 | :return: Iterator over :class:`kicad.pcbnew.Track` 176 | """ 177 | for item in self._obj.GetTracks(): 178 | if type(item) is _pcbnew.TRACK: 179 | yield Track(item) 180 | 181 | @property 182 | def vias(self): 183 | # type: () -> Generator[Via, None, None] 184 | """List of Vias present in the Board 185 | 186 | :return: Iterator over :class:`kicad.pcbnew.Via` 187 | """ 188 | for item in self._obj.GetTracks(): 189 | if type(item) is _pcbnew.VIA: 190 | yield Via(item) 191 | 192 | @property 193 | def zones(self): 194 | # type: () -> Generator[Zone, None, None] 195 | """List of Zones present in the Board 196 | 197 | :return: Iterator over :class:`kicad.pcbnew.Zone` 198 | """ 199 | for idx in range(self._obj.GetAreaCount()): 200 | yield Zone(self._obj.GetArea(idx)) 201 | 202 | def is_zone_filled(self): 203 | pass # TODO: implement 204 | 205 | def zone_fill(self): 206 | pass # TODO: implement 207 | 208 | def zone_unfill(self): 209 | pass # TODO: implement 210 | 211 | @property 212 | def drawings(self): 213 | for item in self._obj.GetDrawings(): 214 | yield from_board_item(item) 215 | 216 | @property 217 | def layers_enabled(self): 218 | return self._obj.GetEnabledLayers() # TODO: add wrapper and setter 219 | 220 | def __repr__(self): 221 | # type: () -> str 222 | return "kicad.pcbnew.Board({})".format(self._obj) 223 | 224 | def __str__(self): 225 | # type: () -> str 226 | return "kicad.pcbnew.Board(\"{}\")".format(self.filepath) 227 | -------------------------------------------------------------------------------- /kicad/pcbnew/drawsegment.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | from kicad.pcbnew.boarditem import BoardItem 17 | from kicad.pcbnew.layer import Layer, BoardItemLayer 18 | 19 | from kicad.util.point import Point2D 20 | 21 | from kicad._native import _pcbnew 22 | 23 | 24 | class Drawsegment(BoardItem): 25 | def __init__(self, drawsegment): 26 | assert isinstance(drawsegment, _pcbnew.DRAWSEGMENT) 27 | super(Drawsegment, self).__init__(drawsegment) 28 | 29 | def get_native(self): 30 | """Get native object from the low level API 31 | 32 | :return: :class:`pcbnew.DRAWSEGMENT` 33 | """ 34 | return self._obj 35 | 36 | @staticmethod 37 | def from_drawsegment(drawsegment): 38 | assert isinstance(drawsegment, _pcbnew.DRAWSEGMENT) 39 | 40 | shape = drawsegment.GetShape() 41 | 42 | if shape is _pcbnew.S_SEGMENT: 43 | return Line(drawsegment) 44 | # elif shape is _pcbnew.S_RECT: 45 | # return Rect(drawsegment) 46 | elif shape is _pcbnew.S_ARC: 47 | return Arc(drawsegment) 48 | elif shape is _pcbnew.S_CIRCLE: 49 | return Circle(drawsegment) 50 | elif shape is _pcbnew.S_POLYGON: 51 | return Polygon(drawsegment) 52 | # elif shape is _pcbnew.S_CURVE: 53 | # return Curve(drawsegment) 54 | else: 55 | raise NotImplementedError(drawsegment.GetShapeStr()) 56 | 57 | @property 58 | def layer(self): 59 | """layer of the drawsegment 60 | 61 | :return: :class:`kicad.pcbnew.Layer` 62 | """ 63 | return BoardItemLayer(self._obj) 64 | 65 | @layer.setter 66 | def layer(self, layer): 67 | assert type(layer) is Layer 68 | self._obj.SetLayer(layer.id) 69 | 70 | @property 71 | def width(self): 72 | """Width of line in mm 73 | 74 | :return: ``float`` 75 | """ 76 | return _pcbnew.ToMM(self._obj.GetWidth()) 77 | 78 | @width.setter 79 | def width(self, width): 80 | self._obj.SetWidth(_pcbnew.FromMM(width)) 81 | 82 | def __repr__(self): 83 | return "kicad.pcbnew.Drawsegment({})".format(self._obj) 84 | 85 | 86 | class Arc(Drawsegment): 87 | def __init__(self, arc): 88 | assert arc.GetShape() is _pcbnew.S_ARC 89 | super(Arc, self).__init__(arc) 90 | 91 | @property 92 | def angle(self): 93 | """angle of arc in degree 94 | 95 | :return: ``float`` 96 | """ 97 | return self._obj.GetAngle() / 10. 98 | 99 | @angle.setter 100 | def angle(self, angle): 101 | assert type(angle) in [int, float] 102 | self._obj.SetAngle(angle * 10.) 103 | 104 | @property 105 | def center(self): 106 | """Center point of arc 107 | 108 | :return: :class:`kicad.util.Point2D` 109 | """ 110 | return Point2D.from_wxPoint(self._obj.GetCenter()) 111 | 112 | @center.setter 113 | def center(self, center): 114 | self._obj.SetCenter(Point2D(center).to_wxPoint()) 115 | 116 | @property 117 | def start(self): 118 | """Start point of arc 119 | 120 | :return: :class:`kicad.util.Point2D` 121 | """ 122 | return Point2D.from_wxPoint(self._obj.GetStart()) 123 | 124 | @start.setter 125 | def start(self, start): 126 | self._obj.SetStart(Point2D(start).to_wxPoint()) 127 | 128 | def __repr__(self): 129 | return "kicad.pcbnew.Arc({})".format(self._obj) 130 | 131 | def __str__(self): 132 | return "kicad.pcbnew.Arc(center={}, start={}, angle={}, width={}, layer=\"{}\")".format(self.center, 133 | self.start, 134 | self.angle, 135 | self.width, 136 | self.layer.name) 137 | 138 | 139 | class Circle(Drawsegment): 140 | def __init__(self, circle): 141 | assert circle.GetShape() is _pcbnew.S_CIRCLE 142 | super(Circle, self).__init__(circle) 143 | 144 | @property 145 | def center(self): 146 | """Center point of circle 147 | 148 | :return: :class:`kicad.util.Point2D` 149 | """ 150 | return Point2D.from_wxPoint(self._obj.GetCenter()) 151 | 152 | @center.setter 153 | def center(self, center): 154 | self._obj.SetCenter(Point2D(center).to_wxPoint()) 155 | 156 | @property 157 | def diameter(self): 158 | """Diameter of circle 159 | 160 | :return: ``float`` 161 | """ 162 | return self.radius * 2.0 163 | 164 | @diameter.setter 165 | def diameter(self, diameter): 166 | self.radius = diameter / 2.0 167 | 168 | @property 169 | def radius(self): 170 | """Radius of circle 171 | 172 | :return: ``float`` 173 | """ 174 | end = Point2D.from_wxPoint(self._obj.GetEnd()) 175 | diff = self.center - end 176 | return max(abs(diff.x), abs(diff.y)) 177 | 178 | @radius.setter 179 | def radius(self, radius): 180 | point2d_radius = self.center + Point2D(radius, radius) 181 | self._obj.SetCenter(point2d_radius.to_wxPoint()) 182 | 183 | def __repr__(self): 184 | return "kicad.pcbnew.Circle({})".format(self._obj) 185 | 186 | def __str__(self): 187 | return "kicad.pcbnew.Circle(center={}, diameter={}, width={}, layer=\"{}\")".format(self.center, 188 | self.diameter, 189 | self.width, 190 | self.layer.name) 191 | 192 | 193 | class Line(Drawsegment): 194 | def __init__(self, line): 195 | assert line.GetShape() is _pcbnew.S_SEGMENT 196 | super(Line, self).__init__(line) 197 | 198 | @property 199 | def start(self): 200 | """Start point of line 201 | 202 | :return: :class:`kicad.util.Point2D` 203 | """ 204 | return Point2D.from_wxPoint(self._obj.GetStart()) 205 | 206 | @start.setter 207 | def start(self, start): 208 | self._obj.SetStart(Point2D(start).to_wxPoint()) 209 | 210 | @property 211 | def end(self): 212 | """End point of line 213 | 214 | :return: :class:`kicad.util.Point2D` 215 | """ 216 | return Point2D.from_wxPoint(self._obj.GetEnd()) 217 | 218 | @end.setter 219 | def end(self, end): 220 | self._obj.SetEnd(Point2D(end).to_wxPoint()) 221 | 222 | def __repr__(self): 223 | return "kicad.pcbnew.Line({})".format(self._obj) 224 | 225 | def __str__(self): 226 | return "kicad.pcbnew.Line(start={}, end={}, width={}, layer=\"{}\")".format(self.start, 227 | self.end, 228 | self.width, 229 | self.layer.name) 230 | 231 | 232 | class Polygon(Drawsegment): 233 | def __init__(self, polygon): 234 | assert polygon.GetShape() is _pcbnew.S_POLYGON 235 | super(Polygon, self).__init__(polygon) 236 | 237 | def __repr__(self): 238 | return "kicad.pcbnew.Polygon({})".format(self._obj) 239 | 240 | def __str__(self): 241 | return "kicad.pcbnew.Polygon(layer=\"{}\")".format(self.layer.name) 242 | -------------------------------------------------------------------------------- /kicad/scripting/footprint_wizard.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # 14 | # (C) 2018 by Thomas Pointhuber, 15 | 16 | import argparse 17 | import os 18 | import sys 19 | 20 | try: 21 | import yaml 22 | _YAML_AVAILABLE = True 23 | except ImportError: 24 | _YAML_AVAILABLE = False 25 | 26 | from kicad.scripting.parameter import ParameterMap, ParsedParameterMap 27 | 28 | from kicad.pcbnew import Module 29 | from kicad._native import _pcbnew 30 | 31 | 32 | class FootprintWizard(object): 33 | def __init__(self): 34 | self.name = "KiCad FP Wizard" 35 | self.description = "Undefined Footprint Wizard plugin" 36 | self.image = "" 37 | self.value = "" 38 | self.reference_prefix = "REF" 39 | 40 | def generate_parameter_list(self, params): 41 | # type: (ParameterMap) -> None 42 | raise NotImplementedError("generate_parameter_list is not implemented!") 43 | 44 | def build_footprint(self, module, params): 45 | # type: (Module, ParsedParameterMap) -> Module 46 | raise NotImplementedError("build_footprint is not implemented!") 47 | 48 | 49 | # https://github.com/KiCad/kicad-source-mirror/blob/master/pcbnew/python/plugins/FootprintWizardBase.py 50 | # https://github.com/KiCad/kicad-source-mirror/blob/72f87861bb22bf2ecb82ea570d9828a287436748/scripting/kicadplugins.i#L478 # noqa 51 | class _FootprintWizardBase(_pcbnew.FootprintWizardPlugin): 52 | UNIT_CONVERSATION_TABLE = { 53 | bool: _pcbnew.uBool, 54 | int: _pcbnew.uInteger, 55 | float: _pcbnew.uFloat, 56 | str: _pcbnew.uString 57 | } # TODO: support more units 58 | 59 | def __init__(self, footprint_wizard): 60 | super(_FootprintWizardBase, self).__init__() 61 | 62 | self.fp_wz = footprint_wizard 63 | 64 | self.GenerateParameterList() 65 | 66 | def GetName(self): # Return the name of this wizard 67 | return self.fp_wz.name 68 | 69 | def GetImage(self): # Return the filename of the preview image associated with this wizard 70 | return self.fp_wz.image 71 | 72 | def GetDescription(self): # Return the description text 73 | return self.fp_wz.description 74 | 75 | def GetValue(self): 76 | return self.fp_wz.value 77 | 78 | def GetReferencePrefix(self): 79 | return self.fp_wz.reference_prefix 80 | 81 | def GenerateParameterList(self): 82 | params = ParameterMap() 83 | self.fp_wz.generate_parameter_list(params) 84 | 85 | for p in params: 86 | kwargs = {} 87 | if p.hint is not None: 88 | kwargs['hint'] = p.hint 89 | if p.designator is not None: 90 | kwargs['designator'] = p.designator 91 | if p.multiple is not None: 92 | kwargs['multiple'] = p.multiple 93 | if p.min_value is not None: 94 | kwargs['min_value'] = p.min_value 95 | if p.max_value is not None: 96 | kwargs['max_value'] = p.max_value 97 | 98 | unit = _FootprintWizardBase.UNIT_CONVERSATION_TABLE.get(p.type, _pcbnew.uString) 99 | self.AddParam(p.page, p.name, unit, p.default, **kwargs) 100 | 101 | def BuildFootprint(self): 102 | # reset variables which are used by footprint wizard 103 | self.buildmessages = '' 104 | self.module = _pcbnew.MODULE(None) 105 | 106 | module = Module(self.module) 107 | 108 | self.buildmessages += 'Building new {} footprint with the following parameters:\n'.format(self.GetName()) 109 | 110 | params = ParameterMap() 111 | self.fp_wz.generate_parameter_list(params) 112 | 113 | fp_params = ParsedParameterMap(params) 114 | 115 | for page in self.parameters: 116 | self.buildmessages += '{}\n'.format(page) 117 | parameters = self.parameters[page] 118 | for key in parameters: 119 | value = parameters[key] 120 | self.buildmessages += '\t{} = {}\n'.format(key, value) 121 | fp_params.add(key, value) 122 | 123 | module.value = self.fp_wz.value 124 | module.reference = "{}**".format(self.fp_wz.reference_prefix) 125 | 126 | fpid = _pcbnew.LIB_ID("", module.value) # the lib name (empty) and the name in library 127 | module.get_native().SetFPID(fpid) 128 | 129 | self.fp_wz.build_footprint(module, fp_params) 130 | 131 | 132 | class FootprintScriptingWizard(object): 133 | def __init__(self, wizard_class): 134 | # TODO: test if class can be created 135 | self.wizard_class = wizard_class 136 | 137 | def create(self): 138 | # type: () -> FootprintWizard 139 | """Create a new object of the given wizard class""" 140 | return self.wizard_class() 141 | 142 | def run_as_script(self): 143 | # type: () -> None 144 | """Run wizard from the command line""" 145 | wizard = self.create() 146 | params = ParameterMap() 147 | wizard.generate_parameter_list(params) 148 | 149 | parser = argparse.ArgumentParser(description=wizard.description) 150 | 151 | # parser.add_argument('--specifications', type=argparse.FileType('r'), nargs='*', 152 | # help='config files defining how the footprint will look like') 153 | parser.add_argument('definitions', type=argparse.FileType('r'), nargs='+', 154 | help='config files defining the footprint properties') 155 | parser.add_argument('--library', type=str, default='.', 156 | help='library path where to store the footprints') 157 | parser.add_argument('--dry-run', action='store_true', 158 | help='do not save the generated footprints') 159 | 160 | args = parser.parse_args() 161 | 162 | if not os.path.isdir(args.library): 163 | parser.error('"{}" is not a valid directory'.format(args.library)) 164 | 165 | if not _YAML_AVAILABLE: 166 | parser.error('PyYAML is required to be installed') 167 | 168 | for definition in args.definitions: 169 | param_generator = self.parse_yaml(params, definition) 170 | for fp_params in param_generator: 171 | print(fp_params) 172 | # TODO: duplicate with _FootprintWizardBase 173 | module = Module(_pcbnew.MODULE(None)) 174 | 175 | module.value = wizard.value # TODO: name? 176 | module.reference = "{}**".format(wizard.reference_prefix) 177 | 178 | fpid = _pcbnew.LIB_ID("", module.value) # the lib name (empty) and the name in library 179 | module.get_native().SetFPID(fpid) 180 | 181 | wizard.build_footprint(module, fp_params) 182 | 183 | fp_name = '{}.kicad_mod'.format(module.get_native().GetFPID().GetLibItemName()) 184 | print('* Footprint generated: "{}"'.format(os.path.join(args.library, fp_name))) 185 | 186 | if not args.dry_run: 187 | module.to_library(args.library) 188 | 189 | def parse_yaml(self, params, definition): 190 | if not _YAML_AVAILABLE: 191 | print("pyyaml not available!") 192 | sys.exit(1) 193 | 194 | parsed = yaml.safe_load(definition) 195 | 196 | for config in parsed: 197 | fp_params = ParsedParameterMap(params) 198 | for key in config: 199 | value = config[key] 200 | fp_params.add(key, value) 201 | # TODO: validate 202 | yield fp_params 203 | 204 | 205 | def register_footprint_wizard(wizard_class): 206 | # create object and register as footprint wizard 207 | registered_wizard = wizard_class() 208 | registered_wizard_base = _FootprintWizardBase(registered_wizard) 209 | registered_wizard_base.register() 210 | 211 | return FootprintScriptingWizard(wizard_class) 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | --------------------------------------------------------------------------------