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