Finish adding square hole support, fix some primitive calculations, etc.
This commit is contained in:
parent
6b672e98ff
commit
a7f1f6ef0f
4 changed files with 228 additions and 138 deletions
|
|
@ -64,7 +64,6 @@ class Primitive(object):
|
|||
@property
|
||||
def flashed(self):
|
||||
'''Is this a flashed primitive'''
|
||||
|
||||
raise NotImplementedError('Is flashed must be '
|
||||
'implemented in subclass')
|
||||
|
||||
|
|
@ -271,9 +270,9 @@ class Line(Primitive):
|
|||
@property
|
||||
def vertices(self):
|
||||
if self._vertices is None:
|
||||
start = self.start
|
||||
end = self.end
|
||||
if isinstance(self.aperture, Rectangle):
|
||||
start = self.start
|
||||
end = self.end
|
||||
width = self.aperture.width
|
||||
height = self.aperture.height
|
||||
|
||||
|
|
@ -289,6 +288,11 @@ 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))
|
||||
elif isinstance(self.aperture, Polygon):
|
||||
points = [map(add, point, vertex)
|
||||
for vertex in self.aperture.vertices
|
||||
for point in (start, end)]
|
||||
self._vertices = convex_hull(points)
|
||||
return self._vertices
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
|
|
@ -309,11 +313,18 @@ class Line(Primitive):
|
|||
|
||||
return nearly_equal(self.start, equiv_start) and nearly_equal(self.end, equiv_end)
|
||||
|
||||
def __str__(self):
|
||||
return "<Line {} to {}>".format(self.start, self.end)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
class Arc(Primitive):
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, start, end, center, direction, aperture, quadrant_mode, **kwargs):
|
||||
def __init__(self, start, end, center, direction, aperture, quadrant_mode,
|
||||
**kwargs):
|
||||
super(Arc, self).__init__(**kwargs)
|
||||
self._start = start
|
||||
self._end = end
|
||||
|
|
@ -371,15 +382,15 @@ class Arc(Primitive):
|
|||
|
||||
@property
|
||||
def start_angle(self):
|
||||
dy, dx = tuple([start - center for start, center
|
||||
dx, dy = tuple([start - center for start, center
|
||||
in zip(self.start, self.center)])
|
||||
return math.atan2(dx, dy)
|
||||
return math.atan2(dy, dx)
|
||||
|
||||
@property
|
||||
def end_angle(self):
|
||||
dy, dx = tuple([end - center for end, center
|
||||
dx, dy = tuple([end - center for end, center
|
||||
in zip(self.end, self.center)])
|
||||
return math.atan2(dx, dy)
|
||||
return math.atan2(dy, dx)
|
||||
|
||||
@property
|
||||
def sweep_angle(self):
|
||||
|
|
@ -399,41 +410,51 @@ class Arc(Primitive):
|
|||
theta0 = (self.start_angle + two_pi) % two_pi
|
||||
theta1 = (self.end_angle + two_pi) % two_pi
|
||||
points = [self.start, self.end]
|
||||
if self.direction == 'counterclockwise':
|
||||
# Passes through 0 degrees
|
||||
if theta0 > theta1:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if theta0 <= math.pi / \
|
||||
2. and (theta1 >= math.pi / 2. or theta1 < theta0):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if theta0 <= math.pi and (theta1 >= math.pi or theta1 < theta0):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if theta0 <= math.pi * \
|
||||
1.5 and (theta1 >= math.pi * 1.5 or theta1 < theta0):
|
||||
points.append((self.center[0], self.center[1] - self.radius))
|
||||
else:
|
||||
# Passes through 0 degrees
|
||||
if theta1 > theta0:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if theta1 <= math.pi / \
|
||||
2. and (theta0 >= math.pi / 2. or theta0 < theta1):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if theta1 <= math.pi and (theta0 >= math.pi or theta0 < theta1):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if theta1 <= math.pi * \
|
||||
1.5 and (theta0 >= math.pi * 1.5 or theta0 < theta1):
|
||||
points.append((self.center[0], self.center[1] - self.radius))
|
||||
if self.quadrant_mode == 'multi-quadrant':
|
||||
if self.direction == 'counterclockwise':
|
||||
# Passes through 0 degrees
|
||||
if theta0 >= theta1:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if (((theta0 <= math.pi / 2.) and ((theta1 >= math.pi / 2.) or (theta1 <= theta0)))
|
||||
or ((theta1 > math.pi / 2.) and (theta1 <= theta0))):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if ((theta0 <= math.pi and (theta1 >= math.pi or theta1 <= theta0))
|
||||
or ((theta1 > math.pi) and (theta1 <= theta0))):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if (theta0 <= math.pi * 1.5 and (theta1 >= math.pi * 1.5 or theta1 <= theta0)
|
||||
or ((theta1 > math.pi * 1.5) and (theta1 <= theta0))):
|
||||
points.append((self.center[0], self.center[1] - self.radius))
|
||||
else:
|
||||
# Passes through 0 degrees
|
||||
if theta1 >= theta0:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if (((theta1 <= math.pi / 2.) and (theta0 >= math.pi / 2. or theta0 <= theta1))
|
||||
or ((theta0 > math.pi / 2.) and (theta0 <= theta1))):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if (((theta1 <= math.pi) and (theta0 >= math.pi or theta0 <= theta1))
|
||||
or ((theta0 > math.pi) and (theta0 <= theta1))):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if (((theta1 <= math.pi * 1.5) and (theta0 >= math.pi * 1.5 or theta0 <= theta1))
|
||||
or ((theta0 > math.pi * 1.5) and (theta0 <= theta1))):
|
||||
points.append((self.center[0], self.center[1] - self.radius))
|
||||
x, y = zip(*points)
|
||||
min_x = min(x) - self.aperture.radius
|
||||
max_x = max(x) + self.aperture.radius
|
||||
min_y = min(y) - self.aperture.radius
|
||||
max_y = max(y) + self.aperture.radius
|
||||
if hasattr(self.aperture, 'radius'):
|
||||
min_x = min(x) - self.aperture.radius
|
||||
max_x = max(x) + self.aperture.radius
|
||||
min_y = min(y) - self.aperture.radius
|
||||
max_y = max(y) + self.aperture.radius
|
||||
else:
|
||||
min_x = min(x) - self.aperture.width
|
||||
max_x = max(x) + self.aperture.width
|
||||
min_y = min(y) - self.aperture.height
|
||||
max_y = max(y) + self.aperture.height
|
||||
|
||||
self._bounding_box = ((min_x, max_x), (min_y, max_y))
|
||||
return self._bounding_box
|
||||
|
||||
|
|
@ -444,32 +465,43 @@ class Arc(Primitive):
|
|||
theta0 = (self.start_angle + two_pi) % two_pi
|
||||
theta1 = (self.end_angle + two_pi) % two_pi
|
||||
points = [self.start, self.end]
|
||||
if self.direction == 'counterclockwise':
|
||||
# Passes through 0 degrees
|
||||
if theta0 > theta1:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if theta0 <= math.pi / 2. and (theta1 >= math.pi / 2. or theta1 < theta0):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if theta0 <= math.pi and (theta1 >= math.pi or theta1 < theta0):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if theta0 <= math.pi * 1.5 and (theta1 >= math.pi * 1.5 or theta1 < theta0):
|
||||
points.append((self.center[0], self.center[1] - self.radius ))
|
||||
else:
|
||||
# Passes through 0 degrees
|
||||
if theta1 > theta0:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if theta1 <= math.pi / 2. and (theta0 >= math.pi / 2. or theta0 < theta1):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if theta1 <= math.pi and (theta0 >= math.pi or theta0 < theta1):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if theta1 <= math.pi * 1.5 and (theta0 >= math.pi * 1.5 or theta0 < theta1):
|
||||
points.append((self.center[0], self.center[1] - self.radius ))
|
||||
if self.quadrant_mode == 'multi-quadrant':
|
||||
if self.direction == 'counterclockwise':
|
||||
# Passes through 0 degrees
|
||||
if theta0 >= theta1:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if (((theta0 <= math.pi / 2.) and (
|
||||
(theta1 >= math.pi / 2.) or (theta1 <= theta0)))
|
||||
or ((theta1 > math.pi / 2.) and (theta1 <= theta0))):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if ((theta0 <= math.pi and (theta1 >= math.pi or theta1 <= theta0))
|
||||
or ((theta1 > math.pi) and (theta1 <= theta0))):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if (theta0 <= math.pi * 1.5 and (
|
||||
theta1 >= math.pi * 1.5 or theta1 <= theta0)
|
||||
or ((theta1 > math.pi * 1.5) and (theta1 <= theta0))):
|
||||
points.append((self.center[0], self.center[1] - self.radius))
|
||||
else:
|
||||
# Passes through 0 degrees
|
||||
if theta1 >= theta0:
|
||||
points.append((self.center[0] + self.radius, self.center[1]))
|
||||
# Passes through 90 degrees
|
||||
if (((theta1 <= math.pi / 2.) and (
|
||||
theta0 >= math.pi / 2. or theta0 <= theta1))
|
||||
or ((theta0 > math.pi / 2.) and (theta0 <= theta1))):
|
||||
points.append((self.center[0], self.center[1] + self.radius))
|
||||
# Passes through 180 degrees
|
||||
if (((theta1 <= math.pi) and (theta0 >= math.pi or theta0 <= theta1))
|
||||
or ((theta0 > math.pi) and (theta0 <= theta1))):
|
||||
points.append((self.center[0] - self.radius, self.center[1]))
|
||||
# Passes through 270 degrees
|
||||
if (((theta1 <= math.pi * 1.5) and (
|
||||
theta0 >= math.pi * 1.5 or theta0 <= theta1))
|
||||
or ((theta0 > math.pi * 1.5) and (theta0 <= theta1))):
|
||||
points.append((self.center[0], self.center[1] - self.radius))
|
||||
x, y = zip(*points)
|
||||
|
||||
min_x = min(x)
|
||||
|
|
@ -489,13 +521,16 @@ class Circle(Primitive):
|
|||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, position, diameter, hole_diameter = None, **kwargs):
|
||||
def __init__(self, position, diameter, hole_diameter=None,
|
||||
hole_width=0, hole_height=0, **kwargs):
|
||||
super(Circle, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._diameter = diameter
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'diameter', 'hole_diameter']
|
||||
self.hole_width = hole_width
|
||||
self.hole_height = hole_height
|
||||
self._to_convert = ['position', 'diameter', 'hole_diameter', 'hole_width', 'hole_height']
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
|
|
@ -631,14 +666,18 @@ class Rectangle(Primitive):
|
|||
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,
|
||||
hole_width=0, hole_height=0, **kwargs):
|
||||
super(Rectangle, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._width = width
|
||||
self._height = height
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'width', 'height', 'hole_diameter']
|
||||
self.hole_width = hole_width
|
||||
self.hole_height = hole_height
|
||||
self._to_convert = ['position', 'width', 'height', 'hole_diameter',
|
||||
'hole_width', 'hole_height']
|
||||
# TODO These are probably wrong when rotated
|
||||
self._lower_left = None
|
||||
self._upper_right = None
|
||||
|
|
@ -736,6 +775,12 @@ class Rectangle(Primitive):
|
|||
|
||||
return nearly_equal(self.position, equiv_position)
|
||||
|
||||
def __str__(self):
|
||||
return "<Rectangle W {} H {} R {}>".format(self.width, self.height, self.rotation * 180/math.pi)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class Diamond(Primitive):
|
||||
"""
|
||||
|
|
@ -898,7 +943,8 @@ class ChamferRectangle(Primitive):
|
|||
((self.position[0] - delta_w), (self.position[1] - delta_h)),
|
||||
((self.position[0] + delta_w), (self.position[1] - delta_h))
|
||||
]
|
||||
for idx, corner, chamfered in enumerate((rect_corners, self.corners)):
|
||||
for idx, params in enumerate(zip(rect_corners, self.corners)):
|
||||
corner, chamfered = params
|
||||
x, y = corner
|
||||
if chamfered:
|
||||
if idx == 0:
|
||||
|
|
@ -1019,14 +1065,18 @@ class Obround(Primitive):
|
|||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, position, width, height, hole_diameter=0, **kwargs):
|
||||
def __init__(self, position, width, height, hole_diameter=0,
|
||||
hole_width=0,hole_height=0, **kwargs):
|
||||
super(Obround, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._width = width
|
||||
self._height = height
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'width', 'height', 'hole_diameter']
|
||||
self.hole_width = hole_width
|
||||
self.hole_height = hole_height
|
||||
self._to_convert = ['position', 'width', 'height', 'hole_diameter',
|
||||
'hole_width', 'hole_height' ]
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
|
|
@ -1116,14 +1166,18 @@ class Polygon(Primitive):
|
|||
"""
|
||||
Polygon flash defined by a set number of sides.
|
||||
"""
|
||||
def __init__(self, position, sides, radius, hole_diameter, **kwargs):
|
||||
def __init__(self, position, sides, radius, hole_diameter=0,
|
||||
hole_width=0, hole_height=0, **kwargs):
|
||||
super(Polygon, self).__init__(**kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self.sides = sides
|
||||
self._radius = radius
|
||||
self.hole_diameter = hole_diameter
|
||||
self._to_convert = ['position', 'radius', 'hole_diameter']
|
||||
self.hole_width = hole_width
|
||||
self.hole_height = hole_height
|
||||
self._to_convert = ['position', 'radius', 'hole_diameter',
|
||||
'hole_width', 'hole_height']
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
|
|
@ -1174,25 +1228,14 @@ class Polygon(Primitive):
|
|||
def vertices(self):
|
||||
|
||||
offset = self.rotation
|
||||
da = 360.0 / self.sides
|
||||
delta_angle = 360.0 / self.sides
|
||||
|
||||
points = []
|
||||
for i in xrange(self.sides):
|
||||
points.append(rotate_point((self.position[0] + self.radius, self.position[1]), offset + da * i, self.position))
|
||||
|
||||
for i in range(self.sides):
|
||||
points.append(
|
||||
rotate_point((self.position[0] + self.radius, self.position[1]), offset + delta_angle * i, self.position))
|
||||
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):
|
||||
"""
|
||||
|
|
@ -1555,15 +1598,12 @@ class SquareRoundDonut(Primitive):
|
|||
class Drill(Primitive):
|
||||
""" A drill hole
|
||||
"""
|
||||
def __init__(self, position, diameter, hit, **kwargs):
|
||||
def __init__(self, position, diameter, **kwargs):
|
||||
super(Drill, self).__init__('dark', **kwargs)
|
||||
validate_coordinates(position)
|
||||
self._position = position
|
||||
self._diameter = diameter
|
||||
self.hit = hit
|
||||
self._to_convert = ['position', 'diameter', 'hit']
|
||||
|
||||
# TODO Ths won't handle the hit updates correctly
|
||||
self._to_convert = ['position', 'diameter']
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
|
|
@ -1606,23 +1646,21 @@ class Drill(Primitive):
|
|||
self.position = tuple(map(add, self.position, (x_offset, y_offset)))
|
||||
|
||||
def __str__(self):
|
||||
return '<Drill %f (%f, %f) [%s]>' % (self.diameter, self.position[0], self.position[1], self.hit)
|
||||
return '<Drill %f %s (%f, %f)>' % (self.diameter, self.units, self.position[0], self.position[1])
|
||||
|
||||
|
||||
class Slot(Primitive):
|
||||
""" A drilled slot
|
||||
"""
|
||||
def __init__(self, start, end, diameter, hit, **kwargs):
|
||||
def __init__(self, start, end, diameter, **kwargs):
|
||||
super(Slot, self).__init__('dark', **kwargs)
|
||||
validate_coordinates(start)
|
||||
validate_coordinates(end)
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.diameter = diameter
|
||||
self.hit = hit
|
||||
self._to_convert = ['start', 'end', 'diameter', 'hit']
|
||||
self._to_convert = ['start', 'end', 'diameter']
|
||||
|
||||
# TODO this needs to use cached bounding box
|
||||
|
||||
@property
|
||||
def flashed(self):
|
||||
|
|
@ -1630,8 +1668,8 @@ class Slot(Primitive):
|
|||
|
||||
def bounding_box(self):
|
||||
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])
|
||||
ll = tuple([c - self.diameter / 2. for c in self.position])
|
||||
ur = tuple([c + self.diameter / 2. for c in self.position])
|
||||
self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
|
||||
return self._bounding_box
|
||||
|
||||
|
|
|
|||
111
gerber/rs274x.py
111
gerber/rs274x.py
|
|
@ -514,32 +514,51 @@ class GerberParser(object):
|
|||
if shape == 'C':
|
||||
diameter = modifiers[0][0]
|
||||
|
||||
if len(modifiers[0]) >= 2:
|
||||
hole_diameter = 0
|
||||
rectangular_hole = (0, 0)
|
||||
if len(modifiers[0]) == 2:
|
||||
hole_diameter = modifiers[0][1]
|
||||
else:
|
||||
hole_diameter = None
|
||||
elif len(modifiers[0]) == 3:
|
||||
rectangular_hole = modifiers[0][1:3]
|
||||
|
||||
aperture = Circle(position=None, diameter=diameter,
|
||||
hole_diameter=hole_diameter,
|
||||
hole_width=rectangular_hole[0],
|
||||
hole_height=rectangular_hole[1],
|
||||
units=self.settings.units)
|
||||
|
||||
aperture = Circle(position=None, diameter=diameter, hole_diameter=hole_diameter, units=self.settings.units)
|
||||
elif shape == 'R':
|
||||
width = modifiers[0][0]
|
||||
height = modifiers[0][1]
|
||||
|
||||
if len(modifiers[0]) >= 3:
|
||||
hole_diameter = 0
|
||||
rectangular_hole = (0, 0)
|
||||
if len(modifiers[0]) == 3:
|
||||
hole_diameter = modifiers[0][2]
|
||||
else:
|
||||
hole_diameter = None
|
||||
elif len(modifiers[0]) == 4:
|
||||
rectangular_hole = modifiers[0][2:4]
|
||||
|
||||
aperture = Rectangle(position=None, width=width, height=height, hole_diameter=hole_diameter, units=self.settings.units)
|
||||
aperture = Rectangle(position=None, width=width, height=height,
|
||||
hole_diameter=hole_diameter,
|
||||
hole_width=rectangular_hole[0],
|
||||
hole_height=rectangular_hole[1],
|
||||
units=self.settings.units)
|
||||
elif shape == 'O':
|
||||
width = modifiers[0][0]
|
||||
height = modifiers[0][1]
|
||||
|
||||
if len(modifiers[0]) >= 3:
|
||||
hole_diameter = 0
|
||||
rectangular_hole = (0, 0)
|
||||
if len(modifiers[0]) == 3:
|
||||
hole_diameter = modifiers[0][2]
|
||||
else:
|
||||
hole_diameter = None
|
||||
elif len(modifiers[0]) == 4:
|
||||
rectangular_hole = modifiers[0][2:4]
|
||||
|
||||
aperture = Obround(position=None, width=width, height=height, hole_diameter=hole_diameter, units=self.settings.units)
|
||||
aperture = Obround(position=None, width=width, height=height,
|
||||
hole_diameter=hole_diameter,
|
||||
hole_width=rectangular_hole[0],
|
||||
hole_height=rectangular_hole[1],
|
||||
units=self.settings.units)
|
||||
elif shape == 'P':
|
||||
outer_diameter = modifiers[0][0]
|
||||
number_vertices = int(modifiers[0][1])
|
||||
|
|
@ -548,11 +567,19 @@ class GerberParser(object):
|
|||
else:
|
||||
rotation = 0
|
||||
|
||||
if len(modifiers[0]) > 3:
|
||||
hole_diameter = 0
|
||||
rectangular_hole = (0, 0)
|
||||
if len(modifiers[0]) == 4:
|
||||
hole_diameter = modifiers[0][3]
|
||||
else:
|
||||
hole_diameter = None
|
||||
aperture = Polygon(position=None, sides=number_vertices, radius=outer_diameter/2.0, hole_diameter=hole_diameter, rotation=rotation)
|
||||
elif len(modifiers[0]) >= 5:
|
||||
rectangular_hole = modifiers[0][3:5]
|
||||
|
||||
aperture = Polygon(position=None, sides=number_vertices,
|
||||
radius=outer_diameter/2.0,
|
||||
hole_diameter=hole_diameter,
|
||||
hole_width=rectangular_hole[0],
|
||||
hole_height=rectangular_hole[1],
|
||||
rotation=rotation)
|
||||
else:
|
||||
aperture = self.macros[shape].build(modifiers)
|
||||
|
||||
|
|
@ -663,13 +690,18 @@ class GerberParser(object):
|
|||
quadrant_mode=self.quadrant_mode,
|
||||
level_polarity=self.level_polarity,
|
||||
units=self.settings.units))
|
||||
# Gerbv seems to reset interpolation mode in regions..
|
||||
# TODO: Make sure this is right.
|
||||
self.interpolation = 'linear'
|
||||
|
||||
elif self.op == "D02" or self.op == "D2":
|
||||
|
||||
if self.region_mode == "on":
|
||||
# D02 in the middle of a region finishes that region and starts a new one
|
||||
if self.current_region and len(self.current_region) > 1:
|
||||
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
|
||||
|
||||
elif self.op == "D03" or self.op == "D3":
|
||||
|
|
@ -694,29 +726,53 @@ class GerberParser(object):
|
|||
|
||||
def _find_center(self, start, end, offsets):
|
||||
"""
|
||||
In single quadrant mode, the offsets are always positive, which means there are 4 possible centers.
|
||||
The correct center is the only one that results in an arc with sweep angle of less than or equal to 90 degrees
|
||||
In single quadrant mode, the offsets are always positive, which means
|
||||
there are 4 possible centers. The correct center is the only one that
|
||||
results in an arc with sweep angle of less than or equal to 90 degrees
|
||||
in the specified direction
|
||||
"""
|
||||
|
||||
two_pi = 2 * math.pi
|
||||
if self.quadrant_mode == 'single-quadrant':
|
||||
# The Gerber spec says single quadrant only has one possible center,
|
||||
# and you can detect it based on the angle. But for real files, this
|
||||
# seems to work better - there is usually only one option that makes
|
||||
# sense for the center (since the distance should be the same
|
||||
# from start and end). We select the center with the least error in
|
||||
# radius from all the options with a valid sweep angle.
|
||||
|
||||
# The Gerber spec says single quadrant only has one possible center, and you can detect
|
||||
# based on the angle. But for real files, this seems to work better - there is usually
|
||||
# only one option that makes sense for the center (since the distance should be the same
|
||||
# from start and end). Find the center that makes the most sense
|
||||
sqdist_diff_min = sys.maxint
|
||||
center = None
|
||||
for factors in [(1, 1), (1, -1), (-1, 1), (-1, -1)]:
|
||||
|
||||
test_center = (start[0] + offsets[0] * factors[0], start[1] + offsets[1] * factors[1])
|
||||
test_center = (start[0] + offsets[0] * factors[0],
|
||||
start[1] + offsets[1] * factors[1])
|
||||
|
||||
# Find angle from center to start and end points
|
||||
start_angle = math.atan2(*reversed([_start - _center for _start, _center in zip(start, test_center)]))
|
||||
end_angle = math.atan2(*reversed([_end - _center for _end, _center in zip(end, test_center)]))
|
||||
|
||||
# Clamp angles to 0, 2pi
|
||||
theta0 = (start_angle + two_pi) % two_pi
|
||||
theta1 = (end_angle + two_pi) % two_pi
|
||||
|
||||
# Determine sweep angle in the current arc direction
|
||||
if self.direction == 'counterclockwise':
|
||||
sweep_angle = abs(theta1 - theta0)
|
||||
else:
|
||||
theta0 += two_pi
|
||||
sweep_angle = abs(theta0 - theta1) % two_pi
|
||||
|
||||
# Calculate the radius error
|
||||
sqdist_start = sq_distance(start, test_center)
|
||||
sqdist_end = sq_distance(end, test_center)
|
||||
|
||||
if abs(sqdist_start - sqdist_end) < sqdist_diff_min:
|
||||
# Take the option with the lowest radius error from the set of
|
||||
# options with a valid sweep angle
|
||||
if ((abs(sqdist_start - sqdist_end) < sqdist_diff_min)
|
||||
and (sweep_angle >= 0)
|
||||
and (sweep_angle <= math.pi / 2.0)):
|
||||
center = test_center
|
||||
sqdist_diff_min = abs(sqdist_start - sqdist_end)
|
||||
|
||||
return center
|
||||
else:
|
||||
return (start[0] + offsets[0], start[1] + offsets[1])
|
||||
|
|
@ -724,7 +780,6 @@ class GerberParser(object):
|
|||
def _evaluate_aperture(self, stmt):
|
||||
self.aperture = stmt.d
|
||||
|
||||
|
||||
def _match_one(expr, data):
|
||||
match = expr.match(data)
|
||||
if match is None:
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ files.
|
|||
|
||||
import os
|
||||
from math import radians, sin, cos
|
||||
from operator import sub
|
||||
from copy import deepcopy
|
||||
from pyhull.convex_hull import ConvexHull
|
||||
from scipy.spatial import ConvexHull
|
||||
|
||||
MILLIMETERS_PER_INCH = 25.4
|
||||
|
||||
|
|
@ -344,5 +342,4 @@ def listdir(directory, ignore_hidden=True, ignore_os=True):
|
|||
|
||||
def convex_hull(points):
|
||||
vertices = ConvexHull(points).vertices
|
||||
return [points[idx] for idx in
|
||||
set([point for pair in vertices for point in pair])]
|
||||
return [points[idx] for idx in vertices]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
## The following requirements were added by pip --freeze:
|
||||
cairocffi==0.6
|
||||
pyhull==1.5.6
|
||||
scipy
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue