From 107d206dba647849fe2fb42cd23ecf9e47edd97c Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Tue, 25 Jun 2019 21:09:03 +0100 Subject: [PATCH] Back to the drawing board for zone filling. This one doesn't cut any corners so should be "correct". And it turns out to be pretty fast. --- pcbnew/zone_filler.cpp | 146 +++++++++++++++++++---------------------- pcbnew/zone_filler.h | 19 +----- 2 files changed, 71 insertions(+), 94 deletions(-) diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 49f93e588a..15cd28d5de 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -463,7 +463,7 @@ void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, int aGap, bool aIgnoreLineWidt * Removes thermal reliefs from the shape for any pads connected to the zone. Does NOT add * in spokes, which must be done later. */ -void ZONE_FILLER::knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ) +void ZONE_FILLER::knockoutThermalReliefs( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ) { SHAPE_POLY_SET holes; @@ -477,10 +477,7 @@ void ZONE_FILLER::knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& } holes.Simplify( SHAPE_POLY_SET::PM_FAST ); - - // Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons - // needed by Gerber files and Fracture() - aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST ); } @@ -488,10 +485,8 @@ void ZONE_FILLER::knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& * Removes clearance from the shape for copper items which share the zone's layer but are * not connected to it. */ -void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ) +void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles ) { - SHAPE_POLY_SET holes; - int zone_clearance = aZone->GetClearance(); int edgeClearance = m_board->GetDesignSettings().m_CopperEdgeClearance; int zone_to_edgecut_clearance = std::max( aZone->GetZoneClearance(), edgeClearance ); @@ -547,7 +542,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S item_boundingbox.Inflate( pad->GetClearance() ); if( item_boundingbox.Intersects( zone_boundingbox ) ) - addKnockout( pad, gap, holes ); + addKnockout( pad, gap, aHoles ); } } } @@ -566,7 +561,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S EDA_RECT item_boundingbox = track->GetBoundingBox(); if( item_boundingbox.Intersects( zone_boundingbox ) ) - track->TransformShapeWithClearanceToPolygon( holes, gap, m_low_def ); + track->TransformShapeWithClearanceToPolygon( aHoles, gap, m_low_def ); } // Add graphic item clearances. They are by definition unconnected, and have no clearance @@ -592,7 +587,7 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S ignoreLineWidth = true; } - addKnockout( aItem, gap, ignoreLineWidth, holes ); + addKnockout( aItem, gap, ignoreLineWidth, aHoles ); }; for( auto module : m_board->Modules() ) @@ -644,14 +639,10 @@ void ZONE_FILLER::knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_S if( zone->GetIsKeepout() || sameNet ) useNetClearance = false; - zone->TransformOutlinesShapeWithClearanceToPolygon( holes, minClearance, useNetClearance ); + zone->TransformOutlinesShapeWithClearanceToPolygon( aHoles, minClearance, useNetClearance ); } - holes.Simplify( SHAPE_POLY_SET::PM_FAST ); - - // Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons - // needed by Gerber files and Fracture() - aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + aHoles.Simplify( SHAPE_POLY_SET::PM_FAST ); } @@ -673,8 +664,8 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, { m_high_def = m_board->GetDesignSettings().m_MaxError; m_low_def = std::min( ARC_LOW_DEF, int( m_high_def*1.5 ) ); // Reasonable value - int outline_half_thickness = aZone->GetMinThickness() / 2; + int numSegs = std::max( GetArcToSegmentCount( outline_half_thickness, m_high_def, 360.0 ), 6 ); std::unique_ptr dumper( new SHAPE_FILE_IO( s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) ); @@ -683,55 +674,63 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, dumper->BeginGroup( "clipper-zone" ); SHAPE_POLY_SET solidAreas = aSmoothedOutline; - std::deque thermalSpokes; + std::deque thermalSpokes; + SHAPE_POLY_SET clearanceHoles; - int numSegs = std::max( GetArcToSegmentCount( outline_half_thickness, m_high_def, 360.0 ), 6 ); - - knockoutThermals( aZone, solidAreas ); + knockoutThermalReliefs( aZone, solidAreas ); if( s_DumpZonesWhenFilling ) dumper->Write( &solidAreas, "solid-areas-minus-thermal-reliefs" ); - buildThermalSpokes( aZone, thermalSpokes ); - - knockoutCopperItems( aZone, solidAreas ); + buildCopperItemClearances( aZone, clearanceHoles ); if( s_DumpZonesWhenFilling ) - dumper->Write( &solidAreas, "solid-areas-minus-clearances" ); + dumper->Write( &solidAreas, "clearance holes" ); - if( thermalSpokes.size() ) + SHAPE_POLY_SET testAreas = solidAreas; + testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); + + // Remove areas that don't meet minimum-width criteria + testAreas.Inflate( -outline_half_thickness, numSegs, true ); + testAreas.Inflate( outline_half_thickness, numSegs, true ); + + buildThermalSpokes( aZone, thermalSpokes ); + + for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes ) { - SHAPE_POLY_SET amalgamatedSpokes; - - for( THERMAL_SPOKE& spoke : thermalSpokes ) + if( testAreas.Contains( spoke.CPoint( 3 ), -1, false, true ) ) { - // Add together all spokes whose endpoints lie within the zone's filled area - if( solidAreas.Contains( spoke.m_TestPtA, -1, false, true ) - && solidAreas.Contains( spoke.m_TestPtB, -1, false, true ) ) - { - amalgamatedSpokes.AddOutline( spoke.m_Outline ); - } + solidAreas.AddOutline( spoke ); + continue; } - amalgamatedSpokes.Simplify( SHAPE_POLY_SET::PM_FAST ); - - // Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons - // needed by Gerber files and Fracture() - solidAreas.BooleanAdd( amalgamatedSpokes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + for( const SHAPE_LINE_CHAIN& other : thermalSpokes ) + { + if( &other != &spoke && other.PointInside( spoke.CPoint( 3 ), 1 ) ) + { + solidAreas.AddOutline( spoke ); + break; + } + } } - solidAreas.Inflate( -outline_half_thickness, numSegs ); solidAreas.Simplify( SHAPE_POLY_SET::PM_FAST ); if( s_DumpZonesWhenFilling ) dumper->Write( &solidAreas, "solid-areas-with-thermal-spokes" ); + solidAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); + solidAreas.Inflate( -outline_half_thickness, numSegs ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &solidAreas, "solid-areas-before-hatching" ); + // Now remove the non filled areas due to the hatch pattern if( aZone->GetFillMode() == ZFM_HATCH_PATTERN ) addHatchFillTypeOnZone( aZone, solidAreas ); if( s_DumpZonesWhenFilling ) - dumper->Write( &solidAreas, "solid-areas-minus-hatching" ); + dumper->Write( &solidAreas, "solid-areas-after-hatching" ); SHAPE_POLY_SET areas_fractured = solidAreas; @@ -810,7 +809,7 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol * Function buildThermalSpokes */ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, - std::deque& aSpokesList ) + std::deque& aSpokesList ) { auto zoneBB = aZone->GetBoundingBox(); int zone_clearance = aZone->GetZoneClearance(); @@ -858,59 +857,50 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, for( int i = 0; i < 4; i++ ) { - THERMAL_SPOKE spoke; + SHAPE_LINE_CHAIN spoke; // polygons are rectangles with width of copper bridge value switch( i ) { case 0: // lower stub - spoke.m_Outline.Append( +spoke_half_w, 0 ); - spoke.m_Outline.Append( -spoke_half_w, 0 ); - spoke.m_Outline.Append( -spoke_half_w, reliefBB.GetBottom() ); - spoke.m_Outline.Append( +spoke_half_w, reliefBB.GetBottom() ); - spoke.m_TestPtA = { epsilon - spoke_half_w, reliefBB.GetBottom() }; - spoke.m_TestPtB = { spoke_half_w - epsilon, reliefBB.GetBottom() }; + spoke.Append( +spoke_half_w, 0 ); + spoke.Append( -spoke_half_w, 0 ); + spoke.Append( -spoke_half_w, reliefBB.GetBottom() ); + spoke.Append( 0, reliefBB.GetBottom() ); // test pt + spoke.Append( +spoke_half_w, reliefBB.GetBottom() ); break; case 1: // upper stub - spoke.m_Outline.Append( +spoke_half_w, 0 ); - spoke.m_Outline.Append( -spoke_half_w, 0 ); - spoke.m_Outline.Append( -spoke_half_w, reliefBB.GetTop() ); - spoke.m_Outline.Append( +spoke_half_w, reliefBB.GetTop() ); - spoke.m_TestPtA = { epsilon - spoke_half_w, reliefBB.GetTop() }; - spoke.m_TestPtB = { spoke_half_w - epsilon, reliefBB.GetTop() }; + spoke.Append( +spoke_half_w, 0 ); + spoke.Append( -spoke_half_w, 0 ); + spoke.Append( -spoke_half_w, reliefBB.GetTop() ); + spoke.Append( 0, reliefBB.GetTop() ); // test pt + spoke.Append( +spoke_half_w, reliefBB.GetTop() ); break; case 2: // right stub - spoke.m_Outline.Append( 0, spoke_half_w ); - spoke.m_Outline.Append( 0, -spoke_half_w ); - spoke.m_Outline.Append( reliefBB.GetRight(), -spoke_half_w ); - spoke.m_Outline.Append( reliefBB.GetRight(), spoke_half_w ); - spoke.m_TestPtA = { reliefBB.GetRight(), epsilon - spoke_half_w }; - spoke.m_TestPtB = { reliefBB.GetRight(), spoke_half_w - epsilon }; + spoke.Append( 0, spoke_half_w ); + spoke.Append( 0, -spoke_half_w ); + spoke.Append( reliefBB.GetRight(), -spoke_half_w ); + spoke.Append( reliefBB.GetRight(), 0 ); // test pt + spoke.Append( reliefBB.GetRight(), spoke_half_w ); break; case 3: // left stub - spoke.m_Outline.Append( 0, spoke_half_w ); - spoke.m_Outline.Append( 0, -spoke_half_w ); - spoke.m_Outline.Append( reliefBB.GetLeft(), -spoke_half_w ); - spoke.m_Outline.Append( reliefBB.GetLeft(), spoke_half_w ); - spoke.m_TestPtA = { reliefBB.GetLeft(), epsilon - spoke_half_w }; - spoke.m_TestPtB = { reliefBB.GetLeft(), spoke_half_w - epsilon }; + spoke.Append( 0, spoke_half_w ); + spoke.Append( 0, -spoke_half_w ); + spoke.Append( reliefBB.GetLeft(), -spoke_half_w ); + spoke.Append( reliefBB.GetLeft(), 0 ); // test pt + spoke.Append( reliefBB.GetLeft(), spoke_half_w ); break; } - for( int j = 0; j < spoke.m_Outline.PointCount(); j++ ) + for( int j = 0; j < spoke.PointCount(); j++ ) { - RotatePoint( spoke.m_Outline.Point( j ), spokeAngle ); - spoke.m_Outline.Point( j ) += padPos + pad->GetOffset(); + RotatePoint( spoke.Point( j ), spokeAngle ); + spoke.Point( j ) += padPos + pad->GetOffset(); } - RotatePoint( spoke.m_TestPtA, spokeAngle ); - spoke.m_TestPtA += padPos + pad->GetOffset(); - RotatePoint( spoke.m_TestPtB, spokeAngle ); - spoke.m_TestPtB += padPos + pad->GetOffset(); - - spoke.m_Outline.SetClosed( true ); + spoke.SetClosed( true ); aSpokesList.push_back( std::move( spoke ) ); } diff --git a/pcbnew/zone_filler.h b/pcbnew/zone_filler.h index 0ef41a891e..fed695fb4c 100644 --- a/pcbnew/zone_filler.h +++ b/pcbnew/zone_filler.h @@ -35,19 +35,6 @@ class COMMIT; class SHAPE_POLY_SET; class SHAPE_LINE_CHAIN; -struct THERMAL_SPOKE -{ - SHAPE_LINE_CHAIN m_Outline; - VECTOR2I m_TestPtA; - VECTOR2I m_TestPtB; - - THERMAL_SPOKE() - { - m_TestPtA = { 0, 0 }; - m_TestPtB = { 0, 0 }; - } -}; - class ZONE_FILLER { @@ -64,9 +51,9 @@ private: void addKnockout( BOARD_ITEM* aItem, int aGap, bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles ); - void knockoutThermals( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ); + void knockoutThermalReliefs( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ); - void knockoutCopperItems( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill ); + void buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles ); /** * Function computeRawFilledArea @@ -86,7 +73,7 @@ private: * Function buildThermalSpokes * Constructs a list of all thermal spokes for the given zone. */ - void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque& aSpokes ); + void buildThermalSpokes( const ZONE_CONTAINER* aZone, std::deque& aSpokes ); /** * Build the filled solid areas polygons from zone outlines (stored in m_Poly)