Improve Excellon compatibility parsing
This commit is contained in:
parent
2451b517e8
commit
516a9d337f
2 changed files with 139 additions and 13 deletions
|
|
@ -890,12 +890,17 @@ class ExcellonParser(object):
|
|||
# from https://math.stackexchange.com/a/1781546
|
||||
if a_s:
|
||||
raise ValueError('Negative arc radius given')
|
||||
r = settings.parse_gerber_value(a)
|
||||
r = self.settings.parse_gerber_value(a)
|
||||
x1, y1 = start
|
||||
x2, y2 = end
|
||||
dx, dy = (x2-x1)/2, (y2-y1)/2
|
||||
x0, y0 = x1+dx, y1+dy
|
||||
f = math.hypot(dx, dy) / math.sqrt(r**2 - a**2)
|
||||
d = math.hypot(dx, dy)
|
||||
if d == 0:
|
||||
raise ValueError('Arc radius notation requires distinct start and end points')
|
||||
if r < d:
|
||||
raise ValueError('Arc radius too small for endpoint distance')
|
||||
f = math.sqrt(r**2 - d**2) / d
|
||||
if clockwise:
|
||||
cx = x0 + f*dy
|
||||
cy = y0 - f*dx
|
||||
|
|
@ -905,16 +910,16 @@ class ExcellonParser(object):
|
|||
i, j = cx-start[0], cy-start[1]
|
||||
|
||||
else: # explicit center given
|
||||
i = settings.parse_gerber_value(i)
|
||||
i = self.settings.parse_gerber_value(i) or 0
|
||||
if i_s:
|
||||
i = -i
|
||||
j = settings.parse_gerber_value(j)
|
||||
j = self.settings.parse_gerber_value(j) or 0
|
||||
if j_s:
|
||||
j = -i
|
||||
j = -j
|
||||
|
||||
self.objects.append(Arc(*start, *end, i, j, True, self.active_tool, unit=self.settings.unit))
|
||||
self.objects.append(Arc(*start, *end, i, j, clockwise, self.active_tool, unit=self.settings.unit))
|
||||
|
||||
@exprs.match(r'(M71|METRIC|M72|INCH)(,LZ|,TZ)?(,0*\.0*)?')
|
||||
@exprs.match(r'(M71|METRIC|M72|INCH)(,LZ|,TZ)?(,([0-9]+\.[0-9]+|0*\.0*))?')
|
||||
def parse_easyeda_format(self, match):
|
||||
metric = match[1] in ('METRIC', 'M71')
|
||||
|
||||
|
|
@ -927,7 +932,10 @@ class ExcellonParser(object):
|
|||
# This is used by newer autodesk eagles, fritzing and diptrace
|
||||
if match[3]:
|
||||
integer, _, fractional = match[3][1:].partition('.')
|
||||
self.settings.number_format = len(integer), len(fractional)
|
||||
if integer.strip('0') or fractional.strip('0'):
|
||||
self.settings.number_format = int(integer), int(fractional)
|
||||
else:
|
||||
self.settings.number_format = len(integer), len(fractional)
|
||||
|
||||
elif self.settings.number_format == (None, None) and not metric and not self.found_kicad_format_comment:
|
||||
self.warn('Using implicit number format from bare "INCH" statement. This is normal for Fritzing, Diptrace, Geda and pcb-rnd.')
|
||||
|
|
@ -953,10 +961,10 @@ class ExcellonParser(object):
|
|||
@exprs.match('(FMAT|VER),?([0-9]*)')
|
||||
def handle_command_format(self, match):
|
||||
if match[1] == 'FMAT':
|
||||
# We do not support integer/fractional decimals specification via FMAT because that's stupid. If you need this,
|
||||
# please raise an issue on our issue tracker, provide a sample file and tell us where on earth you found that
|
||||
# file.
|
||||
if match[2] not in ('', '2'):
|
||||
# We only use FMAT as a compatibility marker. Version 1 drill files encountered in the wild still use the
|
||||
# same coordinate and routing statements that we already support, so rejecting the header unconditionally
|
||||
# needlessly breaks otherwise parseable files.
|
||||
if match[2] not in ('', '1', '2'):
|
||||
raise SyntaxError(f'Unsupported FMAT format version {match[2]}')
|
||||
|
||||
else: # VER
|
||||
|
|
@ -985,6 +993,19 @@ class ExcellonParser(object):
|
|||
else:
|
||||
self.warn('Bare coordinate after end of file')
|
||||
|
||||
@exprs.match(xy_coord + 'G85' + xy_coord)
|
||||
def handle_g85_slot(self, match):
|
||||
if self.program_state == ProgramState.HEADER:
|
||||
return
|
||||
|
||||
self.do_move(match.groups()[:4])
|
||||
start, end = self.do_move(match.groups()[4:])
|
||||
|
||||
if not self.ensure_active_tool():
|
||||
return
|
||||
|
||||
self.objects.append(Line(*start, *end, self.active_tool, unit=self.settings.unit))
|
||||
|
||||
@exprs.match(r'DETECT,ON|ATC,ON|M06')
|
||||
def parse_zuken_legacy_statements(self, match):
|
||||
self.generator_hints.append('zuken')
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ from scipy.spatial import KDTree
|
|||
from gerbonara.excellon import ExcellonFile
|
||||
from gerbonara.rs274x import GerberFile
|
||||
from gerbonara.cam import FileSettings
|
||||
from gerbonara.graphic_objects import Flash
|
||||
from gerbonara.graphic_objects import Arc, Flash, Line
|
||||
|
||||
from .image_support import *
|
||||
from .utils import *
|
||||
|
|
@ -195,3 +195,108 @@ def test_syntax_error():
|
|||
assert 'test_syntax_error.exc' in exc_info.value.msg
|
||||
assert '12' in exc_info.value.msg # lineno
|
||||
|
||||
|
||||
@filter_syntax_warnings
|
||||
def test_easyeda_format_supports_explicit_digit_spec():
|
||||
data = '\n'.join([
|
||||
'M48',
|
||||
'INCH,TZ,2.1',
|
||||
'T01C0.0100',
|
||||
'%',
|
||||
'T01',
|
||||
'X11Y11',
|
||||
'M30',
|
||||
])
|
||||
|
||||
parsed = ExcellonFile.from_string(data)
|
||||
assert parsed.import_settings.number_format == (2, 1)
|
||||
|
||||
|
||||
@filter_syntax_warnings
|
||||
def test_fmat_1_header_is_accepted():
|
||||
data = '\n'.join([
|
||||
'M48',
|
||||
'INCH,TZ,2.4',
|
||||
'FMAT,1',
|
||||
'T01C0.0100',
|
||||
'%',
|
||||
'T01',
|
||||
'X010000Y010000',
|
||||
'X020000Y010000',
|
||||
'M30',
|
||||
])
|
||||
|
||||
parsed = ExcellonFile.from_string(data)
|
||||
drills = list(parsed.drills())
|
||||
assert len(drills) == 2
|
||||
|
||||
|
||||
@filter_syntax_warnings
|
||||
def test_inline_g85_slot_creates_line_slot():
|
||||
data = '\n'.join([
|
||||
'M48',
|
||||
'INCH,TZ',
|
||||
'T01C0.1000',
|
||||
'%',
|
||||
'T01',
|
||||
'X080000Y015000G85X090000Y015000',
|
||||
'M30',
|
||||
])
|
||||
|
||||
parsed = ExcellonFile.from_string(data)
|
||||
slots = list(parsed.slots())
|
||||
assert len(slots) == 1
|
||||
assert isinstance(slots[0], Line)
|
||||
assert slots[0].x1 == 8.0
|
||||
assert slots[0].y1 == 1.5
|
||||
assert slots[0].x2 == 9.0
|
||||
assert slots[0].y2 == 1.5
|
||||
|
||||
|
||||
@filter_syntax_warnings
|
||||
def test_circular_interpolation_uses_signed_center_and_direction():
|
||||
data = '\n'.join([
|
||||
'M48',
|
||||
'INCH,TZ',
|
||||
'T01C0.0100',
|
||||
'%',
|
||||
'T01',
|
||||
'G00X000000Y010000',
|
||||
'M15',
|
||||
'G03X-010000Y000000I000000J-010000',
|
||||
'M16',
|
||||
'M30',
|
||||
])
|
||||
|
||||
parsed = ExcellonFile.from_string(data)
|
||||
slots = list(parsed.slots())
|
||||
assert len(slots) == 1
|
||||
assert isinstance(slots[0], Arc)
|
||||
assert slots[0].cx == 0.0
|
||||
assert slots[0].cy == -1.0
|
||||
assert not slots[0].clockwise
|
||||
|
||||
|
||||
@filter_syntax_warnings
|
||||
def test_radius_arc_interpolation_converts_to_center_offset():
|
||||
data = '\n'.join([
|
||||
'M48',
|
||||
'INCH,TZ',
|
||||
'T01C0.0100',
|
||||
'%',
|
||||
'T01',
|
||||
'G00X010000Y000000',
|
||||
'M15',
|
||||
'G03X000000Y010000A010000',
|
||||
'M16',
|
||||
'M30',
|
||||
])
|
||||
|
||||
parsed = ExcellonFile.from_string(data)
|
||||
slots = list(parsed.slots())
|
||||
assert len(slots) == 1
|
||||
assert isinstance(slots[0], Arc)
|
||||
assert math.isclose(slots[0].cx, -1.0)
|
||||
assert math.isclose(slots[0].cy, 0.0, abs_tol=1e-12)
|
||||
assert not slots[0].clockwise
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue