From 597427d785d6f44348fe15631f2c184504195fb0 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 6 Oct 2014 08:33:53 -0400 Subject: add excellon statements --- gerber/gerber_statements.py | 597 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 gerber/gerber_statements.py (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py new file mode 100644 index 0000000..5a9d046 --- /dev/null +++ b/gerber/gerber_statements.py @@ -0,0 +1,597 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +""" +gerber.statements +================= +**Gerber file statement classes** + +""" +from .utils import parse_gerber_value, write_gerber_value, decimal_string + + +__all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', + 'LPParamStmt', 'ADParamStmt', 'AMParamStmt', 'INParamStmt', + 'LNParamStmt', 'CoordStmt', 'ApertureStmt', 'CommentStmt', + 'EofStmt', 'UnknownStmt'] + + +class Statement(object): + def __init__(self, type): + self.type = type + + def __str__(self): + s = "<{0} ".format(self.__class__.__name__) + + for key, value in self.__dict__.items(): + s += "{0}={1} ".format(key, value) + + s = s.rstrip() + ">" + return s + + +class ParamStmt(Statement): + def __init__(self, param): + Statement.__init__(self, "PARAM") + self.param = param + + +class FSParamStmt(ParamStmt): + """ FS - Gerber Format Specification Statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + """ + """ + param = stmt_dict.get('param').strip() + zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' + notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' + x = map(int, stmt_dict.get('x').strip()) + format = (x[0], x[1]) + return cls(param, zeros, notation, format) + + def __init__(self, param, zero_suppression='leading', + notation='absolute', format=(2, 4)): + """ Initialize FSParamStmt class + + .. note:: + The FS command specifies the format of the coordinate data. It + must only be used once at the beginning of a file. It must be + specified before the first use of coordinate data. + + Parameters + ---------- + param : string + Parameter. + + zero_suppression : string + Zero-suppression mode. May be either 'leading' or 'trailing' + + notation : string + Notation mode. May be either 'absolute' or 'incremental' + + format : tuple (int, int) + Gerber precision format expressed as a tuple containing: + (number of integer-part digits, number of decimal-part digits) + + Returns + ------- + ParamStmt : FSParamStmt + Initialized FSParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.zero_suppression = zero_suppression + self.notation = notation + self.format = format + + def to_gerber(self): + zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' + notation = 'A' if self.notation == 'absolute' else 'I' + format = ''.join(map(str, self.format)) + return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, + format, format) + + def __str__(self): + return ('' % + (self.format[0], self.format[1], self.zero_suppression, + self.notation)) + + +class MOParamStmt(ParamStmt): + """ MO - Gerber Mode (measurement units) Statement. + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + mo = 'inch' if stmt_dict.get('mo') == 'IN' else 'metric' + return cls(param, mo) + + def __init__(self, param, mo): + """ Initialize MOParamStmt class + + Parameters + ---------- + param : string + Parameter. + + mo : string + Measurement units. May be either 'inch' or 'metric' + + Returns + ------- + ParamStmt : MOParamStmt + Initialized MOParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.mode = mo + + def to_gerber(self): + mode = 'MM' if self.mode == 'metric' else 'IN' + return '%MO{0}*%'.format(mode) + + def __str__(self): + mode_str = 'millimeters' if self.mode == 'metric' else 'inches' + return ('' % mode_str) + + +class IPParamStmt(ParamStmt): + """ IP - Gerber Image Polarity Statement. (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + ip = 'positive' if stmt_dict.get('ip') == 'POS' else 'negative' + return cls(param, ip) + + def __init__(self, param, ip): + """ Initialize IPParamStmt class + + Parameters + ---------- + param : string + Parameter string. + + ip : string + Image polarity. May be either'positive' or 'negative' + + Returns + ------- + ParamStmt : IPParamStmt + Initialized IPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.ip = ip + + def to_gerber(self): + ip = 'POS' if self.ip == 'positive' else 'NEG' + return '%IP{0}*%'.format(ip) + + def __str__(self): + return ('' % self.ip) + + +class OFParamStmt(ParamStmt): + """ OF - Gerber Offset statement (Deprecated) + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + a = float(stmt_dict.get('a')) + b = float(stmt_dict.get('b')) + return cls(param, a, b) + + def __init__(self, param, a, b): + """ Initialize OFParamStmt class + + Parameters + ---------- + param : string + Parameter + + a : float + Offset along the output device A axis + + b : float + Offset along the output device B axis + + Returns + ------- + ParamStmt : OFParamStmt + Initialized OFParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.a = a + self.b = b + + def to_gerber(self): + ret = '%OF' + if self.a: + ret += 'A' + decimal_string(self.a, precision=6) + if self.b: + ret += 'B' + decimal_string(self.b, precision=6) + return ret + '*%' + + def __str__(self): + offset_str = '' + if self.a: + offset_str += ('X: %f' % self.a) + if self.b: + offset_str += ('Y: %f' % self.b) + return ('' % offset_str) + + +class LPParamStmt(ParamStmt): + """ LP - Gerber Level Polarity statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('lp') + lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark' + return cls(param, lp) + + def __init__(self, param, lp): + """ Initialize LPParamStmt class + + Parameters + ---------- + param : string + Parameter + + lp : string + Level polarity. May be either 'clear' or 'dark' + + Returns + ------- + ParamStmt : LPParamStmt + Initialized LPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.lp = lp + + def to_gerber(self, settings): + lp = 'C' if self.lp == 'clear' else 'dark' + return '%LP{0}*%'.format(self.lp) + + def __str__(self): + return '' % self.lp + + +class ADParamStmt(ParamStmt): + """ AD - Gerber Aperture Definition Statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + d = int(stmt_dict.get('d')) + shape = stmt_dict.get('shape') + modifiers = stmt_dict.get('modifiers') + if modifiers is not None: + modifiers = [[float(x) for x in m.split('X')] + for m in modifiers.split(',')] + return cls(param, d, shape, modifiers) + + def __init__(self, param, d, shape, modifiers): + """ Initialize ADParamStmt class + + Parameters + ---------- + param : string + Parameter code + + d : int + Aperture D-code + + shape : string + aperture name + + modifiers : list of lists of floats + Shape modifiers + + Returns + ------- + ParamStmt : LPParamStmt + Initialized LPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.d = d + self.shape = shape + self.modifiers = modifiers + + def to_gerber(self, settings): + return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, + ','.join(['X'.join(e) for e in self.modifiers])) + + def __str__(self): + if self.shape == 'C': + shape = 'circle' + elif self.shape == 'R': + shape = 'rectangle' + elif self.shape == 'O': + shape = 'oblong' + else: + shape = self.shape + + return '' % (self.d, shape) + + +class AMParamStmt(ParamStmt): + """ AM - Aperture Macro Statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, name, macro): + """ Initialize AMParamStmt class + + Parameters + ---------- + param : string + Parameter code + + name : string + Aperture macro name + + macro : string + Aperture macro string + + Returns + ------- + ParamStmt : AMParamStmt + Initialized AMParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.name = name + self.macro = macro + + def to_gerber(self): + return '%AM{0}*{1}*%'.format(self.name, self.macro) + + def __str__(self): + return '' % (self.name, macro) + + +class INParamStmt(ParamStmt): + """ IN - Image Name Statement + """ + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, name): + """ Initialize INParamStmt class + + Parameters + ---------- + param : string + Parameter code + + name : string + Image name + + Returns + ------- + ParamStmt : INParamStmt + Initialized INParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.name = name + + def to_gerber(self): + return '%IN{0}*%'.format(self.name) + + def __str__(self): + return '' % self.name + + +class LNParamStmt(ParamStmt): + """ LN - Level Name Statement (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, name): + """ Initialize LNParamStmt class + + Parameters + ---------- + param : string + Parameter code + + name : string + Level name + + Returns + ------- + ParamStmt : LNParamStmt + Initialized LNParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.name = name + + def to_gerber(self): + return '%LN{0}*%'.format(self.name) + + def __str__(self): + return '' % self.name + + +class CoordStmt(Statement): + """ Coordinate Data Block + """ + + @classmethod + def from_dict(cls, stmt_dict, settings): + zeros = settings['zero_suppression'] + format = settings['format'] + function = stmt_dict.get('function') + x = stmt_dict.get('x') + y = stmt_dict.get('y') + i = stmt_dict.get('i') + j = stmt_dict.get('j') + op = stmt_dict.get('op') + + if x is not None: + x = parse_gerber_value(stmt_dict.get('x'), + format, zeros) + if y is not None: + y = parse_gerber_value(stmt_dict.get('y'), + format, zeros) + if i is not None: + i = parse_gerber_value(stmt_dict.get('i'), + format, zeros) + if j is not None: + j = parse_gerber_value(stmt_dict.get('j'), + format, zeros) + return cls(function, x, y, i, j, op, settings) + + def __init__(self, function, x, y, i, j, op, settings): + """ Initialize CoordStmt class + + Parameters + ---------- + function : string + function + + x : float + X coordinate + + y : float + Y coordinate + + i : float + Coordinate offset in the X direction + + j : float + Coordinate offset in the Y direction + + op : string + Operation code + + settings : dict {'zero_suppression', 'format'} + Gerber file coordinate format + + Returns + ------- + Statement : CoordStmt + Initialized CoordStmt class. + + """ + Statement.__init__(self, "COORD") + self.zero_suppression = settings['zero_suppression'] + self.format = settings['format'] + self.function = function + self.x = x + self.y = y + self.i = i + self.j = j + self.op = op + + def to_gerber(self): + ret = '' + if self.function: + ret += self.function + if self.x: + ret += 'X{0}'.format(write_gerber_value(self.x, self.zeros, + self.format)) + if self.y: + ret += 'Y{0}'.format(write_gerber_value(self.y, self. zeros, + self.format)) + if self.i: + ret += 'I{0}'.format(write_gerber_value(self.i, self.zeros, + self.format)) + if self.j: + ret += 'J{0}'.format(write_gerber_value(self.j, self.zeros, + self.format)) + if self.op: + ret += self.op + return ret + '*' + + def __str__(self): + coord_str = '' + if self.function: + coord_str += 'Fn: %s ' % self.function + if self.x: + coord_str += 'X: %f ' % self.x + if self.y: + coord_str += 'Y: %f ' % self.y + if self.i: + coord_str += 'I: %f ' % self.i + if self.j: + coord_str += 'J: %f ' % self.j + if self.op: + if self.op == 'D01': + op = 'Lights On' + elif self.op == 'D02': + op = 'Lights Off' + elif self.op == 'D03': + op = 'Flash' + else: + op = self.op + coord_str += 'Op: %s' % op + + return '' % coord_str + + +class ApertureStmt(Statement): + """ Aperture Statement + """ + def __init__(self, d): + Statement.__init__(self, "APERTURE") + self.d = int(d) + + def to_gerber(self): + return 'G54D{0}*'.format(self.d) + + def __str__(self): + return '' % self.d + + +class CommentStmt(Statement): + """ Comment Statment + """ + def __init__(self, comment): + Statement.__init__(self, "COMMENT") + self.comment = comment + + def to_gerber(self): + return 'G04{0}*'.format(self.comment) + + def __str__(self): + return '' % self.comment + + +class EofStmt(Statement): + """ EOF Statement + """ + def __init__(self): + Statement.__init__(self, "EOF") + + def to_gerber(self): + return 'M02*' + + def __str__(self): + return '' + + +class UnknownStmt(Statement): + """ Unknown Statement + """ + def __init__(self, line): + Statement.__init__(self, "UNKNOWN") + self.line = line -- cgit From af97dcf2a8200d9319e20d2789dbb0baa0611ba5 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 7 Oct 2014 22:44:08 -0400 Subject: fix excellon render --- gerber/gerber_statements.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 5a9d046..2f58a37 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -16,8 +16,8 @@ __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', class Statement(object): - def __init__(self, type): - self.type = type + def __init__(self, stype): + self.type = stype def __str__(self): s = "<{0} ".format(self.__class__.__name__) @@ -47,8 +47,8 @@ class FSParamStmt(ParamStmt): zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' x = map(int, stmt_dict.get('x').strip()) - format = (x[0], x[1]) - return cls(param, zeros, notation, format) + fmt = (x[0], x[1]) + return cls(param, zeros, notation, fmt) def __init__(self, param, zero_suppression='leading', notation='absolute', format=(2, 4)): @@ -88,9 +88,9 @@ class FSParamStmt(ParamStmt): def to_gerber(self): zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' notation = 'A' if self.notation == 'absolute' else 'I' - format = ''.join(map(str, self.format)) + fmt = ''.join(map(str, self.format)) return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, - format, format) + fmt, fmt) def __str__(self): return ('' % @@ -588,6 +588,28 @@ class EofStmt(Statement): def __str__(self): return '' +class QuadrantModeStmt(Statement): + + @classmethod + def from_gerber(cls, line): + line = line.strip() + if 'G74' not in line and 'G75' not in line: + raise ValueError('%s is not a valid quadrant mode statement' + % line) + return (cls('single-quadrant') if line[:3] == 'G74' + else cls('multi-quadrant')) + + def __init__(self, mode): + super(QuadrantModeStmt, self).__init__('Quadrant Mode') + mode = mode.lower + if mode not in ['single-quadrant', 'multi-quadrant']: + raise ValueError('Quadrant mode must be "single-quadrant" \ + or "multi-quadrant"') + self.mode = mode + + def to_gerber(self): + return 'G74*' if self.mode == 'single-quadrant' else 'G75*' + class UnknownStmt(Statement): """ Unknown Statement -- cgit From bcb6cbc50dea975954b8a3864690f68ab5e984b7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 8 Oct 2014 22:49:49 -0400 Subject: start arc --- gerber/gerber_statements.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 2f58a37..90952b2 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -12,7 +12,7 @@ from .utils import parse_gerber_value, write_gerber_value, decimal_string __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', 'LPParamStmt', 'ADParamStmt', 'AMParamStmt', 'INParamStmt', 'LNParamStmt', 'CoordStmt', 'ApertureStmt', 'CommentStmt', - 'EofStmt', 'UnknownStmt'] + 'EofStmt', 'QuadrantModeStmt', 'RegionModeStmt', 'UnknownStmt'] class Statement(object): @@ -601,7 +601,7 @@ class QuadrantModeStmt(Statement): def __init__(self, mode): super(QuadrantModeStmt, self).__init__('Quadrant Mode') - mode = mode.lower + mode = mode.lower() if mode not in ['single-quadrant', 'multi-quadrant']: raise ValueError('Quadrant mode must be "single-quadrant" \ or "multi-quadrant"') @@ -610,6 +610,25 @@ class QuadrantModeStmt(Statement): def to_gerber(self): return 'G74*' if self.mode == 'single-quadrant' else 'G75*' +class RegionModeStmt(Statement): + + @classmethod + def from_gerber(cls, line): + line = line.strip() + 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')) + + def __init__(self, mode): + super(RegionModeStmt, self).__init__('Region Mode') + mode = mode.lower() + if mode not in ['on', 'off']: + raise ValueError('Valid modes are "on" or "off"') + self.mode = mode + + def to_gerber(self): + return 'G36*' if self.mode == 'on' else 'G37*' + class UnknownStmt(Statement): """ Unknown Statement -- cgit From 84bfd34e918251ff82f4b3818bc6268feab72efe Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 09:51:29 -0400 Subject: Add mode statement parsing --- gerber/gerber_statements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 90952b2..76a6f0c 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -600,7 +600,7 @@ class QuadrantModeStmt(Statement): else cls('multi-quadrant')) def __init__(self, mode): - super(QuadrantModeStmt, self).__init__('Quadrant Mode') + super(QuadrantModeStmt, self).__init__('QuadrantMode') mode = mode.lower() if mode not in ['single-quadrant', 'multi-quadrant']: raise ValueError('Quadrant mode must be "single-quadrant" \ @@ -611,7 +611,7 @@ class QuadrantModeStmt(Statement): return 'G74*' if self.mode == 'single-quadrant' else 'G75*' class RegionModeStmt(Statement): - + @classmethod def from_gerber(cls, line): line = line.strip() @@ -620,7 +620,7 @@ class RegionModeStmt(Statement): return (cls('on') if line[:3] == 'G36' else cls('off')) def __init__(self, mode): - super(RegionModeStmt, self).__init__('Region Mode') + super(RegionModeStmt, self).__init__('RegionMode') mode = mode.lower() if mode not in ['on', 'off']: raise ValueError('Valid modes are "on" or "off"') -- cgit From 8851bc17b94a921453b0afd9c2421cb30f8d4425 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 18:09:17 -0400 Subject: Doc update --- gerber/gerber_statements.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 76a6f0c..4c5133a 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -16,6 +16,20 @@ __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', class Statement(object): + """ Gerber statement Base class + + The statement class provides a type attribute. + + Parameters + ---------- + type : string + String identifying the statement type. + + Attributes + ---------- + type : string + String identifying the statement type. + """ def __init__(self, stype): self.type = stype @@ -30,6 +44,20 @@ class Statement(object): class ParamStmt(Statement): + """ Gerber parameter statement Base class + + The parameter statement class provides a parameter type attribute. + + Parameters + ---------- + param : string + two-character code identifying the parameter statement type. + + Attributes + ---------- + param : string + Parameter type code + """ def __init__(self, param): Statement.__init__(self, "PARAM") self.param = param -- cgit From f2f411493ea303075d5dbdd7656c572dda61cf67 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 22:10:28 -0400 Subject: doc update --- gerber/gerber_statements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 4c5133a..2caced5 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -1,9 +1,9 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- """ -gerber.statements -================= -**Gerber file statement classes** +Gerber (RS-274X) Statements +=========================== +**Gerber RS-274X file statement classes** """ from .utils import parse_gerber_value, write_gerber_value, decimal_string -- cgit From 1750c3c60aeffc813dad8191ceabcdb90dd2e0a6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 10 Oct 2014 13:07:54 -0400 Subject: Add tests --- gerber/gerber_statements.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 2caced5..9072b58 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -284,9 +284,9 @@ class LPParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.lp = lp - def to_gerber(self, settings): - lp = 'C' if self.lp == 'clear' else 'dark' - return '%LP{0}*%'.format(self.lp) + def to_gerber(self): + lp = 'C' if self.lp == 'clear' else 'D' + return '%LP{0}*%'.format(lp) def __str__(self): return '' % self.lp @@ -593,6 +593,7 @@ class ApertureStmt(Statement): class CommentStmt(Statement): """ Comment Statment """ + def __init__(self, comment): Statement.__init__(self, "COMMENT") self.comment = comment @@ -616,6 +617,7 @@ class EofStmt(Statement): def __str__(self): return '' + class QuadrantModeStmt(Statement): @classmethod @@ -638,6 +640,7 @@ class QuadrantModeStmt(Statement): def to_gerber(self): return 'G74*' if self.mode == 'single-quadrant' else 'G75*' + class RegionModeStmt(Statement): @classmethod @@ -664,3 +667,6 @@ class UnknownStmt(Statement): def __init__(self, line): Statement.__init__(self, "UNKNOWN") self.line = line + + def to_gerber(self): + return self.line -- cgit From 76c03a55c91addff71339d80cf17560926f1580b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 10 Oct 2014 20:36:38 -0400 Subject: Working region fills and level polarity. Renders Altium-generated gerbers like a champ! --- gerber/gerber_statements.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 9072b58..a22eae2 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -17,9 +17,9 @@ __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', class Statement(object): """ Gerber statement Base class - + The statement class provides a type attribute. - + Parameters ---------- type : string @@ -27,7 +27,7 @@ class Statement(object): Attributes ---------- - type : string + type : string String identifying the statement type. """ def __init__(self, stype): @@ -45,9 +45,9 @@ class Statement(object): class ParamStmt(Statement): """ Gerber parameter statement Base class - + The parameter statement class provides a parameter type attribute. - + Parameters ---------- param : string @@ -55,7 +55,7 @@ class ParamStmt(Statement): Attributes ---------- - param : string + param : string Parameter type code """ def __init__(self, param): @@ -260,7 +260,7 @@ class LPParamStmt(ParamStmt): @classmethod def from_dict(cls, stmt_dict): - param = stmt_dict.get('lp') + param = stmt_dict['param'] lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark' return cls(param, lp) @@ -667,6 +667,6 @@ class UnknownStmt(Statement): def __init__(self, line): Statement.__init__(self, "UNKNOWN") self.line = line - + def to_gerber(self): return self.line -- cgit From ae3bbff8b0849e0b49dc139396d7f8c57334a7b8 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 10 Oct 2014 23:07:51 -0400 Subject: Added excellon format detection --- gerber/gerber_statements.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index a22eae2..218074f 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -133,7 +133,12 @@ class MOParamStmt(ParamStmt): @classmethod def from_dict(cls, stmt_dict): param = stmt_dict.get('param') - mo = 'inch' if stmt_dict.get('mo') == 'IN' else 'metric' + if stmt_dict.get('mo').lower() == 'in': + mo = 'inch' + elif stmt_dict.get('mo').lower() == 'mm': + mo = 'metric' + else: + mo = None return cls(param, mo) def __init__(self, param, mo): -- cgit From 6d2db67e6d0973ce26ce3a6700ca44295f73fea7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 18 Oct 2014 01:44:51 -0400 Subject: Refactor rendering --- gerber/gerber_statements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 218074f..6f7b73d 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -12,7 +12,8 @@ from .utils import parse_gerber_value, write_gerber_value, decimal_string __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', 'LPParamStmt', 'ADParamStmt', 'AMParamStmt', 'INParamStmt', 'LNParamStmt', 'CoordStmt', 'ApertureStmt', 'CommentStmt', - 'EofStmt', 'QuadrantModeStmt', 'RegionModeStmt', 'UnknownStmt'] + 'EofStmt', 'QuadrantModeStmt', 'RegionModeStmt', 'UnknownStmt', + 'ParamStmt'] class Statement(object): -- cgit From 18e3b87625ddb739faeddffcaed48e12db6c7e8b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 19 Oct 2014 22:23:00 -0400 Subject: Test update --- gerber/gerber_statements.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 6f7b73d..44eeee0 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -72,10 +72,10 @@ class FSParamStmt(ParamStmt): def from_dict(cls, stmt_dict): """ """ - param = stmt_dict.get('param').strip() + param = stmt_dict.get('param') zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' - x = map(int, stmt_dict.get('x').strip()) + x = map(int, stmt_dict.get('x')) fmt = (x[0], x[1]) return cls(param, zeros, notation, fmt) @@ -471,9 +471,9 @@ class CoordStmt(Statement): @classmethod def from_dict(cls, stmt_dict, settings): - zeros = settings['zero_suppression'] - format = settings['format'] - function = stmt_dict.get('function') + zeros = settings.zero_suppression + format = settings.format + function = stmt_dict['function'] x = stmt_dict.get('x') y = stmt_dict.get('y') i = stmt_dict.get('i') @@ -527,8 +527,8 @@ class CoordStmt(Statement): """ Statement.__init__(self, "COORD") - self.zero_suppression = settings['zero_suppression'] - self.format = settings['format'] + self.zero_suppression = settings.zero_suppression + self.format = settings.format self.function = function self.x = x self.y = y @@ -628,7 +628,6 @@ class QuadrantModeStmt(Statement): @classmethod def from_gerber(cls, line): - line = line.strip() if 'G74' not in line and 'G75' not in line: raise ValueError('%s is not a valid quadrant mode statement' % line) @@ -651,7 +650,6 @@ class RegionModeStmt(Statement): @classmethod def from_gerber(cls, line): - line = line.strip() 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')) -- cgit From 4f076d7b769b0f488888d268a9a199b7545afdd7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 26 Oct 2014 17:59:57 -0400 Subject: Merge aperture fixses from upstream --- gerber/gerber_statements.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 44eeee0..e392ec5 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -308,9 +308,6 @@ class ADParamStmt(ParamStmt): d = int(stmt_dict.get('d')) shape = stmt_dict.get('shape') modifiers = stmt_dict.get('modifiers') - if modifiers is not None: - modifiers = [[float(x) for x in m.split('X')] - for m in modifiers.split(',')] return cls(param, d, shape, modifiers) def __init__(self, param, d, shape, modifiers): @@ -339,7 +336,10 @@ class ADParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.d = d self.shape = shape - self.modifiers = modifiers + if modifiers is not None: + self.modifiers = [[x for x in m.split("X")] for m in modifiers.split(",")] + else: + self.modifiers = [] def to_gerber(self, settings): return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, -- cgit From 459e0205d17724b35f0f869cd46f679bc70defce Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Sun, 26 Oct 2014 22:25:26 -0200 Subject: Fix ValueError, missing self. --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index e392ec5..32d3784 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -394,7 +394,7 @@ class AMParamStmt(ParamStmt): return '%AM{0}*{1}*%'.format(self.name, self.macro) def __str__(self): - return '' % (self.name, macro) + return '' % (self.name, self.macro) class INParamStmt(ParamStmt): -- cgit From ab69ee0172353e64fbe5099a974341e88feaf24b Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Mon, 10 Nov 2014 12:24:09 -0200 Subject: Bunch of small fixes to improve Gerber read/write. --- gerber/gerber_statements.py | 54 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 32d3784..4aaa1d0 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -215,8 +215,8 @@ class OFParamStmt(ParamStmt): @classmethod def from_dict(cls, stmt_dict): param = stmt_dict.get('param') - a = float(stmt_dict.get('a')) - b = float(stmt_dict.get('b')) + a = float(stmt_dict.get('a', 0)) + b = float(stmt_dict.get('b', 0)) return cls(param, a, b) def __init__(self, param, a, b): @@ -245,17 +245,17 @@ class OFParamStmt(ParamStmt): def to_gerber(self): ret = '%OF' - if self.a: - ret += 'A' + decimal_string(self.a, precision=6) - if self.b: - ret += 'B' + decimal_string(self.b, precision=6) + if self.a is not None: + ret += 'A' + decimal_string(self.a, precision=5) + if self.b is not None: + ret += 'B' + decimal_string(self.b, precision=5) return ret + '*%' def __str__(self): offset_str = '' - if self.a: + if self.a is not None: offset_str += ('X: %f' % self.a) - if self.b: + if self.b is not None: offset_str += ('Y: %f' % self.b) return ('' % offset_str) @@ -341,7 +341,7 @@ class ADParamStmt(ParamStmt): else: self.modifiers = [] - def to_gerber(self, settings): + def to_gerber(self): return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(e) for e in self.modifiers])) @@ -540,18 +540,14 @@ class CoordStmt(Statement): ret = '' if self.function: ret += self.function - if self.x: - ret += 'X{0}'.format(write_gerber_value(self.x, self.zeros, - self.format)) - if self.y: - ret += 'Y{0}'.format(write_gerber_value(self.y, self. zeros, - self.format)) - if self.i: - ret += 'I{0}'.format(write_gerber_value(self.i, self.zeros, - self.format)) - if self.j: - ret += 'J{0}'.format(write_gerber_value(self.j, self.zeros, - self.format)) + if self.x is not None: + ret += 'X{0}'.format(write_gerber_value(self.x, self.format, self.zero_suppression)) + if self.y is not None: + ret += 'Y{0}'.format(write_gerber_value(self.y, self.format, self.zero_suppression)) + if self.i is not None: + ret += 'I{0}'.format(write_gerber_value(self.i, self.format, self.zero_suppression)) + if self.j is not None: + ret += 'J{0}'.format(write_gerber_value(self.j, self.format, self.zero_suppression)) if self.op: ret += self.op return ret + '*' @@ -560,13 +556,13 @@ class CoordStmt(Statement): coord_str = '' if self.function: coord_str += 'Fn: %s ' % self.function - if self.x: + if self.x is not None: coord_str += 'X: %f ' % self.x - if self.y: + if self.y is not None: coord_str += 'Y: %f ' % self.y - if self.i: + if self.i is not None: coord_str += 'I: %f ' % self.i - if self.j: + if self.j is not None: coord_str += 'J: %f ' % self.j if self.op: if self.op == 'D01': @@ -585,12 +581,16 @@ class CoordStmt(Statement): class ApertureStmt(Statement): """ Aperture Statement """ - def __init__(self, d): + def __init__(self, d, deprecated=None): Statement.__init__(self, "APERTURE") self.d = int(d) + self.deprecated = True if deprecated is not None else False def to_gerber(self): - return 'G54D{0}*'.format(self.d) + if self.deprecated: + return 'G54D{0}*'.format(self.d) + else: + return 'D{0}*'.format(self.d) def __str__(self): return '' % self.d -- cgit From 29deffcf77e963ae81aec9f8cbc61b029f3052d5 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 5 Dec 2014 23:59:28 -0500 Subject: add ipc2581 primitives --- gerber/gerber_statements.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 4aaa1d0..05c84b8 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -1,5 +1,19 @@ -#! /usr/bin/env python +#!/usr/bin/env python # -*- coding: utf-8 -*- + +# copyright 2014 Hamilton Kibbe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """ Gerber (RS-274X) Statements =========================== -- cgit From be5b94b8c09f647e5e19f795927060f75461c283 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Mon, 15 Dec 2014 23:38:27 -0200 Subject: Fix parsing for OrCAD. * Modify the way we parse parameters to allow more than one parameter in a single line as in the following example: %FSLAX55Y55*MOIN*% %IR0*IPPOS*OFA0.00000B0.00000*MIA0B0*SFA1.00000B1.00000*% (this is from OrCAD 16 default output) * Add missing deprecated parameters. * Change API to use given FileSettings on output. This allows us to use pcb-tools to convert between FS formats. --- gerber/gerber_statements.py | 434 ++++++++++++++++++++++++++++++-------------- 1 file changed, 297 insertions(+), 137 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 05c84b8..1a6f646 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -23,13 +23,6 @@ Gerber (RS-274X) Statements from .utils import parse_gerber_value, write_gerber_value, decimal_string -__all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', - 'LPParamStmt', 'ADParamStmt', 'AMParamStmt', 'INParamStmt', - 'LNParamStmt', 'CoordStmt', 'ApertureStmt', 'CommentStmt', - 'EofStmt', 'QuadrantModeStmt', 'RegionModeStmt', 'UnknownStmt', - 'ParamStmt'] - - class Statement(object): """ Gerber statement Base class @@ -128,17 +121,21 @@ class FSParamStmt(ParamStmt): self.notation = notation self.format = format - def to_gerber(self): - zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' - notation = 'A' if self.notation == 'absolute' else 'I' - fmt = ''.join(map(str, self.format)) - return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, - fmt, fmt) + def to_gerber(self, settings=None): + if settings: + zero_suppression = 'L' if settings.zero_suppression == 'leading' else 'T' + notation = 'A' if settings.notation == 'absolute' else 'I' + fmt = ''.join(map(str, settings.format)) + else: + zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' + notation = 'A' if self.notation == 'absolute' else 'I' + fmt = ''.join(map(str, self.format)) + + return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, fmt, fmt) def __str__(self): return ('' % - (self.format[0], self.format[1], self.zero_suppression, - self.notation)) + (self.format[0], self.format[1], self.zero_suppression, self.notation)) class MOParamStmt(ParamStmt): @@ -176,7 +173,7 @@ class MOParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.mode = mo - def to_gerber(self): + def to_gerber(self, settings=None): mode = 'MM' if self.mode == 'metric' else 'IN' return '%MO{0}*%'.format(mode) @@ -185,95 +182,6 @@ class MOParamStmt(ParamStmt): return ('' % mode_str) -class IPParamStmt(ParamStmt): - """ IP - Gerber Image Polarity Statement. (Deprecated) - """ - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('param') - ip = 'positive' if stmt_dict.get('ip') == 'POS' else 'negative' - return cls(param, ip) - - def __init__(self, param, ip): - """ Initialize IPParamStmt class - - Parameters - ---------- - param : string - Parameter string. - - ip : string - Image polarity. May be either'positive' or 'negative' - - Returns - ------- - ParamStmt : IPParamStmt - Initialized IPParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.ip = ip - - def to_gerber(self): - ip = 'POS' if self.ip == 'positive' else 'NEG' - return '%IP{0}*%'.format(ip) - - def __str__(self): - return ('' % self.ip) - - -class OFParamStmt(ParamStmt): - """ OF - Gerber Offset statement (Deprecated) - """ - - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('param') - a = float(stmt_dict.get('a', 0)) - b = float(stmt_dict.get('b', 0)) - return cls(param, a, b) - - def __init__(self, param, a, b): - """ Initialize OFParamStmt class - - Parameters - ---------- - param : string - Parameter - - a : float - Offset along the output device A axis - - b : float - Offset along the output device B axis - - Returns - ------- - ParamStmt : OFParamStmt - Initialized OFParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.a = a - self.b = b - - def to_gerber(self): - ret = '%OF' - if self.a is not None: - ret += 'A' + decimal_string(self.a, precision=5) - if self.b is not None: - ret += 'B' + decimal_string(self.b, precision=5) - return ret + '*%' - - def __str__(self): - offset_str = '' - if self.a is not None: - offset_str += ('X: %f' % self.a) - if self.b is not None: - offset_str += ('Y: %f' % self.b) - return ('' % offset_str) - - class LPParamStmt(ParamStmt): """ LP - Gerber Level Polarity statement """ @@ -304,7 +212,7 @@ class LPParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.lp = lp - def to_gerber(self): + def to_gerber(self, settings=None): lp = 'C' if self.lp == 'clear' else 'D' return '%LP{0}*%'.format(lp) @@ -351,13 +259,15 @@ class ADParamStmt(ParamStmt): self.d = d self.shape = shape if modifiers is not None: - self.modifiers = [[x for x in m.split("X")] for m in modifiers.split(",")] + self.modifiers = [[x for x in m.split("X")] for m in modifiers.split(",") if len(m)] else: self.modifiers = [] - def to_gerber(self): - return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, - ','.join(['X'.join(e) for e in self.modifiers])) + def to_gerber(self, settings=None): + if len(self.modifiers): + return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(e) for e in self.modifiers])) + else: + return '%ADD{0}{1}*%'.format(self.d, self.shape) def __str__(self): if self.shape == 'C': @@ -404,15 +314,51 @@ class AMParamStmt(ParamStmt): self.name = name self.macro = macro - def to_gerber(self): + def to_gerber(self, settings=None): return '%AM{0}*{1}*%'.format(self.name, self.macro) def __str__(self): return '' % (self.name, self.macro) +class ASParamStmt(ParamStmt): + """ AS - Axis Select. (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + mode = stmt_dict.get('mode') + return cls(param, mode) + + def __init__(self, param, ip): + """ Initialize ASParamStmt class + + Parameters + ---------- + param : string + Parameter string. + + mode : string + Axis select. May be either 'AXBY' or 'AYBX' + + Returns + ------- + ParamStmt : ASParamStmt + Initialized ASParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.mode = mode + + def to_gerber(self, settings=None): + return '%AS{0}*%'.format(self.mode) + + def __str__(self): + return ('' % self.mode) + + class INParamStmt(ParamStmt): - """ IN - Image Name Statement + """ IN - Image Name Statement (Deprecated) """ @classmethod def from_dict(cls, stmt_dict): @@ -438,13 +384,235 @@ class INParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.name = name - def to_gerber(self): + def to_gerber(self, settings=None): return '%IN{0}*%'.format(self.name) def __str__(self): return '' % self.name +class IPParamStmt(ParamStmt): + """ IP - Gerber Image Polarity Statement. (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + ip = 'positive' if stmt_dict.get('ip') == 'POS' else 'negative' + return cls(param, ip) + + def __init__(self, param, ip): + """ Initialize IPParamStmt class + + Parameters + ---------- + param : string + Parameter string. + + ip : string + Image polarity. May be either'positive' or 'negative' + + Returns + ------- + ParamStmt : IPParamStmt + Initialized IPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.ip = ip + + def to_gerber(self, settings=None): + ip = 'POS' if self.ip == 'positive' else 'NEG' + return '%IP{0}*%'.format(ip) + + def __str__(self): + return ('' % self.ip) + + +class IRParamStmt(ParamStmt): + """ IR - Image Rotation Param (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, angle): + """ Initialize IRParamStmt class + + Parameters + ---------- + param : string + Parameter code + + angle : int + Image angle + + Returns + ------- + ParamStmt : IRParamStmt + Initialized IRParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.angle = angle + + def to_gerber(self, settings=None): + return '%IR{0}*%'.format(self.angle) + + def __str__(self): + return '' % self.angle + + +class MIParamStmt(ParamStmt): + """ MI - Image Mirror Param (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + a = int(stmt_dict.get('a', 0)) + b = int(stmt_dict.get('b', 0)) + return cls(param, a, b) + + def __init__(self, param, a, b): + """ Initialize MIParamStmt class + + Parameters + ---------- + param : string + Parameter code + + a : int + Mirror for A output devices axis (0=disabled, 1=mirrored) + + b : int + Mirror for B output devices axis (0=disabled, 1=mirrored) + + Returns + ------- + ParamStmt : MIParamStmt + Initialized MIParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.a = a + self.b = b + + def to_gerber(self, settings=None): + ret = "%MI" + if self.a is not None: + ret += "A{0}".format(self.a) + if self.b is not None: + ret += "B{0}".format(self.b) + + return ret + + def __str__(self): + return '' % (self.a, self.b) + + +class OFParamStmt(ParamStmt): + """ OF - Gerber Offset statement (Deprecated) + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + a = float(stmt_dict.get('a', 0)) + b = float(stmt_dict.get('b', 0)) + return cls(param, a, b) + + def __init__(self, param, a, b): + """ Initialize OFParamStmt class + + Parameters + ---------- + param : string + Parameter + + a : float + Offset along the output device A axis + + b : float + Offset along the output device B axis + + Returns + ------- + ParamStmt : OFParamStmt + Initialized OFParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.a = a + self.b = b + + def to_gerber(self, settings=None): + ret = '%OF' + if self.a is not None: + ret += 'A' + decimal_string(self.a, precision=5) + if self.b is not None: + ret += 'B' + decimal_string(self.b, precision=5) + return ret + '*%' + + def __str__(self): + offset_str = '' + if self.a is not None: + offset_str += ('X: %f' % self.a) + if self.b is not None: + offset_str += ('Y: %f' % self.b) + return ('' % offset_str) + + +class SFParamStmt(ParamStmt): + """ SF - Scale Factor Param (Deprecated) + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + a = float(stmt_dict.get('a', 1)) + b = float(stmt_dict.get('b', 1)) + return cls(param, a, b) + + def __init__(self, param, a, b): + """ Initialize OFParamStmt class + + Parameters + ---------- + param : string + Parameter + + a : float + Scale factor for the output device A axis + + b : float + Scale factor for the output device B axis + + Returns + ------- + ParamStmt : SFParamStmt + Initialized SFParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.a = a + self.b = b + + def to_gerber(self, settings=None): + ret = '%SF' + if self.a is not None: + ret += 'A' + decimal_string(self.a, precision=5) + if self.b is not None: + ret += 'B' + decimal_string(self.b, precision=5) + return ret + '*%' + + def __str__(self): + scale_factor = '' + if self.a is not None: + scale_factor += ('X: %f' % self.a) + if self.b is not None: + scale_factor += ('Y: %f' % self.b) + return ('' % scale_factor) + + class LNParamStmt(ParamStmt): """ LN - Level Name Statement (Deprecated) """ @@ -472,7 +640,7 @@ class LNParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.name = name - def to_gerber(self): + def to_gerber(self, settings=None): return '%LN{0}*%'.format(self.name) def __str__(self): @@ -485,8 +653,6 @@ class CoordStmt(Statement): @classmethod def from_dict(cls, stmt_dict, settings): - zeros = settings.zero_suppression - format = settings.format function = stmt_dict['function'] x = stmt_dict.get('x') y = stmt_dict.get('y') @@ -495,17 +661,13 @@ class CoordStmt(Statement): op = stmt_dict.get('op') if x is not None: - x = parse_gerber_value(stmt_dict.get('x'), - format, zeros) + x = parse_gerber_value(stmt_dict.get('x'), settings.format, settings.zero_suppression) if y is not None: - y = parse_gerber_value(stmt_dict.get('y'), - format, zeros) + y = parse_gerber_value(stmt_dict.get('y'), settings.format, settings.zero_suppression) if i is not None: - i = parse_gerber_value(stmt_dict.get('i'), - format, zeros) + i = parse_gerber_value(stmt_dict.get('i'), settings.format, settings.zero_suppression) if j is not None: - j = parse_gerber_value(stmt_dict.get('j'), - format, zeros) + j = parse_gerber_value(stmt_dict.get('j'), settings.format, settings.zero_suppression) return cls(function, x, y, i, j, op, settings) def __init__(self, function, x, y, i, j, op, settings): @@ -541,8 +703,6 @@ class CoordStmt(Statement): """ Statement.__init__(self, "COORD") - self.zero_suppression = settings.zero_suppression - self.format = settings.format self.function = function self.x = x self.y = y @@ -550,18 +710,18 @@ class CoordStmt(Statement): self.j = j self.op = op - def to_gerber(self): + def to_gerber(self, settings=None): ret = '' if self.function: ret += self.function if self.x is not None: - ret += 'X{0}'.format(write_gerber_value(self.x, self.format, self.zero_suppression)) + ret += 'X{0}'.format(write_gerber_value(self.x, settings.format, settings.zero_suppression)) if self.y is not None: - ret += 'Y{0}'.format(write_gerber_value(self.y, self.format, self.zero_suppression)) + ret += 'Y{0}'.format(write_gerber_value(self.y, settings.format, settings.zero_suppression)) if self.i is not None: - ret += 'I{0}'.format(write_gerber_value(self.i, self.format, self.zero_suppression)) + ret += 'I{0}'.format(write_gerber_value(self.i, settings.format, settings.zero_suppression)) if self.j is not None: - ret += 'J{0}'.format(write_gerber_value(self.j, self.format, self.zero_suppression)) + ret += 'J{0}'.format(write_gerber_value(self.j, settings.format, settings.zero_suppression)) if self.op: ret += self.op return ret + '*' @@ -600,7 +760,7 @@ class ApertureStmt(Statement): self.d = int(d) self.deprecated = True if deprecated is not None else False - def to_gerber(self): + def to_gerber(self, settings=None): if self.deprecated: return 'G54D{0}*'.format(self.d) else: @@ -618,7 +778,7 @@ class CommentStmt(Statement): Statement.__init__(self, "COMMENT") self.comment = comment - def to_gerber(self): + def to_gerber(self, settings=None): return 'G04{0}*'.format(self.comment) def __str__(self): @@ -631,7 +791,7 @@ class EofStmt(Statement): def __init__(self): Statement.__init__(self, "EOF") - def to_gerber(self): + def to_gerber(self, settings=None): return 'M02*' def __str__(self): @@ -656,7 +816,7 @@ class QuadrantModeStmt(Statement): or "multi-quadrant"') self.mode = mode - def to_gerber(self): + def to_gerber(self, settings=None): return 'G74*' if self.mode == 'single-quadrant' else 'G75*' @@ -675,7 +835,7 @@ class RegionModeStmt(Statement): raise ValueError('Valid modes are "on" or "off"') self.mode = mode - def to_gerber(self): + def to_gerber(self, settings=None): return 'G36*' if self.mode == 'on' else 'G37*' @@ -686,5 +846,5 @@ class UnknownStmt(Statement): Statement.__init__(self, "UNKNOWN") self.line = line - def to_gerber(self): + def to_gerber(self, settings=None): return self.line -- cgit From cbb662491c273e97cfceed94b29a77ce865244dd Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Wed, 14 Jan 2015 03:15:52 -0200 Subject: Refactor AM aperture handling and add unit conversion support * Add support to convert between metric/impertial * AM primitives are now properly created and can be converted between metric/imperial. (only Outline primitive is supported, no rendering yet) --- gerber/gerber_statements.py | 128 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 4 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 1a6f646..f799f5a 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -259,13 +259,19 @@ class ADParamStmt(ParamStmt): self.d = d self.shape = shape if modifiers is not None: - self.modifiers = [[x for x in m.split("X")] for m in modifiers.split(",") if len(m)] + self.modifiers = [[float(x) for x in m.split("X")] for m in modifiers.split(",") if len(m)] else: self.modifiers = [] + def to_inch(self): + self.modifiers = [[x / 25.4 for x in modifier] for modifier in self.modifiers] + + def to_metric(self): + self.modifiers = [[x * 25.4 for x in modifier] for modifier in self.modifiers] + def to_gerber(self, settings=None): if len(self.modifiers): - return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(e) for e in self.modifiers])) + return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(["%.4f" % x for x in modifier]) for modifier in self.modifiers])) else: return '%ADD{0}{1}*%'.format(self.d, self.shape) @@ -282,6 +288,77 @@ class ADParamStmt(ParamStmt): return '' % (self.d, shape) +class AMPrimitive(object): + + def __init__(self, code, exposure): + self.code = code + self.exposure = exposure + + def to_inch(self): + pass + + def to_metric(self): + pass + + +class AMOutlinePrimitive(AMPrimitive): + + @classmethod + def from_gerber(cls, primitive): + modifiers = primitive.split(",") + + code = int(modifiers[0]) + exposure = "on" if modifiers[1] == "1" else "off" + n = int(modifiers[2]) + start_point = (float(modifiers[3]), float(modifiers[4])) + points = [] + + for i in range(n): + points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1]))) + + rotation = float(modifiers[-1]) + + return cls(code, exposure, start_point, points, rotation) + + def __init__(self, code, exposure, start_point, points, rotation): + super(AMOutlinePrimitive, self).__init__(code, exposure) + + self.start_point = start_point + self.points = points + self.rotation = rotation + + def to_inch(self): + self.start_point = tuple([x / 25.4 for x in self.start_point]) + self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points]) + + def to_metric(self): + self.start_point = tuple([x * 25.4 for x in self.start_point]) + self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points]) + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + exposure="1" if self.exposure == "on" else "0", + n_points=len(self.points), + start_point="%.4f,%.4f" % self.start_point, + points=",".join(["%.4f,%.4f" % point for point in self.points]), + rotation=str(self.rotation) + ) + return "{code},{exposure},{n_points},{start_point},{points},{rotation}".format(**data) + + +class AMUnsupportPrimitive: + @classmethod + def from_gerber(cls, primitive): + return cls(primitive) + + def __init__(self, primitive): + self.primitive = primitive + + def to_gerber(self, settings=None): + return self.primitive + + class AMParamStmt(ParamStmt): """ AM - Aperture Macro Statement """ @@ -312,10 +389,29 @@ class AMParamStmt(ParamStmt): """ ParamStmt.__init__(self, param) self.name = name - self.macro = macro + self.primitives = self._parsePrimitives(macro) + + def _parsePrimitives(self, macro): + primitives = [] + + for primitive in macro.split("*"): + if primitive[0] == "4": + primitives.append(AMOutlinePrimitive.from_gerber(primitive)) + else: + primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) + + return primitives + + def to_inch(self): + for primitive in self.primitives: + primitive.to_inch() + + def to_metric(self): + for primitive in self.primitives: + primitive.to_metric() def to_gerber(self, settings=None): - return '%AM{0}*{1}*%'.format(self.name, self.macro) + return '%AM{0}*{1}*%'.format(self.name, "".join([primitive.to_gerber(settings) for primitive in self.primitives])) def __str__(self): return '' % (self.name, self.macro) @@ -726,6 +822,30 @@ class CoordStmt(Statement): ret += self.op return ret + '*' + def to_inch(self): + if self.x is not None: + self.x = self.x / 25.4 + if self.y is not None: + self.y = self.y / 25.4 + if self.i is not None: + self.i = self.i / 25.4 + if self.j is not None: + self.j = self.j / 25.4 + if self.function == "G71": + self.function = "G70" + + def to_metric(self): + if self.x is not None: + self.x = self.x * 25.4 + if self.y is not None: + self.y = self.y * 25.4 + if self.i is not None: + self.i = self.i * 25.4 + if self.j is not None: + self.j = self.j * 25.4 + if self.function == "G70": + self.function = "G71" + def __str__(self): coord_str = '' if self.function: -- cgit From a9b5a17c534247fe5c82c82b945305f3855b8bef Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Wed, 14 Jan 2015 14:30:53 -0200 Subject: Fix Mirror (deprecated) param generation --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index f799f5a..83ae143 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -598,7 +598,7 @@ class MIParamStmt(ParamStmt): ret += "A{0}".format(self.a) if self.b is not None: ret += "B{0}".format(self.b) - + ret += "*%" return ret def __str__(self): -- cgit From c054136a6531404e3b20aadbc7fba2ec25b50a4a Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 26 Jan 2015 22:16:00 -0500 Subject: Added some tests --- gerber/gerber_statements.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 83ae143..3419948 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -251,8 +251,8 @@ class ADParamStmt(ParamStmt): Returns ------- - ParamStmt : LPParamStmt - Initialized LPParamStmt class. + ParamStmt : ADParamStmt + Initialized ADParamStmt class. """ ParamStmt.__init__(self, param) @@ -389,6 +389,7 @@ class AMParamStmt(ParamStmt): """ ParamStmt.__init__(self, param) self.name = name + self.macro = macro self.primitives = self._parsePrimitives(macro) def _parsePrimitives(self, macro): -- cgit From 208149d6769e608918e04977a5af110bce9c5bd6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 26 Jan 2015 22:24:45 -0500 Subject: merge upstream changes --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 3419948..d84b5e0 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -281,7 +281,7 @@ class ADParamStmt(ParamStmt): elif self.shape == 'R': shape = 'rectangle' elif self.shape == 'O': - shape = 'oblong' + shape = 'obround' else: shape = self.shape -- cgit From 1cc20b351c10b1fa19817f29edd8c54a27aeee4b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 2 Feb 2015 11:42:47 -0500 Subject: tests --- gerber/gerber_statements.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index d84b5e0..0978aca 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -145,12 +145,14 @@ class MOParamStmt(ParamStmt): @classmethod def from_dict(cls, stmt_dict): param = stmt_dict.get('param') - if stmt_dict.get('mo').lower() == 'in': + if stmt_dict.get('mo') is None: + mo = None + elif stmt_dict.get('mo').lower() not in ('in', 'mm'): + raise ValueError('Mode may be mm or in') + elif stmt_dict.get('mo').lower() == 'in': mo = 'inch' - elif stmt_dict.get('mo').lower() == 'mm': - mo = 'metric' else: - mo = None + mo = 'metric' return cls(param, mo) def __init__(self, param, mo): @@ -347,7 +349,7 @@ class AMOutlinePrimitive(AMPrimitive): return "{code},{exposure},{n_points},{start_point},{points},{rotation}".format(**data) -class AMUnsupportPrimitive: +class AMUnsupportPrimitive(object): @classmethod def from_gerber(cls, primitive): return cls(primitive) @@ -652,9 +654,9 @@ class OFParamStmt(ParamStmt): def __str__(self): offset_str = '' if self.a is not None: - offset_str += ('X: %f' % self.a) + offset_str += ('X: %f ' % self.a) if self.b is not None: - offset_str += ('Y: %f' % self.b) + offset_str += ('Y: %f ' % self.b) return ('' % offset_str) -- cgit From f98b918634f23cf822b0d054ac4b6a0b790bb760 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 2 Feb 2015 20:03:26 -0500 Subject: Added some Aperture Macro Primitives. Moved AM primitives to seperate file --- gerber/gerber_statements.py | 83 +++++---------------------------------------- 1 file changed, 8 insertions(+), 75 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 0978aca..7b1b56d 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -21,6 +21,7 @@ Gerber (RS-274X) Statements """ from .utils import parse_gerber_value, write_gerber_value, decimal_string +from .am_statements import * class Statement(object): @@ -290,77 +291,6 @@ class ADParamStmt(ParamStmt): return '' % (self.d, shape) -class AMPrimitive(object): - - def __init__(self, code, exposure): - self.code = code - self.exposure = exposure - - def to_inch(self): - pass - - def to_metric(self): - pass - - -class AMOutlinePrimitive(AMPrimitive): - - @classmethod - def from_gerber(cls, primitive): - modifiers = primitive.split(",") - - code = int(modifiers[0]) - exposure = "on" if modifiers[1] == "1" else "off" - n = int(modifiers[2]) - start_point = (float(modifiers[3]), float(modifiers[4])) - points = [] - - for i in range(n): - points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1]))) - - rotation = float(modifiers[-1]) - - return cls(code, exposure, start_point, points, rotation) - - def __init__(self, code, exposure, start_point, points, rotation): - super(AMOutlinePrimitive, self).__init__(code, exposure) - - self.start_point = start_point - self.points = points - self.rotation = rotation - - def to_inch(self): - self.start_point = tuple([x / 25.4 for x in self.start_point]) - self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points]) - - def to_metric(self): - self.start_point = tuple([x * 25.4 for x in self.start_point]) - self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points]) - - def to_gerber(self, settings=None): - data = dict( - code=self.code, - exposure="1" if self.exposure == "on" else "0", - n_points=len(self.points), - start_point="%.4f,%.4f" % self.start_point, - points=",".join(["%.4f,%.4f" % point for point in self.points]), - rotation=str(self.rotation) - ) - return "{code},{exposure},{n_points},{start_point},{points},{rotation}".format(**data) - - -class AMUnsupportPrimitive(object): - @classmethod - def from_gerber(cls, primitive): - return cls(primitive) - - def __init__(self, primitive): - self.primitive = primitive - - def to_gerber(self, settings=None): - return self.primitive - - class AMParamStmt(ParamStmt): """ AM - Aperture Macro Statement """ @@ -396,9 +326,12 @@ class AMParamStmt(ParamStmt): def _parsePrimitives(self, macro): primitives = [] - - for primitive in macro.split("*"): - if primitive[0] == "4": + for primitive in macro.split('*'): + # Couldn't find anything explicit about leading whitespace in the spec... + primitive = primitive.lstrip() + if primitive[0] == '0': + primitives.append(AMCommentPrimitive.from_gerber(primitive)) + if primitive[0] == '4': primitives.append(AMOutlinePrimitive.from_gerber(primitive)) else: primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) @@ -414,7 +347,7 @@ class AMParamStmt(ParamStmt): primitive.to_metric() def to_gerber(self, settings=None): - return '%AM{0}*{1}*%'.format(self.name, "".join([primitive.to_gerber(settings) for primitive in self.primitives])) + return '%AM{0}*{1}%'.format(self.name, '\n'.join([primitive.to_gerber(settings) for primitive in self.primitives])) def __str__(self): return '' % (self.name, self.macro) -- cgit From e38071868a7ea676e6d4bf80e8f8646b8e0af80b Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Sun, 8 Feb 2015 00:29:08 -0200 Subject: Fix copy-paste error on ASParamStmt --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 7b1b56d..48d5d93 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -362,7 +362,7 @@ class ASParamStmt(ParamStmt): mode = stmt_dict.get('mode') return cls(param, mode) - def __init__(self, param, ip): + def __init__(self, param, mode): """ Initialize ASParamStmt class Parameters -- cgit From 41f9475b132001d52064392057e376c6423c33dc Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 9 Feb 2015 17:39:24 -0500 Subject: Tests and bugfixes --- gerber/gerber_statements.py | 47 ++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 16 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 48d5d93..1401345 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -262,19 +262,19 @@ class ADParamStmt(ParamStmt): self.d = d self.shape = shape if modifiers is not None: - self.modifiers = [[float(x) for x in m.split("X")] for m in modifiers.split(",") if len(m)] + self.modifiers = [tuple([float(x) for x in m.split("X")]) for m in modifiers.split(",") if len(m)] else: self.modifiers = [] def to_inch(self): - self.modifiers = [[x / 25.4 for x in modifier] for modifier in self.modifiers] + self.modifiers = [tuple([x / 25.4 for x in modifier]) for modifier in self.modifiers] def to_metric(self): - self.modifiers = [[x * 25.4 for x in modifier] for modifier in self.modifiers] + self.modifiers = [tuple([x * 25.4 for x in modifier]) for modifier in self.modifiers] def to_gerber(self, settings=None): if len(self.modifiers): - return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(["%.4f" % x for x in modifier]) for modifier in self.modifiers])) + return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(["%.4g" % x for x in modifier]) for modifier in self.modifiers])) else: return '%ADD{0}{1}*%'.format(self.d, self.shape) @@ -326,16 +326,30 @@ class AMParamStmt(ParamStmt): def _parsePrimitives(self, macro): primitives = [] - for primitive in macro.split('*'): + for primitive in macro.strip('%\n').split('*'): # Couldn't find anything explicit about leading whitespace in the spec... - primitive = primitive.lstrip() - if primitive[0] == '0': - primitives.append(AMCommentPrimitive.from_gerber(primitive)) - if primitive[0] == '4': - primitives.append(AMOutlinePrimitive.from_gerber(primitive)) - else: - primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) - + primitive = primitive.strip(' *%\n') + if len(primitive): + if primitive[0] == '0': + primitives.append(AMCommentPrimitive.from_gerber(primitive)) + elif primitive[0] == '1': + primitives.append(AMCirclePrimitive.from_gerber(primitive)) + elif primitive[0:2] in ('2,', '20'): + primitives.append(AMVectorLinePrimitive.from_gerber(primitive)) + elif primitive[0:2] == '21': + primitives.append(AMCenterLinePrimitive.from_gerber(primitive)) + elif primitive[0:2] == '22': + primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive)) + elif primitive[0] == '4': + primitives.append(AMOutlinePrimitive.from_gerber(primitive)) + elif primitive[0] == '5': + primitives.append(AMPolygonPrimitive.from_gerber(primitive)) + elif primitive[0] =='6': + primitives.append(AMMoirePrimitive.from_gerber(primitive)) + elif primitive[0] == '7': + primitives.append(AMThermalPrimitive.from_gerber(primitive)) + else: + primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) return primitives def to_inch(self): @@ -465,7 +479,8 @@ class IRParamStmt(ParamStmt): """ @classmethod def from_dict(cls, stmt_dict): - return cls(**stmt_dict) + angle = int(stmt_dict['angle']) + return cls(stmt_dict['param'], angle) def __init__(self, param, angle): """ Initialize IRParamStmt class @@ -639,9 +654,9 @@ class SFParamStmt(ParamStmt): def __str__(self): scale_factor = '' if self.a is not None: - scale_factor += ('X: %f' % self.a) + scale_factor += ('X: %g' % self.a) if self.b is not None: - scale_factor += ('Y: %f' % self.b) + scale_factor += ('Y: %g' % self.b) return ('' % scale_factor) -- cgit From 5cf1fa74b42eb8feaab23078bef6f31f6d647c33 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 15 Feb 2015 02:20:02 -0500 Subject: Tests and bugfixes --- gerber/gerber_statements.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 1401345..a6feef6 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -802,13 +802,13 @@ class CoordStmt(Statement): if self.function: coord_str += 'Fn: %s ' % self.function if self.x is not None: - coord_str += 'X: %f ' % self.x + coord_str += 'X: %g ' % self.x if self.y is not None: - coord_str += 'Y: %f ' % self.y + coord_str += 'Y: %g ' % self.y if self.i is not None: - coord_str += 'I: %f ' % self.i + coord_str += 'I: %g ' % self.i if self.j is not None: - coord_str += 'J: %f ' % self.j + coord_str += 'J: %g ' % self.j if self.op: if self.op == 'D01': op = 'Lights On' @@ -829,7 +829,7 @@ class ApertureStmt(Statement): def __init__(self, d, deprecated=None): Statement.__init__(self, "APERTURE") self.d = int(d) - self.deprecated = True if deprecated is not None else False + self.deprecated = True if deprecated is not None and deprecated is not False else False def to_gerber(self, settings=None): if self.deprecated: -- cgit From 288ac27084b47166ac662402ea340d0aa25d8f56 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 04:31:23 -0500 Subject: Get unit conversion working for Gerber/Excellon files Started operations module for file operations/transforms --- gerber/gerber_statements.py | 62 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 11 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index a6feef6..b231cdb 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -20,7 +20,8 @@ Gerber (RS-274X) Statements **Gerber RS-274X file statement classes** """ -from .utils import parse_gerber_value, write_gerber_value, decimal_string +from .utils import (parse_gerber_value, write_gerber_value, decimal_string, + inch, metric) from .am_statements import * @@ -51,6 +52,15 @@ class Statement(object): s = s.rstrip() + ">" return s + def to_inch(self): + pass + + def to_metric(self): + pass + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + class ParamStmt(Statement): """ Gerber parameter statement Base class @@ -180,6 +190,12 @@ class MOParamStmt(ParamStmt): mode = 'MM' if self.mode == 'metric' else 'IN' return '%MO{0}*%'.format(mode) + def to_inch(self): + self.mode = 'inch' + + def to_metric(self): + self.mode = 'metric' + def __str__(self): mode_str = 'millimeters' if self.mode == 'metric' else 'inches' return ('' % mode_str) @@ -267,10 +283,10 @@ class ADParamStmt(ParamStmt): self.modifiers = [] def to_inch(self): - self.modifiers = [tuple([x / 25.4 for x in modifier]) for modifier in self.modifiers] + self.modifiers = [tuple([inch(x) for x in modifier]) for modifier in self.modifiers] def to_metric(self): - self.modifiers = [tuple([x * 25.4 for x in modifier]) for modifier in self.modifiers] + self.modifiers = [tuple([metric(x) for x in modifier]) for modifier in self.modifiers] def to_gerber(self, settings=None): if len(self.modifiers): @@ -599,6 +615,18 @@ class OFParamStmt(ParamStmt): ret += 'B' + decimal_string(self.b, precision=5) return ret + '*%' + def to_inch(self): + if self.a is not None: + self.a = inch(self.a) + if self.b is not None: + self.b = inch(self.b) + + def to_metric(self): + if self.a is not None: + self.a = metric(self.a) + if self.b is not None: + self.b = metric(self.b) + def __str__(self): offset_str = '' if self.a is not None: @@ -651,6 +679,18 @@ class SFParamStmt(ParamStmt): ret += 'B' + decimal_string(self.b, precision=5) return ret + '*%' + def to_inch(self): + if self.a is not None: + self.a = inch(self.a) + if self.b is not None: + self.b = inch(self.b) + + def to_metric(self): + if self.a is not None: + self.a = metric(self.a) + if self.b is not None: + self.b = metric(self.b) + def __str__(self): scale_factor = '' if self.a is not None: @@ -775,25 +815,25 @@ class CoordStmt(Statement): def to_inch(self): if self.x is not None: - self.x = self.x / 25.4 + self.x = inch(self.x) if self.y is not None: - self.y = self.y / 25.4 + self.y = inch(self.y) if self.i is not None: - self.i = self.i / 25.4 + self.i = inch(self.i) if self.j is not None: - self.j = self.j / 25.4 + self.j = inch(self.j) if self.function == "G71": self.function = "G70" def to_metric(self): if self.x is not None: - self.x = self.x * 25.4 + self.x = metric(self.x) if self.y is not None: - self.y = self.y * 25.4 + self.y = metric(self.y) if self.i is not None: - self.i = self.i * 25.4 + self.i = metric(self.i) if self.j is not None: - self.j = self.j * 25.4 + self.j = metric(self.j) if self.function == "G70": self.function = "G71" -- cgit From e71d7a24b5be3e68d36494869595eec934db4bd2 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 21:14:30 -0500 Subject: Python 3 tests passing --- gerber/gerber_statements.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index b231cdb..f8385c0 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -93,8 +93,7 @@ class FSParamStmt(ParamStmt): param = stmt_dict.get('param') zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' - x = map(int, stmt_dict.get('x')) - fmt = (x[0], x[1]) + fmt = tuple(map(int, stmt_dict.get('x'))) return cls(param, zeros, notation, fmt) def __init__(self, param, zero_suppression='leading', -- cgit From 5966d7830bda7f37ed5ddcc1bfccb93e7f780eaa Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 23:13:23 -0500 Subject: Add offset operation --- gerber/gerber_statements.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index f8385c0..89f4f84 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -58,6 +58,9 @@ class Statement(object): def to_metric(self): pass + def offset(self, x_offset=0, y_offset=0): + pass + def __eq__(self, other): return self.__dict__ == other.__dict__ @@ -626,6 +629,12 @@ class OFParamStmt(ParamStmt): if self.b is not None: self.b = metric(self.b) + def offset(self, x_offset=0, y_offset=0): + if self.a is not None: + self.a += x_offset + if self.b is not None: + self.b += y_offset + def __str__(self): offset_str = '' if self.a is not None: @@ -690,6 +699,12 @@ class SFParamStmt(ParamStmt): if self.b is not None: self.b = metric(self.b) + def offset(self, x_offset=0, y_offset=0): + if self.a is not None: + self.a += x_offset + if self.b is not None: + self.b += y_offset + def __str__(self): scale_factor = '' if self.a is not None: @@ -836,6 +851,16 @@ class CoordStmt(Statement): if self.function == "G70": self.function = "G71" + def offset(self, x_offset=0, y_offset=0): + if self.x is not None: + self.x += x_offset + if self.y is not None: + self.y += y_offset + if self.i is not None: + self.i += x_offset + if self.j is not None: + self.j += y_offset + def __str__(self): coord_str = '' if self.function: -- cgit From 670d3fbbd7ebfb69bd223ac30b73ec47b195b380 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 3 Mar 2015 03:41:55 -0300 Subject: Add aperture macro parsing and evaluation. Aperture macros can get complex with arithmetical operations, variables and variables substitution. Current pcb-tools code just read each macro block as an independent unit, this cannot deal with variables that get changed after used. This patch splits the task in two: first we parse all macro content and creates a bytecode representation of all operations. This bytecode representation will be executed when an AD command is issues passing the required parameters. Parsing is heavily based on gerbv using a Shunting Yard approach to math parsing. Integration with rs274x.py code is not finished as I need to figure out how to integrate the final macro primitives with the graphical primitives already in use. --- gerber/gerber_statements.py | 58 +++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 26 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 89f4f84..19c7138 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -22,7 +22,10 @@ Gerber (RS-274X) Statements """ from .utils import (parse_gerber_value, write_gerber_value, decimal_string, inch, metric) + from .am_statements import * +from .am_read import read_macro +from .am_eval import eval_macro class Statement(object): @@ -340,34 +343,37 @@ class AMParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.name = name self.macro = macro - self.primitives = self._parsePrimitives(macro) - def _parsePrimitives(self, macro): + self.instructions = self.read(macro) + self.primitives = [] + + def read(self, macro): + return read_macro(macro) + + def evaluate(self, modifiers=[]): primitives = [] - for primitive in macro.strip('%\n').split('*'): - # Couldn't find anything explicit about leading whitespace in the spec... - primitive = primitive.strip(' *%\n') - if len(primitive): - if primitive[0] == '0': - primitives.append(AMCommentPrimitive.from_gerber(primitive)) - elif primitive[0] == '1': - primitives.append(AMCirclePrimitive.from_gerber(primitive)) - elif primitive[0:2] in ('2,', '20'): - primitives.append(AMVectorLinePrimitive.from_gerber(primitive)) - elif primitive[0:2] == '21': - primitives.append(AMCenterLinePrimitive.from_gerber(primitive)) - elif primitive[0:2] == '22': - primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive)) - elif primitive[0] == '4': - primitives.append(AMOutlinePrimitive.from_gerber(primitive)) - elif primitive[0] == '5': - primitives.append(AMPolygonPrimitive.from_gerber(primitive)) - elif primitive[0] =='6': - primitives.append(AMMoirePrimitive.from_gerber(primitive)) - elif primitive[0] == '7': - primitives.append(AMThermalPrimitive.from_gerber(primitive)) - else: - primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) + for primitive in eval_macro(self.instructions, modifiers[0]): + if primitive[0] == '0': + primitives.append(AMCommentPrimitive.from_gerber(primitive)) + elif primitive[0] == '1': + primitives.append(AMCirclePrimitive.from_gerber(primitive)) + elif primitive[0:2] in ('2,', '20'): + primitives.append(AMVectorLinePrimitive.from_gerber(primitive)) + elif primitive[0:2] == '21': + primitives.append(AMCenterLinePrimitive.from_gerber(primitive)) + elif primitive[0:2] == '22': + primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive)) + elif primitive[0] == '4': + primitives.append(AMOutlinePrimitive.from_gerber(primitive)) + elif primitive[0] == '5': + primitives.append(AMPolygonPrimitive.from_gerber(primitive)) + elif primitive[0] =='6': + primitives.append(AMMoirePrimitive.from_gerber(primitive)) + elif primitive[0] == '7': + primitives.append(AMThermalPrimitive.from_gerber(primitive)) + else: + primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) + return primitives def to_inch(self): -- cgit From a13b981c1c2ea9ede39e9821d9ba818566f044de Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 5 Mar 2015 14:43:30 -0300 Subject: Fix tests for macros with no variables. All AM*Primitive classes now handles float for all but the code modifiers. This simplifies the reading/parsing. --- gerber/gerber_statements.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 19c7138..99672de 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -350,31 +350,30 @@ class AMParamStmt(ParamStmt): def read(self, macro): return read_macro(macro) - def evaluate(self, modifiers=[]): - primitives = [] + def build(self, modifiers=[[]]): + self.primitives = [] + for primitive in eval_macro(self.instructions, modifiers[0]): if primitive[0] == '0': - primitives.append(AMCommentPrimitive.from_gerber(primitive)) + self.primitives.append(AMCommentPrimitive.from_gerber(primitive)) elif primitive[0] == '1': - primitives.append(AMCirclePrimitive.from_gerber(primitive)) + self.primitives.append(AMCirclePrimitive.from_gerber(primitive)) elif primitive[0:2] in ('2,', '20'): - primitives.append(AMVectorLinePrimitive.from_gerber(primitive)) + self.primitives.append(AMVectorLinePrimitive.from_gerber(primitive)) elif primitive[0:2] == '21': - primitives.append(AMCenterLinePrimitive.from_gerber(primitive)) + self.primitives.append(AMCenterLinePrimitive.from_gerber(primitive)) elif primitive[0:2] == '22': - primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive)) + self.primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive)) elif primitive[0] == '4': - primitives.append(AMOutlinePrimitive.from_gerber(primitive)) + self.primitives.append(AMOutlinePrimitive.from_gerber(primitive)) elif primitive[0] == '5': - primitives.append(AMPolygonPrimitive.from_gerber(primitive)) + self.primitives.append(AMPolygonPrimitive.from_gerber(primitive)) elif primitive[0] =='6': - primitives.append(AMMoirePrimitive.from_gerber(primitive)) + self.primitives.append(AMMoirePrimitive.from_gerber(primitive)) elif primitive[0] == '7': - primitives.append(AMThermalPrimitive.from_gerber(primitive)) + self.primitives.append(AMThermalPrimitive.from_gerber(primitive)) else: - primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) - - return primitives + self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) def to_inch(self): for primitive in self.primitives: -- cgit From b93804ed9a3400099afceacfe5a809ae8bded2a4 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 7 Apr 2015 18:22:02 -0300 Subject: Add unspecified FS D leading zeros format FS D leading zero format (probably form Direct) is an unspecified coordinate format where all numbers are specified with both leading and trailing zeros. --- gerber/gerber_statements.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 99672de..39cecf2 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -97,7 +97,14 @@ class FSParamStmt(ParamStmt): """ """ param = stmt_dict.get('param') - zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' + + if stmt_dict.get('zero') == 'L': + zeros = 'leading' + elif stmt_dict.get('zero') == 'T': + zeros = 'trailing' + else: + zeros = 'none' + notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' fmt = tuple(map(int, stmt_dict.get('x'))) return cls(param, zeros, notation, fmt) @@ -117,7 +124,7 @@ class FSParamStmt(ParamStmt): Parameter. zero_suppression : string - Zero-suppression mode. May be either 'leading' or 'trailing' + Zero-suppression mode. May be either 'leading', 'trailing' or 'none' (all zeros are present) notation : string Notation mode. May be either 'absolute' or 'incremental' -- cgit From 9ab4ec360c9028122648a881516ce5ed8ae63f77 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 7 Apr 2015 18:24:47 -0300 Subject: Fix parsing for AM macros with zero modifiers --- gerber/gerber_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 39cecf2..4e5ed77 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -289,7 +289,7 @@ class ADParamStmt(ParamStmt): ParamStmt.__init__(self, param) self.d = d self.shape = shape - if modifiers is not None: + if modifiers: self.modifiers = [tuple([float(x) for x in m.split("X")]) for m in modifiers.split(",") if len(m)] else: self.modifiers = [] @@ -301,7 +301,7 @@ class ADParamStmt(ParamStmt): self.modifiers = [tuple([metric(x) for x in modifier]) for modifier in self.modifiers] def to_gerber(self, settings=None): - if len(self.modifiers): + if any(self.modifiers): return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(["%.4g" % x for x in modifier]) for modifier in self.modifiers])) else: return '%ADD{0}{1}*%'.format(self.d, self.shape) -- cgit From bbfa66eb381f327b62994b60c321b61a72d25bfe Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 7 Apr 2015 18:25:44 -0300 Subject: Small change on __str__ for SF Statement --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 4e5ed77..b56be0a 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -720,7 +720,7 @@ class SFParamStmt(ParamStmt): def __str__(self): scale_factor = '' if self.a is not None: - scale_factor += ('X: %g' % self.a) + scale_factor += ('X: %g ' % self.a) if self.b is not None: scale_factor += ('Y: %g' % self.b) return ('' % scale_factor) -- cgit From d1c74317c8df83da5d9f01b74c28be2b467fa300 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 7 Apr 2015 18:26:33 -0300 Subject: Add some deprecated but still found statements --- gerber/gerber_statements.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index b56be0a..27066cd 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -760,6 +760,37 @@ class LNParamStmt(ParamStmt): return '' % self.name +class DeprecatedStmt(Statement): + """ Unimportant deprecated statement, will be parsed but not emitted. + """ + @classmethod + def from_gerber(cls, line): + return cls(line) + + def __init__(self, line): + """ Initialize DeprecatedStmt class + + Parameters + ---------- + line : string + Deprecated statement text + + Returns + ------- + DeprecatedStmt + Initialized DeprecatedStmt class. + + """ + Statement.__init__(self, "DEPRECATED") + self.line = line + + def to_gerber(self, settings=None): + return '' + + def __str__(self): + return '' % self.line + + class CoordStmt(Statement): """ Coordinate Data Block """ -- cgit From 50c01d4635b3f86ce98b127308275a46adbeee80 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 7 Apr 2015 18:27:31 -0300 Subject: Fix CommentStmt for multi-line comments --- gerber/gerber_statements.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 27066cd..1159dc0 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -954,7 +954,7 @@ class CommentStmt(Statement): def __init__(self, comment): Statement.__init__(self, "COMMENT") - self.comment = comment + self.comment = comment if comment is not None else "" def to_gerber(self, settings=None): return 'G04{0}*'.format(self.comment) @@ -1026,3 +1026,7 @@ class UnknownStmt(Statement): def to_gerber(self, settings=None): return self.line + + def __str__(self): + return '' % self.line + -- cgit From d7ce97b180d7652b6e55470b3fe755a689b03ba8 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 7 Apr 2015 18:55:03 -0300 Subject: (really) Fix parsing for AM macros with zero modifiers --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 1159dc0..c40eb81 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -292,7 +292,7 @@ class ADParamStmt(ParamStmt): if modifiers: self.modifiers = [tuple([float(x) for x in m.split("X")]) for m in modifiers.split(",") if len(m)] else: - self.modifiers = [] + self.modifiers = [tuple()] def to_inch(self): self.modifiers = [tuple([inch(x) for x in modifier]) for modifier in self.modifiers] -- cgit From 51c630ab77e390cc4a5637fd4b99fb9a6bebcce5 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 14 Apr 2015 23:27:11 -0300 Subject: AMStatement are used as is when gerbers are generated --- gerber/gerber_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index c40eb81..f2fc970 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -391,7 +391,7 @@ class AMParamStmt(ParamStmt): primitive.to_metric() def to_gerber(self, settings=None): - return '%AM{0}*{1}%'.format(self.name, '\n'.join([primitive.to_gerber(settings) for primitive in self.primitives])) + return '%AM{0}*{1}*%'.format(self.name, self.macro) def __str__(self): return '' % (self.name, self.macro) @@ -785,7 +785,7 @@ class DeprecatedStmt(Statement): self.line = line def to_gerber(self, settings=None): - return '' + return self.line def __str__(self): return '' % self.line -- cgit From 8ec3077be988681bbbafcef18ea3a2f84dd61b2b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 16 May 2015 09:45:34 -0400 Subject: Add checks to ensure statement unit conversions are idempotent --- gerber/gerber_statements.py | 113 ++++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 45 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index f2fc970..a198bb9 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -43,8 +43,9 @@ class Statement(object): type : string String identifying the statement type. """ - def __init__(self, stype): + def __init__(self, stype, units='inch'): self.type = stype + self.units = units def __str__(self): s = "<{0} ".format(self.__class__.__name__) @@ -56,10 +57,10 @@ class Statement(object): return s def to_inch(self): - pass + self.units = 'inch' def to_metric(self): - pass + self.units = 'metric' def offset(self, x_offset=0, y_offset=0): pass @@ -156,6 +157,8 @@ class FSParamStmt(ParamStmt): return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, fmt, fmt) + + def __str__(self): return ('' % (self.format[0], self.format[1], self.zero_suppression, self.notation)) @@ -295,10 +298,14 @@ class ADParamStmt(ParamStmt): self.modifiers = [tuple()] def to_inch(self): - self.modifiers = [tuple([inch(x) for x in modifier]) for modifier in self.modifiers] + if self.units == 'metric': + self.units = 'inch' + self.modifiers = [tuple([inch(x) for x in modifier]) for modifier in self.modifiers] def to_metric(self): - self.modifiers = [tuple([metric(x) for x in modifier]) for modifier in self.modifiers] + if self.units == 'inch': + self.units = 'metric' + self.modifiers = [tuple([metric(x) for x in modifier]) for modifier in self.modifiers] def to_gerber(self, settings=None): if any(self.modifiers): @@ -383,12 +390,16 @@ class AMParamStmt(ParamStmt): self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) def to_inch(self): - for primitive in self.primitives: - primitive.to_inch() + if self.units == 'metric': + self.units = 'inch' + for primitive in self.primitives: + primitive.to_inch() def to_metric(self): - for primitive in self.primitives: - primitive.to_metric() + if self.units == 'inch': + self.units = 'metric' + for primitive in self.primitives: + primitive.to_metric() def to_gerber(self, settings=None): return '%AM{0}*{1}*%'.format(self.name, self.macro) @@ -630,16 +641,20 @@ class OFParamStmt(ParamStmt): return ret + '*%' def to_inch(self): - if self.a is not None: - self.a = inch(self.a) - if self.b is not None: - self.b = inch(self.b) + if self.units == 'metric': + self.units = 'inch' + if self.a is not None: + self.a = inch(self.a) + if self.b is not None: + self.b = inch(self.b) def to_metric(self): - if self.a is not None: - self.a = metric(self.a) - if self.b is not None: - self.b = metric(self.b) + if self.units == 'inch': + self.units = 'metric' + if self.a is not None: + self.a = metric(self.a) + if self.b is not None: + self.b = metric(self.b) def offset(self, x_offset=0, y_offset=0): if self.a is not None: @@ -700,16 +715,20 @@ class SFParamStmt(ParamStmt): return ret + '*%' def to_inch(self): - if self.a is not None: - self.a = inch(self.a) - if self.b is not None: - self.b = inch(self.b) + if self.units == 'metric': + self.units = 'inch' + if self.a is not None: + self.a = inch(self.a) + if self.b is not None: + self.b = inch(self.b) def to_metric(self): - if self.a is not None: - self.a = metric(self.a) - if self.b is not None: - self.b = metric(self.b) + if self.units == 'inch': + self.units = 'metric' + if self.a is not None: + self.a = metric(self.a) + if self.b is not None: + self.b = metric(self.b) def offset(self, x_offset=0, y_offset=0): if self.a is not None: @@ -871,28 +890,32 @@ class CoordStmt(Statement): return ret + '*' def to_inch(self): - if self.x is not None: - self.x = inch(self.x) - if self.y is not None: - self.y = inch(self.y) - if self.i is not None: - self.i = inch(self.i) - if self.j is not None: - self.j = inch(self.j) - if self.function == "G71": - self.function = "G70" + if self.units == 'metric': + self.units = 'inch' + if self.x is not None: + self.x = inch(self.x) + if self.y is not None: + self.y = inch(self.y) + if self.i is not None: + self.i = inch(self.i) + if self.j is not None: + self.j = inch(self.j) + if self.function == "G71": + self.function = "G70" def to_metric(self): - if self.x is not None: - self.x = metric(self.x) - if self.y is not None: - self.y = metric(self.y) - if self.i is not None: - self.i = metric(self.i) - if self.j is not None: - self.j = metric(self.j) - if self.function == "G70": - self.function = "G71" + if self.units == 'inch': + self.units = 'metric' + if self.x is not None: + self.x = metric(self.x) + if self.y is not None: + self.y = metric(self.y) + if self.i is not None: + self.i = metric(self.i) + if self.j is not None: + self.j = metric(self.j) + if self.function == "G70": + self.function = "G71" def offset(self, x_offset=0, y_offset=0): if self.x is not None: -- cgit From d3b19efb484941f9726b1ae805c8d39e767bbe15 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Wed, 20 May 2015 16:20:02 -0300 Subject: Add support for PCBmodE generated files. PCBmodE uses a standard but probably undefined behaviour issue on Gerber where it defines circle apertures with a single modifier but leaves a trilling 'X' after it. 'X' is modifiers separator but when there is only one modifier the behaviour is undefined. For parsing we are just ignoring blank modifiers. Test updated to catch this case. --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index a198bb9..fd1e629 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -293,7 +293,7 @@ class ADParamStmt(ParamStmt): self.d = d self.shape = shape if modifiers: - self.modifiers = [tuple([float(x) for x in m.split("X")]) for m in modifiers.split(",") if len(m)] + 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()] -- cgit From 2e2b4e49c3182cc7385f12d760222ecb57cc1356 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Mon, 23 Nov 2015 16:02:16 -0200 Subject: Fix AMParamStmt to_gerber to write changes back. AMParamStmt was not calling to_gerber on each of its primitives on his own to_gerber method. That way primitives that changes after reading, such as when you call to_inch/to_metric was failing because it was writing only the original macro back. --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index fd1e629..9931acf 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -402,7 +402,7 @@ class AMParamStmt(ParamStmt): primitive.to_metric() def to_gerber(self, settings=None): - return '%AM{0}*{1}*%'.format(self.name, self.macro) + return '%AM{0}*{1}%'.format(self.name, "".join([primitive.to_gerber() for primitive in self.primitives])) def __str__(self): return '' % (self.name, self.macro) -- cgit From 4a815bf25ddd1d378ec6ad5af008e5bbcd362b51 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 14:05:00 +0800 Subject: First time any macro renders --- gerber/gerber_statements.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index fd1e629..14a431b 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -26,6 +26,7 @@ from .utils import (parse_gerber_value, write_gerber_value, decimal_string, from .am_statements import * from .am_read import read_macro from .am_eval import eval_macro +from .primitives import AMGroup class Statement(object): @@ -388,6 +389,8 @@ class AMParamStmt(ParamStmt): self.primitives.append(AMThermalPrimitive.from_gerber(primitive)) else: self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) + + return AMGroup(self.primitives, units=self.units) def to_inch(self): if self.units == 'metric': -- cgit From 5476da8aa3f4ee424f56f4f2491e7af1c4b7b758 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Fix a bunch of rendering bugs. - 'clear' polarity primitives no longer erase background - Added aperture macro support for polygons - Added aperture macro rendring support - Renderer now creates a new surface for each layer and merges them instead of working directly on a single surface - Updated examples accordingly --- gerber/gerber_statements.py | 54 +++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 19 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 9931acf..74b3e54 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -43,6 +43,7 @@ class Statement(object): type : string String identifying the statement type. """ + def __init__(self, stype, units='inch'): self.type = stype self.units = units @@ -84,6 +85,7 @@ class ParamStmt(Statement): param : string Parameter type code """ + def __init__(self, param): Statement.__init__(self, "PARAM") self.param = param @@ -157,8 +159,6 @@ class FSParamStmt(ParamStmt): return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, fmt, fmt) - - def __str__(self): return ('' % (self.format[0], self.format[1], self.zero_suppression, self.notation)) @@ -293,19 +293,22 @@ class ADParamStmt(ParamStmt): self.d = d self.shape = shape if modifiers: - self.modifiers = [tuple([float(x) for x in m.split("X") if len(x)]) for m in modifiers.split(",") if len(m)] + 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()] def to_inch(self): if self.units == 'metric': - self.units = 'inch' - self.modifiers = [tuple([inch(x) for x in modifier]) for modifier in self.modifiers] + self.units = 'inch' + self.modifiers = [tuple([inch(x) for x in modifier]) + for modifier in self.modifiers] def to_metric(self): if self.units == 'inch': - self.units = 'metric' - self.modifiers = [tuple([metric(x) for x in modifier]) for modifier in self.modifiers] + self.units = 'metric' + self.modifiers = [tuple([metric(x) for x in modifier]) + for modifier in self.modifiers] def to_gerber(self, settings=None): if any(self.modifiers): @@ -382,12 +385,15 @@ class AMParamStmt(ParamStmt): self.primitives.append(AMOutlinePrimitive.from_gerber(primitive)) elif primitive[0] == '5': self.primitives.append(AMPolygonPrimitive.from_gerber(primitive)) - elif primitive[0] =='6': + elif primitive[0] == '6': self.primitives.append(AMMoirePrimitive.from_gerber(primitive)) elif primitive[0] == '7': - self.primitives.append(AMThermalPrimitive.from_gerber(primitive)) + self.primitives.append( + AMThermalPrimitive.from_gerber(primitive)) else: - self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) + self.primitives.append( + AMUnsupportPrimitive.from_gerber(primitive)) + return self def to_inch(self): if self.units == 'metric': @@ -824,13 +830,17 @@ class CoordStmt(Statement): op = stmt_dict.get('op') if x is not None: - x = parse_gerber_value(stmt_dict.get('x'), settings.format, settings.zero_suppression) + x = parse_gerber_value(stmt_dict.get('x'), settings.format, + settings.zero_suppression) if y is not None: - y = parse_gerber_value(stmt_dict.get('y'), settings.format, settings.zero_suppression) + y = parse_gerber_value(stmt_dict.get('y'), settings.format, + settings.zero_suppression) if i is not None: - i = parse_gerber_value(stmt_dict.get('i'), settings.format, settings.zero_suppression) + i = parse_gerber_value(stmt_dict.get('i'), settings.format, + settings.zero_suppression) if j is not None: - j = parse_gerber_value(stmt_dict.get('j'), settings.format, settings.zero_suppression) + j = parse_gerber_value(stmt_dict.get('j'), settings.format, + settings.zero_suppression) return cls(function, x, y, i, j, op, settings) def __init__(self, function, x, y, i, j, op, settings): @@ -878,13 +888,17 @@ class CoordStmt(Statement): if self.function: ret += self.function if self.x is not None: - ret += 'X{0}'.format(write_gerber_value(self.x, settings.format, settings.zero_suppression)) + ret += 'X{0}'.format(write_gerber_value(self.x, settings.format, + settings.zero_suppression)) if self.y is not None: - ret += 'Y{0}'.format(write_gerber_value(self.y, settings.format, settings.zero_suppression)) + ret += 'Y{0}'.format(write_gerber_value(self.y, settings.format, + settings.zero_suppression)) if self.i is not None: - ret += 'I{0}'.format(write_gerber_value(self.i, settings.format, settings.zero_suppression)) + ret += 'I{0}'.format(write_gerber_value(self.i, settings.format, + settings.zero_suppression)) if self.j is not None: - ret += 'J{0}'.format(write_gerber_value(self.j, settings.format, settings.zero_suppression)) + ret += 'J{0}'.format(write_gerber_value(self.j, settings.format, + settings.zero_suppression)) if self.op: ret += self.op return ret + '*' @@ -956,6 +970,7 @@ class CoordStmt(Statement): class ApertureStmt(Statement): """ Aperture Statement """ + def __init__(self, d, deprecated=None): Statement.__init__(self, "APERTURE") self.d = int(d) @@ -989,6 +1004,7 @@ class CommentStmt(Statement): class EofStmt(Statement): """ EOF Statement """ + def __init__(self): Statement.__init__(self, "EOF") @@ -1043,6 +1059,7 @@ class RegionModeStmt(Statement): class UnknownStmt(Statement): """ Unknown Statement """ + def __init__(self, line): Statement.__init__(self, "UNKNOWN") self.line = line @@ -1052,4 +1069,3 @@ class UnknownStmt(Statement): def __str__(self): return '' % self.line - -- cgit From 29c0d82bf53907030d11df9eb09471b716a0be2e Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 27 Feb 2016 15:24:36 +0800 Subject: RS274X backend for rendering. Incompelte still --- gerber/gerber_statements.py | 65 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 14a431b..bb190f4 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -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') -- cgit From 223a010831f0d9dae4bd6d2e626a603a78eb0b1d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 27 Feb 2016 18:18:04 +0800 Subject: Fix critical issue with rotatin points (when the angle is zero the y would be flipped). Render AM with outline to gerber --- gerber/gerber_statements.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index bb190f4..dcdd90d 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -168,6 +168,10 @@ class FSParamStmt(ParamStmt): class MOParamStmt(ParamStmt): """ MO - Gerber Mode (measurement units) Statement. """ + + @classmethod + def from_units(cls, units): + return cls(None, 'inch') @classmethod def from_dict(cls, stmt_dict): -- cgit 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/gerber_statements.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'gerber/gerber_statements.py') 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, '') -- cgit From 74c638c7181e7a8ca4d0f791545bbf5db8b86c2a Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Thu, 19 May 2016 23:19:28 +0800 Subject: Fix issue where did not always switch into the G01 mode after G03 when the point was unchanged --- gerber/gerber_statements.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index aa25d0a..119df9d 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -887,6 +887,10 @@ class CoordStmt(Statement): def line(cls, func, point): return cls(func, point[0], point[1], None, None, CoordStmt.OP_DRAW, None) + @classmethod + def mode(cls, func): + return cls(func, None, None, None, None, None, None) + @classmethod def arc(cls, func, point, center): return cls(func, point[0], point[1], center[0], center[1], CoordStmt.OP_DRAW, None) -- cgit From c9c1313d598d5afa8cb387a2cfcd4a4281086e01 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 28 May 2016 12:36:31 +0800 Subject: Fix units statement. Keep track of original macro statement in the AMGroup --- gerber/gerber_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 119df9d..b171a7f 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -176,7 +176,7 @@ class MOParamStmt(ParamStmt): @classmethod def from_units(cls, units): - return cls(None, 'inch') + return cls(None, units) @classmethod def from_dict(cls, stmt_dict): @@ -425,7 +425,7 @@ class AMParamStmt(ParamStmt): else: self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) - return AMGroup(self.primitives, units=self.units) + return AMGroup(self.primitives, stmt=self, units=self.units) def to_inch(self): if self.units == 'metric': -- cgit From 49dadd46ee62a863b75087e9ed8f0590183bd525 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Mon, 23 Nov 2015 16:02:16 -0200 Subject: Fix AMParamStmt to_gerber to write changes back. AMParamStmt was not calling to_gerber on each of its primitives on his own to_gerber method. That way primitives that changes after reading, such as when you call to_inch/to_metric was failing because it was writing only the original macro back. --- gerber/gerber_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index b171a7f..725febf 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -440,7 +440,7 @@ class AMParamStmt(ParamStmt): primitive.to_metric() def to_gerber(self, settings=None): - return '%AM{0}*{1}*%'.format(self.name, self.macro) + return '%AM{0}*{1}%'.format(self.name, "".join([primitive.to_gerber() for primitive in self.primitives])) def __str__(self): return '' % (self.name, self.macro) -- cgit From ccb6eb7a766bd6edf314978f3ec4fc0dcd61652d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 16:46:44 +0800 Subject: Add support for polygon apertures --- gerber/gerber_statements.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 725febf..234952e 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -285,9 +285,14 @@ class ADParamStmt(ParamStmt): @classmethod def obround(cls, dcode, width, height): - '''Create an obrou d aperture definition statement''' + '''Create an obround aperture definition statement''' return cls('AD', dcode, 'O', ([width, height],)) + @classmethod + def polygon(cls, dcode, diameter, num_vertices, rotation, hole_diameter): + '''Create a polygon aperture definition statement''' + return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_diameter],)) + @classmethod def macro(cls, dcode, name): return cls('AD', dcode, name, '') -- cgit From b140f5e4767912110f69cbda8417a8e076345b70 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 28 Jun 2016 23:15:20 +0800 Subject: Don't flash G03-only commands --- gerber/gerber_statements.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 234952e..881e5bc 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -1022,6 +1022,16 @@ class CoordStmt(Statement): coord_str += 'Op: %s' % op return '' % coord_str + + @property + def only_function(self): + """ + Returns if the statement only set the function. + """ + + # TODO I would like to refactor this so that the function is handled separately and then + # TODO this isn't required + return self.function != None and self.op == None and self.x == None and self.y == None and self.i == None and self.j == None class ApertureStmt(Statement): -- cgit From 9b0d3b1122ffc3b7c2211b0cdc2cb6de6be9b242 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 10 Jul 2016 15:07:17 +0800 Subject: Fix issue with chaning region mode via flash. Add options for controlling output from rendered gerber --- gerber/gerber_statements.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 881e5bc..52e7ac3 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -886,7 +886,10 @@ class CoordStmt(Statement): @classmethod def move(cls, func, point): - return cls(func, point[0], point[1], None, None, CoordStmt.OP_MOVE, None) + if point: + return cls(func, point[0], point[1], None, None, CoordStmt.OP_MOVE, None) + # No point specified, so just write the function. This is normally for ending a region (D02*) + return cls(func, None, None, None, None, CoordStmt.OP_MOVE, None) @classmethod def line(cls, func, point): @@ -902,7 +905,10 @@ class CoordStmt(Statement): @classmethod def flash(cls, point): - return cls(None, point[0], point[1], None, None, CoordStmt.OP_FLASH, None) + if point: + return cls(None, point[0], point[1], None, None, CoordStmt.OP_FLASH, None) + else: + return cls(None, None, None, None, None, CoordStmt.OP_FLASH, None) def __init__(self, function, x, y, i, j, op, settings): """ Initialize CoordStmt class -- cgit From 7cd6acf12670f3773113f67ed2acb35cb21c32a0 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 24 Jul 2016 17:08:47 +0800 Subject: Add many render tests based on the Umaco gerger specification. Fix multiple rendering bugs, especially related to holes in flashed apertures --- gerber/gerber_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 52e7ac3..3212c1c 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -279,9 +279,9 @@ class ADParamStmt(ParamStmt): return cls('AD', dcode, 'R', ([width, height],)) @classmethod - def circle(cls, dcode, diameter): + def circle(cls, dcode, diameter, hole_diameter): '''Create a circular aperture definition statement''' - return cls('AD', dcode, 'C', ([diameter],)) + return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) @classmethod def obround(cls, dcode, width, height): -- cgit From 965d3ce23b92f8aff1063debd6d3364de15791fe Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 24 Jul 2016 22:08:31 +0800 Subject: Add more tests for rendering to PNG. Start adding tests for rendering to Gerber format. Changed definition of no hole to use None instead of 0 so we can differentiate when writing to Gerber format. Makde polygon use hole diameter instead of hole radius to match other primitives --- gerber/gerber_statements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 3212c1c..fba2a3c 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -281,7 +281,10 @@ class ADParamStmt(ParamStmt): @classmethod def circle(cls, dcode, diameter, hole_diameter): '''Create a circular aperture definition statement''' - return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) + + if hole_diameter != None: + return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) + return cls('AD', dcode, 'C', ([diameter],)) @classmethod def obround(cls, dcode, width, height): -- cgit From 8cd842a41a55ab3d8f558a2e3e198beba7da58a1 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Manually mere rendering changes --- gerber/gerber_statements.py | 47 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index fba2a3c..08dbd82 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -44,6 +44,7 @@ class Statement(object): type : string String identifying the statement type. """ + def __init__(self, stype, units='inch'): self.type = stype self.units = units @@ -85,6 +86,7 @@ class ParamStmt(Statement): param : string Parameter type code """ + def __init__(self, param): Statement.__init__(self, "PARAM") self.param = param @@ -163,8 +165,6 @@ class FSParamStmt(ParamStmt): return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, fmt, fmt) - - def __str__(self): return ('' % (self.format[0], self.format[1], self.zero_suppression, self.notation)) @@ -343,13 +343,15 @@ class ADParamStmt(ParamStmt): def to_inch(self): if self.units == 'metric': - self.units = 'inch' - self.modifiers = [tuple([inch(x) for x in modifier]) for modifier in self.modifiers] + self.units = 'inch' + self.modifiers = [tuple([inch(x) for x in modifier]) + for modifier in self.modifiers] def to_metric(self): if self.units == 'inch': - self.units = 'metric' - self.modifiers = [tuple([metric(x) for x in modifier]) for modifier in self.modifiers] + self.units = 'metric' + self.modifiers = [tuple([metric(x) for x in modifier]) + for modifier in self.modifiers] def to_gerber(self, settings=None): if any(self.modifiers): @@ -426,10 +428,11 @@ class AMParamStmt(ParamStmt): self.primitives.append(AMOutlinePrimitive.from_gerber(primitive)) elif primitive[0] == '5': self.primitives.append(AMPolygonPrimitive.from_gerber(primitive)) - elif primitive[0] =='6': + elif primitive[0] == '6': self.primitives.append(AMMoirePrimitive.from_gerber(primitive)) elif primitive[0] == '7': - self.primitives.append(AMThermalPrimitive.from_gerber(primitive)) + self.primitives.append( + AMThermalPrimitive.from_gerber(primitive)) else: self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) @@ -878,13 +881,17 @@ class CoordStmt(Statement): op = stmt_dict.get('op') if x is not None: - x = parse_gerber_value(stmt_dict.get('x'), settings.format, settings.zero_suppression) + x = parse_gerber_value(stmt_dict.get('x'), settings.format, + settings.zero_suppression) if y is not None: - y = parse_gerber_value(stmt_dict.get('y'), settings.format, settings.zero_suppression) + y = parse_gerber_value(stmt_dict.get('y'), settings.format, + settings.zero_suppression) if i is not None: - i = parse_gerber_value(stmt_dict.get('i'), settings.format, settings.zero_suppression) + i = parse_gerber_value(stmt_dict.get('i'), settings.format, + settings.zero_suppression) if j is not None: - j = parse_gerber_value(stmt_dict.get('j'), settings.format, settings.zero_suppression) + j = parse_gerber_value(stmt_dict.get('j'), settings.format, + settings.zero_suppression) return cls(function, x, y, i, j, op, settings) @classmethod @@ -958,13 +965,17 @@ class CoordStmt(Statement): if self.function: ret += self.function if self.x is not None: - ret += 'X{0}'.format(write_gerber_value(self.x, settings.format, settings.zero_suppression)) + ret += 'X{0}'.format(write_gerber_value(self.x, settings.format, + settings.zero_suppression)) if self.y is not None: - ret += 'Y{0}'.format(write_gerber_value(self.y, settings.format, settings.zero_suppression)) + ret += 'Y{0}'.format(write_gerber_value(self.y, settings.format, + settings.zero_suppression)) if self.i is not None: - ret += 'I{0}'.format(write_gerber_value(self.i, settings.format, settings.zero_suppression)) + ret += 'I{0}'.format(write_gerber_value(self.i, settings.format, + settings.zero_suppression)) if self.j is not None: - ret += 'J{0}'.format(write_gerber_value(self.j, settings.format, settings.zero_suppression)) + ret += 'J{0}'.format(write_gerber_value(self.j, settings.format, + settings.zero_suppression)) if self.op: ret += self.op return ret + '*' @@ -1046,6 +1057,7 @@ class CoordStmt(Statement): class ApertureStmt(Statement): """ Aperture Statement """ + def __init__(self, d, deprecated=None): Statement.__init__(self, "APERTURE") self.d = int(d) @@ -1079,6 +1091,7 @@ class CommentStmt(Statement): class EofStmt(Statement): """ EOF Statement """ + def __init__(self): Statement.__init__(self, "EOF") @@ -1149,6 +1162,7 @@ class RegionModeStmt(Statement): class UnknownStmt(Statement): """ Unknown Statement """ + def __init__(self, line): Statement.__init__(self, "UNKNOWN") self.line = line @@ -1158,4 +1172,3 @@ class UnknownStmt(Statement): def __str__(self): return '' % self.line - -- cgit From 5af19af190c1fb0f0c5be029d46d63e657dde4d9 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Commit partial merge so I can work on the plane --- gerber/gerber_statements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 08dbd82..33fb4ec 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -337,7 +337,8 @@ class ADParamStmt(ParamStmt): 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)] + 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()] -- cgit From 724c2b3bced319ed0b50c4302fed9b0e1aa9ce9c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 5 Nov 2016 20:56:47 -0400 Subject: Finish Merge, most tests passing --- gerber/gerber_statements.py | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 33fb4ec..9fc6fca 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -95,10 +95,10 @@ 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 @@ -173,7 +173,7 @@ class FSParamStmt(ParamStmt): class MOParamStmt(ParamStmt): """ MO - Gerber Mode (measurement units) Statement. """ - + @classmethod def from_units(cls, units): return cls(None, units) @@ -235,7 +235,7 @@ 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? @@ -272,34 +272,34 @@ 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, hole_diameter): '''Create a circular aperture definition statement''' - + if hole_diameter != None: return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) return cls('AD', dcode, 'C', ([diameter],)) - + @classmethod def obround(cls, dcode, width, height): '''Create an obround aperture definition statement''' return cls('AD', dcode, 'O', ([width, height],)) - + @classmethod def polygon(cls, dcode, diameter, num_vertices, rotation, hole_diameter): '''Create a polygon aperture definition statement''' return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_diameter],)) - + @classmethod def macro(cls, dcode, name): return cls('AD', dcode, name, '') - + @classmethod def from_dict(cls, stmt_dict): param = stmt_dict.get('param') @@ -436,7 +436,7 @@ class AMParamStmt(ParamStmt): AMThermalPrimitive.from_gerber(primitive)) else: self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive)) - + return AMGroup(self.primitives, stmt=self, units=self.units) def to_inch(self): @@ -452,7 +452,7 @@ class AMParamStmt(ParamStmt): primitive.to_metric() def to_gerber(self, settings=None): - return '%AM{0}*{1}%'.format(self.name, "".join([primitive.to_gerber() for primitive in self.primitives])) + return '%AM{0}*{1}*%'.format(self.name, self.macro) def __str__(self): return '' % (self.name, self.macro) @@ -864,10 +864,10 @@ class CoordStmt(Statement): """ Coordinate Data Block """ - OP_DRAW = 'D01' + OP_DRAW = 'D01' OP_MOVE = 'D02' OP_FLASH = 'D03' - + FUNC_LINEAR = 'G01' FUNC_ARC_CW = 'G02' FUNC_ARC_CCW = 'G03' @@ -894,26 +894,26 @@ class CoordStmt(Statement): 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): if point: return cls(func, point[0], point[1], None, None, CoordStmt.OP_MOVE, None) # No point specified, so just write the function. This is normally for ending a region (D02*) return cls(func, None, None, 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 mode(cls, func): return cls(func, None, None, None, None, None, 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): if point: @@ -1043,13 +1043,13 @@ class CoordStmt(Statement): coord_str += 'Op: %s' % op return '' % coord_str - + @property def only_function(self): """ Returns if the statement only set the function. """ - + # TODO I would like to refactor this so that the function is handled separately and then # TODO this isn't required return self.function != None and self.op == None and self.x == None and self.y == None and self.i == None and self.j == None @@ -1104,11 +1104,11 @@ class EofStmt(Statement): class QuadrantModeStmt(Statement): - + @classmethod def single(cls): return cls('single-quadrant') - + @classmethod def multi(cls): return cls('multi-quadrant') @@ -1140,11 +1140,11 @@ 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') -- cgit From c70ece73eaef13b755ce117f7b580ecd2d45e604 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 18 Nov 2016 07:56:51 -0500 Subject: Add support for square holes in basic primitives --- gerber/gerber_statements.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 7322b3c..43596be 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -279,22 +279,36 @@ class ADParamStmt(ParamStmt): return cls('AD', dcode, 'R', ([width, height],)) @classmethod - def circle(cls, dcode, diameter, hole_diameter): + def circle(cls, dcode, diameter, hole_diameter=None, hole_width=None, hole_height=None): '''Create a circular aperture definition statement''' - if hole_diameter != None: + if hole_diameter is not None and hole_diameter > 0: return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) + elif (hole_width is not None and hole_width > 0 + and hole_height is not None and hole_height > 0): + return cls('AD', dcode, 'C', ([diameter, hole_width, hole_height],)) return cls('AD', dcode, 'C', ([diameter],)) @classmethod - def obround(cls, dcode, width, height): + def obround(cls, dcode, width, height, hole_diameter=None, hole_width=None, hole_height=None): '''Create an obround aperture definition statement''' + if hole_diameter is not None and hole_diameter > 0: + return cls('AD', dcode, 'O', ([width, height, hole_diameter],)) + elif (hole_width is not None and hole_width > 0 + and hole_height is not None and hole_height > 0): + return cls('AD', dcode, 'O', ([width, height, hole_width, hole_height],)) return cls('AD', dcode, 'O', ([width, height],)) @classmethod - def polygon(cls, dcode, diameter, num_vertices, rotation, hole_diameter): + def polygon(cls, dcode, diameter, num_vertices, rotation, hole_diameter=None, hole_width=None, hole_height=None): '''Create a polygon aperture definition statement''' - return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_diameter],)) + if hole_diameter is not None and hole_diameter > 0: + return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_diameter],)) + elif (hole_width is not None and hole_width > 0 + and hole_height is not None and hole_height > 0): + return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_width, hole_height],)) + return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation],)) + @classmethod def macro(cls, dcode, name): -- cgit From b87629c2ae648aef019284aeca7366698ca0903f Mon Sep 17 00:00:00 2001 From: jaseg Date: Sat, 25 Nov 2017 16:14:23 +0100 Subject: Add hole support to ADParamStmt.rect --- gerber/gerber_statements.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 43596be..339b02a 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -274,14 +274,18 @@ class ADParamStmt(ParamStmt): """ @classmethod - def rect(cls, dcode, width, height): + def rect(cls, dcode, width, height, hole_diameter=None, hole_width=None, hole_height=None): '''Create a rectangular aperture definition statement''' + if hole_diameter is not None and hole_diameter > 0: + return cls('AD', dcode, 'R', ([width, height, hole_diameter],)) + elif (hole_width is not None and hole_width > 0 + and hole_height is not None and hole_height > 0): + return cls('AD', dcode, 'R', ([width, height, hole_width, hole_height],)) return cls('AD', dcode, 'R', ([width, height],)) @classmethod def circle(cls, dcode, diameter, hole_diameter=None, hole_width=None, hole_height=None): '''Create a circular aperture definition statement''' - if hole_diameter is not None and hole_diameter > 0: return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) elif (hole_width is not None and hole_width > 0 -- cgit From a7a5981e0eb2b112a57c6ea1151eb2b88f798857 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 3 Feb 2019 13:42:44 +0900 Subject: Make primitives with unset level polarity inherit from region This fixes region rendering with programatically generated primitives such that clear level polarity works in an intuitive way. This is useful for e.g. cutouts in regions. Before, the renderer would set level polarity twice, both when starting the region and then again once for each region primitive (line or arc). The problem was that the primitives in a region with "clear" polarity would when constructed with unset polarity default to "dark". Thus the renderer would emit something like LPC (clear polarity) -> G36 (start region) -> LPD (dark polarity) -> {lines...} instead of LPC -> G36 -> {lines...}. After this commit, Line and Arc will retain None as level polarity when created with unset level polarity, and region rendering will override None with the region's polarity. Outside regions, the old dark default remains unchanged. Note on verification: Somehow, gEDA gerbv would still render the broken regions the way one would have intended, but other viewers (KiCAD gerbview, the online EasyEDA one and whatever JLC uses to make their silkscreens) would not. --- gerber/gerber_statements.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'gerber/gerber_statements.py') diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 339b02a..28f5e81 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -236,11 +236,6 @@ class LPParamStmt(ParamStmt): 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 -- cgit