KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_merge_applier.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 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "sch_merge_applier.h"
25#include "sch_diff_utils.h"
26
29
30#include <schematic.h>
31#include <schematic_settings.h>
32#include <erc/erc_settings.h>
33#include <sch_item.h>
34#include <sch_screen.h>
35#include <sch_sheet.h>
36#include <sch_sheet_path.h>
37#include <sch_reference_list.h>
38#include <sch_symbol.h>
39#include <properties/property.h>
41#include <trace_helpers.h>
42
43#include <set>
44#include <utility>
45
46#include <wx/log.h>
47
48
49namespace KICAD_DIFF
50{
51
56static bool isSheetItem( const SCH_ITEM* aItem )
57{
58 return aItem && aItem->Type() == SCH_SHEET_T;
59}
60
61
66static int rootFileFormatVersion( const SCHEMATIC* aSchematic )
67{
68 if( !aSchematic )
69 return 0;
70
71 SCH_SCREEN* root = aSchematic->RootScreen();
72 return root ? root->GetFileFormatVersionAtLoad() : 0;
73}
74
75
77 const SCHEMATIC* aTheirs, MERGE_PLAN aPlan ) :
78 m_ancestor( aAncestor ),
79 m_ours( aOurs ),
80 m_theirs( aTheirs ),
81 m_plan( std::move( aPlan ) )
82{}
83
84
85std::map<KIID_PATH, SCH_MERGE_APPLIER::PathedItem>
87 const SCHEMATIC* aSchematic,
88 std::vector<std::unique_ptr<SCH_SHEET_PATH>>& aStorage ) const
89{
90 std::map<KIID_PATH, PathedItem> index;
91
92 // Retain one owned SCH_SHEET_PATH per sheet and alias it across that
93 // sheet's items. The per-sheet visitor materializes the copy once; the
94 // item visitor reuses the stored pointer, matching the original loop.
95 SCH_SHEET_PATH* storedPath = nullptr;
96
98 aSchematic,
99 [&]( const SCH_SHEET_PATH& aPath )
100 {
101 aStorage.push_back( std::make_unique<SCH_SHEET_PATH>( aPath ) );
102 storedPath = aStorage.back().get();
103 },
104 [&]( SCH_ITEM* aItem, const SCH_SHEET_PATH&, const KIID_PATH& aKiidPath )
105 {
106 index[aKiidPath] = { aItem, storedPath };
107 } );
108
109 return index;
110}
111
112
114 SCH_ITEM* aTarget,
115 const std::vector<PROPERTY_RESOLUTION>& aProps,
116 const SCH_ITEM* aOurs,
117 const SCH_ITEM* aTheirs,
118 const SCH_ITEM* aAncestor )
119{
120 PROPERTY_APPLY_COUNTS counts =
121 ApplyPropertyResolutions( aTarget, aProps, aOurs, aTheirs, aAncestor );
122
123 m_report.propertiesApplied += counts.applied;
124 m_report.propertiesFailed += counts.failed;
125 return counts.applied;
126}
127
128
130{
131 if( !m_ancestor )
132 return false;
133
134 m_report = {};
135 m_report.requiresConnectivityRebuild = m_plan.requiresConnectivityRebuild;
136
137 // Build indices for ours/theirs (read-only) and ancestor (mutable target).
138 std::vector<std::unique_ptr<SCH_SHEET_PATH>> ancStorage;
139 std::vector<std::unique_ptr<SCH_SHEET_PATH>> oursStorage;
140 std::vector<std::unique_ptr<SCH_SHEET_PATH>> theirsStorage;
141
142 auto ancestorIndex = indexSchematic( m_ancestor, ancStorage );
143 auto oursIndex = indexSchematic( m_ours, oursStorage );
144 auto theirsIndex = indexSchematic( m_theirs, theirsStorage );
145
146 // A sub-sheet instantiated more than once shares a single SCH_SCREEN, and
147 // WalkSchematic yields one resolution per instance — all aliasing the same
148 // SCH_ITEM under distinct KIID_PATHs. The shared object is cloned/placed
149 // once; placedByItemUuid maps its UUID to that single clone so each *other*
150 // instance's resolution still applies its per-instance fields (a symbol's
151 // Reference/Value live in per-sheet instance data) to the same clone instead
152 // of being dropped. freedAncestorItems guards against double-freeing the
153 // shared ancestor (e.g. a whole-item DELETE on one instance), and
154 // placedScreenItems guards cloneIntoSheet against a duplicate same-UUID
155 // append onto the shared screen.
156 std::set<const SCH_ITEM*> freedAncestorItems;
157 std::set<std::pair<SCH_SCREEN*, KIID>> placedScreenItems;
158 std::map<KIID, SCH_ITEM*> placedByItemUuid;
159
160 auto cloneIntoSheet = [&]( const SCH_ITEM* aSource, SCH_SCREEN* aTargetScreen ) -> SCH_ITEM*
161 {
162 if( !aSource || !aTargetScreen )
163 return nullptr;
164
165 // Place a given item on a given screen at most once. Shared screens are
166 // visited once per instance, so without this a duplicate same-UUID clone
167 // would be appended for every extra instance.
168 if( !placedScreenItems.insert( { aTargetScreen, aSource->m_Uuid } ).second )
169 return nullptr;
170
171 EDA_ITEM* cloned = aSource->Clone();
172 auto* schClone = dynamic_cast<SCH_ITEM*>( cloned );
173
174 if( !schClone )
175 {
176 delete cloned;
177 return nullptr;
178 }
179
180 // The target screen belongs to the transient offline schematic this applier
181 // builds and serializes to disk, not the live editor document. Appending
182 // directly rather than through SCH_COMMIT is deliberate: there is no editor
183 // frame or undo stack to keep in sync, so a commit would be wrong here.
184 aTargetScreen->Append( schClone );
185 return schClone;
186 };
187
188 // Find the sheet path on ancestor matching the item's KIID_PATH prefix.
189 // Returns nullptr if the path is not present — silently falling back to
190 // root would place items on the wrong sheet and corrupt the hierarchy.
191 auto sheetPathOnAncestor = [&]( const KIID_PATH& aFullPath ) -> SCH_SHEET_PATH*
192 {
193 if( aFullPath.empty() )
194 return nullptr;
195
196 KIID_PATH sheetPathKey = aFullPath;
197 sheetPathKey.pop_back();
198
199 for( auto& storedPath : ancStorage )
200 {
201 if( storedPath->Path() == sheetPathKey )
202 return storedPath.get();
203 }
204
205 return nullptr;
206 };
207
208 auto takeFrom = [&]( const std::map<KIID_PATH, PathedItem>& aIndex,
209 std::map<KIID_PATH, PathedItem>::const_iterator aIt,
210 const ITEM_RESOLUTION& aRes,
211 std::map<KIID_PATH, PathedItem>::iterator aAncIt,
212 std::size_t& aCounter ) -> bool
213 {
214 if( aIt == aIndex.end() )
215 return false;
216
217 if( isSheetItem( aIt->second.item ) )
218 {
219 ++m_report.sheetActionsSkipped;
220 wxLogTrace( traceDiffMerge,
221 wxT( "applier: sheet-level resolution skipped for %s" ),
222 aRes.id.AsString() );
223 return false;
224 }
225
226 SCH_ITEM* ancestorItem = nullptr;
227 SCH_SCREEN* ancestorScreen = nullptr;
228
229 if( aAncIt != ancestorIndex.end() )
230 {
231 ancestorItem = aAncIt->second.item;
232 ancestorScreen = aAncIt->second.sheetPath->LastScreen();
233 }
234
235 SCH_SHEET_PATH* targetSheet = sheetPathOnAncestor( aRes.id );
236
237 if( !targetSheet )
238 {
239 wxLogTrace( traceDiffMerge,
240 wxT( "applier: target sheet path missing for %s" ),
241 aRes.id.AsString() );
242 return false;
243 }
244
245 // Detach (don't free) the ancestor copy before cloning so a shared
246 // target screen never holds two same-KIID items at once, and reattach it
247 // if the clone fails so the item is never lost on the error path (the
248 // ancestor used to be deleted before the target/clone were validated).
249 if( ancestorItem && ancestorScreen )
250 {
251 ancestorScreen->SetContentModified();
252 ancestorScreen->Remove( ancestorItem );
253 }
254
255 SCH_ITEM* placed = cloneIntoSheet( aIt->second.item, targetSheet->LastScreen() );
256
257 if( !placed )
258 {
259 if( ancestorItem && ancestorScreen )
260 ancestorScreen->Append( ancestorItem );
261
262 return false;
263 }
264
265 if( !aRes.id.empty() )
266 placedByItemUuid[aRes.id.back()] = placed;
267
268 if( ancestorItem && ancestorScreen )
269 {
270 delete ancestorItem;
271 freedAncestorItems.insert( ancestorItem );
272 ancestorIndex.erase( aAncIt );
273 }
274
275 ++aCounter;
276 return true;
277 };
278
279 // Copy a symbol's per-instance record (Reference/Value/unit) for one sheet
280 // path from a source item onto a destination clone. Only symbols carry
281 // per-instance data; other shared items are identical across instances, so
282 // the single placed clone is already correct.
283 auto copyInstanceData = []( SCH_ITEM* aDst, SCH_ITEM* aSrc, const KIID_PATH& aPath )
284 {
285 SCH_SYMBOL* dstSym = dynamic_cast<SCH_SYMBOL*>( aDst );
286 SCH_SYMBOL* srcSym = dynamic_cast<SCH_SYMBOL*>( aSrc );
287
288 if( !dstSym || !srcSym )
289 return;
290
292
293 if( srcSym->GetInstance( inst, aPath ) )
294 dstSym->AddHierarchicalReference( inst ); // RemoveInstance()s first, so it overwrites
295 };
296
297 // Apply a secondary instance's resolution to the already-placed shared
298 // clone. The object itself was decided by the first instance; here only the
299 // per-instance fields for this instance's sheet are updated, so a second
300 // instance that resolved differently keeps its own value.
301 auto applyInstanceResolution =
302 [&]( SCH_ITEM* aPlaced, const ITEM_RESOLUTION& aRes,
303 std::map<KIID_PATH, PathedItem>::iterator aOursIt,
304 std::map<KIID_PATH, PathedItem>::iterator aTheirsIt )
305 {
306 SCH_SHEET_PATH* targetSheet = sheetPathOnAncestor( aRes.id );
307
308 if( !targetSheet )
309 return;
310
311 SCH_ITEM* ours = aOursIt != oursIndex.end() ? aOursIt->second.item : nullptr;
312 SCH_ITEM* theirs = aTheirsIt != theirsIndex.end() ? aTheirsIt->second.item : nullptr;
313
314 SHEET_SCOPE scope( m_ancestor, targetSheet );
315
316 if( aRes.kind == ITEM_RES::MERGE_PROPS )
317 {
318 // The shared ancestor was freed when the object was first placed, so
319 // PROP_RES::ANCESTOR resolutions here fall back to no change;
320 // OURS/THEIRS/CUSTOM resolve fully.
321 applyPropertyResolutions( aPlaced, aRes.props, ours, theirs, nullptr );
322 }
323 else if( aRes.kind == ITEM_RES::TAKE_THEIRS )
324 {
325 copyInstanceData( aPlaced, theirs, targetSheet->Path() );
326 }
327 else if( aRes.kind == ITEM_RES::TAKE_OURS )
328 {
329 copyInstanceData( aPlaced, ours, targetSheet->Path() );
330 }
331 // TAKE_ANCESTOR / KEEP / DELETE: the first instance's whole-object
332 // decision stands for the shared object.
333
334 targetSheet->LastScreen()->Update( aPlaced );
335 };
336
337 for( const ITEM_RESOLUTION& res : m_plan.actions )
338 {
339 // Document-level resolution (empty KIID_PATH): page settings live on
340 // the root SCH_SCREEN, ERC severities live on the project. Handle
341 // TAKE_OURS / TAKE_THEIRS as whole-side copies, and MERGE_PROPS as
342 // per-property apply so orthogonal doc edits (ours touches paper,
343 // theirs touches ERC severities) auto-merge instead of forcing the
344 // user to pick a side. KEEP and TAKE_ANCESTOR are no-ops.
345 if( res.id.empty() )
346 {
347 auto pickSch = [&]( PROP_RES aKind ) -> const SCHEMATIC*
348 {
349 if( aKind == PROP_RES::OURS ) return m_ours;
350 if( aKind == PROP_RES::THEIRS ) return m_theirs;
351 return m_ancestor;
352 };
353
354 auto markProjectFieldTouched = [&]( const wxString& aProp )
355 {
356 if( aProp == DOC_PROP_ERC_SEVERITIES )
357 m_report.ercSeveritiesTouched = true;
358 else if( aProp == DOC_PROP_DRAWING_SHEET )
359 m_report.drawingSheetFileTouched = true;
360 else
361 return;
362
363 m_report.projectFileTouched = true;
364 };
365
366 // Whole-document resolutions can explicitly choose ancestor values
367 // without mutating memory. Track the exact diverged project fields
368 // so the handler can patch just those subtrees into the output.
369 auto markDivergedProjectFields = [&]( const SCHEMATIC* aSide )
370 {
371 if( !aSide || !aSide->IsValid() || !m_ancestor->IsValid() )
372 return;
373
374 if( aSide->ErcSettings().m_ERCSeverities
375 != m_ancestor->ErcSettings().m_ERCSeverities )
376 {
377 markProjectFieldTouched( DOC_PROP_ERC_SEVERITIES );
378 }
379
380 if( aSide->Settings().m_SchDrawingSheetFileName
381 != m_ancestor->Settings().m_SchDrawingSheetFileName )
382 {
383 markProjectFieldTouched( DOC_PROP_DRAWING_SHEET );
384 }
385 };
386
387 if( res.kind != ITEM_RES::MERGE_PROPS )
388 {
389 markDivergedProjectFields( m_ours );
390 markDivergedProjectFields( m_theirs );
391 }
392
393 auto applyWholeSide = [&]( const SCHEMATIC* aSrc )
394 {
395 if( !aSrc )
396 return;
397
398 if( aSrc->RootScreen() && m_ancestor->RootScreen() )
399 {
400 m_ancestor->RootScreen()->SetPageSettings(
401 aSrc->RootScreen()->GetPageSettings() );
402 }
403
404 if( aSrc->IsValid() && m_ancestor->IsValid()
405 && aSrc->ErcSettings().m_ERCSeverities
406 != m_ancestor->ErcSettings().m_ERCSeverities )
407 {
408 m_ancestor->ErcSettings().m_ERCSeverities =
409 aSrc->ErcSettings().m_ERCSeverities;
410 markProjectFieldTouched( DOC_PROP_ERC_SEVERITIES );
411 }
412
413 // Drawing sheet path lives on SCHEMATIC_SETTINGS (which sits
414 // inside the project file). Settings() wxASSERTs on a null
415 // project; gate on IsValid() like the ERC path.
416 if( aSrc->IsValid() && m_ancestor->IsValid()
417 && aSrc->Settings().m_SchDrawingSheetFileName
418 != m_ancestor->Settings().m_SchDrawingSheetFileName )
419 {
420 m_ancestor->Settings().m_SchDrawingSheetFileName =
421 aSrc->Settings().m_SchDrawingSheetFileName;
422 markProjectFieldTouched( DOC_PROP_DRAWING_SHEET );
423 }
424 };
425
426 if( res.kind == ITEM_RES::TAKE_OURS )
427 {
428 applyWholeSide( m_ours );
429 }
430 else if( res.kind == ITEM_RES::TAKE_THEIRS )
431 {
432 applyWholeSide( m_theirs );
433 }
434 else if( res.kind == ITEM_RES::MERGE_PROPS )
435 {
436 SCH_SCREEN* ancRoot = m_ancestor->RootScreen();
437 PAGE_INFO merged = ancRoot ? ancRoot->GetPageSettings() : PAGE_INFO();
438 bool pageTouched = false;
439
440 for( const PROPERTY_RESOLUTION& prop : res.props )
441 {
442 const SCHEMATIC* src = pickSch( prop.kind );
443
444 if( !src || !src->RootScreen() || !ancRoot )
445 continue;
446
447 const PAGE_INFO& srcPage = src->RootScreen()->GetPageSettings();
448
449 if( prop.name == DOC_PROP_PAGE_FORMAT )
450 {
451 merged.SetType( srcPage.GetType(), merged.IsPortrait() );
452 pageTouched = true;
453 }
454 else if( prop.name == DOC_PROP_PAGE_ORIENTATION )
455 {
456 merged.SetPortrait( srcPage.IsPortrait() );
457 pageTouched = true;
458 }
459 else if( prop.name == DOC_PROP_ERC_SEVERITIES )
460 {
461 markProjectFieldTouched( DOC_PROP_ERC_SEVERITIES );
462
463 if( src->IsValid() && m_ancestor->IsValid()
465 != m_ancestor->ErcSettings().m_ERCSeverities )
466 {
467 m_ancestor->ErcSettings().m_ERCSeverities =
469 }
470 }
471 else if( prop.name == DOC_PROP_DRAWING_SHEET )
472 {
473 markProjectFieldTouched( DOC_PROP_DRAWING_SHEET );
474
475 if( src->IsValid() && m_ancestor->IsValid()
477 != m_ancestor->Settings().m_SchDrawingSheetFileName )
478 {
479 m_ancestor->Settings().m_SchDrawingSheetFileName =
481 }
482 }
483 }
484
485 if( pageTouched && ancRoot )
486 ancRoot->SetPageSettings( merged );
487 }
488
489 continue;
490 }
491
492 // Per-sheet paper-format resolution: SCH_DIFFER emits these with
493 // `id == sheetPath + SCH_SCREEN sentinel KIID`. The sentinel
494 // distinguishes the SCH_SCREEN (page-format) resolution from the
495 // SCH_SHEET symbol that lives at the same sheetPath. Strip the
496 // sentinel, look up the screen, copy paper settings from the chosen
497 // side.
498 if( !res.id.empty() && res.id.back() == SchScreenSentinelKiid() )
499 {
500 KIID_PATH sheetPath = res.id;
501 sheetPath.pop_back();
502
503 auto resolveScreen =
504 [&]( const std::vector<std::unique_ptr<SCH_SHEET_PATH>>& aPaths )
505 -> SCH_SCREEN*
506 {
507 for( const auto& p : aPaths )
508 {
509 if( p->Path() == sheetPath )
510 return p->LastScreen();
511 }
512
513 return nullptr;
514 };
515
516 SCH_SCREEN* ancScreen = resolveScreen( ancStorage );
517
518 if( !ancScreen )
519 continue;
520
521 if( res.kind == ITEM_RES::TAKE_OURS )
522 {
523 if( SCH_SCREEN* src = resolveScreen( oursStorage ) )
524 ancScreen->SetPageSettings( src->GetPageSettings() );
525 }
526 else if( res.kind == ITEM_RES::TAKE_THEIRS )
527 {
528 if( SCH_SCREEN* src = resolveScreen( theirsStorage ) )
529 ancScreen->SetPageSettings( src->GetPageSettings() );
530 }
531 else if( res.kind == ITEM_RES::MERGE_PROPS )
532 {
533 // Orthogonal edits: ours changes Page Format, theirs changes
534 // Page Orientation. Build a new PAGE_INFO field-by-field from
535 // whichever side the per-property resolution names. Without
536 // this branch the screen would silently stay at ancestor.
537 PAGE_INFO merged = ancScreen->GetPageSettings();
538
539 auto pickSrcScreen = [&]( PROP_RES aKind ) -> SCH_SCREEN*
540 {
541 if( aKind == PROP_RES::OURS ) return resolveScreen( oursStorage );
542 if( aKind == PROP_RES::THEIRS ) return resolveScreen( theirsStorage );
543 return ancScreen;
544 };
545
546 for( const PROPERTY_RESOLUTION& prop : res.props )
547 {
548 SCH_SCREEN* src = pickSrcScreen( prop.kind );
549
550 if( !src )
551 continue;
552
553 const PAGE_INFO& srcPage = src->GetPageSettings();
554
555 if( prop.name == DOC_PROP_PAGE_FORMAT )
556 merged.SetType( srcPage.GetType(), merged.IsPortrait() );
557 else if( prop.name == DOC_PROP_PAGE_ORIENTATION )
558 merged.SetPortrait( srcPage.IsPortrait() );
559 }
560
561 ancScreen->SetPageSettings( merged );
562 }
563
564 continue;
565 }
566
567 auto ancIt = ancestorIndex.find( res.id );
568 auto oursIt = oursIndex.find( res.id );
569 auto theirsIt = theirsIndex.find( res.id );
570
571 // Sheet-level operations are explicitly out of scope (P0 from review:
572 // SCH_SHEET::Clone() is shallow and would graft a screen from the
573 // source schematic into ancestor, corrupting ownership).
574 auto resolutionTargetsSheet = [&]() -> bool
575 {
576 if( ancIt != ancestorIndex.end() && isSheetItem( ancIt->second.item ) ) return true;
577 if( oursIt != oursIndex.end() && isSheetItem( oursIt->second.item ) ) return true;
578 if( theirsIt != theirsIndex.end() && isSheetItem( theirsIt->second.item ) ) return true;
579 return false;
580 };
581
582 if( resolutionTargetsSheet() )
583 {
584 ++m_report.sheetActionsSkipped;
585 continue;
586 }
587
588 // A shared SCH_SCREEN surfaces the same item under several instance
589 // paths. If an earlier instance already placed the shared object, apply
590 // this instance's per-instance resolution to that clone rather than
591 // re-cloning it (which would drop this instance's Reference/Value).
592 if( !res.id.empty() )
593 {
594 auto placedIt = placedByItemUuid.find( res.id.back() );
595
596 if( placedIt != placedByItemUuid.end() )
597 {
598 applyInstanceResolution( placedIt->second, res, oursIt, theirsIt );
599 continue;
600 }
601 }
602
603 // Otherwise, if an earlier instance freed this ancestor item without
604 // placing a replacement (e.g. a whole-item DELETE), skip so we don't
605 // double-free it.
606 if( ancIt != ancestorIndex.end() && freedAncestorItems.count( ancIt->second.item ) )
607 continue;
608
609 switch( res.kind )
610 {
612 takeFrom( oursIndex, oursIt, res, ancIt, m_report.itemsTakenOurs );
613 break;
614
616 takeFrom( theirsIndex, theirsIt, res, ancIt, m_report.itemsTakenTheirs );
617 break;
618
620 // Already in ancestor; nothing to do.
621 break;
622
624 {
625 if( ancIt != ancestorIndex.end() )
626 {
627 SCH_ITEM* victim = ancIt->second.item;
628 ancIt->second.sheetPath->LastScreen()->DeleteItem( victim );
629 freedAncestorItems.insert( victim );
630 ancestorIndex.erase( ancIt );
631 ++m_report.itemsDeleted;
632 }
633
634 break;
635 }
636
637 case ITEM_RES::KEEP:
638 // Conservative-conflict default: leave whatever is in ancestor.
639 ++m_report.itemsKept;
640 break;
641
643 {
644 // Align with PCB applier convention: clone OURS as the base, then
645 // apply resolutions. This way property failures default to the
646 // ours value, not the ancestor value.
647 if( oursIt == oursIndex.end() )
648 break;
649
650 SCH_SHEET_PATH* targetSheet = sheetPathOnAncestor( res.id );
651
652 if( !targetSheet )
653 break;
654
655 // applyPropertyResolutions reads from ancestor for
656 // PROP_RES::ANCESTOR, so the ancestor item must outlive that
657 // call. Capture pointers before any mutation.
658 const SCH_ITEM* theirs = theirsIt != theirsIndex.end()
659 ? theirsIt->second.item : nullptr;
660 SCH_ITEM* ancestorItem = nullptr;
661 SCH_SCREEN* ancestorScreen = nullptr;
662
663 if( ancIt != ancestorIndex.end() )
664 {
665 ancestorItem = ancIt->second.item;
666 ancestorScreen = ancIt->second.sheetPath->LastScreen();
667 }
668
669 // Detach ancestor from its screen RTree before the clone goes in,
670 // so the screen never holds two same-KIID items at once. Append /
671 // RTree don't enforce that invariant but property-listener
672 // callbacks fired during Set can observe screen state.
673 if( ancestorItem && ancestorScreen )
674 {
675 ancestorScreen->SetContentModified();
676 ancestorScreen->Remove( ancestorItem );
677 }
678
679 SCH_SCREEN* targetScreen = targetSheet->LastScreen();
680 SCH_ITEM* placed = cloneIntoSheet( oursIt->second.item, targetScreen );
681
682 if( !placed )
683 {
684 if( ancestorItem && ancestorScreen )
685 ancestorScreen->Append( ancestorItem );
686
687 break;
688 }
689
690 if( !res.id.empty() )
691 placedByItemUuid[res.id.back()] = placed;
692
693 {
694 // Per-instance Reference/Value writes route through
695 // SCHEMATIC::CurrentSheet(); point it at the symbol's own sheet
696 // so SetRefProp/SetValueProp update the correct instance (the
697 // differ reads its values under the same scope).
698 SHEET_SCOPE scope( m_ancestor, targetSheet );
699
700 applyPropertyResolutions( placed, res.props,
701 oursIt->second.item, theirs, ancestorItem );
702
703 // Property writes may have moved placed; refresh its RTree entry.
704 targetScreen->Update( placed );
705 }
706
707 // Only free the ancestor if it was actually detached from a
708 // screen above. A null ancestorScreen means we never had it in
709 // hand, which means we never indexed it — leaving it untouched
710 // is correct.
711 if( ancestorItem && ancestorScreen )
712 {
713 delete ancestorItem;
714 freedAncestorItems.insert( ancestorItem );
715 ancestorIndex.erase( ancIt );
716 }
717
718 ++m_report.itemsMergedProps;
719 break;
720 }
721 }
722 }
723
724 // Post-apply validators. Collect every symbol reference across the
725 // hierarchy + schema versions + connectivity ack into VALIDATION_INPUT.
726 {
727 VALIDATION_INPUT vInput;
728
729 if( m_ancestor )
730 {
731 SCH_SHEET_LIST hierarchy = m_ancestor->Hierarchy();
732
733 for( const SCH_SHEET_PATH& sheetPath : hierarchy )
734 {
735 SCH_SCREEN* screen = sheetPath.LastScreen();
736
737 if( !screen )
738 continue;
739
740 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
741 {
742 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
743 REFDES_ENTRY entry;
744 entry.refdes = sym->GetRef( &sheetPath );
745 entry.id = sheetPath.Path();
746 entry.id.push_back( sym->m_Uuid );
747 vInput.refdesEntries.push_back( std::move( entry ) );
748 }
749 }
750 }
751
752 vInput.planRequiredRebuild = m_plan.requiresConnectivityRebuild;
753 vInput.applierReportedRebuild = m_report.connectivityRebuildPerformed;
754
758
759 m_report.validation = RunPostApplyValidators( vInput );
760 }
761
762 return true;
763}
764
765} // namespace KICAD_DIFF
int index
void SetContentModified(bool aModified=true)
Definition base_screen.h:55
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
const KIID m_Uuid
Definition eda_item.h:531
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
virtual EDA_ITEM * Clone() const
Create a duplicate of this item with linked list members set to NULL.
Definition eda_item.cpp:143
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:221
std::map< int, SEVERITY > m_ERCSeverities
bool Apply()
Apply the plan to the ancestor.
SCH_MERGE_APPLIER(SCHEMATIC *aAncestor, const SCHEMATIC *aOurs, const SCHEMATIC *aTheirs, MERGE_PLAN aPlan)
std::map< KIID_PATH, PathedItem > indexSchematic(const SCHEMATIC *aSchematic, std::vector< std::unique_ptr< SCH_SHEET_PATH > > &aStorage) const
Walk a schematic into a KIID_PATH-keyed table of (item, sheet_path).
std::size_t applyPropertyResolutions(SCH_ITEM *aTarget, const std::vector< PROPERTY_RESOLUTION > &aProps, const SCH_ITEM *aOurs, const SCH_ITEM *aTheirs, const SCH_ITEM *aAncestor)
RAII guard that temporarily swaps SCHEMATIC::CurrentSheet to a given path for the duration of a scope...
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
void SetPortrait(bool aIsPortrait)
Rotate the paper page 90 degrees.
bool SetType(PAGE_SIZE_TYPE aPageSize, bool aIsPortrait=false)
Set the name of the page type and also the sizes and margins commonly associated with that type name.
bool IsPortrait() const
Definition page_info.h:124
const PAGE_SIZE_TYPE & GetType() const
Definition page_info.h:98
Holds all the data relating to one schematic.
Definition schematic.h:90
SCHEMATIC_SETTINGS & Settings() const
SCH_SCREEN * RootScreen() const
Helper to retrieve the screen of the root sheet.
bool IsValid() const
A simple test if the schematic is loaded, not a complete one.
Definition schematic.h:174
ERC_SETTINGS & ErcSettings() const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
const PAGE_INFO & GetPageSettings() const
Definition sch_screen.h:137
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition sch_screen.h:138
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
bool Remove(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Remove aItem from the schematic associated with this screen.
int GetFileFormatVersionAtLoad() const
Definition sch_screen.h:135
void Update(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Update aItem's bounding box in the tree.
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
SCH_SCREEN * LastScreen()
Schematic symbol object.
Definition sch_symbol.h:69
void AddHierarchicalReference(const KIID_PATH &aPath, const wxString &aRef, int aUnit)
Add a full hierarchical reference to this symbol.
bool GetInstance(SCH_SYMBOL_INSTANCE &aInstance, const KIID_PATH &aSheetPath, bool aTestFromEnd=false) const
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
const wxChar *const traceDiffMerge
Flag to enable diff/merge engine and renderer debugging output.
const wxString DOC_PROP_ERC_SEVERITIES
const KIID & SchScreenSentinelKiid()
Sentinel KIID appended to a sheet's KIID_PATH to mark a per-sheet SCH_SCREEN resolution (page format ...
const wxString DOC_PROP_PAGE_FORMAT
Property-name keys for the synthetic document-level ITEM_CHANGE (empty KIID_PATH).
static int rootFileFormatVersion(const SCHEMATIC *aSchematic)
Return aSchematic's root-screen file-format version, or 0 when no root screen is reachable.
static bool isSheetItem(const SCH_ITEM *aItem)
True iff aItem is a SCH_SHEET.
const wxString DOC_PROP_PAGE_ORIENTATION
const wxString DOC_PROP_DRAWING_SHEET
PROP_RES
Resolution kind for a single property of a single item.
PROPERTY_APPLY_COUNTS ApplyPropertyResolutions(INSPECTABLE *aTarget, const std::vector< PROPERTY_RESOLUTION > &aProps, const INSPECTABLE *aOurs, const INSPECTABLE *aTheirs, const INSPECTABLE *aAncestor)
Apply per-property merge resolutions to aTarget, sourcing OURS/THEIRS/ANCESTOR values from the matchi...
VALIDATION_REPORT RunPostApplyValidators(const VALIDATION_INPUT &aInput)
Run every standard post-apply validator and merge their reports.
void WalkSchematic(const SCHEMATIC *aSchematic, SHEET_VISITOR &&aSheetVisitor, ITEM_VISITOR &&aItemVisitor, const KIID_PATH &aScope={})
Visit every SCH_ITEM in every sheet of aSchematic in page-number order.
STL namespace.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
Result of planning a 3-way merge.
Applied/failed tallies from ApplyPropertyResolutions, folded into a caller's report.
Reference-designator uniqueness over a flat list of (refdes, id) pairs.
KIID_PATH id
wxString refdes
Inputs needed to run the post-apply validator pipeline.
std::vector< REFDES_ENTRY > refdesEntries
A simple container for schematic symbol instance information.
VECTOR3I res
wxLogTrace helper definitions.
@ SCH_SYMBOL_T
Definition typeinfo.h:169
@ SCH_SHEET_T
Definition typeinfo.h:172