summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gerber/am_statements.py22
-rw-r--r--gerber/cam.py4
-rw-r--r--gerber/gerber_statements.py10
-rw-r--r--gerber/primitives.py32
-rw-r--r--gerber/render/rs274x_backend.py72
5 files changed, 133 insertions, 7 deletions
diff --git a/gerber/am_statements.py b/gerber/am_statements.py
index 05ebd9d..084439c 100644
--- a/gerber/am_statements.py
+++ b/gerber/am_statements.py
@@ -179,6 +179,10 @@ class AMCirclePrimitive(AMPrimitive):
diameter = float(modifiers[2])
position = (float(modifiers[3]), float(modifiers[4]))
return cls(code, exposure, diameter, position)
+
+ @classmethod
+ def from_primitive(cls, primitive):
+ return cls(1, 'on', primitive.diameter, primitive.position)
def __init__(self, code, exposure, diameter, position):
validate_coordinates(position)
@@ -247,6 +251,11 @@ class AMVectorLinePrimitive(AMPrimitive):
------
ValueError, TypeError
"""
+
+ @classmethod
+ def from_primitive(cls, primitive):
+ return cls(2, 'on', primitive.aperture.width, primitive.start, primitive.end, 0)
+
@classmethod
def from_gerber(cls, primitive):
modifiers = primitive.strip(' *').split(',')
@@ -406,6 +415,9 @@ class AMOutlinePrimitive(AMPrimitive):
lines.append(Line(prev_point, cur_point, Circle((0,0), 0)))
prev_point = cur_point
+
+ if lines[0].start != lines[-1].end:
+ raise ValueError('Outline must be closed')
return Outline(lines, units=units)
@@ -762,6 +774,8 @@ class AMThermalPrimitive(AMPrimitive):
points = (self._approximate_arc_cw(inner_start_angle, inner_end_angle, inner_radius, self.position)
+ list(reversed(self._approximate_arc_cw(outer_start_angle, outer_end_angle, outer_radius, self.position))))
+ # Add in the last point since outlines should be closed
+ points.append(points[0])
# There are four outlines at rotated sections
for rotation in [0, 90.0, 180.0, 270.0]:
@@ -818,6 +832,14 @@ class AMCenterLinePrimitive(AMPrimitive):
------
ValueError, TypeError
"""
+
+ @classmethod
+ def from_primitive(cls, primitive):
+ width = primitive.width
+ height = primitive.height
+ center = primitive.position
+ rotation = math.degrees(primitive.rotation)
+ return cls(21, 'on', width, height, center, rotation)
@classmethod
def from_gerber(cls, primitive):
diff --git a/gerber/cam.py b/gerber/cam.py
index 53f5c0d..8e31bf0 100644
--- a/gerber/cam.py
+++ b/gerber/cam.py
@@ -166,6 +166,10 @@ class FileSettings(object):
self.zero_suppression == other.zero_suppression and
self.format == other.format and
self.angle_units == other.angle_units)
+
+ def __str__(self):
+ return ('<Settings: %s %s %s %s %s>' %
+ (self.units, self.notation, self.zero_suppression, self.format, self.angle_units))
class CamFile(object):
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py
index dcdd90d..aa25d0a 100644
--- a/gerber/gerber_statements.py
+++ b/gerber/gerber_statements.py
@@ -93,6 +93,11 @@ class ParamStmt(Statement):
class FSParamStmt(ParamStmt):
""" FS - Gerber Format Specification Statement
"""
+
+ @classmethod
+ def from_settings(cls, settings):
+
+ return cls('FS', settings.zero_suppression, settings.notation, settings.format)
@classmethod
def from_dict(cls, stmt_dict):
@@ -279,6 +284,11 @@ class ADParamStmt(ParamStmt):
return cls('AD', dcode, 'C', ([diameter],))
@classmethod
+ def obround(cls, dcode, width, height):
+ '''Create an obrou d aperture definition statement'''
+ return cls('AD', dcode, 'O', ([width, height],))
+
+ @classmethod
def macro(cls, dcode, name):
return cls('AD', dcode, name, '')
diff --git a/gerber/primitives.py b/gerber/primitives.py
index 07a28db..3c85f17 100644
--- a/gerber/primitives.py
+++ b/gerber/primitives.py
@@ -397,6 +397,19 @@ class Circle(Primitive):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))
+
+ def equivalent(self, other, offset):
+ '''Is this the same as the other circle, ignoring the offiset?'''
+
+ if not isinstance(other, Circle):
+ return False
+
+ if self.diameter != other.diameter:
+ return False
+
+ equiv_position = tuple(map(add, other.position, offset))
+
+ return nearly_equal(self.position, equiv_position)
class Ellipse(Primitive):
@@ -487,6 +500,19 @@ class Rectangle(Primitive):
return (math.cos(math.radians(self.rotation)) * self.height +
math.sin(math.radians(self.rotation)) * self.width)
+ def equivalent(self, other, offset):
+ '''Is this the same as the other rect, ignoring the offiset?'''
+
+ if not isinstance(other, Rectangle):
+ return False
+
+ if self.width != other.width or self.height != other.height or self.rotation != other.rotation:
+ return False
+
+ equiv_position = tuple(map(add, other.position, offset))
+
+ return nearly_equal(self.position, equiv_position)
+
class Diamond(Primitive):
"""
@@ -815,6 +841,9 @@ class Outline(Primitive):
self.primitives = primitives
self._to_convert = ['primitives']
+ if self.primitives[0].start != self.primitives[-1].end:
+ raise ValueError('Outline must be closed')
+
@property
def flashed(self):
return True
@@ -833,6 +862,9 @@ class Outline(Primitive):
def offset(self, x_offset=0, y_offset=0):
for p in self.primitives:
p.offset(x_offset, y_offset)
+
+ if self.primitives[0].start != self.primitives[-1].end:
+ raise ValueError('Outline must be closed')
@property
def width(self):
diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py
index bdb77f4..2a0420e 100644
--- a/gerber/render/rs274x_backend.py
+++ b/gerber/render/rs274x_backend.py
@@ -22,6 +22,14 @@ class AMGroupContext(object):
for primitive in nooffset_group.primitives:
if isinstance(primitive, Outline):
self._render_outline(primitive)
+ elif isinstance(primitive, Circle):
+ self._render_circle(primitive)
+ elif isinstance(primitive, Rectangle):
+ self._render_rectangle(primitive)
+ elif isinstance(primitive, Line):
+ self._render_line(primitive)
+ else:
+ raise ValueError('amgroup')
statement = AMParamStmt('AM', name, self._statements_to_string())
return statement
@@ -33,10 +41,21 @@ class AMGroupContext(object):
macro += statement.to_gerber()
return macro
+
+ def _render_circle(self, circle):
+ self.statements.append(AMCirclePrimitive.from_primitive(circle))
+
+ def _render_rectangle(self, rectangle):
+ self.statements.append(AMCenterLinePrimitive.from_primitive(rectangle))
+
+ def _render_line(self, line):
+ self.statements.append(AMVectorLinePrimitive.from_primitive(line))
def _render_outline(self, outline):
self.statements.append(AMOutlinePrimitive.from_primitive(outline))
-
+
+ def _render_thermal(self, thermal):
+ pass
class Rs274xContext(GerberContext):
@@ -59,6 +78,8 @@ class Rs274xContext(GerberContext):
self._next_dcode = 10
self._rects = {}
self._circles = {}
+ self._obrounds = {}
+ self._polygons = {}
self._macros = {}
self._i_none = 0
@@ -67,9 +88,10 @@ class Rs274xContext(GerberContext):
self.settings = settings
self._start_header(settings)
- self._define_dcodes()
+ #self._define_dcodes()
def _start_header(self, settings):
+ self.header.append(FSParamStmt.from_settings(settings))
self.header.append(MOParamStmt.from_units(settings.units))
def _define_dcodes(self):
@@ -151,8 +173,12 @@ class Rs274xContext(GerberContext):
# We already set the function, so the next command doesn't require that
func = None
- self.body.append(CoordStmt.line(func, self._simplify_point(line.end)))
- self._pos = line.end
+ point = self._simplify_point(line.end)
+
+ # In some files, we see a lot of duplicated ponts, so omit those
+ if point[0] != None or point[1] != None:
+ self.body.append(CoordStmt.line(func, self._simplify_point(line.end)))
+ self._pos = line.end
def _render_arc(self, arc, color):
@@ -269,10 +295,33 @@ class Rs274xContext(GerberContext):
aper = self._get_rectangle(rectangle.width, rectangle.height)
self._render_flash(rectangle, aper)
+ def _get_obround(self, width, height, dcode = None):
+
+ key = (width, height)
+ aper = self._obrounds.get(key, None)
+
+ if not aper:
+ if not dcode:
+ dcode = self._next_dcode
+ self._next_dcode += 1
+ else:
+ self._next_dcode = max(dcode + 1, self._next_dcode)
+
+ aper = ADParamStmt.obround(dcode, width, height)
+ self._obrounds[(width, height)] = aper
+ self.header.append(aper)
+
+ return aper
+
def _render_obround(self, obround, color):
+
+ aper = self._get_obround(obround.width, obround.height)
+ self._render_flash(obround, aper)
+
pass
def _render_polygon(self, polygon, color):
+ raise NotImplementedError('Not implemented yet')
pass
def _render_drill(self, circle, color):
@@ -285,8 +334,19 @@ class Rs274xContext(GerberContext):
for primitive in amgroup.primitives:
hash += primitive.__class__.__name__[0]
+
+ bbox = primitive.bounding_box
+ hash += str((bbox[0][1] - bbox[0][0]) * 100000)[0:2]
+ hash += str((bbox[1][1] - bbox[1][0]) * 100000)[0:2]
+
if hasattr(primitive, 'primitives'):
hash += str(len(primitive.primitives))
+
+ if isinstance(primitive, Rectangle):
+ hash += str(primitive.width * 1000000)[0:2]
+ hash += str(primitive.height * 1000000)[0:2]
+ elif isinstance(primitive, Circle):
+ hash += str(primitive.diameter * 1000000)[0:2]
return hash
@@ -331,9 +391,7 @@ class Rs274xContext(GerberContext):
aper = self._get_amacro(amgroup)
self._render_flash(amgroup, aper)
-
-
-
+
def _render_inverted_layer(self):
pass