Parse misc nc drill files
This commit is contained in:
parent
206f4c57ab
commit
4e838df32a
5 changed files with 300 additions and 12 deletions
|
|
@ -32,6 +32,7 @@ except(ImportError):
|
|||
from io import StringIO
|
||||
|
||||
from .excellon_statements import *
|
||||
from .excellon_tool import ExcellonToolDefinitionParser
|
||||
from .cam import CamFile, FileSettings
|
||||
from .primitives import Drill
|
||||
from .utils import inch, metric
|
||||
|
|
@ -56,12 +57,15 @@ def read(filename):
|
|||
settings = FileSettings(**detect_excellon_format(data))
|
||||
return ExcellonParser(settings).parse(filename)
|
||||
|
||||
def loads(data):
|
||||
def loads(data, settings = None, tools = None):
|
||||
""" Read data from string and return an ExcellonFile
|
||||
Parameters
|
||||
----------
|
||||
data : string
|
||||
string containing Excellon file contents
|
||||
|
||||
tools: dict (optional)
|
||||
externally defined tools
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -70,8 +74,9 @@ def loads(data):
|
|||
|
||||
"""
|
||||
# File object should use settings from source file by default.
|
||||
settings = FileSettings(**detect_excellon_format(data))
|
||||
return ExcellonParser(settings).parse_raw(data)
|
||||
if not settings:
|
||||
settings = FileSettings(**detect_excellon_format(data))
|
||||
return ExcellonParser(settings, tools).parse_raw(data)
|
||||
|
||||
|
||||
class DrillHit(object):
|
||||
|
|
@ -199,7 +204,7 @@ class ExcellonFile(CamFile):
|
|||
for primitive in self.primitives:
|
||||
primitive.to_inch()
|
||||
for hit in self.hits:
|
||||
hit.position = tuple(map(inch, hit,position))
|
||||
hit.position = tuple(map(inch, hit.position))
|
||||
|
||||
|
||||
def to_metric(self):
|
||||
|
|
@ -282,7 +287,7 @@ class ExcellonParser(object):
|
|||
settings : FileSettings or dict-like
|
||||
Excellon file settings to use when interpreting the excellon file.
|
||||
"""
|
||||
def __init__(self, settings=None):
|
||||
def __init__(self, settings=None, ext_tools=None):
|
||||
self.notation = 'absolute'
|
||||
self.units = 'inch'
|
||||
self.zeros = 'leading'
|
||||
|
|
@ -290,6 +295,8 @@ class ExcellonParser(object):
|
|||
self.state = 'INIT'
|
||||
self.statements = []
|
||||
self.tools = {}
|
||||
self.ext_tools = ext_tools or {}
|
||||
self.comment_tools = {}
|
||||
self.hits = []
|
||||
self.active_tool = None
|
||||
self.pos = [0., 0.]
|
||||
|
|
@ -352,6 +359,18 @@ class ExcellonParser(object):
|
|||
detected_format = tuple([int(x) for x in comment_stmt.comment.split('=')[1].split(":")])
|
||||
if detected_format:
|
||||
self.format = detected_format
|
||||
|
||||
if "HEADER:" in comment_stmt.comment:
|
||||
self.state = "HEADER"
|
||||
|
||||
if " Holesize " in comment_stmt.comment:
|
||||
self.state = "HEADER"
|
||||
|
||||
# Parse this as a hole definition
|
||||
tools = ExcellonToolDefinitionParser(self._settings()).parse_raw(comment_stmt.comment)
|
||||
if len(tools) == 1:
|
||||
tool = tools[tools.keys()[0]]
|
||||
self.comment_tools[tool.number] = tool
|
||||
|
||||
elif line[:3] == 'M48':
|
||||
self.statements.append(HeaderBeginStmt())
|
||||
|
|
@ -363,6 +382,16 @@ class ExcellonParser(object):
|
|||
self.state = 'DRILL'
|
||||
elif self.state == 'INIT':
|
||||
self.state = 'HEADER'
|
||||
|
||||
elif line[:3] == 'M00' and self.state == 'DRILL':
|
||||
if self.active_tool:
|
||||
cur_tool_number = self.active_tool.number
|
||||
next_tool = self._get_tool(cur_tool_number + 1)
|
||||
|
||||
self.statements.append(NextToolSelectionStmt(self.active_tool, next_tool))
|
||||
self.active_tool = next_tool
|
||||
else:
|
||||
raise Exception('Invalid state exception')
|
||||
|
||||
elif line[:3] == 'M95':
|
||||
self.statements.append(HeaderEndStmt())
|
||||
|
|
@ -480,8 +509,10 @@ class ExcellonParser(object):
|
|||
|
||||
# T0 is used as END marker, just ignore
|
||||
if stmt.tool != 0:
|
||||
# FIXME: for weird files with no tools defined, original calc from gerbv
|
||||
if stmt.tool not in self.tools:
|
||||
tool = self._get_tool(stmt.tool)
|
||||
|
||||
if not tool:
|
||||
# FIXME: for weird files with no tools defined, original calc from gerbv
|
||||
if self._settings().units == "inch":
|
||||
diameter = (16 + 8 * stmt.tool) / 1000.0;
|
||||
else:
|
||||
|
|
@ -496,7 +527,7 @@ class ExcellonParser(object):
|
|||
self.statements.insert(i, tool)
|
||||
break
|
||||
|
||||
self.active_tool = self.tools[stmt.tool]
|
||||
self.active_tool = tool
|
||||
|
||||
elif line[0] == 'R' and self.state != 'HEADER':
|
||||
stmt = RepeatHoleStmt.from_excellon(line, self._settings())
|
||||
|
|
@ -523,6 +554,9 @@ class ExcellonParser(object):
|
|||
if y is not None:
|
||||
self.pos[1] += y
|
||||
if self.state == 'DRILL':
|
||||
if not self.active_tool:
|
||||
self.active_tool = self._get_tool(1)
|
||||
|
||||
self.hits.append(DrillHit(self.active_tool, tuple(self.pos)))
|
||||
self.active_tool._hit()
|
||||
else:
|
||||
|
|
@ -531,7 +565,23 @@ class ExcellonParser(object):
|
|||
def _settings(self):
|
||||
return FileSettings(units=self.units, format=self.format,
|
||||
zeros=self.zeros, notation=self.notation)
|
||||
|
||||
|
||||
def _get_tool(self, toolid):
|
||||
|
||||
tool = self.tools.get(toolid)
|
||||
if not tool:
|
||||
tool = self.comment_tools.get(toolid)
|
||||
if tool:
|
||||
tool.settings = self._settings()
|
||||
self.tools[toolid] = tool
|
||||
|
||||
if not tool:
|
||||
tool = self.ext_tools.get(toolid)
|
||||
if tool:
|
||||
tool.settings = self._settings()
|
||||
self.tools[toolid] = tool
|
||||
|
||||
return tool
|
||||
|
||||
def detect_excellon_format(data=None, filename=None):
|
||||
""" Detect excellon file decimal format and zero-suppression settings.
|
||||
|
|
|
|||
105
gerber/excellon_settings.py
Normal file
105
gerber/excellon_settings.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from argparse import PARSER
|
||||
|
||||
# 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 Settings Definition File module
|
||||
====================
|
||||
**Excellon file classes**
|
||||
|
||||
This module provides Excellon file classes and parsing utilities
|
||||
"""
|
||||
|
||||
import re
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except(ImportError):
|
||||
from io import StringIO
|
||||
|
||||
from .cam import FileSettings
|
||||
|
||||
def loads(data):
|
||||
""" Read settings file information and return an FileSettings
|
||||
Parameters
|
||||
----------
|
||||
data : string
|
||||
string containing Excellon settings file contents
|
||||
|
||||
Returns
|
||||
-------
|
||||
file settings: FileSettings
|
||||
|
||||
"""
|
||||
|
||||
return ExcellonSettingsParser().parse_raw(data)
|
||||
|
||||
def map_coordinates(value):
|
||||
if value == 'ABSOLUTE':
|
||||
return 'absolute'
|
||||
return 'relative'
|
||||
|
||||
def map_units(value):
|
||||
if value == 'ENGLISH':
|
||||
return 'inch'
|
||||
return 'metric'
|
||||
|
||||
def map_boolean(value):
|
||||
return value == 'YES'
|
||||
|
||||
SETTINGS_KEYS = {
|
||||
'INTEGER-PLACES': (int, 'format-int'),
|
||||
'DECIMAL-PLACES': (int, 'format-dec'),
|
||||
'COORDINATES': (map_coordinates, 'notation'),
|
||||
'OUTPUT-UNITS': (map_units, 'units'),
|
||||
}
|
||||
|
||||
class ExcellonSettingsParser(object):
|
||||
"""Excellon Settings PARSER
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.values = {}
|
||||
self.settings = None
|
||||
|
||||
def parse_raw(self, data):
|
||||
for line in StringIO(data):
|
||||
self._parse(line.strip())
|
||||
|
||||
# Create the FileSettings object
|
||||
self.settings = FileSettings(
|
||||
notation=self.values['notation'],
|
||||
units=self.values['units'],
|
||||
format=(self.values['format-int'], self.values['format-dec'])
|
||||
)
|
||||
|
||||
return self.settings
|
||||
|
||||
def _parse(self, line):
|
||||
|
||||
line_items = line.split()
|
||||
if len(line_items) == 2:
|
||||
|
||||
item_type_info = SETTINGS_KEYS.get(line_items[0])
|
||||
if item_type_info:
|
||||
# Convert the value to the expected type
|
||||
item_value = item_type_info[0](line_items[1])
|
||||
|
||||
self.values[item_type_info[1]] = item_value
|
||||
|
|
@ -36,7 +36,8 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt',
|
|||
'ExcellonStatement', 'ZAxisRoutPositionStmt',
|
||||
'RetractWithClampingStmt', 'RetractWithoutClampingStmt',
|
||||
'CutterCompensationOffStmt', 'CutterCompensationLeftStmt',
|
||||
'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt']
|
||||
'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt',
|
||||
'NextToolSelectionStmt']
|
||||
|
||||
|
||||
class ExcellonStatement(object):
|
||||
|
|
@ -267,7 +268,28 @@ class ToolSelectionStmt(ExcellonStatement):
|
|||
if self.compensation_index is not None:
|
||||
stmt += '%02d' % self.compensation_index
|
||||
return stmt
|
||||
|
||||
|
||||
class NextToolSelectionStmt(ExcellonStatement):
|
||||
|
||||
# TODO the statement exists outside of the context of the file,
|
||||
# so it is imposible to know that it is really the next tool
|
||||
|
||||
def __init__(self, cur_tool, next_tool, **kwargs):
|
||||
"""
|
||||
Select the next tool in the wheel.
|
||||
Parameters
|
||||
----------
|
||||
cur_tool : the tool that is currently selected
|
||||
next_tool : the that that is now selected
|
||||
"""
|
||||
super(NextToolSelectionStmt, self).__init__(**kwargs)
|
||||
|
||||
self.cur_tool = cur_tool
|
||||
self.next_tool = next_tool
|
||||
|
||||
def to_excellon(self, settings=None):
|
||||
stmt = 'M00'
|
||||
return stmt
|
||||
|
||||
class ZAxisInfeedRateStmt(ExcellonStatement):
|
||||
|
||||
|
|
|
|||
111
gerber/excellon_tool.py
Normal file
111
gerber/excellon_tool.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#!/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 Tool Definition File module
|
||||
====================
|
||||
**Excellon file classes**
|
||||
|
||||
This module provides Excellon file classes and parsing utilities
|
||||
"""
|
||||
|
||||
import re
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except(ImportError):
|
||||
from io import StringIO
|
||||
|
||||
from .excellon_statements import ExcellonTool
|
||||
|
||||
def loads(data, settings=None):
|
||||
""" Read tool file information and return a map of tools
|
||||
Parameters
|
||||
----------
|
||||
data : string
|
||||
string containing Excellon Tool Definition file contents
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict tool name: ExcellonTool
|
||||
|
||||
"""
|
||||
return ExcellonToolDefinitionParser(settings).parse_raw(data)
|
||||
|
||||
class ExcellonToolDefinitionParser(object):
|
||||
""" Excellon File Parser
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
"""
|
||||
|
||||
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]+')
|
||||
|
||||
matchers = [
|
||||
(allegro_tool, 'mils'),
|
||||
(allegro_comment_mils, 'mils'),
|
||||
(allegro_comment_mils, 'mm'),
|
||||
]
|
||||
|
||||
def __init__(self, settings=None):
|
||||
self.tools = {}
|
||||
self.settings = settings
|
||||
|
||||
def parse_raw(self, data):
|
||||
for line in StringIO(data):
|
||||
self._parse(line.strip())
|
||||
|
||||
return self.tools
|
||||
|
||||
def _parse(self, line):
|
||||
|
||||
for matcher in ExcellonToolDefinitionParser.matchers:
|
||||
m = matcher[0].match(line)
|
||||
if m:
|
||||
unit = matcher[1]
|
||||
|
||||
size = float(m.group('size'))
|
||||
plated = m.group('plated')
|
||||
toolid = int(m.group('toolid'))
|
||||
xtol = float(m.group('xtol'))
|
||||
ytol = float(m.group('ytol'))
|
||||
|
||||
size = self._convert_length(size, unit)
|
||||
xtol = self._convert_length(xtol, unit)
|
||||
ytol = self._convert_length(ytol, unit)
|
||||
|
||||
tool = ExcellonTool(None, number=toolid, diameter=size)
|
||||
|
||||
self.tools[tool.number] = tool
|
||||
|
||||
break
|
||||
|
||||
def _convert_length(self, value, unit):
|
||||
|
||||
# Convert the value to mm
|
||||
if unit == 'mils':
|
||||
value /= 39.3700787402
|
||||
|
||||
# Now convert to the settings unit
|
||||
if self.settings.units == 'inch':
|
||||
return value / 25.4
|
||||
else:
|
||||
# Already in mm
|
||||
return value
|
||||
|
||||
|
|
@ -755,7 +755,7 @@ class Drill(Primitive):
|
|||
validate_coordinates(position)
|
||||
self.position = position
|
||||
self.diameter = diameter
|
||||
self.hit = hit
|
||||
self.hit = hit
|
||||
self._to_convert = ['position', 'diameter']
|
||||
|
||||
@property
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue