Gerbview: Attempt to parse unknown files as gerber/drill

Test parsing function transmogrified from gerbv 2.7.0. gEDA suite is GPL
2+ so should be license compatible with our GPL3.

Fixes: https://gitlab.com/kicad/code/kicad/-/issues/1848
This commit is contained in:
Mike Williams 2021-08-03 11:09:09 -04:00
parent 21a8dd6302
commit da2e7e158b
5 changed files with 288 additions and 16 deletions

View File

@ -154,6 +154,16 @@ public: EXCELLON_IMAGE( int layer ) :
virtual void ResetDefaultValues() override;
/**
* @brief Performs a heuristics-based check of whether the file is an Excellon drill file.
*
* Does not invoke the full parser.
*
* @param aFullFileName aFullFileName is the full filename of the Excellon file.
* @return True if drill file, false otherwise
*/
static bool TestFileIsExcellon( const wxString& aFullFileName );
/**
* Read and load a drill (EXCELLON format) file.
*

View File

@ -315,6 +315,131 @@ void EXCELLON_IMAGE::ResetDefaultValues()
m_NoTrailingZeros = true;
}
/*
* Original function derived from drill_file_p() of gerbv 2.7.0.
* Copyright of the source file drill.cpp included below:
*/
/*
* gEDA - GNU Electronic Design Automation
* drill.c
* Copyright (C) 2000-2006 Andreas Andersson
*
* $Id$
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
bool EXCELLON_IMAGE::TestFileIsExcellon( const wxString& aFullFileName )
{
char* letter;
bool foundM48 = false;
bool foundM30 = false;
bool foundPercent = false;
bool foundT = false;
bool foundX = false;
bool foundY = false;
FILE* file = wxFopen( aFullFileName, "rb" );
if( file == nullptr )
return false;
FILE_LINE_READER excellonReader( file, aFullFileName );
try
{
while( true )
{
if( excellonReader.ReadLine() == nullptr )
break;
// Remove all whitespace from the beginning and end
char* line = StrPurge( excellonReader.Line() );
// Skip empty lines
if( *line == 0 )
continue;
// Check that file is not binary (non-printing chars)
for( size_t i = 0; i < strlen( line ); i++ )
{
if( !isascii( line[i] ) )
return false;
}
// We don't want to look for any commands after a comment so
// just end the line early if we find a comment
char* buf = strstr( line, ";" );
if( buf != nullptr )
*buf = 0;
// Check for M48 = start of drill header
if( strstr( line, "M48" ) )
foundM48 = true;
// Check for M30 = end of drill program
if( strstr( line, "M30" ) )
if( foundPercent )
foundM30 = true; // Found M30 after % = good
// Check for % on its own line at end of header
if( ( letter = strstr( line, "%" ) ) != nullptr )
if( ( letter[1] == '\r' ) || ( letter[1] == '\n' ) )
foundPercent = true;
// Check for T<number>
if( ( letter = strstr( line, "T" ) ) != nullptr )
{
if( !foundT && ( foundX || foundY ) )
foundT = false; /* Found first T after X or Y */
else
{
// verify next char is digit
if( isdigit( letter[1] ) )
foundT = true;
}
}
// look for X<number> or Y<number>
if( ( letter = strstr( line, "X" ) ) != nullptr )
if( isdigit( letter[1] ) )
foundX = true;
if( ( letter = strstr( line, "Y" ) ) != nullptr )
if( isdigit( letter[1] ) )
foundY = true;
}
}
catch( IO_ERROR& e )
{
return false;
}
/* Now form logical expression determining if this is a drill file */
if( ( ( foundX || foundY ) && foundT ) && ( foundM48 || ( foundPercent && foundM30 ) ) )
return true;
else if( foundM48 && foundT && foundPercent && foundM30 )
/* Pathological case of drill file with valid header
and EOF but no drill XY locations. */
return true;
return false;
}
/*
* Read a EXCELLON file.
* Gerber classes are used because there is likeness between Gerber files

View File

@ -529,17 +529,6 @@ bool GERBVIEW_FRAME::unarchiveFiles( const wxString& aFullFileName, REPORTER* aR
enum GERBER_ORDER_ENUM order;
GERBER_FILE_IMAGE_LIST::GetGerberLayerFromFilename( fname, order, matchedExt );
if( order == GERBER_ORDER_ENUM::GERBER_LAYER_UNKNOWN )
{
if( aReporter )
{
msg.Printf( _( "Skipped file '%s' (unknown type).\n" ), entry->GetName() );
aReporter->Report( msg, RPT_SEVERITY_WARNING );
}
continue;
}
int layer = GetActiveLayer();
if( layer == NO_AVAILABLE_LAYERS )
@ -583,7 +572,33 @@ bool GERBVIEW_FRAME::unarchiveFiles( const wxString& aFullFileName, REPORTER* aR
bool read_ok = true;
if( order != GERBER_ORDER_ENUM::GERBER_DRILL )
// Try to parse files if we can't tell from file extension
if( order == GERBER_ORDER_ENUM::GERBER_LAYER_UNKNOWN )
{
if( EXCELLON_IMAGE::TestFileIsExcellon( unzipped_tempfile ) )
{
order = GERBER_ORDER_ENUM::GERBER_DRILL;
}
else if( GERBER_FILE_IMAGE::TestFileIsRS274( unzipped_tempfile ) )
{
// If we have no way to know what layer it is, just guess
order = GERBER_ORDER_ENUM::GERBER_TOP_COPPER;
}
else
{
if( aReporter )
{
msg.Printf( _( "Skipped file '%s' (unknown type).\n" ), entry->GetName() );
aReporter->Report( msg, RPT_SEVERITY_WARNING );
}
}
}
if( order == GERBER_ORDER_ENUM::GERBER_DRILL )
{
read_ok = Read_EXCELLON_File( unzipped_tempfile );
}
else if( order != GERBER_ORDER_ENUM::GERBER_LAYER_UNKNOWN )
{
// Read gerber files: each file is loaded on a new GerbView layer
read_ok = Read_GERBER_File( unzipped_tempfile );
@ -592,10 +607,6 @@ bool GERBVIEW_FRAME::unarchiveFiles( const wxString& aFullFileName, REPORTER* aR
GetCanvas()->GetView()->SetLayerHasNegatives(
GERBER_DRAW_LAYER( layer ), GetGbrImage( layer )->HasNegativeItems() );
}
else // Everything else is a drill file
{
read_ok = Read_EXCELLON_File( unzipped_tempfile );
}
delete entry;

View File

@ -118,6 +118,16 @@ public:
return wxT( "GERBER_FILE_IMAGE" );
}
/**
* @brief Performs a heuristics-based check of whether the file is an RS274 gerber file.
*
* Does not invoke the full parser.
*
* @param aFullFileName aFullFileName is the full filename of the gerber file.
* @return True if RS274 file, false otherwise
*/
static bool TestFileIsRS274( const wxString& aFullFileName );
/**
* Read and load a gerber file.
*

View File

@ -110,6 +110,122 @@ bool GERBVIEW_FRAME::Read_GERBER_File( const wxString& GERBER_FullFileName )
}
/*
* Original function derived from gerber_is_rs274x_p() of gerbv 2.7.0.
* Copyright of the source file readgerb.cpp included below:
*/
/* gEDA - GNU Electronic Design Automation
* This is a part of gerbv
*
* Copyright (C) 2000-2003 Stefan Petersen (spe@stacken.kth.se)
*
* $Id$
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
*/
bool GERBER_FILE_IMAGE::TestFileIsRS274( const wxString& aFullFileName )
{
char* letter;
bool foundADD = false;
bool foundD0 = false;
bool foundD2 = false;
bool foundM0 = false;
bool foundM2 = false;
bool foundStar = false;
bool foundX = false;
bool foundY = false;
FILE* file = wxFopen( aFullFileName, "rb" );
if( file == nullptr )
return false;
FILE_LINE_READER gerberReader( aFullFileName );
try
{
while( true )
{
if( gerberReader.ReadLine() == nullptr )
break;
// Remove all whitespace from the beginning and end
char* line = StrPurge( gerberReader.Line() );
// Skip empty lines
if( *line == 0 )
continue;
// Check that file is not binary (non-printing chars)
for( size_t i = 0; i < strlen( line ); i++ )
{
if( !isascii( line[i] ) )
return false;
}
if( strstr( line, "%ADD" ) )
foundADD = true;
if( strstr( line, "D00" ) || strstr( line, "D0" ) )
foundD0 = true;
if( strstr( line, "D02" ) || strstr( line, "D2" ) )
foundD2 = true;
if( strstr( line, "M00" ) || strstr( line, "M0" ) )
foundM0 = true;
if( strstr( line, "M02" ) || strstr( line, "M2" ) )
foundM2 = true;
if( strstr( line, "*" ) )
foundStar = true;
/* look for X<number> or Y<number> */
if( ( letter = strstr( line, "X" ) ) != nullptr )
{
if( isdigit( letter[1] ) )
foundX = true;
}
if( ( letter = strstr( line, "Y" ) ) != nullptr )
{
if( isdigit( letter[1] ) )
foundY = true;
}
}
}
catch( IO_ERROR& e )
{
return false;
}
// RS-274X
if( ( foundD0 || foundD2 || foundM0 || foundM2 ) && foundADD && foundStar
&& ( foundX || foundY ) )
return true;
// RS-274D. Could be folded into the expression above, but someday
// we might want to test for them separately.
else if( ( foundD0 || foundD2 || foundM0 || foundM2 ) && !foundADD && foundStar
&& ( foundX || foundY ) )
return true;
return false;
}
// size of a single line of text from a gerber file.
// warning: some files can have *very long* lines, so the buffer must be large.