Skip to content

Instantly share code, notes, and snippets.

@CapnKernel
Created September 15, 2015 17:36
Show Gist options
  • Select an option

  • Save CapnKernel/e0fbd5339ccd2240613c to your computer and use it in GitHub Desktop.

Select an option

Save CapnKernel/e0fbd5339ccd2240613c to your computer and use it in GitHub Desktop.
Analyser for a bunch of gerber files.
#! /bin/env python
# TODO: Check files, because often txt files contain text
# TODO: mfgcode files
import re
import sys
import subprocess
import itertools
import math
# from decimal import Decimal
# import glob
# f=itertools.chain.from_iterable(glob.glob("*.%s"%"".join(map(lambda c:"[%s%s]"%(c.lower(),c.upper())if c.isalpha()else c,ext)))for ext in "drl,dri,txt,cnc,gko,gbr,gol,out,gml,gm1,gm16,gt1,gto,gtp,gts,gtl,g1,gl1,gp1,g2l,gl2,gp2,g2,g3,g3l,gl3,gp3,gp4,gp5,gbo,gbp,gbs,gbl".split(","));print("\0".join(f), end="")'
def iendswith(s1, s2):
return s1.lower().endswith(s2)
def assess1(unclaimed_files):
unclaimed_files2 = list(unclaimed_files)
debug = False
files = {
}
# All file extensions must be lower case
file_uses = (
('outline', '.gko,.gol,.out,.gml,.gm1,.gm16,-edge_cuts.gbr'),
('topcode', 'mfgcode.gtp,_mfgcode.gtp'),
('bottomcode', 'mfgcode.gbp,_mfgcode.gbp'),
('vcut', '-vcut.gbr'),
('topsilk', '.gto'),
('toppaste', '.gtp'),
('topmask', '.gts'),
('topcopper', '.gtl'),
('bottomsilk', '.gbo'),
('bottompaste', '.gbp'),
('bottommask', '.gbs'),
('bottomcopper', '.gbl'),
('eagledri', '.dri'),
('eaglegpi', '.gpi'),
('schbrd', '.brd,.sch'),
('kicadglue', '.gta,.gba'),
('kicadps', '.ps'),
('kicaduser', '-cmts_user.gbr,-dwgs_user.gbr,-eco1_user.gbr,-eco2_user.gbr'),
)
# This is really inefficient
# Fetch out the unambiguous ones.
for use, extstr in file_uses:
extlist = tuple(ext for ext in extstr.split(','))
if debug: print "use=", use, 'extlist=', extlist
for ext in extlist:
if debug: print " ext=", ext
for f in unclaimed_files:
if debug: print " f=", f, "ext=", ext, "endswith=", f.endswith(ext), "iendswith=", iendswith(f, ext)
if iendswith(f, ext):
if debug: print " match"
if use not in files:
files[use] = []
files[use].append(f)
unclaimed_files2.remove(f)
debug = False
if debug: print "unclaimed=", unclaimed_files2
# From hereon we need heuristics
all_matched = False
drill_heuristics = (
(('NPTH', '_npth.txt'), ('drills', '.txt')),
(('NPTH', '-npth.txt'), ('drills', '.txt')),
(('drills', '.txt'),),
(('NPTH', '_npth.drl'), ('drills', '.drl')),
(('NPTH', '-npth.drl'), ('drills', '.drl')),
(('drills', '.drl'),),
(('drills', '.cnc'),),
# unhandled: 'dri' <-- Eagle report file
)
for heuristic in drill_heuristics:
if debug: print "heuristic=", heuristic
all_found_spots = set()
found_spots = {}
for key, val in heuristic:
if debug: print "key=", key, "val=", val
found_spots_for_this_pat = tuple(i for i, f in enumerate(unclaimed_files2) if i not in all_found_spots and iendswith(f, val))
all_found_spots = all_found_spots.union(found_spots_for_this_pat)
if found_spots_for_this_pat:
found_spots[key] = found_spots_for_this_pat
if debug: print "found_spots=", found_spots_for_this_pat, "all_found_spots=", all_found_spots
all_matched = len(found_spots) == len(heuristic)
if debug: print "all_found_spots=", all_found_spots, "found_spots=", found_spots, "all_match1=", all_matched
if all_matched:
break
if all_matched:
# Allocate matched files to use
for use, spots in found_spots.iteritems():
files[use] = list(unclaimed_files2[spot] for spot in spots)
# Remove matched files
unclaimed_files2 = list(f for i, f in enumerate(unclaimed_files2) if i not in all_found_spots)
files['unclaimed'] = unclaimed_files2
if debug: print "files=", files
"""
ext_str = ,,gbr,,gt1,,,,,,,,,,,
gt1
'inner1': g1,gl1,gp1
'inner2': g2l,gl2,gp2,g2
'inner3: g3,g3l,gl3,gp3,gp4,gp5
unclaimed=['A10base-NPTH.drl', 'A10base.drl', 'A10base-Inner2_Cu.gbr', 'A10base-Inner1_Cu.gbr', 'A10base-Edge_Cuts.gbr', 'A10base-Eco2_User.gbr', 'A10base-Eco1_User.gbr', 'A10base-Dwgs_User.gbr', 'A10base-Cmts_User.gbr']
unclaimed=['ADCS_PCB.TXT', 'ADCS_PCB.G1', 'ADCS_PCB.G2'] ADCS_BOARD_v04.zip
"""
# TODO: If we have more than one of something, mark it as partial match
# TODO: If we have unknown unclaimed files, mark it as a partial match
verdict = "perfect"
return verdict, files
def missing_seriousness_report(files):
seriousness = ('fatal', 'unusual', 'warning', 'ok')
missing_layer_seriousness = {
'drills': 'unusual',
'NPTH': 'ok',
'topcode': 'ok',
'bottomcode': 'ok',
'outline': 'fatal',
'vcut': 'ok',
'topsilk': 'warning',
'toppaste': 'warning',
'topmask': 'fatal',
'topcopper': 'fatal',
'bottomsilk': 'warning',
'bottompaste': 'warning',
'bottommask': 'fatal',
'bottomcopper': 'fatal',
}
missing_layers = tuple(layer for layer in missing_layer_seriousness if layer not in files)
print "Missing layer report:"
for s in seriousness:
layers_for_this_seriousness = list(layer for layer, seriousness in missing_layer_seriousness.iteritems() if seriousness == s and layer in missing_layers)
if layers_for_this_seriousness:
print " %s: %s" % (s.upper(), ", ".join(layers_for_this_seriousness))
print
def round_up_measurement(mm):
""" Take a measurement in millimetres. Return an answer in cm. If
the input is less than 1mm over 5cm or 10cm, the answer should be
5 or 10."""
special_zone = any((50 < mm < 51, 100 < mm < 101))
mm2 = mm if special_zone else mm + 0.9999
mm3 = math.floor(mm2)
cm = mm3 / 10
# print "mm=", mm, "mm2=", mm2, "mm3=", mm3, "special_zone=", special_zone, "cm=", cm
return cm
def calculate_likely_prefix(files):
files = files.copy()
del files['unclaimed']
values = tuple(itertools.chain.from_iterable(files.values()))
values_count = len(values)
shortest_name_len = min(len(value) for value in values)
# print "shortest_name_len=", shortest_name_len
matching_all_prefix = ""
for prefix_len in xrange(1, shortest_name_len + 1):
# print "prefix_len=", prefix_len
cand = values[0][0:prefix_len]
# print "cand=", cand
match_vec = list(value.startswith(cand) for value in values)
# print "match_vec=", match_vec
match_count = sum(match_vec)
# print "match_count=", match_count
if match_count < values_count:
# We've started not matching
break
matching_all_prefix = cand
# As a special case, if the matching prefix ends with _, remove it
if len(matching_all_prefix) > 1 and matching_all_prefix[len(matching_all_prefix) - 1] in ('_', '.'):
# print "removing trailing _"
matching_all_prefix = matching_all_prefix[0:len(matching_all_prefix) - 1]
return matching_all_prefix
def findsize(outlines):
assert len(outlines) == 1
outline = outlines[0]
empty_gbr_file = open('/tmp/empty.gbr', 'w')
s = """%MOIN*%
%FSLAX34Y34*%
%IPPOS*%
%ADD10C,0.0080*%
M02*
"""
empty_gbr_file.write(s)
empty_gbr_file.close()
empty_txt_file = open('/tmp/empty.txt', 'w')
s = """M48
INCH,TZ
T10C0.035
%
T10
M30
"""
empty_txt_file.write(s)
empty_txt_file.close()
def_file = open('/tmp/layout.def', 'w')
s = """Row {
Col {
proj1
}
}
"""
def_file.write(s)
def_file.close()
cfg_file = open('/tmp/layout.cfg', 'w')
s = """[DEFAULT]
projdir = .
MergeOut = pnl
[Options]
stencilmode = 0
roundededges = 0
toolholediameter = 0
toolholegrid = 0
toolholekeepout = 3mm
CutLineLayers = None
ExcellonLeadingZeros = 0
OutlineLayerFile = /dev/null
ScoringFile = /dev/null
PanelWidth = 12.6
PanelHeight = 12.6
LeftMargin = 0
RightMargin = 0
TopMargin = 0
BottomMargin = 0
XSpacing = 0
YSpacing = 0
CutLineWidth = 0.01
CropMarkWidth = 0.01
AllowMissingLayers = 0
DrillClusterTolerance = 0
[MergeOutputFiles]
Prefix = silly-prefix
*TopLayer=/dev/null
*BottomLayer=/dev/null
*TopSilkscreen=/dev/null
*BottomSilkscreen=/dev/null
*TopSoldermask=/dev/null
*BottomSoldermask=/dev/null
Drills=/dev/null
Holes=/dev/null
BoardOutline=/dev/null
ToolList = /dev/null
Placement = /dev/null
[Proj1]
Prefix=silly-prefix
*TopLayer=/tmp/empty.gbr
*BottomLayer=/tmp/empty.gbr
*TopSilkscreen=/tmp/empty.gbr
*BottomSilkscreen=/tmp/empty.gbr
*TopSoldermask=/tmp/empty.gbr
*BottomSoldermask=/tmp/empty.gbr
Drills=/tmp/empty.txt
BoardOutline=%s
""" % outline
cfg_file.write(s)
cfg_file.close()
# o = subprocess.check_output(('/home/mjd/git/env/bin/gerbmerge', '/tmp/layout.cfg', '/tmp/layout.def'))
proc = subprocess.Popen(('/home/mjd/git/env/bin/gerbmerge', '/tmp/layout.cfg', '/tmp/layout.def'),stdout=subprocess.PIPE)
for line in proc.stdout:
if 'Job Size' in line:
# print "line=", line
job_size_matcher = re.match(r'.*=\((.*),(.*)mm\)', line)
job_size_mm = (job_size_matcher.group(1), job_size_matcher.group(2).strip())
print "Board size (mm):", job_size_mm
job_size_cm = tuple(round_up_measurement(float(p)) for p in job_size_mm)
job_size_cm_str = tuple(str(int(p)) if str(p).endswith('.0') else str(p) for p in job_size_cm)
print "%s\t%s" % job_size_cm_str
break
def main():
# print "args=", sys.argv
unclaimed_files = sys.argv[1:]
# print "unclaimed_files=", unclaimed_files
verdict, files = assess1(unclaimed_files)
print "verdict=", verdict
print "File allocation:"
for key, val in files.iteritems():
print " %s:\t%s" % (key, ", ".join(val))
print
print "unclaimed=", files['unclaimed']
print
missing_seriousness_report(files)
likely_prefix = calculate_likely_prefix(files)
print "Likely prefix: ", likely_prefix
print
findsize(files['outline'])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment