diff --git a/js/lattice/CompositeEditorLattice.js b/js/lattice/CompositeEditorLattice.js
index 7e3eeee05cfaf4eeca77a74d3cec75534dfe98fc..1f7112758c19f76cf112559cdfc512df8ca99b8f 100644
--- a/js/lattice/CompositeEditorLattice.js
+++ b/js/lattice/CompositeEditorLattice.js
@@ -80,6 +80,7 @@ define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'th
                 self.unset(key, {silent:true});
             });
             this.compositeCells = null;
+            this.showCells();
         },
     };
 
diff --git a/js/lattice/CubeLattice.js b/js/lattice/CubeLattice.js
index 2853e6c4e0f1c89f4fa3561b1451f37690044ba8..492fddc8e02bb17b6363ba5e68b4fdb23e237ed3 100644
--- a/js/lattice/CubeLattice.js
+++ b/js/lattice/CubeLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 5/26/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var CubeLattice = {
 
diff --git a/js/lattice/GIKLattice.js b/js/lattice/GIKLattice.js
index c92f0443f79e214706f015e6e56ac4033d831e7e..25e65679fe5af5c72c1612ccb0a419b3da37acad 100644
--- a/js/lattice/GIKLattice.js
+++ b/js/lattice/GIKLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 5/26/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var GIKLattice = {
 
diff --git a/js/lattice/KelvinLattice.js b/js/lattice/KelvinLattice.js
index f84156ff5fd5dcb6fc1c428ec58f3ee6794412d1..231de87ab506a8eef4d944d930de0a0bbea595e8 100644
--- a/js/lattice/KelvinLattice.js
+++ b/js/lattice/KelvinLattice.js
@@ -3,8 +3,8 @@
  */
 
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var KelvinLattice =  {
 
diff --git a/js/lattice/Lattice.js b/js/lattice/Lattice.js
index 6db3e6a7e5806f6bc18f0c3c939e48b2c3a3d86c..701b7ceb6491bf02a677de4b38463d4a5f6189a9 100644
--- a/js/lattice/Lattice.js
+++ b/js/lattice/Lattice.js
@@ -3,161 +3,124 @@
  */
 
 
-define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel', 'latticeBase'],
+    function(_, Backbone, appState, globals, plist, THREE, three, LatticeBase){
 
-    var Lattice = Backbone.Model.extend({
+    var Lattice = LatticeBase.extend({
 
-        defaults: {
+        defaults: _.extend(LatticeBase.prototype.defaults, {
 
             units: "mm",
 
             nodes: [],
-            cellsMin: null,//min position of cells matrix
-            cellsMax: null,//max position of cells matrix
-            numCells: 0,
-
-            //spacing for connectors/joints
-            cellSeparation: {xy:0, z:0},
-
-            cellType: "cube",
-            connectionType: "face",
-            partType: null,
-            materialType: null,
-            materialClass: "mechanical"
-        },
 
-        initialize: function(){
+            cellSeparation: {xy:0, z:0},//spacing for connectors/joints
+            partType: null
+        }),
+
 
-            this.cells = [[[null]]];//3D matrix containing all cells and null, dynamic size
-            this.sparseCells = [[[null]]];//3D matrix containing highest hierarchical level of cells and null
 
-            //bind events
+        __bindEvents: function(){
+
             this.listenTo(this, "change:partType", this._updatePartType);
             this.listenTo(this, "change:cellType change:connectionType", function(){
                 this._updateLatticeType(false);
             });
             this.listenTo(this, "change:cellSeparation", this._updateCellSeparation);
 
-            this.listenTo(appState, "change:cellMode", this._updateForMode);
-            this.listenTo(appState, "change:cellsVisible", this._setCellVisibility);
-
             this.listenTo(this, "change:materialClass", this._loadMaterialClass);
 
             this.listenTo(appState, "change:currentNav", this._navChanged);
+        },
 
+        __initialize: function(){
             this._updateLatticeType(false);
-
-            appState.setLattice(this);
         },
 
 
 
 
 
-        //add/remove cells
 
-        addCellsInRange: function(range){//add a block of cells (extrude)
-            this.checkForMatrixExpansion(this.sparseCells, range.max, range.min);
 
-            var cellsMin = this.get("cellsMin");
-            var relativeMin = (new THREE.Vector3()).subVectors(range.min, cellsMin);
-            var relativeMax = (new THREE.Vector3()).subVectors(range.max, this.get("cellsMin"));
-
-            for (var x=relativeMin.x;x<=relativeMax.x;x++){
-                for (var y=relativeMin.y;y<=relativeMax.y;y++){
-                    for (var z=relativeMin.z;z<=relativeMax.z;z++){
-                        if (!this.sparseCells[x][y][z]) {
-                            var self = this;
-                             this.makeCellForLatticeType((new THREE.Vector3(x, y, z)).add(cellsMin), function(cell){
-                                self.sparseCells[x][y][z] = cell;
-                                self.set("numCells", self.get("numCells")+1);
-                            });
-                        } else console.warn("already a cell there");
-                    }
-                }
-            }
-            three.render();
-        },
+        //lattice type
 
-        addCellAtIndex: function(indices, noRender, noCheck){//no render no check from fill
+        _updateLatticeType: function(loadingFromFile){//do not clear cells if loading from file (cells array contains important metadata)
 
-            if (!noCheck || noCheck === undefined) this.checkForMatrixExpansion(this.sparseCells, indices, indices);
+            this._setToDefaultsSilently();
+            this._setDefaultCellMode();
+            this._loadMaterialClass();
 
-            var index = (new THREE.Vector3()).subVectors(indices, this.get("cellsMin") || indices);
-            if (!this.sparseCells[index.x][index.y][index.z]) {
-                var self = this;
-                if (!noRender || noRender === undefined) three.setRenderFlag();
-                this.makeCellForLatticeType(indices, function(cell){
-                    self.sparseCells[index.x][index.y][index.z] = cell;
-                    self.set("numCells", self.get("numCells")+1);
-                });
-            } else console.warn("already a cell there");
+            if (loadingFromFile === undefined) loadingFromFile = false;
+            if (loadingFromFile) console.warn('loading from file');
+            this.clearCells();
 
-        },
+            if (this._undo) this._undo();
+            if (globals.basePlane) globals.basePlane.destroy();
+            if (globals.highlighter) globals.highlighter.destroy();
 
-        _indexForPosition: function(absPosition){
-            return new THREE.Vector3(
-                Math.round(absPosition.x/this.xScale()),
-                Math.round(absPosition.y/this.yScale()),
-                Math.round(absPosition.z/this.zScale()));
+            this._initLatticeSubclass();
         },
 
-        _positionForIndex: function(index){
-            var position = index.clone();
-            position.x = (position.x)*this.xScale();
-            position.y = (position.y)*this.yScale();
-            position.z = (position.z)*this.zScale();
-            return position;
+        _setToDefaultsSilently: function(){
+            var newCellType = this.get("cellType");
+            var newConnectionType = this.get("connectionType");
+            if (newConnectionType == this.previous("connectionType")){
+                newConnectionType = _.keys(plist["allConnectionTypes"][newCellType])[0];
+                this.set("connectionType", newConnectionType, {silent:true});
+            }
+            var partType = _.keys(plist["allPartTypes"][newCellType][newConnectionType])[0];
+            this.set("partType", partType, {silent:true});
+            this.set("materialClass", plist.allMaterialTypes[newCellType][newConnectionType], {silent:true});
         },
 
-    //    removeCellAtIndex: function(indices){
-    //
-    //        var index = this._subtract(indices, this.get("cellsMin"));
-    //        var cells = this.get("cells");
-    //        if (index.x<cells.length && index.y<cells[0].length && index.z<cells[0][0].length){
-    //            this.removeCell(cells[index.x][index.y][index.z]);
-    //        }
-    //    },
-
-        removeCell: function(cell){
-            if (!cell) return;
-            var index = (new THREE.Vector3()).subVectors(cell.index, this.get("cellsMin"));
-            cell.destroy();
-            this.sparseCells[index.x][index.y][index.z] = null;
+        _setDefaultCellMode: function(){//if no part associated with this lattice type
+            if (!plist["allPartTypes"][this.get("cellType")][this.get("connectionType")]){
+                appState.set("cellMode", "cell");
+            }
+        },
 
-            //todo shrink cells matrix if needed
+        _updateCellSeparation: function(){
+            var cellSep = this.get("cellSeparation");
+            globals.basePlane.updateXYSeparation(cellSep.xy);
 
-            this.set("numCells", this.get("numCells")-1);
+            var cellMode = appState.get("cellMode");
+            var partType = this.get("partType");
+//            this._iterCells(this.cells, function(cell){
+//                if (cell) cell.updateForScale(cellMode, partType);
+//            });
             three.render();
         },
 
-        clearCells: function(){
-            this._loopCells(this.sparseCells, function(cell){//send destroy to top level
-                if (cell) cell.destroy();
+
+
+
+
+
+        //events
+
+        _loadMaterialClass: function(){
+            var materialClass = this.get("materialClass");
+            this.set("materialType", _.keys(plist.allMaterials[materialClass])[0], {silent:true});//set to default silently
+            if (globals.materials[materialClass]) return;//already loaded
+            require([materialClass + "Materials"], function(MaterialClass){
+                globals.materials[materialClass] = MaterialClass;
             });
-            three.removeAllCells();//todo add flag in cell destroy to avoid redundancy here
-            this.cells = [[[null]]];
-            this.sparseCells = [[[null]]];
-            this.set("cellsMax", null);
-            this.set("cellsMin", null);
-            this.set("nodes", []);
-            this.set("numCells", 0);
-            if (globals.basePlane) globals.basePlane.set("zIndex", 0);
-            three.render();
         },
 
-        calculateBoundingBox: function(){
-            var scale = this._allAxesScales();
-            var min = _.clone(this.get("cellsMin"));
-            var max = _.clone(this.get("cellsMax"));
-            _.each(_.keys(scale), function(key){
-                min[key] *= scale[key];
-                max[key] *= scale[key];
-            });
-            return {min:min, max:max};
+        showCellAtIndex: function(index){
+            var latticeIndex = (new THREE.Vector3()).subVectors(index, this.get("cellsMin"));//index is probably a json object from gcode comment
+            var cell = this.cells[latticeIndex.x][latticeIndex.y][latticeIndex.z];
+            if (cell) cell.show();
+            else console.warn("placing a cell that does not exist");
         },
 
+        _navChanged: function(){
+            var currentNav = appState.get("currentNav");
+            if (!this.inCompositeMode() && this._undoCompositeEditor) this._undoCompositeEditor();
+        },
+
+
 
 
 
@@ -216,295 +179,29 @@ define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'thre
 
 
 
-
-        //cells array
-
-        checkForMatrixExpansion: function(cells, indicesMax, indicesMin){
-
-            if (!cells) {
-                console.warn("no cells specified in matrix expansion");
-                return;
-            }
-
-            if (!this.get("cellsMax") || !this.get("cellsMin")){
-                this.set("cellsMax", indicesMax);
-                this.set("cellsMin", indicesMin);
-                this._expandCellsArray(cells, (new THREE.Vector3()).subVectors(indicesMax, indicesMin), false);
-                return;
-            }
-
-            var lastMax = this.get("cellsMax");
-            var lastMin = this.get("cellsMin");
-            var newMax = this._updateCellsMax(indicesMax, lastMax);
-            var newMin = this._updateCellsMin(indicesMin, lastMin);
-            if (newMax) {
-                this._expandCellsArray(cells, (new THREE.Vector3()).subVectors(newMax, lastMax), false);
-                this.set("cellsMax", newMax);
-            }
-            if (newMin) {
-                this._expandCellsArray(cells, (new THREE.Vector3()).subVectors(lastMin, newMin), true);
-                this.set("cellsMin", newMin);
-            }
-        },
-
-        _expandCellsArray: function(cells, expansion, fromFront){
-
-            _.each(_.keys(expansion), function(key){
-                if (expansion[key] == 0) return;//no expansion on this axis
-
-                var cellsX = cells.length;
-                var cellsY = cells[0].length;
-                var cellsZ = cells[0][0].length;
-
-                if (key=="x"){
-                    for (var x=0;x<expansion[key];x++){
-                        var newLayer = [];
-                        for (var y=0;y<cellsY;y++){
-                            var newCol = [];
-                            for (var z=0;z<cellsZ;z++){
-                                newCol.push(null);
-                            }
-                            newLayer.push(newCol);
-                        }
-                        if (fromFront) cells.unshift(newLayer);
-                        else cells.push(newLayer);
-                    }
-                } else if (key=="y"){
-                    for (var x=0;x<cellsX;x++){
-                        for (var y=0;y<expansion[key];y++){
-                            var newCol = [];
-                            for (var z=0;z<cellsZ;z++){
-                                newCol.push(null);
-                            }
-                            if (fromFront) cells[x].unshift(newCol);
-                            else cells[x].push(newCol);
-                        }
-                    }
-                } else if (key=="z"){
-                    for (var x=0;x<cellsX;x++){
-                        for (var y=0;y<cellsY;y++){
-                            for (var z=0;z<expansion[key];z++){
-                                if (fromFront) cells[x][y].unshift(null);
-                                else cells[x][y].push(null);
-                            }
-                        }
-                    }
-                }
-            });
-        },
-
-        _updateCellsMin: function(newPosition, currentMin){
-            var newMin = new THREE.Vector3();
-            var hasChanged = false;
-            _.each(_.keys(newPosition), function(key){
-                if (newPosition[key]<currentMin[key]){
-                    hasChanged = true;
-                    newMin[key] = newPosition[key];
-                } else {
-                    newMin[key] = currentMin[key];
-                }
-            });
-            if (hasChanged) return newMin;
-            return false;
-        },
-
-        _updateCellsMax: function(newPosition, currentMax){
-            var newMax = new THREE.Vector3();
-            var hasChanged = false;
-            _.each(_.keys(newPosition), function(key){
-                if (newPosition[key]>currentMax[key]){
-                    hasChanged = true;
-                    newMax[key] = newPosition[key];
-                } else {
-                    newMax[key] = currentMax[key];
-                }
-            });
-            if (hasChanged) return newMax;
-            return false;
-        },
-
-
-
-
-
         //composite Cells
 
         setToCompositeMode: function(id, data){
             var self = this;
             require(['compositeEditorLattice'], function(CompositeEditorLattice){
+                self.hideCells();
                 _.extend(self, CompositeEditorLattice);
                 self._initCompositeEditor(id, data);
                 appState.set("currentNav", "navComposite");
             });
         },
 
-
-
-
-
-        //events
-
-        _updatePartType: function(){
-            this._iterCells(this.sparseCells, function(cell){
-                if (cell) cell.destroyParts();
-            });
-            this._updateForMode();
-        },
-
-        _updateForMode: function(){
-            var cellMode = appState.get("cellMode");
-            var numCells = this.get("numCells");
-            this._iterCells(this.sparseCells, function(cell){
-                if (cell) cell.setMode(cellMode, function(){
-                    if (--numCells <= 0) three.render();
-                });
-            });
-        },
-
-        _updateCellSeparation: function(){
-            var cellSep = this.get("cellSeparation");
-            globals.basePlane.updateXYSeparation(cellSep.xy);
-
-            var cellMode = appState.get("cellMode");
-            var partType = this.get("partType");
-//            this._iterCells(this.cells, function(cell){
-//                if (cell) cell.updateForScale(cellMode, partType);
-//            });
-            three.render();
-        },
-
-        _setCellVisibility: function(){
-            if (appState.get("cellsVisible")) this.showCells();
-            else this.hideCells();
-        },
-
-        //hide/show cells during stock simulation
-        hideCells: function(){
-            this._iterCells(this.cells, function(cell){
-                if (cell) cell.hide();
-            });
-            three.render();
-        },
-
-        showCells: function(){
-            var cellMode = appState.get("cellMode");
-            this._iterCells(this.cells, function(cell){
-                if (cell) cell.show(cellMode)
-            });
-            three.render();
+        inCompositeMode: function(){
+            return appState.get("currentNav") == "navComposite";
         },
 
-        showCellAtIndex: function(index){
-            var latticeIndex = (new THREE.Vector3()).subVectors(index, this.get("cellsMin"));//index is probably a json object from gcode comment
-            var cell = this.cells[latticeIndex.x][latticeIndex.y][latticeIndex.z];
-            if (cell) cell.show();
-            else console.warn("placing a cell that does not exist");
-        },
+        clearCompositeCells: function(){
 
-        _loadMaterialClass: function(){
-            var materialClass = this.get("materialClass");
-            this.set("materialType", _.keys(plist.allMaterials[materialClass])[0], {silent:true});//set to default silently
-            if (globals.materials[materialClass]) return;//already loaded
-            require([materialClass + "Materials"], function(MaterialClass){
-                globals.materials[materialClass] = MaterialClass;
-            });
         },
 
-        _navChanged: function(){
-            var currentNav = appState.get("currentNav");
-            if (currentNav != "navComposite" && this._undoCompositeEditor) this._undoCompositeEditor();
-        },
-
-
-
-
-
-        //lattice type
-
-        _updateLatticeType: function(loadingFromFile){//do not clear cells if loading from file (cells array contains important metadata)
-
-            this._setToDefaultsSilently();
-            this._setDefaultCellMode();
-            this._loadMaterialClass();
-
-            if (loadingFromFile === undefined) loadingFromFile = false;
-            if (loadingFromFile) console.warn('loading from file');
-            this.clearCells();
 
-            if (this._undo) this._undo();
-            if (globals.basePlane) globals.basePlane.destroy();
-            if (globals.highlighter) globals.highlighter.destroy();
 
-            var subclass = this._getSubclassForLatticeType();
-            var self = this;
-            require([subclass], function(subclassObject){
-
-                _.extend(self, subclassObject);
-                self._initLatticeType();
-
-                //copy over cells to new lattice type
-                var cells = self.cells;
-                self._loopCells(cells, function(cell, x, y, z){
-                    if (!cell) return;
-                    var index = _.clone(cell.index);
-                    if (cell.destroy) cell.destroy();
-                    self.makeCellForLatticeType(index, function(newCell){
-                        cells[x][y][z] = newCell;
-                    });
-                });
-                three.render();
-            });
-        },
 
-        _getSubclassForLatticeType: function(){
-            var cellType = this.get("cellType");
-            var connectionType = this.get("connectionType");
-            if (cellType == "octa"){
-                if (connectionType == "face"){
-                    return "octaFaceLattice";
-                } else if (connectionType == "edge"){
-                    return "octaEdgeLattice";
-                } else if (connectionType == "edgeRot"){
-                    return "octaRotEdgeLattice";
-                } else if (connectionType == "vertex"){
-                    return "octaVertexLattice";
-                }
-            } else if (cellType == "tetra"){
-                if (connectionType == "stacked") return "tetraStackedLattice";
-                else if (connectionType == "vertex") return "tetraVertexLattice";
-            } else if (cellType == "cube"){
-                if (connectionType == "face"){
-                    return "cubeLattice";
-                } else if (connectionType == "gik"){
-                    return "gikLattice";
-                }
-            } else if (cellType == "truncatedCube"){
-                return "truncatedCubeLattice";
-            } else if (cellType == "kelvin"){
-                return "kelvinLattice";
-            } else {
-                console.warn("unrecognized cell type " + cellType);
-            }
-            return null;
-        },
-
-        _setToDefaultsSilently: function(){
-            var newCellType = this.get("cellType");
-            var newConnectionType = this.get("connectionType");
-            if (newConnectionType == this.previous("connectionType")){
-                newConnectionType = _.keys(plist["allConnectionTypes"][newCellType])[0];
-                this.set("connectionType", newConnectionType, {silent:true});
-            }
-            var partType = _.keys(plist["allPartTypes"][newCellType][newConnectionType])[0];
-            this.set("partType", partType, {silent:true});
-            this.set("materialClass", plist.allMaterialTypes[newCellType][newConnectionType], {silent:true});
-        },
-
-        _setDefaultCellMode: function(){//if no part associated with this lattice type
-            if (!plist["allPartTypes"][this.get("cellType")][this.get("connectionType")]){
-                appState.set("cellMode", "cell");
-            }
-        },
 
 
 
@@ -512,27 +209,6 @@ define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'thre
 
         //utils
 
-        _iterCells: function(cells, callback){
-            _.each(cells, function(cellLayer){
-                _.each(cellLayer, function(cellColumn){
-                    _.each(cellColumn, function(cell){
-                        callback(cell, cellColumn, cellLayer);
-                    });
-                });
-
-            });
-        },
-
-        _loopCells: function(cells, callback){
-            for (var x=0;x<cells.length;x++){
-                for (var y=0;y<cells[0].length;y++){
-                    for (var z=0;z<cells[0][0].length;z++){
-                        callback(cells[x][y][z], x, y, z, this);
-                    }
-                }
-            }
-        },
-
         rasterCells: function(order, callback, var1, var2, var3, cells){//used for CAM raster x/y/z in any order permutation
             //order is of form 'XYZ'
             var firstLetter = order.charAt(0);
@@ -600,27 +276,13 @@ define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'thre
         _getRasterLoopIterator: function(variable){
             if (variable.neg) return -1;
             return 1;
-        },
-
-        _allAxesScales: function(){
-            var xScale = this.xScale();
-            var yScale = this.yScale();
-            var zScale = this.zScale();
-            return {x:xScale, y:yScale, z:zScale};
-        },
-
-
-
-
-
-        //save/load
+        }
+    });
 
-        toJSON: function(){//a minimal toJSON for ui stuff - no need to parse all cells
-            return _.omit(this.attributes, ["cells", "nodes"]);//omit makes a copy
-        }//todo something weird here
 
-    });
 
-    return new Lattice();
+    var lattice = new Lattice();
+    appState.setLattice(lattice);
+    return lattice;
 
 });
\ No newline at end of file
diff --git a/js/lattice/LatticeBase.js b/js/lattice/LatticeBase.js
new file mode 100644
index 0000000000000000000000000000000000000000..5c8d7986b5c16ada1a86e640f6ee9637fe592fac
--- /dev/null
+++ b/js/lattice/LatticeBase.js
@@ -0,0 +1,405 @@
+/**
+ * Created by aghassaei on 6/11/15.
+ */
+
+
+/**
+ * Created by aghassaei on 1/16/15.
+ */
+
+
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
+
+    return Backbone.Model.extend({
+
+        defaults: {
+
+            cellsMin: null,//min position of cells matrix
+            cellsMax: null,//max position of cells matrix
+            numCells: 0,
+
+            cellType: "cube",
+            connectionType: "face",
+            materialType: null,
+            materialClass: "mechanical"
+        },
+
+
+        initialize: function(){
+
+            this.cells = [[[null]]];//3D matrix containing all cells and null, dynamic size
+            this.sparseCells = [[[null]]];//3D matrix containing highest hierarchical level of cells and null
+
+            //bind events
+
+            this.listenTo(appState, "change:cellMode", this._updateForMode);
+            this.listenTo(appState, "change:cellsVisible", this._setCellVisibility);
+
+            if (this.__bindEvents) this.__bindEvents();
+            if (this.__initialize) this.__initialize();
+        },
+
+
+        _initLatticeSubclass: function(){
+            var subclass = this._getSubclassForLatticeType();
+            var self = this;
+            require([subclass], function(subclassObject){
+
+                _.extend(self, subclassObject);
+                self._initLatticeType();
+
+                //copy over cells to new lattice type
+                var cells = self.cells;
+                self._loopCells(cells, function(cell, x, y, z){
+                    if (!cell) return;
+                    var index = _.clone(cell.index);
+                    if (cell.destroy) cell.destroy();
+                    self.makeCellForLatticeType(index, function(newCell){
+                        cells[x][y][z] = newCell;
+                    });
+                });
+                three.render();
+            });
+        },
+
+        _getSubclassForLatticeType: function(){
+            var cellType = this.get("cellType");
+            var connectionType = this.get("connectionType");
+            if (cellType == "octa"){
+                if (connectionType == "face"){
+                    return "octaFaceLattice";
+                } else if (connectionType == "edge"){
+                    return "octaEdgeLattice";
+                } else if (connectionType == "edgeRot"){
+                    return "octaRotEdgeLattice";
+                } else if (connectionType == "vertex"){
+                    return "octaVertexLattice";
+                }
+            } else if (cellType == "tetra"){
+                if (connectionType == "stacked") return "tetraStackedLattice";
+                else if (connectionType == "vertex") return "tetraVertexLattice";
+            } else if (cellType == "cube"){
+                if (connectionType == "face"){
+                    return "cubeLattice";
+                } else if (connectionType == "gik"){
+                    return "gikLattice";
+                }
+            } else if (cellType == "truncatedCube"){
+                return "truncatedCubeLattice";
+            } else if (cellType == "kelvin"){
+                return "kelvinLattice";
+            } else {
+                console.warn("unrecognized cell type " + cellType);
+            }
+            return null;
+        },
+
+
+
+
+
+        //add/remove cells
+
+        addCellsInRange: function(range){//add a block of cells (extrude)
+            this.checkForMatrixExpansion(this.sparseCells, range.max, range.min);
+
+            var cellsMin = this.get("cellsMin");
+            var relativeMin = (new THREE.Vector3()).subVectors(range.min, cellsMin);
+            var relativeMax = (new THREE.Vector3()).subVectors(range.max, this.get("cellsMin"));
+
+            for (var x=relativeMin.x;x<=relativeMax.x;x++){
+                for (var y=relativeMin.y;y<=relativeMax.y;y++){
+                    for (var z=relativeMin.z;z<=relativeMax.z;z++){
+                        if (!this.sparseCells[x][y][z]) {
+                            var self = this;
+                             this.makeCellForLatticeType((new THREE.Vector3(x, y, z)).add(cellsMin), function(cell){
+                                self.sparseCells[x][y][z] = cell;
+                                self.set("numCells", self.get("numCells")+1);
+                            });
+                        } else console.warn("already a cell there");
+                    }
+                }
+            }
+            three.render();
+        },
+
+        addCellAtIndex: function(indices, noRender, noCheck){//no render no check from fill
+
+            if (!noCheck || noCheck === undefined) this.checkForMatrixExpansion(this.sparseCells, indices, indices);
+
+            var index = (new THREE.Vector3()).subVectors(indices, this.get("cellsMin") || indices);
+            if (!this.sparseCells[index.x][index.y][index.z]) {
+                var self = this;
+                if (!noRender || noRender === undefined) three.setRenderFlag();
+                this.makeCellForLatticeType(indices, function(cell){
+                    self.sparseCells[index.x][index.y][index.z] = cell;
+                    self.set("numCells", self.get("numCells")+1);
+                });
+            } else console.warn("already a cell there");
+
+        },
+
+        _indexForPosition: function(absPosition){
+            return new THREE.Vector3(
+                Math.round(absPosition.x/this.xScale()),
+                Math.round(absPosition.y/this.yScale()),
+                Math.round(absPosition.z/this.zScale()));
+        },
+
+        _positionForIndex: function(index){
+            var position = index.clone();
+            position.x = (position.x)*this.xScale();
+            position.y = (position.y)*this.yScale();
+            position.z = (position.z)*this.zScale();
+            return position;
+        },
+
+    //    removeCellAtIndex: function(indices){
+    //
+    //        var index = this._subtract(indices, this.get("cellsMin"));
+    //        var cells = this.get("cells");
+    //        if (index.x<cells.length && index.y<cells[0].length && index.z<cells[0][0].length){
+    //            this.removeCell(cells[index.x][index.y][index.z]);
+    //        }
+    //    },
+
+        removeCell: function(cell){
+            if (!cell) return;
+            var index = (new THREE.Vector3()).subVectors(cell.index, this.get("cellsMin"));
+            cell.destroy();
+            this.sparseCells[index.x][index.y][index.z] = null;
+
+            //todo shrink cells matrix if needed
+
+            this.set("numCells", this.get("numCells")-1);
+            three.render();
+        },
+
+        clearCells: function(){
+            this._loopCells(this.sparseCells, function(cell){//send destroy to top level
+                if (cell) cell.destroy();
+            });
+            three.removeAllCells();//todo add flag in cell destroy to avoid redundancy here
+            this.cells = [[[null]]];
+            this.sparseCells = [[[null]]];
+            this.set("cellsMax", null);
+            this.set("cellsMin", null);
+            this.set("nodes", []);
+            this.set("numCells", 0);
+            if (globals.basePlane) globals.basePlane.set("zIndex", 0);
+            three.render();
+        },
+
+        calculateBoundingBox: function(){
+            var scale = this._allAxesScales();
+            var min = _.clone(this.get("cellsMin"));
+            var max = _.clone(this.get("cellsMax"));
+            _.each(_.keys(scale), function(key){
+                min[key] *= scale[key];
+                max[key] *= scale[key];
+            });
+            return {min:min, max:max};
+        },
+
+
+
+
+
+
+
+
+        //cells array
+
+        checkForMatrixExpansion: function(cells, indicesMax, indicesMin){
+
+            if (!cells) {
+                console.warn("no cells specified in matrix expansion");
+                return;
+            }
+
+            if (!this.get("cellsMax") || !this.get("cellsMin")){
+                this.set("cellsMax", indicesMax);
+                this.set("cellsMin", indicesMin);
+                this._expandCellsArray(cells, (new THREE.Vector3()).subVectors(indicesMax, indicesMin), false);
+                return;
+            }
+
+            var lastMax = this.get("cellsMax");
+            var lastMin = this.get("cellsMin");
+            var newMax = this._updateCellsMax(indicesMax, lastMax);
+            var newMin = this._updateCellsMin(indicesMin, lastMin);
+            if (newMax) {
+                this._expandCellsArray(cells, (new THREE.Vector3()).subVectors(newMax, lastMax), false);
+                this.set("cellsMax", newMax);
+            }
+            if (newMin) {
+                this._expandCellsArray(cells, (new THREE.Vector3()).subVectors(lastMin, newMin), true);
+                this.set("cellsMin", newMin);
+            }
+        },
+
+        _expandCellsArray: function(cells, expansion, fromFront){
+
+            _.each(_.keys(expansion), function(key){
+                if (expansion[key] == 0) return;//no expansion on this axis
+
+                var cellsX = cells.length;
+                var cellsY = cells[0].length;
+                var cellsZ = cells[0][0].length;
+
+                if (key=="x"){
+                    for (var x=0;x<expansion[key];x++){
+                        var newLayer = [];
+                        for (var y=0;y<cellsY;y++){
+                            var newCol = [];
+                            for (var z=0;z<cellsZ;z++){
+                                newCol.push(null);
+                            }
+                            newLayer.push(newCol);
+                        }
+                        if (fromFront) cells.unshift(newLayer);
+                        else cells.push(newLayer);
+                    }
+                } else if (key=="y"){
+                    for (var x=0;x<cellsX;x++){
+                        for (var y=0;y<expansion[key];y++){
+                            var newCol = [];
+                            for (var z=0;z<cellsZ;z++){
+                                newCol.push(null);
+                            }
+                            if (fromFront) cells[x].unshift(newCol);
+                            else cells[x].push(newCol);
+                        }
+                    }
+                } else if (key=="z"){
+                    for (var x=0;x<cellsX;x++){
+                        for (var y=0;y<cellsY;y++){
+                            for (var z=0;z<expansion[key];z++){
+                                if (fromFront) cells[x][y].unshift(null);
+                                else cells[x][y].push(null);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+
+        _updateCellsMin: function(newPosition, currentMin){
+            var newMin = new THREE.Vector3();
+            var hasChanged = false;
+            _.each(_.keys(newPosition), function(key){
+                if (newPosition[key]<currentMin[key]){
+                    hasChanged = true;
+                    newMin[key] = newPosition[key];
+                } else {
+                    newMin[key] = currentMin[key];
+                }
+            });
+            if (hasChanged) return newMin;
+            return false;
+        },
+
+        _updateCellsMax: function(newPosition, currentMax){
+            var newMax = new THREE.Vector3();
+            var hasChanged = false;
+            _.each(_.keys(newPosition), function(key){
+                if (newPosition[key]>currentMax[key]){
+                    hasChanged = true;
+                    newMax[key] = newPosition[key];
+                } else {
+                    newMax[key] = currentMax[key];
+                }
+            });
+            if (hasChanged) return newMax;
+            return false;
+        },
+
+
+
+        //events
+
+        _updatePartType: function(){
+            this._iterCells(this.sparseCells, function(cell){
+                if (cell) cell.destroyParts();
+            });
+            this._updateForMode();
+        },
+
+        _updateForMode: function(){
+            var cellMode = appState.get("cellMode");
+            var numCells = this.get("numCells");
+            this._iterCells(this.sparseCells, function(cell){
+                if (cell) cell.setMode(cellMode, function(){
+                    if (--numCells <= 0) three.render();
+                });
+            });
+        },
+
+        _setCellVisibility: function(){
+            if (appState.get("cellsVisible")) this.showCells();
+            else this.hideCells();
+        },
+
+        hideCells: function(){
+            this._iterCells(this.sparseCells, function(cell){
+                if (cell) cell.hide();
+            });
+            three.render();
+        },
+
+        showCells: function(){
+            var cellMode = appState.get("cellMode");
+            this._iterCells(this.sparseCells, function(cell){
+                if (cell) cell.show(cellMode)
+            });
+            three.render();
+        },
+
+
+
+
+
+
+        //utils
+
+        _iterCells: function(cells, callback){
+            _.each(cells, function(cellLayer){
+                _.each(cellLayer, function(cellColumn){
+                    _.each(cellColumn, function(cell){
+                        callback(cell, cellColumn, cellLayer);
+                    });
+                });
+
+            });
+        },
+
+        _loopCells: function(cells, callback){
+            for (var x=0;x<cells.length;x++){
+                for (var y=0;y<cells[0].length;y++){
+                    for (var z=0;z<cells[0][0].length;z++){
+                        callback(cells[x][y][z], x, y, z, this);
+                    }
+                }
+            }
+        },
+
+        _allAxesScales: function(){
+            var xScale = this.xScale();
+            var yScale = this.yScale();
+            var zScale = this.zScale();
+            return {x:xScale, y:yScale, z:zScale};
+        },
+
+
+
+
+
+        //save/load
+
+        toJSON: function(){//a minimal toJSON for ui stuff - no need to parse all cells
+            return _.omit(this.attributes, ["cells", "nodes"]);//omit makes a copy
+        }//todo something weird here
+
+    });
+});
\ No newline at end of file
diff --git a/js/lattice/OctaEdgeLattice.js b/js/lattice/OctaEdgeLattice.js
index 1c80e60e33a7d5feffe507b0545ccff636afee19..6d95089e8caf3cd98d817828a743d0e19fcad53f 100644
--- a/js/lattice/OctaEdgeLattice.js
+++ b/js/lattice/OctaEdgeLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 5/26/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var OctaEdgeLattice = {
 
diff --git a/js/lattice/OctaFaceLattice.js b/js/lattice/OctaFaceLattice.js
index 5cc6b5d56ff2ea1d5784fc9b06c9a48546a01e8d..e4b4250fb270fd63adfa1c2df481b98cf9e05d82 100644
--- a/js/lattice/OctaFaceLattice.js
+++ b/js/lattice/OctaFaceLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 5/26/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var OctaFaceLattice = {
 
diff --git a/js/lattice/OctaRotEdgeLattice.js b/js/lattice/OctaRotEdgeLattice.js
index 6f94d781b265f973a105942270ade7c5069735c2..b189274e507f876606459e497b3b1e1d15920e9c 100644
--- a/js/lattice/OctaRotEdgeLattice.js
+++ b/js/lattice/OctaRotEdgeLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 5/26/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var OctaRotEdgeLattice = {
 
diff --git a/js/lattice/OctaVertexLattice.js b/js/lattice/OctaVertexLattice.js
index 7173dece647f911852b0e121d11bf85457db1c50..0e2af7538c08fc9c404da51c47c8a7db0fb902a4 100644
--- a/js/lattice/OctaVertexLattice.js
+++ b/js/lattice/OctaVertexLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 5/26/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var OctaVertexLattice = {
 
diff --git a/js/lattice/TetraStackedLattice.js b/js/lattice/TetraStackedLattice.js
index 9cdbc6d00dd408f2106ecc9582884ffdd82d5041..5be5601b4b2d3f0fe624dd971a356174e2c6c194 100644
--- a/js/lattice/TetraStackedLattice.js
+++ b/js/lattice/TetraStackedLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 6/4/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var TetraStackedLattice = {
 
diff --git a/js/lattice/TetraVertexLattice.js b/js/lattice/TetraVertexLattice.js
index 09537f95e54fb6db43871e22b8d08a22230ab95c..47dd4fd0374bed389dc5c61710bc95364d766ead 100644
--- a/js/lattice/TetraVertexLattice.js
+++ b/js/lattice/TetraVertexLattice.js
@@ -2,8 +2,8 @@
  * Created by aghassaei on 6/4/15.
  */
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var TetraVertexLattice = {
 
diff --git a/js/lattice/TruncatedCubeLattice.js b/js/lattice/TruncatedCubeLattice.js
index 2c8eaf4dae8867dd06774ea1071e39f8496d62eb..8df8ebabf1a91a03dd8981676d5bce35bff18aa5 100644
--- a/js/lattice/TruncatedCubeLattice.js
+++ b/js/lattice/TruncatedCubeLattice.js
@@ -3,8 +3,8 @@
  */
 
 
-define(['underscore', 'backbone', 'appState', 'lattice', 'globals', 'plist', 'three', 'threeModel'],
-    function(_, Backbone, appState, lattice, globals, plist, THREE, three){
+define(['underscore', 'backbone', 'appState', 'globals', 'plist', 'three', 'threeModel'],
+    function(_, Backbone, appState, globals, plist, THREE, three){
 
     var TruncatedCubeLattice = {
 
diff --git a/js/main.js b/js/main.js
index d930f73df6405fd0d92be6df438a90e51164592c..f26f8c4b0bb3adc275d41327a6e95368d22fc2cd 100644
--- a/js/main.js
+++ b/js/main.js
@@ -27,6 +27,7 @@ require.config({
         fileSaver: 'models/FileSaver',
 
         //lattice
+        latticeBase: 'lattice/LatticeBase',
         lattice: 'lattice/Lattice',
         cubeLattice: 'lattice/CubeLattice',
         gikLattice: 'lattice/GIKLattice',