diff --git a/toonz/sources/include/tmetaimage.h b/toonz/sources/include/tmetaimage.h
index 5726d1a..9d360c7 100644
--- a/toonz/sources/include/tmetaimage.h
+++ b/toonz/sources/include/tmetaimage.h
@@ -181,8 +181,10 @@ protected:
   virtual void onFixData() { }
 
 public:
-  void setDefaults()
-    { onSetDefaults(); }
+  void setDefaults() {
+    { LockEvents lock(*this); onSetDefaults(); }
+    data().touch();
+  }
   void dataChanged(const TVariant &value)
     { if (m_locks == 0) onDataChanged(value); }
   void fixData()
diff --git a/toonz/sources/include/tools/assistant.h b/toonz/sources/include/tools/assistant.h
index 5be247d..f79377a 100644
--- a/toonz/sources/include/tools/assistant.h
+++ b/toonz/sources/include/tools/assistant.h
@@ -42,6 +42,7 @@ class TPropertyGroup;
 class TTool;
 class TToolViewer;
 class TAssistant;
+class TAssistantBase;
 class TAssistantPoint;
 class TGuideline;
 
@@ -134,7 +135,7 @@ public:
   TAssistantType(const TStringId &name):
     TMetaObjectType(name) { }
   TMetaObjectHandler* createHandler(TMetaObject &obj) const override;
-  virtual TAssistant* createAssistant(TMetaObject &obj) const
+  virtual TAssistantBase* createAssistant(TMetaObject &obj) const
     { return 0; }
 };
 
@@ -182,7 +183,7 @@ public:
     if (!alias5.empty()) registerAlias(TStringId(alias5));
   }
 
-  TAssistant* createAssistant(TMetaObject &obj) const override
+  TAssistantBase* createAssistant(TMetaObject &obj) const override
     { return new Type(obj); }
   QString getLocalName() const override {
     QString localName = Type::getLocalName();
@@ -192,11 +193,19 @@ public:
 
 
 //*****************************************************************************************
-//    TAssistant definition
+//    TAssistantBase definition
 //*****************************************************************************************
 
-class DVAPI TAssistant : public TMetaObjectHandler {
-  Q_DECLARE_TR_FUNCTIONS(TAssistant)
+class DVAPI TAssistantBase : public TMetaObjectHandler {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantBase)
+public:
+  enum {
+    DRAW_ERROR = 1,
+  };
+  
+  static unsigned int drawFlags;
+  static const double lineWidthScale;
+  
 protected:
   const TStringId m_idEnabled;
   const TStringId m_idPoints;
@@ -211,7 +220,7 @@ protected:
   mutable TPropertyGroup m_properties;
 
 public:
-  TAssistant(TMetaObject &object);
+  TAssistantBase(TMetaObject &object);
 
   static QString getLocalName()
     { return QString(); }
@@ -237,11 +246,6 @@ public:
   void setEnabled(bool x)
     { if (getEnabled() != x) data()[m_idEnabled].setBool(x); }
 
-  double getMagnetism() const
-    { return data()[m_idMagnetism].getDouble(); }
-  void setMagnetism(double x)
-    { if (getMagnetism() != x) data()[m_idMagnetism].setDouble(x); }
-
   inline void selectPoint(const TStringId &name) const
     { setPointSelection(name, true); }
   inline void deselectPoint(const TStringId &name) const
@@ -249,7 +253,7 @@ public:
   inline void selectAll() const
     { setAllPointsSelection(true); }
   inline void deselectAll() const
-  { setAllPointsSelection(false); }
+    { setAllPointsSelection(false); }
 
   TPropertyGroup& getProperties() const
     { return m_properties; }
@@ -300,7 +304,7 @@ protected:
   //! try to move point
   virtual void onMovePoint(TAssistantPoint &point, const TPointD &position);
   //! save object data to variant
-  virtual void onFixData();
+  void onFixData() override;
   //! load all properties from variant
   virtual void updateProperties();
   //! load single property from variant
@@ -315,21 +319,52 @@ protected:
   void drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha) const;
   void drawDot(const TPointD &p, double alpha) const;
   void drawPoint(const TAssistantPoint &point, double pixelSize) const;
+  void drawIndex(const TPointD &p, int index, bool selected, double pixelSize) const;
 
   inline void drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize) const
     { drawSegment(p0, p1, pixelSize, getDrawingAlpha()); }
   inline void drawDot(const TPointD &p) const
     { drawDot(p, getDrawingAlpha()); }
 
+  TIntProperty* createSpinProperty(const TStringId &id, int def, int min, int max, bool hasMax = true);
+  inline TIntProperty* createSpinProperty(const TStringId &id, int def, int min)
+    { return createSpinProperty(id, def, min, 0, false); }
+  
   void addProperty(TProperty *p);
   void setTranslation(const TStringId &name, const QString &localName) const;
 
 public:
   virtual void updateTranslation() const;
-  virtual void getGuidelines(const TPointD &position, const TAffine &toTool, TGuidelineList &outGuidelines) const;
   virtual void draw(TToolViewer *viewer, bool enabled) const;
   void draw(TToolViewer *viewer) const { draw(viewer, true); }
   virtual void drawEdit(TToolViewer *viewer) const;
+  virtual void drawEdit(TToolViewer *viewer, int index) const;
+};
+
+
+//*****************************************************************************************
+//    TAssistant definition
+//*****************************************************************************************
+
+class DVAPI TAssistant : public TAssistantBase {
+  Q_DECLARE_TR_FUNCTIONS(TAssistant)
+protected:
+  const TStringId m_idMagnetism;
+
+  void onSetDefaults() override;
+  void onFixData() override;
+
+public:
+  TAssistant(TMetaObject &object);
+
+  double getMagnetism() const
+    { return data()[m_idMagnetism].getDouble(); }
+  void setMagnetism(double x)
+    { if (getMagnetism() != x) data()[m_idMagnetism].setDouble(x); }
+
+public:
+  void updateTranslation() const override;
+  virtual void getGuidelines(const TPointD &position, const TAffine &toTool, TGuidelineList &outGuidelines) const;
 
   static bool calcPerspectiveStep(
     double minStep,
diff --git a/toonz/sources/include/tools/inputmanager.h b/toonz/sources/include/tools/inputmanager.h
index 3dfcc51..011b05a 100644
--- a/toonz/sources/include/tools/inputmanager.h
+++ b/toonz/sources/include/tools/inputmanager.h
@@ -222,6 +222,10 @@ public:
   void insertModifier(int index, const TInputModifierP &modifier);
   void addModifier(const TInputModifierP &modifier)
     { insertModifier(getModifiersCount(), modifier); }
+  void addModifiers(const TInputModifier::List &modifiers) {
+    for(TInputModifier::List::const_iterator i = modifiers.begin(); i != modifiers.end(); ++i)
+      addModifier(*i);
+  }
   void removeModifier(int index);
   void removeModifier(const TInputModifierP &modifier)
     { removeModifier(findModifier(modifier)); }
diff --git a/toonz/sources/include/tools/modifiers/modifierclone.h b/toonz/sources/include/tools/modifiers/modifierclone.h
new file mode 100644
index 0000000..214e70f
--- /dev/null
+++ b/toonz/sources/include/tools/modifiers/modifierclone.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#ifndef MODIFIERCLONE_INCLUDED
+#define MODIFIERCLONE_INCLUDED
+
+// TnzTools includes
+#include <tools/inputmanager.h>
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierClone definition
+//*****************************************************************************************
+
+class DVAPI TModifierClone : public TInputModifier {
+public:
+  class DVAPI Handler : public TMultiTrackHandler {
+  public:
+    TTrackP original;
+    inline explicit Handler(const TTrackP &original = TTrackP()):
+      original(original) { }
+  };
+
+  class DVAPI Interpolator : public TTrackInterpolator {
+  public:
+    const TTrackTransform transform;
+    inline Interpolator(TTrack &track, const TTrackTransform &transform):
+      TTrackInterpolator(track), transform(transform) { }
+    TTrackPoint interpolateFromOriginal(double originalIndex);
+    TTrackPoint interpolate(double index) override;
+  };
+
+
+public:
+  bool keepOriginals;
+  TTrackTransformList transforms;
+
+  TModifierClone(bool keepOriginals = true);
+
+  void modifyTrack(
+    const TTrack &track,
+    TTrackList &outTracks ) override;
+};
+
+#endif
diff --git a/toonz/sources/include/tools/modifiers/modifierjitter.h b/toonz/sources/include/tools/modifiers/modifierjitter.h
new file mode 100644
index 0000000..2f048e4
--- /dev/null
+++ b/toonz/sources/include/tools/modifiers/modifierjitter.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#ifndef MODIFIERJITTER_INCLUDED
+#define MODIFIERJITTER_INCLUDED
+
+// TnzTools includes
+#include <tools/inputmanager.h>
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierJitter definition
+//*****************************************************************************************
+
+class DVAPI TModifierJitter : public TInputModifier {
+public:
+  typedef TSubTrackHandler Handler;
+
+  class DVAPI Interpolator : public TTrackInterpolator {
+  public:
+    const unsigned int seedX;
+    const unsigned int seedY;
+    const double frequency;
+    const double amplitude;
+    Interpolator(TTrack &track, double period, double amplitude);
+    TTrackPoint interpolateFromOriginal(double originalIndex);
+    TTrackPoint interpolate(double index) override;
+  };
+
+public:
+  double period;
+  double amplitude;
+  int skipFirst;
+  
+  TModifierJitter(
+    double period = 30,
+    double amplitude = 10,
+    int skipFirst = 0 );
+
+  void modifyTrack(
+    const TTrack &track,
+    TTrackList &outTracks ) override;
+  
+  void modifyTracks(
+    const TTrackList &tracks,
+    TTrackList &outTracks ) override;
+  
+  static double func(unsigned int seed, double x);
+};
+
+#endif
diff --git a/toonz/sources/include/tools/replicator.h b/toonz/sources/include/tools/replicator.h
new file mode 100644
index 0000000..0556802
--- /dev/null
+++ b/toonz/sources/include/tools/replicator.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#ifndef REPLICATOR_INCLUDED
+#define REPLICATOR_INCLUDED
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/inputmanager.h>
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+
+//*****************************************************************************************
+//    TReplicator definition
+//*****************************************************************************************
+
+class DVAPI TReplicator : public TAssistantBase {
+  Q_DECLARE_TR_FUNCTIONS(TReplicator)
+public:
+  typedef std::vector<TPointD> PointList;
+  
+  static const int multiplierSoftLimit;
+  static const int multiplierLimit;
+  
+  TReplicator(TMetaObject &object);
+
+  virtual int getMultipler() const;
+  virtual void getPoints(const TAffine &toTool, PointList &points) const;
+  virtual void getModifiers(const TAffine &toTool, TInputModifier::List &outModifiers) const;
+  
+  static void transformPoints(const TAffine &aff, PointList &points, int count);
+  static void drawReplicatorPoints(const TPointD *points, int count);
+  
+  //! return summary multiplier, or 0 is no replicators found
+  static int scanReplicators(
+    TTool *tool,
+    PointList *inOutPoints,
+    TInputModifier::List *outModifiers,
+    bool draw,
+    bool enabledOnly,
+    bool markEnabled,
+    bool drawPoints,
+    TImage *skipImage );
+  
+protected:
+  TIntProperty* createCountProperty(const TStringId &id, int def = 1, int min = 1, int max = 0);
+};
+
+
+#endif
diff --git a/toonz/sources/include/tools/tool.h b/toonz/sources/include/tools/tool.h
index 6520c56..f6ba618 100644
--- a/toonz/sources/include/tools/tool.h
+++ b/toonz/sources/include/tools/tool.h
@@ -301,14 +301,19 @@ public:
   };
 
   enum ToolHints  //!  Misc flags related with tool
-  { HintNone              = 0,
-    HintAssistants        = 1 << 0, //!< Draw asistants when tool active
-    HintGuidelines        = 1 << 1, //!< Draw asistant guidelines
-    HintAssistantsEnabled = 1 << 2, //!< Tool will use assisnats
+  { HintNone                 = 0,
+    HintAssistants           = 1 << 0, //!< Draw asistants when tool active
+    HintAssistantsGuidelines = 1 << 1, //!< Draw asistant guidelines
+    HintAssistantsEnabled    = 1 << 2, //!< Mark active assistants
+    HintReplicators          = 1 << 3, //!< Draw replicators
+    HintReplicatorsPoints    = 1 << 4, //!< Draw replicated points
+    HintReplicatorsEnabled   = 1 << 5, //!< Mark active replicators
     
     HintAssistantsAll     = HintAssistants
-                          | HintGuidelines
+                          | HintAssistantsGuidelines
                           | HintAssistantsEnabled,
+    HintReplicatorsAll    = HintReplicators
+                          | HintReplicatorsEnabled,
   };
 
 public:
diff --git a/toonz/sources/include/tools/track.h b/toonz/sources/include/tools/track.h
index a8177b3..2363db2 100644
--- a/toonz/sources/include/tools/track.h
+++ b/toonz/sources/include/tools/track.h
@@ -36,6 +36,7 @@
 class TTrack;
 class TTrackPoint;
 class TTrackTangent;
+class TTrackTransform;
 class TTrackHandler;
 class TSubTrackHandler;
 class TMultiTrackHandler;
@@ -49,6 +50,7 @@ typedef TSmartPointerT<TTrackInterpolator> TTrackInterpolatorP;
 
 typedef std::vector<TTrackPoint> TTrackPointList;
 typedef std::vector<TTrackTangent> TTrackTangentList;
+typedef std::vector<TTrackTransform> TTrackTransformList;
 typedef std::vector<TTrackP> TTrackList;
 
 //===================================================================
@@ -129,6 +131,65 @@ public:
 //    TTrackHandler definition
 //*****************************************************************************************
 
+class DVAPI TTrackTransform {
+public:
+  TAffine transform;
+  TAffine tiltTransform;
+  double pressureScale;
+  double pressureOffset;
+
+  inline TTrackTransform(
+    const TAffine &transform,
+    const TAffine &tiltTransform,
+    double pressureScale = 1,
+    double pressureOffset = 0
+  ):
+    transform(transform),
+    tiltTransform(tiltTransform),
+    pressureScale(pressureScale),
+    pressureOffset(pressureOffset) { }
+
+  inline explicit TTrackTransform(
+    const TAffine &transform,
+    double pressureScale = 1,
+    double pressureOffset = 0
+  ):
+    transform(transform),
+    tiltTransform(makeTiltTransform(transform)),
+    pressureScale(pressureScale),
+    pressureOffset(pressureOffset) { }
+
+  inline explicit TTrackTransform(
+    double pressureScale = 1,
+    double pressureOffset = 0
+  ):
+    pressureScale(pressureScale),
+    pressureOffset(pressureOffset) { }
+  
+  inline TTrackPoint apply(TTrackPoint p) const {
+    p.position = transform * p.position;
+    
+    TPointD t = tiltTransform * p.tilt;
+    p.tilt.x = t.x > -1 ? (t.x < 1 ? t.x : 1) : -1;
+    p.tilt.y = t.y > -1 ? (t.y < 1 ? t.y : 1) : -1;
+    
+    double pr = p.pressure*pressureScale + pressureOffset;
+    p.pressure = pr > 0 ? (pr < 1 ? pr : 1) : 0;
+    
+    return p;
+  }
+
+  inline void recalcTiltTransform()
+    { tiltTransform = makeTiltTransform(transform); }
+
+  static TAffine makeTiltTransform(const TAffine &a);
+};
+
+
+//*****************************************************************************************
+//    TTrackHandler definition
+//*****************************************************************************************
+
 class DVAPI TTrackHandler : public TSmartObject { };
 
 
diff --git a/toonz/sources/include/toonzqt/intfield.h b/toonz/sources/include/toonzqt/intfield.h
index 61d14b2..f18125f 100644
--- a/toonz/sources/include/toonzqt/intfield.h
+++ b/toonz/sources/include/toonzqt/intfield.h
@@ -20,6 +20,7 @@
 
 // forward declaration
 class QSlider;
+class QPushButton;
 class QIntValidator;
 
 //=============================================================================
@@ -166,12 +167,17 @@ class DVAPI IntField : public QWidget {
   RollerField *m_roller;
   IntLineEdit *m_lineEdit;
   QSlider *m_slider;
+  QPushButton *m_inc;
+  QPushButton *m_dec;
+
   bool m_isMaxRangeLimited;
   bool m_isLinearSlider;
 
 public:
-  IntField(QWidget *parent = 0, bool isMaxRangeLimited = true,
-           bool isRollerHide = true);
+  IntField(QWidget *parent = 0,
+           bool isMaxRangeLimited = true,
+           bool isRollerHide = true,
+           bool isSpinnerHide = true );
   ~IntField() {}
 
   /*! Set to \b minValue and \b maxValue slider and text field range.
@@ -204,6 +210,10 @@ public:
   void enableRoller(bool enable);
   bool rollerIsEnabled();
 
+  /*! If \b enable is false set spinner disable and hide it. */
+  void enableSpinner(bool enable);
+  bool spinnerIsEnabled();
+
   void setLineEditBackgroundColor(QColor color);
 
 protected:
@@ -220,6 +230,9 @@ protected slots:
   void onSliderChanged(int value);
   void onSliderReleased() { emit valueChanged(false); }
 
+  void onIncClicked();
+  void onDecClicked();
+
   /*! Set slider and roller value to current value in text field.
   \n	This protected slot is called when text editing is finished.
   \n	If slider value is different from text field value emit signal
diff --git a/toonz/sources/include/tproperty.h b/toonz/sources/include/tproperty.h
index f7c2ea5..b9c52e0 100644
--- a/toonz/sources/include/tproperty.h
+++ b/toonz/sources/include/tproperty.h
@@ -122,7 +122,8 @@ public:
       , m_range(minValue, maxValue)
       , m_value(minValue)
       , m_isMaxRangeLimited(isMaxRangeLimited)
-      , m_isLinearSlider(true) {
+      , m_isLinearSlider(true)
+      , m_isSpinner(false) {
     setValue(value);
   }
 
@@ -151,11 +152,16 @@ public:
   void setNonLinearSlider() { m_isLinearSlider = false; }
   bool isLinearSlider() { return m_isLinearSlider; }
 
+  //! has meaning for int properties only
+  void setSpinner() { m_isSpinner = true; }
+  bool isSpinner() { return m_isSpinner; }
+  
 private:
   Range m_range;
   T m_value;
   bool m_isMaxRangeLimited;
   bool m_isLinearSlider;
+  bool m_isSpinner;
 };
 
 //---------------------------------------------------------
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 9ca30f7..1518eee 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -49,7 +49,10 @@ set(HEADERS
     ../include/tools/track.h
     ../include/tools/inputmanager.h
     ../include/tools/assistant.h
+    ../include/tools/replicator.h
     ../include/tools/modifiers/modifierassistants.h
+    ../include/tools/modifiers/modifierclone.h
+    ../include/tools/modifiers/modifierjitter.h
     ../include/tools/modifiers/modifierline.h
     ../include/tools/modifiers/modifiersegmentation.h
     ../include/tools/modifiers/modifiersimplify.h
@@ -126,7 +129,10 @@ set(SOURCES
     track.cpp
     inputmanager.cpp
     assistant.cpp
+    replicator.cpp
     modifiers/modifierassistants.cpp
+    modifiers/modifierclone.cpp
+    modifiers/modifierjitter.cpp
     modifiers/modifierline.cpp
     modifiers/modifiersegmentation.cpp
     modifiers/modifiersimplify.cpp
@@ -138,6 +144,11 @@ set(SOURCES
     assistants/assistantvanishingpoint.cpp
     assistants/assistantline.cpp
     assistants/assistantellipse.cpp
+    assistants/replicatoraffine.cpp
+    assistants/replicatorgrid.cpp
+    assistants/replicatorjitter.cpp
+    assistants/replicatormirror.cpp
+    assistants/replicatorstar.cpp
     editassistantstool.cpp
 )
 
diff --git a/toonz/sources/tnztools/assistant.cpp b/toonz/sources/tnztools/assistant.cpp
index d43d4c8..a342f5e 100644
--- a/toonz/sources/tnztools/assistant.cpp
+++ b/toonz/sources/tnztools/assistant.cpp
@@ -20,11 +20,15 @@
 
 
 #ifdef MACOSX
-const double line_width_scale = 1.5;
+const double TAssistantBase::lineWidthScale = 1.5;
 #else
-const double line_width_scale = 1.0;
+const double TAssistantBase::lineWidthScale = 1.0;
 #endif
 
+
+unsigned int TAssistantBase::drawFlags = 0;
+
+
 //************************************************************************
 //    TGuideline implementation
 //************************************************************************
@@ -47,11 +51,11 @@ TGuideline::drawSegment(
 
   glPushAttrib(GL_ALL_ATTRIB_BITS);
   tglEnableBlending();
-  tglEnableLineSmooth(true, 1.0 * line_width_scale);
+  tglEnableLineSmooth(true, 1.0 * TAssistant::lineWidthScale);
   TPointD d = p1 - p0;
   double k = norm2(d);
   if (k > TConsts::epsilon*TConsts::epsilon) {
-    k = 0.5*pixelSize*line_width_scale/sqrt(k);
+    k = 0.5*pixelSize*TAssistant::lineWidthScale/sqrt(k);
     d = TPointD(-k*d.y, k*d.x);
     glColor4dv(colorWhite);
     tglDrawSegment(p0 - d, p1 - d);
@@ -145,26 +149,24 @@ TAssistantType::createHandler(TMetaObject &obj) const
 
 
 //************************************************************************
-//    TAssistant implementation
+//    TAssistantBase implementation
 //************************************************************************
 
-TAssistant::TAssistant(TMetaObject &object):
+TAssistantBase::TAssistantBase(TMetaObject &object):
   TMetaObjectHandler(object),
   m_idEnabled("enabled"),
   m_idPoints("points"),
   m_idX("x"),
   m_idY("y"),
-  m_idMagnetism("magnetism"),
   m_basePoint()
 {
   addProperty( new TBoolProperty(m_idEnabled.str(), getEnabled()) );
-  addProperty( new TDoubleProperty(m_idMagnetism.str(), 0.0, 1.0, getMagnetism()) );
 }
 
 //---------------------------------------------------------------------------------------------------
 
 TAssistantPoint&
-TAssistant::addPoint(
+TAssistantBase::addPoint(
   const TStringId &name,
   TAssistantPoint::Type type,
   const TPointD &defPosition,
@@ -185,7 +187,7 @@ TAssistant::addPoint(
 //---------------------------------------------------------------------------------------------------
 
 TAssistantPoint&
-TAssistant::addPoint(
+TAssistantBase::addPoint(
   const TStringId &name,
   TAssistantPoint::Type type,
   const TPointD &defPosition,
@@ -195,35 +197,43 @@ TAssistant::addPoint(
 //---------------------------------------------------------------------------------------------------
 
 const TAssistantPoint&
-TAssistant::getBasePoint() const
+TAssistantBase::getBasePoint() const
   { assert(m_basePoint); return *m_basePoint; }
 
 //---------------------------------------------------------------------------------------------------
 
+TIntProperty*
+TAssistantBase::createSpinProperty(const TStringId &id, int def, int min, int max, bool hasMax) {
+  if (!hasMax && max < def) max = def;
+  assert(min <= def && def <= max);
+  TIntProperty *property = new TIntProperty(id.str(), min, max, def, hasMax);
+  property->setSpinner();
+  return property;
+}
+
+//---------------------------------------------------------------------------------------------------
+
 void
-TAssistant::addProperty(TProperty *p)
+TAssistantBase::addProperty(TProperty *p)
   { m_properties.add(p); }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::setTranslation(const TStringId &name, const QString &localName) const
+TAssistantBase::setTranslation(const TStringId &name, const QString &localName) const
   { m_properties.getProperty(name)->setQStringName( localName ); }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::updateTranslation() const {
-  setTranslation(m_idEnabled, tr("Enabled"));
-  setTranslation(m_idMagnetism, tr("Magnetism"));
-}
+TAssistantBase::updateTranslation() const
+  { setTranslation(m_idEnabled, tr("Enabled")); }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onSetDefaults() {
+TAssistantBase::onSetDefaults() {
   setEnabled(true);
-  setMagnetism(1.0);
   for(TAssistantPointMap::iterator i = m_points.begin(); i != m_points.end(); ++i)
     i->second.position = i->second.defPosition;
   fixPoints();
@@ -233,13 +243,13 @@ TAssistant::onSetDefaults() {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::fixPoints()
+TAssistantBase::fixPoints()
   { onFixPoints(); }
 
 //---------------------------------------------------------------------------------------------------
 
 bool
-TAssistant::move(const TPointD &position) {
+TAssistantBase::move(const TPointD &position) {
   TPointD d = position - getBasePoint().position;
   if (d != TPointD()) {
     for(TAssistantPointMap::iterator i = m_points.begin(); i != m_points.end(); ++i)
@@ -253,7 +263,7 @@ TAssistant::move(const TPointD &position) {
 //---------------------------------------------------------------------------------------------------
 
 bool
-TAssistant::movePoint(const TStringId &name, const TPointD &position) {
+TAssistantBase::movePoint(const TStringId &name, const TPointD &position) {
   TAssistantPointMap::iterator i = m_points.find(name);
   if (i != m_points.end() && i->second.position != position) {
     onMovePoint(i->second, position);
@@ -265,7 +275,7 @@ TAssistant::movePoint(const TStringId &name, const TPointD &position) {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::setPointSelection(const TStringId &name, bool selected)  const {
+TAssistantBase::setPointSelection(const TStringId &name, bool selected)  const {
   if (const TAssistantPoint *p = findPoint(name))
     p->selected = selected;
 }
@@ -273,7 +283,7 @@ TAssistant::setPointSelection(const TStringId &name, bool selected)  const {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::setAllPointsSelection(bool selected) const {
+TAssistantBase::setAllPointsSelection(bool selected) const {
   for(TAssistantPointMap::const_iterator i = points().begin(); i != points().end(); ++i)
     i->second.selected = selected;
 }
@@ -281,7 +291,7 @@ TAssistant::setAllPointsSelection(bool selected) const {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onDataChanged(const TVariant &value) {
+TAssistantBase::onDataChanged(const TVariant &value) {
   const TVariant& pointsData = data()[m_idPoints];
   TVariantPathEntry entry;
 
@@ -303,14 +313,13 @@ TAssistant::onDataChanged(const TVariant &value) {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onDataFieldChanged(const TStringId &name, const TVariant &value) {
-  updateProperty(name, value);
-}
+TAssistantBase::onDataFieldChanged(const TStringId &name, const TVariant &value)
+  { updateProperty(name, value); }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onAllDataChanged() {
+TAssistantBase::onAllDataChanged() {
   const TVariant& pointsData = data()[m_idPoints];
   for(TAssistantPointMap::iterator i = m_points.begin(); i != m_points.end(); ++i) {
     const TVariant& pointData = pointsData[i->first];
@@ -325,32 +334,31 @@ TAssistant::onAllDataChanged() {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onFixPoints()
+TAssistantBase::onFixPoints()
   { }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onMovePoint(TAssistantPoint &point, const TPointD &position)
+TAssistantBase::onMovePoint(TAssistantPoint &point, const TPointD &position)
   { point.position = position; }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onFixData() {
+TAssistantBase::onFixData() {
   TVariant& pointsData = data()[m_idPoints];
   for(TAssistantPointMap::const_iterator i = points().begin(); i != points().end(); ++i) {
     TVariant& pointData = pointsData[i->first];
     pointData[m_idX].setDouble( i->second.position.x );
     pointData[m_idY].setDouble( i->second.position.y );
   }
-  setMagnetism( std::max(0.0, std::min(1.0, getMagnetism())) );
 }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::updateProperties() {
+TAssistantBase::updateProperties() {
   const TVariantMap &map = data().getMap();
   for(TVariantMap::const_iterator i = map.begin(); i != map.end(); ++i)
     if (i->first != m_idPoints)
@@ -360,7 +368,7 @@ TAssistant::updateProperties() {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::updateProperty(const TStringId &name, const TVariant &value) {
+TAssistantBase::updateProperty(const TStringId &name, const TVariant &value) {
   TProperty *property = m_properties.getProperty(name);
   if (!property)
     return;
@@ -371,6 +379,9 @@ TAssistant::updateProperty(const TStringId &name, const TVariant &value) {
   if (TDoubleProperty *doubleProperty = dynamic_cast<TDoubleProperty*>(property)) {
     doubleProperty->setValue( value.getDouble() );
   } else
+  if (TIntProperty *intProperty = dynamic_cast<TIntProperty*>(property)) {
+    intProperty->setValue( (int)value.getDouble() );
+  } else
   if (TStringProperty *stringProperty = dynamic_cast<TStringProperty*>(property)) {
     stringProperty->setValue( to_wstring(value.getString()) );
   } else
@@ -382,7 +393,7 @@ TAssistant::updateProperty(const TStringId &name, const TVariant &value) {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::onPropertyChanged(const TStringId &name) {
+TAssistantBase::onPropertyChanged(const TStringId &name) {
   TProperty *property = m_properties.getProperty(name);
   if (!property)
     return;
@@ -396,6 +407,9 @@ TAssistant::onPropertyChanged(const TStringId &name) {
   if (TDoubleProperty *doubleProperty = dynamic_cast<TDoubleProperty*>(property)) {
     data()[name].setDouble( doubleProperty->getValue() );
   } else
+  if (TIntProperty *intProperty = dynamic_cast<TIntProperty*>(property)) {
+    data()[name].setDouble( (double)intProperty->getValue() );
+  } else
   if (TStringProperty *stringProperty = dynamic_cast<TStringProperty*>(property)) {
     data()[name].setString( to_string(stringProperty->getValue()) );
   } else
@@ -407,29 +421,31 @@ TAssistant::onPropertyChanged(const TStringId &name) {
 //---------------------------------------------------------------------------------------------------
 
 double
-TAssistant::getDrawingAlpha(bool enabled) const
+TAssistantBase::getDrawingAlpha(bool enabled) const
   { return enabled && this->getEnabled() ? 0.5 : 0.25; }
 
 //---------------------------------------------------------------------------------------------------
 
 double
-TAssistant::getDrawingGridAlpha() const
+TAssistantBase::getDrawingGridAlpha() const
   { return 0.2; }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha) const {
+TAssistantBase::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha) const {
   double colorBlack[4] = { 0.0, 0.0, 0.0, alpha };
   double colorWhite[4] = { 1.0, 1.0, 1.0, alpha };
+  
+  if (drawFlags & DRAW_ERROR) colorBlack[0] = 1;
 
   glPushAttrib(GL_ALL_ATTRIB_BITS);
   tglEnableBlending();
-  tglEnableLineSmooth(true, 1.0 * line_width_scale);
+  tglEnableLineSmooth(true, 1.0 * lineWidthScale);
   TPointD d = p1 - p0;
   double k = norm2(d);
   if (k > TConsts::epsilon*TConsts::epsilon) {
-    k = 0.5*pixelSize*line_width_scale/sqrt(k);
+    k = 0.5*pixelSize*lineWidthScale/sqrt(k);
     d = TPointD(-k*d.y, k*d.x);
     glColor4dv(colorWhite);
     tglDrawSegment(p0 - d, p1 - d);
@@ -442,7 +458,7 @@ TAssistant::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha) const {
+TAssistantBase::drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha) const {
   TPointD d = normal*5*pixelSize;
   drawSegment(p - d,p + d, pixelSize, alpha);
 }
@@ -450,7 +466,7 @@ TAssistant::drawMark(const TPointD &p, const TPointD &normal, double pixelSize, 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::drawDot(const TPointD &p, double alpha) const {
+TAssistantBase::drawDot(const TPointD &p, double alpha) const {
   double colorBlack[4] = { 0.0, 0.0, 0.0, alpha };
   double colorWhite[4] = { 1.0, 1.0, 1.0, alpha };
 
@@ -475,7 +491,7 @@ TAssistant::drawDot(const TPointD &p, double alpha) const {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::drawPoint(const TAssistantPoint &point, double pixelSize) const {
+TAssistantBase::drawPoint(const TAssistantPoint &point, double pixelSize) const {
   if (!point.visible) return;
 
   double radius = point.radius;
@@ -508,7 +524,7 @@ TAssistant::drawPoint(const TAssistantPoint &point, double pixelSize) const {
   TPointD gridDy(0.0, pixelSize*radius);
 
   // back line
-  tglEnableLineSmooth(true, 2.0*width*line_width_scale);
+  tglEnableLineSmooth(true, 2.0*width*lineWidthScale);
   glColor4dv(colorWhite);
   if (point.type == TAssistantPoint::CircleCross) {
     tglDrawSegment(point.position - crossDx, point.position + crossDx);
@@ -517,7 +533,7 @@ TAssistant::drawPoint(const TAssistantPoint &point, double pixelSize) const {
   tglDrawCircle(point.position, radius*pixelSize);
 
   // front line
-  glLineWidth(width * line_width_scale);
+  glLineWidth(width * lineWidthScale);
   glColor4dv(colorBlack);
   if (point.type == TAssistantPoint::CircleCross) {
     tglDrawSegment(point.position - crossDx, point.position + crossDx);
@@ -552,19 +568,91 @@ TAssistant::drawPoint(const TAssistantPoint &point, double pixelSize) const {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::getGuidelines(const TPointD &position, const TAffine &toTool, TGuidelineList &outGuidelines) const
-  { }
+TAssistantBase::drawIndex(const TPointD &p, int index, bool selected, double pixelSize) const {
+  static const int segments[7][4] = {
+    { 0, 2, 1, 2 },   // A
+    { 1, 1, 1, 2 },   // B    + A +
+    { 1, 0, 1, 1 },   // C    F   B
+    { 0, 0, 1, 0 },   // D    + G +
+    { 0, 0, 0, 1 },   // E    E   C
+    { 0, 1, 0, 2 },   // F    + D +
+    { 0, 1, 1, 1 } }; // G
+    
+  static const int glyphs[][7] = {
+  // A B C D E F G
+    {1,1,1,1,1,1,0},   // 0
+    {0,1,1,0,0,0,0},   // 1
+    {1,1,0,1,1,0,1},   // 2
+    {1,1,1,1,0,0,1},   // 3
+    {0,1,1,0,0,1,1},   // 4
+    {1,0,1,1,0,1,1},   // 5
+    {1,0,1,1,1,1,1},   // 6
+    {1,1,1,0,0,1,0},   // 7
+    {1,1,1,1,1,1,1},   // 8
+    {1,1,1,1,0,1,1} }; // 9
+  
+  if (index < 0) index = 0;
+  
+  int len = 0;
+  int digits[16] = {};
+  for(int i = index; i; i /= 10)
+    digits[len++] = i%10;
+  if (!len) len = 1;
+  
+  double w = 5, h = 5, d = 0.5, dx = w+2;
+  double alpha = 0.5;
+  double colorBlack[4] = { 0.0, 0.0, 0.0, alpha };
+  double colorWhite[4] = { 1.0, 1.0, 1.0, alpha };
+  if (selected) colorBlack[2] = 1.0;
+
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+  tglEnableBlending();
+  tglEnableLineSmooth(true, 1.0 * lineWidthScale);
+  double k = 0.5*pixelSize*lineWidthScale;
+  
+  double y = p.y;
+  for(int i = 0; i < len; ++i) {
+    double x = p.x + dx*(len-i-1);
+    const int *g = glyphs[digits[i]];
+    for(int i = 0; i < 7; ++i) {
+      if (!g[i]) continue;
+      const int *s = segments[i];
+      if (s[0] == s[2]) {
+        // vertical
+        glColor4dv(colorWhite);
+        tglDrawSegment(
+          TPointD(x + s[0]*w + k, y + s[1]*h + d),
+          TPointD(x + s[2]*w + k, y + s[3]*h - d) );
+        glColor4dv(colorBlack);
+        tglDrawSegment(
+          TPointD(x + s[0]*w - k, y + s[1]*h + d),
+          TPointD(x + s[2]*w - k, y + s[3]*h - d) );
+      } else {
+        // horisontal
+        glColor4dv(colorWhite);
+        tglDrawSegment(
+          TPointD(x + s[0]*w + d, y + s[1]*h + k),
+          TPointD(x + s[2]*w - d, y + s[3]*h + k) );
+        glColor4dv(colorBlack);
+        tglDrawSegment(
+          TPointD(x + s[0]*w + d, y + s[1]*h - k),
+          TPointD(x + s[2]*w - d, y + s[3]*h - k) );
+      }
+    }
+  }
+  glPopAttrib();
+}
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::draw(TToolViewer *viewer, bool enabled) const
+TAssistantBase::draw(TToolViewer*, bool) const
   { }
 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistant::drawEdit(TToolViewer *viewer) const {
+TAssistantBase::drawEdit(TToolViewer *viewer) const {
   // paint all points
   draw(viewer);
   double pixelSize = sqrt(tglGetPixelSize2());
@@ -574,6 +662,56 @@ TAssistant::drawEdit(TToolViewer *viewer) const {
 
 //---------------------------------------------------------------------------------------------------
 
+void
+TAssistantBase::drawEdit(TToolViewer *viewer, int index) const {
+  drawEdit(viewer);
+  drawIndex(
+    getBasePoint().position + TPointD(8, 8),
+    index, getBasePoint().selected, sqrt(tglGetPixelSize2()) );
+}
+
+
+//************************************************************************
+//    TAssistant implementation
+//************************************************************************
+
+TAssistant::TAssistant(TMetaObject &object):
+  TAssistantBase(object),
+  m_idMagnetism("magnetism")
+  { addProperty( new TDoubleProperty(m_idMagnetism.str(), 0.0, 1.0, getMagnetism()) ); }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::updateTranslation() const {
+  TAssistantBase::updateTranslation();
+  setTranslation(m_idMagnetism, tr("Magnetism"));
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::onSetDefaults() {
+  setMagnetism(1.0);
+  TAssistantBase::onSetDefaults();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::onFixData() {
+  TAssistantBase::onFixData();
+  setMagnetism( std::max(0.0, std::min(1.0, getMagnetism())) );
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::getGuidelines(const TPointD&, const TAffine&, TGuidelineList&) const
+  { }
+
+//---------------------------------------------------------------------------------------------------
+
 bool
 TAssistant::calcPerspectiveStep(
   double minStep,
@@ -669,11 +807,11 @@ TAssistant::scanAssistants(
           if (!enabledOnly || assistant->getEnabled())
           {
             found = true;
+            if (!doSomething) return true;
             if (findGuidelines)
               for(int i = 0; i < positionsCount; ++i)
                 assistant->getGuidelines(positions[i], imageToTrack, *outGuidelines);
             if (draw) assistant->draw(viewer, assistant->getEnabled() && markEnabled);
-            if (!doSomething) return true;
           }
 
         if (draw) glPopMatrix();
diff --git a/toonz/sources/tnztools/assistants/assistantellipse.cpp b/toonz/sources/tnztools/assistants/assistantellipse.cpp
index db6055e..29e2060 100644
--- a/toonz/sources/tnztools/assistants/assistantellipse.cpp
+++ b/toonz/sources/tnztools/assistants/assistantellipse.cpp
@@ -57,6 +57,7 @@ public:
     { return tr("Ellipse"); }
 
   void updateTranslation() const override {
+    TAssistant::updateTranslation();
     setTranslation(m_idRestricktA, tr("Restrict A"));
     setTranslation(m_idRestricktB, tr("Restrict B"));
     setTranslation(m_idRepeat, tr("Repeat"));
@@ -448,7 +449,6 @@ private:
     bool grid = getGrid();
     bool ruler = getRestrictA() && getRestrictB();
     bool concentric = !getRestrictA() && !getRestrictB();
-    bool perspective = getPerspective();
 
     drawSegment( ellipseMatrix*TPointD(-crossSize, 0.0),
                  ellipseMatrix*TPointD( crossSize, 0.0), pixelSize, alpha);
diff --git a/toonz/sources/tnztools/assistants/assistantline.cpp b/toonz/sources/tnztools/assistants/assistantline.cpp
index d87134f..f0a1fd6 100644
--- a/toonz/sources/tnztools/assistants/assistantline.cpp
+++ b/toonz/sources/tnztools/assistants/assistantline.cpp
@@ -52,6 +52,7 @@ public:
     { return tr("Line"); }
 
   void updateTranslation() const override {
+    TAssistant::updateTranslation();
     setTranslation(m_idRestricktA, tr("Restrict A"));
     setTranslation(m_idRestricktB, tr("Restrict B"));
     setTranslation(m_idParallel, tr("Parallel"));
diff --git a/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp b/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp
index b660e60..61b0792 100644
--- a/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp
+++ b/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp
@@ -51,6 +51,7 @@ public:
     { return tr("Vanishing Point"); }
 
   void updateTranslation() const override {
+    TAssistant::updateTranslation();
     setTranslation(m_idPassThrough, tr("Pass Through"));
     setTranslation(m_idGrid, tr("Grid"));
     setTranslation(m_idPerspective, tr("Perspective"));
diff --git a/toonz/sources/tnztools/assistants/replicatoraffine.cpp b/toonz/sources/tnztools/assistants/replicatoraffine.cpp
new file mode 100644
index 0000000..f637c90
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/replicatoraffine.cpp
@@ -0,0 +1,341 @@
+
+
+// TnzTools includes
+#include <tools/replicator.h>
+#include <tools/modifiers/modifierclone.h>
+
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TReplicatorAffine implementation
+//*****************************************************************************************
+
+class TReplicatorAffine final : public TReplicator {
+  Q_DECLARE_TR_FUNCTIONS(TReplicatorAffine)
+public:
+  const TStringId m_idFixScale;
+  const TStringId m_idFixAspect;
+  const TStringId m_idFixAngle;
+  const TStringId m_idFixSkew;
+  const TStringId m_idCount;
+  const TStringId m_idCountInv;
+  const TStringId m_idPressure;
+
+protected:
+  TAssistantPoint &m_center0;
+  TAssistantPoint &m_a0;
+  TAssistantPoint &m_b0;
+  TAssistantPoint &m_center1;
+  TAssistantPoint &m_a1;
+  TAssistantPoint &m_b1;
+
+public:
+  TReplicatorAffine(TMetaObject &object):
+    TReplicator(object),
+    m_idFixScale("fixScale"),
+    m_idFixAspect("fixAspect"),
+    m_idFixAngle("fixAngle"),
+    m_idFixSkew("fixSkew"),
+    m_idCount("count"),
+    m_idCountInv("countInv"),
+    m_idPressure("pressure"),
+    m_center0( addPoint("center0", TAssistantPoint::CircleCross) ),
+    m_a0     ( addPoint("a0",      TAssistantPoint::CircleFill, TPointD(40,  0)) ),
+    m_b0     ( addPoint("b0",      TAssistantPoint::Circle,     TPointD( 0, 40)) ),
+    m_center1( addPoint("center1", TAssistantPoint::Circle,     TPointD(50,  0)) ),
+    m_a1     ( addPoint("a1",      TAssistantPoint::CircleFill, TPointD(90,  0)) ),
+    m_b1     ( addPoint("b1",      TAssistantPoint::Circle,     TPointD(50, 40)) )
+  {
+    addProperty( new TBoolProperty(m_idFixScale.str(),  getFixScale()) );
+    addProperty( new TBoolProperty(m_idFixAspect.str(), getFixAspect()) );
+    addProperty( new TBoolProperty(m_idFixAngle.str(),  getFixAngle()) );
+    addProperty( new TBoolProperty(m_idFixSkew.str(),   getFixSkew()) );
+    addProperty( createCountProperty(m_idCount, getCount(), 0) );
+    addProperty( createCountProperty(m_idCountInv, getCountInv(), 0) );
+    addProperty( new TDoubleProperty(m_idPressure.str(), 0.0, 2.0, getPressure()) );
+  }
+
+  
+  static QString getLocalName()
+    { return tr("Replicator Affine"); }
+
+    
+  void updateTranslation() const override {
+    TReplicator::updateTranslation();
+    setTranslation(m_idFixScale, tr("Fix Scale"));
+    setTranslation(m_idFixAspect, tr("Fix Aspect"));
+    setTranslation(m_idFixAngle, tr("Fix Angle"));
+    setTranslation(m_idFixSkew, tr("Fix Skew"));
+    setTranslation(m_idCount, tr("Count"));
+    setTranslation(m_idCountInv, tr("Inv. Count"));
+    setTranslation(m_idPressure, tr("Pressure"));
+  }
+
+  
+  inline bool getFixScale() const
+    { return data()[m_idFixScale].getBool(); }
+  inline bool getFixAspect() const
+    { return data()[m_idFixAspect].getBool(); }
+  inline bool getFixAngle() const
+    { return data()[m_idFixAngle].getBool(); }
+  inline bool getFixSkew() const
+    { return data()[m_idFixSkew].getBool(); }
+  inline int getCount() const
+    { return (int)data()[m_idCount].getDouble(); }
+  inline int getCountInv() const
+    { return (int)data()[m_idCountInv].getDouble(); }
+  inline double getPressure() const
+    { return data()[m_idPressure].getDouble(); }
+
+protected:
+  inline void setCount(int x)
+    { if (getCount() != (double)x) data()[m_idCount].setDouble((double)x); }
+  inline void setCountInv(int x)
+    { if (getCountInv() != (double)x) data()[m_idCountInv].setDouble((double)x); }
+  inline void setPressure(double x)
+    { if (getPressure() != x) data()[m_idPressure].setDouble(x); }
+
+    
+  void onSetDefaults() override {
+    setCount(1);
+    setPressure(1);
+    TReplicator::onSetDefaults();
+  }
+
+  
+  void onFixData() override {
+    TReplicator::onFixData();
+    setCount( std::max(0, std::min(multiplierSoftLimit - 1, getCount())) );
+    setCountInv( std::max(0, std::min(multiplierSoftLimit - 1, getCountInv())) );
+    setPressure( std::max(0.0, std::min(2.0, getPressure())) );
+  }
+
+
+  TAffine getAffine(const TAffine &toTool = TAffine()) const {
+    TPointD c, x, y;
+    c = toTool*m_center0.position;
+    x = toTool*m_a0.position - c;
+    y = toTool*m_b0.position - c;
+    TAffine t0( x.x, y.x, c.x,
+                x.y, y.y, c.y );
+    c = toTool*m_center1.position;
+    x = toTool*m_a1.position - c;
+    y = toTool*m_b1.position - c;
+    TAffine t1( x.x, y.x, c.x,
+                x.y, y.y, c.y );
+    return t1*t0.inv();
+  }
+  
+  
+  bool isMirror() const {
+    TPointD da0 = m_a0.position - m_center0.position;
+    TPointD db0 = m_b0.position - m_center0.position;
+    TPointD pa0(-da0.y, da0.x);
+
+    TPointD da1 = m_a1.position - m_center1.position;
+    TPointD db1 = m_b1.position - m_center1.position;
+    TPointD pa1(-da1.y, da0.x);
+    
+    return (pa0*db0 < 0) != (pa1*db1 < 0);
+  }
+  
+
+  void doFixPoints(int mirror) {
+    TPointD &c0 = m_center0.position;
+    TPointD &a0 = m_a0.position;
+    TPointD &b0 = m_b0.position;
+    
+    TPointD &c1 = m_center1.position;
+    TPointD &a1 = m_a1.position;
+    TPointD &b1 = m_b1.position;
+
+    if (getFixScale()) {
+      double la0 = tdistance(a0, c0);
+      double lb0 = tdistance(b0, c0);
+      double la1 = tdistance(a1, c1);
+      double lb1 = tdistance(b1, c1);
+      a1 = la1 > TConsts::epsilon
+         ? (a1 - c1)*(la0/la1) + c1
+         : a0 - c0 + c1;
+      b1 = lb1 > TConsts::epsilon
+         ? (b1 - c1)*(lb0/lb1) + c1
+         : b0 - c0 + c1;
+    } else
+    if (getFixAspect()) {
+      double la0 = tdistance(a0, c0);
+      double lb0 = tdistance(b0, c0);
+      double la1 = tdistance(a1, c1);
+      double lb1 = tdistance(b1, c1);
+      if (la0 > TConsts::epsilon)
+        b1 = lb1 > TConsts::epsilon
+           ? (b1 - c1)*(lb0/lb1*la1/la0) + c1
+           : (b0 - c0)*(la1/la0) + c1;
+    }
+
+    if (getFixAngle()) {
+      double la0 = tdistance(a0, c0);
+      double lb0 = tdistance(b0, c0);
+      double la1 = tdistance(a1, c1);
+      double lb1 = tdistance(b1, c1);
+      if (la0 > TConsts::epsilon)
+        a1 = (a0 - c0)*(la1/la0) + c1;
+      if (lb0 > TConsts::epsilon)
+        b1 = (b0 - c0)*(lb1/lb0) + c1;
+    } else
+    if (getFixSkew()) {
+      TPointD da0 = a0 - c0;
+      TPointD pa0(-da0.y, da0.x);
+      double x = (b0 - c0)*da0;
+      double y = (b0 - c0)*pa0;
+        
+      TPointD da1 = a1 - c1;
+      TPointD db1 = b1 - c1;
+      TPointD pa1(-da1.y, da1.x);
+      if (mirror) y *= mirror; else      
+        if ((pa1*db1 < 0) != (y < 0)) y = -y;
+      
+      TPointD p = da1*x + pa1*y;
+      double l = norm2(p);
+      if (l > TConsts::epsilon*TConsts::epsilon)
+        b1 = p*sqrt(norm2(db1)/l) + c1;
+    }
+  }
+
+  
+  void onFixPoints() override {
+    doFixPoints(0);
+  }
+  
+  
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    TPointD pc0 = m_center0.position;
+    TPointD pc1 = m_center1.position;
+    int mirror = &point == &m_b1 ? 0 : (isMirror() ? -1 : 1);
+    point.position = position;
+    if (&point == &m_center0) {
+      TPointD d = m_center0.position - pc0;
+      m_a0.position += d;
+      m_b0.position += d;
+      m_a1.position += d;
+      m_b1.position += d;
+      m_center1.position += d;
+    } else
+    if (&point == &m_center1) {
+      TPointD d = m_center1.position - pc1;
+      m_a1.position += d;
+      m_b1.position += d;
+    }
+    doFixPoints(mirror);
+  }
+  
+  
+  void onDataFieldChanged(const TStringId &name, const TVariant &value) override {
+    TReplicator::onDataFieldChanged(name, value);
+    if ( name == m_idFixScale
+      || name == m_idFixAspect
+      || name == m_idFixAngle
+      || name == m_idFixSkew )
+        fixPoints();
+  }
+
+public:
+  int getMultipler() const override
+    { return getCount() + 1; }
+  
+  
+  void getPoints(const TAffine &toTool, PointList &points) const override {
+    points.reserve(points.size() * getMultipler());
+    int pointsCount = (int)points.size();
+
+    TAffine aff = getAffine(toTool);
+    struct {
+      int count;
+      TAffine aff;
+    } t[] = {
+      { getCount(), aff },
+      { getCountInv(), aff.inv() } };
+
+    for(int i = 0; i < 2; ++i) {
+      TAffine a;
+      for(int j = 0; j < t[i].count; ++j) {
+        a = t[i].aff * a;
+        transformPoints(a, points, pointsCount);
+      }
+    }
+  }
+  
+  
+  void getModifiers(
+    const TAffine &toTool,
+    TInputModifier::List &outModifiers ) const override
+  {
+    TAffine aff = getAffine(toTool);
+    double pressure = getPressure();
+    double pressureInv = fabs(pressure) > TConsts::epsilon ? 1/pressure : 0;
+    
+    struct {
+      int count;
+      TAffine aff;
+      double pressure;
+    } t[] = {
+      { getCount(), aff, pressure },
+      { getCountInv(), aff.inv(), pressureInv },
+    };
+
+    TModifierClone *modifier = new TModifierClone();
+    for(int i = 0; i < 2; ++i) {
+      TTrackTransform tt;
+      for(int j = 0; j < t[i].count; ++j) {
+        tt.transform = t[i].aff * tt.transform;
+        tt.tiltTransform = TTrackTransform::makeTiltTransform(tt.transform);
+        tt.pressureScale *= t[i].pressure;
+        modifier->transforms.push_back(tt);
+      }
+    }
+    
+    outModifiers.push_back(modifier);
+  }
+
+  
+  void draw(TToolViewer*, bool enabled) const override {
+    double alpha = getDrawingAlpha(enabled);
+    double gridAlpha = getDrawingGridAlpha();
+    double pixelSize = sqrt(tglGetPixelSize2());
+
+    TPointD c = m_center0.position;
+    TPointD a = m_a0.position;
+    TPointD b = m_b0.position;
+    TAffine aff = getAffine();
+
+    // draw base
+    drawSegment(c, a, pixelSize, alpha);
+    drawSegment(c, b, pixelSize, alpha);
+    
+    // draw clones
+    TAffine t;
+    for(int i = getCount(); i > 0; --i) {
+      t = aff * t;
+      drawSegment(t*c, t*a, pixelSize, gridAlpha);
+      drawSegment(t*c, t*b, pixelSize, gridAlpha);
+    }
+    
+    // draw inverted clones
+    t = TAffine();
+    aff = aff.inv();
+    for(int i = getCountInv(); i > 0; --i) {
+      t = aff * t;
+      drawSegment(t*c, t*a, pixelSize, gridAlpha);
+      drawSegment(t*c, t*b, pixelSize, gridAlpha);
+    }
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TReplicatorAffine> replicatorAffine("replicatorAffine");
diff --git a/toonz/sources/tnztools/assistants/replicatorgrid.cpp b/toonz/sources/tnztools/assistants/replicatorgrid.cpp
new file mode 100644
index 0000000..b276024
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/replicatorgrid.cpp
@@ -0,0 +1,335 @@
+
+
+// TnzTools includes
+#include <tools/replicator.h>
+#include <tools/modifiers/modifierclone.h>
+
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TReplicatorGrid implementation
+//*****************************************************************************************
+
+class TReplicatorGrid final : public TReplicator {
+  Q_DECLARE_TR_FUNCTIONS(TReplicatorGrid)
+public:
+  const TStringId m_idFixAngle;
+  const TStringId m_idFixSkew;
+  const TStringId m_idMirrorA;
+  const TStringId m_idMirrorB;
+  const TStringId m_idCountA;
+  const TStringId m_idCountAInv;
+  const TStringId m_idCountB;
+  const TStringId m_idCountBInv;
+
+protected:
+  TAssistantPoint &m_center;
+  TAssistantPoint &m_a;
+  TAssistantPoint &m_b;
+
+public:
+  TReplicatorGrid(TMetaObject &object):
+    TReplicator(object),
+    m_idFixAngle("fixAngle"),
+    m_idFixSkew("fixSkew"),
+    m_idMirrorA("mirrorA"),
+    m_idMirrorB("mirrorB"),
+    m_idCountA("countA"),
+    m_idCountAInv("countAInv"),
+    m_idCountB("countB"),
+    m_idCountBInv("countBInv"),
+    m_center( addPoint("center", TAssistantPoint::CircleCross) ),
+    m_a     ( addPoint("a",      TAssistantPoint::CircleFill, TPointD(80,   0)) ),
+    m_b     ( addPoint("b",      TAssistantPoint::Circle,     TPointD( 0, -80)) )
+  {
+    addProperty( new TBoolProperty(m_idFixAngle.str(),  getFixAngle()) );
+    addProperty( new TBoolProperty(m_idFixSkew.str(),   getFixSkew()) );
+    addProperty( new TBoolProperty(m_idMirrorA.str(),   getMirrorA()) );
+    addProperty( new TBoolProperty(m_idMirrorB.str(),   getMirrorB()) );
+    addProperty( createCountProperty(m_idCountA, getCountA(), 1) );
+    addProperty( createCountProperty(m_idCountAInv, getCountAInv(), 0) );
+    addProperty( createCountProperty(m_idCountB, getCountB(), 1) );
+    addProperty( createCountProperty(m_idCountBInv, getCountBInv(), 0) );
+  }
+
+  
+  static QString getLocalName()
+    { return tr("Replicator Grid"); }
+
+    
+  void updateTranslation() const override {
+    TReplicator::updateTranslation();
+    setTranslation(m_idFixAngle, tr("Fix Angle"));
+    setTranslation(m_idFixSkew, tr("Fix Skew"));
+    setTranslation(m_idMirrorA, tr("Mirror A"));
+    setTranslation(m_idMirrorB, tr("Mirror B"));
+    setTranslation(m_idCountA, tr("Count A"));
+    setTranslation(m_idCountAInv, tr("Inv. Count A"));
+    setTranslation(m_idCountB, tr("Count B"));
+    setTranslation(m_idCountBInv, tr("Inv. Count B"));
+  }
+
+  
+  inline bool getFixAngle() const
+    { return data()[m_idFixAngle].getBool(); }
+  inline bool getFixSkew() const
+    { return data()[m_idFixSkew].getBool(); }
+  inline bool getMirrorA() const
+    { return data()[m_idMirrorA].getBool(); }
+  inline bool getMirrorB() const
+    { return data()[m_idMirrorB].getBool(); }
+  inline int getCountA() const
+    { return (int)data()[m_idCountA].getDouble(); }
+  inline int getCountAInv() const
+    { return (int)data()[m_idCountAInv].getDouble(); }
+  inline int getCountB() const
+    { return (int)data()[m_idCountB].getDouble(); }
+  inline int getCountBInv() const
+    { return (int)data()[m_idCountBInv].getDouble(); }
+
+protected:
+  inline void setCountA(int x)
+    { if (getCountA() != (double)x) data()[m_idCountA].setDouble((double)x); }
+  inline void setCountAInv(int x)
+    { if (getCountAInv() != (double)x) data()[m_idCountAInv].setDouble((double)x); }
+  inline void setCountB(int x)
+    { if (getCountB() != (double)x) data()[m_idCountB].setDouble((double)x); }
+  inline void setCountBInv(int x)
+    { if (getCountBInv() != (double)x) data()[m_idCountBInv].setDouble((double)x); }
+
+    
+  void onSetDefaults() override {
+    setCountA(3);
+    setCountAInv(2);
+    setCountB(2);
+    setCountBInv(1);
+    TReplicator::onSetDefaults();
+  }
+
+  
+  void onFixData() override {
+    TReplicator::onFixData();
+    setCountA( std::max(1, std::min(multiplierSoftLimit, getCountA())) );
+    setCountAInv( std::max(0, std::min(multiplierSoftLimit, getCountAInv())) );
+    setCountB( std::max(1, std::min(multiplierSoftLimit, getCountB())) );
+    setCountBInv( std::max(0, std::min(multiplierSoftLimit, getCountBInv())) );
+  }
+
+
+  void onFixPoints() override {
+    TPointD &c = m_center.position;
+    TPointD &a = m_a.position;
+    TPointD &b = m_b.position;
+
+    if (getFixAngle()) {
+      double la = tdistance(a, c);
+      double lb = tdistance(b, c);
+      a = TPointD(a.x < c.x ? -la : la, 0) + c;
+      b = TPointD(0, b.y < c.y ? -lb : lb) + c;
+    } else
+    if (getFixSkew()) {
+      TPointD pa(c.y - a.y, a.x - c.x);
+      double l = norm2(pa);
+      if (l > TConsts::epsilon*TConsts::epsilon) {
+        TPointD db = b - c;
+        double k = sqrt(norm2(db)/l);
+        if (db*pa < 0) k = -k;
+        b = pa*k + c;
+      }
+    }
+  }
+  
+  
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    TPointD pc = m_center.position;
+    TPointD pa = m_a.position;
+    point.position = position;
+    if (&point == &m_center) {
+      TPointD d = m_center.position - pc;
+      m_a.position += d;
+      m_b.position += d;
+    } else {
+      fixPoints();
+    }
+  }
+  
+  
+  void onDataFieldChanged(const TStringId &name, const TVariant &value) override {
+    TReplicator::onDataFieldChanged(name, value);
+    if ( name == m_idFixAngle
+      || name == m_idFixSkew )
+        fixPoints();
+  }
+
+ 
+public:
+  int getMultipler() const override {
+    return (getCountA() + getCountAInv())
+         * (getCountB() + getCountBInv())
+         * (getMirrorA() ? 2 : 1)
+         * (getMirrorB() ? 2 : 1);    
+  }
+  
+  
+  void getPoints(const TAffine &toTool, PointList &points) const override {
+    points.reserve(points.size() * getMultipler());
+    int pointsCount = (int)points.size();
+    
+    TPointD c = toTool*m_center.position;
+    TPointD da = toTool*m_a.position - c;
+    TPointD db = toTool*m_b.position - c;
+    
+    bool mirrorA = getMirrorA();
+    bool mirrorB = getMirrorB();
+    TAffine t, ma, mb, mc;
+    if (mirrorA || mirrorB) {
+      if (fabs(da*db) > TConsts::epsilon) {
+        t  = TAffine(  da.x,  db.x, c.x,
+                       da.y,  db.y, c.y ).inv();
+        ma = TAffine( -da.x,  db.x, c.x,
+                      -da.y,  db.y, c.y )*t;
+        mb = TAffine(  da.x, -db.x, c.x,
+                       da.y, -db.y, c.y )*t;
+        mc = TAffine( -da.x, -db.x, c.x,
+                      -da.y, -db.y, c.y )*t;
+      } else {
+        mirrorA = mirrorB = false;
+      }
+    }
+    
+    int a1 = getCountA();
+    int b1 = getCountB();
+    int a0 = -getCountAInv();
+    int b0 = -getCountBInv();
+    
+    for(int ib = b0; ib < b1; ++ib)
+    for(int ia = a0; ia < a1; ++ia) {
+      TPointD o = da*ia + db*ib;
+      if (ia || ib)
+        transformPoints(
+          TAffine( 1, 0, o.x,
+                   0, 1, o.y ),
+          points, pointsCount );
+      if (mirrorA)
+        transformPoints(
+          TAffine( ma.a11, ma.a12, ma.a13 + o.x,
+                   ma.a21, ma.a22, ma.a23 + o.y ),
+          points, pointsCount );
+      if (mirrorB)
+        transformPoints(
+          TAffine( mb.a11, mb.a12, mb.a13 + o.x,
+                   mb.a21, mb.a22, mb.a23 + o.y ),
+          points, pointsCount );
+      if (mirrorA && mirrorB)
+        transformPoints(
+          TAffine( mc.a11, mc.a12, mc.a13 + o.x,
+                   mc.a21, mc.a22, mc.a23 + o.y ),
+          points, pointsCount );
+    }
+  }
+
+  
+  void getModifiers(
+    const TAffine &toTool,
+    TInputModifier::List &outModifiers ) const override
+  {
+    TPointD c = toTool*m_center.position;
+    TPointD da = toTool*m_a.position - c;
+    TPointD db = toTool*m_b.position - c;
+    
+    bool mirrorA = getMirrorA();
+    bool mirrorB = getMirrorB();
+    TAffine t, ma, mb, mc;
+    if (mirrorA || mirrorB) {
+      if (fabs(da*db) > TConsts::epsilon) {
+        t  = TAffine(  da.x,  db.x, c.x,
+                       da.y,  db.y, c.y ).inv();
+        ma = TAffine( -da.x,  db.x, c.x,
+                      -da.y,  db.y, c.y )*t;
+        mb = TAffine(  da.x, -db.x, c.x,
+                       da.y, -db.y, c.y )*t;
+        mc = TAffine( -da.x, -db.x, c.x,
+                      -da.y, -db.y, c.y )*t;
+      } else {
+        mirrorA = mirrorB = false;
+      }
+    }
+    
+    int a1 = getCountA();
+    int b1 = getCountB();
+    int a0 = -getCountAInv();
+    int b0 = -getCountBInv();
+    
+    TModifierClone *modifier = new TModifierClone();
+    for(int ib = b0; ib < b1; ++ib)
+    for(int ia = a0; ia < a1; ++ia) {
+      TPointD o = da*ia + db*ib;
+      if (ia || ib)
+        modifier->transforms.push_back(TTrackTransform(
+          TAffine( 1, 0, o.x,
+                   0, 1, o.y ) ));
+      if (mirrorA)
+        modifier->transforms.push_back(TTrackTransform(
+          TAffine( ma.a11, ma.a12, ma.a13 + o.x,
+                   ma.a21, ma.a22, ma.a23 + o.y ) ));
+      if (mirrorB)
+        modifier->transforms.push_back(TTrackTransform(
+          TAffine( mb.a11, mb.a12, mb.a13 + o.x,
+                   mb.a21, mb.a22, mb.a23 + o.y ) ));
+      if (mirrorA && mirrorB)
+        modifier->transforms.push_back(TTrackTransform(
+          TAffine( mc.a11, mc.a12, mc.a13 + o.x,
+                   mc.a21, mc.a22, mc.a23 + o.y ) ));
+    }
+    outModifiers.push_back(modifier);
+  }
+
+  
+  void draw(TToolViewer*, bool enabled) const override {
+    double alpha = getDrawingAlpha(enabled);
+    double gridAlpha = getDrawingGridAlpha();
+    double pixelSize = sqrt(tglGetPixelSize2());
+
+    TPointD c = m_center.position;
+    TPointD a = m_a.position;
+    TPointD b = m_b.position;
+    TPointD da = a - c;
+    TPointD db = b - c;
+
+    int a1 = getCountA();
+    int b1 = getCountB();
+    int a0 = -getCountAInv();
+    int b0 = -getCountBInv();
+    
+    bool mirrorA = getMirrorA();
+    bool mirrorB = getMirrorB();
+    
+    // draw base
+    drawSegment(c, a, pixelSize, alpha);
+    drawSegment(c, b, pixelSize, alpha);
+    
+    // draw clones
+    for(int ib = b0; ib < b1; ++ib)
+    for(int ia = a0; ia < a1; ++ia) {
+      TPointD o = c + da*ia + db*ib;
+      if (ia || ib) {
+        drawSegment(o, o + da*0.2, pixelSize, gridAlpha);
+        drawSegment(o, o + db*0.2, pixelSize, gridAlpha);
+      }
+      if (mirrorA && (ib || ia != 1))
+        drawSegment(o - da*0.2, o, pixelSize, gridAlpha);
+      if (mirrorB && (ia || ib != 1))
+        drawSegment(o - db*0.2, o, pixelSize, gridAlpha);
+    }
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TReplicatorGrid> replicatorGrid("replicatorGrid");
diff --git a/toonz/sources/tnztools/assistants/replicatorjitter.cpp b/toonz/sources/tnztools/assistants/replicatorjitter.cpp
new file mode 100644
index 0000000..befb329
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/replicatorjitter.cpp
@@ -0,0 +1,149 @@
+
+
+// TnzTools includes
+#include <tools/replicator.h>
+#include <tools/modifiers/modifierjitter.h>
+
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TReplicatorJitter implementation
+//*****************************************************************************************
+
+class TReplicatorJitter final : public TReplicator {
+  Q_DECLARE_TR_FUNCTIONS(TReplicatorJitter)
+public:
+  const TStringId m_idSkipFirst;
+  const TStringId m_idPeriod;
+  const TStringId m_idAmplitude;
+
+protected:
+  TAssistantPoint &m_center;
+
+public:
+  TReplicatorJitter(TMetaObject &object):
+    TReplicator(object),
+    m_idSkipFirst("skipFirst"),
+    m_idPeriod("period"),
+    m_idAmplitude("m_idAmplitude"),
+    m_center( addPoint("center", TAssistantPoint::CircleCross) )
+  {
+    addProperty( createSpinProperty(m_idSkipFirst, getSkipFirst(), 0) );
+
+    TDoubleProperty *p;
+    
+    p = new TDoubleProperty(m_idPeriod.str(), 0.0, 1000, getPeriod());
+    p->setNonLinearSlider();
+    addProperty(p);
+
+    p = new TDoubleProperty(m_idAmplitude.str(), 0.0, 1000, getAmplitude());
+    p->setNonLinearSlider();
+    addProperty(p);
+  }
+
+  
+  static QString getLocalName()
+    { return tr("Jitter"); }
+
+    
+  void updateTranslation() const override {
+    TReplicator::updateTranslation();
+    setTranslation(m_idSkipFirst, tr("Skip First Tracks"));
+    setTranslation(m_idPeriod, tr("Period"));
+    setTranslation(m_idAmplitude, tr("Amplitude"));
+  }
+
+  
+  inline int getSkipFirst() const
+    { return (int)data()[m_idSkipFirst].getDouble(); }
+  inline double getPeriod() const
+    { return data()[m_idPeriod].getDouble(); }
+  inline double getAmplitude() const
+    { return data()[m_idAmplitude].getDouble(); }
+
+protected:
+  inline void setSkipFirst(int x)
+    { if (getSkipFirst() != (double)x) data()[m_idSkipFirst].setDouble((double)x); }
+  inline void setPeriod(double x)
+    { if (getPeriod() != x) data()[m_idPeriod].setDouble(x); }
+  inline void setAmplitude(double x)
+    { if (getAmplitude() != x) data()[m_idAmplitude].setDouble(x); }
+
+    
+  void onSetDefaults() override {
+    setPeriod(30);
+    setAmplitude(10);
+    TReplicator::onSetDefaults();
+  }
+
+  
+  void onFixData() override {
+    TReplicator::onFixData();
+    setPeriod( std::max(0.0, std::min(1000.0, getPeriod())) );
+    setAmplitude( std::max(0.0, std::min(1000.0, getAmplitude())) );
+  }
+
+  
+  double getScale(const TAffine &a) const {
+    return sqrt( a.a11*a.a11 + a.a12*a.a12
+               + a.a21*a.a21 + a.a22*a.a22 )/2;
+  }
+    
+public:
+  
+  void getPoints(const TAffine &toTool, PointList &points) const override {
+    int skipFirst = getSkipFirst();
+    if (skipFirst < 0) skipFirst = 0;
+    if (skipFirst >= (int)points.size()) return;
+    
+    double scale = getScale(toTool);
+    double period = getPeriod()*scale;
+    double amplitude = getAmplitude()*scale;
+    if (!(period > TConsts::epsilon && amplitude > TConsts::epsilon)) {
+      int seedX = 0;
+      int seedY = 7722441;
+      for(PointList::iterator i = points.begin() + skipFirst; i != points.end(); ++i) {
+        i->x += TModifierJitter::func(seedX, 0)*amplitude;
+        i->y += TModifierJitter::func(seedY, 0)*amplitude;
+        ++seedX, ++seedY;
+      }
+    }
+  }
+  
+  
+  void getModifiers(
+    const TAffine &toTool,
+    TInputModifier::List &outModifiers ) const override
+  {
+    double scale = getScale(toTool);
+    outModifiers.push_back(new TModifierJitter(
+      getPeriod()*scale,
+      getAmplitude()*scale,
+      getSkipFirst() ));
+  }
+
+  
+  void draw(TToolViewer*, bool enabled) const override {
+    double alpha = getDrawingAlpha(enabled);
+    double pixelSize = sqrt(tglGetPixelSize2());
+    
+    TPointD c = m_center.position;
+    double h = getPeriod()/2;
+    double q = h/2;
+    double a = getAmplitude();
+    
+    drawSegment(TPointD(c.x-h, c.y  ), TPointD(c.x-q, c.y+a), pixelSize, alpha);
+    drawSegment(TPointD(c.x-q, c.y+a), TPointD(c.x+q, c.y-a), pixelSize, alpha);
+    drawSegment(TPointD(c.x+q, c.y-a), TPointD(c.x+h, c.y  ), pixelSize, alpha);
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TReplicatorJitter> replicatorJitter("replicatorJitter");
diff --git a/toonz/sources/tnztools/assistants/replicatormirror.cpp b/toonz/sources/tnztools/assistants/replicatormirror.cpp
new file mode 100644
index 0000000..0c608c8
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/replicatormirror.cpp
@@ -0,0 +1,161 @@
+
+
+// TnzTools includes
+#include <tools/replicator.h>
+#include <tools/modifiers/modifierclone.h>
+#include <tools/assistants/guidelineline.h>
+
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TReplicatorMirror implementation
+//*****************************************************************************************
+
+class TReplicatorMirror final : public TReplicator {
+  Q_DECLARE_TR_FUNCTIONS(TReplicatorMirror)
+public:
+  const TStringId m_idDiscreteAngle;
+  const TStringId m_idPressure;
+
+protected:
+  TAssistantPoint &m_a;
+  TAssistantPoint &m_b;
+
+public:
+  TReplicatorMirror(TMetaObject &object):
+    TReplicator(object),
+    m_idDiscreteAngle("discreteAngle"),
+    m_idPressure("pressure"),
+    m_a( addPoint("a", TAssistantPoint::CircleCross) ),
+    m_b( addPoint("b", TAssistantPoint::Circle, TPointD(0, -50)) )
+  {
+    addProperty( new TBoolProperty(m_idDiscreteAngle.str(),  getDiscreteAngle()) );
+    addProperty( new TDoubleProperty(m_idPressure.str(), 0.0, 2.0, getPressure()) );
+  }
+
+  
+  static QString getLocalName()
+    { return tr("Replicator Mirror"); }
+
+    
+  void updateTranslation() const override {
+    TReplicator::updateTranslation();
+    setTranslation(m_idDiscreteAngle, tr("Discrete Angle"));
+    setTranslation(m_idPressure, tr("Pressure"));
+  }
+
+  
+  inline bool getDiscreteAngle() const
+    { return data()[m_idDiscreteAngle].getBool(); }
+  inline double getPressure() const
+    { return data()[m_idPressure].getDouble(); }
+
+protected:
+  inline void setPressure(double x)
+    { if (getPressure() != x) data()[m_idPressure].setDouble(x); }
+
+    
+  void onSetDefaults() override {
+    setPressure(1);
+    TReplicator::onSetDefaults();
+  }
+
+  
+  void onFixData() override {
+    TReplicator::onFixData();
+    setPressure( std::max(0.0, std::min(2.0, getPressure())) );
+  }
+
+
+  TPointD fixB() const {
+    TPointD b = m_b.position;
+    
+    if (getDiscreteAngle()) {
+      TPointD d = b - m_a.position;
+      double l = norm2(d);
+      if (l > TConsts::epsilon*TConsts::epsilon) {
+        l = sqrt(l);
+        double angle = atan2(d.y, d.x);
+        angle = round(angle*4/M_PI)*M_PI/4;
+        b.x = cos(angle)*l + m_a.position.x;
+        b.y = sin(angle)*l + m_a.position.y;
+      }
+    }
+    
+    if (areAlmostEqual(b, m_a.position))
+      b = m_a.position + TPointD(1, 0);
+    
+    return b;
+  }
+  
+  
+  TAffine getAffine(const TAffine &toTool = TAffine()) const {
+    TPointD c = toTool*m_a.position;
+    TPointD x = toTool*fixB() - c;
+    TPointD y(-x.y, x.x);
+    
+    TAffine t0( x.x,  y.x, c.x,
+                x.y,  y.y, c.y );
+    TAffine t1( x.x, -y.x, c.x,
+                x.y, -y.y, c.y );
+    return t1*t0.inv();
+  }
+  
+
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    if (&point == &m_a)
+      m_b.position += position - m_a.position;
+    point.position = position;
+  }
+  
+
+public:
+  int getMultipler() const override
+    { return 2; }
+  
+  
+  void getPoints(const TAffine &toTool, PointList &points) const override
+    { transformPoints(getAffine(toTool), points, (int)points.size()); }
+  
+  
+  void getModifiers(
+    const TAffine &toTool,
+    TInputModifier::List &outModifiers ) const override
+  {
+    TModifierClone *modifier = new TModifierClone();
+    modifier->transforms.push_back(TTrackTransform(
+      getAffine(toTool), getPressure() ));
+    outModifiers.push_back(modifier);
+  }
+
+  
+  void draw(TToolViewer*, bool enabled) const override {
+    TPointD p0 = m_a.position;
+    TPointD p1 = fixB();
+    
+    TAffine4 modelview, projection;
+    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+
+    TAffine matrix = (projection*modelview).get2d();
+    TPointD pp0 = matrix*p0;
+    TPointD pp1 = matrix*p1;
+    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+    if (!TGuidelineLine::truncateInfiniteLine(oneBox, pp0, pp1)) return;
+
+    double alpha = getDrawingAlpha(enabled);
+    double pixelSize = sqrt(tglGetPixelSize2());
+    TAffine matrixInv = matrix.inv();
+    drawSegment(matrixInv*pp0, matrixInv*pp1, pixelSize, alpha);
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TReplicatorMirror> replicatorMirror("replicatorMirror");
diff --git a/toonz/sources/tnztools/assistants/replicatorstar.cpp b/toonz/sources/tnztools/assistants/replicatorstar.cpp
new file mode 100644
index 0000000..f9821d7
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/replicatorstar.cpp
@@ -0,0 +1,230 @@
+
+
+// TnzTools includes
+#include <tools/replicator.h>
+#include <tools/modifiers/modifierclone.h>
+#include <tools/assistants/guidelineline.h>
+
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TReplicatorStar implementation
+//*****************************************************************************************
+
+class TReplicatorStar final : public TReplicator {
+  Q_DECLARE_TR_FUNCTIONS(TReplicatorStar)
+public:
+  const TStringId m_idDiscreteAngle;
+  const TStringId m_idMirror;
+  const TStringId m_idCount;
+
+protected:
+  TAssistantPoint &m_center;
+  TAssistantPoint &m_a;
+
+public:
+  TReplicatorStar(TMetaObject &object):
+    TReplicator(object),
+    m_idDiscreteAngle("discreteAngle"),
+    m_idMirror("mirror"),
+    m_idCount("count"),
+    m_center( addPoint("center", TAssistantPoint::CircleCross) ),
+    m_a     ( addPoint("a",      TAssistantPoint::Circle, TPointD(80, 0)) )
+  {
+    addProperty( new TBoolProperty(m_idDiscreteAngle.str(), getDiscreteAngle()) );
+    addProperty( new TBoolProperty(m_idMirror.str(),        getMirror()) );
+    addProperty( createCountProperty(m_idCount, getCount(), 2) );
+  }
+
+  
+  static QString getLocalName()
+    { return tr("Replicator Star"); }
+
+    
+  void updateTranslation() const override {
+    TReplicator::updateTranslation();
+    setTranslation(m_idDiscreteAngle, tr("Discrete Angle"));
+    setTranslation(m_idMirror, tr("Mirror"));
+    setTranslation(m_idCount, tr("Count"));
+  }
+
+  
+  inline bool getDiscreteAngle() const
+    { return data()[m_idDiscreteAngle].getBool(); }
+  inline bool getMirror() const
+    { return data()[m_idMirror].getBool(); }
+  inline int getCount() const
+    { return (int)data()[m_idCount].getDouble(); }
+
+protected:
+  inline void setCount(int x)
+    { if (getCount() != (double)x) data()[m_idCount].setDouble((double)x); }
+
+    
+  void onSetDefaults() override {
+    setCount(6);
+    TReplicator::onSetDefaults();
+  }
+
+  
+  void onFixData() override {
+    TReplicator::onFixData();
+    setCount( std::max(1, std::min(multiplierSoftLimit - 1, getCount())) );
+  }
+
+
+  TPointD fixA() const {
+    TPointD a = m_a.position;
+    
+    if (getDiscreteAngle()) {
+      TPointD d = a - m_center.position;
+      double l = norm2(d);
+      if (l > TConsts::epsilon*TConsts::epsilon) {
+        l = sqrt(l);
+        int count = getCount();
+        if (count > 0) {
+          double angle = atan2(d.y, d.x);
+          angle = round(angle*2*count/M_PI)*M_PI/(2*count);
+          a.x = cos(angle)*l + m_center.position.x;
+          a.y = sin(angle)*l + m_center.position.y;
+        }
+      }
+    }
+    
+    if (areAlmostEqual(a, m_center.position))
+      a = m_center.position + TPointD(1, 0);
+    
+    return a;
+  }
+
+  
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    if (&point == &m_center)
+      m_a.position += position - m_center.position;
+    point.position = position;
+  }
+  
+  
+public:
+  int getMultipler() const override
+    { return getCount() + 1; }
+  
+  
+  void getPoints(const TAffine &toTool, PointList &points) const override {
+    points.reserve(points.size() * getMultipler());
+    int pointsCount = (int)points.size();
+    
+    int count = getCount();
+    bool mirror = getMirror();
+
+    TPointD c = toTool*m_center.position;
+    TPointD x = toTool*fixA() - c;
+    TPointD y(-x.y, x.x);
+
+    TAffine t1( x.x,  y.x, c.x,
+                x.y,  y.y, c.y );
+    TAffine t2( x.x, -y.x, c.x,
+                x.y, -y.y, c.y );
+    
+    TAffine t0 = t1.inv();
+    TRotation r(360.0/getCount());
+                          
+    for(int i = 0; i < count; ++i) {
+      if (i)
+        transformPoints(t1*t0, points, pointsCount);
+      if (mirror) {
+        transformPoints(t2*t0, points, pointsCount);
+        t2 *= r;
+      }
+      t1 *= r;
+    }
+  }
+  
+  
+  void getModifiers(
+    const TAffine &toTool,
+    TInputModifier::List &outModifiers ) const override
+  {
+    int count = getCount();
+    bool mirror = getMirror();
+
+    TPointD c = toTool*m_center.position;
+    TPointD x = toTool*fixA() - c;
+    TPointD y(-x.y, x.x);
+
+    TAffine t1( x.x,  y.x, c.x,
+                x.y,  y.y, c.y );
+    TAffine t2( x.x, -y.x, c.x,
+                x.y, -y.y, c.y );
+    
+    TAffine t0 = t1.inv();
+    TRotation r(360.0/getCount());
+                          
+    TModifierClone *modifier = new TModifierClone();
+    for(int i = 0; i < count; ++i) {
+      if (i)
+        modifier->transforms.push_back(TTrackTransform(t1*t0));
+      if (mirror) {
+        modifier->transforms.push_back(TTrackTransform(t2*t0));
+        t2 *= r;
+      }
+      t1 *= r;
+    }
+    
+    outModifiers.push_back(modifier);
+  }
+
+  
+  void draw(TToolViewer*, bool enabled) const override {
+    double alpha = getDrawingAlpha(enabled);
+    double gridAlpha = getDrawingGridAlpha();
+    double pixelSize = sqrt(tglGetPixelSize2());
+
+    int count = getCount();
+    bool mirror = getMirror();
+
+    TPointD c = m_center.position;
+    TPointD a = fixA();
+    TPointD d = normalize(a - c);
+    
+    double spacing = 10*pixelSize;
+    double l = spacing*count/M_2PI;
+    
+    TPointD p0 = c + d*l;
+    TPointD p1 = p0 + d;
+    TRotation r(360.0/count);
+    
+    TAffine4 modelview, projection;
+    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+
+    TAffine matrix = (projection*modelview).get2d();
+    TAffine matrixInv = matrix.inv();
+    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+    
+    for(int i = 0; i < count; ++i) {
+      TPointD pp0 = matrix*p0;
+      TPointD pp1 = matrix*p1;
+      if (TGuidelineLine::truncateRay(oneBox, pp0, pp1))
+        drawSegment(matrixInv*pp0, matrixInv*pp1, pixelSize, i ? gridAlpha : alpha);
+      p0 = r*(p0 - c) + c;
+      p1 = r*(p1 - c) + c;
+    }
+    
+    TPointD p = TPointD(-d.y, d.x);
+    drawSegment( (mirror ? a+p*15 : a), a-p*15, pixelSize, alpha );
+    
+    drawSegment(c - d*10, c + d*10, pixelSize, alpha);
+    drawSegment(c - p*10, c + p*10, pixelSize, alpha);
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TReplicatorStar> replicatorStar("replicatorStar");
diff --git a/toonz/sources/tnztools/editassistantstool.cpp b/toonz/sources/tnztools/editassistantstool.cpp
index ee3cfc2..2c98f9e 100644
--- a/toonz/sources/tnztools/editassistantstool.cpp
+++ b/toonz/sources/tnztools/editassistantstool.cpp
@@ -5,6 +5,7 @@
 #include <tools/toolhandle.h>
 #include <tools/cursors.h>
 #include <tools/assistant.h>
+#include <tools/replicator.h>
 #include <tools/inputmanager.h>
 
 // TnzLib includes
@@ -32,6 +33,64 @@
 //-------------------------------------------------------------------
 
 //=============================================================================
+// Edit Assistants Swap Undo
+//-----------------------------------------------------------------------------
+
+class EditAssistantsReorderUndo final : public ToolUtils::TToolUndo {
+private:
+  int m_oldPos;
+  int m_newPos;
+
+public:
+  EditAssistantsReorderUndo(
+    TXshSimpleLevel *level,
+    const TFrameId &frameId,
+    int m_oldPos,
+    int m_newPos
+  ):
+    ToolUtils::TToolUndo(level, frameId),
+    m_oldPos(m_oldPos),
+    m_newPos(m_newPos)
+  { }
+
+  QString getToolName() override
+    { return QString("Edit Assistants Tool"); }
+
+  int getSize() const override
+    { return sizeof(*this); }
+
+  void process(int oldPos, int newPos) const {
+    if (oldPos == newPos || oldPos < 0 || newPos < 0)
+      return;
+    TMetaImage *metaImage = dynamic_cast<TMetaImage*>(m_level->getFrame(m_frameId, true).getPointer());
+    if (!metaImage)
+      return;
+    
+    TMetaImage::Writer writer(*metaImage);
+    TMetaObjectList &list = *writer;
+    if (oldPos >= list.size() || newPos >= writer->size())
+      return;
+    
+    TMetaObjectP obj = list[oldPos];
+    list.erase(list.begin() + oldPos);
+    list.insert(list.begin() + newPos, obj);
+  }
+
+  void undo() const override {
+    process(m_newPos, m_oldPos);
+    TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
+    notifyImageChanged();
+  }
+
+  void redo() const override {
+    process(m_oldPos, m_newPos);
+    TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
+    notifyImageChanged();
+  }
+};
+
+
+//=============================================================================
 // Edit Assistants Undo
 //-----------------------------------------------------------------------------
 
@@ -64,7 +123,7 @@ public:
     m_metaObject(metaObject),
     m_oldData(oldData),
     m_newData(m_metaObject->data()),
-    m_size(m_oldData.getMemSize() + m_newData.getMemSize())
+    m_size(sizeof(*this) + m_oldData.getMemSize() + m_newData.getMemSize())
   { }
 
   int getSize() const override
@@ -144,6 +203,7 @@ protected:
   TPropertyGroup m_allProperties;
   TPropertyGroup m_toolProperties;
   TEnumProperty m_assistantType;
+  TIntProperty m_replicatorIndex;
   TStringId m_newAssisnantType;
 
   bool           m_dragging;
@@ -158,16 +218,18 @@ protected:
   TPointD        m_currentPointOffset;
   TPointD        m_currentPosition;
   TGuidelineList m_currentGuidelines;
+  
+  TReplicator::PointList m_replicatorPoints;
+  
+  TMetaImage::Reader   *m_reader;
+  TMetaImage           *m_readImage;
+  TMetaObjectPC         m_readObject;
+  const TAssistantBase *m_readAssistant;
 
-  TMetaImage::Reader *m_reader;
-  TMetaImage         *m_readImage;
-  TMetaObjectPC       m_readObject;
-  const TAssistant   *m_readAssistant;
-
-  TMetaImage::Writer *m_writer;
-  TMetaImage         *m_writeImage;
-  TMetaObjectP        m_writeObject;
-  TAssistant         *m_writeAssistant;
+  TMetaImage::Writer   *m_writer;
+  TMetaImage           *m_writeImage;
+  TMetaObjectP          m_writeObject;
+  TAssistantBase       *m_writeAssistant;
 
   Selection *selection;
   
@@ -180,6 +242,7 @@ public:
   EditAssistantsTool():
     TTool("T_EditAssistants"),
     m_assistantType("AssistantType"),
+    m_replicatorIndex("ReplicatorIndex", 1, 10, 1, false),
     m_dragging(),
     m_dragAllPoints(),
     m_currentAssistantCreated(),
@@ -196,6 +259,7 @@ public:
     // also assign assistants to the "brush" button in toolbar
     bind(MetaImage | EmptyTarget, "T_Brush");
     m_toolProperties.bind(m_assistantType);
+    m_replicatorIndex.setSpinner();
     updateTranslation();
   }
 
@@ -207,7 +271,7 @@ public:
   ToolType getToolType() const override
     { return TTool::LevelWriteTool; }
   unsigned int getToolHints() const override
-    { return TTool::getToolHints() & ~HintAssistantsAll; }
+    { return TTool::getToolHints() & ~(HintAssistantsAll | HintReplicatorsAll); }
   int getCursorId() const override
     { return ToolCursor::StrokeSelectCursor; }
   void onImageChanged() override {
@@ -239,6 +303,10 @@ public:
       m_allProperties.bind( *m_toolProperties.getProperty(i) );
     if (Closer closer = read(ModeAssistant)) {
       m_readAssistant->updateTranslation();
+      if (int i = readReplicatorIndex(*m_reader)) {
+        m_replicatorIndex.setValue(i);
+        m_allProperties.bind( m_replicatorIndex );
+      }
       TPropertyGroup &assistantProperties = m_readAssistant->getProperties();
       for(int i = 0; i < assistantProperties.getPropertyCount(); ++i)
         m_allProperties.bind( *assistantProperties.getProperty(i) );
@@ -256,15 +324,18 @@ public:
 
   void updateTranslation() override {
     m_assistantType.setQStringName( tr("Assistant Type") );
+    m_replicatorIndex.setQStringName( tr("Order") );
     updateAssistantTypes();
     if (Closer closer = read(ModeAssistant))
       m_readAssistant->updateTranslation();
   }
 
   bool onPropertyChanged(std::string name, bool addToUndo) override {
-    if (TProperty *property = m_toolProperties.getProperty(name)) {
-      if (name == m_assistantType.getName())
-        m_newAssisnantType = TStringId::find( to_string(m_assistantType.getValue()) );
+    if (m_replicatorIndex.getName() == name) {
+      writeReplicatorIndex(m_replicatorIndex.getValue());
+    } else
+    if (name == m_assistantType.getName()) {
+      m_newAssisnantType = TStringId::find( to_string(m_assistantType.getValue()) );
     } else {
       if (Closer closer = write(ModeAssistant, true))
         m_writeAssistant->propertyChanged(TStringId::find(name));
@@ -324,7 +395,7 @@ protected:
         && (**m_reader)[m_currentAssistantIndex] == m_currentAssistant )
       {
         m_readObject = (**m_reader)[m_currentAssistantIndex];
-        m_readAssistant = m_readObject->getHandler<TAssistant>();
+        m_readAssistant = m_readObject->getHandler<TAssistantBase>();
         if (mode == ModeAssistant) return true;
 
         if (m_readAssistant->findPoint(m_currentPointName)) {
@@ -360,7 +431,7 @@ protected:
         && (**m_writer)[m_currentAssistantIndex] == m_currentAssistant )
       {
         m_writeObject = (**m_writer)[m_currentAssistantIndex];
-        m_writeAssistant = m_writeObject->getHandler<TAssistant>();
+        m_writeAssistant = m_writeObject->getHandler<TAssistantBase>();
         if ( (mode == ModeAssistant)
           || (mode == ModePoint && m_writeAssistant->findPoint(m_currentPointName)) )
         {
@@ -416,7 +487,7 @@ protected:
     if (Closer closer = read(ModeImage)) {
       for(TMetaObjectListCW::iterator i = (*m_reader)->begin(); i != (*m_reader)->end(); ++i)
         if (*i)
-          if (const TAssistant *assistant = (*i)->getHandler<TAssistant>())
+          if (const TAssistantBase *assistant = (*i)->getHandler<TAssistantBase>())
             assistant->deselectAll();
       if (updateHistory) putHistory(m_readImage, -1);
     }
@@ -451,7 +522,7 @@ protected:
       for(TMetaObjectListCW::iterator i = (*m_reader)->begin(); i != (*m_reader)->end(); ++i) {
         if (!*i) continue;
 
-        const TAssistant *assistant = (*i)->getHandler<TAssistant>();
+        const TAssistantBase *assistant = (*i)->getHandler<TAssistantBase>();
         if (!assistant) continue;
 
         assistant->deselectAll();
@@ -514,6 +585,76 @@ protected:
     return success;
   }
   
+  int readReplicatorIndex(TMetaImage::Reader &reader) {
+    int cnt = (int)reader->size();
+    int index = 0;
+    for(int i = 0; i < cnt; ++i) {
+      if (const TMetaObjectPC &obj = (*reader)[i])
+      if (obj->getHandler<TReplicator>()) {
+        ++index;
+        if (m_currentAssistantIndex == i)
+          return index;
+      }
+    }
+    return 0;
+  }
+
+  void writeReplicatorIndex(int index) {
+    apply();
+    
+    int wantIndex = index;
+    int oldPos = -1;
+    int newPos = -1;
+    bool changed = false;
+    
+    if (Closer closer = write(ModeAssistant)) {
+      if (index < 1)
+        index = 1;
+      
+      int idx = 0;
+      int lastPos = -1;
+      
+      TMetaObjectList &list = **m_writer;
+      
+      int cnt = (int)list.size();
+      for(int i = 0; i < cnt; ++i) {
+        if (list[i] && list[i]->getHandler<TReplicator>()) {
+          ++idx;
+          if (m_currentAssistantIndex == i) oldPos = i;
+          if (idx == index) newPos = i;
+          lastPos = i;
+        }
+      }
+      
+      if (oldPos >= 0 && lastPos >= 0) {
+        assert(idx);
+        if (newPos < 0)
+          { index = idx; newPos = lastPos; }
+        if (oldPos != newPos) {
+          TMetaObjectP obj = list[oldPos];
+          list.erase(list.begin() + oldPos);
+          list.insert(list.begin() + newPos, obj);
+          
+          TUndoManager::manager()->add(new EditAssistantsReorderUndo(
+            getApplication()->getCurrentLevel()->getLevel()->getSimpleLevel(),
+            getCurrentFid(),
+            oldPos,
+            newPos ));
+          
+          changed = true;
+        }
+      }
+    }
+    
+    if (changed) {
+      m_currentAssistantIndex = newPos;
+      invalidate();
+    }
+    
+    if (wantIndex != index)
+      this->updateOptionsBox();
+  }
+  
 public:
   void deselect()
     { resetCurrentPoint(true, false); }
@@ -561,7 +702,7 @@ public:
       resetCurrentPoint(false);
       if (Closer closer = write(ModeImage)) {
         TMetaObjectP object(new TMetaObject(m_newAssisnantType));
-        if (TAssistant *assistant = object->getHandler<TAssistant>()) {
+        if (TAssistantBase *assistant = object->getHandler<TAssistantBase>()) {
           assistant->setDefaults();
           assistant->move(position);
           assistant->selectAll();
@@ -639,18 +780,28 @@ public:
     TPointD position = m_currentPosition + m_currentPointOffset;
     
     // draw assistants
+    int index = 0;
     if (Closer closer = read(ModeImage))
     for(TMetaObjectListCW::iterator i = (*m_reader)->begin(); i != (*m_reader)->end(); ++i)
       if (*i)
-      if (const TAssistant *assistant = (*i)->getHandler<TAssistant>())
+      if (const TAssistantBase *base = (*i)->getHandler<TAssistantBase>())
       {
-        assistant->drawEdit(getViewer());
-        assistant->getGuidelines(
-          position,
-          TAffine(),
-          m_currentGuidelines );
+        if (dynamic_cast<const TReplicator*>(base)) {
+          ++index;
+          base->drawEdit(getViewer(), index);
+        } else {
+          base->drawEdit(getViewer());
+        }
+        
+        if (const TAssistant *assistant = dynamic_cast<const TAssistant*>(base))
+          assistant->getGuidelines(
+            position,
+            TAffine(),
+            m_currentGuidelines );
       }
     
+    TImage *img = getImage(false);
+    
     // draw assistans and guidelines from other layers
     TAssistant::scanAssistants(
       this,          // tool
@@ -660,6 +811,30 @@ public:
       false,         // enabled only
       false,         // mark enabled
       true,          // draw guidelines
+      img );         // skip image
+    
+    // draw replicators from other layers
+    TReplicator::scanReplicators(
+      this,          // tool
+      nullptr,       // in/out points
+      nullptr,       // out modifiers
+      true,          // draw
+      false,         // enabled only
+      false,         // mark enabled
+      false,         // draw points
+      img );         // skip image
+    
+    // draw replicator points
+    m_replicatorPoints.clear();
+    m_replicatorPoints.push_back(position);
+    TReplicator::scanReplicators(
+      this,          // tool
+      &m_replicatorPoints, // in/out points
+      nullptr,       // out modifiers
+      false,         // draw
+      false,         // enabled only
+      false,         // mark enabled
+      true,          // draw points
       nullptr );     // skip image
   }
 };
diff --git a/toonz/sources/tnztools/fullcolorbrushtool.cpp b/toonz/sources/tnztools/fullcolorbrushtool.cpp
index d764b82..cec1fd5 100644
--- a/toonz/sources/tnztools/fullcolorbrushtool.cpp
+++ b/toonz/sources/tnztools/fullcolorbrushtool.cpp
@@ -8,6 +8,7 @@
 #include "tools/toolutils.h"
 #include "tools/toolhandle.h"
 #include "tools/tooloptions.h"
+#include "tools/replicator.h"
 
 #include "mypainttoonzbrush.h"
 
@@ -167,6 +168,18 @@ FullColorBrushTool::FullColorBrushTool(std::string name)
   m_pressure.setId("PressureSensitivity");
 }
 
+//-------------------------------------------------------------------------------------------------------
+
+unsigned int FullColorBrushTool::getToolHints() const {
+  unsigned int h = TTool::getToolHints() & ~HintAssistantsAll;
+  if (m_assistants.getValue()) {
+    h |= HintReplicators;
+    h |= HintReplicatorsPoints;
+    h |= HintReplicatorsEnabled;
+  }
+  return h;
+}
+
 //---------------------------------------------------------------------------------------------------
 
 ToolOptionsBox *FullColorBrushTool::createOptionsBox() {
@@ -229,6 +242,7 @@ void FullColorBrushTool::onActivate() {
 
   setWorkAndBackupImages();
   onColorStyleChanged();
+  updateModifiers();
 }
 
 //--------------------------------------------------------------------------------------------------
@@ -304,10 +318,14 @@ bool FullColorBrushTool::askWrite(const TRect &rect) {
 
 //--------------------------------------------------------------------------------------------------
 
-bool FullColorBrushTool::preLeftButtonDown() {
+void FullColorBrushTool::updateModifiers() {
   m_modifierAssistants->magnetism = m_assistants.getValue() ? 1 : 0;
   m_inputmanager.drawPreview      = false; //!m_modifierAssistants->drawOnly;
 
+  m_modifierReplicate.clear();
+  if (m_assistants.getValue())
+    TReplicator::scanReplicators(this, nullptr, &m_modifierReplicate, false, true, false, false, nullptr);
+  
   m_inputmanager.clearModifiers();
   m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
   m_inputmanager.addModifier(
@@ -315,9 +333,15 @@ bool FullColorBrushTool::preLeftButtonDown() {
 #ifndef NDEBUG
   m_inputmanager.addModifier(TInputModifierP(m_modifierTest.getPointer()));
 #endif
+  m_inputmanager.addModifiers(m_modifierReplicate);
   m_inputmanager.addModifier(
       TInputModifierP(m_modifierSegmentation.getPointer()));
+}
 
+//--------------------------------------------------------------------------------------------------
+
+bool FullColorBrushTool::preLeftButtonDown() {
+  updateModifiers();
   touchImage();
 
   if (m_isFrameCreated) {
@@ -347,6 +371,7 @@ void FullColorBrushTool::handleMouseEvent(MouseEventType type,
     m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
     m_inputmanager.addModifier(
         TInputModifierP(m_modifierAssistants.getPointer()));
+    m_inputmanager.addModifiers(m_modifierReplicate);
     m_inputmanager.addModifier(
         TInputModifierP(m_modifierSegmentation.getPointer()));
     m_inputmanager.drawPreview = true;
diff --git a/toonz/sources/tnztools/fullcolorbrushtool.h b/toonz/sources/tnztools/fullcolorbrushtool.h
index dd9b831..16ce37f 100644
--- a/toonz/sources/tnztools/fullcolorbrushtool.h
+++ b/toonz/sources/tnztools/fullcolorbrushtool.h
@@ -61,8 +61,7 @@ public:
 
   ToolType getToolType() const override
     { return TTool::LevelWriteTool; }
-  unsigned int getToolHints() const override
-    { return TTool::getToolHints() & ~HintAssistantsAll; }
+  unsigned int getToolHints() const override;
 
   ToolOptionsBox *createOptionsBox() override;
 
@@ -115,6 +114,8 @@ public:
   TMyPaintBrushStyle *getBrushStyle();
 
 private:
+  void updateModifiers();
+  
   enum MouseEventType { ME_DOWN, ME_DRAG, ME_UP, ME_MOVE };
   void handleMouseEvent(MouseEventType type, const TPointD &pos,
                         const TMouseEvent &e);
@@ -128,6 +129,7 @@ protected:
   TSmartPointerT<TModifierTangents> m_modifierTangents;
   TSmartPointerT<TModifierAssistants> m_modifierAssistants;
   TSmartPointerT<TModifierSegmentation> m_modifierSegmentation;
+  TInputModifier::List m_modifierReplicate;
 
   TPropertyGroup m_prop;
 
diff --git a/toonz/sources/tnztools/modifiers/modifierclone.cpp b/toonz/sources/tnztools/modifiers/modifierclone.cpp
new file mode 100644
index 0000000..05d96b8
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifierclone.cpp
@@ -0,0 +1,75 @@
+
+#include <tools/modifiers/modifierclone.h>
+
+
+//*****************************************************************************************
+//    TModifierClone::Interpolator implementation
+//*****************************************************************************************
+
+TTrackPoint TModifierClone::Interpolator::interpolateFromOriginal(double originalIndex)
+  { return transform.apply( track.calcPointFromOriginal(originalIndex) ); }
+TTrackPoint TModifierClone::Interpolator::interpolate(double index)
+  { return interpolateFromOriginal(track.originalIndexByIndex(index)); }
+
+
+//*****************************************************************************************
+//    TModifierClone implementation
+//*****************************************************************************************
+
+TModifierClone::TModifierClone(bool keepOriginals):
+  keepOriginals(keepOriginals) { }
+
+void TModifierClone::modifyTrack(const TTrack &track,
+                                 TTrackList &outTracks)
+{
+  if (!track.handler) {
+    Handler *handler = new Handler();
+    track.handler = handler;
+    if (keepOriginals) {
+      handler->original = new TTrack(track);
+      new TTrackIntrOrig(*handler->original);
+    }
+    for(TTrackTransformList::const_iterator i = transforms.begin(); i != transforms.end(); ++i) {
+      handler->tracks.push_back(new TTrack(track));
+      TTrack &subTrack = *handler->tracks.back();
+      new Interpolator(subTrack, *i);
+    }
+  }
+  
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return;
+  
+  if (handler->original)
+    outTracks.push_back(handler->original);
+  outTracks.insert(outTracks.end(), handler->tracks.begin(), handler->tracks.end());
+  if (!track.changed())
+    return;
+
+  int start = track.size() - track.pointsAdded;
+  if (start < 0) start = 0;
+
+  // process original
+  if (handler->original) {
+    TTrack &subTrack = *handler->original;
+    subTrack.truncate(start);
+    for (int i = start; i < track.size(); ++i)
+      subTrack.push_back(subTrack.pointFromOriginal(i), false);
+    subTrack.fix_to(track.fixedSize());
+  }
+  
+  // process sub-tracks
+  for(TTrackList::iterator i = handler->tracks.begin(); i != handler->tracks.end(); ++i) {
+    TTrack &subTrack = **i;
+    Interpolator *intr = dynamic_cast<Interpolator*>(subTrack.getInterpolator().getPointer());
+    if (!intr)
+      continue;
+    subTrack.truncate(start);
+    for (int i = start; i < track.size(); ++i)
+      subTrack.push_back(intr->interpolateFromOriginal(i), false);
+    subTrack.fix_to(track.fixedSize());
+  }
+  
+  track.resetChanges();
+}
+
diff --git a/toonz/sources/tnztools/modifiers/modifierjitter.cpp b/toonz/sources/tnztools/modifiers/modifierjitter.cpp
new file mode 100644
index 0000000..292bb82
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifierjitter.cpp
@@ -0,0 +1,172 @@
+
+#include <tools/modifiers/modifierjitter.h>
+
+
+
+//*****************************************************************************************
+//    static functions
+//*****************************************************************************************
+
+
+namespace {
+
+class Jitter {
+public:
+  static inline double randomNext(unsigned int &seed) {
+    static const unsigned int max = 32767;
+    static const double k = 1.0/max;
+    seed = ((1103515245*seed + 12345) >> 16) & max;
+    return seed*k;
+  }
+
+  static TPointD getPoint(unsigned int seed, int i, double *prevX = nullptr) {
+    unsigned int pseed = seed^i;
+    double dx = randomNext(pseed);
+    double dy = randomNext(pseed)*2 - 1;
+    if (dx < 0.5) {
+      double px = prevX ? *prevX : getPoint(seed, i-1).x;
+      px += 0.5 - i;
+      if (dx < px)
+        dx = randomNext(pseed)*(1 - px) + px;
+    }
+    return TPointD(dx + i, dy);
+  }
+
+  static inline double spline(double x, TPointD *points) {
+    double x0 = points[1].x;
+    double y0 = points[1].y;
+    double x1 = points[2].x;
+    double y1 = points[2].y;
+    double t0 = (y1 - points[0].y)/(x1 - points[0].x)*(x1 - x0);
+    double t1 = (points[3].y - y0)/(points[3].x - x0)*(x1 - x0);
+
+    double l = (x - x0)/(x1 - x0);
+    double ll = l*l;
+    double lll = ll*l;
+
+    return y0*( 2*lll - 3*ll + 1)
+         + y1*(-2*lll + 3*ll    )
+         + t0*(   lll - 2*ll + l)
+         + t1*(   lll -   ll    );
+  }
+
+  static inline double func(unsigned int seed, double x) {
+    int i = (int)floor(x);
+    TPointD points[5];
+    points[0] = getPoint(seed, i-2);
+    for(int j = 1; j < 5; ++j)
+      points[j] = getPoint(seed, i-2+j, &points[j-1].x);
+    return spline(x, &points[ x < points[2].x ? 0 : 1 ]);
+  }
+};
+
+static inline unsigned int trackSeedX(const TTrack &track) {
+  unsigned int seed = track.id;
+  Jitter::randomNext(seed);
+  return seed;
+}
+
+static inline unsigned int trackSeedY(const TTrack &track) {
+  unsigned int seed = track.id^32143;
+  Jitter::randomNext(seed);
+  return seed;
+}
+
+} // namespace
+
+
+
+//*****************************************************************************************
+//    TModifierJitter::Interpolator implementation
+//*****************************************************************************************
+
+
+TModifierJitter::Interpolator::Interpolator(TTrack &track, double period, double amplitude):
+  TTrackInterpolator(track),
+  seedX(trackSeedX(track)),
+  seedY(trackSeedY(track)),
+  frequency(fabs(period) > TConsts::epsilon ? 1/period : 0),
+  amplitude(fabs(period) > TConsts::epsilon ? amplitude : 0)
+  { }
+
+
+TTrackPoint TModifierJitter::Interpolator::interpolateFromOriginal(double originalIndex) {
+  TTrackPoint p = track.calcPointFromOriginal(originalIndex);
+  double l = p.length*frequency;
+  p.position.x += Jitter::func(seedX, l)*amplitude;
+  p.position.y += Jitter::func(seedY, l)*amplitude;
+  return p;
+}
+
+
+TTrackPoint TModifierJitter::Interpolator::interpolate(double index)
+  { return interpolateFromOriginal(track.originalIndexByIndex(index)); }
+
+
+ 
+//*****************************************************************************************
+//    TModifierJitter implementation
+//*****************************************************************************************
+
+
+TModifierJitter::TModifierJitter(double period, double amplitude, int skipFirst):
+  period(period), amplitude(amplitude), skipFirst(skipFirst) { }
+
+
+void TModifierJitter::modifyTrack(const TTrack &track,
+                                  TTrackList &outTracks)
+{
+  if (!track.handler && fabs(period) > TConsts::epsilon) {
+    Handler *handler = new Handler();
+    track.handler = handler;
+    handler->track = new TTrack(track);
+    new Interpolator(*handler->track, period, amplitude);
+  }
+  
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return TInputModifier::modifyTrack(track, outTracks);
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+  
+  if (!track.changed())
+    return;
+
+  Interpolator *intr = dynamic_cast<Interpolator*>(subTrack.getInterpolator().getPointer());
+  if (!intr)
+    return;
+
+  int start = track.size() - track.pointsAdded;
+  if (start < 0) start = 0;
+
+  // process sub-track
+  subTrack.truncate(start);
+  for (int i = start; i < track.size(); ++i)
+    subTrack.push_back(intr->interpolateFromOriginal(i), false);
+  
+  // fit points
+  subTrack.fix_to(track.fixedSize());
+  
+  track.resetChanges();
+}
+
+
+void
+TModifierJitter::modifyTracks(
+    const TTrackList &tracks,
+    TTrackList &outTracks )
+{
+  int cnt = std::min( std::max(0, skipFirst), (int)tracks.size() );
+  TTrackList::const_iterator split = tracks.begin() + cnt;
+  for(TTrackList::const_iterator i = tracks.begin(); i != split; ++i)
+    TInputModifier::modifyTrack(**i, outTracks);
+  for(TTrackList::const_iterator i = split; i != tracks.end(); ++i)
+    modifyTrack(**i, outTracks);
+}
+
+
+double TModifierJitter::func(unsigned int seed, double x)
+  { return Jitter::func(seed, x); }
+
+  
diff --git a/toonz/sources/tnztools/replicator.cpp b/toonz/sources/tnztools/replicator.cpp
new file mode 100644
index 0000000..6e63cb6
--- /dev/null
+++ b/toonz/sources/tnztools/replicator.cpp
@@ -0,0 +1,187 @@
+
+#include <tools/replicator.h>
+
+#include <tools/tool.h>
+
+#include <toonz/tapplication.h>
+#include <toonz/txsheet.h>
+#include <toonz/txsheethandle.h>
+#include <toonz/txshlevelhandle.h>
+#include <toonz/tframehandle.h>
+#include <toonz/tobjecthandle.h>
+#include <toonz/dpiscale.h>
+
+#include <tgl.h>
+
+
+
+const int TReplicator::multiplierSoftLimit = 32;
+const int TReplicator::multiplierLimit = 256;
+
+
+//************************************************************************
+//    TReplicator implementation
+//************************************************************************
+
+TReplicator::TReplicator(TMetaObject &object):
+  TAssistantBase(object) { }
+
+//---------------------------------------------------------------------------------------------------
+
+int
+TReplicator::getMultipler() const
+  { return 1; }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TReplicator::getModifiers(const TAffine&, TInputModifier::List&) const
+  { }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TReplicator::getPoints(const TAffine&, PointList&) const
+  { }
+
+//---------------------------------------------------------------------------------------------------
+
+TIntProperty*
+TReplicator::createCountProperty(const TStringId &id, int def, int min, int max) {
+  if (min < 0) min = 0;
+  if (def < min) def = min;
+  if (max <= 0) max = multiplierSoftLimit;
+  return createSpinProperty(id, def, min, max);
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TReplicator::transformPoints(const TAffine &aff, PointList &points, int count) {
+  points.reserve(points.size() + count);
+  for(int i = 0; i < count; ++i)
+    points.push_back(aff*points[i]);
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TReplicator::drawReplicatorPoints(const TPointD *points, int count) {
+  double colorBlack[4] = { 0.0, 0.0, 0.0, 0.3 };
+  double colorWhite[4] = { 1.0, 1.0, 1.0, 0.3 };
+  
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+  tglEnableBlending();
+  tglEnableLineSmooth(true, 1.0 * lineWidthScale);
+  
+  double pixelSize = sqrt(tglGetPixelSize2());
+  TPointD a(5*pixelSize, 0), da(0, 0.5*pixelSize);
+  TPointD b(0, 5*pixelSize), db(0.5*pixelSize, 0);
+  
+  for(int i = 0; i < count; ++i) {
+    const TPointD &p = points[i];
+    glColor4dv(colorWhite);
+    tglDrawSegment(p - a - da, p + a - da);
+    tglDrawSegment(p - b - db, p + b - db);
+    glColor4dv(colorBlack);
+    tglDrawSegment(p - a + da, p + a + da);
+    tglDrawSegment(p - b + db, p + b + db);
+  }
+
+  glPopAttrib();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+int
+TReplicator::scanReplicators(
+  TTool *tool,
+  PointList *inOutPoints,
+  TInputModifier::List *outModifiers,
+  bool draw,
+  bool enabledOnly,
+  bool markEnabled,
+  bool drawPoints,
+  TImage *skipImage )
+{
+  bool outOfLimit = false;
+  long long multiplier = 0;
+  int inputPoints = inOutPoints ? (int)inOutPoints->size() : 0;
+  
+  if (tool)
+  if (TToolViewer *viewer = tool->getViewer())
+  if (TApplication *application = tool->getApplication())
+  if (TXshLevelHandle *levelHandle = application->getCurrentLevel())
+  if (TXshLevel *level = levelHandle->getLevel())
+  if (TXshSimpleLevel *simpleLevel = level->getSimpleLevel())
+  if (TFrameHandle *frameHandle = application->getCurrentFrame())
+  if (TXsheetHandle *XsheetHandle = application->getCurrentXsheet())
+  if (TXsheet *Xsheet = XsheetHandle->getXsheet())
+  {
+    TPointD dpiScale = getCurrentDpiScale(simpleLevel, tool->getCurrentFid());
+    int frame = frameHandle->getFrame();
+    int count = Xsheet->getColumnCount();
+    TAffine worldToTrack;
+    if ( tool->getToolType() & TTool::LevelTool
+      && !application->getCurrentObject()->isSpline() )
+    {
+      worldToTrack.a11 /= dpiScale.x;
+      worldToTrack.a22 /= dpiScale.y;
+    }
+
+    for(int i = 0; i < count; ++i)
+      if (TXshColumn *column = Xsheet->getColumn(i))
+      if (column->isCamstandVisible())
+      if (column->isPreviewVisible())
+      if (TImageP image = Xsheet->getCell(frame, i).getImage(false))
+      if (image != skipImage)
+      if (image->getType() == TImage::META)
+      if (TMetaImage *metaImage = dynamic_cast<TMetaImage*>(image.getPointer()))
+      {
+        TAffine imageToTrack = worldToTrack * tool->getColumnMatrix(i);
+        if (draw) { glPushMatrix(); tglMultMatrix(imageToTrack); }
+
+        TMetaImage::Reader reader(*metaImage);
+        for(TMetaObjectListCW::iterator i = reader->begin(); i != reader->end(); ++i)
+          if (*i)
+          if (const TReplicator *replicator = (*i)->getHandler<TReplicator>())
+          if (!enabledOnly || replicator->getEnabled())
+          {
+            if (!multiplier) multiplier = 1;
+            bool enabled = replicator->getEnabled();
+            
+            if (enabled) {
+              int m = replicator->getMultipler();
+              if (m <= 0) m = 1;
+              if (multiplier*m > multiplierLimit) {
+                outOfLimit = true;
+              } else {
+                multiplier *= m;
+                if (inOutPoints)
+                  replicator->getPoints(imageToTrack, *inOutPoints);
+                if (outModifiers)
+                  replicator->getModifiers(imageToTrack, *outModifiers);
+              }
+            }
+            
+            if (draw) {
+              unsigned int oldFlags = TReplicator::drawFlags;
+              if (enabled && outOfLimit) TReplicator::drawFlags |= TReplicator::DRAW_ERROR;
+              replicator->draw(viewer, enabled && markEnabled);
+              TReplicator::drawFlags = oldFlags;
+            }
+          }
+
+        if (draw) glPopMatrix();
+      }
+  }
+  
+  
+  if (drawPoints && inOutPoints && (int)inOutPoints->size() > inputPoints)
+    drawReplicatorPoints(
+      inOutPoints->data() + inputPoints,
+      (int)inOutPoints->size() - inputPoints );
+  
+  return (int)multiplier;
+}
+
diff --git a/toonz/sources/tnztools/tool.cpp b/toonz/sources/tnztools/tool.cpp
index ac8bb24..1110f8e 100644
--- a/toonz/sources/tnztools/tool.cpp
+++ b/toonz/sources/tnztools/tool.cpp
@@ -178,7 +178,7 @@ TTool::TTool(std::string name)
 //-------------------------------------------------------------------
 
 unsigned int TTool::getToolHints() const
-  { return HintAssistants | HintGuidelines; }
+  { return HintAssistants | HintAssistantsGuidelines | HintReplicators; }
 
 //-------------------------------------------------------------------
 
diff --git a/toonz/sources/tnztools/tooloptionscontrols.cpp b/toonz/sources/tnztools/tooloptionscontrols.cpp
index f26ee4f..5857d47 100644
--- a/toonz/sources/tnztools/tooloptionscontrols.cpp
+++ b/toonz/sources/tnztools/tooloptionscontrols.cpp
@@ -267,13 +267,21 @@ ToolOptionIntSlider::ToolOptionIntSlider(TTool *tool, TIntProperty *property,
                                          ToolHandle *toolHandle)
     : IntField(0, property->isMaxRangeLimited())
     , ToolOptionControl(tool, property->getName(), toolHandle)
-    , m_property(property) {
+    , m_property(property)
+{
   setLinearSlider(property->isLinearSlider());
   m_property->addListener(this);
   TIntProperty::Range range = property->getRange();
   setRange(range.first, range.second);
-  setMaximumWidth(300);
-  setMinimumWidth(50);
+  if (property->isSpinner()) {
+    enableSlider(false);
+    enableSpinner(true);
+    setMinimumWidth(60);
+    setMaximumWidth(80);
+  } else {
+    setMinimumWidth(50);
+    setMaximumWidth(300);
+  }
   updateStatus();
   connect(this, SIGNAL(valueChanged(bool)), SLOT(onValueChanged(bool)));
   // synchronize the state with the same widgets in other tool option bars
diff --git a/toonz/sources/tnztools/toonzrasterbrushtool.cpp b/toonz/sources/tnztools/toonzrasterbrushtool.cpp
index b4ba5d5..4344e5b 100644
--- a/toonz/sources/tnztools/toonzrasterbrushtool.cpp
+++ b/toonz/sources/tnztools/toonzrasterbrushtool.cpp
@@ -6,6 +6,7 @@
 #include "tools/toolhandle.h"
 #include "tools/toolutils.h"
 #include "tools/tooloptions.h"
+#include "tools/replicator.h"
 
 // TnzQt includes
 #include "toonzqt/dvdialog.h"
@@ -791,6 +792,18 @@ ToonzRasterBrushTool::ToonzRasterBrushTool(std::string name, int targetType)
 
 //-------------------------------------------------------------------------------------------------------
 
+unsigned int ToonzRasterBrushTool::getToolHints() const {
+  unsigned int h = TTool::getToolHints() & ~HintAssistantsAll;
+  if (m_assistants.getValue()) {
+    h |= HintReplicators;
+    h |= HintReplicatorsPoints;
+    h |= HintReplicatorsEnabled;
+  }
+  return h;
+}
+
+//-------------------------------------------------------------------------------------------------------
+
 ToolOptionsBox *ToonzRasterBrushTool::createOptionsBox() {
   TPaletteHandle *currPalette =
       TTool::getApplication()->getPaletteController()->getCurrentLevelPalette();
@@ -1009,6 +1022,7 @@ void ToonzRasterBrushTool::onActivate() {
                                       m_hardness.getValue() * 0.01);
   setWorkAndBackupImages();
 
+  updateModifiers();
   m_brushTimer.start();
   // TODO:app->editImageOrSpline();
 }
@@ -1036,13 +1050,17 @@ bool ToonzRasterBrushTool::askWrite(const TRect &rect) {
   return true;
 }
 
-//--------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------
 
-bool ToonzRasterBrushTool::preLeftButtonDown() {
+void ToonzRasterBrushTool::updateModifiers() {
   int smoothRadius = (int)round(m_smooth.getValue());
   m_modifierAssistants->magnetism = m_assistants.getValue() ? 1 : 0;
   m_inputmanager.drawPreview      = false; //!m_modifierAssistants->drawOnly;
 
+  m_modifierReplicate.clear();
+  if (m_assistants.getValue())
+    TReplicator::scanReplicators(this, nullptr, &m_modifierReplicate, false, true, false, false, nullptr);
+  
   m_inputmanager.clearModifiers();
   m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
   if (smoothRadius > 0) {
@@ -1056,8 +1074,14 @@ bool ToonzRasterBrushTool::preLeftButtonDown() {
 #ifndef NDEBUG
   m_inputmanager.addModifier(TInputModifierP(m_modifierTest.getPointer()));
 #endif
+  m_inputmanager.addModifiers(m_modifierReplicate);
   m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
+}
 
+//--------------------------------------------------------------------------------------------------
+
+bool ToonzRasterBrushTool::preLeftButtonDown() {
+  updateModifiers();
   touchImage();
   if (m_isFrameCreated) {
     setWorkAndBackupImages();
@@ -1084,6 +1108,7 @@ void ToonzRasterBrushTool::handleMouseEvent(MouseEventType type,
     m_inputmanager.clearModifiers();
     m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
     m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
+    m_inputmanager.addModifiers(m_modifierReplicate);
     m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
     m_inputmanager.drawPreview = true;
   }
diff --git a/toonz/sources/tnztools/toonzrasterbrushtool.h b/toonz/sources/tnztools/toonzrasterbrushtool.h
index 65bc306..9209e92 100644
--- a/toonz/sources/tnztools/toonzrasterbrushtool.h
+++ b/toonz/sources/tnztools/toonzrasterbrushtool.h
@@ -108,8 +108,7 @@ public:
 
   ToolType getToolType() const override
     { return TTool::LevelWriteTool; }
-  unsigned int getToolHints() const override
-    { return TTool::getToolHints() & ~HintAssistantsAll; }
+  unsigned int getToolHints() const override;
   
   ToolOptionsBox *createOptionsBox() override;
 
@@ -162,6 +161,8 @@ public:
   bool isMyPaintStyleSelected() { return m_isMyPaintStyleSelected; }
 
 private:
+  void updateModifiers();
+
   enum MouseEventType { ME_DOWN, ME_DRAG, ME_UP, ME_MOVE };
   void handleMouseEvent(MouseEventType type, const TPointD &pos,
                         const TMouseEvent &e);
@@ -177,6 +178,7 @@ protected:
 #ifndef NDEBUG
   TSmartPointerT<TModifierTest> m_modifierTest;
 #endif
+  TInputModifier::List m_modifierReplicate;
 
   class MyPaintStroke: public TTrackHandler {
   public:
diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.cpp b/toonz/sources/tnztools/toonzvectorbrushtool.cpp
index 1e01069..a2e2ef3 100644
--- a/toonz/sources/tnztools/toonzvectorbrushtool.cpp
+++ b/toonz/sources/tnztools/toonzvectorbrushtool.cpp
@@ -6,6 +6,7 @@
 #include "tools/toolhandle.h"
 #include "tools/toolutils.h"
 #include "tools/tooloptions.h"
+#include "tools/replicator.h"
 #include "bluredbrush.h"
 
 // TnzQt includes
@@ -599,6 +600,18 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType)
 
 //-------------------------------------------------------------------------------------------------------
 
+unsigned int ToonzVectorBrushTool::getToolHints() const {
+  unsigned int h = TTool::getToolHints() & ~HintAssistantsAll;
+  if (m_assistants.getValue()) {
+    h |= HintReplicators;
+    h |= HintReplicatorsPoints;
+    h |= HintReplicatorsEnabled;
+  }
+  return h;
+}
+  
+//-------------------------------------------------------------------------------------------------------
+
 ToolOptionsBox *ToonzVectorBrushTool::createOptionsBox() {
   TPaletteHandle *currPalette =
       TTool::getApplication()->getPaletteController()->getCurrentLevelPalette();
@@ -657,6 +670,7 @@ void ToonzVectorBrushTool::onActivate() {
       loadLastBrush();
   }
   resetFrameRange();
+  updateModifiers();
   // TODO:app->editImageOrSpline();
 }
 
@@ -1043,9 +1057,7 @@ void ToonzVectorBrushTool::inputPaintTracks(const TTrackList &tracks) {
 
 //--------------------------------------------------------------------------------------------------
 
-bool ToonzVectorBrushTool::preLeftButtonDown() {
-  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
-
+void ToonzVectorBrushTool::updateModifiers() {
   m_pixelSize = getPixelSize();
   int smoothRadius = (int)round(m_smooth.getValue());
   m_modifierAssistants->magnetism = m_assistants.getValue() ? 1 : 0;
@@ -1054,6 +1066,10 @@ bool ToonzVectorBrushTool::preLeftButtonDown() {
   m_modifierSimplify->step = 2*m_pixelSize;
   m_inputmanager.drawPreview = false;
 
+  m_modifierReplicate.clear();
+  if (m_assistants.getValue())
+    TReplicator::scanReplicators(this, nullptr, &m_modifierReplicate, false, true, false, false, nullptr);
+  
   m_inputmanager.clearModifiers();
   m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
   if (smoothRadius > 0) {
@@ -1067,9 +1083,16 @@ bool ToonzVectorBrushTool::preLeftButtonDown() {
 #ifndef NDEBUG
   m_inputmanager.addModifier(TInputModifierP(m_modifierTest.getPointer()));
 #endif
+  m_inputmanager.addModifiers(m_modifierReplicate);
   m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
   m_inputmanager.addModifier(TInputModifierP(m_modifierSimplify.getPointer()));
+}
 
+//--------------------------------------------------------------------------------------------------
+
+bool ToonzVectorBrushTool::preLeftButtonDown() {
+  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
+  updateModifiers();
   touchImage();
   if (m_isFrameCreated) {
     // When the xsheet frame is selected, whole viewer will be updated from
@@ -1096,6 +1119,7 @@ void ToonzVectorBrushTool::handleMouseEvent(MouseEventType type,
     m_inputmanager.clearModifiers();
     m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
     m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
+    m_inputmanager.addModifiers(m_modifierReplicate);
     m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
     m_inputmanager.addModifier(TInputModifierP(m_modifierSimplify.getPointer()));
     m_inputmanager.drawPreview = true;
diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.h b/toonz/sources/tnztools/toonzvectorbrushtool.h
index 2aff0b4..242b9bd 100644
--- a/toonz/sources/tnztools/toonzvectorbrushtool.h
+++ b/toonz/sources/tnztools/toonzvectorbrushtool.h
@@ -99,8 +99,7 @@ public:
 
   ToolType getToolType() const override
     { return TTool::LevelWriteTool; }
-  unsigned int getToolHints() const override
-    { return TTool::getToolHints() & ~HintAssistantsAll; }
+  unsigned int getToolHints() const override;
 
   ToolOptionsBox *createOptionsBox() override;
 
@@ -168,6 +167,8 @@ protected:
 
   void snap(const TPointD &pos, bool snapEnabled, bool withSelfSnap = false);
 
+  void updateModifiers();
+  
   enum MouseEventType { ME_DOWN, ME_DRAG, ME_UP, ME_MOVE };
   void handleMouseEvent(MouseEventType type, const TPointD &pos,
                         const TMouseEvent &e);
@@ -200,13 +201,14 @@ protected:
 #ifndef NDEBUG
   TSmartPointerT<TModifierTest> m_modifierTest;
 #endif
+  TInputModifier::List m_modifierReplicate;
   
   TrackList m_tracks;
   TrackList m_rangeTracks;
   StrokeList m_firstStrokes;
   TFrameId m_firstFrameId, m_veryFirstFrameId;
   TPixel32 m_currentColor;
-  int m_styleId; // bwtodo: remove
+  int m_styleId;
   double m_minThick, m_maxThick;
 
   // for snapping and framerange
diff --git a/toonz/sources/tnztools/track.cpp b/toonz/sources/tnztools/track.cpp
index 2637216..2d0df30 100644
--- a/toonz/sources/tnztools/track.cpp
+++ b/toonz/sources/tnztools/track.cpp
@@ -11,6 +11,20 @@ TTrack::Id TTrack::m_lastId = 0;
 
 
 //*****************************************************************************************
+//    TTrackTransform implemantation
+//*****************************************************************************************
+
+TAffine TTrackTransform::makeTiltTransform(const TAffine &a) {
+  double l1 = a.a11*a.a11 + a.a21*a.a22;
+  double l2 = a.a11*a.a11 + a.a21*a.a22;
+  double l = std::max(l1, l2);
+  double k = l > TConsts::epsilon*TConsts::epsilon ? 1/sqrt(l) : 0;
+  return TAffine( a.a11*k, a.a12*k, 0,
+                  a.a21*k, a.a22*k, 0 );
+}
+
+
+//*****************************************************************************************
 //    TTrackIntrOrig implemantation
 //*****************************************************************************************
 
diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp
index 7b8dab6..d17ff5a 100644
--- a/toonz/sources/toonz/sceneviewer.cpp
+++ b/toonz/sources/toonz/sceneviewer.cpp
@@ -24,6 +24,7 @@
 #include "tools/toolcommandids.h"
 #include "tools/toolutils.h"
 #include "tools/assistant.h"
+#include "tools/replicator.h"
 
 // TnzQt includes
 #include "toonzqt/icongenerator.h"
@@ -1833,19 +1834,45 @@ void SceneViewer::drawOverlay() {
       glScaled(m_dpiScale.x, m_dpiScale.y, 1);
     m_pixelSize = sqrt(tglGetPixelSize2()) * getDevPixRatio();
     
+    unsigned int hints = tool->getToolHints();
+
     // draw assistans and guidelines
     m_toolHasAssistants = false;
-    unsigned int hints = tool->getToolHints();
     if (hints & TTool::HintAssistantsAll) {
+      bool markEnabled    = hints & TTool::HintAssistantsEnabled;
+      bool drawGuidelines = hints & TTool::HintAssistantsGuidelines;
+      
       m_toolHasAssistants = TAssistant::scanAssistants(
-        tool,                                 // tool
-        &m_toolPos, 1,                        // pointer positions
-        nullptr,                              // out guidelines
-        true,                                 // draw
-        false,                                // enabled only
-        hints & TTool::HintAssistantsEnabled, // mark enabled
-        true,                                 // draw guidelines
-        nullptr );                            // skip image
+        tool,           // tool
+        &m_toolPos, 1,  // pointer positions
+        nullptr,        // out guidelines
+        true,           // draw
+        false,          // enabled only
+        markEnabled,    // mark enabled
+        drawGuidelines, // draw guidelines
+        nullptr );      // skip image
+    }
+    
+    // draw replicators
+    m_toolReplicatedPoints.clear();
+    if (hints & TTool::HintReplicatorsAll) {
+      bool drawPoints  = hints & TTool::HintReplicatorsPoints;
+      bool markEnabled = hints & TTool::HintReplicatorsEnabled;
+      TReplicator::PointList *points = nullptr;
+      if (drawPoints) {
+        m_toolReplicatedPoints.push_back(m_toolPos);
+        points = &m_toolReplicatedPoints;
+      }
+      
+      TReplicator::scanReplicators(
+        tool,           // tool
+        points,         // in/out points
+        nullptr,        // out modifiers
+        true,           // draw
+        false,          // enabled only
+        markEnabled,    // mark enabled
+        drawPoints,     // draw points
+        nullptr );      // skip image
     }
     
     // draw tool
diff --git a/toonz/sources/toonz/sceneviewer.h b/toonz/sources/toonz/sceneviewer.h
index dcb22d7..a7b2f7b 100644
--- a/toonz/sources/toonz/sceneviewer.h
+++ b/toonz/sources/toonz/sceneviewer.h
@@ -73,6 +73,7 @@ class SceneViewer final : public GLWidgetForHighDpi,
   QPointF m_pos;
   TPointD m_toolPos;
   bool m_toolHasAssistants = false;
+  std::vector<TPointD> m_toolReplicatedPoints;
   Qt::MouseButton m_mouseButton;
   bool m_foregroundDrawing;
   bool m_tabletEvent, m_tabletMove;
diff --git a/toonz/sources/toonz/sceneviewerevents.cpp b/toonz/sources/toonz/sceneviewerevents.cpp
index a129d9b..35527c6 100644
--- a/toonz/sources/toonz/sceneviewerevents.cpp
+++ b/toonz/sources/toonz/sceneviewerevents.cpp
@@ -656,9 +656,15 @@ void SceneViewer::onMove(const TMouseEvent &event) {
     if (!cursorSet) setToolCursor(this, tool->getCursorId());
 
     if ( m_toolHasAssistants
-      && (tool->getToolHints() & TTool::HintGuidelines)
+      && (tool->getToolHints() & TTool::HintAssistantsGuidelines)
       && !areAlmostEqual(m_toolPos, pos) )
         invalidateAll();
+    
+    if ( m_toolReplicatedPoints.size() > 1
+      && (tool->getToolHints() & TTool::HintReplicatorsPoints)
+      && !areAlmostEqual(m_toolPos, pos) )
+        invalidateAll();
+
     m_toolPos = pos;
     
 #ifdef WITH_CANON
diff --git a/toonz/sources/toonzqt/intfield.cpp b/toonz/sources/toonzqt/intfield.cpp
index e53c078..e9ad097 100644
--- a/toonz/sources/toonzqt/intfield.cpp
+++ b/toonz/sources/toonzqt/intfield.cpp
@@ -10,6 +10,7 @@
 #include <QAction>
 #include <QFocusEvent>
 #include <QPainter>
+#include <QPushButton>
 
 namespace {
 const int NonLinearSliderPrecision = 2;
@@ -263,11 +264,14 @@ void IntLineEdit::mouseReleaseEvent(QMouseEvent *e) {
 // IntField
 //-----------------------------------------------------------------------------
 
-IntField::IntField(QWidget *parent, bool isMaxRangeLimited, bool isRollerHide)
+IntField::IntField(QWidget *parent, bool isMaxRangeLimited, bool isRollerHide,
+                   bool isSpinnerHide)
     : QWidget(parent)
+    , m_roller(0)
     , m_lineEdit(0)
     , m_slider(0)
-    , m_roller(0)
+    , m_inc(0)
+    , m_dec(0)
     , m_isMaxRangeLimited(isMaxRangeLimited)
     , m_isLinearSlider(true) {
   setObjectName("IntField");
@@ -295,6 +299,19 @@ IntField::IntField(QWidget *parent, bool isMaxRangeLimited, bool isRollerHide)
 
   layout->addWidget(field);
 
+  m_inc = new QPushButton(QString("+"));
+  m_dec = new QPushButton(QString("-"));
+  m_inc->setFixedSize(QSize(20, 20));
+  m_dec->setFixedSize(QSize(20, 20));
+  ret = ret
+     && connect(m_inc, SIGNAL(clicked()), this, SLOT(onIncClicked()))
+     && connect(m_dec, SIGNAL(clicked()), this, SLOT(onDecClicked()));
+   
+  if (isSpinnerHide) enableSpinner(false);
+  
+  layout->addWidget(m_inc);
+  layout->addWidget(m_dec);
+  
   m_slider = new QSlider(Qt::Horizontal, this);
   ret      = ret && connect(m_slider, SIGNAL(valueChanged(int)), this,
                        SLOT(onSliderChanged(int)));
@@ -386,6 +403,24 @@ bool IntField::rollerIsEnabled() { return m_roller->isEnabled(); }
 
 //-----------------------------------------------------------------------------
 
+void IntField::enableSpinner(bool enable) {
+  m_inc->setEnabled(enable);
+  m_dec->setEnabled(enable);
+  if (enable) {
+    m_inc->show();
+    m_dec->show();
+  } else {
+    m_inc->hide();
+    m_dec->hide();
+  }
+}
+
+//-----------------------------------------------------------------------------
+
+bool IntField::spinnerIsEnabled() { return m_inc->isEnabled(); }
+
+//-----------------------------------------------------------------------------
+
 void IntField::setLineEditBackgroundColor(QColor color) {
   m_lineEdit->setLineEditBackgroundColor(color);
 }
@@ -452,6 +487,28 @@ void IntField::onSliderChanged(int sliderPos) {
 
 //-----------------------------------------------------------------------------
 
+void IntField::onIncClicked() {
+  int value = m_lineEdit->getValue() + 1;
+  m_lineEdit->setValue(value);
+  m_slider->setValue(value2pos(value));
+  m_roller->setValue((double)value);
+  m_lineEdit->setCursorPosition(0);
+  emit valueChanged(false);
+}
+
+//-----------------------------------------------------------------------------
+
+void IntField::onDecClicked() {
+  int value = m_lineEdit->getValue() - 1;
+  m_lineEdit->setValue(value);
+  m_slider->setValue(value2pos(value));
+  m_roller->setValue((double)value);
+  m_lineEdit->setCursorPosition(0);
+  emit valueChanged(false);
+}
+
+//-----------------------------------------------------------------------------
+
 void IntField::onEditingFinished() {
   double value = m_lineEdit->getValue();
   // Controllo necessario per evitare che il segnale di cambiamento venga emesso