/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 CERN * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors. * @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 // const int netSettingsSchemaVersion = 0; // const int netSettingsSchemaVersion = 1; // new overbar syntax // const int netSettingsSchemaVersion = 2; // exclude buses from netclass members const int netSettingsSchemaVersion = 3; // netclass assignment patterns static std::optional getInPcbUnits( const nlohmann::json& aObj, const std::string& aKey, std::optional aDefault = std::optional() ) { if( aObj.contains( aKey ) && aObj[aKey].is_number() ) return pcbIUScale.mmToIU( aObj[aKey].get() ); else return aDefault; }; static int getInSchUnits( const nlohmann::json& aObj, const std::string& aKey, int aDefault ) { if( aObj.contains( aKey ) && aObj[aKey].is_number() ) return schIUScale.MilsToIU( aObj[aKey].get() ); else return aDefault; }; NET_SETTINGS::NET_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath ) : NESTED_SETTINGS( "net_settings", netSettingsSchemaVersion, aParent, aPath ) { m_DefaultNetClass = std::make_shared( NETCLASS::Default ); m_DefaultNetClass->SetDescription( _( "This is the default net class." ) ); auto saveNetclass = []( nlohmann::json& json_array, const std::shared_ptr& nc ) { // Note: we're in common/, but we do happen to know which of these // fields are used in which units system. nlohmann::json nc_json = { { "name", nc->GetName().ToUTF8() }, { "wire_width", schIUScale.IUToMils( nc->GetWireWidth() ) }, { "bus_width", schIUScale.IUToMils( nc->GetBusWidth() ) }, { "line_style", nc->GetLineStyle() }, { "schematic_color", nc->GetSchematicColor() }, { "pcb_color", nc->GetPcbColor() } }; auto saveInPcbUnits = []( nlohmann::json& json, const std::string& aKey, int aValue ) { json.push_back( { aKey, pcbIUScale.IUTomm( aValue ) } ); }; if( nc->HasClearance() ) saveInPcbUnits( nc_json, "clearance", nc->GetClearance() ); if( nc->HasTrackWidth() ) saveInPcbUnits( nc_json, "track_width", nc->GetTrackWidth() ); if( nc->HasViaDiameter() ) saveInPcbUnits( nc_json, "via_diameter", nc->GetViaDiameter() ); if( nc->HasViaDrill() ) saveInPcbUnits( nc_json, "via_drill", nc->GetViaDrill() ); if( nc->HasuViaDiameter() ) saveInPcbUnits( nc_json, "microvia_diameter", nc->GetuViaDiameter() ); if( nc->HasuViaDrill() ) saveInPcbUnits( nc_json, "microvia_drill", nc->GetuViaDrill() ); if( nc->HasDiffPairWidth() ) saveInPcbUnits( nc_json, "diff_pair_width", nc->GetDiffPairWidth() ); if( nc->HasDiffPairGap() ) saveInPcbUnits( nc_json, "diff_pair_gap", nc->GetDiffPairGap() ); if( nc->HasDiffPairViaGap() ) saveInPcbUnits( nc_json, "diff_pair_via_gap", nc->GetDiffPairViaGap() ); json_array.push_back( nc_json ); }; auto readNetClass = []( const nlohmann::json& entry ) { wxString name = entry["name"]; std::shared_ptr nc = std::make_shared( name ); if( auto value = getInPcbUnits( entry, "clearance" ) ) nc->SetClearance( *value ); if( auto value = getInPcbUnits( entry, "track_width" ) ) nc->SetTrackWidth( *value ); if( auto value = getInPcbUnits( entry, "via_diameter" ) ) nc->SetViaDiameter( *value ); if( auto value = getInPcbUnits( entry, "via_drill" ) ) nc->SetViaDrill( *value ); if( auto value = getInPcbUnits( entry, "microvia_diameter" ) ) nc->SetuViaDiameter( *value ); if( auto value = getInPcbUnits( entry, "microvia_drill" ) ) nc->SetuViaDrill( *value ); if( auto value = getInPcbUnits( entry, "diff_pair_width" ) ) nc->SetDiffPairWidth( *value ); if( auto value = getInPcbUnits( entry, "diff_pair_gap" ) ) nc->SetDiffPairGap( *value ); if( auto value = getInPcbUnits( entry, "diff_pair_via_gap" ) ) nc->SetDiffPairViaGap( *value ); nc->SetWireWidth( getInSchUnits( entry, "wire_width", nc->GetWireWidth() ) ); nc->SetBusWidth( getInSchUnits( entry, "bus_width", nc->GetBusWidth() ) ); if( entry.contains( "line_style" ) && entry["line_style"].is_number() ) nc->SetLineStyle( entry["line_style"].get() ); if( entry.contains( "pcb_color" ) && entry["pcb_color"].is_string() ) nc->SetPcbColor( entry["pcb_color"].get() ); if( entry.contains( "schematic_color" ) && entry["schematic_color"].is_string() ) { nc->SetSchematicColor( entry["schematic_color"].get() ); } return nc; }; m_params.emplace_back( new PARAM_LAMBDA( "classes", [&]() -> nlohmann::json { nlohmann::json ret = nlohmann::json::array(); if( m_DefaultNetClass ) saveNetclass( ret, m_DefaultNetClass ); for( const auto& [ name, netclass ] : m_NetClasses ) saveNetclass( ret, netclass ); return ret; }, [&]( const nlohmann::json& aJson ) { if( !aJson.is_array() ) return; m_NetClasses.clear(); for( const nlohmann::json& entry : aJson ) { if( !entry.is_object() || !entry.contains( "name" ) ) continue; std::shared_ptr nc = readNetClass( entry ); if( nc->GetName() == NETCLASS::Default ) m_DefaultNetClass = nc; else m_NetClasses[ nc->GetName() ] = nc; } }, {} ) ); m_params.emplace_back( new PARAM_LAMBDA( "net_colors", [&]() -> nlohmann::json { nlohmann::json ret = {}; for( const auto& [ netname, color ] : m_NetColorAssignments ) { std::string key( netname.ToUTF8() ); ret[key] = color; } return ret; }, [&]( const nlohmann::json& aJson ) { if( !aJson.is_object() ) return; m_NetColorAssignments.clear(); for( const auto& pair : aJson.items() ) { wxString key( pair.key().c_str(), wxConvUTF8 ); m_NetColorAssignments[key] = pair.value().get(); } }, {} ) ); m_params.emplace_back( new PARAM_LAMBDA( "netclass_assignments", [&]() -> nlohmann::json { nlohmann::json ret = {}; for( const auto& [ netname, netclassName ] : m_NetClassLabelAssignments ) { std::string key( netname.ToUTF8() ); ret[key] = netclassName; } return ret; }, [&]( const nlohmann::json& aJson ) { if( !aJson.is_object() ) return; m_NetClassLabelAssignments.clear(); for( const auto& pair : aJson.items() ) { wxString key( pair.key().c_str(), wxConvUTF8 ); m_NetClassLabelAssignments[key] = pair.value().get(); } }, {} ) ); m_params.emplace_back( new PARAM_LAMBDA( "netclass_patterns", [&]() -> nlohmann::json { nlohmann::json ret = nlohmann::json::array(); for( const auto& [ matcher, netclassName ] : m_NetClassPatternAssignments ) { nlohmann::json pattern_json = { { "pattern", matcher->GetPattern().ToUTF8() }, { "netclass", netclassName.ToUTF8() } }; ret.push_back( pattern_json ); } return ret; }, [&]( const nlohmann::json& aJson ) { if( !aJson.is_array() ) return; m_NetClassPatternAssignments.clear(); for( const nlohmann::json& entry : aJson ) { if( !entry.is_object() ) continue; if( entry.contains( "pattern" ) && entry["pattern"].is_string() && entry.contains( "netclass" ) && entry["netclass"].is_string() ) { wxString pattern = entry["pattern"].get(); wxString netclass = entry["netclass"].get(); m_NetClassPatternAssignments.push_back( { std::make_unique( pattern, CTX_NETCLASS ), netclass } ); } } }, {} ) ); registerMigration( 0, 1, std::bind( &NET_SETTINGS::migrateSchema0to1, this ) ); registerMigration( 2, 3, std::bind( &NET_SETTINGS::migrateSchema2to3, this ) ); } NET_SETTINGS::~NET_SETTINGS() { // Release early before destroying members if( m_parent ) { m_parent->ReleaseNestedSettings( this ); m_parent = nullptr; } } bool NET_SETTINGS::migrateSchema0to1() { if( m_internals->contains( "classes" ) && m_internals->At( "classes" ).is_array() ) { for( auto& netClass : m_internals->At( "classes" ).items() ) { if( netClass.value().contains( "nets" ) && netClass.value()["nets"].is_array() ) { nlohmann::json migrated = nlohmann::json::array(); for( auto& net : netClass.value()["nets"].items() ) migrated.push_back( ConvertToNewOverbarNotation( net.value().get() ) ); netClass.value()["nets"] = migrated; } } } return true; } bool NET_SETTINGS::migrateSchema2to3() { if( m_internals->contains( "classes" ) && m_internals->At( "classes" ).is_array() ) { nlohmann::json patterns = nlohmann::json::array(); for( auto& netClass : m_internals->At( "classes" ).items() ) { if( netClass.value().contains( "name" ) && netClass.value().contains( "nets" ) && netClass.value()["nets"].is_array() ) { wxString netClassName = netClass.value()["name"].get(); for( auto& net : netClass.value()["nets"].items() ) { nlohmann::json pattern_json = { { "pattern", net.value().get() }, { "netclass", netClassName } }; patterns.push_back( pattern_json ); } } } m_internals->SetFromString( "netclass_patterns", patterns ); } return true; } std::shared_ptr NET_SETTINGS::GetEffectiveNetClass( const wxString& aNetName ) const { auto getNetclass = [&]( const wxString& netclass ) { auto ii = m_NetClasses.find( netclass ); if( ii == m_NetClasses.end() ) return m_DefaultNetClass; else return ii->second; }; // is forced to be part of the Default netclass. if( aNetName.IsEmpty() ) return m_DefaultNetClass; auto it = m_NetClassLabelAssignments.find( aNetName ); if( it != m_NetClassLabelAssignments.end() ) return getNetclass( it->second ); for( const auto& [ matcher, netclassName ] : m_NetClassPatternAssignments ) { if( matcher->StartsWith( aNetName ) ) return getNetclass( netclassName ); } return m_DefaultNetClass; } static bool isSuperSubOverbar( wxChar c ) { return c == '_' || c == '^' || c == '~'; } bool NET_SETTINGS::ParseBusVector( const wxString& aBus, wxString* aName, std::vector* aMemberList ) { auto isDigit = []( wxChar c ) { static wxString digits( wxT( "0123456789" ) ); return digits.Contains( c ); }; size_t busLen = aBus.length(); size_t i = 0; wxString prefix; wxString suffix; wxString tmp; long begin = 0; long end = 0; int braceNesting = 0; prefix.reserve( busLen ); // Parse prefix // for( ; i < busLen; ++i ) { if( aBus[i] == '{' ) { if( i > 0 && isSuperSubOverbar( aBus[i-1] ) ) braceNesting++; else return false; } else if( aBus[i] == '}' ) { braceNesting--; } if( aBus[i] == ' ' || aBus[i] == ']' ) return false; if( aBus[i] == '[' ) break; prefix += aBus[i]; } // Parse start number // i++; // '[' character if( i >= busLen ) return false; for( ; i < busLen; ++i ) { if( aBus[i] == '.' && i + 1 < busLen && aBus[i+1] == '.' ) { tmp.ToLong( &begin ); i += 2; break; } if( !isDigit( aBus[i] ) ) return false; tmp += aBus[i]; } // Parse end number // tmp = wxEmptyString; if( i >= busLen ) return false; for( ; i < busLen; ++i ) { if( aBus[i] == ']' ) { tmp.ToLong( &end ); ++i; break; } if( !isDigit( aBus[i] ) ) return false; tmp += aBus[i]; } // Parse suffix // for( ; i < busLen; ++i ) { if( aBus[i] == '}' ) { braceNesting--; suffix += aBus[i]; } else { return false; } } if( braceNesting != 0 ) return false; if( begin == end ) return false; else if( begin > end ) std::swap( begin, end ); if( aName ) *aName = prefix; if( aMemberList ) { for( long idx = begin; idx <= end; ++idx ) { wxString str = prefix; str << idx; str << suffix; aMemberList->emplace_back( str ); } } return true; } bool NET_SETTINGS::ParseBusGroup( const wxString& aGroup, wxString* aName, std::vector* aMemberList ) { size_t groupLen = aGroup.length(); size_t i = 0; wxString prefix; wxString tmp; int braceNesting = 0; prefix.reserve( groupLen ); // Parse prefix // for( ; i < groupLen; ++i ) { if( aGroup[i] == '{' ) { if( i > 0 && isSuperSubOverbar( aGroup[i-1] ) ) braceNesting++; else break; } else if( aGroup[i] == '}' ) { braceNesting--; } if( aGroup[i] == ' ' || aGroup[i] == '[' || aGroup[i] == ']' ) return false; prefix += aGroup[i]; } if( braceNesting != 0 ) return false; if( aName ) *aName = prefix; // Parse members // i++; // '{' character if( i >= groupLen ) return false; for( ; i < groupLen; ++i ) { if( aGroup[i] == '{' ) { if( i > 0 && isSuperSubOverbar( aGroup[i-1] ) ) braceNesting++; else return false; } else if( aGroup[i] == '}' ) { if( braceNesting ) { braceNesting--; } else { if( aMemberList && !tmp.IsEmpty() ) aMemberList->push_back( EscapeString( tmp, CTX_NETNAME ) ); return true; } } // Commas aren't strictly legal, but we can be pretty sure what the author had in mind. if( aGroup[i] == ' ' || aGroup[i] == ',' ) { if( aMemberList && !tmp.IsEmpty() ) aMemberList->push_back( EscapeString( tmp, CTX_NETNAME ) ); tmp.Clear(); continue; } tmp += aGroup[i]; } return false; }