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