kicad/common/plotters/DXF_plotter.cpp

1049 lines
31 KiB
C++
Raw Normal View History

/**
* @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 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
*/
2009-06-30 10:43:20 +00:00
2018-01-29 15:39:40 +00:00
#include <eda_base_frame.h>
#include <fill_type.h>
#include <plotters_specific.h>
#include <macros.h>
#include <kicad_string.h>
#include <convert_basic_shapes_to_polygon.h>
2020-10-14 11:29:36 +00:00
#include <render_settings.h>
2020-10-14 03:37:48 +00:00
#include <trigo.h>
2009-06-30 10:43:20 +00:00
/**
* 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;
2009-06-30 10:43:20 +00:00
/* 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 }
};
2019-12-28 00:55:11 +00:00
static const char* getDXFLineType( PLOT_DASH_TYPE aType )
{
switch( aType )
{
2019-12-28 00:55:11 +00:00
case PLOT_DASH_TYPE::DEFAULT:
case PLOT_DASH_TYPE::SOLID:
return "CONTINUOUS";
case PLOT_DASH_TYPE::DASH:
return "DASHED";
case PLOT_DASH_TYPE::DOT:
return "DOTTED";
case PLOT_DASH_TYPE::DASHDOT:
return "DASHDOT";
default:
wxFAIL_MSG( "Unhandled PLOT_DASH_TYPE" );
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 = 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 )
{
2019-12-20 14:11:39 +00:00
case DXF_UNITS::MILLIMETERS:
m_unitScalingFactor = 0.00254;
m_measurementDirective = 1;
break;
2019-12-20 14:11:39 +00:00
case DXF_UNITS::INCHES:
default:
m_unitScalingFactor = 0.0001;
m_measurementDirective = 0;
}
}
/**
* 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)
2009-06-30 10:43:20 +00:00
*/
void DXF_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
double aScale, bool aMirror )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
m_plotOffset = aOffset;
m_plotScale = aScale;
2012-08-29 16:59:50 +00:00
/* 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) */
2020-11-16 00:04:55 +00:00
m_paperSize.x = 0;
m_paperSize.y = 0;
2012-08-29 16:59:50 +00:00
/* 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 */
2012-08-29 16:59:50 +00:00
m_IUsPerDecimil = aIusPerDecimil;
2020-11-16 00:04:55 +00:00
m_iuPerDeviceUnit = 1.0 / aIusPerDecimil; // Gives a DXF in decimils
m_iuPerDeviceUnit *= GetUnitScaling(); // Get the scaling factor for the current units
2012-08-29 16:59:50 +00:00
2020-11-16 00:04:55 +00:00
m_plotMirror = false; // No mirroring on DXF
m_currentColor = COLOR4D::BLACK;
2009-06-30 10:43:20 +00:00
}
/**
* Opens the DXF plot with a skeleton header
*/
bool DXF_PLOTTER::StartPlot()
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
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)
2020-11-16 00:04:55 +00:00
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",
2020-11-16 00:04:55 +00:00
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"
2020-11-16 00:04:55 +00:00
"4\n", m_outputFile );
static const char *style_name[4] = {"KICAD", "KICADB", "KICADI", "KICADBI"};
for(int i = 0; i < 4; i++ )
{
2020-11-16 00:04:55 +00:00
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
2020-11-16 00:04:55 +00:00
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 ) )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
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 );
2009-06-30 10:43:20 +00:00
}
// End of layer table, begin entities
fputs( " 0\n"
"ENDTAB\n"
" 0\n"
"ENDSEC\n"
" 0\n"
"SECTION\n"
" 2\n"
2020-11-16 00:04:55 +00:00
"ENTITIES\n", m_outputFile );
return true;
2009-06-30 10:43:20 +00:00
}
bool DXF_PLOTTER::EndPlot()
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
// DXF FOOTER
fputs( " 0\n"
"ENDSEC\n"
" 0\n"
2020-11-16 00:04:55 +00:00
"EOF\n", m_outputFile );
fclose( m_outputFile );
m_outputFile = NULL;
return true;
2009-06-30 10:43:20 +00:00
}
/**
* The DXF exporter handles 'colors' as layers...
2009-06-30 10:43:20 +00:00
*/
void DXF_PLOTTER::SetColor( COLOR4D color )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
if( ( m_colorMode )
|| ( color == COLOR4D::BLACK )
|| ( color == COLOR4D::WHITE ) )
2009-06-30 10:43:20 +00:00
{
m_currentColor = color;
2009-06-30 10:43:20 +00:00
}
else
2020-11-16 00:04:55 +00:00
{
m_currentColor = COLOR4D::BLACK;
2020-11-16 00:04:55 +00:00
}
2009-06-30 10:43:20 +00:00
}
/**
* DXF rectangle: fill not supported
*/
void DXF_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_TYPE fill, int width )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_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 ) );
2009-06-30 10:43:20 +00:00
}
/**
* 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_TYPE fill, int width )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
double radius = userToDeviceSize( diameter / 2 );
DPOINT centre_dev = userToDeviceCoordinates( centre );
if( radius > 0 )
2009-06-30 10:43:20 +00:00
{
wxString cname = getDXFColorName( m_currentColor );
if( fill == FILL_TYPE::NO_FILL )
{
2020-11-16 00:04:55 +00:00
fprintf( m_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 == FILL_TYPE::FILLED_SHAPE )
{
double r = radius*0.5;
2020-11-16 00:04:55 +00:00
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%g\n41\n%g\n", radius, radius);
fprintf( m_outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname ));
fprintf( m_outputFile, "10\n%g\n 20\n%g\n42\n1.0\n",
centre_dev.x-r, centre_dev.y );
2020-11-16 00:04:55 +00:00
fprintf( m_outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname ));
fprintf( m_outputFile, "10\n%g\n 20\n%g\n42\n1.0\n",
centre_dev.x+r, centre_dev.y );
2020-11-16 00:04:55 +00:00
fprintf( m_outputFile, "0\nSEQEND\n");
}
}
2009-06-30 10:43:20 +00:00
}
/**
* 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
2009-06-30 10:43:20 +00:00
*/
void DXF_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList,
FILL_TYPE aFill, int aWidth, void * aData )
2009-06-30 10:43:20 +00:00
{
if( aCornerList.size() <= 1 )
2009-06-30 10:43:20 +00:00
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] );
2009-06-30 10:43:20 +00:00
// Close polygon if 'fill' requested
if( aFill != FILL_TYPE::NO_FILL )
{
if( aCornerList[last] != aCornerList[0] )
LineTo( aCornerList[0] );
}
PenFinish();
return;
2009-06-30 10:43:20 +00:00
}
// 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_TYPE::NO_FILL )
{
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;
bufferPolybase.NewOutline();
// enter outline as polygon:
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
{
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
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 );
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();
2009-06-30 10:43:20 +00:00
}
void DXF_PLOTTER::PenTo( const wxPoint& pos, char plume )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
2009-06-30 10:43:20 +00:00
if( plume == 'Z' )
{
return;
}
DPOINT pos_dev = userToDeviceCoordinates( pos );
2020-11-16 00:04:55 +00:00
DPOINT pen_lastpos_dev = userToDeviceCoordinates( m_penLastpos );
2009-06-30 10:43:20 +00:00
2020-11-16 00:04:55 +00:00
if( m_penLastpos != pos && plume == 'D' )
2009-06-30 10:43:20 +00:00
{
2019-12-28 00:55:11 +00:00
wxASSERT( m_currentLineType >= PLOT_DASH_TYPE::FIRST_TYPE
&& m_currentLineType <= PLOT_DASH_TYPE::LAST_TYPE );
// DXF LINE
2019-12-28 00:55:11 +00:00
wxString cname = getDXFColorName( m_currentColor );
const char* lname = getDXFLineType( static_cast<PLOT_DASH_TYPE>( m_currentLineType ) );
2020-11-16 00:04:55 +00:00
fprintf( m_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 );
2009-06-30 10:43:20 +00:00
}
2020-11-16 00:04:55 +00:00
m_penLastpos = pos;
2009-06-30 10:43:20 +00:00
}
2019-12-28 00:55:11 +00:00
void DXF_PLOTTER::SetDash( PLOT_DASH_TYPE aDashed )
2009-06-30 10:43:20 +00:00
{
2019-12-28 00:55:11 +00:00
wxASSERT( aDashed >= PLOT_DASH_TYPE::FIRST_TYPE && aDashed <= PLOT_DASH_TYPE::LAST_TYPE );
m_currentLineType = aDashed;
2009-06-30 10:43:20 +00:00
}
void DXF_PLOTTER::ThickSegment( const wxPoint& aStart, const wxPoint& aEnd, int aWidth,
2020-10-15 23:33:18 +00:00
OUTLINE_MODE aPlotMode, void* aData )
2009-06-30 10:43:20 +00:00
{
if( aPlotMode == SKETCH )
{
std::vector<wxPoint> 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_TYPE::NO_FILL );
}
else
{
MoveTo( aStart );
FinishTo( aEnd );
}
2009-06-30 10:43:20 +00:00
}
/* Plot an arc in DXF format
* Filling is not supported
2009-06-30 10:43:20 +00:00
*/
void DXF_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
FILL_TYPE fill, int width )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
2009-06-30 10:43:20 +00:00
if( radius <= 0 )
2009-06-30 10:43:20 +00:00
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 );
2009-06-30 10:43:20 +00:00
// Emit a DXF ARC entity
wxString cname = getDXFColorName( m_currentColor );
2020-11-16 00:04:55 +00:00
fprintf( m_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 );
2009-06-30 10:43:20 +00:00
}
/**
* DXF oval pad: always done in sketch mode
*/
void DXF_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
2020-10-15 23:33:18 +00:00
OUTLINE_MODE trace_mode, void* aData )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
wxSize size( aSize );
2009-06-30 10:43:20 +00:00
/* The chip is reduced to an oval tablet with size.y > size.x
* (Oval vertical orientation 0) */
2009-06-30 10:43:20 +00:00
if( size.x > size.y )
{
std::swap( size.x, size.y );
orient = AddAngles( orient, 900 );
2009-06-30 10:43:20 +00:00
}
sketchOval( pos, size, orient, -1 );
2009-06-30 10:43:20 +00:00
}
/**
* 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,
2020-10-15 23:33:18 +00:00
OUTLINE_MODE trace_mode, void* aData )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
Circle( pos, diametre, FILL_TYPE::NO_FILL );
2009-06-30 10:43:20 +00:00
}
/**
* DXF rectangular pad: alwayd done in sketch mode
2009-06-30 10:43:20 +00:00
*/
void DXF_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize,
2020-10-15 23:33:18 +00:00
double orient, OUTLINE_MODE trace_mode, void* aData )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
2009-06-30 10:43:20 +00:00
wxSize size;
int ox, oy, fx, fy;
size.x = padsize.x / 2;
size.y = padsize.y / 2;
2009-06-30 10:43:20 +00:00
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
2009-06-30 10:43:20 +00:00
if( size.x == 0 )
{
ox = pos.x;
oy = pos.y - size.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &ox, &oy, pos.x, pos.y, orient );
fx = pos.x;
fy = pos.y + size.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
MoveTo( wxPoint( ox, oy ) );
FinishTo( wxPoint( fx, fy ) );
2009-06-30 10:43:20 +00:00
return;
}
if( size.y == 0 )
{
ox = pos.x - size.x;
oy = pos.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &ox, &oy, pos.x, pos.y, orient );
fx = pos.x + size.x;
fy = pos.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
MoveTo( wxPoint( ox, oy ) );
FinishTo( wxPoint( fx, fy ) );
2009-06-30 10:43:20 +00:00
return;
}
ox = pos.x - size.x;
oy = pos.y - size.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &ox, &oy, pos.x, pos.y, orient );
MoveTo( wxPoint( ox, oy ) );
2009-06-30 10:43:20 +00:00
fx = pos.x - size.x;
fy = pos.y + size.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
LineTo( wxPoint( fx, fy ) );
2009-06-30 10:43:20 +00:00
fx = pos.x + size.x;
fy = pos.y + size.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
LineTo( wxPoint( fx, fy ) );
2009-06-30 10:43:20 +00:00
fx = pos.x + size.x;
fy = pos.y - size.y;
2009-06-30 10:43:20 +00:00
RotatePoint( &fx, &fy, pos.x, pos.y, orient );
LineTo( wxPoint( fx, fy ) );
2009-06-30 10:43:20 +00:00
FinishTo( wxPoint( ox, oy ) );
2009-06-30 10:43:20 +00:00
}
void DXF_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
int aCornerRadius, double aOrient,
2020-10-15 23:33:18 +00:00
OUTLINE_MODE aTraceMode, void* aData )
{
SHAPE_POLY_SET outline;
TransformRoundChamferedRectToPolygon( outline, aPadPos, aSize, aOrient,
aCornerRadius, 0.0, 0, GetPlotterArcHighDef(), ERROR_INSIDE );
// TransformRoundRectToPolygon creates only one convex polygon
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
MoveTo( wxPoint( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
for( int ii = 1; ii < poly.PointCount(); ++ii )
LineTo( wxPoint( poly.CPoint( ii ).x, poly.CPoint( ii ).y ) );
FinishTo( wxPoint( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
}
void DXF_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
SHAPE_POLY_SET* aPolygons,
2020-10-15 23:33:18 +00:00
OUTLINE_MODE aTraceMode, void* aData )
{
for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
{
SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
MoveTo( wxPoint( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
for( int ii = 1; ii < poly.PointCount(); ++ii )
LineTo( wxPoint( poly.CPoint( ii ).x, poly.CPoint( ii ).y ) );
FinishTo( wxPoint( poly.CPoint( 0 ).x, poly.CPoint( 0 ).y ) );
}
}
2009-06-30 10:43:20 +00:00
/**
* DXF trapezoidal pad: only sketch mode is supported
2009-06-30 10:43:20 +00:00
*/
void DXF_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint *aCorners,
2020-10-15 23:33:18 +00:00
double aPadOrient, OUTLINE_MODE aTrace_Mode, void* aData )
2009-06-30 10:43:20 +00:00
{
2020-11-16 00:04:55 +00:00
wxASSERT( m_outputFile );
wxPoint coord[4]; /* coord actual corners of a trapezoidal trace */
2009-06-30 10:43:20 +00:00
for( int ii = 0; ii < 4; ii++ )
{
coord[ii] = aCorners[ii];
RotatePoint( &coord[ii], aPadOrient );
coord[ii] += aPadPos;
2009-06-30 10:43:20 +00:00
}
// Plot edge:
MoveTo( coord[0] );
LineTo( coord[1] );
LineTo( coord[2] );
LineTo( coord[3] );
FinishTo( coord[0] );
}
void DXF_PLOTTER::FlashRegularPolygon( const wxPoint& aShapePos,
int aRadius, int aCornerCount,
2020-10-15 23:33:18 +00:00
double aOrient, OUTLINE_MODE aTraceMode, void* aData )
{
// Do nothing
wxASSERT( 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.
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 );
}
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
2020-11-16 00:04:55 +00:00
fprintf( m_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"),
2020-11-16 00:04:55 +00:00
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;
2020-11-16 00:04:55 +00:00
fputs( " 1\n", m_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...
2020-11-16 00:04:55 +00:00
putc( '?', m_outputFile );
}
else
{
if( ch == '~' )
{
if( ++i == aText.length() )
break;
ch = aText[i];
if( ch == '~' )
{
// double ~ is really a ~ so go ahead and process the second one
// so what's a triple ~? It could be a real ~ followed by an overbar,
// or it could be an overbar followed by a real ~. The old algorithm
// did the former so we will too....
}
else
{
// Handle the overline toggle
2020-11-16 00:04:55 +00:00
fputs( overlining ? "%%o" : "%%O", m_outputFile );
overlining = !overlining;
}
}
else if( ch == ' ' || ch == '}' || ch == ')' )
{
overlining = false;
}
2020-11-16 00:04:55 +00:00
putc( ch, m_outputFile );
}
}
2020-11-16 00:04:55 +00:00
putc( '\n', m_outputFile );
}
2009-06-30 10:43:20 +00:00
}