KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pns_basics.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, see <https://www.gnu.org/licenses/>.
18 */
19
22#include <optional>
23
24#include <pcbnew/board.h>
25#include <pcbnew/pad.h>
26#include <pcbnew/pcb_track.h>
29
30#include <lset.h>
31#include <padstack.h>
32
34#include <geometry/shape_arc.h>
35#include <geometry/eda_angle.h>
37#include <router/pns_dragger.h>
39#include <router/pns_arc.h>
40#include <router/pns_line.h>
41#include <router/pns_item.h>
43#include <router/pns_node.h>
44#include <router/pns_router.h>
45#include <router/pns_segment.h>
46#include <router/pns_shove.h>
48#include <router/pns_solid.h>
49#include <router/pns_topology.h>
50#include <router/pns_via.h>
51
52static bool isCopper( const PNS::ITEM* aItem )
53{
54 if( !aItem )
55 return false;
56
57 BOARD_ITEM* parent = aItem->Parent();
58
59 if( parent && parent->Type() == PCB_PAD_T )
60 {
61 PAD* pad = static_cast<PAD*>( parent );
62
63 if( pad->IsAperturePad() || pad->IsNPTHWithNoCopper() )
64 return false;
65 }
66
67 return true;
68}
69
70
71static bool isHole( const PNS::ITEM* aItem )
72{
73 if( !aItem )
74 return false;
75
76 return aItem->OfKind( PNS::ITEM::HOLE_T );
77}
78
79
80static bool isEdge( const PNS::ITEM* aItem )
81{
82 if( !aItem )
83 return false;
84
85 const BOARD_ITEM *parent = aItem->BoardItem();
86
87 return parent && ( parent->IsOnLayer( Edge_Cuts ) || parent->IsOnLayer( Margin ) );
88}
89
90
92{
93public:
97
99
100 virtual int Clearance( const PNS::ITEM* aA, const PNS::ITEM* aB,
101 bool aUseClearanceEpsilon = true ) override
102 {
103 PNS::CONSTRAINT constraint;
104 int rv = 0;
105 PNS_LAYER_RANGE layers;
106
107 if( !aB )
108 layers = aA->Layers();
109 else if( isEdge( aA ) )
110 layers = aB->Layers();
111 else if( isEdge( aB ) )
112 layers = aA->Layers();
113 else
114 layers = aA->Layers().Intersection( aB->Layers() );
115
116 // Normalize layer range (no -1 magic numbers)
118
119 // electrical clearances are net-aware; physical clearances are net-blind; same-net or
120 // free-pad pairs with no positive physical rule fall back to -1.
121 const bool sameNet = aA && aB && aA->Net() && aA->Net() == aB->Net();
122 const bool freePad = aA && aB && ( aA->IsFreePad() || aB->IsFreePad() );
123
124 for( int layer = layers.Start(); layer <= layers.End(); ++layer )
125 {
126 if( !sameNet && !freePad )
127 {
128 if( isHole( aA ) && isHole( aB ) )
129 {
130 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_TO_HOLE, aA, aB, layer, &constraint ) )
131 {
132 if( constraint.m_Value.Min() > rv )
133 rv = constraint.m_Value.Min();
134 }
135 }
136 else if( isHole( aA ) || isHole( aB ) )
137 {
138 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_CLEARANCE, aA, aB, layer, &constraint ) )
139 {
140 if( constraint.m_Value.Min() > rv )
141 rv = constraint.m_Value.Min();
142 }
143 }
144 else if( isCopper( aA ) && ( !aB || isCopper( aB ) ) )
145 {
146 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_CLEARANCE, aA, aB, layer, &constraint ) )
147 {
148 if( constraint.m_Value.Min() > rv )
149 rv = constraint.m_Value.Min();
150 }
151 }
152 else if( isEdge( aA ) || ( aB && isEdge( aB ) ) )
153 {
154 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_EDGE_CLEARANCE, aA, aB, layer, &constraint ) )
155 {
156 if( constraint.m_Value.Min() > rv )
157 rv = constraint.m_Value.Min();
158 }
159 }
160 }
161
162 if( isHole( aA ) || isHole( aB ) )
163 {
164 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_PHYSICAL_HOLE_CLEARANCE, aA, aB, layer, &constraint ) )
165 {
166 if( constraint.m_Value.Min() > rv )
167 rv = constraint.m_Value.Min();
168 }
169 }
170
171 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_PHYSICAL_CLEARANCE, aA, aB, layer, &constraint ) )
172 {
173 if( constraint.m_Value.Min() > rv )
174 rv = constraint.m_Value.Min();
175 }
176 }
177
178 if( ( sameNet || freePad ) && rv == 0 )
179 rv = -1;
180
181 return rv;
182 }
183
185
186 virtual PNS::NET_HANDLE DpCoupledNet( PNS::NET_HANDLE aNet ) override { return nullptr; }
187 virtual int DpNetPolarity( PNS::NET_HANDLE aNet ) override { return -1; }
188
189 virtual bool DpNetPair( const PNS::ITEM* aItem, PNS::NET_HANDLE& aNetP,
190 PNS::NET_HANDLE& aNetN ) override
191 {
192 return false;
193 }
194
195 virtual int NetCode( PNS::NET_HANDLE aNet ) override
196 {
197 return -1;
198 }
199
200 virtual wxString NetName( PNS::NET_HANDLE aNet ) override
201 {
202 return wxEmptyString;
203 }
204
205 virtual bool QueryConstraint( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA,
206 const PNS::ITEM* aItemB, int aLayer,
207 PNS::CONSTRAINT* aConstraint ) override
208 {
209 ITEM_KEY key;
210
211 key.a = aItemA;
212 key.b = aItemB;
213 key.type = aType;
214
215 auto it = m_ruleMap.find( key );
216
217 if( it == m_ruleMap.end() )
218 {
219 int cl;
220 switch( aType )
221 {
227 return false;
229 break;
232 return false;
234 break;
235 default: return false;
236 }
237
238 //printf("GetDef %s %s %d cl %d\n", aItemA->KindStr().c_str(), aItemB->KindStr().c_str(), aType, cl );
239
240 aConstraint->m_Type = aType;
241 aConstraint->m_Value.SetMin( cl );
242
243 return true;
244 }
245 else
246 {
247 *aConstraint = it->second;
248 }
249
250 return true;
251 }
252
253 int ClearanceEpsilon() const override { return m_clearanceEpsilon; }
254
255 struct ITEM_KEY
256 {
257 const PNS::ITEM* a = nullptr;
258 const PNS::ITEM* b = nullptr;
260
261 bool operator==( const ITEM_KEY& other ) const
262 {
263 return a == other.a && b == other.b && type == other.type;
264 }
265
266 bool operator<( const ITEM_KEY& other ) const
267 {
268 if( a < other.a )
269 {
270 return true;
271 }
272 else if ( a == other.a )
273 {
274 if( b < other.b )
275 return true;
276 else if ( b == other.b )
277 return type < other.type;
278 }
279
280 return false;
281 }
282 };
283
284 bool IsInNetTie( const PNS::ITEM* aA ) override { return false; }
285
286 bool IsNetTieExclusion( const PNS::ITEM* aItem, const VECTOR2I& aCollisionPos,
287 const PNS::ITEM* aCollidingItem ) override
288 {
289 return false;
290 }
291
292 bool IsDrilledHole( const PNS::ITEM* aItem ) override { return false; }
293
294 bool IsNonPlatedSlot( const PNS::ITEM* aItem ) override { return false; }
295
296 bool IsKeepout( const PNS::ITEM* aObstacle, const PNS::ITEM* aItem, bool* aEnforce ) override
297 {
298 return false;
299 }
300
301 void AddMockRule( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA, const PNS::ITEM* aItemB,
302 PNS::CONSTRAINT& aConstraint )
303 {
304 ITEM_KEY key;
305
306 key.a = aItemA;
307 key.b = aItemB;
308 key.type = aType;
309
310 m_ruleMap[key] = aConstraint;
311 }
312
313 int m_defaultClearance = 200000;
314 int m_defaultHole2Hole = 220000;
316 int m_defaultPhysicalClearance = 0; // 0 means "rule does not match this pair"
317 int m_defaultPhysicalHoleClearance = 0; // 0 means "rule does not match this pair"
319
320private:
321 std::map<ITEM_KEY, PNS::CONSTRAINT> m_ruleMap;
323};
324
325struct PNS_TEST_FIXTURE;
326
328{
329public:
331 m_testFixture( aFixture )
332 {}
333
335
336 void HideItem( PNS::ITEM* aItem ) override {};
337 void DisplayItem( const PNS::ITEM* aItem, int aClearance, bool aEdit = false,
338 int aFlags = 0 ) override {};
340
341 bool TestInheritTrackWidth( PNS::ITEM* aItem, int* aInheritedWidth,
342 const VECTOR2I& aStartPosition = VECTOR2I() )
343 {
344 m_startLayer = aItem->Layer();
345
346 return inheritTrackWidthAndDpGap( aItem, aStartPosition, aInheritedWidth, nullptr );
347 }
348
349 std::unique_ptr<PNS::VIA> TestSyncVia( PCB_VIA* aVia ) { return syncVia( aVia ); }
350
351private:
353};
354
355
357{
359 {
360 m_router = new PNS::ROUTER;
361 m_iface = new MOCK_PNS_KICAD_IFACE( this );
362 m_router->SetInterface( m_iface );
363 }
364
366 {
367 delete m_router;
368 delete m_iface;
369 }
370
375 //std::unique_ptr<BOARD> m_board;
376};
377
378
383
384
385BOOST_FIXTURE_TEST_CASE( PNSShoveOwnsRootLineHistory, PNS_TEST_FIXTURE )
386{
387 PNS::NODE world;
388 world.SetRuleResolver( &m_ruleResolver );
389
390 PNS::SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ) ),
391 reinterpret_cast<PNS::NET_HANDLE>( 1 ) );
392 segment.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
393
394 PNS::LINE line;
395 line.Line().Append( VECTOR2I( 0, 0 ) );
396 line.Line().Append( VECTOR2I( 1000000, 0 ) );
397 line.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
398
399 PNS::SHOVE shove( &world, m_router );
400 shove.SetShovePolicy( &segment, PNS::SHOVE::SHP_SHOVE );
402}
403
404// A microvia stack's hops are copper on a net, so the router has to be able to anchor a route
405// on one. Non-routable means "obstacle carrying no connectivity", which would send
406// snapToItem() to the nearest grid point and start the track off the via. Locked is what
407// keeps the hop from being shoved.
408BOOST_FIXTURE_TEST_CASE( PNSViaStackHopIsRoutableButLocked, PNS_TEST_FIXTURE )
409{
410 BOARD board;
411 board.SetCopperLayerCount( 4 );
413
414 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
415 stack->SetStartLayer( F_Cu );
416 stack->SetEndLayer( In2_Cu );
417 stack->SetViaSize( 300000 );
418 stack->SetViaDrill( 150000 );
419
420 // Deliberately off grid, which is where the grid fallback shows itself.
421 stack->SetPosition( VECTOR2I( 1234567, 7654321 ) );
422 board.Add( stack );
423 stack->Regenerate( &board, nullptr );
424
425 PCB_VIA* hop = nullptr;
426
427 for( BOARD_ITEM* item : stack->GetBoardItems() )
428 {
429 if( item->Type() == PCB_VIA_T )
430 hop = static_cast<PCB_VIA*>( item );
431 }
432
433 BOOST_REQUIRE_MESSAGE( hop, "the stack must have built at least one microvia" );
434
435 m_iface->SetBoard( &board );
436
437 std::unique_ptr<PNS::VIA> synced = m_iface->TestSyncVia( hop );
438
439 BOOST_REQUIRE( synced );
440 BOOST_CHECK_MESSAGE( synced->IsRoutable(), "a stack hop must be a valid route anchor" );
441 BOOST_CHECK_MESSAGE( synced->IsLocked(), "a stack hop must still be locked against shoving" );
442}
443
444
445// Routability decides whether a route may anchor on an item, not whether it collides with one.
446// A stack hop has to obstruct a foreign-net via either way.
447BOOST_FIXTURE_TEST_CASE( PNSViaStackHopIsAnObstacle, PNS_TEST_FIXTURE )
448{
449 BOARD board;
450 board.SetCopperLayerCount( 4 );
452
453 VECTOR2I at( 5000000, 5000000 );
454
455 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
456 stack->SetStartLayer( F_Cu );
457 stack->SetEndLayer( In2_Cu );
458 stack->SetViaSize( 300000 );
459 stack->SetViaDrill( 150000 );
460 stack->SetPosition( at );
461 board.Add( stack );
462 stack->Regenerate( &board, nullptr );
463
464 m_iface->SetBoard( &board );
465
466 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
467 world->SetMaxClearance( 10000000 );
468 world->SetRuleResolver( &m_ruleResolver );
469
470 int hops = 0;
471
472 for( BOARD_ITEM* item : stack->GetBoardItems() )
473 {
474 if( item->Type() != PCB_VIA_T )
475 continue;
476
477 std::unique_ptr<PNS::VIA> synced = m_iface->TestSyncVia( static_cast<PCB_VIA*>( item ) );
478 BOOST_REQUIRE( synced );
479 world->AddRaw( synced.release() );
480 hops++;
481 }
482
483 BOOST_REQUIRE_EQUAL( hops, 2 );
484
485 PNS::VIA* intruder = new PNS::VIA( at, PNS_LAYER_RANGE( F_Cu, B_Cu ), 300000, 150000 );
486 intruder->SetNet( (PNS::NET_HANDLE) 99 );
487 world->AddRaw( intruder );
488
489 m_ruleResolver.m_defaultClearance = 200000;
490
491 PNS::NODE::OBSTACLES obstacles;
492 world->QueryColliding( intruder, obstacles );
493
494 BOOST_CHECK_MESSAGE( obstacles.size() > 0, "a stack hop must obstruct a foreign-net via" );
495}
496
497
498static void dumpObstacles( const PNS::NODE::OBSTACLES &obstacles )
499{
500 for( const PNS::OBSTACLE& obs : obstacles )
501 {
502 BOOST_TEST_MESSAGE( wxString::Format( "%p [%s] - %p [%s], clearance %d",
503 obs.m_head, obs.m_head->KindStr().c_str(),
504 obs.m_item, obs.m_item->KindStr().c_str(),
505 obs.m_clearance ) );
506 }
507}
508
510{
511 PNS::VIA* v1 = new PNS::VIA( VECTOR2I( 0, 1000000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
512 PNS::VIA* v2 = new PNS::VIA( VECTOR2I( 0, 2000000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
513
514 std::unique_ptr<PNS::NODE> world ( new PNS::NODE );
515
516 v1->SetNet( (PNS::NET_HANDLE) 1 );
517 v2->SetNet( (PNS::NET_HANDLE) 2 );
518
519 world->SetMaxClearance( 10000000 );
520 world->SetRuleResolver( &m_ruleResolver );
521
522 world->AddRaw( v1 );
523 world->AddRaw( v2 );
524
525 BOOST_TEST_MESSAGE( "via to via, no violations" );
526 {
527 PNS::NODE::OBSTACLES obstacles;
528 int count = world->QueryColliding( v1, obstacles );
529 dumpObstacles( obstacles );
530 BOOST_CHECK_EQUAL( obstacles.size(), 0 );
531 BOOST_CHECK_EQUAL( count, 0 );
532 }
533
534 BOOST_TEST_MESSAGE( "via to via, forced copper to copper violation" );
535 {
536 PNS::NODE::OBSTACLES obstacles;
537 m_ruleResolver.m_defaultClearance = 1000000;
538 world->QueryColliding( v1, obstacles );
539 dumpObstacles( obstacles );
540
541 BOOST_CHECK_EQUAL( obstacles.size(), 1 );
542 const auto& first = *obstacles.begin();
543
544 BOOST_CHECK_EQUAL( first.m_head, v1 );
545 BOOST_CHECK_EQUAL( first.m_item, v2 );
546 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultClearance );
547 }
548
549 BOOST_TEST_MESSAGE( "via to via, forced hole to hole violation" );
550 {
551 PNS::NODE::OBSTACLES obstacles;
552 m_ruleResolver.m_defaultClearance = 200000;
553 m_ruleResolver.m_defaultHole2Hole = 1000000;
554
555 world->QueryColliding( v1, obstacles );
556 dumpObstacles( obstacles );
557
558 BOOST_CHECK_EQUAL( obstacles.size(), 1 );
559 auto iter = obstacles.begin();
560 const auto& first = *iter++;
561
562 BOOST_CHECK_EQUAL( first.m_head, v1->Hole() );
563 BOOST_CHECK_EQUAL( first.m_item, v2->Hole() );
564 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Hole );
565 }
566
567 BOOST_TEST_MESSAGE( "via to via, forced copper to hole violation" );
568 {
569 PNS::NODE::OBSTACLES obstacles;
570 m_ruleResolver.m_defaultHole2Hole = 220000;
571 m_ruleResolver.m_defaultHole2Copper = 1000000;
572
573 world->QueryColliding( v1, obstacles );
574 dumpObstacles( obstacles );
575
576 BOOST_CHECK_EQUAL( obstacles.size(), 2 );
577 auto iter = obstacles.begin();
578 const auto& first = *iter++;
579
580 // There is no guarantee on what order the two collisions will be in...
581 BOOST_CHECK( ( first.m_head == v1 && first.m_item == v2->Hole() )
582 || ( first.m_head == v1->Hole() && first.m_item == v2 ) );
583
584 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Copper );
585 }
586}
587
588
589BOOST_FIXTURE_TEST_CASE( PNSViaBackdrillRetention, PNS_TEST_FIXTURE )
590{
591 PNS::VIA via( VECTOR2I( 1000, 2000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 40000, 20000, nullptr,
593 via.SetHoleLayers( PNS_LAYER_RANGE( F_Cu, In2_Cu ) );
594 via.SetHolePostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) );
595 via.SetSecondaryDrill( std::optional<int>( 12000 ) );
596 via.SetSecondaryHoleLayers( std::optional<PNS_LAYER_RANGE>( PNS_LAYER_RANGE( F_Cu, In1_Cu ) ) );
597 via.SetSecondaryHolePostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED ) );
598
599 PNS::VIA viaCopy( via );
600 std::unique_ptr<PNS::VIA> viaClone( via.Clone() );
601
602 auto checkVia = [&]( const PNS::VIA& candidate )
603 {
604 BOOST_CHECK_EQUAL( candidate.HoleLayers().Start(), via.HoleLayers().Start() );
605 BOOST_CHECK_EQUAL( candidate.HoleLayers().End(), via.HoleLayers().End() );
606 BOOST_CHECK( candidate.HolePostMachining().has_value() );
607 BOOST_CHECK( candidate.HolePostMachining().value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK );
608 BOOST_CHECK( candidate.SecondaryDrill().has_value() );
609 BOOST_CHECK_EQUAL( candidate.SecondaryDrill().value(), via.SecondaryDrill().value() );
610 BOOST_CHECK( candidate.SecondaryHoleLayers().has_value() );
611 BOOST_CHECK_EQUAL( candidate.SecondaryHoleLayers()->Start(),
612 via.SecondaryHoleLayers()->Start() );
613 BOOST_CHECK_EQUAL( candidate.SecondaryHoleLayers()->End(),
614 via.SecondaryHoleLayers()->End() );
615 BOOST_CHECK( candidate.SecondaryHolePostMachining().has_value() );
616
617 // run this BOOST_CHECK only if possible to avoid crash
618 if( candidate.SecondaryHolePostMachining().has_value() )
619 BOOST_CHECK( candidate.SecondaryHolePostMachining().value() == via.SecondaryHolePostMachining().value() );
620 };
621
622 checkVia( viaCopy );
623 checkVia( *viaClone );
624}
625
626
627BOOST_AUTO_TEST_CASE( PCBViaBackdrillCloneRetainsData )
628{
629 BOARD board;
630 PCB_VIA via( &board );
631
632 via.SetPrimaryDrillStartLayer( F_Cu );
633 via.SetPrimaryDrillEndLayer( B_Cu );
634 via.SetFrontPostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) );
635 via.SetSecondaryDrillSize( std::optional<int>( 15000 ) );
636 via.SetSecondaryDrillStartLayer( F_Cu );
637 via.SetSecondaryDrillEndLayer( In2_Cu );
638
639 via.SetBackPostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE ) );
640 via.SetTertiaryDrillSize( std::optional<int>( 8000 ) );
641 via.SetTertiaryDrillStartLayer( B_Cu );
642 via.SetTertiaryDrillEndLayer( In4_Cu );
643
644 PCB_VIA viaCopy( via );
645 std::unique_ptr<PCB_VIA> viaClone( static_cast<PCB_VIA*>( via.Clone() ) );
646
647 auto checkVia = [&]( const PCB_VIA& candidate )
648 {
649 BOOST_CHECK_EQUAL( candidate.GetPrimaryDrillStartLayer(), via.GetPrimaryDrillStartLayer() );
650 BOOST_CHECK_EQUAL( candidate.GetPrimaryDrillEndLayer(), via.GetPrimaryDrillEndLayer() );
651 BOOST_CHECK( candidate.GetFrontPostMachining().has_value() );
652 BOOST_CHECK_EQUAL( static_cast<int>( candidate.GetFrontPostMachining().value() ),
653 static_cast<int>( via.GetFrontPostMachining().value() ) );
654 BOOST_CHECK( candidate.GetSecondaryDrillSize().has_value() );
655 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillSize().value(),
656 via.GetSecondaryDrillSize().value() );
657 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillStartLayer(),
658 via.GetSecondaryDrillStartLayer() );
659 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillEndLayer(),
660 via.GetSecondaryDrillEndLayer() );
661
662 BOOST_CHECK( candidate.GetBackPostMachining().has_value() );
663 BOOST_CHECK_EQUAL( static_cast<int>( candidate.GetBackPostMachining().value() ),
664 static_cast<int>( via.GetBackPostMachining().value() ) );
665 BOOST_CHECK( candidate.GetTertiaryDrillSize().has_value() );
666 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillSize().value(),
667 via.GetTertiaryDrillSize().value() );
668 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillStartLayer(),
669 via.GetTertiaryDrillStartLayer() );
670 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillEndLayer(),
671 via.GetTertiaryDrillEndLayer() );
672 };
673
674 checkVia( viaCopy );
675 checkVia( *viaClone );
676}
677
678
687BOOST_AUTO_TEST_CASE( PNSLayerRangeSwapBehavior )
688{
689 // On a 2-layer board with FRONT_INNER_BACK mode, BoardCopperLayerCount() returns 2.
690 // The code would calculate PNS_LAYER_RANGE(1, 2 - 2) = PNS_LAYER_RANGE(1, 0)
691 // Since start > end, the constructor swaps them to (0, 1), which would span
692 // both F_Cu and B_Cu incorrectly.
693
694 PNS_LAYER_RANGE innerLayersRange2Layer( 1, 0 ); // What would happen on 2-layer board
695
696 // Verify the swap behavior that causes the bug
697 BOOST_CHECK_EQUAL( innerLayersRange2Layer.Start(), 0 );
698 BOOST_CHECK_EQUAL( innerLayersRange2Layer.End(), 1 );
699 BOOST_CHECK( innerLayersRange2Layer.Overlaps( 0 ) ); // F_Cu
700 BOOST_CHECK( innerLayersRange2Layer.Overlaps( 1 ) ); // B_Cu
701
702 // On a 4-layer board, inner layers are 1 and 2, so PNS_LAYER_RANGE(1, 4-2) = (1, 2)
703 PNS_LAYER_RANGE innerLayersRange4Layer( 1, 2 ); // Correct for 4-layer board
704
705 BOOST_CHECK_EQUAL( innerLayersRange4Layer.Start(), 1 );
706 BOOST_CHECK_EQUAL( innerLayersRange4Layer.End(), 2 );
707 BOOST_CHECK( !innerLayersRange4Layer.Overlaps( 0 ) ); // F_Cu - should not overlap
708 BOOST_CHECK( innerLayersRange4Layer.Overlaps( 1 ) ); // In1_Cu
709 BOOST_CHECK( innerLayersRange4Layer.Overlaps( 2 ) ); // In2_Cu
710 BOOST_CHECK( !innerLayersRange4Layer.Overlaps( 3 ) ); // B_Cu - should not overlap
711}
712
713
721BOOST_FIXTURE_TEST_CASE( PNSSegmentSplitPreservesLockedState, PNS_TEST_FIXTURE )
722{
723 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
724 world->SetMaxClearance( 10000000 );
725 world->SetRuleResolver( &m_ruleResolver );
726
728
729 VECTOR2I segStart( 0, 0 );
730 VECTOR2I segEnd( 10000000, 0 );
731 VECTOR2I splitPt( 5000000, 0 );
732
733 PNS::SEGMENT* lockedSeg = new PNS::SEGMENT( SEG( segStart, segEnd ), net );
734 lockedSeg->SetWidth( 250000 );
735 lockedSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
736 lockedSeg->Mark( PNS::MK_LOCKED );
737
738 BOOST_CHECK( lockedSeg->IsLocked() );
739
740 world->AddRaw( lockedSeg );
741
742 // Clone the locked segment and set up two halves (simulating SplitAdjacentSegments)
743 std::unique_ptr<PNS::SEGMENT> clone1( PNS::Clone( *lockedSeg ) );
744 std::unique_ptr<PNS::SEGMENT> clone2( PNS::Clone( *lockedSeg ) );
745
746 clone1->SetEnds( segStart, splitPt );
747 clone2->SetEnds( splitPt, segEnd );
748
749 BOOST_CHECK_MESSAGE( clone1->IsLocked(),
750 "First half of split locked segment must retain locked state" );
751 BOOST_CHECK_MESSAGE( clone2->IsLocked(),
752 "Second half of split locked segment must retain locked state" );
753 BOOST_CHECK_EQUAL( clone1->Width(), lockedSeg->Width() );
754 BOOST_CHECK_EQUAL( clone2->Width(), lockedSeg->Width() );
755}
756
757
765BOOST_FIXTURE_TEST_CASE( PNSInheritTrackWidthCursorProximity, PNS_TEST_FIXTURE )
766{
767 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
768 world->SetMaxClearance( 10000000 );
769 world->SetRuleResolver( &m_ruleResolver );
770
771 VECTOR2I padPos( 0, 0 );
773
774 // Pad at origin with a small circular shape
776 pad->SetShape( new SHAPE_CIRCLE( padPos, 500000 ) );
777 pad->SetPos( padPos );
778 pad->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
779 pad->SetNet( net );
780 world->AddRaw( pad );
781
782 // Narrow track going right (250um width)
783 int narrowWidth = 250000;
784 PNS::SEGMENT* narrowSeg = new PNS::SEGMENT( SEG( padPos, VECTOR2I( 5000000, 0 ) ), net );
785 narrowSeg->SetWidth( narrowWidth );
786 narrowSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
787 world->AddRaw( narrowSeg );
788
789 // Wide track going up (500um width)
790 int wideWidth = 500000;
791 PNS::SEGMENT* wideSeg = new PNS::SEGMENT( SEG( padPos, VECTOR2I( 0, -5000000 ) ), net );
792 wideSeg->SetWidth( wideWidth );
793 wideSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
794 world->AddRaw( wideSeg );
795
796 int inherited = 0;
797
798 // Without cursor position, should fall back to minimum width
799 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited ) );
800 BOOST_CHECK_EQUAL( inherited, narrowWidth );
801
802 // Cursor near the narrow track (to the right) should select narrow width
803 inherited = 0;
804 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 2000000, 0 ) ) );
805 BOOST_CHECK_EQUAL( inherited, narrowWidth );
806
807 // Cursor near the wide track (upward) should select wide width
808 inherited = 0;
809 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 0, -2000000 ) ) );
810 BOOST_CHECK_EQUAL( inherited, wideWidth );
811
812 // Cursor slightly offset toward narrow track should still select narrow
813 inherited = 0;
814 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 100000, 50000 ) ) );
815 BOOST_CHECK_EQUAL( inherited, narrowWidth );
816
817 // Cursor slightly offset toward wide track should select wide
818 inherited = 0;
819 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 50000, -100000 ) ) );
820 BOOST_CHECK_EQUAL( inherited, wideWidth );
821}
822
823
831BOOST_AUTO_TEST_CASE( PCBExprGeometryDependentFunctionDetection )
832{
833 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
834
835 auto compileAndCheck = [&]( const wxString& aExpr, bool aExpectGeometry )
836 {
837 PCBEXPR_UCODE ucode;
838 PCBEXPR_CONTEXT ctx( 0, F_Cu );
839
840 bool ok = compiler.Compile( aExpr.ToUTF8().data(), &ucode, &ctx );
841 BOOST_CHECK_MESSAGE( ok, "Failed to compile: " + aExpr );
842
843 if( ok )
844 {
845 BOOST_CHECK_MESSAGE( ucode.HasGeometryDependentFunctions() == aExpectGeometry,
846 wxString::Format( "Expression '%s': expected geometry=%s, got %s",
847 aExpr,
848 aExpectGeometry ? "true" : "false",
850 ? "true" : "false" ) );
851 }
852 };
853
854 // Property-based conditions should NOT be geometry-dependent
855 compileAndCheck( wxT( "A.NetClass == 'Power'" ), false );
856 compileAndCheck( wxT( "A.Type == 'via'" ), false );
857 compileAndCheck( wxT( "A.NetName == '/VCC'" ), false );
858
859 // Geometry-dependent functions SHOULD be detected
860 compileAndCheck( wxT( "A.intersectsCourtyard('U1')" ), true );
861 compileAndCheck( wxT( "A.intersectsArea('Zone1')" ), true );
862 compileAndCheck( wxT( "A.enclosedByArea('Zone1')" ), true );
863 compileAndCheck( wxT( "A.intersectsFrontCourtyard('U1')" ), true );
864 compileAndCheck( wxT( "A.intersectsBackCourtyard('U1')" ), true );
865
866 // Deprecated aliases should also be detected
867 compileAndCheck( wxT( "A.insideCourtyard('U1')" ), true );
868 compileAndCheck( wxT( "A.insideArea('Zone1')" ), true );
869}
870
871
880BOOST_FIXTURE_TEST_CASE( PNSCollideSimpleNullShapeGuard, PNS_TEST_FIXTURE )
881{
882 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
883 world->SetMaxClearance( 10000000 );
884 world->SetRuleResolver( &m_ruleResolver );
885
888
889 PNS::SOLID* solid1 = new PNS::SOLID;
890 solid1->SetShape( new SHAPE_CIRCLE( VECTOR2I( 0, 0 ), 500000 ) );
891 solid1->SetPos( VECTOR2I( 0, 0 ) );
892 solid1->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
893 solid1->SetNet( net1 );
894
895 PNS::SOLID* solid2 = new PNS::SOLID;
896 solid2->SetShape( new SHAPE_CIRCLE( VECTOR2I( 100000, 0 ), 500000 ) );
897 solid2->SetPos( VECTOR2I( 100000, 0 ) );
898 solid2->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
899 solid2->SetNet( net2 );
900
901 world->AddRaw( solid1 );
902 world->AddRaw( solid2 );
903
904 // Verify collision works normally with valid shapes
905 PNS::NODE::OBSTACLES obstacles;
906 int count = world->QueryColliding( solid1, obstacles );
907 BOOST_CHECK( count > 0 );
908
909 // Exercise the null-shape guard in collideSimple by calling Collide() directly with a
910 // null-shape solid as the head item. This bypasses the spatial index (which requires a
911 // valid shape for bounding-box computation) and hits the exact code path the guard protects.
912 PNS::SOLID nullShapeSolid;
913 nullShapeSolid.SetPos( VECTOR2I( 0, 0 ) );
914 nullShapeSolid.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
915 nullShapeSolid.SetNet( net2 );
916
917 bool collided = solid1->Collide( &nullShapeSolid, world.get(), F_Cu, nullptr );
918 BOOST_CHECK( !collided );
919}
920
921
930BOOST_FIXTURE_TEST_CASE( PNSComponentDraggerBasicDrag, PNS_TEST_FIXTURE )
931{
932 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
933 world->SetMaxClearance( 10000000 );
934 world->SetRuleResolver( &m_ruleResolver );
935
937
938 VECTOR2I pad1Pos( 0, 0 );
939
940 PNS::SOLID* pad1 = new PNS::SOLID;
941 pad1->SetShape( new SHAPE_CIRCLE( pad1Pos, 500000 ) );
942 pad1->SetPos( pad1Pos );
943 pad1->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
944 pad1->SetNet( net1 );
945 pad1->SetRoutable( true );
946
947 VECTOR2I traceEnd( 2500000, 2500000 );
948 PNS::SEGMENT* trace = new PNS::SEGMENT( SEG( pad1Pos, traceEnd ), net1 );
949 trace->SetWidth( 250000 );
950 trace->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
951
952 world->AddRaw( pad1 );
953 world->AddRaw( trace );
954
955 PNS::COMPONENT_DRAGGER dragger( m_router );
956 dragger.SetWorld( world.get() );
957
958 PNS::ITEM_SET itemsToDrag;
959 itemsToDrag.Add( pad1 );
960
961 bool started = dragger.Start( pad1Pos, itemsToDrag );
962 BOOST_REQUIRE( started );
963
964 // Simulate multiple drag events (mimics mouse movement during drag)
965 VECTOR2I dragPositions[] = {
966 VECTOR2I( 100000, 100000 ),
967 VECTOR2I( 500000, 500000 ),
968 VECTOR2I( 1000000, 1000000 ),
969 VECTOR2I( 500000, 200000 ),
970 VECTOR2I( 0, 0 )
971 };
972
973 for( const VECTOR2I& pos : dragPositions )
974 {
975 bool dragOk = dragger.Drag( pos );
976 BOOST_CHECK( dragOk );
977
978 PNS::NODE* currentNode = dragger.CurrentNode();
979 BOOST_CHECK( currentNode != nullptr );
980 }
981
982 // Verify the dragged items set is populated
983 PNS::ITEM_SET traces = dragger.Traces();
984 BOOST_CHECK( traces.Size() > 0 );
985
986 // Clean up branch nodes before the world is destroyed
987 world->KillChildren();
988}
989
990
991// Dragging the apex of an isolated arc outward keeps a single arc in the chain and
992// changes its radius. Exercises the LINE::DragArc geometric core added for in-router
993// arc dragging.
994BOOST_AUTO_TEST_CASE( PNSLineDragArcResize )
995{
996 // 90-degree CCW arc, centre at origin, radius 1mm.
997 SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), EDA_ANGLE( 90, DEGREES_T ), 250000 );
998
1000 chain.SetWidth( 250000 );
1001 chain.Append( arc );
1002
1003 PNS::LINE line;
1004 line.SetWidth( 250000 );
1005 line.Line() = chain;
1006
1007 BOOST_REQUIRE_EQUAL( line.CLine().ArcCount(), 1 );
1008
1009 double oldRadius = line.CLine().CArcs()[0].GetRadius();
1010 int apexIdx = line.CLine().PointCount() / 2;
1011
1012 // Pull the apex toward the tangent corner at (1mm, 1mm) to grow the radius.
1013 line.DragArc( VECTOR2I( 950000, 950000 ), apexIdx );
1014
1015 BOOST_CHECK_EQUAL( line.CLine().ArcCount(), 1 );
1016 BOOST_CHECK( line.CLine().PointCount() >= 2 );
1017 BOOST_CHECK( line.CLine().CArcs()[0].GetRadius() != oldRadius );
1018}
1019
1020
1021// Driving the arc endpoints together collapses the arc out of the chain (the
1022// "collapse to nothing drops it from the route" path in LINE::DragArc).
1023BOOST_AUTO_TEST_CASE( PNSLineDragArcCollapse )
1024{
1025 SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), EDA_ANGLE( 90, DEGREES_T ), 250000 );
1026
1028 chain.SetWidth( 250000 );
1029 chain.Append( arc );
1030
1031 PNS::LINE line;
1032 line.SetWidth( 250000 );
1033 line.Line() = chain;
1034
1035 BOOST_REQUIRE_EQUAL( line.CLine().ArcCount(), 1 );
1036
1037 // Drag almost onto the tangent corner at (1mm, 1mm), shrinking the arc until its
1038 // endpoints fall within the keep-track threshold and the arc is dropped. Staying a
1039 // hair off the corner keeps the constructed radius positive.
1040 line.DragArc( VECTOR2I( 999950, 999950 ), line.CLine().PointCount() / 2 );
1041
1042 BOOST_CHECK_EQUAL( line.CLine().ArcCount(), 0 );
1043}
1044
1045
1046// An arc whose central angle reaches 180 degrees cannot be dragged, and the refusal
1047// is reported through the router's failure reason so the UI can surface it.
1048BOOST_FIXTURE_TEST_CASE( PNSDragArcRejectsNear180, PNS_TEST_FIXTURE )
1049{
1050 PNS::ROUTING_SETTINGS settings( nullptr, "" );
1051 m_router->LoadSettings( &settings );
1052
1054
1055 auto makeArc = [&]( const EDA_ANGLE& aAngle ) -> PNS::ARC*
1056 {
1057 SHAPE_ARC sa( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), aAngle, 250000 );
1058 PNS::ARC* a = new PNS::ARC( sa, net );
1060 return a;
1061 };
1062
1063 // Shallow arc: drag starts and no failure is reported.
1064 {
1065 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1066 world->SetMaxClearance( 10000000 );
1067 world->SetRuleResolver( &m_ruleResolver );
1068
1069 PNS::ARC* arc = makeArc( EDA_ANGLE( 90, DEGREES_T ) );
1070 world->AddRaw( arc );
1071
1072 PNS::DRAGGER dragger( m_router );
1073 dragger.SetWorld( world.get() );
1074 dragger.SetMode( PNS::DM_ARC );
1075
1076 PNS::ITEM_SET items;
1077 items.Add( arc );
1078
1079 m_router->SetFailureReason( wxEmptyString );
1080 BOOST_CHECK( dragger.Start( arc->Anchor( 0 ), items ) );
1081 BOOST_CHECK( m_router->FailureReason().IsEmpty() );
1082
1083 world->KillChildren();
1084 }
1085
1086 // Major arc (>= 180 deg): drag is refused with an explanatory failure reason.
1087 {
1088 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1089 world->SetMaxClearance( 10000000 );
1090 world->SetRuleResolver( &m_ruleResolver );
1091
1092 PNS::ARC* arc = makeArc( EDA_ANGLE( 270, DEGREES_T ) );
1093 world->AddRaw( arc );
1094
1095 PNS::DRAGGER dragger( m_router );
1096 dragger.SetWorld( world.get() );
1097 dragger.SetMode( PNS::DM_ARC );
1098
1099 PNS::ITEM_SET items;
1100 items.Add( arc );
1101
1102 m_router->SetFailureReason( wxEmptyString );
1103 BOOST_CHECK( !dragger.Start( arc->Anchor( 0 ), items ) );
1104 BOOST_CHECK( !m_router->FailureReason().IsEmpty() );
1105
1106 world->KillChildren();
1107 }
1108
1109 // Clockwise major arc (negative central angle): the magnitude is what matters, so
1110 // it must be refused just like its CCW counterpart.
1111 {
1112 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1113 world->SetMaxClearance( 10000000 );
1114 world->SetRuleResolver( &m_ruleResolver );
1115
1116 PNS::ARC* arc = makeArc( EDA_ANGLE( -270, DEGREES_T ) );
1117 world->AddRaw( arc );
1118
1119 PNS::DRAGGER dragger( m_router );
1120 dragger.SetWorld( world.get() );
1121 dragger.SetMode( PNS::DM_ARC );
1122
1123 PNS::ITEM_SET items;
1124 items.Add( arc );
1125
1126 m_router->SetFailureReason( wxEmptyString );
1127 BOOST_CHECK( !dragger.Start( arc->Anchor( 0 ), items ) );
1128 BOOST_CHECK( !m_router->FailureReason().IsEmpty() );
1129
1130 world->KillChildren();
1131 }
1132}
1133
1134
1135// Base mock's NetCode() returns -1 for everything, which reads as unnetted; use a real net here
1136namespace
1137{
1138struct NETCODE_RULE_RESOLVER : public MOCK_RULE_RESOLVER
1139{
1140 int NetCode( PNS::NET_HANDLE aNet ) override { return aNet ? 1 : -1; }
1141};
1142
1143PNS::ITEM* queryFinishAnchor( PNS::NODE& aWorld, const PNS::LINE& aTrack )
1144{
1145 PNS::TOPOLOGY topo( &aWorld );
1146 VECTOR2I anchorPoint;
1147 PNS_LAYER_RANGE anchorLayers;
1148 PNS::ITEM* anchorItem = nullptr;
1149
1150 BOOST_REQUIRE( topo.NearestUnconnectedAnchorPoint( &aTrack, anchorPoint, anchorLayers,
1151 anchorItem ) );
1152 return anchorItem;
1153}
1154} // namespace
1155
1156
1157// F-key finish adds the track to a temporary branch node; a joint linking only to that
1158// track must still yield a persistent-world anchor, not a dangling branch pointer
1159//
1160// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24985
1161BOOST_FIXTURE_TEST_CASE( PNSFinishAnchorNoDanglingBranchItem, PNS_TEST_FIXTURE )
1162{
1163 NETCODE_RULE_RESOLVER resolver;
1164
1165 PNS::NODE world;
1166 world.SetMaxClearance( 10000000 );
1167 world.SetRuleResolver( &resolver );
1168
1170
1171 // Persistent unconnected target on the same net, away from the track
1172 PNS::SEGMENT* target = new PNS::SEGMENT( SEG( VECTOR2I( 10000000, 10000000 ),
1173 VECTOR2I( 12000000, 10000000 ) ), net );
1174 target->SetWidth( 250000 );
1175 target->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1176 world.AddRaw( target );
1177
1178 // Closed loop; end joint's two links are both owned by the temporary branch node
1179 PNS::LINE track;
1180 track.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1181 track.SetNet( net );
1182 track.SetWidth( 250000 );
1183 track.Line().Append( VECTOR2I( 0, 0 ) );
1184 track.Line().Append( VECTOR2I( 2000000, 0 ) );
1185 track.Line().Append( VECTOR2I( 2000000, 2000000 ) );
1186 track.Line().Append( VECTOR2I( 0, 2000000 ) );
1187 track.Line().Append( VECTOR2I( 0, 0 ) );
1188
1189 // Anchor must be the persistent target, never a link owned by the destroyed temp branch
1190 BOOST_CHECK_EQUAL( queryFinishAnchor( world, track ), target );
1191}
1192
1193
1194// ConnectedJoints must cross arcs when subtracting the track; stopping at an arc left
1195// temporary primitives beyond it selectable as the anchor, reviving the dangling pointer
1196//
1197// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24985
1198BOOST_FIXTURE_TEST_CASE( PNSFinishAnchorCrossesArcInConnectivity, PNS_TEST_FIXTURE )
1199{
1200 NETCODE_RULE_RESOLVER resolver;
1201
1202 PNS::NODE world;
1203 world.SetMaxClearance( 10000000 );
1204 world.SetRuleResolver( &resolver );
1205
1207
1208 // Farther from the track end than its own far segment, wins only once that's subtracted
1209 PNS::SEGMENT* target = new PNS::SEGMENT( SEG( VECTOR2I( 7000000, 0 ),
1210 VECTOR2I( 8000000, 0 ) ), net );
1211 target->SetWidth( 250000 );
1212 target->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1213 world.AddRaw( target );
1214
1215 // Segment-arc-segment track; far segment lies across the arc, so connectivity must cross it
1216 PNS::LINE track;
1217 track.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1218 track.SetNet( net );
1219 track.SetWidth( 250000 );
1220 track.Line().Append( VECTOR2I( 0, 0 ) );
1221 track.Line().Append( VECTOR2I( 1000000, 0 ) );
1222 track.Line().Append( SHAPE_ARC( VECTOR2I( 1000000, 0 ), VECTOR2I( 1500000, 500000 ),
1223 VECTOR2I( 2000000, 0 ), 0 ) );
1224 track.Line().Append( VECTOR2I( 3000000, 0 ) );
1225
1226 // Anchor must be the persistent target, not a branch-owned primitive across the arc
1227 BOOST_CHECK_EQUAL( queryFinishAnchor( world, track ), target );
1228}
1229
1230
1231// Regression tests for issues #18658 and #24132. Physical clearance rules must be
1232// enforced for same-net and free-pad pairs without disturbing the fast path on
1233// boards that do not define them.
1234
1235namespace
1236{
1237PNS::VIA* makeVia( const VECTOR2I& aPos, PNS::NET_HANDLE aNet )
1238{
1239 PNS::VIA* v = new PNS::VIA( aPos, PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
1240 v->SetNet( aNet );
1241 return v;
1242}
1243
1244PNS::SOLID* makePad( const VECTOR2I& aPos, PNS::NET_HANDLE aNet, bool aFreePad = false )
1245{
1246 PNS::SOLID* s = new PNS::SOLID;
1247 s->SetShape( new SHAPE_CIRCLE( aPos, 250000 ) );
1248 s->SetPos( aPos );
1250 s->SetNet( aNet );
1251 s->SetIsFreePad( aFreePad );
1252 return s;
1253}
1254} // namespace
1255
1256
1257// Cross-net via vs via with no physical rules: ordinary CT_CLEARANCE applies.
1258BOOST_FIXTURE_TEST_CASE( PNSCrossNetViaViaElectricalClearanceBaseline, PNS_TEST_FIXTURE )
1259{
1260 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1261 world->SetMaxClearance( 10000000 );
1262 world->SetRuleResolver( &m_ruleResolver );
1263
1264 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1 );
1265 PNS::VIA* v2 = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1266 world->AddRaw( v1 );
1267 world->AddRaw( v2 );
1268
1269 m_ruleResolver.m_defaultClearance = 1000000;
1270
1271 PNS::NODE::OBSTACLES obstacles;
1272 world->QueryColliding( v1, obstacles );
1273
1274 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1275}
1276
1277
1278// Same-net via vs via with no physical rules: fast path keeps clearance = -1.
1279BOOST_FIXTURE_TEST_CASE( PNSSameNetNoPhysicalRulesFastPathNoCollision, PNS_TEST_FIXTURE )
1280{
1281 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1282 world->SetMaxClearance( 10000000 );
1283 world->SetRuleResolver( &m_ruleResolver );
1284
1286 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1287 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 100000 ), net );
1288 world->AddRaw( v1 );
1289
1290 m_ruleResolver.m_hasUserPhysicalRules = false;
1291 m_ruleResolver.m_defaultClearance = 1000000;
1292
1293 PNS::NODE::OBSTACLES obstacles;
1294 world->QueryColliding( v1, obstacles );
1295
1296 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1297}
1298
1299
1300// Same-net via vs via with a matching physical_clearance rule: collision reported.
1301// Regression scenario for issues #18658 and #24132.
1302BOOST_FIXTURE_TEST_CASE( PNSSameNetWithPhysicalRuleCollides, PNS_TEST_FIXTURE )
1303{
1304 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1305 world->SetMaxClearance( 10000000 );
1306 world->SetRuleResolver( &m_ruleResolver );
1307
1309 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1310 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 100000 ), net );
1311 world->AddRaw( v1 );
1312
1313 m_ruleResolver.m_hasUserPhysicalRules = true;
1314 m_ruleResolver.m_defaultPhysicalClearance = 1000000;
1315
1316 PNS::NODE::OBSTACLES obstacles;
1317 world->QueryColliding( v1, obstacles );
1318
1319 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1320 if( !obstacles.empty() )
1321 BOOST_CHECK_EQUAL( obstacles.begin()->m_clearance, 1000000 );
1322}
1323
1324
1325// Free pad vs cross-net pair, no physical rules: fast path applies.
1326BOOST_FIXTURE_TEST_CASE( PNSFreePadNoPhysicalRulesFastPath, PNS_TEST_FIXTURE )
1327{
1328 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1329 world->SetMaxClearance( 10000000 );
1330 world->SetRuleResolver( &m_ruleResolver );
1331
1332 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1333 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1334 world->AddRaw( freePad );
1335 world->AddRaw( v );
1336
1337 m_ruleResolver.m_hasUserPhysicalRules = false;
1338 m_ruleResolver.m_defaultClearance = 1000000;
1339
1340 PNS::NODE::OBSTACLES obstacles;
1341 world->QueryColliding( v, obstacles );
1342
1343 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1344}
1345
1346
1347// Free pad vs cross-net pair, physical rules present but no match: safety net must
1348// keep free pads from reporting collisions just because the board has a physical
1349// rule somewhere.
1351{
1352 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1353 world->SetMaxClearance( 10000000 );
1354 world->SetRuleResolver( &m_ruleResolver );
1355
1356 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1357 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1358 world->AddRaw( freePad );
1359 world->AddRaw( v );
1360
1361 m_ruleResolver.m_hasUserPhysicalRules = true;
1362 m_ruleResolver.m_defaultPhysicalClearance = 0; // rule does not match
1363 m_ruleResolver.m_defaultClearance = 1000000; // would collide if !sameNet block runs
1364
1365 PNS::NODE::OBSTACLES obstacles;
1366 world->QueryColliding( v, obstacles );
1367
1368 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1369}
1370
1371
1372// Free pad with a matching physical_clearance rule: collision reported.
1373BOOST_FIXTURE_TEST_CASE( PNSFreePadPhysicalRuleEnforced, PNS_TEST_FIXTURE )
1374{
1375 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1376 world->SetMaxClearance( 10000000 );
1377 world->SetRuleResolver( &m_ruleResolver );
1378
1379 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1380 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1381 world->AddRaw( freePad );
1382 world->AddRaw( v );
1383
1384 m_ruleResolver.m_hasUserPhysicalRules = true;
1385 m_ruleResolver.m_defaultPhysicalClearance = 1000000;
1386
1387 PNS::NODE::OBSTACLES obstacles;
1388 world->QueryColliding( v, obstacles );
1389
1390 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1391}
1392
1393
1394// Same-net via vs pad with a matching physical_hole_clearance rule. Covers the
1395// drill-into-pad half of issue #24132 and exercises CT_PHYSICAL_HOLE_CLEARANCE.
1396BOOST_FIXTURE_TEST_CASE( PNSSameNetPhysicalHoleClearance, PNS_TEST_FIXTURE )
1397{
1398 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1399 world->SetMaxClearance( 10000000 );
1400 world->SetRuleResolver( &m_ruleResolver );
1401
1403 PNS::SOLID* pad = makePad( VECTOR2I( 0, 0 ), net );
1404 PNS::VIA* via = makeVia( VECTOR2I( 0, 100000 ), net );
1405 world->AddRaw( pad );
1406 world->AddRaw( via );
1407
1408 m_ruleResolver.m_hasUserPhysicalRules = true;
1409 m_ruleResolver.m_defaultPhysicalHoleClearance = 1000000;
1410
1411 PNS::NODE::OBSTACLES obstacles;
1412 world->QueryColliding( via, obstacles );
1413
1414 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1415}
1416
1417
1418BOOST_FIXTURE_TEST_CASE( PNSSameNetSafetyNetOnOverlap, PNS_TEST_FIXTURE )
1419{
1420 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1421 world->SetMaxClearance( 10000000 );
1422 world->SetRuleResolver( &m_ruleResolver );
1423
1425 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1426 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 30000 ), net ); // overlaps the first via
1427 world->AddRaw( v1 );
1428
1429 m_ruleResolver.m_hasUserPhysicalRules = true;
1430 m_ruleResolver.m_defaultPhysicalClearance = 0;
1431
1432 PNS::NODE::OBSTACLES obstacles;
1433 world->QueryColliding( v1, obstacles );
1434
1435 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1436}
1437
1438
1439// Same-net pad+via with both physical_clearance and physical_hole_clearance
1440// matching: the resolver returns max across the two query points.
1441BOOST_FIXTURE_TEST_CASE( PNSBothPhysicalConstraintsMaxWins, PNS_TEST_FIXTURE )
1442{
1443 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1444 world->SetMaxClearance( 10000000 );
1445 world->SetRuleResolver( &m_ruleResolver );
1446
1448 PNS::SOLID* pad = makePad( VECTOR2I( 0, 0 ), net );
1449 PNS::VIA* via = makeVia( VECTOR2I( 0, 100000 ), net );
1450 world->AddRaw( pad );
1451 world->AddRaw( via );
1452
1453 m_ruleResolver.m_hasUserPhysicalRules = true;
1454 m_ruleResolver.m_defaultPhysicalClearance = 100000;
1455 m_ruleResolver.m_defaultPhysicalHoleClearance = 2000000;
1456
1457 PNS::NODE::OBSTACLES obstacles;
1458 world->QueryColliding( via, obstacles );
1459
1460 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1461
1462 // The recursive collideSimple call for the via's hole inserts a separate OBSTACLE
1463 // with the max-accumulated clearance, so look across all entries rather than
1464 // relying on std::set ordering (which is by pointer).
1465 int maxClearance = 0;
1466 for( const PNS::OBSTACLE& obs : obstacles )
1467 maxClearance = std::max( maxClearance, obs.m_clearance );
1468 BOOST_CHECK_EQUAL( maxClearance, 2000000 );
1469}
1470
1471
1472// Diff pair vias must respect copper-to-hole clearance, not just copper-to-copper and
1473// hole-to-hole. EffectiveDiffPairViaGap() is the copper-edge-to-copper-edge distance the
1474// placer fits vias to; it converts each clearance rule to that reference by subtracting the
1475// annular ring(s) of via copper that sit between a hole edge and the copper edge.
1476//
1477// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/21623 where the placer
1478// ignored copper-to-hole clearance and produced DRC violations on diff pair vias.
1479BOOST_AUTO_TEST_CASE( PNSDiffPairViaGapCopperToHoleClearance )
1480{
1481 PNS::SIZES_SETTINGS sizes;
1482
1483 // 600um copper diameter over a 300um drill leaves a 150um annular ring.
1484 sizes.SetViaDiameter( 600000 );
1485 sizes.SetViaDrill( 300000 );
1486 sizes.SetDiffPairViaGapSameAsTraceGap( false );
1487
1489
1490 // Copper-to-hole binds because 400um from a hole edge to the neighbour's copper edge is
1491 // 400000 - 150000 = 250000 copper-to-copper, exceeding both other rules.
1492 sizes.SetDiffPairViaGap( 200000 );
1493 sizes.SetDiffPairHoleToHole( 500000 ); // 500000 - 300000 = 200000 copper-to-copper
1494 sizes.SetDiffPairCopperToHole( 400000 );
1495
1496 BOOST_CHECK_EQUAL( sizes.GetDiffPairCopperToHole(), 400000 );
1497 BOOST_CHECK_EQUAL( sizes.EffectiveDiffPairViaGap(), 250000 );
1498
1499 // Hole-to-hole binds when it is the largest converted rule.
1500 sizes.SetDiffPairHoleToHole( 900000 ); // 900000 - 300000 = 600000 copper-to-copper
1501 BOOST_CHECK_EQUAL( sizes.EffectiveDiffPairViaGap(), 600000 );
1502
1503 // Plain copper-to-copper gap binds when the hole-based rules are slack.
1504 sizes.SetDiffPairHoleToHole( 0 );
1505 sizes.SetDiffPairCopperToHole( 0 );
1506 BOOST_CHECK_EQUAL( sizes.EffectiveDiffPairViaGap(), 200000 );
1507}
1508
1509
1510// Collapsing a padstack via's layer range for the violation highlight orphaned its
1511// layer-indexed shapes. Layer 2 is required here: 0, 1 and 5 resolve by accident.
1512//
1513// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/25139
1514namespace
1515{
1516class PADSTACK_VIA_IFACE : public MOCK_PNS_KICAD_IFACE
1517{
1518public:
1519 PADSTACK_VIA_IFACE( PNS_TEST_FIXTURE* aFixture ) :
1520 MOCK_PNS_KICAD_IFACE( aFixture )
1521 {
1522 }
1523
1524 void SyncWorld( PNS::NODE* aWorld ) override
1525 {
1526 aWorld->SetMaxClearance( 10000000 );
1527 aWorld->SetRuleResolver( GetRuleResolver() );
1528
1529 auto via = std::make_unique<PNS::VIA>( VECTOR2I( 0, 0 ), PNS_LAYER_RANGE( 0, 5 ), 400000, 100000,
1532 via->SetDiameter( 0, 400000 );
1533 via->SetDiameter( 1, 450000 );
1534 via->SetDiameter( 5, 400000 );
1535
1536 aWorld->Add( std::move( via ) );
1537 }
1538
1539 void DisplayItem( const PNS::ITEM* aItem, int aClearance, bool aEdit = false, int aFlags = 0 ) override
1540 {
1541 if( !aItem->OfKind( PNS::ITEM::VIA_T ) )
1542 return;
1543
1544 m_viasDisplayed++;
1545
1546 if( !aItem->Shape( -1 ) )
1547 m_nullShapes++;
1548 }
1549
1550 int m_viasDisplayed = 0;
1551 int m_nullShapes = 0;
1552};
1553} // namespace
1554
1555
1556BOOST_FIXTURE_TEST_CASE( PNSMarkViolationsKeepsPadstackViaShape, PNS_TEST_FIXTURE )
1557{
1558 PADSTACK_VIA_IFACE iface( this );
1559 PNS::ROUTING_SETTINGS settings( nullptr, "" );
1560 PNS::SIZES_SETTINGS sizes;
1561
1562 m_router->SetInterface( &iface );
1563 m_router->LoadSettings( &settings );
1564 m_router->SetMode( PNS::PNS_MODE_ROUTE_SINGLE );
1565
1566 sizes.SetTrackWidth( 200000 );
1567 sizes.SetBoardMinTrackWidth( 100000 );
1568 m_router->UpdateSizes( sizes );
1569
1570 m_router->SyncWorld();
1571
1572 BOOST_REQUIRE( !m_router->StartRouting( VECTOR2I( 300000, 0 ), nullptr, 2 ) );
1573
1574 BOOST_REQUIRE_MESSAGE( iface.m_viasDisplayed > 0, "Via was not highlighted as a violation" );
1575 BOOST_CHECK_MESSAGE( iface.m_nullShapes == 0, "Highlighted front/inner/back via has no resolvable shape" );
1576}
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:408
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
void SetCopperLayerCount(int aCount)
Definition board.cpp:1137
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1203
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
static const LSET & AllTechMask()
Return a mask holding all technical layers (no CU layer) on both side.
Definition lset.cpp:672
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
T Min() const
Definition minoptmax.h:29
void SetMin(T v)
Definition minoptmax.h:38
std::unique_ptr< PNS::VIA > TestSyncVia(PCB_VIA *aVia)
PNS::RULE_RESOLVER * GetRuleResolver() override
void DisplayItem(const PNS::ITEM *aItem, int aClearance, bool aEdit=false, int aFlags=0) override
void HideItem(PNS::ITEM *aItem) override
MOCK_PNS_KICAD_IFACE(PNS_TEST_FIXTURE *aFixture)
PNS_TEST_FIXTURE * m_testFixture
bool TestInheritTrackWidth(PNS::ITEM *aItem, int *aInheritedWidth, const VECTOR2I &aStartPosition=VECTOR2I())
virtual int NetCode(PNS::NET_HANDLE aNet) override
bool IsNetTieExclusion(const PNS::ITEM *aItem, const VECTOR2I &aCollisionPos, const PNS::ITEM *aCollidingItem) override
bool IsKeepout(const PNS::ITEM *aObstacle, const PNS::ITEM *aItem, bool *aEnforce) override
virtual int Clearance(const PNS::ITEM *aA, const PNS::ITEM *aB, bool aUseClearanceEpsilon=true) override
virtual bool QueryConstraint(PNS::CONSTRAINT_TYPE aType, const PNS::ITEM *aItemA, const PNS::ITEM *aItemB, int aLayer, PNS::CONSTRAINT *aConstraint) override
bool IsInNetTie(const PNS::ITEM *aA) override
std::map< ITEM_KEY, PNS::CONSTRAINT > m_ruleMap
virtual PNS::NET_HANDLE DpCoupledNet(PNS::NET_HANDLE aNet) override
bool IsDrilledHole(const PNS::ITEM *aItem) override
void AddMockRule(PNS::CONSTRAINT_TYPE aType, const PNS::ITEM *aItemA, const PNS::ITEM *aItemB, PNS::CONSTRAINT &aConstraint)
bool IsNonPlatedSlot(const PNS::ITEM *aItem) override
bool HasUserDefinedPhysicalConstraint() override
virtual bool DpNetPair(const PNS::ITEM *aItem, PNS::NET_HANDLE &aNetP, PNS::NET_HANDLE &aNetN) override
virtual wxString NetName(PNS::NET_HANDLE aNet) override
int ClearanceEpsilon() const override
virtual int DpNetPolarity(PNS::NET_HANDLE aNet) override
Definition pad.h:61
bool HasGeometryDependentFunctions() const
void SetPosition(const VECTOR2I &aPos) override
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
A microvia stack: several single hop microvias, plus connecting traces for staggered stacks,...
void Regenerate(BOARD *aBoard, BOARD_COMMIT *aCommit)
Rebuild the member vias (and, for a staggered stack, the connecting traces) from the current stack se...
void SetStartLayer(PCB_LAYER_ID aLayer)
void SetViaSize(int aSize)
void SetViaDrill(int aDrill)
void SetEndLayer(PCB_LAYER_ID aLayer)
virtual VECTOR2I Anchor(int n) const override
Definition pns_arc.h:100
const ITEM_SET Traces() override
Function Traces()
NODE * CurrentNode() const override
Function CurrentNode()
bool Start(const VECTOR2I &aP, ITEM_SET &aPrimitives) override
Function Start()
bool Drag(const VECTOR2I &aP) override
Function Drag()
DRAGGER.
Definition pns_dragger.h:48
virtual bool Start(const VECTOR2I &aP, ITEM_SET &aPrimitives) override
Function Start()
void SetMode(PNS::DRAG_MODE aDragMode) override
virtual void SetWorld(NODE *aWorld)
Function SetWorld()
int Size() const
void Add(const LINE &aLine)
Base class for PNS router board items.
Definition pns_item.h:98
BOARD_ITEM * Parent() const
Definition pns_item.h:199
bool IsFreePad() const
Definition pns_item.h:288
void SetLayers(const PNS_LAYER_RANGE &aLayers)
Definition pns_item.h:213
void SetIsFreePad(bool aIsFreePad=true)
Definition pns_item.h:286
virtual const SHAPE * Shape(int aLayer) const
Return the geometrical shape of the item.
Definition pns_item.h:242
const PNS_LAYER_RANGE & Layers() const
Definition pns_item.h:212
virtual NET_HANDLE Net() const
Definition pns_item.h:210
void SetNet(NET_HANDLE aNet)
Definition pns_item.h:209
virtual int Layer() const
Definition pns_item.h:216
bool Collide(const ITEM *aHead, const NODE *aNode, int aLayer, COLLISION_SEARCH_CONTEXT *aCtx=nullptr) const
Check for a collision (clearance violation) with between us and item aOther.
Definition pns_item.cpp:305
bool OfKind(int aKindMask) const
Definition pns_item.h:181
virtual void Mark(int aMarker) const
Definition pns_item.h:261
virtual BOARD_ITEM * BoardItem() const
Definition pns_item.h:207
void SetRoutable(bool aRoutable)
Definition pns_item.h:283
bool IsLocked() const
Definition pns_item.h:278
Represents a track on a PCB, connecting two non-trivial joints (that is, vias, pads,...
Definition pns_line.h:62
void DragArc(const VECTOR2I &aP, int aIndex)
Definition pns_line.cpp:911
const SHAPE_LINE_CHAIN & CLine() const
Definition pns_line.h:146
SHAPE_LINE_CHAIN & Line()
Definition pns_line.h:145
void SetWidth(int aWidth)
Return line width.
Definition pns_line.h:159
Keep the router "world" - i.e.
Definition pns_node.h:243
void SetMaxClearance(int aClearance)
Assign a clearance resolution function object.
Definition pns_node.h:281
void AddRaw(ITEM *aItem, bool aAllowRedundant=false)
Definition pns_node.h:521
bool Add(std::unique_ptr< SEGMENT > aSegment, bool aAllowRedundant=false)
Add an item to the current node.
Definition pns_node.cpp:747
std::set< OBSTACLE > OBSTACLES
Definition pns_node.h:255
void SetRuleResolver(RULE_RESOLVER *aFunc)
Definition pns_node.h:287
Contain all persistent settings of the router, such as the mode, optimization effort,...
int Width() const override
Definition pns_segment.h:96
void SetWidth(int aWidth) override
Definition pns_segment.h:91
The actual Push and Shove algorithm.
Definition pns_shove.h:47
void SetShovePolicy(const LINKED_ITEM *aItem, int aPolicy)
void SetTrackWidth(int aWidth)
void SetBoardMinTrackWidth(int aWidth)
void SetDiffPairViaGapSameAsTraceGap(bool aEnable)
void SetDiffPairCopperToHole(int aCopperToHole)
int GetDiffPairCopperToHole() const
void SetViaDrill(int aDrill)
void SetDiffPairViaGap(int aGap)
void SetDiffPairHoleToHole(int aHoleToHole)
void SetViaDiameter(int aDiameter)
int EffectiveDiffPairViaGap() const
void SetPos(const VECTOR2I &aCenter)
Definition pns_solid.cpp:81
void SetShape(SHAPE *shape)
Definition pns_solid.h:113
bool inheritTrackWidthAndDpGap(PNS::ITEM *aItem, const VECTOR2I &aStartPosition, int *aInheritedWidth, int *aInheritedGap)
std::unique_ptr< PNS::VIA > syncVia(PCB_VIA *aVia)
Represent a contiguous set of PCB layers.
int Start() const
bool Overlaps(const PNS_LAYER_RANGE &aOther) const
int End() const
PNS_LAYER_RANGE Intersection(const PNS_LAYER_RANGE &aOther) const
Shortcut for comparisons/overlap tests.
Definition seg.h:38
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
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.
size_t ArcCount() const
@ DEGREES_T
Definition eda_angle.h:31
static FILENAME_RESOLVER * resolver
constexpr PCB_LAYER_ID PCBNEW_LAYER_ID_START
Definition layer_ids.h:170
@ Edge_Cuts
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ Margin
Definition layer_ids.h:109
@ In4_Cu
Definition layer_ids.h:65
@ In1_Cu
Definition layer_ids.h:62
@ PCB_LAYER_ID_COUNT
Definition layer_ids.h:167
@ F_Cu
Definition layer_ids.h:60
CONSTRAINT_TYPE
Definition pns_node.h:52
void * NET_HANDLE
Definition pns_item.h:55
@ PNS_MODE_ROUTE_SINGLE
Definition pns_router.h:68
@ DM_ARC
Definition pns_router.h:81
@ MK_LOCKED
Definition pns_item.h:45
std::unique_ptr< typename std::remove_const< T >::type > Clone(const T &aItem)
Definition pns_item.h:344
bool operator<(const ITEM_KEY &other) const
bool operator==(const ITEM_KEY &other) const
An abstract function object, returning a design rule (clearance, diff pair gap, etc) required between...
Definition pns_node.h:74
MINOPTMAX< int > m_Value
Definition pns_node.h:76
CONSTRAINT_TYPE m_Type
Definition pns_node.h:75
Hold an object colliding with another object, along with some useful data about the collision.
Definition pns_node.h:89
SETTINGS_MANAGER m_settingsManager
MOCK_RULE_RESOLVER m_ruleResolver
PNS::ROUTER * m_router
MOCK_PNS_KICAD_IFACE * m_iface
static PCB_SHAPE makeArc(const VECTOR2I &aCenter, const VECTOR2I &aStart, double aAngle, PCB_LAYER_ID aLayer=Dwgs_User)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
VECTOR3I v1(5, 5, 5)
static bool isEdge(const PNS::ITEM *aItem)
static bool isHole(const PNS::ITEM *aItem)
static void dumpObstacles(const PNS::NODE::OBSTACLES &obstacles)
static bool isCopper(const PNS::ITEM *aItem)
BOOST_AUTO_TEST_CASE(PCBViaBackdrillCloneRetainsData)
BOOST_FIXTURE_TEST_CASE(PNSShoveOwnsRootLineHistory, PNS_TEST_FIXTURE)
const SHAPE_LINE_CHAIN chain
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2I v2(1, 0)
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683