Fix island removal when copper->edge clearance is 0

Just testing a single point to find copper that is outside the board works
until the arc->segment conversion moves the board outline just enough to
have the filled copper slightly (few IU) outside of the converted outline.

Instead, we do an expensive check of comparing the size of the
intersection between the island and the island's intersection with the
board.  If the intersection is at least half as large as the original
island, we can say with certainty that the island is inside.  The margin
could be much smaller than half but this preserves the intent without
picking an arbitrary number that might be violated

Fixes https://gitlab.com/kicad/code/kicad/issues/13717
This commit is contained in:
Seth Hillbrand 2023-02-01 08:35:54 -08:00
parent fcefb5f86d
commit e60152e351
3 changed files with 83 additions and 16 deletions

View File

@ -235,7 +235,7 @@ public:
VECTOR2<T> operator*( const T& aFactor ) const;
/// Division with a factor
VECTOR2<T> operator/( const T& aFactor ) const;
VECTOR2<T> operator/( double aFactor ) const;
/// Equality operator
bool operator==( const VECTOR2<T>& aVector ) const;
@ -466,7 +466,7 @@ VECTOR2<T> VECTOR2<T>::operator*( const T& aFactor ) const
template <class T>
VECTOR2<T> VECTOR2<T>::operator/( const T& aFactor ) const
VECTOR2<T> VECTOR2<T>::operator/( double aFactor ) const
{
if( std::is_integral<T>::value )
return VECTOR2<T>( KiROUND( x / aFactor ), KiROUND( y / aFactor ) );

View File

@ -562,10 +562,10 @@ double SHAPE_POLY_SET::Area()
for( int i = 0; i < OutlineCount(); i++ )
{
area += Outline( i ).Area();
area += Outline( i ).Area( true );
for( int j = 0; j < HoleCount( i ); j++ )
area -= Hole( i, j ).Area();
area -= Hole( i, j ).Area( true );
}
return area;

View File

@ -541,7 +541,13 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
// Now remove islands which are either outside the board edge or fail to meet the minimum
// area requirements
//
using island_check_return = std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, int>>;
std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, double>> polys_to_check;
// rough estimate to save re-allocation time
polys_to_check.reserve( m_board->GetCopperLayerCount() * aZones.size() );
for( ZONE* zone : aZones )
{
LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask( MAX_CU_LAYERS );
@ -556,28 +562,89 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
continue;
std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
polys_to_check.emplace_back( std::move( zone->GetFilledPolysList( layer ) ), minArea );
}
}
for( int ii = poly->OutlineCount() - 1; ii >= 0; ii-- )
auto island_lambda = [&]( int aStart, int aEnd ) -> island_check_return
{
island_check_return retval;
for( int ii = aStart; ii < aEnd && !cancelled; ++ii )
{
std::vector<SHAPE_LINE_CHAIN>& island = poly->Polygon( ii );
auto [poly, minArea] = polys_to_check[ii];
if( island.empty()
|| !m_boardOutline.Contains( island.front().CPoint( 0 ), -1, 1 )
|| island.front().Area( true ) < minArea )
for( int jj = poly->OutlineCount() - 1; jj >= 0; jj-- )
{
poly->DeletePolygonAndTriangulationData( ii, false );
SHAPE_POLY_SET island;
SHAPE_POLY_SET intersection;
const SHAPE_LINE_CHAIN& test_poly = poly->Polygon( jj ).front();
double island_area = test_poly.Area();
if( island_area < minArea )
continue;
island.AddOutline( test_poly );
intersection.BooleanIntersection( m_boardOutline, island, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
// Nominally, all of these areas should be either inside or outside the board outline. So this test
// should be able to just compare areas (if they are equal, you are inside). But in practice,
// we sometimes can have slight overlap at the edges. So testing against half-size area is
// a fail-safe
if( intersection.Area() < island_area / 2.0 )
retval.emplace_back( poly, jj );
}
}
poly->UpdateTriangulationDataHash();
zone->CalculateFilledArea();
return retval;
};
if( m_progressReporter && m_progressReporter->IsCancelled() )
return false;
auto island_returns = tp.parallelize_loop( 0, polys_to_check.size(), island_lambda );
cancelled = false;
// Allow island removal threads to finish
for( size_t ii = 0; ii < island_returns.size(); ++ii )
{
std::future<island_check_return>& ret = island_returns[ii];
if( ret.valid() )
{
std::future_status status = ret.wait_for( std::chrono::seconds( 0 ) );
while( status != std::future_status::ready )
{
if( m_progressReporter )
{
m_progressReporter->KeepRefreshing();
if( m_progressReporter->IsCancelled() )
cancelled = true;
}
status = ret.wait_for( std::chrono::milliseconds( 100 ) );
}
}
}
if( cancelled )
return false;
for( size_t ii = 0; ii < island_returns.size(); ++ii )
{
std::future<island_check_return>& ret = island_returns[ii];
if( ret.valid() )
{
for( auto action_item : ret.get() )
action_item.first->DeletePolygonAndTriangulationData( action_item.second, true );
}
}
for( ZONE* zone : aZones )
zone->CalculateFilledArea();
if( aCheck )
{
bool outOfDate = false;