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 <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 )
{

View File

@ -35,6 +35,29 @@
#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
* 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

View File

@ -45,9 +45,11 @@
#include <pcbplot.h>
#include <pcbnew.h>
#include <class_board.h>
#include <gendrill_Excellon_writer.h>
#include <wildcards_and_files_ext.h>
#include <reporter.h>
#include <gbr_metadata.h>
// Comment/uncomment this to write or not a comment
// 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;
@ -147,7 +150,7 @@ int EXCELLON_WRITER::createDrillFile( FILE* aFile )
LOCALE_IO dummy; // Use the standard notation for double numbers
writeEXCELLONHeader();
writeEXCELLONHeader( aLayerPair, aGenerateNPTH_list );
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
if( !m_minimalHeader )
{
// The next 2 lines in EXCELLON files are comments:
// The next lines in EXCELLON files are comments:
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() ) );
msg = wxT( ";FORMAT={" );
msg = "; FORMAT={";
// Print precision:
// Note in decimal format the precision is not used.
@ -459,10 +463,10 @@ void EXCELLON_WRITER::writeEXCELLONHeader()
if( m_zeroFormat != DECIMAL_FORMAT )
msg << m_precision.GetPrecisionString();
else
msg << wxT( "-:-" ); // in decimal format the precision is irrelevant
msg << "-:-"; // in decimal format the precision is irrelevant
msg << wxT( "/ absolute / " );
msg << ( m_unitsMetric ? wxT( "metric" ) : wxT( "inch" ) );
msg << "/ absolute / ";
msg << ( m_unitsMetric ? "metric" : "inch" );
/* Adding numbers notation format.
* 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] =
{
wxT( "decimal" ),
wxT( "suppress leading zeros" ),
wxT( "suppress trailing zeros" ),
wxT( "keep zeros" )
"decimal",
"suppress leading zeros",
"suppress trailing zeros",
"keep zeros"
};
msg << zero_fmt[m_zeroFormat] << wxT( "}\n" );
msg << zero_fmt[m_zeroFormat] << "}\n";
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)
}

View File

@ -114,17 +114,22 @@ private:
* @param aFile = an opened file to write to will be closed by CreateDrillFile
* @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
* ;DRILL file {PCBNEW (2007-11-29-b)} date 17/1/2008-21:02:35
* ;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
* INCH,TZ
*/
void writeEXCELLONHeader();
void writeEXCELLONHeader( DRILL_LAYER_PAIR aLayerPair,
bool aGenerateNPTH_list);
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,
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

View File

@ -95,13 +95,13 @@ void GERBER_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
{
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( aReporter )
{
msg.Printf( _( "** Unable to create %s **\n" ), GetChars( fullFilename ) );
msg.Printf( _( "** Unable to create %s **\n" ), fullFilename );
aReporter->Report( msg );
}
break;
@ -110,7 +110,7 @@ void GERBER_WRITER::CreateDrillandMapFilesSet( const wxString& aPlotDirectory,
{
if( aReporter )
{
msg.Printf( _( "Create file %s\n" ), GetChars( fullFilename ) );
msg.Printf( _( "Create file %s\n" ), fullFilename );
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 );
int GERBER_WRITER::createDrillFile( wxString& aFullFilename, bool aIsNpth,
int aLayer1, int aLayer2 )
DRILL_LAYER_PAIR aLayerPair )
{
int holes_count;
@ -148,68 +148,7 @@ int GERBER_WRITER::createDrillFile( wxString& aFullFilename, bool aIsNpth,
// Add the standard X2 FileFunction for drill files
// %TF.FileFunction,Plated[NonPlated],layer1num,layer2num,PTH[NPTH][Blind][Buried],Drill[Route][Mixed]*%
wxString text( "%TF.FileFunction," );
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 << "*%";
wxString text = BuildFileFunctionAttributeString( aLayerPair, aIsNpth );
plotter.AddLineToHeader( text );
// Add file polarity (positive)

View File

@ -84,12 +84,11 @@ private:
* Creates an Excellon drill file
* @param aFullFilename = the full filename
* @param aIsNpth = true for a NPTH file, false for a PTH file
* @param aLayer1 = the first board layer
* @param aLayer2 = the last board layer
* @param aLayerPair = first board layer and the last board layer for this drill file
* for blind buried vias, they are not always top and bottom layers
* @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.

View File

@ -155,20 +155,10 @@ void GERBER_JOBFILE_WRITER::addJSONHeader()
addJSONObject( text );
closeBlockWithSep();
// creates the TF.CreationDate ext:
// 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 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 );
// date and time format, including time and time zone.
text = GbrMakeCreationDateAttributeString( GBR_NC_STRING_FORMAT_GBRJOB ) + "\n";
addJSONObject( text );
closeBlockWithSep();

View File

@ -287,20 +287,11 @@ void AddGerberX2Header( PLOTTER * aPlotter,
text.Printf( wxT( "%%TF.GenerationSoftware,KiCad,Pcbnew,%s*%%" ), GetBuildVersion() );
aPlotter->AddLineToHeader( makeStringCompatX1( text, aUseX1CompatibilityMode ) );
// creates the TF.CreationDate ext:
// 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 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.CreationDate attribute:
text = GbrMakeCreationDateAttributeString( aUseX1CompatibilityMode ?
GBR_NC_STRING_FORMAT_X1 :
GBR_NC_STRING_FORMAT_X2 );
aPlotter->AddLineToHeader( text );
// Creates the TF,.ProjectId. Format is (from Gerber file format doc):
// %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.
// However Kicad does not handle such a project GUID, so it is built from the board name
wxFileName fn = aBoard->GetFileName();
msg = fn.GetFullName();
wxString msg = fn.GetFullName();
// Build a <project GUID>, from the board name
wxString guid = GbrMakeProjectGUIDfromString( msg );