diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 648a50e..8c77431 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -144,10 +144,11 @@ set(SOURCES
     modifiers/modifiertest.cpp
     assistants/guidelineline.cpp
     assistants/guidelineellipse.cpp
-    assistants/assistantvanishingpoint.cpp
+    assistants/assistantellipse.cpp
+    assistants/assistantfisheye.cpp
     assistants/assistantline.cpp
     assistants/assistantperspective.cpp
-    assistants/assistantellipse.cpp
+    assistants/assistantvanishingpoint.cpp
     assistants/replicatoraffine.cpp
     assistants/replicatorgrid.cpp
     assistants/replicatorjitter.cpp
diff --git a/toonz/sources/tnztools/assistants/assistantellipse.cpp b/toonz/sources/tnztools/assistants/assistantellipse.cpp
index 260737f..19a2857 100644
--- a/toonz/sources/tnztools/assistants/assistantellipse.cpp
+++ b/toonz/sources/tnztools/assistants/assistantellipse.cpp
@@ -323,7 +323,7 @@ void TAssistantEllipse::drawConcentricGrid(
   TAffine screenMatrixInv = screenMatrix.inv();
   
   double pixelSize = sqrt(tglGetPixelSize2());
-  double minStep = 20.0*pixelSize;
+  double minStep = 10.0*pixelSize;
   TAffine ellipseMatrixInv = ellipseMatrix.inv();
 
   // calculate bounds
diff --git a/toonz/sources/tnztools/assistants/assistantfisheye.cpp b/toonz/sources/tnztools/assistants/assistantfisheye.cpp
new file mode 100644
index 0000000..cdde562
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/assistantfisheye.cpp
@@ -0,0 +1,242 @@
+
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/assistants/guidelineline.h>
+#include <tools/assistants/guidelineellipse.h>
+
+#include "assistantellipse.h"
+
+// TnzCore includes
+#include <tgl.h>
+
+// std includes
+#include <limits>
+
+
+//*****************************************************************************************
+//    TAssistantFisheye implementation
+//*****************************************************************************************
+
+class TAssistantFisheye final : public TAssistant {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantEllipse)
+public:
+  const TStringId m_idCircle;
+  const TStringId m_idGrid;
+
+protected:
+  TAssistantPoint &m_center;
+  TAssistantPoint &m_a;
+  TAssistantPoint &m_b;
+  TAssistantPoint &m_grid0;
+  TAssistantPoint &m_grid1;
+
+public:
+  TAssistantFisheye(TMetaObject &object):
+    TAssistant(object),
+    m_idCircle("circle"),
+    m_idGrid("grid"),
+    m_center( addPoint("center", TAssistantPoint::CircleCross) ),
+    m_a( addPoint("a", TAssistantPoint::CircleFill, TPointD(200,   0)) ),
+    m_b( addPoint("b", TAssistantPoint::Circle,     TPointD(  0, 200)) ),
+    m_grid0( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD( -25,  25)) ),
+    m_grid1( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD(  25, -25)) )
+  {
+    addProperty( new TBoolProperty(m_idCircle.str(), getCircle()) );
+    addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
+  }
+
+  static QString getLocalName()
+    { return tr("Fish Eye"); }
+
+  void updateTranslation() const override {
+    TAssistant::updateTranslation();
+    setTranslation(m_idCircle, tr("Circle"));
+    setTranslation(m_idGrid, tr("Grid"));
+  }
+
+  inline bool getCircle() const
+    { return data()[m_idCircle].getBool(); }
+  inline bool getGrid() const
+    { return data()[m_idGrid].getBool(); }
+
+  void onDataChanged(const TVariant &value) override {
+    TAssistant::onDataChanged(value);
+    m_grid0.visible = m_grid1.visible = getGrid();
+    if (getCircle() == m_b.visible) {
+      m_b.visible = !getCircle();
+      if (!m_b.visible)
+        fixBAndGrid(m_center.position, m_a.position, m_b.position);
+    }
+  }
+
+private:
+  void fixBAndGrid(
+    TPointD prevCenter,
+    TPointD prevA,
+    TPointD prevB )
+  {
+    const TPointD &center = m_center.position;
+    TPointD da0 = prevA - prevCenter;
+    TPointD da1 = m_a.position - center;
+    double la0 = norm2(da0);
+    double la1 = norm2(da1);
+    if (!(la0 > TConsts::epsilon) || !(la1 > TConsts::epsilon))
+      return;
+    
+    TPointD db = m_b.position - center;
+    TPointD dp0 = TPointD(-da0.y, da0.x);
+    TPointD dp1 = TPointD(-da1.y, da1.x);
+    if (getCircle()) {
+      m_b.position = center + (db*dp0 < 0 ? -dp1 : dp1);
+    } else {
+      m_b.position = db*dp0/la0*dp1 + center;
+    }
+
+    TPointD db0 = prevB - prevCenter;
+    TPointD db1 = m_b.position - center;
+    double lb0 = norm2(db0);
+    double lb1 = norm2(db1);
+    if (!(lb0 > TConsts::epsilon) || !(lb1 > TConsts::epsilon))
+      return;
+
+    TPointD dg0 = m_grid0.position - center;
+    TPointD dg1 = m_grid1.position - center;
+    m_grid0.position = dg0*da0/la0*da1 + dg0*db0/lb0*db1 + center;
+    m_grid1.position = dg1*da0/la0*da1 + dg1*db0/lb0*db1 + center;
+  }
+
+
+public:
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    TPointD prevCenter = m_center.position;
+    TPointD prevA = m_a.position;
+    TPointD prevB = m_b.position;
+    point.position = position;
+    if (&point == &m_center) {
+      TPointD d = m_center.position - prevCenter;
+      m_a.position += d;
+      m_b.position += d;
+      m_grid0.position += d;
+      m_grid1.position += d;
+    } else
+    if (&point == &m_a || &point == &m_b) {
+      fixBAndGrid(prevCenter, prevA, prevB);
+    }
+  }
+
+  TAffine calcEllipseMatrix() const {
+    TPointD da = m_a.position - m_center.position;
+    TPointD db = m_b.position - m_center.position;
+    double r1 = norm(da);
+    if (r1 <= TConsts::epsilon) return TAffine::zero();
+    double r2 = fabs( (rotate90(da)*db)*(1.0/r1) );
+    if (r2 <= TConsts::epsilon) return TAffine::zero();
+    return TAffine::translation(m_center.position)
+         * TAffine::rotation(atan(da))
+         * TAffine::scale(r1, r2);
+  }
+
+  void getGuidelines(
+    const TPointD &position,
+    const TAffine &toTool,
+    TGuidelineList &outGuidelines ) const override
+  {
+    TAffine matrix = calcEllipseMatrix();
+    if (matrix.isZero()) return;
+
+    matrix = toTool*matrix;
+    TAffine matrixInv = matrix.inv();
+
+    TPointD p = matrixInv*position;
+    double l = norm(p);
+    
+    if (l > TConsts::epsilon) {
+      // radius
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineLine(
+          getEnabled(),
+          getMagnetism(),
+          matrix*TPointD(0, 0),
+          matrix*(p/l) )));
+    }
+    
+    if (!(l < 1.0 - TConsts::epsilon)) {
+      // bound ellipse
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineEllipse(
+          getEnabled(),
+          getMagnetism(),
+          matrix )));
+    } else {
+      // ellipse scaled by X
+      double kx = fabs(p.x/sqrt(1.0 - p.y*p.y));
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineEllipse(
+          getEnabled(),
+          getMagnetism(),
+          matrix * TAffine::scale(kx, 1.0) )));
+
+      // ellipse scaled by Y
+      double ky = fabs(p.y/sqrt(1.0 - p.x*p.x));
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineEllipse(
+          getEnabled(),
+          getMagnetism(),
+          matrix * TAffine::scale(1.0, ky) )));
+    }
+  }
+
+public:
+  void draw(TToolViewer *viewer, bool enabled) const override {
+    TAffine ellipseMatrix = calcEllipseMatrix();
+    if (ellipseMatrix.isZero()) return;
+
+    // common data about viewport
+    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+    TAffine4 modelview, projection;
+    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+    TAffine matrix = (projection*modelview).get2d();
+    TAffine matrixInv = matrix.inv();
+
+    double pixelSize = sqrt(tglGetPixelSize2());
+    const double crossSize = 0.1;
+    double alpha = getDrawingAlpha(enabled);
+    
+    drawSegment( ellipseMatrix*TPointD(-crossSize, 0.0),
+                 ellipseMatrix*TPointD( crossSize, 0.0), pixelSize, alpha);
+    drawSegment( ellipseMatrix*TPointD(0.0, -crossSize),
+                 ellipseMatrix*TPointD(0.0,  crossSize), pixelSize, alpha);
+    TAssistantEllipse::drawEllipse(ellipseMatrix, matrixInv, pixelSize, alpha);
+
+    if (getGrid()) {
+      TAssistantEllipse::drawParallelGrid(
+        ellipseMatrix,
+        m_grid0.position,
+        m_grid1.position,
+        true,
+        false,
+        getDrawingGridAlpha() );
+      
+      std::swap(ellipseMatrix.a11, ellipseMatrix.a12);
+      std::swap(ellipseMatrix.a21, ellipseMatrix.a22);
+      
+      TAssistantEllipse::drawParallelGrid(
+        ellipseMatrix,
+        m_grid0.position,
+        m_grid1.position,
+        true,
+        false,
+        getDrawingGridAlpha() );
+    }
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TAssistantFisheye> assistantFisheye("assistantFisheye");
+