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