gendrill_Excellon_writer.cpp 16.9 KB
Newer Older
1
/**
2
 * @file gendrill_Excellon_writer.cpp
3
 * @brief Functions to create EXCELLON drill files and report files.
4
 */
5

6
/*
7
 * This program source code file is part of KiCad, a free EDA CAD application.
8
 *
9 10
 * Copyright (C) 1992-2012 Jean_Pierre Charras <jp.charras at wanadoo.fr>
 * Copyright (C) 1992-2012 KiCad Developers, see change_log.txt for contributors.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *
 * 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
 */

30
/**
31
 * @see for EXCELLON format, see:
32
 * http://www.excellon.com/manuals/program.htm
33 34 35
 * and the CNC-7 manual.
 */

36
#include <fctsys.h>
37

CHARRAS's avatar
CHARRAS committed
38 39
#include <vector>

40 41
#include <plot_common.h>
#include <trigo.h>
42
#include <macros.h>
43 44
#include <kicad_string.h>
#include <wxPcbStruct.h>
45
#include <pgm_base.h>
46
#include <build_version.h>
47

48
#include <class_board.h>
49 50
#include <class_module.h>
#include <class_track.h>
51

52 53
#include <pcbplot.h>
#include <pcbnew.h>
54
#include <gendrill_Excellon_writer.h>
55
#include <wildcards_and_files_ext.h>
56

57
#include <dialog_gendrill.h>   //  Dialog box for drill file generation
58

59

60
/*
CHARRAS's avatar
CHARRAS committed
61 62 63 64
 *  Creates the drill files in EXCELLON format
 *  Number format:
 *      - Floating point format
 *      - integer format
65
 *      - integer format: "Trailing Zero" ( TZ ) or "Leading Zero"
CHARRAS's avatar
CHARRAS committed
66 67 68
 *  Units
 *      - Decimal
 *      - Metric
CHARRAS's avatar
CHARRAS committed
69
 */
70

71

Wayne Stambaugh's avatar
Wayne Stambaugh committed
72
int EXCELLON_WRITER::CreateDrillFile( FILE* aFile )
73
{
74 75
    m_file = aFile;

76 77
    int    diam, holes_count;
    int    x0, y0, xf, yf, xc, yc;
78
    double xt, yt;
79
    char   line[1024];
80

81
    SetLocaleTo_C_standard(); // Use the standard notation for double numbers
82

83
    WriteEXCELLONHeader();
84

CHARRAS's avatar
CHARRAS committed
85
    holes_count = 0;
86

87
    /* Write the tool list */
88
    for( unsigned ii = 0; ii < m_toolListBuffer.size(); ii++ )
89
    {
90
        DRILL_TOOL& tool_descr = m_toolListBuffer[ii];
91 92
        fprintf( m_file, "T%dC%.3f\n", ii + 1,
                 tool_descr.m_Diameter * m_conversionUnits  );
93 94
    }

95
    fputs( "%\n", m_file );                         // End of header info
96

97 98
    fputs( "G90\n", m_file );                       // Absolute mode
    fputs( "G05\n", m_file );                       // Drill mode
Wayne Stambaugh's avatar
Wayne Stambaugh committed
99 100

    // Units :
101
    if( !m_minimalHeader )
102
    {
103
        if( m_unitsDecimal  )
104
            fputs( "M71\n", m_file );       /* M71 = metric mode */
105
        else
106
            fputs( "M72\n", m_file );       /* M72 = inch mode */
107
    }
108

109 110
    /* Read the hole file and generate lines for normal holes (oblong
     * holes will be created later) */
111
    int tool_reference = -2;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
112

113
    for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
114
    {
115
        HOLE_INFO& hole_descr = m_holeListBuffer[ii];
116 117

        if( hole_descr.m_Hole_Shape )
118
            continue;  // oblong holes will be created later
Wayne Stambaugh's avatar
Wayne Stambaugh committed
119

120
        if( tool_reference != hole_descr.m_Tool_Reference )
121
        {
122 123
            tool_reference = hole_descr.m_Tool_Reference;
            fprintf( m_file, "T%d\n", tool_reference );
124 125
        }

126 127
        x0 = hole_descr.m_Hole_Pos.x - m_offset.x;
        y0 = hole_descr.m_Hole_Pos.y - m_offset.y;
128

129
        if( !m_mirror )
130 131
            y0 *= -1;

132 133 134
        xt = x0 * m_conversionUnits;
        yt = y0 * m_conversionUnits;
        WriteCoordinates( line, xt, yt );
135

136
        fputs( line, m_file );
137 138
        holes_count++;
    }
CHARRAS's avatar
CHARRAS committed
139

140 141 142
    /* Read the hole file and generate lines for normal holes (oblong holes
     * will be created later) */
    tool_reference = -2;    // set to a value not used for
143 144
                            // m_holeListBuffer[ii].m_Tool_Reference
    for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
145
    {
146
        HOLE_INFO& hole_descr = m_holeListBuffer[ii];
Wayne Stambaugh's avatar
Wayne Stambaugh committed
147

148
        if( hole_descr.m_Hole_Shape == 0 )
149
            continue;  // wait for oblong holes
Wayne Stambaugh's avatar
Wayne Stambaugh committed
150

151
        if( tool_reference != hole_descr.m_Tool_Reference )
152
        {
153 154
            tool_reference = hole_descr.m_Tool_Reference;
            fprintf( m_file, "T%d\n", tool_reference );
CHARRAS's avatar
CHARRAS committed
155 156
        }

Wayne Stambaugh's avatar
Wayne Stambaugh committed
157 158
        diam = std::min( hole_descr.m_Hole_Size.x, hole_descr.m_Hole_Size.y );

159 160
        if( diam == 0 )
            continue;
161

162
        /* Compute the hole coordinates: */
163 164
        xc = x0 = xf = hole_descr.m_Hole_Pos.x - m_offset.x;
        yc = y0 = yf = hole_descr.m_Hole_Pos.y - m_offset.y;
165

166
        /* Compute the start and end coordinates for the shape */
167
        if( hole_descr.m_Hole_Size.x < hole_descr.m_Hole_Size.y )
168
        {
169
            int delta = ( hole_descr.m_Hole_Size.y - hole_descr.m_Hole_Size.x ) / 2;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
170 171
            y0 -= delta;
            yf += delta;
172 173 174
        }
        else
        {
175
            int delta = ( hole_descr.m_Hole_Size.x - hole_descr.m_Hole_Size.y ) / 2;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
176 177
            x0 -= delta;
            xf += delta;
178
        }
Wayne Stambaugh's avatar
Wayne Stambaugh committed
179

180 181
        RotatePoint( &x0, &y0, xc, yc, hole_descr.m_Hole_Orient );
        RotatePoint( &xf, &yf, xc, yc, hole_descr.m_Hole_Orient );
182

183
        if( !m_mirror )
184
        {
Wayne Stambaugh's avatar
Wayne Stambaugh committed
185 186
            y0 *= -1;
            yf *= -1;
187
        }
188

189 190 191
        xt = x0 * m_conversionUnits;
        yt = y0 * m_conversionUnits;
        WriteCoordinates( line, xt, yt );
192 193 194

        /* remove the '\n' from end of line, because we must add the "G85"
         * command to the line: */
195
        for( int kk = 0; line[kk] != 0; kk++ )
Wayne Stambaugh's avatar
Wayne Stambaugh committed
196
        {
197 198
            if( line[kk] == '\n' || line[kk] =='\r' )
                line[kk] = 0;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
199
        }
CHARRAS's avatar
CHARRAS committed
200

201 202
        fputs( line, m_file );
        fputs( "G85", m_file );    // add the "G85" command
203

204 205 206
        xt = xf * m_conversionUnits;
        yt = yf * m_conversionUnits;
        WriteCoordinates( line, xt, yt );
CHARRAS's avatar
CHARRAS committed
207

208 209
        fputs( line, m_file );
        fputs( "G05\n", m_file );
210
        holes_count++;
211 212
    }

213
    WriteEXCELLONEndOfFile();
214

215
    SetLocaleTo_Default();  // Revert to locale double notation
216

CHARRAS's avatar
CHARRAS committed
217
    return holes_count;
218 219
}

220 221 222 223 224

void EXCELLON_WRITER::SetFormat( bool      aMetric,
                                 zeros_fmt aZerosFmt,
                                 int       aLeftDigits,
                                 int       aRightDigits )
225 226
{
    m_unitsDecimal = aMetric;
227
    m_zeroFormat   = aZerosFmt;
228 229 230

    /* Set conversion scale depending on drill file units */
    if( m_unitsDecimal )
231
        m_conversionUnits = 1.0 / IU_PER_MM; // EXCELLON units = mm
232
    else
233
        m_conversionUnits = 0.001 / IU_PER_MILS; // EXCELLON units = INCHES
234 235 236 237

    m_precision.m_lhs = aLeftDigits;
    m_precision.m_rhs = aRightDigits;
}
238

239 240

void EXCELLON_WRITER::WriteCoordinates( char* aLine, double aCoordX, double aCoordY )
241
{
242
    wxString xs, ys;
243
    int      xpad = m_precision.m_lhs + m_precision.m_rhs;
244 245
    int      ypad = xpad;

246
    switch( m_zeroFormat )
247 248 249
    {
    default:
    case DECIMAL_FORMAT:
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
        /* In Excellon files, resolution is 1/1000 mm or 1/10000 inch (0.1 mil)
         * Although in decimal format, Excellon specifications do not specify
         * clearly the resolution. However it seems to be 1/1000mm or 0.1 mil
         * like in non decimal formats, so we trunk coordinates to 3 or 4 digits in mantissa
         * Decimal format just prohibit useless leading 0:
         * 0.45 or .45 is right, but 00.54 is incorrect.
         */
        if( m_unitsDecimal )
        {
            // resolution is 1/1000 mm
            xs.Printf( wxT( "%.3f" ), aCoordX );
            ys.Printf( wxT( "%.3f" ), aCoordY );
        }
        else
        {
            // resolution is 1/10000 inch
            xs.Printf( wxT( "%.4f" ), aCoordX );
            ys.Printf( wxT( "%.4f" ), aCoordY );
        }

        //Remove useless trailing 0
        while( xs.Last() == '0' )
            xs.RemoveLast();
Wayne Stambaugh's avatar
Wayne Stambaugh committed
273

274 275
        while( ys.Last() == '0' )
            ys.RemoveLast();
Wayne Stambaugh's avatar
Wayne Stambaugh committed
276

277
        sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
278 279
        break;

280
    case SUPPRESS_LEADING:
281
        for( int i = 0; i< m_precision.m_rhs; i++ )
282
        {
283
            aCoordX *= 10; aCoordY *= 10;
284 285
        }

286
        sprintf( aLine, "X%dY%d\n", KiROUND( aCoordX ), KiROUND( aCoordY ) );
287 288 289 290
        break;

    case SUPPRESS_TRAILING:
    {
291
        for( int i = 0; i < m_precision.m_rhs; i++ )
292
        {
293 294
            aCoordX *= 10;
            aCoordY *= 10;
295 296
        }

297
        if( aCoordX < 0 )
298
            xpad++;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
299

300
        if( aCoordY < 0 )
301 302
            ypad++;

303 304
        xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
        ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
305 306

        size_t j = xs.Len() - 1;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
307

308 309 310 311
        while( xs[j] == '0' && j )
            xs.Truncate( j-- );

        j = ys.Len() - 1;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
312

313 314 315
        while( ys[j] == '0' && j )
            ys.Truncate( j-- );

316
        sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
317 318 319 320
        break;
    }

    case KEEP_ZEROS:
321
        for( int i = 0; i< m_precision.m_rhs; i++ )
322
        {
323
            aCoordX *= 10; aCoordY *= 10;
324 325
        }

326
        if( aCoordX < 0 )
327
            xpad++;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
328

329
        if( aCoordY < 0 )
330
            ypad++;
Wayne Stambaugh's avatar
Wayne Stambaugh committed
331

332 333
        xs.Printf( wxT( "%0*d" ), xpad, KiROUND( aCoordX ) );
        ys.Printf( wxT( "%0*d" ), ypad, KiROUND( aCoordY ) );
334
        sprintf( aLine, "X%sY%s\n", TO_UTF8( xs ), TO_UTF8( ys ) );
335 336
        break;
    }
337
}
338

339

340
void EXCELLON_WRITER::WriteEXCELLONHeader()
341
{
342
    fputs( "M48\n", m_file );    // The beginning of a header
343

344
    if( !m_minimalHeader )
345
    {
346
        // The next 2 lines in EXCELLON files are comments:
347 348
        wxString msg = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();

349
        fprintf( m_file, ";DRILL file {%s} date %s\n", TO_UTF8( msg ),
350
                 TO_UTF8( DateAndTime() ) );
charras's avatar
charras committed
351
        msg = wxT( ";FORMAT={" );
352

353
        // Print precision:
354 355
        if( m_zeroFormat != DECIMAL_FORMAT )
            msg << m_precision.GetPrecisionString();
356
        else
357
            msg << wxT( "-:-" );  // in decimal format the precision is irrelevant
Wayne Stambaugh's avatar
Wayne Stambaugh committed
358

359
        msg << wxT( "/ absolute / " );
360
        msg << ( m_unitsDecimal ? wxT( "metric" ) :  wxT( "inch" ) );
361 362 363

        /* Adding numbers notation format.
         * this is same as m_Choice_Zeros_Format strings, but NOT translated
Wayne Stambaugh's avatar
Wayne Stambaugh committed
364 365
         * because some EXCELLON parsers do not like non ASCII values
         * so we use ONLY English (ASCII) strings.
366 367
         * if new options are added in m_Choice_Zeros_Format, they must also
         * be added here
368 369
         */
        msg << wxT( " / " );
370

371
        const wxString zero_fmt[4] =
372
        {
373 374 375 376
            wxT( "decimal" ),
            wxT( "suppress leading zeros" ),
            wxT( "suppress trailing zeros" ),
            wxT( "keep zeros" )
377
        };
378

379
        msg << zero_fmt[m_zeroFormat];
380
        msg << wxT( "}\n" );
381
        fputs( TO_UTF8( msg ), m_file );
382
        fputs( "FMAT,2\n", m_file );     // Use Format 2 commands (version used since 1979)
383 384
    }

385
    fputs( m_unitsDecimal ? "METRIC" : "INCH", m_file );
386

387
    switch( m_zeroFormat )
388 389 390
    {
    case SUPPRESS_LEADING:
    case DECIMAL_FORMAT:
391
        fputs( ",TZ\n", m_file );
392 393 394
        break;

    case SUPPRESS_TRAILING:
395
        fputs( ",LZ\n", m_file );
396 397 398
        break;

    case KEEP_ZEROS:
399
        fputs( ",TZ\n", m_file ); // TZ is acceptable when all zeros are kept
400 401
        break;
    }
402 403
}

404

405
void EXCELLON_WRITER::WriteEXCELLONEndOfFile()
406 407
{
    //add if minimal here
408 409
    fputs( "T0\nM30\n", m_file );
    fclose( m_file );
410 411 412
}


413 414 415 416

/* Helper function for sorting hole list.
 * Compare function used for sorting holes  by increasing diameter value
 * and X value
417
 */
418
static bool CmpHoleDiameterValue( const HOLE_INFO& a, const HOLE_INFO& b )
419
{
420 421
    if( a.m_Hole_Diameter != b.m_Hole_Diameter )
        return a.m_Hole_Diameter < b.m_Hole_Diameter;
422

423 424
    if( a.m_Hole_Pos.x != b.m_Hole_Pos.x )
        return a.m_Hole_Pos.x < b.m_Hole_Pos.x;
425

426 427
    return a.m_Hole_Pos.y < b.m_Hole_Pos.y;
}
428

429

430 431 432
void EXCELLON_WRITER::BuildHolesList( int aFirstLayer,
                                      int aLastLayer,
                                      bool aExcludeThroughHoles,
433 434
                                      bool aGenerateNPTH_list,
                                      bool aMergePTHNPTH )
435 436 437
{
    HOLE_INFO new_hole;
    int       hole_value;
438

439 440
    m_holeListBuffer.clear();
    m_toolListBuffer.clear();
441

442 443 444 445
    if( (aFirstLayer >= 0) && (aLastLayer >= 0) )
    {
        if( aFirstLayer > aLastLayer )
            EXCHG( aFirstLayer, aLastLayer );
446 447
    }

448 449 450 451 452
    if ( aGenerateNPTH_list && aMergePTHNPTH )
    {
        return;
    }

Wayne Stambaugh's avatar
Wayne Stambaugh committed
453
    // build hole list for vias
454 455
    if( ! aGenerateNPTH_list )  // vias are always plated !
    {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
456
        for( VIA* via = GetFirstVia( m_pcb->m_Track ); via; via = GetFirstVia( via->Next() ) )
457 458
        {
            hole_value = via->GetDrillValue();
459

460
            if( hole_value == 0 )   // Should not occur.
461
                continue;
462

463 464 465 466
            new_hole.m_Tool_Reference = -1;         // Flag value for Not initialized
            new_hole.m_Hole_Orient    = 0;
            new_hole.m_Hole_Diameter  = hole_value;
            new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;
467

468
            new_hole.m_Hole_Shape = 0;              // hole shape: round
469
            new_hole.m_Hole_Pos = via->GetStart();
Dick Hollenbeck's avatar
Dick Hollenbeck committed
470

471
            via->LayerPair( &new_hole.m_Hole_Top_Layer, &new_hole.m_Hole_Bottom_Layer );
472

473
            // LayerPair return params with m_Hole_Bottom_Layer > m_Hole_Top_Layer
474 475 476 477
            // Remember: top layer = 0 and bottom layer = 31 for through hole vias
            // the via should be at least from aFirstLayer to aLastLayer
            if( (new_hole.m_Hole_Top_Layer > aFirstLayer) && (aFirstLayer >= 0) )
                continue;   // via above the first layer
478

479 480
            if( (new_hole.m_Hole_Bottom_Layer < aLastLayer) && (aLastLayer >= 0) )
                continue;   // via below the last layer
481

482
            if( aExcludeThroughHoles && (new_hole.m_Hole_Bottom_Layer == B_Cu)
Dick Hollenbeck's avatar
Dick Hollenbeck committed
483
               && (new_hole.m_Hole_Top_Layer == F_Cu) )
484 485 486 487 488 489 490 491
                continue;

            m_holeListBuffer.push_back( new_hole );
        }
    }

    // build hole list for pads (assumed always through holes)
    if( !aExcludeThroughHoles || aGenerateNPTH_list )
492
    {
493 494 495
        for( MODULE* module = m_pcb->m_Modules;  module;  module = module->Next() )
        {
            // Read and analyse pads
496
            for( D_PAD* pad = module->Pads();  pad;  pad = pad->Next() )
497
            {
498 499 500
                if( ! aGenerateNPTH_list &&
                    pad->GetAttribute() == PAD_HOLE_NOT_PLATED &&
                    ! aMergePTHNPTH )
501 502 503 504 505 506 507 508 509 510 511
                    continue;

                if( aGenerateNPTH_list && pad->GetAttribute() != PAD_HOLE_NOT_PLATED )
                    continue;

                if( pad->GetDrillSize().x == 0 )
                    continue;

                new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_HOLE_NOT_PLATED);
                new_hole.m_Tool_Reference = -1;         // Flag is: Not initialized
                new_hole.m_Hole_Orient    = pad->GetOrientation();
Wayne Stambaugh's avatar
Wayne Stambaugh committed
512 513
                new_hole.m_Hole_Shape     = 0;           // hole shape: round
                new_hole.m_Hole_Diameter  = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
514 515
                new_hole.m_Hole_Size.x    = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;

516
                if( pad->GetDrillShape() != PAD_DRILL_CIRCLE )
517 518
                    new_hole.m_Hole_Shape = 1; // oval flag set

Wayne Stambaugh's avatar
Wayne Stambaugh committed
519 520
                new_hole.m_Hole_Size         = pad->GetDrillSize();
                new_hole.m_Hole_Pos          = pad->GetPosition();               // hole position
Dick Hollenbeck's avatar
Dick Hollenbeck committed
521 522
                new_hole.m_Hole_Bottom_Layer = B_Cu;
                new_hole.m_Hole_Top_Layer    = F_Cu;// pad holes are through holes
523 524 525
                m_holeListBuffer.push_back( new_hole );
            }
        }
526 527
    }

528 529
    // Sort holes per increasing diameter value
    sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), CmpHoleDiameterValue );
530

531
    // build the tool list
Wayne Stambaugh's avatar
Wayne Stambaugh committed
532
    int        LastHole = -1; /* Set to not initialized (this is a value not used
533 534 535
                               * for m_holeListBuffer[ii].m_Hole_Diameter) */
    DRILL_TOOL new_tool( 0 );
    unsigned   jj;
536

537 538 539 540 541 542 543 544
    for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
    {
        if( m_holeListBuffer[ii].m_Hole_Diameter != LastHole )
        {
            new_tool.m_Diameter = ( m_holeListBuffer[ii].m_Hole_Diameter );
            m_toolListBuffer.push_back( new_tool );
            LastHole = new_tool.m_Diameter;
        }
545

546
        jj = m_toolListBuffer.size();
547

548
        if( jj == 0 )
Wayne Stambaugh's avatar
Wayne Stambaugh committed
549
            continue;                                        // Should not occurs
550

551
        m_holeListBuffer[ii].m_Tool_Reference = jj;          // Tool value Initialized (value >= 1)
552

553
        m_toolListBuffer.back().m_TotalCount++;
554

555 556
        if( m_holeListBuffer[ii].m_Hole_Shape )
            m_toolListBuffer.back().m_OvalCount++;
557
    }
558
}