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) 2017 Jean_Pierre Charras <jp.charras at wanadoo.fr>
5  * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 1992-2017 KiCad Developers, see change_log.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 <plot_common.h>
41 #include <kicad_string.h>
42 #include <wxPcbStruct.h>
43 #include <pgm_base.h>
44 #include <build_version.h>
45 
46 #include <pcbplot.h>
47 #include <pcbnew.h>
50 #include <reporter.h>
51 
52 // Comment/uncomment this to write or not a comment
53 // in drill file when PTH and NPTH are merged to flag
54 // tools used for PTH and tools used for NPTH
55 // #define WRITE_PTH_NPTH_COMMENT
56 
57 
59  : GENDRILL_WRITER_BASE( aPcb )
60 {
61  m_file = NULL;
63  m_conversionUnits = 0.0001;
64  m_mirror = false;
65  m_merge_PTH_NPTH = false;
66  m_minimalHeader = false;
68 }
69 
70 
71 void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
72  bool aGenDrill, bool aGenMap,
73  REPORTER * aReporter )
74 {
75  wxFileName fn;
76  wxString msg;
77 
78  std::vector<DRILL_LAYER_PAIR> hole_sets = getUniqueLayerPairs();
79 
80  // append a pair representing the NPTH set of holes, for separate drill files.
81  if( !m_merge_PTH_NPTH )
82  hole_sets.push_back( DRILL_LAYER_PAIR( F_Cu, B_Cu ) );
83 
84  for( std::vector<DRILL_LAYER_PAIR>::const_iterator it = hole_sets.begin();
85  it != hole_sets.end(); ++it )
86  {
87  DRILL_LAYER_PAIR pair = *it;
88  // For separate drill files, the last layer pair is the NPTH drill file.
89  bool doing_npth = m_merge_PTH_NPTH ? false : ( it == hole_sets.end() - 1 );
90 
91  buildHolesList( pair, doing_npth );
92 
93  // The file is created if it has holes, or if it is the non plated drill file
94  // to be sure the NPTH file is up to date in separate files mode.
95  if( getHolesCount() > 0 || doing_npth )
96  {
97  fn = getDrillFileName( pair, doing_npth, m_merge_PTH_NPTH );
98  fn.SetPath( aPlotDirectory );
99 
100  if( aGenDrill )
101  {
102  wxString fullFilename = fn.GetFullPath();
103 
104  FILE* file = wxFopen( fullFilename, wxT( "w" ) );
105 
106  if( file == NULL )
107  {
108  if( aReporter )
109  {
110  msg.Printf( _( "** Unable to create %s **\n" ), GetChars( fullFilename ) );
111  aReporter->Report( msg );
112  }
113  break;
114  }
115  else
116  {
117  if( aReporter )
118  {
119  msg.Printf( _( "Create file %s\n" ), GetChars( fullFilename ) );
120  aReporter->Report( msg );
121  }
122  }
123 
124  createDrillFile( file );
125  }
126  }
127  }
128 
129  if( aGenMap )
130  CreateMapFilesSet( aPlotDirectory, aReporter );
131 }
132 
133 
135 {
136  m_file = aFile;
137 
138  int diam, holes_count;
139  int x0, y0, xf, yf, xc, yc;
140  double xt, yt;
141  char line[1024];
142 
143  LOCALE_IO dummy; // Use the standard notation for double numbers
144 
146 
147  holes_count = 0;
148 
149 #ifdef WRITE_PTH_NPTH_COMMENT
150  // if PTH_ and NPTH are merged write a comment in drill file at the
151  // beginning of NPTH section
152  bool writePTHcomment = m_merge_PTH_NPTH;
153  bool writeNPTHcomment = m_merge_PTH_NPTH;
154 #endif
155 
156  /* Write the tool list */
157  for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
158  {
159  DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
160 
161 #ifdef WRITE_PTH_NPTH_COMMENT
162  if( writePTHcomment && !tool_descr.m_Hole_NotPlated )
163  {
164  writePTHcomment = false;
165  fprintf( m_file, ";TYPE=PLATED\n" );
166  }
167 
168  if( writeNPTHcomment && tool_descr.m_Hole_NotPlated )
169  {
170  writeNPTHcomment = false;
171  fprintf( m_file, ";TYPE=NON_PLATED\n" );
172  }
173 #endif
174 
175  fprintf( m_file, "T%dC%.3f\n", ii + 1, tool_descr.m_Diameter * m_conversionUnits );
176  }
177 
178  fputs( "%\n", m_file ); // End of header info
179  fputs( "G90\n", m_file ); // Absolute mode
180  fputs( "G05\n", m_file ); // Drill mode
181 
182  // Units :
183  if( !m_minimalHeader )
184  {
185  if( m_unitsDecimal )
186  fputs( "M71\n", m_file ); /* M71 = metric mode */
187  else
188  fputs( "M72\n", m_file ); /* M72 = inch mode */
189  }
190 
191  /* Read the hole file and generate lines for normal holes (oblong
192  * holes will be created later) */
193  int tool_reference = -2;
194 
195  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
196  {
197  HOLE_INFO& hole_descr = m_holeListBuffer[ii];
198 
199  if( hole_descr.m_Hole_Shape )
200  continue; // oblong holes will be created later
201 
202  if( tool_reference != hole_descr.m_Tool_Reference )
203  {
204  tool_reference = hole_descr.m_Tool_Reference;
205  fprintf( m_file, "T%d\n", tool_reference );
206  }
207 
208  x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
209  y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
210 
211  if( !m_mirror )
212  y0 *= -1;
213 
214  xt = x0 * m_conversionUnits;
215  yt = y0 * m_conversionUnits;
216  writeCoordinates( line, xt, yt );
217 
218  fputs( line, m_file );
219  holes_count++;
220  }
221 
222  /* Read the hole file and generate lines for normal holes (oblong holes
223  * will be created later) */
224  tool_reference = -2; // set to a value not used for
225  // m_holeListBuffer[ii].m_Tool_Reference
226  for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
227  {
228  HOLE_INFO& hole_descr = m_holeListBuffer[ii];
229 
230  if( hole_descr.m_Hole_Shape == 0 )
231  continue; // wait for oblong holes
232 
233  if( tool_reference != hole_descr.m_Tool_Reference )
234  {
235  tool_reference = hole_descr.m_Tool_Reference;
236  fprintf( m_file, "T%d\n", tool_reference );
237  }
238 
239  diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );
240 
241  if( diam == 0 )
242  continue;
243 
244  /* Compute the hole coordinates: */
245  xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
246  yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
247 
248  /* Compute the start and end coordinates for the shape */
249  if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
250  {
251  int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
252  y0 -= delta;
253  yf += delta;
254  }
255  else
256  {
257  int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
258  x0 -= delta;
259  xf += delta;
260  }
261 
262  RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
263  RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
264 
265  if( !m_mirror )
266  {
267  y0 *= -1;
268  yf *= -1;
269  }
270 
271  xt = x0 * m_conversionUnits;
272  yt = y0 * m_conversionUnits;
273  writeCoordinates( line, xt, yt );
274 
275  /* remove the '\n' from end of line, because we must add the "G85"
276  * command to the line: */
277  for( int kk = 0; line[kk] != 0; kk++ )
278  {
279  if( line[kk] == '\n' || line[kk] =='\r' )
280  line[kk] = 0;
281  }
282 
283  fputs( line, m_file );
284  fputs( "G85", m_file ); // add the "G85" command
285 
286  xt = xf * m_conversionUnits;
287  yt = yf * m_conversionUnits;
288  writeCoordinates( line, xt, yt );
289 
290  fputs( line, m_file );
291  fputs( "G05\n", m_file );
292  holes_count++;
293  }
294 
296 
297  return holes_count;
298 }
299 
300 
301 void EXCELLON_WRITER::SetFormat( bool aMetric,
302  ZEROS_FMT aZerosFmt,
303  int aLeftDigits,
304  int aRightDigits )
305 {
306  m_unitsDecimal = aMetric;
307  m_zeroFormat = aZerosFmt;
308 
309  /* Set conversion scale depending on drill file units */
310  if( m_unitsDecimal )
311  m_conversionUnits = 1.0 / IU_PER_MM; // EXCELLON units = mm
312  else
313  m_conversionUnits = 0.001 / IU_PER_MILS; // EXCELLON units = INCHES
314 
315  // Set the zero counts. if aZerosFmt == DECIMAL_FORMAT, these values
316  // will be set, but not used.
317  if( aLeftDigits <= 0 )
318  aLeftDigits = m_unitsDecimal ? 3 : 2;
319 
320  if( aRightDigits <= 0 )
321  aRightDigits = m_unitsDecimal ? 3 : 4;
322 
323  m_precision.m_lhs = aLeftDigits;
324  m_precision.m_rhs = aRightDigits;
325 }
326 
327 
328 void EXCELLON_WRITER::writeCoordinates( char* aLine, double aCoordX, double aCoordY )
329 {
330  wxString xs, ys;
331  int xpad = m_precision.m_lhs + m_precision.m_rhs;
332  int ypad = xpad;
333 
334  switch( m_zeroFormat )
335  {
336  default:
337  case DECIMAL_FORMAT:
338  /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
339  * Although in decimal format, Excellon specifications do not specify
340  * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
341  * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
342  * Decimal format just prohibit useless leading 0:
343  * 0.45 or .45 is right, but 00.54 is incorrect.
344  */
345  if( m_unitsDecimal )
346  {
347  // resolution is 1/1000 mm
348  xs.Printf( wxT( "%.3f" ), aCoordX );
349  ys.Printf( wxT( "%.3f" ), aCoordY );
350  }
351  else
352  {
353  // resolution is 1/10000 inch
354  xs.Printf( wxT( "%.4f" ), aCoordX );
355  ys.Printf( wxT( "%.4f" ), aCoordY );
356  }
357 
358  //Remove useless trailing 0
359  while( xs.Last() == '0' )
360  xs.RemoveLast();
361 
362  while( ys.Last() == '0' )
363  ys.RemoveLast();
364 
365  sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
366  break;
367 
368  case SUPPRESS_LEADING:
369  for( int i = 0; i< m_precision.m_rhs; i++ )
370  {
371  aCoordX *= 10; aCoordY *= 10;
372  }
373 
374  sprintf( aLine, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
375  break;
376 
377  case SUPPRESS_TRAILING:
378  {
379  for( int i = 0; i < m_precision.m_rhs; i++ )
380  {
381  aCoordX *= 10;
382  aCoordY *= 10;
383  }
384 
385  if( aCoordX < 0 )
386  xpad++;
387 
388  if( aCoordY < 0 )
389  ypad++;
390 
391  xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
392  ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
393 
394  size_t j = xs.Len() - 1;
395 
396  while( xs[j] == '0' && j )
397  xs.Truncate( j-- );
398 
399  j = ys.Len() - 1;
400 
401  while( ys[j] == '0' && j )
402  ys.Truncate( j-- );
403 
404  sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
405  break;
406  }
407 
408  case KEEP_ZEROS:
409  for( int i = 0; i< m_precision.m_rhs; i++ )
410  {
411  aCoordX *= 10; aCoordY *= 10;
412  }
413 
414  if( aCoordX < 0 )
415  xpad++;
416 
417  if( aCoordY < 0 )
418  ypad++;
419 
420  xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
421  ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
422  sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
423  break;
424  }
425 }
426 
427 
429 {
430  fputs( "M48\n", m_file ); // The beginning of a header
431 
432  if( !m_minimalHeader )
433  {
434  // The next 2 lines in EXCELLON files are comments:
435  wxString msg;
436  msg << wxT("KiCad") << wxT( " " ) << GetBuildVersion();
437 
438  fprintf( m_file, ";DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) );
439  msg = wxT( ";FORMAT={" );
440 
441  // Print precision:
444  else
445  msg << wxT( "-:-" ); // in decimal format the precision is irrelevant
446 
447  msg << wxT( "/ absolute / " );
448  msg << ( m_unitsDecimal ? wxT( "metric" ) : wxT( "inch" ) );
449 
450  /* Adding numbers notation format.
451  * this is same as m_Choice_Zeros_Format strings, but NOT translated
452  * because some EXCELLON parsers do not like non ASCII values
453  * so we use ONLY English (ASCII) strings.
454  * if new options are added in m_Choice_Zeros_Format, they must also
455  * be added here
456  */
457  msg << wxT( " / " );
458 
459  const wxString zero_fmt[4] =
460  {
461  wxT( "decimal" ),
462  wxT( "suppress leading zeros" ),
463  wxT( "suppress trailing zeros" ),
464  wxT( "keep zeros" )
465  };
466 
467  msg << zero_fmt[m_zeroFormat];
468  msg << wxT( "}\n" );
469  fputs( TO_UTF8( msg ), m_file );
470  fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
471  }
472 
473  fputs( m_unitsDecimal ? "METRIC" : "INCH", m_file );
474 
475  switch( m_zeroFormat )
476  {
477  case SUPPRESS_LEADING:
478  case DECIMAL_FORMAT:
479  fputs( ",TZ\n", m_file );
480  break;
481 
482  case SUPPRESS_TRAILING:
483  fputs( ",LZ\n", m_file );
484  break;
485 
486  case KEEP_ZEROS:
487  fputs( ",TZ\n", m_file ); // TZ is acceptable when all zeros are kept
488  break;
489  }
490 }
491 
492 
494 {
495  //add if minimal here
496  fputs( "T0\nM30\n", m_file );
497  fclose( m_file );
498 }
const wxString DrillFileExtension
static int KiROUND(double v)
KiROUND rounds a floating point number to an int using "round halfway cases away from zero"...
Definition: common.h:107
Class LOCALE_IO is a class that can be instantiated within a scope in which you are expecting excepti...
Definition: common.h:166
virtual const wxString getDrillFileName(DRILL_LAYER_PAIR aPair, bool aNPTH, bool aMerge_PTH_NPTH) const
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:317
Class REPORTER is a pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:61
void CreateMapFilesSet(const wxString &aPlotDirectory, REPORTER *aReporter=NULL)
Function CreateMapFilesSet Creates the full set of map files for the board, in PS, PDF ...
static const int delta[8][2]
Definition: solve.cpp:112
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:47
std::vector< DRILL_TOOL > m_toolListBuffer
int createDrillFile(FILE *aFile)
Function CreateDrillFile Creates an Excellon drill file.
void writeCoordinates(char *aLine, double aCoordX, double aCoordY)
std::vector< DRILL_LAYER_PAIR > getUniqueLayerPairs() const
Get unique layer pairs by examining the micro and blind_buried vias.
wxString GetBuildVersion()
Function GetBuildVersion Return the build version string.
The common library.
Common plot library Plot settings, and plotting engines (Postscript, Gerber, HPGL and DXF) ...
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > DRILL_LAYER_PAIR
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:92
see class PGM_BASE
static LIB_PART * dummy()
Used when a LIB_PART is not found in library to draw a dummy shape This component is a 400 mils squar...
Class BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:169
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_UNDEFINED)=0
Function Report is a pure virtual function to override in the derived object.
Classes used in drill files, map files and report files generation.
std::vector< HOLE_INFO > m_holeListBuffer
wxString DateAndTime()
Function DateAndTime.
Definition: string.cpp:229
#define min(a, b)
Definition: auxiliary.h:85
GENDRILL_WRITER_BASE is a class to create drill maps and drill report, and a helper class to created ...