From 7473e471dc69d09a35bb0762549cc4f3ab8b04b3 Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 1 Feb 2022 22:08:54 +0100 Subject: Add some documentation --- docs/aperture-macros.rst | 48 ++++++++++++++ docs/apertures.rst | 138 +++++++++++++++++++++++++++++++++++++++++ docs/api-concepts.rst | 60 ++++++++++++++++++ docs/conf.py | 57 +++++++++++++++++ docs/file-api.rst | 25 ++++++++ docs/graphic-primitive-api.rst | 27 ++++++++ docs/index.rst | 115 ++++++++++++++++++++++++++++++++++ docs/object-api.rst | 30 +++++++++ docs/utilities.rst | 3 + 9 files changed, 503 insertions(+) create mode 100644 docs/aperture-macros.rst create mode 100644 docs/apertures.rst create mode 100644 docs/api-concepts.rst create mode 100644 docs/conf.py create mode 100644 docs/file-api.rst create mode 100644 docs/graphic-primitive-api.rst create mode 100644 docs/index.rst create mode 100644 docs/object-api.rst create mode 100644 docs/utilities.rst (limited to 'docs') 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`. + +**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 +`: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 `, 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 +========= + -- cgit