diff --git a/gerbview/am_param.cpp b/gerbview/am_param.cpp
index 69549fd7ae..2b41f0d182 100644
--- a/gerbview/am_param.cpp
+++ b/gerbview/am_param.cpp
@@ -69,7 +69,8 @@ bool AM_PARAM::IsImmediate() const
     return is_immediate;
 }
 
-double AM_PARAM::GetValue( const D_CODE* aDcode ) const
+
+double AM_PARAM::GetValueFromMacro( APERTURE_MACRO* aApertureMacro ) const
 {
     // In macros, actual values are sometimes given by an expression like:
     // 0-$2/2-$4
@@ -102,22 +103,15 @@ double AM_PARAM::GetValue( const D_CODE* aDcode ) const
                 break;
 
             case PUSHPARM:
-                // a defered value: get the actual parameter from the aDcode
-                if( aDcode )    // should be always true here
+                // a defered value: get the actual parameter from the aperture macro
+                if( aApertureMacro )    // should be always true here
                 {
-                    if( item.GetIndex() <= aDcode->GetParamCount() )
-                    {
-                        curr_value = aDcode->GetParam( item.GetIndex() );
-                    }
-                    else    // Get parameter from local param definition
-                    {
-                        const APERTURE_MACRO * am_parent = aDcode->GetMacro();
-                        curr_value = am_parent->GetLocalParam( aDcode, item.GetIndex() );
-                    }
-                }
+                    // Get the actual value
+                    curr_value = aApertureMacro->GetLocalParamValue( item.GetIndex() );
+               }
                 else
                 {
-                    wxFAIL_MSG( wxT( "AM_PARAM::GetValue(): NULL param aDcode" ) );
+                    wxFAIL_MSG( wxT( "AM_PARAM::GetValue(): NULL param aApertureMacro" ) );
                 }
 
                 ops.emplace_back( curr_value );
@@ -129,10 +123,8 @@ double AM_PARAM::GetValue( const D_CODE* aDcode ) const
                 break;
 
             default:
-                wxFAIL_MSG( wxString::Format( wxT( "AM_PARAM::GetValue(): dcode %d prm %d/%d: "
-                                                   "unexpected type %d" ),
-                                               aDcode ? aDcode->m_Num_Dcode : -1, ii,
-                                               m_paramStack.size(), item.GetType() ) );
+                wxFAIL_MSG( wxString::Format( wxT( "AM_PARAM::GetValue(): unexpected prm type %d" ),
+                                                   item.GetType() ) );
                 break;
         }
     }
@@ -142,6 +134,7 @@ double AM_PARAM::GetValue( const D_CODE* aDcode ) const
     return result;
 }
 
+
 /**
  * add an operator/operand to the current stack
  * aType = NOP, PUSHVALUE, PUSHPARM, ADD, SUB, MUL, DIV, EQUATE
diff --git a/gerbview/am_param.h b/gerbview/am_param.h
index 7eaf6ee397..30035121ef 100644
--- a/gerbview/am_param.h
+++ b/gerbview/am_param.h
@@ -295,7 +295,7 @@ public:
     void PushOperator( parm_item_type aType, double aValue );
     void PushOperator( parm_item_type aType, int aValue = 0);
 
-    double GetValue( const D_CODE* aDcode ) const;
+    double GetValueFromMacro( APERTURE_MACRO* aApertureMacro ) const;
 
     /**
      * Test if this AM_PARAM holds an immediate parameter or is a pointer into a parameter held
diff --git a/gerbview/am_primitive.cpp b/gerbview/am_primitive.cpp
index 39240c2ec8..37afe673da 100644
--- a/gerbview/am_primitive.cpp
+++ b/gerbview/am_primitive.cpp
@@ -55,7 +55,7 @@ static VECTOR2I mapPt( double x, double y, bool isMetric )
 }
 
 
-bool AM_PRIMITIVE::IsAMPrimitiveExposureOn( const D_CODE* aDcode ) const
+bool AM_PRIMITIVE::IsAMPrimitiveExposureOn( APERTURE_MACRO* aApertMacro ) const
 {
     /*
      * Some but not all primitives use the first parameter as an exposure control.
@@ -75,7 +75,7 @@ bool AM_PRIMITIVE::IsAMPrimitiveExposureOn( const D_CODE* aDcode ) const
     case AMP_OUTLINE:
     case AMP_POLYGON:
         // All have an exposure parameter and can return a value (0 or 1)
-        return m_Params[0].GetValue( aDcode ) != 0;
+        return m_Params[0].GetValueFromMacro( aApertMacro ) != 0;
         break;
 
     case AMP_THERMAL:   // Exposure is always on
@@ -88,11 +88,7 @@ bool AM_PRIMITIVE::IsAMPrimitiveExposureOn( const D_CODE* aDcode ) const
 }
 
 
-// TODO(snh): Remove hard coded count
-const int seg_per_circle = 64;   // Number of segments to approximate a circle
-
-
-void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
+void AM_PRIMITIVE::ConvertBasicShapeToPolygon( APERTURE_MACRO* aApertMacro,
                                                SHAPE_POLY_SET& aShapeBuffer )
 {
     // Draw the primitive shape for flashed items.
@@ -100,7 +96,7 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
     static std::vector<VECTOR2I> polybuffer;
     polybuffer.clear();
 
-    const D_CODE* tool = aDcode;
+    aApertMacro->EvalLocalParams( *this );
 
     switch( m_Primitive_id )
     {
@@ -112,12 +108,12 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * <rotation> is a optional parameter: rotation from origin.
          * type is not stored in parameters list, so the first parameter is exposure
          */
-        ConvertShapeToPolygon( tool, polybuffer );
+        ConvertShapeToPolygon( aApertMacro, polybuffer );
 
         // shape rotation (if any):
         if( m_Params.size() >= 5 )
         {
-            EDA_ANGLE rotation( m_Params[4].GetValue( tool ), DEGREES_T );
+            EDA_ANGLE rotation( m_Params[4].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
             if( !rotation.IsZero() )
             {
@@ -141,10 +137,10 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * type (2), exposure, width, start.x, start.y, end.x, end.y, rotation
          * type is not stored in parameters list, so the first parameter is exposure
          */
-        ConvertShapeToPolygon( tool, polybuffer );
+        ConvertShapeToPolygon( aApertMacro, polybuffer );
 
         // shape rotation:
-        EDA_ANGLE rotation( m_Params[6].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[6].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
         if( !rotation.IsZero() )
         {
@@ -165,10 +161,10 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * type (21), exposure, ,width, height, center pos.x, center pos.y, rotation
          * type is not stored in parameters list, so the first parameter is exposure
          */
-        ConvertShapeToPolygon( tool, polybuffer );
+        ConvertShapeToPolygon( aApertMacro, polybuffer );
 
         // shape rotation:
-        EDA_ANGLE rotation( m_Params[5].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
         if( !rotation.IsZero() )
         {
@@ -186,10 +182,10 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * type (22), exposure, ,width, height, corner pos.x, corner pos.y, rotation
          * type is not stored in parameters list, so the first parameter is exposure
          */
-        ConvertShapeToPolygon( tool, polybuffer );
+        ConvertShapeToPolygon( aApertMacro, polybuffer );
 
         // shape rotation:
-        EDA_ANGLE rotation( m_Params[5].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
         if( !rotation.IsZero() )
         {
@@ -210,12 +206,12 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * on.
          */
         std::vector<VECTOR2I> subshape_poly;
-        VECTOR2I center( mapPt( m_Params[0].GetValue( tool ),
-                                 m_Params[1].GetValue( tool ), m_GerbMetric ) );
-        ConvertShapeToPolygon( tool, subshape_poly );
+        VECTOR2I center( mapPt( m_Params[0].GetValueFromMacro( aApertMacro ),
+                         m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );
+        ConvertShapeToPolygon( aApertMacro, subshape_poly );
 
         // shape rotation:
-        EDA_ANGLE rotation( m_Params[5].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
         // Because a thermal shape has 4 identical sub-shapes, only one is created in subshape_poly.
         // We must draw 4 sub-shapes rotated by 90 deg
@@ -256,17 +252,17 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * crosshair len, rotation.  The type is not stored in parameters list, so the first
          * parameter is pos.x.
          */
-        int outerDiam    = scaletoIU( m_Params[2].GetValue( tool ), m_GerbMetric );
-        int penThickness = scaletoIU( m_Params[3].GetValue( tool ), m_GerbMetric );
-        int gap = scaletoIU( m_Params[4].GetValue( tool ), m_GerbMetric );
-        int numCircles = KiROUND( m_Params[5].GetValue( tool ) );
+        int outerDiam    = scaletoIU( m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric );
+        int penThickness = scaletoIU( m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric );
+        int gap = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );
+        int numCircles = KiROUND( m_Params[5].GetValueFromMacro( aApertMacro ) );
 
         // Adjust the allowed approx error to convert arcs to segments:
         int arc_to_seg_error = gerbIUScale.mmToIU( 0.005 );    // Allow 5 microns
 
         // Draw circles @ position pos.x, pos.y given by the tool:
-        VECTOR2I center( mapPt( m_Params[0].GetValue( tool ), m_Params[1].GetValue( tool ),
-                         m_GerbMetric ) );
+        VECTOR2I center( mapPt( m_Params[0].GetValueFromMacro( aApertMacro ),
+                         m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );
 
         // adjust outerDiam by this on each nested circle
         int diamAdjust = ( gap + penThickness ) * 2;
@@ -292,9 +288,9 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
         }
 
         // Draw the cross:
-        ConvertShapeToPolygon( tool, polybuffer );
+        ConvertShapeToPolygon( aApertMacro, polybuffer );
 
-        EDA_ANGLE rotation( m_Params[8].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[8].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
         for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
         {
@@ -325,11 +321,11 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * type is not stored in parameters list, so the first parameter is exposure
          */
         // m_Params[0] is the exposure and m_Params[1] is the corners count after the first corner
-        int numCorners = (int) m_Params[1].GetValue( tool );
+        int numCorners = (int) m_Params[1].GetValueFromMacro( aApertMacro );
 
         // the shape rotation is the last param of list, after corners
         int       last_prm = m_Params.size() - 1;
-        EDA_ANGLE rotation( m_Params[last_prm].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[last_prm].GetValueFromMacro( aApertMacro ), DEGREES_T );
         VECTOR2I  pos;
 
         // Read points.
@@ -340,9 +336,9 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
 
         for( int i = 0; i <= numCorners; ++i )
         {
-            pos.x = scaletoIU( m_Params[prm_idx].GetValue( tool ), m_GerbMetric );
+            pos.x = scaletoIU( m_Params[prm_idx].GetValueFromMacro( aApertMacro ), m_GerbMetric );
             prm_idx++;
-            pos.y = scaletoIU( m_Params[prm_idx].GetValue( tool ), m_GerbMetric );
+            pos.y = scaletoIU( m_Params[prm_idx].GetValueFromMacro( aApertMacro ), m_GerbMetric );
             prm_idx++;
             polybuffer.push_back(pos);
 
@@ -373,13 +369,14 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
          * type(5), exposure, vertices count, pox.x, pos.y, diameter, rotation
          * type is not stored in parameters list, so the first parameter is exposure
          */
-        VECTOR2I curPos( mapPt( m_Params[2].GetValue( tool ), m_Params[3].GetValue( tool ), m_GerbMetric ) );
+        VECTOR2I curPos( mapPt( m_Params[2].GetValueFromMacro( aApertMacro ),
+                                m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );
 
         // Creates the shape:
-        ConvertShapeToPolygon( tool, polybuffer );
+        ConvertShapeToPolygon( aApertMacro, polybuffer );
 
         // rotate polygon
-        EDA_ANGLE rotation( m_Params[5].GetValue( tool ), DEGREES_T );
+        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
 
         for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
         {
@@ -408,11 +405,9 @@ void AM_PRIMITIVE::ConvertBasicShapeToPolygon( const D_CODE* aDcode,
 }
 
 
-void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
+void AM_PRIMITIVE::ConvertShapeToPolygon( APERTURE_MACRO* aApertMacro,
                                           std::vector<VECTOR2I>&  aBuffer )
 {
-    const D_CODE* tool = aDcode;
-
     switch( m_Primitive_id )
     {
     case AMP_CIRCLE:
@@ -423,17 +418,19 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
          * <rotation> is a optional parameter: rotation from origin.
          * type is not stored in parameters list, so the first parameter is exposure
          */
-        int radius = scaletoIU( m_Params[1].GetValue( tool ), m_GerbMetric ) / 2;
+        int radius = scaletoIU( m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
 
         // A circle primitive can have a 0 size (for instance when used in roundrect macro),
         // so skip it
         if( radius <= 0 )
             break;
 
-        VECTOR2I  center = mapPt( m_Params[2].GetValue( tool ), m_Params[3].GetValue( tool ),
+        VECTOR2I  center = mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ),
                                   m_GerbMetric );
         VECTOR2I  corner;
-        EDA_ANGLE delta = ANGLE_360 / seg_per_circle;    // rot angle in 0.1 degree
+
+        const int seg_per_circle = 64;   // Number of segments to approximate a circle
+        EDA_ANGLE delta = ANGLE_360 / seg_per_circle;
 
         for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta )
         {
@@ -450,11 +447,11 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
     case AMP_LINE2:
     case AMP_LINE20:        // Line with rectangle ends. (Width, start and end pos + rotation)
     {
-        int      width = scaletoIU( m_Params[1].GetValue( tool ), m_GerbMetric );
+        int      width = scaletoIU( m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric );
         VECTOR2I start =
-                mapPt( m_Params[2].GetValue( tool ), m_Params[3].GetValue( tool ), m_GerbMetric );
+                mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric );
         VECTOR2I end =
-                mapPt( m_Params[4].GetValue( tool ), m_Params[5].GetValue( tool ), m_GerbMetric );
+                mapPt( m_Params[4].GetValueFromMacro( aApertMacro ), m_Params[5].GetValueFromMacro( aApertMacro ), m_GerbMetric );
         VECTOR2I delta = end - start;
         int     len   = KiROUND( EuclideanNorm( delta ) );
 
@@ -485,9 +482,9 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
     case AMP_LINE_CENTER:
     {
         VECTOR2I size =
-                mapPt( m_Params[1].GetValue( tool ), m_Params[2].GetValue( tool ), m_GerbMetric );
+                mapPt( m_Params[1].GetValueFromMacro( aApertMacro ), m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric );
         VECTOR2I pos =
-                mapPt( m_Params[3].GetValue( tool ), m_Params[4].GetValue( tool ), m_GerbMetric );
+                mapPt( m_Params[3].GetValueFromMacro( aApertMacro ), m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );
 
         // Build poly:
         pos.x -= size.x / 2;
@@ -505,9 +502,9 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
     case AMP_LINE_LOWER_LEFT:
     {
         VECTOR2I size =
-                mapPt( m_Params[1].GetValue( tool ), m_Params[2].GetValue( tool ), m_GerbMetric );
+                mapPt( m_Params[1].GetValueFromMacro( aApertMacro ), m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric );
         VECTOR2I lowerLeft =
-                mapPt( m_Params[3].GetValue( tool ), m_Params[4].GetValue( tool ), m_GerbMetric );
+                mapPt( m_Params[3].GetValueFromMacro( aApertMacro ), m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );
 
         // Build poly:
         aBuffer.push_back( lowerLeft );
@@ -526,14 +523,14 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
         // this first rotated by 90, 180 and 270 deg.
         // m_Params = center.x (unused here), center.y (unused here), outside diam, inside diam,
         // crosshair thickness.
-        int outerRadius   = scaletoIU( m_Params[2].GetValue( tool ), m_GerbMetric ) / 2;
-        int innerRadius   = scaletoIU( m_Params[3].GetValue( tool ), m_GerbMetric ) / 2;
+        int outerRadius   = scaletoIU( m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
+        int innerRadius   = scaletoIU( m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
 
         // Safety checks to guarantee no divide-by-zero
         outerRadius = std::max( 1, outerRadius );
         innerRadius = std::max( 1, innerRadius );
 
-        int       halfthickness  = scaletoIU( m_Params[4].GetValue( tool ), m_GerbMetric ) / 2;
+        int       halfthickness  = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
         EDA_ANGLE angle_start( asin( (double) halfthickness / innerRadius ), RADIANS_T );
 
         // Draw shape in the first quadrant (X and Y > 0)
@@ -582,8 +579,8 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
     {
         // A cross hair with n concentric circles. Only the cross is built as
         // polygon because circles can be drawn easily
-        int crossHairThickness = scaletoIU( m_Params[6].GetValue( tool ), m_GerbMetric );
-        int crossHairLength    = scaletoIU( m_Params[7].GetValue( tool ), m_GerbMetric );
+        int crossHairThickness = scaletoIU( m_Params[6].GetValueFromMacro( aApertMacro ), m_GerbMetric );
+        int crossHairLength    = scaletoIU( m_Params[7].GetValueFromMacro( aApertMacro ), m_GerbMetric );
 
         // Create cross. First create 1/4 of the shape.
         // Others point are the same, rotated by 90, 180 and 270 deg
@@ -616,8 +613,8 @@ void AM_PRIMITIVE::ConvertShapeToPolygon( const D_CODE* aDcode,
 
     case AMP_POLYGON:   // Creates a regular polygon
     {
-        int vertexcount = KiROUND( m_Params[1].GetValue( tool ) );
-        int radius    = scaletoIU( m_Params[4].GetValue( tool ), m_GerbMetric ) / 2;
+        int vertexcount = KiROUND( m_Params[1].GetValueFromMacro( aApertMacro ) );
+        int radius    = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
 
         // rs274x said: vertex count = 3 ... 10, and the first corner is on the X axis
         if( vertexcount < 3 )
diff --git a/gerbview/am_primitive.h b/gerbview/am_primitive.h
index a644c78647..a0b5b2ea1b 100644
--- a/gerbview/am_primitive.h
+++ b/gerbview/am_primitive.h
@@ -117,18 +117,21 @@ public:
      * In a aperture macro shape, a basic primitive with exposure off is a hole in the shape
      * it is NOT a negative shape
      */
-    bool  IsAMPrimitiveExposureOn( const D_CODE* aDcode ) const;
+    bool  IsAMPrimitiveExposureOn( APERTURE_MACRO* aApertMacro ) const;
 
     /**
      * Generate the polygonal shape of the primitive shape of an aperture
      * macro instance.
      *
-     * @param aParent is the parent GERBER_DRAW_ITEM which is actually drawn.
+     * @param aApertMacro is the aperture macro using this primitive.
      * @param aShapeBuffer is a SHAPE_POLY_SET to put the shape converted to a polygon.
-     * @param aShapePos is the actual shape position.
      */
+#if 0
     void ConvertBasicShapeToPolygon( const D_CODE* aDcode,
                                      SHAPE_POLY_SET& aShapeBuffer );
+#endif
+    void ConvertBasicShapeToPolygon( APERTURE_MACRO* aApertMacro,
+                                     SHAPE_POLY_SET& aShapeBuffer );
 
 private:
     /**
@@ -141,7 +144,8 @@ private:
      *       converted because circles are very easy to draw (no rotation problem) so convert
      *       them in polygons and draw them as polygons is not a good idea.
      */
-    void ConvertShapeToPolygon( const D_CODE* aDcode, std::vector<VECTOR2I>& aBuffer );
+    //void ConvertShapeToPolygon( const D_CODE* aDcode, std::vector<VECTOR2I>& aBuffer );
+    void ConvertShapeToPolygon( APERTURE_MACRO* aApertMacroe, std::vector<VECTOR2I>& aBuffer );
 };
 
 
diff --git a/gerbview/aperture_macro.cpp b/gerbview/aperture_macro.cpp
index dbf2a1af5f..45f6d841bb 100644
--- a/gerbview/aperture_macro.cpp
+++ b/gerbview/aperture_macro.cpp
@@ -32,6 +32,55 @@
 #include <aperture_macro.h>
 #include <gerber_draw_item.h>
 
+void APERTURE_MACRO::InitLocalParams( const D_CODE* aDcode )
+{
+    // store the initial values coming from aDcode into m_localParamValues
+    // for n parameters, they are local params $1 to $n
+    m_localParamValues.clear();
+
+    // Note: id_param = 1... n, not 0
+    for( unsigned id_param = 1; id_param <= aDcode->GetParamCount(); id_param++ )
+        m_localParamValues[id_param] = aDcode->GetParam( id_param );
+
+    m_paramLevelEval = 0;
+}
+
+
+void APERTURE_MACRO::EvalLocalParams( const AM_PRIMITIVE& aPrimitive )
+{
+    // Evaluate m_localParamValues from current m_paramLevelEval to
+    // aPrimitive.m_LocalParamLevel
+    // if m_paramLevelEval >= m_LocalParamLevel, do nothing: the
+    // m_localParamValues are already up to date
+
+    if( m_paramLevelEval >= aPrimitive.m_LocalParamLevel )
+        return;
+
+    for( ; m_paramLevelEval < aPrimitive.m_LocalParamLevel; m_paramLevelEval++ )
+    {
+        AM_PARAM& am_param = m_localParamStack.at( m_paramLevelEval );
+        int prm_index = am_param.GetIndex();
+
+        double value = am_param.GetValueFromMacro( this );
+
+        // if am_param value is not yet stored in m_localParamValues, add it.
+        // if it is already in m_localParamValues, update its value;
+        m_localParamValues[ prm_index ] = value;
+    }
+}
+
+
+double APERTURE_MACRO::GetLocalParamValue( int aIndex )
+{
+    // return the local param value stored in m_localParamValues
+    // if not existing, returns 0
+
+    if( m_localParamValues.find( aIndex ) != m_localParamValues.end() )
+        return m_localParamValues[ aIndex ];
+
+    return 0.0;
+}
+
 
 void APERTURE_MACRO::AddPrimitiveToList( AM_PRIMITIVE& aPrimitive )
 {
@@ -52,25 +101,26 @@ AM_PARAM& APERTURE_MACRO::GetLastLocalParamDefFromStack()
 
 
 SHAPE_POLY_SET* APERTURE_MACRO::GetApertureMacroShape( const GERBER_DRAW_ITEM* aParent,
-                                                       const VECTOR2I&         aShapePos )
+                                                       const VECTOR2I& aShapePos )
 {
     SHAPE_POLY_SET holeBuffer;
 
     m_shape.RemoveAllContours();
-    D_CODE * dcode = aParent->GetDcodeDescr();
+    D_CODE* dcode = aParent->GetDcodeDescr();
+    InitLocalParams( dcode );
 
     for( AM_PRIMITIVE& prim_macro : m_primitivesList )
     {
         if( prim_macro.m_Primitive_id == AMP_COMMENT )
             continue;
 
-        if( prim_macro.IsAMPrimitiveExposureOn( dcode ) )
+        if( prim_macro.IsAMPrimitiveExposureOn( this ) )
         {
-            prim_macro.ConvertBasicShapeToPolygon( dcode, m_shape );
+            prim_macro.ConvertBasicShapeToPolygon( this, m_shape );
         }
         else
         {
-            prim_macro.ConvertBasicShapeToPolygon( dcode, holeBuffer );
+            prim_macro.ConvertBasicShapeToPolygon( this, holeBuffer );
 
             if( holeBuffer.OutlineCount() )     // we have a new hole in shape: remove the hole
             {
@@ -105,27 +155,3 @@ SHAPE_POLY_SET* APERTURE_MACRO::GetApertureMacroShape( const GERBER_DRAW_ITEM* a
 
     return &m_shape;
 }
-
-
-double APERTURE_MACRO::GetLocalParam( const D_CODE* aDcode, unsigned aParamId ) const
-{
-    // find parameter descr.
-    const AM_PARAM * param = nullptr;
-
-    for( unsigned ii = 0; ii < m_localParamStack.size(); ii ++ )
-    {
-        if( m_localParamStack[ii].GetIndex() == aParamId )
-        {
-            param = &m_localParamStack[ii];
-            break;
-        }
-    }
-
-    if ( param == nullptr )    // not found
-        return 0.0;
-
-    // Evaluate parameter
-    double value = param->GetValue( aDcode );
-
-    return value;
-}
diff --git a/gerbview/aperture_macro.h b/gerbview/aperture_macro.h
index c6ef86bf21..9260fdb766 100644
--- a/gerbview/aperture_macro.h
+++ b/gerbview/aperture_macro.h
@@ -68,6 +68,9 @@ class SHAPE_POLY_SET;
 class APERTURE_MACRO
 {
 public:
+    APERTURE_MACRO() :
+        m_paramLevelEval( 0 )
+    {}
     /**
      * Usually, parameters are defined inside the aperture primitive using immediate mode or
      * deferred mode.
@@ -82,17 +85,40 @@ public:
      */
     double GetLocalParam( const D_CODE* aDcode, unsigned aParamId ) const;
 
+    /**
+     * Init m_localParamValues to a initial values coming from aDcode and
+     * clear m_paramLevelEval
+     * must be called once before trying to build the aperture macro shape
+     * corresponding to aDcode
+     */
+    void InitLocalParams( const D_CODE* aDcode );
+
+    /**
+     * Evaluate m_localParamValues from current m_paramLevelEval to
+     * aPrimitive m_LocalParamLevel
+     * if m_paramLevelEval >= m_LocalParamLevel, do nothing
+     * after call, m_paramLevelEval = m_LocalParamLevel
+     */
+    void EvalLocalParams( const AM_PRIMITIVE& aPrimitive );
+
+    /**
+     * @return the local param value stored in m_localParamValues
+     * @param aIndex is the param Id (from $n)
+     * if not found, returns 0
+     */
+    double GetLocalParamValue( int aIndex );
 
     /**
      * Calculate the primitive shape for flashed items.
      *
      * When an item is flashed, this is the shape of the item.
      *
-     * @param aParent is the parent #GERBER_DRAW_ITEM which is actually drawn.
      * @return the shape of the item.
+     * @param aParent is the parent #GERBER_DRAW_ITEM which is actually drawn.
+     * @param aShapePos is the position of the shape to build.
      */
     SHAPE_POLY_SET* GetApertureMacroShape( const GERBER_DRAW_ITEM* aParent,
-                                           const VECTOR2I&         aShapePos );
+                                           const VECTOR2I& aShapePos );
 
     /**
      * The name of the aperture macro as defined like %AMVB_RECTANGLE* (name is VB_RECTANGLE)
@@ -125,6 +151,20 @@ private:
      */
     AM_PARAMS m_localParamStack;
 
+    /**
+     * m_localParamValues is the current value of local parameters after evaluation
+     * the key is the local param id (from $n) and the value is double
+     */
+    std::map<int, double> m_localParamValues;
+
+    /**
+     * the current level of local param values evaluation
+     * when a primitive is evaluated, if its m_LocalParamLevel is smaller than
+     * m_paramLevelEval, all local params must be evaluated from current m_paramLevelEval
+     * upto m_LocalParamLevel before use in this primitive
+     */
+    int m_paramLevelEval;
+
     SHAPE_POLY_SET m_shape;         ///< The shape of the item, calculated by GetApertureMacroShape
 };
 
diff --git a/gerbview/dcode.h b/gerbview/dcode.h
index b7a36734a1..94b3ba18a1 100644
--- a/gerbview/dcode.h
+++ b/gerbview/dcode.h
@@ -105,6 +105,7 @@ public:
      * Return a parameter stored in parameter list.
      *
      * @param aIdx is the index of parameter.
+     * for n parameters from the Dcode definition, aIdx = 1 .. n, not 0
      */
     double GetParam( unsigned aIdx ) const
     {
diff --git a/gerbview/gerber_draw_item.cpp b/gerbview/gerber_draw_item.cpp
index bf6fe03e1b..169e42912f 100644
--- a/gerbview/gerber_draw_item.cpp
+++ b/gerbview/gerber_draw_item.cpp
@@ -320,7 +320,6 @@ const BOX2I GERBER_DRAW_ITEM::GetBoundingBox() const
 
     case GBR_SPOT_MACRO:
     case GBR_SPOT_POLY:
-    {
         if( code )
         {
             if( code->m_Polygon.OutlineCount() == 0 )
@@ -331,7 +330,6 @@ const BOX2I GERBER_DRAW_ITEM::GetBoundingBox() const
         }
 
         break;
-    }
 
     case GBR_SEGMENT:
     {
@@ -861,12 +859,7 @@ bool GERBER_DRAW_ITEM::HitTest( const VECTOR2I& aRefPos, int aAccuracy ) const
     }
 
     case GBR_SPOT_MACRO:
-    {
-        // Aperture macro polygons are already in absolute coordinates
-        SHAPE_POLY_SET* p =
-            GetDcodeDescr()->GetMacro()->GetApertureMacroShape( this, m_Start );
-        return p->Contains( VECTOR2I( aRefPos ), -1, aAccuracy );
-    }
+        return m_AbsolutePolygon.Contains( VECTOR2I( aRefPos ), -1, aAccuracy );
 
     case GBR_SEGMENT:
     case GBR_CIRCLE:
diff --git a/gerbview/gerbview_painter.cpp b/gerbview/gerbview_painter.cpp
index 0dc73fe6a9..3ec714bd9c 100644
--- a/gerbview/gerbview_painter.cpp
+++ b/gerbview/gerbview_painter.cpp
@@ -566,21 +566,26 @@ void GERBVIEW_PAINTER::drawFlashedShape( GERBER_DRAW_ITEM* aItem, bool aFilled )
 
 void GERBVIEW_PAINTER::drawApertureMacro( GERBER_DRAW_ITEM* aParent, bool aFilled )
 {
-    D_CODE*         code = aParent->GetDcodeDescr();
-    APERTURE_MACRO* macro = code->GetMacro();
-    SHAPE_POLY_SET* macroShape = macro->GetApertureMacroShape( aParent, aParent->m_Start );
+    if( aParent->m_AbsolutePolygon.OutlineCount() == 0 )
+    {
+        D_CODE* code = aParent->GetDcodeDescr();
+        APERTURE_MACRO* macro = code->GetMacro();
+        aParent->m_AbsolutePolygon = *macro->GetApertureMacroShape( aParent, aParent->m_Start );
+    }
+
+    SHAPE_POLY_SET& polyset = aParent->m_AbsolutePolygon;
 
     if( !gvconfig()->m_Display.m_DisplayPolygonsFill )
         m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
 
     if( !aFilled )
     {
-        for( int i = 0; i < macroShape->OutlineCount(); i++ )
-            m_gal->DrawPolyline( macroShape->COutline( i ) );
+        for( int i = 0; i < polyset.OutlineCount(); i++ )
+            m_gal->DrawPolyline( polyset.COutline( i ) );
     }
     else
     {
-        m_gal->DrawPolygon( *macroShape );
+        m_gal->DrawPolygon( polyset );
     }
 }
 
diff --git a/gerbview/rs274x.cpp b/gerbview/rs274x.cpp
index c4d28886f1..e7a1336e80 100644
--- a/gerbview/rs274x.cpp
+++ b/gerbview/rs274x.cpp
@@ -1132,7 +1132,7 @@ bool GERBER_FILE_IMAGE::ReadApertureMacro( char *aBuff, unsigned int aBuffSize,
             // in advance, i.e. be immediate.
             wxASSERT( prim.m_Params[1].IsImmediate() );
 
-            paramCount = (int) prim.m_Params[1].GetValue( nullptr ) * 2 + 1;
+            paramCount = (int) prim.m_Params[1].GetValueFromMacro( nullptr ) * 2 + 1;
 
             for( int jj = 0; jj < paramCount && *aText != '*'; ++jj )
             {