From 9febca7da6a730b3b3ca3a54129a9f88e5c44d14 Mon Sep 17 00:00:00 2001 From: opiopan Date: Thu, 21 Mar 2019 22:00:32 +0900 Subject: initial commit --- gerberex/composition.py | 201 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 gerberex/composition.py (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py new file mode 100644 index 0000000..2613a61 --- /dev/null +++ b/gerberex/composition.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2019 Hiroshi Murayama +import os +from functools import reduce +from gerber.cam import FileSettings +from gerber.gerber_statements import EofStmt +from gerber.excellon_statements import * +import gerberex.rs274x +import gerberex.excellon +import gerberex.dxf + +class Composition(object): + def __init__(self, settings = None, comments = None): + self.settings = settings + self.comments = comments if comments != None else [] + +class GerberComposition(Composition): + APERTURE_ID_BIAS = 10 + + def __init__(self, settings=None, comments=None): + super(GerberComposition, self).__init__(settings, comments) + self.param_statements = [] + self.aperture_macros = {} + self.apertures = [] + self.drawings = [] + + def merge(self, file): + if isinstance(file, gerberex.rs274x.GerberFile): + self._merge_gerber(file) + elif isinstance(file, gerberex.dxf.DxfFile): + self._merge_dxf(file) + else: + raise Exception('unsupported file type') + + def dump(self, path): + def statements(): + for s in self.param_statements: + yield s + for k in self.aperture_macros: + yield self.aperture_macros[k] + for s in self.apertures: + yield s + for s in self.drawings: + yield s + yield EofStmt() + with open(path, 'w') as f: + for statement in statements(): + f.write(statement.to_gerber(self.settings) + '\n') + + def _merge_gerber(self, file): + param_statements = [] + aperture_macro_map = {} + aperture_map = {} + + if self.settings: + if self.settings.units == 'metric': + file.to_metric() + else: + file.to_inch() + + for statement in file.statements: + if statement.type == 'COMMENT': + self.comments.append(statement.comment) + elif statement.type == 'PARAM': + if statement.param == 'AM': + name = statement.name + newname = self._register_aperture_macro(statement) + aperture_macro_map[name] = newname + elif statement.param == 'AD': + if not statement.shape in ['C', 'R', 'O']: + statement.shape = aperture_macro_map[statement.shape] + dnum = statement.d + newdnum = self._register_aperture(statement) + aperture_map[dnum] = newdnum + elif statement.param == 'LP': + self.drawings.append(statement) + else: + param_statements.append(statement) + elif statement.type in ['EOF', "DEPRECATED"]: + pass + else: + if statement.type == 'APERTURE': + statement.d = aperture_map[statement.d] + self.drawings.append(statement) + + if not self.settings: + self.settings = file.settings + self.param_statements = param_statements + + def _merge_dxf(self, file): + if self.settings: + if self.settings.units == 'metric': + file.to_metric() + else: + file.to_inch() + + file.dcode = self._register_aperture(file.aperture) + self.drawings.append(file.statements) + + if not self.settings: + self.settings = file.settings + self.param_statements = file.header + + + def _register_aperture_macro(self, statement): + name = statement.name + newname = name + offset = 0 + while newname in self.aperture_macros: + offset += 1 + newname = '%s_%d' % (name, offset) + statement.name = newname + self.aperture_macros[newname] = statement + return newname + + def _register_aperture(self, statement): + statement.d = len(self.apertures) + self.APERTURE_ID_BIAS + self.apertures.append(statement) + return statement.d + +class DrillComposition(Composition): + def __init__(self, settings=None, comments=None): + super(DrillComposition, self).__init__(settings, comments) + self.header1_statements = [] + self.header2_statements = [] + self.tools = [] + self.hits = [] + + def merge(self, file): + if isinstance(file, gerberex.excellon.ExcellonFileEx): + self._merge_excellon(file) + else: + raise Exception('unsupported file type') + + def dump(self, path): + def statements(): + for s in self.header1_statements: + yield s.to_excellon(self.settings) + for t in self.tools: + yield t.to_excellon(self.settings) + for s in self.header2_statements: + yield s.to_excellon(self.settings) + for t in self.tools: + yield ToolSelectionStmt(t.number).to_excellon(self.settings) + for h in self.hits: + if h.tool.number == t.number: + yield CoordinateStmt(*h.position).to_excellon(self.settings) + yield EndOfProgramStmt().to_excellon() + + with open(path, 'w') as f: + for statement in statements(): + f.write(statement + '\n') + + def _merge_excellon(self, file): + tool_map = {} + + if not self.settings: + self.settings = file.settings + else: + if self.settings.units == 'metric': + file.to_metric() + else: + file.to_inch() + + if not self.header1_statements: + in_header1 = True + for statement in file.statements: + if not isinstance(statement, ToolSelectionStmt): + if isinstance(statement, ExcellonTool): + in_header1 = False + else: + if in_header1: + self.header1_statements.append(statement) + else: + self.header2_statements.append(statement) + else: + break + + for tool in iter(file.tools.values()): + num = tool.number + tool_map[num] = self._register_tool(tool) + + for hit in file.hits: + hit.tool = tool_map[hit.tool.number] + self.hits.append(hit) + + def _register_tool(self, tool): + for existing in self.tools: + if existing.equivalent(tool): + return existing + new_tool = ExcellonTool.from_tool(tool) + new_tool.settings = self.settings + def toolnums(): + for tool in self.tools: + yield tool.number + max_num = reduce(lambda x, y: x if x > y else y, toolnums(), 0) + new_tool.number = max_num + 1 + self.tools.append(new_tool) + return new_tool -- cgit From 690df56bb71020901167605a87ec451081fa18d7 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sat, 23 Mar 2019 21:59:13 +0900 Subject: add rotation fuction --- gerberex/composition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 2613a61..afcaf97 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -101,7 +101,7 @@ class GerberComposition(Composition): if not self.settings: self.settings = file.settings - self.param_statements = file.header + self.param_statements = [file.header] def _register_aperture_macro(self, statement): -- cgit From fcd704e1eef9034e2000f55b2918d7df41379408 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sat, 30 Mar 2019 11:16:13 +0900 Subject: add mouse bites generator function --- gerberex/composition.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index afcaf97..73f5702 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -127,10 +127,13 @@ class DrillComposition(Composition): self.header2_statements = [] self.tools = [] self.hits = [] + self.dxf_statements = [] def merge(self, file): if isinstance(file, gerberex.excellon.ExcellonFileEx): self._merge_excellon(file) + elif isinstance(file, gerberex.DxfFile): + self._merge_dxf(file) else: raise Exception('unsupported file type') @@ -147,6 +150,9 @@ class DrillComposition(Composition): for h in self.hits: if h.tool.number == t.number: yield CoordinateStmt(*h.position).to_excellon(self.settings) + for num, statement in self.dxf_statements: + if num == t.number: + yield statement.to_excellon(self.settings) yield EndOfProgramStmt().to_excellon() with open(path, 'w') as f: @@ -186,6 +192,22 @@ class DrillComposition(Composition): hit.tool = tool_map[hit.tool.number] self.hits.append(hit) + def _merge_dxf(self, file): + if not self.settings: + self.settings = file.settings + else: + if self.settings.units == 'metric': + file.to_metric() + else: + file.to_inch() + + if not self.header1_statements: + self.header1_statements = file.header + self.header2_statements = file.header2 + + tool = self._register_tool(ExcellonTool(self.settings, number=1, diameter=file.width)) + self.dxf_statements.append((tool.number, file.statements)) + def _register_tool(self, tool): for existing in self.tools: if existing.equivalent(tool): -- cgit From e3c59e39cf9bc64ce9d76c324b82956a65515f16 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sun, 7 Apr 2019 22:22:33 +0900 Subject: expand test and fix many issues --- gerberex/composition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 73f5702..7abf090 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -202,8 +202,8 @@ class DrillComposition(Composition): file.to_inch() if not self.header1_statements: - self.header1_statements = file.header - self.header2_statements = file.header2 + self.header1_statements = [file.header] + self.header2_statements = [file.header2] tool = self._register_tool(ExcellonTool(self.settings, number=1, diameter=file.width)) self.dxf_statements.append((tool.number, file.statements)) -- cgit From 89b5b714c9d2e00ebcf849783fc30bada956dd59 Mon Sep 17 00:00:00 2001 From: Marin Mikaƫl <41113988+MarinMikael@users.noreply.github.com> Date: Wed, 24 Jul 2019 01:19:09 +0900 Subject: Update composition.py Add DrillSlot support to excellon composition. --- gerberex/composition.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 7abf090..29725ba 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -7,6 +7,7 @@ from functools import reduce from gerber.cam import FileSettings from gerber.gerber_statements import EofStmt from gerber.excellon_statements import * +from gerber.excellon import DrillSlot, DrillHit import gerberex.rs274x import gerberex.excellon import gerberex.dxf @@ -149,7 +150,10 @@ class DrillComposition(Composition): yield ToolSelectionStmt(t.number).to_excellon(self.settings) for h in self.hits: if h.tool.number == t.number: - yield CoordinateStmt(*h.position).to_excellon(self.settings) + if type(h) == DrillSlot: + yield SlotStmt(*h.start, *h.end).to_excellon(self.settings) + elif type(h) == DrillHit: + yield CoordinateStmt(*h.position).to_excellon(self.settings) for num, statement in self.dxf_statements: if num == t.number: yield statement.to_excellon(self.settings) -- cgit From 22f4c8a3f5bdce243908f3787216344b200902df Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sat, 17 Aug 2019 23:38:30 +0900 Subject: router mode and G85 slot in excellon file is supported --- gerberex/composition.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 29725ba..7f691f5 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -150,10 +150,7 @@ class DrillComposition(Composition): yield ToolSelectionStmt(t.number).to_excellon(self.settings) for h in self.hits: if h.tool.number == t.number: - if type(h) == DrillSlot: - yield SlotStmt(*h.start, *h.end).to_excellon(self.settings) - elif type(h) == DrillHit: - yield CoordinateStmt(*h.position).to_excellon(self.settings) + yield h.to_excellon(self.settings) for num, statement in self.dxf_statements: if num == t.number: yield statement.to_excellon(self.settings) -- cgit From 13ab9db6e7571f3d0fcb406bfe6795eea9ce4e1c Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sun, 25 Aug 2019 20:16:53 +0900 Subject: support incremental coordinate for excellon --- gerberex/composition.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 7f691f5..7b1548e 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -124,8 +124,6 @@ class GerberComposition(Composition): class DrillComposition(Composition): def __init__(self, settings=None, comments=None): super(DrillComposition, self).__init__(settings, comments) - self.header1_statements = [] - self.header2_statements = [] self.tools = [] self.hits = [] self.dxf_statements = [] @@ -140,12 +138,6 @@ class DrillComposition(Composition): def dump(self, path): def statements(): - for s in self.header1_statements: - yield s.to_excellon(self.settings) - for t in self.tools: - yield t.to_excellon(self.settings) - for s in self.header2_statements: - yield s.to_excellon(self.settings) for t in self.tools: yield ToolSelectionStmt(t.number).to_excellon(self.settings) for h in self.hits: @@ -157,6 +149,7 @@ class DrillComposition(Composition): yield EndOfProgramStmt().to_excellon() with open(path, 'w') as f: + gerberex.excellon.write_excellon_header(f, self.settings, self.tools) for statement in statements(): f.write(statement + '\n') @@ -171,20 +164,6 @@ class DrillComposition(Composition): else: file.to_inch() - if not self.header1_statements: - in_header1 = True - for statement in file.statements: - if not isinstance(statement, ToolSelectionStmt): - if isinstance(statement, ExcellonTool): - in_header1 = False - else: - if in_header1: - self.header1_statements.append(statement) - else: - self.header2_statements.append(statement) - else: - break - for tool in iter(file.tools.values()): num = tool.number tool_map[num] = self._register_tool(tool) @@ -202,10 +181,6 @@ class DrillComposition(Composition): else: file.to_inch() - if not self.header1_statements: - self.header1_statements = [file.header] - self.header2_statements = [file.header2] - tool = self._register_tool(ExcellonTool(self.settings, number=1, diameter=file.width)) self.dxf_statements.append((tool.number, file.statements)) -- cgit From 2b1c751ff76ebd6901633235ee694cc93dabce81 Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Mon, 9 Sep 2019 09:07:38 +0900 Subject: improve compatibility with RS-274x specification: - can merge multiple files having different file scope modifier, such as AS, MI, OF, SF, and IR - support modal coordinate notation --- gerberex/composition.py | 53 ++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 7b1548e..634640e 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -22,7 +22,6 @@ class GerberComposition(Composition): def __init__(self, settings=None, comments=None): super(GerberComposition, self).__init__(settings, comments) - self.param_statements = [] self.aperture_macros = {} self.apertures = [] self.drawings = [] @@ -37,8 +36,6 @@ class GerberComposition(Composition): def dump(self, path): def statements(): - for s in self.param_statements: - yield s for k in self.aperture_macros: yield self.aperture_macros[k] for s in self.apertures: @@ -46,12 +43,14 @@ class GerberComposition(Composition): for s in self.drawings: yield s yield EofStmt() + self.settings.notation = 'absolute' + self.settings.zeros = 'trailing' with open(path, 'w') as f: + gerberex.rs274x.write_gerber_header(f, self.settings) for statement in statements(): f.write(statement.to_gerber(self.settings) + '\n') def _merge_gerber(self, file): - param_statements = [] aperture_macro_map = {} aperture_map = {} @@ -61,34 +60,27 @@ class GerberComposition(Composition): else: file.to_inch() - for statement in file.statements: - if statement.type == 'COMMENT': - self.comments.append(statement.comment) - elif statement.type == 'PARAM': - if statement.param == 'AM': - name = statement.name - newname = self._register_aperture_macro(statement) - aperture_macro_map[name] = newname - elif statement.param == 'AD': - if not statement.shape in ['C', 'R', 'O']: - statement.shape = aperture_macro_map[statement.shape] - dnum = statement.d - newdnum = self._register_aperture(statement) - aperture_map[dnum] = newdnum - elif statement.param == 'LP': - self.drawings.append(statement) - else: - param_statements.append(statement) - elif statement.type in ['EOF', "DEPRECATED"]: - pass - else: - if statement.type == 'APERTURE': - statement.d = aperture_map[statement.d] - self.drawings.append(statement) + for macro in file.aperture_macros: + statement = file.aperture_macros[macro] + name = statement.name + newname = self._register_aperture_macro(statement) + aperture_macro_map[name] = newname + + for statement in file.aperture_defs: + if statement.param == 'AD': + if statement.shape in aperture_macro_map: + statement.shape = aperture_macro_map[statement.shape] + dnum = statement.d + newdnum = self._register_aperture(statement) + aperture_map[dnum] = newdnum + + for statement in file.main_statements: + if statement.type == 'APERTURE': + statement.d = aperture_map[statement.d] + self.drawings.append(statement) if not self.settings: - self.settings = file.settings - self.param_statements = param_statements + self.settings = file.context def _merge_dxf(self, file): if self.settings: @@ -102,7 +94,6 @@ class GerberComposition(Composition): if not self.settings: self.settings = file.settings - self.param_statements = [file.header] def _register_aperture_macro(self, statement): -- cgit From d7a069324222bb8f69adc9b1c815fc9f3f6a29d6 Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Mon, 30 Sep 2019 18:52:17 +0900 Subject: fix a issue that coordinate normalization for excellon is imperfect --- gerberex/composition.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gerberex/composition.py') diff --git a/gerberex/composition.py b/gerberex/composition.py index 634640e..b5dffb1 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -138,7 +138,9 @@ class DrillComposition(Composition): if num == t.number: yield statement.to_excellon(self.settings) yield EndOfProgramStmt().to_excellon() - + + self.settings.notation = 'absolute' + self.settings.zeros = 'trailing' with open(path, 'w') as f: gerberex.excellon.write_excellon_header(f, self.settings, self.tools) for statement in statements(): -- cgit