KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_matched_length.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.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <common.h>
21#include <numeric>
22#include <board.h>
24#include <footprint.h>
25#include <pad.h>
26#include <zone.h>
27#include <drc/drc_item.h>
32#include <lset.h>
33#include <net_chain_bridging.h>
35#include <string_utils.h>
36
39
40
41/*
42 Single-ended matched length + skew + via count test.
43 Errors generated:
44 - DRCE_LENGTH_OUT_OF_RANGE
45 - DRCE_SKEW_OUT_OF_RANGE
46 - DRCE_TOO_MANY_VIAS
47 Todo: arc support
48*/
49
51{
52public:
54 m_board( nullptr )
55 {}
56
58
59 virtual bool Run() override;
60
61 virtual const wxString GetName() const override { return wxT( "length" ); };
62
63private:
64
65 bool runInternal( bool aDelayReportMode = false );
66
68
69 void checkLengths( const DRC_CONSTRAINT& aConstraint,
70 const std::vector<CONNECTION>& aMatchedConnections );
71 void checkSkews( const DRC_CONSTRAINT& aConstraint,
72 const std::vector<CONNECTION>& aMatchedConnections );
73 void checkViaCounts( const DRC_CONSTRAINT& aConstraint,
74 const std::vector<CONNECTION>& aMatchedConnections );
75 void checkStubLengths( const DRC_CONSTRAINT& aConstraint,
76 DRC_RULE* aRule,
77 const std::vector<CONNECTION>& aMatchedConnections );
78 void checkReturnPath( const DRC_CONSTRAINT& aConstraint,
79 DRC_RULE* aRule,
80 const std::map<wxString, CONNECTION>& aChainAgg );
81
82 // Lazily-built shared topology view of a named chain, built from every
83 // item on the board that carries that chain. Cached for the duration of
84 // a single DRC pass so two rules constraining the same chain don't
85 // rebuild the graph (the topology is a property of the routed copper,
86 // not of which rule matched).
87 std::shared_ptr<CHAIN_TOPOLOGY> chainTopologyFor( const wxString& aChain );
88
89 // Cached zone-coverage union per (reference layer, net pattern) tuple
90 // for the duration of a DRC pass — return-path checks can reuse it
91 // across rules.
92 SHAPE_POLY_SET* zoneUnionFor( PCB_LAYER_ID aRefLayer, const wxString& aRefNet );
93
94private:
97 std::map<wxString, std::shared_ptr<CHAIN_TOPOLOGY>> m_chainTopoCache;
98 std::map<std::pair<PCB_LAYER_ID, wxString>, SHAPE_POLY_SET> m_refUnionCache;
99};
100
102 const std::vector<CONNECTION>& aMatchedConnections )
103{
104 for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
105 {
106 bool minViolation = false;
107 bool maxViolation = false;
108 int minLen = 0;
109 int maxLen = 0;
110
111 const bool isTimeDomain = aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::TIME_DOMAIN );
112 const EDA_DATA_TYPE dataType = isTimeDomain ? EDA_DATA_TYPE::TIME : EDA_DATA_TYPE::DISTANCE;
113
114 if( !isTimeDomain )
115 {
116 if( aConstraint.GetValue().HasMin() && ent.total < aConstraint.GetValue().Min() )
117 {
118 minViolation = true;
119 minLen = aConstraint.GetValue().Min();
120 }
121 else if( aConstraint.GetValue().HasMax() && ent.total > aConstraint.GetValue().Max() )
122 {
123 maxViolation = true;
124 maxLen = aConstraint.GetValue().Max();
125 }
126 }
127 else
128 {
129 if( aConstraint.GetValue().HasMin() && ent.totalDelay < aConstraint.GetValue().Min() )
130 {
131 minViolation = true;
132 minLen = aConstraint.GetValue().Min();
133 }
134 else if( aConstraint.GetValue().HasMax() && ent.totalDelay > aConstraint.GetValue().Max() )
135 {
136 maxViolation = true;
137 maxLen = aConstraint.GetValue().Max();
138 }
139 }
140
141 if( ( minViolation || maxViolation ) )
142 {
143 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LENGTH_OUT_OF_RANGE );
144
145 if( minViolation )
146 {
147 drcItem->SetErrorDetail( formatMsg( _( "(%s min length %s; actual %s)" ),
148 aConstraint.GetName(),
149 minLen,
151 ? ent.totalDelay
152 : ent.total,
153 dataType ) );
154 }
155 else if( maxViolation )
156 {
157 drcItem->SetErrorDetail( formatMsg( _( "(%s max length %s; actual %s)" ),
158 aConstraint.GetName(),
159 maxLen,
161 ? ent.totalDelay
162 : ent.total,
163 dataType ) );
164 }
165
166 for( auto offendingTrack : ent.items )
167 drcItem->AddItem( offendingTrack );
168
169 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
170
171 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
172 }
173 }
174}
175
177 const std::vector<CONNECTION>& aMatchedConnections )
178{
179 auto checkSkewsImpl = [this, &aConstraint]( const std::vector<CONNECTION>& connections )
180 {
181 const bool isTimeDomain = aConstraint.GetOption( DRC_CONSTRAINT::OPTIONS::TIME_DOMAIN );
182 const EDA_DATA_TYPE dataType = isTimeDomain ? EDA_DATA_TYPE::TIME : EDA_DATA_TYPE::DISTANCE;
183
184 double maxLength = 0;
185 wxString maxNetname;
186
187 if( !isTimeDomain )
188 {
189 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
190 {
191 if( ent.total > maxLength )
192 {
193 maxLength = ent.total;
194 maxNetname = ent.netname;
195 }
196 }
197 }
198 else
199 {
200 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
201 {
202 if( ent.totalDelay > maxLength )
203 {
204 maxLength = ent.totalDelay;
205 maxNetname = ent.netname;
206 }
207 }
208 }
209
210 for( const DRC_LENGTH_REPORT::ENTRY& ent : connections )
211 {
212 int skew = isTimeDomain ? KiROUND( ent.totalDelay - maxLength )
213 : KiROUND( ent.total - maxLength );
214
215 bool fail_min = false;
216 bool fail_max = false;
217
218 if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() )
219 fail_max = true;
220 else if( aConstraint.GetValue().HasMin() && abs( skew ) < aConstraint.GetValue().Min() )
221 fail_min = true;
222
223 if( fail_min || fail_max )
224 {
225 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE );
226 wxString msg;
227
228 double reportTotal = isTimeDomain ? ent.totalDelay : ent.total;
229
230 if( fail_min )
231 {
232 msg.Printf( _( "(%s min skew %s; actual %s; target net length %s (from %s); actual %s)" ),
233 aConstraint.GetName(),
234 MessageTextFromValue( aConstraint.GetValue().Min(), true, dataType ),
235 MessageTextFromValue( skew, true, dataType ),
236 MessageTextFromValue( maxLength, true, dataType ),
237 maxNetname,
238 MessageTextFromValue( reportTotal, true, dataType ) );
239 }
240 else
241 {
242 msg.Printf( _( "(%s max skew %s; actual %s; target net length %s (from %s); actual %s)" ),
243 aConstraint.GetName(),
244 MessageTextFromValue( aConstraint.GetValue().Max(), true, dataType ),
245 MessageTextFromValue( skew, true, dataType ),
246 MessageTextFromValue( maxLength, true, dataType ),
247 maxNetname,
248 MessageTextFromValue( reportTotal, true, dataType ) );
249 }
250
251 drcItem->SetErrorDetail( msg );
252
253 for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
254 drcItem->SetItems( offendingTrack );
255
256 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
257
258 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
259 }
260 }
261 };
262
264 {
265 // Find all pairs of nets in the matched connections
266 std::map<int, CONNECTION> netcodeMap;
267
268 for( const DRC_LENGTH_REPORT::ENTRY& ent : aMatchedConnections )
269 netcodeMap[ent.netcode] = ent;
270
271 std::vector<std::vector<CONNECTION>> matchedDiffPairs;
272
273 for( auto& [netcode, connection] : netcodeMap )
274 {
275 NETINFO_ITEM* matchedNet = m_board->DpCoupledNet( connection.netinfo );
276
277 if( matchedNet )
278 {
279 int matchedNetcode = matchedNet->GetNetCode();
280
281 if( netcodeMap.count( matchedNetcode ) )
282 {
283 std::vector<CONNECTION> pair{ connection, netcodeMap[matchedNetcode] };
284 matchedDiffPairs.emplace_back( std::move( pair ) );
285 netcodeMap.erase( matchedNetcode );
286 }
287 }
288 }
289
290 // Test all found pairs of nets
291 for( const std::vector<CONNECTION>& matchedDiffPair : matchedDiffPairs )
292 checkSkewsImpl( matchedDiffPair );
293 }
294 else
295 {
296 // Test all matched nets as a group
297 checkSkewsImpl( aMatchedConnections );
298 }
299}
300
301
303 const std::vector<CONNECTION>& aMatchedConnections )
304{
305 for( const auto& ent : aMatchedConnections )
306 {
307 std::shared_ptr<DRC_ITEM> drcItem = nullptr;
308
309 if( aConstraint.GetValue().HasMax() && ent.viaCount > aConstraint.GetValue().Max() )
310 {
312 wxString msg = wxString::Format( _( "(%s max count %d; actual %d)" ),
313 aConstraint.GetName(),
314 aConstraint.GetValue().Max(),
315 ent.viaCount );
316
317 drcItem->SetErrorMessage( _( "Too many vias on a connection" ) + wxS( " " ) + msg );
318 }
319 else if( aConstraint.GetValue().HasMin() && ent.viaCount < aConstraint.GetValue().Min() )
320 {
322 wxString msg = wxString::Format( _( "(%s min count %d; actual %d)" ),
323 aConstraint.GetName(),
324 aConstraint.GetValue().Min(),
325 ent.viaCount );
326
327 drcItem->SetErrorMessage( _( "Too few vias on a connection" ) + wxS( " " ) + msg );
328 }
329
330 if( drcItem )
331 {
332 for( const BOARD_CONNECTED_ITEM* offendingTrack : ent.items )
333 drcItem->SetItems( offendingTrack );
334
335 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
336
337 reportViolation( drcItem, ( *ent.items.begin() )->GetPosition(), ( *ent.items.begin() )->GetLayer() );
338 }
339 }
340}
341
342
344{
345 return runInternal( false );
346}
347
348
350{
351 m_board = m_drcEngine->GetBoard();
352 m_report.Clear();
353 m_chainTopoCache.clear();
354 m_refUnionCache.clear();
355
356 if( !aDelayReportMode )
357 {
358 if( !reportPhase( _( "Gathering length-constrained connections..." ) ) )
359 return false;
360 }
361
362 LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
363 std::map<DRC_RULE*, std::set<BOARD_CONNECTED_ITEM*> > itemSets;
364
365 std::shared_ptr<FROM_TO_CACHE> ftCache = m_board->GetConnectivity()->GetFromToCache();
366
367 ftCache->Rebuild( m_board );
368
369 const size_t progressDelta = 100;
370 size_t count = 0;
371 size_t ii = 0;
372
374 [&]( BOARD_ITEM *item ) -> bool
375 {
376 count++;
377 return true;
378 } );
379
381 [&]( BOARD_ITEM *item ) -> bool
382 {
383 if( !reportProgress( ii++, count, progressDelta ) )
384 return false;
385
390 {
391 DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( jj, item, nullptr, item->GetLayer() );
392
393 if( constraint.IsNull() )
394 continue;
395
396 BOARD_CONNECTED_ITEM* citem = static_cast<BOARD_CONNECTED_ITEM*>( item );
397
398 itemSets[ constraint.GetParentRule() ].insert( citem );
399 }
400
401 return true;
402 } );
403
404 LENGTH_DELAY_CALCULATION* calc = m_board->GetLengthCalculation();
405
406 std::map< DRC_RULE*, std::vector<CONNECTION> > matches;
407
408 for( const auto& [rule, ruleItems] : itemSets )
409 {
410 std::map<int, std::set<BOARD_CONNECTED_ITEM*> > netMap;
411
412 for( BOARD_CONNECTED_ITEM* item : ruleItems )
413 netMap[item->GetNetCode()].insert( item );
414
415 for( const auto& [netCode, netItems] : netMap )
416 {
417 std::vector<LENGTH_DELAY_CALCULATION_ITEM> lengthItems;
418 lengthItems.reserve( netItems.size() );
419
420 CONNECTION ent;
421 ent.items = netItems;
422 ent.netcode = netCode;
423 ent.netname = m_board->GetNetInfo().GetNetItem( ent.netcode )->GetNetname();
424 ent.netinfo = m_board->GetNetInfo().GetNetItem( ent.netcode );
425
426 ent.viaCount = 0;
427 ent.totalRoute = 0;
428 ent.totalVia = 0;
429 ent.totalPadToDie = 0;
430 ent.fromItem = nullptr;
431 ent.toItem = nullptr;
432
433 for( BOARD_CONNECTED_ITEM* item : netItems )
434 {
435 LENGTH_DELAY_CALCULATION_ITEM lengthItem = calc->GetLengthCalculationItem( item );
436
437 if( lengthItem.Type() != LENGTH_DELAY_CALCULATION_ITEM::TYPE::UNKNOWN )
438 lengthItems.emplace_back( lengthItem );
439 }
440
441 constexpr PATH_OPTIMISATIONS opts = {
442 .OptimiseVias = true, .MergeTracks = true, .OptimiseTracesInPads = true, .InferViaInPad = false
443 };
444 LENGTH_DELAY_STATS details = calc->CalculateLengthDetails( lengthItems, opts, nullptr, nullptr,
447 ent.viaCount = details.NumVias;
448 ent.totalVia = details.ViaLength;
449 ent.totalViaDelay = details.ViaDelay;
450 ent.totalRoute = static_cast<double>( details.TrackLength );
451 ent.totalRouteDelay = static_cast<double>( details.TrackDelay );
452 ent.totalPadToDie = details.PadToDieLength;
453 ent.totalPadToDieDelay = details.PadToDieDelay;
454 ent.total = ent.totalRoute + ent.totalVia + ent.totalPadToDie;
455 ent.totalDelay = ent.totalRouteDelay + static_cast<double>( ent.totalViaDelay )
456 + static_cast<double>( ent.totalPadToDieDelay );
457 ent.matchingRule = rule;
458
459 if( FROM_TO_CACHE::FT_PATH* ftPath = ftCache->QueryFromToPath( ent.items ) )
460 {
461 ent.from = ftPath->fromName;
462 ent.to = ftPath->toName;
463 }
464 else
465 {
466 ent.from = ent.to = _( "<unconstrained>" );
467 }
468
469 m_report.Add( ent );
470 matches[rule].push_back( ent );
471 }
472 }
473
474 if( !aDelayReportMode )
475 {
476 if( !reportPhase( _( "Checking length constraints..." ) ) )
477 return false;
478
479 ii = 0;
480 count = matches.size();
481
482 for( std::pair< DRC_RULE* const, std::vector<CONNECTION> > it : matches )
483 {
484 DRC_RULE *rule = it.first;
485 auto& matchedConnections = it.second;
486
487 if( !reportProgress( ii++, count, progressDelta ) )
488 return false;
489
490 std::sort( matchedConnections.begin(), matchedConnections.end(),
491 [] ( const CONNECTION&a, const CONNECTION&b ) -> int
492 {
493 return a.netname < b.netname;
494 } );
495
496 if( getLogReporter() )
497 {
498 REPORT_AUX( wxString::Format( wxT( "Length-constrained traces for rule '%s':" ),
499 it.first->m_Name ) );
500
501 for( const DRC_LENGTH_REPORT::ENTRY& ent : matchedConnections )
502 {
503 REPORT_AUX( wxString::Format( wxT( " - net: %s, from: %s, to: %s, %d matching items, "
504 "total: %s (tracks: %s, vias: %s, pad-to-die: %s), "
505 "vias: %d" ),
506 ent.netname,
507 ent.from, ent.to,
508 static_cast<int>( ent.items.size() ),
509 MessageTextFromValue( ent.total ),
510 MessageTextFromValue( ent.totalRoute ),
511 MessageTextFromValue( ent.totalVia ),
512 MessageTextFromValue( ent.totalPadToDie ),
513 ent.viaCount ) );
514 }
515 }
516
517 // Check both net-level and net-chain-level length constraints
518 std::optional<DRC_CONSTRAINT> lengthConstraint = rule->FindConstraint( LENGTH_CONSTRAINT );
519 std::optional<DRC_CONSTRAINT> netChainLengthConstraint = rule->FindConstraint( NET_CHAIN_LENGTH_CONSTRAINT );
520
521 // Build per-chain aggregates (kept for the length report). The
522 // shared CHAIN_TOPOLOGY is fetched lazily from the provider's
523 // cache so two rules constraining the same chain don't rebuild
524 // the graph.
525 std::map<wxString, CONNECTION> chainAgg;
526 std::optional<DRC_CONSTRAINT> stubConstraint =
528 std::optional<DRC_CONSTRAINT> returnPathConstraint =
530
531 const bool needsChainAgg =
532 ( netChainLengthConstraint
533 && netChainLengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
534 || ( stubConstraint && stubConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
535 || ( returnPathConstraint
536 && returnPathConstraint->GetSeverity() != RPT_SEVERITY_IGNORE );
537
538 if( needsChainAgg )
539 {
540 for( const CONNECTION& conn : matchedConnections )
541 {
542 if( !conn.netinfo )
543 continue;
544
545 wxString chainName = conn.netinfo->GetNetChain();
546
547 if( chainName.IsEmpty() )
548 continue;
549
550 auto aggIt = chainAgg.find( chainName );
551
552 if( aggIt == chainAgg.end() )
553 {
554 CONNECTION agg = conn;
555 agg.netname = chainName;
556 chainAgg[chainName] = agg;
557 }
558 else
559 {
560 CONNECTION& agg = aggIt->second;
561 agg.total += conn.total;
562 agg.totalDelay += conn.totalDelay;
563 agg.totalRoute += conn.totalRoute;
564 agg.totalRouteDelay += conn.totalRouteDelay;
565 agg.totalVia += conn.totalVia;
566 agg.totalViaDelay += conn.totalViaDelay;
567 agg.totalPadToDie += conn.totalPadToDie;
568 agg.totalPadToDieDelay += conn.totalPadToDieDelay;
569 agg.viaCount += conn.viaCount;
570 agg.items.insert( conn.items.begin(), conn.items.end() );
571 }
572 }
573 }
574
575 if( netChainLengthConstraint && netChainLengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
576 {
577 std::vector<CONNECTION> chainConnections;
578 chainConnections.reserve( chainAgg.size() );
579
580 for( auto& [chainName, agg] : chainAgg )
581 {
582 CONNECTION constraintInput = agg;
583 std::shared_ptr<CHAIN_TOPOLOGY> topo = chainTopologyFor( chainName );
584
585 if( topo && topo->IsValid() )
586 {
587 // Trunk semantics: constrain the matched-length signal
588 // path between terminal pads, not the sum of all member
589 // segments. Bridging is folded into the topology graph.
590 constraintInput.total = topo->TrunkLength();
591 constraintInput.totalDelay = topo->TrunkDelay();
592 constraintInput.totalRoute = topo->TrunkLength();
593 constraintInput.totalRouteDelay = topo->TrunkDelay();
594 }
595 else
596 {
597 // Aggregate fallback (legacy) — preserves behavior for
598 // chains without terminal pads or with looped routing.
599 // Add bridging length/delay through series passives so
600 // the time-domain rule sees a compensated chain.
601 auto [bridging, bridgingDelay] = BoardChainBridging( m_board, chainName );
602
603 constraintInput.total += bridging;
604 constraintInput.totalRoute += bridging;
605 constraintInput.totalDelay += bridgingDelay;
606 constraintInput.totalRouteDelay += bridgingDelay;
607 }
608
609 chainConnections.push_back( std::move( constraintInput ) );
610 }
611
612 checkLengths( *netChainLengthConstraint, chainConnections );
613 }
614
615 if( lengthConstraint && lengthConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
616 {
617 checkLengths( *lengthConstraint, matchedConnections );
618 }
619
620 std::optional<DRC_CONSTRAINT> skewConstraint = rule->FindConstraint( SKEW_CONSTRAINT );
621
622 if( skewConstraint && skewConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
623 checkSkews( *skewConstraint, matchedConnections );
624
625 std::optional<DRC_CONSTRAINT> viaCountConstraint = rule->FindConstraint( VIA_COUNT_CONSTRAINT );
626
627 if( viaCountConstraint && viaCountConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
628 checkViaCounts( *viaCountConstraint, matchedConnections );
629
630 if( returnPathConstraint
631 && returnPathConstraint->GetSeverity() != RPT_SEVERITY_IGNORE
632 && !returnPathConstraint->m_ReferenceLayer.IsEmpty() )
633 {
634 checkReturnPath( *returnPathConstraint, rule, chainAgg );
635 }
636
637 if( stubConstraint && stubConstraint->GetSeverity() != RPT_SEVERITY_IGNORE )
638 checkStubLengths( *stubConstraint, rule, matchedConnections );
639 }
640 }
641
642 return !m_drcEngine->IsCancelled();
643}
644
645
646std::shared_ptr<CHAIN_TOPOLOGY>
648{
649 auto it = m_chainTopoCache.find( aChain );
650
651 if( it != m_chainTopoCache.end() )
652 return it->second;
653
654 std::set<BOARD_CONNECTED_ITEM*> items;
655
656 for( PCB_TRACK* t : m_board->Tracks() )
657 {
658 if( t->GetNet() && t->GetNet()->GetNetChain() == aChain )
659 items.insert( t );
660 }
661
662 for( FOOTPRINT* fp : m_board->Footprints() )
663 {
664 for( PAD* p : fp->Pads() )
665 {
666 if( p->GetNet() && p->GetNet()->GetNetChain() == aChain )
667 items.insert( p );
668 }
669 }
670
671 auto topo = std::make_shared<CHAIN_TOPOLOGY>( m_board, aChain, items );
672 m_chainTopoCache.emplace( aChain, topo );
673 return topo;
674}
675
676
679 const wxString& aRefNet )
680{
681 auto key = std::make_pair( aRefLayer, aRefNet );
682 auto it = m_refUnionCache.find( key );
683
684 if( it != m_refUnionCache.end() )
685 return &it->second;
686
687 SHAPE_POLY_SET refUnion;
688
689 for( ZONE* zone : m_board->Zones() )
690 {
691 if( !zone || !zone->IsOnLayer( aRefLayer ) || zone->GetIsRuleArea() )
692 continue;
693
694 if( !aRefNet.IsEmpty() )
695 {
696 wxString zoneNet = zone->GetNetname();
697
698 if( !WildCompareString( aRefNet, zoneNet, false ) )
699 continue;
700 }
701
702 // Prefer the cached filled-fill polys (respect holes / islands).
703 // Fall back to the zone outline for synthetic / unfilled boards
704 // (test fixtures, freshly imported designs). GetFill returns
705 // nullptr when the zone hasn't been filled for this layer; using
706 // GetFilledPolysList here would assert.
707 if( SHAPE_POLY_SET* filled = zone->GetFill( aRefLayer );
708 filled && filled->OutlineCount() > 0 )
709 {
710 refUnion.BooleanAdd( *filled );
711 }
712 else if( zone->Outline() )
713 {
714 refUnion.BooleanAdd( *zone->Outline() );
715 }
716 }
717
718 refUnion.Simplify();
719 return &m_refUnionCache.emplace( key, std::move( refUnion ) ).first->second;
720}
721
722
724 const DRC_CONSTRAINT& aConstraint, DRC_RULE* aRule,
725 const std::vector<CONNECTION>& aMatchedConnections )
726{
727 const bool isTimeDomain =
729 const EDA_DATA_TYPE dataType =
731 const MINOPTMAX<int>& range = aConstraint.GetValue();
732
733 if( !range.HasMax() && !range.HasMin() )
734 return;
735
736 auto outOfRange = [&]( double aMeasured )
737 {
738 return ( range.HasMax() && aMeasured > range.Max() )
739 || ( range.HasMin() && aMeasured < range.Min() );
740 };
741
742 // Collect the distinct chains touched by the rule's matched items, then
743 // dispatch each chain through the topology if it's valid, falling back
744 // to the legacy proxy otherwise.
745 std::set<wxString> chainNames;
746
747 for( const CONNECTION& conn : aMatchedConnections )
748 {
749 if( conn.netinfo && !conn.netinfo->GetNetChain().IsEmpty() )
750 chainNames.insert( conn.netinfo->GetNetChain() );
751 }
752
753 std::set<wxString> handledByTopology;
754
755 for( const wxString& chainName : chainNames )
756 {
757 std::shared_ptr<CHAIN_TOPOLOGY> topoPtr = chainTopologyFor( chainName );
758
759 if( !topoPtr || !topoPtr->IsValid() )
760 continue;
761
762 handledByTopology.insert( chainName );
763
764 for( const CHAIN_TOPOLOGY::STUB& stub : topoPtr->Stubs() )
765 {
766 const double measured = isTimeDomain ? stub.delay : stub.length;
767
768 if( !outOfRange( measured ) )
769 continue;
770
771 std::shared_ptr<DRC_ITEM> item =
773 item->SetErrorMessage( wxString::Format(
774 _( "Stub length (%s) out of range for net chain '%s'." ),
775 MessageTextFromValue( measured, true, dataType ),
776 chainName ) );
777 item->SetViolatingRule( aRule );
778
779 for( BOARD_CONNECTED_ITEM* it : stub.items )
780 item->AddItem( it );
781
782 reportViolation( item, stub.branchPoint, stub.branchLayer );
783 }
784 }
785
786 // Legacy proxy fallback for chains where the topology builder reported an
787 // invalid status (no terminal pads, disconnected, or cycle). This
788 // preserves behavior on rule sets that pre-date the terminal-pad model.
789 for( const CONNECTION& conn : aMatchedConnections )
790 {
791 NETINFO_ITEM* netInfo = m_board->GetNetInfo().GetNetItem( conn.netcode );
792
793 if( !netInfo || netInfo->GetNetChain().IsEmpty() )
794 continue;
795
796 if( handledByTopology.count( netInfo->GetNetChain() ) )
797 continue;
798
799 const bool onTrunk =
800 ( netInfo->GetTerminalPad( 0 )
801 && netInfo->GetTerminalPad( 0 )->GetNetCode() == conn.netcode )
802 || ( netInfo->GetTerminalPad( 1 )
803 && netInfo->GetTerminalPad( 1 )->GetNetCode() == conn.netcode );
804
805 if( onTrunk )
806 continue;
807
808 const double measured = isTimeDomain ? conn.totalDelay
809 : static_cast<double>( conn.total );
810
811 if( !outOfRange( measured ) )
812 continue;
813
814 std::shared_ptr<DRC_ITEM> item =
816 item->SetErrorMessage( wxString::Format(
817 _( "Stub length (%s) out of range for net chain '%s' on net '%s'." ),
818 MessageTextFromValue( measured, true, dataType ),
819 netInfo->GetNetChain(),
820 netInfo->GetNetname() ) );
821 item->SetViolatingRule( aRule );
822
823 for( BOARD_CONNECTED_ITEM* connItem : conn.items )
824 item->AddItem( connItem );
825
826 VECTOR2I pos;
828
829 if( !conn.items.empty() )
830 {
831 pos = ( *conn.items.begin() )->GetPosition();
832 layer = ( *conn.items.begin() )->GetLayer();
833 }
834
835 reportViolation( item, pos, layer );
836 }
837}
838
839
841 const DRC_CONSTRAINT& aConstraint, DRC_RULE* aRule,
842 const std::map<wxString, CONNECTION>& aChainAgg )
843{
844 const wxString& refLayerName = aConstraint.m_ReferenceLayer;
845 const wxString& refNetPattern = aConstraint.m_ReferenceNet;
846
847 PCB_LAYER_ID refLayer = m_board->GetLayerID( refLayerName );
848
849 if( refLayer == UNDEFINED_LAYER )
850 return;
851
852 SHAPE_POLY_SET& refUnion = *zoneUnionFor( refLayer, refNetPattern );
853 const BOX2I refBbox = refUnion.OutlineCount() ? refUnion.BBox() : BOX2I();
854
855 struct FlaggedRegion
856 {
857 SHAPE_POLY_SET poly;
858 std::vector<BOARD_CONNECTED_ITEM*> items;
860 };
861
862 auto flagItem = [&]( BOARD_CONNECTED_ITEM* aItem,
863 std::vector<FlaggedRegion>& aFlagged,
864 SHAPE_POLY_SET&& aRegion )
865 {
866 for( int o = 0; o < aRegion.OutlineCount(); ++o )
867 {
868 FlaggedRegion fr;
869 fr.poly.AddOutline( aRegion.Outline( o ) );
870
871 for( int h = 0; h < aRegion.HoleCount( o ); ++h )
872 fr.poly.AddHole( aRegion.Hole( o, h ) );
873
874 fr.items.push_back( aItem );
875 fr.layer = aItem->GetLayer();
876 aFlagged.push_back( std::move( fr ) );
877 }
878 };
879
880 for( const auto& [chainName, agg] : aChainAgg )
881 {
882 std::vector<FlaggedRegion> flagged;
883
884 for( BOARD_CONNECTED_ITEM* item : agg.items )
885 {
886 if( !item )
887 continue;
888
889 if( item->Type() != PCB_TRACE_T && item->Type() != PCB_ARC_T )
890 continue;
891
892 BOX2I itemBbox = item->GetBoundingBox();
893
894 if( refUnion.OutlineCount() == 0 || !itemBbox.Intersects( refBbox ) )
895 {
896 // Nothing on the reference layer covers this item.
897 SHAPE_POLY_SET itemPoly;
898 item->TransformShapeToPolygon( itemPoly, item->GetLayer(),
900 flagItem( item, flagged, std::move( itemPoly ) );
901 continue;
902 }
903
904 SHAPE_POLY_SET itemPoly;
905 item->TransformShapeToPolygon( itemPoly, item->GetLayer(),
907
908 SHAPE_POLY_SET diff = itemPoly;
909 diff.BooleanSubtract( refUnion );
910 diff.Simplify();
911
912 if( diff.OutlineCount() == 0 )
913 continue;
914
915 flagItem( item, flagged, std::move( diff ) );
916 }
917
918 if( flagged.empty() )
919 continue;
920
921 // Coalesce flagged regions by spatial adjacency: two regions belong to
922 // the same group if their bounding boxes intersect (within epsilon).
923 // Provider-local union-find over the flagged set only — does NOT use
924 // CONNECTIVITY_DATA, which would merge across covered tracks.
925 std::vector<int> parent( flagged.size() );
926 std::iota( parent.begin(), parent.end(), 0 );
927
928 auto find = [&]( int x )
929 {
930 while( parent[x] != x )
931 {
932 parent[x] = parent[parent[x]];
933 x = parent[x];
934 }
935
936 return x;
937 };
938
939 for( size_t i = 0; i < flagged.size(); ++i )
940 {
941 BOX2I bi = flagged[i].poly.BBox();
942 bi.Inflate( 1 );
943
944 for( size_t j = i + 1; j < flagged.size(); ++j )
945 {
946 BOX2I bj = flagged[j].poly.BBox();
947
948 // Cheap reject: if the inflated bboxes don't even touch,
949 // the polygons cannot be adjacent.
950 if( !bi.Intersects( bj ) )
951 continue;
952
953 // Confirm polygon-level adjacency: two flagged regions
954 // belong to the same group only if their copper polygons
955 // actually intersect (after a 1 IU inflate to bridge
956 // touching boundaries). Bbox-touch alone can merge
957 // unrelated runs whose AABBs overlap but copper does not.
958 SHAPE_POLY_SET probe = flagged[i].poly;
960 probe.BooleanIntersection( flagged[j].poly );
961 probe.Simplify();
962
963 if( probe.OutlineCount() == 0 )
964 continue;
965
966 int ri = find( static_cast<int>( i ) );
967 int rj = find( static_cast<int>( j ) );
968
969 if( ri != rj )
970 parent[ri] = rj;
971 }
972 }
973
974 std::map<int, std::vector<int>> groups;
975
976 for( size_t i = 0; i < flagged.size(); ++i )
977 groups[find( static_cast<int>( i ) )].push_back( static_cast<int>( i ) );
978
979 for( const auto& [root, members] : groups )
980 {
981 SHAPE_POLY_SET unionPoly;
982 std::vector<BOARD_CONNECTED_ITEM*> items;
983 std::set<BOARD_CONNECTED_ITEM*> seen;
984 PCB_LAYER_ID markerLayer = UNDEFINED_LAYER;
985
986 for( int idx : members )
987 {
988 unionPoly.BooleanAdd( flagged[idx].poly );
989
990 for( BOARD_CONNECTED_ITEM* it : flagged[idx].items )
991 {
992 if( seen.insert( it ).second )
993 items.push_back( it );
994 }
995
996 if( markerLayer == UNDEFINED_LAYER )
997 markerLayer = flagged[idx].layer;
998 }
999
1000 unionPoly.Simplify();
1001
1002 VECTOR2I markerPos = unionPoly.OutlineCount()
1003 ? unionPoly.BBox().Centre()
1004 : ( !items.empty()
1005 ? items.front()->GetPosition()
1006 : VECTOR2I() );
1007
1008 std::shared_ptr<DRC_ITEM> drcItem =
1010
1011 wxString msg = wxString::Format(
1012 _( "Net chain '%s' has no copper return path on reference layer '%s'." ),
1013 chainName, refLayerName );
1014
1015 if( !refNetPattern.IsEmpty() )
1016 msg += wxString::Format( _( " (net '%s')" ), refNetPattern );
1017
1018 drcItem->SetErrorMessage( msg );
1019 drcItem->SetViolatingRule( aRule );
1020
1021 for( BOARD_CONNECTED_ITEM* it : items )
1022 drcItem->AddItem( it );
1023
1024 reportViolation( drcItem, markerPos, markerLayer );
1025 }
1026 }
1027}
1028
1029
1030namespace detail
1031{
1033}
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:141
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
constexpr Vec Centre() const
Definition box2.h:97
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
wxString GetName() const
Definition drc_rule.h:208
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:200
wxString m_ReferenceLayer
Definition drc_rule.h:253
bool GetOption(OPTIONS option) const
Definition drc_rule.h:233
DRC_RULE * GetParentRule() const
Definition drc_rule.h:204
wxString m_ReferenceNet
Definition drc_rule.h:258
bool IsNull() const
Definition drc_rule.h:195
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:417
std::optional< DRC_CONSTRAINT > FindConstraint(DRC_CONSTRAINT_T aType)
Definition drc_rule.cpp:72
virtual const wxString GetName() const override
std::shared_ptr< CHAIN_TOPOLOGY > chainTopologyFor(const wxString &aChain)
void checkSkews(const DRC_CONSTRAINT &aConstraint, const std::vector< CONNECTION > &aMatchedConnections)
SHAPE_POLY_SET * zoneUnionFor(PCB_LAYER_ID aRefLayer, const wxString &aRefNet)
virtual ~DRC_TEST_PROVIDER_MATCHED_LENGTH()=default
void checkReturnPath(const DRC_CONSTRAINT &aConstraint, DRC_RULE *aRule, const std::map< wxString, CONNECTION > &aChainAgg)
void checkViaCounts(const DRC_CONSTRAINT &aConstraint, const std::vector< CONNECTION > &aMatchedConnections)
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
std::map< wxString, std::shared_ptr< CHAIN_TOPOLOGY > > m_chainTopoCache
std::map< std::pair< PCB_LAYER_ID, wxString >, SHAPE_POLY_SET > m_refUnionCache
void checkStubLengths(const DRC_CONSTRAINT &aConstraint, DRC_RULE *aRule, const std::vector< CONNECTION > &aMatchedConnections)
void checkLengths(const DRC_CONSTRAINT &aConstraint, const std::vector< CONNECTION > &aMatchedConnections)
virtual bool reportPhase(const wxString &aStageName)
int forEachGeometryItem(const std::vector< KICAD_T > &aTypes, const LSET &aLayers, const std::function< bool(BOARD_ITEM *)> &aFunc)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
REPORTER * getLogReporter() const
wxString formatMsg(const wxString &aFormatString, const wxString &aSource, double aConstraint, double aActual, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:120
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
Lightweight class which holds a pad, via, or a routed trace outline.
TYPE Type() const
Gets the routing item type.
Class which calculates lengths (and associated routing statistics) in a BOARD context.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
T Min() const
Definition minoptmax.h:33
bool HasMax() const
Definition minoptmax.h:38
bool HasMin() const
Definition minoptmax.h:37
T Max() const
Definition minoptmax.h:34
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetChain() const
Definition netinfo.h:115
const wxString & GetNetname() const
Definition netinfo.h:103
int GetNetCode() const
Definition netinfo.h:97
PAD * GetTerminalPad(int aIndex) const
Definition netinfo.h:118
Definition pad.h:55
Represent a set of closed polygons.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
Handle a list of polygons defining a copper zone.
Definition zone.h:74
The common library.
@ ROUND_ALL_CORNERS
All angles are rounded.
@ DRCE_SKEW_OUT_OF_RANGE
Definition drc_item.h:107
@ DRCE_NET_CHAIN_STUB_TOO_LONG
Definition drc_item.h:105
@ DRCE_NET_CHAIN_RETURN_PATH_BREAK
Definition drc_item.h:106
@ DRCE_LENGTH_OUT_OF_RANGE
Definition drc_item.h:104
@ DRCE_VIA_COUNT_OUT_OF_RANGE
Definition drc_item.h:108
DRC_CONSTRAINT_T
Definition drc_rule.h:53
@ NET_CHAIN_LENGTH_CONSTRAINT
Definition drc_rule.h:78
@ NET_CHAIN_STUB_LENGTH_CONSTRAINT
Definition drc_rule.h:79
@ LENGTH_CONSTRAINT
Definition drc_rule.h:77
@ VIA_COUNT_CONSTRAINT
Definition drc_rule.h:85
@ NET_CHAIN_RETURN_PATH_CONSTRAINT
Definition drc_rule.h:80
@ SKEW_CONSTRAINT
Definition drc_rule.h:81
#define REPORT_AUX(s)
#define _(s)
EDA_DATA_TYPE
The type of unit.
Definition eda_units.h:38
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ UNDEFINED_LAYER
Definition layer_ids.h:61
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
std::tuple< double, double > BoardChainBridging(const BOARD *aBoard, const wxString &aNetChain)
@ RPT_SEVERITY_IGNORE
bool WildCompareString(const wxString &pattern, const wxString &string_to_tst, bool case_sensitive)
Compare a string against wild card (* and ?) pattern using the usual rules.
BOARD_CONNECTED_ITEM * fromItem
BOARD_CONNECTED_ITEM * toItem
std::set< BOARD_CONNECTED_ITEM * > items
Holds length measurement result details and statistics.
Struct to control which optimisations the length calculation code runs on the given path objects.
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:94
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:84
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:95
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:93
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687