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>
28
30#include <geometry/shape_arc.h>
31#include <geometry/eda_angle.h>
33#include <router/pns_dragger.h>
35#include <router/pns_arc.h>
36#include <router/pns_line.h>
37#include <router/pns_item.h>
39#include <router/pns_node.h>
40#include <router/pns_router.h>
41#include <router/pns_segment.h>
42#include <router/pns_solid.h>
43#include <router/pns_via.h>
44
45static bool isCopper( const PNS::ITEM* aItem )
46{
47 if( !aItem )
48 return false;
49
50 BOARD_ITEM* parent = aItem->Parent();
51
52 if( parent && parent->Type() == PCB_PAD_T )
53 {
54 PAD* pad = static_cast<PAD*>( parent );
55
56 if( pad->IsAperturePad() || pad->IsNPTHWithNoCopper() )
57 return false;
58 }
59
60 return true;
61}
62
63
64static bool isHole( const PNS::ITEM* aItem )
65{
66 if( !aItem )
67 return false;
68
69 return aItem->OfKind( PNS::ITEM::HOLE_T );
70}
71
72
73static bool isEdge( const PNS::ITEM* aItem )
74{
75 if( !aItem )
76 return false;
77
78 const BOARD_ITEM *parent = aItem->BoardItem();
79
80 return parent && ( parent->IsOnLayer( Edge_Cuts ) || parent->IsOnLayer( Margin ) );
81}
82
83
85{
86public:
90
92
93 virtual int Clearance( const PNS::ITEM* aA, const PNS::ITEM* aB,
94 bool aUseClearanceEpsilon = true ) override
95 {
96 PNS::CONSTRAINT constraint;
97 int rv = 0;
98 PNS_LAYER_RANGE layers;
99
100 if( !aB )
101 layers = aA->Layers();
102 else if( isEdge( aA ) )
103 layers = aB->Layers();
104 else if( isEdge( aB ) )
105 layers = aA->Layers();
106 else
107 layers = aA->Layers().Intersection( aB->Layers() );
108
109 // Normalize layer range (no -1 magic numbers)
111
112 // electrical clearances are net-aware; physical clearances are net-blind; same-net or
113 // free-pad pairs with no positive physical rule fall back to -1.
114 const bool sameNet = aA && aB && aA->Net() && aA->Net() == aB->Net();
115 const bool freePad = aA && aB && ( aA->IsFreePad() || aB->IsFreePad() );
116
117 for( int layer = layers.Start(); layer <= layers.End(); ++layer )
118 {
119 if( !sameNet && !freePad )
120 {
121 if( isHole( aA ) && isHole( aB ) )
122 {
123 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_TO_HOLE, aA, aB, layer, &constraint ) )
124 {
125 if( constraint.m_Value.Min() > rv )
126 rv = constraint.m_Value.Min();
127 }
128 }
129 else if( isHole( aA ) || isHole( aB ) )
130 {
131 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_CLEARANCE, aA, aB, layer, &constraint ) )
132 {
133 if( constraint.m_Value.Min() > rv )
134 rv = constraint.m_Value.Min();
135 }
136 }
137 else if( isCopper( aA ) && ( !aB || isCopper( aB ) ) )
138 {
139 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_CLEARANCE, aA, aB, layer, &constraint ) )
140 {
141 if( constraint.m_Value.Min() > rv )
142 rv = constraint.m_Value.Min();
143 }
144 }
145 else if( isEdge( aA ) || ( aB && isEdge( aB ) ) )
146 {
147 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_EDGE_CLEARANCE, aA, aB, layer, &constraint ) )
148 {
149 if( constraint.m_Value.Min() > rv )
150 rv = constraint.m_Value.Min();
151 }
152 }
153 }
154
155 if( isHole( aA ) || isHole( aB ) )
156 {
157 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_PHYSICAL_HOLE_CLEARANCE, aA, aB, layer, &constraint ) )
158 {
159 if( constraint.m_Value.Min() > rv )
160 rv = constraint.m_Value.Min();
161 }
162 }
163
164 if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_PHYSICAL_CLEARANCE, aA, aB, layer, &constraint ) )
165 {
166 if( constraint.m_Value.Min() > rv )
167 rv = constraint.m_Value.Min();
168 }
169 }
170
171 if( ( sameNet || freePad ) && rv == 0 )
172 rv = -1;
173
174 return rv;
175 }
176
178
179 virtual PNS::NET_HANDLE DpCoupledNet( PNS::NET_HANDLE aNet ) override { return nullptr; }
180 virtual int DpNetPolarity( PNS::NET_HANDLE aNet ) override { return -1; }
181
182 virtual bool DpNetPair( const PNS::ITEM* aItem, PNS::NET_HANDLE& aNetP,
183 PNS::NET_HANDLE& aNetN ) override
184 {
185 return false;
186 }
187
188 virtual int NetCode( PNS::NET_HANDLE aNet ) override
189 {
190 return -1;
191 }
192
193 virtual wxString NetName( PNS::NET_HANDLE aNet ) override
194 {
195 return wxEmptyString;
196 }
197
198 virtual bool QueryConstraint( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA,
199 const PNS::ITEM* aItemB, int aLayer,
200 PNS::CONSTRAINT* aConstraint ) override
201 {
202 ITEM_KEY key;
203
204 key.a = aItemA;
205 key.b = aItemB;
206 key.type = aType;
207
208 auto it = m_ruleMap.find( key );
209
210 if( it == m_ruleMap.end() )
211 {
212 int cl;
213 switch( aType )
214 {
220 return false;
222 break;
225 return false;
227 break;
228 default: return false;
229 }
230
231 //printf("GetDef %s %s %d cl %d\n", aItemA->KindStr().c_str(), aItemB->KindStr().c_str(), aType, cl );
232
233 aConstraint->m_Type = aType;
234 aConstraint->m_Value.SetMin( cl );
235
236 return true;
237 }
238 else
239 {
240 *aConstraint = it->second;
241 }
242
243 return true;
244 }
245
246 int ClearanceEpsilon() const override { return m_clearanceEpsilon; }
247
248 struct ITEM_KEY
249 {
250 const PNS::ITEM* a = nullptr;
251 const PNS::ITEM* b = nullptr;
253
254 bool operator==( const ITEM_KEY& other ) const
255 {
256 return a == other.a && b == other.b && type == other.type;
257 }
258
259 bool operator<( const ITEM_KEY& other ) const
260 {
261 if( a < other.a )
262 {
263 return true;
264 }
265 else if ( a == other.a )
266 {
267 if( b < other.b )
268 return true;
269 else if ( b == other.b )
270 return type < other.type;
271 }
272
273 return false;
274 }
275 };
276
277 bool IsInNetTie( const PNS::ITEM* aA ) override { return false; }
278
279 bool IsNetTieExclusion( const PNS::ITEM* aItem, const VECTOR2I& aCollisionPos,
280 const PNS::ITEM* aCollidingItem ) override
281 {
282 return false;
283 }
284
285 bool IsDrilledHole( const PNS::ITEM* aItem ) override { return false; }
286
287 bool IsNonPlatedSlot( const PNS::ITEM* aItem ) override { return false; }
288
289 bool IsKeepout( const PNS::ITEM* aObstacle, const PNS::ITEM* aItem, bool* aEnforce ) override
290 {
291 return false;
292 }
293
294 void AddMockRule( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA, const PNS::ITEM* aItemB,
295 PNS::CONSTRAINT& aConstraint )
296 {
297 ITEM_KEY key;
298
299 key.a = aItemA;
300 key.b = aItemB;
301 key.type = aType;
302
303 m_ruleMap[key] = aConstraint;
304 }
305
306 int m_defaultClearance = 200000;
307 int m_defaultHole2Hole = 220000;
309 int m_defaultPhysicalClearance = 0; // 0 means "rule does not match this pair"
310 int m_defaultPhysicalHoleClearance = 0; // 0 means "rule does not match this pair"
312
313private:
314 std::map<ITEM_KEY, PNS::CONSTRAINT> m_ruleMap;
316};
317
318struct PNS_TEST_FIXTURE;
319
321{
322public:
324 m_testFixture( aFixture )
325 {}
326
328
329 void HideItem( PNS::ITEM* aItem ) override {};
330 void DisplayItem( const PNS::ITEM* aItem, int aClearance, bool aEdit = false,
331 int aFlags = 0 ) override {};
333
334 bool TestInheritTrackWidth( PNS::ITEM* aItem, int* aInheritedWidth,
335 const VECTOR2I& aStartPosition = VECTOR2I() )
336 {
337 m_startLayer = aItem->Layer();
338 return inheritTrackWidth( aItem, aInheritedWidth, aStartPosition );
339 }
340
341private:
343};
344
345
361
362
367
368static void dumpObstacles( const PNS::NODE::OBSTACLES &obstacles )
369{
370 for( const PNS::OBSTACLE& obs : obstacles )
371 {
372 BOOST_TEST_MESSAGE( wxString::Format( "%p [%s] - %p [%s], clearance %d",
373 obs.m_head, obs.m_head->KindStr().c_str(),
374 obs.m_item, obs.m_item->KindStr().c_str(),
375 obs.m_clearance ) );
376 }
377}
378
380{
381 PNS::VIA* v1 = new PNS::VIA( VECTOR2I( 0, 1000000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
382 PNS::VIA* v2 = new PNS::VIA( VECTOR2I( 0, 2000000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
383
384 std::unique_ptr<PNS::NODE> world ( new PNS::NODE );
385
386 v1->SetNet( (PNS::NET_HANDLE) 1 );
387 v2->SetNet( (PNS::NET_HANDLE) 2 );
388
389 world->SetMaxClearance( 10000000 );
390 world->SetRuleResolver( &m_ruleResolver );
391
392 world->AddRaw( v1 );
393 world->AddRaw( v2 );
394
395 BOOST_TEST_MESSAGE( "via to via, no violations" );
396 {
397 PNS::NODE::OBSTACLES obstacles;
398 int count = world->QueryColliding( v1, obstacles );
399 dumpObstacles( obstacles );
400 BOOST_CHECK_EQUAL( obstacles.size(), 0 );
401 BOOST_CHECK_EQUAL( count, 0 );
402 }
403
404 BOOST_TEST_MESSAGE( "via to via, forced copper to copper violation" );
405 {
406 PNS::NODE::OBSTACLES obstacles;
407 m_ruleResolver.m_defaultClearance = 1000000;
408 world->QueryColliding( v1, obstacles );
409 dumpObstacles( obstacles );
410
411 BOOST_CHECK_EQUAL( obstacles.size(), 1 );
412 const auto& first = *obstacles.begin();
413
414 BOOST_CHECK_EQUAL( first.m_head, v1 );
415 BOOST_CHECK_EQUAL( first.m_item, v2 );
416 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultClearance );
417 }
418
419 BOOST_TEST_MESSAGE( "via to via, forced hole to hole violation" );
420 {
421 PNS::NODE::OBSTACLES obstacles;
422 m_ruleResolver.m_defaultClearance = 200000;
423 m_ruleResolver.m_defaultHole2Hole = 1000000;
424
425 world->QueryColliding( v1, obstacles );
426 dumpObstacles( obstacles );
427
428 BOOST_CHECK_EQUAL( obstacles.size(), 1 );
429 auto iter = obstacles.begin();
430 const auto& first = *iter++;
431
432 BOOST_CHECK_EQUAL( first.m_head, v1->Hole() );
433 BOOST_CHECK_EQUAL( first.m_item, v2->Hole() );
434 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Hole );
435 }
436
437 BOOST_TEST_MESSAGE( "via to via, forced copper to hole violation" );
438 {
439 PNS::NODE::OBSTACLES obstacles;
440 m_ruleResolver.m_defaultHole2Hole = 220000;
441 m_ruleResolver.m_defaultHole2Copper = 1000000;
442
443 world->QueryColliding( v1, obstacles );
444 dumpObstacles( obstacles );
445
446 BOOST_CHECK_EQUAL( obstacles.size(), 2 );
447 auto iter = obstacles.begin();
448 const auto& first = *iter++;
449
450 // There is no guarantee on what order the two collisions will be in...
451 BOOST_CHECK( ( first.m_head == v1 && first.m_item == v2->Hole() )
452 || ( first.m_head == v1->Hole() && first.m_item == v2 ) );
453
454 BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Copper );
455 }
456}
457
458
459BOOST_FIXTURE_TEST_CASE( PNSViaBackdrillRetention, PNS_TEST_FIXTURE )
460{
461 PNS::VIA via( VECTOR2I( 1000, 2000 ), PNS_LAYER_RANGE( F_Cu, B_Cu ), 40000, 20000, nullptr,
463 via.SetHoleLayers( PNS_LAYER_RANGE( F_Cu, In2_Cu ) );
464 via.SetHolePostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) );
465 via.SetSecondaryDrill( std::optional<int>( 12000 ) );
466 via.SetSecondaryHoleLayers( std::optional<PNS_LAYER_RANGE>( PNS_LAYER_RANGE( F_Cu, In1_Cu ) ) );
467 via.SetSecondaryHolePostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED ) );
468
469 PNS::VIA viaCopy( via );
470 std::unique_ptr<PNS::VIA> viaClone( via.Clone() );
471
472 auto checkVia = [&]( const PNS::VIA& candidate )
473 {
474 BOOST_CHECK_EQUAL( candidate.HoleLayers().Start(), via.HoleLayers().Start() );
475 BOOST_CHECK_EQUAL( candidate.HoleLayers().End(), via.HoleLayers().End() );
476 BOOST_CHECK( candidate.HolePostMachining().has_value() );
477 BOOST_CHECK( candidate.HolePostMachining().value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK );
478 BOOST_CHECK( candidate.SecondaryDrill().has_value() );
479 BOOST_CHECK_EQUAL( candidate.SecondaryDrill().value(), via.SecondaryDrill().value() );
480 BOOST_CHECK( candidate.SecondaryHoleLayers().has_value() );
481 BOOST_CHECK_EQUAL( candidate.SecondaryHoleLayers()->Start(),
482 via.SecondaryHoleLayers()->Start() );
483 BOOST_CHECK_EQUAL( candidate.SecondaryHoleLayers()->End(),
484 via.SecondaryHoleLayers()->End() );
485 BOOST_CHECK( candidate.SecondaryHolePostMachining().has_value() );
486
487 // run this BOOST_CHECK only if possible to avoid crash
488 if( candidate.SecondaryHolePostMachining().has_value() )
489 BOOST_CHECK( candidate.SecondaryHolePostMachining().value() == via.SecondaryHolePostMachining().value() );
490 };
491
492 checkVia( viaCopy );
493 checkVia( *viaClone );
494}
495
496
497BOOST_AUTO_TEST_CASE( PCBViaBackdrillCloneRetainsData )
498{
499 BOARD board;
500 PCB_VIA via( &board );
501
502 via.SetPrimaryDrillStartLayer( F_Cu );
503 via.SetPrimaryDrillEndLayer( B_Cu );
504 via.SetFrontPostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) );
505 via.SetSecondaryDrillSize( std::optional<int>( 15000 ) );
506 via.SetSecondaryDrillStartLayer( F_Cu );
507 via.SetSecondaryDrillEndLayer( In2_Cu );
508
509 via.SetBackPostMachining( std::optional<PAD_DRILL_POST_MACHINING_MODE>( PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE ) );
510 via.SetTertiaryDrillSize( std::optional<int>( 8000 ) );
511 via.SetTertiaryDrillStartLayer( B_Cu );
512 via.SetTertiaryDrillEndLayer( In4_Cu );
513
514 PCB_VIA viaCopy( via );
515 std::unique_ptr<PCB_VIA> viaClone( static_cast<PCB_VIA*>( via.Clone() ) );
516
517 auto checkVia = [&]( const PCB_VIA& candidate )
518 {
519 BOOST_CHECK_EQUAL( candidate.GetPrimaryDrillStartLayer(), via.GetPrimaryDrillStartLayer() );
520 BOOST_CHECK_EQUAL( candidate.GetPrimaryDrillEndLayer(), via.GetPrimaryDrillEndLayer() );
521 BOOST_CHECK( candidate.GetFrontPostMachining().has_value() );
522 BOOST_CHECK_EQUAL( static_cast<int>( candidate.GetFrontPostMachining().value() ),
523 static_cast<int>( via.GetFrontPostMachining().value() ) );
524 BOOST_CHECK( candidate.GetSecondaryDrillSize().has_value() );
525 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillSize().value(),
526 via.GetSecondaryDrillSize().value() );
527 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillStartLayer(),
528 via.GetSecondaryDrillStartLayer() );
529 BOOST_CHECK_EQUAL( candidate.GetSecondaryDrillEndLayer(),
530 via.GetSecondaryDrillEndLayer() );
531
532 BOOST_CHECK( candidate.GetBackPostMachining().has_value() );
533 BOOST_CHECK_EQUAL( static_cast<int>( candidate.GetBackPostMachining().value() ),
534 static_cast<int>( via.GetBackPostMachining().value() ) );
535 BOOST_CHECK( candidate.GetTertiaryDrillSize().has_value() );
536 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillSize().value(),
537 via.GetTertiaryDrillSize().value() );
538 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillStartLayer(),
539 via.GetTertiaryDrillStartLayer() );
540 BOOST_CHECK_EQUAL( candidate.GetTertiaryDrillEndLayer(),
541 via.GetTertiaryDrillEndLayer() );
542 };
543
544 checkVia( viaCopy );
545 checkVia( *viaClone );
546}
547
548
557BOOST_AUTO_TEST_CASE( PNSLayerRangeSwapBehavior )
558{
559 // On a 2-layer board with FRONT_INNER_BACK mode, BoardCopperLayerCount() returns 2.
560 // The code would calculate PNS_LAYER_RANGE(1, 2 - 2) = PNS_LAYER_RANGE(1, 0)
561 // Since start > end, the constructor swaps them to (0, 1), which would span
562 // both F_Cu and B_Cu incorrectly.
563
564 PNS_LAYER_RANGE innerLayersRange2Layer( 1, 0 ); // What would happen on 2-layer board
565
566 // Verify the swap behavior that causes the bug
567 BOOST_CHECK_EQUAL( innerLayersRange2Layer.Start(), 0 );
568 BOOST_CHECK_EQUAL( innerLayersRange2Layer.End(), 1 );
569 BOOST_CHECK( innerLayersRange2Layer.Overlaps( 0 ) ); // F_Cu
570 BOOST_CHECK( innerLayersRange2Layer.Overlaps( 1 ) ); // B_Cu
571
572 // On a 4-layer board, inner layers are 1 and 2, so PNS_LAYER_RANGE(1, 4-2) = (1, 2)
573 PNS_LAYER_RANGE innerLayersRange4Layer( 1, 2 ); // Correct for 4-layer board
574
575 BOOST_CHECK_EQUAL( innerLayersRange4Layer.Start(), 1 );
576 BOOST_CHECK_EQUAL( innerLayersRange4Layer.End(), 2 );
577 BOOST_CHECK( !innerLayersRange4Layer.Overlaps( 0 ) ); // F_Cu - should not overlap
578 BOOST_CHECK( innerLayersRange4Layer.Overlaps( 1 ) ); // In1_Cu
579 BOOST_CHECK( innerLayersRange4Layer.Overlaps( 2 ) ); // In2_Cu
580 BOOST_CHECK( !innerLayersRange4Layer.Overlaps( 3 ) ); // B_Cu - should not overlap
581}
582
583
591BOOST_FIXTURE_TEST_CASE( PNSSegmentSplitPreservesLockedState, PNS_TEST_FIXTURE )
592{
593 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
594 world->SetMaxClearance( 10000000 );
595 world->SetRuleResolver( &m_ruleResolver );
596
598
599 VECTOR2I segStart( 0, 0 );
600 VECTOR2I segEnd( 10000000, 0 );
601 VECTOR2I splitPt( 5000000, 0 );
602
603 PNS::SEGMENT* lockedSeg = new PNS::SEGMENT( SEG( segStart, segEnd ), net );
604 lockedSeg->SetWidth( 250000 );
605 lockedSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
606 lockedSeg->Mark( PNS::MK_LOCKED );
607
608 BOOST_CHECK( lockedSeg->IsLocked() );
609
610 world->AddRaw( lockedSeg );
611
612 // Clone the locked segment and set up two halves (simulating SplitAdjacentSegments)
613 std::unique_ptr<PNS::SEGMENT> clone1( PNS::Clone( *lockedSeg ) );
614 std::unique_ptr<PNS::SEGMENT> clone2( PNS::Clone( *lockedSeg ) );
615
616 clone1->SetEnds( segStart, splitPt );
617 clone2->SetEnds( splitPt, segEnd );
618
619 BOOST_CHECK_MESSAGE( clone1->IsLocked(),
620 "First half of split locked segment must retain locked state" );
621 BOOST_CHECK_MESSAGE( clone2->IsLocked(),
622 "Second half of split locked segment must retain locked state" );
623 BOOST_CHECK_EQUAL( clone1->Width(), lockedSeg->Width() );
624 BOOST_CHECK_EQUAL( clone2->Width(), lockedSeg->Width() );
625}
626
627
635BOOST_FIXTURE_TEST_CASE( PNSInheritTrackWidthCursorProximity, PNS_TEST_FIXTURE )
636{
637 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
638 world->SetMaxClearance( 10000000 );
639 world->SetRuleResolver( &m_ruleResolver );
640
641 VECTOR2I padPos( 0, 0 );
643
644 // Pad at origin with a small circular shape
646 pad->SetShape( new SHAPE_CIRCLE( padPos, 500000 ) );
647 pad->SetPos( padPos );
648 pad->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
649 pad->SetNet( net );
650 world->AddRaw( pad );
651
652 // Narrow track going right (250um width)
653 int narrowWidth = 250000;
654 PNS::SEGMENT* narrowSeg = new PNS::SEGMENT( SEG( padPos, VECTOR2I( 5000000, 0 ) ), net );
655 narrowSeg->SetWidth( narrowWidth );
656 narrowSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
657 world->AddRaw( narrowSeg );
658
659 // Wide track going up (500um width)
660 int wideWidth = 500000;
661 PNS::SEGMENT* wideSeg = new PNS::SEGMENT( SEG( padPos, VECTOR2I( 0, -5000000 ) ), net );
662 wideSeg->SetWidth( wideWidth );
663 wideSeg->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
664 world->AddRaw( wideSeg );
665
666 int inherited = 0;
667
668 // Without cursor position, should fall back to minimum width
669 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited ) );
670 BOOST_CHECK_EQUAL( inherited, narrowWidth );
671
672 // Cursor near the narrow track (to the right) should select narrow width
673 inherited = 0;
674 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 2000000, 0 ) ) );
675 BOOST_CHECK_EQUAL( inherited, narrowWidth );
676
677 // Cursor near the wide track (upward) should select wide width
678 inherited = 0;
679 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 0, -2000000 ) ) );
680 BOOST_CHECK_EQUAL( inherited, wideWidth );
681
682 // Cursor slightly offset toward narrow track should still select narrow
683 inherited = 0;
684 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 100000, 50000 ) ) );
685 BOOST_CHECK_EQUAL( inherited, narrowWidth );
686
687 // Cursor slightly offset toward wide track should select wide
688 inherited = 0;
689 BOOST_CHECK( m_iface->TestInheritTrackWidth( pad, &inherited, VECTOR2I( 50000, -100000 ) ) );
690 BOOST_CHECK_EQUAL( inherited, wideWidth );
691}
692
693
701BOOST_AUTO_TEST_CASE( PCBExprGeometryDependentFunctionDetection )
702{
703 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
704
705 auto compileAndCheck = [&]( const wxString& aExpr, bool aExpectGeometry )
706 {
707 PCBEXPR_UCODE ucode;
708 PCBEXPR_CONTEXT ctx( 0, F_Cu );
709
710 bool ok = compiler.Compile( aExpr.ToUTF8().data(), &ucode, &ctx );
711 BOOST_CHECK_MESSAGE( ok, "Failed to compile: " + aExpr );
712
713 if( ok )
714 {
715 BOOST_CHECK_MESSAGE( ucode.HasGeometryDependentFunctions() == aExpectGeometry,
716 wxString::Format( "Expression '%s': expected geometry=%s, got %s",
717 aExpr,
718 aExpectGeometry ? "true" : "false",
720 ? "true" : "false" ) );
721 }
722 };
723
724 // Property-based conditions should NOT be geometry-dependent
725 compileAndCheck( wxT( "A.NetClass == 'Power'" ), false );
726 compileAndCheck( wxT( "A.Type == 'via'" ), false );
727 compileAndCheck( wxT( "A.NetName == '/VCC'" ), false );
728
729 // Geometry-dependent functions SHOULD be detected
730 compileAndCheck( wxT( "A.intersectsCourtyard('U1')" ), true );
731 compileAndCheck( wxT( "A.intersectsArea('Zone1')" ), true );
732 compileAndCheck( wxT( "A.enclosedByArea('Zone1')" ), true );
733 compileAndCheck( wxT( "A.intersectsFrontCourtyard('U1')" ), true );
734 compileAndCheck( wxT( "A.intersectsBackCourtyard('U1')" ), true );
735
736 // Deprecated aliases should also be detected
737 compileAndCheck( wxT( "A.insideCourtyard('U1')" ), true );
738 compileAndCheck( wxT( "A.insideArea('Zone1')" ), true );
739}
740
741
750BOOST_FIXTURE_TEST_CASE( PNSCollideSimpleNullShapeGuard, PNS_TEST_FIXTURE )
751{
752 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
753 world->SetMaxClearance( 10000000 );
754 world->SetRuleResolver( &m_ruleResolver );
755
758
759 PNS::SOLID* solid1 = new PNS::SOLID;
760 solid1->SetShape( new SHAPE_CIRCLE( VECTOR2I( 0, 0 ), 500000 ) );
761 solid1->SetPos( VECTOR2I( 0, 0 ) );
762 solid1->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
763 solid1->SetNet( net1 );
764
765 PNS::SOLID* solid2 = new PNS::SOLID;
766 solid2->SetShape( new SHAPE_CIRCLE( VECTOR2I( 100000, 0 ), 500000 ) );
767 solid2->SetPos( VECTOR2I( 100000, 0 ) );
768 solid2->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
769 solid2->SetNet( net2 );
770
771 world->AddRaw( solid1 );
772 world->AddRaw( solid2 );
773
774 // Verify collision works normally with valid shapes
775 PNS::NODE::OBSTACLES obstacles;
776 int count = world->QueryColliding( solid1, obstacles );
777 BOOST_CHECK( count > 0 );
778
779 // Exercise the null-shape guard in collideSimple by calling Collide() directly with a
780 // null-shape solid as the head item. This bypasses the spatial index (which requires a
781 // valid shape for bounding-box computation) and hits the exact code path the guard protects.
782 PNS::SOLID nullShapeSolid;
783 nullShapeSolid.SetPos( VECTOR2I( 0, 0 ) );
784 nullShapeSolid.SetLayers( PNS_LAYER_RANGE( F_Cu ) );
785 nullShapeSolid.SetNet( net2 );
786
787 bool collided = solid1->Collide( &nullShapeSolid, world.get(), F_Cu, nullptr );
788 BOOST_CHECK( !collided );
789}
790
791
800BOOST_FIXTURE_TEST_CASE( PNSComponentDraggerBasicDrag, PNS_TEST_FIXTURE )
801{
802 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
803 world->SetMaxClearance( 10000000 );
804 world->SetRuleResolver( &m_ruleResolver );
805
807
808 VECTOR2I pad1Pos( 0, 0 );
809
810 PNS::SOLID* pad1 = new PNS::SOLID;
811 pad1->SetShape( new SHAPE_CIRCLE( pad1Pos, 500000 ) );
812 pad1->SetPos( pad1Pos );
813 pad1->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
814 pad1->SetNet( net1 );
815 pad1->SetRoutable( true );
816
817 VECTOR2I traceEnd( 2500000, 2500000 );
818 PNS::SEGMENT* trace = new PNS::SEGMENT( SEG( pad1Pos, traceEnd ), net1 );
819 trace->SetWidth( 250000 );
820 trace->SetLayers( PNS_LAYER_RANGE( F_Cu ) );
821
822 world->AddRaw( pad1 );
823 world->AddRaw( trace );
824
825 PNS::COMPONENT_DRAGGER dragger( m_router );
826 dragger.SetWorld( world.get() );
827
828 PNS::ITEM_SET itemsToDrag;
829 itemsToDrag.Add( pad1 );
830
831 bool started = dragger.Start( pad1Pos, itemsToDrag );
832 BOOST_REQUIRE( started );
833
834 // Simulate multiple drag events (mimics mouse movement during drag)
835 VECTOR2I dragPositions[] = {
836 VECTOR2I( 100000, 100000 ),
837 VECTOR2I( 500000, 500000 ),
838 VECTOR2I( 1000000, 1000000 ),
839 VECTOR2I( 500000, 200000 ),
840 VECTOR2I( 0, 0 )
841 };
842
843 for( const VECTOR2I& pos : dragPositions )
844 {
845 bool dragOk = dragger.Drag( pos );
846 BOOST_CHECK( dragOk );
847
848 PNS::NODE* currentNode = dragger.CurrentNode();
849 BOOST_CHECK( currentNode != nullptr );
850 }
851
852 // Verify the dragged items set is populated
853 PNS::ITEM_SET traces = dragger.Traces();
854 BOOST_CHECK( traces.Size() > 0 );
855
856 // Clean up branch nodes before the world is destroyed
857 world->KillChildren();
858}
859
860
861// Dragging the apex of an isolated arc outward keeps a single arc in the chain and
862// changes its radius. Exercises the LINE::DragArc geometric core added for in-router
863// arc dragging.
864BOOST_AUTO_TEST_CASE( PNSLineDragArcResize )
865{
866 // 90-degree CCW arc, centre at origin, radius 1mm.
867 SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), EDA_ANGLE( 90, DEGREES_T ), 250000 );
868
870 chain.SetWidth( 250000 );
871 chain.Append( arc );
872
873 PNS::LINE line;
874 line.SetWidth( 250000 );
875 line.Line() = chain;
876
877 BOOST_REQUIRE_EQUAL( line.CLine().ArcCount(), 1 );
878
879 double oldRadius = line.CLine().CArcs()[0].GetRadius();
880 int apexIdx = line.CLine().PointCount() / 2;
881
882 // Pull the apex toward the tangent corner at (1mm, 1mm) to grow the radius.
883 line.DragArc( VECTOR2I( 950000, 950000 ), apexIdx );
884
885 BOOST_CHECK_EQUAL( line.CLine().ArcCount(), 1 );
886 BOOST_CHECK( line.CLine().PointCount() >= 2 );
887 BOOST_CHECK( line.CLine().CArcs()[0].GetRadius() != oldRadius );
888}
889
890
891// Driving the arc endpoints together collapses the arc out of the chain (the
892// "collapse to nothing drops it from the route" path in LINE::DragArc).
893BOOST_AUTO_TEST_CASE( PNSLineDragArcCollapse )
894{
895 SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), EDA_ANGLE( 90, DEGREES_T ), 250000 );
896
898 chain.SetWidth( 250000 );
899 chain.Append( arc );
900
901 PNS::LINE line;
902 line.SetWidth( 250000 );
903 line.Line() = chain;
904
905 BOOST_REQUIRE_EQUAL( line.CLine().ArcCount(), 1 );
906
907 // Drag almost onto the tangent corner at (1mm, 1mm), shrinking the arc until its
908 // endpoints fall within the keep-track threshold and the arc is dropped. Staying a
909 // hair off the corner keeps the constructed radius positive.
910 line.DragArc( VECTOR2I( 999950, 999950 ), line.CLine().PointCount() / 2 );
911
912 BOOST_CHECK_EQUAL( line.CLine().ArcCount(), 0 );
913}
914
915
916// An arc whose central angle reaches 180 degrees cannot be dragged, and the refusal
917// is reported through the router's failure reason so the UI can surface it.
918BOOST_FIXTURE_TEST_CASE( PNSDragArcRejectsNear180, PNS_TEST_FIXTURE )
919{
920 PNS::ROUTING_SETTINGS settings( nullptr, "" );
921 m_router->LoadSettings( &settings );
922
924
925 auto makeArc = [&]( const EDA_ANGLE& aAngle ) -> PNS::ARC*
926 {
927 SHAPE_ARC sa( VECTOR2I( 0, 0 ), VECTOR2I( 1000000, 0 ), aAngle, 250000 );
928 PNS::ARC* a = new PNS::ARC( sa, net );
930 return a;
931 };
932
933 // Shallow arc: drag starts and no failure is reported.
934 {
935 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
936 world->SetMaxClearance( 10000000 );
937 world->SetRuleResolver( &m_ruleResolver );
938
939 PNS::ARC* arc = makeArc( EDA_ANGLE( 90, DEGREES_T ) );
940 world->AddRaw( arc );
941
942 PNS::DRAGGER dragger( m_router );
943 dragger.SetWorld( world.get() );
944 dragger.SetMode( PNS::DM_ARC );
945
946 PNS::ITEM_SET items;
947 items.Add( arc );
948
949 m_router->SetFailureReason( wxEmptyString );
950 BOOST_CHECK( dragger.Start( arc->Anchor( 0 ), items ) );
951 BOOST_CHECK( m_router->FailureReason().IsEmpty() );
952
953 world->KillChildren();
954 }
955
956 // Major arc (>= 180 deg): drag is refused with an explanatory failure reason.
957 {
958 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
959 world->SetMaxClearance( 10000000 );
960 world->SetRuleResolver( &m_ruleResolver );
961
962 PNS::ARC* arc = makeArc( EDA_ANGLE( 270, DEGREES_T ) );
963 world->AddRaw( arc );
964
965 PNS::DRAGGER dragger( m_router );
966 dragger.SetWorld( world.get() );
967 dragger.SetMode( PNS::DM_ARC );
968
969 PNS::ITEM_SET items;
970 items.Add( arc );
971
972 m_router->SetFailureReason( wxEmptyString );
973 BOOST_CHECK( !dragger.Start( arc->Anchor( 0 ), items ) );
974 BOOST_CHECK( !m_router->FailureReason().IsEmpty() );
975
976 world->KillChildren();
977 }
978
979 // Clockwise major arc (negative central angle): the magnitude is what matters, so
980 // it must be refused just like its CCW counterpart.
981 {
982 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
983 world->SetMaxClearance( 10000000 );
984 world->SetRuleResolver( &m_ruleResolver );
985
986 PNS::ARC* arc = makeArc( EDA_ANGLE( -270, DEGREES_T ) );
987 world->AddRaw( arc );
988
989 PNS::DRAGGER dragger( m_router );
990 dragger.SetWorld( world.get() );
991 dragger.SetMode( PNS::DM_ARC );
992
993 PNS::ITEM_SET items;
994 items.Add( arc );
995
996 m_router->SetFailureReason( wxEmptyString );
997 BOOST_CHECK( !dragger.Start( arc->Anchor( 0 ), items ) );
998 BOOST_CHECK( !m_router->FailureReason().IsEmpty() );
999
1000 world->KillChildren();
1001 }
1002}
1003
1004
1005// Regression tests for issues #18658 and #24132. Physical clearance rules must be
1006// enforced for same-net and free-pad pairs without disturbing the fast path on
1007// boards that do not define them.
1008
1009namespace
1010{
1011PNS::VIA* makeVia( const VECTOR2I& aPos, PNS::NET_HANDLE aNet )
1012{
1013 PNS::VIA* v = new PNS::VIA( aPos, PNS_LAYER_RANGE( F_Cu, B_Cu ), 50000, 10000 );
1014 v->SetNet( aNet );
1015 return v;
1016}
1017
1018PNS::SOLID* makePad( const VECTOR2I& aPos, PNS::NET_HANDLE aNet, bool aFreePad = false )
1019{
1020 PNS::SOLID* s = new PNS::SOLID;
1021 s->SetShape( new SHAPE_CIRCLE( aPos, 250000 ) );
1022 s->SetPos( aPos );
1024 s->SetNet( aNet );
1025 s->SetIsFreePad( aFreePad );
1026 return s;
1027}
1028} // namespace
1029
1030
1031// Cross-net via vs via with no physical rules: ordinary CT_CLEARANCE applies.
1032BOOST_FIXTURE_TEST_CASE( PNSCrossNetViaViaElectricalClearanceBaseline, PNS_TEST_FIXTURE )
1033{
1034 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1035 world->SetMaxClearance( 10000000 );
1036 world->SetRuleResolver( &m_ruleResolver );
1037
1038 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1 );
1039 PNS::VIA* v2 = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1040 world->AddRaw( v1 );
1041 world->AddRaw( v2 );
1042
1043 m_ruleResolver.m_defaultClearance = 1000000;
1044
1045 PNS::NODE::OBSTACLES obstacles;
1046 world->QueryColliding( v1, obstacles );
1047
1048 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1049}
1050
1051
1052// Same-net via vs via with no physical rules: fast path keeps clearance = -1.
1053BOOST_FIXTURE_TEST_CASE( PNSSameNetNoPhysicalRulesFastPathNoCollision, PNS_TEST_FIXTURE )
1054{
1055 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1056 world->SetMaxClearance( 10000000 );
1057 world->SetRuleResolver( &m_ruleResolver );
1058
1060 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1061 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 100000 ), net );
1062 world->AddRaw( v1 );
1063
1064 m_ruleResolver.m_hasUserPhysicalRules = false;
1065 m_ruleResolver.m_defaultClearance = 1000000;
1066
1067 PNS::NODE::OBSTACLES obstacles;
1068 world->QueryColliding( v1, obstacles );
1069
1070 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1071}
1072
1073
1074// Same-net via vs via with a matching physical_clearance rule: collision reported.
1075// Regression scenario for issues #18658 and #24132.
1076BOOST_FIXTURE_TEST_CASE( PNSSameNetWithPhysicalRuleCollides, PNS_TEST_FIXTURE )
1077{
1078 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1079 world->SetMaxClearance( 10000000 );
1080 world->SetRuleResolver( &m_ruleResolver );
1081
1083 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1084 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 100000 ), net );
1085 world->AddRaw( v1 );
1086
1087 m_ruleResolver.m_hasUserPhysicalRules = true;
1088 m_ruleResolver.m_defaultPhysicalClearance = 1000000;
1089
1090 PNS::NODE::OBSTACLES obstacles;
1091 world->QueryColliding( v1, obstacles );
1092
1093 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1094 if( !obstacles.empty() )
1095 BOOST_CHECK_EQUAL( obstacles.begin()->m_clearance, 1000000 );
1096}
1097
1098
1099// Free pad vs cross-net pair, no physical rules: fast path applies.
1100BOOST_FIXTURE_TEST_CASE( PNSFreePadNoPhysicalRulesFastPath, PNS_TEST_FIXTURE )
1101{
1102 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1103 world->SetMaxClearance( 10000000 );
1104 world->SetRuleResolver( &m_ruleResolver );
1105
1106 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1107 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1108 world->AddRaw( freePad );
1109 world->AddRaw( v );
1110
1111 m_ruleResolver.m_hasUserPhysicalRules = false;
1112 m_ruleResolver.m_defaultClearance = 1000000;
1113
1114 PNS::NODE::OBSTACLES obstacles;
1115 world->QueryColliding( v, obstacles );
1116
1117 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1118}
1119
1120
1121// Free pad vs cross-net pair, physical rules present but no match: safety net must
1122// keep free pads from reporting collisions just because the board has a physical
1123// rule somewhere.
1125{
1126 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1127 world->SetMaxClearance( 10000000 );
1128 world->SetRuleResolver( &m_ruleResolver );
1129
1130 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1131 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1132 world->AddRaw( freePad );
1133 world->AddRaw( v );
1134
1135 m_ruleResolver.m_hasUserPhysicalRules = true;
1136 m_ruleResolver.m_defaultPhysicalClearance = 0; // rule does not match
1137 m_ruleResolver.m_defaultClearance = 1000000; // would collide if !sameNet block runs
1138
1139 PNS::NODE::OBSTACLES obstacles;
1140 world->QueryColliding( v, obstacles );
1141
1142 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1143}
1144
1145
1146// Free pad with a matching physical_clearance rule: collision reported.
1147BOOST_FIXTURE_TEST_CASE( PNSFreePadPhysicalRuleEnforced, PNS_TEST_FIXTURE )
1148{
1149 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1150 world->SetMaxClearance( 10000000 );
1151 world->SetRuleResolver( &m_ruleResolver );
1152
1153 PNS::SOLID* freePad = makePad( VECTOR2I( 0, 0 ), (PNS::NET_HANDLE) 1, /*aFreePad=*/true );
1154 PNS::VIA* v = makeVia( VECTOR2I( 0, 100000 ), (PNS::NET_HANDLE) 2 );
1155 world->AddRaw( freePad );
1156 world->AddRaw( v );
1157
1158 m_ruleResolver.m_hasUserPhysicalRules = true;
1159 m_ruleResolver.m_defaultPhysicalClearance = 1000000;
1160
1161 PNS::NODE::OBSTACLES obstacles;
1162 world->QueryColliding( v, obstacles );
1163
1164 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1165}
1166
1167
1168// Same-net via vs pad with a matching physical_hole_clearance rule. Covers the
1169// drill-into-pad half of issue #24132 and exercises CT_PHYSICAL_HOLE_CLEARANCE.
1170BOOST_FIXTURE_TEST_CASE( PNSSameNetPhysicalHoleClearance, PNS_TEST_FIXTURE )
1171{
1172 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1173 world->SetMaxClearance( 10000000 );
1174 world->SetRuleResolver( &m_ruleResolver );
1175
1177 PNS::SOLID* pad = makePad( VECTOR2I( 0, 0 ), net );
1178 PNS::VIA* via = makeVia( VECTOR2I( 0, 100000 ), net );
1179 world->AddRaw( pad );
1180 world->AddRaw( via );
1181
1182 m_ruleResolver.m_hasUserPhysicalRules = true;
1183 m_ruleResolver.m_defaultPhysicalHoleClearance = 1000000;
1184
1185 PNS::NODE::OBSTACLES obstacles;
1186 world->QueryColliding( via, obstacles );
1187
1188 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1189}
1190
1191
1192BOOST_FIXTURE_TEST_CASE( PNSSameNetSafetyNetOnOverlap, PNS_TEST_FIXTURE )
1193{
1194 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1195 world->SetMaxClearance( 10000000 );
1196 world->SetRuleResolver( &m_ruleResolver );
1197
1199 world->AddRaw( makeVia( VECTOR2I( 0, 0 ), net ) );
1200 PNS::VIA* v1 = makeVia( VECTOR2I( 0, 30000 ), net ); // overlaps the first via
1201 world->AddRaw( v1 );
1202
1203 m_ruleResolver.m_hasUserPhysicalRules = true;
1204 m_ruleResolver.m_defaultPhysicalClearance = 0;
1205
1206 PNS::NODE::OBSTACLES obstacles;
1207 world->QueryColliding( v1, obstacles );
1208
1209 BOOST_CHECK_EQUAL( obstacles.size(), (size_t) 0 );
1210}
1211
1212
1213// Same-net pad+via with both physical_clearance and physical_hole_clearance
1214// matching: the resolver returns max across the two query points.
1215BOOST_FIXTURE_TEST_CASE( PNSBothPhysicalConstraintsMaxWins, PNS_TEST_FIXTURE )
1216{
1217 std::unique_ptr<PNS::NODE> world( new PNS::NODE );
1218 world->SetMaxClearance( 10000000 );
1219 world->SetRuleResolver( &m_ruleResolver );
1220
1222 PNS::SOLID* pad = makePad( VECTOR2I( 0, 0 ), net );
1223 PNS::VIA* via = makeVia( VECTOR2I( 0, 100000 ), net );
1224 world->AddRaw( pad );
1225 world->AddRaw( via );
1226
1227 m_ruleResolver.m_hasUserPhysicalRules = true;
1228 m_ruleResolver.m_defaultPhysicalClearance = 100000;
1229 m_ruleResolver.m_defaultPhysicalHoleClearance = 2000000;
1230
1231 PNS::NODE::OBSTACLES obstacles;
1232 world->QueryColliding( via, obstacles );
1233
1234 BOOST_CHECK_GE( obstacles.size(), (size_t) 1 );
1235
1236 // The recursive collideSimple call for the via's hole inserts a separate OBSTACLE
1237 // with the max-accumulated clearance, so look across all entries rather than
1238 // relying on std::set ordering (which is by pointer).
1239 int maxClearance = 0;
1240 for( const PNS::OBSTACLE& obs : obstacles )
1241 maxClearance = std::max( maxClearance, obs.m_clearance );
1242 BOOST_CHECK_EQUAL( maxClearance, 2000000 );
1243}
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:347
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
T Min() const
Definition minoptmax.h:29
void SetMin(T v)
Definition minoptmax.h:37
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
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
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:863
const SHAPE_LINE_CHAIN & CLine() const
Definition pns_line.h:142
SHAPE_LINE_CHAIN & Line()
Definition pns_line.h:141
void SetWidth(int aWidth)
Return line width.
Definition pns_line.h:155
Keep the router "world" - i.e.
Definition pns_node.h:242
std::set< OBSTACLE > OBSTACLES
Definition pns_node.h:254
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
void SetPos(const VECTOR2I &aCenter)
Definition pns_solid.cpp:81
void SetShape(SHAPE *shape)
Definition pns_solid.h:113
bool inheritTrackWidth(PNS::ITEM *aItem, int *aInheritedWidth, const VECTOR2I &aStartPosition)
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
size_t ArcCount() const
@ DEGREES_T
Definition eda_angle.h:31
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
@ 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
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
VECTOR3I v1(5, 5, 5)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
static bool isEdge(const PNS::ITEM *aItem)
static bool isHole(const PNS::ITEM *aItem)
static void dumpObstacles(const PNS::NODE::OBSTACLES &obstacles)
BOOST_FIXTURE_TEST_CASE(PNSHoleCollisions, PNS_TEST_FIXTURE)
static bool isCopper(const PNS::ITEM *aItem)
BOOST_AUTO_TEST_CASE(PCBViaBackdrillCloneRetainsData)
const SHAPE_LINE_CHAIN chain
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2I v2(1, 0)
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683