diff --git a/pcbnew/pcbplot.cpp b/pcbnew/pcbplot.cpp index 232005114d..0ea739c932 100644 --- a/pcbnew/pcbplot.cpp +++ b/pcbnew/pcbplot.cpp @@ -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; } diff --git a/pcbnew/pcbplot.h b/pcbnew/pcbplot.h index 22f9a71101..9d6edb14cc 100644 --- a/pcbnew/pcbplot.h +++ b/pcbnew/pcbplot.h @@ -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. diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index 89469ad0f6..4f934677fb 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -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 }