diff --git a/python/frep.py b/python/frep.py
index 0d03d6d87b9972186f0855d2d0aabb184fc85a77..0ac053c6ce1b82cb3febed5ed4cd9cffa1c2d256 100755
--- a/python/frep.py
+++ b/python/frep.py
@@ -5,11 +5,9 @@
 #
 # usage:
 #    pcb.py | frep.py [dpi [filename]]
-# with:
-#    https://gitlab.cba.mit.edu/pub/libraries/blob/master/python/pcb.py
 #
-# Neil Gershenfeld 10/8/18
-# (c) Massachusetts Institute of Technology 2018
+# Neil Gershenfeld 9/30/19
+# (c) Massachusetts Institute of Technology 2019
 #
 # This work may be reproduced, modified, distributed,
 # performed, and displayed for any purpose, but must
@@ -35,10 +33,6 @@ frep = json.load(sys.stdin)
 #
 # check arguments
 #
-
-if (frep['zmin'] != frep['zmax']):
-   print('> 2D not (yet) supported')
-   sys.exit()
 if (frep['type'] != 'RGB'):
    print('types other than RGB not (yet) supported')
    sys.exit()
@@ -61,7 +55,7 @@ elif (len(sys.argv) == 3):
 # evaluate
 #
 
-print('evaluating ...')
+print('evaluating')
 xmin = frep['xmin']
 xmax = frep['xmax']
 ymin = frep['ymin']
@@ -72,8 +66,19 @@ x = arange(xmin,xmax,delta)
 y = flip(arange(ymin,ymax,delta),0)
 X = outer(ones(y.size),x)
 Y = outer(y,ones(x.size))
-Z = frep['zmin']
-f = eval(frep['function'])
+if (len(frep['layers']) == 1):
+   Z = frep['layers'][0]
+   print "   z =",Z
+   f = eval(frep['function']).astype(uint32)
+else:
+   f = zeros((y.size,x.size),dtype=uint32)
+   zmin = min(frep['layers'])
+   zmax = max(frep['layers'])
+   for Z in frep['layers']:
+      print "   z =",Z
+      i = int(255*(Z-zmin)/(zmax-zmin)) | (255 << 8) | (255 << 16)
+      flayer = i & (eval(frep['function'])).astype(uint32)
+      f = f + flayer
 
 #
 # construct image
diff --git a/python/pcb.png b/python/pcb.png
index 813108f94ac1ac0a99205740865185a6bf7a277e..a6c3b584b7c8da9dcd489bd414a42d16089e3472 100644
Binary files a/python/pcb.png and b/python/pcb.png differ
diff --git a/python/pcb.py b/python/pcb.py
index d27722692007e27015df1b4349f5e530d2a8dbda..f56a30c1a47d4b279cf48d867c12cdc4028b0610 100755
--- a/python/pcb.py
+++ b/python/pcb.py
@@ -5,11 +5,9 @@
 #
 # usage:
 #    pcb.py | frep.py [dpi [filename]]
-# with:
-#    https://gitlab.cba.mit.edu/pub/libraries/blob/master/python/frep.py
 #
-# Neil Gershenfeld 10/8/18
-# (c) Massachusetts Institute of Technology 2018
+# Neil Gershenfeld 9/30/19
+# (c) Massachusetts Institute of Technology 2019
 #
 # This work may be reproduced, modified, distributed,
 # performed, and displayed for any purpose, but must
@@ -22,12 +20,17 @@
 # uncomment for desired output:
 #
 
-output = "traces, labels, and exterior"
-#output = "traces and exterior"
+#output = "top, labels, and exterior"
+#output = "top, bottom, labels, and exterior"
+output = "top, bottom, labels, holes, and exterior"
+#output = "top traces"
+#output = "top traces and exterior"
+#output = "bottom traces reversed"
+#output = "bottom traces reversed and exterior"
+#output = "holes"
 #output = "interior"
+#output = "holes and interior"
 #output = "exterior"
-#output = "traces"
-#output = "holes"
 #output = "solder mask"
 
 #
@@ -951,6 +954,7 @@ class PCB:
       self.interior = rectangle(x0,x0+width,y0,y0+height)
       self.exterior = subtract(true,rectangle(x0,x0+width,y0,y0+height))
       self.mask = false
+      self.holes = false
    def add(self,part):
       self.board = add(self.board,part)
       self.mask = add(self.mask,move(part,-mask,mask))
@@ -990,6 +994,8 @@ class part:
       deg_angle = angle
       angle = math.pi*angle/180
       self.shape = translate(self.shape,x,y,z)
+      if hasattr(self,'holes'):
+         self.holes = translate(self.holes,x,y,z)
       for i in range(len(self.pad)):
          xnew = math.cos(angle)*self.pad[i].x - math.sin(angle)*self.pad[i].y
          ynew = math.sin(angle)*self.pad[i].x + math.cos(angle)*self.pad[i].y
@@ -1008,12 +1014,15 @@ class part:
          else:
             pcb.labels = add(pcb.labels,text(self.labels[i].text,self.labels[i].x,self.labels[i].y,self.labels[i].z,self.labels[i].line,color=Red,angle=(deg_angle-self.labels[i].angle-180)).shape)
       pcb = pcb.add(self.shape)
-      return pcb
-   def layer(self,pcb,z):
-      pcb = pcb.add(translate(self.shape,0,0,z))
+      if hasattr(self,'holes'):
+         pcb.holes = add(pcb.holes,self.holes)
       return pcb
 
-def rightwire(pcb,width,*points):
+def wire(pcb,width,*points):
+   x0 = points[0].x
+   y0 = points[0].y
+   z0 = points[0].z
+   pcb.board = add(pcb.board,cylinder(x0,y0,z0,z0,width/2))
    for i in range(1,len(points)):
       x0 = points[i-1].x
       y0 = points[i-1].y
@@ -1021,21 +1030,11 @@ def rightwire(pcb,width,*points):
       x1 = points[i].x
       y1 = points[i].y
       z1 = points[i].z
-      if (x0 < x1):
-         pcb.board = add(pcb.board,cube(x0-width/2,x1+width/2,y0-width/2,y0+width/2,z0,z0))
-      elif (x1 < x0):
-         pcb.board = add(pcb.board,cube(x1-width/2,x0+width/2,y0-width/2,y0+width/2,z0,z0))
-      if (y0 < y1):
-         pcb.board = add(pcb.board,cube(x1-width/2,x1+width/2,y0-width/2,y1+width/2,z0,z0))
-      elif (y1 < y0):
-         pcb.board = add(pcb.board,cube(x1-width/2,x1+width/2,y1-width/2,y0+width/2,z0,z0))
+      pcb.board = add(pcb.board,line(x0,y0,x1,y1,z1,width))
+      pcb.board = add(pcb.board,cylinder(x1,y1,z1,z1,width/2))
    return pcb
 
-def wire(pcb,width,*points):
-   x0 = points[0].x
-   y0 = points[0].y
-   z0 = points[0].z
-   pcb.board = add(pcb.board,cylinder(x0,y0,z0,z0,width/2))
+def wirer(pcb,width,*points):
    for i in range(1,len(points)):
       x0 = points[i-1].x
       y0 = points[i-1].y
@@ -1043,14 +1042,33 @@ def wire(pcb,width,*points):
       x1 = points[i].x
       y1 = points[i].y
       z1 = points[i].z
-      pcb.board = add(pcb.board,line(x0,y0,x1,y1,z1,width))
-      pcb.board = add(pcb.board,cylinder(x1,y1,z1,z1,width/2))
+      if (x0 < x1):
+         pcb.board = add(pcb.board,cube(x0-width/2,x1+width/2,y0-width/2,y0+width/2,z0,z0))
+      elif (x1 < x0):
+         pcb.board = add(pcb.board,cube(x1-width/2,x0+width/2,y0-width/2,y0+width/2,z0,z0))
+      if (y0 < y1):
+         pcb.board = add(pcb.board,cube(x1-width/2,x1+width/2,y0-width/2,y1+width/2,z0,z0))
+      elif (y1 < y0):
+         pcb.board = add(pcb.board,cube(x1-width/2,x1+width/2,y1-width/2,y0+width/2,z0,z0))
    return pcb
 
 #
 # PCB library
 #
 
+class via(part):
+   #
+   # via
+   #
+   def __init__(self,zb,zt,rv,rp,value=''):
+      self.value = value
+      self.labels = []
+      self.pad = [point(0,0,0)]
+      self.shape = cylinder(0,0,zb,zt,rp)
+      self.holes = cylinder(0,0,zb,zt,rv)
+      self.pad.append(point(0,0,zt))
+      self.pad.append(point(0,0,zb))
+
 #
 # discretes
 #
@@ -4984,153 +5002,219 @@ class fab(part):
 # define board
 #
 
-w = .015
-width = 1
-height = .93
-mask = .004
-x = 1
-y = 1
-z = -.005
-d = .06
+width = 1.02 # board width
+height = .87 # board height
+x = 1 # x origin
+y = 1 # y origin
+zt = 0 # top z
+zb = -0.06 # bottom z
+w = .015 # wire width
+rv = 0.016 # via size
+rp = 0.03 # pad size
+mask = .004 # solder mask size
 
 pcb = PCB(x,y,width,height,mask)
 
 IC1 = ATtiny44_SOICN('IC1\nt44')
-pcb = IC1.add(pcb,x+.47,y+.59,z)
+pcb = IC1.add(pcb,x+.49,y+.56)
+
+VIC1 = via(zb,zt,rv,rp)
+pcb = VIC1.add(pcb,IC1.pad[14].x+.08,IC1.pad[14].y+.02)
+
+pcb = wire(pcb,w,
+   VIC1.pad[1],
+   IC1.pad[14])
 
 J1 = header_ISP('J1\nISP')
-pcb = J1.add(pcb,IC1.x+.05,IC1.pad[7].y-.22,z,angle=90)
+pcb = J1.add(pcb,IC1.x+.05,IC1.pad[7].y-.22,angle=90)
 
 pcb = wire(pcb,w,
    IC1.pad[8],
-   point(J1.pad[1].x,IC1.pad[8].y,z),
+   point(J1.pad[1].x,IC1.pad[8].y),
    J1.pad[1])
 
 pcb = wire(pcb,w,
    IC1.pad[9],
-   point(J1.pad[3].x,IC1.pad[9].y,z),
+   point(J1.pad[3].x,IC1.pad[9].y),
    J1.pad[3])
    
 pcb = wire(pcb,w,
    IC1.pad[7],
-   point(IC1.pad[7].x,J1.y+.02,z),
-   point(IC1.pad[7].x+.04,J1.y-.02,z),
-   point(J1.pad[4].x,J1.y-.02,z),
+   point(IC1.pad[7].x,J1.y+.02),
+   point(IC1.pad[7].x+.04,J1.y-.02),
+   point(J1.pad[4].x,J1.y-.02),
    J1.pad[4])
    
 pcb = wire(pcb,w,
    IC1.pad[4],
-   point(J1.pad[5].x,IC1.pad[4].y,z),
+   point(J1.pad[5].x,IC1.pad[4].y),
    J1.pad[5])
 
+VJ12 = via(zb,zt,rv,rp)
+pcb = VJ12.add(pcb,J1.pad[2].x+.075,J1.pad[2].y)
+
 pcb = wire(pcb,w,
-   IC1.pad[14],
-   point(J1.x-.01,IC1.pad[14].y,z),
-   point(J1.x-.05,IC1.pad[14].y-.04,z),
-   point(J1.x-.05,J1.y+.02,z),
-   point(J1.x+.05,J1.y+.02,z),
-   point(J1.x+.05,J1.pad[2].y-.08,z),
-   point(J1.pad[6].x,J1.pad[2].y-.08,z),
-   J1.pad[6])
+   VJ12.pad[1],
+   J1.pad[2])
 
-J2 = header_FTDI('J2 FTDI')
-pcb = J2.add(pcb,x+width-.22,IC1.y-.0,z,angle=0)
+VJ16 = via(zb,zt,rv,rp)
+pcb = VJ16.add(pcb,J1.pad[6].x-.075,J1.pad[6].y)
 
 pcb = wire(pcb,w,
-   J1.pad[2],
-   point(J2.x,J1.pad[2].y,z),
-   point(J2.x+.08,J1.pad[2].y+.08,z),
-   point(J2.x+.08,J2.pad[3].y,z),
-   J2.pad[3])
+   VJ16.pad[1],
+   J1.pad[6])
+
+pcb = wire(pcb,w,
+   VJ16.pad[2],
+   VIC1.pad[2])
+
+J2 = header_FTDI('J2 FTDI')
+pcb = J2.add(pcb,x+width-.22,IC1.y-.0,angle=0)
 
 pcb = wire(pcb,w,
    IC1.pad[13],
-   point(IC1.pad[13].x+.105,IC1.pad[13].y,z),
-   point(IC1.pad[13].x+.105,J2.pad[4].y,z),
+   point(IC1.pad[13].x+.105,IC1.pad[13].y),
+   point(IC1.pad[13].x+.105,J2.pad[4].y),
    J2.pad[4])
 
 pcb = wire(pcb,w,
    IC1.pad[12],
-   point(IC1.pad[12].x+.07,IC1.pad[12].y,z),
-   point(IC1.pad[12].x+.07,J2.pad[5].y+.04,z),
-   point(IC1.pad[12].x+.11,J2.pad[5].y,z),
+   point(IC1.pad[12].x+.07,IC1.pad[12].y),
+   point(IC1.pad[12].x+.07,J2.pad[5].y+.04),
+   point(IC1.pad[12].x+.11,J2.pad[5].y),
    J2.pad[5])
 
+VJ2 = via(zb,zt,rv,rp)
+pcb = VJ2.add(pcb,J2.pad[3].x+.1,J2.pad[3].y)
+
+pcb = wire(pcb,w,
+   VJ2.pad[1],
+   J2.pad[3])
+
+pcb = wire(pcb,w,
+   VJ12.pad[2],
+   VJ2.pad[2])
+
 XTAL1 = XTAL_EFOBM('XTAL1\n20 MHz')
-pcb = XTAL1.add(pcb,IC1.pad[4].x-.2,IC1.pad[13].y+.003,z,angle=-90)
+pcb = XTAL1.add(pcb,IC1.pad[4].x-.2,IC1.pad[13].y+.003,angle=-90)
 
 pcb = wire(pcb,w,
    IC1.pad[2],
-   point(XTAL1.x+.12,IC1.pad[2].y,z),
-   point(XTAL1.x+.12,XTAL1.pad[1].y,z),
+   point(XTAL1.x+.12,IC1.pad[2].y),
+   point(XTAL1.x+.12,XTAL1.pad[1].y),
    XTAL1.pad[1])
 
-pcb = wire(pcb,w,
-   J1.pad[6],
-   point(J1.pad[6].x,J1.pad[6].y-.08,z),
-   point(XTAL1.x-.04,J1.pad[6].y-.08,z),
-   point(XTAL1.x-.12,J1.pad[6].y,z),
-   point(XTAL1.x-.12,XTAL1.pad[2].y,z),
-   XTAL1.pad[2])
-
 pcb = wire(pcb,w,
    IC1.pad[3],
    XTAL1.pad[3])
 
+VXTAL = via(zb,zt,rv,rp)
+pcb = VXTAL.add(pcb,XTAL1.x-.12,XTAL1.y)
+
+pcb = wire(pcb,w,
+   XTAL1.pad[2],
+   VXTAL.pad[1])
+
+pcb = wire(pcb,w,
+   VJ16.pad[2],
+   VXTAL.pad[2])
+
 R1 = R_1206('R1\n10k');
-pcb = R1.add(pcb,IC1.pad[1].x,IC1.pad[1].y+.1,z)
+pcb = R1.add(pcb,IC1.pad[1].x,IC1.pad[1].y+.1)
 
 pcb = wire(pcb,w,
    R1.pad[1],
    IC1.pad[1])
 
 pcb = wire(pcb,w,
-   J2.pad[3],
-   point(J2.pad[3].x+.08,J2.pad[3].y,z),
-   point(J2.pad[3].x+.08,R1.y+.06,z),
-   point(R1.pad[1].x,R1.y+.06,z),
+   R1.pad[2],
+   point(J1.pad[5].x,R1.y),
+   J1.pad[5])
+
+VR1 = via(zb,zt,rv,rp)
+pcb = VR1.add(pcb,R1.pad[1].x-.08,R1.y)
+
+pcb = wire(pcb,w,
+   VR1.pad[1],
    R1.pad[1])
 
 pcb = wire(pcb,w,
-   R1.pad[2],
-   point(J1.pad[5].x,R1.y,z),
-   J1.pad[5])
+   VJ2.pad[2],
+   point(VJ2.x,VR1.y,zb),
+   VR1.pad[2])
 
 C1 = C_1206('C1\n1uF');
-pcb = C1.add(pcb,IC1.pad[14].x,R1.y,z)
+pcb = C1.add(pcb,IC1.pad[14].x,R1.y)
 
 pcb = wire(pcb,w,
-   IC1.pad[14],
+   J2.pad[1],
    C1.pad[2])
 
 pcb = wire(pcb,w,
-   C1.pad[1],
-   point(C1.pad[1].x,C1.y+.06,z))
+   C1.pad[2],
+   VIC1.pad[1])
+
+VC1 = via(zb,zt,rv,rp)
+pcb = VC1.add(pcb,C1.pad[1].x-.025,C1.y-.06)
 
 pcb = wire(pcb,w,
-   J2.pad[1],
-   C1.pad[2])
+   VC1.pad[1],
+   C1.pad[1])
+
+pcb = wire(pcb,w,
+   VR1.pad[2],
+   point(VC1.x,VR1.y,zb),
+   VC1.pad[2])
 
 #
 # select output
 #
 
 outputs = {}
-if (output == "traces, labels, and exterior"):
-   outputs["function"] = add(add(color(Tan,pcb.board),pcb.labels),color(White,pcb.exterior))
-elif (output == "traces and exterior"):
+if (output == "top, labels, and exterior"):
+   outputs["function"] = add(add(color(Tan,pcb.board),pcb.labels),
+      color(White,pcb.exterior))
+   outputs["layers"] = [zt]
+elif (output == "top, bottom, labels, and exterior"):
+   outputs["function"] = add(add(color(Tan,pcb.board),pcb.labels),
+      color(White,pcb.exterior))
+   outputs["layers"] = [zb,zt]
+elif (output == "top, bottom, labels, holes, and exterior"):
+   outputs["function"] = add(add(color(Tan,
+      subtract(pcb.board,pcb.holes)),pcb.labels),
+      color(White,pcb.exterior))
+   outputs["layers"] = [zb,zt]
+elif (output == "top traces"):
+   outputs["function"] = color(White,pcb.board)
+   outputs["layers"] = [zt]
+elif (output == "top traces and exterior"):
    outputs["function"] = color(White,add(pcb.board,pcb.exterior))
+   outputs["layers"] = [zt]
+elif (output == "bottom traces reversed"):
+   outputs["function"] = color(White,
+      reflect_x(pcb.board,2*x+width))
+   outputs["layers"] = [zb]
+elif (output == "bottom traces reversed and exterior"):
+   outputs["function"] = color(White,
+      reflect_x(add(pcb.board,pcb.exterior),2*x+width))
+   outputs["layers"] = [zb]
 elif (output == "interior"):
    outputs["function"] = color(White,pcb.interior)
+   outputs["layers"] = [zb]
 elif (output == "exterior"):
    outputs["function"] = color(White,pcb.exterior)
-elif(output == "traces"):
-   outputs["function"] = color(White,pcb.board)
+   outputs["layers"] = [zb]
 elif (output == "holes"):
-   z = z-z
-   outputs["function"] = color(White,add(pcb.exterior,subtract(pcb.interior,pcb.board)))
+   outputs["function"] = color(White,
+      subtract(add(pcb.exterior,pcb.interior),pcb.holes))
+   outputs["layers"] = [zb]
+elif (output == "holes and interior"):
+   outputs["function"] = color(White,
+      subtract(pcb.interior,pcb.holes))
+   outputs["layers"] = [zb]
 elif (output == "solder mask"):
    outputs["function"] = color(White,pcb.mask)
+   outputs["layers"] = [zt]
 else:
    print("oops -- don't recognize output")
 
@@ -5143,8 +5227,6 @@ outputs["xmin"] = x-border # min x to render
 outputs["xmax"] = x+width+border # max x to render
 outputs["ymin"] = y-border # min y to render
 outputs["ymax"] = y+height+border # max y to render
-outputs["zmin"] = z # min z to zender
-outputs["zmax"] = z # max z to zender
 outputs["mm_per_unit"] = 25.4 # use inch units
 outputs["type"] = "RGB" # use RGB color