diff options
Diffstat (limited to 'gerber/render/rs274x_backend.py')
-rw-r--r-- | gerber/render/rs274x_backend.py | 510 |
1 files changed, 0 insertions, 510 deletions
diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py deleted file mode 100644 index c7af2ea..0000000 --- a/gerber/render/rs274x_backend.py +++ /dev/null @@ -1,510 +0,0 @@ -"""Renders an in-memory Gerber file to statements which can be written to a string -""" -from copy import deepcopy - -try: - from cStringIO import StringIO -except(ImportError): - from io import StringIO - -from .render import GerberContext -from ..am_statements import * -from ..gerber_statements import * -from ..primitives import AMGroup, Arc, Circle, Line, Obround, Outline, Polygon, Rectangle - - -class AMGroupContext(object): - '''A special renderer to generate aperature macros from an AMGroup''' - - def __init__(self): - self.statements = [] - - def render(self, amgroup, name): - - if amgroup.stmt: - # We know the statement it was generated from, so use that to create the AMParamStmt - # It will give a much better result - - stmt = deepcopy(amgroup.stmt) - stmt.name = name - - return stmt - - else: - # Clone ourselves, then offset by the psotion so that - # our render doesn't have to consider offset. Just makes things simpler - nooffset_group = deepcopy(amgroup) - nooffset_group.position = (0, 0) - - # Now draw the shapes - 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) - elif isinstance(primitive, Polygon): - self._render_polygon(primitive) - else: - raise ValueError('amgroup') - - statement = AMParamStmt('AM', name, self._statements_to_string()) - return statement - - def _statements_to_string(self): - macro = '' - - for statement in self.statements: - 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_polygon(self, polygon): - self.statements.append(AMPolygonPrimitive.from_primitive(polygon)) - - def _render_thermal(self, thermal): - pass - - -class Rs274xContext(GerberContext): - - def __init__(self, settings): - GerberContext.__init__(self) - self.comments = [] - self.header = [] - self.body = [] - self.end = [EofStmt()] - - # Current values so we know if we have to execute - # moves, levey changes before anything else - self._level_polarity = None - self._pos = (None, None) - self._func = None - self._quadrant_mode = None - self._dcode = None - - # Primarily for testing and comarison to files, should we write - # flashes as a single statement or a move plus flash? Set to true - # to do in a single statement. Normally this can be false - self.condensed_flash = True - - # When closing a region, force a D02 staement to close a region. - # This is normally not necessary because regions are closed with a G37 - # staement, but this will add an extra statement for doubly close - # the region - self.explicit_region_move_end = False - - self._next_dcode = 10 - self._rects = {} - self._circles = {} - self._obrounds = {} - self._polygons = {} - self._macros = {} - - self._i_none = 0 - self._j_none = 0 - - self.settings = settings - - self._start_header(settings) - - def _start_header(self, settings): - self.header.append(FSParamStmt.from_settings(settings)) - self.header.append(MOParamStmt.from_units(settings.units)) - - def _simplify_point(self, point): - return (point[0] if point[0] != self._pos[0] else None, point[1] if point[1] != self._pos[1] else None) - - def _simplify_offset(self, point, offset): - - if point[0] != offset[0]: - xoffset = point[0] - offset[0] - else: - xoffset = self._i_none - - if point[1] != offset[1]: - yoffset = point[1] - offset[1] - else: - yoffset = self._j_none - - return (xoffset, yoffset) - - @property - def statements(self): - return self.comments + self.header + self.body + self.end - - def set_bounds(self, bounds, *args, **kwargs): - pass - - def paint_background(self): - pass - - def _select_aperture(self, aperture): - - # Select the right aperture if not already selected - if aperture: - if isinstance(aperture, Circle): - aper = self._get_circle(aperture.diameter, aperture.hole_diameter, aperture.hole_width, aperture.hole_height) - elif isinstance(aperture, Rectangle): - aper = self._get_rectangle(aperture.width, aperture.height) - elif isinstance(aperture, Obround): - aper = self._get_obround(aperture.width, aperture.height) - elif isinstance(aperture, AMGroup): - aper = self._get_amacro(aperture) - else: - raise NotImplementedError('Line with invalid aperture type') - - if aper.d != self._dcode: - self.body.append(ApertureStmt(aper.d)) - self._dcode = aper.d - - def pre_render_primitive(self, primitive): - - if hasattr(primitive, 'comment'): - self.body.append(CommentStmt(primitive.comment)) - - def _render_line(self, line, color, default_polarity='dark'): - - self._select_aperture(line.aperture) - - self._render_level_polarity(line, default_polarity) - - # Get the right function - if self._func != CoordStmt.FUNC_LINEAR: - func = CoordStmt.FUNC_LINEAR - else: - func = None - self._func = CoordStmt.FUNC_LINEAR - - if self._pos != line.start: - self.body.append(CoordStmt.move(func, self._simplify_point(line.start))) - self._pos = line.start - # We already set the function, so the next command doesn't require that - func = None - - 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 - elif func: - self.body.append(CoordStmt.mode(func)) - - def _render_arc(self, arc, color, default_polarity='dark'): - - # Optionally set the quadrant mode if it has changed: - if arc.quadrant_mode != self._quadrant_mode: - - if arc.quadrant_mode != 'multi-quadrant': - self.body.append(QuadrantModeStmt.single()) - else: - self.body.append(QuadrantModeStmt.multi()) - - self._quadrant_mode = arc.quadrant_mode - - # Select the right aperture if not already selected - self._select_aperture(arc.aperture) - - self._render_level_polarity(arc, default_polarity) - - # Find the right movement mode. Always set to be sure it is really right - dir = arc.direction - if dir == 'clockwise': - func = CoordStmt.FUNC_ARC_CW - self._func = CoordStmt.FUNC_ARC_CW - elif dir == 'counterclockwise': - func = CoordStmt.FUNC_ARC_CCW - self._func = CoordStmt.FUNC_ARC_CCW - else: - raise ValueError('Invalid circular interpolation mode') - - if self._pos != arc.start: - # TODO I'm not sure if this is right - self.body.append(CoordStmt.move(CoordStmt.FUNC_LINEAR, self._simplify_point(arc.start))) - self._pos = arc.start - - center = self._simplify_offset(arc.center, arc.start) - end = self._simplify_point(arc.end) - self.body.append(CoordStmt.arc(func, end, center)) - self._pos = arc.end - - def _render_region(self, region, color): - - self._render_level_polarity(region) - - self.body.append(RegionModeStmt.on()) - - for p in region.primitives: - - # Make programmatically generated primitives within a region with - # unset level polarity inherit the region's level polarity - if isinstance(p, Line): - self._render_line(p, color, default_polarity=region.level_polarity) - else: - self._render_arc(p, color, default_polarity=region.level_polarity) - - if self.explicit_region_move_end: - self.body.append(CoordStmt.move(None, None)) - - self.body.append(RegionModeStmt.off()) - - def _render_level_polarity(self, obj, default='dark'): - obj_polarity = obj.level_polarity if obj.level_polarity is not None else default - if obj_polarity != self._level_polarity: - self._level_polarity = obj_polarity - self.body.append(LPParamStmt('LP', obj_polarity)) - - def _render_flash(self, primitive, aperture): - - self._render_level_polarity(primitive) - - if aperture.d != self._dcode: - self.body.append(ApertureStmt(aperture.d)) - self._dcode = aperture.d - - if self.condensed_flash: - self.body.append(CoordStmt.flash(self._simplify_point(primitive.position))) - else: - self.body.append(CoordStmt.move(None, self._simplify_point(primitive.position))) - self.body.append(CoordStmt.flash(None)) - - self._pos = primitive.position - - def _get_circle(self, diameter, hole_diameter=None, hole_width=None, - hole_height=None, dcode = None): - '''Define a circlar aperture''' - - key = (diameter, hole_diameter, hole_width, hole_height) - aper = self._circles.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.circle(dcode, diameter, hole_diameter, hole_width, hole_height) - self._circles[(diameter, hole_diameter, hole_width, hole_height)] = aper - self.header.append(aper) - - return aper - - def _render_circle(self, circle, color): - - aper = self._get_circle(circle.diameter, circle.hole_diameter, circle.hole_width, circle.hole_height) - self._render_flash(circle, aper) - - def _get_rectangle(self, width, height, hole_diameter=None, hole_width=None, - hole_height=None, dcode = None): - '''Get a rectanglar aperture. If it isn't defined, create it''' - - key = (width, height, hole_diameter, hole_width, hole_height) - aper = self._rects.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.rect(dcode, width, height, hole_diameter, hole_width, hole_height) - self._rects[(width, height, hole_diameter, hole_width, hole_height)] = aper - self.header.append(aper) - - return aper - - def _render_rectangle(self, rectangle, color): - - aper = self._get_rectangle(rectangle.width, rectangle.height, - rectangle.hole_diameter, - rectangle.hole_width, rectangle.hole_height) - self._render_flash(rectangle, aper) - - def _get_obround(self, width, height, hole_diameter=None, hole_width=None, - hole_height=None, dcode = None): - - key = (width, height, hole_diameter, hole_width, hole_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, hole_diameter, hole_width, hole_height) - self._obrounds[key] = aper - self.header.append(aper) - - return aper - - def _render_obround(self, obround, color): - - aper = self._get_obround(obround.width, obround.height, - obround.hole_diameter, obround.hole_width, - obround.hole_height) - self._render_flash(obround, aper) - - def _render_polygon(self, polygon, color): - - aper = self._get_polygon(polygon.radius, polygon.sides, - polygon.rotation, polygon.hole_diameter, - polygon.hole_width, polygon.hole_height) - self._render_flash(polygon, aper) - - def _get_polygon(self, radius, num_vertices, rotation, hole_diameter=None, - hole_width=None, hole_height=None, dcode = None): - - key = (radius, num_vertices, rotation, hole_diameter, hole_width, hole_height) - aper = self._polygons.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.polygon(dcode, radius * 2, num_vertices, - rotation, hole_diameter, hole_width, - hole_height) - self._polygons[key] = aper - self.header.append(aper) - - return aper - - def _render_drill(self, drill, color): - raise ValueError('Drills are not valid in RS274X files') - - def _hash_amacro(self, amgroup): - '''Calculate a very quick hash code for deciding if we should even check AM groups for comparision''' - - # We always start with an X because this forms part of the name - # Basically, in some cases, the name might start with a C, R, etc. That can appear - # to conflict with normal aperture definitions. Technically, it shouldn't because normal - # aperture definitions should have a comma, but in some cases the commit is omitted - hash = 'X' - 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] - - if len(hash) > 20: - # The hash might actually get quite complex, so stop before - # it gets too long - break - - return hash - - def _get_amacro(self, amgroup, dcode = None): - # Macros are a little special since we don't have a good way to compare them quickly - # but in most cases, this should work - - hash = self._hash_amacro(amgroup) - macro = None - macroinfo = self._macros.get(hash, None) - - if macroinfo: - - # We have a definition, but check that the groups actually are the same - for macro in macroinfo: - - # Macros should have positions, right? But if the macro is selected for non-flashes - # then it won't have a position. This is of course a bad gerber, but they do exist - if amgroup.position: - position = amgroup.position - else: - position = (0, 0) - - offset = (position[0] - macro[1].position[0], position[1] - macro[1].position[1]) - if amgroup.equivalent(macro[1], offset): - break - macro = None - - # Did we find one in the group0 - if not macro: - # This is a new macro, so define it - if not dcode: - dcode = self._next_dcode - self._next_dcode += 1 - else: - self._next_dcode = max(dcode + 1, self._next_dcode) - - # Create the statements - # TODO - amrenderer = AMGroupContext() - statement = amrenderer.render(amgroup, hash) - - self.header.append(statement) - - aperdef = ADParamStmt.macro(dcode, hash) - self.header.append(aperdef) - - # Store the dcode and the original so we can check if it really is the same - # If it didn't have a postition, set it to 0, 0 - if amgroup.position == None: - amgroup.position = (0, 0) - macro = (aperdef, amgroup) - - if macroinfo: - macroinfo.append(macro) - else: - self._macros[hash] = [macro] - - return macro[0] - - def _render_amgroup(self, amgroup, color): - - aper = self._get_amacro(amgroup) - self._render_flash(amgroup, aper) - - def _render_inverted_layer(self): - pass - - def new_render_layer(self): - # TODO Might need to implement this - pass - - def flatten(self): - # TODO Might need to implement this - pass - - def dump(self): - """Write the rendered file to a StringIO steam""" - statements = map(lambda stmt: stmt.to_gerber(self.settings), self.statements) - stream = StringIO() - for statement in statements: - stream.write(statement + '\n') - - return stream |