Manually mere rendering changes

refactor
Hamilton Kibbe 2016-01-21 03:57:44 -05:00 zatwierdzone przez Garret Fick
rodzic 965d3ce23b
commit 8cd842a41a
33 zmienionych plików z 1873 dodań i 1047 usunięć

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 102 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 102 KiB

Wyświetl plik

@ -24,6 +24,7 @@ import os
from gerber import PCB
from gerber.render import GerberCairoContext, theme
GERBER_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gerbers'))
@ -33,7 +34,7 @@ ctx = GerberCairoContext()
# Create a new PCB
pcb = PCB.from_directory(GERBER_FOLDER)
pcb.theme = theme.THEMES['OSH Park']
ctx.render_layers(pcb.top_layers, os.path.join(os.path.dirname(__file__), 'pcb_top.png'))
ctx.render_layers(pcb.bottom_layers, os.path.join(os.path.dirname(__file__), 'pcb_bottom.png'))
# Render PCB
ctx.render_layers(pcb.top_layers, os.path.join(os.path.dirname(__file__), 'pcb_top.png',), theme.THEMES['OSH Park'])
ctx.render_layers(pcb.bottom_layers, os.path.join(os.path.dirname(__file__), 'pcb_bottom.png'), theme.THEMES['OSH Park'])

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 97 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 96 KiB

Wyświetl plik

@ -18,15 +18,16 @@
""" This module provides RS-274-X AM macro evaluation.
"""
class OpCode:
PUSH = 1
LOAD = 2
PUSH = 1
LOAD = 2
STORE = 3
ADD = 4
SUB = 5
MUL = 6
DIV = 7
PRIM = 8
ADD = 4
SUB = 5
MUL = 6
DIV = 7
PRIM = 8
@staticmethod
def str(opcode):
@ -49,16 +50,18 @@ class OpCode:
else:
return "UNKNOWN"
def eval_macro(instructions, parameters={}):
if not isinstance(parameters, type({})):
p = {}
for i, val in enumerate(parameters):
p[i+1] = val
p[i + 1] = val
parameters = p
stack = []
def pop():
return stack.pop()

Wyświetl plik

@ -26,7 +26,8 @@ import string
class Token:
ADD = "+"
SUB = "-"
MULT = ("x", "X") # compatibility as many gerber writes do use non compliant X
# compatibility as many gerber writes do use non compliant X
MULT = ("x", "X")
DIV = "/"
OPERATORS = (ADD, SUB, MULT[0], MULT[1], DIV)
LEFT_PARENS = "("
@ -62,6 +63,7 @@ def is_op(token):
class Scanner:
def __init__(self, s):
self.buff = s
self.n = 0
@ -111,7 +113,8 @@ class Scanner:
def print_instructions(instructions):
for opcode, argument in instructions:
print("%s %s" % (OpCode.str(opcode), str(argument) if argument is not None else ""))
print("%s %s" % (OpCode.str(opcode),
str(argument) if argument is not None else ""))
def read_macro(macro):

Wyświetl plik

@ -16,14 +16,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import math
from .utils import validate_coordinates, inch, metric, rotate_point
from .primitives import Circle, Line, Outline, Polygon, Rectangle
from math import asin
import math
from .primitives import Circle, Line, Outline, Polygon, Rectangle
from .utils import validate_coordinates, inch, metric, rotate_point
# TODO: Add support for aperture macro variables
__all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive',
'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive',
'AMMoirePrimitive', 'AMThermalPrimitive', 'AMCenterLinePrimitive',
@ -54,12 +54,14 @@ class AMPrimitive(object):
------
TypeError, ValueError
"""
def __init__(self, code, exposure=None):
VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22, 9999)
if not isinstance(code, int):
raise TypeError('Aperture Macro Primitive code must be an integer')
elif code not in VALID_CODES:
raise ValueError('Invalid Code. Valid codes are %s.' % ', '.join(map(str, VALID_CODES)))
raise ValueError('Invalid Code. Valid codes are %s.' %
', '.join(map(str, VALID_CODES)))
if exposure is not None and exposure.lower() not in ('on', 'off'):
raise ValueError('Exposure must be either on or off')
self.code = code
@ -71,21 +73,21 @@ class AMPrimitive(object):
def to_metric(self):
raise NotImplementedError('Subclass must implement `to-metric`')
def to_primitive(self, units):
"""
Convert to a primitive, as defines the primitives module (for drawing)
"""
raise NotImplementedError('Subclass must implement `to-primitive`')
@property
def _level_polarity(self):
if self.exposure == 'off':
return 'clear'
return 'dark'
def to_primitive(self, units):
""" Return a Primitive instance based on the specified macro params.
"""
print('Rendering {}s is not supported yet.'.format(str(self.__class__)))
def __eq__(self, other):
return self.__dict__ == other.__dict__
class AMCommentPrimitive(AMPrimitive):
""" Aperture Macro Comment primitive. Code 0
@ -207,11 +209,11 @@ class AMCirclePrimitive(AMPrimitive):
self.position = tuple([metric(x) for x in self.position])
def to_gerber(self, settings=None):
data = dict(code = self.code,
exposure = '1' if self.exposure == 'on' else 0,
diameter = self.diameter,
x = self.position[0],
y = self.position[1])
data = dict(code=self.code,
exposure='1' if self.exposure == 'on' else 0,
diameter=self.diameter,
x=self.position[0],
y=self.position[1])
return '{code},{exposure},{diameter},{x},{y}*'.format(**data)
def to_primitive(self, units):
@ -294,21 +296,26 @@ class AMVectorLinePrimitive(AMPrimitive):
self.start = tuple([metric(x) for x in self.start])
self.end = tuple([metric(x) for x in self.end])
def to_gerber(self, settings=None):
fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rotation}*'
data = dict(code = self.code,
exp = 1 if self.exposure == 'on' else 0,
width = self.width,
startx = self.start[0],
starty = self.start[1],
endx = self.end[0],
endy = self.end[1],
rotation = self.rotation)
data = dict(code=self.code,
exp=1 if self.exposure == 'on' else 0,
width=self.width,
startx=self.start[0],
starty=self.start[1],
endx=self.end[0],
endy=self.end[1],
rotation=self.rotation)
return fmtstr.format(**data)
def to_primitive(self, units):
"""
Convert this to a primitive. We use the Outline to represent this (instead of Line)
because the behaviour of the end caps is different for aperture macros compared to Lines
when rotated.
"""
# Use a line to generate our vertices easily
line = Line(self.start, self.end, Rectangle(None, self.width, self.width))
vertices = line.vertices
@ -385,7 +392,8 @@ class AMOutlinePrimitive(AMPrimitive):
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])))
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)
@ -425,6 +433,10 @@ class AMOutlinePrimitive(AMPrimitive):
return "{code},{exposure},{n_points},{start_point},{points},\n{rotation}*".format(**data)
def to_primitive(self, units):
"""
Convert this to a drawable primitive. This uses the Outline instead of Line
primitive to handle differences in end caps when rotated.
"""
lines = []
prev_point = rotate_point(self.start_point, self.rotation)
@ -500,7 +512,6 @@ class AMPolygonPrimitive(AMPrimitive):
rotation = float(modifiers[6])
return cls(code, exposure, vertices, position, diameter, rotation)
def __init__(self, code, exposure, vertices, position, diameter, rotation):
""" Initialize AMPolygonPrimitive
"""
@ -529,7 +540,7 @@ class AMPolygonPrimitive(AMPrimitive):
exposure="1" if self.exposure == "on" else "0",
vertices=self.vertices,
position="%.4g,%.4g" % self.position,
diameter = '%.4g' % self.diameter,
diameter='%.4g' % self.diameter,
rotation=str(self.rotation)
)
fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*"
@ -633,17 +644,16 @@ class AMMoirePrimitive(AMPrimitive):
self.crosshair_thickness = metric(self.crosshair_thickness)
self.crosshair_length = metric(self.crosshair_length)
def to_gerber(self, settings=None):
data = dict(
code=self.code,
position="%.4g,%.4g" % self.position,
diameter = self.diameter,
ring_thickness = self.ring_thickness,
gap = self.gap,
max_rings = self.max_rings,
crosshair_thickness = self.crosshair_thickness,
crosshair_length = self.crosshair_length,
diameter=self.diameter,
ring_thickness=self.ring_thickness,
gap=self.gap,
max_rings=self.max_rings,
crosshair_thickness=self.crosshair_thickness,
crosshair_length=self.crosshair_length,
rotation=self.rotation
)
fmt = "{code},{position},{diameter},{ring_thickness},{gap},{max_rings},{crosshair_thickness},{crosshair_length},{rotation}*"
@ -698,7 +708,7 @@ class AMThermalPrimitive(AMPrimitive):
code = int(modifiers[0])
position = (float(modifiers[1]), float(modifiers[2]))
outer_diameter = float(modifiers[3])
inner_diameter= float(modifiers[4])
inner_diameter = float(modifiers[4])
gap = float(modifiers[5])
rotation = float(modifiers[6])
return cls(code, position, outer_diameter, inner_diameter, gap, rotation)
@ -720,7 +730,6 @@ class AMThermalPrimitive(AMPrimitive):
self.inner_diameter = inch(self.inner_diameter)
self.gap = inch(self.gap)
def to_metric(self):
self.position = tuple([metric(x) for x in self.position])
self.outer_diameter = metric(self.outer_diameter)
@ -873,14 +882,14 @@ class AMCenterLinePrimitive(AMPrimitive):
exposure = 'on' if float(modifiers[1]) == 1 else 'off'
width = float(modifiers[2])
height = float(modifiers[3])
center= (float(modifiers[4]), float(modifiers[5]))
center = (float(modifiers[4]), float(modifiers[5]))
rotation = float(modifiers[6])
return cls(code, exposure, width, height, center, rotation)
def __init__(self, code, exposure, width, height, center, rotation):
if code != 21:
raise ValueError('CenterLinePrimitive code is 21')
super (AMCenterLinePrimitive, self).__init__(code, exposure)
super(AMCenterLinePrimitive, self).__init__(code, exposure)
self.width = width
self.height = height
validate_coordinates(center)
@ -900,9 +909,9 @@ class AMCenterLinePrimitive(AMPrimitive):
def to_gerber(self, settings=None):
data = dict(
code=self.code,
exposure = '1' if self.exposure == 'on' else '0',
width = self.width,
height = self.height,
exposure='1' if self.exposure == 'on' else '0',
width=self.width,
height=self.height,
center="%.4g,%.4g" % self.center,
rotation=self.rotation
)
@ -986,7 +995,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
def __init__(self, code, exposure, width, height, lower_left, rotation):
if code != 22:
raise ValueError('LowerLeftLinePrimitive code is 22')
super (AMLowerLeftLinePrimitive, self).__init__(code, exposure)
super(AMLowerLeftLinePrimitive, self).__init__(code, exposure)
self.width = width
self.height = height
validate_coordinates(lower_left)
@ -1003,23 +1012,31 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
self.width = metric(self.width)
self.height = metric(self.height)
def to_primitive(self, units):
# TODO I think I have merged this wrong
# Offset the primitive from macro position
position = tuple([a + b for a , b in zip (position, self.lower_left)])
position = tuple([pos + offset for pos, offset in
zip(position, (self.width/2, self.height/2))])
# Return a renderable primitive
return Rectangle(self.position, self.width, self.height,
level_polarity=self._level_polarity, units=units)
def to_gerber(self, settings=None):
data = dict(
code=self.code,
exposure = '1' if self.exposure == 'on' else '0',
width = self.width,
height = self.height,
exposure='1' if self.exposure == 'on' else '0',
width=self.width,
height=self.height,
lower_left="%.4g,%.4g" % self.lower_left,
rotation=self.rotation
)
fmt = "{code},{exposure},{width},{height},{lower_left},{rotation}*"
return fmt.format(**data)
def to_primitive(self, units):
raise NotImplementedError()
class AMUnsupportPrimitive(AMPrimitive):
@classmethod
def from_gerber(cls, primitive):
return cls(primitive)

Wyświetl plik

@ -22,6 +22,7 @@ CAM File
This module provides common base classes for Excellon/Gerber CNC files
"""
class FileSettings(object):
""" CAM File Settings
@ -52,6 +53,7 @@ class FileSettings(object):
specify both. `zero_suppression` will take on the opposite value of `zeros`
and vice versa
"""
def __init__(self, notation='absolute', units='inch',
zero_suppression=None, format=(2, 5), zeros=None,
angle_units='degrees'):
@ -248,6 +250,12 @@ class CamFile(object):
"""
pass
def to_inch(self):
pass
def to_metric(self):
pass
def render(self, ctx, invert=False, filename=None):
""" Generate image of layer.
@ -262,15 +270,11 @@ class CamFile(object):
ctx.set_bounds(self.bounding_box)
ctx._paint_background()
if invert:
ctx.invert = True
ctx._clear_mask()
ctx.invert = invert
ctx._new_render_layer()
for p in self.primitives:
ctx.render(p)
if invert:
ctx.invert = False
ctx._render_mask()
ctx._flatten()
if filename is not None:
ctx.dump(filename)

Wyświetl plik

@ -22,7 +22,6 @@ from .exceptions import ParseError
from .utils import detect_file_format
def read(filename):
""" Read a gerber or excellon file and return a representative object.
@ -73,5 +72,3 @@ def loads(data):
return excellon.loads(data)
else:
raise TypeError('Unable to detect file format')

Wyświetl plik

@ -81,7 +81,7 @@ def loads(data, settings = None, tools = None):
return ExcellonParser(settings, tools).parse_raw(data)
class DrillHit(object):
class DrillHit(object):
"""Drill feature that is a single drill hole.
Attributes
@ -92,6 +92,7 @@ class DrillHit(object):
Center position of the drill.
"""
def __init__(self, tool, position):
self.tool = tool
self.position = position
@ -184,6 +185,7 @@ class ExcellonFile(CamFile):
either 'inch' or 'metric'.
"""
def __init__(self, statements, tools, hits, settings, filename=None):
super(ExcellonFile, self).__init__(statements=statements,
settings=settings,
@ -193,7 +195,9 @@ class ExcellonFile(CamFile):
@property
def primitives(self):
"""
Gets the primitives. Note that unlike Gerber, this generates new objects
"""
primitives = []
for hit in self.hits:
if isinstance(hit, DrillHit):
@ -203,8 +207,7 @@ class ExcellonFile(CamFile):
else:
raise ValueError('Unknown hit type')
return primitives
return primitives
@property
def bounds(self):
@ -237,7 +240,8 @@ class ExcellonFile(CamFile):
rprt += ' Code Size Hits Path Length\n'
rprt += ' --------------------------------------\n'
for tool in iter(self.tools.values()):
rprt += toolfmt.format(tool.number, tool.diameter, tool.hit_count, self.path_length(tool.number))
rprt += toolfmt.format(tool.number, tool.diameter,
tool.hit_count, self.path_length(tool.number))
if filename is not None:
with open(filename, 'w') as f:
f.write(rprt)
@ -245,13 +249,21 @@ class ExcellonFile(CamFile):
def write(self, filename=None):
filename = filename if filename is not None else self.filename
with open(filename, 'w') as f:
self.writes(f)
def writes(self, f):
# Copy the header verbatim
for statement in self.statements:
f.write(statement.to_excellon(self.settings) + '\n')
with open(filename, 'w') as f:
for statement in self.statements:
if not isinstance(statement, ToolSelectionStmt):
f.write(statement.to_excellon(self.settings) + '\n')
else:
break
# Write out coordinates for drill hits by tool
for tool in iter(self.tools.values()):
f.write(ToolSelectionStmt(tool.number).to_excellon(self.settings) + '\n')
for hit in self.hits:
if hit.tool.number == tool.number:
f.write(CoordinateStmt(
*hit.position).to_excellon(self.settings) + '\n')
f.write(EndOfProgramStmt().to_excellon() + '\n')
def to_inch(self):
"""
@ -265,9 +277,8 @@ class ExcellonFile(CamFile):
tool.to_inch()
for primitive in self.primitives:
primitive.to_inch()
for hit in self.hits:
hit.to_inch()
for hit in self.hits:
hit.to_inch()
def to_metric(self):
""" Convert units to metric
@ -288,8 +299,8 @@ class ExcellonFile(CamFile):
statement.offset(x_offset, y_offset)
for primitive in self.primitives:
primitive.offset(x_offset, y_offset)
for hit in self. hits:
hit.offset(x_offset, y_offset)
for hit in self. hits:
hit.offset(x_offset, y_offset)
def path_length(self, tool_number=None):
""" Return the path length for a given tool
@ -299,9 +310,11 @@ class ExcellonFile(CamFile):
for hit in self.hits:
tool = hit.tool
num = tool.number
positions[num] = (0, 0) if positions.get(num) is None else positions[num]
positions[num] = (0, 0) if positions.get(
num) is None else positions[num]
lengths[num] = 0.0 if lengths.get(num) is None else lengths[num]
lengths[num] = lengths[num] + math.hypot(*tuple(map(operator.sub, positions[num], hit.position)))
lengths[num] = lengths[
num] + math.hypot(*tuple(map(operator.sub, positions[num], hit.position)))
positions[num] = hit.position
if tool_number is None:
@ -310,13 +323,13 @@ class ExcellonFile(CamFile):
return lengths.get(tool_number)
def hit_count(self, tool_number=None):
counts = {}
for tool in iter(self.tools.values()):
counts[tool.number] = tool.hit_count
if tool_number is None:
return counts
else:
return counts.get(tool_number)
counts = {}
for tool in iter(self.tools.values()):
counts[tool.number] = tool.hit_count
if tool_number is None:
return counts
else:
return counts.get(tool_number)
def update_tool(self, tool_number, **kwargs):
""" Change parameters of a tool
@ -340,7 +353,6 @@ class ExcellonFile(CamFile):
hit.tool = newtool
class ExcellonParser(object):
""" Excellon File Parser
@ -348,8 +360,8 @@ class ExcellonParser(object):
----------
settings : FileSettings or dict-like
Excellon file settings to use when interpreting the excellon file.
"""
def __init__(self, settings=None, ext_tools=None):
"""
def __init__(self, settings=None, ext_tools=None):
self.notation = 'absolute'
self.units = 'inch'
self.zeros = 'leading'
@ -371,7 +383,6 @@ class ExcellonParser(object):
self.notation = settings.notation
self.format = settings.format
@property
def coordinates(self):
return [(stmt.x, stmt.y) for stmt in self.statements if isinstance(stmt, CoordinateStmt)]
@ -421,7 +432,8 @@ class ExcellonParser(object):
# get format from altium comment
if "FILE_FORMAT" in comment_stmt.comment:
detected_format = tuple([int(x) for x in comment_stmt.comment.split('=')[1].split(":")])
detected_format = tuple(
[int(x) for x in comment_stmt.comment.split('=')[1].split(":")])
if detected_format:
self.format = detected_format
@ -553,7 +565,7 @@ class ExcellonParser(object):
self.format = stmt.format
self.statements.append(stmt)
elif line[:3] == 'M71' or line [:3] == 'M72':
elif line[:3] == 'M71' or line[:3] == 'M72':
stmt = MeasuringModeStmt.from_excellon(line)
self.units = stmt.units
self.statements.append(stmt)
@ -603,20 +615,22 @@ class ExcellonParser(object):
self.statements.append(stmt)
# T0 is used as END marker, just ignore
if stmt.tool != 0:
if stmt.tool != 0:
tool = self._get_tool(stmt.tool)
if not tool:
# FIXME: for weird files with no tools defined, original calc from gerbv
# FIXME: for weird files with no tools defined, original calc from gerbv
if self._settings().units == "inch":
diameter = (16 + 8 * stmt.tool) / 1000.0;
diameter = (16 + 8 * stmt.tool) / 1000.0
else:
diameter = metric((16 + 8 * stmt.tool) / 1000.0);
diameter = metric((16 + 8 * stmt.tool) / 1000.0)
tool = ExcellonTool(self._settings(), number=stmt.tool, diameter=diameter)
tool = ExcellonTool(
self._settings(), number=stmt.tool, diameter=diameter)
self.tools[tool.number] = tool
# FIXME: need to add this tool definition inside header to make sure it is properly written
# FIXME: need to add this tool definition inside header to
# make sure it is properly written
for i, s in enumerate(self.statements):
if isinstance(s, ToolSelectionStmt) or isinstance(s, ExcellonTool):
self.statements.insert(i, tool)
@ -787,7 +801,7 @@ def detect_excellon_format(data=None, filename=None):
and 'FILE_FORMAT' in stmt.comment]
detected_format = (tuple([int(val) for val in
format_comment[0].split('=')[1].split(':')])
format_comment[0].split('=')[1].split(':')])
if len(format_comment) == 1 else None)
detected_zeros = zero_statements[0] if len(zero_statements) == 1 else None
@ -852,6 +866,6 @@ def _layer_size_score(size, hole_count, hole_area):
hole_percentage = hole_area / board_area
hole_score = (hole_percentage - 0.25) ** 2
size_score = (board_area - 8) **2
size_score = (board_area - 8) ** 2
return hole_score * size_score

Wyświetl plik

@ -56,6 +56,7 @@ class ExcellonStatement(object):
def to_excellon(self, settings=None):
raise NotImplementedError('to_excellon must be implemented in a '
'subclass')
def to_inch(self):
self.units = 'inch'
@ -68,6 +69,7 @@ class ExcellonStatement(object):
def __eq__(self, other):
return self.__dict__ == other.__dict__
class ExcellonTool(ExcellonStatement):
""" Excellon Tool class
@ -239,7 +241,6 @@ class ExcellonTool(ExcellonStatement):
if self.diameter is not None:
self.diameter = inch(self.diameter)
def to_metric(self):
if self.settings.units != 'metric':
self.settings.units = 'metric'
@ -648,6 +649,7 @@ class EndOfProgramStmt(ExcellonStatement):
if self.y is not None:
self.y += y_offset
class UnitStmt(ExcellonStatement):
@classmethod
@ -689,6 +691,7 @@ class UnitStmt(ExcellonStatement):
def to_metric(self):
self.units = 'metric'
class IncrementalModeStmt(ExcellonStatement):
@classmethod
@ -784,6 +787,7 @@ class MeasuringModeStmt(ExcellonStatement):
def to_metric(self):
self.units = 'metric'
class RouteModeStmt(ExcellonStatement):
def __init__(self, **kwargs):

Wyświetl plik

@ -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 ('<Format Spec: %d:%d %s zero suppression %s notation>' %
(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 '<Unknown Statement: \'%s\'>' % self.line

Wyświetl plik

@ -95,7 +95,8 @@ def sort_layers(layers):
'bottompaste', 'drill', ]
output = []
drill_layers = [layer for layer in layers if layer.layer_class == 'drill']
internal_layers = list(sorted([layer for layer in layers if layer.layer_class == 'internal']))
internal_layers = list(sorted([layer for layer in layers
if layer.layer_class == 'internal']))
for layer_class in layer_order:
if layer_class == 'internal':
@ -151,6 +152,8 @@ class PCBLayer(object):
else:
return None
def __repr__(self):
return '<PCBLayer: {}>'.format(self.layer_class)
class DrillLayer(PCBLayer):
@classmethod
@ -163,6 +166,7 @@ class DrillLayer(PCBLayer):
class InternalLayer(PCBLayer):
@classmethod
def from_gerber(cls, camfile):
filename = camfile.filename
@ -208,6 +212,7 @@ class InternalLayer(PCBLayer):
class LayerSet(object):
def __init__(self, name, layers, **kwargs):
super(LayerSet, self).__init__(**kwargs)
self.name = name

Wyświetl plik

@ -22,6 +22,7 @@ CAM File Operations
"""
import copy
def to_inch(cam_file):
""" Convert Gerber or Excellon file units to imperial
@ -39,6 +40,7 @@ def to_inch(cam_file):
cam_file.to_inch()
return cam_file
def to_metric(cam_file):
""" Convert Gerber or Excellon file units to metric
@ -56,6 +58,7 @@ def to_metric(cam_file):
cam_file.to_metric()
return cam_file
def offset(cam_file, x_offset, y_offset):
""" Offset a Cam file by a specified amount in the X and Y directions.
@ -79,6 +82,7 @@ def offset(cam_file, x_offset, y_offset):
cam_file.offset(x_offset, y_offset)
return cam_file
def scale(cam_file, x_scale, y_scale):
""" Scale a Cam file by a specified amount in the X and Y directions.
@ -101,6 +105,7 @@ def scale(cam_file, x_scale, y_scale):
# TODO
pass
def rotate(cam_file, angle):
""" Rotate a Cam file a specified amount about the origin.

Wyświetl plik

@ -63,13 +63,15 @@ class PCB(object):
@property
def top_layers(self):
board_layers = [l for l in reversed(self.layers) if l.layer_class in ('topsilk', 'topmask', 'top')]
board_layers = [l for l in reversed(self.layers) if l.layer_class in
('topsilk', 'topmask', 'top')]
drill_layers = [l for l in self.drill_layers if 'top' in l.layers]
return board_layers + drill_layers
@property
def bottom_layers(self):
board_layers = [l for l in self.layers if l.layer_class in ('bottomsilk', 'bottommask', 'bottom')]
board_layers = [l for l in self.layers if l.layer_class in
('bottomsilk', 'bottommask', 'bottom')]
drill_layers = [l for l in self.drill_layers if 'bottom' in l.layers]
return board_layers + drill_layers
@ -77,11 +79,17 @@ class PCB(object):
def drill_layers(self):
return [l for l in self.layers if l.layer_class == 'drill']
@property
def copper_layers(self):
return [layer for layer in self.layers if layer.layer_class in
('top', 'bottom', 'internal')]
@property
def layer_count(self):
""" Number of *COPPER* layers
"""
return len([l for l in self.layers if l.layer_class in ('top', 'bottom', 'internal')])
return len([l for l in self.layers if l.layer_class in
('top', 'bottom', 'internal')])
@property
def board_bounds(self):
@ -91,4 +99,3 @@ class PCB(object):
for layer in self.layers:
if layer.layer_class == 'top':
return layer.bounds

Plik diff jest za duży Load Diff

Wyświetl plik

@ -12,7 +12,7 @@
# 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
# See the License for the specific language governing permissions and
# limitations under the License.
try:
@ -21,7 +21,8 @@ except ImportError:
import cairocffi as cairo
import math
from operator import mul, div
from operator import mul, di
import tempfile
from ..primitives import *
@ -36,11 +37,14 @@ except(ImportError):
class GerberCairoContext(GerberContext):
def __init__(self, scale=300):
GerberContext.__init__(self)
super(GerberCairoContext, self).__init__()
self.scale = (scale, scale)
self.surface = None
self.ctx = None
self.active_layer = None
self.output_ctx = None
self.bg = False
self.mask = None
self.mask_ctx = None
@ -50,37 +54,40 @@ class GerberCairoContext(GerberContext):
@property
def origin_in_pixels(self):
return tuple(map(mul, self.origin_in_inch, self.scale)) if self.origin_in_inch is not None else (0.0, 0.0)
return (self.scale_point(self.origin_in_inch)
if self.origin_in_inch is not None else (0.0, 0.0))
@property
def size_in_pixels(self):
return tuple(map(mul, self.size_in_inch, self.scale)) if self.size_in_inch is not None else (0.0, 0.0)
return (self.scale_point(self.size_in_inch)
if self.size_in_inch is not None else (0.0, 0.0))
def set_bounds(self, bounds, new_surface=False):
origin_in_inch = (bounds[0][0], bounds[1][0])
size_in_inch = (abs(bounds[0][1] - bounds[0][0]), abs(bounds[1][1] - bounds[1][0]))
size_in_pixels = tuple(map(mul, size_in_inch, self.scale))
size_in_inch = (abs(bounds[0][1] - bounds[0][0]),
abs(bounds[1][1] - bounds[1][0]))
size_in_pixels = self.scale_point(size_in_inch)
self.origin_in_inch = origin_in_inch if self.origin_in_inch is None else self.origin_in_inch
self.size_in_inch = size_in_inch if self.size_in_inch is None else self.size_in_inch
if (self.surface is None) or new_surface:
self.surface_buffer = tempfile.NamedTemporaryFile()
self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1])
self.ctx = cairo.Context(self.surface)
self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
self.ctx.scale(1, -1)
self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1])
self.mask = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1])
self.mask_ctx = cairo.Context(self.mask)
self.mask_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
self.mask_ctx.scale(1, -1)
self.mask_ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1])
self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0, x0=-self.origin_in_pixels[0], y0=self.size_in_pixels[1] + self.origin_in_pixels[1])
self.surface = cairo.SVGSurface(
self.surface_buffer, size_in_pixels[0], size_in_pixels[1])
self.output_ctx = cairo.Context(self.surface)
self.output_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
self.output_ctx.scale(1, -1)
self.output_ctx.translate(-(origin_in_inch[0] * self.scale[0]),
(-origin_in_inch[1] * self.scale[0]) - size_in_pixels[1])
self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0,
x0=-self.origin_in_pixels[0],
y0=self.size_in_pixels[1] + self.origin_in_pixels[1])
def render_layers(self, layers, filename, theme=THEMES['default']):
""" Render a set of layers
"""
self.set_bounds(layers[0].bounds, True)
self._paint_background(True)
for layer in layers:
self._render_layer(layer, theme)
self.dump(filename)
@ -117,46 +124,46 @@ class GerberCairoContext(GerberContext):
self.color = settings.color
self.alpha = settings.alpha
self.invert = settings.invert
# Get a new clean layer to render on
self._new_render_layer()
if settings.mirror:
raise Warning('mirrored layers aren\'t supported yet...')
if self.invert:
self._clear_mask()
for prim in layer.primitives:
self.render(prim)
if self.invert:
self._render_mask()
# Add layer to image
self._flatten()
def _render_line(self, line, color):
start = map(mul, line.start, self.scale)
end = map(mul, line.end, self.scale)
start = [pos * scale for pos, scale in zip(line.start, self.scale)]
end = [pos * scale for pos, scale in zip(line.end, self.scale)]
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER
if line.level_polarity == "dark"
else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
if isinstance(line.aperture, Circle):
width = line.aperture.diameter
ctx.set_line_width(width * self.scale[0])
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
ctx.move_to(*start)
ctx.line_to(*end)
ctx.stroke()
width = line.aperture.diameter
self.ctx.set_line_width(width * self.scale[0])
self.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
self.ctx.move_to(*start)
self.ctx.line_to(*end)
self.ctx.stroke()
elif isinstance(line.aperture, Rectangle):
points = [tuple(map(mul, x, self.scale)) for x in line.vertices]
ctx.set_line_width(0)
ctx.move_to(*points[0])
points = [self.scale_point(x) for x in line.vertices]
self.ctx.set_line_width(0)
self.ctx.move_to(*points[0])
for point in points[1:]:
ctx.line_to(*point)
ctx.fill()
self.ctx.line_to(*point)
self.ctx.fill()
def _render_arc(self, arc, color):
center = map(mul, arc.center, self.scale)
start = map(mul, arc.start, self.scale)
end = map(mul, arc.end, self.scale)
center = self.scale_point(arc.center)
start = self.scale_point(arc.start)
end = self.scale_point(arc.end)
radius = self.scale[0] * arc.radius
angle1 = arc.start_angle
angle2 = arc.end_angle
@ -169,141 +176,137 @@ class GerberCairoContext(GerberContext):
width = max(arc.aperture.width, arc.aperture.height, 0.001)
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER
if arc.level_polarity == "dark"\
else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
ctx.set_line_width(width * self.scale[0])
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
ctx.move_to(*start) # You actually have to do this...
self.ctx.set_line_width(width * self.scale[0])
self.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
self.ctx.move_to(*start) # You actually have to do this...
if arc.direction == 'counterclockwise':
ctx.arc(center[0], center[1], radius, angle1, angle2)
self.ctx.arc(center[0], center[1], radius, angle1, angle2)
else:
ctx.arc_negative(center[0], center[1], radius, angle1, angle2)
ctx.move_to(*end) # ...lame
ctx.stroke()
self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2)
self.ctx.move_to(*end) # ...lame
def _render_region(self, region, color):
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER
if region.level_polarity == "dark"
else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
ctx.set_line_width(0)
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale)))
for p in region.primitives:
if isinstance(p, Line):
ctx.line_to(*tuple(map(mul, p.end, self.scale)))
self.ctx.set_line_width(0)
self.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
self.ctx.move_to(*self.scale_point(region.primitives[0].start))
for prim in region.primitives:
if isinstance(prim, Line):
self.ctx.line_to(*self.scale_point(prim.end))
else:
center = map(mul, p.center, self.scale)
start = map(mul, p.start, self.scale)
end = map(mul, p.end, self.scale)
radius = self.scale[0] * p.radius
angle1 = p.start_angle
angle2 = p.end_angle
if p.direction == 'counterclockwise':
ctx.arc(center[0], center[1], radius, angle1, angle2)
center = self.scale_point(prim.center)
radius = self.scale[0] * prim.radius
angle1 = prim.start_angle
angle2 = prim.end_angle
if prim.direction == 'counterclockwise':
self.ctx.arc(*center, radius=radius,
angle1=angle1, angle2=angle2)
else:
ctx.arc_negative(center[0], center[1], radius, angle1, angle2)
ctx.fill()
self.ctx.arc_negative(*center, radius=radius,
angle1=angle1, angle2=angle2)
self.ctx.fill()
def _render_circle(self, circle, color):
center = tuple(map(mul, circle.position, self.scale))
center = self.scale_point(circle.position)
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER
if circle.level_polarity == "dark"
else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
if circle.hole_diameter > 0:
ctx.push_group()
self.ctx.push_group()
ctx.set_line_width(0)
ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi)
ctx.fill()
self.ctx.set_line_width(0)
self.ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi)
self.ctx.fill()
if circle.hole_diameter > 0:
# Render the center clear
ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
ctx.set_operator(cairo.OPERATOR_CLEAR)
ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi)
ctx.fill()
self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi)
self.ctx.fill()
ctx.pop_group_to_source()
ctx.paint_with_alpha(1)
self.ctx.pop_group_to_source()
self.ctx.paint_with_alpha(1)
def _render_rectangle(self, rectangle, color):
ll = map(mul, rectangle.lower_left, self.scale)
width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale)))
lower_left = self.scale_point(rectangle.lower_left)
width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))])
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if rectangle.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER
if rectangle.level_polarity == "dark"
else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
if rectangle.rotation != 0:
ctx.save()
self.ctx.save()
center = map(mul, rectangle.position, self.scale)
matrix = cairo.Matrix()
matrix.translate(center[0], center[1])
# For drawing, we already handles the translation
ll[0] = ll[0] - center[0]
ll[1] = ll[1] - center[1]
lower_left[0] = lower_left[0] - center[0]
lower_left[1] = lower_left[1] - center[1]
matrix.rotate(rectangle.rotation)
ctx.transform(matrix)
self.ctx.transform(matrix)
if rectangle.hole_diameter > 0:
ctx.push_group()
self.ctx.push_group()
ctx.set_line_width(0)
ctx.rectangle(ll[0], ll[1], width, height)
ctx.fill()
self.ctx.set_line_width(0)
self.ctx.rectangle(lower_left[0], lower_left[1], width, height)
self.ctx.fill()
if rectangle.hole_diameter > 0:
# Render the center clear
ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
center = map(mul, rectangle.position, self.scale)
ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi)
ctx.fill()
self.ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi)
self.ctx.fill()
ctx.pop_group_to_source()
ctx.paint_with_alpha(1)
self.ctx.pop_group_to_source()
self.ctx.paint_with_alpha(1)
if rectangle.rotation != 0:
ctx.restore()
self.ctx.restore()
def _render_obround(self, obround, color):
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
if obround.hole_diameter > 0:
ctx.push_group()
self.ctx.push_group()
self._render_circle(obround.subshapes['circle1'], color)
self._render_circle(obround.subshapes['circle2'], color)
@ -311,55 +314,54 @@ class GerberCairoContext(GerberContext):
if obround.hole_diameter > 0:
# Render the center clear
ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
center = map(mul, obround.position, self.scale)
ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi)
ctx.fill()
self.ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi)
self.ctx.fill()
ctx.pop_group_to_source()
ctx.paint_with_alpha(1)
self.ctx.pop_group_to_source()
self.ctx.paint_with_alpha(1)
def _render_polygon(self, polygon, color):
# TODO Ths does not handle rotation of a polygon
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
if polygon.hole_radius > 0:
ctx.push_group()
self.ctx.push_group()
vertices = polygon.vertices
ctx.set_line_width(0)
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
self.ctx.set_line_width(0)
self.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
# Start from before the end so it is easy to iterate and make sure it is closed
ctx.move_to(*map(mul, vertices[-1], self.scale))
self.ctx.move_to(*map(mul, vertices[-1], self.scale))
for v in vertices:
ctx.line_to(*map(mul, v, self.scale))
self.ctx.line_to(*map(mul, v, self.scale))
ctx.fill()
self.ctx.fill()
if polygon.hole_radius > 0:
# Render the center clear
center = tuple(map(mul, polygon.position, self.scale))
ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
ctx.set_operator(cairo.OPERATOR_CLEAR)
ctx.set_line_width(0)
ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi)
ctx.fill()
self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_line_width(0)
self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi)
self.ctx.fill()
ctx.pop_group_to_source()
ctx.paint_with_alpha(1)
self.ctx.pop_group_to_source()
self.ctx.paint_with_alpha(1)
def _render_drill(self, circle, color):
def _render_drill(self, circle, color=None):
color = color if color is not None else self.drill_color
self._render_circle(circle, color)
def _render_slot(self, slot, color):
@ -369,19 +371,17 @@ class GerberCairoContext(GerberContext):
width = slot.diameter
if not self.invert:
ctx = self.ctx
ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
else:
ctx = self.mask_ctx
ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
ctx.set_operator(cairo.OPERATOR_CLEAR)
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
ctx.set_line_width(width * self.scale[0])
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
ctx.move_to(*start)
ctx.line_to(*end)
ctx.stroke()
self.ctx.set_line_width(width * self.scale[0])
self.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
self.ctx.move_to(*start)
self.ctx.line_to(*end)
self.ctx.stroke()
def _render_amgroup(self, amgroup, color):
self.ctx.push_group()
@ -391,33 +391,52 @@ class GerberCairoContext(GerberContext):
self.ctx.paint_with_alpha(1)
def _render_test_record(self, primitive, color):
position = tuple(map(add, primitive.position, self.origin_in_inch))
position = [pos + origin for pos, origin in zip(primitive.position, self.origin_in_inch)]
self.ctx.set_operator(cairo.OPERATOR_OVER)
self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
self.ctx.select_font_face(
'monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
self.ctx.set_font_size(13)
self._render_circle(Circle(position, 0.015), color)
self.ctx.set_source_rgba(*color, alpha=self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER if primitive.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
self.ctx.move_to(*[self.scale[0] * (coord + 0.015) for coord in position])
self.ctx.set_operator(
cairo.OPERATOR_OVER if primitive.level_polarity == 'dark' else cairo.OPERATOR_CLEAR)
self.ctx.move_to(*[self.scale[0] * (coord + 0.015)
for coord in position])
self.ctx.scale(1, -1)
self.ctx.show_text(primitive.net_name)
self.ctx.scale(1, -1)
def _clear_mask(self):
self.mask_ctx.set_operator(cairo.OPERATOR_OVER)
self.mask_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=self.alpha)
self.mask_ctx.paint()
self.ctx.scale(1, -1)
def _render_mask(self):
self.ctx.set_operator(cairo.OPERATOR_OVER)
ptn = cairo.SurfacePattern(self.mask)
def _new_render_layer(self, color=None):
size_in_pixels = self.scale_point(self.size_in_inch)
layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1])
ctx = cairo.Context(layer)
ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
ctx.scale(1, -1)
ctx.translate(-(self.origin_in_inch[0] * self.scale[0]),
(-self.origin_in_inch[1] * self.scale[0])
- size_in_pixels[1])
if self.invert:
ctx.set_operator(cairo.OPERATOR_OVER)
ctx.set_source_rgba(*self.color, alpha=self.alpha)
ctx.paint()
self.ctx = ctx
self.active_layer = layer
def _flatten(self):
self.output_ctx.set_operator(cairo.OPERATOR_OVER)
ptn = cairo.SurfacePattern(self.active_layer)
ptn.set_matrix(self._xform_matrix)
self.ctx.set_source(ptn)
self.ctx.paint()
self.output_ctx.set_source(ptn)
self.output_ctx.paint()
self.ctx = None
self.active_layer = None
def _paint_background(self, force=False):
if (not self.bg) or force:
self.bg = True
self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0)
self.ctx.paint()
if (not self.bg) or force:
self.bg = True
self.output_ctx.set_operator(cairo.OPERATOR_OVER)
self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0)
self.output_ctx.paint()
def scale_point(self, point):
return tuple([coord * scale for coord, scale in zip(point, self.scale)])

Wyświetl plik

@ -57,12 +57,14 @@ class GerberContext(object):
alpha : float
Rendering opacity. Between 0.0 (transparent) and 1.0 (opaque.)
"""
def __init__(self, units='inch'):
self._units = units
self._color = (0.7215, 0.451, 0.200)
self._background_color = (0.0, 0.0, 0.0)
self._alpha = 1.0
self._invert = False
self.ctx = None
@property
def units(self):
@ -134,11 +136,10 @@ class GerberContext(object):
def render(self, primitive):
if not primitive:
return
color = (self.color if primitive.level_polarity == 'dark'
else self.background_color)
self._pre_render_primitive(primitive)
color = self.color
if isinstance(primitive, Line):
self._render_line(primitive, color)
elif isinstance(primitive, Arc):
@ -180,6 +181,7 @@ class GerberContext(object):
"""
return
def _render_line(self, primitive, color):
pass
@ -215,9 +217,9 @@ class GerberContext(object):
class RenderSettings(object):
def __init__(self, color=(0.0, 0.0, 0.0), alpha=1.0, invert=False, mirror=False):
self.color = color
self.alpha = alpha
self.invert = invert
self.mirror = mirror

Wyświetl plik

@ -23,7 +23,7 @@ COLORS = {
'white': (1.0, 1.0, 1.0),
'red': (1.0, 0.0, 0.0),
'green': (0.0, 1.0, 0.0),
'blue' : (0.0, 0.0, 1.0),
'blue': (0.0, 0.0, 1.0),
'fr-4': (0.290, 0.345, 0.0),
'green soldermask': (0.0, 0.612, 0.396),
'blue soldermask': (0.059, 0.478, 0.651),
@ -36,6 +36,7 @@ COLORS = {
class Theme(object):
def __init__(self, name=None, **kwargs):
self.name = 'Default' if name is None else name
self.background = kwargs.get('background', RenderSettings(COLORS['black'], alpha=0.0))
@ -67,4 +68,3 @@ THEMES = {
topmask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True),
bottommask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True)),
}

Wyświetl plik

@ -200,7 +200,8 @@ class GerberParser(object):
DEPRECATED_FORMAT = re.compile(r'(?P<format>G9[01])\*')
# end deprecated
PARAMS = (FS, MO, LP, AD_CIRCLE, AD_RECT, AD_OBROUND, AD_POLY, AD_MACRO, AM, AS, IN, IP, IR, MI, OF, SF, LN)
PARAMS = (FS, MO, LP, AD_CIRCLE, AD_RECT, AD_OBROUND, AD_POLY,
AD_MACRO, AM, AS, IN, IP, IR, MI, OF, SF, LN)
PARAM_STMT = [re.compile(r"%?{0}\*%?".format(p)) for p in PARAMS]
@ -418,7 +419,8 @@ class GerberParser(object):
# deprecated codes
(deprecated_unit, r) = _match_one(self.DEPRECATED_UNIT, line)
if deprecated_unit:
stmt = MOParamStmt(param="MO", mo="inch" if "G70" in deprecated_unit["mode"] else "metric")
stmt = MOParamStmt(param="MO", mo="inch" if "G70" in
deprecated_unit["mode"] else "metric")
self.settings.units = stmt.mode
yield stmt
line = r
@ -532,7 +534,9 @@ class GerberParser(object):
if self.region_mode == 'on' and stmt.mode == 'off':
# Sometimes we have regions that have no points. Skip those
if self.current_region:
self.primitives.append(Region(self.current_region, level_polarity=self.level_polarity, units=self.settings.units))
self.primitives.append(Region(self.current_region,
level_polarity=self.level_polarity, units=self.settings.units))
self.current_region = None
self.region_mode = stmt.mode
elif stmt.type == 'QuadrantMode':
@ -562,7 +566,8 @@ class GerberParser(object):
self.interpolation = 'linear'
elif stmt.function in ('G02', 'G2', 'G03', 'G3'):
self.interpolation = 'arc'
self.direction = ('clockwise' if stmt.function in ('G02', 'G2') else 'counterclockwise')
self.direction = ('clockwise' if stmt.function in
('G02', 'G2') else 'counterclockwise')
if stmt.only_function:
# Sometimes we get a coordinate statement
@ -582,16 +587,30 @@ class GerberParser(object):
if self.interpolation == 'linear':
if self.region_mode == 'off':
self.primitives.append(Line(start, end, self.apertures[self.aperture], level_polarity=self.level_polarity, units=self.settings.units))
self.primitives.append(Line(start, end,
self.apertures[self.aperture],
level_polarity=self.level_polarity,
units=self.settings.units))
else:
# from gerber spec revision J3, Section 4.5, page 55:
# The segments are not graphics objects in themselves; segments are part of region which is the graphics object. The segments have no thickness.
# The current aperture is associated with the region. This has no graphical effect, but allows all its attributes to be applied to the region.
# The current aperture is associated with the region.
# This has no graphical effect, but allows all its attributes to
# be applied to the region.
if self.current_region is None:
self.current_region = [Line(start, end, self.apertures.get(self.aperture, Circle((0,0), 0)), level_polarity=self.level_polarity, units=self.settings.units),]
else:
self.current_region.append(Line(start, end, self.apertures.get(self.aperture, Circle((0,0), 0)), level_polarity=self.level_polarity, units=self.settings.units))
self.current_region = [Line(start, end,
self.apertures.get(self.aperture,
Circle((0, 0), 0)),
level_polarity=self.level_polarity,
units=self.settings.units), ]
else:
self.current_region.append(Line(start, end,
self.apertures.get(self.aperture,
Circle((0, 0), 0)),
level_polarity=self.level_polarity,
units=self.settings.units))
else:
i = 0 if stmt.i is None else stmt.i
j = 0 if stmt.j is None else stmt.j
@ -614,17 +633,23 @@ class GerberParser(object):
elif self.op == "D03" or self.op == "D3":
primitive = copy.deepcopy(self.apertures[self.aperture])
# XXX: temporary fix because there are no primitives for Macros and Polygon
if primitive is not None:
# XXX: just to make it easy to spot
if isinstance(primitive, type([])):
print(primitive[0].to_gerber())
else:
if not isinstance(primitive, AMParamStmt):
primitive.position = (x, y)
primitive.level_polarity = self.level_polarity
primitive.units = self.settings.units
self.primitives.append(primitive)
else:
# Aperture Macro
for am_prim in primitive.primitives:
renderable = am_prim.to_primitive((x, y),
self.level_polarity,
self.settings.units)
if renderable is not None:
self.primitives.append(renderable)
self.x, self.y = x, y
def _find_center(self, start, end, offsets):

Wyświetl plik

@ -7,6 +7,7 @@ from .tests import *
from ..am_statements import *
from ..am_statements import inch, metric
def test_AMPrimitive_ctor():
for exposure in ('on', 'off', 'ON', 'OFF'):
for code in (0, 1, 2, 4, 5, 6, 7, 20, 21, 22):
@ -20,13 +21,13 @@ def test_AMPrimitive_validation():
assert_raises(ValueError, AMPrimitive, 0, 'exposed')
assert_raises(ValueError, AMPrimitive, 3, 'off')
def test_AMPrimitive_conversion():
p = AMPrimitive(4, 'on')
assert_raises(NotImplementedError, p.to_inch)
assert_raises(NotImplementedError, p.to_metric)
def test_AMCommentPrimitive_ctor():
c = AMCommentPrimitive(0, ' This is a comment *')
assert_equal(c.code, 0)
@ -47,6 +48,7 @@ def test_AMCommentPrimitive_dump():
c = AMCommentPrimitive(0, 'Rectangle with rounded corners.')
assert_equal(c.to_gerber(), '0 Rectangle with rounded corners. *')
def test_AMCommentPrimitive_conversion():
c = AMCommentPrimitive(0, 'Rectangle with rounded corners.')
ci = c
@ -56,6 +58,7 @@ def test_AMCommentPrimitive_conversion():
assert_equal(c, ci)
assert_equal(c, cm)
def test_AMCommentPrimitive_string():
c = AMCommentPrimitive(0, 'Test Comment')
assert_equal(str(c), '<Aperture Macro Comment: Test Comment>')
@ -83,7 +86,7 @@ def test_AMCirclePrimitive_factory():
assert_equal(c.code, 1)
assert_equal(c.exposure, 'off')
assert_equal(c.diameter, 5)
assert_equal(c.position, (0,0))
assert_equal(c.position, (0, 0))
def test_AMCirclePrimitive_dump():
@ -92,6 +95,7 @@ def test_AMCirclePrimitive_dump():
c = AMCirclePrimitive(1, 'on', 5, (0, 0))
assert_equal(c.to_gerber(), '1,1,5,0,0*')
def test_AMCirclePrimitive_conversion():
c = AMCirclePrimitive(1, 'off', 25.4, (25.4, 0))
c.to_inch()
@ -103,8 +107,11 @@ def test_AMCirclePrimitive_conversion():
assert_equal(c.diameter, 25.4)
assert_equal(c.position, (25.4, 0))
def test_AMVectorLinePrimitive_validation():
assert_raises(ValueError, AMVectorLinePrimitive, 3, 'on', 0.1, (0,0), (3.3, 5.4), 0)
assert_raises(ValueError, AMVectorLinePrimitive,
3, 'on', 0.1, (0, 0), (3.3, 5.4), 0)
def test_AMVectorLinePrimitive_factory():
l = AMVectorLinePrimitive.from_gerber('20,1,0.9,0,0.45,12,0.45,0*')
@ -115,26 +122,32 @@ def test_AMVectorLinePrimitive_factory():
assert_equal(l.end, (12, 0.45))
assert_equal(l.rotation, 0)
def test_AMVectorLinePrimitive_dump():
l = AMVectorLinePrimitive.from_gerber('20,1,0.9,0,0.45,12,0.45,0*')
assert_equal(l.to_gerber(), '20,1,0.9,0.0,0.45,12.0,0.45,0.0*')
def test_AMVectorLinePrimtive_conversion():
l = AMVectorLinePrimitive(20, 'on', 25.4, (0,0), (25.4, 25.4), 0)
l = AMVectorLinePrimitive(20, 'on', 25.4, (0, 0), (25.4, 25.4), 0)
l.to_inch()
assert_equal(l.width, 1)
assert_equal(l.start, (0, 0))
assert_equal(l.end, (1, 1))
l = AMVectorLinePrimitive(20, 'on', 1, (0,0), (1, 1), 0)
l = AMVectorLinePrimitive(20, 'on', 1, (0, 0), (1, 1), 0)
l.to_metric()
assert_equal(l.width, 25.4)
assert_equal(l.start, (0, 0))
assert_equal(l.end, (25.4, 25.4))
def test_AMOutlinePrimitive_validation():
assert_raises(ValueError, AMOutlinePrimitive, 7, 'on', (0,0), [(3.3, 5.4), (4.0, 5.4), (0, 0)], 0)
assert_raises(ValueError, AMOutlinePrimitive, 4, 'on', (0,0), [(3.3, 5.4), (4.0, 5.4), (0, 1)], 0)
assert_raises(ValueError, AMOutlinePrimitive, 7, 'on',
(0, 0), [(3.3, 5.4), (4.0, 5.4), (0, 0)], 0)
assert_raises(ValueError, AMOutlinePrimitive, 4, 'on',
(0, 0), [(3.3, 5.4), (4.0, 5.4), (0, 1)], 0)
def test_AMOutlinePrimitive_factory():
o = AMOutlinePrimitive.from_gerber('4,1,3,0,0,3,3,3,0,0,0,0*')
@ -144,14 +157,17 @@ def test_AMOutlinePrimitive_factory():
assert_equal(o.points, [(3, 3), (3, 0), (0, 0)])
assert_equal(o.rotation, 0)
def test_AMOUtlinePrimitive_dump():
o = AMOutlinePrimitive(4, 'on', (0, 0), [(3, 3), (3, 0), (0, 0)], 0)
# New lines don't matter for Gerber, but we insert them to make it easier to remove
# For test purposes we can ignore them
assert_equal(o.to_gerber().replace('\n', ''), '4,1,3,0,0,3,3,3,0,0,0,0*')
def test_AMOutlinePrimitive_conversion():
o = AMOutlinePrimitive(4, 'on', (0, 0), [(25.4, 25.4), (25.4, 0), (0, 0)], 0)
o = AMOutlinePrimitive(
4, 'on', (0, 0), [(25.4, 25.4), (25.4, 0), (0, 0)], 0)
o.to_inch()
assert_equal(o.start_point, (0, 0))
assert_equal(o.points, ((1., 1.), (1., 0.), (0., 0.)))
@ -167,6 +183,7 @@ def test_AMPolygonPrimitive_validation():
assert_raises(ValueError, AMPolygonPrimitive, 5, 'on', 2, (3.3, 5.4), 3, 0)
assert_raises(ValueError, AMPolygonPrimitive, 5, 'on', 13, (3.3, 5.4), 3, 0)
def test_AMPolygonPrimitive_factory():
p = AMPolygonPrimitive.from_gerber('5,1,3,3.3,5.4,3,0')
assert_equal(p.code, 5)
@ -176,10 +193,12 @@ def test_AMPolygonPrimitive_factory():
assert_equal(p.diameter, 3)
assert_equal(p.rotation, 0)
def test_AMPolygonPrimitive_dump():
p = AMPolygonPrimitive(5, 'on', 3, (3.3, 5.4), 3, 0)
assert_equal(p.to_gerber(), '5,1,3,3.3,5.4,3,0*')
def test_AMPolygonPrimitive_conversion():
p = AMPolygonPrimitive(5, 'off', 3, (25.4, 0), 25.4, 0)
p.to_inch()
@ -193,7 +212,9 @@ def test_AMPolygonPrimitive_conversion():
def test_AMMoirePrimitive_validation():
assert_raises(ValueError, AMMoirePrimitive, 7, (0, 0), 5.1, 0.2, 0.4, 6, 0.1, 6.1, 0)
assert_raises(ValueError, AMMoirePrimitive, 7,
(0, 0), 5.1, 0.2, 0.4, 6, 0.1, 6.1, 0)
def test_AMMoirePrimitive_factory():
m = AMMoirePrimitive.from_gerber('6,0,0,5,0.5,0.5,2,0.1,6,0*')
@ -207,10 +228,12 @@ def test_AMMoirePrimitive_factory():
assert_equal(m.crosshair_length, 6)
assert_equal(m.rotation, 0)
def test_AMMoirePrimitive_dump():
m = AMMoirePrimitive.from_gerber('6,0,0,5,0.5,0.5,2,0.1,6,0*')
assert_equal(m.to_gerber(), '6,0,0,5.0,0.5,0.5,2,0.1,6.0,0.0*')
def test_AMMoirePrimitive_conversion():
m = AMMoirePrimitive(6, (25.4, 25.4), 25.4, 25.4, 25.4, 6, 25.4, 25.4, 0)
m.to_inch()
@ -230,10 +253,12 @@ def test_AMMoirePrimitive_conversion():
assert_equal(m.crosshair_thickness, 25.4)
assert_equal(m.crosshair_length, 25.4)
def test_AMThermalPrimitive_validation():
assert_raises(ValueError, AMThermalPrimitive, 8, (0.0, 0.0), 7, 5, 0.2, 0.0)
assert_raises(TypeError, AMThermalPrimitive, 7, (0.0, '0'), 7, 5, 0.2, 0.0)
def test_AMThermalPrimitive_factory():
t = AMThermalPrimitive.from_gerber('7,0,0,7,6,0.2,45*')
assert_equal(t.code, 7)
@ -243,10 +268,12 @@ def test_AMThermalPrimitive_factory():
assert_equal(t.gap, 0.2)
assert_equal(t.rotation, 45)
def test_AMThermalPrimitive_dump():
t = AMThermalPrimitive.from_gerber('7,0,0,7,6,0.2,30*')
assert_equal(t.to_gerber(), '7,0,0,7.0,6.0,0.2,30.0*')
def test_AMThermalPrimitive_conversion():
t = AMThermalPrimitive(7, (25.4, 25.4), 25.4, 25.4, 25.4, 0.0)
t.to_inch()
@ -264,7 +291,9 @@ def test_AMThermalPrimitive_conversion():
def test_AMCenterLinePrimitive_validation():
assert_raises(ValueError, AMCenterLinePrimitive, 22, 1, 0.2, 0.5, (0, 0), 0)
assert_raises(ValueError, AMCenterLinePrimitive,
22, 1, 0.2, 0.5, (0, 0), 0)
def test_AMCenterLinePrimtive_factory():
l = AMCenterLinePrimitive.from_gerber('21,1,6.8,1.2,3.4,0.6,0*')
@ -275,10 +304,12 @@ def test_AMCenterLinePrimtive_factory():
assert_equal(l.center, (3.4, 0.6))
assert_equal(l.rotation, 0)
def test_AMCenterLinePrimitive_dump():
l = AMCenterLinePrimitive.from_gerber('21,1,6.8,1.2,3.4,0.6,0*')
assert_equal(l.to_gerber(), '21,1,6.8,1.2,3.4,0.6,0.0*')
def test_AMCenterLinePrimitive_conversion():
l = AMCenterLinePrimitive(21, 'on', 25.4, 25.4, (25.4, 25.4), 0)
l.to_inch()
@ -292,8 +323,11 @@ def test_AMCenterLinePrimitive_conversion():
assert_equal(l.height, 25.4)
assert_equal(l.center, (25.4, 25.4))
def test_AMLowerLeftLinePrimitive_validation():
assert_raises(ValueError, AMLowerLeftLinePrimitive, 23, 1, 0.2, 0.5, (0, 0), 0)
assert_raises(ValueError, AMLowerLeftLinePrimitive,
23, 1, 0.2, 0.5, (0, 0), 0)
def test_AMLowerLeftLinePrimtive_factory():
l = AMLowerLeftLinePrimitive.from_gerber('22,1,6.8,1.2,3.4,0.6,0*')
@ -304,10 +338,12 @@ def test_AMLowerLeftLinePrimtive_factory():
assert_equal(l.lower_left, (3.4, 0.6))
assert_equal(l.rotation, 0)
def test_AMLowerLeftLinePrimitive_dump():
l = AMLowerLeftLinePrimitive.from_gerber('22,1,6.8,1.2,3.4,0.6,0*')
assert_equal(l.to_gerber(), '22,1,6.8,1.2,3.4,0.6,0.0*')
def test_AMLowerLeftLinePrimitive_conversion():
l = AMLowerLeftLinePrimitive(22, 'on', 25.4, 25.4, (25.4, 25.4), 0)
l.to_inch()
@ -321,24 +357,23 @@ def test_AMLowerLeftLinePrimitive_conversion():
assert_equal(l.height, 25.4)
assert_equal(l.lower_left, (25.4, 25.4))
def test_AMUnsupportPrimitive():
u = AMUnsupportPrimitive.from_gerber('Test')
assert_equal(u.primitive, 'Test')
u = AMUnsupportPrimitive('Test')
assert_equal(u.to_gerber(), 'Test')
def test_AMUnsupportPrimitive_smoketest():
u = AMUnsupportPrimitive.from_gerber('Test')
u.to_inch()
u.to_metric()
def test_inch():
assert_equal(inch(25.4), 1)
def test_metric():
assert_equal(metric(1), 25.4)

Wyświetl plik

@ -54,17 +54,20 @@ def test_filesettings_dict_assign():
assert_equal(fs.zero_suppression, 'leading')
assert_equal(fs.format, (1, 2))
def test_camfile_init():
""" Smoke test CamFile test
"""
cf = CamFile()
def test_camfile_settings():
""" Test CamFile Default Settings
"""
cf = CamFile()
assert_equal(cf.settings, FileSettings())
def test_bounds_override_smoketest():
cf = CamFile()
cf.bounds
@ -89,7 +92,7 @@ def test_zeros():
assert_equal(fs.zeros, 'trailing')
assert_equal(fs.zero_suppression, 'leading')
fs.zeros= 'leading'
fs.zeros = 'leading'
assert_equal(fs.zeros, 'leading')
assert_equal(fs.zero_suppression, 'trailing')
@ -113,21 +116,27 @@ def test_zeros():
def test_filesettings_validation():
""" Test FileSettings constructor argument validation
"""
# absolute-ish is not a valid notation
assert_raises(ValueError, FileSettings, 'absolute-ish', 'inch', None, (2, 5), None)
assert_raises(ValueError, FileSettings, 'absolute-ish',
'inch', None, (2, 5), None)
# degrees kelvin isn't a valid unit for a CAM file
assert_raises(ValueError, FileSettings, 'absolute', 'degrees kelvin', None, (2, 5), None)
assert_raises(ValueError, FileSettings, 'absolute',
'degrees kelvin', None, (2, 5), None)
assert_raises(ValueError, FileSettings, 'absolute', 'inch', 'leading', (2, 5), 'leading')
assert_raises(ValueError, FileSettings, 'absolute',
'inch', 'leading', (2, 5), 'leading')
# Technnically this should be an error, but Eangle files often do this incorrectly so we
# allow it
# assert_raises(ValueError, FileSettings, 'absolute', 'inch', 'following', (2, 5), None)
#assert_raises(ValueError, FileSettings, 'absolute',
# 'inch', 'following', (2, 5), None)
assert_raises(ValueError, FileSettings, 'absolute', 'inch', None, (2, 5), 'following')
assert_raises(ValueError, FileSettings, 'absolute', 'inch', None, (2, 5, 6), None)
assert_raises(ValueError, FileSettings, 'absolute',
'inch', None, (2, 5), 'following')
assert_raises(ValueError, FileSettings, 'absolute',
'inch', None, (2, 5, 6), None)
def test_key_validation():
fs = FileSettings()
@ -138,5 +147,3 @@ def test_key_validation():
assert_raises(ValueError, fs.__setitem__, 'zero_suppression', 'following')
assert_raises(ValueError, fs.__setitem__, 'zeros', 'following')
assert_raises(ValueError, fs.__setitem__, 'format', (2, 5, 6))

Wyświetl plik

@ -12,9 +12,10 @@ import os
NCDRILL_FILE = os.path.join(os.path.dirname(__file__),
'resources/ncdrill.DRD')
'resources/ncdrill.DRD')
TOP_COPPER_FILE = os.path.join(os.path.dirname(__file__),
'resources/top_copper.GTL')
'resources/top_copper.GTL')
def test_file_type_detection():
""" Test file type detection
@ -38,6 +39,3 @@ def test_file_type_validation():
""" Test file format validation
"""
assert_raises(ParseError, read, 'LICENSE')

Wyświetl plik

@ -13,6 +13,7 @@ from .tests import *
NCDRILL_FILE = os.path.join(os.path.dirname(__file__),
'resources/ncdrill.DRD')
def test_format_detection():
""" Test file type detection
"""
@ -75,7 +76,8 @@ def test_conversion():
for statement in ncdrill_inch.statements:
statement.to_metric()
for m_tool, i_tool in zip(iter(ncdrill.tools.values()), iter(ncdrill_inch.tools.values())):
for m_tool, i_tool in zip(iter(ncdrill.tools.values()),
iter(ncdrill_inch.tools.values())):
assert_equal(i_tool, m_tool)
for m, i in zip(ncdrill.primitives, inch_primitives):
@ -188,12 +190,10 @@ def test_parse_incremental_position():
p = ExcellonParser(FileSettings(notation='incremental'))
p._parse_line('X01Y01')
p._parse_line('X01Y01')
assert_equal(p.pos, [2.,2.])
assert_equal(p.pos, [2., 2.])
def test_parse_unknown():
p = ExcellonParser(FileSettings())
p._parse_line('Not A Valid Statement')
assert_equal(p.statements[0].stmt, 'Not A Valid Statement')

Wyświetl plik

@ -7,11 +7,13 @@ from .tests import assert_equal, assert_not_equal, assert_raises
from ..excellon_statements import *
from ..cam import FileSettings
def test_excellon_statement_implementation():
stmt = ExcellonStatement()
assert_raises(NotImplementedError, stmt.from_excellon, None)
assert_raises(NotImplementedError, stmt.to_excellon)
def test_excellontstmt():
""" Smoke test ExcellonStatement
"""
@ -20,17 +22,18 @@ def test_excellontstmt():
stmt.to_metric()
stmt.offset()
def test_excellontool_factory():
""" Test ExcellonTool factory methods
"""
exc_line = 'T8F01B02S00003H04Z05C0.12500'
settings = FileSettings(format=(2, 5), zero_suppression='trailing',
units='inch', notation='absolute')
units='inch', notation='absolute')
tool = ExcellonTool.from_excellon(exc_line, settings)
assert_equal(tool.number, 8)
assert_equal(tool.diameter, 0.125)
assert_equal(tool.feed_rate, 1)
assert_equal(tool.retract_rate,2)
assert_equal(tool.retract_rate, 2)
assert_equal(tool.rpm, 3)
assert_equal(tool.max_hit_count, 4)
assert_equal(tool.depth_offset, 5)
@ -41,7 +44,7 @@ def test_excellontool_factory():
assert_equal(tool.number, 8)
assert_equal(tool.diameter, 0.125)
assert_equal(tool.feed_rate, 1)
assert_equal(tool.retract_rate,2)
assert_equal(tool.retract_rate, 2)
assert_equal(tool.rpm, 3)
assert_equal(tool.max_hit_count, 4)
assert_equal(tool.depth_offset, 5)
@ -55,7 +58,7 @@ def test_excellontool_dump():
'T07F0S0C0.04300', 'T08F0S0C0.12500', 'T09F0S0C0.13000',
'T08B01F02H03S00003C0.12500Z04', 'T01F0S300.999C0.01200']
settings = FileSettings(format=(2, 5), zero_suppression='trailing',
units='inch', notation='absolute')
units='inch', notation='absolute')
for line in exc_lines:
tool = ExcellonTool.from_excellon(line, settings)
assert_equal(tool.to_excellon(), line)
@ -63,7 +66,7 @@ def test_excellontool_dump():
def test_excellontool_order():
settings = FileSettings(format=(2, 5), zero_suppression='trailing',
units='inch', notation='absolute')
units='inch', notation='absolute')
line = 'T8F00S00C0.12500'
tool1 = ExcellonTool.from_excellon(line, settings)
line = 'T8C0.12500F00S00'
@ -72,36 +75,48 @@ def test_excellontool_order():
assert_equal(tool1.feed_rate, tool2.feed_rate)
assert_equal(tool1.rpm, tool2.rpm)
def test_excellontool_conversion():
tool = ExcellonTool.from_dict(FileSettings(units='metric'), {'number': 8, 'diameter': 25.4})
tool = ExcellonTool.from_dict(FileSettings(units='metric'),
{'number': 8, 'diameter': 25.4})
tool.to_inch()
assert_equal(tool.diameter, 1.)
tool = ExcellonTool.from_dict(FileSettings(units='inch'), {'number': 8, 'diameter': 1.})
tool = ExcellonTool.from_dict(FileSettings(units='inch'),
{'number': 8, 'diameter': 1.})
tool.to_metric()
assert_equal(tool.diameter, 25.4)
# Shouldn't change units if we're already using target units
tool = ExcellonTool.from_dict(FileSettings(units='inch'), {'number': 8, 'diameter': 25.4})
tool = ExcellonTool.from_dict(FileSettings(units='inch'),
{'number': 8, 'diameter': 25.4})
tool.to_inch()
assert_equal(tool.diameter, 25.4)
tool = ExcellonTool.from_dict(FileSettings(units='metric'), {'number': 8, 'diameter': 1.})
tool = ExcellonTool.from_dict(FileSettings(units='metric'),
{'number': 8, 'diameter': 1.})
tool.to_metric()
assert_equal(tool.diameter, 1.)
def test_excellontool_repr():
tool = ExcellonTool.from_dict(FileSettings(), {'number': 8, 'diameter': 0.125})
tool = ExcellonTool.from_dict(FileSettings(),
{'number': 8, 'diameter': 0.125})
assert_equal(str(tool), '<ExcellonTool 08: 0.125in. dia.>')
tool = ExcellonTool.from_dict(FileSettings(units='metric'), {'number': 8, 'diameter': 0.125})
tool = ExcellonTool.from_dict(FileSettings(units='metric'),
{'number': 8, 'diameter': 0.125})
assert_equal(str(tool), '<ExcellonTool 08: 0.125mm dia.>')
def test_excellontool_equality():
t = ExcellonTool.from_dict(FileSettings(), {'number': 8, 'diameter': 0.125})
t1 = ExcellonTool.from_dict(FileSettings(), {'number': 8, 'diameter': 0.125})
t = ExcellonTool.from_dict(
FileSettings(), {'number': 8, 'diameter': 0.125})
t1 = ExcellonTool.from_dict(
FileSettings(), {'number': 8, 'diameter': 0.125})
assert_equal(t, t1)
t1 = ExcellonTool.from_dict(FileSettings(units='metric'), {'number': 8, 'diameter': 0.125})
t1 = ExcellonTool.from_dict(FileSettings(units='metric'),
{'number': 8, 'diameter': 0.125})
assert_not_equal(t, t1)
def test_toolselection_factory():
""" Test ToolSelectionStmt factory method
"""
@ -115,6 +130,7 @@ def test_toolselection_factory():
assert_equal(stmt.tool, 42)
assert_equal(stmt.compensation_index, None)
def test_toolselection_dump():
""" Test ToolSelectionStmt to_excellon()
"""
@ -123,6 +139,7 @@ def test_toolselection_dump():
stmt = ToolSelectionStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_z_axis_infeed_rate_factory():
""" Test ZAxisInfeedRateStmt factory method
"""
@ -133,6 +150,7 @@ def test_z_axis_infeed_rate_factory():
stmt = ZAxisInfeedRateStmt.from_excellon('F03')
assert_equal(stmt.rate, 3)
def test_z_axis_infeed_rate_dump():
""" Test ZAxisInfeedRateStmt to_excellon()
"""
@ -145,11 +163,12 @@ def test_z_axis_infeed_rate_dump():
stmt = ZAxisInfeedRateStmt.from_excellon(input_rate)
assert_equal(stmt.to_excellon(), expected_output)
def test_coordinatestmt_factory():
""" Test CoordinateStmt factory method
"""
settings = FileSettings(format=(2, 5), zero_suppression='trailing',
units='inch', notation='absolute')
units='inch', notation='absolute')
line = 'X0278207Y0065293'
stmt = CoordinateStmt.from_excellon(line, settings)
@ -165,7 +184,7 @@ def test_coordinatestmt_factory():
# assert_equal(stmt.y, 0.575)
settings = FileSettings(format=(2, 4), zero_suppression='leading',
units='inch', notation='absolute')
units='inch', notation='absolute')
line = 'X9660Y4639'
stmt = CoordinateStmt.from_excellon(line, settings)
@ -173,12 +192,12 @@ def test_coordinatestmt_factory():
assert_equal(stmt.y, 0.4639)
assert_equal(stmt.to_excellon(settings), "X9660Y4639")
assert_equal(stmt.units, 'inch')
settings.units = 'metric'
stmt = CoordinateStmt.from_excellon(line, settings)
assert_equal(stmt.units, 'metric')
def test_coordinatestmt_dump():
""" Test CoordinateStmt to_excellon()
"""
@ -186,102 +205,110 @@ def test_coordinatestmt_dump():
'X251295Y81528', 'X2525Y78', 'X255Y575', 'Y52',
'X2675', 'Y575', 'X2425', 'Y52', 'X23', ]
settings = FileSettings(format=(2, 4), zero_suppression='leading',
units='inch', notation='absolute')
units='inch', notation='absolute')
for line in lines:
stmt = CoordinateStmt.from_excellon(line, settings)
assert_equal(stmt.to_excellon(settings), line)
def test_coordinatestmt_conversion():
settings = FileSettings()
settings.units = 'metric'
stmt = CoordinateStmt.from_excellon('X254Y254', settings)
#No effect
# No effect
stmt.to_metric()
assert_equal(stmt.x, 25.4)
assert_equal(stmt.y, 25.4)
stmt.to_inch()
assert_equal(stmt.units, 'inch')
assert_equal(stmt.x, 1.)
assert_equal(stmt.y, 1.)
#No effect
# No effect
stmt.to_inch()
assert_equal(stmt.x, 1.)
assert_equal(stmt.y, 1.)
settings.units = 'inch'
stmt = CoordinateStmt.from_excellon('X01Y01', settings)
#No effect
# No effect
stmt.to_inch()
assert_equal(stmt.x, 1.)
assert_equal(stmt.y, 1.)
stmt.to_metric()
assert_equal(stmt.units, 'metric')
assert_equal(stmt.x, 25.4)
assert_equal(stmt.y, 25.4)
#No effect
# No effect
stmt.to_metric()
assert_equal(stmt.x, 25.4)
assert_equal(stmt.y, 25.4)
def test_coordinatestmt_offset():
stmt = CoordinateStmt.from_excellon('X01Y01', FileSettings())
stmt.offset()
assert_equal(stmt.x, 1)
assert_equal(stmt.y, 1)
stmt.offset(1,0)
stmt.offset(1, 0)
assert_equal(stmt.x, 2.)
assert_equal(stmt.y, 1.)
stmt.offset(0,1)
stmt.offset(0, 1)
assert_equal(stmt.x, 2.)
assert_equal(stmt.y, 2.)
def test_coordinatestmt_string():
settings = FileSettings(format=(2, 4), zero_suppression='leading',
units='inch', notation='absolute')
units='inch', notation='absolute')
stmt = CoordinateStmt.from_excellon('X9660Y4639', settings)
assert_equal(str(stmt), '<Coordinate Statement: X: 0.966 Y: 0.4639 >')
def test_repeathole_stmt_factory():
stmt = RepeatHoleStmt.from_excellon('R0004X015Y32', FileSettings(zeros='leading', units='inch'))
stmt = RepeatHoleStmt.from_excellon('R0004X015Y32',
FileSettings(zeros='leading',
units='inch'))
assert_equal(stmt.count, 4)
assert_equal(stmt.xdelta, 1.5)
assert_equal(stmt.ydelta, 32)
assert_equal(stmt.units, 'inch')
stmt = RepeatHoleStmt.from_excellon('R0004X015Y32', FileSettings(zeros='leading', units='metric'))
stmt = RepeatHoleStmt.from_excellon('R0004X015Y32',
FileSettings(zeros='leading',
units='metric'))
assert_equal(stmt.units, 'metric')
def test_repeatholestmt_dump():
line = 'R4X015Y32'
stmt = RepeatHoleStmt.from_excellon(line, FileSettings())
assert_equal(stmt.to_excellon(FileSettings()), line)
def test_repeatholestmt_conversion():
line = 'R4X0254Y254'
settings = FileSettings()
settings.units = 'metric'
stmt = RepeatHoleStmt.from_excellon(line, settings)
#No effect
# No effect
stmt.to_metric()
assert_equal(stmt.xdelta, 2.54)
assert_equal(stmt.ydelta, 25.4)
stmt.to_inch()
assert_equal(stmt.units, 'inch')
assert_equal(stmt.xdelta, 0.1)
assert_equal(stmt.ydelta, 1.)
#no effect
# no effect
stmt.to_inch()
assert_equal(stmt.xdelta, 0.1)
assert_equal(stmt.ydelta, 1.)
@ -289,26 +316,28 @@ def test_repeatholestmt_conversion():
line = 'R4X01Y1'
settings.units = 'inch'
stmt = RepeatHoleStmt.from_excellon(line, settings)
#no effect
# no effect
stmt.to_inch()
assert_equal(stmt.xdelta, 1.)
assert_equal(stmt.ydelta, 10.)
stmt.to_metric()
assert_equal(stmt.units, 'metric')
assert_equal(stmt.xdelta, 25.4)
assert_equal(stmt.ydelta, 254.)
#No effect
# No effect
stmt.to_metric()
assert_equal(stmt.xdelta, 25.4)
assert_equal(stmt.ydelta, 254.)
def test_repeathole_str():
stmt = RepeatHoleStmt.from_excellon('R4X015Y32', FileSettings())
assert_equal(str(stmt), '<Repeat Hole: 4 times, offset X: 1.5 Y: 32>')
def test_commentstmt_factory():
""" Test CommentStmt factory method
"""
@ -333,42 +362,52 @@ def test_commentstmt_dump():
stmt = CommentStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_header_begin_stmt():
stmt = HeaderBeginStmt()
assert_equal(stmt.to_excellon(None), 'M48')
def test_header_end_stmt():
stmt = HeaderEndStmt()
assert_equal(stmt.to_excellon(None), 'M95')
def test_rewindstop_stmt():
stmt = RewindStopStmt()
assert_equal(stmt.to_excellon(None), '%')
def test_z_axis_rout_position_stmt():
stmt = ZAxisRoutPositionStmt()
assert_equal(stmt.to_excellon(None), 'M15')
def test_retract_with_clamping_stmt():
stmt = RetractWithClampingStmt()
assert_equal(stmt.to_excellon(None), 'M16')
def test_retract_without_clamping_stmt():
stmt = RetractWithoutClampingStmt()
assert_equal(stmt.to_excellon(None), 'M17')
def test_cutter_compensation_off_stmt():
stmt = CutterCompensationOffStmt()
assert_equal(stmt.to_excellon(None), 'G40')
def test_cutter_compensation_left_stmt():
stmt = CutterCompensationLeftStmt()
assert_equal(stmt.to_excellon(None), 'G41')
def test_cutter_compensation_right_stmt():
stmt = CutterCompensationRightStmt()
assert_equal(stmt.to_excellon(None), 'G42')
def test_endofprogramstmt_factory():
settings = FileSettings(units='inch')
stmt = EndOfProgramStmt.from_excellon('M30X01Y02', settings)
@ -384,61 +423,65 @@ def test_endofprogramstmt_factory():
assert_equal(stmt.x, None)
assert_equal(stmt.y, 2.)
def test_endofprogramStmt_dump():
lines = ['M30X01Y02',]
lines = ['M30X01Y02', ]
for line in lines:
stmt = EndOfProgramStmt.from_excellon(line, FileSettings())
assert_equal(stmt.to_excellon(FileSettings()), line)
def test_endofprogramstmt_conversion():
settings = FileSettings()
settings.units = 'metric'
stmt = EndOfProgramStmt.from_excellon('M30X0254Y254', settings)
#No effect
# No effect
stmt.to_metric()
assert_equal(stmt.x, 2.54)
assert_equal(stmt.y, 25.4)
stmt.to_inch()
assert_equal(stmt.units, 'inch')
assert_equal(stmt.x, 0.1)
assert_equal(stmt.y, 1.0)
#No effect
# No effect
stmt.to_inch()
assert_equal(stmt.x, 0.1)
assert_equal(stmt.y, 1.0)
settings.units = 'inch'
stmt = EndOfProgramStmt.from_excellon('M30X01Y1', settings)
#No effect
# No effect
stmt.to_inch()
assert_equal(stmt.x, 1.)
assert_equal(stmt.y, 10.0)
stmt.to_metric()
assert_equal(stmt.units, 'metric')
assert_equal(stmt.x, 25.4)
assert_equal(stmt.y, 254.)
#No effect
# No effect
stmt.to_metric()
assert_equal(stmt.x, 25.4)
assert_equal(stmt.y, 254.)
def test_endofprogramstmt_offset():
stmt = EndOfProgramStmt(1, 1)
stmt.offset()
assert_equal(stmt.x, 1)
assert_equal(stmt.y, 1)
stmt.offset(1,0)
stmt.offset(1, 0)
assert_equal(stmt.x, 2.)
assert_equal(stmt.y, 1.)
stmt.offset(0,1)
stmt.offset(0, 1)
assert_equal(stmt.x, 2.)
assert_equal(stmt.y, 2.)
def test_unitstmt_factory():
""" Test UnitStmt factory method
"""
@ -471,6 +514,7 @@ def test_unitstmt_dump():
stmt = UnitStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_unitstmt_conversion():
stmt = UnitStmt.from_excellon('METRIC,TZ')
stmt.to_inch()
@ -480,6 +524,7 @@ def test_unitstmt_conversion():
stmt.to_metric()
assert_equal(stmt.units, 'metric')
def test_incrementalmode_factory():
""" Test IncrementalModeStmt factory method
"""
@ -527,6 +572,7 @@ def test_versionstmt_dump():
stmt = VersionStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_versionstmt_validation():
""" Test VersionStmt input validation
"""
@ -608,6 +654,7 @@ def test_measmodestmt_validation():
assert_raises(ValueError, MeasuringModeStmt.from_excellon, 'M70')
assert_raises(ValueError, MeasuringModeStmt, 'millimeters')
def test_measmodestmt_conversion():
line = 'M72'
stmt = MeasuringModeStmt.from_excellon(line)
@ -621,27 +668,33 @@ def test_measmodestmt_conversion():
stmt.to_inch()
assert_equal(stmt.units, 'inch')
def test_routemode_stmt():
stmt = RouteModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G00')
def test_linearmode_stmt():
stmt = LinearModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G01')
def test_drillmode_stmt():
stmt = DrillModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G05')
def test_absolutemode_stmt():
stmt = AbsoluteModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G90')
def test_unknownstmt():
stmt = UnknownStmt('TEST')
assert_equal(stmt.stmt, 'TEST')
assert_equal(str(stmt), '<Unknown Statement: TEST>')
def test_unknownstmt_dump():
stmt = UnknownStmt('TEST')
assert_equal(stmt.to_excellon(FileSettings()), 'TEST')

Wyświetl plik

@ -7,6 +7,7 @@ from .tests import *
from ..gerber_statements import *
from ..cam import FileSettings
def test_Statement_smoketest():
stmt = Statement('Test')
assert_equal(stmt.type, 'Test')
@ -16,7 +17,8 @@ def test_Statement_smoketest():
assert_in('units=inch', str(stmt))
stmt.to_metric()
stmt.offset(1, 1)
assert_in('type=Test',str(stmt))
assert_in('type=Test', str(stmt))
def test_FSParamStmt_factory():
""" Test FSParamStruct factory
@ -35,6 +37,7 @@ def test_FSParamStmt_factory():
assert_equal(fs.notation, 'incremental')
assert_equal(fs.format, (2, 7))
def test_FSParamStmt():
""" Test FSParamStmt initialization
"""
@ -48,6 +51,7 @@ def test_FSParamStmt():
assert_equal(stmt.notation, notation)
assert_equal(stmt.format, fmt)
def test_FSParamStmt_dump():
""" Test FSParamStmt to_gerber()
"""
@ -62,16 +66,20 @@ def test_FSParamStmt_dump():
settings = FileSettings(zero_suppression='leading', notation='absolute')
assert_equal(fs.to_gerber(settings), '%FSLAX25Y25*%')
def test_FSParamStmt_string():
""" Test FSParamStmt.__str__()
"""
stmt = {'param': 'FS', 'zero': 'L', 'notation': 'A', 'x': '27'}
fs = FSParamStmt.from_dict(stmt)
assert_equal(str(fs), '<Format Spec: 2:7 leading zero suppression absolute notation>')
assert_equal(str(fs),
'<Format Spec: 2:7 leading zero suppression absolute notation>')
stmt = {'param': 'FS', 'zero': 'T', 'notation': 'I', 'x': '25'}
fs = FSParamStmt.from_dict(stmt)
assert_equal(str(fs), '<Format Spec: 2:5 trailing zero suppression incremental notation>')
assert_equal(str(fs),
'<Format Spec: 2:5 trailing zero suppression incremental notation>')
def test_MOParamStmt_factory():
""" Test MOParamStruct factory
@ -94,6 +102,7 @@ def test_MOParamStmt_factory():
stmt = {'param': 'MO', 'mo': 'degrees kelvin'}
assert_raises(ValueError, MOParamStmt.from_dict, stmt)
def test_MOParamStmt():
""" Test MOParamStmt initialization
"""
@ -106,6 +115,7 @@ def test_MOParamStmt():
stmt = MOParamStmt(param, mode)
assert_equal(stmt.mode, mode)
def test_MOParamStmt_dump():
""" Test MOParamStmt to_gerber()
"""
@ -117,6 +127,7 @@ def test_MOParamStmt_dump():
mo = MOParamStmt.from_dict(stmt)
assert_equal(mo.to_gerber(), '%MOMM*%')
def test_MOParamStmt_conversion():
stmt = {'param': 'MO', 'mo': 'MM'}
mo = MOParamStmt.from_dict(stmt)
@ -128,6 +139,7 @@ def test_MOParamStmt_conversion():
mo.to_metric()
assert_equal(mo.mode, 'metric')
def test_MOParamStmt_string():
""" Test MOParamStmt.__str__()
"""
@ -139,6 +151,7 @@ def test_MOParamStmt_string():
mo = MOParamStmt.from_dict(stmt)
assert_equal(str(mo), '<Mode: millimeters>')
def test_IPParamStmt_factory():
""" Test IPParamStruct factory
"""
@ -150,6 +163,7 @@ def test_IPParamStmt_factory():
ip = IPParamStmt.from_dict(stmt)
assert_equal(ip.ip, 'negative')
def test_IPParamStmt():
""" Test IPParamStmt initialization
"""
@ -159,6 +173,7 @@ def test_IPParamStmt():
assert_equal(stmt.param, param)
assert_equal(stmt.ip, ip)
def test_IPParamStmt_dump():
""" Test IPParamStmt to_gerber()
"""
@ -170,6 +185,7 @@ def test_IPParamStmt_dump():
ip = IPParamStmt.from_dict(stmt)
assert_equal(ip.to_gerber(), '%IPNEG*%')
def test_IPParamStmt_string():
stmt = {'param': 'IP', 'ip': 'POS'}
ip = IPParamStmt.from_dict(stmt)
@ -179,22 +195,26 @@ def test_IPParamStmt_string():
ip = IPParamStmt.from_dict(stmt)
assert_equal(str(ip), '<Image Polarity: negative>')
def test_IRParamStmt_factory():
stmt = {'param': 'IR', 'angle': '45'}
ir = IRParamStmt.from_dict(stmt)
assert_equal(ir.param, 'IR')
assert_equal(ir.angle, 45)
def test_IRParamStmt_dump():
stmt = {'param': 'IR', 'angle': '45'}
ir = IRParamStmt.from_dict(stmt)
assert_equal(ir.to_gerber(), '%IR45*%')
def test_IRParamStmt_string():
stmt = {'param': 'IR', 'angle': '45'}
ir = IRParamStmt.from_dict(stmt)
assert_equal(str(ir), '<Image Angle: 45>')
def test_OFParamStmt_factory():
""" Test OFParamStmt factory
"""
@ -203,6 +223,7 @@ def test_OFParamStmt_factory():
assert_equal(of.a, 0.1234567)
assert_equal(of.b, 0.1234567)
def test_OFParamStmt():
""" Test IPParamStmt initialization
"""
@ -213,6 +234,7 @@ def test_OFParamStmt():
assert_equal(stmt.a, val)
assert_equal(stmt.b, val)
def test_OFParamStmt_dump():
""" Test OFParamStmt to_gerber()
"""
@ -220,10 +242,11 @@ def test_OFParamStmt_dump():
of = OFParamStmt.from_dict(stmt)
assert_equal(of.to_gerber(), '%OFA0.12345B0.12345*%')
def test_OFParamStmt_conversion():
stmt = {'param': 'OF', 'a': '2.54', 'b': '25.4'}
of = OFParamStmt.from_dict(stmt)
of.units='metric'
of.units = 'metric'
# No effect
of.to_metric()
@ -235,7 +258,7 @@ def test_OFParamStmt_conversion():
assert_equal(of.a, 0.1)
assert_equal(of.b, 1.0)
#No effect
# No effect
of.to_inch()
assert_equal(of.a, 0.1)
assert_equal(of.b, 1.0)
@ -244,7 +267,7 @@ def test_OFParamStmt_conversion():
of = OFParamStmt.from_dict(stmt)
of.units = 'inch'
#No effect
# No effect
of.to_inch()
assert_equal(of.a, 0.1)
assert_equal(of.b, 1.0)
@ -254,11 +277,12 @@ def test_OFParamStmt_conversion():
assert_equal(of.a, 2.54)
assert_equal(of.b, 25.4)
#No effect
# No effect
of.to_metric()
assert_equal(of.a, 2.54)
assert_equal(of.b, 25.4)
def test_OFParamStmt_offset():
s = OFParamStmt('OF', 0, 0)
s.offset(1, 0)
@ -268,6 +292,7 @@ def test_OFParamStmt_offset():
assert_equal(s.a, 1.)
assert_equal(s.b, 1.)
def test_OFParamStmt_string():
""" Test OFParamStmt __str__
"""
@ -275,6 +300,7 @@ def test_OFParamStmt_string():
of = OFParamStmt.from_dict(stmt)
assert_equal(str(of), '<Offset: X: 0.123456 Y: 0.123456 >')
def test_SFParamStmt_factory():
stmt = {'param': 'SF', 'a': '1.4', 'b': '0.9'}
sf = SFParamStmt.from_dict(stmt)
@ -282,18 +308,20 @@ def test_SFParamStmt_factory():
assert_equal(sf.a, 1.4)
assert_equal(sf.b, 0.9)
def test_SFParamStmt_dump():
stmt = {'param': 'SF', 'a': '1.4', 'b': '0.9'}
sf = SFParamStmt.from_dict(stmt)
assert_equal(sf.to_gerber(), '%SFA1.4B0.9*%')
def test_SFParamStmt_conversion():
stmt = {'param': 'OF', 'a': '2.54', 'b': '25.4'}
of = SFParamStmt.from_dict(stmt)
of.units = 'metric'
of.to_metric()
#No effect
# No effect
assert_equal(of.a, 2.54)
assert_equal(of.b, 25.4)
@ -302,7 +330,7 @@ def test_SFParamStmt_conversion():
assert_equal(of.a, 0.1)
assert_equal(of.b, 1.0)
#No effect
# No effect
of.to_inch()
assert_equal(of.a, 0.1)
assert_equal(of.b, 1.0)
@ -311,7 +339,7 @@ def test_SFParamStmt_conversion():
of = SFParamStmt.from_dict(stmt)
of.units = 'inch'
#No effect
# No effect
of.to_inch()
assert_equal(of.a, 0.1)
assert_equal(of.b, 1.0)
@ -321,11 +349,12 @@ def test_SFParamStmt_conversion():
assert_equal(of.a, 2.54)
assert_equal(of.b, 25.4)
#No effect
# No effect
of.to_metric()
assert_equal(of.a, 2.54)
assert_equal(of.b, 25.4)
def test_SFParamStmt_offset():
s = SFParamStmt('OF', 0, 0)
s.offset(1, 0)
@ -335,11 +364,13 @@ def test_SFParamStmt_offset():
assert_equal(s.a, 1.)
assert_equal(s.b, 1.)
def test_SFParamStmt_string():
stmt = {'param': 'SF', 'a': '1.4', 'b': '0.9'}
sf = SFParamStmt.from_dict(stmt)
assert_equal(str(sf), '<Scale Factor: X: 1.4 Y: 0.9>')
def test_LPParamStmt_factory():
""" Test LPParamStmt factory
"""
@ -351,6 +382,7 @@ def test_LPParamStmt_factory():
lp = LPParamStmt.from_dict(stmt)
assert_equal(lp.lp, 'dark')
def test_LPParamStmt_dump():
""" Test LPParamStmt to_gerber()
"""
@ -362,6 +394,7 @@ def test_LPParamStmt_dump():
lp = LPParamStmt.from_dict(stmt)
assert_equal(lp.to_gerber(), '%LPD*%')
def test_LPParamStmt_string():
""" Test LPParamStmt.__str__()
"""
@ -373,6 +406,7 @@ def test_LPParamStmt_string():
lp = LPParamStmt.from_dict(stmt)
assert_equal(str(lp), '<Level Polarity: clear>')
def test_AMParamStmt_factory():
name = 'DONUTVAR'
macro = (
@ -387,7 +421,7 @@ def test_AMParamStmt_factory():
7,0,0,7,6,0.2,0*
8,THIS IS AN UNSUPPORTED PRIMITIVE*
''')
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro})
s.build()
assert_equal(len(s.primitives), 10)
assert_true(isinstance(s.primitives[0], AMCommentPrimitive))
@ -401,15 +435,16 @@ def test_AMParamStmt_factory():
assert_true(isinstance(s.primitives[8], AMThermalPrimitive))
assert_true(isinstance(s.primitives[9], AMUnsupportPrimitive))
def testAMParamStmt_conversion():
name = 'POLYGON'
macro = '5,1,8,25.4,25.4,25.4,0*'
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro})
s.build()
s.units = 'metric'
#No effect
# No effect
s.to_metric()
assert_equal(s.primitives[0].position, (25.4, 25.4))
assert_equal(s.primitives[0].diameter, 25.4)
@ -419,17 +454,17 @@ def testAMParamStmt_conversion():
assert_equal(s.primitives[0].position, (1., 1.))
assert_equal(s.primitives[0].diameter, 1.)
#No effect
# No effect
s.to_inch()
assert_equal(s.primitives[0].position, (1., 1.))
assert_equal(s.primitives[0].diameter, 1.)
macro = '5,1,8,1,1,1,0*'
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro})
s.build()
s.units = 'inch'
#No effect
# No effect
s.to_inch()
assert_equal(s.primitives[0].position, (1., 1.))
assert_equal(s.primitives[0].diameter, 1.)
@ -439,15 +474,16 @@ def testAMParamStmt_conversion():
assert_equal(s.primitives[0].position, (25.4, 25.4))
assert_equal(s.primitives[0].diameter, 25.4)
#No effect
# No effect
s.to_metric()
assert_equal(s.primitives[0].position, (25.4, 25.4))
assert_equal(s.primitives[0].diameter, 25.4)
def test_AMParamStmt_dump():
name = 'POLYGON'
macro = '5,1,8,25.4,25.4,25.4,0.0'
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro})
s.build()
assert_equal(s.to_gerber(), '%AMPOLYGON*5,1,8,25.4,25.4,25.4,0.0*%')
@ -455,29 +491,34 @@ def test_AMParamStmt_dump():
s.build()
assert_equal(s.to_gerber(), '%AMOC8*5,1,8,0,0,1.08239X$1,22.5*%')
def test_AMParamStmt_string():
name = 'POLYGON'
macro = '5,1,8,25.4,25.4,25.4,0*'
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro})
s.build()
assert_equal(str(s), '<Aperture Macro POLYGON: 5,1,8,25.4,25.4,25.4,0*>')
def test_ASParamStmt_factory():
stmt = {'param': 'AS', 'mode': 'AXBY'}
s = ASParamStmt.from_dict(stmt)
assert_equal(s.param, 'AS')
assert_equal(s.mode, 'AXBY')
def test_ASParamStmt_dump():
stmt = {'param': 'AS', 'mode': 'AXBY'}
s = ASParamStmt.from_dict(stmt)
assert_equal(s.to_gerber(), '%ASAXBY*%')
def test_ASParamStmt_string():
stmt = {'param': 'AS', 'mode': 'AXBY'}
s = ASParamStmt.from_dict(stmt)
assert_equal(str(s), '<Axis Select: AXBY>')
def test_INParamStmt_factory():
""" Test INParamStmt factory
"""
@ -485,6 +526,7 @@ def test_INParamStmt_factory():
inp = INParamStmt.from_dict(stmt)
assert_equal(inp.name, 'test')
def test_INParamStmt_dump():
""" Test INParamStmt to_gerber()
"""
@ -492,11 +534,13 @@ def test_INParamStmt_dump():
inp = INParamStmt.from_dict(stmt)
assert_equal(inp.to_gerber(), '%INtest*%')
def test_INParamStmt_string():
stmt = {'param': 'IN', 'name': 'test'}
inp = INParamStmt.from_dict(stmt)
assert_equal(str(inp), '<Image Name: test>')
def test_LNParamStmt_factory():
""" Test LNParamStmt factory
"""
@ -504,6 +548,7 @@ def test_LNParamStmt_factory():
lnp = LNParamStmt.from_dict(stmt)
assert_equal(lnp.name, 'test')
def test_LNParamStmt_dump():
""" Test LNParamStmt to_gerber()
"""
@ -511,11 +556,13 @@ def test_LNParamStmt_dump():
lnp = LNParamStmt.from_dict(stmt)
assert_equal(lnp.to_gerber(), '%LNtest*%')
def test_LNParamStmt_string():
stmt = {'param': 'LN', 'name': 'test'}
lnp = LNParamStmt.from_dict(stmt)
assert_equal(str(lnp), '<Level Name: test>')
def test_comment_stmt():
""" Test comment statement
"""
@ -523,31 +570,37 @@ def test_comment_stmt():
assert_equal(stmt.type, 'COMMENT')
assert_equal(stmt.comment, 'A comment')
def test_comment_stmt_dump():
""" Test CommentStmt to_gerber()
"""
stmt = CommentStmt('A comment')
assert_equal(stmt.to_gerber(), 'G04A comment*')
def test_comment_stmt_string():
stmt = CommentStmt('A comment')
assert_equal(str(stmt), '<Comment: A comment>')
def test_eofstmt():
""" Test EofStmt
"""
stmt = EofStmt()
assert_equal(stmt.type, 'EOF')
def test_eofstmt_dump():
""" Test EofStmt to_gerber()
"""
stmt = EofStmt()
assert_equal(stmt.to_gerber(), 'M02*')
def test_eofstmt_string():
assert_equal(str(EofStmt()), '<EOF Statement>')
def test_quadmodestmt_factory():
""" Test QuadrantModeStmt.from_gerber()
"""
@ -560,6 +613,7 @@ def test_quadmodestmt_factory():
stmt = QuadrantModeStmt.from_gerber(line)
assert_equal(stmt.mode, 'multi-quadrant')
def test_quadmodestmt_validation():
""" Test QuadrantModeStmt input validation
"""
@ -567,6 +621,7 @@ def test_quadmodestmt_validation():
assert_raises(ValueError, QuadrantModeStmt.from_gerber, line)
assert_raises(ValueError, QuadrantModeStmt, 'quadrant-ful')
def test_quadmodestmt_dump():
""" Test QuadrantModeStmt.to_gerber()
"""
@ -574,6 +629,7 @@ def test_quadmodestmt_dump():
stmt = QuadrantModeStmt.from_gerber(line)
assert_equal(stmt.to_gerber(), line)
def test_regionmodestmt_factory():
""" Test RegionModeStmt.from_gerber()
"""
@ -586,6 +642,7 @@ def test_regionmodestmt_factory():
stmt = RegionModeStmt.from_gerber(line)
assert_equal(stmt.mode, 'off')
def test_regionmodestmt_validation():
""" Test RegionModeStmt input validation
"""
@ -593,6 +650,7 @@ def test_regionmodestmt_validation():
assert_raises(ValueError, RegionModeStmt.from_gerber, line)
assert_raises(ValueError, RegionModeStmt, 'off-ish')
def test_regionmodestmt_dump():
""" Test RegionModeStmt.to_gerber()
"""
@ -600,6 +658,7 @@ def test_regionmodestmt_dump():
stmt = RegionModeStmt.from_gerber(line)
assert_equal(stmt.to_gerber(), line)
def test_unknownstmt():
""" Test UnknownStmt
"""
@ -608,6 +667,7 @@ def test_unknownstmt():
assert_equal(stmt.type, 'UNKNOWN')
assert_equal(stmt.line, line)
def test_unknownstmt_dump():
""" Test UnknownStmt.to_gerber()
"""
@ -616,15 +676,17 @@ def test_unknownstmt_dump():
stmt = UnknownStmt(line)
assert_equal(stmt.to_gerber(), line)
def test_statement_string():
""" Test Statement.__str__()
"""
stmt = Statement('PARAM')
assert_in('type=PARAM', str(stmt))
stmt.test='PASS'
stmt.test = 'PASS'
assert_in('test=PASS', str(stmt))
assert_in('type=PARAM', str(stmt))
def test_ADParamStmt_factory():
""" Test ADParamStmt factory
"""
@ -656,12 +718,14 @@ def test_ADParamStmt_factory():
assert_equal(ad.shape, 'R')
assert_equal(ad.modifiers, [(1.42, 1.24)])
def test_ADParamStmt_conversion():
stmt = {'param': 'AD', 'd': 0, 'shape': 'C', 'modifiers': '25.4X25.4,25.4X25.4'}
stmt = {'param': 'AD', 'd': 0, 'shape': 'C',
'modifiers': '25.4X25.4,25.4X25.4'}
ad = ADParamStmt.from_dict(stmt)
ad.units = 'metric'
#No effect
# No effect
ad.to_metric()
assert_equal(ad.modifiers[0], (25.4, 25.4))
assert_equal(ad.modifiers[1], (25.4, 25.4))
@ -671,7 +735,7 @@ def test_ADParamStmt_conversion():
assert_equal(ad.modifiers[0], (1., 1.))
assert_equal(ad.modifiers[1], (1., 1.))
#No effect
# No effect
ad.to_inch()
assert_equal(ad.modifiers[0], (1., 1.))
assert_equal(ad.modifiers[1], (1., 1.))
@ -680,7 +744,7 @@ def test_ADParamStmt_conversion():
ad = ADParamStmt.from_dict(stmt)
ad.units = 'inch'
#No effect
# No effect
ad.to_inch()
assert_equal(ad.modifiers[0], (1., 1.))
assert_equal(ad.modifiers[1], (1., 1.))
@ -689,11 +753,12 @@ def test_ADParamStmt_conversion():
assert_equal(ad.modifiers[0], (25.4, 25.4))
assert_equal(ad.modifiers[1], (25.4, 25.4))
#No effect
# No effect
ad.to_metric()
assert_equal(ad.modifiers[0], (25.4, 25.4))
assert_equal(ad.modifiers[1], (25.4, 25.4))
def test_ADParamStmt_dump():
stmt = {'param': 'AD', 'd': 0, 'shape': 'C'}
ad = ADParamStmt.from_dict(stmt)
@ -702,6 +767,7 @@ def test_ADParamStmt_dump():
ad = ADParamStmt.from_dict(stmt)
assert_equal(ad.to_gerber(), '%ADD0C,1X1,1X1*%')
def test_ADPamramStmt_string():
stmt = {'param': 'AD', 'd': 0, 'shape': 'C'}
ad = ADParamStmt.from_dict(stmt)
@ -719,12 +785,14 @@ def test_ADPamramStmt_string():
ad = ADParamStmt.from_dict(stmt)
assert_equal(str(ad), '<Aperture Definition: 0: test>')
def test_MIParamStmt_factory():
stmt = {'param': 'MI', 'a': 1, 'b': 1}
mi = MIParamStmt.from_dict(stmt)
assert_equal(mi.a, 1)
assert_equal(mi.b, 1)
def test_MIParamStmt_dump():
stmt = {'param': 'MI', 'a': 1, 'b': 1}
mi = MIParamStmt.from_dict(stmt)
@ -736,6 +804,7 @@ def test_MIParamStmt_dump():
mi = MIParamStmt.from_dict(stmt)
assert_equal(mi.to_gerber(), '%MIA0B1*%')
def test_MIParamStmt_string():
stmt = {'param': 'MI', 'a': 1, 'b': 1}
mi = MIParamStmt.from_dict(stmt)
@ -749,6 +818,7 @@ def test_MIParamStmt_string():
mi = MIParamStmt.from_dict(stmt)
assert_equal(str(mi), '<Image Mirror: A=1 B=0>')
def test_coordstmt_ctor():
cs = CoordStmt('G04', 0.0, 0.1, 0.2, 0.3, 'D01', FileSettings())
assert_equal(cs.function, 'G04')
@ -758,8 +828,10 @@ def test_coordstmt_ctor():
assert_equal(cs.j, 0.3)
assert_equal(cs.op, 'D01')
def test_coordstmt_factory():
stmt = {'function': 'G04', 'x': '0', 'y': '001', 'i': '002', 'j': '003', 'op': 'D01'}
stmt = {'function': 'G04', 'x': '0', 'y': '001',
'i': '002', 'j': '003', 'op': 'D01'}
cs = CoordStmt.from_dict(stmt, FileSettings())
assert_equal(cs.function, 'G04')
assert_equal(cs.x, 0.0)
@ -768,15 +840,17 @@ def test_coordstmt_factory():
assert_equal(cs.j, 0.3)
assert_equal(cs.op, 'D01')
def test_coordstmt_dump():
cs = CoordStmt('G04', 0.0, 0.1, 0.2, 0.3, 'D01', FileSettings())
assert_equal(cs.to_gerber(FileSettings()), 'G04X0Y001I002J003D01*')
def test_coordstmt_conversion():
cs = CoordStmt('G71', 25.4, 25.4, 25.4, 25.4, 'D01', FileSettings())
cs.units = 'metric'
#No effect
# No effect
cs.to_metric()
assert_equal(cs.x, 25.4)
assert_equal(cs.y, 25.4)
@ -792,7 +866,7 @@ def test_coordstmt_conversion():
assert_equal(cs.j, 1.)
assert_equal(cs.function, 'G70')
#No effect
# No effect
cs.to_inch()
assert_equal(cs.x, 1.)
assert_equal(cs.y, 1.)
@ -803,7 +877,7 @@ def test_coordstmt_conversion():
cs = CoordStmt('G70', 1., 1., 1., 1., 'D01', FileSettings())
cs.units = 'inch'
#No effect
# No effect
cs.to_inch()
assert_equal(cs.x, 1.)
assert_equal(cs.y, 1.)
@ -818,7 +892,7 @@ def test_coordstmt_conversion():
assert_equal(cs.j, 25.4)
assert_equal(cs.function, 'G71')
#No effect
# No effect
cs.to_metric()
assert_equal(cs.x, 25.4)
assert_equal(cs.y, 25.4)
@ -826,6 +900,7 @@ def test_coordstmt_conversion():
assert_equal(cs.j, 25.4)
assert_equal(cs.function, 'G71')
def test_coordstmt_offset():
c = CoordStmt('G71', 0, 0, 0, 0, 'D01', FileSettings())
c.offset(1, 0)
@ -839,9 +914,11 @@ def test_coordstmt_offset():
assert_equal(c.i, 1.)
assert_equal(c.j, 1.)
def test_coordstmt_string():
cs = CoordStmt('G04', 0, 1, 2, 3, 'D01', FileSettings())
assert_equal(str(cs), '<Coordinate Statement: Fn: G04 X: 0 Y: 1 I: 2 J: 3 Op: Lights On>')
assert_equal(str(cs),
'<Coordinate Statement: Fn: G04 X: 0 Y: 1 I: 2 J: 3 Op: Lights On>')
cs = CoordStmt('G04', None, None, None, None, 'D02', FileSettings())
assert_equal(str(cs), '<Coordinate Statement: Fn: G04 Op: Lights Off>')
cs = CoordStmt('G04', None, None, None, None, 'D03', FileSettings())
@ -849,6 +926,7 @@ def test_coordstmt_string():
cs = CoordStmt('G04', None, None, None, None, 'TEST', FileSettings())
assert_equal(str(cs), '<Coordinate Statement: Fn: G04 Op: TEST>')
def test_aperturestmt_ctor():
ast = ApertureStmt(3, False)
assert_equal(ast.d, 3)
@ -863,11 +941,10 @@ def test_aperturestmt_ctor():
assert_equal(ast.d, 3)
assert_equal(ast.deprecated, False)
def test_aperturestmt_dump():
ast = ApertureStmt(3, False)
assert_equal(ast.to_gerber(), 'D3*')
ast = ApertureStmt(3, True)
assert_equal(ast.to_gerber(), 'G54D3*')
assert_equal(str(ast), '<Aperture: 3>')

Wyświetl plik

@ -2,18 +2,21 @@
# -*- coding: utf-8 -*-
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
from ..ipc356 import *
from ..ipc356 import *
from ..cam import FileSettings
from .tests import *
import os
IPC_D_356_FILE = os.path.join(os.path.dirname(__file__),
'resources/ipc-d-356.ipc')
'resources/ipc-d-356.ipc')
def test_read():
ipcfile = read(IPC_D_356_FILE)
assert(isinstance(ipcfile, IPC_D_356))
def test_parser():
ipcfile = read(IPC_D_356_FILE)
assert_equal(ipcfile.settings.units, 'inch')
@ -28,6 +31,7 @@ def test_parser():
assert_equal(set(ipcfile.outlines[0].points),
{(0., 0.), (2.25, 0.), (2.25, 1.5), (0., 1.5), (0.13, 0.024)})
def test_comment():
c = IPC356_Comment('Layer Stackup:')
assert_equal(c.comment, 'Layer Stackup:')
@ -36,6 +40,7 @@ def test_comment():
assert_raises(ValueError, IPC356_Comment.from_line, 'P JOB')
assert_equal(str(c), '<IPC-D-356 Comment: Layer Stackup:>')
def test_parameter():
p = IPC356_Parameter('VER', 'IPC-D-356A')
assert_equal(p.parameter, 'VER')
@ -43,27 +48,32 @@ def test_parameter():
p = IPC356_Parameter.from_line('P VER IPC-D-356A ')
assert_equal(p.parameter, 'VER')
assert_equal(p.value, 'IPC-D-356A')
assert_raises(ValueError, IPC356_Parameter.from_line, 'C Layer Stackup: ')
assert_raises(ValueError, IPC356_Parameter.from_line,
'C Layer Stackup: ')
assert_equal(str(p), '<IPC-D-356 Parameter: VER=IPC-D-356A>')
def test_eof():
e = IPC356_EndOfFile()
assert_equal(e.to_netlist(), '999')
assert_equal(str(e), '<IPC-D-356 EOF>')
def test_outline():
type = 'BOARD_EDGE'
points = [(0.01, 0.01), (2., 2.), (4., 2.), (4., 6.)]
b = IPC356_Outline(type, points)
assert_equal(b.type, type)
assert_equal(b.points, points)
b = IPC356_Outline.from_line('389BOARD_EDGE X100Y100 X20000Y20000'
' X40000 Y60000', FileSettings(units='inch'))
b = IPC356_Outline.from_line('389BOARD_EDGE X100Y100 X20000Y20000 X40000 Y60000',
FileSettings(units='inch'))
assert_equal(b.type, 'BOARD_EDGE')
assert_equal(b.points, points)
def test_test_record():
assert_raises(ValueError, IPC356_TestRecord.from_line, 'P JOB', FileSettings())
assert_raises(ValueError, IPC356_TestRecord.from_line,
'P JOB', FileSettings())
record_string = '317+5VDC VIA - D0150PA00X 006647Y 012900X0000 S3'
r = IPC356_TestRecord.from_line(record_string, FileSettings(units='inch'))
assert_equal(r.feature_type, 'through-hole')
@ -81,8 +91,7 @@ def test_test_record():
assert_almost_equal(r.x_coord, 6.647)
assert_almost_equal(r.y_coord, 12.9)
assert_equal(r.rect_x, 0.)
assert_equal(str(r),
'<IPC-D-356 +5VDC Test Record: through-hole>')
assert_equal(str(r), '<IPC-D-356 +5VDC Test Record: through-hole>')
record_string = '327+3.3VDC R40 -1 PA01X 032100Y 007124X0236Y0315R180 S0'
r = IPC356_TestRecord.from_line(record_string, FileSettings(units='inch'))
@ -98,13 +107,13 @@ def test_test_record():
assert_almost_equal(r.rect_y, 0.0315)
assert_equal(r.rect_rotation, 180)
assert_equal(r.soldermask_info, 'none')
r = IPC356_TestRecord.from_line(record_string, FileSettings(units='metric'))
r = IPC356_TestRecord.from_line(
record_string, FileSettings(units='metric'))
assert_almost_equal(r.x_coord, 32.1)
assert_almost_equal(r.y_coord, 7.124)
assert_almost_equal(r.rect_x, 0.236)
assert_almost_equal(r.rect_y, 0.315)
record_string = '317 J4 -M2 D0330PA00X 012447Y 008030X0000 S1'
r = IPC356_TestRecord.from_line(record_string, FileSettings(units='inch'))
assert_equal(r.feature_type, 'through-hole')

Wyświetl plik

@ -15,7 +15,7 @@ def test_guess_layer_class():
test_vectors = [(None, 'unknown'), ('NCDRILL.TXT', 'unknown'),
('example_board.gtl', 'top'),
('exampmle_board.sst', 'topsilk'),
('ipc-d-356.ipc', 'ipc_netlist'),]
('ipc-d-356.ipc', 'ipc_netlist'), ]
for hint in hints:
for ext in hint.ext:

Wyświetl plik

@ -9,31 +9,35 @@ from .tests import *
TOP_COPPER_FILE = os.path.join(os.path.dirname(__file__),
'resources/top_copper.GTL')
'resources/top_copper.GTL')
MULTILINE_READ_FILE = os.path.join(os.path.dirname(__file__),
'resources/multiline_read.ger')
'resources/multiline_read.ger')
def test_read():
top_copper = read(TOP_COPPER_FILE)
assert(isinstance(top_copper, GerberFile))
def test_multiline_read():
multiline = read(MULTILINE_READ_FILE)
assert(isinstance(multiline, GerberFile))
assert_equal(10, len(multiline.statements))
def test_comments_parameter():
top_copper = read(TOP_COPPER_FILE)
assert_equal(top_copper.comments[0], 'This is a comment,:')
def test_size_parameter():
top_copper = read(TOP_COPPER_FILE)
size = top_copper.size
assert_almost_equal(size[0], 2.256900, 6)
assert_almost_equal(size[1], 1.500000, 6)
def test_conversion():
import copy
top_copper = read(TOP_COPPER_FILE)
@ -50,4 +54,3 @@ def test_conversion():
for i, m in zip(top_copper.primitives, top_copper_inch.primitives):
assert_equal(i, m)

Wyświetl plik

@ -52,7 +52,7 @@ def test_format():
((2, 6), '-1', -0.000001), ((2, 5), '-1', -0.00001),
((2, 4), '-1', -0.0001), ((2, 3), '-1', -0.001),
((2, 2), '-1', -0.01), ((2, 1), '-1', -0.1),
((2, 6), '0', 0) ]
((2, 6), '0', 0)]
for fmt, string, value in test_cases:
assert_equal(value, parse_gerber_value(string, fmt, zero_suppression))
assert_equal(string, write_gerber_value(value, fmt, zero_suppression))
@ -76,7 +76,7 @@ def test_decimal_truncation():
value = 1.123456789
for x in range(10):
result = decimal_string(value, precision=x)
calculated = '1.' + ''.join(str(y) for y in range(1,x+1))
calculated = '1.' + ''.join(str(y) for y in range(1, x + 1))
assert_equal(result, calculated)
@ -96,25 +96,34 @@ def test_parse_format_validation():
"""
assert_raises(ValueError, parse_gerber_value, '00001111', (7, 5))
assert_raises(ValueError, parse_gerber_value, '00001111', (5, 8))
assert_raises(ValueError, parse_gerber_value, '00001111', (13,1))
assert_raises(ValueError, parse_gerber_value, '00001111', (13, 1))
def test_write_format_validation():
""" Test write_gerber_value() format validation
"""
assert_raises(ValueError, write_gerber_value, 69.0, (7, 5))
assert_raises(ValueError, write_gerber_value, 69.0, (5, 8))
assert_raises(ValueError, write_gerber_value, 69.0, (13,1))
assert_raises(ValueError, write_gerber_value, 69.0, (13, 1))
def test_detect_format_with_short_file():
""" Verify file format detection works with short files
"""
assert_equal('unknown', detect_file_format('gerber/tests/__init__.py'))
def test_validate_coordinates():
assert_raises(TypeError, validate_coordinates, 3)
assert_raises(TypeError, validate_coordinates, 3.1)
assert_raises(TypeError, validate_coordinates, '14')
assert_raises(TypeError, validate_coordinates, (0,))
assert_raises(TypeError, validate_coordinates, (0,1,2))
assert_raises(TypeError, validate_coordinates, (0,'string'))
assert_raises(TypeError, validate_coordinates, (0, 1, 2))
assert_raises(TypeError, validate_coordinates, (0, 'string'))
def test_convex_hull():
points = [(0, 0), (1, 0), (1, 1), (0.5, 0.5), (0, 1), (0, 0)]
expected = [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]
assert_equal(set(convex_hull(points)), set(expected))

Wyświetl plik

@ -16,7 +16,8 @@ from nose import with_setup
__all__ = ['assert_in', 'assert_not_in', 'assert_equal', 'assert_not_equal',
'assert_almost_equal', 'assert_array_almost_equal', 'assert_true',
'assert_false', 'assert_raises', 'raises', 'with_setup' ]
'assert_false', 'assert_raises', 'raises', 'with_setup']
def assert_array_almost_equal(arr1, arr2, decimal=6):
assert_equal(len(arr1), len(arr2))

Wyświetl plik

@ -23,15 +23,15 @@ This module provides utility functions for working with Gerber and Excellon
files.
"""
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
# License:
import os
from math import radians, sin, cos
from operator import sub
from copy import deepcopy
from pyhull.convex_hull import ConvexHull
MILLIMETERS_PER_INCH = 25.4
def parse_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
""" Convert gerber/excellon formatted string to floating-point number
@ -92,7 +92,8 @@ def parse_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
else:
digits = list(value)
result = float(''.join(digits[:integer_digits] + ['.'] + digits[integer_digits:]))
result = float(
''.join(digits[:integer_digits] + ['.'] + digits[integer_digits:]))
return -result if negative else result
@ -132,7 +133,8 @@ def write_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
raise ValueError('Parser only supports precision up to 6:7 format')
# Edge case... (per Gerber spec we should return 0 in all cases, see page 77)
# Edge case... (per Gerber spec we should return 0 in all cases, see page
# 77)
if value == 0:
return '0'
@ -222,7 +224,7 @@ def detect_file_format(data):
elif '%FS' in line:
return 'rs274x'
elif ((len(line.split()) >= 2) and
(line.split()[0] == 'P') and (line.split()[1] == 'JOB')):
(line.split()[0] == 'P') and (line.split()[1] == 'JOB')):
return 'ipc_d_356'
return 'unknown'
@ -252,6 +254,7 @@ def metric(value):
"""
return value * MILLIMETERS_PER_INCH
def inch(value):
""" Convert millimeter value to inches
@ -310,6 +313,26 @@ def sq_distance(point1, point2):
def listdir(directory, ignore_hidden=True, ignore_os=True):
""" List files in given directory.
Differs from os.listdir() in that hidden and OS-generated files are ignored
by default.
Parameters
----------
directory : str
path to the directory for which to list files.
ignore_hidden : bool
If True, ignore files beginning with a leading '.'
ignore_os : bool
If True, ignore OS-generated files, e.g. Thumbs.db
Returns
-------
files : list
list of files in specified directory
"""
os_files = ('.DS_Store', 'Thumbs.db', 'ethumbs.db')
files = os.listdir(directory)
if ignore_hidden:
@ -317,3 +340,9 @@ def listdir(directory, ignore_hidden=True, ignore_os=True):
if ignore_os:
files = [f for f in files if not f in os_files]
return files
def convex_hull(points):
vertices = ConvexHull(points).vertices
return [points[idx] for idx in
set([point for pair in vertices for point in pair])]

Wyświetl plik

@ -1,2 +1,3 @@
## The following requirements were added by pip --freeze:
cairocffi==0.6
pyhull==1.5.6