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>
38#include <router/pns_dragger.h>
40#include <router/pns_arc.h>
41#include <router/pns_line.h>
42#include <router/pns_item.h>
44#include <router/pns_node.h>
45#include <router/pns_router.h>
46#include <router/pns_segment.h>
47#include <router/pns_shove.h>
49#include <router/pns_solid.h>
50#include <router/pns_topology.h>
51#include <router/pns_via.h>
52
53static bool isCopper( const PNS::ITEM* aItem )
54{
55 if( !aItem )
56 return false;
57
58 BOARD_ITEM* parent = aItem->Parent();
59
60 if( parent && parent->Type() == PCB_PAD_T )
61 {
62 PAD* pad = static_cast<PAD*>( parent );
63
64 if( pad->IsAperturePad() || pad->IsNPTHWithNoCopper() )
65 return false;
66 }
67
68 return true;
69}
70
71
72static bool isHole( const PNS::ITEM* aItem )
73{
74 if( !aItem )
75 return false;
76
77 return aItem->OfKind( PNS::ITEM::HOLE_T );
78}
79
80
81static bool isEdge( const PNS::ITEM* aItem )
82{
83 if( !aItem )
84 return false;
85
86 const BOARD_ITEM *parent = aItem->BoardItem();
87
88 return parent && ( parent->IsOnLayer( Edge_Cuts ) || parent->IsOnLayer( Margin ) );
89}
90
91
93{
94public:
98
100
101 virtual int Clearance( const PNS::ITEM* aA, const PNS::ITEM* aB,
102 bool aUseClearanceEpsilon = true ) override
103 {
104 PNS::CONSTRAINT constraint;
105 int rv = 0;
106 PNS_LAYER_RANGE layers;
107
108 if( !aB )
109 layers = aA->Layers();
110 else if( isEdge( aA ) )
111 layers = aB->Layers();
112 else if( isEdge( aB ) )
113 layers = aA->Layers();
114 else
115 layers = aA->Layers().Intersection( aB->Layers() );
116
117 // Normalize layer range (no -1 magic numbers)
119
120 // electrical clearances are net-aware; physical clearances are net-blind; same-net or
121 // free-pad pairs with no positive physical rule fall back to -1.
122 const bool sameNet = aA && aB && aA->Net() && aA->Net() == aB->Net();
123 const bool freePad = aA && aB && ( aA->IsFreePad() || aB->IsFreePad() );
124
125 for( int layer = layers.Start(); layer <= layers.End(); ++layer )
126 {
127 if( !sameNet && !freePad )
128 {
129 if( isHole( aA ) && isHole( aB ) )
130 {
131 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_TO_HOLE, aA, aB, layer, &constraint ) )
132 {
133 if( constraint.m_Value.Min() > rv )
134 rv = constraint.m_Value.Min();
135 }
136 }
137 else if( isHole( aA ) || isHole( aB ) )
138 {
139 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_CLEARANCE, aA, aB, layer, &constraint ) )
140 {
141 if( constraint.m_Value.Min() > rv )
142 rv = constraint.m_Value.Min();
143 }
144 }
145 else if( isCopper( aA ) && ( !aB || isCopper( aB ) ) )
146 {
147 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_CLEARANCE, aA, aB, layer, &constraint ) )
148 {
149 if( constraint.m_Value.Min() > rv )
150 rv = constraint.m_Value.Min();
151 }
152 }
153 else if( isEdge( aA ) || ( aB && isEdge( aB ) ) )
154 {
155 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_EDGE_CLEARANCE, aA, aB, layer, &constraint ) )
156 {
157 if( constraint.m_Value.Min() > rv )
158 rv = constraint.m_Value.Min();
159 }
160 }
161 }
162
163 if( isHole( aA ) || isHole( aB ) )
164 {
165 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_PHYSICAL_HOLE_CLEARANCE, aA, aB, layer, &constraint ) )
166 {
167 if( constraint.m_Value.Min() > rv )
168 rv = constraint.m_Value.Min();
169 }
170 }
171
172 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_PHYSICAL_CLEARANCE, aA, aB, layer, &constraint ) )
173 {
174 if( constraint.m_Value.Min() > rv )
175 rv = constraint.m_Value.Min();
176 }
177 }
178
179 if( ( sameNet || freePad ) && rv == 0 )
180 rv = -1;
181
182 return rv;
183 }
184
186
187 virtual PNS::NET_HANDLE DpCoupledNet( PNS::NET_HANDLE aNet ) override { return nullptr; }
188 virtual int DpNetPolarity( PNS::NET_HANDLE aNet ) override { return -1; }
189
190 virtual bool DpNetPair( const PNS::ITEM* aItem, PNS::NET_HANDLE& aNetP,
191 PNS::NET_HANDLE& aNetN ) override
192 {
193 return false;
194 }
195
196 virtual int NetCode( PNS::NET_HANDLE aNet ) override
197 {
198 return -1;
199 }
200
201 virtual wxString NetName( PNS::NET_HANDLE aNet ) override
202 {
203 return wxEmptyString;
204 }
205
206 virtual bool QueryConstraint( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA,
207 const PNS::ITEM* aItemB, int aLayer,
208 PNS::CONSTRAINT* aConstraint ) override
209 {
210 ITEM_KEY key;
211
212 key.a = aItemA;
213 key.b = aItemB;
214 key.type = aType;
215
216 auto it = m_ruleMap.find( key );
217
218 if( it == m_ruleMap.end() )
219 {
220 int cl;
221 switch( aType )
222 {
228 return false;
230 break;
233 return false;
235 break;
236 default: return false;
237 }
238
239 //printf("GetDef %s %s %d cl %d\n", aItemA->KindStr().c_str(), aItemB->KindStr().c_str(), aType, cl );
240
241 aConstraint->m_Type = aType;
242 aConstraint->m_Value.SetMin( cl );
243
244 return true;
245 }
246 else
247 {
248 *aConstraint = it->second;
249 }
250
251 return true;
252 }
253
254 int ClearanceEpsilon() const override { return m_clearanceEpsilon; }
255
256 struct ITEM_KEY
257 {
258 const PNS::ITEM* a = nullptr;
259 const PNS::ITEM* b = nullptr;
261
262 bool operator==( const ITEM_KEY& other ) const
263 {
264 return a == other.a && b == other.b && type == other.type;
265 }
266
267 bool operator<( const ITEM_KEY& other ) const
268 {
269 if( a < other.a )
270 {
271 return true;
272 }
273 else if ( a == other.a )
274 {
275 if( b < other.b )
276 return true;
277 else if ( b == other.b )
278 return type < other.type;
279 }
280
281 return false;
282 }
283 };
284
285 bool IsInNetTie( const PNS::ITEM* aA ) override { return false; }
286
287 bool IsNetTieExclusion( const PNS::ITEM* aItem, const VECTOR2I& aCollisionPos,
288 const PNS::ITEM* aCollidingItem ) override
289 {
290 return false;
291 }
292
293 bool IsDrilledHole( const PNS::ITEM* aItem ) override { return false; }
294
295 bool IsNonPlatedSlot( const PNS::ITEM* aItem ) override { return false; }
296
297 bool IsKeepout( const PNS::ITEM* aObstacle, const PNS::ITEM* aItem, bool* aEnforce ) override
298 {
299 return false;
300 }
301
302 void AddMockRule( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA, const PNS::ITEM* aItemB,
303 PNS::CONSTRAINT& aConstraint )
304 {
305 ITEM_KEY key;
306
307 key.a = aItemA;
308 key.b = aItemB;
309 key.type = aType;
310
311 m_ruleMap[key] = aConstraint;
312 }
313
314 int m_defaultClearance = 200000;
315 int m_defaultHole2Hole = 220000;
317 int m_defaultPhysicalClearance = 0; // 0 means "rule does not match this pair"
318 int m_defaultPhysicalHoleClearance = 0; // 0 means "rule does not match this pair"
320
321private:
322 std::map<ITEM_KEY, PNS::CONSTRAINT> m_ruleMap;
324};
325
326struct PNS_TEST_FIXTURE;
327
329{
330public:
332 m_testFixture( aFixture )
333 {}
334
336
337 void HideItem( PNS::ITEM* aItem ) override {};
338 void DisplayItem( const PNS::ITEM* aItem, int aClearance, bool aEdit = false,
339 int aFlags = 0 ) override {};
341
342 bool TestInheritTrackWidth( PNS::ITEM* aItem, int* aInheritedWidth,
343 const VECTOR2I& aStartPosition = VECTOR2I() )
344 {
345 m_startLayer = aItem->Layer();
346
347 return inheritTrackWidthAndDpGap( aItem, aStartPosition, aInheritedWidth, nullptr );
348 }
349
350 std::unique_ptr<PNS::VIA> TestSyncVia( PCB_VIA* aVia ) { return syncVia( aVia ); }
351
352private:
354};
355
356
358{
360 {
361 m_router = new PNS::ROUTER;
362 m_iface = new MOCK_PNS_KICAD_IFACE( this );
363 m_router->SetInterface( m_iface );
364 }
365
367 {
368 delete m_router;
369 delete m_iface;
370 }
371
376 //std::unique_ptr<BOARD> m_board;
377};
378
379
384
385
386BOOST_FIXTURE_TEST_CASE( PNSShoveOwnsRootLineHistory, PNS_TEST_FIXTURE )
387{
388 PNS::NODE world;
389 world.SetRuleResolver( &m_ruleResolver );
390
391 PNS::SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ) ),
392 reinterpret_cast<PNS::NET_HANDLE>( 1 ) );
393 segment.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
394
395 PNS::LINE line;
396 line.Line().Append( VECTOR2I( 0, 0 ) );
397 line.Line().Append( VECTOR2I( 1000000, 0 ) );
398 line.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
399
400 PNS::SHOVE shove( &world, m_router );
401 shove.SetShovePolicy( &segment, PNS::SHOVE::SHP_SHOVE );
403}
404
405// A microvia stack's hops are copper on a net, so the router has to be able to anchor a route
406// on one. Non-routable means "obstacle carrying no connectivity", which would send
407// snapToItem() to the nearest grid point and start the track off the via. Locked is what
408// keeps the hop from being shoved.
409BOOST_FIXTURE_TEST_CASE( PNSViaStackHopIsRoutableButLocked, PNS_TEST_FIXTURE )
410{
411 BOARD board;
412 board.SetCopperLayerCount( 4 );
414
415 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
416 stack->SetStartLayer( F_Cu );
417 stack->SetEndLayer( In2_Cu );
418 stack->SetViaSize( 300000 );
419 stack->SetViaDrill( 150000 );
420
421 // Deliberately off grid, which is where the grid fallback shows itself.
422 stack->SetPosition( VECTOR2I( 1234567, 7654321 ) );
423 board.Add( stack );
424 stack->Regenerate( &board, nullptr );
425
426 PCB_VIA* hop = nullptr;
427
428 for( BOARD_ITEM* item : stack->GetBoardItems() )
429 {
430 if( item->Type() == PCB_VIA_T )
431 hop = static_cast<PCB_VIA*>( item );
432 }
433
434 BOOST_REQUIRE_MESSAGE( hop, "the stack must have built at least one microvia" );
435
436 m_iface->SetBoard( &board );
437
438 std::unique_ptr<PNS::VIA> synced = m_iface->TestSyncVia( hop );
439
440 BOOST_REQUIRE( synced );
441 BOOST_CHECK_MESSAGE( synced->IsRoutable(), "a stack hop must be a valid route anchor" );
442 BOOST_CHECK_MESSAGE( synced->IsLocked(), "a stack hop must still be locked against shoving" );
443}
444
445
446// Routability decides whether a route may anchor on an item, not whether it collides with one.
447// A stack hop has to obstruct a foreign-net via either way.
448BOOST_FIXTURE_TEST_CASE( PNSViaStackHopIsAnObstacle, PNS_TEST_FIXTURE )
449{
450 BOARD board;
451 board.SetCopperLayerCount( 4 );
453
454 VECTOR2I at( 5000000, 5000000 );
455
456 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
457 stack->SetStartLayer( F_Cu );
458 stack->SetEndLayer( In2_Cu );
459 stack->SetViaSize( 300000 );
460 stack->SetViaDrill( 150000 );
461 stack->SetPosition( at );
462 board.Add( stack );
463 stack->Regenerate( &board, nullptr );
464
465 m_iface->SetBoard( &board );
466
467 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
468 world->SetMaxClearance( 10000000 );
469 world->SetRuleResolver( &m_ruleResolver );
470
471 int hops = 0;
472
473 for( BOARD_ITEM* item : stack->GetBoardItems() )
474 {
475 if( item->Type() != PCB_VIA_T )
476 continue;
477
478 std::unique_ptr<PNS::VIA> synced = m_iface->TestSyncVia( static_cast<PCB_VIA*>( item ) );
479 BOOST_REQUIRE( synced );
480 world->AddRaw( synced.release() );
481 hops++;
482 }
483
484 BOOST_REQUIRE_EQUAL( hops, 2 );
485
486 PNS::VIA* intruder = new PNS::VIA( at, PNS_LAYER_RANGE( F_Cu, B_Cu ), 300000, 150000 );
487 intruder->SetNet( (PNS::NET_HANDLE) 99 );
488 world->AddRaw( intruder );
489
490 m_ruleResolver.m_defaultClearance = 200000;
491
492 PNS::NODE::OBSTACLES obstacles;
493 world->QueryColliding( intruder, obstacles );
494
495 BOOST_CHECK_MESSAGE( obstacles.size() > 0, "a stack hop must obstruct a foreign-net via" );
496}
497
498
499static void dumpObstacles( const PNS::NODE::OBSTACLES &obstacles )
500{
501 for( const PNS::OBSTACLE& obs : obstacles )
502 {
503 BOOST_TEST_MESSAGE( wxString::Format( "%p [%s] - %p [%s], clearance %d",
504 obs.m_head, obs.m_head->KindStr().c_str(),
505 obs.m_item, obs.m_item->KindStr().c_str(),
506 obs.m_clearance ) );
507 }
508}
509
511{
512 PNS::VIA* v1 = new PNS::VIA( VECTOR2I( 0, 1000000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
513 PNS::VIA* v2 = new PNS::VIA( VECTOR2I( 0, 2000000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
514
515 std::unique_ptr<PNS::NODE> world ( new PNS::NODE );
516
517 v1->SetNet( (PNS::NET_HANDLE) 1 );
518 v2->SetNet( (PNS::NET_HANDLE) 2 );
519
520 world->SetMaxClearance( 10000000 );
521 world->SetRuleResolver( &m_ruleResolver );
522
523 world->AddRaw( v1 );
524 world->AddRaw( v2 );
525
526 BOOST_TEST_MESSAGE( "via to via, no violations" );
527 {
528 PNS::NODE::OBSTACLES obstacles;
529 int count = world->QueryColliding( v1, obstacles );
530 dumpObstacles( obstacles );
531 BOOST_CHECK_EQUAL( obstacles.size(), 0 );
532 BOOST_CHECK_EQUAL( count, 0 );
533 }
534
535 BOOST_TEST_MESSAGE( "via to via, forced copper to copper violation" );
536 {
537 PNS::NODE::OBSTACLES obstacles;
538 m_ruleResolver.m_defaultClearance = 1000000;
539 world->QueryColliding( v1, obstacles );
540 dumpObstacles( obstacles );
541
542 BOOST_CHECK_EQUAL( obstacles.size(), 1 );
543 const auto& first = *obstacles.begin();
544
545 BOOST_CHECK_EQUAL( first.m_head, v1 );
546 BOOST_CHECK_EQUAL( first.m_item, v2 );
547 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultClearance );
548 }
549
550 BOOST_TEST_MESSAGE( "via to via, forced hole to hole violation" );
551 {
552 PNS::NODE::OBSTACLES obstacles;
553 m_ruleResolver.m_defaultClearance = 200000;
554 m_ruleResolver.m_defaultHole2Hole = 1000000;
555
556 world->QueryColliding( v1, obstacles );
557 dumpObstacles( obstacles );
558
559 BOOST_CHECK_EQUAL( obstacles.size(), 1 );
560 auto iter = obstacles.begin();
561 const auto& first = *iter++;
562
563 BOOST_CHECK_EQUAL( first.m_head, v1->Hole() );
564 BOOST_CHECK_EQUAL( first.m_item, v2->Hole() );
565 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Hole );
566 }
567
568 BOOST_TEST_MESSAGE( "via to via, forced copper to hole violation" );
569 {
570 PNS::NODE::OBSTACLES obstacles;
571 m_ruleResolver.m_defaultHole2Hole = 220000;
572 m_ruleResolver.m_defaultHole2Copper = 1000000;
573
574 world->QueryColliding( v1, obstacles );
575 dumpObstacles( obstacles );
576
577 BOOST_CHECK_EQUAL( obstacles.size(), 2 );
578 auto iter = obstacles.begin();
579 const auto& first = *iter++;
580
581 // There is no guarantee on what order the two collisions will be in...
582 BOOST_CHECK( ( first.m_head == v1 && first.m_item == v2->Hole() )
583 || ( first.m_head == v1->Hole() && first.m_item == v2 ) );
584
585 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Copper );
586 }
587}
588
589
590BOOST_FIXTURE_TEST_CASE( PNSViaBackdrillRetention, PNS_TEST_FIXTURE )
591{
592 PNS::VIA via( VECTOR2I( 1000, 2000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 40000, 20000, nullptr,
594 via.SetHoleLayers( PNS_LAYER_RANGE( F_Cu, In2_Cu ) );
595 via.SetHolePostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) );
596 via.SetSecondaryDrill( std::optional<int>( 12000 ) );
597 via.SetSecondaryHoleLayers( std::optional<PNS_LAYER_RANGE>( PNS_LAYER_RANGE( F_Cu, In1_Cu ) ) );
598 via.SetSecondaryHolePostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED ) );
599
600 PNS::VIA viaCopy( via );
601 std::unique_ptr<PNS::VIA> viaClone( via.Clone() );
602
603 auto checkVia = [&]( const PNS::VIA& candidate )
604 {
605 BOOST_CHECK_EQUAL( candidate.HoleLayers().Start(), via.HoleLayers().Start() );
606 BOOST_CHECK_EQUAL( candidate.HoleLayers().End(), via.HoleLayers().End() );
607 BOOST_CHECK( candidate.HolePostMachining().has_value() );
608 BOOST_CHECK( candidate.HolePostMachining().value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK );
609 BOOST_CHECK( candidate.SecondaryDrill().has_value() );
610 BOOST_CHECK_EQUAL( candidate.SecondaryDrill().value(), via.SecondaryDrill().value() );
611 BOOST_CHECK( candidate.SecondaryHoleLayers().has_value() );
612 BOOST_CHECK_EQUAL( candidate.SecondaryHoleLayers()->Start(),
613 via.SecondaryHoleLayers()->Start() );
614 BOOST_CHECK_EQUAL( candidate.SecondaryHoleLayers()->End(),
615 via.SecondaryHoleLayers()->End() );
616 BOOST_CHECK( candidate.SecondaryHolePostMachining().has_value() );
617
618 // run this BOOST_CHECK only if possible to avoid crash
619 if( candidate.SecondaryHolePostMachining().has_value() )
620 BOOST_CHECK( candidate.SecondaryHolePostMachining().value() == via.SecondaryHolePostMachining().value() );
621 };
622
623 checkVia( viaCopy );
624 checkVia( *viaClone );
625}
626
627
628BOOST_AUTO_TEST_CASE( PCBViaBackdrillCloneRetainsData )
629{
630 BOARD board;
631 PCB_VIA via( &board );
632
633 via.SetPrimaryDrillStartLayer( F_Cu );
634 via.SetPrimaryDrillEndLayer( B_Cu );
635 via.SetFrontPostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) );
636 via.SetSecondaryDrillSize( std::optional<int>( 15000 ) );
637 via.SetSecondaryDrillStartLayer( F_Cu );
638 via.SetSecondaryDrillEndLayer( In2_Cu );
639
640 via.SetBackPostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE ) );
641 via.SetTertiaryDrillSize( std::optional<int>( 8000 ) );
642 via.SetTertiaryDrillStartLayer( B_Cu );
643 via.SetTertiaryDrillEndLayer( In4_Cu );
644
645 PCB_VIA viaCopy( via );
646 std::unique_ptr<PCB_VIA> viaClone( static_cast<PCB_VIA*>( via.Clone() ) );
647
648 auto checkVia = [&]( const PCB_VIA& candidate )
649 {
650 BOOST_CHECK_EQUAL( candidate.GetPrimaryDrillStartLayer(), via.GetPrimaryDrillStartLayer() );
651 BOOST_CHECK_EQUAL( candidate.GetPrimaryDrillEndLayer(), via.GetPrimaryDrillEndLayer() );
652 BOOST_CHECK( candidate.GetFrontPostMachining().has_value() );
653 BOOST_CHECK_EQUAL( static_cast<int>( candidate.GetFrontPostMachining().value() ),
654 static_cast<int>( via.GetFrontPostMachining().value() ) );
655 BOOST_CHECK( candidate.GetSecondaryDrillSize().has_value() );
656 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillSize().value(),
657 via.GetSecondaryDrillSize().value() );
658 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillStartLayer(),
659 via.GetSecondaryDrillStartLayer() );
660 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillEndLayer(),
661 via.GetSecondaryDrillEndLayer() );
662
663 BOOST_CHECK( candidate.GetBackPostMachining().has_value() );
664 BOOST_CHECK_EQUAL( static_cast<int>( candidate.GetBackPostMachining().value() ),
665 static_cast<int>( via.GetBackPostMachining().value() ) );
666 BOOST_CHECK( candidate.GetTertiaryDrillSize().has_value() );
667 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillSize().value(),
668 via.GetTertiaryDrillSize().value() );
669 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillStartLayer(),
670 via.GetTertiaryDrillStartLayer() );
671 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillEndLayer(),
672 via.GetTertiaryDrillEndLayer() );
673 };
674
675 checkVia( viaCopy );
676 checkVia( *viaClone );
677}
678
679
688BOOST_AUTO_TEST_CASE( PNSLayerRangeSwapBehavior )
689{
690 // On a 2-layer board with FRONT_INNER_BACK mode, BoardCopperLayerCount() returns 2.
691 // The code would calculate PNS_LAYER_RANGE(1, 2 - 2) = PNS_LAYER_RANGE(1, 0)
692 // Since start > end, the constructor swaps them to (0, 1), which would span
693 // both F_Cu and B_Cu incorrectly.
694
695 PNS_LAYER_RANGE innerLayersRange2Layer( 1, 0 ); // What would happen on 2-layer board
696
697 // Verify the swap behavior that causes the bug
698 BOOST_CHECK_EQUAL( innerLayersRange2Layer.Start(), 0 );
699 BOOST_CHECK_EQUAL( innerLayersRange2Layer.End(), 1 );
700 BOOST_CHECK( innerLayersRange2Layer.Overlaps( 0 ) ); // F_Cu
701 BOOST_CHECK( innerLayersRange2Layer.Overlaps( 1 ) ); // B_Cu
702
703 // On a 4-layer board, inner layers are 1 and 2, so PNS_LAYER_RANGE(1, 4-2) = (1, 2)
704 PNS_LAYER_RANGE innerLayersRange4Layer( 1, 2 ); // Correct for 4-layer board
705
706 BOOST_CHECK_EQUAL( innerLayersRange4Layer.Start(), 1 );
707 BOOST_CHECK_EQUAL( innerLayersRange4Layer.End(), 2 );
708 BOOST_CHECK( !innerLayersRange4Layer.Overlaps( 0 ) ); // F_Cu - should not overlap
709 BOOST_CHECK( innerLayersRange4Layer.Overlaps( 1 ) ); // In1_Cu
710 BOOST_CHECK( innerLayersRange4Layer.Overlaps( 2 ) ); // In2_Cu
711 BOOST_CHECK( !innerLayersRange4Layer.Overlaps( 3 ) ); // B_Cu - should not overlap
712}
713
714
722BOOST_FIXTURE_TEST_CASE( PNSSegmentSplitPreservesLockedState, PNS_TEST_FIXTURE )
723{
724 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
725 world->SetMaxClearance( 10000000 );
726 world->SetRuleResolver( &m_ruleResolver );
727
729
730 VECTOR2I segStart( 0, 0 );
731 VECTOR2I segEnd( 10000000, 0 );
732 VECTOR2I splitPt( 5000000, 0 );
733
734 PNS::SEGMENT* lockedSeg = new PNS::SEGMENT( SEG( segStart, segEnd ), net );
735 lockedSeg->SetWidth( 250000 );
736 lockedSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
737 lockedSeg->Mark( PNS::MK_LOCKED );
738
739 BOOST_CHECK( lockedSeg->IsLocked() );
740
741 world->AddRaw( lockedSeg );
742
743 // Clone the locked segment and set up two halves (simulating SplitAdjacentSegments)
744 std::unique_ptr<PNS::SEGMENT> clone1( PNS::Clone( *lockedSeg ) );
745 std::unique_ptr<PNS::SEGMENT> clone2( PNS::Clone( *lockedSeg ) );
746
747 clone1->SetEnds( segStart, splitPt );
748 clone2->SetEnds( splitPt, segEnd );
749
750 BOOST_CHECK_MESSAGE( clone1->IsLocked(),
751 "First half of split locked segment must retain locked state" );
752 BOOST_CHECK_MESSAGE( clone2->IsLocked(),
753 "Second half of split locked segment must retain locked state" );
754 BOOST_CHECK_EQUAL( clone1->Width(), lockedSeg->Width() );
755 BOOST_CHECK_EQUAL( clone2->Width(), lockedSeg->Width() );
756}
757
758
766BOOST_FIXTURE_TEST_CASE( PNSInheritTrackWidthCursorProximity, PNS_TEST_FIXTURE )
767{
768 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
769 world->SetMaxClearance( 10000000 );
770 world->SetRuleResolver( &m_ruleResolver );
771
772 VECTOR2I padPos( 0, 0 );
774
775 // Pad at origin with a small circular shape
777 pad->SetShape( new SHAPE_CIRCLE( padPos, 500000 ) );
778 pad->SetPos( padPos );
779 pad->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
780 pad->SetNet( net );
781 world->AddRaw( pad );
782
783 // Narrow track going right (250um width)
784 int narrowWidth = 250000;
785 PNS::SEGMENT* narrowSeg = new PNS::SEGMENT( SEG( padPos, VECTOR2I( 5000000, 0 ) ), net );
786 narrowSeg->SetWidth( narrowWidth );
787 narrowSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
788 world->AddRaw( narrowSeg );
789
790 // Wide track going up (500um width)
791 int wideWidth = 500000;
792 PNS::SEGMENT* wideSeg = new PNS::SEGMENT( SEG( padPos, VECTOR2I( 0, -5000000 ) ), net );
793 wideSeg->SetWidth( wideWidth );
794 wideSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
795 world->AddRaw( wideSeg );
796
797 int inherited = 0;
798
799 // Without cursor position, should fall back to minimum width
800 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited ) );
801 BOOST_CHECK_EQUAL( inherited, narrowWidth );
802
803 // Cursor near the narrow track (to the right) should select narrow width
804 inherited = 0;
805 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 2000000, 0 ) ) );
806 BOOST_CHECK_EQUAL( inherited, narrowWidth );
807
808 // Cursor near the wide track (upward) should select wide width
809 inherited = 0;
810 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 0, -2000000 ) ) );
811 BOOST_CHECK_EQUAL( inherited, wideWidth );
812
813 // Cursor slightly offset toward narrow track should still select narrow
814 inherited = 0;
815 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 100000, 50000 ) ) );
816 BOOST_CHECK_EQUAL( inherited, narrowWidth );
817
818 // Cursor slightly offset toward wide track should select wide
819 inherited = 0;
820 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 50000, -100000 ) ) );
821 BOOST_CHECK_EQUAL( inherited, wideWidth );
822}
823
824
832BOOST_AUTO_TEST_CASE( PCBExprGeometryDependentFunctionDetection )
833{
834 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
835
836 auto compileAndCheck = [&]( const wxString& aExpr, bool aExpectGeometry )
837 {
838 PCBEXPR_UCODE ucode;
839 PCBEXPR_CONTEXT ctx( 0, F_Cu );
840
841 bool ok = compiler.Compile( aExpr.ToUTF8().data(), &ucode, &ctx );
842 BOOST_CHECK_MESSAGE( ok, "Failed to compile: " + aExpr );
843
844 if( ok )
845 {
846 BOOST_CHECK_MESSAGE( ucode.HasGeometryDependentFunctions() == aExpectGeometry,
847 wxString::Format( "Expression '%s': expected geometry=%s, got %s",
848 aExpr,
849 aExpectGeometry ? "true" : "false",
851 ? "true" : "false" ) );
852 }
853 };
854
855 // Property-based conditions should NOT be geometry-dependent
856 compileAndCheck( wxT( "A.NetClass == 'Power'" ), false );
857 compileAndCheck( wxT( "A.Type == 'via'" ), false );
858 compileAndCheck( wxT( "A.NetName == '/VCC'" ), false );
859
860 // Geometry-dependent functions SHOULD be detected
861 compileAndCheck( wxT( "A.intersectsCourtyard('U1')" ), true );
862 compileAndCheck( wxT( "A.intersectsArea('Zone1')" ), true );
863 compileAndCheck( wxT( "A.enclosedByArea('Zone1')" ), true );
864 compileAndCheck( wxT( "A.intersectsFrontCourtyard('U1')" ), true );
865 compileAndCheck( wxT( "A.intersectsBackCourtyard('U1')" ), true );
866
867 // Deprecated aliases should also be detected
868 compileAndCheck( wxT( "A.insideCourtyard('U1')" ), true );
869 compileAndCheck( wxT( "A.insideArea('Zone1')" ), true );
870}
871
872
881BOOST_FIXTURE_TEST_CASE( PNSCollideSimpleNullShapeGuard, PNS_TEST_FIXTURE )
882{
883 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
884 world->SetMaxClearance( 10000000 );
885 world->SetRuleResolver( &m_ruleResolver );
886
889
890 PNS::SOLID* solid1 = new PNS::SOLID;
891 solid1->SetShape( new SHAPE_CIRCLE( VECTOR2I( 0, 0 ), 500000 ) );
892 solid1->SetPos( VECTOR2I( 0, 0 ) );
893 solid1->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
894 solid1->SetNet( net1 );
895
896 PNS::SOLID* solid2 = new PNS::SOLID;
897 solid2->SetShape( new SHAPE_CIRCLE( VECTOR2I( 100000, 0 ), 500000 ) );
898 solid2->SetPos( VECTOR2I( 100000, 0 ) );
899 solid2->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
900 solid2->SetNet( net2 );
901
902 world->AddRaw( solid1 );
903 world->AddRaw( solid2 );
904
905 // Verify collision works normally with valid shapes
906 PNS::NODE::OBSTACLES obstacles;
907 int count = world->QueryColliding( solid1, obstacles );
908 BOOST_CHECK( count > 0 );
909
910 // Exercise the null-shape guard in collideSimple by calling Collide() directly with a
911 // null-shape solid as the head item. This bypasses the spatial index (which requires a
912 // valid shape for bounding-box computation) and hits the exact code path the guard protects.
913 PNS::SOLID nullShapeSolid;
914 nullShapeSolid.SetPos( VECTOR2I( 0, 0 ) );
915 nullShapeSolid.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
916 nullShapeSolid.SetNet( net2 );
917
918 bool collided = solid1->Collide( &nullShapeSolid, world.get(), F_Cu, nullptr );
919 BOOST_CHECK( !collided );
920}
921
922
923// Does any dragged item have a vertex at aPoint? aCopperOnly ignores pads and vias.
924static bool tracesTouch( PNS::ITEM_SET& aTraces, const VECTOR2I& aPoint, bool aCopperOnly = false )
925{
926 const int tolerance = 1000;
927
928 for( PNS::ITEM* item : aTraces.Items() )
929 {
930 if( aCopperOnly && item->OfKind( PNS::ITEM::SOLID_T | PNS::ITEM::VIA_T ) )
931 continue;
932
933 if( item->Kind() == PNS::ITEM::LINE_T )
934 {
935 const SHAPE_LINE_CHAIN& chain = static_cast<PNS::LINE*>( item )->CLine();
936
937 for( int ii = 0; ii < chain.PointCount(); ii++ )
938 {
939 if( ( chain.CPoint( ii ) - aPoint ).EuclideanNorm() <= tolerance )
940 return true;
941 }
942 }
943 else
944 {
945 for( int ii = 0; ii < item->AnchorCount(); ii++ )
946 {
947 if( ( item->Anchor( ii ) - aPoint ).EuclideanNorm() <= tolerance )
948 return true;
949 }
950 }
951 }
952
953 return false;
954}
955
956
957static PNS::SOLID* makeTestPad( const VECTOR2I& aPos, PNS::NET_HANDLE aNet, int aRadius = 500000 )
958{
960
961 pad->SetShape( new SHAPE_CIRCLE( aPos, aRadius ) );
962 pad->SetPos( aPos );
963 pad->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
964 pad->SetNet( aNet );
965 pad->SetRoutable( true );
966
967 return pad;
968}
969
970
971static PNS::SEGMENT* makeTestTrack( const VECTOR2I& aStart, const VECTOR2I& aEnd, PNS::NET_HANDLE aNet )
972{
973 PNS::SEGMENT* track = new PNS::SEGMENT( SEG( aStart, aEnd ), aNet );
974
975 track->SetWidth( 250000 );
976 track->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
977
978 return track;
979}
980
981
982static std::unique_ptr<PNS::NODE> makeTestWorld( PNS::RULE_RESOLVER* aResolver )
983{
984 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
985
986 world->SetMaxClearance( 10000000 );
987 world->SetRuleResolver( aResolver );
988
989 return world;
990}
991
992
993// Pad and track selected, far end free: both travel whole.
994BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerCarriesSelectedCopper, PNS_TEST_FIXTURE )
995{
996 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
998 VECTOR2I padPos( 0, 0 );
999 VECTOR2I freeEnd( 2500000, 0 );
1000
1001 PNS::SOLID* pad = makeTestPad( padPos, net );
1002 PNS::SEGMENT* track = makeTestTrack( padPos, freeEnd, net );
1003 world->AddRaw( pad );
1004 world->AddRaw( track );
1005
1006 PNS::BLOCK_DRAGGER dragger( m_router );
1007 dragger.SetWorld( world.get() );
1008
1009 PNS::ITEM_SET itemsToDrag;
1010 itemsToDrag.Add( pad );
1011 itemsToDrag.Add( track );
1012 BOOST_REQUIRE( dragger.Start( padPos, itemsToDrag ) );
1013
1014 const VECTOR2I delta( 1000000, 1000000 );
1015 dragger.Drag( padPos + delta );
1016
1017 PNS::ITEM_SET traces = dragger.Traces();
1018 PNS::NODE* node = dragger.CurrentNode();
1019
1020 BOOST_CHECK( tracesTouch( traces, padPos + delta ) );
1021 BOOST_CHECK( tracesTouch( traces, freeEnd + delta ) );
1022 BOOST_CHECK( node->Overrides( pad ) );
1023 BOOST_CHECK( node->Overrides( track ) );
1024
1025 world->KillChildren();
1026}
1027
1028
1029// A track can end anywhere inside a pad, not just on its anchor, and still has to follow it.
1030BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerFollowsTrackEndingOffThePadAnchor, PNS_TEST_FIXTURE )
1031{
1032 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1034 VECTOR2I padPos( 0, 0 );
1035 VECTOR2I trackStart( 300000, 0 ); // inside the pad, away from its anchor
1036 VECTOR2I trackEnd( 3000000, 0 );
1037
1038 PNS::SOLID* pad = makeTestPad( padPos, net ); // a circle of radius 500000
1039 PNS::SEGMENT* track = makeTestTrack( trackStart, trackEnd, net );
1040 world->AddRaw( pad );
1041 world->AddRaw( track );
1042
1043 PNS::BLOCK_DRAGGER dragger( m_router );
1044 dragger.SetWorld( world.get() );
1045
1046 PNS::ITEM_SET itemsToDrag;
1047 itemsToDrag.Add( pad );
1048 BOOST_REQUIRE( dragger.Start( padPos, itemsToDrag ) );
1049
1050 const VECTOR2I delta( 0, 2000000 );
1051 dragger.Drag( padPos + delta );
1052
1053 PNS::ITEM_SET traces = dragger.Traces();
1054 PNS::NODE* node = dragger.CurrentNode();
1055
1056 BOOST_CHECK( tracesTouch( traces, trackStart + delta, true ) );
1057 BOOST_CHECK( tracesTouch( traces, trackEnd, true ) );
1058 BOOST_CHECK( node->Overrides( track ) );
1059
1060 world->KillChildren();
1061}
1062
1063
1064// An unselected pad holds a selected track even when the track ends away from its anchor.
1065BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerPinsSelectedTrackInsideUnselectedPad, PNS_TEST_FIXTURE )
1066{
1067 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1069 VECTOR2I padPos( 0, 0 );
1070 VECTOR2I trackStart( 300000, 0 ); // inside the pad, away from its anchor
1071 VECTOR2I trackEnd( 3000000, 0 );
1072
1073 PNS::SOLID* pad = makeTestPad( padPos, net ); // a circle of radius 500000, not selected
1074 PNS::SEGMENT* track = makeTestTrack( trackStart, trackEnd, net );
1075 world->AddRaw( pad );
1076 world->AddRaw( track );
1077
1078 PNS::BLOCK_DRAGGER dragger( m_router );
1079 dragger.SetWorld( world.get() );
1080
1081 PNS::ITEM_SET itemsToDrag;
1082 itemsToDrag.Add( track );
1083 BOOST_REQUIRE( dragger.Start( trackEnd, itemsToDrag ) );
1084
1085 const VECTOR2I delta( 0, 2000000 );
1086 dragger.Drag( trackEnd + delta );
1087
1088 PNS::ITEM_SET traces = dragger.Traces();
1089
1090 // The pad cannot move, so the end inside it must stay and the track must stretch
1091 BOOST_CHECK( tracesTouch( traces, trackStart, true ) );
1092 BOOST_CHECK( !tracesTouch( traces, trackStart + delta, true ) );
1093 BOOST_CHECK( tracesTouch( traces, trackEnd + delta, true ) );
1094 BOOST_CHECK( dragger.CurrentNode()->Overrides( track ) );
1095 BOOST_CHECK( !dragger.CurrentNode()->Overrides( pad ) );
1096
1097 world->KillChildren();
1098}
1099
1100
1101// A junction inside a selected pad, away from its anchor, travels with that pad.
1102BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerCarriesJunctionInsideSelectedPad, PNS_TEST_FIXTURE )
1103{
1104 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1106
1107 VECTOR2I padPos( 0, 0 );
1108 VECTOR2I junction( 800000, 0 ); // inside the pad, off its anchor, and a three way joint
1109 VECTOR2I runEnd( 4000000, 0 );
1110 VECTOR2I selFar( 6000000, 0 );
1111
1112 PNS::SOLID* pad = makeTestPad( padPos, net, 1500000 );
1113 PNS::SEGMENT* selected = makeTestTrack( selFar, runEnd, net );
1114 PNS::SEGMENT* run = makeTestTrack( runEnd, junction, net );
1115 PNS::SEGMENT* branchUp = makeTestTrack( junction, VECTOR2I( 800000, 2500000 ), net );
1116 PNS::SEGMENT* branchDown = makeTestTrack( junction, VECTOR2I( 800000, -2500000 ), net );
1117
1118 world->AddRaw( pad );
1119 world->AddRaw( selected );
1120 world->AddRaw( run );
1121 world->AddRaw( branchUp );
1122 world->AddRaw( branchDown );
1123
1124 PNS::BLOCK_DRAGGER dragger( m_router );
1125 dragger.SetWorld( world.get() );
1126
1127 PNS::ITEM_SET itemsToDrag;
1128 itemsToDrag.Add( pad );
1129 itemsToDrag.Add( selected );
1130 BOOST_REQUIRE( dragger.Start( padPos, itemsToDrag ) );
1131
1132 const VECTOR2I delta( 0, 3000000 );
1133 dragger.Drag( padPos + delta );
1134
1135 PNS::ITEM_SET traces = dragger.Traces();
1136
1137 // The pad is moving and the junction sits inside it, so the junction has to move too
1138 BOOST_CHECK( tracesTouch( traces, junction + delta, true ) );
1139 BOOST_CHECK( !tracesTouch( traces, junction, true ) );
1140 BOOST_CHECK( dragger.CurrentNode()->Overrides( run ) );
1141
1142 world->KillChildren();
1143}
1144
1145
1146// A selected arc with both ends free travels rigidly, keeping its shape.
1147BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerCarriesSelectedArc, PNS_TEST_FIXTURE )
1148{
1149 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1151 VECTOR2I arcStart( 0, 0 );
1152 VECTOR2I arcMid( 1000000, 1000000 );
1153 VECTOR2I arcEnd( 2000000, 0 );
1154
1155 PNS::ARC* arc = new PNS::ARC( SHAPE_ARC( arcStart, arcMid, arcEnd, 250000 ), net );
1156 arc->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1157 world->AddRaw( arc );
1158
1159 PNS::BLOCK_DRAGGER dragger( m_router );
1160 dragger.SetWorld( world.get() );
1161
1162 PNS::ITEM_SET itemsToDrag;
1163 itemsToDrag.Add( arc );
1164 BOOST_REQUIRE( dragger.Start( arcStart, itemsToDrag ) );
1165 BOOST_CHECK_EQUAL( dragger.AnchoredLines().size(), 0u );
1166
1167 const VECTOR2I delta( 0, 2000000 );
1168 dragger.Drag( arcStart + delta );
1169
1170 PNS::ITEM_SET traces = dragger.Traces();
1171
1172 BOOST_CHECK( tracesTouch( traces, arcStart + delta, true ) );
1173 BOOST_CHECK( tracesTouch( traces, arcEnd + delta, true ) );
1174 BOOST_CHECK( dragger.CurrentNode()->Overrides( arc ) );
1175
1176 world->KillChildren();
1177}
1178
1179
1180// A selected arc held at one end is rerouted rather than parked, and keeps being an arc.
1181BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerReroutesSelectedArcHeldAtOneEnd, PNS_TEST_FIXTURE )
1182{
1183 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1185 VECTOR2I arcStart( 0, 0 );
1186 VECTOR2I arcMid( 1000000, 1000000 );
1187 VECTOR2I arcEnd( 2000000, 0 );
1188
1189 world->AddRaw( makeTestPad( arcStart, net, 300000 ) ); // unselected, holds the start
1190
1191 PNS::ARC* arc = new PNS::ARC( SHAPE_ARC( arcStart, arcMid, arcEnd, 250000 ), net );
1192 arc->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1193 world->AddRaw( arc );
1194
1195 PNS::BLOCK_DRAGGER dragger( m_router );
1196 dragger.SetWorld( world.get() );
1197
1198 PNS::ITEM_SET itemsToDrag;
1199 itemsToDrag.Add( arc );
1200 BOOST_REQUIRE( dragger.Start( arcEnd, itemsToDrag ) );
1201
1202 // Only one end is held, so this is a reroute and not an anchored line
1203 BOOST_CHECK_EQUAL( dragger.AnchoredLines().size(), 0u );
1204
1205 const VECTOR2I delta( 0, 2000000 );
1206 dragger.Drag( arcEnd + delta );
1207
1208 PNS::ITEM_SET traces = dragger.Traces();
1209
1210 BOOST_CHECK( tracesTouch( traces, arcStart, true ) );
1211 BOOST_CHECK( !tracesTouch( traces, arcStart + delta, true ) );
1212 BOOST_CHECK( dragger.CurrentNode()->Overrides( arc ) );
1213
1214 for( PNS::ITEM* item : traces.Items() )
1215 {
1216 if( item->Kind() == PNS::ITEM::LINE_T )
1217 BOOST_CHECK( static_cast<PNS::LINE*>( item )->CLine().ArcCount() > 0 );
1218 }
1219
1220 world->KillChildren();
1221}
1222
1223
1224// Two selected pads with an unselected track between them: the track travels whole.
1225BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerCarriesTrackBetweenSelectedPads, PNS_TEST_FIXTURE )
1226{
1227 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1229 VECTOR2I padAPos( 0, 0 );
1230 VECTOR2I padBPos( 5000000, 0 );
1231
1232 PNS::SOLID* padA = makeTestPad( padAPos, net );
1233 PNS::SOLID* padB = makeTestPad( padBPos, net );
1234 PNS::SEGMENT* track = makeTestTrack( padAPos, padBPos, net );
1235 world->AddRaw( padA );
1236 world->AddRaw( padB );
1237 world->AddRaw( track );
1238
1239 PNS::BLOCK_DRAGGER dragger( m_router );
1240 dragger.SetWorld( world.get() );
1241
1242 PNS::ITEM_SET itemsToDrag;
1243 itemsToDrag.Add( padA );
1244 itemsToDrag.Add( padB );
1245 BOOST_REQUIRE( dragger.Start( padAPos, itemsToDrag ) );
1246
1247 const VECTOR2I delta( 0, 2000000 );
1248 dragger.Drag( padAPos + delta );
1249
1250 PNS::ITEM_SET traces = dragger.Traces();
1251 PNS::NODE* node = dragger.CurrentNode();
1252
1253 // Both ends moved by the same delta, so the track kept its length
1254 BOOST_CHECK( tracesTouch( traces, padAPos + delta, true ) );
1255 BOOST_CHECK( tracesTouch( traces, padBPos + delta, true ) );
1256 BOOST_CHECK( !tracesTouch( traces, padBPos, true ) );
1257 BOOST_CHECK( node->Overrides( padA ) );
1258 BOOST_CHECK( node->Overrides( padB ) );
1259 BOOST_CHECK( node->Overrides( track ) );
1260
1261 world->KillChildren();
1262}
1263
1264
1265// Far end held by an unselected pad: the track stretches, the held end stays.
1266BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerStretchesTrackHeldAtOneEnd, PNS_TEST_FIXTURE )
1267{
1268 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1270 VECTOR2I draggedPadPos( 0, 0 );
1271 VECTOR2I heldPadPos( 5000000, 0 );
1272
1273 PNS::SOLID* draggedPad = makeTestPad( draggedPadPos, net );
1274 PNS::SOLID* heldPad = makeTestPad( heldPadPos, net );
1275 PNS::SEGMENT* track = makeTestTrack( draggedPadPos, heldPadPos, net );
1276 world->AddRaw( draggedPad );
1277 world->AddRaw( heldPad );
1278 world->AddRaw( track );
1279
1280 PNS::BLOCK_DRAGGER dragger( m_router );
1281 dragger.SetWorld( world.get() );
1282
1283 PNS::ITEM_SET itemsToDrag;
1284 itemsToDrag.Add( draggedPad );
1285 itemsToDrag.Add( track );
1286 BOOST_REQUIRE( dragger.Start( draggedPadPos, itemsToDrag ) );
1287
1288 const VECTOR2I delta( 0, 2000000 );
1289 dragger.Drag( draggedPadPos + delta );
1290
1291 PNS::ITEM_SET traces = dragger.Traces();
1292 PNS::NODE* node = dragger.CurrentNode();
1293
1294 BOOST_CHECK( tracesTouch( traces, draggedPadPos + delta, true ) );
1295 BOOST_CHECK( tracesTouch( traces, heldPadPos, true ) );
1296 BOOST_CHECK( node->Overrides( track ) );
1297 BOOST_CHECK( !node->Overrides( heldPad ) );
1298
1299 world->KillChildren();
1300}
1301
1302
1303// Only the pad selected: the unselected copper on it is rerouted, its free end stays.
1304BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerReroutesUnselectedCopper, PNS_TEST_FIXTURE )
1305{
1306 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1308 VECTOR2I padPos( 0, 0 );
1309 VECTOR2I corner( 3000000, 0 );
1310 VECTOR2I farEnd( 3000000, 3000000 );
1311
1312 PNS::SOLID* pad = makeTestPad( padPos, net );
1313 PNS::SEGMENT* first = makeTestTrack( padPos, corner, net );
1314 PNS::SEGMENT* second = makeTestTrack( corner, farEnd, net );
1315 world->AddRaw( pad );
1316 world->AddRaw( first );
1317 world->AddRaw( second );
1318
1319 PNS::BLOCK_DRAGGER dragger( m_router );
1320 dragger.SetWorld( world.get() );
1321
1322 PNS::ITEM_SET itemsToDrag;
1323 itemsToDrag.Add( pad );
1324 BOOST_REQUIRE( dragger.Start( padPos, itemsToDrag ) );
1325
1326 const VECTOR2I delta( 0, 1000000 );
1327 dragger.Drag( padPos + delta );
1328
1329 PNS::ITEM_SET traces = dragger.Traces();
1330 PNS::NODE* node = dragger.CurrentNode();
1331
1332 BOOST_CHECK( tracesTouch( traces, padPos + delta, true ) );
1333 BOOST_CHECK( tracesTouch( traces, farEnd, true ) );
1334 BOOST_CHECK( node->Overrides( first ) );
1335 BOOST_CHECK( node->Overrides( second ) );
1336
1337 world->KillChildren();
1338}
1339
1340
1341// A selected segment runs round a plain corner into an unselected one. The unselected one
1342// is picked up from the corner, not assembled through it into the selected copper.
1343BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerStopsAtTheSelectedCopper, PNS_TEST_FIXTURE )
1344{
1345 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1347 VECTOR2I padPos( 0, 0 );
1348 VECTOR2I corner( 3000000, 0 );
1349 VECTOR2I farEnd( 3000000, 3000000 );
1350
1351 PNS::SOLID* pad = makeTestPad( padPos, net );
1352 PNS::SEGMENT* selected = makeTestTrack( padPos, corner, net );
1353 PNS::SEGMENT* unselected = makeTestTrack( corner, farEnd, net );
1354 world->AddRaw( pad );
1355 world->AddRaw( selected );
1356 world->AddRaw( unselected );
1357
1358 PNS::BLOCK_DRAGGER dragger( m_router );
1359 dragger.SetWorld( world.get() );
1360
1361 PNS::ITEM_SET itemsToDrag;
1362 itemsToDrag.Add( pad );
1363 itemsToDrag.Add( selected );
1364 BOOST_REQUIRE( dragger.Start( padPos, itemsToDrag ) );
1365
1366 const VECTOR2I delta( 0, 1000000 );
1367 dragger.Drag( padPos + delta );
1368
1369 PNS::ITEM_SET traces = dragger.Traces();
1370 PNS::NODE* node = dragger.CurrentNode();
1371
1372 BOOST_CHECK( tracesTouch( traces, corner + delta ) );
1373 BOOST_CHECK( tracesTouch( traces, farEnd, true ) );
1374 BOOST_CHECK( !tracesTouch( traces, farEnd + delta ) );
1375 BOOST_CHECK( !tracesTouch( traces, padPos, true ) );
1376
1377 // Both originals have to be replaced in the node, or commit leaves copper behind.
1378 BOOST_CHECK( node->Overrides( selected ) );
1379 BOOST_CHECK( node->Overrides( unselected ) );
1380
1381 world->KillChildren();
1382}
1383
1384
1385// Unselected copper between two selected segments travels with them. Its line assembles
1386// through both of their corners, so the clip keeps an interior slice.
1387BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerCarriesCopperBetweenSelectedSegments, PNS_TEST_FIXTURE )
1388{
1389 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1391 VECTOR2I a( 0, 0 );
1392 VECTOR2I b( 3000000, 0 );
1393 VECTOR2I c( 3000000, 3000000 );
1394 VECTOR2I d( 6000000, 3000000 );
1395
1396 PNS::SEGMENT* first = makeTestTrack( a, b, net );
1397 PNS::SEGMENT* middle = makeTestTrack( b, c, net );
1398 PNS::SEGMENT* last = makeTestTrack( c, d, net );
1399 world->AddRaw( first );
1400 world->AddRaw( middle );
1401 world->AddRaw( last );
1402
1403 PNS::BLOCK_DRAGGER dragger( m_router );
1404 dragger.SetWorld( world.get() );
1405
1406 PNS::ITEM_SET itemsToDrag;
1407 itemsToDrag.Add( first );
1408 itemsToDrag.Add( last );
1409 BOOST_REQUIRE( dragger.Start( a, itemsToDrag ) );
1410
1411 const VECTOR2I delta( 0, 1000000 );
1412 dragger.Drag( a + delta );
1413
1414 PNS::ITEM_SET traces = dragger.Traces();
1415 PNS::NODE* node = dragger.CurrentNode();
1416
1417 BOOST_CHECK( tracesTouch( traces, a + delta ) );
1418 BOOST_CHECK( tracesTouch( traces, b + delta ) );
1419 BOOST_CHECK( tracesTouch( traces, c + delta ) );
1420 BOOST_CHECK( tracesTouch( traces, d + delta ) );
1421 BOOST_CHECK( node->Overrides( first ) );
1422 BOOST_CHECK( node->Overrides( middle ) );
1423 BOOST_CHECK( node->Overrides( last ) );
1424
1425 world->KillChildren();
1426}
1427
1428
1429// A via is rigid like a pad, and an unselected via holds a line's end like an unselected pad.
1430BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerCarriesViaAndPinsOnUnselectedVia, PNS_TEST_FIXTURE )
1431{
1432 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1434 VECTOR2I draggedViaPos( 0, 0 );
1435 VECTOR2I heldViaPos( 5000000, 0 );
1436
1437 PNS::VIA* draggedVia = new PNS::VIA( draggedViaPos, PNS_LAYER_RANGE( F_Cu, B_Cu ), 800000, 400000, net );
1438 PNS::VIA* heldVia = new PNS::VIA( heldViaPos, PNS_LAYER_RANGE( F_Cu, B_Cu ), 800000, 400000, net );
1439 PNS::SEGMENT* track = makeTestTrack( draggedViaPos, heldViaPos, net );
1440 world->AddRaw( draggedVia );
1441 world->AddRaw( heldVia );
1442 world->AddRaw( track );
1443
1444 PNS::BLOCK_DRAGGER dragger( m_router );
1445 dragger.SetWorld( world.get() );
1446
1447 PNS::ITEM_SET itemsToDrag;
1448 itemsToDrag.Add( draggedVia );
1449 itemsToDrag.Add( track );
1450 BOOST_REQUIRE( dragger.Start( draggedViaPos, itemsToDrag ) );
1451
1452 const VECTOR2I delta( 0, 2000000 );
1453 dragger.Drag( draggedViaPos + delta );
1454
1455 PNS::ITEM_SET traces = dragger.Traces();
1456 PNS::NODE* node = dragger.CurrentNode();
1457
1458 BOOST_CHECK( tracesTouch( traces, draggedViaPos + delta, true ) );
1459 BOOST_CHECK( tracesTouch( traces, heldViaPos, true ) );
1460 BOOST_CHECK( !tracesTouch( traces, heldViaPos + delta ) );
1461 BOOST_CHECK( node->Overrides( draggedVia ) );
1462 BOOST_CHECK( node->Overrides( track ) );
1463 BOOST_CHECK( !node->Overrides( heldVia ) );
1464
1465 world->KillChildren();
1466}
1467
1468
1469// Unselected locked copper neither follows nor is touched.
1470BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerLeavesLockedCopperBehind, PNS_TEST_FIXTURE )
1471{
1472 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1474 VECTOR2I padPos( 0, 0 );
1475 VECTOR2I freeEnd( 2500000, 0 );
1476
1477 PNS::SOLID* pad = makeTestPad( padPos, net );
1478 PNS::SEGMENT* locked = makeTestTrack( padPos, freeEnd, net );
1479 locked->Mark( PNS::MK_LOCKED );
1480 world->AddRaw( pad );
1481 world->AddRaw( locked );
1482
1483 PNS::BLOCK_DRAGGER dragger( m_router );
1484 dragger.SetWorld( world.get() );
1485
1486 PNS::ITEM_SET itemsToDrag;
1487 itemsToDrag.Add( pad );
1488 BOOST_REQUIRE( dragger.Start( padPos, itemsToDrag ) );
1489
1490 const VECTOR2I delta( 1000000, 1000000 );
1491 dragger.Drag( padPos + delta );
1492
1493 PNS::ITEM_SET traces = dragger.Traces();
1494 PNS::NODE* node = dragger.CurrentNode();
1495
1496 BOOST_CHECK( tracesTouch( traces, padPos + delta ) );
1497 BOOST_CHECK( !tracesTouch( traces, freeEnd + delta ) );
1498 BOOST_CHECK( !tracesTouch( traces, freeEnd, true ) );
1499 BOOST_CHECK( node->Overrides( pad ) );
1500 BOOST_CHECK( !node->Overrides( locked ) );
1501
1502 world->KillChildren();
1503}
1504
1505
1506// An arc held at both ends is reported and left where it is.
1507BOOST_FIXTURE_TEST_CASE( PNSBlockDraggerReportsArcsHeldAtBothEnds, PNS_TEST_FIXTURE )
1508{
1509 std::unique_ptr<PNS::NODE> world = makeTestWorld( &m_ruleResolver );
1511 VECTOR2I arcStart( 0, 0 );
1512 VECTOR2I arcMid( 1000000, 1000000 );
1513 VECTOR2I arcEnd( 2000000, 0 );
1514
1515 PNS::ARC* arc = new PNS::ARC( SHAPE_ARC( arcStart, arcMid, arcEnd, 250000 ), net );
1516 arc->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1517 world->AddRaw( makeTestPad( arcStart, net, 300000 ) );
1518 world->AddRaw( makeTestPad( arcEnd, net, 300000 ) );
1519 world->AddRaw( arc );
1520
1521 PNS::BLOCK_DRAGGER dragger( m_router );
1522 dragger.SetWorld( world.get() );
1523
1524 PNS::ITEM_SET itemsToDrag;
1525 itemsToDrag.Add( arc );
1526 BOOST_REQUIRE( dragger.Start( arcStart, itemsToDrag ) );
1527 BOOST_CHECK_EQUAL( dragger.AnchoredLines().size(), 1u );
1528
1529 dragger.Drag( arcStart + VECTOR2I( 0, 1000000 ) );
1530
1531 BOOST_CHECK( !dragger.CurrentNode()->Overrides( arc ) );
1532
1533 world->KillChildren();
1534}
1535
1536
1545BOOST_FIXTURE_TEST_CASE( PNSComponentDraggerBasicDrag, PNS_TEST_FIXTURE )
1546{
1547 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1548 world->SetMaxClearance( 10000000 );
1549 world->SetRuleResolver( &m_ruleResolver );
1550
1552
1553 VECTOR2I pad1Pos( 0, 0 );
1554
1555 PNS::SOLID* pad1 = new PNS::SOLID;
1556 pad1->SetShape( new SHAPE_CIRCLE( pad1Pos, 500000 ) );
1557 pad1->SetPos( pad1Pos );
1558 pad1->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1559 pad1->SetNet( net1 );
1560 pad1->SetRoutable( true );
1561
1562 VECTOR2I traceEnd( 2500000, 2500000 );
1563 PNS::SEGMENT* trace = new PNS::SEGMENT( SEG( pad1Pos, traceEnd ), net1 );
1564 trace->SetWidth( 250000 );
1565 trace->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1566
1567 world->AddRaw( pad1 );
1568 world->AddRaw( trace );
1569
1570 PNS::COMPONENT_DRAGGER dragger( m_router );
1571 dragger.SetWorld( world.get() );
1572
1573 PNS::ITEM_SET itemsToDrag;
1574 itemsToDrag.Add( pad1 );
1575
1576 bool started = dragger.Start( pad1Pos, itemsToDrag );
1577 BOOST_REQUIRE( started );
1578
1579 // Simulate multiple drag events (mimics mouse movement during drag)
1580 VECTOR2I dragPositions[] = {
1581 VECTOR2I( 100000, 100000 ),
1582 VECTOR2I( 500000, 500000 ),
1583 VECTOR2I( 1000000, 1000000 ),
1584 VECTOR2I( 500000, 200000 ),
1585 VECTOR2I( 0, 0 )
1586 };
1587
1588 for( const VECTOR2I& pos : dragPositions )
1589 {
1590 bool dragOk = dragger.Drag( pos );
1591 BOOST_CHECK( dragOk );
1592
1593 PNS::NODE* currentNode = dragger.CurrentNode();
1594 BOOST_CHECK( currentNode != nullptr );
1595 }
1596
1597 // Verify the dragged items set is populated
1598 PNS::ITEM_SET traces = dragger.Traces();
1599 BOOST_CHECK( traces.Size() > 0 );
1600
1601 // Clean up branch nodes before the world is destroyed
1602 world->KillChildren();
1603}
1604
1605
1606// Dragging the apex of an isolated arc outward keeps a single arc in the chain and
1607// changes its radius. Exercises the LINE::DragArc geometric core added for in-router
1608// arc dragging.
1609BOOST_AUTO_TEST_CASE( PNSLineDragArcResize )
1610{
1611 // 90-degree CCW arc, centre at origin, radius 1mm.
1612 SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), EDA_ANGLE( 90, DEGREES_T ), 250000 );
1613
1615 chain.SetWidth( 250000 );
1616 chain.Append( arc );
1617
1618 PNS::LINE line;
1619 line.SetWidth( 250000 );
1620 line.Line() = chain;
1621
1622 BOOST_REQUIRE_EQUAL( line.CLine().ArcCount(), 1 );
1623
1624 double oldRadius = line.CLine().CArcs()[0].GetRadius();
1625 int apexIdx = line.CLine().PointCount() / 2;
1626
1627 // Pull the apex toward the tangent corner at (1mm, 1mm) to grow the radius.
1628 line.DragArc( VECTOR2I( 950000, 950000 ), apexIdx );
1629
1630 BOOST_CHECK_EQUAL( line.CLine().ArcCount(), 1 );
1631 BOOST_CHECK( line.CLine().PointCount() >= 2 );
1632 BOOST_CHECK( line.CLine().CArcs()[0].GetRadius() != oldRadius );
1633}
1634
1635
1636// Driving the arc endpoints together collapses the arc out of the chain (the
1637// "collapse to nothing drops it from the route" path in LINE::DragArc).
1638BOOST_AUTO_TEST_CASE( PNSLineDragArcCollapse )
1639{
1640 SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), EDA_ANGLE( 90, DEGREES_T ), 250000 );
1641
1643 chain.SetWidth( 250000 );
1644 chain.Append( arc );
1645
1646 PNS::LINE line;
1647 line.SetWidth( 250000 );
1648 line.Line() = chain;
1649
1650 BOOST_REQUIRE_EQUAL( line.CLine().ArcCount(), 1 );
1651
1652 // Drag almost onto the tangent corner at (1mm, 1mm), shrinking the arc until its
1653 // endpoints fall within the keep-track threshold and the arc is dropped. Staying a
1654 // hair off the corner keeps the constructed radius positive.
1655 line.DragArc( VECTOR2I( 999950, 999950 ), line.CLine().PointCount() / 2 );
1656
1657 BOOST_CHECK_EQUAL( line.CLine().ArcCount(), 0 );
1658}
1659
1660
1661// An arc whose central angle reaches 180 degrees cannot be dragged, and the refusal
1662// is reported through the router's failure reason so the UI can surface it.
1663BOOST_FIXTURE_TEST_CASE( PNSDragArcRejectsNear180, PNS_TEST_FIXTURE )
1664{
1665 PNS::ROUTING_SETTINGS settings( nullptr, "" );
1666 m_router->LoadSettings( &settings );
1667
1669
1670 auto makeArc = [&]( const EDA_ANGLE& aAngle ) -> PNS::ARC*
1671 {
1672 SHAPE_ARC sa( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), aAngle, 250000 );
1673 PNS::ARC* a = new PNS::ARC( sa, net );
1675 return a;
1676 };
1677
1678 // Shallow arc: drag starts and no failure is reported.
1679 {
1680 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1681 world->SetMaxClearance( 10000000 );
1682 world->SetRuleResolver( &m_ruleResolver );
1683
1684 PNS::ARC* arc = makeArc( EDA_ANGLE( 90, DEGREES_T ) );
1685 world->AddRaw( arc );
1686
1687 PNS::DRAGGER dragger( m_router );
1688 dragger.SetWorld( world.get() );
1689 dragger.SetMode( PNS::DM_ARC );
1690
1691 PNS::ITEM_SET items;
1692 items.Add( arc );
1693
1694 m_router->SetFailureReason( wxEmptyString );
1695 BOOST_CHECK( dragger.Start( arc->Anchor( 0 ), items ) );
1696 BOOST_CHECK( m_router->FailureReason().IsEmpty() );
1697
1698 world->KillChildren();
1699 }
1700
1701 // Major arc (>= 180 deg): drag is refused with an explanatory failure reason.
1702 {
1703 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1704 world->SetMaxClearance( 10000000 );
1705 world->SetRuleResolver( &m_ruleResolver );
1706
1707 PNS::ARC* arc = makeArc( EDA_ANGLE( 270, DEGREES_T ) );
1708 world->AddRaw( arc );
1709
1710 PNS::DRAGGER dragger( m_router );
1711 dragger.SetWorld( world.get() );
1712 dragger.SetMode( PNS::DM_ARC );
1713
1714 PNS::ITEM_SET items;
1715 items.Add( arc );
1716
1717 m_router->SetFailureReason( wxEmptyString );
1718 BOOST_CHECK( !dragger.Start( arc->Anchor( 0 ), items ) );
1719 BOOST_CHECK( !m_router->FailureReason().IsEmpty() );
1720
1721 world->KillChildren();
1722 }
1723
1724 // Clockwise major arc (negative central angle): the magnitude is what matters, so
1725 // it must be refused just like its CCW counterpart.
1726 {
1727 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1728 world->SetMaxClearance( 10000000 );
1729 world->SetRuleResolver( &m_ruleResolver );
1730
1731 PNS::ARC* arc = makeArc( EDA_ANGLE( -270, DEGREES_T ) );
1732 world->AddRaw( arc );
1733
1734 PNS::DRAGGER dragger( m_router );
1735 dragger.SetWorld( world.get() );
1736 dragger.SetMode( PNS::DM_ARC );
1737
1738 PNS::ITEM_SET items;
1739 items.Add( arc );
1740
1741 m_router->SetFailureReason( wxEmptyString );
1742 BOOST_CHECK( !dragger.Start( arc->Anchor( 0 ), items ) );
1743 BOOST_CHECK( !m_router->FailureReason().IsEmpty() );
1744
1745 world->KillChildren();
1746 }
1747}
1748
1749
1750// Base mock's NetCode() returns -1 for everything, which reads as unnetted; use a real net here
1751namespace
1752{
1753struct NETCODE_RULE_RESOLVER : public MOCK_RULE_RESOLVER
1754{
1755 int NetCode( PNS::NET_HANDLE aNet ) override { return aNet ? 1 : -1; }
1756};
1757
1758PNS::ITEM* queryFinishAnchor( PNS::NODE& aWorld, const PNS::LINE& aTrack )
1759{
1760 PNS::TOPOLOGY topo( &aWorld );
1761 VECTOR2I anchorPoint;
1762 PNS_LAYER_RANGE anchorLayers;
1763 PNS::ITEM* anchorItem = nullptr;
1764
1765 BOOST_REQUIRE( topo.NearestUnconnectedAnchorPoint( &aTrack, anchorPoint, anchorLayers,
1766 anchorItem ) );
1767 return anchorItem;
1768}
1769} // namespace
1770
1771
1772// F-key finish adds the track to a temporary branch node; a joint linking only to that
1773// track must still yield a persistent-world anchor, not a dangling branch pointer
1774//
1775// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24985
1776BOOST_FIXTURE_TEST_CASE( PNSFinishAnchorNoDanglingBranchItem, PNS_TEST_FIXTURE )
1777{
1778 NETCODE_RULE_RESOLVER resolver;
1779
1780 PNS::NODE world;
1781 world.SetMaxClearance( 10000000 );
1782 world.SetRuleResolver( &resolver );
1783
1785
1786 // Persistent unconnected target on the same net, away from the track
1787 PNS::SEGMENT* target = new PNS::SEGMENT( SEG( VECTOR2I( 10000000, 10000000 ),
1788 VECTOR2I( 12000000, 10000000 ) ), net );
1789 target->SetWidth( 250000 );
1790 target->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1791 world.AddRaw( target );
1792
1793 // Closed loop; end joint's two links are both owned by the temporary branch node
1794 PNS::LINE track;
1795 track.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1796 track.SetNet( net );
1797 track.SetWidth( 250000 );
1798 track.Line().Append( VECTOR2I( 0, 0 ) );
1799 track.Line().Append( VECTOR2I( 2000000, 0 ) );
1800 track.Line().Append( VECTOR2I( 2000000, 2000000 ) );
1801 track.Line().Append( VECTOR2I( 0, 2000000 ) );
1802 track.Line().Append( VECTOR2I( 0, 0 ) );
1803
1804 // Anchor must be the persistent target, never a link owned by the destroyed temp branch
1805 BOOST_CHECK_EQUAL( queryFinishAnchor( world, track ), target );
1806}
1807
1808
1809// ConnectedJoints must cross arcs when subtracting the track; stopping at an arc left
1810// temporary primitives beyond it selectable as the anchor, reviving the dangling pointer
1811//
1812// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24985
1813BOOST_FIXTURE_TEST_CASE( PNSFinishAnchorCrossesArcInConnectivity, PNS_TEST_FIXTURE )
1814{
1815 NETCODE_RULE_RESOLVER resolver;
1816
1817 PNS::NODE world;
1818 world.SetMaxClearance( 10000000 );
1819 world.SetRuleResolver( &resolver );
1820
1822
1823 // Farther from the track end than its own far segment, wins only once that's subtracted
1824 PNS::SEGMENT* target = new PNS::SEGMENT( SEG( VECTOR2I( 7000000, 0 ),
1825 VECTOR2I( 8000000, 0 ) ), net );
1826 target->SetWidth( 250000 );
1827 target->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1828 world.AddRaw( target );
1829
1830 // Segment-arc-segment track; far segment lies across the arc, so connectivity must cross it
1831 PNS::LINE track;
1832 track.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
1833 track.SetNet( net );
1834 track.SetWidth( 250000 );
1835 track.Line().Append( VECTOR2I( 0, 0 ) );
1836 track.Line().Append( VECTOR2I( 1000000, 0 ) );
1837 track.Line().Append( SHAPE_ARC( VECTOR2I( 1000000, 0 ), VECTOR2I( 1500000, 500000 ),
1838 VECTOR2I( 2000000, 0 ), 0 ) );
1839 track.Line().Append( VECTOR2I( 3000000, 0 ) );
1840
1841 // Anchor must be the persistent target, not a branch-owned primitive across the arc
1842 BOOST_CHECK_EQUAL( queryFinishAnchor( world, track ), target );
1843}
1844
1845
1846// Regression tests for issues #18658 and #24132. Physical clearance rules must be
1847// enforced for same-net and free-pad pairs without disturbing the fast path on
1848// boards that do not define them.
1849
1850namespace
1851{
1852PNS::VIA* makeVia( const VECTOR2I& aPos, PNS::NET_HANDLE aNet )
1853{
1854 PNS::VIA* v = new PNS::VIA( aPos, PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
1855 v->SetNet( aNet );
1856 return v;
1857}
1858
1859PNS::SOLID* makePad( const VECTOR2I& aPos, PNS::NET_HANDLE aNet, bool aFreePad = false )
1860{
1861 PNS::SOLID* s = new PNS::SOLID;
1862 s->SetShape( new SHAPE_CIRCLE( aPos, 250000 ) );
1863 s->SetPos( aPos );
1865 s->SetNet( aNet );
1866 s->SetIsFreePad( aFreePad );
1867 return s;
1868}
1869} // namespace
1870
1871
1872// Cross-net via vs via with no physical rules: ordinary CT_CLEARANCE applies.
1873BOOST_FIXTURE_TEST_CASE( PNSCrossNetViaViaElectricalClearanceBaseline, PNS_TEST_FIXTURE )
1874{
1875 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1876 world->SetMaxClearance( 10000000 );
1877 world->SetRuleResolver( &m_ruleResolver );
1878
1879 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1 );
1880 PNS::VIA* v2 = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1881 world->AddRaw( v1 );
1882 world->AddRaw( v2 );
1883
1884 m_ruleResolver.m_defaultClearance = 1000000;
1885
1886 PNS::NODE::OBSTACLES obstacles;
1887 world->QueryColliding( v1, obstacles );
1888
1889 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1890}
1891
1892
1893// Same-net via vs via with no physical rules: fast path keeps clearance = -1.
1894BOOST_FIXTURE_TEST_CASE( PNSSameNetNoPhysicalRulesFastPathNoCollision, PNS_TEST_FIXTURE )
1895{
1896 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1897 world->SetMaxClearance( 10000000 );
1898 world->SetRuleResolver( &m_ruleResolver );
1899
1901 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1902 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 100000 ), net );
1903 world->AddRaw( v1 );
1904
1905 m_ruleResolver.m_hasUserPhysicalRules = false;
1906 m_ruleResolver.m_defaultClearance = 1000000;
1907
1908 PNS::NODE::OBSTACLES obstacles;
1909 world->QueryColliding( v1, obstacles );
1910
1911 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1912}
1913
1914
1915// Same-net via vs via with a matching physical_clearance rule: collision reported.
1916// Regression scenario for issues #18658 and #24132.
1917BOOST_FIXTURE_TEST_CASE( PNSSameNetWithPhysicalRuleCollides, PNS_TEST_FIXTURE )
1918{
1919 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1920 world->SetMaxClearance( 10000000 );
1921 world->SetRuleResolver( &m_ruleResolver );
1922
1924 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1925 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 100000 ), net );
1926 world->AddRaw( v1 );
1927
1928 m_ruleResolver.m_hasUserPhysicalRules = true;
1929 m_ruleResolver.m_defaultPhysicalClearance = 1000000;
1930
1931 PNS::NODE::OBSTACLES obstacles;
1932 world->QueryColliding( v1, obstacles );
1933
1934 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1935 if( !obstacles.empty() )
1936 BOOST_CHECK_EQUAL( obstacles.begin()->m_clearance, 1000000 );
1937}
1938
1939
1940// Free pad vs cross-net pair, no physical rules: fast path applies.
1941BOOST_FIXTURE_TEST_CASE( PNSFreePadNoPhysicalRulesFastPath, PNS_TEST_FIXTURE )
1942{
1943 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1944 world->SetMaxClearance( 10000000 );
1945 world->SetRuleResolver( &m_ruleResolver );
1946
1947 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1948 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1949 world->AddRaw( freePad );
1950 world->AddRaw( v );
1951
1952 m_ruleResolver.m_hasUserPhysicalRules = false;
1953 m_ruleResolver.m_defaultClearance = 1000000;
1954
1955 PNS::NODE::OBSTACLES obstacles;
1956 world->QueryColliding( v, obstacles );
1957
1958 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1959}
1960
1961
1962// Free pad vs cross-net pair, physical rules present but no match: safety net must
1963// keep free pads from reporting collisions just because the board has a physical
1964// rule somewhere.
1966{
1967 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1968 world->SetMaxClearance( 10000000 );
1969 world->SetRuleResolver( &m_ruleResolver );
1970
1971 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1972 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1973 world->AddRaw( freePad );
1974 world->AddRaw( v );
1975
1976 m_ruleResolver.m_hasUserPhysicalRules = true;
1977 m_ruleResolver.m_defaultPhysicalClearance = 0; // rule does not match
1978 m_ruleResolver.m_defaultClearance = 1000000; // would collide if !sameNet block runs
1979
1980 PNS::NODE::OBSTACLES obstacles;
1981 world->QueryColliding( v, obstacles );
1982
1983 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1984}
1985
1986
1987// Free pad with a matching physical_clearance rule: collision reported.
1988BOOST_FIXTURE_TEST_CASE( PNSFreePadPhysicalRuleEnforced, PNS_TEST_FIXTURE )
1989{
1990 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1991 world->SetMaxClearance( 10000000 );
1992 world->SetRuleResolver( &m_ruleResolver );
1993
1994 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1995 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1996 world->AddRaw( freePad );
1997 world->AddRaw( v );
1998
1999 m_ruleResolver.m_hasUserPhysicalRules = true;
2000 m_ruleResolver.m_defaultPhysicalClearance = 1000000;
2001
2002 PNS::NODE::OBSTACLES obstacles;
2003 world->QueryColliding( v, obstacles );
2004
2005 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
2006}
2007
2008
2009// Same-net via vs pad with a matching physical_hole_clearance rule. Covers the
2010// drill-into-pad half of issue #24132 and exercises CT_PHYSICAL_HOLE_CLEARANCE.
2011BOOST_FIXTURE_TEST_CASE( PNSSameNetPhysicalHoleClearance, PNS_TEST_FIXTURE )
2012{
2013 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
2014 world->SetMaxClearance( 10000000 );
2015 world->SetRuleResolver( &m_ruleResolver );
2016
2018 PNS::SOLID* pad = makePad( VECTOR2I( 0, 0 ), net );
2019 PNS::VIA* via = makeVia( VECTOR2I( 0, 100000 ), net );
2020 world->AddRaw( pad );
2021 world->AddRaw( via );
2022
2023 m_ruleResolver.m_hasUserPhysicalRules = true;
2024 m_ruleResolver.m_defaultPhysicalHoleClearance = 1000000;
2025
2026 PNS::NODE::OBSTACLES obstacles;
2027 world->QueryColliding( via, obstacles );
2028
2029 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
2030}
2031
2032
2033BOOST_FIXTURE_TEST_CASE( PNSSameNetSafetyNetOnOverlap, PNS_TEST_FIXTURE )
2034{
2035 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
2036 world->SetMaxClearance( 10000000 );
2037 world->SetRuleResolver( &m_ruleResolver );
2038
2040 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
2041 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 30000 ), net ); // overlaps the first via
2042 world->AddRaw( v1 );
2043
2044 m_ruleResolver.m_hasUserPhysicalRules = true;
2045 m_ruleResolver.m_defaultPhysicalClearance = 0;
2046
2047 PNS::NODE::OBSTACLES obstacles;
2048 world->QueryColliding( v1, obstacles );
2049
2050 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
2051}
2052
2053
2054// Same-net pad+via with both physical_clearance and physical_hole_clearance
2055// matching: the resolver returns max across the two query points.
2056BOOST_FIXTURE_TEST_CASE( PNSBothPhysicalConstraintsMaxWins, PNS_TEST_FIXTURE )
2057{
2058 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
2059 world->SetMaxClearance( 10000000 );
2060 world->SetRuleResolver( &m_ruleResolver );
2061
2063 PNS::SOLID* pad = makePad( VECTOR2I( 0, 0 ), net );
2064 PNS::VIA* via = makeVia( VECTOR2I( 0, 100000 ), net );
2065 world->AddRaw( pad );
2066 world->AddRaw( via );
2067
2068 m_ruleResolver.m_hasUserPhysicalRules = true;
2069 m_ruleResolver.m_defaultPhysicalClearance = 100000;
2070 m_ruleResolver.m_defaultPhysicalHoleClearance = 2000000;
2071
2072 PNS::NODE::OBSTACLES obstacles;
2073 world->QueryColliding( via, obstacles );
2074
2075 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
2076
2077 // The recursive collideSimple call for the via's hole inserts a separate OBSTACLE
2078 // with the max-accumulated clearance, so look across all entries rather than
2079 // relying on std::set ordering (which is by pointer).
2080 int maxClearance = 0;
2081 for( const PNS::OBSTACLE& obs : obstacles )
2082 maxClearance = std::max( maxClearance, obs.m_clearance );
2083 BOOST_CHECK_EQUAL( maxClearance, 2000000 );
2084}
2085
2086
2087// Diff pair vias must respect copper-to-hole clearance, not just copper-to-copper and
2088// hole-to-hole. EffectiveDiffPairViaGap() is the copper-edge-to-copper-edge distance the
2089// placer fits vias to; it converts each clearance rule to that reference by subtracting the
2090// annular ring(s) of via copper that sit between a hole edge and the copper edge.
2091//
2092// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/21623 where the placer
2093// ignored copper-to-hole clearance and produced DRC violations on diff pair vias.
2094BOOST_AUTO_TEST_CASE( PNSDiffPairViaGapCopperToHoleClearance )
2095{
2096 PNS::SIZES_SETTINGS sizes;
2097
2098 // 600um copper diameter over a 300um drill leaves a 150um annular ring.
2099 sizes.SetViaDiameter( 600000 );
2100 sizes.SetViaDrill( 300000 );
2101 sizes.SetDiffPairViaGapSameAsTraceGap( false );
2102
2104
2105 // Copper-to-hole binds because 400um from a hole edge to the neighbour's copper edge is
2106 // 400000 - 150000 = 250000 copper-to-copper, exceeding both other rules.
2107 sizes.SetDiffPairViaGap( 200000 );
2108 sizes.SetDiffPairHoleToHole( 500000 ); // 500000 - 300000 = 200000 copper-to-copper
2109 sizes.SetDiffPairCopperToHole( 400000 );
2110
2111 BOOST_CHECK_EQUAL( sizes.GetDiffPairCopperToHole(), 400000 );
2112 BOOST_CHECK_EQUAL( sizes.EffectiveDiffPairViaGap(), 250000 );
2113
2114 // Hole-to-hole binds when it is the largest converted rule.
2115 sizes.SetDiffPairHoleToHole( 900000 ); // 900000 - 300000 = 600000 copper-to-copper
2116 BOOST_CHECK_EQUAL( sizes.EffectiveDiffPairViaGap(), 600000 );
2117
2118 // Plain copper-to-copper gap binds when the hole-based rules are slack.
2119 sizes.SetDiffPairHoleToHole( 0 );
2120 sizes.SetDiffPairCopperToHole( 0 );
2121 BOOST_CHECK_EQUAL( sizes.EffectiveDiffPairViaGap(), 200000 );
2122}
2123
2124
2125// Collapsing a padstack via's layer range for the violation highlight orphaned its
2126// layer-indexed shapes. Layer 2 is required here: 0, 1 and 5 resolve by accident.
2127//
2128// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/25139
2129namespace
2130{
2131class PADSTACK_VIA_IFACE : public MOCK_PNS_KICAD_IFACE
2132{
2133public:
2134 PADSTACK_VIA_IFACE( PNS_TEST_FIXTURE* aFixture ) :
2135 MOCK_PNS_KICAD_IFACE( aFixture )
2136 {
2137 }
2138
2139 void SyncWorld( PNS::NODE* aWorld ) override
2140 {
2141 aWorld->SetMaxClearance( 10000000 );
2142 aWorld->SetRuleResolver( GetRuleResolver() );
2143
2144 auto via = std::make_unique<PNS::VIA>( VECTOR2I( 0, 0 ), PNS_LAYER_RANGE( 0, 5 ), 400000, 100000,
2147 via->SetDiameter( 0, 400000 );
2148 via->SetDiameter( 1, 450000 );
2149 via->SetDiameter( 5, 400000 );
2150
2151 aWorld->Add( std::move( via ) );
2152 }
2153
2154 void DisplayItem( const PNS::ITEM* aItem, int aClearance, bool aEdit = false, int aFlags = 0 ) override
2155 {
2156 if( !aItem->OfKind( PNS::ITEM::VIA_T ) )
2157 return;
2158
2159 m_viasDisplayed++;
2160
2161 if( !aItem->Shape( -1 ) )
2162 m_nullShapes++;
2163 }
2164
2165 int m_viasDisplayed = 0;
2166 int m_nullShapes = 0;
2167};
2168} // namespace
2169
2170
2171BOOST_FIXTURE_TEST_CASE( PNSMarkViolationsKeepsPadstackViaShape, PNS_TEST_FIXTURE )
2172{
2173 PADSTACK_VIA_IFACE iface( this );
2174 PNS::ROUTING_SETTINGS settings( nullptr, "" );
2175 PNS::SIZES_SETTINGS sizes;
2176
2177 m_router->SetInterface( &iface );
2178 m_router->LoadSettings( &settings );
2179 m_router->SetMode( PNS::PNS_MODE_ROUTE_SINGLE );
2180
2181 sizes.SetTrackWidth( 200000 );
2182 sizes.SetBoardMinTrackWidth( 100000 );
2183 m_router->UpdateSizes( sizes );
2184
2185 m_router->SyncWorld();
2186
2187 BOOST_REQUIRE( !m_router->StartRouting( VECTOR2I( 300000, 0 ), nullptr, 2 ) );
2188
2189 BOOST_REQUIRE_MESSAGE( iface.m_viasDisplayed > 0, "Via was not highlighted as a violation" );
2190 BOOST_CHECK_MESSAGE( iface.m_nullShapes == 0, "Highlighted front/inner/back via has no resolvable shape" );
2191}
2192
2193
2194namespace
2195{
2196class COUNTED_HOLE : public PNS::HOLE
2197{
2198public:
2199 COUNTED_HOLE( SHAPE* aShape, int& aAlive ) :
2200 PNS::HOLE( aShape ),
2201 m_alive( aAlive )
2202 {
2203 m_alive++;
2204 }
2205
2206 ~COUNTED_HOLE() override { m_alive--; }
2207
2208private:
2209 int& m_alive;
2210};
2211} // namespace
2212
2213
2218BOOST_AUTO_TEST_CASE( PNSNodeFreesHolesOfItsViasAndSolids )
2219{
2220 int holesAlive = 0;
2221
2222 {
2223 PNS::NODE world;
2224
2225 PNS::VIA* via = new PNS::VIA( VECTOR2I( 0, 0 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
2226 via->SetHole( new COUNTED_HOLE( new SHAPE_CIRCLE( VECTOR2I( 0, 0 ), 5000 ), holesAlive ) );
2227 world.AddRaw( via );
2228
2229 std::unique_ptr<PNS::SOLID> solid = std::make_unique<PNS::SOLID>();
2230 solid->SetShape( new SHAPE_CIRCLE( VECTOR2I( 1000000, 0 ), 50000 ) );
2231 solid->SetLayers( PNS_LAYER_RANGE( F_Cu, B_Cu ) );
2232 solid->SetHole( new COUNTED_HOLE( new SHAPE_CIRCLE( VECTOR2I( 1000000, 0 ), 5000 ), holesAlive ) );
2233 world.Add( std::move( solid ) );
2234
2235 // A via added and removed again is freed as garbage, with the hole handed back to it
2236 PNS::VIA* removed = new PNS::VIA( VECTOR2I( 2000000, 0 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
2237 removed->SetHole( new COUNTED_HOLE( new SHAPE_CIRCLE( VECTOR2I( 2000000, 0 ), 5000 ), holesAlive ) );
2238 world.AddRaw( removed );
2239 world.Remove( removed );
2240
2241 BOOST_CHECK_EQUAL( holesAlive, 3 );
2242 }
2243
2244 BOOST_CHECK_EQUAL( holesAlive, 0 );
2245}
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:418
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
void SetCopperLayerCount(int aCount)
Definition board.cpp:1133
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1194
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:41
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
bool Start(const VECTOR2I &aP, ITEM_SET &aPrimitives) override
Sort the selection and the unselected copper touching it.
const std::vector< LINE > & AnchoredLines() const
Selected arcs held at both ends, which cannot move.
bool Drag(const VECTOR2I &aP) override
Rebuild the block at aP: displace rigid and bridge items, reroute connections.
NODE * CurrentNode() const override
The current branch, or the world before the first Drag().
const ITEM_SET Traces() override
Everything displaced or rerouted by the last Drag().
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)
std::vector< ITEM * > & Items()
Definition pns_itemset.h:95
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:292
void SetLayers(const PNS_LAYER_RANGE &aLayers)
Definition pns_item.h:213
void SetIsFreePad(bool aIsFreePad=true)
Definition pns_item.h:290
virtual const SHAPE * Shape(int aLayer) const
Return the geometrical shape of the item.
Definition pns_item.h:246
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:265
virtual BOARD_ITEM * BoardItem() const
Definition pns_item.h:207
void SetRoutable(bool aRoutable)
Definition pns_item.h:287
bool IsLocked() const
Definition pns_item.h:282
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:905
const SHAPE_LINE_CHAIN & CLine() const
Definition pns_line.h:149
SHAPE_LINE_CHAIN & Line()
Modifiable accessor to the underlying shape.
Definition pns_line.h:148
void SetWidth(int aWidth)
Set line width.
Definition pns_line.h:162
Keep the router "world" - i.e.
Definition pns_node.h:244
bool Overrides(ITEM *aItem) const
Check if this branch contains an updated version of the m_item from the root branch.
Definition pns_node.h:515
void SetMaxClearance(int aClearance)
Set the worst-case clearance between any pair of items.
Definition pns_node.h:282
void AddRaw(ITEM *aItem, bool aAllowRedundant=false)
Definition pns_node.h:522
bool Add(std::unique_ptr< SEGMENT > aSegment, bool aAllowRedundant=false)
Add an item to the current node.
Definition pns_node.cpp:753
std::set< OBSTACLE > OBSTACLES
Definition pns_node.h:256
void SetRuleResolver(RULE_RESOLVER *aFunc)
Assign a clearance resolution function object.
Definition pns_node.h:288
void Remove(ARC *aArc)
Remove an item from this branch.
Definition pns_node.cpp:997
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
virtual void SetHole(HOLE *aHole) override
Definition pns_via.h:328
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
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:30
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:69
@ DM_ARC
Definition pns_router.h:82
@ MK_LOCKED
Definition pns_item.h:45
std::unique_ptr< typename std::remove_const< T >::type > Clone(const T &aItem)
Definition pns_item.h:348
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_VIA * makeVia(BOARD *aBoard, const VECTOR2I &aPos, VIATYPE aType, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, int aDrill)
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 PNS::SOLID * makeTestPad(const VECTOR2I &aPos, PNS::NET_HANDLE aNet, int aRadius=500000)
static bool isHole(const PNS::ITEM *aItem)
static void dumpObstacles(const PNS::NODE::OBSTACLES &obstacles)
static bool isCopper(const PNS::ITEM *aItem)
static PNS::SEGMENT * makeTestTrack(const VECTOR2I &aStart, const VECTOR2I &aEnd, PNS::NET_HANDLE aNet)
BOOST_AUTO_TEST_CASE(PCBViaBackdrillCloneRetainsData)
BOOST_FIXTURE_TEST_CASE(PNSShoveOwnsRootLineHistory, PNS_TEST_FIXTURE)
static std::unique_ptr< PNS::NODE > makeTestWorld(PNS::RULE_RESOLVER *aResolver)
static bool tracesTouch(PNS::ITEM_SET &aTraces, const VECTOR2I &aPoint, bool aCopperOnly=false)
const SHAPE_LINE_CHAIN chain
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2I v2(1, 0)
int delta
@ 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:708