From 20a9af279ac2217a39b73903ff94b916a3025be2 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 1 Mar 2016 00:06:14 +0800 Subject: More rendering of AMGroup to statements --- gerber/am_statements.py | 22 +++++++++++++ gerber/cam.py | 4 +++ gerber/gerber_statements.py | 10 ++++++ gerber/primitives.py | 32 ++++++++++++++++++ gerber/render/rs274x_backend.py | 72 +++++++++++++++++++++++++++++++++++++---- 5 files changed, 133 insertions(+), 7 deletions(-) (limited to 'gerber') 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 ('' % + (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): @@ -278,6 +283,11 @@ class ADParamStmt(ParamStmt): '''Create a circular aperture definition statement''' 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 -- cgit