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