KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pns_dp_meander_placer.cpp
Go to the documentation of this file.
1/*
2 * KiRouter - a push-and-(sometimes-)shove PCB router
3 *
4 * Copyright (C) 2013-2014 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * Author: Tomasz Wlostowski <[email protected]>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include <optional>
23
24#include <core/typeinfo.h>
25
27
28#include "pns_node.h"
29#include "pns_itemset.h"
30#include "pns_topology.h"
32#include "pns_diff_pair.h"
33#include "pns_router.h"
34#include "pns_solid.h"
35
36
37namespace PNS {
38
40 MEANDER_PLACER_BASE( aRouter )
41{
42 m_world = nullptr;
43 m_currentNode = nullptr;
44
47
50
51 // Init temporary variables (do not leave uninitialized members)
52 m_initialSegment = nullptr;
53 m_lastLength = 0;
54 m_lastDelay = 0;
56
57 m_netClass = nullptr;
58}
59
60
64
65
67{
68 return m_currentTraceP;
69}
70
71
76
77
78NODE* DP_MEANDER_PLACER::CurrentNode( bool aLoopsRemoved ) const
79{
80 if( !m_currentNode )
81 return m_world;
82
83 return m_currentNode;
84}
85
86
87bool DP_MEANDER_PLACER::Start( const VECTOR2I& aP, ITEM* aStartItem )
88{
89 if( !aStartItem || !aStartItem->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
90 {
91 Router()->SetFailureReason( _( "Please select a track whose length you want to tune." ) );
92 return false;
93 }
94
95 m_initialSegment = static_cast<LINKED_ITEM*>( aStartItem );
96 m_currentNode = nullptr;
98
99 m_world = Router()->GetWorld()->Branch();
100
101 TOPOLOGY topo( m_world );
102
104 {
105 Router()->SetFailureReason( _( "Unable to find complementary differential pair "
106 "net for length tuning. Make sure the names of the nets "
107 "belonging to a differential pair end with either _N/_P "
108 "or +/-." ) );
109 return false;
110 }
111
112 if( m_originPair.Gap() < 0 )
113 m_originPair.SetGap( Router()->Sizes().DiffPairGap() );
114
115 if( !m_originPair.PLine().SegmentCount() || !m_originPair.NLine().SegmentCount() )
116 return false;
117
118 m_tunedPathP = topo.AssembleTuningPath( Router()->GetInterface(), m_originPair.PLine().GetLink( 0 ), &m_startPad_p,
119 &m_endPad_p );
120
123
124 if( m_startPad_p )
125 {
126 m_padToDieLengthP += m_startPad_p->GetPadToDie();
127 m_padToDieDelayP += m_startPad_p->GetPadToDieDelay();
128 }
129
130 if( m_endPad_p )
131 {
132 m_padToDieLengthP += m_endPad_p->GetPadToDie();
133 m_padToDieDelayP += m_endPad_p->GetPadToDieDelay();
134 }
135
136 m_tunedPathN = topo.AssembleTuningPath( Router()->GetInterface(), m_originPair.NLine().GetLink( 0 ), &m_startPad_n,
137 &m_endPad_n );
138
141
142 if( m_startPad_n )
143 {
144 m_padToDieLengthN += m_startPad_n->GetPadToDie();
145 m_padToDieDelayN += m_startPad_n->GetPadToDieDelay();
146 }
147
148 if( m_endPad_n )
149 {
150 m_padToDieLengthN += m_endPad_n->GetPadToDie();
151 m_padToDieDelayN += m_endPad_n->GetPadToDieDelay();
152 }
153
154 m_world->Remove( m_originPair.PLine() );
155 m_world->Remove( m_originPair.NLine() );
156
158
159 const BOARD_CONNECTED_ITEM* conItem = static_cast<BOARD_CONNECTED_ITEM*>( aStartItem->GetSourceItem() );
160 m_netClass = conItem->GetEffectiveNetClass();
161
163
164 return true;
165}
166
167
171
172
174{
177 return std::max( totalP, totalN );
178}
179
180
182{
183 const int64_t totalP = m_padToDieDelayP + lineDelay( m_tunedPathP, m_startPad_p, m_endPad_p );
184 const int64_t totalN = m_padToDieDelayP + lineDelay( m_tunedPathN, m_startPad_n, m_endPad_n );
185 return std::max( totalP, totalN );
186}
187
188
190{
191 const VECTOR2I a( ( aCoupledSegs.coupledP.A + aCoupledSegs.coupledN.A ) / 2 );
192 const VECTOR2I b( ( aCoupledSegs.coupledP.B + aCoupledSegs.coupledN.B ) / 2 );
193
194 return SEG( a, b );
195}
196
197
199{
200 VECTOR2I midp = ( aPair.coupledP.A + aPair.coupledN.A ) / 2;
201
202 //DrawDebugPoint(midp, 6);
203
204 return aPair.coupledP.Side( midp ) > 0;
205}
206
207
208bool DP_MEANDER_PLACER::Move( const VECTOR2I& aP, ITEM* aEndItem )
209{
211
212 if( m_currentStart == aP )
213 return false;
214
215 DIFF_PAIR::COUPLED_SEGMENTS_VEC coupledSegments;
216
217 if( m_currentNode )
218 delete m_currentNode;
219
220 m_currentNode = m_world->Branch();
221
222 SHAPE_LINE_CHAIN preP, tunedP, postP;
223 SHAPE_LINE_CHAIN preN, tunedN, postN;
224
225 m_originPair.CP().Split( m_currentStart, aP, preP, tunedP, postP );
226 m_originPair.CN().Split( m_currentStart, aP, preN, tunedN, postN );
227
228 // Bail out early if the tuned sections are empty (issue #22041). This can happen when the
229 // split points are too close together or outside the line chain.
230 if( tunedP.PointCount() == 0 || tunedN.PointCount() == 0 )
231 {
236
237 return false;
238 }
239
240 auto updateStatus =
241 [&]()
242 {
243 if( m_lastLength > m_settings.m_targetLength.Max() )
245 else if( m_lastLength < m_settings.m_targetLength.Min() )
247 else
249 };
250
251 DIFF_PAIR tuned( m_originPair );
252
253 tuned.SetShape( tunedP, tunedN );
254
255 tuned.CoupledSegmentPairs( coupledSegments );
256
257 if( coupledSegments.size() == 0 )
258 {
259 // Tuning started at an uncoupled area of the DP; we won't get a valid result until the
260 // cursor is moved far enough along a coupled area. Prevent the track from disappearing and
261 // the length from being zero by just using the original.
265 updateStatus();
266
267 return false;
268 }
269
270 m_result = MEANDERED_LINE( this, true );
271 m_result.SetWidth( tuned.Width() );
272
273 int offset = ( tuned.Gap() + tuned.Width() ) / 2;
274
275 if( pairOrientation( coupledSegments[0] ) )
276 offset *= -1;
277
278 m_result.SetBaselineOffset( offset );
279
280 for( const ITEM* item : m_tunedPathP.CItems() )
281 {
282 if( const LINE* l = dyn_cast<const LINE*>( item ) )
283 {
284 PNS_DBG( Dbg(), AddShape, &l->CLine(), YELLOW, 10000, wxT( "tuned-path-p" ) );
285
286 m_router->GetInterface()->DisplayPathLine( l->CLine(), 1 );
287 }
288 }
289
290 for( const ITEM* item : m_tunedPathN.CItems() )
291 {
292 if( const LINE* l = dyn_cast<const LINE*>( item ) )
293 {
294 PNS_DBG( Dbg(), AddShape, &l->CLine(), YELLOW, 10000, wxT( "tuned-path-n" ) );
295
296 m_router->GetInterface()->DisplayPathLine( l->CLine(), 1 );
297 }
298 }
299
300 int curIndexP = 0, curIndexN = 0;
301
302 for( const DIFF_PAIR::COUPLED_SEGMENTS& sp : coupledSegments )
303 {
304 SEG base = baselineSegment( sp );
305 bool side = false;
306
307 if( m_settings.m_initialSide == 0 )
308 side = base.Side( aP ) < 0;
309 else
310 side = m_settings.m_initialSide < 0;
311
312 PNS_DBG( Dbg(), AddShape, base, GREEN, 10000, wxT( "dp-baseline" ) );
313
314 while( sp.indexP >= curIndexP && curIndexP != -1 )
315 {
316 if( tunedP.IsArcSegment( curIndexP ) )
317 {
318 ssize_t arcIndex = tunedP.ArcIndex( curIndexP );
319
320 m_result.AddArcAndPt( tunedP.Arc( arcIndex ), tunedN.CPoint( curIndexN ) );
321 }
322 else
323 {
324 m_result.AddCorner( tunedP.CPoint( curIndexP ), tunedN.CPoint( curIndexN ) );
325 }
326
327 curIndexP = tunedP.NextShape( curIndexP );
328 }
329
330 while( sp.indexN >= curIndexN && curIndexN != -1 )
331 {
332 if( tunedN.IsArcSegment( curIndexN ) )
333 {
334 ssize_t arcIndex = tunedN.ArcIndex( curIndexN );
335
336 m_result.AddPtAndArc( tunedP.CPoint( sp.indexP ), tunedN.Arc( arcIndex ) );
337 }
338 else
339 {
340 m_result.AddCorner( tunedP.CPoint( sp.indexP ), tunedN.CPoint( curIndexN ) );
341 }
342
343 curIndexN = tunedN.NextShape( curIndexN );
344 }
345
346 m_result.MeanderSegment( base, side );
347 }
348
349 while( curIndexP < tunedP.PointCount() && curIndexP != -1 )
350 {
351 if( tunedP.IsArcSegment( curIndexP ) )
352 {
353 ssize_t arcIndex = tunedP.ArcIndex( curIndexP );
354
355 m_result.AddArcAndPt( tunedP.Arc( arcIndex ), tunedN.CPoint( curIndexN ) );
356 }
357 else
358 {
359 m_result.AddCorner( tunedP.CPoint( curIndexP ), tunedN.CPoint( curIndexN ) );
360 }
361
362 curIndexP = tunedP.NextShape( curIndexP );
363 }
364
365 while( curIndexN < tunedN.PointCount() && curIndexN != -1 )
366 {
367 if( tunedN.IsArcSegment( curIndexN ) )
368 {
369 ssize_t arcIndex = tunedN.ArcIndex( curIndexN );
370
371 m_result.AddPtAndArc( tunedP.CLastPoint(), tunedN.Arc( arcIndex ) );
372 }
373 else
374 {
375 m_result.AddCorner( tunedP.CLastPoint(), tunedN.CPoint( curIndexN ) );
376 }
377
378 curIndexN = tunedN.NextShape( curIndexN );
379 }
380
381 m_result.AddCorner( tunedP.CLastPoint(), tunedN.CLastPoint() );
382
383 long long int dpLen = origPathLength();
384 int64_t dpDelay = origPathDelay();
385
387
388 if( dpLen > m_settings.m_targetLength.Max() )
389 {
391 m_lastLength = dpLen;
392 m_lastDelay = dpDelay;
393 }
394 else
395 {
396 m_lastLength = dpLen - std::max( tunedP.Length(), tunedN.Length() );
397
398 if( m_settings.m_isTimeDomain )
399 {
400 int64_t tunedPDelay = m_router->GetInterface()->CalculateDelayForShapeLineChain(
401 tunedP, GetOriginPair().Width(), true, GetOriginPair().Gap(), m_router->GetCurrentLayer(),
402 m_netClass );
403 int64_t tunedNDelay = m_router->GetInterface()->CalculateDelayForShapeLineChain(
404 tunedN, GetOriginPair().Width(), true, GetOriginPair().Gap(), m_router->GetCurrentLayer(),
405 m_netClass );
406
407 m_lastDelay = dpDelay - std::max( tunedPDelay, tunedNDelay );
408 }
409
410 tuneLineLength( m_result, m_settings.m_targetLength.Opt() - dpLen );
411 }
412
413 if( m_lastStatus != TOO_LONG )
414 {
415 tunedP.Clear();
416 tunedN.Clear();
417
418 for( MEANDER_SHAPE* m : m_result.Meanders() )
419 {
420 if( m->Type() != MT_EMPTY )
421 {
422 tunedP.Append( m->CLine( 0 ) );
423 tunedN.Append( m->CLine( 1 ) );
424 }
425 }
426
427 m_lastLength += std::max( tunedP.Length(), tunedN.Length() );
428
429 if( m_settings.m_isTimeDomain )
430 {
431 int64_t tunedPDelay = m_router->GetInterface()->CalculateDelayForShapeLineChain(
432 tunedP, GetOriginPair().Width(), true, GetOriginPair().Gap(), m_router->GetCurrentLayer(),
433 m_netClass );
434 int64_t tunedNDelay = m_router->GetInterface()->CalculateDelayForShapeLineChain(
435 tunedN, GetOriginPair().Width(), true, GetOriginPair().Gap(), m_router->GetCurrentLayer(),
436 m_netClass );
437
438 m_lastDelay += std::max( tunedPDelay, tunedNDelay );
439 }
440
441 updateStatus();
442 }
443
444 m_finalShapeP.Clear();
445 m_finalShapeN.Clear();
446
447 if( m_settings.m_keepEndpoints )
448 {
449 preP.Simplify();
450 tunedP.Simplify();
451 postP.Simplify();
452
453 m_finalShapeP.Append( preP );
454 m_finalShapeP.Append( tunedP );
455 m_finalShapeP.Append( postP );
456
457 preN.Simplify();
458 tunedN.Simplify();
459 postN.Simplify();
460
461 m_finalShapeN.Append( preN );
462 m_finalShapeN.Append( tunedN );
463 m_finalShapeN.Append( postN );
464 }
465 else
466 {
467 m_finalShapeP.Append( preP );
468 m_finalShapeP.Append( tunedP );
469 m_finalShapeP.Append( postP );
470 m_finalShapeP.Simplify();
471
472 m_finalShapeN.Append( preN );
473 m_finalShapeN.Append( tunedN );
474 m_finalShapeN.Append( postN );
475 m_finalShapeN.Simplify();
476 }
477
478 return true;
479}
480
481
482bool DP_MEANDER_PLACER::FixRoute( const VECTOR2I& aP, ITEM* aEndItem, bool aForceFinish )
483{
484 LINE lP( m_originPair.PLine(), m_finalShapeP );
485 LINE lN( m_originPair.NLine(), m_finalShapeN );
486
487 m_currentNode->Add( lP );
488 m_currentNode->Add( lN );
489
491
492 return true;
493}
494
495
497{
498 m_world->KillChildren();
499 return true;
500}
501
502
504{
505 return m_originPair.CP().SegmentCount() > 0 || m_originPair.CN().SegmentCount() > 0;
506}
507
508
510{
511 if( m_currentNode )
513
514 m_currentNode = nullptr;
515 return true;
516}
517
518
520{
521 LINE l1( m_originPair.PLine(), aShape->CLine( 0 ) );
522 LINE l2( m_originPair.NLine(), aShape->CLine( 1 ) );
523
524 if( m_currentNode->CheckColliding( &l1 ) )
525 return false;
526
527 if( m_currentNode->CheckColliding( &l2 ) )
528 return false;
529
530 int w = aShape->Width();
531 int clearance = w + w * 3;
532
533 return m_result.CheckSelfIntersections( aShape, clearance );
534}
535
536
538{
541
542 ITEM_SET traces;
543
544 traces.Add( &m_currentTraceP );
545 traces.Add( &m_currentTraceN );
546
547 return traces;
548}
549
550
552{
553 ITEM_SET lines;
554
555 for( ITEM* item : m_tunedPathN )
556 lines.Add( item );
557
558 for( ITEM* item : m_tunedPathP )
559 lines.Add( item );
560
561 return lines;
562}
563
564
566{
567 return m_currentStart;
568}
569
570
572{
573 return m_currentEnd;
574}
575
576
578{
579 return m_initialSegment->Layers().Start();
580}
581
582
584{
585 if( m_lastLength )
586 return m_lastLength;
587 else
588 return origPathLength();
589}
590
591
593{
594 if( m_lastDelay )
595 return m_lastDelay;
596 else
597 return origPathDelay();
598}
599
600
605
606
607const std::vector<NET_HANDLE> DP_MEANDER_PLACER::CurrentNets() const
608{
609 std::vector<NET_HANDLE> rv;
610 rv.push_back( m_originPair.NetP() );
611 rv.push_back( m_originPair.NetN() );
612 return rv;
613}
614
615
617{
618 // If this is a time domain tuning, calculate the target length for the desired total delay
619 if( m_settings.m_isTimeDomain )
620 {
621 const int64_t curDelay = origPathDelay();
622
623 const int64_t desiredDelayMin = m_settings.m_targetLengthDelay.Min();
624 const int64_t desiredDelayOpt = m_settings.m_targetLengthDelay.Opt();
625 const int64_t desiredDelayMax = m_settings.m_targetLengthDelay.Max();
626
627 const int64_t delayDifferenceOpt = desiredDelayOpt - curDelay;
628
629 const int64_t curLength = origPathLength();
630 const int64_t lengthDiffMin = m_router->GetInterface()->CalculateLengthForDelay(
631 desiredDelayOpt - desiredDelayMin, GetOriginPair().Width(), true, GetOriginPair().Gap(),
632 m_router->GetCurrentLayer(), m_netClass );
633 int64_t lengthDiffOpt = m_router->GetInterface()->CalculateLengthForDelay(
634 std::abs( delayDifferenceOpt ), GetOriginPair().Width(), true, GetOriginPair().Gap(),
635 m_router->GetCurrentLayer(), m_netClass );
636 const int64_t lengthDiffMax = m_router->GetInterface()->CalculateLengthForDelay(
637 desiredDelayMax - desiredDelayOpt, GetOriginPair().Width(), true, GetOriginPair().Gap(),
638 m_router->GetCurrentLayer(), m_netClass );
639
640 lengthDiffOpt = delayDifferenceOpt > 0 ? lengthDiffOpt : -lengthDiffOpt;
641
642 m_settings.m_targetLength.SetMin( curLength + lengthDiffOpt - lengthDiffMin );
643 m_settings.m_targetLength.SetOpt( curLength + lengthDiffOpt );
644 m_settings.m_targetLength.SetMax( curLength + lengthDiffOpt + lengthDiffMax );
645 }
646}
647}
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.
ROUTER * Router() const
Return current router settings.
ROUTER * m_router
DEBUG_DECORATOR * Dbg() const
Basic class for a differential pair.
int Width() const
std::vector< COUPLED_SEGMENTS > COUPLED_SEGMENTS_VEC
void SetShape(const SHAPE_LINE_CHAIN &aP, const SHAPE_LINE_CHAIN &aN, bool aSwapLanes=false)
int Gap() const
void CoupledSegmentPairs(COUPLED_SEGMENTS_VEC &aPairs) const
bool Start(const VECTOR2I &aP, ITEM *aStartItem) override
Start routing a single track at point aP, taking item aStartItem as anchor (unless NULL).
bool CheckFit(MEANDER_SHAPE *aShape) override
Checks if it's OK to place the shape aShape (i.e.
const ITEM_SET Traces() override
Function Traces()
bool pairOrientation(const DIFF_PAIR::COUPLED_SEGMENTS &aPair)
void calculateTimeDomainTargets()
Current routing start point (end of tail, beginning of head).
VECTOR2I m_currentStart
Current world state.
bool FixRoute(const VECTOR2I &aP, ITEM *aEndItem, bool aForceFinish=false) override
Commit the currently routed track to the parent node, taking aP as the final end point and aEndItem a...
int CurrentLayer() const override
Function CurrentLayer()
TUNING_STATUS TuningStatus() const override
Return the tuning status (too short, too long, etc.) of the trace(s) being tuned.
long long int TuningLengthResult() const override
Return the resultant length or skew of the tuned traces.
const SEG baselineSegment(const DIFF_PAIR::COUPLED_SEGMENTS &aCoupledSegs)
const DIFF_PAIR & GetOriginPair()
const ITEM_SET TunedPath() override
bool HasPlacedAnything() const override
bool Move(const VECTOR2I &aP, ITEM *aEndItem) override
Move the end of the currently routed trace to the point aP, taking aEndItem as anchor (if not NULL).
const VECTOR2I & CurrentEnd() const override
Function CurrentEnd()
const VECTOR2I & CurrentStart() const override
Function CurrentStart()
int64_t TuningDelayResult() const override
Return the resultant delay or skew of the tuned traces.
long long int origPathLength() const
const std::vector< NET_HANDLE > CurrentNets() const override
Function CurrentNets()
NODE * CurrentNode(bool aLoopsRemoved=false) const override
Return the most recent world state.
void Add(const LINE &aLine)
Base class for PNS router board items.
Definition pns_item.h:98
BOARD_ITEM * GetSourceItem() const
Definition pns_item.h:202
bool OfKind(int aKindMask) const
Definition pns_item.h:181
Represents a track on a PCB, connecting two non-trivial joints (that is, vias, pads,...
Definition pns_line.h:62
Represent a set of meanders fitted over a single or two lines.
void tuneLineLength(MEANDERED_LINE &aTuned, long long int aElongation)
Take a set of meanders in aTuned and tunes their length to extend the original line length by aElonga...
TUNING_STATUS
< Result of the length tuning operation
int m_currentWidth
Meander settings.
MEANDER_SETTINGS m_settings
The current end point.
int64_t lineDelay(const ITEM_SET &aLine, const SOLID *aStartPad, const SOLID *aEndPad) const
Calculate the total delay of the line represented by an item set (tracks and vias)
NODE * m_world
Width of the meandered trace(s).
VECTOR2I getSnappedStartPoint(LINKED_ITEM *aStartItem, VECTOR2I aStartPoint)
long long int lineLength(const ITEM_SET &aLine, const SOLID *aStartPad, const SOLID *aEndPad) const
Calculate the total length of the line represented by an item set (tracks and vias)
int Width() const
const SHAPE_LINE_CHAIN & CLine(int aShape) const
Keep the router "world" - i.e.
Definition pns_node.h:240
NODE * Branch()
Create a lightweight copy (called branch) of self that tracks the changes (added/removed items) wrs t...
Definition pns_node.cpp:157
void SetFailureReason(const wxString &aReason)
Definition pns_router.h:231
void CommitRouting()
NODE * GetWorld() const
Definition pns_router.h:182
const DIFF_PAIR AssembleDiffPair(SEGMENT *aStart)
const ITEM_SET AssembleTuningPath(ROUTER_IFACE *aRouterIface, ITEM *aStart, SOLID **aStartPad=nullptr, SOLID **aEndPad=nullptr)
Like AssembleTrivialPath, but follows the track length algorithm, which discards segments that are fu...
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
int Side(const VECTOR2I &aP) const
Determine on which side of directed line passing via segment ends point aP lies.
Definition seg.h:143
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
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.
void Clear()
Remove all points from the line chain.
void Simplify(int aTolerance=0)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
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 VECTOR2I & CLastPoint() const
Return the last point in the line chain.
bool IsArcSegment(size_t aSegment) const
long long int Length() const
Return length of the line chain in Euclidean metric.
@ GREEN
Definition color4d.h:57
@ YELLOW
Definition color4d.h:67
#define _(s)
Push and Shove diff pair dimensions (gap) settings dialog.
@ MT_EMPTY
Definition pns_meander.h:49
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
#define PNS_DBG(dbg, method,...)
int clearance
Casted dyn_cast(From aObject)
A lightweight dynamic downcast.
Definition typeinfo.h:61
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695