KiCad PCB EDA Suite
Loading...
Searching...
No Matches
multichannel_tool.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 2
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, see <https://www.gnu.org/licenses/>.
18 */
19
20
21#include <board_commit.h>
22#include <tools/pcb_actions.h>
23#include <tools/pcb_selection.h>
24
27
28#include "multichannel_tool.h"
29
30#include <pcbexpr_evaluator.h>
31
32#include <zone.h>
33#include <board.h>
34#include <netinfo.h>
38#include <pcb_group.h>
39#include <pcb_generator.h>
40#include <footprint.h>
41#include <pad.h>
42#include <pcb_text.h>
46#include <algorithm>
47#include <pcb_track.h>
48#include <tool/tool_manager.h>
50#include <chrono>
51#include <core/profile.h>
52#include <thread_pool.h>
54#include <string_utils.h>
55#include <wx/log.h>
56#include <wx/richmsgdlg.h>
57#include <pgm_base.h>
58
59
60#define MULTICHANNEL_EXTRA_DEBUG
61
62static const wxString traceMultichannelTool = wxT( "MULTICHANNEL_TOOL" );
63
64
65static wxString FormatComponentList( const std::set<FOOTPRINT*>& aComponents )
66{
67 std::vector<wxString> refs;
68
69 for( FOOTPRINT* fp : aComponents )
70 {
71 if( !fp )
72 continue;
73
74 refs.push_back( fp->GetReferenceAsString() );
75 }
76
77 std::sort( refs.begin(), refs.end(),
78 []( const wxString& aLhs, const wxString& aRhs )
79 {
80 return aLhs.CmpNoCase( aRhs ) < 0;
81 } );
82
83 if( refs.empty() )
84 return _( "(none)" );
85
86 wxString result;
87 wxString line;
88 size_t componentsOnLine = 0;
89
90 for( const wxString& ref : refs )
91 {
92 if( componentsOnLine == 10 )
93 {
94 if( !result.IsEmpty() )
95 result += wxT( "\n" );
96
97 result += line;
98 line.clear();
99 componentsOnLine = 0;
100 }
101
102 AccumulateDescription( line, ref );
103 componentsOnLine++;
104 }
105
106 if( !line.IsEmpty() )
107 {
108 if( !result.IsEmpty() )
109 result += wxT( "\n" );
110
111 result += line;
112 }
113
114 return result;
115}
116
117
118static void ShowTopologyMismatchReasons( wxWindow* aParent, const wxString& aSummary,
119 const std::vector<wxString>& aReasons )
120{
121 if( !aParent || aReasons.empty() )
122 return;
123
124 wxString reasonText;
125
126 for( size_t idx = 0; idx < aReasons.size(); ++idx )
127 {
128 if( idx > 0 )
129 reasonText += wxT( "\n" );
130
131 reasonText += aReasons[idx];
132 }
133
134 wxRichMessageDialog dlg( aParent, aSummary, _( "Topology mismatch" ), wxICON_ERROR | wxOK );
135 dlg.ShowDetailedText( reasonText );
136 dlg.ShowModal();
137}
138
139
141{
142}
143
144
149
150void MULTICHANNEL_TOOL::ShowMismatchDetails( wxWindow* aParent, const wxString& aSummary,
151 const std::vector<wxString>& aReasons ) const
152{
153 wxWindow* parent = aParent ? aParent : frame();
154 ShowTopologyMismatchReasons( parent, aSummary, aReasons );
155}
156
157
163
164
166 std::set<FOOTPRINT*>& aComponents )
167{
168 if( !aRuleArea || !aRuleArea->m_zone )
169 return false;
170
171 // When we're copying the layout of a design block, we are provided an exact list of items
172 // rather than querying the board for items that are inside the area.
174 {
175 // Get all board connected items that are from the design bloc
176 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
177 {
178 if( item->Type() == PCB_FOOTPRINT_T )
179 aComponents.insert( static_cast<FOOTPRINT*>( item ) );
180 }
181
182 return (int) aComponents.size();
183 }
184
185
187 PCBEXPR_UCODE ucode;
188 PCBEXPR_CONTEXT ctx, preflightCtx;
189
190 auto reportError =
191 [&]( const wxString& aMessage, int aOffset )
192 {
193 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
194 };
195
196 ctx.SetErrorCallback( reportError );
197 preflightCtx.SetErrorCallback( reportError );
198 compiler.SetErrorCallback( reportError );
199 //compiler.SetDebugReporter( m_reporter );
200
201 wxLogTrace( traceMultichannelTool, wxT( "rule area '%s'" ), aRuleArea->m_zone->GetZoneName() );
202
203 wxString ruleText;
204
205 switch( aRuleArea->m_zone->GetPlacementAreaSourceType() )
206 {
208 ruleText = wxT( "A.memberOfSheetOrChildren('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
209 break;
211 ruleText = wxT( "A.hasComponentClass('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
212 break;
214 ruleText = wxT( "A.memberOfGroup('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
215 break;
217 // For design blocks, handled above outside the rules system
218 break;
219 }
220
221 auto ok = compiler.Compile( ruleText, &ucode, &preflightCtx );
222
223 if( !ok )
224 return false;
225
226 for( FOOTPRINT* fp : board()->Footprints() )
227 {
228 ctx.SetItems( fp, fp );
229 LIBEVAL::VALUE* val = ucode.Run( &ctx );
230
231 if( val->AsDouble() != 0.0 )
232 {
233 wxLogTrace( traceMultichannelTool, wxT( " - %s [sheet %s]" ),
234 fp->GetReference(),
235 fp->GetSheetname() );
236
237 aComponents.insert( fp );
238 }
239 }
240
241 return true;
242}
243
244
245bool MULTICHANNEL_TOOL::findOtherItemsInRuleArea( RULE_AREA* aRuleArea, std::set<BOARD_ITEM*>& aItems )
246{
247 if( !aRuleArea || !aRuleArea->m_zone )
248 return false;
249
250 // When we're copying the layout of a design block, we are provided an exact list of items
251 // rather than querying the board for items that are inside the area.
253 {
254 // Get all board items that aren't footprints. Connected items are usually handled by the
255 // routing path, except zones which are copied via "other items".
256 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
257 {
258 if( item->Type() == PCB_FOOTPRINT_T )
259 continue;
260
261 // Generators are copied whole by the routing pass. Nested groups are not yet
262 // preserved (TODO) so they are skipped here.
263 if( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T )
264 continue;
265
266 // Cells are copied with their owning PCB_TABLE, not as standalone items.
267 if( item->Type() == PCB_TABLECELL_T )
268 continue;
269
270 if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item ) )
271 {
272 if( !boardItem->IsConnected() || boardItem->Type() == PCB_ZONE_T )
273 aItems.insert( boardItem );
274 }
275 }
276
277 return aItems.size() > 0;
278 }
279
280 // The design-block apply target uses a scratch zone not on the board, so enclosedByArea()
281 // below finds nothing. Resolve the group's items directly from the group.
283 && ( !aRuleArea->m_components.empty() || aRuleArea->m_group ) )
284 {
285 bool zoneOnBoard = false;
286
287 for( ZONE* zone : board()->Zones() )
288 {
289 if( zone == aRuleArea->m_zone )
290 {
291 zoneOnBoard = true;
292 break;
293 }
294 }
295
296 if( !zoneOnBoard )
297 {
298 EDA_GROUP* group = aRuleArea->m_group;
299
300 if( !group && !aRuleArea->m_components.empty() )
301 group = ( *aRuleArea->m_components.begin() )->GetParentGroup();
302
303 if( group )
304 {
305 for( EDA_ITEM* member : group->GetItems() )
306 {
307 if( member->Type() == PCB_FOOTPRINT_T || member->Type() == PCB_GROUP_T
308 || member->Type() == PCB_GENERATOR_T || member->Type() == PCB_TABLECELL_T )
309 continue;
310
311 if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( member ) )
312 {
313 if( !boardItem->IsConnected() || boardItem->Type() == PCB_ZONE_T )
314 aItems.insert( boardItem );
315 }
316 }
317 }
318
319 return aItems.size() > 0;
320 }
321 }
322
324 PCBEXPR_UCODE ucode;
325 PCBEXPR_CONTEXT ctx, preflightCtx;
326
327 auto reportError =
328 [&]( const wxString& aMessage, int aOffset )
329 {
330 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
331 };
332
333 ctx.SetErrorCallback( reportError );
334 preflightCtx.SetErrorCallback( reportError );
335 compiler.SetErrorCallback( reportError );
336
337 // Use the zone's UUID to identify it uniquely. Using the zone name could match other zones
338 // with the same name (e.g., a copper fill zone with the same name as a rule area).
339 wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ),
340 aRuleArea->m_zone->m_Uuid.AsString() );
341
342 if( !compiler.Compile( ruleText, &ucode, &preflightCtx ) )
343 return false;
344
345 auto testAndAdd =
346 [&]( BOARD_ITEM* aItem )
347 {
348 ctx.SetItems( aItem, aItem );
349 auto val = ucode.Run( &ctx );
350
351 if( val->AsDouble() != 0.0 )
352 aItems.insert( aItem );
353 };
354
355 for( ZONE* zone : board()->Zones() )
356 {
357 if( zone == aRuleArea->m_zone )
358 continue;
359
360 testAndAdd( zone );
361 }
362
363 for( BOARD_ITEM* drawing : board()->Drawings() )
364 {
365 if( !drawing->IsConnected() )
366 testAndAdd( drawing );
367 }
368
369 return true;
370}
371
372
373std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInSheet( wxString aSheetName ) const
374{
375 std::set<FOOTPRINT*> rv;
376
377 if( aSheetName.EndsWith( wxT( "/" ) ) )
378 aSheetName.RemoveLast();
379
380 wxString childPrefix = aSheetName + wxT( "/" );
381
382 for( FOOTPRINT* fp : board()->Footprints() )
383 {
384 auto sn = fp->GetSheetname();
385
386 if( sn.EndsWith( wxT( "/" ) ) )
387 sn.RemoveLast();
388
389 if( sn == aSheetName || sn.StartsWith( childPrefix ) )
390 rv.insert( fp );
391 }
392
393 return rv;
394}
395
396
397std::set<FOOTPRINT*>
398MULTICHANNEL_TOOL::queryComponentsInComponentClass( const wxString& aComponentClassName ) const
399{
400 std::set<FOOTPRINT*> rv;
401
402 for( FOOTPRINT* fp : board()->Footprints() )
403 {
404 if( fp->GetComponentClass()->ContainsClassName( aComponentClassName ) )
405 rv.insert( fp );
406 }
407
408 return rv;
409}
410
411
412static void collectGroupFootprints( EDA_GROUP* aGroup, std::set<FOOTPRINT*>& aOut )
413{
414 for( EDA_ITEM* item : aGroup->GetItems() )
415 {
416 if( item->Type() == PCB_FOOTPRINT_T )
417 aOut.insert( static_cast<FOOTPRINT*>( item ) );
418 else if( item->Type() == PCB_GROUP_T )
419 collectGroupFootprints( static_cast<PCB_GROUP*>( item ), aOut );
420 }
421}
422
423
424static void collectGroupBoardItems( EDA_GROUP* aGroup, std::set<BOARD_ITEM*>& aOut )
425{
426 for( EDA_ITEM* item : aGroup->GetItems() )
427 {
428 // A generator's own bounding box already covers its children (meander arcs).
429 if( item->Type() == PCB_GROUP_T )
430 collectGroupBoardItems( static_cast<PCB_GROUP*>( item ), aOut );
431 else if( item->IsBOARD_ITEM() )
432 aOut.insert( static_cast<BOARD_ITEM*>( item ) );
433 }
434}
435
436
437std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInGroup( const wxString& aGroupName ) const
438{
439 std::set<FOOTPRINT*> rv;
440
441 for( PCB_GROUP* group : board()->Groups() )
442 {
443 if( group->GetName() == aGroupName )
445 }
446
447 return rv;
448}
449
450
451std::set<BOARD_ITEM*> MULTICHANNEL_TOOL::queryBoardItemsInGroup( const wxString& aGroupName ) const
452{
453 std::set<BOARD_ITEM*> rv;
454
455 for( PCB_GROUP* group : board()->Groups() )
456 {
457 if( group->GetName() != aGroupName )
458 continue;
459
460 for( EDA_ITEM* item : group->GetItems() )
461 {
462 if( item->IsBOARD_ITEM() )
463 rv.insert( static_cast<BOARD_ITEM*>( item ) );
464 }
465 }
466
467 return rv;
468}
469
470
471const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( std::set<FOOTPRINT*>& aFootprints, int aMargin )
472{
473 std::vector<VECTOR2I> bbCorners;
474 bbCorners.reserve( aFootprints.size() * 4 );
475
476 for( FOOTPRINT* fp : aFootprints )
477 {
478 const BOX2I bb = fp->GetBoundingBox( false ).GetInflated( aMargin );
479 KIGEOM::CollectBoxCorners( bb, bbCorners );
480 }
481
482 std::vector<VECTOR2I> hullVertices;
483 BuildConvexHull( hullVertices, bbCorners );
484
485 SHAPE_LINE_CHAIN hull( hullVertices );
486
487 // Make the newly computed convex hull use only 90 degree segments
488 return KIGEOM::RectifyPolygon( hull );
489}
490
491const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( const std::set<BOARD_ITEM*>& aItems, int aMargin )
492{
493 std::vector<VECTOR2I> bbCorners;
494 bbCorners.reserve( aItems.size() * 4 );
495
496 for( BOARD_ITEM* item : aItems )
497 {
498 BOX2I bb = item->GetBoundingBox();
499
500 if( item->Type() == PCB_FOOTPRINT_T )
501 bb = static_cast<FOOTPRINT*>( item )->GetBoundingBox( false );
502
503 KIGEOM::CollectBoxCorners( bb.GetInflated( aMargin ), bbCorners );
504 }
505
506 std::vector<VECTOR2I> hullVertices;
507 BuildConvexHull( hullVertices, bbCorners );
508
509 SHAPE_LINE_CHAIN hull( hullVertices );
510
511 // Make the newly computed convex hull use only 90 degree segments
512 return KIGEOM::RectifyPolygon( hull );
513}
514
515
516// Returns each parent sheet path above aSheetName, e.g. "/A/B/C/" -> { "/A/", "/A/B/" }.
517// The root and the sheet itself are left out.
518static std::vector<wxString> getParentSheetPaths( const wxString& aSheetName )
519{
520 std::vector<wxString> segments;
521 wxString cur;
522
523 for( wxUniChar ch : aSheetName )
524 {
525 if( ch == '/' )
526 {
527 if( !cur.IsEmpty() )
528 segments.push_back( cur );
529
530 cur.clear();
531 }
532 else
533 {
534 cur += ch;
535 }
536 }
537
538 if( !cur.IsEmpty() )
539 segments.push_back( cur );
540
541 std::vector<wxString> rv;
542 wxString prefix = wxT( "/" );
543
544 for( size_t i = 0; i + 1 < segments.size(); ++i )
545 {
546 prefix += segments[i] + wxT( "/" );
547 rv.push_back( prefix );
548 }
549
550 return rv;
551}
552
553
555{
556 // Sheet path -> sheet file. Container sheets that only hold subsheets have no file of
557 // their own, so they map to an empty string.
558 std::map<wxString, wxString> uniqueSheets;
559 std::set<wxString> uniqueComponentClasses;
560 std::set<wxString> uniqueGroups;
561
562 m_areas.m_areas.clear();
563
564 for( const FOOTPRINT* fp : board()->Footprints() )
565 {
566 uniqueSheets[fp->GetSheetname()] = fp->GetSheetfile();
567
568 // Offer the parent sheets as channels too, not just the deepest one.
569 for( const wxString& parent : getParentSheetPaths( fp->GetSheetname() ) )
570 uniqueSheets.emplace( parent, wxString() );
571
572 const COMPONENT_CLASS* compClass = fp->GetComponentClass();
573
574 for( const COMPONENT_CLASS* singleClass : compClass->GetConstituentClasses() )
575 uniqueComponentClasses.insert( singleClass->GetName() );
576
577 // Offer every named group up the chain, not just the immediate parent, so a
578 // channel group wrapping several sub-groups can be picked too.
579 for( EDA_GROUP* grp = fp->GetParentGroup(); grp; grp = grp->AsEdaItem()->GetParentGroup() )
580 {
581 if( !grp->GetName().IsEmpty() )
582 uniqueGroups.insert( grp->GetName() );
583 }
584 }
585
586 for( const auto& [sheetPath, sheetFile] : uniqueSheets )
587 {
588 RULE_AREA ent;
589
591 ent.m_generateEnabled = false;
592 ent.m_sheetPath = sheetPath;
593 ent.m_sheetName = sheetFile;
595 m_areas.m_areas.push_back( ent );
596
597 wxLogTrace( traceMultichannelTool, wxT("found sheet '%s' @ '%s' s %d\n"),
598 ent.m_sheetName,
599 ent.m_sheetPath,
600 (int) m_areas.m_areas.size() );
601 }
602
603 for( const wxString& compClass : uniqueComponentClasses )
604 {
605 RULE_AREA ent;
606
608 ent.m_generateEnabled = false;
609 ent.m_componentClass = compClass;
611 m_areas.m_areas.push_back( ent );
612
613 wxLogTrace( traceMultichannelTool, wxT( "found component class '%s' s %d\n" ),
615 static_cast<int>( m_areas.m_areas.size() ) );
616 }
617
618 for( const wxString& groupName : uniqueGroups )
619 {
620 RULE_AREA ent;
621
623 ent.m_generateEnabled = false;
624 ent.m_groupName = groupName;
626 m_areas.m_areas.push_back( ent );
627
628 wxLogTrace( traceMultichannelTool, wxT( "found group '%s' s %d\n" ),
630 static_cast<int>( m_areas.m_areas.size() ) );
631 }
632}
633
634
636{
637 m_areas.m_areas.clear();
638
639 for( ZONE* zone : board()->Zones() )
640 {
641 if( !zone->GetIsRuleArea() )
642 continue;
643
644 if( !zone->GetPlacementAreaEnabled() )
645 continue;
646
647 RULE_AREA area;
648
649 area.m_existsAlready = true;
650 area.m_zone = zone;
651 area.m_ruleName = zone->GetZoneName();
652 area.m_center = zone->Outline()->COutline( 0 ).Centre();
653
655
656 m_areas.m_areas.push_back( area );
657
658 wxLogTrace( traceMultichannelTool, wxT( "RA '%s', %d footprints\n" ), area.m_ruleName,
659 (int) area.m_components.size() );
660 }
661
662 wxLogTrace( traceMultichannelTool, wxT( "Total RAs found: %d\n" ), (int) m_areas.m_areas.size() );
663}
664
665
667{
668 for( RULE_AREA& ra : m_areas.m_areas )
669 {
670 if( ra.m_ruleName == aName )
671 return &ra;
672 }
673
674 return nullptr;
675}
676
677
679{
681}
682
683
685{
686 std::vector<ZONE*> refRAs;
687
688 auto isSelectedItemAnRA =
689 []( EDA_ITEM* aItem ) -> ZONE*
690 {
691 if( !aItem || aItem->Type() != PCB_ZONE_T )
692 return nullptr;
693
694 ZONE* zone = static_cast<ZONE*>( aItem );
695
696 if( !zone->GetIsRuleArea() )
697 return nullptr;
698
699 if( !zone->GetPlacementAreaEnabled() )
700 return nullptr;
701
702 return zone;
703 };
704
705 for( EDA_ITEM* item : selection() )
706 {
707 if( ZONE* zone = isSelectedItemAnRA( item ) )
708 {
709 refRAs.push_back( zone );
710 }
711 else if( item->Type() == PCB_GROUP_T )
712 {
713 PCB_GROUP *group = static_cast<PCB_GROUP*>( item );
714
715 for( EDA_ITEM* grpItem : group->GetItems() )
716 {
717 if( ZONE* grpZone = isSelectedItemAnRA( grpItem ) )
718 refRAs.push_back( grpZone );
719 }
720 }
721 }
722
723 if( refRAs.size() != 1 )
724 {
727 this,
728 _( "Select a reference Rule Area to copy from..." ),
729 [&]( EDA_ITEM* aItem )
730 {
731 return isSelectedItemAnRA( aItem ) != nullptr;
732 }
733 } );
734
735 return 0;
736 }
737
739
740 int status = CheckRACompatibility( refRAs.front() );
741
742 if( status < 0 )
743 return status;
744
745 if( m_areas.m_areas.size() <= 1 )
746 {
747 frame()->ShowInfoBarError( _( "No Rule Areas to repeat layout to have been found." ), true );
748 return 0;
749 }
750
752 int ret = dialog.ShowModal();
753
754 if( ret != wxID_OK )
755 return 0;
756
757 return RepeatLayout( aEvent, refRAs.front() );
758}
759
760
762{
763 m_areas.m_refRA = nullptr;
764
765 for( RULE_AREA& ra : m_areas.m_areas )
766 {
767 if( ra.m_zone == aRefZone )
768 {
769 m_areas.m_refRA = &ra;
770 break;
771 }
772 }
773
774 if( !m_areas.m_refRA )
775 return -1;
776
777 m_areas.m_compatMap.clear();
778
779 std::vector<RULE_AREA*> targets;
780
781 for( RULE_AREA& ra : m_areas.m_areas )
782 {
783 if( ra.m_zone == m_areas.m_refRA->m_zone )
784 continue;
785
786 targets.push_back( &ra );
787 m_areas.m_compatMap[&ra] = RULE_AREA_COMPAT_DATA();
788 }
789
790 if( targets.empty() )
791 return 0;
792
793 int total = static_cast<int>( targets.size() );
794 std::atomic<int> completed( 0 );
795 std::atomic<bool> cancelled( false );
796 std::atomic<int> matchedComponents( 0 );
797 std::atomic<int> totalComponents( 0 );
798 RULE_AREA* refRA = m_areas.m_refRA;
799
801 isoParams.m_cancelled = &cancelled;
802 isoParams.m_matchedComponents = &matchedComponents;
803 isoParams.m_totalComponents = &totalComponents;
804
805 // Process RA resolutions sequentially on a single background thread.
806 // Each resolveConnectionTopology call internally parallelizes its MRV scan
807 // across the thread pool, creating many short-lived tasks that fully utilize
808 // all available cores. Running the outer loop sequentially avoids thread
809 // pool starvation from nested parallelism.
811
812 auto future = tp.submit_task(
813 [this, refRA, &targets, &completed, &cancelled, &matchedComponents, &isoParams]()
814 {
815 for( RULE_AREA* target : targets )
816 {
817 if( cancelled.load( std::memory_order_relaxed ) )
818 break;
819
820 matchedComponents.store( 0, std::memory_order_relaxed );
821
822 RULE_AREA_COMPAT_DATA& compatData = m_areas.m_compatMap[target];
823 resolveConnectionTopology( refRA, target, compatData, isoParams );
824 completed.fetch_add( 1, std::memory_order_relaxed );
825 }
826 } );
827
828 if( Pgm().IsGUI() )
829 {
830 std::unique_ptr<WX_PROGRESS_REPORTER> reporter;
831 auto startTime = std::chrono::steady_clock::now();
832 double highWaterMark = 0.0;
833
834 while( future.wait_for( std::chrono::milliseconds( 100 ) ) != std::future_status::ready )
835 {
836 if( !reporter )
837 {
838 auto elapsed = std::chrono::steady_clock::now() - startTime;
839
840 if( elapsed > std::chrono::seconds( 1 ) )
841 {
842 reporter = std::make_unique<WX_PROGRESS_REPORTER>(
843 frame(), _( "Checking Rule Area compatibility..." ), 1, PR_CAN_ABORT );
844 }
845 else
846 {
847 // Flush background-thread log messages so timing traces appear promptly
848 wxLog::FlushActive();
849 }
850 }
851
852 if( reporter )
853 {
854 int done = completed.load( std::memory_order_relaxed );
855 int matched = matchedComponents.load( std::memory_order_relaxed );
856 int compTotal = totalComponents.load( std::memory_order_relaxed );
857
858 double fraction = ( compTotal > 0 )
859 ? static_cast<double>( matched ) / compTotal
860 : 0.0;
861 double progress = static_cast<double>( done + fraction ) / total;
862
863 if( progress > highWaterMark )
864 highWaterMark = progress;
865
866 reporter->SetCurrentProgress( highWaterMark );
867 reporter->Report( wxString::Format(
868 _( "Resolving topology %d of %d (%d/%d components)" ),
869 done + 1, total, matched, compTotal ) );
870
871 if( !reporter->KeepRefreshing() )
872 cancelled.store( true, std::memory_order_relaxed );
873 }
874 }
875 }
876 else
877 {
878 future.wait();
879 }
880
881 if( cancelled.load( std::memory_order_relaxed ) )
882 {
883 m_areas.m_compatMap.clear();
884 return -1;
885 }
886
887 return 0;
888}
889
890
891int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, RULE_AREA& aRefArea, RULE_AREA& aTargetArea,
892 REPEAT_LAYOUT_OPTIONS& aOptions, BOARD_COMMIT* aExternalCommit,
893 wxString* aErrorOut )
894{
895 wxCHECK_MSG( aRefArea.m_zone, -1, wxT( "Reference Rule Area has no zone." ) );
896 wxCHECK_MSG( aTargetArea.m_zone, -1, wxT( "Target Rule Area has no zone." ) );
897
898 const bool silent = aErrorOut != nullptr;
899
900 auto reportError = [&]( const wxString& aMsg )
901 {
902 if( aErrorOut )
903 *aErrorOut = aMsg;
904 else if( Pgm().IsGUI() )
905 frame()->ShowInfoBarError( aMsg, true );
906 };
907
909
910 if( !resolveConnectionTopology( &aRefArea, &aTargetArea, compat ) )
911 {
912 if( silent )
913 {
914 *aErrorOut = compat.m_errorMsg;
915 }
916 else if( Pgm().IsGUI() )
917 {
918 wxString summary = wxString::Format( _( "Rule Area topologies do not match: %s" ), compat.m_errorMsg );
920 }
921
922 return -1;
923 }
924
925 std::optional<BOARD_COMMIT> localCommit;
926
927 if( !aExternalCommit )
928 localCommit.emplace( GetManager(), true, false );
929
930 BOARD_COMMIT& commit = aExternalCommit ? *aExternalCommit : *localCommit;
931
932 // If no anchor is provided, pick the first matched pair to avoid center-alignment shifting
933 // the whole group. This keeps Apply Design Block Layout from moving the group to wherever
934 // the source design block happened to be placed.
935 if( aTargetArea.m_sourceType == PLACEMENT_SOURCE_T::GROUP_PLACEMENT && !aOptions.m_anchorFp )
936 {
937 if( !compat.m_matchingComponents.empty() )
938 aOptions.m_anchorFp = compat.m_matchingComponents.begin()->first;
939 }
940
941 if( !copyRuleAreaContents( &aRefArea, &aTargetArea, &commit, aOptions, compat ) )
942 {
943 auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
944 aRefArea.m_zone->GetZoneName(), aTargetArea.m_zone->GetZoneName() );
945
946 if( !aExternalCommit )
947 commit.Revert();
948
949 reportError( errMsg );
950
951 return -1;
952 }
953
955 {
956 EDA_GROUP* group = aTargetArea.m_group;
957
958 if( !group && !aTargetArea.m_components.empty() )
959 group = ( *aTargetArea.m_components.begin() )->GetParentGroup();
960
961 if( !group )
962 {
963 if( !aExternalCommit )
964 commit.Revert();
965
966 reportError( _( "Target group does not have a group." ) );
967
968 return -1;
969 }
970
971 commit.Modify( group->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
972
973 for( BOARD_ITEM* item : compat.m_groupableItems )
974 {
975 commit.Modify( item );
976 group->AddItem( item );
977 }
978 }
979
980 if( !aExternalCommit )
981 commit.Push( _( "Repeat layout" ) );
982
983 return 0;
984}
985
986
987int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone )
988{
989 int totalCopied = 0;
990
991 BOARD_COMMIT commit( GetManager(), true, false );
992
993 for( auto& [targetArea, compatData] : m_areas.m_compatMap )
994 {
995 if( !compatData.m_doCopy )
996 {
997 wxLogTrace( traceMultichannelTool, wxT( "skipping copy to RA '%s' (disabled in dialog)\n" ),
998 targetArea->m_ruleName );
999 continue;
1000 }
1001
1002 if( !compatData.m_isOk )
1003 continue;
1004
1005 if( !copyRuleAreaContents( m_areas.m_refRA, targetArea, &commit, m_areas.m_options, compatData ) )
1006 {
1007 auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
1008 m_areas.m_refRA->m_zone->GetZoneName(),
1009 targetArea->m_zone->GetZoneName() );
1010
1011 commit.Revert();
1012
1013 if( Pgm().IsGUI() )
1014 frame()->ShowInfoBarError( errMsg, true );
1015
1016 return -1;
1017 }
1018
1019 totalCopied++;
1020 wxSafeYield();
1021 }
1022
1023 if( m_areas.m_options.m_groupItems )
1024 {
1025 for( const auto& [targetArea, compatData] : m_areas.m_compatMap )
1026 {
1027 if( compatData.m_groupableItems.size() < 2 )
1028 continue;
1029
1030 pruneExistingGroups( commit, compatData.m_affectedItems );
1031
1032 PCB_GROUP* group = new PCB_GROUP( board() );
1033
1034 commit.Add( group );
1035
1036 for( BOARD_ITEM* item : compatData.m_groupableItems )
1037 {
1038 commit.Modify( item );
1039 group->AddItem( item );
1040 }
1041 }
1042 }
1043
1044 commit.Push( _( "Repeat layout" ) );
1045
1046 if( Pgm().IsGUI() )
1047 frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ), true );
1048
1049 return 0;
1050}
1051
1052
1053wxString MULTICHANNEL_TOOL::stripComponentIndex( const wxString& aRef ) const
1054{
1055 wxString rv;
1056
1057 // fixme: i'm pretty sure this can be written in a simpler way, but I really suck at figuring
1058 // out which wx's built in functions would do it for me. And I hate regexps :-)
1059 for( auto k : aRef )
1060 {
1061 if( !k.IsAscii() )
1062 break;
1063
1064 char c;
1065 k.GetAsChar( &c );
1066
1067 if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c == '_' ) )
1068 rv.Append( k );
1069 else
1070 break;
1071 }
1072
1073 return rv;
1074}
1075
1076
1077int MULTICHANNEL_TOOL::findRoutingInRuleArea( RULE_AREA* aRuleArea, std::set<BOARD_CONNECTED_ITEM*>& aOutput,
1078 std::shared_ptr<CONNECTIVITY_DATA> aConnectivity,
1079 const SHAPE_POLY_SET& aRAPoly, const REPEAT_LAYOUT_OPTIONS& aOpts ) const
1080{
1081 if( !aRuleArea || !aRuleArea->m_zone )
1082 return 0;
1083
1084 // The user also will consider tracks and vias that are inside the source area but
1085 // not connected to any of the source pads to count as "routing" (e.g. stitching vias)
1086
1087 int count = 0;
1088
1089 // When we're copying the layout of a design block, we are provided an exact list of items
1090 // rather than querying the board for items that are inside the area.
1092 {
1093 // Get all board connected items that are from the design block, except pads,
1094 // which shouldn't be copied
1095 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
1096 {
1097 // Include any connected items except pads.
1098 if( item->Type() == PCB_PAD_T )
1099 continue;
1100
1101 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1102 {
1103 // Zones are handled by the "copy other items" path, we need this check here
1104 // because design blocks explicitly include them as part of the block contents,
1105 // but other RA types grab them by querying the board for items enclosed by the RA polygon
1106 if( bci->Type() == PCB_ZONE_T )
1107 continue;
1108
1109 // Tracks inside a generator (meander) are copied with the generator.
1110 if( EDA_GROUP* parent = bci->GetParentGroup() )
1111 {
1112 if( parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
1113 continue;
1114 }
1115
1116 if( bci->IsConnected() )
1117 aOutput.insert( bci );
1118 }
1119 }
1120
1121 return (int) aOutput.size();
1122 }
1123
1124 // The design-block apply target uses a scratch zone not on the board, so enclosedByArea()
1125 // below finds nothing. Match routing against the zone outline directly.
1127 {
1128 bool zoneOnBoard = false;
1129
1130 for( ZONE* zone : board()->Zones() )
1131 {
1132 if( zone == aRuleArea->m_zone )
1133 {
1134 zoneOnBoard = true;
1135 break;
1136 }
1137 }
1138
1139 if( !zoneOnBoard )
1140 {
1141 const SHAPE_POLY_SET& areaOutline = *aRuleArea->m_zone->Outline();
1142 int maxError = board()->GetDesignSettings().m_MaxError;
1143
1144 auto enclosedByZone = [&]( BOARD_CONNECTED_ITEM* aItem )
1145 {
1146 if( aOutput.contains( aItem ) )
1147 return;
1148
1149 // Tracks inside a generator (meander) are removed with the generator.
1150 if( EDA_GROUP* parent = aItem->GetParentGroup() )
1151 {
1152 if( parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
1153 return;
1154 }
1155
1156 if( !( aRuleArea->m_zone->GetLayerSet() & aItem->GetLayerSet() ).any() )
1157 return;
1158
1159 SHAPE_POLY_SET itemShape;
1160 aItem->TransformShapeToPolygon( itemShape, aItem->GetLayer(), 0, maxError, ERROR_OUTSIDE );
1161
1162 if( itemShape.IsEmpty() )
1163 return;
1164
1165 itemShape.BooleanSubtract( areaOutline );
1166
1167 if( itemShape.IsEmpty() )
1168 {
1169 aOutput.insert( aItem );
1170 count++;
1171 }
1172 };
1173
1174 for( PCB_TRACK* track : board()->Tracks() )
1175 enclosedByZone( track );
1176
1177 for( BOARD_ITEM* drawing : board()->Drawings() )
1178 {
1179 if( drawing->IsConnected() )
1180 enclosedByZone( static_cast<BOARD_CONNECTED_ITEM*>( drawing ) );
1181 }
1182
1183 return count;
1184 }
1185 }
1186
1188 PCBEXPR_UCODE ucode;
1189 PCBEXPR_CONTEXT ctx, preflightCtx;
1190
1191 auto reportError =
1192 [&]( const wxString& aMessage, int aOffset )
1193 {
1194 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s" ), aMessage );
1195 };
1196
1197 ctx.SetErrorCallback( reportError );
1198 preflightCtx.SetErrorCallback( reportError );
1199 compiler.SetErrorCallback( reportError );
1200
1201 // Use the zone's UUID to identify it uniquely. Using the zone name could match other zones
1202 // with the same name (e.g., a copper fill zone with the same name as a rule area).
1203 wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ),
1204 aRuleArea->m_zone->m_Uuid.AsString() );
1205
1206 auto testAndAdd =
1207 [&]( BOARD_CONNECTED_ITEM* aItem )
1208 {
1209 if( aOutput.contains( aItem ) )
1210 return;
1211
1212 // Tracks inside a generator (meander) are copied with the generator.
1213 if( EDA_GROUP* parent = aItem->GetParentGroup() )
1214 {
1215 if( parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
1216 return;
1217 }
1218
1219 ctx.SetItems( aItem, aItem );
1220 LIBEVAL::VALUE* val = ucode.Run( &ctx );
1221
1222 if( val->AsDouble() != 0.0 )
1223 {
1224 aOutput.insert( aItem );
1225 count++;
1226 }
1227 };
1228
1229 if( compiler.Compile( ruleText, &ucode, &preflightCtx ) )
1230 {
1231 for( PCB_TRACK* track : board()->Tracks() )
1232 testAndAdd( track );
1233
1234 for( BOARD_ITEM* drawing : board()->Drawings() )
1235 {
1236 if( drawing->IsConnected() )
1237 testAndAdd( static_cast<BOARD_CONNECTED_ITEM*>( drawing ) );
1238 }
1239 }
1240
1241 return count;
1242}
1243
1244
1246 BOARD_COMMIT* aCommit, REPEAT_LAYOUT_OPTIONS aOpts,
1247 RULE_AREA_COMPAT_DATA& aCompatData )
1248{
1249 // copy RA shapes first
1250 SHAPE_LINE_CHAIN refOutline = aRefArea->m_zone->Outline()->COutline( 0 );
1251 SHAPE_LINE_CHAIN targetOutline = aTargetArea->m_zone->Outline()->COutline( 0 );
1252
1253 FOOTPRINT* targetAnchorFp = nullptr;
1254 VECTOR2I disp = aTargetArea->m_center - aRefArea->m_center;
1255 EDA_ANGLE rot = EDA_ANGLE( 0 );
1256
1257 if( aOpts.m_anchorFp )
1258 {
1259 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1260 {
1261 if( refFP->GetReference() == aOpts.m_anchorFp->GetReference() )
1262 targetAnchorFp = targetFP;
1263 }
1264
1265 // If the dialog-selected anchor reference doesn't exist in the target area (e.g. refs don't match),
1266 // fall back to the first matched pair to avoid center-alignment shifting the whole group.
1267 if( !targetAnchorFp && !aCompatData.m_matchingComponents.empty() )
1268 targetAnchorFp = aCompatData.m_matchingComponents.begin()->second;
1269
1270 if( targetAnchorFp )
1271 {
1272 VECTOR2I oldpos = aOpts.m_anchorFp->GetPosition();
1273 rot = EDA_ANGLE( targetAnchorFp->GetOrientationDegrees() - aOpts.m_anchorFp->GetOrientationDegrees() );
1274 aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( rot ) );
1275 oldpos = aOpts.m_anchorFp->GetPosition();
1276 VECTOR2I newpos = targetAnchorFp->GetPosition();
1277 disp = newpos - oldpos;
1278 aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( -rot ) );
1279 }
1280 }
1281
1282 SHAPE_POLY_SET refPoly;
1283 refPoly.AddOutline( refOutline );
1284 refPoly.CacheTriangulation();
1285
1286 SHAPE_POLY_SET targetPoly;
1287
1288 SHAPE_LINE_CHAIN newTargetOutline( refOutline );
1289 newTargetOutline.Rotate( rot, VECTOR2( 0, 0 ) );
1290 newTargetOutline.Move( disp );
1291 targetPoly.AddOutline( newTargetOutline );
1292 targetPoly.CacheTriangulation();
1293
1294 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
1295
1296 // Group placement targets let RepeatLayout() reuse the existing target group, and m_groupItems
1297 // flat-groups every copy into one rule-area group. Reconstructing source groups here in either
1298 // case strands their members and leaves empty phantom clones behind (issue 22316).
1299 const bool preserveGroups = aTargetArea->m_sourceType != PLACEMENT_SOURCE_T::GROUP_PLACEMENT
1300 && !aOpts.m_groupItems;
1301
1302 // Defer reconstruction until every copy is made. Cloning a source group the moment one member
1303 // is copied would duplicate user groups that merely overlap the source area (issue 22316); a
1304 // group is rebuilt only once all of its members have been reproduced.
1305 std::vector<std::pair<BOARD_ITEM*, BOARD_ITEM*>> groupFixupPairs;
1306 std::set<BOARD_ITEM*> reproducedSourceItems;
1307
1308 auto fixupParentGroup =
1309 [&]( BOARD_ITEM* sourceItem, BOARD_ITEM* destItem )
1310 {
1311 // The copy inherits the source's parent-group pointer but is not a member of that
1312 // group; clear the dangling reference.
1313 destItem->SetParentGroup( nullptr );
1314
1315 if( !preserveGroups )
1316 return;
1317
1318 if( sourceItem->GetParentGroup() )
1319 groupFixupPairs.emplace_back( sourceItem, destItem );
1320
1321 reproducedSourceItems.insert( sourceItem );
1322 };
1323
1324 // Only stage changes for a target Rule Area zone if it actually belongs to the board.
1325 // In some workflows (e.g. ApplyDesignBlockLayout), the target area is a temporary zone
1326 // and is not added to the BOARD.
1327 bool targetZoneOnBoard = false;
1328
1329 if( aTargetArea->m_zone )
1330 {
1331 for( ZONE* z : board()->Zones() )
1332 {
1333 if( z == aTargetArea->m_zone )
1334 {
1335 targetZoneOnBoard = true;
1336 break;
1337 }
1338 }
1339 }
1340
1341 if( targetZoneOnBoard )
1342 {
1343 aCommit->Modify( aTargetArea->m_zone );
1344 aCompatData.m_affectedItems.insert( aTargetArea->m_zone );
1345 aCompatData.m_groupableItems.insert( aTargetArea->m_zone );
1346
1347 // The source rule-area zone maps to the target zone; treat it as reproduced so a group
1348 // containing it can still be rebuilt.
1349 if( preserveGroups )
1350 {
1351 if( aRefArea->m_zone->GetParentGroup() )
1352 groupFixupPairs.emplace_back( aRefArea->m_zone, aTargetArea->m_zone );
1353
1354 reproducedSourceItems.insert( aRefArea->m_zone );
1355 }
1356 }
1357
1358 if( aOpts.m_copyRouting )
1359 {
1360 std::set<BOARD_CONNECTED_ITEM*> refRouting;
1361 std::set<BOARD_CONNECTED_ITEM*> targetRouting;
1362
1363 wxLogTrace( traceMultichannelTool, wxT( "copying routing: %d fps\n" ),
1364 (int) aCompatData.m_matchingComponents.size() );
1365
1366 std::set<int> refc;
1367 std::set<int> targc;
1368
1369 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1370 {
1371 for( PAD* pad : refFP->Pads() )
1372 refc.insert( pad->GetNetCode() );
1373
1374 for( PAD* pad : targetFP->Pads() )
1375 targc.insert( pad->GetNetCode() );
1376 }
1377
1378 findRoutingInRuleArea( aTargetArea, targetRouting, connectivity, targetPoly, aOpts );
1379 findRoutingInRuleArea( aRefArea, refRouting, connectivity, refPoly, aOpts );
1380
1381 // Nets used by the target group's own items, footprint pads included.
1382 std::set<int> targetGroupNets;
1383
1384 if( aTargetArea->m_group )
1385 {
1386 for( EDA_ITEM* member : aTargetArea->m_group->GetItems() )
1387 {
1388 if( member->Type() == PCB_FOOTPRINT_T )
1389 {
1390 for( PAD* pad : static_cast<FOOTPRINT*>( member )->Pads() )
1391 targetGroupNets.insert( pad->GetNetCode() );
1392 }
1393 else if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( member ) )
1394 {
1395 targetGroupNets.insert( bci->GetNetCode() );
1396 }
1397 }
1398 }
1399
1400 for( BOARD_CONNECTED_ITEM* item : targetRouting )
1401 {
1402 // Never remove pads as part of routing copy.
1403 if( item->Type() == PCB_PAD_T )
1404 continue;
1405
1406 if( aRefArea->m_designBlockItems.count( item ) )
1407 continue;
1408
1409 // Design block apply: replace only the group's own routing and loose routing on the
1410 // group's nets. Other groups' routing belongs to stacked instances (issue 24767).
1411 // Everything else is unrelated and just sits inside the block's area (issue 24944).
1412 if( aTargetArea->m_group && item->GetParentGroup() != aTargetArea->m_group )
1413 {
1414 if( item->GetParentGroup() )
1415 continue;
1416
1417 if( item->IsLocked() )
1418 continue;
1419
1420 if( item->GetNetCode() <= 0 || !targetGroupNets.contains( item->GetNetCode() ) )
1421 continue;
1422 }
1423
1424 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1425 continue;
1426
1427 if( aOpts.m_connectedRoutingOnly && !targc.contains( item->GetNetCode() ) )
1428 continue;
1429
1430 // item already removed
1431 if( aCommit->GetStatus( item ) != 0 )
1432 continue;
1433
1434 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1435 {
1436 continue;
1437 }
1438
1439 aCompatData.m_affectedItems.insert( item );
1440 aCommit->Remove( item );
1441 }
1442
1443 for( BOARD_CONNECTED_ITEM* item : refRouting )
1444 {
1445 // Never copy pads as part of routing copy.
1446 if( item->Type() == PCB_PAD_T )
1447 continue;
1448
1449 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1450 continue;
1451
1452 if( aOpts.m_connectedRoutingOnly && !refc.contains( item->GetNetCode() ) )
1453 continue;
1454
1455 if( !aRefArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1456 continue;
1457
1458 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1459 continue;
1460
1461 BOARD_CONNECTED_ITEM* copied = static_cast<BOARD_CONNECTED_ITEM*>( item->Duplicate( false ) );
1462
1463 fixupNet( item, copied, aCompatData.m_matchingComponents );
1464 fixupParentGroup( item, copied );
1465
1466 copied->Rotate( VECTOR2( 0, 0 ), rot );
1467 copied->Move( disp );
1468 aCompatData.m_groupableItems.insert( copied );
1469 aCommit->Add( copied );
1470 }
1471
1472 // Copy generators (meanders) whole so they are not flattened to loose tracks. Design
1473 // block apply has an exact item list, other rule areas resolve them by area.
1474 std::vector<PCB_GENERATOR*> refGenerators;
1475 std::vector<PCB_GENERATOR*> targetGenerators;
1476
1478 {
1479 for( EDA_ITEM* item : aRefArea->m_designBlockItems )
1480 {
1481 if( item->Type() == PCB_GENERATOR_T )
1482 refGenerators.push_back( static_cast<PCB_GENERATOR*>( item ) );
1483 }
1484
1485 EDA_GROUP* targetGroup = aTargetArea->m_group;
1486
1487 if( !targetGroup && !aTargetArea->m_components.empty() )
1488 targetGroup = ( *aTargetArea->m_components.begin() )->GetParentGroup();
1489
1490 if( targetGroup )
1491 {
1492 for( EDA_ITEM* member : targetGroup->GetItems() )
1493 {
1494 if( member->Type() == PCB_GENERATOR_T )
1495 targetGenerators.push_back( static_cast<PCB_GENERATOR*>( member ) );
1496 }
1497 }
1498 }
1499 else
1500 {
1501 const SHAPE_LINE_CHAIN& refOut = aRefArea->m_zone->Outline()->COutline( 0 );
1502 const SHAPE_LINE_CHAIN& targetOut = aTargetArea->m_zone->Outline()->COutline( 0 );
1503
1504 for( PCB_GENERATOR* gen : board()->Generators() )
1505 {
1506 if( gen->GetGeneratorType() != wxT( "tuning_pattern" ) )
1507 continue;
1508
1509 if( gen->HitTest( refOut, false ) )
1510 refGenerators.push_back( gen );
1511 else if( gen->HitTest( targetOut, false ) )
1512 targetGenerators.push_back( gen );
1513 }
1514 }
1515
1516 // Remove the target's existing generators so the copy replaces them.
1517 for( PCB_GENERATOR* gen : targetGenerators )
1518 {
1519 gen->RunOnChildren(
1520 [&]( BOARD_ITEM* child )
1521 {
1522 aCommit->Remove( child );
1523 },
1525 aCommit->Remove( gen );
1526 }
1527
1528 for( PCB_GENERATOR* gen : refGenerators )
1529 {
1530 PCB_GENERATOR* clone = gen->DeepClone();
1531
1532 clone->ClearFlags();
1533 clone->Rotate( VECTOR2( 0, 0 ), rot );
1534 clone->Move( disp );
1535 aCommit->Add( clone );
1536
1537 clone->RunOnChildren(
1538 [&]( BOARD_ITEM* child )
1539 {
1540 child->ClearFlags();
1541
1542 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( child ) )
1543 fixupNet( bci, bci, aCompatData.m_matchingComponents );
1544
1545 aCommit->Add( child );
1546 },
1548
1549 aCompatData.m_groupableItems.insert( clone );
1550 }
1551 }
1552
1553 if( aOpts.m_copyOtherItems )
1554 {
1555 std::set<BOARD_ITEM*> sourceItems;
1556 std::set<BOARD_ITEM*> targetItems;
1557
1558 findOtherItemsInRuleArea( aRefArea, sourceItems );
1559 findOtherItemsInRuleArea( aTargetArea, targetItems );
1560
1561 // Apply Design Block Layout uses synthetic copper-only rule area zones that don't
1562 // reflect the layers the user actually drew on. The source items were collected by
1563 // explicit enumeration (m_designBlockItems) and the destination is a group bounding
1564 // box, so the per-item layer filter would incorrectly reject silkscreen, fab and
1565 // user drawings. Skip the layer filter only when both halves are the synthetic
1566 // design-block-to-group flow; regular GROUP_PLACEMENT rule areas have user-authored
1567 // layer sets that must still be honored.
1568 const bool skipLayerFilter = aRefArea->m_sourceType == PLACEMENT_SOURCE_T::DESIGN_BLOCK
1569 && aTargetArea->m_sourceType
1571
1572 for( BOARD_ITEM* item : targetItems )
1573 {
1574 if( item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
1575 continue;
1576
1577 // Don't remove the appended source items: this geometric query can pick them up, but
1578 // they're deleted when the temporary append is reverted, leaving dangling pointers.
1579 if( aRefArea->m_designBlockItems.count( item ) )
1580 continue;
1581
1582 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1583 continue;
1584
1585 // item already removed
1586 if( aCommit->GetStatus( item ) != 0 )
1587 continue;
1588
1589 if( item->Type() == PCB_ZONE_T )
1590 {
1591 ZONE* zone = static_cast<ZONE*>( item );
1592
1593 // Check all zone layers are included in the target rule area.
1594 if( skipLayerFilter
1595 || aTargetArea->m_zone->GetLayerSet().ContainsAll( zone->GetLayerSet() ) )
1596 {
1597 aCompatData.m_affectedItems.insert( zone );
1598 aCommit->Remove( zone );
1599 }
1600 }
1601 else
1602 {
1603 if( skipLayerFilter
1604 || aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1605 {
1606 aCompatData.m_affectedItems.insert( item );
1607 aCommit->Remove( item );
1608 }
1609 }
1610 }
1611
1612 for( BOARD_ITEM* item : sourceItems )
1613 {
1614 if( item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
1615 continue;
1616
1617 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1618 continue;
1619
1620 BOARD_ITEM* copied = nullptr;
1621
1622 if( item->Type() == PCB_ZONE_T )
1623 {
1624 ZONE* zone = static_cast<ZONE*>( item );
1625
1626 if( !skipLayerFilter )
1627 {
1628 LSET allowedLayers =
1629 aRefArea->m_zone->GetLayerSet() & aTargetArea->m_zone->GetLayerSet();
1630
1631 // Check all zone layers are included in both source and target rule areas.
1632 if( !allowedLayers.ContainsAll( zone->GetLayerSet() ) )
1633 continue;
1634 }
1635
1636 ZONE* targetZone = static_cast<ZONE*>( item->Duplicate( false ) );
1637 fixupNet( zone, targetZone, aCompatData.m_matchingComponents );
1638
1639 copied = targetZone;
1640 }
1641 else
1642 {
1643 if( !skipLayerFilter )
1644 {
1645 if( !aRefArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1646 continue;
1647
1648 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1649 continue;
1650 }
1651
1652 copied = static_cast<BOARD_ITEM*>( item->Clone() );
1653 }
1654
1655 if( copied )
1656 {
1657 fixupParentGroup( item, copied );
1658
1659 copied->ClearFlags();
1660 copied->Rotate( VECTOR2( 0, 0 ), rot );
1661 copied->Move( disp );
1662 aCompatData.m_groupableItems.insert( copied );
1663 aCommit->Add( copied );
1664 }
1665 }
1666 }
1667
1668 if( aOpts.m_copyPlacement )
1669 {
1670 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1671 {
1672 if( !aRefArea->m_zone->GetLayerSet().Contains( refFP->GetLayer() ) )
1673 {
1674 wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (ref layer)\n" ),
1675 refFP->GetReference() );
1676 continue;
1677 }
1678 if( !aTargetArea->m_zone->GetLayerSet().Contains( refFP->GetLayer() ) )
1679 {
1680 wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (target layer)\n" ),
1681 refFP->GetReference() );
1682 continue;
1683 }
1684
1685 // For regular Rule Area repeat, ignore source footprints outside the reference area.
1686 // For Design Block apply, use the exact source item set collected from the block.
1688 && !refFP->GetEffectiveShape( refFP->GetLayer() )->Collide( &refPoly, 0 ) )
1689 {
1690 continue;
1691 }
1692
1693 if( targetFP->IsLocked() && !aOpts.m_includeLockedItems )
1694 continue;
1695
1696 aCommit->Modify( targetFP );
1697
1698 targetFP->SetLayerAndFlip( refFP->GetLayer() );
1699 targetFP->SetOrientation( refFP->GetOrientation() );
1700 targetFP->SetPosition( refFP->GetPosition() );
1701 targetFP->Rotate( VECTOR2( 0, 0 ), rot );
1702 targetFP->Move( disp );
1703
1704 for( PCB_FIELD* refField : refFP->GetFields() )
1705 {
1706 wxCHECK2( refField, continue );
1707
1708 PCB_FIELD* targetField = targetFP->GetField( refField->GetName() );
1709
1710 if( !targetField )
1711 continue;
1712
1713 targetField->SetLayerSet( refField->GetLayerSet() );
1714 targetField->SetVisible( refField->IsVisible() );
1715 targetField->SetAttributes( refField->GetAttributes() );
1716 targetField->SetPosition( refField->GetPosition() );
1717 targetField->SetTextAngle( refField->GetTextAngle() );
1718 targetField->Rotate( VECTOR2( 0, 0 ), rot );
1719 targetField->Move( disp );
1720 targetField->SetIsKnockout( refField->IsKnockout() );
1721 }
1722
1723 // Copy non-field text items. Texts can share content (e.g. "${REFERENCE}" on both
1724 // F.SilkS and B.SilkS), so match one-to-one and prefer the same layer. Otherwise both
1725 // collapse onto one target item and the other side's text is lost.
1726 std::set<PCB_TEXT*> consumedTargets;
1727
1728 for( BOARD_ITEM* refItem : refFP->GraphicalItems() )
1729 {
1730 if( refItem->Type() != PCB_TEXT_T )
1731 continue;
1732
1733 PCB_TEXT* refText = static_cast<PCB_TEXT*>( refItem );
1734 PCB_TEXT* targetText = nullptr;
1735
1736 for( BOARD_ITEM* targetItem : targetFP->GraphicalItems() )
1737 {
1738 if( targetItem->Type() != PCB_TEXT_T )
1739 continue;
1740
1741 PCB_TEXT* candidate = static_cast<PCB_TEXT*>( targetItem );
1742
1743 if( consumedTargets.contains( candidate ) || candidate->GetText() != refText->GetText() )
1744 {
1745 continue;
1746 }
1747
1748 targetText = candidate;
1749
1750 if( candidate->GetLayer() == refText->GetLayer() )
1751 break;
1752 }
1753
1754 if( !targetText )
1755 continue;
1756
1757 consumedTargets.insert( targetText );
1758
1759 targetText->SetLayer( refText->GetLayer() );
1760 targetText->SetVisible( refText->IsVisible() );
1761 targetText->SetAttributes( refText->GetAttributes() );
1762 targetText->SetPosition( refText->GetPosition() );
1763 targetText->SetTextAngle( refText->GetTextAngle() );
1764 targetText->Rotate( VECTOR2( 0, 0 ), rot );
1765 targetText->Move( disp );
1766 targetText->SetIsKnockout( refText->IsKnockout() );
1767 }
1768
1769 // Copy 3D model settings
1770 targetFP->Models() = refFP->Models();
1771
1772 aCompatData.m_affectedItems.insert( targetFP );
1773 aCompatData.m_groupableItems.insert( targetFP );
1774
1775 // The matched footprint maps to its target; treat it as reproduced so a group
1776 // containing it can be rebuilt.
1777 if( preserveGroups && refFP->GetParentGroup() )
1778 groupFixupPairs.emplace_back( refFP, targetFP );
1779
1780 if( preserveGroups )
1781 reproducedSourceItems.insert( refFP );
1782 }
1783 }
1784
1785 // Rebuild a source group only when all of its members were reproduced. A group that merely
1786 // overlaps the source area keeps uncopied members, so it is left untouched rather than
1787 // partially duplicated (issue 22316).
1788 if( preserveGroups && !groupFixupPairs.empty() )
1789 {
1790 std::map<EDA_GROUP*, EDA_GROUP*> groupMap;
1791 std::map<EDA_GROUP*, bool> fullyReproducedCache;
1792
1793 auto groupFullyReproduced =
1794 [&]( EDA_GROUP* aGroup )
1795 {
1796 if( auto it = fullyReproducedCache.find( aGroup ); it != fullyReproducedCache.end() )
1797 return it->second;
1798
1799 bool reproduced = true;
1800
1801 for( EDA_ITEM* member : aGroup->GetItems() )
1802 {
1803 // Nested groups are not reproduced, so a parent containing one can never
1804 // be fully reproduced.
1805 if( !member->IsBOARD_ITEM()
1806 || !reproducedSourceItems.contains( static_cast<BOARD_ITEM*>( member ) ) )
1807 {
1808 reproduced = false;
1809 break;
1810 }
1811 }
1812
1813 fullyReproducedCache[aGroup] = reproduced;
1814 return reproduced;
1815 };
1816
1817 for( const auto& [sourceItem, destItem] : groupFixupPairs )
1818 {
1819 EDA_GROUP* parentGroup = sourceItem->GetParentGroup();
1820
1821 if( !parentGroup || !groupFullyReproduced( parentGroup ) )
1822 continue;
1823
1824 if( !groupMap.contains( parentGroup ) )
1825 {
1826 PCB_GROUP* newGroup = static_cast<PCB_GROUP*>(
1827 static_cast<PCB_GROUP*>( parentGroup->AsEdaItem() )->Duplicate( false ) );
1828 newGroup->GetItems().clear();
1829 newGroup->SetParentGroup( nullptr );
1830
1831 if( newGroup->Type() == PCB_GENERATOR_T )
1832 {
1833 newGroup->Rotate( VECTOR2( 0, 0 ), rot );
1834 newGroup->Move( disp );
1835 }
1836
1837 groupMap[parentGroup] = newGroup;
1838 aCommit->Add( newGroup );
1839 }
1840
1841 // AddItem reparents the footprint out of any group it already belongs to; stage that
1842 // group so the membership change is captured for undo.
1843 if( EDA_GROUP* oldGroup = destItem->GetParentGroup() )
1844 {
1845 if( oldGroup != groupMap[parentGroup] )
1846 aCommit->Modify( oldGroup->AsEdaItem() );
1847 }
1848
1849 groupMap[parentGroup]->AddItem( destItem );
1850 }
1851 }
1852
1853 aTargetArea->m_zone->RemoveAllContours();
1854 aTargetArea->m_zone->AddPolygon( newTargetOutline );
1855 aTargetArea->m_zone->UnHatchBorder();
1856 aTargetArea->m_zone->HatchBorder();
1857
1858 return true;
1859}
1860
1866 TMATCH::COMPONENT_MATCHES& aComponentMatches )
1867{
1868 // Copy as no-net.
1869 if( aComponentMatches.empty() )
1870 {
1871 aTarget->SetNetCode( 0 );
1872 return;
1873 }
1874
1875 auto connectivity = board()->GetConnectivity();
1876 const std::vector<BOARD_CONNECTED_ITEM*> refConnectedPads = connectivity->GetNetItems( aRef->GetNetCode(),
1877 { PCB_PAD_T } );
1878
1879 for( const BOARD_CONNECTED_ITEM* refConItem : refConnectedPads )
1880 {
1881 if( refConItem->Type() != PCB_PAD_T )
1882 continue;
1883
1884 const PAD* refPad = static_cast<const PAD*>( refConItem );
1885 FOOTPRINT* sourceFootprint = refPad->GetParentFootprint();
1886
1887 if( aComponentMatches.contains( sourceFootprint ) )
1888 {
1889 const FOOTPRINT* targetFootprint = aComponentMatches[sourceFootprint];
1890 std::vector<const PAD*> targetFpPads = targetFootprint->GetPads( refPad->GetNumber() );
1891
1892 if( !targetFpPads.empty() )
1893 {
1894 int targetNetCode = targetFpPads[0]->GetNet()->GetNetCode();
1895 aTarget->SetNetCode( targetNetCode );
1896
1897 break;
1898 }
1899 }
1900 }
1901}
1902
1903
1904std::vector<NETINFO_ITEM*> MULTICHANNEL_TOOL::IsolateDesignBlockAutoNets( BOARD* aBoard,
1905 const std::set<FOOTPRINT*>& aFootprints,
1906 const std::unordered_set<EDA_ITEM*>& aItems )
1907{
1908 std::vector<NETINFO_ITEM*> created;
1909 std::map<int, NETINFO_ITEM*> remap;
1910 int counter = 0;
1911
1912 // Auto-generated names are tied to a reference designator, so a block's Net-(D3-A) collides
1913 // with a different part's Net-(D3-A) on the board. Named/power nets are intentional and left
1914 // alone so the topology matcher keeps excluding real global rails.
1915 auto isAutoName = []( const wxString& aName )
1916 {
1917 return aName.StartsWith( wxT( "Net-(" ) ) || aName.StartsWith( wxT( "unconnected-" ) );
1918 };
1919
1920 auto remapItem = [&]( BOARD_CONNECTED_ITEM* aItem )
1921 {
1922 int code = aItem->GetNetCode();
1923
1924 if( code <= 0 )
1925 return;
1926
1927 NETINFO_ITEM* oldNet = aBoard->FindNet( code );
1928
1929 if( !oldNet || !isAutoName( oldNet->GetNetname() ) )
1930 return;
1931
1932 auto it = remap.find( code );
1933
1934 if( it == remap.end() )
1935 {
1936 wxString name;
1937
1938 do
1939 {
1940 name = wxString::Format( wxT( "__dbapply_%d_%d" ), code, counter++ );
1941 } while( aBoard->FindNet( name ) );
1942
1943 NETINFO_ITEM* newNet = new NETINFO_ITEM( aBoard, name );
1944 aBoard->Add( newNet );
1945 created.push_back( newNet );
1946 it = remap.emplace( code, newNet ).first;
1947 }
1948
1949 aItem->SetNet( it->second );
1950 };
1951
1952 for( FOOTPRINT* fp : aFootprints )
1953 {
1954 for( PAD* pad : fp->Pads() )
1955 remapItem( pad );
1956 }
1957
1958 for( EDA_ITEM* item : aItems )
1959 {
1960 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1961 remapItem( bci );
1962 }
1963
1964 return created;
1965}
1966
1967
1968// A placed design block or repeated sheet stamps the originating symbol instance UUID into each
1969// footprint's path. When that linkage is complete and unique it is an authoritative one to one
1970// mapping, independent of net topology. Returns false (and leaves aResult untouched) unless it
1971// yields a full pad compatible bijection, so callers can fall back to topology matching.
1972static bool matchBySymbolInstancePath( const std::set<FOOTPRINT*>& aRef, const std::set<FOOTPRINT*>& aTarget,
1973 TMATCH::COMPONENT_MATCHES& aResult )
1974{
1975 if( aRef.empty() || aRef.size() != aTarget.size() )
1976 return false;
1977
1978 auto symbolUuid = []( const FOOTPRINT* aFp ) -> KIID
1979 {
1980 const KIID_PATH& path = aFp->GetPath();
1981 return path.empty() ? niluuid : path.back();
1982 };
1983
1984 std::map<KIID, FOOTPRINT*> targetByUuid;
1985
1986 for( FOOTPRINT* fp : aTarget )
1987 {
1988 KIID uuid = symbolUuid( fp );
1989
1990 // A missing or duplicated UUID (copy paste, hand built group) is not a clean instance link
1991 if( uuid == niluuid || !targetByUuid.emplace( uuid, fp ).second )
1992 return false;
1993 }
1994
1996 std::set<FOOTPRINT*> used;
1997
1998 for( FOOTPRINT* refFp : aRef )
1999 {
2000 KIID uuid = symbolUuid( refFp );
2001
2002 if( uuid == niluuid )
2003 return false;
2004
2005 auto it = targetByUuid.find( uuid );
2006
2007 if( it == targetByUuid.end() )
2008 return false;
2009
2010 FOOTPRINT* targetFp = it->second;
2011
2012 // Routing and placement copy only makes sense between pad compatible footprints
2013 if( refFp->GetFPID() != targetFp->GetFPID() || refFp->Pads().size() != targetFp->Pads().size() )
2014 return false;
2015
2016 if( !used.insert( targetFp ).second )
2017 return false;
2018
2019 result[refFp] = targetFp;
2020 }
2021
2022 aResult = std::move( result );
2023 return true;
2024}
2025
2026
2028 RULE_AREA_COMPAT_DATA& aMatches,
2029 const TMATCH::ISOMORPHISM_PARAMS& aParams )
2030{
2031 using namespace TMATCH;
2032
2033 // Footprint-free design block has nothing to match to
2034 if( aRefArea->m_sourceType == PLACEMENT_SOURCE_T::DESIGN_BLOCK && aRefArea->m_components.empty()
2035 && aTargetArea->m_components.empty() )
2036 {
2037 aMatches.m_matchingComponents.clear();
2038 aMatches.m_isOk = true;
2039 aMatches.m_errorMsg = _( "OK" );
2040 aMatches.m_mismatchReasons.clear();
2041 return true;
2042 }
2043
2044 // Placement areas resolve their components from their source, so two areas sharing a sheet,
2045 // component class or group resolve to identical footprints. Repeating into such a target would
2046 // move and delete the reference's own items, corrupting the placement rather than copying it.
2047 if( !aRefArea->m_components.empty() )
2048 {
2049 std::set<FOOTPRINT*> shared;
2050 std::set_intersection( aRefArea->m_components.begin(), aRefArea->m_components.end(),
2051 aTargetArea->m_components.begin(), aTargetArea->m_components.end(),
2052 std::inserter( shared, shared.begin() ) );
2053
2054 if( !shared.empty() )
2055 {
2056 aMatches.m_matchingComponents.clear();
2057 aMatches.m_isOk = false;
2058 aMatches.m_errorMsg = _( "Target Rule Area shares components with the reference area" );
2059 aMatches.m_mismatchReasons.clear();
2060 aMatches.m_mismatchReasons.push_back(
2061 _( "This target Rule Area selects the same components as the reference area. "
2062 "Repeat layout cannot copy a Rule Area onto itself. Give each placement Rule "
2063 "Area a distinct sheet, component class or group." ) );
2064 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Shared components:\n%s" ),
2065 FormatComponentList( shared ) ) );
2066 return false;
2067 }
2068 }
2069
2070 // A global rail connects >=2 pads in more than one channel; find them across all areas so the
2071 // exclusion is the same for every target, not just the one being matched against.
2072 std::unordered_map<int, std::vector<const RULE_AREA*>> netInternalAreas;
2073
2074 for( const RULE_AREA& area : m_areas.m_areas )
2075 {
2076 std::unordered_map<int, int> areaNetPadCounts;
2077
2078 for( const FOOTPRINT* fp : area.m_components )
2079 {
2080 for( const PAD* pad : fp->Pads() )
2081 {
2082 if( pad->GetNetCode() > 0 )
2083 areaNetPadCounts[pad->GetNetCode()]++;
2084 }
2085 }
2086
2087 for( const auto& [netCode, padCount] : areaNetPadCounts )
2088 {
2089 if( padCount >= 2 )
2090 netInternalAreas[netCode].push_back( &area );
2091 }
2092 }
2093
2094 // Require two component-disjoint areas so overlapping rule areas can't make a per-channel net
2095 // look global.
2096 auto disjoint =
2097 []( const RULE_AREA* aA, const RULE_AREA* aB )
2098 {
2099 for( FOOTPRINT* fp : aA->m_components )
2100 {
2101 if( aB->m_components.count( fp ) )
2102 return false;
2103 }
2104
2105 return true;
2106 };
2107
2108 std::unordered_set<int> globalNets;
2109
2110 for( const auto& [netCode, areas] : netInternalAreas )
2111 {
2112 for( size_t i = 0; i < areas.size() && !globalNets.count( netCode ); i++ )
2113 {
2114 for( size_t j = i + 1; j < areas.size(); j++ )
2115 {
2116 if( disjoint( areas[i], areas[j] ) )
2117 {
2118 globalNets.insert( netCode );
2119 break;
2120 }
2121 }
2122 }
2123 }
2124
2125 PROF_TIMER timerBuild;
2126 std::unique_ptr<CONNECTION_GRAPH> cgRef( CONNECTION_GRAPH::BuildFromFootprintSet( aRefArea->m_components,
2127 aTargetArea->m_components,
2128 globalNets ) );
2129 std::unique_ptr<CONNECTION_GRAPH> cgTarget( CONNECTION_GRAPH::BuildFromFootprintSet( aTargetArea->m_components,
2130 aRefArea->m_components,
2131 globalNets ) );
2132 timerBuild.Stop();
2133
2134 wxLogTrace( traceMultichannelTool, wxT( "Graph construction: %s (%d + %d components)" ),
2135 timerBuild.to_string(),
2136 (int) aRefArea->m_components.size(),
2137 (int) aTargetArea->m_components.size() );
2138
2139 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> mismatchReasons;
2140
2141 PROF_TIMER timerIso;
2142 bool status = cgRef->FindIsomorphism( cgTarget.get(), aMatches.m_matchingComponents,
2143 mismatchReasons, aParams );
2144 timerIso.Stop();
2145
2146 wxLogTrace( traceMultichannelTool, wxT( "FindIsomorphism: %s, result=%d" ),
2147 timerIso.to_string(), status ? 1 : 0 );
2148
2149 // Net topology can legitimately differ between a design block and its placed instance once the
2150 // user edits connectivity on the board (a wire tying two block pads onto one net, etc.). Fall
2151 // back to the symbol instance linkage, which still gives the correct mapping in that case. Skip
2152 // it on a cancelled scan so cancellation is not mistaken for a topology miss.
2153 const bool cancelled = aParams.m_cancelled && aParams.m_cancelled->load( std::memory_order_relaxed );
2154
2155 if( !status && !cancelled
2156 && matchBySymbolInstancePath( aRefArea->m_components, aTargetArea->m_components,
2157 aMatches.m_matchingComponents ) )
2158 {
2159 status = true;
2160 }
2161
2162 aMatches.m_isOk = status;
2163
2164 if( status )
2165 {
2166 aMatches.m_errorMsg = _( "OK" );
2167 aMatches.m_mismatchReasons.clear();
2168 return true;
2169 }
2170
2171 aMatches.m_mismatchReasons.clear();
2172
2173 for( const auto& reason : mismatchReasons )
2174 {
2175 if( reason.m_reason.IsEmpty() )
2176 continue;
2177
2178 if( !reason.m_reference.IsEmpty() && !reason.m_candidate.IsEmpty() )
2179 {
2180 aMatches.m_mismatchReasons.push_back( wxString::Format( wxT( "%s -> %s: %s" ),
2181 reason.m_reference,
2182 reason.m_candidate,
2183 reason.m_reason ) );
2184 }
2185 else if( !reason.m_reference.IsEmpty() )
2186 {
2187 aMatches.m_mismatchReasons.push_back( wxString::Format( wxT( "%s: %s" ),
2188 reason.m_reference,
2189 reason.m_reason ) );
2190 }
2191 else
2192 aMatches.m_mismatchReasons.push_back( reason.m_reason );
2193 }
2194
2195 if( aMatches.m_mismatchReasons.empty() )
2196 aMatches.m_mismatchReasons.push_back( _( "Topology mismatch" ) );
2197
2198 // Component count mismatch
2199 if( aRefArea->m_components.size() != aTargetArea->m_components.size() )
2200 {
2201 aMatches.m_mismatchReasons.push_back(
2202 wxString::Format( _( "Reference area total components: %d" ), (int) aRefArea->m_components.size() ) );
2203 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Reference area components:\n%s" ),
2204 FormatComponentList( aRefArea->m_components ) ) );
2205 aMatches.m_mismatchReasons.push_back(
2206 wxString::Format( _( "Target area total components: %d" ), (int) aTargetArea->m_components.size() ) );
2207 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Target area components:\n%s" ),
2208 FormatComponentList( aTargetArea->m_components ) ) );
2209 }
2210
2211 aMatches.m_errorMsg = aMatches.m_mismatchReasons.front();
2212
2213 return status;
2214}
2215
2216
2218 const std::unordered_set<BOARD_ITEM*>& aItemsToRemove )
2219{
2220 // Note: groups are only collections, not "real" hierarchy. A group's members are still parented
2221 // by the board (and therefore nested groups are still in the board's list of groups).
2222 for( PCB_GROUP* group : board()->Groups() )
2223 {
2224 std::vector<EDA_ITEM*> pruneList;
2225
2226 for( EDA_ITEM* refItem : group->GetItems() )
2227 {
2228 for( BOARD_ITEM* testItem : aItemsToRemove )
2229 {
2230 if( refItem->m_Uuid == testItem->m_Uuid )
2231 pruneList.push_back( refItem );
2232 }
2233 }
2234
2235 if( !pruneList.empty() )
2236 {
2237 aCommit.Modify( group );
2238
2239 for( EDA_ITEM* item : pruneList )
2240 group->RemoveItem( item );
2241
2242 if( group->GetItems().size() < 2 )
2243 aCommit.Remove( group );
2244 }
2245 }
2246
2247 return false;
2248}
2249
2250
2252{
2253 if( Pgm().IsGUI() )
2254 {
2256
2257 if( m_areas.m_areas.size() <= 1 )
2258 {
2259 frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
2260 "schematic has only one or no hierarchical sheets, "
2261 "groups, or component classes." ),
2262 true );
2263 return 0;
2264 }
2265
2267 int ret = dialog.ShowModal();
2268
2269 if( ret != wxID_OK )
2270 return 0;
2271 }
2272
2273 for( ZONE* zone : board()->Zones() )
2274 {
2275 if( !zone->GetIsRuleArea() )
2276 continue;
2277
2278 if( !zone->GetPlacementAreaEnabled() )
2279 continue;
2280
2281 std::set<FOOTPRINT*> components;
2282 RULE_AREA zoneRA;
2283 zoneRA.m_zone = zone;
2284 zoneRA.m_sourceType = zone->GetPlacementAreaSourceType();
2285 findComponentsInRuleArea( &zoneRA, components );
2286
2287 if( components.empty() )
2288 continue;
2289
2290 for( RULE_AREA& ra : m_areas.m_areas )
2291 {
2292 if( components == ra.m_components )
2293 {
2294 if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::SHEETNAME )
2295 {
2296 wxLogTrace( traceMultichannelTool,
2297 wxT( "Placement rule area for sheet '%s' already exists as '%s'\n" ),
2298 ra.m_sheetPath, zone->GetZoneName() );
2299 }
2300 else if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::COMPONENT_CLASS )
2301 {
2302 wxLogTrace( traceMultichannelTool,
2303 wxT( "Placement rule area for component class '%s' already exists as '%s'\n" ),
2304 ra.m_componentClass, zone->GetZoneName() );
2305 }
2306 else
2307 {
2308 wxLogTrace( traceMultichannelTool,
2309 wxT( "Placement rule area for group '%s' already exists as '%s'\n" ),
2310 ra.m_groupName, zone->GetZoneName() );
2311 }
2312
2313 ra.m_oldZone = zone;
2314 ra.m_existsAlready = true;
2315 }
2316 }
2317 }
2318
2319 wxLogTrace( traceMultichannelTool, wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() );
2320
2321 BOARD_COMMIT commit( GetManager(), true, false );
2322
2323 for( RULE_AREA& ra : m_areas.m_areas )
2324 {
2325 if( !ra.m_generateEnabled )
2326 continue;
2327
2328 if( ra.m_existsAlready && !m_areas.m_replaceExisting )
2329 continue;
2330
2331 if( ra.m_components.empty() )
2332 continue;
2333
2334 SHAPE_LINE_CHAIN raOutline;
2335
2336 // Groups are a way for the user to more explicitly provide a list of items to include in
2337 // the multichannel tool, as opposed to inferring them based on sheet structure or component classes.
2338 // So for group-based RAs, we build the RA outline based everything in the group, not just components.
2340 {
2341 std::set<BOARD_ITEM*> groupItems = queryBoardItemsInGroup( ra.m_groupName );
2342
2343 if( groupItems.empty() )
2344 {
2345 wxLogTrace( traceMultichannelTool,
2346 wxT( "Skipping placement rule area generation for source group '%s': group has no board items." ),
2347 ra.m_groupName );
2348 continue;
2349 }
2350
2351 raOutline = buildRAOutline( groupItems, 100000 );
2352 }
2353 else
2354 {
2355 // Start from the footprints, then also take everything in the design-block groups
2356 // they belong to (recursively) so routing and meanders that extend past the
2357 // footprints land inside the outline. Group membership keeps it bounded to this channel.
2358 std::set<BOARD_ITEM*> outlineItems;
2359 std::set<EDA_GROUP*> groups;
2360 std::set<int> channelNets;
2361
2362 for( FOOTPRINT* fp : ra.m_components )
2363 {
2364 outlineItems.insert( fp );
2365
2366 for( PAD* pad : fp->Pads() )
2367 channelNets.insert( pad->GetNetCode() );
2368
2369 for( EDA_GROUP* g = fp->GetParentGroup(); g; g = g->AsEdaItem()->GetParentGroup() )
2370 groups.insert( g );
2371 }
2372
2373 for( EDA_GROUP* g : groups )
2374 collectGroupBoardItems( g, outlineItems );
2375
2376 // Also include tracks and vias on nets local to this channel (all pads on the net
2377 // belong to the channel), so loose connections between blocks land in the outline.
2378 std::set<int> foreignNets;
2379
2380 for( FOOTPRINT* fp : board()->Footprints() )
2381 {
2382 if( ra.m_components.count( fp ) )
2383 continue;
2384
2385 for( PAD* pad : fp->Pads() )
2386 foreignNets.insert( pad->GetNetCode() );
2387 }
2388
2389 for( PCB_TRACK* track : board()->Tracks() )
2390 {
2391 int net = track->GetNetCode();
2392
2393 if( net > 0 && channelNets.count( net ) && !foreignNets.count( net ) )
2394 outlineItems.insert( track );
2395 }
2396
2397 raOutline = buildRAOutline( outlineItems, 100000 );
2398 }
2399
2400 std::unique_ptr<ZONE> newZone( new ZONE( board() ) );
2401
2403 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_sheetPath ) );
2405 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_componentClass ) );
2406 else
2407 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_groupName ) );
2408
2409 wxLogTrace( traceMultichannelTool, wxT( "Generated rule area '%s' (%d components)\n" ),
2410 newZone->GetZoneName(),
2411 (int) ra.m_components.size() );
2412
2413 newZone->SetIsRuleArea( true );
2414 newZone->SetLayerSet( LSET::AllCuMask() );
2415 newZone->SetPlacementAreaEnabled( true );
2416 newZone->SetDoNotAllowZoneFills( false );
2417 newZone->SetDoNotAllowVias( false );
2418 newZone->SetDoNotAllowTracks( false );
2419 newZone->SetDoNotAllowPads( false );
2420 newZone->SetDoNotAllowFootprints( false );
2421
2423 {
2424 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::SHEETNAME );
2425 newZone->SetPlacementAreaSource( ra.m_sheetPath );
2426 }
2428 {
2429 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::COMPONENT_CLASS );
2430 newZone->SetPlacementAreaSource( ra.m_componentClass );
2431 }
2432 else
2433 {
2434 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT );
2435 newZone->SetPlacementAreaSource( ra.m_groupName );
2436 }
2437
2438 newZone->AddPolygon( raOutline );
2439 newZone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
2440
2441 if( ra.m_existsAlready )
2442 {
2443 commit.Remove( ra.m_oldZone );
2444 }
2445
2446 ra.m_zone = newZone.release();
2447 commit.Add( ra.m_zone );
2448 }
2449
2450 // fixme: handle corner cases where the items belonging to a Rule Area already
2451 // belong to other groups.
2452
2453 if( m_areas.m_options.m_groupItems )
2454 {
2455 for( RULE_AREA& ra : m_areas.m_areas )
2456 {
2457 if( !ra.m_generateEnabled )
2458 continue;
2459
2460 if( ra.m_existsAlready && !m_areas.m_replaceExisting )
2461 continue;
2462
2463 // A group needs at least 2 items (zone + at least 1 component)
2464 if( ra.m_components.empty() )
2465 continue;
2466
2467 std::unordered_set<BOARD_ITEM*> toPrune;
2468
2469 std::copy( ra.m_components.begin(), ra.m_components.end(), std::inserter( toPrune, toPrune.begin() ) );
2470
2471 if( ra.m_existsAlready )
2472 toPrune.insert( ra.m_zone );
2473
2474 pruneExistingGroups( commit, toPrune );
2475
2476 PCB_GROUP* group = new PCB_GROUP( board() );
2477
2478 commit.Add( group );
2479
2480 commit.Modify( ra.m_zone );
2481 group->AddItem( ra.m_zone );
2482
2483 for( FOOTPRINT* fp : ra.m_components )
2484 {
2485 commit.Modify( fp );
2486 group->AddItem( fp );
2487 }
2488 }
2489 }
2490
2491 commit.Push( _( "Auto-generate placement rule areas" ) );
2492
2493 return true;
2494}
const char * name
@ ERROR_OUTSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:295
virtual void SetLayerSet(const LSET &aLayers)
Definition board_item.h:323
virtual bool IsKnockout() const
Definition board_item.h:382
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:383
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:343
FOOTPRINT * GetParentFootprint() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1355
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2777
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1158
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:646
constexpr BOX2< Vec > GetInflated(coord_type aDx, coord_type aDy) const
Get a new rectangle that is this one, inflated by aDx and aDy.
Definition box2.h:634
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:68
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
int GetStatus(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Returns status of an item.
Definition commit.cpp:191
A lightweight representation of a component class.
const std::vector< COMPONENT_CLASS * > & GetConstituentClasses() const
Fetches a vector of the constituent classes for this (effective) class.
const std::vector< BOARD_CONNECTED_ITEM * > GetNetItems(int aNetCode, const std::vector< KICAD_T > &aTypes) const
Function GetNetItems() Returns the list of items that belong to a certain net.
int ShowModal() override
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:42
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:50
virtual EDA_ITEM * AsEdaItem()=0
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
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:114
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:154
virtual void SetParentGroup(EDA_GROUP *aGroup)
Definition eda_item.h:113
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:110
virtual bool IsVisible() const
Definition eda_text.h:208
void SetAttributes(const EDA_TEXT &aSrc, bool aSetPosition=true)
Set the text attributes from another instance.
Definition eda_text.cpp:428
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
const TEXT_ATTRIBUTES & GetAttributes() const
Definition eda_text.h:252
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
double GetOrientationDegrees() const
Definition footprint.h:439
std::deque< PAD * > & Pads()
Definition footprint.h:375
const LIB_ID & GetFPID() const
Definition footprint.h:444
std::vector< const PAD * > GetPads(const wxString &aPadNumber, const PAD *aIgnore=nullptr) const
const wxString & GetReference() const
Definition footprint.h:857
VECTOR2I GetPosition() const override
Definition footprint.h:406
Definition kiid.h:46
wxString AsString() const
Definition kiid.cpp:264
void SetErrorCallback(std::function< void(const wxString &aMessage, int aOffset)> aCallback)
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
void SetErrorCallback(std::function< void(const wxString &aMessage, int aOffset)> aCallback)
VALUE * Run(CONTEXT *ctx)
virtual double AsDouble() const
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
bool ContainsAll(const LSET &aLayers) const
See if this layer set contains all layers in another set.
Definition lset.h:85
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
int CheckRACompatibility(ZONE *aRefZone)
std::set< FOOTPRINT * > queryComponentsInSheet(wxString aSheetName) const
bool findOtherItemsInRuleArea(RULE_AREA *aRuleArea, std::set< BOARD_ITEM * > &aItems)
int repeatLayout(const TOOL_EVENT &aEvent)
bool findComponentsInRuleArea(RULE_AREA *aRuleArea, std::set< FOOTPRINT * > &aComponents)
void UpdatePickedItem(const EDA_ITEM *aItem) override
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
const SHAPE_LINE_CHAIN buildRAOutline(std::set< FOOTPRINT * > &aFootprints, int aMargin)
bool resolveConnectionTopology(RULE_AREA *aRefArea, RULE_AREA *aTargetArea, RULE_AREA_COMPAT_DATA &aMatches, const TMATCH::ISOMORPHISM_PARAMS &aParams={})
int findRoutingInRuleArea(RULE_AREA *aRuleArea, std::set< BOARD_CONNECTED_ITEM * > &aOutput, std::shared_ptr< CONNECTIVITY_DATA > aConnectivity, const SHAPE_POLY_SET &aRAPoly, const REPEAT_LAYOUT_OPTIONS &aOpts) const
wxString stripComponentIndex(const wxString &aRef) const
static std::vector< NETINFO_ITEM * > IsolateDesignBlockAutoNets(BOARD *aBoard, const std::set< FOOTPRINT * > &aFootprints, const std::unordered_set< EDA_ITEM * > &aItems)
Remap auto-generated nets (Net-(...), unconnected-...) of a design block that was appended for layout...
RULE_AREAS_DATA m_areas
bool pruneExistingGroups(COMMIT &aCommit, const std::unordered_set< BOARD_ITEM * > &aItemsToCheck)
void ShowMismatchDetails(wxWindow *aParent, const wxString &aSummary, const std::vector< wxString > &aReasons) const
int RepeatLayout(const TOOL_EVENT &aEvent, ZONE *aRefZone)
int AutogenerateRuleAreas(const TOOL_EVENT &aEvent)
void fixupNet(BOARD_CONNECTED_ITEM *aRef, BOARD_CONNECTED_ITEM *aTarget, TMATCH::COMPONENT_MATCHES &aComponentMatches)
Attempts to make sure copied items are assigned the right net.
bool copyRuleAreaContents(RULE_AREA *aRefArea, RULE_AREA *aTargetArea, BOARD_COMMIT *aCommit, REPEAT_LAYOUT_OPTIONS aOpts, RULE_AREA_COMPAT_DATA &aCompatData)
std::set< FOOTPRINT * > queryComponentsInComponentClass(const wxString &aComponentClassName) const
RULE_AREA * findRAByName(const wxString &aName)
std::set< FOOTPRINT * > queryComponentsInGroup(const wxString &aGroupName) const
std::set< BOARD_ITEM * > queryBoardItemsInGroup(const wxString &aGroupName) const
Handle the data for a net.
Definition netinfo.h:46
const wxString & GetNetname() const
Definition netinfo.h:100
Definition pad.h:61
const wxString & GetNumber() const
Definition pad.h:143
void SetItems(BOARD_ITEM *a, BOARD_ITEM *b=nullptr)
static TOOL_ACTION repeatLayout
static TOOL_ACTION generatePlacementRuleAreas
static TOOL_ACTION selectItemInteractively
Selection of reference points/items.
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
PCB_GENERATOR * DeepClone() const
void Move(const VECTOR2I &aMoveVector) override
Move this object.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void Move(const VECTOR2I &aMoveVector) override
Move this object.
EDA_ANGLE GetTextAngle() const override
Definition pcb_text.cpp:544
virtual VECTOR2I GetPosition() const override
Definition pcb_text.h:93
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:95
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition pcb_text.h:97
void SetTextAngle(const EDA_ANGLE &aAngle) override
Definition pcb_text.cpp:553
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:565
T * frame() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
const PCB_SELECTION & selection() const
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
std::string to_string()
Definition profile.h:153
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={})
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
static std::unique_ptr< CONNECTION_GRAPH > BuildFromFootprintSet(const std::set< FOOTPRINT * > &aFps, const std::set< FOOTPRINT * > &aOtherChannelFps={}, const std::unordered_set< int > &aGlobalNets={})
TOOL_MANAGER * GetManager() const
Return the instance of TOOL_MANAGER that takes care of the tool.
Definition tool_base.h:142
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
Generic, UI-independent tool event.
Definition tool_event.h:167
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
Define a general 2D-vector/point.
Definition vector2d.h:67
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:813
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1393
wxString GetPlacementAreaSource() const
Definition zone.h:818
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1520
PLACEMENT_SOURCE_T GetPlacementAreaSourceType() const
Definition zone.h:820
SHAPE_POLY_SET * Outline()
Definition zone.h:418
const wxString & GetZoneName() const
Definition zone.h:160
bool GetPlacementAreaEnabled() const
Definition zone.h:815
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
void UnHatchBorder()
Clear the zone's hatch.
Definition zone.cpp:1514
void RemoveAllContours(void)
Definition zone.h:650
void BuildConvexHull(std::vector< VECTOR2I > &aResult, const std::vector< VECTOR2I > &aPoly)
Calculate the convex hull of a list of points in counter-clockwise order.
#define _(s)
@ RECURSE
Definition eda_item.h:49
@ NO_RECURSE
Definition eda_item.h:50
KIID niluuid(0)
static bool matchBySymbolInstancePath(const std::set< FOOTPRINT * > &aRef, const std::set< FOOTPRINT * > &aTarget, TMATCH::COMPONENT_MATCHES &aResult)
static wxString FormatComponentList(const std::set< FOOTPRINT * > &aComponents)
static const wxString traceMultichannelTool
static void collectGroupFootprints(EDA_GROUP *aGroup, std::set< FOOTPRINT * > &aOut)
static void collectGroupBoardItems(EDA_GROUP *aGroup, std::set< BOARD_ITEM * > &aOut)
static void ShowTopologyMismatchReasons(wxWindow *aParent, const wxString &aSummary, const std::vector< wxString > &aReasons)
static std::vector< wxString > getParentSheetPaths(const wxString &aSheetName)
SHAPE_LINE_CHAIN RectifyPolygon(const SHAPE_LINE_CHAIN &aPoly)
void CollectBoxCorners(const BOX2I &aBox, std::vector< VECTOR2I > &aCorners)
Add the 4 corners of a BOX2I to a vector.
std::map< FOOTPRINT *, FOOTPRINT * > COMPONENT_MATCHES
Definition topo_match.h:187
Class to handle a set of BOARD_ITEMs.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
Utility functions for working with shapes.
void AccumulateDescription(wxString &aDesc, const wxString &aItem)
Utility to build comma separated lists in messages.
std::vector< wxString > m_mismatchReasons
std::unordered_set< BOARD_ITEM * > m_affectedItems
Filled in by copyRuleAreaContents with items that were affected by the copy operation.
TMATCH::COMPONENT_MATCHES m_matchingComponents
std::unordered_set< BOARD_ITEM * > m_groupableItems
Filled in by copyRuleAreaContents with affected items that can be grouped together.
VECTOR2I m_center
std::unordered_set< EDA_ITEM * > m_designBlockItems
wxString m_sheetName
PLACEMENT_SOURCE_T m_sourceType
wxString m_componentClass
std::set< FOOTPRINT * > m_components
wxString m_ruleName
PCB_GROUP * m_group
wxString m_groupName
wxString m_sheetPath
std::atomic< bool > * m_cancelled
Definition topo_match.h:46
std::atomic< int > * m_matchedComponents
Definition topo_match.h:47
std::atomic< int > * m_totalComponents
Definition topo_match.h:48
bool copied
std::string path
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:84
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:104
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:88
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:79
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
#define PR_CAN_ABORT