summaryrefslogtreecommitdiff
path: root/gerber
diff options
context:
space:
mode:
authorGarret Fick <garret@ficksworkshop.com>2016-03-13 14:27:09 +0800
committerGarret Fick <garret@ficksworkshop.com>2016-03-13 14:27:09 +0800
commit7053d320f0b3e9404edb4c05710001ea58d44995 (patch)
tree63f8a77ea4ee53c9c8bd42737ed46494431f3140 /gerber
parent97924d188bf8fcc7d7537007464e65cbdc8c7bbb (diff)
downloadgerbonara-7053d320f0b3e9404edb4c05710001ea58d44995.tar.gz
gerbonara-7053d320f0b3e9404edb4c05710001ea58d44995.tar.bz2
gerbonara-7053d320f0b3e9404edb4c05710001ea58d44995.zip
Better detection of plated tools
Diffstat (limited to 'gerber')
-rwxr-xr-xgerber/excellon.py61
-rw-r--r--gerber/excellon_report/excellon_drr.py25
-rw-r--r--gerber/excellon_statements.py14
-rw-r--r--gerber/excellon_tool.py85
4 files changed, 162 insertions, 23 deletions
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 <garret@ficksworkshop.com>
+
+# 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<size>[0-9/.]+)\s+(?P<plated>P|N)\s+T(?P<toolid>[0-9]{2})\s+(?P<xtol>[0-9/.]+)\s+(?P<ytol>[0-9/.]+)')
- allegro_comment_mils = re.compile('Holesize (?P<toolid>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)) MILS Quantity = [0-9]+')
- allegro_comment_mm = re.compile('Holesize (?P<toolid>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)) MM Quantity = [0-9]+')
+ allegro_comment_mils = re.compile('Holesize (?P<toolid>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MILS Quantity = [0-9]+')
+ allegro2_comment_mils = re.compile('T(?P<toolid>[0-9]{1,2}) Holesize (?P<toolid2>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MILS Quantity = [0-9]+')
+ allegro_comment_mm = re.compile('Holesize (?P<toolid>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MM Quantity = [0-9]+')
+ allegro2_comment_mm = re.compile('T(?P<toolid>[0-9]{1,2}) Holesize (?P<toolid2>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(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