Created
September 15, 2015 17:36
-
-
Save CapnKernel/e0fbd5339ccd2240613c to your computer and use it in GitHub Desktop.
Analyser for a bunch of gerber files.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /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)) | |
| 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 "unclaimed=", files['unclaimed'] | |
| missing_seriousness_report(files) | |
| likely_prefix = calculate_likely_prefix(files) | |
| print "Likely prefix: ", likely_prefix | |
| 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