move plotter related files to subdir plotters

This commit is contained in:
jean-pierre charras 2018-01-28 17:49:55 +01:00
parent 19e3815c85
commit 93fd0c9358
8 changed files with 6034 additions and 0 deletions

View File

@ -0,0 +1,541 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2017 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
*/
/**
* @file class_plotter.cpp
* @brief KiCad: Base of all the plot routines
* the class PLOTTER handle basic functions to plot schematic and boards
* with different plot formats.
*
* There are currently engines for:
* HPGL
* POSTSCRIPT
* GERBER
* DXF
* an SVG 'plot' is also provided along with the 'print' function by wx, but
* is not handled here.
*/
#include <fctsys.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <common.h>
#include <class_plotter.h>
#include <macros.h>
#include <class_base_screen.h>
#include <drawtxt.h>
#include <geometry/shape_line_chain.h>
PLOTTER::PLOTTER( )
{
plotScale = 1;
defaultPenWidth = 0;
currentPenWidth = -1; // To-be-set marker
penState = 'Z'; // End-of-path idle
m_plotMirror = false; // Plot mirror option flag
m_mirrorIsHorizontal = true;
m_yaxisReversed = false;
outputFile = 0;
colorMode = false; // Starts as a BW plot
negativeMode = false;
// Temporary init to avoid not initialized vars, will be set later
m_IUsPerDecimil = 1; // will be set later to the actual value
iuPerDeviceUnit = 1; // will be set later to the actual value
m_dotMarkLength_mm = 0.1; // Dotted line parameter in mm: segment
// Dashed line parameter is 5 * dotted line mark
// Dashed line gap is 3 * dotted line mark
}
PLOTTER::~PLOTTER()
{
// Emergency cleanup, but closing the file is
// usually made in EndPlot().
if( outputFile )
fclose( outputFile );
}
bool PLOTTER::OpenFile( const wxString& aFullFilename )
{
filename = aFullFilename;
wxASSERT( !outputFile );
// Open the file in text mode (not suitable for all plotters
// but only for most of them
outputFile = wxFopen( filename, wxT( "wt" ) );
if( outputFile == NULL )
return false ;
return true;
}
DPOINT PLOTTER::userToDeviceCoordinates( const wxPoint& aCoordinate )
{
wxPoint pos = aCoordinate - plotOffset;
double x = pos.x * plotScale;
double y = ( paperSize.y - pos.y * plotScale );
if( m_plotMirror )
{
if( m_mirrorIsHorizontal )
x = ( paperSize.x - pos.x * plotScale );
else
y = pos.y * plotScale;
}
if( m_yaxisReversed )
y = paperSize.y - y;
x *= iuPerDeviceUnit;
y *= iuPerDeviceUnit;
return DPOINT( x, y );
}
DPOINT PLOTTER::userToDeviceSize( const wxSize& size )
{
return DPOINT( size.x * plotScale * iuPerDeviceUnit,
size.y * plotScale * iuPerDeviceUnit );
}
double PLOTTER::userToDeviceSize( double size ) const
{
return size * plotScale * iuPerDeviceUnit;
}
double PLOTTER::GetDotMarkLenIU() const
{
return userToDeviceSize( std::max( 1.0,
m_dotMarkLength_mm * 10000 / 25.4 * m_IUsPerDecimil - GetCurrentLineWidth() ) );
}
double PLOTTER::GetDashMarkLenIU() const
{
return std::max( GetDashGapLenIU(), 5.0 * GetDotMarkLenIU() );
}
double PLOTTER::GetDashGapLenIU() const
{
return 3.0 * GetDotMarkLenIU() + userToDeviceSize( 2 * GetCurrentLineWidth() );
}
void PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
FILL_T fill, int width )
{
wxPoint start, end;
const int delta = 50; // increment (in 0.1 degrees) to draw circles
if( StAngle > EndAngle )
std::swap( StAngle, EndAngle );
SetCurrentLineWidth( width );
/* Please NOTE the different sign due to Y-axis flip */
start.x = centre.x + KiROUND( cosdecideg( radius, -StAngle ) );
start.y = centre.y + KiROUND( sindecideg( radius, -StAngle ) );
MoveTo( start );
for( int ii = StAngle + delta; ii < EndAngle; ii += delta )
{
end.x = centre.x + KiROUND( cosdecideg( radius, -ii ) );
end.y = centre.y + KiROUND( sindecideg( radius, -ii ) );
LineTo( end );
}
end.x = centre.x + KiROUND( cosdecideg( radius, -EndAngle ) );
end.y = centre.y + KiROUND( sindecideg( radius, -EndAngle ) );
FinishTo( end );
}
void PLOTTER::PlotImage(const wxImage & aImage, const wxPoint& aPos, double aScaleFactor )
{
wxSize size( aImage.GetWidth() * aScaleFactor,
aImage.GetHeight() * aScaleFactor );
wxPoint start = aPos;
start.x -= size.x / 2;
start.y -= size.y / 2;
wxPoint end = start;
end.x += size.x;
end.y += size.y;
Rect( start, end, NO_FILL );
}
void PLOTTER::markerSquare( const wxPoint& position, int radius )
{
double r = KiROUND( radius / 1.4142 );
std::vector< wxPoint > corner_list;
wxPoint corner;
corner.x = position.x + r;
corner.y = position.y + r;
corner_list.push_back( corner );
corner.x = position.x + r;
corner.y = position.y - r;
corner_list.push_back( corner );
corner.x = position.x - r;
corner.y = position.y - r;
corner_list.push_back( corner );
corner.x = position.x - r;
corner.y = position.y + r;
corner_list.push_back( corner );
corner.x = position.x + r;
corner.y = position.y + r;
corner_list.push_back( corner );
PlotPoly( corner_list, NO_FILL, GetCurrentLineWidth() );
}
void PLOTTER::markerCircle( const wxPoint& position, int radius )
{
Circle( position, radius * 2, NO_FILL, GetCurrentLineWidth() );
}
void PLOTTER::markerLozenge( const wxPoint& position, int radius )
{
std::vector< wxPoint > corner_list;
wxPoint corner;
corner.x = position.x;
corner.y = position.y + radius;
corner_list.push_back( corner );
corner.x = position.x + radius;
corner.y = position.y,
corner_list.push_back( corner );
corner.x = position.x;
corner.y = position.y - radius;
corner_list.push_back( corner );
corner.x = position.x - radius;
corner.y = position.y;
corner_list.push_back( corner );
corner.x = position.x;
corner.y = position.y + radius;
corner_list.push_back( corner );
PlotPoly( corner_list, NO_FILL, GetCurrentLineWidth() );
}
void PLOTTER::markerHBar( const wxPoint& pos, int radius )
{
MoveTo( wxPoint( pos.x - radius, pos.y ) );
FinishTo( wxPoint( pos.x + radius, pos.y ) );
}
void PLOTTER::markerSlash( const wxPoint& pos, int radius )
{
MoveTo( wxPoint( pos.x - radius, pos.y - radius ) );
FinishTo( wxPoint( pos.x + radius, pos.y + radius ) );
}
void PLOTTER::markerBackSlash( const wxPoint& pos, int radius )
{
MoveTo( wxPoint( pos.x + radius, pos.y - radius ) );
FinishTo( wxPoint( pos.x - radius, pos.y + radius ) );
}
void PLOTTER::markerVBar( const wxPoint& pos, int radius )
{
MoveTo( wxPoint( pos.x, pos.y - radius ) );
FinishTo( wxPoint( pos.x, pos.y + radius ) );
}
void PLOTTER::Marker( const wxPoint& position, int diametre, unsigned aShapeId )
{
int radius = diametre / 2;
/* Marker are composed by a series of 'parts' superimposed; not every
combination make sense, obviously. Since they are used in order I
tried to keep the uglier/more complex constructions at the end.
Also I avoided the |/ |\ -/ -\ construction because they're *very*
ugly... if needed they could be added anyway... I'd like to see
a board with more than 58 drilling/slotting tools!
If Visual C++ supported the 0b literals they would be optimally
and easily encoded as an integer array. We have to do with octal */
static const unsigned char marker_patterns[MARKER_COUNT] = {
// Bit order: O Square Lozenge - | \ /
// First choice: simple shapes
0003, // X
0100, // O
0014, // +
0040, // Sq
0020, // Lz
// Two simple shapes
0103, // X O
0017, // X +
0043, // X Sq
0023, // X Lz
0114, // O +
0140, // O Sq
0120, // O Lz
0054, // + Sq
0034, // + Lz
0060, // Sq Lz
// Three simple shapes
0117, // X O +
0143, // X O Sq
0123, // X O Lz
0057, // X + Sq
0037, // X + Lz
0063, // X Sq Lz
0154, // O + Sq
0134, // O + Lz
0074, // + Sq Lz
// Four simple shapes
0174, // O Sq Lz +
0163, // X O Sq Lz
0157, // X O Sq +
0137, // X O Lz +
0077, // X Sq Lz +
// This draws *everything *
0177, // X O Sq Lz +
// Here we use the single bars... so the cross is forbidden
0110, // O -
0104, // O |
0101, // O /
0050, // Sq -
0044, // Sq |
0041, // Sq /
0030, // Lz -
0024, // Lz |
0021, // Lz /
0150, // O Sq -
0144, // O Sq |
0141, // O Sq /
0130, // O Lz -
0124, // O Lz |
0121, // O Lz /
0070, // Sq Lz -
0064, // Sq Lz |
0061, // Sq Lz /
0170, // O Sq Lz -
0164, // O Sq Lz |
0161, // O Sq Lz /
// Last resort: the backlash component (easy to confound)
0102, // \ O
0042, // \ Sq
0022, // \ Lz
0142, // \ O Sq
0122, // \ O Lz
0062, // \ Sq Lz
0162 // \ O Sq Lz
};
if( aShapeId >= MARKER_COUNT )
{
// Fallback shape
markerCircle( position, radius );
}
else
{
// Decode the pattern and draw the corresponding parts
unsigned char pat = marker_patterns[aShapeId];
if( pat & 0001 )
markerSlash( position, radius );
if( pat & 0002 )
markerBackSlash( position, radius );
if( pat & 0004 )
markerVBar( position, radius );
if( pat & 0010 )
markerHBar( position, radius );
if( pat & 0020 )
markerLozenge( position, radius );
if( pat & 0040 )
markerSquare( position, radius );
if( pat & 0100 )
markerCircle( position, radius );
}
}
void PLOTTER::segmentAsOval( const wxPoint& start, const wxPoint& end, int width,
EDA_DRAW_MODE_T tracemode )
{
wxPoint center( (start.x + end.x) / 2, (start.y + end.y) / 2 );
wxSize size( end.x - start.x, end.y - start.y );
double orient;
if( size.y == 0 )
orient = 0;
else if( size.x == 0 )
orient = 900;
else
orient = -ArcTangente( size.y, size.x );
size.x = KiROUND( EuclideanNorm( size ) ) + width;
size.y = width;
FlashPadOval( center, size, orient, tracemode, NULL );
}
void PLOTTER::sketchOval( const wxPoint& pos, const wxSize& aSize, double orient, int width )
{
SetCurrentLineWidth( width );
width = currentPenWidth;
int radius, deltaxy, cx, cy;
wxSize size( aSize );
if( size.x > size.y )
{
std::swap( size.x, size.y );
orient = AddAngles( orient, 900 );
}
deltaxy = size.y - size.x; /* distance between centers of the oval */
radius = ( size.x - width ) / 2;
cx = -radius;
cy = -deltaxy / 2;
RotatePoint( &cx, &cy, orient );
MoveTo( wxPoint( cx + pos.x, cy + pos.y ) );
cx = -radius;
cy = deltaxy / 2;
RotatePoint( &cx, &cy, orient );
FinishTo( wxPoint( cx + pos.x, cy + pos.y ) );
cx = radius;
cy = -deltaxy / 2;
RotatePoint( &cx, &cy, orient );
MoveTo( wxPoint( cx + pos.x, cy + pos.y ) );
cx = radius;
cy = deltaxy / 2;
RotatePoint( &cx, &cy, orient );
FinishTo( wxPoint( cx + pos.x, cy + pos.y ) );
cx = 0;
cy = deltaxy / 2;
RotatePoint( &cx, &cy, orient );
Arc( wxPoint( cx + pos.x, cy + pos.y ),
orient + 1800, orient + 3600,
radius, NO_FILL );
cx = 0;
cy = -deltaxy / 2;
RotatePoint( &cx, &cy, orient );
Arc( wxPoint( cx + pos.x, cy + pos.y ),
orient, orient + 1800,
radius, NO_FILL );
}
void PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, int width,
EDA_DRAW_MODE_T tracemode, void* aData )
{
if( tracemode == FILLED )
{
SetCurrentLineWidth( width );
MoveTo( start );
FinishTo( end );
}
else
{
SetCurrentLineWidth( -1 );
segmentAsOval( start, end, width, tracemode );
}
}
void PLOTTER::ThickArc( const wxPoint& centre, double StAngle, double EndAngle,
int radius, int width, EDA_DRAW_MODE_T tracemode, void* aData )
{
if( tracemode == FILLED )
Arc( centre, StAngle, EndAngle, radius, NO_FILL, width );
else
{
SetCurrentLineWidth( -1 );
Arc( centre, StAngle, EndAngle,
radius - ( width - currentPenWidth ) / 2, NO_FILL, -1 );
Arc( centre, StAngle, EndAngle,
radius + ( width - currentPenWidth ) / 2, NO_FILL, -1 );
}
}
void PLOTTER::ThickRect( const wxPoint& p1, const wxPoint& p2, int width,
EDA_DRAW_MODE_T tracemode, void* aData )
{
if( tracemode == FILLED )
Rect( p1, p2, NO_FILL, width );
else
{
SetCurrentLineWidth( -1 );
wxPoint offsetp1( p1.x - (width - currentPenWidth) / 2,
p1.y - (width - currentPenWidth) / 2 );
wxPoint offsetp2( p2.x + (width - currentPenWidth) / 2,
p2.y + (width - currentPenWidth) / 2 );
Rect( offsetp1, offsetp2, NO_FILL, -1 );
offsetp1.x += (width - currentPenWidth);
offsetp1.y += (width - currentPenWidth);
offsetp2.x -= (width - currentPenWidth);
offsetp2.y -= (width - currentPenWidth);
Rect( offsetp1, offsetp2, NO_FILL, -1 );
}
}
void PLOTTER::ThickCircle( const wxPoint& pos, int diametre, int width,
EDA_DRAW_MODE_T tracemode, void* aData )
{
if( tracemode == FILLED )
Circle( pos, diametre, NO_FILL, width );
else
{
SetCurrentLineWidth( -1 );
Circle( pos, diametre - width + currentPenWidth, NO_FILL, -1 );
Circle( pos, diametre + width - currentPenWidth, NO_FILL, -1 );
}
}
void PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill,
int aWidth, void * aData )
{
std::vector< wxPoint > cornerList;
for( int ii = 0; ii < aCornerList.PointCount(); ii++ )
cornerList.push_back( wxPoint( aCornerList.CPoint( ii ) ) );
PlotPoly( cornerList , aFill, aWidth, aData );
}
void PLOTTER::SetPageSettings( const PAGE_INFO& aPageSettings )
{
pageInfo = aPageSettings;
}

View File

@ -0,0 +1,982 @@
/**
* @file common_plotDXF_functions.cpp
* @brief KiCad: Common plot DXF Routines.
*/
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 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 <fctsys.h>
#include <gr_basic.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <class_plotter.h>
#include <macros.h>
#include <kicad_string.h>
#include <convert_basic_shapes_to_polygon.h>
/**
* Oblique angle for DXF native text
* (I don't remember if 15 degrees is the ISO value... it looks nice anyway)
*/
static const double DXF_OBLIQUE_ANGLE = 15;
/* The layer/colors palette. The acad/DXF palette is divided in 3 zones:
- The primary colors (1 - 9)
- An HSV zone (10-250, 5 values x 2 saturations x 10 hues
- Greys (251 - 255)
There is *no* black... the white does it on paper, usually, and
anyway it depends on the plotter configuration, since DXF colors
are meant to be logical only (they represent *both* line color and
width); later version with plot styles only complicate the matter!
As usual, brown and magenta/purple are difficult to place since
they are actually variations of other colors.
*/
static const struct
{
const char *name;
int color;
} dxf_layer[NBCOLORS] =
{
{ "BLACK", 7 }, // In DXF, color 7 is *both* white and black!
{ "GRAY1", 251 },
{ "GRAY2", 8 },
{ "GRAY3", 9 },
{ "WHITE", 7 },
{ "LYELLOW", 51 },
{ "BLUE1", 178 },
{ "GREEN1", 98 },
{ "CYAN1", 138 },
{ "RED1", 18 },
{ "MAGENTA1", 228 },
{ "BROWN1", 58 },
{ "BLUE2", 5 },
{ "GREEN2", 3 },
{ "CYAN2", 4 },
{ "RED2", 1 },
{ "MAGENTA2", 6 },
{ "BROWN2", 54 },
{ "BLUE3", 171 },
{ "GREEN3", 91 },
{ "CYAN3", 131 },
{ "RED3", 11 },
{ "MAGENTA3", 221 },
{ "YELLOW3", 2 },
{ "BLUE4", 5 },
{ "GREEN4", 3 },
{ "CYAN4", 4 },
{ "RED4", 1 },
{ "MAGENTA4", 6 },
{ "YELLOW4", 2 }
};
static const char* getDXFLineType( PlotDashType aType )
{
switch( aType )
{
case PLOTDASHTYPE_SOLID: return "CONTINUOUS";
case PLOTDASHTYPE_DASH: return "DASHED";
case PLOTDASHTYPE_DOT: return "DOTTED";
case PLOTDASHTYPE_DASHDOT: return "DASHDOT";
}
wxFAIL_MSG( "Unhandled PlotDashType" );
return "CONTINUOUS";
}
// A helper function to create a color name acceptable in DXF files
// DXF files do not use a RGB definition
static wxString getDXFColorName( COLOR4D aColor )
{
EDA_COLOR_T color = ColorFindNearest( int( aColor.r*255 ),
int( aColor.g*255 ),
int( aColor.b*255 ) );
wxString cname( dxf_layer[color].name );
return cname;
}
/**
* Set the scale/position for the DXF plot
* The DXF engine doesn't support line widths and mirroring. The output
* coordinate system is in the first quadrant (in mm)
*/
void DXF_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
{
plotOffset = aOffset;
plotScale = aScale;
/* DXF paper is 'virtual' so there is no need of a paper size.
Also this way we can handle the aux origin which can be useful
(for example when aligning to a mechanical drawing) */
paperSize.x = 0;
paperSize.y = 0;
/* Like paper size DXF units are abstract too. Anyway there is a
* system variable (MEASUREMENT) which will be set to 1 to indicate
* metric units */
m_IUsPerDecimil = aIusPerDecimil;
iuPerDeviceUnit = 1.0 / aIusPerDecimil; // Gives a DXF in decimils
iuPerDeviceUnit *= 0.00254; // ... now in mm
SetDefaultLineWidth( 0 ); // No line width on DXF
m_plotMirror = false; // No mirroring on DXF
m_currentColor = COLOR4D::BLACK;
}
/**
* Opens the DXF plot with a skeleton header
*/
bool DXF_PLOTTER::StartPlot()
{
wxASSERT( outputFile );
// DXF HEADER - Boilerplate
// Defines the minimum for drawing i.e. the angle system and the
// 4 linetypes (CONTINUOUS, DOTDASH, DASHED and DOTTED)
fputs( " 0\n"
"SECTION\n"
" 2\n"
"HEADER\n"
" 9\n"
"$ANGBASE\n"
" 50\n"
"0.0\n"
" 9\n"
"$ANGDIR\n"
" 70\n"
" 1\n"
" 9\n"
"$MEASUREMENT\n"
" 70\n"
"0\n"
" 0\n" // This means 'metric units'
"ENDSEC\n"
" 0\n"
"SECTION\n"
" 2\n"
"TABLES\n"
" 0\n"
"TABLE\n"
" 2\n"
"LTYPE\n"
" 70\n"
"4\n"
" 0\n"
"LTYPE\n"
" 5\n"
"40F\n"
" 2\n"
"CONTINUOUS\n"
" 70\n"
"0\n"
" 3\n"
"Solid line\n"
" 72\n"
"65\n"
" 73\n"
"0\n"
" 40\n"
"0.0\n"
" 0\n"
"LTYPE\n"
" 5\n"
"410\n"
" 2\n"
"DASHDOT\n"
" 70\n"
"0\n"
" 3\n"
"Dash Dot ____ _ ____ _\n"
" 72\n"
"65\n"
" 73\n"
"4\n"
" 40\n"
"2.0\n"
" 49\n"
"1.25\n"
" 49\n"
"-0.25\n"
" 49\n"
"0.25\n"
" 49\n"
"-0.25\n"
" 0\n"
"LTYPE\n"
" 5\n"
"411\n"
" 2\n"
"DASHED\n"
" 70\n"
"0\n"
" 3\n"
"Dashed __ __ __ __ __\n"
" 72\n"
"65\n"
" 73\n"
"2\n"
" 40\n"
"0.75\n"
" 49\n"
"0.5\n"
" 49\n"
"-0.25\n"
" 0\n"
"LTYPE\n"
" 5\n"
"43B\n"
" 2\n"
"DOTTED\n"
" 70\n"
"0\n"
" 3\n"
"Dotted . . . .\n"
" 72\n"
"65\n"
" 73\n"
"2\n"
" 40\n"
"0.2\n"
" 49\n"
"0.0\n"
" 49\n"
"-0.2\n"
" 0\n"
"ENDTAB\n",
outputFile );
// Text styles table
// Defines 4 text styles, one for each bold/italic combination
fputs( " 0\n"
"TABLE\n"
" 2\n"
"STYLE\n"
" 70\n"
"4\n", outputFile );
static const char *style_name[4] = {"KICAD", "KICADB", "KICADI", "KICADBI"};
for(int i = 0; i < 4; i++ )
{
fprintf( outputFile,
" 0\n"
"STYLE\n"
" 2\n"
"%s\n" // Style name
" 70\n"
"0\n" // Standard flags
" 40\n"
"0\n" // Non-fixed height text
" 41\n"
"1\n" // Width factor (base)
" 42\n"
"1\n" // Last height (mandatory)
" 50\n"
"%g\n" // Oblique angle
" 71\n"
"0\n" // Generation flags (default)
" 3\n"
// The standard ISO font (when kicad is build with it
// the dxf text in acad matches *perfectly*)
"isocp.shx\n", // Font name (when not bigfont)
// Apply a 15 degree angle to italic text
style_name[i], i < 2 ? 0 : DXF_OBLIQUE_ANGLE );
}
// Layer table - one layer per color
fprintf( outputFile,
" 0\n"
"ENDTAB\n"
" 0\n"
"TABLE\n"
" 2\n"
"LAYER\n"
" 70\n"
"%d\n", NBCOLORS );
/* The layer/colors palette. The acad/DXF palette is divided in 3 zones:
- The primary colors (1 - 9)
- An HSV zone (10-250, 5 values x 2 saturations x 10 hues
- Greys (251 - 255)
*/
for( EDA_COLOR_T i = BLACK; i < NBCOLORS; i = NextColor(i) )
{
fprintf( outputFile,
" 0\n"
"LAYER\n"
" 2\n"
"%s\n" // Layer name
" 70\n"
"0\n" // Standard flags
" 62\n"
"%d\n" // Color number
" 6\n"
"CONTINUOUS\n",// Linetype name
dxf_layer[i].name, dxf_layer[i].color );
}
// End of layer table, begin entities
fputs( " 0\n"
"ENDTAB\n"
" 0\n"
"ENDSEC\n"
" 0\n"
"SECTION\n"
" 2\n"
"ENTITIES\n", outputFile );
return true;
}
bool DXF_PLOTTER::EndPlot()
{
wxASSERT( outputFile );
// DXF FOOTER
fputs( " 0\n"
"ENDSEC\n"
" 0\n"
"EOF\n", outputFile );
fclose( outputFile );
outputFile = NULL;
return true;
}
/**
* The DXF exporter handles 'colors' as layers...
*/
void DXF_PLOTTER::SetColor( COLOR4D color )
{
if( ( colorMode )
|| ( color == COLOR4D::BLACK )
|| ( color == COLOR4D::WHITE ) )
{
m_currentColor = color;
}
else
m_currentColor = COLOR4D::BLACK;
}
/**
* DXF rectangle: fill not supported
*/
void DXF_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
{
wxASSERT( outputFile );
MoveTo( p1 );
LineTo( wxPoint( p1.x, p2.y ) );
LineTo( wxPoint( p2.x, p2.y ) );
LineTo( wxPoint( p2.x, p1.y ) );
FinishTo( wxPoint( p1.x, p1.y ) );
}
/**
* DXF circle: full functionality; it even does 'fills' drawing a
* circle with a dual-arc polyline wide as the radius.
*
* I could use this trick to do other filled primitives
*/
void DXF_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_T fill, int width )
{
wxASSERT( outputFile );
double radius = userToDeviceSize( diameter / 2 );
DPOINT centre_dev = userToDeviceCoordinates( centre );
if( radius > 0 )
{
wxString cname = getDXFColorName( m_currentColor );
if( !fill )
{
fprintf( outputFile, "0\nCIRCLE\n8\n%s\n10\n%g\n20\n%g\n40\n%g\n",
TO_UTF8( cname ),
centre_dev.x, centre_dev.y, radius );
}
if( fill == FILLED_SHAPE )
{
double r = radius*0.5;
fprintf( outputFile, "0\nPOLYLINE\n");
fprintf( outputFile, "8\n%s\n66\n1\n70\n1\n", TO_UTF8( cname ));
fprintf( outputFile, "40\n%g\n41\n%g\n", radius, radius);
fprintf( outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname ));
fprintf( outputFile, "10\n%g\n 20\n%g\n42\n1.0\n",
centre_dev.x-r, centre_dev.y );
fprintf( outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname ));
fprintf( outputFile, "10\n%g\n 20\n%g\n42\n1.0\n",
centre_dev.x+r, centre_dev.y );
fprintf( outputFile, "0\nSEQEND\n");
}
}
}
/**
* DXF polygon: doesn't fill it but at least it close the filled ones
* DXF does not know thick outline.
* It does not know thhick segments, therefore filled polygons with thick outline
* are converted to inflated polygon by aWidth/2
*/
void DXF_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList,
FILL_T aFill, int aWidth, void * aData )
{
if( aCornerList.size() <= 1 )
return;
unsigned last = aCornerList.size() - 1;
// Plot outlines with lines (thickness = 0) to define the polygon
if( aWidth <= 0 )
{
MoveTo( aCornerList[0] );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
LineTo( aCornerList[ii] );
// Close polygon if 'fill' requested
if( aFill )
{
if( aCornerList[last] != aCornerList[0] )
LineTo( aCornerList[0] );
}
PenFinish();
return;
}
// if the polygon outline has thickness, and is not filled
// (i.e. is a polyline) plot outlines with thick segments
if( aWidth > 0 && !aFill )
{
MoveTo( aCornerList[0] );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
ThickSegment( aCornerList[ii-1], aCornerList[ii],
aWidth, FILLED, NULL );
return;
}
// The polygon outline has thickness, and is filled
// Build and plot the polygon which contains the initial
// polygon and its thick outline
SHAPE_POLY_SET bufferOutline;
SHAPE_POLY_SET bufferPolybase;
const int circleToSegmentsCount = 16;
bufferPolybase.NewOutline();
// enter outline as polygon:
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
{
TransformRoundedEndsSegmentToPolygon( bufferOutline,
aCornerList[ii-1], aCornerList[ii], circleToSegmentsCount, aWidth );
}
// enter the initial polygon:
for( unsigned ii = 0; ii < aCornerList.size(); ii++ )
{
bufferPolybase.Append( aCornerList[ii] );
}
// Merge polygons to build the polygon which contains the initial
// polygon and its thick outline
// create the outline which contains thick outline:
bufferPolybase.BooleanAdd( bufferOutline, SHAPE_POLY_SET::PM_FAST );
bufferPolybase.Fracture( SHAPE_POLY_SET::PM_FAST );
if( bufferPolybase.OutlineCount() < 1 ) // should not happen
return;
const SHAPE_LINE_CHAIN& path = bufferPolybase.COutline( 0 );
if( path.PointCount() < 2 ) // should not happen
return;
// Now, output the final polygon to DXF file:
last = path.PointCount() - 1;
VECTOR2I point = path.CPoint( 0 );
wxPoint startPoint( point.x, point.y );
MoveTo( startPoint );
for( int ii = 1; ii < path.PointCount(); ii++ )
{
point = path.CPoint( ii );
LineTo( wxPoint( point.x, point.y ) );
}
// Close polygon, if needed
point = path.CPoint( last );
wxPoint endPoint( point.x, point.y );
if( endPoint != startPoint )
LineTo( startPoint );
PenFinish();
}
void DXF_PLOTTER::PenTo( const wxPoint& pos, char plume )
{
wxASSERT( outputFile );
if( plume == 'Z' )
{
return;
}
DPOINT pos_dev = userToDeviceCoordinates( pos );
DPOINT pen_lastpos_dev = userToDeviceCoordinates( penLastpos );
if( penLastpos != pos && plume == 'D' )
{
wxASSERT( m_currentLineType >= 0 && m_currentLineType < 4 );
// DXF LINE
wxString cname = getDXFColorName( m_currentColor );
const char *lname = getDXFLineType( (PlotDashType) m_currentLineType );
fprintf( outputFile, "0\nLINE\n8\n%s\n6\n%s\n10\n%g\n20\n%g\n11\n%g\n21\n%g\n",
TO_UTF8( cname ), lname,
pen_lastpos_dev.x, pen_lastpos_dev.y, pos_dev.x, pos_dev.y );
}
penLastpos = pos;
}
void DXF_PLOTTER::SetDash( int dashed )
{
wxASSERT( dashed >= 0 && dashed < 4 );
m_currentLineType = dashed;
}
void DXF_PLOTTER::ThickSegment( const wxPoint& aStart, const wxPoint& aEnd, int aWidth,
EDA_DRAW_MODE_T aPlotMode, void* aData )
{
if( aPlotMode == SKETCH )
{
std::vector<wxPoint> cornerList;
SHAPE_POLY_SET outlineBuffer;
TransformOvalClearanceToPolygon( outlineBuffer,
aStart, aEnd, aWidth, 32 , 1.0 );
const SHAPE_LINE_CHAIN& path = outlineBuffer.COutline(0 );
for( int jj = 0; jj < path.PointCount(); jj++ )
cornerList.push_back( wxPoint( path.CPoint( jj ).x , path.CPoint( jj ).y ) );
// Ensure the polygon is closed
if( cornerList[0] != cornerList[cornerList.size() - 1] )
cornerList.push_back( cornerList[0] );
PlotPoly( cornerList, NO_FILL );
}
else
{
MoveTo( aStart );
FinishTo( aEnd );
}
}
/* Plot an arc in DXF format
* Filling is not supported
*/
void DXF_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
FILL_T fill, int width )
{
wxASSERT( outputFile );
if( radius <= 0 )
return;
// In DXF, arcs are drawn CCW.
// In Kicad, arcs are CW or CCW
// If StAngle > EndAngle, it is CW. So transform it to CCW
if( StAngle > EndAngle )
{
std::swap( StAngle, EndAngle );
}
DPOINT centre_dev = userToDeviceCoordinates( centre );
double radius_dev = userToDeviceSize( radius );
// Emit a DXF ARC entity
wxString cname = getDXFColorName( m_currentColor );
fprintf( outputFile,
"0\nARC\n8\n%s\n10\n%g\n20\n%g\n40\n%g\n50\n%g\n51\n%g\n",
TO_UTF8( cname ),
centre_dev.x, centre_dev.y, radius_dev,
StAngle / 10.0, EndAngle / 10.0 );
}
/**
* DXF oval pad: always done in sketch mode
*/
void DXF_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
wxSize size( aSize );
/* The chip is reduced to an oval tablet with size.y > size.x
* (Oval vertical orientation 0) */
if( size.x > size.y )
{
std::swap( size.x, size.y );
orient = AddAngles( orient, 900 );
}
sketchOval( pos, size, orient, -1 );
}
/**
* DXF round pad: always done in sketch mode; it could be filled but it isn't
* pretty if other kinds of pad aren't...
*/
void DXF_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
Circle( pos, diametre, NO_FILL );
}
/**
* DXF rectangular pad: alwayd done in sketch mode
*/
void DXF_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize,
double orient, EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
wxSize size;
int ox, oy, fx, fy;
size.x = padsize.x / 2;
size.y = padsize.y / 2;
if( size.x < 0 )
size.x = 0;
if( size.y < 0 )
size.y = 0;
// If a dimension is zero, the trace is reduced to 1 line
if( size.x == 0 )
{
ox = pos.x;
oy = pos.y - size.y;
RotatePoint( &ox, &oy, pos.x, pos.y, orient );
fx = pos.x;
fy = pos.y + size.y;
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
MoveTo( wxPoint( ox, oy ) );
FinishTo( wxPoint( fx, fy ) );
return;
}
if( size.y == 0 )
{
ox = pos.x - size.x;
oy = pos.y;
RotatePoint( &ox, &oy, pos.x, pos.y, orient );
fx = pos.x + size.x;
fy = pos.y;
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
MoveTo( wxPoint( ox, oy ) );
FinishTo( wxPoint( fx, fy ) );
return;
}
ox = pos.x - size.x;
oy = pos.y - size.y;
RotatePoint( &ox, &oy, pos.x, pos.y, orient );
MoveTo( wxPoint( ox, oy ) );
fx = pos.x - size.x;
fy = pos.y + size.y;
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
LineTo( wxPoint( fx, fy ) );
fx = pos.x + size.x;
fy = pos.y + size.y;
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
LineTo( wxPoint( fx, fy ) );
fx = pos.x + size.x;
fy = pos.y - size.y;
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
LineTo( wxPoint( fx, fy ) );
FinishTo( wxPoint( ox, oy ) );
}
void DXF_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
int aCornerRadius, double aOrient,
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
SHAPE_POLY_SET outline;
const int segmentToCircleCount = 64;
TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient,
aCornerRadius, segmentToCircleCount );
// TransformRoundRectToPolygon creates only one convex polygon
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
MoveTo( wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) );
for( int ii = 1; ii < poly.PointCount(); ++ii )
LineTo( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
FinishTo( wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) );
}
void DXF_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
SHAPE_POLY_SET* aPolygons,
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
{
SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
MoveTo( wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) );
for( int ii = 1; ii < poly.PointCount(); ++ii )
LineTo( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
FinishTo(wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) );
}
}
/**
* DXF trapezoidal pad: only sketch mode is supported
*/
void DXF_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint *aCorners,
double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode, void* aData )
{
wxASSERT( outputFile );
wxPoint coord[4]; /* coord actual corners of a trapezoidal trace */
for( int ii = 0; ii < 4; ii++ )
{
coord[ii] = aCorners[ii];
RotatePoint( &coord[ii], aPadOrient );
coord[ii] += aPadPos;
}
// Plot edge:
MoveTo( coord[0] );
LineTo( coord[1] );
LineTo( coord[2] );
LineTo( coord[3] );
FinishTo( coord[0] );
}
/**
* Checks if a given string contains non-ASCII characters.
* FIXME: the performance of this code is really poor, but in this case it can be
* acceptable because the plot operation is not called very often.
* @param string String to check
* @return true if it contains some non-ASCII character, false if all characters are
* inside ASCII range (<=255).
*/
bool containsNonAsciiChars( const wxString& string )
{
for( unsigned i = 0; i < string.length(); i++ )
{
wchar_t ch = string[i];
if( ch > 255 )
return true;
}
return false;
}
void DXF_PLOTTER::Text( const wxPoint& aPos,
COLOR4D aColor,
const wxString& aText,
double aOrient,
const wxSize& aSize,
enum EDA_TEXT_HJUSTIFY_T aH_justify,
enum EDA_TEXT_VJUSTIFY_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
bool aMultilineAllowed,
void* aData )
{
// Fix me: see how to use DXF text mode for multiline texts
if( aMultilineAllowed && !aText.Contains( wxT( "\n" ) ) )
aMultilineAllowed = false; // the text has only one line.
if( textAsLines || containsNonAsciiChars( aText ) || aMultilineAllowed )
{
// output text as graphics.
// Perhaps multiline texts could be handled as DXF text entity
// but I do not want spend time about this (JPC)
PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify,
aWidth, aItalic, aBold, aMultilineAllowed );
}
else
{
/* Emit text as a text entity. This loses formatting and shape but it's
more useful as a CAD object */
DPOINT origin_dev = userToDeviceCoordinates( aPos );
SetColor( aColor );
wxString cname = getDXFColorName( m_currentColor );
DPOINT size_dev = userToDeviceSize( aSize );
int h_code = 0, v_code = 0;
switch( aH_justify )
{
case GR_TEXT_HJUSTIFY_LEFT:
h_code = 0;
break;
case GR_TEXT_HJUSTIFY_CENTER:
h_code = 1;
break;
case GR_TEXT_HJUSTIFY_RIGHT:
h_code = 2;
break;
}
switch( aV_justify )
{
case GR_TEXT_VJUSTIFY_TOP:
v_code = 3;
break;
case GR_TEXT_VJUSTIFY_CENTER:
v_code = 2;
break;
case GR_TEXT_VJUSTIFY_BOTTOM:
v_code = 1;
break;
}
// Position, size, rotation and alignment
// The two alignment point usages is somewhat idiot (see the DXF ref)
// Anyway since we don't use the fit/aligned options, they're the same
fprintf( outputFile,
" 0\n"
"TEXT\n"
" 7\n"
"%s\n" // Text style
" 8\n"
"%s\n" // Layer name
" 10\n"
"%g\n" // First point X
" 11\n"
"%g\n" // Second point X
" 20\n"
"%g\n" // First point Y
" 21\n"
"%g\n" // Second point Y
" 40\n"
"%g\n" // Text height
" 41\n"
"%g\n" // Width factor
" 50\n"
"%g\n" // Rotation
" 51\n"
"%g\n" // Oblique angle
" 71\n"
"%d\n" // Mirror flags
" 72\n"
"%d\n" // H alignment
" 73\n"
"%d\n", // V alignment
aBold ? (aItalic ? "KICADBI" : "KICADB")
: (aItalic ? "KICADI" : "KICAD"),
TO_UTF8( cname ),
origin_dev.x, origin_dev.x,
origin_dev.y, origin_dev.y,
size_dev.y, fabs( size_dev.x / size_dev.y ),
aOrient / 10.0,
aItalic ? DXF_OBLIQUE_ANGLE : 0,
size_dev.x < 0 ? 2 : 0, // X mirror flag
h_code, v_code );
/* There are two issue in emitting the text:
- Our overline character (~) must be converted to the appropriate
control sequence %%O or %%o
- Text encoding in DXF is more or less unspecified since depends on
the DXF declared version, the acad version reading it *and* some
system variables to be put in the header handled only by newer acads
Also before R15 unicode simply is not supported (you need to use
bigfonts which are a massive PITA). Common denominator solution:
use Latin1 (and however someone could choke on it, anyway). Sorry
for the extended latin people. If somewant want to try fixing this
recent version seems to use UTF-8 (and not UCS2 like the rest of
Windows)
XXX Actually there is a *third* issue: older DXF formats are limited
to 255 bytes records (it was later raised to 2048); since I'm lazy
and text so long is not probable I just don't implement this rule.
If someone is interested in fixing this, you have to emit the first
partial lines with group code 3 (max 250 bytes each) and then finish
with a group code 1 (less than 250 bytes). The DXF refs explains it
in no more details...
*/
bool overlining = false;
fputs( " 1\n", outputFile );
for( unsigned i = 0; i < aText.length(); i++ )
{
/* Here I do a bad thing: writing the output one byte at a time!
but today I'm lazy and I have no idea on how to coerce a Unicode
wxString to spit out latin1 encoded text ...
Atleast stdio is *supposed* to do output buffering, so there is
hope is not too slow */
wchar_t ch = aText[i];
if( ch > 255 )
{
// I can't encode this...
putc( '?', outputFile );
}
else
{
if( ch == '~' )
{
// Handle the overline toggle
fputs( overlining ? "%%o" : "%%O", outputFile );
overlining = !overlining;
}
else
{
putc( ch, outputFile );
}
}
}
putc( '\n', outputFile );
}
}

View File

@ -0,0 +1,974 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2017 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
*/
/**
* @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 <class_plotter.h>
#include <macros.h>
#include <kicad_string.h>
#include <convert_basic_shapes_to_polygon.h>
#include <build_version.h>
#include <plot_auxiliary_data.h>
GERBER_PLOTTER::GERBER_PLOTTER()
{
workFile = NULL;
finalFile = NULL;
currentAperture = apertures.end();
m_apertureAttribute = 0;
// number of digits after the point (number of digits of the mantissa
// Be carefull: the Gerber coordinates are stored in an integer
// so 6 digits (inches) or 5 digits (mm) is a good value
// To avoid overflow, 7 digits (inches) or 6 digits is a max.
// with lower values than 6 digits (inches) or 5 digits (mm),
// Creating self-intersecting polygons from non-intersecting polygons
// happen easily.
m_gerberUnitInch = false;
m_gerberUnitFmt = 6;
m_useX2Attributes = false;
m_useNetAttributes = true;
}
void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
{
wxASSERT( aMirror == false );
m_plotMirror = false;
plotOffset = aOffset;
wxASSERT( aScale == 1 ); // aScale parameter is not used in Gerber
plotScale = 1; // Plot scale is *always* 1.0
m_IUsPerDecimil = aIusPerDecimil;
// gives now a default value to iuPerDeviceUnit (because the units of the caller is now known)
// which could be modified later by calling SetGerberCoordinatesFormat()
iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 );
// 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
}
void GERBER_PLOTTER::SetGerberCoordinatesFormat( int aResolution, bool aUseInches )
{
m_gerberUnitInch = aUseInches;
m_gerberUnitFmt = aResolution;
iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 );
if( ! m_gerberUnitInch )
iuPerDeviceUnit *= 25.4; // gerber output in mm
}
void GERBER_PLOTTER::emitDcode( const DPOINT& pt, int dcode )
{
fprintf( outputFile, "X%dY%dD%02d*\n",
KiROUND( pt.x ), KiROUND( pt.y ), dcode );
}
void GERBER_PLOTTER::clearNetAttribute()
{
// disable a Gerber net attribute (exists only in X2 with net attributes mode).
if( m_objectAttributesDictionnary.empty() ) // No net attribute or not X2 mode
return;
// Remove all net attributes from object attributes dictionnary
fputs( "%TD*%\n", outputFile );
m_objectAttributesDictionnary.clear();
}
void GERBER_PLOTTER::StartBlock( void* aData )
{
// Currently, it is the same as EndBlock(): clear all aperture net attributes
EndBlock( aData );
}
void GERBER_PLOTTER::EndBlock( void* aData )
{
// Remove all net attributes from object attributes dictionnary
clearNetAttribute();
}
void GERBER_PLOTTER::formatNetAttribute( GBR_NETLIST_METADATA* aData )
{
// print a Gerber net attribute record.
// it is added to the object attributes dictionnary
// On file, only modified or new attributes are printed.
if( aData == NULL || !m_useX2Attributes || !m_useNetAttributes )
return;
bool clearDict;
std::string short_attribute_string;
if( !FormatNetAttribute( short_attribute_string, m_objectAttributesDictionnary,
aData, clearDict ) )
return;
if( clearDict )
clearNetAttribute();
if( !short_attribute_string.empty() )
fputs( short_attribute_string.c_str(), outputFile );
}
bool GERBER_PLOTTER::StartPlot()
{
wxASSERT( outputFile );
finalFile = outputFile; // the actual gerber file will be created later
// 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;
for( unsigned ii = 0; ii < m_headerExtraLines.GetCount(); ii++ )
{
if( ! m_headerExtraLines[ii].IsEmpty() )
fprintf( outputFile, "%s\n", TO_UTF8( m_headerExtraLines[ii] ) );
}
// Set coordinate format to 3.6 or 4.5 absolute, leading zero omitted
// the number of digits for the integer part of coordintes is needed
// in gerber format, but is not very important when omitting leading zeros
// It is fixed here to 3 (inch) or 4 (mm), but is not actually used
int leadingDigitCount = m_gerberUnitInch ? 3 : 4;
fprintf( outputFile, "%%FSLAX%d%dY%d%d*%%\n",
leadingDigitCount, m_gerberUnitFmt,
leadingDigitCount, m_gerberUnitFmt );
fprintf( outputFile,
"G04 Gerber Fmt %d.%d, Leading zero omitted, Abs format (unit %s)*\n",
leadingDigitCount, m_gerberUnitFmt,
m_gerberUnitInch ? "inch" : "mm" );
wxString Title = creator + wxT( " " ) + GetBuildVersion();
fprintf( outputFile, "G04 Created by KiCad (%s) date %s*\n",
TO_UTF8( Title ), TO_UTF8( DateAndTime() ) );
/* Mass parameter: unit = INCHES/MM */
if( m_gerberUnitInch )
fputs( "%MOIN*%\n", outputFile );
else
fputs( "%MOMM*%\n", outputFile );
// Be sure the usual dark polarity is selected:
fputs( "%LPD*%\n", outputFile );
// Specify linear interpol (G01):
fputs( "G01*\n", outputFile );
fputs( "G04 APERTURE LIST*\n", outputFile );
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, void* aData )
{
if( width == DO_NOT_SET_LINE_WIDTH )
return;
int pen_width;
if( width > 0 )
pen_width = width;
else
pen_width = defaultPenWidth;
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
int aperture_attribute = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
selectAperture( wxSize( pen_width, pen_width ), APERTURE::Plotting, aperture_attribute );
currentPenWidth = pen_width;
}
std::vector<APERTURE>::iterator GERBER_PLOTTER::getAperture( const wxSize& aSize,
APERTURE::APERTURE_TYPE aType, int aApertureAttribute )
{
int last_D_code = 9;
// Search an existing aperture
std::vector<APERTURE>::iterator tool = apertures.begin();
while( tool != apertures.end() )
{
last_D_code = tool->m_DCode;
if( (tool->m_Type == aType) && (tool->m_Size == aSize) && (tool->m_ApertureAttribute == aApertureAttribute) )
return tool;
++tool;
}
// Allocate a new aperture
APERTURE new_tool;
new_tool.m_Size = aSize;
new_tool.m_Type = aType;
new_tool.m_DCode = last_D_code + 1;
new_tool.m_ApertureAttribute = aApertureAttribute;
apertures.push_back( new_tool );
return apertures.end() - 1;
}
void GERBER_PLOTTER::selectAperture( const wxSize& aSize,
APERTURE::APERTURE_TYPE aType,
int aApertureAttribute )
{
bool change = ( currentAperture == apertures.end() ) ||
( currentAperture->m_Type != aType ) ||
( currentAperture->m_Size != aSize );
if( !m_useX2Attributes || !m_useNetAttributes )
aApertureAttribute = 0;
else
change = change || ( currentAperture->m_ApertureAttribute != aApertureAttribute );
if( change )
{
// Pick an existing aperture or create a new one
currentAperture = getAperture( aSize, aType, aApertureAttribute );
fprintf( outputFile, "D%d*\n", currentAperture->m_DCode );
}
}
void GERBER_PLOTTER::writeApertureList()
{
wxASSERT( outputFile );
char cbuf[1024];
// Init
for( std::vector<APERTURE>::iterator tool = apertures.begin();
tool != apertures.end(); ++tool )
{
// apertude sizes are in inch or mm, regardless the
// coordinates format
double fscale = 0.0001 * plotScale / m_IUsPerDecimil; // inches
if(! m_gerberUnitInch )
fscale *= 25.4; // size in mm
int attribute = tool->m_ApertureAttribute;
if( attribute != m_apertureAttribute )
fputs( GBR_APERTURE_METADATA::FormatAttribute(
(GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB) attribute ).c_str(), outputFile );
char* text = cbuf + sprintf( cbuf, "%%ADD%d", tool->m_DCode );
/* Please note: the Gerber specs for mass parameters say that
exponential syntax is *not* allowed and the decimal point should
also be always inserted. So the %g format is ruled out, but %f is fine
(the # modifier forces the decimal point). Sadly the %f formatter
can't remove trailing zeros but thats not a problem, since nothing
forbid it (the file is only slightly longer) */
switch( tool->m_Type )
{
case APERTURE::Circle:
sprintf( text, "C,%#f*%%\n", tool->m_Size.x * fscale );
break;
case APERTURE::Rect:
sprintf( text, "R,%#fX%#f*%%\n",
tool->m_Size.x * fscale,
tool->m_Size.y * fscale );
break;
case APERTURE::Plotting:
sprintf( text, "C,%#f*%%\n", tool->m_Size.x * fscale );
break;
case APERTURE::Oval:
sprintf( text, "O,%#fX%#f*%%\n",
tool->m_Size.x * fscale,
tool->m_Size.y * fscale );
break;
}
fputs( cbuf, outputFile );
m_apertureAttribute = attribute;
// Currently reset the aperture attribute. Perhaps a better optimization
// is to store the last attribute
if( attribute )
{
fputs( "%TD*%\n", outputFile );
m_apertureAttribute = 0;
}
}
}
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, double aStAngle, double aEndAngle,
int aRadius, FILL_T aFill, int aWidth )
{
SetCurrentLineWidth( aWidth );
wxPoint start, end;
start.x = aCenter.x + KiROUND( cosdecideg( aRadius, aStAngle ) );
start.y = aCenter.y - KiROUND( sindecideg( aRadius, aStAngle ) );
MoveTo( start );
end.x = aCenter.x + KiROUND( cosdecideg( aRadius, aEndAngle ) );
end.y = aCenter.y - KiROUND( sindecideg( aRadius, aEndAngle ) );
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",
KiROUND( devEnd.x ), KiROUND( devEnd.y ),
KiROUND( devCenter.x ), KiROUND( devCenter.y ) );
fprintf( outputFile, "G01*\n" ); // Back to linear interp.
}
void GERBER_PLOTTER:: PlotPoly( const std::vector< wxPoint >& aCornerList,
FILL_T aFill, int aWidth, void * aData )
{
if( aCornerList.size() <= 1 )
return;
// Gerber format does not know filled polygons with thick outline
// Therefore, to plot a filled polygon with outline having a thickness,
// one should plot outline as thick segments
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
SetCurrentLineWidth( aWidth, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
if( aFill )
{
fputs( "G36*\n", outputFile );
MoveTo( aCornerList[0] );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
LineTo( aCornerList[ii] );
FinishTo( aCornerList[0] );
fputs( "G37*\n", outputFile );
}
if( aWidth > 0 )
{
MoveTo( aCornerList[0] );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
LineTo( aCornerList[ii] );
// Ensure the thick outline is closed for filled polygons
// (if not filled, could be only a polyline)
if( aFill && ( aCornerList[aCornerList.size()-1] != aCornerList[0] ) )
LineTo( aCornerList[0] );
PenFinish();
}
}
void GERBER_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, int width,
EDA_DRAW_MODE_T tracemode, void* aData )
{
if( tracemode == FILLED )
{
GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
SetCurrentLineWidth( width, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
MoveTo( start );
FinishTo( end );
}
else
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
segmentAsOval( start, end, width, tracemode );
}
}
void GERBER_PLOTTER::ThickArc( const wxPoint& centre, double StAngle, double EndAngle,
int radius, int width, EDA_DRAW_MODE_T tracemode, void* aData )
{
GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
SetCurrentLineWidth( width, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
if( tracemode == FILLED )
Arc( centre, StAngle, EndAngle, radius, NO_FILL, DO_NOT_SET_LINE_WIDTH );
else
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
Arc( centre, StAngle, EndAngle,
radius - ( width - currentPenWidth ) / 2,
NO_FILL, DO_NOT_SET_LINE_WIDTH );
Arc( centre, StAngle, EndAngle,
radius + ( width - currentPenWidth ) / 2, NO_FILL,
DO_NOT_SET_LINE_WIDTH );
}
}
void GERBER_PLOTTER::ThickRect( const wxPoint& p1, const wxPoint& p2, int width,
EDA_DRAW_MODE_T tracemode, void* aData )
{
GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
SetCurrentLineWidth( width, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
if( tracemode == FILLED )
Rect( p1, p2, NO_FILL, DO_NOT_SET_LINE_WIDTH );
else
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
wxPoint offsetp1( p1.x - (width - currentPenWidth) / 2,
p1.y - (width - currentPenWidth) / 2 );
wxPoint offsetp2( p2.x + (width - currentPenWidth) / 2,
p2.y + (width - currentPenWidth) / 2 );
Rect( offsetp1, offsetp2, NO_FILL, -1 );
offsetp1.x += (width - currentPenWidth);
offsetp1.y += (width - currentPenWidth);
offsetp2.x -= (width - currentPenWidth);
offsetp2.y -= (width - currentPenWidth);
Rect( offsetp1, offsetp2, NO_FILL, DO_NOT_SET_LINE_WIDTH );
}
}
void GERBER_PLOTTER::ThickCircle( const wxPoint& pos, int diametre, int width,
EDA_DRAW_MODE_T tracemode, void* aData )
{
GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
SetCurrentLineWidth( width, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
if( tracemode == FILLED )
Circle( pos, diametre, NO_FILL, DO_NOT_SET_LINE_WIDTH );
else
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata );
Circle( pos, diametre - (width - currentPenWidth),
NO_FILL, DO_NOT_SET_LINE_WIDTH );
Circle( pos, diametre + (width - currentPenWidth),
NO_FILL, DO_NOT_SET_LINE_WIDTH );
}
}
void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxSize size( diametre, diametre );
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
if( trace_mode == SKETCH )
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
Circle( pos, diametre - currentPenWidth, NO_FILL, DO_NOT_SET_LINE_WIDTH );
}
else
{
DPOINT pos_dev = userToDeviceCoordinates( pos );
int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
selectAperture( size, APERTURE::Circle, aperture_attrib );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
emitDcode( pos_dev, 3 );
}
}
void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
int x0, y0, x1, y1, delta;
wxSize size( aSize );
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
/* 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. */
std::swap( size.x, size.y );
DPOINT pos_dev = userToDeviceCoordinates( pos );
int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
selectAperture( size, APERTURE::Oval, aperture_attrib );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
emitDcode( pos_dev, 3 );
}
else /* Plot pad as a segment. */
{
if( size.x > size.y )
{
std::swap( size.x, size.y );
if( orient < 2700 )
orient += 900;
else
orient -= 2700;
}
if( trace_mode == FILLED )
{
// TODO: use an aperture macro to declare the rotated pad
//
// Flash a pad anchor, if a netlist attribute is set
if( aData )
FlashPadCircle( pos, size.x, trace_mode, aData );
// The pad is reduced to an segment 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 );
GBR_METADATA metadata;
if( gbr_metadata )
{
metadata = *gbr_metadata;
// If the pad is drawn on a copper layer,
// set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR
if( metadata.IsCopper() )
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
// Clear .P attribute, only allowed for flashed items
wxString attrname( ".P" );
metadata.m_NetlistMetadata.ClearAttribute( &attrname );
}
ThickSegment( wxPoint( pos.x + x0, pos.y + y0 ),
wxPoint( pos.x + x1, pos.y + y1 ),
size.x, trace_mode, &metadata );
}
else
{
sketchOval( pos, size, orient, -1 );
}
}
}
void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
double orient, EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
wxSize size( aSize );
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
// Plot as an aperture flash
switch( int( orient ) )
{
case 900:
case 2700: // rotation of 90 degrees or 270 swaps sizes
std::swap( size.x, size.y );
// Pass through
case 0:
case 1800:
if( trace_mode == SKETCH )
{
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
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 );
}
else
{
DPOINT pos_dev = userToDeviceCoordinates( pos );
int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
selectAperture( size, APERTURE::Rect, aperture_attrib );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
emitDcode( pos_dev, 3 );
}
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, aData );
}
break;
}
}
void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
int aCornerRadius, double aOrient,
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
// Currently, a Pad RoundRect is plotted as polygon.
// TODO: use Aperture macro and flash it
SHAPE_POLY_SET outline;
const int segmentToCircleCount = 64;
TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient,
aCornerRadius, segmentToCircleCount );
std::vector< wxPoint > cornerList;
cornerList.reserve( segmentToCircleCount + 5 );
// TransformRoundRectToPolygon creates only one convex polygon
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
for( int ii = 0; ii < poly.PointCount(); ++ii )
cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
// Close polygon
cornerList.push_back( cornerList[0] );
GBR_METADATA gbr_metadata;
if( aData )
{
gbr_metadata = *static_cast<GBR_METADATA*>( aData );
// If the pad is drawn on a copper layer,
// set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR
if( gbr_metadata.IsCopper() )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
wxString attrname( ".P" );
gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers
}
PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
// Now, flash a pad anchor, if a netlist attribute is set
// (remove me when a Aperture macro will be used)
if( aData && aTraceMode == FILLED )
{
int diameter = std::min( aSize.x, aSize.y );
FlashPadCircle( aPadPos, diameter, aTraceMode , aData );
}
}
void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
SHAPE_POLY_SET* aPolygons,
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
// A Pad custom is plotted as polygon.
// A flashed circle @aPadPos is added (anchor pad)
// However, because the anchor pad can be circle or rect, we use only
// a circle not bigger than the rect.
// the main purpose is to print a flashed DCode as pad anchor
FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData );
GBR_METADATA gbr_metadata;
if( aData )
{
gbr_metadata = *static_cast<GBR_METADATA*>( aData );
// If the pad is drawn on a copper layer,
// set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR
if( gbr_metadata.IsCopper() )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
wxString attrname( ".P" );
gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers
}
std::vector< wxPoint > cornerList;
for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
{
SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
cornerList.clear();
for( int ii = 0; ii < poly.PointCount(); ++ii )
cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
// Close polygon
cornerList.push_back( cornerList[0] );
PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
}
}
void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners,
double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode, void* aData )
{
// Currently, a Pad Trapezoid is plotted as polygon.
// TODO: use Aperture macro and flash it
// polygon corners list
std::vector< wxPoint > cornerList;
for( int ii = 0; ii < 4; ii++ )
cornerList.push_back( aCorners[ii] );
// Now, flash a pad anchor, if a netlist attribute is set
// (remove me when a Aperture macro will be used)
if( aData && (aTrace_Mode==FILLED) )
{
// Calculate the radius of the circle inside the shape
// It is the smaller dist from shape pos to edges
int radius = INT_MAX;
for( unsigned ii = 0, jj = cornerList.size()-1; ii < cornerList.size();
jj = ii, ii++ )
{
SEG segment( aCorners[ii], aCorners[jj] );
int dist = segment.LineDistance( VECTOR2I( 0, 0) );
radius = std::min( radius, dist );
}
FlashPadCircle( aPadPos, radius*2, aTrace_Mode, aData );
}
// 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] );
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
GBR_METADATA metadata;
if( gbr_metadata )
{
metadata = *gbr_metadata;
// If the pad is drawn on a copper layer,
// set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR
if( metadata.IsCopper() )
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
wxString attrname( ".P" );
metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers
}
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &metadata );
PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &metadata );
}
void GERBER_PLOTTER::Text( const wxPoint& aPos, const COLOR4D aColor,
const wxString& aText, double aOrient, const wxSize& aSize,
enum EDA_TEXT_HJUSTIFY_T aH_justify, enum EDA_TEXT_VJUSTIFY_T aV_justify,
int aWidth, bool aItalic, bool aBold, bool aMultilineAllowed,
void* aData )
{
GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
if( gbr_metadata )
formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
PLOTTER::Text( aPos, aColor, aText, aOrient, aSize,
aH_justify, aV_justify, aWidth, aItalic, aBold, aMultilineAllowed, aData );
}
void GERBER_PLOTTER::SetLayerPolarity( bool aPositive )
{
if( aPositive )
fprintf( outputFile, "%%LPD*%%\n" );
else
fprintf( outputFile, "%%LPC*%%\n" );
}

View File

@ -0,0 +1,693 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2017 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
*/
/**
* @file common_plotHPGL_functions.cpp
* @brief KiCad: Common plot HPGL Routines
* Since this plot engine is mostly intended for import in external programs,
* sadly HPGL/2 isn't supported a lot... some of the primitives use overlapped
* strokes to fill the shape
*/
/* Some HPGL commands:
* Note: the HPGL unit is 25 micrometers
* All commands MUST be terminated by a semi-colon or a linefeed.
* Spaces can NOT be substituted for required commas in the syntax of a command.
*
*
* AA (Arc Absolute): Angle is a floating point # (requires non integer value)
* Draws an arc with the center at (X,Y).
* A positive angle creates a counter-clockwise arc.
* If the chord angle is specified,
* this will be the number of degrees used for stepping around the arc.
* If no value is given then a default value of five degrees is used.
* AA x, y, a {,b};
*
* AR (Arc Relative):
* AR Dx, Dy, a {, b};
*
* CA (Alternate Character Set):
* CA {n};
*
* CI (Circle):
* CI r {,b};
*
* CP (Character Plot):
* CP {h, v};
* h [-127.9999 .. 127.9999] Anzahl der Zeichen horizontal
* v [-127.9999 .. 127.9999] Anzahl der Zeichen vertikal
*
* CS (Standard Character Set):
* CS {n};
*
* DR (Relative Direction for Label Text):
* DR s, a;
*
* DI (Absolute Direction for Label Text):
* DI {s, a};
*
* DT (Define Terminator - this character becomes unavailable except to terminate a label string.
* Default is ^C control-C):
* DT t;
*
* EA (rEctangle Absolute - Unfilled, from current position to diagonal x,y):
* EA x, y;
*
* ER (rEctangle Relative - Unfilled, from current position to diagonal x,y):
* ER x,y;
*
* FT (Fill Type):
* FT {s {,l {a}}};
*
* IM (Input Mask):
* IM {f};
*
* IN (Initialize): This command instructs the controller to begin processing the HPGL plot file.
* Without this, the commands in the file are received but never executed.
* If multiple IN s are found during execution of the file,
* the controller performs a Pause/Cancel operation.
* All motion from the previous job, yet to be executed, is lost,
* and the new information is executed.
* IN;
*
* IP Input P1 and P2:
* IP {P1x, P1y {, P2x, P2y}};
*
* IW (Input Window):
* IW {XUL, YUL, XOR, YOR};
*
* LB (Label):
* LB c1 .. cn t;
*
* PA (Plot Absolute): Moves to an absolute HPGL position and sets absolute mode for
* future PU and PD commands. If no arguments follow the command,
* only absolute mode is set.
* PA {x1, y1 {{PU|PD|,} ..., ..., xn, yn}};
* P1x, P1y, P2x, P2y [Integer in ASCII]
*
* PD (Pen Down): Executes <current pen> pen then moves to the requested position
* if one is specified. This position is dependent on whether absolute
* or relative mode is set. This command performs no motion in 3-D mode,
* but the outputs and feedrates are affected.
* PD {x, y};
*
* PM Polygon mode
* associated commands:
* PM2 End polygon mode
* FP Fill polygon
* EP Draw polygon outline
*
* PR (Plot Relative): Moves to the relative position specified and sets relative mode
* for future PU and PD commands.
* If no arguments follow the command, only relative mode is set.
* PR {Dx1, Dy1 {{PU|PD|,} ..., ..., Dxn, Dyn}};
*
* PS (Paper Size):
* PS {n};
*
* PT (Pen Thickness): in mm
* PT {l};
*
* PU (Pen Up): Executes <current pen> pen then moves to the requested position
* if one is specified. This position is dependent on whether absolute
* or relative mode is set.
* This command performs no motion in 3-D mode, but the outputs
* and feedrates are affected.
* PU {x, y};
*
* RA (Rectangle Absolute - Filled, from current position to diagonal x,y):
* RA x, y;
*
* RO (Rotate Coordinate System):
* RO;
*
* RR (Rectangle Relative - Filled, from current position to diagonal x,y):
* RR x, y;
*
* SA (Select Alternate Set):
* SA;
*
* SC (Scale):
* SC {Xmin, Xmax, Ymin, Ymax};
*
* SI (Absolute Character Size):
* SI b, h;
* b [-127.9999 .. 127.9999, keine 0]
* h [-127.9999 .. 127.9999, keine 0]
*
* SL (Character Slant):
* SL {a};
* a [-3.5 .. -0.5, 0.5 .. 3.5]
*
* SP (Select Pen): Selects a new pen or tool for use.
* If no pen number or a value of zero is given,
* the controller performs an EOF (end of file command).
* Once an EOF is performed, no motion is executed,
* until a new IN command is received.
* SP n;
*
* SR (Relative Character Size):
* SR {b, h};
* b [-127.9999 .. 127.9999, keine 0]
* h [-127.9999 .. 127.9999, keine 0]
*
* SS (Select Standard Set):
* SS;
*
* TL (Tick Length):
* TL {tp {, tm}};
*
* UC (User Defined Character):
* UC {i,} x1, y1, {i,} x2, y2, ... {i,} xn, yn;
*
* VS (Velocity Select):
* VS {v {, n}};
* v [1 .. 40] in cm/s
* n [1 .. 8]
*
* XT (X Tick):
* XT;
*
* YT (Y Tick):
* YT;
*/
#include <fctsys.h>
#include <gr_basic.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <class_plotter.h>
#include <macros.h>
#include <kicad_string.h>
#include <convert_basic_shapes_to_polygon.h>
// The hpgl command to close a polygon def, fill it and plot outline:
// PM 2; ends the polygon definition and closes it if not closed
// FP; fills the polygon
// EP; draws the polygon outline. It usually gives a better look to the filled polygon
static const char hpgl_end_polygon_cmd[] = "PM 2; FP; EP;\n";
// HPGL scale factor (1 PLU = 1/40mm = 25 micrometers)
static const double PLUsPERDECIMIL = 0.102041;
HPGL_PLOTTER::HPGL_PLOTTER()
{
SetPenSpeed( 40 ); // Default pen speed = 40 cm/s; Pen speed is *always* in cm
SetPenNumber( 1 ); // Default pen num = 1
SetPenDiameter( 0.0 );
}
void HPGL_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
{
plotOffset = aOffset;
plotScale = aScale;
m_IUsPerDecimil = aIusPerDecimil;
iuPerDeviceUnit = PLUsPERDECIMIL / aIusPerDecimil;
/* Compute the paper size in IUs */
paperSize = pageInfo.GetSizeMils();
paperSize.x *= 10.0 * aIusPerDecimil;
paperSize.y *= 10.0 * aIusPerDecimil;
SetDefaultLineWidth( 0 ); // HPGL has pen sizes instead
m_plotMirror = aMirror;
}
/**
* At the start of the HPGL plot pen speed and number are requested
*/
bool HPGL_PLOTTER::StartPlot()
{
wxASSERT( outputFile );
fprintf( outputFile, "IN;VS%d;PU;PA;SP%d;\n", penSpeed, penNumber );
// Set HPGL Pen Thickness (in mm) (usefull in polygon fill command)
double penThicknessMM = userToDeviceSize( penDiameter )/40;
fprintf( outputFile, "PT %.1f;\n", penThicknessMM );
return true;
}
/**
* HPGL end of plot: pen return and release
*/
bool HPGL_PLOTTER::EndPlot()
{
wxASSERT( outputFile );
fputs( "PU;PA;SP0;\n", outputFile );
fclose( outputFile );
outputFile = NULL;
return true;
}
void HPGL_PLOTTER::SetPenDiameter( double diameter )
{
penDiameter = diameter;
}
/**
* HPGL rectangle: fill not supported
*/
void HPGL_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
{
wxASSERT( outputFile );
DPOINT p2dev = userToDeviceCoordinates( p2 );
MoveTo( p1 );
fprintf( outputFile, "EA %.0f,%.0f;\n", p2dev.x, p2dev.y );
PenFinish();
}
// HPGL circle
void HPGL_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_T fill,
int width )
{
wxASSERT( outputFile );
double radius = userToDeviceSize( diameter / 2 );
SetCurrentLineWidth( width );
if( fill == FILLED_SHAPE )
{
// Draw the filled area
MoveTo( centre );
fprintf( outputFile, "PM 0; CI %g;\n", radius );
fprintf( outputFile, hpgl_end_polygon_cmd ); // Close, fill polygon and draw outlines
PenFinish();
}
if( radius > 0 )
{
MoveTo( centre );
fprintf( outputFile, "CI %g;\n", radius );
PenFinish();
}
}
/**
* HPGL polygon:
*/
void HPGL_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList,
FILL_T aFill, int aWidth, void * aData )
{
if( aCornerList.size() <= 1 )
return;
SetCurrentLineWidth( aWidth );
MoveTo( aCornerList[0] );
if( aFill == FILLED_SHAPE )
{
// Draw the filled area
SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
fprintf( outputFile, "PM 0;\n" ); // Start polygon
for( unsigned ii = 1; ii < aCornerList.size(); ++ii )
LineTo( aCornerList[ii] );
int ii = aCornerList.size() - 1;
if( aCornerList[ii] != aCornerList[0] )
LineTo( aCornerList[0] );
fprintf( outputFile, hpgl_end_polygon_cmd ); // Close, fill polygon and draw outlines
}
else
{
// Plot only the polygon outline.
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
LineTo( aCornerList[ii] );
// Always close polygon if filled.
if( aFill )
{
int ii = aCornerList.size() - 1;
if( aCornerList[ii] != aCornerList[0] )
LineTo( aCornerList[0] );
}
}
PenFinish();
}
/**
* Pen control logic (remove redundant pen activations)
*/
void HPGL_PLOTTER::penControl( char plume )
{
wxASSERT( outputFile );
switch( plume )
{
case 'U':
if( penState != 'U' )
{
fputs( "PU;", outputFile );
penState = 'U';
}
break;
case 'D':
if( penState != 'D' )
{
fputs( "PD;", outputFile );
penState = 'D';
}
break;
case 'Z':
fputs( "PU;", outputFile );
penState = 'U';
penLastpos.x = -1;
penLastpos.y = -1;
break;
}
}
void HPGL_PLOTTER::PenTo( const wxPoint& pos, char plume )
{
wxASSERT( outputFile );
if( plume == 'Z' )
{
penControl( 'Z' );
return;
}
penControl( plume );
DPOINT pos_dev = userToDeviceCoordinates( pos );
if( penLastpos != pos )
fprintf( outputFile, "PA %.0f,%.0f;\n", pos_dev.x, pos_dev.y );
penLastpos = pos;
}
/**
* HPGL supports dashed lines
*/
void HPGL_PLOTTER::SetDash( int dashed )
{
wxASSERT( outputFile );
switch( dashed )
{
case PLOTDASHTYPE_DASH:
fprintf( outputFile, "LT -2 4 1;\n" );
break;
case PLOTDASHTYPE_DOT:
fprintf( outputFile, "LT -1 2 1;\n" );
break;
case PLOTDASHTYPE_DASHDOT:
fprintf( outputFile, "LT -4 6 1;\n" );
break;
default:
fputs( "LT;\n", outputFile );
}
}
void HPGL_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end,
int width, EDA_DRAW_MODE_T tracemode, void* aData )
{
wxASSERT( outputFile );
wxPoint center;
wxSize size;
// Suppress overlap if pen is too big
if( penDiameter >= width )
{
MoveTo( start );
FinishTo( end );
}
else
segmentAsOval( start, end, width, tracemode );
}
/* Plot an arc:
* Center = center coord
* Stangl, endAngle = angle of beginning and end
* Radius = radius of the arc
* Command
* PU PY x, y; PD start_arc_X AA, start_arc_Y, angle, NbSegm; PU;
* Or PU PY x, y; PD start_arc_X AA, start_arc_Y, angle, PU;
*/
void HPGL_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
FILL_T fill, int width )
{
wxASSERT( outputFile );
double angle;
if( radius <= 0 )
return;
DPOINT centre_dev = userToDeviceCoordinates( centre );
if( m_plotMirror )
angle = StAngle - EndAngle;
else
angle = EndAngle - StAngle;
NORMALIZE_ANGLE_180( angle );
angle /= 10;
// Calculate arc start point:
wxPoint cmap;
cmap.x = centre.x + KiROUND( cosdecideg( radius, StAngle ) );
cmap.y = centre.y - KiROUND( sindecideg( radius, StAngle ) );
DPOINT cmap_dev = userToDeviceCoordinates( cmap );
fprintf( outputFile,
"PU;PA %.0f,%.0f;PD;AA %.0f,%.0f,",
cmap_dev.x, cmap_dev.y,
centre_dev.x, centre_dev.y );
fprintf( outputFile, "%.0f", angle );
fprintf( outputFile, ";PU;\n" );
PenFinish();
}
/* Plot oval pad.
*/
void HPGL_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
int deltaxy, cx, cy;
wxSize size( aSize );
/* The pad will be drawn as an oblong shape with size.y > size.x
* (Oval vertical orientation 0)
*/
if( size.x > size.y )
{
std::swap( size.x, size.y );
orient = AddAngles( orient, 900 );
}
deltaxy = size.y - size.x; // distance between centers of the oval
if( trace_mode == FILLED )
{
FlashPadRect( pos, wxSize( size.x, deltaxy + KiROUND( penDiameter ) ),
orient, trace_mode, aData );
cx = 0; cy = deltaxy / 2;
RotatePoint( &cx, &cy, orient );
FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData );
cx = 0; cy = -deltaxy / 2;
RotatePoint( &cx, &cy, orient );
FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData );
}
else // Plot in outline mode.
{
sketchOval( pos, size, orient, KiROUND( penDiameter ) );
}
}
/* Plot round pad or via.
*/
void HPGL_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
EDA_DRAW_MODE_T trace_mode, void* aData )
{
wxASSERT( outputFile );
DPOINT pos_dev = userToDeviceCoordinates( pos );
int radius = diametre / 2;
if( trace_mode == FILLED )
{
// if filled mode, the pen diameter is removed from diameter
// to keep the pad size
radius -= KiROUND( penDiameter ) / 2;
}
if( radius < 0 )
radius = 0;
double rsize = userToDeviceSize( radius );
if( trace_mode == FILLED ) // Plot in filled mode.
{
// A filled polygon uses always the current point to start the polygon.
// Gives a correct current starting point for the circle
MoveTo( wxPoint( pos.x+radius, pos.y ) );
// Plot filled area and its outline
fprintf( outputFile, "PM 0; PA %.0f,%.0f;CI %.0f;%s",
pos_dev.x, pos_dev.y, rsize, hpgl_end_polygon_cmd );
}
else
{
// Draw outline only:
fprintf( outputFile, "PA %.0f,%.0f;CI %.0f;\n",
pos_dev.x, pos_dev.y, rsize );
}
PenFinish();
}
void HPGL_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize,
double orient, EDA_DRAW_MODE_T trace_mode, void* aData )
{
// Build rect polygon:
std::vector<wxPoint> corners;
int dx = padsize.x / 2;
int dy = padsize.y / 2;
if( trace_mode == FILLED )
{
// in filled mode, the pen diameter is removed from size
// to compensate the extra size due to this pen size
dx -= KiROUND( penDiameter ) / 2;
dx = std::max( dx, 0);
dy -= KiROUND( penDiameter ) / 2;
dy = std::max( dy, 0);
}
corners.push_back( wxPoint( - dx, - dy ) );
corners.push_back( wxPoint( - dx, + dy ) );
corners.push_back( wxPoint( + dx, + dy ) );
corners.push_back( wxPoint( + dx, - dy ) );
for( unsigned ii = 0; ii < corners.size(); ii++ )
{
RotatePoint( &corners[ii], orient );
corners[ii] += pos;
}
PlotPoly( corners, trace_mode == FILLED ? FILLED_SHAPE : NO_FILL );
}
void HPGL_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
int aCornerRadius, double aOrient,
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
SHAPE_POLY_SET outline;
const int segmentToCircleCount = 32;
wxSize size = aSize;
if( aTraceMode == FILLED )
{
// in filled mode, the pen diameter is removed from size
// to keep the pad size
size.x -= KiROUND( penDiameter ) / 2;
size.x = std::max( size.x, 0);
size.y -= KiROUND( penDiameter ) / 2;
size.y = std::max( size.y, 0);
// keep aCornerRadius to a value < min size x,y < 2:
aCornerRadius = std::min( aCornerRadius, std::min( size.x, size.y ) /2 );
}
TransformRoundRectToPolygon( outline, aPadPos, size, aOrient,
aCornerRadius, segmentToCircleCount );
// TransformRoundRectToPolygon creates only one convex polygon
std::vector< wxPoint > cornerList;
cornerList.reserve( segmentToCircleCount + 4 );
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
for( int ii = 0; ii < poly.PointCount(); ++ii )
cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL );
}
void HPGL_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
SHAPE_POLY_SET* aPolygons,
EDA_DRAW_MODE_T aTraceMode, void* aData )
{
std::vector< wxPoint > cornerList;
for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
{
SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
cornerList.clear();
cornerList.reserve( poly.PointCount() );
for( int ii = 1; ii < poly.PointCount(); ++ii )
cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL );
}
}
void HPGL_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners,
double aPadOrient, EDA_DRAW_MODE_T aTraceMode, void* aData )
{
std::vector< wxPoint > cornerList;
cornerList.reserve( 4 );
for( int ii = 0; ii < 4; ii++ )
{
wxPoint coord( aCorners[ii] );
RotatePoint( &coord, aPadOrient );
coord += aPadPos;
cornerList.push_back( coord );
}
PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL );
}

View File

@ -0,0 +1,857 @@
/**
* @file common_plotPDF_functions.cpp
* @brief Kicad: Common plot PDF Routines
*/
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2012 Lorenzo Marcantonio, l.marcantonio@logossrl.com
* Copyright (C) 1992-2017 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 <fctsys.h>
#include <pgm_base.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <common.h>
#include <class_plotter.h>
#include <macros.h>
#include <kicad_string.h>
#include <wx/zstream.h>
#include <wx/mstream.h>
/*
* Open or create the plot file aFullFilename
* return true if success, false if the file cannot be created/opened
*
* Opens the PDF file in binary mode
*/
bool PDF_PLOTTER::OpenFile( const wxString& aFullFilename )
{
filename = aFullFilename;
wxASSERT( !outputFile );
// Open the PDF file in binary mode
outputFile = wxFopen( filename, wxT( "wb" ) );
if( outputFile == NULL )
return false ;
return true;
}
void PDF_PLOTTER::SetPageSettings( const PAGE_INFO& aPageSettings )
{
pageInfo = aPageSettings;
}
void PDF_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
{
m_plotMirror = aMirror;
plotOffset = aOffset;
plotScale = aScale;
m_IUsPerDecimil = aIusPerDecimil;
// The CTM is set to 1 user unit per decimil
iuPerDeviceUnit = 1.0 / aIusPerDecimil;
SetDefaultLineWidth( 100 / iuPerDeviceUnit ); // arbitrary default
/* The paper size in this engined is handled page by page
Look in the StartPage function */
}
/**
* Pen width setting for PDF. Since the specs *explicitly* says that a 0
* width is a bad thing to use (since it results in 1 pixel traces), we
* convert such requests to the minimal width (like 1)
* Note pen width = 0 is used in plot polygons to plot filled polygons with
* no outline thickness
* use in this case pen width = 1 does not actally change the polygon
*/
void PDF_PLOTTER::SetCurrentLineWidth( int width, void* aData )
{
wxASSERT( workFile );
int pen_width;
if( width > 0 )
pen_width = width;
else if( width == 0 )
pen_width = 1;
else
pen_width = defaultPenWidth;
if( pen_width != currentPenWidth )
fprintf( workFile, "%g w\n",
userToDeviceSize( pen_width ) );
currentPenWidth = pen_width;
}
/**
* PDF supports colors fully. It actually has distinct fill and pen colors,
* but we set both at the same time.
*
* XXX Keeping them divided could result in a minor optimization in
* eeschema filled shapes, but would propagate to all the other plot
* engines. Also arcs are filled as pies but only the arc is stroked so
* it would be difficult to handle anyway.
*/
void PDF_PLOTTER::emitSetRGBColor( double r, double g, double b )
{
wxASSERT( workFile );
fprintf( workFile, "%g %g %g rg %g %g %g RG\n",
r, g, b, r, g, b );
}
/**
* PDF supports dashed lines
*/
void PDF_PLOTTER::SetDash( int dashed )
{
wxASSERT( workFile );
switch( dashed )
{
case PLOTDASHTYPE_DASH:
fprintf( workFile, "[%d %d] 0 d\n",
(int) GetDashMarkLenIU(), (int) GetDashGapLenIU() );
break;
case PLOTDASHTYPE_DOT:
fprintf( workFile, "[%d %d] 0 d\n",
(int) GetDotMarkLenIU(), (int) GetDashGapLenIU() );
break;
case PLOTDASHTYPE_DASHDOT:
fprintf( workFile, "[%d %d %d %d] 0 d\n",
(int) GetDashMarkLenIU(), (int) GetDashGapLenIU(),
(int) GetDotMarkLenIU(), (int) GetDashGapLenIU() );
break;
default:
fputs( "[] 0 d\n", workFile );
}
}
/**
* Rectangles in PDF. Supported by the native operator
*/
void PDF_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
{
wxASSERT( workFile );
DPOINT p1_dev = userToDeviceCoordinates( p1 );
DPOINT p2_dev = userToDeviceCoordinates( p2 );
SetCurrentLineWidth( width );
fprintf( workFile, "%g %g %g %g re %c\n", p1_dev.x, p1_dev.y,
p2_dev.x - p1_dev.x, p2_dev.y - p1_dev.y,
fill == NO_FILL ? 'S' : 'B' );
}
/**
* Circle drawing for PDF. They're approximated by curves, but fill is supported
*/
void PDF_PLOTTER::Circle( const wxPoint& pos, int diametre, FILL_T aFill, int width )
{
wxASSERT( workFile );
DPOINT pos_dev = userToDeviceCoordinates( pos );
double radius = userToDeviceSize( diametre / 2.0 );
/* OK. Here's a trick. PDF doesn't support circles or circular angles, that's
a fact. You'll have to do with cubic beziers. These *can't* represent
circular arcs (NURBS can, beziers don't). But there is a widely known
approximation which is really good
*/
SetCurrentLineWidth( width );
double magic = radius * 0.551784; // You don't want to know where this come from
// This is the convex hull for the bezier approximated circle
fprintf( workFile, "%g %g m "
"%g %g %g %g %g %g c "
"%g %g %g %g %g %g c "
"%g %g %g %g %g %g c "
"%g %g %g %g %g %g c %c\n",
pos_dev.x - radius, pos_dev.y,
pos_dev.x - radius, pos_dev.y + magic,
pos_dev.x - magic, pos_dev.y + radius,
pos_dev.x, pos_dev.y + radius,
pos_dev.x + magic, pos_dev.y + radius,
pos_dev.x + radius, pos_dev.y + magic,
pos_dev.x + radius, pos_dev.y,
pos_dev.x + radius, pos_dev.y - magic,
pos_dev.x + magic, pos_dev.y - radius,
pos_dev.x, pos_dev.y - radius,
pos_dev.x - magic, pos_dev.y - radius,
pos_dev.x - radius, pos_dev.y - magic,
pos_dev.x - radius, pos_dev.y,
aFill == NO_FILL ? 's' : 'b' );
}
/**
* The PDF engine can't directly plot arcs, it uses the base emulation.
* So no filled arcs (not a great loss... )
*/
void PDF_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
FILL_T fill, int width )
{
wxASSERT( workFile );
if( radius <= 0 )
return;
/* Arcs are not so easily approximated by beziers (in the general case),
so we approximate them in the old way */
wxPoint start, end;
const int delta = 50; // increment (in 0.1 degrees) to draw circles
if( StAngle > EndAngle )
std::swap( StAngle, EndAngle );
SetCurrentLineWidth( width );
// Usual trig arc plotting routine...
start.x = centre.x + KiROUND( cosdecideg( radius, -StAngle ) );
start.y = centre.y + KiROUND( sindecideg( radius, -StAngle ) );
DPOINT pos_dev = userToDeviceCoordinates( start );
fprintf( workFile, "%g %g m ", pos_dev.x, pos_dev.y );
for( int ii = StAngle + delta; ii < EndAngle; ii += delta )
{
end.x = centre.x + KiROUND( cosdecideg( radius, -ii ) );
end.y = centre.y + KiROUND( sindecideg( radius, -ii ) );
pos_dev = userToDeviceCoordinates( end );
fprintf( workFile, "%g %g l ", pos_dev.x, pos_dev.y );
}
end.x = centre.x + KiROUND( cosdecideg( radius, -EndAngle ) );
end.y = centre.y + KiROUND( sindecideg( radius, -EndAngle ) );
pos_dev = userToDeviceCoordinates( end );
fprintf( workFile, "%g %g l ", pos_dev.x, pos_dev.y );
// The arc is drawn... if not filled we stroke it, otherwise we finish
// closing the pie at the center
if( fill == NO_FILL )
{
fputs( "S\n", workFile );
}
else
{
pos_dev = userToDeviceCoordinates( centre );
fprintf( workFile, "%g %g l b\n", pos_dev.x, pos_dev.y );
}
}
/**
* Polygon plotting for PDF. Everything is supported
*/
void PDF_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList,
FILL_T aFill, int aWidth, void * aData )
{
wxASSERT( workFile );
if( aCornerList.size() <= 1 )
return;
SetCurrentLineWidth( aWidth );
DPOINT pos = userToDeviceCoordinates( aCornerList[0] );
fprintf( workFile, "%g %g m\n", pos.x, pos.y );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
{
pos = userToDeviceCoordinates( aCornerList[ii] );
fprintf( workFile, "%g %g l\n", pos.x, pos.y );
}
// Close path and stroke(/fill)
fprintf( workFile, "%c\n", aFill == NO_FILL ? 'S' : 'b' );
}
void PDF_PLOTTER::PenTo( const wxPoint& pos, char plume )
{
wxASSERT( workFile );
if( plume == 'Z' )
{
if( penState != 'Z' )
{
fputs( "S\n", workFile );
penState = 'Z';
penLastpos.x = -1;
penLastpos.y = -1;
}
return;
}
if( penState != plume || pos != penLastpos )
{
DPOINT pos_dev = userToDeviceCoordinates( pos );
fprintf( workFile, "%g %g %c\n",
pos_dev.x, pos_dev.y,
( plume=='D' ) ? 'l' : 'm' );
}
penState = plume;
penLastpos = pos;
}
/**
* PDF images are handles as inline, not XObject streams...
*/
void PDF_PLOTTER::PlotImage( const wxImage & aImage, const wxPoint& aPos,
double aScaleFactor )
{
wxASSERT( workFile );
wxSize pix_size( aImage.GetWidth(), aImage.GetHeight() );
// Requested size (in IUs)
DPOINT drawsize( aScaleFactor * pix_size.x,
aScaleFactor * pix_size.y );
// calculate the bitmap start position
wxPoint start( aPos.x - drawsize.x / 2,
aPos.y + drawsize.y / 2);
DPOINT dev_start = userToDeviceCoordinates( start );
/* PDF has an uhm... simplified coordinate system handling. There is
*one* operator to do everything (the PS concat equivalent). At least
they kept the matrix stack to save restore environments. Also images
are always emitted at the origin with a size of 1x1 user units.
What we need to do is:
1) save the CTM end estabilish the new one
2) plot the image
3) restore the CTM
4) profit
*/
fprintf( workFile, "q %g 0 0 %g %g %g cm\n", // Step 1
userToDeviceSize( drawsize.x ),
userToDeviceSize( drawsize.y ),
dev_start.x, dev_start.y );
/* An inline image is a cross between a dictionary and a stream.
A real ugly construct (compared with the elegance of the PDF
format). Also it accepts some 'abbreviations', which is stupid
since the content stream is usually compressed anyway... */
fprintf( workFile,
"BI\n"
" /BPC 8\n"
" /CS %s\n"
" /W %d\n"
" /H %d\n"
"ID\n", colorMode ? "/RGB" : "/G", pix_size.x, pix_size.y );
/* Here comes the stream (in binary!). I *could* have hex or ascii84
encoded it, but who cares? I'll go through zlib anyway */
for( int y = 0; y < pix_size.y; y++ )
{
for( int x = 0; x < pix_size.x; x++ )
{
unsigned char r = aImage.GetRed( x, y ) & 0xFF;
unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
// As usual these days, stdio buffering has to suffeeeeerrrr
if( colorMode )
{
putc( r, workFile );
putc( g, workFile );
putc( b, workFile );
}
else
{
// Grayscale conversion
putc( (r + g + b) / 3, workFile );
}
}
}
fputs( "EI Q\n", workFile ); // Finish step 2 and do step 3
}
/**
* Allocate a new handle in the table of the PDF object. The
* handle must be completed using startPdfObject. It's an in-RAM operation
* only, no output is done.
*/
int PDF_PLOTTER::allocPdfObject()
{
xrefTable.push_back( 0 );
return xrefTable.size() - 1;
}
/**
* Open a new PDF object and returns the handle if the parameter is -1.
* Otherwise fill in the xref entry for the passed object
*/
int PDF_PLOTTER::startPdfObject(int handle)
{
wxASSERT( outputFile );
wxASSERT( !workFile );
if( handle < 0)
handle = allocPdfObject();
xrefTable[handle] = ftell( outputFile );
fprintf( outputFile, "%d 0 obj\n", handle );
return handle;
}
/**
* Close the current PDF object
*/
void PDF_PLOTTER::closePdfObject()
{
wxASSERT( outputFile );
wxASSERT( !workFile );
fputs( "endobj\n", outputFile );
}
/**
* Starts a PDF stream (for the page). Returns the object handle opened
* Pass -1 (default) for a fresh object. Especially from PDF 1.5 streams
* can contain a lot of things, but for the moment we only handle page
* content.
*/
int PDF_PLOTTER::startPdfStream(int handle)
{
wxASSERT( outputFile );
wxASSERT( !workFile );
handle = startPdfObject( handle );
// This is guaranteed to be handle+1 but needs to be allocated since
// you could allocate more object during stream preparation
streamLengthHandle = allocPdfObject();
fprintf( outputFile,
"<< /Length %d 0 R /Filter /FlateDecode >>\n" // Length is deferred
"stream\n", handle + 1 );
// Open a temporary file to accumulate the stream
workFilename = filename + wxT(".tmp");
workFile = wxFopen( workFilename, wxT( "w+b" ));
wxASSERT( workFile );
return handle;
}
/**
* Finish the current PDF stream (writes the deferred length, too)
*/
void PDF_PLOTTER::closePdfStream()
{
wxASSERT( workFile );
long stream_len = ftell( workFile );
if( stream_len < 0 )
{
wxASSERT( false );
return;
}
// Rewind the file, read in the page stream and DEFLATE it
fseek( workFile, 0, SEEK_SET );
unsigned char *inbuf = new unsigned char[stream_len];
int rc = fread( inbuf, 1, stream_len, workFile );
wxASSERT( rc == stream_len );
(void) rc;
// We are done with the temporary file, junk it
fclose( workFile );
workFile = 0;
::wxRemoveFile( workFilename );
// NULL means memos owns the memory, but provide a hint on optimum size needed.
wxMemoryOutputStream memos( NULL, std::max( 2000l, stream_len ) ) ;
{
/* Somewhat standard parameters to compress in DEFLATE. The PDF spec is
* misleading, it says it wants a DEFLATE stream but it really want a ZLIB
* stream! (a DEFLATE stream would be generated with -15 instead of 15)
* rc = deflateInit2( &zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, 15,
* 8, Z_DEFAULT_STRATEGY );
*/
wxZlibOutputStream zos( memos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
zos.Write( inbuf, stream_len );
delete[] inbuf;
} // flush the zip stream using zos destructor
wxStreamBuffer* sb = memos.GetOutputStreamBuffer();
unsigned out_count = sb->Tell();
fwrite( sb->GetBufferStart(), 1, out_count, outputFile );
fputs( "endstream\n", outputFile );
closePdfObject();
// Writing the deferred length as an indirect object
startPdfObject( streamLengthHandle );
fprintf( outputFile, "%u\n", out_count );
closePdfObject();
}
/**
* Starts a new page in the PDF document
*/
void PDF_PLOTTER::StartPage()
{
wxASSERT( outputFile );
wxASSERT( !workFile );
// Compute the paper size in IUs
paperSize = pageInfo.GetSizeMils();
paperSize.x *= 10.0 / iuPerDeviceUnit;
paperSize.y *= 10.0 / iuPerDeviceUnit;
// Open the content stream; the page object will go later
pageStreamHandle = startPdfStream();
/* Now, until ClosePage *everything* must be wrote in workFile, to be
compressed later in closePdfStream */
// Default graphic settings (coordinate system, default color and line style)
fprintf( workFile,
"%g 0 0 %g 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG %g w\n",
0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY,
userToDeviceSize( defaultPenWidth ) );
}
/**
* Close the current page in the PDF document (and emit its compressed stream)
*/
void PDF_PLOTTER::ClosePage()
{
wxASSERT( workFile );
// Close the page stream (and compress it)
closePdfStream();
// Emit the page object and put it in the page list for later
pageHandles.push_back( startPdfObject() );
/* Page size is in 1/72 of inch (default user space units)
Works like the bbox in postscript but there is no need for
swapping the sizes, since PDF doesn't require a portrait page.
We use the MediaBox but PDF has lots of other less used boxes
to use */
const double BIGPTsPERMIL = 0.072;
wxSize psPaperSize = pageInfo.GetSizeMils();
fprintf( outputFile,
"<<\n"
"/Type /Page\n"
"/Parent %d 0 R\n"
"/Resources <<\n"
" /ProcSet [/PDF /Text /ImageC /ImageB]\n"
" /Font %d 0 R >>\n"
"/MediaBox [0 0 %d %d]\n"
"/Contents %d 0 R\n"
">>\n",
pageTreeHandle,
fontResDictHandle,
int( ceil( psPaperSize.x * BIGPTsPERMIL ) ),
int( ceil( psPaperSize.y * BIGPTsPERMIL ) ),
pageStreamHandle );
closePdfObject();
// Mark the page stream as idle
pageStreamHandle = 0;
}
/**
* The PDF engine supports multiple pages; the first one is opened
* 'for free' the following are to be closed and reopened. Between
* each page parameters can be set
*/
bool PDF_PLOTTER::StartPlot()
{
wxASSERT( outputFile );
// First things first: the customary null object
xrefTable.clear();
xrefTable.push_back( 0 );
/* The header (that's easy!). The second line is binary junk required
to make the file binary from the beginning (the important thing is
that they must have the bit 7 set) */
fputs( "%PDF-1.5\n%\200\201\202\203\n", outputFile );
/* Allocate an entry for the page tree root, it will go in every page
parent entry */
pageTreeHandle = allocPdfObject();
/* In the same way, the font resource dictionary is used by every page
(it *could* be inherited via the Pages tree */
fontResDictHandle = allocPdfObject();
/* Now, the PDF is read from the end, (more or less)... so we start
with the page stream for page 1. Other more important stuff is written
at the end */
StartPage();
return true;
}
bool PDF_PLOTTER::EndPlot()
{
wxASSERT( outputFile );
// Close the current page (often the only one)
ClosePage();
/* We need to declare the resources we're using (fonts in particular)
The useful standard one is the Helvetica family. Adding external fonts
is *very* involved! */
struct {
const char *psname;
const char *rsname;
int font_handle;
} fontdefs[4] = {
{ "/Helvetica", "/KicadFont", 0 },
{ "/Helvetica-Oblique", "/KicadFontI", 0 },
{ "/Helvetica-Bold", "/KicadFontB", 0 },
{ "/Helvetica-BoldOblique", "/KicadFontBI", 0 }
};
/* Declare the font resources. Since they're builtin fonts, no descriptors (yay!)
We'll need metrics anyway to do any aligment (these are in the shared with
the postscript engine) */
for( int i = 0; i < 4; i++ )
{
fontdefs[i].font_handle = startPdfObject();
fprintf( outputFile,
"<< /BaseFont %s\n"
" /Type /Font\n"
" /Subtype /Type1\n"
/* Adobe is so Mac-based that the nearest thing to Latin1 is
the Windows ANSI encoding! */
" /Encoding /WinAnsiEncoding\n"
">>\n",
fontdefs[i].psname );
closePdfObject();
}
// Named font dictionary (was allocated, now we emit it)
startPdfObject( fontResDictHandle );
fputs( "<<\n", outputFile );
for( int i = 0; i < 4; i++ )
{
fprintf( outputFile, " %s %d 0 R\n",
fontdefs[i].rsname, fontdefs[i].font_handle );
}
fputs( ">>\n", outputFile );
closePdfObject();
/* The page tree: it's a B-tree but luckily we only have few pages!
So we use just an array... The handle was allocated at the beginning,
now we instantiate the corresponding object */
startPdfObject( pageTreeHandle );
fputs( "<<\n"
"/Type /Pages\n"
"/Kids [\n", outputFile );
for( unsigned i = 0; i < pageHandles.size(); i++ )
fprintf( outputFile, "%d 0 R\n", pageHandles[i] );
fprintf( outputFile,
"]\n"
"/Count %ld\n"
">>\n", (long) pageHandles.size() );
closePdfObject();
// The info dictionary
int infoDictHandle = startPdfObject();
char date_buf[250];
time_t ltime = time( NULL );
strftime( date_buf, 250, "D:%Y%m%d%H%M%S",
localtime( &ltime ) );
if( title.IsEmpty() )
{
// Windows uses '\' and other platforms ue '/' as sepatator
title = filename.AfterLast('\\');
title = title.AfterLast('/');
}
fprintf( outputFile,
"<<\n"
"/Producer (KiCAD PDF)\n"
"/CreationDate (%s)\n"
"/Creator (%s)\n"
"/Title (%s)\n"
"/Trapped false\n",
date_buf,
TO_UTF8( creator ),
TO_UTF8( title ) );
fputs( ">>\n", outputFile );
closePdfObject();
// The catalog, at last
int catalogHandle = startPdfObject();
fprintf( outputFile,
"<<\n"
"/Type /Catalog\n"
"/Pages %d 0 R\n"
"/Version /1.5\n"
"/PageMode /UseNone\n"
"/PageLayout /SinglePage\n"
">>\n", pageTreeHandle );
closePdfObject();
/* Emit the xref table (format is crucial to the byte, each entry must
be 20 bytes long, and object zero must be done in that way). Also
the offset must be kept along for the trailer */
long xref_start = ftell( outputFile );
fprintf( outputFile,
"xref\n"
"0 %ld\n"
"0000000000 65535 f \n", (long) xrefTable.size() );
for( unsigned i = 1; i < xrefTable.size(); i++ )
{
fprintf( outputFile, "%010ld 00000 n \n", xrefTable[i] );
}
// Done the xref, go for the trailer
fprintf( outputFile,
"trailer\n"
"<< /Size %lu /Root %d 0 R /Info %d 0 R >>\n"
"startxref\n"
"%ld\n" // The offset we saved before
"%%%%EOF\n",
(unsigned long) xrefTable.size(), catalogHandle, infoDictHandle, xref_start );
fclose( outputFile );
outputFile = NULL;
return true;
}
void PDF_PLOTTER::Text( const wxPoint& aPos,
const COLOR4D aColor,
const wxString& aText,
double aOrient,
const wxSize& aSize,
enum EDA_TEXT_HJUSTIFY_T aH_justify,
enum EDA_TEXT_VJUSTIFY_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
bool aMultilineAllowed,
void* aData )
{
// PDF files do not like 0 sized texts which create broken files.
if( aSize.x == 0 || aSize.y == 0 )
return;
// Fix me: see how to use PDF text mode for multiline texts
if( aMultilineAllowed && !aText.Contains( wxT( "\n" ) ) )
aMultilineAllowed = false; // the text has only one line.
// Emit native PDF text (if requested)
// Currently: is not supported, because only our stroke font is alloxed: disable it
// However, shadowed texts (searchable texts) works reasonably well because
// pixel accurate precision is not requested, so we add searchable texts
// behind our stroked font texts
bool use_native_font = false;
// render_mode 0 shows the text, render_mode 3 is invisible
int render_mode = use_native_font ? 0 : 3;
const char *fontname = aItalic ? (aBold ? "/KicadFontBI" : "/KicadFontI")
: (aBold ? "/KicadFontB" : "/KicadFont");
// Compute the copious tranformation parameters of the Curent Transform Matrix
double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
double wideningFactor, heightFactor;
computeTextParameters( aPos, aText, aOrient, aSize, m_plotMirror, aH_justify,
aV_justify, aWidth, aItalic, aBold,
&wideningFactor, &ctm_a, &ctm_b, &ctm_c,
&ctm_d, &ctm_e, &ctm_f, &heightFactor );
SetColor( aColor );
SetCurrentLineWidth( aWidth, aData );
/* We use the full CTM instead of the text matrix because the same
coordinate system will be used for the overlining. Also the %f
for the trig part of the matrix to avoid %g going in exponential
format (which is not supported)
render_mode 0 shows the text, render_mode 3 is invisible */
fprintf( workFile, "q %f %f %f %f %g %g cm BT %s %g Tf %d Tr %g Tz ",
ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f,
fontname, heightFactor, render_mode,
wideningFactor * 100 );
// The text must be escaped correctly
fputsPostscriptString( workFile, aText );
fputs( " Tj ET\n", workFile );
// We are in text coordinates, plot the overbars, if we're not doing phantom text
if( use_native_font )
{
std::vector<int> pos_pairs;
postscriptOverlinePositions( aText, aSize.x, aItalic, aBold, &pos_pairs );
int overbar_y = KiROUND( aSize.y * 1.1 );
for( unsigned i = 0; i < pos_pairs.size(); i += 2)
{
/* This is a nontrivial situation: we are *not* in the user
coordinate system, so the userToDeviceCoordinates function
can't be used! Strange as it may seem, the userToDeviceSize
is the right function to use here... */
DPOINT dev_from = userToDeviceSize( wxSize( pos_pairs[i], overbar_y ) );
DPOINT dev_to = userToDeviceSize( wxSize( pos_pairs[i + 1], overbar_y ) );
fprintf( workFile, "%g %g m %g %g l ",
dev_from.x, dev_from.y, dev_to.x, dev_to.y );
}
}
// Stroke and restore the CTM
fputs( "S Q\n", workFile );
// Plot the stroked text (if requested)
if( !use_native_font )
{
PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify,
aWidth, aItalic, aBold, aMultilineAllowed );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,645 @@
/**
* @file common_plotPS_functions.cpp
* @brief Kicad: Common plot SVG functions
*/
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 1992-2017 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
*/
/* Some info on basic items SVG format, used here:
* The root element of all SVG files is the <svg> element.
*
* The <g> element is used to group SVG shapes together.
* Once grouped you can transform the whole group of shapes as if it was a single shape.
* This is an advantage compared to a nested <svg> element
* which cannot be the target of transformation by itself.
*
* The <rect> element represents a rectangle.
* Using this element you can draw rectangles of various width, height,
* with different stroke (outline) and fill colors, with sharp or rounded corners etc.
*
* <svg xmlns="http://www.w3.org/2000/svg"
* xmlns:xlink="http://www.w3.org/1999/xlink">
*
* <rect x="10" y="10" height="100" width="100"
* style="stroke:#006600; fill: #00cc00"/>
*
* </svg>
*
* The <circle> element is used to draw circles.
* <circle cx="40" cy="40" r="24" style="stroke:#006600; fill:#00cc00"/>
*
* The <ellipse> element is used to draw ellipses.
* An ellipse is a circle that does not have equal height and width.
* Its radius in the x and y directions are different, in other words.
* <ellipse cx="40" cy="40" rx="30" ry="15"
* style="stroke:#006600; fill:#00cc00"/>
*
* The <line> element is used to draw lines.
*
* <line x1="0" y1="10" x2="0" y2="100" style="stroke:#006600;"/>
* <line x1="10" y1="10" x2="100" y2="100" style="stroke:#006600;"/>
*
* The <polyline> element is used to draw multiple connected lines
* Here is a simple example:
*
* <polyline points="0,0 30,0 15,30" style="stroke:#006600;"/>
*
* The <polygon> element is used to draw with multiple (3 or more) sides / edges.
* Here is a simple example:
*
* <polygon points="0,0 50,0 25,50" style="stroke:#660000; fill:#cc3333;"/>
*
* The <path> element is used to draw advanced shapes combined from lines and arcs,
* with or without fill.
* It is probably the most advanced and versatile SVG shape of them all.
* It is probably also the hardest element to master.
* <path d="M50,50
* A30,30 0 0,1 35,20
* L100,100
* M110,110
* L100,0"
* style="stroke:#660000; fill:none;"/>
*
* Draw an elliptic arc: it is one of basic path command:
* <path d="M(startx,starty) A(radiusx,radiusy)
* rotation-axe-x
* flag_arc_large,flag_sweep endx,endy">
* flag_arc_large: 0 = small arc > 180 deg, 1 = large arc > 180 deg
* flag_sweep : 0 = CCW, 1 = CW
* The center of ellipse is automatically calculated.
*/
#include <fctsys.h>
#include <trigo.h>
#include <wxstruct.h>
#include <class_eda_rect.h>
#include <base_struct.h>
#include <common.h>
#include <class_plotter.h>
#include <macros.h>
#include <kicad_string.h>
/**
* Function XmlEsc
* translates '<' to "&lt;", '>' to "&gt;" and so on, according to the spec:
* http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
* May be moved to a library if needed generally, but not expecting that.
*/
static wxString XmlEsc( const wxString& aStr, bool isAttribute = false )
{
wxString escaped;
escaped.reserve( aStr.length() );
for( wxString::const_iterator it = aStr.begin(); it != aStr.end(); ++it )
{
const wxChar c = *it;
switch( c )
{
case wxS( '<' ):
escaped.append( wxS( "&lt;" ) );
break;
case wxS( '>' ):
escaped.append( wxS( "&gt;" ) );
break;
case wxS( '&' ):
escaped.append( wxS( "&amp;" ) );
break;
case wxS( '\r' ):
escaped.append( wxS( "&#xD;" ) );
break;
default:
if( isAttribute )
{
switch( c )
{
case wxS( '"' ):
escaped.append( wxS( "&quot;" ) );
break;
case wxS( '\t' ):
escaped.append( wxS( "&#x9;" ) );
break;
case wxS( '\n' ):
escaped.append( wxS( "&#xA;" ));
break;
default:
escaped.append(c);
}
}
else
escaped.append(c);
}
}
return escaped;
}
SVG_PLOTTER::SVG_PLOTTER()
{
m_graphics_changed = true;
SetTextMode( PLOTTEXTMODE_STROKE );
m_fillMode = NO_FILL; // or FILLED_SHAPE or FILLED_WITH_BG_BODYCOLOR
m_pen_rgb_color = 0; // current color value (black)
m_brush_rgb_color = 0; // current color value (black)
m_dashed = false;
}
void SVG_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
{
m_plotMirror = aMirror;
m_yaxisReversed = true; // unlike other plotters, SVG has Y axis reversed
plotOffset = aOffset;
plotScale = aScale;
m_IUsPerDecimil = aIusPerDecimil;
iuPerDeviceUnit = 1.0 / aIusPerDecimil;
/* Compute the paper size in IUs */
paperSize = pageInfo.GetSizeMils();
paperSize.x *= 10.0 * aIusPerDecimil;
paperSize.y *= 10.0 * aIusPerDecimil;
SetDefaultLineWidth( 100 * aIusPerDecimil ); // arbitrary default
}
void SVG_PLOTTER::SetColor( COLOR4D color )
{
PSLIKE_PLOTTER::SetColor( color );
if( m_graphics_changed )
setSVGPlotStyle();
}
void SVG_PLOTTER::setFillMode( FILL_T fill )
{
if( m_fillMode != fill )
{
m_graphics_changed = true;
m_fillMode = fill;
}
}
void SVG_PLOTTER::setSVGPlotStyle()
{
fputs( "</g>\n<g style=\"", outputFile );
fputs( "fill:#", outputFile );
// output the background fill color
fprintf( outputFile, "%6.6lX; ", m_brush_rgb_color );
switch( m_fillMode )
{
case NO_FILL:
fputs( "fill-opacity:0.0; ", outputFile );
break;
case FILLED_SHAPE:
fputs( "fill-opacity:1.0; ", outputFile );
break;
case FILLED_WITH_BG_BODYCOLOR:
fputs( "fill-opacity:0.6; ", outputFile );
break;
}
double pen_w = userToDeviceSize( GetCurrentLineWidth() );
fprintf( outputFile, "\nstroke:#%6.6lX; stroke-width:%g; stroke-opacity:1; \n",
m_pen_rgb_color, pen_w );
fputs( "stroke-linecap:round; stroke-linejoin:round;", outputFile );
switch( m_dashed )
{
case PLOTDASHTYPE_DASH:
fprintf( outputFile, "stroke-dasharray:%g,%g;",
GetDashMarkLenIU(), GetDashGapLenIU() );
break;
case PLOTDASHTYPE_DOT:
fprintf( outputFile, "stroke-dasharray:%g,%g;",
GetDotMarkLenIU(), GetDashGapLenIU() );
break;
case PLOTDASHTYPE_DASHDOT:
fprintf( outputFile, "stroke-dasharray:%g,%g,%g,%g;",
GetDashMarkLenIU(), GetDashGapLenIU(), GetDotMarkLenIU(), GetDashGapLenIU() );
break;
}
fputs( "\">\n", outputFile );
m_graphics_changed = false;
}
/* Set the current line width (in IUs) for the next plot
*/
void SVG_PLOTTER::SetCurrentLineWidth( int width, void* aData )
{
int pen_width;
if( width >= 0 )
pen_width = width;
else
pen_width = defaultPenWidth;
if( pen_width != currentPenWidth )
{
m_graphics_changed = true;
currentPenWidth = pen_width;
}
if( m_graphics_changed )
setSVGPlotStyle();
}
/* initialize m_red, m_green, m_blue ( 0 ... 255)
* from reduced values r, g ,b ( 0.0 to 1.0 )
*/
void SVG_PLOTTER::emitSetRGBColor( double r, double g, double b )
{
int red = (int) ( 255.0 * r );
int green = (int) ( 255.0 * g );
int blue = (int) ( 255.0 * b );
long rgb_color = (red << 16) | (green << 8) | blue;
if( m_pen_rgb_color != rgb_color )
{
m_graphics_changed = true;
m_pen_rgb_color = rgb_color;
// Currently, use the same color for brush and pen
// (i.e. to draw and fill a contour)
m_brush_rgb_color = rgb_color;
}
}
/**
* SVG supports dashed lines
*/
void SVG_PLOTTER::SetDash( int dashed )
{
if( m_dashed != dashed )
{
m_graphics_changed = true;
m_dashed = dashed;
}
if( m_graphics_changed )
setSVGPlotStyle();
}
void SVG_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
{
EDA_RECT rect( p1, wxSize( p2.x -p1.x, p2.y -p1.y ) );
rect.Normalize();
DPOINT org_dev = userToDeviceCoordinates( rect.GetOrigin() );
DPOINT end_dev = userToDeviceCoordinates( rect.GetEnd() );
DSIZE size_dev = end_dev - org_dev;
// Ensure size of rect in device coordinates is > 0
// I don't know if this is a SVG issue or a Inkscape issue, but
// Inkscape has problems with negative or null values for width and/or height, so avoid them
DBOX rect_dev( org_dev, size_dev);
rect_dev.Normalize();
setFillMode( fill );
SetCurrentLineWidth( width );
// Rectangles having a 0 size value for height or width are just not drawn on Inscape,
// so use a line when happens.
if( rect_dev.GetSize().x == 0.0 || rect_dev.GetSize().y == 0.0 ) // Draw a line
fprintf( outputFile,
"<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\" />\n",
rect_dev.GetPosition().x, rect_dev.GetPosition().y,
rect_dev.GetEnd().x, rect_dev.GetEnd().y
);
else
fprintf( outputFile,
"<rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" rx=\"%g\" />\n",
rect_dev.GetPosition().x, rect_dev.GetPosition().y,
rect_dev.GetSize().x, rect_dev.GetSize().y,
0.0 // radius of rounded corners
);
}
void SVG_PLOTTER::Circle( const wxPoint& pos, int diametre, FILL_T fill, int width )
{
DPOINT pos_dev = userToDeviceCoordinates( pos );
double radius = userToDeviceSize( diametre / 2.0 );
setFillMode( fill );
SetCurrentLineWidth( width );
fprintf( outputFile,
"<circle cx=\"%g\" cy=\"%g\" r=\"%g\" /> \n",
pos_dev.x, pos_dev.y, radius );
}
void SVG_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
FILL_T fill, int width )
{
/* Draws an arc of a circle, centred on (xc,yc), with starting point
* (x1, y1) and ending at (x2, y2). The current pen is used for the outline
* and the current brush for filling the shape.
*
* The arc is drawn in an anticlockwise direction from the start point to
* the end point
*/
if( radius <= 0 )
return;
if( StAngle > EndAngle )
std::swap( StAngle, EndAngle );
setFillMode( fill );
SetCurrentLineWidth( width );
// Calculate start point.
DPOINT centre_dev = userToDeviceCoordinates( centre );
double radius_dev = userToDeviceSize( radius );
if( !m_yaxisReversed ) // Should be never the case
{
double tmp = StAngle;
StAngle = -EndAngle;
EndAngle = -tmp;
}
if( m_plotMirror )
{
if( m_mirrorIsHorizontal )
{
StAngle = 1800.0 -StAngle;
EndAngle = 1800.0 -EndAngle;
std::swap( StAngle, EndAngle );
}
else
{
StAngle = -StAngle;
EndAngle = -EndAngle;
}
}
DPOINT start;
start.x = radius_dev;
RotatePoint( &start.x, &start.y, StAngle );
DPOINT end;
end.x = radius_dev;
RotatePoint( &end.x, &end.y, EndAngle );
start += centre_dev;
end += centre_dev;
double theta1 = DECIDEG2RAD( StAngle );
if( theta1 < 0 )
theta1 = theta1 + M_PI * 2;
double theta2 = DECIDEG2RAD( EndAngle );
if( theta2 < 0 )
theta2 = theta2 + M_PI * 2;
if( theta2 < theta1 )
theta2 = theta2 + M_PI * 2;
int flg_arc = 0; // flag for large or small arc. 0 means less than 180 degrees
if( fabs( theta2 - theta1 ) > M_PI )
flg_arc = 1;
int flg_sweep = 0; // flag for sweep always 0
// Draw a single arc: an arc is one of 3 curve commands (2 other are 2 bezier curves)
// params are start point, radius1, radius2, X axe rotation,
// flag arc size (0 = small arc > 180 deg, 1 = large arc > 180 deg),
// sweep arc ( 0 = CCW, 1 = CW),
// end point
fprintf( outputFile, "<path d=\"M%g %g A%g %g 0.0 %d %d %g %g \" />\n",
start.x, start.y, radius_dev, radius_dev,
flg_arc, flg_sweep,
end.x, end.y );
}
void SVG_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList,
FILL_T aFill, int aWidth, void * aData )
{
if( aCornerList.size() <= 1 )
return;
setFillMode( aFill );
SetCurrentLineWidth( aWidth );
switch( aFill )
{
case NO_FILL:
fprintf( outputFile, "<polyline fill=\"none;\"\n" );
break;
case FILLED_WITH_BG_BODYCOLOR:
case FILLED_SHAPE:
fprintf( outputFile, "<polyline style=\"fill-rule:evenodd;\"\n" );
break;
}
DPOINT pos = userToDeviceCoordinates( aCornerList[0] );
fprintf( outputFile, "points=\"%d,%d\n", (int) pos.x, (int) pos.y );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
{
pos = userToDeviceCoordinates( aCornerList[ii] );
fprintf( outputFile, "%d,%d\n", (int) pos.x, (int) pos.y );
}
// ensure the shape is closed, for filled shapes (that are closed polygons):
// (svg does not close automatically a polygon
if( aCornerList.front() != aCornerList.back() && aFill != NO_FILL )
{
pos = userToDeviceCoordinates( aCornerList.front() );
fprintf( outputFile, "%d,%d\n", (int) pos.x, (int) pos.y );
}
// Close/(fill) the path
fprintf( outputFile, "\" /> \n" );
}
/**
* Postscript-likes at the moment are the only plot engines supporting bitmaps...
*/
void SVG_PLOTTER::PlotImage( const wxImage& aImage, const wxPoint& aPos,
double aScaleFactor )
{
// in svg file we must insert a link to a png image file to plot an image
// the image itself is not included in the svg file.
// So we prefer skip the image, and just draw a rectangle,
// like other plotters which do not support images
PLOTTER::PlotImage( aImage, aPos, aScaleFactor );
}
void SVG_PLOTTER::PenTo( const wxPoint& pos, char plume )
{
if( plume == 'Z' )
{
if( penState != 'Z' )
{
fputs( "\" />\n", outputFile );
penState = 'Z';
penLastpos.x = -1;
penLastpos.y = -1;
}
return;
}
if( penState == 'Z' ) // here plume = 'D' or 'U'
{
DPOINT pos_dev = userToDeviceCoordinates( pos );
// Ensure we do not use a fill mode when moving tne pen,
// in SVG mode (i;e. we are plotting only basic lines, not a filled area
if( m_fillMode != NO_FILL )
{
setFillMode( NO_FILL );
setSVGPlotStyle();
}
fprintf( outputFile, "<path d=\"M%d %d\n",
(int) pos_dev.x, (int) pos_dev.y );
}
else if( penState != plume || pos != penLastpos )
{
DPOINT pos_dev = userToDeviceCoordinates( pos );
fprintf( outputFile, "L%d %d\n",
(int) pos_dev.x, (int) pos_dev.y );
}
penState = plume;
penLastpos = pos;
}
/**
* The code within this function
* creates SVG files header
*/
bool SVG_PLOTTER::StartPlot()
{
wxASSERT( outputFile );
wxString msg;
static const char* header[] =
{
"<?xml version=\"1.0\" standalone=\"no\"?>\n",
" <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \n",
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"> \n",
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" \n",
NULL
};
// Write header.
for( int ii = 0; header[ii] != NULL; ii++ )
{
fputs( header[ii], outputFile );
}
// Write viewport pos and size
wxPoint origin; // TODO set to actual value
fprintf( outputFile,
" width=\"%gcm\" height=\"%gcm\" viewBox=\"%d %d %d %d \">\n",
(double) paperSize.x / m_IUsPerDecimil * 2.54 / 10000,
(double) paperSize.y / m_IUsPerDecimil * 2.54 / 10000,
origin.x, origin.y,
(int) ( paperSize.x / m_IUsPerDecimil ),
(int) ( paperSize.y / m_IUsPerDecimil) );
// Write title
char date_buf[250];
time_t ltime = time( NULL );
strftime( date_buf, 250, "%Y/%m/%d %H:%M:%S",
localtime( &ltime ) );
fprintf( outputFile,
"<title>SVG Picture created as %s date %s </title>\n",
TO_UTF8( XmlEsc( wxFileName( filename ).GetFullName() ) ), date_buf );
// End of header
fprintf( outputFile, " <desc>Picture generated by %s </desc>\n",
TO_UTF8( XmlEsc( creator ) ) );
// output the pen and brush color (RVB values in hex) and opacity
double opacity = 1.0; // 0.0 (transparent to 1.0 (solid)
fprintf( outputFile,
"<g style=\"fill:#%6.6lX; fill-opacity:%g;stroke:#%6.6lX; stroke-opacity:%g;\n",
m_brush_rgb_color, opacity, m_pen_rgb_color, opacity );
// output the pen cap and line joint
fputs( "stroke-linecap:round; stroke-linejoin:round; \"\n", outputFile );
fputs( " transform=\"translate(0 0) scale(1 1)\">\n", outputFile );
return true;
}
bool SVG_PLOTTER::EndPlot()
{
fputs( "</g> \n</svg>\n", outputFile );
fclose( outputFile );
outputFile = NULL;
return true;
}
void SVG_PLOTTER::Text( const wxPoint& aPos,
const COLOR4D aColor,
const wxString& aText,
double aOrient,
const wxSize& aSize,
enum EDA_TEXT_HJUSTIFY_T aH_justify,
enum EDA_TEXT_VJUSTIFY_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
bool aMultilineAllowed,
void* aData )
{
setFillMode( NO_FILL );
SetColor( aColor );
SetCurrentLineWidth( aWidth );
// TODO: see if the postscript native text code can be used in SVG plotter
PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify,
aWidth, aItalic, aBold, aMultilineAllowed );
}

View File

@ -0,0 +1,165 @@
/**
* @file common_plot_functions.cpp
* @brief Kicad: Common plotting functions
*/
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2017 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 <fctsys.h>
#include <base_struct.h>
#include <class_plotter.h>
#include <worksheet.h>
#include <class_base_screen.h>
#include <drawtxt.h>
#include <class_title_block.h>
#include "worksheet_shape_builder.h"
#include "class_worksheet_dataitem.h"
#include <wx/filename.h>
wxString GetDefaultPlotExtension( PlotFormat aFormat )
{
switch( aFormat )
{
case PLOT_FORMAT_DXF:
return DXF_PLOTTER::GetDefaultFileExtension();
case PLOT_FORMAT_POST:
return PS_PLOTTER::GetDefaultFileExtension();
case PLOT_FORMAT_PDF:
return PDF_PLOTTER::GetDefaultFileExtension();
case PLOT_FORMAT_HPGL:
return HPGL_PLOTTER::GetDefaultFileExtension();
case PLOT_FORMAT_GERBER:
return GERBER_PLOTTER::GetDefaultFileExtension();
case PLOT_FORMAT_SVG:
return SVG_PLOTTER::GetDefaultFileExtension();
default:
wxASSERT( false );
return wxEmptyString;
}
}
void PlotWorkSheet( PLOTTER* plotter, const TITLE_BLOCK& aTitleBlock,
const PAGE_INFO& aPageInfo,
int aSheetNumber, int aNumberOfSheets,
const wxString &aSheetDesc, const wxString &aFilename )
{
/* Note: Page sizes values are given in mils
*/
double iusPerMil = plotter->GetIUsPerDecimil() * 10.0;
COLOR4D plotColor = plotter->GetColorMode() ? COLOR4D( RED ) : COLOR4D::BLACK;
plotter->SetColor( plotColor );
WS_DRAW_ITEM_LIST drawList;
// Print only a short filename, if aFilename is the full filename
wxFileName fn( aFilename );
// Prepare plot parameters
drawList.SetPenSize(PLOTTER::USE_DEFAULT_LINE_WIDTH );
drawList.SetMilsToIUfactor( iusPerMil );
drawList.SetSheetNumber( aSheetNumber );
drawList.SetSheetCount( aNumberOfSheets );
drawList.SetFileName( fn.GetFullName() ); // Print only the short filename
drawList.SetSheetName( aSheetDesc );
drawList.BuildWorkSheetGraphicList( aPageInfo,
aTitleBlock, plotColor, plotColor );
// Draw item list
for( WS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item;
item = drawList.GetNext() )
{
plotter->SetCurrentLineWidth( PLOTTER::USE_DEFAULT_LINE_WIDTH );
switch( item->GetType() )
{
case WS_DRAW_ITEM_BASE::wsg_line:
{
WS_DRAW_ITEM_LINE* line = (WS_DRAW_ITEM_LINE*) item;
plotter->SetCurrentLineWidth( line->GetPenWidth() );
plotter->MoveTo( line->GetStart() );
plotter->FinishTo( line->GetEnd() );
}
break;
case WS_DRAW_ITEM_BASE::wsg_rect:
{
WS_DRAW_ITEM_RECT* rect = (WS_DRAW_ITEM_RECT*) item;
plotter->Rect( rect->GetStart(),
rect->GetEnd(),
NO_FILL,
rect->GetPenWidth() );
}
break;
case WS_DRAW_ITEM_BASE::wsg_text:
{
WS_DRAW_ITEM_TEXT* text = (WS_DRAW_ITEM_TEXT*) item;
plotter->Text( text->GetTextPos(), text->GetColor(),
text->GetShownText(), text->GetTextAngle(),
text->GetTextSize(),
text->GetHorizJustify(), text->GetVertJustify(),
text->GetPenWidth(),
text->IsItalic(), text->IsBold(),
text->IsMultilineAllowed() );
}
break;
case WS_DRAW_ITEM_BASE::wsg_poly:
{
WS_DRAW_ITEM_POLYGON* poly = (WS_DRAW_ITEM_POLYGON*) item;
plotter->PlotPoly( poly->m_Corners,
poly->IsFilled() ? FILLED_SHAPE : NO_FILL,
poly->GetPenWidth() );
}
break;
case WS_DRAW_ITEM_BASE::wsg_bitmap:
{
WS_DRAW_ITEM_BITMAP* bm = (WS_DRAW_ITEM_BITMAP*) item;
WORKSHEET_DATAITEM_BITMAP* parent = (WORKSHEET_DATAITEM_BITMAP*)bm->GetParent();
if( parent->m_ImageBitmap == NULL )
break;
parent->m_ImageBitmap->PlotImage( plotter, bm->GetPosition(),
plotColor, PLOTTER::USE_DEFAULT_LINE_WIDTH );
}
break;
}
}
}