diff --git a/libs/kimath/include/geometry/geometry_utils.h b/libs/kimath/include/geometry/geometry_utils.h index dd256c3280..aa9317d1e2 100644 --- a/libs/kimath/include/geometry/geometry_utils.h +++ b/libs/kimath/include/geometry/geometry_utils.h @@ -37,7 +37,6 @@ class EDA_RECT; - /** * @return the number of segments to approximate a arc by segments * with a given max error (this number is >= 1) @@ -48,8 +47,24 @@ class EDA_RECT; */ int GetArcToSegmentCount( int aRadius, int aErrorMax, double aArcAngleDegree ); +/** When creating polygons to create a clearance polygonal area, the polygon must + * be same or bigger than the original shape. + * Polygons are bigger if the original shape has arcs (round rectangles, ovals, circles...) + * In some cases (in fact only one: when building layer solder mask) modifying + * shapes when converting them to polygons is not acceptable (the modification + * can break calculations) + * so one can disable the shape expansion by calling KeepPolyInsideShape( true ) + * Important: calling KeepPolyInsideShape( false ) after calculations is + * mandatory to break oher calculations + * @param aDisable = false to create polygons same or outside the original shape + * = true to create polygons same or inside the original shape and minimize + * shape geometric changes + */ +void DisableArcRadiusCorrection( bool aDisable ); + /** - * @return the correction factor to approximate a circle by segments + * @return the correction factor to approximate a circle by segments or 1.0 + * depending on the last call to DisableArcRadiusCorrection() * @param aSegCountforCircle is the number of segments to approximate the circle * * When creating a polygon from a circle, the polygon is inside the circle. @@ -58,6 +73,8 @@ int GetArcToSegmentCount( int aRadius, int aErrorMax, double aArcAngleDegree ); * the equivalent polygon outside the circle * The correction factor is a scaling factor to apply to the radius to build a * polygon outside the circle (only the middle of each segment is on the circle + * + * */ double GetCircletoPolyCorrectionFactor( int aSegCountforCircle ); diff --git a/libs/kimath/src/geometry/geometry_utils.cpp b/libs/kimath/src/geometry/geometry_utils.cpp index e4898a637f..8ea449924f 100644 --- a/libs/kimath/src/geometry/geometry_utils.cpp +++ b/libs/kimath/src/geometry/geometry_utils.cpp @@ -60,6 +60,23 @@ int GetArcToSegmentCount( int aRadius, int aErrorMax, double aArcAngleDegree ) return std::max( segCount, 1 ); } +// When creating polygons to create a clearance polygonal area, the polygon must +// be same or bigger than the original shape. +// Polygons are bigger if the original shape has arcs (round rectangles, ovals, circles...) +// In some cases (in fact only one: when building layer solder mask) modifying +// shapes when converting them to polygons is not acceptable (the modification +// can break calculations) +// so one can disable the shape expansion by calling KeepPolyInsideShape( true ) +// Important: calling KeepPolyInsideShape( false ) after calculations is +// mandatory to break oher calculations +static bool s_disable_arc_correction = false; + +// Enable (aInside = false) or disable (aInside = true) polygonal shape expansion +// when converting pads shapes and other items shapes to polygons: +void DisableArcRadiusCorrection( bool aDisable ) +{ + s_disable_arc_correction = aDisable; +} double GetCircletoPolyCorrectionFactor( int aSegCountforCircle ) { @@ -71,8 +88,7 @@ double GetCircletoPolyCorrectionFactor( int aSegCountforCircle ) * the correctionFactor is 1 /cos( PI/aSegCountforCircle ) */ aSegCountforCircle = std::max( MIN_SEGCOUNT_FOR_CIRCLE, aSegCountforCircle ); - - return 1.0 / cos( M_PI / aSegCountforCircle ); + return s_disable_arc_correction ? 1.0 : 1.0 / cos( M_PI / aSegCountforCircle ); } diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index 4eafc8b871..4383f60366 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -8,7 +8,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -362,7 +362,11 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, wxSize extraSize = margin * 2; extraSize.x += width_adj; extraSize.y += width_adj; + + // Store these parameters that can be modified to plot inflated/deflated pads shape wxSize deltaSize = pad->GetDelta(); // has meaning only for trapezoidal pads + PAD_SHAPE_T padShape = pad->GetShape(); + double padCornerRadius = pad->GetRoundRectCornerRadius(); if( pad->GetShape() == PAD_SHAPE_TRAPEZOID ) { // The easy way is to use BuildPadPolygon to calculate @@ -430,8 +434,15 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, itemplotter.PlotPad( pad, color, plotMode ); break; - case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_RECT: + if( margin.x > 0 ) + { + pad->SetShape( PAD_SHAPE_ROUNDRECT ); + pad->SetSize( padPlotsSize ); + pad->SetRoundRectCornerRadius( margin.x ); + } + // Fall through + case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_ROUNDRECT: case PAD_SHAPE_CHAMFERED_RECT: pad->SetSize( padPlotsSize ); @@ -465,8 +476,11 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, break; } - pad->SetSize( tmppadsize ); // Restore the pad size + // Restore the pad parameters modified by the plot code + pad->SetSize( tmppadsize ); pad->SetDelta( deltaSize ); + pad->SetShape( padShape ); + pad->SetRoundRectCornerRadius( padCornerRadius ); } aPlotter->EndBlock( NULL ); @@ -788,17 +802,34 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, * 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) + * We have 2 algos: + * the initial algo, that create polygons for every shape, inflate and deflate polygons + * with Min Thickness/2, and merges the result. + * Drawback: pads attributes are lost (annoying in Gerber) + * the new algo: + * create initial polygons for every shape (pad or polygon), + * inflate and deflate polygons + * with Min Thickness/2, and merges the result (like initial algo) + * remove all initial polygons. + * The remaining polygons are areas with thickness < min thickness + * plot all initial shapes by flashing (or using regions) for pad and polygons + * (shapes will be better) and remaining polygons to + * remove areas with thickness < min thickness from final mask + * + * TODO: remove old code after more testing. */ +#define NEW_ALGO 1 +extern void KeepPolyInside( bool aInside ); + void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness ) { PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask; + // Set the current arc to segment max approx error + int currMaxError = aBoard->GetDesignSettings().m_MaxError; + aBoard->GetDesignSettings().m_MaxError = Millimeter2iu( 0.005 ); + // We remove 1nm as we expand both sides of the shapes, so allowing for // a strictly greater than or equal comparison in the shape separation (boolean add) // means that we will end up with separate shapes that then are shrunk @@ -808,8 +839,12 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, itemplotter.SetLayerSet( aLayerMask ); // Plot edge layer and graphic items. - // They do not have a solder Mask margin, because they graphic items + // They do not have a solder Mask margin, because they are graphic items // on this layer (like logos), not actually areas around pads. + + // Normal mode to generate polygons from shapes with arcs, if any: + DisableArcRadiusCorrection( false ); + itemplotter.PlotBoardGraphicItems(); for( auto module : aBoard->Modules() ) @@ -827,15 +862,26 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, // of pad + clearance around the pad, where clearance = solder mask clearance + extra margin. // Extra margin is half the min width for solder mask, which 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 + + // Will contain shapes inflated by inflate value that will be merged and deflated by + // inflate value to build final polygons + // After calculations the remaining polygons are polygons to plot + SHAPE_POLY_SET areas; + // Will contain exact shapes of all items on solder mask + SHAPE_POLY_SET initialPolys; + +#if NEW_ALGO + // Generate polygons with arcs inside the shape or exact shape + // to minimize shape changes created by arc to segment size correction. + DisableArcRadiusCorrection( true ); +#endif // Plot pads for( auto module : aBoard->Modules() ) { - // add shapes with exact size + // add shapes with their exact mask layer size in initialPolys module->TransformPadsShapesWithClearanceToPolygon( layer, initialPolys, 0 ); - // add shapes inflated by aMinThickness/2 + // add shapes inflated by aMinThickness/2 in areas module->TransformPadsShapesWithClearanceToPolygon( layer, areas, inflate ); } @@ -865,8 +911,10 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, if( !( via_set & aLayerMask ).any() ) continue; - via->TransformShapeWithClearanceToPolygon( areas, via_margin ); + // add shapes with their exact mask layer size in initialPolys via->TransformShapeWithClearanceToPolygon( initialPolys, via_clearance ); + // add shapes inflated by aMinThickness/2 in areas + via->TransformShapeWithClearanceToPolygon( areas, via_margin ); } } @@ -882,18 +930,35 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, if( zone->GetLayer() != layer ) continue; - // Some intersecting zones, despite being on the same layer with the same net, cannot be - // merged due to other parameters such as fillet radius. The copper pour will end up + // Some intersecting zones, despite being on the same layer, cannot be + // merged due to other parameters such as fillet radius. The filled areas will end up // effectively merged though, so we want to keep the corners of such intersections sharp. std::set colinearCorners; zone->GetColinearCorners( aBoard, colinearCorners ); + // add shapes inflated by aMinThickness/2 in areas zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin, false, &colinearCorners ); + // add shapes with their exact mask layer size in initialPolys zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false, &colinearCorners ); } + int maxError = aBoard->GetDesignSettings().m_MaxError; + int numSegs = std::max( GetArcToSegmentCount( inflate, maxError, 360.0 ), 12 ); + + // Merge all polygons: After deflating, not merged (not overlapping) polygons + // will have the initial shape (with perhaps small changes due to deflating transform) + areas.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + areas.Deflate( inflate, numSegs ); + + // Restore initial settings: + aBoard->GetDesignSettings().m_MaxError = currMaxError; + + // Restore normal option to build polygons from item shapes: + DisableArcRadiusCorrection( false ); + +#if !NEW_ALGO // 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. @@ -902,18 +967,61 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, ZONE_CONTAINER zone( aBoard ); zone.SetMinThickness( 0 ); // trace polygons only zone.SetLayer( layer ); - int maxError = aBoard->GetDesignSettings().m_MaxError; - int numSegs = std::max( GetArcToSegmentCount( inflate, maxError, 360.0 ), 6 ); - - areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST ); - areas.Deflate( inflate, numSegs ); - // 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, SHAPE_POLY_SET::PM_FAST ); areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); itemplotter.PlotFilledAreas( &zone, areas ); +#else + + // Remove initial shapes: each shape will be added later, as flashed item or region + // with a suitable attribute. + // Do not merge pads is mandatory in Gerber files: They must be indentified as pads + + // we deflate areas in polygons, to avoid after subtracting initial shapes + // having small artifacts due to approximations during polygon transforms + areas.BooleanSubtract( initialPolys, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + // Slightly inflate polygons to avoid any gap between them and other shapes, + // These gaps are created by arc to segments approximations + areas.Inflate( Millimeter2iu( 0.002 ),6 ); + + // Now, only polygons with a too small thickness are stored in areas. + areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + // Plot each initial shape (pads and polygons on mask layer), with suitable attributes: + PlotStandardLayer( aBoard, aPlotter, aLayerMask, aPlotOpt ); + + // Add shapes corresponding to areas having too small thickness. + std::vector cornerList; + + for( int ii = 0; ii < areas.OutlineCount(); ii++ ) + { + cornerList.clear(); + const SHAPE_LINE_CHAIN& path = areas.COutline( ii ); + + // polygon area in mm^2 : + double curr_area = path.Area() / ( IU_PER_MM * IU_PER_MM ); + + // Skip very small polygons: they are certainly artifacts created by + // arc approximations and polygon transforms + // (inflate/deflate transforms) + constexpr double poly_min_area_mm2 = 0.01; // 0.01 mm^2 gives a good filtering + + if( curr_area < poly_min_area_mm2 ) + continue; + + for( int jj = 0; jj < path.PointCount(); jj++ ) + cornerList.emplace_back( (wxPoint) path.CPoint( jj ) ); + + // Ensure the polygon is closed + if( cornerList[0] != cornerList[cornerList.size() - 1] ) + cornerList.push_back( cornerList[0] ); + + aPlotter->PlotPoly( cornerList, FILLED_SHAPE ); + } +#endif } diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index f5221de06a..1338688f74 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -75,7 +75,7 @@ COLOR4D BRDITEMS_PLOTTER::getColor( LAYER_NUM aLayer ) { COLOR4D color = m_colors->GetColor( aLayer ); - // A hack to avoid plotting ahite itmen in white color, expecting the paper + // A hack to avoid plotting a white item in white color, expecting the paper // is also white: use a non white color: if( color == COLOR4D::WHITE ) color = COLOR4D( LIGHTGRAY );