move plotter related files to subdir plotters
This commit is contained in:
parent
19e3815c85
commit
93fd0c9358
|
@ -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;
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -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" );
|
||||
}
|
|
@ -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 );
|
||||
}
|
|
@ -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( <ime ) );
|
||||
|
||||
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
|
@ -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 "<", '>' to ">" 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( "<" ) );
|
||||
break;
|
||||
case wxS( '>' ):
|
||||
escaped.append( wxS( ">" ) );
|
||||
break;
|
||||
case wxS( '&' ):
|
||||
escaped.append( wxS( "&" ) );
|
||||
break;
|
||||
case wxS( '\r' ):
|
||||
escaped.append( wxS( "
" ) );
|
||||
break;
|
||||
default:
|
||||
if( isAttribute )
|
||||
{
|
||||
switch( c )
|
||||
{
|
||||
case wxS( '"' ):
|
||||
escaped.append( wxS( """ ) );
|
||||
break;
|
||||
case wxS( '\t' ):
|
||||
escaped.append( wxS( "	" ) );
|
||||
break;
|
||||
case wxS( '\n' ):
|
||||
escaped.append( wxS( "
" ));
|
||||
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( <ime ) );
|
||||
|
||||
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 );
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue