/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 CERN * @author Jon Evans * * 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 . */ #include #include #include #include #include #include #include #include #include extern const char* traceSettings; ///! Update the schema version whenever a migration is required const int projectFileSchemaVersion = 1; PROJECT_FILE::PROJECT_FILE( const wxString& aFullPath ) : JSON_SETTINGS( aFullPath, SETTINGS_LOC::PROJECT, projectFileSchemaVersion ), m_sheets(), m_boards(), m_project( nullptr ), m_ErcSettings( nullptr ), m_SchematicSettings( nullptr ), m_BoardSettings() { // Keep old files around m_deleteLegacyAfterMigration = false; m_params.emplace_back( new PARAM_LIST( "sheets", &m_sheets, {} ) ); m_params.emplace_back( new PARAM_LIST( "boards", &m_boards, {} ) ); m_params.emplace_back( new PARAM_WXSTRING_MAP( "text_variables", &m_TextVars, {} ) ); m_params.emplace_back( new PARAM_LIST( "libraries.pinned_symbol_libs", &m_PinnedSymbolLibs, {} ) ); m_params.emplace_back( new PARAM_LIST( "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_BoardPageLayoutDescrFile, "" ) ); 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_params.emplace_back( new PARAM( "schematic.legacy_lib_dir", &m_LegacyLibDir, "" ) ); m_params.emplace_back( new PARAM_LAMBDA( "schematic.legacy_lib_list", [&]() -> nlohmann::json { nlohmann::json ret = nlohmann::json::array(); for( const wxString& libName : m_LegacyLibNames ) ret.push_back( libName ); return ret; }, [&]( const nlohmann::json& aJson ) { if( aJson.empty() || !aJson.is_array() ) return; m_LegacyLibNames.clear(); for( const nlohmann::json& entry : aJson ) m_LegacyLibNames.push_back( entry.get() ); }, {} ) ); m_NetSettings = std::make_shared( this, "net_settings" ); m_params.emplace_back( new PARAM_LAMBDA( "board.layer_presets", [&]() -> nlohmann::json { nlohmann::json ret = nlohmann::json::array(); for( const LAYER_PRESET& preset : m_LayerPresets ) { nlohmann::json js = { { "name", preset.name }, { "activeLayer", preset.activeLayer } }; nlohmann::json layers = nlohmann::json::array(); for( PCB_LAYER_ID layer : preset.layers.Seq() ) layers.push_back( static_cast( layer ) ); js["layers"] = layers; nlohmann::json renderLayers = nlohmann::json::array(); for( GAL_LAYER_ID layer : preset.renderLayers.Seq() ) renderLayers.push_back( static_cast( layer ) ); js["renderLayers"] = renderLayers; ret.push_back( js ); } return ret; }, [&]( const nlohmann::json& aVal ) { if( aVal.empty() || !aVal.is_array() ) return; m_LayerPresets.clear(); for( const nlohmann::json& preset : aVal ) { if( preset.contains( "name" ) ) { LAYER_PRESET p( preset.at( "name" ).get() ); if( preset.contains( "activeLayer" ) && preset.at( "activeLayer" ).is_number_integer() ) { int active = preset.at( "activeLayer" ).get(); if( active >= 0 && active < PCB_LAYER_ID_COUNT ) p.activeLayer = static_cast( active ); } if( preset.contains( "layers" ) && preset.at( "layers" ).is_array() ) { p.layers.reset(); for( const nlohmann::json& layer : preset.at( "layers" ) ) { if( layer.is_number_integer() ) { int layerNum = layer.get(); if( layerNum >= 0 && layerNum < PCB_LAYER_ID_COUNT ) p.layers.set( layerNum ); } } } if( preset.contains( "renderLayers" ) && preset.at( "renderLayers" ).is_array() ) { p.renderLayers.reset(); for( const nlohmann::json& layer : preset.at( "renderLayers" ) ) { if( layer.is_number_integer() ) { int layerNum = layer.get(); if( layerNum >= GAL_LAYER_ID_START && layerNum < GAL_LAYER_ID_END ) p.renderLayers.set( static_cast( layerNum ) ); } } } m_LayerPresets.emplace_back( p ); } } }, {} ) ); } bool PROJECT_FILE::MigrateFromLegacy( wxConfigBase* aCfg ) { bool ret = true; wxString str; long index = 0; std::set 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( "/eeschema" ) ); fromLegacyString( aCfg, "LibDir", "schematic.legacy_lib_dir" ); aCfg->SetPath( wxT( "/eeschema/libraries" ) ); { int libIdx = 1; wxString libKey = wxT( "LibName" ); libKey << libIdx; nlohmann::json libs = nlohmann::json::array(); while( aCfg->Read( libKey, &str ) ) { libs.push_back( str ); libKey = wxT( "LibName" ); libKey << ++libIdx; } ( *this )[PointerFromString( "schematic.legacy_lib_list" )] = libs; } group_blacklist.insert( wxT( "/eeschema" ) ); aCfg->SetPath( wxT( "/text_variables" ) ); { int txtIdx = 1; wxString txtKey; txtKey << txtIdx; nlohmann::json vars = nlohmann::json(); while( aCfg->Read( txtKey, &str ) ) { wxArrayString tokens = wxSplit( str, ':' ); if( tokens.size() == 2 ) vars[ tokens[0].ToStdString() ] = tokens[1]; txtKey.clear(); txtKey << ++txtIdx; } ( *this )[PointerFromString( "text_variables" )] = vars; } group_blacklist.insert( wxT( "/text_variables" ) ); aCfg->SetPath( wxT( "/schematic_editor" ) ); fromLegacyString( aCfg, "PageLayoutDescrFile", "schematic.page_layout_descr_file" ); fromLegacyString( aCfg, "PlotDirectoryName", "schematic.plot_directory" ); fromLegacyString( aCfg, "NetFmtName", "schematic.net_format_name" ); fromLegacy( aCfg, "SpiceAjustPassiveValues", "schematic.spice_adjust_passive_values" ); fromLegacy( aCfg, "SubpartIdSeparator", "schematic.subpart_id_separator" ); fromLegacy( aCfg, "SubpartFirstId", "schematic.subpart_first_id" ); fromLegacy( aCfg, "LineThickness", "schematic.drawing.default_line_thickness" ); fromLegacy( aCfg, "WireThickness", "schematic.drawing.default_wire_thickness" ); fromLegacy( aCfg, "BusThickness", "schematic.drawing.default_bus_thickness" ); fromLegacy( aCfg, "LabSize", "schematic.drawing.default_text_size" ); fromLegacy( aCfg, "PinSymbolSize", "schematic.drawing.pin_symbol_size" ); fromLegacy( aCfg, "JunctionSize", "schematic.drawing.default_junction_size" ); fromLegacyString( aCfg, "FieldNameTemplates", "schematic.drawing.field_names" ); fromLegacy( aCfg, "TextOffsetRatio", "schematic.drawing.text_offset_ratio" ); // All schematic_editor keys we keep are migrated above group_blacklist.insert( wxT( "/schematic_editor" ) ); 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( aCfg, "AllowMicroVias", bp + "rules.allow_microvias" ); fromLegacy( aCfg, "AllowBlindVias", bp + "rules.allow_blind_buried_vias" ); fromLegacy( aCfg, "MinClearance", bp + "rules.min_clearance" ); fromLegacy( aCfg, "MinTrackWidth", bp + "rules.min_track_width" ); fromLegacy( aCfg, "MinViaAnnulus", bp + "rules.min_via_annulus" ); fromLegacy( aCfg, "MinViaDiameter", bp + "rules.min_via_diameter" ); if( !fromLegacy( aCfg, "MinThroughDrill", bp + "rules.min_through_hole_diameter" ) ) fromLegacy( aCfg, "MinViaDrill", bp + "rules.min_through_hole_diameter" ); fromLegacy( aCfg, "MinMicroViaDiameter", bp + "rules.min_microvia_diameter" ); fromLegacy( aCfg, "MinMicroViaDrill", bp + "rules.min_microvia_drill" ); fromLegacy( aCfg, "MinHoleToHole", bp + "rules.min_hole_to_hole" ); fromLegacy( aCfg, "CopperEdgeClearance", bp + "rules.min_copper_edge_clearance" ); fromLegacy( aCfg, "SolderMaskClearance", bp + "rules.solder_mask_clearance" ); fromLegacy( aCfg, "SolderMaskMinWidth", bp + "rules.solder_mask_min_width" ); fromLegacy( aCfg, "SolderPasteClearance", bp + "rules.solder_paste_clearance" ); fromLegacy( aCfg, "SolderPasteRatio", bp + "rules.solder_paste_margin_ratio" ); if( !fromLegacy( aCfg, "SilkLineWidth", bp + "defaults.silk_line_width" ) ) fromLegacy( aCfg, "ModuleOutlineThickness", bp + "defaults.silk_line_width" ); if( !fromLegacy( aCfg, "SilkTextSizeV", bp + "defaults.silk_text_size_v" ) ) fromLegacy( aCfg, "ModuleTextSizeV", bp + "defaults.silk_text_size_v" ); if( !fromLegacy( aCfg, "SilkTextSizeH", bp + "defaults.silk_text_size_h" ) ) fromLegacy( aCfg, "ModuleTextSizeH", bp + "defaults.silk_text_size_h" ); if( !fromLegacy( aCfg, "SilkTextSizeThickness", bp + "defaults.silk_text_thickness" ) ) fromLegacy( aCfg, "ModuleTextSizeThickness", bp + "defaults.silk_text_thickness" ); fromLegacy( aCfg, "SilkTextItalic", bp + "defaults.silk_text_italic" ); fromLegacy( aCfg, "SilkTextUpright", bp + "defaults.silk_text_upright" ); if( !fromLegacy( aCfg, "CopperLineWidth", bp + "defaults.copper_line_width" ) ) fromLegacy( aCfg, "DrawSegmentWidth", bp + "defaults.copper_line_width" ); if( !fromLegacy( aCfg, "CopperTextSizeV", bp + "defaults.copper_text_size_v" ) ) fromLegacy( aCfg, "PcbTextSizeV", bp + "defaults.copper_text_size_v" ); if( !fromLegacy( aCfg, "CopperTextSizeH", bp + "defaults.copper_text_size_h" ) ) fromLegacy( aCfg, "PcbTextSizeH", bp + "defaults.copper_text_size_h" ); if( !fromLegacy( aCfg, "CopperTextThickness", bp + "defaults.copper_text_thickness" ) ) fromLegacy( aCfg, "PcbTextThickness", bp + "defaults.copper_text_thickness" ); fromLegacy( aCfg, "CopperTextItalic", bp + "defaults.copper_text_italic" ); fromLegacy( aCfg, "CopperTextUpright", bp + "defaults.copper_text_upright" ); if( !fromLegacy( aCfg, "EdgeCutLineWidth", bp + "defaults.board_outline_line_width" ) ) fromLegacy( aCfg, "BoardOutlineThickness", bp + "defaults.board_outline_line_width" ); fromLegacy( aCfg, "CourtyardLineWidth", bp + "defaults.courtyard_line_width" ); fromLegacy( aCfg, "FabLineWidth", bp + "defaults.fab_line_width" ); fromLegacy( aCfg, "FabTextSizeV", bp + "defaults.fab_text_size_v" ); fromLegacy( aCfg, "FabTextSizeH", bp + "defaults.fab_text_size_h" ); fromLegacy( aCfg, "FabTextSizeThickness", bp + "defaults.fab_text_thickness" ); fromLegacy( aCfg, "FabTextItalic", bp + "defaults.fab_text_italic" ); fromLegacy( aCfg, "FabTextUpright", bp + "defaults.fab_text_upright" ); if( !fromLegacy( aCfg, "OthersLineWidth", bp + "defaults.other_line_width" ) ) fromLegacy( aCfg, "ModuleOutlineThickness", bp + "defaults.other_line_width" ); fromLegacy( aCfg, "OthersTextSizeV", bp + "defaults.other_text_size_v" ); fromLegacy( aCfg, "OthersTextSizeH", bp + "defaults.other_text_size_h" ); fromLegacy( aCfg, "OthersTextSizeThickness", bp + "defaults.other_text_thickness" ); fromLegacy( aCfg, "OthersTextItalic", bp + "defaults.other_text_italic" ); fromLegacy( aCfg, "OthersTextUpright", bp + "defaults.other_text_upright" ); fromLegacy( aCfg, "DimensionUnits", bp + "defaults.dimension_units" ); fromLegacy( aCfg, "DimensionPrecision", bp + "defaults.dimension_precision" ); std::string sev = bp + "rule_severities"; fromLegacy( aCfg, "RequireCourtyardDefinitions", sev + "legacy_no_courtyard_defined" ); fromLegacy( aCfg, "ProhibitOverlappingCourtyards", sev + "legacy_courtyards_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; } group_blacklist.insert( wxT( "/pcbnew" ) ); // 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 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 wxString& 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().c_str(), wxConvUTF8 ) ); aPair.second = wxString( aJson[1].get().c_str(), wxConvUTF8 ); }