Drill files generation: add 3 structured comments in NC (Excellon) drill files and remove duplicate code.

This commit is contained in:
jean-pierre charras 2018-11-21 15:33:17 +01:00
parent 106eaaade6
commit 0b890b4d1d
10 changed files with 227 additions and 116 deletions

View File

@ -31,6 +31,47 @@
#include <fctsys.h> #include <fctsys.h>
#include <gbr_metadata.h> #include <gbr_metadata.h>
wxString GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT aFormat )
{
// creates the CreationDate attribute:
// The attribute value must conform to the full version of the ISO 8601
// date and time format, including time and time zone. Note that this is
// the date the Gerber file was effectively created,
// not the time the project of PCB was started
wxDateTime date( wxDateTime::GetTimeNow() );
// Date format: see http://www.cplusplus.com/reference/ctime/strftime
wxString timezone_offset; // ISO 8601 offset from UTC in timezone
timezone_offset = date.Format( "%z" ); // Extract the time zone offset
// The time zone offset format is +mm or +hhmm (or -mm or -hhmm)
// (mm = number of minutes, hh = number of hours. 1h00mn is returned as +0100)
// we want +(or -) hh:mm
if( timezone_offset.Len() > 3 ) // format +hhmm or -hhmm found
// Add separator between hours and minutes
timezone_offset.insert( 3, ":", 1 );
wxString msg;
switch( aFormat )
{
case GBR_NC_STRING_FORMAT_X2:
msg.Printf( "%%TF.CreationDate,%s%s*%%", date.FormatISOCombined(), timezone_offset );
break;
case GBR_NC_STRING_FORMAT_X1:
msg.Printf( "G04 #@! TF.CreationDate,%s%s", date.FormatISOCombined(), timezone_offset );
break;
case GBR_NC_STRING_FORMAT_GBRJOB:
msg.Printf( "\"CreationDate\": \"%s%s\"", date.FormatISOCombined(), timezone_offset );
break;
case GBR_NC_STRING_FORMAT_NCDRILL:
msg.Printf( "; #@! TF.CreationDate,%s%s", date.FormatISOCombined(), timezone_offset );
break;
}
return msg;
}
wxString GbrMakeProjectGUIDfromString( wxString& aText ) wxString GbrMakeProjectGUIDfromString( wxString& aText )
{ {

View File

@ -35,6 +35,29 @@
#include <gbr_netlist_metadata.h> #include <gbr_netlist_metadata.h>
/** creates the TF.CreationDate attribute:
* The attribute value must conform to the full version of the ISO 8601
* date and time format, including time and time zone. Note that this is
* the date the Gerber file is effectively created,
* not the time the project of PCB was started
* @param aFormat = string compatibility: X1, X2, GBRJOB or NC drill synthax.
* exemple of structured comment (compatible X1 gerber)
* G04 #@! TF.CreationDate,2018-11-21T08:49:16+01:00* (exemple of X1 attribute)
* exemple NC drill files
* ; #@! TF.CreationDate,2018-11-21T08:49:16+01:00* (exemple of NC drill comment)
* exemple of X2 attribute:
* %TF.CreationDate,2018-11-06T08:25:24+01:00*%
*/
enum GBR_NC_STRING_FORMAT // Options for string format in some attribute strings
{
GBR_NC_STRING_FORMAT_X1,
GBR_NC_STRING_FORMAT_X2,
GBR_NC_STRING_FORMAT_GBRJOB,
GBR_NC_STRING_FORMAT_NCDRILL
};
wxString GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT aFormat );
/** A helper function to build a project GUID using format RFC4122 Version 1 or 4 /** A helper function to build a project GUID using format RFC4122 Version 1 or 4
* from the project name, because a kicad project has no specific GUID * from the project name, because a kicad project has no specific GUID
* RFC4122 is used mainly for its syntax, because fields have no meaning for Gerber files * RFC4122 is used mainly for its syntax, because fields have no meaning for Gerber files

View File

@ -45,9 +45,11 @@
#include <pcbplot.h> #include <pcbplot.h>
#include <pcbnew.h> #include <pcbnew.h>
#include <class_board.h>
#include <gendrill_Excellon_writer.h> #include <gendrill_Excellon_writer.h>
#include <wildcards_and_files_ext.h> #include <wildcards_and_files_ext.h>
#include <reporter.h> #include <reporter.h>
#include <gbr_metadata.h>
// Comment/uncomment this to write or not a comment // Comment/uncomment this to write or not a comment
// in drill file when PTH and NPTH are merged to flag // in drill file when PTH and NPTH are merged to flag
@ -126,7 +128,7 @@ void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
} }
} }
createDrillFile( file ); createDrillFile( file, pair, doing_npth );
} }
} }
} }
@ -136,7 +138,8 @@ void EXCELLON_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
} }
int EXCELLON_WRITER::createDrillFile( FILE* aFile ) int EXCELLON_WRITER::createDrillFile( FILE* aFile, DRILL_LAYER_PAIR aLayerPair,
bool aGenerateNPTH_list )
{ {
m_file = aFile; m_file = aFile;
@ -147,7 +150,7 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile )
LOCALE_IO dummy; // Use the standard notation for double numbers LOCALE_IO dummy; // Use the standard notation for double numbers
writeEXCELLONHeader(); writeEXCELLONHeader( aLayerPair, aGenerateNPTH_list );
holes_count = 0; holes_count = 0;
@ -440,18 +443,19 @@ void EXCELLON_WRITER::writeCoordinates( char* aLine, double aCoordX, double aCoo
} }
void EXCELLON_WRITER::writeEXCELLONHeader() void EXCELLON_WRITER::writeEXCELLONHeader( DRILL_LAYER_PAIR aLayerPair,
bool aGenerateNPTH_list)
{ {
fputs( "M48\n", m_file ); // The beginning of a header fputs( "M48\n", m_file ); // The beginning of a header
if( !m_minimalHeader ) if( !m_minimalHeader )
{ {
// The next 2 lines in EXCELLON files are comments: // The next lines in EXCELLON files are comments:
wxString msg; wxString msg;
msg << wxT("KiCad") << wxT( " " ) << GetBuildVersion(); msg << "KiCad " << GetBuildVersion();
fprintf( m_file, ";DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) ); fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) );
msg = wxT( ";FORMAT={" ); msg = "; FORMAT={";
// Print precision: // Print precision:
// Note in decimal format the precision is not used. // Note in decimal format the precision is not used.
@ -459,10 +463,10 @@ void EXCELLON_WRITER::writeEXCELLONHeader()
if( m_zeroFormat != DECIMAL_FORMAT ) if( m_zeroFormat != DECIMAL_FORMAT )
msg << m_precision.GetPrecisionString(); msg << m_precision.GetPrecisionString();
else else
msg << wxT( "-:-" ); // in decimal format the precision is irrelevant msg << "-:-"; // in decimal format the precision is irrelevant
msg << wxT( "/ absolute / " ); msg << "/ absolute / ";
msg << ( m_unitsMetric ? wxT( "metric" ) : wxT( "inch" ) ); msg << ( m_unitsMetric ? "metric" : "inch" );
/* Adding numbers notation format. /* Adding numbers notation format.
* this is same as m_Choice_Zeros_Format strings, but NOT translated * this is same as m_Choice_Zeros_Format strings, but NOT translated
@ -475,14 +479,34 @@ void EXCELLON_WRITER::writeEXCELLONHeader()
const wxString zero_fmt[4] = const wxString zero_fmt[4] =
{ {
wxT( "decimal" ), "decimal",
wxT( "suppress leading zeros" ), "suppress leading zeros",
wxT( "suppress trailing zeros" ), "suppress trailing zeros",
wxT( "keep zeros" ) "keep zeros"
}; };
msg << zero_fmt[m_zeroFormat] << wxT( "}\n" ); msg << zero_fmt[m_zeroFormat] << "}\n";
fputs( TO_UTF8( msg ), m_file ); fputs( TO_UTF8( msg ), m_file );
// add the structured comment TF.CreationDate:
// The attribute value must conform to the full version of the ISO 8601
msg = GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_NCDRILL ) + "\n";
fputs( TO_UTF8( msg ), m_file );
// Add the application name that created the drill file
msg = "; #@! TF.GenerationSoftware,Kicad,Pcbnew,";
msg << GetBuildVersion() << "\n";
fputs( TO_UTF8( msg ), m_file );
if( !m_merge_PTH_NPTH )
{
// Add the standard X2 FileFunction for drill files
// TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH]
msg = BuildFileFunctionAttributeString( aLayerPair, aGenerateNPTH_list, true )
+ "\n";
fputs( TO_UTF8( msg ), m_file );
}
fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979) fputs( "FMAT,2\n", m_file ); // Use Format 2 commands (version used since 1979)
} }

View File

@ -114,17 +114,22 @@ private:
* @param aFile = an opened file to write to will be closed by CreateDrillFile * @param aFile = an opened file to write to will be closed by CreateDrillFile
* @return hole count * @return hole count
*/ */
int createDrillFile( FILE * aFile ); int createDrillFile( FILE * aFile, DRILL_LAYER_PAIR aLayerPair,
bool aGenerateNPTH_list );
/* Print the DRILL file header. The full header is: /* Print the DRILL file header. The full header is somethink like:
* M48 * M48
* ;DRILL file {PCBNEW (2007-11-29-b)} date 17/1/2008-21:02:35 * ;DRILL file {PCBNEW (2007-11-29-b)} date 17/1/2008-21:02:35
* ;FORMAT={ <precision> / absolute / <units> / <numbers format>} * ;FORMAT={ <precision> / absolute / <units> / <numbers format>}
* ; #@! TF.FileFunction,Plated,1,4,PTH
* ; #@! TF.CreationDate,2018-11-23T15:59:51+01:00
* ; #@! TF.GenerationSoftware,Kicad,Pcbnew,2017.04
* FMAT,2 * FMAT,2
* INCH,TZ * INCH,TZ
*/ */
void writeEXCELLONHeader(); void writeEXCELLONHeader( DRILL_LAYER_PAIR aLayerPair,
bool aGenerateNPTH_list);
void writeEXCELLONEndOfFile(); void writeEXCELLONEndOfFile();

View File

@ -342,3 +342,88 @@ void GENDRILL_WRITER_BASE::CreateMapFilesSet( const wxString& aPlotDirectory,
} }
} }
} }
const wxString GENDRILL_WRITER_BASE::BuildFileFunctionAttributeString(
DRILL_LAYER_PAIR aLayerPair, bool aIsNpth, bool aCompatNCdrill ) const
{
// Build a wxString containing the .FileFunction attribute for drill files.
// %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
wxString text;
if( aCompatNCdrill )
text = "; #@! ";
else
text = "%";
text << "TF.FileFunction,";
if( aIsNpth )
text << "NonPlated,";
else
text << "Plated,";
int layer1 = aLayerPair.first;
int layer2 = aLayerPair.second;
// In Gerber files, layers num are 1 to copper layer count instead of F_Cu to B_Cu
// (0 to copper layer count-1)
// Note also for a n copper layers board, gerber layers num are 1 ... n
layer1 += 1;
if( layer2 == B_Cu )
layer2 = m_pcb->GetCopperLayerCount();
else
layer2 += 1;
text << layer1 << ",";
text << layer2 << ",";
// Now add PTH or NPTH or Blind or Buried attribute
int toplayer = 1;
int bottomlayer = m_pcb->GetCopperLayerCount();
if( aIsNpth )
text << "NPTH";
else if( layer1 == toplayer && layer2 == bottomlayer )
text << "PTH";
else if( layer1 == toplayer || layer2 == bottomlayer )
text << "Blind";
else
text << "Buried";
// In NC drill file, these previous parameters should be enough:
if( aCompatNCdrill )
return text;
// Now add Drill or Route or Mixed:
// file containing only round holes have Drill attribute
// file containing only oblong holes have Routed attribute
// file containing both holes have Mixed attribute
bool hasOblong = false;
bool hasDrill = false;
for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
{
const HOLE_INFO& hole_descr = m_holeListBuffer[ii];
if( hole_descr.m_Hole_Shape ) // m_Hole_Shape not 0 is an oblong hole)
hasOblong = true;
else
hasDrill = true;
}
if( hasOblong && hasDrill )
text << ",Mixed";
else if( hasDrill )
text << ",Drill";
else if( hasOblong )
text << ",Route";
// else: empty file.
// End of .FileFunction attribute:
text << "*%";
return text;
}

View File

@ -340,6 +340,20 @@ protected:
*/ */
virtual const wxString getDrillFileName( DRILL_LAYER_PAIR aPair, bool aNPTH, virtual const wxString getDrillFileName( DRILL_LAYER_PAIR aPair, bool aNPTH,
bool aMerge_PTH_NPTH ) const; bool aMerge_PTH_NPTH ) const;
/**
* @return a wxString containing the .FileFunction attribute.
* the standard X2 FileFunction for drill files is
* %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
* There is no X1 version, as the Gerber drill files uses only X2 format
* There is a compatible NC drill version.
* @param aLayerPair is the layer pair (Drill from rom first layer to second layer)
* @param aIsNpth is true when generating NPTH drill file
* @param aCompatNCdrill is true when generating NC (Excellon) compatible drill file
*/
const wxString BuildFileFunctionAttributeString( DRILL_LAYER_PAIR aLayerPair,
bool aIsNpth, bool aCompatNCdrill = false ) const;
}; };
#endif // #define GENDRILL_FILE_WRITER_BASE_H #endif // #define GENDRILL_FILE_WRITER_BASE_H

View File

@ -95,13 +95,13 @@ void GERBER_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
{ {
wxString fullFilename = fn.GetFullPath(); wxString fullFilename = fn.GetFullPath();
int result = createDrillFile( fullFilename, doing_npth, pair.first, pair.second ); int result = createDrillFile( fullFilename, doing_npth, pair );
if( result < 0 ) if( result < 0 )
{ {
if( aReporter ) if( aReporter )
{ {
msg.Printf( _( "** Unable to create %s **\n" ), GetChars( fullFilename ) ); msg.Printf( _( "** Unable to create %s **\n" ), fullFilename );
aReporter->Report( msg ); aReporter->Report( msg );
} }
break; break;
@ -110,7 +110,7 @@ void GERBER_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
{ {
if( aReporter ) if( aReporter )
{ {
msg.Printf( _( "Create file %s\n" ), GetChars( fullFilename ) ); msg.Printf( _( "Create file %s\n" ), fullFilename );
aReporter->Report( msg ); aReporter->Report( msg );
} }
} }
@ -127,7 +127,7 @@ void GERBER_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
static void convertOblong2Segment( wxSize aSize, double aOrient, wxPoint& aStart, wxPoint& aEnd ); static void convertOblong2Segment( wxSize aSize, double aOrient, wxPoint& aStart, wxPoint& aEnd );
int GERBER_WRITER::createDrillFile( wxString& aFullFilename, bool aIsNpth, int GERBER_WRITER::createDrillFile( wxString& aFullFilename, bool aIsNpth,
int aLayer1, int aLayer2 ) DRILL_LAYER_PAIR aLayerPair )
{ {
int holes_count; int holes_count;
@ -148,68 +148,7 @@ int GERBER_WRITER::createDrillFile( wxString& aFullFilename, bool aIsNpth,
// Add the standard X2 FileFunction for drill files // Add the standard X2 FileFunction for drill files
// %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*% // %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
wxString text( "%TF.FileFunction," ); wxString text = BuildFileFunctionAttributeString( aLayerPair, aIsNpth );
if( aIsNpth )
text << "NonPlated,";
else
text << "Plated,";
// In Gerber files, layers num are 1 to copper layer count instead of F_Cu to B_Cu
// (0 to copper layer count-1)
// Note also for a n copper layers board, gerber layers num are 1 ... n
aLayer1 += 1;
if( aLayer2 == B_Cu )
aLayer2 = m_pcb->GetCopperLayerCount();
else
aLayer2 += 1;
text << aLayer1 << ",";
text << aLayer2 << ",";
// Now add PTH or NPTH or Blind or Buried attribute
int toplayer = 1;
int bottomlayer = m_pcb->GetCopperLayerCount();
if( aIsNpth )
text << "NPTH";
else if( aLayer1 == toplayer && aLayer2 == bottomlayer )
text << "PTH";
else if( aLayer1 == toplayer || aLayer2 == bottomlayer )
text << "Blind";
else
text << "Buried";
// Now add Drill or Route or Mixed:
// file containing only round holes have Drill attribute
// file containing only oblong holes have Routed attribute
// file containing both holes have Mixed attribute
bool hasOblong = false;
bool hasDrill = false;
for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
{
HOLE_INFO& hole_descr = m_holeListBuffer[ii];
if( hole_descr.m_Hole_Shape ) // m_Hole_Shape not 0 is an oblong hole)
hasOblong = true;
else
hasDrill = true;
}
if( hasOblong && hasDrill )
text << ",Mixed";
else if( hasDrill )
text << ",Drill";
else if( hasOblong )
text << ",Route";
// else: empty file.
// End of .FileFunction attribute:
text << "*%";
plotter.AddLineToHeader( text ); plotter.AddLineToHeader( text );
// Add file polarity (positive) // Add file polarity (positive)

View File

@ -84,12 +84,11 @@ private:
* Creates an Excellon drill file * Creates an Excellon drill file
* @param aFullFilename = the full filename * @param aFullFilename = the full filename
* @param aIsNpth = true for a NPTH file, false for a PTH file * @param aIsNpth = true for a NPTH file, false for a PTH file
* @param aLayer1 = the first board layer * @param aLayerPair = first board layer and the last board layer for this drill file
* @param aLayer2 = the last board layer
* for blind buried vias, they are not always top and bottom layers * for blind buried vias, they are not always top and bottom layers
* @return hole count, or -1 if the file cannot be created * @return hole count, or -1 if the file cannot be created
*/ */
int createDrillFile( wxString& aFullFilename, bool aIsNpth, int aLayer1, int aLayer2 ); int createDrillFile( wxString& aFullFilename, bool aIsNpth, DRILL_LAYER_PAIR aLayerPair );
/** /**
* @return a filename which identify the drill file function. * @return a filename which identify the drill file function.

View File

@ -155,20 +155,10 @@ void GERBER_JOBFILE_WRITER::addJSONHeader()
addJSONObject( text ); addJSONObject( text );
closeBlockWithSep(); closeBlockWithSep();
// creates the TF.CreationDate ext: // creates the CreationDate attribute:
// The attribute value must conform to the full version of the ISO 8601 // The attribute value must conform to the full version of the ISO 8601
// date and time format, including time and time zone. Note that this is // date and time format, including time and time zone.
// the date the Gerber file was effectively created, text = GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_GBRJOB ) + "\n";
// not the time the project of PCB was started
wxDateTime date( wxDateTime::GetTimeNow() );
// Date format: see http://www.cplusplus.com/reference/ctime/strftime
wxString msg = date.Format( wxT( "%z" ) ); // Extract the time zone offset
// The time zone offset format is + (or -) mm or hhmm (mm = number of minutes, hh = number of hours)
// we want +(or -) hh:mm
if( msg.Len() > 3 )
msg.insert( 3, ":", 1 );
text.Printf( wxT( "\"CreationDate\": \"%s%s\"\n" ),date.FormatISOCombined(), msg );
addJSONObject( text ); addJSONObject( text );
closeBlockWithSep(); closeBlockWithSep();

View File

@ -287,20 +287,11 @@ void AddGerberX2Header( PLOTTER * aPlotter,
text.Printf( wxT( "%%TF.GenerationSoftware,KiCad,Pcbnew,%s*%%" ), GetBuildVersion() ); text.Printf( wxT( "%%TF.GenerationSoftware,KiCad,Pcbnew,%s*%%" ), GetBuildVersion() );
aPlotter->AddLineToHeader( makeStringCompatX1( text, aUseX1CompatibilityMode ) ); aPlotter->AddLineToHeader( makeStringCompatX1( text, aUseX1CompatibilityMode ) );
// creates the TF.CreationDate ext: // creates the TF.CreationDate attribute:
// The attribute value must conform to the full version of the ISO 8601 text = GbrMakeCreationDateAttributeString( aUseX1CompatibilityMode ?
// date and time format, including time and time zone. Note that this is GBR_NC_STRING_FORMAT_X1 :
// the date the Gerber file was effectively created, GBR_NC_STRING_FORMAT_X2 );
// not the time the project of PCB was started aPlotter->AddLineToHeader( text );
wxDateTime date( wxDateTime::GetTimeNow() );
// Date format: see http://www.cplusplus.com/reference/ctime/strftime
wxString msg = date.Format( wxT( "%z" ) ); // Extract the time zone offset
// The time zone offset format is + (or -) mm or hhmm (mm = number of minutes, hh = number of hours)
// we want +(or -) hh:mm
if( msg.Len() > 3 )
msg.insert( 3, ":", 1 ),
text.Printf( wxT( "%%TF.CreationDate,%s%s*%%" ), GetChars( date.FormatISOCombined() ), GetChars( msg ) );
aPlotter->AddLineToHeader( makeStringCompatX1( text, aUseX1CompatibilityMode ) );
// Creates the TF,.ProjectId. Format is (from Gerber file format doc): // Creates the TF,.ProjectId. Format is (from Gerber file format doc):
// %TF.ProjectId,<project id>,<project GUID>,<revision id>*% // %TF.ProjectId,<project id>,<project GUID>,<revision id>*%
@ -312,7 +303,7 @@ void AddGerberX2Header( PLOTTER * aPlotter,
// <project GUID> is a string which is an unique id of a project. // <project GUID> is a string which is an unique id of a project.
// However Kicad does not handle such a project GUID, so it is built from the board name // However Kicad does not handle such a project GUID, so it is built from the board name
wxFileName fn = aBoard->GetFileName(); wxFileName fn = aBoard->GetFileName();
msg = fn.GetFullName(); wxString msg = fn.GetFullName();
// Build a <project GUID>, from the board name // Build a <project GUID>, from the board name
wxString guid = GbrMakeProjectGUIDfromString( msg ); wxString guid = GbrMakeProjectGUIDfromString( msg );