Merge pull request #1 from andreika-git/master

sync
This commit is contained in:
rusefillc 2021-04-30 21:33:47 -04:00 committed by GitHub
commit e6f439b14e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 33 deletions

View File

@ -165,8 +165,8 @@ class ApertureMacroPrimitive:
raise
def rotate(self):
if self.code == 1: # Circle: nothing to do
pass
if self.code == 1: # Circle: [andreika]: FIX circle rotation
rotatexypair(self.parms, 2)
elif self.code in (2,20): # Line (vector): fields (2,3) and (4,5) must be rotated, no need to
# rotate field 6
rotatexypair(self.parms, 2)

View File

@ -217,6 +217,8 @@ def constructApertureTable(fileList):
#print 'Reading apertures from %s ...' % fname
knownMacroNames = {}
# [andreika]: units conversion
units_div = 1.0
fid = file(fname,'rt')
for line in fid:
@ -231,6 +233,13 @@ def constructApertureTable(fileList):
# Ignore %AMOC8* from Eagle for now as it uses a macro parameter.
if line[:7]=='%AMOC8*':
continue
# [andreika]: units conversion
if line[:7]=='%MOMM*%' and config.Config['measurementunits'] == 'inch':
units_div = 1.0 / 25.4
continue
if line[:7]=='%MOIN*%' and config.Config['measurementunits'] == 'mm':
units_div = 25.4
continue
# parseApertureMacro() sucks up all macro lines up to terminating '%'
AM = amacro.parseApertureMacro(line, fid)
@ -255,6 +264,11 @@ def constructApertureTable(fileList):
# If this is an aperture definition, add the string representation
# to the dictionary. It might already exist.
if A:
# [andreika]: apply units
if type(A.dimx) == float or type(A.dimx) == int:
A.dimx *= units_div
if type(A.dimy) == float or type(A.dimy) == int:
A.dimy *= units_div
AT[A.hash()] = A
fid.close()

View File

@ -52,6 +52,7 @@ Config = {
'fiducialpoints': None, # List of X,Y co-ordinates at which to draw fiducials
'fiducialcopperdiameter': 0.08, # Diameter of copper part of fiducial
'fiducialmaskdiameter': 0.32, # Diameter of fiducial soldermask opening
'fixedrotationorigin': 0, # [andreika]: add settings to disable shifting of the rotating origin
}
# This dictionary is indexed by lowercase layer name and has as values a file

View File

@ -172,6 +172,7 @@ def writeExcellonHeader(fid):
fid.write("INCH,%s\n" % zerosDef)
else: # metric - mm
fid.write("METRIC,%s\n" % zerosDef)
def writeExcellonHeaderEnd(fid):
fid.write('%\n')
def writeExcellonFooter(fid):
@ -180,6 +181,9 @@ def writeExcellonFooter(fid):
def writeExcellonTool(fid, tool, size):
fid.write('%sC%f\n' % (tool, size))
def writeExcellonToolSelection(fid, tool, size):
fid.write('%s\n' % (tool))
def writeFiducials(fid, drawcode, OriginX, OriginY, MaxXExtent, MaxYExtent):
"""Place fiducials at arbitrary points. The FiducialPoints list in the config specifies
sets of X,Y co-ordinates. Positive values of X/Y represent offsets from the lower left
@ -706,9 +710,13 @@ def merge(opts, args, gui = None):
size = config.GlobalToolMap[tool]
except:
raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool map" % tool
writeExcellonTool(fid, tool, size)
writeExcellonHeaderEnd(fid)
for tool in Tools:
size = config.GlobalToolMap[tool]
writeExcellonToolSelection(fid, tool, size)
#for row in Layout:
# row.writeExcellon(fid, size)
for job in Place.jobs:

View File

@ -80,7 +80,7 @@ IgnoreList = ( \
re.compile(r'\*'), # Empty statement
re.compile(r'^%IN.*\*%'),
re.compile(r'^%ICAS\*%'), # Not in RS274X spec.
re.compile(r'^%MOIN\*%'),
#re.compile(r'^%MOIN\*%'), # [andreika]: don't ignore
re.compile(r'^%ASAXBY\*%'),
re.compile(r'^%AD\*%'), # GerbTool empty aperture definition
re.compile(r'^%LN.*\*%') # Layer name
@ -88,7 +88,8 @@ IgnoreList = ( \
# Patterns for Excellon interpretation
xtool_pat = re.compile(r'^(T\d+)$') # Tool selection
xydraw_pat = re.compile(r'^X([+-]?\d+)Y([+-]?\d+)$') # Plunge command
xydraw_pat = re.compile(r'^X([+-]?\d+)Y([+-]?\d+)(?:G85X([+-]?\d+)Y([+-]?\d+))?$') # Plunge command with optional G85
xydraw_pat2 = re.compile(r'^X([+-]?\d+\.\d*)Y([+-]?\d+\.\d*)(?:G85X([+-]?\d+\.\d*)Y([+-]?\d+\.\d*))?$') # Plunge command with optional G85
xdraw_pat = re.compile(r'^X([+-]?\d+)$') # Plunge command, repeat last Y value
ydraw_pat = re.compile(r'^Y([+-]?\d+)$') # Plunge command, repeat last X value
xtdef_pat = re.compile(r'^(T\d+)(?:F\d+)?(?:S\d+)?C([0-9.]+)$') # Tool+diameter definition with optional
@ -172,8 +173,9 @@ class Job:
# This is to help sorting all jobs and writing out all plunge
# commands for a single tool.
#
# The key to this dictionary is the full tool name, e.g., T03
# as a string. Each command is an (X,Y) integer tuple.
# The key to this dictionary is the full tool name, e.g., T03 as a
# string. Each command is an (X,Y,STOP_X,STOP_Y) integer tuple.
# STOP_X and STOP_Y are not none only if this is a G85 command.
self.xcommands = {}
# This is a dictionary mapping LOCAL tool names (e.g., T03) to diameters
@ -263,6 +265,11 @@ class Job:
and ( type( command_list[1] ) == types.IntType ): ## ensure that first two elemenst are integers
command_list[0] += x_shift / 10
command_list[1] += y_shift / 10
if ( type( command_list[2] ) == types.IntType ) \
and ( type( command_list[3] ) == types.IntType ): ## ensure that first two elemenst are integerslen(command_list) == 4:
# G85 command, need to shift the second pair of xy, too.
command_list[2] += x_shift / 10
command_list[3] += y_shift / 10
command[index] = tuple(command_list) ## convert list back to tuple
self.xcommands[tool] = command ## set modified command
@ -294,6 +301,12 @@ class Job:
x_div = 1.0
y_div = 1.0
# [andreika]: use local units conversion
units_div = 1.0
# [andreika]: store fill mode separately because of G01 can be inside G36/37
in_fill_mode = False
# Drawing commands can be repeated with X or Y omitted if they are
# the same as before. These variables store the last X/Y value as
# integers in hundred-thousandths of an inch.
@ -346,6 +359,11 @@ class Job:
A = aptable.parseAperture(line, self.apmxlat[layername])
if not A:
raise RuntimeError, "Unknown aperture definition in file %s" % fullname
# [andreika]: apply units
if type(A.dimx) == float or type(A.dimx) == int:
A.dimx *= units_div
if type(A.dimy) == float or type(A.dimy) == int:
A.dimy *= units_div
hash = A.hash()
if not RevGAT.has_key(hash):
@ -367,11 +385,21 @@ class Job:
# DipTrace specific fixes, but could be emitted by any CAD program. They are Standard Gerber RS-274X
# a hack to fix lack of recognition for metric direction from DipTrace - %MOMM*%
if (line[:7] == '%MOMM*%'):
# [andreika]: just set units to mm, no error
if (config.Config['measurementunits'] == 'inch'):
raise RuntimeError, "File %s units do match config file" % fullname
#raise RuntimeError, "File %s units do match config file" % fullname
units_div = 1.0 / 25.4
continue
else:
#print "ignoring metric directive: " + line
continue # ignore it so func doesn't choke on it
# [andreika]: add reciprocal conversion
if (line[:7] == '%MOIN*%'):
if (config.Config['measurementunits'] == 'mm'):
units_div = 25.4
continue
else:
continue # ignore it so func doesn't choke on it
if line[:3] == '%SF': # scale factor - we will ignore it
print 'Scale factor parameter ignored: ' + line
@ -425,6 +453,7 @@ class Job:
continue
# allow for metric - scale to 1/1000 mm
# [andreika]: use local units
if config.Config['measurementunits'] == 'inch':
if item[0]=='X': # M.N specification for X-axis.
fracpart = int(item[2])
@ -471,6 +500,12 @@ class Job:
elif gcode==75:
circ_signed = True
# [andreika]: we store fill mode separately
if gcode==36:
in_fill_mode = True
elif gcode==37:
in_fill_mode = False
continue
raise RuntimeError, "G-Code 'G%02d' is not supported" % gcode
@ -564,7 +599,9 @@ class Job:
# It's also OK if we're in the middle of a G36 polygon fill as we're only defining
# the polygon extents.
if (d != 2) and (last_gmode != 36):
raise RuntimeError, 'File %s has draw command %s with no aperture chosen' % (fullname, sub_line)
# [andreika]: check for fill mode more accurately
if not in_fill_mode:
raise RuntimeError, 'File %s has draw command %s with no aperture chosen' % (fullname, sub_line)
# Save last_x/y BEFORE scaling to 2.5 format else subsequent single-ordinate
# flashes (e.g., Y with no X) will be scaled twice!
@ -582,11 +619,12 @@ class Job:
self.miny = min(self.miny,0)
self.maxy = max(self.maxy,0)
x = int(round(x*x_div))
y = int(round(y*y_div))
# [andreika]: add units_div
x = int(round(x*x_div*units_div))
y = int(round(y*y_div*units_div))
if I is not None:
I = int(round(I*x_div))
J = int(round(J*y_div))
I = int(round(I*x_div*units_div))
J = int(round(J*y_div*units_div))
self.commands[layername].append((x,y,I,J,d,circ_signed))
else:
self.commands[layername].append((x,y,d))
@ -650,9 +688,22 @@ class Job:
def xln2tenthou(L, divisor=divisor, zeropadto=zeropadto):
V = []
for s in L:
if not suppress_leading:
s = s + '0'*(zeropadto-len(s))
V.append(int(round(int(s)*divisor)))
if s is not None:
if not suppress_leading:
s = s + '0'*(zeropadto-len(s))
V.append(int(round(int(s)*divisor)))
else:
V.append(None)
return tuple(V)
# Helper function to convert X/Y strings into integers in units of ten-thousandth of an inch.
def xln2tenthou2 (L, divisor=divisor, zeropadto=zeropadto):
V = []
for s in L:
if s is not None:
V.append(int(float(s)*1000*divisor))
else:
V.append(None)
return tuple(V)
for line in fid.xreadlines():
@ -742,26 +793,30 @@ class Job:
# Plunge command?
match = xydraw_pat.match(line)
if match:
x, y = xln2tenthou(match.groups())
x, y, stop_x, stop_y = xln2tenthou(match.groups())
else:
match = xdraw_pat.match(line)
match = xydraw_pat2.match(line)
if match:
x = xln2tenthou(match.groups())[0]
y = last_y
x, y, stop_x, stop_y = xln2tenthou2(match.groups())
else:
match = ydraw_pat.match(line)
match = xdraw_pat.match(line)
if match:
y = xln2tenthou(match.groups())[0]
x = last_x
x = xln2tenthou(match.groups())[0]
y = last_y
else:
match = ydraw_pat.match(line)
if match:
y = xln2tenthou(match.groups())[0]
x = last_x
if match:
if currtool is None:
raise RuntimeError, 'File %s has plunge command without previous tool selection' % fullname
try:
self.xcommands[currtool].append((x,y))
self.xcommands[currtool].append((x,y,stop_x,stop_y))
except KeyError:
self.xcommands[currtool] = [(x,y)]
self.xcommands[currtool] = [(x,y,stop_x,stop_y)]
last_x = x
last_y = y
@ -874,10 +929,17 @@ class Job:
for ltool in ltools:
if self.xcommands.has_key(ltool):
for cmd in self.xcommands[ltool]:
x, y = cmd
x, y, stop_x, stop_y = cmd
new_x = x+DX
new_y = y+DY
fid.write('X%sY%s\n' % (formatForXln(new_x), formatForXln(new_y)))
if stop_x is None:
fid.write('X%sY%s\n' % (formatForXln(new_x), formatForXln(new_y)))
else:
new_stop_x = stop_x+DX
new_stop_y = stop_y+DY
fid.write('X%sY%sG85X%sY%s\n' %
(formatForXln(new_x), formatForXln(new_y),
formatForXln(new_stop_x), formatForXln(new_stop_y)))
def writeDrillHits(self, fid, diameter, toolNum, Xoff, Yoff):
"""Write a drill hit pattern. diameter is tool diameter in inches, while toolNum is
@ -905,10 +967,12 @@ class Job:
for ltool in ltools:
if self.xcommands.has_key(ltool):
for cmd in self.xcommands[ltool]:
x, y = cmd
x, y, stop_x, stop_y = cmd
# add metric support (1/1000 mm vs. 1/100,000 inch)
# TODO - verify metric scaling is correct???
makestroke.drawDrillHit(fid, 10*x+DX, 10*y+DY, toolNum)
if stop_x is not None:
makestroke.drawDrillHit(fid, 10*stop_x+DX, 10*stop_y+DY, toolNum)
def aperturesAndMacros(self, layername):
"Return dictionaries whose keys are all necessary aperture names and macro names for this layer"
@ -1124,8 +1188,9 @@ class Job:
keys = self.xcommands.keys()
for toolname in keys:
# Remember Excellon is 2.4 format while Gerber data is 2.5 format
validList = [(x,y) for x,y in self.xcommands[toolname] if self.inBorders(10*x,10*y)]
validList = [tup for tup in self.xcommands[toolname]
if (self.inBorders(10*tup[0],10*tup[1]) and
(tup[2] is None or self.inBorders(10*tup[2],10*tup[3])))]
if validList:
self.xcommands[toolname] = validList
else:
@ -1348,7 +1413,11 @@ def rotateJob(job, degrees = 90, firstpass = True):
# We also have to take aperture change commands and
# replace them with the new aperture code if we have
# a rotation.
offset = job.maxy-job.miny
# [andreika]: add 'fixedrotationorigin' setting to disable shifting
if config.Config['fixedrotationorigin']:
offset = 0
else:
offset = job.maxy-job.miny
for layername in job.commands.keys():
J.commands[layername] = []
J.apertures[layername] = []
@ -1410,7 +1479,7 @@ def rotateJob(job, degrees = 90, firstpass = True):
for tool in job.xcommands.keys():
J.xcommands[tool] = []
for x,y in job.xcommands[tool]:
for x,y,stop_x,stop_y in job.xcommands[tool]:
# add metric support (1/1000 mm vs. 1/100,000 inch)
# NOTE: There don't appear to be any need for a change. The usual x10 factor seems to apply
@ -1420,8 +1489,16 @@ def rotateJob(job, degrees = 90, firstpass = True):
newx = int(round(newx/10.0))
newy = int(round(newy/10.0))
if stop_x is not None:
newstop_x = -(10*stop_y - job.miny) + job.minx + offset
newstop_y = (10*stop_x - job.minx) + job.miny
J.xcommands[tool].append((newx,newy))
newstop_x = int(round(newstop_x/10.0))
newstop_y = int(round(newstop_y/10.0))
else:
newstop_x = None
newstop_y = None
J.xcommands[tool].append((newx,newy,newstop_x,newstop_y))
# Rotate some more if required
degrees -= 90