329 lines
9.6 KiB
C++
329 lines
9.6 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2010-2014 Jean-Pierre Charras jp.charras at wanadoo.fr
|
|
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* 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, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <math/util.h> // for KiROUND
|
|
|
|
#include <gerber_file_image.h>
|
|
#include <base_units.h>
|
|
|
|
|
|
/* These routines read the text string point from Text.
|
|
* On exit, Text points the beginning of the sequence unread
|
|
*/
|
|
|
|
// conversion scale from gerber file units to Gerbview internal units
|
|
// depending on the gerber file format
|
|
// this scale list assumes gerber units are imperial.
|
|
// for metric gerber units, the imperial to metric conversion is made in read functions
|
|
#define SCALE_LIST_SIZE 9
|
|
static double scale_list[SCALE_LIST_SIZE] =
|
|
{
|
|
1000.0 * GERB_IU_PER_MM * 0.0254, // x.1 format (certainly useless)
|
|
100.0 * GERB_IU_PER_MM * 0.0254, // x.2 format (certainly useless)
|
|
10.0 * GERB_IU_PER_MM * 0.0254, // x.3 format
|
|
1.0 * GERB_IU_PER_MM * 0.0254, // x.4 format
|
|
0.1 * GERB_IU_PER_MM * 0.0254, // x.5 format
|
|
0.01 * GERB_IU_PER_MM * 0.0254, // x.6 format
|
|
0.001 * GERB_IU_PER_MM * 0.0254, // x.7 format (currently the max allowed precision)
|
|
0.0001 * GERB_IU_PER_MM * 0.0254, // provided, but not used
|
|
0.00001 * GERB_IU_PER_MM * 0.0254, // provided, but not used
|
|
};
|
|
|
|
|
|
/**
|
|
* Convert a coordinate given in floating point to GerbView's internal units
|
|
* (currently = 10 nanometers).
|
|
*/
|
|
int scaletoIU( double aCoord, bool isMetric )
|
|
{
|
|
int ret;
|
|
|
|
if( isMetric ) // gerber are units in mm
|
|
ret = KiROUND( aCoord * GERB_IU_PER_MM );
|
|
else // gerber are units in inches
|
|
ret = KiROUND( aCoord * GERB_IU_PER_MM * 25.4 );
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// An useful function used when reading gerber files
|
|
static bool IsNumber( char x )
|
|
{
|
|
return ( ( x >= '0' ) && ( x <='9' ) )
|
|
|| ( x == '-' ) || ( x == '+' ) || ( x == '.' );
|
|
}
|
|
|
|
|
|
VECTOR2I GERBER_FILE_IMAGE::ReadXYCoord( char*& aText, bool aExcellonMode )
|
|
{
|
|
VECTOR2I pos( 0, 0 );
|
|
bool is_float = false;
|
|
|
|
std::string line;
|
|
|
|
// Reserve the anticipated length plus an optional sign and decimal
|
|
line.reserve( std::max( m_FmtLen.x, m_FmtLen.y ) + 3 );
|
|
|
|
// Set up return value for case where aText == nullptr
|
|
if( !m_Relative )
|
|
pos = m_CurrentPos;
|
|
|
|
if( aText == nullptr )
|
|
return pos;
|
|
|
|
while( *aText && ( ( *aText == 'X' ) || ( *aText == 'Y' ) || ( *aText == 'A' ) ) )
|
|
{
|
|
double decimal_scale = 1.0;
|
|
int nbdigits = 0;
|
|
int current_coord = 0;
|
|
char type_coord = *aText++;
|
|
|
|
line.clear();
|
|
|
|
while( IsNumber( *aText ) )
|
|
{
|
|
if( *aText == '.' ) // Force decimal format if reading a floating point number
|
|
is_float = true;
|
|
|
|
// count digits only (sign and decimal point are not counted)
|
|
if( (*aText >= '0') && (*aText <='9') )
|
|
nbdigits++;
|
|
|
|
line.push_back( *( aText++ ) );
|
|
}
|
|
|
|
double val = strtod( line.data(), nullptr );
|
|
|
|
if( is_float )
|
|
{
|
|
current_coord = scaletoIU( val, m_GerbMetric );
|
|
}
|
|
else
|
|
{
|
|
int fmt_scale = (type_coord == 'X') ? m_FmtScale.x : m_FmtScale.y;
|
|
|
|
if( m_NoTrailingZeros )
|
|
{
|
|
// no trailing zero format, we need to add missing zeros.
|
|
int digit_count = (type_coord == 'X') ? m_FmtLen.x : m_FmtLen.y;
|
|
|
|
// Truncate the extra digits if the len is more than expected
|
|
// because the conversion to internal units expect exactly
|
|
// digit_count digits. Alternatively, add some additional digits
|
|
// to pad out to the missing zeros
|
|
if( nbdigits < digit_count || ( aExcellonMode && ( nbdigits > digit_count ) ) )
|
|
decimal_scale = std::pow<double>( 10, digit_count - nbdigits );
|
|
}
|
|
|
|
double real_scale = scale_list[fmt_scale];
|
|
|
|
if( m_GerbMetric )
|
|
real_scale = real_scale / 25.4;
|
|
|
|
current_coord = KiROUND( val * real_scale * decimal_scale );
|
|
}
|
|
|
|
if( type_coord == 'X' )
|
|
{
|
|
pos.x = current_coord;
|
|
}
|
|
else if( type_coord == 'Y' )
|
|
{
|
|
pos.y = current_coord;
|
|
}
|
|
else if( type_coord == 'A' )
|
|
{
|
|
m_ArcRadius = current_coord;
|
|
m_LastArcDataType = ARC_INFO_TYPE_RADIUS;
|
|
}
|
|
}
|
|
|
|
if( m_Relative )
|
|
pos += m_CurrentPos;
|
|
|
|
m_CurrentPos = pos;
|
|
return pos;
|
|
}
|
|
|
|
|
|
VECTOR2I GERBER_FILE_IMAGE::ReadIJCoord( char*& aText )
|
|
{
|
|
VECTOR2I pos( 0, 0 );
|
|
bool is_float = false;
|
|
|
|
std::string line;
|
|
|
|
// Reserve the anticipated length plus an optional sign and decimal
|
|
line.reserve( std::max( m_FmtLen.x, m_FmtLen.y ) + 3 );
|
|
|
|
if( aText == nullptr )
|
|
return pos;
|
|
|
|
while( *aText && ( ( *aText == 'I' ) || ( *aText == 'J' ) ) )
|
|
{
|
|
double decimal_scale = 1.0;
|
|
int nbdigits = 0;
|
|
int current_coord = 0;
|
|
char type_coord = *aText++;
|
|
|
|
line.clear();
|
|
|
|
while( IsNumber( *aText ) )
|
|
{
|
|
if( *aText == '.' ) // Force decimal format if reading a floating point number
|
|
is_float = true;
|
|
|
|
// count digits only (sign and decimal point are not counted)
|
|
if( (*aText >= '0') && (*aText <='9') )
|
|
nbdigits++;
|
|
|
|
line.push_back( *( aText++ ) );
|
|
}
|
|
|
|
double val = strtod( line.data(), nullptr );
|
|
|
|
if( is_float )
|
|
{
|
|
current_coord = scaletoIU( val, m_GerbMetric );
|
|
}
|
|
else
|
|
{
|
|
int fmt_scale = ( type_coord == 'I' ) ? m_FmtScale.x : m_FmtScale.y;
|
|
|
|
if( m_NoTrailingZeros )
|
|
{
|
|
// no trailing zero format, we need to add missing zeros.
|
|
int digit_count = ( type_coord == 'I' ) ? m_FmtLen.x : m_FmtLen.y;
|
|
|
|
// Truncate the extra digits if the len is more than expected
|
|
// because the conversion to internal units expect exactly
|
|
// digit_count digits. Alternatively, add some additional digits
|
|
// to pad out to the missing zeros
|
|
if( nbdigits < digit_count )
|
|
decimal_scale = std::pow<double>( 10, digit_count - nbdigits );
|
|
}
|
|
|
|
double real_scale = scale_list[fmt_scale];
|
|
|
|
if( m_GerbMetric )
|
|
real_scale = real_scale / 25.4;
|
|
|
|
current_coord = KiROUND( val * real_scale * decimal_scale );
|
|
}
|
|
|
|
if( type_coord == 'I' )
|
|
{
|
|
pos.x = current_coord;
|
|
}
|
|
else if( type_coord == 'J' )
|
|
{
|
|
pos.y = current_coord;
|
|
}
|
|
}
|
|
|
|
m_IJPos = pos;
|
|
m_LastArcDataType = ARC_INFO_TYPE_CENTER;
|
|
m_LastCoordIsIJPos = true;
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
// Helper functions:
|
|
|
|
/**
|
|
* Read an integer from an ASCII character buffer.
|
|
*
|
|
* If there is a comma after the integer, then skip over that.
|
|
*
|
|
* @param text is a reference to a character pointer from which bytes are read
|
|
* and the pointer is advanced for each byte read.
|
|
* @param aSkipSeparator set to true (default) to skip comma.
|
|
* @return The integer read in.
|
|
*/
|
|
int ReadInt( char*& text, bool aSkipSeparator = true )
|
|
{
|
|
int ret;
|
|
|
|
// For strtol, a string starting by 0X or 0x is a valid number in hexadecimal or octal.
|
|
// However, 'X' is a separator in Gerber strings with numbers.
|
|
// We need to detect that
|
|
if( strncasecmp( text, "0X", 2 ) == 0 )
|
|
{
|
|
text++;
|
|
ret = 0;
|
|
}
|
|
else
|
|
{
|
|
ret = (int) strtol( text, &text, 10 );
|
|
}
|
|
|
|
if( *text == ',' || isspace( *text ) )
|
|
{
|
|
if( aSkipSeparator )
|
|
++text;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Read a double precision floating point number from an ASCII character buffer.
|
|
*
|
|
* If there is a comma after the number, then skip over that.
|
|
*
|
|
* @param text is a reference to a character pointer from which the ASCII double
|
|
* is read from and the pointer advanced for each character read.
|
|
* @param aSkipSeparator set to true (default) to skip comma.
|
|
* @return number read.
|
|
*/
|
|
double ReadDouble( char*& text, bool aSkipSeparator = true )
|
|
{
|
|
double ret;
|
|
|
|
// For strtod, a string starting by 0X or 0x is a valid number in hexadecimal or octal.
|
|
// However, 'X' is a separator in Gerber strings with numbers.
|
|
// We need to detect that
|
|
if( strncasecmp( text, "0X", 2 ) == 0 )
|
|
{
|
|
text++;
|
|
ret = 0.0;
|
|
}
|
|
else
|
|
{
|
|
ret = strtod( text, &text );
|
|
}
|
|
|
|
if( *text == ',' || isspace( *text ) )
|
|
{
|
|
if( aSkipSeparator )
|
|
++text;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|