kicad/common/common_plotGERBER_functions...

523 lines
13 KiB
C++
Raw Normal View History

/**
* @file common_plotGERBER_functions.cpp
* @brief Common GERBER plot routines.
*/
2008-12-23 07:37:39 +00:00
#include <fctsys.h>
#include <gr_basic.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <common.h>
#include <plot_common.h>
#include <macros.h>
#include <kicad_string.h>
2008-12-23 07:37:39 +00:00
#include <build_version.h>
2008-12-23 07:37:39 +00:00
void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
2008-12-23 07:37:39 +00:00
{
wxASSERT( !outputFile );
wxASSERT( aMirror == false );
plotMirror = false;
plotOffset = aOffset;
2009-06-28 18:13:55 +00:00
wxASSERT( aScale == 1 );
plotScale = 1;
iuPerDeviceUnit = 1.0 / aIusPerDecimil;
/* We don't handle the filmbox, and it's more useful to keep the
* origin at the origin */
paperSize.x = 0;
paperSize.y = 0;
SetDefaultLineWidth( 100 * aIusPerDecimil ); // Arbitrary default
2008-12-23 07:37:39 +00:00
}
/**
* Emit a D-Code record, using proper conversions
* to format a leading zero omitted gerber coordinate
* (for 4 decimal positions, see header generation in start_plot
*/
void GERBER_PLOTTER::emitDcode( const DPOINT& pt, int dcode )
{
fprintf( outputFile, "X%dY%dD%02d*\n",
int( pt.x ), int( pt.y ), dcode );
}
2008-12-23 07:37:39 +00:00
/**
* Function start_plot
2008-12-23 07:37:39 +00:00
* Write GERBER header to file
* initialize global variable g_Plot_PlotOutputFile
* @param aFile: an opened file to write to
*/
bool GERBER_PLOTTER::StartPlot( FILE* aFile )
2008-12-23 07:37:39 +00:00
{
wxASSERT( !outputFile );
finalFile = aFile;
// Create a temporary filename to store gerber file
// note tmpfile() does not work under Vista and W7 in user mode
m_workFilename = filename + wxT(".tmp");
workFile = wxFopen( m_workFilename, wxT( "wt" ));
outputFile = workFile;
wxASSERT( outputFile );
if( outputFile == NULL )
return false;
wxString Title = creator + wxT( " " ) + GetBuildVersion();
fprintf( outputFile, "G04 (created by %s) date %s*\n",
TO_UTF8( Title ), TO_UTF8( DateAndTime() ) );
2008-12-23 07:37:39 +00:00
/* Mass parameter: unit = INCHES */
fputs( "%MOIN*%\n", outputFile );
2008-12-23 07:37:39 +00:00
/* Set coordinate format to 3.4 absolute, leading zero omitted */
2008-12-23 07:37:39 +00:00
fputs( "G04 Gerber Fmt 3.4, Leading zero omitted, Abs format*\n%FSLAX34Y34*%\n",
outputFile );
2008-12-23 07:37:39 +00:00
/* Specify linear interpol (G01), unit = INCH (G70), abs format (G90) */
fputs( "G01*\nG70*\nG90*\n", outputFile );
fputs( "G04 APERTURE LIST*\n", outputFile );
/* Select the default aperture */
SetCurrentLineWidth( -1 );
return true;
2008-12-23 07:37:39 +00:00
}
2009-06-28 18:13:55 +00:00
bool GERBER_PLOTTER::EndPlot()
{
char line[1024];
wxString msg;
wxASSERT( outputFile );
/* Outfile is actually a temporary file i.e. workFile */
fputs( "M02*\n", outputFile );
fflush( outputFile );
fclose( workFile );
workFile = wxFopen( m_workFilename, wxT( "rt" ));
wxASSERT( workFile );
outputFile = finalFile;
2008-12-23 07:37:39 +00:00
// Placement of apertures in RS274X
while( fgets( line, 1024, workFile ) )
{
fputs( line, outputFile );
2009-06-28 18:13:55 +00:00
if( strcmp( strtok( line, "\n\r" ), "G04 APERTURE LIST*" ) == 0 )
{
writeApertureList();
fputs( "G04 APERTURE END LIST*\n", outputFile );
2009-06-28 18:13:55 +00:00
}
}
fclose( workFile );
fclose( finalFile );
::wxRemoveFile( m_workFilename );
outputFile = 0;
return true;
}
2009-06-28 18:13:55 +00:00
void GERBER_PLOTTER::SetDefaultLineWidth( int width )
{
defaultPenWidth = width;
currentAperture = apertures.end();
}
2009-06-28 18:13:55 +00:00
void GERBER_PLOTTER::SetCurrentLineWidth( int width )
2008-12-23 07:37:39 +00:00
{
int pen_width;
if( width > 0 )
pen_width = width;
else
pen_width = defaultPenWidth;
selectAperture( wxSize( pen_width, pen_width ), APERTURE::Plotting );
currentPenWidth = pen_width;
}
2009-06-28 18:13:55 +00:00
std::vector<APERTURE>::iterator GERBER_PLOTTER::getAperture( const wxSize& size,
APERTURE::APERTURE_TYPE type )
{
int last_D_code = 9;
2009-06-28 18:13:55 +00:00
// Search an existing aperture
std::vector<APERTURE>::iterator tool = apertures.begin();
2009-06-28 18:13:55 +00:00
while( tool != apertures.end() )
{
last_D_code = tool->DCode;
if( (tool->Type == type) && (tool->Size == size) )
2009-06-28 18:13:55 +00:00
return tool;
2009-06-28 18:13:55 +00:00
tool++;
}
2009-06-28 18:13:55 +00:00
// Allocate a new aperture
APERTURE new_tool;
new_tool.Size = size;
new_tool.Type = type;
new_tool.DCode = last_D_code + 1;
2009-06-28 18:13:55 +00:00
apertures.push_back( new_tool );
return apertures.end() - 1;
}
2009-06-28 18:13:55 +00:00
void GERBER_PLOTTER::selectAperture( const wxSize& size,
APERTURE::APERTURE_TYPE type )
{
wxASSERT( outputFile );
if( ( currentAperture == apertures.end() )
|| ( currentAperture->Type != type )
|| ( currentAperture->Size != size ) )
2009-06-28 18:13:55 +00:00
{
/* Pick an existing aperture or create a new one */
currentAperture = getAperture( size, type );
fprintf( outputFile, "G54D%d*\n", currentAperture->DCode );
}
}
2009-06-28 18:13:55 +00:00
/**
* Generate the table of D codes
*/
void GERBER_PLOTTER::writeApertureList()
{
wxASSERT( outputFile );
char cbuf[1024];
/* Init : */
for( std::vector<APERTURE>::iterator tool = apertures.begin();
2009-06-28 18:13:55 +00:00
tool != apertures.end(); tool++ )
{
const double fscale = 0.0001f * plotScale
* iuPerDeviceUnit ; // For 3.4 format
char* text = cbuf + sprintf( cbuf, "%%ADD%d", tool->DCode );
switch( tool->Type )
2009-06-28 18:13:55 +00:00
{
case APERTURE::Circle:
sprintf( text, "C,%g*%%\n", tool->Size.x * fscale );
2009-06-28 18:13:55 +00:00
break;
case APERTURE::Rect:
sprintf( text, "R,%gX%g*%%\n",
tool->Size.x * fscale,
tool->Size.y * fscale );
2009-06-28 18:13:55 +00:00
break;
case APERTURE::Plotting:
sprintf( text, "C,%g*%%\n", tool->Size.x * fscale );
2009-06-28 18:13:55 +00:00
break;
case APERTURE::Oval:
sprintf( text, "O,%gX%g*%%\n",
tool->Size.x * fscale,
tool->Size.y * fscale );
2009-06-28 18:13:55 +00:00
break;
}
fputs( cbuf, outputFile );
}
}
2009-06-28 18:13:55 +00:00
void GERBER_PLOTTER::PenTo( const wxPoint& aPos, char plume )
{
wxASSERT( outputFile );
DPOINT pos_dev = userToDeviceCoordinates( aPos );
2009-06-28 18:13:55 +00:00
switch( plume )
{
case 'Z':
break;
2008-12-23 07:37:39 +00:00
2009-06-28 18:13:55 +00:00
case 'U':
emitDcode( pos_dev, 2 );
2009-06-28 18:13:55 +00:00
break;
case 'D':
emitDcode( pos_dev, 1 );
2009-06-28 18:13:55 +00:00
}
2008-12-23 07:37:39 +00:00
penState = plume;
2008-12-23 07:37:39 +00:00
}
2009-06-28 18:13:55 +00:00
void GERBER_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill,
int width )
2008-12-23 07:37:39 +00:00
{
std::vector< wxPoint > cornerList;
// Build corners list
cornerList.push_back( p1 );
wxPoint corner(p1.x, p2.y);
cornerList.push_back( corner );
cornerList.push_back( p2 );
corner.x = p2.x;
corner.y = p1.y;
cornerList.push_back( corner );
cornerList.push_back( p1 );
PlotPoly( cornerList, fill, width );
2008-12-23 07:37:39 +00:00
}
2009-06-28 18:13:55 +00:00
void GERBER_PLOTTER::Circle( const wxPoint& aCentre, int aDiameter, FILL_T aFill,
int aWidth )
2008-12-23 07:37:39 +00:00
{
wxASSERT( outputFile );
2009-06-28 18:13:55 +00:00
wxPoint start, end;
double radius = aDiameter / 2;
const int delta = 3600 / 32; /* increment (in 0.1 degrees) to draw circles */
2009-06-28 18:13:55 +00:00
// 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
start.x = aCentre.x + KiROUND( radius );
2009-06-28 18:13:55 +00:00
start.y = aCentre.y;
SetCurrentLineWidth( aWidth );
MoveTo( start );
2009-06-28 18:13:55 +00:00
for( int ii = delta; ii < 3600; ii += delta )
2008-12-23 07:37:39 +00:00
{
end.x = aCentre.x + (int) ( radius * cos( DEG2RAD( ii / 10.0 ) ) );
end.y = aCentre.y + (int) ( radius * sin( DEG2RAD( ii / 10.0 ) ) );
LineTo( end );
2008-12-23 07:37:39 +00:00
}
FinishTo( start );
2008-12-23 07:37:39 +00:00
}
/**
* Gerber polygon: they can (and *should*) be filled with the
* appropriate G36/G37 sequence (raster fills are deprecated)
2009-06-28 18:13:55 +00:00
*/
void GERBER_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList,
FILL_T aFill, int aWidth )
2008-12-23 07:37:39 +00:00
{
if( aCornerList.size() <= 1 )
return;
SetCurrentLineWidth( aWidth );
2009-06-28 18:13:55 +00:00
if( aFill )
fputs( "G36*\n", outputFile );
MoveTo( aCornerList[0] );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
2008-12-23 07:37:39 +00:00
{
LineTo( aCornerList[ii] );
}
2009-06-28 18:13:55 +00:00
if( aFill )
{
FinishTo( aCornerList[0] );
fputs( "G37*\n", outputFile );
}
else
{
PenFinish();
2008-12-23 07:37:39 +00:00
}
}
/**
* Filled circular flashes are stored as apertures
*/
void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
EDA_DRAW_MODE_T trace_mode )
{
wxASSERT( outputFile );
2009-06-28 18:13:55 +00:00
wxSize size( diametre, diametre );
2008-12-23 07:37:39 +00:00
2009-06-28 18:13:55 +00:00
switch( trace_mode )
{
case LINE:
case SKETCH:
SetCurrentLineWidth( -1 );
Circle( pos, diametre - currentPenWidth, NO_FILL );
2009-06-28 18:13:55 +00:00
break;
case FILLED:
DPOINT pos_dev = userToDeviceCoordinates( pos );
selectAperture( size, APERTURE::Circle );
emitDcode( pos_dev, 3 );
2009-06-28 18:13:55 +00:00
break;
}
}
2009-06-28 18:13:55 +00:00
/**
* Filled oval flashes are handled as aperture in the 90 degree positions only
*/
void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, int orient,
EDA_DRAW_MODE_T trace_mode )
2008-12-23 07:37:39 +00:00
{
wxASSERT( outputFile );
2009-06-28 18:13:55 +00:00
int x0, y0, x1, y1, delta;
wxSize size( aSize );
/* Plot a flashed shape. */
2009-06-28 18:13:55 +00:00
if( ( orient == 0 || orient == 900 || orient == 1800 || orient == 2700 )
&& trace_mode == FILLED )
{
if( orient == 900 || orient == 2700 ) /* orientation turned 90 deg. */
2009-06-28 18:13:55 +00:00
EXCHG( size.x, size.y );
DPOINT pos_dev = userToDeviceCoordinates( pos );
selectAperture( size, APERTURE::Oval );
emitDcode( pos_dev, 3 );
}
else /* Plot pad as a segment. */
2008-12-23 07:37:39 +00:00
{
2009-06-28 18:13:55 +00:00
if( size.x > size.y )
{
EXCHG( size.x, size.y );
2009-06-28 18:13:55 +00:00
if( orient < 2700 )
orient += 900;
else
orient -= 2700;
}
2009-06-28 18:13:55 +00:00
if( trace_mode == FILLED )
{
/* XXX to do: use an aperture macro to declare the rotated pad */
/* The pad is reduced to an oval with dy > dx */
2009-06-28 18:13:55 +00:00
delta = size.y - size.x;
x0 = 0;
y0 = -delta / 2;
x1 = 0;
y1 = delta / 2;
RotatePoint( &x0, &y0, orient );
RotatePoint( &x1, &y1, orient );
ThickSegment( wxPoint( pos.x + x0, pos.y + y0 ),
2009-06-28 18:13:55 +00:00
wxPoint( pos.x + x1, pos.y + y1 ),
size.x, trace_mode );
}
else
{
sketchOval( pos, size, orient, -1 );
}
2008-12-23 07:37:39 +00:00
}
}
2009-06-28 18:13:55 +00:00
/**
* Filled rect flashes are handled as aperture in the 90 degree positions only
*/
void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
int orient, EDA_DRAW_MODE_T trace_mode )
2009-06-28 18:13:55 +00:00
{
wxASSERT( outputFile );
wxSize size( aSize );
/* Plot as flashed. */
switch( orient )
{
case 900:
case 2700: /* rotation of 90 degrees or 270 swaps dimensions */
EXCHG( size.x, size.y );
2008-12-23 07:37:39 +00:00
// Pass through
case 0:
case 1800:
switch( trace_mode )
{
case LINE:
case SKETCH:
SetCurrentLineWidth( -1 );
Rect( wxPoint( pos.x - (size.x - currentPenWidth) / 2,
pos.y - (size.y - currentPenWidth) / 2 ),
wxPoint( pos.x + (size.x - currentPenWidth) / 2,
pos.y + (size.y - currentPenWidth) / 2 ),
NO_FILL );
break;
case FILLED:
DPOINT pos_dev = userToDeviceCoordinates( pos );
selectAperture( size, APERTURE::Rect );
emitDcode( pos_dev, 3 );
break;
}
break;
default: // plot pad shape as polygon
{
// XXX to do: use an aperture macro to declare the rotated pad
wxPoint coord[4];
// coord[0] is assumed the lower left
// coord[1] is assumed the upper left
// coord[2] is assumed the upper right
// coord[3] is assumed the lower right
/* Trace the outline. */
coord[0].x = -size.x/2; // lower left
coord[0].y = size.y/2;
coord[1].x = -size.x/2; // upper left
coord[1].y = -size.y/2;
coord[2].x = size.x/2; // upper right
coord[2].y = -size.y/2;
coord[3].x = size.x/2; // lower right
coord[3].y = size.y/2;
FlashPadTrapez( pos, coord, orient, trace_mode );
}
break;
}
2008-12-23 07:37:39 +00:00
}
2009-06-28 18:13:55 +00:00
/**
* Trapezoidal pad at the moment are *never* handled as aperture, since
* they require aperture macros
*/
void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners,
int aPadOrient, EDA_DRAW_MODE_T aTrace_Mode )
{
// XXX to do: use an aperture macro to declare the pad
// polygon corners list
std::vector< wxPoint > cornerList;
for( int ii = 0; ii < 4; ii++ )
cornerList.push_back( aCorners[ii] );
// Draw the polygon and fill the interior as required
for( unsigned ii = 0; ii < 4; ii++ )
{
RotatePoint( &cornerList[ii], aPadOrient );
cornerList[ii] += aPadPos;
}
// Close the polygon
cornerList.push_back( cornerList[0] );
2008-12-23 07:37:39 +00:00
SetCurrentLineWidth( -1 );
PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL );
2009-06-28 18:13:55 +00:00
}
/**
* Change the plot polarity and begin a new layer
* Used to 'scratch off' silk screen away from solder mask
*/
void GERBER_PLOTTER::SetLayerPolarity( bool aPositive )
{
if( aPositive )
fprintf( outputFile, "%%LPD*%%\n" );
else
fprintf( outputFile, "%%LPC*%%\n" );
}