From f6dac9eb13717ef149fe485289addc2747843fae Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Tue, 8 Mar 2022 18:06:05 +0000 Subject: [PATCH] Support text knockouts in non-copper zones. --- pcbnew/zone_filler.cpp | 159 +++++++++++++++++++++++++++-------------- pcbnew/zone_filler.h | 10 +-- 2 files changed, 113 insertions(+), 56 deletions(-) diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 9b1ea47d72..75f4bf896b 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -960,7 +960,7 @@ void ZONE_FILLER::subtractHigherPriorityZones( const ZONE* aZone, PCB_LAYER_ID a SHAPE_POLY_SET d = a; \ d.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \ d.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \ - aRawPolys = d; \ + aFillPolys = d; \ return false; \ } \ } @@ -977,11 +977,9 @@ void ZONE_FILLER::subtractHigherPriorityZones( const ZONE* aZone, PCB_LAYER_ID a * 5 - Removes unconnected copper islands, deleting any affected spokes * 6 - Adds in the remaining spokes */ -bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, - PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, +bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, const SHAPE_POLY_SET& aSmoothedOutline, - const SHAPE_POLY_SET& aMaxExtents, - SHAPE_POLY_SET& aRawPolys ) + const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys ) { m_maxError = m_board->GetDesignSettings().m_MaxError; @@ -1010,14 +1008,14 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, std::deque thermalSpokes; SHAPE_POLY_SET clearanceHoles; - aRawPolys = aSmoothedOutline; - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In1_Cu, wxT( "smoothed-outline" ) ); + aFillPolys = aSmoothedOutline; + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) ); if( m_progressReporter && m_progressReporter->IsCancelled() ) return false; - knockoutThermalReliefs( aZone, aLayer, aRawPolys, thermalConnectionPads, noConnectionPads ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) ); + knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) ); if( m_progressReporter && m_progressReporter->IsCancelled() ) return false; @@ -1036,7 +1034,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary // because the "real" subtract-clearance-holes has to be done after the spokes are added. static const bool USE_BBOX_CACHES = true; - SHAPE_POLY_SET testAreas = aRawPolys; + SHAPE_POLY_SET testAreas = aFillPolys; testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) ); @@ -1070,7 +1068,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, if( m_debugZoneFiller ) debugSpokes.AddOutline( spoke ); - aRawPolys.AddOutline( spoke ); + aFillPolys.AddOutline( spoke ); continue; } @@ -1090,7 +1088,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, if( m_debugZoneFiller ) debugSpokes.AddOutline( spoke ); - aRawPolys.AddOutline( spoke ); + aFillPolys.AddOutline( spoke ); break; } } @@ -1101,14 +1099,14 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, if( m_progressReporter && m_progressReporter->IsCancelled() ) return false; - aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In8_Cu, wxT( "after-spoke-trimming" ) ); + aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) ); // Prune features that don't meet minimum-width criteria if( half_min_width - epsilon > epsilon ) - aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy ); + aFillPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In9_Cu, wxT( "deflated" ) ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) ); if( m_progressReporter && m_progressReporter->IsCancelled() ) return false; @@ -1116,7 +1114,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, // Now remove the non filled areas due to the hatch pattern if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN ) { - if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aRawPolys ) ) + if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) ) return false; } @@ -1125,22 +1123,98 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, // Re-inflate after pruning of areas that don't meet minimum-width criteria if( half_min_width - epsilon > epsilon ) - aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy ); + aFillPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In15_Cu, wxT( "after-reinflating" ) ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) ); // Ensure additive changes (thermal stubs and particularly inflating acute corners) do not // add copper outside the zone boundary or inside the clearance holes - aRawPolys.BooleanIntersection( aMaxExtents, SHAPE_POLY_SET::PM_FAST ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In16_Cu, wxT( "after-trim-to-outline" ) ); - aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) ); + aFillPolys.BooleanIntersection( aMaxExtents, SHAPE_POLY_SET::PM_FAST ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) ); + aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) ); // Lastly give any same-net but higher-priority zones control over their own area. - subtractHigherPriorityZones( aZone, aLayer, aRawPolys ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) ); + subtractHigherPriorityZones( aZone, aLayer, aFillPolys ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) ); - aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST ); + aFillPolys.Fracture( SHAPE_POLY_SET::PM_FAST ); + return true; +} + + +bool ZONE_FILLER::fillNonCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, + const SHAPE_POLY_SET& aSmoothedOutline, + SHAPE_POLY_SET& aFillPolys ) +{ + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox(); + SHAPE_POLY_SET clearanceHoles; + long ticker = 0; + + auto checkForCancel = + [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool + { + return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled(); + }; + + auto knockoutGraphicClearance = + [&]( BOARD_ITEM* aItem ) + { + if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer ) + && aItem->GetBoundingBox().Intersects( zone_boundingbox ) ) + { + DRC_CONSTRAINT cc = bds.m_DRCEngine->EvalRules( MECHANICAL_CLEARANCE_CONSTRAINT, + aZone, aItem, aLayer ); + + addKnockout( aItem, aLayer, cc.GetValue().Min(), false, clearanceHoles ); + } + }; + + for( FOOTPRINT* footprint : m_board->Footprints() ) + { + if( checkForCancel( m_progressReporter ) ) + return false; + + knockoutGraphicClearance( &footprint->Reference() ); + knockoutGraphicClearance( &footprint->Value() ); + + for( BOARD_ITEM* item : footprint->GraphicalItems() ) + knockoutGraphicClearance( item ); + } + + for( BOARD_ITEM* item : m_board->Drawings() ) + { + if( checkForCancel( m_progressReporter ) ) + return false; + + knockoutGraphicClearance( item ); + } + + aFillPolys = aSmoothedOutline; + aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); + + // Features which are min_width should survive pruning; features that are *less* than + // min_width should not. Therefore we subtract epsilon from the min_width when + // deflating/inflating. + int half_min_width = aZone->GetMinThickness() / 2; + int epsilon = Millimeter2iu( 0.001 ); + int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, FULL_CIRCLE ); + + aFillPolys.Deflate( half_min_width - epsilon, numSegs ); + + // Remove the non filled areas due to the hatch pattern + if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN ) + { + if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys ) ) + return false; + } + + // Re-inflate after pruning of areas that don't meet minimum-width criteria + if( half_min_width - epsilon > epsilon ) + aFillPolys.Inflate( half_min_width - epsilon, numSegs ); + + aFillPolys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); return true; } @@ -1176,27 +1250,8 @@ bool ZONE_FILLER::fillSingleZone( ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_S } else { - // Features which are min_width should survive pruning; features that are *less* than - // min_width should not. Therefore we subtract epsilon from the min_width when - // deflating/inflating. - int half_min_width = aZone->GetMinThickness() / 2; - int epsilon = Millimeter2iu( 0.001 ); - int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, FULL_CIRCLE ); - - smoothedPoly.Deflate( half_min_width - epsilon, numSegs ); - - // Remove the non filled areas due to the hatch pattern - if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN ) - addHatchFillTypeOnZone( aZone, aLayer, debugLayer, smoothedPoly ); - - // Re-inflate after pruning of areas that don't meet minimum-width criteria - if( half_min_width - epsilon > epsilon ) - smoothedPoly.Inflate( half_min_width - epsilon, numSegs ); - - aFillPolys = smoothedPoly; - - aFillPolys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - aZone->SetNeedRefill( false ); + if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) ) + aZone->SetNeedRefill( false ); } return true; @@ -1324,7 +1379,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer, - PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aRawPolys ) + PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys ) { // Build grid: @@ -1340,7 +1395,7 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer int linethickness = thickness - aZone->GetMinThickness(); int gridsize = thickness + aZone->GetHatchGap(); - SHAPE_POLY_SET filledPolys = aRawPolys; + SHAPE_POLY_SET filledPolys = aFillPolys; // Use a area that contains the rotated bbox by orientation, and after rotate the result // by -orientation. if( !aZone->GetHatchOrientation().IsZero() ) @@ -1462,7 +1517,7 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer // The fill has already been deflated to ensure GetMinThickness() so we just have to // account for anything beyond that. - SHAPE_POLY_SET deflatedFilledPolys = aRawPolys; + SHAPE_POLY_SET deflatedFilledPolys = aFillPolys; deflatedFilledPolys.Deflate( outline_margin - aZone->GetMinThickness(), 16 ); holes.BooleanIntersection( deflatedFilledPolys, SHAPE_POLY_SET::PM_FAST ); DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) ); @@ -1545,8 +1600,8 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to // generate strictly simple polygons needed by Gerber files and Fracture() - aRawPolys.BooleanSubtract( aRawPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In14_Cu, wxT( "after-hatching" ) ); + aFillPolys.BooleanSubtract( aFillPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) ); return true; } diff --git a/pcbnew/zone_filler.h b/pcbnew/zone_filler.h index 9a954b171f..fe30c9035d 100644 --- a/pcbnew/zone_filler.h +++ b/pcbnew/zone_filler.h @@ -88,8 +88,10 @@ private: */ bool fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, const SHAPE_POLY_SET& aSmoothedOutline, - const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aRawPolys ); + const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys ); + bool fillNonCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, + const SHAPE_POLY_SET& aSmoothedOutline, SHAPE_POLY_SET& aFillPolys ); /** * Function buildThermalSpokes * Constructs a list of all thermal spokes for the given zone. @@ -108,7 +110,7 @@ private: * @param aFillPolys: A reference to a SHAPE_POLY_SET buffer to store polygons with no holes * (holes are linked to main outline by overlapping segments, and these polygons are shrunk * by aZone->GetMinThickness() / 2 to be drawn with a outline thickness = aZone->GetMinThickness() - * aFinalPolys are polygons that will be drawn on screen and plotted + * aFillPolys are polygons that will be drawn on screen and plotted */ bool fillSingleZone( ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aFillPolys ); @@ -116,11 +118,11 @@ private: * for zones having the ZONE_FILL_MODE::ZONE_FILL_MODE::HATCH_PATTERN, create a grid pattern * in filled areas of aZone, giving to the filled polygons a fill style like a grid * @param aZone is the zone to modify - * @param aRawPolys: A reference to a SHAPE_POLY_SET buffer containing the initial + * @param aFillPolys: A reference to a SHAPE_POLY_SET buffer containing the initial * filled areas, and after adding the grid pattern, the modified filled areas with holes */ bool addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, - SHAPE_POLY_SET& aRawPolys ); + SHAPE_POLY_SET& aFillPolys ); BOARD* m_board; SHAPE_POLY_SET m_boardOutline; // the board outlines, if exists