From 43a786899469992318040f6ba1eeca44973620fc Mon Sep 17 00:00:00 2001
From: Ian McInerney <ian.s.mcinerney@ieee.org>
Date: Sat, 4 Jan 2025 21:38:37 +0000
Subject: [PATCH] Fix reading old plot layer settings into new layer IDs

Fixes https://gitlab.com/kicad/code/kicad/-/issues/19475
---
 common/lset.cpp                               | 120 ----------
 include/base_set.h                            | 137 ++++++++++++
 include/lset.h                                |  20 --
 .../kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp |   2 +-
 pcbnew/pcb_plot_params.cpp                    | 207 +++++++++++++++++-
 pcbnew/pcb_plot_params_parser.h               |   4 +-
 6 files changed, 340 insertions(+), 150 deletions(-)

diff --git a/common/lset.cpp b/common/lset.cpp
index 9a0289e320..0f01bd39f2 100644
--- a/common/lset.cpp
+++ b/common/lset.cpp
@@ -297,126 +297,6 @@ LSEQ LSET::TechAndUserUIOrder() const
 }
 
 
-std::string LSET::FmtBin() const
-{
-    std::string ret;
-
-    int     bit_count = size();
-
-    for( int bit=0;  bit<bit_count;  ++bit )
-    {
-        if( bit )
-        {
-            if( !( bit % 8 ) )
-                ret += '|';
-            else if( !( bit % 4 ) )
-                ret += '_';
-        }
-
-        ret += (*this)[bit] ? '1' :  '0';
-    }
-
-    // reverse of string
-    return std::string( ret.rbegin(), ret.rend() );
-}
-
-
-std::string LSET::FmtHex() const
-{
-    std::string ret;
-
-    static const char hex[] = "0123456789abcdef";
-
-    size_t nibble_count = ( size() + 3 ) / 4;
-
-    for( size_t nibble = 0; nibble < nibble_count; ++nibble )
-    {
-        unsigned int ndx = 0;
-
-        // test 4 consecutive bits and set ndx to 0-15
-        for( size_t nibble_bit = 0; nibble_bit < 4; ++nibble_bit )
-        {
-            size_t nibble_pos = nibble_bit + ( nibble * 4 );
-            // make sure it's not extra bits that don't exist in the bitset but need to in the
-            // hex format
-            if( nibble_pos >= size() )
-                break;
-
-            if( ( *this )[nibble_pos] )
-                ndx |= ( 1 << nibble_bit );
-        }
-
-        if( nibble && !( nibble % 8 ) )
-            ret += '_';
-
-        assert( ndx < arrayDim( hex ) );
-
-        ret += hex[ndx];
-    }
-
-    // reverse of string
-    return std::string( ret.rbegin(), ret.rend() );
-}
-
-
-int LSET::ParseHex( const std::string& str )
-{
-    return ParseHex( str.c_str(), str.length() );
-}
-
-
-int LSET::ParseHex( const char* aStart, int aCount )
-{
-    LSET tmp;
-
-    const char* rstart = aStart + aCount - 1;
-    const char* rend   = aStart - 1;
-
-    const int bitcount = size();
-
-    int nibble_ndx = 0;
-
-    while( rstart > rend )
-    {
-        int cc = *rstart--;
-
-        if( cc == '_' )
-            continue;
-
-        int nibble;
-
-        if( cc >= '0' && cc <= '9' )
-            nibble = cc - '0';
-        else if( cc >= 'a' && cc <= 'f' )
-            nibble = cc - 'a' + 10;
-        else if( cc >= 'A' && cc <= 'F' )
-            nibble = cc - 'A' + 10;
-        else
-            break;
-
-        int bit = nibble_ndx * 4;
-
-        for( int ndx=0; bit<bitcount && ndx<4; ++bit, ++ndx )
-            if( nibble & (1<<ndx) )
-                tmp.set( bit );
-
-        if( bit >= bitcount )
-            break;
-
-        ++nibble_ndx;
-    }
-
-    int byte_count = aStart + aCount - 1 - rstart;
-
-    assert( byte_count >= 0 );
-
-    if( byte_count > 0 )
-        *this = tmp;
-
-    return byte_count;
-}
-
-
 LSEQ LSET::Seq( const LSEQ& aSequence ) const
 {
     LSEQ ret;
diff --git a/include/base_set.h b/include/base_set.h
index 20db3c8c6a..6c4c819eec 100644
--- a/include/base_set.h
+++ b/include/base_set.h
@@ -26,6 +26,7 @@
 #include <stdexcept>
 #include <dynamic_bitset.h>
 
+#include <core/arraydim.h>
 #include <core/kicad_algo.h>
 #include <kicommon.h>
 
@@ -214,6 +215,142 @@ public:
         return alg::lexicographical_compare_three_way( begin(), end(), other.begin(), other.end() ) < 0;
     }
 
+    /**
+     * Return a binary string showing contents of this set.
+     */
+    std::string FmtBin() const
+    {
+        std::string ret;
+
+        int     bit_count = size();
+
+        for( int bit=0;  bit<bit_count;  ++bit )
+        {
+            if( bit )
+            {
+                if( !( bit % 8 ) )
+                    ret += '|';
+                else if( !( bit % 4 ) )
+                    ret += '_';
+            }
+
+            ret += (*this)[bit] ? '1' :  '0';
+        }
+
+        // reverse of string
+        return std::string( ret.rbegin(), ret.rend() );
+    }
+
+    /**
+     * Return a hex string showing contents of this set.
+     */
+    std::string FmtHex() const
+    {
+        std::string ret;
+
+        static const char hex[] = "0123456789abcdef";
+
+        size_t nibble_count = ( size() + 3 ) / 4;
+
+        for( size_t nibble = 0; nibble < nibble_count; ++nibble )
+        {
+            unsigned int ndx = 0;
+
+            // test 4 consecutive bits and set ndx to 0-15
+            for( size_t nibble_bit = 0; nibble_bit < 4; ++nibble_bit )
+            {
+                size_t nibble_pos = nibble_bit + ( nibble * 4 );
+                // make sure it's not extra bits that don't exist in the bitset but need to in the
+                // hex format
+                if( nibble_pos >= size() )
+                    break;
+
+                if( ( *this )[nibble_pos] )
+                    ndx |= ( 1 << nibble_bit );
+            }
+
+            if( nibble && !( nibble % 8 ) )
+                ret += '_';
+
+            assert( ndx < arrayDim( hex ) );
+
+            ret += hex[ndx];
+        }
+
+        // reverse of string
+        return std::string( ret.rbegin(), ret.rend() );
+    }
+
+    /**
+     * Convert the output of FmtHex() and replaces this set's values
+     * with those given in the input string.  Parsing stops at the first
+     * non hex ASCII byte, except that marker bytes output from FmtHex are
+     * not terminators.
+     * @return int - number of bytes consumed
+     */
+    int ParseHex( const std::string& str )
+    {
+        return ParseHex( str.c_str(), str.length() );
+    }
+
+    /**
+     * Convert the output of FmtHex() and replaces this set's values
+     * with those given in the input string.  Parsing stops at the first
+     * non hex ASCII byte, except that marker bytes output from FmtHex are
+     * not terminators.
+     * @return int - number of bytes consumed
+     */
+    int ParseHex( const char* aStart, int aCount )
+    {
+        BASE_SET tmp(size());
+
+        const char* rstart = aStart + aCount - 1;
+        const char* rend   = aStart - 1;
+
+        const int bitcount = size();
+
+        int nibble_ndx = 0;
+
+        while( rstart > rend )
+        {
+            int cc = *rstart--;
+
+            if( cc == '_' )
+                continue;
+
+            int nibble;
+
+            if( cc >= '0' && cc <= '9' )
+                nibble = cc - '0';
+            else if( cc >= 'a' && cc <= 'f' )
+                nibble = cc - 'a' + 10;
+            else if( cc >= 'A' && cc <= 'F' )
+                nibble = cc - 'A' + 10;
+            else
+                break;
+
+            int bit = nibble_ndx * 4;
+
+            for( int ndx=0; bit<bitcount && ndx<4; ++bit, ++ndx )
+                if( nibble & (1<<ndx) )
+                    tmp.set( bit );
+
+            if( bit >= bitcount )
+                break;
+
+            ++nibble_ndx;
+        }
+
+        int byte_count = aStart + aCount - 1 - rstart;
+
+        assert( byte_count >= 0 );
+
+        if( byte_count > 0 )
+            *this = tmp;
+
+        return byte_count;
+    }
+
     // Custom iterator to iterate over set bits
     class KICOMMON_API set_bits_iterator
     {
diff --git a/include/lset.h b/include/lset.h
index 2dc1c2b6c7..c915d41113 100644
--- a/include/lset.h
+++ b/include/lset.h
@@ -249,26 +249,6 @@ public:
         }
     }
 
-    /**
-     * Return a hex string showing contents of this LSEQ.
-     */
-    std::string FmtHex() const;
-
-    /**
-     * Convert the output of FmtHex() and replaces this set's values
-     * with those given in the input string.  Parsing stops at the first
-     * non hex ASCII byte, except that marker bytes output from FmtHex are
-     * not terminators.
-     * @return int - number of bytes consumed
-     */
-    int ParseHex( const char* aStart, int aCount );
-    int ParseHex( const std::string& str );
-
-    /**
-     * Return a binary string showing contents of this LSEQ.
-     */
-    std::string FmtBin() const;
-
     /**
      * Find the first set PCB_LAYER_ID. Returns UNDEFINED_LAYER if more
      * than one is set or UNSELECTED_LAYER if none is set.
diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp
index c6a5529a12..f9385e1901 100644
--- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp
+++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp
@@ -2482,7 +2482,7 @@ void PCB_IO_KICAD_SEXPR_PARSER::parseSetup()
         case T_pcbplotparams:
         {
             PCB_PLOT_PARAMS        plotParams;
-            PCB_PLOT_PARAMS_PARSER parser( reader );
+            PCB_PLOT_PARAMS_PARSER parser( reader, m_requiredVersion );
             // parser must share the same current line as our current PCB parser
             // synchronize it.
             parser.SyncLineReaderWith( *this );
diff --git a/pcbnew/pcb_plot_params.cpp b/pcbnew/pcb_plot_params.cpp
index 9a67406675..20cc6698ab 100644
--- a/pcbnew/pcb_plot_params.cpp
+++ b/pcbnew/pcb_plot_params.cpp
@@ -404,18 +404,183 @@ bool PCB_PLOT_PARAMS::SetHPGLPenSpeed( int aValue )
 }
 
 
-PCB_PLOT_PARAMS_PARSER::PCB_PLOT_PARAMS_PARSER( LINE_READER* aReader ) :
-    PCB_PLOT_PARAMS_LEXER( aReader )
+PCB_PLOT_PARAMS_PARSER::PCB_PLOT_PARAMS_PARSER( LINE_READER* aReader, int aBoardFileVersion ) :
+    PCB_PLOT_PARAMS_LEXER( aReader ),
+    m_boardFileVersion( aBoardFileVersion )
 {
 }
 
 
 PCB_PLOT_PARAMS_PARSER::PCB_PLOT_PARAMS_PARSER( char* aLine, const wxString& aSource ) :
-    PCB_PLOT_PARAMS_LEXER( aLine, aSource )
+    PCB_PLOT_PARAMS_LEXER( aLine, aSource ),
+    m_boardFileVersion( 0 )
 {
 }
 
 
+/**
+ * These are the layer IDs from before 5e0abadb23425765e164f49ee2f893e94ddb97fc,
+ * and are needed for mapping old PCB files to the new layer numbering.
+ */
+enum LEGACY_PCB_LAYER_ID: int
+{
+    LEGACY_UNDEFINED_LAYER = -1,
+    LEGACY_UNSELECTED_LAYER = -2,
+
+    LEGACY_F_Cu = 0,
+    LEGACY_In1_Cu,
+    LEGACY_In2_Cu,
+    LEGACY_In3_Cu,
+    LEGACY_In4_Cu,
+    LEGACY_In5_Cu,
+    LEGACY_In6_Cu,
+    LEGACY_In7_Cu,
+    LEGACY_In8_Cu,
+    LEGACY_In9_Cu,
+    LEGACY_In10_Cu,
+    LEGACY_In11_Cu,
+    LEGACY_In12_Cu,
+    LEGACY_In13_Cu,
+    LEGACY_In14_Cu,
+    LEGACY_In15_Cu,
+    LEGACY_In16_Cu,
+    LEGACY_In17_Cu,
+    LEGACY_In18_Cu,
+    LEGACY_In19_Cu,
+    LEGACY_In20_Cu,
+    LEGACY_In21_Cu,
+    LEGACY_In22_Cu,
+    LEGACY_In23_Cu,
+    LEGACY_In24_Cu,
+    LEGACY_In25_Cu,
+    LEGACY_In26_Cu,
+    LEGACY_In27_Cu,
+    LEGACY_In28_Cu,
+    LEGACY_In29_Cu,
+    LEGACY_In30_Cu,
+    LEGACY_B_Cu,           // 31
+
+    LEGACY_B_Adhes,
+    LEGACY_F_Adhes,
+
+    LEGACY_B_Paste,
+    LEGACY_F_Paste,
+
+    LEGACY_B_SilkS,
+    LEGACY_F_SilkS,
+
+    LEGACY_B_Mask,
+    LEGACY_F_Mask,         // 39
+
+    LEGACY_Dwgs_User,
+    LEGACY_Cmts_User,
+    LEGACY_Eco1_User,
+    LEGACY_Eco2_User,
+    LEGACY_Edge_Cuts,
+    LEGACY_Margin,         // 45
+
+    LEGACY_B_CrtYd,
+    LEGACY_F_CrtYd,
+
+    LEGACY_B_Fab,
+    LEGACY_F_Fab,          // 49
+
+    // User definable layers.
+    LEGACY_User_1,
+    LEGACY_User_2,
+    LEGACY_User_3,
+    LEGACY_User_4,
+    LEGACY_User_5,
+    LEGACY_User_6,
+    LEGACY_User_7,
+    LEGACY_User_8,
+    LEGACY_User_9,
+
+    LEGACY_Rescue,         // 59
+
+    // Four reserved layers (60 - 63) for future expansion within the 64 bit integer limit.
+
+    LEGACY_PCB_LAYER_ID_COUNT
+};
+
+/*
+ * Mapping to translate a legacy layer ID into the new PCB layer IDs.
+ */
+static const std::map<LEGACY_PCB_LAYER_ID, PCB_LAYER_ID> s_legacyLayerIdMap{
+    {LEGACY_F_Cu,      F_Cu},
+    {LEGACY_B_Cu,      B_Cu},
+    {LEGACY_In1_Cu,    In1_Cu},
+    {LEGACY_In2_Cu,    In2_Cu},
+    {LEGACY_In3_Cu,    In3_Cu},
+    {LEGACY_In4_Cu,    In4_Cu},
+    {LEGACY_In5_Cu,    In5_Cu},
+    {LEGACY_In6_Cu,    In6_Cu},
+    {LEGACY_In7_Cu,    In7_Cu},
+    {LEGACY_In8_Cu,    In8_Cu},
+    {LEGACY_In9_Cu,    In9_Cu},
+    {LEGACY_In10_Cu,   In10_Cu},
+    {LEGACY_In11_Cu,   In11_Cu},
+    {LEGACY_In12_Cu,   In12_Cu},
+    {LEGACY_In13_Cu,   In13_Cu},
+    {LEGACY_In14_Cu,   In14_Cu},
+    {LEGACY_In15_Cu,   In15_Cu},
+    {LEGACY_In16_Cu,   In16_Cu},
+    {LEGACY_In17_Cu,   In17_Cu},
+    {LEGACY_In18_Cu,   In18_Cu},
+    {LEGACY_In19_Cu,   In19_Cu},
+    {LEGACY_In20_Cu,   In20_Cu},
+    {LEGACY_In21_Cu,   In21_Cu},
+    {LEGACY_In22_Cu,   In22_Cu},
+    {LEGACY_In23_Cu,   In23_Cu},
+    {LEGACY_In24_Cu,   In24_Cu},
+    {LEGACY_In25_Cu,   In25_Cu},
+    {LEGACY_In26_Cu,   In26_Cu},
+    {LEGACY_In27_Cu,   In27_Cu},
+    {LEGACY_In28_Cu,   In28_Cu},
+    {LEGACY_In29_Cu,   In29_Cu},
+    {LEGACY_In30_Cu,   In30_Cu},
+    {LEGACY_F_Mask,    F_Mask},
+    {LEGACY_B_Mask,    B_Mask},
+    {LEGACY_F_SilkS,   F_SilkS},
+    {LEGACY_B_SilkS,   B_SilkS},
+    {LEGACY_F_Adhes,   F_Adhes},
+    {LEGACY_B_Adhes,   B_Adhes},
+    {LEGACY_F_Paste,   F_Paste},
+    {LEGACY_B_Paste,   B_Paste},
+    {LEGACY_Dwgs_User, Dwgs_User},
+    {LEGACY_Cmts_User, Cmts_User},
+    {LEGACY_Eco1_User, Eco1_User},
+    {LEGACY_Eco2_User, Eco2_User},
+    {LEGACY_Edge_Cuts, Edge_Cuts},
+    {LEGACY_Margin,    Margin},
+    {LEGACY_B_CrtYd,   B_CrtYd},
+    {LEGACY_F_CrtYd,   F_CrtYd},
+    {LEGACY_B_Fab,     B_Fab},
+    {LEGACY_F_Fab,     F_Fab},
+    {LEGACY_User_1,    User_1},
+    {LEGACY_User_2,    User_2},
+    {LEGACY_User_3,    User_3},
+    {LEGACY_User_4,    User_4},
+    {LEGACY_User_5,    User_5},
+    {LEGACY_User_6,    User_6},
+    {LEGACY_User_7,    User_7},
+    {LEGACY_User_8,    User_8},
+    {LEGACY_User_9,    User_9},
+    {LEGACY_Rescue,    Rescue},
+};
+
+
+LSET remapLegacyLayerLSET( const BASE_SET& aLegacyLSET )
+{
+    LSET newLayers;
+
+    for( const auto& [legacyLayer, newLayer] : s_legacyLayerIdMap )
+        newLayers[newLayer] = aLegacyLSET[legacyLayer];
+
+    return newLayers;
+}
+
+
 void PCB_PLOT_PARAMS_PARSER::Parse( PCB_PLOT_PARAMS* aPcbPlotParams )
 {
     T   token;
@@ -451,8 +616,21 @@ void PCB_PLOT_PARAMS_PARSER::Parse( PCB_PLOT_PARAMS* aPcbPlotParams )
             }
             else if( cur.find_first_of( "0x" ) == 0 ) // pretty ver. 4.
             {
-                // skip the leading 2 0x bytes.
-                aPcbPlotParams->m_layerSelection.ParseHex( cur.c_str() + 2, cur.size() - 2 );
+                // The layers were renumbered in 5e0abadb23425765e164f49ee2f893e94ddb97fc, but there wasn't
+                // a board file version change with it, so this value is the one immediately after that happened.
+                if( m_boardFileVersion < 20240819 )
+                {
+                    BASE_SET legacyLSET( LEGACY_PCB_LAYER_ID_COUNT );
+
+                    // skip the leading 2 0x bytes.
+                    legacyLSET.ParseHex( cur.c_str() + 2, cur.size() - 2 );
+                    aPcbPlotParams->SetLayerSelection( remapLegacyLayerLSET( legacyLSET ) );
+                }
+                else
+                {
+                    // skip the leading 2 0x bytes.
+                    aPcbPlotParams->m_layerSelection.ParseHex( cur.c_str() + 2, cur.size() - 2 );
+                }
             }
             else
             {
@@ -470,9 +648,22 @@ void PCB_PLOT_PARAMS_PARSER::Parse( PCB_PLOT_PARAMS* aPcbPlotParams )
 
             if( cur.find_first_of( "0x" ) == 0 )
             {
-                // skip the leading 2 0x bytes.
-                aPcbPlotParams->m_plotOnAllLayersSelection.ParseHex( cur.c_str() + 2,
-                                                                     cur.size() - 2 );
+                // The layers were renumbered in 5e0abadb23425765e164f49ee2f893e94ddb97fc, but there wasn't
+                // a board file version change with it, so this value is the one immediately after that happened.
+                if( m_boardFileVersion < 20240819 )
+                {
+                    BASE_SET legacyLSET( LEGACY_PCB_LAYER_ID_COUNT );
+
+                    // skip the leading 2 0x bytes.
+                    legacyLSET.ParseHex( cur.c_str() + 2, cur.size() - 2 );
+                    aPcbPlotParams->SetPlotOnAllLayersSelection( remapLegacyLayerLSET( legacyLSET ) );
+                }
+                else
+                {
+                    // skip the leading 2 0x bytes.
+                    aPcbPlotParams->m_plotOnAllLayersSelection.ParseHex( cur.c_str() + 2,
+                                                                         cur.size() - 2 );
+                }
             }
             else
             {
diff --git a/pcbnew/pcb_plot_params_parser.h b/pcbnew/pcb_plot_params_parser.h
index 303e5ac0d4..bde48f8317 100644
--- a/pcbnew/pcb_plot_params_parser.h
+++ b/pcbnew/pcb_plot_params_parser.h
@@ -37,7 +37,7 @@ class LINE_READER;
 class PCB_PLOT_PARAMS_PARSER : public PCB_PLOT_PARAMS_LEXER
 {
 public:
-    PCB_PLOT_PARAMS_PARSER( LINE_READER* aReader );
+    PCB_PLOT_PARAMS_PARSER( LINE_READER* aReader, int aBoardFileVersion );
     PCB_PLOT_PARAMS_PARSER( char* aLine, const wxString& aSource );
 
     LINE_READER* GetReader() { return reader; };
@@ -69,6 +69,8 @@ private:
      * Search for the RIGHT parenthesis which closes the current description.
      */
     void skipCurrent();
+
+    int m_boardFileVersion;
 };
 
 #endif // PCB_PLOT_PARAMS_PARSER_H_