router mode and G85 slot in excellon file is supported
This commit is contained in:
parent
4c7aef6a82
commit
22f4c8a3f5
3 changed files with 249 additions and 15 deletions
|
|
@ -150,10 +150,7 @@ class DrillComposition(Composition):
|
|||
yield ToolSelectionStmt(t.number).to_excellon(self.settings)
|
||||
for h in self.hits:
|
||||
if h.tool.number == t.number:
|
||||
if type(h) == DrillSlot:
|
||||
yield SlotStmt(*h.start, *h.end).to_excellon(self.settings)
|
||||
elif type(h) == DrillHit:
|
||||
yield CoordinateStmt(*h.position).to_excellon(self.settings)
|
||||
yield h.to_excellon(self.settings)
|
||||
for num, statement in self.dxf_statements:
|
||||
if num == t.number:
|
||||
yield statement.to_excellon(self.settings)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@
|
|||
|
||||
# Copyright 2019 Hiroshi Murayama <opiopan@gmail.com>
|
||||
|
||||
from gerber.excellon import (ExcellonParser, detect_excellon_format, ExcellonFile)
|
||||
from gerber.excellon_statements import UnitStmt
|
||||
import operator
|
||||
|
||||
from gerber.excellon import ExcellonParser, detect_excellon_format, ExcellonFile, DrillHit, DrillSlot
|
||||
from gerber.excellon_statements import UnitStmt, CoordinateStmt, UnknownStmt, SlotStmt, DrillModeStmt, \
|
||||
ToolSelectionStmt, ZAxisRoutPositionStmt, \
|
||||
RetractWithClampingStmt, RetractWithoutClampingStmt, \
|
||||
EndOfProgramStmt
|
||||
from gerber.cam import FileSettings
|
||||
from gerber.utils import inch, metric
|
||||
from gerber.utils import inch, metric, write_gerber_value, parse_gerber_value
|
||||
from gerberex.utility import rotate
|
||||
|
||||
def loads(data, filename=None, settings=None, tools=None, format=None):
|
||||
|
|
@ -20,11 +25,93 @@ def loads(data, filename=None, settings=None, tools=None, format=None):
|
|||
class ExcellonFileEx(ExcellonFile):
|
||||
@classmethod
|
||||
def from_file(cls, file):
|
||||
statements = [
|
||||
UnitStmtEx.from_statement(s) if isinstance(s, UnitStmt) else s \
|
||||
for s in file.statements
|
||||
]
|
||||
return cls(statements, file.tools, file.hits, file.settings, file.filename)
|
||||
def correct_statements():
|
||||
lazy_stmt = None
|
||||
modifier = None
|
||||
for stmt in file.statements:
|
||||
modifier = lazy_stmt
|
||||
lazy_stmt = None
|
||||
if isinstance(stmt, UnitStmt):
|
||||
yield UnitStmtEx.from_statement(stmt)
|
||||
elif isinstance(stmt, CoordinateStmt):
|
||||
new_stmt = CoordinateStmtEx.from_statement(stmt)
|
||||
if modifier and new_stmt.mode is None:
|
||||
new_stmt.mode = modifier
|
||||
yield new_stmt
|
||||
elif isinstance(stmt, UnknownStmt):
|
||||
line = stmt.stmt.strip()
|
||||
mode = None
|
||||
if line[:3] == 'G02':
|
||||
mode = CoordinateStmtEx.MODE_CIRCULER_CW
|
||||
elif line[:3] == 'G03':
|
||||
mode = CoordinateStmtEx.MODE_CIRCULER_CCW
|
||||
else:
|
||||
yield stmt
|
||||
continue
|
||||
if len(line) == 3:
|
||||
lazy_stmt = mode
|
||||
continue
|
||||
new_stmt = CoordinateStmtEx.from_excellon(line[3:], file.settings)
|
||||
new_stmt.mode = mode
|
||||
yield new_stmt
|
||||
else:
|
||||
yield stmt
|
||||
|
||||
def generate_hits(statements):
|
||||
STAT_DRILL = 0
|
||||
STAT_ROUT_UP = 1
|
||||
STAT_ROUT_DOWN = 2
|
||||
status = STAT_DRILL
|
||||
current_tool = None
|
||||
rout_statements = []
|
||||
|
||||
def make_rout(status, statements):
|
||||
if status != STAT_ROUT_DOWN or len(statements) == 0 or current_tool is None:
|
||||
return None
|
||||
return DrillRout.from_coordinates(current_tool, statements)
|
||||
|
||||
for stmt in statements:
|
||||
if isinstance(stmt, ToolSelectionStmt):
|
||||
current_tool = file.tools[stmt.tool]
|
||||
elif isinstance(stmt, DrillModeStmt):
|
||||
rout = make_rout(status, rout_statements)
|
||||
rout_statements = []
|
||||
if rout is not None:
|
||||
yield rout
|
||||
status = STAT_DRILL
|
||||
elif isinstance(stmt, ZAxisRoutPositionStmt) and status == STAT_ROUT_UP:
|
||||
status = STAT_ROUT_DOWN
|
||||
elif isinstance(stmt, RetractWithClampingStmt) or isinstance(stmt, RetractWithoutClampingStmt):
|
||||
rout = make_rout(status, rout_statements)
|
||||
rout_statements = []
|
||||
if rout is not None:
|
||||
yield rout
|
||||
status = STAT_ROUT_UP
|
||||
elif isinstance(stmt, SlotStmt):
|
||||
yield DrillSlotEx(current_tool, (stmt.x_start, stmt.y_start),
|
||||
(stmt.x_end, stmt.y_end), DrillSlotEx.TYPE_G85)
|
||||
elif isinstance(stmt, CoordinateStmtEx):
|
||||
if stmt.mode is None:
|
||||
if status != STAT_DRILL:
|
||||
raise Exception('invalid statement sequence')
|
||||
yield DrillHitEx(current_tool, (stmt.x, stmt.y))
|
||||
else:
|
||||
if stmt.mode == stmt.MODE_ROUT:
|
||||
status = STAT_ROUT_UP
|
||||
if status == STAT_ROUT_UP:
|
||||
rout_statements = [stmt]
|
||||
elif status == STAT_ROUT_DOWN:
|
||||
rout_statements.append(stmt)
|
||||
else:
|
||||
raise Exception('invalid statement sequence')
|
||||
|
||||
statements = [s for s in correct_statements()]
|
||||
hits = [h for h in generate_hits(statements)]
|
||||
return cls(statements, file.tools, hits, file.settings, file.filename)
|
||||
|
||||
@property
|
||||
def primitives(self):
|
||||
return []
|
||||
|
||||
def __init__(self, statements, tools, hits, settings, filename=None):
|
||||
super(ExcellonFileEx, self).__init__(statements, tools, hits, settings, filename)
|
||||
|
|
@ -33,20 +120,102 @@ class ExcellonFileEx(ExcellonFile):
|
|||
if angle % 360 == 0:
|
||||
return
|
||||
for hit in self.hits:
|
||||
hit.position = rotate(hit.position[0], hit.position[1], angle, center)
|
||||
hit.rotate(angle, center)
|
||||
|
||||
def to_inch(self):
|
||||
if self.units == 'metric':
|
||||
super(ExcellonFileEx, self).to_inch()
|
||||
for hit in self.hits:
|
||||
hit.position = (inch(hit.position[0]), inch(hit.position[1]))
|
||||
hit.to_inch()
|
||||
|
||||
def to_metric(self):
|
||||
if self.units == 'inch':
|
||||
super(ExcellonFileEx, self).to_metric()
|
||||
for hit in self.hits:
|
||||
hit.position = (metric(hit.position[0]), metric(hit.position[1]))
|
||||
hit.to_metric()
|
||||
|
||||
def write(self, filename=None):
|
||||
filename = filename if filename is not None else self.filename
|
||||
with open(filename, 'w') as f:
|
||||
|
||||
for statement in self.statements:
|
||||
if not isinstance(statement, ToolSelectionStmt):
|
||||
f.write(statement.to_excellon(self.settings) + '\n')
|
||||
else:
|
||||
break
|
||||
|
||||
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(hit.to_excellon(self.settings) + '\n')
|
||||
f.write(EndOfProgramStmt().to_excellon() + '\n')
|
||||
|
||||
class DrillHitEx(DrillHit):
|
||||
def rotate(self, angle, center=(0,0)):
|
||||
self.position = rotate(*self.position, angle, center)
|
||||
|
||||
def to_excellon(self, settings):
|
||||
return CoordinateStmtEx(*self.position).to_excellon(settings)
|
||||
|
||||
class DrillSlotEx(DrillSlot):
|
||||
def rotate(self, angle, center=(0,0)):
|
||||
self.start = rotate(*self.start, angle, center)
|
||||
self.end = rotate(*self.end, angle, center)
|
||||
|
||||
def to_excellon(self, settings):
|
||||
return SlotStmt(*self.start, *self.end).to_excellon(settings)
|
||||
|
||||
class DrillRout(object):
|
||||
class Node(object):
|
||||
def __init__(self, mode, x, y, radius):
|
||||
self.mode = mode
|
||||
self.position = (x, y)
|
||||
self.radius = radius
|
||||
|
||||
@classmethod
|
||||
def from_coordinates(cls, tool, coordinates):
|
||||
nodes = [cls.Node(c.mode, c.x, c.y, c.radius) for c in coordinates]
|
||||
return cls(tool, nodes)
|
||||
|
||||
def __init__(self, tool, nodes):
|
||||
self.tool = tool
|
||||
self.nodes = nodes
|
||||
|
||||
def to_excellon(self, settings):
|
||||
node = self.nodes[0]
|
||||
excellon = CoordinateStmtEx(*node.position, node.radius,
|
||||
CoordinateStmtEx.MODE_ROUT).to_excellon(settings) + '\nM15\n'
|
||||
for node in self.nodes[1:]:
|
||||
excellon += CoordinateStmtEx(*node.position, node.radius,
|
||||
node.mode).to_excellon(settings) + '\n'
|
||||
excellon += 'M16\nG05\n'
|
||||
return excellon
|
||||
|
||||
def to_inch(self):
|
||||
if self.tool.settings.units == 'metric':
|
||||
self.tool.to_inch()
|
||||
for node in self.nodes:
|
||||
node.position = tuple(map(inch, node.position))
|
||||
node.radius = inch(
|
||||
node.radius) if node.radius is not None else None
|
||||
|
||||
def to_metric(self):
|
||||
if self.tool.settings.units == 'inch':
|
||||
self.tool.to_metric()
|
||||
for node in self.nodes:
|
||||
node.position = tuple(map(metric, node.position))
|
||||
node.radius = metric(
|
||||
node.radius) if node.radius is not None else None
|
||||
|
||||
def offset(self, x_offset=0, y_offset=0):
|
||||
for node in self.nodes:
|
||||
node.position = tuple(map(operator.add, node.position, (x_offset, y_offset)))
|
||||
|
||||
def rotate(self, angle, center=(0, 0)):
|
||||
for node in self.nodes:
|
||||
node.position = rotate(*node.position, angle, center)
|
||||
|
||||
class UnitStmtEx(UnitStmt):
|
||||
@classmethod
|
||||
|
|
@ -62,3 +231,63 @@ class UnitStmtEx(UnitStmt):
|
|||
'LZ' if self.zeros == 'leading' else 'TZ',
|
||||
'0' * format[0], '0' * format[1])
|
||||
return stmt
|
||||
|
||||
class CoordinateStmtEx(CoordinateStmt):
|
||||
MODE_ROUT = 'ROUT'
|
||||
MODE_LINEAR = 'LINEAR'
|
||||
MODE_CIRCULER_CW = 'CW'
|
||||
MODE_CIRCULER_CCW = 'CCW'
|
||||
|
||||
@classmethod
|
||||
def from_statement(cls, stmt):
|
||||
newStmt = cls(x=stmt.x, y=stmt.y)
|
||||
newStmt.mode = stmt.mode
|
||||
newStmt.radius = stmt.radius if isinstance(stmt, CoordinateStmtEx) else None
|
||||
return newStmt
|
||||
|
||||
@classmethod
|
||||
def from_excellon(cls, line, settings, **kwargs):
|
||||
parts = line.split('A')
|
||||
stmt = cls.from_statement(CoordinateStmt.from_excellon(parts[0], settings))
|
||||
if len(parts) > 1:
|
||||
stmt.radius = parse_gerber_value(
|
||||
parts[1], settings.format, settings.zero_suppression)
|
||||
return stmt
|
||||
|
||||
def __init__(self, x=None, y=None, radius=None, mode=None, **kwargs):
|
||||
super(CoordinateStmtEx, self).__init__(x, y, **kwargs)
|
||||
self.mode = mode
|
||||
self.radius = radius
|
||||
|
||||
def to_excellon(self, settings):
|
||||
stmt = ''
|
||||
if self.mode == self.MODE_ROUT:
|
||||
stmt += "G00"
|
||||
if self.mode == self.MODE_LINEAR:
|
||||
stmt += "G01"
|
||||
if self.mode == self.MODE_CIRCULER_CW:
|
||||
stmt += "G02"
|
||||
if self.mode == self.MODE_CIRCULER_CCW:
|
||||
stmt += "G03"
|
||||
if self.x is not None:
|
||||
stmt += 'X%s' % write_gerber_value(self.x, settings.format,
|
||||
settings.zero_suppression)
|
||||
if self.y is not None:
|
||||
stmt += 'Y%s' % write_gerber_value(self.y, settings.format,
|
||||
settings.zero_suppression)
|
||||
if self.radius is not None:
|
||||
stmt += 'A%s' % write_gerber_value(self.radius, settings.format,
|
||||
settings.zero_suppression)
|
||||
return stmt
|
||||
|
||||
def __str__(self):
|
||||
coord_str = ''
|
||||
if self.x is not None:
|
||||
coord_str += 'X: %g ' % self.x
|
||||
if self.y is not None:
|
||||
coord_str += 'Y: %g ' % self.y
|
||||
if self.radius is not None:
|
||||
coord_str += 'A: %g ' % self.radius
|
||||
|
||||
return '<Coordinate Statement: %s(%s)>' % \
|
||||
(coord_str, self.mode if self.mode else 'HIT')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue