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