532 lines
14 KiB
C++
532 lines
14 KiB
C++
/**
|
|
* @file common_plotGERBER_functions.cpp
|
|
* @brief Common GERBER plot routines.
|
|
*/
|
|
|
|
#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>
|
|
|
|
#include <build_version.h>
|
|
|
|
|
|
void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
|
|
double aScale, bool aMirror )
|
|
{
|
|
wxASSERT( !outputFile );
|
|
wxASSERT( aMirror == false );
|
|
plotMirror = false;
|
|
plotOffset = aOffset;
|
|
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
|
|
}
|
|
|
|
/**
|
|
* 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 );
|
|
}
|
|
|
|
/**
|
|
* Function start_plot
|
|
* 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 )
|
|
{
|
|
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() ) );
|
|
|
|
/* Mass parameter: unit = INCHES */
|
|
fputs( "%MOIN*%\n", outputFile );
|
|
|
|
/* Set coordinate format to 3.4 absolute, leading zero omitted */
|
|
fputs( "G04 Gerber Fmt 3.4, Leading zero omitted, Abs format*\n%FSLAX34Y34*%\n",
|
|
outputFile );
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
// Placement of apertures in RS274X
|
|
while( fgets( line, 1024, workFile ) )
|
|
{
|
|
fputs( line, outputFile );
|
|
|
|
if( strcmp( strtok( line, "\n\r" ), "G04 APERTURE LIST*" ) == 0 )
|
|
{
|
|
writeApertureList();
|
|
fputs( "G04 APERTURE END LIST*\n", outputFile );
|
|
}
|
|
}
|
|
|
|
fclose( workFile );
|
|
fclose( finalFile );
|
|
::wxRemoveFile( m_workFilename );
|
|
outputFile = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::SetDefaultLineWidth( int width )
|
|
{
|
|
defaultPenWidth = width;
|
|
currentAperture = apertures.end();
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::SetCurrentLineWidth( int width )
|
|
{
|
|
int pen_width;
|
|
|
|
if( width > 0 )
|
|
pen_width = width;
|
|
else
|
|
pen_width = defaultPenWidth;
|
|
|
|
selectAperture( wxSize( pen_width, pen_width ), APERTURE::Plotting );
|
|
currentPenWidth = pen_width;
|
|
}
|
|
|
|
|
|
std::vector<APERTURE>::iterator GERBER_PLOTTER::getAperture( const wxSize& size,
|
|
APERTURE::APERTURE_TYPE type )
|
|
{
|
|
int last_D_code = 9;
|
|
|
|
// Search an existing aperture
|
|
std::vector<APERTURE>::iterator tool = apertures.begin();
|
|
|
|
while( tool != apertures.end() )
|
|
{
|
|
last_D_code = tool->DCode;
|
|
|
|
if( (tool->Type == type) && (tool->Size == size) )
|
|
return tool;
|
|
|
|
tool++;
|
|
}
|
|
|
|
// Allocate a new aperture
|
|
APERTURE new_tool;
|
|
new_tool.Size = size;
|
|
new_tool.Type = type;
|
|
new_tool.DCode = last_D_code + 1;
|
|
apertures.push_back( new_tool );
|
|
return apertures.end() - 1;
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::selectAperture( const wxSize& size,
|
|
APERTURE::APERTURE_TYPE type )
|
|
{
|
|
wxASSERT( outputFile );
|
|
|
|
if( ( currentAperture == apertures.end() )
|
|
|| ( currentAperture->Type != type )
|
|
|| ( currentAperture->Size != size ) )
|
|
{
|
|
/* Pick an existing aperture or create a new one */
|
|
currentAperture = getAperture( size, type );
|
|
fprintf( outputFile, "G54D%d*\n", currentAperture->DCode );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate the table of D codes
|
|
*/
|
|
void GERBER_PLOTTER::writeApertureList()
|
|
{
|
|
wxASSERT( outputFile );
|
|
char cbuf[1024];
|
|
|
|
/* Init : */
|
|
for( std::vector<APERTURE>::iterator tool = apertures.begin();
|
|
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 )
|
|
{
|
|
case APERTURE::Circle:
|
|
sprintf( text, "C,%g*%%\n", tool->Size.x * fscale );
|
|
break;
|
|
|
|
case APERTURE::Rect:
|
|
sprintf( text, "R,%gX%g*%%\n",
|
|
tool->Size.x * fscale,
|
|
tool->Size.y * fscale );
|
|
break;
|
|
|
|
case APERTURE::Plotting:
|
|
sprintf( text, "C,%g*%%\n", tool->Size.x * fscale );
|
|
break;
|
|
|
|
case APERTURE::Oval:
|
|
sprintf( text, "O,%gX%g*%%\n",
|
|
tool->Size.x * fscale,
|
|
tool->Size.y * fscale );
|
|
break;
|
|
}
|
|
|
|
fputs( cbuf, outputFile );
|
|
}
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::PenTo( const wxPoint& aPos, char plume )
|
|
{
|
|
wxASSERT( outputFile );
|
|
DPOINT pos_dev = userToDeviceCoordinates( aPos );
|
|
|
|
switch( plume )
|
|
{
|
|
case 'Z':
|
|
break;
|
|
|
|
case 'U':
|
|
emitDcode( pos_dev, 2 );
|
|
break;
|
|
|
|
case 'D':
|
|
emitDcode( pos_dev, 1 );
|
|
}
|
|
|
|
penState = plume;
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill,
|
|
int width )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::Circle( const wxPoint& aCenter, int aDiameter, FILL_T aFill,
|
|
int aWidth )
|
|
{
|
|
Arc( aCenter, 0, 3600, aDiameter / 2, aFill, aWidth );
|
|
}
|
|
|
|
|
|
void GERBER_PLOTTER::Arc( const wxPoint& aCenter, int aStAngle, int aEndAngle,
|
|
int aRadius, FILL_T aFill, int aWidth )
|
|
{
|
|
wxASSERT( outputFile );
|
|
wxPoint start, end;
|
|
start.x = aCenter.x + KiROUND( aRadius*cos( DEG2RAD( aStAngle/10.0 ) ) );
|
|
start.y = aCenter.y - KiROUND( aRadius*sin( DEG2RAD( aStAngle/10.0 ) ) );
|
|
SetCurrentLineWidth( aWidth );
|
|
MoveTo( start );
|
|
end.x = aCenter.x + KiROUND( aRadius*cos( DEG2RAD( aEndAngle/10.0 ) ) );
|
|
end.y = aCenter.y - KiROUND( aRadius*sin( DEG2RAD( aEndAngle/10.0 ) ) );
|
|
DPOINT devEnd = userToDeviceCoordinates( end );
|
|
DPOINT devCenter = userToDeviceCoordinates( aCenter )
|
|
- userToDeviceCoordinates( start );
|
|
fprintf( outputFile, "G75*\n" ); // Multiquadrant mode
|
|
|
|
if( aStAngle < aEndAngle )
|
|
fprintf( outputFile, "G03" );
|
|
else
|
|
fprintf( outputFile, "G02" );
|
|
fprintf( outputFile, "X%dY%dI%dJ%dD01*\n", int( devEnd.x ), int( devEnd.y ),
|
|
int( devCenter.x ), int( devCenter.y ) );
|
|
fprintf( outputFile, "G74*\nG01*\n" ); // Back to single quadrant and linear interp.
|
|
}
|
|
|
|
|
|
/**
|
|
* Gerber polygon: they can (and *should*) be filled with the
|
|
* appropriate G36/G37 sequence (raster fills are deprecated)
|
|
*/
|
|
void GERBER_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList,
|
|
FILL_T aFill, int aWidth )
|
|
{
|
|
if( aCornerList.size() <= 1 )
|
|
return;
|
|
|
|
SetCurrentLineWidth( aWidth );
|
|
|
|
if( aFill )
|
|
fputs( "G36*\n", outputFile );
|
|
|
|
MoveTo( aCornerList[0] );
|
|
|
|
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
|
|
{
|
|
LineTo( aCornerList[ii] );
|
|
}
|
|
|
|
if( aFill )
|
|
{
|
|
FinishTo( aCornerList[0] );
|
|
fputs( "G37*\n", outputFile );
|
|
}
|
|
else
|
|
{
|
|
PenFinish();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Filled circular flashes are stored as apertures
|
|
*/
|
|
void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
|
|
EDA_DRAW_MODE_T trace_mode )
|
|
{
|
|
wxASSERT( outputFile );
|
|
wxSize size( diametre, diametre );
|
|
|
|
switch( trace_mode )
|
|
{
|
|
case LINE:
|
|
case SKETCH:
|
|
SetCurrentLineWidth( -1 );
|
|
Circle( pos, diametre - currentPenWidth, NO_FILL );
|
|
break;
|
|
|
|
case FILLED:
|
|
DPOINT pos_dev = userToDeviceCoordinates( pos );
|
|
selectAperture( size, APERTURE::Circle );
|
|
emitDcode( pos_dev, 3 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 )
|
|
{
|
|
wxASSERT( outputFile );
|
|
int x0, y0, x1, y1, delta;
|
|
wxSize size( aSize );
|
|
|
|
/* Plot a flashed shape. */
|
|
if( ( orient == 0 || orient == 900 || orient == 1800 || orient == 2700 )
|
|
&& trace_mode == FILLED )
|
|
{
|
|
if( orient == 900 || orient == 2700 ) /* orientation turned 90 deg. */
|
|
EXCHG( size.x, size.y );
|
|
|
|
DPOINT pos_dev = userToDeviceCoordinates( pos );
|
|
selectAperture( size, APERTURE::Oval );
|
|
emitDcode( pos_dev, 3 );
|
|
}
|
|
else /* Plot pad as a segment. */
|
|
{
|
|
if( size.x > size.y )
|
|
{
|
|
EXCHG( size.x, size.y );
|
|
|
|
if( orient < 2700 )
|
|
orient += 900;
|
|
else
|
|
orient -= 2700;
|
|
}
|
|
|
|
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 */
|
|
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 ),
|
|
wxPoint( pos.x + x1, pos.y + y1 ),
|
|
size.x, trace_mode );
|
|
}
|
|
else
|
|
{
|
|
sketchOval( pos, size, orient, -1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 )
|
|
|
|
{
|
|
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 );
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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] );
|
|
|
|
SetCurrentLineWidth( -1 );
|
|
PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL );
|
|
}
|
|
|
|
/**
|
|
* 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" );
|
|
}
|
|
|