KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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
76bool EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory, bool aGenDrill,
77 bool aGenMap, REPORTER * aReporter )
78{
79 bool success = true;
80 wxFileName fn;
81 wxString msg;
82
83 std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
84
85 // append a pair representing the NPTH set of holes, for separate drill files.
86 if( !m_merge_PTH_NPTH )
87 hole_sets.emplace_back( F_Cu, B_Cu );
88
89 for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin();
90 it != hole_sets.end(); ++it )
91 {
92 DRILL_LAYER_PAIR pair = *it;
93 // For separate drill files, the last layer pair is the NPTH drill file.
94 bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
95
96 buildHolesList( pair, doing_npth );
97
98 // The file is created if it has holes, or if it is the non plated drill file to be
99 // sure the NPTH file is up to date in separate files mode.
100 // Also a PTH drill/map file is always created, to be sure at least one plated hole
101 // drill file is created (do not create any PTH drill file can be seen as not working
102 // drill generator).
103 if( getHolesCount() > 0 || doing_npth || pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
104 {
105 fn = getDrillFileName( pair, doing_npth, m_merge_PTH_NPTH );
106 fn.SetPath( aPlotDirectory );
107
108 if( aGenDrill )
109 {
110 wxString fullFilename = fn.GetFullPath();
111
112 FILE* file = wxFopen( fullFilename, wxT( "w" ) );
113
114 if( file == nullptr )
115 {
116 if( aReporter )
117 {
118 msg.Printf( _( "Failed to create file '%s'." ), fullFilename );
119 aReporter->Report( msg, RPT_SEVERITY_ERROR );
120 success = false;
121 }
122
123 break;
124 }
125 else
126 {
127 if( aReporter )
128 {
129 msg.Printf( _( "Created file '%s'" ), fullFilename );
130 aReporter->Report( msg, RPT_SEVERITY_ACTION );
131 }
132 }
133
134 TYPE_FILE file_type = TYPE_FILE::PTH_FILE;
135
136 // Only external layer pair can have non plated hole
137 // internal layers have only plated via holes
138 if( pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
139 {
140 if( m_merge_PTH_NPTH )
141 file_type = TYPE_FILE::MIXED_FILE;
142 else if( doing_npth )
143 file_type = TYPE_FILE::NPTH_FILE;
144 }
145
146 createDrillFile( file, pair, file_type );
147 }
148 }
149 }
150
151 if( aGenMap )
152 success &= CreateMapFilesSet( aPlotDirectory, aReporter );
153
154 if( aReporter )
155 aReporter->ReportTail( _( "Done." ), RPT_SEVERITY_INFO );
156
157 return success;
158}
159
160
162{
163 // Hole attributes are comments (lines starting by ';') in the drill files
164 // For tools (file header), they are similar to X2 apertures attributes.
165 // for attributes added in coordinate list, they are just comments.
166 if( !m_minimalHeader )
167 {
168 switch( aAttribute )
169 {
170 case HOLE_ATTRIBUTE::HOLE_VIA_THROUGH:
171 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,ViaDrill\n" );
172 break;
173
174 case HOLE_ATTRIBUTE::HOLE_VIA_BURIED:
175 fprintf( m_file, "; #@! TA.AperFunction,Plated,Buried,ViaDrill\n" );
176 break;
177
178 case HOLE_ATTRIBUTE::HOLE_PAD:
179 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,ComponentDrill\n" );
180 break;
181
182 case HOLE_ATTRIBUTE::HOLE_MECHANICAL:
183 fprintf( m_file, "; #@! TA.AperFunction,NonPlated,NPTH,ComponentDrill\n" );
184 break;
185
186 case HOLE_ATTRIBUTE::HOLE_UNKNOWN:
187 fprintf( m_file, "; #@! TD\n" );
188 break;
189 }
190 }
191}
192
193
195 TYPE_FILE aHolesType )
196{
197 m_file = aFile;
198
199 int diam, holes_count;
200 int x0, y0, xf, yf, xc, yc;
201 double xt, yt;
202 char line[1024];
203
204 LOCALE_IO dummy; // Use the standard notation for double numbers
205
206 writeEXCELLONHeader( aLayerPair, aHolesType );
207
208 holes_count = 0;
209
210 /* Write the tool list */
211 for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
212 {
213 DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
214
215#if USE_ATTRIB_FOR_HOLES
217#endif
218
219 // if units are mm, the resolution is 0.001 mm (3 digits in mantissa)
220 // if units are inches, the resolution is 0.1 mil (4 digits in mantissa)
221 if( m_unitsMetric )
222 fprintf( m_file, "T%dC%.3f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
223 else
224 fprintf( m_file, "T%dC%.4f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
225 }
226
227 fputs( "%\n", m_file ); // End of header info
228 fputs( "G90\n", m_file ); // Absolute mode
229 fputs( "G05\n", m_file ); // Drill mode
230
231 /* Read the hole list and generate data for normal holes (oblong
232 * holes will be created later) */
233 int tool_reference = -2;
234
235 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
236 {
237 HOLE_INFO& hole_descr = m_holeListBuffer[ii];
238
239 if( hole_descr.m_Hole_Shape )
240 continue; // oblong holes will be created later
241
242 if( tool_reference != hole_descr.m_Tool_Reference )
243 {
244 tool_reference = hole_descr.m_Tool_Reference;
245 fprintf( m_file, "T%d\n", tool_reference );
246 }
247
248 x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
249 y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
250
251 if( !m_mirror )
252 y0 *= -1;
253
254 xt = x0 * m_conversionUnits;
255 yt = y0 * m_conversionUnits;
256 writeCoordinates( line, sizeof( line ), xt, yt );
257
258 fputs( line, m_file );
259 holes_count++;
260 }
261
262 /* Read the hole list and generate data for oblong holes
263 */
264 tool_reference = -2; // set to a value not used for
265 // m_holeListBuffer[ii].m_Tool_Reference
266
267 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
268 {
269 HOLE_INFO& hole_descr = m_holeListBuffer[ii];
270
271 if( hole_descr.m_Hole_Shape == 0 )
272 continue; // wait for oblong holes
273
274 if( tool_reference != hole_descr.m_Tool_Reference )
275 {
276 tool_reference = hole_descr.m_Tool_Reference;
277 fprintf( m_file, "T%d\n", tool_reference );
278 }
279
280 diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
281
282 if( diam == 0 )
283 continue;
284
285 /* Compute the hole coordinates: */
286 xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
287 yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
288
289 /* Compute the start and end coordinates for the shape */
290 if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
291 {
292 int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
293 y0 -= delta;
294 yf += delta;
295 }
296 else
297 {
298 int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
299 x0 -= delta;
300 xf += delta;
301 }
302
303 RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
304 RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
305
306 if( !m_mirror )
307 {
308 y0 *= -1;
309 yf *= -1;
310 }
311
312 xt = x0 * m_conversionUnits;
313 yt = y0 * m_conversionUnits;
314
316 fputs( "G00", m_file ); // Select the routing mode
317
318 writeCoordinates( line, sizeof( line ), xt, yt );
319
321 {
322 /* remove the '\n' from end of line, because we must add the "G85"
323 * command to the line: */
324 for( int kk = 0; line[kk] != 0; kk++ )
325 {
326 if( line[kk] < ' ' )
327 line[kk] = 0;
328 }
329
330 fputs( line, m_file );
331 fputs( "G85", m_file ); // add the "G85" command
332 }
333 else
334 {
335 fputs( line, m_file );
336 fputs( "M15\nG01", m_file ); // tool down and linear routing from last coordinates
337 }
338
339 xt = xf * m_conversionUnits;
340 yt = yf * m_conversionUnits;
341 writeCoordinates( line, sizeof( line ), xt, yt );
342
343 fputs( line, m_file );
344
346 fputs( "M16\n", m_file ); // Tool up (end routing)
347
348 fputs( "G05\n", m_file ); // Select drill mode
349 holes_count++;
350 }
351
353
354 return holes_count;
355}
356
357
358void EXCELLON_WRITER::SetFormat( bool aMetric, ZEROS_FMT aZerosFmt, int aLeftDigits,
359 int aRightDigits )
360{
361 m_unitsMetric = aMetric;
362 m_zeroFormat = aZerosFmt;
363
364 /* Set conversion scale depending on drill file units */
365 if( m_unitsMetric )
366 m_conversionUnits = 1.0 / pcbIUScale.IU_PER_MM; // EXCELLON units = mm
367 else
368 m_conversionUnits = 0.001 / pcbIUScale.IU_PER_MILS; // EXCELLON units = INCHES
369
370 // Set the zero counts. if aZerosFmt == DECIMAL_FORMAT, these values
371 // will be set, but not used.
372 if( aLeftDigits <= 0 )
373 aLeftDigits = m_unitsMetric ? 3 : 2;
374
375 if( aRightDigits <= 0 )
376 aRightDigits = m_unitsMetric ? 3 : 4;
377
378 m_precision.m_Lhs = aLeftDigits;
379 m_precision.m_Rhs = aRightDigits;
380}
381
382
383void EXCELLON_WRITER::writeCoordinates( char* aLine, size_t aLineSize, double aCoordX,
384 double aCoordY )
385{
386 wxString xs, ys;
387 int xpad = m_precision.m_Lhs + m_precision.m_Rhs;
388 int ypad = xpad;
389
390 switch( m_zeroFormat )
391 {
392 default:
393 case DECIMAL_FORMAT:
394 /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
395 * Although in decimal format, Excellon specifications do not specify
396 * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
397 * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
398 * Decimal format just prohibit useless leading 0:
399 * 0.45 or .45 is right, but 00.54 is incorrect.
400 */
401 if( m_unitsMetric )
402 {
403 // resolution is 1/1000 mm
404 xs.Printf( wxT( "%.3f" ), aCoordX );
405 ys.Printf( wxT( "%.3f" ), aCoordY );
406 }
407 else
408 {
409 // resolution is 1/10000 inch
410 xs.Printf( wxT( "%.4f" ), aCoordX );
411 ys.Printf( wxT( "%.4f" ), aCoordY );
412 }
413
414 //Remove useless trailing 0
415 while( xs.Last() == '0' )
416 xs.RemoveLast();
417
418 if( xs.Last() == '.' ) // however keep a trailing 0 after the floating point separator
419 xs << '0';
420
421 while( ys.Last() == '0' )
422 ys.RemoveLast();
423
424 if( ys.Last() == '.' )
425 ys << '0';
426
427 std::snprintf( aLine, aLineSize, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
428 break;
429
430 case SUPPRESS_LEADING:
431 for( int i = 0; i< m_precision.m_Rhs; i++ )
432 {
433 aCoordX *= 10; aCoordY *= 10;
434 }
435
436 std::snprintf( aLine, aLineSize, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
437 break;
438
440 {
441 for( int i = 0; i < m_precision.m_Rhs; i++ )
442 {
443 aCoordX *= 10;
444 aCoordY *= 10;
445 }
446
447 if( aCoordX < 0 )
448 xpad++;
449
450 if( aCoordY < 0 )
451 ypad++;
452
453 xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
454 ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
455
456 size_t j = xs.Len() - 1;
457
458 while( xs[j] == '0' && j )
459 xs.Truncate( j-- );
460
461 j = ys.Len() - 1;
462
463 while( ys[j] == '0' && j )
464 ys.Truncate( j-- );
465
466 std::snprintf( aLine, aLineSize, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
467 break;
468 }
469
470 case KEEP_ZEROS:
471 for( int i = 0; i< m_precision.m_Rhs; i++ )
472 {
473 aCoordX *= 10; aCoordY *= 10;
474 }
475
476 if( aCoordX < 0 )
477 xpad++;
478
479 if( aCoordY < 0 )
480 ypad++;
481
482 xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
483 ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
484 std::snprintf( aLine, aLineSize, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
485 break;
486 }
487}
488
489
491{
492 fputs( "M48\n", m_file ); // The beginning of a header
493
494 if( !m_minimalHeader )
495 {
496 // The next lines in EXCELLON files are comments:
497 wxString msg;
498 msg << wxT( "KiCad " ) << GetBuildVersion();
499
500 fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( GetISO8601CurrentDateTime() ) );
501 msg = wxT( "; FORMAT={" );
502
503 // Print precision:
504 // Note in decimal format the precision is not used.
505 // the floating point notation has higher priority than the precision.
508 else
509 msg << wxT( "-:-" ); // in decimal format the precision is irrelevant
510
511 msg << wxT( "/ absolute / " );
512 msg << ( m_unitsMetric ? wxT( "metric" ) : wxT( "inch" ) );
513
514 /* Adding numbers notation format.
515 * this is same as m_Choice_Zeros_Format strings, but NOT translated
516 * because some EXCELLON parsers do not like non ASCII values
517 * so we use ONLY English (ASCII) strings.
518 * if new options are added in m_Choice_Zeros_Format, they must also
519 * be added here
520 */
521 msg << wxT( " / " );
522
523 const wxString zero_fmt[4] =
524 {
525 wxT( "decimal" ),
526 wxT( "suppress leading zeros" ),
527 wxT( "suppress trailing zeros" ),
528 wxT( "keep zeros" )
529 };
530
531 msg << zero_fmt[m_zeroFormat] << wxT( "}\n" );
532 fputs( TO_UTF8( msg ), m_file );
533
534 // add the structured comment TF.CreationDate:
535 // The attribute value must conform to the full version of the ISO 8601
537 fputs( TO_UTF8( msg ), m_file );
538
539 // Add the application name that created the drill file
540 msg = wxT( "; #@! TF.GenerationSoftware,Kicad,Pcbnew," );
541 msg << GetBuildVersion() << wxT( "\n" );
542 fputs( TO_UTF8( msg ), m_file );
543
544 // Add the standard X2 FileFunction for drill files
545 // TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH]
546 msg = BuildFileFunctionAttributeString( aLayerPair, aHolesType , true ) + wxT( "\n" );
547 fputs( TO_UTF8( msg ), m_file );
548
549 fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
550 }
551
552 fputs( m_unitsMetric ? "METRIC" : "INCH", m_file );
553
554 switch( m_zeroFormat )
555 {
556 case DECIMAL_FORMAT:
557 fputs( "\n", m_file );
558 break;
559
560 case SUPPRESS_LEADING:
561 fputs( ",TZ\n", m_file );
562 break;
563
565 fputs( ",LZ\n", m_file );
566 break;
567
568 case KEEP_ZEROS:
569 // write nothing, but TZ is acceptable when all zeros are kept
570 fputs( "\n", m_file );
571 break;
572 }
573}
574
575
577{
578 // add if minimal here
579 fputs( "M30\n", m_file );
580 fclose( m_file );
581}
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:276
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 writeCoordinates(char *aLine, size_t aLineSize, double aCoordX, double aCoordY)
Create a line like according to the selected 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.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board.
int createDrillFile(FILE *aFile, DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHolesType)
Create an Excellon drill file.
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.
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
bool 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...
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:49
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
static const std::string DrillFileExtension
@ B_Cu
Definition: layer_ids.h:96
@ F_Cu
Definition: layer_ids.h:65
This file contains miscellaneous commonly used macros and functions.
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
std::vector< FAB_LAYER_COLOR > dummy
wxString GetISO8601CurrentDateTime()
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:391
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)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:228
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:85
Definition of file extensions used in Kicad.