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>
36#include <pcb_group.h>
37#include <pcb_generator.h>
38#include <footprint.h>
39#include <pad.h>
40#include <pcb_text.h>
44#include <algorithm>
45#include <pcb_track.h>
46#include <tool/tool_manager.h>
48#include <chrono>
49#include <core/profile.h>
50#include <thread_pool.h>
52#include <string_utils.h>
53#include <wx/log.h>
54#include <wx/richmsgdlg.h>
55#include <pgm_base.h>
56
57
58#define MULTICHANNEL_EXTRA_DEBUG
59
60static const wxString traceMultichannelTool = wxT( "MULTICHANNEL_TOOL" );
61
62
63static wxString FormatComponentList( const std::set<FOOTPRINT*>& aComponents )
64{
65 std::vector<wxString> refs;
66
67 for( FOOTPRINT* fp : aComponents )
68 {
69 if( !fp )
70 continue;
71
72 refs.push_back( fp->GetReferenceAsString() );
73 }
74
75 std::sort( refs.begin(), refs.end(),
76 []( const wxString& aLhs, const wxString& aRhs )
77 {
78 return aLhs.CmpNoCase( aRhs ) < 0;
79 } );
80
81 if( refs.empty() )
82 return _( "(none)" );
83
84 wxString result;
85 wxString line;
86 size_t componentsOnLine = 0;
87
88 for( const wxString& ref : refs )
89 {
90 if( componentsOnLine == 10 )
91 {
92 if( !result.IsEmpty() )
93 result += wxT( "\n" );
94
95 result += line;
96 line.clear();
97 componentsOnLine = 0;
98 }
99
100 AccumulateDescription( line, ref );
101 componentsOnLine++;
102 }
103
104 if( !line.IsEmpty() )
105 {
106 if( !result.IsEmpty() )
107 result += wxT( "\n" );
108
109 result += line;
110 }
111
112 return result;
113}
114
115
116static void ShowTopologyMismatchReasons( wxWindow* aParent, const wxString& aSummary,
117 const std::vector<wxString>& aReasons )
118{
119 if( !aParent || aReasons.empty() )
120 return;
121
122 wxString reasonText;
123
124 for( size_t idx = 0; idx < aReasons.size(); ++idx )
125 {
126 if( idx > 0 )
127 reasonText += wxT( "\n" );
128
129 reasonText += aReasons[idx];
130 }
131
132 wxRichMessageDialog dlg( aParent, aSummary, _( "Topology mismatch" ), wxICON_ERROR | wxOK );
133 dlg.ShowDetailedText( reasonText );
134 dlg.ShowModal();
135}
136
137
139{
140}
141
142
147
148void MULTICHANNEL_TOOL::ShowMismatchDetails( wxWindow* aParent, const wxString& aSummary,
149 const std::vector<wxString>& aReasons ) const
150{
151 wxWindow* parent = aParent ? aParent : frame();
152 ShowTopologyMismatchReasons( parent, aSummary, aReasons );
153}
154
155
161
162
164 std::set<FOOTPRINT*>& aComponents )
165{
166 if( !aRuleArea || !aRuleArea->m_zone )
167 return false;
168
169 // When we're copying the layout of a design block, we are provided an exact list of items
170 // rather than querying the board for items that are inside the area.
172 {
173 // Get all board connected items that are from the design bloc
174 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
175 {
176 if( item->Type() == PCB_FOOTPRINT_T )
177 aComponents.insert( static_cast<FOOTPRINT*>( item ) );
178 }
179
180 return (int) aComponents.size();
181 }
182
183
185 PCBEXPR_UCODE ucode;
186 PCBEXPR_CONTEXT ctx, preflightCtx;
187
188 auto reportError =
189 [&]( const wxString& aMessage, int aOffset )
190 {
191 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
192 };
193
194 ctx.SetErrorCallback( reportError );
195 preflightCtx.SetErrorCallback( reportError );
196 compiler.SetErrorCallback( reportError );
197 //compiler.SetDebugReporter( m_reporter );
198
199 wxLogTrace( traceMultichannelTool, wxT( "rule area '%s'" ), aRuleArea->m_zone->GetZoneName() );
200
201 wxString ruleText;
202
203 switch( aRuleArea->m_zone->GetPlacementAreaSourceType() )
204 {
206 ruleText = wxT( "A.memberOfSheetOrChildren('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
207 break;
209 ruleText = wxT( "A.hasComponentClass('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
210 break;
212 ruleText = wxT( "A.memberOfGroup('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
213 break;
215 // For design blocks, handled above outside the rules system
216 break;
217 }
218
219 auto ok = compiler.Compile( ruleText, &ucode, &preflightCtx );
220
221 if( !ok )
222 return false;
223
224 for( FOOTPRINT* fp : board()->Footprints() )
225 {
226 ctx.SetItems( fp, fp );
227 LIBEVAL::VALUE* val = ucode.Run( &ctx );
228
229 if( val->AsDouble() != 0.0 )
230 {
231 wxLogTrace( traceMultichannelTool, wxT( " - %s [sheet %s]" ),
232 fp->GetReference(),
233 fp->GetSheetname() );
234
235 aComponents.insert( fp );
236 }
237 }
238
239 return true;
240}
241
242
243bool MULTICHANNEL_TOOL::findOtherItemsInRuleArea( RULE_AREA* aRuleArea, std::set<BOARD_ITEM*>& aItems )
244{
245 if( !aRuleArea || !aRuleArea->m_zone )
246 return false;
247
248 // When we're copying the layout of a design block, we are provided an exact list of items
249 // rather than querying the board for items that are inside the area.
251 {
252 // Get all board items that aren't footprints. Connected items are usually handled by the
253 // routing path, except zones which are copied via "other items".
254 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
255 {
256 if( item->Type() == PCB_FOOTPRINT_T )
257 continue;
258
259 // Generators are copied whole by the routing pass. Nested groups are not yet
260 // preserved (TODO) so they are skipped here.
261 if( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T )
262 continue;
263
264 // Cells are copied with their owning PCB_TABLE, not as standalone items.
265 if( item->Type() == PCB_TABLECELL_T )
266 continue;
267
268 if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item ) )
269 {
270 if( !boardItem->IsConnected() || boardItem->Type() == PCB_ZONE_T )
271 aItems.insert( boardItem );
272 }
273 }
274
275 return aItems.size() > 0;
276 }
277
278 // The design-block apply target uses a scratch zone not on the board, so enclosedByArea()
279 // below finds nothing. Resolve the group's items directly from the group.
281 && ( !aRuleArea->m_components.empty() || aRuleArea->m_group ) )
282 {
283 bool zoneOnBoard = false;
284
285 for( ZONE* zone : board()->Zones() )
286 {
287 if( zone == aRuleArea->m_zone )
288 {
289 zoneOnBoard = true;
290 break;
291 }
292 }
293
294 if( !zoneOnBoard )
295 {
296 EDA_GROUP* group = aRuleArea->m_group;
297
298 if( !group && !aRuleArea->m_components.empty() )
299 group = ( *aRuleArea->m_components.begin() )->GetParentGroup();
300
301 if( group )
302 {
303 for( EDA_ITEM* member : group->GetItems() )
304 {
305 if( member->Type() == PCB_FOOTPRINT_T || member->Type() == PCB_GROUP_T
306 || member->Type() == PCB_GENERATOR_T || member->Type() == PCB_TABLECELL_T )
307 continue;
308
309 if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( member ) )
310 {
311 if( !boardItem->IsConnected() || boardItem->Type() == PCB_ZONE_T )
312 aItems.insert( boardItem );
313 }
314 }
315 }
316
317 return aItems.size() > 0;
318 }
319 }
320
322 PCBEXPR_UCODE ucode;
323 PCBEXPR_CONTEXT ctx, preflightCtx;
324
325 auto reportError =
326 [&]( const wxString& aMessage, int aOffset )
327 {
328 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
329 };
330
331 ctx.SetErrorCallback( reportError );
332 preflightCtx.SetErrorCallback( reportError );
333 compiler.SetErrorCallback( reportError );
334
335 // Use the zone's UUID to identify it uniquely. Using the zone name could match other zones
336 // with the same name (e.g., a copper fill zone with the same name as a rule area).
337 wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ),
338 aRuleArea->m_zone->m_Uuid.AsString() );
339
340 if( !compiler.Compile( ruleText, &ucode, &preflightCtx ) )
341 return false;
342
343 auto testAndAdd =
344 [&]( BOARD_ITEM* aItem )
345 {
346 ctx.SetItems( aItem, aItem );
347 auto val = ucode.Run( &ctx );
348
349 if( val->AsDouble() != 0.0 )
350 aItems.insert( aItem );
351 };
352
353 for( ZONE* zone : board()->Zones() )
354 {
355 if( zone == aRuleArea->m_zone )
356 continue;
357
358 testAndAdd( zone );
359 }
360
361 for( BOARD_ITEM* drawing : board()->Drawings() )
362 {
363 if( !drawing->IsConnected() )
364 testAndAdd( drawing );
365 }
366
367 return true;
368}
369
370
371std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInSheet( wxString aSheetName ) const
372{
373 std::set<FOOTPRINT*> rv;
374
375 if( aSheetName.EndsWith( wxT( "/" ) ) )
376 aSheetName.RemoveLast();
377
378 wxString childPrefix = aSheetName + wxT( "/" );
379
380 for( FOOTPRINT* fp : board()->Footprints() )
381 {
382 auto sn = fp->GetSheetname();
383
384 if( sn.EndsWith( wxT( "/" ) ) )
385 sn.RemoveLast();
386
387 if( sn == aSheetName || sn.StartsWith( childPrefix ) )
388 rv.insert( fp );
389 }
390
391 return rv;
392}
393
394
395std::set<FOOTPRINT*>
396MULTICHANNEL_TOOL::queryComponentsInComponentClass( const wxString& aComponentClassName ) const
397{
398 std::set<FOOTPRINT*> rv;
399
400 for( FOOTPRINT* fp : board()->Footprints() )
401 {
402 if( fp->GetComponentClass()->ContainsClassName( aComponentClassName ) )
403 rv.insert( fp );
404 }
405
406 return rv;
407}
408
409
410std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInGroup( const wxString& aGroupName ) const
411{
412 std::set<FOOTPRINT*> rv;
413
414 for( PCB_GROUP* group : board()->Groups() )
415 {
416 if( group->GetName() == aGroupName )
417 {
418 for( EDA_ITEM* item : group->GetItems() )
419 {
420 if( item->Type() == PCB_FOOTPRINT_T )
421 rv.insert( static_cast<FOOTPRINT*>( item ) );
422 }
423 }
424 }
425
426 return rv;
427}
428
429
430std::set<BOARD_ITEM*> MULTICHANNEL_TOOL::queryBoardItemsInGroup( const wxString& aGroupName ) const
431{
432 std::set<BOARD_ITEM*> rv;
433
434 for( PCB_GROUP* group : board()->Groups() )
435 {
436 if( group->GetName() != aGroupName )
437 continue;
438
439 for( EDA_ITEM* item : group->GetItems() )
440 {
441 if( item->IsBOARD_ITEM() )
442 rv.insert( static_cast<BOARD_ITEM*>( item ) );
443 }
444 }
445
446 return rv;
447}
448
449
450const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( std::set<FOOTPRINT*>& aFootprints, int aMargin )
451{
452 std::vector<VECTOR2I> bbCorners;
453 bbCorners.reserve( aFootprints.size() * 4 );
454
455 for( FOOTPRINT* fp : aFootprints )
456 {
457 const BOX2I bb = fp->GetBoundingBox( false ).GetInflated( aMargin );
458 KIGEOM::CollectBoxCorners( bb, bbCorners );
459 }
460
461 std::vector<VECTOR2I> hullVertices;
462 BuildConvexHull( hullVertices, bbCorners );
463
464 SHAPE_LINE_CHAIN hull( hullVertices );
465
466 // Make the newly computed convex hull use only 90 degree segments
467 return KIGEOM::RectifyPolygon( hull );
468}
469
470const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( const std::set<BOARD_ITEM*>& aItems, int aMargin )
471{
472 std::vector<VECTOR2I> bbCorners;
473 bbCorners.reserve( aItems.size() * 4 );
474
475 for( BOARD_ITEM* item : aItems )
476 {
477 BOX2I bb = item->GetBoundingBox();
478
479 if( item->Type() == PCB_FOOTPRINT_T )
480 bb = static_cast<FOOTPRINT*>( item )->GetBoundingBox( false );
481
482 KIGEOM::CollectBoxCorners( bb.GetInflated( aMargin ), bbCorners );
483 }
484
485 std::vector<VECTOR2I> hullVertices;
486 BuildConvexHull( hullVertices, bbCorners );
487
488 SHAPE_LINE_CHAIN hull( hullVertices );
489
490 // Make the newly computed convex hull use only 90 degree segments
491 return KIGEOM::RectifyPolygon( hull );
492}
493
494
496{
497 using PathAndName = std::pair<wxString, wxString>;
498 std::set<PathAndName> uniqueSheets;
499 std::set<wxString> uniqueComponentClasses;
500 std::set<wxString> uniqueGroups;
501
502 m_areas.m_areas.clear();
503
504 for( const FOOTPRINT* fp : board()->Footprints() )
505 {
506 uniqueSheets.insert( PathAndName( fp->GetSheetname(), fp->GetSheetfile() ) );
507
508 const COMPONENT_CLASS* compClass = fp->GetComponentClass();
509
510 for( const COMPONENT_CLASS* singleClass : compClass->GetConstituentClasses() )
511 uniqueComponentClasses.insert( singleClass->GetName() );
512
513 if( fp->GetParentGroup() && !fp->GetParentGroup()->GetName().IsEmpty() )
514 uniqueGroups.insert( fp->GetParentGroup()->GetName() );
515 }
516
517 for( const PathAndName& sheet : uniqueSheets )
518 {
519 RULE_AREA ent;
520
522 ent.m_generateEnabled = false;
523 ent.m_sheetPath = sheet.first;
524 ent.m_sheetName = sheet.second;
526 m_areas.m_areas.push_back( ent );
527
528 wxLogTrace( traceMultichannelTool, wxT("found sheet '%s' @ '%s' s %d\n"),
529 ent.m_sheetName,
530 ent.m_sheetPath,
531 (int) m_areas.m_areas.size() );
532 }
533
534 for( const wxString& compClass : uniqueComponentClasses )
535 {
536 RULE_AREA ent;
537
539 ent.m_generateEnabled = false;
540 ent.m_componentClass = compClass;
542 m_areas.m_areas.push_back( ent );
543
544 wxLogTrace( traceMultichannelTool, wxT( "found component class '%s' s %d\n" ),
546 static_cast<int>( m_areas.m_areas.size() ) );
547 }
548
549 for( const wxString& groupName : uniqueGroups )
550 {
551 RULE_AREA ent;
552
554 ent.m_generateEnabled = false;
555 ent.m_groupName = groupName;
557 m_areas.m_areas.push_back( ent );
558
559 wxLogTrace( traceMultichannelTool, wxT( "found group '%s' s %d\n" ),
561 static_cast<int>( m_areas.m_areas.size() ) );
562 }
563}
564
565
567{
568 m_areas.m_areas.clear();
569
570 for( ZONE* zone : board()->Zones() )
571 {
572 if( !zone->GetIsRuleArea() )
573 continue;
574
575 if( !zone->GetPlacementAreaEnabled() )
576 continue;
577
578 RULE_AREA area;
579
580 area.m_existsAlready = true;
581 area.m_zone = zone;
582 area.m_ruleName = zone->GetZoneName();
583 area.m_center = zone->Outline()->COutline( 0 ).Centre();
584
586
587 m_areas.m_areas.push_back( area );
588
589 wxLogTrace( traceMultichannelTool, wxT( "RA '%s', %d footprints\n" ), area.m_ruleName,
590 (int) area.m_components.size() );
591 }
592
593 wxLogTrace( traceMultichannelTool, wxT( "Total RAs found: %d\n" ), (int) m_areas.m_areas.size() );
594}
595
596
598{
599 for( RULE_AREA& ra : m_areas.m_areas )
600 {
601 if( ra.m_ruleName == aName )
602 return &ra;
603 }
604
605 return nullptr;
606}
607
608
610{
612}
613
614
616{
617 std::vector<ZONE*> refRAs;
618
619 auto isSelectedItemAnRA =
620 []( EDA_ITEM* aItem ) -> ZONE*
621 {
622 if( !aItem || aItem->Type() != PCB_ZONE_T )
623 return nullptr;
624
625 ZONE* zone = static_cast<ZONE*>( aItem );
626
627 if( !zone->GetIsRuleArea() )
628 return nullptr;
629
630 if( !zone->GetPlacementAreaEnabled() )
631 return nullptr;
632
633 return zone;
634 };
635
636 for( EDA_ITEM* item : selection() )
637 {
638 if( ZONE* zone = isSelectedItemAnRA( item ) )
639 {
640 refRAs.push_back( zone );
641 }
642 else if( item->Type() == PCB_GROUP_T )
643 {
644 PCB_GROUP *group = static_cast<PCB_GROUP*>( item );
645
646 for( EDA_ITEM* grpItem : group->GetItems() )
647 {
648 if( ZONE* grpZone = isSelectedItemAnRA( grpItem ) )
649 refRAs.push_back( grpZone );
650 }
651 }
652 }
653
654 if( refRAs.size() != 1 )
655 {
658 this,
659 _( "Select a reference Rule Area to copy from..." ),
660 [&]( EDA_ITEM* aItem )
661 {
662 return isSelectedItemAnRA( aItem ) != nullptr;
663 }
664 } );
665
666 return 0;
667 }
668
670
671 int status = CheckRACompatibility( refRAs.front() );
672
673 if( status < 0 )
674 return status;
675
676 if( m_areas.m_areas.size() <= 1 )
677 {
678 frame()->ShowInfoBarError( _( "No Rule Areas to repeat layout to have been found." ), true );
679 return 0;
680 }
681
683 int ret = dialog.ShowModal();
684
685 if( ret != wxID_OK )
686 return 0;
687
688 return RepeatLayout( aEvent, refRAs.front() );
689}
690
691
693{
694 m_areas.m_refRA = nullptr;
695
696 for( RULE_AREA& ra : m_areas.m_areas )
697 {
698 if( ra.m_zone == aRefZone )
699 {
700 m_areas.m_refRA = &ra;
701 break;
702 }
703 }
704
705 if( !m_areas.m_refRA )
706 return -1;
707
708 m_areas.m_compatMap.clear();
709
710 std::vector<RULE_AREA*> targets;
711
712 for( RULE_AREA& ra : m_areas.m_areas )
713 {
714 if( ra.m_zone == m_areas.m_refRA->m_zone )
715 continue;
716
717 targets.push_back( &ra );
718 m_areas.m_compatMap[&ra] = RULE_AREA_COMPAT_DATA();
719 }
720
721 if( targets.empty() )
722 return 0;
723
724 int total = static_cast<int>( targets.size() );
725 std::atomic<int> completed( 0 );
726 std::atomic<bool> cancelled( false );
727 std::atomic<int> matchedComponents( 0 );
728 std::atomic<int> totalComponents( 0 );
729 RULE_AREA* refRA = m_areas.m_refRA;
730
732 isoParams.m_cancelled = &cancelled;
733 isoParams.m_matchedComponents = &matchedComponents;
734 isoParams.m_totalComponents = &totalComponents;
735
736 // Process RA resolutions sequentially on a single background thread.
737 // Each resolveConnectionTopology call internally parallelizes its MRV scan
738 // across the thread pool, creating many short-lived tasks that fully utilize
739 // all available cores. Running the outer loop sequentially avoids thread
740 // pool starvation from nested parallelism.
742
743 auto future = tp.submit_task(
744 [this, refRA, &targets, &completed, &cancelled, &matchedComponents, &isoParams]()
745 {
746 for( RULE_AREA* target : targets )
747 {
748 if( cancelled.load( std::memory_order_relaxed ) )
749 break;
750
751 matchedComponents.store( 0, std::memory_order_relaxed );
752
753 RULE_AREA_COMPAT_DATA& compatData = m_areas.m_compatMap[target];
754 resolveConnectionTopology( refRA, target, compatData, isoParams );
755 completed.fetch_add( 1, std::memory_order_relaxed );
756 }
757 } );
758
759 if( Pgm().IsGUI() )
760 {
761 std::unique_ptr<WX_PROGRESS_REPORTER> reporter;
762 auto startTime = std::chrono::steady_clock::now();
763 double highWaterMark = 0.0;
764
765 while( future.wait_for( std::chrono::milliseconds( 100 ) ) != std::future_status::ready )
766 {
767 if( !reporter )
768 {
769 auto elapsed = std::chrono::steady_clock::now() - startTime;
770
771 if( elapsed > std::chrono::seconds( 1 ) )
772 {
773 reporter = std::make_unique<WX_PROGRESS_REPORTER>(
774 frame(), _( "Checking Rule Area compatibility..." ), 1, PR_CAN_ABORT );
775 }
776 else
777 {
778 // Flush background-thread log messages so timing traces appear promptly
779 wxLog::FlushActive();
780 }
781 }
782
783 if( reporter )
784 {
785 int done = completed.load( std::memory_order_relaxed );
786 int matched = matchedComponents.load( std::memory_order_relaxed );
787 int compTotal = totalComponents.load( std::memory_order_relaxed );
788
789 double fraction = ( compTotal > 0 )
790 ? static_cast<double>( matched ) / compTotal
791 : 0.0;
792 double progress = static_cast<double>( done + fraction ) / total;
793
794 if( progress > highWaterMark )
795 highWaterMark = progress;
796
797 reporter->SetCurrentProgress( highWaterMark );
798 reporter->Report( wxString::Format(
799 _( "Resolving topology %d of %d (%d/%d components)" ),
800 done + 1, total, matched, compTotal ) );
801
802 if( !reporter->KeepRefreshing() )
803 cancelled.store( true, std::memory_order_relaxed );
804 }
805 }
806 }
807 else
808 {
809 future.wait();
810 }
811
812 if( cancelled.load( std::memory_order_relaxed ) )
813 {
814 m_areas.m_compatMap.clear();
815 return -1;
816 }
817
818 return 0;
819}
820
821
822int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, RULE_AREA& aRefArea, RULE_AREA& aTargetArea,
823 REPEAT_LAYOUT_OPTIONS& aOptions, BOARD_COMMIT* aExternalCommit,
824 wxString* aErrorOut )
825{
826 wxCHECK_MSG( aRefArea.m_zone, -1, wxT( "Reference Rule Area has no zone." ) );
827 wxCHECK_MSG( aTargetArea.m_zone, -1, wxT( "Target Rule Area has no zone." ) );
828
829 const bool silent = aErrorOut != nullptr;
830
831 auto reportError = [&]( const wxString& aMsg )
832 {
833 if( aErrorOut )
834 *aErrorOut = aMsg;
835 else if( Pgm().IsGUI() )
836 frame()->ShowInfoBarError( aMsg, true );
837 };
838
840
841 if( !resolveConnectionTopology( &aRefArea, &aTargetArea, compat ) )
842 {
843 if( silent )
844 {
845 *aErrorOut = compat.m_errorMsg;
846 }
847 else if( Pgm().IsGUI() )
848 {
849 wxString summary = wxString::Format( _( "Rule Area topologies do not match: %s" ), compat.m_errorMsg );
851 }
852
853 return -1;
854 }
855
856 std::optional<BOARD_COMMIT> localCommit;
857
858 if( !aExternalCommit )
859 localCommit.emplace( GetManager(), true, false );
860
861 BOARD_COMMIT& commit = aExternalCommit ? *aExternalCommit : *localCommit;
862
863 // If no anchor is provided, pick the first matched pair to avoid center-alignment shifting
864 // the whole group. This keeps Apply Design Block Layout from moving the group to wherever
865 // the source design block happened to be placed.
866 if( aTargetArea.m_sourceType == PLACEMENT_SOURCE_T::GROUP_PLACEMENT && !aOptions.m_anchorFp )
867 {
868 if( !compat.m_matchingComponents.empty() )
869 aOptions.m_anchorFp = compat.m_matchingComponents.begin()->first;
870 }
871
872 if( !copyRuleAreaContents( &aRefArea, &aTargetArea, &commit, aOptions, compat ) )
873 {
874 auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
875 aRefArea.m_zone->GetZoneName(), aTargetArea.m_zone->GetZoneName() );
876
877 if( !aExternalCommit )
878 commit.Revert();
879
880 reportError( errMsg );
881
882 return -1;
883 }
884
886 {
887 EDA_GROUP* group = aTargetArea.m_group;
888
889 if( !group && !aTargetArea.m_components.empty() )
890 group = ( *aTargetArea.m_components.begin() )->GetParentGroup();
891
892 if( !group )
893 {
894 if( !aExternalCommit )
895 commit.Revert();
896
897 reportError( _( "Target group does not have a group." ) );
898
899 return -1;
900 }
901
902 commit.Modify( group->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
903
904 for( BOARD_ITEM* item : compat.m_groupableItems )
905 {
906 commit.Modify( item );
907 group->AddItem( item );
908 }
909 }
910
911 if( !aExternalCommit )
912 commit.Push( _( "Repeat layout" ) );
913
914 return 0;
915}
916
917
918int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone )
919{
920 int totalCopied = 0;
921
922 BOARD_COMMIT commit( GetManager(), true, false );
923
924 for( auto& [targetArea, compatData] : m_areas.m_compatMap )
925 {
926 if( !compatData.m_doCopy )
927 {
928 wxLogTrace( traceMultichannelTool, wxT( "skipping copy to RA '%s' (disabled in dialog)\n" ),
929 targetArea->m_ruleName );
930 continue;
931 }
932
933 if( !compatData.m_isOk )
934 continue;
935
936 if( !copyRuleAreaContents( m_areas.m_refRA, targetArea, &commit, m_areas.m_options, compatData ) )
937 {
938 auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
939 m_areas.m_refRA->m_zone->GetZoneName(),
940 targetArea->m_zone->GetZoneName() );
941
942 commit.Revert();
943
944 if( Pgm().IsGUI() )
945 frame()->ShowInfoBarError( errMsg, true );
946
947 return -1;
948 }
949
950 totalCopied++;
951 wxSafeYield();
952 }
953
954 if( m_areas.m_options.m_groupItems )
955 {
956 for( const auto& [targetArea, compatData] : m_areas.m_compatMap )
957 {
958 if( compatData.m_groupableItems.size() < 2 )
959 continue;
960
961 pruneExistingGroups( commit, compatData.m_affectedItems );
962
963 PCB_GROUP* group = new PCB_GROUP( board() );
964
965 commit.Add( group );
966
967 for( BOARD_ITEM* item : compatData.m_groupableItems )
968 {
969 commit.Modify( item );
970 group->AddItem( item );
971 }
972 }
973 }
974
975 commit.Push( _( "Repeat layout" ) );
976
977 if( Pgm().IsGUI() )
978 frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ), true );
979
980 return 0;
981}
982
983
984wxString MULTICHANNEL_TOOL::stripComponentIndex( const wxString& aRef ) const
985{
986 wxString rv;
987
988 // fixme: i'm pretty sure this can be written in a simpler way, but I really suck at figuring
989 // out which wx's built in functions would do it for me. And I hate regexps :-)
990 for( auto k : aRef )
991 {
992 if( !k.IsAscii() )
993 break;
994
995 char c;
996 k.GetAsChar( &c );
997
998 if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c == '_' ) )
999 rv.Append( k );
1000 else
1001 break;
1002 }
1003
1004 return rv;
1005}
1006
1007
1008int MULTICHANNEL_TOOL::findRoutingInRuleArea( RULE_AREA* aRuleArea, std::set<BOARD_CONNECTED_ITEM*>& aOutput,
1009 std::shared_ptr<CONNECTIVITY_DATA> aConnectivity,
1010 const SHAPE_POLY_SET& aRAPoly, const REPEAT_LAYOUT_OPTIONS& aOpts ) const
1011{
1012 if( !aRuleArea || !aRuleArea->m_zone )
1013 return 0;
1014
1015 // The user also will consider tracks and vias that are inside the source area but
1016 // not connected to any of the source pads to count as "routing" (e.g. stitching vias)
1017
1018 int count = 0;
1019
1020 // When we're copying the layout of a design block, we are provided an exact list of items
1021 // rather than querying the board for items that are inside the area.
1023 {
1024 // Get all board connected items that are from the design block, except pads,
1025 // which shouldn't be copied
1026 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
1027 {
1028 // Include any connected items except pads.
1029 if( item->Type() == PCB_PAD_T )
1030 continue;
1031
1032 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1033 {
1034 // Zones are handled by the "copy other items" path, we need this check here
1035 // because design blocks explicitly include them as part of the block contents,
1036 // but other RA types grab them by querying the board for items enclosed by the RA polygon
1037 if( bci->Type() == PCB_ZONE_T )
1038 continue;
1039
1040 // Tracks inside a generator (meander) are copied with the generator.
1041 if( EDA_GROUP* parent = bci->GetParentGroup() )
1042 {
1043 if( parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
1044 continue;
1045 }
1046
1047 if( bci->IsConnected() )
1048 aOutput.insert( bci );
1049 }
1050 }
1051
1052 return (int) aOutput.size();
1053 }
1054
1055 // The design-block apply target uses a scratch zone not on the board, so enclosedByArea()
1056 // below finds nothing. Match routing against the zone outline directly.
1058 {
1059 bool zoneOnBoard = false;
1060
1061 for( ZONE* zone : board()->Zones() )
1062 {
1063 if( zone == aRuleArea->m_zone )
1064 {
1065 zoneOnBoard = true;
1066 break;
1067 }
1068 }
1069
1070 if( !zoneOnBoard )
1071 {
1072 const SHAPE_POLY_SET& areaOutline = *aRuleArea->m_zone->Outline();
1073 int maxError = board()->GetDesignSettings().m_MaxError;
1074
1075 auto enclosedByZone = [&]( BOARD_CONNECTED_ITEM* aItem )
1076 {
1077 if( aOutput.contains( aItem ) )
1078 return;
1079
1080 // Tracks inside a generator (meander) are removed with the generator.
1081 if( EDA_GROUP* parent = aItem->GetParentGroup() )
1082 {
1083 if( parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
1084 return;
1085 }
1086
1087 if( !( aRuleArea->m_zone->GetLayerSet() & aItem->GetLayerSet() ).any() )
1088 return;
1089
1090 SHAPE_POLY_SET itemShape;
1091 aItem->TransformShapeToPolygon( itemShape, aItem->GetLayer(), 0, maxError, ERROR_OUTSIDE );
1092
1093 if( itemShape.IsEmpty() )
1094 return;
1095
1096 itemShape.BooleanSubtract( areaOutline );
1097
1098 if( itemShape.IsEmpty() )
1099 {
1100 aOutput.insert( aItem );
1101 count++;
1102 }
1103 };
1104
1105 for( PCB_TRACK* track : board()->Tracks() )
1106 enclosedByZone( track );
1107
1108 for( BOARD_ITEM* drawing : board()->Drawings() )
1109 {
1110 if( drawing->IsConnected() )
1111 enclosedByZone( static_cast<BOARD_CONNECTED_ITEM*>( drawing ) );
1112 }
1113
1114 return count;
1115 }
1116 }
1117
1119 PCBEXPR_UCODE ucode;
1120 PCBEXPR_CONTEXT ctx, preflightCtx;
1121
1122 auto reportError =
1123 [&]( const wxString& aMessage, int aOffset )
1124 {
1125 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s" ), aMessage );
1126 };
1127
1128 ctx.SetErrorCallback( reportError );
1129 preflightCtx.SetErrorCallback( reportError );
1130 compiler.SetErrorCallback( reportError );
1131
1132 // Use the zone's UUID to identify it uniquely. Using the zone name could match other zones
1133 // with the same name (e.g., a copper fill zone with the same name as a rule area).
1134 wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ),
1135 aRuleArea->m_zone->m_Uuid.AsString() );
1136
1137 auto testAndAdd =
1138 [&]( BOARD_CONNECTED_ITEM* aItem )
1139 {
1140 if( aOutput.contains( aItem ) )
1141 return;
1142
1143 ctx.SetItems( aItem, aItem );
1144 LIBEVAL::VALUE* val = ucode.Run( &ctx );
1145
1146 if( val->AsDouble() != 0.0 )
1147 {
1148 aOutput.insert( aItem );
1149 count++;
1150 }
1151 };
1152
1153 if( compiler.Compile( ruleText, &ucode, &preflightCtx ) )
1154 {
1155 for( PCB_TRACK* track : board()->Tracks() )
1156 testAndAdd( track );
1157
1158 for( BOARD_ITEM* drawing : board()->Drawings() )
1159 {
1160 if( drawing->IsConnected() )
1161 testAndAdd( static_cast<BOARD_CONNECTED_ITEM*>( drawing ) );
1162 }
1163
1164 for( PCB_GENERATOR* generator : board()->Generators() )
1165 {
1166 if( generator->GetGeneratorType() != wxT( "tuning_pattern" ) )
1167 continue;
1168
1169 if( !generator->HitTest( aRAPoly.Outline( 0 ), false ) )
1170 continue;
1171
1172 for( EDA_ITEM* member : generator->GetItems() )
1173 {
1174 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( member ) )
1175 {
1176 if( !aOutput.contains( bci ) )
1177 {
1178 aOutput.insert( bci );
1179 count++;
1180 }
1181 }
1182 }
1183 }
1184 }
1185
1186 return count;
1187}
1188
1189
1191 BOARD_COMMIT* aCommit, REPEAT_LAYOUT_OPTIONS aOpts,
1192 RULE_AREA_COMPAT_DATA& aCompatData )
1193{
1194 // copy RA shapes first
1195 SHAPE_LINE_CHAIN refOutline = aRefArea->m_zone->Outline()->COutline( 0 );
1196 SHAPE_LINE_CHAIN targetOutline = aTargetArea->m_zone->Outline()->COutline( 0 );
1197
1198 FOOTPRINT* targetAnchorFp = nullptr;
1199 VECTOR2I disp = aTargetArea->m_center - aRefArea->m_center;
1200 EDA_ANGLE rot = EDA_ANGLE( 0 );
1201
1202 if( aOpts.m_anchorFp )
1203 {
1204 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1205 {
1206 if( refFP->GetReference() == aOpts.m_anchorFp->GetReference() )
1207 targetAnchorFp = targetFP;
1208 }
1209
1210 // If the dialog-selected anchor reference doesn't exist in the target area (e.g. refs don't match),
1211 // fall back to the first matched pair to avoid center-alignment shifting the whole group.
1212 if( !targetAnchorFp && !aCompatData.m_matchingComponents.empty() )
1213 targetAnchorFp = aCompatData.m_matchingComponents.begin()->second;
1214
1215 if( targetAnchorFp )
1216 {
1217 VECTOR2I oldpos = aOpts.m_anchorFp->GetPosition();
1218 rot = EDA_ANGLE( targetAnchorFp->GetOrientationDegrees() - aOpts.m_anchorFp->GetOrientationDegrees() );
1219 aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( rot ) );
1220 oldpos = aOpts.m_anchorFp->GetPosition();
1221 VECTOR2I newpos = targetAnchorFp->GetPosition();
1222 disp = newpos - oldpos;
1223 aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( -rot ) );
1224 }
1225 }
1226
1227 SHAPE_POLY_SET refPoly;
1228 refPoly.AddOutline( refOutline );
1229 refPoly.CacheTriangulation();
1230
1231 SHAPE_POLY_SET targetPoly;
1232
1233 SHAPE_LINE_CHAIN newTargetOutline( refOutline );
1234 newTargetOutline.Rotate( rot, VECTOR2( 0, 0 ) );
1235 newTargetOutline.Move( disp );
1236 targetPoly.AddOutline( newTargetOutline );
1237 targetPoly.CacheTriangulation();
1238
1239 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
1240
1241 // Group placement targets let RepeatLayout() reuse the existing target group, and m_groupItems
1242 // flat-groups every copy into one rule-area group. Reconstructing source groups here in either
1243 // case strands their members and leaves empty phantom clones behind (issue 22316).
1244 const bool preserveGroups = aTargetArea->m_sourceType != PLACEMENT_SOURCE_T::GROUP_PLACEMENT
1245 && !aOpts.m_groupItems;
1246
1247 // Defer reconstruction until every copy is made. Cloning a source group the moment one member
1248 // is copied would duplicate user groups that merely overlap the source area (issue 22316); a
1249 // group is rebuilt only once all of its members have been reproduced.
1250 std::vector<std::pair<BOARD_ITEM*, BOARD_ITEM*>> groupFixupPairs;
1251 std::set<BOARD_ITEM*> reproducedSourceItems;
1252
1253 auto fixupParentGroup =
1254 [&]( BOARD_ITEM* sourceItem, BOARD_ITEM* destItem )
1255 {
1256 // The copy inherits the source's parent-group pointer but is not a member of that
1257 // group; clear the dangling reference.
1258 destItem->SetParentGroup( nullptr );
1259
1260 if( !preserveGroups )
1261 return;
1262
1263 if( sourceItem->GetParentGroup() )
1264 groupFixupPairs.emplace_back( sourceItem, destItem );
1265
1266 reproducedSourceItems.insert( sourceItem );
1267 };
1268
1269 // Only stage changes for a target Rule Area zone if it actually belongs to the board.
1270 // In some workflows (e.g. ApplyDesignBlockLayout), the target area is a temporary zone
1271 // and is not added to the BOARD.
1272 bool targetZoneOnBoard = false;
1273
1274 if( aTargetArea->m_zone )
1275 {
1276 for( ZONE* z : board()->Zones() )
1277 {
1278 if( z == aTargetArea->m_zone )
1279 {
1280 targetZoneOnBoard = true;
1281 break;
1282 }
1283 }
1284 }
1285
1286 if( targetZoneOnBoard )
1287 {
1288 aCommit->Modify( aTargetArea->m_zone );
1289 aCompatData.m_affectedItems.insert( aTargetArea->m_zone );
1290 aCompatData.m_groupableItems.insert( aTargetArea->m_zone );
1291
1292 // The source rule-area zone maps to the target zone; treat it as reproduced so a group
1293 // containing it can still be rebuilt.
1294 if( preserveGroups )
1295 {
1296 if( aRefArea->m_zone->GetParentGroup() )
1297 groupFixupPairs.emplace_back( aRefArea->m_zone, aTargetArea->m_zone );
1298
1299 reproducedSourceItems.insert( aRefArea->m_zone );
1300 }
1301 }
1302
1303 if( aOpts.m_copyRouting )
1304 {
1305 std::set<BOARD_CONNECTED_ITEM*> refRouting;
1306 std::set<BOARD_CONNECTED_ITEM*> targetRouting;
1307
1308 wxLogTrace( traceMultichannelTool, wxT( "copying routing: %d fps\n" ),
1309 (int) aCompatData.m_matchingComponents.size() );
1310
1311 std::set<int> refc;
1312 std::set<int> targc;
1313
1314 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1315 {
1316 for( PAD* pad : refFP->Pads() )
1317 refc.insert( pad->GetNetCode() );
1318
1319 for( PAD* pad : targetFP->Pads() )
1320 targc.insert( pad->GetNetCode() );
1321 }
1322
1323 findRoutingInRuleArea( aTargetArea, targetRouting, connectivity, targetPoly, aOpts );
1324 findRoutingInRuleArea( aRefArea, refRouting, connectivity, refPoly, aOpts );
1325
1326 for( BOARD_CONNECTED_ITEM* item : targetRouting )
1327 {
1328 // Never remove pads as part of routing copy.
1329 if( item->Type() == PCB_PAD_T )
1330 continue;
1331
1332 if( aRefArea->m_designBlockItems.count( item ) )
1333 continue;
1334
1335 // Design block apply (target has a group): don't remove routing owned by another
1336 // group, or applying to stacked instances wipes the previous instance's traces.
1337 if( aTargetArea->m_group && item->GetParentGroup() && item->GetParentGroup() != aTargetArea->m_group )
1338 {
1339 continue;
1340 }
1341
1342 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1343 continue;
1344
1345 if( aOpts.m_connectedRoutingOnly && !targc.contains( item->GetNetCode() ) )
1346 continue;
1347
1348 // item already removed
1349 if( aCommit->GetStatus( item ) != 0 )
1350 continue;
1351
1352 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1353 {
1354 continue;
1355 }
1356
1357 aCompatData.m_affectedItems.insert( item );
1358 aCommit->Remove( item );
1359 }
1360
1361 for( BOARD_CONNECTED_ITEM* item : refRouting )
1362 {
1363 // Never copy pads as part of routing copy.
1364 if( item->Type() == PCB_PAD_T )
1365 continue;
1366
1367 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1368 continue;
1369
1370 if( aOpts.m_connectedRoutingOnly && !refc.contains( item->GetNetCode() ) )
1371 continue;
1372
1373 if( !aRefArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1374 continue;
1375
1376 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1377 continue;
1378
1379 BOARD_CONNECTED_ITEM* copied = static_cast<BOARD_CONNECTED_ITEM*>( item->Duplicate( false ) );
1380
1381 fixupNet( item, copied, aCompatData.m_matchingComponents );
1382 fixupParentGroup( item, copied );
1383
1384 copied->Rotate( VECTOR2( 0, 0 ), rot );
1385 copied->Move( disp );
1386 aCompatData.m_groupableItems.insert( copied );
1387 aCommit->Add( copied );
1388 }
1389
1390 // Copy generators (meanders) whole so they are not flattened to loose tracks.
1392 {
1393 // Remove the target group's existing generators so a re-apply replaces them.
1394 EDA_GROUP* targetGroup = aTargetArea->m_group;
1395
1396 if( !targetGroup && !aTargetArea->m_components.empty() )
1397 targetGroup = ( *aTargetArea->m_components.begin() )->GetParentGroup();
1398
1399 if( targetGroup )
1400 {
1401 std::vector<PCB_GENERATOR*> targetGenerators;
1402
1403 for( EDA_ITEM* member : targetGroup->GetItems() )
1404 {
1405 if( member->Type() == PCB_GENERATOR_T )
1406 targetGenerators.push_back( static_cast<PCB_GENERATOR*>( member ) );
1407 }
1408
1409 for( PCB_GENERATOR* gen : targetGenerators )
1410 {
1411 gen->RunOnChildren(
1412 [&]( BOARD_ITEM* child )
1413 {
1414 aCommit->Remove( child );
1415 },
1417 aCommit->Remove( gen );
1418 }
1419 }
1420
1421 for( EDA_ITEM* item : aRefArea->m_designBlockItems )
1422 {
1423 if( item->Type() != PCB_GENERATOR_T )
1424 continue;
1425
1426 PCB_GENERATOR* clone = static_cast<PCB_GENERATOR*>( item )->DeepClone();
1427
1428 clone->ClearFlags();
1429 clone->Rotate( VECTOR2( 0, 0 ), rot );
1430 clone->Move( disp );
1431 aCommit->Add( clone );
1432
1433 clone->RunOnChildren(
1434 [&]( BOARD_ITEM* child )
1435 {
1436 child->ClearFlags();
1437
1438 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( child ) )
1439 fixupNet( bci, bci, aCompatData.m_matchingComponents );
1440
1441 aCommit->Add( child );
1442 },
1444
1445 aCompatData.m_groupableItems.insert( clone );
1446 }
1447 }
1448 }
1449
1450 if( aOpts.m_copyOtherItems )
1451 {
1452 std::set<BOARD_ITEM*> sourceItems;
1453 std::set<BOARD_ITEM*> targetItems;
1454
1455 findOtherItemsInRuleArea( aRefArea, sourceItems );
1456 findOtherItemsInRuleArea( aTargetArea, targetItems );
1457
1458 // Apply Design Block Layout uses synthetic copper-only rule area zones that don't
1459 // reflect the layers the user actually drew on. The source items were collected by
1460 // explicit enumeration (m_designBlockItems) and the destination is a group bounding
1461 // box, so the per-item layer filter would incorrectly reject silkscreen, fab and
1462 // user drawings. Skip the layer filter only when both halves are the synthetic
1463 // design-block-to-group flow; regular GROUP_PLACEMENT rule areas have user-authored
1464 // layer sets that must still be honored.
1465 const bool skipLayerFilter = aRefArea->m_sourceType == PLACEMENT_SOURCE_T::DESIGN_BLOCK
1466 && aTargetArea->m_sourceType
1468
1469 for( BOARD_ITEM* item : targetItems )
1470 {
1471 if( item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
1472 continue;
1473
1474 // Don't remove the appended source items: this geometric query can pick them up, but
1475 // they're deleted when the temporary append is reverted, leaving dangling pointers.
1476 if( aRefArea->m_designBlockItems.count( item ) )
1477 continue;
1478
1479 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1480 continue;
1481
1482 // item already removed
1483 if( aCommit->GetStatus( item ) != 0 )
1484 continue;
1485
1486 if( item->Type() == PCB_ZONE_T )
1487 {
1488 ZONE* zone = static_cast<ZONE*>( item );
1489
1490 // Check all zone layers are included in the target rule area.
1491 if( skipLayerFilter
1492 || aTargetArea->m_zone->GetLayerSet().ContainsAll( zone->GetLayerSet() ) )
1493 {
1494 aCompatData.m_affectedItems.insert( zone );
1495 aCommit->Remove( zone );
1496 }
1497 }
1498 else
1499 {
1500 if( skipLayerFilter
1501 || aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1502 {
1503 aCompatData.m_affectedItems.insert( item );
1504 aCommit->Remove( item );
1505 }
1506 }
1507 }
1508
1509 for( BOARD_ITEM* item : sourceItems )
1510 {
1511 if( item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
1512 continue;
1513
1514 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1515 continue;
1516
1517 BOARD_ITEM* copied = nullptr;
1518
1519 if( item->Type() == PCB_ZONE_T )
1520 {
1521 ZONE* zone = static_cast<ZONE*>( item );
1522
1523 if( !skipLayerFilter )
1524 {
1525 LSET allowedLayers =
1526 aRefArea->m_zone->GetLayerSet() & aTargetArea->m_zone->GetLayerSet();
1527
1528 // Check all zone layers are included in both source and target rule areas.
1529 if( !allowedLayers.ContainsAll( zone->GetLayerSet() ) )
1530 continue;
1531 }
1532
1533 ZONE* targetZone = static_cast<ZONE*>( item->Duplicate( false ) );
1534 fixupNet( zone, targetZone, aCompatData.m_matchingComponents );
1535
1536 copied = targetZone;
1537 }
1538 else
1539 {
1540 if( !skipLayerFilter )
1541 {
1542 if( !aRefArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1543 continue;
1544
1545 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1546 continue;
1547 }
1548
1549 copied = static_cast<BOARD_ITEM*>( item->Clone() );
1550 }
1551
1552 if( copied )
1553 {
1554 fixupParentGroup( item, copied );
1555
1556 copied->ClearFlags();
1557 copied->Rotate( VECTOR2( 0, 0 ), rot );
1558 copied->Move( disp );
1559 aCompatData.m_groupableItems.insert( copied );
1560 aCommit->Add( copied );
1561 }
1562 }
1563 }
1564
1565 if( aOpts.m_copyPlacement )
1566 {
1567 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1568 {
1569 if( !aRefArea->m_zone->GetLayerSet().Contains( refFP->GetLayer() ) )
1570 {
1571 wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (ref layer)\n" ),
1572 refFP->GetReference() );
1573 continue;
1574 }
1575 if( !aTargetArea->m_zone->GetLayerSet().Contains( refFP->GetLayer() ) )
1576 {
1577 wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (target layer)\n" ),
1578 refFP->GetReference() );
1579 continue;
1580 }
1581
1582 // For regular Rule Area repeat, ignore source footprints outside the reference area.
1583 // For Design Block apply, use the exact source item set collected from the block.
1585 && !refFP->GetEffectiveShape( refFP->GetLayer() )->Collide( &refPoly, 0 ) )
1586 {
1587 continue;
1588 }
1589
1590 if( targetFP->IsLocked() && !aOpts.m_includeLockedItems )
1591 continue;
1592
1593 aCommit->Modify( targetFP );
1594
1595 targetFP->SetLayerAndFlip( refFP->GetLayer() );
1596 targetFP->SetOrientation( refFP->GetOrientation() );
1597 targetFP->SetPosition( refFP->GetPosition() );
1598 targetFP->Rotate( VECTOR2( 0, 0 ), rot );
1599 targetFP->Move( disp );
1600
1601 for( PCB_FIELD* refField : refFP->GetFields() )
1602 {
1603 wxCHECK2( refField, continue );
1604
1605 PCB_FIELD* targetField = targetFP->GetField( refField->GetName() );
1606
1607 if( !targetField )
1608 continue;
1609
1610 targetField->SetLayerSet( refField->GetLayerSet() );
1611 targetField->SetVisible( refField->IsVisible() );
1612 targetField->SetAttributes( refField->GetAttributes() );
1613 targetField->SetPosition( refField->GetPosition() );
1614 targetField->Rotate( VECTOR2( 0, 0 ), rot );
1615 targetField->Move( disp );
1616 targetField->SetIsKnockout( refField->IsKnockout() );
1617 }
1618
1619 // Copy non-field text items. Texts can share content (e.g. "${REFERENCE}" on both
1620 // F.SilkS and B.SilkS), so match one-to-one and prefer the same layer. Otherwise both
1621 // collapse onto one target item and the other side's text is lost.
1622 std::set<PCB_TEXT*> consumedTargets;
1623
1624 for( BOARD_ITEM* refItem : refFP->GraphicalItems() )
1625 {
1626 if( refItem->Type() != PCB_TEXT_T )
1627 continue;
1628
1629 PCB_TEXT* refText = static_cast<PCB_TEXT*>( refItem );
1630 PCB_TEXT* targetText = nullptr;
1631
1632 for( BOARD_ITEM* targetItem : targetFP->GraphicalItems() )
1633 {
1634 if( targetItem->Type() != PCB_TEXT_T )
1635 continue;
1636
1637 PCB_TEXT* candidate = static_cast<PCB_TEXT*>( targetItem );
1638
1639 if( consumedTargets.contains( candidate ) || candidate->GetText() != refText->GetText() )
1640 {
1641 continue;
1642 }
1643
1644 targetText = candidate;
1645
1646 if( candidate->GetLayer() == refText->GetLayer() )
1647 break;
1648 }
1649
1650 if( !targetText )
1651 continue;
1652
1653 consumedTargets.insert( targetText );
1654
1655 targetText->SetLayer( refText->GetLayer() );
1656 targetText->SetVisible( refText->IsVisible() );
1657 targetText->SetAttributes( refText->GetAttributes() );
1658 targetText->SetPosition( refText->GetPosition() );
1659 targetText->Rotate( VECTOR2( 0, 0 ), rot );
1660 targetText->Move( disp );
1661 targetText->SetIsKnockout( refText->IsKnockout() );
1662 }
1663
1664 // Copy 3D model settings
1665 targetFP->Models() = refFP->Models();
1666
1667 aCompatData.m_affectedItems.insert( targetFP );
1668 aCompatData.m_groupableItems.insert( targetFP );
1669
1670 // The matched footprint maps to its target; treat it as reproduced so a group
1671 // containing it can be rebuilt.
1672 if( preserveGroups && refFP->GetParentGroup() )
1673 groupFixupPairs.emplace_back( refFP, targetFP );
1674
1675 if( preserveGroups )
1676 reproducedSourceItems.insert( refFP );
1677 }
1678 }
1679
1680 // Rebuild a source group only when all of its members were reproduced. A group that merely
1681 // overlaps the source area keeps uncopied members, so it is left untouched rather than
1682 // partially duplicated (issue 22316).
1683 if( preserveGroups && !groupFixupPairs.empty() )
1684 {
1685 std::map<EDA_GROUP*, EDA_GROUP*> groupMap;
1686 std::map<EDA_GROUP*, bool> fullyReproducedCache;
1687
1688 auto groupFullyReproduced =
1689 [&]( EDA_GROUP* aGroup )
1690 {
1691 if( auto it = fullyReproducedCache.find( aGroup ); it != fullyReproducedCache.end() )
1692 return it->second;
1693
1694 bool reproduced = true;
1695
1696 for( EDA_ITEM* member : aGroup->GetItems() )
1697 {
1698 // Nested groups are not reproduced, so a parent containing one can never
1699 // be fully reproduced.
1700 if( !member->IsBOARD_ITEM()
1701 || !reproducedSourceItems.contains( static_cast<BOARD_ITEM*>( member ) ) )
1702 {
1703 reproduced = false;
1704 break;
1705 }
1706 }
1707
1708 fullyReproducedCache[aGroup] = reproduced;
1709 return reproduced;
1710 };
1711
1712 for( const auto& [sourceItem, destItem] : groupFixupPairs )
1713 {
1714 EDA_GROUP* parentGroup = sourceItem->GetParentGroup();
1715
1716 if( !parentGroup || !groupFullyReproduced( parentGroup ) )
1717 continue;
1718
1719 if( !groupMap.contains( parentGroup ) )
1720 {
1721 PCB_GROUP* newGroup = static_cast<PCB_GROUP*>(
1722 static_cast<PCB_GROUP*>( parentGroup->AsEdaItem() )->Duplicate( false ) );
1723 newGroup->GetItems().clear();
1724 newGroup->SetParentGroup( nullptr );
1725
1726 if( newGroup->Type() == PCB_GENERATOR_T )
1727 {
1728 newGroup->Rotate( VECTOR2( 0, 0 ), rot );
1729 newGroup->Move( disp );
1730 }
1731
1732 groupMap[parentGroup] = newGroup;
1733 aCommit->Add( newGroup );
1734 }
1735
1736 // AddItem reparents the footprint out of any group it already belongs to; stage that
1737 // group so the membership change is captured for undo.
1738 if( EDA_GROUP* oldGroup = destItem->GetParentGroup() )
1739 {
1740 if( oldGroup != groupMap[parentGroup] )
1741 aCommit->Modify( oldGroup->AsEdaItem() );
1742 }
1743
1744 groupMap[parentGroup]->AddItem( destItem );
1745 }
1746 }
1747
1748 aTargetArea->m_zone->RemoveAllContours();
1749 aTargetArea->m_zone->AddPolygon( newTargetOutline );
1750 aTargetArea->m_zone->UnHatchBorder();
1751 aTargetArea->m_zone->HatchBorder();
1752
1753 return true;
1754}
1755
1761 TMATCH::COMPONENT_MATCHES& aComponentMatches )
1762{
1763 // Copy as no-net.
1764 if( aComponentMatches.empty() )
1765 {
1766 aTarget->SetNetCode( 0 );
1767 return;
1768 }
1769
1770 auto connectivity = board()->GetConnectivity();
1771 const std::vector<BOARD_CONNECTED_ITEM*> refConnectedPads = connectivity->GetNetItems( aRef->GetNetCode(),
1772 { PCB_PAD_T } );
1773
1774 for( const BOARD_CONNECTED_ITEM* refConItem : refConnectedPads )
1775 {
1776 if( refConItem->Type() != PCB_PAD_T )
1777 continue;
1778
1779 const PAD* refPad = static_cast<const PAD*>( refConItem );
1780 FOOTPRINT* sourceFootprint = refPad->GetParentFootprint();
1781
1782 if( aComponentMatches.contains( sourceFootprint ) )
1783 {
1784 const FOOTPRINT* targetFootprint = aComponentMatches[sourceFootprint];
1785 std::vector<const PAD*> targetFpPads = targetFootprint->GetPads( refPad->GetNumber() );
1786
1787 if( !targetFpPads.empty() )
1788 {
1789 int targetNetCode = targetFpPads[0]->GetNet()->GetNetCode();
1790 aTarget->SetNetCode( targetNetCode );
1791
1792 break;
1793 }
1794 }
1795 }
1796}
1797
1798
1800 RULE_AREA_COMPAT_DATA& aMatches,
1801 const TMATCH::ISOMORPHISM_PARAMS& aParams )
1802{
1803 using namespace TMATCH;
1804
1805 // Footprint-free design block has nothing to match to
1806 if( aRefArea->m_sourceType == PLACEMENT_SOURCE_T::DESIGN_BLOCK && aRefArea->m_components.empty()
1807 && aTargetArea->m_components.empty() )
1808 {
1809 aMatches.m_matchingComponents.clear();
1810 aMatches.m_isOk = true;
1811 aMatches.m_errorMsg = _( "OK" );
1812 aMatches.m_mismatchReasons.clear();
1813 return true;
1814 }
1815
1816 // Placement areas resolve their components from their source, so two areas sharing a sheet,
1817 // component class or group resolve to identical footprints. Repeating into such a target would
1818 // move and delete the reference's own items, corrupting the placement rather than copying it.
1819 if( !aRefArea->m_components.empty() )
1820 {
1821 std::set<FOOTPRINT*> shared;
1822 std::set_intersection( aRefArea->m_components.begin(), aRefArea->m_components.end(),
1823 aTargetArea->m_components.begin(), aTargetArea->m_components.end(),
1824 std::inserter( shared, shared.begin() ) );
1825
1826 if( !shared.empty() )
1827 {
1828 aMatches.m_matchingComponents.clear();
1829 aMatches.m_isOk = false;
1830 aMatches.m_errorMsg = _( "Target Rule Area shares components with the reference area" );
1831 aMatches.m_mismatchReasons.clear();
1832 aMatches.m_mismatchReasons.push_back(
1833 _( "This target Rule Area selects the same components as the reference area. "
1834 "Repeat layout cannot copy a Rule Area onto itself. Give each placement Rule "
1835 "Area a distinct sheet, component class or group." ) );
1836 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Shared components:\n%s" ),
1837 FormatComponentList( shared ) ) );
1838 return false;
1839 }
1840 }
1841
1842 PROF_TIMER timerBuild;
1843 std::unique_ptr<CONNECTION_GRAPH> cgRef( CONNECTION_GRAPH::BuildFromFootprintSet( aRefArea->m_components,
1844 aTargetArea->m_components ) );
1845 std::unique_ptr<CONNECTION_GRAPH> cgTarget( CONNECTION_GRAPH::BuildFromFootprintSet( aTargetArea->m_components,
1846 aRefArea->m_components ) );
1847 timerBuild.Stop();
1848
1849 wxLogTrace( traceMultichannelTool, wxT( "Graph construction: %s (%d + %d components)" ),
1850 timerBuild.to_string(),
1851 (int) aRefArea->m_components.size(),
1852 (int) aTargetArea->m_components.size() );
1853
1854 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> mismatchReasons;
1855
1856 PROF_TIMER timerIso;
1857 bool status = cgRef->FindIsomorphism( cgTarget.get(), aMatches.m_matchingComponents,
1858 mismatchReasons, aParams );
1859 timerIso.Stop();
1860
1861 wxLogTrace( traceMultichannelTool, wxT( "FindIsomorphism: %s, result=%d" ),
1862 timerIso.to_string(), status ? 1 : 0 );
1863
1864 aMatches.m_isOk = status;
1865
1866 if( status )
1867 {
1868 aMatches.m_errorMsg = _( "OK" );
1869 aMatches.m_mismatchReasons.clear();
1870 return true;
1871 }
1872
1873 aMatches.m_mismatchReasons.clear();
1874
1875 for( const auto& reason : mismatchReasons )
1876 {
1877 if( reason.m_reason.IsEmpty() )
1878 continue;
1879
1880 if( !reason.m_reference.IsEmpty() && !reason.m_candidate.IsEmpty() )
1881 {
1882 aMatches.m_mismatchReasons.push_back( wxString::Format( wxT( "%s -> %s: %s" ),
1883 reason.m_reference,
1884 reason.m_candidate,
1885 reason.m_reason ) );
1886 }
1887 else if( !reason.m_reference.IsEmpty() )
1888 {
1889 aMatches.m_mismatchReasons.push_back( wxString::Format( wxT( "%s: %s" ),
1890 reason.m_reference,
1891 reason.m_reason ) );
1892 }
1893 else
1894 aMatches.m_mismatchReasons.push_back( reason.m_reason );
1895 }
1896
1897 if( aMatches.m_mismatchReasons.empty() )
1898 aMatches.m_mismatchReasons.push_back( _( "Topology mismatch" ) );
1899
1900 // Component count mismatch
1901 if( aRefArea->m_components.size() != aTargetArea->m_components.size() )
1902 {
1903 aMatches.m_mismatchReasons.push_back(
1904 wxString::Format( _( "Reference area total components: %d" ), (int) aRefArea->m_components.size() ) );
1905 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Reference area components:\n%s" ),
1906 FormatComponentList( aRefArea->m_components ) ) );
1907 aMatches.m_mismatchReasons.push_back(
1908 wxString::Format( _( "Target area total components: %d" ), (int) aTargetArea->m_components.size() ) );
1909 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Target area components:\n%s" ),
1910 FormatComponentList( aTargetArea->m_components ) ) );
1911 }
1912
1913 aMatches.m_errorMsg = aMatches.m_mismatchReasons.front();
1914
1915 return status;
1916}
1917
1918
1920 const std::unordered_set<BOARD_ITEM*>& aItemsToRemove )
1921{
1922 // Note: groups are only collections, not "real" hierarchy. A group's members are still parented
1923 // by the board (and therefore nested groups are still in the board's list of groups).
1924 for( PCB_GROUP* group : board()->Groups() )
1925 {
1926 std::vector<EDA_ITEM*> pruneList;
1927
1928 for( EDA_ITEM* refItem : group->GetItems() )
1929 {
1930 for( BOARD_ITEM* testItem : aItemsToRemove )
1931 {
1932 if( refItem->m_Uuid == testItem->m_Uuid )
1933 pruneList.push_back( refItem );
1934 }
1935 }
1936
1937 if( !pruneList.empty() )
1938 {
1939 aCommit.Modify( group );
1940
1941 for( EDA_ITEM* item : pruneList )
1942 group->RemoveItem( item );
1943
1944 if( group->GetItems().size() < 2 )
1945 aCommit.Remove( group );
1946 }
1947 }
1948
1949 return false;
1950}
1951
1952
1954{
1955 if( Pgm().IsGUI() )
1956 {
1958
1959 if( m_areas.m_areas.size() <= 1 )
1960 {
1961 frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
1962 "schematic has only one or no hierarchical sheets, "
1963 "groups, or component classes." ),
1964 true );
1965 return 0;
1966 }
1967
1969 int ret = dialog.ShowModal();
1970
1971 if( ret != wxID_OK )
1972 return 0;
1973 }
1974
1975 for( ZONE* zone : board()->Zones() )
1976 {
1977 if( !zone->GetIsRuleArea() )
1978 continue;
1979
1980 if( !zone->GetPlacementAreaEnabled() )
1981 continue;
1982
1983 std::set<FOOTPRINT*> components;
1984 RULE_AREA zoneRA;
1985 zoneRA.m_zone = zone;
1986 zoneRA.m_sourceType = zone->GetPlacementAreaSourceType();
1987 findComponentsInRuleArea( &zoneRA, components );
1988
1989 if( components.empty() )
1990 continue;
1991
1992 for( RULE_AREA& ra : m_areas.m_areas )
1993 {
1994 if( components == ra.m_components )
1995 {
1996 if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::SHEETNAME )
1997 {
1998 wxLogTrace( traceMultichannelTool,
1999 wxT( "Placement rule area for sheet '%s' already exists as '%s'\n" ),
2000 ra.m_sheetPath, zone->GetZoneName() );
2001 }
2002 else if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::COMPONENT_CLASS )
2003 {
2004 wxLogTrace( traceMultichannelTool,
2005 wxT( "Placement rule area for component class '%s' already exists as '%s'\n" ),
2006 ra.m_componentClass, zone->GetZoneName() );
2007 }
2008 else
2009 {
2010 wxLogTrace( traceMultichannelTool,
2011 wxT( "Placement rule area for group '%s' already exists as '%s'\n" ),
2012 ra.m_groupName, zone->GetZoneName() );
2013 }
2014
2015 ra.m_oldZone = zone;
2016 ra.m_existsAlready = true;
2017 }
2018 }
2019 }
2020
2021 wxLogTrace( traceMultichannelTool, wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() );
2022
2023 BOARD_COMMIT commit( GetManager(), true, false );
2024
2025 for( RULE_AREA& ra : m_areas.m_areas )
2026 {
2027 if( !ra.m_generateEnabled )
2028 continue;
2029
2030 if( ra.m_existsAlready && !m_areas.m_replaceExisting )
2031 continue;
2032
2033 if( ra.m_components.empty() )
2034 continue;
2035
2036 SHAPE_LINE_CHAIN raOutline;
2037
2038 // Groups are a way for the user to more explicitly provide a list of items to include in
2039 // the multichannel tool, as opposed to inferring them based on sheet structure or component classes.
2040 // So for group-based RAs, we build the RA outline based everything in the group, not just components.
2042 {
2043 std::set<BOARD_ITEM*> groupItems = queryBoardItemsInGroup( ra.m_groupName );
2044
2045 if( groupItems.empty() )
2046 {
2047 wxLogTrace( traceMultichannelTool,
2048 wxT( "Skipping placement rule area generation for source group '%s': group has no board items." ),
2049 ra.m_groupName );
2050 continue;
2051 }
2052
2053 raOutline = buildRAOutline( groupItems, 100000 );
2054 }
2055 else
2056 {
2057 raOutline = buildRAOutline( ra.m_components, 100000 );
2058 }
2059
2060 std::unique_ptr<ZONE> newZone( new ZONE( board() ) );
2061
2063 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_sheetPath ) );
2065 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_componentClass ) );
2066 else
2067 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_groupName ) );
2068
2069 wxLogTrace( traceMultichannelTool, wxT( "Generated rule area '%s' (%d components)\n" ),
2070 newZone->GetZoneName(),
2071 (int) ra.m_components.size() );
2072
2073 newZone->SetIsRuleArea( true );
2074 newZone->SetLayerSet( LSET::AllCuMask() );
2075 newZone->SetPlacementAreaEnabled( true );
2076 newZone->SetDoNotAllowZoneFills( false );
2077 newZone->SetDoNotAllowVias( false );
2078 newZone->SetDoNotAllowTracks( false );
2079 newZone->SetDoNotAllowPads( false );
2080 newZone->SetDoNotAllowFootprints( false );
2081
2083 {
2084 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::SHEETNAME );
2085 newZone->SetPlacementAreaSource( ra.m_sheetPath );
2086 }
2088 {
2089 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::COMPONENT_CLASS );
2090 newZone->SetPlacementAreaSource( ra.m_componentClass );
2091 }
2092 else
2093 {
2094 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT );
2095 newZone->SetPlacementAreaSource( ra.m_groupName );
2096 }
2097
2098 newZone->AddPolygon( raOutline );
2099 newZone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
2100
2101 if( ra.m_existsAlready )
2102 {
2103 commit.Remove( ra.m_oldZone );
2104 }
2105
2106 ra.m_zone = newZone.release();
2107 commit.Add( ra.m_zone );
2108 }
2109
2110 // fixme: handle corner cases where the items belonging to a Rule Area already
2111 // belong to other groups.
2112
2113 if( m_areas.m_options.m_groupItems )
2114 {
2115 for( RULE_AREA& ra : m_areas.m_areas )
2116 {
2117 if( !ra.m_generateEnabled )
2118 continue;
2119
2120 if( ra.m_existsAlready && !m_areas.m_replaceExisting )
2121 continue;
2122
2123 // A group needs at least 2 items (zone + at least 1 component)
2124 if( ra.m_components.empty() )
2125 continue;
2126
2127 std::unordered_set<BOARD_ITEM*> toPrune;
2128
2129 std::copy( ra.m_components.begin(), ra.m_components.end(), std::inserter( toPrune, toPrune.begin() ) );
2130
2131 if( ra.m_existsAlready )
2132 toPrune.insert( ra.m_zone );
2133
2134 pruneExistingGroups( commit, toPrune );
2135
2136 PCB_GROUP* group = new PCB_GROUP( board() );
2137
2138 commit.Add( group );
2139
2140 commit.Modify( ra.m_zone );
2141 group->AddItem( ra.m_zone );
2142
2143 for( FOOTPRINT* fp : ra.m_components )
2144 {
2145 commit.Modify( fp );
2146 group->AddItem( fp );
2147 }
2148 }
2149 }
2150
2151 commit.Push( _( "Auto-generate placement rule areas" ) );
2152
2153 return true;
2154}
@ ERROR_OUTSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:265
virtual void SetLayerSet(const LSET &aLayers)
Definition board_item.h:293
virtual bool IsKnockout() const
Definition board_item.h:352
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:353
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:313
FOOTPRINT * GetParentFootprint() const
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:634
constexpr BOX2< Vec > GetInflated(coord_type aDx, coord_type aDy) const
Get a new rectangle that is this one, inflated by aDx and aDy.
Definition box2.h:634
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:68
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
int GetStatus(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Returns status of an item.
Definition commit.cpp:191
A lightweight representation of a component class.
const std::vector< COMPONENT_CLASS * > & GetConstituentClasses() const
Fetches a vector of the constituent classes for this (effective) class.
const std::vector< BOARD_CONNECTED_ITEM * > GetNetItems(int aNetCode, const std::vector< KICAD_T > &aTypes) const
Function GetNetItems() Returns the list of items that belong to a certain net.
int ShowModal() override
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:42
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:50
virtual EDA_ITEM * AsEdaItem()=0
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
const KIID m_Uuid
Definition eda_item.h:531
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:114
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:154
virtual void SetParentGroup(EDA_GROUP *aGroup)
Definition eda_item.h:113
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:110
virtual bool IsVisible() const
Definition eda_text.h:208
void SetAttributes(const EDA_TEXT &aSrc, bool aSetPosition=true)
Set the text attributes from another instance.
Definition eda_text.cpp:428
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
const TEXT_ATTRIBUTES & GetAttributes() const
Definition eda_text.h:252
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
double GetOrientationDegrees() const
Definition footprint.h:436
std::vector< const PAD * > GetPads(const wxString &aPadNumber, const PAD *aIgnore=nullptr) const
const wxString & GetReference() const
Definition footprint.h:841
VECTOR2I GetPosition() const override
Definition footprint.h:403
wxString AsString() const
Definition kiid.cpp:242
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
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
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.
void Move(const VECTOR2I &aMoveVector) override
Move this object.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:49
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.
virtual VECTOR2I GetPosition() const override
Definition pcb_text.h:93
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:95
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition pcb_text.h:97
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:564
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.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
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={})
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:811
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1350
wxString GetPlacementAreaSource() const
Definition zone.h:816
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1477
PLACEMENT_SOURCE_T GetPlacementAreaSourceType() const
Definition zone.h:818
SHAPE_POLY_SET * Outline()
Definition zone.h:418
const wxString & GetZoneName() const
Definition zone.h:160
bool GetPlacementAreaEnabled() const
Definition zone.h:813
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:1471
void RemoveAllContours(void)
Definition zone.h:650
void BuildConvexHull(std::vector< VECTOR2I > &aResult, const std::vector< VECTOR2I > &aPoly)
Calculate the convex hull of a list of points in counter-clockwise order.
#define _(s)
@ RECURSE
Definition eda_item.h:49
@ NO_RECURSE
Definition eda_item.h:50
static wxString FormatComponentList(const std::set< FOOTPRINT * > &aComponents)
static const wxString traceMultichannelTool
static void ShowTopologyMismatchReasons(wxWindow *aParent, const wxString &aSummary, const std::vector< wxString > &aReasons)
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:180
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:45
std::atomic< int > * m_matchedComponents
Definition topo_match.h:46
std::atomic< int > * m_totalComponents
Definition topo_match.h:47
bool copied
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:84
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:104
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:88
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:79
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
#define PR_CAN_ABORT