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 <dick@softplc.com>
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;
72  m_useRouteModeForOval = true;
73 }
74 
75 
76 void 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
212  writeHoleAttribute( tool_descr.m_HoleAttribute );
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 
316  if( !m_useRouteModeForOval )
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 
354 void 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 / IU_PER_MM; // EXCELLON units = mm
363  else
364  m_conversionUnits = 0.001 / 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 
379 void 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 
434  case SUPPRESS_TRAILING:
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 << "KiCad " << GetBuildVersion();
494 
495  fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) );
496  msg = "; 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 << "-:-"; // in decimal format the precision is irrelevant
505 
506  msg << "/ absolute / ";
507  msg << ( m_unitsMetric ? "metric" : "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  "decimal",
521  "suppress leading zeros",
522  "suppress trailing zeros",
523  "keep zeros"
524  };
525 
526  msg << zero_fmt[m_zeroFormat] << "}\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 = "; #@! TF.GenerationSoftware,Kicad,Pcbnew,";
536  msg << GetBuildVersion() << "\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 )
542  + "\n";
543  fputs( TO_UTF8( msg ), m_file );
544 
545  fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
546  }
547 
548  fputs( m_unitsMetric ? "METRIC" : "INCH", m_file );
549 
550  switch( m_zeroFormat )
551  {
552  case DECIMAL_FORMAT:
553  fputs( "\n", m_file );
554  break;
555 
556  case SUPPRESS_LEADING:
557  fputs( ",TZ\n", m_file );
558  break;
559 
560  case SUPPRESS_TRAILING:
561  fputs( ",LZ\n", m_file );
562  break;
563 
564  case KEEP_ZEROS:
565  // write nothing, but TZ is acceptable when all zeros are kept
566  fputs( "\n", m_file );
567  break;
568  }
569 }
570 
571 
573 {
574  // add if minimal here
575  fputs( "T0\nM30\n", m_file );
576  fclose( m_file );
577 }
Handle special data (items attributes) during plot.
virtual const wxString getDrillFileName(DRILL_LAYER_PAIR aPair, bool aNPTH, bool aMerge_PTH_NPTH) const
Plot settings, and plotting engines (PostScript, Gerber, HPGL and DXF)
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
static constexpr double IU_PER_MM
Mock up a conversion function.
void CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=nullptr)
Create the full set of map files for the board, in PS, PDF ...
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
wxString GbrMakeCreationDateAttributeString(GBR_NC_STRING_FORMAT aFormat)
std::vector< DRILL_LAYER_PAIR > getUniqueLayerPairs() const
Get unique layer pairs by examining the micro and blind_buried vias.
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:229
HOLE_ATTRIBUTE m_HoleAttribute
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:70
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
This file contains miscellaneous commonly used macros and functions.
void SetFormat(bool aMetric, ZEROS_FMT aZerosFmt=DECIMAL_FORMAT, int aLeftDigits=0, int aRightDigits=0)
Initialize internal parameters to match the given format.
Board plot function definition file.
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
std::vector< DRILL_TOOL > m_toolListBuffer
void writeCoordinates(char *aLine, double aCoordX, double aCoordY)
Create a line like according to the selected format.
wxString GetBuildVersion()
Get the full KiCad version string.
const wxString BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHoleType, bool aCompatNCdrill=false) const
Definition of file extensions used in Kicad.
#define _(s)
Handle hole which must be drilled (diameter, position and layers).
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
void writeHoleAttribute(HOLE_ATTRIBUTE aAttribute)
Write a comment string giving the hole attribute.
void buildHolesList(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Create the list of holes and tools for a given board.
int createDrillFile(FILE *aFile, DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHolesType)
Create an Excellon drill file.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
Definition: layer_ids.h:71
#define IU_PER_MILS
Definition: plotter.cpp:136
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:73
void writeEXCELLONHeader(DRILL_LAYER_PAIR aLayerPair, TYPE_FILE aHolesType)
Print the DRILL file header.
Classes used in drill files, map files and report files generation.
void CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board.
constexpr int delta
wxString DateAndTime()
std::vector< HOLE_INFO > m_holeListBuffer
const std::string DrillFileExtension
Create drill maps and drill reports and drill files.