KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_differ.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 */
20
21#include "pcb_differ.h"
22
25#include <hashtables.h>
26
27#include <board.h>
29#include <project.h>
32#include <netclass.h>
33#include <richio.h>
35
36#include <wx/file.h>
37#include <wx/filename.h>
39#include <board_item.h>
40#include <footprint.h>
41#include <pad.h>
42#include <pcb_track.h>
43#include <pcb_shape.h>
44#include <pcb_text.h>
45#include <pcb_field.h>
46#include <zone.h>
47#include <properties/property.h>
49
50#include <algorithm>
51#include <set>
52
53
54namespace KICAD_DIFF
55{
56
58{
59 if( const PCB_FIELD* field = dynamic_cast<const PCB_FIELD*>( &aItem ) )
60 {
61 if( const FOOTPRINT* fp = field->GetParentFootprint() )
62 return KIID::FromDeterministicString( fp->m_Uuid.AsString() + wxS( ":" ) + field->GetUntranslatedName() );
63 }
64
65 return aItem.m_Uuid;
66}
67
68
69BOARD_ITEM* FindPcbDiffItem( const BOARD* aBoard, const KIID& aId )
70{
71 if( !aBoard )
72 return nullptr;
73
74 // ResolveItem uses the board's UUID cache for top-level items and footprint children.
75 // Request nullptr on a miss instead of the DELETED_BOARD_ITEM sentinel so field diff
76 // IDs reach the name-based lookup below and missing items still return nullptr.
77 if( BOARD_ITEM* item = aBoard->ResolveItem( aId, /* aAllowNullptrReturn */ true ) )
78 return item;
79
80 for( FOOTPRINT* fp : aBoard->Footprints() )
81 {
82 for( PCB_FIELD* field : fp->GetFields() )
83 {
84 if( PcbDiffItemId( *field ) == aId )
85 return field;
86 }
87 }
88
89 return nullptr;
90}
91
92
93PCB_DIFFER::PCB_DIFFER( const BOARD* aBefore, const BOARD* aAfter, const wxString& aPath ) :
94 m_before( aBefore ),
95 m_after( aAfter ),
96 m_path( aPath )
97{
98}
99
100
101PCB_DIFFER::~PCB_DIFFER() = default;
102
103
104wxString PCB_DIFFER::itemTypeName( const BOARD_ITEM* aItem )
105{
106 if( !aItem )
107 return wxEmptyString;
108
109 if( const PCB_FIELD* field = dynamic_cast<const PCB_FIELD*>( aItem ) )
110 return field->GetName();
111
112 return aItem->GetClass();
113}
114
115
116std::optional<wxString> PCB_DIFFER::itemRefdes( const BOARD_ITEM* aItem )
117{
118 if( auto fp = dynamic_cast<const FOOTPRINT*>( aItem ) )
119 return fp->GetReference();
120
121 if( auto track = dynamic_cast<const PCB_TRACK*>( aItem ) )
122 {
123 if( track->GetNetCode() > 0 && !track->GetNetname().IsEmpty() )
124 return track->GetNetname();
125 }
126
127 if( auto fp = dynamic_cast<const FOOTPRINT*>( aItem ? aItem->GetParent() : nullptr ) )
128 {
129 // Pad / footprint-child: borrow the parent's refdes for cross-probing.
130 return fp->GetReference();
131 }
132
133 return std::nullopt;
134}
135
136
138{
140 d.id = KIID_PATH();
141 d.id.push_back( aItem->m_Uuid );
142 d.type = itemTypeName( aItem );
143 d.position = aItem->GetPosition();
144 d.bbox = aItem->GetBoundingBox();
145
146 // keyProps for similarity fallback: identifying fields specific to the
147 // item type. Footprints carry their library id (very stable); tracks
148 // and pads carry net code.
149 if( auto fp = dynamic_cast<const FOOTPRINT*>( aItem ) )
150 {
151 d.keyProps.emplace_back( wxS( "lib_id" ), fp->GetFPIDAsString().ToStdString() );
152 d.keyProps.emplace_back( wxS( "reference" ), fp->GetReference().ToStdString() );
153 }
154 else if( auto track = dynamic_cast<const PCB_TRACK*>( aItem ) )
155 {
156 d.keyProps.emplace_back( wxS( "net" ), std::to_string( track->GetNetCode() ) );
157 d.keyProps.emplace_back( wxS( "layer" ), std::to_string( track->GetLayer() ) );
158 }
159 else if( auto pad = dynamic_cast<const PAD*>( aItem ) )
160 {
161 d.keyProps.emplace_back( wxS( "number" ), pad->GetNumber().ToStdString() );
162 d.keyProps.emplace_back( wxS( "net" ), std::to_string( pad->GetNetCode() ) );
163 }
164 else if( auto zone = dynamic_cast<const ZONE*>( aItem ) )
165 {
166 d.keyProps.emplace_back( wxS( "name" ), zone->GetZoneName().ToStdString() );
167 d.keyProps.emplace_back( wxS( "net" ), std::to_string( zone->GetNetCode() ) );
168 }
169
170 return d;
171}
172
173
174// Library metadata that is never worth showing, for any change kind.
175static bool pcbLibraryMetadataNoise( const wxString& aName )
176{
177 return aName == wxS( "Library Link" ) || aName == wxS( "Library Description" ) || aName == wxS( "Keywords" );
178}
179
180
181// Project-default propagation and library-reorganization noise. Inside a
182// footprint, per-child layout shifts follow the parent's move, so they are
183// noise too.
184static bool pcbDiffPropertyIsNoise( const wxString& aName, bool aInsideFootprint )
185{
186 const bool globalNoise = pcbLibraryMetadataNoise( aName ) || aName == wxS( "Auto Thickness" )
187 || aName == wxS( "Keep Upright" ) || aName == wxS( "Thickness" )
188 || aName == wxS( "Enable Teardrops" ) || aName == wxS( "Thermal Relief Spoke Angle" )
189 || aName == wxS( "Pin Name" ) || aName == wxS( "Net" ) || aName == wxS( "Pad Shape" );
190
191 if( globalNoise )
192 return true;
193
194 const bool childLayoutNoise = aName == wxS( "Position X" ) || aName == wxS( "Position Y" )
195 || aName == wxS( "Start X" ) || aName == wxS( "Start Y" ) || aName == wxS( "End X" )
196 || aName == wxS( "End Y" ) || aName == wxS( "Center X" ) || aName == wxS( "Center Y" )
197 || aName == wxS( "Orientation" ) || aName == wxS( "Height" )
198 || aName == wxS( "Width" ) || aName == wxS( "Layer" ) || aName == wxS( "Line Width" )
199 || aName == wxS( "Hole Size X" ) || aName == wxS( "Hole Size Y" );
200
201 return aInsideFootprint && childLayoutNoise;
202}
203
204
205// Full property list for an added or removed item. Only library metadata is
206// dropped: net, pad shape, geometry and the like describe what was added or
207// removed and are worth showing, unlike in the modified case.
208static std::vector<PROPERTY_DELTA> pcbAddedRemovedProperties( const BOARD_ITEM* aItem, bool aAsAfter )
209{
210 std::vector<PROPERTY_DELTA> deltas = ItemProperties( aItem, aAsAfter );
211
212 std::erase_if( deltas,
213 [&]( const PROPERTY_DELTA& d )
214 {
215 return pcbLibraryMetadataNoise( d.name );
216 } );
217
218 return deltas;
219}
220
221
222std::vector<PROPERTY_DELTA> PCB_DIFFER::diffProperties( const BOARD_ITEM* aBefore, const BOARD_ITEM* aAfter ) const
223{
224 auto deltas = DiffItemProperties( aBefore, aAfter );
225
226 const bool insideFootprint = aBefore && aBefore->GetParent() && aBefore->GetParent()->Type() == PCB_FOOTPRINT_T;
227
228 std::erase_if( deltas,
229 [&]( const PROPERTY_DELTA& d )
230 {
231 return pcbDiffPropertyIsNoise( d.name, insideFootprint );
232 } );
233
234 if( auto zoneA = dynamic_cast<const ZONE*>( aBefore ) )
235 {
236 if( auto zoneB = dynamic_cast<const ZONE*>( aAfter ) )
237 {
238 auto toPolygonSet = []( const SHAPE_POLY_SET* aPoly ) -> DIFF_VALUE::PolygonSet
239 {
241
242 if( !aPoly )
243 return out;
244
245 for( int o = 0; o < aPoly->OutlineCount(); ++o )
246 {
247 const auto& polyA = aPoly->CPolygon( o );
248 std::vector<std::vector<VECTOR2I>> contours;
249
250 for( const auto& contour : polyA )
251 {
252 std::vector<VECTOR2I> pts;
253 pts.reserve( contour.PointCount() );
254
255 for( int p = 0; p < contour.PointCount(); ++p )
256 pts.push_back( contour.CPoint( p ) );
257
258 contours.push_back( std::move( pts ) );
259 }
260
261 out.push_back( std::move( contours ) );
262 }
263
264 return out;
265 };
266
267 DIFF_VALUE::PolygonSet outlineA = toPolygonSet( zoneA->Outline() );
268 DIFF_VALUE::PolygonSet outlineB = toPolygonSet( zoneB->Outline() );
269
270 if( outlineA != outlineB )
271 {
273 d.name = wxS( "Outline" );
274 d.before = DIFF_VALUE::FromPolygonSet( std::move( outlineA ) );
275 d.after = DIFF_VALUE::FromPolygonSet( std::move( outlineB ) );
276 deltas.push_back( std::move( d ) );
277 }
278
279 for( PCB_LAYER_ID layer : zoneB->GetLayerSet().Seq() )
280 {
281 if( !zoneA->GetLayerSet().Contains( layer ) )
282 continue;
283
284 DIFF_VALUE::PolygonSet fillA = toPolygonSet( zoneA->GetFilledPolysList( layer ).get() );
285 DIFF_VALUE::PolygonSet fillB = toPolygonSet( zoneB->GetFilledPolysList( layer ).get() );
286
287 if( fillA == fillB )
288 continue;
289
291 d.name = wxString::Format( wxS( "Filled Area (%s)" ), LayerName( layer ) );
292 d.before = DIFF_VALUE::FromPolygonSet( std::move( fillA ) );
293 d.after = DIFF_VALUE::FromPolygonSet( std::move( fillB ) );
294 deltas.push_back( std::move( d ) );
295 }
296 }
297 }
298
299 return deltas;
300}
301
302
303std::vector<ITEM_CHANGE> PCB_DIFFER::diffFootprintChildren( const FOOTPRINT* aBefore, const FOOTPRINT* aAfter ) const
304{
305 std::vector<ITEM_CHANGE> children;
306
307 if( !aBefore || !aAfter )
308 return children;
309
310 // Keep the parent footprint in the path so consumers can group child changes
311 // and the merge applier can locate their destination footprint.
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;
316
317 auto childDescriptor = [&]( const FOOTPRINT* aFp, const BOARD_ITEM* aChild ) -> ITEM_DESCRIPTOR
318 {
319 ITEM_DESCRIPTOR d = makeDescriptor( aChild );
320 d.id = KIID_PATH();
321 d.id.push_back( aFp->m_Uuid );
322 d.id.push_back( PcbDiffItemId( *aChild ) );
323
324 return d;
325 };
326
327 auto collect = [&]( const FOOTPRINT* aFp, std::vector<ITEM_DESCRIPTOR>& aOut,
328 std::map<KIID_PATH, const BOARD_ITEM*>& aMap )
329 {
330 for( const PAD* pad : aFp->Pads() )
331 {
332 ITEM_DESCRIPTOR d = childDescriptor( aFp, pad );
333 aOut.push_back( d );
334 aMap[d.id] = pad;
335 }
336
337 for( const BOARD_ITEM* item : aFp->GraphicalItems() )
338 {
339 ITEM_DESCRIPTOR d = childDescriptor( aFp, item );
340 aOut.push_back( d );
341 aMap[d.id] = item;
342 }
343
344 for( const ZONE* zone : aFp->Zones() )
345 {
346 ITEM_DESCRIPTOR d = childDescriptor( aFp, zone );
347 aOut.push_back( d );
348 aMap[d.id] = zone;
349 }
350
351 for( const PCB_FIELD* field : aFp->GetFields() )
352 {
353 ITEM_DESCRIPTOR d = childDescriptor( aFp, field );
354 aOut.push_back( d );
355 aMap[d.id] = field;
356 }
357 };
358
359 collect( aBefore, beforeDesc, beforeMap );
360 collect( aAfter, afterDesc, afterMap );
361
362 IDENTITY_RECONCILER reconciler( m_options.identity );
363 RECONCILIATION recon = reconciler.Reconcile( beforeDesc, afterDesc );
364
365 // Matched pairs: compute property deltas. Items count as "changed" if any
366 // property surfaced a delta OR if their semantic operator== rejects equality
367 // (which covers fields that aren't exposed through PROPERTY_MANAGER).
368 for( const auto& [idA, idB] : recon.aToB )
369 {
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;
374
375 if( !a || !b )
376 continue;
377
378 std::vector<PROPERTY_DELTA> propDeltas;
379
380 if( m_options.deepCompare )
381 propDeltas = diffProperties( a, b );
382
383 if( propDeltas.empty() )
384 continue;
385
386 ITEM_CHANGE c;
387 c.id = idA;
388 c.typeName = itemTypeName( a );
390 c.bbox = b->GetBoundingBox();
391 c.refdes = itemRefdes( b );
392 c.properties = std::move( propDeltas );
393 children.push_back( std::move( c ) );
394 }
395
396 auto isLibraryUuidNoise = []( const BOARD_ITEM* aItem )
397 {
398 return aItem && ( aItem->Type() == PCB_SHAPE_T || aItem->Type() == PCB_TEXT_T );
399 };
400
401 for( const KIID_PATH& idA : recon.aOnly )
402 {
403 auto it = beforeMap.find( idA );
404
405 if( it == beforeMap.end() || !it->second )
406 continue;
407
408 const BOARD_ITEM* a = it->second;
409
410 if( isLibraryUuidNoise( a ) )
411 continue;
412
413 ITEM_CHANGE c;
414 c.id = idA;
415 c.typeName = itemTypeName( a );
417 c.bbox = a->GetBoundingBox();
418 c.refdes = itemRefdes( a );
419 c.properties = pcbAddedRemovedProperties( a, /*aAsAfter=*/false );
420 children.push_back( std::move( c ) );
421 }
422
423 for( const KIID_PATH& idB : recon.bOnly )
424 {
425 auto it = afterMap.find( idB );
426
427 if( it == afterMap.end() || !it->second )
428 continue;
429
430 const BOARD_ITEM* b = it->second;
431
432 if( isLibraryUuidNoise( b ) )
433 continue;
434
435 ITEM_CHANGE c;
436 c.id = idB;
437 c.typeName = itemTypeName( b );
439 c.bbox = b->GetBoundingBox();
440 c.refdes = itemRefdes( b );
441 c.properties = pcbAddedRemovedProperties( b, /*aAsAfter=*/true );
442 children.push_back( std::move( c ) );
443 }
444
445 sortChanges( children );
446 return children;
447}
448
449
450void PCB_DIFFER::sortChanges( std::vector<ITEM_CHANGE>& aChanges )
451{
452 std::sort( aChanges.begin(), aChanges.end(),
453 []( const ITEM_CHANGE& aL, const ITEM_CHANGE& aR )
454 {
455 if( aL.id < aR.id )
456 return true;
457 if( aR.id < aL.id )
458 return false;
459
460 if( aL.typeName != aR.typeName )
461 return aL.typeName < aR.typeName;
462
463 return static_cast<int>( aL.kind ) < static_cast<int>( aR.kind );
464 } );
465}
466
467
469{
471 result.path = m_path;
472 result.docType = wxS( "kicad_pcb" );
473
474 if( !m_before || !m_after )
475 return result;
476
477 // Build descriptors for the top-level item set.
478 const BOARD_ITEM_SET beforeSet = m_before->GetItemSet();
479 const BOARD_ITEM_SET afterSet = m_after->GetItemSet();
480
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;
485
486 beforeDesc.reserve( beforeSet.size() );
487 afterDesc.reserve( afterSet.size() );
488
489 for( const BOARD_ITEM* item : beforeSet )
490 {
491 if( !item )
492 continue;
493
494 beforeDesc.push_back( makeDescriptor( item ) );
495 beforeMap[item->m_Uuid] = item;
496 }
497
498 for( const BOARD_ITEM* item : afterSet )
499 {
500 if( !item )
501 continue;
502
503 afterDesc.push_back( makeDescriptor( item ) );
504 afterMap[item->m_Uuid] = item;
505 }
506
507 if( m_options.progress )
508 m_options.progress( 0.2 );
509
510 IDENTITY_RECONCILER reconciler( m_options.identity );
511 RECONCILIATION recon = reconciler.Reconcile( beforeDesc, afterDesc );
512
513 if( m_options.progress )
514 m_options.progress( 0.5 );
515
516 // Duplicate-UUID records (within either side).
517 for( const KIID_PATH& dup : recon.duplicatesA )
518 {
519 ITEM_CHANGE c;
520 c.id = dup;
521 c.typeName = wxS( "BOARD_ITEM" );
523 result.changes.push_back( std::move( c ) );
524 }
525
526 for( const KIID_PATH& dup : recon.duplicatesB )
527 {
528 if( std::find_if( result.changes.begin(), result.changes.end(),
529 [&]( const ITEM_CHANGE& aC )
530 {
531 return aC.id == dup && aC.kind == CHANGE_KIND::DUPLICATE_UUID;
532 } )
533 != result.changes.end() )
534 {
535 continue;
536 }
537
538 ITEM_CHANGE c;
539 c.id = dup;
540 c.typeName = wxS( "BOARD_ITEM" );
542 result.changes.push_back( std::move( c ) );
543 }
544
545 // Matched pairs.
546 for( const auto& [idA, idB] : recon.aToB )
547 {
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;
554
555 if( !a || !b )
556 continue;
557
558 std::vector<PROPERTY_DELTA> propDeltas;
559
560 if( m_options.deepCompare )
561 propDeltas = diffProperties( a, b );
562
563 // For footprints, also walk children. A footprint may compare unequal
564 // because a child changed, in which case the parent's properties
565 // themselves haven't moved — we still need to record children.
566 std::vector<ITEM_CHANGE> childChanges;
567
568 if( auto fpA = dynamic_cast<const FOOTPRINT*>( a ) )
569 {
570 if( auto fpB = dynamic_cast<const FOOTPRINT*>( b ) )
571 childChanges = diffFootprintChildren( fpA, fpB );
572 }
573
574 if( propDeltas.empty() && childChanges.empty() )
575 continue;
576
577 ITEM_CHANGE c;
578 c.id = idA;
579 c.typeName = itemTypeName( a );
581 c.bbox = b->GetBoundingBox();
582 c.refdes = itemRefdes( b );
583 c.properties = std::move( propDeltas );
584 c.children = std::move( childChanges );
585 result.changes.push_back( std::move( c ) );
586 }
587
588 // Auto-generated items (teardrops, etc.) flip in lockstep with a global
589 // setting rather than being user-authored. Their presence on one side and
590 // not the other is reported as the Enable Teardrops setting change, not
591 // as N separate add/remove records. Teardrops historically render as
592 // ZONEs flagged with IsTeardropArea, hence the second branch.
593 auto isAutoGenerated = []( const BOARD_ITEM* aItem )
594 {
595 if( !aItem )
596 return false;
597
598 if( aItem->Type() == PCB_GENERATOR_T )
599 return true;
600
601 if( auto zone = dynamic_cast<const ZONE*>( aItem ) )
602 return zone->IsTeardropArea();
603
604 return false;
605 };
606
607 // Items present only in ancestor: REMOVED.
608 for( const KIID_PATH& idA : recon.aOnly )
609 {
610 auto it = beforeMap.find( idA.back() );
611
612 if( it == beforeMap.end() || !it->second )
613 continue;
614
615 const BOARD_ITEM* a = it->second;
616
617 if( isAutoGenerated( a ) )
618 continue;
619
620 ITEM_CHANGE c;
621 c.id = idA;
622 c.typeName = itemTypeName( a );
624 c.bbox = a->GetBoundingBox();
625 c.refdes = itemRefdes( a );
626 c.properties = pcbAddedRemovedProperties( a, /*aAsAfter=*/false );
627
628 // For footprints, snapshot child items so the consumer can show what's
629 // being removed without re-walking.
630 if( auto fp = dynamic_cast<const FOOTPRINT*>( a ) )
631 {
632 std::vector<ITEM_CHANGE> dummyAfter;
633 FOOTPRINT empty( nullptr );
634 // diffFootprintChildren needs two footprints; producing an empty
635 // "after" gives us a REMOVED record per child.
637 }
638
639 result.changes.push_back( std::move( c ) );
640 }
641
642 // Items present only in after: ADDED.
643 for( const KIID_PATH& idB : recon.bOnly )
644 {
645 auto it = afterMap.find( idB.back() );
646
647 if( it == afterMap.end() || !it->second )
648 continue;
649
650 const BOARD_ITEM* b = it->second;
651
652 if( isAutoGenerated( b ) )
653 continue;
654
655 ITEM_CHANGE c;
656 c.id = idB;
657 c.typeName = itemTypeName( b );
659 c.bbox = b->GetBoundingBox();
660 c.refdes = itemRefdes( b );
661 c.properties = pcbAddedRemovedProperties( b, /*aAsAfter=*/true );
662
663 if( auto fp = dynamic_cast<const FOOTPRINT*>( b ) )
664 {
665 FOOTPRINT empty( nullptr );
667 }
668
669 result.changes.push_back( std::move( c ) );
670 }
671
672 // Document-level settings — board thickness, paper format. These aren't
673 // walked items so the per-item loop above can't catch a change to them;
674 // emit a single synthetic ITEM_CHANGE with an empty KIID_PATH so the
675 // merge engine can plan a resolution on it and the applier knows which
676 // side's settings to carry over. Without this the applier defaults to
677 // the new (empty) result BOARD's settings and silently reverts both
678 // sides' divergent changes to defaults.
679 std::vector<PROPERTY_DELTA> docDeltas;
680
681 AppendPaperDeltas( docDeltas, m_before->GetPageSettings(), m_after->GetPageSettings() );
682 AppendTitleBlockDeltas( docDeltas, m_before->GetTitleBlock(), m_after->GetTitleBlock() );
683
684 const BOARD_DESIGN_SETTINGS& beforeDS = m_before->GetDesignSettings();
685 const BOARD_DESIGN_SETTINGS& afterDS = m_after->GetDesignSettings();
686
687 const int beforeThickness = beforeDS.GetBoardThickness();
688 const int afterThickness = afterDS.GetBoardThickness();
689
690 if( beforeThickness != afterThickness )
691 {
694 d.before = DIFF_VALUE::FromInt( beforeThickness );
695 d.after = DIFF_VALUE::FromInt( afterThickness );
696 docDeltas.push_back( std::move( d ) );
697 }
698
699 auto summarizePlotOptions = []( const PCB_PLOT_PARAMS& aParams ) -> std::string
700 {
701 STRING_FORMATTER formatter;
702 aParams.Format( &formatter );
703 return wxString::Format( wxS( "hash %zx" ), std::hash<std::string>{}( formatter.GetString() ) )
704 .ToStdString();
705 };
706
707 const std::string beforePlot = summarizePlotOptions( m_before->GetPlotOptions() );
708 const std::string afterPlot = summarizePlotOptions( m_after->GetPlotOptions() );
709
710 if( beforePlot != afterPlot )
711 {
714 d.before = DIFF_VALUE::FromString( beforePlot );
715 d.after = DIFF_VALUE::FromString( afterPlot );
716 docDeltas.push_back( std::move( d ) );
717 }
718
719 if( m_before->LegacyTeardrops() != m_after->LegacyTeardrops() )
720 {
723 d.before = DIFF_VALUE::FromBool( m_before->LegacyTeardrops() );
724 d.after = DIFF_VALUE::FromBool( m_after->LegacyTeardrops() );
725 docDeltas.push_back( std::move( d ) );
726 }
727
728 // Board properties and variants get one property each, so edits to different entries merge cleanly
729 auto appendKeyedDeltas =
730 [&]( const wxString& aPrefix, const std::map<wxString, wxString>& aBefore,
731 const std::map<wxString, wxString>& aAfter )
732 {
733 std::set<wxString> names;
734
735 for( const auto& [name, value] : aBefore )
736 names.insert( name );
737
738 for( const auto& [name, value] : aAfter )
739 names.insert( name );
740
741 auto render = []( const std::map<wxString, wxString>& aMap, const wxString& aName )
742 {
743 auto it = aMap.find( aName );
744
745 if( it == aMap.end() )
746 return wxString( wxS( "(none)" ) );
747
748 return wxS( "\"" ) + it->second + wxS( "\"" );
749 };
750
751 for( const wxString& name : names )
752 {
753 const wxString before = render( aBefore, name );
754 const wxString after = render( aAfter, name );
755
756 if( before != after )
757 {
759 d.name = aPrefix + name;
760 d.before = DIFF_VALUE::FromString( before );
761 d.after = DIFF_VALUE::FromString( after );
762 docDeltas.push_back( std::move( d ) );
763 }
764 }
765 };
766
767 auto boardVariants = []( const BOARD* aBoard )
768 {
769 std::map<wxString, wxString> result;
770
771 for( const wxString& name : aBoard->GetVariantNames() )
772 result[name] = aBoard->GetVariantDescription( name );
773
774 return result;
775 };
776
777 appendKeyedDeltas( DOC_PROP_BOARD_PROPERTY, m_before->GetProperties(), m_after->GetProperties() );
778 appendKeyedDeltas( DOC_PROP_VARIANT, boardVariants( m_before ), boardVariants( m_after ) );
779
780 // Stackup is structural (layer count, dielectric materials, copper
781 // weights) so a per-field walk would explode the change list. Detect
782 // any change with BOARD_STACKUP::operator== and emit a single delta
783 // carrying a human-readable summary; the applier copies whole
784 // BOARD_DESIGN_SETTINGS so a TAKE_OURS / TAKE_THEIRS resolution preserves
785 // the chosen side's full stackup.
786 const BOARD_STACKUP& beforeStackup = beforeDS.GetStackupDescriptor();
787 const BOARD_STACKUP& afterStackup = afterDS.GetStackupDescriptor();
788
789 auto summarizeStackup = []( const BOARD_STACKUP& aStackup ) -> std::string
790 {
791 int copper = 0;
792 int dielectric = 0;
793
794 for( const BOARD_STACKUP_ITEM* item : aStackup.GetList() )
795 {
796 if( !item )
797 continue;
798
799 if( item->GetType() == BS_ITEM_TYPE_COPPER )
800 ++copper;
801 else if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
802 ++dielectric;
803 }
804
805 // Content hash matching BOARD_STACKUP::operator== fields so
806 // before/after render differently when any compared field
807 // changed and the copper/dielectric counts didn't.
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 ) ) );
813
814 for( const BOARD_STACKUP_ITEM* item : aStackup.GetList() )
815 {
816 if( !item )
817 continue;
818
819 h = KiHashCombine( h, std::hash<int>{}( static_cast<int>( item->GetType() ) ) );
820 h = KiHashCombine( h, std::hash<int>{}( static_cast<int>( item->GetBrdLayerId() ) ) );
821 h = KiHashCombine( h, std::hash<std::string>{}( item->GetLayerName().ToStdString() ) );
822 h = KiHashCombine( h, std::hash<bool>{}( item->IsEnabled() ) );
823
824 for( int sub = 0; sub < item->GetSublayersCount(); ++sub )
825 {
826 h = KiHashCombine( h, std::hash<int>{}( item->GetThickness( sub ) ) );
827 h = KiHashCombine( h, std::hash<std::string>{}( item->GetMaterial( sub ).ToStdString() ) );
828 h = KiHashCombine( h, std::hash<std::string>{}( item->GetColor( sub ).ToStdString() ) );
829
830 if( item->HasEpsilonRValue() )
831 h = KiHashCombine( h, std::hash<double>{}( item->GetEpsilonR( sub ) ) );
832
833 if( item->HasLossTangentValue() )
834 h = KiHashCombine( h, std::hash<double>{}( item->GetLossTangent( sub ) ) );
835 }
836 }
837
838 return wxString::Format( wxS( "%d copper / %d dielectric layers (hash %zx)" ), copper, dielectric, h )
839 .ToStdString();
840 };
841
842 if( beforeStackup != afterStackup )
843 {
846 d.before = DIFF_VALUE::FromString( summarizeStackup( beforeStackup ) );
847 d.after = DIFF_VALUE::FromString( summarizeStackup( afterStackup ) );
848 docDeltas.push_back( std::move( d ) );
849 }
850
851 // DRC severity overrides live in the project file. Diff only fires when
852 // sibling .kicad_pro files were loaded — for plain .kicad_pcb temp blobs
853 // (git mergetool case) both sides see defaults and we never get here.
854 const std::map<int, SEVERITY>& beforeDRC = beforeDS.m_DRCSeverities;
855 const std::map<int, SEVERITY>& afterDRC = afterDS.m_DRCSeverities;
856
857 if( beforeDRC != afterDRC )
858 {
863 docDeltas.push_back( std::move( d ) );
864 }
865
866 // Net classes live in PROJECT_FILE via NET_SETTINGS. Both NET_SETTINGS
867 // instances are reached via BOARD_DESIGN_SETTINGS::m_NetSettings (a
868 // shared_ptr). After A1, NET_SETTINGS::operator== is content-aware
869 // (covers default-netclass in-place edits + per-named-class parameter
870 // edits + label / pattern / color / chain-class maps), so equality
871 // alone is enough to gate the delta. Render a count + content hash
872 // summary so two configurations with the same class count but
873 // different parameters produce distinct before / after strings.
874 auto summarizeNetSettings = []( const NET_SETTINGS& aSettings ) -> std::string
875 {
876 std::size_t h = 0;
877 auto hashCombine = [&h]( std::size_t v )
878 {
879 h = KiHashCombine( h, v );
880 };
881
882 auto hashNetclass = [&]( const NETCLASS* nc )
883 {
884 if( !nc )
885 return;
886
887 hashCombine( std::hash<std::string>{}( nc->GetName().ToStdString() ) );
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 ) ) );
901 hashCombine( std::hash<std::string>{}( nc->GetTuningProfile().ToStdString() ) );
902
903 // Per-netclass color overrides are part of NETCLASS::EqualsByPersistedFields;
904 // include them so a color-only edit produces a distinct rendered summary.
905 hashCombine( std::hash<std::string>{}( nc->GetSchematicColor( true ).ToCSSString().ToStdString() ) );
906 hashCombine( std::hash<std::string>{}( nc->GetPcbColor( true ).ToCSSString().ToStdString() ) );
907 };
908
909 hashNetclass( aSettings.GetDefaultNetclass().get() );
910
911 for( const auto& [name, nc] : aSettings.GetNetclasses() )
912 hashNetclass( nc.get() );
913
914 for( const auto& [netname, classes] : aSettings.GetNetclassLabelAssignments() )
915 {
916 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
917
918 for( const wxString& c : classes )
919 hashCombine( std::hash<std::string>{}( c.ToStdString() ) );
920 }
921
922 for( const auto& [chain, className] : aSettings.GetNetChainClasses() )
923 {
924 hashCombine( std::hash<std::string>{}( chain.ToStdString() ) );
925 hashCombine( std::hash<std::string>{}( className.ToStdString() ) );
926 }
927
928 for( const auto& [chain, netclass] : aSettings.GetNetChainNetClasses() )
929 {
930 hashCombine( std::hash<std::string>{}( chain.ToStdString() ) );
931 hashCombine( std::hash<std::string>{}( netclass.ToStdString() ) );
932 }
933
934 for( const auto& [netname, color] : aSettings.GetNetColorAssignments() )
935 {
936 hashCombine( std::hash<std::string>{}( netname.ToStdString() ) );
937 hashCombine( std::hash<std::string>{}( color.ToCSSString().ToStdString() ) );
938 }
939
940 const std::size_t classCount = aSettings.GetNetclasses().size() + ( aSettings.GetDefaultNetclass() ? 1u : 0u );
941
942 return wxString::Format( wxS( "%zu netclass(es) (hash %zx)" ), classCount, h ).ToStdString();
943 };
944
945 const std::shared_ptr<NET_SETTINGS>& beforeNet = beforeDS.m_NetSettings;
946 const std::shared_ptr<NET_SETTINGS>& afterNet = afterDS.m_NetSettings;
947
948 // Diff fires only when both sides have NET_SETTINGS instances. A null
949 // pointer on either side means the BOARD wasn't loaded with a project file
950 // (e.g. plain .kicad_pcb temp blob from git mergetool); skip silently —
951 // matches DRC severities behaviour.
952 if( beforeNet && afterNet && *beforeNet != *afterNet )
953 {
956 d.before = DIFF_VALUE::FromString( summarizeNetSettings( *beforeNet ) );
957 d.after = DIFF_VALUE::FromString( summarizeNetSettings( *afterNet ) );
958 docDeltas.push_back( std::move( d ) );
959 }
960
961 // Custom DRC rules live in a sibling .kicad_dru file next to the .kicad_pcb.
962 // The file content isn't stored on BOARD; we read it from disk. For
963 // headless / temp-blob merges (git mergetool) the sibling file usually
964 // isn't present and both sides return empty strings, so no delta fires.
965 auto readSiblingDruContent = []( const BOARD* aBoard ) -> wxString
966 {
967 if( !aBoard )
968 return wxEmptyString;
969
970 wxString boardPath = aBoard->GetFileName();
971
972 if( boardPath.IsEmpty() )
973 return wxEmptyString;
974
975 wxFileName fn( boardPath );
977
978 if( !fn.FileExists() )
979 return wxEmptyString;
980
981 wxFile file( fn.GetFullPath() );
982
983 if( !file.IsOpened() )
984 return wxEmptyString;
985
986 wxString contents;
987 file.ReadAll( &contents );
988 return contents;
989 };
990
991 auto summarizeRules = []( const wxString& aContents ) -> std::string
992 {
993 if( aContents.IsEmpty() )
994 return "(no custom rules)";
995
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();
998 };
999
1000 const wxString beforeRules = readSiblingDruContent( m_before );
1001 const wxString afterRules = readSiblingDruContent( m_after );
1002
1003 if( beforeRules != afterRules )
1004 {
1007 d.before = DIFF_VALUE::FromString( summarizeRules( beforeRules ) );
1008 d.after = DIFF_VALUE::FromString( summarizeRules( afterRules ) );
1009 docDeltas.push_back( std::move( d ) );
1010 }
1011
1012 // Footprint library table (fp-lib-table) lives in the project directory
1013 // (no extension). Read content directly; the same content-comparison
1014 // pattern as custom DRC rules applies.
1015 auto readProjectFileNamed = []( const BOARD* aBoard, const std::string& aFileName ) -> wxString
1016 {
1017 if( !aBoard )
1018 return wxEmptyString;
1019
1020 wxString boardPath = aBoard->GetFileName();
1021
1022 if( boardPath.IsEmpty() )
1023 return wxEmptyString;
1024
1025 wxFileName fn( boardPath );
1026 fn.SetFullName( wxString::FromUTF8( aFileName ) );
1027
1028 if( !fn.FileExists() )
1029 return wxEmptyString;
1030
1031 wxFile file( fn.GetFullPath() );
1032
1033 if( !file.IsOpened() )
1034 return wxEmptyString;
1035
1036 wxString contents;
1037 file.ReadAll( &contents );
1038 return contents;
1039 };
1040
1041 auto summarizeTable = []( const wxString& aContents, const wxString& aLabel ) -> std::string
1042 {
1043 if( aContents.IsEmpty() )
1044 return wxString::Format( wxS( "(no %s)" ), aLabel ).ToStdString();
1045
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();
1048 };
1049
1050 const wxString beforeFp = readProjectFileNamed( m_before, FILEEXT::FootprintLibraryTableFileName );
1051 const wxString afterFp = readProjectFileNamed( m_after, FILEEXT::FootprintLibraryTableFileName );
1052
1053 if( beforeFp != afterFp )
1054 {
1057 d.before = DIFF_VALUE::FromString( summarizeTable( beforeFp, wxS( "fp-lib-table" ) ) );
1058 d.after = DIFF_VALUE::FromString( summarizeTable( afterFp, wxS( "fp-lib-table" ) ) );
1059 docDeltas.push_back( std::move( d ) );
1060 }
1061
1062 const wxString beforeSym = readProjectFileNamed( m_before, FILEEXT::SymbolLibraryTableFileName );
1063 const wxString afterSym = readProjectFileNamed( m_after, FILEEXT::SymbolLibraryTableFileName );
1064
1065 if( beforeSym != afterSym )
1066 {
1069 d.before = DIFF_VALUE::FromString( summarizeTable( beforeSym, wxS( "sym-lib-table" ) ) );
1070 d.after = DIFF_VALUE::FromString( summarizeTable( afterSym, wxS( "sym-lib-table" ) ) );
1071 docDeltas.push_back( std::move( d ) );
1072 }
1073
1074 // Drawing sheet file path lives in PROJECT_FILE::m_BoardDrawingSheetFile.
1075 // Resolved to absolute on load; for diff purposes the stored path is
1076 // what serializes back, so compare those strings directly.
1077 auto boardDrawingSheet = []( const BOARD* aBoard ) -> wxString
1078 {
1079 if( !aBoard || !aBoard->GetProject() )
1080 return wxEmptyString;
1081
1083 };
1084
1085 const wxString beforeSheet = boardDrawingSheet( m_before );
1086 const wxString afterSheet = boardDrawingSheet( m_after );
1087
1088 if( beforeSheet != afterSheet )
1089 {
1092 d.before = DIFF_VALUE::FromString( beforeSheet );
1093 d.after = DIFF_VALUE::FromString( afterSheet );
1094 docDeltas.push_back( std::move( d ) );
1095 }
1096
1097 // One property per embedded file, so files added on different sides merge cleanly
1098 const EMBEDDED_FILES* beforeFiles = m_before->GetEmbeddedFiles();
1099 const EMBEDDED_FILES* afterFiles = m_after->GetEmbeddedFiles();
1100 std::set<wxString> fileNames;
1101
1102 for( const auto& [name, file] : beforeFiles->EmbeddedFileMap() )
1103 fileNames.insert( name );
1104
1105 for( const auto& [name, file] : afterFiles->EmbeddedFileMap() )
1106 fileNames.insert( name );
1107
1108 auto fileHash = []( const EMBEDDED_FILES::EMBEDDED_FILE* aFile ) -> wxString
1109 {
1110 return aFile ? wxString::FromUTF8( aFile->data_hash ) : wxString( wxS( "(none)" ) );
1111 };
1112
1113 for( const wxString& name : fileNames )
1114 {
1115 const wxString beforeHash = fileHash( beforeFiles->GetEmbeddedFile( name ) );
1116 const wxString afterHash = fileHash( afterFiles->GetEmbeddedFile( name ) );
1117
1118 if( beforeHash != afterHash )
1119 {
1122 d.before = DIFF_VALUE::FromString( beforeHash );
1123 d.after = DIFF_VALUE::FromString( afterHash );
1124 docDeltas.push_back( std::move( d ) );
1125 }
1126 }
1127
1128 if( beforeFiles->GetAreFontsEmbedded() != afterFiles->GetAreFontsEmbedded() )
1129 {
1132 d.before = DIFF_VALUE::FromBool( beforeFiles->GetAreFontsEmbedded() );
1133 d.after = DIFF_VALUE::FromBool( afterFiles->GetAreFontsEmbedded() );
1134 docDeltas.push_back( std::move( d ) );
1135 }
1136
1137 if( !docDeltas.empty() )
1138 {
1139 ITEM_CHANGE c;
1140 c.id = KIID_PATH(); // empty path = document-scope sentinel
1141 c.typeName = wxS( "BOARD" );
1143 c.bbox = BOX2I(); // document-scoped, no spatial location
1144 c.properties = std::move( docDeltas );
1145 result.changes.push_back( std::move( c ) );
1146 }
1147
1148 sortChanges( result.changes );
1149
1150 if( m_options.progress )
1151 m_options.progress( 1.0 );
1152
1153 return result;
1154}
1155
1156} // namespace KICAD_DIFF
const char * name
std::set< BOARD_ITEM *, CompareByUuid > BOARD_ITEM_SET
Set of BOARD_ITEMs ordered by UUID.
Definition board.h:395
static void hashCombine(std::size_t &aSeed, std::size_t aValue)
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
int GetBoardThickness() const
The full thickness of the board including copper and masks.
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:267
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const FOOTPRINTS & Footprints() const
Definition board.h:465
const wxString & GetFileName() const
Definition board.h:454
PROJECT * GetProject() const
Definition board.h:774
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2142
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
EMBEDDED_FILE * GetEmbeddedFile(const wxString &aName) const
Returns the embedded file with the given name or nullptr if it does not exist.
const std::map< wxString, std::shared_ptr< EMBEDDED_FILE > > & EmbeddedFileMap() const
Provide an iterable view of the file collection.
bool GetAreFontsEmbedded() const
ZONES & Zones()
Definition footprint.h:361
std::deque< PAD * > & Pads()
Definition footprint.h:355
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
DRAWINGS & GraphicalItems()
Definition footprint.h:358
static DIFF_VALUE FromInt(int aValue)
static DIFF_VALUE FromBool(bool aValue)
static DIFF_VALUE FromString(const wxString &aValue)
std::vector< std::vector< std::vector< VECTOR2I > > > PolygonSet
static DIFF_VALUE FromPolygonSet(PolygonSet aValue)
Reconciles item identity across two snapshots of the same document.
RECONCILIATION Reconcile(const std::vector< ITEM_DESCRIPTOR > &aA, const std::vector< ITEM_DESCRIPTOR > &aB) const
const BOARD * m_after
Definition pcb_differ.h:97
static void sortChanges(std::vector< ITEM_CHANGE > &aChanges)
Stable, deterministic sort of ITEM_CHANGEs (by id, then typeName, then kind).
const BOARD * m_before
Definition pcb_differ.h:96
PCB_DIFFER(const BOARD *aBefore, const BOARD *aAfter, const wxString &aPath=wxEmptyString)
std::vector< ITEM_CHANGE > diffFootprintChildren(const FOOTPRINT *aBefore, const FOOTPRINT *aAfter) const
Construct a child-level diff for nested items inside a footprint pair.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
static std::optional< wxString > itemRefdes(const BOARD_ITEM *aItem)
Extract a presentation label: footprint refdes, or routing net name for tracks/vias.
std::vector< PROPERTY_DELTA > diffProperties(const BOARD_ITEM *aBefore, const BOARD_ITEM *aAfter) const
Compute property deltas between two items of the same dynamic type.
static wxString itemTypeName(const BOARD_ITEM *aItem)
Convert the dynamic class string for an item into the type name used in diffs.
ITEM_DESCRIPTOR makeDescriptor(const BOARD_ITEM *aItem) const
Build the ITEM_DESCRIPTOR for the reconciler from a BOARD_ITEM.
virtual wxString GetClass() const =0
Return the class name.
Definition kiid.h:46
static KIID FromDeterministicString(const wxString &aName)
Build a deterministic UUID from an arbitrary name string.
Definition kiid.cpp:325
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:44
NET_SETTINGS stores various net-related settings in a project context.
Definition pad.h:61
Parameters and options when plotting/printing a board.
wxString m_BoardDrawingSheetFile
PcbNew params.
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
Represent a set of closed polygons.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
const std::string & GetString()
Definition richio.h:453
Handle a list of polygons defining a copper zone.
Definition zone.h:70
static bool empty(const wxTextEntryBase *aCtrl)
static std::string ToStdString(const wxString &aStr)
static const std::string SymbolLibraryTableFileName
static const std::string FootprintLibraryTableFileName
static const std::string DesignRulesFileExtension
std::size_t KiHashCombine(std::size_t aSeed, std::size_t aValue)
Fold aValue into the running hash aSeed using the well-known Boost hash_combine mixing step.
Definition hashtables.h:42
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
const wxString DOC_PROP_SYM_LIB_TABLE
const wxString DOC_PROP_VARIANT
const wxString DOC_PROP_BOARD_THICKNESS
std::vector< PROPERTY_DELTA > DiffItemProperties(const INSPECTABLE *aBefore, const INSPECTABLE *aAfter)
Enumerate the property deltas between two items of the same dynamic type.
const wxString DOC_PROP_PLOT_OPTIONS
Board-level settings stored in the board file.
const wxString DOC_PROP_NET_CLASSES
std::string SummarizeSeverities(const SeverityMap &aMap)
Format a severity-override map (DRC or ERC, keyed by error code, value is a SEVERITY enum) as a short...
std::vector< PROPERTY_DELTA > ItemProperties(const INSPECTABLE *aItem, bool aAsAfter)
List one item's properties as one-sided deltas for an added or removed item.
void AppendTitleBlockDeltas(std::vector< PROPERTY_DELTA > &aDeltas, const TITLE_BLOCK &aBefore, const TITLE_BLOCK &aAfter)
Append a delta for every title block field that differs between the two documents,...
static bool pcbDiffPropertyIsNoise(const wxString &aName, bool aInsideFootprint)
const wxString DOC_PROP_CUSTOM_RULES
const wxString DOC_PROP_LEGACY_TEARDROPS
const wxString DOC_PROP_FP_LIB_TABLE
void AppendPaperDeltas(std::vector< PROPERTY_DELTA > &aDeltas, const PAGE_INFO &aBefore, const PAGE_INFO &aAfter)
Append DOC_PROP_PAGE_FORMAT and/or DOC_PROP_PAGE_ORIENTATION deltas to aDeltas when the two PAGE_INFO...
const wxString DOC_PROP_EMBED_FONTS
BOARD_ITEM * FindPcbDiffItem(const BOARD *aBoard, const KIID &aId)
Resolve an actual UUID or a footprint field's diff ID on a board.
const wxString DOC_PROP_LAYER_STACKUP
const wxString DOC_PROP_DRAWING_SHEET
static std::vector< PROPERTY_DELTA > pcbAddedRemovedProperties(const BOARD_ITEM *aItem, bool aAsAfter)
const wxString DOC_PROP_DRC_SEVERITIES
KIID PcbDiffItemId(const EDA_ITEM &aItem)
Fields match by parent footprint and untranslated name, including after a library refresh.
const wxString DOC_PROP_EMBEDDED_FILE
Prefix of the property for one embedded file, followed by the file name.
static bool pcbLibraryMetadataNoise(const wxString &aName)
const wxString DOC_PROP_BOARD_PROPERTY
Prefixes of the property for one board property or variant, followed by its name.
The full set of changes between two parsed documents of one type.
One change record on a single item.
std::vector< PROPERTY_DELTA > properties
std::optional< wxString > refdes
std::vector< ITEM_CHANGE > children
Descriptor used by the identity reconciler to compare items across two documents.
std::vector< std::pair< wxString, std::string > > keyProps
Single (name, before, after) triple for one mutated property on an item.
Maps every item in document A to either a peer in document B or to "only-in-A", and vice versa.
std::set< KIID_PATH > aOnly
std::vector< KIID_PATH > duplicatesA
std::map< KIID_PATH, KIID_PATH > aToB
std::vector< KIID_PATH > duplicatesB
std::set< KIID_PATH > bOnly
const SHAPE_LINE_CHAIN chain
static wxString render(const std::vector< TEXT_DIFF_LINE > &aLines)
wxString result
Test unit parsing edge cases and error handling.
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
Definition of file extensions used in Kicad.