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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24
25#include <board_commit.h>
26#include <tools/pcb_actions.h>
27#include <tools/pcb_selection.h>
28
31
32#include "multichannel_tool.h"
33
34#include <pcbexpr_evaluator.h>
35
36#include <zone.h>
39#include <pcb_group.h>
40#include <pcb_generator.h>
41#include <footprint.h>
42#include <pad.h>
43#include <pcb_text.h>
47#include <algorithm>
48#include <pcb_track.h>
49#include <tool/tool_manager.h>
51#include <chrono>
52#include <core/profile.h>
53#include <thread_pool.h>
55#include <string_utils.h>
56#include <wx/log.h>
57#include <wx/richmsgdlg.h>
58#include <pgm_base.h>
59
60
61#define MULTICHANNEL_EXTRA_DEBUG
62
63static const wxString traceMultichannelTool = wxT( "MULTICHANNEL_TOOL" );
64
65
66static wxString FormatComponentList( const std::set<FOOTPRINT*>& aComponents )
67{
68 std::vector<wxString> refs;
69
70 for( FOOTPRINT* fp : aComponents )
71 {
72 if( !fp )
73 continue;
74
75 refs.push_back( fp->GetReferenceAsString() );
76 }
77
78 std::sort( refs.begin(), refs.end(),
79 []( const wxString& aLhs, const wxString& aRhs )
80 {
81 return aLhs.CmpNoCase( aRhs ) < 0;
82 } );
83
84 if( refs.empty() )
85 return _( "(none)" );
86
87 wxString result;
88 wxString line;
89 size_t componentsOnLine = 0;
90
91 for( const wxString& ref : refs )
92 {
93 if( componentsOnLine == 10 )
94 {
95 if( !result.IsEmpty() )
96 result += wxT( "\n" );
97
98 result += line;
99 line.clear();
100 componentsOnLine = 0;
101 }
102
103 AccumulateDescription( line, ref );
104 componentsOnLine++;
105 }
106
107 if( !line.IsEmpty() )
108 {
109 if( !result.IsEmpty() )
110 result += wxT( "\n" );
111
112 result += line;
113 }
114
115 return result;
116}
117
118
119static void ShowTopologyMismatchReasons( wxWindow* aParent, const wxString& aSummary,
120 const std::vector<wxString>& aReasons )
121{
122 if( !aParent || aReasons.empty() )
123 return;
124
125 wxString reasonText;
126
127 for( size_t idx = 0; idx < aReasons.size(); ++idx )
128 {
129 if( idx > 0 )
130 reasonText += wxT( "\n" );
131
132 reasonText += aReasons[idx];
133 }
134
135 wxRichMessageDialog dlg( aParent, aSummary, _( "Topology mismatch" ), wxICON_ERROR | wxOK );
136 dlg.ShowDetailedText( reasonText );
137 dlg.ShowModal();
138}
139
140
142{
143}
144
145
150
151void MULTICHANNEL_TOOL::ShowMismatchDetails( wxWindow* aParent, const wxString& aSummary,
152 const std::vector<wxString>& aReasons ) const
153{
154 wxWindow* parent = aParent ? aParent : frame();
155 ShowTopologyMismatchReasons( parent, aSummary, aReasons );
156}
157
158
164
165
167 std::set<FOOTPRINT*>& aComponents )
168{
169 if( !aRuleArea || !aRuleArea->m_zone )
170 return false;
171
172 // When we're copying the layout of a design block, we are provided an exact list of items
173 // rather than querying the board for items that are inside the area.
175 {
176 // Get all board connected items that are from the design bloc
177 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
178 {
179 if( item->Type() == PCB_FOOTPRINT_T )
180 aComponents.insert( static_cast<FOOTPRINT*>( item ) );
181 }
182
183 return (int) aComponents.size();
184 }
185
186
188 PCBEXPR_UCODE ucode;
189 PCBEXPR_CONTEXT ctx, preflightCtx;
190
191 auto reportError =
192 [&]( const wxString& aMessage, int aOffset )
193 {
194 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
195 };
196
197 ctx.SetErrorCallback( reportError );
198 preflightCtx.SetErrorCallback( reportError );
199 compiler.SetErrorCallback( reportError );
200 //compiler.SetDebugReporter( m_reporter );
201
202 wxLogTrace( traceMultichannelTool, wxT( "rule area '%s'" ), aRuleArea->m_zone->GetZoneName() );
203
204 wxString ruleText;
205
206 switch( aRuleArea->m_zone->GetPlacementAreaSourceType() )
207 {
209 ruleText = wxT( "A.memberOfSheetOrChildren('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
210 break;
212 ruleText = wxT( "A.hasComponentClass('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
213 break;
215 ruleText = wxT( "A.memberOfGroup('" ) + aRuleArea->m_zone->GetPlacementAreaSource() + wxT( "')" );
216 break;
218 // For design blocks, handled above outside the rules system
219 break;
220 }
221
222 auto ok = compiler.Compile( ruleText, &ucode, &preflightCtx );
223
224 if( !ok )
225 return false;
226
227 for( FOOTPRINT* fp : board()->Footprints() )
228 {
229 ctx.SetItems( fp, fp );
230 LIBEVAL::VALUE* val = ucode.Run( &ctx );
231
232 if( val->AsDouble() != 0.0 )
233 {
234 wxLogTrace( traceMultichannelTool, wxT( " - %s [sheet %s]" ),
235 fp->GetReference(),
236 fp->GetSheetname() );
237
238 aComponents.insert( fp );
239 }
240 }
241
242 return true;
243}
244
245
246bool MULTICHANNEL_TOOL::findOtherItemsInRuleArea( RULE_AREA* aRuleArea, std::set<BOARD_ITEM*>& aItems )
247{
248 if( !aRuleArea || !aRuleArea->m_zone )
249 return false;
250
251 // When we're copying the layout of a design block, we are provided an exact list of items
252 // rather than querying the board for items that are inside the area.
254 {
255 // Get all board items that aren't footprints. Connected items are usually handled by the
256 // routing path, except zones which are copied via "other items".
257 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
258 {
259 if( item->Type() == PCB_FOOTPRINT_T )
260 continue;
261
262 // TODO: Preserve nested groups when applying design block layout.
263 if( item->Type() == PCB_GROUP_T )
264 continue;
265
266 if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item ) )
267 {
268 if( !boardItem->IsConnected() || boardItem->Type() == PCB_ZONE_T )
269 aItems.insert( boardItem );
270 }
271 }
272
273 return aItems.size() > 0;
274 }
275
277 PCBEXPR_UCODE ucode;
278 PCBEXPR_CONTEXT ctx, preflightCtx;
279
280 auto reportError =
281 [&]( const wxString& aMessage, int aOffset )
282 {
283 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage );
284 };
285
286 ctx.SetErrorCallback( reportError );
287 preflightCtx.SetErrorCallback( reportError );
288 compiler.SetErrorCallback( reportError );
289
290 // Use the zone's UUID to identify it uniquely. Using the zone name could match other zones
291 // with the same name (e.g., a copper fill zone with the same name as a rule area).
292 wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ),
293 aRuleArea->m_zone->m_Uuid.AsString() );
294
295 if( !compiler.Compile( ruleText, &ucode, &preflightCtx ) )
296 return false;
297
298 auto testAndAdd =
299 [&]( BOARD_ITEM* aItem )
300 {
301 ctx.SetItems( aItem, aItem );
302 auto val = ucode.Run( &ctx );
303
304 if( val->AsDouble() != 0.0 )
305 aItems.insert( aItem );
306 };
307
308 for( ZONE* zone : board()->Zones() )
309 {
310 if( zone == aRuleArea->m_zone )
311 continue;
312
313 testAndAdd( zone );
314 }
315
316 for( BOARD_ITEM* drawing : board()->Drawings() )
317 {
318 if( !drawing->IsConnected() )
319 testAndAdd( drawing );
320 }
321
322 return true;
323}
324
325
326std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInSheet( wxString aSheetName ) const
327{
328 std::set<FOOTPRINT*> rv;
329
330 if( aSheetName.EndsWith( wxT( "/" ) ) )
331 aSheetName.RemoveLast();
332
333 wxString childPrefix = aSheetName + wxT( "/" );
334
335 for( FOOTPRINT* fp : board()->Footprints() )
336 {
337 auto sn = fp->GetSheetname();
338
339 if( sn.EndsWith( wxT( "/" ) ) )
340 sn.RemoveLast();
341
342 if( sn == aSheetName || sn.StartsWith( childPrefix ) )
343 rv.insert( fp );
344 }
345
346 return rv;
347}
348
349
350std::set<FOOTPRINT*>
351MULTICHANNEL_TOOL::queryComponentsInComponentClass( const wxString& aComponentClassName ) const
352{
353 std::set<FOOTPRINT*> rv;
354
355 for( FOOTPRINT* fp : board()->Footprints() )
356 {
357 if( fp->GetComponentClass()->ContainsClassName( aComponentClassName ) )
358 rv.insert( fp );
359 }
360
361 return rv;
362}
363
364
365std::set<FOOTPRINT*> MULTICHANNEL_TOOL::queryComponentsInGroup( const wxString& aGroupName ) const
366{
367 std::set<FOOTPRINT*> rv;
368
369 for( PCB_GROUP* group : board()->Groups() )
370 {
371 if( group->GetName() == aGroupName )
372 {
373 for( EDA_ITEM* item : group->GetItems() )
374 {
375 if( item->Type() == PCB_FOOTPRINT_T )
376 rv.insert( static_cast<FOOTPRINT*>( item ) );
377 }
378 }
379 }
380
381 return rv;
382}
383
384
385std::set<BOARD_ITEM*> MULTICHANNEL_TOOL::queryBoardItemsInGroup( const wxString& aGroupName ) const
386{
387 std::set<BOARD_ITEM*> rv;
388
389 for( PCB_GROUP* group : board()->Groups() )
390 {
391 if( group->GetName() != aGroupName )
392 continue;
393
394 for( EDA_ITEM* item : group->GetItems() )
395 {
396 if( item->IsBOARD_ITEM() )
397 rv.insert( static_cast<BOARD_ITEM*>( item ) );
398 }
399 }
400
401 return rv;
402}
403
404
405const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( std::set<FOOTPRINT*>& aFootprints, int aMargin )
406{
407 std::vector<VECTOR2I> bbCorners;
408 bbCorners.reserve( aFootprints.size() * 4 );
409
410 for( FOOTPRINT* fp : aFootprints )
411 {
412 const BOX2I bb = fp->GetBoundingBox( false ).GetInflated( aMargin );
413 KIGEOM::CollectBoxCorners( bb, bbCorners );
414 }
415
416 std::vector<VECTOR2I> hullVertices;
417 BuildConvexHull( hullVertices, bbCorners );
418
419 SHAPE_LINE_CHAIN hull( hullVertices );
420
421 // Make the newly computed convex hull use only 90 degree segments
422 return KIGEOM::RectifyPolygon( hull );
423}
424
425const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( const std::set<BOARD_ITEM*>& aItems, int aMargin )
426{
427 std::vector<VECTOR2I> bbCorners;
428 bbCorners.reserve( aItems.size() * 4 );
429
430 for( BOARD_ITEM* item : aItems )
431 {
432 BOX2I bb = item->GetBoundingBox();
433
434 if( item->Type() == PCB_FOOTPRINT_T )
435 bb = static_cast<FOOTPRINT*>( item )->GetBoundingBox( false );
436
437 KIGEOM::CollectBoxCorners( bb.GetInflated( aMargin ), bbCorners );
438 }
439
440 std::vector<VECTOR2I> hullVertices;
441 BuildConvexHull( hullVertices, bbCorners );
442
443 SHAPE_LINE_CHAIN hull( hullVertices );
444
445 // Make the newly computed convex hull use only 90 degree segments
446 return KIGEOM::RectifyPolygon( hull );
447}
448
449
451{
452 using PathAndName = std::pair<wxString, wxString>;
453 std::set<PathAndName> uniqueSheets;
454 std::set<wxString> uniqueComponentClasses;
455 std::set<wxString> uniqueGroups;
456
457 m_areas.m_areas.clear();
458
459 for( const FOOTPRINT* fp : board()->Footprints() )
460 {
461 uniqueSheets.insert( PathAndName( fp->GetSheetname(), fp->GetSheetfile() ) );
462
463 const COMPONENT_CLASS* compClass = fp->GetComponentClass();
464
465 for( const COMPONENT_CLASS* singleClass : compClass->GetConstituentClasses() )
466 uniqueComponentClasses.insert( singleClass->GetName() );
467
468 if( fp->GetParentGroup() && !fp->GetParentGroup()->GetName().IsEmpty() )
469 uniqueGroups.insert( fp->GetParentGroup()->GetName() );
470 }
471
472 for( const PathAndName& sheet : uniqueSheets )
473 {
474 RULE_AREA ent;
475
477 ent.m_generateEnabled = false;
478 ent.m_sheetPath = sheet.first;
479 ent.m_sheetName = sheet.second;
481 m_areas.m_areas.push_back( ent );
482
483 wxLogTrace( traceMultichannelTool, wxT("found sheet '%s' @ '%s' s %d\n"),
484 ent.m_sheetName,
485 ent.m_sheetPath,
486 (int) m_areas.m_areas.size() );
487 }
488
489 for( const wxString& compClass : uniqueComponentClasses )
490 {
491 RULE_AREA ent;
492
494 ent.m_generateEnabled = false;
495 ent.m_componentClass = compClass;
497 m_areas.m_areas.push_back( ent );
498
499 wxLogTrace( traceMultichannelTool, wxT( "found component class '%s' s %d\n" ),
501 static_cast<int>( m_areas.m_areas.size() ) );
502 }
503
504 for( const wxString& groupName : uniqueGroups )
505 {
506 RULE_AREA ent;
507
509 ent.m_generateEnabled = false;
510 ent.m_groupName = groupName;
512 m_areas.m_areas.push_back( ent );
513
514 wxLogTrace( traceMultichannelTool, wxT( "found group '%s' s %d\n" ),
516 static_cast<int>( m_areas.m_areas.size() ) );
517 }
518}
519
520
522{
523 m_areas.m_areas.clear();
524
525 for( ZONE* zone : board()->Zones() )
526 {
527 if( !zone->GetIsRuleArea() )
528 continue;
529
530 if( !zone->GetPlacementAreaEnabled() )
531 continue;
532
533 RULE_AREA area;
534
535 area.m_existsAlready = true;
536 area.m_zone = zone;
537 area.m_ruleName = zone->GetZoneName();
538 area.m_center = zone->Outline()->COutline( 0 ).Centre();
539
541
542 m_areas.m_areas.push_back( area );
543
544 wxLogTrace( traceMultichannelTool, wxT( "RA '%s', %d footprints\n" ), area.m_ruleName,
545 (int) area.m_components.size() );
546 }
547
548 wxLogTrace( traceMultichannelTool, wxT( "Total RAs found: %d\n" ), (int) m_areas.m_areas.size() );
549}
550
551
553{
554 for( RULE_AREA& ra : m_areas.m_areas )
555 {
556 if( ra.m_ruleName == aName )
557 return &ra;
558 }
559
560 return nullptr;
561}
562
563
565{
567}
568
569
571{
572 std::vector<ZONE*> refRAs;
573
574 auto isSelectedItemAnRA =
575 []( EDA_ITEM* aItem ) -> ZONE*
576 {
577 if( !aItem || aItem->Type() != PCB_ZONE_T )
578 return nullptr;
579
580 ZONE* zone = static_cast<ZONE*>( aItem );
581
582 if( !zone->GetIsRuleArea() )
583 return nullptr;
584
585 if( !zone->GetPlacementAreaEnabled() )
586 return nullptr;
587
588 return zone;
589 };
590
591 for( EDA_ITEM* item : selection() )
592 {
593 if( ZONE* zone = isSelectedItemAnRA( item ) )
594 {
595 refRAs.push_back( zone );
596 }
597 else if( item->Type() == PCB_GROUP_T )
598 {
599 PCB_GROUP *group = static_cast<PCB_GROUP*>( item );
600
601 for( EDA_ITEM* grpItem : group->GetItems() )
602 {
603 if( ZONE* grpZone = isSelectedItemAnRA( grpItem ) )
604 refRAs.push_back( grpZone );
605 }
606 }
607 }
608
609 if( refRAs.size() != 1 )
610 {
613 this,
614 _( "Select a reference Rule Area to copy from..." ),
615 [&]( EDA_ITEM* aItem )
616 {
617 return isSelectedItemAnRA( aItem ) != nullptr;
618 }
619 } );
620
621 return 0;
622 }
623
625
626 int status = CheckRACompatibility( refRAs.front() );
627
628 if( status < 0 )
629 return status;
630
631 if( m_areas.m_areas.size() <= 1 )
632 {
633 frame()->ShowInfoBarError( _( "No Rule Areas to repeat layout to have been found." ), true );
634 return 0;
635 }
636
638 int ret = dialog.ShowModal();
639
640 if( ret != wxID_OK )
641 return 0;
642
643 return RepeatLayout( aEvent, refRAs.front() );
644}
645
646
648{
649 m_areas.m_refRA = nullptr;
650
651 for( RULE_AREA& ra : m_areas.m_areas )
652 {
653 if( ra.m_zone == aRefZone )
654 {
655 m_areas.m_refRA = &ra;
656 break;
657 }
658 }
659
660 if( !m_areas.m_refRA )
661 return -1;
662
663 m_areas.m_compatMap.clear();
664
665 std::vector<RULE_AREA*> targets;
666
667 for( RULE_AREA& ra : m_areas.m_areas )
668 {
669 if( ra.m_zone == m_areas.m_refRA->m_zone )
670 continue;
671
672 targets.push_back( &ra );
673 m_areas.m_compatMap[&ra] = RULE_AREA_COMPAT_DATA();
674 }
675
676 if( targets.empty() )
677 return 0;
678
679 int total = static_cast<int>( targets.size() );
680 std::atomic<int> completed( 0 );
681 std::atomic<bool> cancelled( false );
682 std::atomic<int> matchedComponents( 0 );
683 std::atomic<int> totalComponents( 0 );
684 RULE_AREA* refRA = m_areas.m_refRA;
685
687 isoParams.m_cancelled = &cancelled;
688 isoParams.m_matchedComponents = &matchedComponents;
689 isoParams.m_totalComponents = &totalComponents;
690
691 // Process RA resolutions sequentially on a single background thread.
692 // Each resolveConnectionTopology call internally parallelizes its MRV scan
693 // across the thread pool, creating many short-lived tasks that fully utilize
694 // all available cores. Running the outer loop sequentially avoids thread
695 // pool starvation from nested parallelism.
697
698 auto future = tp.submit_task(
699 [this, refRA, &targets, &completed, &cancelled, &matchedComponents, &isoParams]()
700 {
701 for( RULE_AREA* target : targets )
702 {
703 if( cancelled.load( std::memory_order_relaxed ) )
704 break;
705
706 matchedComponents.store( 0, std::memory_order_relaxed );
707
708 RULE_AREA_COMPAT_DATA& compatData = m_areas.m_compatMap[target];
709 resolveConnectionTopology( refRA, target, compatData, isoParams );
710 completed.fetch_add( 1, std::memory_order_relaxed );
711 }
712 } );
713
714 if( Pgm().IsGUI() )
715 {
716 std::unique_ptr<WX_PROGRESS_REPORTER> reporter;
717 auto startTime = std::chrono::steady_clock::now();
718 double highWaterMark = 0.0;
719
720 while( future.wait_for( std::chrono::milliseconds( 100 ) ) != std::future_status::ready )
721 {
722 if( !reporter )
723 {
724 auto elapsed = std::chrono::steady_clock::now() - startTime;
725
726 if( elapsed > std::chrono::seconds( 1 ) )
727 {
728 reporter = std::make_unique<WX_PROGRESS_REPORTER>(
729 frame(), _( "Checking Rule Area compatibility..." ), 1, PR_CAN_ABORT );
730 }
731 else
732 {
733 // Flush background-thread log messages so timing traces appear promptly
734 wxLog::FlushActive();
735 }
736 }
737
738 if( reporter )
739 {
740 int done = completed.load( std::memory_order_relaxed );
741 int matched = matchedComponents.load( std::memory_order_relaxed );
742 int compTotal = totalComponents.load( std::memory_order_relaxed );
743
744 double fraction = ( compTotal > 0 )
745 ? static_cast<double>( matched ) / compTotal
746 : 0.0;
747 double progress = static_cast<double>( done + fraction ) / total;
748
749 if( progress > highWaterMark )
750 highWaterMark = progress;
751
752 reporter->SetCurrentProgress( highWaterMark );
753 reporter->Report( wxString::Format(
754 _( "Resolving topology %d of %d (%d/%d components)" ),
755 done + 1, total, matched, compTotal ) );
756
757 if( !reporter->KeepRefreshing() )
758 cancelled.store( true, std::memory_order_relaxed );
759 }
760 }
761 }
762 else
763 {
764 future.wait();
765 }
766
767 if( cancelled.load( std::memory_order_relaxed ) )
768 {
769 m_areas.m_compatMap.clear();
770 return -1;
771 }
772
773 return 0;
774}
775
776
777int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, RULE_AREA& aRefArea, RULE_AREA& aTargetArea,
778 REPEAT_LAYOUT_OPTIONS& aOptions, BOARD_COMMIT* aExternalCommit,
779 wxString* aErrorOut )
780{
781 wxCHECK_MSG( aRefArea.m_zone, -1, wxT( "Reference Rule Area has no zone." ) );
782 wxCHECK_MSG( aTargetArea.m_zone, -1, wxT( "Target Rule Area has no zone." ) );
783
784 const bool silent = aErrorOut != nullptr;
785
786 auto reportError = [&]( const wxString& aMsg )
787 {
788 if( aErrorOut )
789 *aErrorOut = aMsg;
790 else if( Pgm().IsGUI() )
791 frame()->ShowInfoBarError( aMsg, true );
792 };
793
795
796 if( !resolveConnectionTopology( &aRefArea, &aTargetArea, compat ) )
797 {
798 if( silent )
799 {
800 *aErrorOut = compat.m_errorMsg;
801 }
802 else if( Pgm().IsGUI() )
803 {
804 wxString summary = wxString::Format( _( "Rule Area topologies do not match: %s" ), compat.m_errorMsg );
806 }
807
808 return -1;
809 }
810
811 std::optional<BOARD_COMMIT> localCommit;
812
813 if( !aExternalCommit )
814 localCommit.emplace( GetManager(), true, false );
815
816 BOARD_COMMIT& commit = aExternalCommit ? *aExternalCommit : *localCommit;
817
818 // If no anchor is provided, pick the first matched pair to avoid center-alignment shifting
819 // the whole group. This keeps Apply Design Block Layout from moving the group to wherever
820 // the source design block happened to be placed.
821 if( aTargetArea.m_sourceType == PLACEMENT_SOURCE_T::GROUP_PLACEMENT && !aOptions.m_anchorFp )
822 {
823 if( !compat.m_matchingComponents.empty() )
824 aOptions.m_anchorFp = compat.m_matchingComponents.begin()->first;
825 }
826
827 if( !copyRuleAreaContents( &aRefArea, &aTargetArea, &commit, aOptions, compat ) )
828 {
829 auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
830 aRefArea.m_zone->GetZoneName(), aTargetArea.m_zone->GetZoneName() );
831
832 if( !aExternalCommit )
833 commit.Revert();
834
835 reportError( errMsg );
836
837 return -1;
838 }
839
841 {
842 if( aTargetArea.m_components.size() == 0 || !( *aTargetArea.m_components.begin() )->GetParentGroup() )
843 {
844 if( !aExternalCommit )
845 commit.Revert();
846
847 reportError( _( "Target group does not have a group." ) );
848
849 return -1;
850 }
851
852 EDA_GROUP* group = ( *aTargetArea.m_components.begin() )->GetParentGroup();
853
854 commit.Modify( group->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
855
856 for( BOARD_ITEM* item : compat.m_groupableItems )
857 {
858 commit.Modify( item );
859 group->AddItem( item );
860 }
861 }
862
863 if( !aExternalCommit )
864 commit.Push( _( "Repeat layout" ) );
865
866 return 0;
867}
868
869
870int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone )
871{
872 int totalCopied = 0;
873
874 BOARD_COMMIT commit( GetManager(), true, false );
875
876 for( auto& [targetArea, compatData] : m_areas.m_compatMap )
877 {
878 if( !compatData.m_doCopy )
879 {
880 wxLogTrace( traceMultichannelTool, wxT( "skipping copy to RA '%s' (disabled in dialog)\n" ),
881 targetArea->m_ruleName );
882 continue;
883 }
884
885 if( !compatData.m_isOk )
886 continue;
887
888 if( !copyRuleAreaContents( m_areas.m_refRA, targetArea, &commit, m_areas.m_options, compatData ) )
889 {
890 auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ),
891 m_areas.m_refRA->m_zone->GetZoneName(),
892 targetArea->m_zone->GetZoneName() );
893
894 commit.Revert();
895
896 if( Pgm().IsGUI() )
897 frame()->ShowInfoBarError( errMsg, true );
898
899 return -1;
900 }
901
902 totalCopied++;
903 wxSafeYield();
904 }
905
906 if( m_areas.m_options.m_groupItems )
907 {
908 for( const auto& [targetArea, compatData] : m_areas.m_compatMap )
909 {
910 if( compatData.m_groupableItems.size() < 2 )
911 continue;
912
913 pruneExistingGroups( commit, compatData.m_affectedItems );
914
915 PCB_GROUP* group = new PCB_GROUP( board() );
916
917 commit.Add( group );
918
919 for( BOARD_ITEM* item : compatData.m_groupableItems )
920 {
921 commit.Modify( item );
922 group->AddItem( item );
923 }
924 }
925 }
926
927 commit.Push( _( "Repeat layout" ) );
928
929 if( Pgm().IsGUI() )
930 frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ), true );
931
932 return 0;
933}
934
935
936wxString MULTICHANNEL_TOOL::stripComponentIndex( const wxString& aRef ) const
937{
938 wxString rv;
939
940 // fixme: i'm pretty sure this can be written in a simpler way, but I really suck at figuring
941 // out which wx's built in functions would do it for me. And I hate regexps :-)
942 for( auto k : aRef )
943 {
944 if( !k.IsAscii() )
945 break;
946
947 char c;
948 k.GetAsChar( &c );
949
950 if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c == '_' ) )
951 rv.Append( k );
952 else
953 break;
954 }
955
956 return rv;
957}
958
959
960int MULTICHANNEL_TOOL::findRoutingInRuleArea( RULE_AREA* aRuleArea, std::set<BOARD_CONNECTED_ITEM*>& aOutput,
961 std::shared_ptr<CONNECTIVITY_DATA> aConnectivity,
962 const SHAPE_POLY_SET& aRAPoly, const REPEAT_LAYOUT_OPTIONS& aOpts ) const
963{
964 if( !aRuleArea || !aRuleArea->m_zone )
965 return 0;
966
967 // The user also will consider tracks and vias that are inside the source area but
968 // not connected to any of the source pads to count as "routing" (e.g. stitching vias)
969
970 int count = 0;
971
972 // When we're copying the layout of a design block, we are provided an exact list of items
973 // rather than querying the board for items that are inside the area.
975 {
976 // Get all board connected items that are from the design block, except pads,
977 // which shouldn't be copied
978 for( EDA_ITEM* item : aRuleArea->m_designBlockItems )
979 {
980 // Include any connected items except pads.
981 if( item->Type() == PCB_PAD_T )
982 continue;
983
984 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
985 {
986 // Zones are handled by the "copy other items" path, we need this check here
987 // because design blocks explicitly include them as part of the block contents,
988 // but other RA types grab them by querying the board for items enclosed by the RA polygon
989 if( bci->Type() == PCB_ZONE_T )
990 continue;
991
992 if( bci->IsConnected() )
993 aOutput.insert( bci );
994 }
995 }
996
997 return (int) aOutput.size();
998 }
999
1001 PCBEXPR_UCODE ucode;
1002 PCBEXPR_CONTEXT ctx, preflightCtx;
1003
1004 auto reportError =
1005 [&]( const wxString& aMessage, int aOffset )
1006 {
1007 wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s" ), aMessage );
1008 };
1009
1010 ctx.SetErrorCallback( reportError );
1011 preflightCtx.SetErrorCallback( reportError );
1012 compiler.SetErrorCallback( reportError );
1013
1014 // Use the zone's UUID to identify it uniquely. Using the zone name could match other zones
1015 // with the same name (e.g., a copper fill zone with the same name as a rule area).
1016 wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ),
1017 aRuleArea->m_zone->m_Uuid.AsString() );
1018
1019 auto testAndAdd =
1020 [&]( BOARD_CONNECTED_ITEM* aItem )
1021 {
1022 if( aOutput.contains( aItem ) )
1023 return;
1024
1025 ctx.SetItems( aItem, aItem );
1026 LIBEVAL::VALUE* val = ucode.Run( &ctx );
1027
1028 if( val->AsDouble() != 0.0 )
1029 {
1030 aOutput.insert( aItem );
1031 count++;
1032 }
1033 };
1034
1035 if( compiler.Compile( ruleText, &ucode, &preflightCtx ) )
1036 {
1037 for( PCB_TRACK* track : board()->Tracks() )
1038 testAndAdd( track );
1039
1040 for( BOARD_ITEM* drawing : board()->Drawings() )
1041 {
1042 if( drawing->IsConnected() )
1043 testAndAdd( static_cast<BOARD_CONNECTED_ITEM*>( drawing ) );
1044 }
1045
1046 for( PCB_GENERATOR* generator : board()->Generators() )
1047 {
1048 if( generator->GetGeneratorType() != wxT( "tuning_pattern" ) )
1049 continue;
1050
1051 if( !generator->HitTest( aRAPoly.Outline( 0 ), false ) )
1052 continue;
1053
1054 for( EDA_ITEM* member : generator->GetItems() )
1055 {
1056 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( member ) )
1057 {
1058 if( !aOutput.contains( bci ) )
1059 {
1060 aOutput.insert( bci );
1061 count++;
1062 }
1063 }
1064 }
1065 }
1066 }
1067
1068 return count;
1069}
1070
1071
1073 BOARD_COMMIT* aCommit, REPEAT_LAYOUT_OPTIONS aOpts,
1074 RULE_AREA_COMPAT_DATA& aCompatData )
1075{
1076 // copy RA shapes first
1077 SHAPE_LINE_CHAIN refOutline = aRefArea->m_zone->Outline()->COutline( 0 );
1078 SHAPE_LINE_CHAIN targetOutline = aTargetArea->m_zone->Outline()->COutline( 0 );
1079
1080 FOOTPRINT* targetAnchorFp = nullptr;
1081 VECTOR2I disp = aTargetArea->m_center - aRefArea->m_center;
1082 EDA_ANGLE rot = EDA_ANGLE( 0 );
1083
1084 if( aOpts.m_anchorFp )
1085 {
1086 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1087 {
1088 if( refFP->GetReference() == aOpts.m_anchorFp->GetReference() )
1089 targetAnchorFp = targetFP;
1090 }
1091
1092 // If the dialog-selected anchor reference doesn't exist in the target area (e.g. refs don't match),
1093 // fall back to the first matched pair to avoid center-alignment shifting the whole group.
1094 if( !targetAnchorFp && !aCompatData.m_matchingComponents.empty() )
1095 targetAnchorFp = aCompatData.m_matchingComponents.begin()->second;
1096
1097 if( targetAnchorFp )
1098 {
1099 VECTOR2I oldpos = aOpts.m_anchorFp->GetPosition();
1100 rot = EDA_ANGLE( targetAnchorFp->GetOrientationDegrees() - aOpts.m_anchorFp->GetOrientationDegrees() );
1101 aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( rot ) );
1102 oldpos = aOpts.m_anchorFp->GetPosition();
1103 VECTOR2I newpos = targetAnchorFp->GetPosition();
1104 disp = newpos - oldpos;
1105 aOpts.m_anchorFp->Rotate( VECTOR2( 0, 0 ), EDA_ANGLE( -rot ) );
1106 }
1107 }
1108
1109 SHAPE_POLY_SET refPoly;
1110 refPoly.AddOutline( refOutline );
1111 refPoly.CacheTriangulation();
1112
1113 SHAPE_POLY_SET targetPoly;
1114
1115 SHAPE_LINE_CHAIN newTargetOutline( refOutline );
1116 newTargetOutline.Rotate( rot, VECTOR2( 0, 0 ) );
1117 newTargetOutline.Move( disp );
1118 targetPoly.AddOutline( newTargetOutline );
1119 targetPoly.CacheTriangulation();
1120
1121 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
1122 std::map<EDA_GROUP*, EDA_GROUP*> groupMap;
1123
1124 // For Apply Design Block Layout, grouping is handled later by RepeatLayout() using the
1125 // existing target group. Do not clone groups here or we end up with duplicates.
1126 const bool preserveGroups = aTargetArea->m_sourceType != PLACEMENT_SOURCE_T::GROUP_PLACEMENT;
1127
1128 auto fixupParentGroup =
1129 [&]( BOARD_ITEM* sourceItem, BOARD_ITEM* destItem )
1130 {
1131 if( !preserveGroups )
1132 return;
1133
1134 if( EDA_GROUP* parentGroup = sourceItem->GetParentGroup() )
1135 {
1136 if( !groupMap.contains( parentGroup ) )
1137 {
1138 PCB_GROUP* newGroup = static_cast<PCB_GROUP*>(
1139 static_cast<PCB_GROUP*>( parentGroup->AsEdaItem() )->Duplicate( false ) );
1140 newGroup->GetItems().clear();
1141 newGroup->SetParentGroup( nullptr );
1142
1143 if( newGroup->Type() == PCB_GENERATOR_T )
1144 {
1145 newGroup->Rotate( VECTOR2( 0, 0 ), rot );
1146 newGroup->Move( disp );
1147 }
1148
1149 groupMap[parentGroup] = newGroup;
1150 aCommit->Add( newGroup );
1151 }
1152
1153 groupMap[parentGroup]->AddItem( destItem );
1154 }
1155 };
1156
1157 // Only stage changes for a target Rule Area zone if it actually belongs to the board.
1158 // In some workflows (e.g. ApplyDesignBlockLayout), the target area is a temporary zone
1159 // and is not added to the BOARD.
1160 bool targetZoneOnBoard = false;
1161
1162 if( aTargetArea->m_zone )
1163 {
1164 for( ZONE* z : board()->Zones() )
1165 {
1166 if( z == aTargetArea->m_zone )
1167 {
1168 targetZoneOnBoard = true;
1169 break;
1170 }
1171 }
1172 }
1173
1174 if( targetZoneOnBoard )
1175 {
1176 aCommit->Modify( aTargetArea->m_zone );
1177 aCompatData.m_affectedItems.insert( aTargetArea->m_zone );
1178 aCompatData.m_groupableItems.insert( aTargetArea->m_zone );
1179 }
1180
1181 if( aOpts.m_copyRouting )
1182 {
1183 std::set<BOARD_CONNECTED_ITEM*> refRouting;
1184 std::set<BOARD_CONNECTED_ITEM*> targetRouting;
1185
1186 wxLogTrace( traceMultichannelTool, wxT( "copying routing: %d fps\n" ),
1187 (int) aCompatData.m_matchingComponents.size() );
1188
1189 std::set<int> refc;
1190 std::set<int> targc;
1191
1192 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1193 {
1194 for( PAD* pad : refFP->Pads() )
1195 refc.insert( pad->GetNetCode() );
1196
1197 for( PAD* pad : targetFP->Pads() )
1198 targc.insert( pad->GetNetCode() );
1199 }
1200
1201 findRoutingInRuleArea( aTargetArea, targetRouting, connectivity, targetPoly, aOpts );
1202 findRoutingInRuleArea( aRefArea, refRouting, connectivity, refPoly, aOpts );
1203
1204 for( BOARD_CONNECTED_ITEM* item : targetRouting )
1205 {
1206 // Never remove pads as part of routing copy.
1207 if( item->Type() == PCB_PAD_T )
1208 continue;
1209
1210 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1211 continue;
1212
1213 if( aOpts.m_connectedRoutingOnly && !targc.contains( item->GetNetCode() ) )
1214 continue;
1215
1216 // item already removed
1217 if( aCommit->GetStatus( item ) != 0 )
1218 continue;
1219
1220 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1221 {
1222 continue;
1223 }
1224
1225 aCompatData.m_affectedItems.insert( item );
1226 aCommit->Remove( item );
1227 }
1228
1229 for( BOARD_CONNECTED_ITEM* item : refRouting )
1230 {
1231 // Never copy pads as part of routing copy.
1232 if( item->Type() == PCB_PAD_T )
1233 continue;
1234
1235 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1236 continue;
1237
1238 if( aOpts.m_connectedRoutingOnly && !refc.contains( item->GetNetCode() ) )
1239 continue;
1240
1241 if( !aRefArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1242 continue;
1243
1244 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1245 continue;
1246
1247 BOARD_CONNECTED_ITEM* copied = static_cast<BOARD_CONNECTED_ITEM*>( item->Duplicate( false ) );
1248
1249 fixupNet( item, copied, aCompatData.m_matchingComponents );
1250 fixupParentGroup( item, copied );
1251
1252 copied->Rotate( VECTOR2( 0, 0 ), rot );
1253 copied->Move( disp );
1254 aCompatData.m_groupableItems.insert( copied );
1255 aCommit->Add( copied );
1256 }
1257 }
1258
1259 if( aOpts.m_copyOtherItems )
1260 {
1261 std::set<BOARD_ITEM*> sourceItems;
1262 std::set<BOARD_ITEM*> targetItems;
1263
1264 findOtherItemsInRuleArea( aRefArea, sourceItems );
1265 findOtherItemsInRuleArea( aTargetArea, targetItems );
1266
1267 // Apply Design Block Layout uses synthetic copper-only rule area zones that don't
1268 // reflect the layers the user actually drew on. The source items were collected by
1269 // explicit enumeration (m_designBlockItems) and the destination is a group bounding
1270 // box, so the per-item layer filter would incorrectly reject silkscreen, fab and
1271 // user drawings. Skip the layer filter only when both halves are the synthetic
1272 // design-block-to-group flow; regular GROUP_PLACEMENT rule areas have user-authored
1273 // layer sets that must still be honored.
1274 const bool skipLayerFilter = aRefArea->m_sourceType == PLACEMENT_SOURCE_T::DESIGN_BLOCK
1275 && aTargetArea->m_sourceType
1277
1278 for( BOARD_ITEM* item : targetItems )
1279 {
1280 if( item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
1281 continue;
1282
1283 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1284 continue;
1285
1286 // item already removed
1287 if( aCommit->GetStatus( item ) != 0 )
1288 continue;
1289
1290 if( item->Type() == PCB_ZONE_T )
1291 {
1292 ZONE* zone = static_cast<ZONE*>( item );
1293
1294 // Check all zone layers are included in the target rule area.
1295 if( skipLayerFilter
1296 || aTargetArea->m_zone->GetLayerSet().ContainsAll( zone->GetLayerSet() ) )
1297 {
1298 aCompatData.m_affectedItems.insert( zone );
1299 aCommit->Remove( zone );
1300 }
1301 }
1302 else
1303 {
1304 if( skipLayerFilter
1305 || aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1306 {
1307 aCompatData.m_affectedItems.insert( item );
1308 aCommit->Remove( item );
1309 }
1310 }
1311 }
1312
1313 for( BOARD_ITEM* item : sourceItems )
1314 {
1315 if( item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
1316 continue;
1317
1318 if( item->IsLocked() && !aOpts.m_includeLockedItems )
1319 continue;
1320
1321 BOARD_ITEM* copied = nullptr;
1322
1323 if( item->Type() == PCB_ZONE_T )
1324 {
1325 ZONE* zone = static_cast<ZONE*>( item );
1326
1327 if( !skipLayerFilter )
1328 {
1329 LSET allowedLayers =
1330 aRefArea->m_zone->GetLayerSet() & aTargetArea->m_zone->GetLayerSet();
1331
1332 // Check all zone layers are included in both source and target rule areas.
1333 if( !allowedLayers.ContainsAll( zone->GetLayerSet() ) )
1334 continue;
1335 }
1336
1337 ZONE* targetZone = static_cast<ZONE*>( item->Duplicate( false ) );
1338 fixupNet( zone, targetZone, aCompatData.m_matchingComponents );
1339
1340 copied = targetZone;
1341 }
1342 else
1343 {
1344 if( !skipLayerFilter )
1345 {
1346 if( !aRefArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1347 continue;
1348
1349 if( !aTargetArea->m_zone->GetLayerSet().Contains( item->GetLayer() ) )
1350 continue;
1351 }
1352
1353 copied = static_cast<BOARD_ITEM*>( item->Clone() );
1354 }
1355
1356 if( copied )
1357 {
1358 fixupParentGroup( item, copied );
1359
1360 copied->ClearFlags();
1361 copied->Rotate( VECTOR2( 0, 0 ), rot );
1362 copied->Move( disp );
1363 aCompatData.m_groupableItems.insert( copied );
1364 aCommit->Add( copied );
1365 }
1366 }
1367 }
1368
1369 if( aOpts.m_copyPlacement )
1370 {
1371 for( const auto& [refFP, targetFP] : aCompatData.m_matchingComponents )
1372 {
1373 if( !aRefArea->m_zone->GetLayerSet().Contains( refFP->GetLayer() ) )
1374 {
1375 wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (ref layer)\n" ),
1376 refFP->GetReference() );
1377 continue;
1378 }
1379 if( !aTargetArea->m_zone->GetLayerSet().Contains( refFP->GetLayer() ) )
1380 {
1381 wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (target layer)\n" ),
1382 refFP->GetReference() );
1383 continue;
1384 }
1385
1386 // For regular Rule Area repeat, ignore source footprints outside the reference area.
1387 // For Design Block apply, use the exact source item set collected from the block.
1389 && !refFP->GetEffectiveShape( refFP->GetLayer() )->Collide( &refPoly, 0 ) )
1390 {
1391 continue;
1392 }
1393
1394 if( targetFP->IsLocked() && !aOpts.m_includeLockedItems )
1395 continue;
1396
1397 aCommit->Modify( targetFP );
1398
1399 targetFP->SetLayerAndFlip( refFP->GetLayer() );
1400 targetFP->SetOrientation( refFP->GetOrientation() );
1401 targetFP->SetPosition( refFP->GetPosition() );
1402 targetFP->Rotate( VECTOR2( 0, 0 ), rot );
1403 targetFP->Move( disp );
1404
1405 for( PCB_FIELD* refField : refFP->GetFields() )
1406 {
1407 wxCHECK2( refField, continue );
1408
1409 PCB_FIELD* targetField = targetFP->GetField( refField->GetName() );
1410
1411 if( !targetField )
1412 continue;
1413
1414 targetField->SetLayerSet( refField->GetLayerSet() );
1415 targetField->SetVisible( refField->IsVisible() );
1416 targetField->SetAttributes( refField->GetAttributes() );
1417 targetField->SetPosition( refField->GetPosition() );
1418 targetField->Rotate( VECTOR2( 0, 0 ), rot );
1419 targetField->Move( disp );
1420 targetField->SetIsKnockout( refField->IsKnockout() );
1421 }
1422
1423 // Copy non-field text items (user-added text on the footprint)
1424 for( BOARD_ITEM* refItem : refFP->GraphicalItems() )
1425 {
1426 if( refItem->Type() != PCB_TEXT_T )
1427 continue;
1428
1429 PCB_TEXT* refText = static_cast<PCB_TEXT*>( refItem );
1430
1431 for( BOARD_ITEM* targetItem : targetFP->GraphicalItems() )
1432 {
1433 if( targetItem->Type() != PCB_TEXT_T )
1434 continue;
1435
1436 PCB_TEXT* targetText = static_cast<PCB_TEXT*>( targetItem );
1437
1438 // Match text items by their text content
1439 if( targetText->GetText() == refText->GetText() )
1440 {
1441 targetText->SetLayer( refText->GetLayer() );
1442 targetText->SetVisible( refText->IsVisible() );
1443 targetText->SetAttributes( refText->GetAttributes() );
1444 targetText->SetPosition( refText->GetPosition() );
1445 targetText->Rotate( VECTOR2( 0, 0 ), rot );
1446 targetText->Move( disp );
1447 targetText->SetIsKnockout( refText->IsKnockout() );
1448 break;
1449 }
1450 }
1451 }
1452
1453 // Copy 3D model settings
1454 targetFP->Models() = refFP->Models();
1455
1456 aCompatData.m_affectedItems.insert( targetFP );
1457 aCompatData.m_groupableItems.insert( targetFP );
1458 }
1459 }
1460
1461 aTargetArea->m_zone->RemoveAllContours();
1462 aTargetArea->m_zone->AddPolygon( newTargetOutline );
1463 aTargetArea->m_zone->UnHatchBorder();
1464 aTargetArea->m_zone->HatchBorder();
1465
1466 return true;
1467}
1468
1474 TMATCH::COMPONENT_MATCHES& aComponentMatches )
1475{
1476 auto connectivity = board()->GetConnectivity();
1477 const std::vector<BOARD_CONNECTED_ITEM*> refConnectedPads = connectivity->GetNetItems( aRef->GetNetCode(),
1478 { PCB_PAD_T } );
1479
1480 for( const BOARD_CONNECTED_ITEM* refConItem : refConnectedPads )
1481 {
1482 if( refConItem->Type() != PCB_PAD_T )
1483 continue;
1484
1485 const PAD* refPad = static_cast<const PAD*>( refConItem );
1486 FOOTPRINT* sourceFootprint = refPad->GetParentFootprint();
1487
1488 if( aComponentMatches.contains( sourceFootprint ) )
1489 {
1490 const FOOTPRINT* targetFootprint = aComponentMatches[sourceFootprint];
1491 std::vector<const PAD*> targetFpPads = targetFootprint->GetPads( refPad->GetNumber() );
1492
1493 if( !targetFpPads.empty() )
1494 {
1495 int targetNetCode = targetFpPads[0]->GetNet()->GetNetCode();
1496 aTarget->SetNetCode( targetNetCode );
1497
1498 break;
1499 }
1500 }
1501 }
1502}
1503
1504
1506 RULE_AREA_COMPAT_DATA& aMatches,
1507 const TMATCH::ISOMORPHISM_PARAMS& aParams )
1508{
1509 using namespace TMATCH;
1510
1511 PROF_TIMER timerBuild;
1512 std::unique_ptr<CONNECTION_GRAPH> cgRef( CONNECTION_GRAPH::BuildFromFootprintSet( aRefArea->m_components,
1513 aTargetArea->m_components ) );
1514 std::unique_ptr<CONNECTION_GRAPH> cgTarget( CONNECTION_GRAPH::BuildFromFootprintSet( aTargetArea->m_components,
1515 aRefArea->m_components ) );
1516 timerBuild.Stop();
1517
1518 wxLogTrace( traceMultichannelTool, wxT( "Graph construction: %s (%d + %d components)" ),
1519 timerBuild.to_string(),
1520 (int) aRefArea->m_components.size(),
1521 (int) aTargetArea->m_components.size() );
1522
1523 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> mismatchReasons;
1524
1525 PROF_TIMER timerIso;
1526 bool status = cgRef->FindIsomorphism( cgTarget.get(), aMatches.m_matchingComponents,
1527 mismatchReasons, aParams );
1528 timerIso.Stop();
1529
1530 wxLogTrace( traceMultichannelTool, wxT( "FindIsomorphism: %s, result=%d" ),
1531 timerIso.to_string(), status ? 1 : 0 );
1532
1533 aMatches.m_isOk = status;
1534
1535 if( status )
1536 {
1537 aMatches.m_errorMsg = _( "OK" );
1538 aMatches.m_mismatchReasons.clear();
1539 return true;
1540 }
1541
1542 aMatches.m_mismatchReasons.clear();
1543
1544 for( const auto& reason : mismatchReasons )
1545 {
1546 if( reason.m_reason.IsEmpty() )
1547 continue;
1548
1549 if( !reason.m_reference.IsEmpty() && !reason.m_candidate.IsEmpty() )
1550 {
1551 aMatches.m_mismatchReasons.push_back( wxString::Format( wxT( "%s -> %s: %s" ),
1552 reason.m_reference,
1553 reason.m_candidate,
1554 reason.m_reason ) );
1555 }
1556 else if( !reason.m_reference.IsEmpty() )
1557 {
1558 aMatches.m_mismatchReasons.push_back( wxString::Format( wxT( "%s: %s" ),
1559 reason.m_reference,
1560 reason.m_reason ) );
1561 }
1562 else
1563 aMatches.m_mismatchReasons.push_back( reason.m_reason );
1564 }
1565
1566 if( aMatches.m_mismatchReasons.empty() )
1567 aMatches.m_mismatchReasons.push_back( _( "Topology mismatch" ) );
1568
1569 // Component count mismatch
1570 if( aRefArea->m_components.size() != aTargetArea->m_components.size() )
1571 {
1572 aMatches.m_mismatchReasons.push_back(
1573 wxString::Format( _( "Reference area total components: %d" ), (int) aRefArea->m_components.size() ) );
1574 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Reference area components:\n%s" ),
1575 FormatComponentList( aRefArea->m_components ) ) );
1576 aMatches.m_mismatchReasons.push_back(
1577 wxString::Format( _( "Target area total components: %d" ), (int) aTargetArea->m_components.size() ) );
1578 aMatches.m_mismatchReasons.push_back( wxString::Format( _( "Target area components:\n%s" ),
1579 FormatComponentList( aTargetArea->m_components ) ) );
1580 }
1581
1582 aMatches.m_errorMsg = aMatches.m_mismatchReasons.front();
1583
1584 return status;
1585}
1586
1587
1589 const std::unordered_set<BOARD_ITEM*>& aItemsToRemove )
1590{
1591 // Note: groups are only collections, not "real" hierarchy. A group's members are still parented
1592 // by the board (and therefore nested groups are still in the board's list of groups).
1593 for( PCB_GROUP* group : board()->Groups() )
1594 {
1595 std::vector<EDA_ITEM*> pruneList;
1596
1597 for( EDA_ITEM* refItem : group->GetItems() )
1598 {
1599 for( BOARD_ITEM* testItem : aItemsToRemove )
1600 {
1601 if( refItem->m_Uuid == testItem->m_Uuid )
1602 pruneList.push_back( refItem );
1603 }
1604 }
1605
1606 if( !pruneList.empty() )
1607 {
1608 aCommit.Modify( group );
1609
1610 for( EDA_ITEM* item : pruneList )
1611 group->RemoveItem( item );
1612
1613 if( group->GetItems().size() < 2 )
1614 aCommit.Remove( group );
1615 }
1616 }
1617
1618 return false;
1619}
1620
1621
1623{
1624 if( Pgm().IsGUI() )
1625 {
1627
1628 if( m_areas.m_areas.size() <= 1 )
1629 {
1630 frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the "
1631 "schematic has only one or no hierarchical sheets, "
1632 "groups, or component classes." ),
1633 true );
1634 return 0;
1635 }
1636
1638 int ret = dialog.ShowModal();
1639
1640 if( ret != wxID_OK )
1641 return 0;
1642 }
1643
1644 for( ZONE* zone : board()->Zones() )
1645 {
1646 if( !zone->GetIsRuleArea() )
1647 continue;
1648
1649 if( !zone->GetPlacementAreaEnabled() )
1650 continue;
1651
1652 std::set<FOOTPRINT*> components;
1653 RULE_AREA zoneRA;
1654 zoneRA.m_zone = zone;
1655 zoneRA.m_sourceType = zone->GetPlacementAreaSourceType();
1656 findComponentsInRuleArea( &zoneRA, components );
1657
1658 if( components.empty() )
1659 continue;
1660
1661 for( RULE_AREA& ra : m_areas.m_areas )
1662 {
1663 if( components == ra.m_components )
1664 {
1665 if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::SHEETNAME )
1666 {
1667 wxLogTrace( traceMultichannelTool,
1668 wxT( "Placement rule area for sheet '%s' already exists as '%s'\n" ),
1669 ra.m_sheetPath, zone->GetZoneName() );
1670 }
1671 else if( zone->GetPlacementAreaSourceType() == PLACEMENT_SOURCE_T::COMPONENT_CLASS )
1672 {
1673 wxLogTrace( traceMultichannelTool,
1674 wxT( "Placement rule area for component class '%s' already exists as '%s'\n" ),
1675 ra.m_componentClass, zone->GetZoneName() );
1676 }
1677 else
1678 {
1679 wxLogTrace( traceMultichannelTool,
1680 wxT( "Placement rule area for group '%s' already exists as '%s'\n" ),
1681 ra.m_groupName, zone->GetZoneName() );
1682 }
1683
1684 ra.m_oldZone = zone;
1685 ra.m_existsAlready = true;
1686 }
1687 }
1688 }
1689
1690 wxLogTrace( traceMultichannelTool, wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() );
1691
1692 BOARD_COMMIT commit( GetManager(), true, false );
1693
1694 for( RULE_AREA& ra : m_areas.m_areas )
1695 {
1696 if( !ra.m_generateEnabled )
1697 continue;
1698
1699 if( ra.m_existsAlready && !m_areas.m_replaceExisting )
1700 continue;
1701
1702 if( ra.m_components.empty() )
1703 continue;
1704
1705 SHAPE_LINE_CHAIN raOutline;
1706
1707 // Groups are a way for the user to more explicitly provide a list of items to include in
1708 // the multichannel tool, as opposed to inferring them based on sheet structure or component classes.
1709 // So for group-based RAs, we build the RA outline based everything in the group, not just components.
1711 {
1712 std::set<BOARD_ITEM*> groupItems = queryBoardItemsInGroup( ra.m_groupName );
1713
1714 if( groupItems.empty() )
1715 {
1716 wxLogTrace( traceMultichannelTool,
1717 wxT( "Skipping placement rule area generation for source group '%s': group has no board items." ),
1718 ra.m_groupName );
1719 continue;
1720 }
1721
1722 raOutline = buildRAOutline( groupItems, 100000 );
1723 }
1724 else
1725 {
1726 raOutline = buildRAOutline( ra.m_components, 100000 );
1727 }
1728
1729 std::unique_ptr<ZONE> newZone( new ZONE( board() ) );
1730
1732 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_sheetPath ) );
1734 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_componentClass ) );
1735 else
1736 newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_groupName ) );
1737
1738 wxLogTrace( traceMultichannelTool, wxT( "Generated rule area '%s' (%d components)\n" ),
1739 newZone->GetZoneName(),
1740 (int) ra.m_components.size() );
1741
1742 newZone->SetIsRuleArea( true );
1743 newZone->SetLayerSet( LSET::AllCuMask() );
1744 newZone->SetPlacementAreaEnabled( true );
1745 newZone->SetDoNotAllowZoneFills( false );
1746 newZone->SetDoNotAllowVias( false );
1747 newZone->SetDoNotAllowTracks( false );
1748 newZone->SetDoNotAllowPads( false );
1749 newZone->SetDoNotAllowFootprints( false );
1750
1752 {
1753 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::SHEETNAME );
1754 newZone->SetPlacementAreaSource( ra.m_sheetPath );
1755 }
1757 {
1758 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::COMPONENT_CLASS );
1759 newZone->SetPlacementAreaSource( ra.m_componentClass );
1760 }
1761 else
1762 {
1763 newZone->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT );
1764 newZone->SetPlacementAreaSource( ra.m_groupName );
1765 }
1766
1767 newZone->AddPolygon( raOutline );
1768 newZone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
1769
1770 if( ra.m_existsAlready )
1771 {
1772 commit.Remove( ra.m_oldZone );
1773 }
1774
1775 ra.m_zone = newZone.release();
1776 commit.Add( ra.m_zone );
1777 }
1778
1779 // fixme: handle corner cases where the items belonging to a Rule Area already
1780 // belong to other groups.
1781
1782 if( m_areas.m_options.m_groupItems )
1783 {
1784 for( RULE_AREA& ra : m_areas.m_areas )
1785 {
1786 if( !ra.m_generateEnabled )
1787 continue;
1788
1789 if( ra.m_existsAlready && !m_areas.m_replaceExisting )
1790 continue;
1791
1792 // A group needs at least 2 items (zone + at least 1 component)
1793 if( ra.m_components.empty() )
1794 continue;
1795
1796 std::unordered_set<BOARD_ITEM*> toPrune;
1797
1798 std::copy( ra.m_components.begin(), ra.m_components.end(), std::inserter( toPrune, toPrune.begin() ) );
1799
1800 if( ra.m_existsAlready )
1801 toPrune.insert( ra.m_zone );
1802
1803 pruneExistingGroups( commit, toPrune );
1804
1805 PCB_GROUP* group = new PCB_GROUP( board() );
1806
1807 commit.Add( group );
1808
1809 commit.Modify( ra.m_zone );
1810 group->AddItem( ra.m_zone );
1811
1812 for( FOOTPRINT* fp : ra.m_components )
1813 {
1814 commit.Modify( fp );
1815 group->AddItem( fp );
1816 }
1817 }
1818 }
1819
1820 commit.Push( _( "Auto-generate placement rule areas" ) );
1821
1822 return true;
1823}
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:268
virtual void SetLayerSet(const LSET &aLayers)
Definition board_item.h:296
virtual bool IsKnockout() const
Definition board_item.h:355
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:356
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:316
FOOTPRINT * GetParentFootprint() const
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:571
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:638
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:72
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:90
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:106
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:78
int GetStatus(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Returns status of an item.
Definition commit.cpp:171
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:46
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:54
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:100
const KIID m_Uuid
Definition eda_item.h:535
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:118
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
virtual void SetParentGroup(EDA_GROUP *aGroup)
Definition eda_item.h:117
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:114
virtual bool IsVisible() const
Definition eda_text.h:212
void SetAttributes(const EDA_TEXT &aSrc, bool aSetPosition=true)
Set the text attributes from another instance.
Definition eda_text.cpp:432
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:385
const TEXT_ATTRIBUTES & GetAttributes() const
Definition eda_text.h:256
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
double GetOrientationDegrees() const
Definition footprint.h:424
std::vector< const PAD * > GetPads(const wxString &aPadNumber, const PAD *aIgnore=nullptr) const
const wxString & GetReference() const
Definition footprint.h:829
VECTOR2I GetPosition() const override
Definition footprint.h:405
wxString AsString() const
Definition kiid.cpp:244
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:599
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:65
const wxString & GetNumber() const
Definition pad.h:147
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.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
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:97
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:99
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition pcb_text.h:101
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:445
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:50
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:90
std::string to_string()
Definition profile.h:157
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.
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.
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:146
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
Generic, UI-independent tool event.
Definition tool_event.h:171
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:71
Handle a list of polygons defining a copper zone.
Definition zone.h:74
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:802
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1263
wxString GetPlacementAreaSource() const
Definition zone.h:807
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1390
PLACEMENT_SOURCE_T GetPlacementAreaSourceType() const
Definition zone.h:809
SHAPE_POLY_SET * Outline()
Definition zone.h:422
const wxString & GetZoneName() const
Definition zone.h:164
bool GetPlacementAreaEnabled() const
Definition zone.h:804
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:137
void UnHatchBorder()
Clear the zone's hatch.
Definition zone.cpp:1384
void RemoveAllContours(void)
Definition zone.h:641
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)
@ NO_RECURSE
Definition eda_item.h:54
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:184
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
wxString m_groupName
wxString m_sheetPath
std::atomic< bool > * m_cancelled
Definition topo_match.h:49
std::atomic< int > * m_matchedComponents
Definition topo_match.h:50
std::atomic< int > * m_totalComponents
Definition topo_match.h:51
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:31
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:88
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:108
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:105
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:89
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:83
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:84
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
#define PR_CAN_ABORT