// The helper function for D_CODE::ConvertShapeToPolygon().
// Add a hole to a polygon
static void addHoleToPolygon( SHAPE_POLY_SET*       aPolygon,
                              APERTURE_DEF_HOLETYPE aHoleShape,
                              wxSize                aSize,
                              wxPoint               aAnchorPos )
{
    wxPoint currpos;
    SHAPE_POLY_SET holeBuffer;

    if( aHoleShape == APT_DEF_ROUND_HOLE )
    {
        TransformCircleToPolygon( holeBuffer, wxPoint( 0, 0 ), aSize.x / 2, SEGS_CNT );
    }
    else if( aHoleShape == APT_DEF_RECT_HOLE )
    {
        holeBuffer.NewOutline();
        currpos.x = aSize.x / 2;
        currpos.y = aSize.y / 2;
        holeBuffer.Append( VECTOR2I( currpos ) );       // link to hole and begin hole
        currpos.x -= aSize.x;
        holeBuffer.Append( VECTOR2I( currpos ) );
        currpos.y -= aSize.y;
        holeBuffer.Append( VECTOR2I( currpos ) );
        currpos.x += aSize.x;
        holeBuffer.Append( VECTOR2I( currpos ) );
        currpos.y += aSize.y;
        holeBuffer.Append( VECTOR2I( currpos ) );       // close hole
    }

    aPolygon->BooleanSubtract( holeBuffer, SHAPE_POLY_SET::PM_FAST );

    // Needed for legacy canvas only
    aPolygon->Fracture( SHAPE_POLY_SET::PM_FAST );
}
/* This function is used to extract a board outlines (3D view, automatic zones build ...)
 * Any closed outline inside the main outline is a hole
 * All contours should be closed, i.e. valid closed polygon vertices
 */
bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines,
        wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation )
{
    PCB_TYPE_COLLECTOR  items;

    // Get all the DRAWSEGMENTS and module graphics into 'items',
    // then keep only those on layer == Edge_Cuts.
    static const KICAD_T  scan_graphics[] = { PCB_LINE_T, PCB_MODULE_EDGE_T, EOT };
    items.Collect( aBoard, scan_graphics );

    // Make a working copy of aSegList, because the list is modified during calculations
    std::vector< DRAWSEGMENT* > segList;

    for( int ii = 0; ii < items.GetCount(); ii++ )
    {
        if( items[ii]->GetLayer() == Edge_Cuts )
            segList.push_back( static_cast< DRAWSEGMENT* >( items[ii] ) );
    }

    bool success = ConvertOutlineToPolygon( segList, aOutlines, aErrorText, aTolerance, aErrorLocation );

    if( !success || !aOutlines.OutlineCount() )
    {
        // Creates a valid polygon outline is not possible.
        // So uses the board edge cuts bounding box to create a
        // rectangular outline
        // When no edge cuts items, build a contour
        // from global bounding box

        EDA_RECT bbbox = aBoard->GetBoardEdgesBoundingBox();

        // If null area, uses the global bounding box.
        if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
            bbbox = aBoard->ComputeBoundingBox();

        // Ensure non null area. If happen, gives a minimal size.
        if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
            bbbox.Inflate( Millimeter2iu( 1.0 ) );

        aOutlines.RemoveAllContours();
        aOutlines.NewOutline();

        wxPoint corner;
        aOutlines.Append( bbbox.GetOrigin() );

        corner.x = bbbox.GetOrigin().x;
        corner.y = bbbox.GetEnd().y;
        aOutlines.Append( corner );

        aOutlines.Append( bbbox.GetEnd() );

        corner.x = bbbox.GetEnd().x;
        corner.y = bbbox.GetOrigin().y;
        aOutlines.Append( corner );
    }

    return success;
}
/**
 * Function TransformRingToPolygon
 * Creates a polygon from a ring
 * Convert arcs to multiple straight segments
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aCentre = centre of the arc or circle
 * @param aRadius = radius of the circle
 * @param aCircleToSegmentsCount = the number of segments to approximate a circle
 * @param aWidth = width (thickness) of the ring
 */
void TransformRingToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                             wxPoint aCentre, int aRadius,
                             int aCircleToSegmentsCount, int aWidth )
{
    // Compute the corners positions and creates the poly
    wxPoint curr_point;
    int     inner_radius    = aRadius - ( aWidth / 2 );
    int     outer_radius    = inner_radius + aWidth;

    if( inner_radius <= 0 )
    {   //In this case, the ring is just a circle (no hole inside)
        TransformCircleToPolygon( aCornerBuffer, aCentre, aRadius + ( aWidth / 2 ),
                                  aCircleToSegmentsCount );
        return;
    }

    aCornerBuffer.NewOutline();

    // Draw the inner circle of the ring
    int     delta = 3600 / aCircleToSegmentsCount;   // rotate angle in 0.1 degree

    for( int ii = 0; ii < 3600; ii += delta )
    {
        curr_point.x    = inner_radius;
        curr_point.y    = 0;
        RotatePoint( &curr_point, ii );
        curr_point      += aCentre;
        aCornerBuffer.Append( curr_point.x, curr_point.y );
    }

    // Draw the last point of inner circle
    aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y );

    // Draw the outer circle of the ring
    // the first point creates also a segment from the inner to the outer polygon
    for( int ii = 0; ii < 3600; ii += delta )
    {
        curr_point.x    = outer_radius;
        curr_point.y    = 0;
        RotatePoint( &curr_point, -ii );
        curr_point      += aCentre;
        aCornerBuffer.Append( curr_point.x, curr_point.y );
    }

    // Draw the last point of outer circle
    aCornerBuffer.Append( aCentre.x + outer_radius, aCentre.y );

    // And connect the outer polygon to the inner polygon,.
    // because a segment from inner to the outer polygon was already created,
    // the final polygon is the inner and the outer outlines connected by
    // 2 overlapping segments
    aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y );
}
/**
 * Function TransformBoundingBoxWithClearanceToPolygon
 * Convert the text bounding box to a rectangular polygon
 * Used in filling zones calculations
 * Circles and arcs are approximated by segments
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aClearanceValue = the clearance around the text bounding box
 */
void TEXTE_PCB::TransformBoundingBoxWithClearanceToPolygon(
                    SHAPE_POLY_SET& aCornerBuffer,
                    int             aClearanceValue ) const
{
    if( GetText().Length() == 0 )
        return;

    wxPoint  corners[4];    // Buffer of polygon corners

    EDA_RECT rect = GetTextBox( -1 );
    rect.Inflate( aClearanceValue );
    corners[0].x = rect.GetOrigin().x;
    corners[0].y = rect.GetOrigin().y;
    corners[1].y = corners[0].y;
    corners[1].x = rect.GetRight();
    corners[2].x = corners[1].x;
    corners[2].y = rect.GetBottom();
    corners[3].y = corners[2].y;
    corners[3].x = corners[0].x;

    aCornerBuffer.NewOutline();

    for( int ii = 0; ii < 4; ii++ )
    {
        // Rotate polygon
        RotatePoint( &corners[ii].x, &corners[ii].y, m_Pos.x, m_Pos.y, m_Orient );
        aCornerBuffer.Append( corners[ii].x, corners[ii].y );
    }
}
 /* Function TransformSolidAreasShapesToPolygonSet
 * Convert solid areas full shapes to polygon set
 * (the full shape is the polygon area with a thick outline)
 * Used in 3D view
 * Arcs (ends of segments) are approximated by segments
 * aCornerBuffer = a buffer to store the polygons
 * aCircleToSegmentsCount = the number of segments to approximate a circle
 * aCorrectionFactor = the correction to apply to arcs radius to roughly
 * keep arc radius when approximated by segments
 */
void ZONE_CONTAINER::TransformSolidAreasShapesToPolygonSet(
        SHAPE_POLY_SET& aCornerBuffer,
        int                    aCircleToSegmentsCount,
        double                 aCorrectionFactor )
{
    if( GetFilledPolysList().IsEmpty() )
        return;

    // add filled areas polygons
    aCornerBuffer.Append( m_FilledPolysList );

    // add filled areas outlines, which are drawn with thick lines
    for( int i = 0; i < m_FilledPolysList.OutlineCount(); i++ )
    {
        const SHAPE_LINE_CHAIN& path = m_FilledPolysList.COutline( i );

        for( int j = 0; j < path.PointCount(); j++ )
        {
            const VECTOR2I& a = path.CPoint( j );
            const VECTOR2I& b = path.CPoint( j + 1 );

            TransformRoundedEndsSegmentToPolygon( aCornerBuffer, wxPoint( a.x, a.y ), wxPoint( b.x, b.y ),
                                                    aCircleToSegmentsCount,
                                                    GetMinThickness() );
        }
    }
}
void ZONE_CONTAINER::TransformSolidAreasShapesToPolygonSet(
        SHAPE_POLY_SET& aCornerBuffer, int aError ) const
{
    if( GetFilledPolysList().IsEmpty() )
        return;

    // add filled areas polygons
    aCornerBuffer.Append( m_FilledPolysList );
    auto board = GetBoard();
    int maxError = ARC_HIGH_DEF;

    if( board )
        maxError = board->GetDesignSettings().m_MaxError;

    // add filled areas outlines, which are drawn with thick lines
    for( int i = 0; i < m_FilledPolysList.OutlineCount(); i++ )
    {
        const SHAPE_LINE_CHAIN& path = m_FilledPolysList.COutline( i );

        for( int j = 0; j < path.PointCount(); j++ )
        {
            const VECTOR2I& a = path.CPoint( j );
            const VECTOR2I& b = path.CPoint( j + 1 );

            TransformRoundedEndsSegmentToPolygon( aCornerBuffer, wxPoint( a.x, a.y ),
                    wxPoint( b.x, b.y ), maxError, GetMinThickness() );
        }
    }
}
/*
 * Function BuildPadShapePolygon
 * Build the Corner list of the polygonal shape,
 * depending on shape, extra size (clearance ...) pad and orientation
 * Note: for Round and oval pads this function is equivalent to
 * TransformShapeWithClearanceToPolygon, but not for other shapes
 */
void D_PAD::BuildPadShapePolygon( SHAPE_POLY_SET& aCornerBuffer,
                                  wxSize aInflateValue, int aSegmentsPerCircle,
                                  double aCorrectionFactor ) const
{
    wxPoint corners[4];
    wxPoint PadShapePos = ShapePos();         /* Note: for pad having a shape offset,
                                                     * the pad position is NOT the shape position */
    switch( GetShape() )
    {
    case PAD_SHAPE_CIRCLE:
    case PAD_SHAPE_OVAL:
        TransformShapeWithClearanceToPolygon( aCornerBuffer, aInflateValue.x,
                                              aSegmentsPerCircle, aCorrectionFactor );
        break;

    case PAD_SHAPE_TRAPEZOID:
    case PAD_SHAPE_RECT:
        aCornerBuffer.NewOutline();

        BuildPadPolygon( corners, aInflateValue, m_Orient );
        for( int ii = 0; ii < 4; ii++ )
        {
            corners[ii] += PadShapePos;          // Shift origin to position
            aCornerBuffer.Append( corners[ii].x, corners[ii].y );
        }

        break;
    }
}
/**
 * Function TransformRoundRectToPolygon
 * convert a rectangle with rounded corners to a polygon
 * Convert arcs to multiple straight lines
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aPosition = the coordinate of the center of the rectangle
 * @param aSize = the size of the rectangle
 * @param aRadius = radius of rounded corners
 * @param aRotation = rotation in 0.1 degrees of the rectangle
 * @param aCircleToSegmentsCount = the number of segments to approximate a circle
 */
void TransformRoundRectToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                  const wxPoint& aPosition, const wxSize& aSize,
                                  double aRotation, int aCornerRadius,
                                  int aCircleToSegmentsCount )
{
    wxPoint corners[4];
    GetRoundRectCornerCenters( corners, aCornerRadius, aPosition, aSize, aRotation );

    SHAPE_POLY_SET outline;
    outline.NewOutline();

    for( int ii = 0; ii < 4; ++ii )
        outline.Append( corners[ii].x, corners[ii].y );

    outline.Inflate( aCornerRadius, aCircleToSegmentsCount );

    // Add the outline:
    aCornerBuffer.Append( outline );
}
/**
 * Function TransformRingToPolygon
 * Creates a polygon from a ring
 * Convert arcs to multiple straight segments
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aCentre = centre of the arc or circle
 * @param aRadius = radius of the circle
 * @param aCircleToSegmentsCount = the number of segments to approximate a circle
 * @param aWidth = width (thickness) of the ring
 */
void TransformRingToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                             wxPoint aCentre, int aRadius,
                             int aCircleToSegmentsCount, int aWidth )
{
    int     delta = 3600 / aCircleToSegmentsCount;   // rotate angle in 0.1 degree

    // Compute the corners posituions and creates poly
    wxPoint curr_point;
    int     inner_radius    = aRadius - ( aWidth / 2 );
    int     outer_radius    = inner_radius + aWidth;

    aCornerBuffer.NewOutline();

    // Draw the inner circle of the ring
    for( int ii = 0; ii < 3600; ii += delta )
    {
        curr_point.x    = inner_radius;
        curr_point.y    = 0;
        RotatePoint( &curr_point, ii );
        curr_point      += aCentre;
        aCornerBuffer.Append( curr_point.x, curr_point.y );
    }

    // Draw the last point of inner circle
    aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y );

    // Draw the outer circle of the ring
    for( int ii = 0; ii < 3600; ii += delta )
    {
        curr_point.x    = outer_radius;
        curr_point.y    = 0;
        RotatePoint( &curr_point, -ii );
        curr_point      += aCentre;
        aCornerBuffer.Append( curr_point.x, curr_point.y );
    }

    // Draw the last point of outer circle
    aCornerBuffer.Append( aCentre.x + outer_radius, aCentre.y );
    aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y );
}
/*
 * Function BuildPadShapePolygon
 * Build the Corner list of the polygonal shape,
 * depending on shape, extra size (clearance ...) pad and orientation
 * Note: for Round and oval pads this function is equivalent to
 * TransformShapeWithClearanceToPolygon, but not for other shapes
 */
void D_PAD::BuildPadShapePolygon(
        SHAPE_POLY_SET& aCornerBuffer, wxSize aInflateValue, int aError ) const
{
    wxPoint corners[4];
    wxPoint padShapePos = ShapePos();       /* Note: for pad having a shape offset,
                                             * the pad position is NOT the shape position */
    switch( GetShape() )
    {
    case PAD_SHAPE_CIRCLE:
    case PAD_SHAPE_OVAL:
    case PAD_SHAPE_ROUNDRECT:
    case PAD_SHAPE_CHAMFERED_RECT:
    {
        // We are using TransformShapeWithClearanceToPolygon to build the shape.
        // Currently, this method uses only the same inflate value for X and Y dirs.
        // so because here this is not the case, we use a inflated dummy pad to build
        // the polygonal shape
        // TODO: remove this dummy pad when TransformShapeWithClearanceToPolygon will use
        // a wxSize to inflate the pad size
        D_PAD dummy( *this );
        dummy.SetSize( GetSize() + aInflateValue + aInflateValue );
        dummy.TransformShapeWithClearanceToPolygon( aCornerBuffer, 0 );
    }
        break;

    case PAD_SHAPE_TRAPEZOID:
    case PAD_SHAPE_RECT:
        aCornerBuffer.NewOutline();

        BuildPadPolygon( corners, aInflateValue, m_Orient );
        for( int ii = 0; ii < 4; ii++ )
        {
            corners[ii] += padShapePos;          // Shift origin to position
            aCornerBuffer.Append( corners[ii].x, corners[ii].y );
        }

        break;

    case PAD_SHAPE_CUSTOM:
        // for a custom shape, that is in fact a polygon (with holes), we can use only a inflate value.
        // so use ( aInflateValue.x + aInflateValue.y ) / 2 as polygon inflate value.
        // (different values for aInflateValue.x and aInflateValue.y has no sense for a custom pad)
        TransformShapeWithClearanceToPolygon(
                aCornerBuffer, ( aInflateValue.x + aInflateValue.y ) / 2 );
        break;
    }
}
const SHAPE_POLY_SET ConvertPolyListToPolySet( const CPOLYGONS_LIST& aList )
{
    SHAPE_POLY_SET rv;

    unsigned corners_count = aList.GetCornersCount();

    // Enter main outline: this is the first contour
    unsigned ic = 0;

    if( !corners_count )
        return rv;

    int index = 0;

    while( ic < corners_count )
    {
        int hole = -1;

        if( index == 0 )
        {
            rv.NewOutline();
            hole = -1;
        }
        else
        {
            hole = rv.NewHole();
        }

        while( ic < corners_count )
        {
            rv.Append( aList.GetX( ic ), aList.GetY( ic ), 0, hole );

            if( aList.IsEndContour( ic ) )
                break;

            ic++;
        }
        ic++;

        index++;
    }

    return rv;
}
/**
 * Function TransformCircleToPolygon
 * convert a circle to a polygon, using multiple straight lines
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aCenter = the center of the circle
 * @param aRadius = the radius of the circle
 * @param aCircleToSegmentsCount = the number of segments to approximate a circle
 * Note: the polygon is inside the circle, so if you want to have the polygon
 * outside the circle, you should give aRadius calculated with a corrrection factor
 */
void TransformCircleToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                               wxPoint aCenter, int aRadius,
                               int aCircleToSegmentsCount )
{
    wxPoint corner_position;
    int     delta       = 3600 / aCircleToSegmentsCount;    // rot angle in 0.1 degree
    int     halfstep    = 1800 / aCircleToSegmentsCount;    // the starting value for rot angles

    aCornerBuffer.NewOutline();

    for( int ii = 0; ii < aCircleToSegmentsCount; ii++ )
    {
        corner_position.x   = aRadius;
        corner_position.y   = 0;
        int     angle = (ii * delta) + halfstep;
        RotatePoint( &corner_position.x, &corner_position.y, angle );
        corner_position += aCenter;
        aCornerBuffer.Append( corner_position.x, corner_position.y );
    }
}
void TransformRoundChamferedRectToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                  const wxPoint& aPosition, const wxSize& aSize,
                                  double aRotation, int aCornerRadius,
                                  double aChamferRatio, int aChamferCorners,
                                  int aCircleToSegmentsCount )
{
    // Build the basic shape in orientation 0.0, position 0,0 for chamfered corners
    // or in actual position/orientation for round rect only
    wxPoint corners[4];
    GetRoundRectCornerCenters( corners, aCornerRadius,
                               aChamferCorners ? wxPoint( 0, 0 ) : aPosition,
                               aSize, aChamferCorners ? 0.0 : aRotation );

    SHAPE_POLY_SET outline;
    outline.NewOutline();

    for( int ii = 0; ii < 4; ++ii )
        outline.Append( corners[ii].x, corners[ii].y );

    outline.Inflate( aCornerRadius, aCircleToSegmentsCount );

    if( aChamferCorners == RECT_NO_CHAMFER )      // no chamfer
    {
        // Add the outline:
        aCornerBuffer.Append( outline );
        return;
    }

    // Now we have the round rect outline, in position 0,0 orientation 0.0.
    // Chamfer the corner(s).
    int chamfer_value = aChamferRatio * std::min( aSize.x, aSize.y );

    SHAPE_POLY_SET chamfered_corner;    // corner shape for the current corner to chamfer

    int corner_id[4] =
    {
        RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT,
        RECT_CHAMFER_BOTTOM_LEFT, RECT_CHAMFER_BOTTOM_RIGHT
    };
    // Depending on the corner position, signX[] and signY[] give the sign of chamfer
    // coordinates relative to the corner position
    // The first corner is the top left corner, then top right, bottom left and bottom right
    int signX[4] = {1, -1, 1,-1 };
    int signY[4] = {1, 1, -1,-1 };

    for( int ii = 0; ii < 4; ii++ )
    {
        if( (corner_id[ii] & aChamferCorners) == 0 )
            continue;

        VECTOR2I corner_pos( -signX[ii]*aSize.x/2, -signY[ii]*aSize.y/2 );

        if( aCornerRadius )
        {
            // We recreate a rectangular area covering the full rounded corner (max size = aSize/2)
            // to rebuild the corner before chamfering, to be sure the rounded corner shape does not
            // overlap the chamfered corner shape:
            chamfered_corner.RemoveAllContours();
            chamfered_corner.NewOutline();
            chamfered_corner.Append( 0, 0 );
            chamfered_corner.Append( 0, signY[ii]*aSize.y/2 );
            chamfered_corner.Append( signX[ii]*aSize.x/2, signY[ii]*aSize.y/2 );
            chamfered_corner.Append( signX[ii]*aSize.x/2, 0 );
            chamfered_corner.Move( corner_pos );
            outline.BooleanAdd( chamfered_corner, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
        }

        // Now chamfer this corner
        chamfered_corner.RemoveAllContours();
        chamfered_corner.NewOutline();
        chamfered_corner.Append( 0, 0 );
        chamfered_corner.Append( 0, signY[ii]*chamfer_value );
        chamfered_corner.Append( signX[ii]*chamfer_value, 0 );
        chamfered_corner.Move( corner_pos );
        outline.BooleanSubtract( chamfered_corner, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
    }

    // Rotate and move the outline:
    if( aRotation != 0.0 )
        outline.Rotate( DECIDEG2RAD( -aRotation ), VECTOR2I( 0, 0 ) );

    outline.Move( VECTOR2I( aPosition ) );

    // Add the outline:
    aCornerBuffer.Append( outline );
}
Beispiel #14
0
std::unique_ptr<PNS::SOLID> PNS_KICAD_IFACE::syncPad( D_PAD* aPad )
{
    LAYER_RANGE layers( 0, MAX_CU_LAYERS - 1 );

    // ignore non-copper pads
    if( ( aPad->GetLayerSet() & LSET::AllCuMask()).none() )
        return NULL;

    switch( aPad->GetAttribute() )
    {
    case PAD_ATTRIB_STANDARD:
        break;

    case PAD_ATTRIB_SMD:
    case PAD_ATTRIB_HOLE_NOT_PLATED:
    case PAD_ATTRIB_CONN:
        {
            LSET lmsk = aPad->GetLayerSet();
            bool is_copper = false;

            for( int i = 0; i < MAX_CU_LAYERS; i++ )
            {
                if( lmsk[i] )
                {
                    is_copper = true;

                    if( aPad->GetAttribute() != PAD_ATTRIB_HOLE_NOT_PLATED )
                        layers = LAYER_RANGE( i );

                    break;
                }
            }

            if( !is_copper )
                return NULL;
        }
        break;

    default:
        wxLogTrace( "PNS", "unsupported pad type 0x%x", aPad->GetAttribute() );
        return NULL;
    }

    std::unique_ptr< PNS::SOLID > solid( new PNS::SOLID );

    solid->SetLayers( layers );
    solid->SetNet( aPad->GetNetCode() );
    solid->SetParent( aPad );

    wxPoint wx_c = aPad->ShapePos();
    wxSize  wx_sz = aPad->GetSize();
    wxPoint offset = aPad->GetOffset();

    VECTOR2I c( wx_c.x, wx_c.y );
    VECTOR2I sz( wx_sz.x, wx_sz.y );

    RotatePoint( &offset, aPad->GetOrientation() );

    solid->SetPos( VECTOR2I( c.x - offset.x, c.y - offset.y ) );
    solid->SetOffset( VECTOR2I( offset.x, offset.y ) );

    double orient = aPad->GetOrientation() / 10.0;

    if( aPad->GetShape() == PAD_SHAPE_CIRCLE )
    {
        solid->SetShape( new SHAPE_CIRCLE( c, sz.x / 2 ) );
    }
    else if( aPad->GetShape() == PAD_SHAPE_CUSTOM )
    {
        SHAPE_POLY_SET outline;
        outline.Append( aPad->GetCustomShapeAsPolygon() );
        aPad->CustomShapeAsPolygonToBoardPosition( &outline, wx_c, aPad->GetOrientation() );

        SHAPE_CONVEX* shape = new SHAPE_CONVEX();

        // We use the convex hull of the pad shape, because PnS knows
        // only convex shapes.
        std::vector<wxPoint> convex_hull;
        BuildConvexHull( convex_hull, outline );

        for( unsigned ii = 0; ii < convex_hull.size(); ii++ )
            shape->Append( convex_hull[ii] );

        solid->SetShape( shape );
    }
    else
    {
        if( orient == 0.0 || orient == 90.0 || orient == 180.0 || orient == 270.0 )
        {
            if( orient == 90.0 || orient == 270.0 )
                sz = VECTOR2I( sz.y, sz.x );

            switch( aPad->GetShape() )
            {
            case PAD_SHAPE_OVAL:
                if( sz.x == sz.y )
                    solid->SetShape( new SHAPE_CIRCLE( c, sz.x / 2 ) );
                else
                {
                    VECTOR2I delta;

                    if( sz.x > sz.y )
                        delta = VECTOR2I( ( sz.x - sz.y ) / 2, 0 );
                    else
                        delta = VECTOR2I( 0, ( sz.y - sz.x ) / 2 );

                    SHAPE_SEGMENT* shape = new SHAPE_SEGMENT( c - delta, c + delta,
                                                              std::min( sz.x, sz.y ) );
                    solid->SetShape( shape );
                }
                break;

            case PAD_SHAPE_RECT:
                solid->SetShape( new SHAPE_RECT( c - sz / 2, sz.x, sz.y ) );
                break;

            case PAD_SHAPE_TRAPEZOID:
            {
                wxPoint coords[4];
                aPad->BuildPadPolygon( coords, wxSize( 0, 0 ), aPad->GetOrientation() );
                SHAPE_CONVEX* shape = new SHAPE_CONVEX();

                for( int ii = 0; ii < 4; ii++ )
                {
                    shape->Append( wx_c + coords[ii] );
                }

                solid->SetShape( shape );
                break;
            }

            case PAD_SHAPE_ROUNDRECT:
            {
                SHAPE_POLY_SET outline;
                const int segmentToCircleCount = 64;

                aPad->BuildPadShapePolygon( outline, wxSize( 0, 0 ), segmentToCircleCount, 1.0 );

                // TransformRoundRectToPolygon creates only one convex polygon
                SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
                SHAPE_CONVEX* shape = new SHAPE_CONVEX();

                for( int ii = 0; ii < poly.PointCount(); ++ii )
                {
                    shape->Append( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
                }

                solid->SetShape( shape );
            }
                break;

            default:
                wxLogTrace( "PNS", "unsupported pad shape" );
                return nullptr;
            }
        }
        else
        {
            switch( aPad->GetShape() )
            {
            // PAD_SHAPE_CIRCLE and PAD_SHAPE_CUSTOM already handled above

            case PAD_SHAPE_OVAL:
                if( sz.x == sz.y )
                    solid->SetShape( new SHAPE_CIRCLE( c, sz.x / 2 ) );
                else
                {
                    wxPoint start;
                    wxPoint end;
                    wxPoint corner;

                    SHAPE_CONVEX* shape = new SHAPE_CONVEX();

                    int w = aPad->BuildSegmentFromOvalShape( start, end, 0.0, wxSize( 0, 0 ) );

                    if( start.y == 0 )
                        corner = wxPoint( start.x, -( w / 2 ) );
                    else
                        corner = wxPoint( w / 2, start.y );

                    RotatePoint( &start, aPad->GetOrientation() );
                    RotatePoint( &corner, aPad->GetOrientation() );
                    shape->Append( wx_c + corner );

                    for( int rot = 100; rot <= 1800; rot += 100 )
                    {
                        wxPoint p( corner );
                        RotatePoint( &p, start, rot );
                        shape->Append( wx_c + p );
                    }

                    if( end.y == 0 )
                        corner = wxPoint( end.x, w / 2 );
                    else
                        corner = wxPoint( -( w / 2 ), end.y );

                    RotatePoint( &end, aPad->GetOrientation() );
                    RotatePoint( &corner, aPad->GetOrientation() );
                    shape->Append( wx_c + corner );

                    for( int rot = 100; rot <= 1800; rot += 100 )
                    {
                        wxPoint p( corner );
                        RotatePoint( &p, end, rot );
                        shape->Append( wx_c + p );
                    }

                    solid->SetShape( shape );
                }
                break;

            case PAD_SHAPE_RECT:
            case PAD_SHAPE_TRAPEZOID:
            {
                wxPoint coords[4];
                aPad->BuildPadPolygon( coords, wxSize( 0, 0 ), aPad->GetOrientation() );

                SHAPE_CONVEX* shape = new SHAPE_CONVEX();
                for( int ii = 0; ii < 4; ii++ )
                {
                    shape->Append( wx_c + coords[ii] );
                }

                solid->SetShape( shape );
                break;
            }

            case PAD_SHAPE_ROUNDRECT:
            {
                SHAPE_POLY_SET outline;
                const int segmentToCircleCount = 32;
                aPad->BuildPadShapePolygon( outline, wxSize( 0, 0 ),
                                            segmentToCircleCount, 1.0 );

                // TransformRoundRectToPolygon creates only one convex polygon
                SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
                SHAPE_CONVEX* shape = new SHAPE_CONVEX();

                for( int ii = 0; ii < poly.PointCount(); ++ii )
                {
                    shape->Append( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) );
                }

                solid->SetShape( shape );
                break;
            }

            default:
                wxLogTrace( "PNS", "unsupported pad shape" );
                return nullptr;
            }
        }
    }
    return solid;
}
/*
 * Note 1: polygons are drawm using outlines witk a thickness = aMinThicknessValue
 * so shapes must take in account this outline thickness
 *
 * Note 2:
 *      Trapezoidal pads are not considered here because they are very special case
 *      and are used in microwave applications and they *DO NOT* have a thermal relief that
 *      change the shape by creating stubs and destroy their properties.
 */
void CreateThermalReliefPadPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                    const D_PAD&    aPad,
                                    int             aThermalGap,
                                    int             aCopperThickness,
                                    int             aMinThicknessValue,
                                    int             aError,
                                    double          aThermalRot )
{
    wxPoint corner, corner_end;
    wxSize  copper_thickness;
    wxPoint padShapePos = aPad.ShapePos();      // Note: for pad having a shape offset,
                                                // the pad position is NOT the shape position

    /* Keep in account the polygon outline thickness
     * aThermalGap must be increased by aMinThicknessValue/2 because drawing external outline
     * with a thickness of aMinThicknessValue will reduce gap by aMinThicknessValue/2
     */
    aThermalGap += aMinThicknessValue / 2;

    /* Keep in account the polygon outline thickness
     * copper_thickness must be decreased by aMinThicknessValue because drawing outlines
     * with a thickness of aMinThicknessValue will increase real thickness by aMinThicknessValue
     */
    int     dx = aPad.GetSize().x / 2;
    int     dy = aPad.GetSize().y / 2;

    copper_thickness.x = std::min( aPad.GetSize().x, aCopperThickness ) - aMinThicknessValue;
    copper_thickness.y = std::min( aPad.GetSize().y, aCopperThickness ) - aMinThicknessValue;

    if( copper_thickness.x < 0 )
        copper_thickness.x = 0;

    if( copper_thickness.y < 0 )
        copper_thickness.y = 0;

    switch( aPad.GetShape() )
    {
    case PAD_SHAPE_CIRCLE:    // Add 4 similar holes
        {
            /* we create 4 copper holes and put them in position 1, 2, 3 and 4
             * here is the area of the rectangular pad + its thermal gap
             * the 4 copper holes remove the copper in order to create the thermal gap
             * 4 ------ 1
             * |        |
             * |        |
             * |        |
             * |        |
             * 3 ------ 2
             * holes 2, 3, 4 are the same as hole 1, rotated 90, 180, 270 deg
             */

            // Build the hole pattern, for the hole in the X >0, Y > 0 plane:
            // The pattern roughtly is a 90 deg arc pie
            std::vector <wxPoint> corners_buffer;

            int    numSegs = std::max( GetArcToSegmentCount( dx + aThermalGap, aError, 360.0 ), 6 );
            double correction = GetCircletoPolyCorrectionFactor( numSegs );
            double delta = 3600.0 / numSegs;

            // Radius of outer arcs of the shape corrected for arc approximation by lines
            int outer_radius = KiROUND( ( dx + aThermalGap ) * correction );

            // Crosspoint of thermal spoke sides, the first point of polygon buffer
            corners_buffer.push_back( wxPoint( copper_thickness.x / 2, copper_thickness.y / 2 ) );

            // Add an intermediate point on spoke sides, to allow a > 90 deg angle between side
            // and first seg of arc approx
            corner.x = copper_thickness.x / 2;
            int y = outer_radius - (aThermalGap / 4);
            corner.y = KiROUND( sqrt( ( (double) y * y  - (double) corner.x * corner.x ) ) );

            if( aThermalRot != 0 )
                corners_buffer.push_back( corner );

            // calculate the starting point of the outter arc
            corner.x = copper_thickness.x / 2;

            corner.y = KiROUND( sqrt( ( (double) outer_radius * outer_radius ) -
                                      ( (double) corner.x * corner.x ) ) );
            RotatePoint( &corner, 90 ); // 9 degrees is the spoke fillet size

            // calculate the ending point of the outer arc
            corner_end.x = corner.y;
            corner_end.y = corner.x;

            // calculate intermediate points (y coordinate from corner.y to corner_end.y
            while( (corner.y > corner_end.y)  && (corner.x < corner_end.x) )
            {
                corners_buffer.push_back( corner );
                RotatePoint( &corner, delta );
            }

            corners_buffer.push_back( corner_end );

            /* add an intermediate point, to avoid angles < 90 deg between last arc approx line
             * and radius line
             */
            corner.x = corners_buffer[1].y;
            corner.y = corners_buffer[1].x;
            corners_buffer.push_back( corner );

            // Now, add the 4 holes ( each is the pattern, rotated by 0, 90, 180 and 270  deg
            // aThermalRot = 450 (45.0 degrees orientation) work fine.
            double angle_pad = aPad.GetOrientation();              // Pad orientation
            double th_angle  = aThermalRot;

            for( unsigned ihole = 0; ihole < 4; ihole++ )
            {
                aCornerBuffer.NewOutline();

                for( unsigned ii = 0; ii < corners_buffer.size(); ii++ )
                {
                    corner = corners_buffer[ii];
                    RotatePoint( &corner, th_angle + angle_pad );          // Rotate by segment angle and pad orientation
                    corner += padShapePos;
                    aCornerBuffer.Append( corner.x, corner.y );
                }

                th_angle += 900;       // Note: th_angle in in 0.1 deg.
            }
        }
        break;

    case PAD_SHAPE_OVAL:
        {
            // Oval pad support along the lines of round and rectangular pads
            std::vector <wxPoint> corners_buffer;               // Polygon buffer as vector

            dx = (aPad.GetSize().x / 2) + aThermalGap;     // Cutout radius x
            dy = (aPad.GetSize().y / 2) + aThermalGap;     // Cutout radius y

            wxPoint shape_offset;

            // We want to calculate an oval shape with dx > dy.
            // if this is not the case, exchange dx and dy, and rotate the shape 90 deg.
            int supp_angle = 0;

            if( dx < dy )
            {
                std::swap( dx, dy );
                supp_angle = 900;
                std::swap( copper_thickness.x, copper_thickness.y );
            }

            int deltasize = dx - dy;        // = distance between shape position and the 2 demi-circle ends centre
            // here we have dx > dy
            // Radius of outer arcs of the shape:
            int outer_radius = dy;     // The radius of the outer arc is radius end + aThermalGap


            int    numSegs = std::max( GetArcToSegmentCount( outer_radius, aError, 360.0 ), 6 );
            double delta = 3600.0 / numSegs;

            // Some coordinate fiddling, depending on the shape offset direction
            shape_offset = wxPoint( deltasize, 0 );

            // Crosspoint of thermal spoke sides, the first point of polygon buffer
            corner.x = copper_thickness.x / 2;
            corner.y = copper_thickness.y / 2;
            corners_buffer.push_back( corner );

            // Arc start point calculation, the intersecting point of cutout arc and thermal spoke edge
            // If copper thickness is more than shape offset, we need to calculate arc intercept point.
            if( copper_thickness.x > deltasize )
            {
                corner.x = copper_thickness.x / 2;
                corner.y = KiROUND( sqrt( ( (double) outer_radius * outer_radius ) -
                                        ( (double) ( corner.x - delta ) * ( corner.x - deltasize ) ) ) );
                corner.x -= deltasize;

                /* creates an intermediate point, to have a > 90 deg angle
                 * between the side and the first segment of arc approximation
                 */
                wxPoint intpoint = corner;
                intpoint.y -= aThermalGap / 4;
                corners_buffer.push_back( intpoint + shape_offset );
                RotatePoint( &corner, 90 ); // 9 degrees of thermal fillet
            }
            else
            {
                corner.x = copper_thickness.x / 2;
                corner.y = outer_radius;
                corners_buffer.push_back( corner );
            }

            // Add an intermediate point on spoke sides, to allow a > 90 deg angle between side
            // and first seg of arc approx
            wxPoint last_corner;
            last_corner.y = copper_thickness.y / 2;
            int     px = outer_radius - (aThermalGap / 4);
            last_corner.x =
                KiROUND( sqrt( ( ( (double) px * px ) - (double) last_corner.y * last_corner.y ) ) );

            // Arc stop point calculation, the intersecting point of cutout arc and thermal spoke edge
            corner_end.y = copper_thickness.y / 2;
            corner_end.x =
                KiROUND( sqrt( ( (double) outer_radius *
                             outer_radius ) - ( (double) corner_end.y * corner_end.y ) ) );
            RotatePoint( &corner_end, -90 ); // 9 degrees of thermal fillet

            // calculate intermediate arc points till limit is reached
            while( (corner.y > corner_end.y)  && (corner.x < corner_end.x) )
            {
                corners_buffer.push_back( corner + shape_offset );
                RotatePoint( &corner, delta );
            }

            //corners_buffer.push_back(corner + shape_offset);      // TODO: about one mil geometry error forms somewhere.
            corners_buffer.push_back( corner_end + shape_offset );
            corners_buffer.push_back( last_corner + shape_offset );         // Enabling the line above shows intersection point.

            /* Create 2 holes, rotated by pad rotation.
             */
            double angle = aPad.GetOrientation() + supp_angle;

            for( int irect = 0; irect < 2; irect++ )
            {
                aCornerBuffer.NewOutline();
                for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
                {
                    wxPoint cpos = corners_buffer[ic];
                    RotatePoint( &cpos, angle );
                    cpos += padShapePos;
                    aCornerBuffer.Append( cpos.x, cpos.y );
                }

                angle = AddAngles( angle, 1800 ); // this is calculate hole 3
            }

            // Create holes, that are the mirrored from the previous holes
            for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
            {
                wxPoint swap = corners_buffer[ic];
                swap.x = -swap.x;
                corners_buffer[ic] = swap;
            }

            // Now add corner 4 and 2 (2 is the corner 4 rotated by 180 deg
            angle = aPad.GetOrientation() + supp_angle;

            for( int irect = 0; irect < 2; irect++ )
            {
                aCornerBuffer.NewOutline();

                for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
                {
                    wxPoint cpos = corners_buffer[ic];
                    RotatePoint( &cpos, angle );
                    cpos += padShapePos;
                    aCornerBuffer.Append( cpos.x, cpos.y );
                }

                angle = AddAngles( angle, 1800 );
            }
        }
        break;

    case PAD_SHAPE_CHAMFERED_RECT:
    case PAD_SHAPE_ROUNDRECT:   // thermal shape is the same for rectangular shapes.
    case PAD_SHAPE_RECT:
        {
            /* we create 4 copper holes and put them in position 1, 2, 3 and 4
             * here is the area of the rectangular pad + its thermal gap
             * the 4 copper holes remove the copper in order to create the thermal gap
             * 1 ------ 4
             * |        |
             * |        |
             * |        |
             * |        |
             * 2 ------ 3
             * hole 3 is the same as hole 1, rotated 180 deg
             * hole 4 is the same as hole 2, rotated 180 deg and is the same as hole 1, mirrored
             */

            // First, create a rectangular hole for position 1 :
            // 2 ------- 3
            //  |        |
            //  |        |
            //  |        |
            // 1  -------4

            // Modified rectangles with one corner rounded. TODO: merging with oval thermals
            // and possibly round too.

            std::vector <wxPoint> corners_buffer;               // Polygon buffer as vector

            dx = (aPad.GetSize().x / 2) + aThermalGap;         // Cutout radius x
            dy = (aPad.GetSize().y / 2) + aThermalGap;         // Cutout radius y

            // calculation is optimized for pad shape with dy >= dx (vertical rectangle).
            // if it is not the case, just rotate this shape 90 degrees:
            double angle = aPad.GetOrientation();
            wxPoint corner_origin_pos( -aPad.GetSize().x / 2, -aPad.GetSize().y / 2 );

            if( dy < dx )
            {
                std::swap( dx, dy );
                std::swap( copper_thickness.x, copper_thickness.y );
                std::swap( corner_origin_pos.x, corner_origin_pos.y );
                angle += 900.0;
            }
            // Now calculate the hole pattern in position 1 ( top left pad corner )

            // The first point of polygon buffer is left lower corner, second the crosspoint of
            // thermal spoke sides, the third is upper right corner and the rest are rounding
            // vertices going anticlockwise. Note the inverted Y-axis in corners_buffer y coordinates.
            wxPoint arc_end_point( -dx, -(aThermalGap / 4 + copper_thickness.y / 2) );
            corners_buffer.push_back( arc_end_point );          // Adds small miters to zone
            corners_buffer.push_back( wxPoint( -(dx - aThermalGap / 4), -copper_thickness.y / 2 ) );    // fill and spoke corner
            corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -copper_thickness.y / 2 ) );
            corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -(dy - aThermalGap / 4) ) );
            // The first point to build the rounded corner:
            wxPoint arc_start_point( -(aThermalGap / 4 + copper_thickness.x / 2) , -dy );
            corners_buffer.push_back( arc_start_point );

            int    numSegs = std::max( GetArcToSegmentCount( aThermalGap, aError, 360.0 ), 6 );
            double correction = GetCircletoPolyCorrectionFactor( numSegs );
            int    rounding_radius = KiROUND( aThermalGap * correction ); // Corner rounding radius

            // Calculate arc angle parameters.
            // the start angle id near 900 decidegrees, the final angle is near 1800.0 decidegrees.
            double arc_increment = 3600.0 / numSegs;

            // the arc_angle_start is 900.0 or slighly more, depending on the actual arc starting point
            double arc_angle_start = atan2( -arc_start_point.y -corner_origin_pos.y, arc_start_point.x - corner_origin_pos.x ) * 1800/M_PI;
            if( arc_angle_start < 900.0 )
                arc_angle_start = 900.0;

            bool first_point = true;
            for( double curr_angle = arc_angle_start; ; curr_angle += arc_increment )
            {
                wxPoint corner_position = wxPoint( rounding_radius, 0 );
                RotatePoint( &corner_position, curr_angle );        // Rounding vector rotation
                corner_position += corner_origin_pos;               // Rounding vector + Pad corner offset

                // The arc angle is <= 90 degrees, therefore the arc is finished if the x coordinate
                // decrease or the y coordinate is smaller than the y end point
                if( !first_point &&
                    ( corner_position.x >= corners_buffer.back().x || corner_position.y > arc_end_point.y ) )
                    break;

                first_point = false;

                // Note: for hole in position 1, arc x coordinate is always < x starting point
                // and arc y coordinate is always <= y ending point
                if( corner_position != corners_buffer.back()    // avoid duplicate corners.
                    && corner_position.x <= arc_start_point.x )   // skip current point at the right of the starting point
                    corners_buffer.push_back( corner_position );
            }

            for( int irect = 0; irect < 2; irect++ )
            {
                aCornerBuffer.NewOutline();

                for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
                {
                    wxPoint cpos = corners_buffer[ic];
                    RotatePoint( &cpos, angle );            // Rotate according to module orientation
                    cpos += padShapePos;                    // Shift origin to position
                    aCornerBuffer.Append( cpos.x, cpos.y );
                }

                angle = AddAngles( angle, 1800 );       // this is calculate hole 3
            }

            // Create holes, that are the mirrored from the previous holes
            for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
            {
                wxPoint swap = corners_buffer[ic];
                swap.x = -swap.x;
                corners_buffer[ic] = swap;
            }

            // Now add corner 4 and 2 (2 is the corner 4 rotated by 180 deg
            for( int irect = 0; irect < 2; irect++ )
            {
                aCornerBuffer.NewOutline();

                for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
                {
                    wxPoint cpos = corners_buffer[ic];
                    RotatePoint( &cpos, angle );
                    cpos += padShapePos;
                    aCornerBuffer.Append( cpos.x, cpos.y );
                }

                angle = AddAngles( angle, 1800 );
            }
        }
        break;

    case PAD_SHAPE_TRAPEZOID:
        {
        SHAPE_POLY_SET antipad;       // The full antipad area

        // We need a length to build the stubs of the thermal reliefs
        // the value is not very important. The pad bounding box gives a reasonable value
        EDA_RECT bbox = aPad.GetBoundingBox();
        int stub_len = std::max( bbox.GetWidth(), bbox.GetHeight() );

        aPad.TransformShapeWithClearanceToPolygon( antipad, aThermalGap );

        SHAPE_POLY_SET stub;          // A basic stub ( a rectangle)
        SHAPE_POLY_SET stubs;        // the full stubs shape


        // We now substract the stubs (connections to the copper zone)
        //ClipperLib::Clipper clip_engine;
        // Prepare a clipping transform
        //clip_engine.AddPath( antipad, ClipperLib::ptSubject, true );

        // Create stubs and add them to clipper engine
        wxPoint stubBuffer[4];
        stubBuffer[0].x = stub_len;
        stubBuffer[0].y = copper_thickness.y/2;
        stubBuffer[1] = stubBuffer[0];
        stubBuffer[1].y = -copper_thickness.y/2;
        stubBuffer[2] = stubBuffer[1];
        stubBuffer[2].x = -stub_len;
        stubBuffer[3] = stubBuffer[2];
        stubBuffer[3].y = copper_thickness.y/2;

        stub.NewOutline();

        for( unsigned ii = 0; ii < arrayDim( stubBuffer ); ii++ )
        {
            wxPoint cpos = stubBuffer[ii];
            RotatePoint( &cpos, aPad.GetOrientation() );
            cpos += padShapePos;
            stub.Append( cpos.x, cpos.y );
        }

        stubs.Append( stub );

        stubBuffer[0].y = stub_len;
        stubBuffer[0].x = copper_thickness.x/2;
        stubBuffer[1] = stubBuffer[0];
        stubBuffer[1].x = -copper_thickness.x/2;
        stubBuffer[2] = stubBuffer[1];
        stubBuffer[2].y = -stub_len;
        stubBuffer[3] = stubBuffer[2];
        stubBuffer[3].x = copper_thickness.x/2;

        stub.RemoveAllContours();
        stub.NewOutline();

        for( unsigned ii = 0; ii < arrayDim( stubBuffer ); ii++ )
        {
            wxPoint cpos = stubBuffer[ii];
            RotatePoint( &cpos, aPad.GetOrientation() );
            cpos += padShapePos;
            stub.Append( cpos.x, cpos.y );
        }

        stubs.Append( stub );
        stubs.Simplify( SHAPE_POLY_SET::PM_FAST );

        antipad.BooleanSubtract( stubs, SHAPE_POLY_SET::PM_FAST );
        aCornerBuffer.Append( antipad );

        break;
        }

    default:
        ;
    }
}
void D_PAD::TransformShapeWithClearanceToPolygon(
        SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aError, bool ignoreLineWidth ) const
{
    wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for pads." );

    double  angle = m_Orient;
    int     dx = (m_Size.x / 2) + aClearanceValue;
    int     dy = (m_Size.y / 2) + aClearanceValue;

    wxPoint padShapePos = ShapePos();               /* Note: for pad having a shape offset,
                                                     * the pad position is NOT the shape position */

    switch( GetShape() )
    {
    case PAD_SHAPE_CIRCLE:
    {
        TransformCircleToPolygon( aCornerBuffer, padShapePos, dx, aError );
    }
        break;

    case PAD_SHAPE_OVAL:
        // An oval pad has the same shape as a segment with rounded ends
        {
        int width;
        wxPoint shape_offset;
        if( dy > dx )   // Oval pad X/Y ratio for choosing translation axis
        {
            shape_offset.y = dy - dx;
            width = dx * 2;
        }
        else    //if( dy <= dx )
        {
            shape_offset.x = dy - dx;
            width = dy * 2;
        }

        RotatePoint( &shape_offset, angle );
        wxPoint start = padShapePos - shape_offset;
        wxPoint end = padShapePos + shape_offset;
        TransformOvalClearanceToPolygon( aCornerBuffer, start, end, width, aError );
        }
        break;

    case PAD_SHAPE_TRAPEZOID:
    case PAD_SHAPE_RECT:
    {
        wxPoint corners[4];
        BuildPadPolygon( corners, wxSize( 0, 0 ), angle );

        SHAPE_POLY_SET outline;
        outline.NewOutline();

        for( int ii = 0; ii < 4; ii++ )
        {
            corners[ii] += padShapePos;
            outline.Append( corners[ii].x, corners[ii].y );
        }

        int    numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ), 6 );
        double correction = GetCircletoPolyCorrectionFactor( numSegs );

        int rounding_radius = KiROUND( aClearanceValue * correction );
        outline.Inflate( rounding_radius, numSegs );

        aCornerBuffer.Append( outline );
    }
        break;

    case PAD_SHAPE_CHAMFERED_RECT:
    case PAD_SHAPE_ROUNDRECT:
    {
        SHAPE_POLY_SET outline;
        int            radius = GetRoundRectCornerRadius() + aClearanceValue;
        int            numSegs = std::max( GetArcToSegmentCount( radius, aError, 360.0 ), 6 );
        double         correction = GetCircletoPolyCorrectionFactor( numSegs );
        int            clearance = KiROUND( aClearanceValue * correction );
        int            rounding_radius = GetRoundRectCornerRadius() + clearance;
        wxSize         shapesize( m_Size );

        shapesize.x += clearance * 2;
        shapesize.y += clearance * 2;
        bool doChamfer = GetShape() == PAD_SHAPE_CHAMFERED_RECT;

        TransformRoundChamferedRectToPolygon( outline, padShapePos, shapesize, angle,
                rounding_radius, doChamfer ? GetChamferRectRatio() : 0.0,
                doChamfer ? GetChamferPositions() : 0, aError );

        aCornerBuffer.Append( outline );
    }
        break;

    case PAD_SHAPE_CUSTOM:
    {
        int    numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ), 6 );
        double correction = GetCircletoPolyCorrectionFactor( numSegs );
        int    clearance = KiROUND( aClearanceValue * correction );
        SHAPE_POLY_SET outline;     // Will contain the corners in board coordinates
        outline.Append( m_customShapeAsPolygon );
        CustomShapeAsPolygonToBoardPosition( &outline, GetPosition(), GetOrientation() );
        outline.Simplify( SHAPE_POLY_SET::PM_FAST );
        outline.Inflate( clearance, numSegs );
        outline.Fracture( SHAPE_POLY_SET::PM_FAST );
        aCornerBuffer.Append( outline );
    }
        break;
    }
}
void DRAWSEGMENT::TransformShapeWithClearanceToPolygon(
        SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aError, bool ignoreLineWidth ) const
{
    // The full width of the lines to create:
    int linewidth = ignoreLineWidth ? 0 : m_Width;

    linewidth += 2 * aClearanceValue;

    // Creating a reliable clearance shape for circles and arcs is not so easy, due to
    // the error created by segment approximation.
    // for a circle this is not so hard: create a polygon from a circle slightly bigger:
    // thickness = linewidth + s_error_max, and radius = initial radius + s_error_max/2
    // giving a shape with a suitable internal radius and external radius
    // For an arc this is more tricky: TODO

    switch( m_Shape )
    {
    case S_CIRCLE:
        TransformRingToPolygon(
                aCornerBuffer, GetCenter(), GetRadius(), aError, linewidth );
        break;

    case S_ARC:
        TransformArcToPolygon(
                aCornerBuffer, GetCenter(), GetArcStart(), m_Angle, aError, linewidth );
        break;

    case S_SEGMENT:
        TransformOvalClearanceToPolygon(
                aCornerBuffer, m_Start, m_End, linewidth, aError );
        break;

    case S_POLYGON:
        if( IsPolyShapeValid() )
        {
            // The polygon is expected to be a simple polygon
            // not self intersecting, no hole.
            MODULE* module = GetParentModule();     // NULL for items not in footprints
            double orientation = module ? module->GetOrientation() : 0.0;
            wxPoint offset;

            if( module )
                offset = module->GetPosition();

            // Build the polygon with the actual position and orientation:
            std::vector< wxPoint> poly;
            poly = BuildPolyPointsList();

            for( unsigned ii = 0; ii < poly.size(); ii++ )
            {
                RotatePoint( &poly[ii], orientation );
                poly[ii] += offset;
            }

            // If the polygon is not filled, treat it as a closed set of lines
            if( !IsPolygonFilled() )
            {
                for( size_t ii = 1; ii < poly.size(); ii++ )
                {
                    TransformOvalClearanceToPolygon( aCornerBuffer, poly[ii - 1], poly[ii],
                            linewidth, aError );
                }

                TransformOvalClearanceToPolygon( aCornerBuffer, poly.back(), poly.front(),
                        linewidth, aError );
                break;
            }

            // Generate polygons for the outline + clearance
            // This code is compatible with a polygon with holes linked to external outline
            // by overlapping segments.

            // Insert the initial polygon:
            aCornerBuffer.NewOutline();

            for( unsigned ii = 0; ii < poly.size(); ii++ )
                aCornerBuffer.Append( poly[ii].x, poly[ii].y );

            if( linewidth )     // Add thick outlines
            {
                wxPoint corner1( poly[poly.size()-1] );

                for( unsigned ii = 0; ii < poly.size(); ii++ )
                {
                    wxPoint corner2( poly[ii] );

                    if( corner2 != corner1 )
                    {
                        TransformRoundedEndsSegmentToPolygon(
                                aCornerBuffer, corner1, corner2, aError, linewidth );
                    }

                    corner1 = corner2;
                }
            }
        }
        break;

    case S_CURVE:       // Bezier curve
        {
            std::vector<wxPoint> ctrlPoints = { m_Start, m_BezierC1, m_BezierC2, m_End };
            BEZIER_POLY converter( ctrlPoints );
            std::vector< wxPoint> poly;
            converter.GetPoly( poly, m_Width );

            for( unsigned ii = 1; ii < poly.size(); ii++ )
            {
                TransformRoundedEndsSegmentToPolygon(
                        aCornerBuffer, poly[ii - 1], poly[ii], aError, linewidth );
            }
        }
        break;

    default:
        break;
    }
}
void TransformOvalClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                wxPoint aStart, wxPoint aEnd, int aWidth,
                                int aCircleToSegmentsCount, double aCorrectionFactor )
{
    // To build the polygonal shape outside the actual shape, we use a bigger
    // radius to build rounded ends.
    // However, the width of the segment is too big.
    // so, later, we will clamp the polygonal shape with the bounding box
    // of the segment.
    int     radius  = aWidth / 2;

    // Note if we want to compensate the radius reduction of a circle due to
    // the segment approx, aCorrectionFactor must be calculated like this:
    // For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2)
    // aCorrectionFactor is 1 /cos( PI/s_CircleToSegmentsCount  )

    radius = radius * aCorrectionFactor;    // make segments outside the circles

    // end point is the coordinate relative to aStart
    wxPoint endp    = aEnd - aStart;
    wxPoint startp  = aStart;
    wxPoint corner;
    SHAPE_POLY_SET polyshape;

    polyshape.NewOutline();

    // normalize the position in order to have endp.x >= 0
    // it makes calculations more easy to understand
    if( endp.x < 0 )
    {
        endp    = aStart - aEnd;
        startp  = aEnd;
    }

    // delta_angle is in radian
    double delta_angle = atan2( (double)endp.y, (double)endp.x );
    int seg_len        = KiROUND( EuclideanNorm( endp ) );

    double delta = 3600.0 / aCircleToSegmentsCount;    // rot angle in 0.1 degree

    // Compute the outlines of the segment, and creates a polygon
    // Note: the polygonal shape is built from the equivalent horizontal
    // segment starting ar 0,0, and ending at seg_len,0

    // add right rounded end:
    for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ )
    {
        corner = wxPoint( 0, radius );
        RotatePoint( &corner, delta*ii );
        corner.x += seg_len;
        polyshape.Append( corner.x, corner.y );
    }

    // Finish arc:
    corner = wxPoint( seg_len, -radius );
    polyshape.Append( corner.x, corner.y );

    // add left rounded end:
    for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ )
    {
        corner = wxPoint( 0, -radius );
        RotatePoint( &corner, delta*ii );
        polyshape.Append( corner.x, corner.y );
    }

    // Finish arc:
    corner = wxPoint( 0, radius );
    polyshape.Append( corner.x, corner.y );

    // Now, clamp the polygonal shape (too big) with the segment bounding box
    // the polygonal shape bbox equivalent to the segment has a too big height,
    // and the right width
    if( aCorrectionFactor > 1.0 )
    {
        SHAPE_POLY_SET bbox;
        bbox.NewOutline();
        // Build the bbox (a horizontal rectangle).
        int halfwidth = aWidth / 2;     // Use the exact segment width for the bbox height
        corner.x = -radius - 2;         // use a bbox width slightly bigger to avoid
                                        // creating useless corner at segment ends
        corner.y = halfwidth;
        bbox.Append( corner.x, corner.y );
        corner.y = -halfwidth;
        bbox.Append( corner.x, corner.y );
        corner.x = radius + seg_len + 2;
        bbox.Append( corner.x, corner.y );
        corner.y = halfwidth;
        bbox.Append( corner.x, corner.y );

        // Now, clamp the shape
        polyshape.BooleanIntersection( bbox, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
        // Note the final polygon is a simple, convex polygon with no hole
        // due to the shape of initial polygons
    }

    // Rotate and move the polygon to its right location
    polyshape.Rotate( delta_angle, VECTOR2I( 0, 0 ) );
    polyshape.Move( startp );

    aCornerBuffer.Append( polyshape);
}
Beispiel #19
0
void ZONE_FILLER::buildUnconnectedThermalStubsPolygonList( SHAPE_POLY_SET& aCornerBuffer,
                                              const ZONE_CONTAINER*       aZone,
                                              const SHAPE_POLY_SET&       aRawFilledArea,
                                              double                aArcCorrection,
                                              double                aRoundPadThermalRotation ) const
{
    SHAPE_LINE_CHAIN spokes;
    BOX2I itemBB;
    VECTOR2I ptTest[4];
    auto zoneBB = aRawFilledArea.BBox();


    int      zone_clearance = aZone->GetZoneClearance();

    int      biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
    biggest_clearance = std::max( biggest_clearance, zone_clearance );
    zoneBB.Inflate( biggest_clearance );

    // half size of the pen used to draw/plot zones outlines
    int pen_radius = aZone->GetMinThickness() / 2;

    for( auto module : m_board->Modules() )
    {
        for( auto pad : module->Pads() )
        {
            // Rejects non-standard pads with tht-only thermal reliefs
            if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL
             && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
                continue;

            if( aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL
             && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL )
                continue;

            if( !pad->IsOnLayer( aZone->GetLayer() ) )
                continue;

            if( pad->GetNetCode() != aZone->GetNetCode() )
                continue;

            // Calculate thermal bridge half width
            int thermalBridgeWidth = aZone->GetThermalReliefCopperBridge( pad )
                                     - aZone->GetMinThickness();
            if( thermalBridgeWidth <= 0 )
                continue;

            // we need the thermal bridge half width
            // with a small extra size to be sure we create a stub
            // slightly larger than the actual stub
            thermalBridgeWidth = ( thermalBridgeWidth + 4 ) / 2;

            int thermalReliefGap = aZone->GetThermalReliefGap( pad );

            itemBB = pad->GetBoundingBox();
            itemBB.Inflate( thermalReliefGap );
            if( !( itemBB.Intersects( zoneBB ) ) )
                continue;

            // Thermal bridges are like a segment from a starting point inside the pad
            // to an ending point outside the pad

            // calculate the ending point of the thermal pad, outside the pad
            VECTOR2I endpoint;
            endpoint.x = ( pad->GetSize().x / 2 ) + thermalReliefGap;
            endpoint.y = ( pad->GetSize().y / 2 ) + thermalReliefGap;

            // Calculate the starting point of the thermal stub
            // inside the pad
            VECTOR2I startpoint;
            int copperThickness = aZone->GetThermalReliefCopperBridge( pad )
                                  - aZone->GetMinThickness();

            if( copperThickness < 0 )
                copperThickness = 0;

            // Leave a small extra size to the copper area inside to pad
            copperThickness += KiROUND( IU_PER_MM * 0.04 );

            startpoint.x = std::min( pad->GetSize().x, copperThickness );
            startpoint.y = std::min( pad->GetSize().y, copperThickness );

            startpoint.x /= 2;
            startpoint.y /= 2;

            // This is a CIRCLE pad tweak
            // for circle pads, the thermal stubs orientation is 45 deg
            double fAngle = pad->GetOrientation();
            if( pad->GetShape() == PAD_SHAPE_CIRCLE )
            {
                endpoint.x     = KiROUND( endpoint.x * aArcCorrection );
                endpoint.y     = endpoint.x;
                fAngle = aRoundPadThermalRotation;
            }

            // contour line width has to be taken into calculation to avoid "thermal stub bleed"
            endpoint.x += pen_radius;
            endpoint.y += pen_radius;
            // compute north, south, west and east points for zone connection.
            ptTest[0] = VECTOR2I( 0, endpoint.y );       // lower point
            ptTest[1] = VECTOR2I( 0, -endpoint.y );      // upper point
            ptTest[2] = VECTOR2I( endpoint.x, 0 );       // right point
            ptTest[3] = VECTOR2I( -endpoint.x, 0 );      // left point

            // Test all sides
            for( int i = 0; i < 4; i++ )
            {
                // rotate point
                RotatePoint( ptTest[i], fAngle );

                // translate point
                ptTest[i] += pad->ShapePos();

                if( aRawFilledArea.Contains( ptTest[i] ) )
                    continue;

                spokes.Clear();

                // polygons are rectangles with width of copper bridge value
                switch( i )
                {
                case 0:       // lower stub
                    spokes.Append( -thermalBridgeWidth, endpoint.y );
                    spokes.Append( +thermalBridgeWidth, endpoint.y );
                    spokes.Append( +thermalBridgeWidth, startpoint.y );
                    spokes.Append( -thermalBridgeWidth, startpoint.y );
                    break;

                case 1:       // upper stub
                    spokes.Append( -thermalBridgeWidth, -endpoint.y );
                    spokes.Append( +thermalBridgeWidth, -endpoint.y );
                    spokes.Append( +thermalBridgeWidth, -startpoint.y );
                    spokes.Append( -thermalBridgeWidth, -startpoint.y );
                    break;

                case 2:       // right stub
                    spokes.Append( endpoint.x, -thermalBridgeWidth );
                    spokes.Append( endpoint.x, thermalBridgeWidth );
                    spokes.Append( +startpoint.x, thermalBridgeWidth );
                    spokes.Append( +startpoint.x, -thermalBridgeWidth );
                    break;

                case 3:       // left stub
                    spokes.Append( -endpoint.x, -thermalBridgeWidth );
                    spokes.Append( -endpoint.x, thermalBridgeWidth );
                    spokes.Append( -startpoint.x, thermalBridgeWidth );
                    spokes.Append( -startpoint.x, -thermalBridgeWidth );
                    break;
                }

                aCornerBuffer.NewOutline();

                // add computed polygon to list
                for( int ic = 0; ic < spokes.PointCount(); ic++ )
                {
                    auto cpos = spokes.CPoint( ic );
                    RotatePoint( cpos, fAngle );                               // Rotate according to module orientation
                    cpos += pad->ShapePos();                              // Shift origin to position
                    aCornerBuffer.Append( cpos );
                }
            }
        }
    }
}
/**
 * Function TransformShapeWithClearanceToPolygon
 * Convert the track shape to a closed polygon
 * Used in filling zones calculations
 * Circles and arcs are approximated by segments
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aClearanceValue = the clearance around the pad
 * @param aCircleToSegmentsCount = the number of segments to approximate a circle
 * @param aCorrectionFactor = the correction to apply to circles radius to keep
 * clearance when the circle is approxiamted by segment bigger or equal
 * to the real clearance value (usually near from 1.0)
 */
void DRAWSEGMENT::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                                        int             aClearanceValue,
                                                        int             aCircleToSegmentsCount,
                                                        double          aCorrectionFactor ) const
{
    // The full width of the lines to create:
    int linewidth = m_Width + (2 * aClearanceValue);

    switch( m_Shape )
    {
    case S_CIRCLE:
        TransformRingToPolygon( aCornerBuffer, GetCenter(), GetRadius(),
                                aCircleToSegmentsCount, linewidth ) ;
        break;

    case S_ARC:
        TransformArcToPolygon( aCornerBuffer, GetCenter(),
                               GetArcStart(), m_Angle,
                               aCircleToSegmentsCount, linewidth );
        break;

    case S_SEGMENT:
        TransformRoundedEndsSegmentToPolygon( aCornerBuffer, m_Start, m_End,
                                              aCircleToSegmentsCount, linewidth );
        break;

    case S_POLYGON:
        if ( GetPolyPoints().size() < 2 )
            break;      // Malformed polygon.
        {
        // The polygon is expected to be a simple polygon
        // not self intersecting, no hole.
        MODULE* module = GetParentModule();     // NULL for items not in footprints
        double orientation = module ? module->GetOrientation() : 0.0;

        // Build the polygon with the actual position and orientation:
        std::vector< wxPoint> poly;
        poly = GetPolyPoints();

        for( unsigned ii = 0; ii < poly.size(); ii++ )
        {
            RotatePoint( &poly[ii], orientation );
            poly[ii] += GetPosition();
        }

        // Generate polygons for the outline + clearance
        // This code is compatible with a polygon with holes linked to external outline
        // by overlapping segments.

        // Insert the initial polygon:
        aCornerBuffer.NewOutline();

        for( unsigned ii = 0; ii < poly.size(); ii++ )
            aCornerBuffer.Append( poly[ii].x, poly[ii].y );

        if( linewidth )     // Add thick outlines
        {
            CPolyPt corner1( poly[poly.size()-1] );

            for( unsigned ii = 0; ii < poly.size(); ii++ )
            {
                CPolyPt corner2( poly[ii] );

                if( corner2 != corner1 )
                {
                    TransformRoundedEndsSegmentToPolygon( aCornerBuffer,
                            corner1, corner2, aCircleToSegmentsCount, linewidth );
                }

                corner1 = corner2;
            }
        }
        }
        break;

    case S_CURVE:       // Bezier curve (TODO: not yet in use)
        break;

    default:
        break;
    }
}
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 PCB_PAINTER::draw( const D_PAD* aPad, int aLayer )
{
    PAD_SHAPE_T shape;
    double m, n;
    double orientation = aPad->GetOrientation();

    // Draw description layer
    if( IsNetnameLayer( aLayer ) )
    {
        VECTOR2D position( aPad->ShapePos() );

        // Is anything that we can display enabled?
        if( m_pcbSettings.m_netNamesOnPads || m_pcbSettings.m_padNumbers )
        {
            bool displayNetname = ( m_pcbSettings.m_netNamesOnPads && !aPad->GetNetname().empty() );
            VECTOR2D padsize = VECTOR2D( aPad->GetSize() );
            double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
            double size = padsize.y;

            // Keep the size ratio for the font, but make it smaller
            if( padsize.x < padsize.y )
            {
                orientation += 900.0;
                size = padsize.x;
                std::swap( padsize.x, padsize.y );
            }
            else if( padsize.x == padsize.y )
            {
                // If the text is displayed on a symmetrical pad, do not rotate it
                orientation = 0.0;
            }

            // Font size limits
            if( size > maxSize )
                size = maxSize;

            m_gal->Save();
            m_gal->Translate( position );

            // do not display descriptions upside down
            NORMALIZE_ANGLE_90( orientation );
            m_gal->Rotate( DECIDEG2RAD( -orientation ) );

            // Default font settings
            m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
            m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
            m_gal->SetFontBold( false );
            m_gal->SetFontItalic( false );
            m_gal->SetTextMirrored( false );
            m_gal->SetStrokeColor( m_pcbSettings.GetColor( NULL, aLayer ) );
            m_gal->SetIsStroke( true );
            m_gal->SetIsFill( false );

            // Set the text position to the pad shape position (the pad position is not the best place)
            VECTOR2D textpos( 0.0, 0.0 );

            // Divide the space, to display both pad numbers and netnames
            // and set the Y text position to display 2 lines
            if( displayNetname && m_pcbSettings.m_padNumbers )
            {
                size = size / 2.0;
                textpos.y = size / 2.0;
            }

            if( displayNetname )
            {
                // calculate the size of net name text:
                double tsize = 1.5 * padsize.x / aPad->GetShortNetname().Length();
                tsize = std::min( tsize, size );
                // Use a smaller text size to handle interline, pen size..
                tsize *= 0.7;
                VECTOR2D namesize( tsize, tsize );

                m_gal->SetGlyphSize( namesize );
                m_gal->SetLineWidth( namesize.x / 12.0 );
                m_gal->BitmapText( aPad->GetShortNetname(), textpos, 0.0 );
            }

            if( m_pcbSettings.m_padNumbers )
            {
                const wxString& padName = aPad->GetName();
                textpos.y = -textpos.y;
                double tsize = 1.5 * padsize.x / padName.Length();
                tsize = std::min( tsize, size );
                // Use a smaller text size to handle interline, pen size..
                tsize *= 0.7;
                tsize = std::min( tsize, size );
                VECTOR2D numsize( tsize, tsize );

                m_gal->SetGlyphSize( numsize );
                m_gal->SetLineWidth( numsize.x / 12.0 );
                m_gal->BitmapText( padName, textpos, 0.0 );
            }

            m_gal->Restore();
        }
        return;
    }

    // Pad drawing
    COLOR4D color;

    // Pad holes color is type specific
    if( aLayer == LAYER_PADS_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES )
    {
        // Hole color is the background color for plated holes, but a specific color
        // for not plated holes (LAYER_NON_PLATEDHOLES color layer )
        if( aPad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED )
            color = m_pcbSettings.GetColor( nullptr, LAYER_NON_PLATEDHOLES );
        // Don't let pads that *should* be NPTH get lost
        else if( aPad->PadShouldBeNPTH() )
            color = m_pcbSettings.GetColor( aPad, aLayer );
        else
            color = m_pcbSettings.GetBackgroundColor();
    }
    else
    {
        color = m_pcbSettings.GetColor( aPad, aLayer );
    }

    VECTOR2D size;

    if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
    {
        // Outline mode
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetStrokeColor( color );
    }
    else
    {
        // Filled mode
        m_gal->SetIsFill( true );
        m_gal->SetIsStroke( false );
        m_gal->SetFillColor( color );
    }

    m_gal->Save();
    m_gal->Translate( VECTOR2D( aPad->GetPosition() ) );
    m_gal->Rotate( -aPad->GetOrientationRadians() );

    int custom_margin = 0;     // a clearance/margin for custom shape, for solder paste/mask

    // Choose drawing settings depending on if we are drawing a pad itself or a hole
    if( aLayer == LAYER_PADS_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES )
    {
        // Drawing hole: has same shape as PAD_CIRCLE or PAD_OVAL
        size  = getDrillSize( aPad ) / 2.0;
        shape = getDrillShape( aPad ) == PAD_DRILL_SHAPE_OBLONG ? PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE;
    }
    else if( aLayer == F_Mask || aLayer == B_Mask )
    {
        // Drawing soldermask
        int soldermaskMargin = aPad->GetSolderMaskMargin();
        custom_margin = soldermaskMargin;

        m_gal->Translate( VECTOR2D( aPad->GetOffset() ) );
        size  = VECTOR2D( aPad->GetSize().x / 2.0 + soldermaskMargin,
                          aPad->GetSize().y / 2.0 + soldermaskMargin );
        shape = aPad->GetShape();
    }
    else if( aLayer == F_Paste || aLayer == B_Paste )
    {
        // Drawing solderpaste
        wxSize solderpasteMargin = aPad->GetSolderPasteMargin();
        // try to find a clearance which can be used for custom shapes
        custom_margin = solderpasteMargin.x;

        m_gal->Translate( VECTOR2D( aPad->GetOffset() ) );
        size  = VECTOR2D( aPad->GetSize().x / 2.0 + solderpasteMargin.x,
                          aPad->GetSize().y / 2.0 + solderpasteMargin.y );
        shape = aPad->GetShape();
    }
    else
    {
        // Drawing every kind of pad
        m_gal->Translate( VECTOR2D( aPad->GetOffset() ) );
        size  = VECTOR2D( aPad->GetSize() ) / 2.0;
        shape = aPad->GetShape();
    }

    switch( shape )
    {
    case PAD_SHAPE_OVAL:
        if( size.y >= size.x )
        {
            m = ( size.y - size.x );
            n = size.x;

            m_gal->DrawArc( VECTOR2D( 0, -m ), n, -M_PI, 0 );
            m_gal->DrawArc( VECTOR2D( 0, m ),  n, M_PI, 0 );

            if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
            {
                m_gal->DrawLine( VECTOR2D( -n, -m ), VECTOR2D( -n, m ) );
                m_gal->DrawLine( VECTOR2D( n, -m ),  VECTOR2D( n, m ) );
            }
            else
            {
                m_gal->DrawRectangle( VECTOR2D( -n, -m ), VECTOR2D( n, m ) );
            }
        }
        else
        {
            m = ( size.x - size.y );
            n = size.y;
            m_gal->DrawArc( VECTOR2D( -m, 0 ), n, M_PI / 2, 3 * M_PI / 2 );
            m_gal->DrawArc( VECTOR2D( m, 0 ),  n, M_PI / 2, -M_PI / 2 );

            if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
            {
                m_gal->DrawLine( VECTOR2D( -m, -n ), VECTOR2D( m, -n ) );
                m_gal->DrawLine( VECTOR2D( -m, n ),  VECTOR2D( m, n ) );
            }
            else
            {
                m_gal->DrawRectangle( VECTOR2D( -m, -n ), VECTOR2D( m, n ) );
            }
        }
        break;

    case PAD_SHAPE_RECT:
        m_gal->DrawRectangle( VECTOR2D( -size.x, -size.y ), VECTOR2D( size.x, size.y ) );
        break;

    case PAD_SHAPE_ROUNDRECT:
    {
        SHAPE_POLY_SET polySet;
        wxSize prsize( size.x * 2, size.y * 2 );
        const int segmentToCircleCount = 64;
        const int corner_radius = aPad->GetRoundRectCornerRadius( prsize );
        TransformRoundRectToPolygon( polySet, wxPoint( 0, 0 ), prsize,
                0.0, corner_radius, segmentToCircleCount );
        m_gal->DrawPolygon( polySet );
        break;
    }

    case PAD_SHAPE_CUSTOM:
    {   // Draw the complex custom shape

        // Use solder[Paste/Mask]size or pad size to build pad shape
        // however, solder[Paste/Mask] size has no actual meaning for a
        // custom shape, because it is a set of basic shapes
        // We use the custom_margin (good for solder mask, but approximative
        // for solder paste).
        if( custom_margin )
        {
            SHAPE_POLY_SET outline;
            outline.Append( aPad->GetCustomShapeAsPolygon() );
            const int segmentToCircleCount = ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF;
            outline.Inflate( custom_margin, segmentToCircleCount );
            m_gal->DrawPolygon( outline );
        }
        else
        {
            // Draw the polygon: only one polygon is expected
            // However we provide a multi polygon shape drawing
            // ( for the future or  to show even an incorrect shape
            m_gal->DrawPolygon( aPad->GetCustomShapeAsPolygon() );
        }
    }
        break;

    case PAD_SHAPE_TRAPEZOID:
    {
        std::deque<VECTOR2D> pointList;
        wxPoint corners[4];

        VECTOR2D padSize = VECTOR2D( aPad->GetSize().x, aPad->GetSize().y ) / 2;
        VECTOR2D deltaPadSize = size - padSize; // = solder[Paste/Mask]Margin or 0

        aPad->BuildPadPolygon( corners, wxSize( deltaPadSize.x, deltaPadSize.y ), 0.0 );
        SHAPE_POLY_SET polySet;
        polySet.NewOutline();
        polySet.Append( VECTOR2I( corners[0] ) );
        polySet.Append( VECTOR2I( corners[1] ) );
        polySet.Append( VECTOR2I( corners[2] ) );
        polySet.Append( VECTOR2I( corners[3] ) );

        m_gal->DrawPolygon( polySet );
    }
    break;

    case PAD_SHAPE_CIRCLE:
        m_gal->DrawCircle( VECTOR2D( 0.0, 0.0 ), size.x );
        break;
    }

    m_gal->Restore();

    // Clearance lines
    // It has to be called after GAL::Restore() as TransformShapeWithClearanceToPolygon()
    // returns already transformed coordinates
    constexpr int clearanceFlags = /*PCB_RENDER_SETTINGS::CL_EXISTING |*/ PCB_RENDER_SETTINGS::CL_PADS;

    if( ( m_pcbSettings.m_clearance & clearanceFlags ) == clearanceFlags
            && ( aLayer == LAYER_PAD_FR
                || aLayer == LAYER_PAD_BK
                || aLayer == LAYER_PADS_TH ) )
    {
        SHAPE_POLY_SET polySet;
        constexpr int SEGCOUNT = 64;
        aPad->TransformShapeWithClearanceToPolygon( polySet, aPad->GetClearance(), SEGCOUNT, 1.0 );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetIsStroke( true );
        m_gal->SetIsFill( false );
        m_gal->SetStrokeColor( color );
        m_gal->DrawPolygon( polySet );
    }
}
/**
 * Function TransformRoundedEndsSegmentToPolygon
 * convert a segment with rounded ends to a polygon
 * Convert arcs to multiple straight lines
 * @param aCornerBuffer = a buffer to store the polygon
 * @param aStart = the segment start point coordinate
 * @param aEnd = the segment end point coordinate
 * @param aCircleToSegmentsCount = the number of segments to approximate a circle
 * @param aWidth = the segment width
 * Note: the polygon is inside the arc ends, so if you want to have the polygon
 * outside the circle, you should give aStart and aEnd calculated with a correction factor
 */
void TransformRoundedEndsSegmentToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                           wxPoint aStart, wxPoint aEnd,
                                           int aCircleToSegmentsCount,
                                           int aWidth )
{
    int     radius  = aWidth / 2;
    wxPoint endp    = aEnd - aStart; // end point coordinate for the same segment starting at (0,0)
    wxPoint startp  = aStart;
    wxPoint corner;
    VECTOR2I polypoint;

    aCornerBuffer.NewOutline();

    // normalize the position in order to have endp.x >= 0;
    if( endp.x < 0 )
    {
        endp    = aStart - aEnd;
        startp  = aEnd;
    }

    double delta_angle = ArcTangente( endp.y, endp.x ); // delta_angle is in 0.1 degrees
    int seg_len        = KiROUND( EuclideanNorm( endp ) );

    int delta = 3600 / aCircleToSegmentsCount;    // rot angle in 0.1 degree

    // Compute the outlines of the segment, and creates a polygon
    // add right rounded end:
    for( int ii = 0; ii < 1800; ii += delta )
    {
        corner = wxPoint( 0, radius );
        RotatePoint( &corner, ii );
        corner.x += seg_len;
        RotatePoint( &corner, -delta_angle );
        corner += startp;
        polypoint.x = corner.x;
        polypoint.y = corner.y;
        aCornerBuffer.Append( polypoint.x, polypoint.y );
    }

    // Finish arc:
    corner = wxPoint( seg_len, -radius );
    RotatePoint( &corner, -delta_angle );
    corner += startp;
    polypoint.x = corner.x;
    polypoint.y = corner.y;
    aCornerBuffer.Append( polypoint.x, polypoint.y );

    // add left rounded end:
    for( int ii = 0; ii < 1800; ii += delta )
    {
        corner = wxPoint( 0, -radius );
        RotatePoint( &corner, ii );
        RotatePoint( &corner, -delta_angle );
        corner += startp;
        polypoint.x = corner.x;
        polypoint.y = corner.y;
        aCornerBuffer.Append( polypoint.x, polypoint.y );
    }

    // Finish arc:
    corner = wxPoint( 0, radius );
    RotatePoint( &corner, -delta_angle );
    corner += startp;
    polypoint.x = corner.x;
    polypoint.y = corner.y;
    aCornerBuffer.Append( polypoint.x, polypoint.y );
}
bool DRC_COURTYARD_OVERLAP::RunDRC( BOARD& aBoard ) const
{
    wxLogTrace( DRC_COURTYARD_TRACE, "Running DRC: Courtyard" );

    // Detects missing (or malformed) footprint courtyard,
    // and for footprint with courtyard, courtyards overlap.
    wxString msg;
    bool     success = true;

    const DRC_MARKER_FACTORY& marker_factory = GetMarkerFactory();

    // Update courtyard polygons, and test for missing courtyard definition:
    for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() )
    {
        wxPoint pos = footprint->GetPosition();
        bool    is_ok = footprint->BuildPolyCourtyard();

        if( !is_ok && aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards )
        {
            auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker(
                    pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) );
            HandleMarker( std::move( marker ) );
            success = false;
        }

        if( !aBoard.GetDesignSettings().m_RequireCourtyards )
            continue;

        if( footprint->GetPolyCourtyardFront().OutlineCount() == 0
                && footprint->GetPolyCourtyardBack().OutlineCount() == 0 && is_ok )
        {
            auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker(
                    pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) );
            HandleMarker( std::move( marker ) );
            success = false;
        }
    }

    if( !aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards )
        return success;

    wxLogTrace( DRC_COURTYARD_TRACE, "Checking for courtyard overlap" );

    // Now test for overlapping on top layer:
    SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint

    for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() )
    {
        if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 )
            continue; // No courtyard defined

        for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() )
        {
            if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 )
                continue; // No courtyard defined

            courtyard.RemoveAllContours();
            courtyard.Append( footprint->GetPolyCourtyardFront() );

            // Build the common area between footprint and the candidate:
            courtyard.BooleanIntersection(
                    candidate->GetPolyCourtyardFront(), SHAPE_POLY_SET::PM_FAST );

            // If no overlap, courtyard is empty (no common area).
            // Therefore if a common polygon exists, this is a DRC error
            if( courtyard.OutlineCount() )
            {
                //Overlap between footprint and candidate
                VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 );
                auto      marker = std::unique_ptr<MARKER_PCB>(
                        marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate,
                                DRCE_OVERLAPPING_FOOTPRINTS ) );
                HandleMarker( std::move( marker ) );
                success = false;
            }
        }
    }

    // Test for overlapping on bottom layer:
    for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() )
    {
        if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 )
            continue; // No courtyard defined

        for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() )
        {
            if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 )
                continue; // No courtyard defined

            courtyard.RemoveAllContours();
            courtyard.Append( footprint->GetPolyCourtyardBack() );

            // Build the common area between footprint and the candidate:
            courtyard.BooleanIntersection(
                    candidate->GetPolyCourtyardBack(), SHAPE_POLY_SET::PM_FAST );

            // If no overlap, courtyard is empty (no common area).
            // Therefore if a common polygon exists, this is a DRC error
            if( courtyard.OutlineCount() )
            {
                //Overlap between footprint and candidate
                VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 );
                auto      marker = std::unique_ptr<MARKER_PCB>(
                        marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate,
                                DRCE_OVERLAPPING_FOOTPRINTS ) );
                HandleMarker( std::move( marker ) );
                success = false;
            }
        }
    }

    return success;
}
/**
 * DXF polygon: doesn't fill it but at least it close the filled ones
 * DXF does not know thick outline.
 * It does not know thhick segments, therefore filled polygons with thick outline
 * are converted to inflated polygon by aWidth/2
 */
void DXF_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList,
                            FILL_T aFill, int aWidth)
{
    if( aCornerList.size() <= 1 )
        return;

    unsigned last = aCornerList.size() - 1;

    // Plot outlines with lines (thickness = 0) to define the polygon
    if( aWidth <= 0  )
    {
        MoveTo( aCornerList[0] );

        for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
            LineTo( aCornerList[ii] );

        // Close polygon if 'fill' requested
        if( aFill )
        {
            if( aCornerList[last] != aCornerList[0] )
                LineTo( aCornerList[0] );
        }

        PenFinish();

        return;
    }


    // if the polygon outline has thickness, and is not filled
    // (i.e. is a polyline) plot outlines with thick segments
    if( aWidth > 0 && !aFill )
    {
        MoveTo( aCornerList[0] );

        for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
            ThickSegment( aCornerList[ii-1], aCornerList[ii],
                          aWidth, FILLED );

        return;
    }

    // The polygon outline has thickness, and is filled
    // Build and plot the polygon which contains the initial
    // polygon and its thick outline
    SHAPE_POLY_SET  bufferOutline;
    SHAPE_POLY_SET  bufferPolybase;
    const int circleToSegmentsCount = 16;

    bufferPolybase.NewOutline();

    // enter outline as polygon:
    for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
    {
        TransformRoundedEndsSegmentToPolygon( bufferOutline,
            aCornerList[ii-1], aCornerList[ii], circleToSegmentsCount, aWidth );
    }

    // enter the initial polygon:
    for( unsigned ii = 0; ii < aCornerList.size(); ii++ )
    {
        bufferPolybase.Append( aCornerList[ii] );
    }

    // Merge polygons to build the polygon which contains the initial
    // polygon and its thick outline

    bufferPolybase.BooleanAdd( bufferOutline ); // create the outline which contains thick outline
    bufferPolybase.Fracture();


    if( bufferPolybase.OutlineCount() < 1 )      // should not happen
        return;

    const SHAPE_LINE_CHAIN& path = bufferPolybase.COutline( 0 );

    if( path.PointCount() < 2 )           // should not happen
        return;

    // Now, output the final polygon to DXF file:
    last = path.PointCount() - 1;
	  VECTOR2I point = path.CPoint( 0 );

    wxPoint startPoint( point.x, point.y );
    MoveTo( startPoint );

    for( int ii = 1; ii < path.PointCount(); ii++ )
    {
        point = path.CPoint( ii );
        LineTo( wxPoint( point.x, point.y ) );
    }

    // Close polygon, if needed
    point = path.CPoint( last );
    wxPoint endPoint( point.x, point.y );

    if( endPoint != startPoint )
        LineTo( startPoint );

    PenFinish();
}
/* Function TransformShapeWithClearanceToPolygon
 * Convert the pad shape to a closed polygon
 * Used in filling zones calculations and 3D view generation
 * Circles and arcs are approximated by segments
 * aCornerBuffer = a SHAPE_POLY_SET to store the polygon corners
 * aClearanceValue = the clearance around the pad
 * aCircleToSegmentsCount = the number of segments to approximate a circle
 * aCorrectionFactor = the correction to apply to circles radius to keep
 * clearance when the circle is approximated by segment bigger or equal
 * to the real clearance value (usually near from 1.0)
 */
void D_PAD:: TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                                   int             aClearanceValue,
                                                   int             aCircleToSegmentsCount,
                                                   double          aCorrectionFactor ) const
{
    double  angle = m_Orient;
    int     dx = (m_Size.x / 2) + aClearanceValue;
    int     dy = (m_Size.y / 2) + aClearanceValue;

    wxPoint PadShapePos = ShapePos();               /* Note: for pad having a shape offset,
                                                     * the pad position is NOT the shape position */

    switch( GetShape() )
    {
    case PAD_SHAPE_CIRCLE:
        dx = KiROUND( dx * aCorrectionFactor );
        TransformCircleToPolygon( aCornerBuffer, PadShapePos, dx,
                                  aCircleToSegmentsCount );
        break;

    case PAD_SHAPE_OVAL:
        // An oval pad has the same shape as a segment with rounded ends
        {
        int width;
        wxPoint shape_offset;
        if( dy > dx )   // Oval pad X/Y ratio for choosing translation axis
        {
            dy = KiROUND( dy * aCorrectionFactor );
            shape_offset.y = dy - dx;
            width = dx * 2;
        }
        else    //if( dy <= dx )
        {
            dx = KiROUND( dx * aCorrectionFactor );
            shape_offset.x = dy - dx;
            width = dy * 2;
        }

        RotatePoint( &shape_offset, angle );
        wxPoint start = PadShapePos - shape_offset;
        wxPoint end = PadShapePos + shape_offset;
        TransformRoundedEndsSegmentToPolygon( aCornerBuffer, start, end,
                                              aCircleToSegmentsCount, width );
        }
        break;

    case PAD_SHAPE_TRAPEZOID:
    case PAD_SHAPE_RECT:
    {
        wxPoint corners[4];
        BuildPadPolygon( corners, wxSize( 0, 0 ), angle );

        SHAPE_POLY_SET outline;

        outline.NewOutline();

        for( int ii = 0; ii < 4; ii++ )
        {
            corners[ii] += PadShapePos;
            outline.Append( corners[ii].x, corners[ii].y );
        }

        double rounding_radius = aClearanceValue * aCorrectionFactor;

        outline.Inflate( (int) rounding_radius, aCircleToSegmentsCount );

        aCornerBuffer.Append( outline );
    }
        break;
    }
}
bool D_PAD::buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError )

{
    SHAPE_POLY_SET aux_polyset;

    for( unsigned cnt = 0; cnt < m_basicShapes.size(); ++cnt )
    {
        const PAD_CS_PRIMITIVE& bshape = m_basicShapes[cnt];

        switch( bshape.m_Shape )
        {
        case S_CURVE:
        {
            std::vector<wxPoint> ctrlPoints = { bshape.m_Start, bshape.m_Ctrl1, bshape.m_Ctrl2, bshape.m_End };
            BEZIER_POLY converter( ctrlPoints );
            std::vector< wxPoint> poly;
            converter.GetPoly( poly, bshape.m_Thickness );

            for( unsigned ii = 1; ii < poly.size(); ii++ )
            {
                TransformRoundedEndsSegmentToPolygon(
                        aux_polyset, poly[ii - 1], poly[ii], aError, bshape.m_Thickness );
            }
            break;
        }

        case S_SEGMENT:         // usual segment : line with rounded ends
        {
            TransformRoundedEndsSegmentToPolygon(
                    aux_polyset, bshape.m_Start, bshape.m_End, aError, bshape.m_Thickness );
            break;
        }

        case S_ARC:             // Arc with rounded ends
        {
            TransformArcToPolygon( aux_polyset, bshape.m_Start, bshape.m_End, bshape.m_ArcAngle,
                    aError, bshape.m_Thickness );
            break;
        }

        case S_CIRCLE:          //  ring or circle
        {
            if( bshape.m_Thickness )    // ring
                TransformRingToPolygon(
                        aux_polyset, bshape.m_Start, bshape.m_Radius, aError, bshape.m_Thickness );
            else                // Filled circle
                TransformCircleToPolygon( aux_polyset, bshape.m_Start, bshape.m_Radius, aError );
            break;
        }

        case S_POLYGON:         // polygon
            if( bshape.m_Poly.size() < 2 )
                break;      // Malformed polygon.

            {
            // Insert the polygon:
            const std::vector< wxPoint>& poly = bshape.m_Poly;
            aux_polyset.NewOutline();

            if( bshape.m_Thickness )
            {
                SHAPE_POLY_SET polyset;
                polyset.NewOutline();

                for( unsigned ii = 0; ii < poly.size(); ii++ )
                {
                    polyset.Append( poly[ii].x, poly[ii].y );
                }

                int numSegs = std::max(
                        GetArcToSegmentCount( bshape.m_Thickness / 2, aError, 360.0 ), 6 );
                polyset.Inflate( bshape.m_Thickness / 2, numSegs );

                aux_polyset.Append( polyset );
            }

            else
                for( unsigned ii = 0; ii < poly.size(); ii++ )
                    aux_polyset.Append( poly[ii].x, poly[ii].y );
            }
            break;

        default:
            break;
        }
    }

    aux_polyset.Simplify( SHAPE_POLY_SET::PM_FAST );

    // Merge all polygons with the initial pad anchor shape
    if( aux_polyset.OutlineCount() )
    {
        aMergedPolygon->BooleanAdd( aux_polyset, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
        aMergedPolygon->Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
    }

    return aMergedPolygon->OutlineCount() <= 1;
}
Beispiel #28
0
void ZONE_FILLER::buildZoneFeatureHoleList( const ZONE_CONTAINER* aZone,
        SHAPE_POLY_SET& aFeatures ) const
{
    int segsPerCircle;
    double correctionFactor;

    // Set the number of segments in arc approximations
    if( aZone->GetArcSegmentCount() == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF  )
        segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF;
    else
        segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF;

    /* calculates the coeff to compensate radius reduction of holes clearance
     * due to the segment approx.
     * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2)
     * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount  )
     */
    correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle );

    aFeatures.RemoveAllContours();

    int outline_half_thickness = aZone->GetMinThickness() / 2;

    // When removing holes, the holes must be expanded by outline_half_thickness
    // to take in account the thickness of the zone outlines
    int zone_clearance = aZone->GetClearance() + outline_half_thickness;

    // When holes are created by non copper items (edge cut items), use only
    // the m_ZoneClearance parameter (zone clearance with no netclass clearance)
    int zone_to_edgecut_clearance = aZone->GetZoneClearance() + outline_half_thickness;

    /* store holes (i.e. tracks and pads areas as polygons outlines)
     * in a polygon list
     */

    /* items ouside the zone bounding box are skipped
     * the bounding box is the zone bounding box + the biggest clearance found in Netclass list
     */
    EDA_RECT    item_boundingbox;
    EDA_RECT    zone_boundingbox = aZone->GetBoundingBox();
    int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
    biggest_clearance = std::max( biggest_clearance, zone_clearance );
    zone_boundingbox.Inflate( biggest_clearance );

    /*
     * First : Add pads. Note: pads having the same net as zone are left in zone.
     * Thermal shapes will be created later if necessary
     */

    /* Use a dummy pad to calculate hole clearance when a pad is not on all copper layers
     * and this pad has a hole
     * This dummy pad has the size and shape of the hole
     * Therefore, this dummy pad is a circle or an oval.
     * A pad must have a parent because some functions expect a non null parent
     * to find the parent board, and some other data
     */
    MODULE  dummymodule( m_board );   // Creates a dummy parent
    D_PAD   dummypad( &dummymodule );

    for( MODULE* module = m_board->m_Modules; module; module = module->Next() )
    {
        D_PAD* nextpad;

        for( D_PAD* pad = module->PadsList(); pad != NULL; pad = nextpad )
        {
            nextpad = pad->Next();      // pad pointer can be modified by next code, so
                                        // calculate the next pad here

            if( !pad->IsOnLayer( aZone->GetLayer() ) )
            {
                /* Test for pads that are on top or bottom only and have a hole.
                 * There are curious pads but they can be used for some components that are
                 * inside the board (in fact inside the hole. Some photo diodes and Leds are
                 * like this)
                 */
                if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
                    continue;

                // Use a dummy pad to calculate a hole shape that have the same dimension as
                // the pad hole
                dummypad.SetSize( pad->GetDrillSize() );
                dummypad.SetOrientation( pad->GetOrientation() );
                dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ?
                        PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE );
                dummypad.SetPosition( pad->GetPosition() );

                pad = &dummypad;
            }

            // Note: netcode <=0 means not connected item
            if( ( pad->GetNetCode() != aZone->GetNetCode() ) || ( pad->GetNetCode() <= 0 ) )
            {
                int item_clearance = pad->GetClearance() + outline_half_thickness;
                item_boundingbox = pad->GetBoundingBox();
                item_boundingbox.Inflate( item_clearance );

                if( item_boundingbox.Intersects( zone_boundingbox ) )
                {
                    int clearance = std::max( zone_clearance, item_clearance );

                    // PAD_SHAPE_CUSTOM can have a specific keepout, to avoid to break the shape
                    if( pad->GetShape() == PAD_SHAPE_CUSTOM
                        && pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
                    {
                        // the pad shape in zone can be its convex hull or
                        // the shape itself
                        SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() );
                        outline.Inflate( KiROUND( clearance * correctionFactor ), segsPerCircle );
                        pad->CustomShapeAsPolygonToBoardPosition( &outline,
                                pad->GetPosition(), pad->GetOrientation() );

                        if( pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
                        {
                            std::vector<wxPoint> convex_hull;
                            BuildConvexHull( convex_hull, outline );

                            aFeatures.NewOutline();

                            for( unsigned ii = 0; ii < convex_hull.size(); ++ii )
                                aFeatures.Append( convex_hull[ii] );
                        }
                        else
                            aFeatures.Append( outline );
                    }
                    else
                        pad->TransformShapeWithClearanceToPolygon( aFeatures,
                                clearance,
                                segsPerCircle,
                                correctionFactor );
                }

                continue;
            }

            // Pads are removed from zone if the setup is PAD_ZONE_CONN_NONE
            // or if they have a custom shape, because a thermal relief will break
            // the shape
            if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_NONE
                || pad->GetShape() == PAD_SHAPE_CUSTOM )
            {
                int gap = zone_clearance;
                int thermalGap = aZone->GetThermalReliefGap( pad );
                gap = std::max( gap, thermalGap );
                item_boundingbox = pad->GetBoundingBox();
                item_boundingbox.Inflate( gap );

                if( item_boundingbox.Intersects( zone_boundingbox ) )
                {
                    // PAD_SHAPE_CUSTOM has a specific keepout, to avoid to break the shape
                    // the pad shape in zone can be its convex hull or the shape itself
                    if( pad->GetShape() == PAD_SHAPE_CUSTOM
                        && pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
                    {
                        // the pad shape in zone can be its convex hull or
                        // the shape itself
                        SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() );
                        outline.Inflate( KiROUND( gap * correctionFactor ), segsPerCircle );
                        pad->CustomShapeAsPolygonToBoardPosition( &outline,
                                pad->GetPosition(), pad->GetOrientation() );

                        std::vector<wxPoint> convex_hull;
                        BuildConvexHull( convex_hull, outline );

                        aFeatures.NewOutline();

                        for( unsigned ii = 0; ii < convex_hull.size(); ++ii )
                            aFeatures.Append( convex_hull[ii] );
                    }
                    else
                        pad->TransformShapeWithClearanceToPolygon( aFeatures,
                                gap, segsPerCircle, correctionFactor );
                }
            }
        }
    }

    /* Add holes (i.e. tracks and vias areas as polygons outlines)
     * in cornerBufferPolysToSubstract
     */
    for( auto track : m_board->Tracks() )
    {
        if( !track->IsOnLayer( aZone->GetLayer() ) )
            continue;

        if( track->GetNetCode() == aZone->GetNetCode()  && ( aZone->GetNetCode() != 0) )
            continue;

        int item_clearance = track->GetClearance() + outline_half_thickness;
        item_boundingbox = track->GetBoundingBox();

        if( item_boundingbox.Intersects( zone_boundingbox ) )
        {
            int clearance = std::max( zone_clearance, item_clearance );
            track->TransformShapeWithClearanceToPolygon( aFeatures,
                    clearance,
                    segsPerCircle,
                    correctionFactor );
        }
    }

    /* Add module edge items that are on copper layers
     * Pcbnew allows these items to be on copper layers in microwave applictions
     * This is a bad thing, but must be handled here, until a better way is found
     */
    for( auto module : m_board->Modules() )
    {
        for( auto item : module->GraphicalItems() )
        {
            if( !item->IsOnLayer( aZone->GetLayer() ) && !item->IsOnLayer( Edge_Cuts ) )
                continue;

            if( item->Type() != PCB_MODULE_EDGE_T )
                continue;

            item_boundingbox = item->GetBoundingBox();

            if( item_boundingbox.Intersects( zone_boundingbox ) )
            {
                int zclearance = zone_clearance;

                if( item->IsOnLayer( Edge_Cuts ) )
                    // use only the m_ZoneClearance, not the clearance using
                    // the netclass value, because we do not have a copper item
                    zclearance = zone_to_edgecut_clearance;

                ( (EDGE_MODULE*) item )->TransformShapeWithClearanceToPolygon(
                        aFeatures, zclearance, segsPerCircle, correctionFactor );
            }
        }
    }

    // Add graphic items (copper texts) and board edges
    // Currently copper texts have no net, so only the zone_clearance
    // is used.
    for( auto item : m_board->Drawings() )
    {
        if( item->GetLayer() != aZone->GetLayer() && item->GetLayer() != Edge_Cuts )
            continue;

        int zclearance = zone_clearance;

        if( item->GetLayer() == Edge_Cuts )
            // use only the m_ZoneClearance, not the clearance using
            // the netclass value, because we do not have a copper item
            zclearance = zone_to_edgecut_clearance;

        switch( item->Type() )
        {
        case PCB_LINE_T:
            ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon(
                    aFeatures,
                    zclearance, segsPerCircle, correctionFactor );
            break;

        case PCB_TEXT_T:
            ( (TEXTE_PCB*) item )->TransformBoundingBoxWithClearanceToPolygon(
                    aFeatures, zclearance );
            break;

        default:
            break;
        }
    }

    // Add zones outlines having an higher priority and keepout
    for( int ii = 0; ii < m_board->GetAreaCount(); ii++ )
    {
        ZONE_CONTAINER* zone = m_board->GetArea( ii );

        // If the zones share no common layers
        if( !aZone->CommonLayerExists( zone->GetLayerSet() ) )
            continue;

        if( !zone->GetIsKeepout() && zone->GetPriority() <= aZone->GetPriority() )
            continue;

        if( zone->GetIsKeepout() && !zone->GetDoNotAllowCopperPour() )
            continue;

        // A highter priority zone or keepout area is found: remove this area
        item_boundingbox = zone->GetBoundingBox();

        if( !item_boundingbox.Intersects( zone_boundingbox ) )
            continue;

        // Add the zone outline area.
        // However if the zone has the same net as the current zone,
        // do not add any clearance.
        // the zone will be connected to the current zone, but filled areas
        // will use different parameters (clearance, thermal shapes )
        bool    same_net = aZone->GetNetCode() == zone->GetNetCode();
        bool    use_net_clearance = true;
        int     min_clearance = zone_clearance;

        // Do not forget to make room to draw the thick outlines
        // of the hole created by the area of the zone to remove
        int holeclearance = zone->GetClearance() + outline_half_thickness;

        // The final clearance is obviously the max value of each zone clearance
        min_clearance = std::max( min_clearance, holeclearance );

        if( zone->GetIsKeepout() || same_net )
        {
            // Just take in account the fact the outline has a thickness, so
            // the actual area to substract is inflated to take in account this fact
            min_clearance = outline_half_thickness;
            use_net_clearance = false;
        }

        zone->TransformOutlinesShapeWithClearanceToPolygon(
                aFeatures, min_clearance, use_net_clearance );
    }

    // Remove thermal symbols
    for( auto module : m_board->Modules() )
    {
        for( auto pad : module->Pads() )
        {
            // Rejects non-standard pads with tht-only thermal reliefs
            if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL
                && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
                continue;

            if( aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL
                && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL )
                continue;

            if( !pad->IsOnLayer( aZone->GetLayer() ) )
                continue;

            if( pad->GetNetCode() != aZone->GetNetCode() )
                continue;

            item_boundingbox = pad->GetBoundingBox();
            int thermalGap = aZone->GetThermalReliefGap( pad );
            item_boundingbox.Inflate( thermalGap, thermalGap );

            if( item_boundingbox.Intersects( zone_boundingbox ) )
            {
                CreateThermalReliefPadPolygon( aFeatures,
                        *pad, thermalGap,
                        aZone->GetThermalReliefCopperBridge( pad ),
                        aZone->GetMinThickness(),
                        segsPerCircle,
                        correctionFactor, s_thermalRot );
            }
        }
    }
}
void EDA_3D_CANVAS::buildTechLayers3DView( REPORTER* aErrorMessages, REPORTER* aActivity )
{
    BOARD* pcb = GetBoard();
    bool useTextures = isRealisticMode() && isEnabled( FL_RENDER_TEXTURES );

    // Number of segments to draw a circle using segments
    const int       segcountforcircle   = 18;
    double          correctionFactor    = 1.0 / cos( M_PI / (segcountforcircle * 2) );
    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) );

    // segments to draw a circle to build texts. Is is used only to build
    // the shape of each segment of the stroke font, therefore no need to have
    // many segments per circle.
    const int       segcountInStrokeFont = 8;

    SHAPE_POLY_SET bufferPolys;
    SHAPE_POLY_SET allLayerHoles;              // Contains through holes, calculated only once
    SHAPE_POLY_SET bufferPcbOutlines;          // stores the board main outlines

    // 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 no optimization of large holes shape.
    buildBoardThroughHolesPolygonList( allLayerHoles, segcountLowQuality, false );

    // draw graphic items, on technical layers

    static const LAYER_ID teckLayerList[] = {
        B_Adhes,
        F_Adhes,
        B_Paste,
        F_Paste,
        B_SilkS,
        F_SilkS,
        B_Mask,
        F_Mask,
    };

    // User layers are not drawn here, only technical layers
    for( LSEQ seq = LSET::AllTechMask().Seq( teckLayerList, DIM( teckLayerList ) );  seq;  ++seq )
    {
        LAYER_ID layer = *seq;

        if( !is3DLayerEnabled( layer ) )
            continue;

        if( layer == Edge_Cuts && isEnabled( FL_SHOW_BOARD_BODY )  )
            continue;

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

        bufferPolys.RemoveAllContours();

        for( BOARD_ITEM* item = pcb->m_Drawings; item; item = item->Next() )
        {
            if( !item->IsOnLayer( layer ) )
                continue;

            switch( item->Type() )
            {
            case PCB_LINE_T:
                ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon(
                        bufferPolys, 0, segcountforcircle, correctionFactor );
                break;

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

            default:
                break;
            }
        }

        for( MODULE* module = pcb->m_Modules; module; module = module->Next() )
        {
            if( layer == F_SilkS || layer == B_SilkS )
            {
                // On silk screen layers, the pad shape is only the pad outline
                // never a filled shape
                D_PAD*  pad = module->Pads();
                int     linewidth = g_DrawDefaultLineThickness;

                for( ; pad; pad = pad->Next() )
                {
                    if( !pad->IsOnLayer( layer ) )
                        continue;

                    buildPadShapeThickOutlineAsPolygon( pad, bufferPolys,
                            linewidth, segcountforcircle, correctionFactor );
                }
            }
            else
                module->TransformPadsShapesWithClearanceToPolygon( layer,
                        bufferPolys, 0, segcountforcircle, correctionFactor );

            // On tech layers, use a poor circle approximation, only for texts (stroke font)
            module->TransformGraphicShapesWithClearanceToPolygonSet( layer,
                    bufferPolys, 0, segcountforcircle, correctionFactor, segcountInStrokeFont );
        }

        // Draw non copper zones
        if( isEnabled( FL_ZONE ) )
        {
            for( int ii = 0; ii < pcb->GetAreaCount(); ii++ )
            {
                ZONE_CONTAINER* zone = pcb->GetArea( ii );

                if( !zone->IsOnLayer( layer ) )
                    continue;

                zone->TransformSolidAreasShapesToPolygonSet(
                        bufferPolys, segcountLowQuality, correctionFactorLQ );
            }
        }

        // bufferPolys contains polygons to merge. Many overlaps .
        // Calculate merged polygons and remove pads and vias holes
        if( bufferPolys.IsEmpty() )
            continue;

        // Solder mask layers are "negative" layers.
        // Shapes should be removed from the full board area.
        if( layer == B_Mask || layer == F_Mask )
        {
            SHAPE_POLY_SET cuts = bufferPolys;
            bufferPolys = bufferPcbOutlines;

            cuts.Append(allLayerHoles);
            cuts.Simplify();

            bufferPolys.BooleanSubtract( cuts );
        }
        // Remove holes from Solder paste layers and silkscreen
        else if( layer == B_Paste || layer == F_Paste
                 || layer == B_SilkS || layer == F_SilkS  )
        {
            bufferPolys.BooleanSubtract( allLayerHoles );
        }

        int thickness = 0;

        if( layer != B_Mask && layer != F_Mask )
            thickness = GetPrm3DVisu().GetLayerObjectThicknessBIU( layer );

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

        if( layer == Edge_Cuts )
        {
            thickness = GetPrm3DVisu().GetLayerZcoordBIU( F_Cu )
                        - GetPrm3DVisu().GetLayerZcoordBIU( B_Cu );
            zpos = GetPrm3DVisu().GetLayerZcoordBIU( B_Cu )
                   + (thickness / 2);
        }
        else
        {
            // for Draw3D_SolidHorizontalPolyPolygons, zpos it the middle between bottom and top
            // sides.
            // However for top layers, zpos should be the bottom layer pos,
            // and for bottom layers, zpos should be the top layer pos.
            if( Get3DLayer_Z_Orientation( layer ) > 0 )
                zpos += thickness/2;
            else
                zpos -= thickness/2 ;
        }


        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 znormal must face the layer direction
        // because it will draw just one plane
        if( !thickness )
            zNormal = Get3DLayer_Z_Orientation( layer );


        setGLTechLayersColor( layer );
        Draw3D_SolidHorizontalPolyPolygons( bufferPolys, zpos,
                thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures,
                zNormal );
    }
}
/**
 * Function ConvertOutlineToPolygon
 * build a polygon (with holes) from a DRAWSEGMENT list, which is expected to be
 * a outline, therefore a closed main outline with perhaps closed inner outlines.
 * These closed inner outlines are considered as holes in the main outline
 * @param aSegList the initial list of drawsegments (only lines, circles and arcs).
 * @param aPolygons will contain the complex polygon.
 * @param aTolerance is the max distance between points that is still accepted as connected (internal units)
 * @param aErrorText is a wxString to return error message.
 * @param aErrorLocation is the optional position of the error in the outline
 */
bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SET& aPolygons,
                              wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation )
{
    if( aSegList.size() == 0 )
        return true;

    wxString msg;

    // Make a working copy of aSegList, because the list is modified during calculations
    std::vector< DRAWSEGMENT* > segList = aSegList;

    DRAWSEGMENT* graphic;
    wxPoint prevPt;

    // Find edge point with minimum x, this should be in the outer polygon
    // which will define the perimeter Edge.Cuts polygon.
    wxPoint xmin    = wxPoint( INT_MAX, 0 );
    int     xmini   = 0;

    for( size_t i = 0; i < segList.size(); i++ )
    {
        graphic = (DRAWSEGMENT*) segList[i];

        switch( graphic->GetShape() )
        {
        case S_SEGMENT:
            {
                if( graphic->GetStart().x < xmin.x )
                {
                    xmin    = graphic->GetStart();
                    xmini   = i;
                }

                if( graphic->GetEnd().x < xmin.x )
                {
                    xmin    = graphic->GetEnd();
                    xmini   = i;
                }
            }
            break;

        case S_ARC:
            // Freerouter does not yet understand arcs, so approximate
            // an arc with a series of short lines and put those
            // line segments into the !same! PATH.
            {
                wxPoint  pstart = graphic->GetArcStart();
                wxPoint  center = graphic->GetCenter();
                double   angle  = -graphic->GetAngle();
                double   radius = graphic->GetRadius();
                int      steps  = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 );
                wxPoint  pt;

                for( int step = 1; step<=steps; ++step )
                {
                    double rotation = ( angle * step ) / steps;

                    pt = pstart;

                    RotatePoint( &pt, center, rotation );

                    if( pt.x < xmin.x )
                    {
                        xmin  = pt;
                        xmini = i;
                    }
                }
            }
            break;

        case S_CIRCLE:
            {
                wxPoint pt = graphic->GetCenter();

                // pt has minimum x point
                pt.x -= graphic->GetRadius();

                // when the radius <= 0, this is a mal-formed circle. Skip it
                if( graphic->GetRadius() > 0 && pt.x < xmin.x )
                {
                    xmin  = pt;
                    xmini = i;
                }
            }
            break;

        case S_CURVE:
            {
                graphic->RebuildBezierToSegmentsPointsList( graphic->GetWidth() );

                for( unsigned int jj = 0; jj < graphic->GetBezierPoints().size(); jj++ )
                {
                    wxPoint pt = graphic->GetBezierPoints()[jj];

                    if( pt.x < xmin.x )
                    {
                        xmin  = pt;
                        xmini = i;
                    }
                }
            }
            break;

        case S_POLYGON:
            {
                const auto poly = graphic->GetPolyShape();
                MODULE* module = aSegList[0]->GetParentModule();
                double orientation = module ? module->GetOrientation() : 0.0;
                VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 );

                for( auto iter = poly.CIterate(); iter; iter++ )
                {
                    auto pt = *iter;
                    RotatePoint( pt, orientation );
                    pt += offset;

                    if( pt.x < xmin.x )
                    {
                        xmin.x = pt.x;
                        xmin.y = pt.y;
                        xmini = i;
                    }
                }
            }
            break;
        default:
            break;
        }
    }

    // Grab the left most point, assume its on the board's perimeter, and see if we
    // can put enough graphics together by matching endpoints to formulate a cohesive
    // polygon.

    graphic = (DRAWSEGMENT*) segList[xmini];

    // The first DRAWSEGMENT is in 'graphic', ok to remove it from 'items'
    segList.erase( segList.begin() + xmini );

    // Output the Edge.Cuts perimeter as circle or polygon.
    if( graphic->GetShape() == S_CIRCLE )
    {
        int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_LOW_DEF, 360.0 );
        TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(), steps );
    }
    else if( graphic->GetShape() == S_POLYGON )
    {
        MODULE* module = graphic->GetParentModule();     // NULL for items not in footprints
        double orientation = module ? module->GetOrientation() : 0.0;
        VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 );

        aPolygons.NewOutline();

        for( auto it = graphic->GetPolyShape().CIterate( 0 ); it; it++ )
        {
            auto pt = *it;
            RotatePoint( pt, orientation );
            pt += offset;
            aPolygons.Append( pt );
        }
    }
    else
    {
        // Polygon start point. Arbitrarily chosen end of the
        // segment and build the poly from here.

        wxPoint startPt = wxPoint( graphic->GetEnd() );
        prevPt = graphic->GetEnd();
        aPolygons.NewOutline();
        aPolygons.Append( prevPt );

        // Do not append the other end point yet of this 'graphic', this first
        // 'graphic' might be an arc or a curve.

        for(;;)
        {
            switch( graphic->GetShape() )
            {
            case S_SEGMENT:
                {
                    wxPoint  nextPt;

                    // Use the line segment end point furthest away from
                    // prevPt as we assume the other end to be ON prevPt or
                    // very close to it.

                    if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
                        nextPt = graphic->GetEnd();
                    else
                        nextPt = graphic->GetStart();

                    aPolygons.Append( nextPt );
                    prevPt = nextPt;
                }
                break;

            case S_ARC:
                // We do not support arcs in polygons, so approximate
                // an arc with a series of short lines and put those
                // line segments into the !same! PATH.
                {
                    wxPoint pstart  = graphic->GetArcStart();
                    wxPoint pend    = graphic->GetArcEnd();
                    wxPoint pcenter = graphic->GetCenter();
                    double  angle   = -graphic->GetAngle();
                    double  radius  = graphic->GetRadius();
                    int     steps   = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 );

                    if( !close_enough( prevPt, pstart, aTolerance ) )
                    {
                        wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) );

                        angle = -angle;
                        std::swap( pstart, pend );
                    }

                    wxPoint nextPt;

                    for( int step = 1; step<=steps; ++step )
                    {
                        double rotation = ( angle * step ) / steps;
                        nextPt = pstart;
                        RotatePoint( &nextPt, pcenter, rotation );

                        aPolygons.Append( nextPt );
                    }

                    prevPt = nextPt;
                }
                break;

            case S_CURVE:
                // We do not support Bezier curves in polygons, so approximate
                // with a series of short lines and put those
                // line segments into the !same! PATH.
                {
                    wxPoint  nextPt;
                    bool reverse = false;

                    // Use the end point furthest away from
                    // prevPt as we assume the other end to be ON prevPt or
                    // very close to it.

                    if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
                        nextPt = graphic->GetEnd();
                    else
                    {
                        nextPt = graphic->GetStart();
                        reverse = true;
                    }

                    if( reverse )
                    {
                        for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- )
                            aPolygons.Append( graphic->GetBezierPoints()[jj] );
                    }
                    else
                    {
                        for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ )
                            aPolygons.Append( graphic->GetBezierPoints()[jj] );
                    }

                    prevPt = nextPt;
                }
                break;

            default:
                if( aErrorText )
                {
                    msg.Printf( "Unsupported DRAWSEGMENT type %s.",
                                BOARD_ITEM::ShowShape( graphic->GetShape() ) );

                    *aErrorText << msg << "\n";
                }

                if( aErrorLocation )
                    *aErrorLocation = graphic->GetPosition();

                return false;
            }

            // Get next closest segment.

            graphic = findPoint( prevPt, segList, aTolerance );

            // If there are no more close segments, check if the board
            // outline polygon can be closed.

            if( !graphic )
            {
                if( close_enough( startPt, prevPt, aTolerance ) )
                {
                    // Close the polygon back to start point
                    // aPolygons.Append( startPt ); // not needed
                }
                else
                {
                    if( aErrorText )
                    {
                        msg.Printf( _( "Unable to find segment with an endpoint of (%s, %s)." ),
                                    StringFromValue( MILLIMETRES, prevPt.x, true ),
                                    StringFromValue( MILLIMETRES, prevPt.y, true ) );

                        *aErrorText << msg << "\n";
                    }

                    if( aErrorLocation )
                        *aErrorLocation = prevPt;

                    return false;
                }
                break;
            }
        }
    }

    while( segList.size() )
    {
        // emit a signal layers keepout for every interior polygon left...
        int hole = aPolygons.NewHole();

        graphic = (DRAWSEGMENT*) segList[0];
        segList.erase( segList.begin() );

        // Both circles and polygons on the edge cuts layer are closed items that
        // do not connect to other elements, so we process them independently
        if( graphic->GetShape() == S_POLYGON )
        {
            MODULE* module = graphic->GetParentModule();     // NULL for items not in footprints
            double orientation = module ? module->GetOrientation() : 0.0;
            VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 );

            for( auto it = graphic->GetPolyShape().CIterate(); it; it++ )
            {
                auto val = *it;
                RotatePoint( val, orientation );
                val += offset;

                aPolygons.Append( val, -1, hole );
            }
        }
        else if( graphic->GetShape() == S_CIRCLE )
        {
            // make a circle by segments;
            wxPoint  center  = graphic->GetCenter();
            double   angle   = 3600.0;
            wxPoint  start   = center;
            int      radius  = graphic->GetRadius();
            int      steps   = GetArcToSegmentCount( radius, ARC_LOW_DEF, 360.0 );
            wxPoint  nextPt;

            start.x += radius;

            for( int step = 0; step < steps; ++step )
            {
                double rotation = ( angle * step ) / steps;
                nextPt = start;
                RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation );
                aPolygons.Append( nextPt, -1, hole );
            }
        }
        else
        {
            // Polygon start point. Arbitrarily chosen end of the
            // segment and build the poly from here.

            wxPoint startPt( graphic->GetEnd() );
            prevPt = graphic->GetEnd();
            aPolygons.Append( prevPt, -1, hole );

            // do not append the other end point yet, this first 'graphic' might be an arc
            for(;;)
            {
                switch( graphic->GetShape() )
                {
                case S_SEGMENT:
                    {
                        wxPoint nextPt;

                        // Use the line segment end point furthest away from
                        // prevPt as we assume the other end to be ON prevPt or
                        // very close to it.

                        if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
                        {
                            nextPt = graphic->GetEnd();
                        }
                        else
                        {
                            nextPt = graphic->GetStart();
                        }

                        prevPt = nextPt;
                        aPolygons.Append( prevPt, -1, hole );
                    }
                    break;

                case S_ARC:
                    // Freerouter does not yet understand arcs, so approximate
                    // an arc with a series of short lines and put those
                    // line segments into the !same! PATH.
                    {
                        wxPoint pstart  = graphic->GetArcStart();
                        wxPoint pend    = graphic->GetArcEnd();
                        wxPoint pcenter = graphic->GetCenter();
                        double  angle   = -graphic->GetAngle();
                        int     radius  = graphic->GetRadius();
                        int     steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 );

                        if( !close_enough( prevPt, pstart, aTolerance ) )
                        {
                            wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) );

                            angle = -angle;
                            std::swap( pstart, pend );
                        }

                        wxPoint nextPt;

                        for( int step = 1; step <= steps; ++step )
                        {
                            double rotation = ( angle * step ) / steps;

                            nextPt = pstart;
                            RotatePoint( &nextPt, pcenter, rotation );

                            aPolygons.Append( nextPt, -1, hole );
                        }

                        prevPt = nextPt;
                    }
                    break;

                case S_CURVE:
                    // We do not support Bezier curves in polygons, so approximate
                    // with a series of short lines and put those
                    // line segments into the !same! PATH.
                    {
                        wxPoint  nextPt;
                        bool reverse = false;

                        // Use the end point furthest away from
                        // prevPt as we assume the other end to be ON prevPt or
                        // very close to it.

                        if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
                            nextPt = graphic->GetEnd();
                        else
                        {
                            nextPt = graphic->GetStart();
                            reverse = true;
                        }

                        if( reverse )
                        {
                            for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- )
                                aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole );
                        }
                        else
                        {
                            for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ )
                                aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole );
                        }

                        prevPt = nextPt;
                    }
                    break;

                default:
                    if( aErrorText )
                    {
                        msg.Printf( "Unsupported DRAWSEGMENT type %s.",
                                    BOARD_ITEM::ShowShape( graphic->GetShape() ) );

                        *aErrorText << msg << "\n";
                    }

                    if( aErrorLocation )
                        *aErrorLocation = graphic->GetPosition();

                    return false;
                }

                // Get next closest segment.

                graphic = findPoint( prevPt, segList, aTolerance );

                // If there are no more close segments, check if polygon
                // can be closed.

                if( !graphic )
                {
                    if( close_enough( startPt, prevPt, aTolerance ) )
                    {
                        // Close the polygon back to start point
                        // aPolygons.Append( startPt, -1, hole );   // not needed
                    }
                    else
                    {
                        if( aErrorText )
                        {
                            msg.Printf( _( "Unable to find segment with an endpoint of (%s, %s)." ),
                                        StringFromValue( MILLIMETRES, prevPt.x, true ),
                                        StringFromValue( MILLIMETRES, prevPt.y, true ) );

                            *aErrorText << msg << "\n";
                        }

                        if( aErrorLocation )
                            *aErrorLocation = prevPt;

                        return false;
                    }
                    break;
                }
            }
        }
    }

    return true;
}