7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-03-30 06:16:56 +00:00

Handle clipping silk to mask when there is more than one layer.

This commit is contained in:
Jeff Young 2025-02-14 14:19:14 +00:00
parent 4959b480c3
commit b44261c2bd
3 changed files with 177 additions and 148 deletions

View File

@ -484,7 +484,7 @@ bool PLOT_CONTROLLER::PlotLayer()
return false;
// Fully delegated to the parent
PlotOneBoardLayer( m_board, m_plotter, ToLAYER_ID( GetLayer() ), GetPlotOptions() );
PlotOneBoardLayer( m_board, m_plotter, ToLAYER_ID( GetLayer() ), GetPlotOptions(), true );
PlotInteractiveLayer( m_board, m_plotter, GetPlotOptions() );
return true;
}

View File

@ -192,7 +192,7 @@ void PlotInteractiveLayer( BOARD* aBoard, PLOTTER* aPlotter, const PCB_PLOT_PARA
* @param aPlotOpt is the plot options (files, sketch). Has meaning for some formats only.
*/
void PlotOneBoardLayer( BOARD* aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
const PCB_PLOT_PARAMS& aPlotOpt );
const PCB_PLOT_PARAMS& aPlotOpt, bool isPrimaryLayer );
/**
* Plot copper or technical layers.

View File

@ -46,12 +46,84 @@
#include <gbr_metadata.h>
#include <advanced_config.h>
/*
* 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.
void GenerateLayerPoly( SHAPE_POLY_SET* aResult, BOARD *aBoard, PCB_LAYER_ID aLayer,
bool aPlotFPText, bool aPlotReferences, bool aPlotValues );
void PlotLayer( BOARD* aBoard, PLOTTER* aPlotter, const LSET& layerMask,
const PCB_PLOT_PARAMS& plotOpts )
{
// PlotLayerOutlines() is designed only for DXF plotters.
if( plotOpts.GetFormat() == PLOT_FORMAT::DXF && plotOpts.GetDXFPlotPolygonMode() )
PlotLayerOutlines( aBoard, aPlotter, layerMask, plotOpts );
else
PlotStandardLayer( aBoard, aPlotter, layerMask, plotOpts );
};
void PlotPolySet( BOARD* aBoard, PLOTTER* aPlotter, const PCB_PLOT_PARAMS& aPlotOpt,
SHAPE_POLY_SET* aPolySet, PCB_LAYER_ID aLayer )
{
BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
LSET layers = { aLayer };
itemplotter.SetLayerSet( layers );
// To avoid a lot of code, use a ZONE 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 zone( aBoard );
zone.SetMinThickness( 0 );
zone.SetLayer( aLayer );
aPolySet->Fracture();
itemplotter.PlotZone( &zone, aLayer, *aPolySet );
}
/**
* 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.
*/
static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness );
void PlotSolderMaskLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_PLOT_PARAMS& aPlotOpt )
{
if( aBoard->GetDesignSettings().m_SolderMaskMinWidth == 0 )
{
PlotLayer( aBoard, aPlotter, aLayerMask, aPlotOpt );
return;
}
SHAPE_POLY_SET solderMask;
PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
GenerateLayerPoly( &solderMask, aBoard, layer, aPlotOpt.GetPlotFPText(),
aPlotOpt.GetPlotReference(), aPlotOpt.GetPlotValue() );
PlotPolySet( aBoard, aPlotter, aPlotOpt, &solderMask, layer );
}
void PlotClippedSilkLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_PLOT_PARAMS& aPlotOpt )
{
SHAPE_POLY_SET silkscreen, solderMask;
PCB_LAYER_ID silkLayer = aLayerMask[F_SilkS] ? F_SilkS : B_SilkS;
PCB_LAYER_ID maskLayer = aLayerMask[F_SilkS] ? F_Mask : B_Mask;
GenerateLayerPoly( &silkscreen, aBoard, silkLayer, aPlotOpt.GetPlotFPText(),
aPlotOpt.GetPlotReference(), aPlotOpt.GetPlotValue() );
GenerateLayerPoly( &solderMask, aBoard, maskLayer, aPlotOpt.GetPlotFPText(),
aPlotOpt.GetPlotReference(), aPlotOpt.GetPlotValue() );
silkscreen.BooleanSubtract( solderMask );
PlotPolySet( aBoard, aPlotter, aPlotOpt, &silkscreen, silkLayer );
}
void PlotBoardLayers( BOARD* aBoard, PLOTTER* aPlotter, const LSEQ& aLayers,
@ -68,8 +140,7 @@ void PlotBoardLayers( BOARD* aBoard, PLOTTER* aPlotter, const LSEQ& aLayers,
&& !aPlotOptions.GetLayerSelection().ClearNonCopperLayers().empty() );
for( PCB_LAYER_ID layer : aLayers )
PlotOneBoardLayer( aBoard, aPlotter, layer, aPlotOptions );
PlotOneBoardLayer( aBoard, aPlotter, layer, aPlotOptions, layer == aLayers[0] );
if( plot_mark )
{
@ -122,8 +193,7 @@ void PlotInteractiveLayer( BOARD* aBoard, PLOTTER* aPlotter, const PCB_PLOT_PARA
properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), _( "Library Description" ),
fp->GetLibDescription() ) );
properties.emplace_back( wxString::Format( wxT( "!%s = %s" ),
_( "Keywords" ),
properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), _( "Keywords" ),
fp->GetKeywords() ) );
#endif
// Draw items are plotted with a position offset. So we need to move
@ -147,20 +217,9 @@ void PlotInteractiveLayer( BOARD* aBoard, PLOTTER* aPlotter, const PCB_PLOT_PARA
void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
const PCB_PLOT_PARAMS& aPlotOpt )
const PCB_PLOT_PARAMS& aPlotOpt, bool isPrimaryLayer )
{
auto plotLayer =
[&]( LSET layerMask, PCB_PLOT_PARAMS& plotOpts )
{
// PlotLayerOutlines() is designed only for DXF plotters.
if( plotOpts.GetFormat() == PLOT_FORMAT::DXF && plotOpts.GetDXFPlotPolygonMode() )
PlotLayerOutlines( aBoard, aPlotter, layerMask, plotOpts );
else
PlotStandardLayer( aBoard, aPlotter, layerMask, plotOpts );
};
PCB_PLOT_PARAMS plotOpt = aPlotOpt;
int soldermask_min_thickness = aBoard->GetDesignSettings().m_SolderMaskMinWidth;
// Set a default color and the text mode for this layer
aPlotter->SetColor( BLACK );
@ -179,7 +238,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
else
plotOpt.SetSkipPlotNPTH_Pads( true );
plotLayer( layer_mask, plotOpt );
PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
}
else
{
@ -194,15 +253,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
plotOpt.SetDXFPlotPolygonMode( true );
// Plot solder mask:
if( soldermask_min_thickness == 0 )
{
plotLayer( layer_mask, plotOpt );
}
else
{
PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt,
soldermask_min_thickness );
}
PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt );
break;
@ -216,36 +267,45 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
// Use outline mode for DXF
plotOpt.SetDXFPlotPolygonMode( true );
plotLayer( layer_mask, plotOpt );
PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
break;
case F_SilkS:
case B_SilkS:
plotLayer( layer_mask, plotOpt );
// Gerber: Subtract soldermask from silkscreen if enabled
if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER
&& plotOpt.GetSubtractMaskFromSilk() )
if( plotOpt.GetSubtractMaskFromSilk() )
{
if( aLayer == F_SilkS )
layer_mask = LSET( { F_Mask } );
if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER && isPrimaryLayer )
{
// Use old-school, positive/negative mask plotting which preserves utilization
// of Gerber aperture masks. This method can only be used when the given silk
// layer is the primary layer as the negative mask will also knockout any other
// (non-silk) layers that were plotted before the silk layer.
PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
// Create the mask to subtract by creating a negative layer polarity
aPlotter->SetLayerPolarity( false );
// Disable plot pad holes
plotOpt.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE );
// Plot the mask
layer_mask = ( aLayer == F_SilkS ) ? LSET( { F_Mask } ) : LSET( { B_Mask } );
PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt );
// Disable the negative polarity
aPlotter->SetLayerPolarity( true );
}
else
layer_mask = LSET( { B_Mask } );
{
PlotClippedSilkLayer( aBoard, aPlotter, layer_mask, plotOpt );
}
// Create the mask to subtract by creating a negative layer polarity
aPlotter->SetLayerPolarity( false );
// Disable plot pad holes
plotOpt.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE );
// Plot the mask
PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
// Disable the negative polarity
aPlotter->SetLayerPolarity( true );
break;
}
PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
break;
case Dwgs_User:
@ -259,7 +319,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
case F_Fab:
case B_Fab:
default:
plotLayer( layer_mask, plotOpt );
PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
break;
}
}
@ -858,59 +918,57 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
/**
* 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.
* Generates a SHAPE_POLY_SET representing the plotted items on a layer.
*/
void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness )
void GenerateLayerPoly( SHAPE_POLY_SET* aResult, BOARD *aBoard, PCB_LAYER_ID aLayer,
bool aPlotFPText, bool aPlotReferences, bool aPlotValues )
{
#define ERROR maxError, ERROR_OUTSIDE
int maxError = aBoard->GetDesignSettings().m_MaxError;
PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
SHAPE_POLY_SET buffer;
SHAPE_POLY_SET* boardOutline = nullptr;
int inflate = 0;
if( aBoard->GetBoardPolygonOutlines( buffer ) )
boardOutline = &buffer;
// 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)
int inflate = aMinThickness / 2 - 1;
BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
itemplotter.SetLayerSet( aLayerMask );
if( aLayer == F_Mask || aLayer == B_Mask )
{
// 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)
inflate = aBoard->GetDesignSettings().m_SolderMaskMinWidth / 2 - 1;
}
// Build polygons for each pad shape. The size of the shape on solder mask should be size
// 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.
// (distance < SolderMaskMinWidth).
// Will contain shapes inflated by inflate value that will be merged and deflated by inflate
// value to build final polygons
SHAPE_POLY_SET areas;
// Will contain exact shapes of all items on solder mask
SHAPE_POLY_SET initialPolys;
// Will contain exact shapes of all items on solder mask. We add this back in at the end just
// to make sure that any artefacts introduced by the inflate/deflate don't remove parts of the
// individual shapes.
SHAPE_POLY_SET exactPolys;
auto handleFPTextItem =
[&]( const PCB_TEXT& aText )
{
if( !itemplotter.GetPlotFPText() )
if( !aPlotFPText )
return;
if( aText.GetText() == wxT( "${REFERENCE}" ) && !itemplotter.GetPlotReference() )
if( !aText.IsVisible() )
return;
if( aText.GetText() == wxT( "${VALUE}" ) && !itemplotter.GetPlotValue() )
if( aText.GetText() == wxT( "${REFERENCE}" ) && !aPlotReferences )
return;
// add shapes with their exact mask layer size in initialPolys
aText.TransformTextToPolySet( initialPolys, 0, maxError, ERROR_OUTSIDE );
if( aText.GetText() == wxT( "${VALUE}" ) && !aPlotValues )
return;
// add shapes inflated by aMinThickness/2 in areas
aText.TransformTextToPolySet( areas, inflate, maxError, ERROR_OUTSIDE );
if( inflate != 0 )
aText.TransformTextToPolySet( exactPolys, 0, ERROR );
aText.TransformTextToPolySet( *aResult, inflate, ERROR );
};
// Generate polygons with arcs inside the shape or exact shape to minimize shape changes
@ -920,29 +978,26 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
// Plot footprint pads and graphics
for( const FOOTPRINT* footprint : aBoard->Footprints() )
{
// add shapes with their exact mask layer size in initialPolys
footprint->TransformPadsToPolySet( initialPolys, layer, 0, maxError, ERROR_OUTSIDE );
// add shapes inflated by aMinThickness/2 in areas
footprint->TransformPadsToPolySet( areas, layer, inflate, maxError, ERROR_OUTSIDE );
if( inflate != 0 )
footprint->TransformPadsToPolySet( exactPolys, aLayer, 0, ERROR );
footprint->TransformPadsToPolySet( *aResult, aLayer, inflate, ERROR );
for( const PCB_FIELD* field : footprint->GetFields() )
{
if( field->IsReference() && !itemplotter.GetPlotReference() )
if( field->IsReference() && !aPlotReferences )
continue;
if( field->IsValue() && !itemplotter.GetPlotValue() )
if( field->IsValue() && !aPlotValues )
continue;
if( !field->IsVisible() && !itemplotter.GetPlotInvisibleText() )
continue;
if( field->IsOnLayer( layer ) )
if( field->IsOnLayer( aLayer ) )
handleFPTextItem( static_cast<const PCB_TEXT&>( *field ) );
}
for( const BOARD_ITEM* item : footprint->GraphicalItems() )
{
if( item->IsOnLayer( layer ) )
if( item->IsOnLayer( aLayer ) )
{
if( item->Type() == PCB_TEXT_T )
{
@ -950,13 +1005,10 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
}
else
{
// add shapes with their exact mask layer size in initialPolys
item->TransformShapeToPolygon( initialPolys, layer, 0, maxError,
ERROR_OUTSIDE );
if( inflate != 0 )
item->TransformShapeToPolygon( exactPolys, aLayer, 0, ERROR );
// add shapes inflated by aMinThickness/2 in areas
item->TransformShapeToPolygon( areas, layer, inflate, maxError,
ERROR_OUTSIDE );
item->TransformShapeToPolygon( *aResult, aLayer, inflate, ERROR );
}
}
}
@ -971,90 +1023,67 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
// Note: IsOnLayer() checks relevant mask layers of untented vias
if( !via->IsOnLayer( layer ) )
if( !via->IsOnLayer( aLayer ) )
continue;
int clearance = via->GetSolderMaskExpansion();
// add shapes with their exact mask layer size in initialPolys
via->TransformShapeToPolygon( initialPolys, layer, clearance, maxError, ERROR_OUTSIDE );
if( inflate != 0 )
via->TransformShapeToPolygon( exactPolys, aLayer, clearance, ERROR );
// add shapes inflated by aMinThickness/2 in areas
clearance += inflate;
via->TransformShapeToPolygon( areas, layer, clearance, maxError, ERROR_OUTSIDE );
via->TransformShapeToPolygon( *aResult, aLayer, clearance + inflate, ERROR );
}
// Add filled zone areas.
#if 0 // Set to 1 if a solder mask expansion must be applied to zones on solder mask
int zone_margin = aBoard->GetDesignSettings().m_SolderMaskExpansion;
#else
int zone_margin = 0;
#endif
for( const BOARD_ITEM* item : aBoard->Drawings() )
{
if( item->IsOnLayer( layer ) )
if( item->IsOnLayer( aLayer ) )
{
if( item->Type() == PCB_TEXT_T )
{
const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item );
// add shapes with their exact mask layer size in initialPolys
text->TransformTextToPolySet( initialPolys, 0, maxError, ERROR_OUTSIDE );
if( inflate != 0 )
text->TransformTextToPolySet( exactPolys, 0, ERROR );
// add shapes inflated by aMinThickness/2 in areas
text->TransformTextToPolySet( areas, inflate, maxError, ERROR_OUTSIDE );
text->TransformTextToPolySet( *aResult, inflate, ERROR );
}
else
{
// add shapes with their exact mask layer size in initialPolys
item->TransformShapeToPolygon( initialPolys, layer, 0, maxError,
ERROR_OUTSIDE );
if( inflate != 0 )
item->TransformShapeToPolygon( exactPolys, aLayer, 0, ERROR );
// add shapes inflated by aMinThickness/2 in areas
item->TransformShapeToPolygon( areas, layer, inflate, maxError, ERROR_OUTSIDE );
item->TransformShapeToPolygon( *aResult, aLayer, inflate, ERROR );
}
}
}
// Add filled zone areas.
for( ZONE* zone : aBoard->Zones() )
{
if( zone->GetIsRuleArea() )
continue;
if( !zone->IsOnLayer( layer ) )
if( !zone->IsOnLayer( aLayer ) )
continue;
// add shapes inflated by aMinThickness/2 in areas
zone->TransformSmoothedOutlineToPolygon( areas, inflate + zone_margin, maxError,
ERROR_OUTSIDE, boardOutline );
if( inflate != 0 )
zone->TransformSmoothedOutlineToPolygon( exactPolys, 0, ERROR, boardOutline );
// add shapes with their exact mask layer size in initialPolys
zone->TransformSmoothedOutlineToPolygon( initialPolys, zone_margin, maxError,
ERROR_OUTSIDE, boardOutline );
zone->TransformSmoothedOutlineToPolygon( *aResult, inflate, ERROR, boardOutline );
}
}
// 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();
areas.Deflate( inflate, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
// Merge all polygons
aResult->Simplify();
// To avoid a lot of code, use a ZONE 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 zone( aBoard );
zone.SetMinThickness( 0 ); // trace polygons only
zone.SetLayer( layer );
// 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();
itemplotter.PlotZone( &zone, layer, areas );
if( inflate != 0 )
{
aResult->Deflate( inflate, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
// Add back in the exact polys. This is mandatory because inflate/deflate transform is
// not perfect, and we want the initial areas perfectly kept.
aResult->BooleanAdd( exactPolys );
}
#undef ERROR
}