From 02954407703430dcefba0b165360d201fe8e205c Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 31 Mar 2023 16:31:44 +0200 Subject: Improve layer stack handling --- gerbonara/cam.py | 5 ++++- gerbonara/excellon.py | 22 ++++++++++++++++++---- gerbonara/layers.py | 11 +++++++++-- gerbonara/rs274x.py | 18 ++++++++++++++---- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/gerbonara/cam.py b/gerbonara/cam.py index 9819087..14d4c61 100644 --- a/gerbonara/cam.py +++ b/gerbonara/cam.py @@ -444,5 +444,8 @@ class LazyCamFile: def save(self, filename, *args, **kwargs): """ Copy this Gerber file to the new path. """ - shutil.copy(self.original_path, filename) + if 'instance' in self.__dict__: # instance has been loaded, and might have been modified + self.instance.save(filename, *args, **kwargs) + else: + shutil.copy(self.original_path, filename) diff --git a/gerbonara/excellon.py b/gerbonara/excellon.py index fbbeb50..1ce9c64 100755 --- a/gerbonara/excellon.py +++ b/gerbonara/excellon.py @@ -245,6 +245,16 @@ class ExcellonFile(CamFile): """ Test if there are multiple plating values used in this file. """ return len({obj.plated for obj in self.objects}) > 1 + @property + def is_plated_tristate(self): + if self.is_plated: + return True + + if self.is_nonplated: + return False + + return None + def append(self, obj_or_comment): """ Add a :py:class:`.GraphicObject` or a comment (str) to this file. """ if isinstance(obj_or_comment, str): @@ -252,11 +262,11 @@ class ExcellonFile(CamFile): else: self.objects.append(obj_or_comment) - def to_excellon(self): + def to_excellon(self, plated=None, errors='raise'): """ Counterpart to :py:meth:`~.rs274x.GerberFile.to_excellon`. Does nothing and returns :py:obj:`self`. """ return self - def to_gerber(self): + def to_gerber(self, errros='raise'): """ Convert this excellon file into a :py:class:`~.rs274x.GerberFile`. """ apertures = {} out = GerberFile() @@ -274,14 +284,18 @@ class ExcellonFile(CamFile): def generator(self): return self.generator_hints[0] if self.generator_hints else None - def merge(self, other): + def merge(self, other, mode='ignored', keep_settings=False): if other is None: return + + if not isinstance(other, ExcellonFile): + other = other.to_excellon(plated=self.is_plated_tristate) self.objects += other.objects self.comments += other.comments self.generator_hints = None - self.import_settings = None + if not keep_settings: + self.import_settings = None @classmethod def open(kls, filename, plated=None, settings=None, external_tools=None): diff --git a/gerbonara/layers.py b/gerbonara/layers.py index abdd570..b2b3900 100644 --- a/gerbonara/layers.py +++ b/gerbonara/layers.py @@ -113,16 +113,22 @@ class NamingScheme: def _match_files(filenames): matches = {} for generator, rules in MATCH_RULES.items(): + already_matched = set() gen = {} matches[generator] = gen for layer, regex in rules.items(): for fn in filenames: + if fn in already_matched: + continue + if (m := re.fullmatch(regex, fn.name, re.IGNORECASE)): if layer == 'inner copper': target = 'inner_' + ''.join(e or '' for e in m.groups()) + ' copper' else: target = layer + gen[target] = gen.get(target, []) + [fn] + already_matched.add(fn) return matches @@ -615,8 +621,10 @@ class LayerStack: if self.drill_pth is not None: yield get_name('plated drill', self.drill_pth), self.drill_pth + if self.drill_npth is not None: yield get_name('nonplated drill', self.drill_npth), self.drill_npth + for layer in self._drill_layers: yield get_name('unknown drill', layer), layer @@ -872,8 +880,7 @@ class LayerStack: npth_out.append(obj) self.drill_pth, self.drill_npth = pth_out, npth_out - self._drill_layers = unknown_out if unknown_out else None - self._drill_layers = [] + self._drill_layers = [unknown_out] if unknown_out else [] @property def drill_layers(self): diff --git a/gerbonara/rs274x.py b/gerbonara/rs274x.py index 5956b53..5ef93dd 100644 --- a/gerbonara/rs274x.py +++ b/gerbonara/rs274x.py @@ -73,7 +73,7 @@ class GerberFile(CamFile): self.apertures = [] # FIXME get rid of this? apertures are already in the objects. self.file_attrs = file_attrs or {} - def to_excellon(self, plated=None): + def to_excellon(self, plated=None, errors='raise'): """ Convert this excellon file into a :py:class:`~.excellon.ExcellonFile`. This will convert interpolated lines into slots, and circular aperture flashes into holes. Other features such as ``G36`` polygons or flashes with non-circular apertures will result in a :py:obj:`ValueError`. You can, of course, programmatically remove such @@ -83,7 +83,15 @@ class GerberFile(CamFile): for obj in self.objects: if not (isinstance(obj, go.Line) or isinstance(obj, go.Arc) or isinstance(obj, go.Flash)) or \ not isinstance(obj.aperture, apertures.CircleAperture): - raise ValueError(f'Cannot convert {obj} to excellon!') + if errors == 'raise': + raise ValueError(f'Cannot convert {obj} to excellon.') + elif errors == 'warn': + warnings.warn(f'Gerber to Excellon conversion: Cannot convert {obj} to excellon.') + continue + elif errors == 'ignore': + continue + else: + raise ValueError('Invalid "errors" parameter. Allowed values: "raise", "warn" or "ignore".') if not (new_tool := new_tools.get(id(obj.aperture))): # TODO plating? @@ -92,9 +100,9 @@ class GerberFile(CamFile): return ExcellonFile(objects=new_objs, comments=self.comments) - def to_gerber(self): + def to_gerber(self, errors='raise'): """ Counterpart to :py:meth:`~.excellon.ExcellonFile.to_gerber`. Does nothing and returns :py:obj:`self`. """ - return + return self def merge(self, other, mode='above', keep_settings=False): """ Merge ``other`` into ``self``, i.e. add all objects that are in ``other`` to ``self``. This resets @@ -110,6 +118,8 @@ class GerberFile(CamFile): if other is None: return + other = other.to_gerber() + if not keep_settings: self.import_settings = None self.comments += other.comments -- cgit