KiCad PCB EDA Suite
Loading...
Searching...
No Matches
length_delay_calculation.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
25
26#include <wx/log.h>
27
28#include <board.h>
30#include <footprint.h>
33#include <geometry/circle.h>
34#include <pad.h>
35#include <pcb_track.h>
36
37
39 const std::shared_ptr<SHAPE_POLY_SET>& aPadShape,
40 const VECTOR2I& aInsidePoint, VECTOR2I& aIntersection )
41{
42 bool found = false;
43 int64_t bestDistSq = std::numeric_limits<int64_t>::max();
44
45 for( int i = 0; i < aPadShape->OutlineCount(); i++ )
46 {
47 const SHAPE_LINE_CHAIN& outline = aPadShape->Outline( i );
48
49 for( int j = 0; j < outline.SegmentCount(); j++ )
50 {
51 const SEG& seg = outline.CSegment( j );
52 std::vector<VECTOR2I> intersections;
53
54 if( !aArc.IntersectLine( seg, &intersections ) )
55 continue;
56
57 for( const VECTOR2I& pt : intersections )
58 {
59 if( !seg.Contains( pt ) )
60 continue;
61
62 int64_t distSq = ( pt - aInsidePoint ).SquaredEuclideanNorm();
63
64 if( distSq < bestDistSq )
65 {
66 bestDistSq = distSq;
67 aIntersection = pt;
68 found = true;
69 }
70 }
71 }
72 }
73
74 return found;
75}
76
77
79 bool aForward )
80{
81 wxASSERT( aLine.PointCount() >= 2 );
82
83 const int start = aForward ? 0 : aLine.PointCount() - 1;
84 const int delta = aForward ? 1 : -1;
85
86 const auto& shape = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );
87
88 // Find the first point OUTSIDE the pad
89 int firstOutside = -1;
90 VECTOR2I intersectionPt;
91 bool hasIntersection = false;
92
93 for( int vertex = start + delta; aForward ? vertex < aLine.PointCount() : vertex >= 0; vertex += delta )
94 {
95 if( !shape->Contains( aLine.GetPoint( vertex ) ) )
96 {
97 firstOutside = vertex;
98 int prevVertex = vertex - delta;
99
100 // Check if the crossing segment is part of an arc
101 ssize_t arcIdx = aLine.ArcIndex( prevVertex );
102
103 if( arcIdx >= 0 && aLine.ArcIndex( vertex ) == arcIdx )
104 {
105 hasIntersection = findArcPadIntersection( aLine.Arc( arcIdx ), shape, aLine.GetPoint( prevVertex ),
106 intersectionPt );
107 }
108
109 // Fallback to segment intersection if arc intersection didn't work
110 if( !hasIntersection )
111 {
112 SEG seg( aLine.GetPoint( vertex ), aLine.GetPoint( prevVertex ) );
113 VECTOR2I loc;
114
115 if( shape->Collide( seg, 0, nullptr, &loc ) )
116 {
117 intersectionPt = loc;
118 hasIntersection = true;
119 }
120 }
121
122 break;
123 }
124 }
125
126 if( firstOutside < 0 )
127 return; // All points inside pad, nothing to clip
128
129 // Build new chain using Slice (preserves arcs correctly without index corruption)
130 SHAPE_LINE_CHAIN newChain;
131
132 if( aForward )
133 {
134 // Chain: padCenter -> intersection -> [firstOutside to end]
135 newChain.Append( aPad->GetPosition() );
136 if( hasIntersection )
137 newChain.Append( intersectionPt );
138 newChain.Append( aLine.Slice( firstOutside, -1 ) );
139 }
140 else
141 {
142 // Chain: [0 to firstOutside] -> intersection -> padCenter
143 newChain.Append( aLine.Slice( 0, firstOutside ) );
144 if( hasIntersection )
145 newChain.Append( intersectionPt );
146 newChain.Append( aPad->GetPosition() );
147 }
148
149 aLine = newChain;
150}
151
152
153LENGTH_DELAY_STATS LENGTH_DELAY_CALCULATION::CalculateLengthDetails( std::vector<LENGTH_DELAY_CALCULATION_ITEM>& aItems,
154 const PATH_OPTIMISATIONS aOptimisations,
155 const PAD* aStartPad, const PAD* aEndPad,
156 const LENGTH_DELAY_LAYER_OPT aLayerOpt,
157 const LENGTH_DELAY_DOMAIN_OPT aDomain ) const
158{
159 const bool doTrace = wxLog::IsAllowedTraceMask( wxT( "PNS_TUNE" ) );
160
161 if( doTrace )
162 {
163 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "" ) );
164 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "========== CalculateLengthDetails START ==========" ) );
165 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: input has %zu items" ), aItems.size() );
166 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: optimisations - OptimiseVias=%d, MergeTracks=%d, OptimiseTracesInPads=%d, InferViaInPad=%d" ),
167 aOptimisations.OptimiseVias, aOptimisations.MergeTracks,
168 aOptimisations.OptimiseTracesInPads, aOptimisations.InferViaInPad );
169
170 // Count initial items by type
171 int initialPads = 0, initialVias = 0, initialLines = 0, initialUnknown = 0;
172
173 for( const LENGTH_DELAY_CALCULATION_ITEM& item : aItems )
174 {
175 switch( item.Type() )
176 {
177 case LENGTH_DELAY_CALCULATION_ITEM::TYPE::PAD: initialPads++; break;
178 case LENGTH_DELAY_CALCULATION_ITEM::TYPE::VIA: initialVias++; break;
179 case LENGTH_DELAY_CALCULATION_ITEM::TYPE::LINE: initialLines++; break;
180 default: initialUnknown++; break;
181 }
182 }
183
184 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: initial items - PADs=%d, VIAs=%d, LINEs=%d, UNKNOWN=%d" ),
185 initialPads, initialVias, initialLines, initialUnknown );
186 }
187
188 // If this set of items has not been optimised, optimise for shortest electrical path
189 if( aOptimisations.OptimiseVias || aOptimisations.MergeTracks || aOptimisations.MergeTracks )
190 {
191 if( doTrace )
192 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: performing optimisations..." ) );
193
194 std::vector<LENGTH_DELAY_CALCULATION_ITEM*> pads;
195 std::vector<LENGTH_DELAY_CALCULATION_ITEM*> lines;
196 std::vector<LENGTH_DELAY_CALCULATION_ITEM*> vias;
197
198 // Map of line endpoints to line objects
199 std::map<VECTOR2I, std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*>> linesPositionMap;
200
201 // Map of pad positions to pad objects
202 std::map<VECTOR2I, std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*>> padsPositionMap;
203
204 for( LENGTH_DELAY_CALCULATION_ITEM& item : aItems )
205 {
206 if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::PAD )
207 {
208 pads.emplace_back( &item );
209 padsPositionMap[item.GetPad()->GetPosition()].insert( &item );
210 }
211 else if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::VIA )
212 {
213 vias.emplace_back( &item );
214 }
215 else if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::LINE )
216 {
217 lines.emplace_back( &item );
218 linesPositionMap[item.GetLine().CPoint( 0 )].insert( &item );
219 linesPositionMap[item.GetLine().CLastPoint()].insert( &item );
220 }
221 }
222
223 if( aOptimisations.OptimiseVias )
224 {
225 if( doTrace )
226 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: optimising via layers (%zu vias)" ), vias.size() );
227
228 optimiseVias( vias, lines, linesPositionMap, padsPositionMap );
229 }
230
231 if( aOptimisations.MergeTracks )
232 {
233 if( doTrace )
234 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: merging tracks (%zu lines)" ), lines.size() );
235
236 mergeLines( lines, linesPositionMap );
237 }
238
239 // Clip traces inside VIA pads after merging
240 if( aOptimisations.OptimiseVias )
241 {
243 {
244 const PCB_VIA* pcbVia = via->GetVia();
245
246 for( LENGTH_DELAY_CALCULATION_ITEM* lineItem : lines )
247 {
248 if( lineItem->GetMergeStatus() != LENGTH_DELAY_CALCULATION_ITEM::MERGE_STATUS::MERGED_IN_USE )
249 {
250 continue;
251 }
252
253 OptimiseTraceInVia( lineItem->GetLine(), pcbVia, lineItem->GetStartLayer() );
254 }
255 }
256 }
257
258 if( aOptimisations.OptimiseTracesInPads )
259 {
260 if( doTrace )
261 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: optimising traces in pads" ) );
262
263 optimiseTracesInPads( pads, lines );
264 }
265 }
266
267 LENGTH_DELAY_STATS details;
268
269 // Create the layer detail maps if required
271 {
272 if( doTrace )
273 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: creating layer detail maps" ) );
274
275 details.LayerLengths = std::make_unique<std::map<PCB_LAYER_ID, int64_t>>();
276
278 details.LayerDelays = std::make_unique<std::map<PCB_LAYER_ID, int64_t>>();
279 }
280
281 const bool useHeight = m_board->GetDesignSettings().m_UseHeightForLengthCalcs;
282
283 if( doTrace )
284 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: useHeight=%d" ), useHeight );
285
286 // If this is a contiguous set of items, check if we have an inferred fanout via at either end. Note that this
287 // condition only arises as a result of how PNS assembles tuning paths - for DRC / net inspector calculations these
288 // fanout vias will be present in the object set and therefore do not need to be inferred
289 if( aOptimisations.InferViaInPad && useHeight )
290 {
291 if( doTrace )
292 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: inferring vias in pads" ) );
293
294 inferViaInPad( aStartPad, aItems.front(), details );
295 inferViaInPad( aEndPad, aItems.back(), details );
296 }
297
298 // Add stats for each item
299 int processedPads = 0, processedVias = 0, processedLines = 0;
300 int mergedRetired = 0, unknownType = 0;
301
302 if( doTrace )
303 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: processing %zu items..." ), aItems.size() );
304
305 for( const LENGTH_DELAY_CALCULATION_ITEM& item : aItems )
306 {
307 // Don't include merged items
309 || item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::UNKNOWN )
310 {
312 mergedRetired++;
313 else
314 unknownType++;
315 continue;
316 }
317
318 // Calculate the space domain statistics
319 if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::LINE )
320 {
321 const int64_t length = item.GetLine().Length();
322
323 details.TrackLength += length;
324 processedLines++;
325
326 if( details.LayerLengths )
327 ( *details.LayerLengths )[item.GetStartLayer()] += length;
328 }
329 else if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::VIA && useHeight )
330 {
331 const auto [layerStart, layerEnd] = item.GetLayers();
332 int64_t viaHeight = StackupHeight( layerStart, layerEnd );
333 details.ViaLength += viaHeight;
334 details.NumVias += 1;
335 processedVias++;
336
337 if( doTrace )
338 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: via from layer %d to %d, height=%lld" ),
339 layerStart, layerEnd, viaHeight );
340 }
341 else if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::PAD )
342 {
343 int64_t padToDie = item.GetPad()->GetPadToDieLength();
344 details.PadToDieLength += padToDie;
345 details.NumPads += 1;
346 processedPads++;
347
348 if( doTrace && padToDie > 0 )
349 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: pad with pad-to-die length=%lld" ), padToDie );
350 }
351 }
352
353 if( doTrace )
354 {
355 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: processed items - PADs=%d, VIAs=%d, LINEs=%d" ),
356 processedPads, processedVias, processedLines );
357 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: skipped items - merged/retired=%d, unknown=%d" ),
358 mergedRetired, unknownType );
359 }
360
361 // Calculate the time domain statistics if required
362 if( aDomain == LENGTH_DELAY_DOMAIN_OPT::WITH_DELAY_DETAIL && !aItems.empty() )
363 {
364 if( doTrace )
365 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: calculating time domain statistics" ) );
366
367 // TODO(JJ): Populate this
369 ctx.NetClass = aItems.front().GetEffectiveNetClass(); // We don't care if this is merged for net class lookup
370
371 const std::vector<int64_t> itemDelays = m_tuningProfileParameters->GetPropagationDelays( aItems, ctx );
372
373 wxASSERT( itemDelays.size() == aItems.size() );
374
375 for( size_t i = 0; i < aItems.size(); ++i )
376 {
377 const LENGTH_DELAY_CALCULATION_ITEM& item = aItems[i];
378
379 if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::LINE )
380 {
381 details.TrackDelay += itemDelays[i];
382
383 if( details.LayerDelays )
384 ( *details.LayerDelays )[item.GetStartLayer()] += itemDelays[i];
385 }
386 else if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::VIA && useHeight )
387 {
388 details.ViaDelay += itemDelays[i];
389 }
390 else if( item.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::PAD )
391 {
392 details.PadToDieDelay += itemDelays[i];
393 }
394 }
395
396 if( doTrace )
397 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: total delays - Track=%lld, Via=%lld, PadToDie=%lld" ),
398 details.TrackDelay, details.ViaDelay, details.PadToDieDelay );
399 }
400
401 if( doTrace )
402 {
403 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "CalculateLengthDetails: RESULTS:" ) );
404 wxLogTrace( wxT( "PNS_TUNE" ), wxT( " Track length: %lld" ), details.TrackLength );
405 wxLogTrace( wxT( "PNS_TUNE" ), wxT( " Via length: %d (from %d vias)" ), details.ViaLength, details.NumVias );
406 wxLogTrace( wxT( "PNS_TUNE" ), wxT( " Pad-to-die length: %d (from %d pads)" ), details.PadToDieLength, details.NumPads );
407 wxLogTrace( wxT( "PNS_TUNE" ), wxT( " TOTAL LENGTH: %lld" ), details.TotalLength() );
408 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "========== CalculateLengthDetails END ==========" ) );
409 wxLogTrace( wxT( "PNS_TUNE" ), wxT( "" ) );
410 }
411
412 return details;
413}
414
415
417 LENGTH_DELAY_STATS& aDetails ) const
418{
419 if( aPad && aItem.Type() == LENGTH_DELAY_CALCULATION_ITEM::TYPE::LINE )
420 {
421 const PCB_LAYER_ID startBottomLayer = aItem.GetStartLayer();
422 const LSET padLayers = aPad->Padstack().LayerSet();
423
424 if( !padLayers.Contains( startBottomLayer ) )
425 {
426 // This must be either F_Cu or B_Cu
427 const PCB_LAYER_ID padLayer = padLayers.Contains( F_Cu ) ? F_Cu : B_Cu;
428
429 aDetails.NumVias += 1;
430 aDetails.ViaLength += StackupHeight( startBottomLayer, padLayer );
431 }
432 }
433}
434
435
436int64_t LENGTH_DELAY_CALCULATION::CalculateLength( std::vector<LENGTH_DELAY_CALCULATION_ITEM>& aItems,
437 const PATH_OPTIMISATIONS aOptimisations, const PAD* aStartPad,
438 const PAD* aEndPad ) const
439{
440 return CalculateLengthDetails( aItems, aOptimisations, aStartPad, aEndPad ).TotalLength();
441}
442
443
444int64_t LENGTH_DELAY_CALCULATION::CalculateDelay( std::vector<LENGTH_DELAY_CALCULATION_ITEM>& aItems,
445 const PATH_OPTIMISATIONS aOptimisations, const PAD* aStartPad,
446 const PAD* aEndPad ) const
447{
448 return CalculateLengthDetails( aItems, aOptimisations, aStartPad, aEndPad, LENGTH_DELAY_LAYER_OPT::NO_LAYER_DETAIL,
450 .TotalDelay();
451}
452
453
454int LENGTH_DELAY_CALCULATION::StackupHeight( const PCB_LAYER_ID aFirstLayer, const PCB_LAYER_ID aSecondLayer ) const
455{
456 if( !m_board || !m_board->GetDesignSettings().m_UseHeightForLengthCalcs )
457 return 0;
458
459 if( m_board->GetDesignSettings().m_HasStackup )
460 {
461 const BOARD_STACKUP& stackup = m_board->GetDesignSettings().GetStackupDescriptor();
462 return stackup.GetLayerDistance( aFirstLayer, aSecondLayer );
463 }
464 else
465 {
466 BOARD_STACKUP stackup;
467 stackup.BuildDefaultStackupList( &m_board->GetDesignSettings(), m_board->GetCopperLayerCount() );
468 return stackup.GetLayerDistance( aFirstLayer, aSecondLayer );
469 }
470}
471
472
474 std::vector<LENGTH_DELAY_CALCULATION_ITEM*>& aLines,
475 std::map<VECTOR2I, std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*>>& aLinesPositionMap )
476{
477 // Vector of pads, and an associated flag to indicate whether they have been visited by the clustering algorithm
478 std::vector<LENGTH_DELAY_CALCULATION_ITEM*> pads;
479
480 auto removeFromPositionMap = [&aLinesPositionMap]( LENGTH_DELAY_CALCULATION_ITEM* line )
481 {
482 aLinesPositionMap[line->GetLine().CPoint( 0 )].erase( line );
483 aLinesPositionMap[line->GetLine().CLastPoint()].erase( line );
484 };
485
486 // Attempts to merge unmerged lines in to aPrimaryLine
487 auto tryMerge = [&removeFromPositionMap, &aLinesPositionMap]( const MERGE_POINT aMergePoint,
488 const VECTOR2I& aMergePos,
489 const LENGTH_DELAY_CALCULATION_ITEM* aPrimaryItem,
490 SHAPE_LINE_CHAIN& aPrimaryLine, bool* aDidMerge )
491 {
492 const auto startItr = aLinesPositionMap.find( aMergePos );
493
494 if( startItr == aLinesPositionMap.end() )
495 return;
496
497 std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*>& startItems = startItr->second;
498
499 if( startItems.size() != 1 )
500 return;
501
502 LENGTH_DELAY_CALCULATION_ITEM* lineToMerge = *startItems.begin();
503
504 // Don't merge if line is an arc
505 if( !lineToMerge->GetLine().CArcs().empty() )
506 return;
507
508 // Don't merge if lines are on different layers
509 if( aPrimaryItem->GetStartLayer() != lineToMerge->GetStartLayer() )
510 return;
511
512 // Merge the lines
514 mergeShapeLineChains( aPrimaryLine, lineToMerge->GetLine(), aMergePoint );
515 removeFromPositionMap( lineToMerge );
516 *aDidMerge = true;
517 };
518
519 // Cluster all lines in to contiguous entities
520 for( LENGTH_DELAY_CALCULATION_ITEM* primaryItem : aLines )
521 {
522 // Don't start with an already merged line
523 if( primaryItem->GetMergeStatus() != LENGTH_DELAY_CALCULATION_ITEM::MERGE_STATUS::UNMERGED )
524 continue;
525
526 // Remove starting line from the position map
527 removeFromPositionMap( primaryItem );
528
529 SHAPE_LINE_CHAIN& primaryLine = primaryItem->GetLine();
530
531 // Merge all endpoints
533 bool mergeComplete = false;
534
535 while( !mergeComplete )
536 {
537 bool startMerged = false;
538 bool endMerged = false;
539
540 VECTOR2I startPos = primaryLine.CPoint( 0 );
541 VECTOR2I endPos = primaryLine.CLastPoint();
542
543 tryMerge( MERGE_POINT::START, startPos, primaryItem, primaryLine, &startMerged );
544 tryMerge( MERGE_POINT::END, endPos, primaryItem, primaryLine, &endMerged );
545
546 mergeComplete = !startMerged && !endMerged;
547 }
548 }
549}
550
551
553 const MERGE_POINT aMergePoint )
554{
555 if( aMergePoint == MERGE_POINT::START )
556 {
557 if( aSecondary.GetPoint( 0 ) == aPrimary.GetPoint( 0 ) )
558 {
559 for( auto itr = aSecondary.CPoints().begin() + 1; itr != aSecondary.CPoints().end(); ++itr )
560 aPrimary.Insert( 0, *itr );
561 }
562 else
563 {
564 wxASSERT( aSecondary.CLastPoint() == aPrimary.GetPoint( 0 ) );
565
566 for( auto itr = aSecondary.CPoints().rbegin() + 1; itr != aSecondary.CPoints().rend(); ++itr )
567 aPrimary.Insert( 0, *itr );
568 }
569 }
570 else
571 {
572 if( aSecondary.GetPoint( 0 ) == aPrimary.CLastPoint() )
573 {
574 for( auto itr = aSecondary.CPoints().begin() + 1; itr != aSecondary.CPoints().end(); ++itr )
575 aPrimary.Append( *itr );
576 }
577 else
578 {
579 wxASSERT( aSecondary.CLastPoint() == aPrimary.CLastPoint() );
580
581 for( auto itr = aSecondary.CPoints().rbegin() + 1; itr != aSecondary.CPoints().rend(); ++itr )
582 aPrimary.Append( *itr );
583 }
584 }
585}
586
587
588void LENGTH_DELAY_CALCULATION::optimiseTracesInPads( const std::vector<LENGTH_DELAY_CALCULATION_ITEM*>& aPads,
589 const std::vector<LENGTH_DELAY_CALCULATION_ITEM*>& aLines )
590{
591 for( LENGTH_DELAY_CALCULATION_ITEM* padItem : aPads )
592 {
593 const PAD* pad = padItem->GetPad();
594
595 for( LENGTH_DELAY_CALCULATION_ITEM* lineItem : aLines )
596 {
597 // Ignore merged lines
598 if( lineItem->GetMergeStatus() != LENGTH_DELAY_CALCULATION_ITEM::MERGE_STATUS::MERGED_IN_USE )
599 continue;
600
601 const PCB_LAYER_ID pcbLayer = lineItem->GetStartLayer();
602 SHAPE_LINE_CHAIN& line = lineItem->GetLine();
603
604 OptimiseTraceInPad( line, pad, pcbLayer );
605 }
606 }
607}
608
609
611 const std::vector<LENGTH_DELAY_CALCULATION_ITEM*>& aVias, std::vector<LENGTH_DELAY_CALCULATION_ITEM*>& aLines,
612 std::map<VECTOR2I, std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*>>& aLinesPositionMap,
613 const std::map<VECTOR2I, std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*>>& aPadsPositionMap ) const
614{
615 for( LENGTH_DELAY_CALCULATION_ITEM* via : aVias )
616 {
617 const PCB_VIA* pcbVia = via->GetVia();
618
619 std::unordered_set<LENGTH_DELAY_CALCULATION_ITEM*> connectedLines;
620
621 const VECTOR2I viaPos = pcbVia->GetPosition();
622
623 auto exactMatch = aLinesPositionMap.find( viaPos );
624
625 if( exactMatch != aLinesPositionMap.end() )
626 connectedLines.insert( exactMatch->second.begin(), exactMatch->second.end() );
627
628 int maxRadius = 0;
629
631 [&]( PCB_LAYER_ID layer )
632 {
633 maxRadius = std::max( maxRadius, pcbVia->GetWidth( layer ) / 2 );
634 } );
635
636 const int64_t maxRadiusSq = static_cast<int64_t>( maxRadius ) * maxRadius;
637
638 for( const auto& [pos, lineSet] : aLinesPositionMap )
639 {
640 if( pos == viaPos )
641 continue;
642
643 if( ( pos - viaPos ).SquaredEuclideanNorm() > maxRadiusSq )
644 continue;
645
646 for( LENGTH_DELAY_CALCULATION_ITEM* lineItem : lineSet )
647 {
648 PCB_LAYER_ID layer = lineItem->GetStartLayer();
649 std::shared_ptr<SHAPE> shape = pcbVia->GetEffectiveShape( layer, FLASHING::ALWAYS_FLASHED );
650
651 if( shape && shape->Collide( pos, 0 ) )
652 connectedLines.insert( lineItem );
653 }
654 }
655
656 const PAD* coincidentPad = nullptr;
657
658 for( PAD* pad : m_board->GetConnectivity()->GetConnectedPads( pcbVia ) )
659 {
660 coincidentPad = pad;
661 break;
662 }
663
664 LSET spanLayers;
665
666 for( const LENGTH_DELAY_CALCULATION_ITEM* lineItem : connectedLines )
667 spanLayers.set( lineItem->GetStartLayer() );
668
669 if( coincidentPad )
670 {
671 PCB_LAYER_ID padSideLayer;
672
673 if( coincidentPad->GetAttribute() == PAD_ATTRIB::SMD || coincidentPad->GetAttribute() == PAD_ATTRIB::CONN )
674 {
675 padSideLayer = *coincidentPad->Padstack().LayerSet().CuStack().begin();
676 }
677 else
678 {
679 padSideLayer = coincidentPad->GetParentFootprint()->GetLayer();
680 }
681
682 spanLayers.set( padSideLayer );
683 }
684
685 wxLogTrace( wxT( "PNS_TUNE" ),
686 wxT( "optimiseVias: via@(%d,%d) connectedLines=%zu coincidentPad=%d "
687 "spanLayerCount=%d" ),
688 viaPos.x, viaPos.y, connectedLines.size(), coincidentPad ? 1 : 0,
689 static_cast<int>( spanLayers.count() ) );
690
691 const LSEQ cuStack = spanLayers.CuStack();
692
693 if( cuStack.empty() )
694 {
695 // Nothing connects to this via
696 via->SetLayers( pcbVia->GetLayer(), pcbVia->GetLayer() );
697 }
698 else if( cuStack.size() == 1 )
699 {
700 // Stub via
701 via->SetLayers( cuStack.front(), cuStack.front() );
702 }
703 else
704 {
705 // Signal transitions layers
706 via->SetLayers( cuStack.front(), cuStack.back() );
707 }
708 }
709}
710
711
713 const PCB_LAYER_ID aPcbLayer )
714{
715 // Only consider lines which terminate in the pad
716 if( aLine.CPoint( 0 ) != aPad->GetPosition() && aLine.CLastPoint() != aPad->GetPosition() )
717 return;
718
719 if( !aPad->FlashLayer( aPcbLayer ) )
720 return;
721
722 const auto& shape = aPad->GetEffectivePolygon( aPcbLayer, ERROR_INSIDE );
723
724 if( shape->Contains( aLine.CPoint( 0 ) ) )
725 clipLineToPad( aLine, aPad, aPcbLayer, true );
726 else if( shape->Contains( aLine.CLastPoint() ) )
727 clipLineToPad( aLine, aPad, aPcbLayer, false );
728}
729
730
732 bool aForward )
733{
734 wxASSERT( aLine.PointCount() >= 2 );
735
736 const int start = aForward ? 0 : aLine.PointCount() - 1;
737 const int delta = aForward ? 1 : -1;
738
739 std::shared_ptr<SHAPE> viaShape = aVia->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
740
741 if( !viaShape )
742 return;
743
744 const SHAPE_CIRCLE* viaCircle = dynamic_cast<const SHAPE_CIRCLE*>( viaShape.get() );
745
746 if( !viaCircle )
747 return;
748
749 // Find the first point OUTSIDE the via pad
750 int firstOutside = -1;
751 VECTOR2I intersectionPt;
752 bool hasIntersection = false;
753
754 for( int vertex = start + delta; aForward ? vertex < aLine.PointCount() : vertex >= 0; vertex += delta )
755 {
756 if( !viaShape->Collide( aLine.GetPoint( vertex ), 0 ) )
757 {
758 firstOutside = vertex;
759 int prevVertex = vertex - delta;
760
761 SEG seg( aLine.GetPoint( vertex ), aLine.GetPoint( prevVertex ) );
762
763 CIRCLE circle( viaCircle->GetCenter(), viaCircle->GetRadius() );
764
765 std::vector<VECTOR2I> pts = circle.Intersect( seg );
766
767 if( !pts.empty() )
768 {
769 // Pick the intersection closest to the outside vertex
770 VECTOR2I outside = aLine.GetPoint( vertex );
771 intersectionPt = pts[0];
772
773 for( size_t i = 1; i < pts.size(); i++ )
774 {
775 if( ( pts[i] - outside ).SquaredEuclideanNorm()
776 < ( intersectionPt - outside ).SquaredEuclideanNorm() )
777 {
778 intersectionPt = pts[i];
779 }
780 }
781
782 hasIntersection = true;
783 }
784
785 break;
786 }
787 }
788
789 if( firstOutside < 0 )
790 return;
791
792 SHAPE_LINE_CHAIN newChain;
793
794 if( aForward )
795 {
796 // viaCenter -> intersection -> [firstOutside to end]
797 newChain.Append( aVia->GetPosition() );
798
799 if( hasIntersection )
800 newChain.Append( intersectionPt );
801
802 newChain.Append( aLine.Slice( firstOutside, -1 ) );
803 }
804 else
805 {
806 // [0 to firstOutside] -> intersection -> viaCenter
807 newChain.Append( aLine.Slice( 0, firstOutside ) );
808
809 if( hasIntersection )
810 newChain.Append( intersectionPt );
811
812 newChain.Append( aVia->GetPosition() );
813 }
814
815 aLine = newChain;
816}
817
818
820{
821 std::shared_ptr<SHAPE> viaShape = aVia->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
822
823 if( !viaShape )
824 return;
825
826 if( viaShape->Collide( aLine.CPoint( 0 ), 0 ) )
827 clipLineToVia( aLine, aVia, aLayer, true );
828 else if( viaShape->Collide( aLine.CLastPoint(), 0 ) )
829 clipLineToVia( aLine, aVia, aLayer, false );
830}
831
832
834{
835 std::shared_ptr<SHAPE> shape = aVia->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
836 return shape && shape->Collide( aPoint, 0 );
837}
838
839
842{
843 if( const PCB_TRACK* track = dynamic_cast<const PCB_TRACK*>( aBoardItem ) )
844 {
845 if( track->Type() == PCB_VIA_T )
846 {
847 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
848
850 item.SetVia( via );
852 item.SetEffectiveNetClass( via->GetEffectiveNetClass() );
853
854 return item;
855 }
856
857 if( track->Type() == PCB_ARC_T )
858 {
859 const PCB_ARC* arcParent = static_cast<const PCB_ARC*>( track );
860 SHAPE_ARC shapeArc( arcParent->GetStart(), arcParent->GetMid(), arcParent->GetEnd(),
861 arcParent->GetWidth() );
862 SHAPE_LINE_CHAIN chainArc( shapeArc );
863
865 item.SetLine( chainArc );
866 item.SetLayers( track->GetLayer() );
867 item.SetEffectiveNetClass( arcParent->GetEffectiveNetClass() );
868
869 return item;
870 }
871
872 if( track->Type() == PCB_TRACE_T )
873 {
874 std::vector<VECTOR2I> points{ track->GetStart(), track->GetEnd() };
875 SHAPE_LINE_CHAIN shape( points );
876
878 item.SetLine( shape );
879 item.SetLayers( track->GetLayer() );
880 item.SetEffectiveNetClass( track->GetEffectiveNetClass() );
881
882 return item;
883 }
884 }
885 else if( const PAD* pad = dynamic_cast<const PAD*>( aBoardItem ) )
886 {
888 item.SetPad( pad );
889
890 const LSET& layers = pad->Padstack().LayerSet();
891 PCB_LAYER_ID firstLayer = UNDEFINED_LAYER;
892 PCB_LAYER_ID secondLayer = UNDEFINED_LAYER;
893
894 for( auto itr = layers.copper_layers_begin(); itr != layers.copper_layers_end(); ++itr )
895 {
896 if( firstLayer == UNDEFINED_LAYER )
897 firstLayer = *itr;
898 else
899 secondLayer = *itr;
900 }
901
902 item.SetLayers( firstLayer, secondLayer );
903 item.SetEffectiveNetClass( pad->GetEffectiveNetClass() );
904
905 return item;
906 }
907
908 return {};
909}
910
911
913 std::unique_ptr<TUNING_PROFILE_PARAMETERS_IFACE>&& aProvider )
914{
915 m_tuningProfileParameters = std::move( aProvider );
916}
917
918
923
924
925int64_t LENGTH_DELAY_CALCULATION::CalculateLengthForDelay( const int64_t aDesiredDelay,
926 const TUNING_PROFILE_GEOMETRY_CONTEXT& aCtx ) const
927{
928 return m_tuningProfileParameters->GetTrackLengthForPropagationDelay( aDesiredDelay, aCtx );
929}
930
931
933 const SHAPE_LINE_CHAIN& aShape, const TUNING_PROFILE_GEOMETRY_CONTEXT& aCtx ) const
934{
935 return m_tuningProfileParameters->CalculatePropagationDelayForShapeLineChain( aShape, aCtx );
936}
@ ERROR_INSIDE
BASE_SET & set(size_t pos)
Definition base_set.h:116
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual NETCLASS * GetEffectiveNetClass() const
Return the NETCLASS for this item.
FOOTPRINT * GetParentFootprint() const
Manage layers needed to make a physical board.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
int GetLayerDistance(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Calculate the distance (height) between the two given copper layers.
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:417
Lightweight class which holds a pad, via, or a routed trace outline.
void SetLine(const SHAPE_LINE_CHAIN &aLine)
Sets the source SHAPE_LINE_CHAIN of this item.
void SetVia(const PCB_VIA *aVia)
Sets the VIA associated with this item.
TYPE Type() const
Gets the routing item type.
void SetPad(const PAD *aPad)
Sets the parent PAD associated with this item.
void SetMergeStatus(const MERGE_STATUS aStatus)
Sets the MERGE_STATUS of this item.
SHAPE_LINE_CHAIN & GetLine() const
Gets the SHAPE_LINE_CHAIN associated with this item.
PCB_LAYER_ID GetStartLayer() const
Gets the start board layer for the proxied item.
void SetEffectiveNetClass(const NETCLASS *aNetClass)
Sets the effective net class for the item.
void CalculateViaLayers(const BOARD *aBoard)
Calculates active via payers for a proxied VIA object.
void SetLayers(const PCB_LAYER_ID aStart, const PCB_LAYER_ID aEnd=PCB_LAYER_ID::UNDEFINED_LAYER)
Sets the first and last layers associated with this item.
std::unique_ptr< TUNING_PROFILE_PARAMETERS_IFACE > m_tuningProfileParameters
The active provider of tuning profile parameters.
int64_t CalculateLengthForDelay(int64_t aDesiredDelay, const TUNING_PROFILE_GEOMETRY_CONTEXT &aCtx) const
Calculates the length of track required for the given delay in a specific geometry context.
int64_t CalculatePropagationDelayForShapeLineChain(const SHAPE_LINE_CHAIN &aShape, const TUNING_PROFILE_GEOMETRY_CONTEXT &aCtx) const
Gets the propagation delay for the given shape line chain.
static void clipLineToPad(SHAPE_LINE_CHAIN &aLine, const PAD *aPad, PCB_LAYER_ID aLayer, bool aForward=true)
Clips the given line to the minimal direct electrical length within the pad.
MERGE_POINT
Enum to describe whether track merging is attempted from the start or end of a track segment.
void inferViaInPad(const PAD *aPad, const LENGTH_DELAY_CALCULATION_ITEM &aItem, LENGTH_DELAY_STATS &aDetails) const
Infers if there is a via in the given pad.
static void optimiseTracesInPads(const std::vector< LENGTH_DELAY_CALCULATION_ITEM * > &aPads, const std::vector< LENGTH_DELAY_CALCULATION_ITEM * > &aLines)
Optimises the given set of items to minimise the electrical path length.
static void mergeLines(std::vector< LENGTH_DELAY_CALCULATION_ITEM * > &aLines, std::map< VECTOR2I, std::unordered_set< LENGTH_DELAY_CALCULATION_ITEM * > > &aLinesPositionMap)
Merges any lines (traces) that are contiguous, on one layer, and with no junctions.
int64_t CalculateDelay(std::vector< LENGTH_DELAY_CALCULATION_ITEM > &aItems, PATH_OPTIMISATIONS aOptimisations, const PAD *aStartPad=nullptr, const PAD *aEndPad=nullptr) const
Calculates the electrical propagation delay of the given items.
int64_t CalculateLength(std::vector< LENGTH_DELAY_CALCULATION_ITEM > &aItems, PATH_OPTIMISATIONS aOptimisations, const PAD *aStartPad=nullptr, const PAD *aEndPad=nullptr) const
Calculates the electrical length of the given items.
int StackupHeight(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Returns the stackup distance between the two given layers.
void SynchronizeTuningProfileProperties() const
Ensure time domain properties provider is synced with board / project settings if required.
static void OptimiseTraceInVia(SHAPE_LINE_CHAIN &aLine, const PCB_VIA *aVia, PCB_LAYER_ID aLayer)
Clips trace portions inside a VIA pad and replaces them with a straight-line segment from the VIA edg...
static void clipLineToVia(SHAPE_LINE_CHAIN &aLine, const PCB_VIA *aVia, PCB_LAYER_ID aLayer, bool aForward)
Clips the given line to the minimal direct electrical length within the via.
BOARD * m_board
The parent board for all items.
LENGTH_DELAY_CALCULATION_ITEM GetLengthCalculationItem(const BOARD_CONNECTED_ITEM *aBoardItem) const
Return a LENGTH_CALCULATION_ITEM constructed from the given BOARD_CONNECTED_ITEM.
void SetTuningProfileParametersProvider(std::unique_ptr< TUNING_PROFILE_PARAMETERS_IFACE > &&aProvider)
Sets the provider for tuning profile parameter resolution.
static void mergeShapeLineChains(SHAPE_LINE_CHAIN &aPrimary, const SHAPE_LINE_CHAIN &aSecondary, MERGE_POINT aMergePoint)
Merges two SHAPE_LINE_CHAINs where there is a shared endpoing.
static bool findArcPadIntersection(const SHAPE_ARC &aArc, const std::shared_ptr< SHAPE_POLY_SET > &aPadShape, const VECTOR2I &aInsidePoint, VECTOR2I &aIntersection)
Finds the intersection point between an arc and a pad shape.
LENGTH_DELAY_STATS CalculateLengthDetails(std::vector< LENGTH_DELAY_CALCULATION_ITEM > &aItems, PATH_OPTIMISATIONS aOptimisations, const PAD *aStartPad=nullptr, const PAD *aEndPad=nullptr, LENGTH_DELAY_LAYER_OPT aLayerOpt=LENGTH_DELAY_LAYER_OPT::NO_LAYER_DETAIL, LENGTH_DELAY_DOMAIN_OPT aDomain=LENGTH_DELAY_DOMAIN_OPT::NO_DELAY_DETAIL) const
Calculates the electrical length of the given items.
void optimiseVias(const std::vector< LENGTH_DELAY_CALCULATION_ITEM * > &aVias, std::vector< LENGTH_DELAY_CALCULATION_ITEM * > &aLines, std::map< VECTOR2I, std::unordered_set< LENGTH_DELAY_CALCULATION_ITEM * > > &aLinesPositionMap, const std::map< VECTOR2I, std::unordered_set< LENGTH_DELAY_CALCULATION_ITEM * > > &aPadsPositionMap) const
Optimises the via layers.
static bool IsPointInsideViaPad(const PCB_VIA *aVia, const VECTOR2I &aPoint, PCB_LAYER_ID aLayer)
Returns true if the given point falls inside VIA pad shape on the given layer.
static void OptimiseTraceInPad(SHAPE_LINE_CHAIN &aLine, const PAD *aPad, PCB_LAYER_ID aPcbLayer)
Optimises the given trace / line to minimise the electrical path length within the given pad.
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
copper_layers_iterator copper_layers_end() const
Definition lset.cpp:923
LSEQ CuStack() const
Return a sequence of copper layers in starting from the front/top and extending to the back/bottom.
Definition lset.cpp:263
copper_layers_iterator copper_layers_begin() const
Definition lset.cpp:917
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
void ForEachUniqueLayer(const std::function< void(PCB_LAYER_ID)> &aMethod) const
Runs the given callable for each active unique copper layer in this padstack, meaning F_Cu for MODE::...
const LSET & LayerSet() const
Definition padstack.h:322
Definition pad.h:55
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition pad.cpp:439
PAD_ATTRIB GetAttribute() const
Definition pad.h:563
VECTOR2I GetPosition() const override
Definition pad.h:209
const PADSTACK & Padstack() const
Definition pad.h:333
const std::shared_ptr< SHAPE_POLY_SET > & GetEffectivePolygon(PCB_LAYER_ID aLayer, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Definition pad.cpp:948
const VECTOR2I & GetMid() const
Definition pcb_track.h:290
const VECTOR2I & GetStart() const
Definition pcb_track.h:97
const VECTOR2I & GetEnd() const
Definition pcb_track.h:94
virtual int GetWidth() const
Definition pcb_track.h:91
VECTOR2I GetPosition() const override
Definition pcb_track.h:557
std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT) const override
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
const PADSTACK & Padstack() const
Definition pcb_track.h:406
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
int GetWidth() const override
Definition seg.h:42
bool Contains(const SEG &aSeg) const
Definition seg.h:324
int IntersectLine(const SEG &aSeg, std::vector< VECTOR2I > *aIpsBuffer) const
Find intersection points between this arc and aSeg, treating aSeg as an infinite line.
int GetRadius() const
const VECTOR2I GetCenter() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
virtual const VECTOR2I GetPoint(int aIndex) const override
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
const std::vector< SHAPE_ARC > & CArcs() const
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const SHAPE_LINE_CHAIN Slice(int aStartIndex, int aEndIndex) const
Return a subset of this line chain containing the [start_index, end_index] range of points.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
void Insert(size_t aVertex, const VECTOR2I &aP)
const std::vector< VECTOR2I > & CPoints() const
a few functions useful in geometry calculations.
@ ALWAYS_FLASHED
Always flashed for connectivity.
Definition layer_ids.h:186
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ B_Cu
Definition layer_ids.h:65
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:64
LENGTH_DELAY_DOMAIN_OPT
Enum which controls the calculation domain of the length / delay calculation methods.
LENGTH_DELAY_LAYER_OPT
Enum which controls the level of detail returned by the length / delay calculation methods.
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:100
Holds length measurement result details and statistics.
std::unique_ptr< std::map< PCB_LAYER_ID, int64_t > > LayerDelays
int64_t TotalLength() const
Calculates the total electrical length for this set of statistics.
std::unique_ptr< std::map< PCB_LAYER_ID, int64_t > > LayerLengths
int64_t TotalDelay() const
Calculates the total electrical propagation delay for this set of statistics.
Struct to control which optimisations the length calculation code runs on the given path objects.
bool InferViaInPad
Determines if there is a via-in-pad present on the board but not in the item set.
bool OptimiseVias
Optimise vias for electrical length calculations, including effective via span and trace clipping ins...
bool MergeTracks
Merges all contiguous (end-to-end, same layer) tracks.
bool OptimiseTracesInPads
Optimises the electrical length of tracks within pads.
A data structure to contain basic geometry data which can affect signal propagation calculations.
const NETCLASS * NetClass
The net class this track belongs to.
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
int delta
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:94
@ 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