1112 lines
34 KiB
C++
1112 lines
34 KiB
C++
/**
|
|
* @file DXF_plotter.cpp
|
|
* @brief Kicad: specialized plotter for DXF files format
|
|
*/
|
|
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2017-2023 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 <plotters/plotter_dxf.h>
|
|
#include <macros.h>
|
|
#include <string_utils.h>
|
|
#include <convert_basic_shapes_to_polygon.h>
|
|
#include <trigo.h>
|
|
#include <fmt/core.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 },
|
|
{ "LORANGE", 41 },
|
|
{ "BLUE1", 178 },
|
|
{ "GREEN1", 98 },
|
|
{ "CYAN1", 138 },
|
|
{ "RED1", 18 },
|
|
{ "MAGENTA1", 228 },
|
|
{ "BROWN1", 58 },
|
|
{ "ORANGE1", 34 },
|
|
{ "BLUE2", 5 },
|
|
{ "GREEN2", 3 },
|
|
{ "CYAN2", 4 },
|
|
{ "RED2", 1 },
|
|
{ "MAGENTA2", 6 },
|
|
{ "BROWN2", 54 },
|
|
{ "ORANGE2", 42 },
|
|
{ "BLUE3", 171 },
|
|
{ "GREEN3", 91 },
|
|
{ "CYAN3", 131 },
|
|
{ "RED3", 11 },
|
|
{ "MAGENTA3", 221 },
|
|
{ "YELLOW3", 2 },
|
|
{ "ORANGE3", 32 },
|
|
{ "BLUE4", 5 },
|
|
{ "GREEN4", 3 },
|
|
{ "CYAN4", 4 },
|
|
{ "RED4", 1 },
|
|
{ "MAGENTA4", 6 },
|
|
{ "YELLOW4", 2 },
|
|
{ "ORANGE4", 40 }
|
|
};
|
|
|
|
|
|
static const char* getDXFLineType( LINE_STYLE aType )
|
|
{
|
|
switch( aType )
|
|
{
|
|
case LINE_STYLE::DEFAULT:
|
|
case LINE_STYLE::SOLID:
|
|
return "CONTINUOUS";
|
|
case LINE_STYLE::DASH:
|
|
return "DASHED";
|
|
case LINE_STYLE::DOT:
|
|
return "DOTTED";
|
|
case LINE_STYLE::DASHDOT:
|
|
return "DASHDOT";
|
|
default:
|
|
wxFAIL_MSG( "Unhandled LINE_STYLE" );
|
|
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( const COLOR4D& aColor )
|
|
{
|
|
EDA_COLOR_T color = COLOR4D::FindNearestLegacyColor( int( aColor.r * 255 ),
|
|
int( aColor.g * 255 ),
|
|
int( aColor.b * 255 ) );
|
|
wxString cname( dxf_layer[color].name );
|
|
return cname;
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::SetUnits( DXF_UNITS aUnit )
|
|
{
|
|
m_plotUnits = aUnit;
|
|
|
|
switch( aUnit )
|
|
{
|
|
case DXF_UNITS::MILLIMETERS:
|
|
m_unitScalingFactor = 0.00254;
|
|
m_measurementDirective = 1;
|
|
break;
|
|
|
|
case DXF_UNITS::INCHES:
|
|
default:
|
|
m_unitScalingFactor = 0.0001;
|
|
m_measurementDirective = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// convert aValue to a string, and remove trailing zeros
|
|
// In DXF files coordinates need a high precision: at least 9 digits when given
|
|
// in inches and 7 digits when in mm.
|
|
// So we use 16 digits and remove trailing 0 (if any)
|
|
static std::string formatCoord( double aValue )
|
|
{
|
|
std::string buf;
|
|
|
|
buf = fmt::format( "{:.16f}", aValue );
|
|
|
|
// remove trailing zeros
|
|
while( !buf.empty() && buf[buf.size() - 1] == '0' )
|
|
{
|
|
buf.pop_back();
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil,
|
|
double aScale, bool aMirror )
|
|
{
|
|
m_plotOffset = aOffset;
|
|
m_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) */
|
|
m_paperSize.x = 0;
|
|
m_paperSize.y = 0;
|
|
|
|
/* Like paper size DXF units are abstract too. Anyway there is a
|
|
* system variable (MEASUREMENT) which will be set to 0 to indicate
|
|
* english units */
|
|
m_IUsPerDecimil = aIusPerDecimil;
|
|
m_iuPerDeviceUnit = 1.0 / aIusPerDecimil; // Gives a DXF in decimils
|
|
m_iuPerDeviceUnit *= GetUnitScaling(); // Get the scaling factor for the current units
|
|
|
|
m_plotMirror = false; // No mirroring on DXF
|
|
m_currentColor = COLOR4D::BLACK;
|
|
}
|
|
|
|
|
|
bool DXF_PLOTTER::StartPlot( const wxString& aPageNumber )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
// DXF HEADER - Boilerplate
|
|
// Defines the minimum for drawing i.e. the angle system and the
|
|
// 4 linetypes (CONTINUOUS, DOTDASH, DASHED and DOTTED)
|
|
fprintf( m_outputFile,
|
|
" 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"
|
|
"%u\n"
|
|
" 0\n"
|
|
"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",
|
|
GetMeasurementDirective() );
|
|
|
|
// 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", m_outputFile );
|
|
|
|
static const char *style_name[4] = {"KICAD", "KICADB", "KICADI", "KICADBI"};
|
|
for(int i = 0; i < 4; i++ )
|
|
{
|
|
fprintf( m_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 );
|
|
}
|
|
|
|
EDA_COLOR_T numLayers = NBCOLORS;
|
|
|
|
// If printing in monochrome, only output the black layer
|
|
if( !GetColorMode() )
|
|
numLayers = static_cast<EDA_COLOR_T>( 1 );
|
|
|
|
// Layer table - one layer per color
|
|
fprintf( m_outputFile,
|
|
" 0\n"
|
|
"ENDTAB\n"
|
|
" 0\n"
|
|
"TABLE\n"
|
|
" 2\n"
|
|
"LAYER\n"
|
|
" 70\n"
|
|
"%d\n", numLayers );
|
|
|
|
/* 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)
|
|
*/
|
|
|
|
wxASSERT( numLayers <= NBCOLORS );
|
|
|
|
for( EDA_COLOR_T i = BLACK; i < numLayers; i = static_cast<EDA_COLOR_T>( int( i ) + 1 ) )
|
|
{
|
|
fprintf( m_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", m_outputFile );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DXF_PLOTTER::EndPlot()
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
// DXF FOOTER
|
|
fputs( " 0\n"
|
|
"ENDSEC\n"
|
|
" 0\n"
|
|
"EOF\n", m_outputFile );
|
|
fclose( m_outputFile );
|
|
m_outputFile = nullptr;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::SetColor( const COLOR4D& color )
|
|
{
|
|
if( ( m_colorMode )
|
|
|| ( color == COLOR4D::BLACK )
|
|
|| ( color == COLOR4D::WHITE ) )
|
|
{
|
|
m_currentColor = color;
|
|
}
|
|
else
|
|
{
|
|
m_currentColor = COLOR4D::BLACK;
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
if( p1 != p2 )
|
|
{
|
|
MoveTo( p1 );
|
|
LineTo( VECTOR2I( p1.x, p2.y ) );
|
|
LineTo( VECTOR2I( p2.x, p2.y ) );
|
|
LineTo( VECTOR2I( p2.x, p1.y ) );
|
|
FinishTo( VECTOR2I( p1.x, p1.y ) );
|
|
}
|
|
else
|
|
{
|
|
// Draw as a point
|
|
wxString cname = getDXFColorName( m_currentColor );
|
|
VECTOR2D point_dev = userToDeviceCoordinates( p1 );
|
|
|
|
fprintf( m_outputFile, "0\nPOINT\n8\n%s\n10\n%s\n20\n%s\n",
|
|
TO_UTF8( cname ),
|
|
formatCoord( point_dev.x ).c_str(),
|
|
formatCoord( point_dev.y ).c_str() );
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::Circle( const VECTOR2I& centre, int diameter, FILL_T fill, int width )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
double radius = userToDeviceSize( diameter / 2 );
|
|
VECTOR2D centre_dev = userToDeviceCoordinates( centre );
|
|
|
|
wxString cname = getDXFColorName( m_currentColor );
|
|
|
|
if( radius > 0 )
|
|
{
|
|
if( fill == FILL_T::NO_FILL )
|
|
{
|
|
fprintf( m_outputFile, "0\nCIRCLE\n8\n%s\n10\n%s\n20\n%s\n40\n%s\n",
|
|
TO_UTF8( cname ),
|
|
formatCoord( centre_dev.x ).c_str(),
|
|
formatCoord( centre_dev.y ).c_str(),
|
|
formatCoord( radius ).c_str() );
|
|
}
|
|
else if( fill == FILL_T::FILLED_SHAPE )
|
|
{
|
|
double r = radius * 0.5;
|
|
fprintf( m_outputFile, "0\nPOLYLINE\n" );
|
|
fprintf( m_outputFile, "8\n%s\n66\n1\n70\n1\n", TO_UTF8( cname ) );
|
|
fprintf( m_outputFile, "40\n%s\n41\n%s\n",
|
|
formatCoord( radius ).c_str(),
|
|
formatCoord( radius ).c_str() );
|
|
fprintf( m_outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname ) );
|
|
fprintf( m_outputFile, "10\n%s\n 20\n%s\n42\n1.0\n",
|
|
formatCoord( centre_dev.x-r ).c_str(),
|
|
formatCoord( centre_dev.y ).c_str() );
|
|
fprintf( m_outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname ) );
|
|
fprintf( m_outputFile, "10\n%s\n 20\n%s\n42\n1.0\n",
|
|
formatCoord( centre_dev.x+r ).c_str(),
|
|
formatCoord( centre_dev.y ).c_str() );
|
|
fprintf( m_outputFile, "0\nSEQEND\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Draw as a point
|
|
fprintf( m_outputFile, "0\nPOINT\n8\n%s\n10\n%s\n20\n%s\n",
|
|
TO_UTF8( cname ),
|
|
formatCoord( centre_dev.x ).c_str(),
|
|
formatCoord( centre_dev.y ).c_str() );
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::PlotPoly( const std::vector<VECTOR2I>& 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 != FILL_T::NO_FILL )
|
|
{
|
|
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 == FILL_T::NO_FILL )
|
|
{
|
|
MoveTo( aCornerList[0] );
|
|
|
|
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
|
|
ThickSegment( aCornerList[ii-1], aCornerList[ii], aWidth, FILLED, nullptr );
|
|
|
|
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;
|
|
|
|
bufferPolybase.NewOutline();
|
|
|
|
// enter outline as polygon:
|
|
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
|
|
{
|
|
TransformOvalToPolygon( bufferOutline, aCornerList[ ii - 1 ], aCornerList[ ii ],
|
|
aWidth, GetPlotterArcHighDef(), ERROR_INSIDE );
|
|
}
|
|
|
|
// 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 );
|
|
|
|
VECTOR2I startPoint( point.x, point.y );
|
|
MoveTo( startPoint );
|
|
|
|
for( int ii = 1; ii < path.PointCount(); ii++ )
|
|
{
|
|
point = path.CPoint( ii );
|
|
LineTo( VECTOR2I( point.x, point.y ) );
|
|
}
|
|
|
|
// Close polygon, if needed
|
|
point = path.CPoint( last );
|
|
VECTOR2I endPoint( point.x, point.y );
|
|
|
|
if( endPoint != startPoint )
|
|
LineTo( startPoint );
|
|
|
|
PenFinish();
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::PenTo( const VECTOR2I& pos, char plume )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
if( plume == 'Z' )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VECTOR2D pos_dev = userToDeviceCoordinates( pos );
|
|
VECTOR2D pen_lastpos_dev = userToDeviceCoordinates( m_penLastpos );
|
|
|
|
if( m_penLastpos != pos && plume == 'D' )
|
|
{
|
|
wxASSERT( m_currentLineType >= LINE_STYLE::FIRST_TYPE
|
|
&& m_currentLineType <= LINE_STYLE::LAST_TYPE );
|
|
// DXF LINE
|
|
wxString cname = getDXFColorName( m_currentColor );
|
|
const char* lname = getDXFLineType( static_cast<LINE_STYLE>( m_currentLineType ) );
|
|
fprintf( m_outputFile, "0\nLINE\n8\n%s\n6\n%s\n10\n%s\n20\n%s\n11\n%s\n21\n%s\n",
|
|
TO_UTF8( cname ), lname,
|
|
formatCoord( pen_lastpos_dev.x ).c_str(),
|
|
formatCoord( pen_lastpos_dev.y ).c_str(),
|
|
formatCoord( pos_dev.x ).c_str(),
|
|
formatCoord( pos_dev.y ).c_str() );
|
|
}
|
|
|
|
m_penLastpos = pos;
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle )
|
|
{
|
|
wxASSERT( aLineStyle >= LINE_STYLE::FIRST_TYPE
|
|
&& aLineStyle <= LINE_STYLE::LAST_TYPE );
|
|
|
|
m_currentLineType = aLineStyle;
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::ThickSegment( const VECTOR2I& aStart, const VECTOR2I& aEnd, int aWidth,
|
|
OUTLINE_MODE aPlotMode, void* aData )
|
|
{
|
|
if( aPlotMode == SKETCH )
|
|
{
|
|
std::vector<VECTOR2I> cornerList;
|
|
SHAPE_POLY_SET outlineBuffer;
|
|
TransformOvalToPolygon( outlineBuffer, aStart, aEnd, aWidth, GetPlotterArcHighDef(),
|
|
ERROR_INSIDE );
|
|
const SHAPE_LINE_CHAIN& path = outlineBuffer.COutline( 0 );
|
|
|
|
cornerList.reserve( path.PointCount() );
|
|
|
|
for( int jj = 0; jj < path.PointCount(); jj++ )
|
|
cornerList.emplace_back( 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, FILL_T::NO_FILL );
|
|
}
|
|
else
|
|
{
|
|
MoveTo( aStart );
|
|
FinishTo( aEnd );
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
|
|
const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
if( aRadius <= 0 )
|
|
return;
|
|
|
|
EDA_ANGLE startAngle = -aStartAngle;
|
|
EDA_ANGLE endAngle = startAngle - aAngle;
|
|
|
|
// In DXF, arcs are drawn CCW.
|
|
// If startAngle > endAngle, it is CW. So transform it to CCW
|
|
if( endAngle < startAngle )
|
|
std::swap( startAngle, endAngle );
|
|
|
|
VECTOR2D centre_device = userToDeviceCoordinates( aCenter );
|
|
double radius_device = userToDeviceSize( aRadius );
|
|
|
|
// Emit a DXF ARC entity
|
|
wxString cname = getDXFColorName( m_currentColor );
|
|
fprintf( m_outputFile,
|
|
"0\nARC\n8\n%s\n10\n%s\n20\n%s\n40\n%s\n50\n%.8f\n51\n%.8f\n",
|
|
TO_UTF8( cname ),
|
|
formatCoord( centre_device.x ).c_str(),
|
|
formatCoord( centre_device.y ).c_str(),
|
|
formatCoord( radius_device ).c_str(),
|
|
startAngle.AsDegrees(), endAngle.AsDegrees() );
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::FlashPadOval( const VECTOR2I& aPos, const VECTOR2I& aSize,
|
|
const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
VECTOR2I size( aSize );
|
|
EDA_ANGLE orient( aOrient );
|
|
|
|
/* 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 += ANGLE_90;
|
|
}
|
|
|
|
sketchOval( aPos, size, orient, -1 );
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::FlashPadCircle( const VECTOR2I& pos, int diametre,
|
|
OUTLINE_MODE trace_mode, void* aData )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
Circle( pos, diametre, FILL_T::NO_FILL );
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::FlashPadRect( const VECTOR2I& aPos, const VECTOR2I& aPadSize,
|
|
const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
|
|
VECTOR2I size, start, end;
|
|
|
|
size.x = aPadSize.x / 2;
|
|
size.y = aPadSize.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 )
|
|
{
|
|
start = VECTOR2I( aPos.x, aPos.y - size.y );
|
|
end = VECTOR2I( aPos.x, aPos.y + size.y );
|
|
RotatePoint( start, aPos, aOrient );
|
|
RotatePoint( end, aPos, aOrient );
|
|
MoveTo( start );
|
|
FinishTo( end );
|
|
return;
|
|
}
|
|
|
|
if( size.y == 0 )
|
|
{
|
|
start = VECTOR2I( aPos.x - size.x, aPos.y );
|
|
end = VECTOR2I( aPos.x + size.x, aPos.y );
|
|
RotatePoint( start, aPos, aOrient );
|
|
RotatePoint( end, aPos, aOrient );
|
|
MoveTo( start );
|
|
FinishTo( end );
|
|
return;
|
|
}
|
|
|
|
start = VECTOR2I( aPos.x - size.x, aPos.y - size.y );
|
|
RotatePoint( start, aPos, aOrient );
|
|
MoveTo( start );
|
|
|
|
end = VECTOR2I( aPos.x - size.x, aPos.y + size.y );
|
|
RotatePoint( end, aPos, aOrient );
|
|
LineTo( end );
|
|
|
|
end = VECTOR2I( aPos.x + size.x, aPos.y + size.y );
|
|
RotatePoint( end, aPos, aOrient );
|
|
LineTo( end );
|
|
|
|
end = VECTOR2I( aPos.x + size.x, aPos.y - size.y );
|
|
RotatePoint( end, aPos, aOrient );
|
|
LineTo( end );
|
|
|
|
FinishTo( start );
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::FlashPadRoundRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
|
|
int aCornerRadius, const EDA_ANGLE& aOrient,
|
|
OUTLINE_MODE aTraceMode, void* aData )
|
|
{
|
|
SHAPE_POLY_SET outline;
|
|
TransformRoundChamferedRectToPolygon( outline, aPadPos, aSize, aOrient, aCornerRadius, 0.0, 0,
|
|
0, GetPlotterArcHighDef(), ERROR_INSIDE );
|
|
|
|
// TransformRoundRectToPolygon creates only one convex polygon
|
|
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
|
|
|
|
MoveTo( VECTOR2I( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
|
|
|
|
for( int ii = 1; ii < poly.PointCount(); ++ii )
|
|
LineTo( VECTOR2I( poly.CPoint( ii ).x, poly.CPoint( ii ).y ) );
|
|
|
|
FinishTo( VECTOR2I( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
|
|
}
|
|
|
|
void DXF_PLOTTER::FlashPadCustom( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
|
|
const EDA_ANGLE& aOrient, SHAPE_POLY_SET* aPolygons,
|
|
OUTLINE_MODE aTraceMode, void* aData )
|
|
{
|
|
for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
|
|
{
|
|
SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
|
|
|
|
MoveTo( VECTOR2I( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
|
|
|
|
for( int ii = 1; ii < poly.PointCount(); ++ii )
|
|
LineTo( VECTOR2I( poly.CPoint( ii ).x, poly.CPoint( ii ).y ) );
|
|
|
|
FinishTo( VECTOR2I( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::FlashPadTrapez( const VECTOR2I& aPadPos, const VECTOR2I* aCorners,
|
|
const EDA_ANGLE& aPadOrient, OUTLINE_MODE aTraceMode,
|
|
void* aData )
|
|
{
|
|
wxASSERT( m_outputFile );
|
|
VECTOR2I 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] );
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::FlashRegularPolygon( const VECTOR2I& aShapePos, int aRadius, int aCornerCount,
|
|
const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode,
|
|
void* aData )
|
|
{
|
|
// Do nothing
|
|
wxASSERT( 0 );
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a given string contains non-ASCII characters.
|
|
*
|
|
* @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 VECTOR2I& aPos,
|
|
const COLOR4D& aColor,
|
|
const wxString& aText,
|
|
const EDA_ANGLE& aOrient,
|
|
const VECTOR2I& aSize,
|
|
enum GR_TEXT_H_ALIGN_T aH_justify,
|
|
enum GR_TEXT_V_ALIGN_T aV_justify,
|
|
int aWidth,
|
|
bool aItalic,
|
|
bool aBold,
|
|
bool aMultilineAllowed,
|
|
KIFONT::FONT* aFont,
|
|
const KIFONT::METRICS& aFontMetrics,
|
|
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.
|
|
|
|
bool processSuperSub = aText.Contains( wxT( "^{" ) ) || aText.Contains( wxT( "_{" ) );
|
|
|
|
if( m_textAsLines || containsNonAsciiChars( aText ) || aMultilineAllowed || processSuperSub )
|
|
{
|
|
// 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, aFont, aFontMetrics, aData );
|
|
}
|
|
else
|
|
{
|
|
TEXT_ATTRIBUTES attrs;
|
|
attrs.m_Halign = aH_justify;
|
|
attrs.m_Valign =aV_justify;
|
|
attrs.m_StrokeWidth = aWidth;
|
|
attrs.m_Angle = aOrient;
|
|
attrs.m_Italic = aItalic;
|
|
attrs.m_Bold = aBold;
|
|
attrs.m_Mirrored = aSize.x < 0;
|
|
attrs.m_Multiline = false;
|
|
plotOneLineOfText( aPos, aColor, aText, attrs );
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_PLOTTER::PlotText( const VECTOR2I& aPos,
|
|
const COLOR4D& aColor,
|
|
const wxString& aText,
|
|
const TEXT_ATTRIBUTES& aAttributes,
|
|
KIFONT::FONT* aFont,
|
|
const KIFONT::METRICS& aFontMetrics,
|
|
void* aData )
|
|
{
|
|
TEXT_ATTRIBUTES attrs = aAttributes;
|
|
// Fix me: see how to use DXF text mode for multiline texts
|
|
if( attrs.m_Multiline && !aText.Contains( wxT( "\n" ) ) )
|
|
attrs.m_Multiline = false; // the text has only one line.
|
|
|
|
bool processSuperSub = aText.Contains( wxT( "^{" ) ) || aText.Contains( wxT( "_{" ) );
|
|
|
|
if( m_textAsLines || containsNonAsciiChars( aText ) || attrs.m_Multiline || processSuperSub )
|
|
{
|
|
// output text as graphics.
|
|
// Perhaps multiline texts could be handled as DXF text entity
|
|
// but I do not want spend time about that (JPC)
|
|
PLOTTER::PlotText( aPos, aColor, aText, aAttributes, aFont, aFontMetrics, aData );
|
|
}
|
|
else
|
|
{
|
|
plotOneLineOfText( aPos, aColor, aText, attrs );
|
|
}
|
|
}
|
|
|
|
void DXF_PLOTTER::plotOneLineOfText( const VECTOR2I& aPos, const COLOR4D& aColor,
|
|
const wxString& aText, const TEXT_ATTRIBUTES& aAttributes )
|
|
{
|
|
/* Emit text as a text entity. This loses formatting and shape but it's
|
|
more useful as a CAD object */
|
|
VECTOR2D origin_dev = userToDeviceCoordinates( aPos );
|
|
SetColor( aColor );
|
|
wxString cname = getDXFColorName( m_currentColor );
|
|
VECTOR2D size_dev = userToDeviceSize( aAttributes.m_Size );
|
|
int h_code = 0, v_code = 0;
|
|
|
|
switch( aAttributes.m_Halign )
|
|
{
|
|
case GR_TEXT_H_ALIGN_LEFT: h_code = 0; break;
|
|
case GR_TEXT_H_ALIGN_CENTER: h_code = 1; break;
|
|
case GR_TEXT_H_ALIGN_RIGHT: h_code = 2; break;
|
|
case GR_TEXT_H_ALIGN_INDETERMINATE:
|
|
wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
|
|
break;
|
|
}
|
|
|
|
switch( aAttributes.m_Valign )
|
|
{
|
|
case GR_TEXT_V_ALIGN_TOP: v_code = 3; break;
|
|
case GR_TEXT_V_ALIGN_CENTER: v_code = 2; break;
|
|
case GR_TEXT_V_ALIGN_BOTTOM: v_code = 1; break;
|
|
case GR_TEXT_V_ALIGN_INDETERMINATE:
|
|
wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
|
|
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( m_outputFile,
|
|
" 0\n"
|
|
"TEXT\n"
|
|
" 7\n"
|
|
"%s\n" // Text style
|
|
" 8\n"
|
|
"%s\n" // Layer name
|
|
" 10\n"
|
|
"%s\n" // First point X
|
|
" 11\n"
|
|
"%s\n" // Second point X
|
|
" 20\n"
|
|
"%s\n" // First point Y
|
|
" 21\n"
|
|
"%s\n" // Second point Y
|
|
" 40\n"
|
|
"%s\n" // Text height
|
|
" 41\n"
|
|
"%s\n" // Width factor
|
|
" 50\n"
|
|
"%.8f\n" // Rotation
|
|
" 51\n"
|
|
"%.8f\n" // Oblique angle
|
|
" 71\n"
|
|
"%d\n" // Mirror flags
|
|
" 72\n"
|
|
"%d\n" // H alignment
|
|
" 73\n"
|
|
"%d\n", // V alignment
|
|
aAttributes.m_Bold ? ( aAttributes.m_Italic ? "KICADBI" : "KICADB" )
|
|
: ( aAttributes.m_Italic ? "KICADI" : "KICAD" ),
|
|
TO_UTF8( cname ),
|
|
formatCoord( origin_dev.x ).c_str(), formatCoord( origin_dev.x ).c_str(),
|
|
formatCoord( origin_dev.y ).c_str(), formatCoord( origin_dev.y ).c_str(),
|
|
formatCoord( size_dev.y ).c_str(), formatCoord( fabs( size_dev.x / size_dev.y ) ).c_str(),
|
|
aAttributes.m_Angle.AsDegrees(),
|
|
aAttributes.m_Italic ? DXF_OBLIQUE_ANGLE : 0,
|
|
aAttributes.m_Mirrored ? 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...
|
|
*/
|
|
|
|
int braceNesting = 0;
|
|
int overbarDepth = -1;
|
|
|
|
fputs( " 1\n", m_outputFile );
|
|
|
|
for( unsigned int 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 ...
|
|
|
|
At least 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( '?', m_outputFile );
|
|
}
|
|
else
|
|
{
|
|
if( aText[i] == '~' && i+1 < aText.length() && aText[i+1] == '{' )
|
|
{
|
|
fputs( "%%o", m_outputFile );
|
|
overbarDepth = braceNesting;
|
|
|
|
// Skip the '{'
|
|
i++;
|
|
continue;
|
|
}
|
|
else if( aText[i] == '{' )
|
|
{
|
|
braceNesting++;
|
|
}
|
|
else if( aText[i] == '}' )
|
|
{
|
|
if( braceNesting > 0 )
|
|
braceNesting--;
|
|
|
|
if( braceNesting == overbarDepth )
|
|
{
|
|
fputs( "%%O", m_outputFile );
|
|
overbarDepth = -1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
putc( ch, m_outputFile );
|
|
}
|
|
}
|
|
|
|
putc( '\n', m_outputFile );
|
|
}
|