RS274X backend for rendering. Incompelte still
This commit is contained in:
parent
02dbc6a51e
commit
29c0d82bf5
4 changed files with 403 additions and 4 deletions
|
|
@ -226,6 +226,11 @@ class LPParamStmt(ParamStmt):
|
|||
param = stmt_dict['param']
|
||||
lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark'
|
||||
return cls(param, lp)
|
||||
|
||||
@classmethod
|
||||
def from_region(cls, region):
|
||||
#todo what is the first param?
|
||||
return cls(None, region.level_polarity)
|
||||
|
||||
def __init__(self, param, lp):
|
||||
""" Initialize LPParamStmt class
|
||||
|
|
@ -258,7 +263,21 @@ class LPParamStmt(ParamStmt):
|
|||
class ADParamStmt(ParamStmt):
|
||||
""" AD - Gerber Aperture Definition Statement
|
||||
"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def rect(cls, dcode, width, height):
|
||||
'''Create a rectangular aperture definition statement'''
|
||||
return cls('AD', dcode, 'R', ([width, height],))
|
||||
|
||||
@classmethod
|
||||
def circle(cls, dcode, diameter):
|
||||
'''Create a circular aperture definition statement'''
|
||||
return cls('AD', dcode, 'C', ([diameter],))
|
||||
|
||||
@classmethod
|
||||
def macro(cls, dcode, name):
|
||||
return cls('AD', dcode, name, '')
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, stmt_dict):
|
||||
param = stmt_dict.get('param')
|
||||
|
|
@ -293,7 +312,9 @@ class ADParamStmt(ParamStmt):
|
|||
ParamStmt.__init__(self, param)
|
||||
self.d = d
|
||||
self.shape = shape
|
||||
if modifiers:
|
||||
if isinstance(modifiers, tuple):
|
||||
self.modifiers = modifiers
|
||||
elif modifiers:
|
||||
self.modifiers = [tuple([float(x) for x in m.split("X") if len(x)]) for m in modifiers.split(",") if len(m)]
|
||||
else:
|
||||
self.modifiers = [tuple()]
|
||||
|
|
@ -817,6 +838,14 @@ class CoordStmt(Statement):
|
|||
""" Coordinate Data Block
|
||||
"""
|
||||
|
||||
OP_DRAW = 'D01'
|
||||
OP_MOVE = 'D02'
|
||||
OP_FLASH = 'D03'
|
||||
|
||||
FUNC_LINEAR = 'G01'
|
||||
FUNC_ARC_CW = 'G02'
|
||||
FUNC_ARC_CCW = 'G03'
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, stmt_dict, settings):
|
||||
function = stmt_dict['function']
|
||||
|
|
@ -835,6 +864,22 @@ class CoordStmt(Statement):
|
|||
if j is not None:
|
||||
j = parse_gerber_value(stmt_dict.get('j'), settings.format, settings.zero_suppression)
|
||||
return cls(function, x, y, i, j, op, settings)
|
||||
|
||||
@classmethod
|
||||
def move(cls, func, point):
|
||||
return cls(func, point[0], point[1], None, None, CoordStmt.OP_MOVE, None)
|
||||
|
||||
@classmethod
|
||||
def line(cls, func, point):
|
||||
return cls(func, point[0], point[1], None, None, CoordStmt.OP_DRAW, None)
|
||||
|
||||
@classmethod
|
||||
def arc(cls, func, point, center):
|
||||
return cls(func, point[0], point[1], center[0], center[1], CoordStmt.OP_DRAW, None)
|
||||
|
||||
@classmethod
|
||||
def flash(cls, point):
|
||||
return cls(None, point[0], point[1], None, None, CoordStmt.OP_FLASH, None)
|
||||
|
||||
def __init__(self, function, x, y, i, j, op, settings):
|
||||
""" Initialize CoordStmt class
|
||||
|
|
@ -1003,6 +1048,14 @@ class EofStmt(Statement):
|
|||
|
||||
|
||||
class QuadrantModeStmt(Statement):
|
||||
|
||||
@classmethod
|
||||
def single(cls):
|
||||
return cls('single-quadrant')
|
||||
|
||||
@classmethod
|
||||
def multi(cls):
|
||||
return cls('multi-quadrant')
|
||||
|
||||
@classmethod
|
||||
def from_gerber(cls, line):
|
||||
|
|
@ -1031,6 +1084,14 @@ class RegionModeStmt(Statement):
|
|||
if 'G36' not in line and 'G37' not in line:
|
||||
raise ValueError('%s is not a valid region mode statement' % line)
|
||||
return (cls('on') if line[:3] == 'G36' else cls('off'))
|
||||
|
||||
@classmethod
|
||||
def on(cls):
|
||||
return cls('on')
|
||||
|
||||
@classmethod
|
||||
def off(cls):
|
||||
return cls('off')
|
||||
|
||||
def __init__(self, mode):
|
||||
super(RegionModeStmt, self).__init__('RegionMode')
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@
|
|||
import math
|
||||
from operator import add, sub
|
||||
|
||||
from .utils import validate_coordinates, inch, metric, rotate_point
|
||||
from .utils import validate_coordinates, inch, metric, rotate_point, nearly_equal
|
||||
from jsonpickle.util import PRIMITIVES
|
||||
from __builtin__ import False
|
||||
|
||||
|
||||
class Primitive(object):
|
||||
|
|
@ -120,6 +121,9 @@ class Primitive(object):
|
|||
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
def to_statement(self):
|
||||
pass
|
||||
|
||||
|
||||
class Line(Primitive):
|
||||
|
|
@ -216,7 +220,16 @@ class Line(Primitive):
|
|||
def offset(self, x_offset=0, y_offset=0):
|
||||
self.start = tuple(map(add, self.start, (x_offset, y_offset)))
|
||||
self.end = tuple(map(add, self.end, (x_offset, y_offset)))
|
||||
|
||||
def equivalent(self, other, offset):
|
||||
|
||||
if not isinstance(other, Line):
|
||||
return False
|
||||
|
||||
equiv_start = tuple(map(add, other.start, offset))
|
||||
equiv_end = tuple(map(add, other.end, offset))
|
||||
|
||||
return nearly_equal(self.start, equiv_start) and nearly_equal(self.end, equiv_end)
|
||||
|
||||
class Arc(Primitive):
|
||||
"""
|
||||
|
|
@ -736,7 +749,7 @@ class AMGroup(Primitive):
|
|||
elif prim:
|
||||
self.primitives.append(prim)
|
||||
self._position = None
|
||||
self._to_convert = ['arimitives']
|
||||
self._to_convert = ['primitives']
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
|
|
@ -776,6 +789,21 @@ class AMGroup(Primitive):
|
|||
|
||||
self._position = new_pos
|
||||
|
||||
def equivalent(self, other, offset):
|
||||
'''
|
||||
Is this the macro group the same as the other, ignoring the position offset?
|
||||
'''
|
||||
|
||||
if len(self.primitives) != len(other.primitives):
|
||||
return False
|
||||
|
||||
# We know they have the same number of primitives, so now check them all
|
||||
for i in range(0, len(self.primitives)):
|
||||
if not self.primitives[i].equivalent(other.primitives[i], offset):
|
||||
return False
|
||||
|
||||
# If we didn't find any differences, then they are the same
|
||||
return True
|
||||
|
||||
class Outline(Primitive):
|
||||
"""
|
||||
|
|
@ -816,6 +844,20 @@ class Outline(Primitive):
|
|||
bounding_box = self.bounding_box()
|
||||
return bounding_box[1][1] - bounding_box[1][0]
|
||||
|
||||
def equivalent(self, other, offset):
|
||||
'''
|
||||
Is this the outline the same as the other, ignoring the position offset?
|
||||
'''
|
||||
|
||||
# Quick check if it even makes sense to compare them
|
||||
if type(self) != type(other) or len(self.primitives) != len(other.primitives):
|
||||
return False
|
||||
|
||||
for i in range(0, len(self.primitives)):
|
||||
if not self.primitives[i].equivalent(other.primitives[i], offset):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
class Region(Primitive):
|
||||
"""
|
||||
|
|
|
|||
290
gerber/render/rs274x_backend.py
Normal file
290
gerber/render/rs274x_backend.py
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
|
||||
from .render import GerberContext
|
||||
from ..gerber_statements import *
|
||||
from ..primitives import AMGroup, Arc, Circle, Line, Rectangle
|
||||
|
||||
class Rs274xContext(GerberContext):
|
||||
|
||||
def __init__(self, settings):
|
||||
GerberContext.__init__(self)
|
||||
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
|
||||
|
||||
self._next_dcode = 10
|
||||
self._rects = {}
|
||||
self._circles = {}
|
||||
self._macros = {}
|
||||
|
||||
self._i_none = 0
|
||||
self._j_none = 0
|
||||
|
||||
self._define_dcodes()
|
||||
|
||||
|
||||
def _define_dcodes(self):
|
||||
|
||||
self._get_circle(.1575, 10)
|
||||
self._get_circle(.035, 17)
|
||||
self._get_rectangle(0.1575, 0.1181, 15)
|
||||
self._get_rectangle(0.0492, 0.0118, 16)
|
||||
self._get_circle(.0197, 11)
|
||||
self._get_rectangle(0.0236, 0.0591, 12)
|
||||
self._get_circle(.005, 18)
|
||||
self._get_circle(.008, 19)
|
||||
self._get_circle(.009, 20)
|
||||
self._get_circle(.01, 21)
|
||||
self._get_circle(.02, 22)
|
||||
self._get_circle(.006, 23)
|
||||
self._get_circle(.015, 24)
|
||||
self._get_rectangle(0.1678, 0.1284, 26)
|
||||
self._get_rectangle(0.0338, 0.0694, 25)
|
||||
|
||||
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.header + self.body + self.end
|
||||
|
||||
def set_bounds(self, bounds):
|
||||
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)
|
||||
elif isinstance(aperture, Rectangle):
|
||||
aper = self._get_rectangle(aperture.width, aperture.height)
|
||||
else:
|
||||
raise NotImplementedError('Line with invalid aperture type')
|
||||
|
||||
if aper.d != self._dcode:
|
||||
self.body.append(ApertureStmt(aper.d))
|
||||
self._dcode = aper.d
|
||||
|
||||
def _render_line(self, line, color):
|
||||
|
||||
self._select_aperture(line.aperture)
|
||||
|
||||
# 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
|
||||
|
||||
self.body.append(CoordStmt.line(func, self._simplify_point(line.end)))
|
||||
self._pos = line.end
|
||||
|
||||
def _render_arc(self, arc, color):
|
||||
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
|
||||
if isinstance(p, Line):
|
||||
self._render_line(p, color)
|
||||
else:
|
||||
self._render_arc(p, color)
|
||||
|
||||
|
||||
self.body.append(RegionModeStmt.off())
|
||||
|
||||
def _render_level_polarity(self, region):
|
||||
if region.level_polarity != self._level_polarity:
|
||||
self._level_polarity = region.level_polarity
|
||||
self.body.append(LPParamStmt.from_region(region))
|
||||
|
||||
def _render_flash(self, primitive, aperture):
|
||||
|
||||
if aperture.d != self._dcode:
|
||||
self.body.append(ApertureStmt(aperture.d))
|
||||
self._dcode = aperture.d
|
||||
|
||||
self.body.append(CoordStmt.flash( self._simplify_point(primitive.position)))
|
||||
self._pos = primitive.position
|
||||
|
||||
def _get_circle(self, diameter, dcode = None):
|
||||
'''Define a circlar aperture'''
|
||||
|
||||
aper = self._circles.get(diameter, 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)
|
||||
self._circles[diameter] = aper
|
||||
self.header.append(aper)
|
||||
|
||||
return aper
|
||||
|
||||
def _render_circle(self, circle, color):
|
||||
|
||||
aper = self._get_circle(circle.diameter)
|
||||
self._render_flash(circle, aper)
|
||||
|
||||
def _get_rectangle(self, width, height, dcode = None):
|
||||
'''Get a rectanglar aperture. If it isn't defined, create it'''
|
||||
|
||||
key = (width, 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)
|
||||
self._rects[(width, height)] = aper
|
||||
self.header.append(aper)
|
||||
|
||||
return aper
|
||||
|
||||
def _render_rectangle(self, rectangle, color):
|
||||
|
||||
aper = self._get_rectangle(rectangle.width, rectangle.height)
|
||||
self._render_flash(rectangle, aper)
|
||||
|
||||
def _render_obround(self, obround, color):
|
||||
pass
|
||||
|
||||
def _render_polygon(self, polygon, color):
|
||||
pass
|
||||
|
||||
def _render_drill(self, circle, color):
|
||||
pass
|
||||
|
||||
def _hash_amacro(self, amgroup):
|
||||
'''Calculate a very quick hash code for deciding if we should even check AM groups for comparision'''
|
||||
|
||||
hash = ''
|
||||
for primitive in amgroup.primitives:
|
||||
|
||||
hash += primitive.__class__.__name__[0]
|
||||
if hasattr(primitive, 'primitives'):
|
||||
hash += str(len(primitive.primitives))
|
||||
|
||||
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 = self._macros.get(hash, None)
|
||||
|
||||
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
|
||||
statements = []
|
||||
aperdef = ADParamStmt.macro(dcode, hash)
|
||||
|
||||
# Store the dcode and the original so we can check if it really is the same
|
||||
macro = (aperdef, amgroup)
|
||||
self._macros[hash] = macro
|
||||
|
||||
else:
|
||||
# We hae a definition, but check that the groups actually are the same
|
||||
offset = (amgroup.position[0] - macro[1].position[0], amgroup.position[1] - macro[1].position[1])
|
||||
if not amgroup.equivalent(macro[1], offset):
|
||||
raise ValueError('Two AMGroup have the same hash but are not equivalent')
|
||||
|
||||
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 post_render_primitives(self):
|
||||
'''No more primitives, so set the end marker'''
|
||||
|
||||
self.body.append()
|
||||
|
|
@ -288,3 +288,9 @@ def rotate_point(point, angle, center=(0.0, 0.0)):
|
|||
x = center[0] + (cos(angle) * xdelta) - (sin(angle) * ydelta)
|
||||
y = center[1] + (sin(angle) * xdelta) - (cos(angle) * ydelta)
|
||||
return (x, y)
|
||||
|
||||
|
||||
def nearly_equal(point1, point2, ndigits = 6):
|
||||
'''Are the points nearly equal'''
|
||||
|
||||
return round(point1[0] - point2[0], ndigits) == 0 and round(point1[1] - point2[1], ndigits) == 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue