303 std::vector<ITEM_CHANGE> children;
305 if( !aBefore || !aAfter )
310 std::vector<ITEM_DESCRIPTOR> beforeDesc;
311 std::vector<ITEM_DESCRIPTOR> afterDesc;
312 std::map<KIID_PATH, const BOARD_ITEM*> beforeMap;
313 std::map<KIID_PATH, const BOARD_ITEM*> afterMap;
325 auto collect = [&](
const FOOTPRINT* aFp, std::vector<ITEM_DESCRIPTOR>& aOut,
326 std::map<KIID_PATH, const BOARD_ITEM*>& aMap )
357 collect( aBefore, beforeDesc, beforeMap );
358 collect( aAfter, afterDesc, afterMap );
366 for(
const auto& [idA, idB] : recon.
aToB )
368 auto itA = beforeMap.find( idA );
369 auto itB = afterMap.find( idB );
370 const BOARD_ITEM* a = itA == beforeMap.end() ? nullptr : itA->second;
371 const BOARD_ITEM* b = itB == afterMap.end() ? nullptr : itB->second;
376 std::vector<PROPERTY_DELTA> propDeltas;
381 if( propDeltas.empty() )
391 children.push_back( std::move( c ) );
394 auto isLibraryUuidNoise = [](
const BOARD_ITEM* aItem )
401 auto it = beforeMap.find( idA );
403 if( it == beforeMap.end() || !it->second )
408 if( isLibraryUuidNoise( a ) )
418 children.push_back( std::move( c ) );
423 auto it = afterMap.find( idB );
425 if( it == afterMap.end() || !it->second )
430 if( isLibraryUuidNoise( b ) )
440 children.push_back( std::move( c ) );
470 result.docType = wxS(
"kicad_pcb" );
479 std::vector<ITEM_DESCRIPTOR> beforeDesc;
480 std::vector<ITEM_DESCRIPTOR> afterDesc;
481 std::map<KIID, const BOARD_ITEM*> beforeMap;
482 std::map<KIID, const BOARD_ITEM*> afterMap;
484 beforeDesc.reserve( beforeSet.size() );
485 afterDesc.reserve( afterSet.size() );
493 beforeMap[item->m_Uuid] = item;
502 afterMap[item->m_Uuid] = item;
521 result.changes.push_back( std::move( c ) );
526 if( std::find_if(
result.changes.begin(),
result.changes.end(),
529 return aC.id == dup && aC.kind == CHANGE_KIND::DUPLICATE_UUID;
540 result.changes.push_back( std::move( c ) );
544 for(
const auto& [idA, idB] : recon.
aToB )
546 const KIID& uuidA = idA.back();
547 const KIID& uuidB = idB.back();
548 auto itA = beforeMap.find( uuidA );
549 auto itB = afterMap.find( uuidB );
550 const BOARD_ITEM* a = itA == beforeMap.end() ? nullptr : itA->second;
551 const BOARD_ITEM* b = itB == afterMap.end() ? nullptr : itB->second;
556 std::vector<PROPERTY_DELTA> propDeltas;
564 std::vector<ITEM_CHANGE> childChanges;
566 if(
auto fpA =
dynamic_cast<const FOOTPRINT*
>( a ) )
568 if(
auto fpB =
dynamic_cast<const FOOTPRINT*
>( b ) )
572 if( propDeltas.empty() && childChanges.empty() )
582 c.
children = std::move( childChanges );
583 result.changes.push_back( std::move( c ) );
591 auto isAutoGenerated = [](
const BOARD_ITEM* aItem )
599 if(
auto zone =
dynamic_cast<const ZONE*
>( aItem ) )
600 return zone->IsTeardropArea();
608 auto it = beforeMap.find( idA.back() );
610 if( it == beforeMap.end() || !it->second )
615 if( isAutoGenerated( a ) )
628 if(
auto fp =
dynamic_cast<const FOOTPRINT*
>( a ) )
630 std::vector<ITEM_CHANGE> dummyAfter;
637 result.changes.push_back( std::move( c ) );
643 auto it = afterMap.find( idB.back() );
645 if( it == afterMap.end() || !it->second )
650 if( isAutoGenerated( b ) )
661 if(
auto fp =
dynamic_cast<const FOOTPRINT*
>( b ) )
667 result.changes.push_back( std::move( c ) );
677 std::vector<PROPERTY_DELTA> docDeltas;
687 if( beforeThickness != afterThickness )
693 docDeltas.push_back( std::move( d ) );
705 auto summarizeStackup = [](
const BOARD_STACKUP& aStackup ) -> std::string
724 std::size_t h = std::hash<std::string>{}( aStackup.m_FinishType.ToStdString() );
725 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_HasDielectricConstrains ) );
726 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_HasThicknessConstrains ) );
727 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_EdgePlating ) );
728 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( aStackup.m_EdgeConnectorConstraints ) ) );
735 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( item->GetType() ) ) );
736 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( item->GetBrdLayerId() ) ) );
738 h =
KiHashCombine( h, std::hash<bool>{}( item->IsEnabled() ) );
740 for(
int sub = 0; sub < item->GetSublayersCount(); ++sub )
742 h =
KiHashCombine( h, std::hash<int>{}( item->GetThickness( sub ) ) );
746 if( item->HasEpsilonRValue() )
747 h =
KiHashCombine( h, std::hash<double>{}( item->GetEpsilonR( sub ) ) );
749 if( item->HasLossTangentValue() )
750 h =
KiHashCombine( h, std::hash<double>{}( item->GetLossTangent( sub ) ) );
754 return wxString::Format( wxS(
"%d copper / %d dielectric layers (hash %zx)" ), copper, dielectric, h )
758 if( beforeStackup != afterStackup )
764 docDeltas.push_back( std::move( d ) );
773 if( beforeDRC != afterDRC )
779 docDeltas.push_back( std::move( d ) );
790 auto summarizeNetSettings = [](
const NET_SETTINGS& aSettings ) -> std::string
798 auto hashNetclass = [&](
const NETCLASS* nc )
804 hashCombine(
static_cast<std::size_t
>( nc->GetPriority() ) );
805 hashCombine(
static_cast<std::size_t
>( nc->GetClearanceOpt().value_or( -1 ) ) );
806 hashCombine(
static_cast<std::size_t
>( nc->GetTrackWidthOpt().value_or( -1 ) ) );
807 hashCombine(
static_cast<std::size_t
>( nc->GetViaDiameterOpt().value_or( -1 ) ) );
808 hashCombine(
static_cast<std::size_t
>( nc->GetViaDrillOpt().value_or( -1 ) ) );
809 hashCombine(
static_cast<std::size_t
>( nc->GetuViaDiameterOpt().value_or( -1 ) ) );
810 hashCombine(
static_cast<std::size_t
>( nc->GetuViaDrillOpt().value_or( -1 ) ) );
811 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairWidthOpt().value_or( -1 ) ) );
812 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairGapOpt().value_or( -1 ) ) );
813 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairViaGapOpt().value_or( -1 ) ) );
814 hashCombine(
static_cast<std::size_t
>( nc->GetWireWidthOpt().value_or( -1 ) ) );
815 hashCombine(
static_cast<std::size_t
>( nc->GetBusWidthOpt().value_or( -1 ) ) );
816 hashCombine(
static_cast<std::size_t
>( nc->GetLineStyleOpt().value_or( -1 ) ) );
825 hashNetclass( aSettings.GetDefaultNetclass().get() );
827 for(
const auto& [
name, nc] : aSettings.GetNetclasses() )
828 hashNetclass( nc.get() );
830 for(
const auto& [netname, classes] : aSettings.GetNetclassLabelAssignments() )
832 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
834 for(
const wxString& c : classes )
835 hashCombine( std::hash<std::string>{}( c.ToStdString() ) );
838 for(
const auto& [
chain, className] : aSettings.GetNetChainClasses() )
841 hashCombine( std::hash<std::string>{}( className.ToStdString() ) );
844 for(
const auto& [
chain, netclass] : aSettings.GetNetChainNetClasses() )
847 hashCombine( std::hash<std::string>{}( netclass.ToStdString() ) );
850 for(
const auto& [netname, color] : aSettings.GetNetColorAssignments() )
852 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
856 const std::size_t classCount = aSettings.GetNetclasses().size() + ( aSettings.GetDefaultNetclass() ? 1u : 0u );
858 return wxString::Format( wxS(
"%zu netclass(es) (hash %zx)" ), classCount, h ).ToStdString();
861 const std::shared_ptr<NET_SETTINGS>& beforeNet = beforeDS.
m_NetSettings;
862 const std::shared_ptr<NET_SETTINGS>& afterNet = afterDS.
m_NetSettings;
868 if( beforeNet && afterNet && *beforeNet != *afterNet )
874 docDeltas.push_back( std::move( d ) );
881 auto readSiblingDruContent = [](
const BOARD* aBoard ) -> wxString
884 return wxEmptyString;
886 wxString boardPath = aBoard->GetFileName();
888 if( boardPath.IsEmpty() )
889 return wxEmptyString;
891 wxFileName fn( boardPath );
894 if( !fn.FileExists() )
895 return wxEmptyString;
897 wxFile file( fn.GetFullPath() );
899 if( !file.IsOpened() )
900 return wxEmptyString;
903 file.ReadAll( &contents );
907 auto summarizeRules = [](
const wxString& aContents ) -> std::string
909 if( aContents.IsEmpty() )
910 return "(no custom rules)";
912 std::size_t h = std::hash<std::string>{}( aContents.ToStdString() );
913 return wxString::Format( wxS(
"%zu byte(s) (hash %zx)" ), aContents.size(), h ).ToStdString();
916 const wxString beforeRules = readSiblingDruContent(
m_before );
917 const wxString afterRules = readSiblingDruContent(
m_after );
919 if( beforeRules != afterRules )
925 docDeltas.push_back( std::move( d ) );
931 auto readProjectFileNamed = [](
const BOARD* aBoard,
const std::string& aFileName ) -> wxString
934 return wxEmptyString;
938 if( boardPath.IsEmpty() )
939 return wxEmptyString;
941 wxFileName fn( boardPath );
942 fn.SetFullName( wxString::FromUTF8( aFileName ) );
944 if( !fn.FileExists() )
945 return wxEmptyString;
947 wxFile file( fn.GetFullPath() );
949 if( !file.IsOpened() )
950 return wxEmptyString;
953 file.ReadAll( &contents );
957 auto summarizeTable = [](
const wxString& aContents,
const wxString& aLabel ) -> std::string
959 if( aContents.IsEmpty() )
960 return wxString::Format( wxS(
"(no %s)" ), aLabel ).ToStdString();
962 std::size_t h = std::hash<std::string>{}( aContents.ToStdString() );
963 return wxString::Format( wxS(
"%zu byte(s) (hash %zx)" ), aContents.size(), h ).ToStdString();
969 if( beforeFp != afterFp )
975 docDeltas.push_back( std::move( d ) );
981 if( beforeSym != afterSym )
987 docDeltas.push_back( std::move( d ) );
993 auto boardDrawingSheet = [](
const BOARD* aBoard ) -> wxString
996 return wxEmptyString;
1001 const wxString beforeSheet = boardDrawingSheet(
m_before );
1002 const wxString afterSheet = boardDrawingSheet(
m_after );
1004 if( beforeSheet != afterSheet )
1010 docDeltas.push_back( std::move( d ) );
1013 if( !docDeltas.empty() )
1021 result.changes.push_back( std::move( c ) );