bool BOARD::CombineAreas( PICKED_ITEMS_LIST* aDeletedList, ZONE_CONTAINER* area_ref,
                          ZONE_CONTAINER* area_to_combine )
{
    if( area_ref == area_to_combine )
    {
        wxASSERT( 0 );
        return false;
    }

    SHAPE_POLY_SET mergedOutlines = *area_ref->Outline();
    SHAPE_POLY_SET areaToMergePoly = *area_to_combine->Outline();

    mergedOutlines.BooleanAdd( areaToMergePoly, SHAPE_POLY_SET::PM_FAST  );
    mergedOutlines.Simplify( SHAPE_POLY_SET::PM_FAST );

    // We should have one polygon with hole
    // We can have 2 polygons with hole, if the 2 initial polygons have only one common corner
    // and therefore cannot be merged (they are dectected as intersecting)
    // but we should never have more than 2 polys
    if( mergedOutlines.OutlineCount() > 2 )
    {
        wxLogMessage(wxT("BOARD::CombineAreas error: more than 2 polys after merging") );
        return false;
    }

    if( mergedOutlines.OutlineCount() > 1 )
        return false;

    // Update the area with the new merged outline
    delete area_ref->Outline();
    area_ref->SetOutline( new SHAPE_POLY_SET( mergedOutlines ) );

    RemoveArea( aDeletedList, area_to_combine );

    area_ref->SetLocalFlags( 1 );
    area_ref->Hatch();

    return true;
}
void C3D_RENDER_OGL_LEGACY::reload( REPORTER *aStatusTextReporter )
{
    m_reloadRequested = false;

    ogl_free_all_display_lists();

    COBJECT2D_STATS::Instance().ResetStats();

#ifdef PRINT_STATISTICS_3D_VIEWER
    printf("InitSettings...\n");
#endif

    unsigned stats_startReloadTime = GetRunningMicroSecs();

    m_settings.InitSettings( aStatusTextReporter );

#ifdef PRINT_STATISTICS_3D_VIEWER
    unsigned stats_endReloadTime = GetRunningMicroSecs();
#endif

    SFVEC3F camera_pos = m_settings.GetBoardCenter3DU();
    m_settings.CameraGet().SetBoardLookAtPos( camera_pos );

#ifdef PRINT_STATISTICS_3D_VIEWER
    unsigned stats_start_OpenGL_Load_Time = GetRunningMicroSecs();
#endif

    if( aStatusTextReporter )
        aStatusTextReporter->Report( _( "Load OpenGL: board" ) );

    // Create Board
    // /////////////////////////////////////////////////////////////////////////

    CCONTAINER2D boardContainer;
    Convert_shape_line_polygon_to_triangles( m_settings.GetBoardPoly(),
                                             boardContainer,
                                             m_settings.BiuTo3Dunits(),
                                             (const BOARD_ITEM &)*m_settings.GetBoard() );

    const LIST_OBJECT2D &listBoardObject2d = boardContainer.GetList();

    if( listBoardObject2d.size() > 0 )
    {
        // We will set a unitary Z so it will in future used with transformations
        // since the board poly will be used not only to draw itself but also the
        // solder mask layers.
        const float layer_z_top = 1.0f;
        const float layer_z_bot = 0.0f;

        CLAYER_TRIANGLES *layerTriangles = new CLAYER_TRIANGLES( listBoardObject2d.size() );

        // Convert the list of objects(triangles) to triangle layer structure
        for( LIST_OBJECT2D::const_iterator itemOnLayer = listBoardObject2d.begin();
             itemOnLayer != listBoardObject2d.end();
             ++itemOnLayer )
        {
            const COBJECT2D *object2d_A = static_cast<const COBJECT2D *>(*itemOnLayer);

            wxASSERT( object2d_A->GetObjectType() == OBJ2D_TRIANGLE );

            const CTRIANGLE2D *tri = (const CTRIANGLE2D *)object2d_A;

            const SFVEC2F &v1 = tri->GetP1();
            const SFVEC2F &v2 = tri->GetP2();
            const SFVEC2F &v3 = tri->GetP3();

            add_triangle_top_bot( layerTriangles,
                                  v1,
                                  v2,
                                  v3,
                                  layer_z_top,
                                  layer_z_bot );
        }

        const SHAPE_POLY_SET &boardPoly = m_settings.GetBoardPoly();

        wxASSERT( boardPoly.OutlineCount() > 0 );

        if( boardPoly.OutlineCount() > 0 )
        {
            layerTriangles->AddToMiddleContourns( boardPoly,
                                                  layer_z_bot,
                                                  layer_z_top,
                                                  m_settings.BiuTo3Dunits(),
                                                  false );

            m_ogl_disp_list_board = new CLAYERS_OGL_DISP_LISTS( *layerTriangles,
                                                                m_ogl_circle_texture,
                                                                layer_z_top,
                                                                layer_z_top );
        }

        delete layerTriangles;
    }

    // Create Through Holes and vias
    // /////////////////////////////////////////////////////////////////////////

    if( aStatusTextReporter )
        aStatusTextReporter->Report( _( "Load OpenGL: holes and vias" ) );

    m_ogl_disp_list_through_holes_outer = generate_holes_display_list(
                m_settings.GetThroughHole_Outer().GetList(),
                m_settings.GetThroughHole_Outer_poly(),
                1.0f,
                0.0f,
                false );

    SHAPE_POLY_SET bodyHoles = m_settings.GetThroughHole_Outer_poly();

    bodyHoles.BooleanAdd( m_settings.GetThroughHole_Outer_poly_NPTH(),
                          SHAPE_POLY_SET::PM_FAST );

    m_ogl_disp_list_through_holes_outer_with_npth = generate_holes_display_list(
                m_settings.GetThroughHole_Outer().GetList(),
                bodyHoles,
                1.0f,
                0.0f,
                false );

    m_ogl_disp_list_through_holes_inner = generate_holes_display_list(
                m_settings.GetThroughHole_Inner().GetList(),
                m_settings.GetThroughHole_Inner_poly(),
                1.0f,
                0.0f,
                true );


    m_ogl_disp_list_through_holes_vias_outer = generate_holes_display_list(
                m_settings.GetThroughHole_Vias_Outer().GetList(),
                m_settings.GetThroughHole_Vias_Outer_poly(),
                1.0f,
                0.0f,
                false );

    // Not in use
    //m_ogl_disp_list_through_holes_vias_inner = generate_holes_display_list(
    //      m_settings.GetThroughHole_Vias_Inner().GetList(),
    //      m_settings.GetThroughHole_Vias_Inner_poly(),
    //      1.0f, 0.0f,
    //      false );

    const MAP_POLY & innerMapHoles = m_settings.GetPolyMapHoles_Inner();
    const MAP_POLY & outerMapHoles = m_settings.GetPolyMapHoles_Outer();

    wxASSERT( innerMapHoles.size() == outerMapHoles.size() );

    const MAP_CONTAINER_2D &map_holes = m_settings.GetMapLayersHoles();

    if( outerMapHoles.size() > 0 )
    {
        float layer_z_bot = 0.0f;
        float layer_z_top = 0.0f;

        for( MAP_POLY::const_iterator ii = outerMapHoles.begin();
             ii != outerMapHoles.end();
             ++ii )
        {
            LAYER_ID layer_id = static_cast<LAYER_ID>(ii->first);
            const SHAPE_POLY_SET *poly = static_cast<const SHAPE_POLY_SET *>(ii->second);
            const CBVHCONTAINER2D *container = map_holes.at( layer_id );

            get_layer_z_pos( layer_id, layer_z_top, layer_z_bot );

            m_ogl_disp_lists_layers_holes_outer[layer_id] = generate_holes_display_list(
                        container->GetList(),
                        *poly,
                        layer_z_top,
                        layer_z_bot,
                        false );
        }

        for( MAP_POLY::const_iterator ii = innerMapHoles.begin();
             ii != innerMapHoles.end();
             ++ii )
        {
            LAYER_ID layer_id = static_cast<LAYER_ID>(ii->first);
            const SHAPE_POLY_SET *poly = static_cast<const SHAPE_POLY_SET *>(ii->second);
            const CBVHCONTAINER2D *container = map_holes.at( layer_id );

            get_layer_z_pos( layer_id, layer_z_top, layer_z_bot );

            m_ogl_disp_lists_layers_holes_inner[layer_id] = generate_holes_display_list(
                        container->GetList(),
                        *poly,
                        layer_z_top,
                        layer_z_bot,
                        false );
        }
    }

    // Generate vertical cylinders of vias and pads (copper)
    generate_3D_Vias_and_Pads();

    // Add layers maps
    // /////////////////////////////////////////////////////////////////////////

    if( aStatusTextReporter )
        aStatusTextReporter->Report( _( "Load OpenGL: layers" ) );

    for( MAP_CONTAINER_2D::const_iterator ii = m_settings.GetMapLayers().begin();
         ii != m_settings.GetMapLayers().end();
         ++ii )
    {
        LAYER_ID layer_id = static_cast<LAYER_ID>(ii->first);

        if( !m_settings.Is3DLayerEnabled( layer_id ) )
            continue;

        const CBVHCONTAINER2D *container2d = static_cast<const CBVHCONTAINER2D *>(ii->second);
        const LIST_OBJECT2D &listObject2d = container2d->GetList();

        if( listObject2d.size() == 0 )
            continue;

        float layer_z_bot = 0.0f;
        float layer_z_top = 0.0f;

        get_layer_z_pos( layer_id, layer_z_top, layer_z_bot );

        // Calculate an estimation for the nr of triangles based on the nr of objects
        unsigned int nrTrianglesEstimation = listObject2d.size() * 8;

        CLAYER_TRIANGLES *layerTriangles = new CLAYER_TRIANGLES( nrTrianglesEstimation );

        m_triangles[layer_id] = layerTriangles;

        for( LIST_OBJECT2D::const_iterator itemOnLayer = listObject2d.begin();
             itemOnLayer != listObject2d.end();
             ++itemOnLayer )
        {
            const COBJECT2D *object2d_A = static_cast<const COBJECT2D *>(*itemOnLayer);

            switch( object2d_A->GetObjectType() )
            {
                case OBJ2D_FILLED_CIRCLE:
                    add_object_to_triangle_layer( (const CFILLEDCIRCLE2D *)object2d_A,
                                                  layerTriangles,
                                                  layer_z_top, layer_z_bot );
                break;

                case OBJ2D_POLYGON4PT:
                    add_object_to_triangle_layer( (const CPOLYGON4PTS2D *)object2d_A,
                                                  layerTriangles,
                                                  layer_z_top, layer_z_bot );
                break;

                case OBJ2D_RING:
                    add_object_to_triangle_layer( (const CRING2D *)object2d_A,
                                                  layerTriangles,
                                                  layer_z_top, layer_z_bot );
                break;

                case OBJ2D_TRIANGLE:
                    add_object_to_triangle_layer( (const CTRIANGLE2D *)object2d_A,
                                                  layerTriangles,
                                                  layer_z_top, layer_z_bot );
                break;

                case OBJ2D_ROUNDSEG:
                    add_object_to_triangle_layer( (const CROUNDSEGMENT2D *) object2d_A,
                                                  layerTriangles,
                                                  layer_z_top, layer_z_bot );
                break;

                default:
                    wxFAIL_MSG("C3D_RENDER_OGL_LEGACY: Object type is not implemented");
                break;
            }
        }

        const MAP_POLY &map_poly = m_settings.GetPolyMap();

        if( map_poly.find( layer_id ) != map_poly.end() )
        {
            const SHAPE_POLY_SET *polyList = map_poly.at( layer_id );

            layerTriangles->AddToMiddleContourns( *polyList,
                                                  layer_z_bot,
                                                  layer_z_top,
                                                  m_settings.BiuTo3Dunits(),
                                                  false );
        }

        // Create display list
        // /////////////////////////////////////////////////////////////////////
        m_ogl_disp_lists_layers[layer_id] = new CLAYERS_OGL_DISP_LISTS( *layerTriangles,
                                                                        m_ogl_circle_texture,
                                                                        layer_z_bot,
                                                                        layer_z_top );
    }// for each layer on map

#ifdef PRINT_STATISTICS_3D_VIEWER
    unsigned stats_end_OpenGL_Load_Time = GetRunningMicroSecs();
#endif

    // Load 3D models
    // /////////////////////////////////////////////////////////////////////////
#ifdef PRINT_STATISTICS_3D_VIEWER
    unsigned stats_start_models_Load_Time = GetRunningMicroSecs();
#endif

    if( aStatusTextReporter )
        aStatusTextReporter->Report( _( "Loading 3D models" ) );

    load_3D_models();

#ifdef PRINT_STATISTICS_3D_VIEWER
    unsigned stats_end_models_Load_Time = GetRunningMicroSecs();


    printf( "C3D_RENDER_OGL_LEGACY::reload times:\n" );
    printf( "  Reload board:             %.3f ms\n",
            (float)( stats_endReloadTime        - stats_startReloadTime        ) / 1000.0f );
    printf( "  Loading to openGL:        %.3f ms\n",
            (float)( stats_end_OpenGL_Load_Time - stats_start_OpenGL_Load_Time ) / 1000.0f );
    printf( "  Loading 3D models:        %.3f ms\n",
            (float)( stats_end_models_Load_Time - stats_start_models_Load_Time ) / 1000.0f );
    COBJECT2D_STATS::Instance().PrintStats();
#endif

    if( aStatusTextReporter )
    {
        // Calculation time in seconds
        const double calculation_time = (double)( GetRunningMicroSecs() -
                                                  stats_startReloadTime) / 1e6;

        aStatusTextReporter->Report( wxString::Format( _( "Reload time %.3f s" ),
                                                       calculation_time ) );
    }
}
예제 #3
0
/* Plot a solder mask layer.
 * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers,
 * unless the minimum thickness is 0.
 * Currently the algo is:
 * 1 - build all pad shapes as polygons with a size inflated by
 *      mask clearance + (min width solder mask /2)
 * 2 - Merge shapes
 * 3 - deflate result by (min width solder mask /2)
 * 4 - oring result by all pad shapes as polygons with a size inflated by
 *      mask clearance only (because deflate sometimes creates shape artifacts)
 * 5 - draw result as polygons
 *
 * TODO:
 * make this calculation only for shapes with clearance near than (min width solder mask)
 * (using DRC algo)
 * plot all other shapes by flashing the basing shape
 * (shapes will be better, and calculations faster)
 */
void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
                          LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt,
                          int aMinThickness )
{
    LAYER_ID    layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
    int         inflate = aMinThickness/2;

    BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
    itemplotter.SetLayerSet( aLayerMask );

    // Plot edge layer and graphic items
    // They do not have a solder Mask margin, because they are only graphic items
    // on this layer (like logos), not actually areas around pads.
    itemplotter.PlotBoardGraphicItems();

    for( MODULE* module = aBoard->m_Modules;  module;  module = module->Next() )
    {
        for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item->Next() )
        {
            if( layer != item->GetLayer() )
                continue;

            switch( item->Type() )
            {
            case PCB_MODULE_EDGE_T:
                itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
                break;

            default:
                break;
            }
        }
    }

    // Build polygons for each pad shape.
    // the size of the shape on solder mask should be:
    // size of pad + clearance around the pad.
    // clearance = solder mask clearance + extra margin
    // extra margin is half the min width for solder mask
    // This extra margin is used to merge too close shapes
    // (distance < aMinThickness), and will be removed when creating
    // the actual shapes
    SHAPE_POLY_SET areas;           // Contains shapes to plot
    SHAPE_POLY_SET initialPolys;    // Contains exact shapes to plot

    /* calculates the coeff to compensate radius reduction of holes clearance
     * due to the segment approx ( 1 /cos( PI/circleToSegmentsCount )
     */
    int circleToSegmentsCount = 32;
    double correction = 1.0 / cos( M_PI / circleToSegmentsCount );

    // Plot pads
    for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
    {
        // add shapes with exact size
        module->TransformPadsShapesWithClearanceToPolygon( layer,
                initialPolys, 0,
                circleToSegmentsCount, correction );
        // add shapes inflated by aMinThickness/2
        module->TransformPadsShapesWithClearanceToPolygon( layer,
                areas, inflate,
                circleToSegmentsCount, correction );
    }

    // Plot vias on solder masks, if aPlotOpt.GetPlotViaOnMaskLayer() is true,
    if( aPlotOpt.GetPlotViaOnMaskLayer() )
    {
        // The current layer is a solder mask,
        // use the global mask clearance for vias
        int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin;
        int via_margin = via_clearance + inflate;

        for( TRACK* track = aBoard->m_Track; track; track = track->Next() )
        {
            const VIA* via = dyn_cast<const VIA*>( track );

            if( !via )
                continue;

            // vias are plotted only if they are on the corresponding
            // external copper layer
            LSET via_set = via->GetLayerSet();

            if( via_set[B_Cu] )
                via_set.set( B_Mask );

            if( via_set[F_Cu] )
                via_set.set( F_Mask );

            if( !( via_set & aLayerMask ).any() )
                continue;

            via->TransformShapeWithClearanceToPolygon( areas, via_margin,
                    circleToSegmentsCount,
                    correction );
            via->TransformShapeWithClearanceToPolygon( initialPolys, via_clearance,
                    circleToSegmentsCount,
                    correction );
        }
    }

    // Add filled zone areas.
#if 0   // Set to 1 if a solder mask margin must be applied to zones on solder mask
    int zone_margin = aBoard->GetDesignSettings().m_SolderMaskMargin;
#else
    int zone_margin = 0;
#endif

    for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ )
    {
        ZONE_CONTAINER* zone = aBoard->GetArea( ii );

        if( zone->GetLayer() != layer )
            continue;

        zone->TransformOutlinesShapeWithClearanceToPolygon( areas,
                inflate+zone_margin, false );
        zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys,
                zone_margin, false );
    }

    // To avoid a lot of code, use a ZONE_CONTAINER
    // to handle and plot polygons, because our polygons look exactly like
    // filled areas in zones
    // Note, also this code is not optimized: it creates a lot of copy/duplicate data
    // However it is not complex, and fast enough for plot purposes (copy/convert data
    // is only a very small calculation time for these calculations)
    ZONE_CONTAINER zone( aBoard );
    zone.SetArcSegmentCount( 32 );
    zone.SetMinThickness( 0 );      // trace polygons only
    zone.SetLayer ( layer );

    areas.BooleanAdd( initialPolys );
    areas.Inflate( -inflate, circleToSegmentsCount );

    // Combine the current areas to initial areas. This is mandatory because
    // inflate/deflate transform is not perfect, and we want the initial areas perfectly kept
    areas.BooleanAdd( initialPolys );
    areas.Fracture();

    zone.AddFilledPolysList( areas );

    itemplotter.PlotFilledAreas( &zone );
}
/**
 * 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();
}
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 );
}