KiCad PCB EDA Suite
gendrill_Excellon_writer.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 (C) 2018 Jean_Pierre Charras <jp.charras at wanadoo.fr>
5 * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
6 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
38#include <plotters/plotter.h>
39#include <string_utils.h>
40#include <locale_io.h>
41#include <macros.h>
42#include <pcb_edit_frame.h>
43#include <build_version.h>
44#include <math/util.h> // for KiROUND
45#include <trigo.h>
46
47#include <pcbplot.h>
48#include <board.h>
51#include <reporter.h>
52#include <gbr_metadata.h>
53
54
55// Oblong holes can be drilled by a "canned slot" command (G85) or a routing command
56// a linear routing command (G01) is perhaps more usual for drill files
57//
58// set m_useRouteModeForOval to false to use a canned slot hole (old way)
59// set m_useRouteModeForOval to true (preferred mode) to use a linear routed hole (new way)
60
61
63 : GENDRILL_WRITER_BASE( aPcb )
64{
65 m_file = nullptr;
67 m_conversionUnits = 0.0001;
68 m_mirror = false;
69 m_merge_PTH_NPTH = false;
70 m_minimalHeader = false;
73}
74
75
76void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory, bool aGenDrill,
77 bool aGenMap, REPORTER * aReporter )
78{
79 wxFileName fn;
80 wxString msg;
81
82 std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
83
84 // append a pair representing the NPTH set of holes, for separate drill files.
85 if( !m_merge_PTH_NPTH )
86 hole_sets.emplace_back( F_Cu, B_Cu );
87
88 for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin();
89 it != hole_sets.end(); ++it )
90 {
91 DRILL_LAYER_PAIR pair = *it;
92 // For separate drill files, the last layer pair is the NPTH drill file.
93 bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
94
95 buildHolesList( pair, doing_npth );
96
97 // The file is created if it has holes, or if it is the non plated drill file to be
98 // sure the NPTH file is up to date in separate files mode.
99 // Also a PTH drill/map file is always created, to be sure at least one plated hole
100 // drill file is created (do not create any PTH drill file can be seen as not working
101 // drill generator).
102 if( getHolesCount() > 0 || doing_npth || pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
103 {
104 fn = getDrillFileName( pair, doing_npth, m_merge_PTH_NPTH );
105 fn.SetPath( aPlotDirectory );
106
107 if( aGenDrill )
108 {
109 wxString fullFilename = fn.GetFullPath();
110
111 FILE* file = wxFopen( fullFilename, wxT( "w" ) );
112
113 if( file == nullptr )
114 {
115 if( aReporter )
116 {
117 msg.Printf( _( "Failed to create file '%s'." ), fullFilename );
118 aReporter->Report( msg, RPT_SEVERITY_ERROR );
119 }
120
121 break;
122 }
123 else
124 {
125 if( aReporter )
126 {
127 msg.Printf( _( "Created file '%s'" ), fullFilename );
128 aReporter->Report( msg, RPT_SEVERITY_ACTION );
129 }
130 }
131
132 TYPE_FILE file_type = TYPE_FILE::PTH_FILE;
133
134 // Only external layer pair can have non plated hole
135 // internal layers have only plated via holes
136 if( pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
137 {
138 if( m_merge_PTH_NPTH )
139 file_type = TYPE_FILE::MIXED_FILE;
140 else if( doing_npth )
141 file_type = TYPE_FILE::NPTH_FILE;
142 }
143
144 createDrillFile( file, pair, file_type );
145 }
146 }
147 }
148
149 if( aGenMap )
150 CreateMapFilesSet( aPlotDirectory, aReporter );
151
152 if( aReporter )
153 aReporter->ReportTail( _( "Done." ), RPT_SEVERITY_INFO );
154}
155
156
158{
159 // Hole attributes are comments (lines starting by ';') in the drill files
160 // For tools (file header), they are similar to X2 apertures attributes.
161 // for attributes added in coordinate list, they are just comments.
162 if( !m_minimalHeader )
163 {
164 switch( aAttribute )
165 {
167 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,ViaDrill\n" );
168 break;
169
171 fprintf( m_file, "; #@! TA.AperFunction,Plated,Buried,ViaDrill\n" );
172 break;
173
175 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,ComponentDrill\n" );
176 break;
177
179 fprintf( m_file, "; #@! TA.AperFunction,NonPlated,NPTH,ComponentDrill\n" );
180 break;
181
183 fprintf( m_file, "; #@! TD\n" );
184 break;
185 }
186 }
187}
188
189
191 TYPE_FILE aHolesType )
192{
193 m_file = aFile;
194
195 int diam, holes_count;
196 int x0, y0, xf, yf, xc, yc;
197 double xt, yt;
198 char line[1024];
199
200 LOCALE_IO dummy; // Use the standard notation for double numbers
201
202 writeEXCELLONHeader( aLayerPair, aHolesType );
203
204 holes_count = 0;
205
206 /* Write the tool list */
207 for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
208 {
209 DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
210
211#if USE_ATTRIB_FOR_HOLES
213#endif
214
215 // if units are mm, the resolution is 0.001 mm (3 digits in mantissa)
216 // if units are inches, the resolution is 0.1 mil (4 digits in mantissa)
217 if( m_unitsMetric )
218 fprintf( m_file, "T%dC%.3f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
219 else
220 fprintf( m_file, "T%dC%.4f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
221 }
222
223 fputs( "%\n", m_file ); // End of header info
224 fputs( "G90\n", m_file ); // Absolute mode
225 fputs( "G05\n", m_file ); // Drill mode
226
227 /* Read the hole list and generate data for normal holes (oblong
228 * holes will be created later) */
229 int tool_reference = -2;
230
231 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
232 {
233 HOLE_INFO& hole_descr = m_holeListBuffer[ii];
234
235 if( hole_descr.m_Hole_Shape )
236 continue; // oblong holes will be created later
237
238 if( tool_reference != hole_descr.m_Tool_Reference )
239 {
240 tool_reference = hole_descr.m_Tool_Reference;
241 fprintf( m_file, "T%d\n", tool_reference );
242 }
243
244 x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
245 y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
246
247 if( !m_mirror )
248 y0 *= -1;
249
250 xt = x0 * m_conversionUnits;
251 yt = y0 * m_conversionUnits;
252 writeCoordinates( line, xt, yt );
253
254 fputs( line, m_file );
255 holes_count++;
256 }
257
258 /* Read the hole list and generate data for oblong holes
259 */
260 tool_reference = -2; // set to a value not used for
261 // m_holeListBuffer[ii].m_Tool_Reference
262
263 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
264 {
265 HOLE_INFO& hole_descr = m_holeListBuffer[ii];
266
267 if( hole_descr.m_Hole_Shape == 0 )
268 continue; // wait for oblong holes
269
270 if( tool_reference != hole_descr.m_Tool_Reference )
271 {
272 tool_reference = hole_descr.m_Tool_Reference;
273 fprintf( m_file, "T%d\n", tool_reference );
274 }
275
276 diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
277
278 if( diam == 0 )
279 continue;
280
281 /* Compute the hole coordinates: */
282 xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
283 yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
284
285 /* Compute the start and end coordinates for the shape */
286 if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
287 {
288 int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
289 y0 -= delta;
290 yf += delta;
291 }
292 else
293 {
294 int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
295 x0 -= delta;
296 xf += delta;
297 }
298
299 RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
300 RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
301
302 if( !m_mirror )
303 {
304 y0 *= -1;
305 yf *= -1;
306 }
307
308 xt = x0 * m_conversionUnits;
309 yt = y0 * m_conversionUnits;
310
312 fputs( "G00", m_file ); // Select the routing mode
313
314 writeCoordinates( line, xt, yt );
315
317 {
318 /* remove the '\n' from end of line, because we must add the "G85"
319 * command to the line: */
320 for( int kk = 0; line[kk] != 0; kk++ )
321 {
322 if( line[kk] < ' ' )
323 line[kk] = 0;
324 }
325
326 fputs( line, m_file );
327 fputs( "G85", m_file ); // add the "G85" command
328 }
329 else
330 {
331 fputs( line, m_file );
332 fputs( "M15\nG01", m_file ); // tool down and linear routing from last coordinates
333 }
334
335 xt = xf * m_conversionUnits;
336 yt = yf * m_conversionUnits;
337 writeCoordinates( line, xt, yt );
338
339 fputs( line, m_file );
340
342 fputs( "M16\n", m_file ); // Tool up (end routing)
343
344 fputs( "G05\n", m_file ); // Select drill mode
345 holes_count++;
346 }
347
349
350 return holes_count;
351}
352
353
354void EXCELLON_WRITER::SetFormat( bool aMetric, ZEROS_FMT aZerosFmt, int aLeftDigits,
355 int aRightDigits )
356{
357 m_unitsMetric = aMetric;
358 m_zeroFormat = aZerosFmt;
359
360 /* Set conversion scale depending on drill file units */
361 if( m_unitsMetric )
362 m_conversionUnits = 1.0 / pcbIUScale.IU_PER_MM; // EXCELLON units = mm
363 else
364 m_conversionUnits = 0.001 / pcbIUScale.IU_PER_MILS; // EXCELLON units = INCHES
365
366 // Set the zero counts. if aZerosFmt == DECIMAL_FORMAT, these values
367 // will be set, but not used.
368 if( aLeftDigits <= 0 )
369 aLeftDigits = m_unitsMetric ? 3 : 2;
370
371 if( aRightDigits <= 0 )
372 aRightDigits = m_unitsMetric ? 3 : 4;
373
374 m_precision.m_Lhs = aLeftDigits;
375 m_precision.m_Rhs = aRightDigits;
376}
377
378
379void EXCELLON_WRITER::writeCoordinates( char* aLine, double aCoordX, double aCoordY )
380{
381 wxString xs, ys;
382 int xpad = m_precision.m_Lhs + m_precision.m_Rhs;
383 int ypad = xpad;
384
385 switch( m_zeroFormat )
386 {
387 default:
388 case DECIMAL_FORMAT:
389 /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
390 * Although in decimal format, Excellon specifications do not specify
391 * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
392 * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
393 * Decimal format just prohibit useless leading 0:
394 * 0.45 or .45 is right, but 00.54 is incorrect.
395 */
396 if( m_unitsMetric )
397 {
398 // resolution is 1/1000 mm
399 xs.Printf( wxT( "%.3f" ), aCoordX );
400 ys.Printf( wxT( "%.3f" ), aCoordY );
401 }
402 else
403 {
404 // resolution is 1/10000 inch
405 xs.Printf( wxT( "%.4f" ), aCoordX );
406 ys.Printf( wxT( "%.4f" ), aCoordY );
407 }
408
409 //Remove useless trailing 0
410 while( xs.Last() == '0' )
411 xs.RemoveLast();
412
413 if( xs.Last() == '.' ) // however keep a trailing 0 after the floating point separator
414 xs << '0';
415
416 while( ys.Last() == '0' )
417 ys.RemoveLast();
418
419 if( ys.Last() == '.' )
420 ys << '0';
421
422 sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
423 break;
424
425 case SUPPRESS_LEADING:
426 for( int i = 0; i< m_precision.m_Rhs; i++ )
427 {
428 aCoordX *= 10; aCoordY *= 10;
429 }
430
431 sprintf( aLine, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
432 break;
433
435 {
436 for( int i = 0; i < m_precision.m_Rhs; i++ )
437 {
438 aCoordX *= 10;
439 aCoordY *= 10;
440 }
441
442 if( aCoordX < 0 )
443 xpad++;
444
445 if( aCoordY < 0 )
446 ypad++;
447
448 xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
449 ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
450
451 size_t j = xs.Len() - 1;
452
453 while( xs[j] == '0' && j )
454 xs.Truncate( j-- );
455
456 j = ys.Len() - 1;
457
458 while( ys[j] == '0' && j )
459 ys.Truncate( j-- );
460
461 sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
462 break;
463 }
464
465 case KEEP_ZEROS:
466 for( int i = 0; i< m_precision.m_Rhs; i++ )
467 {
468 aCoordX *= 10; aCoordY *= 10;
469 }
470
471 if( aCoordX < 0 )
472 xpad++;
473
474 if( aCoordY < 0 )
475 ypad++;
476
477 xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
478 ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
479 sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
480 break;
481 }
482}
483
484
486{
487 fputs( "M48\n", m_file ); // The beginning of a header
488
489 if( !m_minimalHeader )
490 {
491 // The next lines in EXCELLON files are comments:
492 wxString msg;
493 msg << wxT( "KiCad " ) << GetBuildVersion();
494
495 fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) );
496 msg = wxT( "; FORMAT={" );
497
498 // Print precision:
499 // Note in decimal format the precision is not used.
500 // the floating point notation has higher priority than the precision.
503 else
504 msg << wxT( "-:-" ); // in decimal format the precision is irrelevant
505
506 msg << wxT( "/ absolute / " );
507 msg << ( m_unitsMetric ? wxT( "metric" ) : wxT( "inch" ) );
508
509 /* Adding numbers notation format.
510 * this is same as m_Choice_Zeros_Format strings, but NOT translated
511 * because some EXCELLON parsers do not like non ASCII values
512 * so we use ONLY English (ASCII) strings.
513 * if new options are added in m_Choice_Zeros_Format, they must also
514 * be added here
515 */
516 msg << wxT( " / " );
517
518 const wxString zero_fmt[4] =
519 {
520 wxT( "decimal" ),
521 wxT( "suppress leading zeros" ),
522 wxT( "suppress trailing zeros" ),
523 wxT( "keep zeros" )
524 };
525
526 msg << zero_fmt[m_zeroFormat] << wxT( "}\n" );
527 fputs( TO_UTF8( msg ), m_file );
528
529 // add the structured comment TF.CreationDate:
530 // The attribute value must conform to the full version of the ISO 8601
532 fputs( TO_UTF8( msg ), m_file );
533
534 // Add the application name that created the drill file
535 msg = wxT( "; #@! TF.GenerationSoftware,Kicad,Pcbnew," );
536 msg << GetBuildVersion() << wxT( "\n" );
537 fputs( TO_UTF8( msg ), m_file );
538
539 // Add the standard X2 FileFunction for drill files
540 // TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH]
541 msg = BuildFileFunctionAttributeString( aLayerPair, aHolesType , true ) + wxT( "\n" );
542 fputs( TO_UTF8( msg ), m_file );
543
544 fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
545 }
546
547 fputs( m_unitsMetric ? "METRIC" : "INCH", m_file );
548
549 switch( m_zeroFormat )
550 {
551 case DECIMAL_FORMAT:
552 fputs( "\n", m_file );
553 break;
554
555 case SUPPRESS_LEADING:
556 fputs( ",TZ\n", m_file );
557 break;
558
560 fputs( ",LZ\n", m_file );
561 break;
562
563 case KEEP_ZEROS:
564 // write nothing, but TZ is acceptable when all zeros are kept
565 fputs( "\n", m_file );
566 break;
567 }
568}
569
570
572{
573 // add if minimal here
574 fputs( "T0\nM30\n", m_file );
575 fclose( m_file );
576}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
wxString GetBuildVersion()
Get the full KiCad version string.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:265
HOLE_ATTRIBUTE m_HoleAttribute
void SetFormat(bool aMetric, ZEROS_FMT aZerosFmt=DECIMAL_FORMAT, int aLeftDigits=0, int aRightDigits=0)
Initialize internal parameters to match the given format.
void writeEXCELLONHeader(DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHolesType)
Print the DRILL file header.
void writeHoleAttribute(HOLE_ATTRIBUTE aAttribute)
Write a comment string giving the hole attribute.
int createDrillFile(FILE *aFile, DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHolesType)
Create an Excellon drill file.
void writeCoordinates(char *aLine, double aCoordX, double aCoordY)
Create a line like according to the selected format.
void CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board.
Create drill maps and drill reports and drill files.
std::vector< DRILL_LAYER_PAIR > getUniqueLayerPairs() const
Get unique layer pairs by examining the micro and blind_buried vias.
virtual const wxString getDrillFileName(DRILL_LAYER_PAIR aPair, bool aNPTH, bool aMerge_PTH_NPTH) const
void buildHolesList(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Create the list of holes and tools for a given board.
void CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=nullptr)
Create the full set of map files for the board, in PS, PDF ... format (use SetMapFileFormat() to sele...
const wxString BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHoleType, bool aCompatNCdrill=false) const
std::vector< HOLE_INFO > m_holeListBuffer
std::vector< DRILL_TOOL > m_toolListBuffer
Handle hole which must be drilled (diameter, position and layers).
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:41
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:71
virtual REPORTER & ReportTail(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Places the report at the end of the list, for objects that support report ordering.
Definition: reporter.h:99
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
#define _(s)
wxString GbrMakeCreationDateAttributeString(GBR_NC_STRING_FORMAT aFormat)
Handle special data (items attributes) during plot.
@ GBR_NC_STRING_FORMAT_NCDRILL
Definition: gbr_metadata.h:64
Classes used in drill files, map files and report files generation.
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
const std::string DrillFileExtension
@ B_Cu
Definition: layer_ids.h:95
@ F_Cu
Definition: layer_ids.h:64
This file contains miscellaneous commonly used macros and functions.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
Plot settings, and plotting engines (PostScript, Gerber, HPGL and DXF)
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:74
wxString DateAndTime()
const double IU_PER_MM
Definition: base_units.h:77
const double IU_PER_MILS
Definition: base_units.h:78
constexpr int delta
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Definition: trigo.cpp:183
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:80
Definition of file extensions used in Kicad.