export_idf.cpp 18.2 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 25 26 27 28 29 30 31 32 33 34 35
/**
 * @file export_idf.cpp
 */

/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013  Cirilo Bernardo
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */


#include <list>
#include <wxPcbStruct.h>
#include <macros.h>
#include <pcbnew.h>
#include <class_board.h>
#include <class_module.h>
#include <class_edge_mod.h>
36
#include <idf_parser.h>
37
#include <3d_struct.h>
38
#include <build_version.h>
39 40 41 42 43 44 45 46 47 48

// assumed default graphical line thickness: 10000 IU == 0.1mm
#define LINE_WIDTH (100000)

/**
 * Function idf_export_outline
 * retrieves line segment information from the edge layer and compiles
 * the data into a form which can be output as an IDFv3 compliant
 * BOARD_OUTLINE section.
 */
49
static void idf_export_outline( BOARD* aPcb, IDF3_BOARD& aIDFBoard )
50
{
51
    double scale = aIDFBoard.GetUserScale();
52 53 54 55 56

    DRAWSEGMENT* graphic;               // KiCad graphical item
    IDF_POINT sp, ep;                   // start and end points from KiCad item

    std::list< IDF_SEGMENT* > lines;    // IDF intermediate form of KiCad graphical item
57
    IDF_OUTLINE* outline = NULL;        // graphical items forming an outline or cutout
58 59 60 61 62 63 64

    // NOTE: IMPLEMENTATION
    // If/when component cutouts are allowed, we must implement them separately. Cutouts
    // must be added to the board outline section and not to the Other Outline section.
    // The module cutouts should be handled via the idf_export_module() routine.

    double offX, offY;
65
    aIDFBoard.GetUserOffset( offX, offY );
66 67 68 69

    // Retrieve segments and arcs from the board
    for( BOARD_ITEM* item = aPcb->m_Drawings; item; item = item->Next() )
    {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
70
        if( item->Type() != PCB_LINE_T || item->GetLayer() != Edge_Cuts )
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
            continue;

        graphic = (DRAWSEGMENT*) item;

        switch( graphic->GetShape() )
        {
        case S_SEGMENT:
            {
                sp.x    = graphic->GetStart().x * scale + offX;
                sp.y    = -graphic->GetStart().y * scale + offY;
                ep.x    = graphic->GetEnd().x * scale + offX;
                ep.y    = -graphic->GetEnd().y * scale + offY;
                IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep );

                if( seg )
                    lines.push_back( seg );
            }
            break;

        case S_ARC:
            {
                sp.x = graphic->GetCenter().x * scale + offX;
                sp.y = -graphic->GetCenter().y * scale + offY;
                ep.x = graphic->GetArcStart().x * scale + offX;
                ep.y = -graphic->GetArcStart().y * scale + offY;
                IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, -graphic->GetAngle() / 10.0, true );

                if( seg )
                    lines.push_back( seg );
            }
            break;

        case S_CIRCLE:
            {
                sp.x = graphic->GetCenter().x * scale + offX;
                sp.y = -graphic->GetCenter().y * scale + offY;
                ep.x = sp.x - graphic->GetRadius() * scale;
                ep.y = sp.y;
                // Circles must always have an angle of +360 deg. to appease
                // quirky MCAD implementations of IDF.
                IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, 360.0, true );

                if( seg )
                    lines.push_back( seg );
            }
            break;

        default:
            break;
        }
    }

    // if there is no outline then use the bounding box
    if( lines.empty() )
    {
        goto UseBoundingBox;
    }

    // get the board outline and write it out
    // note: we do not use a try/catch block here since we intend
    // to simply ignore unclosed loops and continue processing
    // until we're out of segments to process
133 134
    outline = new IDF_OUTLINE;
    IDF3::GetOutline( lines, *outline );
135

136
    if( outline->empty() )
137 138
        goto UseBoundingBox;

139 140
    aIDFBoard.AddBoardOutline( outline );
    outline = NULL;
141 142 143 144

    // get all cutouts and write them out
    while( !lines.empty() )
    {
145 146
        if( !outline )
            outline = new IDF_OUTLINE;
147

148 149 150 151 152
        IDF3::GetOutline( lines, *outline );

        if( outline->empty() )
        {
            outline->Clear();
153
            continue;
154
        }
155

156 157
        aIDFBoard.AddBoardOutline( outline );
        outline = NULL;
158 159 160 161 162 163 164 165 166 167 168 169 170
    }

    return;

UseBoundingBox:

    // clean up if necessary
    while( !lines.empty() )
    {
        delete lines.front();
        lines.pop_front();
    }

171 172 173 174
    if( outline )
        outline->Clear();
    else
        outline = new IDF_OUTLINE;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

    // fetch a rectangular bounding box for the board;
    // there is always some uncertainty in the board dimensions
    // computed via ComputeBoundingBox() since this depends on the
    // individual module entities.
    EDA_RECT bbbox = aPcb->ComputeBoundingBox( true );

    // convert to mm and compensate for an assumed LINE_WIDTH line thickness
    double  x   = ( bbbox.GetOrigin().x + LINE_WIDTH / 2 ) * scale + offX;
    double  y   = ( bbbox.GetOrigin().y + LINE_WIDTH / 2 ) * scale + offY;
    double  dx  = ( bbbox.GetSize().x - LINE_WIDTH ) * scale;
    double  dy  = ( bbbox.GetSize().y - LINE_WIDTH ) * scale;

    double px[4], py[4];
    px[0]   = x;
    py[0]   = y;

    px[1]   = x;
    py[1]   = y + dy;

    px[2]   = x + dx;
    py[2]   = y + dy;

    px[3]   = x + dx;
    py[3]   = y;

    IDF_POINT p1, p2;

    p1.x    = px[3];
    p1.y    = py[3];
    p2.x    = px[0];
    p2.y    = py[0];

208
    outline->push( new IDF_SEGMENT( p1, p2 ) );
209 210 211 212 213 214 215

    for( int i = 1; i < 4; ++i )
    {
        p1.x    = px[i - 1];
        p1.y    = py[i - 1];
        p2.x    = px[i];
        p2.y    = py[i];
216

217
        outline->push( new IDF_SEGMENT( p1, p2 ) );
218 219
    }

220
    aIDFBoard.AddBoardOutline( outline );
221 222 223 224 225 226 227 228 229 230 231
}


/**
 * Function idf_export_module
 * retrieves information from all board modules, adds drill holes to
 * the DRILLED_HOLES or BOARD_OUTLINE section as appropriate,
 * compiles data for the PLACEMENT section and compiles data for
 * the library ELECTRICAL section.
 */
static void idf_export_module( BOARD* aPcb, MODULE* aModule,
232
        IDF3_BOARD& aIDFBoard )
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
{
    // Reference Designator
    std::string crefdes = TO_UTF8( aModule->GetReference() );

    if( crefdes.empty() || !crefdes.compare( "~" ) )
    {
        std::string cvalue = TO_UTF8( aModule->GetValue() );

        // if both the RefDes and Value are empty or set to '~' the board owns the part,
        // otherwise associated parts of the module must be marked NOREFDES.
        if( cvalue.empty() || !cvalue.compare( "~" ) )
            crefdes = "BOARD";
        else
            crefdes = "NOREFDES";
    }

    // TODO: If module cutouts are supported we must add code here
    // for( EDA_ITEM* item = aModule->GraphicalItems();  item != NULL;  item = item->Next() )
    // {
    // if( ( item->Type() != PCB_MODULE_EDGE_T )
Dick Hollenbeck's avatar
Dick Hollenbeck committed
253
    // || (item->GetLayer() != Edge_Cuts ) ) continue;
254 255 256 257 258
    // code to export cutouts
    // }

    // Export pads
    double  drill, x, y;
259
    double  scale = aIDFBoard.GetUserScale();
260 261 262 263 264 265
    IDF3::KEY_PLATING kplate;
    std::string pintype;
    std::string tstr;

    double dx, dy;

266
    aIDFBoard.GetUserOffset( dx, dy );
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

    for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
    {
        drill = (double) pad->GetDrillSize().x * scale;
        x     = pad->GetPosition().x * scale + dx;
        y     = -pad->GetPosition().y * scale + dy;

        // Export the hole on the edge layer
        if( drill > 0.0 )
        {
            // plating
            if( pad->GetAttribute() == PAD_HOLE_NOT_PLATED )
                kplate = IDF3::NPTH;
            else
                kplate = IDF3::PTH;

            // hole type
            tstr = TO_UTF8( pad->GetPadName() );

            if( tstr.empty() || !tstr.compare( "0" ) || !tstr.compare( "~" )
287 288
                || ( kplate == IDF3::NPTH )
                ||( pad->GetDrillShape() == PAD_DRILL_OBLONG ) )
289 290 291 292 293 294 295 296 297 298 299 300
                pintype = "MTG";
            else
                pintype = "PIN";

            // fields:
            // 1. hole dia. : float
            // 2. X coord : float
            // 3. Y coord : float
            // 4. plating : PTH | NPTH
            // 5. Assoc. part : BOARD | NOREFDES | PANEL | {"refdes"}
            // 6. type : PIN | VIA | MTG | TOOL | { "other" }
            // 7. owner : MCAD | ECAD | UNOWNED
301
            if( ( pad->GetDrillShape() == PAD_DRILL_OBLONG )
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
                && ( pad->GetDrillSize().x != pad->GetDrillSize().y ) )
            {
                // NOTE: IDF does not have direct support for slots;
                // slots are implemented as a board cutout and we
                // cannot represent plating or reference designators

                double dlength = pad->GetDrillSize().y * scale;

                // NOTE: The orientation of modules and pads have
                // the opposite sense due to KiCad drawing on a
                // screen with a LH coordinate system
                double angle = pad->GetOrientation() / 10.0;

                if( dlength < drill )
                {
                    std::swap( drill, dlength );
                    angle += M_PI2;
                }

                // NOTE: KiCad measures a slot's length from end to end
                // rather than between the centers of the arcs
                dlength -= drill;

                aIDFBoard.AddSlot( drill, dlength, angle, x, y );
            }
            else
            {
329 330 331 332 333 334 335 336 337 338 339 340 341
                IDF_DRILL_DATA *dp = new IDF_DRILL_DATA( drill, x, y, kplate, crefdes,
                                                         pintype, IDF3::ECAD );

                if( !aIDFBoard.AddDrill( dp ) )
                {
                    delete dp;

                    std::ostringstream ostr;
                    ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
                    ostr << "(): could not add drill";

                    throw std::runtime_error( ostr.str() );
                }
342 343 344 345
            }
        }
    }

346 347 348
    // add any valid models to the library item list
    std::string refdes;

349 350
    IDF3_COMPONENT* comp = NULL;

351 352 353 354 355
    for( S3D_MASTER* modfile = aModule->Models(); modfile != 0; modfile = modfile->Next() )
    {
        if( !modfile->Is3DType( S3D_MASTER::FILE3D_IDF ) )
            continue;

unknown's avatar
unknown committed
356 357 358 359
        if( refdes.empty() )
        {
            refdes = TO_UTF8( aModule->GetReference() );

360 361 362 363
            // NOREFDES cannot be used or else the software gets confused
            // when writing out the placement data due to conflicting
            // placement and layer specifications; to work around this we
            // create a (hopefully) unique refdes for our exported part.
unknown's avatar
unknown committed
364
            if( refdes.empty() || !refdes.compare( "~" ) )
365
                refdes = aIDFBoard.GetNewRefDes();
unknown's avatar
unknown committed
366 367
        }

368 369 370 371 372 373 374
        IDF3_COMP_OUTLINE* outline;

        outline = aIDFBoard.GetComponentOutline( modfile->GetShape3DName() );

        if( !outline )
            throw( std::runtime_error( aIDFBoard.GetError() ) );

375
        double rotz = aModule->GetOrientation()/10.0;
376 377 378
        double locx = modfile->m_MatPosition.x;
        double locy = modfile->m_MatPosition.y;
        double locz = modfile->m_MatPosition.z;
379
        double lrot = modfile->m_MatRotation.z;
380

Dick Hollenbeck's avatar
Dick Hollenbeck committed
381
        bool top = ( aModule->GetLayer() == B_Cu ) ? false : true;
382 383 384

        if( top )
        {
385
            rotz += modfile->m_MatRotation.z;
386 387 388 389
            locy = -locy;
            RotatePoint( &locx, &locy, aModule->GetOrientation() );
            locy = -locy;
        }
390

391 392 393 394 395 396 397 398 399 400 401 402 403 404
        if( !top )
        {
            RotatePoint( &locx, &locy, aModule->GetOrientation() );
            locy = -locy;

            rotz = 180.0 - rotz;

            if( rotz >= 360.0 )
                while( rotz >= 360.0 ) rotz -= 360.0;

            if( rotz <= -360.0 )
                while( rotz <= -360.0 ) rotz += 360.0;
        }

405 406
        if( comp == NULL )
            comp = aIDFBoard.FindComponent( refdes );
407

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
        if( comp == NULL )
        {
            comp = new IDF3_COMPONENT( &aIDFBoard );

            if( comp == NULL )
                throw( std::runtime_error( aIDFBoard.GetError() ) );

            comp->SetRefDes( refdes );

            if( top )
                comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                   -aModule->GetPosition().y * scale + dy,
                                   rotz, IDF3::LYR_TOP );
            else
                comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                   -aModule->GetPosition().y * scale + dy,
                                   rotz, IDF3::LYR_BOTTOM );

            comp->SetPlacement( IDF3::PS_ECAD );

            aIDFBoard.AddComponent( comp );
        }
        else
        {
            double refX, refY, refA;
            IDF3::IDF_LAYER side;

            if( ! comp->GetPosition( refX, refY, refA, side ) )
            {
                // place the item
                if( top )
                    comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                       -aModule->GetPosition().y * scale + dy,
                                       rotz, IDF3::LYR_TOP );
442 443 444 445 446 447 448
                else
                    comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                       -aModule->GetPosition().y * scale + dy,
                                       rotz, IDF3::LYR_BOTTOM );

                comp->SetPlacement( IDF3::PS_ECAD );

449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
            }
            else
            {
                // check that the retrieved component matches this one
                refX = refX - ( aModule->GetPosition().x * scale + dx );
                refY = refY - ( -aModule->GetPosition().y * scale + dy );
                refA = refA - rotz;
                refA *= refA;
                refX *= refX;
                refY *= refY;
                refX += refY;

                // conditions: same side, X,Y coordinates within 10 microns,
                // angle within 0.01 degree
                if( ( top && side == IDF3::LYR_BOTTOM ) || ( !top && side == IDF3::LYR_TOP )
                    || ( refA > 0.0001 ) || ( refX > 0.0001 ) )
                {
                    comp->GetPosition( refX, refY, refA, side );

                    std::ostringstream ostr;
                    ostr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                    ostr << "* conflicting Reference Designator '" << refdes << "'\n";
                    ostr << "* X loc: " << (aModule->GetPosition().x * scale + dx);
                    ostr << " vs. " << refX << "\n";
                    ostr << "* Y loc: " << (-aModule->GetPosition().y * scale + dy);
                    ostr << " vs. " << refY << "\n";
                    ostr << "* angle: " << rotz;
                    ostr << " vs. " << refA << "\n";

                    if( top )
                        ostr << "* TOP vs. ";
                    else
                        ostr << "* BOTTOM vs. ";

                    if( side == IDF3::LYR_TOP )
                        ostr << "TOP";
                    else
                        ostr << "BOTTOM";
Dick Hollenbeck's avatar
Dick Hollenbeck committed
487

488 489 490 491 492 493 494 495 496 497 498
                    throw( std::runtime_error( ostr.str() ) );
                }
            }
        }


        // create the local data ...
        IDF3_COMP_OUTLINE_DATA* data = new IDF3_COMP_OUTLINE_DATA( comp, outline );

        data->SetOffsets( locx, locy, locz, lrot );
        comp->AddOutlineData( data );
499 500 501
    }

    return;
502 503 504 505 506 507 508 509
}


/**
 * Function Export_IDF3
 * generates IDFv3 compliant board (*.emn) and library (*.emp)
 * files representing the user's PCB design.
 */
510
bool Export_IDF3( BOARD* aPcb, const wxString& aFullFileName, bool aUseThou )
511
{
512
    IDF3_BOARD idfBoard( IDF3::CAD_ELEC );
513 514 515

    SetLocaleTo_C_standard();

516 517 518 519 520 521 522 523 524 525
    bool ok = true;
    double scale = 1e-6;    // we must scale internal units to mm for IDF
    IDF3::IDF_UNIT idfUnit;

    if( aUseThou )
    {
        idfUnit = IDF3::UNIT_THOU;
        idfBoard.SetUserPrecision( 1 );
    }
    else
unknown's avatar
unknown committed
526
    {
527 528 529 530 531 532 533 534 535 536 537
        idfUnit = IDF3::UNIT_MM;
        idfBoard.SetUserPrecision( 5 );
    }

    wxFileName brdName = aPcb->GetFileName();

    idfBoard.SetUserScale( scale );
    idfBoard.SetBoardThickness( aPcb->GetDesignSettings().GetBoardThickness() * scale );
    idfBoard.SetBoardName( TO_UTF8( brdName.GetFullName() ) );
    idfBoard.SetBoardVersion( 0 );
    idfBoard.SetLibraryVersion( 0 );
538

539 540 541 542 543 544
    std::ostringstream ostr;
    ostr << "Created by KiCad " << TO_UTF8( GetBuildVersion() );
    idfBoard.SetIDFSource( ostr.str() );

    try
    {
unknown's avatar
unknown committed
545 546
        // set up the global offsets
        EDA_RECT bbox = aPcb->ComputeBoundingBox( true );
547 548
        idfBoard.SetUserOffset( -bbox.Centre().x * scale,
                            bbox.Centre().y * scale );
549

unknown's avatar
unknown committed
550 551
        // Export the board outline
        idf_export_outline( aPcb, idfBoard );
552

unknown's avatar
unknown committed
553 554 555
        // Output the drill holes and module (library) data.
        for( MODULE* module = aPcb->m_Modules; module != 0; module = module->Next() )
            idf_export_module( aPcb, module, idfBoard );
556

557 558 559 560 561 562 563 564
        if( !idfBoard.WriteFile( aFullFileName, idfUnit, false ) )
        {
            wxString msg;
            msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( idfBoard.GetError().c_str() );
            wxMessageBox( msg );

            ok = false;
        }
unknown's avatar
unknown committed
565
    }
566
    catch( const IO_ERROR& ioe )
unknown's avatar
unknown committed
567
    {
568 569 570 571 572 573
        wxString msg;
        msg << _( "IDF Export Failed:\n" ) << ioe.errorText;
        wxMessageBox( msg );

        ok = false;
    }
unknown's avatar
unknown committed
574
    catch( const std::exception& e )
575 576 577 578 579
    {
        wxString msg;
        msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( e.what() );
        wxMessageBox( msg );
        ok = false;
unknown's avatar
unknown committed
580
    }
581 582 583

    SetLocaleTo_Default();

584
    return ok;
585
}