From 7053d320f0b3e9404edb4c05710001ea58d44995 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 13 Mar 2016 14:27:09 +0800 Subject: Better detection of plated tools --- gerber/excellon.py | 61 +++++++++++++++++------- gerber/excellon_report/excellon_drr.py | 25 ++++++++++ gerber/excellon_statements.py | 14 +++++- gerber/excellon_tool.py | 85 ++++++++++++++++++++++++++++++++-- 4 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 gerber/excellon_report/excellon_drr.py diff --git a/gerber/excellon.py b/gerber/excellon.py index 4456329..0637b23 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -175,21 +175,12 @@ class ExcellonFile(CamFile): def write(self, filename=None): filename = filename if filename is not None else self.filename with open(filename, 'w') as f: - - # Copy the header verbatim - for statement in self.statements: - if not isinstance(statement, ToolSelectionStmt): - f.write(statement.to_excellon(self.settings) + '\n') - else: - break - - # Write out coordinates for drill hits by tool - for tool in iter(self.tools.values()): - f.write(ToolSelectionStmt(tool.number).to_excellon(self.settings) + '\n') - for hit in self.hits: - if hit.tool.number == tool.number: - f.write(CoordinateStmt(*hit.position).to_excellon(self.settings) + '\n') - f.write(EndOfProgramStmt().to_excellon() + '\n') + self.writes(f) + + def writes(self, f): + # Copy the header verbatim + for statement in self.statements: + f.write(statement.to_excellon(self.settings) + '\n') def to_inch(self): """ @@ -300,6 +291,8 @@ class ExcellonParser(object): self.hits = [] self.active_tool = None self.pos = [0., 0.] + # Default for lated is None, which means we don't know + self.plated = ExcellonTool.PLATED_UNKNOWN if settings is not None: self.units = settings.units self.zeros = settings.zeros @@ -360,6 +353,12 @@ class ExcellonParser(object): if detected_format: self.format = detected_format + if "TYPE=PLATED" in comment_stmt.comment: + self.plated = ExcellonTool.PLATED_YES + + if "TYPE=NON_PLATED" in comment_stmt.comment: + self.plated = ExcellonTool.PLATED_NO + if "HEADER:" in comment_stmt.comment: self.state = "HEADER" @@ -370,7 +369,7 @@ class ExcellonParser(object): tools = ExcellonToolDefinitionParser(self._settings()).parse_raw(comment_stmt.comment) if len(tools) == 1: tool = tools[tools.keys()[0]] - self.comment_tools[tool.number] = tool + self._add_comment_tool(tool) elif line[:3] == 'M48': self.statements.append(HeaderBeginStmt()) @@ -503,7 +502,8 @@ class ExcellonParser(object): elif line[0] == 'T' and self.state == 'HEADER': if not ',OFF' in line and not ',ON' in line: - tool = ExcellonTool.from_excellon(line, self._settings()) + tool = ExcellonTool.from_excellon(line, self._settings(), None, self.plated) + self._merge_properties(tool) self.tools[tool.number] = tool self.statements.append(tool) else: @@ -572,6 +572,33 @@ class ExcellonParser(object): return FileSettings(units=self.units, format=self.format, zeros=self.zeros, notation=self.notation) + def _add_comment_tool(self, tool): + """ + Add a tool that was defined in the comments to this file. + + If we have already found this tool, then we will merge this comment tool definition into + the information for the tool + """ + + existing = self.tools.get(tool.number) + if existing and existing.plated == None: + existing.plated = tool.plated + + self.comment_tools[tool.number] = tool + + def _merge_properties(self, tool): + """ + When we have externally defined tools, merge the properties of that tool into this one + + For now, this is only plated + """ + + if tool.plated == ExcellonTool.PLATED_UNKNOWN: + ext_tool = self.ext_tools.get(tool.number) + + if ext_tool: + tool.plated = ext_tool.plated + def _get_tool(self, toolid): tool = self.tools.get(toolid) diff --git a/gerber/excellon_report/excellon_drr.py b/gerber/excellon_report/excellon_drr.py new file mode 100644 index 0000000..ab9e857 --- /dev/null +++ b/gerber/excellon_report/excellon_drr.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2015 Garret Fick + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Excellon DRR File module +==================== +**Excellon file classes** + +Extra parsers for allegro misc files that can be useful when the Excellon file doesn't contain parameter information +""" + diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 66641a1..18eaea1 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -111,9 +111,14 @@ class ExcellonTool(ExcellonStatement): hit_count : integer Number of tool hits in excellon file. """ + + PLATED_UNKNOWN = None + PLATED_YES = 'plated' + PLATED_NO = 'nonplated' + PLATED_OPTIONAL = 'optional' @classmethod - def from_excellon(cls, line, settings, id=None): + def from_excellon(cls, line, settings, id=None, plated=None): """ Create a Tool from an excellon file tool definition line. Parameters @@ -150,6 +155,10 @@ class ExcellonTool(ExcellonStatement): args['number'] = int(val) elif cmd == 'Z': args['depth_offset'] = parse_gerber_value(val, nformat, zero_suppression) + + if plated != ExcellonTool.PLATED_UNKNOWN: + # Sometimees we can can parse the + args['plated'] = plated return cls(settings, **args) @classmethod @@ -182,6 +191,8 @@ class ExcellonTool(ExcellonStatement): self.diameter = kwargs.get('diameter') self.max_hit_count = kwargs.get('max_hit_count') self.depth_offset = kwargs.get('depth_offset') + self.plated = kwargs.get('plated') + self.hit_count = 0 def to_excellon(self, settings=None): @@ -235,6 +246,7 @@ class ExcellonTool(ExcellonStatement): and self.rpm == other.rpm and self.depth_offset == other.depth_offset and self.max_hit_count == other.max_hit_count + and self.plated == other.plated and self.settings.units == other.settings.units) def __repr__(self): diff --git a/gerber/excellon_tool.py b/gerber/excellon_tool.py index 31d72d5..bd76e54 100644 --- a/gerber/excellon_tool.py +++ b/gerber/excellon_tool.py @@ -54,13 +54,17 @@ class ExcellonToolDefinitionParser(object): """ allegro_tool = re.compile(r'(?P[0-9/.]+)\s+(?PP|N)\s+T(?P[0-9]{2})\s+(?P[0-9/.]+)\s+(?P[0-9/.]+)') - allegro_comment_mils = re.compile('Holesize (?P[0-9]{1,2})\. = (?P[0-9/.]+) Tolerance = \+(?P[0-9/.]+)/-(?P[0-9/.]+) (?P(PLATED)|(NON_PLATED)) MILS Quantity = [0-9]+') - allegro_comment_mm = re.compile('Holesize (?P[0-9]{1,2})\. = (?P[0-9/.]+) Tolerance = \+(?P[0-9/.]+)/-(?P[0-9/.]+) (?P(PLATED)|(NON_PLATED)) MM Quantity = [0-9]+') + allegro_comment_mils = re.compile('Holesize (?P[0-9]{1,2})\. = (?P[0-9/.]+) Tolerance = \+(?P[0-9/.]+)/-(?P[0-9/.]+) (?P(PLATED)|(NON_PLATED)|(OPTIONAL)) MILS Quantity = [0-9]+') + allegro2_comment_mils = re.compile('T(?P[0-9]{1,2}) Holesize (?P[0-9]{1,2})\. = (?P[0-9/.]+) Tolerance = \+(?P[0-9/.]+)/-(?P[0-9/.]+) (?P(PLATED)|(NON_PLATED)|(OPTIONAL)) MILS Quantity = [0-9]+') + allegro_comment_mm = re.compile('Holesize (?P[0-9]{1,2})\. = (?P[0-9/.]+) Tolerance = \+(?P[0-9/.]+)/-(?P[0-9/.]+) (?P(PLATED)|(NON_PLATED)|(OPTIONAL)) MM Quantity = [0-9]+') + allegro2_comment_mm = re.compile('T(?P[0-9]{1,2}) Holesize (?P[0-9]{1,2})\. = (?P[0-9/.]+) Tolerance = \+(?P[0-9/.]+)/-(?P[0-9/.]+) (?P(PLATED)|(NON_PLATED)|(OPTIONAL)) MM Quantity = [0-9]+') matchers = [ (allegro_tool, 'mils'), (allegro_comment_mils, 'mils'), + (allegro2_comment_mils, 'mils'), (allegro_comment_mm, 'mm'), + (allegro2_comment_mm, 'mm'), ] def __init__(self, settings=None): @@ -81,7 +85,7 @@ class ExcellonToolDefinitionParser(object): unit = matcher[1] size = float(m.group('size')) - plated = m.group('plated') + platedstr = m.group('plated') toolid = int(m.group('toolid')) xtol = float(m.group('xtol')) ytol = float(m.group('ytol')) @@ -90,7 +94,16 @@ class ExcellonToolDefinitionParser(object): xtol = self._convert_length(xtol, unit) ytol = self._convert_length(ytol, unit) - tool = ExcellonTool(None, number=toolid, diameter=size) + if platedstr == 'PLATED': + plated = ExcellonTool.PLATED_YES + elif platedstr == 'NON_PLATED': + plated = ExcellonTool.PLATED_NO + elif platedstr == 'OPTIONAL': + plated = ExcellonTool.PLATED_OPTIONAL + else: + plated = ExcellonTool.PLATED_UNKNOWN + + tool = ExcellonTool(None, number=toolid, diameter=size, plated=plated) self.tools[tool.number] = tool @@ -108,4 +121,66 @@ class ExcellonToolDefinitionParser(object): else: # Already in mm return value - \ No newline at end of file + +def loads_rep(data, settings=None): + """ Read tool report information generated by PADS and return a map of tools + Parameters + ---------- + data : string + string containing Excellon Report file contents + + Returns + ------- + dict tool name: ExcellonTool + + """ + return ExcellonReportParser(settings).parse_raw(data) + +class ExcellonReportParser(object): + + # We sometimes get files with different encoding, so we can't actually + # match the text - the best we can do it detect the table header + header = re.compile(r'====\s+====\s+====\s+====\s+=====\s+===') + + def __init__(self, settings=None): + self.tools = {} + self.settings = settings + + self.found_header = False + + def parse_raw(self, data): + for line in StringIO(data): + self._parse(line.strip()) + + return self.tools + + def _parse(self, line): + + # skip empty lines and "comments" + if not line.strip(): + return + + if not self.found_header: + # Try to find the heaader, since we need that to be sure we understand the contents correctly. + if ExcellonReportParser.header.match(line): + self.found_header = True + + elif line[0] != '=': + # Already found the header, so we know to to map the contents + parts = line.split() + if len(parts) == 6: + toolid = int(parts[0]) + size = float(parts[1]) + if parts[2] == 'x': + plated = ExcellonTool.PLATED_YES + elif parts[2] == '-': + plated = ExcellonTool.PLATED_NO + else: + plated = ExcellonTool.PLATED_UNKNOWN + feedrate = int(parts[3]) + speed = int(parts[4]) + qty = int(parts[5]) + + tool = ExcellonTool(None, number=toolid, diameter=size, plated=plated, feed_rate=feedrate, rpm=speed) + + self.tools[tool.number] = tool \ No newline at end of file -- cgit