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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
39
42
43#include <board.h>
45#include <layer_ids.h>
46#include <drc/drc_item.h>
47#include <drc/drc_engine.h>
48#include <footprint.h>
49#include <pad.h>
50#include <pcb_marker.h>
53
54
56{
58
60 {
61 if( m_board && m_board->GetDesignSettings().m_DRCEngine )
62 m_board->GetDesignSettings().m_DRCEngine->ClearViolationHandler();
63
64 if( m_board )
65 {
66 m_board->SetProject( nullptr );
67 m_board = nullptr;
68 }
69 }
70
72 std::unique_ptr<BOARD> m_board;
73};
74
75
76BOOST_FIXTURE_TEST_CASE( CreepageNPTHBetweenPadsIssue24286, DRC_CREEPAGE_NPTH_PADS_FIXTURE )
77{
78 KI_TEST::LoadBoard( m_settingsManager, "issue24286/issue24286", m_board );
79
80 BOOST_REQUIRE_MESSAGE( m_board, "Failed to load board issue24286" );
81
82 struct ViolationInfo
83 {
84 std::shared_ptr<DRC_ITEM> item;
85 VECTOR2I pos;
86 std::vector<PCB_SHAPE> pathShapes;
87 int layer;
88 };
89
90 std::vector<ViolationInfo> violations;
91 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
92
93 BOOST_REQUIRE_MESSAGE( bds.m_DRCEngine, "DRC engine not initialized" );
94
95 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
97
99
101 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
102 const std::function<void( PCB_MARKER* )>& aPathGenerator )
103 {
104 if( bds.GetSeverity( aItem->GetErrorCode() ) != SEVERITY::RPT_SEVERITY_ERROR )
105 return;
106
107 ViolationInfo vi;
108 vi.item = aItem;
109 vi.pos = aPos;
110 vi.layer = aLayer;
111
112 if( aPathGenerator )
113 {
114 PCB_MARKER marker( aItem, aPos, aLayer );
115 aPathGenerator( &marker );
116 vi.pathShapes = marker.GetPath();
117 }
118
119 violations.push_back( vi );
120 } );
121
122 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
123
125
126 BOOST_TEST_MESSAGE( wxString::Format( "Found %d creepage violations",
127 (int) violations.size() ) );
128
129 for( const ViolationInfo& vi : violations )
130 {
131 BOOST_TEST_MESSAGE( wxString::Format( " layer=%d arrow=(%.4f,%.4f) shapes=%d msg=%s",
132 vi.layer,
133 vi.pos.x / 1e6, vi.pos.y / 1e6,
134 (int) vi.pathShapes.size(),
135 vi.item->GetErrorMessage( false ) ) );
136
137 for( size_t j = 0; j < vi.pathShapes.size(); j++ )
138 {
139 const PCB_SHAPE& s = vi.pathShapes[j];
140
141 if( s.GetShape() == SHAPE_T::SEGMENT )
142 {
143 BOOST_TEST_MESSAGE( wxString::Format(
144 " [%zu] SEG: (%.4f,%.4f)->(%.4f,%.4f)", j,
145 s.GetStart().x / 1e6, s.GetStart().y / 1e6,
146 s.GetEnd().x / 1e6, s.GetEnd().y / 1e6 ) );
147 }
148 else if( s.GetShape() == SHAPE_T::ARC )
149 {
150 BOOST_TEST_MESSAGE( wxString::Format(
151 " [%zu] ARC: (%.4f,%.4f)->(%.4f,%.4f) c=(%.4f,%.4f)", j,
152 s.GetStart().x / 1e6, s.GetStart().y / 1e6,
153 s.GetEnd().x / 1e6, s.GetEnd().y / 1e6,
154 s.GetCenter().x / 1e6, s.GetCenter().y / 1e6 ) );
155 }
156 }
157 }
158
159 // Locate the C4 pads to drive the geometric assertions.
160 PAD* pad1 = nullptr;
161 PAD* pad2 = nullptr;
162
163 for( FOOTPRINT* fp : m_board->Footprints() )
164 {
165 if( fp->GetReference() != wxT( "C4" ) )
166 continue;
167
168 for( PAD* p : fp->Pads() )
169 {
170 if( p->GetNumber() == wxT( "1" ) )
171 pad1 = p;
172 else if( p->GetNumber() == wxT( "2" ) )
173 pad2 = p;
174 }
175 }
176
177 BOOST_REQUIRE_MESSAGE( pad1 && pad2, "C4 pads 1 and 2 not found in board" );
178
179 const VECTOR2I p1Pos = pad1->GetPosition();
180 const VECTOR2I p2Pos = pad2->GetPosition();
181 const SEG directSeg( p1Pos, p2Pos );
182 const double directDist = ( p2Pos - p1Pos ).EuclideanNorm() / 1e6;
183
184 BOOST_TEST_MESSAGE( wxString::Format(
185 "C4 pad1 at (%.4f, %.4f) mm, pad2 at (%.4f, %.4f) mm, center-to-center %.4f mm",
186 p1Pos.x / 1e6, p1Pos.y / 1e6, p2Pos.x / 1e6, p2Pos.y / 1e6, directDist ) );
187
188 // Find the violation reported between pad1 and pad2 on the F.Cu layer. The bug
189 // surfaces specifically on F.Cu (the user-reported "incorrect" path); B.Cu is
190 // separately reported and not the regression target here.
191 const ViolationInfo* c4Violation = nullptr;
192
193 for( const ViolationInfo& vi : violations )
194 {
195 if( vi.layer != F_Cu )
196 continue;
197
198 const KIID idA = vi.item->GetMainItemID();
199 const KIID idB = vi.item->GetAuxItemID();
200 const bool matchA = ( idA == pad1->m_Uuid || idA == pad2->m_Uuid );
201 const bool matchB = ( idB == pad1->m_Uuid || idB == pad2->m_Uuid );
202
203 if( matchA && matchB && idA != idB )
204 {
205 c4Violation = &vi;
206 break;
207 }
208 }
209
210 BOOST_REQUIRE_MESSAGE( c4Violation,
211 "No F.Cu creepage violation reported between C4 pad1 and pad2" );
212
213 BOOST_TEST_MESSAGE( wxString::Format(
214 "C4 violation: layer=%d shapes=%d arrow=(%.4f, %.4f) mm",
215 c4Violation->layer,
216 (int) c4Violation->pathShapes.size(),
217 c4Violation->pos.x / 1e6, c4Violation->pos.y / 1e6 ) );
218
219 BOOST_REQUIRE_GE( c4Violation->pathShapes.size(), 1u );
220
221 // Sum the geometric length of the reported path.
222 double pathLen = 0.0;
223
224 for( const PCB_SHAPE& s : c4Violation->pathShapes )
225 {
226 if( s.GetShape() == SHAPE_T::SEGMENT )
227 {
228 pathLen += ( s.GetEnd() - s.GetStart() ).EuclideanNorm() / 1e6;
229 }
230 else if( s.GetShape() == SHAPE_T::ARC )
231 {
233 arc.SetArcGeometry( s.GetStart(), s.GetArcMid(), s.GetEnd() );
234 pathLen += arc.GetLength() / 1e6;
235 }
236 }
237
238 BOOST_TEST_MESSAGE( wxString::Format( "C4 path total length: %.4f mm (direct: %.4f mm)",
239 pathLen, directDist ) );
240
241 // Path must not cut through the NPTH slot interior. The capacitor is centred on the
242 // NPTH oval so the direct centre-to-centre segment runs straight through the slot.
243 // A correct creepage path must wrap around the slot, so every segment of the reported
244 // path must lie at least 0.4 mm (half the slot's short axis, minus a 100 um tolerance)
245 // off the direct line.
246 bool pathLeavesDirectLine = false;
247
248 for( const PCB_SHAPE& s : c4Violation->pathShapes )
249 {
250 const int distStart = directSeg.Distance( s.GetStart() );
251 const int distEnd = directSeg.Distance( s.GetEnd() );
252
253 if( distStart > 400000 || distEnd > 400000 )
254 {
255 pathLeavesDirectLine = true;
256 break;
257 }
258 }
259
260 BOOST_CHECK_MESSAGE( pathLeavesDirectLine,
261 "Creepage path between C4 pads stays on the centre line of the NPTH slot, "
262 "indicating it cuts through the slot interior instead of going around it." );
263
264 // The actual surface creepage between two THT pads centred on a 4mm x 1mm NPTH slot
265 // must be appreciably longer than the centre-to-centre distance. A direct-through-slot
266 // path would report ~ directDist - 1.6 mm (subtracting the two pad radii). The correct
267 // path wraps around the slot end caps, adding at least the short-axis radius twice
268 // (~1 mm) plus an arc length. Require the reported distance to exceed the direct
269 // centre-to-centre distance (with a small margin) as a coarse sanity check on the
270 // routing geometry.
271 BOOST_CHECK_MESSAGE( pathLen >= directDist - 0.1,
272 wxString::Format( "Reported creepage path length %.4f mm is shorter than the "
273 "pad-centre to pad-centre distance %.4f mm, which is impossible "
274 "for a path that routes around the NPTH slot.",
275 pathLen, directDist ) );
276
277 // Parse the violation message's reported "actual N.NNNN mm" and verify it lies in the
278 // expected range. Before the fix the creepage validator rejected legitimate tangent
279 // paths to BE_SHAPE_ARC obstacles (it failed to ignore the obstacle's own parent for
280 // arc shapes), forcing Dijkstra onto a longer fallback that connected through arc
281 // endpoints. The reported distance landed around 4.5 mm. With the fix the path uses
282 // proper tangent points and the reported distance drops below 4 mm.
283 wxString errMsg = c4Violation->item->GetErrorMessage( false );
284 double reportedActual = 0.0;
285 int actualPos = errMsg.Find( wxT( "actual " ) );
286
287 BOOST_REQUIRE_MESSAGE( actualPos != wxNOT_FOUND,
288 wxString::Format( "Could not find 'actual' in error message: %s",
289 errMsg ) );
290
291 wxString tail = errMsg.Mid( actualPos + 7 );
292 int spacePos = tail.Find( ' ' );
293
294 if( spacePos != wxNOT_FOUND )
295 tail = tail.Left( spacePos );
296
297 BOOST_REQUIRE_MESSAGE( tail.ToDouble( &reportedActual ),
298 wxString::Format( "Could not parse reported actual from '%s'", tail ) );
299
300 BOOST_CHECK_MESSAGE( reportedActual < 4.0,
301 wxString::Format( "Reported creepage actual %.4f mm exceeds 4 mm; this indicates "
302 "the validator is still falling back to arc-endpoint connections "
303 "instead of tangent-on-arc paths.",
304 reportedActual ) );
305}
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:168
void ClearViolationHandler()
Definition drc_engine.h:173
const KIID m_Uuid
Definition eda_item.h:535
SHAPE_T GetShape() const
Definition eda_shape.h:189
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:236
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:194
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:48
Definition pad.h:65
VECTOR2I GetPosition() const override
Definition pad.h:219
const std::vector< PCB_SHAPE > & GetPath() const
Definition pcb_marker.h:162
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
Definition seg.h:42
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:702
@ DRCE_CREEPAGE
Definition drc_item.h:45
@ DRCE_FIRST
Definition drc_item.h:39
@ DRCE_LAST
Definition drc_item.h:124
@ SEGMENT
Definition eda_shape.h:50
@ NO_FILL
Definition eda_shape.h:64
@ F_Cu
Definition layer_ids.h:64
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:687