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-2018 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 <plotter.h>
39 #include <kicad_string.h>
40 #include <locale_io.h>
41 #include <pcb_edit_frame.h>
42 #include <pgm_base.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 // Comment/uncomment this to write or not a comment
55 // in drill file when PTH and NPTH are merged to flag
56 // tools used for PTH and tools used for NPTH
57 // #define WRITE_PTH_NPTH_COMMENT
58 
59 // Oblong holes can be drilled by a "canned slot" command (G85) or a routing command
60 // a linear routing command (G01) is perhaps more usual for drill files
61 //
62 // set m_useRouteModeForOval to false to use a canned slot hole (old way)
63 // set m_useRouteModeForOval to true (prefered mode) to use a linear routed hole (new way)
64 
66  : GENDRILL_WRITER_BASE( aPcb )
67 {
68  m_file = NULL;
70  m_conversionUnits = 0.0001;
71  m_mirror = false;
72  m_merge_PTH_NPTH = false;
73  m_minimalHeader = false;
75  m_useRouteModeForOval = true;
76 }
77 
78 
79 void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
80  bool aGenDrill, bool aGenMap,
81  REPORTER * aReporter )
82 {
83  wxFileName fn;
84  wxString msg;
85 
86  std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
87 
88  // append a pair representing the NPTH set of holes, for separate drill files.
89  if( !m_merge_PTH_NPTH )
90  hole_sets.emplace_back( F_Cu, B_Cu );
91 
92  for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin();
93  it != hole_sets.end(); ++it )
94  {
95  DRILL_LAYER_PAIR pair = *it;
96  // For separate drill files, the last layer pair is the NPTH drill file.
97  bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
98 
99  buildHolesList( pair, doing_npth );
100 
101  // The file is created if it has holes, or if it is the non plated drill file
102  // to be sure the NPTH file is up to date in separate files mode.
103  // Also a PTH drill/map file is always created, to be sure at least one plated hole drill file
104  // is created (do not create any PTH drill file can be seen as not working drill generator).
105  if( getHolesCount() > 0 || doing_npth || pair == DRILL_LAYER_PAIR( F_Cu, B_Cu ) )
106  {
107  fn = getDrillFileName( pair, doing_npth, m_merge_PTH_NPTH );
108  fn.SetPath( aPlotDirectory );
109 
110  if( aGenDrill )
111  {
112  wxString fullFilename = fn.GetFullPath();
113 
114  FILE* file = wxFopen( fullFilename, wxT( "w" ) );
115 
116  if( file == NULL )
117  {
118  if( aReporter )
119  {
120  msg.Printf( _( "** Unable to create %s **\n" ), fullFilename );
121  aReporter->Report( msg );
122  }
123  break;
124  }
125  else
126  {
127  if( aReporter )
128  {
129  msg.Printf( _( "Create file %s\n" ), fullFilename );
130  aReporter->Report( msg );
131  }
132  }
133 
134  createDrillFile( file, pair, doing_npth );
135  }
136  }
137  }
138 
139  if( aGenMap )
140  CreateMapFilesSet( aPlotDirectory, aReporter );
141 }
142 
143 
145  bool aGenerateNPTH_list )
146 {
147  m_file = aFile;
148 
149  int diam, holes_count;
150  int x0, y0, xf, yf, xc, yc;
151  double xt, yt;
152  char line[1024];
153 
154  LOCALE_IO dummy; // Use the standard notation for double numbers
155 
156  writeEXCELLONHeader( aLayerPair, aGenerateNPTH_list );
157 
158  holes_count = 0;
159 
160 #ifdef WRITE_PTH_NPTH_COMMENT
161  // if PTH_ and NPTH are merged write a comment in drill file at the
162  // beginning of NPTH section
163  bool writePTHcomment = m_merge_PTH_NPTH;
164  bool writeNPTHcomment = m_merge_PTH_NPTH;
165 #endif
166 
167  /* Write the tool list */
168  for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
169  {
170  DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
171 
172 #ifdef WRITE_PTH_NPTH_COMMENT
173  if( writePTHcomment && !tool_descr.m_Hole_NotPlated )
174  {
175  writePTHcomment = false;
176  fprintf( m_file, ";TYPE=PLATED\n" );
177  }
178 
179  if( writeNPTHcomment && tool_descr.m_Hole_NotPlated )
180  {
181  writeNPTHcomment = false;
182  fprintf( m_file, ";TYPE=NON_PLATED\n" );
183  }
184 #endif
185 
186  if( m_unitsMetric ) // if units are mm, the resolution is 0.001 mm (3 digits in mantissa)
187  fprintf( m_file, "T%dC%.3f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
188  else // if units are inches, the resolution is 0.1 mil (4 digits in mantissa)
189  fprintf( m_file, "T%dC%.4f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
190  }
191 
192  fputs( "%\n", m_file ); // End of header info
193  fputs( "G90\n", m_file ); // Absolute mode
194  fputs( "G05\n", m_file ); // Drill mode
195 
196  /* Read the hole file and generate lines for normal holes (oblong
197  * holes will be created later) */
198  int tool_reference = -2;
199 
200  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
201  {
202  HOLE_INFO& hole_descr = m_holeListBuffer[ii];
203 
204  if( hole_descr.m_Hole_Shape )
205  continue; // oblong holes will be created later
206 
207  if( tool_reference != hole_descr.m_Tool_Reference )
208  {
209  tool_reference = hole_descr.m_Tool_Reference;
210  fprintf( m_file, "T%d\n", tool_reference );
211  }
212 
213  x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
214  y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
215 
216  if( !m_mirror )
217  y0 *= -1;
218 
219  xt = x0 * m_conversionUnits;
220  yt = y0 * m_conversionUnits;
221  writeCoordinates( line, xt, yt );
222 
223  fputs( line, m_file );
224  holes_count++;
225  }
226 
227  /* Read the hole file and generate lines for normal holes (oblong holes
228  * will be created later) */
229  tool_reference = -2; // set to a value not used for
230  // m_holeListBuffer[ii].m_Tool_Reference
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 == 0 )
236  continue; // wait for oblong holes
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  diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
245 
246  if( diam == 0 )
247  continue;
248 
249  /* Compute the hole coordinates: */
250  xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
251  yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
252 
253  /* Compute the start and end coordinates for the shape */
254  if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
255  {
256  int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
257  y0 -= delta;
258  yf += delta;
259  }
260  else
261  {
262  int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
263  x0 -= delta;
264  xf += delta;
265  }
266 
267  RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
268  RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
269 
270  if( !m_mirror )
271  {
272  y0 *= -1;
273  yf *= -1;
274  }
275 
276  xt = x0 * m_conversionUnits;
277  yt = y0 * m_conversionUnits;
278 
280  fputs( "G00", m_file ); // Select the routing mode
281 
282  writeCoordinates( line, xt, yt );
283 
284  if( !m_useRouteModeForOval )
285  {
286  /* remove the '\n' from end of line, because we must add the "G85"
287  * command to the line: */
288  for( int kk = 0; line[kk] != 0; kk++ )
289  {
290  if( line[kk] < ' ' )
291  line[kk] = 0;
292  }
293 
294  fputs( line, m_file );
295  fputs( "G85", m_file ); // add the "G85" command
296  }
297  else
298  {
299  fputs( line, m_file );
300  fputs( "M15\nG01", m_file ); // tool down and linear routing from last coordinates
301  }
302 
303  xt = xf * m_conversionUnits;
304  yt = yf * m_conversionUnits;
305  writeCoordinates( line, xt, yt );
306 
307  fputs( line, m_file );
308 
310  fputs( "M16\n", m_file ); // Tool up (end routing)
311 
312  fputs( "G05\n", m_file ); // Select drill mode
313  holes_count++;
314  }
315 
317 
318  return holes_count;
319 }
320 
321 
322 void EXCELLON_WRITER::SetFormat( bool aMetric,
323  ZEROS_FMT aZerosFmt,
324  int aLeftDigits,
325  int aRightDigits )
326 {
327  m_unitsMetric = aMetric;
328  m_zeroFormat = aZerosFmt;
329 
330  /* Set conversion scale depending on drill file units */
331  if( m_unitsMetric )
332  m_conversionUnits = 1.0 / IU_PER_MM; // EXCELLON units = mm
333  else
334  m_conversionUnits = 0.001 / IU_PER_MILS; // EXCELLON units = INCHES
335 
336  // Set the zero counts. if aZerosFmt == DECIMAL_FORMAT, these values
337  // will be set, but not used.
338  if( aLeftDigits <= 0 )
339  aLeftDigits = m_unitsMetric ? 3 : 2;
340 
341  if( aRightDigits <= 0 )
342  aRightDigits = m_unitsMetric ? 3 : 4;
343 
344  m_precision.m_Lhs = aLeftDigits;
345  m_precision.m_Rhs = aRightDigits;
346 }
347 
348 
349 void EXCELLON_WRITER::writeCoordinates( char* aLine, double aCoordX, double aCoordY )
350 {
351  wxString xs, ys;
352  int xpad = m_precision.m_Lhs + m_precision.m_Rhs;
353  int ypad = xpad;
354 
355  switch( m_zeroFormat )
356  {
357  default:
358  case DECIMAL_FORMAT:
359  /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
360  * Although in decimal format, Excellon specifications do not specify
361  * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
362  * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
363  * Decimal format just prohibit useless leading 0:
364  * 0.45 or .45 is right, but 00.54 is incorrect.
365  */
366  if( m_unitsMetric )
367  {
368  // resolution is 1/1000 mm
369  xs.Printf( wxT( "%.3f" ), aCoordX );
370  ys.Printf( wxT( "%.3f" ), aCoordY );
371  }
372  else
373  {
374  // resolution is 1/10000 inch
375  xs.Printf( wxT( "%.4f" ), aCoordX );
376  ys.Printf( wxT( "%.4f" ), aCoordY );
377  }
378 
379  //Remove useless trailing 0
380  while( xs.Last() == '0' )
381  xs.RemoveLast();
382 
383  if( xs.Last() == '.' ) // however keep a trailing 0 after the floating point separator
384  xs << '0';
385 
386  while( ys.Last() == '0' )
387  ys.RemoveLast();
388 
389  if( ys.Last() == '.' )
390  ys << '0';
391 
392  sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
393  break;
394 
395  case SUPPRESS_LEADING:
396  for( int i = 0; i< m_precision.m_Rhs; i++ )
397  {
398  aCoordX *= 10; aCoordY *= 10;
399  }
400 
401  sprintf( aLine, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
402  break;
403 
404  case SUPPRESS_TRAILING:
405  {
406  for( int i = 0; i < m_precision.m_Rhs; i++ )
407  {
408  aCoordX *= 10;
409  aCoordY *= 10;
410  }
411 
412  if( aCoordX < 0 )
413  xpad++;
414 
415  if( aCoordY < 0 )
416  ypad++;
417 
418  xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
419  ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
420 
421  size_t j = xs.Len() - 1;
422 
423  while( xs[j] == '0' && j )
424  xs.Truncate( j-- );
425 
426  j = ys.Len() - 1;
427 
428  while( ys[j] == '0' && j )
429  ys.Truncate( j-- );
430 
431  sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
432  break;
433  }
434 
435  case KEEP_ZEROS:
436  for( int i = 0; i< m_precision.m_Rhs; i++ )
437  {
438  aCoordX *= 10; aCoordY *= 10;
439  }
440 
441  if( aCoordX < 0 )
442  xpad++;
443 
444  if( aCoordY < 0 )
445  ypad++;
446 
447  xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
448  ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
449  sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
450  break;
451  }
452 }
453 
454 
456  bool aGenerateNPTH_list)
457 {
458  fputs( "M48\n", m_file ); // The beginning of a header
459 
460  if( !m_minimalHeader )
461  {
462  // The next lines in EXCELLON files are comments:
463  wxString msg;
464  msg << "KiCad " << GetBuildVersion();
465 
466  fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) );
467  msg = "; FORMAT={";
468 
469  // Print precision:
470  // Note in decimal format the precision is not used.
471  // the floating point notation has higher priority than the precision.
474  else
475  msg << "-:-"; // in decimal format the precision is irrelevant
476 
477  msg << "/ absolute / ";
478  msg << ( m_unitsMetric ? "metric" : "inch" );
479 
480  /* Adding numbers notation format.
481  * this is same as m_Choice_Zeros_Format strings, but NOT translated
482  * because some EXCELLON parsers do not like non ASCII values
483  * so we use ONLY English (ASCII) strings.
484  * if new options are added in m_Choice_Zeros_Format, they must also
485  * be added here
486  */
487  msg << wxT( " / " );
488 
489  const wxString zero_fmt[4] =
490  {
491  "decimal",
492  "suppress leading zeros",
493  "suppress trailing zeros",
494  "keep zeros"
495  };
496 
497  msg << zero_fmt[m_zeroFormat] << "}\n";
498  fputs( TO_UTF8( msg ), m_file );
499 
500  // add the structured comment TF.CreationDate:
501  // The attribute value must conform to the full version of the ISO 8601
503  fputs( TO_UTF8( msg ), m_file );
504 
505  // Add the application name that created the drill file
506  msg = "; #@! TF.GenerationSoftware,Kicad,Pcbnew,";
507  msg << GetBuildVersion() << "\n";
508  fputs( TO_UTF8( msg ), m_file );
509 
510  if( !m_merge_PTH_NPTH )
511  {
512  // Add the standard X2 FileFunction for drill files
513  // TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH]
514  msg = BuildFileFunctionAttributeString( aLayerPair, aGenerateNPTH_list, true )
515  + "\n";
516  fputs( TO_UTF8( msg ), m_file );
517  }
518 
519  fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
520  }
521 
522  fputs( m_unitsMetric ? "METRIC" : "INCH", m_file );
523 
524  switch( m_zeroFormat )
525  {
526  case DECIMAL_FORMAT:
527  fputs( "\n", m_file );
528  break;
529 
530  case SUPPRESS_LEADING:
531  fputs( ",TZ\n", m_file );
532  break;
533 
534  case SUPPRESS_TRAILING:
535  fputs( ",LZ\n", m_file );
536  break;
537 
538  case KEEP_ZEROS:
539  // write nothing, but TZ is acceptable when all zeros are kept
540  fputs( "\n", m_file );
541  break;
542  }
543 }
544 
545 
547 {
548  //add if minimal here
549  fputs( "T0\nM30\n", m_file );
550  fclose( m_file );
551 }
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.
static LIB_PART * dummy()
Used to draw a dummy shape when a LIB_PART is not found in library.
Definition: sch_symbol.cpp:69
int createDrillFile(FILE *aFile, DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Function CreateDrillFile Creates an Excellon drill file.
wxString GbrMakeCreationDateAttributeString(GBR_NC_STRING_FORMAT aFormat)
void writeEXCELLONHeader(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
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:228
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:64
void CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=NULL)
Function CreateMapFilesSet Creates the full set of map files for the board, in PS,...
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
void SetFormat(bool aMetric, ZEROS_FMT aZerosFmt=DECIMAL_FORMAT, int aLeftDigits=0, int aRightDigits=0)
Function SetFormat Initialize internal parameters to match the given format.
Board plot function definition file.
#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)
#define NULL
wxString GetBuildVersion()
Get the full KiCad version string.
Definition of file extensions used in Kicad.
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
const wxString BuildFileFunctionAttributeString(DRILL_LAYER_PAIR aLayerPair, bool aIsNpth, bool aCompatNCdrill=false) const
void buildHolesList(DRILL_LAYER_PAIR aLayerPair, bool aGenerateNPTH_list)
Function BuildHolesList Create the list of holes and tools for a given board The list is sorted by in...
void CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=NULL)
Function CreateDrillandMapFilesSet Creates the full set of Excellon drill file for the board filename...
see class PGM_BASE
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
#define _(s)
Definition: 3d_actions.cpp:33
#define IU_PER_MILS
Definition: plotter.cpp:137
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:68
Classes used in drill files, map files and report files generation.
std::vector< HOLE_INFO > m_holeListBuffer
wxString DateAndTime()
Definition: string.cpp:411
const std::string DrillFileExtension
GENDRILL_WRITER_BASE is a class to create drill maps and drill report, and a helper class to created ...