From deb2bb2bbfc13e6dce8adf493221a4fe4929a344 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 23 Jan 2022 01:19:30 +0100 Subject: Squash some more bugs --- gerbonara/gerber/rs274x.py | 76 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 22 deletions(-) (limited to 'gerbonara/gerber/rs274x.py') diff --git a/gerbonara/gerber/rs274x.py b/gerbonara/gerber/rs274x.py index 50de8ca..e7459ec 100644 --- a/gerbonara/gerber/rs274x.py +++ b/gerbonara/gerber/rs274x.py @@ -20,18 +20,12 @@ """ This module provides an RS-274-X class and parser. """ -import copy -import json -import os import re -import sys import math import warnings -import functools from pathlib import Path from itertools import count, chain from io import StringIO -import textwrap import dataclasses from .cam import CamFile, FileSettings @@ -359,7 +353,7 @@ class GraphicsState: polarity_dark=self.polarity_dark, unit=self.file_settings.unit) - def interpolate(self, x, y, i=None, j=None, aperture=True): + def interpolate(self, x, y, i=None, j=None, aperture=True, multi_quadrant=False): if self.point is None: warnings.warn('D01 interpolation without preceding D02 move.', SyntaxWarning) self.point = (0, 0) @@ -393,22 +387,41 @@ class GraphicsState: if j is None: warnings.warn('Arc is missing J value', SyntaxWarning) j = 0 - return self._create_arc(old_point, self.map_coord(*self.point), (i, j), aperture) + return self._create_arc(old_point, self.map_coord(*self.point), (i, j), aperture, multi_quadrant) def _create_line(self, old_point, new_point, aperture=True): return go.Line(*old_point, *new_point, self.aperture if aperture else None, polarity_dark=self.polarity_dark, unit=self.file_settings.unit) - def _create_arc(self, old_point, new_point, control_point, aperture=True): + def _create_arc(self, old_point, new_point, control_point, aperture=True, multi_quadrant=False): clockwise = self.interpolation_mode == InterpMode.CIRCULAR_CW - return go.Arc(*old_point, *new_point, *self.map_coord(*control_point, relative=True), - clockwise=clockwise, aperture=(self.aperture if aperture else None), - polarity_dark=self.polarity_dark, unit=self.file_settings.unit) + + if not multi_quadrant: + return go.Arc(*old_point, *new_point, *self.map_coord(*control_point, relative=True), + clockwise=clockwise, aperture=(self.aperture if aperture else None), + polarity_dark=self.polarity_dark, unit=self.file_settings.unit) + + else: + # Super-legacy. No one uses this EXCEPT everything that mentor graphics / siemens make uses this m( + (cx, cy) = self.map_coord(*control_point, relative=True) + arc = lambda cx, cy: go.Arc(*old_point, *new_point, cx, cy, + clockwise=clockwise, aperture=(self.aperture if aperture else None), + polarity_dark=self.polarity_dark, unit=self.file_settings.unit) + arcs = [ arc(cx, cy), arc(-cx, cy), arc(cx, -cy), arc(-cx, -cy) ] + arcs = [ a for a in arcs if a.sweep_angle() <= math.pi/2 ] + arcs = sorted(arcs, key=lambda a: a.numeric_error()) + return arcs[0] + def update_point(self, x, y, unit=None): old_point = self.point x, y = MM(x, unit), MM(y, unit) + if (x is None or y is None) and self.point is None: + warnings.warn('Coordinate omitted from first coordinate statement in the file. This is likely a Siemens ' + 'file. We pretend the omitted coordinate was 0.', SyntaxWarning) + self.point = (0, 0) + if x is None: x = self.point[0] if y is None: @@ -475,6 +488,7 @@ class GerberParser: 'scale_factor': fr"SF(A(?P{DECIMAL}))?(B(?P{DECIMAL}))?", 'aperture_definition': fr"ADD(?P\d+)(?PC|R|O|P|{NAME})(?P,[^,%]*)?$", 'aperture_macro': fr"AM(?P{NAME})\*(?P[^%]*)", + 'siemens_garbage': r'^ICAS$', 'old_unit':r'(?PG7[01])', 'old_notation': r'(?PG9[01])', 'eof': r"M0?[02]", @@ -549,10 +563,10 @@ class GerberParser: #print(f' match: {name} / {match}') try: getattr(self, f'_parse_{name}')(match) - except: - print(f'Line {lineno}: {line}') - print(f' match: {name} / {match}') - raise + except Exception as e: + #print(f'Line {lineno}: {line}') + #print(f' match: {name} / {match}') + raise SyntaxError(f'Syntax error in line {lineno} "{line}": {e}') from e line = line[match.end(0):] break @@ -594,8 +608,16 @@ class GerberParser: op = 'D01' else: - raise SyntaxError('Ambiguous coordinate statement. Coordinate statement does not have an operation '\ - 'mode and the last operation statement was not D01.') + if 'siemens' in self.generator_hints: + warnings.warn('Ambiguous coordinate statement. Coordinate statement does not have an operation '\ + 'mode and the last operation statement was not D01. This is garbage, and forbidden '\ + 'by spec. but since this looks like a Siemens/Mentor Graphics file, we will let it '\ + 'slide and treat this as a D01.', SyntaxWarning) + op = 'D01' + else: + raise SyntaxError('Ambiguous coordinate statement. Coordinate statement does not have an '\ + 'operation mode and the last operation statement was not D01. This is garbage, and '\ + 'forbidden by spec.') self.last_operation = op @@ -606,12 +628,14 @@ class GerberParser: 'This can cause problems with older gerber interpreters.', SyntaxWarning) elif self.multi_quadrant_mode: - raise SyntaxError('Circular arc interpolation in multi-quadrant mode (G74) is not implemented.') + warnings.warn('Deprecated G74 multi-quadant mode arc found. G74 is bad and you should feel bad.', SyntaxWarning) if self.current_region is None: - self.target.objects.append(self.graphics_state.interpolate(x, y, i, j)) + self.target.objects.append(self.graphics_state.interpolate(x, y, i, j, + multi_quadrant=bool(self.multi_quadrant_mode))) else: - self.current_region.append(self.graphics_state.interpolate(x, y, i, j, aperture=False)) + self.current_region.append(self.graphics_state.interpolate(x, y, i, j, aperture=False, + multi_quadrant=bool(self.multi_quadrant_mode))) elif op in ('D2', 'D02'): self.graphics_state.update_point(x, y) @@ -771,6 +795,9 @@ class GerberParser: warnings.warn('Deprecated SF (scale factor) statement found. This deprecated since rev. I1 (Dec 2012).', DeprecationWarning) self.graphics_state.scale_factor = a, b + def _parse_siemens_garbage(self, match): + self.generator_hints.append('siemens') + def _parse_comment(self, match): cmt = match["comment"].strip() @@ -798,6 +825,9 @@ class GerberParser: name = re.sub(r'\W+', '_', name) self.layer_hints.append(f'{name} copper') + elif cmt.startswith('Mentor Graphics'): + self.generator_hints.append('siemens') + else: self.target.comments.append(cmt) @@ -854,5 +884,7 @@ if __name__ == '__main__': parser.add_argument('testfile') args = parser.parse_args() - print(GerberFile.open(args.testfile).to_gerber()) + bounds = (0.0, 0.0), (6.0, 6.0) # bottom left, top right + svg = str(GerberFile.open(args.testfile).to_svg(force_bounds=bounds, arg_unit='inch', color='white')) + print(svg) -- cgit