summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2022-02-02 10:53:19 +0100
committerjaseg <git@jaseg.de>2022-02-02 10:53:19 +0100
commit0fa713af4be2a5a229a773ca62b894347d6d0b8a (patch)
treeb91d6f2892fe58b892196db377a6eb092984a735
parent7473e471dc69d09a35bb0762549cc4f3ab8b04b3 (diff)
downloadgerbonara-0fa713af4be2a5a229a773ca62b894347d6d0b8a.tar.gz
gerbonara-0fa713af4be2a5a229a773ca62b894347d6d0b8a.tar.bz2
gerbonara-0fa713af4be2a5a229a773ca62b894347d6d0b8a.zip
More doc
-rw-r--r--docs/apertures.rst157
-rw-r--r--docs/file-api.rst5
-rw-r--r--docs/utilities.rst5
-rw-r--r--gerbonara/apertures.py188
-rwxr-xr-xgerbonara/excellon.py4
-rw-r--r--gerbonara/graphic_objects.py2
6 files changed, 184 insertions, 177 deletions
diff --git a/docs/apertures.rst b/docs/apertures.rst
index 2a17a04..51d4b0f 100644
--- a/docs/apertures.rst
+++ b/docs/apertures.rst
@@ -1,124 +1,17 @@
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.
+Gerbonara maps all standard Gerber apertures to subclasses of the :py:class:`.Aperture` class. These subclasses:
+:py:class:`.CircleAperture`, :py:class:`.RectangleAperture`, :py:class:`.ObroundAperture` and
+:py:class:`.PolygonAperture`. :doc:`Aperture macro<aperture-macros>` instantiations get mapped to
+:py:class:`.ApertureMacroInstance` (also an :py:class:`Aperture` subclass). The basic aperture shapes each support a
+central hole through their :py:class:`~.CircleAperture.hole_dia` attribute. This "hole" is just a cut-out in the
+aperture itself, and does not imply an actual drilled hole in the board. Drilled holes in the board are specified
+completely separately through an :py:class:`.ExcellonFile`.
+
+Gerbonara is able to rotate any aperture. The Gerber standard does not support rotation for standard apertures in any
+widespread way, so Gerbolyze handles rotation by converting rotated standard apertures into aperture macros during
+export as necessary.
Aperture generalization
-----------------------
@@ -132,7 +25,29 @@ For polygons, we simply change the angle parameter. However, for rectangles and
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.
+a non-standard :py:attr:`.RectangleAperture.rotation` attribute to all apertures except :py:class:`.CircleAperture` and
+transparently converts rotated instances to the appropriate :py:class:`.ApertureMacroInstance` objects while it writes
+out the file. Be aware that this may mean that an object that in memory has a :py:class:`.RectangleAperture` might end
+up with an aperture macro instance in the output Gerber file.
+
+Aperture classes
+----------------
+
+.. autoclass:: gerbonara.apertures.Aperture
+ :members:
+
+.. autoclass:: gerbonara.apertures.CircleAperture
+ :members:
+
+.. autoclass:: gerbonara.apertures.RectangleAperture
+ :members:
+
+.. autoclass:: gerbonara.apertures.ObroundAperture
+ :members:
+
+.. autoclass:: gerbonara.apertures.PolygonAperture
+ :members:
+
+.. autoclass:: gerbonara.apertures.ApertureMacroInstance
+ :members:
diff --git a/docs/file-api.rst b/docs/file-api.rst
index d73b5f1..ed23672 100644
--- a/docs/file-api.rst
+++ b/docs/file-api.rst
@@ -7,9 +7,8 @@ Gerbonara currently supports three file types: RS-274-X Gerber as `specified by
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.
+: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:
diff --git a/docs/utilities.rst b/docs/utilities.rst
index 89b1cde..80ce5ec 100644
--- a/docs/utilities.rst
+++ b/docs/utilities.rst
@@ -1,3 +1,8 @@
Utilities
=========
+.. autoclass:: gerbonara.utils.LengthUnit
+ :members:
+
+.. autoclass:: gerbonara.cam.FileSettings
+ :members:
diff --git a/gerbonara/apertures.py b/gerbonara/apertures.py
index 086d5b1..e22b702 100644
--- a/gerbonara/apertures.py
+++ b/gerbonara/apertures.py
@@ -10,23 +10,23 @@ from . import graphic_primitives as gp
def _flash_hole(self, x, y, unit=None, polarity_dark=True):
if getattr(self, 'hole_rect_h', None) is not None:
- return [*self.primitives(x, y, unit, polarity_dark),
+ return [*self._primitives(x, y, unit, polarity_dark),
gp.Rectangle((x, y),
(self.unit.convert_to(unit, self.hole_dia), self.unit.convert_to(unit, self.hole_rect_h)),
rotation=self.rotation, polarity_dark=(not polarity_dark))]
elif self.hole_dia is not None:
- return [*self.primitives(x, y, unit, polarity_dark),
+ return [*self._primitives(x, y, unit, polarity_dark),
gp.Circle(x, y, self.unit.convert_to(unit, self.hole_dia/2), polarity_dark=(not polarity_dark))]
else:
- return self.primitives(x, y, unit, polarity_dark)
+ return self._primitives(x, y, unit, polarity_dark)
-def strip_right(*args):
+def _strip_right(*args):
args = list(args)
while args and args[-1] is None:
args.pop()
return args
-def none_close(a, b):
+def _none_close(a, b):
if a is None and b is None:
return True
elif a is not None and b is not None:
@@ -35,24 +35,37 @@ def none_close(a, b):
return False
class Length:
+ """ Marker indicating that a dataclass field of an :py:class:`.Aperture` contains a physical length or coordinate
+ measured in the :py:class:`.Aperture`'s native unit from :py:attr:`.Aperture.unit`.
+ """
def __init__(self, obj_type):
self.type = obj_type
@dataclass
class Aperture:
+ """ Base class for all apertures. """
_ : KW_ONLY
+ #: :py:class:`gerbonara.utils.LengthUnit` used for all length fields of this aperture.
unit : str = None
+ #: GerberX2 attributes of this aperture. Note that this will only contain aperture attributes, not file attributes.
+ #: File attributes are stored in the :py:attr:`~.GerberFile.attrs` of the :py:class:`.GerberFile`.
attrs : dict = field(default_factory=dict)
- original_number : str = None
+ #: 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`. When
+ #: you programmatically create a new aperture, you do not have to set this.
+ original_number : int = None
@property
def hole_shape(self):
- if hasattr(self, 'hole_rect_h') and self.hole_rect_h is not None:
+ """ Get shape of hole based on :py:attr:`hole_dia` and :py:attr:`hole_rect_h`: "rect" or "circle" or None. """
+ if getattr(self, 'hole_rect_h') is not None:
return 'rect'
- else:
+ elif getattr(self, 'hole_dia') is not None:
return 'circle'
+ else:
+ return None
- def params(self, unit=None):
+ def _params(self, unit=None):
out = []
for f in fields(self):
if f.kw_only:
@@ -66,24 +79,52 @@ class Aperture:
return out
def flash(self, x, y, unit=None, polarity_dark=True):
- return self.primitives(x, y, unit, polarity_dark)
+ """ Render this aperture into a ``list`` of :py:class:`.GraphicPrimitive` instances in the given unit. If no
+ unit is given, use this aperture's local unit.
+
+ :param float x: X coordinate of center of flash.
+ :param float y: Y coordinate of center of flash.
+ :param LengthUnit unit: Physical length unit to use for the returned primitives.
+ :param bool polarity_dark: Polarity of this flash. ``True`` renders this aperture as usual. ``False`` flips the polarity of all primitives.
+
+ :returns: Rendered graphic primitivees.
+ :rtype: list(:py:class:`.GraphicPrimitive`)
+ """
+ return self._primitives(x, y, unit, polarity_dark)
def equivalent_width(self, unit=None):
+ """ Get the width of a line interpolated using this aperture in the given :py:class:`~.LengthUnit`.
+
+ :rtype: float
+ """
raise ValueError('Non-circular aperture used in interpolation statement, line width is not properly defined.')
def to_gerber(self, settings=None):
+ """ Return the Gerber aperture definition for this aperture using the given :py:class:`.FileSettings`.
+
+ :rtype: str
+ """
# Hack: The standard aperture shapes C, R, O do not have a rotation parameter. To make this API easier to use,
# we emulate this parameter. Our circle, rectangle and oblong classes below have a rotation parameter. Only at
# export time during to_gerber, this parameter is evaluated.
unit = settings.unit if settings else None
actual_inst = self._rotated()
- params = 'X'.join(f'{float(par):.4}' for par in actual_inst.params(unit) if par is not None)
+ params = 'X'.join(f'{float(par):.4}' for par in actual_inst._params(unit) if par is not None)
if params:
- return f'{actual_inst.gerber_shape_code},{params}'
+ return f'{actual_inst._gerber_shape_code},{params}'
else:
- return actual_inst.gerber_shape_code
+ return actual_inst._gerber_shape_code
+
+ def to_macro(self):
+ """ Convert this :py:class:`.Aperture` into an :py:class:`.ApertureMacro` inside an
+ :py:class:`.ApertureMacroInstance`.
+ """
+ raise NotImplementedError()
def __eq__(self, other):
+ """ Compare two apertures. Apertures are compared based on their Gerber representation. Two apertures are
+ considered equal if their Gerber aperture definitions are identical.
+ """
# We need to choose some unit here.
return hasattr(other, 'to_gerber') and self.to_gerber(MM) == other.to_gerber(MM)
@@ -95,39 +136,44 @@ class Aperture:
@dataclass(unsafe_hash=True)
class ExcellonTool(Aperture):
- gerber_shape_code = 'C'
- human_readable_shape = 'drill'
+ """ Special Aperture_ subclass for use in :py:class:`.ExcellonFile`. Similar to :py:class:`.CircleAperture`, but
+ does not have :py:attr:`.CircleAperture.hole_dia` or :py:attr:`.CircleAperture.hole_rect_h`, and has the additional
+ :py:attr:`plated` attribute.
+ """
+ _gerber_shape_code = 'C'
+ _human_readable_shape = 'drill'
+ #: float with diameter of this tool in :py:attr:`unit` units.
diameter : Length(float)
+ #: bool or ``None`` for "unknown", indicating whether this tool creates plated (``True``) or non-plated (``False``)
+ #: holes.
plated : bool = None
- depth_offset : Length(float) = 0
- def primitives(self, x, y, unit=None, polarity_dark=True):
+ def _primitives(self, x, y, unit=None, polarity_dark=True):
return [ gp.Circle(x, y, self.unit.convert_to(unit, self.diameter/2), polarity_dark=polarity_dark) ]
def to_xnc(self, settings):
- z_off = 'Z' + settings.write_excellon_value(self.depth_offset, self.unit) if self.depth_offset is not None else ''
- return 'C' + settings.write_excellon_value(self.diameter, self.unit) + z_off
+ return 'C' + settings.write_excellon_value(self.diameter, self.unit)
def __eq__(self, other):
+ """ Compare two :py:class:`.ExcellonTool` instances. They are considered equal if their diameter and plating
+ match.
+ """
if not isinstance(other, ExcellonTool):
return False
if not self.plated == other.plated:
return False
- if not none_close(self.depth_offset, self.unit(other.depth_offset, other.unit)):
- return False
-
- return none_close(self.diameter, self.unit(other.diameter, other.unit))
+ return _none_close(self.diameter, self.unit(other.diameter, other.unit))
def __str__(self):
plated = '' if self.plated is None else (' plated' if self.plated else ' non-plated')
- z_off = '' if self.depth_offset is None else f' z_offset={self.depth_offset}'
- return f'<Excellon Tool d={self.diameter:.3f}{plated}{z_off} [{self.unit}]>'
+ return f'<Excellon Tool d={self.diameter:.3f}{plated} [{self.unit}]>'
def equivalent_width(self, unit=MM):
return unit(self.diameter, self.unit)
+ # Internal use, for layer dilation.
def dilated(self, offset, unit=MM):
offset = unit(offset, self.unit)
return replace(self, diameter=self.diameter+2*offset)
@@ -136,22 +182,29 @@ class ExcellonTool(Aperture):
return self
def to_macro(self):
- return ApertureMacroInstance(GenericMacros.circle, self.params(unit=MM))
+ return ApertureMacroInstance(GenericMacros.circle, self._params(unit=MM))
- def params(self, unit=None):
+ def _params(self, unit=None):
return [self.unit.convert_to(unit, self.diameter)]
@dataclass
class CircleAperture(Aperture):
- gerber_shape_code = 'C'
- human_readable_shape = 'circle'
+ """ Besides flashing circles or rings, CircleApertures are used to set the width of a
+ :py:class:`~.graphic_objects.Line` or :py:class:`~.graphic_objects.Arc`.
+ """
+ _gerber_shape_code = 'C'
+ _human_readable_shape = 'circle'
+ #: float with diameter of the circle in :py:attr:`unit` units.
diameter : Length(float)
+ #: float with the hole diameter of this aperture in :py:attr:`unit` units. ``0`` for no hole.
hole_dia : Length(float) = None
+ #: float or None. If not None, specifies a rectangular hole of size `hole_dia * hole_rect_h` instead of a round hole.
hole_rect_h : Length(float) = None
- rotation : float = 0 # radians; for rectangular hole; see hack in Aperture.to_gerber
+ # float with radians. This is only used for rectangular holes (as circles are rotationally symmetric).
+ rotation : float = 0
- def primitives(self, x, y, unit=None, polarity_dark=True):
+ def _primitives(self, x, y, unit=None, polarity_dark=True):
return [ gp.Circle(x, y, self.unit.convert_to(unit, self.diameter/2), polarity_dark=polarity_dark) ]
def __str__(self):
@@ -173,10 +226,10 @@ class CircleAperture(Aperture):
return self.to_macro(self.rotation)
def to_macro(self):
- return ApertureMacroInstance(GenericMacros.circle, self.params(unit=MM))
+ return ApertureMacroInstance(GenericMacros.circle, self._params(unit=MM))
- def params(self, unit=None):
- return strip_right(
+ def _params(self, unit=None):
+ return _strip_right(
self.unit.convert_to(unit, self.diameter),
self.unit.convert_to(unit, self.hole_dia),
self.unit.convert_to(unit, self.hole_rect_h))
@@ -184,15 +237,20 @@ class CircleAperture(Aperture):
@dataclass
class RectangleAperture(Aperture):
- gerber_shape_code = 'R'
- human_readable_shape = 'rect'
+ _gerber_shape_code = 'R'
+ _human_readable_shape = 'rect'
+ #: float with the width of the rectangle in :py:attr:`unit` units.
w : Length(float)
+ #: float with the height of the rectangle in :py:attr:`unit` units.
h : Length(float)
+ #: float with the hole diameter of this aperture in :py:attr:`unit` units. ``0`` for no hole.
hole_dia : Length(float) = None
+ #: float or None. If not None, specifies a rectangular hole of size `hole_dia * hole_rect_h` instead of a round hole.
hole_rect_h : Length(float) = None
+ # Rotation in radians. This rotates both the aperture and the rectangular hole if it has one.
rotation : float = 0 # radians
- def primitives(self, x, y, unit=None, polarity_dark=True):
+ def _primitives(self, x, y, unit=None, polarity_dark=True):
return [ gp.Rectangle(x, y, self.unit.convert_to(unit, self.w), self.unit.convert_to(unit, self.h),
rotation=self.rotation, polarity_dark=polarity_dark) ]
@@ -224,8 +282,8 @@ class RectangleAperture(Aperture):
MM(self.hole_rect_h, self.unit) or 0,
self.rotation])
- def params(self, unit=None):
- return strip_right(
+ def _params(self, unit=None):
+ return _strip_right(
self.unit.convert_to(unit, self.w),
self.unit.convert_to(unit, self.h),
self.unit.convert_to(unit, self.hole_dia),
@@ -234,15 +292,26 @@ class RectangleAperture(Aperture):
@dataclass
class ObroundAperture(Aperture):
- gerber_shape_code = 'O'
- human_readable_shape = 'obround'
+ """ Aperture whose shape is the convex hull of two circles of equal radii.
+
+ Obrounds are specified through width and height of their bounding rectangle.. The smaller one of these will be the
+ diameter of the obround's ends. If :py:attr:`w` is larger, the result will be a landscape obround. If :py:attr:`h`
+ is larger, it will be a portrait obround.
+ """
+ _gerber_shape_code = 'O'
+ _human_readable_shape = 'obround'
+ #: float with the width of the bounding rectangle of this obround in :py:attr:`unit` units.
w : Length(float)
+ #: float with the height of the bounding rectangle of this obround in :py:attr:`unit` units.
h : Length(float)
+ #: float with the hole diameter of this aperture in :py:attr:`unit` units. ``0`` for no hole.
hole_dia : Length(float) = None
+ #: float or None. If not None, specifies a rectangular hole of size `hole_dia * hole_rect_h` instead of a round hole.
hole_rect_h : Length(float) = None
+ #: Rotation in radians. This rotates both the aperture and the rectangular hole if it has one.
rotation : float = 0
- def primitives(self, x, y, unit=None, polarity_dark=True):
+ def _primitives(self, x, y, unit=None, polarity_dark=True):
return [ gp.Obround(x, y, self.unit.convert_to(unit, self.w), self.unit.convert_to(unit, self.h),
rotation=self.rotation, polarity_dark=polarity_dark) ]
@@ -273,8 +342,8 @@ class ObroundAperture(Aperture):
MM(inst.hole_rect_h, self.unit),
inst.rotation])
- def params(self, unit=None):
- return strip_right(
+ def _params(self, unit=None):
+ return _strip_right(
self.unit.convert_to(unit, self.w),
self.unit.convert_to(unit, self.h),
self.unit.convert_to(unit, self.hole_dia),
@@ -283,16 +352,24 @@ class ObroundAperture(Aperture):
@dataclass
class PolygonAperture(Aperture):
- gerber_shape_code = 'P'
+ """ Aperture whose shape is a regular n-sided polygon (e.g. pentagon, hexagon etc.). Note that this only supports
+ round holes.
+ """
+ _gerber_shape_code = 'P'
+ #: Diameter of circumscribing circle, i.e. the circle that all the polygon's corners lie on. In
+ #: :py:attr:`unit` units.
diameter : Length(float)
+ #: Number of corners of this polygon. Three for a triangle, four for a square, five for a pentagon etc.
n_vertices : int
+ #: Rotation in radians.
rotation : float = 0
+ #: float with the hole diameter of this aperture in :py:attr:`unit` units. ``0`` for no hole.
hole_dia : Length(float) = None
def __post_init__(self):
self.n_vertices = int(self.n_vertices)
- def primitives(self, x, y, unit=None, polarity_dark=True):
+ def _primitives(self, x, y, unit=None, polarity_dark=True):
return [ gp.RegularPolygon(x, y, self.unit.convert_to(unit, self.diameter)/2, self.n_vertices,
rotation=self.rotation, polarity_dark=polarity_dark) ]
@@ -309,7 +386,7 @@ class PolygonAperture(Aperture):
return self
def to_macro(self):
- return ApertureMacroInstance(GenericMacros.polygon, self.params(MM))
+ return ApertureMacroInstance(GenericMacros.polygon, self._params(MM))
def params(self, unit=None):
rotation = self.rotation % (2*math.pi / self.n_vertices) if self.rotation is not None else None
@@ -322,15 +399,26 @@ class PolygonAperture(Aperture):
@dataclass
class ApertureMacroInstance(Aperture):
+ """ 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 :py:class:`.ApertureMacroInstance` is
+ one such binding of a macro to a particular set of parameters. Note that you still need an
+ :py:class:`.ApertureMacroInstance` even if your :py:class:`.ApertureMacro` has no parameters since an
+ :py:class:`.ApertureMacro` is not an :py:class:`.Aperture` by itself.
+ """
+ #: The :py:class:`.ApertureMacro` bound in this instance
macro : object
- parameters : [float]
+ #: The parameters to the :py:class:`.ApertureMacro`. All elements should be floats or ints. The first item in the
+ #: list is parameter ``$1``, the second is ``$2`` etc.
+ parameters : list
+ #: Aperture rotation in radians. When saving, a copy of the :py:class:`.ApertureMacro` is re-written with this
+ #: rotation.
rotation : float = 0
@property
- def gerber_shape_code(self):
+ def _gerber_shape_code(self):
return self.macro.name
- def primitives(self, x, y, unit=None, polarity_dark=True):
+ def _primitives(self, x, y, unit=None, polarity_dark=True):
out = list(self.macro.to_graphic_primitives(
offset=(x, y), rotation=self.rotation,
parameters=self.parameters, unit=unit, polarity_dark=polarity_dark))
diff --git a/gerbonara/excellon.py b/gerbonara/excellon.py
index 96a78be..a6e9566 100755
--- a/gerbonara/excellon.py
+++ b/gerbonara/excellon.py
@@ -268,7 +268,7 @@ class ExcellonFile(CamFile):
# Build tool index
tool_map = { id(obj.tool): obj.tool for obj in self.objects }
- tools = sorted(tool_map.items(), key=lambda id_tool: (id_tool[1].plated, id_tool[1].diameter, id_tool[1].depth_offset))
+ tools = sorted(tool_map.items(), key=lambda id_tool: (id_tool[1].plated, id_tool[1].diameter))
tools = { tool_id: index for index, (tool_id, _tool) in enumerate(tools, start=1) }
# FIXME dedup tools
@@ -526,7 +526,7 @@ class ExcellonParser(object):
params = { m[0]: self.settings.parse_gerber_value(m[1:]) for m in re.findall('[BCFHSTZ][.0-9]+', match[2]) }
- self.tools[index] = ExcellonTool(diameter=params.get('C'), depth_offset=params.get('Z'), plated=self.is_plated,
+ self.tools[index] = ExcellonTool(diameter=params.get('C'), plated=self.is_plated,
unit=self.settings.unit)
if set(params.keys()) == set('TFSC'):
diff --git a/gerbonara/graphic_objects.py b/gerbonara/graphic_objects.py
index 99f990f..1ab03dd 100644
--- a/gerbonara/graphic_objects.py
+++ b/gerbonara/graphic_objects.py
@@ -37,7 +37,7 @@ class GraphicObject:
#: 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`).
+ #: :py:class:`gerbonara.utils.LengthUnit` used for all coordinate fields of this object (such as ``x`` or ``y``).
unit : str = None