305 std::vector<ITEM_CHANGE> children;
307 if( !aBefore || !aAfter )
312 std::vector<ITEM_DESCRIPTOR> beforeDesc;
313 std::vector<ITEM_DESCRIPTOR> afterDesc;
314 std::map<KIID_PATH, const BOARD_ITEM*> beforeMap;
315 std::map<KIID_PATH, const BOARD_ITEM*> afterMap;
327 auto collect = [&](
const FOOTPRINT* aFp, std::vector<ITEM_DESCRIPTOR>& aOut,
328 std::map<KIID_PATH, const BOARD_ITEM*>& aMap )
359 collect( aBefore, beforeDesc, beforeMap );
360 collect( aAfter, afterDesc, afterMap );
368 for(
const auto& [idA, idB] : recon.
aToB )
370 auto itA = beforeMap.find( idA );
371 auto itB = afterMap.find( idB );
372 const BOARD_ITEM* a = itA == beforeMap.end() ? nullptr : itA->second;
373 const BOARD_ITEM* b = itB == afterMap.end() ? nullptr : itB->second;
378 std::vector<PROPERTY_DELTA> propDeltas;
383 if( propDeltas.empty() )
393 children.push_back( std::move( c ) );
396 auto isLibraryUuidNoise = [](
const BOARD_ITEM* aItem )
403 auto it = beforeMap.find( idA );
405 if( it == beforeMap.end() || !it->second )
410 if( isLibraryUuidNoise( a ) )
420 children.push_back( std::move( c ) );
425 auto it = afterMap.find( idB );
427 if( it == afterMap.end() || !it->second )
432 if( isLibraryUuidNoise( b ) )
442 children.push_back( std::move( c ) );
472 result.docType = wxS(
"kicad_pcb" );
481 std::vector<ITEM_DESCRIPTOR> beforeDesc;
482 std::vector<ITEM_DESCRIPTOR> afterDesc;
483 std::map<KIID, const BOARD_ITEM*> beforeMap;
484 std::map<KIID, const BOARD_ITEM*> afterMap;
486 beforeDesc.reserve( beforeSet.size() );
487 afterDesc.reserve( afterSet.size() );
495 beforeMap[item->m_Uuid] = item;
504 afterMap[item->m_Uuid] = item;
523 result.changes.push_back( std::move( c ) );
528 if( std::find_if(
result.changes.begin(),
result.changes.end(),
531 return aC.id == dup && aC.kind == CHANGE_KIND::DUPLICATE_UUID;
542 result.changes.push_back( std::move( c ) );
546 for(
const auto& [idA, idB] : recon.
aToB )
548 const KIID& uuidA = idA.back();
549 const KIID& uuidB = idB.back();
550 auto itA = beforeMap.find( uuidA );
551 auto itB = afterMap.find( uuidB );
552 const BOARD_ITEM* a = itA == beforeMap.end() ? nullptr : itA->second;
553 const BOARD_ITEM* b = itB == afterMap.end() ? nullptr : itB->second;
558 std::vector<PROPERTY_DELTA> propDeltas;
566 std::vector<ITEM_CHANGE> childChanges;
568 if(
auto fpA =
dynamic_cast<const FOOTPRINT*
>( a ) )
570 if(
auto fpB =
dynamic_cast<const FOOTPRINT*
>( b ) )
574 if( propDeltas.empty() && childChanges.empty() )
584 c.
children = std::move( childChanges );
585 result.changes.push_back( std::move( c ) );
593 auto isAutoGenerated = [](
const BOARD_ITEM* aItem )
601 if(
auto zone =
dynamic_cast<const ZONE*
>( aItem ) )
602 return zone->IsTeardropArea();
610 auto it = beforeMap.find( idA.back() );
612 if( it == beforeMap.end() || !it->second )
617 if( isAutoGenerated( a ) )
630 if(
auto fp =
dynamic_cast<const FOOTPRINT*
>( a ) )
632 std::vector<ITEM_CHANGE> dummyAfter;
639 result.changes.push_back( std::move( c ) );
645 auto it = afterMap.find( idB.back() );
647 if( it == afterMap.end() || !it->second )
652 if( isAutoGenerated( b ) )
663 if(
auto fp =
dynamic_cast<const FOOTPRINT*
>( b ) )
669 result.changes.push_back( std::move( c ) );
679 std::vector<PROPERTY_DELTA> docDeltas;
690 if( beforeThickness != afterThickness )
696 docDeltas.push_back( std::move( d ) );
699 auto summarizePlotOptions = [](
const PCB_PLOT_PARAMS& aParams ) -> std::string
702 aParams.Format( &formatter );
703 return wxString::Format( wxS(
"hash %zx" ), std::hash<std::string>{}( formatter.
GetString() ) )
707 const std::string beforePlot = summarizePlotOptions(
m_before->GetPlotOptions() );
708 const std::string afterPlot = summarizePlotOptions(
m_after->GetPlotOptions() );
710 if( beforePlot != afterPlot )
716 docDeltas.push_back( std::move( d ) );
725 docDeltas.push_back( std::move( d ) );
729 auto appendKeyedDeltas =
730 [&](
const wxString& aPrefix,
const std::map<wxString, wxString>& aBefore,
731 const std::map<wxString, wxString>& aAfter )
733 std::set<wxString> names;
735 for(
const auto& [
name, value] : aBefore )
736 names.insert(
name );
738 for(
const auto& [
name, value] : aAfter )
739 names.insert(
name );
741 auto render = [](
const std::map<wxString, wxString>& aMap,
const wxString& aName )
743 auto it = aMap.find( aName );
745 if( it == aMap.end() )
746 return wxString( wxS(
"(none)" ) );
748 return wxS(
"\"" ) + it->second + wxS(
"\"" );
751 for(
const wxString&
name : names )
753 const wxString before =
render( aBefore,
name );
754 const wxString after =
render( aAfter,
name );
756 if( before != after )
762 docDeltas.push_back( std::move( d ) );
767 auto boardVariants = [](
const BOARD* aBoard )
769 std::map<wxString, wxString>
result;
771 for(
const wxString&
name : aBoard->GetVariantNames() )
789 auto summarizeStackup = [](
const BOARD_STACKUP& aStackup ) -> std::string
808 std::size_t h = std::hash<std::string>{}( aStackup.m_FinishType.ToStdString() );
809 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_HasDielectricConstrains ) );
810 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_HasThicknessConstrains ) );
811 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_EdgePlating ) );
812 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( aStackup.m_EdgeConnectorConstraints ) ) );
819 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( item->GetType() ) ) );
820 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( item->GetBrdLayerId() ) ) );
822 h =
KiHashCombine( h, std::hash<bool>{}( item->IsEnabled() ) );
824 for(
int sub = 0; sub < item->GetSublayersCount(); ++sub )
826 h =
KiHashCombine( h, std::hash<int>{}( item->GetThickness( sub ) ) );
830 if( item->HasEpsilonRValue() )
831 h =
KiHashCombine( h, std::hash<double>{}( item->GetEpsilonR( sub ) ) );
833 if( item->HasLossTangentValue() )
834 h =
KiHashCombine( h, std::hash<double>{}( item->GetLossTangent( sub ) ) );
838 return wxString::Format( wxS(
"%d copper / %d dielectric layers (hash %zx)" ), copper, dielectric, h )
842 if( beforeStackup != afterStackup )
848 docDeltas.push_back( std::move( d ) );
857 if( beforeDRC != afterDRC )
863 docDeltas.push_back( std::move( d ) );
874 auto summarizeNetSettings = [](
const NET_SETTINGS& aSettings ) -> std::string
882 auto hashNetclass = [&](
const NETCLASS* nc )
888 hashCombine(
static_cast<std::size_t
>( nc->GetPriority() ) );
889 hashCombine(
static_cast<std::size_t
>( nc->GetClearanceOpt().value_or( -1 ) ) );
890 hashCombine(
static_cast<std::size_t
>( nc->GetTrackWidthOpt().value_or( -1 ) ) );
891 hashCombine(
static_cast<std::size_t
>( nc->GetViaDiameterOpt().value_or( -1 ) ) );
892 hashCombine(
static_cast<std::size_t
>( nc->GetViaDrillOpt().value_or( -1 ) ) );
893 hashCombine(
static_cast<std::size_t
>( nc->GetuViaDiameterOpt().value_or( -1 ) ) );
894 hashCombine(
static_cast<std::size_t
>( nc->GetuViaDrillOpt().value_or( -1 ) ) );
895 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairWidthOpt().value_or( -1 ) ) );
896 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairGapOpt().value_or( -1 ) ) );
897 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairViaGapOpt().value_or( -1 ) ) );
898 hashCombine(
static_cast<std::size_t
>( nc->GetWireWidthOpt().value_or( -1 ) ) );
899 hashCombine(
static_cast<std::size_t
>( nc->GetBusWidthOpt().value_or( -1 ) ) );
900 hashCombine(
static_cast<std::size_t
>( nc->GetLineStyleOpt().value_or( -1 ) ) );
909 hashNetclass( aSettings.GetDefaultNetclass().get() );
911 for(
const auto& [
name, nc] : aSettings.GetNetclasses() )
912 hashNetclass( nc.get() );
914 for(
const auto& [netname, classes] : aSettings.GetNetclassLabelAssignments() )
916 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
918 for(
const wxString& c : classes )
919 hashCombine( std::hash<std::string>{}( c.ToStdString() ) );
922 for(
const auto& [
chain, className] : aSettings.GetNetChainClasses() )
925 hashCombine( std::hash<std::string>{}( className.ToStdString() ) );
928 for(
const auto& [
chain, netclass] : aSettings.GetNetChainNetClasses() )
931 hashCombine( std::hash<std::string>{}( netclass.ToStdString() ) );
934 for(
const auto& [netname, color] : aSettings.GetNetColorAssignments() )
936 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
940 const std::size_t classCount = aSettings.GetNetclasses().size() + ( aSettings.GetDefaultNetclass() ? 1u : 0u );
942 return wxString::Format( wxS(
"%zu netclass(es) (hash %zx)" ), classCount, h ).ToStdString();
945 const std::shared_ptr<NET_SETTINGS>& beforeNet = beforeDS.
m_NetSettings;
946 const std::shared_ptr<NET_SETTINGS>& afterNet = afterDS.
m_NetSettings;
952 if( beforeNet && afterNet && *beforeNet != *afterNet )
958 docDeltas.push_back( std::move( d ) );
965 auto readSiblingDruContent = [](
const BOARD* aBoard ) -> wxString
968 return wxEmptyString;
970 wxString boardPath = aBoard->GetFileName();
972 if( boardPath.IsEmpty() )
973 return wxEmptyString;
975 wxFileName fn( boardPath );
978 if( !fn.FileExists() )
979 return wxEmptyString;
981 wxFile file( fn.GetFullPath() );
983 if( !file.IsOpened() )
984 return wxEmptyString;
987 file.ReadAll( &contents );
991 auto summarizeRules = [](
const wxString& aContents ) -> std::string
993 if( aContents.IsEmpty() )
994 return "(no custom rules)";
996 std::size_t h = std::hash<std::string>{}( aContents.ToStdString() );
997 return wxString::Format( wxS(
"%zu byte(s) (hash %zx)" ), aContents.size(), h ).ToStdString();
1000 const wxString beforeRules = readSiblingDruContent(
m_before );
1001 const wxString afterRules = readSiblingDruContent(
m_after );
1003 if( beforeRules != afterRules )
1009 docDeltas.push_back( std::move( d ) );
1015 auto readProjectFileNamed = [](
const BOARD* aBoard,
const std::string& aFileName ) -> wxString
1018 return wxEmptyString;
1022 if( boardPath.IsEmpty() )
1023 return wxEmptyString;
1025 wxFileName fn( boardPath );
1026 fn.SetFullName( wxString::FromUTF8( aFileName ) );
1028 if( !fn.FileExists() )
1029 return wxEmptyString;
1031 wxFile file( fn.GetFullPath() );
1033 if( !file.IsOpened() )
1034 return wxEmptyString;
1037 file.ReadAll( &contents );
1041 auto summarizeTable = [](
const wxString& aContents,
const wxString& aLabel ) -> std::string
1043 if( aContents.IsEmpty() )
1044 return wxString::Format( wxS(
"(no %s)" ), aLabel ).ToStdString();
1046 std::size_t h = std::hash<std::string>{}( aContents.ToStdString() );
1047 return wxString::Format( wxS(
"%zu byte(s) (hash %zx)" ), aContents.size(), h ).ToStdString();
1053 if( beforeFp != afterFp )
1059 docDeltas.push_back( std::move( d ) );
1065 if( beforeSym != afterSym )
1071 docDeltas.push_back( std::move( d ) );
1077 auto boardDrawingSheet = [](
const BOARD* aBoard ) -> wxString
1080 return wxEmptyString;
1085 const wxString beforeSheet = boardDrawingSheet(
m_before );
1086 const wxString afterSheet = boardDrawingSheet(
m_after );
1088 if( beforeSheet != afterSheet )
1094 docDeltas.push_back( std::move( d ) );
1100 std::set<wxString> fileNames;
1103 fileNames.insert(
name );
1106 fileNames.insert(
name );
1110 return aFile ? wxString::FromUTF8( aFile->data_hash ) : wxString( wxS(
"(none)" ) );
1113 for(
const wxString&
name : fileNames )
1118 if( beforeHash != afterHash )
1124 docDeltas.push_back( std::move( d ) );
1134 docDeltas.push_back( std::move( d ) );
1137 if( !docDeltas.empty() )
1145 result.changes.push_back( std::move( c ) );