diff --git a/common/gbr_metadata.cpp b/common/gbr_metadata.cpp index 45136c7d7a..4373b30895 100644 --- a/common/gbr_metadata.cpp +++ b/common/gbr_metadata.cpp @@ -31,6 +31,47 @@ #include #include +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 ) { diff --git a/include/gbr_metadata.h b/include/gbr_metadata.h index 1e6dc5bb2b..223304b749 100644 --- a/include/gbr_metadata.h +++ b/include/gbr_metadata.h @@ -35,6 +35,29 @@ #include +/** 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 diff --git a/pcbnew/exporters/gendrill_Excellon_writer.cpp b/pcbnew/exporters/gendrill_Excellon_writer.cpp index 63ad138339..d4e9173bc9 100644 --- a/pcbnew/exporters/gendrill_Excellon_writer.cpp +++ b/pcbnew/exporters/gendrill_Excellon_writer.cpp @@ -45,9 +45,11 @@ #include #include +#include #include #include #include +#include // 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={" ); + fprintf( m_file, "; DRILL file {%s} date %s\n", TO_UTF8( msg ), TO_UTF8( DateAndTime() ) ); + 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) } diff --git a/pcbnew/exporters/gendrill_Excellon_writer.h b/pcbnew/exporters/gendrill_Excellon_writer.h index 90a3257412..2c91c8a82c 100644 --- a/pcbnew/exporters/gendrill_Excellon_writer.h +++ b/pcbnew/exporters/gendrill_Excellon_writer.h @@ -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={ / absolute / / } + * ; #@! 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(); diff --git a/pcbnew/exporters/gendrill_file_writer_base.cpp b/pcbnew/exporters/gendrill_file_writer_base.cpp index aaa7b9a598..ee71bf2c51 100644 --- a/pcbnew/exporters/gendrill_file_writer_base.cpp +++ b/pcbnew/exporters/gendrill_file_writer_base.cpp @@ -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; +} diff --git a/pcbnew/exporters/gendrill_file_writer_base.h b/pcbnew/exporters/gendrill_file_writer_base.h index e4c6e6da72..31b820f060 100644 --- a/pcbnew/exporters/gendrill_file_writer_base.h +++ b/pcbnew/exporters/gendrill_file_writer_base.h @@ -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 diff --git a/pcbnew/exporters/gendrill_gerber_writer.cpp b/pcbnew/exporters/gendrill_gerber_writer.cpp index 2dc1db860e..688d64378a 100644 --- a/pcbnew/exporters/gendrill_gerber_writer.cpp +++ b/pcbnew/exporters/gendrill_gerber_writer.cpp @@ -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) diff --git a/pcbnew/exporters/gendrill_gerber_writer.h b/pcbnew/exporters/gendrill_gerber_writer.h index f03aba7385..596089d2f9 100644 --- a/pcbnew/exporters/gendrill_gerber_writer.h +++ b/pcbnew/exporters/gendrill_gerber_writer.h @@ -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. diff --git a/pcbnew/exporters/gerber_jobfile_writer.cpp b/pcbnew/exporters/gerber_jobfile_writer.cpp index 79698a01d8..ebc5744ca1 100644 --- a/pcbnew/exporters/gerber_jobfile_writer.cpp +++ b/pcbnew/exporters/gerber_jobfile_writer.cpp @@ -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(); diff --git a/pcbnew/pcbplot.cpp b/pcbnew/pcbplot.cpp index 39b55989d2..686fa0248b 100644 --- a/pcbnew/pcbplot.cpp +++ b/pcbnew/pcbplot.cpp @@ -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,,,*% @@ -312,7 +303,7 @@ void AddGerberX2Header( PLOTTER * aPlotter, // 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 , from the board name wxString guid = GbrMakeProjectGUIDfromString( msg );