From 6b7503be8ea578ccb89ccca755ed3bfe183c6215 Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Sat, 8 Aug 2020 22:47:57 +0100
Subject: [PATCH] ADDED properties passed between eescema and pcbnew.

And referencable from textVars.

Fixes https://gitlab.com/kicad/code/kicad/issues/5079
---
 common/netlist.keywords                       |  1 +
 .../netlist_exporter_generic.cpp              | 32 ++++++++-----
 pcbnew/class_module.cpp                       |  5 ++
 pcbnew/class_module.h                         | 20 ++++----
 pcbnew/kicad_plugin.cpp                       |  9 ++++
 pcbnew/kicad_plugin.h                         |  3 +-
 .../netlist_reader/board_netlist_updater.cpp  | 15 +++++-
 .../netlist_reader/kicad_netlist_reader.cpp   | 48 ++++++++++++++++---
 pcbnew/netlist_reader/netlist_reader.h        | 17 +++----
 pcbnew/netlist_reader/pcb_netlist.cpp         |  7 +--
 pcbnew/netlist_reader/pcb_netlist.h           | 10 +++-
 pcbnew/pcb_parser.cpp                         | 11 +++++
 12 files changed, 138 insertions(+), 40 deletions(-)

diff --git a/common/netlist.keywords b/common/netlist.keywords
index f5d39cd67f..edabd96803 100644
--- a/common/netlist.keywords
+++ b/common/netlist.keywords
@@ -28,6 +28,7 @@ part
 pin
 pins
 pinfunction
+property
 ref
 sheetpath
 source
diff --git a/eeschema/netlist_exporters/netlist_exporter_generic.cpp b/eeschema/netlist_exporters/netlist_exporter_generic.cpp
index 7d246d6743..168acf645b 100644
--- a/eeschema/netlist_exporters/netlist_exporter_generic.cpp
+++ b/eeschema/netlist_exporters/netlist_exporter_generic.cpp
@@ -205,9 +205,9 @@ XNODE* NETLIST_EXPORTER_GENERIC::makeComponents( unsigned aCtl )
     // Output is xml, so there is no reason to remove spaces from the field values.
     // And XML element names need not be translated to various languages.
 
-    for( unsigned i = 0; i < sheetList.size(); i++ )
+    for( unsigned ii = 0; ii < sheetList.size(); ii++ )
     {
-        SCH_SHEET_PATH sheet = sheetList[i];
+        SCH_SHEET_PATH sheet = sheetList[ii];
 
         auto cmp =
                 [sheet]( SCH_COMPONENT* a, SCH_COMPONENT* b )
@@ -218,10 +218,10 @@ XNODE* NETLIST_EXPORTER_GENERIC::makeComponents( unsigned aCtl )
 
         std::set<SCH_COMPONENT*, decltype( cmp )> ordered_components( cmp );
 
-        for( auto item : sheetList[i].LastScreen()->Items().OfType( SCH_COMPONENT_T ) )
+        for( SCH_ITEM* item : sheetList[ii].LastScreen()->Items().OfType( SCH_COMPONENT_T ) )
         {
-            auto comp = static_cast<SCH_COMPONENT*>( item );
-            auto test = ordered_components.insert( comp );
+            SCH_COMPONENT* comp = static_cast<SCH_COMPONENT*>( item );
+            auto           test = ordered_components.insert( comp );
 
             if( !test.second )
             {
@@ -233,7 +233,7 @@ XNODE* NETLIST_EXPORTER_GENERIC::makeComponents( unsigned aCtl )
             }
         }
 
-        for( auto item : ordered_components )
+        for( EDA_ITEM* item : ordered_components )
         {
             SCH_COMPONENT* comp = findNextComponent( item, &sheet );
 
@@ -242,17 +242,16 @@ XNODE* NETLIST_EXPORTER_GENERIC::makeComponents( unsigned aCtl )
                || ( ( aCtl & GNL_OPT_KICAD ) && !comp->GetIncludeOnBoard() ) )
                 continue;
 
-            XNODE* xcomp;  // current component being constructed
-
             // Output the component's elements in order of expected access frequency.
             // This may not always look best, but it will allow faster execution
             // under XSL processing systems which do sequential searching within
             // an element.
 
+            XNODE* xcomp;  // current component being constructed
             xcomps->AddChild( xcomp = node( "comp" ) );
-            xcomp->AddAttribute( "ref", comp->GetRef( &sheet ) );
 
-            addComponentFields( xcomp, comp, &sheetList[i] );
+            xcomp->AddAttribute( "ref", comp->GetRef( &sheet ) );
+            addComponentFields( xcomp, comp, &sheetList[ii] );
 
             XNODE*  xlibsource;
             xcomp->AddChild( xlibsource = node( "libsource" ) );
@@ -268,9 +267,20 @@ XNODE* NETLIST_EXPORTER_GENERIC::makeComponents( unsigned aCtl )
 
             xlibsource->AddAttribute( "description", comp->GetDescription() );
 
-            XNODE* xsheetpath;
+            std::vector<SCH_FIELD>& fields = comp->GetFields();
 
+            for( size_t jj = MANDATORY_FIELDS; jj < fields.size(); ++jj )
+            {
+                XNODE* xproperty;
+                xcomp->AddChild( xproperty = node( "property" ) );
+
+                xproperty->AddAttribute( "name", fields[jj].GetName() );
+                xproperty->AddAttribute( "value", fields[jj].GetText() );
+            }
+
+            XNODE* xsheetpath;
             xcomp->AddChild( xsheetpath = node( "sheetpath" ) );
+
             xsheetpath->AddAttribute( "names", sheet.PathHumanReadable() );
             xsheetpath->AddAttribute( "tstamps", sheet.PathAsString() );
             xcomp->AddChild( node( "tstamp", comp->m_Uuid.AsString() ) );
diff --git a/pcbnew/class_module.cpp b/pcbnew/class_module.cpp
index 5a8f5babf7..c988d6a30b 100644
--- a/pcbnew/class_module.cpp
+++ b/pcbnew/class_module.cpp
@@ -373,6 +373,11 @@ bool MODULE::ResolveTextVar( wxString* token, int aDepth ) const
         *token = GetLayerName();
         return true;
     }
+    else if( m_properties.count( *token ) )
+    {
+        *token = m_properties.at( *token );
+        return true;
+    }
 
     return false;
 }
diff --git a/pcbnew/class_module.h b/pcbnew/class_module.h
index bb17ac7440..aaf822e4c8 100644
--- a/pcbnew/class_module.h
+++ b/pcbnew/class_module.h
@@ -488,6 +488,9 @@ public:
     TEXTE_MODULE& Value() const { return *m_Value; }
     TEXTE_MODULE& Reference() const { return *m_Reference; }
 
+    const std::map<wxString, wxString>& GetProperties() const { return m_properties; }
+    void SetProperties( const std::map<wxString, wxString>& aProps ) { m_properties = aProps; }
+
     /**
      * Function FindPadByName
      * returns a D_PAD* with a matching name.  Note that names may not be
@@ -696,10 +699,9 @@ public:
 #endif
 
 private:
-    DRAWINGS        m_drawings;         // BOARD_ITEMs for drawings on the board, owned by pointer.
-    PADS            m_pads;             // D_PAD items, owned by pointer
+    DRAWINGS               m_drawings;  // BOARD_ITEMs for drawings on the board, owned by pointer.
+    PADS                   m_pads;      // D_PAD items, owned by pointer
     MODULE_ZONE_CONTAINERS m_fp_zones;  // MODULE_ZONE_CONTAINER items, owned by pointer
-    std::list<MODULE_3D_SETTINGS> m_3D_Drawings;  // Linked list of 3D models.
 
     double         m_Orient;            // Orientation in tenths of a degree, 900=90.0 degrees.
     wxPoint        m_Pos;               // Position of module on the board in internal units.
@@ -727,13 +729,13 @@ private:
     int            m_CntRot90;          // Horizontal automatic placement cost ( 0..10 ).
     int            m_CntRot180;         // Vertical automatic placement cost ( 0..10 ).
 
-    wxArrayString* m_initial_comments;  ///< leading s-expression comments in the module,
-                                        ///< lazily allocated only if needed for speed
+    std::list<MODULE_3D_SETTINGS> m_3D_Drawings;  // Linked list of 3D models.
+    std::map<wxString, wxString>  m_properties;
+    wxArrayString*                m_initial_comments;  // s-expression comments in the module,
+                                                       // lazily allocated only if needed for speed
 
-    /// Used in DRC to test the courtyard area (a polygon which can be not basic
-    /// Note also a footprint can have courtyards on both board sides
-    SHAPE_POLY_SET m_poly_courtyard_front;
-    SHAPE_POLY_SET m_poly_courtyard_back;
+    SHAPE_POLY_SET m_poly_courtyard_front;  // Note that a module can have both front and back
+    SHAPE_POLY_SET m_poly_courtyard_back;   // courtyards populated.
 };
 
 #endif     // MODULE_H_
diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp
index 39aa354513..d3b74dc2fd 100644
--- a/pcbnew/kicad_plugin.cpp
+++ b/pcbnew/kicad_plugin.cpp
@@ -954,6 +954,15 @@ void PCB_IO::format( MODULE* aModule, int aNestLevel ) const
         m_out->Print( aNestLevel+1, "(tags %s)\n",
                       m_out->Quotew( aModule->GetKeywords() ).c_str() );
 
+    const std::map<wxString, wxString>& props = aModule->GetProperties();
+
+    for( const std::pair<const wxString, wxString>& prop : props )
+    {
+        m_out->Print( aNestLevel+1, "(property %s %s)\n",
+                      m_out->Quotew( prop.first ).c_str(),
+                      m_out->Quotew( prop.second ).c_str() );
+    }
+
     if( !( m_ctl & CTL_OMIT_PATH ) && !aModule->GetPath().empty() )
         m_out->Print( aNestLevel+1, "(path %s)\n",
                       m_out->Quotew( aModule->GetPath().AsString() ).c_str() );
diff --git a/pcbnew/kicad_plugin.h b/pcbnew/kicad_plugin.h
index dcefbd8c92..4793155d68 100644
--- a/pcbnew/kicad_plugin.h
+++ b/pcbnew/kicad_plugin.h
@@ -73,7 +73,8 @@ class TEXTE_PCB;
 //#define SEXPR_BOARD_FILE_VERSION    20200625  // Multilayer zones, zone names, island controls
 //#define SEXPR_BOARD_FILE_VERSION    20200628  // remove visibility settings
 //#define SEXPR_BOARD_FILE_VERSION    20200724  // Add KIID to module components
-#define SEXPR_BOARD_FILE_VERSION    20200807  // Add zone hatch advanced settings
+//#define SEXPR_BOARD_FILE_VERSION    20200807  // Add zone hatch advanced settings
+#define SEXPR_BOARD_FILE_VERSION    20200808  // Add properties to modules
 
 #define CTL_STD_LAYER_NAMES         (1 << 0)    ///< Use English Standard layer names
 #define CTL_OMIT_NETS               (1 << 1)    ///< Omit pads net names (useless in library)
diff --git a/pcbnew/netlist_reader/board_netlist_updater.cpp b/pcbnew/netlist_reader/board_netlist_updater.cpp
index ab3dfeb914..15104454a4 100644
--- a/pcbnew/netlist_reader/board_netlist_updater.cpp
+++ b/pcbnew/netlist_reader/board_netlist_updater.cpp
@@ -285,7 +285,7 @@ bool BOARD_NETLIST_UPDATER::updateComponentParameters( MODULE* aPcbComponent,
                     aPcbComponent->GetReference(),
                     aPcbComponent->GetPath().AsString(),
                     aNewComponent->GetPath().AsString() );
-        m_reporter->Report( msg, RPT_SEVERITY_INFO );
+        m_reporter->Report( msg, RPT_SEVERITY_ACTION );
 
         if( !m_isDryRun )
         {
@@ -294,6 +294,19 @@ bool BOARD_NETLIST_UPDATER::updateComponentParameters( MODULE* aPcbComponent,
         }
     }
 
+    if( aPcbComponent->GetProperties() != aNewComponent->GetProperties() )
+    {
+        msg.Printf( _( "Update %s properties." ),
+                    aPcbComponent->GetReference() );
+        m_reporter->Report( msg, RPT_SEVERITY_ACTION );
+
+        if( !m_isDryRun )
+        {
+            changed = true;
+            aPcbComponent->SetProperties( aNewComponent->GetProperties() );
+        }
+    }
+
     if( changed && copy )
         m_commit.Modified( aPcbComponent, copy );
     else
diff --git a/pcbnew/netlist_reader/kicad_netlist_reader.cpp b/pcbnew/netlist_reader/kicad_netlist_reader.cpp
index 37f54a97cd..0ebb0d7d70 100644
--- a/pcbnew/netlist_reader/kicad_netlist_reader.cpp
+++ b/pcbnew/netlist_reader/kicad_netlist_reader.cpp
@@ -282,11 +282,12 @@ void KICAD_NETLIST_PARSER::parseComponent()
 {
    /* Parses a section like
      * (comp (ref P1)
-     * (value DB25FEMALE)
-     * (footprint DB25FC)
-     * (libsource (lib conn) (part DB25))
-     * (sheetpath (names /) (tstamps /))
-     * (tstamp 68183921-93a5-49ac-91b0-49d05a0e1647))
+     *   (value DB25FEMALE)
+     *   (footprint DB25FC)
+     *   (libsource (lib conn) (part DB25))
+     *   (property (name PINCOUNT) (value 25))
+     *   (sheetpath (names /) (tstamps /))
+     *   (tstamp 68183921-93a5-49ac-91b0-49d05a0e1647))
      *
      * other fields (unused) are skipped
      * A component need a reference, value, footprint name and a full time stamp
@@ -300,6 +301,7 @@ void KICAD_NETLIST_PARSER::parseComponent()
     wxString    name;
     KIID_PATH   path;
     KIID        uuid;
+    std::map<wxString, wxString> properties;
 
     // The token comp was read, so the next data is (ref P1)
     while( (token = NextTok()) != T_RIGHT )
@@ -329,7 +331,7 @@ void KICAD_NETLIST_PARSER::parseComponent()
 
         case T_libsource:
             // Read libsource
-            while( (token = NextTok()) != T_RIGHT )
+            while( ( token = NextTok() ) != T_RIGHT )
             {
                 if( token == T_LEFT )
                     token = NextTok();
@@ -358,6 +360,39 @@ void KICAD_NETLIST_PARSER::parseComponent()
             }
             break;
 
+        case T_property:
+        {
+            wxString propName;
+            wxString propValue;
+
+            while( (token = NextTok() ) != T_RIGHT )
+            {
+                if( token == T_LEFT )
+                    token = NextTok();
+
+                if( token == T_name )
+                {
+                    NeedSYMBOLorNUMBER();
+                    propName = FROM_UTF8( CurText() );
+                    NeedRIGHT();
+                }
+                else if( token == T_value )
+                {
+                    NeedSYMBOLorNUMBER();
+                    propValue = FROM_UTF8( CurText() );
+                    NeedRIGHT();
+                }
+                else
+                {
+                    Expecting( "name or value" );
+                }
+            }
+
+            if( !propName.IsEmpty() )
+                properties[ propName ] = propValue;
+        }
+            break;
+
         case T_sheetpath:
             while( ( token = NextTok() ) != T_EOF )
             {
@@ -397,6 +432,7 @@ void KICAD_NETLIST_PARSER::parseComponent()
     COMPONENT* component = new COMPONENT( fpid, ref, value, path );
     component->SetName( name );
     component->SetLibrary( library );
+    component->SetProperties( properties );
     m_netlist->AddComponent( component );
 }
 
diff --git a/pcbnew/netlist_reader/netlist_reader.h b/pcbnew/netlist_reader/netlist_reader.h
index 13b3e0c313..b2d5a051a5 100644
--- a/pcbnew/netlist_reader/netlist_reader.h
+++ b/pcbnew/netlist_reader/netlist_reader.h
@@ -309,11 +309,12 @@ private:
      * Function parseComponent
      * parse a component description:
      * (comp (ref P1)
-     * (value DB25FEMELLE)
-     * (footprint DB25FC)
-     * (libsource (lib conn) (part DB25))
-     * (sheetpath (names /) (tstamps /))
-     * (tstamp 3256759C))
+     *   (value DB25FEMELLE)
+     *   (footprint DB25FC)
+     *   (libsource (lib conn) (part DB25))
+     *   (property (name PINCOUNT) (value 25))
+     *   (sheetpath (names /) (tstamps /))
+     *   (tstamp 3256759C))
      */
     void parseComponent();
 
@@ -321,9 +322,9 @@ private:
      * Function parseNet
      * Parses a section like
      * (net (code 20) (name /PC-A0)
-     *  (node (ref BUS1) (pin 62))
-     *  (node (ref U3) (pin 3))
-     *  (node (ref U9) (pin M6)))
+     *   (node (ref BUS1) (pin 62))
+     *   (node (ref U3) (pin 3))
+     *   (node (ref U9) (pin M6)))
      *
      * and set the corresponding pads netnames
      */
diff --git a/pcbnew/netlist_reader/pcb_netlist.cpp b/pcbnew/netlist_reader/pcb_netlist.cpp
index 28e0f352a6..0fccea2d6c 100644
--- a/pcbnew/netlist_reader/pcb_netlist.cpp
+++ b/pcbnew/netlist_reader/pcb_netlist.cpp
@@ -48,6 +48,7 @@ void COMPONENT::SetModule( MODULE* aModule )
     aModule->SetValue( m_value );
     aModule->SetFPID( m_fpid );
     aModule->SetPath( m_path );
+    aModule->SetProperties( m_properties );
 }
 
 
@@ -56,10 +57,10 @@ COMPONENT_NET COMPONENT::m_emptyNet;
 
 const COMPONENT_NET& COMPONENT::GetNet( const wxString& aPinName ) const
 {
-    for( unsigned i = 0;  i < m_nets.size();  i++ )
+    for( const COMPONENT_NET& net : m_nets )
     {
-        if( m_nets[i].GetPinName() == aPinName )
-            return m_nets[i];
+        if( net.GetPinName() == aPinName )
+            return net;
     }
 
     return m_emptyNet;
diff --git a/pcbnew/netlist_reader/pcb_netlist.h b/pcbnew/netlist_reader/pcb_netlist.h
index e3eb7cc53f..7a07be5a56 100644
--- a/pcbnew/netlist_reader/pcb_netlist.h
+++ b/pcbnew/netlist_reader/pcb_netlist.h
@@ -105,6 +105,9 @@ class COMPONENT
     /// The #MODULE loaded for #m_fpid.
     std::unique_ptr< MODULE > m_footprint;
 
+    /// Component-specific properties found in the netlist.
+    std::map<wxString, wxString> m_properties;
+
     static COMPONENT_NET    m_emptyNet;
 
 public:
@@ -142,9 +145,14 @@ public:
     const wxString& GetLibrary() const { return m_library; }
 
     const wxString& GetReference() const { return m_reference; }
-
     const wxString& GetValue() const { return m_value; }
 
+    void SetProperties( std::map<wxString, wxString>& aProps )
+    {
+        m_properties = std::move( aProps );
+    }
+    const std::map<wxString, wxString>& GetProperties() const { return m_properties; }
+
     void SetFPID( const LIB_ID& aFPID ) { m_fpid = aFPID;  }
     const LIB_ID& GetFPID() const { return m_fpid; }
 
diff --git a/pcbnew/pcb_parser.cpp b/pcbnew/pcb_parser.cpp
index 55db4553c4..4857ac79da 100644
--- a/pcbnew/pcb_parser.cpp
+++ b/pcbnew/pcb_parser.cpp
@@ -2454,6 +2454,8 @@ MODULE* PCB_PARSER::parseMODULE_unchecked( wxArrayString* aInitialComments )
 
     std::unique_ptr<MODULE> module( new MODULE( m_board ) );
 
+    std::map<wxString, wxString> properties;
+
     module->SetInitialComments( aInitialComments );
 
     token = NextTok();
@@ -2550,6 +2552,14 @@ MODULE* PCB_PARSER::parseMODULE_unchecked( wxArrayString* aInitialComments )
             NeedRIGHT();
             break;
 
+        case T_property:
+            NeedSYMBOL();
+            name = FromUTF8();
+            NeedSYMBOL();
+            properties[ name ] = FromUTF8();
+            NeedRIGHT();
+            break;
+
         case T_path:
             NeedSYMBOLorNUMBER(); // Paths can be numerical so a number is also a symbol here
             module->SetPath( KIID_PATH( FromUTF8() ) );
@@ -2717,6 +2727,7 @@ MODULE* PCB_PARSER::parseMODULE_unchecked( wxArrayString* aInitialComments )
     }
 
     module->SetFPID( fpid );
+    module->SetProperties( properties );
     module->CalculateBoundingBox();
 
     return module.release();