kicad/gerbview/rs274x.cpp

1158 lines
35 KiB
C++
Raw Normal View History

2014-10-21 18:36:45 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
2018-05-13 16:44:26 +00:00
* Copyright (C) 2007-2018 Jean-Pierre Charras jp.charras at wanadoo.fr
* Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
2014-10-21 18:36:45 +00:00
*
* 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
*/
/**
* @file rs274x.cpp
*/
#include <base_units.h>
#include <math/util.h> // for KiROUND
#include <gerbview.h>
#include <gerber_file_image.h>
#include <ignore.h>
2020-10-24 14:45:37 +00:00
#include <macros.h>
#include <X2_gerber_attributes.h>
#include <gbr_metadata.h>
2010-11-30 20:41:35 +00:00
extern int ReadInt( char*& text, bool aSkipSeparator = true );
extern double ReadDouble( char*& text, bool aSkipSeparator = true );
2010-11-30 20:41:35 +00:00
#define CODE( x, y ) ( ( (x) << 8 ) + (y) )
// See rs274xrevd_e.pdf, table 1: RS-274X parameters order of entry
// in gerber files, when a coordinate is given (like X78Y600 or I0J80):
// Y and Y are logical coordinates
// A and B are plotter coordinates
// Usually A = X, B = Y
// But we can have A = Y, B = X and/or offset, mirror, scale;
// Also:
// Image is what you must plot (the entire data of the file).
// Layer is just a set of data blocks with their parameters. An image can have more than one
// layer so a gerber layer is not like a board layer or the graphic layers used in GerbView
// to show a file.
enum RS274X_PARAMETERS {
// Directive parameters: single usage recommended
// Must be at the beginning of the file
AXIS_SELECT = CODE( 'A', 'S' ), // Default: A=X, B=Y
FORMAT_STATEMENT = CODE( 'F', 'S' ), // no default: this command must exists
MIRROR_IMAGE = CODE( 'M', 'I' ), // Default: mo mirror
MODE_OF_UNITS = CODE( 'M', 'O' ), // Default: inch
INCH = CODE( 'I', 'N' ),
MILLIMETER = CODE( 'M', 'M' ),
OFFSET = CODE( 'O', 'F' ), // Default: A = 0, B = 0
SCALE_FACTOR = CODE( 'S', 'F' ), // Default: A = 1.0, B = 1.0
// Image parameters:
// commands used only once at the beginning of the file, and are deprecated
IMAGE_JUSTIFY = CODE( 'I', 'J' ), // Default: no justification
IMAGE_NAME = CODE( 'I', 'N' ), // Default: void
IMAGE_OFFSET = CODE( 'I', 'O' ), // Default: A = 0, B = 0
IMAGE_POLARITY = CODE( 'I', 'P' ), // Default: Positive
IMAGE_ROTATION = CODE( 'I', 'R' ), // Default: 0
// Aperture parameters:
// Usually for the whole file
AP_DEFINITION = CODE( 'A', 'D' ),
AP_MACRO = CODE( 'A', 'M' ),
// X2 extension attribute commands
// Mainly are found standard attributes and user attributes
// standard attributes commands are:
// TF (file attribute) TO (net attribute)
// TA (aperture attribute) and TD (delete aperture attribute)
FILE_ATTRIBUTE = CODE( 'T', 'F' ),
// X2 extension Net attribute info
// Net attribute options are:
// TO (net attribute data): TO.CN or TO.P TO.N or TO.C
NET_ATTRIBUTE = CODE( 'T', 'O' ),
// X2 extension Aperture attribute TA
APERTURE_ATTRIBUTE = CODE( 'T', 'A' ),
// TD (delete aperture/object attribute):
// Delete aperture attribute added by %TA or Oblect attribute added b %TO
// TD (delete all) or %TD<attr name> to delete <attr name>.
// eg: TD.P or TD.N or TD.C ...
REMOVE_APERTURE_ATTRIBUTE = CODE( 'T', 'D' ),
// Layer specific parameters
// May be used singly or may be layer specific
// These parameters are at the beginning of the file or layer
2010-10-17 16:42:06 +00:00
// and reset some layer parameters (like interpolation)
KNOCKOUT = CODE( 'K', 'O' ), // Default: off
STEP_AND_REPEAT = CODE( 'S', 'R' ), // Default: A = 1, B = 1
ROTATE = CODE( 'R', 'O' ), // Default: 0
LOAD_POLARITY = CODE( 'L', 'P' ), //LPC or LPD. Default: Dark (LPD)
LOAD_NAME = CODE( 'L', 'N' ), // Deprecated: equivalent to G04
};
int GERBER_FILE_IMAGE::ReadXCommandID( char*& text )
{
/* reads two bytes of data and assembles them into an int with the first
* byte in the sequence put into the most significant part of a 16 bit value
*/
2007-09-25 15:10:01 +00:00
int result;
int currbyte;
2007-09-25 15:10:01 +00:00
if( text && *text )
{
currbyte = *text++;
result = ( currbyte & 0xFF ) << 8;
}
2007-09-25 15:10:01 +00:00
else
return -1;
2008-11-07 07:55:28 +00:00
2007-09-25 15:10:01 +00:00
if( text && *text )
{
currbyte = *text++;
result += currbyte & 0xFF;
}
2007-09-25 15:10:01 +00:00
else
return -1;
2008-11-07 07:55:28 +00:00
2007-09-25 15:10:01 +00:00
return result;
}
bool GERBER_FILE_IMAGE::ReadRS274XCommand( char *aBuff, unsigned int aBuffSize, char*& aText )
{
2007-09-25 15:10:01 +00:00
bool ok = true;
int code_command;
aText++;
2007-09-25 15:10:01 +00:00
for( ; ; )
2007-09-25 15:10:01 +00:00
{
while( *aText )
2007-09-25 15:10:01 +00:00
{
switch( *aText )
2007-09-25 15:10:01 +00:00
{
2008-11-08 06:44:07 +00:00
case '%': // end of command
aText++;
2007-09-25 15:10:01 +00:00
m_CommandState = CMD_IDLE;
goto exit; // success completion
case ' ':
case '\r':
case '\n':
aText++;
2007-09-25 15:10:01 +00:00
break;
case '*':
aText++;
2007-09-25 15:10:01 +00:00
break;
default:
code_command = ReadXCommandID( aText );
ok = ExecuteRS274XCommand( code_command, aBuff, aBuffSize, aText );
2007-09-25 15:10:01 +00:00
if( !ok )
goto exit;
2007-09-25 15:10:01 +00:00
break;
}
}
2008-11-08 06:44:07 +00:00
// end of current line, read another one.
if( fgets( aBuff, aBuffSize, m_Current_File ) == nullptr )
2007-09-25 15:10:01 +00:00
{
2008-11-08 06:44:07 +00:00
// end of file
2007-09-25 15:10:01 +00:00
ok = false;
break;
}
m_LineNum++;
aText = aBuff;
2007-09-25 15:10:01 +00:00
}
exit:
return ok;
}
bool GERBER_FILE_IMAGE::ExecuteRS274XCommand( int aCommand, char* aBuff,
unsigned int aBuffSize, char*& aText )
{
2007-09-25 15:10:01 +00:00
int code;
2013-03-06 19:32:59 +00:00
int seq_len; // not used, just provided
int seq_char;
bool ok = true;
2007-09-25 15:10:01 +00:00
wxString msg;
double fcoord;
2013-03-06 19:32:59 +00:00
bool x_fmt_known = false;
bool y_fmt_known = false;
// conv_scale = scaling factor from inch to Internal Unit
double conv_scale = IU_PER_MILS * 1000;
if( m_GerbMetric )
conv_scale /= 25.4;
2007-09-25 15:10:01 +00:00
switch( aCommand )
2007-09-25 15:10:01 +00:00
{
2008-11-07 07:55:28 +00:00
case FORMAT_STATEMENT:
2013-03-06 19:32:59 +00:00
seq_len = 2;
2008-11-07 07:55:28 +00:00
while( *aText != '*' )
2007-09-25 15:10:01 +00:00
{
switch( *aText )
2007-09-25 15:10:01 +00:00
{
case ' ':
aText++;
2007-09-25 15:10:01 +00:00
break;
case 'D': // Non-standard option for all zeros (leading + tailing)
msg.Printf( _( "RS274X: Invalid GERBER format command '%c' at line %d: \"%s\"" ),
'D', m_LineNum, aBuff );
AddMessageToList( msg );
msg.Printf( _("GERBER file \"%s\" may not display as intended." ),
m_FileName.ToAscii() );
AddMessageToList( msg );
KI_FALLTHROUGH;
2007-09-25 15:10:01 +00:00
case 'L': // No Leading 0
m_NoTrailingZeros = false;
aText++;
2007-09-25 15:10:01 +00:00
break;
case 'T': // No trailing 0
m_NoTrailingZeros = true;
aText++;
break;
case 'A': // Absolute coord
m_Relative = false;
aText++;
2007-09-25 15:10:01 +00:00
break;
case 'I': // Relative coord
m_Relative = true;
aText++;
2007-09-25 15:10:01 +00:00
break;
2013-03-06 19:32:59 +00:00
case 'G':
case 'N': // Sequence code (followed by one digit: the sequence len)
// (sometimes found before the X,Y sequence)
// Obscure option
aText++;
seq_char = *aText++;
2020-09-21 11:15:03 +00:00
2013-03-06 19:32:59 +00:00
if( (seq_char >= '0') && (seq_char <= '9') )
seq_len = seq_char - '0';
2020-09-21 11:15:03 +00:00
2013-03-06 19:32:59 +00:00
break;
case 'M': // Sequence code (followed by one digit: the sequence len)
// (sometimes found after the X,Y sequence)
// Obscure option
aText++;
code = *aText;
2020-09-21 11:15:03 +00:00
if( ( code >= '0' ) && ( code <= '9' ) )
aText++; // skip the digit
2020-09-21 11:15:03 +00:00
2007-09-25 15:10:01 +00:00
break;
case 'X':
2013-03-06 19:32:59 +00:00
case 'Y':
{
code = *(aText++);
char ctmp = *(aText++) - '0';
2020-09-21 11:15:03 +00:00
if( code == 'X' )
2007-09-25 15:10:01 +00:00
{
2013-03-06 19:32:59 +00:00
x_fmt_known = true;
// number of digits after the decimal point (0 to 7 allowed)
m_FmtScale.x = *aText - '0';
m_FmtLen.x = ctmp + m_FmtScale.x;
// m_FmtScale is 0 to 7
// (Old Gerber specification was 0 to 6)
if( m_FmtScale.x < 0 )
m_FmtScale.x = 0;
2020-09-21 11:15:03 +00:00
if( m_FmtScale.x > 7 )
m_FmtScale.x = 7;
2007-09-25 15:10:01 +00:00
}
else
{
2013-03-06 19:32:59 +00:00
y_fmt_known = true;
m_FmtScale.y = *aText - '0';
m_FmtLen.y = ctmp + m_FmtScale.y;
2020-09-21 11:15:03 +00:00
if( m_FmtScale.y < 0 )
m_FmtScale.y = 0;
2020-09-21 11:15:03 +00:00
if( m_FmtScale.y > 7 )
m_FmtScale.y = 7;
}
2020-09-21 11:15:03 +00:00
aText++;
}
break;
2007-09-25 15:10:01 +00:00
case '*':
break;
default:
2013-03-06 19:32:59 +00:00
msg.Printf( wxT( "Unknown id (%c) in FS command" ),
*aText );
AddMessageToList( msg );
GetEndOfBlock( aBuff, aBuffSize, aText, m_Current_File );
ok = false;
2007-09-25 15:10:01 +00:00
break;
}
}
2020-09-21 11:15:03 +00:00
2013-03-06 19:32:59 +00:00
if( !x_fmt_known || !y_fmt_known )
AddMessageToList( wxT( "RS274X: Format Statement (FS) without X or Y format" ) );
2007-09-25 15:10:01 +00:00
break;
case AXIS_SELECT: // command ASAXBY*% or %ASAYBX*%
m_SwapAxis = false;
2020-09-21 11:15:03 +00:00
if( strncasecmp( aText, "AYBX", 4 ) == 0 )
m_SwapAxis = true;
2020-09-21 11:15:03 +00:00
break;
case MIRROR_IMAGE: // command %MIA0B0*%, %MIA0B1*%, %MIA1B0*%, %MIA1B1*%
m_MirrorA = m_MirrorB = false;
2020-09-21 11:15:03 +00:00
while( *aText && *aText != '*' )
{
switch( *aText )
{
case 'A': // Mirror A axis ?
aText++;
2020-09-21 11:15:03 +00:00
if( *aText == '1' )
m_MirrorA = true;
2020-09-21 11:15:03 +00:00
break;
case 'B': // Mirror B axis ?
aText++;
2020-09-21 11:15:03 +00:00
if( *aText == '1' )
m_MirrorB = true;
2020-09-21 11:15:03 +00:00
break;
default:
aText++;
break;
}
}
2007-09-25 15:10:01 +00:00
break;
case MODE_OF_UNITS:
code = ReadXCommandID( aText );
2020-09-21 11:15:03 +00:00
2007-09-25 15:10:01 +00:00
if( code == INCH )
m_GerbMetric = false;
2007-09-25 15:10:01 +00:00
else if( code == MILLIMETER )
m_GerbMetric = true;
2020-09-21 11:15:03 +00:00
conv_scale = m_GerbMetric ? IU_PER_MILS / 25.4 : IU_PER_MILS;
2007-09-25 15:10:01 +00:00
break;
case FILE_ATTRIBUTE: // Command %TF ...
2020-09-21 11:15:03 +00:00
{
X2_ATTRIBUTE dummy;
dummy.ParseAttribCmd( m_Current_File, aBuff, aBuffSize, aText, m_LineNum );
if( dummy.IsFileFunction() )
{
delete m_FileFunction;
m_FileFunction = new X2_ATTRIBUTE_FILEFUNCTION( dummy );
// Don't set this until we get a file function; other code expects m_IsX2_file == true
// to mean that we have a valid m_FileFunction
m_IsX2_file = true;
}
else if( dummy.IsFileMD5() )
{
m_MD5_value = dummy.GetPrm( 1 );
}
else if( dummy.IsFilePart() )
{
m_PartString = dummy.GetPrm( 1 );
}
2020-09-21 11:15:03 +00:00
}
break;
case APERTURE_ATTRIBUTE: // Command %TA
2020-09-21 11:15:03 +00:00
{
X2_ATTRIBUTE dummy;
dummy.ParseAttribCmd( m_Current_File, aBuff, aBuffSize, aText, m_LineNum );
if( dummy.GetAttribute() == ".AperFunction" )
{
m_AperFunction = dummy.GetPrm( 1 );
// A few function values can have other parameters. Add them
for( int ii = 2; ii < dummy.GetPrmCount(); ii++ )
m_AperFunction << "," << dummy.GetPrm( ii );
}
2020-09-21 11:15:03 +00:00
}
break;
case NET_ATTRIBUTE: // Command %TO currently %TO.P %TO.N and %TO.C
2020-09-21 11:15:03 +00:00
{
X2_ATTRIBUTE dummy;
dummy.ParseAttribCmd( m_Current_File, aBuff, aBuffSize, aText, m_LineNum );
if( dummy.GetAttribute() == ".N" )
{
m_NetAttributeDict.m_NetAttribType |= GBR_NETLIST_METADATA::GBR_NETINFO_NET;
m_NetAttributeDict.m_Netname = FormatStringFromGerber( dummy.GetPrm( 1 ) );
}
else if( dummy.GetAttribute() == ".C" )
{
m_NetAttributeDict.m_NetAttribType |= GBR_NETLIST_METADATA::GBR_NETINFO_CMP;
m_NetAttributeDict.m_Cmpref = FormatStringFromGerber( dummy.GetPrm( 1 ) );
}
else if( dummy.GetAttribute() == ".P" )
{
m_NetAttributeDict.m_NetAttribType |= GBR_NETLIST_METADATA::GBR_NETINFO_PAD;
m_NetAttributeDict.m_Cmpref = FormatStringFromGerber( dummy.GetPrm( 1 ) );
m_NetAttributeDict.m_Padname.SetField( FormatStringFromGerber( dummy.GetPrm( 2 ) ), true, true );
if( dummy.GetPrmCount() > 3 )
2020-09-21 11:15:03 +00:00
{
m_NetAttributeDict.m_PadPinFunction.SetField(
FormatStringFromGerber( dummy.GetPrm( 3 ) ), true, true );
2020-09-21 11:15:03 +00:00
}
else
2020-09-21 11:15:03 +00:00
{
m_NetAttributeDict.m_PadPinFunction.Clear();
2020-09-21 11:15:03 +00:00
}
}
2020-09-21 11:15:03 +00:00
}
break;
case REMOVE_APERTURE_ATTRIBUTE: // Command %TD ...
2020-09-21 11:15:03 +00:00
{
X2_ATTRIBUTE dummy;
dummy.ParseAttribCmd( m_Current_File, aBuff, aBuffSize, aText, m_LineNum );
RemoveAttribute( dummy );
2020-09-21 11:15:03 +00:00
}
break;
case OFFSET: // command: OFAnnBnn (nn = float number) = layer Offset
2007-09-25 15:10:01 +00:00
m_Offset.x = m_Offset.y = 0;
while( *aText != '*' )
2007-09-25 15:10:01 +00:00
{
switch( *aText )
2007-09-25 15:10:01 +00:00
{
case 'A': // A axis offset in current unit (inch or mm)
aText++;
fcoord = ReadDouble( aText );
// Dick Hollenbeck's KiROUND R&D // This provides better project control over rounding to int from double // than wxRound() did. This scheme provides better logging in Debug builds // and it provides for compile time calculation of constants. #include <stdio.h> #include <assert.h> #include <limits.h> //-----<KiROUND KIT>------------------------------------------------------------ /** * KiROUND * rounds a floating point number to an int using * "round halfway cases away from zero". * In Debug build an assert fires if will not fit into an int. */ #if defined( DEBUG ) // DEBUG: a macro to capture line and file, then calls this inline static inline int KiRound( double v, int line, const char* filename ) { v = v < 0 ? v - 0.5 : v + 0.5; if( v > INT_MAX + 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' > 0 ' for int\n", __FUNCTION__, filename, line, v ); } else if( v < INT_MIN - 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' < 0 ' for int\n", __FUNCTION__, filename, line, v ); } return int( v ); } #define KiROUND( v ) KiRound( v, __LINE__, __FILE__ ) #else // RELEASE: a macro so compile can pre-compute constants. #define KiROUND( v ) int( (v) < 0 ? (v) - 0.5 : (v) + 0.5 ) #endif //-----</KiROUND KIT>----------------------------------------------------------- // Only a macro is compile time calculated, an inline function causes a static constructor // in a situation like this. // Therefore the Release build is best done with a MACRO not an inline function. int Computed = KiROUND( 14.3 * 8 ); int main( int argc, char** argv ) { for( double d = double(INT_MAX)-1; d < double(INT_MAX)+8; d += 2.0 ) { int i = KiROUND( d ); printf( "t: %d %.16g\n", i, d ); } return 0; }
2012-04-19 06:55:45 +00:00
m_Offset.x = KiROUND( fcoord * conv_scale );
2007-09-25 15:10:01 +00:00
break;
case 'B': // B axis offset in current unit (inch or mm)
aText++;
fcoord = ReadDouble( aText );
// Dick Hollenbeck's KiROUND R&D // This provides better project control over rounding to int from double // than wxRound() did. This scheme provides better logging in Debug builds // and it provides for compile time calculation of constants. #include <stdio.h> #include <assert.h> #include <limits.h> //-----<KiROUND KIT>------------------------------------------------------------ /** * KiROUND * rounds a floating point number to an int using * "round halfway cases away from zero". * In Debug build an assert fires if will not fit into an int. */ #if defined( DEBUG ) // DEBUG: a macro to capture line and file, then calls this inline static inline int KiRound( double v, int line, const char* filename ) { v = v < 0 ? v - 0.5 : v + 0.5; if( v > INT_MAX + 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' > 0 ' for int\n", __FUNCTION__, filename, line, v ); } else if( v < INT_MIN - 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' < 0 ' for int\n", __FUNCTION__, filename, line, v ); } return int( v ); } #define KiROUND( v ) KiRound( v, __LINE__, __FILE__ ) #else // RELEASE: a macro so compile can pre-compute constants. #define KiROUND( v ) int( (v) < 0 ? (v) - 0.5 : (v) + 0.5 ) #endif //-----</KiROUND KIT>----------------------------------------------------------- // Only a macro is compile time calculated, an inline function causes a static constructor // in a situation like this. // Therefore the Release build is best done with a MACRO not an inline function. int Computed = KiROUND( 14.3 * 8 ); int main( int argc, char** argv ) { for( double d = double(INT_MAX)-1; d < double(INT_MAX)+8; d += 2.0 ) { int i = KiROUND( d ); printf( "t: %d %.16g\n", i, d ); } return 0; }
2012-04-19 06:55:45 +00:00
m_Offset.y = KiROUND( fcoord * conv_scale );
2007-09-25 15:10:01 +00:00
break;
}
}
break;
case SCALE_FACTOR:
m_Scale.x = m_Scale.y = 1.0;
while( *aText != '*' )
{
switch( *aText )
{
case 'A': // A axis scale
aText++;
m_Scale.x = ReadDouble( aText );
break;
case 'B': // B axis scale
aText++;
m_Scale.y = ReadDouble( aText );
break;
}
}
break;
case IMAGE_OFFSET: // command: IOAnnBnn (nn = float number) = Image Offset
m_ImageOffset.x = m_ImageOffset.y = 0;
while( *aText != '*' )
{
switch( *aText )
{
case 'A': // A axis offset in current unit (inch or mm)
aText++;
fcoord = ReadDouble( aText );
// Dick Hollenbeck's KiROUND R&D // This provides better project control over rounding to int from double // than wxRound() did. This scheme provides better logging in Debug builds // and it provides for compile time calculation of constants. #include <stdio.h> #include <assert.h> #include <limits.h> //-----<KiROUND KIT>------------------------------------------------------------ /** * KiROUND * rounds a floating point number to an int using * "round halfway cases away from zero". * In Debug build an assert fires if will not fit into an int. */ #if defined( DEBUG ) // DEBUG: a macro to capture line and file, then calls this inline static inline int KiRound( double v, int line, const char* filename ) { v = v < 0 ? v - 0.5 : v + 0.5; if( v > INT_MAX + 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' > 0 ' for int\n", __FUNCTION__, filename, line, v ); } else if( v < INT_MIN - 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' < 0 ' for int\n", __FUNCTION__, filename, line, v ); } return int( v ); } #define KiROUND( v ) KiRound( v, __LINE__, __FILE__ ) #else // RELEASE: a macro so compile can pre-compute constants. #define KiROUND( v ) int( (v) < 0 ? (v) - 0.5 : (v) + 0.5 ) #endif //-----</KiROUND KIT>----------------------------------------------------------- // Only a macro is compile time calculated, an inline function causes a static constructor // in a situation like this. // Therefore the Release build is best done with a MACRO not an inline function. int Computed = KiROUND( 14.3 * 8 ); int main( int argc, char** argv ) { for( double d = double(INT_MAX)-1; d < double(INT_MAX)+8; d += 2.0 ) { int i = KiROUND( d ); printf( "t: %d %.16g\n", i, d ); } return 0; }
2012-04-19 06:55:45 +00:00
m_ImageOffset.x = KiROUND( fcoord * conv_scale );
break;
case 'B': // B axis offset in current unit (inch or mm)
aText++;
fcoord = ReadDouble( aText );
// Dick Hollenbeck's KiROUND R&D // This provides better project control over rounding to int from double // than wxRound() did. This scheme provides better logging in Debug builds // and it provides for compile time calculation of constants. #include <stdio.h> #include <assert.h> #include <limits.h> //-----<KiROUND KIT>------------------------------------------------------------ /** * KiROUND * rounds a floating point number to an int using * "round halfway cases away from zero". * In Debug build an assert fires if will not fit into an int. */ #if defined( DEBUG ) // DEBUG: a macro to capture line and file, then calls this inline static inline int KiRound( double v, int line, const char* filename ) { v = v < 0 ? v - 0.5 : v + 0.5; if( v > INT_MAX + 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' > 0 ' for int\n", __FUNCTION__, filename, line, v ); } else if( v < INT_MIN - 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' < 0 ' for int\n", __FUNCTION__, filename, line, v ); } return int( v ); } #define KiROUND( v ) KiRound( v, __LINE__, __FILE__ ) #else // RELEASE: a macro so compile can pre-compute constants. #define KiROUND( v ) int( (v) < 0 ? (v) - 0.5 : (v) + 0.5 ) #endif //-----</KiROUND KIT>----------------------------------------------------------- // Only a macro is compile time calculated, an inline function causes a static constructor // in a situation like this. // Therefore the Release build is best done with a MACRO not an inline function. int Computed = KiROUND( 14.3 * 8 ); int main( int argc, char** argv ) { for( double d = double(INT_MAX)-1; d < double(INT_MAX)+8; d += 2.0 ) { int i = KiROUND( d ); printf( "t: %d %.16g\n", i, d ); } return 0; }
2012-04-19 06:55:45 +00:00
m_ImageOffset.y = KiROUND( fcoord * conv_scale );
break;
}
}
break;
case IMAGE_ROTATION: // command IR0* or IR90* or IR180* or IR270*
if( strncasecmp( aText, "0*", 2 ) == 0 )
m_ImageRotation = 0;
else if( strncasecmp( aText, "90*", 3 ) == 0 )
2010-10-17 16:42:06 +00:00
m_ImageRotation = 90;
else if( strncasecmp( aText, "180*", 4 ) == 0 )
2010-10-17 16:42:06 +00:00
m_ImageRotation = 180;
else if( strncasecmp( aText, "270*", 4 ) == 0 )
2010-10-17 16:42:06 +00:00
m_ImageRotation = 270;
else
AddMessageToList( _( "RS274X: Command \"IR\" rotation value not allowed" ) );
2020-09-21 11:15:03 +00:00
break;
case STEP_AND_REPEAT: // command SR, like %SRX3Y2I5.0J2*%
2010-10-17 16:42:06 +00:00
m_Iterpolation = GERB_INTERPOL_LINEAR_1X; // Start a new Gerber layer
GetLayerParams().m_StepForRepeat.x = 0.0;
GetLayerParams().m_StepForRepeat.x = 0.0; // offset for Step and Repeat command
GetLayerParams().m_XRepeatCount = 1;
GetLayerParams().m_YRepeatCount = 1; // The repeat count
GetLayerParams().m_StepForRepeatMetric = m_GerbMetric; // the step units
2020-09-21 11:15:03 +00:00
while( *aText && *aText != '*' )
{
switch( *aText )
{
case 'I': // X axis offset
aText++;
GetLayerParams().m_StepForRepeat.x = ReadDouble( aText );
break;
case 'J': // Y axis offset
aText++;
GetLayerParams().m_StepForRepeat.y = ReadDouble( aText );
break;
case 'X': // X axis repeat count
aText++;
GetLayerParams().m_XRepeatCount = ReadInt( aText );
break;
case 'Y': // Y axis offset
aText++;
GetLayerParams().m_YRepeatCount = ReadInt( aText );
break;
2020-09-21 11:15:03 +00:00
default:
aText++;
break;
}
}
break;
case IMAGE_JUSTIFY: // Command IJAnBn*
2022-01-04 01:00:40 +00:00
m_ImageJustifyXCenter = false; // Image Justify Center on X axis (default = false)
m_ImageJustifyYCenter = false; // Image Justify Center on Y axis (default = false)
m_ImageJustifyOffset = VECTOR2I( 0, 0 ); // Image Justify Offset on XY axis (default = 0,0)
while( *aText && *aText != '*' )
{
// IJ command is (for A or B axis) AC or AL or A<coordinate>
switch( *aText )
{
case 'A': // A axis justify
aText++;
2020-09-21 11:15:03 +00:00
if( *aText == 'C' )
{
m_ImageJustifyXCenter = true;
aText++;
}
else if( *aText == 'L' )
{
m_ImageJustifyXCenter = true;
aText++;
}
2020-09-21 11:15:03 +00:00
else
{
m_ImageJustifyOffset.x = KiROUND( ReadDouble( aText ) * conv_scale);
}
break;
case 'B': // B axis justify
aText++;
2020-09-21 11:15:03 +00:00
if( *aText == 'C' )
{
m_ImageJustifyYCenter = true;
aText++;
}
else if( *aText == 'L' )
{
m_ImageJustifyYCenter = true;
aText++;
}
2020-09-21 11:15:03 +00:00
else
{
m_ImageJustifyOffset.y = KiROUND( ReadDouble( aText ) * conv_scale);
}
break;
2020-09-21 11:15:03 +00:00
default:
aText++;
break;
}
}
2020-09-21 11:15:03 +00:00
if( m_ImageJustifyXCenter )
m_ImageJustifyOffset.x = 0;
2020-09-21 11:15:03 +00:00
if( m_ImageJustifyYCenter )
m_ImageJustifyOffset.y = 0;
2020-09-21 11:15:03 +00:00
break;
2007-09-25 15:10:01 +00:00
case KNOCKOUT:
2010-10-17 16:42:06 +00:00
m_Iterpolation = GERB_INTERPOL_LINEAR_1X; // Start a new Gerber layer
msg = _( "RS274X: Command KNOCKOUT ignored by GerbView" ) ;
AddMessageToList( msg );
break;
case ROTATE: // Layer rotation: command like %RO45*%
2020-09-21 11:15:03 +00:00
m_Iterpolation = GERB_INTERPOL_LINEAR_1X; // Start a new Gerber layer
m_LocalRotation = ReadDouble( aText ); // Store layer rotation in degrees
break;
2007-09-25 15:10:01 +00:00
case IMAGE_NAME:
m_ImageName.Empty();
2020-09-21 11:15:03 +00:00
while( *aText != '*' )
m_ImageName.Append( *aText++ );
break;
2007-09-25 15:10:01 +00:00
case LOAD_NAME:
// %LN is a (deprecated) equivalentto G04: a comment
while( *aText && *aText != '*' )
aText++; // Skip text
2007-09-25 15:10:01 +00:00
break;
case IMAGE_POLARITY:
if( strncasecmp( aText, "NEG", 3 ) == 0 )
m_ImageNegative = true;
2007-09-25 15:10:01 +00:00
else
m_ImageNegative = false;
2007-09-25 15:10:01 +00:00
break;
case LOAD_POLARITY:
if( *aText == 'C' )
GetLayerParams().m_LayerNegative = true;
2007-09-25 15:10:01 +00:00
else
GetLayerParams().m_LayerNegative = false;
2016-05-26 11:57:43 +00:00
2007-09-25 15:10:01 +00:00
break;
case AP_MACRO: // lines like %AMMYMACRO*
// 5,1,8,0,0,1.08239X$1,22.5*
// %
/*ok = */ReadApertureMacro( aBuff, aBuffSize, aText, m_Current_File );
break;
2008-11-08 06:44:07 +00:00
case AP_DEFINITION:
/* input example: %ADD30R,0.081800X0.101500*%
* Aperture definition has 4 options: C, R, O, P
* (Circle, Rect, Oval, regular Polygon)
* and shapes can have a hole (round or rectangular).
* All optional parameters values start by X
* at this point, text points to 2nd 'D'
*/
if( *aText++ != 'D' )
2007-09-25 15:10:01 +00:00
{
ok = false;
2008-11-07 07:55:28 +00:00
break;
2007-09-25 15:10:01 +00:00
}
m_Has_DCode = true;
2008-11-07 07:55:28 +00:00
code = ReadInt( aText );
2008-11-07 07:55:28 +00:00
D_CODE* dcode;
dcode = GetDCODEOrCreate( code );
if( dcode == nullptr )
2007-09-25 15:10:01 +00:00
break;
2008-11-07 07:55:28 +00:00
dcode->m_AperFunction = m_AperFunction;
// at this point, text points to character after the ADD<num>,
// i.e. R in example above. If aText[0] is one of the usual
// apertures: (C,R,O,P), there is a comma after it.
if( aText[1] == ',' )
2007-09-25 15:10:01 +00:00
{
char stdAperture = *aText;
aText += 2; // skip "C," for example
// First parameter is the size X:
dcode->m_Size.x = KiROUND( ReadDouble( aText ) * conv_scale );
dcode->m_Size.y = dcode->m_Size.x;
2007-09-25 15:10:01 +00:00
switch( stdAperture ) // Aperture desceiption has optional parameters. Read them
2007-09-25 15:10:01 +00:00
{
case 'C': // Circle
2008-11-08 06:44:07 +00:00
dcode->m_Shape = APT_CIRCLE;
while( *aText == ' ' )
aText++;
2007-09-25 15:10:01 +00:00
if( *aText == 'X' )
2007-09-25 15:10:01 +00:00
{
aText++;
2007-09-25 15:10:01 +00:00
dcode->m_Drill.x = dcode->m_Drill.y =
KiROUND( ReadDouble( aText ) * conv_scale );
dcode->m_DrillShape = APT_DEF_ROUND_HOLE;
2007-09-25 15:10:01 +00:00
}
2008-11-07 07:55:28 +00:00
while( *aText == ' ' )
aText++;
2007-09-25 15:10:01 +00:00
if( *aText == 'X' )
2007-09-25 15:10:01 +00:00
{
aText++;
2007-09-25 15:10:01 +00:00
dcode->m_Drill.y =
KiROUND( ReadDouble( aText ) * conv_scale );
2008-11-07 07:55:28 +00:00
dcode->m_DrillShape = APT_DEF_RECT_HOLE;
2007-09-25 15:10:01 +00:00
}
dcode->m_Defined = true;
2007-09-25 15:10:01 +00:00
break;
2008-11-08 06:44:07 +00:00
case 'O': // oval
2007-09-25 15:10:01 +00:00
case 'R': // rect
dcode->m_Shape = (stdAperture == 'O') ? APT_OVAL : APT_RECT;
2008-11-07 07:55:28 +00:00
while( *aText == ' ' )
aText++;
2007-09-25 15:10:01 +00:00
if( *aText == 'X' ) // Second parameter: size Y
2007-09-25 15:10:01 +00:00
{
aText++;
2007-09-25 15:10:01 +00:00
dcode->m_Size.y =
KiROUND( ReadDouble( aText ) * conv_scale );
2007-09-25 15:10:01 +00:00
}
2008-11-07 07:55:28 +00:00
while( *aText == ' ' )
aText++;
2007-09-25 15:10:01 +00:00
if( *aText == 'X' ) // third parameter: drill size (or drill size X)
2007-09-25 15:10:01 +00:00
{
aText++;
dcode->m_Drill.x = KiROUND( ReadDouble( aText ) * conv_scale );
dcode->m_Drill.y = dcode->m_Drill.x;
dcode->m_DrillShape = APT_DEF_ROUND_HOLE;
2007-09-25 15:10:01 +00:00
}
2008-11-07 07:55:28 +00:00
while( *aText == ' ' )
aText++;
2007-09-25 15:10:01 +00:00
if( *aText == 'X' ) // fourth parameter: drill size Y
2007-09-25 15:10:01 +00:00
{
aText++;
2007-09-25 15:10:01 +00:00
dcode->m_Drill.y =
KiROUND( ReadDouble( aText ) * conv_scale );
dcode->m_DrillShape = APT_DEF_RECT_HOLE;
2007-09-25 15:10:01 +00:00
}
dcode->m_Defined = true;
2007-09-25 15:10:01 +00:00
break;
case 'P':
/* Regular polygon: a command line like %ADD12P,0.040X10X25X0.025X0.025X0.0150*%
* params are: <diameter>, X<edge count>, X<Rotation>, X<X hole dim>, X<Y hole dim>
*/
dcode->m_Shape = APT_POLYGON;
while( *aText == ' ' )
aText++;
if( *aText == 'X' )
{
aText++;
dcode->m_EdgesCount = ReadInt( aText );
}
while( *aText == ' ' )
aText++;
if( *aText == 'X' )
{
aText++;
dcode->m_Rotation = EDA_ANGLE( ReadDouble( aText ), DEGREES_T );
}
while( *aText == ' ' )
aText++;
if( *aText == 'X' )
{
aText++;
dcode->m_Drill.x = KiROUND( ReadDouble( aText ) * conv_scale );
dcode->m_Drill.y = dcode->m_Drill.x;
dcode->m_DrillShape = APT_DEF_ROUND_HOLE;
}
while( *aText == ' ' )
aText++;
if( *aText == 'X' )
{
aText++;
dcode->m_Drill.y = KiROUND( ReadDouble( aText ) * conv_scale );
dcode->m_DrillShape = APT_DEF_RECT_HOLE;
}
dcode->m_Defined = true;
2007-09-25 15:10:01 +00:00
break;
}
}
else // aText[0] starts an aperture macro name
{
APERTURE_MACRO am_lookup;
while( *aText && *aText != '*' && *aText != ',' )
am_lookup.name.Append( *aText++ );
// When an aperture definition is like %AMLINE2* 22,1,$1,$2,0,0,-45*
// the ADDxx<MACRO_NAME> command has parameters, like %ADD14LINE2,0.8X0.5*%
if( *aText == ',' )
{ // Read aperture macro parameters and store them
aText++; // aText points the first parameter
while( *aText && *aText != '*' )
{
double param = ReadDouble( aText );
dcode->AppendParam( param );
while( isspace( *aText ) )
aText++;
// Skip 'X' separator:
if( *aText == 'X' || *aText == 'x' )
aText++;
}
}
// lookup the aperture macro here.
APERTURE_MACRO* pam = FindApertureMacro( am_lookup );
if( !pam )
{
msg.Printf( wxT( "RS274X: aperture macro %s not found\n" ),
TO_UTF8( am_lookup.name ) );
AddMessageToList( msg );
ok = false;
break;
}
2008-11-14 07:46:43 +00:00
dcode->m_Shape = APT_MACRO;
dcode->SetMacro( pam );
dcode->m_Defined = true;
}
2007-09-25 15:10:01 +00:00
break;
default:
ok = false;
2007-09-25 15:10:01 +00:00
break;
}
ignore_unused( seq_len );
2013-04-18 17:03:47 +00:00
ok = GetEndOfBlock( aBuff, aBuffSize, aText, m_Current_File );
2008-11-07 07:55:28 +00:00
2007-09-25 15:10:01 +00:00
return ok;
}
2007-09-25 15:10:01 +00:00
bool GERBER_FILE_IMAGE::GetEndOfBlock( char* aBuff, unsigned int aBuffSize, char*& aText, FILE* gerber_file )
{
for( ; ; )
2007-09-25 15:10:01 +00:00
{
while( (aText < aBuff + aBuffSize) && *aText )
2007-09-25 15:10:01 +00:00
{
if( *aText == '*' )
return true;
2008-11-07 07:55:28 +00:00
if( *aText == '%' )
return true;
2008-11-07 07:55:28 +00:00
aText++;
2007-09-25 15:10:01 +00:00
}
if( fgets( aBuff, aBuffSize, gerber_file ) == nullptr )
2007-09-25 15:10:01 +00:00
break;
2008-11-07 07:55:28 +00:00
m_LineNum++;
aText = aBuff;
2007-09-25 15:10:01 +00:00
}
return false;
}
2013-04-18 17:03:47 +00:00
char* GERBER_FILE_IMAGE::GetNextLine( char *aBuff, unsigned int aBuffSize, char* aText, FILE* aFile )
{
for( ; ; )
{
switch (*aText )
{
case ' ': // skip blanks
case '\n':
case '\r': // Skip line terminators
++aText;
break;
case 0: // End of text found in aBuff: Read a new string
if( fgets( aBuff, aBuffSize, aFile ) == nullptr )
return nullptr;
m_LineNum++;
aText = aBuff;
return aText;
default:
return aText;
}
}
return aText;
}
bool GERBER_FILE_IMAGE::ReadApertureMacro( char *aBuff, unsigned int aBuffSize,
char*& aText,
FILE* gerber_file )
{
wxString msg;
2008-11-14 18:26:56 +00:00
APERTURE_MACRO am;
2007-09-25 15:10:01 +00:00
2008-11-08 06:44:07 +00:00
// read macro name
while( *aText )
2007-09-25 15:10:01 +00:00
{
if( *aText == '*' )
2008-11-08 06:44:07 +00:00
{
++aText;
2007-09-25 15:10:01 +00:00
break;
2008-11-08 06:44:07 +00:00
}
2008-11-07 07:55:28 +00:00
am.name.Append( *aText++ );
2007-09-25 15:10:01 +00:00
}
// Read aperture macro parameters
for( ; ; )
2007-09-25 15:10:01 +00:00
{
if( *aText == '*' )
++aText;
2008-11-07 07:55:28 +00:00
aText = GetNextLine( aBuff, aBuffSize, aText, gerber_file );
if( aText == nullptr ) // End of File
return false;
2008-11-08 06:44:07 +00:00
// aText points the beginning of a new line.
// Test for the last line in aperture macro lis:
// last line is % or *% sometime found.
if( *aText == '*' )
++aText;
if( *aText == '%' )
break; // exit with aText still pointing at %
2008-11-08 06:44:07 +00:00
int paramCount = 0; // will be set to the minimal parameters count,
// depending on the actual primitive
2010-10-25 09:57:00 +00:00
int primitive_type = AMP_UNKNOWN;
// Test for a valid symbol at the beginning of a description:
// it can be: a parameter declaration like $1=$2/4
// or a digit (macro primitive selection)
// all other symbols are illegal.
if( *aText == '$' ) // local parameter declaration, inside the aperture macro
2010-10-25 09:57:00 +00:00
{
am.m_localparamStack.push_back( AM_PARAM() );
AM_PARAM& param = am.m_localparamStack.back();
aText = GetNextLine( aBuff, aBuffSize, aText, gerber_file );
if( aText == nullptr) // End of File
return false;
param.ReadParam( aText );
continue;
2010-10-25 09:57:00 +00:00
}
else if( !isdigit(*aText) ) // Ill. symbol
2010-10-25 09:57:00 +00:00
{
msg.Printf( wxT( "RS274X: Aperture Macro \"%s\": ill. symbol, line: \"%s\"" ),
am.name, FROM_UTF8( aBuff ) );
AddMessageToList( msg );
2010-10-25 09:57:00 +00:00
primitive_type = AMP_COMMENT;
}
else
primitive_type = ReadInt( aText );
2010-10-25 09:57:00 +00:00
bool is_comment = false;
switch( primitive_type )
2008-11-08 06:44:07 +00:00
{
case AMP_COMMENT: // lines starting by 0 are a comment
paramCount = 0;
is_comment = true;
2020-09-21 11:15:03 +00:00
2010-10-25 09:57:00 +00:00
// Skip comment
while( *aText && ( *aText != '*' ) )
aText++;
2020-09-21 11:15:03 +00:00
2010-10-25 09:57:00 +00:00
break;
2008-11-08 06:44:07 +00:00
case AMP_CIRCLE:
paramCount = 4; // minimal count. can have a optional parameter (rotation)
2008-11-08 06:44:07 +00:00
break;
2008-11-08 06:44:07 +00:00
case AMP_LINE2:
case AMP_LINE20:
paramCount = 7;
break;
2008-11-08 06:44:07 +00:00
case AMP_LINE_CENTER:
case AMP_LINE_LOWER_LEFT:
paramCount = 6;
break;
2008-11-08 06:44:07 +00:00
case AMP_EOF:
paramCount = 0;
break;
2008-11-08 06:44:07 +00:00
case AMP_OUTLINE:
paramCount = 4; // partial count. other parameters are vertices and rotation
// Second parameter is vertice (coordinate pairs) count.
2008-11-08 06:44:07 +00:00
break;
2008-11-08 06:44:07 +00:00
case AMP_POLYGON:
paramCount = 6;
2008-11-08 06:44:07 +00:00
break;
2008-11-08 06:44:07 +00:00
case AMP_MOIRE:
paramCount = 9;
break;
2008-11-08 06:44:07 +00:00
case AMP_THERMAL:
paramCount = 6;
break;
default:
msg.Printf( wxT( "RS274X: Aperture Macro \"%s\": Invalid primitive id code %d, line %d: \"%s\"" ),
am.name, primitive_type, m_LineNum, FROM_UTF8( aBuff ) );
AddMessageToList( msg );
return false;
2008-11-08 06:44:07 +00:00
}
if( is_comment )
continue;
AM_PRIMITIVE prim( m_GerbMetric );
prim.primitive_id = (AM_PRIMITIVE_ID) primitive_type;
int ii;
for( ii = 0; ii < paramCount && *aText && *aText != '*'; ++ii )
2008-11-08 06:44:07 +00:00
{
2010-11-30 20:41:35 +00:00
prim.params.push_back( AM_PARAM() );
2008-11-14 18:26:56 +00:00
2010-11-30 20:41:35 +00:00
AM_PARAM& param = prim.params.back();
aText = GetNextLine( aBuff, aBuffSize, aText, gerber_file );
if( aText == nullptr) // End of File
return false;
param.ReadParam( aText );
2008-11-08 06:44:07 +00:00
}
if( ii < paramCount )
{
// maybe some day we can throw an exception and track a line number
msg.Printf( "RS274X: read macro descr type %d: read %d parameters, insufficient parameters\n",
prim.primitive_id, ii );
AddMessageToList( msg );
}
// there are more parameters to read if this is an AMP_OUTLINE
2008-11-08 06:44:07 +00:00
if( prim.primitive_id == AMP_OUTLINE )
{
// so far we have read [0]:exposure, [1]:#points, [2]:X start, [3]: Y start
// Now read all the points, plus trailing rotation in degrees.
// params[1] is a count of polygon points, so it must be given
// in advance, i.e. be immediate.
wxASSERT( prim.params[1].IsImmediate() );
paramCount = (int) prim.params[1].GetValue( nullptr ) * 2 + 1;
2008-11-08 06:44:07 +00:00
for( int jj = 0; jj < paramCount && *aText != '*'; ++jj )
2008-11-08 06:44:07 +00:00
{
2010-11-30 20:41:35 +00:00
prim.params.push_back( AM_PARAM() );
2008-11-14 18:26:56 +00:00
2010-11-30 20:41:35 +00:00
AM_PARAM& param = prim.params.back();
aText = GetNextLine( aBuff, aBuffSize, aText, gerber_file );
if( aText == nullptr ) // End of File
return false;
param.ReadParam( aText );
2008-11-08 06:44:07 +00:00
}
}
// AMP_CIRCLE can have a optional parameter (rotation)
if( prim.primitive_id == AMP_CIRCLE && aText && *aText != '*' )
{
prim.params.push_back( AM_PARAM() );
AM_PARAM& param = prim.params.back();
param.ReadParam( aText );
}
am.primitives.push_back( prim );
2007-09-25 15:10:01 +00:00
}
2008-11-08 06:44:07 +00:00
m_aperture_macros.insert( am );
return true;
}