KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_creepage_issue24286.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
35
38
39#include <board.h>
41#include <layer_ids.h>
42#include <drc/drc_item.h>
43#include <drc/drc_engine.h>
44#include <footprint.h>
45#include <pad.h>
46#include <pcb_marker.h>
49
50
52{
54
56 {
57 if( m_board && m_board->GetDesignSettings().m_DRCEngine )
58 m_board->GetDesignSettings().m_DRCEngine->ClearViolationHandler();
59
60 if( m_board )
61 {
62 m_board->SetProject( nullptr );
63 m_board = nullptr;
64 }
65 }
66
68 std::unique_ptr<BOARD> m_board;
69};
70
71
72BOOST_FIXTURE_TEST_CASE( CreepageNPTHBetweenPadsIssue24286, DRC_CREEPAGE_NPTH_PADS_FIXTURE )
73{
74 KI_TEST::LoadBoard( m_settingsManager, "issue24286/issue24286", m_board );
75
76 BOOST_REQUIRE_MESSAGE( m_board, "Failed to load board issue24286" );
77
78 struct ViolationInfo
79 {
80 std::shared_ptr<DRC_ITEM> item;
81 VECTOR2I pos;
82 std::vector<PCB_SHAPE> pathShapes;
83 int layer;
84 };
85
86 std::vector<ViolationInfo> violations;
87 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
88
89 BOOST_REQUIRE_MESSAGE( bds.m_DRCEngine, "DRC engine not initialized" );
90
91 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
93
95
97 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
98 const std::function<void( PCB_MARKER* )>& aPathGenerator )
99 {
100 if( bds.GetSeverity( aItem->GetErrorCode() ) != SEVERITY::RPT_SEVERITY_ERROR )
101 return;
102
103 ViolationInfo vi;
104 vi.item = aItem;
105 vi.pos = aPos;
106 vi.layer = aLayer;
107
108 if( aPathGenerator )
109 {
110 PCB_MARKER marker( aItem, aPos, aLayer );
111 aPathGenerator( &marker );
112 vi.pathShapes = marker.GetPath();
113 }
114
115 violations.push_back( vi );
116 } );
117
118 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
119
121
122 BOOST_TEST_MESSAGE( wxString::Format( "Found %d creepage violations",
123 (int) violations.size() ) );
124
125 for( const ViolationInfo& vi : violations )
126 {
127 BOOST_TEST_MESSAGE( wxString::Format( " layer=%d arrow=(%.4f,%.4f) shapes=%d msg=%s",
128 vi.layer,
129 vi.pos.x / 1e6, vi.pos.y / 1e6,
130 (int) vi.pathShapes.size(),
131 vi.item->GetErrorMessage( false ) ) );
132
133 for( size_t j = 0; j < vi.pathShapes.size(); j++ )
134 {
135 const PCB_SHAPE& s = vi.pathShapes[j];
136
137 if( s.GetShape() == SHAPE_T::SEGMENT )
138 {
139 BOOST_TEST_MESSAGE( wxString::Format(
140 " [%zu] SEG: (%.4f,%.4f)->(%.4f,%.4f)", j,
141 s.GetStart().x / 1e6, s.GetStart().y / 1e6,
142 s.GetEnd().x / 1e6, s.GetEnd().y / 1e6 ) );
143 }
144 else if( s.GetShape() == SHAPE_T::ARC )
145 {
146 BOOST_TEST_MESSAGE( wxString::Format(
147 " [%zu] ARC: (%.4f,%.4f)->(%.4f,%.4f) c=(%.4f,%.4f)", j,
148 s.GetStart().x / 1e6, s.GetStart().y / 1e6,
149 s.GetEnd().x / 1e6, s.GetEnd().y / 1e6,
150 s.GetCenter().x / 1e6, s.GetCenter().y / 1e6 ) );
151 }
152 }
153 }
154
155 // Locate the C4 pads to drive the geometric assertions.
156 PAD* pad1 = nullptr;
157 PAD* pad2 = nullptr;
158
159 for( FOOTPRINT* fp : m_board->Footprints() )
160 {
161 if( fp->GetReference() != wxT( "C4" ) )
162 continue;
163
164 for( PAD* p : fp->Pads() )
165 {
166 if( p->GetNumber() == wxT( "1" ) )
167 pad1 = p;
168 else if( p->GetNumber() == wxT( "2" ) )
169 pad2 = p;
170 }
171 }
172
173 BOOST_REQUIRE_MESSAGE( pad1 && pad2, "C4 pads 1 and 2 not found in board" );
174
175 const VECTOR2I p1Pos = pad1->GetPosition();
176 const VECTOR2I p2Pos = pad2->GetPosition();
177 const SEG directSeg( p1Pos, p2Pos );
178 const double directDist = ( p2Pos - p1Pos ).EuclideanNorm() / 1e6;
179
180 BOOST_TEST_MESSAGE( wxString::Format(
181 "C4 pad1 at (%.4f, %.4f) mm, pad2 at (%.4f, %.4f) mm, center-to-center %.4f mm",
182 p1Pos.x / 1e6, p1Pos.y / 1e6, p2Pos.x / 1e6, p2Pos.y / 1e6, directDist ) );
183
184 // Find the violation reported between pad1 and pad2 on the F.Cu layer. The bug
185 // surfaces specifically on F.Cu (the user-reported "incorrect" path); B.Cu is
186 // separately reported and not the regression target here.
187 const ViolationInfo* c4Violation = nullptr;
188
189 for( const ViolationInfo& vi : violations )
190 {
191 if( vi.layer != F_Cu )
192 continue;
193
194 const KIID idA = vi.item->GetMainItemID();
195 const KIID idB = vi.item->GetAuxItemID();
196 const bool matchA = ( idA == pad1->m_Uuid || idA == pad2->m_Uuid );
197 const bool matchB = ( idB == pad1->m_Uuid || idB == pad2->m_Uuid );
198
199 if( matchA && matchB && idA != idB )
200 {
201 c4Violation = &vi;
202 break;
203 }
204 }
205
206 BOOST_REQUIRE_MESSAGE( c4Violation,
207 "No F.Cu creepage violation reported between C4 pad1 and pad2" );
208
209 BOOST_TEST_MESSAGE( wxString::Format(
210 "C4 violation: layer=%d shapes=%d arrow=(%.4f, %.4f) mm",
211 c4Violation->layer,
212 (int) c4Violation->pathShapes.size(),
213 c4Violation->pos.x / 1e6, c4Violation->pos.y / 1e6 ) );
214
215 BOOST_REQUIRE_GE( c4Violation->pathShapes.size(), 1u );
216
217 // Sum the geometric length of the reported path.
218 double pathLen = 0.0;
219
220 for( const PCB_SHAPE& s : c4Violation->pathShapes )
221 {
222 if( s.GetShape() == SHAPE_T::SEGMENT )
223 {
224 pathLen += ( s.GetEnd() - s.GetStart() ).EuclideanNorm() / 1e6;
225 }
226 else if( s.GetShape() == SHAPE_T::ARC )
227 {
229 arc.SetArcGeometry( s.GetStart(), s.GetArcMid(), s.GetEnd() );
230 pathLen += arc.GetLength() / 1e6;
231 }
232 }
233
234 BOOST_TEST_MESSAGE( wxString::Format( "C4 path total length: %.4f mm (direct: %.4f mm)",
235 pathLen, directDist ) );
236
237 // Path must not cut through the NPTH slot interior. The capacitor is centred on the
238 // NPTH oval so the direct centre-to-centre segment runs straight through the slot.
239 // A correct creepage path must wrap around the slot, so every segment of the reported
240 // path must lie at least 0.4 mm (half the slot's short axis, minus a 100 um tolerance)
241 // off the direct line.
242 bool pathLeavesDirectLine = false;
243
244 for( const PCB_SHAPE& s : c4Violation->pathShapes )
245 {
246 const int distStart = directSeg.Distance( s.GetStart() );
247 const int distEnd = directSeg.Distance( s.GetEnd() );
248
249 if( distStart > 400000 || distEnd > 400000 )
250 {
251 pathLeavesDirectLine = true;
252 break;
253 }
254 }
255
256 BOOST_CHECK_MESSAGE( pathLeavesDirectLine,
257 "Creepage path between C4 pads stays on the centre line of the NPTH slot, "
258 "indicating it cuts through the slot interior instead of going around it." );
259
260 // The actual surface creepage between two THT pads centred on a 4mm x 1mm NPTH slot
261 // must be appreciably longer than the centre-to-centre distance. A direct-through-slot
262 // path would report ~ directDist - 1.6 mm (subtracting the two pad radii). The correct
263 // path wraps around the slot end caps, adding at least the short-axis radius twice
264 // (~1 mm) plus an arc length. Require the reported distance to exceed the direct
265 // centre-to-centre distance (with a small margin) as a coarse sanity check on the
266 // routing geometry.
267 BOOST_CHECK_MESSAGE( pathLen >= directDist - 0.1,
268 wxString::Format( "Reported creepage path length %.4f mm is shorter than the "
269 "pad-centre to pad-centre distance %.4f mm, which is impossible "
270 "for a path that routes around the NPTH slot.",
271 pathLen, directDist ) );
272
273 // Parse the violation message's reported "actual N.NNNN mm" and verify it lies in the
274 // expected range. Before the fix the creepage validator rejected legitimate tangent
275 // paths to BE_SHAPE_ARC obstacles (it failed to ignore the obstacle's own parent for
276 // arc shapes), forcing Dijkstra onto a longer fallback that connected through arc
277 // endpoints. The reported distance landed around 4.5 mm. With the fix the path uses
278 // proper tangent points and the reported distance drops below 4 mm.
279 wxString errMsg = c4Violation->item->GetErrorMessage( false );
280 double reportedActual = 0.0;
281 int actualPos = errMsg.Find( wxT( "actual " ) );
282
283 BOOST_REQUIRE_MESSAGE( actualPos != wxNOT_FOUND,
284 wxString::Format( "Could not find 'actual' in error message: %s",
285 errMsg ) );
286
287 wxString tail = errMsg.Mid( actualPos + 7 );
288 int spacePos = tail.Find( ' ' );
289
290 if( spacePos != wxNOT_FOUND )
291 tail = tail.Left( spacePos );
292
293 BOOST_REQUIRE_MESSAGE( tail.ToDouble( &reportedActual ),
294 wxString::Format( "Could not parse reported actual from '%s'", tail ) );
295
296 BOOST_CHECK_MESSAGE( reportedActual < 4.0,
297 wxString::Format( "Reported creepage actual %.4f mm exceeds 4 mm; this indicates "
298 "the validator is still falling back to arc-endpoint connections "
299 "instead of tangent-on-arc paths.",
300 reportedActual ) );
301}
Container for design settings for a BOARD object.
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
SEVERITY GetSeverity(int aDRCErrorCode)
void RunTests(EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, BOARD_COMMIT *aCommit=nullptr)
Run the DRC tests.
void SetViolationHandler(DRC_VIOLATION_HANDLER aHandler)
Set an optional DRC violation handler (receives DRC_ITEMs and positions).
Definition drc_engine.h:164
void ClearViolationHandler()
Definition drc_engine.h:169
const KIID m_Uuid
Definition eda_item.h:531
SHAPE_T GetShape() const
Definition eda_shape.h:185
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
double GetLength() const
VECTOR2I GetArcMid() const
Definition kiid.h:44
Definition pad.h:61
VECTOR2I GetPosition() const override
Definition pad.cpp:245
const std::vector< PCB_SHAPE > & GetPath() const
Definition pcb_marker.h:158
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
Definition seg.h:38
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:698
@ DRCE_CREEPAGE
Definition drc_item.h:41
@ DRCE_FIRST
Definition drc_item.h:35
@ DRCE_LAST
Definition drc_item.h:121
@ SEGMENT
Definition eda_shape.h:46
@ NO_FILL
Definition eda_shape.h:60
@ F_Cu
Definition layer_ids.h:60
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
BOOST_FIXTURE_TEST_CASE(CreepageNPTHBetweenPadsIssue24286, DRC_CREEPAGE_NPTH_PADS_FIXTURE)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683