7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-21 00:21:25 +00:00

ADDED: pcbnew fill avoids kissing fills

Previously, fills could end up just barely touching, leading to DRC
errors even if there was enough room to fill the remaining space.  This
was due to how we shrink/expand the zones to remove small features.  By
adding a zero-width line between points that should be connected, we
expand back to the correct width.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/14130
This commit is contained in:
Seth Hillbrand 2024-07-09 20:45:05 -07:00
parent 17b0dabec3
commit 845130ba9e
10 changed files with 249 additions and 14 deletions

View File

@ -115,6 +115,8 @@ static const wxChar EnableAPILogging[] = wxT( "EnableAPILogging" );
static const wxChar MaxFileSystemWatchers[] = wxT( "MaxFileSystemWatchers" );
static const wxChar MinorSchematicGraphSize[] = wxT( "MinorSchematicGraphSize" );
static const wxChar ResolveTextRecursionDepth[] = wxT( "ResolveTextRecursionDepth" );
static const wxChar ZoneConnectionFiller[] = wxT( "ZoneConnectionFiller" );
} // namespace KEYS
@ -276,6 +278,8 @@ ADVANCED_CFG::ADVANCED_CFG()
m_ResolveTextRecursionDepth = 3;
m_ZoneConnectionFiller = false;
loadFromConfigFile();
}
@ -513,6 +517,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
&m_ResolveTextRecursionDepth,
m_ResolveTextRecursionDepth, 0, 10 ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::ZoneConnectionFiller,
&m_ZoneConnectionFiller, m_ZoneConnectionFiller ) );
// Special case for trace mask setting...we just grab them and set them immediately
// Because we even use wxLogTrace inside of advanced config
wxString traceMasks;

View File

@ -626,6 +626,15 @@ public:
*/
int m_ResolveTextRecursionDepth;
/**
* Use the new zone-connection fill routine
*
* Setting name: "ZoneConnectionFiller"
* Valid values: true or false
* Default value: false
*/
bool m_ZoneConnectionFiller;
///@}
private:

View File

@ -1312,6 +1312,14 @@ public:
*/
void RemoveContour( int aContourIdx, int aPolygonIdx = -1 );
/**
* Delete the \a aOutlineIdx-th outline of the set including its contours and holes.
*
* @param aOutlineIdx is the index of the outline to be removed.
*/
void RemoveOutline( int aOutlineIdx );
/**
* Look for null segments; ie, segments whose ends are exactly the same and deletes them.
*

View File

@ -58,11 +58,12 @@ class VERTEX
{
public:
VERTEX( int aIndex, double aX, double aY, VERTEX_SET* aParent ) :
VERTEX( int aIndex, double aX, double aY, VERTEX_SET* aParent, void* aUserData = nullptr ) :
i( aIndex ),
x( aX ),
y( aY ),
parent( aParent )
parent( aParent ),
m_userData( aUserData )
{
}
@ -75,6 +76,8 @@ class VERTEX
}
bool operator!=( const VERTEX& rhs ) const { return !( *this == rhs ); }
void* GetUserData() const { return m_userData; }
/**
* Remove the node from the linked list and z-ordered linked list.
*/
@ -228,6 +231,8 @@ class VERTEX
// previous and next nodes in z-order
VERTEX* prevZ = nullptr;
VERTEX* nextZ = nullptr;
void* m_userData = nullptr;
};
class VERTEX_SET
@ -248,16 +253,19 @@ public:
* @param aIndex the index of the vertex
* @param pt the point to insert
* @param last the last vertex in the list
* @param aUserData user data to associate with the vertex
* @return the newly inserted vertex
*/
VERTEX* insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last );
VERTEX* insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last, void* aUserData = nullptr );
/**
* Create a list of vertices from a line chain
* @param points the line chain to create the list from
* @param aTail the optional vertex to which to append the list
* @param aUserData user data to associate with the vertices
* @return the first vertex in the list
*/
VERTEX* createList( const SHAPE_LINE_CHAIN& points );
VERTEX* createList( const SHAPE_LINE_CHAIN& points, VERTEX* aTail = nullptr, void* aUserData = nullptr );
protected:

View File

@ -2444,6 +2444,12 @@ void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
}
void SHAPE_POLY_SET::RemoveOutline( int aOutlineIdx )
{
m_polys.erase( m_polys.begin() + aOutlineIdx );
}
int SHAPE_POLY_SET::RemoveNullSegments()
{
int removed = 0;

View File

@ -6,9 +6,9 @@ void VERTEX_SET::SetBoundingBox( const BOX2I& aBBox ) { m_bbox = aBBox; }
/**
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
*/
VERTEX* VERTEX_SET::createList( const SHAPE_LINE_CHAIN& points )
VERTEX* VERTEX_SET::createList( const SHAPE_LINE_CHAIN& points, VERTEX* aTail, void* aUserData )
{
VERTEX* tail = nullptr;
VERTEX* tail = aTail;
double sum = 0.0;
// Check for winding order
@ -28,7 +28,7 @@ VERTEX* VERTEX_SET::createList( const SHAPE_LINE_CHAIN& points )
VECTOR2I diff = pt - last_pt;
if( diff.SquaredEuclideanNorm() > m_simplificationLevel )
{
tail = insertVertex( i, pt, tail );
tail = insertVertex( i, pt, tail, aUserData );
last_pt = pt;
}
};
@ -184,9 +184,9 @@ bool VERTEX_SET::middleInside( const VERTEX* a, const VERTEX* b ) const
*
* @return a pointer to the newly created vertex.
*/
VERTEX* VERTEX_SET::insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last )
VERTEX* VERTEX_SET::insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last, void* aUserData )
{
m_vertices.emplace_back( aIndex, pt.x, pt.y, this );
m_vertices.emplace_back( aIndex, pt.x, pt.y, this, aUserData );
VERTEX* p = &m_vertices.back();
@ -208,9 +208,9 @@ VERTEX* VERTEX_SET::insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last )
VERTEX* VERTEX::split( VERTEX* b )
{
parent->m_vertices.emplace_back( i, x, y, parent );
VERTEX* a2 = parent->insertVertex( i, VECTOR2I( x, y ), nullptr );
parent->m_vertices.emplace_back( b->i, b->x, b->y, parent );
parent->m_vertices.emplace_back( i, x, y, parent, m_userData );
VERTEX* a2 = parent->insertVertex( i, VECTOR2I( x, y ), nullptr, m_userData );
parent->m_vertices.emplace_back( b->i, b->x, b->y, parent, m_userData );
VERTEX* b2 = &parent->m_vertices.back();
VERTEX* an = next;
VERTEX* bp = b->prev;

View File

@ -45,11 +45,152 @@
#include <geometry/shape_poly_set.h>
#include <geometry/convex_hull.h>
#include <geometry/geometry_utils.h>
#include <geometry/vertex_set.h>
#include <kidialog.h>
#include <core/thread_pool.h>
#include <math/util.h> // for KiROUND
#include "zone_filler.h"
// Helper classes for connect_nearby_polys
class RESULTS
{
public:
RESULTS( int aOutline1, int aOutline2, int aVertex1, int aVertex2 ) :
m_outline1( aOutline1 ), m_outline2( aOutline2 ),
m_vertex1( aVertex1 ), m_vertex2( aVertex2 )
{
}
bool operator<( const RESULTS& aOther ) const
{
if( m_outline1 != aOther.m_outline1 )
return m_outline1 < aOther.m_outline1;
if( m_outline2 != aOther.m_outline2 )
return m_outline2 < aOther.m_outline2;
if( m_vertex1 != aOther.m_vertex1 )
return m_vertex1 < aOther.m_vertex1;
return m_vertex2 < aOther.m_vertex2;
}
int m_outline1;
int m_outline2;
int m_vertex1;
int m_vertex2;
};
class VERTEX_CONNECTOR : protected VERTEX_SET
{
public:
VERTEX_CONNECTOR( const BOX2I& aBBox, const SHAPE_POLY_SET& aPolys, int aDist ) : VERTEX_SET( 0 )
{
SetBoundingBox( aBBox );
VERTEX* tail = nullptr;
for( int i = 0; i < aPolys.OutlineCount(); i++ )
tail = createList( aPolys.Outline( i ), tail, (void*)( intptr_t )( i ) );
tail->updateList();
m_dist = aDist;
}
VERTEX* getPoint( VERTEX* aPt ) const
{
// z-order range for the current point ± limit bounding box
const int32_t maxZ = zOrder( aPt->x + m_dist, aPt->y + m_dist );
const int32_t minZ = zOrder( aPt->x - m_dist, aPt->y - m_dist );
const SEG::ecoord limit2 = SEG::Square( m_dist );
// first look for points in increasing z-order
VERTEX* p = aPt->nextZ;
SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max();
VERTEX* retval = nullptr;
auto check_pt = [&]( VERTEX* p )
{
VECTOR2D diff( p->x - aPt->x, p->y - aPt->y );
SEG::ecoord dist2 = diff.SquaredEuclideanNorm();
if( dist2 < limit2 && dist2 < min_dist
&& area( p->prev, p, p->next ) < 0
&& !locallyInside( p, aPt ) )
{
min_dist = dist2;
retval = p;
}
};
while( p && p->z <= maxZ )
{
check_pt( p );
p = p->nextZ;
}
p = aPt->prevZ;
while( p && p->z >= minZ )
{
check_pt( p );
p = p->prevZ;
}
return retval;
}
void FindResults()
{
VERTEX* p = m_vertices.front().next;
std::set<VERTEX*> visited;
while( p != &m_vertices.front() )
{
// Skip points that are concave
if( area( p->prev, p, p->next ) >= 0 )
{
p = p->next;
continue;
}
VERTEX* q = nullptr;
if( ( visited.empty() || !visited.contains( p ) ) && ( q = getPoint( p ) ) )
{
visited.insert( p );
if( !visited.contains( q ) &&
m_results.emplace( (intptr_t) p->GetUserData(), (intptr_t) q->GetUserData(),
p->i, q->i ).second )
{
// We don't want to connect multiple points in the same vicinity, so skip
// 2 points before and after each point and match.
visited.insert( p->prev );
visited.insert( p->prev->prev );
visited.insert( p->next );
visited.insert( p->next->next );
visited.insert( q->prev );
visited.insert( q->prev->prev );
visited.insert( q->next );
visited.insert( q->next->next );
visited.insert( q );
}
}
p = p->next;
}
}
std::set<RESULTS> GetResults() const
{
return m_results;
}
private:
std::set<RESULTS> m_results;
int m_dist;
};
ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
m_board( aBoard ),
@ -1419,6 +1560,32 @@ void ZONE_FILLER::subtractHigherPriorityZones( const ZONE* aZone, PCB_LAYER_ID a
}
void ZONE_FILLER::connect_nearby_polys( SHAPE_POLY_SET& aPolys, double aDistance )
{
if( aPolys.OutlineCount() < 1 )
return;
VERTEX_CONNECTOR vs( aPolys.BBoxFromCaches(), aPolys, aDistance );
vs.FindResults();
// This cannot be a reference because we need to do the comparison below while
// changing the values
for( const RESULTS& result : vs.GetResults() )
{
SHAPE_LINE_CHAIN& line1 = aPolys.Outline( result.m_outline1 );
SHAPE_LINE_CHAIN& line2 = aPolys.Outline( result.m_outline2 );
VECTOR2I pt1 = line1.CPoint( result.m_vertex1 );
VECTOR2I pt2 = line2.CPoint( result.m_vertex2 );
// Duplicate the point first, so that we return to the same location
line1.Insert( result.m_vertex1, pt1 );
line1.Insert( result.m_vertex1 + 1, pt2 );
}
}
#define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
{ if( m_debugZoneFiller && aDebugLayer == b ) \
{ \
@ -1615,6 +1782,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
if( m_progressReporter && m_progressReporter->IsCancelled() )
return false;
/* -------------------------------------------------------------------------------------
* Process the hatch pattern (note that we do this while deflated)
*/
@ -1624,6 +1792,21 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) )
return false;
}
else
{
/* -------------------------------------------------------------------------------------
* Connect nearby polygons
*/
if( ADVANCED_CFG::GetCfg().m_ZoneConnectionFiller )
{
aFillPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
connect_nearby_polys( aFillPolys, aZone->GetMinThickness() );
}
DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In10_Cu, wxT( "connected-nearby-polys" ) );
}
if( m_progressReporter && m_progressReporter->IsCancelled() )
return false;

View File

@ -104,6 +104,13 @@ private:
const std::vector<PAD*>& aSpokedPadsList,
std::deque<SHAPE_LINE_CHAIN>& aSpokes );
/**
* Create strands of zero-width between elements of SHAPE_POLY_SET that are within
* aDistance of each other. When we inflate these strands, they will create minimum
* width bands
*/
void connect_nearby_polys( SHAPE_POLY_SET& aPolys, double aDistance );
/**
* Build the filled solid areas polygons from zone outlines (stored in m_Poly)
* The solid areas can be more than one on copper layers, and do not have holes

View File

@ -185,6 +185,13 @@
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [],
"viewports": []
},

View File

@ -50,10 +50,10 @@ BOOST_FIXTURE_TEST_CASE( DRCCopperConn, DRC_REGRESSION_TEST_FIXTURE )
std::vector<std::pair<wxString, int>> tests =
{
{ "issue9870", 13 },
{ "issue9870", 12 },
{ "connection_width_rules", 3 },
{ "issue12831", 0 },
{ "issue14130", 1 }
{ "issue14130", 0 }
};
for( const std::pair<wxString, int>& test : tests )