gerbonara/gerber/gerber_statements.py
jaseg a7a5981e0e 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.
2019-02-03 14:37:26 +09:00

1189 lines
33 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
#
# 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
===========================
**Gerber RS-274X file statement classes**
"""
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
from .primitives import AMGroup
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, units='inch'):
self.type = stype
self.units = units
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
def to_inch(self):
self.units = 'inch'
def to_metric(self):
self.units = 'metric'
def offset(self, x_offset=0, y_offset=0):
pass
def __eq__(self, other):
return self.__dict__ == other.__dict__
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
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):
"""
"""
param = stmt_dict.get('param')
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)
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', 'trailing' or 'none' (all zeros are present)
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, 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 ('<Format Spec: %d:%d %s zero suppression %s notation>' %
(self.format[0], self.format[1], self.zero_suppression, self.notation))
class MOParamStmt(ParamStmt):
""" MO - Gerber Mode (measurement units) Statement.
"""
@classmethod
def from_units(cls, units):
return cls(None, units)
@classmethod
def from_dict(cls, stmt_dict):
param = stmt_dict.get('param')
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'
else:
mo = '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, settings=None):
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: %s>' % mode_str)
class LPParamStmt(ParamStmt):
""" LP - Gerber Level Polarity statement
"""
@classmethod
def from_dict(cls, stmt_dict):
param = stmt_dict['param']
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=None):
lp = 'C' if self.lp == 'clear' else 'D'
return '%LP{0}*%'.format(lp)
def __str__(self):
return '<Level Polarity: %s>' % self.lp
class ADParamStmt(ParamStmt):
""" AD - Gerber Aperture Definition Statement
"""
@classmethod
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
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, 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=None, hole_width=None, hole_height=None):
'''Create a polygon aperture definition statement'''
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):
return cls('AD', dcode, name, '')
@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')
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 : ADParamStmt
Initialized ADParamStmt class.
"""
ParamStmt.__init__(self, param)
self.d = d
self.shape = shape
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()]
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]
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]
def to_gerber(self, settings=None):
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)
def __str__(self):
if self.shape == 'C':
shape = 'circle'
elif self.shape == 'R':
shape = 'rectangle'
elif self.shape == 'O':
shape = 'obround'
else:
shape = self.shape
return '<Aperture Definition: %d: %s>' % (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
self.instructions = self.read(macro)
self.primitives = []
def read(self, macro):
return read_macro(macro)
def build(self, modifiers=[[]]):
self.primitives = []
for primitive in eval_macro(self.instructions, modifiers[0]):
if primitive[0] == '0':
self.primitives.append(AMCommentPrimitive.from_gerber(primitive))
elif primitive[0] == '1':
self.primitives.append(AMCirclePrimitive.from_gerber(primitive))
elif primitive[0:2] in ('2,', '20'):
self.primitives.append(AMVectorLinePrimitive.from_gerber(primitive))
elif primitive[0:2] == '21':
self.primitives.append(AMCenterLinePrimitive.from_gerber(primitive))
elif primitive[0:2] == '22':
self.primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive))
elif primitive[0] == '4':
self.primitives.append(AMOutlinePrimitive.from_gerber(primitive))
elif primitive[0] == '5':
self.primitives.append(AMPolygonPrimitive.from_gerber(primitive))
elif primitive[0] == '6':
self.primitives.append(AMMoirePrimitive.from_gerber(primitive))
elif primitive[0] == '7':
self.primitives.append(
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):
if self.units == 'metric':
self.units = 'inch'
for primitive in self.primitives:
primitive.to_inch()
def to_metric(self):
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, "".join([primitive.to_gerber() for primitive in self.primitives]))
def __str__(self):
return '<Aperture Macro %s: %s>' % (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, mode):
""" 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 ('<Axis Select: %s>' % self.mode)
class INParamStmt(ParamStmt):
""" IN - Image Name Statement (Deprecated)
"""
@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, settings=None):
return '%IN{0}*%'.format(self.name)
def __str__(self):
return '<Image Name: %s>' % 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 ('<Image Polarity: %s>' % self.ip)
class IRParamStmt(ParamStmt):
""" IR - Image Rotation Param (Deprecated)
"""
@classmethod
def from_dict(cls, stmt_dict):
angle = int(stmt_dict['angle'])
return cls(stmt_dict['param'], angle)
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 '<Image Angle: %s>' % 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)
ret += "*%"
return ret
def __str__(self):
return '<Image Mirror: A=%d B=%d>' % (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 to_inch(self):
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.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:
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:
offset_str += ('X: %f ' % self.a)
if self.b is not None:
offset_str += ('Y: %f ' % self.b)
return ('<Offset: %s>' % 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 to_inch(self):
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.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:
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:
scale_factor += ('X: %g ' % self.a)
if self.b is not None:
scale_factor += ('Y: %g' % self.b)
return ('<Scale Factor: %s>' % scale_factor)
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, settings=None):
return '%LN{0}*%'.format(self.name)
def __str__(self):
return '<Level Name: %s>' % 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 self.line
def __str__(self):
return '<Deprecated Statement: \'%s\'>' % self.line
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']
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'), settings.format,
settings.zero_suppression)
if y is not None:
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)
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):
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:
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
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.function = function
self.x = x
self.y = y
self.i = i
self.j = j
self.op = op
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, 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))
if self.i is not None:
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))
if self.op:
ret += self.op
return ret + '*'
def to_inch(self):
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.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:
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:
coord_str += 'Fn: %s ' % self.function
if self.x is not None:
coord_str += 'X: %g ' % self.x
if self.y is not None:
coord_str += 'Y: %g ' % self.y
if self.i is not None:
coord_str += 'I: %g ' % self.i
if self.j is not None:
coord_str += 'J: %g ' % 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 '<Coordinate Statement: %s>' % 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):
""" Aperture Statement
"""
def __init__(self, d, deprecated=None):
Statement.__init__(self, "APERTURE")
self.d = int(d)
self.deprecated = True if deprecated is not None and deprecated is not False else False
def to_gerber(self, settings=None):
if self.deprecated:
return 'G54D{0}*'.format(self.d)
else:
return 'D{0}*'.format(self.d)
def __str__(self):
return '<Aperture: %d>' % self.d
class CommentStmt(Statement):
""" Comment Statment
"""
def __init__(self, comment):
Statement.__init__(self, "COMMENT")
self.comment = comment if comment is not None else ""
def to_gerber(self, settings=None):
return 'G04{0}*'.format(self.comment)
def __str__(self):
return '<Comment: %s>' % self.comment
class EofStmt(Statement):
""" EOF Statement
"""
def __init__(self):
Statement.__init__(self, "EOF")
def to_gerber(self, settings=None):
return 'M02*'
def __str__(self):
return '<EOF 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):
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__('QuadrantMode')
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, settings=None):
return 'G74*' if self.mode == 'single-quadrant' else 'G75*'
class RegionModeStmt(Statement):
@classmethod
def from_gerber(cls, line):
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')
mode = mode.lower()
if mode not in ['on', 'off']:
raise ValueError('Valid modes are "on" or "off"')
self.mode = mode
def to_gerber(self, settings=None):
return 'G36*' if self.mode == 'on' else 'G37*'
class UnknownStmt(Statement):
""" Unknown Statement
"""
def __init__(self, line):
Statement.__init__(self, "UNKNOWN")
self.line = line
def to_gerber(self, settings=None):
return self.line
def __str__(self):
return '<Unknown Statement: \'%s\'>' % self.line