KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_cam_backdrill.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 3
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 <boost/test/unit_test.hpp>
21
22#include <board.h>
23#include <footprint.h>
24#include <netinfo.h>
25#include <pad.h>
26#include <pcb_shape.h>
30#include <pcbnew/pcb_track.h>
33#include <base_units.h>
34
35#include <map>
36#include <memory>
37
38#include <core/utf8.h>
39
40#include <wx/dir.h>
41#include <wx/ffile.h>
42#include <wx/filename.h>
43#include <wx/tokenzr.h>
44#include <wx/utils.h>
45
46
47namespace
48{
49wxFileName MakeTempDir()
50{
51 wxFileName tempDir( wxFileName::GetTempDir(), wxEmptyString );
52 tempDir.AppendDir( wxString::Format( "kicad-backdrill-%llu", static_cast<unsigned long long>( wxGetUTCTime() ) ) );
53 BOOST_REQUIRE( tempDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
54
55 return tempDir;
56}
57} // anonymous namespace
58
59
60BOOST_AUTO_TEST_CASE( BackdrillCamOutputs )
61{
62 wxFileName tempDir = MakeTempDir();
63 wxFileName boardFile( tempDir.GetFullPath(), wxT( "backdrill_board.kicad_pcb" ) );
64
65 BOARD board;
66 board.SetCopperLayerCount( 6 );
67 board.SetFileName( boardFile.GetFullPath() );
68
69 PCB_VIA* via = new PCB_VIA( &board );
70 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
71 via->SetPosition( VECTOR2I( 0, 0 ) );
72 via->SetLayerPair( F_Cu, B_Cu );
73 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
74 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
75 via->SetSecondaryDrillSize( pcbIUScale.mmToIU( 0.20 ) );
76 via->SetSecondaryDrillStartLayer( F_Cu );
77 via->SetSecondaryDrillEndLayer( In3_Cu );
78 via->SetFrontPostMachiningMode( PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK );
79 via->SetFrontPostMachiningSize( pcbIUScale.mmToIU( 0.60 ) );
80 via->SetFrontPostMachiningDepth( pcbIUScale.mmToIU( 0.15 ) );
81 via->SetFrontPostMachiningAngle( 900 );
82 board.Add( via );
83
84 EXCELLON_WRITER excellon( &board );
85 excellon.SetOptions( false, false, VECTOR2I( 0, 0 ), false );
86 excellon.SetFormat( true );
87 BOOST_REQUIRE( excellon.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false, nullptr ) );
88
89 wxFileName excellonFile( tempDir.GetFullPath(), wxT( "backdrill_board_Backdrills_Drill_1_4.drl" ) );
90 BOOST_REQUIRE( excellonFile.FileExists() );
91
92 wxFFile excellonStream( excellonFile.GetFullPath(), wxT( "rb" ) );
93 wxString excellonContents;
94 BOOST_REQUIRE( excellonStream.ReadAll( &excellonContents ) );
95 BOOST_CHECK( excellonContents.Contains( wxT( "TF.FileFunction,NonPlated,1,4,Blind" ) ) );
96 BOOST_CHECK( excellonContents.Contains( wxT( "; Backdrill" ) ) );
97 BOOST_CHECK( excellonContents.Contains( wxT( "post-machining" ) ) );
98
99 wxFileName layerPairFile( tempDir.GetFullPath(), wxT( "backdrill_board-front-in3-backdrill.drl" ) );
100 BOOST_REQUIRE( layerPairFile.FileExists() );
101
102 wxFFile layerPairStream( layerPairFile.GetFullPath(), wxT( "rb" ) );
103 wxString layerPairContents;
104 BOOST_REQUIRE( layerPairStream.ReadAll( &layerPairContents ) );
105 BOOST_CHECK( layerPairContents.Contains( wxT( "; backdrill" ) ) );
106
107 wxFileName pthFile( tempDir.GetFullPath(), wxT( "backdrill_board-PTH.drl" ) );
108 BOOST_REQUIRE( pthFile.FileExists() );
109
110 wxFFile pthStream( pthFile.GetFullPath(), wxT( "rb" ) );
111 wxString pthContents;
112 BOOST_REQUIRE( pthStream.ReadAll( &pthContents ) );
113 BOOST_CHECK( pthContents.Contains( wxT( "; Post-machining front countersink dia 0.600mm depth 0.150mm angle 90deg" ) ) );
114
115 GERBER_WRITER gerber( &board );
116 gerber.SetOptions( VECTOR2I( 0, 0 ) );
117 gerber.SetFormat( 6 );
118 BOOST_REQUIRE( gerber.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false, false, nullptr ) );
119
120 wxFileName gerberFile( tempDir.GetFullPath(), wxT( "backdrill_board_Backdrills_Drill_1_4-drl.gbr" ) );
121 BOOST_REQUIRE( gerberFile.FileExists() );
122
123 wxFFile gerberStream( gerberFile.GetFullPath(), wxT( "rb" ) );
124 wxString gerberContents;
125 BOOST_REQUIRE( gerberStream.ReadAll( &gerberContents ) );
126 BOOST_CHECK( gerberContents.Contains( wxT( "%TA.AperFunction,BackDrill*%" ) ) );
127 BOOST_CHECK( gerberContents.Contains( wxT( "%TF.FileFunction,NonPlated,1,4,Blind,Drill*%" ) ) );
128
129 wxFileName gerberLayerPairFile( tempDir.GetFullPath(),
130 wxT( "backdrill_board-front-in3-backdrill-drl.gbr" ) );
131 BOOST_REQUIRE( gerberLayerPairFile.FileExists() );
132
133 wxFFile gerberLayerPairStream( gerberLayerPairFile.GetFullPath(), wxT( "rb" ) );
134 wxString gerberLayerPairContents;
135 BOOST_REQUIRE( gerberLayerPairStream.ReadAll( &gerberLayerPairContents ) );
136 BOOST_CHECK( gerberLayerPairContents.Contains( wxT( "%TF.FileFunction,NonPlated,1,4,Blind,Drill*%" ) ) );
137 BOOST_CHECK( gerberLayerPairContents.Contains( wxT( "%TA.AperFunction,BackDrill*%" ) ) );
138
139 wxFileName odbRoot( tempDir.GetFullPath(), wxEmptyString );
140 odbRoot.AppendDir( wxT( "odb_out" ) );
141 BOOST_REQUIRE( odbRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
142
143 PCB_IO_ODBPP odbExporter;
144 std::map<std::string, UTF8> props;
145 props["units"] = "mm";
146 props["sigfig"] = "4";
147 BOOST_REQUIRE_NO_THROW( odbExporter.SaveBoard( odbRoot.GetFullPath(), board, &props ) );
148
149 wxFileName drill1Dir( odbRoot.GetFullPath(), wxEmptyString );
150 drill1Dir.AppendDir( wxT( "steps" ) );
151 drill1Dir.AppendDir( wxT( "pcb" ) );
152 drill1Dir.AppendDir( wxT( "layers" ) );
153 drill1Dir.AppendDir( wxT( "drill1" ) );
154 BOOST_REQUIRE( drill1Dir.DirExists() );
155
156 wxFileName toolsFile( drill1Dir.GetFullPath(), wxT( "tools" ) );
157 BOOST_REQUIRE( toolsFile.FileExists() );
158
159 wxFFile toolsStream( toolsFile.GetFullPath(), wxT( "rb" ) );
160 wxString toolsContents;
161 BOOST_REQUIRE( toolsStream.ReadAll( &toolsContents ) );
162 BOOST_CHECK( toolsContents.Contains( wxT( "TYPE=NON_PLATED" ) ) );
163 BOOST_CHECK( toolsContents.Contains( wxT( "TYPE2=BLIND" ) ) );
164
165 wxFileName matrixFile( odbRoot.GetFullPath(), wxEmptyString );
166 matrixFile.AppendDir( wxT( "matrix" ) );
167 matrixFile.SetFullName( wxT( "matrix" ) );
168 BOOST_REQUIRE( matrixFile.FileExists() );
169
170 wxFFile matrixStream( matrixFile.GetFullPath(), wxT( "rb" ) );
171 wxString matrixContents;
172 BOOST_REQUIRE( matrixStream.ReadAll( &matrixContents ) );
173 BOOST_CHECK( matrixContents.Contains( wxT( "ADD_TYPE=BACKDRILL" ) ) );
174
175 matrixStream.Close();
176 toolsStream.Close();
177 gerberStream.Close();
178 gerberLayerPairStream.Close();
179 excellonStream.Close();
180 layerPairStream.Close();
181 pthStream.Close();
182
183 wxFileName::Rmdir( odbRoot.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
184 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
185}
186
187
188// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23914
189// Only front-side (secondary) backdrill holes were exported; back-side (tertiary)
190// backdrill operations never produced a drill file.
191BOOST_AUTO_TEST_CASE( FrontAndBackBackdrillCamOutputs )
192{
193 wxFileName tempDir = MakeTempDir();
194 wxFileName boardFile( tempDir.GetFullPath(), wxT( "backdrill_pair_board.kicad_pcb" ) );
195
196 BOARD board;
197 board.SetCopperLayerCount( 6 );
198 board.SetFileName( boardFile.GetFullPath() );
199
200 PCB_VIA* topVia = new PCB_VIA( &board );
202 topVia->SetPosition( VECTOR2I( 0, 0 ) );
203 topVia->SetLayerPair( F_Cu, B_Cu );
204 topVia->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
205 topVia->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
206 topVia->SetSecondaryDrillSize( pcbIUScale.mmToIU( 0.40 ) );
209 board.Add( topVia );
210
211 PCB_VIA* bottomVia = new PCB_VIA( &board );
213 bottomVia->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 5.0 ), 0 ) );
214 bottomVia->SetLayerPair( F_Cu, B_Cu );
215 bottomVia->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
216 bottomVia->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
217 bottomVia->SetTertiaryDrillSize( pcbIUScale.mmToIU( 0.40 ) );
218 bottomVia->SetTertiaryDrillStartLayer( B_Cu );
219 bottomVia->SetTertiaryDrillEndLayer( In3_Cu );
220 board.Add( bottomVia );
221
222 EXCELLON_WRITER excellon( &board );
223 excellon.SetOptions( false, false, VECTOR2I( 0, 0 ), false );
224 excellon.SetFormat( true );
225 BOOST_REQUIRE( excellon.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false, nullptr ) );
226
227 wxFileName topBackdrillFile( tempDir.GetFullPath(),
228 wxT( "backdrill_pair_board_Backdrills_Drill_1_2.drl" ) );
229 BOOST_CHECK_MESSAGE( topBackdrillFile.FileExists(),
230 "Front-side backdrill drill file should be produced" );
231
232 // Start=B_Cu (UI index 6) drilled toward In3_Cu (UI index 4) in a 6-layer board
233 wxFileName bottomBackdrillFile( tempDir.GetFullPath(),
234 wxT( "backdrill_pair_board_Backdrills_Drill_6_4.drl" ) );
235 BOOST_CHECK_MESSAGE( bottomBackdrillFile.FileExists(),
236 "Back-side (tertiary) backdrill drill file should be produced" );
237
238 if( bottomBackdrillFile.FileExists() )
239 {
240 wxFFile stream( bottomBackdrillFile.GetFullPath(), wxT( "rb" ) );
241 wxString contents;
242 BOOST_REQUIRE( stream.ReadAll( &contents ) );
243 BOOST_CHECK( contents.Contains( wxT( "; Backdrill" ) ) );
244 stream.Close();
245 }
246
247 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
248}
249
250
251// Stronger coverage for https://gitlab.com/kicad/code/kicad/-/issues/23914
252// A single via can carry both a front-side (secondary) and a back-side
253// (tertiary) backdrill. Both drill files must be produced.
254BOOST_AUTO_TEST_CASE( DualBackdrillSameViaCamOutputs )
255{
256 wxFileName tempDir = MakeTempDir();
257 wxFileName boardFile( tempDir.GetFullPath(), wxT( "dual_backdrill_board.kicad_pcb" ) );
258
259 BOARD board;
260 board.SetCopperLayerCount( 6 );
261 board.SetFileName( boardFile.GetFullPath() );
262
263 PCB_VIA* via = new PCB_VIA( &board );
264 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
265 via->SetPosition( VECTOR2I( 0, 0 ) );
266 via->SetLayerPair( F_Cu, B_Cu );
267 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
268 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
269 via->SetSecondaryDrillSize( pcbIUScale.mmToIU( 0.40 ) );
270 via->SetSecondaryDrillStartLayer( F_Cu );
271 via->SetSecondaryDrillEndLayer( In1_Cu );
272 via->SetTertiaryDrillSize( pcbIUScale.mmToIU( 0.40 ) );
273 via->SetTertiaryDrillStartLayer( B_Cu );
274 via->SetTertiaryDrillEndLayer( In3_Cu );
275 board.Add( via );
276
277 EXCELLON_WRITER excellon( &board );
278 excellon.SetOptions( false, false, VECTOR2I( 0, 0 ), false );
279 excellon.SetFormat( true );
280 BOOST_REQUIRE( excellon.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false,
281 nullptr ) );
282
283 wxFileName topBackdrillFile( tempDir.GetFullPath(),
284 wxT( "dual_backdrill_board_Backdrills_Drill_1_2.drl" ) );
285 BOOST_CHECK( topBackdrillFile.FileExists() );
286
287 wxFileName bottomBackdrillFile( tempDir.GetFullPath(),
288 wxT( "dual_backdrill_board_Backdrills_Drill_6_4.drl" ) );
289 BOOST_CHECK( bottomBackdrillFile.FileExists() );
290
291 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
292}
293
294
295// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23451
296// GERBER_WRITER::SetFormat() precision was not passed to the plotter; output always used 4.6.
297BOOST_AUTO_TEST_CASE( GerberDrillPrecision )
298{
299 wxFileName tempDir = MakeTempDir();
300 wxFileName boardFile( tempDir.GetFullPath(), wxT( "precision_board.kicad_pcb" ) );
301
302 BOARD board;
303 board.SetCopperLayerCount( 2 );
304 board.SetFileName( boardFile.GetFullPath() );
305
306 PCB_VIA* via = new PCB_VIA( &board );
307 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
308 via->SetPosition( VECTOR2I( 0, 0 ) );
309 via->SetLayerPair( F_Cu, B_Cu );
310 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
311 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
312 board.Add( via );
313
314 // Verify precision 5 produces "Fmt 4.5" in the file header
315 GERBER_WRITER gerber5( &board );
316 gerber5.SetOptions( VECTOR2I( 0, 0 ) );
317 gerber5.SetFormat( 5 );
318 BOOST_REQUIRE( gerber5.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false, false, nullptr ) );
319
320 wxFileName gerberFile5( tempDir.GetFullPath(), wxT( "precision_board-PTH-drl.gbr" ) );
321 BOOST_REQUIRE( gerberFile5.FileExists() );
322
323 wxFFile gerberStream5( gerberFile5.GetFullPath(), wxT( "rb" ) );
324 wxString gerberContents5;
325 BOOST_REQUIRE( gerberStream5.ReadAll( &gerberContents5 ) );
326 BOOST_CHECK_MESSAGE( gerberContents5.Contains( wxT( "Fmt 4.5" ) ),
327 "Expected 'Fmt 4.5' in gerber header with precision=5" );
328 BOOST_CHECK( !gerberContents5.Contains( wxT( "Fmt 4.6" ) ) );
329 gerberStream5.Close();
330
331 // Verify precision 6 produces "Fmt 4.6" in the file header
332 GERBER_WRITER gerber6( &board );
333 gerber6.SetOptions( VECTOR2I( 0, 0 ) );
334 gerber6.SetFormat( 6 );
335 BOOST_REQUIRE( gerber6.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false, false, nullptr ) );
336
337 wxFFile gerberStream6( gerberFile5.GetFullPath(), wxT( "rb" ) );
338 wxString gerberContents6;
339 BOOST_REQUIRE( gerberStream6.ReadAll( &gerberContents6 ) );
340 BOOST_CHECK_MESSAGE( gerberContents6.Contains( wxT( "Fmt 4.6" ) ),
341 "Expected 'Fmt 4.6' in gerber header with precision=6" );
342 BOOST_CHECK( !gerberContents6.Contains( wxT( "Fmt 4.5" ) ) );
343 gerberStream6.Close();
344
345 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
346}
347
348
349// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23452
350// Drill files for vias spanning inner-to-back or back-to-inner layers produced
351// reversed layer order in the generated file name (e.g. "back-in2" instead of "in2-back").
352BOOST_AUTO_TEST_CASE( DrillFileLayerOrderInFilename )
353{
354 wxFileName tempDir = MakeTempDir();
355 wxFileName boardFile( tempDir.GetFullPath(), wxT( "layer_order_board.kicad_pcb" ) );
356
357 BOARD board;
358 board.SetCopperLayerCount( 4 );
359 board.SetFileName( boardFile.GetFullPath() );
360
361 // Via spanning In2 (bottom inner) to B_Cu: file must be named "in2-back", not "back-in2".
362 PCB_VIA* via = new PCB_VIA( &board );
363 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
364 via->SetPosition( VECTOR2I( 0, 0 ) );
365 via->SetViaType( VIATYPE::BURIED );
366 via->SetLayerPair( In2_Cu, B_Cu );
367 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
368 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
369 board.Add( via );
370
371 EXCELLON_WRITER excellon( &board );
372 excellon.SetOptions( false, false, VECTOR2I( 0, 0 ), false );
373 excellon.SetFormat( true );
374 BOOST_REQUIRE( excellon.CreateDrillandMapFilesSet( tempDir.GetFullPath(), true, false, nullptr ) );
375
376 // Correct order: top inner layer first, then back layer.
377 wxFileName correctFile( tempDir.GetFullPath(), wxT( "layer_order_board-in2-back.drl" ) );
378 wxFileName reversedFile( tempDir.GetFullPath(), wxT( "layer_order_board-back-in2.drl" ) );
379 BOOST_CHECK_MESSAGE( correctFile.FileExists(), "Expected drill file 'in2-back' not found" );
380 BOOST_CHECK_MESSAGE( !reversedFile.FileExists(), "Incorrectly named drill file 'back-in2' found" );
381
382 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
383}
384
385
386// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23005
387// GenDrillReportFile crashed when aReporter was null (the default)
388BOOST_AUTO_TEST_CASE( DrillReportNullReporter )
389{
390 wxFileName tempDir = MakeTempDir();
391 wxFileName boardFile( tempDir.GetFullPath(), wxT( "test_board.kicad_pcb" ) );
392
393 BOARD board;
394 board.SetCopperLayerCount( 2 );
395 board.SetFileName( boardFile.GetFullPath() );
396
397 wxFileName reportFile( tempDir.GetFullPath(), wxT( "test_board-drl.rpt" ) );
398
399 // Valid path with null reporter should succeed without crashing
400 EXCELLON_WRITER excellon( &board );
401 BOOST_CHECK( excellon.GenDrillReportFile( reportFile.GetFullPath() ) );
402 BOOST_CHECK( reportFile.FileExists() );
403
404 GERBER_WRITER gerber( &board );
405 BOOST_CHECK( gerber.GenDrillReportFile( reportFile.GetFullPath() ) );
406
407 // Invalid path with null reporter should return false without crashing
408 EXCELLON_WRITER excellon2( &board );
409 BOOST_CHECK( !excellon2.GenDrillReportFile( wxT( "/nonexistent/path/report.rpt" ) ) );
410
411 GERBER_WRITER gerber2( &board );
412 BOOST_CHECK( !gerber2.GenDrillReportFile( wxT( "/nonexistent/path/report.rpt" ) ) );
413
414 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
415}
416
417
418// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23289
419// GenDrillReportFile crashed with SIGSEGV when the board had drills because
420// printToolSummary() passed integer literal 0 instead of the FILE* to fmt::print()
421BOOST_AUTO_TEST_CASE( DrillReportWithTools )
422{
423 wxFileName tempDir = MakeTempDir();
424 wxFileName boardFile( tempDir.GetFullPath(), wxT( "test_board_with_drills.kicad_pcb" ) );
425
426 BOARD board;
427 board.SetCopperLayerCount( 2 );
428 board.SetFileName( boardFile.GetFullPath() );
429
430 PCB_VIA* via = new PCB_VIA( &board );
431 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
432 via->SetPosition( VECTOR2I( 0, 0 ) );
433 via->SetLayerPair( F_Cu, B_Cu );
434 via->SetDrill( pcbIUScale.mmToIU( 0.30 ) );
435 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.60 ) );
436 board.Add( via );
437
438 wxFileName reportFile( tempDir.GetFullPath(), wxT( "test_board_with_drills-drl.rpt" ) );
439
440 EXCELLON_WRITER excellon( &board );
441 excellon.SetOptions( false, false, VECTOR2I( 0, 0 ), false );
442 excellon.SetFormat( true );
443 BOOST_CHECK_NO_THROW( excellon.GenDrillReportFile( reportFile.GetFullPath() ) );
444 BOOST_CHECK( reportFile.FileExists() );
445
446 wxFFile reportStream( reportFile.GetFullPath(), wxT( "rb" ) );
447 wxString reportContents;
448 BOOST_REQUIRE( reportStream.ReadAll( &reportContents ) );
449 BOOST_CHECK( reportContents.Contains( wxT( "T1" ) ) );
450 BOOST_CHECK( reportContents.Contains( wxT( "0.300mm" ) ) );
451 reportStream.Close();
452
453 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
454}
455
456
457// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24014
458// A non-filled PCB_SHAPE rectangle on F.SilkS was emitted as a donut_rc symbol with a
459// corner radius smaller than half the line width. Many ODB++ viewers reject the
460// resulting degenerate symbol, so the outline appeared to be missing. The exporter now
461// emits the four outline edges as line features, matching how a rectangle built from
462// individual line segments is exported.
463BOOST_AUTO_TEST_CASE( OdbPpUnfilledRectangleOnSilk )
464{
465 wxFileName tempDir = MakeTempDir();
466 wxFileName boardFile( tempDir.GetFullPath(), wxT( "silk_rect_board.kicad_pcb" ) );
467
468 BOARD board;
469 board.SetCopperLayerCount( 2 );
470 board.SetFileName( boardFile.GetFullPath() );
471
472 // Non-filled rectangle on F.SilkS
473 PCB_SHAPE* rect = new PCB_SHAPE( &board, SHAPE_T::RECTANGLE );
474 rect->SetStart( VECTOR2I( pcbIUScale.mmToIU( 10.0 ), pcbIUScale.mmToIU( 10.0 ) ) );
475 rect->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 30.0 ), pcbIUScale.mmToIU( 20.0 ) ) );
476 rect->SetLayer( F_SilkS );
477 rect->SetFilled( false );
478 rect->SetWidth( pcbIUScale.mmToIU( 0.15 ) );
479 board.Add( rect );
480
481 wxFileName odbRoot( tempDir.GetFullPath(), wxEmptyString );
482 odbRoot.AppendDir( wxT( "odb_out" ) );
483 BOOST_REQUIRE( odbRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
484
485 PCB_IO_ODBPP odbExporter;
486 std::map<std::string, UTF8> props;
487 props["units"] = "mm";
488 props["sigfig"] = "4";
489 BOOST_REQUIRE_NO_THROW( odbExporter.SaveBoard( odbRoot.GetFullPath(), board, &props ) );
490
491 wxFileName silkFeatures( odbRoot.GetFullPath(), wxT( "features" ) );
492 silkFeatures.AppendDir( wxT( "steps" ) );
493 silkFeatures.AppendDir( wxT( "pcb" ) );
494 silkFeatures.AppendDir( wxT( "layers" ) );
495 silkFeatures.AppendDir( wxT( "f.silkscreen" ) );
496 BOOST_REQUIRE( silkFeatures.FileExists() );
497
498 wxFFile silkStream( silkFeatures.GetFullPath(), wxT( "rb" ) );
499 wxString silkContents;
500 BOOST_REQUIRE( silkStream.ReadAll( &silkContents ) );
501 silkStream.Close();
502
503 // Four ODB++ line ("L ...") features describe the rectangle outline.
504 int lineCount = 0;
505 wxStringTokenizer lines( silkContents, wxT( "\n" ) );
506
507 while( lines.HasMoreTokens() )
508 {
509 if( lines.GetNextToken().StartsWith( wxT( "L " ) ) )
510 lineCount++;
511 }
512
513 BOOST_CHECK_EQUAL( lineCount, 4 );
514
515 // The degenerate donut_rc symbol should not appear anymore.
516 BOOST_CHECK( !silkContents.Contains( wxT( "donut_rc" ) ) );
517
518 wxFileName::Rmdir( odbRoot.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
519 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
520}
521
522
523BOOST_AUTO_TEST_CASE( OdbPpDegenerateTrackArc )
524{
525 wxFileName tempDir = MakeTempDir();
526
527 BOARD board;
528 board.SetCopperLayerCount( 2 );
529
530 NETINFO_ITEM* net = new NETINFO_ITEM( &board, wxT( "TestNet" ), 1 );
531 board.Add( net );
532
533 PCB_ARC* arc = new PCB_ARC( &board );
534 arc->SetStart( VECTOR2I( 110737101, 51206997 ) );
535 arc->SetMid( VECTOR2I( 110737003, 51206898 ) );
536 arc->SetEnd( VECTOR2I( 110736905, 51206799 ) );
537 arc->SetWidth( pcbIUScale.mmToIU( 0.11684 ) );
538 arc->SetLayer( F_Cu );
539 arc->SetNet( net );
540
541 board.Add( arc );
542
543 wxFileName odbRoot( tempDir.GetFullPath(), wxEmptyString );
544 odbRoot.AppendDir( wxT( "odb_out" ) );
545 BOOST_REQUIRE( odbRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
546
547 PCB_IO_ODBPP odbExporter;
548 std::map<std::string, UTF8> props;
549 props["units"] = "mm";
550 props["sigfig"] = "4";
551 BOOST_REQUIRE_NO_THROW( odbExporter.SaveBoard( odbRoot.GetFullPath(), board, &props ) );
552
553 wxFileName copperFeatures( odbRoot.GetFullPath(), wxT( "features" ) );
554 copperFeatures.AppendDir( wxT( "steps" ) );
555 copperFeatures.AppendDir( wxT( "pcb" ) );
556 copperFeatures.AppendDir( wxT( "layers" ) );
557 copperFeatures.AppendDir( wxT( "f.cu" ) );
558 BOOST_REQUIRE( copperFeatures.FileExists() );
559
560 wxFFile copperStream( copperFeatures.GetFullPath(), wxT( "rb" ) );
561 wxString copperContents;
562 BOOST_REQUIRE( copperStream.ReadAll( &copperContents ) );
563 copperStream.Close();
564
565 int lineCount = 0;
566 int arcCount = 0;
567 wxStringTokenizer lines( copperContents, wxT( "\n" ) );
568
569 while( lines.HasMoreTokens() )
570 {
571 wxString line = lines.GetNextToken();
572
573 if( line.StartsWith( wxT( "L " ) ) )
574 lineCount++;
575 else if( line.StartsWith( wxT( "A " ) ) )
576 arcCount++;
577 }
578
579 BOOST_CHECK_EQUAL( lineCount, 1 );
580 BOOST_CHECK_EQUAL( arcCount, 0 );
581
582 wxFileName::Rmdir( odbRoot.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
583 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
584}
585
586
587// Export aBoard to a fresh ODB++ tree below aTempDir and return the root of that tree. Symbol
588// dimensions come out in micrometres because the file units are millimetres.
589static wxFileName ExportOdbTree( BOARD* aBoard, const wxFileName& aTempDir )
590{
591 wxFileName odbRoot( aTempDir.GetFullPath(), wxEmptyString );
592 odbRoot.AppendDir( wxT( "odb_out" ) );
593 BOOST_REQUIRE( odbRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
594
595 PCB_IO_ODBPP odbExporter;
596 std::map<std::string, UTF8> props;
597 props["units"] = "mm";
598 props["sigfig"] = "4";
599 BOOST_REQUIRE_NO_THROW( odbExporter.SaveBoard( odbRoot.GetFullPath(), *aBoard, &props ) );
600
601 return odbRoot;
602}
603
604
605static wxString ReadOdbLayerFeatures( const wxFileName& aOdbRoot, const wxString& aLayerDir )
606{
607 wxFileName features( aOdbRoot.GetFullPath(), wxT( "features" ) );
608 features.AppendDir( wxT( "steps" ) );
609 features.AppendDir( wxT( "pcb" ) );
610 features.AppendDir( wxT( "layers" ) );
611 features.AppendDir( aLayerDir );
612 BOOST_REQUIRE( features.FileExists() );
613
614 wxFFile stream( features.GetFullPath(), wxT( "rb" ) );
615 wxString contents;
616 BOOST_REQUIRE( stream.ReadAll( &contents ) );
617 stream.Close();
618
619 return contents;
620}
621
622
623// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/25089
624// The inner diameter of the donut_r symbol standing in for an unfilled circle subtracted only
625// half the line width from the diameter, so the exported annulus was a quarter width too thin
626// and sat off-centre from the circle it came from. The board is the reporter's own project.
627BOOST_AUTO_TEST_CASE( OdbPpUnfilledCircleAnnulus )
628{
629 SETTINGS_MANAGER settingsManager;
630 std::unique_ptr<BOARD> board;
631
632 KI_TEST::LoadBoard( settingsManager, wxT( "issue25089/odb_circles" ), board );
633 BOOST_REQUIRE( board );
634
635 wxFileName tempDir = MakeTempDir();
636 wxFileName odbRoot = ExportOdbTree( board.get(), tempDir );
637
638 // Every circle in the project is 2 mm across with a 0.1 mm stroke, so the ring spans radius
639 // 0.95 mm to 1.05 mm
640 for( const wxString& layerDir : { wxString( wxT( "f.cu" ) ), wxString( wxT( "edge.cuts" ) ) } )
641 {
642 wxString contents = ReadOdbLayerFeatures( odbRoot, layerDir );
643
644 BOOST_CHECK_MESSAGE( contents.Contains( wxT( "donut_r2100.0x1900.0" ) ),
645 "Wrong annulus on " + layerDir + ", features file holds:\n"
646 + contents );
647 }
648
649 wxFileName::Rmdir( odbRoot.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
650 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
651}
652
653
654// Companion to OdbPpUnfilledCircleAnnulus covering the two circles that have no donut_r spelling,
655// derived from the same project. On F.Cu the stroke is as wide as the circle and closes the hole;
656// on B.Cu the radius sits at the EDA_SHAPE clamp of INT_MAX / 2, where the doubled diameter used to
657// overflow a signed int and emit a negative dimension.
658BOOST_AUTO_TEST_CASE( OdbPpUnfilledCircleWithoutHole )
659{
660 SETTINGS_MANAGER settingsManager;
661 std::unique_ptr<BOARD> board;
662
663 KI_TEST::LoadBoard( settingsManager, wxT( "issue25089/odb_circle_edge_cases" ), board );
664 BOOST_REQUIRE( board );
665
666 wxFileName tempDir = MakeTempDir();
667 wxFileName odbRoot = ExportOdbTree( board.get(), tempDir );
668
669 wxString frontContents = ReadOdbLayerFeatures( odbRoot, wxT( "f.cu" ) );
670
671 BOOST_CHECK_MESSAGE( frontContents.Contains( wxT( "r400.0" ) )
672 && !frontContents.Contains( wxT( "donut" ) ),
673 "Circle with a hole-closing stroke should export as a solid pad, "
674 "features file holds:\n"
675 + frontContents );
676
677 // Feature records carry signed Y coordinates, so only the symbol definitions can be checked
678 wxString backContents = ReadOdbLayerFeatures( odbRoot, wxT( "b.cu" ) );
679 wxStringTokenizer backLines( backContents, wxT( "\n" ) );
680
681 while( backLines.HasMoreTokens() )
682 {
683 wxString line = backLines.GetNextToken();
684
685 if( line.StartsWith( wxT( "$" ) ) )
686 {
687 BOOST_CHECK_MESSAGE( !line.Contains( wxT( "-" ) ),
688 "Oversized circle overflowed to a negative symbol dimension: "
689 + line );
690 }
691 }
692
693 wxFileName::Rmdir( odbRoot.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
694 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
695}
696
697
698namespace
699{
700PAD* AddSlotPad( FOOTPRINT* aFootprint, const VECTOR2I& aPos, PAD_ATTRIB aAttribute )
701{
702 PAD* pad = new PAD( aFootprint );
703 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
704 pad->SetAttribute( aAttribute );
705 pad->SetLayerSet( aAttribute == PAD_ATTRIB::NPTH ? PAD::UnplatedHoleMask()
706 : PAD::PTHMask() );
707 pad->SetPosition( aPos );
709 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( pcbIUScale.mmToIU( 2.0 ), pcbIUScale.mmToIU( 1.0 ) ) );
710 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
711 pad->SetDrillSize( VECTOR2I( pcbIUScale.mmToIU( 1.7 ), pcbIUScale.mmToIU( 0.6 ) ) );
712 aFootprint->Add( pad );
713
714 return pad;
715}
716} // anonymous namespace
717
718
719// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24677
720// Plated through-hole pads with a slotted (oval) drill were dropped entirely from the
721// ODB++ drill output. Round holes and non-plated slots exported fine, but plated slots
722// were never written to the plated drill layer. The exporter now emits PTH slots on the
723// plated drill layer just like NPTH slots on the non-plated layer.
724BOOST_AUTO_TEST_CASE( OdbPpPlatedSlotDrill )
725{
726 wxFileName tempDir = MakeTempDir();
727 wxFileName boardFile( tempDir.GetFullPath(), wxT( "plated_slot_board.kicad_pcb" ) );
728
729 BOARD board;
730 board.SetCopperLayerCount( 2 );
731 board.SetFileName( boardFile.GetFullPath() );
732
733 FOOTPRINT* fp = new FOOTPRINT( &board );
734 fp->SetPosition( VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ) );
735 board.Add( fp );
736
737 // A plated slot and a non-plated slot in the same design.
738 AddSlotPad( fp, VECTOR2I( pcbIUScale.mmToIU( 50.0 ), pcbIUScale.mmToIU( 50.0 ) ),
740 AddSlotPad( fp, VECTOR2I( pcbIUScale.mmToIU( 60.0 ), pcbIUScale.mmToIU( 50.0 ) ),
742
743 wxFileName odbRoot( tempDir.GetFullPath(), wxEmptyString );
744 odbRoot.AppendDir( wxT( "odb_out" ) );
745 BOOST_REQUIRE( odbRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
746
747 PCB_IO_ODBPP odbExporter;
748 std::map<std::string, UTF8> props;
749 props["units"] = "mm";
750 props["sigfig"] = "4";
751 BOOST_REQUIRE_NO_THROW( odbExporter.SaveBoard( odbRoot.GetFullPath(), board, &props ) );
752
753 auto layerDir = [&]( const wxString& aLayer )
754 {
755 wxFileName dir( odbRoot.GetFullPath(), wxEmptyString );
756 dir.AppendDir( wxT( "steps" ) );
757 dir.AppendDir( wxT( "pcb" ) );
758 dir.AppendDir( wxT( "layers" ) );
759 dir.AppendDir( aLayer );
760
761 return dir;
762 };
763
764 auto readFile = []( const wxFileName& aFile )
765 {
766 wxFFile stream( aFile.GetFullPath(), wxT( "rb" ) );
767 wxString contents;
768 BOOST_REQUIRE( stream.ReadAll( &contents ) );
769 stream.Close();
770
771 return contents;
772 };
773
774 // ODB++ array members (e.g. drill tools) are indented, so trim before matching.
775 auto countLinesStartingWith = []( const wxString& aContents, const wxString& aPrefix )
776 {
777 int count = 0;
778 wxStringTokenizer lines( aContents, wxT( "\n" ) );
779
780 while( lines.HasMoreTokens() )
781 {
782 wxString line = lines.GetNextToken();
783 line.Trim( false );
784
785 if( line.StartsWith( aPrefix ) )
786 count++;
787 }
788
789 return count;
790 };
791
792 auto containsOvalSymbol = []( const wxString& aContents )
793 {
794 wxStringTokenizer lines( aContents, wxT( "\n" ) );
795
796 while( lines.HasMoreTokens() )
797 {
798 wxString line = lines.GetNextToken();
799
800 // Symbol definition lines look like "$0 oval<w>x<h>".
801 if( line.StartsWith( wxT( "$" ) ) && line.Contains( wxT( "oval" ) ) )
802 return true;
803 }
804
805 return false;
806 };
807
808 // The plated slot must appear on the plated drill layer as exactly one oval pad feature,
809 // and the slot must NOT leak onto this layer as a non-plated hole.
810 wxFileName platedDir = layerDir( wxT( "drill_plated_f.cu-b.cu" ) );
811 BOOST_REQUIRE_MESSAGE( platedDir.DirExists(), "Plated drill layer should exist" );
812
813 wxFileName platedFeatures( platedDir.GetFullPath(), wxT( "features" ) );
814 BOOST_REQUIRE( platedFeatures.FileExists() );
815 wxString platedContents = readFile( platedFeatures );
816
817 BOOST_CHECK_EQUAL( countLinesStartingWith( platedContents, wxT( "P " ) ), 1 );
818 BOOST_CHECK_MESSAGE( containsOvalSymbol( platedContents ),
819 "Plated slot should use an oval symbol" );
820
821 wxFileName platedTools( platedDir.GetFullPath(), wxT( "tools" ) );
822 BOOST_REQUIRE( platedTools.FileExists() );
823 wxString platedToolsContents = readFile( platedTools );
824 BOOST_CHECK_EQUAL( countLinesStartingWith( platedToolsContents, wxT( "TYPE=PLATED" ) ), 1 );
825 BOOST_CHECK_EQUAL( countLinesStartingWith( platedToolsContents, wxT( "TYPE=NON_PLATED" ) ), 0 );
826
827 // The non-plated slot must still appear on the non-plated drill layer (unchanged), and
828 // the plated slot must NOT leak onto it.
829 wxFileName nonPlatedDir = layerDir( wxT( "drill_non-plated_f.cu-b.cu" ) );
830 BOOST_REQUIRE_MESSAGE( nonPlatedDir.DirExists(), "Non-plated drill layer should exist" );
831
832 wxFileName nonPlatedFeatures( nonPlatedDir.GetFullPath(), wxT( "features" ) );
833 BOOST_REQUIRE( nonPlatedFeatures.FileExists() );
834 wxString nonPlatedContents = readFile( nonPlatedFeatures );
835
836 BOOST_CHECK_EQUAL( countLinesStartingWith( nonPlatedContents, wxT( "P " ) ), 1 );
837 BOOST_CHECK_MESSAGE( containsOvalSymbol( nonPlatedContents ),
838 "Non-plated slot should still export an oval symbol" );
839
840 wxFileName nonPlatedTools( nonPlatedDir.GetFullPath(), wxT( "tools" ) );
841 BOOST_REQUIRE( nonPlatedTools.FileExists() );
842 wxString nonPlatedToolsContents = readFile( nonPlatedTools );
843 BOOST_CHECK_EQUAL( countLinesStartingWith( nonPlatedToolsContents, wxT( "TYPE=NON_PLATED" ) ),
844 1 );
845 BOOST_CHECK_EQUAL( countLinesStartingWith( nonPlatedToolsContents, wxT( "TYPE=PLATED" ) ), 0 );
846
847 wxFileName::Rmdir( odbRoot.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
848 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
849}
850
851
852// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/25021
853// Backdrill holes are recorded as non-plated, so the report's backdrill sections asked
854// printToolSummary() for plated tools only and always claimed zero holes, even though
855// the matching Excellon files carried them.
856BOOST_AUTO_TEST_CASE( DrillReportBackdrillHoleCount )
857{
858 SETTINGS_MANAGER settingsManager;
859 std::unique_ptr<BOARD> board;
860
861 KI_TEST::LoadBoard( settingsManager, wxT( "issue25021/backdrill" ), board );
862 BOOST_REQUIRE( board );
863
864 wxFileName tempDir = MakeTempDir();
865 wxFileName boardFile( tempDir.GetFullPath(), wxT( "backdrill.kicad_pcb" ) );
866
867 // Keep the derived drill file names out of the source tree
868 board->SetFileName( boardFile.GetFullPath() );
869
870 wxFileName reportFile( tempDir.GetFullPath(), wxT( "backdrill-drl.rpt" ) );
871
872 EXCELLON_WRITER excellon( board.get() );
873 excellon.SetOptions( false, false, VECTOR2I( 0, 0 ), false );
874 excellon.SetFormat( true );
875 BOOST_REQUIRE( excellon.GenDrillReportFile( reportFile.GetFullPath() ) );
876
877 wxFFile reportStream( reportFile.GetFullPath(), wxT( "rb" ) );
878 wxString reportContents;
879 BOOST_REQUIRE( reportStream.ReadAll( &reportContents ) );
880 reportStream.Close();
881
882 struct BACKDRILL_SECTION
883 {
884 long holes = -1;
885 int toolLines = 0;
886 };
887
888 std::map<wxString, BACKDRILL_SECTION> sections;
889 wxString currentFile;
890 wxStringTokenizer lines( reportContents, wxT( "\n" ) );
891
892 while( lines.HasMoreTokens() )
893 {
894 wxString line = lines.GetNextToken();
895 line.Trim( true ).Trim( false );
896
897 if( line.StartsWith( wxT( "Drill file '" ) ) )
898 currentFile = line.AfterFirst( '\'' ).BeforeFirst( '\'' );
899
900 if( line.StartsWith( wxT( "T" ) ) && line.Contains( wxT( "0.330mm" ) ) )
901 sections[currentFile].toolLines++;
902
903 wxString count;
904
905 if( line.StartsWith( wxT( "Total backdrilled holes count " ), &count ) )
906 {
907 long value = -1;
908 BOOST_REQUIRE( count.ToLong( &value ) );
909 sections[currentFile].holes = value;
910 }
911 }
912
913 // The board backdrills two vias from F.Cu down to In3.Cu, and one via each from
914 // B.Cu up to In3.Cu and to In6.Cu
915 const std::map<wxString, long> expected = {
916 { wxT( "backdrill_Backdrills_Drill_1_4.drl" ), 2 },
917 { wxT( "backdrill_Backdrills_Drill_10_4.drl" ), 1 },
918 { wxT( "backdrill_Backdrills_Drill_10_7.drl" ), 1 }
919 };
920
921 // No backdrill section beyond the three expected ones
922 BOOST_CHECK_EQUAL( sections.size(), expected.size() );
923
924 for( const auto& [file, holes] : expected )
925 {
926 auto section = sections.find( file );
927
928 if( section == sections.end() )
929 {
930 BOOST_ERROR( "No backdrill section for " << file );
931 continue;
932 }
933
934 BOOST_CHECK_EQUAL( section->second.holes, holes );
935
936 // The section must also list the 0.33mm backdrill tool it counted
937 BOOST_CHECK_EQUAL( section->second.toolLines, 1 );
938 }
939
940 // Plated through holes and the unplated summary must be unaffected
941 BOOST_CHECK( reportContents.Contains( wxT( "Total plated holes count 4" ) ) );
942 BOOST_CHECK( reportContents.Contains( wxT( "Total unplated holes count 0" ) ) );
943
944 wxFileName::Rmdir( tempDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
945}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
void SetFileName(const wxString &aFileName)
Definition board.h:450
void SetCopperLayerCount(int aCount)
Definition board.cpp:1137
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:142
Create Excellon drill, drill map, and drill report files.
void SetFormat(bool aMetric, ZEROS_FMT aZerosFmt=DECIMAL_FORMAT, int aLeftDigits=0, int aRightDigits=0)
Initialize internal parameters to match the given format.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board.
void SetOptions(bool aMirror, bool aMinimalHeader, const VECTOR2I &aOffset, bool aMerge_PTH_NPTH)
Initialize internal parameters to match drill options.
void SetPosition(const VECTOR2I &aPos) override
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
bool GenDrillReportFile(const wxString &aFullFileName, REPORTER *aReporter=nullptr)
Create a plain text report file giving a list of drill values and drill count for through holes,...
Used to create Gerber drill files.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, bool aGenTenting, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board filenames are computed from the board name,...
void SetOptions(const VECTOR2I &aOffset)
Initialize internal parameters to match drill options.
void SetFormat(int aRightDigits=6)
Initialize internal parameters to match the given format.
Handle the data for a net.
Definition netinfo.h:50
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
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
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:606
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:627
void SetMid(const VECTOR2I &aMid)
Definition pcb_track.h:286
void SaveBoard(const wxString &aFileName, BOARD &aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
void SetWidth(int aWidth) override
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
void SetSecondaryDrillStartLayer(PCB_LAYER_ID aLayer)
void SetTertiaryDrillEndLayer(PCB_LAYER_ID aLayer)
void SetDrill(int aDrill)
Definition pcb_track.h:771
void SetPadstackMode(PADSTACK::MODE aMode)
Definition pcb_track.h:482
void SetSecondaryDrillEndLayer(PCB_LAYER_ID aLayer)
void SetPosition(const VECTOR2I &aPoint) override
Definition pcb_track.h:581
void SetLayerPair(PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer)
For a via m_layer contains the top layer, the other layer is in m_bottomLayer/.
void SetTertiaryDrillStartLayer(PCB_LAYER_ID aLayer)
void SetTertiaryDrillSize(const VECTOR2I &aSize)
void SetSecondaryDrillSize(const VECTOR2I &aSize)
void SetWidth(int aWidth) override
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
Classes used in drill files, map files and report files generation.
Classes used in drill files, map files and report files generation.
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ F_SilkS
Definition layer_ids.h:96
@ In1_Cu
Definition layer_ids.h:62
@ In3_Cu
Definition layer_ids.h:64
@ F_Cu
Definition layer_ids.h:60
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
PAD_ATTRIB
The set of pad shapes, used with PAD::{Set,Get}Attribute().
Definition padstack.h:96
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ PTH
Plated through hole pad.
Definition padstack.h:97
static bool readFile(const wxString &aFileName, wxString &aOut, size_t aLimit=0)
Read a file into aOut.
static wxString ReadOdbLayerFeatures(const wxFileName &aOdbRoot, const wxString &aLayerDir)
static wxFileName ExportOdbTree(BOARD *aBoard, const wxFileName &aTempDir)
BOOST_AUTO_TEST_CASE(BackdrillCamOutputs)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683