KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_creepage_issue24544.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
41
44
45#include <board.h>
47#include <layer_ids.h>
48#include <drc/drc_item.h>
49#include <drc/drc_engine.h>
50#include <footprint.h>
51#include <pad.h>
52#include <pcb_shape.h>
53#include <pcb_marker.h>
56
60#include <geometry/shape_arc.h>
61
62
64{
66
68 {
69 if( m_board && m_board->GetDesignSettings().m_DRCEngine )
70 m_board->GetDesignSettings().m_DRCEngine->ClearViolationHandler();
71
72 if( m_board )
73 {
74 m_board->SetProject( nullptr );
75 m_board = nullptr;
76 }
77 }
78
80 std::unique_ptr<BOARD> m_board;
81};
82
83
85{
86 KI_TEST::LoadBoard( m_settingsManager, "issue24544/issue24544", m_board );
87
88 BOOST_REQUIRE_MESSAGE( m_board, "Failed to load board issue24544" );
89
90 struct ViolationInfo
91 {
92 std::shared_ptr<DRC_ITEM> item;
93 VECTOR2I pos;
94 std::vector<PCB_SHAPE> pathShapes;
95 int layer = 0;
96 };
97
98 std::vector<ViolationInfo> violations;
99 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
100
101 BOOST_REQUIRE_MESSAGE( bds.m_DRCEngine, "DRC engine not initialized" );
102
103 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
105
107
109 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
110 const std::function<void( PCB_MARKER* )>& aPathGenerator )
111 {
112 if( bds.GetSeverity( aItem->GetErrorCode() ) != SEVERITY::RPT_SEVERITY_ERROR )
113 return;
114
115 ViolationInfo vi;
116 vi.item = aItem;
117 vi.pos = aPos;
118 vi.layer = aLayer;
119
120 if( aPathGenerator )
121 {
122 PCB_MARKER marker( aItem, aPos, aLayer );
123 aPathGenerator( &marker );
124 vi.pathShapes = marker.GetPath();
125 }
126
127 violations.push_back( vi );
128 } );
129
130 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
131
133
134 BOOST_TEST_MESSAGE( wxString::Format( "Found %d creepage violations",
135 (int) violations.size() ) );
136
137 // Recover the rounded-rectangle slot geometry from Edge.Cuts. The repro draws the slot
138 // as a gr_rect with a positive corner radius. We use the geometry directly (rather than a
139 // hard-coded constant) so the test tracks the data file.
140 VECTOR2I slotStart, slotEnd;
141 int slotRadius = 0;
142 bool slotFound = false;
143
144 for( BOARD_ITEM* item : m_board->Drawings() )
145 {
146 PCB_SHAPE* s = dynamic_cast<PCB_SHAPE*>( item );
147
148 if( !s || !s->IsOnLayer( Edge_Cuts ) )
149 continue;
150
151 if( s->GetShape() != SHAPE_T::RECTANGLE || s->GetCornerRadius() <= 0 )
152 continue;
153
154 slotStart = s->GetStart();
155 slotEnd = s->GetEnd();
156 slotRadius = s->GetCornerRadius();
157 slotFound = true;
158 break;
159 }
160
161 BOOST_REQUIRE_MESSAGE( slotFound, "Rounded-rectangle slot not found on Edge.Cuts" );
162
163 const int x1 = std::min( slotStart.x, slotEnd.x );
164 const int y1 = std::min( slotStart.y, slotEnd.y );
165 const int x2 = std::max( slotStart.x, slotEnd.x );
166 const int y2 = std::max( slotStart.y, slotEnd.y );
167 const int r = slotRadius;
168
169 BOOST_TEST_MESSAGE( wxString::Format(
170 "Rounded slot box (%.4f,%.4f)-(%.4f,%.4f) mm, corner radius %.4f mm",
171 x1 / 1e6, y1 / 1e6, x2 / 1e6, y2 / 1e6, r / 1e6 ) );
172
173 // Build the exact rounded-rectangle slot outline as a polygon so we can test whether a
174 // creepage path segment ever crosses into the slot interior. A correct path that respects
175 // the rounded ends wraps the curved boundary and stays out of the slot region entirely.
176 SHAPE_POLY_SET slotPoly;
177 slotPoly.NewOutline();
178
179 const int ERR = 1000; // arc approximation error, 1 um
180
181 // Top edge (left arc end -> right arc end), then right arc, bottom edge, left arc.
182 slotPoly.Append( x1 + r, y1 );
183 slotPoly.Append( x2 - r, y1 );
184
185 auto appendArc = [&]( const VECTOR2I& aCenter, const EDA_ANGLE& aStart, const EDA_ANGLE& aEnd )
186 {
187 VECTOR2I startPt( aCenter.x + KiROUND( r * aStart.Cos() ),
188 aCenter.y + KiROUND( r * aStart.Sin() ) );
189 SHAPE_ARC realArc( aCenter, startPt, aEnd - aStart, 0 );
191
192 for( int i = 0; i < chain.PointCount(); ++i )
193 slotPoly.Append( chain.CPoint( i ) );
194 };
195
196 // Right-side end cap: from (x2-r, y1) sweeping to (x2-r, y2) through (x2, y1+r)..(x2, y2-r).
197 appendArc( { x2 - r, y1 + r }, EDA_ANGLE( -90.0, DEGREES_T ), EDA_ANGLE( 0.0, DEGREES_T ) );
198
199 if( ( y2 - y1 ) > 2 * r )
200 appendArc( { x2 - r, y2 - r }, EDA_ANGLE( 0.0, DEGREES_T ), EDA_ANGLE( 90.0, DEGREES_T ) );
201
202 slotPoly.Append( x2 - r, y2 );
203 slotPoly.Append( x1 + r, y2 );
204
205 // Left-side end cap.
206 appendArc( { x1 + r, y2 - r }, EDA_ANGLE( 90.0, DEGREES_T ), EDA_ANGLE( 180.0, DEGREES_T ) );
207
208 if( ( y2 - y1 ) > 2 * r )
209 appendArc( { x1 + r, y1 + r }, EDA_ANGLE( 180.0, DEGREES_T ),
210 EDA_ANGLE( 270.0, DEGREES_T ) );
211
212 slotPoly.Outline( 0 ).SetClosed( true );
213
214 // Identify the creepage violation whose path wraps the slot. We accept any F.Cu creepage
215 // violation reported between two C4 pads (the pair separated by the slot).
216 PAD* pad1 = nullptr;
217 PAD* pad2 = nullptr;
218
219 for( FOOTPRINT* fp : m_board->Footprints() )
220 {
221 if( fp->GetReference() != wxT( "C4" ) )
222 continue;
223
224 for( PAD* p : fp->Pads() )
225 {
226 if( p->GetNumber() == wxT( "1" ) )
227 pad1 = p;
228 else if( p->GetNumber() == wxT( "2" ) )
229 pad2 = p;
230 }
231 }
232
233 BOOST_REQUIRE_MESSAGE( pad1 && pad2, "C4 pads 1 and 2 not found in board" );
234
235 // The #24543 root cause only triggers when a pad is rotated off-axis (so GetEffectiveShape()
236 // emits a SHAPE_SIMPLE rather than an axis-aligned SHAPE_RECT). Confirm the precondition so a
237 // future data drift to orthogonal pads cannot silently neuter this regression.
238 auto isOrthogonal =
239 []( const PAD* aPad )
240 {
241 double deg = aPad->GetOrientation().Normalize().AsDegrees();
242 double mod = std::fmod( deg, 90.0 );
243 return mod < 0.01 || mod > 89.99;
244 };
245
246 BOOST_REQUIRE_MESSAGE( !isOrthogonal( pad1 ) || !isOrthogonal( pad2 ),
247 "Expected at least one C4 pad to be rotated off-axis" );
248
249 const ViolationInfo* slotViolation = nullptr;
250
251 for( const ViolationInfo& vi : violations )
252 {
253 if( vi.layer != F_Cu )
254 continue;
255
256 const KIID idA = vi.item->GetMainItemID();
257 const KIID idB = vi.item->GetAuxItemID();
258 const bool matchA = ( idA == pad1->m_Uuid || idA == pad2->m_Uuid );
259 const bool matchB = ( idB == pad1->m_Uuid || idB == pad2->m_Uuid );
260
261 if( matchA && matchB && idA != idB && !vi.pathShapes.empty() )
262 {
263 slotViolation = &vi;
264 break;
265 }
266 }
267
268 BOOST_REQUIRE_MESSAGE( slotViolation,
269 "No creepage violation reported between C4 pad1 and pad2" );
270
271 // Densify the path into a point list. ARC path shapes are expanded along their true arc so
272 // that a path correctly hugging the slot's rounded end is NOT misread as cutting the chord.
273 std::vector<VECTOR2I> pathPts;
274
275 auto pushPt = [&]( const VECTOR2I& aPt )
276 {
277 if( pathPts.empty() || pathPts.back() != aPt )
278 pathPts.push_back( aPt );
279 };
280
281 for( const PCB_SHAPE& s : slotViolation->pathShapes )
282 {
283 BOOST_TEST_MESSAGE( wxString::Format(
284 " path shape type=%d start(%.4f,%.4f) end(%.4f,%.4f) mid(%.4f,%.4f)",
285 (int) s.GetShape(), s.GetStart().x / 1e6, s.GetStart().y / 1e6,
286 s.GetEnd().x / 1e6, s.GetEnd().y / 1e6,
287 s.GetArcMid().x / 1e6, s.GetArcMid().y / 1e6 ) );
288
289 if( s.GetShape() == SHAPE_T::ARC )
290 {
291 SHAPE_ARC arc( s.GetStart(), s.GetArcMid(), s.GetEnd(), 0 );
293
294 for( int i = 0; i < chain.PointCount(); ++i )
295 pushPt( chain.CPoint( i ) );
296 }
297 else
298 {
299 pushPt( s.GetStart() );
300 pushPt( s.GetEnd() );
301 }
302 }
303
304 BOOST_REQUIRE_MESSAGE( pathPts.size() >= 2, "Reported creepage path has no usable geometry" );
305
306 // (1) The path must respect the slot boundary - no point may fall inside the slot interior.
307 // Sample each path span and the densified arc points against the true rounded-slot polygon.
308 double maxInsideMM = 0.0;
309 VECTOR2I worstInside;
310
311 for( size_t i = 0; i + 1 < pathPts.size(); ++i )
312 {
313 const VECTOR2I& a = pathPts[i];
314 const VECTOR2I& b = pathPts[i + 1];
315 const int steps = 32;
316
317 for( int k = 0; k <= steps; ++k )
318 {
319 VECTOR2I pt = a + ( b - a ) * k / steps;
320
321 if( slotPoly.Contains( pt ) )
322 {
323 double depth = std::sqrt( (double) slotPoly.COutline( 0 ).SquaredDistance( pt ) )
324 / 1e6;
325
326 if( depth > maxInsideMM )
327 {
328 maxInsideMM = depth;
329 worstInside = pt;
330 }
331 }
332 }
333 }
334
335 BOOST_TEST_MESSAGE( wxString::Format(
336 "Path has %d shapes; deepest excursion into the slot interior is %.4f mm at "
337 "(%.4f,%.4f)",
338 (int) slotViolation->pathShapes.size(), maxInsideMM, worstInside.x / 1e6,
339 worstInside.y / 1e6 ) );
340
341 BOOST_CHECK_MESSAGE( maxInsideMM < 0.02,
342 wxString::Format( "Creepage path passes through the slot interior by %.4f mm at "
343 "(%.4f,%.4f); it must route around the slot.",
344 maxInsideMM, worstInside.x / 1e6, worstInside.y / 1e6 ) );
345
346 // (2) The path must actually follow the CURVED part of a rounded end, not merely the straight
347 // side between the two corners. The four rounded corners occupy the quadrants where x is within
348 // r of a short end AND y is within r of a long side; only there does the boundary curve. A
349 // point that is flush against the outline inside one of those quadrants proves the path hugged
350 // an arc. A path that cuts a chord across the corner never touches the rounded boundary there.
351 auto onRoundedCorner =
352 [&]( const VECTOR2I& aPt ) -> bool
353 {
354 bool inEndBand = ( aPt.x < x1 + r ) || ( aPt.x > x2 - r );
355 bool inSideBand = ( aPt.y < y1 + r ) || ( aPt.y > y2 - r );
356
357 if( !inEndBand || !inSideBand )
358 return false;
359
360 double distToOutline =
361 std::sqrt( (double) slotPoly.COutline( 0 ).SquaredDistance( aPt ) ) / 1e6;
362
363 return distToOutline < 0.02;
364 };
365
366 bool wrapsRoundedCorner = false;
367
368 for( const VECTOR2I& pt : pathPts )
369 {
370 if( onRoundedCorner( pt ) )
371 {
372 wrapsRoundedCorner = true;
373 break;
374 }
375 }
376
377 BOOST_CHECK_MESSAGE( wrapsRoundedCorner,
378 "Creepage path does not run flush against a rounded corner of the slot; it is not "
379 "respecting the slot's curved boundary." );
380}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
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)
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:80
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:232
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
int GetCornerRadius() const
VECTOR2I GetArcMid() const
Definition kiid.h:44
Definition pad.h:61
const std::vector< PCB_SHAPE > & GetPath() const
Definition pcb_marker.h:158
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
const SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError=DefaultAccuracyForPCB(), int *aActualError=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
SEG::ecoord SquaredDistance(const VECTOR2I &aP, bool aOutlineOnly=false) const override
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
Represent a set of closed polygons.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
@ DRCE_CREEPAGE
Definition drc_item.h:41
@ DRCE_FIRST
Definition drc_item.h:35
@ DRCE_LAST
Definition drc_item.h:120
@ DEGREES_T
Definition eda_angle.h:31
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
#define ERR
@ Edge_Cuts
Definition layer_ids.h:108
@ 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(CreepageRoundedSlotIssue24544, DRC_CREEPAGE_ROUNDED_SLOT_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))
const SHAPE_LINE_CHAIN chain
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683