269 std::vector<ITEM_CHANGE> children;
271 if( !aBefore || !aAfter )
277 std::vector<ITEM_DESCRIPTOR> beforeDesc;
278 std::vector<ITEM_DESCRIPTOR> afterDesc;
279 std::map<KIID_PATH, const BOARD_ITEM*> beforeMap;
280 std::map<KIID_PATH, const BOARD_ITEM*> afterMap;
291 d.
id.push_back( aChild->m_Uuid );
296 auto collect = [&](
const FOOTPRINT* aFp, std::vector<ITEM_DESCRIPTOR>& aOut,
297 std::map<KIID_PATH, const BOARD_ITEM*>& aMap )
328 collect( aBefore, beforeDesc, beforeMap );
329 collect( aAfter, afterDesc, afterMap );
337 for(
const auto& [idA, idB] : recon.
aToB )
339 auto itA = beforeMap.find( idA );
340 auto itB = afterMap.find( idB );
341 const BOARD_ITEM* a = itA == beforeMap.end() ? nullptr : itA->second;
342 const BOARD_ITEM* b = itB == afterMap.end() ? nullptr : itB->second;
347 std::vector<PROPERTY_DELTA> propDeltas;
352 if( propDeltas.empty() )
362 children.push_back( std::move( c ) );
365 auto isLibraryUuidNoise = [](
const BOARD_ITEM* aItem )
372 auto it = beforeMap.find( idA );
374 if( it == beforeMap.end() || !it->second )
379 if( isLibraryUuidNoise( a ) )
389 children.push_back( std::move( c ) );
394 auto it = afterMap.find( idB );
396 if( it == afterMap.end() || !it->second )
401 if( isLibraryUuidNoise( b ) )
411 children.push_back( std::move( c ) );
441 result.docType = wxS(
"kicad_pcb" );
450 std::vector<ITEM_DESCRIPTOR> beforeDesc;
451 std::vector<ITEM_DESCRIPTOR> afterDesc;
452 std::map<KIID, const BOARD_ITEM*> beforeMap;
453 std::map<KIID, const BOARD_ITEM*> afterMap;
455 beforeDesc.reserve( beforeSet.size() );
456 afterDesc.reserve( afterSet.size() );
464 beforeMap[item->m_Uuid] = item;
473 afterMap[item->m_Uuid] = item;
492 result.changes.push_back( std::move( c ) );
497 if( std::find_if(
result.changes.begin(),
result.changes.end(),
500 return aC.id == dup && aC.kind == CHANGE_KIND::DUPLICATE_UUID;
511 result.changes.push_back( std::move( c ) );
515 for(
const auto& [idA, idB] : recon.
aToB )
517 const KIID& uuidA = idA.back();
518 const KIID& uuidB = idB.back();
519 auto itA = beforeMap.find( uuidA );
520 auto itB = afterMap.find( uuidB );
521 const BOARD_ITEM* a = itA == beforeMap.end() ? nullptr : itA->second;
522 const BOARD_ITEM* b = itB == afterMap.end() ? nullptr : itB->second;
527 std::vector<PROPERTY_DELTA> propDeltas;
535 std::vector<ITEM_CHANGE> childChanges;
537 if(
auto fpA =
dynamic_cast<const FOOTPRINT*
>( a ) )
539 if(
auto fpB =
dynamic_cast<const FOOTPRINT*
>( b ) )
543 if( propDeltas.empty() && childChanges.empty() )
553 c.
children = std::move( childChanges );
554 result.changes.push_back( std::move( c ) );
562 auto isAutoGenerated = [](
const BOARD_ITEM* aItem )
570 if(
auto zone =
dynamic_cast<const ZONE*
>( aItem ) )
571 return zone->IsTeardropArea();
579 auto it = beforeMap.find( idA.back() );
581 if( it == beforeMap.end() || !it->second )
586 if( isAutoGenerated( a ) )
599 if(
auto fp =
dynamic_cast<const FOOTPRINT*
>( a ) )
601 std::vector<ITEM_CHANGE> dummyAfter;
608 result.changes.push_back( std::move( c ) );
614 auto it = afterMap.find( idB.back() );
616 if( it == afterMap.end() || !it->second )
621 if( isAutoGenerated( b ) )
632 if(
auto fp =
dynamic_cast<const FOOTPRINT*
>( b ) )
638 result.changes.push_back( std::move( c ) );
648 std::vector<PROPERTY_DELTA> docDeltas;
658 if( beforeThickness != afterThickness )
664 docDeltas.push_back( std::move( d ) );
676 auto summarizeStackup = [](
const BOARD_STACKUP& aStackup ) -> std::string
695 std::size_t h = std::hash<std::string>{}( aStackup.m_FinishType.ToStdString() );
696 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_HasDielectricConstrains ) );
697 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_HasThicknessConstrains ) );
698 h =
KiHashCombine( h, std::hash<bool>{}( aStackup.m_EdgePlating ) );
699 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( aStackup.m_EdgeConnectorConstraints ) ) );
706 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( item->GetType() ) ) );
707 h =
KiHashCombine( h, std::hash<int>{}(
static_cast<int>( item->GetBrdLayerId() ) ) );
709 h =
KiHashCombine( h, std::hash<bool>{}( item->IsEnabled() ) );
711 for(
int sub = 0; sub < item->GetSublayersCount(); ++sub )
713 h =
KiHashCombine( h, std::hash<int>{}( item->GetThickness( sub ) ) );
717 if( item->HasEpsilonRValue() )
718 h =
KiHashCombine( h, std::hash<double>{}( item->GetEpsilonR( sub ) ) );
720 if( item->HasLossTangentValue() )
721 h =
KiHashCombine( h, std::hash<double>{}( item->GetLossTangent( sub ) ) );
725 return wxString::Format( wxS(
"%d copper / %d dielectric layers (hash %zx)" ), copper, dielectric, h )
729 if( beforeStackup != afterStackup )
735 docDeltas.push_back( std::move( d ) );
744 if( beforeDRC != afterDRC )
750 docDeltas.push_back( std::move( d ) );
761 auto summarizeNetSettings = [](
const NET_SETTINGS& aSettings ) -> std::string
769 auto hashNetclass = [&](
const NETCLASS* nc )
775 hashCombine(
static_cast<std::size_t
>( nc->GetPriority() ) );
776 hashCombine(
static_cast<std::size_t
>( nc->GetClearanceOpt().value_or( -1 ) ) );
777 hashCombine(
static_cast<std::size_t
>( nc->GetTrackWidthOpt().value_or( -1 ) ) );
778 hashCombine(
static_cast<std::size_t
>( nc->GetViaDiameterOpt().value_or( -1 ) ) );
779 hashCombine(
static_cast<std::size_t
>( nc->GetViaDrillOpt().value_or( -1 ) ) );
780 hashCombine(
static_cast<std::size_t
>( nc->GetuViaDiameterOpt().value_or( -1 ) ) );
781 hashCombine(
static_cast<std::size_t
>( nc->GetuViaDrillOpt().value_or( -1 ) ) );
782 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairWidthOpt().value_or( -1 ) ) );
783 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairGapOpt().value_or( -1 ) ) );
784 hashCombine(
static_cast<std::size_t
>( nc->GetDiffPairViaGapOpt().value_or( -1 ) ) );
785 hashCombine(
static_cast<std::size_t
>( nc->GetWireWidthOpt().value_or( -1 ) ) );
786 hashCombine(
static_cast<std::size_t
>( nc->GetBusWidthOpt().value_or( -1 ) ) );
787 hashCombine(
static_cast<std::size_t
>( nc->GetLineStyleOpt().value_or( -1 ) ) );
796 hashNetclass( aSettings.GetDefaultNetclass().get() );
798 for(
const auto& [
name, nc] : aSettings.GetNetclasses() )
799 hashNetclass( nc.get() );
801 for(
const auto& [netname, classes] : aSettings.GetNetclassLabelAssignments() )
803 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
805 for(
const wxString& c : classes )
806 hashCombine( std::hash<std::string>{}( c.ToStdString() ) );
809 for(
const auto& [
chain, className] : aSettings.GetNetChainClasses() )
812 hashCombine( std::hash<std::string>{}( className.ToStdString() ) );
815 for(
const auto& [
chain, netclass] : aSettings.GetNetChainNetClasses() )
818 hashCombine( std::hash<std::string>{}( netclass.ToStdString() ) );
821 for(
const auto& [netname, color] : aSettings.GetNetColorAssignments() )
823 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
827 const std::size_t classCount = aSettings.GetNetclasses().size() + ( aSettings.GetDefaultNetclass() ? 1u : 0u );
829 return wxString::Format( wxS(
"%zu netclass(es) (hash %zx)" ), classCount, h ).ToStdString();
832 const std::shared_ptr<NET_SETTINGS>& beforeNet = beforeDS.
m_NetSettings;
833 const std::shared_ptr<NET_SETTINGS>& afterNet = afterDS.
m_NetSettings;
839 if( beforeNet && afterNet && *beforeNet != *afterNet )
845 docDeltas.push_back( std::move( d ) );
852 auto readSiblingDruContent = [](
const BOARD* aBoard ) -> wxString
855 return wxEmptyString;
857 wxString boardPath = aBoard->GetFileName();
859 if( boardPath.IsEmpty() )
860 return wxEmptyString;
862 wxFileName fn( boardPath );
865 if( !fn.FileExists() )
866 return wxEmptyString;
868 wxFile file( fn.GetFullPath() );
870 if( !file.IsOpened() )
871 return wxEmptyString;
874 file.ReadAll( &contents );
878 auto summarizeRules = [](
const wxString& aContents ) -> std::string
880 if( aContents.IsEmpty() )
881 return "(no custom rules)";
883 std::size_t h = std::hash<std::string>{}( aContents.ToStdString() );
884 return wxString::Format( wxS(
"%zu byte(s) (hash %zx)" ), aContents.size(), h ).ToStdString();
887 const wxString beforeRules = readSiblingDruContent(
m_before );
888 const wxString afterRules = readSiblingDruContent(
m_after );
890 if( beforeRules != afterRules )
896 docDeltas.push_back( std::move( d ) );
902 auto readProjectFileNamed = [](
const BOARD* aBoard,
const std::string& aFileName ) -> wxString
905 return wxEmptyString;
909 if( boardPath.IsEmpty() )
910 return wxEmptyString;
912 wxFileName fn( boardPath );
913 fn.SetFullName( wxString::FromUTF8( aFileName ) );
915 if( !fn.FileExists() )
916 return wxEmptyString;
918 wxFile file( fn.GetFullPath() );
920 if( !file.IsOpened() )
921 return wxEmptyString;
924 file.ReadAll( &contents );
928 auto summarizeTable = [](
const wxString& aContents,
const wxString& aLabel ) -> std::string
930 if( aContents.IsEmpty() )
931 return wxString::Format( wxS(
"(no %s)" ), aLabel ).ToStdString();
933 std::size_t h = std::hash<std::string>{}( aContents.ToStdString() );
934 return wxString::Format( wxS(
"%zu byte(s) (hash %zx)" ), aContents.size(), h ).ToStdString();
940 if( beforeFp != afterFp )
946 docDeltas.push_back( std::move( d ) );
952 if( beforeSym != afterSym )
958 docDeltas.push_back( std::move( d ) );
964 auto boardDrawingSheet = [](
const BOARD* aBoard ) -> wxString
967 return wxEmptyString;
972 const wxString beforeSheet = boardDrawingSheet(
m_before );
973 const wxString afterSheet = boardDrawingSheet(
m_after );
975 if( beforeSheet != afterSheet )
981 docDeltas.push_back( std::move( d ) );
984 if( !docDeltas.empty() )
992 result.changes.push_back( std::move( c ) );