diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp index 3a1e4e84a9..289330101a 100644 --- a/pcbnew/pcb_edit_frame.cpp +++ b/pcbnew/pcb_edit_frame.cpp @@ -35,6 +35,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -47,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -2362,51 +2368,75 @@ static void processTextItem( const PCB_TEXT& aSrc, PCB_TEXT& aDest, } -static PCB_TEXT* getMatchingTextItem( PCB_TEXT* aRefItem, FOOTPRINT* aFootprint ) +template +static std::vector> matchItemsBySimilarity( const std::vector& aExisting, + const std::vector& aNew ) { - std::vector candidates; - - for( BOARD_ITEM* item : aFootprint->GraphicalItems() ) + struct MATCH_CANDIDATE { - PCB_TEXT* candidate = dynamic_cast( item ); + T* existing; + T* updated; + double score; + }; - if( candidate && candidate->GetText() == aRefItem->GetText() ) - candidates.push_back( candidate ); + std::vector candidates; + + for( T* existing : aExisting ) + { + for( T* updated : aNew ) + { + if( existing->Type() != updated->Type() ) + continue; + + double similarity = existing->Similarity( *updated ); + + if( similarity <= 0.0 ) + continue; + + double score = similarity; + + if constexpr( std::is_same_v ) + { + if( existing->GetNumber() == updated->GetNumber() ) + score += 2.0; + } + + candidates.push_back( { existing, updated, score } ); + } } - if( candidates.size() == 0 ) - return nullptr; + std::sort( candidates.begin(), candidates.end(), + []( const MATCH_CANDIDATE& a, const MATCH_CANDIDATE& b ) + { + if( a.score != b.score ) + return a.score > b.score; - if( candidates.size() == 1 ) - return candidates[0]; + if( a.existing != b.existing ) + return a.existing < b.existing; - // Try refining the match by layer - std::vector candidatesOnSameLayer; + return a.updated < b.updated; + } ); - for( PCB_TEXT* candidate : candidates ) + std::vector> matches; + matches.reserve( candidates.size() ); + + std::unordered_set matchedExisting; + std::unordered_set matchedNew; + + for( const MATCH_CANDIDATE& candidate : candidates ) { - if( candidate->GetLayer() == aRefItem->GetLayer() ) - candidatesOnSameLayer.push_back( candidate ); + if( matchedExisting.find( candidate.existing ) != matchedExisting.end() ) + continue; + + if( matchedNew.find( candidate.updated ) != matchedNew.end() ) + continue; + + matchedExisting.insert( candidate.existing ); + matchedNew.insert( candidate.updated ); + matches.emplace_back( candidate.existing, candidate.updated ); } - if( candidatesOnSameLayer.size() == 1 ) - return candidatesOnSameLayer[0]; - - // Last ditch effort: refine by position - std::vector candidatesAtSamePos; - - for( PCB_TEXT* candidate : candidatesOnSameLayer.size() ? candidatesOnSameLayer : candidates ) - { - if( candidate->GetFPRelativePosition() == aRefItem->GetFPRelativePosition() ) - candidatesAtSamePos.push_back( candidate ); - } - - if( candidatesAtSamePos.size() > 0 ) - return candidatesAtSamePos[0]; - else if( candidatesOnSameLayer.size() > 0 ) - return candidatesOnSameLayer[0]; - else - return candidates[0]; + return matches; } @@ -2451,49 +2481,227 @@ void PCB_EDIT_FRAME::ExchangeFootprint( FOOTPRINT* aExisting, FOOTPRINT* aNew, aNew->SetLocked( aExisting->IsLocked() ); - // Now transfer the net info from "old" pads to the new footprint - for( PAD* newPad : aNew->Pads() ) + const_cast( aNew->m_Uuid ) = aExisting->m_Uuid; + const_cast( aNew->Reference().m_Uuid ) = aExisting->Reference().m_Uuid; + const_cast( aNew->Value().m_Uuid ) = aExisting->Value().m_Uuid; + + std::vector oldPads; + oldPads.reserve( aExisting->Pads().size() ); + + for( PAD* pad : aExisting->Pads() ) + oldPads.push_back( pad ); + + std::vector newPads; + newPads.reserve( aNew->Pads().size() ); + + for( PAD* pad : aNew->Pads() ) + newPads.push_back( pad ); + + auto padMatches = matchItemsBySimilarity( oldPads, newPads ); + std::unordered_set matchedNewPads; + + for( const auto& match : padMatches ) { - PAD* oldPad = nullptr; + PAD* oldPad = match.first; + PAD* newPad = match.second; - // Pads with no numbers can't be matched. (Then again, they're never connected to a - // net either, so it's just the UUID retention that we can't perform.) - if( newPad->GetNumber().IsEmpty() ) - { - newPad->SetNetCode( NETINFO_LIST::UNCONNECTED ); - continue; - } - - // Search for a similar pad to reuse UUID and net info - PAD* last_pad = nullptr; - - while( true ) - { - oldPad = aExisting->FindPadByNumber( newPad->GetNumber(), last_pad ); - - if( !oldPad ) - break; - - if( newPad->IsOnCopperLayer() == oldPad->IsOnCopperLayer() ) // a candidate is found - break; - - last_pad = oldPad; - } - - if( oldPad ) - { - const_cast( newPad->m_Uuid ) = oldPad->m_Uuid; - newPad->SetLocalRatsnestVisible( oldPad->GetLocalRatsnestVisible() ); - newPad->SetPinFunction( oldPad->GetPinFunction() ); - newPad->SetPinType( oldPad->GetPinType() ); - } + matchedNewPads.insert( newPad ); + const_cast( newPad->m_Uuid ) = oldPad->m_Uuid; + newPad->SetLocalRatsnestVisible( oldPad->GetLocalRatsnestVisible() ); + newPad->SetPinFunction( oldPad->GetPinFunction() ); + newPad->SetPinType( oldPad->GetPinType() ); if( newPad->IsOnCopperLayer() ) - newPad->SetNetCode( oldPad ? oldPad->GetNetCode() : NETINFO_LIST::UNCONNECTED ); + newPad->SetNetCode( oldPad->GetNetCode() ); else newPad->SetNetCode( NETINFO_LIST::UNCONNECTED ); } + for( PAD* newPad : aNew->Pads() ) + { + if( matchedNewPads.find( newPad ) != matchedNewPads.end() ) + continue; + + const_cast( newPad->m_Uuid ) = KIID(); + newPad->SetNetCode( NETINFO_LIST::UNCONNECTED ); + } + + std::vector oldDrawings; + oldDrawings.reserve( aExisting->GraphicalItems().size() ); + + for( BOARD_ITEM* item : aExisting->GraphicalItems() ) + oldDrawings.push_back( item ); + + std::vector newDrawings; + newDrawings.reserve( aNew->GraphicalItems().size() ); + + for( BOARD_ITEM* item : aNew->GraphicalItems() ) + newDrawings.push_back( item ); + + auto drawingMatches = matchItemsBySimilarity( oldDrawings, newDrawings ); + std::unordered_map oldToNewDrawings; + std::unordered_set matchedNewDrawings; + + for( const auto& match : drawingMatches ) + { + BOARD_ITEM* oldItem = match.first; + BOARD_ITEM* newItem = match.second; + + oldToNewDrawings[ oldItem ] = newItem; + matchedNewDrawings.insert( newItem ); + const_cast( newItem->m_Uuid ) = oldItem->m_Uuid; + } + + for( BOARD_ITEM* newItem : newDrawings ) + { + if( matchedNewDrawings.find( newItem ) == matchedNewDrawings.end() ) + const_cast( newItem->m_Uuid ) = KIID(); + } + + std::vector oldZones; + oldZones.reserve( aExisting->Zones().size() ); + + for( ZONE* zone : aExisting->Zones() ) + oldZones.push_back( zone ); + + std::vector newZones; + newZones.reserve( aNew->Zones().size() ); + + for( ZONE* zone : aNew->Zones() ) + newZones.push_back( zone ); + + auto zoneMatches = matchItemsBySimilarity( oldZones, newZones ); + std::unordered_set matchedNewZones; + + for( const auto& match : zoneMatches ) + { + ZONE* oldZone = match.first; + ZONE* newZone = match.second; + + matchedNewZones.insert( newZone ); + const_cast( newZone->m_Uuid ) = oldZone->m_Uuid; + } + + for( ZONE* newZone : newZones ) + { + if( matchedNewZones.find( newZone ) == matchedNewZones.end() ) + const_cast( newZone->m_Uuid ) = KIID(); + } + + std::vector oldPoints; + oldPoints.reserve( aExisting->Points().size() ); + + for( PCB_POINT* point : aExisting->Points() ) + oldPoints.push_back( point ); + + std::vector newPoints; + newPoints.reserve( aNew->Points().size() ); + + for( PCB_POINT* point : aNew->Points() ) + newPoints.push_back( point ); + + auto pointMatches = matchItemsBySimilarity( oldPoints, newPoints ); + std::unordered_set matchedNewPoints; + + for( const auto& match : pointMatches ) + { + PCB_POINT* oldPoint = match.first; + PCB_POINT* newPoint = match.second; + + matchedNewPoints.insert( newPoint ); + const_cast( newPoint->m_Uuid ) = oldPoint->m_Uuid; + } + + for( PCB_POINT* newPoint : newPoints ) + { + if( matchedNewPoints.find( newPoint ) == matchedNewPoints.end() ) + const_cast( newPoint->m_Uuid ) = KIID(); + } + + std::vector oldGroups; + oldGroups.reserve( aExisting->Groups().size() ); + + for( PCB_GROUP* group : aExisting->Groups() ) + oldGroups.push_back( group ); + + std::vector newGroups; + newGroups.reserve( aNew->Groups().size() ); + + for( PCB_GROUP* group : aNew->Groups() ) + newGroups.push_back( group ); + + auto groupMatches = matchItemsBySimilarity( oldGroups, newGroups ); + std::unordered_set matchedNewGroups; + + for( const auto& match : groupMatches ) + { + PCB_GROUP* oldGroup = match.first; + PCB_GROUP* newGroup = match.second; + + matchedNewGroups.insert( newGroup ); + const_cast( newGroup->m_Uuid ) = oldGroup->m_Uuid; + } + + for( PCB_GROUP* newGroup : newGroups ) + { + if( matchedNewGroups.find( newGroup ) == matchedNewGroups.end() ) + const_cast( newGroup->m_Uuid ) = KIID(); + } + + std::vector oldFieldsVec; + std::vector newFieldsVec; + + oldFieldsVec.reserve( aExisting->GetFields().size() ); + + for( PCB_FIELD* field : aExisting->GetFields() ) + { + if( field->IsReference() || field->IsValue() ) + continue; + + oldFieldsVec.push_back( field ); + } + + newFieldsVec.reserve( aNew->GetFields().size() ); + + for( PCB_FIELD* field : aNew->GetFields() ) + { + if( field->IsReference() || field->IsValue() ) + continue; + + newFieldsVec.push_back( field ); + } + + auto fieldMatches = matchItemsBySimilarity( oldFieldsVec, newFieldsVec ); + std::unordered_map oldToNewFields; + std::unordered_set matchedNewFields; + + for( const auto& match : fieldMatches ) + { + PCB_FIELD* oldField = match.first; + PCB_FIELD* newField = match.second; + + oldToNewFields[ oldField ] = newField; + matchedNewFields.insert( newField ); + const_cast( newField->m_Uuid ) = oldField->m_Uuid; + } + + for( PCB_FIELD* newField : newFieldsVec ) + { + if( matchedNewFields.find( newField ) == matchedNewFields.end() ) + const_cast( newField->m_Uuid ) = KIID(); + } + + std::unordered_map oldToNewTexts; + + for( const auto& match : drawingMatches ) + { + PCB_TEXT* oldText = dynamic_cast( match.first ); + PCB_TEXT* newText = dynamic_cast( match.second ); + + if( oldText && newText ) + oldToNewTexts[ oldText ] = newText; + } + std::set handledTextItems; for( BOARD_ITEM* oldItem : aExisting->GraphicalItems() ) @@ -2506,7 +2714,12 @@ void PCB_EDIT_FRAME::ExchangeFootprint( FOOTPRINT* aExisting, FOOTPRINT* aNew, if( dynamic_cast( oldTextItem ) ) continue; - PCB_TEXT* newTextItem = getMatchingTextItem( oldTextItem, aNew ); + PCB_TEXT* newTextItem = nullptr; + + auto textMatchIt = oldToNewTexts.find( oldTextItem ); + + if( textMatchIt != oldToNewTexts.end() ) + newTextItem = textMatchIt->second; if( newTextItem ) { @@ -2566,7 +2779,12 @@ void PCB_EDIT_FRAME::ExchangeFootprint( FOOTPRINT* aExisting, FOOTPRINT* aNew, if( oldField->IsReference() || oldField->IsValue() ) continue; - PCB_FIELD* newField = aNew->GetField( oldField->GetName() ); + PCB_FIELD* newField = nullptr; + + auto fieldMatchIt = oldToNewFields.find( oldField ); + + if( fieldMatchIt != oldToNewFields.end() ) + newField = fieldMatchIt->second; if( newField ) { @@ -2662,7 +2880,6 @@ void PCB_EDIT_FRAME::ExchangeFootprint( FOOTPRINT* aExisting, FOOTPRINT* aNew, } // Updating other parameters - const_cast( aNew->m_Uuid ) = aExisting->m_Uuid; aNew->SetPath( aExisting->GetPath() ); aNew->SetSheetfile( aExisting->GetSheetfile() ); aNew->SetSheetname( aExisting->GetSheetname() );