From c0aa6965de7b83ca78dd5c4d700b50a2a03a34e4 Mon Sep 17 00:00:00 2001
From: Jon Evans <jon@craftyjon.com>
Date: Sun, 31 May 2020 17:42:04 -0400
Subject: [PATCH] Migrate PcbNew project settings to new framework

Various architecture upgrades to support this.
Creating a BOARD now requires a valid PROJECT, which caused
some (mostly transparent) changes to the Python API internals.

ADDED: Project local settings file
CHANGED: Board design settings are no longer stored in PCB file
CHANGED: Net classes are no longer stored in PCB file
CHANGED: Importing board settings now reads boards, not just projects

Fixes https://gitlab.com/kicad/code/kicad/-/issues/2578
Fixes https://gitlab.com/kicad/code/kicad/-/issues/4070
---
 3d-viewer/3d_canvas/board_adapter.cpp         |    5 +-
 common/CMakeLists.txt                         |    9 +-
 common/lib_tree_model_adapter.cpp             |   47 +-
 common/lib_tree_model_adapter.h               |   10 +-
 {pcbnew => common}/netclass.cpp               |  122 +-
 common/project.cpp                            |   18 +-
 common/project/net_settings.cpp               |  142 ++
 common/project/project_file.cpp               |  478 +++++++
 common/project/project_local_settings.cpp     |  105 ++
 common/settings/json_settings.cpp             |   60 +-
 common/settings/nested_settings.cpp           |   26 +-
 common/settings/project_file.cpp              |  247 ----
 common/settings/settings_manager.cpp          |  107 +-
 {pcbnew => common}/swig/netclass.i            |    0
 common/wildcards_and_files_ext.cpp            |    1 +
 cvpcb/auto_associate.cpp                      |    2 +-
 cvpcb/dialogs/dialog_config_equfiles.cpp      |    2 +-
 eeschema/files-io.cpp                         |    6 +-
 eeschema/symbol_tree_model_adapter.cpp        |    2 +-
 .../symbol_tree_synchronizing_adapter.cpp     |    2 +-
 include/board_design_settings.h               |  153 +--
 include/footprint_editor_settings.h           |    2 +
 include/layers_id_colors_and_visibility.h     |    3 +
 {pcbnew => include}/netclass.h                |   10 -
 include/pcb_base_frame.h                      |    4 +-
 include/project.h                             |   19 +
 include/project/net_settings.h                |   46 +
 include/{settings => project}/project_file.h  |   60 +-
 include/project/project_local_settings.h      |   83 ++
 include/settings/color_settings.h             |   10 +-
 include/settings/json_settings.h              |   22 +-
 include/settings/nested_settings.h            |    9 +-
 include/settings/parameters.h                 |  124 +-
 include/settings/settings_manager.h           |   32 +-
 include/wildcards_and_files_ext.h             |    1 +
 kicad/kicad_manager_frame.cpp                 |    8 -
 pcbnew/CMakeLists.txt                         |    2 +-
 pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp  |    2 +-
 pcbnew/board_design_settings.cpp              | 1143 +++++++----------
 pcbnew/board_item_container.h                 |   40 +-
 pcbnew/class_board.cpp                        |  201 ++-
 pcbnew/class_board.h                          |   82 +-
 pcbnew/class_pad.cpp                          |   31 -
 pcbnew/class_pad.h                            |    8 -
 pcbnew/dialogs/dialog_board_setup.cpp         |   77 +-
 pcbnew/dialogs/dialog_export_idf.cpp          |    1 +
 pcbnew/dialogs/dialog_export_step.cpp         |    1 +
 pcbnew/dialogs/dialog_export_vrml.cpp         |    1 +
 .../dialog_global_edit_tracks_and_vias.cpp    |    4 +-
 pcbnew/dialogs/dialog_import_settings.cpp     |    2 +-
 pcbnew/dialogs/dialog_netlist.cpp             |    1 +
 pcbnew/dialogs/panel_modedit_defaults.cpp     |    4 +-
 pcbnew/dialogs/panel_setup_netclasses.cpp     |    8 +-
 pcbnew/drc/drc_netclass_tester.cpp            |    2 +-
 pcbnew/drc/drc_rule_parser.cpp                |    2 +-
 pcbnew/exporters/export_gencad.cpp            |    1 +
 pcbnew/exporters/gerber_jobfile_writer.cpp    |    2 +-
 pcbnew/files.cpp                              |   85 +-
 pcbnew/footprint_edit_frame.cpp               |    6 -
 pcbnew/footprint_edit_frame.h                 |    1 -
 pcbnew/footprint_editor_settings.cpp          |    2 +-
 pcbnew/fp_tree_model_adapter.cpp              |    2 +-
 pcbnew/initpcb.cpp                            |   14 +-
 pcbnew/kicad_plugin.cpp                       |  181 +--
 pcbnew/kicad_plugin.h                         |    6 +-
 pcbnew/legacy_plugin.cpp                      |   25 +-
 pcbnew/netinfo_item.cpp                       |    4 +-
 pcbnew/netlist_reader/netlist.cpp             |    1 +
 pcbnew/pcb_base_frame.cpp                     |   14 +-
 pcbnew/pcb_edit_frame.cpp                     |   33 +-
 pcbnew/pcb_edit_frame.h                       |   33 +-
 pcbnew/pcb_layer_widget.cpp                   |   24 -
 pcbnew/pcb_parser.cpp                         |   79 +-
 pcbnew/pcbnew_config.cpp                      |   52 +-
 pcbnew/router/pns_kicad_iface.cpp             |    4 +-
 pcbnew/router/pns_sizes_settings.cpp          |    4 +-
 .../specctra_export.cpp                       |    4 +-
 .../specctra_import.cpp                       |    2 +-
 pcbnew/swig/board.i                           |   19 +
 pcbnew/swig/pcbnew_scripting_helpers.cpp      |   72 ++
 pcbnew/swig/pcbnew_scripting_helpers.h        |    6 +
 pcbnew/tools/pcb_editor_control.cpp           |    3 +-
 pcbnew/tools/pcbnew_control.cpp               |    2 +-
 pcbnew/zone_settings.h                        |    3 +-
 qa/pcbnew/drc/test_drc_courtyard_invalid.cpp  |   28 +-
 qa/pcbnew/drc/test_drc_courtyard_overlap.cpp  |   22 +-
 qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp   |   25 +-
 87 files changed, 2415 insertions(+), 1933 deletions(-)
 rename {pcbnew => common}/netclass.cpp (50%)
 create mode 100644 common/project/net_settings.cpp
 create mode 100644 common/project/project_file.cpp
 create mode 100644 common/project/project_local_settings.cpp
 delete mode 100644 common/settings/project_file.cpp
 rename {pcbnew => common}/swig/netclass.i (100%)
 rename {pcbnew => include}/netclass.h (95%)
 create mode 100644 include/project/net_settings.h
 rename include/{settings => project}/project_file.h (63%)
 create mode 100644 include/project/project_local_settings.h

diff --git a/3d-viewer/3d_canvas/board_adapter.cpp b/3d-viewer/3d_canvas/board_adapter.cpp
index 335b6ab90f..8fc70360c8 100644
--- a/3d-viewer/3d_canvas/board_adapter.cpp
+++ b/3d-viewer/3d_canvas/board_adapter.cpp
@@ -188,8 +188,7 @@ bool BOARD_ADAPTER::Is3DLayerEnabled( PCB_LAYER_ID aLayer ) const
 
     case B_Cu:
     case F_Cu:
-        return m_board->GetDesignSettings().IsLayerVisible( aLayer ) ||
-               GetFlag( FL_USE_REALISTIC_MODE );
+        return m_board->IsLayerVisible( aLayer ) || GetFlag( FL_USE_REALISTIC_MODE );
         break;
 
     default:
@@ -201,7 +200,7 @@ bool BOARD_ADAPTER::Is3DLayerEnabled( PCB_LAYER_ID aLayer ) const
             return false;
         }
 
-        return m_board->GetDesignSettings().IsLayerVisible( aLayer );
+        return m_board->IsLayerVisible( aLayer );
     }
 
     // The layer has a flag, return the flag
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 0883e764f2..aac8a743c8 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -317,8 +317,10 @@ set( COMMON_SRCS
     lib_tree_model.cpp
     lib_tree_model_adapter.cpp
     lockfile.cpp
+    lset.cpp
     marker_base.cpp
     msgpanel.cpp
+    netclass.cpp
     observable.cpp
     prependpath.cpp
     printout.cpp
@@ -398,9 +400,12 @@ set( COMMON_SRCS
     settings/common_settings.cpp
     settings/json_settings.cpp
     settings/nested_settings.cpp
-    settings/project_file.cpp
     settings/settings_manager.cpp
 
+    project/net_settings.cpp
+    project/project_file.cpp
+    project/project_local_settings.cpp
+
     libeval/numeric_evaluator.cpp
     )
 
@@ -438,7 +443,6 @@ set( PCB_COMMON_SRCS
     eda_text.cpp
     fp_lib_table.cpp
     hash_eda.cpp
-    lset.cpp
     origin_viewitem.cpp
     page_info.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/pcb_base_frame.cpp
@@ -453,7 +457,6 @@ set( PCB_COMMON_SRCS
     ${CMAKE_SOURCE_DIR}/pcbnew/class_edge_mod.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/class_marker_pcb.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/class_module.cpp
-    ${CMAKE_SOURCE_DIR}/pcbnew/netclass.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/netinfo_item.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/netinfo_list.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/class_pad.cpp
diff --git a/common/lib_tree_model_adapter.cpp b/common/lib_tree_model_adapter.cpp
index ac031f33cd..56677762c4 100644
--- a/common/lib_tree_model_adapter.cpp
+++ b/common/lib_tree_model_adapter.cpp
@@ -24,6 +24,7 @@
 #include <kiface_i.h>
 #include <config_params.h>
 #include <lib_tree_model_adapter.h>
+#include <project/project_file.h>
 #include <settings/app_settings.h>
 #include <wx/tokenzr.h>
 #include <wx/wupdlock.h>
@@ -73,7 +74,7 @@ unsigned int LIB_TREE_MODEL_ADAPTER::IntoArray( LIB_TREE_NODE const& aNode,
 }
 
 
-LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent ) :
+LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, wxString aPinnedKey ) :
         m_parent( aParent ),
         m_filter( CMP_FILTER_NONE ),
         m_show_units( true ),
@@ -81,7 +82,9 @@ LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent ) :
         m_freeze( 0 ),
         m_col_part( nullptr ),
         m_col_desc( nullptr ),
-        m_widget( nullptr )
+        m_widget( nullptr ),
+        m_pinnedLibs(),
+        m_pinnedKey( aPinnedKey )
 {
     // Default column widths
     m_colWidths[PART_COL] = 360;
@@ -90,12 +93,15 @@ LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent ) :
     auto cfg = Kiface().KifaceSettings();
     m_colWidths[PART_COL] = cfg->m_LibTree.column_width;
 
-    // TODO(JE) PROJECT
-#if 0
     // Read the pinned entries from the project config
-    m_parent->Kiway().Prj().ConfigLoad( Kiface().KifaceSearch(), m_parent->GetName(),
-                                        GetProjectFileParameters() );
-#endif
+    PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
+
+    std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
+                                             project.m_PinnedSymbolLibs :
+                                             project.m_PinnedFootprintLibs;
+
+    for( const wxString& entry : entries )
+        m_pinnedLibs.push_back( entry );
 }
 
 
@@ -117,32 +123,27 @@ void LIB_TREE_MODEL_ADAPTER::SaveColWidths()
 }
 
 
-std::vector<PARAM_CFG*>& LIB_TREE_MODEL_ADAPTER::GetProjectFileParameters()
-{
-    if( !m_projectFileParams.empty() )
-        return m_projectFileParams;
-
-    m_projectFileParams.push_back( new PARAM_CFG_LIBNAME_LIST( PINNED_ITEMS_KEY, &m_pinnedLibs ) );
-
-    return m_projectFileParams;
-}
-
-
 void LIB_TREE_MODEL_ADAPTER::SavePinnedItems()
 {
+    PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
+
+    std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
+                                     project.m_PinnedSymbolLibs :
+                                     project.m_PinnedFootprintLibs;
+
+    entries.clear();
     m_pinnedLibs.clear();
 
     for( auto& child: m_tree.m_Children )
     {
         if( child->m_Pinned )
+        {
             m_pinnedLibs.push_back( child->m_LibId.GetLibNickname() );
+            entries.push_back( child->m_LibId.GetLibNickname() );
+        }
     }
 
-    // TODO(JE) PROJECT
-#if 0
-    m_parent->Kiway().Prj().ConfigSave( Kiface().KifaceSearch(), m_parent->GetName(),
-                                        GetProjectFileParameters() );
-#endif
+
 }
 
 
diff --git a/common/lib_tree_model_adapter.h b/common/lib_tree_model_adapter.h
index eedda7ca81..016c22e57b 100644
--- a/common/lib_tree_model_adapter.h
+++ b/common/lib_tree_model_adapter.h
@@ -136,8 +136,6 @@ public:
     void SaveColWidths();
     void SavePinnedItems();
 
-    std::vector<PARAM_CFG*>& GetProjectFileParameters();
-
     /**
      * Set the component filter type. Must be set before adding libraries
      *
@@ -282,7 +280,12 @@ protected:
 
     LIB_TREE_NODE_ROOT m_tree;
 
-    LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent );
+    /**
+     * Creates the adapter
+     * @param aParent is the parent frame
+     * @param aPinnedKey is the key to load the pinned libraries list from the project file
+     */
+    LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, wxString aPinnedKey );
 
     LIB_TREE_NODE_LIB& DoAddLibraryNode( wxString const& aNodeName, wxString const& aDesc );
 
@@ -368,6 +371,7 @@ private:
 
     int                     m_colWidths[NUM_COLS];
     wxArrayString           m_pinnedLibs;
+    wxString                m_pinnedKey;
 
     /**
      * Find any results worth highlighting and expand them, according to given criteria
diff --git a/pcbnew/netclass.cpp b/common/netclass.cpp
similarity index 50%
rename from pcbnew/netclass.cpp
rename to common/netclass.cpp
index a2edb1d172..fa2f419ae4 100644
--- a/pcbnew/netclass.cpp
+++ b/common/netclass.cpp
@@ -24,15 +24,16 @@
  */
 
 #include <fctsys.h>
-#include <common.h>
 #include <kicad_string.h>
-#include <pcbnew.h>
-#include <richio.h>
 #include <macros.h>
 
-#include <class_board.h>
 #include <netclass.h>
 
+#ifndef PCBNEW
+#define PCBNEW // needed to define the right value of Millimeter2iu(x)
+#endif
+#include <base_units.h>
+
 // This will get mapped to "kicad_default" in the specctra_export.
 const char NETCLASS::Default[] = "Default";
 
@@ -152,84 +153,6 @@ NETCLASSPTR NETCLASSES::Find( const wxString& aName ) const
 }
 
 
-void BOARD::SynchronizeNetsAndNetClasses()
-{
-    NETCLASSES& netClasses = m_designSettings.m_NetClasses;
-    NETCLASSPTR defaultNetClass = netClasses.GetDefault();
-
-    // set all NETs to the default NETCLASS, then later override some
-    // as we go through the NETCLASSes.
-
-    for( NETINFO_LIST::iterator net( m_NetInfo.begin() ), netEnd( m_NetInfo.end() );
-                net != netEnd; ++net )
-    {
-        net->SetClass( defaultNetClass );
-    }
-
-    // Add netclass name and pointer to nets.  If a net is in more than one netclass,
-    // set the net's name and pointer to only the first netclass.  Subsequent
-    // and therefore bogus netclass memberships will be deleted in logic below this loop.
-    for( NETCLASSES::iterator clazz = netClasses.begin(); clazz != netClasses.end(); ++clazz )
-    {
-        NETCLASSPTR netclass = clazz->second;
-
-        for( NETCLASS::const_iterator member = netclass->begin(); member != netclass->end(); ++member )
-        {
-            const wxString& netname = *member;
-
-            // although this overall function seems to be adequately fast,
-            // FindNet( wxString ) uses now a fast binary search and is fast
-            // event for large net lists
-            NETINFO_ITEM* net = FindNet( netname );
-
-            if( net && net->GetClassName() == NETCLASS::Default )
-            {
-                net->SetClass( netclass );
-            }
-        }
-    }
-
-    // Finally, make sure that every NET is in a NETCLASS, even if that
-    // means the Default NETCLASS.  And make sure that all NETCLASSes do not
-    // contain netnames that do not exist, by deleting all netnames from
-    // every netclass and re-adding them.
-
-    for( NETCLASSES::iterator clazz = netClasses.begin(); clazz != netClasses.end(); ++clazz )
-    {
-        NETCLASSPTR netclass = clazz->second;
-
-        netclass->Clear();
-    }
-
-    defaultNetClass->Clear();
-
-    for( NETINFO_LIST::iterator net( m_NetInfo.begin() ), netEnd( m_NetInfo.end() );
-            net != netEnd; ++net )
-    {
-        const wxString& classname = net->GetClassName();
-
-        // because of the std:map<> this should be fast, and because of
-        // prior logic, netclass should not be NULL.
-        NETCLASSPTR netclass = netClasses.Find( classname );
-
-        wxASSERT( netclass );
-
-        netclass->Add( net->GetNetname() );
-    }
-
-    // Set initial values for custom track width & via size to match the default netclass settings
-    m_designSettings.UseCustomTrackViaSize( false );
-    m_designSettings.SetCustomTrackWidth( defaultNetClass->GetTrackWidth() );
-    m_designSettings.SetCustomViaSize( defaultNetClass->GetViaDiameter() );
-    m_designSettings.SetCustomViaDrill( defaultNetClass->GetViaDrill() );
-    m_designSettings.SetCustomDiffPairWidth( defaultNetClass->GetDiffPairWidth() );
-    m_designSettings.SetCustomDiffPairGap( defaultNetClass->GetDiffPairGap() );
-    m_designSettings.SetCustomDiffPairViaGap( defaultNetClass->GetDiffPairViaGap() );
-
-    InvokeListeners( &BOARD_LISTENER::OnBoardNetSettingsChanged, *this );
-}
-
-
 #if defined(DEBUG)
 
 void NETCLASS::Show( int nestLevel, std::ostream& os ) const
@@ -250,38 +173,3 @@ void NETCLASS::Show( int nestLevel, std::ostream& os ) const
 }
 
 #endif
-
-
-void NETCLASS::Format( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aControlBits ) const
-{
-    aFormatter->Print( aNestLevel, "(net_class %s %s\n",
-                       aFormatter->Quotew( GetName() ).c_str(),
-                       aFormatter->Quotew( GetDescription() ).c_str() );
-
-    aFormatter->Print( aNestLevel+1, "(clearance %s)\n", FormatInternalUnits( GetClearance() ).c_str() );
-    aFormatter->Print( aNestLevel+1, "(trace_width %s)\n", FormatInternalUnits( GetTrackWidth() ).c_str() );
-
-    aFormatter->Print( aNestLevel+1, "(via_dia %s)\n", FormatInternalUnits( GetViaDiameter() ).c_str() );
-    aFormatter->Print( aNestLevel+1, "(via_drill %s)\n", FormatInternalUnits( GetViaDrill() ).c_str() );
-
-    aFormatter->Print( aNestLevel+1, "(uvia_dia %s)\n", FormatInternalUnits( GetuViaDiameter() ).c_str() );
-    aFormatter->Print( aNestLevel+1, "(uvia_drill %s)\n", FormatInternalUnits( GetuViaDrill() ).c_str() );
-
-    // Save the diff_pair_gap and diff_pair_width values only if not the default, to avoid unnecessary
-    // incompatibility  with previous Pcbnew versions.
-    if( ( DEFAULT_DIFF_PAIR_WIDTH != GetDiffPairWidth() ) ||
-        ( DEFAULT_DIFF_PAIR_GAP != GetDiffPairGap() ) )
-    {
-        aFormatter->Print( aNestLevel+1, "(diff_pair_width %s)\n",
-                FormatInternalUnits( GetDiffPairWidth() ).c_str() );
-        aFormatter->Print( aNestLevel+1, "(diff_pair_gap %s)\n",
-                FormatInternalUnits( GetDiffPairGap() ).c_str() );
-
-        // 6.0 TODO: figure out what to do with DiffPairViaGap...
-    }
-
-    for( NETCLASS::const_iterator it = begin(); it != end(); ++it )
-        aFormatter->Print( aNestLevel+1, "(add_net %s)\n", aFormatter->Quotew( *it ).c_str() );
-
-    aFormatter->Print( aNestLevel, ")\n\n" );
-}
diff --git a/common/project.cpp b/common/project.cpp
index ab0f6e50d4..ee98406979 100644
--- a/common/project.cpp
+++ b/common/project.cpp
@@ -23,20 +23,20 @@
 
 #include <wx/stdpaths.h>
 
+#include <common.h> // NAMELESS_PROJECT
+#include <config_params.h>
+#include <confirm.h>
 #include <fctsys.h>
+#include <fp_lib_table.h>
+#include <kicad_string.h>
+#include <kiface_ids.h>
+#include <kiway.h>
 #include <macros.h>
 #include <pgm_base.h>
 #include <project.h>
-#include <common.h>         // NAMELESS_PROJECT
-#include <confirm.h>
-#include <kicad_string.h>
-#include <config_params.h>
-#include <wildcards_and_files_ext.h>
-#include <fp_lib_table.h>
-#include <kiway.h>
-#include <kiface_ids.h>
+#include <project/project_file.h>
 #include <trace_helpers.h>
-#include <settings/project_file.h>
+#include <wildcards_and_files_ext.h>
 
 
 PROJECT::PROJECT() :
diff --git a/common/project/net_settings.cpp b/common/project/net_settings.cpp
new file mode 100644
index 0000000000..441c8689c6
--- /dev/null
+++ b/common/project/net_settings.cpp
@@ -0,0 +1,142 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2020 CERN
+ * @author Jon Evans <jon@craftyjon.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <project/net_settings.h>
+#include <settings/parameters.h>
+
+// Netclasses were originally only stored in board files.  The IU context is PCBNEW.
+#ifndef PCBNEW
+#define PCBNEW
+#endif
+#include <base_units.h>
+
+
+const int netSettingsSchemaVersion = 0;
+
+
+NET_SETTINGS::NET_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath ) :
+        NESTED_SETTINGS( "net_settings", netSettingsSchemaVersion, aParent, aPath ),
+        m_NetClasses()
+{
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "classes",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json ret = nlohmann::json::array();
+
+                NETCLASSPTR                netclass = m_NetClasses.GetDefault();
+                NETCLASSES::const_iterator nc       = m_NetClasses.begin();
+
+                for( unsigned int idx = 0; idx <= m_NetClasses.GetCount(); idx++ )
+                {
+                    if( idx > 0 )
+                    {
+                        netclass = nc->second;
+                        ++nc;
+                    }
+
+                    nlohmann::json netJson = {
+                        { "name",               netclass->GetName().ToUTF8() },
+                        { "clearance",          Iu2Millimeter( netclass->GetClearance() ) },
+                        { "track_width",        Iu2Millimeter( netclass->GetTrackWidth() ) },
+                        { "via_diameter",       Iu2Millimeter( netclass->GetViaDiameter() ) },
+                        { "via_drill",          Iu2Millimeter( netclass->GetViaDrill() ) },
+                        { "microvia_diameter",  Iu2Millimeter( netclass->GetuViaDiameter() ) },
+                        { "microvia_drill",     Iu2Millimeter( netclass->GetuViaDrill() ) },
+                        { "diff_pair_width",    Iu2Millimeter( netclass->GetDiffPairWidth() ) },
+                        { "diff_pair_gap",      Iu2Millimeter( netclass->GetDiffPairGap() ) },
+                        { "diff_pair_via_gap",  Iu2Millimeter( netclass->GetDiffPairViaGap() ) }
+                        };
+
+                    nlohmann::json nets = nlohmann::json::array();
+
+                    for( NETCLASS::const_iterator i = netclass->begin(); i != netclass->end(); ++i )
+                        if( !i->empty() )
+                            nets.push_back( std::string( i->ToUTF8() ) );
+
+                    netJson["nets"] = nets;
+
+                    ret.push_back( netJson );
+                }
+
+                return ret;
+            },
+            [&]( const nlohmann::json& aJson )
+            {
+                if( !aJson.is_array() )
+                    return;
+
+                m_NetClasses.Clear();
+                NETCLASSPTR netclass;
+                NETCLASSPTR defaultClass = m_NetClasses.GetDefault();
+
+                auto get =
+                        []( const nlohmann::json& aObj, const std::string& aKey, int aDefault )
+                        {
+                            if( aObj.contains( aKey ) )
+                                return Millimeter2iu( aObj[aKey].get<double>() );
+                            else
+                                return aDefault;
+                        };
+
+                for( const nlohmann::json& entry : aJson )
+                {
+                    if( !entry.is_object() || !entry.contains( "name" ) )
+                        continue;
+
+                    wxString name = entry["name"];
+
+                    if( name == defaultClass->GetName() )
+                        netclass = defaultClass;
+                    else
+                        netclass = std::make_shared<NETCLASS>( name );
+
+                    netclass->SetClearance( get( aJson, "clearance", netclass->GetClearance() ) );
+                    netclass->SetTrackWidth(
+                            get( aJson, "track_width", netclass->GetTrackWidth() ) );
+                    netclass->SetViaDiameter(
+                            get( aJson, "via_diameter", netclass->GetViaDiameter() ) );
+                    netclass->SetViaDrill( get( aJson, "via_drill", netclass->GetViaDrill() ) );
+                    netclass->SetuViaDiameter(
+                            get( aJson, "microvia_diameter", netclass->GetuViaDiameter() ) );
+                    netclass->SetuViaDrill(
+                            get( aJson, "microvia_drill", netclass->GetuViaDrill() ) );
+                    netclass->SetDiffPairWidth(
+                            get( aJson, "diff_pair_width", netclass->GetDiffPairWidth() ) );
+                    netclass->SetDiffPairGap(
+                            get( aJson, "diff_pair_gap", netclass->GetDiffPairGap() ) );
+                    netclass->SetDiffPairViaGap(
+                            get( aJson, "diff_pair_via_gap", netclass->GetDiffPairViaGap() ) );
+
+                    if( netclass != defaultClass )
+                        m_NetClasses.Add( netclass );
+                }
+            }, {} ) );
+}
+
+
+NET_SETTINGS::~NET_SETTINGS()
+{
+    // Release early before destroying members
+    if( m_parent )
+    {
+        m_parent->ReleaseNestedSettings( this );
+        m_parent = nullptr;
+    }
+}
diff --git a/common/project/project_file.cpp b/common/project/project_file.cpp
new file mode 100644
index 0000000000..ad64a3817b
--- /dev/null
+++ b/common/project/project_file.cpp
@@ -0,0 +1,478 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2020 CERN
+ * @author Jon Evans <jon@craftyjon.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config_params.h>
+#include <project.h>
+#include <project/net_settings.h>
+#include <project/project_file.h>
+#include <settings/common_settings.h>
+#include <settings/parameters.h>
+#include <wildcards_and_files_ext.h>
+#include <wx/config.h>
+#include <wx/log.h>
+
+extern const char* traceSettings;
+
+///! Update the schema version whenever a migration is required
+const int projectFileSchemaVersion = 1;
+
+
+PROJECT_FILE::PROJECT_FILE( const std::string& aFullPath ) :
+        JSON_SETTINGS( aFullPath, SETTINGS_LOC::PROJECT, projectFileSchemaVersion ),
+        m_sheets(), m_boards(), m_BoardSettings()
+{
+    // Keep old files around
+    m_deleteLegacyAfterMigration = false;
+
+    m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "sheets", &m_sheets, {} ) );
+
+    m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "boards", &m_boards, {} ) );
+
+    m_params.emplace_back(
+            new PARAM_LIST<wxString>( "libraries.pinned_symbol_libs", &m_PinnedSymbolLibs, {} ) );
+
+    m_params.emplace_back( new PARAM_LIST<wxString>(
+            "libraries.pinned_footprint_libs", &m_PinnedFootprintLibs, {} ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH_LIST( "cvpcb.equivalence_files", &m_EquivalenceFiles, {} ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH( "pcbnew.page_layout_descr_file", &m_PageLayoutDescrFile, "" ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH( "pcbnew.last_paths.netlist", &m_PcbLastPath[LAST_PATH_NETLIST], "" ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH( "pcbnew.last_paths.step", &m_PcbLastPath[LAST_PATH_STEP], "" ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH( "pcbnew.last_paths.idf", &m_PcbLastPath[LAST_PATH_IDF], "" ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH( "pcbnew.last_paths.vmrl", &m_PcbLastPath[LAST_PATH_VRML], "" ) );
+
+    m_params.emplace_back( new PARAM_PATH(
+            "pcbnew.last_paths.specctra_dsn", &m_PcbLastPath[LAST_PATH_SPECCTRADSN], "" ) );
+
+    m_params.emplace_back(
+            new PARAM_PATH( "pcbnew.last_paths.gencad", &m_PcbLastPath[LAST_PATH_GENCAD], "" ) );
+
+    m_NetSettings = std::make_shared<NET_SETTINGS>( this, "net_settings" );
+}
+
+
+bool PROJECT_FILE::MigrateFromLegacy( wxConfigBase* aCfg )
+{
+    bool     ret = true;
+    wxString str;
+    long     index = 0;
+
+    std::set<wxString> group_blacklist;
+
+    // Legacy files don't store board info; they assume board matches project name
+    // We will leave m_boards empty here so it can be populated with other code
+
+    // First handle migration of data that will be stored locally in this object
+
+    auto loadPinnedLibs =
+            [&]( const std::string& aDest )
+            {
+                int      libIndex = 1;
+                wxString libKey   = wxT( "PinnedItems" );
+                libKey << libIndex;
+
+                nlohmann::json libs = nlohmann::json::array();
+
+                while( aCfg->Read( libKey, &str ) )
+                {
+                    libs.push_back( str );
+
+                    aCfg->DeleteEntry( libKey, true );
+
+                    libKey = wxT( "PinnedItems" );
+                    libKey << ++libIndex;
+                }
+
+                ( *this )[PointerFromString( aDest )] = libs;
+            };
+
+    aCfg->SetPath( wxT( "/LibeditFrame" ) );
+    loadPinnedLibs( "libraries.pinned_symbol_libs" );
+
+    aCfg->SetPath( wxT( "/ModEditFrame" ) );
+    loadPinnedLibs( "libraries.pinned_footprint_libs" );
+
+    aCfg->SetPath( wxT( "/cvpcb/equfiles" ) );
+
+    {
+        int      eqIdx = 1;
+        wxString eqKey = wxT( "EquName" );
+        eqKey << eqIdx;
+
+        nlohmann::json eqs = nlohmann::json::array();
+
+        while( aCfg->Read( eqKey, &str ) )
+        {
+            eqs.push_back( str );
+
+            eqKey = wxT( "EquName" );
+            eqKey << ++eqIdx;
+        }
+
+        ( *this )[PointerFromString( "cvpcb.equivalence_files" )] = eqs;
+    }
+
+    // All CvPcb params that we want to keep have been migrated above
+    group_blacklist.insert( wxT( "/cvpcb" ) );
+
+    aCfg->SetPath( wxT( "/pcbnew" ) );
+
+    fromLegacyString( aCfg, "PageLayoutDescrFile",       "pcbnew.page_layout_descr_file" );
+    fromLegacyString( aCfg, "LastNetListRead",           "pcbnew.last_paths.netlist" );
+    fromLegacyString( aCfg, "LastSTEPExportPath",        "pcbnew.last_paths.step" );
+    fromLegacyString( aCfg, "LastIDFExportPath",         "pcbnew.last_paths.idf" );
+    fromLegacyString( aCfg, "LastVRMLExportPath",        "pcbnew.last_paths.vmrl" );
+    fromLegacyString( aCfg, "LastSpecctraDSNExportPath", "pcbnew.last_paths.specctra_dsn" );
+    fromLegacyString( aCfg, "LastGenCADExportPath",      "pcbnew.last_paths.gencad" );
+
+    std::string bp = "board.design_settings.";
+
+    {
+        int      idx = 1;
+        wxString key = wxT( "DRCExclusion" );
+        key << idx;
+
+        nlohmann::json exclusions = nlohmann::json::array();
+
+        while( aCfg->Read( key, &str ) )
+        {
+            exclusions.push_back( str );
+
+            key = wxT( "DRCExclusion" );
+            key << ++idx;
+        }
+
+        ( *this )[PointerFromString( bp + "drc_exclusions" )] = exclusions;
+    }
+
+    fromLegacy<bool>( aCfg,   "AllowMicroVias",  bp + "rules.allow_microvias" );
+    fromLegacy<bool>( aCfg,   "AllowBlindVias",  bp + "rules.allow_blind_buried_vias" );
+    fromLegacy<double>( aCfg, "MinClearance",    bp + "rules.min_clearance" );
+    fromLegacy<double>( aCfg, "MinTrackWidth",   bp + "rules.min_track_width" );
+    fromLegacy<double>( aCfg, "MinViaAnnulus",   bp + "rules.min_via_annulus" );
+    fromLegacy<double>( aCfg, "MinViaDiameter",  bp + "rules.min_via_diameter" );
+
+    if( !fromLegacy<double>( aCfg, "MinThroughDrill", bp + "rules.min_through_hole_diameter" ) )
+        fromLegacy<double>( aCfg, "MinViaDrill", bp + "rules.min_through_hole_diameter" );
+
+    fromLegacy<double>( aCfg, "MinMicroViaDiameter",  bp + "rules.min_microvia_diameter" );
+    fromLegacy<double>( aCfg, "MinMicroViaDrill",     bp + "rules.min_microvia_drill" );
+    fromLegacy<double>( aCfg, "MinHoleToHole",        bp + "rules.min_hole_to_hole" );
+    fromLegacy<double>( aCfg, "CopperEdgeClearance",  bp + "rules.min_copper_edge_clearance" );
+    fromLegacy<double>( aCfg, "SolderMaskClearance",  bp + "rules.solder_mask_clearance" );
+    fromLegacy<double>( aCfg, "SolderMaskMinWidth",   bp + "rules.solder_mask_min_width" );
+    fromLegacy<double>( aCfg, "SolderPasteClearance", bp + "rules.solder_paste_clearance" );
+    fromLegacy<double>( aCfg, "SolderPasteRatio",     bp + "rules.solder_paste_margin_ratio" );
+
+    if( !fromLegacy<double>( aCfg, "SilkLineWidth", bp + "defaults.silk_line_width" ) )
+        fromLegacy<double>( aCfg, "ModuleOutlineThickness", bp + "defaults.silk_line_width" );
+
+    if( !fromLegacy<double>( aCfg, "SilkTextSizeV", bp + "defaults.silk_text_size_v" ) )
+        fromLegacy<double>( aCfg, "ModuleTextSizeV", bp + "defaults.silk_text_size_v" );
+
+    if( !fromLegacy<double>( aCfg, "SilkTextSizeH", bp + "defaults.silk_text_size_h" ) )
+        fromLegacy<double>( aCfg, "ModuleTextSizeH", bp + "defaults.silk_text_size_h" );
+
+    if( !fromLegacy<double>( aCfg, "SilkTextSizeThickness", bp + "defaults.silk_text_thickness" ) )
+        fromLegacy<double>( aCfg, "ModuleTextSizeThickness", bp + "defaults.silk_text_thickness" );
+
+    fromLegacy<bool>( aCfg, "SilkTextItalic",   bp + "defaults.silk_text_italic" );
+    fromLegacy<bool>( aCfg, "SilkTextUpright",  bp + "defaults.silk_text_upright" );
+
+    if( !fromLegacy<double>( aCfg, "CopperLineWidth", bp + "defaults.copper_line_width" ) )
+        fromLegacy<double>( aCfg, "DrawSegmentWidth", bp + "defaults.copper_line_width" );
+
+    if( !fromLegacy<double>( aCfg, "CopperTextSizeV", bp + "defaults.copper_text_size_v" ) )
+        fromLegacy<double>( aCfg, "PcbTextSizeV", bp + "defaults.copper_text_size_v" );
+
+    if( !fromLegacy<double>( aCfg, "CopperTextSizeH", bp + "defaults.copper_text_size_h" ) )
+        fromLegacy<double>( aCfg, "PcbTextSizeH", bp + "defaults.copper_text_size_h" );
+
+    if( !fromLegacy<double>( aCfg, "CopperTextThickness", bp + "defaults.copper_text_thickness" ) )
+        fromLegacy<double>( aCfg, "PcbTextThickness", bp + "defaults.copper_text_thickness" );
+
+    fromLegacy<bool>( aCfg, "CopperTextItalic",   bp + "defaults.copper_text_italic" );
+    fromLegacy<bool>( aCfg, "CopperTextUpright",  bp + "defaults.copper_text_upright" );
+
+    if( !fromLegacy<double>( aCfg, "EdgeCutLineWidth", bp + "defaults.board_outline_line_width" ) )
+        fromLegacy<double>(
+                aCfg, "BoardOutlineThickness", bp + "defaults.board_outline_line_width" );
+
+    fromLegacy<double>( aCfg, "CourtyardLineWidth",   bp + "defaults.courtyard_line_width" );
+
+    fromLegacy<double>( aCfg, "FabLineWidth",         bp + "defaults.fab_line_width" );
+    fromLegacy<double>( aCfg, "FabTextSizeV",         bp + "defaults.fab_text_size_v" );
+    fromLegacy<double>( aCfg, "FabTextSizeH",         bp + "defaults.fab_text_size_h" );
+    fromLegacy<double>( aCfg, "FabTextSizeThickness", bp + "defaults.fab_text_thickness" );
+    fromLegacy<bool>(   aCfg, "FabTextItalic",        bp + "defaults.fab_text_italic" );
+    fromLegacy<bool>(   aCfg, "FabTextUpright",       bp + "defaults.fab_text_upright" );
+
+    if( !fromLegacy<double>( aCfg, "OthersLineWidth", bp + "defaults.other_line_width" ) )
+        fromLegacy<double>( aCfg, "ModuleOutlineThickness", bp + "defaults.other_line_width" );
+
+    fromLegacy<double>( aCfg, "OthersTextSizeV",         bp + "defaults.other_text_size_v" );
+    fromLegacy<double>( aCfg, "OthersTextSizeH",         bp + "defaults.other_text_size_h" );
+    fromLegacy<double>( aCfg, "OthersTextSizeThickness", bp + "defaults.other_text_thickness" );
+    fromLegacy<bool>(   aCfg, "OthersTextItalic",        bp + "defaults.other_text_italic" );
+    fromLegacy<bool>(   aCfg, "OthersTextUpright",       bp + "defaults.other_text_upright" );
+
+    fromLegacy<int>( aCfg, "DimensionUnits",     bp + "defaults.dimension_units" );
+    fromLegacy<int>( aCfg, "DimensionPrecision", bp + "defaults.dimension_precision" );
+
+    std::string sev = bp + "rule_severities";
+
+    fromLegacy<bool>(
+            aCfg, "RequireCourtyardDefinitions", sev + "legacy_no_courtyard_defined" );
+
+    fromLegacy<bool>( aCfg, "ProhibitOverlappingCourtyards", sev + "legacy_ourtyards_overlap" );
+
+    {
+        int      idx     = 1;
+        wxString keyBase = "TrackWidth";
+        wxString key     = keyBase;
+        double   val;
+
+        nlohmann::json widths = nlohmann::json::array();
+
+        key << idx;
+
+        while( aCfg->Read( key, &val ) )
+        {
+            widths.push_back( val );
+            key = keyBase;
+            key << ++idx;
+        }
+
+        ( *this )[PointerFromString( bp + "track_widths" )] = widths;
+    }
+
+    {
+        int      idx     = 1;
+        wxString keyBase = "ViaDiameter";
+        wxString key     = keyBase;
+        double   diameter;
+        double   drill   = 1.0;
+
+        nlohmann::json vias = nlohmann::json::array();
+
+        key << idx;
+
+        while( aCfg->Read( key, &diameter ) )
+        {
+            key = "ViaDrill";
+            aCfg->Read( key << idx, &drill );
+
+            nlohmann::json via = { { "diameter", diameter }, { "drill", drill } };
+            vias.push_back( via );
+
+            key = keyBase;
+            key << ++idx;
+        }
+
+        ( *this )[PointerFromString( bp + "via_dimensions" )] = vias;
+    }
+
+    {
+        int      idx     = 1;
+        wxString keyBase = "dPairWidth";
+        wxString key     = keyBase;
+        double   width;
+        double   gap     = 1.0;
+        double   via_gap = 1.0;
+
+        nlohmann::json pairs = nlohmann::json::array();
+
+        key << idx;
+
+        while( aCfg->Read( key, &width ) )
+        {
+            key = "dPairGap";
+            aCfg->Read( key << idx, &gap );
+
+            key = "dPairViaGap";
+            aCfg->Read( key << idx, &via_gap );
+
+            nlohmann::json pair = { { "width", width }, { "gap", gap }, { "via_gap", via_gap } };
+            pairs.push_back( pair );
+
+            key = keyBase;
+            key << ++idx;
+        }
+
+        ( *this )[PointerFromString( bp + "diff_pair_dimensions" )] = pairs;
+    }
+
+    // NOTE: severities are just left alone to be migrated by BOARD_DESIGN_SETTINGS when it
+    // initializes, so that common doesn't need knowledge of the DRC error list (this is the
+    // downside of storing them as string keys...  Do not blacklist the /pcbnew group so that
+    // this works!
+
+    // General group is unused these days, we can throw it away
+    group_blacklist.insert( wxT( "/general" ) );
+
+    // Next load sheet names and put all other legacy data in the legacy dict
+    aCfg->SetPath( wxT( "/" ) );
+
+    auto loadSheetNames =
+            [&]() -> bool
+            {
+                int            sheet = 1;
+                wxString       entry;
+                nlohmann::json arr   = nlohmann::json::array();
+
+                wxLogTrace( traceSettings, "Migrating sheet names" );
+
+                aCfg->SetPath( wxT( "/sheetnames" ) );
+
+                while( aCfg->Read( wxString::Format( "%d", sheet++ ), &entry ) )
+                {
+                    wxArrayString tokens = wxSplit( entry, ':' );
+
+                    if( tokens.size() == 2 )
+                    {
+                        wxLogTrace( traceSettings, "%d: %s = %s", sheet, tokens[0], tokens[1] );
+                        arr.push_back( nlohmann::json::array( { tokens[0], tokens[1] } ) );
+                    }
+                }
+
+                ( *this )[PointerFromString( "sheets" )] = arr;
+
+                aCfg->SetPath( "/" );
+
+                // TODO: any reason we want to fail on this?
+                return true;
+            };
+
+    std::vector<wxString> groups;
+
+    groups.emplace_back( "" );
+
+    auto loadLegacyPairs =
+            [&]( const std::string& aGroup ) -> bool
+            {
+                wxLogTrace( traceSettings, "Migrating group %s", aGroup );
+                bool     success = true;
+                wxString keyStr;
+                wxString val;
+
+                index = 0;
+
+                while( aCfg->GetNextEntry( keyStr, index ) )
+                {
+                    if( !aCfg->Read( keyStr, &val ) )
+                        continue;
+
+                    std::string key( keyStr.ToUTF8() );
+
+                    wxLogTrace( traceSettings, "    %s = %s", key, val );
+
+                    try
+                    {
+                        nlohmann::json::json_pointer ptr( "/legacy" + aGroup + "/" + key );
+                        ( *this )[ptr] = val;
+                    }
+                    catch( ... )
+                    {
+                        success = false;
+                    }
+                }
+
+                return success;
+            };
+
+    for( size_t i = 0; i < groups.size(); i++ )
+    {
+        aCfg->SetPath( groups[i] );
+
+        if( groups[i] == wxT( "/sheetnames" ) )
+        {
+            ret |= loadSheetNames();
+            continue;
+        }
+
+        aCfg->DeleteEntry( wxT( "last_client" ), true );
+        aCfg->DeleteEntry( wxT( "update" ), true );
+        aCfg->DeleteEntry( wxT( "version" ), true );
+
+        ret &= loadLegacyPairs( groups[i].ToStdString() );
+
+        index = 0;
+
+        while( aCfg->GetNextGroup( str, index ) )
+        {
+            wxString group = groups[i] + "/" + str;
+
+            if( !group_blacklist.count( group ) )
+                groups.emplace_back( group );
+        }
+
+        aCfg->SetPath( "/" );
+    }
+
+    return ret;
+}
+
+
+bool PROJECT_FILE::SaveToFile( const std::string& aDirectory, bool aForce )
+{
+    wxASSERT( m_project );
+
+    ( *this )[PointerFromString( "meta.filename" )] =
+            m_project->GetProjectName() + "." + ProjectFileExtension;
+
+    return JSON_SETTINGS::SaveToFile( aDirectory, aForce );
+}
+
+
+wxString PROJECT_FILE::getFileExt() const
+{
+    return ProjectFileExtension;
+}
+
+
+wxString PROJECT_FILE::getLegacyFileExt() const
+{
+    return LegacyProjectFileExtension;
+}
+
+
+void to_json( nlohmann::json& aJson, const FILE_INFO_PAIR& aPair )
+{
+    aJson = nlohmann::json::array( { aPair.first.AsString().ToUTF8(), aPair.second.ToUTF8() } );
+}
+
+
+void from_json( const nlohmann::json& aJson, FILE_INFO_PAIR& aPair )
+{
+    wxASSERT( aJson.is_array() && aJson.size() == 2 );
+    aPair.first  = KIID( wxString( aJson[0].get<std::string>().c_str(), wxConvUTF8 ) );
+    aPair.second = wxString( aJson[1].get<std::string>().c_str(), wxConvUTF8 );
+}
diff --git a/common/project/project_local_settings.cpp b/common/project/project_local_settings.cpp
new file mode 100644
index 0000000000..0766f0004f
--- /dev/null
+++ b/common/project/project_local_settings.cpp
@@ -0,0 +1,105 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2020 CERN
+ * @author Jon Evans <jon@craftyjon.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <project.h>
+#include <project/project_local_settings.h>
+#include <settings/parameters.h>
+
+const int projectLocalSettingsVersion = 1;
+
+
+PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( const std::string& aFilename ) :
+        JSON_SETTINGS( aFilename, SETTINGS_LOC::PROJECT, projectLocalSettingsVersion,
+                       /* aCreateIfMissing = */ true, /* aCreateIfDefault = */ false,
+                       /* aWriteFile = */ true ),
+        m_project( nullptr )
+{
+    m_params.emplace_back( new PARAM_LAMBDA<std::string>( "board.visible_layers",
+            [&]() -> std::string
+            {
+                return m_VisibleLayers.FmtHex();
+            },
+            [&]( const std::string& aString )
+            {
+                m_VisibleLayers.ParseHex( aString.c_str(), aString.size() );
+            },
+            LSET::AllLayersMask().FmtHex() ) );
+
+    static GAL_SET defaultVisible;
+    defaultVisible.set().reset( GAL_LAYER_INDEX( LAYER_MOD_TEXT_INVISIBLE ) );
+
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "board.visible_items",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json ret = nlohmann::json::array();
+
+                for( size_t i = 0; i < m_VisibleItems.size(); i++ )
+                    if( m_VisibleItems.test( i ) )
+                        ret.push_back( i );
+
+                return ret;
+            },
+            [&]( const nlohmann::json& aVal )
+            {
+                if( !aVal.is_array() || aVal.empty() )
+                {
+                    m_VisibleItems = defaultVisible;
+                    return;
+                }
+
+                m_VisibleItems.reset();
+
+                for( const nlohmann::json& entry : aVal )
+                {
+                    try
+                    {
+                        int i = entry.get<int>();
+                        m_VisibleItems.set( i );
+                    }
+                    catch( ... )
+                    {
+                        // Non-integer or out of range entry in the array; ignore
+                    }
+                }
+            },
+            {} ) );
+}
+
+
+bool PROJECT_LOCAL_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
+{
+    /**
+     * The normal legacy migration code won't be used for this because the only legacy
+     * information stored here was stored in board files, so we do that migration when loading
+     * the board.
+     */
+    return true;
+}
+
+
+bool PROJECT_LOCAL_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
+{
+    wxASSERT( m_project );
+
+    ( *this )[PointerFromString( "meta.filename" )] =
+            m_project->GetProjectName() + "." + ProjectLocalSettingsFileExtension;
+
+    return JSON_SETTINGS::SaveToFile( aDirectory, aForce );
+}
diff --git a/common/settings/json_settings.cpp b/common/settings/json_settings.cpp
index 5b088e6c9a..d519fadb55 100644
--- a/common/settings/json_settings.cpp
+++ b/common/settings/json_settings.cpp
@@ -46,11 +46,11 @@ JSON_SETTINGS::JSON_SETTINGS( const std::string& aFilename, SETTINGS_LOC aLocati
         m_createIfDefault( aCreateIfDefault ),
         m_writeFile( aWriteFile ),
         m_deleteLegacyAfterMigration( true ),
+        m_resetParamsIfMissing( true ),
         m_schemaVersion( aSchemaVersion ),
         m_manager( nullptr )
 {
-    m_params.emplace_back(
-            new PARAM<std::string>( "meta.filename", &m_filename, m_filename, true ) );
+    ( *this )[PointerFromString( "meta.filename" )] = GetFullFilename();
 
     m_params.emplace_back(
             new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion, true ) );
@@ -66,13 +66,19 @@ JSON_SETTINGS::~JSON_SETTINGS()
 }
 
 
+wxString JSON_SETTINGS::GetFullFilename() const
+{
+    return wxString( m_filename.c_str(), wxConvUTF8 ) + "." + getFileExt();
+}
+
+
 void JSON_SETTINGS::Load()
 {
     for( auto param : m_params )
     {
         try
         {
-            param->Load( this );
+            param->Load( this, m_resetParamsIfMissing );
         }
         catch( ... )
         {
@@ -106,7 +112,8 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
 
         if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
         {
-            wxLogTrace( traceSettings, "%s: could not create temp file for migration", m_filename );
+            wxLogTrace( traceSettings, "%s: could not create temp file for migration",
+                        GetFullFilename() );
             backed_up = false;
         }
 
@@ -118,11 +125,12 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
         if( !MigrateFromLegacy( cfg.get() ) )
         {
             wxLogTrace( traceSettings,
-                        "%s: migrated; not all settings were found in legacy file", m_filename );
+                        "%s: migrated; not all settings were found in legacy file",
+                        GetFullFilename() );
         }
         else
         {
-            wxLogTrace( traceSettings, "%s: migrated from legacy format", m_filename );
+            wxLogTrace( traceSettings, "%s: migrated from legacy format", GetFullFilename() );
         }
 
         if( backed_up )
@@ -186,14 +194,15 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
             }
             catch( ... )
             {
-                wxLogTrace( traceSettings, "%s: file version could not be read!", m_filename );
+                wxLogTrace(
+                        traceSettings, "%s: file version could not be read!", GetFullFilename() );
                 success = false;
             }
 
             if( filever >= 0 && filever < m_schemaVersion )
             {
                 wxLogTrace( traceSettings, "%s: attempting migration from version %d to %d",
-                        m_filename, filever, m_schemaVersion );
+                            GetFullFilename(), filever, m_schemaVersion );
 
                 if( Migrate() )
                 {
@@ -201,13 +210,13 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
                 }
                 else
                 {
-                    wxLogTrace( traceSettings, "%s: migration failed!", m_filename );
+                    wxLogTrace( traceSettings, "%s: migration failed!", GetFullFilename() );
                 }
             }
             else if( filever > m_schemaVersion )
             {
                 wxLogTrace( traceSettings,
-                        "%s: warning: file version %d is newer than latest (%d)", m_filename,
+                        "%s: warning: file version %d is newer than latest (%d)", GetFullFilename(),
                         filever, m_schemaVersion );
             }
         }
@@ -227,7 +236,7 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
     for( auto settings : m_nested_settings )
         settings->LoadFromFile();
 
-    wxLogTrace( traceSettings, "Loaded %s with schema %d", GetFilename(), m_schemaVersion );
+    wxLogTrace( traceSettings, "Loaded %s with schema %d", GetFullFilename(), m_schemaVersion );
 
     // If we migrated, clean up the legacy file (with no extension)
     if( legacy_migrated || migrated )
@@ -292,7 +301,7 @@ bool JSON_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
     {
         wxLogTrace( traceSettings,
                 "File for %s doesn't exist and m_createIfMissing == false; not saving",
-                m_filename );
+                GetFullFilename() );
         return false;
     }
 
@@ -305,27 +314,25 @@ bool JSON_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
 
     if( !modified && !aForce && path.FileExists() )
     {
-        wxLogTrace( traceSettings, "%s contents not modified, skipping save", m_filename );
+        wxLogTrace( traceSettings, "%s contents not modified, skipping save", GetFullFilename() );
         return false;
     }
     else if( !modified && !aForce && !m_createIfDefault )
     {
         wxLogTrace( traceSettings,
                 "%s contents still default and m_createIfDefault == false; not saving",
-                m_filename );
+                    GetFullFilename() );
         return false;
     }
 
-    wxLogTrace( traceSettings, "Saving %s", m_filename );
-
     if( !path.DirExists() && !path.Mkdir() )
     {
         wxLogTrace( traceSettings, "Warning: could not create path %s, can't save %s",
-                    path.GetPath(), m_filename );
+                    path.GetPath(), GetFullFilename() );
         return false;
     }
 
-    wxLogTrace( traceSettings, "Saving %s", m_filename );
+    wxLogTrace( traceSettings, "Saving %s", GetFullFilename() );
 
     LOCALE_IO dummy;
 
@@ -336,7 +343,7 @@ bool JSON_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
     }
     catch( const std::exception& e )
     {
-        wxLogTrace( traceSettings, "Warning: could not save %s: %s", m_filename, e.what() );
+        wxLogTrace( traceSettings, "Warning: could not save %s: %s", GetFullFilename(), e.what() );
     }
     catch( ... )
     {
@@ -346,9 +353,9 @@ bool JSON_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
 }
 
 
-OPT<nlohmann::json> JSON_SETTINGS::GetJson( std::string aPath ) const
+OPT<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
 {
-    nlohmann::json::json_pointer ptr = PointerFromString( std::move( aPath ) );
+    nlohmann::json::json_pointer ptr = PointerFromString( aPath );
 
     if( this->contains( ptr ) )
     {
@@ -498,6 +505,9 @@ void JSON_SETTINGS::AddNestedSettings( NESTED_SETTINGS* aSettings )
 
 void JSON_SETTINGS::ReleaseNestedSettings( NESTED_SETTINGS* aSettings )
 {
+    if( !aSettings )
+        return;
+
     auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
                             [&aSettings]( const JSON_SETTINGS* aPtr ) {
                               return aPtr == aSettings;
@@ -509,21 +519,23 @@ void JSON_SETTINGS::ReleaseNestedSettings( NESTED_SETTINGS* aSettings )
         ( *it )->SaveToFile();
         m_nested_settings.erase( it );
     }
+
+    aSettings->SetParent( nullptr );
 }
 
 
 // Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
 
-template<> OPT<wxString> JSON_SETTINGS::Get( std::string aPath ) const
+template<> OPT<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
 {
-    if( OPT<nlohmann::json> opt_json = GetJson( std::move( aPath ) ) )
+    if( OPT<nlohmann::json> opt_json = GetJson( aPath ) )
         return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
 
     return NULLOPT;
 }
 
 
-template<> void JSON_SETTINGS::Set<wxString>( std::string aPath, wxString aVal )
+template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
 {
     ( *this )[PointerFromString( std::move( aPath ) ) ] = aVal.ToUTF8();
 }
diff --git a/common/settings/nested_settings.cpp b/common/settings/nested_settings.cpp
index c685a7e895..7ece005482 100644
--- a/common/settings/nested_settings.cpp
+++ b/common/settings/nested_settings.cpp
@@ -30,19 +30,14 @@ NESTED_SETTINGS::NESTED_SETTINGS( const std::string& aName, int aVersion, JSON_S
         JSON_SETTINGS( aName, SETTINGS_LOC::NESTED, aVersion ),
         m_parent( aParent ), m_path( aPath )
 {
-    if( m_parent )
-    {
-    	m_parent->AddNestedSettings( this );
-    }
-
-    // In case we were created after the parent's ctor
-    LoadFromFile();
+    SetParent( aParent );
 }
 
 
 NESTED_SETTINGS::~NESTED_SETTINGS()
 {
-    m_parent->ReleaseNestedSettings( this );
+    if( m_parent )
+        m_parent->ReleaseNestedSettings( this );
 }
 
 
@@ -77,6 +72,9 @@ bool NESTED_SETTINGS::LoadFromFile( const std::string& aDirectory )
 
 bool NESTED_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
 {
+    if( !m_parent )
+        return false;
+
     bool modified = Store();
 
     try
@@ -108,3 +106,15 @@ bool NESTED_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
 
     return modified;
 }
+
+
+void NESTED_SETTINGS::SetParent( JSON_SETTINGS* aParent )
+{
+    m_parent = aParent;
+
+    if( m_parent )
+        m_parent->AddNestedSettings( this );
+
+    // In case we were created after the parent's ctor
+    LoadFromFile();
+}
diff --git a/common/settings/project_file.cpp b/common/settings/project_file.cpp
deleted file mode 100644
index d30b9161b1..0000000000
--- a/common/settings/project_file.cpp
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright (C) 2020 CERN
- * @author Jon Evans <jon@craftyjon.com>
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-#include <config_params.h>
-#include <settings/common_settings.h>
-#include <settings/parameters.h>
-#include <settings/project_file.h>
-#include <wildcards_and_files_ext.h>
-#include <wx/config.h>
-#include <wx/log.h>
-
-extern const char* traceSettings;
-
-///! Update the schema version whenever a migration is required
-const int projectFileSchemaVersion = 1;
-
-
-PROJECT_FILE::PROJECT_FILE( const std::string& aFullPath ) :
-        JSON_SETTINGS( aFullPath, SETTINGS_LOC::NONE, projectFileSchemaVersion ),
-        m_sheets(), m_boards()
-{
-    // Keep old files around
-    m_deleteLegacyAfterMigration = false;
-
-    m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "sheets", &m_sheets, {} ) );
-
-    m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "boards", &m_boards, {} ) );
-
-    m_params.emplace_back(
-            new PARAM_LIST<wxString>( "libraries.pinned_symbol_libs", &m_PinnedSymbolLibs, {} ) );
-
-    m_params.emplace_back( new PARAM_LIST<wxString>(
-            "libraries.pinned_footprint_libs", &m_PinnedFootprintLibs, {} ) );
-
-    m_params.emplace_back(
-            new PARAM_PATH_LIST( "cvpcb.equivalence_files", &m_EquivalenceFiles, {} ) );
-}
-
-
-bool PROJECT_FILE::MigrateFromLegacy( wxConfigBase* aLegacyFile )
-{
-    bool     ret = true;
-    wxString str;
-    long     index = 0;
-
-    std::set<wxString> group_blacklist;
-
-    // Legacy files don't store board info; they assume board matches project name
-    // We will leave m_boards empty here so it can be populated with other code
-
-    // First handle migration of data that will be stored locally in this object
-
-    auto loadPinnedLibs =
-            [&]( const std::string& aDest )
-            {
-                int      libIndex = 1;
-                wxString libKey   = wxT( "PinnedItems" );
-                libKey << libIndex;
-
-                nlohmann::json libs = nlohmann::json::array();
-
-                while( aLegacyFile->Read( libKey, &str ) )
-                {
-                    libs.push_back( str );
-
-                    aLegacyFile->DeleteEntry( libKey, true );
-
-                    libKey = wxT( "PinnedItems" );
-                    libKey << ++libIndex;
-                }
-
-                ( *this )[PointerFromString( aDest )] = libs;
-            };
-
-    aLegacyFile->SetPath( wxT( "/LibeditFrame" ) );
-    loadPinnedLibs( "libraries.pinned_symbol_libs" );
-
-    aLegacyFile->SetPath( wxT( "/ModEditFrame" ) );
-    loadPinnedLibs( "libraries.pinned_footprint_libs" );
-
-    aLegacyFile->SetPath( wxT( "/cvpcb/equfiles" ) );
-
-    {
-        int      eqIdx = 1;
-        wxString eqKey = wxT( "EquName" );
-        eqKey << eqIdx;
-
-        nlohmann::json eqs = nlohmann::json::array();
-
-        while( aLegacyFile->Read( eqKey, &str ) )
-        {
-            eqs.push_back( str );
-
-            eqKey = wxT( "EquName" );
-            eqKey << ++eqIdx;
-        }
-
-        ( *this )[PointerFromString( "cvpcb.equivalence_files" )] = eqs;
-    }
-
-    // No other cvpcb params are currently used
-    group_blacklist.insert( "/cvpcb" );
-
-    // Next load sheet names and put all other legacy data in the legacy dict
-    aLegacyFile->SetPath( "/" );
-
-    auto loadSheetNames =
-            [&]() -> bool
-            {
-                int            sheet = 1;
-                wxString       entry;
-                nlohmann::json arr   = nlohmann::json::array();
-
-                wxLogTrace( traceSettings, "Migrating sheet names" );
-
-                aLegacyFile->SetPath( wxT( "/sheetnames" ) );
-
-                while( aLegacyFile->Read( wxString::Format( "%d", sheet++ ), &entry ) )
-                {
-                    wxArrayString tokens = wxSplit( entry, ':' );
-
-                    if( tokens.size() == 2 )
-                    {
-                        wxLogTrace( traceSettings, "%d: %s = %s", sheet, tokens[0], tokens[1] );
-                        arr.push_back( nlohmann::json::array( { tokens[0], tokens[1] } ) );
-                    }
-                }
-
-                ( *this )[PointerFromString( "sheets" )] = arr;
-
-                aLegacyFile->SetPath( "/" );
-
-                // TODO: any reason we want to fail on this?
-                return true;
-            };
-
-    std::vector<wxString> groups;
-
-    groups.emplace_back( "" );
-
-    auto loadLegacyPairs =
-            [&]( const std::string& aGroup ) -> bool
-            {
-                wxLogTrace( traceSettings, "Migrating group %s", aGroup );
-                bool     success = true;
-                wxString keyStr;
-                wxString val;
-
-                index = 0;
-
-                while( aLegacyFile->GetNextEntry( keyStr, index ) )
-                {
-                    if( !aLegacyFile->Read( keyStr, &val ) )
-                        continue;
-
-                    std::string key( keyStr.ToUTF8() );
-
-                    wxLogTrace( traceSettings, "    %s = %s", key, val );
-
-                    try
-                    {
-                        nlohmann::json::json_pointer ptr( "/legacy" + aGroup + "/" + key );
-                        ( *this )[ptr] = val;
-                    }
-                    catch( ... )
-                    {
-                        success = false;
-                    }
-                }
-
-                return success;
-            };
-
-    for( size_t i = 0; i < groups.size(); i++ )
-    {
-        aLegacyFile->SetPath( groups[i] );
-
-        if( groups[i] == wxT( "/sheetnames" ) )
-        {
-            ret |= loadSheetNames();
-            continue;
-        }
-
-        aLegacyFile->DeleteEntry( wxT( "last_client" ), true );
-        aLegacyFile->DeleteEntry( wxT( "update" ), true );
-        aLegacyFile->DeleteEntry( wxT( "version" ), true );
-
-        ret &= loadLegacyPairs( groups[i].ToStdString() );
-
-        index = 0;
-
-        while( aLegacyFile->GetNextGroup( str, index ) )
-        {
-            wxString group = groups[i] + "/" + str;
-
-            if( !group_blacklist.count( group ) )
-                groups.emplace_back( group );
-        }
-
-        aLegacyFile->SetPath( "/" );
-    }
-
-    return ret;
-}
-
-
-wxString PROJECT_FILE::getFileExt() const
-{
-    return ProjectFileExtension;
-}
-
-
-wxString PROJECT_FILE::getLegacyFileExt() const
-{
-    return LegacyProjectFileExtension;
-}
-
-
-void to_json( nlohmann::json& aJson, const FILE_INFO_PAIR& aPair )
-{
-    aJson = nlohmann::json::array( { aPair.first.AsString().ToUTF8(), aPair.second.ToUTF8() } );
-}
-
-
-void from_json( const nlohmann::json& aJson, FILE_INFO_PAIR& aPair )
-{
-    wxASSERT( aJson.is_array() && aJson.size() == 2 );
-    aPair.first  = KIID( wxString( aJson[0].get<std::string>().c_str(), wxConvUTF8 ) );
-    aPair.second = wxString( aJson[1].get<std::string>().c_str(), wxConvUTF8 );
-}
\ No newline at end of file
diff --git a/common/settings/settings_manager.cpp b/common/settings/settings_manager.cpp
index e130e73dca..559614b81a 100644
--- a/common/settings/settings_manager.cpp
+++ b/common/settings/settings_manager.cpp
@@ -30,10 +30,11 @@
 #include <gestfich.h>
 #include <macros.h>
 #include <project.h>
+#include <project/project_file.h>
+#include <project/project_local_settings.h>
 #include <settings/app_settings.h>
 #include <settings/color_settings.h>
 #include <settings/common_settings.h>
-#include <settings/project_file.h>
 #include <settings/settings_manager.h>
 #include <wildcards_and_files_ext.h>
 
@@ -46,7 +47,7 @@ const char* traceSettings = "SETTINGS";
 
 
 /// Project settings path will be <projectname> + this
-#define PROJECT_SETTINGS_DIR_SUFFIX wxT( "-settings" )
+#define PROJECT_BACKUPS_DIR_SUFFIX wxT( "-backups" )
 
 
 SETTINGS_MANAGER::SETTINGS_MANAGER() :
@@ -83,7 +84,7 @@ JSON_SETTINGS* SETTINGS_MANAGER::RegisterSettings( JSON_SETTINGS* aSettings, boo
 
     ptr->SetManager( this );
 
-    wxLogTrace( traceSettings, "Registered new settings object %s", ptr->GetFilename() );
+    wxLogTrace( traceSettings, "Registered new settings object %s", ptr->GetFullFilename() );
 
     if( aLoadNow )
         ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
@@ -139,13 +140,13 @@ void SETTINGS_MANAGER::Save( JSON_SETTINGS* aSettings )
 
     if( it != m_settings.end() )
     {
-        wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFilename() );
+        wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFullFilename() );
         ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
     }
 }
 
 
-void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings )
+void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave )
 {
     auto it = std::find_if( m_settings.begin(), m_settings.end(),
                             [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
@@ -155,8 +156,11 @@ void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings )
 
     if( it != m_settings.end() )
     {
-        wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFilename() );
-        ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
+        wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFullFilename() );
+
+        if( aSave )
+            ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
+
         m_settings.erase( it );
     }
 }
@@ -332,8 +336,7 @@ std::string SETTINGS_MANAGER::GetPathForSettingsFile( JSON_SETTINGS* aSettings )
         return GetUserSettingsPath();
 
     case SETTINGS_LOC::PROJECT:
-        // TODO(JE)
-        return "";
+        return std::string( Prj().GetProjectPath().ToUTF8() );
 
     case SETTINGS_LOC::COLORS:
         return GetColorSettingsPath();
@@ -667,7 +670,7 @@ bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* a
 }
 
 
-bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath )
+bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
 {
     // Normalize path to new format even if migrating from a legacy file
     wxFileName path( aFullPath );
@@ -678,33 +681,45 @@ bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath )
     wxString fullPath = path.GetFullPath();
 
     // Unload if already loaded
-    if( m_projects.count( fullPath ) && !UnloadProject( *m_projects.at( fullPath ).get() ) )
+    if( m_projects.count( fullPath ) && !UnloadProject( m_projects.at( fullPath ).get() ) )
         return false;
 
     // No MDI yet
-    if( !m_projects.empty() && !UnloadProject( *m_projects.begin()->second ) )
+    if( aSetActive && !m_projects.empty() && !UnloadProject( m_projects.begin()->second.get() ) )
         return false;
 
     wxLogTrace( traceSettings, "Load project %s", fullPath );
 
-    m_projects[fullPath] = std::make_unique<PROJECT>();
-    m_projects[fullPath]->setProjectFullName( fullPath );
+    std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
+    project->setProjectFullName( fullPath );
 
-    return loadProjectFile( *m_projects[fullPath] );
+    bool success = loadProjectFile( *project );
+
+    m_projects[fullPath].reset( project.release() );
+
+    std::string fn( path.GetName() );
+
+    PROJECT_LOCAL_SETTINGS* settings = static_cast<PROJECT_LOCAL_SETTINGS*>(
+            RegisterSettings( new PROJECT_LOCAL_SETTINGS( fn ) ) );
+
+    m_projects[fullPath]->setLocalSettings( settings );
+    settings->SetProject( m_projects[fullPath].get() );
+
+    return success;
 }
 
 
-bool SETTINGS_MANAGER::UnloadProject( PROJECT& aProject )
+bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
 {
-    if( !m_projects.count( aProject.GetProjectFullName() ) )
+    if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
         return false;
 
-    if( !unloadProjectFile( aProject ) )
+    if( !unloadProjectFile( aProject, aSave ) )
         return false;
 
-    wxLogTrace( traceSettings, "Unload project %s", aProject.GetProjectFullName() );
+    wxLogTrace( traceSettings, "Unload project %s", aProject->GetProjectFullName() );
 
-    m_projects.erase( aProject.GetProjectFullName() );
+    m_projects.erase( aProject->GetProjectFullName() );
 
     return true;
 }
@@ -712,34 +727,50 @@ bool SETTINGS_MANAGER::UnloadProject( PROJECT& aProject )
 
 PROJECT& SETTINGS_MANAGER::Prj() const
 {
-    // No MDI yet
-    wxASSERT( m_projects.size() == 1 );
+    // No MDI yet:  First project in the list is the active project
     return *m_projects.begin()->second;
 }
 
 
-bool SETTINGS_MANAGER::SaveProject()
+PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
 {
-    wxString name = Prj().GetProjectFullName();
+    if( m_projects.count( aFullPath ) )
+        return m_projects.at( aFullPath ).get();
 
-    if( !m_project_files.count( name ) )
+    return nullptr;
+}
+
+
+bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath )
+{
+    wxString path = aFullPath;
+
+    if( path.empty() )
+        path = Prj().GetProjectFullName();
+
+    if( !m_project_files.count( path ) )
         return false;
 
-    m_project_files[name]->SaveToFile();
+    PROJECT_FILE* project     = m_project_files.at( path );
+    std::string   projectPath = GetPathForSettingsFile( project );
+
+    project->SaveToFile( projectPath );
+    Prj().GetLocalSettings().SaveToFile( projectPath );
 
     return true;
 }
 
 
-wxString SETTINGS_MANAGER::GetProjectSettingsPath() const
+wxString SETTINGS_MANAGER::GetProjectBackupsPath() const
 {
-    return Prj().GetProjectPath() + Prj().GetProjectName() + PROJECT_SETTINGS_DIR_SUFFIX;
+    return Prj().GetProjectPath() + Prj().GetProjectName() + PROJECT_BACKUPS_DIR_SUFFIX;
 }
 
 
 bool SETTINGS_MANAGER::loadProjectFile( PROJECT& aProject )
 {
-    std::string fn( aProject.GetProjectFullName().ToUTF8() );
+    wxFileName fullFn( aProject.GetProjectFullName() );
+    std::string fn( fullFn.GetName().ToUTF8() );
 
     PROJECT_FILE* file =
             static_cast<PROJECT_FILE*>( RegisterSettings( new PROJECT_FILE( fn ), false ) );
@@ -747,14 +778,18 @@ bool SETTINGS_MANAGER::loadProjectFile( PROJECT& aProject )
     m_project_files[aProject.GetProjectFullName()] = file;
 
     aProject.setProjectFile( file );
+    file->SetProject( &aProject );
 
-    return file->LoadFromFile();
+    return file->LoadFromFile( std::string( fullFn.GetPath().ToUTF8() ) );
 }
 
 
-bool SETTINGS_MANAGER::unloadProjectFile( PROJECT& aProject )
+bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
 {
-    wxString name = aProject.GetProjectFullName();
+    if( !aProject )
+        return false;
+
+    wxString name = aProject->GetProjectFullName();
 
     if( !m_project_files.count( name ) )
         return false;
@@ -769,7 +804,13 @@ bool SETTINGS_MANAGER::unloadProjectFile( PROJECT& aProject )
 
     if( it != m_settings.end() )
     {
-        ( *it )->SaveToFile();
+        std::string projectPath = GetPathForSettingsFile( it->get() );
+
+        FlushAndRelease( &aProject->GetLocalSettings(), aSave );
+
+        if( aSave )
+            ( *it )->SaveToFile( projectPath );
+
         m_settings.erase( it );
     }
 
diff --git a/pcbnew/swig/netclass.i b/common/swig/netclass.i
similarity index 100%
rename from pcbnew/swig/netclass.i
rename to common/swig/netclass.i
diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp
index 3731dcab68..7d91bd47c9 100644
--- a/common/wildcards_and_files_ext.cpp
+++ b/common/wildcards_and_files_ext.cpp
@@ -122,6 +122,7 @@ const std::string VrmlFileExtension( "wrl" );
 
 const std::string ProjectFileExtension( "kicad_pro" );
 const std::string LegacyProjectFileExtension( "pro" );
+const std::string ProjectLocalSettingsFileExtension( "kicad_prl" );
 const std::string LegacySchematicFileExtension( "sch" );
 const std::string KiCadSchematicFileExtension( "kicad_sch" );
 const std::string NetlistFileExtension( "net" );
diff --git a/cvpcb/auto_associate.cpp b/cvpcb/auto_associate.cpp
index 7f6bf9fba4..8872a37c76 100644
--- a/cvpcb/auto_associate.cpp
+++ b/cvpcb/auto_associate.cpp
@@ -39,7 +39,7 @@
 #include <cvpcb_association.h>
 #include <cvpcb_mainframe.h>
 #include <listboxes.h>
-#include <settings/project_file.h>
+#include <project/project_file.h>
 
 #define QUOTE   '\''
 
diff --git a/cvpcb/dialogs/dialog_config_equfiles.cpp b/cvpcb/dialogs/dialog_config_equfiles.cpp
index a8d42a0733..052314effe 100644
--- a/cvpcb/dialogs/dialog_config_equfiles.cpp
+++ b/cvpcb/dialogs/dialog_config_equfiles.cpp
@@ -35,7 +35,7 @@
 
 #include <cvpcb_mainframe.h>
 #include <dialog_config_equfiles.h>
-#include <settings/project_file.h>
+#include <project/project_file.h>
 #include <settings/settings_manager.h>
 #include <wildcards_and_files_ext.h>
 
diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp
index 0ce6ecea22..55a0e1bf5b 100644
--- a/eeschema/files-io.cpp
+++ b/eeschema/files-io.cpp
@@ -54,7 +54,7 @@
 #include <connection_graph.h>
 #include <tool/actions.h>
 #include <tools/sch_editor_control.h>
-#include <settings/project_file.h>
+#include <project/project_file.h>
 #include <settings/settings_manager.h>
 #include <netlist.h>
 #include <widgets/infobar.h>
@@ -262,7 +262,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
         return false;
 
     wxFileName pro = fullFileName;
-    pro.SetExt( LegacyProjectFileExtension );
+    pro.SetExt( ProjectFileExtension );
 
     bool is_new = !wxFileName::IsFileReadable( fullFileName );
 
@@ -332,6 +332,8 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
     Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, NULL );
     Prj().SchSymbolLibTable();
 
+    Schematic().SetProject( Prj() );
+
     SetShutdownBlockReason( _( "Schematic file changes are unsaved" ) );
 
     if( is_new )
diff --git a/eeschema/symbol_tree_model_adapter.cpp b/eeschema/symbol_tree_model_adapter.cpp
index c288c50480..3ec560e3b6 100644
--- a/eeschema/symbol_tree_model_adapter.cpp
+++ b/eeschema/symbol_tree_model_adapter.cpp
@@ -43,7 +43,7 @@ SYMBOL_TREE_MODEL_ADAPTER::PTR SYMBOL_TREE_MODEL_ADAPTER::Create( EDA_BASE_FRAME
 
 
 SYMBOL_TREE_MODEL_ADAPTER::SYMBOL_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs ) :
-        LIB_TREE_MODEL_ADAPTER( aParent ),
+        LIB_TREE_MODEL_ADAPTER( aParent, "pinned_symbol_libs" ),
         m_libs( (SYMBOL_LIB_TABLE*) aLibs )
 {}
 
diff --git a/eeschema/symbol_tree_synchronizing_adapter.cpp b/eeschema/symbol_tree_synchronizing_adapter.cpp
index 10fa479746..589353cb09 100644
--- a/eeschema/symbol_tree_synchronizing_adapter.cpp
+++ b/eeschema/symbol_tree_synchronizing_adapter.cpp
@@ -39,7 +39,7 @@ LIB_TREE_MODEL_ADAPTER::PTR SYMBOL_TREE_SYNCHRONIZING_ADAPTER::Create( LIB_EDIT_
 
 SYMBOL_TREE_SYNCHRONIZING_ADAPTER::SYMBOL_TREE_SYNCHRONIZING_ADAPTER( LIB_EDIT_FRAME* aParent,
                                                                       LIB_MANAGER* aLibMgr ) :
-        LIB_TREE_MODEL_ADAPTER( aParent ),
+        LIB_TREE_MODEL_ADAPTER( aParent, "pinned_symbol_libs" ),
         m_frame( aParent ),
         m_libMgr( aLibMgr ),
         m_lastSyncHash( -1 )
diff --git a/include/board_design_settings.h b/include/board_design_settings.h
index 0ad8fa330f..16edf9021e 100644
--- a/include/board_design_settings.h
+++ b/include/board_design_settings.h
@@ -30,6 +30,9 @@
 #include <config_params.h>
 #include <board_stackup_manager/class_board_stackup.h>
 #include <drc/drc_rule.h>
+#include <settings/nested_settings.h>
+#include <widgets/ui_common.h>
+#include <zone_settings.h>
 
 
 #define DEFAULT_SILK_LINE_WIDTH       0.12
@@ -208,7 +211,7 @@ enum class VIATYPE : int;
  * BOARD_DESIGN_SETTINGS
  * contains design settings for a BOARD object.
  */
-class BOARD_DESIGN_SETTINGS
+class BOARD_DESIGN_SETTINGS : public NESTED_SETTINGS
 {
 public:
     // Note: the first value in each dimensions list is the current netclass value
@@ -217,7 +220,7 @@ public:
     std::vector<DIFF_PAIR_DIMENSION> m_DiffPairDimensionsList;
 
     // List of netclasses. There is always the default netclass.
-    NETCLASSES                       m_NetClasses;
+    //NETCLASSES                       m_NetClasses;
     std::vector<DRC_SELECTOR*>       m_DRCRuleSelectors;
     std::vector<DRC_RULE*>           m_DRCRules;
 
@@ -242,6 +245,9 @@ public:
 
     std::map< int, int > m_DRCSeverities;   // Map from DRCErrorCode to SEVERITY
 
+    /// Excluded DRC items
+    std::set<wxString> m_DrcExclusions;
+
     /** Option to handle filled polygons in zones:
      * the "legacy" option is using thick outlines around filled polygons: give the best shape
      * the "new" option is using only filled polygons (no outline: give the faster redraw time
@@ -310,9 +316,7 @@ private:
     int        m_copperLayerCount; ///< Number of copper layers for this design
 
     LSET       m_enabledLayers;    ///< Bit-mask for layer enabling
-    LSET       m_visibleLayers;    ///< Bit-mask for layer visibility
 
-    int        m_visibleElements;  ///< Bit-mask for element category visibility
     int        m_boardThickness;   ///< Board thickness for 3D viewer
 
     /// Current net class name used to display netclass info.
@@ -325,8 +329,25 @@ private:
      */
     BOARD_STACKUP m_stackup;
 
+    /// Net classes that are loaded from the board file before these were stored in the project
+    NETCLASSES m_internalNetClasses;
+
+    /// This will point to m_internalNetClasses until it is repointed to the project after load
+    NETCLASSES* m_netClasses;
+
+    /// The defualt settings that will be used for new zones
+    ZONE_SETTINGS m_defaultZoneSettings;
+
+    SEVERITY severityFromString( const wxString& aSeverity );
+
+    wxString severityToString( const SEVERITY& aSeverity );
+
 public:
-    BOARD_DESIGN_SETTINGS();
+    BOARD_DESIGN_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath );
+
+    virtual ~BOARD_DESIGN_SETTINGS();
+
+    bool LoadFromFile( const std::string& aDirectory = "" ) override;
 
     BOARD_STACKUP& GetStackupDescriptor() { return m_stackup; }
 
@@ -337,13 +358,36 @@ public:
      */
     bool Ignore( int aDRCErrorCode );
 
+    NETCLASSES& GetNetClasses() const
+    {
+        return *m_netClasses;
+    }
+
+    void SetNetClasses( NETCLASSES* aNetClasses )
+    {
+        if( aNetClasses )
+            m_netClasses = aNetClasses;
+        else
+            m_netClasses = &m_internalNetClasses;
+    }
+
+    ZONE_SETTINGS& GetDefaultZoneSettings()
+    {
+        return m_defaultZoneSettings;
+    }
+
+    void SetDefaultZoneSettings( const ZONE_SETTINGS& aSettings )
+    {
+        m_defaultZoneSettings = aSettings;
+    }
+
     /**
      * Function GetDefault
      * @return the default netclass.
      */
     inline NETCLASS* GetDefault() const
     {
-        return m_NetClasses.GetDefaultPtr();
+        return GetNetClasses().GetDefaultPtr();
     }
 
     /**
@@ -712,95 +756,6 @@ public:
      */
     void SetCopperEdgeClearance( int aDistance );
 
-    /**
-     * Function GetVisibleLayers
-     * returns a bit-mask of all the layers that are visible
-     * @return int - the visible layers in bit-mapped form.
-     */
-    inline LSET GetVisibleLayers() const
-    {
-        return m_visibleLayers;
-    }
-
-    /**
-     * Function SetVisibleAlls
-     * Set the bit-mask of all visible elements categories,
-     * including enabled layers
-     */
-    void SetVisibleAlls();
-
-    /**
-     * Function SetVisibleLayers
-     * changes the bit-mask of visible layers
-     * @param aMask = The new bit-mask of visible layers
-     */
-    inline void SetVisibleLayers( LSET aMask )
-    {
-        m_visibleLayers = aMask & m_enabledLayers;
-    }
-
-    /**
-     * Function IsLayerVisible
-     * tests whether a given layer is visible
-     * @param aLayerId = The layer to be tested
-     * @return bool - true if the layer is visible.
-     */
-    inline bool IsLayerVisible( PCB_LAYER_ID aLayerId ) const
-    {
-        // If a layer is disabled, it is automatically invisible
-        return (m_visibleLayers & m_enabledLayers)[aLayerId];
-    }
-
-    /**
-     * Function SetLayerVisibility
-     * changes the visibility of a given layer
-     * @param aLayerId = The layer to be changed
-     * @param aNewState = The new visibility state of the layer
-     */
-    void SetLayerVisibility( PCB_LAYER_ID aLayerId, bool aNewState );
-
-    /**
-     * Function GetVisibleElements
-     * returns a bit-mask of all the element categories that are visible
-     * @return int - the visible element categories in bit-mapped form.
-     */
-    inline int GetVisibleElements() const
-    {
-        return m_visibleElements;
-    }
-
-    /**
-     * Function SetVisibleElements
-     * changes the bit-mask of visible element categories
-     * @param aMask = The new bit-mask of visible element categories
-     */
-    inline void SetVisibleElements( int aMask )
-    {
-        m_visibleElements = aMask;
-    }
-
-    /**
-     * Function IsElementVisible
-     * tests whether a given element category is visible. Keep this as an
-     * inline function.
-     * @param aElementCategory is from the enum by the same name
-     * @return bool - true if the element is visible.
-     * @see enum GAL_LAYER_ID
-     */
-    inline bool IsElementVisible( GAL_LAYER_ID aElementCategory ) const
-    {
-        return ( m_visibleElements & ( 1 << GAL_LAYER_INDEX( aElementCategory ) ) );
-    }
-
-    /**
-     * Function SetElementVisibility
-     * changes the visibility of an element category
-     * @param aElementCategory is from the enum by the same name
-     * @param aNewState = The new visibility state of the element category
-     * @see enum GAL_LAYER_ID
-     */
-    void SetElementVisibility( GAL_LAYER_ID aElementCategory, bool aNewState );
-
     /**
      * Function GetEnabledLayers
      * returns a bit-mask of all the layers that are enabled
@@ -845,14 +800,6 @@ public:
      */
     void SetCopperLayerCount( int aNewLayerCount );
 
-    /**
-     * Function AppendConfigs
-     * appends to @a aResult the configuration setting accessors which will later
-     * allow reading or writing of configuration file information directly into
-     * this object.
-     */
-    void AppendConfigs( BOARD* aBoard, std::vector<PARAM_CFG*>* aResult );
-
     inline int GetBoardThickness() const { return m_boardThickness; }
     inline void SetBoardThickness( int aThickness ) { m_boardThickness = aThickness; }
 
diff --git a/include/footprint_editor_settings.h b/include/footprint_editor_settings.h
index 57648bfb6d..740f6065a6 100644
--- a/include/footprint_editor_settings.h
+++ b/include/footprint_editor_settings.h
@@ -44,6 +44,8 @@ public:
 
     bool Migrate() override;
 
+    /// Only some of these settings are actually used for footprint editing
+    // TODO: factor out the relevant stuff so the whole BDS doesn't have to be here
     BOARD_DESIGN_SETTINGS m_DesignSettings;
 
     // Only the magneticPads element is used
diff --git a/include/layers_id_colors_and_visibility.h b/include/layers_id_colors_and_visibility.h
index 39847161bc..fbc7422064 100644
--- a/include/layers_id_colors_and_visibility.h
+++ b/include/layers_id_colors_and_visibility.h
@@ -218,6 +218,8 @@ enum GAL_LAYER_ID: int
 /// Use this macro to convert a GAL layer to a 0-indexed offset from LAYER_VIAS
 #define GAL_LAYER_INDEX( x ) ( x - GAL_LAYER_ID_START )
 
+constexpr int GAL_LAYER_ID_COUNT = GAL_LAYER_ID_END - GAL_LAYER_ID_START;
+
 inline GAL_LAYER_ID operator++( GAL_LAYER_ID& a )
 {
     a = GAL_LAYER_ID( int( a ) + 1 );
@@ -402,6 +404,7 @@ public:
     }
 };
 
+typedef std::bitset<GAL_LAYER_ID_COUNT>     GAL_SET;
 
 typedef std::bitset<PCB_LAYER_ID_COUNT>     BASE_SET;
 
diff --git a/pcbnew/netclass.h b/include/netclass.h
similarity index 95%
rename from pcbnew/netclass.h
rename to include/netclass.h
index 6c4885fccf..8874e34ddf 100644
--- a/pcbnew/netclass.h
+++ b/include/netclass.h
@@ -201,16 +201,6 @@ public:
      */
     void SetParams( const NETCLASS& aDefaults );
 
-    /**
-     * Function Format
-     * outputs the net class to \a aFormatter in s-expression form.
-     *
-     * @param aFormatter The #OUTPUTFORMATTER object to write to.
-     * @param aNestLevel The indentation next level.
-     * @param aControlBits The control bit definition for object specific formatting.
-     * @throw IO_ERROR on write error.
-     */
-    void Format( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aControlBits ) const;
 
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const;
diff --git a/include/pcb_base_frame.h b/include/pcb_base_frame.h
index 78f8b04285..63e9a72898 100644
--- a/include/pcb_base_frame.h
+++ b/include/pcb_base_frame.h
@@ -148,12 +148,10 @@ public:
     void SetTitleBlock( const TITLE_BLOCK& aTitleBlock ) override;
 
     /**
-     * Function GetDesignSettings
-     * returns the BOARD_DESIGN_SETTINGS for the BOARD owned by this frame.
+     * Returns the BOARD_DESIGN_SETTINGS for the open project
      * Overloaded in FOOTPRINT_EDIT_FRAME.
      */
     virtual BOARD_DESIGN_SETTINGS& GetDesignSettings() const;
-    virtual void SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings );
 
     /**
      * Helper to retrieve the current color settings
diff --git a/include/project.h b/include/project.h
index 96750d3171..ef2e69a901 100644
--- a/include/project.h
+++ b/include/project.h
@@ -48,6 +48,7 @@ class KIWAY;
 class SYMBOL_LIB_TABLE;
 class FILENAME_RESOLVER;
 class PROJECT_FILE;
+class PROJECT_LOCAL_SETTINGS;
 
 #define VTBL_ENTRY      virtual
 
@@ -132,6 +133,12 @@ public:
         return *m_projectFile;
     }
 
+    VTBL_ENTRY PROJECT_LOCAL_SETTINGS& GetLocalSettings() const
+    {
+        wxASSERT( m_localSettings );
+        return *m_localSettings;
+    }
+
     /**
      * Function ConfigSave
      * saves the current "project" parameters into the wxConfigBase* derivative.
@@ -340,6 +347,15 @@ private:
         m_projectFile = aFile;
     }
 
+    /**
+     * Sets the local settings backing store.  Should only be called by SETTINGS_MANAGER on load.
+     * @param aSettings is the local settings object (may or may not exist on disk at this point)
+     */
+    VTBL_ENTRY void setLocalSettings( PROJECT_LOCAL_SETTINGS* aSettings )
+    {
+        m_localSettings = aSettings;
+    }
+
     /**
      * Function configCreate
      * loads a *.pro file and returns a wxConfigBase.
@@ -362,6 +378,9 @@ private:
     /// Backing store for project data -- owned by SETTINGS_MANAGER
     PROJECT_FILE*   m_projectFile;
 
+    /// Backing store for project local settings -- owned by SETTINGS_MANAGER
+    PROJECT_LOCAL_SETTINGS* m_localSettings;
+
     std::map<KIID, wxString>     m_sheetNames;
     std::map<wxString, wxString> m_textVars;
 
diff --git a/include/project/net_settings.h b/include/project/net_settings.h
new file mode 100644
index 0000000000..b755ff726d
--- /dev/null
+++ b/include/project/net_settings.h
@@ -0,0 +1,46 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2020 CERN
+ * @author Jon Evans <jon@craftyjon.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KICAD_NET_SETTINGS_H
+#define KICAD_NET_SETTINGS_H
+
+#include <netclass.h>
+#include <settings/nested_settings.h>
+
+/**
+ * NET_SETTINGS stores various net-related settings in a project context.  These settings are
+ * accessible and editable from both the schematic and PCB editors.
+ */
+class NET_SETTINGS : public NESTED_SETTINGS
+{
+public:
+    NET_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath );
+
+    virtual ~NET_SETTINGS();
+
+    NETCLASSES m_NetClasses;
+
+private:
+    NETCLASSPTR m_defaultClass;
+
+    // TODO: Add diff pairs, bus information, etc here.
+};
+
+#endif // KICAD_NET_SETTINGS_H
diff --git a/include/settings/project_file.h b/include/project/project_file.h
similarity index 63%
rename from include/settings/project_file.h
rename to include/project/project_file.h
index 7587b18fa3..a4127c47c0 100644
--- a/include/settings/project_file.h
+++ b/include/project/project_file.h
@@ -23,7 +23,9 @@
 
 #include <common.h>
 #include <settings/json_settings.h>
+#include <settings/nested_settings.h>
 
+class NET_SETTINGS;
 
 /**
  * For files like sheets and boards, a pair of that object KIID and display name
@@ -31,6 +33,20 @@
  */
 typedef std::pair<KIID, wxString> FILE_INFO_PAIR;
 
+/**
+ * For storing PcbNew MRU paths of various types
+ */
+enum LAST_PATH_TYPE : unsigned int
+{
+    LAST_PATH_NETLIST = 0,
+    LAST_PATH_STEP,
+    LAST_PATH_IDF,
+    LAST_PATH_VRML,
+    LAST_PATH_SPECCTRADSN,
+    LAST_PATH_GENCAD,
+
+    LAST_PATH_SIZE
+};
 
 /**
  * PROJECT_FILE is the backing store for a PROJECT, in JSON format.
@@ -47,9 +63,16 @@ public:
      */
     PROJECT_FILE( const std::string& aFullPath );
 
-    virtual ~PROJECT_FILE() {}
+    virtual ~PROJECT_FILE() = default;
 
-    virtual bool MigrateFromLegacy( wxConfigBase* aLegacyFile ) override;
+    virtual bool MigrateFromLegacy( wxConfigBase* aCfg ) override;
+
+    bool SaveToFile( const std::string& aDirectory = "", bool aForce = false ) override;
+
+    void SetProject( PROJECT* aProject )
+    {
+        m_project = aProject;
+    }
 
     std::vector<FILE_INFO_PAIR>& GetSheets()
     {
@@ -61,6 +84,11 @@ public:
         return m_boards;
     }
 
+    NET_SETTINGS& NetSettings()
+    {
+        return *m_NetSettings;
+    }
+
 protected:
     wxString getFileExt() const override;
 
@@ -74,6 +102,9 @@ private:
     /// A list of board files in this project
     std::vector<FILE_INFO_PAIR> m_boards;
 
+    /// A link to the owning PROJECT
+    PROJECT* m_project;
+
     /**
      * Below are project-level settings that have not been moved to a dedicated file
      */
@@ -95,6 +126,31 @@ public:
 
     /// List of equivalence (equ) files used in the project
     std::vector<wxString> m_EquivalenceFiles;
+
+    /**
+     * PcbNew params
+     */
+
+    /// Page layout description file
+    wxString m_PageLayoutDescrFile;
+
+    /// MRU path storage
+    wxString m_PcbLastPath[LAST_PATH_SIZE];
+
+    /**
+     * Board design settings for this project's board.  This will be initialized by PcbNew after
+     * loading a board so that BOARD_DESIGN_SETTINGS doesn't need to live in common for now.
+     * Owned by the BOARD; may be null if a board isn't loaded: be careful
+     */
+    NESTED_SETTINGS* m_BoardSettings;
+
+    /**
+     * Net settings for this project (owned here)
+     * NOTE: If we go multi-board in the future, we have to decide whether to use a global
+     * NET_SETTINGS or have one per board.  Right now I think global makes more sense (one set of
+     * schematics, one netlist partitioned into multiple boards)
+     */
+     std::shared_ptr<NET_SETTINGS> m_NetSettings;
 };
 
 // Specializations to allow directly reading/writing FILE_INFO_PAIRs from JSON
diff --git a/include/project/project_local_settings.h b/include/project/project_local_settings.h
new file mode 100644
index 0000000000..a2ce498777
--- /dev/null
+++ b/include/project/project_local_settings.h
@@ -0,0 +1,83 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2020 CERN
+ * @author Jon Evans <jon@craftyjon.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KICAD_PROJECT_LOCAL_SETTINGS_H
+#define KICAD_PROJECT_LOCAL_SETTINGS_H
+
+#include <layers_id_colors_and_visibility.h>
+#include <settings/json_settings.h>
+#include <wildcards_and_files_ext.h>
+
+class PROJECT;
+
+/**
+ * The project local settings are things that are attached to a particular project, but also might
+ * be particular to a certain user editing that project, or change quickly, and therefore may not
+ * want to be checked in to version control or otherwise distributed with the main project.
+ *
+ * Examples include layer visibility, recently-used design entry settings, and so on.
+ *
+ * The backing store is a JSON file named <project>.kicad_prl
+ *
+ * This file doesn't need to exist for a project to be loaded.  It will be created on-demand if
+ * any of the things stored here are modified by the user.
+ */
+class PROJECT_LOCAL_SETTINGS : public JSON_SETTINGS
+{
+public:
+    PROJECT_LOCAL_SETTINGS( const std::string& aFilename );
+
+    virtual ~PROJECT_LOCAL_SETTINGS() {}
+
+    bool MigrateFromLegacy( wxConfigBase* aLegacyConfig ) override;
+
+    bool SaveToFile( const std::string& aDirectory = "", bool aForce = false ) override;
+
+    void SetProject( PROJECT* aProject )
+    {
+        m_project = aProject;
+    }
+
+protected:
+
+    wxString getFileExt() const override
+    {
+        return ProjectLocalSettingsFileExtension;
+    }
+
+private:
+
+    /// A link to the owning project
+    PROJECT* m_project;
+
+public:
+
+    /**
+     * Board settings
+     */
+
+    /// The board layers that are turned on for viewing (@see PCB_LAYER_ID)
+    LSET m_VisibleLayers;
+
+    /// The GAL layers (aka items) that are turned on for viewing (@see GAL_LAYER_ID)
+    GAL_SET m_VisibleItems;
+};
+
+#endif
diff --git a/include/settings/color_settings.h b/include/settings/color_settings.h
index 98afc21551..8f1fbac266 100644
--- a/include/settings/color_settings.h
+++ b/include/settings/color_settings.h
@@ -105,17 +105,15 @@ public:
             m_map( aMap )
     {}
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
 
-        COLOR4D val = m_default;
-
         if( OPT<COLOR4D> optval = aSettings->Get<COLOR4D>( m_path ) )
-            val = *optval;
-
-        ( *m_map )[ m_key ] = val;
+            ( *m_map )[ m_key ] = *optval;
+        else if( aResetIfMissing )
+            ( *m_map )[ m_key ] = m_default;
     }
 
     void Store( JSON_SETTINGS* aSettings) const override
diff --git a/include/settings/json_settings.h b/include/settings/json_settings.h
index b74dae95b9..5731e1c376 100644
--- a/include/settings/json_settings.h
+++ b/include/settings/json_settings.h
@@ -55,6 +55,8 @@ public:
 
     std::string GetFilename() const { return m_filename; }
 
+    wxString GetFullFilename() const;
+
     SETTINGS_LOC GetLocation() const { return m_location; }
 
     void SetLegacyFilename( const std::string& aFilename ) { m_legacy_filename = aFilename; }
@@ -81,8 +83,7 @@ public:
     /**
      * Calls Store() and then writes the contents of the JSON document to a file
      * @param aDirectory is the directory to save to, including trailing separator
-     * @param aForce if true will always save, even if contents are not modified
-     * @return true if the file was saved
+c     * @return true if the file was saved
      */
     virtual bool SaveToFile( const std::string& aDirectory = "", bool aForce = false );
 
@@ -97,7 +98,7 @@ public:
      * @param aPath is a string containing one or more keys separated by '.'
      * @return a JSON object from within this one
      */
-    OPT<nlohmann::json> GetJson( std::string aPath ) const;
+    OPT<nlohmann::json> GetJson( const std::string& aPath ) const;
 
     /**
      * Fetches a value from within the JSON document.
@@ -107,9 +108,9 @@ public:
      * @return a value from within this document
      */
     template<typename ValueType>
-    OPT<ValueType> Get( std::string aPath ) const
+    OPT<ValueType> Get( const std::string& aPath ) const
     {
-        if( OPT<nlohmann::json> ret = GetJson( std::move( aPath ) ) )
+        if( OPT<nlohmann::json> ret = GetJson( aPath ) )
         {
             try
             {
@@ -131,9 +132,9 @@ public:
      * @param aVal is the value to store
      */
     template<typename ValueType>
-    void Set( std::string aPath, ValueType aVal )
+    void Set( const std::string& aPath, ValueType aVal )
     {
-        ( *this )[PointerFromString( std::move( aPath ) ) ] = aVal;
+        ( *this )[PointerFromString( aPath ) ] = aVal;
     }
 
     /**
@@ -253,6 +254,9 @@ protected:
     /// Whether or not to delete legacy file after migration
     bool m_deleteLegacyAfterMigration;
 
+    /// Whether or not to set parameters to their default value if missing from JSON on Load()
+    bool m_resetParamsIfMissing;
+
     /// Version of this settings schema.
     int m_schemaVersion;
 
@@ -265,9 +269,9 @@ protected:
 
 // Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
 
-template<> OPT<wxString> JSON_SETTINGS::Get( std::string aPath ) const;
+template<> OPT<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const;
 
-template<> void JSON_SETTINGS::Set<wxString>( std::string aPath, wxString aVal );
+template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal );
 
 // Specializations to allow directly reading/writing wxStrings from JSON
 
diff --git a/include/settings/nested_settings.h b/include/settings/nested_settings.h
index 203099c57d..6fd4c5f181 100644
--- a/include/settings/nested_settings.h
+++ b/include/settings/nested_settings.h
@@ -49,7 +49,14 @@ public:
      */
     bool SaveToFile( const std::string& aDirectory = "", bool aForce = false ) override;
 
-private:
+    void SetParent( JSON_SETTINGS* aParent );
+
+    JSON_SETTINGS* GetParent()
+    {
+        return m_parent;
+    }
+
+protected:
 
     /// A pointer to the parent object to load and store from
     JSON_SETTINGS* m_parent;
diff --git a/include/settings/parameters.h b/include/settings/parameters.h
index 91090ca07a..424ca7e1ff 100644
--- a/include/settings/parameters.h
+++ b/include/settings/parameters.h
@@ -42,8 +42,9 @@ public:
     /**
      * Loads the value of this parameter from JSON to the underlying storage
      * @param aSettings is the JSON_SETTINGS object to load from.
+     * @param aResetIfMissing if true will set the parameter to its default value if load fails
      */
-    virtual void Load( JSON_SETTINGS* aSettings ) const = 0;
+    virtual void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const = 0;
 
     /**
      * Stores the value of this parameter to the given JSON_SETTINGS object
@@ -90,42 +91,42 @@ public:
     PARAM( const std::string& aJsonPath, ValueType* aPtr, ValueType aDefault,
            bool aReadOnly = false ) :
             PARAM_BASE( aJsonPath, aReadOnly ),
-            m_ptr( aPtr ),
-            m_default( aDefault ),
             m_min(),
             m_max(),
-            m_use_minmax( false )
+            m_use_minmax( false ),
+            m_ptr( aPtr ),
+            m_default( aDefault )
     { }
 
     PARAM( const std::string& aJsonPath, ValueType* aPtr, ValueType aDefault, ValueType aMin,
            ValueType aMax, bool aReadOnly = false ) :
             PARAM_BASE( aJsonPath, aReadOnly ),
-            m_ptr( aPtr ),
-            m_default( aDefault ),
             m_min( aMin ),
             m_max( aMax ),
-            m_use_minmax( true )
+            m_use_minmax( true ),
+            m_ptr( aPtr ),
+            m_default( aDefault )
     { }
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
 
-        ValueType val = m_default;
-
         if( OPT<ValueType> optval = aSettings->Get<ValueType>( m_path ) )
         {
-            val = *optval;
+            ValueType val = *optval;
 
             if( m_use_minmax )
             {
                 if( m_max < val || val < m_min )
                     val = m_default;
             }
-        }
 
-        *m_ptr = val;
+            *m_ptr = val;
+        }
+        else if( aResetIfMissing )
+            *m_ptr = m_default;
     }
 
     void Store( JSON_SETTINGS* aSettings ) const override
@@ -157,13 +158,66 @@ public:
     }
 
 private:
-    ValueType* m_ptr;
-    ValueType m_default;
     ValueType m_min;
     ValueType m_max;
     bool m_use_minmax;
+
+protected:
+    ValueType* m_ptr;
+    ValueType m_default;
 };
 
+/**
+ * Stores a path as a string with directory separators normalized to unix-style
+ */
+class PARAM_PATH : public PARAM<wxString>
+{
+public:
+    PARAM_PATH( const std::string& aJsonPath, wxString* aPtr, wxString aDefault,
+                bool aReadOnly = false ) :
+            PARAM( aJsonPath, aPtr, aDefault, aReadOnly )
+    { }
+
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
+    {
+        if( m_readOnly )
+            return;
+
+        PARAM::Load( aSettings, aResetIfMissing );
+
+        *m_ptr = fromFileFormat( *m_ptr );
+    }
+
+    void Store( JSON_SETTINGS* aSettings ) const override
+    {
+        aSettings->Set<wxString>( m_path, toFileFormat( *m_ptr ) );
+    }
+
+    bool MatchesFile( JSON_SETTINGS* aSettings ) const override
+    {
+        if( OPT<wxString> optval = aSettings->Get<wxString>( m_path ) )
+            return fromFileFormat( *optval ) == *m_ptr;
+
+        return false;
+    }
+
+private:
+    wxString toFileFormat( const wxString& aString ) const
+    {
+        wxString ret = aString;
+        ret.Replace( wxT( "\\" ), wxT( "/" ) );
+        return ret;
+    }
+
+    wxString fromFileFormat( const wxString& aString ) const
+    {
+        wxString ret = aString;
+#ifdef __WINDOWS__
+        ret.Replace( wxT( "/" ), wxT( "\\" ) );
+#endif
+        return ret;
+    }
+};
 
 /**
  * Like a normal param, but with custom getter and setter functions
@@ -182,28 +236,28 @@ public:
             m_setter( aSetter )
     { }
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
 
-        ValueType val = m_default;
-
         if( std::is_same<ValueType, nlohmann::json>::value )
         {
             if( OPT<nlohmann::json> optval = aSettings->GetJson( m_path ) )
-                val = *optval;
+                m_setter( *optval );
+            else
+                m_setter( m_default );
         }
         else
         {
             if( OPT<ValueType> optval = aSettings->Get<ValueType>( m_path ) )
-                val = *optval;
+                m_setter( *optval );
+            else
+                m_setter( m_default );
         }
-
-        m_setter( val );
     }
 
-    void Store( JSON_SETTINGS* aSettings) const override
+    void Store( JSON_SETTINGS* aSettings ) const override
     {
         try
         {
@@ -287,7 +341,7 @@ public:
             m_scale( aScale )
     { }
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
@@ -296,6 +350,8 @@ public:
 
         if( OPT<double> optval = aSettings->Get<double>( m_path ) )
             dval = *optval;
+        else if( !aResetIfMissing )
+            return;
 
         ValueType val = KiROUND<ValueType>( dval / m_scale );
 
@@ -363,25 +419,25 @@ public:
             m_default( aDefault )
     { }
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
 
-        std::vector<Type> val = m_default;
-
         if( OPT<nlohmann::json> js = aSettings->GetJson( m_path ) )
         {
+            std::vector<Type> val;
+
             if( js->is_array() )
             {
-                val.clear();
-
                 for( const auto& el : js->items() )
                     val.push_back( el.value().get<Type>() );
             }
-        }
 
-        *m_ptr = val;
+            *m_ptr = val;
+        }
+        else if( aResetIfMissing )
+            *m_ptr = m_default;
     }
 
     void Store( JSON_SETTINGS* aSettings) const override
@@ -445,12 +501,12 @@ public:
             PARAM_LIST( aJsonPath, aPtr, aDefault, aReadOnly )
     { }
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
 
-        PARAM_LIST::Load( aSettings );
+        PARAM_LIST::Load( aSettings, aResetIfMissing );
 
         for( size_t i = 0; i < m_ptr->size(); i++ )
             ( *m_ptr )[i] = fromFileFormat( ( *m_ptr )[i] );
@@ -526,7 +582,7 @@ public:
             m_default( aDefault )
     { }
 
-    void Load( JSON_SETTINGS* aSettings ) const override
+    void Load( JSON_SETTINGS* aSettings, bool aResetIfMissing = true ) const override
     {
         if( m_readOnly )
             return;
diff --git a/include/settings/settings_manager.h b/include/settings/settings_manager.h
index 73213fe85c..37f773ed1b 100644
--- a/include/settings/settings_manager.h
+++ b/include/settings/settings_manager.h
@@ -61,7 +61,7 @@ public:
      * If the given settings object is registered, save it to disk and unregister it
      * @param aSettings is the object to release
      */
-    void FlushAndRelease( JSON_SETTINGS* aSettings );
+    void FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave = true );
 
     /**
      * Returns a handle to the a given settings by type
@@ -184,16 +184,18 @@ public:
     /**
      * Loads a project or sets up a new project with a specified path
      * @param aFullPath is the full path to the project
+     * @param aSetActive if true will set the loaded project as the active project
      * @return true if the PROJECT_FILE was successfully loaded from disk
      */
-    bool LoadProject( const wxString& aFullPath );
+    bool LoadProject( const wxString& aFullPath, bool aSetActive = true );
 
     /**
      * Saves, unloads and unregisters the given PROJECT
      * @param aProject is the project object to unload
+     * @param aSave if true will save the project before unloading
      * @return true if the PROJECT file was successfully saved
      */
-    bool UnloadProject( PROJECT& aProject );
+    bool UnloadProject( PROJECT* aProject, bool aSave = true );
 
     /**
      * A helper while we are not MDI-capable -- return the one and only project
@@ -202,16 +204,23 @@ public:
     PROJECT& Prj() const;
 
     /**
-     * Saves the one and only project
-     * TODO: Update for MDI
-     * @return true if save was successful
+     * Retrieves a loaded project by name
+     * @param aFullPath is the full path including name and extension to the project file
+     * @return a pointer to the project if loaded, or nullptr
      */
-    bool SaveProject();
+    PROJECT* GetProject( const wxString& aFullPath ) const;
 
     /**
-     * @return the path to the settings folder for the loaded project
+     * Saves a loaded project.
+     * @param aFullPath is the project name to save.  If empty, will save the first loaded project.
+     * @return true if save was successful
      */
-    wxString GetProjectSettingsPath() const;
+    bool SaveProject( const wxString& aFullPath = wxEmptyString );
+
+    /**
+     * @return the path to the backups folder for the loaded project
+     */
+    wxString GetProjectBackupsPath() const;
 
     /**
      * Checks if a given path is probably a valid KiCad configuration directory.
@@ -300,11 +309,12 @@ private:
     bool loadProjectFile( PROJECT& aProject );
 
     /**
-     * Saves, unloads and unregisters the given PROJECT_FILE
+     * Optionally saves, and then unloads and unregisters the given PROJECT_FILE
      * @param aProject is the project object to unload the file for
+     * @param aSave if true will save the project file before unloading
      * @return true if the PROJECT file was successfully saved
      */
-    bool unloadProjectFile( PROJECT& aProject );
+    bool unloadProjectFile( PROJECT* aProject, bool aSave );
 
 private:
 
diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h
index e7cb974511..541ff77ebf 100644
--- a/include/wildcards_and_files_ext.h
+++ b/include/wildcards_and_files_ext.h
@@ -116,6 +116,7 @@ extern const std::string SchematicBackupFileExtension;
 extern const std::string VrmlFileExtension;
 extern const std::string ProjectFileExtension;
 extern const std::string LegacyProjectFileExtension;
+extern const std::string ProjectLocalSettingsFileExtension;
 extern const std::string LegacySchematicFileExtension;
 extern const std::string KiCadSchematicFileExtension;
 extern const std::string NetlistFileExtension;
diff --git a/kicad/kicad_manager_frame.cpp b/kicad/kicad_manager_frame.cpp
index 2b433f430e..6451f5983f 100644
--- a/kicad/kicad_manager_frame.cpp
+++ b/kicad/kicad_manager_frame.cpp
@@ -56,14 +56,6 @@
 
 #define SEP()   wxFileName::GetPathSeparator()
 
-// Not really useful, provided to save/restore params in project config file,
-// (Add them in s_KicadManagerParams if any)
-// Used also to create new .pro files from the kicad.pro template file
-// for new projects
-#define     GeneralGroupName            wxT( "/general" )
-
-std::vector<PARAM_CFG*>     s_KicadManagerParams;
-
 
 // Menubar and toolbar event table
 BEGIN_EVENT_TABLE( KICAD_MANAGER_FRAME, EDA_BASE_FRAME )
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 052431f496..1e14156409 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -448,7 +448,6 @@ if( KICAD_SCRIPTING )   # Generate pcbnew.py and pcbnew_wrap.cxx using swig
         DEPENDS swig/pcb_target.i
         DEPENDS swig/pcb_plot_params.i
         DEPENDS swig/footprint.i
-        DEPENDS swig/netclass.i
         DEPENDS swig/netinfo.i
         DEPENDS swig/pad.i
         DEPENDS swig/pcb_text.i
@@ -463,6 +462,7 @@ if( KICAD_SCRIPTING )   # Generate pcbnew.py and pcbnew_wrap.cxx using swig
         DEPENDS ${CMAKE_SOURCE_DIR}/common/swig/kicad.i
         DEPENDS ${CMAKE_SOURCE_DIR}/common/swig/wx.i
         DEPENDS ${CMAKE_SOURCE_DIR}/common/swig/ki_exception.i
+        DEPENDS ${CMAKE_SOURCE_DIR}/common/swig/netclass.i
         DEPENDS ${CMAKE_SOURCE_DIR}/scripting/kicadplugins.i
 
         COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/docstrings
diff --git a/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp b/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp
index 72cde5b289..3769360256 100644
--- a/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp
+++ b/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp
@@ -757,7 +757,7 @@ void ALTIUM_PCB::ParseClasses6Data(
         if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
         {
             const NETCLASSPTR& netclass = std::make_shared<NETCLASS>( elem.name );
-            designSettings.m_NetClasses.Add( netclass );
+            designSettings.GetNetClasses().Add( netclass );
 
             for( const auto& name : elem.names )
             {
diff --git a/pcbnew/board_design_settings.cpp b/pcbnew/board_design_settings.cpp
index 40382e30bb..c0740335ab 100644
--- a/pcbnew/board_design_settings.cpp
+++ b/pcbnew/board_design_settings.cpp
@@ -32,502 +32,33 @@
 #include <drc/drc.h>
 #include <widgets/ui_common.h>
 #include <drc/drc_rule.h>
-
-#define CopperLayerCountKey         wxT( "CopperLayerCount" )
-#define BoardThicknessKey           wxT( "BoardThickness" )
-
-#define LayerKeyPrefix              wxT( "Layer" )
-#define LayerNameKey                wxT( "Name" )
-#define LayerTypeKey                wxT( "Type" )
-#define LayerEnabledKey             wxT( "Enabled" )
-
-#define NetclassNameKey             wxT( "Name" )
-#define ClearanceKey                wxT( "Clearance" )
-#define TrackWidthKey               wxT( "TrackWidth" )
-#define ViaDiameterKey              wxT( "ViaDiameter" )
-#define ViaDrillKey                 wxT( "ViaDrill" )
-#define uViaDiameterKey             wxT( "uViaDiameter" )
-#define uViaDrillKey                wxT( "uViaDrill" )
-#define dPairWidthKey               wxT( "dPairWidth" )
-#define dPairGapKey                 wxT( "dPairGap" )
-#define dPairViaGapKey              wxT( "dPairViaGap" )
+#include <settings/parameters.h>
+#include <project/project_file.h>
 
 
-class PARAM_CFG_SEVERITIES : public PARAM_CFG
+const int bdsSchemaVersion = 0;
+
+
+BOARD_DESIGN_SETTINGS::BOARD_DESIGN_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath ) :
+        NESTED_SETTINGS( "board_design_settings", bdsSchemaVersion, aParent, aPath ),
+        m_Pad_Master( NULL )
+
 {
-protected:
-    BOARD* m_Pt_param;   ///< Pointer to the parameter value
+    // We want to leave alone parameters that aren't found in the project JSON as they may be
+    // initialized by the board file parser before NESTED_SETTINGS::LoadFromFile is called.
+    m_resetParamsIfMissing = false;
 
-public:
-    PARAM_CFG_SEVERITIES( BOARD* ptparam, const wxChar* group = nullptr ) :
-            PARAM_CFG( wxEmptyString, PARAM_SEVERITIES, group )
-    {
-        m_Pt_param = ptparam;
-    }
+    // Create a default NETCLASS list so that things don't break horribly if there's no project
+    // loaded.  This also is used during file load for legacy boards that have netclasses stored
+    // in the file.  After load, this information will be moved to the project and the pointer
+    // updated.
+    m_netClasses = &m_internalNetClasses;
 
-    void ReadParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        BOARD*                 board = m_Pt_param;
-        BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
-        wxString               oldPath = aConfig->GetPath();
-
-        // Read legacy settings first so that modern settings will overwrite them
-        bool flag;
-
-        if( aConfig->Read( wxT( "RequireCourtyardDefinitions" ), &flag, false ) )
-        {
-            if( flag )
-                bds.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_ERROR;
-            else
-                bds.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_IGNORE;
-        }
-
-        if( aConfig->Read( wxT( "ProhibitOverlappingCourtyards" ), &flag, false ) )
-        {
-            if( flag )
-                bds.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
-            else
-                bds.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_IGNORE;
-        }
-
-        DRC_ITEM drc( 0 );
-        wxString severity;
-
-        auto mapSeverity = []( const wxString& aSeverity )
-                           {
-                               if( aSeverity == wxT( "warning" ) )
-                                   return RPT_SEVERITY_WARNING;
-                               else if( aSeverity == wxT( "ignore" ) )
-                                   return RPT_SEVERITY_IGNORE;
-                               else
-                                   return RPT_SEVERITY_ERROR;
-                           };
-
-        for( int i = DRCE_FIRST; i <= DRCE_LAST; ++i )
-        {
-            wxString name = drc.GetErrorText( i, false );
-            name.Replace( wxT( " " ), wxT( "_" ) );
-
-            if( aConfig->Read( name, &severity, wxEmptyString ) )
-                bds.m_DRCSeverities[i] = mapSeverity( severity );
-        }
-
-        aConfig->SetPath( oldPath );
-    }
-
-    void SaveParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        BOARD*                 board = m_Pt_param;
-        BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
-        wxString               oldPath = aConfig->GetPath();
-        DRC_ITEM               drc( 0 );
-
-        auto mapSeverity = []( int aSeverity )
-                           {
-                               if( aSeverity == RPT_SEVERITY_IGNORE )
-                                   return wxT( "ignore" );
-                               else if( aSeverity == RPT_SEVERITY_WARNING )
-                                   return wxT( "warning" );
-                               else
-                                   return wxT( "error" );
-                           };
-
-        for( int i = DRCE_FIRST; i <= DRCE_LAST; ++i )
-        {
-            wxString name = drc.GetErrorText( i, false );
-            name.Replace( wxT( " " ), wxT( "_" ) );
-
-            aConfig->Write( name, mapSeverity( bds.m_DRCSeverities[i] ) );
-        }
-
-        aConfig->SetPath( oldPath );
-    }
-};
-
-
-//
-// NOTE: layer configuration info is stored in both the BOARD and BOARD_DESIGN_SETTINGS so one
-// of the two needs to read/write the config so we don't end up with order dependency issues.
-//
-class PARAM_CFG_LAYERS : public PARAM_CFG
-{
-protected:
-    BOARD* m_Pt_param;   ///< Pointer to the parameter value
-
-public:
-    PARAM_CFG_LAYERS( BOARD* ptparam, const wxChar* group = nullptr ) :
-            PARAM_CFG( wxEmptyString, PARAM_LAYERS, group )
-    {
-        m_Pt_param = ptparam;
-    }
-
-    void ReadParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        BOARD*                 board = m_Pt_param;
-        BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
-        LSET                   enabledLayers = bds.GetEnabledLayers();
-        wxString               oldPath = aConfig->GetPath();
-        wxString               layerKeyPrefix = LayerKeyPrefix;
-
-        bds.SetCopperLayerCount( aConfig->Read( CopperLayerCountKey, 2 ) );
-
-        double thickness = aConfig->ReadDouble( BoardThicknessKey, DEFAULT_BOARD_THICKNESS_MM );
-        bds.SetBoardThickness( Millimeter2iu( thickness ) );
-
-        for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq )
-        {
-            PCB_LAYER_ID layer = *seq;
-            wxString     path = layerKeyPrefix + wxT( "." ) + board->GetStandardLayerName( layer );
-            wxString     layerName;
-            int          layerType;
-            bool         layerEnabled;
-
-            aConfig->SetPath( oldPath );
-            aConfig->SetPath( path );
-
-            if( aConfig->Read( LayerNameKey, &layerName ) )
-                board->SetLayerName( layer, layerName );
-
-            if( aConfig->Read( LayerTypeKey, &layerType ) )
-                board->SetLayerType( layer, (LAYER_T) layerType );
-
-            if( aConfig->Read( LayerEnabledKey, &layerEnabled ) )
-                enabledLayers.set( layer, layerEnabled );
-        }
-
-        board->SetEnabledLayers( enabledLayers );
-
-        aConfig->SetPath( oldPath );
-    }
-
-    void SaveParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        BOARD*                 board = m_Pt_param;
-        BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
-        wxString               oldPath = aConfig->GetPath();
-        wxString               layerKeyPrefix = LayerKeyPrefix;
-
-        aConfig->Write( CopperLayerCountKey, board->GetCopperLayerCount() );
-        aConfig->Write( BoardThicknessKey, Iu2Millimeter( bds.GetBoardThickness() ) );
-
-        for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq )
-        {
-            PCB_LAYER_ID layer = *seq;
-            wxString     path = layerKeyPrefix + wxT( "." ) + board->GetStandardLayerName( layer );
-            wxString     layerName = board->GetLayerName( layer );
-            LAYER_T      layerType = board->GetLayerType( layer );
-
-            aConfig->SetPath( oldPath );
-            aConfig->SetPath( path );
-
-            if( IsCopperLayer( layer ) )
-            {
-                aConfig->Write( LayerNameKey, layerName );
-                aConfig->Write( LayerTypeKey, (int) layerType );
-            }
-
-            aConfig->Write( LayerEnabledKey, board->IsLayerEnabled( layer ) );
-        }
-
-        aConfig->SetPath( oldPath );
-    }
-};
-
-
-class PARAM_CFG_TRACKWIDTHS : public PARAM_CFG
-{
-protected:
-    std::vector<int>* m_Pt_param;   ///< Pointer to the parameter value
-
-public:
-    PARAM_CFG_TRACKWIDTHS( std::vector<int>* ptparam, const wxChar* group = nullptr ) :
-            PARAM_CFG( wxEmptyString, PARAM_TRACKWIDTHS, group )
-    {
-        m_Pt_param = ptparam;
-    }
-
-    void ReadParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        m_Pt_param->clear();
-
-        for( int index = 1; ; ++index )
-        {
-            wxString key = TrackWidthKey;
-            double width;
-
-            if( !aConfig->Read( key << index, &width ) )
-                break;
-
-            m_Pt_param->push_back( Millimeter2iu( width ) );
-        }
-    }
-
-    void SaveParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        for( size_t index = 1; index <= m_Pt_param->size(); ++index )
-        {
-            wxString key = TrackWidthKey;
-            aConfig->Write( key << index, Iu2Millimeter( m_Pt_param->at( index - 1 ) ) );
-        }
-    }
-};
-
-
-class PARAM_CFG_VIADIMENSIONS : public PARAM_CFG
-{
-protected:
-    std::vector<VIA_DIMENSION>* m_Pt_param;   ///< Pointer to the parameter value
-
-public:
-    PARAM_CFG_VIADIMENSIONS( std::vector<VIA_DIMENSION>* ptparam, const wxChar* group = nullptr ) :
-            PARAM_CFG( wxEmptyString, PARAM_VIADIMENSIONS, group )
-    {
-        m_Pt_param = ptparam;
-    }
-
-    void ReadParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        m_Pt_param->clear();
-
-        for( int index = 1; ; ++index )
-        {
-            double diameter = 0.0, drill = 0.0;
-
-            wxString key = ViaDiameterKey;
-
-            if( !aConfig->Read( key << index, &diameter ) )
-                break;
-
-            key = ViaDrillKey;
-            drill = aConfig->ReadDouble( key << index, 0.0 );
-
-            m_Pt_param->emplace_back( VIA_DIMENSION( Millimeter2iu( diameter ),
-                                                     Millimeter2iu( drill ) ) );
-        }
-    }
-
-    void SaveParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        for( size_t index = 1; index <= m_Pt_param->size(); ++index )
-        {
-            wxString key = ViaDiameterKey;
-            aConfig->Write( key << index, Iu2Millimeter( m_Pt_param->at( index - 1 ).m_Diameter ) );
-            key = ViaDrillKey;
-            aConfig->Write( key << index, Iu2Millimeter( m_Pt_param->at( index - 1 ).m_Drill ) );
-        }
-    }
-};
-
-
-class PARAM_CFG_DIFFPAIRDIMENSIONS : public PARAM_CFG
-{
-protected:
-    std::vector<DIFF_PAIR_DIMENSION>* m_Pt_param;   ///< Pointer to the parameter value
-
-public:
-    PARAM_CFG_DIFFPAIRDIMENSIONS( std::vector<DIFF_PAIR_DIMENSION>* ptparam,
-                                  const wxChar* group = nullptr ) :
-            PARAM_CFG( wxEmptyString, PARAM_DIFFPAIRDIMENSIONS, group )
-    {
-        m_Pt_param = ptparam;
-    }
-
-    void ReadParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        m_Pt_param->clear();
-
-        for( int index = 1; ; ++index )
-        {
-            double width, gap, viagap;
-
-            wxString key = dPairWidthKey;
-
-            if( !aConfig->Read( key << index, &width ) )
-                break;
-
-            key = dPairGapKey;
-            gap = aConfig->ReadDouble( key << index, 0.0 );
-
-            key = dPairViaGapKey;
-            viagap = aConfig->ReadDouble( key << index, 0.0 );
-
-            m_Pt_param->emplace_back( DIFF_PAIR_DIMENSION( Millimeter2iu( width ),
-                                                           Millimeter2iu( gap ),
-                                                           Millimeter2iu( viagap ) ) );
-        }
-    }
-
-    void SaveParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        for( size_t index = 1; index <= m_Pt_param->size(); ++index )
-        {
-            wxString key = dPairWidthKey;
-            aConfig->Write( key << index, Iu2Millimeter( m_Pt_param->at( index - 1 ).m_Width ) );
-            key = dPairGapKey;
-            aConfig->Write( key << index, Iu2Millimeter( m_Pt_param->at( index - 1 ).m_Gap ) );
-            key = dPairViaGapKey;
-            aConfig->Write( key << index, Iu2Millimeter( m_Pt_param->at( index - 1 ).m_ViaGap ) );
-        }
-    }
-};
-
-
-class PARAM_CFG_NETCLASSES : public PARAM_CFG
-{
-protected:
-    NETCLASSES* m_Pt_param;     ///<  Pointer to the parameter value
-
-public:
-    PARAM_CFG_NETCLASSES( const wxChar* ident, NETCLASSES* ptparam,
-                          const wxChar* group = nullptr ) :
-            PARAM_CFG( ident, PARAM_NETCLASSES, group )
-    {
-        m_Pt_param = ptparam;
-    }
-
-    void ReadParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        wxString oldPath = aConfig->GetPath();
-
-        m_Pt_param->Clear();
-
-        for( int index = 0; ; ++index )
-        {
-            wxString    path = "";
-            NETCLASSPTR netclass;
-            wxString    netclassName;
-
-            if( index == 0 )
-                path = "Default";
-            else
-                path << index;
-
-            aConfig->SetPath( oldPath );
-            aConfig->SetPath( m_Ident );
-            aConfig->SetPath( path );
-
-            if( !aConfig->Read( NetclassNameKey, &netclassName ) )
-                break;
-
-            if( index == 0 )
-                netclass = m_Pt_param->GetDefault();
-            else
-                netclass = std::make_shared<NETCLASS>( netclassName );
-
-#define READ_MM( aKey, aDefault ) Millimeter2iu( aConfig->ReadDouble( aKey, aDefault ) )
-            netclass->SetClearance( READ_MM( ClearanceKey, netclass->GetClearance() ) );
-            netclass->SetTrackWidth( READ_MM( TrackWidthKey, netclass->GetTrackWidth() ) );
-            netclass->SetViaDiameter( READ_MM( ViaDiameterKey, netclass->GetViaDiameter() ) );
-            netclass->SetViaDrill( READ_MM( ViaDrillKey, netclass->GetViaDrill() ) );
-            netclass->SetuViaDiameter( READ_MM( uViaDiameterKey, netclass->GetuViaDiameter() ) );
-            netclass->SetuViaDrill( READ_MM( uViaDrillKey, netclass->GetuViaDrill() ) );
-            netclass->SetDiffPairWidth( READ_MM( dPairWidthKey, netclass->GetDiffPairWidth() ) );
-            netclass->SetDiffPairGap( READ_MM( dPairGapKey, netclass->GetDiffPairGap() ) );
-            netclass->SetDiffPairViaGap( READ_MM( dPairViaGapKey, netclass->GetDiffPairViaGap() ) );
-
-            if( index > 0 )
-                m_Pt_param->Add( netclass );
-        }
-
-        aConfig->SetPath( oldPath );
-    }
-
-    void SaveParam( wxConfigBase* aConfig ) const override
-    {
-        if( !m_Pt_param || !aConfig )
-            return;
-
-        wxString                   oldPath = aConfig->GetPath();
-        NETCLASSES::const_iterator nc = m_Pt_param->begin();
-
-        for( unsigned index = 0; index <= m_Pt_param->GetCount(); ++index )
-        {
-            wxString    path = "";
-            NETCLASSPTR netclass;
-
-            if( index == 0 )
-                path = "Default";
-            else
-                path << index;
-
-            aConfig->SetPath( oldPath );
-            aConfig->SetPath( m_Ident );
-            aConfig->SetPath( path );
-
-            if( index == 0 )
-            {
-                netclass = m_Pt_param->GetDefault();
-            }
-            else
-            {
-                netclass = nc->second;
-                ++nc;
-            }
-
-            aConfig->Write( NetclassNameKey, netclass->GetName() );
-
-#define WRITE_MM( aKey, aValue ) aConfig->Write( aKey, Iu2Millimeter( aValue ) )
-            WRITE_MM( ClearanceKey,    netclass->GetClearance() );
-            WRITE_MM( TrackWidthKey,   netclass->GetTrackWidth() );
-            WRITE_MM( ViaDiameterKey,  netclass->GetViaDiameter() );
-            WRITE_MM( ViaDrillKey,     netclass->GetViaDrill() );
-            WRITE_MM( uViaDiameterKey, netclass->GetuViaDiameter() );
-            WRITE_MM( uViaDrillKey,    netclass->GetuViaDrill() );
-            WRITE_MM( dPairWidthKey,   netclass->GetDiffPairWidth() );
-            WRITE_MM( dPairGapKey,     netclass->GetDiffPairGap() );
-            WRITE_MM( dPairViaGapKey,  netclass->GetDiffPairViaGap() );
-        }
-
-        aConfig->SetPath( oldPath );
-    }
-};
-
-
-BOARD_DESIGN_SETTINGS::BOARD_DESIGN_SETTINGS() :
-    m_Pad_Master( NULL )
-{
     m_HasStackup = false;                   // no stackup defined by default
 
     LSET all_set = LSET().set();
     m_enabledLayers = all_set;              // All layers enabled at first.
                                             // SetCopperLayerCount() will adjust this.
-    SetVisibleLayers( all_set );
-
-    // set all but hidden text as visible.
-    m_visibleElements = ~( 1 << GAL_LAYER_INDEX( LAYER_MOD_TEXT_INVISIBLE ) );
 
     SetCopperLayerCount( 2 );               // Default design is a double sided board
     m_CurrentViaType = VIATYPE::THROUGH;
@@ -642,212 +173,499 @@ BOARD_DESIGN_SETTINGS::BOARD_DESIGN_SETTINGS() :
     m_viaSizeIndex = 0;
     m_trackWidthIndex = 0;
     m_diffPairIndex = 0;
-}
 
-// Add parameters to save in project config.
-// values are saved in mm
-void BOARD_DESIGN_SETTINGS::AppendConfigs( BOARD* aBoard, std::vector<PARAM_CFG*>* aResult )
-{
-    aResult->push_back( new PARAM_CFG_LAYERS( aBoard ) );
+    // Parameters stored in JSON in the project file
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "AllowMicroVias" ),
-          &m_MicroViasAllowed, false ) );
+    // NOTE: Previously, BOARD_DESIGN_SETTINGS stored the basic board layer information (layer
+    // names and enable/disable state) in the project file even though this information is also
+    // stored in the board file.  This was implemented for importing these settings from another
+    // project.  Going forward, the import feature will just import from other board files (since
+    // we could have multi-board projects in the future anyway) so this functionality is dropped.
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "AllowBlindVias" ),
-          &m_BlindBuriedViaAllowed, false ) );
+    m_params.emplace_back( new PARAM<bool>( "rules.allow_microvias", &m_MicroViasAllowed, false ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinClearance" ),
-          &m_MinClearance,
-          Millimeter2iu( DEFAULT_MINCLEARANCE ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back(
+            new PARAM<bool>( "rules.allow_blind_buried_vias", &m_BlindBuriedViaAllowed, false ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinTrackWidth" ),
-          &m_TrackMinWidth,
-          Millimeter2iu( DEFAULT_TRACKMINWIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_clearance", &m_MinClearance,
+            Millimeter2iu( DEFAULT_MINCLEARANCE ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
+            MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinViaAnnulus" ),
-          &m_ViasMinAnnulus,
-          Millimeter2iu( DEFAULT_VIASMINSIZE ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_track_width", &m_TrackMinWidth,
+            Millimeter2iu( DEFAULT_TRACKMINWIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
+            MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinViaDiameter" ),
-          &m_ViasMinSize,
-          Millimeter2iu( DEFAULT_VIASMINSIZE ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_via_annulus", &m_ViasMinAnnulus,
+            Millimeter2iu( DEFAULT_VIASMINSIZE ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
+            MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinThroughDrill" ),
-          &m_MinThroughDrill,
-          Millimeter2iu( DEFAULT_MINTHROUGHDRILL ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
-          nullptr, MM_PER_IU, wxT( "MinViaDrill" ) ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_via_diameter", &m_ViasMinSize,
+            Millimeter2iu( DEFAULT_VIASMINSIZE ), Millimeter2iu( 0.01 ), Millimeter2iu( 25.0 ),
+            MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinMicroViaDiameter" ),
-          &m_MicroViasMinSize,
-          Millimeter2iu( DEFAULT_MICROVIASMINSIZE ), Millimeter2iu( 0.01 ), Millimeter2iu( 10.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_through_hole_diameter",
+            &m_MinThroughDrill, Millimeter2iu( DEFAULT_MINTHROUGHDRILL ), Millimeter2iu( 0.01 ),
+            Millimeter2iu( 25.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinMicroViaDrill" ),
-          &m_MicroViasMinDrill,
-          Millimeter2iu( DEFAULT_MICROVIASMINDRILL ), Millimeter2iu( 0.01 ), Millimeter2iu( 10.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_microvia_diameter",
+            &m_MicroViasMinSize, Millimeter2iu( DEFAULT_MICROVIASMINSIZE ), Millimeter2iu( 0.01 ),
+            Millimeter2iu( 10.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "MinHoleToHole" ),
-          &m_HoleToHoleMin,
-          Millimeter2iu( DEFAULT_HOLETOHOLEMIN ), Millimeter2iu( 0.0 ), Millimeter2iu( 10.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_microvia_drill", &m_MicroViasMinDrill,
+            Millimeter2iu( DEFAULT_MICROVIASMINDRILL ), Millimeter2iu( 0.01 ),
+            Millimeter2iu( 10.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_SEVERITIES( aBoard ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_hole_to_hole", &m_HoleToHoleMin,
+            Millimeter2iu( DEFAULT_HOLETOHOLEMIN ), Millimeter2iu( 0.00 ), Millimeter2iu( 10.0 ),
+            MM_PER_IU ) );
 
     // Note: a clearance of -0.01 is a flag indicating we should use the legacy (pre-6.0) method
     // based on the edge cut thicknesses.
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "CopperEdgeClearance" ),
-          &m_CopperEdgeClearance,
-          Millimeter2iu( LEGACY_COPPEREDGECLEARANCE ), Millimeter2iu( -0.01 ), Millimeter2iu( 25.0 ),
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.min_copper_edge_clearance",
+            &m_CopperEdgeClearance, Millimeter2iu( LEGACY_COPPEREDGECLEARANCE ),
+            Millimeter2iu( -0.01 ), Millimeter2iu( 25.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_TRACKWIDTHS( &m_TrackWidthList ) );
-    aResult->push_back( new PARAM_CFG_VIADIMENSIONS( &m_ViasDimensionsList ) );
-    aResult->push_back( new PARAM_CFG_DIFFPAIRDIMENSIONS( &m_DiffPairDimensionsList ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.solder_mask_clearance",
+            &m_SolderMaskMargin, Millimeter2iu( DEFAULT_SOLDERMASK_CLEARANCE ),
+            Millimeter2iu( -1.0 ), Millimeter2iu( 1.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_NETCLASSES( wxT( "Netclasses" ), &m_NetClasses ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.solder_mask_min_width",
+            &m_SolderMaskMinWidth, Millimeter2iu( DEFAULT_SOLDERMASK_MIN_WIDTH ), 0,
+            Millimeter2iu( 1.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SilkLineWidth" ),
-          &m_LineThickness[ LAYER_CLASS_SILK ],
-          Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU, wxT( "ModuleOutlineThickness" ) ) );
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.solder_paste_clearance",
+            &m_SolderPasteMargin, Millimeter2iu( DEFAULT_SOLDERPASTE_CLEARANCE ),
+            Millimeter2iu( -1.0 ), Millimeter2iu( 1.0 ), MM_PER_IU ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SilkTextSizeV" ),
-          &m_TextSize[ LAYER_CLASS_SILK ].y,
-          Millimeter2iu( DEFAULT_SILK_TEXT_SIZE ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU, wxT( "ModuleTextSizeV" ) ) );
+    m_params.emplace_back( new PARAM<double>( "rules.solder_paste_margin_ratio",
+            &m_SolderPasteMarginRatio, DEFAULT_SOLDERPASTE_RATIO, -0.5, 1.0 ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SilkTextSizeH" ),
-          &m_TextSize[ LAYER_CLASS_SILK ].x,
-          Millimeter2iu( DEFAULT_SILK_TEXT_SIZE ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU, wxT( "ModuleTextSizeH" ) ) );
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "rule_severities",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json ret = {};
+                DRC_ITEM drc( 0 );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SilkTextSizeThickness" ),
-          &m_TextThickness[ LAYER_CLASS_SILK ],
-          Millimeter2iu( DEFAULT_SILK_TEXT_WIDTH ), 1, TEXTS_MAX_WIDTH,
-          nullptr, MM_PER_IU, wxT( "ModuleTextSizeThickness" ) ) );
+                for( int i = DRCE_FIRST; i <= DRCE_LAST; ++i )
+                {
+                    if( !m_DRCSeverities.count( i ) )
+                        continue;
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "SilkTextItalic" ),
-          &m_TextItalic[ LAYER_CLASS_SILK ], false ) );
+                    wxString name = drc.GetErrorText( i, false );
+                    name.Replace( wxT( " " ), wxT( "_" ) );
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "SilkTextUpright" ),
-          &m_TextUpright[ LAYER_CLASS_SILK ], true ) );
+                    ret[std::string( name.ToUTF8() )] =
+                            severityToString( static_cast<SEVERITY>( m_DRCSeverities[i] ) );
+                }
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "CopperLineWidth" ),
-          &m_LineThickness[ LAYER_CLASS_COPPER ],
-          Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU, wxT( "DrawSegmentWidth" ) ) );
+                return ret;
+            },
+            [&]( const nlohmann::json& aJson )
+            {
+                if( !aJson.is_object() )
+                    return;
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "CopperTextSizeV" ),
-          &m_TextSize[ LAYER_CLASS_COPPER ].y,
-          Millimeter2iu( DEFAULT_COPPER_TEXT_SIZE  ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU, wxT( "PcbTextSizeV" ) ) );
+                DRC_ITEM drc( 0 );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "CopperTextSizeH" ),
-          &m_TextSize[ LAYER_CLASS_COPPER ].x,
-          Millimeter2iu( DEFAULT_COPPER_TEXT_SIZE  ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU, wxT( "PcbTextSizeH" ) ) );
+                for( int i = DRCE_FIRST; i <= DRCE_LAST; ++i )
+                {
+                    wxString name = drc.GetErrorText( i, false );
+                    name.Replace( wxT( " " ), wxT( "_" ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "CopperTextThickness" ),
-          &m_TextThickness[ LAYER_CLASS_COPPER ],
-          Millimeter2iu( DEFAULT_COPPER_TEXT_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU, wxT( "PcbTextThickness" ) ) );
+                    std::string key( name.ToUTF8() );
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "CopperTextItalic" ),
-          &m_TextItalic[ LAYER_CLASS_COPPER ], false ) );
+                    if( aJson.contains( key ) )
+                        m_DRCSeverities[i] = severityFromString( aJson[key] );
+                }
+            }, {} ) );
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "CopperTextUpright" ),
-          &m_TextUpright[ LAYER_CLASS_COPPER ], true ) );
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "drc_exclusions",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json js = nlohmann::json::array();
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "EdgeCutLineWidth" ),
-          &m_LineThickness[ LAYER_CLASS_EDGES ],
-          Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU, wxT( "BoardOutlineThickness" ) ) );
+                for( const auto& entry : m_DrcExclusions )
+                    js.push_back( entry );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "CourtyardLineWidth" ),
-          &m_LineThickness[ LAYER_CLASS_COURTYARD ],
-          Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU ) );
+                return js;
+            },
+            [&]( const nlohmann::json& aObj )
+            {
+                m_DrcExclusions.clear();
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "FabLineWidth" ),
-          &m_LineThickness[ LAYER_CLASS_FAB ],
-          Millimeter2iu( DEFAULT_LINE_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU ) );
+                if( !aObj.is_array() )
+                    return;
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "FabTextSizeV" ),
-          &m_TextSize[ LAYER_CLASS_FAB ].x,
-          Millimeter2iu( DEFAULT_TEXT_SIZE ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU ) );
+                for( const nlohmann::json& entry : aObj )
+                {
+                    if( entry.empty() )
+                        continue;
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "FabTextSizeH" ),
-          &m_TextSize[ LAYER_CLASS_FAB ].y,
-          Millimeter2iu( DEFAULT_TEXT_SIZE ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU ) );
+                    m_DrcExclusions.insert( entry.get<wxString>() );
+                }
+            },
+            {} ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "FabTextSizeThickness" ),
-          &m_TextThickness[ LAYER_CLASS_FAB ],
-          Millimeter2iu( DEFAULT_TEXT_WIDTH ), 1, TEXTS_MAX_WIDTH,
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "track_widths",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json js = nlohmann::json::array();
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "FabTextItalic" ),
-          &m_TextItalic[ LAYER_CLASS_FAB ], false ) );
+                for( const int& width : m_TrackWidthList )
+                    js.push_back( Iu2Millimeter( width ) );
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "FabTextUpright" ),
-          &m_TextUpright[ LAYER_CLASS_FAB ], true ) );
+                return js;
+            },
+            [&]( const nlohmann::json& aJson )
+            {
+                if( !aJson.is_array() )
+                    return;
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "OthersLineWidth" ),
-          &m_LineThickness[ LAYER_CLASS_OTHERS ],
-          Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ), Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ),
-          nullptr, MM_PER_IU, wxT( "ModuleOutlineThickness" ) ) );
+                m_TrackWidthList.clear();
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "OthersTextSizeV" ),
-          &m_TextSize[ LAYER_CLASS_OTHERS ].x,
-          Millimeter2iu( DEFAULT_TEXT_SIZE ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU ) );
+                for( const nlohmann::json& entry : aJson )
+                {
+                    if( entry.empty() )
+                        continue;
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "OthersTextSizeH" ),
-          &m_TextSize[ LAYER_CLASS_OTHERS ].y,
-          Millimeter2iu( DEFAULT_TEXT_SIZE ), TEXTS_MIN_SIZE, TEXTS_MAX_SIZE,
-          nullptr, MM_PER_IU ) );
+                    m_TrackWidthList.emplace_back( Millimeter2iu( entry.get<double>() ) );
+                }
+            },
+            {} ) );
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "OthersTextSizeThickness" ),
-          &m_TextThickness[ LAYER_CLASS_OTHERS ],
-          Millimeter2iu( DEFAULT_TEXT_WIDTH ), 1, TEXTS_MAX_WIDTH,
-          nullptr, MM_PER_IU ) );
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "via_dimensions",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json js = nlohmann::json::array();
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "OthersTextItalic" ),
-          &m_TextItalic[ LAYER_CLASS_OTHERS ], false ) );
+                for( const auto& via : m_ViasDimensionsList )
+                {
+                    nlohmann::json entry = {};
 
-    aResult->push_back( new PARAM_CFG_BOOL( wxT( "OthersTextUpright" ),
-          &m_TextUpright[ LAYER_CLASS_OTHERS ], true ) );
+                    entry["diameter"] = Iu2Millimeter( via.m_Diameter );
+                    entry["drill"]    = Iu2Millimeter( via.m_Drill );
 
-    aResult->push_back( new PARAM_CFG_INT( wxT( "DimensionUnits" ),
-          &m_DimensionUnits, 0, 0, 2 ) );
-    aResult->push_back( new PARAM_CFG_INT( wxT( "DimensionPrecision" ),
-          &m_DimensionPrecision, 1, 0, 2 ) );
+                    js.push_back( entry );
+                }
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SolderMaskClearance" ),
-          &m_SolderMaskMargin,
-          Millimeter2iu( DEFAULT_SOLDERMASK_CLEARANCE ), Millimeter2iu( -1.0 ), Millimeter2iu( 1.0 ),
-          nullptr, MM_PER_IU ) );
+                return js;
+            },
+            [&]( const nlohmann::json& aObj )
+            {
+                if( !aObj.is_array() )
+                    return;
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SolderMaskMinWidth" ),
-          &m_SolderMaskMinWidth,
-          Millimeter2iu( DEFAULT_SOLDERMASK_MIN_WIDTH ), 0, Millimeter2iu( 1.0 ),
-          nullptr, MM_PER_IU ) );
+                m_ViasDimensionsList.clear();
 
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "SolderPasteClearance" ),
-          &m_SolderPasteMargin,
-          Millimeter2iu( DEFAULT_SOLDERPASTE_CLEARANCE ), Millimeter2iu( -1.0 ), Millimeter2iu( 1.0 ),
-          nullptr, MM_PER_IU ) );
+                for( const nlohmann::json& entry : aObj )
+                {
+                    if( entry.empty() || !entry.is_object() )
+                        continue;
 
-    aResult->push_back( new PARAM_CFG_DOUBLE( wxT( "SolderPasteRatio" ),
-          &m_SolderPasteMarginRatio,
-          DEFAULT_SOLDERPASTE_RATIO, -0.5, 1.0 ) );
+                    if( !entry.contains( "diameter" ) || !entry.contains( "drill" ) )
+                        continue;
+
+                    int diameter = Millimeter2iu( entry["diameter"].get<double>() );
+                    int drill    = Millimeter2iu( entry["drill"].get<double>() );
+
+                    m_ViasDimensionsList.emplace_back( VIA_DIMENSION( diameter, drill ) );
+                }
+            },
+            {} ) );
+
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "diff_pair_dimensions",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json js = nlohmann::json::array();
+
+                for( const auto& pair : m_DiffPairDimensionsList )
+                {
+                    nlohmann::json entry = {};
+
+                    entry["width"]   = Iu2Millimeter( pair.m_Width );
+                    entry["gap"]     = Iu2Millimeter( pair.m_Gap );
+                    entry["via_gap"] = Iu2Millimeter( pair.m_ViaGap );
+
+                    js.push_back( entry );
+                }
+
+                return js;
+            },
+            [&]( const nlohmann::json& aObj )
+            {
+                if( !aObj.is_array() )
+                    return;
+
+              m_DiffPairDimensionsList.clear();
+
+                for( const nlohmann::json& entry : aObj )
+                {
+                    if( entry.empty() || !entry.is_object() )
+                        continue;
+
+                    if( !entry.contains( "width" ) || !entry.contains( "gap" )
+                            || !entry.contains( "via_gap" ) )
+                        continue;
+
+                    int width   = Millimeter2iu( entry["width"].get<int>() );
+                    int gap     = Millimeter2iu( entry["gap"].get<int>() );
+                    int via_gap = Millimeter2iu( entry["via_gap"].get<int>() );
+
+                    m_DiffPairDimensionsList.emplace_back(
+                            DIFF_PAIR_DIMENSION( width, gap, via_gap ) );
+                }
+            },
+            {} ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.silk_line_width",
+            &m_LineThickness[LAYER_CLASS_SILK], Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.silk_text_size_v",
+            &m_TextSize[LAYER_CLASS_SILK].y, Millimeter2iu( DEFAULT_SILK_TEXT_SIZE ),
+            TEXTS_MIN_SIZE, TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.silk_text_size_h",
+            &m_TextSize[LAYER_CLASS_SILK].x, Millimeter2iu( DEFAULT_SILK_TEXT_SIZE ),
+            TEXTS_MIN_SIZE, TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.silk_text_thickness",
+            &m_TextThickness[LAYER_CLASS_SILK], Millimeter2iu( DEFAULT_SILK_TEXT_WIDTH ), 1,
+            TEXTS_MAX_WIDTH, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.silk_text_italic", &m_TextItalic[LAYER_CLASS_SILK], false ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.silk_text_upright", &m_TextUpright[ LAYER_CLASS_SILK ], true ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.copper_line_width",
+            &m_LineThickness[LAYER_CLASS_COPPER], Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.copper_text_size_v",
+            &m_TextSize[LAYER_CLASS_COPPER].y, Millimeter2iu( DEFAULT_COPPER_TEXT_SIZE ),
+            TEXTS_MIN_SIZE, TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.copper_text_size_h",
+            &m_TextSize[LAYER_CLASS_COPPER].x, Millimeter2iu( DEFAULT_COPPER_TEXT_SIZE ),
+            TEXTS_MIN_SIZE, TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.copper_text_thickness",
+            &m_TextThickness[LAYER_CLASS_COPPER], Millimeter2iu( DEFAULT_COPPER_TEXT_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.copper_text_italic", &m_TextItalic[LAYER_CLASS_COPPER], false ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.copper_text_upright", &m_TextUpright[LAYER_CLASS_COPPER], true ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.board_outline_line_width",
+            &m_LineThickness[LAYER_CLASS_EDGES], Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.courtyard_line_width",
+            &m_LineThickness[LAYER_CLASS_COURTYARD], Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.fab_line_width",
+            &m_LineThickness[LAYER_CLASS_FAB], Millimeter2iu( DEFAULT_LINE_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.fab_text_size_v",
+            &m_TextSize[LAYER_CLASS_FAB].y, Millimeter2iu( DEFAULT_TEXT_SIZE ),
+            TEXTS_MIN_SIZE, TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.fab_text_size_h",
+            &m_TextSize[LAYER_CLASS_FAB].x, Millimeter2iu( DEFAULT_TEXT_SIZE ),
+            TEXTS_MIN_SIZE, TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.fab_text_thickness",
+            &m_TextThickness[LAYER_CLASS_FAB], Millimeter2iu( DEFAULT_TEXT_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back(
+            new PARAM<bool>( "defaults.fab_text_italic", &m_TextItalic[LAYER_CLASS_FAB], false ) );
+
+    m_params.emplace_back(
+            new PARAM<bool>( "defaults.fab_text_upright", &m_TextUpright[LAYER_CLASS_FAB], true ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.other_line_width",
+            &m_LineThickness[LAYER_CLASS_OTHERS], Millimeter2iu( DEFAULT_LINE_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.other_text_size_v",
+            &m_TextSize[LAYER_CLASS_OTHERS].y, Millimeter2iu( DEFAULT_TEXT_SIZE ), TEXTS_MIN_SIZE,
+            TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.other_text_size_h",
+            &m_TextSize[LAYER_CLASS_OTHERS].x, Millimeter2iu( DEFAULT_TEXT_SIZE ), TEXTS_MIN_SIZE,
+            TEXTS_MAX_SIZE, MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.other_text_thickness",
+            &m_TextThickness[LAYER_CLASS_OTHERS], Millimeter2iu( DEFAULT_TEXT_WIDTH ),
+            Millimeter2iu( 0.01 ), Millimeter2iu( 5.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.other_text_italic", &m_TextItalic[LAYER_CLASS_OTHERS], false ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.other_text_upright", &m_TextUpright[LAYER_CLASS_OTHERS], true ) );
+
+    m_params.emplace_back(
+            new PARAM<int>( "defaults.dimension_units", &m_DimensionUnits, 0, 0, 2 ) );
+
+    m_params.emplace_back(
+            new PARAM<int>( "defaults.dimension_precision", &m_DimensionPrecision, 1, 0, 2 ) );
+
+    m_params.emplace_back( new PARAM<bool>(
+            "defaults.zones.45_degree_only", &m_defaultZoneSettings.m_Zone_45_Only, false ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "defaults.zones.min_clearance",
+            &m_defaultZoneSettings.m_ZoneClearance, Mils2iu( ZONE_CLEARANCE_MIL ),
+            Millimeter2iu( 0.0 ), Millimeter2iu( 25.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "defaults.pads",
+            [&]() -> nlohmann::json
+            {
+                nlohmann::json ret =
+                        {
+                            { "width",  Iu2Millimeter( m_Pad_Master.GetSize().x ) },
+                            { "height", Iu2Millimeter( m_Pad_Master.GetSize().y ) },
+                            { "drill",  Iu2Millimeter( m_Pad_Master.GetDrillSize().x ) }
+                        };
+
+                return ret;
+            },
+            [&]( const nlohmann::json& aJson )
+            {
+                if( aJson.contains( "width" ) && aJson.contains( "height" )
+                        && aJson.contains( "drill" ) )
+                {
+                    wxSize sz;
+                    sz.SetWidth( Millimeter2iu( aJson["width"].get<double>() ) );
+                    sz.SetHeight( Millimeter2iu( aJson["height"].get<double>() ) );
+
+                    m_Pad_Master.SetSize( sz );
+
+                    int drill = Millimeter2iu( aJson["drill"].get<double>() );
+
+                    m_Pad_Master.SetDrillSize( wxSize( drill, drill ) );
+                }
+            }, {} ) );
+
+    m_params.emplace_back( new PARAM_SCALED<int>( "rules.max_error", &m_MaxError, ARC_HIGH_DEF,
+            Millimeter2iu( 0.0001 ), Millimeter2iu( 1.0 ), MM_PER_IU ) );
+
+    m_params.emplace_back(
+            new PARAM<bool>( "zones_use_no_outline", &m_ZoneUseNoOutlineInFill, false ) );
+}
+
+
+BOARD_DESIGN_SETTINGS::~BOARD_DESIGN_SETTINGS()
+{
+    if( m_parent )
+    {
+        m_parent->ReleaseNestedSettings( this );
+        m_parent = nullptr;
+    }
+}
+
+
+SEVERITY BOARD_DESIGN_SETTINGS::severityFromString( const wxString& aSeverity )
+{
+    if( aSeverity == wxT( "warning" ) )
+        return RPT_SEVERITY_WARNING;
+    else if( aSeverity == wxT( "ignore" ) )
+        return RPT_SEVERITY_IGNORE;
+    else
+        return RPT_SEVERITY_ERROR;
+}
+
+wxString BOARD_DESIGN_SETTINGS::severityToString( const SEVERITY& aSeverity )
+{
+    if( aSeverity == RPT_SEVERITY_IGNORE )
+        return wxT( "ignore" );
+    else if( aSeverity == RPT_SEVERITY_WARNING )
+        return wxT( "warning" );
+    else
+        return wxT( "error" );
+}
+
+
+bool BOARD_DESIGN_SETTINGS::LoadFromFile( const std::string& aDirectory )
+{
+    bool ret = NESTED_SETTINGS::LoadFromFile( aDirectory );
+
+    // A number of things won't have been translated by the PROJECT_FILE migration because of
+    // descoped objects required to decode this data.  So, it will be in the legacy.pcbnew
+    // section and needs to be pulled out here
+
+    PROJECT_FILE* project = dynamic_cast<PROJECT_FILE*>( GetParent() );
+
+    if( !project )
+        return ret;
+
+    bool migrated = false;
+
+    DRC_ITEM drc( 0 );
+
+    auto drcName =
+            [drc]( int aCode ) -> std::string
+            {
+                wxString name = drc.GetErrorText( aCode, false );
+                name.Replace( wxT( " " ), wxT( "_" ) );
+                return std::string( name.ToUTF8() );
+            };
+
+    std::string bp = "board.design_settings.rule_severities.";
+    std::string rs = "rule_severities.";
+
+    if( OPT<bool> v =
+                    project->Get<bool>( PointerFromString( bp + "legacy_no_courtyard_defined" ) ) )
+    {
+        if( *v )
+            ( *this )[PointerFromString( rs + drcName( DRCE_MISSING_COURTYARD ) )] = "error";
+        else
+            ( *this )[PointerFromString( rs + drcName( DRCE_MISSING_COURTYARD ) )] = "ignore";
+
+        project->erase( PointerFromString( bp + "legacy_no_courtyard_defined" ) );
+        migrated = true;
+    }
+
+    if( OPT<bool> v = project->Get<bool>( PointerFromString( bp + "legacy_ourtyards_overlap" ) ) )
+    {
+        if( *v )
+            ( *this )[PointerFromString( rs + drcName( DRCE_OVERLAPPING_FOOTPRINTS ) )] = "error";
+        else
+            ( *this )[PointerFromString( rs + drcName( DRCE_OVERLAPPING_FOOTPRINTS ) )] = "ignore";
+
+        project->erase( PointerFromString( bp + "legacy_ourtyards_overlap" ) );
+        migrated = true;
+    }
+
+    if( project->contains( PointerFromString( "legacy.pcbnew" ) ) )
+    {
+        // DRC Severities
+        for( int i = DRCE_FIRST; i <= DRCE_LAST; ++i )
+        {
+            std::string key( severityToString( static_cast<SEVERITY>( i ) ).ToUTF8() );
+
+            if( OPT<wxString> v = project->Get<wxString>( "legacy.pcbnew." + key ) )
+                ( *this )[PointerFromString( rs + key )] = *v;
+        }
+
+        // We are the only place that needs this info, so now it can be deleted
+        project->at( "legacy" ).erase( "pcbnew" );
+        migrated = true;
+    }
+
+    // Now that we have everything, we need to load again
+    if( migrated )
+        Load();
+
+    return ret;
 }
 
 
@@ -865,12 +683,12 @@ bool BOARD_DESIGN_SETTINGS::Ignore( int aDRCErrorCode )
 
 bool BOARD_DESIGN_SETTINGS::SetCurrentNetClass( const wxString& aNetClassName )
 {
-    NETCLASSPTR netClass = m_NetClasses.Find( aNetClassName );
+    NETCLASSPTR netClass = GetNetClasses().Find( aNetClassName );
     bool        lists_sizes_modified = false;
 
     // if not found (should not happen) use the default
     if( !netClass )
-        netClass = m_NetClasses.GetDefault();
+        netClass = GetNetClasses().GetDefault();
 
     m_currentNetClassName = netClass->GetName();
 
@@ -949,7 +767,7 @@ int BOARD_DESIGN_SETTINGS::GetBiggestClearanceValue()
 {
     int clearance = GetDefault()->GetClearance();
 
-    for( const std::pair<const wxString, NETCLASSPTR>& netclass : m_NetClasses.NetClasses() )
+    for( const std::pair<const wxString, NETCLASSPTR>& netclass : GetNetClasses().NetClasses() )
         clearance = std::max( clearance, netclass.second->GetClearance() );
 
     for( const DRC_RULE* rule : m_DRCRules )
@@ -963,7 +781,7 @@ int BOARD_DESIGN_SETTINGS::GetSmallestClearanceValue()
 {
     int clearance = GetDefault()->GetClearance();
 
-    for( const std::pair<const wxString, NETCLASSPTR>& netclass : m_NetClasses.NetClasses() )
+    for( const std::pair<const wxString, NETCLASSPTR>& netclass : GetNetClasses().NetClasses() )
         clearance = std::min( clearance, netclass.second->GetClearance() );
 
     return clearance;
@@ -972,7 +790,7 @@ int BOARD_DESIGN_SETTINGS::GetSmallestClearanceValue()
 
 int BOARD_DESIGN_SETTINGS::GetCurrentMicroViaSize()
 {
-    NETCLASSPTR netclass = m_NetClasses.Find( m_currentNetClassName );
+    NETCLASSPTR netclass = GetNetClasses().Find( m_currentNetClassName );
 
     return netclass->GetuViaDiameter();
 }
@@ -980,7 +798,7 @@ int BOARD_DESIGN_SETTINGS::GetCurrentMicroViaSize()
 
 int BOARD_DESIGN_SETTINGS::GetCurrentMicroViaDrill()
 {
-    NETCLASSPTR netclass = m_NetClasses.Find( m_currentNetClassName );
+    NETCLASSPTR netclass = GetNetClasses().Find( m_currentNetClassName );
 
     return netclass->GetuViaDrill();
 }
@@ -1032,28 +850,6 @@ void BOARD_DESIGN_SETTINGS::SetCopperEdgeClearance( int aDistance )
 }
 
 
-void BOARD_DESIGN_SETTINGS::SetVisibleAlls()
-{
-    SetVisibleLayers( LSET().set() );
-    m_visibleElements = -1;
-}
-
-
-void BOARD_DESIGN_SETTINGS::SetLayerVisibility( PCB_LAYER_ID aLayer, bool aNewState )
-{
-    m_visibleLayers.set( aLayer, aNewState && IsLayerEnabled( aLayer ));
-}
-
-
-void BOARD_DESIGN_SETTINGS::SetElementVisibility( GAL_LAYER_ID aElementCategory, bool aNewState )
-{
-    if( aNewState )
-        m_visibleElements |= 1 << GAL_LAYER_INDEX( aElementCategory );
-    else
-        m_visibleElements &= ~( 1 << GAL_LAYER_INDEX( aElementCategory ) );
-}
-
-
 void BOARD_DESIGN_SETTINGS::SetCopperLayerCount( int aNewLayerCount )
 {
     m_copperLayerCount = aNewLayerCount;
@@ -1073,9 +869,6 @@ void BOARD_DESIGN_SETTINGS::SetEnabledLayers( LSET aMask )
 
     m_enabledLayers = aMask;
 
-    // A disabled layer cannot be visible
-    m_visibleLayers &= aMask;
-
     // update m_CopperLayerCount to ensure its consistency with m_EnabledLayers
     m_copperLayerCount = ( aMask & LSET::AllCuMask() ).count();
 }
diff --git a/pcbnew/board_item_container.h b/pcbnew/board_item_container.h
index 35ac895eca..9f4b7f4ea5 100644
--- a/pcbnew/board_item_container.h
+++ b/pcbnew/board_item_container.h
@@ -68,26 +68,26 @@ public:
         Remove( aItem );
         delete aItem;
     }
-
-    /**
-     * @brief Fetch the zone settings for this container
-     */
-    const ZONE_SETTINGS& GetZoneSettings() const
-    {
-        return m_zoneSettings;
-    }
-
-    /**
-     * @brief Set the zone settings for this container
-     * @param aSettings new Zone settings for this container
-     */
-    void SetZoneSettings( const ZONE_SETTINGS& aSettings )
-    {
-        m_zoneSettings = aSettings;
-    }
-
-private:
-    ZONE_SETTINGS m_zoneSettings;
+
+    /**
+     * @brief Fetch the zone settings for this container
+     */
+    virtual const ZONE_SETTINGS& GetZoneSettings() const
+    {
+        return m_zoneSettings;
+    }
+
+    /**
+     * @brief Set the zone settings for this container
+     * @param aSettings new Zone settings for this container
+     */
+    virtual void SetZoneSettings( const ZONE_SETTINGS& aSettings )
+    {
+        m_zoneSettings = aSettings;
+    }
+
+private:
+    ZONE_SETTINGS m_zoneSettings;
 };
 
 #endif /* BOARD_ITEM_CONTAINER_H */
diff --git a/pcbnew/class_board.cpp b/pcbnew/class_board.cpp
index 1e9022688b..001401a5a5 100644
--- a/pcbnew/class_board.cpp
+++ b/pcbnew/class_board.cpp
@@ -41,6 +41,10 @@
 #include <connectivity/connectivity_data.h>
 #include <pgm_base.h>
 #include <pcbnew_settings.h>
+#include <project.h>
+#include <project/net_settings.h>
+#include <project/project_file.h>
+#include <project/project_local_settings.h>
 #include <ratsnest/ratsnest_data.h>
 #include <ratsnest/ratsnest_viewitem.h>
 
@@ -81,23 +85,19 @@ DELETED_BOARD_ITEM* g_DeletedItem = nullptr;
  */
 wxPoint BOARD_ITEM::ZeroOffset( 0, 0 );
 
-// Dummy settings used to initialize the board.
-// This is needed because some APIs that make use of BOARD without the context of a frame or
-// application, and so the BOARD needs to store a valid pointer to a PCBNEW_SETTINGS even if
-// one hasn't been provided by the application.
-static PCBNEW_SETTINGS dummyGeneralSettings;
 
 BOARD::BOARD() :
         BOARD_ITEM_CONTAINER( (BOARD_ITEM*) NULL, PCB_T ),
         m_paper( PAGE_INFO::A4 ),
+        m_project( nullptr ),
+        m_designSettings( new BOARD_DESIGN_SETTINGS( nullptr, "board.design_settings" ) ),
         m_NetInfo( this ),
-        m_project( nullptr )
+        m_LegacyDesignSettingsLoaded( false ),
+        m_LegacyNetclassesLoaded( false )
 {
     // we have not loaded a board yet, assume latest until then.
     m_fileFormatVersionAtLoad = LEGACY_BOARD_FILE_VERSION;
 
-    m_generalSettings = &dummyGeneralSettings;
-
     m_CurrentZoneContour = NULL;            // This ZONE_CONTAINER handle the
                                             // zone contour currently in progress
 
@@ -113,19 +113,25 @@ BOARD::BOARD() :
             m_Layer[layer].m_type = LT_UNDEFINED;
     }
 
+    BOARD_DESIGN_SETTINGS& bds = GetDesignSettings();
+
     // Initialize default netclass.
-    NETCLASS* defaultClass = m_designSettings.GetDefault();
+    NETCLASS* defaultClass = bds.GetDefault();
     defaultClass->SetDescription( _( "This is the default net class." ) );
-    m_designSettings.SetCurrentNetClass( defaultClass->GetName() );
+    bds.SetCurrentNetClass( defaultClass->GetName() );
 
     // Set sensible initial values for custom track width & via size
-    m_designSettings.UseCustomTrackViaSize( false );
-    m_designSettings.SetCustomTrackWidth( m_designSettings.GetCurrentTrackWidth() );
-    m_designSettings.SetCustomViaSize( m_designSettings.GetCurrentViaSize() );
-    m_designSettings.SetCustomViaDrill( m_designSettings.GetCurrentViaDrill() );
+    bds.UseCustomTrackViaSize( false );
+    bds.SetCustomTrackWidth( bds.GetCurrentTrackWidth() );
+    bds.SetCustomViaSize( bds.GetCurrentViaSize() );
+    bds.SetCustomViaDrill( bds.GetCurrentViaDrill() );
 
     // Initialize ratsnest
     m_connectivity.reset( new CONNECTIVITY_DATA() );
+
+    // Set flag bits on these that will only be cleared if these are loaded from a legacy file
+    m_LegacyVisibleLayers.reset().set( Rescue );
+    m_LegacyVisibleItems.reset().set( GAL_LAYER_INDEX( GAL_LAYER_ID_BITMASK_END ) );
 }
 
 
@@ -170,6 +176,54 @@ void BOARD::BuildConnectivity()
 }
 
 
+void BOARD::SetProject( PROJECT* aProject )
+{
+    m_project = aProject;
+
+    if( aProject )
+    {
+        PROJECT_FILE& project = aProject->GetProjectFile();
+
+        // Link the design settings object to the project file
+        project.m_BoardSettings  = &GetDesignSettings();
+
+        // Set parent, which also will load the values from JSON stored in the project
+        project.m_BoardSettings->SetParent( &project );
+
+        // The netclasses pointer will be pointing to the internal netclasses list at this point. If
+        // it has anything other than the default net, this means we loaded some netclasses from a
+        // board saved in legacy format where the netclass info is included.  Move this info to the
+        // netclasses stored in the project.
+
+        NETCLASSES& local = GetDesignSettings().GetNetClasses();
+
+        if( m_LegacyNetclassesLoaded )
+            project.NetSettings().m_NetClasses = local;
+
+        GetDesignSettings().SetNetClasses( &project.NetSettings().m_NetClasses );
+    }
+}
+
+
+void BOARD::ClearProject()
+{
+    if( !m_project )
+        return;
+
+    PROJECT_FILE& project = m_project->GetProjectFile();
+
+    // Owned by the BOARD
+    if( project.m_BoardSettings )
+    {
+        project.ReleaseNestedSettings( project.m_BoardSettings );
+        project.m_BoardSettings = nullptr;
+    }
+
+    GetDesignSettings().SetParent( nullptr );
+    m_project = nullptr;
+}
+
+
 const wxPoint BOARD::GetPosition() const
 {
     return ZeroOffset;
@@ -392,50 +446,56 @@ LAYER_T LAYER::ParseType( const char* aType )
 
 int BOARD::GetCopperLayerCount() const
 {
-    return m_designSettings.GetCopperLayerCount();
+    return GetDesignSettings().GetCopperLayerCount();
 }
 
 
 void BOARD::SetCopperLayerCount( int aCount )
 {
-    m_designSettings.SetCopperLayerCount( aCount );
+    GetDesignSettings().SetCopperLayerCount( aCount );
 }
 
 
 LSET BOARD::GetEnabledLayers() const
 {
-    return m_designSettings.GetEnabledLayers();
+    return GetDesignSettings().GetEnabledLayers();
+}
+
+
+bool BOARD::IsLayerVisible( PCB_LAYER_ID aLayer ) const
+{
+    // If there is no project, assume layer is visible always
+    return GetDesignSettings().IsLayerEnabled( aLayer )
+           && ( !m_project || m_project->GetLocalSettings().m_VisibleLayers[aLayer] );
 }
 
 
 LSET BOARD::GetVisibleLayers() const
 {
-    return m_designSettings.GetVisibleLayers();
+    return m_project ? m_project->GetLocalSettings().m_VisibleLayers : LSET::AllLayersMask();
 }
 
 
 void BOARD::SetEnabledLayers( LSET aLayerSet )
 {
-    m_designSettings.SetEnabledLayers( aLayerSet );
+    GetDesignSettings().SetEnabledLayers( aLayerSet );
 }
 
 
 void BOARD::SetVisibleLayers( LSET aLayerSet )
 {
-    m_designSettings.SetVisibleLayers( aLayerSet );
+    if( m_project )
+        m_project->GetLocalSettings().m_VisibleLayers = aLayerSet;
 }
 
 
-void BOARD::SetVisibleElements( int aMask )
+void BOARD::SetVisibleElements( const GAL_SET& aSet )
 {
     // Call SetElementVisibility for each item
     // to ensure specific calculations that can be needed by some items,
     // just changing the visibility flags could be not sufficient.
-    for( GAL_LAYER_ID ii = GAL_LAYER_ID_START; ii < GAL_LAYER_ID_BITMASK_END; ++ii )
-    {
-        int item_mask = 1 << GAL_LAYER_INDEX( ii );
-        SetElementVisibility( ii, aMask & item_mask );
-    }
+    for( size_t i = 0; i < aSet.size(); i++ )
+        SetElementVisibility( GAL_LAYER_ID_START + static_cast<int>( i ), aSet[i] );
 }
 
 
@@ -450,21 +510,22 @@ void BOARD::SetVisibleAlls()
 }
 
 
-int BOARD::GetVisibleElements() const
+GAL_SET BOARD::GetVisibleElements() const
 {
-    return m_designSettings.GetVisibleElements();
+    return m_project ? m_project->GetLocalSettings().m_VisibleItems : GAL_SET().set();
 }
 
 
 bool BOARD::IsElementVisible( GAL_LAYER_ID aLayer ) const
 {
-    return m_designSettings.IsElementVisible( aLayer );
+    return !m_project || m_project->GetLocalSettings().m_VisibleItems[aLayer - GAL_LAYER_ID_START];
 }
 
 
 void BOARD::SetElementVisibility( GAL_LAYER_ID aLayer, bool isEnabled )
 {
-    m_designSettings.SetElementVisibility( aLayer, isEnabled );
+    if( m_project )
+        m_project->GetLocalSettings().m_VisibleItems.set( aLayer - GAL_LAYER_ID_START, isEnabled );
 
     switch( aLayer )
     {
@@ -1216,6 +1277,86 @@ int BOARD::SortedNetnamesList( wxArrayString& aNames, bool aSortbyPadsCount )
 }
 
 
+void BOARD::SynchronizeNetsAndNetClasses()
+{
+    NETCLASSES& netClasses      = GetDesignSettings().GetNetClasses();
+    NETCLASSPTR defaultNetClass = netClasses.GetDefault();
+
+    // set all NETs to the default NETCLASS, then later override some
+    // as we go through the NETCLASSes.
+
+    for( NETINFO_LIST::iterator net( m_NetInfo.begin() ), netEnd( m_NetInfo.end() );
+         net != netEnd; ++net )
+    {
+        net->SetClass( defaultNetClass );
+    }
+
+    // Add netclass name and pointer to nets.  If a net is in more than one netclass,
+    // set the net's name and pointer to only the first netclass.  Subsequent
+    // and therefore bogus netclass memberships will be deleted in logic below this loop.
+    for( NETCLASSES::iterator clazz = netClasses.begin(); clazz != netClasses.end(); ++clazz )
+    {
+        NETCLASSPTR netclass = clazz->second;
+
+        for( NETCLASS::const_iterator member = netclass->begin(); member != netclass->end(); ++member )
+        {
+            const wxString& netname = *member;
+
+            // although this overall function seems to be adequately fast,
+            // FindNet( wxString ) uses now a fast binary search and is fast
+            // event for large net lists
+            NETINFO_ITEM* net = FindNet( netname );
+
+            if( net && net->GetClassName() == NETCLASS::Default )
+            {
+                net->SetClass( netclass );
+            }
+        }
+    }
+
+    // Finally, make sure that every NET is in a NETCLASS, even if that
+    // means the Default NETCLASS.  And make sure that all NETCLASSes do not
+    // contain netnames that do not exist, by deleting all netnames from
+    // every netclass and re-adding them.
+
+    for( NETCLASSES::iterator clazz = netClasses.begin(); clazz != netClasses.end(); ++clazz )
+    {
+        NETCLASSPTR netclass = clazz->second;
+
+        netclass->Clear();
+    }
+
+    defaultNetClass->Clear();
+
+    for( NETINFO_LIST::iterator net( m_NetInfo.begin() ), netEnd( m_NetInfo.end() );
+         net != netEnd; ++net )
+    {
+        const wxString& classname = net->GetClassName();
+
+        // because of the std:map<> this should be fast, and because of
+        // prior logic, netclass should not be NULL.
+        NETCLASSPTR netclass = netClasses.Find( classname );
+
+        wxASSERT( netclass );
+
+        netclass->Add( net->GetNetname() );
+    }
+
+    BOARD_DESIGN_SETTINGS& bds = GetDesignSettings();
+
+    // Set initial values for custom track width & via size to match the default netclass settings
+    bds.UseCustomTrackViaSize( false );
+    bds.SetCustomTrackWidth( defaultNetClass->GetTrackWidth() );
+    bds.SetCustomViaSize( defaultNetClass->GetViaDiameter() );
+    bds.SetCustomViaDrill( defaultNetClass->GetViaDrill() );
+    bds.SetCustomDiffPairWidth( defaultNetClass->GetDiffPairWidth() );
+    bds.SetCustomDiffPairGap( defaultNetClass->GetDiffPairGap() );
+    bds.SetCustomDiffPairViaGap( defaultNetClass->GetDiffPairViaGap() );
+
+    InvokeListeners( &BOARD_LISTENER::OnBoardNetSettingsChanged, *this );
+}
+
+
 int BOARD::SetAreasNetCodesFromNetNames()
 {
     int error_count = 0;
diff --git a/pcbnew/class_board.h b/pcbnew/class_board.h
index 917f5f2996..9b07ef4138 100644
--- a/pcbnew/class_board.h
+++ b/pcbnew/class_board.h
@@ -42,7 +42,6 @@
 
 class PCB_BASE_FRAME;
 class PCB_EDIT_FRAME;
-class PCBNEW_SETTINGS;
 class PICKED_ITEMS_LIST;
 class BOARD;
 class ZONE_CONTAINER;
@@ -211,13 +210,24 @@ private:
 
     std::shared_ptr<CONNECTIVITY_DATA>      m_connectivity;
 
-    BOARD_DESIGN_SETTINGS   m_designSettings;
-    PCBNEW_SETTINGS*        m_generalSettings;      // reference only; I have no ownership
     PAGE_INFO               m_paper;
     TITLE_BLOCK             m_titles;               // text in lower right of screen and plots
     PCB_PLOT_PARAMS         m_plotOptions;
+    PROJECT*                m_project;              // project this board is a part of
+
+    /**
+     * All of the board design settings are stored as a JSON object inside the project file.  The
+     * object itself is located here because the alternative is to require a valid project be
+     * passed in when constructing a BOARD, since things in the BOARD constructor rely on access
+     * to the BOARD_DESIGN_SETTINGS object.
+     *
+     * A reference to this object is set up in the PROJECT_FILE for the PROJECT this board is
+     * part of, so that the JSON load/store operations work.  This link is established when
+     * boards are loaded from disk.
+     */
+    std::unique_ptr<BOARD_DESIGN_SETTINGS> m_designSettings;
+
     NETINFO_LIST            m_NetInfo;              // net info list (name, design constraints ..
-    PROJECT*                m_project;              // project this board is a part of (if any)
 
     std::vector<BOARD_LISTENER*> m_listeners;
 
@@ -282,6 +292,16 @@ public:
     /// zone contour currently in progress
     ZONE_CONTAINER*             m_CurrentZoneContour;
 
+    /// Visibility settings stored in board prior to 6.0, only used for loading legacy files
+    LSET    m_LegacyVisibleLayers;
+    GAL_SET m_LegacyVisibleItems;
+
+    /// True if the legacy board design settings were loaded from a file
+    bool m_LegacyDesignSettingsLoaded;
+
+    /// True if netclasses were loaded from the file
+    bool m_LegacyNetclassesLoaded;
+
     BOARD();
     ~BOARD();
 
@@ -354,7 +374,15 @@ public:
     void DeleteZONEOutlines();
 
     PROJECT* GetProject() const            { return m_project; }
-    void SetProject( PROJECT* aProject )   { m_project = aProject; }
+
+    /**
+     * Links a board to a given project.  Should be called immediately after loading board in
+     * order for everything to work
+     * @param aProject is a loaded project to link to
+     */
+    void SetProject( PROJECT* aProject );
+
+    void ClearProject();
 
     /**
      * Function ResetNetHighLight
@@ -434,7 +462,7 @@ public:
      */
     bool IsLayerEnabled( PCB_LAYER_ID aLayer ) const
     {
-        return m_designSettings.IsLayerEnabled( aLayer );
+        return GetDesignSettings().IsLayerEnabled( aLayer );
     }
 
     /**
@@ -444,10 +472,7 @@ public:
      * @param aLayer = The layer to be tested
      * @return bool - true if the layer is visible.
      */
-    bool IsLayerVisible( PCB_LAYER_ID aLayer ) const
-    {
-        return m_designSettings.IsLayerVisible( aLayer );
-    }
+    bool IsLayerVisible( PCB_LAYER_ID aLayer ) const;
 
     /**
      * Function GetVisibleLayers
@@ -469,13 +494,11 @@ public:
     // are not stored in the bitmap.
 
     /**
-     * Function GetVisibleElements
-     * is a proxy function that calls the correspondent function in m_BoardSettings
-     * returns a bit-mask of all the element categories that are visible
-     * @return int - the visible element bitmap or-ed from enum GAL_LAYER_ID
+     * Returns a set of all the element categories that are visible
+     * @return the set of visible GAL layers
      * @see enum GAL_LAYER_ID
      */
-    int GetVisibleElements() const;
+    GAL_SET GetVisibleElements() const;
 
     /**
      * Function SetVisibleElements
@@ -484,7 +507,7 @@ public:
      * @param aMask = The new bit-mask of visible element bitmap or-ed from enum GAL_LAYER_ID
      * @see enum GAL_LAYER_ID
      */
-    void SetVisibleElements( int aMask );
+    void SetVisibleElements( const GAL_SET& aMask );
 
     /**
      * Function SetVisibleAlls
@@ -528,16 +551,22 @@ public:
     BOARD_DESIGN_SETTINGS& GetDesignSettings() const
     {
         // remove const-ness with cast. TODO(snh): Make GetDesignSettings const
-        return const_cast<BOARD_DESIGN_SETTINGS&>( m_designSettings );
+        // NOTE(JE) If we want this to be const, it's going to have to involve making BOARD and
+        // everything else that relies on BOARD_DESIGN_SETTINGS aware of the PROJECT so that it
+        // can be retrieved from there.  This will also currently require constructing BOARD with
+        // a valid PROJECT passed in to the ctor.
+
+        return const_cast<BOARD_DESIGN_SETTINGS&>( *m_designSettings.get() );
     }
 
-    /**
-     * Function SetDesignSettings
-     * @param aDesignSettings the new BOARD_DESIGN_SETTINGS to use
-     */
-    void SetDesignSettings( const BOARD_DESIGN_SETTINGS& aDesignSettings )
+    const ZONE_SETTINGS& GetZoneSettings() const override
     {
-        m_designSettings = aDesignSettings;
+        return GetDesignSettings().GetDefaultZoneSettings();
+    }
+
+    void SetZoneSettings( const ZONE_SETTINGS& aSettings ) override
+    {
+        GetDesignSettings().SetDefaultZoneSettings( aSettings );
     }
 
     const PAGE_INFO& GetPageSettings() const                { return m_paper; }
@@ -551,13 +580,6 @@ public:
 
     wxString GetSelectMenuText( EDA_UNITS aUnits ) const override;
 
-    const PCBNEW_SETTINGS& GeneralSettings() const { return *m_generalSettings; }
-
-    void SetGeneralSettings( PCBNEW_SETTINGS* aSettings )
-    {
-        m_generalSettings = aSettings;
-    }
-
     /**
      * Function GetBoardPolygonOutlines
      * Extracts the board outlines and build a closed polygon
diff --git a/pcbnew/class_pad.cpp b/pcbnew/class_pad.cpp
index 4d0186a7b7..e131bf5acf 100644
--- a/pcbnew/class_pad.cpp
+++ b/pcbnew/class_pad.cpp
@@ -487,37 +487,6 @@ void D_PAD::MirrorXPrimitives( int aX )
 }
 
 
-void D_PAD::AppendConfigs( std::vector<PARAM_CFG*>* aResult )
-{
-    // Parameters stored in config are only significant parameters
-    // for a template.
-    // So not all parameters are stored, just few.
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "PadDrill" ),
-                                                      &m_Drill.x,
-                                                      Millimeter2iu( 0.6 ),
-                                                      Millimeter2iu( 0.1 ), Millimeter2iu( 10.0 ),
-                                                      NULL, MM_PER_IU ) );
-
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "PadDrillOvalY" ),
-                                                      &m_Drill.y,
-                                                      Millimeter2iu( 0.6 ),
-                                                      Millimeter2iu( 0.1 ), Millimeter2iu( 10.0 ),
-                                                      NULL, MM_PER_IU ) );
-
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "PadSizeH" ),
-                                                      &m_Size.x,
-                                                      Millimeter2iu( 1.4 ),
-                                                      Millimeter2iu( 0.1 ), Millimeter2iu( 20.0 ),
-                                                      NULL, MM_PER_IU ) );
-
-    aResult->push_back( new PARAM_CFG_INT_WITH_SCALE( wxT( "PadSizeV" ),
-                                                      &m_Size.y,
-                                                      Millimeter2iu( 1.4 ),
-                                                      Millimeter2iu( 0.1 ), Millimeter2iu( 20.0 ),
-                                                      NULL, MM_PER_IU ) );
-}
-
-
 // Returns the position of the pad.
 wxPoint D_PAD::ShapePos() const
 {
diff --git a/pcbnew/class_pad.h b/pcbnew/class_pad.h
index f04dbb9a09..c34f8e2941 100644
--- a/pcbnew/class_pad.h
+++ b/pcbnew/class_pad.h
@@ -551,14 +551,6 @@ public:
      */
     wxString ShowPadAttr() const;
 
-    /**
-     * Function AppendConfigs
-     * appends to @a aResult the configuration setting accessors which will later
-     * allow reading or writing of configuration file information directly into
-     * this object.
-     */
-    void AppendConfigs( std::vector<PARAM_CFG*>* aResult );
-
     EDA_ITEM* Clone() const override;
 
     /**
diff --git a/pcbnew/dialogs/dialog_board_setup.cpp b/pcbnew/dialogs/dialog_board_setup.cpp
index abb5f4b560..56cd7cf1ab 100644
--- a/pcbnew/dialogs/dialog_board_setup.cpp
+++ b/pcbnew/dialogs/dialog_board_setup.cpp
@@ -24,18 +24,24 @@
 #include <panel_setup_tracks_and_vias.h>
 #include <panel_setup_mask_and_paste.h>
 #include <../board_stackup_manager/panel_board_stackup.h>
+#include <confirm.h>
 #include <kiface_i.h>
 #include <drc/drc.h>
 #include <drc/drc_item.h>
 #include <dialog_import_settings.h>
+#include <io_mgr.h>
 #include <panel_setup_severities.h>
 #include <panel_text_variables.h>
+#include <project.h>
+#include <project/project_file.h>
+#include <settings/settings_manager.h>
+#include <wildcards_and_files_ext.h>
 
 #include "dialog_board_setup.h"
 #include "panel_setup_rules.h"
 
 DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame ) :
-        PAGED_DIALOG( aFrame, _( "Board Setup" ), _( "Import Settings from Another Project..." ) ),
+        PAGED_DIALOG( aFrame, _( "Board Setup" ), _( "Import Settings from Another Board..." ) ),
         m_frame( aFrame )
 {
     m_layers = new PANEL_SETUP_LAYERS( this, aFrame );
@@ -127,30 +133,59 @@ void DIALOG_BOARD_SETUP::OnAuxiliaryAction( wxCommandEvent& event )
     if( importDlg.ShowModal() == wxID_CANCEL )
         return;
 
-    wxConfigBase* cfg = new wxFileConfig( wxEmptyString, wxEmptyString, importDlg.GetFilePath() );
+    wxFileName boardFn( importDlg.GetFilePath() );
+    wxFileName projectFn( boardFn );
 
-    // We do not want expansion of env var values when reading our project config file
-    cfg->SetExpandEnvVars( false );
-    cfg->SetPath( wxCONFIG_PATH_SEPARATOR );
+    projectFn.SetExt( ProjectFileExtension );
 
-    BOARD* dummyBoard = new BOARD();
-    std::vector<PARAM_CFG*> designSettingsConfig;
+    if( !m_frame->GetSettingsManager()->LoadProject( projectFn.GetFullPath(), false ) )
+    {
+        wxString msg = wxString::Format( _( "Error importing settings from borad:\n"
+                                            "Associated project file %s could not be loaded" ),
+                                         projectFn.GetFullPath() );
+        DisplayErrorMessage( this, msg );
 
-    dummyBoard->GetDesignSettings().AppendConfigs( dummyBoard, &designSettingsConfig );
-    wxConfigLoadParams( cfg, designSettingsConfig, GROUP_PCB );
+        return;
+    }
+
+    PROJECT* otherPrj = m_frame->GetSettingsManager()->GetProject( projectFn.GetFullPath() );
+
+    PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) );
+
+    BOARD* otherBoard = new BOARD();
+
+    try
+    {
+        otherBoard = pi->Load( boardFn.GetFullPath(), nullptr, nullptr );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        if( ioe.Problem() != wxT( "CANCEL" ) )
+        {
+            wxString msg =
+                    wxString::Format( _( "Error loading board file:\n%s" ), boardFn.GetFullPath() );
+            DisplayErrorMessage( this, msg, ioe.What() );
+        }
+
+        m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
+
+        return;
+    }
+
+    otherBoard->SetProject( otherPrj );
 
     if( importDlg.m_LayersOpt->GetValue() )
-        m_layers->ImportSettingsFrom( dummyBoard );
+        m_layers->ImportSettingsFrom( otherBoard );
     if( importDlg.m_TextAndGraphicsOpt->GetValue() )
-        m_textAndGraphics->ImportSettingsFrom( dummyBoard );
+        m_textAndGraphics->ImportSettingsFrom( otherBoard );
     if( importDlg.m_ConstraintsOpt->GetValue() )
-        m_constraints->ImportSettingsFrom( dummyBoard );
+        m_constraints->ImportSettingsFrom( otherBoard );
     if( importDlg.m_NetclassesOpt->GetValue() )
-        m_netclasses->ImportSettingsFrom( dummyBoard );
+        m_netclasses->ImportSettingsFrom( otherBoard );
     if( importDlg.m_TracksAndViasOpt->GetValue() )
-        m_tracksAndVias->ImportSettingsFrom( dummyBoard );
+        m_tracksAndVias->ImportSettingsFrom( otherBoard );
     if( importDlg.m_MaskAndPasteOpt->GetValue() )
-        m_maskAndPaste->ImportSettingsFrom( dummyBoard );
+        m_maskAndPaste->ImportSettingsFrom( otherBoard );
 
     // If layers options are imported, import also the stackup
     // layers options and stackup are linked, so they cannot be imported
@@ -159,12 +194,16 @@ void DIALOG_BOARD_SETUP::OnAuxiliaryAction( wxCommandEvent& event )
     // Note also currently only the list of enabled layers can be imported, because
     // we import settings from a .pro project file, not the settings inside
     // a board, and info only living in the board is not imported.
+    // TODO: Add import of physical settings now that we are actually loading the board here
     if( importDlg.m_LayersOpt->GetValue() )
-        m_physicalStackup->ImportSettingsFrom( dummyBoard );
+        m_physicalStackup->ImportSettingsFrom( otherBoard );
 
     if( importDlg.m_SeveritiesOpt->GetValue() )
-        m_severities->ImportSettingsFrom( dummyBoard->GetDesignSettings().m_DRCSeverities );
+        m_severities->ImportSettingsFrom( otherBoard->GetDesignSettings().m_DRCSeverities );
 
-    delete dummyBoard;
-    delete cfg;
+    otherBoard->ClearProject();
+
+    m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
+
+    delete otherBoard;
 }
diff --git a/pcbnew/dialogs/dialog_export_idf.cpp b/pcbnew/dialogs/dialog_export_idf.cpp
index 30fc89dc61..9b1f4d9ada 100644
--- a/pcbnew/dialogs/dialog_export_idf.cpp
+++ b/pcbnew/dialogs/dialog_export_idf.cpp
@@ -30,6 +30,7 @@
 #include <dialog_export_idf_base.h>
 #include <pcb_edit_frame.h>
 #include <pcbnew_settings.h>
+#include <project/project_file.h> // LAST_PATH_TYPE
 #include <confirm.h>
 
 
diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp
index 65995e271b..9a5ede7273 100644
--- a/pcbnew/dialogs/dialog_export_step.cpp
+++ b/pcbnew/dialogs/dialog_export_step.cpp
@@ -33,6 +33,7 @@
 #include "class_board.h"
 #include "dialog_export_step_base.h"
 #include <pcbnew_settings.h>
+#include <project/project_file.h> // LAST_PATH_TYPE
 #include <widgets/text_ctrl_eval.h>
 #include <wx_html_report_panel.h>
 
diff --git a/pcbnew/dialogs/dialog_export_vrml.cpp b/pcbnew/dialogs/dialog_export_vrml.cpp
index d3b9147d75..9a2c431fe5 100644
--- a/pcbnew/dialogs/dialog_export_vrml.cpp
+++ b/pcbnew/dialogs/dialog_export_vrml.cpp
@@ -35,6 +35,7 @@
 #include <pcb_edit_frame.h>
 #include <pcbnew_settings.h>
 #include <pcbnew.h>
+#include <project/project_file.h>  // LAST_PATH_TYPE
 
 
 /* the dialog to create VRML files, derived from DIALOG_EXPORT_3DFILE_BASE,
diff --git a/pcbnew/dialogs/dialog_global_edit_tracks_and_vias.cpp b/pcbnew/dialogs/dialog_global_edit_tracks_and_vias.cpp
index 599c5284c1..bfb0e5e2e8 100644
--- a/pcbnew/dialogs/dialog_global_edit_tracks_and_vias.cpp
+++ b/pcbnew/dialogs/dialog_global_edit_tracks_and_vias.cpp
@@ -170,7 +170,7 @@ void DIALOG_GLOBAL_EDIT_TRACKS_AND_VIAS::buildFilterLists()
 
     // Populate the netclass filter list with netclass names
     wxArrayString netclassNames;
-    NETCLASSES&   netclasses = m_brd->GetDesignSettings().m_NetClasses;
+    NETCLASSES&   netclasses = m_brd->GetDesignSettings().GetNetClasses();
 
     netclassNames.push_back( netclasses.GetDefaultPtr()->GetName() );
 
@@ -200,7 +200,7 @@ void DIALOG_GLOBAL_EDIT_TRACKS_AND_VIAS::buildNetclassesGrid()
     m_netclassGrid->SetCellValue( 0, GRID_uVIASIZE, _( "uVia Size" ) );
     m_netclassGrid->SetCellValue( 0, GRID_uVIADRILL, _( "uVia Drill" ) );
 
-    NETCLASSES& netclasses = m_brd->GetDesignSettings().m_NetClasses;
+    NETCLASSES& netclasses = m_brd->GetDesignSettings().GetNetClasses();
     NETCLASS*   defaultNetclass = m_brd->GetDesignSettings().GetDefault();
     m_netclassGrid->AppendRows( netclasses.GetCount() + 1 );
 
diff --git a/pcbnew/dialogs/dialog_import_settings.cpp b/pcbnew/dialogs/dialog_import_settings.cpp
index 6d7ec1a274..3cc2bbe9fd 100644
--- a/pcbnew/dialogs/dialog_import_settings.cpp
+++ b/pcbnew/dialogs/dialog_import_settings.cpp
@@ -59,7 +59,7 @@ void DIALOG_IMPORT_SETTINGS::OnBrowseClicked( wxCommandEvent& event )
     fn.SetExt( LegacyProjectFileExtension );
 
     wxFileDialog dlg( this, _( "Import Settings From" ), fn.GetPath(), fn.GetFullName(),
-                      ProjectFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR );
+                      PcbFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR );
 
     if( dlg.ShowModal() == wxID_OK )
         m_filePathCtrl->SetValue( dlg.GetPath() );
diff --git a/pcbnew/dialogs/dialog_netlist.cpp b/pcbnew/dialogs/dialog_netlist.cpp
index e618106177..39a534e1ce 100644
--- a/pcbnew/dialogs/dialog_netlist.cpp
+++ b/pcbnew/dialogs/dialog_netlist.cpp
@@ -41,6 +41,7 @@
 #include <wildcards_and_files_ext.h>
 #include <netlist_reader/pcb_netlist.h>
 #include <netlist_reader/board_netlist_updater.h>
+#include <project/project_file.h>  // LAST_PATH_TYPE
 
 #include <dialog_netlist.h>
 
diff --git a/pcbnew/dialogs/panel_modedit_defaults.cpp b/pcbnew/dialogs/panel_modedit_defaults.cpp
index 1c826ccb3c..9274f3de90 100644
--- a/pcbnew/dialogs/panel_modedit_defaults.cpp
+++ b/pcbnew/dialogs/panel_modedit_defaults.cpp
@@ -24,6 +24,7 @@
 #include <fctsys.h>
 #include <widgets/paged_dialog.h>
 #include <footprint_edit_frame.h>
+#include <footprint_editor_settings.h>
 #include <widgets/wx_grid.h>
 #include <grid_tricks.h>
 
@@ -361,7 +362,8 @@ bool PANEL_MODEDIT_DEFAULTS::TransferDataFromWindow()
         m_brdSettings.m_DefaultFPTextItems.emplace_back( text, visible, layer );
     }
 
-    m_frame->SetDesignSettings( m_brdSettings );
+    if( FOOTPRINT_EDITOR_SETTINGS* cfg = m_frame->GetSettings() )
+        cfg->m_DesignSettings = m_brdSettings;
 
     return true;
 }
diff --git a/pcbnew/dialogs/panel_setup_netclasses.cpp b/pcbnew/dialogs/panel_setup_netclasses.cpp
index 10fa75a87f..79d3c82b22 100644
--- a/pcbnew/dialogs/panel_setup_netclasses.cpp
+++ b/pcbnew/dialogs/panel_setup_netclasses.cpp
@@ -152,8 +152,8 @@ static void netclassToGridRow( EDA_UNITS aUnits, wxGrid* aGrid, int aRow, const
 
 bool PANEL_SETUP_NETCLASSES::TransferDataToWindow()
 {
-    NETCLASSES& netclasses = m_BrdSettings->m_NetClasses;
-    NETCLASSPTR netclass = netclasses.GetDefault();
+    NETCLASSES& netclasses = m_BrdSettings->GetNetClasses();
+    NETCLASSPTR netclass   = netclasses.GetDefault();
 
     if( m_netclassGrid->GetNumberRows() )
         m_netclassGrid->DeleteRows( 0, m_netclassGrid->GetNumberRows() );
@@ -250,7 +250,7 @@ bool PANEL_SETUP_NETCLASSES::TransferDataFromWindow()
     if( !validateData() )
         return false;
 
-    NETCLASSES& netclasses = m_BrdSettings->m_NetClasses;
+    NETCLASSES& netclasses = m_BrdSettings->GetNetClasses();
 
     // Remove all netclasses from board. We'll copy new list after
     netclasses.Clear();
@@ -263,7 +263,7 @@ bool PANEL_SETUP_NETCLASSES::TransferDataFromWindow()
     {
         NETCLASSPTR nc = std::make_shared<NETCLASS>( m_netclassGrid->GetCellValue( row, GRID_NAME ) );
 
-        if( m_BrdSettings->m_NetClasses.Add( nc ) )
+        if( netclasses.Add( nc ) )
             gridRowToNetclass( m_Frame->GetUserUnits(), m_netclassGrid, row, nc );
     }
 
diff --git a/pcbnew/drc/drc_netclass_tester.cpp b/pcbnew/drc/drc_netclass_tester.cpp
index 6d2ad68078..e3f5eacfb3 100644
--- a/pcbnew/drc/drc_netclass_tester.cpp
+++ b/pcbnew/drc/drc_netclass_tester.cpp
@@ -39,7 +39,7 @@ bool DRC_NETCLASS_TESTER::RunDRC( EDA_UNITS aUnits, BOARD& aBoard )
     m_board = &aBoard;
 
     bool        success = true;
-    NETCLASSES& netclasses = m_board->GetDesignSettings().m_NetClasses;
+    NETCLASSES& netclasses = m_board->GetDesignSettings().GetNetClasses();
 
     success &= checkNetClass( netclasses.GetDefault() );
 
diff --git a/pcbnew/drc/drc_rule_parser.cpp b/pcbnew/drc/drc_rule_parser.cpp
index 7ac7de2697..f4b92b90df 100644
--- a/pcbnew/drc/drc_rule_parser.cpp
+++ b/pcbnew/drc/drc_rule_parser.cpp
@@ -132,7 +132,7 @@ void DRC_RULES_PARSER::Parse( std::vector<DRC_SELECTOR*>& aSelectors,
 
 DRC_SELECTOR* DRC_RULES_PARSER::parseDRC_SELECTOR( wxString* aRuleName )
 {
-    NETCLASSES&   netclasses = m_board->GetDesignSettings().m_NetClasses;
+    NETCLASSES&   netclasses = m_board->GetDesignSettings().GetNetClasses();
     DRC_SELECTOR* selector = new DRC_SELECTOR();
     T             token;
 
diff --git a/pcbnew/exporters/export_gencad.cpp b/pcbnew/exporters/export_gencad.cpp
index 63e9a0cfc7..66a6d3eee1 100644
--- a/pcbnew/exporters/export_gencad.cpp
+++ b/pcbnew/exporters/export_gencad.cpp
@@ -45,6 +45,7 @@
 #include <pcbnew.h>
 #include <pcbnew_settings.h>
 #include <pgm_base.h>
+#include <project/project_file.h> // LAST_PATH_TYPE
 #include <trigo.h>
 
 static bool CreateHeaderInfoData( FILE* aFile, PCB_EDIT_FRAME* frame );
diff --git a/pcbnew/exporters/gerber_jobfile_writer.cpp b/pcbnew/exporters/gerber_jobfile_writer.cpp
index fb925f066b..d299f80e3a 100644
--- a/pcbnew/exporters/gerber_jobfile_writer.cpp
+++ b/pcbnew/exporters/gerber_jobfile_writer.cpp
@@ -452,7 +452,7 @@ void GERBER_JOBFILE_WRITER::addJSONDesignRules()
     bool                         hasInnerLayers = m_pcb->GetCopperLayerCount() > 2;
 
     // Search a smaller clearance in other net classes, if any.
-    for( const std::pair<const wxString, NETCLASSPTR>& entry : dsnSettings.m_NetClasses )
+    for( const std::pair<const wxString, NETCLASSPTR>& entry : dsnSettings.GetNetClasses() )
         minclearanceOuter = std::min( minclearanceOuter, entry.second->GetClearance() );
 
     // job file knows different clearance types.
diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp
index b33b49c05a..f220d55dff 100644
--- a/pcbnew/files.cpp
+++ b/pcbnew/files.cpp
@@ -52,6 +52,8 @@
 
 #include <wx/wupdlock.h>
 #include <settings/settings_manager.h>
+#include <project/project_file.h>
+#include <project/project_local_settings.h>
 
 
 //#define     USE_INSTRUMENTATION     1
@@ -305,14 +307,19 @@ bool PCB_EDIT_FRAME::Files_io_from_id( int id )
                 return false;
         }
 
-        if( !Clear_Pcb( false ) )
-            return false;
+        GetSettingsManager()->SaveProject( GetSettingsManager()->Prj().GetProjectFullName() );
+        GetBoard()->ClearProject();
 
         wxFileName fn( wxStandardPaths::Get().GetDocumentsDir(), wxT( "noname" ),
-                       LegacyProjectFileExtension );
+                       ProjectFileExtension );
 
         GetSettingsManager()->LoadProject( fn.GetFullPath() );
 
+        LoadProjectSettings();
+
+        if( !Clear_Pcb( false ) )
+            return false;
+
         onBoardLoaded();
 
         OnModify();
@@ -478,7 +485,7 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
     ReleaseFile();
 
     wxFileName pro = fullFileName;
-    pro.SetExt( LegacyProjectFileExtension );
+    pro.SetExt( ProjectFileExtension );
 
     bool is_new = !wxFileName::IsFileReadable( fullFileName );
 
@@ -494,7 +501,11 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
     wxWindowUpdateLocker no_update( m_Layers );     // Avoid flicker when rebuilding m_Layers
 
-    Clear_Pcb( false );     // pass false since we prompted above for a modified board
+    // Unlink the old project if needed
+    GetBoard()->ClearProject();
+
+    // No save prompt (we already prompted above), and only reset to a new blank board if new
+    Clear_Pcb( false, !is_new );
 
     IO_MGR::PCB_FILE_T  pluginType = plugin_type( fullFileName, aCtl );
 
@@ -502,8 +513,7 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
     if( !converted )
     {
-        // PROJECT::SetProjectFullName() is an impactful function.  It should only be
-        // called under carefully considered circumstances.
+        // Loading a project should only be done under carefully considered circumstances.
 
         // The calling code should know not to ask me here to change projects unless
         // it knows what consequences that will have on other KIFACEs running and using
@@ -560,40 +570,30 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
                 DisplayErrorMessage( this, msg, ioe.What() );
             }
 
+            // We didn't create a new blank board above, so do that now
+            Clear_Pcb( false );
+
             return false;
         }
 
-        BOARD_DESIGN_SETTINGS& bds = loadedBoard->m_designSettings;
+        SetBoard( loadedBoard );
 
-        if( bds.m_CopperEdgeClearance == Millimeter2iu( LEGACY_COPPEREDGECLEARANCE ) )
+        // On save; design settings will be removed from the board
+        if( loadedBoard->m_LegacyDesignSettingsLoaded )
+            loadedBoard->SetModified();
+
+        // Move legacy view settings to local project settings
+        if( !loadedBoard->m_LegacyVisibleLayers.test( Rescue ) )
         {
-            // 5.1 boards stored some settings in the config so as not to bump the file version.
-            // These will have been loaded into the config-initialized board, so we copy them
-            // from there.
-            BOARD_DESIGN_SETTINGS& configBds = GetBoard()->GetDesignSettings();
-
-            bds.m_DRCSeverities                     = configBds.m_DRCSeverities;
-            bds.m_HoleToHoleMin                     = configBds.m_HoleToHoleMin;
-            bds.m_LineThickness[LAYER_CLASS_OTHERS] = configBds.m_LineThickness[LAYER_CLASS_OTHERS];
-            bds.m_TextSize[LAYER_CLASS_OTHERS]      = configBds.m_TextSize[LAYER_CLASS_OTHERS];
-            bds.m_TextThickness[LAYER_CLASS_OTHERS] = configBds.m_TextThickness[LAYER_CLASS_OTHERS];
-            std::copy( configBds.m_TextItalic,  configBds.m_TextItalic + 4,  bds.m_TextItalic );
-            std::copy( configBds.m_TextUpright, configBds.m_TextUpright + 4, bds.m_TextUpright );
-            bds.m_DiffPairDimensionsList            = configBds.m_DiffPairDimensionsList;
-            bds.m_CopperEdgeClearance               = configBds.m_CopperEdgeClearance;
-
-            // Before we had a copper edge clearance setting, the edge line widths could be used
-            // as a kludge to control them.  So if there's no setting then infer it from the
-            // edge widths.
-            if( bds.m_CopperEdgeClearance == Millimeter2iu( LEGACY_COPPEREDGECLEARANCE ) )
-                bds.SetCopperEdgeClearance( inferLegacyEdgeClearance( loadedBoard ) );
+            Prj().GetLocalSettings().m_VisibleLayers = loadedBoard->m_LegacyVisibleLayers;
+            loadedBoard->SetModified();
         }
 
-        // We store the severities in the config to keep board-file changes to a minimum
-        BOARD_DESIGN_SETTINGS& configBds = GetBoard()->GetDesignSettings();
-        bds.m_DRCSeverities              = configBds.m_DRCSeverities;
-
-        SetBoard( loadedBoard );
+        if( !loadedBoard->m_LegacyVisibleItems.test( GAL_LAYER_INDEX( GAL_LAYER_ID_BITMASK_END ) ) )
+        {
+            Prj().GetLocalSettings().m_VisibleItems = loadedBoard->m_LegacyVisibleItems;
+            loadedBoard->SetModified();
+        }
 
         // we should not ask PLUGINs to do these items:
         loadedBoard->BuildListOfNets();
@@ -723,6 +723,21 @@ bool PCB_EDIT_FRAME::SavePcbFile( const wxString& aFileName, bool aCreateBackupF
         return false;
     }
 
+    // TODO: this will break if we ever go multi-board
+    wxFileName projectFile( pcbFileName );
+    projectFile.SetExt( ProjectFileExtension );
+
+    if( !projectFile.FileExists() )
+    {
+        // If this is a new board, project filename won't be set yet
+        if( projectFile.GetFullPath() != Prj().GetProjectFullName() )
+        {
+            GetBoard()->ClearProject();
+            GetSettingsManager()->LoadProject( projectFile.GetFullPath() );
+            GetBoard()->SetProject( &Prj() );
+        }
+    }
+
     wxString backupFileName;
 
     if( aCreateBackupFile )
@@ -743,6 +758,8 @@ bool PCB_EDIT_FRAME::SavePcbFile( const wxString& aFileName, bool aCreateBackupF
     // edited via the DRC dialog as well as the Board Setup dialog), DRC exclusions, etc.
     SaveProjectSettings();
 
+    GetSettingsManager()->SaveProject();
+
     ClearMsgPanel();
 
     wxString    upperTxt;
diff --git a/pcbnew/footprint_edit_frame.cpp b/pcbnew/footprint_edit_frame.cpp
index 3c0376c0ae..b5fabf3f1b 100644
--- a/pcbnew/footprint_edit_frame.cpp
+++ b/pcbnew/footprint_edit_frame.cpp
@@ -401,12 +401,6 @@ BOARD_DESIGN_SETTINGS& FOOTPRINT_EDIT_FRAME::GetDesignSettings() const
 }
 
 
-void FOOTPRINT_EDIT_FRAME::SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings )
-{
-    GetBoard()->SetDesignSettings( aSettings );
-}
-
-
 const PCB_PLOT_PARAMS& FOOTPRINT_EDIT_FRAME::GetPlotSettings() const
 {
     wxFAIL_MSG( "Plotting not supported in Footprint Editor" );
diff --git a/pcbnew/footprint_edit_frame.h b/pcbnew/footprint_edit_frame.h
index 348fb54b45..17ecc8c746 100644
--- a/pcbnew/footprint_edit_frame.h
+++ b/pcbnew/footprint_edit_frame.h
@@ -74,7 +74,6 @@ public:
     FOOTPRINT_EDITOR_SETTINGS* GetSettings();
 
     BOARD_DESIGN_SETTINGS& GetDesignSettings() const override;
-    void SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings ) override;
 
     const PCB_PLOT_PARAMS& GetPlotSettings() const override;
     void SetPlotSettings( const PCB_PLOT_PARAMS& aSettings ) override;
diff --git a/pcbnew/footprint_editor_settings.cpp b/pcbnew/footprint_editor_settings.cpp
index cf892ccc84..42b8998faf 100644
--- a/pcbnew/footprint_editor_settings.cpp
+++ b/pcbnew/footprint_editor_settings.cpp
@@ -39,7 +39,7 @@ const int fpEditSchemaVersion = 1;
 
 FOOTPRINT_EDITOR_SETTINGS::FOOTPRINT_EDITOR_SETTINGS() :
         APP_SETTINGS_BASE( "fpedit", fpEditSchemaVersion ),
-        m_DesignSettings(),
+        m_DesignSettings( nullptr, "fpedit.settings" ),
         m_MagneticItems(),
         m_Display(),
         m_UserGrid(),
diff --git a/pcbnew/fp_tree_model_adapter.cpp b/pcbnew/fp_tree_model_adapter.cpp
index 19ece66f70..bbc629cdb7 100644
--- a/pcbnew/fp_tree_model_adapter.cpp
+++ b/pcbnew/fp_tree_model_adapter.cpp
@@ -34,7 +34,7 @@ FP_TREE_MODEL_ADAPTER::PTR FP_TREE_MODEL_ADAPTER::Create( EDA_BASE_FRAME* aParen
 
 
 FP_TREE_MODEL_ADAPTER::FP_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs ) :
-        LIB_TREE_MODEL_ADAPTER( aParent ),
+        LIB_TREE_MODEL_ADAPTER( aParent, "pinned_footprint_libs" ),
         m_libs( (FP_LIB_TABLE*) aLibs )
 {}
 
diff --git a/pcbnew/initpcb.cpp b/pcbnew/initpcb.cpp
index 1fc7a9b2cf..91043b83bb 100644
--- a/pcbnew/initpcb.cpp
+++ b/pcbnew/initpcb.cpp
@@ -29,6 +29,9 @@
 #include <fctsys.h>
 #include <confirm.h>
 #include <pcb_edit_frame.h>
+#include <project.h>
+#include <project/net_settings.h>
+#include <project/project_file.h>
 
 #include <class_board.h>
 
@@ -55,18 +58,11 @@ bool PCB_EDIT_FRAME::Clear_Pcb( bool aQuery, bool aFinal )
     GetScreen()->ClearUndoRedoList();
     GetScreen()->ClrModify();
 
-    // Items visibility flags will be set because a new board will be created.
-    // Grid and ratsnest can be left to their previous state
-    bool showGrid = IsElementVisible( LAYER_GRID );
-    bool showRats = GetDisplayOptions().m_ShowGlobalRatsnest;
-
     if( !aFinal )
     {
         // delete the old BOARD and create a new BOARD so that the default
         // layer names are put into the BOARD.
         SetBoard( new BOARD() );
-        SetElementVisibility( LAYER_GRID, showGrid );
-        SetElementVisibility( LAYER_RATSNEST, showRats );
 
         // clear filename, to avoid overwriting an old file
         GetBoard()->SetFileName( wxEmptyString );
@@ -118,10 +114,6 @@ bool FOOTPRINT_EDIT_FRAME::Clear_Pcb( bool aQuery )
 
     BOARD* board = new BOARD;
 
-    // Transfer current design settings
-    if( GetBoard() )
-        board->SetDesignSettings( GetBoard()->GetDesignSettings() );
-
     board->SynchronizeNetsAndNetClasses();
     SetBoard( board );
 
diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp
index 814c4a6432..a1f65e071d 100644
--- a/pcbnew/kicad_plugin.cpp
+++ b/pcbnew/kicad_plugin.cpp
@@ -472,8 +472,6 @@ void PCB_IO::formatLayer( const BOARD_ITEM* aItem ) const
 
 void PCB_IO::formatSetup( BOARD* aBoard, int aNestLevel ) const
 {
-    const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
-
     // Setup
     m_out->Print( aNestLevel, "(setup\n" );
 
@@ -483,104 +481,7 @@ void PCB_IO::formatSetup( BOARD* aBoard, int aNestLevel ) const
     if( aBoard->GetDesignSettings().m_HasStackup )
         stackup.FormatBoardStackup( m_out,aBoard, aNestLevel+1 );
 
-    // Save current default track width, for compatibility with older Pcbnew version;
-    m_out->Print( aNestLevel+1, "(last_trace_width %s)\n",
-                  FormatInternalUnits( dsnSettings.GetCurrentTrackWidth() ).c_str() );
-
-    // Save custom track widths list (the first is not saved here: it's the netclass value)
-    for( unsigned ii = 1; ii < dsnSettings.m_TrackWidthList.size(); ii++ )
-    {
-        m_out->Print( aNestLevel+1, "(user_trace_width %s)\n",
-                      FormatInternalUnits( dsnSettings.m_TrackWidthList[ii] ).c_str() );
-    }
-
-    m_out->Print( aNestLevel+1, "(trace_clearance %s)\n",
-                  FormatInternalUnits( dsnSettings.GetDefault()->GetClearance() ).c_str() );
-
-    // ZONE_SETTINGS
-    m_out->Print( aNestLevel+1, "(zone_clearance %s)\n",
-                  FormatInternalUnits( aBoard->GetZoneSettings().m_ZoneClearance ).c_str() );
-    m_out->Print( aNestLevel+1, "(zone_45_only %s)\n",
-                  aBoard->GetZoneSettings().m_Zone_45_Only ? "yes" : "no" );
-
-    m_out->Print( aNestLevel+1, "(trace_min %s)\n",
-                  FormatInternalUnits( dsnSettings.m_TrackMinWidth ).c_str() );
-    m_out->Print( aNestLevel+1, "(clearance_min %s)\n",
-                  FormatInternalUnits( dsnSettings.m_MinClearance ).c_str() );
-    m_out->Print( aNestLevel+1, "(via_min_annulus %s)\n",
-                  FormatInternalUnits( dsnSettings.m_ViasMinAnnulus ).c_str() );
-    m_out->Print( aNestLevel+1, "(via_min_size %s)\n",
-                  FormatInternalUnits( dsnSettings.m_ViasMinSize ).c_str() );
-    m_out->Print( aNestLevel+1, "(through_hole_min %s)\n",
-                  FormatInternalUnits( dsnSettings.m_MinThroughDrill ).c_str() );
-    m_out->Print( aNestLevel+1, "(hole_to_hole_min %s)\n",
-                  FormatInternalUnits( dsnSettings.m_HoleToHoleMin ).c_str() );
-
-    // Save current default via size, for compatibility with older Pcbnew version;
-    m_out->Print( aNestLevel+1, "(via_size %s)\n",
-                  FormatInternalUnits( dsnSettings.GetDefault()->GetViaDiameter() ).c_str() );
-    m_out->Print( aNestLevel+1, "(via_drill %s)\n",
-                  FormatInternalUnits( dsnSettings.GetDefault()->GetViaDrill() ).c_str() );
-
-    // Save custom via dimensions list (the first is not saved here: it's the netclass value)
-    for( unsigned ii = 1; ii < dsnSettings.m_ViasDimensionsList.size(); ii++ )
-        m_out->Print( aNestLevel+1, "(user_via %s %s)\n",
-                      FormatInternalUnits( dsnSettings.m_ViasDimensionsList[ii].m_Diameter ).c_str(),
-                      FormatInternalUnits( dsnSettings.m_ViasDimensionsList[ii].m_Drill ).c_str() );
-
-    // Save custom diff-pair dimensions (the first is not saved here: it's the netclass value)
-    for( unsigned ii = 1; ii < dsnSettings.m_DiffPairDimensionsList.size(); ii++ )
-    {
-        m_out->Print( aNestLevel+1, "(user_diff_pair %s %s %s)\n",
-                      FormatInternalUnits( dsnSettings.m_DiffPairDimensionsList[ii].m_Width ).c_str(),
-                      FormatInternalUnits( dsnSettings.m_DiffPairDimensionsList[ii].m_Gap ).c_str(),
-                      FormatInternalUnits( dsnSettings.m_DiffPairDimensionsList[ii].m_ViaGap ).c_str() );
-    }
-
-    // for old versions compatibility:
-    if( dsnSettings.m_BlindBuriedViaAllowed )
-        m_out->Print( aNestLevel+1, "(blind_buried_vias_allowed yes)\n" );
-
-    m_out->Print( aNestLevel+1, "(uvia_size %s)\n",
-                  FormatInternalUnits( dsnSettings.GetDefault()->GetuViaDiameter() ).c_str() );
-    m_out->Print( aNestLevel+1, "(uvia_drill %s)\n",
-                  FormatInternalUnits( dsnSettings.GetDefault()->GetuViaDrill() ).c_str() );
-    m_out->Print( aNestLevel+1, "(uvias_allowed %s)\n",
-                  ( dsnSettings.m_MicroViasAllowed ) ? "yes" : "no" );
-    m_out->Print( aNestLevel+1, "(uvia_min_size %s)\n",
-                  FormatInternalUnits( dsnSettings.m_MicroViasMinSize ).c_str() );
-    m_out->Print( aNestLevel+1, "(uvia_min_drill %s)\n",
-                  FormatInternalUnits( dsnSettings.m_MicroViasMinDrill ).c_str() );
-
-    m_out->Print( aNestLevel+1, "(max_error %s)\n",
-                  FormatInternalUnits( dsnSettings.m_MaxError ).c_str() );
-
-    // Store this option only if it is not the legacy option:
-    if( dsnSettings.m_ZoneUseNoOutlineInFill )
-        m_out->Print( aNestLevel+1, "(filled_areas_thickness no)\n" );
-
-    formatDefaults( dsnSettings, aNestLevel+1 );
-
-    m_out->Print( aNestLevel+1, "(pad_size %s %s)\n",
-                  FormatInternalUnits( dsnSettings.m_Pad_Master.GetSize().x ).c_str(),
-                  FormatInternalUnits( dsnSettings.m_Pad_Master.GetSize().y ).c_str() );
-    m_out->Print( aNestLevel+1, "(pad_drill %s)\n",
-                  FormatInternalUnits( dsnSettings.m_Pad_Master.GetDrillSize().x ).c_str() );
-
-    m_out->Print( aNestLevel+1, "(pad_to_mask_clearance %s)\n",
-                  FormatInternalUnits( dsnSettings.m_SolderMaskMargin ).c_str() );
-
-    if( dsnSettings.m_SolderMaskMinWidth )
-        m_out->Print( aNestLevel+1, "(solder_mask_min_width %s)\n",
-                      FormatInternalUnits( dsnSettings.m_SolderMaskMinWidth ).c_str() );
-
-    if( dsnSettings.m_SolderPasteMargin != 0 )
-        m_out->Print( aNestLevel+1, "(pad_to_paste_clearance %s)\n",
-                      FormatInternalUnits( dsnSettings.m_SolderPasteMargin ).c_str() );
-
-    if( dsnSettings.m_SolderPasteMarginRatio != 0 )
-        m_out->Print( aNestLevel+1, "(pad_to_paste_clearance_ratio %s)\n",
-                      Double2Str( dsnSettings.m_SolderPasteMarginRatio ).c_str() );
+    BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
 
     if( dsnSettings.m_AuxOrigin != wxPoint( 0, 0 ) )
         m_out->Print( aNestLevel+1, "(aux_axis_origin %s %s)\n",
@@ -592,71 +493,12 @@ void PCB_IO::formatSetup( BOARD* aBoard, int aNestLevel ) const
                       FormatInternalUnits( dsnSettings.m_GridOrigin.x ).c_str(),
                       FormatInternalUnits( dsnSettings.m_GridOrigin.y ).c_str() );
 
-    m_out->Print( aNestLevel+1, "(visible_elements %X)\n",
-                  dsnSettings.GetVisibleElements() );
-
     aBoard->GetPlotOptions().Format( m_out, aNestLevel+1 );
 
     m_out->Print( aNestLevel, ")\n\n" );
 }
 
 
-void PCB_IO::formatDefaults( const BOARD_DESIGN_SETTINGS& aSettings, int aNestLevel ) const
-{
-    m_out->Print( aNestLevel, "(defaults\n" );
-
-    m_out->Print( aNestLevel+1, "(edge_clearance %s)\n",
-                  FormatInternalUnits( aSettings.m_CopperEdgeClearance ).c_str() );
-
-    m_out->Print( aNestLevel+1, "(edge_cuts_line_width %s)\n",
-                  FormatInternalUnits( aSettings.m_LineThickness[ LAYER_CLASS_EDGES ] ).c_str() );
-
-    m_out->Print( aNestLevel+1, "(courtyard_line_width %s)\n",
-                  FormatInternalUnits( aSettings.m_LineThickness[ LAYER_CLASS_COURTYARD ] ).c_str() );
-
-    m_out->Print( aNestLevel+1, "(copper_line_width %s)\n",
-                  FormatInternalUnits( aSettings.m_LineThickness[ LAYER_CLASS_COPPER ] ).c_str() );
-    m_out->Print( aNestLevel+1, "(copper_text_dims (size %s %s) (thickness %s)%s%s)\n",
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_COPPER ].x ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_COPPER ].y ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextThickness[ LAYER_CLASS_COPPER ] ).c_str(),
-                  aSettings.m_TextItalic[ LAYER_CLASS_COPPER ] ? " italic" : "",
-                  aSettings.m_TextUpright[ LAYER_CLASS_COPPER ] ? " keep_upright" : "" );
-
-    m_out->Print( aNestLevel+1, "(silk_line_width %s)\n",
-                  FormatInternalUnits( aSettings.m_LineThickness[ LAYER_CLASS_SILK ] ).c_str() );
-    m_out->Print( aNestLevel+1, "(silk_text_dims (size %s %s) (thickness %s)%s%s)\n",
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_SILK ].x ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_SILK ].y ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextThickness[ LAYER_CLASS_SILK ] ).c_str(),
-                  aSettings.m_TextItalic[ LAYER_CLASS_SILK ] ? " italic" : "",
-                  aSettings.m_TextUpright[ LAYER_CLASS_SILK ] ? " keep_upright" : "" );
-
-    m_out->Print( aNestLevel+1, "(fab_layers_line_width %s)\n",
-                  FormatInternalUnits( aSettings.m_LineThickness[ LAYER_CLASS_FAB ] ).c_str() );
-    m_out->Print( aNestLevel+1, "(fab_layers_text_dims (size %s %s) (thickness %s)%s%s)\n",
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_FAB ].x ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_FAB ].y ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextThickness[ LAYER_CLASS_FAB ] ).c_str(),
-                  aSettings.m_TextItalic[ LAYER_CLASS_OTHERS ] ? " italic" : "",
-                  aSettings.m_TextUpright[ LAYER_CLASS_OTHERS ] ? " keep_upright" : "" );
-
-    m_out->Print( aNestLevel+1, "(other_layers_line_width %s)\n",
-                  FormatInternalUnits( aSettings.m_LineThickness[ LAYER_CLASS_OTHERS ] ).c_str() );
-    m_out->Print( aNestLevel+1, "(other_layers_text_dims (size %s %s) (thickness %s)%s%s)\n",
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_OTHERS ].x ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextSize[ LAYER_CLASS_OTHERS ].y ).c_str(),
-                  FormatInternalUnits( aSettings.m_TextThickness[ LAYER_CLASS_OTHERS ] ).c_str(),
-                  aSettings.m_TextItalic[ LAYER_CLASS_OTHERS ] ? " italic" : "",
-                  aSettings.m_TextUpright[ LAYER_CLASS_OTHERS ] ? " keep_upright" : "" );
-
-    m_out->Print( aNestLevel+1, "(dimension_units %d)\n", aSettings.m_DimensionUnits );
-    m_out->Print( aNestLevel+1, "(dimension_precision %d)\n", aSettings.m_DimensionPrecision );
-
-    m_out->Print( aNestLevel, ")\n" );
-}
-
-
 void PCB_IO::formatGeneral( BOARD* aBoard, int aNestLevel ) const
 {
     const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
@@ -683,7 +525,6 @@ void PCB_IO::formatBoardLayers( BOARD* aBoard, int aNestLevel ) const
     m_out->Print( aNestLevel, "(layers\n" );
 
     // Save only the used copper layers from front to back.
-    LSET visible_layers = aBoard->GetVisibleLayers();
 
     for( LSEQ cu = aBoard->GetEnabledLayers().CuStack();  cu;  ++cu )
     {
@@ -693,9 +534,6 @@ void PCB_IO::formatBoardLayers( BOARD* aBoard, int aNestLevel ) const
                       m_out->Quotew( aBoard->GetLayerName( layer ) ).c_str(),
                       LAYER::ShowType( aBoard->GetLayerType( layer ) ) );
 
-        if( !visible_layers[layer] )
-            m_out->Print( 0, " hide" );
-
         m_out->Print( 0, ")\n" );
     }
 
@@ -730,9 +568,6 @@ void PCB_IO::formatBoardLayers( BOARD* aBoard, int aNestLevel ) const
         m_out->Print( aNestLevel+1, "(%d %s user", layer,
                       m_out->Quotew( aBoard->GetLayerName( layer ) ).c_str() );
 
-        if( !visible_layers[layer] )
-            m_out->Print( 0, " hide" );
-
         m_out->Print( 0, ")\n" );
     }
 
@@ -742,7 +577,6 @@ void PCB_IO::formatBoardLayers( BOARD* aBoard, int aNestLevel ) const
 
 void PCB_IO::formatNetInformation( BOARD* aBoard, int aNestLevel ) const
 {
-    const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
     for( NETINFO_ITEM* net : *m_mapping )
     {
         m_out->Print( aNestLevel, "(net %d %s)\n",
@@ -751,19 +585,6 @@ void PCB_IO::formatNetInformation( BOARD* aBoard, int aNestLevel ) const
     }
 
     m_out->Print( 0, "\n" );
-
-    // Save the default net class first.
-    NETCLASS defaultNC = *dsnSettings.GetDefault();
-    filterNetClass( *aBoard, defaultNC );       // Remove empty nets (from a copy of a netclass)
-    defaultNC.Format( m_out, aNestLevel, m_ctl );
-
-    // Save the rest of the net classes alphabetically.
-    for( const auto& it : dsnSettings.m_NetClasses )
-    {
-        NETCLASS netclass = *it.second;
-        filterNetClass( *aBoard, netclass );    // Remove empty nets (from a copy of a netclass)
-        netclass.Format( m_out, aNestLevel, m_ctl );
-    }
 }
 
 
diff --git a/pcbnew/kicad_plugin.h b/pcbnew/kicad_plugin.h
index f780449ccb..2366e7c45a 100644
--- a/pcbnew/kicad_plugin.h
+++ b/pcbnew/kicad_plugin.h
@@ -70,7 +70,8 @@ class TEXTE_PCB;
 //#define SEXPR_BOARD_FILE_VERSION    20200512  // page -> paper
 //#define SEXPR_BOARD_FILE_VERSION    20200518  // save hole_to_hole_min
 //#define SEXPR_BOARD_FILE_VERSION    20200614  // Add support for fp_rects and gr_rects
-#define SEXPR_BOARD_FILE_VERSION      20200625  // Multilayer zones, zone names, island controls
+//#define SEXPR_BOARD_FILE_VERSION    20200625  // Multilayer zones, zone names, island controls
+#define SEXPR_BOARD_FILE_VERSION      20200628  // remove visibility settings
 
 #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)
@@ -216,9 +217,6 @@ protected:
     /// formats the board setup information
     void formatSetup( BOARD* aBoard, int aNestLevel = 0 ) const;
 
-    /// formats the defaults subsection of the board setup
-    void formatDefaults( const BOARD_DESIGN_SETTINGS& aSettings, int aNestLevel ) const;
-
     /// formats the General section of the file
     void formatGeneral( BOARD* aBoard, int aNestLevel = 0 ) const;
 
diff --git a/pcbnew/legacy_plugin.cpp b/pcbnew/legacy_plugin.cpp
index 8789d140bc..83be7acf7c 100644
--- a/pcbnew/legacy_plugin.cpp
+++ b/pcbnew/legacy_plugin.cpp
@@ -832,13 +832,13 @@ void LEGACY_PLUGIN::loadSHEET()
 
 void LEGACY_PLUGIN::loadSETUP()
 {
-    NETCLASS*             netclass_default = m_board->GetDesignSettings().GetDefault();
-    // TODO Orson: is it really necessary to first operate on a copy and then apply it?
-    // would not it be better to use reference here and apply all the changes instantly?
-    BOARD_DESIGN_SETTINGS bds = m_board->GetDesignSettings();
-    ZONE_SETTINGS         zs  = m_board->GetZoneSettings();
-    char*                 line;
-    char*                 saveptr;
+    BOARD_DESIGN_SETTINGS& bds              = m_board->GetDesignSettings();
+    ZONE_SETTINGS          zs               = m_board->GetZoneSettings();
+    NETCLASS*              netclass_default = bds.GetDefault();
+    char*                  line;
+    char*                  saveptr;
+
+    m_board->m_LegacyDesignSettingsLoaded = true;
 
     while( ( line = READLINE( m_reader ) ) != NULL )
     {
@@ -1118,12 +1118,17 @@ void LEGACY_PLUGIN::loadSETUP()
         else if( TESTLINE( "VisibleElements" ) )
         {
             int visibleElements = hexParse( line + SZ( "VisibleElements" ) );
-            bds.SetVisibleElements( visibleElements );
+
+            GAL_SET visibles;
+
+            for( size_t i = 0; i < visibles.size(); i++ )
+                visibles.set( i, visibleElements & ( 1u << i ) );
+
+            m_board->SetVisibleElements( visibles );
         }
 
         else if( TESTLINE( "$EndSETUP" ) )
         {
-            m_board->SetDesignSettings( bds );
             m_board->SetZoneSettings( zs );
 
             // Very old *.brd file does not have  NETCLASSes
@@ -2399,7 +2404,7 @@ void LEGACY_PLUGIN::loadNETCLASS()
 
         else if( TESTLINE( "$EndNCLASS" ) )
         {
-            if( !m_board->GetDesignSettings().m_NetClasses.Add( nc ) )
+            if( !m_board->GetDesignSettings().GetNetClasses().Add( nc ) )
             {
                 // Must have been a name conflict, this is a bad board file.
                 // User may have done a hand edit to the file.
diff --git a/pcbnew/netinfo_item.cpp b/pcbnew/netinfo_item.cpp
index 96b79e12ec..377006aa64 100644
--- a/pcbnew/netinfo_item.cpp
+++ b/pcbnew/netinfo_item.cpp
@@ -56,7 +56,7 @@ NETINFO_ITEM::NETINFO_ITEM( BOARD* aParent, const wxString& aNetName, int aNetCo
     m_parent = aParent;
 
     if( aParent )
-        m_NetClass = aParent->GetDesignSettings().m_NetClasses.GetDefault();
+        m_NetClass = aParent->GetDesignSettings().GetNetClasses().GetDefault();
     else
         m_NetClass = std::make_shared<NETCLASS>( "<invalid>" );
 }
@@ -71,7 +71,7 @@ NETINFO_ITEM::~NETINFO_ITEM()
 void NETINFO_ITEM::SetClass( const NETCLASSPTR& aNetClass )
 {
     wxCHECK( m_parent, /* void */ );
-    m_NetClass = aNetClass ? aNetClass : m_parent->GetDesignSettings().m_NetClasses.GetDefault();
+    m_NetClass = aNetClass ? aNetClass : m_parent->GetDesignSettings().GetNetClasses().GetDefault();
 }
 
 
diff --git a/pcbnew/netlist_reader/netlist.cpp b/pcbnew/netlist_reader/netlist.cpp
index b1b471e915..b0e9bff033 100644
--- a/pcbnew/netlist_reader/netlist.cpp
+++ b/pcbnew/netlist_reader/netlist.cpp
@@ -48,6 +48,7 @@ using namespace std::placeholders;
 #include <tool/tool_manager.h>
 #include <tools/pcb_actions.h>
 #include <tools/selection_tool.h>
+#include <project/project_file.h>  // LAST_PATH_TYPE
 #include <view/view.h>
 
 extern void SpreadFootprints( std::vector<MODULE*>* aFootprints,
diff --git a/pcbnew/pcb_base_frame.cpp b/pcbnew/pcb_base_frame.cpp
index 1968987edf..b7c25418e5 100644
--- a/pcbnew/pcb_base_frame.cpp
+++ b/pcbnew/pcb_base_frame.cpp
@@ -136,7 +136,6 @@ void PCB_BASE_FRAME::SetBoard( BOARD* aBoard )
     {
         delete m_Pcb;
         m_Pcb = aBoard;
-        m_Pcb->SetGeneralSettings( m_Settings );
 
         wxCommandEvent e( BOARD_CHANGED );
         ProcessEventLocally( e );
@@ -277,13 +276,6 @@ BOARD_DESIGN_SETTINGS& PCB_BASE_FRAME::GetDesignSettings() const
 }
 
 
-void PCB_BASE_FRAME::SetDesignSettings( const BOARD_DESIGN_SETTINGS& aSettings )
-{
-    wxASSERT( m_Pcb );
-    m_Pcb->SetDesignSettings( aSettings );
-}
-
-
 void PCB_BASE_FRAME::SetDrawBgColor( COLOR4D aColor )
 {
     m_drawBgColor= aColor;
@@ -294,14 +286,14 @@ void PCB_BASE_FRAME::SetDrawBgColor( COLOR4D aColor )
 const ZONE_SETTINGS& PCB_BASE_FRAME::GetZoneSettings() const
 {
     wxASSERT( m_Pcb );
-    return m_Pcb->GetZoneSettings();
+    return m_Pcb->GetDesignSettings().GetDefaultZoneSettings();
 }
 
 
 void PCB_BASE_FRAME::SetZoneSettings( const ZONE_SETTINGS& aSettings )
 {
     wxASSERT( m_Pcb );
-    m_Pcb->SetZoneSettings( aSettings );
+    m_Pcb->GetDesignSettings().SetDefaultZoneSettings( aSettings );
 }
 
 
@@ -735,8 +727,6 @@ void PCB_BASE_FRAME::ActivateGalCanvas()
                                        GetCanvas()->GetViewControls(), config(), this );
     }
 
-    SetBoard( m_Pcb );
-
     if( m_toolManager )
         m_toolManager->ResetTools( TOOL_BASE::GAL_SWITCH );
 
diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp
index cb2ada5148..529d555ba6 100644
--- a/pcbnew/pcb_edit_frame.cpp
+++ b/pcbnew/pcb_edit_frame.cpp
@@ -52,6 +52,7 @@
 #include <kicad_string.h>
 #include <pcb_draw_panel_gal.h>
 #include <functional>
+#include <project/project_file.h>
 #include <settings/settings_manager.h>
 #include <tool/tool_manager.h>
 #include <tool/tool_dispatcher.h>
@@ -369,6 +370,9 @@ PCB_EDIT_FRAME::~PCB_EDIT_FRAME()
 
 void PCB_EDIT_FRAME::SetBoard( BOARD* aBoard )
 {
+    if( m_Pcb )
+        m_Pcb->ClearProject();
+
     PCB_BASE_EDIT_FRAME::SetBoard( aBoard );
 
     aBoard->SetProject( &Prj() );
@@ -497,39 +501,42 @@ void PCB_EDIT_FRAME::OnQuit( wxCommandEvent& event )
 
 void PCB_EDIT_FRAME::RecordDRCExclusions()
 {
-    m_drcExclusions.clear();
+    BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
+    bds.m_DrcExclusions.clear();
 
     for( MARKER_PCB* marker : GetBoard()->Markers() )
     {
         if( marker->IsExcluded() )
-            m_drcExclusions.insert( marker->Serialize() );
+            bds.m_DrcExclusions.insert( marker->Serialize() );
     }
 }
 
 
 void PCB_EDIT_FRAME::ResolveDRCExclusions()
 {
+    BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
+
     for( MARKER_PCB* marker : GetBoard()->Markers() )
     {
-        auto i = m_drcExclusions.find( marker->Serialize() );
+        auto i = bds.m_DrcExclusions.find( marker->Serialize() );
 
-        if( i != m_drcExclusions.end() )
+        if( i != bds.m_DrcExclusions.end() )
         {
             marker->SetExcluded( true );
-            m_drcExclusions.erase( i );
+            bds.m_DrcExclusions.erase( i );
         }
     }
 
     BOARD_COMMIT commit( this );
 
-    for( const wxString& exclusionData : m_drcExclusions )
+    for( const wxString& exclusionData : bds.m_DrcExclusions )
     {
         MARKER_PCB* marker = MARKER_PCB::Deserialize( exclusionData );
         marker->SetExcluded( true );
         commit.Add( marker );
     }
 
-    m_drcExclusions.clear();
+    bds.m_DrcExclusions.clear();
 
     commit.Push( wxEmptyString, false, false );
 }
@@ -847,10 +854,12 @@ void PCB_EDIT_FRAME::ShowChangedLanguage()
 
 wxString PCB_EDIT_FRAME::GetLastPath( LAST_PATH_TYPE aType )
 {
-    if( m_lastPath[ aType ].IsEmpty() )
+    PROJECT_FILE& project = Prj().GetProjectFile();
+
+    if( project.m_PcbLastPath[ aType ].IsEmpty() )
         return wxEmptyString;
 
-    wxFileName absoluteFileName = m_lastPath[ aType ];
+    wxFileName absoluteFileName = project.m_PcbLastPath[ aType ];
     wxFileName pcbFileName = GetBoard()->GetFileName();
 
     absoluteFileName.MakeAbsolute( pcbFileName.GetPath() );
@@ -860,14 +869,16 @@ wxString PCB_EDIT_FRAME::GetLastPath( LAST_PATH_TYPE aType )
 
 void PCB_EDIT_FRAME::SetLastPath( LAST_PATH_TYPE aType, const wxString& aLastPath )
 {
+    PROJECT_FILE& project = Prj().GetProjectFile();
+
     wxFileName relativeFileName = aLastPath;
     wxFileName pcbFileName = GetBoard()->GetFileName();
 
     relativeFileName.MakeRelativeTo( pcbFileName.GetPath() );
 
-    if( relativeFileName.GetFullPath() != m_lastPath[ aType ] )
+    if( relativeFileName.GetFullPath() != project.m_PcbLastPath[ aType ] )
     {
-        m_lastPath[ aType ] = relativeFileName.GetFullPath();
+        project.m_PcbLastPath[ aType ] = relativeFileName.GetFullPath();
         SaveProjectSettings();
     }
 }
diff --git a/pcbnew/pcb_edit_frame.h b/pcbnew/pcb_edit_frame.h
index 3c8b5b0f84..6bc0fc099b 100644
--- a/pcbnew/pcb_edit_frame.h
+++ b/pcbnew/pcb_edit_frame.h
@@ -24,7 +24,6 @@
 #include <unordered_map>
 #include <map>
 #include "pcb_base_edit_frame.h"
-#include "config_params.h"
 #include "undo_redo_container.h"
 #include "zones.h"
 
@@ -60,6 +59,7 @@ class IO_ERROR;
 class FP_LIB_TABLE;
 class BOARD_NETLIST_UPDATER;
 class ACTION_MENU;
+enum LAST_PATH_TYPE : unsigned int;
 
 namespace PCB { struct IFACE; }     // KIFACE_I is in pcbnew.cpp
 
@@ -73,18 +73,6 @@ enum TRACK_ACTION_RESULT
     TRACK_ACTION_NONE           //!< TRACK_ACTION_NONE - Nothing to change
 };
 
-enum LAST_PATH_TYPE
-{
-    LAST_PATH_NETLIST = 0,
-    LAST_PATH_STEP,
-    LAST_PATH_IDF,
-    LAST_PATH_VRML,
-    LAST_PATH_SPECCTRADSN,
-    LAST_PATH_GENCAD,
-
-    LAST_PATH_SIZE
-};
-
 /**
  * PCB_EDIT_FRAME
  * is the main frame for Pcbnew.
@@ -102,11 +90,6 @@ class PCB_EDIT_FRAME : public PCB_BASE_EDIT_FRAME
     ACTION_TOOLBAR*         m_microWaveToolBar;
 
 protected:
-    std::vector<PARAM_CFG*> m_projectFileParams;
-
-    wxString                m_lastPath[ LAST_PATH_SIZE ];
-
-    std::set<wxString>      m_drcExclusions;
 
     /**
      * Store the previous layer toolbar icon state information
@@ -377,20 +360,6 @@ public:
 
 #endif
 
-    /**
-     * Function GetProjectFileParameters
-     * returns a project file parameter list for Pcbnew.
-     * <p>
-     * Populate a project file parameter array specific to Pcbnew.
-     * Creating the parameter list at run time has the advantage of being able
-     * to define local variables.  The old method of statically building the array
-     * at compile time requiring global variable definitions by design.
-     * </p>
-     * @return std::vector<PARAM_CFG*> - it is only good until SetBoard() is called, so
-     *         don't keep it around past that event.
-     */
-    std::vector<PARAM_CFG*>& GetProjectFileParameters();
-
     /**
      * Function SaveProjectSettings
      * saves changes to the project settings to the project (.pro) file.
diff --git a/pcbnew/pcb_layer_widget.cpp b/pcbnew/pcb_layer_widget.cpp
index 727e020d05..c0a38c17af 100644
--- a/pcbnew/pcb_layer_widget.cpp
+++ b/pcbnew/pcb_layer_widget.cpp
@@ -331,7 +331,6 @@ void PCB_LAYER_WIDGET::SetLayersManagerTabsText()
 void PCB_LAYER_WIDGET::ReFillRender()
 {
     BOARD* board = myframe->GetBoard();
-    auto settings = board->GetDesignSettings();
 
     ClearRenderRows();
 
@@ -346,17 +345,6 @@ void PCB_LAYER_WIDGET::ReFillRender()
         if( m_fp_editor_mode && !isAllowedInFpMode( renderRow.id ) )
             continue;
 
-        // Don't remove microvia and bblind vias if they're not allowed: that's only a DRC
-        // setting (which might be set to ignore) and the user can add them irrespective of
-        // the setting.
-        /*
-        if( renderRow.id == LAYER_VIA_MICROVIA && !settings.m_MicroViasAllowed )
-            continue;
-
-        if( renderRow.id == LAYER_VIA_BBLIND && !settings.m_BlindBuriedViaAllowed )
-            continue;
-        */
-
         if( !renderRow.spacer )
         {
             renderRow.tooltip = wxGetTranslation( s_render_rows[row].tooltip );
@@ -640,10 +628,6 @@ void PCB_LAYER_WIDGET::OnLayerVisible( int aLayer, bool isVisible, bool isFinal
 
         brd->SetVisibleLayers( visibleLayers );
 
-        // Layer visibility is not stored in .kicad_mod files
-        if( !m_fp_editor_mode )
-            myframe->OnModify();
-
         if( myframe->GetCanvas() )
             myframe->GetCanvas()->GetView()->SetLayerVisible( aLayer, isVisible );
     }
@@ -685,14 +669,6 @@ void PCB_LAYER_WIDGET::OnRenderEnable( int aId, bool isEnabled )
     BOARD*  brd = myframe->GetBoard();
     wxASSERT( aId > GAL_LAYER_ID_START && aId < GAL_LAYER_ID_END );
 
-    if( myframe->IsType( FRAME_PCB_EDITOR ) )
-    {
-        // The layer visibility status is saved in the board file so set the board
-        // modified state so the user has the option to save the changes.
-        if( brd->IsElementVisible( static_cast<GAL_LAYER_ID>( aId ) ) != isEnabled )
-            myframe->OnModify();
-    }
-
     // Grid is not set through the board visibility
     if( aId == LAYER_GRID )
         myframe->SetGridVisibility( isEnabled );
diff --git a/pcbnew/pcb_parser.cpp b/pcbnew/pcb_parser.cpp
index e1049d5142..f940e2f92c 100644
--- a/pcbnew/pcb_parser.cpp
+++ b/pcbnew/pcb_parser.cpp
@@ -577,6 +577,7 @@ BOARD* PCB_PARSER::parseBOARD_unchecked()
 
         case T_net_class:
             parseNETCLASS();
+            m_board->m_LegacyNetclassesLoaded = true;
             break;
 
         case T_gr_arc:
@@ -1253,6 +1254,7 @@ void PCB_PARSER::parseLayers()
     LSET    enabledLayers;
     int     copperLayerCount = 0;
     LAYER   layer;
+    bool    anyHidden = false;
 
     std::unordered_map< std::string, std::string > v3_layer_names;
     std::vector<LAYER>  cu;
@@ -1292,6 +1294,8 @@ void PCB_PARSER::parseLayers()
 
             if( it->m_visible )
                 visibleLayers.set( it->m_number );
+            else
+                anyHidden = true;
 
             m_board->SetLayerDescr( PCB_LAYER_ID( it->m_number ), *it );
 
@@ -1341,6 +1345,8 @@ void PCB_PARSER::parseLayers()
 
         if( layer.m_visible )
             visibleLayers.set( layer.m_number );
+        else
+            anyHidden = true;
 
         m_board->SetLayerDescr( it->second, layer );
 
@@ -1364,8 +1370,10 @@ void PCB_PARSER::parseLayers()
     m_board->SetCopperLayerCount( copperLayerCount );
     m_board->SetEnabledLayers( enabledLayers );
 
-    // call SetEnabledLayers before SetVisibleLayers()
-    m_board->SetVisibleLayers( visibleLayers );
+    // Only set this if any layers were explicitly marked as hidden.  Otherwise, we want to leave
+    // this alone; default visibility will show everything
+    if( anyHidden )
+        m_board->m_LegacyVisibleLayers = visibleLayers;
 }
 
 
@@ -1433,9 +1441,9 @@ void PCB_PARSER::parseSetup()
                  wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as setup." ) );
 
     T token;
-    NETCLASS* defaultNetClass = m_board->GetDesignSettings().GetDefault();
-    BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
-    ZONE_SETTINGS zoneSettings = m_board->GetZoneSettings();
+    NETCLASS*              defaultNetClass = m_board->GetDesignSettings().GetDefault();
+    BOARD_DESIGN_SETTINGS& designSettings  = m_board->GetDesignSettings();
+    ZONE_SETTINGS&         zoneSettings    = designSettings.GetDefaultZoneSettings();
 
     // Missing soldermask min width value means that the user has set the value to 0 and
     // not the default value (0.25mm)
@@ -1461,67 +1469,80 @@ void PCB_PARSER::parseSetup()
 
         case T_user_trace_width:
             designSettings.m_TrackWidthList.push_back( parseBoardUnits( T_user_trace_width ) );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_trace_clearance:
             defaultNetClass->SetClearance( parseBoardUnits( T_trace_clearance ) );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_zone_clearance:
             zoneSettings.m_ZoneClearance = parseBoardUnits( T_zone_clearance );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_zone_45_only:
             zoneSettings.m_Zone_45_Only = parseBool();
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_clearance_min:
             designSettings.m_MinClearance = parseBoardUnits( T_clearance_min );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_trace_min:
             designSettings.m_TrackMinWidth = parseBoardUnits( T_trace_min );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_via_size:
             defaultNetClass->SetViaDiameter( parseBoardUnits( T_via_size ) );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_via_drill:
             defaultNetClass->SetViaDrill( parseBoardUnits( T_via_drill ) );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_via_min_annulus:
             designSettings.m_ViasMinAnnulus = parseBoardUnits( T_via_min_annulus );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_via_min_size:
             designSettings.m_ViasMinSize = parseBoardUnits( T_via_min_size );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_through_hole_min:
             designSettings.m_MinThroughDrill = parseBoardUnits( T_through_hole_min );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         // Legacy token for T_through_hole_min
         case T_via_min_drill:
             designSettings.m_MinThroughDrill = parseBoardUnits( T_via_min_drill );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_hole_to_hole_min:
             designSettings.m_HoleToHoleMin = parseBoardUnits( T_hole_to_hole_min );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
@@ -1530,37 +1551,44 @@ void PCB_PARSER::parseSetup()
                 int viaSize = parseBoardUnits( "user via size" );
                 int viaDrill = parseBoardUnits( "user via drill" );
                 designSettings.m_ViasDimensionsList.emplace_back( VIA_DIMENSION( viaSize, viaDrill ) );
+                m_board->m_LegacyDesignSettingsLoaded = true;
                 NeedRIGHT();
             }
             break;
 
         case T_uvia_size:
             defaultNetClass->SetuViaDiameter( parseBoardUnits( T_uvia_size ) );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_uvia_drill:
             defaultNetClass->SetuViaDrill( parseBoardUnits( T_uvia_drill ) );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_uvias_allowed:
             designSettings.m_MicroViasAllowed = parseBool();
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_blind_buried_vias_allowed:
             designSettings.m_BlindBuriedViaAllowed = parseBool();
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_uvia_min_size:
             designSettings.m_MicroViasMinSize = parseBoardUnits( T_uvia_min_size );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_uvia_min_drill:
             designSettings.m_MicroViasMinDrill = parseBoardUnits( T_uvia_min_drill );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
@@ -1570,49 +1598,58 @@ void PCB_PARSER::parseSetup()
                 int gap = parseBoardUnits( "user diff-pair gap" );
                 int viaGap = parseBoardUnits( "user diff-pair via gap" );
                 designSettings.m_DiffPairDimensionsList.emplace_back( DIFF_PAIR_DIMENSION( width, gap, viaGap ) );
+                m_board->m_LegacyDesignSettingsLoaded = true;
                 NeedRIGHT();
             }
             break;
 
         case T_segment_width:   // note: legacy (pre-6.0) token
             designSettings.m_LineThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( T_segment_width );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_edge_width:      // note: legacy (pre-6.0) token
             designSettings.m_LineThickness[ LAYER_CLASS_EDGES ] = parseBoardUnits( T_edge_width );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_mod_edge_width:  // note: legacy (pre-6.0) token
             designSettings.m_LineThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( T_mod_edge_width );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_pcb_text_width:  // note: legacy (pre-6.0) token
             designSettings.m_TextThickness[ LAYER_CLASS_COPPER ] = parseBoardUnits( T_pcb_text_width );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_mod_text_width:  // note: legacy (pre-6.0) token
             designSettings.m_TextThickness[ LAYER_CLASS_SILK ] = parseBoardUnits( T_mod_text_width );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_pcb_text_size:   // note: legacy (pre-6.0) token
             designSettings.m_TextSize[ LAYER_CLASS_COPPER ].x = parseBoardUnits( "pcb text width" );
             designSettings.m_TextSize[ LAYER_CLASS_COPPER ].y = parseBoardUnits( "pcb text height" );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_mod_text_size:   // note: legacy (pre-6.0) token
             designSettings.m_TextSize[ LAYER_CLASS_SILK ].x = parseBoardUnits( "module text width" );
             designSettings.m_TextSize[ LAYER_CLASS_SILK ].y = parseBoardUnits( "module text height" );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_defaults:
             parseDefaults( designSettings );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             break;
 
         case T_pad_size:
@@ -1621,6 +1658,7 @@ void PCB_PARSER::parseSetup()
                 sz.SetWidth( parseBoardUnits( "master pad width" ) );
                 sz.SetHeight( parseBoardUnits( "master pad height" ) );
                 designSettings.m_Pad_Master.SetSize( sz );
+                m_board->m_LegacyDesignSettingsLoaded = true;
                 NeedRIGHT();
             }
             break;
@@ -1629,27 +1667,32 @@ void PCB_PARSER::parseSetup()
             {
                 int drillSize = parseBoardUnits( T_pad_drill );
                 designSettings.m_Pad_Master.SetDrillSize( wxSize( drillSize, drillSize ) );
+                m_board->m_LegacyDesignSettingsLoaded = true;
                 NeedRIGHT();
             }
             break;
 
         case T_pad_to_mask_clearance:
             designSettings.m_SolderMaskMargin = parseBoardUnits( T_pad_to_mask_clearance );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_solder_mask_min_width:
             designSettings.m_SolderMaskMinWidth = parseBoardUnits( T_solder_mask_min_width );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_pad_to_paste_clearance:
             designSettings.m_SolderPasteMargin = parseBoardUnits( T_pad_to_paste_clearance );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_pad_to_paste_clearance_ratio:
             designSettings.m_SolderPasteMarginRatio = parseDouble( T_pad_to_paste_clearance_ratio );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
@@ -1657,8 +1700,9 @@ void PCB_PARSER::parseSetup()
             {
                 int x = parseBoardUnits( "auxiliary origin X" );
                 int y = parseBoardUnits( "auxiliary origin Y" );
-                // m_board->SetAuxOrigin( wxPoint( x, y ) );    gets overwritten via SetDesignSettings below
                 designSettings.m_AuxOrigin = wxPoint( x, y );
+                // Aux origin still stored in board for the moment
+                //m_board->m_LegacyDesignSettingsLoaded = true;
                 NeedRIGHT();
             }
             break;
@@ -1667,24 +1711,36 @@ void PCB_PARSER::parseSetup()
             {
                 int x = parseBoardUnits( "grid origin X" );
                 int y = parseBoardUnits( "grid origin Y" );
-                // m_board->SetGridOrigin( wxPoint( x, y ) );   gets overwritten SetDesignSettings below
                 designSettings.m_GridOrigin = wxPoint( x, y );
+                // Grid origin still stored in board for the moment
+                //m_board->m_LegacyDesignSettingsLoaded = true;
                 NeedRIGHT();
             }
             break;
 
+        // Stored in board prior to 6.0
         case T_visible_elements:
-            designSettings.SetVisibleElements( parseHex() | MIN_VISIBILITY_MASK );
-            NeedRIGHT();
+            {
+                m_board->m_LegacyVisibleItems.reset();
+
+                int visible = parseHex() | MIN_VISIBILITY_MASK;
+
+                for( size_t i = 0; i < sizeof( int ) * CHAR_BIT; i++ )
+                    m_board->m_LegacyVisibleItems.set( i, visible & ( 1u << i ) );
+
+                NeedRIGHT();
+            }
             break;
 
         case T_max_error:
             designSettings.m_MaxError = parseBoardUnits( T_max_error );
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
         case T_filled_areas_thickness:
             designSettings.m_ZoneUseNoOutlineInFill = not parseBool();
+            m_board->m_LegacyDesignSettingsLoaded = true;
             NeedRIGHT();
             break;
 
@@ -1707,9 +1763,6 @@ void PCB_PARSER::parseSetup()
             Unexpected( CurText() );
         }
     }
-
-    //m_board->SetDesignSettings( designSettings );
-    m_board->SetZoneSettings( zoneSettings );
 }
 
 
@@ -1925,7 +1978,7 @@ void PCB_PARSER::parseNETCLASS()
         NeedRIGHT();
     }
 
-    if( !m_board->GetDesignSettings().m_NetClasses.Add( nc ) )
+    if( !m_board->GetDesignSettings().GetNetClasses().Add( nc ) )
     {
         // Must have been a name conflict, this is a bad board file.
         // User may have done a hand edit to the file.
diff --git a/pcbnew/pcbnew_config.cpp b/pcbnew/pcbnew_config.cpp
index 9d3d115353..30160ca175 100644
--- a/pcbnew/pcbnew_config.cpp
+++ b/pcbnew/pcbnew_config.cpp
@@ -46,6 +46,7 @@
 #include <invoke_pcb_dialog.h>
 #include <wildcards_and_files_ext.h>
 #include <widgets/paged_dialog.h>
+#include <project/project_file.h>
 
 
 void PCB_EDIT_FRAME::On3DShapeLibWizard( wxCommandEvent& event )
@@ -77,7 +78,9 @@ bool PCB_EDIT_FRAME::LoadProjectSettings()
 {
     wxLogDebug( wxT( "Loading project '%s' settings." ), GetChars( Prj().GetProjectFullName() ) );
 
-    bool rc = Prj().ConfigLoad( Kiface().KifaceSearch(), GROUP_PCB, GetProjectFileParameters() );
+    PROJECT_FILE& project = Prj().GetProjectFile();
+
+    BASE_SCREEN::m_PageLayoutDescrFileName = project.m_PageLayoutDescrFile;
 
     // Load the page layout decr file, from the filename stored in
     // BASE_SCREEN::m_PageLayoutDescrFileName, read in config project file
@@ -88,7 +91,7 @@ bool PCB_EDIT_FRAME::LoadProjectSettings()
 
     pglayout.SetPageLayout( filename );
 
-    return rc;
+    return true;
 }
 
 
@@ -105,45 +108,12 @@ void PCB_EDIT_FRAME::SaveProjectSettings()
     if( !IsWritable( fn ) )
         return;
 
-    wxString pro_name = fn.GetFullPath();
+    PROJECT_FILE& project = Prj().GetProjectFile();
+
+    // TODO: Can this be pulled out of BASE_SCREEN?
+    project.m_PageLayoutDescrFile = BASE_SCREEN::m_PageLayoutDescrFileName;
 
     RecordDRCExclusions();
-    Prj().ConfigSave( Kiface().KifaceSearch(), GROUP_PCB, GetProjectFileParameters(), pro_name );
-}
-
-
-std::vector<PARAM_CFG*>& PCB_EDIT_FRAME::GetProjectFileParameters()
-{
-    m_projectFileParams.clear();
-
-    // This one cannot be cached because some settings are going to/from the BOARD,
-    // so pointers into that cannot be saved for long.
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "PageLayoutDescrFile" ),
-                                                           &BASE_SCREEN::m_PageLayoutDescrFileName ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "LastNetListRead" ),
-                                                           &m_lastPath[ LAST_PATH_NETLIST ] ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "LastSTEPExportPath" ),
-                                                           &m_lastPath[ LAST_PATH_STEP ] ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "LastIDFExportPath" ),
-                                                           &m_lastPath[ LAST_PATH_IDF ] ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "LastVRMLExportPath" ),
-                                                           &m_lastPath[ LAST_PATH_VRML ] ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "LastSpecctraDSNExportPath" ),
-                                                           &m_lastPath[ LAST_PATH_SPECCTRADSN ] ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_FILENAME( wxT( "LastGenCADExportPath" ),
-                                                           &m_lastPath[ LAST_PATH_GENCAD ] ) );
-
-    m_projectFileParams.push_back( new PARAM_CFG_WXSTRING_SET( wxT( "DRCExclusion" ),
-                                                               &m_drcExclusions ) );
-
-    GetBoard()->GetDesignSettings().AppendConfigs( GetBoard(), &m_projectFileParams);
-
-    return m_projectFileParams;
+
+    GetSettingsManager()->SaveProject();
 }
diff --git a/pcbnew/router/pns_kicad_iface.cpp b/pcbnew/router/pns_kicad_iface.cpp
index 1ebeac416c..c7188ed945 100644
--- a/pcbnew/router/pns_kicad_iface.cpp
+++ b/pcbnew/router/pns_kicad_iface.cpp
@@ -126,7 +126,7 @@ PNS_PCBNEW_RULE_RESOLVER::PNS_PCBNEW_RULE_RESOLVER( BOARD* aBoard, PNS::ROUTER*
         ent.coupledNet = DpCoupledNet( i );
 
         wxString netClassName = ni->GetClassName();
-        NETCLASSPTR nc = m_board->GetDesignSettings().m_NetClasses.Find( netClassName );
+        NETCLASSPTR nc = m_board->GetDesignSettings().GetNetClasses().Find( netClassName );
 
         int clearance = nc->GetClearance();
         ent.clearance = clearance;
@@ -154,7 +154,7 @@ PNS_PCBNEW_RULE_RESOLVER::PNS_PCBNEW_RULE_RESOLVER( BOARD* aBoard, PNS::ROUTER*
         }
     }
 
-    auto defaultRule = m_board->GetDesignSettings().m_NetClasses.Find ("Default");
+    auto defaultRule = m_board->GetDesignSettings().GetNetClasses().Find ("Default");
 
     if( defaultRule )
     {
diff --git a/pcbnew/router/pns_sizes_settings.cpp b/pcbnew/router/pns_sizes_settings.cpp
index b7286374ad..b86f0ac401 100644
--- a/pcbnew/router/pns_sizes_settings.cpp
+++ b/pcbnew/router/pns_sizes_settings.cpp
@@ -89,12 +89,12 @@ void SIZES_SETTINGS::Init( BOARD* aBoard, ITEM* aStartItem, int aNet )
         if( ni )
         {
             wxString netClassName = ni->GetClassName();
-            netClass = bds.m_NetClasses.Find( netClassName );
+            netClass = bds.GetNetClasses().Find( netClassName );
         }
     }
 
     if( !netClass )
-        netClass = bds.m_NetClasses.GetDefault();
+        netClass = bds.GetNetClasses().GetDefault();
 
     m_trackWidth = 0;
 
diff --git a/pcbnew/specctra_import_export/specctra_export.cpp b/pcbnew/specctra_import_export/specctra_export.cpp
index 9ff39dfd42..cac1f74f3e 100644
--- a/pcbnew/specctra_import_export/specctra_export.cpp
+++ b/pcbnew/specctra_import_export/specctra_export.cpp
@@ -1430,7 +1430,7 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
 
     //-----< output vias used in netclasses >-----------------------------------
     {
-        NETCLASSES& nclasses = aBoard->GetDesignSettings().m_NetClasses;
+        NETCLASSES& nclasses = aBoard->GetDesignSettings().GetNetClasses();
 
         // Assume the netclass vias are all the same kind of thru, blind, or buried vias.
         // This is in lieu of either having each netclass via have its own layer pair in
@@ -1641,7 +1641,7 @@ void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
 
 
     //-----<output NETCLASSs>----------------------------------------------------
-    NETCLASSES& nclasses = aBoard->GetDesignSettings().m_NetClasses;
+    NETCLASSES& nclasses = aBoard->GetDesignSettings().GetNetClasses();
 
     exportNETCLASS( nclasses.GetDefault(), aBoard );
 
diff --git a/pcbnew/specctra_import_export/specctra_import.cpp b/pcbnew/specctra_import_export/specctra_import.cpp
index 39916834aa..bbb4cb4b9f 100644
--- a/pcbnew/specctra_import_export/specctra_import.cpp
+++ b/pcbnew/specctra_import_export/specctra_import.cpp
@@ -499,7 +499,7 @@ void SPECCTRA_DB::FromSESSION( BOARD* aBoard )
                                                   GetChars( psid ) ) );
             }
 
-            NETCLASSPTR netclass = aBoard->GetDesignSettings().m_NetClasses.GetDefault();
+            NETCLASSPTR netclass = aBoard->GetDesignSettings().GetNetClasses().GetDefault();
 
             int via_drill_default = netclass->GetViaDrill();
 
diff --git a/pcbnew/swig/board.i b/pcbnew/swig/board.i
index 3bdc1e610c..c0742c2c5c 100644
--- a/pcbnew/swig/board.i
+++ b/pcbnew/swig/board.i
@@ -96,11 +96,16 @@ HANDLE_EXCEPTIONS(BOARD::TracksInNetBetweenPoints)
 }
 %{
 #include <layers_id_colors_and_visibility.h>
+#include <pcbnew_scripting_helpers.h>
 %}
 
 
 // std::vector templates
 %template(VIA_DIMENSION_Vector) std::vector<VIA_DIMENSION>;
+
+// Do not permit default BOARD ctor since it won't initialize the project
+%ignore BOARD::BOARD();
+
 %include class_board.h
 %{
 #include <class_board.h>
@@ -123,11 +128,25 @@ HANDLE_EXCEPTIONS(BOARD::TracksInNetBetweenPoints)
 
 %extend BOARD
 {
+    // NOTE: this does not generate a ctor, despite swig docs saying it should.  Not sure why.
+    // Because of this, we use the __init__ override hack below.
+    // BOARD()
+    // {
+    //     return CreateEmptyBoard();
+    // }
+
     // BOARD_ITEM_CONTAINER's interface functions will be implemented by SWIG
     // automatically and inherited by the python wrapper class.
 
     %pythoncode
     %{
+    def __init__(self, *args):
+        this = CreateEmptyBoard()
+
+        try:
+            self.this.append(this)
+        except:
+            self.this = this
 
     def GetModules(self):             return self.Modules()
     def GetDrawings(self):            return self.Drawings()
diff --git a/pcbnew/swig/pcbnew_scripting_helpers.cpp b/pcbnew/swig/pcbnew_scripting_helpers.cpp
index 9e846eccff..e5c3daf3cb 100644
--- a/pcbnew/swig/pcbnew_scripting_helpers.cpp
+++ b/pcbnew/swig/pcbnew_scripting_helpers.cpp
@@ -42,9 +42,19 @@
 #include <pcbnew.h>
 #include <pcbnew_scripting_helpers.h>
 #include <project.h>
+#include <settings/settings_manager.h>
+#include <wildcards_and_files_ext.h>
 
 static PCB_EDIT_FRAME* s_PcbEditFrame = NULL;
 
+/**
+ * We need to track the loaded PROJECTs for each loaded BOARD here, since in Python you can
+ * easily load more than one board if desired.
+ */
+static std::map<wxString, PROJECT*> s_Projects;
+
+static SETTINGS_MANAGER* s_SettingsManager = nullptr;
+
 BOARD* GetBoard()
 {
     if( s_PcbEditFrame )
@@ -73,17 +83,72 @@ BOARD* LoadBoard( wxString& aFileName )
 }
 
 
+SETTINGS_MANAGER* GetSettingsManager()
+{
+    if( !s_SettingsManager )
+        s_SettingsManager = new SETTINGS_MANAGER;
+
+    return s_SettingsManager;
+}
+
+
+PROJECT* GetDefaultProject()
+{
+    PROJECT* project = nullptr;
+
+    if( s_Projects.count( "" ) )
+        project = s_Projects.at( "" );
+    else
+    {
+        GetSettingsManager()->LoadProject( "" );
+        project = GetSettingsManager()->GetProject( "" );
+        s_Projects[""] = project;
+    }
+
+    return project;
+}
+
+
 BOARD* LoadBoard( wxString& aFileName, IO_MGR::PCB_FILE_T aFormat )
 {
+    wxFileName pro = aFileName;
+    pro.SetExt( ProjectFileExtension );
+    pro.MakeAbsolute();
+    wxString projectPath = pro.GetFullPath();
+
+    PROJECT* project = nullptr;
+
+    if( s_Projects.count( projectPath ) )
+        project = s_Projects.at( projectPath );
+    else if( GetSettingsManager()->LoadProject( projectPath ) )
+    {
+        project = GetSettingsManager()->GetProject( projectPath );
+        s_Projects[projectPath] = project;
+    }
+
+    // Board cannot be loaded without a project, so create the default project
+    if( !project )
+        project = GetDefaultProject();
+
     BOARD* brd = IO_MGR::Load( aFormat, aFileName );
 
     if( brd )
     {
+        brd->SetProject( project );
         brd->BuildConnectivity();
         brd->BuildListOfNets();
         brd->SynchronizeNetsAndNetClasses();
     }
 
+    return brd;
+}
+
+
+BOARD* CreateEmptyBoard()
+{
+    BOARD* brd = new BOARD();
+
+    brd->SetProject( GetDefaultProject() );
 
     return brd;
 }
@@ -97,6 +162,13 @@ bool SaveBoard( wxString& aFileName, BOARD* aBoard, IO_MGR::PCB_FILE_T aFormat )
 
     IO_MGR::Save( aFormat, aFileName, aBoard, NULL );
 
+    wxFileName pro = aFileName;
+    pro.SetExt( ProjectFileExtension );
+    pro.MakeAbsolute();
+    wxString projectPath = pro.GetFullPath();
+
+    GetSettingsManager()->SaveProject( pro.GetFullPath() );
+
     return true;
 }
 
diff --git a/pcbnew/swig/pcbnew_scripting_helpers.h b/pcbnew/swig/pcbnew_scripting_helpers.h
index 2c341943a6..2379312add 100644
--- a/pcbnew/swig/pcbnew_scripting_helpers.h
+++ b/pcbnew/swig/pcbnew_scripting_helpers.h
@@ -45,6 +45,12 @@ BOARD*  LoadBoard( wxString& aFileName, IO_MGR::PCB_FILE_T aFormat );
 // Default LoadBoard() to load .kicad_pcb files:.
 BOARD*  LoadBoard( wxString& aFileName );
 
+/**
+ * Constructs a default BOARD with a tempoary (no filename) project
+ * @return the created board
+ */
+BOARD* CreateEmptyBoard();
+
 // Boards can be saved only as .kicad_pcb file format,
 // so no option to choose the file format.
 bool    SaveBoard( wxString& aFileName, BOARD* aBoard );
diff --git a/pcbnew/tools/pcb_editor_control.cpp b/pcbnew/tools/pcb_editor_control.cpp
index cc08b8e953..d4c16c0017 100644
--- a/pcbnew/tools/pcb_editor_control.cpp
+++ b/pcbnew/tools/pcb_editor_control.cpp
@@ -32,8 +32,8 @@
 #include <board_commit.h>
 #include <class_board.h>
 #include <class_module.h>
-#include <class_track.h>
 #include <class_pcb_target.h>
+#include <class_track.h>
 #include <class_zone.h>
 #include <collectors.h>
 #include <confirm.h>
@@ -52,6 +52,7 @@
 #include <pcbnew_id.h>
 #include <pcbnew_settings.h>
 #include <project.h>
+#include <project/project_file.h> // LAST_PATH_TYPE
 #include <tool/tool_manager.h>
 #include <tools/tool_event_utils.h>
 #include <view/view_controls.h>
diff --git a/pcbnew/tools/pcbnew_control.cpp b/pcbnew/tools/pcbnew_control.cpp
index b4701b1682..40df69ea47 100644
--- a/pcbnew/tools/pcbnew_control.cpp
+++ b/pcbnew/tools/pcbnew_control.cpp
@@ -883,7 +883,7 @@ int PCBNEW_CONTROL::AppendBoard( PLUGIN& pi, wxString& fileName )
         props["page_width"]  = xbuf;
         props["page_height"] = ybuf;
 
-        editFrame->GetDesignSettings().m_NetClasses.Clear();
+        editFrame->GetDesignSettings().GetNetClasses().Clear();
         pi.Load( fileName, brd, &props );
     }
     catch( const IO_ERROR& ioe )
diff --git a/pcbnew/zone_settings.h b/pcbnew/zone_settings.h
index ba492884b4..d39023f150 100644
--- a/pcbnew/zone_settings.h
+++ b/pcbnew/zone_settings.h
@@ -32,7 +32,8 @@
 
 #include <layers_id_colors_and_visibility.h>
 #include <zones.h>
-#include <wx/dataview.h>
+
+class wxDataViewListCtrl;
 
 enum class ZONE_FILL_MODE
 {
diff --git a/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp b/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp
index df2827dc3a..64038d6d55 100644
--- a/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp
+++ b/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp
@@ -228,25 +228,6 @@ std::unique_ptr<BOARD> MakeBoard( const std::vector<COURTYARD_INVALID_TEST_MODUL
 }
 
 
-/**
- * Get a #BOARD_DESIGN_SETTINGS object that will cause DRC to
- * check for courtyard invalidity
- */
-static BOARD_DESIGN_SETTINGS GetOverlapCheckDesignSettings()
-{
-    BOARD_DESIGN_SETTINGS des_settings;
-
-    // do the overlap tests - that's a different test, but if not set,
-    // the invalid courtyard checks don't run either
-    des_settings.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
-
-    // we will also check for missing courtyards here
-    des_settings.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_ERROR;
-
-    return des_settings;
-}
-
-
 /**
  * Check if a #MARKER_PCB is described by a particular #COURTYARD_INVALID_INFO object.
  */
@@ -301,7 +282,14 @@ void DoCourtyardInvalidTest( const COURTYARD_INVALID_CASE& aCase,
     // Dump if env var set
     aDumper.DumpBoardToFile( *board, aCase.m_case_name );
 
-    board->SetDesignSettings( GetOverlapCheckDesignSettings() );
+    BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
+
+    // do the overlap tests - that's a different test, but if not set,
+    // the invalid courtyard checks don't run either
+    bds.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
+
+    // we will also check for missing courtyards here
+    bds.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_ERROR;
 
     // list of markers to collect
     std::vector<std::unique_ptr<MARKER_PCB>> markers;
diff --git a/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp b/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp
index f8b52f836a..8f3e829e60 100644
--- a/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp
+++ b/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp
@@ -441,21 +441,6 @@ static void CheckCollisionsMatchExpected( BOARD& aBoard,
 }
 
 
-/**
- * Get a #BOARD_DESIGN_SETTINGS object that will cause DRC to check for courtyard overlaps
- */
-static BOARD_DESIGN_SETTINGS GetOverlapCheckDesignSettings()
-{
-    BOARD_DESIGN_SETTINGS des_settings;
-    des_settings.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
-
-    // we might not always have courtyards - that's a separate test
-    des_settings.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_IGNORE;
-
-    return des_settings;
-}
-
-
 /**
  * Run a single courtyard overlap testcase
  * @param aCase The testcase to run.
@@ -468,7 +453,12 @@ static void DoCourtyardOverlapTest( const COURTYARD_OVERLAP_TEST_CASE& aCase,
     // Dump if env var set
     aDumper.DumpBoardToFile( *board, aCase.m_case_name );
 
-    board->SetDesignSettings( GetOverlapCheckDesignSettings() );
+    BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
+
+    bds.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
+
+    // we might not always have courtyards - that's a separate test
+    bds.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_IGNORE;
 
     // list of markers to collect
     std::vector<std::unique_ptr<MARKER_PCB>> markers;
diff --git a/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp b/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp
index 8450f868eb..1ec499f537 100644
--- a/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp
+++ b/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp
@@ -69,7 +69,7 @@ public:
         if( m_exec_context.m_verbose )
             std::cout << "Running DRC check: " << getRunnerIntro() << std::endl;
 
-        aBoard.SetDesignSettings( getDesignSettings() );
+        setDesignSettings( aBoard.GetDesignSettings() );
 
         std::vector<std::unique_ptr<MARKER_PCB>> markers;
 
@@ -101,9 +101,10 @@ private:
     virtual std::string getRunnerIntro() const = 0;
 
     /**
-     * Get suitable design settings for this DRC runner
+     * Set suitable design settings for this DRC runner
+     * @param aSettings is a reference to the design settings object of the board under test
      */
-    virtual BOARD_DESIGN_SETTINGS getDesignSettings() const = 0;
+    virtual void setDesignSettings( BOARD_DESIGN_SETTINGS& aSettings ) const = 0;
 
     virtual std::unique_ptr<DRC_TEST_PROVIDER> createDrcProvider(
             BOARD& aBoard, DRC_TEST_PROVIDER::MARKER_HANDLER aHandler ) = 0;
@@ -157,13 +158,10 @@ private:
         return "Courtyard overlap";
     }
 
-    BOARD_DESIGN_SETTINGS getDesignSettings() const override
+    void setDesignSettings( BOARD_DESIGN_SETTINGS& aSettings ) const override
     {
-        BOARD_DESIGN_SETTINGS des_settings;
-        des_settings.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_IGNORE;
-        des_settings.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
-
-        return des_settings;
+        aSettings.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_IGNORE;
+        aSettings.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
     }
 
     std::unique_ptr<DRC_TEST_PROVIDER> createDrcProvider(
@@ -194,13 +192,10 @@ private:
         return "Courtyard missing";
     }
 
-    BOARD_DESIGN_SETTINGS getDesignSettings() const override
+    void setDesignSettings( BOARD_DESIGN_SETTINGS& aSettings ) const override
     {
-        BOARD_DESIGN_SETTINGS des_settings;
-        des_settings.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_ERROR;
-        des_settings.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_IGNORE;
-
-        return des_settings;
+        aSettings.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_ERROR;
+        aSettings.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_IGNORE;
     }
 
     std::unique_ptr<DRC_TEST_PROVIDER> createDrcProvider(