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