rs274x.cpp 32.4 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) 2007-2014 Jean-Pierre Charras  jp.charras at wanadoo.fr
 * Copyright (C) 1992-2014 KiCad Developers, see change_log.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
/**
 * @file rs274x.cpp
 */
28

29 30 31
#include <fctsys.h>
#include <common.h>
#include <macros.h>
32
#include <base_units.h>
33

34 35
#include <gerbview.h>
#include <class_GERBER.h>
36
#include <class_X2_gerber_attributes.h>
37

38 39
extern int ReadInt( char*& text, bool aSkipSeparator = true );
extern double ReadDouble( char*& text, bool aSkipSeparator = true );
40
extern bool GetEndOfBlock( char buff[GERBER_BUFZ], char*& text, FILE* gerber_file );
41

42

43
#define CODE( x, y ) ( ( (x) << 8 ) + (y) )
44

45 46 47 48 49 50 51 52
// See rs274xrevd_e.pdf, table 1: RS-274X parameters order of entry
// in gerber files, when a coordinate is given (like X78Y600 or I0J80):
//      Y and Y are logical coordinates
//      A and B are plotter coordiantes
//      Usually A = X, B = Y
//      But we can have A = Y, B = X and/or offset, mirror, scale;
// Also:
//  Image is what you must plot (the entire data of the file).
53
//  Layer is just a set of data blocks with their parameters. An image can have more than one
54
//   layer so a gerber layer is not like a board layer or the graphic layers used in GerbView
55
//   to show a file.
56
enum RS274X_PARAMETERS {
57
    // Directive parameters: single usage recommended
58
    // Must be at the beginning of the file
59 60 61 62
    AXIS_SELECT   = CODE( 'A', 'S' ),           // Default: A=X, B=Y
    FORMAT_STATEMENT = CODE( 'F', 'S' ),        // no default: this command must exists
    MIRROR_IMAGE  = CODE( 'M', 'I' ),           // Default: mo mirror
    MODE_OF_UNITS = CODE( 'M', 'O' ),           // Default:  inch
63 64
    INCH   = CODE( 'I', 'N' ),
    MILLIMETER = CODE( 'M', 'M' ),
65 66 67 68 69 70 71 72 73 74 75 76 77 78
    OFFSET = CODE( 'O', 'F' ),                  // Default: A = 0, B = 0
    SCALE_FACTOR   = CODE( 'S', 'F' ),          // Default:  A = 1.0, B = 1.0

    // Image parameters:
    // commands used only once at the beginning of the file
    IMAGE_JUSTIFY  = CODE( 'I', 'J' ),          // Default: no justification
    IMAGE_NAME     = CODE( 'I', 'N' ),          // Default: void
    IMAGE_OFFSET   = CODE( 'I', 'O' ),          // Default: A = 0, B = 0
    IMAGE_POLARITY = CODE( 'I', 'P' ),          // Default: Positive
    IMAGE_ROTATION = CODE( 'I', 'R' ),          // Default: 0
    PLOTTER_FILM   = CODE( 'P', 'M' ),

    // Aperture parameters:
    // Usually for the whole file
79 80
    AP_DEFINITION   = CODE( 'A', 'D' ),
    AP_MACRO = CODE( 'A', 'M' ),
81

82 83 84 85 86 87 88
    // X2 extention attribute commands
    // Mainly are found standard attributes and user attributes
    // standard attributes commands are:
    // TF (file attribute)
    // TA (aperture attribute) and TD (delete aperture attribute)
    FILE_ATTRIBUTE   = CODE( 'T', 'F' ),

89 90 91
    // Layer specific parameters
    // May be used singly or may be layer specfic
    // theses parameters are at the beginning of the file or layer
92
    // and reset some layer parameters (like interpolation)
93
    LAYER_NAME      = CODE( 'L', 'N' ),         // Default: Positive
94
    LAYER_POLARITY  = CODE( 'L', 'P' ),
95
    KNOCKOUT = CODE( 'K', 'O' ),                // Default: off
96
    STEP_AND_REPEAT = CODE( 'S', 'R' ),         //  Default: A = 1, B = 1
97 98 99 100
    ROTATE = CODE( 'R', 'O' ),                  //  Default: 0

    // Miscellaneous parameters:
    INCLUDE_FILE   = CODE( 'I', 'F' )
101 102 103
};


dickelbeck's avatar
dickelbeck committed
104 105
/**
 * Function ReadXCommand
106
 * reads in two bytes of data and assembles them into an int with the first
dickelbeck's avatar
dickelbeck committed
107 108
 * byte in the sequence put into the most significant part of a 16 bit value
 * and the second byte put into the least significant part of the 16 bit value.
109 110
 * @param text A reference to a pointer to read bytes from and to advance as
 *             they are read.
dickelbeck's avatar
dickelbeck committed
111
 * @return int - with 16 bits of data in the ls bits, upper bits zeroed.
dickelbeck's avatar
dickelbeck committed
112 113
 */
static int ReadXCommand( char*& text )
114
{
dickelbeck's avatar
dickelbeck committed
115 116 117
    int result;

    if( text && *text )
dickelbeck's avatar
dickelbeck committed
118
        result = *text++ << 8;
dickelbeck's avatar
dickelbeck committed
119 120
    else
        return -1;
dickelbeck's avatar
dickelbeck committed
121

dickelbeck's avatar
dickelbeck committed
122
    if( text && *text )
dickelbeck's avatar
dickelbeck committed
123
        result += *text++;
dickelbeck's avatar
dickelbeck committed
124 125
    else
        return -1;
dickelbeck's avatar
dickelbeck committed
126

dickelbeck's avatar
dickelbeck committed
127
    return result;
128 129 130
}


131
bool GERBER_IMAGE::ReadRS274XCommand( char buff[GERBER_BUFZ], char*& text )
132
{
dickelbeck's avatar
dickelbeck committed
133 134 135 136 137
    bool ok = true;
    int  code_command;

    text++;

138
    for( ; ; )
dickelbeck's avatar
dickelbeck committed
139 140 141 142 143
    {
        while( *text )
        {
            switch( *text )
            {
144
            case '%':       // end of command
dickelbeck's avatar
dickelbeck committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
                text++;
                m_CommandState = CMD_IDLE;
                goto exit;  // success completion

            case ' ':
            case '\r':
            case '\n':
                text++;
                break;

            case '*':
                text++;
                break;

            default:
                code_command = ReadXCommand( text );
                ok = ExecuteRS274XCommand( code_command, buff, text );
                if( !ok )
                    goto exit;
                break;
            }
        }

168
        // end of current line, read another one.
dickelbeck's avatar
dickelbeck committed
169
        if( fgets( buff, GERBER_BUFZ, m_Current_File ) == NULL )
dickelbeck's avatar
dickelbeck committed
170
        {
171
            // end of file
dickelbeck's avatar
dickelbeck committed
172 173 174
            ok = false;
            break;
        }
dickelbeck's avatar
dickelbeck committed
175

dickelbeck's avatar
dickelbeck committed
176 177 178 179 180
        text = buff;
    }

exit:
    return ok;
181 182 183
}


184
bool GERBER_IMAGE::ExecuteRS274XCommand( int       command,
185 186
                                   char buff[GERBER_BUFZ],
                                   char*&    text )
187
{
dickelbeck's avatar
dickelbeck committed
188
    int      code;
189 190
    int      seq_len;    // not used, just provided
    int      seq_char;
191
    bool     ok = true;
dickelbeck's avatar
dickelbeck committed
192
    char     line[GERBER_BUFZ];
dickelbeck's avatar
dickelbeck committed
193 194
    wxString msg;
    double   fcoord;
195 196
    bool     x_fmt_known = false;
    bool     y_fmt_known = false;
197 198

    // conv_scale = scaling factor from inch to Internal Unit
199
    double   conv_scale = IU_PER_MILS * 1000;
200 201
    if( m_GerbMetric )
        conv_scale /= 25.4;
dickelbeck's avatar
dickelbeck committed
202

203
//    DBG( printf( "%22s: Command <%c%c>\n", __func__, (command >> 8) & 0xFF, command & 0xFF ); )
204

dickelbeck's avatar
dickelbeck committed
205 206
    switch( command )
    {
dickelbeck's avatar
dickelbeck committed
207
    case FORMAT_STATEMENT:
208
        seq_len = 2;
dickelbeck's avatar
dickelbeck committed
209

dickelbeck's avatar
dickelbeck committed
210 211 212 213 214 215 216 217 218
        while( *text != '*' )
        {
            switch( *text )
            {
            case ' ':
                text++;
                break;

            case 'L':       // No Leading 0
219 220
                m_DecimalFormat = false;
                m_NoTrailingZeros = false;
dickelbeck's avatar
dickelbeck committed
221 222 223 224
                text++;
                break;

            case 'T':       // No trailing 0
225 226 227 228 229
                m_DecimalFormat = false;
                m_NoTrailingZeros = true;
                text++;
                break;

230
            case 'A':       // Absolute coord
231
                m_Relative = false;
dickelbeck's avatar
dickelbeck committed
232
                text++;
dickelbeck's avatar
dickelbeck committed
233 234
                break;

235 236
            case 'I':       // Relative coord
                m_Relative = true;
dickelbeck's avatar
dickelbeck committed
237
                text++;
dickelbeck's avatar
dickelbeck committed
238 239
                break;

240 241 242 243
            case 'G':
            case 'N':       // Sequence code (followed by one digit: the sequence len)
                            // (sometimes found before the X,Y sequence)
                            // Obscure option
dickelbeck's avatar
dickelbeck committed
244
                text++;
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
                seq_char = *text++;
                if( (seq_char >= '0') && (seq_char <= '9') )
                    seq_len = seq_char - '0';
                break;

            case 'D':
            case 'M':       // Sequence code (followed by one digit: the sequence len)
                            // (sometimes found after the X,Y sequence)
                            // Obscure option
                code = *text++;
                if( ( *text >= '0' ) && ( *text<= '9' ) )
                    text++;     // skip the digit
                else if( code == 'D' )
                    // Decimal format: sometimes found, but not really documented
                    m_DecimalFormat = true;
dickelbeck's avatar
dickelbeck committed
260 261 262
                break;

            case 'X':
263
            case 'Y':
264 265 266 267
            {
                code = *(text++);
                char ctmp = *(text++) - '0';
                if( code == 'X' )
dickelbeck's avatar
dickelbeck committed
268
                {
269
                    x_fmt_known = true;
270
                    // number of digits after the decimal point (0 to 7 allowed)
271 272
                    m_FmtScale.x = *text - '0';
                    m_FmtLen.x   = ctmp + m_FmtScale.x;
273

274 275
                    // m_FmtScale is 0 to 7
                    // (Old Gerber specification was 0 to 6)
276 277
                    if( m_FmtScale.x < 0 )
                        m_FmtScale.x = 0;
278 279
                    if( m_FmtScale.x > 7 )
                        m_FmtScale.x = 7;
dickelbeck's avatar
dickelbeck committed
280
                }
281 282
                else
                {
283
                    y_fmt_known = true;
284 285
                    m_FmtScale.y = *text - '0';
                    m_FmtLen.y   = ctmp + m_FmtScale.y;
286 287
                    if( m_FmtScale.y < 0 )
                        m_FmtScale.y = 0;
288 289
                    if( m_FmtScale.y > 7 )
                        m_FmtScale.y = 7;
290 291 292 293
                }
                text++;
            }
            break;
dickelbeck's avatar
dickelbeck committed
294 295 296 297 298

            case '*':
                break;

            default:
299 300 301
                msg.Printf( wxT( "Unknown id (%c) in FS command" ),
                           *text );
                ReportMessage( msg );
dickelbeck's avatar
dickelbeck committed
302
                GetEndOfBlock( buff, text, m_Current_File );
303
                ok = false;
dickelbeck's avatar
dickelbeck committed
304 305 306
                break;
            }
        }
307 308
        if( !x_fmt_known || !y_fmt_known )
            ReportMessage( wxT( "RS274X: Format Statement (FS) without X or Y format" ) );
309

dickelbeck's avatar
dickelbeck committed
310 311
        break;

312 313 314 315 316 317
    case AXIS_SELECT:       // command ASAXBY*% or %ASAYBX*%
        m_SwapAxis = false;
        if( strnicmp( text, "AYBX", 4 ) == 0 )
            m_SwapAxis = true;
        break;

318
    case MIRROR_IMAGE:      // command %MIA0B0*%, %MIA0B1*%, %MIA1B0*%, %MIA1B1*%
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
        m_MirrorA = m_MirrorB = 0;
        while( *text && *text != '*' )
        {
            switch( *text )
            {
            case 'A':       // Mirror A axis ?
                text++;
                if( *text == '1' )
                    m_MirrorA = true;
                break;

            case 'B':       // Mirror B axis ?
                text++;
                if( *text == '1' )
                    m_MirrorB = true;
                break;

            default:
                text++;
                break;
            }
        }
dickelbeck's avatar
dickelbeck committed
341 342 343 344 345
        break;

    case MODE_OF_UNITS:
        code = ReadXCommand( text );
        if( code == INCH )
346
            m_GerbMetric = false;
dickelbeck's avatar
dickelbeck committed
347
        else if( code == MILLIMETER )
348
            m_GerbMetric = true;
349
        conv_scale = m_GerbMetric ? IU_PER_MILS / 25.4 : IU_PER_MILS;
dickelbeck's avatar
dickelbeck committed
350 351
        break;

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    case FILE_ATTRIBUTE:    // Command %TF ...
        m_IsX2_file = true;
    {
        X2_ATTRIBUTE dummy;
        dummy.ParseAttribCmd( m_Current_File, buff, GERBER_BUFZ, text );
        if( dummy.IsFileFunction() )
        {
            delete m_FileFunction;
            m_FileFunction = new X2_ATTRIBUTE_FILEFUNCTION( dummy );
        }
        else if( dummy.IsFileMD5() )
        {
            m_MD5_value = dummy.GetPrm( 1 );
        }
        else if( dummy.IsFilePart() )
        {
            m_PartString = dummy.GetPrm( 1 );
        }
     }
        break;

373
    case OFFSET:        // command: OFAnnBnn (nn = float number) = layer Offset
dickelbeck's avatar
dickelbeck committed
374 375 376 377 378
        m_Offset.x = m_Offset.y = 0;
        while( *text != '*' )
        {
            switch( *text )
            {
379
            case 'A':       // A axis offset in current unit (inch or mm)
dickelbeck's avatar
dickelbeck committed
380 381
                text++;
                fcoord     = ReadDouble( text );
382
                m_Offset.x = KiROUND( fcoord * conv_scale );
dickelbeck's avatar
dickelbeck committed
383 384
                break;

385
            case 'B':       // B axis offset in current unit (inch or mm)
dickelbeck's avatar
dickelbeck committed
386 387
                text++;
                fcoord     = ReadDouble( text );
388
                m_Offset.y = KiROUND( fcoord * conv_scale );
dickelbeck's avatar
dickelbeck committed
389 390 391 392 393 394
                break;
            }
        }
        break;

    case SCALE_FACTOR:
395
        m_Scale.x = m_Scale.y = 1.0;
396 397 398 399 400 401
        while( *text != '*' )
        {
            switch( *text )
            {
            case 'A':       // A axis scale
                text++;
402
                m_Scale.x = ReadDouble( text );
403 404 405 406
                break;

            case 'B':       // B axis scale
                text++;
407
                m_Scale.y = ReadDouble( text );
408 409 410
                break;
            }
        }
411
        break;
412

413 414 415
    case IMAGE_OFFSET:  // command: IOAnnBnn (nn = float number) = Image Offset
        m_ImageOffset.x = m_ImageOffset.y = 0;
        while( *text != '*' )
416
        {
417 418 419 420 421
            switch( *text )
            {
            case 'A':       // A axis offset in current unit (inch or mm)
                text++;
                fcoord     = ReadDouble( text );
422
                m_ImageOffset.x = KiROUND( fcoord * conv_scale );
423 424 425 426 427
                break;

            case 'B':       // B axis offset in current unit (inch or mm)
                text++;
                fcoord     = ReadDouble( text );
428
                m_ImageOffset.y = KiROUND( fcoord * conv_scale );
429 430
                break;
            }
431 432 433
        }
        break;

434 435
    case IMAGE_ROTATION:    // command IR0* or IR90* or IR180* or IR270*
        if( strnicmp( text, "0*", 2 ) == 0 )
436
            m_ImageRotation = 0;
437
        if( strnicmp( text, "90*", 2 ) == 0 )
438
            m_ImageRotation = 90;
439
        if( strnicmp( text, "180*", 2 ) == 0 )
440
            m_ImageRotation = 180;
441
        if( strnicmp( text, "270*", 2 ) == 0 )
442
            m_ImageRotation = 270;
443 444 445 446
        else
            ReportMessage( _( "RS274X: Command \"IR\" rotation value not allowed" ) );
        break;

447
    case STEP_AND_REPEAT:   // command SR, like %SRX3Y2I5.0J2*%
448
        m_Iterpolation = GERB_INTERPOL_LINEAR_1X;       // Start a new Gerber layer
449 450 451 452 453
        GetLayerParams().m_StepForRepeat.x = 0.0;
        GetLayerParams().m_StepForRepeat.x = 0.0;       // offset for Step and Repeat command
        GetLayerParams().m_XRepeatCount = 1;
        GetLayerParams().m_YRepeatCount = 1;            // The repeat count
        GetLayerParams().m_StepForRepeatMetric = m_GerbMetric;  // the step units
454 455 456 457 458 459
        while( *text && *text != '*' )
        {
            switch( *text )
            {
            case 'I':       // X axis offset
                text++;
460
                GetLayerParams().m_StepForRepeat.x = ReadDouble( text );
461 462 463 464
                break;

            case 'J':       // Y axis offset
                text++;
465
                GetLayerParams().m_StepForRepeat.y = ReadDouble( text );
466 467 468 469
                break;

            case 'X':       // X axis repeat count
                text++;
470
                GetLayerParams().m_XRepeatCount = ReadInt( text );
471 472 473 474
                break;

            case 'Y':       // Y axis offset
                text++;
475
                GetLayerParams().m_YRepeatCount = ReadInt( text );
476 477 478 479 480 481 482 483
                break;
            default:
                text++;
                break;
            }
        }
        break;

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
    case IMAGE_JUSTIFY: // Command IJAnBn*
        m_ImageJustifyXCenter = false;          // Image Justify Center on X axis (default = false)
        m_ImageJustifyYCenter = false;          // Image Justify Center on Y axis (default = false)
        m_ImageJustifyOffset = wxPoint(0,0);    // Image Justify Offset on XY axis (default = 0,0)
        while( *text && *text != '*' )
        {
            // IJ command is (for A or B axis) AC or AL or A<coordinate>
            switch( *text )
            {
            case 'A':       // A axis justify
                text++;
                if( *text == 'C' )
                {
                    m_ImageJustifyXCenter = true;
                    text++;
                }
                else if( *text == 'L' )
                {
                    m_ImageJustifyXCenter = true;
                    text++;
                }
505
                else m_ImageJustifyOffset.x = KiROUND( ReadDouble( text ) * conv_scale);
506 507 508 509 510 511 512 513 514 515 516 517 518 519
                break;

            case 'B':       // B axis justify
                text++;
                if( *text == 'C' )
                {
                    m_ImageJustifyYCenter = true;
                    text++;
                }
                else if( *text == 'L' )
                {
                    m_ImageJustifyYCenter = true;
                    text++;
                }
520
                else m_ImageJustifyOffset.y = KiROUND( ReadDouble( text ) * conv_scale);
521 522 523 524 525 526 527 528 529 530 531 532
                break;
            default:
                text++;
                break;
            }
        }
        if( m_ImageJustifyXCenter )
            m_ImageJustifyOffset.x = 0;
        if( m_ImageJustifyYCenter )
            m_ImageJustifyOffset.y = 0;
        break;

dickelbeck's avatar
dickelbeck committed
533
    case KNOCKOUT:
534
        m_Iterpolation = GERB_INTERPOL_LINEAR_1X;       // Start a new Gerber layer
535
        msg = _( "RS274X: Command KNOCKOUT ignored by GerbView" ) ;
536 537 538 539 540 541
        ReportMessage( msg );
        break;

    case PLOTTER_FILM:  // Command PF <string>
        // This is an info about film that must be used to plot this file
        // Has no meaning here. We just display this string
542
        msg = wxT( "Plotter Film info:<br>" );
543 544 545 546
        while( *text != '*' )
        {
           msg.Append( *text++ );
        }
547
        ReportMessage( msg );
dickelbeck's avatar
dickelbeck committed
548 549
        break;

550
    case ROTATE:        // Layer rotation: command like %RO45*%
551 552
        m_Iterpolation = GERB_INTERPOL_LINEAR_1X;       // Start a new Gerber layer
        m_LocalRotation =ReadDouble( text );             // Store layer rotation in degrees
553 554
        break;

dickelbeck's avatar
dickelbeck committed
555
    case IMAGE_NAME:
556
        m_ImageName.Empty();
dickelbeck's avatar
dickelbeck committed
557 558
        while( *text != '*' )
        {
559
            m_ImageName.Append( *text++ );
dickelbeck's avatar
dickelbeck committed
560
        }
561

562
        break;
dickelbeck's avatar
dickelbeck committed
563

564
    case LAYER_NAME:
565
        m_Iterpolation = GERB_INTERPOL_LINEAR_1X;       // Start a new Gerber layer
566
        GetLayerParams( ).m_LayerName.Empty();
567 568
        while( *text != '*' )
        {
569
            GetLayerParams( ).m_LayerName.Append( *text++ );
570
        }
571

dickelbeck's avatar
dickelbeck committed
572 573 574 575
        break;

    case IMAGE_POLARITY:
        if( strnicmp( text, "NEG", 3 ) == 0 )
576
            m_ImageNegative = true;
dickelbeck's avatar
dickelbeck committed
577
        else
578
            m_ImageNegative = false;
579
        DBG( printf( "%22s: IMAGE_POLARITY m_ImageNegative=%s\n", __func__,
580
                   m_ImageNegative ? "true" : "false" ); )
dickelbeck's avatar
dickelbeck committed
581 582 583 584
        break;

    case LAYER_POLARITY:
        if( *text == 'C' )
585
            GetLayerParams().m_LayerNegative = true;
jean-pierre charras's avatar
jean-pierre charras committed
586

dickelbeck's avatar
dickelbeck committed
587
        else
588
            GetLayerParams().m_LayerNegative = false;
589
        DBG( printf( "%22s: LAYER_POLARITY m_LayerNegative=%s\n", __func__,
590
                   GetLayerParams().m_LayerNegative ? "true" : "false" ); )
dickelbeck's avatar
dickelbeck committed
591 592 593
        break;

    case INCLUDE_FILE:
594
        if( m_FilesPtr >= INCLUDE_FILES_CNT_MAX )
dickelbeck's avatar
dickelbeck committed
595
        {
596
            ok = false;
597
            ReportMessage( _( "Too many include files!!" ) );
dickelbeck's avatar
dickelbeck committed
598 599
            break;
        }
dickelbeck's avatar
dickelbeck committed
600 601
        strcpy( line, text );
        strtok( line, "*%%\n\r" );
dickelbeck's avatar
dickelbeck committed
602
        m_FilesList[m_FilesPtr] = m_Current_File;
dickelbeck's avatar
dickelbeck committed
603 604

        m_Current_File = fopen( line, "rt" );
dickelbeck's avatar
dickelbeck committed
605 606
        if( m_Current_File == 0 )
        {
607 608
            msg.Printf( wxT( "include file <%s> not found." ), line );
            ReportMessage( msg );
609
            ok = false;
dickelbeck's avatar
dickelbeck committed
610 611 612 613 614 615
            m_Current_File = m_FilesList[m_FilesPtr];
            break;
        }
        m_FilesPtr++;
        break;

616 617 618
    case AP_MACRO:  // lines like %AMMYMACRO*
                    // 5,1,8,0,0,1.08239X$1,22.5*
                    // %
619 620 621
        ok = ReadApertureMacro( buff, text, m_Current_File );
        break;

622
    case AP_DEFINITION:
623

624 625 626 627 628 629 630
        /* input example:  %ADD30R,0.081800X0.101500*%
         * Aperture definition has 4 options: C, R, O, P
         * (Circle, Rect, Oval, regular Polygon)
         * and shapes can have a hole (round or rectangular).
         * All optional parameters values start by X
         * at this point, text points to 2nd 'D'
         */
631
        if( *text++ != 'D' )
dickelbeck's avatar
dickelbeck committed
632
        {
633
            ok = false;
dickelbeck's avatar
dickelbeck committed
634
            break;
dickelbeck's avatar
dickelbeck committed
635
        }
636

637
        m_Has_DCode = true;
dickelbeck's avatar
dickelbeck committed
638

639
        code = ReadInt( text );
dickelbeck's avatar
dickelbeck committed
640

641
        D_CODE* dcode;
642
        dcode = GetDCODE( code );
dickelbeck's avatar
dickelbeck committed
643 644
        if( dcode == NULL )
            break;
dickelbeck's avatar
dickelbeck committed
645

646 647 648
        // at this point, text points to character after the ADD<num>,
        // i.e. R in example above.  If text[0] is one of the usual
        // apertures: (C,R,O,P), there is a comma after it.
649
        if( text[1] == ',' )
dickelbeck's avatar
dickelbeck committed
650
        {
651
            char stdAperture = *text;
652 653 654

            text += 2;              // skip "C," for example

655 656
            dcode->m_Size.x = KiROUND( ReadDouble( text ) * conv_scale );
            dcode->m_Size.y = dcode->m_Size.x;
dickelbeck's avatar
dickelbeck committed
657

658
            switch( stdAperture )   // Aperture desceiption has optional parameters. Read them
dickelbeck's avatar
dickelbeck committed
659 660
            {
            case 'C':               // Circle
661
                dcode->m_Shape = APT_CIRCLE;
dickelbeck's avatar
dickelbeck committed
662 663 664 665 666 667 668
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Drill.x = dcode->m_Drill.y =
669
                                           KiROUND( ReadDouble( text ) * conv_scale );
670
                    dcode->m_DrillShape = APT_DEF_ROUND_HOLE;
dickelbeck's avatar
dickelbeck committed
671
                }
dickelbeck's avatar
dickelbeck committed
672

dickelbeck's avatar
dickelbeck committed
673 674 675 676 677 678 679
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Drill.y =
680
                        KiROUND( ReadDouble( text ) * conv_scale );
dickelbeck's avatar
dickelbeck committed
681

682
                    dcode->m_DrillShape = APT_DEF_RECT_HOLE;
dickelbeck's avatar
dickelbeck committed
683
                }
684
                dcode->m_Defined = true;
dickelbeck's avatar
dickelbeck committed
685 686
                break;

687
            case 'O':               // oval
dickelbeck's avatar
dickelbeck committed
688
            case 'R':               // rect
689
                dcode->m_Shape = (stdAperture == 'O') ? APT_OVAL : APT_RECT;
dickelbeck's avatar
dickelbeck committed
690

dickelbeck's avatar
dickelbeck committed
691 692 693 694 695 696 697
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Size.y =
698
                        KiROUND( ReadDouble( text ) * conv_scale );
dickelbeck's avatar
dickelbeck committed
699
                }
dickelbeck's avatar
dickelbeck committed
700

dickelbeck's avatar
dickelbeck committed
701 702 703 704 705 706
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
707 708
                    dcode->m_Drill.x = KiROUND( ReadDouble( text ) * conv_scale );
                    dcode->m_Drill.y = dcode->m_Drill.x;
709
                    dcode->m_DrillShape = APT_DEF_ROUND_HOLE;
dickelbeck's avatar
dickelbeck committed
710
                }
dickelbeck's avatar
dickelbeck committed
711

dickelbeck's avatar
dickelbeck committed
712 713 714
                while( *text == ' ' )
                    text++;

715
                if( *text == 'X' )
dickelbeck's avatar
dickelbeck committed
716 717 718
                {
                    text++;
                    dcode->m_Drill.y =
719
                        KiROUND( ReadDouble( text ) * conv_scale );
720
                    dcode->m_DrillShape = APT_DEF_RECT_HOLE;
dickelbeck's avatar
dickelbeck committed
721
                }
722
                dcode->m_Defined = true;
dickelbeck's avatar
dickelbeck committed
723 724
                break;

725
            case 'P':
726

727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
                /* Regular polygon: a command line like %ADD12P,0.040X10X25X0.025X0.025X0.0150*%
                 * params are: <diameter>, X<edge count>, X<Rotation>, X<X hole dim>, X<Y hole dim>
                 */
                dcode->m_Shape = APT_POLYGON;
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_EdgesCount = ReadInt( text );
                }

                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Rotation = ReadDouble( text );
                }

                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
755 756
                    dcode->m_Drill.x = KiROUND( ReadDouble( text ) * conv_scale );
                    dcode->m_Drill.y = dcode->m_Drill.x =
757 758 759 760 761 762 763 764 765
                    dcode->m_DrillShape = APT_DEF_ROUND_HOLE;
                }

                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
766
                    dcode->m_Drill.y = KiROUND( ReadDouble( text ) * conv_scale );
767 768
                    dcode->m_DrillShape = APT_DEF_RECT_HOLE;
                }
769
                dcode->m_Defined = true;
dickelbeck's avatar
dickelbeck committed
770 771 772
                break;
            }
        }
773 774
        else    // text[0] starts an aperture macro name
        {
775
            APERTURE_MACRO am_lookup;
776

777
            while( *text && *text != '*' && *text != ',' )
778 779
                am_lookup.name.Append( *text++ );

780 781 782 783 784
            // When an aperture definition is like %AMLINE2* 22,1,$1,$2,0,0,-45*
            // the ADDxx<MACRO_NAME> command has parameters, like %ADD14LINE2,0.8X0.5*%
            if( *text == ',' )
            {   // Read aperture macro parameters and store them
                text++;     // text points the first parameter
785
                while( *text && *text != '*' )
786 787 788
                {
                    double param = ReadDouble( text );
                    dcode->AppendParam( param );
789 790 791
                    while( isspace( *text ) ) text++;
                    if( *text == 'X' )
                        ++text;
792 793 794 795 796 797 798
                }
            }

            // lookup the aperture macro here.
            APERTURE_MACRO* pam = FindApertureMacro( am_lookup );
            if( !pam )
            {
799
                msg.Printf( wxT( "RS274X: aperture macro %s not found\n" ),
800
                           TO_UTF8( am_lookup.name ) );
801
                ReportMessage( msg );
802 803 804 805
                ok = false;
                break;
            }

dickelbeck's avatar
dickelbeck committed
806
            dcode->m_Shape = APT_MACRO;
807 808
            dcode->SetMacro( (APERTURE_MACRO*) pam );
        }
dickelbeck's avatar
dickelbeck committed
809 810 811
        break;

    default:
812
        ok = false;
dickelbeck's avatar
dickelbeck committed
813 814 815
        break;
    }

816 817
    (void) seq_len;     // quiet g++, or delete the unused variable.

dickelbeck's avatar
dickelbeck committed
818
    ok = GetEndOfBlock( buff, text, m_Current_File );
dickelbeck's avatar
dickelbeck committed
819

dickelbeck's avatar
dickelbeck committed
820
    return ok;
821 822
}

dickelbeck's avatar
dickelbeck committed
823

dickelbeck's avatar
dickelbeck committed
824
bool GetEndOfBlock( char buff[GERBER_BUFZ], char*& text, FILE* gerber_file )
825
{
826
    for( ; ; )
dickelbeck's avatar
dickelbeck committed
827
    {
dickelbeck's avatar
dickelbeck committed
828
        while( (text < buff + GERBER_BUFZ) && *text )
dickelbeck's avatar
dickelbeck committed
829 830
        {
            if( *text == '*' )
831
                return true;
dickelbeck's avatar
dickelbeck committed
832

dickelbeck's avatar
dickelbeck committed
833
            if( *text == '%' )
834
                return true;
dickelbeck's avatar
dickelbeck committed
835

dickelbeck's avatar
dickelbeck committed
836 837 838
            text++;
        }

dickelbeck's avatar
dickelbeck committed
839
        if( fgets( buff, GERBER_BUFZ, gerber_file ) == NULL )
dickelbeck's avatar
dickelbeck committed
840
            break;
dickelbeck's avatar
dickelbeck committed
841

dickelbeck's avatar
dickelbeck committed
842 843 844
        text = buff;
    }

845
    return false;
846 847
}

848

849 850
/**
 * Function GetNextLine
851 852 853
 * test for an end of line
 * if an end of line is found:
 *   read a new line
854
 * @param aBuff = buffer (size = GERBER_BUFZ) to fill with a new line
855 856 857 858 859 860
 * @param aText = pointer to the last useful char in aBuff
 *          on return: points the beginning of the next line.
 * @param aFile = the opened GERBER file to read
 * @return a pointer to the beginning of the next line or NULL if end of file
*/
static char* GetNextLine(  char aBuff[GERBER_BUFZ], char* aText, FILE* aFile  )
861
{
862
    for( ; ; )
863
    {
864
        switch (*aText )
865
        {
866 867 868 869 870 871 872 873 874 875 876
            case ' ':     // skip blanks
            case '\n':
            case '\r':    // Skip line terminators
                ++aText;
                break;

            case 0:    // End of text found in aBuff: Read a new string
                if( fgets( aBuff, GERBER_BUFZ, aFile ) == NULL )
                    return NULL;
                aText = aBuff;
                return aText;
877

878 879
            default:
                return aText;
880 881
        }
    }
882
    return aText;
883 884 885
}


886
bool GERBER_IMAGE::ReadApertureMacro( char buff[GERBER_BUFZ],
887 888
                                char*&    text,
                                FILE*     gerber_file )
889
{
890
    wxString       msg;
dickelbeck's avatar
dickelbeck committed
891
    APERTURE_MACRO am;
dickelbeck's avatar
dickelbeck committed
892

893 894
    // read macro name
    while( *text )
dickelbeck's avatar
dickelbeck committed
895 896
    {
        if( *text == '*' )
897 898
        {
            ++text;
dickelbeck's avatar
dickelbeck committed
899
            break;
900
        }
dickelbeck's avatar
dickelbeck committed
901

902
        am.name.Append( *text++ );
dickelbeck's avatar
dickelbeck committed
903 904
    }

905
    // Read aperture macro parameters
906
    for( ; ; )
dickelbeck's avatar
dickelbeck committed
907 908
    {
        if( *text == '*' )
909
            ++text;
dickelbeck's avatar
dickelbeck committed
910

911 912
        text = GetNextLine( buff, text, gerber_file );  // Get next line
        if( text == NULL )  // End of File
913
            return false;
914

915 916 917 918 919 920
        // text points the beginning of a new line.

        // Test for the last line in aperture macro lis:
        // last line is % or *% sometime found.
        if( *text == '*' )
            ++text;
921 922 923
        if( *text == '%' )
            break;      // exit with text still pointing at %

924
        int paramCount = 0;
925 926 927 928 929
        int primitive_type = AMP_UNKNOWN;
        // Test for a valid symbol at the beginning of a description:
        // it can be: a parameter declaration like $1=$2/4
        // or a digit (macro primitive selection)
        // all other symbols are illegal.
930
        if( *text == '$' )  // local parameter declaration, inside the aperture macro
931
        {
932 933 934 935 936 937 938
            am.m_localparamStack.push_back( AM_PARAM() );
            AM_PARAM& param = am.m_localparamStack.back();
            text = GetNextLine(  buff, text, gerber_file );
            if( text == NULL)   // End of File
                return false;
            param.ReadParam( text );
            continue;
939 940 941 942
        }
        else if( !isdigit(*text)  )     // Ill. symbol
        {
            msg.Printf( wxT( "RS274X: Aperture Macro \"%s\": ill. symbol, line: \"%s\"" ),
943
                        GetChars( am.name ), GetChars( FROM_UTF8( buff ) ) );
944 945 946 947 948 949
            ReportMessage( msg );
            primitive_type = AMP_COMMENT;
        }
        else
            primitive_type = ReadInt( text );

950
        switch( primitive_type )
951
        {
952 953
        case AMP_COMMENT:     // lines starting by 0 are a comment
            paramCount = 0;
954 955 956 957 958
            // Skip comment
            while( *text && (*text != '*') )
                text++;
            break;

959 960 961
        case AMP_CIRCLE:
            paramCount = 4;
            break;
962

963 964 965 966
        case AMP_LINE2:
        case AMP_LINE20:
            paramCount = 7;
            break;
967

968 969 970 971
        case AMP_LINE_CENTER:
        case AMP_LINE_LOWER_LEFT:
            paramCount = 6;
            break;
972

973 974 975
        case AMP_EOF:
            paramCount = 0;
            break;
976

977 978 979
        case AMP_OUTLINE:
            paramCount = 4;
            break;
980

981
        case AMP_POLYGON:
982
            paramCount = 6;
983
            break;
984

985 986 987
        case AMP_MOIRE:
            paramCount = 9;
            break;
988

989 990 991
        case AMP_THERMAL:
            paramCount = 6;
            break;
992

993
        default:
994
            // @todo, there needs to be a way of reporting the line number
995
            msg.Printf( wxT( "RS274X: Aperture Macro \"%s\": Invalid primitive id code %d, line: \"%s\"" ),
996
                        GetChars( am.name ), primitive_type,  GetChars( FROM_UTF8( buff ) ) );
997
            ReportMessage( msg );
998
            return false;
999 1000
        }

1001 1002
        AM_PRIMITIVE prim( m_GerbMetric );
        prim.primitive_id = (AM_PRIMITIVE_ID) primitive_type;
1003
        int i;
1004

1005
        for( i = 0; i < paramCount && *text && *text != '*'; ++i )
1006
        {
1007
            prim.params.push_back( AM_PARAM() );
dickelbeck's avatar
dickelbeck committed
1008

1009
            AM_PARAM& param = prim.params.back();
1010

1011
            text = GetNextLine(  buff, text, gerber_file );
1012

1013
            if( text == NULL)   // End of File
1014
                return false;
1015

1016
            param.ReadParam( text );
1017 1018
        }

1019
        if( i < paramCount )
1020 1021
        {
            // maybe some day we can throw an exception and track a line number
1022
            msg.Printf( wxT( "RS274X: read macro descr type %d: read %d parameters, insufficient parameters\n" ),
1023
                        prim.primitive_id, i );
1024
            ReportMessage( msg );
1025

1026
        }
1027
        // there are more parameters to read if this is an AMP_OUTLINE
1028 1029
        if( prim.primitive_id == AMP_OUTLINE )
        {
1030 1031 1032
            // so far we have read [0]:exposure, [1]:#points, [2]:X start, [3]: Y start
            // Now read all the points, plus trailing rotation in degrees.

1033 1034 1035 1036
            // params[1] is a count of polygon points, so it must be given
            // in advance, i.e. be immediate.
            wxASSERT( prim.params[1].IsImmediate() );

1037
            paramCount = (int) prim.params[1].GetValue( 0 ) * 2 + 1;
1038

1039
            for( int i = 0; i < paramCount && *text != '*'; ++i )
1040
            {
1041
                prim.params.push_back( AM_PARAM() );
dickelbeck's avatar
dickelbeck committed
1042

1043
                AM_PARAM& param = prim.params.back();
1044

1045
                text = GetNextLine(  buff, text, gerber_file );
1046

1047
                if( text == NULL )  // End of File
1048
                    return false;
1049

1050
                param.ReadParam( text );
1051 1052
            }
        }
1053 1054

        am.primitives.push_back( prim );
dickelbeck's avatar
dickelbeck committed
1055 1056
    }

1057 1058 1059
    m_aperture_macros.insert( am );

    return true;
1060
}
1061