specctra_import.cpp 17.9 KB
Newer Older
1
/*
2
 * This program source code file is part of KiCad, a free EDA CAD application.
3
 *
4
 * Copyright (C) 2007-2010 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5
 * Copyright (C) 2007 KiCad Developers, see change_log.txt for contributors.
dickelbeck's avatar
dickelbeck committed
6
 *
7 8 9 10
 * 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.
dickelbeck's avatar
dickelbeck committed
11
 *
12 13 14 15
 * 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.
dickelbeck's avatar
dickelbeck committed
16
 *
17 18
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
dickelbeck's avatar
dickelbeck committed
19 20 21
 * 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.,
22 23 24
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

dickelbeck's avatar
dickelbeck committed
25

26
/*  This source is a complement to specctra.cpp and implements the import of
dickelbeck's avatar
dickelbeck committed
27 28
    a specctra session file (*.ses), and import of a specctra design file
    (*.dsn) file.  The specification for the grammar of the specctra dsn file
29
    used to develop this code is given here:
30
    http://tech.groups.yahoo.com/group/kicad-users/files/  then file "specctra.pdf"
31 32 33 34
    Also see the comments at the top of the specctra.cpp file itself.
*/


35 36 37 38 39 40 41 42 43 44 45 46 47
#include <class_drawpanel.h>    // m_canvas
#include <confirm.h>            // DisplayError()
#include <gestfich.h>           // EDA_FileSelector()
#include <wxPcbStruct.h>

#include <class_board.h>
#include <class_module.h>
#include <class_edge_mod.h>
#include <class_track.h>
#include <class_zone.h>
#include <class_drawsegment.h>

#include <specctra.h>
48 49 50 51


using namespace DSN;

52
void PCB_EDIT_FRAME::ImportSpecctraDesign( wxCommandEvent& event )
53
{
dickelbeck's avatar
dickelbeck committed
54 55
    /* @todo write this someday

dickelbeck's avatar
dickelbeck committed
56 57
    if( !Clear_Pcb( true ) )
        return;
dickelbeck's avatar
dickelbeck committed
58
    */
59 60 61
}


62
void PCB_EDIT_FRAME::ImportSpecctraSession( wxCommandEvent& event )
63
{
dickelbeck's avatar
dickelbeck committed
64
/*
dickelbeck's avatar
dickelbeck committed
65 66 67 68 69
    if( GetScreen()->IsModify() )
    {
        if( !IsOK( this, _( "Board Modified: Continue ?" ) ) )
            return;
    }
dickelbeck's avatar
dickelbeck committed
70
*/
dickelbeck's avatar
dickelbeck committed
71

72
    wxString fullFileName = GetScreen()->GetFileName();
73 74 75 76
    wxString path;
    wxString name;
    wxString ext;

dickelbeck's avatar
dickelbeck committed
77 78 79
    wxString sessionExt( wxT( ".ses" ) );
    wxString mask = wxT( "*" ) + sessionExt;

80 81
    wxFileName::SplitPath( fullFileName, &path, &name, &ext );
    name += sessionExt;
dickelbeck's avatar
dickelbeck committed
82

83
    fullFileName = EDA_FileSelector( _( "Merge Specctra Session file:" ),
84 85 86 87 88 89
                            path,
                            name,
                            sessionExt,
                            mask,
                            this,
                            wxFD_OPEN,
90
                            false
91
                            );
dickelbeck's avatar
dickelbeck committed
92

93
    if( fullFileName == wxEmptyString )
dickelbeck's avatar
dickelbeck committed
94
        return;
dickelbeck's avatar
dickelbeck committed
95

dickelbeck's avatar
dickelbeck committed
96 97
    SPECCTRA_DB     db;

98
    LOCALE_IO       toggle;
dickelbeck's avatar
dickelbeck committed
99 100 101

    try
    {
102
        db.LoadSESSION( fullFileName );
103
        db.FromSESSION( GetBoard() );
dickelbeck's avatar
dickelbeck committed
104
    }
Dick Hollenbeck's avatar
Dick Hollenbeck committed
105
    catch( IO_ERROR& ioe )
dickelbeck's avatar
dickelbeck committed
106
    {
dickelbeck's avatar
dickelbeck committed
107 108 109 110 111
        ioe.errorText += '\n';
        ioe.errorText += _("BOARD may be corrupted, do not save it.");
        ioe.errorText += '\n';
        ioe.errorText += _("Fix problem and try again.");

dickelbeck's avatar
dickelbeck committed
112 113 114
        DisplayError( this, ioe.errorText );
        return;
    }
dickelbeck's avatar
dickelbeck committed
115

116
    OnModify();
117
    GetBoard()->m_Status_Pcb = 0;
118

119 120 121 122 123 124 125 126
    /* At this point we should call Compile_Ratsnest()
     * but this could be time consumming.
     * So if incorrect number of Connecred and No connected pads is accepted
     * until Compile_Ratsnest() is called (when track tool selected for instance)
     * leave the next line commented
     * Otherwise uncomment this line
    */
    //Compile_Ratsnest( NULL, true );
dickelbeck's avatar
dickelbeck committed
127

128
    SetStatusText( wxString( _( "Session file imported and merged OK." ) ) );
dickelbeck's avatar
dickelbeck committed
129

130
    m_canvas->Refresh( true );
dickelbeck's avatar
dickelbeck committed
131 132 133 134
}


namespace DSN {
dickelbeck's avatar
dickelbeck committed
135

136 137 138

/**
 * Function scale
139
 * converts a session file distance to KiCad units of deci-mils.
140 141 142
 * @param distance The session file length to convert.
 * @param aResolution The session UNIT_RES which holds the engineering unit
 *  specifier
143
 * @return int - The KiCad length in deci-mils
144
 */
145
static int scale( double distance, UNIT_RES* aResolution )
dickelbeck's avatar
dickelbeck committed
146
{
dickelbeck's avatar
dickelbeck committed
147 148
    double  resValue = aResolution->GetValue();

149
    double  factor;     // multiply this times session value to get mils for KiCad.
dickelbeck's avatar
dickelbeck committed
150 151 152 153 154

    switch( aResolution->GetEngUnits() )
    {
    default:
    case T_inch:
155
        factor = 1000.0;
dickelbeck's avatar
dickelbeck committed
156 157 158 159 160
        break;
    case T_mil:
        factor = 1.0;
        break;
    case T_cm:
161
        factor = 1000.0/2.54;
dickelbeck's avatar
dickelbeck committed
162 163
        break;
    case T_mm:
164
        factor = 1000.0/25.4;
dickelbeck's avatar
dickelbeck committed
165 166
        break;
    case T_um:
167
        factor = 1.0/25.4;
dickelbeck's avatar
dickelbeck committed
168 169 170
        break;
    }

dickelbeck's avatar
dickelbeck committed
171
    // the factor of 10.0 is used to convert mils to deci-mils, the units
172
    // used within KiCad.
dickelbeck's avatar
dickelbeck committed
173 174
    factor *= 10.0;

175
    int ret = wxRound( factor * distance / resValue );
176
    return ret;
177 178
}

179 180 181 182

/**
 * Function mapPt
 * translates a point from the Specctra Session format coordinate system
183
 * to the KiCad coordinate system.
184
 * @param aPoint The session point to translate
185
 * @param aResolution - The amount to scale the point.
186
 * @return wxPoint - The KiCad coordinate system point.
187
 */
188 189 190
static wxPoint mapPt( const POINT& aPoint, UNIT_RES* aResolution )
{
    wxPoint ret(  scale( aPoint.x, aResolution ),
191
                 -scale( aPoint.y, aResolution ) );    // negate y
dickelbeck's avatar
dickelbeck committed
192

dickelbeck's avatar
dickelbeck committed
193 194
    return ret;
}
dickelbeck's avatar
dickelbeck committed
195

dickelbeck's avatar
dickelbeck committed
196

197
TRACK* SPECCTRA_DB::makeTRACK( PATH* aPath, int aPointIndex, int aNetcode ) throw( IO_ERROR )
198 199 200 201 202
{
    int layerNdx = findLayerName( aPath->layer_id );

    if( layerNdx == -1 )
    {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
203
        wxString layerName = FROM_UTF8( aPath->layer_id.c_str() );
204
        ThrowIOError( _("Session file uses invalid layer id \"%s\""),
205
                        GetChars(layerName) );
206 207 208 209 210 211 212 213 214 215 216 217 218 219
    }

    TRACK* track = new TRACK( sessionBoard );

    track->m_Start   = mapPt( aPath->points[aPointIndex+0], routeResolution );
    track->m_End     = mapPt( aPath->points[aPointIndex+1], routeResolution );
    track->SetLayer( pcbLayer2kicad[layerNdx] );
    track->m_Width   = scale( aPath->aperture_width, routeResolution );
    track->SetNet( aNetcode );

    return track;
}


220
SEGVIA* SPECCTRA_DB::makeVIA( PADSTACK* aPadstack, const POINT& aPoint, int aNetCode ) throw( IO_ERROR )
dickelbeck's avatar
dickelbeck committed
221 222 223 224 225 226
{
    SEGVIA* via = 0;
    SHAPE*  shape;

    int     shapeCount = aPadstack->Length();
    int     drillDiam = -1;
227
    int     copperLayerCount = sessionBoard->GetCopperLayerCount();
dickelbeck's avatar
dickelbeck committed
228 229


230
    // The drill diameter is encoded in the padstack name if Pcbnew did the DSN export.
dickelbeck's avatar
dickelbeck committed
231 232 233 234 235
    // It is in mils and is after the colon and before the last '_'
    int     drillStartNdx = aPadstack->padstack_id.find( ':' );

    if( drillStartNdx != -1 )
    {
236
        ++drillStartNdx;    // skip over the ':'
dickelbeck's avatar
dickelbeck committed
237 238 239
        int drillEndNdx = aPadstack->padstack_id.rfind( '_' );
        if( drillEndNdx != -1 )
        {
240
            std::string diamTxt( aPadstack->padstack_id, drillStartNdx, drillEndNdx-drillStartNdx );
241 242 243 244
            const char* sdiamTxt = diamTxt.c_str();
            double drillMils = strtod( sdiamTxt, 0 );

            // drillMils is not in the session units, but actual mils so we don't use scale()
245
            drillDiam = (int) (drillMils * 10);
246 247 248
/** @todo: see if we use default netclass or specific value
*/
            drillDiam = -1;         // import as default: real drill is the netclass value
dickelbeck's avatar
dickelbeck committed
249 250 251 252 253
        }
    }

    if( shapeCount == 0 )
    {
254
        ThrowIOError( _( "Session via padstack has no shapes") );
dickelbeck's avatar
dickelbeck committed
255 256 257 258
    }
    else if( shapeCount == 1 )
    {
        shape = (SHAPE*) (*aPadstack)[0];
259 260
        DSN_T type = shape->shape->Type();
        if( type != T_circle )
261 262
            ThrowIOError( _( "Unsupported via shape: %s"),
                     GetChars( GetTokenString( type ) ) );
263 264 265 266 267 268

        CIRCLE* circle = (CIRCLE*) shape->shape;
        int viaDiam = scale( circle->diameter, routeResolution );

        via = new SEGVIA( sessionBoard );
        via->SetPosition( mapPt( aPoint, routeResolution ) );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
269
        via->SetDrill( drillDiam );
270 271
        via->m_Shape = VIA_THROUGH;
        via->m_Width = viaDiam;
dickelbeck's avatar
dickelbeck committed
272
        via->SetLayerPair( LAYER_N_FRONT, LAYER_N_BACK );
dickelbeck's avatar
dickelbeck committed
273
    }
274
    else if( shapeCount == copperLayerCount )
dickelbeck's avatar
dickelbeck committed
275 276
    {
        shape = (SHAPE*) (*aPadstack)[0];
277 278
        DSN_T type = shape->shape->Type();
        if( type != T_circle )
279 280
            ThrowIOError( _( "Unsupported via shape: %s"),
                     GetChars( GetTokenString( type ) ) );
281 282 283 284 285 286

        CIRCLE* circle = (CIRCLE*) shape->shape;
        int viaDiam = scale( circle->diameter, routeResolution );

        via = new SEGVIA( sessionBoard );
        via->SetPosition( mapPt( aPoint, routeResolution ) );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
287
        via->SetDrill( drillDiam );
288 289
        via->m_Shape = VIA_THROUGH;
        via->m_Width = viaDiam;
dickelbeck's avatar
dickelbeck committed
290
        via->SetLayerPair( LAYER_N_FRONT, LAYER_N_BACK );
291 292 293 294 295 296 297 298
    }
    else    // VIA_MICROVIA or VIA_BLIND_BURIED
    {
        int topLayerNdx = -1;
        int botLayerNdx = 7000;
        int viaDiam = -1;

        for( int i=0; i<shapeCount;  ++i )
dickelbeck's avatar
dickelbeck committed
299
        {
300 301 302
            shape = (SHAPE*) (*aPadstack)[i];
            DSN_T type = shape->shape->Type();
            if( type != T_circle )
303 304
                ThrowIOError( _( "Unsupported via shape: %s"),
                         GetChars( GetTokenString( type ) ) );
305

dickelbeck's avatar
dickelbeck committed
306
            CIRCLE* circle = (CIRCLE*) shape->shape;
307 308 309 310

            int layerNdx = findLayerName( circle->layer_id );
            if( layerNdx == -1 )
            {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
311
                wxString layerName = FROM_UTF8( circle->layer_id.c_str() );
312
                ThrowIOError( _("Session file uses invalid layer id \"%s\""),
313
                                GetChars( layerName ) );
314 315 316 317 318 319 320 321 322 323
            }

            if( layerNdx > topLayerNdx )
                topLayerNdx = layerNdx;

            if( layerNdx < botLayerNdx )
                botLayerNdx = layerNdx;

            if( viaDiam == -1 )
                viaDiam = scale( circle->diameter, routeResolution );
dickelbeck's avatar
dickelbeck committed
324
        }
325 326 327

        via = new SEGVIA( sessionBoard );
        via->SetPosition( mapPt( aPoint, routeResolution ) );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
328
        via->SetDrill( drillDiam );
329 330 331 332 333 334 335 336 337 338 339 340 341

        if( (topLayerNdx==0 && botLayerNdx==1)
         || (topLayerNdx==copperLayerCount-2 && botLayerNdx==copperLayerCount-1))
            via->m_Shape = VIA_MICROVIA;
        else
            via->m_Shape = VIA_BLIND_BURIED;

        via->m_Width = viaDiam;

        topLayerNdx = pcbLayer2kicad[topLayerNdx];
        botLayerNdx = pcbLayer2kicad[botLayerNdx];

        via->SetLayerPair( topLayerNdx, botLayerNdx );
dickelbeck's avatar
dickelbeck committed
342 343 344 345 346 347 348 349 350 351
    }

    if( via )
        via->SetNet( aNetCode );

    return via;
}



dickelbeck's avatar
dickelbeck committed
352
// no UI code in this function, throw exception to report problems to the
353
// UI handler: void PCB_EDIT_FRAME::ImportSpecctraSession( wxCommandEvent& event )
dickelbeck's avatar
dickelbeck committed
354

355
void SPECCTRA_DB::FromSESSION( BOARD* aBoard ) throw( IO_ERROR )
dickelbeck's avatar
dickelbeck committed
356
{
357
    sessionBoard = aBoard;      // not owned here
dickelbeck's avatar
dickelbeck committed
358

dickelbeck's avatar
dickelbeck committed
359 360
    if( !session )
        ThrowIOError( _("Session file is missing the \"session\" section") );
dickelbeck's avatar
dickelbeck committed
361

362
    /* Dick 16-Jan-2012: session need not have a placement section.
dickelbeck's avatar
dickelbeck committed
363 364
    if( !session->placement )
        ThrowIOError( _("Session file is missing the \"placement\" section") );
365
    */
dickelbeck's avatar
dickelbeck committed
366 367 368 369 370 371

    if( !session->route )
        ThrowIOError( _("Session file is missing the \"routes\" section") );

    if( !session->route->library )
        ThrowIOError( _("Session file is missing the \"library_out\" section") );
dickelbeck's avatar
dickelbeck committed
372

dickelbeck's avatar
dickelbeck committed
373
    // delete all the old tracks and vias
374
    aBoard->m_Track.DeleteAll();
dickelbeck's avatar
dickelbeck committed
375

dickelbeck's avatar
dickelbeck committed
376
    aBoard->DeleteMARKERs();
dickelbeck's avatar
dickelbeck committed
377

378 379
    buildLayerMaps( aBoard );

380
    if( session->placement )
dickelbeck's avatar
dickelbeck committed
381
    {
382 383 384 385 386
        // Walk the PLACEMENT object's COMPONENTs list, and for each PLACE within
        // each COMPONENT, reposition and re-orient each component and put on
        // correct side of the board.
        COMPONENTS& components = session->placement->components;
        for( COMPONENTS::iterator comp=components.begin();  comp!=components.end();  ++comp )
dickelbeck's avatar
dickelbeck committed
387
        {
388 389
            PLACES& places = comp->places;
            for( unsigned i=0; i<places.size();  ++i )
dickelbeck's avatar
dickelbeck committed
390
            {
391
                PLACE* place = &places[i];  // '&' even though places[] holds a pointer!
dickelbeck's avatar
dickelbeck committed
392

393 394 395 396 397 398 399 400
                wxString reference = FROM_UTF8( place->component_id.c_str() );
                MODULE* module = aBoard->FindModuleByReference( reference );
                if( !module )
                {
                    ThrowIOError(
                       _("Session file has 'reference' to non-existent component \"%s\""),
                       GetChars( reference ) );
                }
dickelbeck's avatar
dickelbeck committed
401

402 403
                if( !place->hasVertex )
                    continue;
dickelbeck's avatar
dickelbeck committed
404

405 406
                UNIT_RES* resolution = place->GetUnits();
                wxASSERT( resolution );
dickelbeck's avatar
dickelbeck committed
407

408 409 410 411
                wxPoint newPos = mapPt( place->vertex, resolution );
                module->SetPosition( newPos );

                if( place->side == T_front )
dickelbeck's avatar
dickelbeck committed
412
                {
413 414 415 416 417 418 419 420
                    // convert from degrees to tenths of degrees used in KiCad.
                    int orientation = (int) (place->rotation * 10.0);
                    if( module->GetLayer() != LAYER_N_FRONT )
                    {
                        // module is on copper layer (back)
                        module->Flip( module->m_Pos );
                    }
                    module->SetOrientation( orientation );
dickelbeck's avatar
dickelbeck committed
421
                }
422
                else if( place->side == T_back )
dickelbeck's avatar
dickelbeck committed
423
                {
424 425 426 427 428 429 430 431 432 433 434 435
                    int orientation = (int) ((place->rotation + 180.0) * 10.0);
                    if( module->GetLayer() != LAYER_N_BACK )
                    {
                        // module is on component layer (front)
                        module->Flip( module->m_Pos );
                    }
                    module->SetOrientation( orientation );
                }
                else
                {
                    // as I write this, the PARSER *is* catching this, so we should never see below:
                    wxFAIL_MSG( wxT("DSN::PARSER did not catch an illegal side := 'back|front'") );
436
                }
dickelbeck's avatar
dickelbeck committed
437 438
            }
        }
dickelbeck's avatar
dickelbeck committed
439 440
    }

441 442
    routeResolution = session->route->GetUnits();

dickelbeck's avatar
dickelbeck committed
443
    // Walk the NET_OUTs and create tracks and vias anew.
dickelbeck's avatar
dickelbeck committed
444
    NET_OUTS& net_outs = session->route->net_outs;
445
    for( NET_OUTS::iterator net=net_outs.begin();  net!=net_outs.end();  ++net )
dickelbeck's avatar
dickelbeck committed
446
    {
dickelbeck's avatar
dickelbeck committed
447
        int         netCode = 0;
dickelbeck's avatar
dickelbeck committed
448

dickelbeck's avatar
dickelbeck committed
449 450
        // page 143 of spec says wire's net_id is optional
        if( net->net_id.size() )
451
        {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
452
            wxString netName = FROM_UTF8( net->net_id.c_str() );
dickelbeck's avatar
dickelbeck committed
453

454 455 456
            NETINFO_ITEM* net = aBoard->FindNet( netName );
            if( net )
                netCode = net->GetNet();
457 458 459 460
            else  // else netCode remains 0
            {
                // int breakhere = 1;
            }
461 462 463 464 465 466 467 468 469 470
        }

        WIRES& wires = net->wires;
        for( unsigned i=0;  i<wires.size();  ++i )
        {
            WIRE*   wire  = &wires[i];
            DSN_T   shape = wire->shape->Type();

            if( shape != T_path )
            {
471 472 473 474 475 476
                /*  shape == T_polygon is expected from freerouter if you have
                    a zone on a non "power" type layer, i.e. a T_signal layer
                    and the design does a round trip back in as session here.
                    We kept our own zones in the BOARD, so ignore this so called
                    'wire'.

Dick Hollenbeck's avatar
Dick Hollenbeck committed
477
                wxString netId = FROM_UTF8( wire->net_id.c_str() );
478 479
                ThrowIOError(
                    _("Unsupported wire shape: \"%s\" for net: \"%s\""),
480
                    DLEX::GetTokenString(shape).GetData(),
481 482
                    netId.GetData()
                    );
483
                */
484
            }
485
            else
486
            {
487 488 489 490 491 492 493 494 495 496 497 498 499 500
                PATH*   path = (PATH*) wire->shape;
                for( unsigned pt=0;  pt<path->points.size()-1;  ++pt )
                {
                    /* a debugging aid, may come in handy
                    if( path->points[pt].x == 547800
                    &&  path->points[pt].y == -380250 )
                    {
                        int breakhere = 1;
                    }
                    */

                    TRACK* track = makeTRACK( path, pt, netCode );
                    aBoard->Add( track );
                }
501 502 503 504
            }
        }

        WIRE_VIAS& wire_vias = net->wire_vias;
dickelbeck's avatar
dickelbeck committed
505
        LIBRARY& library = *session->route->library;
506 507
        for( unsigned i=0;  i<wire_vias.size();  ++i )
        {
dickelbeck's avatar
dickelbeck committed
508 509 510 511 512
            int         netCode = 0;

            // page 144 of spec says wire_via's net_id is optional
            if( net->net_id.size() )
            {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
513
                wxString netName = FROM_UTF8( net->net_id.c_str() );
dickelbeck's avatar
dickelbeck committed
514

515 516 517
                NETINFO_ITEM* net = aBoard->FindNet( netName );
                if( net )
                    netCode = net->GetNet();
518

dickelbeck's avatar
dickelbeck committed
519 520 521 522 523 524 525 526 527 528
                // else netCode remains 0
            }

            WIRE_VIA* wire_via = &wire_vias[i];

            // example: (via Via_15:8_mil 149000 -71000 )

            PADSTACK* padstack = library.FindPADSTACK( wire_via->GetPadstackId() );
            if( !padstack )
            {
529 530 531 532 533 534 535 536 537
                // Dick  Feb 29, 2008:
                // Freerouter has a bug where it will not round trip all vias.
                // Vias which have a (use_via) element will be round tripped.
                // Vias which do not, don't come back in in the session library,
                // even though they may be actually used in the pre-routed,
                // protected wire_vias. So until that is fixed, create the
                // padstack from its name as a work around.


538
                // Could use a STRING_FORMATTER here and convert the entire
dickelbeck's avatar
dickelbeck committed
539
                // wire_via to text and put that text into the exception.
Dick Hollenbeck's avatar
Dick Hollenbeck committed
540
                wxString psid( FROM_UTF8( wire_via->GetPadstackId().c_str() ) );
dickelbeck's avatar
dickelbeck committed
541 542

                ThrowIOError( _("A wire_via references a missing padstack \"%s\""),
543
                             GetChars( psid ) );
dickelbeck's avatar
dickelbeck committed
544 545 546 547 548
            }

            for( unsigned v=0;  v<wire_via->vertexes.size();  ++v )
            {
                SEGVIA* via = makeVIA( padstack, wire_via->vertexes[v], netCode );
549
                aBoard->Add( via );
dickelbeck's avatar
dickelbeck committed
550
            }
551
        }
dickelbeck's avatar
dickelbeck committed
552
    }
553 554 555
}


dickelbeck's avatar
dickelbeck committed
556 557
} // namespace DSN