Update triangulation to handle poly-intersection

Polygon intersections happen against the original outline, not against
the currently remaining polygon.  This avoids pathalogical cases

Adds new simplification system to avoid duplicated points
Adds new edge-splitting algorithm to provide additional fall-back
Verifies that polygon cuts do not swap holes for outlines (negative
area)

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17559
This commit is contained in:
Seth Hillbrand 2024-03-25 17:24:50 -07:00
parent 79174f8223
commit c3f6a84d66
6 changed files with 1120 additions and 118 deletions

View File

@ -81,6 +81,8 @@ public:
/// therefore cannot be polygons
VERTEX* firstVertex = createList( aPoly );
wxLogTrace( TRIANGULATE_TRACE, "Created list with %f area", firstVertex->area() );
if( !firstVertex || firstVertex->prev == firstVertex->next )
return false;
@ -96,7 +98,10 @@ public:
auto retval = earcutList( firstVertex );
if( !retval )
{
wxLogTrace( TRIANGULATE_TRACE, "Tesselation failed, logging remaining vertices" );
logRemaining();
}
m_vertices.clear();
return retval;
@ -266,25 +271,29 @@ private:
}
/**
* Returns the signed area of the polygon connected to the current vertex
*/
double area() const
* Returns the signed area of the polygon connected to the current vertex,
* optionally ending at a specified vertex.
*/
double area( const VERTEX* aEnd = nullptr ) const
{
const VERTEX* p = this;
double a = 0.0;
do
{
a += ( p->x * p->next->y - p->next->x * p->y );
a += ( p->x + p->next->x ) * ( p->next->y - p->y );
p = p->next;
} while( p != this );
} while( p != this && p != aEnd );
if( p != this )
a += ( p->x + aEnd->x ) * ( aEnd->y - p->y );
return a / 2;
}
const size_t i;
const double x;
const double y;
double x;
double y;
POLYGON_TRIANGULATION* parent;
// previous and next vertices nodes in a polygon ring
@ -335,34 +344,42 @@ private:
if( !p.next || p.next == &p || seen.find( &p ) != seen.end() )
continue;
seen.insert( &p );
// Don't both logging tiny areas
if( std::abs( p.area() ) < 10 )
continue;
int count = 1;
wxString msg = wxString::Format( "Remaining: %d,%d,", static_cast<int>( p.x ),
static_cast<int>( p.y ) );
VERTEX* q = p.next;
do
{
msg += wxString::Format( "%d,%d,", static_cast<int>( q->x ),
static_cast<int>( q->y ) );
seen.insert( q );
q = q->next;
count++;
} while( q != &p );
// Don't log anything that only has 2 or fewer points
if( count < 3 )
continue;
msg.RemoveLast();
wxLogTrace( TRIANGULATE_TRACE, msg );
logVertices( &p, &seen );
}
}
void logVertices( VERTEX* aStart, std::set<VERTEX*>* aSeen )
{
if( aSeen && aSeen->count( aStart ) )
return;
if( aSeen )
aSeen->insert( aStart );
int count = 1;
VERTEX* p = aStart->next;
wxString msg = wxString::Format( "Vertices: %d,%d,", static_cast<int>( aStart->x ),
static_cast<int>( aStart->y ) );
do
{
msg += wxString::Format( "%d,%d,", static_cast<int>( p->x ), static_cast<int>( p->y ) );
if( aSeen )
aSeen->insert( p );
p = p->next;
count++;
} while( p != aStart );
if( count < 3 ) // Don't log anything that only has 2 or fewer points
return;
msg.RemoveLast();
wxLogTrace( TRIANGULATE_TRACE, msg );
}
/**
* Iterate through the list to remove NULL triangles if they exist.
*
@ -374,10 +391,18 @@ private:
{
VERTEX* retval = nullptr;
VERTEX* p = aStart->next;
size_t count = 0;
while( p != aStart )
{
if( *p == *( p->next ) || area( p->prev, p, p->next ) == 0.0 )
// We make a dummy triangle that is actually part of the existing line segment
// and measure its area. This will not be exactly zero due to floating point
// errors. We then look for areas that are less than 4 times the area of the
// dummy triangle. For small triangles, this is a small number
VERTEX tmp( 0, 0.5 * ( p->prev->x + p->next->x ), 0.5 * ( p->prev->y + p->next->y ), this );
double null_area = 4.0 * std::abs( area( p->prev, &tmp, p->next ) );
if( *p == *( p->next ) || std::abs( area( p->prev, p, p->next ) ) <= null_area )
{
// This is a spike, remove it, leaving only one point
if( *( p->next ) == *( p->prev ) )
@ -386,6 +411,7 @@ private:
p = p->prev;
p->next->remove();
retval = aStart;
++count;
if( p == p->next )
break;
@ -398,61 +424,29 @@ private:
// We needed an end point above that wouldn't be removed, so
// here we do the final check for this as a Steiner point
if( area( aStart->prev, aStart, aStart->next ) == 0.0 )
VERTEX tmp( 0, 0.5 * ( aStart->prev->x + aStart->next->x ),
0.5 * ( aStart->prev->y + aStart->next->y ), this );
double null_area = 4.0 * std::abs( area( aStart->prev, &tmp, aStart->next ) );
if( std::abs( area( aStart->prev, aStart, aStart->next ) ) <= null_area )
{
retval = p->next;
p->remove();
++count;
}
wxLogTrace( TRIANGULATE_TRACE, "Removed %zu NULL triangles", count );
return retval;
}
/**
* Take a Clipper path and converts it into a circular, doubly-linked list for triangulation.
*/
VERTEX* createList( const ClipperLib::Path& aPath )
{
VERTEX* tail = nullptr;
double sum = 0.0;
auto len = aPath.size();
// Check for winding order
for( size_t i = 0; i < len; i++ )
{
auto p1 = aPath.at( i );
auto p2 = aPath.at( ( i + 1 ) < len ? i + 1 : 0 );
sum += ( ( p2.X - p1.X ) * ( p2.Y + p1.Y ) );
}
if( sum <= 0.0 )
{
for( auto point : aPath )
tail = insertVertex( VECTOR2I( point.X, point.Y ), tail );
}
else
{
for( size_t i = 0; i < len; i++ )
{
auto p = aPath.at( len - i - 1 );
tail = insertVertex( VECTOR2I( p.X, p.Y ), tail );
}
}
if( tail && ( *tail == *tail->next ) )
{
tail->next->remove();
}
return tail;
}
/**
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
*/
VERTEX* createList( const SHAPE_LINE_CHAIN& points )
{
wxLogTrace( TRIANGULATE_TRACE, "Creating list from %d points", points.PointCount() );
VERTEX* tail = nullptr;
double sum = 0.0;
@ -465,17 +459,34 @@ private:
sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
}
VECTOR2I last_pt{ std::numeric_limits<int>::max(), std::numeric_limits<int>::max() };
VECTOR2I::extended_type sq_dist = ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel;
sq_dist *= sq_dist;
auto addVertex = [&]( int i )
{
const VECTOR2I& pt = points.CPoint( i );
VECTOR2I diff = pt - last_pt;
if( diff.SquaredEuclideanNorm() > sq_dist )
{
tail = insertVertex( pt, tail );
last_pt = pt;
}
};
if( sum > 0.0 )
for( int i = points.PointCount() - 1; i >= 0; i--)
tail = insertVertex( points.CPoint( i ), tail );
{
for( int i = points.PointCount() - 1; i >= 0; i-- )
addVertex( i );
}
else
{
for( int i = 0; i < points.PointCount(); i++ )
tail = insertVertex( points.CPoint( i ), tail );
addVertex( i );
}
if( tail && ( *tail == *tail->next ) )
{
tail->next->remove();
}
return tail;
}
@ -495,12 +506,15 @@ private:
*/
bool earcutList( VERTEX* aPoint, int pass = 0 )
{
wxLogTrace( TRIANGULATE_TRACE, "earcutList starting at %p for pass %d", aPoint, pass );
if( !aPoint )
return true;
VERTEX* stop = aPoint;
VERTEX* prev;
VERTEX* next;
int internal_pass = 1;
while( aPoint->prev != aPoint->next )
{
@ -511,7 +525,14 @@ private:
{
// Tiny ears cannot be seen on the screen
if( !isTooSmall( aPoint ) )
{
m_result.AddTriangle( prev->i, aPoint->i, next->i );
}
else
{
wxLogTrace( TRIANGULATE_TRACE, "Ignoring tiny ear with area %f",
area( prev, aPoint, next ) );
}
aPoint->remove();
@ -528,6 +549,9 @@ private:
locallyInside( prev, nextNext ) &&
locallyInside( nextNext, prev ) )
{
wxLogTrace( TRIANGULATE_TRACE,
"Local intersection detected. Merging minor triangle with area %f",
area( prev, aPoint, nextNext ) );
m_result.AddTriangle( prev->i, aPoint->i, nextNext->i );
// remove two nodes involved
@ -548,16 +572,35 @@ private:
*/
if( aPoint == stop )
{
// First, try to remove the remaining steiner points
// If aPoint is a steiner, we need to re-assign both the start and stop points
if( auto newPoint = removeNullTriangles( aPoint ) )
VERTEX* newPoint;
// Removing null triangles will remove steiner points as well as colinear points
// that are three in a row. Because our next step is to subdivide the polygon,
// we need to allow it to add the subdivided points first. This is why we only
// run the RemoveNullTriangles function after the first pass.
if( ( internal_pass == 2 ) && ( newPoint = removeNullTriangles( aPoint ) ) )
{
aPoint = newPoint;
stop = newPoint;
continue;
}
++internal_pass;
// This will subdivide the polygon 2 times. The first pass will add enough points
// such that each edge is less than the average edge length. If this doesn't work
// The next pass will remove the null triangles (above) and subdivide the polygon
// again, this time adding one point to each long edge (and thereby changing the locations)
if( internal_pass < 4 )
{
wxLogTrace( TRIANGULATE_TRACE, "Subdividing polygon" );
subdividePolygon( aPoint, internal_pass );
continue;
}
// If we don't have any NULL triangles left, cut the polygon in two and try again
wxLogTrace( TRIANGULATE_TRACE, "Splitting polygon" );
if( !splitPolygon( aPoint ) )
return false;
@ -661,6 +704,59 @@ private:
return true;
}
/**
* Inserts a new vertex halfway between each existing pair of vertices.
*/
void subdividePolygon( VERTEX* aStart, int pass = 0 )
{
VERTEX* p = aStart;
struct VertexComparator {
bool operator()(const std::pair<VERTEX*,double>& a, const std::pair<VERTEX*,double>& b) const {
return a.second > b.second;
}
};
std::set<std::pair<VERTEX*,double>, VertexComparator> longest;
double avg = 0.0;
do
{
double len = ( p->x - p->next->x ) * ( p->x - p->next->x ) +
( p->y - p->next->y ) * ( p->y - p->next->y );
longest.emplace( p, len );
avg += len;
p = p->next;
} while (p != aStart);
avg /= longest.size();
wxLogTrace( TRIANGULATE_TRACE, "Average length: %f", avg );
for( auto it = longest.begin(); it != longest.end() && it->second > avg; ++it )
{
wxLogTrace( TRIANGULATE_TRACE, "Subdividing edge with length %f", it->second );
VERTEX* a = it->first;
VERTEX* b = a->next;
VERTEX* last = a;
// We adjust the number of divisions based on the pass in order to progressively
// subdivide the polygon when triangulation fails
int divisions = avg / it->second + 2 + pass;
double step = 1.0 / divisions;
for( int i = 1; i < divisions; i++ )
{
double x = a->x * ( 1.0 - step * i ) + b->x * ( step * i );
double y = a->y * ( 1.0 - step * i ) + b->y * ( step * i );
last = insertVertex( VECTOR2I( x, y ), last );
}
}
// update z-order of the vertices
aStart->updateList();
}
/**
* If we cannot find an ear to slice in the current polygon list, we
* use this to split the polygon into two separate lists and slice them each
@ -671,39 +767,61 @@ private:
{
VERTEX* origPoly = start;
// If we have fewer than 4 points, we cannot split the polygon
if( !start || !start->next || start->next == start->prev
|| start->next->next == start->prev )
{
return true;
}
// Our first attempts to split the polygon will be at overlapping points.
// These are natural split points and we only need to switch the loop directions
// to generate two new loops. Since they are overlapping, we are do not
// need to create a new segment to disconnect the two loops.
do
{
VERTEX* nextZ = origPoly->nextZ;
std::vector<VERTEX*> overlapPoints;
VERTEX* z_pt = origPoly;
if( nextZ && nextZ != origPoly->next && nextZ != origPoly->prev && *nextZ == *origPoly )
while ( z_pt->prevZ && *z_pt->prevZ == *origPoly )
z_pt = z_pt->prevZ;
overlapPoints.push_back( z_pt );
while( z_pt->nextZ && *z_pt->nextZ == *origPoly )
{
std::swap( origPoly->next, nextZ->next );
origPoly->next->prev = origPoly;
nextZ->next->prev = nextZ;
origPoly->updateList();
nextZ->updateList();
return earcutList( origPoly ) && earcutList( nextZ );
z_pt = z_pt->nextZ;
overlapPoints.push_back( z_pt );
}
VERTEX* prevZ = origPoly->prevZ;
if( prevZ && prevZ != origPoly->next && prevZ != origPoly->prev && *prevZ == *origPoly )
if( overlapPoints.size() != 2 || overlapPoints[0]->next == overlapPoints[1]
|| overlapPoints[0]->prev == overlapPoints[1] )
{
std::swap( origPoly->next, prevZ->next );
origPoly->next->prev = origPoly;
prevZ->next->prev = prevZ;
origPoly->updateList();
prevZ->updateList();
return earcutList( origPoly ) && earcutList( prevZ );
origPoly = origPoly->next;
continue;
}
origPoly = origPoly->next;
if( overlapPoints[0]->area( overlapPoints[1] ) < 0 || overlapPoints[1]->area( overlapPoints[0] ) < 0 )
{
wxLogTrace( TRIANGULATE_TRACE, "Split generated a hole, skipping" );
origPoly = origPoly->next;
continue;
}
wxLogTrace( TRIANGULATE_TRACE, "Splitting at overlap point %f, %f", overlapPoints[0]->x, overlapPoints[0]->y );
std::swap( overlapPoints[0]->next, overlapPoints[1]->next );
overlapPoints[0]->next->prev = overlapPoints[0];
overlapPoints[1]->next->prev = overlapPoints[1];
overlapPoints[0]->updateList();
overlapPoints[1]->updateList();
logVertices( overlapPoints[0], nullptr );
logVertices( overlapPoints[1], nullptr );
bool retval = earcutList( overlapPoints[0] ) && earcutList( overlapPoints[1] );
wxLogTrace( TRIANGULATE_TRACE, "%s at first overlap split", retval ? "Success" : "Failed" );
return retval;
} while ( origPoly != start );
@ -718,14 +836,17 @@ private:
while( marker != origPoly->prev )
{
// Find a diagonal line that is wholly enclosed by the polygon interior
if( origPoly->i != marker->i && goodSplit( origPoly, marker ) )
if( origPoly->next && origPoly->i != marker->i && goodSplit( origPoly, marker ) )
{
VERTEX* newPoly = origPoly->split( marker );
origPoly->updateList();
newPoly->updateList();
return earcutList( origPoly ) && earcutList( newPoly );
bool retval = earcutList( origPoly ) && earcutList( newPoly );
wxLogTrace( TRIANGULATE_TRACE, "%s at split", retval ? "Success" : "Failed" );
return retval;
}
marker = marker->next;
@ -734,6 +855,7 @@ private:
origPoly = origPoly->next;
} while( origPoly != start );
wxLogTrace( TRIANGULATE_TRACE, "Could not find a valid split point" );
return false;
}
@ -754,9 +876,9 @@ private:
bool local_split = locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
bool same_dir = area( a->prev, a, b->prev ) != 0.0 || area( a, b->prev, b ) != 0.0;
bool has_len = ( *a == *b ) && area( a->prev, a, a->next ) > 0 && area( b->prev, b, b->next ) > 0;
bool pos_area = a->area( b ) > 0 && b->area( a ) > 0;
return no_intersect && local_split && ( same_dir || has_len ) && !a_on_edge && !b_on_edge;
return no_intersect && local_split && ( same_dir || has_len ) && !a_on_edge && !b_on_edge && pos_area;
}
@ -824,20 +946,25 @@ private:
*/
bool intersectsPolygon( const VERTEX* a, const VERTEX* b ) const
{
const VERTEX* p = a->next;
do
for( auto it = m_vertices.begin(); it != m_vertices.end(); )
{
if( p->i != a->i &&
p->next->i != a->i &&
p->i != b->i &&
p->next->i != b->i && intersects( p, p->next, a, b ) )
const VERTEX* p = &*it;
const VERTEX* q = &*( ++it );
if( p->i == a->i || p->i == b->i || q->i == a->i || q->i == b->i )
continue;
if( intersects( p, q, a, b ) )
return true;
}
p = p->next;
} while( p != a );
if( m_vertices.front().i == a->i || m_vertices.front().i == b->i
|| m_vertices.back().i == a->i || m_vertices.back().i == b->i )
{
return false;
}
return false;
return intersects( a, b, &m_vertices.back(), &m_vertices.front() );
}
/**

View File

@ -122,6 +122,18 @@ public:
virtual size_t GetPointCount() const override { return 3; }
virtual size_t GetSegmentCount() const override { return 3; }
double Area() const
{
VECTOR2I& aa = parent->m_vertices[a];
VECTOR2I& bb = parent->m_vertices[b];
VECTOR2I& cc = parent->m_vertices[c];
VECTOR2D ba = bb - aa;
VECTOR2D cb = cc - bb;
return std::abs( cb.Cross( ba ) * 0.5 );
}
int a;
int b;
int c;

View File

@ -3301,6 +3301,11 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
triangulationValid = true;
}
if( triangulationValid && wxLog::IsLevelEnabled(wxLOG_Trace, wxLOG_COMPONENT) )
{
}
return triangulationValid;
};

View File

@ -0,0 +1,764 @@
(kicad_pcb
(version 20240225)
(generator "pcbnew")
(generator_version "8.99")
(general
(thickness 2.93)
(legacy_teardrops no)
)
(paper "A3")
(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal)
(34 "B.Paste" user)
(35 "F.Paste" user)
(36 "B.SilkS" user "B.Silkscreen")
(37 "F.SilkS" user "F.Silkscreen")
(38 "B.Mask" user)
(39 "F.Mask" user)
(40 "Dwgs.User" user "User.Drawings")
(41 "Cmts.User" user "User.Comments")
(42 "Eco1.User" user "User.Eco1")
(44 "Edge.Cuts" user)
(45 "Margin" user)
(46 "B.CrtYd" user "B.Courtyard")
(47 "F.CrtYd" user "F.Courtyard")
(48 "B.Fab" user)
(49 "F.Fab" user)
)
(setup
(stackup
(layer "F.SilkS"
(type "Top Silk Screen")
(color "White")
)
(layer "F.Paste"
(type "Top Solder Paste")
)
(layer "F.Mask"
(type "Top Solder Mask")
(color "Green")
(thickness 0.01)
)
(layer "F.Cu"
(type "copper")
(thickness 0.7)
)
(layer "dielectric 1"
(type "core")
(thickness 1.51)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "B.Cu"
(type "copper")
(thickness 0.7)
)
(layer "B.Mask"
(type "Bottom Solder Mask")
(color "Green")
(thickness 0.01)
)
(layer "B.Paste"
(type "Bottom Solder Paste")
)
(layer "B.SilkS"
(type "Bottom Silk Screen")
(color "White")
)
(copper_finish "None")
(dielectric_constraints no)
)
(pad_to_mask_clearance 0)
(allow_soldermask_bridges_in_footprints no)
(pcbplotparams
(layerselection 0x00010f0_ffffffff)
(plot_on_all_layers_selection 0x0000000_00000000)
(disableapertmacros no)
(usegerberextensions no)
(usegerberattributes yes)
(usegerberadvancedattributes yes)
(creategerberjobfile yes)
(dashed_line_dash_ratio 12.000000)
(dashed_line_gap_ratio 3.000000)
(svgprecision 6)
(plotframeref no)
(viasonmask no)
(mode 1)
(useauxorigin no)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(pdf_front_fp_property_popups yes)
(pdf_back_fp_property_popups yes)
(pdf_metadata yes)
(dxfpolygonmode yes)
(dxfimperialunits yes)
(dxfusepcbnewfont yes)
(psnegative no)
(psa4output no)
(plotreference yes)
(plotvalue yes)
(plotfptext yes)
(plotinvisibletext no)
(sketchpadsonfab no)
(subtractmaskfromsilk no)
(outputformat 1)
(mirror no)
(drillshape 0)
(scaleselection 1)
(outputdirectory "production/")
)
)
(net 0 "")
(net 1 "GND")
(footprint "0IBF_IC_spezial:SO-8_3.9x4.9mm_P1.27mm_IBF"
(layer "B.Cu")
(uuid "1ad96dda-c5a4-4d6f-ba7d-da49edca76c6")
(at 69 -14 -90)
(descr "SO8, IBFEEW standard")
(tags "SOIC SO8")
(property "Reference" "IC5"
(at 0 3.4 -90)
(layer "B.SilkS")
(uuid "91cf874a-2683-4eac-8c02-ec27e7cef146")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "Value" "AD8418AWBRZ"
(at 0 -3.4 -90)
(layer "B.Fab")
(uuid "ae3c89cf-d675-4ab4-8a7d-35f8cb2bc764")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "Footprint" "0IBF_IC_spezial:SO-8_3.9x4.9mm_P1.27mm_IBF"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "c154c976-206c-4254-8621-109b26ffe6e1")
(effects
(font
(size 1.27 1.27)
)
(justify mirror)
)
)
(property "Datasheet" ""
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "d03956b1-495c-450d-9f53-dc64e416b740")
(effects
(font
(size 1.27 1.27)
)
(justify mirror)
)
)
(property "Description" "OPV, current sense over the TOP OPV, differential amplifier, high common mode range (70V) / Gain=20 / bis 250kHz SO8"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "25a61d2e-91cd-458e-996b-07adc5ee1c5f")
(effects
(font
(size 1.27 1.27)
)
(justify mirror)
)
)
(property "Bemerkung" ""
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "92dfeab9-ec22-42da-8058-31270f643fbc")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "MF" "AD"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "59d72731-d1bd-4cf1-8f00-795ecb55befd")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "MPN" "AD8418AWBRZ"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "d3f4f278-9ceb-465c-a04d-50b8dbf3d937")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "RS" "798-9903"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "dadeae0e-1ba2-4823-8a0d-6a2c3e497294")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "Farnell" "2383472"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "bb4037c8-2139-4dd0-8121-203fefeae157")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "mouser" "584-AD8418AWBRZ "
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "5fbe0553-ff44-4294-a653-c897779ca6cd")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "digikey" "505-AD8418AWBRZ-ND"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "be3953fd-f3c4-453c-bf98-83f21661bed2")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "Alternative" "AD8418AWHRZ"
(at 0 0 90)
(unlocked yes)
(layer "B.Fab")
(hide yes)
(uuid "80345cbe-3cff-44c1-a8da-cdc22317152e")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(path "/21ecf582-2fe5-40fc-9946-89f54de57e7c/e362915b-176f-4d63-b94c-3c4bc02f69ce")
(attr smd)
(fp_line
(start -1.4 2.6)
(end -2.3 2.6)
(stroke
(width 0.12)
(type solid)
)
(layer "B.SilkS")
(uuid "59c84261-214c-4045-b201-b933060eca42")
)
(fp_line
(start -1.4 2.6)
(end 1.5 2.6)
(stroke
(width 0.12)
(type solid)
)
(layer "B.SilkS")
(uuid "7770e627-ff1c-4cb5-9985-440e844d900f")
)
(fp_line
(start -1.4 2.6)
(end -1.4 -2.5)
(stroke
(width 0.12)
(type solid)
)
(layer "B.SilkS")
(uuid "deb670a5-2b5e-4af6-aeb7-f0e19f653a5f")
)
(fp_line
(start 1.5 2.6)
(end 1.5 -2.5)
(stroke
(width 0.12)
(type solid)
)
(layer "B.SilkS")
(uuid "9e6fe172-e3f0-4a7d-9a20-69e6090a13b3")
)
(fp_line
(start -1.4 -2.5)
(end 1.5 -2.5)
(stroke
(width 0.12)
(type solid)
)
(layer "B.SilkS")
(uuid "a34eee1c-44e0-4385-a098-6de3bae9ccb8")
)
(fp_circle
(center -0.9 1.9)
(end -0.758579 1.9)
(stroke
(width 0.3)
(type solid)
)
(fill none)
(layer "B.SilkS")
(uuid "4f640165-a9f9-43b2-9a98-99c0a62aca93")
)
(fp_rect
(start 3.6 2.6)
(end -3.6 -2.6)
(stroke
(width 0.05)
(type solid)
)
(fill none)
(layer "B.CrtYd")
(uuid "9fafdfb7-50ee-475d-b7d4-bb5d5f876ddb")
)
(fp_line
(start -2 2.45)
(end -2 -2.45)
(stroke
(width 0.1)
(type solid)
)
(layer "B.Fab")
(uuid "c7436507-07fb-46f8-956f-0311d4aa71e1")
)
(fp_line
(start 1.95 2.45)
(end -2 2.45)
(stroke
(width 0.1)
(type solid)
)
(layer "B.Fab")
(uuid "9eb58b5d-b7c4-44b1-b922-bf6540946081")
)
(fp_line
(start -2 -2.45)
(end 1.95 -2.45)
(stroke
(width 0.1)
(type solid)
)
(layer "B.Fab")
(uuid "2cee7285-1029-4ee3-9c66-f5ab4015596b")
)
(fp_line
(start 1.95 -2.45)
(end 1.95 2.45)
(stroke
(width 0.1)
(type solid)
)
(layer "B.Fab")
(uuid "36e74742-80ac-4087-854e-8c822e8edec0")
)
(fp_rect
(start -2 1.8)
(end -2.7 2)
(stroke
(width 0.1)
(type solid)
)
(fill solid)
(layer "B.Fab")
(uuid "ca52708f-b42d-4fbe-85ef-a9ca4d5eae07")
)
(fp_rect
(start -2 0.530524)
(end -2.7 0.730524)
(stroke
(width 0.1)
(type solid)
)
(fill solid)
(layer "B.Fab")
(uuid "4c71da20-e6d0-44c9-8a28-37334c1f03e8")
)
(fp_rect
(start -2.010594 -0.7)
(end -2.710594 -0.5)
(stroke
(width 0.1)
(type solid)
)
(fill solid)
(layer "B.Fab")
(uuid "4992741c-abd9-455d-a0f3-a58debc209b2")
)
(fp_rect
(start -1.960594 -2)
(end -2.660594 -1.8)
(stroke
(width 0.1)
(type solid)
)
(fill solid)
(layer "B.Fab")
(uuid "cb3d1cf6-2687-4f89-878d-76a2ec02cab9")
)
(fp_circle
(center -0.9 1.9)
(end -0.758579 1.9)
(stroke
(width 0.3)
(type solid)
)
(fill none)
(layer "B.Fab")
(uuid "3ec80412-5185-4a19-b416-225283df27de")
)
(fp_text user "${REFERENCE}"
(at 0 -0.2 0)
(layer "B.Fab")
(uuid "2182389f-6690-4415-9442-9029d6188c5d")
(effects
(font
(size 0.98 0.98)
(thickness 0.15)
)
(justify mirror)
)
)
(pad "2" smd roundrect
(at -2.575 0.635 270)
(size 1.75 0.6)
(layers "B.Cu" "B.Paste" "B.Mask")
(roundrect_rratio 0.25)
(net 1 "GND")
(pinfunction "gnd")
(pintype "input")
(uuid "e5cb8713-1203-435e-89cf-405cc13573f8")
)
(pad "3" smd roundrect
(at -2.575 -0.635 270)
(size 1.75 0.6)
(layers "B.Cu" "B.Paste" "B.Mask")
(roundrect_rratio 0.25)
(net 1 "GND")
(pinfunction "ref2")
(pintype "input")
(uuid "fefd8876-a0f1-45c7-9cb9-4bf1469da825")
)
(model "${KICAD6_3DMODEL_DIR}/Package_SO.3dshapes/SOIC-8_3.9x4.9mm_P1.27mm.wrl"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)
(gr_circle
(center 55 -47)
(end 65 -47)
(locked yes)
(stroke
(width 0.15)
(type default)
)
(fill none)
(layer "Cmts.User")
(uuid "4662a067-9f60-4ec4-80f5-5b883188d85a")
)
(gr_line
(start 78 -119)
(end 78 21)
(stroke
(width 0.15)
(type default)
)
(layer "Cmts.User")
(uuid "bf5a10c5-bece-4392-bf41-15786c10b42f")
)
(gr_line
(start -20 -47)
(end 120 -47)
(locked yes)
(stroke
(width 0.15)
(type default)
)
(layer "Cmts.User")
(uuid "e4ee0deb-a01a-4a3d-9440-f9cf8dec9745")
)
(gr_line
(start 55.25 -120)
(end 55.25 20)
(locked yes)
(stroke
(width 0.15)
(type default)
)
(layer "Cmts.User")
(uuid "f7eb59e2-1cd0-46e1-a39b-01a9441cc9f0")
)
(gr_line
(start 110.5 -95)
(end 104 -101.2)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "19557c0c-8a26-402f-ba86-3c9441f3f322")
)
(gr_line
(start 110.5 -1.27)
(end 110.5 -95)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "2c6ccce8-2443-4f53-bdc6-23b6573b46bd")
)
(gr_line
(start 5 -97.5)
(end 8 -101.2)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "47d9bdef-d743-4104-867d-863ccee27302")
)
(gr_line
(start 0 -1.27)
(end 71.12 -1.27)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "6519683c-afc2-40bd-9759-e72b2bbcbdf8")
)
(gr_line
(start 0 -95)
(end 2 -97.5)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "8a178068-3413-4e86-993a-090afdda555b")
)
(gr_line
(start 5 -97.5)
(end 2 -97.5)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "a10eb751-171b-4a2e-9311-0e6ddf378147")
)
(gr_line
(start 71.12 -1.27)
(end 110.5 -1.27)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "ca421978-203f-416c-adc9-837c0311de64")
)
(gr_line
(start 0 -95)
(end 0 -1.27)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "f2966019-2f39-4eac-89ae-f67c452bf71a")
)
(gr_line
(start 104 -101.2)
(end 8 -101.2)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "f4537f53-f7b5-4dc9-b61b-145ea11df1ed")
)
(gr_text "TOP"
(at -14.3 50.65 0)
(layer "F.Cu")
(uuid "ae1f1aff-d924-4501-93ab-977713ab026e")
(effects
(font
(size 1.5 1.5)
(thickness 0.3)
(bold yes)
)
(justify left bottom)
)
)
(gr_text "Pumpensteuerung SEQU\nBedienteil 140mm v31a"
(at -5.25 52.1 0)
(layer "F.Cu")
(uuid "e03ce3b4-270c-4001-a4a7-fa9658e4de2b")
(effects
(font
(size 1.3 1.3)
(thickness 0.2)
)
(justify left bottom)
)
)
(gr_text "compare GND zone fill around both PAD4 on both AD8418:\n- these pin is unconnected in eeschema (not available in the symbol)\n- on one footprint pad 4 is leaved unconnected\n- on the other footprint pad4 is overflooded with copper"
(at 8.6 34 0)
(layer "F.SilkS")
(uuid "a9f77747-db5c-4f04-aa3d-be45ba408159")
(effects
(font
(size 3 3)
(thickness 0.4)
)
(justify left bottom)
)
)
(zone
(net 1)
(net_name "GND")
(layer "B.Cu")
(uuid "28454119-9ab7-4f74-87f5-a0c5fc0bb6ed")
(name "gnd")
(hatch edge 0.5)
(priority 2)
(connect_pads
(clearance 0.4)
)
(min_thickness 0.2)
(filled_areas_thickness no)
(fill yes
(thermal_gap 0.4)
(thermal_bridge_width 0.23)
)
(polygon
(pts
(xy 55.242749 -5.605916) (xy 95.722142 -5.547992) (xy 95.74 -34.12555) (xy 55.180826 -34.138786)
)
)
(filled_polygon
(layer "B.Cu")
(pts
(xy 95.64097 -34.125582) (xy 95.668861 -34.121563) (xy 95.694489 -34.109849) (xy 95.715778 -34.091389)
(xy 95.731005 -34.067679) (xy 95.738935 -34.040639) (xy 95.739938 -34.02652) (xy 95.722203 -5.647071)
(xy 95.718175 -5.619182) (xy 95.706454 -5.593558) (xy 95.687987 -5.572273) (xy 95.664273 -5.557054)
(xy 95.637231 -5.549132) (xy 95.623071 -5.548133) (xy 55.341386 -5.605774) (xy 55.313507 -5.609824)
(xy 55.287891 -5.621567) (xy 55.266622 -5.64005) (xy 55.251422 -5.663777) (xy 55.243522 -5.690825)
(xy 55.242534 -5.704545) (xy 55.221709 -15.299999) (xy 68.48 -15.299999) (xy 68.548262 -15.3) (xy 68.54828 -15.300002)
(xy 68.614309 -15.308019) (xy 68.614317 -15.30802) (xy 68.678918 -15.323942) (xy 68.741139 -15.347539)
(xy 68.741153 -15.347545) (xy 68.800055 -15.378459) (xy 68.800059 -15.378462) (xy 68.854811 -15.416255)
(xy 68.854817 -15.41626) (xy 68.904605 -15.460368) (xy 68.904624 -15.460387) (xy 68.925898 -15.4844)
(xy 68.947395 -15.502617) (xy 68.973154 -15.514041) (xy 69.001088 -15.517744) (xy 69.028934 -15.513428)
(xy 69.054436 -15.501441) (xy 69.074102 -15.4844) (xy 69.095375 -15.460387) (xy 69.095394 -15.460368)
(xy 69.145182 -15.41626) (xy 69.145188 -15.416255) (xy 69.19994 -15.378462) (xy 69.199944 -15.378459)
(xy 69.258846 -15.347545) (xy 69.25886 -15.347539) (xy 69.321081 -15.323942) (xy 69.385678 -15.308021)
(xy 69.451723 -15.3) (xy 69.519999 -15.3) (xy 69.52 -15.300001) (xy 69.52 -16.459999) (xy 69.519999 -16.46)
(xy 68.480001 -16.46) (xy 68.48 -16.459999) (xy 68.48 -15.299999) (xy 69.75 -15.299999) (xy 69.818262 -15.3)
(xy 69.81828 -15.300002) (xy 69.884309 -15.308019) (xy 69.884317 -15.30802) (xy 69.948918 -15.323942)
(xy 70.011139 -15.347539) (xy 70.011153 -15.347545) (xy 70.070055 -15.378459) (xy 70.070059 -15.378462)
(xy 70.124811 -15.416255) (xy 70.124817 -15.41626) (xy 70.174605 -15.460368) (xy 70.174631 -15.460394)
(xy 70.218739 -15.510182) (xy 70.218744 -15.510188) (xy 70.256537 -15.56494) (xy 70.25654 -15.564944)
(xy 70.287454 -15.623846) (xy 70.28746 -15.62386) (xy 70.311057 -15.686081) (xy 70.326978 -15.750678)
(xy 70.334999 -15.816723) (xy 70.335 -15.816735) (xy 70.335 -16.459999) (xy 70.334999 -16.46) (xy 69.750001 -16.46)
(xy 69.75 -16.459999) (xy 69.75 -15.299999) (xy 68.48 -15.299999) (xy 55.221709 -15.299999) (xy 55.220588 -15.816737)
(xy 67.665001 -15.816737) (xy 67.665002 -15.81672) (xy 67.673019 -15.75069) (xy 67.67302 -15.750682)
(xy 67.688942 -15.686081) (xy 67.712539 -15.62386) (xy 67.712545 -15.623846) (xy 67.743459 -15.564944)
(xy 67.743462 -15.56494) (xy 67.781255 -15.510188) (xy 67.78126 -15.510182) (xy 67.825368 -15.460394)
(xy 67.825394 -15.460368) (xy 67.875182 -15.41626) (xy 67.875188 -15.416255) (xy 67.92994 -15.378462)
(xy 67.929944 -15.378459) (xy 67.988846 -15.347545) (xy 67.98886 -15.347539) (xy 68.051081 -15.323942)
(xy 68.115678 -15.308021) (xy 68.181723 -15.3) (xy 68.249999 -15.3) (xy 68.25 -15.300001) (xy 68.25 -16.459999)
(xy 68.249999 -16.46) (xy 67.665002 -16.46) (xy 67.665001 -16.459999) (xy 67.665001 -15.816737)
(xy 55.220588 -15.816737) (xy 55.218693 -16.690001) (xy 67.665 -16.690001) (xy 67.665001 -16.69)
(xy 68.249999 -16.69) (xy 68.25 -16.690001) (xy 68.48 -16.690001) (xy 68.480001 -16.69) (xy 69.519999 -16.69)
(xy 69.52 -16.690001) (xy 69.75 -16.690001) (xy 69.750001 -16.69) (xy 70.334998 -16.69) (xy 70.334999 -16.690001)
(xy 70.334999 -17.333262) (xy 70.334997 -17.333279) (xy 70.32698 -17.399309) (xy 70.326979 -17.399317)
(xy 70.311057 -17.463918) (xy 70.28746 -17.526139) (xy 70.287454 -17.526153) (xy 70.25654 -17.585055)
(xy 70.256537 -17.585059) (xy 70.218744 -17.639811) (xy 70.218739 -17.639817) (xy 70.174631 -17.689605)
(xy 70.174605 -17.689631) (xy 70.124817 -17.733739) (xy 70.124811 -17.733744) (xy 70.070059 -17.771537)
(xy 70.070055 -17.77154) (xy 70.011153 -17.802454) (xy 70.011139 -17.80246) (xy 69.948918 -17.826057)
(xy 69.884321 -17.841978) (xy 69.81827 -17.849999) (xy 69.75 -17.849998) (xy 69.75 -16.690001) (xy 69.52 -16.690001)
(xy 69.52 -17.849998) (xy 69.519999 -17.849999) (xy 69.451737 -17.849999) (xy 69.451721 -17.849998)
(xy 69.38569 -17.84198) (xy 69.385682 -17.841979) (xy 69.321081 -17.826057) (xy 69.25886 -17.80246)
(xy 69.258846 -17.802454) (xy 69.199944 -17.77154) (xy 69.19994 -17.771537) (xy 69.145188 -17.733744)
(xy 69.145182 -17.733739) (xy 69.095394 -17.689631) (xy 69.095369 -17.689606) (xy 69.074103 -17.665601)
(xy 69.052606 -17.647383) (xy 69.026848 -17.635959) (xy 68.998914 -17.632255) (xy 68.971068 -17.636571)
(xy 68.945566 -17.648557) (xy 68.925897 -17.665601) (xy 68.90463 -17.689606) (xy 68.904605 -17.689631)
(xy 68.854817 -17.733739) (xy 68.854811 -17.733744) (xy 68.800059 -17.771537) (xy 68.800055 -17.77154)
(xy 68.741153 -17.802454) (xy 68.741139 -17.80246) (xy 68.678918 -17.826057) (xy 68.614321 -17.841978)
(xy 68.54827 -17.849999) (xy 68.48 -17.849998) (xy 68.48 -16.690001) (xy 68.25 -16.690001) (xy 68.25 -17.849998)
(xy 68.249999 -17.849999) (xy 68.181737 -17.849999) (xy 68.181721 -17.849998) (xy 68.11569 -17.84198)
(xy 68.115682 -17.841979) (xy 68.051081 -17.826057) (xy 67.98886 -17.80246) (xy 67.988846 -17.802454)
(xy 67.929944 -17.77154) (xy 67.92994 -17.771537) (xy 67.875188 -17.733744) (xy 67.875182 -17.733739)
(xy 67.825394 -17.689631) (xy 67.825368 -17.689605) (xy 67.78126 -17.639817) (xy 67.781255 -17.639811)
(xy 67.743462 -17.585059) (xy 67.743459 -17.585055) (xy 67.712545 -17.526153) (xy 67.712539 -17.526139)
(xy 67.688942 -17.463918) (xy 67.673021 -17.399321) (xy 67.665 -17.333276) (xy 67.665 -16.690001)
(xy 55.218693 -16.690001) (xy 55.181041 -34.03954) (xy 55.184991 -34.067438) (xy 55.196641 -34.093096)
(xy 55.215048 -34.114431) (xy 55.23872 -34.129717) (xy 55.265739 -34.137715) (xy 55.280073 -34.138753)
)
)
)
)

View File

@ -46,6 +46,7 @@ set( QA_PCBNEW_SRCS
test_reference_image_load.cpp
test_save_load.cpp
test_tracks_cleaner.cpp
test_triangulation.cpp
test_zone_filler.cpp
drc/test_custom_rule_severities.cpp

View File

@ -0,0 +1,93 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew_utils/board_test_utils.h>
#include <board.h>
#include <board_design_settings.h>
#include <pad.h>
#include <pcb_track.h>
#include <footprint.h>
#include <zone.h>
#include <drc/drc_item.h>
#include <settings/settings_manager.h>
struct TRIANGULATE_TEST_FIXTURE
{
TRIANGULATE_TEST_FIXTURE() :
m_settingsManager( true /* headless */ )
{ }
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( RegressionTriangulationTests, TRIANGULATE_TEST_FIXTURE )
{
std::vector<wxString> tests = {
"issue2568",
"issue5313",
"issue5320",
"issue5567",
"issue5830",
"issue6039",
"issue6260",
"issue7086",
"issue14294",
"issue17559"
};
for( const wxString& relPath : tests )
{
KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
for( ZONE* zone : m_board->Zones() )
{
for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq())
{
auto poly = zone->GetFilledPolysList( layer );
double area = poly->Area();
double tri_area = 0.0;
for( int ii = 0; ii < poly->TriangulatedPolyCount(); ii++ )
{
const auto tri_poly = poly->TriangulatedPolygon( ii );
for( auto& tri : tri_poly->Triangles() )
tri_area += tri.Area();
}
double diff = std::abs( area - tri_area );
// The difference should be less than 1e-4 square mm
BOOST_CHECK_MESSAGE( diff < 1e8, "Triangulation area mismatch in " + relPath + " layer " + LayerName( layer ) + " difference: " + std::to_string( diff ) );
}
}
}
}