// Sort vias for uniqueness
static int ViaSort( const void* aRefptr, const void* aObjptr )
{
    VIA* padref = *(VIA**) aRefptr;
    VIA* padcmp = *(VIA**) aObjptr;

    if( padref->GetWidth() != padcmp->GetWidth() )
        return padref->GetWidth() - padcmp->GetWidth();

    if( padref->GetDrillValue() != padcmp->GetDrillValue() )
        return padref->GetDrillValue() - padcmp->GetDrillValue();

    if( padref->GetLayerSet() != padcmp->GetLayerSet() )
        return padref->GetLayerSet().FmtBin().compare( padcmp->GetLayerSet().FmtBin() );

    return 0;
}
/* Extract the D356 record from the vias */
static void build_via_testpoints( BOARD *aPcb,
    std::vector <D356_RECORD>& aRecords )
{
    wxPoint origin = aPcb->GetAuxOrigin();

    // Enumerate all the track segments and keep the vias
    for( TRACK *track = aPcb->m_Track; track; track = track->Next() )
    {
        if( track->Type() == PCB_VIA_T )
        {
            VIA *via = (VIA*) track;
            NETINFO_ITEM *net = track->GetNet();

            D356_RECORD rk;
            rk.smd = false;
            rk.hole = true;
            if( net )
                rk.netname = net->GetNetname();
            else
                rk.netname = wxEmptyString;
            rk.refdes = wxT("VIA");
            rk.pin = wxT("");
            rk.midpoint = true; // Vias are always midpoints
            rk.drill = via->GetDrillValue();
            rk.mechanical = false;

            LAYER_ID top_layer, bottom_layer;

            via->LayerPair( &top_layer, &bottom_layer );

            rk.access = via_access_code( aPcb, top_layer, bottom_layer );
            rk.x_location = via->GetPosition().x - origin.x;
            rk.y_location = origin.y - via->GetPosition().y;
            rk.x_size = via->GetWidth();
            rk.y_size = 0; // Round so height = 0
            rk.rotation = 0;
            rk.soldermask = 3; // XXX always tented?

            aRecords.push_back( rk );
        }
    }
}
void EXCELLON_WRITER::BuildHolesList( LAYER_PAIR aLayerPair,
                                      bool aGenerateNPTH_list )
{
    HOLE_INFO new_hole;

    m_holeListBuffer.clear();
    m_toolListBuffer.clear();

    wxASSERT(  aLayerPair.first < aLayerPair.second );  // fix the caller

    // build hole list for vias
    if( ! aGenerateNPTH_list )  // vias are always plated !
    {
        for( VIA* via = GetFirstVia( m_pcb->m_Track ); via; via = GetFirstVia( via->Next() ) )
        {
            int hole_sz = via->GetDrillValue();

            if( hole_sz == 0 )   // Should not occur.
                continue;

            new_hole.m_Tool_Reference = -1;         // Flag value for Not initialized
            new_hole.m_Hole_Orient    = 0;
            new_hole.m_Hole_Diameter  = hole_sz;
            new_hole.m_Hole_NotPlated = false;
            new_hole.m_Hole_Size.x = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;

            new_hole.m_Hole_Shape = 0;              // hole shape: round
            new_hole.m_Hole_Pos = via->GetStart();

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

            // LayerPair() returns params with m_Hole_Bottom_Layer > m_Hole_Top_Layer
            // Remember: top layer = 0 and bottom layer = 31 for through hole vias
            // Any captured via should be from aLayerPair.first to aLayerPair.second exactly.
            if( new_hole.m_Hole_Top_Layer    != aLayerPair.first ||
                new_hole.m_Hole_Bottom_Layer != aLayerPair.second )
                continue;

            m_holeListBuffer.push_back( new_hole );
        }
    }

    if( aLayerPair == LAYER_PAIR( F_Cu, B_Cu ) )
    {
        // add holes for thru hole pads
        for( MODULE* module = m_pcb->m_Modules;  module;  module = module->Next() )
        {
            for( D_PAD* pad = module->Pads();  pad;  pad = pad->Next() )
            {
                if( !m_merge_PTH_NPTH )
                {
                    if( !aGenerateNPTH_list && pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED )
                        continue;

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

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

                new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED);
                new_hole.m_Tool_Reference = -1;         // Flag is: Not initialized
                new_hole.m_Hole_Orient    = pad->GetOrientation();
                new_hole.m_Hole_Shape     = 0;           // hole shape: round
                new_hole.m_Hole_Diameter  = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
                new_hole.m_Hole_Size.x    = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;

                if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
                    new_hole.m_Hole_Shape = 1; // oval flag set

                new_hole.m_Hole_Size         = pad->GetDrillSize();
                new_hole.m_Hole_Pos          = pad->GetPosition();  // hole position
                new_hole.m_Hole_Bottom_Layer = B_Cu;
                new_hole.m_Hole_Top_Layer    = F_Cu;    // pad holes are through holes
                m_holeListBuffer.push_back( new_hole );
            }
        }
    }

    // Sort holes per increasing diameter value
    sort( m_holeListBuffer.begin(), m_holeListBuffer.end(), CmpHoleSettings );

    // build the tool list
    int last_hole = -1;     // Set to not initialized (this is a value not used
                            // for m_holeListBuffer[ii].m_Hole_Diameter)
    bool last_notplated_opt = false;

    DRILL_TOOL new_tool( 0, false );
    unsigned   jj;

    for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
    {
        if( m_holeListBuffer[ii].m_Hole_Diameter != last_hole ||
            m_holeListBuffer[ii].m_Hole_NotPlated != last_notplated_opt )
        {
            new_tool.m_Diameter = m_holeListBuffer[ii].m_Hole_Diameter;
            new_tool.m_Hole_NotPlated = m_holeListBuffer[ii].m_Hole_NotPlated;
            m_toolListBuffer.push_back( new_tool );
            last_hole = new_tool.m_Diameter;
            last_notplated_opt = new_tool.m_Hole_NotPlated;
        }

        jj = m_toolListBuffer.size();

        if( jj == 0 )
            continue;                                        // Should not occurs

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

        m_toolListBuffer.back().m_TotalCount++;

        if( m_holeListBuffer[ii].m_Hole_Shape )
            m_toolListBuffer.back().m_OvalCount++;
    }
}
void EXCELLON_WRITER::BuildHolesList( int aFirstLayer,
                                      int aLastLayer,
                                      bool aExcludeThroughHoles,
                                      bool aGenerateNPTH_list,
                                      bool aMerge_PTH_NPTH )
{
    HOLE_INFO new_hole;
    int       hole_value;

    m_holeListBuffer.clear();
    m_toolListBuffer.clear();

    if( (aFirstLayer >= 0) && (aLastLayer >= 0) )
    {
        if( aFirstLayer > aLastLayer )
            std::swap( aFirstLayer, aLastLayer );
    }

    if ( aGenerateNPTH_list && aMerge_PTH_NPTH )
    {
        return;
    }

    // build hole list for vias
    if( ! aGenerateNPTH_list )  // vias are always plated !
    {
        for( VIA* via = GetFirstVia( m_pcb->m_Track ); via; via = GetFirstVia( via->Next() ) )
        {
            hole_value = via->GetDrillValue();

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

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

            new_hole.m_Hole_Shape = 0;              // hole shape: round
            new_hole.m_Hole_Pos = via->GetStart();

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

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

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

            if( aExcludeThroughHoles && (new_hole.m_Hole_Bottom_Layer == B_Cu)
               && (new_hole.m_Hole_Top_Layer == F_Cu) )
                continue;

            m_holeListBuffer.push_back( new_hole );
        }
    }

    // build hole list for pads (assumed always through holes)
    if( !aExcludeThroughHoles || aGenerateNPTH_list )
    {
        for( MODULE* module = m_pcb->m_Modules;  module;  module = module->Next() )
        {
            // Read and analyse pads
            for( D_PAD* pad = module->Pads();  pad;  pad = pad->Next() )
            {
                if( ! aGenerateNPTH_list &&
                    pad->GetAttribute() == PAD_HOLE_NOT_PLATED &&
                    ! aMerge_PTH_NPTH )
                    continue;

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

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

                new_hole.m_Hole_NotPlated = (pad->GetAttribute() == PAD_HOLE_NOT_PLATED);
                new_hole.m_Tool_Reference = -1;         // Flag is: Not initialized
                new_hole.m_Hole_Orient    = pad->GetOrientation();
                new_hole.m_Hole_Shape     = 0;           // hole shape: round
                new_hole.m_Hole_Diameter  = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
                new_hole.m_Hole_Size.x    = new_hole.m_Hole_Size.y = new_hole.m_Hole_Diameter;

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

                new_hole.m_Hole_Size         = pad->GetDrillSize();
                new_hole.m_Hole_Pos          = pad->GetPosition();               // hole position
                new_hole.m_Hole_Bottom_Layer = B_Cu;
                new_hole.m_Hole_Top_Layer    = F_Cu;// pad holes are through holes
                m_holeListBuffer.push_back( new_hole );
            }
        }
    }

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

    // build the tool list
    int        LastHole = -1; /* Set to not initialized (this is a value not used
                               * for m_holeListBuffer[ii].m_Hole_Diameter) */
    DRILL_TOOL new_tool( 0 );
    unsigned   jj;

    for( unsigned ii = 0; ii < m_holeListBuffer.size(); ii++ )
    {
        if( m_holeListBuffer[ii].m_Hole_Diameter != LastHole )
        {
            new_tool.m_Diameter = ( m_holeListBuffer[ii].m_Hole_Diameter );
            m_toolListBuffer.push_back( new_tool );
            LastHole = new_tool.m_Diameter;
        }

        jj = m_toolListBuffer.size();

        if( jj == 0 )
            continue;                                        // Should not occurs

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

        m_toolListBuffer.back().m_TotalCount++;

        if( m_holeListBuffer[ii].m_Hole_Shape )
            m_toolListBuffer.back().m_OvalCount++;
    }
}
void EDA_3D_CANVAS::buildBoardThroughHolesPolygonList( SHAPE_POLY_SET& allBoardHoles,
                                                int aSegCountPerCircle, bool aOptimizeLargeCircles )
{
    // hole diameter value to change seg count by circle:
    int small_hole_limit = Millimeter2iu( 1.0 );
    int copper_thickness = GetPrm3DVisu().GetCopperThicknessBIU();

    BOARD* pcb = GetBoard();

    // Build holes of through vias:
    for( TRACK* track = pcb->m_Track;  track;  track = track->Next() )
    {
        if( track->Type() != PCB_VIA_T )
            continue;

        VIA *via = static_cast<VIA*>( track );

        if( via->GetViaType() != VIA_THROUGH )
            continue;

        int holediameter = via->GetDrillValue();
        int hole_outer_radius = (holediameter + copper_thickness) / 2;

        TransformCircleToPolygon( allBoardHoles,
                                  via->GetStart(), hole_outer_radius,
                                  aSegCountPerCircle );
    }

    // Build holes of through pads:
    for( MODULE* footprint = pcb->m_Modules; footprint; footprint = footprint->Next() )
    {
        for( D_PAD* pad = footprint->Pads(); pad; pad = pad->Next() )
        {
            // Calculate a factor to apply to segcount for large holes ( > 1 mm)
            // (bigger pad drill size -> more segments) because holes in pads can have
            // very different sizes and optimizing this segcount gives a better look
            // Mainly mounting holes have a size bigger than small_hole_limit
            wxSize padHole = pad->GetDrillSize();

            if( ! padHole.x )       // Not drilled pad like SMD pad
                continue;

            // we use the hole diameter to calculate the seg count.
            // for round holes, padHole.x == padHole.y
            // for oblong holes, the diameter is the smaller of (padHole.x, padHole.y)
            int diam = std::min( padHole.x, padHole.y );
            int segcount = aSegCountPerCircle;

            if( diam > small_hole_limit )
            {
                double segFactor = (double)diam / small_hole_limit;
                segcount = (int)(aSegCountPerCircle * segFactor);

                // limit segcount to 48. For a circle this is a very good approx.
                if( segcount > 48 )
                    segcount = 48;
            }

            // The hole in the body is inflated by copper thickness.
            int inflate = copper_thickness;

            // If not plated, no copper.
            if( pad->GetAttribute () == PAD_HOLE_NOT_PLATED )
                inflate = 0;

            pad->BuildPadDrillShapePolygon( allBoardHoles, inflate, segcount );
        }
    }

    allBoardHoles.Simplify();
}
void EDA_3D_CANVAS::buildBoard3DView( GLuint aBoardList, GLuint aBodyOnlyList,
                                      REPORTER* aErrorMessages, REPORTER* aActivity  )
{
    BOARD* pcb = GetBoard();

    // If FL_RENDER_SHOW_HOLES_IN_ZONES is true, holes are correctly removed from copper zones areas.
    // If FL_RENDER_SHOW_HOLES_IN_ZONES is false, holes are not removed from copper zones areas,
    // but the calculation time is twice shorter.
    bool remove_Holes = isEnabled( FL_RENDER_SHOW_HOLES_IN_ZONES );

    bool realistic_mode = isRealisticMode();
    bool useTextures = isRealisticMode() && isEnabled( FL_RENDER_TEXTURES );

    // Number of segments to convert a circle to polygon
    // We use 2 values: the first gives a good shape (for instanes rond pads)
    // the second is used to speed up calculations, when a poor approximation is acceptable (holes)
    const int       segcountforcircle   = 18;
    double          correctionFactor    = 1.0 / cos( M_PI / (segcountforcircle * 2.0) );
    const int       segcountLowQuality  = 12;   // segments to draw a circle with low quality
                                                // to reduce time calculations
                                                // for holes and items which do not need
                                                // a fine representation
    double          correctionFactorLQ  = 1.0 / cos( M_PI / (segcountLowQuality * 2.0) );

    SHAPE_POLY_SET  bufferPolys;        // copper areas: tracks, pads and filled zones areas
                                        // when holes are removed from zones
    SHAPE_POLY_SET  bufferPcbOutlines;  // stores the board main outlines
    SHAPE_POLY_SET  bufferZonesPolys;   // copper filled zones areas
                                        // when holes are not removed from zones
    SHAPE_POLY_SET  currLayerHoles;     // Contains holes for the current layer
    SHAPE_POLY_SET  allLayerHoles;      // Contains holes for all layers

    // Build a polygon from edge cut items
    wxString msg;

    if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) )
    {
        if( aErrorMessages )
        {
            msg << wxT("\n") << _("Unable to calculate the board outlines.\n"
                                  "Therefore use the board boundary box.") << wxT("\n\n");

            aErrorMessages->Report( msg, REPORTER::RPT_WARNING );
        }
    }

    // Build board holes, with optimization of large holes shape.
    buildBoardThroughHolesPolygonList( allLayerHoles, segcountLowQuality, true );

    LSET            cu_set = LSET::AllCuMask( GetPrm3DVisu().m_CopperLayersCount );

    glNewList( aBoardList, GL_COMPILE );

    for( LSEQ cu = cu_set.CuStack();  cu;  ++cu )
    {
        LAYER_ID layer = *cu;

        // Skip non enabled layers in normal mode,
        // and internal layers in realistic mode
        if( !is3DLayerEnabled( layer ) )
            continue;

        if( aActivity )
            aActivity->Report( wxString::Format( _( "Build layer %s" ), LSET::Name( layer ) ) );

        bufferPolys.RemoveAllContours();
        bufferZonesPolys.RemoveAllContours();
        currLayerHoles.RemoveAllContours();

        // Draw track shapes:
        for( TRACK* track = pcb->m_Track;  track;  track = track->Next() )
        {
            if( !track->IsOnLayer( layer ) )
                continue;

            track->TransformShapeWithClearanceToPolygon( bufferPolys,
                                                         0, segcountforcircle,
                                                         correctionFactor );

            // Add blind/buried via holes
            if( track->Type() == PCB_VIA_T )
            {
                VIA *via = static_cast<VIA*>( track );

                if( via->GetViaType() == VIA_THROUGH )
                    continue;   // already done

                int holediameter = via->GetDrillValue();
                int thickness = GetPrm3DVisu().GetCopperThicknessBIU();
                int hole_outer_radius = (holediameter + thickness) / 2;

                TransformCircleToPolygon( currLayerHoles,
                                          via->GetStart(), hole_outer_radius,
                                          segcountLowQuality );
            }
        }

        // draw pad shapes
        for( MODULE* module = pcb->m_Modules;  module;  module = module->Next() )
        {
            // Note: NPTH pads are not drawn on copper layers when the pad
            // has same shape as its hole
            module->TransformPadsShapesWithClearanceToPolygon( layer,
                                                               bufferPolys,
                                                               0,
                                                               segcountforcircle,
                                                               correctionFactor, true );

            // Micro-wave modules may have items on copper layers
            module->TransformGraphicShapesWithClearanceToPolygonSet( layer,
                                                                     bufferPolys,
                                                                     0,
                                                                     segcountforcircle,
                                                                     correctionFactor );

            // pad holes are already in list.
        }

        // Draw copper zones. Note:
        // * if the holes are removed from copper zones
        // the polygons are stored in bufferPolys (which contains all other polygons)
        // * if the holes are NOT removed from copper zones
        // the polygons are stored in bufferZonesPolys
        if( isEnabled( FL_ZONE ) )
        {
            for( int ii = 0; ii < pcb->GetAreaCount(); ii++ )
            {
                ZONE_CONTAINER* zone = pcb->GetArea( ii );
                LAYER_NUM       zonelayer = zone->GetLayer();

                if( zonelayer == layer )
                {
                    zone->TransformSolidAreasShapesToPolygonSet(
                        remove_Holes ? bufferPolys : bufferZonesPolys,
                        segcountLowQuality, correctionFactorLQ );
                }
            }
        }

        // draw graphic items on copper layers (texts)
        for( BOARD_ITEM* item = pcb->m_Drawings; item; item = item->Next() )
        {
            if( !item->IsOnLayer( layer ) )
                continue;

            switch( item->Type() )
            {
            case PCB_LINE_T:    // should not exist on copper layers
                ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon(
                    bufferPolys, 0, segcountforcircle, correctionFactor );
                break;

            case PCB_TEXT_T:
                ( (TEXTE_PCB*) item )->TransformShapeWithClearanceToPolygonSet(
                    bufferPolys, 0, segcountLowQuality, correctionFactor );
                break;

            default:
                break;
            }
        }

        // bufferPolys contains polygons to merge. Many overlaps .
        // Calculate merged polygons
        if( bufferPolys.IsEmpty() )
            continue;

        // Use Clipper lib to subtract holes to copper areas
        if( currLayerHoles.OutlineCount() )
        {
            currLayerHoles.Append(allLayerHoles);
            currLayerHoles.Simplify();
            bufferPolys.BooleanSubtract( currLayerHoles );
        }
        else
            bufferPolys.BooleanSubtract( allLayerHoles );

        int thickness = GetPrm3DVisu().GetLayerObjectThicknessBIU( layer );
        int zpos = GetPrm3DVisu().GetLayerZcoordBIU( layer );

        float zNormal = 1.0f; // When using thickness it will draw first the top and then botton (with z inverted)

        // If we are not using thickness, then the z-normal has to match the layer direction
        // because just one plane will be drawn
        if( !thickness )
            zNormal = Get3DLayer_Z_Orientation( layer );

        if( realistic_mode )
        {
            setGLCopperColor();
        }
        else
        {
            EDA_COLOR_T color = g_ColorsSettings.GetLayerColor( layer );
            SetGLColor( color );
        }

        // If holes are removed from copper zones, bufferPolys contains all polygons
        // to draw (tracks+zones+texts).
        Draw3D_SolidHorizontalPolyPolygons( bufferPolys, zpos, thickness,
                                            GetPrm3DVisu().m_BiuTo3Dunits, useTextures,
                                            zNormal );

        // If holes are not removed from copper zones (for calculation time reasons,
        // the zone polygons are stored in bufferZonesPolys and have to be drawn now:
        if( !bufferZonesPolys.IsEmpty() )
        {
            Draw3D_SolidHorizontalPolyPolygons( bufferZonesPolys, zpos, thickness,
                                    GetPrm3DVisu().m_BiuTo3Dunits, useTextures,
                                    zNormal );
        }
    }

    if( aActivity )
        aActivity->Report( _( "Build board body" ) );

    // Draw plated vertical holes inside the board, but not always. They are drawn:
    // - if the board body is not shown, to show the holes.
    // - or if the copper thickness is shown
    if( !isEnabled( FL_SHOW_BOARD_BODY ) || isEnabled( FL_USE_COPPER_THICKNESS ) )
    {
        // Draw vias holes (vertical cylinders)
        for( const TRACK* track = pcb->m_Track;  track;  track = track->Next() )
        {
            if( track->Type() == PCB_VIA_T )
            {
                const VIA *via = static_cast<const VIA*>(track);
                draw3DViaHole( via );
            }
        }

        // Draw pads holes (vertical cylinders)
        for( const MODULE* module = pcb->m_Modules;  module;  module = module->Next() )
        {
            for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() )
                if( pad->GetAttribute () != PAD_HOLE_NOT_PLATED )
                    draw3DPadHole( pad );
        }
    }

    glEndList();

    // Build the body board:
    glNewList( aBodyOnlyList, GL_COMPILE );

    if( isRealisticMode() )
    {
        setGLEpoxyColor( 1.00 );
    }
    else
    {
        EDA_COLOR_T color = g_ColorsSettings.GetLayerColor( Edge_Cuts );
        SetGLColor( color, 0.7 );
    }

    float copper_thickness = GetPrm3DVisu().GetCopperThicknessBIU();

    // a small offset between substrate and external copper layer to avoid artifacts
    // when drawing copper items on board
    float epsilon = Millimeter2iu( 0.01 );
    float zpos = GetPrm3DVisu().GetLayerZcoordBIU( B_Cu );
    float board_thickness = GetPrm3DVisu().GetLayerZcoordBIU( F_Cu )
                        - GetPrm3DVisu().GetLayerZcoordBIU( B_Cu );

    // items on copper layers and having a thickness = copper_thickness
    // are drawn from zpos - copper_thickness/2 to zpos + copper_thickness
    // therefore substrate position is copper_thickness/2 to
    // substrate_height - copper_thickness/2
    zpos += (copper_thickness + epsilon) / 2.0f;
    board_thickness -= copper_thickness + epsilon;

    bufferPcbOutlines.BooleanSubtract( allLayerHoles );

    if( !bufferPcbOutlines.IsEmpty() )
    {
        Draw3D_SolidHorizontalPolyPolygons( bufferPcbOutlines, zpos + board_thickness / 2.0,
                                            board_thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures,
                                            1.0f );
    }

    glEndList();
}
void BOARD_ITEM::SwapData( BOARD_ITEM* aImage )
{
    if( aImage == NULL )
    {
        return;
    }

    EDA_ITEM * pnext = Next();
    EDA_ITEM * pback = Back();

    switch( Type() )
    {
    case PCB_MODULE_T:
    {
        MODULE* tmp = (MODULE*) aImage->Clone();
        ( (MODULE*) aImage )->Copy( (MODULE*) this );
        ( (MODULE*) this )->Copy( tmp );
        delete tmp;
    }
        break;

    case PCB_ZONE_AREA_T:
    {
        ZONE_CONTAINER* tmp = (ZONE_CONTAINER*) aImage->Clone();
        ( (ZONE_CONTAINER*) aImage )->Copy( (ZONE_CONTAINER*) this );
        ( (ZONE_CONTAINER*) this )->Copy( tmp );
        delete tmp;
    }
        break;

    case PCB_LINE_T:
        std::swap( *((DRAWSEGMENT*)this), *((DRAWSEGMENT*)aImage) );
        break;

    case PCB_TRACE_T:
    case PCB_VIA_T:
    {
        TRACK* track = (TRACK*) this;
        TRACK* image = (TRACK*) aImage;

        EXCHG(track->m_Layer, image->m_Layer );

        // swap start, end, width and shape for track and image.
        wxPoint exchp = track->GetStart();
        track->SetStart( image->GetStart() );
        image->SetStart( exchp );
        exchp = track->GetEnd();
        track->SetEnd( image->GetEnd() );
        image->SetEnd( exchp );

        int atmp = track->GetWidth();
        track->SetWidth( image->GetWidth() );
        image->SetWidth( atmp );

        if( Type() == PCB_VIA_T )
        {
            VIA *via = static_cast<VIA*>( this );
            VIA *viaimage = static_cast<VIA*>( aImage );

            VIATYPE_T viatmp = via->GetViaType();
            via->SetViaType( viaimage->GetViaType() );
            viaimage->SetViaType( viatmp );

            int drilltmp = via->GetDrillValue();

            if( via->IsDrillDefault() )
                drilltmp = -1;

            int itmp = viaimage->GetDrillValue();

            if( viaimage->IsDrillDefault() )
                itmp = -1;

            EXCHG(itmp, drilltmp );

            if( drilltmp > 0 )
                via->SetDrill( drilltmp );
            else
                via->SetDrillDefault();

            if( itmp > 0 )
                viaimage->SetDrill( itmp );
            else
                viaimage->SetDrillDefault();
        }
    }
        break;

    case PCB_TEXT_T:
        std::swap( *((TEXTE_PCB*)this), *((TEXTE_PCB*)aImage) );
        break;

    case PCB_TARGET_T:
        std::swap( *((PCB_TARGET*)this), *((PCB_TARGET*)aImage) );
        break;

    case PCB_DIMENSION_T:
        std::swap( *((DIMENSION*)this), *((DIMENSION*)aImage) );
        break;

    case PCB_ZONE_T:
    default:
        wxLogMessage( wxT( "SwapData() error: unexpected type %d" ), Type() );
        break;
    }

    if( pnext != Next() || pback != Back() )
    {
        Pnext = pnext;
        Pback = pback;
#ifdef DEBUG
        wxLogMessage( wxT( "SwapData Error: %s Pnext or Pback pointers modified" ),
                      GetClass().GetData() );
#endif
    }
}
void BOARD_ITEM::SwapData( BOARD_ITEM* aImage )
{
    if( aImage == NULL )
        return;

    // Remark: to create images of edited items to undo, we are using Clone method
    // which can duplication of items foe copy, but does not clone all members
    // mainly pointers in chain and time stamp, which is set to new, unique value.
    // So we have to use the current values of these parameters.

    EDA_ITEM * pnext = Next();
    EDA_ITEM * pback = Back();
    DHEAD* mylist    = m_List;
    time_t timestamp = GetTimeStamp();

    switch( Type() )
    {
    case PCB_MODULE_T:
    {
        MODULE* tmp = (MODULE*) aImage->Clone();
        ( (MODULE*) aImage )->Copy( (MODULE*) this );
        ( (MODULE*) this )->Copy( tmp );
        delete tmp;
    }
        break;

    case PCB_ZONE_AREA_T:
    {
        ZONE_CONTAINER* tmp = (ZONE_CONTAINER*) aImage->Clone();
        ( (ZONE_CONTAINER*) aImage )->Copy( (ZONE_CONTAINER*) this );
        ( (ZONE_CONTAINER*) this )->Copy( tmp );
        delete tmp;
    }
        break;

    case PCB_LINE_T:
        std::swap( *((DRAWSEGMENT*)this), *((DRAWSEGMENT*)aImage) );
        break;

    case PCB_TRACE_T:
    case PCB_VIA_T:
    {
        TRACK* track = (TRACK*) this;
        TRACK* image = (TRACK*) aImage;

        std::swap(track->m_Layer, image->m_Layer );

        // swap start, end, width and shape for track and image.
        wxPoint exchp = track->GetStart();
        track->SetStart( image->GetStart() );
        image->SetStart( exchp );
        exchp = track->GetEnd();
        track->SetEnd( image->GetEnd() );
        image->SetEnd( exchp );

        int atmp = track->GetWidth();
        track->SetWidth( image->GetWidth() );
        image->SetWidth( atmp );

        if( Type() == PCB_VIA_T )
        {
            VIA *via = static_cast<VIA*>( this );
            VIA *viaimage = static_cast<VIA*>( aImage );

            VIATYPE_T viatmp = via->GetViaType();
            via->SetViaType( viaimage->GetViaType() );
            viaimage->SetViaType( viatmp );

            int drilltmp = via->GetDrillValue();

            if( via->IsDrillDefault() )
                drilltmp = -1;

            int itmp = viaimage->GetDrillValue();

            if( viaimage->IsDrillDefault() )
                itmp = -1;

            std::swap(itmp, drilltmp );

            if( drilltmp > 0 )
                via->SetDrill( drilltmp );
            else
                via->SetDrillDefault();

            if( itmp > 0 )
                viaimage->SetDrill( itmp );
            else
                viaimage->SetDrillDefault();
        }
    }
        break;

    case PCB_TEXT_T:
        std::swap( *((TEXTE_PCB*)this), *((TEXTE_PCB*)aImage) );
        break;

    case PCB_TARGET_T:
        std::swap( *((PCB_TARGET*)this), *((PCB_TARGET*)aImage) );
        break;

    case PCB_DIMENSION_T:
        std::swap( *((DIMENSION*)this), *((DIMENSION*)aImage) );
        break;

    case PCB_ZONE_T:
    default:
        wxLogMessage( wxT( "SwapData() error: unexpected type %d" ), Type() );
        break;
    }

    // Restore pointers and time stamp, to be sure they are not broken
    Pnext = pnext;
    Pback = pback;
    m_List = mylist;
    SetTimeStamp( timestamp );
}
// Emit PADS and PADSTACKS. They are sorted and emitted uniquely.
// Via name is synthesized from their attributes, pads are numbered
static void CreatePadsShapesSection( FILE* aFile, BOARD* aPcb )
{
    std::vector<D_PAD*> pads;
    std::vector<D_PAD*> padstacks;
    std::vector<VIA*>   vias;
    std::vector<VIA*>   viastacks;

    padstacks.resize( 1 ); // We count pads from 1

    // The master layermask (i.e. the enabled layers) for padstack generation
    LSET    master_layermask = aPcb->GetDesignSettings().GetEnabledLayers();
    int     cu_count = aPcb->GetCopperLayerCount();

    fputs( "$PADS\n", aFile );

    // Enumerate and sort the pads
    if( aPcb->GetPadCount() > 0 )
    {
        pads = aPcb->GetPads();
        qsort( &pads[0], aPcb->GetPadCount(), sizeof( D_PAD* ),
               PadListSortByShape );
    }

    // The same for vias
    for( VIA* via = GetFirstVia( aPcb->m_Track ); via;
            via = GetFirstVia( via->Next() ) )
    {
        vias.push_back( via );
    }

    qsort( &vias[0], vias.size(), sizeof(VIA*), ViaSort );

    // Emit vias pads
    TRACK* old_via = 0;

    for( unsigned i = 0; i < vias.size(); i++ )
    {
        VIA* via = vias[i];

        if( old_via && 0 == ViaSort( &old_via, &via ) )
            continue;

        old_via = via;
        viastacks.push_back( via );
        fprintf( aFile, "PAD V%d.%d.%s ROUND %g\nCIRCLE 0 0 %g\n",
                via->GetWidth(), via->GetDrillValue(),
                fmt_mask( via->GetLayerSet() ).c_str(),
                via->GetDrillValue() / SCALE_FACTOR,
                via->GetWidth() / (SCALE_FACTOR * 2) );
    }

    // Emit component pads
    D_PAD* old_pad = 0;
    int    pad_name_number = 0;

    for( unsigned i = 0; i<pads.size(); ++i )
    {
        D_PAD* pad = pads[i];

        pad->SetSubRatsnest( pad_name_number );

        if( old_pad && 0==D_PAD::Compare( old_pad, pad ) )
            continue;  // already created

        old_pad = pad;

        pad_name_number++;
        pad->SetSubRatsnest( pad_name_number );

        fprintf( aFile, "PAD P%d", pad->GetSubRatsnest() );

        padstacks.push_back( pad ); // Will have its own padstack later
        int dx = pad->GetSize().x / 2;
        int dy = pad->GetSize().y / 2;

        switch( pad->GetShape() )
        {
        default:
        case PAD_SHAPE_CIRCLE:
            fprintf( aFile, " ROUND %g\n",
                     pad->GetDrillSize().x / SCALE_FACTOR );
            /* Circle is center, radius */
            fprintf( aFile, "CIRCLE %g %g %g\n",
                    pad->GetOffset().x / SCALE_FACTOR,
                    -pad->GetOffset().y / SCALE_FACTOR,
                    pad->GetSize().x / (SCALE_FACTOR * 2) );
            break;

        case PAD_SHAPE_RECT:
            fprintf( aFile, " RECTANGULAR %g\n",
                     pad->GetDrillSize().x / SCALE_FACTOR );

            // Rectangle is begin, size *not* begin, end!
            fprintf( aFile, "RECTANGLE %g %g %g %g\n",
                    (-dx + pad->GetOffset().x ) / SCALE_FACTOR,
                    (-dy - pad->GetOffset().y ) / SCALE_FACTOR,
                    dx / (SCALE_FACTOR / 2), dy / (SCALE_FACTOR / 2) );
            break;

        case PAD_SHAPE_OVAL:     // Create outline by 2 lines and 2 arcs
            {
                // OrCAD Layout call them OVAL or OBLONG - GenCAD call them FINGERs
                fprintf( aFile, " FINGER %g\n",
                         pad->GetDrillSize().x / SCALE_FACTOR );
                int dr = dx - dy;

                if( dr >= 0 )       // Horizontal oval
                {
                    int radius = dy;
                    fprintf( aFile, "LINE %g %g %g %g\n",
                             (-dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - radius) / SCALE_FACTOR,
                             (dr + pad->GetOffset().x ) / SCALE_FACTOR,
                             (-pad->GetOffset().y - radius) / SCALE_FACTOR );

                    // GenCAD arcs are (start, end, center)
                    fprintf( aFile, "ARC %g %g %g %g %g %g\n",
                             (dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - radius) / SCALE_FACTOR,
                             (dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y + radius) / SCALE_FACTOR,
                             (dr + pad->GetOffset().x) / SCALE_FACTOR,
                             -pad->GetOffset().y / SCALE_FACTOR );

                    fprintf( aFile, "LINE %g %g %g %g\n",
                             (dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y + radius) / SCALE_FACTOR,
                             (-dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y + radius) / SCALE_FACTOR );
                    fprintf( aFile, "ARC %g %g %g %g %g %g\n",
                             (-dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y + radius) / SCALE_FACTOR,
                             (-dr + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - radius) / SCALE_FACTOR,
                             (-dr + pad->GetOffset().x) / SCALE_FACTOR,
                             -pad->GetOffset().y / SCALE_FACTOR );
                }
                else        // Vertical oval
                {
                    dr = -dr;
                    int radius = dx;
                    fprintf( aFile, "LINE %g %g %g %g\n",
                             (-radius + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - dr) / SCALE_FACTOR,
                             (-radius + pad->GetOffset().x ) / SCALE_FACTOR,
                             (-pad->GetOffset().y + dr) / SCALE_FACTOR );
                    fprintf( aFile, "ARC %g %g %g %g %g %g\n",
                             (-radius + pad->GetOffset().x ) / SCALE_FACTOR,
                             (-pad->GetOffset().y + dr) / SCALE_FACTOR,
                             (radius + pad->GetOffset().x ) / SCALE_FACTOR,
                             (-pad->GetOffset().y + dr) / SCALE_FACTOR,
                             pad->GetOffset().x / SCALE_FACTOR,
                             (-pad->GetOffset().y + dr) / SCALE_FACTOR );

                    fprintf( aFile, "LINE %g %g %g %g\n",
                             (radius + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y + dr) / SCALE_FACTOR,
                             (radius + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - dr) / SCALE_FACTOR );
                    fprintf( aFile, "ARC %g %g %g %g %g %g\n",
                             (radius + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - dr) / SCALE_FACTOR,
                             (-radius + pad->GetOffset().x) / SCALE_FACTOR,
                             (-pad->GetOffset().y - dr) / SCALE_FACTOR,
                             pad->GetOffset().x / SCALE_FACTOR,
                             (-pad->GetOffset().y - dr) / SCALE_FACTOR );
                }
            }
            break;

        case PAD_SHAPE_TRAPEZOID:
            fprintf( aFile, " POLYGON %g\n",
                     pad->GetDrillSize().x / SCALE_FACTOR );

            // XXX TO BE IMPLEMENTED! and I don't know if it could be actually imported by something
            break;
        }
    }

    fputs( "\n$ENDPADS\n\n", aFile );

    // Now emit the padstacks definitions, using the combined layer masks
    fputs( "$PADSTACKS\n", aFile );

    // Via padstacks
    for( unsigned i = 0; i < viastacks.size(); i++ )
    {
        VIA* via = viastacks[i];

        LSET mask = via->GetLayerSet() & master_layermask;

        fprintf( aFile, "PADSTACK VIA%d.%d.%s %g\n",
                 via->GetWidth(), via->GetDrillValue(),
                 fmt_mask( mask ).c_str(),
                 via->GetDrillValue() / SCALE_FACTOR );

        for( LSEQ seq = mask.Seq( gc_seq, DIM( gc_seq ) );  seq;  ++seq )
        {
            LAYER_ID layer = *seq;

            fprintf( aFile, "PAD V%d.%d.%s %s 0 0\n",
                    via->GetWidth(), via->GetDrillValue(),
                    fmt_mask( mask ).c_str(),
                    GenCADLayerName( cu_count, layer ).c_str()
                    );
        }
    }

    /* Component padstacks
     *  CAM350 don't apply correctly the FLIP semantics for padstacks, i.e. doesn't
     *  swap the top and bottom layers... so I need to define the shape as MIRRORX
     *  and define a separate 'flipped' padstack... until it appears yet another
     *  noncompliant importer */
    for( unsigned i = 1; i < padstacks.size(); i++ )
    {
        D_PAD* pad = padstacks[i];

        // Straight padstack
        fprintf( aFile, "PADSTACK PAD%u %g\n", i, pad->GetDrillSize().x / SCALE_FACTOR );

        LSET pad_set = pad->GetLayerSet() & master_layermask;

        // the special gc_seq
        for( LSEQ seq = pad_set.Seq( gc_seq, DIM( gc_seq ) );  seq;  ++seq )
        {
            LAYER_ID layer = *seq;

            fprintf( aFile, "PAD P%u %s 0 0\n", i, GenCADLayerName( cu_count, layer ).c_str() );
        }

        // Flipped padstack
        fprintf( aFile, "PADSTACK PAD%uF %g\n", i, pad->GetDrillSize().x / SCALE_FACTOR );

        // the normal LAYER_ID sequence is inverted from gc_seq[]
        for( LSEQ seq = pad_set.Seq();  seq;  ++seq )
        {
            LAYER_ID layer = *seq;

            fprintf( aFile, "PAD P%u %s 0 0\n", i, GenCADLayerNameFlipped( cu_count, layer ).c_str() );
        }
    }

    fputs( "$ENDPADSTACKS\n\n", aFile );
}