Remove a long-standing hack to keep divots out of adjacent zones.

The new algorithm unions any adjacent zones before doing the
chamfer/fillet and then subtracts the other zones back out afterwards.

Fixes https://gitlab.com/kicad/code/kicad/issues/3812
This commit is contained in:
Jeff Young 2020-08-12 19:42:40 +01:00
parent a6d44676b3
commit 463100d67f
12 changed files with 82 additions and 143 deletions

View File

@ -774,7 +774,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
auto layerContainer = m_layers_poly.find( layer );
if( layerContainer != m_layers_poly.end() )
zone->TransformSolidAreasShapesToPolygonSet( layer, *layerContainer->second );
zone->TransformSolidAreasShapesToPolygon( layer, *layerContainer->second );
}
}
}
@ -1050,7 +1050,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
if( !zone->IsOnLayer( curr_layer_id ) )
continue;
zone->TransformSolidAreasShapesToPolygonSet( curr_layer_id, *layerPoly );
zone->TransformSolidAreasShapesToPolygon( curr_layer_id, *layerPoly );
}
}

View File

@ -1151,11 +1151,9 @@ class SHAPE_POLY_SET : public SHAPE
* returns a chamfered version of the aIndex-th polygon.
* @param aDistance is the chamfering distance.
* @param aIndex is the index of the polygon to be chamfered.
* @param aPreserveCorners an optional set of corners which should not be chamfered.
* @return POLYGON - A polygon containing the chamfered version of the aIndex-th polygon.
*/
POLYGON ChamferPolygon( unsigned int aDistance, int aIndex,
std::set<VECTOR2I>* aPreserveCorners );
POLYGON ChamferPolygon( unsigned int aDistance, int aIndex );
/**
* Function Fillet
@ -1163,32 +1161,26 @@ class SHAPE_POLY_SET : public SHAPE
* @param aRadius is the fillet radius.
* @param aErrorMax is the maximum allowable deviation of the polygon from the circle
* @param aIndex is the index of the polygon to be filleted
* @param aPreserveCorners an optional set of corners which should not be filleted.
* @return POLYGON - A polygon containing the filleted version of the aIndex-th polygon.
*/
POLYGON FilletPolygon( unsigned int aRadius, int aErrorMax, int aIndex,
std::set<VECTOR2I>* aPreserveCorners = nullptr );
POLYGON FilletPolygon( unsigned int aRadius, int aErrorMax, int aIndex );
/**
* Function Chamfer
* returns a chamfered version of the polygon set.
* @param aDistance is the chamfering distance.
* @param aPreserveCorners an optional set of corners which should not be chamfered.
* @return SHAPE_POLY_SET - A set containing the chamfered version of this set.
*/
SHAPE_POLY_SET Chamfer( int aDistance,
std::set<VECTOR2I>* aPreserveCorners = nullptr );
SHAPE_POLY_SET Chamfer( int aDistance );
/**
* Function Fillet
* returns a filleted version of the polygon set.
* @param aRadius is the fillet radius.
* @param aErrorMax is the maximum allowable deviation of the polygon from the circle
* @param aPreserveCorners an optional set of corners which should not be filleted.
* @return SHAPE_POLY_SET - A set containing the filleted version of this set.
*/
SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners = nullptr );
SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax );
/**
* Function DistanceToPolygon
@ -1306,12 +1298,10 @@ class SHAPE_POLY_SET : public SHAPE
* @param aIndex is the index of the polygon that will be chamfered/filleted.
* @param aErrorMax is the maximum allowable deviation of the polygon from the circle
* if aMode = FILLETED. If aMode = CHAMFERED, it is unused.
* @param aPreserveCorners an optional set of corners which should be skipped.
* @return POLYGON - the chamfered/filleted version of the polygon.
*/
POLYGON chamferFilletPolygon( CORNER_MODE aMode, unsigned int aDistance,
int aIndex, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners );
int aIndex, int aErrorMax );
///> Returns true if the polygon set has any holes that touch share a vertex.
bool hasTouchingHoles( const POLYGON& aPoly ) const;

View File

@ -1556,18 +1556,16 @@ int SHAPE_POLY_SET::TotalVertices() const
}
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex,
std::set<VECTOR2I>* aPreserveCorners )
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
{
return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0, aPreserveCorners );
return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
}
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
int aIndex,
std::set<VECTOR2I>* aPreserveCorners )
int aIndex )
{
return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax, aPreserveCorners );
return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
}
@ -1676,32 +1674,30 @@ bool SHAPE_POLY_SET::IsVertexInHole( int aGlobalIdx )
}
SHAPE_POLY_SET SHAPE_POLY_SET::Chamfer( int aDistance, std::set<VECTOR2I>* aPreserveCorners )
SHAPE_POLY_SET SHAPE_POLY_SET::Chamfer( int aDistance )
{
SHAPE_POLY_SET chamfered;
for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx, aPreserveCorners ) );
chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx ) );
return chamfered;
}
SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners )
SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
{
SHAPE_POLY_SET filleted;
for( size_t idx = 0; idx < m_polys.size(); idx++ )
filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx, aPreserveCorners ) );
filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx ) );
return filleted;
}
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode,
unsigned int aDistance, int aIndex, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners )
unsigned int aDistance, int aIndex, int aErrorMax )
{
// Null segments create serious issues in calculations. Remove them:
RemoveNullSegments();
@ -1728,12 +1724,6 @@ SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode,
int x1 = currContour.CPoint( currVertex ).x;
int y1 = currContour.CPoint( currVertex ).y;
if( aPreserveCorners && aPreserveCorners->count( VECTOR2I( x1, y1 ) ) > 0 )
{
newContour.Append( x1, y1 );
continue;
}
// Indices for previous and next vertices.
int prevVertex;
int nextVertex;

View File

@ -91,7 +91,7 @@ void BOARD::ConvertBrdLayerToPolygonalContours( PCB_LAYER_ID aLayer, SHAPE_POLY_
ZONE_CONTAINER* zone = GetArea( ii );
if( zone->GetLayerSet().test( aLayer ) )
zone->TransformSolidAreasShapesToPolygonSet( aLayer, aOutlines );
zone->TransformSolidAreasShapesToPolygon( aLayer, aOutlines );
}
// convert graphic items on copper layers (texts)
@ -244,7 +244,7 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet( PCB_LAYER_ID aLaye
}
void ZONE_CONTAINER::TransformSolidAreasShapesToPolygonSet( PCB_LAYER_ID aLayer,
void ZONE_CONTAINER::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer,
SHAPE_POLY_SET& aCornerBuffer,
int aError ) const
{

View File

@ -1164,65 +1164,55 @@ bool ZONE_CONTAINER::IsIsland( PCB_LAYER_ID aLayer, int aPolyIdx )
}
/*
* Some intersecting zones, despite being on the same layer with the same net, cannot be
* merged due to other parameters such as fillet radius. The copper pour will end up
* effectively merged though, so we want to keep the corners of such intersections sharp.
*/
void ZONE_CONTAINER::GetColinearCorners( BOARD* aBoard, std::set<VECTOR2I>& aCorners )
void ZONE_CONTAINER::GetInteractingZones( PCB_LAYER_ID aLayer,
std::vector<ZONE_CONTAINER*>* aZones ) const
{
int epsilon = Millimeter2iu( 0.001 );
// Things get messy when zone of different nets intersect. To do it right we'd need to
// run our colinear test with the final filled regions rather than the outline regions.
// However, since there's no order dependance the only way to do that is to iterate
// through successive zone fills until the results are no longer changing -- and that's
// not going to happen. So we punt and ignore any "messy" corners.
std::set<VECTOR2I> colinearCorners;
std::set<VECTOR2I> messyCorners;
for( ZONE_CONTAINER* candidate : aBoard->Zones() )
for( ZONE_CONTAINER* candidate : GetBoard()->Zones() )
{
if( candidate == this )
continue;
if( candidate->GetLayerSet() != GetLayerSet() )
if( !candidate->GetLayerSet().test( aLayer ) )
continue;
if( candidate->GetIsKeepout() != GetIsKeepout() )
if( candidate->GetIsKeepout() )
continue;
if( candidate->GetNetCode() != GetNetCode() )
continue;
for( auto iter = m_Poly->CIterate(); iter; iter++ )
{
if( candidate->m_Poly->Collide( iter.Get(), epsilon ) )
{
if( candidate->GetNetCode() == GetNetCode() )
colinearCorners.insert( VECTOR2I( iter.Get() ) );
else
messyCorners.insert( VECTOR2I( iter.Get() ) );
aZones->push_back( candidate );
break;
}
}
}
for( VECTOR2I corner : colinearCorners )
{
if( messyCorners.count( corner ) == 0 )
aCorners.insert( corner );
}
}
bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly,
std::set<VECTOR2I>* aPreserveCorners ) const
bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly, PCB_LAYER_ID aLayer ) const
{
if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations do not like it ...
return false;
std::vector<ZONE_CONTAINER*> interactingZones;
GetInteractingZones( aLayer, &interactingZones );
aSmoothedPoly = *m_Poly;
for( ZONE_CONTAINER* zone : interactingZones )
aSmoothedPoly.BooleanAdd( *zone->Outline(), SHAPE_POLY_SET::PM_FAST );
// Make a smoothed polygon out of the user-drawn polygon if required
switch( m_cornerSmoothingType )
{
case ZONE_SETTINGS::SMOOTHING_CHAMFER:
aSmoothedPoly = m_Poly->Chamfer( m_cornerRadius, aPreserveCorners );
aSmoothedPoly = aSmoothedPoly.Chamfer( m_cornerRadius );
break;
case ZONE_SETTINGS::SMOOTHING_FILLET:
@ -1233,7 +1223,7 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly,
if( board )
maxError = board->GetDesignSettings().m_MaxError;
aSmoothedPoly = m_Poly->Fillet( m_cornerRadius, maxError, aPreserveCorners );
aSmoothedPoly = aSmoothedPoly.Fillet( m_cornerRadius, maxError );
break;
}
default:
@ -1242,10 +1232,13 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly,
// We can avoid issues by creating a very small chamfer which remove acute angles,
// or left it without chamfer and use only CPOLYGONS_LIST::InflateOutline to create
// clearance areas
aSmoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ), aPreserveCorners );
aSmoothedPoly = aSmoothedPoly.Chamfer( Millimeter2iu( 0.0 ) );
break;
}
if( interactingZones.size() )
aSmoothedPoly.BooleanIntersection( *m_Poly, SHAPE_POLY_SET::PM_FAST );
return true;
};
@ -1283,11 +1276,11 @@ double ZONE_CONTAINER::CalculateFilledArea()
* @param aPreserveCorners an optional set of corners which should not be chamfered/filleted
*/
void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
int aClearance, std::set<VECTOR2I>* aPreserveCorners ) const
int aClearance ) const
{
// Creates the zone outline polygon (with holes if any)
SHAPE_POLY_SET polybuffer;
BuildSmoothedPoly( polybuffer, aPreserveCorners );
BuildSmoothedPoly( polybuffer, GetLayer() );
// Calculate the polygon with clearance
// holes are linked to the main outline, so only one polygon is created.

View File

@ -318,16 +318,15 @@ public:
return HitTestCutout( VECTOR2I( aRefPos.x, aRefPos.y ), aOutlineIdx, aHoleIdx );
}
/**
* Some intersecting zones, despite being on the same layer with the same net, cannot be
* merged due to other parameters such as fillet radius. The copper pour will end up
* effectively merged though, so we want to keep the corners of such intersections sharp.
* effectively merged though, so we need to do some calculations with them in mind.
*/
void GetColinearCorners( BOARD* aBoard, std::set<VECTOR2I>& colinearCorners );
void GetInteractingZones( PCB_LAYER_ID aLayer, std::vector<ZONE_CONTAINER*>* aZones ) const;
/**
* Function TransformSolidAreasShapesToPolygonSet
* Function TransformSolidAreasShapesToPolygon
* Convert solid areas full shapes to polygon set
* (the full shape is the polygon area with a thick outline)
* Used in 3D view
@ -336,8 +335,8 @@ public:
* @param aCornerBuffer = a buffer to store the polygons
* @param aError = Maximum error allowed between true arc and polygon approx
*/
void TransformSolidAreasShapesToPolygonSet( PCB_LAYER_ID aLayer,
SHAPE_POLY_SET& aCornerBuffer, int aError = ARC_HIGH_DEF ) const;
void TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aCornerBuffer,
int aError = ARC_HIGH_DEF ) const;
/**
* Function TransformOutlinesShapeWithClearanceToPolygon
@ -348,14 +347,9 @@ public:
* Circles (vias) and arcs (ends of tracks) are approximated by segments
* @param aCornerBuffer = a buffer to store the polygon
* @param aMinClearanceValue = the min clearance around outlines
* @param aUseNetClearance = true to use a clearance which is the max value between
* aMinClearanceValue and the net clearance
* false to use aMinClearanceValue only
* @param aPreserveCorners an optional set of corners which should not be smoothed.
* if both aMinClearanceValue = 0 and aUseNetClearance = false: create the zone outline polygon.
*/
void TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
int aMinClearanceValue, std::set<VECTOR2I>* aPreserveCorners = nullptr ) const;
int aMinClearanceValue ) const;
/**
* Function TransformShapeWithClearanceToPolygon
@ -652,12 +646,8 @@ public:
/**
* Function GetSmoothedPoly
* returns a pointer to the corner-smoothed version of m_Poly.
* @param aPreserveCorners - set of corners which should /not/ be smoothed
* @return SHAPE_POLY_SET* - pointer to the polygon.
*/
bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly,
std::set<VECTOR2I>* aPreserveCorners ) const;
bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly, PCB_LAYER_ID aLayer ) const;
void SetCornerSmoothingType( int aType ) { m_cornerSmoothingType = aType; };

View File

@ -685,10 +685,7 @@ void DRC::testZones( BOARD_COMMIT& aCommit )
}
ZONE_CONTAINER* zoneRef = m_pcb->GetArea( ii );
std::set<VECTOR2I> colinearCorners;
zoneRef->GetColinearCorners( m_pcb, colinearCorners );
zoneRef->BuildSmoothedPoly( smoothed_polys[ii], &colinearCorners );
zoneRef->BuildSmoothedPoly( smoothed_polys[ii], zoneRef->GetLayer() );
}
// iterate through all areas

View File

@ -868,18 +868,10 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
if( zone->GetLayer() != layer )
continue;
// Some intersecting zones, despite being on the same layer, cannot be
// merged due to other parameters such as fillet radius. The filled areas will end up
// effectively merged though, so we want to keep the corners of such intersections sharp.
std::set<VECTOR2I> colinearCorners;
zone->GetColinearCorners( aBoard, colinearCorners );
// add shapes inflated by aMinThickness/2 in areas
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin,
&colinearCorners );
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin );
// add shapes with their exact mask layer size in initialPolys
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin,
&colinearCorners );
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin );
}
int maxError = aBoard->GetDesignSettings().m_MaxError;

View File

@ -736,13 +736,14 @@ bool PNS_KICAD_IFACE_BASE::syncZone( PNS::NODE* aWorld, ZONE_CONTAINER* aZone )
if( !aZone->GetIsKeepout() || !aZone->GetDoNotAllowTracks() )
return false;
// Some intersecting zones, despite being on the same layer with the same net, cannot be
// merged due to other parameters such as fillet radius. The copper pour will end up
// effectively merged though, so we want to keep the corners of such intersections sharp.
std::set<VECTOR2I> colinearCorners;
aZone->GetColinearCorners( m_board, colinearCorners );
LSET layers = aZone->GetLayerSet();
aZone->BuildSmoothedPoly( poly, &colinearCorners );
for( int layer = F_Cu; layer <= B_Cu; layer++ )
{
if( ! layers[ layer ] )
continue;
aZone->BuildSmoothedPoly( poly, ToLAYER_ID( layer ) );
poly.CacheTriangulation();
if( !poly.IsTriangulationUpToDate() )
@ -759,13 +760,6 @@ bool PNS_KICAD_IFACE_BASE::syncZone( PNS::NODE* aWorld, ZONE_CONTAINER* aZone )
return false;
}
LSET layers = aZone->GetLayerSet();
for( int layer = F_Cu; layer <= B_Cu; layer++ )
{
if ( ! layers[layer] )
continue;
for( int outline = 0; outline < poly.OutlineCount(); outline++ )
{
auto tri = poly.TriangulatedPolygon( outline );

View File

@ -741,7 +741,6 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, PCB_LA
*/
void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
const SHAPE_POLY_SET& aSmoothedOutline,
std::set<VECTOR2I>* aPreserveCorners,
SHAPE_POLY_SET& aRawPolys,
SHAPE_POLY_SET& aFinalPolys )
{
@ -928,15 +927,13 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys )
{
SHAPE_POLY_SET smoothedPoly;
std::set<VECTOR2I> colinearCorners;
aZone->GetColinearCorners( m_board, colinearCorners );
/*
* convert outlines + holes to outlines without holes (adding extra segments if necessary)
* m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
* this zone
*/
if ( !aZone->BuildSmoothedPoly( smoothedPoly, &colinearCorners ) )
if ( !aZone->BuildSmoothedPoly( smoothedPoly, aLayer ) )
return false;
if( m_progressReporter && m_progressReporter->IsCancelled() )
@ -944,8 +941,7 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
if( aZone->IsOnCopperLayer() )
{
computeRawFilledArea( aZone, aLayer, smoothedPoly, &colinearCorners, aRawPolys,
aFinalPolys );
computeRawFilledArea( aZone, aLayer, smoothedPoly, aRawPolys, aFinalPolys );
}
else
{

View File

@ -70,7 +70,6 @@ private:
*/
void computeRawFilledArea( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
const SHAPE_POLY_SET& aSmoothedOutline,
std::set<VECTOR2I>* aPreserveCorners,
SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys );
/**

View File

@ -670,10 +670,8 @@ void test::DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZones()
{
ZONE_CONTAINER* zone = m_board->GetArea( ii );
ZONE_CONTAINER* zoneRef = m_board->GetArea( ii );
std::set<VECTOR2I> colinearCorners;
zoneRef->GetColinearCorners( m_board, colinearCorners );
zoneRef->BuildSmoothedPoly( smoothed_polys[ii], &colinearCorners );
zoneRef->BuildSmoothedPoly( smoothed_polys[ii], zoneRef->GetLayer() );
}
// iterate through all areas