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