summaryrefslogtreecommitdiff
path: root/gerbonara/gerber/primitives.py
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-11-10 21:39:03 +0100
committerjaseg <git@jaseg.de>2021-11-10 21:39:03 +0100
commitd21a2e67ff34d3f29e37a01f926b9e8f72003637 (patch)
tree8218f339a3336283c060aa5657a76a463b8e1982 /gerbonara/gerber/primitives.py
parent125eb821b9f5d4c58b17d43e318e9a6829120d03 (diff)
downloadgerbonara-d21a2e67ff34d3f29e37a01f926b9e8f72003637.tar.gz
gerbonara-d21a2e67ff34d3f29e37a01f926b9e8f72003637.tar.bz2
gerbonara-d21a2e67ff34d3f29e37a01f926b9e8f72003637.zip
WIP
Diffstat (limited to 'gerbonara/gerber/primitives.py')
-rw-r--r--gerbonara/gerber/primitives.py332
1 files changed, 26 insertions, 306 deletions
diff --git a/gerbonara/gerber/primitives.py b/gerbonara/gerber/primitives.py
index 445b605..25f8e06 100644
--- a/gerbonara/gerber/primitives.py
+++ b/gerbonara/gerber/primitives.py
@@ -24,239 +24,44 @@ from .utils import rotate_point, nearly_equal
-
-class Primitive(object):
- """ Base class for all Cam file primitives
-
- Parameters
- ---------
- level_polarity : string
- Polarity of the parameter. May be 'dark' or 'clear'. Dark indicates
- a "positive" primitive, i.e. indicating where coppper should remain,
- and clear indicates a negative primitive, such as where copper should
- be removed. clear primitives are often used to create cutouts in region
- pours.
-
- rotation : float
- Rotation of a primitive about its origin in degrees. Positive rotation
- is counter-clockwise as viewed from the board top.
-
- units : string
- Units in which primitive was defined. 'inch' or 'metric'
-
- net_name : string
- Name of the electrical net the primitive belongs to
- """
-
- def __init__(self, level_polarity='dark', rotation=0, units=None, net_name=None):
- self.level_polarity = level_polarity
- self.net_name = net_name
- self._to_convert = list()
- self._memoized = list()
- self._units = units
- self._rotation = rotation
- self._cos_theta = math.cos(math.radians(rotation))
- self._sin_theta = math.sin(math.radians(rotation))
- self._bounding_box = None
- self._vertices = None
- self._segments = None
-
- @property
- def flashed(self):
- '''Is this a flashed primitive'''
- raise NotImplementedError('Is flashed must be '
- 'implemented in subclass')
+class Primitive:
+ def __init__(self, polarity_dark=True, rotation=0, **meta):
+ self.polarity_dark = polarity_dark
+ self.meta = meta
+ self.rotation = rotation
def __eq__(self, other):
return self.__dict__ == other.__dict__
- @property
- def units(self):
- return self._units
-
- @units.setter
- def units(self, value):
- self._changed()
- self._units = value
-
- @property
- def rotation(self):
- return self._rotation
-
- @rotation.setter
- def rotation(self, value):
- self._changed()
- self._rotation = value
- self._cos_theta = math.cos(math.radians(value))
- self._sin_theta = math.sin(math.radians(value))
-
- @property
- def vertices(self):
+ def aperture(self):
return None
- @property
- def segments(self):
- if self._segments is None:
- if self.vertices is not None and len(self.vertices):
- self._segments = [segment for segment in
- combinations(self.vertices, 2)]
- return self._segments
-
- @property
- def bounding_box(self):
- """ Calculate axis-aligned bounding box
-
- will be helpful for sweep & prune during DRC clearance checks.
-
- Return ((min x, max x), (min y, max y))
- """
- raise NotImplementedError('Bounding box calculation must be '
- 'implemented in subclass')
-
- @property
- def bounding_box_no_aperture(self):
- """ Calculate bouxing box without considering the aperture
-
- for most objects, this is the same as the bounding_box, but is different for
- Lines and Arcs (which are not flashed)
-
- Return ((min x, min y), (max x, max y))
- """
- return self.bounding_box
-
- def to_inch(self):
- """ Convert primitive units to inches.
- """
- if self.units == 'metric':
- self.units = 'inch'
- for attr, value in [(attr, getattr(self, attr))
- for attr in self._to_convert]:
- if hasattr(value, 'to_inch'):
- value.to_inch()
- else:
- try:
- if len(value) > 1:
- if hasattr(value[0], 'to_inch'):
- for v in value:
- v.to_inch()
- elif isinstance(value[0], tuple):
- setattr(self, attr,
- [tuple(map(inch, point))
- for point in value])
- else:
- setattr(self, attr, tuple(map(inch, value)))
- except:
- if value is not None:
- setattr(self, attr, inch(value))
-
- def to_metric(self):
- """ Convert primitive units to metric.
- """
- if self.units == 'inch':
- self.units = 'metric'
- for attr, value in [(attr, getattr(self, attr)) for attr in self._to_convert]:
- if hasattr(value, 'to_metric'):
- value.to_metric()
- else:
- try:
- if len(value) > 1:
- if hasattr(value[0], 'to_metric'):
- for v in value:
- v.to_metric()
- elif isinstance(value[0], tuple):
- setattr(self, attr,
- [tuple(map(metric, point))
- for point in value])
- else:
- setattr(self, attr, tuple(map(metric, value)))
- except:
- if value is not None:
- setattr(self, attr, metric(value))
-
- def offset(self, x_offset=0, y_offset=0):
- """ Move the primitive by the specified x and y offset amount.
-
- values are specified in the primitive's native units
- """
- if hasattr(self, 'position'):
- self._changed()
- self.position = tuple([coord + offset for coord, offset
- in zip(self.position,
- (x_offset, y_offset))])
-
- def to_statement(self):
- pass
-
- def _changed(self):
- """ Clear memoized properties.
-
- Forces a recalculation next time any memoized propery is queried.
- This must be called from a subclass every time a parameter that affects
- a memoized property is changed. The easiest way to do this is to call
- _changed() from property.setter methods.
- """
- self._bounding_box = None
- self._vertices = None
- self._segments = None
- for attr in self._memoized:
- setattr(self, attr, None)
class Line(Primitive):
- """
- """
-
- def __init__(self, start, end, aperture, level_polarity=None, **kwargs):
- super(Line, self).__init__(**kwargs)
- self.level_polarity = level_polarity
- self._start = start
- self._end = end
+ def __init__(self, start, end, aperture, polarity_dark=True, rotation=0, **meta):
+ super().__init__(polarity_dark, rotation, **meta)
+ self.start = start
+ self.end = end
self.aperture = aperture
- self._to_convert = ['start', 'end', 'aperture']
-
- @property
- def flashed(self):
- return False
-
- @property
- def start(self):
- return self._start
-
- @start.setter
- def start(self, value):
- self._changed()
- self._start = value
-
- @property
- def end(self):
- return self._end
-
- @end.setter
- def end(self, value):
- self._changed()
- self._end = value
@property
def angle(self):
- delta_x, delta_y = tuple(
- [end - start for end, start in zip(self.end, self.start)])
- angle = math.atan2(delta_y, delta_x)
- return angle
+ delta_x, delta_y = tuple(end - start for end, start in zip(self.end, self.start))
+ return math.atan2(delta_y, delta_x)
@property
def bounding_box(self):
- if self._bounding_box is None:
- if isinstance(self.aperture, Circle):
- width_2 = self.aperture.radius
- height_2 = width_2
- else:
- width_2 = self.aperture.width / 2.
- height_2 = self.aperture.height / 2.
- min_x = min(self.start[0], self.end[0]) - width_2
- max_x = max(self.start[0], self.end[0]) + width_2
- min_y = min(self.start[1], self.end[1]) - height_2
- max_y = max(self.start[1], self.end[1]) + height_2
- self._bounding_box = ((min_x, min_y), (max_x, max_y))
- return self._bounding_box
+ if isinstance(self.aperture, Circle):
+ width_2 = self.aperture.radius
+ height_2 = width_2
+ else:
+ width_2 = self.aperture.width / 2.
+ height_2 = self.aperture.height / 2.
+ min_x = min(self.start[0], self.end[0]) - width_2
+ max_x = max(self.start[0], self.end[0]) + width_2
+ min_y = min(self.start[1], self.end[1]) - height_2
+ max_y = max(self.start[1], self.end[1]) + height_2
+ return (min_x, min_y), (max_x, max_y)
@property
def bounding_box_no_aperture(self):
@@ -320,11 +125,7 @@ class Line(Primitive):
return str(self)
class Arc(Primitive):
- """
- """
-
- def __init__(self, start, end, center, direction, aperture, quadrant_mode,
- level_polarity=None, **kwargs):
+ def __init__(self, start, end, center, direction, aperture, level_polarity=None, **kwargs):
super(Arc, self).__init__(**kwargs)
self.level_polarity = level_polarity
self._start = start
@@ -332,7 +133,6 @@ class Arc(Primitive):
self._center = center
self.direction = direction
self.aperture = aperture
- self._quadrant_mode = quadrant_mode
self._to_convert = ['start', 'end', 'center', 'aperture']
@property
@@ -367,15 +167,6 @@ class Arc(Primitive):
self._center = value
@property
- def quadrant_mode(self):
- return self._quadrant_mode
-
- @quadrant_mode.setter
- def quadrant_mode(self, quadrant_mode):
- self._changed()
- self._quadrant_mode = quadrant_mode
-
- @property
def radius(self):
dy, dx = tuple([start - center for start, center
in zip(self.start, self.center)])
@@ -411,39 +202,6 @@ class Arc(Primitive):
theta0 = (self.start_angle + two_pi) % two_pi
theta1 = (self.end_angle + two_pi) % two_pi
points = [self.start, self.end]
- if self.quadrant_mode == 'multi-quadrant':
- if self.direction == 'counterclockwise':
- # Passes through 0 degrees
- if theta0 >= theta1:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta0 <= math.pi / 2.) and ((theta1 >= math.pi / 2.) or (theta1 <= theta0)))
- or ((theta1 > math.pi / 2.) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if ((theta0 <= math.pi and (theta1 >= math.pi or theta1 <= theta0))
- or ((theta1 > math.pi) and (theta1 <= theta0))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (theta0 <= math.pi * 1.5 and (theta1 >= math.pi * 1.5 or theta1 <= theta0)
- or ((theta1 > math.pi * 1.5) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] - self.radius))
- else:
- # Passes through 0 degrees
- if theta1 >= theta0:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta1 <= math.pi / 2.) and (theta0 >= math.pi / 2. or theta0 <= theta1))
- or ((theta0 > math.pi / 2.) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if (((theta1 <= math.pi) and (theta0 >= math.pi or theta0 <= theta1))
- or ((theta0 > math.pi) and (theta0 <= theta1))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (((theta1 <= math.pi * 1.5) and (theta0 >= math.pi * 1.5 or theta0 <= theta1))
- or ((theta0 > math.pi * 1.5) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] - self.radius))
x, y = zip(*points)
if hasattr(self.aperture, 'radius'):
min_x = min(x) - self.aperture.radius
@@ -466,43 +224,6 @@ class Arc(Primitive):
theta0 = (self.start_angle + two_pi) % two_pi
theta1 = (self.end_angle + two_pi) % two_pi
points = [self.start, self.end]
- if self.quadrant_mode == 'multi-quadrant':
- if self.direction == 'counterclockwise':
- # Passes through 0 degrees
- if theta0 >= theta1:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta0 <= math.pi / 2.) and (
- (theta1 >= math.pi / 2.) or (theta1 <= theta0)))
- or ((theta1 > math.pi / 2.) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if ((theta0 <= math.pi and (theta1 >= math.pi or theta1 <= theta0))
- or ((theta1 > math.pi) and (theta1 <= theta0))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (theta0 <= math.pi * 1.5 and (
- theta1 >= math.pi * 1.5 or theta1 <= theta0)
- or ((theta1 > math.pi * 1.5) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] - self.radius))
- else:
- # Passes through 0 degrees
- if theta1 >= theta0:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta1 <= math.pi / 2.) and (
- theta0 >= math.pi / 2. or theta0 <= theta1))
- or ((theta0 > math.pi / 2.) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if (((theta1 <= math.pi) and (theta0 >= math.pi or theta0 <= theta1))
- or ((theta0 > math.pi) and (theta0 <= theta1))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (((theta1 <= math.pi * 1.5) and (
- theta0 >= math.pi * 1.5 or theta0 <= theta1))
- or ((theta0 > math.pi * 1.5) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] - self.radius))
x, y = zip(*points)
min_x = min(x)
@@ -522,8 +243,7 @@ class Circle(Primitive):
"""
"""
- def __init__(self, position, diameter, hole_diameter=None,
- hole_width=0, hole_height=0, **kwargs):
+ def __init__(self, position, diameter, polarity_dark=True):
super(Circle, self).__init__(**kwargs)
validate_coordinates(position)
self._position = position
@@ -1087,7 +807,7 @@ class Region(Primitive):
@property
def bounding_box(self):
if self._bounding_box is None:
- xlims, ylims = zip(*[p.bounding_box_no_aperture for p in self.primitives])
+ xlims, ylims = zip(*[p.bounding_box for p in self.primitives])
minx, maxx = zip(*xlims)
miny, maxy = zip(*ylims)
min_x = min(minx)