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