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:
parent
17b0dabec3
commit
845130ba9e
common
include
libs/kimath
pcbnew
qa
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -185,6 +185,13 @@
|
||||
],
|
||||
"zones_allow_external_fillets": false
|
||||
},
|
||||
"ipc2581": {
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": ""
|
||||
},
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
|
@ -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 )
|
||||
|
Loading…
Reference in New Issue
Block a user