From 81ae51d4be7f669172a95d03f4145d589fdbc319 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 8 Nov 2024 12:26:02 +0100 Subject: Improve allegro inner layer matching --- gerbonara/cli.py | 14 ++++++++++ gerbonara/layers.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 3 deletions(-) (limited to 'gerbonara') diff --git a/gerbonara/cli.py b/gerbonara/cli.py index 22aa098..5b67da8 100644 --- a/gerbonara/cli.py +++ b/gerbonara/cli.py @@ -23,8 +23,10 @@ import dataclasses import re import warnings import json +import sys import itertools import webbrowser +import warnings from pathlib import Path from .utils import MM, Inch @@ -37,6 +39,18 @@ from .cad.kicad import tmtheme from .cad import protoserve +def _showwarning(message, category, filename, lineno, file=None, line=None): + if file is None: + file = sys.stderr + + filename = Path(filename) + gerbonara_module_install_location = Path(__file__).parent.parent + if filename.is_relative_to(gerbonara_module_install_location): + filename = filename.relative_to(gerbonara_module_install_location) + + print(f'{filename}:{lineno}: {message}', file=file) +warnings.showwarning = _showwarning + def _print_version(ctx, param, value): if value and not ctx.resilient_parsing: click.echo(f'Version {__version__}') diff --git a/gerbonara/layers.py b/gerbonara/layers.py index f98adee..6069cdb 100644 --- a/gerbonara/layers.py +++ b/gerbonara/layers.py @@ -455,6 +455,7 @@ class LayerStack: given value. :rtype: :py:class:`LayerStack` """ + print_layermap = False if autoguess: generator, filemap = _best_match(files) @@ -480,13 +481,43 @@ class LayerStack: filemap[layer] = filemap.get(layer, []) + [fn] if 'autoguess' in filemap: - warnings.warn(f'This generator ({generator}) often exports ambiguous filenames. Falling back to autoguesser for some files. Use at your own peril.') - for key, values in _do_autoguess(filemap.pop('autoguess')).items(): + warnings.warn(f'This generator ({generator}) often exports ambiguous filenames. Falling back to autoguesser for some files. Use at your own peril. Autoguessed files: {", ".join(f.name for f in filemap["autoguess"])}') + print_layermap = True + autoguess_filenames = filemap.pop('autoguess') + + matched = set() + for key, values in _do_autoguess(autoguess_filenames).items(): filemap[key] = filemap.get(key, []) + values + matched |= set(values) + + if generator == 'allegro': + # Allegro gerbers often contain the inner layers with completely random filenames and no indication of + # layer ordering except for drawings in the mechanical files. We fall back to alphabetic ordering. + for fn in autoguess_filenames: + if fn not in matched: + with open(fn) as f: + header = f.read(16384) + if re.search(r'G04 Layer:\s*ETCH/.*\*', header): + filemap['unknown copper'] = filemap.get('unknown copper', []) + [fn] + + if (unk := filemap.pop('unknown copper', None)): + unk = sorted(unk, key=str) + if 'top copper' not in filemap: + filemap['top copper'], *unk = [unk] + if 'bottom copper' not in filemap: + *unk, filemap['bottom copper'] = [unk] + + i = 1 + while unk and i < 128: + key = f'inner_{i:02d} copper' + if key not in filemap: + filemap[key] = [unk.pop(0)] + i += 1 if sum(len(files) for files in filemap.values()) < 6 and autoguess: warnings.warn('Ambiguous gerber filenames. Trying last-resort autoguesser.') generator = None + print_layermap = True filemap = _do_autoguess(files) if len(filemap) < 6: raise ValueError('Cannot figure out gerber file mapping. Partial map is: ', filemap) @@ -518,6 +549,10 @@ class LayerStack: excellon_settings = FileSettings(number_format=(2, 4)) automatch_drill_scale = True + print('remaining filemap') + import pprint + pprint.pprint(filemap) + if len(filemap) < 6: raise SystemError('Cannot figure out gerber file mapping') # FIXME use layer metadata from comments and ipc file if available @@ -648,9 +683,42 @@ class LayerStack: for obj in drill_file.objects: obj.scale(scale) - return kls(layers, drill_pth, drill_npth, drill_layers, board_name=board_name, + stack = kls(layers, drill_pth, drill_npth, drill_layers, board_name=board_name, original_path=original_path, was_zipped=was_zipped, generator=[*all_generator_hints, None][0]) + if print_layermap: + warnings.warn('Auto-guessed layer map:\n' + stack.format_layer_map()) + return stack + + def format_layer_map(self): + lines = [] + def print_layer(prefix, file): + nonlocal lines + if file is None: + lines.append(f'{prefix} ') + else: + lines.append(f'{prefix} {file.original_path.name} {file}') + + lines.append(' Drill files:') + print_layer(' Plated holes:', self.drill_pth) + print_layer(' Nonplated holes:', self.drill_npth) + for i, l in enumerate(self._drill_layers): + print_layer(f' Additional drill layer {i}:', l) + print_layer(' Board outline:', self['mechanical outline']) + + lines.append(' Soldermask:') + print_layer(' Top:', self['top mask']) + print_layer(' Bottom:', self['bottom mask']) + + lines.append(' Silkscreen:') + print_layer(' Top:', self['top silk']) + print_layer(' Bottom:', self['bottom silk']) + + lines.append(' Copper:') + for (side, _use), layer in self.copper_layers: + print_layer(f' {side}:', layer) + return '\n'.join(lines) + def save_to_zipfile(self, path, prefix='', overwrite_existing=True, board_name=None, naming_scheme={}, gerber_settings=None, excellon_settings=None): """ Save this board into a zip file at the given path. For other options, see -- cgit