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 The 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 //case HOLE_ATTRIBUTE::HOLE_PAD_CASTELLATED:
180 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,ComponentDrill\n" );
181 break;
182
183 case HOLE_ATTRIBUTE::HOLE_PAD_CASTELLATED:
184 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,CastelletedDrill\n" );
185 break;
186
187 case HOLE_ATTRIBUTE::HOLE_PAD_PRESSFIT:
188 fprintf( m_file, "; #@! TA.AperFunction,Plated,PTH,ComponentDrill,PressFit\n" );
189 break;
190
191 case HOLE_ATTRIBUTE::HOLE_MECHANICAL:
192 fprintf( m_file, "; #@! TA.AperFunction,NonPlated,NPTH,ComponentDrill\n" );
193 break;
194
195 case HOLE_ATTRIBUTE::HOLE_UNKNOWN:
196 fprintf( m_file, "; #@! TD\n" );
197 break;
198 }
199 }
200}
201
202
204 TYPE_FILE aHolesType )
205{
206 m_file = aFile;
207
208 int diam, holes_count;
209 int x0, y0, xf, yf, xc, yc;
210 double xt, yt;
211 char line[1024];
212
213 LOCALE_IO dummy; // Use the standard notation for double numbers
214
215 writeEXCELLONHeader( aLayerPair, aHolesType );
216
217 holes_count = 0;
218
219 /* Write the tool list */
220 for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
221 {
222 DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
223
224#if USE_ATTRIB_FOR_HOLES
226#endif
227
228 // if units are mm, the resolution is 0.001 mm (3 digits in mantissa)
229 // if units are inches, the resolution is 0.1 mil (4 digits in mantissa)
230 if( m_unitsMetric )
231 fprintf( m_file, "T%dC%.3f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
232 else
233 fprintf( m_file, "T%dC%.4f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
234 }
235
236 fputs( "%\n", m_file ); // End of header info
237 fputs( "G90\n", m_file ); // Absolute mode
238 fputs( "G05\n", m_file ); // Drill mode
239
240 /* Read the hole list and generate data for normal holes (oblong
241 * holes will be created later) */
242 int tool_reference = -2;
243
244 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
245 {
246 HOLE_INFO& hole_descr = m_holeListBuffer[ii];
247
248 if( hole_descr.m_Hole_Shape )
249 continue; // oblong holes will be created later
250
251 if( tool_reference != hole_descr.m_Tool_Reference )
252 {
253 tool_reference = hole_descr.m_Tool_Reference;
254 fprintf( m_file, "T%d\n", tool_reference );
255 }
256
257 x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
258 y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
259
260 if( !m_mirror )
261 y0 *= -1;
262
263 xt = x0 * m_conversionUnits;
264 yt = y0 * m_conversionUnits;
265 writeCoordinates( line, sizeof( line ), xt, yt );
266
267 fputs( line, m_file );
268 holes_count++;
269 }
270
271 /* Read the hole list and generate data for oblong holes
272 */
273 tool_reference = -2; // set to a value not used for
274 // m_holeListBuffer[ii].m_Tool_Reference
275
276 for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
277 {
278 HOLE_INFO& hole_descr = m_holeListBuffer[ii];
279
280 if( hole_descr.m_Hole_Shape == 0 )
281 continue; // wait for oblong holes
282
283 if( tool_reference != hole_descr.m_Tool_Reference )
284 {
285 tool_reference = hole_descr.m_Tool_Reference;
286 fprintf( m_file, "T%d\n", tool_reference );
287 }
288
289 diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
290
291 if( diam == 0 )
292 continue;
293
294 /* Compute the hole coordinates: */
295 xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
296 yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
297
298 /* Compute the start and end coordinates for the shape */
299 if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
300 {
301 int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
302 y0 -= delta;
303 yf += delta;
304 }
305 else
306 {
307 int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
308 x0 -= delta;
309 xf += delta;
310 }
311
312 RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
313 RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
314
315 if( !m_mirror )
316 {
317 y0 *= -1;
318 yf *= -1;
319 }
320
321 xt = x0 * m_conversionUnits;
322 yt = y0 * m_conversionUnits;
323
325 fputs( "G00", m_file ); // Select the routing mode
326
327 writeCoordinates( line, sizeof( line ), xt, yt );
328
330 {
331 /* remove the '\n' from end of line, because we must add the "G85"
332 * command to the line: */
333 for( int kk = 0; line[kk] != 0; kk++ )
334 {
335 if( line[kk] < ' ' )
336 line[kk] = 0;
337 }
338
339 fputs( line, m_file );
340 fputs( "G85", m_file ); // add the "G85" command
341 }
342 else
343 {
344 fputs( line, m_file );
345 fputs( "M15\nG01", m_file ); // tool down and linear routing from last coordinates
346 }
347
348 xt = xf * m_conversionUnits;
349 yt = yf * m_conversionUnits;
350 writeCoordinates( line, sizeof( line ), xt, yt );
351
352 fputs( line, m_file );
353
355 fputs( "M16\n", m_file ); // Tool up (end routing)
356
357 fputs( "G05\n", m_file ); // Select drill mode
358 holes_count++;
359 }
360
362
363 return holes_count;
364}
365
366
367void EXCELLON_WRITER::SetFormat( bool aMetric, ZEROS_FMT aZerosFmt, int aLeftDigits,
368 int aRightDigits )
369{
370 m_unitsMetric = aMetric;
371 m_zeroFormat = aZerosFmt;
372
373 /* Set conversion scale depending on drill file units */
374 if( m_unitsMetric )
375 m_conversionUnits = 1.0 / pcbIUScale.IU_PER_MM; // EXCELLON units = mm
376 else
377 m_conversionUnits = 0.001 / pcbIUScale.IU_PER_MILS; // EXCELLON units = in
378
379 // Set the zero counts. if aZerosFmt == DECIMAL_FORMAT, these values
380 // will be set, but not used.
381 if( aLeftDigits <= 0 )
382 aLeftDigits = m_unitsMetric ? 3 : 2;
383
384 if( aRightDigits <= 0 )
385 aRightDigits = m_unitsMetric ? 3 : 4;
386
387 m_precision.m_Lhs = aLeftDigits;
388 m_precision.m_Rhs = aRightDigits;
389}
390
391
392void EXCELLON_WRITER::writeCoordinates( char* aLine, size_t aLineSize, double aCoordX,
393 double aCoordY )
394{
395 wxString xs, ys;
396 int xpad = m_precision.m_Lhs + m_precision.m_Rhs;
397 int ypad = xpad;
398
399 switch( m_zeroFormat )
400 {
401 default:
402 case DECIMAL_FORMAT:
403 /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
404 * Although in decimal format, Excellon specifications do not specify
405 * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
406 * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
407 * Decimal format just prohibit useless leading 0:
408 * 0.45 or .45 is right, but 00.54 is incorrect.
409 */
410 if( m_unitsMetric )
411 {
412 // resolution is 1/1000 mm
413 xs.Printf( wxT( "%.3f" ), aCoordX );
414 ys.Printf( wxT( "%.3f" ), aCoordY );
415 }
416 else
417 {
418 // resolution is 1/10000 inch
419 xs.Printf( wxT( "%.4f" ), aCoordX );
420 ys.Printf( wxT( "%.4f" ), aCoordY );
421 }
422
423 //Remove useless trailing 0
424 while( xs.Last() == '0' )
425 xs.RemoveLast();
426
427 if( xs.Last() == '.' ) // however keep a trailing 0 after the floating point separator
428 xs << '0';
429
430 while( ys.Last() == '0' )
431 ys.RemoveLast();
432
433 if( ys.Last() == '.' )
434 ys << '0';
435
436 std::snprintf( aLine, aLineSize, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
437 break;
438
439 case SUPPRESS_LEADING:
440 for( int i = 0; i< m_precision.m_Rhs; i++ )
441 {
442 aCoordX *= 10; aCoordY *= 10;
443 }
444
445 std::snprintf( aLine, aLineSize, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
446 break;
447
449 {
450 for( int i = 0; i < m_precision.m_Rhs; i++ )
451 {
452 aCoordX *= 10;
453 aCoordY *= 10;
454 }
455
456 if( aCoordX < 0 )
457 xpad++;
458
459 if( aCoordY < 0 )
460 ypad++;
461
462 xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
463 ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
464
465 size_t j = xs.Len() - 1;
466
467 while( xs[j] == '0' && j )
468 xs.Truncate( j-- );
469
470 j = ys.Len() - 1;
471
472 while( ys[j] == '0' && j )
473 ys.Truncate( j-- );
474
475 std::snprintf( aLine, aLineSize, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
476 break;
477 }
478
479 case KEEP_ZEROS:
480 for( int i = 0; i< m_precision.m_Rhs; i++ )
481 {
482 aCoordX *= 10; aCoordY *= 10;
483 }
484
485 if( aCoordX < 0 )
486 xpad++;
487
488 if( aCoordY < 0 )
489 ypad++;
490
491 xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
492 ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
493 std::snprintf( aLine, aLineSize, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
494 break;
495 }
496}
497
498
500{
501 fputs( "M48\n", m_file ); // The beginning of a header
502
503 if( !m_minimalHeader )
504 {
505 // The next lines in EXCELLON files are comments:
506 wxString msg;
507 msg << wxT( "KiCad " ) << GetBuildVersion();
508
509 fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( GetISO8601CurrentDateTime() ) );
510 msg = wxT( "; FORMAT={" );
511
512 // Print precision:
513 // Note in decimal format the precision is not used.
514 // the floating point notation has higher priority than the precision.
517 else
518 msg << wxT( "-:-" ); // in decimal format the precision is irrelevant
519
520 msg << wxT( "/ absolute / " );
521 msg << ( m_unitsMetric ? wxT( "metric" ) : wxT( "inch" ) );
522
523 /* Adding numbers notation format.
524 * this is same as m_Choice_Zeros_Format strings, but NOT translated
525 * because some EXCELLON parsers do not like non ASCII values
526 * so we use ONLY English (ASCII) strings.
527 * if new options are added in m_Choice_Zeros_Format, they must also
528 * be added here
529 */
530 msg << wxT( " / " );
531
532 const wxString zero_fmt[4] =
533 {
534 wxT( "decimal" ),
535 wxT( "suppress leading zeros" ),
536 wxT( "suppress trailing zeros" ),
537 wxT( "keep zeros" )
538 };
539
540 msg << zero_fmt[m_zeroFormat] << wxT( "}\n" );
541 fputs( TO_UTF8( msg ), m_file );
542
543 // add the structured comment TF.CreationDate:
544 // The attribute value must conform to the full version of the ISO 8601
546 fputs( TO_UTF8( msg ), m_file );
547
548 // Add the application name that created the drill file
549 msg = wxT( "; #@! TF.GenerationSoftware,Kicad,Pcbnew," );
550 msg << GetBuildVersion() << wxT( "\n" );
551 fputs( TO_UTF8( msg ), m_file );
552
553 // Add the standard X2 FileFunction for drill files
554 // TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH]
555 msg = BuildFileFunctionAttributeString( aLayerPair, aHolesType , true ) + wxT( "\n" );
556 fputs( TO_UTF8( msg ), m_file );
557
558 fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
559 }
560
561 fputs( m_unitsMetric ? "METRIC" : "INCH", m_file );
562
563 switch( m_zeroFormat )
564 {
565 case DECIMAL_FORMAT:
566 fputs( "\n", m_file );
567 break;
568
569 case SUPPRESS_LEADING:
570 fputs( ",TZ\n", m_file );
571 break;
572
574 fputs( ",LZ\n", m_file );
575 break;
576
577 case KEEP_ZEROS:
578 // write nothing, but TZ is acceptable when all zeros are kept
579 fputs( "\n", m_file );
580 break;
581 }
582}
583
584
586{
587 // add if minimal here
588 fputs( "M30\n", m_file );
589 fclose( m_file );
590}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:112
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
wxString GetBuildVersion()
Get the full KiCad version string.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:317
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:41
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition: reporter.h:102
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:112
#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:65
@ F_Cu
Definition: layer_ids.h:64
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:429
const double IU_PER_MM
Definition: base_units.h:76
const double IU_PER_MILS
Definition: base_units.h:77
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:229
Definition of file extensions used in Kicad.