#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2019 Hiroshi Murayama <opiopan@gmail.com> 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 = [] 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') 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) 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: 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 _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): 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