diff --git a/pcbnew/netlist_reader/board_netlist_updater.cpp b/pcbnew/netlist_reader/board_netlist_updater.cpp index 3694603f0c..ea13d5f775 100644 --- a/pcbnew/netlist_reader/board_netlist_updater.cpp +++ b/pcbnew/netlist_reader/board_netlist_updater.cpp @@ -1292,6 +1292,31 @@ bool BOARD_NETLIST_UPDATER::UpdateNetlist( NETLIST& aNetlist ) EscapeHTML( component->GetFPID().Format().wx_str() ) ); m_reporter->Report( msg, RPT_SEVERITY_INFO ); + const LIB_ID& compFpid = component->GetFPID(); + + if( compFpid.IsLegacy() ) + { + msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. " + "Use the full 'Library:Footprint' format to avoid repeated update " + "notifications." ), + component->GetReference(), + EscapeHTML( compFpid.Format().wx_str() ) ); + m_reporter->Report( msg, RPT_SEVERITY_WARNING ); + ++m_warningCount; + } + + // When the schematic-side FPID has no library nickname (legacy format like + // "DGG56" instead of "Package_SO:DGG56"), matching should compare only the + // footprint item name. Otherwise the board footprint (which always has a library + // nickname) will never match, causing perpetual "change footprint" notifications. + auto fpidMatches = [&]( const LIB_ID& aBoardFpid, const LIB_ID& aExpectedFpid ) -> bool + { + if( aExpectedFpid.IsLegacy() ) + return aBoardFpid.GetLibItemName() == aExpectedFpid.GetLibItemName(); + + return aBoardFpid == aExpectedFpid; + }; + int matchCount = 0; for( FOOTPRINT* footprint : m_board->Footprints() ) @@ -1321,7 +1346,7 @@ bool BOARD_NETLIST_UPDATER::UpdateNetlist( NETLIST& aNetlist ) { FOOTPRINT* tmp = footprint; - if( m_replaceFootprints && component->GetFPID() != footprint->GetFPID() ) + if( m_replaceFootprints && !fpidMatches( footprint->GetFPID(), compFpid ) ) tmp = replaceFootprint( aNetlist, footprint, component ); if( !tmp ) diff --git a/pcbnew/netlist_reader/netlist.cpp b/pcbnew/netlist_reader/netlist.cpp index 2a883f15be..4de4640c1b 100644 --- a/pcbnew/netlist_reader/netlist.cpp +++ b/pcbnew/netlist_reader/netlist.cpp @@ -199,7 +199,22 @@ void PCB_EDIT_FRAME::LoadFootprints( NETLIST& aNetlist, REPORTER& aReporter ) else fpOnBoard = m_pcb->FindFootprintByReference( component->GetReference() ); - bool footprintMisMatch = fpOnBoard && fpOnBoard->GetFPID() != component->GetFPID(); + // When the schematic-side FPID has no library nickname (legacy format), match + // only by item name so we don't flag a mismatch against a fully qualified board FPID. + bool footprintMisMatch = false; + + if( fpOnBoard ) + { + if( component->GetFPID().IsLegacy() ) + { + footprintMisMatch = + fpOnBoard->GetFPID().GetLibItemName() != component->GetFPID().GetLibItemName(); + } + else + { + footprintMisMatch = fpOnBoard->GetFPID() != component->GetFPID(); + } + } if( footprintMisMatch && !aNetlist.GetReplaceFootprints() ) { diff --git a/qa/tests/common/CMakeLists.txt b/qa/tests/common/CMakeLists.txt index 6338939882..bd0c8643ca 100644 --- a/qa/tests/common/CMakeLists.txt +++ b/qa/tests/common/CMakeLists.txt @@ -46,6 +46,7 @@ set( QA_COMMON_SRCS test_kicad_stroke_font.cpp test_kiid.cpp test_layer_ids.cpp + test_lib_id.cpp test_layer_range.cpp test_lset.cpp test_property.cpp diff --git a/qa/tests/common/test_lib_id.cpp b/qa/tests/common/test_lib_id.cpp new file mode 100644 index 0000000000..02253da8da --- /dev/null +++ b/qa/tests/common/test_lib_id.cpp @@ -0,0 +1,114 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +#include + + +BOOST_AUTO_TEST_SUITE( LibId ) + + +BOOST_AUTO_TEST_CASE( ParseFullyQualified ) +{ + LIB_ID id; + BOOST_CHECK( id.Parse( "Package_SO:DGG56" ) == -1 ); + BOOST_CHECK_EQUAL( id.GetLibNickname(), "Package_SO" ); + BOOST_CHECK_EQUAL( id.GetLibItemName(), "DGG56" ); + BOOST_CHECK( id.IsValid() ); + BOOST_CHECK( !id.IsLegacy() ); + BOOST_CHECK( !id.empty() ); +} + + +BOOST_AUTO_TEST_CASE( ParseLegacy ) +{ + LIB_ID id; + BOOST_CHECK( id.Parse( "DGG56" ) == -1 ); + BOOST_CHECK( id.GetLibNickname().empty() ); + BOOST_CHECK_EQUAL( id.GetLibItemName(), "DGG56" ); + BOOST_CHECK( !id.IsValid() ); + BOOST_CHECK( id.IsLegacy() ); + BOOST_CHECK( !id.empty() ); +} + + +BOOST_AUTO_TEST_CASE( EqualityFullyQualified ) +{ + LIB_ID a( wxT( "Package_SO" ), wxT( "DGG56" ) ); + LIB_ID b( wxT( "Package_SO" ), wxT( "DGG56" ) ); + LIB_ID c( wxT( "OtherLib" ), wxT( "DGG56" ) ); + + BOOST_CHECK( a == b ); + BOOST_CHECK( a != c ); +} + + +BOOST_AUTO_TEST_CASE( EqualityLegacyVsFullyQualified ) +{ + LIB_ID legacy; + legacy.Parse( "DGG56" ); + + LIB_ID qualified( wxT( "Package_SO" ), wxT( "DGG56" ) ); + + // Standard equality requires both library and item name to match + BOOST_CHECK( legacy != qualified ); + + // Legacy matching (item name only) should match when we explicitly compare just item names + BOOST_CHECK( legacy.IsLegacy() ); + BOOST_CHECK_EQUAL( legacy.GetLibItemName(), qualified.GetLibItemName() ); +} + + +BOOST_AUTO_TEST_CASE( FormatRoundTrip ) +{ + LIB_ID qualified( wxT( "Package_SO" ), wxT( "DGG56" ) ); + BOOST_CHECK_EQUAL( qualified.Format().wx_str(), wxString( wxT( "Package_SO:DGG56" ) ) ); + + LIB_ID legacy; + legacy.Parse( "DGG56" ); + BOOST_CHECK_EQUAL( legacy.Format().wx_str(), wxString( wxT( "DGG56" ) ) ); + + // Re-parse the formatted strings + LIB_ID reparsed; + reparsed.Parse( qualified.Format() ); + BOOST_CHECK( reparsed == qualified ); + + LIB_ID reparsedLegacy; + reparsedLegacy.Parse( legacy.Format() ); + BOOST_CHECK( reparsedLegacy.IsLegacy() ); + BOOST_CHECK_EQUAL( reparsedLegacy.GetLibItemName(), legacy.GetLibItemName() ); +} + + +BOOST_AUTO_TEST_CASE( EmptyId ) +{ + LIB_ID empty; + BOOST_CHECK( empty.empty() ); + BOOST_CHECK( !empty.IsValid() ); + BOOST_CHECK( !empty.IsLegacy() ); +} + + +BOOST_AUTO_TEST_SUITE_END()