Gerber plot: new algo to generate solder mask layers with non 0 min thickness mask
Previously, all overlapping polygons (pads and min thickness areas to remove) were merged. Drawback: pads attributes are lost. In Gerber this is annoying. Now the pads are plot as flashed or Regions items, and min thickness areas are added but shapes are not merged and keep their attributes.
This commit is contained in:
parent
615deb40f3
commit
3e247a70d5
|
@ -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 );
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<VECTOR2I> 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<wxPoint> 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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
Loading…
Reference in New Issue