diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | doc/Makefile | 177 | ||||
-rw-r--r-- | doc/make.bat | 242 | ||||
-rw-r--r-- | doc/source/about.rst | 40 | ||||
-rw-r--r-- | doc/source/conf.py | 262 | ||||
-rw-r--r-- | doc/source/documentation/excellon.rst | 42 | ||||
-rw-r--r-- | doc/source/documentation/index.rst | 10 | ||||
-rw-r--r-- | doc/source/documentation/operations.rst | 24 | ||||
-rw-r--r-- | doc/source/documentation/render.rst | 11 | ||||
-rw-r--r-- | doc/source/documentation/rs274x.rst | 37 | ||||
-rw-r--r-- | doc/source/features.rst | 14 | ||||
-rw-r--r-- | doc/source/index.rst | 24 | ||||
-rw-r--r-- | docs/aperture-macros.rst | 48 | ||||
-rw-r--r-- | docs/apertures.rst | 138 | ||||
-rw-r--r-- | docs/api-concepts.rst | 60 | ||||
-rw-r--r-- | docs/conf.py | 57 | ||||
-rw-r--r-- | docs/file-api.rst | 25 | ||||
-rw-r--r-- | docs/graphic-primitive-api.rst | 27 | ||||
-rw-r--r-- | docs/index.rst | 115 | ||||
-rw-r--r-- | docs/object-api.rst | 30 | ||||
-rw-r--r-- | docs/utilities.rst | 3 | ||||
-rw-r--r-- | gerbonara/apertures.py | 1 | ||||
-rwxr-xr-x | gerbonara/excellon.py | 10 | ||||
-rw-r--r-- | gerbonara/graphic_objects.py | 298 | ||||
-rw-r--r-- | gerbonara/graphic_primitives.py | 4 | ||||
-rw-r--r-- | gerbonara/rs274x.py | 11 | ||||
-rw-r--r-- | setup.py | 2 |
28 files changed, 788 insertions, 941 deletions
@@ -2,3 +2,4 @@ gerbonara_test_failures *.egg-info __pycache__ .tox +docs/_build/ @@ -1,13 +1,21 @@ -PYTHON ?= python -PYTEST ?= pytest +PYTHON ?= python +PYTEST ?= pytest +SPHINX_BUILD ?= sphinx-build + +all: docs sdist bdist_wheel .PHONY: clean -clean: doc-clean +clean: find . -name '*.pyc' -delete rm -rf *.egg-info rm -f .coverage rm -f coverage.xml + rm -rf docs/_build + +.PHONY: docs +docs: + sphinx-build -E docs docs/_build .PHONY: test test: @@ -30,7 +38,7 @@ bdist_wheel: python3 setup.py bdist_wheel upload: sdist bdist_wheel - twine upload -s -i contact@gerbonara.io --config-file ~/.pypirc --skip-existing --repository pypi dist/* + twine upload -s -i gerbonara@jaseg.de --config-file ~/.pypirc --skip-existing --repository pypi dist/* testupload: sdist bdist_wheel twine upload --config-file ~/.pypirc --skip-existing --repository testpypi dist/* diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 5c6b560..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make <target>' where <target> is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GerberTools.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GerberTools.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/GerberTools" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GerberTools" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index 2860659..0000000 --- a/doc/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
-set I18NSPHINXOPTS=%SPHINXOPTS% source
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
- set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^<target^>` where ^<target^> is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. texinfo to make Texinfo files
- echo. gettext to make PO message catalogs
- echo. changes to make an overview over all changed/added/deprecated items
- echo. xml to make Docutils-native XML files
- echo. pseudoxml to make pseudoxml-XML files for display purposes
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-
-%SPHINXBUILD% 2> nul
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.http://sphinx-doc.org/
- exit /b 1
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\GerberTools.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\GerberTools.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "latexpdf" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- cd %BUILDDIR%/latex
- make all-pdf
- cd %BUILDDIR%/..
- echo.
- echo.Build finished; the PDF files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "latexpdfja" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- cd %BUILDDIR%/latex
- make all-pdf-ja
- cd %BUILDDIR%/..
- echo.
- echo.Build finished; the PDF files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "texinfo" (
- %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
- goto end
-)
-
-if "%1" == "gettext" (
- %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-if "%1" == "xml" (
- %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The XML files are in %BUILDDIR%/xml.
- goto end
-)
-
-if "%1" == "pseudoxml" (
- %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
- goto end
-)
-
-:end
diff --git a/doc/source/about.rst b/doc/source/about.rst deleted file mode 100644 index 85e7184..0000000 --- a/doc/source/about.rst +++ /dev/null @@ -1,40 +0,0 @@ -About PCB Tools -=============== - - -PCB Tools provides a set of utilities for visualizing and working with PCB -design files in a variety of formats. The design files are generally referred -to as Gerber files. This is a generic term that may refer to -`RS-274X (Gerber) <http://en.wikipedia.org/wiki/Gerber_format>`_, -`ODB++ <http://en.wikipedia.org/wiki/ODB%2B%2B>`_ , -or `Excellon <http://en.wikipedia.org/wiki/Excellon_format>`_ files. These -file formats are used by the CNC equipment used to manufacutre PCBs. - -PCB Tools currently supports the following file formats: - -- Gerber (RS-274X) -- Excellon - -with planned support for IPC-2581, ODB++ and more. - -Image Rendering -~~~~~~~~~~~~~~~ -.. image:: ../../examples/cairo_example.png - :alt: Rendering Example - -The PCB Tools module provides tools to visualize PCBs and export images in a -variety of formats, including SVG and PNG. - - - - -Future Plans -~~~~~~~~~~~~ -We are working on adding the following features to PCB Tools: - -- Design Rules Checking -- Editing -- Panelization - - - diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 238a0b8..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,262 +0,0 @@ -# -*- coding: utf-8 -*-
-#
-# Gerber Tools documentation build configuration file, created by
-# sphinx-quickstart on Sun Sep 28 18:16:46 2014.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys
-import os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('../../'))
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.autosummary',
- 'numpydoc',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'PCB Tools'
-copyright = u'2014 Paulo Henrique Silva <ph.silva@gmail.com>, Hamilton Kibbe <ham@hamiltonkib.be>'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '0.1'
-# The full version, including alpha/beta/rc tags.
-release = '0.1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = []
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-add_module_names = False
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# Add any extra paths that contain custom files (such as robots.txt or
-# .htaccess) here, relative to this directory. These files are copied
-# directly to the root of the documentation.
-#html_extra_path = []
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'PCBToolsdoc'
-
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-# author, documentclass [howto, manual, or own class]).
-latex_documents = [
- ('index', 'PCBTools.tex', u'PCB Tools Documentation',
- u'Hamilton Kibbe', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output ---------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pcbtools', u'PCB Tools Documentation',
- [u'Hamilton Kibbe'], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- ('index', 'PCBTools', u'PCB Tools Documentation',
- u'Hamilton Kibbe', 'PCBTools', 'Tools for working with PCB CAM files.',
- 'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
diff --git a/doc/source/documentation/excellon.rst b/doc/source/documentation/excellon.rst deleted file mode 100644 index 9cd7ee1..0000000 --- a/doc/source/documentation/excellon.rst +++ /dev/null @@ -1,42 +0,0 @@ -:mod:`excellon` --- Excellon file handling -============================================== - -.. module:: excellon - :synopsis: Functions and classes for handling Excellon files -.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be> - - -The Excellon format is the most common format for exporting PCB drill -information. The Excellon format is used to program CNC drilling macines for -drilling holes in PCBs. As such, excellon files are sometimes refererred to as -NC-drill files. The Excellon format reference is available -`here <http://www.excellon.com/manuals/program.htm>`_. The :mod:`excellon` -submodule implements classes to read and write excellon files without having -to know the precise details of the format. - -The :mod:`excellon` submodule's :func:`read` function serves as a -simple interface for parsing excellon files. The :class:`ExcellonFile` class -stores all the information contained in an Excellon file allowing the file to -be analyzed, modified, and updated. The :class:`ExcellonParser` class is used -in the background for parsing RS-274X files. - -.. _excellon-contents: - -Functions ---------- -The :mod:`excellon` module defines the following functions: - -.. autofunction:: gerber.excellon.read - - -Classes -------- -The :mod:`excellon` module defines the following classes: - -.. autoclass:: gerber.excellon.ExcellonFile - :members: - - -.. autoclass:: gerber.excellon.ExcellonParser - :members: - diff --git a/doc/source/documentation/index.rst b/doc/source/documentation/index.rst deleted file mode 100644 index dec83f9..0000000 --- a/doc/source/documentation/index.rst +++ /dev/null @@ -1,10 +0,0 @@ -PCB Tools Reference -====================== - -.. toctree:: - :maxdepth: 2 - - Gerber (RS-274X) Files <rs274x> - Excellon Files <excellon> - Operations <operations> - Rendering <render> diff --git a/doc/source/documentation/operations.rst b/doc/source/documentation/operations.rst deleted file mode 100644 index 6551236..0000000 --- a/doc/source/documentation/operations.rst +++ /dev/null @@ -1,24 +0,0 @@ -:mod:`operations` --- Cam File operations -========================================= - -.. module:: operations - :synopsis: Functions for modifying CAM files -.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be> - - -The :mod:`operations` module provides functions which modify -:class:`gerber.cam.CamFile` objects. All of the functions in this module -return a modified copy of the supplied file. - -.. _operations-contents: - -Functions ---------- -The :mod:`operations` module defines the following functions: - -.. autofunction:: gerber.operations.to_inch -.. autofunction:: gerber.operations.to_metric -.. autofunction:: gerber.operations.offset - - - diff --git a/doc/source/documentation/render.rst b/doc/source/documentation/render.rst deleted file mode 100644 index 324ef71..0000000 --- a/doc/source/documentation/render.rst +++ /dev/null @@ -1,11 +0,0 @@ -:mod:`render` --- Gerber file Rendering -============================================== - -.. module:: render - :synopsis: Functions and classes for handling Excellon files -.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be> - -Render Module -------------- -.. automodule:: gerber.render.render - :members: diff --git a/doc/source/documentation/rs274x.rst b/doc/source/documentation/rs274x.rst deleted file mode 100644 index 064c82f..0000000 --- a/doc/source/documentation/rs274x.rst +++ /dev/null @@ -1,37 +0,0 @@ -:mod:`rs274x` --- RS-274X file handling -============================================== - -.. module:: rs274x - :synopsis: Functions and classes for handling RS-274X files -.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be> - - -The RS-274X (Gerber) format is the most common format for exporting PCB -artwork. The Specification is published by Ucamco and is available -`here <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_. -The :mod:`rs274x` submodule implements classes to read and write -RS-274X files without having to know the precise details of the format. - -The :mod:`rs274x` submodule's :func:`read` function serves as a -simple interface for parsing gerber files. The :class:`GerberFile` class -stores all the information contained in a gerber file allowing the file to be -analyzed, modified, and updated. The :class:`GerberParser` class is used in -the background for parsing RS-274X files. - -.. _gerber-contents: - -Functions ---------- -The :mod:`rs274x` module defines the following functions: - -.. autofunction:: gerber.rs274x.read - -Classes -------- -The :mod:`rs274x` module defines the following classes: - -.. autoclass:: gerber.rs274x.GerberFile - :members: - -.. autoclass:: gerber.rs274x.GerberParser - :members: diff --git a/doc/source/features.rst b/doc/source/features.rst deleted file mode 100644 index 67d9e2a..0000000 --- a/doc/source/features.rst +++ /dev/null @@ -1,14 +0,0 @@ -Feature Suppport -================ - -Currently supported features are as follows: - -============ ======== =========== ================ ====== ======= ======= -File Format Parsing Rendering Unit Conversion Scale Offset Rotate -============ ======== =========== ================ ====== ======= ======= -RS274-X Yes Yes Yes No Yes No -Excellon Yes Yes Yes No Yes No -ODB++ No No No No No No -============ ======== =========== ================ ====== ======= ======= - - diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index c96ff8a..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. PCB-tools documentation master file, created by
- sphinx-quickstart on Sun Sep 28 18:16:46 2014.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-PCB-Tools
-========================================
-
-Contents:
-
-.. toctree::
- :maxdepth: 1
-
- about
- features
- documentation/index
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/docs/aperture-macros.rst b/docs/aperture-macros.rst new file mode 100644 index 0000000..1284c49 --- /dev/null +++ b/docs/aperture-macros.rst @@ -0,0 +1,48 @@ +Aperture Macros +=============== + +.. autoclass:: gerbonara.aperture_macros.parse.ApertureMacro + :members: + +.. autoclass:: gerbonara.aperture_macros.parse.GenericMacros + :members: + +.. autoclass:: gerbonara.aperture_macros.expression.Expression + :members: + +.. autoclass:: gerbonara.aperture_macros.expression.UnitExpression + :members: + +.. autoclass:: gerbonara.aperture_macros.expression.ConstantExpression + :members: + +.. autoclass:: gerbonara.aperture_macros.expression.VariableExpression + :members: + +.. autoclass:: gerbonara.aperture_macros.expression.OperatorExpression + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.Primitive + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.Circle + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.VectorLine + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.CenterLine + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.Polygon + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.Thermal + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.Outline + :members: + +.. autoclass:: gerbonara.aperture_macros.primitive.Comment + :members: + diff --git a/docs/apertures.rst b/docs/apertures.rst new file mode 100644 index 0000000..2a17a04 --- /dev/null +++ b/docs/apertures.rst @@ -0,0 +1,138 @@ +Apertures in Gerbonara +====================== + +Gerbonara maps all standard Gerber apertures to subclasses of the Aperture_ class. These subclasses: CircleAperture_, +RectangleAperture_, ObroundAperture_ and PolygonAperture_. Aperture macro instantiations get mapped to +ApertureMacroInstance_ (also an Aperture_ subclass). + +All Aperture_ subclasses have these common attributes: + + +`hole_dia` + float with diameter of hole. 0 for no hole. + +`hole_rect_h` + float or None. If not None, specifies a rectangular hole of size `hole_dia * hole_rect_h` instead of a round hole. + +`unit` + LengthUnit_ for all of this aperture's fields + +`attrs` + GerberX2 attributes of this aperture. Note that this will only contain aperture attributes, not file attributes. + File attributes are stored in the `attrs` of GerberFile_. + +`original_number` + int of aperture index this aperture had when it was read from the Gerber file. This field is purely informational + since apertures are de-duplicated and re-numbered when writing a Gerber file. For `D10`, this field would be `10`. + If you programmatically create a new aperture, you do not have to set this. + +`rotation` + Aperture rotation in radians counter-clockwise. This field is not part of the Gerber standard. Standard rectangle + and obround apertures do not support rotation. Gerbonara converts rotated apertures into aperture macros during + Gerber export as necessary. + +CircleAperture +-------------- + +This is the only one valid for use in Line_ or Arc_. + +Attributes: + +Common attributes: + `hole_dia`, `hole_rect_h`, `unit`, `attrs`, and `original_number`. `rotation` is present but has no effect in + CircleAperture_. + +`diameter` + float with diameter of aperture in the unit from the aperture's `unit` field. + +RectangleAperture +----------------- + +Common attributes: + `hole_dia`, `hole_rect_h`, `unit`, `attrs`, `original_number`, and `rotation` + +`w`, `h` + floats with width or height of rectangle in units from the aperture's `unit` field. + +ObroundAperture +--------------- + +Aperture whose shape is the convex hull of two circles of equal radii. + +Common attributes: + `hole_dia`, `hole_rect_h`, `unit`, `attrs`, `original_number`, and `rotation` + +`w`, `h` + floats with width and height of bounding box of obround. The smaller one of these will be the diameter of the + obround's ends. If `w` is larger, the result will be a landscape obround. If `h` is larger, it will be a portrait + obround. + +PolygonAperture +--------------- + +Aperture whose shape is a regular n-sided polygon (e.g. pentagon, hexagon etc.). + + +Common attributes: + `hole_dia`, `unit`, `attrs`, `original_number`, and `rotation`. `hole_rect_h` is not supported in PolygonAperture_ + since the Gerber spec does not list it. + +`diameter` + float with diameter of circumscribing circle, i.e. the circle that all the polygon's corners lie on. + +`n_vertices` + int with number of corners of this polygon. Three for a triangle, four for a square, five for a pentagon etc. + +ApertureMacroInstance +--------------------- + +One instance of an aperture macro. An aperture macro defined with an `AM` statement can be instantiated by multiple `AD` +aperture definition statements using different parameters. An ApertureMacroInstance_ is one such binding of a macro to a +particular set of parameters. Note that you still need an ApertureMacroInstance_ even if your ApertureMacro_ has no +parameters since an ApertureMacro_ is not an Aperture_ by itself. + +Attributes: + +Common attributes: + `unit`, `attrs`, `original_number`, and `rotation`. ApertureMacroInstance_ does not support `hole_dia` or + `hole_rect_h`. `rotation` is handled by re-writing the ApertureMacro_ during export. + +`macro` + The ApertureMacro_ that is bound here + +`parameters` + list of ints or floats with the parameters for this macro. The first element is `$1`, the second is `$2` etc. + +ExcellonTool +------------ + +Special Aperture_ subclass for use in ExcellonFile_. Similar to CircleAperture_, but does not have `hole_dia` or +`hole_rect_h`, and has additional `plated` and `depth_offset` attributes. + + +Common attributes: + `unit`, `original_number` + +`plated` + bool or None. True if this hole/slot is copper-plated, False if not, and None if it is undefined or unknown. + +`depth_offset` + float with Excellon depth offset for this hole or slot. If the fab supports this, this can be used to create + features that do not go all the way through the board. + +Aperture generalization +----------------------- + +Gerbonara supports rotating both individual graphic objects and whole files. Alas, this was not a use case that was +intended when the Gerber format was developed. We can rotate lines, arcs, and regions alright by simply rotatint all of +their points. Flashes are where things get tricky: Individual flashes cannot be rotated at all in any widely supported +way. There are some newer additions to the standard, but I would be surprised if any of the cheap board houses +understand those. The only way to rotate a flash is to rotate the aperture, not the flash. For cirlces, this is a no-op. +For polygons, we simply change the angle parameter. However, for rectangles and obrounds this gets tricky: Neither one +supports a rotation parameter. The only way to rotate these is to convert them to an aperture macro, then rotate that. + +This behavior of using aperture macros for general rotated rectangles is common behavior among CAD tools. Gerbonara adds +a non-standard `rotation` attribute to all apertures except CircleAperture_ and transparently converts rotated instances +to the appropriate ApertureMacroInstance_ objects while it writes out the file. Be aware that this may mean that an +object that in memory has a RectangleAperture_ might end up with an aperture macro instance in the output Gerber file. + diff --git a/docs/api-concepts.rst b/docs/api-concepts.rst new file mode 100644 index 0000000..075498b --- /dev/null +++ b/docs/api-concepts.rst @@ -0,0 +1,60 @@ +Gerbonara API concepts +====================== + +High-level overview +------------------- + +Gerbonara's API is split into three larger sub-areas: + +**File API** + This is where the main user interface classes live: :py:class:`.LayerStack` (for opening a directory/zip full of + files, and automatically matching file roles based on filenames), :py:class:`.GerberFile` (for opening an individual + RS-274X file), :py:class:`.ExcellonFile` (for Excellon drill files) and :py:class:`.Netlist` (for IPC-356 netlist + files). + +**Graphic Object API** + This is where the nuts and bolts inside a :py:class:`.GerberFile` or :py:class:`.ExcellonFile` such as + :py:class:`~.graphic_objects.Line`, :py:class:`~.graphic_objects.Arc`, :py:class:`.Region` and :py:class:`.Flash` + live. Everything in here has explicit unit support. A part of the Graphic object API is the :doc:`Aperture + API<apertures>`. + +**Graphic Primitive API** + This is a rendering abstraction layer. Graphic objects can be converted into graphic primitives for rendering. + Graphic primitives are unit-less. Units are converted during :py:class:`.GraphicObject` to + :py:class:`.GraphicPrimitive` rendering. + +The hierarchy works like: A :py:class:`.LayerStack` contains either a :py:class:`.GerberFile`, an +:py:class:`.ExcellonFile` or a :py:class:`.Netlist` for each layer. Each of these file objects contains a number of +:py:class:`.GraphicObject` instances such as :py:class:`~.graphic_objects.Line` or :py:class:`.Flash`. These objects can +easily be changed or deleted, and new ones can be created programmatically. For rendering, each of these objects as well +as file objects can be rendered into :py:class:`.GraphicPrimitive` instances using +:py:meth:`.GraphicObject.to_primitives`. + +Apertures +--------- + +Gerber apertures are represented by subclasses of :py:class:`.Aperture` such as :py:class:`.CircleAperture`. An instance +of an aperture class is stored inside the :py:attr:`~.graphic_objects.Line.aperture` field of a +:py:class:`.GraphicObject`. :py:class:`.GraphicObject` subclasses that have an aperture are +:py:class:`~.graphic_objects.Line`, :py:class:`~.graphic_objects.Arc` and :py:class:`.Flash`. You can create and +duplicate :py:class:`.Aperture` objects as needed. They are automatically de-duplicated when a Gerber file is written. + +Gerbonara has full aperture macro support. Each aperture macro is represented by an :py:class:`.parse.ApertureMacro` +instance. Like apertures, :py:class:`.parse.ApertureMacro` instances are de-duplicated when writing a file. An aperture +macro-based aperture definition is represented by the :py:class:`.ApertureMacroInstance` subclass of +:py:class:`.Aperture`. An aperture macro instance basically binds an aperture macro to a given set of macro parameters. +Note that even if a macro does not accept any parameters you still cannot directly stick it into the aperture field of a +graphic object, and instead need to wrap it inside an :py:class:`.ApertureMacroInstance` first. + +Excellon vs. Gerber +------------------- + +Excellon files use the same graphic object classes as Gerber files. Inside an Excellon file, only +:py:class:`~.graphic_objects.Line`, :py:class:`~.graphic_objects.Arc` and :py:class:`.Flash` are allowed. Lines and arcs map to milled +Excellon slots. Excellon drills are mapped to :py:class:`.Flash` instances. + +Excellon drills are internally handled using a special :py:class:`.ExcellonTool` aperture class. When you put a +:py:class:`.GraphicObject` from an Excellon file into a Gerber file, these become circular apertures. You can also take +objects from an Excellon file and put them into a Gerber file if they have a simple :py:class:`.CircleAperture`. Copying +objects with other apertures into an Excellon file will raise an error when saving. + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..915f0dc --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,57 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +from pathlib import Path +import sys +sys.path.insert(0, str(Path(__file__).parent.parent.absolute())) + +# -- Project information ----------------------------------------------------- + +project = 'gerbonara' +copyright = '2022, Jan Götte' +author = 'jaseg' + +# The full version, including alpha/beta/rc tags +release = '0.9.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', +] + +autodoc_member_order = 'groupwise' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/file-api.rst b/docs/file-api.rst new file mode 100644 index 0000000..d73b5f1 --- /dev/null +++ b/docs/file-api.rst @@ -0,0 +1,25 @@ +Layers and Files +================ + +Gerbonara currently supports three file types: RS-274-X Gerber as `specified by Ucamco +<https://www.ucamco.com/en/gerber>`:py:class:`._` through :py:class:`.GerberFile`, Excellon/XNC through +:py:class:`.ExcellonFile`, and IPC-356 netlists through :py:class:`.Netlist`. + +Usually, a PCB is sent to a manufacturer as a bundle of several of these files. Such a bundle of files (each of which is +either a :py:class:`.GerberFile` or an :py:class:`.ExcellonFile`) is represented by :py:class:`.LayerStack`. +:py:class:`.LayerStack` contains logic to automatcally +recognize a wide variety of CAD tools from file name and syntactic hints, and can automatically match all files in a +folder to their appropriate layers. + +.. autoclass:: gerbonara.layers.LayerStack + :members: + +.. autoclass:: gerbonara.rs274x.GerberFile + :members: + +.. autoclass:: gerbonara.excellon.ExcellonFile + :members: + +.. autoclass:: gerbonara.ipc356.Netlist + :members: + diff --git a/docs/graphic-primitive-api.rst b/docs/graphic-primitive-api.rst new file mode 100644 index 0000000..d506e87 --- /dev/null +++ b/docs/graphic-primitive-api.rst @@ -0,0 +1,27 @@ +Graphic Primitives +================== + +.. autoclass:: gerbonara.graphic_primitives.GraphicPrimitive + :members: + +.. autoclass:: gerbonara.graphic_primitives.Circle + :members: + +.. autoclass:: gerbonara.graphic_primitives.Obround + :members: + +.. autoclass:: gerbonara.graphic_primitives.ArcPoly + :members: + +.. autoclass:: gerbonara.graphic_primitives.Line + :members: + +.. autoclass:: gerbonara.graphic_primitives.Arc + :members: + +.. autoclass:: gerbonara.graphic_primitives.Rectangle + :members: + +.. autoclass:: gerbonara.graphic_primitives.RegularPolygon + :members: + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..55d2456 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,115 @@ +Welcome to gerbonara's documentation! +===================================== + +Gerbonara is a library to read, modify and write PCB manufacturing files such as Gerber, Excellon and IPC-356 through a +pythonic API. Gerbonara can open a folder of manufacturing files, and parse file names and metadata to figure out which +file contains what. Gerbonara is tested using an extensive library of real-world example files from CAD tools including +KiCAD, Altium, Eagle, Allegro, gEDA, Fritzing, Siemens/Mentor Graphics PADS, and Target3001!. + +Gerbonara's API is built on two principles: + +**Meaningful, object-oriented API** + Gerbonara abstracts away the details of the underlying file format such as tool indices, coordinate notation and + graphical state, and presents meaningful "graphical objects" such as a :py:class:`~primitives.Line`, + :py:class:`~primitives.Arc`, or :py:class:`.Region` through its API. These objects can be easily created, + manipulated or deleted from code without breaking anything else. You can even copy graphical objects between files, + and Gerbonara will automatically convert coordinate format, units etc. for you. :py:class:`.GerberFile` and + :py:class:`.ExcellonFile` use the same types of :doc:`graphic objects <object-api>`, so objects can be directly + copied between file types without conversion. + +**Unit-safety** + Gerbonara embeds physical :py:class:`.LengthUnit` information in all objects. The high-level API such as + :py:meth:`.LayerStack.merge` or :py:meth:`.GerberFile.offset` accepts arguments with an explicitly given unit and + automatically converts them as needed. Objects can be copied between :py:class:`.GerberFile` instances and unit + conversion will be handled transparently in the background. + +Gerbonara was started as an extensive refactoring of the pcb-tools_ and pcb-tools-extension_ packages. Both of these +have statement-based APIs, that is, they parse input files into one python object for every line in the file. This means +that when saving files they can recreate the input file almost byte by byte, but manipulating a file by changing +statements without breaking things is *hard*. + +Gerbonara powers gerbolyze_, a tool for converting SVG_ vector graphics files into Gerber, and embedding SVG_ into +existing Gerber files exported from a normal PCB tool for artistic purposes. + +Features +======== + + * File I/O + * Gerber, Excellon (drill file), IPC-356 (netlist) read and write + * supports file-level operations: offset, rotate, merge for all file types + * Modification API (:py:class:`GraphicObject`) + * Rendering API (:py:class:`GraphicPrimitive`) + * SVG export + * Full aperture macro support, including transformations (offset, rotation) + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api-concepts + file-api + object-api + apertures + aperture-macros + graphic-primitive-api + utilities + +Quick Start +=========== + + + +Development +=========== + +Gerbonara is developed on Gitlab under the gerbolyze org: + +https://gitlab.com/gerbolyze/gerbonara/ + +A mirror of the repository can be found at: + +https://git.jaseg.de/gerbonara + +Our issue tracker is also on Gitlab: + +https://gitlab.com/gerbolyze/gerbonara/-/issues + +With Gebronara, we aim to support as many different format variants as possible. If you have a file that Gerbonara can't +open, please file an issue on our issue tracker. Even if Gerbonara can open all your files, for regression testing we +are very interested in example files generated by any CAD or CAM tool that is not already on the list of supported +tools. + +Supported CAD Tools +=================== + +Compatibility with the output of these CAD tools is tested as part of our test suite using example files generated by +these tools. Note that not all of these tools come with default Gerber file naming rules, so YMMV if your Gerbers use +some non-standard naming convention. + + * Allegro + * Altium + * Diptrace + * Eagle + * EasyEDA + * Fritzing + * gEDA + * KiCAD + * pcb-rnd + * Siemens / Mentor Graphics Xpedition + * Siemens / Mentor Graphics PADS + * Target 3001! + * Upverter + * Soon: Zuken CADSTAR and CR-8000 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. _pcb-tools: https://github.com/opiopan/pcb-tools-extension +.. _pcb-tools-extension: https://github.com/curtacircuitos/pcb-tools/issues +.. _gerbolyze: https://github.com/jaseg/gerbolyze +.. _SVG: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics + diff --git a/docs/object-api.rst b/docs/object-api.rst new file mode 100644 index 0000000..f6345b9 --- /dev/null +++ b/docs/object-api.rst @@ -0,0 +1,30 @@ +Graphic Objects +=============== + +Graphic objects are the lego blocks a gerbonara :py:class:`gerbonara.rs274x.GerberFile` or +:py:class:`gerbonara.excellon.ExcellonFile` is built from. They are stored in the file's +:py:attr:`gerbonara.rs274x.GerberFile.objects` list. You can directly manipulate that list from code. + +There are four graphic object types: :py:class:`gerbonara.graphic_objects.Flash`, +:py:class:`gerbonara.graphic_objects.Line`, :py:class:`gerbonara.graphic_objects.Arc`, and +:py:class:`gerbonara.graphic_objects.Region` . All of them are derived from +:py:class:`gerbonara.graphic_objects.GraphicObject`. + +.. autoclass:: gerbonara.graphic_objects.GraphicObject + :members: + +.. autoclass:: gerbonara.graphic_objects.Flash + :members: + +.. autoclass:: gerbonara.graphic_objects.Line + :members: + +.. autoclass:: gerbonara.graphic_objects.Arc + :members: + +.. autoclass:: gerbonara.graphic_objects.Region + :members: + +.. _pcb-tools: https://github.com/opiopan/pcb-tools-extension +.. _gerbolyze: https://github.com/jaseg/gerbolyze +.. _svg-flatten: https://github.com/jaseg/gerbolyze/tree/main/svg-flatten diff --git a/docs/utilities.rst b/docs/utilities.rst new file mode 100644 index 0000000..89b1cde --- /dev/null +++ b/docs/utilities.rst @@ -0,0 +1,3 @@ +Utilities +========= + diff --git a/gerbonara/apertures.py b/gerbonara/apertures.py index ab62077..086d5b1 100644 --- a/gerbonara/apertures.py +++ b/gerbonara/apertures.py @@ -95,6 +95,7 @@ class Aperture: @dataclass(unsafe_hash=True) class ExcellonTool(Aperture): + gerber_shape_code = 'C' human_readable_shape = 'drill' diameter : Length(float) plated : bool = None diff --git a/gerbonara/excellon.py b/gerbonara/excellon.py index 3382ffe..96a78be 100755 --- a/gerbonara/excellon.py +++ b/gerbonara/excellon.py @@ -197,6 +197,9 @@ class ExcellonFile(CamFile): else: self.objects.append(obj_or_comment) + def to_excellon(self): + return self + def to_gerber(self): apertures = {} out = GerberFile() @@ -292,7 +295,7 @@ class ExcellonFile(CamFile): yield 'M30' - def to_excellon(self, settings=None, drop_comments=True): + def generate_excellon(self, settings=None, drop_comments=True): ''' Export to Excellon format. This function always generates XNC, which is a well-defined subset of Excellon. ''' if settings is None: @@ -306,10 +309,11 @@ class ExcellonFile(CamFile): def save(self, filename, settings=None, drop_comments=True): with open(filename, 'w') as f: - f.write(self.to_excellon(settings, drop_comments=drop_comments)) + f.write(self.generate_excellon(settings, drop_comments=drop_comments)) def offset(self, x=0, y=0, unit=MM): - self.objects = [ obj.with_offset(x, y, unit) for obj in self.objects ] + for obj in self.objects: + obj.offset(x, y, unit) def rotate(self, angle, cx=0, cy=0, unit=MM): if math.isclose(angle % (2*math.pi), 0): diff --git a/gerbonara/graphic_objects.py b/gerbonara/graphic_objects.py index 1f475a6..99f990f 100644 --- a/gerbonara/graphic_objects.py +++ b/gerbonara/graphic_objects.py @@ -1,8 +1,9 @@ import math +import copy from dataclasses import dataclass, KW_ONLY, astuple, replace, field, fields -from .utils import MM, InterpMode +from .utils import MM, InterpMode, to_unit from . import graphic_primitives as gp @@ -18,27 +19,94 @@ class Length: def __init__(self, obj_type): self.type = obj_type + def __repr__(self): + # This makes the automatically generated method signatures in the Sphinx docs look nice + return 'float' + @dataclass -class GerberObject: +class GraphicObject: + """ Base class for the graphic objects that make up a :py:class:`gerbonara.rs274x.GerberFile` or + :py:class:`gerbonara.excellon.ExcellonFile`. """ _ : KW_ONLY + + #: bool representing the *color* of this feature: whether this is a *dark* or *clear* feature. Clear and dark are + #: meant in the sense that they are used in the Gerber spec and refer to whether the transparency film that this + #: file describes ends up black or clear at this spot. In a standard green PCB, a *polarity_dark=True* line will + #: show up as copper on the copper layer, white ink on the silkscreen layer, or an opening on the soldermask layer. + #: Clear features erase dark features, they are not transparent in the colloquial meaning. This property is ignored + #: for features of an :py:class:`gerbonara.excellon.ExcellonFile`. polarity_dark : bool = True + + #: :py:class:`gerbonara.utils.LengthUnit` used for all coordinate fields of this feature (such as `x` or `y`). unit : str = None + + + #: `dict` containing GerberX2 attributes attached to this feature. Note that this does not include file attributes, + #: which are stored in the :py:class:`gerbonara.rs274x.GerberFile` object instead. attrs : dict = field(default_factory=dict) def converted(self, unit): - return replace(self, - **{ f.name: self.unit.convert_to(unit, getattr(self, f.name)) - for f in fields(self) if type(f.type) is Length }) + """ Convert this gerber object to another :py:class:`gerbonara.utils.LengthUnit`. + + :param unit: Either a :py:class:`gerbonara.utils.LengthUnit` instance or one of the strings ``'mm'`` or ``'inch'``. + + :returns: A copy of this object using the new unit. + """ + copy = copy.copy(self) + copy.convert_to(unit) + + def convert_to(self, unit): + """ Convert this gerber object to another :py:class:`gerbonara.utils.LengthUnit` in-place. + + :param unit: Either a :py:class:`gerbonara.utils.LengthUnit` instance or one of the strings ``'mm'`` or ``'inch'``. + """ + + for f in fields(self): + if type(f.type) is Length: + setattr(self, f.name, self.unit.convert_to(unit, getattr(self, f.name))) + + self.unit = to_unit(unit) + + def offset(self, dx, dy, unit=MM): + """ Add an offset to the location of this feature. The location can be given in either unit, and is + automatically converted into this object's local unit. + + :param float dx: X offset, positive values move the object right. + :param float dy: Y offset, positive values move the object up. This is the opposite of the normal screen + coordinate system used in SVG and other computer graphics APIs. + """ - def with_offset(self, dx, dy, unit=MM): dx, dy = self.unit(dx, unit), self.unit(dy, unit) - return self._with_offset(dx, dy) + self._offset(dx, dy) def rotate(self, rotation, cx=0, cy=0, unit=MM): + """ Rotate this object. The center of rotation can be given in either unit, and is automatically converted into + this object's local unit. + + .. note:: The center's Y coordinate as well as the angle's polarity are flipped compared to computer graphics + convention since Gerber uses a bottom-to-top Y axis. + + :param float rotation: rotation in radians clockwise. + :param float cx: X coordinate of center of rotation in *unit* units. + :param float cy: Y coordinate of center of rotation. (0,0) is at the bottom left of the image. + :param unit: :py:class:`gerbonara.utils.LengthUnit` or str with unit for *cx* and *cy* + """ + cx, cy = self.unit(cx, unit), self.unit(cy, unit) self._rotate(rotation, cx, cy) def bounding_box(self, unit=None): + """ Return axis-aligned bounding box of this object in given unit. If no unit is given, return the bounding box + in the object's local unit (``self.unit``). + + .. note:: This method returns bounding boxes in a different format than legacy pcb-tools_, which used + ``(min_x, max_x), (min_y, max_y)`` + + :param unit: :py:class:`gerbonara.utils.LengthUnit` or str with unit for return value. + + :returns: tuple of tuples of floats: ``(min_x, min_y), (max_x, max_y)`` + """ + bboxes = [ p.bounding_box() for p in self.to_primitives(unit) ] min_x = min(min_x for (min_x, _min_y), _ in bboxes) min_y = min(min_y for (_min_x, min_y), _ in bboxes) @@ -47,16 +115,62 @@ class GerberObject: return ((min_x, min_y), (max_x, max_y)) def to_primitives(self, unit=None): - raise NotImplementedError() + """ Render this object into low-level graphical primitives (subclasses of :py:class:`GraphicPrimitive`). This + computes out all coordinates in case aperture macros are involved, and resolves units. The output primitives are + converted into the given unit, and will be stripped of unit information. If no unit is given, use this object's + native unit (``self.unit``). + + :param unit: :py:class:`gerbonara.utils.LengthUnit` or str with unit for return value. + + :rtype: Iterator[:py:class:`GraphicPrimitive`] + """ + return self._to_primitives(unit) + + def _to_statements(self, gs): + """ Serialize this object into Gerber statements. + + :param gs: :py:class:`rs274x.GraphicsState` object containing current Gerber state (polarity, selected aperture, + interpolation mode etc.). + + :returns: Iterator yielding one string per line of output Gerber + :rtype: Iterator[str] + """ + self._to_statements(gs) + + def _to_xnc(self, ctx): + """ Serialize this object into XNC Excellon statements. + + :param ctx: :py:class:`excellon.ExcellonContext` object containing current Excellon state (selected tool, + interpolation mode etc.). + + :returns: Iterator yielding one string per line of output XNC code + :rtype: Iterator[str] + """ + self._to_xnc(ctx) + @dataclass -class Flash(GerberObject): +class Flash(GraphicObject): + """ A flash is what happens when you "stamp" a Gerber aperture at some location. The :py:attr:`polarity_dark` + attribute that Flash inherits from :py:class:`GraphicObject` is ``True`` for normal flashes. If you set a Flash's + ``polarity_dark`` to ``False``, you invert the polarity of all of its features. + + Flashes are also used to represent drilled holes in an :py:class:`gerbonara.excellon.ExcellonFile`. In this case, + :py:attr:`aperture` should be an instance of :py:class:`ExcellonTool`. + """ + + #: float with X coordinate of the center of this flash. x : Length(float) + + #: float with Y coordinate of the center of this flash. y : Length(float) + + #: Flashed Aperture. must be a subclass of :py:class:`Aperture`. aperture : object @property def tool(self): + """ Alias for :py:attr:`aperture` for use inside an :py:class:`gerbonara.excellon.ExcellonFile`. """ return self.aperture @tool.setter @@ -65,19 +179,23 @@ class Flash(GerberObject): @property def plated(self): - return self.tool.plated + """ (Excellon only) Returns if this is a plated hole. ``True`` (plated), ``False`` (non-plated) or ``None`` + (plating undefined) + """ + return getattr(self.tool, 'plated', None) - def _with_offset(self, dx, dy): - return replace(self, x=self.x+dx, y=self.y+dy) + def __offset(self, dx, dy): + self.x += dx + self.y += dy def _rotate(self, rotation, cx=0, cy=0): self.x, self.y = gp.rotate_point(self.x, self.y, rotation, cx, cy) - def to_primitives(self, unit=None): + def _to_primitives(self, unit=None): conv = self.converted(unit) yield from self.aperture.flash(conv.x, conv.y, unit, self.polarity_dark) - def to_statements(self, gs): + def _to_statements(self, gs): yield from gs.set_polarity(self.polarity_dark) yield from gs.set_aperture(self.aperture) @@ -87,7 +205,7 @@ class Flash(GerberObject): gs.update_point(self.x, self.y, unit=self.unit) - def to_xnc(self, ctx): + def _to_xnc(self, ctx): yield from ctx.select_tool(self.tool) yield from ctx.drill_mode() @@ -97,11 +215,31 @@ class Flash(GerberObject): ctx.set_current_point(self.unit, self.x, self.y) + # internally used to compute Excellon file path length def curve_length(self, unit=MM): return 0 -class Region(GerberObject): +class Region(GraphicObject): + """ Gerber "region", roughly equivalent to what in computer graphics you would call a polygon. A region is a single + filled area defined by a list of coordinates on its contour. A region's polarity is its "fill". A region does not + have a "stroke", and thus does not have an `aperture` field. Note that regions are a strict subset of what modern + computer graphics considers a polygon or path. Be careful when converting shapes from somewhere else into Gerber + regions. For arbitrary shapes (e.g. SVG paths) this is non-trivial, and I recommend you hava look at Gerbolyze_ / + svg-flatten_. Here's a list of special features of Gerber regions: + + * A region's outline consists of straigt line segments and circular arcs and must always be closed. + * A region is always exactly one connected component. + * A region must not overlap itself anywhere. + * A region cannot have holes. + + There is one exception from the last two rules: To emulate a region with a hole in it, *cut-ins* are allowed. At a + cut-in, the region is allowed to touch (but never overlap!) itself. + + :attr poly: :py:class:`graphic_primitives.ArcPoly` describing the actual outline of this Region. The coordinates of + this poly are in the unit of this instance's :py:attr:`unit` field. + """ + def __init__(self, outline=None, arc_centers=None, *, unit, polarity_dark): super().__init__(unit=unit, polarity_dark=polarity_dark) outline = [] if outline is None else outline @@ -114,11 +252,8 @@ class Region(GerberObject): def __bool__(self): return bool(self.poly) - def _with_offset(self, dx, dy): - return Region([ (x+dx, y+dy) for x, y in self.poly.outline ], - self.poly.arc_centers, - polarity_dark=self.polarity_dark, - unit=self.unit) + def _offset(self, dx, dy): + self.poly.outline = [ (x+dx, y+dy) for x, y in self.poly.outline ] def _rotate(self, angle, cx=0, cy=0): self.poly.outline = [ gp.rotate_point(x, y, angle, cx, cy) for x, y in self.poly.outline ] @@ -138,7 +273,7 @@ class Region(GerberObject): else: self.poly.arc_centers.append(None) - def to_primitives(self, unit=None): + def _to_primitives(self, unit=None): self.poly.polarity_dark = self.polarity_dark # FIXME: is this the right spot to do this? if unit == self.unit: yield self.poly @@ -188,17 +323,37 @@ class Region(GerberObject): yield 'G37*' @dataclass -class Line(GerberObject): - # Line with *round* end caps. +class Line(GraphicObject): + """ A line is what happens when you "drag" a Gerber :py:class:`Aperture` from one point to another. Note that Gerber + lines are substantially funkier than normal lines as we know them from modern computer graphics such as SVG. A + Gerber line is defined as the area that is covered when you drag its aperture along. This means that for a + rectangular aperture, a horizontal line and a vertical line using the same aperture will have different widths. + + .. warning:: Try to only ever use :py:class:`CircleAperture` with :py:class:`Line` and :py:class:`Arc` since other + aperture types are not widely supported by renderers / photoplotters even though they are part of the + spec. + + .. note:: If you manipulate a :py:class:`Line`, it is okay to assume that it has round end caps and a defined width + as exceptions are really rare. + """ + #: X coordinate of start point x1 : Length(float) + #: Y coordinate of start point y1 : Length(float) + #: X coordinate of end point x2 : Length(float) + #: Y coordinate of end point y2 : Length(float) + #: Aperture for this line. Should be a subclass of :py:class:`CircleAperture`, whose diameter determines the line + #: width. aperture : object - def _with_offset(self, dx, dy): - return replace(self, x1=self.x1+dx, y1=self.y1+dy, x2=self.x2+dx, y2=self.y2+dy) + def _offset(self, dx, dy): + self.x1 += dx + self.y1 += dy + self.x2 += dx + self.y2 += dy def _rotate(self, rotation, cx=0, cy=0): self.x1, self.y1 = gp.rotate_point(self.x1, self.y1, rotation, cx, cy) @@ -206,18 +361,17 @@ class Line(GerberObject): @property def p1(self): + """ Convenience alias for ``(self.x1, self.y1)`` returning start point of the line. """ return self.x1, self.y1 @property def p2(self): + """ Convenience alias for ``(self.x2, self.y2)`` returning end point of the line. """ return self.x2, self.y2 @property - def end_point(self): - return self.p2 - - @property def tool(self): + """ Alias for :py:attr:`aperture` for use inside an :py:class:`gerbonara.excellon.ExcellonFile`. """ return self.aperture @tool.setter @@ -226,14 +380,17 @@ class Line(GerberObject): @property def plated(self): + """ (Excellon only) Returns if this is a plated hole. ``True`` (plated), ``False`` (non-plated) or ``None`` + (plating undefined) + """ return self.tool.plated - def to_primitives(self, unit=None): + def _to_primitives(self, unit=None): conv = self.converted(unit) w = self.aperture.equivalent_width(unit) if self.aperture else 0.1 # for debugging yield gp.Line(*conv.p1, *conv.p2, w, polarity_dark=self.polarity_dark) - def to_statements(self, gs): + def _to_statements(self, gs): yield from gs.set_polarity(self.polarity_dark) yield from gs.set_aperture(self.aperture) yield from gs.set_interpolation_mode(InterpMode.LINEAR) @@ -245,7 +402,7 @@ class Line(GerberObject): gs.update_point(*self.p2, unit=self.unit) - def to_xnc(self, ctx): + def _to_xnc(self, ctx): yield from ctx.select_tool(self.tool) yield from ctx.route_mode(self.unit, *self.p1) @@ -255,26 +412,61 @@ class Line(GerberObject): ctx.set_current_point(self.unit, *self.p2) + # internally used to compute Excellon file path length def curve_length(self, unit=MM): return self.unit.convert_to(unit, math.dist(self.p1, self.p2)) @dataclass -class Arc(GerberObject): +class Arc(GraphicObject): + """ Like :py:class:`Line`, but a circular arc. Has start ``(x1, y1)`` and end ``(x2, y2)`` attributes like a + :py:class:`Line`, but additionally has a center ``(cx, cy)`` specified relative to the start point ``(x1, y1)``, as + well as a ``clockwise`` attribute indicating the arc's direction. + + .. note:: The same warning on apertures that applies to :py:class:`Line` applies to :py:class:`Arc`, too. + + .. warning:: When creating your own circles, you have to take care yourself that the center is actually the center + of a circle that goes through both (x1,y1) and (x2,y2). Elliptical arcs are *not* supported by either + us or the Gerber standard. + """ + #: X coordinate of start point x1 : Length(float) + #: Y coordinate of start point y1 : Length(float) + #: X coordinate of end point x2 : Length(float) + #: Y coordinate of end point y2 : Length(float) - # relative to (x1, x2) + #: X coordinate of arc center relative to ``x1`` cx : Length(float) + #: Y coordinate of arc center relative to ``x1`` cy : Length(float) + #: Direction of arc. ``True`` means clockwise. For a given center coordinate and endpoints there are always two + #: possible arcs, the large one and the small one. Flipping this switches between them. clockwise : bool + #: Aperture for this arc. Should be a subclass of :py:class:`CircleAperture`, whose diameter determines the line + #: width. aperture : object - def _with_offset(self, dx, dy): - return replace(self, x1=self.x1+dx, y1=self.y1+dy, x2=self.x2+dx, y2=self.y2+dy) + def _offset(self, dx, dy): + self.x1 += dx + self.y1 += dy + self.x2 += dx + self.y2 += dy def numeric_error(self, unit=None): + """ Gerber arcs are sligtly over-determined. Since we have not just a radius, but center X and Y coordinates, an + "impossible" arc can be specified, where the start and end points do not lie on a circle around its center. This + function returns the absolute difference between the two radii (start - center) and (end - center) as an + indication on how bad this arc is. + + .. note:: For arcs read from a Gerber file, this value can easily be in the order of magnitude of 1e-4. Gerber + files have very limited numerical resolution, and rounding errors will necessarily lead to numerical + accuracy issues with arcs. + + :rtype: float + """ + # This function is used internally to determine the right arc in multi-quadrant mode conv = self.converted(unit) cx, cy = conv.cx + conv.x1, conv.cy + conv.y1 r1 = math.dist((cx, cy), conv.p1) @@ -282,6 +474,11 @@ class Arc(GerberObject): return abs(r1 - r2) def sweep_angle(self): + """ Calculate absolute sweep angle of arc. This is always a positive number. + + :returns: Angle in clockwise radian between ``0`` and ``2*math.pi`` + :rtype: float + """ cx, cy = self.cx + self.x1, self.cy + self.y1 x1, y1 = self.x1 - cx, self.y1 - cy x2, y2 = self.x2 - cx, self.y2 - cy @@ -301,26 +498,35 @@ class Arc(GerberObject): @property def p1(self): + """ Convenience alias for ``(self.x1, self.y1)`` returning start point of the arc. """ return self.x1, self.y1 @property def p2(self): + """ Convenience alias for ``(self.x2, self.y2)`` returning end point of the arc. """ return self.x2, self.y2 @property def center(self): + """ Returns the center of the arc in **absolute** coordinates. + + :returns: ``(self.x1 + self.cx, self.y1 + self.cy)`` + :rtype: tuple(float) + """ return self.cx + self.x1, self.cy + self.y1 @property def center_relative(self): - return self.cx, self.cy + """ Returns the center of the arc in relative coordinates. - @property - def end_point(self): - return self.p2 + :returns: ``(self.cx, self.cy)`` + :rtype: tuple(float) + """ + return self.cx, self.cy @property def tool(self): + """ Alias for :py:attr:`aperture` for use inside an :py:class:`gerbonara.excellon.ExcellonFile`. """ return self.aperture @tool.setter @@ -329,6 +535,9 @@ class Arc(GerberObject): @property def plated(self): + """ (Excellon only) Returns if this is a plated hole. ``True`` (plated), ``False`` (non-plated) or ``None`` + (plating undefined) + """ return self.tool.plated def _rotate(self, rotation, cx=0, cy=0): @@ -338,7 +547,7 @@ class Arc(GerberObject): self.x2, self.y2 = gp.rotate_point(self.x2, self.y2, rotation, cx, cy) self.cx, self.cy = new_cx - self.x1, new_cy - self.y1 - def to_primitives(self, unit=None): + def _to_primitives(self, unit=None): conv = self.converted(unit) w = self.aperture.equivalent_width(unit) if self.aperture else 0.1 # for debugging yield gp.Arc(x1=conv.x1, y1=conv.y1, @@ -348,7 +557,7 @@ class Arc(GerberObject): width=w, polarity_dark=self.polarity_dark) - def to_statements(self, gs): + def _to_statements(self, gs): yield from gs.set_polarity(self.polarity_dark) yield from gs.set_aperture(self.aperture) # TODO is the following line correct? @@ -363,7 +572,7 @@ class Arc(GerberObject): gs.update_point(*self.p2, unit=self.unit) - def to_xnc(self, ctx): + def _to_xnc(self, ctx): yield from ctx.select_tool(self.tool) yield from ctx.route_mode(self.unit, self.x1, self.y1) code = 'G02' if self.clockwise else 'G03' @@ -376,6 +585,7 @@ class Arc(GerberObject): ctx.set_current_point(self.unit, self.x2, self.y2) + # internally used to compute Excellon file path length def curve_length(self, unit=MM): return self.unit.convert_to(unit, math.hypot(self.cx, self.cy) * self.sweep_angle) diff --git a/gerbonara/graphic_primitives.py b/gerbonara/graphic_primitives.py index 65aa28c..889aa92 100644 --- a/gerbonara/graphic_primitives.py +++ b/gerbonara/graphic_primitives.py @@ -201,10 +201,10 @@ class ArcPoly(GraphicPrimitive): # list of (x : float, y : float) tuples. Describes closed outline, i.e. first and last point are considered # connected. - outline : [(float,)] + outline : list # must be either None (all segments are straight lines) or same length as outline. # Straight line segments have None entry. - arc_centers : [(float,)] = None + arc_centers : list = None @property def segments(self): diff --git a/gerbonara/rs274x.py b/gerbonara/rs274x.py index 8bd622b..837440f 100644 --- a/gerbonara/rs274x.py +++ b/gerbonara/rs274x.py @@ -79,6 +79,9 @@ class GerberFile(CamFile): return ExcellonFile(objects=new_objs, comments=self.comments) + def to_gerber(self): + return + def merge(self, other): """ Merge other GerberFile into this one """ if other is None: @@ -222,9 +225,9 @@ class GerberFile(CamFile): def save(self, filename, settings=None, drop_comments=True): with open(filename, 'w', encoding='utf-8') as f: # Encoding is specified as UTF-8 by spec. - f.write(self.to_gerber(settings, drop_comments=drop_comments)) + f.write(self.generate_gerber(settings, drop_comments=drop_comments)) - def to_gerber(self, settings=None, drop_comments=True): + def generate_gerber(self, settings=None, drop_comments=True): # Use given settings, or use same settings as original file if not given, or use defaults if not imported from a # file if settings is None: @@ -245,8 +248,8 @@ class GerberFile(CamFile): def offset(self, dx=0, dy=0, unit=MM): # TODO round offset to file resolution - - self.objects = [ obj.with_offset(dx, dy, unit) for obj in self.objects ] + for obj in self.objects: + obj.with_offset(dx, dy, unit) def rotate(self, angle:'radian', center=(0,0), unit=MM): """ Rotate file contents around given point. @@ -30,7 +30,7 @@ setup( name='gerbonara', version=version(), author='jaseg, XenGi', - author_email='contact@gerbonara.jaseg.de', + author_email='gerbonara@jaseg.de', description='Tools to handle Gerber and Excellon files in Python', long_description=long_description(), long_description_content_type='text/markdown', |