Browse Source

Preserve as much footprint data as possible

When updating the footprint from library, try to match as many things to
their new counterparts as possible to minimize VCS churn

Fixes https://gitlab.com/kicad/code/kicad/-/issues/21308
pull/19/head
Seth Hillbrand 2 months ago
parent
commit
5d50dc7ced
  1. 343
      pcbnew/pcb_edit_frame.cpp

343
pcbnew/pcb_edit_frame.cpp

@ -35,6 +35,11 @@
#include <confirm.h>
#include <lset.h>
#include <trace_helpers.h>
#include <algorithm>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <pcbnew_id.h>
#include <pcbnew_settings.h>
#include <pcb_layer_box_selector.h>
@ -47,6 +52,7 @@
#include <dialogs/dialog_table_properties.h>
#include <gal/graphics_abstraction_layer.h>
#include <pcb_target.h>
#include <pcb_point.h>
#include <layer_pairs.h>
#include <drawing_sheet/ds_proxy_view_item.h>
#include <wildcards_and_files_ext.h>
@ -2362,51 +2368,75 @@ static void processTextItem( const PCB_TEXT& aSrc, PCB_TEXT& aDest,
}
static PCB_TEXT* getMatchingTextItem( PCB_TEXT* aRefItem, FOOTPRINT* aFootprint )
template<typename T>
static std::vector<std::pair<T*, T*>> matchItemsBySimilarity( const std::vector<T*>& aExisting,
const std::vector<T*>& aNew )
{
std::vector<PCB_TEXT*> candidates;
struct MATCH_CANDIDATE
{
T* existing;
T* updated;
double score;
};
for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
std::vector<MATCH_CANDIDATE> candidates;
for( T* existing : aExisting )
{
PCB_TEXT* candidate = dynamic_cast<PCB_TEXT*>( item );
for( T* updated : aNew )
{
if( existing->Type() != updated->Type() )
continue;
if( candidate && candidate->GetText() == aRefItem->GetText() )
candidates.push_back( candidate );
}
double similarity = existing->Similarity( *updated );
if( candidates.size() == 0 )
return nullptr;
if( similarity <= 0.0 )
continue;
if( candidates.size() == 1 )
return candidates[0];
double score = similarity;
// Try refining the match by layer
std::vector<PCB_TEXT*> candidatesOnSameLayer;
if constexpr( std::is_same_v<T, PAD> )
{
if( existing->GetNumber() == updated->GetNumber() )
score += 2.0;
}
for( PCB_TEXT* candidate : candidates )
{
if( candidate->GetLayer() == aRefItem->GetLayer() )
candidatesOnSameLayer.push_back( candidate );
candidates.push_back( { existing, updated, score } );
}
}
if( candidatesOnSameLayer.size() == 1 )
return candidatesOnSameLayer[0];
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( a.existing != b.existing )
return a.existing < b.existing;
return a.updated < b.updated;
} );
std::vector<std::pair<T*, T*>> matches;
matches.reserve( candidates.size() );
// Last ditch effort: refine by position
std::vector<PCB_TEXT*> candidatesAtSamePos;
std::unordered_set<T*> matchedExisting;
std::unordered_set<T*> matchedNew;
for( PCB_TEXT* candidate : candidatesOnSameLayer.size() ? candidatesOnSameLayer : candidates )
for( const MATCH_CANDIDATE& candidate : candidates )
{
if( candidate->GetFPRelativePosition() == aRefItem->GetFPRelativePosition() )
candidatesAtSamePos.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( 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() )
{
PAD* oldPad = nullptr;
const_cast<KIID&>( aNew->m_Uuid ) = aExisting->m_Uuid;
const_cast<KIID&>( aNew->Reference().m_Uuid ) = aExisting->Reference().m_Uuid;
const_cast<KIID&>( aNew->Value().m_Uuid ) = aExisting->Value().m_Uuid;
// 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;
}
std::vector<PAD*> oldPads;
oldPads.reserve( aExisting->Pads().size() );
// Search for a similar pad to reuse UUID and net info
PAD* last_pad = nullptr;
for( PAD* pad : aExisting->Pads() )
oldPads.push_back( pad );
while( true )
{
oldPad = aExisting->FindPadByNumber( newPad->GetNumber(), last_pad );
std::vector<PAD*> newPads;
newPads.reserve( aNew->Pads().size() );
if( !oldPad )
break;
for( PAD* pad : aNew->Pads() )
newPads.push_back( pad );
if( newPad->IsOnCopperLayer() == oldPad->IsOnCopperLayer() ) // a candidate is found
break;
auto padMatches = matchItemsBySimilarity<PAD>( oldPads, newPads );
std::unordered_set<PAD*> matchedNewPads;
last_pad = oldPad;
}
for( const auto& match : padMatches )
{
PAD* oldPad = match.first;
PAD* newPad = match.second;
if( oldPad )
{
const_cast<KIID&>( newPad->m_Uuid ) = oldPad->m_Uuid;
newPad->SetLocalRatsnestVisible( oldPad->GetLocalRatsnestVisible() );
newPad->SetPinFunction( oldPad->GetPinFunction() );
newPad->SetPinType( oldPad->GetPinType() );
}
matchedNewPads.insert( newPad );
const_cast<KIID&>( 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<KIID&>( newPad->m_Uuid ) = KIID();
newPad->SetNetCode( NETINFO_LIST::UNCONNECTED );
}
std::vector<BOARD_ITEM*> oldDrawings;
oldDrawings.reserve( aExisting->GraphicalItems().size() );
for( BOARD_ITEM* item : aExisting->GraphicalItems() )
oldDrawings.push_back( item );
std::vector<BOARD_ITEM*> newDrawings;
newDrawings.reserve( aNew->GraphicalItems().size() );
for( BOARD_ITEM* item : aNew->GraphicalItems() )
newDrawings.push_back( item );
auto drawingMatches = matchItemsBySimilarity<BOARD_ITEM>( oldDrawings, newDrawings );
std::unordered_map<BOARD_ITEM*, BOARD_ITEM*> oldToNewDrawings;
std::unordered_set<BOARD_ITEM*> matchedNewDrawings;
for( const auto& match : drawingMatches )
{
BOARD_ITEM* oldItem = match.first;
BOARD_ITEM* newItem = match.second;
oldToNewDrawings[ oldItem ] = newItem;
matchedNewDrawings.insert( newItem );
const_cast<KIID&>( newItem->m_Uuid ) = oldItem->m_Uuid;
}
for( BOARD_ITEM* newItem : newDrawings )
{
if( matchedNewDrawings.find( newItem ) == matchedNewDrawings.end() )
const_cast<KIID&>( newItem->m_Uuid ) = KIID();
}
std::vector<ZONE*> oldZones;
oldZones.reserve( aExisting->Zones().size() );
for( ZONE* zone : aExisting->Zones() )
oldZones.push_back( zone );
std::vector<ZONE*> newZones;
newZones.reserve( aNew->Zones().size() );
for( ZONE* zone : aNew->Zones() )
newZones.push_back( zone );
auto zoneMatches = matchItemsBySimilarity<ZONE>( oldZones, newZones );
std::unordered_set<ZONE*> matchedNewZones;
for( const auto& match : zoneMatches )
{
ZONE* oldZone = match.first;
ZONE* newZone = match.second;
matchedNewZones.insert( newZone );
const_cast<KIID&>( newZone->m_Uuid ) = oldZone->m_Uuid;
}
for( ZONE* newZone : newZones )
{
if( matchedNewZones.find( newZone ) == matchedNewZones.end() )
const_cast<KIID&>( newZone->m_Uuid ) = KIID();
}
std::vector<PCB_POINT*> oldPoints;
oldPoints.reserve( aExisting->Points().size() );
for( PCB_POINT* point : aExisting->Points() )
oldPoints.push_back( point );
std::vector<PCB_POINT*> newPoints;
newPoints.reserve( aNew->Points().size() );
for( PCB_POINT* point : aNew->Points() )
newPoints.push_back( point );
auto pointMatches = matchItemsBySimilarity<PCB_POINT>( oldPoints, newPoints );
std::unordered_set<PCB_POINT*> matchedNewPoints;
for( const auto& match : pointMatches )
{
PCB_POINT* oldPoint = match.first;
PCB_POINT* newPoint = match.second;
matchedNewPoints.insert( newPoint );
const_cast<KIID&>( newPoint->m_Uuid ) = oldPoint->m_Uuid;
}
for( PCB_POINT* newPoint : newPoints )
{
if( matchedNewPoints.find( newPoint ) == matchedNewPoints.end() )
const_cast<KIID&>( newPoint->m_Uuid ) = KIID();
}
std::vector<PCB_GROUP*> oldGroups;
oldGroups.reserve( aExisting->Groups().size() );
for( PCB_GROUP* group : aExisting->Groups() )
oldGroups.push_back( group );
std::vector<PCB_GROUP*> newGroups;
newGroups.reserve( aNew->Groups().size() );
for( PCB_GROUP* group : aNew->Groups() )
newGroups.push_back( group );
auto groupMatches = matchItemsBySimilarity<PCB_GROUP>( oldGroups, newGroups );
std::unordered_set<PCB_GROUP*> matchedNewGroups;
for( const auto& match : groupMatches )
{
PCB_GROUP* oldGroup = match.first;
PCB_GROUP* newGroup = match.second;
matchedNewGroups.insert( newGroup );
const_cast<KIID&>( newGroup->m_Uuid ) = oldGroup->m_Uuid;
}
for( PCB_GROUP* newGroup : newGroups )
{
if( matchedNewGroups.find( newGroup ) == matchedNewGroups.end() )
const_cast<KIID&>( newGroup->m_Uuid ) = KIID();
}
std::vector<PCB_FIELD*> oldFieldsVec;
std::vector<PCB_FIELD*> 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<PCB_FIELD>( oldFieldsVec, newFieldsVec );
std::unordered_map<PCB_FIELD*, PCB_FIELD*> oldToNewFields;
std::unordered_set<PCB_FIELD*> matchedNewFields;
for( const auto& match : fieldMatches )
{
PCB_FIELD* oldField = match.first;
PCB_FIELD* newField = match.second;
oldToNewFields[ oldField ] = newField;
matchedNewFields.insert( newField );
const_cast<KIID&>( newField->m_Uuid ) = oldField->m_Uuid;
}
for( PCB_FIELD* newField : newFieldsVec )
{
if( matchedNewFields.find( newField ) == matchedNewFields.end() )
const_cast<KIID&>( newField->m_Uuid ) = KIID();
}
std::unordered_map<PCB_TEXT*, PCB_TEXT*> oldToNewTexts;
for( const auto& match : drawingMatches )
{
PCB_TEXT* oldText = dynamic_cast<PCB_TEXT*>( match.first );
PCB_TEXT* newText = dynamic_cast<PCB_TEXT*>( match.second );
if( oldText && newText )
oldToNewTexts[ oldText ] = newText;
}
std::set<PCB_TEXT*> handledTextItems;
for( BOARD_ITEM* oldItem : aExisting->GraphicalItems() )
@ -2506,7 +2714,12 @@ void PCB_EDIT_FRAME::ExchangeFootprint( FOOTPRINT* aExisting, FOOTPRINT* aNew,
if( dynamic_cast<PCB_DIMENSION_BASE*>( 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<KIID&>( aNew->m_Uuid ) = aExisting->m_Uuid;
aNew->SetPath( aExisting->GetPath() );
aNew->SetSheetfile( aExisting->GetSheetfile() );
aNew->SetSheetname( aExisting->GetSheetname() );

Loading…
Cancel
Save