Commit partial merge so I can work on the plane
This commit is contained in:
parent
8d5e782ccf
commit
5af19af190
11 changed files with 257 additions and 117 deletions
|
|
@ -19,10 +19,13 @@
|
|||
from math import asin
|
||||
import math
|
||||
|
||||
from .primitives import *
|
||||
from .primitives import Circle, Line, Outline, Polygon, Rectangle
|
||||
from .utils import validate_coordinates, inch, metric
|
||||
from .utils import validate_coordinates, inch, metric, rotate_point
|
||||
|
||||
|
||||
|
||||
# TODO: Add support for aperture macro variables
|
||||
__all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive',
|
||||
'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive',
|
||||
|
|
|
|||
|
|
@ -267,8 +267,7 @@ class CamFile(object):
|
|||
filename : string <optional>
|
||||
If provided, save the rendered image to `filename`
|
||||
"""
|
||||
|
||||
ctx.set_bounds(self.bounding_box)
|
||||
ctx.set_bounds(self.bounds)
|
||||
ctx._paint_background()
|
||||
ctx.invert = invert
|
||||
ctx._new_render_layer()
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ def loads(data, settings = None, tools = None):
|
|||
settings = FileSettings(**detect_excellon_format(data))
|
||||
return ExcellonParser(settings, tools).parse_raw(data)
|
||||
|
||||
|
||||
|
||||
class DrillHit(object):
|
||||
"""Drill feature that is a single drill hole.
|
||||
|
||||
|
|
@ -91,8 +91,7 @@ class DrillHit(object):
|
|||
position : tuple(float, float)
|
||||
Center position of the drill.
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
def __init__(self, tool, position):
|
||||
self.tool = tool
|
||||
self.position = position
|
||||
|
|
@ -194,7 +193,7 @@ class ExcellonFile(CamFile):
|
|||
self.hits = hits
|
||||
|
||||
@property
|
||||
def primitives(self):
|
||||
def primitives(self):
|
||||
"""
|
||||
Gets the primitives. Note that unlike Gerber, this generates new objects
|
||||
"""
|
||||
|
|
@ -262,7 +261,7 @@ class ExcellonFile(CamFile):
|
|||
for hit in self.hits:
|
||||
if hit.tool.number == tool.number:
|
||||
f.write(CoordinateStmt(
|
||||
*hit.position).to_excellon(self.settings) + '\n')
|
||||
*hit.position).to_excellon(self.settings) + '\n')
|
||||
f.write(EndOfProgramStmt().to_excellon() + '\n')
|
||||
|
||||
def to_inch(self):
|
||||
|
|
@ -276,7 +275,7 @@ class ExcellonFile(CamFile):
|
|||
for tool in iter(self.tools.values()):
|
||||
tool.to_inch()
|
||||
for primitive in self.primitives:
|
||||
primitive.to_inch()
|
||||
primitive.to_inch()
|
||||
for hit in self.hits:
|
||||
hit.to_inch()
|
||||
|
||||
|
|
@ -298,7 +297,7 @@ class ExcellonFile(CamFile):
|
|||
for statement in self.statements:
|
||||
statement.offset(x_offset, y_offset)
|
||||
for primitive in self.primitives:
|
||||
primitive.offset(x_offset, y_offset)
|
||||
primitive.offset(x_offset, y_offset)
|
||||
for hit in self. hits:
|
||||
hit.offset(x_offset, y_offset)
|
||||
|
||||
|
|
@ -359,7 +358,7 @@ class ExcellonParser(object):
|
|||
Parameters
|
||||
----------
|
||||
settings : FileSettings or dict-like
|
||||
Excellon file settings to use when interpreting the excellon file.
|
||||
Excellon file settings to use when interpreting the excellon file.
|
||||
"""
|
||||
def __init__(self, settings=None, ext_tools=None):
|
||||
self.notation = 'absolute'
|
||||
|
|
@ -614,12 +613,12 @@ class ExcellonParser(object):
|
|||
stmt = ToolSelectionStmt.from_excellon(line)
|
||||
self.statements.append(stmt)
|
||||
|
||||
# T0 is used as END marker, just ignore
|
||||
# T0 is used as END marker, just ignore
|
||||
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 gerb
|
||||
if self._settings().units == "inch":
|
||||
diameter = (16 + 8 * stmt.tool) / 1000.0
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -337,7 +337,8 @@ class ADParamStmt(ParamStmt):
|
|||
if isinstance(modifiers, tuple):
|
||||
self.modifiers = modifiers
|
||||
elif modifiers:
|
||||
self.modifiers = [tuple([float(x) for x in m.split("X") if len(x)]) for m in modifiers.split(",") if len(m)]
|
||||
self.modifiers = [tuple([float(x) for x in m.split("X") if len(x)])
|
||||
for m in modifiers.split(",") if len(m)]
|
||||
else:
|
||||
self.modifiers = [tuple()]
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
# limitations under the License.
|
||||
|
||||
|
||||
from itertools import combinations
|
||||
|
||||
import math
|
||||
from operator import add
|
||||
|
||||
from .utils import validate_coordinates, inch, metric, convex_hull, rotate_point, nearly_equal
|
||||
from itertools import combinations
|
||||
from .utils import validate_coordinates, inch, metric, convex_hull, rotate_point, nearly_equal
|
||||
|
||||
|
||||
|
||||
|
|
@ -50,9 +50,9 @@ class Primitive(object):
|
|||
|
||||
def __init__(self, level_polarity='dark', rotation=0, units=None, net_name=None):
|
||||
self.level_polarity = level_polarity
|
||||
self.net_name = net_name
|
||||
self.net_name = net_name
|
||||
self._to_convert = list()
|
||||
self.id = id
|
||||
self.id = id
|
||||
self._memoized = list()
|
||||
self._units = units
|
||||
self._rotation = rotation
|
||||
|
|
@ -60,18 +60,21 @@ class Primitive(object):
|
|||
self._sin_theta = math.sin(math.radians(rotation))
|
||||
self._bounding_box = None
|
||||
self._vertices = None
|
||||
self._segments = None
|
||||
self._segments = None
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
'''Is this a flashed primitive'''
|
||||
|
||||
raise NotImplementedError('Is flashed must be '
|
||||
'implemented in subclass')
|
||||
'implemented in subclass')
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self._units
|
||||
return self._units
|
||||
|
||||
@units.setter
|
||||
def units(self, value):
|
||||
|
|
@ -81,7 +84,7 @@ class Primitive(object):
|
|||
@property
|
||||
def rotation(self):
|
||||
return self._rotation
|
||||
|
||||
|
||||
@rotation.setter
|
||||
def rotation(self, value):
|
||||
self._changed()
|
||||
|
|
@ -172,8 +175,8 @@ class Primitive(object):
|
|||
except:
|
||||
if value is not None:
|
||||
setattr(self, attr, metric(value))
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
""" Move the primitive by the specified x and y offset amount.
|
||||
|
||||
values are specified in the primitive's native units
|
||||
|
|
@ -183,10 +186,7 @@ class Primitive(object):
|
|||
self.position = tuple([coord + offset for coord, offset
|
||||
in zip(self.position,
|
||||
(x_offset, y_offset))])
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
|
||||
def to_statement(self):
|
||||
pass
|
||||
|
||||
|
|
@ -201,9 +201,8 @@ class Primitive(object):
|
|||
self._bounding_box = None
|
||||
self._vertices = None
|
||||
self._segments = None
|
||||
for attr in self._memoized:
|
||||
setattr(self, attr, None)
|
||||
|
||||
for attr in self._memoized:
|
||||
setattr(self, attr, None)
|
||||
|
||||
class Line(Primitive):
|
||||
"""
|
||||
|
|
@ -238,7 +237,6 @@ class Line(Primitive):
|
|||
self._changed()
|
||||
self._end = value
|
||||
|
||||
|
||||
@property
|
||||
def angle(self):
|
||||
delta_x, delta_y = tuple(
|
||||
|
|
@ -246,7 +244,7 @@ class Line(Primitive):
|
|||
angle = math.atan2(delta_y, delta_x)
|
||||
return angle
|
||||
|
||||
@property
|
||||
@property
|
||||
def bounding_box(self):
|
||||
if self._bounding_box is None:
|
||||
if isinstance(self.aperture, Circle):
|
||||
|
|
@ -261,7 +259,7 @@ class Line(Primitive):
|
|||
max_y = max(self.start[1], self.end[1]) + height_2
|
||||
self._bounding_box = ((min_x, max_x), (min_y, max_y))
|
||||
return self._bounding_box
|
||||
|
||||
|
||||
@property
|
||||
def bounding_box_no_aperture(self):
|
||||
'''Gets the bounding box without the aperture'''
|
||||
|
|
@ -293,13 +291,13 @@ class Line(Primitive):
|
|||
# The line is defined by the convex hull of the points
|
||||
self._vertices = convex_hull((start_ll, start_lr, start_ul, start_ur, end_ll, end_lr, end_ul, end_ur))
|
||||
return self._vertices
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
self._changed()
|
||||
self.start = tuple([coord + offset for coord, offset
|
||||
in zip(self.start, (x_offset, y_offset))])
|
||||
self.end = tuple([coord + offset for coord, offset
|
||||
in zip(self.end, (x_offset, y_offset))])
|
||||
self._changed()
|
||||
|
||||
def equivalent(self, other, offset):
|
||||
|
||||
|
|
@ -308,12 +306,14 @@ class Line(Primitive):
|
|||
|
||||
equiv_start = tuple(map(add, other.start, offset))
|
||||
equiv_end = tuple(map(add, other.end, offset))
|
||||
|
||||
|
||||
return nearly_equal(self.start, equiv_start) and nearly_equal(self.end, equiv_end)
|
||||
|
||||
class Arc(Primitive):
|
||||
"""
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, start, end, center, direction, aperture, quadrant_mode, **kwargs):
|
||||
super(Arc, self).__init__(**kwargs)
|
||||
self._start = start
|
||||
|
|
@ -436,7 +436,7 @@ class Arc(Primitive):
|
|||
min_y = min(y) - self.aperture.radius
|
||||
max_y = max(y) + self.aperture.radius
|
||||
self._bounding_box = ((min_x, max_x), (min_y, max_y))
|
||||
return self._bounding_box
|
||||
return self._bounding_box
|
||||
|
||||
@property
|
||||
def bounding_box_no_aperture(self):
|
||||
|
|
@ -488,12 +488,13 @@ class Arc(Primitive):
|
|||
|
||||
class Circle(Primitive):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, position, diameter, hole_diameter = None, **kwargs):
|
||||
"""
|
||||
|
||||
def __init__(self, position, diameter, hole_diameter = None, **kwargs):
|
||||
super(Circle, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._diameter = diameter
|
||||
self._diameter = diameter
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'diameter', 'hole_diameter']
|
||||
|
||||
|
|
@ -537,7 +538,7 @@ class Circle(Primitive):
|
|||
min_y = self.position[1] - self.radius
|
||||
max_y = self.position[1] + self.radius
|
||||
self._bounding_box = ((min_x, max_x), (min_y, max_y))
|
||||
return self._bounding_box
|
||||
return self._bounding_box
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
self.position = tuple(map(add, self.position, (x_offset, y_offset)))
|
||||
|
|
@ -553,7 +554,7 @@ class Circle(Primitive):
|
|||
|
||||
equiv_position = tuple(map(add, other.position, offset))
|
||||
|
||||
return nearly_equal(self.position, equiv_position)
|
||||
return nearly_equal(self.position, equiv_position)
|
||||
|
||||
|
||||
class Ellipse(Primitive):
|
||||
|
|
@ -575,7 +576,7 @@ class Ellipse(Primitive):
|
|||
@property
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
|
||||
@position.setter
|
||||
def position(self, value):
|
||||
self._changed()
|
||||
|
|
@ -625,18 +626,19 @@ class Ellipse(Primitive):
|
|||
|
||||
|
||||
class Rectangle(Primitive):
|
||||
"""
|
||||
"""
|
||||
When rotated, the rotation is about the center point.
|
||||
|
||||
Only aperture macro generated Rectangle objects can be rotated. If you aren't in a AMGroup,
|
||||
then you don't need to worry about rotation
|
||||
"""
|
||||
def __init__(self, position, width, height, hole_diameter=0, **kwargs):
|
||||
"""
|
||||
|
||||
def __init__(self, position, width, height, hole_diameter=0, **kwargs):
|
||||
super(Rectangle, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._height = height
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'width', 'height', 'hole_diameter']
|
||||
# TODO These are probably wrong when rotated
|
||||
|
|
@ -656,14 +658,14 @@ class Rectangle(Primitive):
|
|||
self._changed()
|
||||
self._position = value
|
||||
|
||||
@property
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@width.setter
|
||||
def width(self, value):
|
||||
self._changed()
|
||||
self._width = value
|
||||
self._width = value
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
|
|
@ -685,7 +687,7 @@ class Rectangle(Primitive):
|
|||
def upper_right(self):
|
||||
return (self.position[0] + (self._abs_width / 2.),
|
||||
self.position[1] + (self._abs_height / 2.))
|
||||
|
||||
|
||||
@property
|
||||
def lower_left(self):
|
||||
return (self.position[0] - (self.axis_aligned_width / 2.),
|
||||
|
|
@ -765,7 +767,7 @@ class Diamond(Primitive):
|
|||
@position.setter
|
||||
def position(self, value):
|
||||
self._changed()
|
||||
self._position = value
|
||||
self._position = value
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
|
|
@ -776,7 +778,7 @@ class Diamond(Primitive):
|
|||
self._changed()
|
||||
self._width = value
|
||||
|
||||
@property
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
|
|
@ -950,7 +952,7 @@ class RoundRectangle(Primitive):
|
|||
@height.setter
|
||||
def height(self, value):
|
||||
self._changed()
|
||||
self._height = value
|
||||
self._height = value
|
||||
|
||||
@property
|
||||
def radius(self):
|
||||
|
|
@ -985,21 +987,22 @@ class RoundRectangle(Primitive):
|
|||
return (self._cos_theta * self.width +
|
||||
self._sin_theta * self.height)
|
||||
|
||||
@property
|
||||
@property
|
||||
def axis_aligned_height(self):
|
||||
return (self._cos_theta * self.height +
|
||||
self._sin_theta * self.width)
|
||||
|
||||
|
||||
class Obround(Primitive):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, position, width, height, hole_diameter=0, **kwargs):
|
||||
"""
|
||||
|
||||
def __init__(self, position, width, height, hole_diameter=0, **kwargs):
|
||||
super(Obround, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._height = height
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'width', 'height', 'hole_diameter']
|
||||
|
||||
|
|
@ -1014,7 +1017,7 @@ class Obround(Primitive):
|
|||
@position.setter
|
||||
def position(self, value):
|
||||
self._changed()
|
||||
self._position = value
|
||||
self._position = value
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
|
|
@ -1030,7 +1033,7 @@ class Obround(Primitive):
|
|||
return (self.position[0] + (self._abs_width / 2.),
|
||||
self.position[1] + (self._abs_height / 2.))
|
||||
|
||||
@property
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
|
|
@ -1093,7 +1096,7 @@ class Obround(Primitive):
|
|||
|
||||
|
||||
class Polygon(Primitive):
|
||||
"""
|
||||
"""
|
||||
Polygon flash defined by a set number of sides.
|
||||
"""
|
||||
def __init__(self, position, sides, radius, hole_diameter, **kwargs):
|
||||
|
|
@ -1126,7 +1129,7 @@ class Polygon(Primitive):
|
|||
@position.setter
|
||||
def position(self, value):
|
||||
self._changed()
|
||||
self._position = value
|
||||
self._position = value
|
||||
|
||||
@property
|
||||
def radius(self):
|
||||
|
|
@ -1162,6 +1165,18 @@ class Polygon(Primitive):
|
|||
|
||||
return points
|
||||
|
||||
@property
|
||||
def vertices(self):
|
||||
if self._vertices is None:
|
||||
theta = math.radians(360/self.sides)
|
||||
vertices = [(self.position[0] + (math.cos(theta * side) * self.radius),
|
||||
self.position[1] + (math.sin(theta * side) * self.radius))
|
||||
for side in range(self.sides)]
|
||||
self._vertices = [(((x * self._cos_theta) - (y * self._sin_theta)),
|
||||
((x * self._sin_theta) + (y * self._cos_theta)))
|
||||
for x, y in vertices]
|
||||
return self._vertices
|
||||
|
||||
def equivalent(self, other, offset):
|
||||
"""
|
||||
Is this the outline the same as the other, ignoring the position offset?
|
||||
|
|
@ -1170,7 +1185,7 @@ class Polygon(Primitive):
|
|||
# Quick check if it even makes sense to compare them
|
||||
if type(self) != type(other) or self.sides != other.sides or self.radius != other.radius:
|
||||
return False
|
||||
|
||||
|
||||
equiv_pos = tuple(map(add, other.position, offset))
|
||||
|
||||
return nearly_equal(self.position, equiv_pos)
|
||||
|
|
@ -1178,7 +1193,7 @@ class Polygon(Primitive):
|
|||
|
||||
class AMGroup(Primitive):
|
||||
"""
|
||||
"""
|
||||
"""
|
||||
def __init__(self, amprimitives, stmt = None, **kwargs):
|
||||
"""
|
||||
|
||||
|
|
@ -1281,6 +1296,7 @@ class Outline(Primitive):
|
|||
Outlines only exist as the rendering for a apeture macro outline.
|
||||
They don't exist outside of AMGroup objects
|
||||
"""
|
||||
|
||||
def __init__(self, primitives, **kwargs):
|
||||
super(Outline, self).__init__(**kwargs)
|
||||
self.primitives = primitives
|
||||
|
|
@ -1295,16 +1311,19 @@ class Outline(Primitive):
|
|||
|
||||
@property
|
||||
def bounding_box(self):
|
||||
xlims, ylims = zip(*[p.bounding_box for p in self.primitives])
|
||||
minx, maxx = zip(*xlims)
|
||||
miny, maxy = zip(*ylims)
|
||||
min_x = min(minx)
|
||||
max_x = max(maxx)
|
||||
min_y = min(miny)
|
||||
max_y = max(maxy)
|
||||
return ((min_x, max_x), (min_y, max_y))
|
||||
if self._bounding_box is None:
|
||||
xlims, ylims = zip(*[p.bounding_box for p in self.primitives])
|
||||
minx, maxx = zip(*xlims)
|
||||
miny, maxy = zip(*ylims)
|
||||
min_x = min(minx)
|
||||
max_x = max(maxx)
|
||||
min_y = min(miny)
|
||||
max_y = max(maxy)
|
||||
self._bounding_box = ((min_x, max_x), (min_y, max_y))
|
||||
return self._bounding_box
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
self._changed()
|
||||
for p in self.primitives:
|
||||
p.offset(x_offset, y_offset)
|
||||
|
||||
|
|
@ -1416,11 +1435,11 @@ class SquareButterfly(Primitive):
|
|||
self._to_convert = ['position', 'side']
|
||||
|
||||
# TODO This does not reset bounding box correctly
|
||||
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
return True
|
||||
|
||||
|
||||
@property
|
||||
def bounding_box(self):
|
||||
if self._bounding_box is None:
|
||||
|
|
@ -1456,9 +1475,10 @@ class Donut(Primitive):
|
|||
else:
|
||||
# Hexagon
|
||||
self.width = 0.5 * math.sqrt(3.) * outer_diameter
|
||||
self.height = outer_diameter
|
||||
self.height = outer_diameter
|
||||
|
||||
self._to_convert = ['position', 'width', 'height', 'inner_diameter', 'outer_diameter']
|
||||
self._to_convert = ['position', 'width',
|
||||
'height', 'inner_diameter', 'outer_diameter']
|
||||
|
||||
# TODO This does not reset bounding box correctly
|
||||
|
||||
|
|
@ -1474,7 +1494,7 @@ class Donut(Primitive):
|
|||
@property
|
||||
def upper_right(self):
|
||||
return (self.position[0] + (self.width / 2.),
|
||||
self.position[1] + (self.height / 2.))
|
||||
self.position[1] + (self.height / 2.)
|
||||
|
||||
@property
|
||||
def bounding_box(self):
|
||||
|
|
@ -1526,11 +1546,13 @@ class Drill(Primitive):
|
|||
self.hit = hit
|
||||
self._to_convert = ['position', 'diameter', 'hit']
|
||||
|
||||
# TODO Ths won't handle the hit updates correctly
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
@property
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
|
|
@ -1588,19 +1610,13 @@ class Slot(Primitive):
|
|||
@property
|
||||
def flashed(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def radius(self):
|
||||
return self.diameter / 2.
|
||||
|
||||
@property
|
||||
|
||||
def bounding_box(self):
|
||||
radius = self.radius
|
||||
min_x = min(self.start[0], self.end[0]) - radius
|
||||
max_x = max(self.start[0], self.end[0]) + radius
|
||||
min_y = min(self.start[1], self.end[1]) - radius
|
||||
max_y = max(self.start[1], self.end[1]) + radius
|
||||
return ((min_x, max_x), (min_y, max_y))
|
||||
if self._bounding_box is None:
|
||||
ll = tuple([c - self.outer_diameter / 2. for c in self.position])
|
||||
ur = tuple([c + self.outer_diameter / 2. for c in self.position])
|
||||
self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
|
||||
return self._bounding_box
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
self.start = tuple(map(add, self.start, (x_offset, y_offset)))
|
||||
|
|
|
|||
|
|
@ -12,6 +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
|
||||
# limitations under the License.
|
||||
|
||||
|
|
@ -22,14 +23,14 @@ except ImportError:
|
|||
|
||||
import math
|
||||
from operator import mul, div
|
||||
|
||||
import tempfile
|
||||
|
||||
import cairocffi as cairo
|
||||
|
||||
from ..primitives import *
|
||||
from .render import GerberContext, RenderSettings
|
||||
from .theme import THEMES
|
||||
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except(ImportError):
|
||||
|
|
@ -138,20 +139,35 @@ class GerberCairoContext(GerberContext):
|
|||
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:
|
||||
<<<<<<< HEAD
|
||||
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)
|
||||
=======
|
||||
self.ctx.set_source_rgba(*color, alpha=self.alpha)
|
||||
self.ctx.set_operator(cairo.OPERATOR_OVER
|
||||
if line.level_polarity == 'dark'
|
||||
else cairo.OPERATOR_CLEAR)
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
else:
|
||||
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):
|
||||
<<<<<<< HEAD
|
||||
width = line.aperture.diameter
|
||||
=======
|
||||
width = line.aperture.diameter
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
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)
|
||||
<<<<<<< HEAD
|
||||
self.ctx.stroke()
|
||||
=======
|
||||
self.ctx.stroke()
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
elif isinstance(line.aperture, Rectangle):
|
||||
points = [self.scale_point(x) for x in line.vertices]
|
||||
self.ctx.set_line_width(0)
|
||||
|
|
@ -176,6 +192,7 @@ class GerberCairoContext(GerberContext):
|
|||
width = max(arc.aperture.width, arc.aperture.height, 0.001)
|
||||
|
||||
if not self.invert:
|
||||
<<<<<<< HEAD
|
||||
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"\
|
||||
|
|
@ -184,25 +201,50 @@ class GerberCairoContext(GerberContext):
|
|||
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
|
||||
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
|
||||
|
||||
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':
|
||||
self.ctx.arc(center[0], center[1], radius, angle1, angle2)
|
||||
else:
|
||||
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:
|
||||
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
|
||||
=======
|
||||
self.ctx.set_source_rgba(*color, alpha=self.alpha)
|
||||
self.ctx.set_operator(cairo.OPERATOR_OVER
|
||||
if region.level_polarity == "dark"
|
||||
if arc.level_polarity == 'dark'
|
||||
else cairo.OPERATOR_CLEAR)
|
||||
else:
|
||||
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
|
||||
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
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':
|
||||
<<<<<<< HEAD
|
||||
self.ctx.arc(center[0], center[1], radius, angle1, angle2)
|
||||
else:
|
||||
self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2)
|
||||
=======
|
||||
self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2)
|
||||
else:
|
||||
self.ctx.arc_negative(*center, radius=radius,
|
||||
angle1=angle1, angle2=angle2)
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
self.ctx.move_to(*end) # ...lame
|
||||
|
||||
def _render_region(self, region, color):
|
||||
if not self.invert:
|
||||
<<<<<<< HEAD
|
||||
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"
|
||||
=======
|
||||
self.ctx.set_source_rgba(*color, alpha=self.alpha)
|
||||
self.ctx.set_operator(cairo.OPERATOR_OVER
|
||||
if region.level_polarity == 'dark'
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
else cairo.OPERATOR_CLEAR)
|
||||
else:
|
||||
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
|
||||
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
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))
|
||||
|
|
@ -220,6 +262,7 @@ class GerberCairoContext(GerberContext):
|
|||
else:
|
||||
self.ctx.arc_negative(*center, radius=radius,
|
||||
angle1=angle1, angle2=angle2)
|
||||
<<<<<<< HEAD
|
||||
self.ctx.fill()
|
||||
def _render_circle(self, circle, color):
|
||||
center = self.scale_point(circle.position)
|
||||
|
|
@ -249,10 +292,28 @@ class GerberCairoContext(GerberContext):
|
|||
|
||||
self.ctx.pop_group_to_source()
|
||||
self.ctx.paint_with_alpha(1)
|
||||
=======
|
||||
self.ctx.fill()
|
||||
|
||||
def _render_circle(self, circle, color):
|
||||
center = self.scale_point(circle.position)
|
||||
if not self.invert:
|
||||
self.ctx.set_source_rgba(*color, alpha=self.alpha)
|
||||
self.ctx.set_operator(
|
||||
cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR)
|
||||
else:
|
||||
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
|
||||
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
|
||||
self.ctx.set_line_width(0)
|
||||
self.ctx.arc(*center, radius=circle.radius *
|
||||
self.scale[0], angle1=0, angle2=2 * math.pi)
|
||||
self.ctx.fill()
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
|
||||
def _render_rectangle(self, rectangle, color):
|
||||
lower_left = self.scale_point(rectangle.lower_left)
|
||||
width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))])
|
||||
<<<<<<< HEAD
|
||||
|
||||
if not self.invert:
|
||||
self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha)
|
||||
|
|
@ -295,6 +356,19 @@ class GerberCairoContext(GerberContext):
|
|||
|
||||
if rectangle.rotation != 0:
|
||||
self.ctx.restore()
|
||||
=======
|
||||
|
||||
if not self.invert:
|
||||
self.ctx.set_source_rgba(*color, alpha=self.alpha)
|
||||
self.ctx.set_operator(
|
||||
cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR)
|
||||
else:
|
||||
self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
|
||||
self.ctx.set_operator(cairo.OPERATOR_CLEAR)
|
||||
self.ctx.set_line_width(0)
|
||||
self.ctx.rectangle(*lower_left, width=width, height=height)
|
||||
self.ctx.fill()
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
|
||||
def _render_obround(self, obround, color):
|
||||
|
||||
|
|
@ -424,7 +498,11 @@ class GerberCairoContext(GerberContext):
|
|||
|
||||
def _flatten(self):
|
||||
self.output_ctx.set_operator(cairo.OPERATOR_OVER)
|
||||
<<<<<<< HEAD
|
||||
ptn = cairo.SurfacePattern(self.active_layer)
|
||||
=======
|
||||
ptn = cairo.SurfacePattern(self.active_layer)
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
ptn.set_matrix(self._xform_matrix)
|
||||
self.output_ctx.set_source(ptn)
|
||||
self.output_ctx.paint()
|
||||
|
|
@ -435,8 +513,16 @@ class GerberCairoContext(GerberContext):
|
|||
if (not self.bg) or force:
|
||||
self.bg = True
|
||||
self.output_ctx.set_operator(cairo.OPERATOR_OVER)
|
||||
<<<<<<< HEAD
|
||||
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)])
|
||||
return tuple([coord * scale for coord, scale in zip(point, self.scale)])
|
||||
=======
|
||||
self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0)
|
||||
self.output_ctx.paint()
|
||||
|
||||
def scale_point(self, point):
|
||||
return tuple([coord * scale for coord, scale in zip(point, self.scale)])
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ class GerberContext(object):
|
|||
return
|
||||
|
||||
|
||||
|
||||
def _render_line(self, primitive, color):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ class GerberFile(CamFile):
|
|||
`bounds` is stored as ((min x, max x), (min y, max y))
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, statements, settings, primitives, apertures, filename=None):
|
||||
super(GerberFile, self).__init__(statements, settings, primitives, filename)
|
||||
|
||||
|
|
@ -568,7 +569,7 @@ class GerberParser(object):
|
|||
self.interpolation = 'arc'
|
||||
self.direction = ('clockwise' if stmt.function in
|
||||
('G02', 'G2') else 'counterclockwise')
|
||||
|
||||
|
||||
if stmt.only_function:
|
||||
# Sometimes we get a coordinate statement
|
||||
# that only sets the function. If so, don't
|
||||
|
|
@ -594,7 +595,6 @@ class GerberParser(object):
|
|||
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.
|
||||
|
|
@ -616,12 +616,24 @@ class GerberParser(object):
|
|||
j = 0 if stmt.j is None else stmt.j
|
||||
center = self._find_center(start, end, (i, j))
|
||||
if self.region_mode == 'off':
|
||||
self.primitives.append(Arc(start, end, center, self.direction, self.apertures[self.aperture], quadrant_mode=self.quadrant_mode, level_polarity=self.level_polarity, units=self.settings.units))
|
||||
self.primitives.append(Arc(start, end, center, self.direction,
|
||||
self.apertures[self.aperture],
|
||||
quadrant_mode=self.quadrant_mode,
|
||||
level_polarity=self.level_polarity,
|
||||
units=self.settings.units))
|
||||
else:
|
||||
if self.current_region is None:
|
||||
self.current_region = [Arc(start, end, center, self.direction, self.apertures.get(self.aperture, Circle((0,0), 0)), quadrant_mode=self.quadrant_mode, level_polarity=self.level_polarity, units=self.settings.units),]
|
||||
self.current_region = [Arc(start, end, center, self.direction,
|
||||
self.apertures.get(self.aperture, Circle((0,0), 0)),
|
||||
quadrant_mode=self.quadrant_mode,
|
||||
level_polarity=self.level_polarity,
|
||||
units=self.settings.units),]
|
||||
else:
|
||||
self.current_region.append(Arc(start, end, center, self.direction, self.apertures.get(self.aperture, Circle((0,0), 0)), quadrant_mode=self.quadrant_mode, level_polarity=self.level_polarity, units=self.settings.units))
|
||||
self.current_region.append(Arc(start, end, center, self.direction,
|
||||
self.apertures.get(self.aperture, Circle((0,0), 0)),
|
||||
quadrant_mode=self.quadrant_mode,
|
||||
level_polarity=self.level_polarity,
|
||||
units=self.settings.units))
|
||||
|
||||
elif self.op == "D02" or self.op == "D2":
|
||||
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ def test_AMOUtlinePrimitive_dump():
|
|||
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)
|
||||
|
|
@ -259,6 +260,7 @@ def test_AMThermalPrimitive_validation():
|
|||
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)
|
||||
|
|
@ -269,11 +271,13 @@ def test_AMThermalPrimitive_factory():
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ def test_zeros():
|
|||
def test_filesettings_validation():
|
||||
""" Test FileSettings constructor argument validation
|
||||
"""
|
||||
<<<<<<< HEAD
|
||||
# absolute-ish is not a valid notation
|
||||
assert_raises(ValueError, FileSettings, 'absolute-ish',
|
||||
'inch', None, (2, 5), None)
|
||||
|
|
@ -132,6 +133,16 @@ def test_filesettings_validation():
|
|||
#assert_raises(ValueError, FileSettings, 'absolute',
|
||||
# 'inch', 'following', (2, 5), None)
|
||||
|
||||
=======
|
||||
assert_raises(ValueError, FileSettings, 'absolute-ish',
|
||||
'inch', 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', 'following', (2, 5), None)
|
||||
>>>>>>> 5476da8... Fix a bunch of rendering bugs.
|
||||
assert_raises(ValueError, FileSettings, 'absolute',
|
||||
'inch', None, (2, 5), 'following')
|
||||
assert_raises(ValueError, FileSettings, 'absolute',
|
||||
|
|
|
|||
|
|
@ -204,7 +204,8 @@ def test_arc_bounds():
|
|||
|
||||
def test_arc_conversion():
|
||||
c = Circle((0, 0), 25.4, units='metric')
|
||||
a = Arc((2.54, 25.4), (254.0, 2540.0), (25400.0, 254000.0),'clockwise', c, 'single-quadrant', units='metric')
|
||||
a = Arc((2.54, 25.4), (254.0, 2540.0), (25400.0, 254000.0),
|
||||
'clockwise', c, 'single-quadrant', units='metric')
|
||||
|
||||
# No effect
|
||||
a.to_metric()
|
||||
|
|
@ -227,7 +228,8 @@ def test_arc_conversion():
|
|||
assert_equal(a.aperture.diameter, 1.0)
|
||||
|
||||
c = Circle((0, 0), 1.0, units='inch')
|
||||
a = Arc((0.1, 1.0), (10.0, 100.0), (1000.0, 10000.0),'clockwise', c, 'single-quadrant', units='inch')
|
||||
a = Arc((0.1, 1.0), (10.0, 100.0), (1000.0, 10000.0),
|
||||
'clockwise', c, 'single-quadrant', units='inch')
|
||||
a.to_metric()
|
||||
assert_equal(a.start, (2.54, 25.4))
|
||||
assert_equal(a.end, (254.0, 2540.0))
|
||||
|
|
@ -254,12 +256,14 @@ def test_circle_radius():
|
|||
c = Circle((1, 1), 2)
|
||||
assert_equal(c.radius, 1)
|
||||
|
||||
|
||||
def test_circle_hole_radius():
|
||||
""" Test Circle primitive hole radius calculation
|
||||
"""
|
||||
c = Circle((1, 1), 4, 2)
|
||||
assert_equal(c.hole_radius, 1)
|
||||
|
||||
|
||||
def test_circle_bounds():
|
||||
""" Test Circle bounding box calculation
|
||||
"""
|
||||
|
|
@ -301,7 +305,7 @@ def test_circle_conversion():
|
|||
assert_equal(c.diameter, 10.)
|
||||
assert_equal(c.hole_diameter, 5.)
|
||||
|
||||
#no effect
|
||||
# no effect
|
||||
c.to_inch()
|
||||
assert_equal(c.position, (0.1, 1.))
|
||||
assert_equal(c.diameter, 10.)
|
||||
|
|
@ -338,13 +342,14 @@ def test_circle_conversion():
|
|||
assert_equal(c.diameter, 254.)
|
||||
assert_equal(c.hole_diameter, 127.)
|
||||
|
||||
#no effect
|
||||
# no effect
|
||||
c.to_metric()
|
||||
assert_equal(c.position, (2.54, 25.4))
|
||||
assert_equal(c.diameter, 254.)
|
||||
assert_equal(c.hole_diameter, 127.)
|
||||
|
||||
|
||||
|
||||
def test_circle_offset():
|
||||
c = Circle((0, 0), 1)
|
||||
c.offset(1, 0)
|
||||
|
|
@ -443,6 +448,7 @@ def test_rectangle_hole_radius():
|
|||
assert_equal(0.5, r.hole_radius)
|
||||
|
||||
|
||||
|
||||
def test_rectangle_bounds():
|
||||
""" Test rectangle bounding box calculation
|
||||
"""
|
||||
|
|
@ -530,7 +536,7 @@ def test_rectangle_conversion():
|
|||
assert_equal(r.hole_diameter, 127.0)
|
||||
|
||||
r.to_metric()
|
||||
assert_equal(r.position, (2.54,25.4))
|
||||
assert_equal(r.position, (2.54, 25.4))
|
||||
assert_equal(r.width, 254.0)
|
||||
assert_equal(r.height, 2540.0)
|
||||
assert_equal(r.hole_diameter, 127.0)
|
||||
|
|
@ -881,6 +887,7 @@ def test_polygon_ctor():
|
|||
assert_equal(p.hole_diameter, hole_diameter)
|
||||
|
||||
|
||||
|
||||
def test_polygon_bounds():
|
||||
""" Test polygon bounding box calculation
|
||||
"""
|
||||
|
|
@ -1201,6 +1208,7 @@ def test_drill_ctor_validation():
|
|||
assert_raises(TypeError, Drill, 3, 5, None)
|
||||
assert_raises(TypeError, Drill, (3,4,5), 5, None)
|
||||
|
||||
|
||||
def test_drill_bounds():
|
||||
d = Drill((0, 0), 2, None)
|
||||
xbounds, ybounds = d.bounding_box
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue