diff --git a/Screen Shot 2018-06-04 at 22.28.05.png b/Screen Shot 2018-06-04 at 22.28.05.png new file mode 100644 index 0000000..9000e35 Binary files /dev/null and b/Screen Shot 2018-06-04 at 22.28.05.png differ diff --git a/diff-74b1-b1d3.png b/diff-74b1-b1d3.png new file mode 100644 index 0000000..05d38f5 Binary files /dev/null and b/diff-74b1-b1d3.png differ diff --git a/diff-b1d3-192a.png b/diff-b1d3-192a.png new file mode 100644 index 0000000..f9a6df2 Binary files /dev/null and b/diff-b1d3-192a.png differ diff --git a/kidiff_gui.py b/kidiff_gui.py new file mode 100644 index 0000000..2dfe21b --- /dev/null +++ b/kidiff_gui.py @@ -0,0 +1,1127 @@ +#!/usr/local/bin/python3 + +# TODO Add progress tk pages +# TODO Make composite images +# TODO Honour layer selection + +# The Python install in macOS is driving me completely mad as you can only run pcbnew +# scripting from a specific version of python2 placed within the Kicad executable. This makes it +# impossible to import pcbnew from another python script especially as I have +# chosen to write the main script in python3. This has annoying consequences as I have to call +# several processes using subprocess calls and saving and then re-reading +# the output rather than simply returning them. + +import os +import subprocess +import tkinter as tk +import webbrowser +from subprocess import PIPE, STDOUT, Popen +from tkinter import * +from tkinter import filedialog, ttk +from tkinter.messagebox import showinfo + +# import imageme +import PIL +from PIL import Image + +import tkUI +from tkUI import * + +# TODO Incorporate these full paths + +gitProg = '/usr/local/bin/git' +fossilProg = '/usr/local/bin/fossil' +svnProg = '/usr/bin/svn' +plotDir = '/Plots' +webDir = '/web' +pcbDraw = '/Users/johnpateman/Kicad/PcbDraw/pcbdraw.py' + +layerCols = { + 'F_Cu': "#952927", + 'B_Cu': "#359632", + 'B_Paste': "#3DC9C9", + 'F_Paste': "#969696", + 'F_SilkS': "#339697", + 'B_SilkS': "#481649", + 'B_Mask': "#943197", + 'F_Mask': "#943197", + 'Edge_Cuts': "#C9C83B", + 'Margin': "#D357D2", + 'In1_Cu': "#C2C200", + 'In2_Cu': "#C200C2", + 'Dwgs_User': "#0364D3", + 'Cmts_User': "#7AC0F4", + 'Eco1_User': "#008500", + 'Eco2_User': "#C2C200", + 'B_Fab': "#858585", + 'F_Fab': "#C2C200", + 'B_Adhes': "#3545A8", + 'F_Adhes': "#A74AA8", + 'B_CrtYd': "#D3D04B", + 'F_CrtYd': "#A7A7A7", +} + + +def getGitDiff(diff1, diff2, prjctName, prjctPath): + '''Given two git artifacts, write out two kicad_pcb files to their respective + directories (named after the artifact). Returns the date and time of both commits''' + + artifact1 = diff1[:6] + artifact2 = diff2[:6] + + findDiff = 'cd ' + prjctPath + ' && git diff --name-only ' + \ + artifact1 + ' ' + artifact2 + ' | grep .kicad_pcb' + + changes = Popen( + findDiff, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = changes.communicate() + + changed = (stdout.decode('utf-8')) + + if changed == '': + print("No .kicad_pcb files differ between these commits") + sys.exit() + + outputDir1 = prjctPath + plotDir + '/' + artifact1 + outputDir2 = prjctPath + plotDir + '/' + artifact2 + + if not os.path.exists(outputDir1): + os.makedirs(outputDir1) + + if not os.path.exists(outputDir2): + os.makedirs(outputDir2) + + gitArtifact1 = 'cd ' + prjctPath + ' && git show ' + artifact1 + \ + ':' + prjctName + ' > ' + outputDir1 + '/' + prjctName + + gitArtifact2 = 'cd ' + prjctPath + ' && git show ' + artifact2 + \ + ':' + prjctName + ' > ' + outputDir2 + '/' + prjctName + + ver1 = Popen( + gitArtifact1, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = ver1.communicate() + + ver2 = Popen( + gitArtifact2, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = ver2.communicate() + + gitDateTime1 = 'cd ' + prjctPath + ' && git show -s --format="%ci" ' + artifact1 + gitDateTime2 = 'cd ' + prjctPath + ' && git show -s --format="%ci" ' + artifact2 + + dt1 = Popen( + gitDateTime1, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = dt1.communicate() + + dateTime1 = stdout.decode('utf-8') + date1, time1, UTC = dateTime1.split(' ') + print(date1, time1) + + dt2 = Popen( + gitDateTime2, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = dt2.communicate() + + dateTime2 = stdout.decode('utf-8') + date2, time2, UTC = dateTime2.split(' ') + print(date2, time2) + + times = date1 + " " + time1 + " " + date2 + " " + time2 + + return (times) + + +def getSVNDiff(diff1, diff2, prjctName, prjctPath): + '''Given two SVN revisions, write out two kicad_pcb files to their respective + directories (named after the revision number). Returns the date and time of both commits''' + + svnChanged = 'cd ' + prjctPath + ' && svn diff --summarize -r ' + \ + diff1 + ':' + diff2 + ' ' + prjctName + + changed = Popen( + svnChanged, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = changed.communicate() + + changed, *boardName = (stdout.decode('utf-8')) + + if changed != 'M': + print("No .kicad_pcb files differ between these commits") + sys.exit() + + outputDir1 = prjctPath + plotDir + '/' + diff1 + outputDir2 = prjctPath + plotDir + '/' + diff2 + + if not os.path.exists(outputDir1): + os.makedirs(outputDir1) + + if not os.path.exists(outputDir2): + os.makedirs(outputDir2) + + SVNdiffCmd1 = 'cd ' + prjctPath + ' && svn cat -r ' + diff1 + \ + " " + prjctName + ' > ' + outputDir1 + '/' + prjctName + SVNdiffCmd2 = 'cd ' + prjctPath + ' && svn cat -r ' + diff2 + \ + " " + prjctName + ' > ' + outputDir2 + '/' + prjctName + + ver1 = Popen( + SVNdiffCmd1, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = ver1.communicate() + + ver2 = Popen( + SVNdiffCmd2, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = ver2.communicate() + + dateTime1 = 'cd ' + prjctPath + ' && svn log -r' + diff1 + dateTime2 = 'cd ' + prjctPath + ' && svn log -r' + diff2 + + dt1 = Popen( + dateTime1, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = dt1.communicate() + dateTime = stdout.decode('utf-8') + cmt = (dateTime.splitlines()[1]).split('|') + _, SVNdate1, SVNtime1, SVNutc, *_ = cmt[2].split(' ') + + dt2 = Popen( + dateTime2, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = dt2.communicate() + dateTime = stdout.decode('utf-8') + cmt = (dateTime.splitlines()[1]).split('|') + _, SVNdate2, SVNtime2, SVNutc, *_ = cmt[2].split(' ') + + times = SVNdate1 + " " + SVNtime1 + " " + SVNdate2 + " " + SVNtime2 + + print(times) + + return (times) + + +def getFossilDiff(diff1, diff2, prjctName, prjctPath): + '''Given two Fossil artifacts, write out two kicad_pcb files to their respective + directories (named after the artifacts). Returns the date and time of both commits''' + + artifact1 = diff1[:6] + artifact2 = diff2[:6] + + findDiff = 'cd ' + prjctPath + ' && fossil diff --brief -r ' + \ + artifact1 + ' --to ' + artifact2 + ' | grep .kicad_pcb' + + changes = Popen( + findDiff, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = changes.communicate() + + changed = (stdout.decode('utf-8')) + print(changed) + if changed == '': + print("No .kicad_pcb files differ between these commits") + sys.exit() + + outputDir1 = prjctPath + plotDir + '/' + artifact1 + outputDir2 = prjctPath + plotDir + '/' + artifact2 + + if not os.path.exists(outputDir1): + os.makedirs(outputDir1) + + if not os.path.exists(outputDir2): + os.makedirs(outputDir2) + + fossilArtifact1 = 'cd ' + prjctPath + ' && fossil cat ' + prjctPath + '/' + prjctName + \ + ' -r ' + artifact1 + ' > ' + outputDir1 + '/' + prjctName + fossilArtifact2 = 'cd ' + prjctPath + ' && fossil cat ' + prjctPath + '/' + prjctName + \ + ' -r ' + artifact2 + ' > ' + outputDir2 + '/' + prjctName + + fossilInfo1 = 'cd ' + prjctPath + ' && fossil info ' + artifact1 + fossilInfo2 = 'cd ' + prjctPath + ' && fossil info ' + artifact2 + + ver1 = Popen( + fossilArtifact1, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = ver1.communicate() + + info1 = Popen( + fossilInfo1, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = info1.communicate() + + dateTime = stdout.decode('utf-8') + dateTime = dateTime.strip() + uuid, _, _, _, _, _, _, _, _, artifactRef, dateDiff1, timeDiff1, *junk1 = dateTime.split( + " ") + + ver2 = Popen( + fossilArtifact2, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = ver2.communicate() + + info2 = Popen( + fossilInfo2, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = info2.communicate() + + dateTime = stdout.decode('utf-8') + dateTime = dateTime.strip() + uuid, _, _, _, _, _, _, _, _, artifactRef, dateDiff2, timeDiff2, *junk1 = dateTime.split( + " ") + + dateTime = dateDiff1 + " " + timeDiff1 + " " + dateDiff2 + " " + timeDiff2 + + return dateTime + + +def getProject(): + + selected = tk.filedialog.askopenfile( + initialdir="~/", + title="Select kicad_pcb file in a VC directory", + filetypes=(("KiCad pcb files", "*.kicad_pcb"), ("all files", "*.*"))) + if selected: + path, prjct = os.path.split(selected.name) + + return (path, prjct) + + +def getSCM(prjctPath): + '''Determines which SCM methodology is in place when passed the enclosing + directory. NB no facility to deal with directories with multiple VCS in place. + Easy to add additional SCMs but also would need to write handling code''' + + scm = '' + + # Check if git + gitCmd = 'cd ' + prjctPath + ' && ' + gitProg + ' status' + git = Popen( + gitCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = git.communicate() + if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): + scm = 'Git' + + # check if Fossil + fossilCmd = 'cd ' + prjctPath + ' && ' + fossilProg + ' status' + fossil = Popen( + fossilCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = fossil.communicate() + if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): + scm = 'Fossil' + + # check if SVN + svnCmd = 'cd ' + prjctPath + ' && ' + svnProg + ' log | perl -l40pe "s/^-+/\n/"' + svn = Popen( + svnCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = svn.communicate() + if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): + scm = 'SVN' + + return scm + + +def fossilDiff(path, kicadPCB): + '''Returns list of Fossil artifacts from a directory containing a + *.kicad_pcb file.''' + + fossilCmd = 'cd ' + path + ' && ' + fossilProg + ' finfo -b ' + kicadPCB + fossil = Popen( + fossilCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, _ = fossil.communicate() + line = (stdout.decode('utf-8').splitlines()) + fArtifacts = [a.replace(' ', '\t\t', 5) for a in line] + return fArtifacts + + +def gitDiff(path, kicadPCB): + '''Returns list of Git artifacts from a directory containing a + *.kicad_pcb file.''' + + gitCmd = 'cd ' + path + ' && ' + gitProg + ' log --pretty=format:"%h \t %s"' + git = Popen( + gitCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, _ = git.communicate() + gArtifacts = (stdout.decode('utf-8').splitlines()) + return gArtifacts + + +def svnDiff(path, kicadPCB): + '''Returns list of SVN resvisions from a directory containing a + *.kicad_pcb file.''' + svnCmd = 'cd ' + path + ' && ' + svnProg + ' log -r HEAD:0 | perl -l40pe "s/^-+/\n/"' + print(svnCmd) + svn = Popen( + svnCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = svn.communicate() + sArtifacts = (stdout.decode('utf-8').splitlines()) + sArtifacts = list(filter(None, sArtifacts)) + return sArtifacts + + +def makeSVG(d1, d2, prjctName, prjctPath, reqLayers): + '''Hands off required .kicad_pcb files to "plotPCB2.py" + and generate .svg files. Does not use the 'reqLayers as the plotting routine + is written in python2 and can't seem to pass layer list easily. Routine is + v quick so no major overhead in plotting unescessay layers to svg. Easiest to + write it to a 'layers' file in the output directory''' + + print("Generating .svg files") + + d1 = d1[:6] + d2 = d2[:6] + + Diff1 = prjctPath + plotDir + '/' + d1 + '/' + prjctName + Diff2 = prjctPath + plotDir + '/' + d2 + '/' + prjctName + + d1SVG = '/tmp/svg/' + d1 + d2SVG = '/tmp/svg/' + d2 + + if not os.path.exists(d1SVG): + os.makedirs(d1SVG) + if not os.path.exists(d2SVG): + os.makedirs(d2SVG) + + +# plot1Cmd = '/usr/local/bin/plotPCB2_DIMS.py ' + Diff1 + " " + d1SVG +# plot2Cmd = '/usr/local/bin/plotPCB2_DIMS.py ' + Diff2 + " " + d2SVG +# These should return the board dimensions which might be useful for plotting/alignment + + plot1Cmd = '/usr/local/bin/plotPCB2.py ' + Diff1 + " " + d1SVG + plot2Cmd = '/usr/local/bin/plotPCB2.py ' + Diff2 + " " + d2SVG + + Popen( + plot1Cmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + Popen( + plot2Cmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + + # if pcbDraw: + # prjct, ext = prjctName.split('.') + # comp1 = pcbDraw + ' /dev/null ' + d1SVG + '/' + prjct + '-Comp.svg ' + Diff1 + # comp2 = pcbDraw + ' /dev/null ' + d2SVG + '/' + prjct + '-Comp.svg ' + Diff2 + # print(comp1, comp2) + # Popen(comp1, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) + # Popen(comp2, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) + + return (d1, d2) + + +def makePNG(svgDir1, svgDir2, qual, prjctName, prjctPath): + '''Convert svg files in tmp directories in to .png files with defined + quality (dpi)''' + + svgDirTop = '/tmp/svg/' + svgDir1 + '/' + svgDirBottom = '/tmp/svg/' + svgDir2 + '/' + qual = str(qual.get()) + + svgdirs = [] + + svgdirs.append(svgDirTop) + svgdirs.append(svgDirBottom) + + for d in svgdirs: + directory = os.fsencode(d) + + _, _, _, diff, e = d.split('/') + print("Converting .svg files in ", d, e, " to .png") + + for file in os.listdir(directory): + + filename = os.fsdecode(file) + + if filename.endswith(".svg"): + + basename, ext = filename.split('.') + command1 = 'convert -density ' + qual + ' -fuzz 1% -trim +repage ' + \ + d + filename + ' ' + d + basename + '.png' + + svgs = subprocess.Popen( + command1, shell=True, stdout=subprocess.PIPE) + out, err = svgs.communicate() + + print("Inverting .png files in ", d, e) + for file in os.listdir(directory): + + filename = os.fsdecode(file) + + if filename.endswith(".png"): + + basename, ext = filename.split('.') + + command2 = 'convert ' + d + basename + '.png -negate ' + \ + prjctPath + plotDir + '/' + diff + '/' + basename + '.png' + + pngs = subprocess.Popen( + command2, shell=True, stdout=subprocess.PIPE) + out, err = pngs.communicate() + + +def comparePNG(diff1, diff2, prjctName, prjctPath): + '''Generate png diffs between DIFF_1 and DIFF_2. Originally the intention was + to use the ImageMagic 'composite stereo 0' function to identify + where items have moved but I could not get this to work. + This flattens the original files to greyscale and they need to be converted + back to rgb in order to be colourised.''' + + diffDir = prjctPath + plotDir + '/diff-' + diff1 + '-' + diff2 + + print("Generating *.png diff files") + if not os.path.exists(diffDir): + os.makedirs(diffDir) + + pngFullPath1 = prjctPath + plotDir + '/' + diff1 + pngFullPath2 = prjctPath + plotDir + '/' + diff2 + + pngBasePath = prjctPath + plotDir + + directory1 = os.fsencode(pngFullPath1) + + for file in os.listdir(directory1): + plot = os.fsdecode(file) + if plot.endswith(".png"): + p1 = pngBasePath + '/' + diff1 + '/' + plot + p2 = pngBasePath + '/' + diff2 + '/' + plot + c1 = 'convert ' + p1 + ' -flatten -grayscale Rec709Luminance ' + p1 + c2 = 'convert ' + p2 + ' -flatten -grayscale Rec709Luminance ' + p2 + composite = 'convert ' + p1 + ' ' + p2 + \ + ' "(" -clone 0-1 -compose darken -composite ")" -channel RGB -combine ' + \ + diffDir + '/' + plot + + comp1 = subprocess.Popen(c1, shell=True, executable='/bin/bash') + comp1 = subprocess.Popen(c2, shell=True, executable='/bin/bash') + diffs = subprocess.Popen( + composite, shell=True, executable='/bin/bash') + + out, err = diffs.communicate() + + page, layerExt = plot.split('-') + layer, ext = layerExt.split('.') + + colour = layerCols.get(layer, '#ffffff') + print(layer, colour) + + colourize = 'convert ' + diffDir + '/' + plot + ' -fill "' + colour + \ + '" -fuzz 75% -opaque "#ffffff" ' + diffDir + '/' + plot + + diffs = subprocess.Popen( + colourize, shell=True, stdout=subprocess.PIPE) + out, err = diffs.communicate() + + col1A = 'convert ' + p1 + ' -define png:color-type=2 ' + p1 + col1 = 'convert ' + p1 + ' -fill "' + colour + '" -fuzz 75% -opaque "#ffffff" ' + p1 + + col2A = 'convert ' + p2 + ' -define png:color-type=2 ' + p2 + col2 = 'convert ' + p2 + ' -fill "' + colour + '" -fuzz 75% -opaque "#ffffff" ' + p2 + + colour1 = subprocess.Popen( + col1A, shell=True, stdout=subprocess.PIPE) + out, err = colour1.communicate() + + colour1 = subprocess.Popen( + col1, shell=True, stdout=subprocess.PIPE) + out, err = colour1.communicate() + + colour2 = subprocess.Popen( + col2A, shell=True, stdout=subprocess.PIPE) + out, err = colour2.communicate() + + colour2 = subprocess.Popen( + col2, shell=True, stdout=subprocess.PIPE) + out, err = colour2.communicate() + + +def makeSupportFiles(prjctName, prjctPath): + '''Setup web directories for output + ''' + + webd = prjctPath + plotDir + webDir + webIndex = webd + '/index.html' + + if not os.path.exists(webd): + os.makedirs(webd) + os.makedirs(webd + '/thumbs') + os.makedirs(webd + '/tryptych') + + if os.path.exists(webIndex): + os.remove(webIndex) + + return + + +def getBoardData(board): + '''Takes a board reference and returns the + basic parameters from it. + Might be safer to split off the top section + before the modules to avoid the possibility of + recyling keywords like 'title' ''' + + prms = { + 'title ': "", + 'rev': "", + 'company': "", + 'date': "", + 'page': "", + 'thickness': 0, + 'drawings': 0, + 'tracks': 0, + 'zones': 0, + 'modules': 0, + 'nets': 0 + } + + thickDone = False + + with open(board, 'r') as f: + for line in f: + for key in prms: + if key in line: + line = line.strip() + line = line.strip(")") + line = line.strip("(") + if 'thickness' in line and thickDone == False: + thickDone = True + prms[key] = (line.split(" "))[1] + elif 'thickness' in line and thickDone == True: + continue + + # Deal with spaces in quoted string - need space after 'title' + if key == 'title ' or key == 'company': + prms[key] = line.split('"')[1::2] + else: + prms[key] = (line.split(" "))[1] + return (prms) + + +def makeOutput(diffDir1, diffDir2, prjctName, prjctPath, times): + '''Write out HTML using template. Iterate through files in diff directories, generating + thumbnails and three way view (tryptych) page. + ''' + webd = prjctPath + plotDir + webDir + + board1 = prjctPath + "/" + plotDir + "/" + diffDir1 + "/" + prjctName + board2 = prjctPath + "/" + plotDir + "/" + diffDir2 + "/" + prjctName + + webIndex = webd + '/index.html' + + webOut = open(webIndex, 'w') + + D1DATE, D1TIME, D2DATE, D2TIME = times.split(" ") + + board_1_Info = getBoardData(board1) + board_2_Info = getBoardData(board2) + + print(board_1_Info) + print(board_2_Info) + + TITLE = board_1_Info.get('title') + DATE = board_1_Info.get('date') + COMPANY = board_1_Info.get('company') + + THICK1 = board_1_Info.get('thickness') + DRAWINGS1 = board_1_Info.get('drawings') + TRACKS1 = board_1_Info.get('tracks') + ZONES1 = board_1_Info.get('zones') + MODULES1 = board_1_Info.get('modules') + NETS1 = board_1_Info.get('nets') + + THICK2 = board_2_Info.get('thickness') + DRAWINGS2 = board_2_Info.get('drawings') + TRACKS2 = board_2_Info.get('tracks') + ZONES2 = board_2_Info.get('zones') + MODULES2 = board_2_Info.get('modules') + NETS2 = board_2_Info.get('nets') + + # TODO Improve CSS colourscheme + + indexHead = ''' + + +
+ + + + +
+ {TITLE}
+ {COMPANY} + |
+ |||||
+ Version
+ |
+
+ {diffDir1}
+ |
+
+ {diffDir2}
+ |
+
+ Thickness (mm)
+ |
+
+ {THICK1}
+ |
+
+ {THICK2}
+ |
+
+ Date
+ |
+
+ {D1DATE}
+ |
+
+ {D2DATE}
+ |
+
+ Drawings
+ |
+
+ {DRAWINGS1}
+ |
+
+ {DRAWINGS2}
+ |
+
+ Time
+ |
+
+ {D1TIME}
+ |
+
+ {D2TIME}
+ |
+
+ Tracks
+ |
+
+ {TRACKS1}
+ |
+
+ {TRACKS2}
+ |
+
+ | +
+ Zones
+ |
+
+ {ZONES1}
+ |
+
+ {ZONES2}
+ |
+ ||
+ Modules
+ |
+
+ {MODULES1}
+ |
+
+ {MODULES2}
+ |
+ |||
+ Nets
+ |
+
+ {NETS1}
+ |
+
+ {NETS2}
+ |
+