diff --git a/files.html b/files.html
index 0235a44bb712f8d3c4fa1fc30a79b875f1de985c..d8a656fc940992ee5c7a6f009055c1ed9a541fd0 100644
--- a/files.html
+++ b/files.html
@@ -19,6 +19,7 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/files.js'>files.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/mods.js'>mods.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/modules.js'>modules.js</a><br>
+<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node_modules</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/printserver.js'>printserver.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/programs.js'>programs.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/serialserver.js'>serialserver.js</a><br>
@@ -83,6 +84,7 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/inflate'>inflate</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/reflect'>reflect</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/repeat'>repeat</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/revolve'>revolve</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/rotate'>rotate</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/scale'>scale</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/frep/transforms/shear'>shear</a><br>
diff --git a/modules/frep/transforms/revolve b/modules/frep/transforms/revolve
new file mode 100755
index 0000000000000000000000000000000000000000..94fca97657889191320ec42d9018902f8731c40f
--- /dev/null
+++ b/modules/frep/transforms/revolve
@@ -0,0 +1,240 @@
+//
+// frep revolve
+//
+// Neil Gershenfeld
+// (c) Massachusetts Institute of Technology 2018
+//
+// This work may be reproduced, modified, distributed, performed, and
+// displayed for any purpose, but must acknowledge the mods
+// project. Copyright is retained and must be preserved. The work is
+// provided as is; no warranty is provided, and users accept all
+// liability.
+//
+// closure
+//
+(function(){
+//
+// module globals
+//
+var mod = {}
+//
+// name
+//
+var name = 'frep revolve'
+//
+// initialization
+//
+var init = function() {
+   mod.x.checked = false
+   mod.y.checked = false
+   mod.z.checked = true
+   mod.ox.value = 0
+   mod.oy.value = 0
+   mod.oz.value = 0
+   }
+//
+// inputs
+//
+var inputs = {
+   shape:{type:'',
+      event:function(evt){
+         mod.shape = evt.detail
+         outputs.shape.event()
+         }}}
+//
+// outputs
+//
+/*
+revolve
+   axis x y z
+   origin x y z
+   x
+      x = rxz
+      y = y
+   y
+      x = x
+      y = ryz
+   z
+      x = rxy
+      y = z
+*/
+var outputs = {
+   shape:{type:'',
+      event:function(){
+         var fn = mod.shape.function
+         var variables = mod.shape.variables
+         var type = mod.shape.type        
+         var ox = parseFloat(mod.ox.value)
+         var oy = parseFloat(mod.oy.value)
+         var oz = parseFloat(mod.oz.value)
+         var limits = []
+         for (var v = 0; v < mod.shape.limits.length; ++v) {
+            limits[v] = []
+            limits[v][0] = mod.shape.limits[v][0]
+            limits[v][1] = mod.shape.limits[v][1]
+            }
+         if (mod.x.checked) {
+            var xvar = variables[0]
+            var re = new RegExp(xvar,'g')
+            fn = fn.replace(re,`(${2*origin0}-(${xvar}))`)
+            var dx = mod.shape.limits[0][1]-mod.shape.limits[0][0]
+            var dl = mod.shape.limits[0][0]-origin0
+            limits[0][0] = origin0-(dl+dx)
+            limits[0][1] = origin0-dl
+            }
+         else if (mod.y.checked) {
+            var yvar = variables[1]
+            var re = new RegExp(yvar,'g')
+            fn = fn.replace(re,`(${2*origin0}-(${yvar}))`)
+            var dy = mod.shape.limits[1][1]-mod.shape.limits[1][0]
+            var dl = mod.shape.limits[1][0]-origin0
+            limits[1][0] = origin0-(dl+dy)
+            limits[1][1] = origin0-dl
+            }
+         else if (mod.z.checked) {
+            var zvar = variables[2]
+            var re = new RegExp(zvar,'g')
+            fn = fn.replace(re,`(${2*origin0}-(${zvar}))`)
+            var dz = mod.shape.limits[2][1]-mod.shape.limits[2][0]
+            var dl = mod.shape.limits[2][0]-origin0
+            limits[2][0] = origin0-(dl+dz)
+            limits[2][1] = origin0-dl
+            }
+         else if (mod.xy.checked) {
+            var xvar = variables[0]
+            var yvar = variables[1]
+            var xre = new RegExp(xvar,'g')
+            var yre = new RegExp(yvar,'g')
+            fn = fn.replace(xre,'TEMP')
+            fn = fn.replace(yre,`((${origin1})+(${xvar})-(${origin0}))`)
+            fn = fn.replace(/TEMP/g,`((${origin0})+(${yvar})-(${origin1}))`)
+            var dx = mod.shape.limits[0][1]-mod.shape.limits[0][0]
+            var dy = mod.shape.limits[1][1]-mod.shape.limits[1][0]
+            var dxl = mod.shape.limits[0][0]-origin0
+            var dyl = mod.shape.limits[1][0]-origin1
+            limits[0][0] = origin0+dyl
+            limits[0][1] = origin0+dyl+dy
+            limits[1][0] = origin1+dxl
+            limits[1][1] = origin1+dxl+dx
+            }
+         else if (mod.xz.checked) {
+            var xvar = variables[0]
+            var zvar = variables[2]
+            var xre = new RegExp(xvar,'g')
+            var zre = new RegExp(zvar,'g')
+            fn = fn.replace(xre,'TEMP')
+            fn = fn.replace(zre,`((${origin1})+(${xvar})-(${origin0}))`)
+            fn = fn.replace(/TEMP/g,`((${origin0})+(${zvar})-(${origin1}))`)
+            var dx = mod.shape.limits[0][1]-mod.shape.limits[0][0]
+            var dz = mod.shape.limits[2][1]-mod.shape.limits[2][0]
+            var dxl = mod.shape.limits[0][0]-origin0
+            var dzl = mod.shape.limits[2][0]-origin1
+            limits[0][0] = origin0+dzl
+            limits[0][1] = origin0+dzl+dz
+            limits[2][0] = origin1+dxl
+            limits[2][1] = origin1+dxl+dx
+            }
+         else if (mod.yz.checked) {
+            var yvar = variables[1]
+            var zvar = variables[2]
+            var yre = new RegExp(yvar,'g')
+            var zre = new RegExp(zvar,'g')
+            fn = fn.replace(yre,'TEMP')
+            fn = fn.replace(zre,`((${origin1})+(${yvar})-(${origin0}))`)
+            fn = fn.replace(/TEMP/g,`((${origin0})+(${zvar})-(${origin1}))`)
+            var dy = mod.shape.limits[1][1]-mod.shape.limits[1][0]
+            var dz = mod.shape.limits[2][1]-mod.shape.limits[2][0]
+            var dyl = mod.shape.limits[1][0]-origin0
+            var dzl = mod.shape.limits[2][0]-origin1
+            limits[1][0] = origin0+dzl
+            limits[1][1] = origin0+dzl+dz
+            limits[2][0] = origin1+dyl
+            limits[2][1] = origin1+dyl+dy
+            }
+         var shape = {function:fn,variables:variables,limits:limits,type:type}
+         mods.output(mod,'shape',shape)}
+         }}
+//
+// interface
+//
+var interface = function(div){
+   mod.div = div
+   //
+   // axis
+   //
+   div.appendChild(document.createTextNode('axis: '))
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('x'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'axis'
+      input.id = mod.div.id+'x'
+      div.appendChild(input)
+      mod.x = input
+   div.appendChild(document.createTextNode(' y'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'axis'
+      input.id = mod.div.id+'y'
+      div.appendChild(input)
+      mod.y = input
+   div.appendChild(document.createTextNode(' z'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'axis'
+      input.id = mod.div.id+'z'
+      div.appendChild(input)
+      mod.z = input
+   div.appendChild(document.createElement('br'))
+   //
+   // origin
+   //
+   div.appendChild(document.createTextNode('origin:'))
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('x: '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 3
+      div.appendChild(input)
+      mod.ox = input
+   div.appendChild(document.createTextNode(' y: '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 3
+      div.appendChild(input)
+      mod.oy = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('z: '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 3
+      div.appendChild(input)
+      mod.oz = input
+   div.appendChild(document.createElement('br'))
+   //
+   // output button
+   //
+   var btn = document.createElement('button')
+      btn.style.padding = mods.ui.padding
+      btn.style.margin = 1
+      btn.appendChild(document.createTextNode('output'))
+      btn.addEventListener('click',function(){
+         outputs.shape.event()
+         })
+      div.appendChild(btn)
+   }
+//
+// local functions
+//
+;
+//
+// return values
+//
+return ({
+   name:name,
+   init:init,
+   inputs:inputs,
+   outputs:outputs,
+   interface:interface
+   })
+}())
diff --git a/modules/index.js b/modules/index.js
index 40abbae9baa265afc23e9c91b03d1d8635e0496b..8eb59cf8b932ebbcc849311767275b3fd410b2c2 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -54,6 +54,7 @@ module_menu('      extrude','modules/frep/transforms/extrude')
 module_menu('      inflate','modules/frep/transforms/inflate')
 module_menu('      reflect','modules/frep/transforms/reflect')
 module_menu('      repeat','modules/frep/transforms/repeat')
+module_menu('      revolve','modules/frep/transforms/revolve')
 module_menu('      rotate','modules/frep/transforms/rotate')
 module_menu('      scale','modules/frep/transforms/scale')
 module_menu('      shear','modules/frep/transforms/shear')