Better detection of plated tools
This commit is contained in:
parent
97924d188b
commit
7053d320f0
4 changed files with 162 additions and 23 deletions
|
|
@ -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)
|
||||
|
|
|
|||
25
gerber/excellon_report/excellon_drr.py
Normal file
25
gerber/excellon_report/excellon_drr.py
Normal file
|
|
@ -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
|
||||
"""
|
||||
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue