/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 2007-2008 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2007 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
 */

 
/*  This source is a complement to specctra.cpp and implements the export to
    specctra dsn file format.  The specification for the grammar of the specctra
    dsn file used to develop this code is given here:
    http://www.autotraxeda.com/docs/SPECCTRA/SPECCTRA.pdf
        
    Also see the comments at the top of the specctra.cpp file itself.
*/


#include "specctra.h"
#include "collectors.h"
#include "wxPcbStruct.h"        // Change_Side_Module()
#include "pcbstruct.h"          // HISTORY_NUMBER

using namespace DSN;


// see wxPcbStruct.h
void WinEDA_PcbFrame::ExportToSPECCTRA( wxCommandEvent& event )
{
    wxString        fullFileName = GetScreen()->m_FileName;
    wxString        std_ext = wxT( ".dsn" );
    wxString        mask    = wxT( "*" ) + std_ext;
    
    ChangeFileNameExt( fullFileName, std_ext );
    
    fullFileName = EDA_FileSelector( _( "Specctra DSN file:" ),
                                     wxEmptyString,     /* Chemin par defaut */
                                     fullFileName,      /* nom fichier par defaut */
                                     std_ext,           /* extension par defaut */
                                     mask,              /* Masque d'affichage */
                                     this,
                                     wxFD_SAVE,
                                     FALSE
                                     );
    if( fullFileName == wxEmptyString )
        return;

    
    SPECCTRA_DB     db;
    bool            ok = true;
    wxString        errorText;
    
    db.SetPCB( SPECCTRA_DB::MakePCB() );

    try 
    {    
        db.FromBOARD( m_Pcb );
        db.ExportPCB(  fullFileName, true );
    
        // if an exception is thrown by FromBOARD or ExportPCB(), then 
        // ~SPECCTRA_DB() will close the file.
    } 
    catch( IOError ioe )
    {
        ok = false;
        
        // copy the error string to safe place, ioe is in this scope only.
        errorText = ioe.errorText;
    }

    if( ok )
    {
        // @todo display a message saying the export is complete.
    }
    else
        DisplayError( this, errorText );
}


namespace DSN {

struct POINT_PAIR
{
    POINT       p1;         ///< start
    POINT       p2;         ///< end
    BOARD_ITEM* item;       ///< the item which has these points, TRACK or DRAWSEGMENT
};
typedef std::vector<POINT_PAIR>     POINT_PAIRS; 


static inline void swap( POINT_PAIR& pair )
{
    POINT temp = pair.p1;
    pair.p1 = pair.p2;
    pair.p2 = temp;
}


/**
 * Function scale
 * converts a distance from kicad units to our reported specctra dsn units:
 * 1/10000 inches (deci-mils) to mils.  So the factor of 10 comes in.
 */
static inline double scale( int kicadDist )
{
    return kicadDist/10.0;
}

static inline double mapX( int x )
{
    return scale(x);
}

static inline double mapY( int y )
{
    return -scale(y);      // make y negative, since it is increasing going down.
}


/**
 * Function mapPt
 * converts a Kicad point into a DSN file point.  Kicad's BOARD coordinates
 * are in deci-mils  (i.e. 1/10,000th of an inch) and we are exporting in units
 * of mils, so we have to divide by 10.
 */
static POINT mapPt( const wxPoint& pt )
{
    POINT ret;
    ret.x = mapX( pt.x );
    ret.y = mapY( pt.y );
    ret.FixNegativeZero();
    return ret;
}


/**
 * Function swapEnds
 * will swap ends of any POINT_PAIR in the POINT_PAIRS list in order to
 * make the consecutive POINT_PAIRs be "connected" at their ends.
 */
static void swapEnds( POINT_PAIRS& aList )
{
    POINT   temp;
    
    if( aList.size() <= 1 )
        return;
    
    for( unsigned i=0;  i<aList.size();  ++i )
    {
        if( aList[i].p1 == aList[i+1].p1 )
            swap( aList[i] ); 

        else if( aList[i].p1 == aList[i+1].p2 )
        {
            swap( aList[i] );
            swap( aList[i+1] );
            ++i;    // skip next one, we swapped i+1 here
        }
    }
}


/**
 * Function isRectangle
 * tests to see if the POINT_PAIRS list makes up a vertically/horizontally 
 * oriented rectangle.
 * @return bool - true if there are 4 point pairs making a rectangle.
 */ 
static bool isRectangle( POINT_PAIRS& aList )
{
    if( aList.size() == 4 )
    {
        for( unsigned i=0;  i<aList.size();  ++i )
        {
            if( i < aList.size()-1 )
                if( aList[i].p2 != aList[i+1].p1 )
                    return false;
            
            if( aList[i].p1.x != aList[i].p2.x 
            &&  aList[i].p1.y != aList[i].p2.y )
                return false;
        }
        
        return ( aList[0].p1 == aList[3].p2 );
    }
    return false;
}


/**************************************************************************/
static int Pad_list_Sort_by_Shapes( const void* refptr, const void* objptr )
/**************************************************************************/
{
    const D_PAD* padref = *(D_PAD**)refptr;
    const D_PAD* padcmp = *(D_PAD**)objptr;

    return D_PAD::Compare( padref, padcmp );     
}


/**
 * Function makePath
 * creates a PATH element with a single straight line, a pair of vertices.
 */
static PATH* makePath( const POINT& aStart, const POINT& aEnd, const std::string& aLayerName )
{
    PATH*   path = new PATH( 0, T_path );

    path->AppendPoint( aStart );
    path->AppendPoint( aEnd );
    path->SetLayerId( aLayerName.c_str() );
    return path;
}


static QARC* makeArc( const POINT& aStart, const POINT& aEnd, 
                     const POINT& aCenter, const std::string& aLayerName ) 
{
    QARC*   qarc = new QARC(0);
    
    qarc->SetStart( aStart );
    qarc->SetEnd( aEnd );
    qarc->SetCenter( aCenter );
    qarc->SetLayerId( aLayerName.c_str() );
    return qarc;
}


IMAGE* SPECCTRA_DB::makeIMAGE( MODULE* aModule )
{
    PADSTACKS&  padstacks = pcb->library->padstacks;

    TYPE_COLLECTOR  pads;
    static const KICAD_T scanPADs[] = { TYPEPAD, EOT };
    
    // get all the MODULE's pads.        
    pads.Collect( aModule, scanPADs );

    IMAGE*  image = new IMAGE(0);
    
    image->image_id = CONV_TO_UTF8( aModule->m_LibRef );
        
    // from the pads, and make an IMAGE using collated padstacks.
    for( int p=0;  p<pads.GetCount();  ++p )
    {
        D_PAD* pad = (D_PAD*) pads[p];

        // see if this pad is a through hole with no copper on its perimeter
        if( !pad->IsOnLayer( LAYER_CMP_N ) && !pad->IsOnLayer( COPPER_LAYER_N ) )
        {
            if( pad->m_Drill.x!=0 )
            {
                KEEPOUT* keepout = new KEEPOUT(image, T_keepout);
                image->keepouts.push_back( keepout );
                
                WINDOW* window = new WINDOW(keepout);
                keepout->windows.push_back( window );
                
                CIRCLE* circle = new CIRCLE(window);
                window->SetShape( circle );
                
                circle->SetDiameter( scale(pad->m_Drill.x) );
                circle->SetVertex( POINT( mapPt( pad->m_Pos0 ) ) );
                circle->layer_id = "signal";
                
                // ?? the keepout is not affecting the power layers? 
            }
        }
        else
        {
            PADSTACK*   padstack = &padstacks[pad->m_logical_connexion];
            
            PIN*    pin = new PIN(image);
            image->pins.push_back( pin );
            
            pin->padstack_id = padstack->padstack_id;
            pin->pin_id      = CONV_TO_UTF8( pad->ReturnStringPadName() );
            pin->SetVertex( mapPt( pad->m_Pos0 ) );
        }
    }
    
    return image;
}    


void SPECCTRA_DB::makePADSTACKs( BOARD* aBoard, TYPE_COLLECTOR& aPads )
{
    char    name[80];       // padstack name builder
    
    if( aPads.GetCount() )
    {
        qsort( (void*) aPads.BasePtr(), aPads.GetCount(), sizeof(D_PAD*), Pad_list_Sort_by_Shapes );
    }

    D_PAD*  old_pad = NULL;

#define COPPER_LAYERS    2      // top and bottom

    int reportedLayers = COPPER_LAYERS;     // how many layers are reported.

    
    // for now, report on only the top and bottom layers with respect to the copper
    // within a pad's padstack.  this is usually correct, but not rigorous.  We could do
    // better if there was actually a "layer type" field within Kicad which would
    // hold one of:  T_signal, T_power, T_mixed, T_jumper
    // See bottom of page 74 of the SECCTRA Design Language Reference, May 2000.
    
    std::string layerId[COPPER_LAYERS] = {
        CONV_TO_UTF8(aBoard->GetLayerName( LAYER_CMP_N )),
        CONV_TO_UTF8(aBoard->GetLayerName( COPPER_LAYER_N )), 
    };
    
#if 1
// Late breaking news: we can use the reserved layer name "signal" and report the 
    // padstack as a single layer. See <reserved_layer_name> in the spec. 
    // But this probably gives problems for a "power" layer or power pin, we'll see.
    reportedLayers = 1;
    layerId[0] = "signal";
#endif
    
    for( int i=0;  i<aPads.GetCount();  ++i )
    {
        D_PAD*  pad = (D_PAD*) aPads[i];

        bool doLayer[COPPER_LAYERS] =  { 
            pad->IsOnLayer( LAYER_CMP_N ),
            pad->IsOnLayer( COPPER_LAYER_N ) 
        };
        
        if( old_pad && 0==D_PAD::Compare( old_pad, pad ) )
        {
            // padstacks.size()-1 is the index of the matching padstack in LIBRARY::padstacks
            pad->m_logical_connexion = pcb->library->padstacks.size()-1;
            
            // this is the same as the last pad, so do not add it to the padstack list.
            continue;
        }

        // if pad has no copper presence, then it will be made into
        // an "image->keepout" later.  No copper pad here, it is probably a hole.        
        if( !doLayer[0] && !doLayer[1] )
        {
            // padstacks.size()-1 is the index of the matching padstack in LIBRARY::padstacks
            pad->m_logical_connexion = pcb->library->padstacks.size()-1;
            
            continue;
        }

#if 1   // late breaking news..... see above        
        doLayer[0] = true;
#endif        
        
        old_pad = pad;

        PADSTACK*   padstack = new PADSTACK( pcb->library );
        pcb->library->AddPadstack( padstack );
        
        // padstacks.size()-1 is the index of the matching padstack in LIBRARY::padstacks
        pad->m_logical_connexion = pcb->library->padstacks.size()-1;
        
        // paddOfset is the offset of copper shape relative to hole position, 
        // and pad->m_Pos is hole position. All shapes must be shifted by
        // this distance, normally (0,0).
        
        // Note that the y correction here is set negative.
        POINT       padOffset( scale(pad->m_Offset.x), -scale(pad->m_Offset.y) );

        int         coppers = 0;
        
        switch( pad->m_PadShape )
        {
        default:
        case PAD_CIRCLE:
            {
                double  diameter = scale(pad->m_Size.x);
                
                for( int layer=0;  layer<reportedLayers;  ++layer )
                {
                    if( doLayer[layer] )
                    {
                        SHAPE*      shape = new SHAPE( padstack );
                        padstack->Append( shape );
    
                        CIRCLE*     circle = new CIRCLE( shape );
                        shape->SetShape( circle );
                        
                        circle->SetLayerId( layerId[layer].c_str() );
                        circle->SetDiameter( diameter );
                        circle->SetVertex( padOffset );
                        ++coppers;
                    }
                }
                
                snprintf( name, sizeof(name), "Round%dPad_%.6g_mil", coppers, scale(pad->m_Size.x) );
                name[ sizeof(name)-1 ] = 0;

                // @todo verify that all pad names are unique, there is a chance that 
                // D_PAD::Compare() could say two pads are different, yet the get the same
                // name here. If so, blend in the padNdx into the name.
                
                padstack->SetPadstackId( name );
            }
            break;

        case PAD_RECT:
            {
                double dx = scale( pad->m_Size.x ) / 2.0;
                double dy = scale( pad->m_Size.y ) / 2.0;
                
                POINT   lowerLeft( -dx, -dy );
                POINT   upperRight( dx, dy );

                lowerLeft  += padOffset;
                upperRight += padOffset;

                for( int layer=0;  layer<reportedLayers;  ++layer )
                {
                    if( doLayer[layer] )
                    {
                        SHAPE*      shape = new SHAPE( padstack );
                        padstack->Append( shape );
                        
                        RECTANGLE*  rect = new RECTANGLE( shape );
                        shape->SetShape( rect );
                        
                        rect->SetLayerId( layerId[layer].c_str() );
                        rect->SetCorners( lowerLeft, upperRight );
                        ++coppers;
                    }
                }
                
                snprintf( name, sizeof(name),  "Rect%dPad_%.6gx%.6g_mil", 
                         coppers, scale(pad->m_Size.x), scale(pad->m_Size.y)  );
                name[ sizeof(name)-1 ] = 0;

                // @todo verify that all pad names are unique, there is a chance that 
                // D_PAD::Compare() could say two pads are different, yet they get the same
                // name here. If so, blend in the padNdx into the name.
                
                padstack->SetPadstackId( name );
            }
            break;
            
        case PAD_OVAL:
            {
                double dx = scale( pad->m_Size.x ) / 2.0;
                double dy = scale( pad->m_Size.y ) / 2.0;
                double dr = dx - dy;

                if( dr >= 0 )       // oval is horizontal
                {
                    double  radius = dy;

                    for( int layer=0;  layer<reportedLayers;  ++layer )
                    {
                        if( doLayer[layer] )
                        {
                            // each oval is 2 lines and 4 (quarter circle) qarcs
        
                            SHAPE*  shape;
                            PATH*   path;
                            QARC*   qarc;
                            
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            path = makePath( 
                                    POINT( -dr + padOffset.x, padOffset.y - radius ),     // aStart
                                    POINT(  dr + padOffset.x, padOffset.y - radius ),     // aEnd
                                    layerId[layer] );
                            shape->SetShape( path );
                            
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            // @todo: this 1/2 circle arc needs to be split into two quarter circle arcs 
                            qarc = makeArc( 
                                    POINT(  dr + padOffset.x, padOffset.y - radius),      // aStart
                                    POINT(  dr + padOffset.x, padOffset.y + radius),      // aEnd
                                    POINT(  dr + padOffset.x, padOffset.y ),              // aCenter
                                    layerId[layer] );
                            shape->SetShape( qarc );
    
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            path = makePath( 
                                    POINT(  dr + padOffset.x, padOffset.y + radius ),     // aStart
                                    POINT( -dr + padOffset.x, padOffset.y + radius ),     // aEnd
                                    layerId[layer] );
                            shape->SetShape( path );
    
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            // @todo: this 1/2 circle arc needs to be split into two quarter circle arcs 
                            qarc = makeArc( 
                                    POINT( -dr + padOffset.x, padOffset.y + radius),      // aStart
                                    POINT( -dr + padOffset.x, padOffset.y - radius),      // aEnd
                                    POINT( -dr + padOffset.x, padOffset.y ),              // aCenter
                                    layerId[layer] );
                            shape->SetShape( qarc );
                            ++coppers;
                        }
                    }
                }
                else        // oval is vertical
                {
                    double  radius = dx;
                    
                    dr = -dr;

                    for( int layer=0;  layer<reportedLayers;  ++layer )
                    {
                        if( doLayer[layer] )
                        {
                            // each oval is 2 lines and 2 qarcs
        
                            SHAPE*  shape;
                            PATH*   path;
                            QARC*   qarc;
                            
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            path = makePath( 
                                    POINT( -radius + padOffset.x, padOffset.y - dr ),   // aStart
                                    POINT( -radius + padOffset.x, padOffset.y + dr ),   // aEnd
                                    layerId[layer] );
                            shape->SetShape( path );
    
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            // @todo: this 1/2 circle arc needs to be split into two quarter circle arcs 
                            qarc = makeArc( 
                                    POINT( -radius + padOffset.x, padOffset.y + dr ),   // aStart
                                    POINT(  radius + padOffset.x, padOffset.y + dr),    // aEnd
                                    POINT(  padOffset.x, padOffset.y +dr ),             // aCenter
                                    layerId[layer] );
                            shape->SetShape( qarc );
    
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            path = makePath( 
                                    POINT(  radius + padOffset.x, padOffset.y + dr ),   // aStart
                                    POINT(  radius + padOffset.x, padOffset.y - dr ),   // aEnd
                                    layerId[layer] );
                            shape->SetShape( path );
    
                            shape = new SHAPE( padstack );
                            padstack->Append( shape );
                            // @todo: this 1/2 circle arc needs to be split into two quarter circle arcs 
                            qarc = makeArc( 
                                    POINT(  radius + padOffset.x, padOffset.y - dr),    // aStart
                                    POINT( -radius + padOffset.x, padOffset.y - dr),    // aEnd
                                    POINT( padOffset.x, padOffset.y - dr ),             // aCenter
                                    layerId[layer] );
                            shape->SetShape( qarc );
                            ++coppers;
                        }
                    }
                }
                
                snprintf( name, sizeof(name),  "Oval%dPad_%.6gx%.6g_mil", 
                         coppers, scale(pad->m_Size.x), scale(pad->m_Size.y)  );
                name[ sizeof(name)-1 ] = 0;
                
                // @todo verify that all pad names are unique, there is a chance that 
                // D_PAD::Compare() could say two pads are different, yet they get the same
                // name here. If so, blend in the padNdx into the name.
                
                padstack->SetPadstackId( name );
            }
            break;

/*            
        case PAD_TRAPEZOID:
            break;
*/            
        }
    }

    // unique pads are now in the padstack list.  
    // next we add the via's which may be used.

    int defaultViaSize = aBoard->m_BoardSettings->m_CurrentViaSize;
    if( defaultViaSize )
    {
        PADSTACK*   padstack = new PADSTACK( pcb->library );
        pcb->library->AddPadstack( padstack );

        // remember this index, it is the default via and also the start of the 
        // vias within the padstack list.  Before this index are the pads.
        // At this index and later are the vias.
        pcb->library->SetViaStartIndex( pcb->library->padstacks.size()-1 );
        
        SHAPE*      shape = new SHAPE( padstack );
        padstack->Append( shape );

        CIRCLE*     circle = new CIRCLE( shape );
        shape->SetShape( circle );
        circle->SetLayerId( layerId[0].c_str() ); 
        circle->SetDiameter( scale(defaultViaSize) );

        padstack->SetPadstackId( "Via_Default" );
    }

    for( int i=0;  i<HISTORY_NUMBER;  ++i )
    {
        int viaSize = aBoard->m_BoardSettings->m_ViaSizeHistory[i]; 
        if( !viaSize )
            break;
        
        if( viaSize == defaultViaSize )
            continue;
        
        PADSTACK*   padstack = new PADSTACK( pcb->library );
        pcb->library->AddPadstack( padstack );
        
        SHAPE*      shape = new SHAPE( padstack );
        padstack->Append( shape );

        CIRCLE*     circle = new CIRCLE( shape );
        shape->SetShape( circle );
        circle->SetLayerId( layerId[0].c_str() ); 
        circle->SetDiameter( scale(viaSize) );

        snprintf( name, sizeof(name),  "Via_%.6g_mil", scale(viaSize) ); 
        name[ sizeof(name)-1 ] = 0;
        
        padstack->SetPadstackId( name );
    }
}


void SPECCTRA_DB::FromBOARD( BOARD* aBoard )
{
    TYPE_COLLECTOR          items;
    POINT_PAIRS             ppairs;
    POINT_PAIR              pair;

    if( !pcb )
        pcb = SPECCTRA_DB::MakePCB();

    //  DSN Images (=Kicad MODULES and pads) must be presented from the
    //  top view.  So we temporarily flip any modules which are on the back
    //  side of the board to the front, and record this in the MODULE's flag field.
    for( MODULE* module = aBoard->m_Modules;  module;  module = module->Next() )
    {
        module->flag = 0;
        if( module->GetLayer() == COPPER_LAYER_N )
        {
            aBoard->Change_Side_Module( module, NULL );
            module->flag = 1;
        }
    }
    
    // a space in a quoted token is NOT a terminator, true establishes this.
    pcb->parser->space_in_quoted_tokens = true;
    
    //-----<unit_descriptor> & <resolution_descriptor>--------------------
    {    
        pcb->unit->units = T_mil;
        pcb->resolution->units = T_mil;
        pcb->resolution->value = 100;
    }

    
    //-----<boundary_descriptor>------------------------------------------
    {
        // get all the DRAWSEGMENTS into 'items', then look for layer == EDGE_N,
        // and those segments comprise the board's perimeter.
        static const KICAD_T  scanDRAWSEGMENTS[] = { TYPEDRAWSEGMENT, EOT };
        items.Collect( aBoard, scanDRAWSEGMENTS );
    
        bool haveEdges = false;
        ppairs.clear();
        for( int i=0;  i<items.GetCount();  ++i )
        {
            DRAWSEGMENT* item = (DRAWSEGMENT*) items[i];
            
            wxASSERT( item->Type() == TYPEDRAWSEGMENT );
            
            if( item->GetLayer() == EDGE_N )
            {
                pair.p1 = mapPt( item->m_Start );
                pair.p2 = mapPt( item->m_End );
                pair.item = item;
                ppairs.push_back( pair );
                haveEdges = true;
            }
        }
    
        if( haveEdges )
        {
            swapEnds( ppairs );
    
#if 0 && defined(DEBUG)        
            for( unsigned i=0;  i<ppairs.size();  ++i )
            {
                POINT_PAIR* p = &ppairs[i];
                p->item->Show( 0, std::cout );
            }
#endif        
    
            BOUNDARY*   boundary = new BOUNDARY(0);
    
            if( isRectangle( ppairs ) )
            {
                RECTANGLE*  rect = new RECTANGLE( boundary );
                rect->layer_id = "pcb";
                
                // opposite corners
                rect->SetCorners( ppairs[0].p1, ppairs[2].p1 );
                
                boundary->rectangle = rect;
            }
            else
            {
                PATH*  path = new PATH( boundary );
                
                path->layer_id = "pcb";
                for( unsigned i=0; i<ppairs.size();  ++i )
                {
                    // unless its a closed polygon, this probably won't work,
                    // otherwise it will.
                    path->points.push_back( ppairs[i].p1 );
                }
                
                boundary->paths.push_back( path );
            }
    
            pcb->structure->SetBOUNDARY( boundary );
        }
        else
        {
            aBoard->ComputeBoundaryBox();
            
            BOUNDARY*   boundary = new BOUNDARY(0);
            RECTANGLE*  rect = new RECTANGLE( boundary );
    
            rect->layer_id = "pcb";
            
            // opposite corners
            wxPoint bottomRight;
            bottomRight.x = aBoard->m_BoundaryBox.GetRight();
            bottomRight.y = aBoard->m_BoundaryBox.GetBottom();
            
            rect->SetCorners( mapPt( aBoard->m_BoundaryBox.GetOrigin() ),
                              mapPt( bottomRight ) );
            
            boundary->rectangle = rect;
            
            pcb->structure->SetBOUNDARY( boundary );
        }
    }

    
    //-----<layer_descriptor>-----------------------------------------------
    {
        // specctra wants top physical layer first, then going down to the 
        // bottom most physical layer in physical sequence.
        // @question : why does Kicad not display layers in that order?
        int layerCount = aBoard->GetCopperLayerCount();
        
        for( int ndx=layerCount-1;  ndx >= 0;  --ndx )
        {
            wxString    layerName = aBoard->GetLayerName( ndx>0 && ndx==layerCount-1 ? LAYER_CMP_N : ndx );
            LAYER*      layer = new LAYER( pcb->structure );
            
            layer->name = CONV_TO_UTF8( layerName );
            
            // layer->type =  @todo need this, the export would be better.
            
            pcb->structure->layers.push_back( layer );
        }
    }

    
    //-----<zone containers become planes>--------------------------------------------
    {
        static const KICAD_T  scanZONEs[] = { TYPEZONE_CONTAINER, EOT };
        items.Collect( aBoard, scanZONEs );

        for( int i=0;  i<items.GetCount();  ++i )
        {
            ZONE_CONTAINER* item = (ZONE_CONTAINER*) items[i];

            wxString        layerName = aBoard->GetLayerName( item->GetLayer() );
            COPPER_PLANE*   plane = new COPPER_PLANE( pcb->structure );
            PATH*           polygon = new PATH( plane, T_polygon );

            plane->path = polygon;
            plane->name = CONV_TO_UTF8( item->m_Netname );
            
            polygon->layer_id = CONV_TO_UTF8( layerName );
            
            int count = item->m_Poly->corner.size();
            for( int j=0; j<count; ++j )
            {
                wxPoint   point( item->m_Poly->corner[j].x, item->m_Poly->corner[j].y );

                polygon->points.push_back( mapPt(point) );
            }
            
            pcb->structure->planes.push_back( plane );
        }
    }

    // keepouts could go here, there are none in Kicad at this time.
    // although COPPER_PLANEs probably will need them for the thru holes, etc.
    // but in that case they are WINDOWs within the COPPER_PLANEs.

    
    //-----<build the initial padstack list>--------------------------------
    {
        TYPE_COLLECTOR  pads;
        static const KICAD_T scanPADs[] = { TYPEPAD, EOT };
    
        // get all the D_PADs into 'pads'.        
        pads.Collect( aBoard, scanPADs );

        makePADSTACKs( aBoard, pads );
        
#if 0 && defined(DEBUG)
        for( int p=0;  p<pads.GetCount();  ++p )
            pads[p]->Show( 0, std::cout );
#endif    
    }

    
    //-----<build the images and components>---------------------------------
    {
        static const KICAD_T scanMODULEs[] = { TYPEMODULE, EOT };
        items.Collect( aBoard, scanMODULEs );
        
        for( int m=0;  m<items.GetCount();  ++m )
        {
            MODULE* module = (MODULE*) items[m];
            
            IMAGE*  image  = makeIMAGE( module );

            IMAGE*  registered = pcb->library->LookupIMAGE( image );  
            if( registered != image )
            {
                // If our new 'image' is not a unique IMAGE, delete it.
                // In either case, 'registered' is the one we'll work with henceforth.
                delete image;
            }

            const std::string& imageId = registered->image_id;
            
            COMPONENT* comp = pcb->placement->LookupCOMPONENT( imageId );
            
            PLACE* place = new PLACE( comp );
            comp->places.push_back( place );
            
            place->SetRotation( module->m_Orient/10.0 );
            place->SetVertex( mapPt( module->m_Pos ) );
            place->component_id = CONV_TO_UTF8( module->GetReference() );
            place->part_number  = CONV_TO_UTF8( module->GetValue() ); 
            
            // module is flipped from bottom side, set side to T_back
            if( module->flag )      
                place->side = T_back;
        }
    }
    
    
    //-----<via_descriptor>-------------------------------------------------
    {
        // Output the vias in the padstack list here, by name
        VIA*        vias = pcb->structure->via;
        PADSTACKS&  padstacks = pcb->library->padstacks;
        int         viaNdx = pcb->library->via_start_index;

        if( viaNdx != -1 )
        {
            for(  ; viaNdx < (int)padstacks.size();  ++viaNdx )
            {
                vias->AppendVia( padstacks[viaNdx].padstack_id.c_str() );
            }
        }
    }

    
    //-----<create the nets>------------------------------------------------
    {
        NETWORK*    network = pcb->network;
        static const KICAD_T scanNETs[] = { PCB_EQUIPOT_STRUCT_TYPE, EOT };

        items.Collect( aBoard, scanNETs );
        
        PIN_REF emptypin(0);
        
        for( int i=0;  i<items.GetCount();  ++i )
        {
            EQUIPOT*   kinet = (EQUIPOT*)items[i];

            NET* net = new NET( network );
            network->nets.push_back( net );
            
            net->net_id = CONV_TO_UTF8( kinet->m_Netname );
            net->net_number = kinet->GetNet();
            
            D_PAD**  ppad = kinet->m_PadzoneStart;
            for(   ; ppad < kinet->m_PadzoneEnd;   ++ppad )
            {
                D_PAD* pad = *ppad;

                wxASSERT( pad->Type() == TYPEPAD );
                
                // push on an empty one, then fill it via 'pin_ref'
                net->pins.push_back( emptypin );
                PIN_REF* pin_ref = &net->pins.back();
                
                pin_ref->SetParent( net );
                pin_ref->component_id = CONV_TO_UTF8( ((MODULE*)pad->m_Parent)->GetReference() );;
                pin_ref->pin_id = CONV_TO_UTF8( pad->ReturnStringPadName() );               
            }
        }
    }
    
    
    //-----<restore MODULEs>------------------------------------------------
    
    //  DSN Images (=Kicad MODULES and pads) must be presented from the
    //  top view.  Restore those that were flipped.
    for( MODULE* module = aBoard->m_Modules;  module;  module = module->Next() )
    {
        if( module->flag )
        {
            aBoard->Change_Side_Module( module, NULL );
            module->flag = 0;
        }
    }
    
}

    
}       // namespace DSN

