diff --git a/GUI/ba3d/ba3d/def.cpp b/GUI/ba3d/ba3d/def.cpp
index d4ebc458df7c60c4ad72f319b6c3def42a878461..af3c92aad9b55794797347057a2b5111f31017fa 100644
--- a/GUI/ba3d/ba3d/def.cpp
+++ b/GUI/ba3d/ba3d/def.cpp
@@ -34,7 +34,7 @@ xyz xyz::normalized() const {
   return QVector3D(*this).normalized();
 }
 
-xyz xyz::interpolateTo(flt rat, rc to) const {
+xyz xyz::interpolateTo(rc to, flt rat) const {
   return *this * (1 - rat) + to * rat;
 }
 
diff --git a/GUI/ba3d/ba3d/def.h b/GUI/ba3d/ba3d/def.h
index f3b8a7b25f5f8f9fbef39ec32065348a14ba657d..90c6f384cc08911b0d8b735a106b29ae46c24629 100644
--- a/GUI/ba3d/ba3d/def.h
+++ b/GUI/ba3d/ba3d/def.h
@@ -97,7 +97,7 @@ struct xyz {
   flt length()     const;
   xyz normalized() const;
 
-  xyz interpolateTo(flt, rc) const;
+  xyz interpolateTo(rc, flt) const;
 
   static xyz const _0, _1, _x, _y, _z;
 };
diff --git a/GUI/ba3d/ba3d/model/model.cpp b/GUI/ba3d/ba3d/model/model.cpp
index 90d36fbe444ba9afef906592536f154403f43af1..b0f763db286b060bb0486e81bdea98891892a223 100644
--- a/GUI/ba3d/ba3d/model/model.cpp
+++ b/GUI/ba3d/ba3d/model/model.cpp
@@ -20,13 +20,67 @@ Model::~Model() {
   }
 }
 
-void Model::clear(bool alsoBlend) {
+void Model::clearOpaque() {
   while (!objects.isEmpty())
     delete objects.first();
-  if (alsoBlend)
-    while (!objectsBlend.isEmpty())
-      delete objectsBlend.first();
-  emit updated();
+  emit updated(false);
+}
+
+void Model::clearBlend() {
+  while (!objectsBlend.isEmpty())
+    delete objectsBlend.first();
+  emit updated(false);
+}
+
+particle::Particle* Model::newParticle(particle::kind k, flp R) {
+  using namespace particle;
+
+  flp D = 2*R;
+
+  switch (k) {
+  case kind::None:
+    return nullptr;
+  case kind::FullSphere:
+    return new FullSphere(R);
+  case kind::FullSpheroid:
+    return new FullSpheroid(R/2, D);
+  case kind::Cylinder:
+    return new Cylinder(R, D);
+  case kind::TruncatedSphere:
+    return new TruncatedSphere(R, D/3);
+  case kind::TruncatedSpheroid:
+    return new TruncatedSpheroid(R, 2*R, 1.5);
+  case kind::Cone:
+    return new Cone(R, D, 1.3f);
+  case kind::Icosahedron:
+    return new Icosahedron(R * geometry::icosahedronL2R);
+  case kind::Dodecahedron:
+    return new Dodecahedron(R * geometry::dodecahedronL2R);
+  case kind::TruncatedCube:
+    return new TruncatedCube(D, D/3);
+  case kind::Prism6:
+    return new Prism6(R, D);
+  case kind::Cone6:
+    return new Cone6(R, D, 1.3f);
+  case kind::Pyramid:
+    return new Pyramid(D, D, 1.3f);
+  case kind::Cuboctahedron:
+    return new Cuboctahedron(D, R*3/2, 2.f/3, 2);
+  case kind::Prism3:
+    return new Prism3(R, D);
+  case kind::Tetrahedron:
+    return new Tetrahedron(R, D, 1.3f);
+  case kind::EllipsoidalCylinder:
+    return new EllipsoidalCylinder(R, R/2, D);
+  case kind::Box:
+    return new Box(D, D, D);
+  case kind::HemiEllipsoid:
+    return new HemiEllipsoid(R, R, D);
+  case kind::AnisoPyramid:
+    return new AnisoPyramid(R, D, D, 1.3f);
+  }
+
+  return nullptr;
 }
 
 void Model::add(Object* o) {
diff --git a/GUI/ba3d/ba3d/model/model.h b/GUI/ba3d/ba3d/model/model.h
index 5da09e9c012d8ed6b1e29d4b5648ddd53c608263..4fe158639bce3e762fc92408351b7a9c3fcbef3a 100644
--- a/GUI/ba3d/ba3d/model/model.h
+++ b/GUI/ba3d/ba3d/model/model.h
@@ -5,6 +5,7 @@
 
 #include <ba3d/view/camera.h>
 #include "object.h"
+#include "particles.h"
 #include <QVector>
 #include <QHash>
 
@@ -23,7 +24,10 @@ public:
   Model();
   virtual ~Model();
 
-  void clear(bool alsoBlend);
+  void clearOpaque();
+  void clearBlend();
+
+  static particle::Particle* newParticle(particle::kind k, ba3d::flp R);
 
   void add(Object*);        // add an opaque object, the model takes ownership
   void addBlend(Object*);   // add a transparent object, the model takes ownership
@@ -34,7 +38,7 @@ public:
   virtual void cameraUpdated(Camera const&) {}
 
 signals:
-  void updated();
+  void updated(bool withEye);
 
 protected:
   Camera::pos_t defCamPos;    // default camera params
diff --git a/GUI/ba3d/ba3d/model/particles.cpp b/GUI/ba3d/ba3d/model/particles.cpp
index 16e9c9602d9c226686a0c192bfa55d7c8fb5cfe0..1a5916499cb70975885e6d479e8f76e67aaccdbc 100644
--- a/GUI/ba3d/ba3d/model/particles.cpp
+++ b/GUI/ba3d/ba3d/model/particles.cpp
@@ -28,8 +28,13 @@ void Particle::set() {
   transform(xyz::_0, xyz::_0);
 }
 
-void Particle::transform(xyz rotate, xyz translate) {
-  base::transform(turn, scale, rotate, offset + translate);
+void Particle::transform(xyz rotate_, xyz translate_) {
+  base::transform(turn, scale,
+                  (rotate = rotate_), offset + (translate = translate_));
+}
+
+void Particle::fancy(xyz rotate, flt r) {
+  base::transform(turn, scale*r, rotate, offset + translate);
 }
 
 //------------------------------------------------------------------------------
diff --git a/GUI/ba3d/ba3d/model/particles.h b/GUI/ba3d/ba3d/model/particles.h
index ff226750980b10569df7c9b7c9b4c918f4d758d4..64fbd685994f14991d37b95a4947c823712a60f1 100644
--- a/GUI/ba3d/ba3d/model/particles.h
+++ b/GUI/ba3d/ba3d/model/particles.h
@@ -25,7 +25,8 @@ protected:
   Particle(geometry::key);
   xyz turn,   // turn before scale
       scale,  // geometries are of 1-size (box 1x1x1, sphere D=1), need scaling
-      offset; // geometries centered around origin; particles stand on z=0 plane
+      offset, // geometries centered around origin; particles stand on z=0 plane
+      rotate, translate;  // remembered
 
   void set();
 
@@ -34,6 +35,7 @@ public:
                     lastKind  = kind::AnisoPyramid;
 
   void transform(xyz rotate, xyz translate);
+  void fancy(xyz rotate, flt r);
 };
 
 //------------------------------------------------------------------------------
diff --git a/GUI/ba3d/ba3d/view/camera.cpp b/GUI/ba3d/ba3d/view/camera.cpp
index 3f9e562503f49a49301530d09473c0cc300000f7..78afbe07e25ba8b3d77992af5d838e06874a7864 100644
--- a/GUI/ba3d/ba3d/view/camera.cpp
+++ b/GUI/ba3d/ba3d/view/camera.cpp
@@ -1,7 +1,6 @@
 // GPL3; https://github.com/jburle/ba3d
 
 #include "camera.h"
-#include <QQuaternion>
 
 namespace ba3d {
 //------------------------------------------------------------------------------
@@ -16,15 +15,17 @@ Camera::Camera()
 Camera::pos_t::pos_t() : eye(), ctr(), up() {
 }
 
-Camera::pos_t::pos_t(xyz::rc eye_, xyz::rc ctr_, xyz::rc up_)
-  : eye(eye_), ctr(ctr_), up(up_) {
+Camera::pos_t::pos_t(xyz::rc eye_, xyz::rc ctr_, xyz::rc up_,
+                     QQuaternion const& rot_)
+  : eye(eye_), ctr(ctr_), up(up_), rot(rot_) {
 }
 
-Camera::pos_t Camera::pos_t::interpolateTo(flt r, rc to) const {
+Camera::pos_t Camera::pos_t::interpolateTo(rc to, flt r) const {
   return pos_t(
-    eye.interpolateTo(r, to.eye),
-    ctr.interpolateTo(r, to.ctr),
-    up.interpolateTo(r, to.up)
+    eye.interpolateTo(to.eye, r),
+    ctr.interpolateTo(to.ctr, r),
+    up.interpolateTo(to.up, r),
+    QQuaternion::slerp(rot, to.rot, r)
   );
 }
 
@@ -38,7 +39,7 @@ void Camera::set() {
   matModel.setToIdentity();
   matModel.lookAt((pos.eye-pos.ctr)*zoom + pos.ctr, pos.ctr, pos.up);
 
-  QQuaternion rt(rot * addRot);
+  QQuaternion rt(pos.rot * addRot);
   matModel.translate(+pos.ctr);
   matModel.rotate(rt);
   matModel.translate(-pos.ctr);
@@ -65,7 +66,7 @@ void Camera::zoomBy(flt zoom_) {
 
 void Camera::endTransform(bool keep) {
   if (keep) {
-    rot     = (rot * addRot).normalized();
+    pos.rot = (pos.rot * addRot).normalized();
     pos.eye = pos.eye * zoom; // TODO limit
   }
 
diff --git a/GUI/ba3d/ba3d/view/camera.h b/GUI/ba3d/ba3d/view/camera.h
index a9f9756c0a9eea32d3d2b632fa69260d28c9c240..485a889ea50289e48849cf217815ec61cb3b5ad7 100644
--- a/GUI/ba3d/ba3d/view/camera.h
+++ b/GUI/ba3d/ba3d/view/camera.h
@@ -6,6 +6,7 @@
 #include "../def.h"
 #include <QColor>
 #include <QMatrix4x4>
+#include <QQuaternion>
 
 namespace ba3d {
 //------------------------------------------------------------------------------
@@ -24,11 +25,13 @@ public:
     typedef pos_t const& rc;
 
     pos_t();
-    pos_t(xyz::rc eye, xyz::rc ctr, xyz::rc up);
+    pos_t(xyz::rc eye, xyz::rc ctr, xyz::rc up,
+          QQuaternion const& = QQuaternion());
 
     xyz eye, ctr, up;
+    QQuaternion rot;
 
-    pos_t interpolateTo(flt, rc) const;
+    pos_t interpolateTo(rc, flt) const;
   };
 
   void lookAt(pos_t::rc);
@@ -58,7 +61,7 @@ private:
   xyz lightPos, lightPosRotated;
 
   // transformation
-  QQuaternion rot, addRot;        // rotation, additional rotation
+  QQuaternion addRot;        // rotation, additional rotation
   QMatrix4x4  matModel, matProj;
 };
 
diff --git a/GUI/ba3d/ba3d/view/canvas.cpp b/GUI/ba3d/ba3d/view/canvas.cpp
index 689354bb08f5031d42f1b3e64f327ed439c4839c..49d81cf5f4e8263f88b5b0b09ec4dfd641d24b97 100644
--- a/GUI/ba3d/ba3d/view/canvas.cpp
+++ b/GUI/ba3d/ba3d/view/canvas.cpp
@@ -48,8 +48,11 @@ void Canvas::setModel(Model* m) {
 
   disconnect(modelUpdated);
   model = m;
-  modelUpdated = connect(model, &Model::updated, [this]() {
-    setCamera();
+  modelUpdated = connect(model, &Model::updated, [this](bool withEye) {
+    if (withEye)
+      setCamera();
+    else
+      update();
   });
 
   setCamera();
@@ -94,7 +97,7 @@ void Canvas::paintGL() {
     // opaque objects
     model->draw(*this);
 
-    // transparebnt objects
+    // transparent objects
     glEnable(GL_BLEND); glDepthMask(false);
     glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     model->drawBlend(*this);
diff --git a/GUI/ba3d/demo/demo_model.cpp b/GUI/ba3d/demo/demo_model.cpp
index 2f2697153e8f0282281226efa99af2b9f13d5ca3..57b043fa785bba7857bf55062e78c290a6c3869c 100644
--- a/GUI/ba3d/demo/demo_model.cpp
+++ b/GUI/ba3d/demo/demo_model.cpp
@@ -1,7 +1,6 @@
 // GPL3; https://github.com/jburle/ba3d
 
 #include "demo_model.h"
-#include "lattice.h"
 #include <ba3d/model/layer.h>
 #include <QApplication>
 #include <thread>
@@ -13,30 +12,54 @@ using namespace ba3d;
 
 DemoModel::DemoModel() {
   setCameraSide();
-
-  addSubstrate();
-  addLayer();
-
-  calc(0);
 }
 
 void DemoModel::switchBack() {
   switch (back) {
-  case hasNONE:
-    addSubstrate(); back = hasSUBSTRATE;
+  case backNONE:
+    addSubstrate(); back = backSUBSTRATE;
     break;
-  case hasSUBSTRATE:
-    addLayer(); back = hasLAYER;
+  case backSUBSTRATE:
+    addLayer(); back = backLAYER;
     break;
-  case hasLAYER:
-    super::clear(true); back = hasNONE;
+  case backLAYER:
+    super::clearBlend(); back = backNONE;
     break;
   }
 
-  snooze();
+  snooze(false);
 }
 
-void DemoModel::calc(float sigma) {
+void DemoModel::switchFront() {
+  switch (front) {
+  case frontNONE:
+    one(); front = frontONE;
+    break;
+  case frontONE:
+    super::clearOpaque(); kind = ba3d::particle::kind::None;
+    front = frontSQUARELOW; szSample = 400; square(0);
+    break;
+  case frontSQUARELOW:
+    super::clearOpaque(); ps.clear(); kind = ba3d::particle::kind::None;
+    front = frontSQUAREHIGH; szSample = 800; square(0);
+    break;
+  case frontSQUAREHIGH:
+    super::clearOpaque(); ps.clear(); front = frontNONE;
+    break;
+  }
+
+  snooze(false);
+}
+
+void DemoModel::switchOne() {
+  if (frontONE == front)
+    one();
+}
+
+void DemoModel::square(float sigma) {
+  if (frontSQUARELOW != front && frontSQUAREHIGH != front)
+    return;
+
   uint n = qFloor(szSample / spacing / 2 - 1);
   auto mesh = squareLattice(n, sigma);
   for (auto& m: mesh)
@@ -44,9 +67,9 @@ void DemoModel::calc(float sigma) {
 
   if (ps.empty()) { // first time - init
     ps.resize(mesh.count());
-    flt R = 6;
     for (auto& p: ps)
       add((p = new particle::TruncatedSphere(R, R)));
+    activeMesh.clear();
   }
 
   EXPECT (ps.count() == mesh.count())
@@ -55,7 +78,27 @@ void DemoModel::calc(float sigma) {
     activeMesh = mesh;
     for (uint i=0; i < ps.count(); ++i)
       ps.at(i)->transform(xyz::_0, activeMesh.at(i));
+
+    uint const steps = 20;
+    for_int (s, steps + 1) {
+      for (uint i=0; i < ps.count(); ++i)
+        ps.at(i)->fancy(xyz::_0, flt(s) / steps);
+      snooze(false);
+    }
   } else {
+    // back
+    auto home = squareLattice(n, 0), from = activeMesh;
+    for (auto& m: home)
+      m = m  * spacing + xyz(0, 0, -20);
+    uint const steps = 30;
+    for_int (s, steps + 1) {
+      for (uint i=0; i < ps.count(); ++i)
+        ps.at(i)->transform(xyz::_0, from.at(i).interpolateTo(home.at(i), flt(s)/steps));
+      snooze(false);
+    }
+
+    activeMesh = home;
+
     float const step = .1; bool go = true;
     while (go) {
       go = false;
@@ -63,60 +106,91 @@ void DemoModel::calc(float sigma) {
         auto& p = ps.at(i);
         auto& newPos = mesh.at(i);
         auto& pos = activeMesh[i];
-        if (step > (pos - newPos).length())
-          pos = newPos;
-        else {
-          pos = pos + (newPos - pos).normalized() * step;
-          go = true;
+        auto np = pos + (newPos - pos).normalized() * step;
+        // neigbours
+        auto hasSpace = [&](int xi, int yi) -> bool {
+          int n = activeMesh.n;
+          if (xi < -n || n <= xi || yi < -n || n <= yi)
+            return true;
+          auto p = activeMesh.at(activeMesh.index(xi,yi));
+          return (np-p).length() > 2*R;
+        };
+
+        int xi = activeMesh.ix(i), yi = activeMesh.iy(i);
+        if (hasSpace(xi-1, yi-1) && hasSpace(xi-1, yi-0) && hasSpace(xi-1, yi+1)
+         && hasSpace(xi+1, yi-1) && hasSpace(xi+1, yi-0) && hasSpace(xi+1, yi+1)
+         && hasSpace(xi-0, yi-1) && hasSpace(xi-0, yi+1)
+         && (newPos - np).length() > step*1.001) {
+            go = true;
+            pos = np;
+            p->transform(xyz::_0, pos);
         }
-        p->transform(xyz::_0, pos);
       }
 
-      snooze();
+      snooze(false);
     }
   }
 
-  snooze();
+  snooze(false);
 }
 
-void DemoModel::flip() {
-  if (!activeMesh.empty()) {
-    for (int deg=0; deg <=360; deg += 5) {
-      for (uint i=0; i < ps.count(); ++i) {
-        auto& p = ps.at(i);
-        auto& pos = activeMesh[i];
-        p->transform(xyz(0,deg,0), pos);
-      }
+void DemoModel::one() {
+  using eKind = ba3d::particle::kind;
+  if (kind != eKind::None)
+    oneOut();
+  if (kind == eKind::AnisoPyramid)
+    kind = eKind::None;
+  else
+    oneIn((kind = eKind(int(kind) + 1)));
+}
 
-      snooze();
-    }
+void DemoModel::oneOut() {
+  uint const steps = 40;
+  for_i (steps + 1) {
+    flt a = 360.f / steps * i / 3;
+    p->fancy(xyz(a, a, a), flt(steps-i) / steps);
+    snooze(false);
   }
-
-  snooze();
+  rem(p);
 }
 
-static flt const hgtLayer = 20, hgtSubstrate = 35;
+void DemoModel::oneIn(particle::kind kind) {
+  add((p = newParticle(kind, R)));
+  p->color = Qt::cyan;
+  p->transform(xyz::_0, xyz(0, 0, -hgtLayer));
+
+  uint const steps = 140;
+  for_i (steps + 1) {
+    flt a = 360.f / steps * i;
+    p->fancy(xyz(a*2, a, a), flt(i) / steps);
+    snooze(false);
+  }
+}
 
 void DemoModel::setCameraTop(bool animate) {
-  setCamera(Camera::pos_t(xyz(10, 10, szSample), xyz(0, 0, -20), xyz::_y), animate);
+  setCamera(Camera::pos_t(xyz(0, 0, szSample), xyz(0, 0, -20), xyz::_y), animate);
 }
 
 void DemoModel::setCameraSide(bool animate) {
   setCamera(Camera::pos_t(xyz(-10, -szSample*1.1, 2*hgtLayer), xyz(0, 0, -20), xyz::_z), animate);
 }
 
+void DemoModel::setCameraOne(bool animate) {
+  setCamera(Camera::pos_t(xyz(0, 0, spacing), xyz(0, 0, -20), xyz::_y), animate);
+}
+
 void DemoModel::setCamera(Camera::pos_t::rc to, bool animate) {
   if (animate) {
     auto from = camPos;
 
     uint const frames = 45;
-    for_i (frames) {
-      defCamPos = from.interpolateTo(flt(i) / frames, to);
-      snooze();
+    for_i (frames + 1) {
+      defCamPos = from.interpolateTo(to, flt(i) / frames);
+      snooze(true);
     }
   } else {
     defCamPos = to;
-    snooze();
+    snooze(true);
   }
 }
 
@@ -140,8 +214,8 @@ void DemoModel::addLayer(dr z, QColor clr) {
   addBlend(l);
 }
 
-void DemoModel::snooze() {
-  emit updated();
+void DemoModel::snooze(bool withEye) {
+  emit updated(withEye);
   qApp->processEvents();
   std::this_thread::sleep_for(std::chrono::milliseconds(10));
 }
diff --git a/GUI/ba3d/demo/demo_model.h b/GUI/ba3d/demo/demo_model.h
index 0fc6ceebff7b0e3b7daf36d918cf214956788081..8428af82596950d80e8b8e7420b8ec4b12d6d129 100644
--- a/GUI/ba3d/demo/demo_model.h
+++ b/GUI/ba3d/demo/demo_model.h
@@ -5,6 +5,7 @@
 
 #include <ba3d/model/model.h>
 #include <ba3d/model/particles.h>
+#include "lattice.h"
 #include <QAtomicInteger>
 
 //------------------------------------------------------------------------------
@@ -20,12 +21,18 @@ public:
 
   DemoModel();
   void switchBack();
+  void switchFront();
+  void switchOne();
 
-  void calc(float sigma);
-  void flip();
+  void square(float sigma);
+  void one();
+  void oneOut();
+  void oneIn(ba3d::particle::kind);
 
   void setCameraTop(bool animate = false);
   void setCameraSide(bool animate = false);
+  void setCameraOne(bool animate = false);
+
   void setCamera(Camera::pos_t::rc, bool animate = false);
 
   void cameraUpdated(Camera const&);
@@ -34,10 +41,13 @@ private:
   void addSubstrate();
   void addLayer();
 
-  enum {hasNONE, hasSUBSTRATE, hasLAYER } back = hasNONE;
+  enum {backNONE,  backSUBSTRATE, backLAYER }                   back  = backNONE;
+  enum {frontNONE, frontONE, frontSQUARELOW, frontSQUAREHIGH }  front = frontNONE;
 
   flt szSample = 400;
-  flt spacing  = 20; // of particles
+  flt const spacing  = 20; // of particles
+  flt const R = 6;
+  flt const hgtLayer = 20, hgtSubstrate = 35;
 
   Camera::pos_t camPos;
 
@@ -45,10 +55,12 @@ private:
   void addLayer(ba3d::dr, QColor);
 
   QVector<Particle*> ps;
-  QVector<xyz> activeMesh;
+  Particle* p; ba3d::particle::kind kind = ba3d::particle::kind::None;
+
+  Lattice activeMesh;
 
 private:
-  void snooze();
+  void snooze(bool withEye);
 };
 
 //------------------------------------------------------------------------------
diff --git a/GUI/ba3d/demo/lattice.cpp b/GUI/ba3d/demo/lattice.cpp
index 48db0fcd1d4d049397d1a72b5b30fcbddb0a49ab..e9c7c106c24fc69af83af5bcd49ebe1d2f0b4489 100644
--- a/GUI/ba3d/demo/lattice.cpp
+++ b/GUI/ba3d/demo/lattice.cpp
@@ -7,7 +7,30 @@
 
 //------------------------------------------------------------------------------
 
-QVector<ba3d::xyz> squareLattice(uint n, float sigma) {
+Lattice::Lattice() : n(0) {}
+
+Lattice::Lattice(uint n_, uint nn) : super (nn), n(n_) {}
+
+uint Lattice::index(int ix, int iy) {
+  int nx = n, ny = n;
+  EXPECT (-nx <= ix && ix <= +nx)
+  EXPECT (-ny <= iy && iy <= +ny)
+  uint i = (2*nx + 1) * (iy + ny) + (ix + nx);
+  ENSURE (i < count())
+  return i;
+}
+
+int Lattice::ix(uint i) {
+  int nr = 2*n + 1;
+  return i % nr - n;
+}
+
+int Lattice::iy(uint i) {
+  int nr = 2*n + 1;
+  return i / nr - n;
+}
+
+Lattice squareLattice(uint n, float sigma) {
   using flt = ba3d::flt;
   using xyz = ba3d::xyz;
 
@@ -44,28 +67,19 @@ QVector<ba3d::xyz> squareLattice(uint n, float sigma) {
 
   uint nn = (2*n + 1) * (2*n + 1); // total number
 
-  auto mesh = QVector<xyz>(nn);
-
-  auto index = [&](int ix, int iy) -> uint {
-    int nx = n, ny = n;
-    EXPECT (-nx <= ix && ix <= +nx)
-    EXPECT (-ny <= iy && iy <= +ny)
-    uint i = (2*nx + 1) * (iy + ny) + (ix + nx);
-    ENSURE (i < nn)
-    return i;
-  };
+  auto mesh = Lattice(n, nn);
 
   auto get = [&](int ix, int iy) -> xyz::rc {
-    return mesh.at(index(ix, iy));
+    return mesh.at(mesh.index(ix, iy));
   };
 
   auto isMade = [&](int ix, int iy) -> bool {
-    return xyz::_0 != mesh.at(index(ix, iy));
+    return xyz::_0 != mesh.at(mesh.index(ix, iy));
   };
 
   auto put = [&](int ix, int iy) {
     if (!isMade(ix, iy))
-      mesh[index(ix, iy)] = placeXY(ix, iy);
+      mesh[mesh.index(ix, iy)] = placeXY(ix, iy);
   };
 
   auto growBy1Quadrant = [&](uint n, int mx, int my) {
diff --git a/GUI/ba3d/demo/lattice.h b/GUI/ba3d/demo/lattice.h
index 674b079d281008dfa4f7a558e10006b9555026d7..9fa06dcce97f220723eef19fae8ee47468388730 100644
--- a/GUI/ba3d/demo/lattice.h
+++ b/GUI/ba3d/demo/lattice.h
@@ -8,7 +8,20 @@
 
 //------------------------------------------------------------------------------
 
-QVector<ba3d::xyz> squareLattice(uint n, float sigma); // n half-size
+class Lattice : public QVector<ba3d::xyz> {
+public:
+  using super = QVector<ba3d::xyz>;
+
+  Lattice();
+  Lattice(uint n, uint nn);
+
+  uint index(int ix, int iy);
+  int ix(uint);
+  int iy(uint);
+  uint n;
+};
+
+Lattice squareLattice(uint n, float sigma); // n half-size
 
 //------------------------------------------------------------------------------
 #endif
diff --git a/GUI/ba3d/demo/mainwin.cpp b/GUI/ba3d/demo/mainwin.cpp
index 6d56cb10e68fbdd5294cbd5ea3d088afceab5476..810561b6fadd8f0b78717a80738a9bb30d6f52cb 100644
--- a/GUI/ba3d/demo/mainwin.cpp
+++ b/GUI/ba3d/demo/mainwin.cpp
@@ -50,21 +50,23 @@ void MainWin::createLayout() {
 
   auto btnSide  = new QPushButton("side");
   auto btnTop   = new QPushButton("top");
-  auto btn0     = new QPushButton("0");
-  auto btn1     = new QPushButton("1");
+  auto btnOne   = new QPushButton("one");
+  auto btn0     = new QPushButton("=");
+  auto btn1     = new QPushButton("1|n");
+  auto btnP     = new QPushButton("p");
   auto sldSigma = new QSlider(Qt::Horizontal);
-  auto btnCalc  = new QPushButton("Calc");
-  auto btnFlip  = new QPushButton("Flip");
+  auto btnSq    = new QPushButton("><");
 
   hb->addWidget(btnSide);
   hb->addWidget(btnTop);
+  hb->addWidget(btnOne);
   hb->addStretch();
   hb->addWidget(btn0);
   hb->addWidget(btn1);
+  hb->addWidget(btnP);
   hb->addStretch();
   hb->addWidget(sldSigma);
-  hb->addWidget(btnCalc);
-  hb->addWidget(btnFlip);
+  hb->addWidget(btnSq);
 
   sldSigma->setRange(0,30);
   sldSigma->setSingleStep(5);
@@ -79,24 +81,24 @@ void MainWin::createLayout() {
     model()->setCameraTop(true);
   });
 
+  connect(btnOne, &QPushButton::clicked, [this]() {
+    model()->setCameraOne(true);
+  });
+
   connect(btn0, &QPushButton::clicked, [this]() {
     model()->switchBack();
   });
 
-  auto calc = [this, sldSigma]() {
-    model()->calc(sldSigma->value() / 100.f);
-  };
-
-  auto flip = [this]() {
-    model()->flip();
-  };
+  connect(btn1, &QPushButton::clicked, [this]() {
+    model()->switchFront();
+  });
 
-  connect(btnCalc, &QPushButton::clicked, [calc]() {
-    calc();
+  connect(btnP, &QPushButton::clicked, [this]() {
+    model()->switchOne();
   });
 
-  connect(btnFlip, &QPushButton::clicked, [flip]() {
-    flip();
+  connect(btnSq, &QPushButton::clicked, [this, sldSigma]() {
+    model()->square(sldSigma->value() / 100.f);
   });
 }
 
diff --git a/GUI/ba3d/showcase/modelLayers.cpp b/GUI/ba3d/showcase/modelLayers.cpp
index 4b6d45cd687fab5d8dc19ec9c7223498e7f2fb33..13b34898cb7e9d3f4be3cbca0809095fb63b504e 100644
--- a/GUI/ba3d/showcase/modelLayers.cpp
+++ b/GUI/ba3d/showcase/modelLayers.cpp
@@ -46,7 +46,7 @@ void ModelLayers::showKind(particle::kind k) {
         p->transform(xyz::_0, xyz((i-4)*sz/10, (j-4)*sz/10, z[zi]+.001f));
       }
 
-  emit updated();
+  emit updated(false);
 }
 
 //------------------------------------------------------------------------------
diff --git a/GUI/ba3d/showcase/modelShowcase.cpp b/GUI/ba3d/showcase/modelShowcase.cpp
index 11d74771299bcbb06339ca315b23d0772046c6e0..65ee1946236e837e27485fd8fc39b5e969ae27de 100644
--- a/GUI/ba3d/showcase/modelShowcase.cpp
+++ b/GUI/ba3d/showcase/modelShowcase.cpp
@@ -33,57 +33,7 @@ void ModelShowcase::showKind(kind k) {
 
   add((p = newParticle(k, R)));
 
-  emit updated();
-}
-
-particle::Particle* ModelShowcase::newParticle(kind k, flp R) {
-  using namespace particle;
-
-  flp D = 2*R;
-
-  switch (k) {
-  case kind::None:
-    return nullptr;
-  case kind::FullSphere:
-    return new FullSphere(R);
-  case kind::FullSpheroid:
-    return new FullSpheroid(R/2, D);
-  case kind::Cylinder:
-    return new Cylinder(R, D);
-  case kind::TruncatedSphere:
-    return new TruncatedSphere(R, D/3);
-  case kind::TruncatedSpheroid:
-    return new TruncatedSpheroid(R, 2*R, 1.5);
-  case kind::Cone:
-    return new Cone(R, D, 1.3f);
-  case kind::Icosahedron:
-    return new Icosahedron(R * geometry::icosahedronL2R);
-  case kind::Dodecahedron:
-    return new Dodecahedron(R * geometry::dodecahedronL2R);
-  case kind::TruncatedCube:
-    return new TruncatedCube(D, D/3);
-  case kind::Prism6:
-    return new Prism6(R, D);
-  case kind::Cone6:
-    return new Cone6(R, D, 1.3f);
-  case kind::Pyramid:
-    return new Pyramid(D, D, 1.3f);
-  case kind::Cuboctahedron:
-    return new Cuboctahedron(D, R*3/2, 2.f/3, 2);
-  case kind::Prism3:
-    return new Prism3(R, D);
-  case kind::Tetrahedron:
-    return new Tetrahedron(R, D, 1.3f);
-  case kind::EllipsoidalCylinder:
-    return new EllipsoidalCylinder(R, R/2, D);
-  case kind::Box:
-    return new Box(D, D, D);
-  case kind::HemiEllipsoid:
-    return new HemiEllipsoid(R, R, D);
-  case kind::AnisoPyramid:
-    return new AnisoPyramid(R, D, D, 1.3f);
-  }
-  return nullptr;
+  emit updated(false);
 }
 
 //------------------------------------------------------------------------------
diff --git a/GUI/ba3d/showcase/modelShowcase.h b/GUI/ba3d/showcase/modelShowcase.h
index 347a0c4dccbbdca8e856750e8856fc108a46e3e7..48e3bf2bdf569bab429f6320f963b94b1f2ededa 100644
--- a/GUI/ba3d/showcase/modelShowcase.h
+++ b/GUI/ba3d/showcase/modelShowcase.h
@@ -16,8 +16,6 @@ public:
   ModelShowcase();
   void showKind(kind);
 
-  static Particle* newParticle(kind k, ba3d::flp R);
-
 private:
   Particle *p;
 };