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