7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-02 18:46:54 +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; return false;
// Fully delegated to the parent // 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() ); PlotInteractiveLayer( m_board, m_plotter, GetPlotOptions() );
return true; 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. * @param aPlotOpt is the plot options (files, sketch). Has meaning for some formats only.
*/ */
void PlotOneBoardLayer( BOARD* aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer, 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. * Plot copper or technical layers.

View File

@ -46,12 +46,84 @@
#include <gbr_metadata.h> #include <gbr_metadata.h>
#include <advanced_config.h> #include <advanced_config.h>
/*
* Plot a solder mask layer. Solder mask layers have a minimum thickness value and cannot be void GenerateLayerPoly( SHAPE_POLY_SET* aResult, BOARD *aBoard, PCB_LAYER_ID aLayer,
* drawn like standard layers, unless the minimum thickness is 0. 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, void PlotSolderMaskLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness ); 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, 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() ); && !aPlotOptions.GetLayerSelection().ClearNonCopperLayers().empty() );
for( PCB_LAYER_ID layer : aLayers ) for( PCB_LAYER_ID layer : aLayers )
PlotOneBoardLayer( aBoard, aPlotter, layer, aPlotOptions ); PlotOneBoardLayer( aBoard, aPlotter, layer, aPlotOptions, layer == aLayers[0] );
if( plot_mark ) 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" ), properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), _( "Library Description" ),
fp->GetLibDescription() ) ); fp->GetLibDescription() ) );
properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), _( "Keywords" ),
_( "Keywords" ),
fp->GetKeywords() ) ); fp->GetKeywords() ) );
#endif #endif
// Draw items are plotted with a position offset. So we need to move // 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, 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; PCB_PLOT_PARAMS plotOpt = aPlotOpt;
int soldermask_min_thickness = aBoard->GetDesignSettings().m_SolderMaskMinWidth;
// Set a default color and the text mode for this layer // Set a default color and the text mode for this layer
aPlotter->SetColor( BLACK ); aPlotter->SetColor( BLACK );
@ -179,7 +238,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
else else
plotOpt.SetSkipPlotNPTH_Pads( true ); plotOpt.SetSkipPlotNPTH_Pads( true );
plotLayer( layer_mask, plotOpt ); PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
} }
else else
{ {
@ -194,15 +253,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
plotOpt.SetDXFPlotPolygonMode( true ); plotOpt.SetDXFPlotPolygonMode( true );
// Plot solder mask: // Plot solder mask:
if( soldermask_min_thickness == 0 ) PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt );
{
plotLayer( layer_mask, plotOpt );
}
else
{
PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt,
soldermask_min_thickness );
}
break; break;
@ -216,36 +267,45 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
// Use outline mode for DXF // Use outline mode for DXF
plotOpt.SetDXFPlotPolygonMode( true ); plotOpt.SetDXFPlotPolygonMode( true );
plotLayer( layer_mask, plotOpt ); PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
break; break;
case F_SilkS: case F_SilkS:
case B_SilkS: case B_SilkS:
plotLayer( layer_mask, plotOpt ); if( plotOpt.GetSubtractMaskFromSilk() )
// Gerber: Subtract soldermask from silkscreen if enabled
if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER
&& plotOpt.GetSubtractMaskFromSilk() )
{ {
if( aLayer == F_SilkS ) if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER && isPrimaryLayer )
layer_mask = LSET( { F_Mask } ); {
// 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 else
layer_mask = LSET( { B_Mask } ); {
PlotClippedSilkLayer( aBoard, aPlotter, layer_mask, plotOpt );
}
// Create the mask to subtract by creating a negative layer polarity break;
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 );
} }
PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
break; break;
case Dwgs_User: case Dwgs_User:
@ -259,7 +319,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
case F_Fab: case F_Fab:
case B_Fab: case B_Fab:
default: default:
plotLayer( layer_mask, plotOpt ); PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
break; break;
} }
} }
@ -858,59 +918,57 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
/** /**
* Plot a solder mask layer. * Generates a SHAPE_POLY_SET representing the plotted items on a 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,
void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, bool aPlotFPText, bool aPlotReferences, bool aPlotValues )
const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness )
{ {
#define ERROR maxError, ERROR_OUTSIDE
int maxError = aBoard->GetDesignSettings().m_MaxError; int maxError = aBoard->GetDesignSettings().m_MaxError;
PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
SHAPE_POLY_SET buffer; SHAPE_POLY_SET buffer;
SHAPE_POLY_SET* boardOutline = nullptr; SHAPE_POLY_SET* boardOutline = nullptr;
int inflate = 0;
if( aBoard->GetBoardPolygonOutlines( buffer ) ) if( aBoard->GetBoardPolygonOutlines( buffer ) )
boardOutline = &buffer; boardOutline = &buffer;
// We remove 1nm as we expand both sides of the shapes, so allowing for a strictly greater if( aLayer == F_Mask || aLayer == B_Mask )
// than or equal comparison in the shape separation (boolean add) {
int inflate = aMinThickness / 2 - 1; // 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)
BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); inflate = aBoard->GetDesignSettings().m_SolderMaskMinWidth / 2 - 1;
itemplotter.SetLayerSet( aLayerMask ); }
// Build polygons for each pad shape. The size of the shape on solder mask should be size // 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. // 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 // 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 // Will contain exact shapes of all items on solder mask. We add this back in at the end just
// value to build final polygons // to make sure that any artefacts introduced by the inflate/deflate don't remove parts of the
SHAPE_POLY_SET areas; // individual shapes.
SHAPE_POLY_SET exactPolys;
// Will contain exact shapes of all items on solder mask
SHAPE_POLY_SET initialPolys;
auto handleFPTextItem = auto handleFPTextItem =
[&]( const PCB_TEXT& aText ) [&]( const PCB_TEXT& aText )
{ {
if( !itemplotter.GetPlotFPText() ) if( !aPlotFPText )
return; return;
if( aText.GetText() == wxT( "${REFERENCE}" ) && !itemplotter.GetPlotReference() ) if( !aText.IsVisible() )
return; return;
if( aText.GetText() == wxT( "${VALUE}" ) && !itemplotter.GetPlotValue() ) if( aText.GetText() == wxT( "${REFERENCE}" ) && !aPlotReferences )
return; return;
// add shapes with their exact mask layer size in initialPolys if( aText.GetText() == wxT( "${VALUE}" ) && !aPlotValues )
aText.TransformTextToPolySet( initialPolys, 0, maxError, ERROR_OUTSIDE ); return;
// add shapes inflated by aMinThickness/2 in areas if( inflate != 0 )
aText.TransformTextToPolySet( areas, inflate, maxError, ERROR_OUTSIDE ); aText.TransformTextToPolySet( exactPolys, 0, ERROR );
aText.TransformTextToPolySet( *aResult, inflate, ERROR );
}; };
// Generate polygons with arcs inside the shape or exact shape to minimize shape changes // 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 // Plot footprint pads and graphics
for( const FOOTPRINT* footprint : aBoard->Footprints() ) for( const FOOTPRINT* footprint : aBoard->Footprints() )
{ {
// add shapes with their exact mask layer size in initialPolys if( inflate != 0 )
footprint->TransformPadsToPolySet( initialPolys, layer, 0, maxError, ERROR_OUTSIDE ); footprint->TransformPadsToPolySet( exactPolys, aLayer, 0, ERROR );
// add shapes inflated by aMinThickness/2 in areas
footprint->TransformPadsToPolySet( areas, layer, inflate, maxError, ERROR_OUTSIDE ); footprint->TransformPadsToPolySet( *aResult, aLayer, inflate, ERROR );
for( const PCB_FIELD* field : footprint->GetFields() ) for( const PCB_FIELD* field : footprint->GetFields() )
{ {
if( field->IsReference() && !itemplotter.GetPlotReference() ) if( field->IsReference() && !aPlotReferences )
continue; continue;
if( field->IsValue() && !itemplotter.GetPlotValue() ) if( field->IsValue() && !aPlotValues )
continue; continue;
if( !field->IsVisible() && !itemplotter.GetPlotInvisibleText() ) if( field->IsOnLayer( aLayer ) )
continue;
if( field->IsOnLayer( layer ) )
handleFPTextItem( static_cast<const PCB_TEXT&>( *field ) ); handleFPTextItem( static_cast<const PCB_TEXT&>( *field ) );
} }
for( const BOARD_ITEM* item : footprint->GraphicalItems() ) for( const BOARD_ITEM* item : footprint->GraphicalItems() )
{ {
if( item->IsOnLayer( layer ) ) if( item->IsOnLayer( aLayer ) )
{ {
if( item->Type() == PCB_TEXT_T ) if( item->Type() == PCB_TEXT_T )
{ {
@ -950,13 +1005,10 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
} }
else else
{ {
// add shapes with their exact mask layer size in initialPolys if( inflate != 0 )
item->TransformShapeToPolygon( initialPolys, layer, 0, maxError, item->TransformShapeToPolygon( exactPolys, aLayer, 0, ERROR );
ERROR_OUTSIDE );
// add shapes inflated by aMinThickness/2 in areas item->TransformShapeToPolygon( *aResult, aLayer, inflate, ERROR );
item->TransformShapeToPolygon( areas, layer, inflate, maxError,
ERROR_OUTSIDE );
} }
} }
} }
@ -971,90 +1023,67 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_VIA* via = static_cast<const PCB_VIA*>( track ); const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
// Note: IsOnLayer() checks relevant mask layers of untented vias // Note: IsOnLayer() checks relevant mask layers of untented vias
if( !via->IsOnLayer( layer ) ) if( !via->IsOnLayer( aLayer ) )
continue; continue;
int clearance = via->GetSolderMaskExpansion(); int clearance = via->GetSolderMaskExpansion();
// add shapes with their exact mask layer size in initialPolys if( inflate != 0 )
via->TransformShapeToPolygon( initialPolys, layer, clearance, maxError, ERROR_OUTSIDE ); via->TransformShapeToPolygon( exactPolys, aLayer, clearance, ERROR );
// add shapes inflated by aMinThickness/2 in areas via->TransformShapeToPolygon( *aResult, aLayer, clearance + inflate, ERROR );
clearance += inflate;
via->TransformShapeToPolygon( areas, layer, clearance, maxError, ERROR_OUTSIDE );
} }
// 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() ) for( const BOARD_ITEM* item : aBoard->Drawings() )
{ {
if( item->IsOnLayer( layer ) ) if( item->IsOnLayer( aLayer ) )
{ {
if( item->Type() == PCB_TEXT_T ) if( item->Type() == PCB_TEXT_T )
{ {
const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item ); const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item );
// add shapes with their exact mask layer size in initialPolys if( inflate != 0 )
text->TransformTextToPolySet( initialPolys, 0, maxError, ERROR_OUTSIDE ); text->TransformTextToPolySet( exactPolys, 0, ERROR );
// add shapes inflated by aMinThickness/2 in areas text->TransformTextToPolySet( *aResult, inflate, ERROR );
text->TransformTextToPolySet( areas, inflate, maxError, ERROR_OUTSIDE );
} }
else else
{ {
// add shapes with their exact mask layer size in initialPolys if( inflate != 0 )
item->TransformShapeToPolygon( initialPolys, layer, 0, maxError, item->TransformShapeToPolygon( exactPolys, aLayer, 0, ERROR );
ERROR_OUTSIDE );
// add shapes inflated by aMinThickness/2 in areas item->TransformShapeToPolygon( *aResult, aLayer, inflate, ERROR );
item->TransformShapeToPolygon( areas, layer, inflate, maxError, ERROR_OUTSIDE );
} }
} }
} }
// Add filled zone areas.
for( ZONE* zone : aBoard->Zones() ) for( ZONE* zone : aBoard->Zones() )
{ {
if( zone->GetIsRuleArea() ) if( zone->GetIsRuleArea() )
continue; continue;
if( !zone->IsOnLayer( layer ) ) if( !zone->IsOnLayer( aLayer ) )
continue; continue;
// add shapes inflated by aMinThickness/2 in areas if( inflate != 0 )
zone->TransformSmoothedOutlineToPolygon( areas, inflate + zone_margin, maxError, zone->TransformSmoothedOutlineToPolygon( exactPolys, 0, ERROR, boardOutline );
ERROR_OUTSIDE, boardOutline );
// add shapes with their exact mask layer size in initialPolys zone->TransformSmoothedOutlineToPolygon( *aResult, inflate, ERROR, boardOutline );
zone->TransformSmoothedOutlineToPolygon( initialPolys, zone_margin, maxError,
ERROR_OUTSIDE, boardOutline );
} }
} }
// Merge all polygons: After deflating, not merged (not overlapping) polygons will have the // Merge all polygons
// initial shape (with perhaps small changes due to deflating transform) aResult->Simplify();
areas.Simplify();
areas.Deflate( inflate, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
// To avoid a lot of code, use a ZONE to handle and plot polygons, because our polygons look if( inflate != 0 )
// exactly like filled areas in zones. {
// Note, also this code is not optimized: it creates a lot of copy/duplicate data. aResult->Deflate( inflate, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
// However it is not complex, and fast enough for plot purposes (copy/convert data is only a // Add back in the exact polys. This is mandatory because inflate/deflate transform is
// very small calculation time for these calculations). // not perfect, and we want the initial areas perfectly kept.
ZONE zone( aBoard ); aResult->BooleanAdd( exactPolys );
zone.SetMinThickness( 0 ); // trace polygons only }
zone.SetLayer( layer ); #undef ERROR
// 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 );
} }