summaryrefslogtreecommitdiff
path: root/gerber/render/rs274x_backend.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerber/render/rs274x_backend.py')
-rw-r--r--gerber/render/rs274x_backend.py510
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