common_plotGERBER_functions.cpp 16.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2014 KiCad Developers, see CHANGELOG.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
 */

25 26 27 28
/**
 * @file common_plotGERBER_functions.cpp
 * @brief Common GERBER plot routines.
 */
charras's avatar
charras committed
29

30 31 32 33 34 35 36 37 38
#include <fctsys.h>
#include <gr_basic.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <common.h>
#include <plot_common.h>
#include <macros.h>
#include <kicad_string.h>
charras's avatar
charras committed
39

40
#include <build_version.h>
41

42

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
GERBER_PLOTTER::GERBER_PLOTTER()
{
    workFile  = 0;
    finalFile = 0;
    currentAperture = apertures.end();

    // 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;
}
charras's avatar
charras committed
59

60

61 62
void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
				  double aScale, bool aMirror )
charras's avatar
charras committed
63
{
64
    wxASSERT( !outputFile );
65
    wxASSERT( aMirror == false );
66
    m_plotMirror = false;
67
    plotOffset = aOffset;
68 69 70
    wxASSERT( aScale == 1 );    // aScale parameter is not used in Gerber
    plotScale = 1;              // Plot scale is *always* 1.0

71
    m_IUsPerDecimil = aIusPerDecimil;
72 73 74
    // 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 );
75

76 77
    // We don't handle the filmbox, and it's more useful to keep the
    // origin at the origin
78 79 80
    paperSize.x = 0;
    paperSize.y = 0;
    SetDefaultLineWidth( 100 * aIusPerDecimil ); // Arbitrary default
charras's avatar
charras committed
81 82
}

83

84 85 86 87 88 89 90 91 92 93 94 95
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
}


96 97 98
void GERBER_PLOTTER::emitDcode( const DPOINT& pt, int dcode )
{

99
    fprintf( outputFile, "X%dY%dD%02d*\n",
100
	    KiROUND( pt.x ), KiROUND( pt.y ), dcode );
101
}
charras's avatar
charras committed
102

103

104
bool GERBER_PLOTTER::StartPlot()
charras's avatar
charras committed
105
{
106 107 108
    wxASSERT( outputFile );

    finalFile = outputFile;     // the actual gerber file will be created later
charras's avatar
charras committed
109 110

    // Create a temporary filename to store gerber file
111
    // note tmpfile() does not work under Vista and W7 in user mode
charras's avatar
charras committed
112
    m_workFilename = filename + wxT(".tmp");
113 114 115
    workFile   = wxFopen( m_workFilename, wxT( "wt" ));
    outputFile = workFile;
    wxASSERT( outputFile );
116

117
    if( outputFile == NULL )
charras's avatar
charras committed
118 119
        return false;

120
    if( ! m_attribFunction.IsEmpty() )
121
    {
122
        fprintf( outputFile, "%s\n", TO_UTF8( m_attribFunction ) );
123 124
    }

125 126 127 128 129 130 131 132 133 134 135 136 137
    // 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" );
138

139
    wxString Title = creator + wxT( " " ) + GetBuildVersion();
140
    fprintf( outputFile, "G04 Created by KiCad (%s) date %s*\n",
141
             TO_UTF8( Title ), TO_UTF8( DateAndTime() ) );
charras's avatar
charras committed
142

143 144 145 146 147
    /* Mass parameter: unit = INCHES/MM */
    if( m_gerberUnitInch )
        fputs( "%MOIN*%\n", outputFile );
    else
        fputs( "%MOMM*%\n", outputFile );
charras's avatar
charras committed
148

149 150
    /* Specify linear interpol (G01) */
    fputs( "G01*\n", outputFile );
151
    fputs( "G04 APERTURE LIST*\n", outputFile );
152
    /* Select the default aperture */
153
    SetCurrentLineWidth( -1 );
charras's avatar
charras committed
154 155

    return true;
charras's avatar
charras committed
156 157
}

charras's avatar
charras committed
158

159
bool GERBER_PLOTTER::EndPlot()
160 161 162 163
{
    char     line[1024];
    wxString msg;

164
    wxASSERT( outputFile );
165

166 167 168
    /* Outfile is actually a temporary file i.e. workFile */
    fputs( "M02*\n", outputFile );
    fflush( outputFile );
169

170 171 172 173
    fclose( workFile );
    workFile   = wxFopen( m_workFilename, wxT( "rt" ));
    wxASSERT( workFile );
    outputFile = finalFile;
charras's avatar
charras committed
174

175
    // Placement of apertures in RS274X
176
    while( fgets( line, 1024, workFile ) )
177
    {
178
        fputs( line, outputFile );
179

charras's avatar
charras committed
180 181
        if( strcmp( strtok( line, "\n\r" ), "G04 APERTURE LIST*" ) == 0 )
        {
182 183
            writeApertureList();
            fputs( "G04 APERTURE END LIST*\n", outputFile );
charras's avatar
charras committed
184
        }
185 186
    }

187 188
    fclose( workFile );
    fclose( finalFile );
charras's avatar
charras committed
189
    ::wxRemoveFile( m_workFilename );
190
    outputFile = 0;
charras's avatar
charras committed
191 192

    return true;
193 194
}

charras's avatar
charras committed
195

196
void GERBER_PLOTTER::SetDefaultLineWidth( int width )
197
{
198 199
    defaultPenWidth = width;
    currentAperture = apertures.end();
200 201
}

charras's avatar
charras committed
202

203
void GERBER_PLOTTER::SetCurrentLineWidth( int width )
charras's avatar
charras committed
204
{
205 206 207 208 209
    int pen_width;

    if( width > 0 )
        pen_width = width;
    else
210
        pen_width = defaultPenWidth;
211

212 213
    selectAperture( wxSize( pen_width, pen_width ), APERTURE::Plotting );
    currentPenWidth = pen_width;
214 215
}

charras's avatar
charras committed
216

217 218
std::vector<APERTURE>::iterator GERBER_PLOTTER::getAperture( const wxSize&           size,
                                                             APERTURE::APERTURE_TYPE type )
219 220
{
    int last_D_code = 9;
charras's avatar
charras committed
221

222
    // Search an existing aperture
223
    std::vector<APERTURE>::iterator tool = apertures.begin();
224

charras's avatar
charras committed
225 226
    while( tool != apertures.end() )
    {
227
        last_D_code = tool->DCode;
228

229
        if( (tool->Type == type) && (tool->Size == size) )
charras's avatar
charras committed
230
            return tool;
231

charras's avatar
charras committed
232
        tool++;
233
    }
charras's avatar
charras committed
234

235
    // Allocate a new aperture
236
    APERTURE new_tool;
237 238 239
    new_tool.Size  = size;
    new_tool.Type  = type;
    new_tool.DCode = last_D_code + 1;
charras's avatar
charras committed
240 241
    apertures.push_back( new_tool );
    return apertures.end() - 1;
242 243
}

charras's avatar
charras committed
244

245 246
void GERBER_PLOTTER::selectAperture( const wxSize&           size,
                                     APERTURE::APERTURE_TYPE type )
247
{
248
    wxASSERT( outputFile );
249

250 251 252
    if( ( currentAperture == apertures.end() )
       || ( currentAperture->Type != type )
       || ( currentAperture->Size != size ) )
charras's avatar
charras committed
253
    {
Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
254
        // Pick an existing aperture or create a new one
255
        currentAperture = getAperture( size, type );
256
        fprintf( outputFile, "D%d*\n", currentAperture->DCode );
257 258 259
    }
}

charras's avatar
charras committed
260

261
void GERBER_PLOTTER::writeApertureList()
262
{
263
    wxASSERT( outputFile );
264 265
    char cbuf[1024];

Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
266
    // Init
267
    for( std::vector<APERTURE>::iterator tool = apertures.begin();
charras's avatar
charras committed
268
         tool != apertures.end(); tool++ )
269
    {
270 271 272 273 274 275 276
        // 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

277
        char* text = cbuf + sprintf( cbuf, "%%ADD%d", tool->DCode );
278

Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
279 280 281 282 283 284 285
        /* 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) */

286
        switch( tool->Type )
charras's avatar
charras committed
287
        {
288
        case APERTURE::Circle:
Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
289
            sprintf( text, "C,%#f*%%\n", tool->Size.x * fscale );
charras's avatar
charras committed
290
            break;
291

292
        case APERTURE::Rect:
Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
293
            sprintf( text, "R,%#fX%#f*%%\n",
294 295
	             tool->Size.x * fscale,
                     tool->Size.y * fscale );
charras's avatar
charras committed
296
            break;
297

298
        case APERTURE::Plotting:
Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
299
            sprintf( text, "C,%#f*%%\n", tool->Size.x * fscale );
charras's avatar
charras committed
300
            break;
301

302
        case APERTURE::Oval:
Lorenzo Marcantonio's avatar
Lorenzo Marcantonio committed
303
            sprintf( text, "O,%#fX%#f*%%\n",
304
	            tool->Size.x * fscale,
305
		    tool->Size.y * fscale );
charras's avatar
charras committed
306 307
            break;
        }
308

309
        fputs( cbuf, outputFile );
310 311 312
    }
}

charras's avatar
charras committed
313

314
void GERBER_PLOTTER::PenTo( const wxPoint& aPos, char plume )
315
{
316 317
    wxASSERT( outputFile );
    DPOINT pos_dev = userToDeviceCoordinates( aPos );
318

charras's avatar
charras committed
319 320 321 322
    switch( plume )
    {
    case 'Z':
        break;
charras's avatar
charras committed
323

charras's avatar
charras committed
324
    case 'U':
325
        emitDcode( pos_dev, 2 );
charras's avatar
charras committed
326 327 328
        break;

    case 'D':
329
        emitDcode( pos_dev, 1 );
charras's avatar
charras committed
330
    }
charras's avatar
charras committed
331

332
    penState = plume;
charras's avatar
charras committed
333 334
}

charras's avatar
charras committed
335

336
void GERBER_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
charras's avatar
charras committed
337
{
338
    std::vector< wxPoint > cornerList;
339 340 341 342 343 344 345 346 347 348 349 350

    // 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 );
charras's avatar
charras committed
351 352
}

charras's avatar
charras committed
353

354
void GERBER_PLOTTER::Circle( const wxPoint& aCenter, int aDiameter, FILL_T aFill, int aWidth )
charras's avatar
charras committed
355
{
356 357 358
    Arc( aCenter, 0, 3600, aDiameter / 2, aFill, aWidth );
}

charras's avatar
charras committed
359

360
void GERBER_PLOTTER::Arc( const wxPoint& aCenter, double aStAngle, double aEndAngle,
361 362 363 364
                          int aRadius, FILL_T aFill, int aWidth )
{
    wxASSERT( outputFile );
    wxPoint start, end;
365 366
    start.x = aCenter.x + KiROUND( cosdecideg( aRadius, aStAngle ) );
    start.y = aCenter.y - KiROUND( sindecideg( aRadius, aStAngle ) );
367 368
    SetCurrentLineWidth( aWidth );
    MoveTo( start );
369 370
    end.x = aCenter.x + KiROUND( cosdecideg( aRadius, aEndAngle ) );
    end.y = aCenter.y - KiROUND( sindecideg( aRadius, aEndAngle ) );
371
    DPOINT devEnd = userToDeviceCoordinates( end );
372
    DPOINT devCenter = userToDeviceCoordinates( aCenter ) - userToDeviceCoordinates( start );
373

374 375 376 377 378 379
    fprintf( outputFile, "G75*\n" ); // Multiquadrant mode

    if( aStAngle < aEndAngle )
        fprintf( outputFile, "G03" );
    else
        fprintf( outputFile, "G02" );
380 381 382 383

    fprintf( outputFile, "X%dY%dI%dJ%dD01*\n",
             KiROUND( devEnd.x ), KiROUND( devEnd.y ),
             KiROUND( devCenter.x ), KiROUND( devCenter.y ) );
384
    fprintf( outputFile, "G01*\n" ); // Back to linear interp.
charras's avatar
charras committed
385 386 387
}


388
void GERBER_PLOTTER:: PlotPoly( const std::vector< wxPoint >& aCornerList,
389
                               FILL_T aFill, int aWidth )
charras's avatar
charras committed
390
{
391 392 393
    if( aCornerList.size() <= 1 )
        return;

394
    // Gerber format does not know filled polygons with thick outline
395
    // Therefore, to plot a filled polygon with outline having a thickness,
396 397
    // one should plot outline as thick segments

398
    SetCurrentLineWidth( aWidth );
charras's avatar
charras committed
399 400

    if( aFill )
401
    {
402
        fputs( "G36*\n", outputFile );
403

404
        MoveTo( aCornerList[0] );
405

406 407
        for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
            LineTo( aCornerList[ii] );
charras's avatar
charras committed
408

409 410
        FinishTo( aCornerList[0] );
        fputs( "G37*\n", outputFile );
411
    }
412 413

    if( aWidth > 0 )
414
    {
415 416 417 418 419
        MoveTo( aCornerList[0] );

        for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
            LineTo( aCornerList[ii] );

420 421 422 423 424
        // 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] );

425
        PenFinish();
charras's avatar
charras committed
426 427 428
    }
}

429 430

void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, EDA_DRAW_MODE_T trace_mode )
431
{
432
    wxASSERT( outputFile );
charras's avatar
charras committed
433
    wxSize size( diametre, diametre );
charras's avatar
charras committed
434

435
    if( trace_mode == SKETCH )
436
    {
437 438
        SetCurrentLineWidth( -1 );
        Circle( pos, diametre - currentPenWidth, NO_FILL );
439 440 441
    }
    else
    {
442
        DPOINT pos_dev = userToDeviceCoordinates( pos );
443
        selectAperture( size, APERTURE::Circle );
444
        emitDcode( pos_dev, 3 );
445 446 447
    }
}

charras's avatar
charras committed
448

449
void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
450
                                   EDA_DRAW_MODE_T trace_mode )
charras's avatar
charras committed
451
{
452
    wxASSERT( outputFile );
charras's avatar
charras committed
453
    int x0, y0, x1, y1, delta;
454
    wxSize size( aSize );
455

456
    /* Plot a flashed shape. */
charras's avatar
charras committed
457 458
    if( ( orient == 0 || orient == 900 || orient == 1800 || orient == 2700 )
       && trace_mode == FILLED )
459
    {
460
        if( orient == 900 || orient == 2700 ) /* orientation turned 90 deg. */
charras's avatar
charras committed
461
            EXCHG( size.x, size.y );
462

463
        DPOINT pos_dev = userToDeviceCoordinates( pos );
464
        selectAperture( size, APERTURE::Oval );
465
        emitDcode( pos_dev, 3 );
466
    }
467
    else /* Plot pad as a segment. */
charras's avatar
charras committed
468
    {
charras's avatar
charras committed
469 470 471
        if( size.x > size.y )
        {
            EXCHG( size.x, size.y );
472

charras's avatar
charras committed
473 474 475 476 477
            if( orient < 2700 )
                orient += 900;
            else
                orient -= 2700;
        }
478

charras's avatar
charras committed
479 480
        if( trace_mode == FILLED )
        {
481
	    /* XXX to do: use an aperture macro to declare the rotated pad */
482
            /* The pad  is reduced to an oval with dy > dx */
charras's avatar
charras committed
483 484 485 486 487 488 489
            delta = size.y - size.x;
            x0    = 0;
            y0    = -delta / 2;
            x1    = 0;
            y1    = delta / 2;
            RotatePoint( &x0, &y0, orient );
            RotatePoint( &x1, &y1, orient );
490
            ThickSegment( wxPoint( pos.x + x0, pos.y + y0 ),
charras's avatar
charras committed
491 492 493 494
                           wxPoint( pos.x + x1, pos.y + y1 ),
                           size.x, trace_mode );
        }
        else
495
        {
496
            sketchOval( pos, size, orient, -1 );
497
        }
charras's avatar
charras committed
498
    }
499 500
}

charras's avatar
charras committed
501

502
void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
503
                                   double orient, EDA_DRAW_MODE_T trace_mode )
charras's avatar
charras committed
504

505
{
506 507
    wxASSERT( outputFile );
    wxSize size( aSize );
508

509 510
    // Plot as an aperture flash
    switch( int( orient ) )
511 512
    {
    case 900:
513
    case 2700:        // rotation of 90 degrees or 270 swaps sizes
514
        EXCHG( size.x, size.y );
charras's avatar
charras committed
515

516
	// Pass through
517 518
    case 0:
    case 1800:
519
        if( trace_mode == SKETCH )
520 521 522 523 524 525 526
        {
            SetCurrentLineWidth( -1 );
            Rect( wxPoint( pos.x - (size.x - currentPenWidth) / 2,
                           pos.y - (size.y - currentPenWidth) / 2 ),
                  wxPoint( pos.x + (size.x - currentPenWidth) / 2,
                           pos.y + (size.y - currentPenWidth) / 2 ),
                  NO_FILL );
527 528 529
        }
        else
        {
530 531 532 533 534
            DPOINT pos_dev = userToDeviceCoordinates( pos );
            selectAperture( size, APERTURE::Rect );
            emitDcode( pos_dev, 3 );
        }
        break;
535

536
    default: // plot pad shape as polygon
537
	{
538
	    // XXX to do: use an aperture macro to declare the rotated pad
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
	    wxPoint coord[4];
	    // coord[0] is assumed the lower left
	    // coord[1] is assumed the upper left
	    // coord[2] is assumed the upper right
	    // coord[3] is assumed the lower right

	    /* Trace the outline. */
	    coord[0].x = -size.x/2;   // lower left
	    coord[0].y = size.y/2;
	    coord[1].x = -size.x/2;   // upper left
	    coord[1].y = -size.y/2;
	    coord[2].x = size.x/2;    // upper right
	    coord[2].y = -size.y/2;
	    coord[3].x = size.x/2;    // lower right
	    coord[3].y = size.y/2;

	    FlashPadTrapez( pos, coord, orient, trace_mode );
	}
	break;
558
    }
charras's avatar
charras committed
559 560
}

charras's avatar
charras committed
561

562
void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos,  const wxPoint* aCorners,
563
                                     double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode )
564

565
{
566
    // XXX to do: use an aperture macro to declare the pad
567
    // polygon corners list
568
    std::vector< wxPoint > cornerList;
569 570

    for( int ii = 0; ii < 4; ii++ )
571 572
        cornerList.push_back( aCorners[ii] );

573
    // Draw the polygon and fill the interior as required
574
    for( unsigned ii = 0; ii < 4; ii++ )
575
    {
576 577
        RotatePoint( &cornerList[ii], aPadOrient );
        cornerList[ii] += aPadPos;
578
    }
579

580
    // Close the polygon
581
    cornerList.push_back( cornerList[0] );
charras's avatar
charras committed
582

583
    SetCurrentLineWidth( -1 );
584
    PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL );
charras's avatar
charras committed
585
}
586

587

588 589 590
void GERBER_PLOTTER::SetLayerPolarity( bool aPositive )
{
    if( aPositive )
591
        fprintf( outputFile, "%%LPD*%%\n" );
592
    else
593
        fprintf( outputFile, "%%LPC*%%\n" );
594
}