From 0a609dd48d5b31adc2b5c64725633189893ca36d Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Sun, 11 Jul 2021 12:49:36 +0100
Subject: [PATCH] Add footprint library checking to DRC.

Fixes https://gitlab.com/kicad/code/kicad/issues/6821
---
 pcbnew/CMakeLists.txt                         |   3 +-
 .../dialogs/dialog_footprint_properties.cpp   |   2 +-
 .../dialog_footprint_properties_fp_editor.cpp |   2 +-
 pcbnew/drc/drc_item.cpp                       |   5 +
 pcbnew/drc/drc_item.h                         |   2 +
 pcbnew/drc/drc_test_provider.h                |   7 -
 .../drc/drc_test_provider_annular_width.cpp   |   8 -
 pcbnew/drc/drc_test_provider_connectivity.cpp |   8 -
 .../drc_test_provider_copper_clearance.cpp    |   8 -
 .../drc_test_provider_courtyard_clearance.cpp |   8 -
 .../drc_test_provider_diff_pair_coupling.cpp  |   5 -
 pcbnew/drc/drc_test_provider_disallow.cpp     |   8 -
 .../drc/drc_test_provider_edge_clearance.cpp  |   8 -
 pcbnew/drc/drc_test_provider_hole_size.cpp    |   8 -
 pcbnew/drc/drc_test_provider_hole_to_hole.cpp |   8 -
 .../drc/drc_test_provider_library_parity.cpp  | 519 ++++++++++++++++++
 .../drc/drc_test_provider_matched_length.cpp  |   5 -
 pcbnew/drc/drc_test_provider_misc.cpp         |   8 -
 ...=> drc_test_provider_schematic_parity.cpp} |  34 +-
 .../drc/drc_test_provider_silk_clearance.cpp  |   5 -
 pcbnew/drc/drc_test_provider_silk_to_mask.cpp |   5 -
 pcbnew/drc/drc_test_provider_track_width.cpp  |   8 -
 pcbnew/drc/drc_test_provider_via_diameter.cpp |   8 -
 pcbnew/footprint.cpp                          |  76 ++-
 pcbnew/footprint.h                            |  16 +-
 pcbnew/plugins/kicad/pcb_plugin.cpp           |   4 +-
 qa/data/issue1358.kicad_pro                   |   1 +
 qa/data/issue2528.kicad_pro                   |   1 +
 qa/data/issue4774.kicad_pro                   |   1 +
 qa/data/issue5854.kicad_pro                   |   1 +
 qa/data/issue5978.kicad_pro                   |   1 +
 qa/data/issue5990.kicad_pro                   |   1 +
 qa/data/issue6879.kicad_pro                   |   1 +
 qa/data/issue6945.kicad_pro                   |   1 +
 qa/data/issue7267.kicad_pro                   |   1 +
 qa/data/issue7325.kicad_pro                   |   1 +
 qa/data/issue7975.kicad_pro                   |   1 +
 qa/data/issue8003.kicad_pro                   |  28 +-
 qa/data/issue8407.kicad_pro                   |   1 +
 qa/data/issue9081.kicad_pro                   |   1 +
 qa/drc_proto/CMakeLists.txt                   |   2 +-
 qa/pns/CMakeLists.txt                         |   2 +-
 42 files changed, 657 insertions(+), 166 deletions(-)
 create mode 100644 pcbnew/drc/drc_test_provider_library_parity.cpp
 rename pcbnew/drc/{drc_test_provider_lvs.cpp => drc_test_provider_schematic_parity.cpp} (90%)

diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 0ae24f0bd5..b551222a45 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -238,7 +238,8 @@ set( PCBNEW_DRC_SRCS
     drc/drc_test_provider_edge_clearance.cpp
     drc/drc_test_provider_hole_to_hole.cpp
     drc/drc_test_provider_hole_size.cpp
-    drc/drc_test_provider_lvs.cpp
+    drc/drc_test_provider_library_parity.cpp
+    drc/drc_test_provider_schematic_parity.cpp
     drc/drc_test_provider_misc.cpp
     drc/drc_test_provider_track_width.cpp
     drc/drc_test_provider_via_diameter.cpp
diff --git a/pcbnew/dialogs/dialog_footprint_properties.cpp b/pcbnew/dialogs/dialog_footprint_properties.cpp
index a64885fd3a..1a4a16f9e5 100644
--- a/pcbnew/dialogs/dialog_footprint_properties.cpp
+++ b/pcbnew/dialogs/dialog_footprint_properties.cpp
@@ -508,7 +508,7 @@ bool DIALOG_FOOTPRINT_PROPERTIES::TransferDataFromWindow()
 
     // Copy the models from the panel to the footprint
     std::vector<FP_3DMODEL>& panelList = m_3dPanel->GetModelList();
-    std::list<FP_3DMODEL>*   fpList    = &m_footprint->Models();
+    std::vector<FP_3DMODEL>* fpList    = &m_footprint->Models();
     fpList->clear();
     fpList->insert( fpList->end(), panelList.begin(), panelList.end() );
 
diff --git a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp
index 36749fa09e..b2f4973555 100644
--- a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp
+++ b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp
@@ -443,7 +443,7 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataFromWindow()
 
     // Copy the models from the panel to the footprint
     std::vector<FP_3DMODEL>& panelList = m_3dPanel->GetModelList();
-    std::list<FP_3DMODEL>*   fpList    = &m_footprint->Models();
+    std::vector<FP_3DMODEL>* fpList    = &m_footprint->Models();
     fpList->clear();
     fpList->insert( fpList->end(), panelList.begin(), panelList.end() );
 
diff --git a/pcbnew/drc/drc_item.cpp b/pcbnew/drc/drc_item.cpp
index 8af220c15b..27d9649815 100644
--- a/pcbnew/drc/drc_item.cpp
+++ b/pcbnew/drc/drc_item.cpp
@@ -170,6 +170,10 @@ DRC_ITEM DRC_ITEM::netConflict( DRCE_NET_CONFLICT,
         _( "Pad net doesn't match schematic" ),
         wxT( "net_conflict" ) );
 
+DRC_ITEM DRC_ITEM::libFootprintIssues( DRCE_LIB_FOOTPRINT_ISSUES,
+        _( "Library footprint issue" ),
+        wxT( "lib_footprint_issues" ) );
+
 DRC_ITEM DRC_ITEM::unresolvedVariable( DRCE_UNRESOLVED_VARIABLE,
         _( "Unresolved text variable" ),
         wxT( "unresolved_variable" ) );
@@ -298,6 +302,7 @@ std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode )
     case DRCE_DUPLICATE_FOOTPRINT:      return std::make_shared<DRC_ITEM>( duplicateFootprints );
     case DRCE_NET_CONFLICT:             return std::make_shared<DRC_ITEM>( netConflict );
     case DRCE_EXTRA_FOOTPRINT:          return std::make_shared<DRC_ITEM>( extraFootprint );
+    case DRCE_LIB_FOOTPRINT_ISSUES:     return std::make_shared<DRC_ITEM>( libFootprintIssues );
     case DRCE_UNRESOLVED_VARIABLE:      return std::make_shared<DRC_ITEM>( unresolvedVariable );
     case DRCE_OVERLAPPING_SILK:         return std::make_shared<DRC_ITEM>( silkOverlaps );
     case DRCE_SILK_MASK_CLEARANCE:      return std::make_shared<DRC_ITEM>( silkMaskClearance );
diff --git a/pcbnew/drc/drc_item.h b/pcbnew/drc/drc_item.h
index 2202b8d9bc..b5c29b7fd7 100644
--- a/pcbnew/drc/drc_item.h
+++ b/pcbnew/drc/drc_item.h
@@ -68,6 +68,7 @@ enum PCB_DRC_CODE {
     DRCE_NET_CONFLICT,                   // pad net doesn't match netlist
 
     DRCE_FOOTPRINT_TYPE_MISMATCH,        // footprint attribute does not match actual pads
+    DRCE_LIB_FOOTPRINT_ISSUES,           // footprint does not match the current library
     DRCE_PAD_TH_WITH_NO_HOLE,            // footprint has Plated Through-Hole with no hole
 
     DRCE_UNRESOLVED_VARIABLE,
@@ -164,6 +165,7 @@ private:
     static DRC_ITEM missingFootprint;
     static DRC_ITEM extraFootprint;
     static DRC_ITEM netConflict;
+    static DRC_ITEM libFootprintIssues;
     static DRC_ITEM unresolvedVariable;
     static DRC_ITEM silkMaskClearance;
     static DRC_ITEM silkOverlaps;
diff --git a/pcbnew/drc/drc_test_provider.h b/pcbnew/drc/drc_test_provider.h
index c410aeb41a..ef2ecdaf60 100644
--- a/pcbnew/drc/drc_test_provider.h
+++ b/pcbnew/drc/drc_test_provider.h
@@ -91,13 +91,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const = 0;
 
-    virtual int GetNumPhases() const = 0;
-
-    virtual bool IsRuleDriven() const
-    {
-        return m_isRuleDriven;
-    }
-
     bool IsEnabled() const
     {
         return m_enabled;
diff --git a/pcbnew/drc/drc_test_provider_annular_width.cpp b/pcbnew/drc/drc_test_provider_annular_width.cpp
index 0bde18c48e..38ad04faf8 100644
--- a/pcbnew/drc/drc_test_provider_annular_width.cpp
+++ b/pcbnew/drc/drc_test_provider_annular_width.cpp
@@ -63,8 +63,6 @@ public:
     }
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
-
-    int GetNumPhases() const override;
 };
 
 
@@ -164,12 +162,6 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run()
 }
 
 
-int DRC_TEST_PROVIDER_ANNULAR_WIDTH::GetNumPhases() const
-{
-    return 1;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_ANNULAR_WIDTH::GetConstraintTypes() const
 {
     return { ANNULAR_WIDTH_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_connectivity.cpp b/pcbnew/drc/drc_test_provider_connectivity.cpp
index 4acf134e31..6be0b52e8a 100644
--- a/pcbnew/drc/drc_test_provider_connectivity.cpp
+++ b/pcbnew/drc/drc_test_provider_connectivity.cpp
@@ -65,8 +65,6 @@ public:
     }
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
-
-    int GetNumPhases() const override;
 };
 
 
@@ -175,12 +173,6 @@ bool DRC_TEST_PROVIDER_CONNECTIVITY::Run()
 }
 
 
-int DRC_TEST_PROVIDER_CONNECTIVITY::GetNumPhases() const
-{
-    return 3;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_CONNECTIVITY::GetConstraintTypes() const
 {
     return {};
diff --git a/pcbnew/drc/drc_test_provider_copper_clearance.cpp b/pcbnew/drc/drc_test_provider_copper_clearance.cpp
index 977467e1ea..2abc25894b 100644
--- a/pcbnew/drc/drc_test_provider_copper_clearance.cpp
+++ b/pcbnew/drc/drc_test_provider_copper_clearance.cpp
@@ -80,8 +80,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
     bool testTrackAgainstItem( PCB_TRACK* track, SHAPE* trackShape, PCB_LAYER_ID layer,
                                BOARD_ITEM* other );
@@ -1006,12 +1004,6 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZonesToZones()
 }
 
 
-int DRC_TEST_PROVIDER_COPPER_CLEARANCE::GetNumPhases() const
-{
-    return 4;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_COPPER_CLEARANCE::GetConstraintTypes() const
 {
     return { CLEARANCE_CONSTRAINT, HOLE_CLEARANCE_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp b/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp
index 39cb51f830..677fed73da 100644
--- a/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp
+++ b/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp
@@ -69,8 +69,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
     bool testFootprintCourtyardDefinitions();
 
@@ -319,12 +317,6 @@ bool DRC_TEST_PROVIDER_COURTYARD_CLEARANCE::Run()
 }
 
 
-int DRC_TEST_PROVIDER_COURTYARD_CLEARANCE::GetNumPhases() const
-{
-    return 2;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_COURTYARD_CLEARANCE::GetConstraintTypes() const
 {
     return { COURTYARD_CLEARANCE_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp b/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp
index 634cf7cafa..bd03165d83 100644
--- a/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp
+++ b/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp
@@ -74,11 +74,6 @@ public:
         return "Tests differential pair coupling";
     }
 
-    virtual int GetNumPhases() const override
-    {
-        return 1;
-    }
-
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
 private:
diff --git a/pcbnew/drc/drc_test_provider_disallow.cpp b/pcbnew/drc/drc_test_provider_disallow.cpp
index 52a56f7692..b84a0f609b 100644
--- a/pcbnew/drc/drc_test_provider_disallow.cpp
+++ b/pcbnew/drc/drc_test_provider_disallow.cpp
@@ -60,8 +60,6 @@ public:
     }
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
-
-    int GetNumPhases() const override;
 };
 
 
@@ -169,12 +167,6 @@ bool DRC_TEST_PROVIDER_DISALLOW::Run()
 }
 
 
-int DRC_TEST_PROVIDER_DISALLOW::GetNumPhases() const
-{
-    return 1;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_DISALLOW::GetConstraintTypes() const
 {
     return { DISALLOW_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_edge_clearance.cpp b/pcbnew/drc/drc_test_provider_edge_clearance.cpp
index 040ce53d8a..6ef6b42e6d 100644
--- a/pcbnew/drc/drc_test_provider_edge_clearance.cpp
+++ b/pcbnew/drc/drc_test_provider_edge_clearance.cpp
@@ -70,8 +70,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
     bool testAgainstEdge( BOARD_ITEM* item, SHAPE* itemShape, BOARD_ITEM* other,
                           DRC_CONSTRAINT_T aConstraintType, PCB_DRC_CODE aErrorCode );
@@ -282,12 +280,6 @@ bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::Run()
 }
 
 
-int DRC_TEST_PROVIDER_EDGE_CLEARANCE::GetNumPhases() const
-{
-    return 1;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_EDGE_CLEARANCE::GetConstraintTypes() const
 {
     return { EDGE_CLEARANCE_CONSTRAINT, SILK_CLEARANCE_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_hole_size.cpp b/pcbnew/drc/drc_test_provider_hole_size.cpp
index 83a0e6d0d7..707f27868e 100644
--- a/pcbnew/drc/drc_test_provider_hole_size.cpp
+++ b/pcbnew/drc/drc_test_provider_hole_size.cpp
@@ -62,8 +62,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
     void checkVia( PCB_VIA* via, bool aExceedMicro, bool aExceedStd );
     void checkPad( PAD* aPad );
@@ -251,12 +249,6 @@ void DRC_TEST_PROVIDER_HOLE_SIZE::checkVia( PCB_VIA* via, bool aExceedMicro, boo
 }
 
 
-int DRC_TEST_PROVIDER_HOLE_SIZE::GetNumPhases() const
-{
-    return 2;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_HOLE_SIZE::GetConstraintTypes() const
 {
     return { HOLE_SIZE_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_hole_to_hole.cpp b/pcbnew/drc/drc_test_provider_hole_to_hole.cpp
index fa9800369b..e357052f26 100644
--- a/pcbnew/drc/drc_test_provider_hole_to_hole.cpp
+++ b/pcbnew/drc/drc_test_provider_hole_to_hole.cpp
@@ -70,8 +70,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
     bool testHoleAgainstHole( BOARD_ITEM* aItem, SHAPE_CIRCLE* aHole, BOARD_ITEM* aOther );
 
@@ -327,12 +325,6 @@ bool DRC_TEST_PROVIDER_HOLE_TO_HOLE::testHoleAgainstHole( BOARD_ITEM* aItem, SHA
 }
 
 
-int DRC_TEST_PROVIDER_HOLE_TO_HOLE::GetNumPhases() const
-{
-    return 1;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_HOLE_TO_HOLE::GetConstraintTypes() const
 {
     return { HOLE_TO_HOLE_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_library_parity.cpp b/pcbnew/drc/drc_test_provider_library_parity.cpp
new file mode 100644
index 0000000000..424339c3d3
--- /dev/null
+++ b/pcbnew/drc/drc_test_provider_library_parity.cpp
@@ -0,0 +1,519 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2021 KiCad Developers.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <kiway.h>
+#include <netlist_reader/pcb_netlist.h>
+#include <fp_lib_table.h>
+#include <board.h>
+#include <fp_shape.h>
+#include <fp_text.h>
+#include <zone.h>
+#include <footprint.h>
+#include <pad.h>
+#include <drc/drc_engine.h>
+#include <drc/drc_item.h>
+#include <drc/drc_test_provider.h>
+#include <macros.h>
+
+/*
+    Library parity test.
+
+    Errors generated:
+    - DRCE_LIB_FOOTPRINT_ISSUES
+*/
+
+class DRC_TEST_PROVIDER_LIBRARY_PARITY : public DRC_TEST_PROVIDER
+{
+public:
+    DRC_TEST_PROVIDER_LIBRARY_PARITY()
+    {
+        m_isRuleDriven = false;
+    }
+
+    virtual ~DRC_TEST_PROVIDER_LIBRARY_PARITY()
+    {
+    }
+
+    virtual bool Run() override;
+
+    virtual const wxString GetName() const override
+    {
+        return "library_parity";
+    };
+
+    virtual const wxString GetDescription() const override
+    {
+        return "Performs board footprint vs library integity checks";
+    }
+
+    virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
+};
+
+
+#define TEST( a, b ) { if( a != b ) return true; }
+#define TEST_PADS( a, b ) { if( padsNeedUpdate( a, b ) ) return true; }
+#define TEST_SHAPES( a, b ) { if( shapesNeedUpdate( a, b ) ) return true; }
+#define TEST_PRIMITIVES( a, b ) { if( primitivesNeedUpdate( a, b ) ) return true; }
+#define TEST_ZONES( a, b ) { if( zonesNeedUpdate( a, b ) ) return true; }
+#define TEST_MODELS( a, b ) { if( modelsNeedUpdate( a, b ) ) return true; }
+
+
+bool primitivesNeedUpdate( const std::shared_ptr<PCB_SHAPE>& a,
+                           const std::shared_ptr<PCB_SHAPE>& b )
+{
+    TEST( a->GetShape(), b->GetShape() );
+
+    switch( a->GetShape() )
+    {
+    case SHAPE_T::SEGMENT:
+    case SHAPE_T::RECT:
+    case SHAPE_T::CIRCLE:
+        TEST( a->GetStart(), b->GetStart() );
+        TEST( a->GetEnd(), b->GetEnd() );
+        break;
+
+    case SHAPE_T::ARC:
+        TEST( a->GetStart(), b->GetStart() );
+        TEST( a->GetEnd(), b->GetEnd() );
+        TEST( a->GetCenter(), b->GetCenter() );
+        TEST( a->GetArcAngle(), b->GetArcAngle() );
+        break;
+
+    case SHAPE_T::BEZIER:
+        TEST( a->GetStart(), b->GetStart() );
+        TEST( a->GetEnd(), b->GetEnd() );
+        TEST( a->GetBezierC1(), b->GetBezierC1() );
+        TEST( a->GetBezierC2(), b->GetBezierC2() );
+        break;
+
+    case SHAPE_T::POLY:
+        TEST( a->GetPolyShape().TotalVertices(), b->GetPolyShape().TotalVertices() );
+
+        for( int ii = 0; ii < a->GetPolyShape().TotalVertices(); ++ii )
+            TEST( a->GetPolyShape().CVertex( ii ), b->GetPolyShape().CVertex( ii ) );
+
+        break;
+
+    default:
+        UNIMPLEMENTED_FOR( a->SHAPE_T_asString() );
+    }
+
+    TEST( a->GetWidth(), b->GetWidth() );
+    TEST( a->IsFilled(), b->IsFilled() );
+
+    return false;
+}
+
+
+bool padsNeedUpdate( const PAD* a, const PAD* b )
+{
+    TEST( a->GetPadToDieLength(), b->GetPadToDieLength() );
+    TEST( a->GetPos0(), b->GetPos0() );
+
+    TEST( a->GetNumber(), b->GetNumber() );
+
+    // These are assigned from the schematic and not from the library
+    // TEST( a->GetPinFunction(), b->GetPinFunction() );
+    // TEST( a->GetPinType(), b->GetPinType() );
+
+    TEST( a->GetRemoveUnconnected(), b->GetRemoveUnconnected() );
+
+    // NB: KeepTopBottom is undefined if RemoveUnconnected is NOT set.
+    if( a->GetRemoveUnconnected() )
+        TEST( a->GetKeepTopBottom(), b->GetKeepTopBottom() );
+
+    TEST( a->GetShape(), b->GetShape() );
+    TEST( a->GetLayerSet(), b->GetLayerSet() );
+    TEST( a->GetAttribute(), b->GetAttribute() );
+    TEST( a->GetProperty(), b->GetProperty() );
+
+    // The pad orientation, for historical reasons is the pad rotation + parent rotation.
+    TEST( NormalizeAnglePos( a->GetOrientation() - a->GetParent()->GetOrientation() ),
+          NormalizeAnglePos( b->GetOrientation() - b->GetParent()->GetOrientation() ) );
+
+    TEST( a->GetSize(), b->GetSize() );
+    TEST( a->GetDelta(), b->GetDelta() );
+    TEST( a->GetRoundRectCornerRadius(), b->GetRoundRectCornerRadius() );
+    TEST( a->GetRoundRectRadiusRatio(), b->GetRoundRectRadiusRatio() );
+    TEST( a->GetChamferRectRatio(), b->GetChamferRectRatio() );
+    TEST( a->GetChamferPositions(), b->GetChamferPositions() );
+    TEST( a->GetOffset(), b->GetOffset() );
+
+    TEST( a->GetDrillShape(), b->GetDrillShape() );
+    TEST( a->GetDrillSize(), b->GetDrillSize() );
+
+    TEST( a->GetLocalClearance(), b->GetLocalClearance() );
+    TEST( a->GetLocalSolderMaskMargin(), b->GetLocalSolderMaskMargin() );
+    TEST( a->GetLocalSolderPasteMargin(), b->GetLocalSolderPasteMargin() );
+    TEST( a->GetLocalSolderPasteMarginRatio(), b->GetLocalSolderPasteMarginRatio() );
+
+    TEST( a->GetZoneConnection(), b->GetZoneConnection() );
+    TEST( a->GetThermalGap(), b->GetThermalGap() );
+    TEST( a->GetThermalSpokeWidth(), b->GetThermalSpokeWidth() );
+    TEST( a->GetCustomShapeInZoneOpt(), b->GetCustomShapeInZoneOpt() );
+
+    TEST( a->GetPrimitives().size(), b->GetPrimitives().size() );
+
+    for( size_t ii = 0; ii < a->GetPrimitives().size(); ++ii )
+        TEST_PRIMITIVES( a->GetPrimitives()[ii], b->GetPrimitives()[ii] );
+
+    return false;
+}
+
+
+bool shapesNeedUpdate( const FP_SHAPE* a, const FP_SHAPE* b )
+{
+    TEST( a->GetShape(), b->GetShape() );
+
+    switch( a->GetShape() )
+    {
+    case SHAPE_T::SEGMENT:
+    case SHAPE_T::RECT:
+    case SHAPE_T::CIRCLE:
+        TEST( a->GetStart0(), b->GetStart0() );
+        TEST( a->GetEnd0(), b->GetEnd0() );
+        break;
+
+    case SHAPE_T::ARC:
+        TEST( a->GetStart0(), b->GetStart0() );
+        TEST( a->GetEnd0(), b->GetEnd0() );
+        TEST( a->GetCenter0(), b->GetCenter0() );
+        TEST( a->GetArcAngle(), b->GetArcAngle() );
+        break;
+
+    case SHAPE_T::BEZIER:
+        TEST( a->GetStart0(), b->GetStart0() );
+        TEST( a->GetEnd0(), b->GetEnd0() );
+        TEST( a->GetBezierC1_0(), b->GetBezierC1_0() );
+        TEST( a->GetBezierC2_0(), b->GetBezierC2_0() );
+        break;
+
+    case SHAPE_T::POLY:
+        TEST( a->GetPolyShape().TotalVertices(), b->GetPolyShape().TotalVertices() );
+
+        for( int ii = 0; ii < a->GetPolyShape().TotalVertices(); ++ii )
+            TEST( a->GetPolyShape().CVertex( ii ), b->GetPolyShape().CVertex( ii ) );
+
+        break;
+
+    default:
+        UNIMPLEMENTED_FOR( a->SHAPE_T_asString() );
+    }
+
+    TEST( a->GetWidth(), b->GetWidth() );
+    TEST( a->IsFilled(), b->IsFilled() );
+
+    TEST( a->GetLayer(), b->GetLayer() );
+
+    return false;
+}
+
+
+bool textsNeedUpdate( const FP_TEXT* a, const FP_TEXT* b )
+{
+    TEST( a->GetLayer(), b->GetLayer() );
+    TEST( a->IsKeepUpright(), b->IsKeepUpright() );
+
+    TEST( a->GetText(), b->GetText() );
+
+    TEST( a->GetTextThickness(), b->GetTextThickness() );
+    TEST( a->GetTextAngle(), b->GetTextAngle() );
+    TEST( a->IsItalic(), b->IsItalic() );
+    TEST( a->IsBold(), b->IsBold() );
+    TEST( a->IsVisible(), b->IsVisible() );
+    TEST( a->IsMirrored(), b->IsMirrored() );
+
+    TEST( a->GetHorizJustify(), b->GetHorizJustify() );
+    TEST( a->GetVertJustify(), b->GetVertJustify() );
+
+    TEST( a->GetTextSize(), b->GetTextSize() );
+    TEST( a->GetPos0(), b->GetPos0() );
+
+    return false;
+}
+
+
+bool zonesNeedUpdate( const FP_ZONE* a, const FP_ZONE* b )
+{
+    TEST( a->GetCornerSmoothingType(), b->GetCornerSmoothingType() );
+    TEST( a->GetCornerRadius(), b->GetCornerRadius() );
+    TEST( a->GetZoneName(), b->GetZoneName() );
+    TEST( a->GetPriority(), b->GetPriority() );
+
+    TEST( a->GetIsRuleArea(), b->GetIsRuleArea() );
+    TEST( a->GetDoNotAllowCopperPour(), b->GetDoNotAllowCopperPour() );
+    TEST( a->GetDoNotAllowFootprints(), b->GetDoNotAllowFootprints() );
+    TEST( a->GetDoNotAllowPads(), b->GetDoNotAllowPads() );
+    TEST( a->GetDoNotAllowTracks(), b->GetDoNotAllowTracks() );
+    TEST( a->GetDoNotAllowVias(), b->GetDoNotAllowVias() );
+
+    TEST( a->GetLayerSet(), b->GetLayerSet() );
+
+    TEST( a->GetPadConnection(), b->GetPadConnection() );
+    TEST( a->GetLocalClearance(), b->GetLocalClearance() );
+    TEST( a->GetThermalReliefGap(), b->GetThermalReliefGap() );
+    TEST( a->GetThermalReliefSpokeWidth(), b->GetThermalReliefSpokeWidth() );
+
+    TEST( a->GetMinThickness(), b->GetMinThickness() );
+
+    TEST( a->GetFillVersion(), b->GetFillVersion() );
+    TEST( a->GetIslandRemovalMode(), b->GetIslandRemovalMode() );
+    TEST( a->GetMinIslandArea(), b->GetMinIslandArea() );
+
+    TEST( a->GetFillMode(), b->GetFillMode() );
+    TEST( a->GetHatchThickness(), b->GetHatchThickness() );
+    TEST( a->GetHatchGap(), b->GetHatchGap() );
+    TEST( a->GetHatchOrientation(), b->GetHatchOrientation() );
+    TEST( a->GetHatchSmoothingLevel(), b->GetHatchSmoothingLevel() );
+    TEST( a->GetHatchSmoothingValue(), b->GetHatchSmoothingValue() );
+    TEST( a->GetHatchBorderAlgorithm(), b->GetHatchBorderAlgorithm() );
+    TEST( a->GetHatchHoleMinArea(), b->GetHatchHoleMinArea() );
+
+    TEST( a->Outline()->TotalVertices(), b->Outline()->TotalVertices() );
+
+    for( int ii = 0; ii < a->Outline()->TotalVertices(); ++ii )
+    {
+        TEST( a->Outline()->CVertex( ii ) - a->GetParent()->GetPosition(),
+              b->Outline()->CVertex( ii ) - b->GetParent()->GetPosition() );
+    }
+
+    return false;
+}
+
+
+bool modelsNeedUpdate( const FP_3DMODEL& a, const FP_3DMODEL& b )
+{
+#define EPSILON 0.000001
+#define TEST_V3D( a, b ) { if( abs( a.x - b.x ) > EPSILON    \
+                            || abs( a.y - b.y ) > EPSILON    \
+                            || abs( a.z - b.z ) > EPSILON )  \
+                               return true;                  }
+
+    TEST_V3D( a.m_Scale, b.m_Scale );
+    TEST_V3D( a.m_Rotation, b.m_Rotation );
+    TEST_V3D( a.m_Offset, b.m_Offset );
+    TEST( a.m_Opacity, b.m_Opacity );
+    TEST( a.m_Filename, b.m_Filename );
+    TEST( a.m_Show, b.m_Show );
+
+    return false;
+}
+
+
+bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFootprint )
+{
+    TEST( GetDescription(), aLibFootprint->GetDescription() );
+    TEST( GetKeywords(), aLibFootprint->GetKeywords() );
+    TEST( GetAttributes(), aLibFootprint->GetAttributes() );
+
+    TEST( GetPlacementCost90(), aLibFootprint->GetPlacementCost90() );
+    TEST( GetPlacementCost180(), aLibFootprint->GetPlacementCost180() );
+
+    TEST( GetLocalClearance(), aLibFootprint->GetLocalClearance() );
+    TEST( GetLocalSolderMaskMargin(), aLibFootprint->GetLocalSolderMaskMargin() );
+    TEST( GetLocalSolderPasteMargin(), aLibFootprint->GetLocalSolderPasteMargin() );
+    TEST( GetLocalSolderPasteMarginRatio(), aLibFootprint->GetLocalSolderPasteMarginRatio() );
+
+    TEST( GetZoneConnection(), aLibFootprint->GetZoneConnection() );
+
+    // Text items are really problematic.  We don't want to test the reference, but after that
+    // it gets messy.  What about the value?  Depends on whether or not it's a singleton part.
+    // And what about other texts?  They might be added only to instances on the board, or even
+    // changed for instances on the board.  Or they might want to be tested for equality.
+    // Currently we punt and ignore all the text items.
+
+    // Drawings and pads are also somewhat problematic as there's no gaurantee that they'll be
+    // in the same order in the two footprints.  Rather than builds some sophisticated hashing
+    // algorithm we use the footprint sorting functions to attempt to sort them in the same order.
+
+    std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> aShapes;
+    std::copy_if( GraphicalItems().begin(), GraphicalItems().end(),
+                  std::inserter( aShapes, aShapes.begin() ),
+                  []( BOARD_ITEM* item )
+                  {
+                      return item->Type() == PCB_FP_SHAPE_T;
+                  } );
+    std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> bShapes;
+    std::copy_if( aLibFootprint->GraphicalItems().begin(), aLibFootprint->GraphicalItems().end(),
+                  std::inserter( bShapes, bShapes.begin() ),
+                  []( BOARD_ITEM* item )
+                  {
+                      return item->Type() == PCB_FP_SHAPE_T;
+                  } );
+
+    std::set<PAD*, FOOTPRINT::cmp_pads> aPads( Pads().begin(), Pads().end() );
+    std::set<PAD*, FOOTPRINT::cmp_pads> bPads( aLibFootprint->Pads().begin(), aLibFootprint->Pads().end() );
+
+    std::set<FP_ZONE*, FOOTPRINT::cmp_zones> aZones( Zones().begin(), Zones().end() );
+    std::set<FP_ZONE*, FOOTPRINT::cmp_zones> bZones( aLibFootprint->Zones().begin(), aLibFootprint->Zones().end() );
+
+    TEST( aPads.size(), bPads.size() );
+    TEST( aZones.size(), bZones.size() );
+    TEST( aShapes.size(), bShapes.size() );
+
+    for( auto aIt = aPads.begin(), bIt = bPads.begin(); aIt != aPads.end(); aIt++, bIt++ )
+        TEST_PADS( *aIt, *bIt );
+
+    for( auto aIt = aShapes.begin(), bIt = bShapes.begin(); aIt != aShapes.end(); aIt++, bIt++ )
+    {
+        if( ( *aIt )->Type() == PCB_FP_SHAPE_T )
+            TEST_SHAPES( static_cast<FP_SHAPE*>( *aIt ), static_cast<FP_SHAPE*>( *bIt ) );
+    }
+
+    for( auto aIt = aZones.begin(), bIt = bZones.begin(); aIt != aZones.end(); aIt++, bIt++ )
+        TEST_ZONES( *aIt, *bIt );
+
+    TEST( Models().size(), aLibFootprint->Models().size() );
+
+    for( size_t ii = 0; ii < Models().size(); ++ii )
+        TEST_MODELS( Models()[ii], aLibFootprint->Models()[ii] );
+
+    return false;
+}
+
+
+bool DRC_TEST_PROVIDER_LIBRARY_PARITY::Run()
+{
+    BOARD*   board = m_drcEngine->GetBoard();
+    PROJECT* project = board->GetProject();
+
+    if( !project )
+    {
+        reportAux( _( "No project loaded, skipping library parity tests." ) );
+        return true;    // Continue with other tests
+    }
+
+    if( !reportPhase( _( "Loading footprint library table..." ) ) )
+        return false;   // DRC cancelled
+
+    std::map<LIB_ID, std::shared_ptr<FOOTPRINT>> libFootprintCache;
+
+    FP_LIB_TABLE* libTable = project->PcbFootprintLibs();
+    wxString      msg;
+    int           ii = 0;
+    const int     delta = 50;  // Number of tests between calls to progress bar
+
+    if( !reportPhase( _( "Checking board footprints against library..." ) ) )
+        return false;
+
+    for( FOOTPRINT* footprint : board->Footprints() )
+    {
+        if( m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_ISSUES ) )
+            return true;    // Continue with other tests
+
+        if( !reportProgress( ii++, board->Footprints().size(), delta ) )
+            return false;   // DRC cancelled
+
+        LIB_ID               fpID = footprint->GetFPID();
+        wxString             libName = fpID.GetLibNickname();
+        wxString             fpName = fpID.GetLibItemName();
+        const LIB_TABLE_ROW* libTableRow = nullptr;
+
+        try
+        {
+            libTableRow = libTable->FindRow( libName );
+        }
+        catch( const IO_ERROR& )
+        {
+        }
+
+        if( !libTableRow )
+        {
+            std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
+            msg.Printf( _( "The current configuration does not include the library '%s'." ),
+                        libName );
+            drcItem->SetErrorMessage( msg );
+            drcItem->SetItems( footprint );
+            reportViolation( drcItem, footprint->GetCenter() );
+
+            continue;
+        }
+        else if( !libTable->HasLibrary( libName, true ) )
+        {
+            std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
+            msg.Printf( _( "The library '%s' is not enabled in the current configuration." ),
+                        libName );
+            drcItem->SetErrorMessage( msg );
+            drcItem->SetItems( footprint );
+            reportViolation( drcItem, footprint->GetCenter() );
+
+            continue;
+        }
+
+        auto                       cacheIt = libFootprintCache.find( fpID );
+        std::shared_ptr<FOOTPRINT> libFootprint;
+
+        if( cacheIt != libFootprintCache.end() )
+        {
+            libFootprint = cacheIt->second;
+        }
+        else
+        {
+            try
+            {
+                libFootprint.reset( libTable->FootprintLoad( libName, fpName, true ) );
+
+                if( libFootprint )
+                    libFootprintCache[ fpID ] = libFootprint;
+            }
+            catch( const IO_ERROR& )
+            {
+            }
+        }
+
+        if( !libFootprint )
+        {
+            std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
+            msg.Printf( "Footprint '%s' not found in library '%s'.",
+                        fpName,
+                        libName );
+            drcItem->SetErrorMessage( msg );
+            drcItem->SetItems( footprint );
+            reportViolation( drcItem, footprint->GetCenter() );
+        }
+        else if( footprint->FootprintNeedsUpdate( libFootprint.get() ) )
+        {
+            std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
+            msg.Printf( "Footprint '%s' does not match copy in library '%s'.",
+                        fpName,
+                        libName );
+            drcItem->SetErrorMessage( msg );
+            drcItem->SetItems( footprint );
+            reportViolation( drcItem, footprint->GetCenter() );
+        }
+    }
+
+    return true;
+}
+
+
+std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_LIBRARY_PARITY::GetConstraintTypes() const
+{
+    return {};
+}
+
+
+namespace detail
+{
+static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_LIBRARY_PARITY> dummy;
+}
diff --git a/pcbnew/drc/drc_test_provider_matched_length.cpp b/pcbnew/drc/drc_test_provider_matched_length.cpp
index 81639ebda7..951cb63bb3 100644
--- a/pcbnew/drc/drc_test_provider_matched_length.cpp
+++ b/pcbnew/drc/drc_test_provider_matched_length.cpp
@@ -66,11 +66,6 @@ public:
         return "Tests matched track lengths.";
     }
 
-    virtual int GetNumPhases() const override
-    {
-        return 1;
-    }
-
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
     DRC_LENGTH_REPORT BuildLengthReport() const;
diff --git a/pcbnew/drc/drc_test_provider_misc.cpp b/pcbnew/drc/drc_test_provider_misc.cpp
index 79dd8f49db..078242c519 100644
--- a/pcbnew/drc/drc_test_provider_misc.cpp
+++ b/pcbnew/drc/drc_test_provider_misc.cpp
@@ -70,8 +70,6 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
     void testOutline();
     void testDisabledLayers();
@@ -284,12 +282,6 @@ bool DRC_TEST_PROVIDER_MISC::Run()
 }
 
 
-int DRC_TEST_PROVIDER_MISC::GetNumPhases() const
-{
-    return 3;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_MISC::GetConstraintTypes() const
 {
     return {};
diff --git a/pcbnew/drc/drc_test_provider_lvs.cpp b/pcbnew/drc/drc_test_provider_schematic_parity.cpp
similarity index 90%
rename from pcbnew/drc/drc_test_provider_lvs.cpp
rename to pcbnew/drc/drc_test_provider_schematic_parity.cpp
index 8d85cdf7a9..c1368e5b07 100644
--- a/pcbnew/drc/drc_test_provider_lvs.cpp
+++ b/pcbnew/drc/drc_test_provider_schematic_parity.cpp
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2004-2020 KiCad Developers.
+ * Copyright (C) 2004-2021 KiCad Developers.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -32,7 +32,7 @@
 #include <netlist_reader/pcb_netlist.h>
 
 /*
-    Layout-versus-schematic (LVS) test.
+    Schematic parity test.
 
     Errors generated:
     - DRCE_MISSING_FOOTPRINT
@@ -44,15 +44,15 @@
     - cross-check PCB fields against SCH fields
 */
 
-class DRC_TEST_PROVIDER_LVS : public DRC_TEST_PROVIDER
+class DRC_TEST_PROVIDER_SCHEMATIC_PARITY : public DRC_TEST_PROVIDER
 {
 public:
-    DRC_TEST_PROVIDER_LVS()
+    DRC_TEST_PROVIDER_SCHEMATIC_PARITY()
     {
         m_isRuleDriven = false;
     }
 
-    virtual ~DRC_TEST_PROVIDER_LVS()
+    virtual ~DRC_TEST_PROVIDER_SCHEMATIC_PARITY()
     {
     }
 
@@ -60,7 +60,7 @@ public:
 
     virtual const wxString GetName() const override
     {
-        return "LVS";
+        return "schematic_parity";
     };
 
     virtual const wxString GetDescription() const override
@@ -70,14 +70,12 @@ public:
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
-    int GetNumPhases() const override;
-
 private:
-    void testFootprints( NETLIST& aNetlist );
+    void testNetlist( NETLIST& aNetlist );
 };
 
 
-void DRC_TEST_PROVIDER_LVS::testFootprints( NETLIST& aNetlist )
+void DRC_TEST_PROVIDER_SCHEMATIC_PARITY::testNetlist( NETLIST& aNetlist )
 {
     BOARD* board = m_drcEngine->GetBoard();
 
@@ -211,7 +209,7 @@ void DRC_TEST_PROVIDER_LVS::testFootprints( NETLIST& aNetlist )
 }
 
 
-bool DRC_TEST_PROVIDER_LVS::Run()
+bool DRC_TEST_PROVIDER_SCHEMATIC_PARITY::Run()
 {
     if( m_drcEngine->GetTestFootprints() )
     {
@@ -222,11 +220,11 @@ bool DRC_TEST_PROVIDER_LVS::Run()
 
         if( !netlist )
         {
-            reportAux( _("No netlist provided, skipping LVS.") );
+            reportAux( _( "No netlist provided, skipping schematic parity tests." ) );
             return true;
         }
 
-        testFootprints( *netlist );
+        testNetlist( *netlist );
 
         reportRuleStatistics();
     }
@@ -235,13 +233,7 @@ bool DRC_TEST_PROVIDER_LVS::Run()
 }
 
 
-int DRC_TEST_PROVIDER_LVS::GetNumPhases() const
-{
-    return m_drcEngine->GetTestFootprints() ? 1 : 0;
-}
-
-
-std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_LVS::GetConstraintTypes() const
+std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_SCHEMATIC_PARITY::GetConstraintTypes() const
 {
     return {};
 }
@@ -249,5 +241,5 @@ std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_LVS::GetConstraintTypes() const
 
 namespace detail
 {
-static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_LVS> dummy;
+static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_SCHEMATIC_PARITY> dummy;
 }
diff --git a/pcbnew/drc/drc_test_provider_silk_clearance.cpp b/pcbnew/drc/drc_test_provider_silk_clearance.cpp
index 301a51b2a1..7da92e80d1 100644
--- a/pcbnew/drc/drc_test_provider_silk_clearance.cpp
+++ b/pcbnew/drc/drc_test_provider_silk_clearance.cpp
@@ -68,11 +68,6 @@ public:
         return "Tests for overlapping silkscreen features.";
     }
 
-    virtual int GetNumPhases() const override
-    {
-        return 1;
-    }
-
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
 private:
diff --git a/pcbnew/drc/drc_test_provider_silk_to_mask.cpp b/pcbnew/drc/drc_test_provider_silk_to_mask.cpp
index 5298e3da97..82e97d284d 100644
--- a/pcbnew/drc/drc_test_provider_silk_to_mask.cpp
+++ b/pcbnew/drc/drc_test_provider_silk_to_mask.cpp
@@ -65,11 +65,6 @@ public:
         return "Tests for silkscreen being clipped by solder mask";
     }
 
-    virtual int GetNumPhases() const override
-    {
-        return 1;
-    }
-
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
 
 private:
diff --git a/pcbnew/drc/drc_test_provider_track_width.cpp b/pcbnew/drc/drc_test_provider_track_width.cpp
index 19c1be6c0c..8287e44ab1 100644
--- a/pcbnew/drc/drc_test_provider_track_width.cpp
+++ b/pcbnew/drc/drc_test_provider_track_width.cpp
@@ -59,8 +59,6 @@ public:
     }
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
-
-    int GetNumPhases() const override;
 };
 
 
@@ -171,12 +169,6 @@ bool DRC_TEST_PROVIDER_TRACK_WIDTH::Run()
 }
 
 
-int DRC_TEST_PROVIDER_TRACK_WIDTH::GetNumPhases() const
-{
-    return 1;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_TRACK_WIDTH::GetConstraintTypes() const
 {
     return { TRACK_WIDTH_CONSTRAINT };
diff --git a/pcbnew/drc/drc_test_provider_via_diameter.cpp b/pcbnew/drc/drc_test_provider_via_diameter.cpp
index c4a884894b..8286961e2f 100644
--- a/pcbnew/drc/drc_test_provider_via_diameter.cpp
+++ b/pcbnew/drc/drc_test_provider_via_diameter.cpp
@@ -58,8 +58,6 @@ public:
     }
 
     virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override;
-
-    int GetNumPhases() const override;
 };
 
 
@@ -160,12 +158,6 @@ bool DRC_TEST_PROVIDER_VIA_DIAMETER::Run()
 }
 
 
-int DRC_TEST_PROVIDER_VIA_DIAMETER::GetNumPhases() const
-{
-    return 1;
-}
-
-
 std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_VIA_DIAMETER::GetConstraintTypes() const
 {
     return { VIA_DIAMETER_CONSTRAINT };
diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp
index 3a7801c724..834c94db10 100644
--- a/pcbnew/footprint.cpp
+++ b/pcbnew/footprint.cpp
@@ -2168,28 +2168,48 @@ bool FOOTPRINT::HasThroughHolePads() const
 }
 
 
-bool FOOTPRINT::cmp_drawings::operator()( const BOARD_ITEM* aFirst,
-                                          const BOARD_ITEM* aSecond ) const
+#define TEST( a, b ) { if( a != b ) return a < b; }
+#define TEST_PT( a, b ) { if( a.x != b.x ) return a.x < b.x; if( a.y != b.y ) return a.y < b.y; }
+
+
+bool FOOTPRINT::cmp_drawings::operator()( const BOARD_ITEM* itemA, const BOARD_ITEM* itemB ) const
 {
-    if( aFirst->Type() != aSecond->Type() )
-        return aFirst->Type() < aSecond->Type();
+    TEST( itemA->Type(), itemB->Type() );
+    TEST( itemA->GetLayer(), itemB->GetLayer() );
 
-    if( aFirst->GetLayer() != aSecond->GetLayer() )
-        return aFirst->GetLayer() < aSecond->GetLayer();
-
-    if( aFirst->Type() == PCB_FP_SHAPE_T )
+    if( itemA->Type() == PCB_FP_SHAPE_T )
     {
-        const FP_SHAPE* dwgA = static_cast<const FP_SHAPE*>( aFirst );
-        const FP_SHAPE* dwgB = static_cast<const FP_SHAPE*>( aSecond );
+        const FP_SHAPE* dwgA = static_cast<const FP_SHAPE*>( itemA );
+        const FP_SHAPE* dwgB = static_cast<const FP_SHAPE*>( itemB );
 
-        if( dwgA->GetShape() != dwgB->GetShape() )
-            return dwgA->GetShape() < dwgB->GetShape();
+        TEST( dwgA->GetShape(), dwgB->GetShape() );
+
+        TEST_PT( dwgA->GetStart0(), dwgB->GetStart0() );
+        TEST_PT( dwgA->GetEnd0(), dwgB->GetEnd0() );
+
+        if( dwgA->GetShape() == SHAPE_T::ARC )
+        {
+            TEST_PT( dwgA->GetCenter0(), dwgB->GetCenter0() );
+        }
+        else if( dwgA->GetShape() == SHAPE_T::BEZIER )
+        {
+            TEST_PT( dwgA->GetBezierC1_0(), dwgB->GetBezierC1_0() );
+            TEST_PT( dwgA->GetBezierC2_0(), dwgB->GetBezierC2_0() );
+        }
+        else if( dwgA->GetShape() == SHAPE_T::POLY )
+        {
+            TEST( dwgA->GetPolyShape().TotalVertices(), dwgB->GetPolyShape().TotalVertices() );
+
+            for( int ii = 0; ii < dwgA->GetPolyShape().TotalVertices(); ++ii )
+                TEST_PT( dwgA->GetPolyShape().CVertex( ii ), dwgB->GetPolyShape().CVertex( ii ) );
+        }
+
+        TEST( dwgA->GetWidth(), dwgB->GetWidth() );
     }
 
-    if( aFirst->m_Uuid != aSecond->m_Uuid ) // shopuld be always the case foer valid boards
-        return aFirst->m_Uuid < aSecond->m_Uuid;
+    TEST( itemA->m_Uuid, itemB->m_Uuid );   // should be always the case for valid boards
 
-    return aFirst < aSecond;
+    return itemA < itemB;
 }
 
 
@@ -2198,13 +2218,35 @@ bool FOOTPRINT::cmp_pads::operator()( const PAD* aFirst, const PAD* aSecond ) co
     if( aFirst->GetNumber() != aSecond->GetNumber() )
         return StrNumCmp( aFirst->GetNumber(), aSecond->GetNumber() ) < 0;
 
-    if( aFirst->m_Uuid != aSecond->m_Uuid ) // shopuld be always the case foer valid boards
-        return aFirst->m_Uuid < aSecond->m_Uuid;
+    TEST_PT( aFirst->GetPos0(), aSecond->GetPos0() );
+    TEST_PT( aFirst->GetSize(), aSecond->GetSize() );
+    TEST( aFirst->GetShape(), aSecond->GetShape() );
+
+    TEST( aFirst->m_Uuid, aSecond->m_Uuid );   // should be always the case for valid boards
 
     return aFirst < aSecond;
 }
 
 
+bool FOOTPRINT::cmp_zones::operator()( const FP_ZONE* aFirst, const FP_ZONE* aSecond ) const
+{
+    TEST( aFirst->GetPriority(), aSecond->GetPriority() );
+    TEST( aFirst->GetLayerSet().Seq(), aSecond->GetLayerSet().Seq() );
+
+    TEST( aFirst->Outline()->TotalVertices(), aSecond->Outline()->TotalVertices() );
+
+    for( int ii = 0; ii < aFirst->Outline()->TotalVertices(); ++ii )
+        TEST_PT( aFirst->Outline()->CVertex( ii ), aSecond->Outline()->CVertex( ii ) );
+
+    TEST( aFirst->m_Uuid, aSecond->m_Uuid );   // should be always the case for valid boards
+
+    return aFirst < aSecond;
+}
+
+
+#undef TEST
+
+
 void FOOTPRINT::TransformPadsWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                                      PCB_LAYER_ID aLayer, int aClearance,
                                                      int aMaxError, ERROR_LOC aErrorLoc,
diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h
index d1d57ca6aa..04567eed5d 100644
--- a/pcbnew/footprint.h
+++ b/pcbnew/footprint.h
@@ -180,8 +180,8 @@ public:
 
     bool HasThroughHolePads() const;
 
-    std::list<FP_3DMODEL>& Models()             { return m_3D_Drawings; }
-    const std::list<FP_3DMODEL>& Models() const { return m_3D_Drawings; }
+    std::vector<FP_3DMODEL>& Models()             { return m_3D_Drawings; }
+    const std::vector<FP_3DMODEL>& Models() const { return m_3D_Drawings; }
 
     void SetPosition( const wxPoint& aPos ) override;
     wxPoint GetPosition() const override { return m_pos; }
@@ -655,6 +655,11 @@ public:
      */
     static const wxChar* StringLibNameInvalidChars( bool aUserReadable );
 
+    /**
+     * Return true if a board footprint differs from the library version.
+     */
+    bool FootprintNeedsUpdate( const FOOTPRINT* aLibFootprint );
+
     /**
      * Take ownership of caller's heap allocated aInitialComments block.
      *
@@ -723,6 +728,11 @@ public:
         bool operator()( const PAD* aFirst, const PAD* aSecond ) const;
     };
 
+    struct cmp_zones
+    {
+        bool operator()( const FP_ZONE* aFirst, const FP_ZONE* aSecond ) const;
+    };
+
 
 #if defined(DEBUG)
     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
@@ -778,7 +788,7 @@ private:
     int             m_rot90Cost;         // Horizontal automatic placement cost ( 0..10 ).
     int             m_rot180Cost;        // Vertical automatic placement cost ( 0..10 ).
 
-    std::list<FP_3DMODEL>         m_3D_Drawings;       // Linked list of 3D models.
+    std::vector<FP_3DMODEL>       m_3D_Drawings;       // 3D models.
     std::map<wxString, wxString>  m_properties;
     wxArrayString*                m_initial_comments;  // s-expression comments in the footprint,
                                                        // lazily allocated only if needed for speed
diff --git a/pcbnew/plugins/kicad/pcb_plugin.cpp b/pcbnew/plugins/kicad/pcb_plugin.cpp
index 05cfb07d9e..08a01f16cc 100644
--- a/pcbnew/plugins/kicad/pcb_plugin.cpp
+++ b/pcbnew/plugins/kicad/pcb_plugin.cpp
@@ -1225,8 +1225,8 @@ void PCB_PLUGIN::format( const FOOTPRINT* aFootprint, int aNestLevel ) const
     std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> sorted_drawings(
             aFootprint->GraphicalItems().begin(),
             aFootprint->GraphicalItems().end() );
-    std::set<BOARD_ITEM*, BOARD_ITEM::ptr_cmp> sorted_zones( aFootprint->Zones().begin(),
-                                                             aFootprint->Zones().end() );
+    std::set<FP_ZONE*, FOOTPRINT::cmp_zones> sorted_zones( aFootprint->Zones().begin(),
+                                                           aFootprint->Zones().end() );
     std::set<BOARD_ITEM*, PCB_GROUP::ptr_cmp> sorted_groups( aFootprint->Groups().begin(),
                                                              aFootprint->Groups().end() );
 
diff --git a/qa/data/issue1358.kicad_pro b/qa/data/issue1358.kicad_pro
index 9a19e8294c..7ed001781b 100755
--- a/qa/data/issue1358.kicad_pro
+++ b/qa/data/issue1358.kicad_pro
@@ -70,6 +70,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue2528.kicad_pro b/qa/data/issue2528.kicad_pro
index c6cdf3e39a..9dac24766f 100644
--- a/qa/data/issue2528.kicad_pro
+++ b/qa/data/issue2528.kicad_pro
@@ -70,6 +70,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue4774.kicad_pro b/qa/data/issue4774.kicad_pro
index d7147fec91..33c06aae03 100644
--- a/qa/data/issue4774.kicad_pro
+++ b/qa/data/issue4774.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue5854.kicad_pro b/qa/data/issue5854.kicad_pro
index 80c3d5d718..ccf29b8c6b 100644
--- a/qa/data/issue5854.kicad_pro
+++ b/qa/data/issue5854.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue5978.kicad_pro b/qa/data/issue5978.kicad_pro
index a3bcd1c0ca..7ca859b753 100644
--- a/qa/data/issue5978.kicad_pro
+++ b/qa/data/issue5978.kicad_pro
@@ -76,6 +76,7 @@
         "items_not_allowed": "error",
         "keepout": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_too_small": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue5990.kicad_pro b/qa/data/issue5990.kicad_pro
index 3f6614108b..86c625ec89 100755
--- a/qa/data/issue5990.kicad_pro
+++ b/qa/data/issue5990.kicad_pro
@@ -76,6 +76,7 @@
         "items_not_allowed": "error",
         "keepout": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_too_small": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue6879.kicad_pro b/qa/data/issue6879.kicad_pro
index f5ecbf0b78..8e19f3b9ee 100755
--- a/qa/data/issue6879.kicad_pro
+++ b/qa/data/issue6879.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue6945.kicad_pro b/qa/data/issue6945.kicad_pro
index b91a40bf0c..df42218c6f 100644
--- a/qa/data/issue6945.kicad_pro
+++ b/qa/data/issue6945.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue7267.kicad_pro b/qa/data/issue7267.kicad_pro
index c97a4a1d67..daa0e7ee74 100755
--- a/qa/data/issue7267.kicad_pro
+++ b/qa/data/issue7267.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue7325.kicad_pro b/qa/data/issue7325.kicad_pro
index adc515328a..8e95a6b65a 100755
--- a/qa/data/issue7325.kicad_pro
+++ b/qa/data/issue7325.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue7975.kicad_pro b/qa/data/issue7975.kicad_pro
index b8f1357531..279e58a00c 100644
--- a/qa/data/issue7975.kicad_pro
+++ b/qa/data/issue7975.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue8003.kicad_pro b/qa/data/issue8003.kicad_pro
index b50cada7ef..5b527e88fd 100644
--- a/qa/data/issue8003.kicad_pro
+++ b/qa/data/issue8003.kicad_pro
@@ -48,13 +48,20 @@
           "min_clearance": 0.508
         }
       },
-      "diff_pair_dimensions": [],
+      "diff_pair_dimensions": [
+        {
+          "gap": 0.0,
+          "via_gap": 0.0,
+          "width": 0.0
+        }
+      ],
       "drc_exclusions": [],
       "meta": {
         "version": 2
       },
       "rule_severities": {
         "annular_width": "error",
+        "aperture_clearance": "error",
         "clearance": "error",
         "copper_edge_clearance": "error",
         "courtyards_overlap": "error",
@@ -69,6 +76,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
@@ -81,6 +89,9 @@
         "silk_over_copper": "warning",
         "silk_overlap": "warning",
         "skew_out_of_range": "error",
+        "starved_thermal": "error",
+        "text_height": "warning",
+        "text_thickness": "warning",
         "too_many_vias": "error",
         "track_dangling": "warning",
         "track_width": "error",
@@ -95,21 +106,32 @@
         "allow_blind_buried_vias": false,
         "allow_microvias": false,
         "max_error": 0.005,
+        "min_aperture_clearance": 0.049999999999999996,
         "min_clearance": 0.0,
         "min_copper_edge_clearance": 0.01,
         "min_hole_clearance": 0.0,
         "min_hole_to_hole": 0.25,
         "min_microvia_diameter": 0.19999999999999998,
         "min_microvia_drill": 0.09999999999999999,
+        "min_resolved_spokes": 2,
         "min_silk_clearance": 0.0,
+        "min_text_height": 0.7999999999999999,
+        "min_text_thickness": 0.12,
         "min_through_hole_diameter": 0.3,
         "min_track_width": 0.19999999999999998,
         "min_via_annular_width": 0.049999999999999996,
         "min_via_diameter": 0.39999999999999997,
         "use_height_for_length_calcs": true
       },
-      "track_widths": [],
-      "via_dimensions": [],
+      "track_widths": [
+        0.0
+      ],
+      "via_dimensions": [
+        {
+          "diameter": 0.0,
+          "drill": 0.0
+        }
+      ],
       "zones_allow_external_fillets": false,
       "zones_use_no_outline": true
     },
diff --git a/qa/data/issue8407.kicad_pro b/qa/data/issue8407.kicad_pro
index 7221d043b0..31ef7631e7 100644
--- a/qa/data/issue8407.kicad_pro
+++ b/qa/data/issue8407.kicad_pro
@@ -83,6 +83,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/data/issue9081.kicad_pro b/qa/data/issue9081.kicad_pro
index 9c75d2361e..9b51fe1860 100644
--- a/qa/data/issue9081.kicad_pro
+++ b/qa/data/issue9081.kicad_pro
@@ -75,6 +75,7 @@
         "item_on_disabled_layer": "error",
         "items_not_allowed": "error",
         "length_out_of_range": "error",
+        "lib_footprint_issues": "ignore",
         "malformed_courtyard": "error",
         "microvia_drill_out_of_range": "error",
         "missing_courtyard": "ignore",
diff --git a/qa/drc_proto/CMakeLists.txt b/qa/drc_proto/CMakeLists.txt
index d3dea5d406..3dfc688f93 100644
--- a/qa/drc_proto/CMakeLists.txt
+++ b/qa/drc_proto/CMakeLists.txt
@@ -44,7 +44,7 @@ add_executable( drc_proto
     ../../pcbnew/drc/drc_test_provider_connectivity.cpp
     ../../pcbnew/drc/drc_test_provider_courtyard_clearance.cpp
     ../../pcbnew/drc/drc_test_provider_via_diameter.cpp
-    ../../pcbnew/drc/drc_test_provider_lvs.cpp
+    ../../pcbnew/drc/drc_test_provider_schematic_parity.cpp
     ../../pcbnew/drc/drc_test_provider_misc.cpp
     ../../pcbnew/drc/drc_test_provider_silk_to_mask.cpp
     ../../pcbnew/drc/drc_test_provider_silk_clearance.cpp
diff --git a/qa/pns/CMakeLists.txt b/qa/pns/CMakeLists.txt
index 2502d71f6c..5ab10c9a52 100644
--- a/qa/pns/CMakeLists.txt
+++ b/qa/pns/CMakeLists.txt
@@ -43,7 +43,7 @@ add_executable( test_pns
     ../../pcbnew/drc/drc_test_provider_connectivity.cpp
     ../../pcbnew/drc/drc_test_provider_courtyard_clearance.cpp
     ../../pcbnew/drc/drc_test_provider_via_diameter.cpp
-    ../../pcbnew/drc/drc_test_provider_lvs.cpp
+    ../../pcbnew/drc/drc_test_provider_schematic_parity.cpp
     ../../pcbnew/drc/drc_test_provider_misc.cpp
     ../../pcbnew/drc/drc_test_provider_silk_to_mask.cpp
     ../../pcbnew/drc/drc_test_provider_silk_clearance.cpp