KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_via_stack_saveload.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
20#include <algorithm>
21#include <filesystem>
22#include <memory>
23#include <set>
24#include <vector>
25
26#include <board.h>
27#include <board_commit.h>
30#include <netclass.h>
31#include <netinfo.h>
32#include <json_common.h>
33#include <collectors.h>
34#include <common.h>
35#include <footprint.h>
36#include <pad.h>
38#include <lset.h>
39#include <netinfo.h>
40#include <pcb_group.h>
41#include <inspectable_impl.h>
42#include <properties/property.h>
44#include <pcb_track.h>
45#include <tool/tool_manager.h>
47#include <generators_mgr.h>
48#include <router/router_tool.h>
52
53BOOST_AUTO_TEST_SUITE( ViaStackSaveLoad )
54
55
56static PCB_TRACK* makeSegment( BOARD* aBoard, const VECTOR2I& aStart, const VECTOR2I& aEnd )
57{
58 PCB_TRACK* track = new PCB_TRACK( aBoard );
59 track->SetStart( aStart );
60 track->SetEnd( aEnd );
61 track->SetWidth( pcbIUScale.mmToIU( 0.2 ) );
62 track->SetLayer( In1_Cu );
63 aBoard->Add( track );
64
65 return track;
66}
67
68
69// The factory must know the via_stack type (registration happened at static init).
70BOOST_AUTO_TEST_CASE( FactoryRegistered )
71{
72 std::unique_ptr<PCB_GENERATOR> gen( GENERATORS_MGR::Instance().CreateFromType( wxS( "via_stack" ) ) );
73
74 BOOST_REQUIRE( gen );
75 BOOST_CHECK_EQUAL( gen->GetGeneratorType(), wxS( "via_stack" ) );
76 BOOST_CHECK( dynamic_cast<PCB_VIA_STACK*>( gen.get() ) != nullptr );
77}
78
79
80// Build a via_stack with members and every stack level property, save, reload, and
81// verify the properties and membership survive.
82BOOST_AUTO_TEST_CASE( PropertiesAndMembersRoundTrip )
83{
84 auto board = std::make_unique<BOARD>();
85 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
86
87 PCB_TRACK* seg0 = makeSegment( board.get(), VECTOR2I( 0, 0 ), VECTOR2I( pcbIUScale.mmToIU( 0.35 ), 0 ) );
88 PCB_TRACK* seg1 = makeSegment( board.get(), VECTOR2I( pcbIUScale.mmToIU( 0.35 ), 0 ),
89 VECTOR2I( pcbIUScale.mmToIU( 0.70 ), 0 ) );
90
91 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
92 stack->SetStartLayer( F_Cu );
93 stack->SetEndLayer( In2_Cu );
95 stack->SetPitch( pcbIUScale.mmToIU( 0.35 ) );
96 stack->SetFilled( true );
97 stack->SetCapped( true );
98 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
99 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
100 stack->SetUseNetclass( true );
101 stack->SetPresetName( wxS( "HDI stack" ) );
102
103 SHAPE_LINE_CHAIN hops;
104 hops.Append( VECTOR2I( 0, 0 ) );
105 hops.Append( VECTOR2I( pcbIUScale.mmToIU( 0.35 ), 0 ) );
106 hops.Append( VECTOR2I( pcbIUScale.mmToIU( 0.70 ), 0 ) );
107 stack->SetHops( hops );
108
109 stack->AddItem( seg0 );
110 stack->AddItem( seg1 );
111 board->Add( stack );
112
113 auto path = std::filesystem::temp_directory_path() / "via_stack_saveload_tst.kicad_pcb";
114 ::KI_TEST::DumpBoardToFile( *board, path.string() );
115
116 std::unique_ptr<BOARD> reloaded = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
117
118 BOOST_REQUIRE_EQUAL( reloaded->Generators().size(), 1u );
119
120 PCB_VIA_STACK* loaded = dynamic_cast<PCB_VIA_STACK*>( reloaded->Generators().front() );
121 BOOST_REQUIRE( loaded );
122
123 BOOST_CHECK_EQUAL( (int) loaded->GetStartLayer(), (int) F_Cu );
124 BOOST_CHECK_EQUAL( (int) loaded->GetEndLayer(), (int) In2_Cu );
125 BOOST_CHECK( loaded->GetStyle() == VIA_STACK_STYLE::STAGGERED );
126 BOOST_CHECK_EQUAL( loaded->GetPitch(), pcbIUScale.mmToIU( 0.35 ) );
127 BOOST_CHECK_EQUAL( loaded->IsFilled(), true );
128 BOOST_CHECK_EQUAL( loaded->IsCapped(), true );
129 BOOST_CHECK_EQUAL( loaded->GetViaSize(), pcbIUScale.mmToIU( 0.30 ) );
130 BOOST_CHECK_EQUAL( loaded->GetViaDrill(), pcbIUScale.mmToIU( 0.15 ) );
131 BOOST_CHECK_EQUAL( loaded->GetUseNetclass(), true );
132 BOOST_CHECK_EQUAL( loaded->GetPresetName(), wxS( "HDI stack" ) );
133
134 BOOST_REQUIRE( loaded->GetHops().has_value() );
135 BOOST_CHECK_EQUAL( loaded->GetHops()->PointCount(), 3 );
136 BOOST_CHECK( loaded->GetHops()->CPoint( 2 ) == VECTOR2I( pcbIUScale.mmToIU( 0.70 ), 0 ) );
137
138 BOOST_CHECK_EQUAL( loaded->GetItems().size(), 2u );
139}
140
141
142// A stacked stack materializes one coaxial microvia per adjacent copper pair, no traces.
143BOOST_AUTO_TEST_CASE( StackedGeometry )
144{
145 auto board = std::make_unique<BOARD>();
146 board->SetCopperLayerCount( 4 );
147 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
148
149 VECTOR2I origin( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
150
151 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
152 stack->SetStartLayer( F_Cu );
153 stack->SetEndLayer( In2_Cu );
155 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
156 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
157 stack->SetPosition( origin );
158 board->Add( stack );
159
160 stack->Regenerate( board.get(), nullptr );
161
162 std::vector<PCB_VIA*> vias;
163 int traces = 0;
164
165 for( BOARD_ITEM* item : stack->GetBoardItems() )
166 {
167 if( item->Type() == PCB_VIA_T )
168 vias.push_back( static_cast<PCB_VIA*>( item ) );
169 else if( item->Type() == PCB_TRACE_T )
170 traces++;
171 }
172
173 BOOST_CHECK_EQUAL( vias.size(), 2u ); // F.Cu-In1 and In1-In2
174 BOOST_CHECK_EQUAL( traces, 0 );
175
176 std::set<std::pair<int, int>> pairs;
177
178 for( PCB_VIA* via : vias )
179 {
180 BOOST_CHECK( via->GetViaType() == VIATYPE::MICROVIA );
181 BOOST_CHECK( via->GetPosition() == origin ); // coaxial
182 pairs.insert( { (int) via->TopLayer(), (int) via->BottomLayer() } );
183 }
184
185 BOOST_CHECK_EQUAL( pairs.count( { (int) F_Cu, (int) In1_Cu } ), 1u );
186 BOOST_CHECK_EQUAL( pairs.count( { (int) In1_Cu, (int) In2_Cu } ), 1u );
187}
188
189
190// A staggered stack offsets each hop along the hop polyline and joins them with traces.
191BOOST_AUTO_TEST_CASE( StaggeredGeometry )
192{
193 auto board = std::make_unique<BOARD>();
194 board->SetCopperLayerCount( 4 );
195 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
196
197 VECTOR2I p0( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
198 VECTOR2I p1( pcbIUScale.mmToIU( 10.35 ), pcbIUScale.mmToIU( 10 ) );
199
200 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
201 stack->SetStartLayer( F_Cu );
202 stack->SetEndLayer( In2_Cu );
204 stack->SetPitch( pcbIUScale.mmToIU( 0.35 ) );
205 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
206 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
207
208 SHAPE_LINE_CHAIN hops;
209 hops.Append( p0 );
210 hops.Append( p1 );
211 stack->SetHops( hops );
212 board->Add( stack );
213
214 stack->Regenerate( board.get(), nullptr );
215
216 int vias = 0;
217 std::vector<PCB_TRACK*> traces;
218
219 for( BOARD_ITEM* item : stack->GetBoardItems() )
220 {
221 if( item->Type() == PCB_VIA_T )
222 vias++;
223 else if( item->Type() == PCB_TRACE_T )
224 traces.push_back( static_cast<PCB_TRACK*>( item ) );
225 }
226
227 BOOST_CHECK_EQUAL( vias, 2 ); // one per hop
228 BOOST_REQUIRE_EQUAL( traces.size(), 1u ); // one shared-layer trace between the two hops
229
230 PCB_TRACK* trace = traces.front();
231 BOOST_CHECK_EQUAL( (int) trace->GetLayer(), (int) In1_Cu );
232 BOOST_CHECK( trace->GetStart() == p0 );
233 BOOST_CHECK( trace->GetEnd() == p1 );
234}
235
236
237// Moving a staggered stack must carry the hop polyline along, or the next regenerate snaps
238// the vias back to the old location.
239BOOST_AUTO_TEST_CASE( MoveTranslatesHops )
240{
241 auto board = std::make_unique<BOARD>();
242 board->SetCopperLayerCount( 4 );
243 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
244
245 VECTOR2I p0( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
246 VECTOR2I p1( pcbIUScale.mmToIU( 10.35 ), pcbIUScale.mmToIU( 10 ) );
247 VECTOR2I delta( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( -3 ) );
248
249 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
250 stack->SetStartLayer( F_Cu );
251 stack->SetEndLayer( In2_Cu );
253 stack->SetPitch( pcbIUScale.mmToIU( 0.35 ) );
254 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
255 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
256 stack->SetPosition( p0 );
257
258 SHAPE_LINE_CHAIN hops;
259 hops.Append( p0 );
260 hops.Append( p1 );
261 stack->SetHops( hops );
262 board->Add( stack );
263 stack->Regenerate( board.get(), nullptr );
264
265 stack->Move( delta );
266 stack->Regenerate( board.get(), nullptr );
267
268 std::set<VECTOR2I> positions;
269
270 for( BOARD_ITEM* item : stack->GetBoardItems() )
271 {
272 if( item->Type() == PCB_VIA_T )
273 positions.insert( static_cast<PCB_VIA*>( item )->GetPosition() );
274 }
275
276 BOOST_CHECK_EQUAL( positions.count( p0 + delta ), 1u );
277 BOOST_CHECK_EQUAL( positions.count( p1 + delta ), 1u );
278 BOOST_CHECK_EQUAL( positions.count( p0 ), 0u );
279}
280
281
282// Half a turn about the first hop puts the second the same distance the other side, whichever
283// way the rotation runs.
284BOOST_AUTO_TEST_CASE( RotateTurnsTheHopsWithTheStack )
285{
286 auto board = std::make_unique<BOARD>();
287 board->SetCopperLayerCount( 4 );
288 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
289
290 VECTOR2I p0( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
291 VECTOR2I p1( pcbIUScale.mmToIU( 10.35 ), pcbIUScale.mmToIU( 10 ) );
292
293 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
294 stack->SetStartLayer( F_Cu );
295 stack->SetEndLayer( In2_Cu );
297 stack->SetPitch( pcbIUScale.mmToIU( 0.35 ) );
298 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
299 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
300 stack->SetPosition( p0 );
301
302 SHAPE_LINE_CHAIN hops;
303 hops.Append( p0 );
304 hops.Append( p1 );
305 stack->SetHops( hops );
306 board->Add( stack );
307 stack->Regenerate( board.get(), nullptr );
308
309 stack->Rotate( p0, EDA_ANGLE( 180, DEGREES_T ) );
310 stack->Regenerate( board.get(), nullptr );
311
312 std::set<VECTOR2I> positions;
313
314 for( BOARD_ITEM* item : stack->GetBoardItems() )
315 {
316 if( item->Type() == PCB_VIA_T )
317 positions.insert( static_cast<PCB_VIA*>( item )->GetPosition() );
318 }
319
320 BOOST_CHECK_EQUAL( positions.count( p0 ), 1u );
321 BOOST_CHECK_EQUAL( positions.count( p0 - ( p1 - p0 ) ), 1u );
322 BOOST_CHECK_EQUAL( positions.count( p1 ), 0u );
323}
324
325
326// Flipping puts the stack on the other side of the board, so its span has to flip with it.
327// Mirroring stays in plane and must leave the span alone.
328BOOST_AUTO_TEST_CASE( FlipSwapsTheSpanButMirrorDoesNot )
329{
330 auto board = std::make_unique<BOARD>();
331 board->SetCopperLayerCount( 4 );
332 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
333
334 VECTOR2I p0( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
335 VECTOR2I p1( pcbIUScale.mmToIU( 10.35 ), pcbIUScale.mmToIU( 10 ) );
336 VECTOR2I centre( 0, 0 );
337
338 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
339 stack->SetStartLayer( F_Cu );
340 stack->SetEndLayer( In2_Cu );
342 stack->SetPitch( pcbIUScale.mmToIU( 0.35 ) );
343 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
344 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
345 stack->SetPosition( p0 );
346
347 SHAPE_LINE_CHAIN hops;
348 hops.Append( p0 );
349 hops.Append( p1 );
350 stack->SetHops( hops );
351 board->Add( stack );
352 stack->Regenerate( board.get(), nullptr );
353
354 stack->Flip( centre, FLIP_DIRECTION::LEFT_RIGHT );
355
356 BOOST_CHECK_EQUAL( (int) stack->GetStartLayer(), (int) B_Cu );
357 BOOST_CHECK_EQUAL( (int) stack->GetEndLayer(), (int) In1_Cu );
358
359 BOOST_REQUIRE( stack->GetHops().has_value() );
360 BOOST_CHECK( stack->GetHops()->CPoint( 0 ) == VECTOR2I( -p0.x, p0.y ) );
361 BOOST_CHECK( stack->GetHops()->CPoint( 1 ) == VECTOR2I( -p1.x, p1.y ) );
362
363 stack->Flip( centre, FLIP_DIRECTION::LEFT_RIGHT );
364
365 BOOST_CHECK_EQUAL( (int) stack->GetStartLayer(), (int) F_Cu );
366 BOOST_CHECK_EQUAL( (int) stack->GetEndLayer(), (int) In2_Cu );
367
368 stack->Mirror( centre, FLIP_DIRECTION::LEFT_RIGHT );
369
370 BOOST_CHECK_EQUAL( (int) stack->GetStartLayer(), (int) F_Cu );
371 BOOST_CHECK_EQUAL( (int) stack->GetEndLayer(), (int) In2_Cu );
372
373 BOOST_REQUIRE( stack->GetHops().has_value() );
374 BOOST_CHECK( stack->GetHops()->CPoint( 0 ) == VECTOR2I( -p0.x, p0.y ) );
375 BOOST_CHECK( stack->GetHops()->CPoint( 1 ) == VECTOR2I( -p1.x, p1.y ) );
376}
377
378
379// A span referencing layers absent from the board must build nothing instead of iterating
380// LAYER_RANGE forever.
381BOOST_AUTO_TEST_CASE( InvalidSpanBuildsNothing )
382{
383 auto board = std::make_unique<BOARD>();
384 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
385 board->SetCopperLayerCount( 4 );
386
387 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
388 stack->SetStartLayer( F_Cu );
389 stack->SetEndLayer( In6_Cu ); // not present on a 4 layer board
391 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
392 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
393 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
394 board->Add( stack );
395
396 stack->Regenerate( board.get(), nullptr );
397
398 BOOST_CHECK_EQUAL( stack->GetItems().size(), 0u );
399 BOOST_CHECK( !PCB_VIA_STACK::IsSpanValid( board.get(), F_Cu, In6_Cu ) );
400 BOOST_CHECK( !PCB_VIA_STACK::IsSpanValid( board.get(), F_Cu, F_Cu ) );
401
402 // A layer combo the board cannot fill reports UNDEFINED_LAYER.
403 BOOST_CHECK( !PCB_VIA_STACK::IsSpanValid( board.get(), F_Cu, UNDEFINED_LAYER ) );
404 BOOST_CHECK( !PCB_VIA_STACK::IsSpanValid( board.get(), UNDEFINED_LAYER, In2_Cu ) );
405 BOOST_CHECK( PCB_VIA_STACK::IsSpanValid( board.get(), F_Cu, In2_Cu ) );
406 BOOST_CHECK( PCB_VIA_STACK::IsSpanValid( board.get(), F_Cu, B_Cu ) );
407}
408
409
410static PCB_VIA* makeMicrovia( BOARD* aBoard, const VECTOR2I& aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom,
411 int aNet = 0 )
412{
413 PCB_VIA* via = new PCB_VIA( aBoard );
414 via->SetViaType( VIATYPE::MICROVIA );
415 via->SetLayerPair( aTop, aBottom );
416 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
417 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
418 via->SetPosition( aPos );
419 via->SetNetCode( aNet );
420 aBoard->Add( via );
421
422 return via;
423}
424
425
426// Two coaxial single hop microvias make a stacked stack.
427BOOST_AUTO_TEST_CASE( CreateFromItemsStacked )
428{
429 auto board = std::make_unique<BOARD>();
430 board->SetCopperLayerCount( 4 );
431 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
432
433 VECTOR2I pos( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
434
435 std::vector<BOARD_ITEM*> items;
436 items.push_back( makeMicrovia( board.get(), pos, F_Cu, In1_Cu ) );
437 items.push_back( makeMicrovia( board.get(), pos, In1_Cu, In2_Cu ) );
438
439 PCB_VIA_STACK* stack = PCB_VIA_STACK::CreateFromItems( items, board.get() );
440
441 BOOST_REQUIRE( stack );
442 BOOST_CHECK( stack->GetStyle() == VIA_STACK_STYLE::STACKED );
443 BOOST_CHECK_EQUAL( (int) stack->GetStartLayer(), (int) F_Cu );
444 BOOST_CHECK_EQUAL( (int) stack->GetEndLayer(), (int) In2_Cu );
445 BOOST_CHECK_EQUAL( stack->GetViaSize(), pcbIUScale.mmToIU( 0.3 ) );
446
447 delete stack;
448}
449
450
451// A skipped layer must be rejected.
452BOOST_AUTO_TEST_CASE( CreateFromItemsRejectsGap )
453{
454 auto board = std::make_unique<BOARD>();
455 board->SetCopperLayerCount( 4 );
456 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
457
458 VECTOR2I pos( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
459
460 std::vector<BOARD_ITEM*> items;
461 items.push_back( makeMicrovia( board.get(), pos, F_Cu, In1_Cu ) );
462 items.push_back( makeMicrovia( board.get(), pos, In2_Cu, B_Cu ) );
463
464 BOOST_CHECK( PCB_VIA_STACK::CreateFromItems( items, board.get() ) == nullptr );
465}
466
467
468// The router hand-off resumes at the via reaching the end layer, which on a staggered stack
469// is nowhere near the stack's own position.
470BOOST_AUTO_TEST_CASE( OnlyTheLastHopReachesTheEndLayer )
471{
472 auto board = std::make_unique<BOARD>();
473 board->SetCopperLayerCount( 4 );
474 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
475
476 VECTOR2I first( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
477 VECTOR2I last( pcbIUScale.mmToIU( 7 ), pcbIUScale.mmToIU( 5 ) );
478
479 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
480 stack->SetStartLayer( F_Cu );
481 stack->SetEndLayer( In2_Cu );
483 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
484 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
485
486 SHAPE_LINE_CHAIN hops;
487 hops.Append( first );
488 hops.Append( last );
489 stack->SetHops( hops );
490 stack->SetPosition( first );
491
492 board->Add( stack );
493 stack->Regenerate( board.get(), nullptr );
494
495 int onEnd = 0;
496 VECTOR2I found;
497
498 for( BOARD_ITEM* item : stack->GetBoardItems() )
499 {
500 PCB_VIA* via = dynamic_cast<PCB_VIA*>( item );
501
502 if( via && via->IsOnLayer( stack->GetEndLayer() ) )
503 {
504 ++onEnd;
505 found = via->GetPosition();
506 }
507 }
508
509 BOOST_CHECK_EQUAL( onEnd, 1 );
510 BOOST_CHECK_MESSAGE( found == last, "the end layer via is the last hop, not the stack position" );
511 BOOST_CHECK( stack->GetPosition() != found );
512}
513
514
515BOOST_AUTO_TEST_CASE( PendingStackExpansionMatcher )
516{
517 auto board = std::make_unique<BOARD>();
518 board->SetCopperLayerCount( 4 );
519 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
520
521 board->Add( new NETINFO_ITEM( board.get(), wxT( "sig" ), 1 ) );
522 board->Add( new NETINFO_ITEM( board.get(), wxT( "other" ), 2 ) );
523
524 auto makeVia = [&]( PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, int aNet )
525 {
526 PCB_VIA* via = new PCB_VIA( board.get() );
527 via->SetViaType( VIATYPE::MICROVIA );
528 via->SetLayerPair( aTop, aBottom );
529 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
530 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
531 via->SetNetCode( aNet );
532 board->Add( via );
533 return via;
534 };
535
536 VIA_STACK_PRESET preset;
537 preset.m_Name = wxT( "fanout" );
538
539 std::vector<PENDING_STACK_EXPANSION> pending = { { F_Cu, In2_Cu, 1, preset } };
540 std::set<KIID> preRoute;
541
542 PCB_VIA* match = makeVia( F_Cu, In2_Cu, 1 );
543
544 BOOST_REQUIRE( MatchPendingStackExpansion( match, preRoute, pending ) );
545 BOOST_CHECK_EQUAL( MatchPendingStackExpansion( match, preRoute, pending )->m_Name, wxT( "fanout" ) );
546
547 // Routing upward records the span end to start, while a via always holds it top down.
548 std::vector<PENDING_STACK_EXPANSION> upward = { { In2_Cu, F_Cu, 1, preset } };
549 BOOST_CHECK( MatchPendingStackExpansion( match, preRoute, upward ) );
550
551 PCB_VIA* wrongNet = makeVia( F_Cu, In2_Cu, 2 );
552 BOOST_CHECK( MatchPendingStackExpansion( wrongNet, preRoute, pending ) == nullptr );
553
554 PCB_VIA* wrongSpan = makeVia( F_Cu, In1_Cu, 1 );
555 BOOST_CHECK( MatchPendingStackExpansion( wrongSpan, preRoute, pending ) == nullptr );
556
557 // A via that already existed is not one the route created.
558 preRoute.insert( match->m_Uuid );
559 BOOST_CHECK( MatchPendingStackExpansion( match, preRoute, pending ) == nullptr );
560}
561
562
563BOOST_AUTO_TEST_CASE( DuplicatePresetNamesAreDropped )
564{
565 BOARD_DESIGN_SETTINGS bds( nullptr, "board.design_settings" );
566
567 nlohmann::json entries = nlohmann::json::array();
568
569 entries.push_back( { { "name", "HDI" }, { "start_layer", "F.Cu" }, { "end_layer", "In1.Cu" } } );
570 entries.push_back( { { "name", "hdi" }, { "start_layer", "F.Cu" }, { "end_layer", "In2.Cu" } } );
571 entries.push_back( { { "name", "Other" }, { "start_layer", "F.Cu" }, { "end_layer", "In1.Cu" } } );
572
573 bds.Set( "via_stack_presets", entries );
574 bds.Load();
575
576 BOOST_REQUIRE_EQUAL( bds.m_ViaStackPresets.size(), 2u );
577 BOOST_CHECK_EQUAL( bds.m_ViaStackPresets[0].m_Name, wxS( "HDI" ) );
578 BOOST_CHECK_EQUAL( bds.m_ViaStackPresets[1].m_Name, wxS( "Other" ) );
579}
580
581
582// Capping is an IPC-4761 attribute that reaches the fab, so grouping loose vias must not drop it.
583// Only a hop touching an outer layer carries it, and that hop is the last one on a stack anchored
584// at the back.
585BOOST_AUTO_TEST_CASE( CreateFromItemsKeepsCapping )
586{
587 auto board = std::make_unique<BOARD>();
588 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
589
590 auto capped = []( PCB_VIA* aVia )
591 {
592 aVia->Padstack().Drill().is_capped = true;
593 return aVia;
594 };
595
596 VECTOR2I front( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
597
598 std::vector<BOARD_ITEM*> fromFront = { capped( makeMicrovia( board.get(), front, F_Cu, In1_Cu ) ),
599 makeMicrovia( board.get(), front, In1_Cu, In2_Cu ) };
600
601 std::unique_ptr<PCB_VIA_STACK> topAnchored( PCB_VIA_STACK::CreateFromItems( fromFront, board.get() ) );
602
603 BOOST_REQUIRE( topAnchored );
604 BOOST_CHECK_MESSAGE( topAnchored->IsCapped(), "capping was dropped on a stack anchored at the front" );
605
606 VECTOR2I back( pcbIUScale.mmToIU( 15 ), pcbIUScale.mmToIU( 5 ) );
607
608 std::vector<BOARD_ITEM*> fromBack = { makeMicrovia( board.get(), back, In1_Cu, In2_Cu ),
609 capped( makeMicrovia( board.get(), back, In2_Cu, B_Cu ) ) };
610
611 std::unique_ptr<PCB_VIA_STACK> bottomAnchored( PCB_VIA_STACK::CreateFromItems( fromBack, board.get() ) );
612
613 BOOST_REQUIRE( bottomAnchored );
614 BOOST_CHECK_MESSAGE( bottomAnchored->IsCapped(), "capping was dropped on a stack anchored at the back" );
615}
616
617
618// Two hops can share a position while a third is offset. The hop chain must keep one point per
619// via, or a later regenerate matches hop n to the wrong coordinate.
620BOOST_AUTO_TEST_CASE( CreateFromItemsKeepsCoincidentHops )
621{
622 auto board = std::make_unique<BOARD>();
623 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
624
625 VECTOR2I at( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
626 VECTOR2I offset( at.x + pcbIUScale.mmToIU( 0.45 ), at.y );
627
628 std::vector<BOARD_ITEM*> items = { makeMicrovia( board.get(), at, F_Cu, In1_Cu ),
629 makeMicrovia( board.get(), at, In1_Cu, In2_Cu ),
630 makeMicrovia( board.get(), offset, In2_Cu, In3_Cu ) };
631
632 std::unique_ptr<PCB_VIA_STACK> stack( PCB_VIA_STACK::CreateFromItems( items, board.get() ) );
633
634 BOOST_REQUIRE( stack );
635 BOOST_REQUIRE( stack->GetStyle() == VIA_STACK_STYLE::STAGGERED );
636 BOOST_REQUIRE( stack->GetHops().has_value() );
637
638 BOOST_CHECK_MESSAGE( stack->GetHops()->PointCount() == 3,
639 "hop chain has " << stack->GetHops()->PointCount() << " points for 3 vias" );
640
641 BOOST_CHECK( stack->GetHops()->CPoint( 0 ) == at );
642 BOOST_CHECK( stack->GetHops()->CPoint( 1 ) == at );
643 BOOST_CHECK( stack->GetHops()->CPoint( 2 ) == offset );
644}
645
646
647// Vias on different nets must be rejected.
648BOOST_AUTO_TEST_CASE( CreateFromItemsRejectsMixedNets )
649{
650 auto board = std::make_unique<BOARD>();
651 board->SetCopperLayerCount( 4 );
652 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
653
654 board->Add( new NETINFO_ITEM( board.get(), wxT( "A" ), 1 ) );
655 board->Add( new NETINFO_ITEM( board.get(), wxT( "B" ), 2 ) );
656
657 VECTOR2I pos( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
658
659 PCB_VIA* upper = makeMicrovia( board.get(), pos, F_Cu, In1_Cu );
660 PCB_VIA* lower = makeMicrovia( board.get(), pos, In1_Cu, In2_Cu );
661
662 upper->SetNetCode( 1 );
663 lower->SetNetCode( 2 );
664
665 std::vector<BOARD_ITEM*> items = { upper, lower };
666
667 BOOST_CHECK( PCB_VIA_STACK::CreateFromItems( items, board.get() ) == nullptr );
668
669 // Same net still works.
670 lower->SetNetCode( 1 );
671
672 std::unique_ptr<PCB_VIA_STACK> stack( PCB_VIA_STACK::CreateFromItems( items, board.get() ) );
673
674 BOOST_REQUIRE( stack );
675 BOOST_CHECK_EQUAL( stack->GetNetCode(), 1 );
676}
677
678
679// A layer name a hand-edited file got wrong must not resolve to a real layer, or the stack
680// loads as a plausible span nobody asked for.
681BOOST_AUTO_TEST_CASE( AnUnknownLayerNameLeavesTheSpanAlone )
682{
683 PCB_VIA_STACK stack( nullptr, In1_Cu );
684 stack.SetStartLayer( In1_Cu );
685 stack.SetEndLayer( In3_Cu );
686
687 STRING_ANY_MAP props;
688 props.set( "start_layer", wxString( wxS( "Not.A.Layer" ) ) );
689 props.set( "end_layer", wxString( wxS( "B.Adhes" ) ) );
690
691 stack.SetProperties( props );
692
693 BOOST_CHECK_EQUAL( (int) stack.GetStartLayer(), (int) In1_Cu );
694 BOOST_CHECK_EQUAL( (int) stack.GetEndLayer(), (int) In3_Cu );
695}
696
697
698// A preset names two layers, so placement anchors on one of them and lands on the other.
699// Anywhere else is refused rather than walking a span the preset never described.
711
712
713// A stack rebuilds what it owns, so a stray trace taken in here is deleted on the next
714// regenerate.
715BOOST_AUTO_TEST_CASE( CreateFromItemsTakesOnlyTheTracesLinkingItsHops )
716{
717 auto board = std::make_unique<BOARD>();
718 board->SetCopperLayerCount( 4 );
719 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
720
721 board->Add( new NETINFO_ITEM( board.get(), wxT( "A" ), 1 ) );
722 board->Add( new NETINFO_ITEM( board.get(), wxT( "B" ), 2 ) );
723
724 VECTOR2I p0( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
725 VECTOR2I p1( pcbIUScale.mmToIU( 5.35 ), pcbIUScale.mmToIU( 5 ) );
726 VECTOR2I away( pcbIUScale.mmToIU( 9 ), pcbIUScale.mmToIU( 5 ) );
727
728 PCB_VIA* hop0 = makeMicrovia( board.get(), p0, F_Cu, In1_Cu, 1 );
729 PCB_VIA* hop1 = makeMicrovia( board.get(), p1, In1_Cu, In2_Cu, 1 );
730
731 PCB_TRACK* connector = makeSegment( board.get(), p0, p1 );
732 connector->SetNetCode( 1 );
733
734 PCB_TRACK* offLanding = makeSegment( board.get(), p0, p1 );
735 offLanding->SetLayer( In2_Cu );
736 offLanding->SetNetCode( 1 );
737
738 PCB_TRACK* otherNet = makeSegment( board.get(), p0, p1 );
739 otherNet->SetNetCode( 2 );
740
741 PCB_TRACK* loose = makeSegment( board.get(), p0, away );
742 loose->SetNetCode( 1 );
743
744 PCB_TRACK* zeroLength = makeSegment( board.get(), p0, p0 );
745 zeroLength->SetNetCode( 1 );
746
747 std::vector<BOARD_ITEM*> items = { hop0, hop1, connector, offLanding, otherNet, loose, zeroLength };
748 std::vector<BOARD_ITEM*> members;
749
750 std::unique_ptr<PCB_VIA_STACK> stack( PCB_VIA_STACK::CreateFromItems( items, board.get(), &members ) );
751
752 BOOST_REQUIRE( stack );
753 BOOST_CHECK_EQUAL( members.size(), 3u );
754 BOOST_CHECK_EQUAL( std::count( members.begin(), members.end(), (BOARD_ITEM*) connector ), 1 );
755
756 for( PCB_TRACK* stray : { offLanding, otherNet, loose, zeroLength } )
757 {
758 BOOST_CHECK_MESSAGE( std::count( members.begin(), members.end(), (BOARD_ITEM*) stray ) == 0,
759 "a trace that does not link two hops joined the stack" );
760 }
761}
762
763
764// Taking a via a generator already owns would leave both rebuilding the same copper.
765BOOST_AUTO_TEST_CASE( CreateFromItemsRejectsViasOwnedByAGenerator )
766{
767 auto board = std::make_unique<BOARD>();
768 board->SetCopperLayerCount( 4 );
769 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
770
771 VECTOR2I pos( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
772
773 PCB_VIA_STACK* owner = new PCB_VIA_STACK( board.get(), F_Cu );
774 owner->SetStartLayer( F_Cu );
775 owner->SetEndLayer( In2_Cu );
776 owner->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
777 owner->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
778 owner->SetPosition( pos );
779 board->Add( owner );
780 owner->Regenerate( board.get(), nullptr );
781
782 std::vector<BOARD_ITEM*> items;
783
784 for( BOARD_ITEM* item : owner->GetBoardItems() )
785 items.push_back( item );
786
787 BOOST_REQUIRE_EQUAL( items.size(), 2u );
788
789 BOOST_CHECK( PCB_VIA_STACK::CreateFromItems( items, board.get() ) == nullptr );
790}
791
792
793// A router style microvia spanning several hops expands into a stack of single hop vias.
794BOOST_AUTO_TEST_CASE( ExpandMultiHopMicrovia )
795{
796 auto board = std::make_unique<BOARD>();
797 board->SetCopperLayerCount( 4 );
798 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
799
800 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "N1" ), 1 );
801 board->Add( net );
802
803 VECTOR2I pos( pcbIUScale.mmToIU( 7 ), pcbIUScale.mmToIU( 7 ) );
804 makeMicrovia( board.get(), pos, F_Cu, In2_Cu, 1 );
805
806 int expanded = PCB_VIA_STACK::ExpandMultiHopMicrovias( board.get(), nullptr );
807
808 BOOST_CHECK_EQUAL( expanded, 1 );
809 BOOST_REQUIRE_EQUAL( board->Generators().size(), 1u );
810
811 PCB_VIA_STACK* stack = dynamic_cast<PCB_VIA_STACK*>( board->Generators().front() );
812 BOOST_REQUIRE( stack );
813 BOOST_CHECK_EQUAL( stack->GetNetCode(), 1 );
814
815 int hops = 0;
816
817 for( BOARD_ITEM* item : stack->GetBoardItems() )
818 {
819 PCB_VIA* via = dynamic_cast<PCB_VIA*>( item );
821 BOOST_CHECK( via->GetPosition() == pos );
822 BOOST_CHECK_EQUAL( via->GetNetCode(), 1 );
823 hops++;
824 }
825
826 BOOST_CHECK_EQUAL( hops, 2 );
827
828 // The original multi hop via must be gone.
829 for( PCB_TRACK* track : board->Tracks() )
830 {
831 if( track->Type() == PCB_VIA_T )
832 {
833 PCB_VIA* via = static_cast<PCB_VIA*>( track );
834 BOOST_CHECK( via->GetParentGroup() != nullptr );
835 }
836 }
837
838 // A second run finds nothing.
839 BOOST_CHECK_EQUAL( PCB_VIA_STACK::ExpandMultiHopMicrovias( board.get(), nullptr ), 0 );
840}
841
842
843// The router expands through a commit so the route and its stacks undo as one step.
844BOOST_AUTO_TEST_CASE( ExpansionThroughACommitIsUndoable )
845{
846 BOARD board;
847 TOOL_MANAGER mgr;
848 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
850
851 board.SetCopperLayerCount( 4 );
853 board.Add( new NETINFO_ITEM( &board, wxT( "N1" ), 1 ) );
854
855 VECTOR2I pos( pcbIUScale.mmToIU( 7 ), pcbIUScale.mmToIU( 7 ) );
856 makeMicrovia( &board, pos, F_Cu, In2_Cu, 1 );
857
858 auto countVias = [&]() -> size_t
859 {
860 size_t n = 0;
861
862 for( PCB_TRACK* track : board.Tracks() )
863 {
864 if( track->Type() == PCB_VIA_T )
865 n++;
866 }
867
868 return n;
869 };
870
871 BOOST_REQUIRE_EQUAL( countVias(), 1u );
872
873 {
874 BOARD_COMMIT reverted( &mgr, true, false );
875
877 reverted.Revert();
878
879 BOOST_CHECK_EQUAL( board.Generators().size(), 0u );
880 BOOST_REQUIRE_EQUAL( countVias(), 1u );
881
882 PCB_VIA* restored = dynamic_cast<PCB_VIA*>( board.Tracks().front() );
883
884 BOOST_REQUIRE( restored );
885 BOOST_CHECK_EQUAL( (int) restored->TopLayer(), (int) F_Cu );
886 BOOST_CHECK_EQUAL( (int) restored->BottomLayer(), (int) In2_Cu );
887 BOOST_CHECK( restored->GetParentGroup() == nullptr );
888 }
889
890 BOARD_COMMIT commit( &mgr, true, false );
891
893 commit.Push( wxT( "Expand Microvia Stacks" ) );
894
895 BOOST_REQUIRE_EQUAL( board.Generators().size(), 1u );
896 BOOST_CHECK_EQUAL( countVias(), 2u );
897
898 for( PCB_TRACK* track : board.Tracks() )
899 {
900 if( track->Type() == PCB_VIA_T )
901 BOOST_CHECK( track->GetParentGroup() != nullptr );
902 }
903}
904
905
906// Connectivity spike: two coaxial microvia hops share the inner landing layer at one point.
907// The connectivity engine must treat them as one net, otherwise a stacked stack would be
908// electrically broken. This is the load-bearing assumption of the whole feature.
909BOOST_AUTO_TEST_CASE( CoaxialHopsConnect )
910{
911 auto board = std::make_unique<BOARD>();
912 board->SetCopperLayerCount( 4 );
913 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
914
915 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "N1" ), 1 );
916 board->Add( net );
917
918 VECTOR2I origin( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 20 ) );
919
920 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
921 stack->SetStartLayer( F_Cu );
922 stack->SetEndLayer( In2_Cu );
924 stack->SetViaSize( pcbIUScale.mmToIU( 0.30 ) );
925 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
926 stack->SetPosition( origin );
927 stack->SetNetCode( net->GetNetCode() );
928 board->Add( stack );
929
930 stack->Regenerate( board.get(), nullptr );
931 board->BuildConnectivity();
932
933 std::vector<PCB_VIA*> vias;
934
935 for( BOARD_ITEM* item : stack->GetBoardItems() )
936 {
937 if( item->Type() == PCB_VIA_T )
938 vias.push_back( static_cast<PCB_VIA*>( item ) );
939 }
940
941 BOOST_REQUIRE_EQUAL( vias.size(), 2u );
942
943 std::vector<BOARD_CONNECTED_ITEM*> connected = board->GetConnectivity()->GetConnectedItems( vias[0] );
944
945 bool otherFound = std::find( connected.begin(), connected.end(), static_cast<BOARD_CONNECTED_ITEM*>( vias[1] ) )
946 != connected.end();
947
948 BOOST_CHECK_MESSAGE( otherFound, "Coaxial microvia hops sharing the inner layer must be connected." );
949}
950
951
952// Mirror the placement tool's commit sequence and verify the board state selection
953// depends on: members on the board exactly once, parented to the stack, promotable.
954BOOST_AUTO_TEST_CASE( PlacedStackCommitState )
955{
956 BOARD board;
957 TOOL_MANAGER mgr;
958 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
959 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
960 mgr.RegisterTool( dummyTool );
961
962 board.SetCopperLayerCount( 4 );
964
965 VECTOR2I pos( pcbIUScale.mmToIU( 12 ), pcbIUScale.mmToIU( 12 ) );
966
967 PCB_VIA_STACK settings( &board, F_Cu );
968 settings.SetStartLayer( F_Cu );
969 settings.SetEndLayer( In2_Cu );
971 settings.SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
972 settings.SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
973 settings.SetPosition( pos );
974
975 BOARD_COMMIT commit( &mgr, true, false );
976
977 PCB_VIA_STACK* stack = static_cast<PCB_VIA_STACK*>( settings.Clone() );
978 stack->SetUuidDirect( KIID() );
979 stack->SetParent( &board );
980 stack->SetNetCode( 0 );
981 stack->SetFlags( IS_NEW );
982
983 stack->EditStart( nullptr, &board, &commit );
984 stack->Update( nullptr, &board, &commit );
985 stack->EditFinish( nullptr, &board, &commit );
986 stack->ClearFlags( IS_NEW );
987
988 commit.Push( wxT( "Place Microvia Stack" ) );
989
990 BOOST_REQUIRE_EQUAL( board.Generators().size(), 1u );
991 BOOST_CHECK_EQUAL( board.Generators().front(), static_cast<PCB_GENERATOR*>( stack ) );
992
993 // Exactly two vias on the board, no duplicates.
994 std::vector<PCB_VIA*> boardVias;
995
996 for( PCB_TRACK* track : board.Tracks() )
997 {
998 if( track->Type() == PCB_VIA_T )
999 boardVias.push_back( static_cast<PCB_VIA*>( track ) );
1000 }
1001
1002 BOOST_REQUIRE_EQUAL( boardVias.size(), 2u );
1003 BOOST_CHECK_EQUAL( stack->GetItems().size(), 2u );
1004
1005 for( PCB_VIA* via : boardVias )
1006 {
1007 // Parented to the stack and promotable to it, which is how selection works.
1008 BOOST_CHECK_EQUAL( via->GetParentGroup(), static_cast<EDA_GROUP*>( stack ) );
1009
1010 EDA_GROUP* top = PCB_GROUP::TopLevelGroup( via, nullptr, false );
1011 BOOST_CHECK_EQUAL( top, static_cast<EDA_GROUP*>( stack ) );
1012
1013 // No leftover edit flags that would make items invisible to hit testing.
1014 BOOST_CHECK( !via->HasFlag( IS_NEW ) );
1015 BOOST_CHECK( !via->HasFlag( IN_EDIT ) );
1016 }
1017
1018 BOOST_CHECK( !stack->HasFlag( IN_EDIT ) );
1019 BOOST_CHECK( !stack->HasFlag( IS_NEW ) );
1020
1021 // Selection heuristics and the view index need a real bounding box covering the
1022 // members, the PCB_GENERATOR base returns an empty one.
1023 BOOST_CHECK( stack->GetBoundingBox().Contains( pos ) );
1024 BOOST_CHECK( stack->ViewBBox().Contains( pos ) );
1025
1026 // Generator members are vetoed by PCB_SELECTION_TOOL::Selectable, so the generator
1027 // itself must be hit testable or nothing about the stack can be clicked.
1028 BOOST_CHECK( stack->HitTest( pos, 0 ) );
1029
1030 BOX2I around( pos - VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ),
1031 VECTOR2I( pcbIUScale.mmToIU( 2 ), pcbIUScale.mmToIU( 2 ) ) );
1032 BOOST_CHECK( stack->HitTest( around, false, 0 ) );
1033 BOOST_CHECK( stack->HitTest( around, true, 0 ) );
1034
1035 // Box select and select-all query the view by layer, and LAYER_ANCHOR alone is
1036 // skipped there. The stack must also register on a real layer.
1037 std::vector<int> viewLayers = stack->ViewGetLayers();
1038 BOOST_CHECK( std::find( viewLayers.begin(), viewLayers.end(), (int) stack->GetLayer() ) != viewLayers.end() );
1039}
1040
1041
1042// Every connecting trace of a staggered stack must be a member of the stack, or the
1043// router locks only some of them and the rest float free.
1044BOOST_AUTO_TEST_CASE( StaggeredAllTracesAreMembers )
1045{
1046 auto board = std::make_unique<BOARD>();
1047 board->SetCopperLayerCount( 6 );
1048 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
1049
1050 // F.Cu -> In3 spans 4 copper layers = 3 hops = 3 via positions and 2 connecting traces.
1051 SHAPE_LINE_CHAIN hops;
1052 hops.Append( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
1053 hops.Append( VECTOR2I( pcbIUScale.mmToIU( 10.5 ), pcbIUScale.mmToIU( 10 ) ) );
1054 hops.Append( VECTOR2I( pcbIUScale.mmToIU( 11 ), pcbIUScale.mmToIU( 10 ) ) );
1055
1056 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1057 stack->SetStartLayer( F_Cu );
1058 stack->SetEndLayer( In3_Cu );
1060 stack->SetPitch( pcbIUScale.mmToIU( 0.5 ) );
1061 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1062 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1063 stack->SetHops( hops );
1064 board->Add( stack );
1065 stack->Regenerate( board.get(), nullptr );
1066
1067 int vias = 0;
1068 int traces = 0;
1069
1070 for( BOARD_ITEM* item : stack->GetBoardItems() )
1071 {
1072 if( item->Type() == PCB_VIA_T )
1073 vias++;
1074 else if( item->Type() == PCB_TRACE_T )
1075 traces++;
1076
1077 // The point of the test: EVERY member is parented to the stack.
1078 BOOST_CHECK_EQUAL( item->GetParentGroup(), static_cast<EDA_GROUP*>( stack ) );
1079 }
1080
1081 BOOST_CHECK_EQUAL( vias, 3 );
1082 BOOST_CHECK_EQUAL( traces, 2 );
1083
1084 // And on the board, no connecting trace is left ungrouped.
1085 for( PCB_TRACK* track : board->Tracks() )
1086 {
1087 if( track->Type() == PCB_TRACE_T )
1088 BOOST_CHECK_EQUAL( track->GetParentGroup(), static_cast<EDA_GROUP*>( stack ) );
1089 }
1090}
1091
1092
1093// A placed stack must inherit the net of the copper it lands on.
1094BOOST_AUTO_TEST_CASE( NetInheritedFromLandingCopper )
1095{
1096 auto board = std::make_unique<BOARD>();
1097 board->SetCopperLayerCount( 4 );
1098 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
1099
1100 NETINFO_ITEM* netA = new NETINFO_ITEM( board.get(), wxT( "A" ), 1 );
1101 board->Add( netA );
1102 NETINFO_ITEM* netB = new NETINFO_ITEM( board.get(), wxT( "B" ), 2 );
1103 board->Add( netB );
1104
1105 VECTOR2I padPos( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) );
1106 VECTOR2I trackPos( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 20 ) );
1107 VECTOR2I emptyPos( pcbIUScale.mmToIU( 40 ), pcbIUScale.mmToIU( 40 ) );
1108
1109 FOOTPRINT* fp = new FOOTPRINT( board.get() );
1110 fp->SetPosition( padPos );
1111 board->Add( fp );
1112
1113 PAD* pad = new PAD( fp );
1114 pad->SetPosition( padPos );
1115 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ) );
1117 pad->SetLayerSet( LSET( { F_Cu } ) );
1118 pad->SetNetCode( 1 );
1119 fp->Add( pad );
1120
1121 PCB_TRACK* track = new PCB_TRACK( board.get() );
1122 track->SetStart( trackPos - VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ) );
1123 track->SetEnd( trackPos + VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ) );
1124 track->SetWidth( pcbIUScale.mmToIU( 0.3 ) );
1125 track->SetLayer( F_Cu );
1126 track->SetNetCode( 2 );
1127 board->Add( track );
1128
1129 BOOST_CHECK_EQUAL( PCB_VIA_STACK::FindNetAtPosition( board.get(), padPos, F_Cu ), 1 );
1130 BOOST_CHECK_EQUAL( PCB_VIA_STACK::FindNetAtPosition( board.get(), trackPos, F_Cu ), 2 );
1131 BOOST_CHECK_EQUAL( PCB_VIA_STACK::FindNetAtPosition( board.get(), emptyPos, F_Cu ), 0 );
1132
1133 // The inherited net propagates to every hop on regeneration.
1134 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1135 stack->SetStartLayer( F_Cu );
1136 stack->SetEndLayer( In2_Cu );
1138 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1139 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1140 stack->SetPosition( padPos );
1141 stack->SetNetCode( PCB_VIA_STACK::FindNetAtPosition( board.get(), padPos, F_Cu ) );
1142 board->Add( stack );
1143 stack->Regenerate( board.get(), nullptr );
1144
1145 for( BOARD_ITEM* item : stack->GetBoardItems() )
1146 {
1147 PCB_VIA* via = dynamic_cast<PCB_VIA*>( item );
1148 BOOST_REQUIRE( via );
1149 BOOST_CHECK_EQUAL( via->GetNetCode(), 1 );
1150 }
1151
1152 // SetNetCode re-nets every existing member, so a stack can be re-netted as a unit.
1153 stack->SetNetCode( 2 );
1154
1155 for( BOARD_ITEM* item : stack->GetBoardItems() )
1156 {
1157 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1158 BOOST_CHECK_EQUAL( bci->GetNetCode(), 2 );
1159 }
1160
1161 BOOST_CHECK_EQUAL( stack->GetNetCode(), 2 );
1162}
1163
1164
1165// Placement snaps to copper on any layer the stack spans, so the net has to be looked for
1166// there too and not just on the start layer.
1167BOOST_AUTO_TEST_CASE( NetInheritedFromCopperOnAnInnerLayer )
1168{
1169 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1170 board->SetCopperLayerCount( 4 );
1171 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
1172
1173 board->Add( new NETINFO_ITEM( board.get(), wxT( "inner" ), 1 ) );
1174
1175 VECTOR2I landing( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
1176
1177 PCB_TRACK* track = new PCB_TRACK( board.get() );
1178 track->SetStart( landing - VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ) );
1179 track->SetEnd( landing + VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ) );
1180 track->SetWidth( pcbIUScale.mmToIU( 0.3 ) );
1181 track->SetLayer( In1_Cu );
1182 track->SetNetCode( 1 );
1183 board->Add( track );
1184
1185 LSET span( { F_Cu, In1_Cu, In2_Cu } );
1186
1187 BOOST_CHECK_EQUAL( PCB_VIA_STACK::FindNetAtPosition( board.get(), landing, F_Cu ), 0 );
1188 BOOST_CHECK_EQUAL( PCB_VIA_STACK::FindNetAtPosition( board.get(), landing, span ), 1 );
1189
1190 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1191 stack->SetStartLayer( F_Cu );
1192 stack->SetEndLayer( In2_Cu );
1194 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1195 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1196 stack->SetPosition( landing );
1197 stack->SetNetCode( PCB_VIA_STACK::FindNetAtPosition( board.get(), landing, span ) );
1198 board->Add( stack );
1199 stack->Regenerate( board.get(), nullptr );
1200
1201 BOOST_CHECK_EQUAL( stack->GetNetCode(), 1 );
1202
1203 for( BOARD_ITEM* item : stack->GetBoardItems() )
1204 {
1205 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1206 BOOST_CHECK_EQUAL( bci->GetNetCode(), 1 );
1207 }
1208}
1209
1210
1211BOOST_AUTO_TEST_CASE( NetLookupIgnoresMechanicalHoles )
1212{
1213 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1214 board->SetCopperLayerCount( 4 );
1215 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
1216
1217 board->Add( new NETINFO_ITEM( board.get(), wxT( "sig" ), 1 ) );
1218
1219 VECTOR2I holePos( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
1220
1221 PCB_TRACK* track = new PCB_TRACK( board.get() );
1222 track->SetStart( holePos - VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ) );
1223 track->SetEnd( holePos + VECTOR2I( pcbIUScale.mmToIU( 1 ), 0 ) );
1224 track->SetWidth( pcbIUScale.mmToIU( 0.3 ) );
1225 track->SetLayer( F_Cu );
1226 track->SetNetCode( 1 );
1227 board->Add( track );
1228
1229 FOOTPRINT* fp = new FOOTPRINT( board.get() );
1230 fp->SetPosition( holePos );
1231 board->Add( fp );
1232
1233 PAD* hole = new PAD( fp );
1234 hole->SetPosition( holePos );
1237 hole->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( pcbIUScale.mmToIU( 2 ), pcbIUScale.mmToIU( 2 ) ) );
1239 hole->SetDrillSize( VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ) );
1240 hole->SetLayerSet( LSET::AllCuMask( 4 ) );
1241 fp->Add( hole );
1242
1243 BOOST_REQUIRE_EQUAL( hole->GetNetCode(), 0 );
1244
1245 // The hole sits over the track. It must not shadow it.
1246 BOOST_CHECK_EQUAL( PCB_VIA_STACK::FindNetAtPosition( board.get(), holePos, F_Cu ), 1 );
1247}
1248
1249
1250// Minimal guide so the real GENERAL_COLLECTOR can run headlessly.
1252{
1253public:
1254 bool IsLayerVisible( PCB_LAYER_ID ) const override { return true; }
1255 PCB_LAYER_ID GetPreferredLayer() const override { return F_Cu; }
1256 bool IgnoreLockedItems() const override { return false; }
1257 bool IncludeSecondary() const override { return true; }
1258 bool IgnoreFPTextOnBack() const override { return false; }
1259 bool IgnoreFPTextOnFront() const override { return false; }
1260 bool IgnoreFootprintsOnBack() const override { return false; }
1261 bool IgnoreFootprintsOnFront() const override { return false; }
1262 bool IgnorePadsOnBack() const override { return false; }
1263 bool IgnorePadsOnFront() const override { return false; }
1264 bool IgnoreThroughHolePads() const override { return false; }
1265 bool IgnoreFPValues() const override { return false; }
1266 bool IgnoreFPReferences() const override { return false; }
1267 bool IgnoreThroughVias() const override { return false; }
1268 bool IgnoreBlindBuriedVias() const override { return false; }
1269 bool IgnoreMicroVias() const override { return false; }
1270 bool IgnoreTracks() const override { return false; }
1271 bool IgnoreZoneFills() const override { return true; }
1272 bool IgnoreNoNets() const override { return false; }
1273 int Accuracy() const override { return 0; }
1274 double OnePixelInIU() const override { return 1.0; }
1275};
1276
1277
1278// Run the real click collection over a placed stack, a click on the stack position must
1279// collect the member vias so selection can promote them to the stack.
1280BOOST_AUTO_TEST_CASE( PlacedStackClickCollection )
1281{
1282 auto board = std::make_unique<BOARD>();
1283 board->SetCopperLayerCount( 4 );
1284 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
1285
1286 VECTOR2I pos( pcbIUScale.mmToIU( 15 ), pcbIUScale.mmToIU( 15 ) );
1287
1288 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1289 stack->SetStartLayer( F_Cu );
1290 stack->SetEndLayer( In2_Cu );
1292 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1293 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1294 stack->SetPosition( pos );
1295 board->Add( stack );
1296 stack->Regenerate( board.get(), nullptr );
1297
1299 GENERAL_COLLECTOR collector;
1300
1301 collector.Collect( board.get(), GENERAL_COLLECTOR::AllBoardItems, pos, guide );
1302
1303 int viasCollected = 0;
1304
1305 for( int i = 0; i < collector.GetCount(); i++ )
1306 {
1307 if( collector[i]->Type() == PCB_VIA_T )
1308 viasCollected++;
1309 }
1310
1311 BOOST_CHECK_MESSAGE( viasCollected == 2, "Expected both member vias in the click collection, got "
1312 << viasCollected << " of " << collector.GetCount()
1313 << " collected items" );
1314
1315 // And the promotion target resolves to the stack.
1316 for( int i = 0; i < collector.GetCount(); i++ )
1317 {
1318 if( collector[i]->Type() == PCB_VIA_T )
1319 {
1320 BOOST_CHECK_EQUAL( PCB_GROUP::TopLevelGroup( collector[i], nullptr, false ),
1321 static_cast<EDA_GROUP*>( stack ) );
1322 }
1323 }
1324}
1325
1326
1327// Reverting an edit must put the stack back exactly as it was: the old member vias back on
1328// the board, still bound to the stack. swapData() exchanges the group's member set with the
1329// undo image, so unless the members' back-pointers are repaired afterwards they still name
1330// the image, and the restored stack is a group of vias that do not agree they are in it.
1331BOOST_AUTO_TEST_CASE( RevertEditRestoresMemberGrouping )
1332{
1333 BOARD board;
1334 TOOL_MANAGER mgr;
1335 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
1336 mgr.RegisterTool( new KI_TEST::DUMMY_TOOL() );
1337
1338 // The edit below deepens the stack to In3.Cu, so the board has to be able to hold it.
1339 board.SetCopperLayerCount( 6 );
1341
1342 VECTOR2I pos( pcbIUScale.mmToIU( 12 ), pcbIUScale.mmToIU( 12 ) );
1343
1344 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
1345 stack->SetStartLayer( F_Cu );
1346 stack->SetEndLayer( In2_Cu );
1348 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1349 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1350 stack->SetPosition( pos );
1351 board.Add( stack );
1352 stack->Regenerate( &board, nullptr );
1353
1354 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 2u );
1355
1356 // Deepen the stack by one hop, then throw the edit away.
1357 BOARD_COMMIT commit( &mgr, true, false );
1358
1359 commit.Modify( stack );
1360 stack->SetEndLayer( In3_Cu );
1361 stack->Regenerate( &board, &commit );
1362
1363 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 3u );
1364
1365 commit.Revert();
1366
1368 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 2u );
1369
1370 std::vector<PCB_VIA*> boardVias;
1371
1372 for( PCB_TRACK* track : board.Tracks() )
1373 {
1374 if( track->Type() == PCB_VIA_T )
1375 boardVias.push_back( static_cast<PCB_VIA*>( track ) );
1376 }
1377
1378 BOOST_CHECK_MESSAGE( boardVias.size() == 2,
1379 "Revert left " << boardVias.size() << " vias on the board, expected 2" );
1380
1381 // The crux: every restored member must point back at the stack. A stale back-pointer
1382 // makes the via unselectable as part of the stack, unlocked in the router, and orphaned
1383 // when the stack is deleted.
1384 for( PCB_VIA* via : boardVias )
1385 {
1386 BOOST_CHECK_MESSAGE( via->GetParentGroup() == static_cast<EDA_GROUP*>( stack ),
1387 "Member via at " << via->GetPosition().x << "," << via->GetPosition().y
1388 << " lost its back-pointer to the stack after revert" );
1389 }
1390
1391 // And the group's own view of its members must agree with the board's.
1392 for( BOARD_ITEM* item : stack->GetBoardItems() )
1393 {
1394 BOOST_CHECK_MESSAGE( std::find( board.Tracks().begin(), board.Tracks().end(), item ) != board.Tracks().end(),
1395 "Stack holds a member that is not on the board after revert" );
1396 }
1397}
1398
1399
1400// A stack's members sit on every layer it spans, so its own layer cannot be taken from a
1401// member: the loader picks one out of an unordered set, and which one it gets varies between
1402// loads of the same file. The stack's layer is the start layer, saved and restored as such.
1403BOOST_AUTO_TEST_CASE( LoadedStackKeepsItsOwnLayer )
1404{
1405 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1406
1407 board->SetCopperLayerCount( 6 );
1408 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
1409
1410 VECTOR2I pos( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 20 ) );
1411
1412 // Deliberately not F_Cu, and spanning three layers so members exist on In1/In2/In3.
1413 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1414 stack->SetStartLayer( In1_Cu );
1415 stack->SetEndLayer( In3_Cu );
1417 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1418 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1419 stack->SetPosition( pos );
1420 board->Add( stack );
1421 stack->Regenerate( board.get(), nullptr );
1422
1423 BOOST_REQUIRE_EQUAL( stack->GetLayer(), In1_Cu );
1424 BOOST_REQUIRE( stack->GetItems().size() > 1 );
1425
1426 // The layer must survive a properties round trip, which is what save/load does.
1427 PCB_VIA_STACK reloaded( nullptr, F_Cu );
1428 reloaded.SetProperties( stack->GetProperties() );
1429
1430 BOOST_CHECK_MESSAGE( reloaded.GetStartLayer() == In1_Cu, "Start layer lost in the properties round trip" );
1431 BOOST_CHECK_MESSAGE( reloaded.GetLayer() == In1_Cu, "Stack layer " << reloaded.GetLayer()
1432 << " does not track the start layer " << In1_Cu
1433 << " after a properties round trip" );
1434
1435 // ViewGetLayers drives box select and the view index; it must name the real layer.
1436 std::vector<int> viewLayers = reloaded.ViewGetLayers();
1437 BOOST_CHECK( std::find( viewLayers.begin(), viewLayers.end(), (int) In1_Cu ) != viewLayers.end() );
1438}
1439
1440
1441// A stack whose members are gone has nothing left to describe: it reloads with no size, so it
1442// cannot be seen or picked, and with no net, so anything that rebuilds it emits unconnected
1443// vias. Shrinking the board out from under a stack's span produces exactly that, because
1444// BuildMembers() declines an impossible span after Regenerate() has already dropped the old
1445// members. Such a stack must not reach the file at all.
1446BOOST_AUTO_TEST_CASE( MemberlessStackIsNotSaved )
1447{
1448 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1449
1450 board->SetCopperLayerCount( 4 );
1451 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
1452
1453 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "MICROVIA_NET" ), 1 );
1454 board->Add( net );
1455
1456 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1457 stack->SetStartLayer( F_Cu );
1458 stack->SetEndLayer( In2_Cu );
1460 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1461 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1462 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
1463 stack->SetNetCode( net->GetNetCode() );
1464 board->Add( stack );
1465
1466 // Never regenerated, so it holds nothing. A file, a script or the API can carry one.
1467 BOOST_REQUIRE_MESSAGE( stack->GetItems().empty(), "Precondition failed: the stack has members, so this test is "
1468 "not exercising a ghost" );
1469
1470 auto path = std::filesystem::temp_directory_path() / "via_stack_ghost_tst.kicad_pcb";
1471 ::KI_TEST::DumpBoardToFile( *board, path.string() );
1472
1473 std::unique_ptr<BOARD> reloaded = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
1474
1475 BOOST_CHECK_MESSAGE( reloaded->Generators().empty(),
1476 "A member-less via stack was written to the board file; it reloads "
1477 "invisible, unpickable and on no net" );
1478}
1479
1480
1481// The ordinary case must keep working: a stack that still has members is saved, and its net
1482// comes back with it.
1483BOOST_AUTO_TEST_CASE( PopulatedStackKeepsItsNetAcrossReload )
1484{
1485 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1486
1487 board->SetCopperLayerCount( 4 );
1488 board->SetEnabledLayers( LSET::AllCuMask( 4 ) | LSET::AllTechMask() );
1489
1490 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "MICROVIA_NET" ), 1 );
1491 board->Add( net );
1492
1493 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1494 stack->SetStartLayer( F_Cu );
1495 stack->SetEndLayer( In2_Cu );
1497 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1498 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1499 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
1500 stack->SetNetCode( net->GetNetCode() );
1501 board->Add( stack );
1502 stack->Regenerate( board.get(), nullptr );
1503
1504 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 2u );
1505
1506 auto path = std::filesystem::temp_directory_path() / "via_stack_netkeep_tst.kicad_pcb";
1507 ::KI_TEST::DumpBoardToFile( *board, path.string() );
1508
1509 std::unique_ptr<BOARD> reloaded = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
1510
1511 BOOST_REQUIRE_EQUAL( reloaded->Generators().size(), 1u );
1512
1513 PCB_VIA_STACK* loaded = dynamic_cast<PCB_VIA_STACK*>( reloaded->Generators().front() );
1514 BOOST_REQUIRE( loaded );
1515
1516 NETINFO_ITEM* loadedNet = reloaded->FindNet( wxT( "MICROVIA_NET" ) );
1517 BOOST_REQUIRE( loadedNet );
1518
1519 BOOST_CHECK_MESSAGE( loaded->GetNetCode() == loadedNet->GetNetCode(),
1520 "Reloaded stack reports net " << loaded->GetNetCode() << ", expected "
1521 << loadedNet->GetNetCode() );
1522}
1523
1524
1525// The Properties panel is built from the property manager; a type that declares nothing shows
1526// an empty panel. The rows report only: the panel cannot rebuild a stack, so editing one of
1527// these would change the settings and leave the drawn vias behind.
1528BOOST_AUTO_TEST_CASE( StackPropertiesAreRegisteredAndReadOnly )
1529{
1531 propMgr.Rebuild();
1532
1533 for( const wxString& name : { wxT( "Start Layer" ), wxT( "End Layer" ), wxT( "Style" ), wxT( "Net" ),
1534 wxT( "Pitch" ), wxT( "Via Diameter" ), wxT( "Via Hole" ), wxT( "Copper-filled" ),
1535 wxT( "Capped" ), wxT( "Use Netclass Values" ) } )
1536 {
1537 PROPERTY_BASE* prop = propMgr.GetProperty( TYPE_HASH( PCB_VIA_STACK ), name );
1538
1539 BOOST_REQUIRE_MESSAGE( prop, "Property \"" << name
1540 << "\" is missing, so the Properties "
1541 "panel cannot show it" );
1542 BOOST_CHECK_MESSAGE( !prop->Writeable( nullptr ), "Property \"" << name
1543 << "\" is editable, but editing it cannot "
1544 "rebuild the stack" );
1545 }
1546
1547 // A stack spans layers; the single inherited layer would be misleading.
1548 BOOST_CHECK_MESSAGE( propMgr.GetProperty( TYPE_HASH( PCB_VIA_STACK ), wxT( "Layer" ) ) == nullptr,
1549 "The inherited \"Layer\" property should be masked for a via stack" );
1550}
1551
1552
1553BOOST_AUTO_TEST_CASE( CollectExpandableMicroviasNamesOnlyLooseMultiHopVias )
1554{
1555 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1556
1557 board->SetCopperLayerCount( 6 );
1558 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
1559
1560 auto addVia = [&]( const VECTOR2I& aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, VIATYPE aType )
1561 {
1562 PCB_VIA* via = new PCB_VIA( board.get() );
1563 via->SetViaType( aType );
1564 via->SetLayerPair( aTop, aBottom );
1565 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
1566 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
1567 via->SetPosition( aPos );
1568 board->Add( via );
1569
1570 return via;
1571 };
1572
1573 PCB_VIA* multiHop =
1574 addVia( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In2_Cu, VIATYPE::MICROVIA );
1575
1576 addVia( VECTOR2I( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In1_Cu, VIATYPE::MICROVIA );
1577 addVia( VECTOR2I( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In2_Cu, VIATYPE::BURIED );
1578
1579 // Already owned by a stack, so not loose.
1580 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1581 board->Add( stack );
1582 PCB_VIA* owned =
1583 addVia( VECTOR2I( pcbIUScale.mmToIU( 40 ), pcbIUScale.mmToIU( 10 ) ), F_Cu, In2_Cu, VIATYPE::MICROVIA );
1584 stack->AddItem( owned );
1585
1586 std::set<KIID> ids = PCB_VIA_STACK::CollectExpandableMicrovias( board.get() );
1587
1588 BOOST_CHECK_MESSAGE( ids.count( multiHop->m_Uuid ) == 1,
1589 "A loose multi-hop microvia must be reported as expandable" );
1590 BOOST_CHECK_MESSAGE( ids.count( owned->m_Uuid ) == 0, "A via already in a stack is not expandable" );
1591 BOOST_CHECK_EQUAL( ids.size(), 1u );
1592}
1593
1594
1595static const VECTOR2I PRE_ROUTE_POS( pcbIUScale.mmToIU( 50 ), pcbIUScale.mmToIU( 50 ) );
1596static const VECTOR2I ROUTED_POS( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
1597
1598
1599// Two loose multi-hop microvias sharing a net and layer pair: one already on the board, one
1600// standing in for the via a route just placed. Expansion deletes the vias it takes, so tests
1601// identify them by position afterwards rather than by pointer.
1602static void makeSharedNetMultiHopPair( BOARD* aBoard )
1603{
1604 aBoard->SetCopperLayerCount( 6 );
1606
1607 NETINFO_ITEM* net = new NETINFO_ITEM( aBoard, wxT( "SHARED" ), 1 );
1608 aBoard->Add( net );
1609
1610 for( const VECTOR2I& pos : { PRE_ROUTE_POS, ROUTED_POS } )
1611 {
1612 PCB_VIA* via = new PCB_VIA( aBoard );
1613 via->SetViaType( VIATYPE::MICROVIA );
1614 via->SetLayerPair( F_Cu, In2_Cu );
1615 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
1616 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
1617 via->SetPosition( pos );
1618 via->SetNetCode( net->GetNetCode() );
1619 aBoard->Add( via );
1620 }
1621}
1622
1623
1624static std::vector<VECTOR2I> looseMicroviaPositions( BOARD* aBoard )
1625{
1626 std::vector<VECTOR2I> positions;
1627
1628 for( PCB_TRACK* track : aBoard->Tracks() )
1629 {
1630 if( track->Type() == PCB_VIA_T && !track->GetParentGroup()
1631 && static_cast<PCB_VIA*>( track )->GetViaType() == VIATYPE::MICROVIA )
1632 {
1633 positions.push_back( track->GetPosition() );
1634 }
1635 }
1636
1637 return positions;
1638}
1639
1640
1641// Matching on net and layer pair alone, as the router did, reaches across the whole board.
1642// This is what the snapshot guard exists to prevent.
1643BOOST_AUTO_TEST_CASE( ExpansionWithoutAGuardTakesEveryMatchingVia )
1644{
1645 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1646 makeSharedNetMultiHopPair( board.get() );
1647
1648 VIA_STACK_PRESET preset;
1649
1650 auto unguarded = [&]( PCB_VIA* aVia ) -> const VIA_STACK_PRESET*
1651 {
1652 return aVia->GetNetCode() == 1 ? &preset : nullptr;
1653 };
1654
1655 BOOST_CHECK_EQUAL( PCB_VIA_STACK::ExpandMultiHopMicrovias( board.get(), nullptr, unguarded ), 2 );
1656 BOOST_CHECK_EQUAL( board->Generators().size(), 2u );
1657}
1658
1659
1660// With the snapshot taken before routing, only the via the route created is expanded.
1661BOOST_AUTO_TEST_CASE( ExpansionLeavesPreRouteViasAlone )
1662{
1663 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1664
1665 board->SetCopperLayerCount( 6 );
1666 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
1667
1668 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "SHARED" ), 1 );
1669 board->Add( net );
1670
1671 auto addMultiHop = [&]( const VECTOR2I& aPos )
1672 {
1673 PCB_VIA* via = new PCB_VIA( board.get() );
1674 via->SetViaType( VIATYPE::MICROVIA );
1675 via->SetLayerPair( F_Cu, In2_Cu );
1676 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
1677 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
1678 via->SetPosition( aPos );
1679 via->SetNetCode( net->GetNetCode() );
1680 board->Add( via );
1681 };
1682
1683 addMultiHop( PRE_ROUTE_POS );
1684
1685 std::set<KIID> beforeRouting = PCB_VIA_STACK::CollectExpandableMicrovias( board.get() );
1686 BOOST_REQUIRE_EQUAL( beforeRouting.size(), 1u );
1687
1688 addMultiHop( ROUTED_POS );
1689
1690 VIA_STACK_PRESET preset;
1691
1692 std::vector<PENDING_STACK_EXPANSION> pending = { { F_Cu, In2_Cu, net->GetNetCode(), preset } };
1693
1694 auto guarded = [&]( PCB_VIA* aVia ) -> const VIA_STACK_PRESET*
1695 {
1696 return MatchPendingStackExpansion( aVia, beforeRouting, pending );
1697 };
1698
1699 BOOST_CHECK_EQUAL( PCB_VIA_STACK::ExpandMultiHopMicrovias( board.get(), nullptr, guarded ), 1 );
1700
1701 BOOST_REQUIRE_EQUAL( board->Generators().size(), 1u );
1702 BOOST_CHECK_MESSAGE( board->Generators().front()->GetPosition() == ROUTED_POS,
1703 "The stack was built at the wrong via" );
1704
1705 std::vector<VECTOR2I> loose = looseMicroviaPositions( board.get() );
1706
1707 BOOST_REQUIRE_MESSAGE( loose.size() == 1,
1708 "Expected the pre-existing via to be left loose, found " << loose.size() );
1709 BOOST_CHECK_MESSAGE( loose.front() == PRE_ROUTE_POS, "A via that predates the route was swallowed into a stack" );
1710}
1711
1712
1713BOOST_AUTO_TEST_CASE( MovingAStackReusesItsMembers )
1714{
1715 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1716
1717 board->SetCopperLayerCount( 6 );
1718 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
1719
1720 VECTOR2I origin( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
1721
1722 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1723 stack->SetStartLayer( F_Cu );
1724 stack->SetEndLayer( In4_Cu );
1726 stack->SetPitch( pcbIUScale.mmToIU( 0.5 ) );
1727 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1728 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1729 stack->SetPosition( origin );
1730 board->Add( stack );
1731 stack->Regenerate( board.get(), nullptr );
1732
1733 auto memberIds = [&]()
1734 {
1735 std::set<KIID> ids;
1736
1737 for( BOARD_ITEM* item : stack->GetBoardItems() )
1738 ids.insert( item->m_Uuid );
1739
1740 return ids;
1741 };
1742
1743 auto countVias = [&]() -> size_t
1744 {
1745 size_t n = 0;
1746
1747 for( PCB_TRACK* track : board->Tracks() )
1748 {
1749 if( track->Type() == PCB_VIA_T )
1750 n++;
1751 }
1752
1753 return n;
1754 };
1755
1756 const std::set<KIID> before = memberIds();
1757 const size_t viasBefore = countVias();
1758
1759 BOOST_REQUIRE( before.size() > 1 );
1760 BOOST_REQUIRE( viasBefore > 1 );
1761
1762 for( int step = 0; step < 5; ++step )
1763 {
1764 stack->Move( VECTOR2I( pcbIUScale.mmToIU( 0.2 ), 0 ) );
1765 stack->Regenerate( board.get(), nullptr );
1766 }
1767
1768 BOOST_CHECK_MESSAGE( memberIds() == before, "Moving the stack replaced its members instead of moving them, so a "
1769 "drag would stage one generation of vias per motion event" );
1770
1771 BOOST_CHECK_MESSAGE( countVias() == viasBefore, "The board gained or lost vias while the stack was moved: "
1772 << countVias() << " now, " << viasBefore << " before" );
1773}
1774
1775
1776// Pinned against a committed board file rather than against the writer, so a renamed token or
1777// a changed unit is caught instead of round-tripping quietly. Regenerate this fixture only
1778// alongside a deliberate file format change.
1779BOOST_AUTO_TEST_CASE( GoldenFileStillLoads )
1780{
1781 std::string path = KI_TEST::GetPcbnewTestDataDir() + "via_stacks.kicad_pcb";
1782
1783 std::unique_ptr<BOARD> board = ::KI_TEST::ReadBoardFromFileOrStream( path );
1784 BOOST_REQUIRE( board );
1785
1786 BOOST_REQUIRE_EQUAL( board->Generators().size(), 2u );
1787
1788 PCB_VIA_STACK* stacked = nullptr;
1789 PCB_VIA_STACK* staggered = nullptr;
1790
1791 for( PCB_GENERATOR* gen : board->Generators() )
1792 {
1793 PCB_VIA_STACK* stack = dynamic_cast<PCB_VIA_STACK*>( gen );
1794 BOOST_REQUIRE( stack );
1795
1796 if( stack->GetStyle() == VIA_STACK_STYLE::STACKED )
1797 stacked = stack;
1798 else
1799 staggered = stack;
1800 }
1801
1802 BOOST_REQUIRE( stacked );
1803 BOOST_REQUIRE( staggered );
1804
1805 BOOST_CHECK_EQUAL( (int) stacked->GetStartLayer(), (int) F_Cu );
1806 BOOST_CHECK_EQUAL( (int) stacked->GetEndLayer(), (int) In2_Cu );
1807 BOOST_CHECK_EQUAL( (int) stacked->GetLayer(), (int) F_Cu );
1808 BOOST_CHECK_EQUAL( stacked->GetViaSize(), pcbIUScale.mmToIU( 0.25 ) );
1809 BOOST_CHECK_EQUAL( stacked->GetViaDrill(), pcbIUScale.mmToIU( 0.10 ) );
1810 BOOST_CHECK_EQUAL( stacked->IsFilled(), true );
1811 BOOST_CHECK_EQUAL( stacked->IsCapped(), true );
1812 BOOST_CHECK_EQUAL( stacked->GetUseNetclass(), false );
1813 BOOST_CHECK_EQUAL( stacked->GetPresetName(), wxS( "HDI 1+N+1" ) );
1814 BOOST_CHECK_EQUAL( stacked->GetItems().size(), 2u );
1815 BOOST_CHECK( stacked->GetPosition() == VECTOR2I( pcbIUScale.mmToIU( 20 ), pcbIUScale.mmToIU( 20 ) ) );
1816
1817 BOOST_CHECK_EQUAL( (int) staggered->GetStartLayer(), (int) In1_Cu );
1818 BOOST_CHECK_EQUAL( (int) staggered->GetEndLayer(), (int) In3_Cu );
1819 BOOST_CHECK_EQUAL( (int) staggered->GetLayer(), (int) In1_Cu );
1820 BOOST_CHECK_EQUAL( staggered->GetPitch(), pcbIUScale.mmToIU( 0.45 ) );
1821 BOOST_CHECK_EQUAL( staggered->IsFilled(), false );
1822 BOOST_CHECK_EQUAL( staggered->GetPresetName(), wxS( "Staggered 0.45" ) );
1823
1824 // One hop position per microvia, so an In1.Cu to In3.Cu span carries two.
1825 BOOST_REQUIRE( staggered->GetHops().has_value() );
1826 BOOST_CHECK_EQUAL( staggered->GetHops()->PointCount(), 2 );
1827 BOOST_CHECK( staggered->GetHops()->CPoint( 1 ) == VECTOR2I( pcbIUScale.mmToIU( 40.45 ), pcbIUScale.mmToIU( 20 ) ) );
1828
1829 // The members must come back attached, not as loose vias.
1830 for( BOARD_ITEM* item : stacked->GetBoardItems() )
1831 BOOST_CHECK( item->GetParentGroup() == static_cast<EDA_GROUP*>( stacked ) );
1832
1833 // Only a hop reaching an outer layer is capped, so the fixture must say what a regenerate
1834 // would produce.
1835 for( BOARD_ITEM* item : stacked->GetBoardItems() )
1836 {
1837 PCB_VIA* hop = static_cast<PCB_VIA*>( item );
1838 bool outer = IsExternalCopperLayer( hop->TopLayer() ) || IsExternalCopperLayer( hop->BottomLayer() );
1839
1840 BOOST_CHECK_MESSAGE( hop->Padstack().Drill().is_capped.value_or( false ) == outer,
1841 "hop " << LSET::Name( hop->TopLayer() ) << "-" << LSET::Name( hop->BottomLayer() )
1842 << " capping disagrees with the generator" );
1843 }
1844}
1845
1846
1847// Deleting a stack takes its members with it, and reverting the same staging puts them back.
1848BOOST_AUTO_TEST_CASE( DeletingAStackRemovesItsMembersAndRevertRestoresThem )
1849{
1850 BOARD board;
1851 TOOL_MANAGER mgr;
1852 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
1853 mgr.RegisterTool( new KI_TEST::DUMMY_TOOL() );
1854
1855 board.SetCopperLayerCount( 4 );
1857
1858 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
1859 stack->SetStartLayer( F_Cu );
1860 stack->SetEndLayer( In2_Cu );
1862 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1863 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1864 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
1865 board.Add( stack );
1866 stack->Regenerate( &board, nullptr );
1867
1868 auto countVias = [&]() -> size_t
1869 {
1870 size_t n = 0;
1871
1872 for( PCB_TRACK* track : board.Tracks() )
1873 {
1874 if( track->Type() == PCB_VIA_T )
1875 n++;
1876 }
1877
1878 return n;
1879 };
1880
1881 const size_t before = countVias();
1882 BOOST_REQUIRE_EQUAL( before, 2u );
1883
1884 {
1885 BOARD_COMMIT reverted( &mgr, true, false );
1886
1887 stack->Remove( nullptr, &board, &reverted );
1888 reverted.Revert();
1889
1890 BOOST_CHECK_MESSAGE( countVias() == before,
1891 "Reverting the delete left " << countVias() << " vias, expected " << before );
1892 BOOST_CHECK_EQUAL( board.Generators().size(), 1u );
1893 }
1894
1895 BOARD_COMMIT commit( &mgr, true, false );
1896
1897 stack->Remove( nullptr, &board, &commit );
1898 commit.Push( wxT( "Delete Microvia Stack" ) );
1899
1900 BOOST_CHECK_EQUAL( countVias(), 0u );
1901 BOOST_CHECK_EQUAL( board.Generators().size(), 0u );
1902}
1903
1904
1905// The preset list is project-file data with a hand-written reader, and no test covered it.
1906BOOST_AUTO_TEST_CASE( ViaStackPresetsRoundTripThroughSettings )
1907{
1908 BOARD_DESIGN_SETTINGS bds( nullptr, "board.design_settings" );
1909
1910 VIA_STACK_PRESET preset;
1911 preset.m_Name = wxS( "HDI 1+N+1" );
1912 preset.m_StartLayer = F_Cu;
1913 preset.m_EndLayer = In2_Cu;
1914 preset.m_Staggered = true;
1915 preset.m_ViaSize = pcbIUScale.mmToIU( 0.25 );
1916 preset.m_ViaDrill = pcbIUScale.mmToIU( 0.10 );
1917 preset.m_UseNetclass = true;
1918 preset.m_Filled = false;
1919 preset.m_Capped = true;
1920 preset.m_Pitch = pcbIUScale.mmToIU( 0.45 );
1921
1922 bds.m_ViaStackPresets.push_back( preset );
1923 bds.Store();
1924
1925 bds.m_ViaStackPresets.clear();
1926 bds.Load();
1927
1928 BOOST_REQUIRE_EQUAL( bds.m_ViaStackPresets.size(), 1u );
1929 BOOST_CHECK( bds.m_ViaStackPresets[0] == preset );
1930
1931 // Layers are stored by canonical name, not by layer id.
1932 std::optional<nlohmann::json> stored = bds.Get<nlohmann::json>( "via_stack_presets" );
1933
1934 BOOST_REQUIRE( stored.has_value() );
1935 BOOST_REQUIRE( stored->is_array() && stored->size() == 1 );
1936 BOOST_CHECK_EQUAL( ( *stored )[0]["start_layer"].get<std::string>(), "F.Cu" );
1937 BOOST_CHECK_EQUAL( ( *stored )[0]["end_layer"].get<std::string>(), "In2.Cu" );
1938}
1939
1940
1941// The reader sanitises hand-edited project files: a bad layer id would make every LAYER_RANGE
1942// built from the preset run away, and an entry with no name is not a preset at all.
1943BOOST_AUTO_TEST_CASE( MalformedPresetEntriesAreRejected )
1944{
1945 BOARD_DESIGN_SETTINGS bds( nullptr, "board.design_settings" );
1946
1947 nlohmann::json entries = nlohmann::json::array();
1948
1949 entries.push_back( { { "name", "unknown layer" }, { "start_layer", "Nonsense" }, { "end_layer", "" } } );
1950 entries.push_back( { { "start_layer", "F.Cu" }, { "end_layer", "In1.Cu" } } ); // no name
1951 entries.push_back( "not an object" );
1952 entries.push_back( { { "name", "not copper" }, { "start_layer", "F.SilkS" }, { "end_layer", "Edge.Cuts" } } );
1953 entries.push_back( { { "name", "still numeric" }, { "start_layer", 0 }, { "end_layer", 4 } } );
1954
1955 bds.Set( "via_stack_presets", entries );
1956 bds.Load();
1957
1958 BOOST_REQUIRE_EQUAL( bds.m_ViaStackPresets.size(), 3u );
1959
1960 for( const VIA_STACK_PRESET& p : bds.m_ViaStackPresets )
1961 {
1962 BOOST_CHECK_MESSAGE( IsCopperLayer( p.m_StartLayer ) && IsCopperLayer( p.m_EndLayer ),
1963 "Preset \"" << p.m_Name << "\" kept a non-copper layer" );
1964 }
1965}
1966
1967
1968// Widening a stack's span must leave exactly the hops the new span needs.
1969BOOST_AUTO_TEST_CASE( EditingAnExistingStackRebuildsItsHops )
1970{
1971 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1972
1973 board->SetCopperLayerCount( 6 );
1974 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
1975
1976 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
1977 stack->SetStartLayer( F_Cu );
1978 stack->SetEndLayer( In1_Cu );
1980 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
1981 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
1982 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
1983 board->Add( stack );
1984 stack->Regenerate( board.get(), nullptr );
1985
1986 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 1u );
1987
1988 stack->SetEndLayer( In4_Cu );
1989 stack->Regenerate( board.get(), nullptr );
1990
1991 std::set<std::pair<int, int>> pairs;
1992
1993 for( BOARD_ITEM* item : stack->GetBoardItems() )
1994 {
1995 BOOST_REQUIRE_EQUAL( item->Type(), PCB_VIA_T );
1996 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1997 pairs.insert( { (int) via->TopLayer(), (int) via->BottomLayer() } );
1998 }
1999
2000 BOOST_CHECK_EQUAL( pairs.size(), 4u );
2001 BOOST_CHECK_EQUAL( pairs.count( { (int) In3_Cu, (int) In4_Cu } ), 1u );
2002
2003 size_t vias = 0;
2004
2005 for( PCB_TRACK* track : board->Tracks() )
2006 {
2007 if( track->Type() == PCB_VIA_T )
2008 vias++;
2009 }
2010
2011 BOOST_CHECK_MESSAGE( vias == 4, "Widening the span left " << vias << " vias on the board, "
2012 << "expected 4" );
2013}
2014
2015
2016// Only microvias make a stack; a selection holding any other via type is refused outright.
2017BOOST_AUTO_TEST_CASE( CreateFromItemsRejectsNonMicrovias )
2018{
2019 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
2020
2021 board->SetCopperLayerCount( 6 );
2022 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
2023
2024 VECTOR2I pos( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
2025
2026 auto addVia = [&]( PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, VIATYPE aType )
2027 {
2028 PCB_VIA* via = new PCB_VIA( board.get() );
2029 via->SetViaType( aType );
2030 via->SetLayerPair( aTop, aBottom );
2031 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
2032 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
2033 via->SetPosition( pos );
2034 board->Add( via );
2035
2036 return via;
2037 };
2038
2040 {
2041 std::vector<BOARD_ITEM*> items = { addVia( F_Cu, In1_Cu, VIATYPE::MICROVIA ), addVia( In1_Cu, In2_Cu, type ) };
2042
2043 BOOST_CHECK_MESSAGE( PCB_VIA_STACK::CreateFromItems( items, board.get() ) == nullptr,
2044 "A selection containing a non-microvia was accepted as a stack" );
2045 }
2046
2047 // Two microvias that do tile a span are accepted, so the rejection above is about the type.
2048 std::vector<BOARD_ITEM*> good = { addVia( In2_Cu, In3_Cu, VIATYPE::MICROVIA ),
2049 addVia( In3_Cu, In4_Cu, VIATYPE::MICROVIA ) };
2050
2051 std::unique_ptr<PCB_VIA_STACK> made( PCB_VIA_STACK::CreateFromItems( good, board.get() ) );
2052 BOOST_CHECK( made != nullptr );
2053}
2054
2055
2056// Capping plates a lid over a filled via so the outer face is flat and solderable. Only a hop
2057// that reaches an outer layer has such a face; the rest are buried under the next build-up
2058// layer, and asking the fabricator to cap those describes a step it cannot perform.
2059BOOST_AUTO_TEST_CASE( CappingOnlyMarksTheHopOnTheOuterLayer )
2060{
2061 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
2062
2063 board->SetCopperLayerCount( 6 );
2064 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
2065
2066 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), F_Cu );
2067 stack->SetStartLayer( F_Cu );
2068 stack->SetEndLayer( In3_Cu );
2070 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
2071 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
2072 stack->SetCapped( true );
2073 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
2074 board->Add( stack );
2075 stack->Regenerate( board.get(), nullptr );
2076
2077 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 3u );
2078
2079 int capped = 0;
2080
2081 for( BOARD_ITEM* item : stack->GetBoardItems() )
2082 {
2083 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2084
2085 BOOST_CHECK_MESSAGE( via->Padstack().Drill().is_filled.value_or( false ),
2086 "Every hop of a stacked stack must be filled" );
2087
2088 if( via->Padstack().Drill().is_capped.value_or( false ) )
2089 {
2090 capped++;
2091
2092 BOOST_CHECK_MESSAGE( IsExternalCopperLayer( via->TopLayer() )
2093 || IsExternalCopperLayer( via->BottomLayer() ),
2094 "A buried hop was marked capped" );
2095 }
2096 }
2097
2098 BOOST_CHECK_MESSAGE( capped == 1, "Expected exactly the outer hop to be capped, found " << capped );
2099}
2100
2101
2102// A stack that never reaches an outer layer has nothing to cap.
2103BOOST_AUTO_TEST_CASE( CappingAnInternalStackMarksNothing )
2104{
2105 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
2106
2107 board->SetCopperLayerCount( 6 );
2108 board->SetEnabledLayers( LSET::AllCuMask( 6 ) | LSET::AllTechMask() );
2109
2110 PCB_VIA_STACK* stack = new PCB_VIA_STACK( board.get(), In1_Cu );
2111 stack->SetStartLayer( In1_Cu );
2112 stack->SetEndLayer( In3_Cu );
2114 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
2115 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
2116 stack->SetCapped( true );
2117 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
2118 board->Add( stack );
2119 stack->Regenerate( board.get(), nullptr );
2120
2121 BOOST_REQUIRE_EQUAL( stack->GetItems().size(), 2u );
2122
2123 for( BOARD_ITEM* item : stack->GetBoardItems() )
2124 {
2125 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2126
2127 BOOST_CHECK_MESSAGE( !via->Padstack().Drill().is_capped.value_or( false ),
2128 "A wholly internal stack has no outer face to cap" );
2129 }
2130}
2131
2132
2133// A stack draws and hit-tests on its own layer, so a preset must set it too.
2134BOOST_AUTO_TEST_CASE( ApplyPresetKeepsTheStackLayerInStep )
2135{
2136 BOARD board;
2137 board.SetCopperLayerCount( 4 );
2139
2140 PCB_VIA_STACK stack( &board, F_Cu );
2141
2142 VIA_STACK_PRESET preset;
2143 preset.m_Name = wxT( "buried" );
2144 preset.m_StartLayer = In1_Cu;
2145 preset.m_EndLayer = In2_Cu;
2146
2147 stack.ApplyPreset( preset );
2148
2150 BOOST_CHECK_MESSAGE( stack.GetLayer() == In1_Cu, "the stack's own layer must follow the preset's start layer" );
2151
2152 std::vector<int> layers = stack.ViewGetLayers();
2153
2154 BOOST_CHECK_MESSAGE( std::find( layers.begin(), layers.end(), In1_Cu ) != layers.end(),
2155 "the stack must register in the view on its start layer" );
2156}
2157
2158
2159// Editing a stack must not rewrite its connecting traces to whatever width the toolbar shows.
2160BOOST_AUTO_TEST_CASE( ConnectingTraceWidthSurvivesAnEdit )
2161{
2162 BOARD board;
2163 board.SetCopperLayerCount( 4 );
2165
2167
2168 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
2169 stack->SetStartLayer( F_Cu );
2170 stack->SetEndLayer( In2_Cu );
2172 stack->SetPitch( pcbIUScale.mmToIU( 0.6 ) );
2173 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
2174 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
2175 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
2176 board.Add( stack );
2177 stack->Regenerate( &board, nullptr );
2178
2179 auto traceWidth = [&]() -> int
2180 {
2181 for( BOARD_ITEM* item : stack->GetBoardItems() )
2182 {
2183 if( item->Type() == PCB_TRACE_T )
2184 return static_cast<PCB_TRACK*>( item )->GetWidth();
2185 }
2186
2187 return 0;
2188 };
2189
2190 int placed = traceWidth();
2191 BOOST_REQUIRE( placed > 0 );
2192
2193 // The user changes the toolbar track width, then nudges the stack's pitch.
2194 bds.SetCustomTrackWidth( placed * 3 );
2195 bds.UseCustomTrackViaSize( true );
2196
2197 stack->SetPitch( pcbIUScale.mmToIU( 0.7 ) );
2198 stack->Regenerate( &board, nullptr );
2199
2200 BOOST_CHECK_MESSAGE( traceWidth() == placed, "trace width changed from " << placed << " to " << traceWidth()
2201 << " without the user touching it" );
2202}
2203
2204
2205// Ctrl+D on a stack goes through DeepDuplicate, which must give the copy its own hops.
2206BOOST_AUTO_TEST_CASE( DeepDuplicateGivesTheCopyItsOwnMembers )
2207{
2208 BOARD board;
2209 board.SetCopperLayerCount( 4 );
2211
2212 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
2213 stack->SetStartLayer( F_Cu );
2214 stack->SetEndLayer( In2_Cu );
2215 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
2216 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
2217 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
2218 board.Add( stack );
2219 stack->Regenerate( &board, nullptr );
2220
2221 BOOST_REQUIRE_EQUAL( stack->GetBoardItems().size(), 2u );
2222
2224
2226 BOOST_CHECK( dynamic_cast<PCB_VIA_STACK*>( copy ) );
2227 BOOST_CHECK( copy->m_Uuid != stack->m_Uuid );
2228
2229 // A group holds an item only once, so copying by reference would move the vias to the
2230 // copy rather than alias them.
2231 BOOST_CHECK_EQUAL( stack->GetBoardItems().size(), 2u );
2232 BOOST_CHECK_EQUAL( copy->GetBoardItems().size(), 2u );
2233
2234 delete copy;
2235}
2236
2237
2238// A preset that says "use netclass" must still do so after the router expands its via.
2239BOOST_AUTO_TEST_CASE( ExpansionHonoursAPresetsUseNetclass )
2240{
2241 BOARD board;
2242 board.SetCopperLayerCount( 4 );
2244
2246
2247 const int netclassDia = pcbIUScale.mmToIU( 0.25 );
2248 const int netclassDrill = pcbIUScale.mmToIU( 0.12 );
2249
2250 bds.m_NetSettings->GetDefaultNetclass()->SetuViaDiameter( netclassDia );
2251 bds.m_NetSettings->GetDefaultNetclass()->SetuViaDrill( netclassDrill );
2252
2253 // A bare test board has no project, so SynchronizeNetsAndNetClasses cannot do this.
2254 NETINFO_ITEM* netinfo = new NETINFO_ITEM( &board, wxT( "N1" ), 1 );
2255 netinfo->SetNetClass( bds.m_NetSettings->GetDefaultNetclass() );
2256 board.Add( netinfo );
2257
2258 // A routed multi-hop microvia, deliberately not the netclass size.
2259 PCB_VIA* via = new PCB_VIA( &board );
2260 via->SetViaType( VIATYPE::MICROVIA );
2261 via->SetLayerPair( F_Cu, In2_Cu );
2262 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.40 ) );
2263 via->SetDrill( pcbIUScale.mmToIU( 0.20 ) );
2264 via->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
2265 via->SetNetCode( netinfo->GetNetCode() );
2266 board.Add( via );
2267
2268 VIA_STACK_PRESET preset;
2269 preset.m_Name = wxT( "netclass stack" );
2270 preset.m_UseNetclass = true;
2271
2272 int expanded = PCB_VIA_STACK::ExpandMultiHopMicrovias( &board, nullptr,
2273 [&]( PCB_VIA* )
2274 {
2275 return &preset;
2276 } );
2277
2278 BOOST_REQUIRE_EQUAL( expanded, 1 );
2279 BOOST_REQUIRE_EQUAL( board.Generators().size(), 1u );
2280
2281 PCB_VIA_STACK* stack = dynamic_cast<PCB_VIA_STACK*>( board.Generators().front() );
2282 BOOST_REQUIRE( stack );
2283 BOOST_CHECK( stack->GetUseNetclass() );
2284
2285 for( BOARD_ITEM* item : stack->GetBoardItems() )
2286 {
2287 if( item->Type() != PCB_VIA_T )
2288 continue;
2289
2290 PCB_VIA* hop = static_cast<PCB_VIA*>( item );
2291
2292 BOOST_CHECK_MESSAGE( hop->GetWidth( PADSTACK::ALL_LAYERS ) == netclassDia,
2293 "hop diameter " << hop->GetWidth( PADSTACK::ALL_LAYERS ) << " should be the netclass "
2294 << netclassDia );
2295 BOOST_CHECK_EQUAL( hop->GetDrillValue(), netclassDrill );
2296 }
2297}
2298
2299
2300BOOST_AUTO_TEST_CASE( AnUnbuildableSpanKeepsTheExistingHops )
2301{
2302 BOARD board;
2303 board.SetCopperLayerCount( 4 );
2305
2306 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
2307 stack->SetStartLayer( F_Cu );
2308 stack->SetEndLayer( In2_Cu );
2309 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
2310 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
2311 stack->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ) );
2312 board.Add( stack );
2313 stack->Regenerate( &board, nullptr );
2314
2315 BOOST_REQUIRE_EQUAL( stack->GetBoardItems().size(), 2u );
2316
2317 // The span becomes unbuildable, as it would from a hand-edited layer name or a board
2318 // whose copper layer count was reduced.
2319 stack->SetEndLayer( In6_Cu );
2320 stack->Regenerate( &board, nullptr );
2321
2322 BOOST_CHECK_MESSAGE( stack->GetBoardItems().size() == 2u, "hops were destroyed, leaving an invisible stack: "
2323 << stack->GetBoardItems().size() << " members" );
2324}
2325
2326
2327// Widening a staggered span adds a hop, and it has to carry on from where the chain runs,
2328// not jump back onto the +X axis.
2329BOOST_AUTO_TEST_CASE( WideningAStaggeredSpanContinuesTheChain )
2330{
2331 BOARD board;
2332 board.SetCopperLayerCount( 6 );
2334
2335 const int pitch = pcbIUScale.mmToIU( 0.5 );
2336 VECTOR2I origin( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
2337
2338 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
2339 stack->SetStartLayer( F_Cu );
2340 stack->SetEndLayer( In2_Cu );
2342 stack->SetPitch( pitch );
2343 stack->SetViaSize( pcbIUScale.mmToIU( 0.3 ) );
2344 stack->SetViaDrill( pcbIUScale.mmToIU( 0.15 ) );
2345 stack->SetPosition( origin );
2346
2347 // The user steered this chain downwards, not along +X.
2348 SHAPE_LINE_CHAIN hops;
2349 hops.Append( origin );
2350 hops.Append( origin + VECTOR2I( 0, pitch ) );
2351 stack->SetHops( hops );
2352
2353 board.Add( stack );
2354 stack->Regenerate( &board, nullptr );
2355
2356 // Deepen it by one hop.
2357 stack->SetEndLayer( In3_Cu );
2358 stack->Regenerate( &board, nullptr );
2359
2360 std::vector<VECTOR2I> centres;
2361
2362 for( BOARD_ITEM* item : stack->GetBoardItems() )
2363 {
2364 if( item->Type() == PCB_VIA_T )
2365 centres.push_back( item->GetPosition() );
2366 }
2367
2368 // Member order is not guaranteed, so ask where the hops are rather than which is which.
2369 auto has = [&]( const VECTOR2I& aPoint )
2370 {
2371 return std::find( centres.begin(), centres.end(), aPoint ) != centres.end();
2372 };
2373
2374 BOOST_REQUIRE_EQUAL( centres.size(), 3u );
2375
2376 for( const VECTOR2I& c : centres )
2377 BOOST_TEST_MESSAGE( "hop at " << c.x << "," << c.y );
2378
2379 BOOST_CHECK_MESSAGE( has( origin + VECTOR2I( 0, 2 * pitch ) ), "the new hop did not continue the chain" );
2380 BOOST_CHECK_MESSAGE( !has( origin + VECTOR2I( 2 * pitch, 0 ) ), "the new hop fell back onto the +X axis" );
2381}
2382
2383
2384// An unset fill on the source via means "ask the board", not "filled".
2385BOOST_AUTO_TEST_CASE( CreateFromItemsTakesFillFromTheBoardDefault )
2386{
2387 for( bool boardFills : { false, true } )
2388 {
2389 BOARD board;
2390 board.SetCopperLayerCount( 4 );
2392 board.GetDesignSettings().m_FillVias = boardFills;
2393
2394 // Offset, so this is a staggered stack. A coaxial one is forced filled whatever the
2395 // board says, because something lands on every hop.
2396 VECTOR2I at( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) );
2397 VECTOR2I next( at.x + pcbIUScale.mmToIU( 0.5 ), at.y );
2398
2399 std::vector<BOARD_ITEM*> items;
2400 int hop = 0;
2401
2402 for( auto pair : { std::make_pair( F_Cu, In1_Cu ), std::make_pair( In1_Cu, In2_Cu ) } )
2403 {
2404 PCB_VIA* via = new PCB_VIA( &board );
2405 via->SetViaType( VIATYPE::MICROVIA );
2406 via->SetLayerPair( pair.first, pair.second );
2407 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
2408 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
2409 via->SetPosition( hop++ == 0 ? at : next );
2410 board.Add( via );
2411 items.push_back( via );
2412 }
2413
2414 // is_filled deliberately left unset on both.
2415 std::unique_ptr<PCB_VIA_STACK> stack( PCB_VIA_STACK::CreateFromItems( items, &board ) );
2416
2417 BOOST_REQUIRE( stack );
2418 BOOST_CHECK_MESSAGE( stack->IsFilled() == boardFills,
2419 "board fill " << boardFills << " gave stack fill " << stack->IsFilled() );
2420 }
2421}
2422
2423
2424// Expansion is for microvias only. A plain via on the same net and span, placed for ordinary
2425// routing, must be left exactly as the user drew it.
2426BOOST_AUTO_TEST_CASE( ExpansionLeavesPlainViasAlone )
2427{
2428 BOARD board;
2429 board.SetCopperLayerCount( 4 );
2431
2432 NETINFO_ITEM* net = new NETINFO_ITEM( &board, wxT( "N" ), 1 );
2433 board.Add( net );
2434
2435 auto addVia = [&]( VIATYPE aType, const VECTOR2I& aPos )
2436 {
2437 PCB_VIA* via = new PCB_VIA( &board );
2438 via->SetViaType( aType );
2439 via->SetLayerPair( F_Cu, In2_Cu );
2440 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.3 ) );
2441 via->SetDrill( pcbIUScale.mmToIU( 0.15 ) );
2442 via->SetPosition( aPos );
2443 via->SetNetCode( net->GetNetCode() );
2444 board.Add( via );
2445 return via;
2446 };
2447
2448 // Same net, same span, same everything except the via type.
2449 KIID throughId = addVia( VIATYPE::THROUGH, VECTOR2I( pcbIUScale.mmToIU( 10 ), 0 ) )->m_Uuid;
2450 KIID buriedId = addVia( VIATYPE::BURIED, VECTOR2I( pcbIUScale.mmToIU( 20 ), 0 ) )->m_Uuid;
2451 KIID microId = addVia( VIATYPE::MICROVIA, VECTOR2I( pcbIUScale.mmToIU( 30 ), 0 ) )->m_Uuid;
2452
2453 VIA_STACK_PRESET preset;
2454 preset.m_Name = wxT( "p" );
2455
2456 // The router's matcher: anything on this net is fair game as far as it is concerned.
2457 auto matcher = [&]( PCB_VIA* aVia ) -> const VIA_STACK_PRESET*
2458 {
2459 return aVia->GetNetCode() == net->GetNetCode() ? &preset : nullptr;
2460 };
2461
2462 BOOST_CHECK_EQUAL( PCB_VIA_STACK::ExpandMultiHopMicrovias( &board, nullptr, matcher ), 1 );
2463
2464 BOOST_REQUIRE_EQUAL( board.Generators().size(), 1u );
2465
2466 // Compare by uuid: the expanded microvia is deleted, so its pointer must not be touched.
2467 std::set<KIID> remaining;
2468
2469 for( PCB_TRACK* track : board.Tracks() )
2470 remaining.insert( track->m_Uuid );
2471
2472 BOOST_CHECK_MESSAGE( remaining.count( throughId ), "a through via was converted" );
2473 BOOST_CHECK_MESSAGE( remaining.count( buriedId ), "a buried via was converted" );
2474 BOOST_CHECK_MESSAGE( !remaining.count( microId ), "the microvia should have been expanded" );
2475}
2476
2477
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
General utilities for PCB file IO for QA programs.
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Container for design settings for a BOARD object.
void UseCustomTrackViaSize(bool aEnabled)
Enables/disables custom track/via size settings.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetCustomTrackWidth(int aWidth)
Sets custom width for track (i.e.
std::vector< VIA_STACK_PRESET > m_ViaStackPresets
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
void SetUuidDirect(const KIID &aUuid)
Raw UUID assignment.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
const GENERATORS & Generators() const
Definition board.h:476
const TRACKS & Tracks() const
Definition board.h:461
void SetCopperLayerCount(int aCount)
Definition board.cpp:1137
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1203
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
An abstract base class whose derivatives may be passed to a GENERAL_COLLECTOR telling it what should ...
Definition collectors.h:50
int GetCount() const
Return the number of objects in the list.
Definition collector.h:79
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:64
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
const KIID m_Uuid
Definition eda_item.h:597
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:160
bool HasFlag(EDA_ITEM_FLAGS aFlag) const
Definition eda_item.h:168
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
void SetPosition(const VECTOR2I &aPos) override
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:203
static const std::vector< KICAD_T > AllBoardItems
A scan list for all editable board items.
Definition collectors.h:38
void Collect(BOARD_ITEM *aItem, const std::vector< KICAD_T > &aScanList, const VECTOR2I &aRefPos, const COLLECTORS_GUIDE &aGuide)
Scan a BOARD_ITEM using this class's Inspector method, which does the collection.
static GENERATORS_MGR & Instance()
void Set(const std::string &aPath, ValueType aVal)
Stores a value into the JSON document Will throw an exception if ValueType isn't something that the l...
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
std::optional< ValueType > Get(const std::string &aPath) const
Fetches a value from within the JSON document.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
Definition kiid.h:46
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
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
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
void SetuViaDiameter(int aSize)
Definition netclass.h:165
void SetuViaDrill(int aSize)
Definition netclass.h:173
Handle the data for a net.
Definition netinfo.h:50
int GetNetCode() const
Definition netinfo.h:104
void SetNetClass(const std::shared_ptr< NETCLASS > &aNetClass)
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
DRILL_PROPS & Drill()
Definition padstack.h:361
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
void SetAttribute(PAD_ATTRIB aAttribute)
Definition pad.cpp:1639
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:196
void SetDrillShape(PAD_DRILL_SHAPE aShape)
Definition pad.h:431
void SetPosition(const VECTOR2I &aPos) override
Definition pad.cpp:235
void SetDrillSize(const VECTOR2I &aSize)
Definition pad.h:317
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.cpp:255
void SetLayerSet(const LSET &aLayers) override
Definition pad.cpp:1955
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
void SetPosition(const VECTOR2I &aPos) override
VECTOR2I GetPosition() const override
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
static EDA_GROUP * TopLevelGroup(BOARD_ITEM *aItem, EDA_GROUP *aScope, bool isFootprintEditor)
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
PCB_GROUP * DeepDuplicate(bool addToParentGroup, BOARD_COMMIT *aCommit=nullptr, std::map< KIID, KIID > *aKIIDMap=nullptr) const
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
A microvia stack: several single hop microvias, plus connecting traces for staggered stacks,...
void SetHops(const SHAPE_LINE_CHAIN &aHops)
bool Update(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void ApplyPreset(const VIA_STACK_PRESET &aPreset)
static int ExpandMultiHopMicrovias(BOARD *aBoard, BOARD_COMMIT *aCommit, const std::function< const VIA_STACK_PRESET *(PCB_VIA *)> &aMatcher=nullptr)
Replace loose microvias spanning more than one hop with stacks of single hop microvias.
void SetProperties(const STRING_ANY_MAP &aProps) override
void SetPitch(int aPitch)
static PCB_VIA_STACK * CreateFromItems(const std::vector< BOARD_ITEM * > &aItems, BOARD *aBoard, std::vector< BOARD_ITEM * > *aMembers=nullptr)
Build a stack from existing board items (microvias plus optional connecting traces).
static std::set< KIID > CollectExpandableMicrovias(BOARD *aBoard)
Ids of the vias ExpandMultiHopMicrovias would consider right now.
void EditStart(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
void Mirror(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Mirror this object relative to a given horizontal axis the layer is not changed.
static int FindNetAtPosition(BOARD *aBoard, const VECTOR2I &aPosition, PCB_LAYER_ID aLayer)
Net of the copper under a point on the given layer, pads first, then tracks, then filled zones.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
void EditFinish(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
void Remove(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
bool IsCapped() const
void Regenerate(BOARD *aBoard, BOARD_COMMIT *aCommit)
Rebuild the member vias (and, for a staggered stack, the connecting traces) from the current stack se...
int GetViaDrill() const
bool IsFilled() const
const std::optional< SHAPE_LINE_CHAIN > & GetHops() const
const BOX2I ViewBBox() const override
Return the bounding box of the item covering all its layers.
VIA_STACK_STYLE GetStyle() const
std::vector< int > ViewGetLayers() const override
void SetFilled(bool aFilled)
void SetPresetName(const wxString &aName)
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
const wxString & GetPresetName() const
int GetNetCode() const
PCB_LAYER_ID GetStartLayer() const
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
void SetStartLayer(PCB_LAYER_ID aLayer)
PCB_LAYER_ID GetEndLayer() const
bool GetUseNetclass() const
static bool IsSpanValid(BOARD *aBoard, PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd)
True when both span ends are copper layers within the board's copper layer count.
void SetViaSize(int aSize)
void SetCapped(bool aCapped)
void SetViaDrill(int aDrill)
bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const override
Test if aPosition is inside or on the boundary of this item.
int GetPitch() const
const STRING_ANY_MAP GetProperties() const override
void SetUseNetclass(bool aUse)
void SetStyle(VIA_STACK_STYLE aStyle)
int GetViaSize() const
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void SetEndLayer(PCB_LAYER_ID aLayer)
void SetNetCode(int aNet)
PCB_LAYER_ID BottomLayer() const
const PADSTACK & Padstack() const
Definition pcb_track.h:418
int GetWidth() const override
PCB_LAYER_ID TopLayer() const
int GetDrillValue() const
Calculate the drill value for vias (m_drill if > 0, or default drill value for the board).
VIATYPE GetViaType() const
Definition pcb_track.h:410
virtual bool Writeable(INSPECTABLE *aObject) const
Definition property.h:288
Provide class metadata.Helper macro to map type hashes to names.
static PROPERTY_MANAGER & Instance()
void Rebuild()
Rebuild the list of all registered properties.
PROPERTY_BASE * GetProperty(TYPE_ID aType, const wxString &aProperty) const
Return a property for a specific type.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
A name/value tuple with unique names and wxAny values.
void set(const std::string &aKey, const T &aVar)
Minimal COLLECTORS_GUIDE so GetCoverageArea() can be exercised headlessly.
bool IgnoreMicroVias() const override
bool IgnoreThroughHolePads() const override
bool IgnoreBlindBuriedVias() const override
bool IgnorePadsOnFront() const override
bool IgnoreFootprintsOnFront() const override
bool IgnoreFPReferences() const override
bool IgnoreTracks() const override
bool IgnoreFPTextOnFront() const override
bool IgnoreNoNets() const override
bool IsLayerVisible(PCB_LAYER_ID) const override
bool IgnoreFootprintsOnBack() const override
PCB_LAYER_ID GetPreferredLayer() const override
bool IgnoreZoneFills() const override
bool IncludeSecondary() const override
Determine if the secondary criteria or 2nd choice items should be included.
bool IgnoreThroughVias() const override
bool IgnoreFPValues() const override
double OnePixelInIU() const override
bool IgnorePadsOnBack() const override
bool IgnoreLockedItems() const override
bool IgnoreFPTextOnBack() const override
Master controller class:
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
@ DEGREES_T
Definition eda_angle.h:31
#define IGNORE_PARENT_GROUP
Definition eda_item.h:55
#define IS_NEW
New item, just created.
#define IN_EDIT
Item currently edited.
static PCB_TRACK * makeSegment(BOARD *aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:714
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ In4_Cu
Definition layer_ids.h:65
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
@ In6_Cu
Definition layer_ids.h:67
@ In3_Cu
Definition layer_ids.h:64
@ F_Cu
Definition layer_ids.h:60
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
void DumpBoardToFile(BOARD &board, const std::filesystem::path &aFilename)
Utility function to simply write a Board out to a file.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
Class to handle a set of BOARD_ITEMs.
VIATYPE
#define TYPE_HASH(x)
Definition property.h:74
CITER next(CITER it)
Definition ptree.cpp:120
PCB_LAYER_ID ViaStackTargetLayer(PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd, PCB_LAYER_ID aCurrent)
Layer a microvia stack drop lands on when invoked on aCurrent.
const VIA_STACK_PRESET * MatchPendingStackExpansion(PCB_VIA *aVia, const std::set< KIID > &aPreRoute, const std::vector< PENDING_STACK_EXPANSION > &aPending)
Preset a route's stacked drop wants for aVia, or nullptr when the via is not one of them.
std::optional< bool > is_capped
True if the drill hole should be capped.
Definition padstack.h:279
A named microvia stack definition, chosen while routing instead of entering the values each time.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
KIBIS top(path, &reporter)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(FactoryRegistered)
static PCB_VIA * makeMicrovia(BOARD *aBoard, const VECTOR2I &aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, int aNet=0)
static std::vector< VECTOR2I > looseMicroviaPositions(BOARD *aBoard)
static PCB_TRACK * makeSegment(BOARD *aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
static const VECTOR2I ROUTED_POS(pcbIUScale.mmToIU(10), pcbIUScale.mmToIU(10))
static void makeSharedNetMultiHopPair(BOARD *aBoard)
static const VECTOR2I PRE_ROUTE_POS(pcbIUScale.mmToIU(50), pcbIUScale.mmToIU(50))
int delta
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683