kopia lustrzana https://gitlab.com/gerbolyze/gerbonara
Add CNC base classes and settings class
rodzic
c60a858c9c
commit
1cb7856e88
|
@ -0,0 +1,117 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
gerber.cnc
|
||||
============
|
||||
**CNC file classes**
|
||||
|
||||
This module provides common base classes for Excellon/Gerber CNC files
|
||||
"""
|
||||
|
||||
|
||||
class FileSettings(object):
|
||||
""" CNC File Settings
|
||||
|
||||
Provides a common representation of gerber/excellon file settings
|
||||
"""
|
||||
def __init__(self, notation='absolute', units='inch',
|
||||
zero_suppression='trailing', format=(2,5)):
|
||||
if notation not in ['absolute', 'incremental']:
|
||||
raise ValueError('Notation must be either absolute or incremental')
|
||||
self.notation = notation
|
||||
|
||||
if units not in ['inch', 'metric']:
|
||||
raise ValueError('Units must be either inch or metric')
|
||||
self.units = units
|
||||
|
||||
if zero_suppression not in ['leading', 'trailing']:
|
||||
raise ValueError('Zero suppression must be either leading or \
|
||||
trailling')
|
||||
self.zero_suppression = zero_suppression
|
||||
|
||||
if len(format) != 2:
|
||||
raise ValueError('Format must be a tuple(n=2) of integers')
|
||||
self.format = format
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 'notation':
|
||||
return self.notation
|
||||
elif key == 'units':
|
||||
return self.units
|
||||
elif key =='zero_suppression':
|
||||
return self.zero_suppression
|
||||
elif key == 'format':
|
||||
return self.format
|
||||
else:
|
||||
raise KeyError()
|
||||
|
||||
class CncFile(object):
|
||||
""" Base class for Gerber/Excellon files.
|
||||
|
||||
Provides a common set of settings parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
settings : FileSettings
|
||||
The current file configuration.
|
||||
|
||||
filename : string
|
||||
Name of the file that this CncFile represents.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
settings : FileSettings
|
||||
File settings as a FileSettings object
|
||||
|
||||
notation : string
|
||||
File notation setting. May be either 'absolute' or 'incremental'
|
||||
|
||||
units : string
|
||||
File units setting. May be 'inch' or 'metric'
|
||||
|
||||
zero_suppression : string
|
||||
File zero-suppression setting. May be either 'leading' or 'trailling'
|
||||
|
||||
format : tuple (<int>, <int>)
|
||||
File decimal representation format as a tuple of (integer digits,
|
||||
decimal digits)
|
||||
"""
|
||||
|
||||
def __init__(self, settings=None, filename=None):
|
||||
if settings is not None:
|
||||
self.notation = settings['notation']
|
||||
self.units = settings['units']
|
||||
self.zero_suppression = settings['zero_suppression']
|
||||
self.format = settings['format']
|
||||
else:
|
||||
self.notation = 'absolute'
|
||||
self.units = 'inch'
|
||||
self.zero_suppression = 'trailing'
|
||||
self.format = (2,5)
|
||||
self.filename = filename
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
""" File settings
|
||||
|
||||
Returns
|
||||
-------
|
||||
settings : FileSettings (dict-like)
|
||||
A FileSettings object with the specified configuration.
|
||||
"""
|
||||
return FileSettings(self.notation, self.units, self.zero_suppression,
|
||||
self.format)
|
|
@ -2,6 +2,7 @@
|
|||
import re
|
||||
from itertools import tee, izip
|
||||
from .utils import parse_gerber_value
|
||||
from .cnc import CncFile, FileSettings
|
||||
|
||||
|
||||
def read(filename):
|
||||
|
@ -10,7 +11,7 @@ def read(filename):
|
|||
return ExcellonParser().parse(filename)
|
||||
|
||||
|
||||
class ExcellonFile(object):
|
||||
class ExcellonFile(CncFile):
|
||||
""" A class representing a single excellon file
|
||||
|
||||
The ExcellonFile class represents a single excellon file.
|
||||
|
@ -34,11 +35,10 @@ class ExcellonFile(object):
|
|||
either 'inch' or 'metric'.
|
||||
|
||||
"""
|
||||
def __init__(self, tools, hits, settings, filename):
|
||||
def __init__(self, tools, hits, settings, filename=None):
|
||||
super(ExcellonFile, self).__init__(settings, filename)
|
||||
self.tools = tools
|
||||
self.hits = hits
|
||||
self.settings = settings
|
||||
self.filename = filename
|
||||
|
||||
def report(self):
|
||||
""" Print drill report
|
||||
|
@ -53,11 +53,67 @@ class ExcellonFile(object):
|
|||
ctx.dump(filename)
|
||||
|
||||
|
||||
class Tool(object):
|
||||
class ExcellonTool(object):
|
||||
""" Excellon Tool class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
settings : FileSettings (dict-like)
|
||||
File-wide settings.
|
||||
|
||||
kwargs : dict-like
|
||||
Tool settings from the excellon statement. Valid keys are:
|
||||
diameter : Tool diameter [expressed in file units]
|
||||
rpm : Tool RPM
|
||||
feed_rate : Z-axis tool feed rate
|
||||
retract_rate : Z-axis tool retraction rate
|
||||
max_hit_count : Number of hits allowed before a tool change
|
||||
depth_offset : Offset of tool depth from tip of tool.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
number : integer
|
||||
Tool number from the excellon file
|
||||
|
||||
diameter : float
|
||||
Tool diameter in file units
|
||||
|
||||
rpm : float
|
||||
Tool RPM
|
||||
|
||||
feed_rate : float
|
||||
Tool Z-axis feed rate.
|
||||
|
||||
retract_rate : float
|
||||
Tool Z-axis retract rate
|
||||
|
||||
depth_offset : float
|
||||
Offset of depth measurement from tip of tool
|
||||
|
||||
max_hit_count : integer
|
||||
Maximum number of tool hits allowed before a tool change
|
||||
|
||||
hit_count : integer
|
||||
Number of tool hits in excellon file.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line, settings):
|
||||
""" Create a Tool from an excellon gile tool definition line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line : string
|
||||
Tool definition line from an excellon file.
|
||||
|
||||
settings : FileSettings (dict-like)
|
||||
Excellon file-wide settings
|
||||
|
||||
Returns
|
||||
-------
|
||||
tool : Tool
|
||||
An ExcellonTool representing the tool defined in `line`
|
||||
"""
|
||||
commands = re.split('([BCFHSTZ])', line)[1:]
|
||||
commands = [(command, value) for command, value in pairwise(commands)]
|
||||
args = {}
|
||||
|
@ -89,13 +145,19 @@ class Tool(object):
|
|||
self.max_hit_count = kwargs.get('max_hit_count')
|
||||
self.depth_offset = kwargs.get('depth_offset')
|
||||
self.units = settings.get('units', 'inch')
|
||||
self.hit_count = 0
|
||||
|
||||
def _hit(self):
|
||||
self.hit_count += 1
|
||||
|
||||
def __repr__(self):
|
||||
unit = 'in.' if self.units == 'inch' else 'mm'
|
||||
return '<Tool %d: %0.3f%s dia.>' % (self.number, self.diameter, unit)
|
||||
return '<ExcellonTool %d: %0.3f%s dia.>' % (self.number, self.diameter, unit)
|
||||
|
||||
|
||||
class ExcellonParser(object):
|
||||
""" Excellon File Parser
|
||||
"""
|
||||
def __init__(self, ctx=None):
|
||||
self.ctx = ctx
|
||||
self.notation = 'absolute'
|
||||
|
@ -115,13 +177,11 @@ class ExcellonParser(object):
|
|||
with open(filename, 'r') as f:
|
||||
for line in f:
|
||||
self._parse(line)
|
||||
settings = {'notation': self.notation, 'units': self.units,
|
||||
'zero_suppression': self.zero_suppression,
|
||||
'format': self.format}
|
||||
return ExcellonFile(self.tools, self.hits, settings, filename)
|
||||
return ExcellonFile(self.tools, self.hits, self._settings(), filename)
|
||||
|
||||
def dump(self, filename):
|
||||
self.ctx.dump(filename)
|
||||
if self.ctx is not None:
|
||||
self.ctx.dump(filename)
|
||||
|
||||
def _parse(self, line):
|
||||
if 'M48' in line:
|
||||
|
@ -159,7 +219,7 @@ class ExcellonParser(object):
|
|||
|
||||
# tool definition
|
||||
if line[0] == 'T' and self.state == 'HEADER':
|
||||
tool = Tool.from_line(line, self._settings())
|
||||
tool = ExcellonTool.from_line(line, self._settings())
|
||||
self.tools[tool.number] = tool
|
||||
|
||||
elif line[0] == 'T' and self.state != 'HEADER':
|
||||
|
@ -187,13 +247,16 @@ class ExcellonParser(object):
|
|||
self.pos[1] += y
|
||||
if self.state == 'DRILL':
|
||||
self.hits.append((self.active_tool, self.pos))
|
||||
self.active_tool._hit()
|
||||
if self.ctx is not None:
|
||||
self.ctx.drill(self.pos[0], self.pos[1],
|
||||
self.active_tool.diameter)
|
||||
|
||||
def _settings(self):
|
||||
return {'units': self.units, 'zero_suppression': self.zero_suppression,
|
||||
'format': self.format}
|
||||
return FileSettings(units=self.units, format=self.format,
|
||||
zero_suppression=self.zero_suppression,
|
||||
notation=self.notation)
|
||||
|
||||
|
||||
|
||||
def pairwise(iterator):
|
||||
|
|
|
@ -27,6 +27,9 @@ This module provides an RS-274-X class and parser
|
|||
import re
|
||||
import json
|
||||
from .statements import *
|
||||
from .cnc import CncFile, FileSettings
|
||||
|
||||
|
||||
|
||||
|
||||
def read(filename):
|
||||
|
@ -35,7 +38,7 @@ def read(filename):
|
|||
return GerberParser().parse(filename)
|
||||
|
||||
|
||||
class GerberFile(object):
|
||||
class GerberFile(CncFile):
|
||||
""" A class representing a single gerber file
|
||||
|
||||
The GerberFile class represents a single gerber file.
|
||||
|
@ -68,9 +71,8 @@ class GerberFile(object):
|
|||
|
||||
"""
|
||||
def __init__(self, statements, settings, filename=None):
|
||||
self.filename = filename
|
||||
super(GerberFile, self).__init__(settings, filename)
|
||||
self.statements = statements
|
||||
self.settings = settings
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
|
@ -90,7 +92,8 @@ class GerberFile(object):
|
|||
def bounds(self):
|
||||
xbounds = [0.0, 0.0]
|
||||
ybounds = [0.0, 0.0]
|
||||
for stmt in [stmt for stmt in self.statements if isinstance(stmt, CoordStmt)]:
|
||||
for stmt in [stmt for stmt in self.statements
|
||||
if isinstance(stmt, CoordStmt)]:
|
||||
if stmt.x is not None and stmt.x < xbounds[0]:
|
||||
xbounds[0] = stmt.x
|
||||
if stmt.x is not None and stmt.x > xbounds[1]:
|
||||
|
@ -169,7 +172,7 @@ class GerberParser(object):
|
|||
EOF_STMT = re.compile(r"(?P<eof>M02)\*")
|
||||
|
||||
def __init__(self):
|
||||
self.settings = {}
|
||||
self.settings = FileSettings()
|
||||
self.statements = []
|
||||
|
||||
def parse(self, filename):
|
||||
|
@ -240,13 +243,13 @@ class GerberParser(object):
|
|||
if param:
|
||||
if param["param"] == "FS":
|
||||
stmt = FSParamStmt.from_dict(param)
|
||||
self.settings = {'zero_suppression': stmt.zero_suppression,
|
||||
'format': stmt.format,
|
||||
'notation': stmt.notation}
|
||||
self.settings.zero_suppression = stmt.zero_suppression
|
||||
self.settings.format = stmt.format
|
||||
self.settings.notation = stmt.notation
|
||||
yield stmt
|
||||
elif param["param"] == "MO":
|
||||
stmt = MOParamStmt.from_dict(param)
|
||||
self.settings['units'] = stmt.mode
|
||||
self.settings.units = stmt.mode
|
||||
yield stmt
|
||||
elif param["param"] == "IP":
|
||||
yield IPParamStmt.from_dict(param)
|
||||
|
|
Ładowanie…
Reference in New Issue