KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_zone_filler.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
25#include <boost/test/data/test_case.hpp>
26
28#include <board.h>
29#include <board_commit.h>
31#include <drc/drc_engine.h>
32#include <pad.h>
33#include <pcb_track.h>
34#include <footprint.h>
35#include <zone.h>
36#include <drc/drc_engine.h>
37#include <drc/drc_item.h>
40#include <advanced_config.h>
42#include <teardrop/teardrop.h>
43
44
53
54
55int delta = KiROUND( 0.006 * pcbIUScale.IU_PER_MM );
56
57
59{
60 KI_TEST::LoadBoard( m_settingsManager, "zone_filler", m_board );
61
62 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
63
64 KI_TEST::FillZones( m_board.get() );
65
66 // Now that the zones are filled we're going to increase the size of -some- pads and
67 // tracks so that they generate DRC errors. The test then makes sure that those errors
68 // are generated, and that the other pads and tracks do -not- generate errors.
69
70 for( PAD* pad : m_board->Footprints()[0]->Pads() )
71 {
72 if( pad->GetNumber() == "2" || pad->GetNumber() == "4" || pad->GetNumber() == "6" )
73 {
74 pad->SetSize( PADSTACK::ALL_LAYERS,
75 pad->GetSize( PADSTACK::ALL_LAYERS ) + VECTOR2I( delta, delta ) );
76 }
77 }
78
79 int ii = 0;
80 KIID arc8;
81 KIID arc12;
82
83 for( PCB_TRACK* track : m_board->Tracks() )
84 {
85 if( track->Type() == PCB_ARC_T )
86 {
87 ii++;
88
89 if( ii == 8 )
90 {
91 arc8 = track->m_Uuid;
92 track->SetWidth( track->GetWidth() + delta + delta );
93 }
94 else if( ii == 12 )
95 {
96 arc12 = track->m_Uuid;
97 track->Move( VECTOR2I( -delta, -delta ) );
98 }
99 }
100 }
101
102 bool foundPad2Error = false;
103 bool foundPad4Error = false;
104 bool foundPad6Error = false;
105 bool foundArc8Error = false;
106 bool foundArc12Error = false;
107 bool foundOtherError = false;
108
109 bds.m_DRCEngine->InitEngine( wxFileName() ); // Just to be sure to be sure
110
112 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
113 const std::function<void( PCB_MARKER* )>& aPathGenerator )
114 {
115 if( aItem->GetErrorCode() == DRCE_CLEARANCE )
116 {
117 BOARD_ITEM* item_a = m_board->ResolveItem( aItem->GetMainItemID() );
118 PAD* pad_a = dynamic_cast<PAD*>( item_a );
119 PCB_TRACK* trk_a = dynamic_cast<PCB_TRACK*>( item_a );
120
121 BOARD_ITEM* item_b = m_board->ResolveItem( aItem->GetAuxItemID() );
122 PAD* pad_b = dynamic_cast<PAD*>( item_b );
123 PCB_TRACK* trk_b = dynamic_cast<PCB_TRACK*>( item_b );
124
125 if( pad_a && pad_a->GetNumber() == "2" ) foundPad2Error = true;
126 else if( pad_a && pad_a->GetNumber() == "4" ) foundPad4Error = true;
127 else if( pad_a && pad_a->GetNumber() == "6" ) foundPad6Error = true;
128 else if( pad_b && pad_b->GetNumber() == "2" ) foundPad2Error = true;
129 else if( pad_b && pad_b->GetNumber() == "4" ) foundPad4Error = true;
130 else if( pad_b && pad_b->GetNumber() == "6" ) foundPad6Error = true;
131 else if( trk_a && trk_a->m_Uuid == arc8 ) foundArc8Error = true;
132 else if( trk_a && trk_a->m_Uuid == arc12 ) foundArc12Error = true;
133 else if( trk_b && trk_b->m_Uuid == arc8 ) foundArc8Error = true;
134 else if( trk_b && trk_b->m_Uuid == arc12 ) foundArc12Error = true;
135 else foundOtherError = true;
136
137 }
138 } );
139
140 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
141
142 BOOST_CHECK_EQUAL( foundPad2Error, true );
143 BOOST_CHECK_EQUAL( foundPad4Error, true );
144 BOOST_CHECK_EQUAL( foundPad6Error, true );
145 BOOST_CHECK_EQUAL( foundArc8Error, true );
146 BOOST_CHECK_EQUAL( foundArc12Error, true );
147 BOOST_CHECK_EQUAL( foundOtherError, false );
148}
149
150
152{
153 KI_TEST::LoadBoard( m_settingsManager, "notched_zones", m_board );
154
155 // Older algorithms had trouble where the filleted zones intersected and left notches.
156 // See:
157 // https://gitlab.com/kicad/code/kicad/-/issues/2737
158 // https://gitlab.com/kicad/code/kicad/-/issues/2752
159 SHAPE_POLY_SET frontCopper;
160
161 KI_TEST::FillZones( m_board.get() );
162
163 frontCopper = SHAPE_POLY_SET();
164
165 for( ZONE* zone : m_board->Zones() )
166 {
167 if( zone->GetLayerSet().Contains( F_Cu ) )
168 {
169 frontCopper.BooleanAdd( *zone->GetFilledPolysList( F_Cu ) );
170 }
171 }
172
173 BOOST_CHECK_EQUAL( frontCopper.OutlineCount(), 2 );
174}
175
176
177static const std::vector<wxString> RegressionZoneFillTests_tests = {
178 "issue18",
179 "issue2568",
180 "issue3812",
181 "issue5102",
182 "issue5313",
183 "issue5320",
184 "issue5567",
185 "issue5830",
186 "issue6039",
187 "issue6260",
188 "issue6284",
189 "issue7086",
190 "issue14294", // Bad Clipper2 fill
191 "fill_bad" // Missing zone clearance expansion
192};
193
194
196 boost::unit_test::data::make( RegressionZoneFillTests_tests ), relPath )
197{
198 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
199
200 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
201
202 KI_TEST::FillZones( m_board.get() );
203
204 std::vector<DRC_ITEM> violations;
205
207 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
208 const std::function<void( PCB_MARKER* )>& aPathGenerator )
209 {
210 if( aItem->GetErrorCode() == DRCE_CLEARANCE )
211 violations.push_back( *aItem );
212 } );
213
214 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
215
216 if( violations.empty() )
217 {
218 BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
219 BOOST_TEST_MESSAGE( wxString::Format( "Zone fill regression: %s passed", relPath ) );
220 }
221 else
222 {
223 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCH );
224
225 std::map<KIID, EDA_ITEM*> itemMap;
226 m_board->FillItemMap( itemMap );
227
228 for( const DRC_ITEM& item : violations )
229 BOOST_TEST_MESSAGE( item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR, itemMap ) );
230
231 BOOST_ERROR( wxString::Format( "Zone fill regression: %s failed", relPath ) );
232 }
233}
234
235
245BOOST_FIXTURE_TEST_CASE( RegressionZoneClearanceWithIterativeRefill, ZONE_FILL_TEST_FIXTURE )
246{
247 ADVANCED_CFG& cfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
248 bool originalIterativeRefill = cfg.m_ZoneFillIterativeRefill;
249
250 struct ScopeGuard { bool& ref; bool orig; ~ScopeGuard() { ref = orig; } }
251 guard{ cfg.m_ZoneFillIterativeRefill, originalIterativeRefill };
252
253 auto runDrcClearanceCheck =
254 [this]( bool aIterative ) -> int
255 {
256 ADVANCED_CFG& innerCfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
257 innerCfg.m_ZoneFillIterativeRefill = aIterative;
258
259 KI_TEST::LoadBoard( m_settingsManager, "issue23053/issue23053", m_board );
260
261 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
262
263 KI_TEST::FillZones( m_board.get() );
264
265 std::vector<DRC_ITEM> violations;
266
267 std::map<KIID, EDA_ITEM*> itemMap;
268 m_board->FillItemMap( itemMap );
269 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::MM );
270
272 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos,
273 int aLayer,
274 const std::function<void( PCB_MARKER* )>& aPathGenerator )
275 {
276 if( aItem->GetErrorCode() == DRCE_CLEARANCE )
277 {
278 BOARD_ITEM* itemA = m_board->ResolveItem( aItem->GetMainItemID() );
279 BOARD_ITEM* itemB = m_board->ResolveItem( aItem->GetAuxItemID() );
280
281 if( dynamic_cast<ZONE*>( itemA ) && dynamic_cast<ZONE*>( itemB ) )
282 {
283 violations.push_back( *aItem );
284
286 aItem->ShowReport( &unitsProvider,
287 RPT_SEVERITY_ERROR, itemMap ) );
288 }
289 }
290 } );
291
292 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
293
294 return static_cast<int>( violations.size() );
295 };
296
297 int iterativeViolations = runDrcClearanceCheck( true );
298
299 BOOST_CHECK_MESSAGE( iterativeViolations == 0,
300 wxString::Format( "Iterative refill produced %d zone-to-zone clearance "
301 "violations (expected 0)", iterativeViolations ) );
302
303 int nonIterativeViolations = runDrcClearanceCheck( false );
304
305 BOOST_CHECK_MESSAGE( nonIterativeViolations == 0,
306 wxString::Format( "Non-iterative refill produced %d zone-to-zone clearance "
307 "violations (expected 0)", nonIterativeViolations ) );
308}
309
310
311static const std::vector<wxString> RegressionSliverZoneFillTests_tests = {
312 "issue16182" // Slivers
313};
314
315
316BOOST_DATA_TEST_CASE_F( ZONE_FILL_TEST_FIXTURE, RegressionSliverZoneFillTests,
317 boost::unit_test::data::make( RegressionSliverZoneFillTests_tests ),
318 relPath )
319{
320 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
321
322 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
323
324 KI_TEST::FillZones( m_board.get() );
325
326 std::vector<DRC_ITEM> violations;
327
329 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
330 const std::function<void( PCB_MARKER* )>& aPathGenerator )
331 {
332 if( aItem->GetErrorCode() == DRCE_COPPER_SLIVER )
333 violations.push_back( *aItem );
334 } );
335
336 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
337
338 if( violations.empty() )
339 {
340 BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
341 BOOST_TEST_MESSAGE( wxString::Format( "Zone fill copper sliver regression: %s passed", relPath ) );
342 }
343 else
344 {
345 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCH );
346
347 std::map<KIID, EDA_ITEM*> itemMap;
348 m_board->FillItemMap( itemMap );
349
350 for( const DRC_ITEM& item : violations )
351 BOOST_TEST_MESSAGE( item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR, itemMap ) );
352
353 BOOST_ERROR( wxString::Format( "Zone fill copper sliver regression: %s failed", relPath ) );
354 }
355}
356
357
358static const std::vector<std::pair<wxString,int>> RegressionTeardropFill_tests = {
359 { "teardrop_issue_JPC2", 5 }, // Arcs with teardrops connecting to pads
360};
361
362
364 boost::unit_test::data::make( RegressionTeardropFill_tests ), test )
365{
366 const wxString& relPath = test.first;
367 const int count = test.second;
368
369 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
370
371 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
372
373 KI_TEST::FillZones( m_board.get() );
374
375 int zoneCount = 0;
376
377 for( ZONE* zone : m_board->Zones() )
378 {
379 if( zone->IsTeardropArea() )
380 zoneCount++;
381 }
382
383 BOOST_CHECK_MESSAGE( zoneCount == count, "Expected " << count << " teardrop zones in "
384 << relPath << ", found "
385 << zoneCount );
386}
387
388
390{
391
392 std::vector<wxString> tests = { { "issue19956/issue19956" } // Arcs with teardrops connecting to pads
393 };
394
395 for( const wxString& relPath : tests )
396 {
397 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
398 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
399 KI_TEST::FillZones( m_board.get() );
400
401 for( ZONE* zone : m_board->Zones() )
402 {
403 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
404 {
405 std::shared_ptr<SHAPE> a_shape( zone->GetEffectiveShape( layer ) );
406
407 for( PAD* pad : m_board->GetPads() )
408 {
409 std::shared_ptr<SHAPE> pad_shape( pad->GetEffectiveShape( layer ) );
410 int clearance = pad_shape->GetClearance( a_shape.get() );
411 BOOST_CHECK_MESSAGE( pad->GetNetCode() == zone->GetNetCode() || clearance != 0,
412 wxString::Format( "Pad %s from Footprint %s has net code %s and "
413 "is connected to zone with net code %s",
414 pad->GetNumber(),
415 pad->GetParentFootprint()->GetReferenceAsString(),
416 pad->GetNetname(),
417 zone->GetNetname() ) );
418 }
419 }
420 }
421 }
422}
423
424
439BOOST_FIXTURE_TEST_CASE( RegressionZonePriorityIsolatedIslands, ZONE_FILL_TEST_FIXTURE )
440{
441 // Enable iterative refill to fix issue 21746
442 ADVANCED_CFG& cfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
443 bool originalIterativeRefill = cfg.m_ZoneFillIterativeRefill;
444 cfg.m_ZoneFillIterativeRefill = true;
445
446 // Restore config at end of scope to avoid polluting other tests
447 struct ScopeGuard { bool& ref; bool orig; ~ScopeGuard() { ref = orig; } } guard{ cfg.m_ZoneFillIterativeRefill, originalIterativeRefill };
448
449 KI_TEST::LoadBoard( m_settingsManager, "issue21746/issue21746", m_board );
450
451 KI_TEST::FillZones( m_board.get() );
452
453 // Find the GND zone
454 ZONE* gndZone = nullptr;
455
456 for( ZONE* zone : m_board->Zones() )
457 {
458 if( zone->GetNetname() == "GND" )
459 {
460 gndZone = zone;
461 break;
462 }
463 }
464
465 BOOST_REQUIRE_MESSAGE( gndZone != nullptr, "GND zone not found in test board" );
466
467 // Calculate board outline area
468 SHAPE_POLY_SET boardOutline;
469 bool hasOutline = m_board->GetBoardPolygonOutlines( boardOutline, true );
470 BOOST_REQUIRE_MESSAGE( hasOutline, "Board outline not found" );
471
472 double boardArea = 0.0;
473
474 for( int i = 0; i < boardOutline.OutlineCount(); i++ )
475 boardArea += boardOutline.Outline( i ).Area();
476
477 // Get GND zone filled area
478 gndZone->CalculateFilledArea();
479 double gndFilledArea = gndZone->GetFilledArea();
480
481 // The GND zone should fill at least 25% of the board area
482 // With the bug, it fills almost nothing because VDD knocks it out
483 double fillRatio = gndFilledArea / boardArea;
484
485 BOOST_TEST_MESSAGE( wxString::Format( "Board area: %.2f sq mm, GND filled area: %.2f sq mm, "
486 "Fill ratio: %.1f%%",
487 boardArea / 1e6, gndFilledArea / 1e6,
488 fillRatio * 100.0 ) );
489
490 BOOST_CHECK_MESSAGE( fillRatio >= 0.25,
491 wxString::Format( "GND zone fill ratio %.1f%% is less than expected 25%%. "
492 "This indicates issue 21746 - lower priority zones not "
493 "filling areas where higher priority isolated islands "
494 "were removed.",
495 fillRatio * 100.0 ) );
496}
497
498
512BOOST_FIXTURE_TEST_CASE( RegressionViaFlashingUnreachableZone, ZONE_FILL_TEST_FIXTURE )
513{
514 KI_TEST::LoadBoard( m_settingsManager, "issue22010/issue22010", m_board );
515
516 KI_TEST::FillZones( m_board.get() );
517
518 // Find vias with zone_layer_connections set for In1.Cu or In2.Cu
519 // After filling, vias that the zone doesn't actually reach should NOT be flashed
520 int viasWithUnreachableFlashing = 0;
521 int totalConditionalVias = 0;
522
523 PCB_LAYER_ID in1Cu = m_board->GetLayerID( wxT( "In1.Cu" ) );
524 PCB_LAYER_ID in2Cu = m_board->GetLayerID( wxT( "In2.Cu" ) );
525
526 for( PCB_TRACK* track : m_board->Tracks() )
527 {
528 if( track->Type() != PCB_VIA_T )
529 continue;
530
531 PCB_VIA* via = static_cast<PCB_VIA*>( track );
532
533 if( !via->GetRemoveUnconnected() )
534 continue;
535
536 totalConditionalVias++;
537
538 // Check if via is flashed on In1.Cu or In2.Cu
539 bool flashedOnIn1 = via->FlashLayer( in1Cu );
540 bool flashedOnIn2 = via->FlashLayer( in2Cu );
541
542 if( !flashedOnIn1 && !flashedOnIn2 )
543 continue;
544
545 VECTOR2I viaCenter = via->GetPosition();
546 int holeRadius = via->GetDrillValue() / 2;
547
548 // Check if any zone fill actually reaches this via
549 bool zoneReachesVia = false;
550
551 for( ZONE* zone : m_board->Zones() )
552 {
553 if( zone->GetIsRuleArea() )
554 continue;
555
556 if( zone->GetNetCode() != via->GetNetCode() )
557 continue;
558
559 for( PCB_LAYER_ID layer : { in1Cu, in2Cu } )
560 {
561 if( !zone->IsOnLayer( layer ) )
562 continue;
563
564 if( !zone->HasFilledPolysForLayer( layer ) )
565 continue;
566
567 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
568
569 if( fill->Contains( viaCenter, -1, holeRadius ) )
570 {
571 zoneReachesVia = true;
572 break;
573 }
574 }
575
576 if( zoneReachesVia )
577 break;
578 }
579
580 // If via is flashed but zone doesn't reach it, that's the bug
581 if( !zoneReachesVia && ( flashedOnIn1 || flashedOnIn2 ) )
582 viasWithUnreachableFlashing++;
583 }
584
585 BOOST_TEST_MESSAGE( wxString::Format( "Total conditional vias: %d, Vias with unreachable "
586 "flashing: %d", totalConditionalVias,
587 viasWithUnreachableFlashing ) );
588
589 BOOST_CHECK_MESSAGE( viasWithUnreachableFlashing == 0,
590 wxString::Format( "Found %d vias flashed on zone layers where the zone "
591 "fill doesn't actually reach them. This indicates "
592 "issue 22010 is not fixed.",
593 viasWithUnreachableFlashing ) );
594}
595
596
610{
611 KI_TEST::LoadBoard( m_settingsManager, "issue12964/issue12964", m_board );
612
613 KI_TEST::FillZones( m_board.get() );
614
615 int viasShortingZones = 0;
616 int totalConditionalVias = 0;
617
618 for( PCB_TRACK* track : m_board->Tracks() )
619 {
620 if( track->Type() != PCB_VIA_T )
621 continue;
622
623 PCB_VIA* via = static_cast<PCB_VIA*>( track );
624
625 if( !via->GetRemoveUnconnected() )
626 continue;
627
628 totalConditionalVias++;
629
630 VECTOR2I viaCenter = via->GetPosition();
631
632 for( ZONE* zone : m_board->Zones() )
633 {
634 if( zone->GetIsRuleArea() )
635 continue;
636
637 if( zone->GetNetCode() == via->GetNetCode() )
638 continue;
639
640 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
641 {
642 if( !via->FlashLayer( layer ) )
643 continue;
644
645 if( !zone->HasFilledPolysForLayer( layer ) )
646 continue;
647
648 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
649 int viaRadius = via->GetWidth( layer ) / 2;
650
651 if( fill->Contains( viaCenter, -1, viaRadius ) )
652 {
653 BOOST_TEST_MESSAGE( wxString::Format(
654 "Via at (%d, %d) on net %s is flashing on layer %s where zone "
655 "net %s is filled - this creates a short!",
656 viaCenter.x, viaCenter.y, via->GetNetname(),
657 m_board->GetLayerName( layer ), zone->GetNetname() ) );
658 viasShortingZones++;
659 }
660 }
661 }
662 }
663
664 BOOST_TEST_MESSAGE( wxString::Format( "Total conditional vias: %d, Vias shorting zones: %d",
665 totalConditionalVias, viasShortingZones ) );
666
667 BOOST_CHECK_MESSAGE( viasShortingZones == 0,
668 wxString::Format( "Found %d vias flashed on layers where they short to "
669 "zones with different nets. This indicates issue 12964 "
670 "is not fixed.",
671 viasShortingZones ) );
672}
673
674
687BOOST_FIXTURE_TEST_CASE( HatchZoneThermalConnectivity, ZONE_FILL_TEST_FIXTURE )
688{
689 KI_TEST::LoadBoard( m_settingsManager, "hatch_thermal_connectivity/hatch_thermal_connectivity",
690 m_board );
691
692 KI_TEST::FillZones( m_board.get() );
693
694 m_board->BuildConnectivity();
695
696 int unconnectedCount = m_board->GetConnectivity()->GetUnconnectedCount( false );
697
698 BOOST_CHECK_MESSAGE( unconnectedCount == 0,
699 wxString::Format( "Found %d unconnected items after zone fill. "
700 "Hatch zone thermal reliefs should maintain connectivity "
701 "even with large hatch gaps.",
702 unconnectedCount ) );
703}
704
705
722BOOST_FIXTURE_TEST_CASE( RegressionShallowArcZoneFill, ZONE_FILL_TEST_FIXTURE )
723{
724 KI_TEST::LoadBoard( m_settingsManager, "issue22475/issue22475", m_board );
725
726 PCB_LAYER_ID in1Cu = m_board->GetLayerID( wxT( "In1.Cu" ) );
727
728 ZONE* gndZone = nullptr;
729
730 for( ZONE* zone : m_board->Zones() )
731 {
732 if( zone->GetNetname() == "GND" && zone->IsOnLayer( in1Cu ) )
733 {
734 gndZone = zone;
735 break;
736 }
737 }
738
739 BOOST_REQUIRE_MESSAGE( gndZone != nullptr, "GND zone on In1.Cu not found in test board" );
740
741 if( !gndZone )
742 return;
743
744 KI_TEST::FillZones( m_board.get() );
745
746 BOOST_REQUIRE_MESSAGE( gndZone->HasFilledPolysForLayer( in1Cu ),
747 "GND zone has no fill on In1.Cu" );
748
749 const std::shared_ptr<SHAPE_POLY_SET>& fill = gndZone->GetFilledPolysList( in1Cu );
750
751 // The zone fill should produce a single contiguous outline. Multiple outlines
752 // indicate disconnected fill areas caused by malformed clearance holes.
753 BOOST_CHECK_EQUAL( fill->OutlineCount(), 1 );
754
755 double zoneOutlineArea = gndZone->Outline()->Area();
756
757 BOOST_REQUIRE_MESSAGE( zoneOutlineArea > 0.0, "Zone outline area must be positive" );
758
759 double fillArea = 0.0;
760
761 for( int i = 0; i < fill->OutlineCount(); i++ )
762 fillArea += std::abs( fill->Outline( i ).Area() );
763
764 double fillRatio = fillArea / zoneOutlineArea;
765
766 // The zone should be mostly filled. A low fill ratio indicates excessive voids
767 // from malformed clearance holes around shallow arcs.
768 BOOST_CHECK_GE( fillRatio, 0.90 );
769}
770
771
784BOOST_FIXTURE_TEST_CASE( RegressionIterativeRefillRespectsKeepouts, ZONE_FILL_TEST_FIXTURE )
785{
786 // Enable iterative refill
787 ADVANCED_CFG& cfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
788 bool originalIterativeRefill = cfg.m_ZoneFillIterativeRefill;
789 cfg.m_ZoneFillIterativeRefill = true;
790
791 struct ScopeGuard { bool& ref; bool orig; ~ScopeGuard() { ref = orig; } }
792 guard{ cfg.m_ZoneFillIterativeRefill, originalIterativeRefill };
793
794 KI_TEST::LoadBoard( m_settingsManager, "issue22809/issue22809", m_board );
795
796 KI_TEST::FillZones( m_board.get() );
797
798 // Find all zone keepouts
799 std::vector<ZONE*> keepouts;
800
801 for( ZONE* zone : m_board->Zones() )
802 {
803 if( zone->GetIsRuleArea() && zone->GetDoNotAllowZoneFills() )
804 keepouts.push_back( zone );
805 }
806
807 BOOST_REQUIRE_MESSAGE( !keepouts.empty(), "No zone keepouts found in test board" );
808
809 // For each keepout, check that no zone fill exists inside it
810 int violationCount = 0;
811
812 for( ZONE* keepout : keepouts )
813 {
814 for( PCB_LAYER_ID layer : keepout->GetLayerSet().Seq() )
815 {
816 SHAPE_POLY_SET keepoutOutline( *keepout->Outline() );
817 keepoutOutline.ClearArcs();
818
819 for( ZONE* zone : m_board->Zones() )
820 {
821 if( zone->GetIsRuleArea() )
822 continue;
823
824 if( !zone->IsOnLayer( layer ) )
825 continue;
826
827 if( !zone->HasFilledPolysForLayer( layer ) )
828 continue;
829
830 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
831
832 // Check if any fill intersects the keepout
833 SHAPE_POLY_SET intersection = *fill;
834 intersection.BooleanIntersection( keepoutOutline );
835
836 if( intersection.OutlineCount() > 0 )
837 {
838 double intersectionArea = 0;
839
840 for( int i = 0; i < intersection.OutlineCount(); i++ )
841 intersectionArea += std::abs( intersection.Outline( i ).Area() );
842
843 // Allow for small numerical errors (less than 1 square mm)
844 if( intersectionArea > 1e6 )
845 {
846 BOOST_TEST_MESSAGE( wxString::Format(
847 "Zone %s fill on layer %s overlaps keepout by %.2f sq mm",
848 zone->GetNetname(),
849 m_board->GetLayerName( layer ),
850 intersectionArea / 1e6 ) );
851 violationCount++;
852 }
853 }
854 }
855 }
856 }
857
858 BOOST_CHECK_MESSAGE( violationCount == 0,
859 wxString::Format( "Found %d zone fills overlapping keepout areas. "
860 "This indicates issue 22809 - iterative refiller "
861 "ignores zone keepouts.", violationCount ) );
862}
863
864
878BOOST_FIXTURE_TEST_CASE( RegressionTHPadInnerLayerFlashing, ZONE_FILL_TEST_FIXTURE )
879{
880 KI_TEST::LoadBoard( m_settingsManager, "issue22826/issue22826", m_board );
881
882 KI_TEST::FillZones( m_board.get() );
883
884 PCB_LAYER_ID in2Cu = m_board->GetLayerID( wxT( "In2.Cu" ) );
885 int padsWithMissingFlashing = 0;
886 int totalConditionalPads = 0;
887
888 for( FOOTPRINT* footprint : m_board->Footprints() )
889 {
890 for( PAD* pad : footprint->Pads() )
891 {
892 if( !pad->GetRemoveUnconnected() )
893 continue;
894
895 if( !pad->HasHole() )
896 continue;
897
898 if( pad->GetNetname() != "VBUS_DUT" && pad->GetNetname() != "VBUS_DBG" )
899 continue;
900
901 totalConditionalPads++;
902
903 // Check if the pad should flash on In2.Cu
904 bool shouldFlash = false;
905
906 for( ZONE* zone : m_board->Zones() )
907 {
908 if( zone->GetIsRuleArea() )
909 continue;
910
911 if( zone->GetNetCode() != pad->GetNetCode() )
912 continue;
913
914 if( !zone->IsOnLayer( in2Cu ) )
915 continue;
916
917 if( zone->Outline()->Contains( pad->GetPosition() ) )
918 {
919 shouldFlash = true;
920 break;
921 }
922 }
923
924 if( shouldFlash && !pad->FlashLayer( in2Cu ) )
925 {
926 BOOST_TEST_MESSAGE( wxString::Format(
927 "Pad %s at (%d, %d) on net %s is inside zone but not flashing on In2.Cu",
928 pad->GetNumber(), pad->GetPosition().x, pad->GetPosition().y,
929 pad->GetNetname() ) );
930 padsWithMissingFlashing++;
931 }
932 }
933 }
934
935 BOOST_TEST_MESSAGE( wxString::Format( "Total conditional pads: %d, Pads with missing "
936 "flashing: %d", totalConditionalPads,
937 padsWithMissingFlashing ) );
938
939 BOOST_CHECK_MESSAGE( padsWithMissingFlashing == 0,
940 wxString::Format( "Found %d TH pads that should flash on inner layers "
941 "but don't. This indicates issue 22826 is not fixed.",
942 padsWithMissingFlashing ) );
943}
944
945
954BOOST_FIXTURE_TEST_CASE( RegressionRoundRectTeardropGeometry, ZONE_FILL_TEST_FIXTURE )
955{
956 KI_TEST::LoadBoard( m_settingsManager, "issue19405_roundrect_teardrop", m_board );
957
958 // Set up tool manager for teardrop generation
959 TOOL_MANAGER toolMgr;
960 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, nullptr );
961
962 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
963 toolMgr.RegisterTool( dummyTool );
964
965 // Generate teardrops
966 BOARD_COMMIT commit( dummyTool );
967 TEARDROP_MANAGER teardropMgr( m_board.get(), &toolMgr );
968 teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true );
969
970 if( !commit.Empty() )
971 commit.Push( _( "Add teardrops" ), SKIP_UNDO | SKIP_SET_DIRTY );
972
973 // Find teardrop zones
974 int teardropCount = 0;
975 bool foundBadTeardrop = false;
976
977 for( ZONE* zone : m_board->Zones() )
978 {
979 if( !zone->IsTeardropArea() )
980 continue;
981
982 teardropCount++;
983
984 // Get the teardrop outline
985 const SHAPE_POLY_SET* outline = zone->Outline();
986
987 if( !outline || outline->OutlineCount() == 0 )
988 continue;
989
990 const SHAPE_LINE_CHAIN& chain = outline->Outline( 0 );
991
992 // Check that the teardrop polygon is convex or at least doesn't have
993 // any sharp concave angles that would indicate intersection with the pad corner.
994 // A well-formed teardrop should have all turns in the same direction
995 // (or very close to it) except at the pad anchor points.
996 int concaveCount = 0;
997
998 for( int i = 0; i < chain.PointCount(); i++ )
999 {
1000 int prev = ( i == 0 ) ? chain.PointCount() - 1 : i - 1;
1001 int next = ( i + 1 ) % chain.PointCount();
1002
1003 VECTOR2I v1 = chain.CPoint( i ) - chain.CPoint( prev );
1004 VECTOR2I v2 = chain.CPoint( next ) - chain.CPoint( i );
1005
1006 // Cross product gives handedness of turn
1007 int64_t cross = (int64_t) v1.x * v2.y - (int64_t) v1.y * v2.x;
1008
1009 // Count significant concave turns (negative cross product for CCW polygons)
1010 // Small values are numerical noise
1011 if( cross < -1000 )
1012 concaveCount++;
1013 }
1014
1015 // A teardrop should have at most 2-3 concave points (at the pad anchor points)
1016 // Many concave points indicate the curve is intersecting the pad corner
1017 if( concaveCount > 5 )
1018 {
1019 BOOST_TEST_MESSAGE( wxString::Format( "Teardrop has %d concave vertices, "
1020 "indicating possible corner intersection",
1021 concaveCount ) );
1022 foundBadTeardrop = true;
1023 }
1024 }
1025
1026 BOOST_CHECK_MESSAGE( teardropCount > 0, "Expected at least one teardrop zone" );
1027
1028 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
1029 "Found teardrop with excessive concave vertices, indicating "
1030 "issue 19405 - teardrop curve intersecting rounded rectangle corner" );
1031}
1032
1033
1042{
1043 KI_TEST::LoadBoard( m_settingsManager, "oval_teardrop", m_board );
1044
1045 // Set up tool manager for teardrop generation
1046 TOOL_MANAGER toolMgr;
1047 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, nullptr );
1048
1049 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
1050 toolMgr.RegisterTool( dummyTool );
1051
1052 // Generate teardrops
1053 BOARD_COMMIT commit( dummyTool );
1054 TEARDROP_MANAGER teardropMgr( m_board.get(), &toolMgr );
1055 teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true );
1056
1057 if( !commit.Empty() )
1058 commit.Push( _( "Add teardrops" ), SKIP_UNDO | SKIP_SET_DIRTY );
1059
1060 // Find teardrop zones
1061 int teardropCount = 0;
1062 bool foundBadTeardrop = false;
1063
1064 for( ZONE* zone : m_board->Zones() )
1065 {
1066 if( !zone->IsTeardropArea() )
1067 continue;
1068
1069 teardropCount++;
1070
1071 const SHAPE_POLY_SET* outline = zone->Outline();
1072
1073 if( !outline || outline->OutlineCount() == 0 )
1074 continue;
1075
1076 const SHAPE_LINE_CHAIN& chain = outline->Outline( 0 );
1077
1078 // Check for excessive concave vertices that would indicate the teardrop curve
1079 // is not tangent to the oval's semicircular end
1080 int concaveCount = 0;
1081
1082 for( int i = 0; i < chain.PointCount(); i++ )
1083 {
1084 int prev = ( i == 0 ) ? chain.PointCount() - 1 : i - 1;
1085 int next = ( i + 1 ) % chain.PointCount();
1086
1087 VECTOR2I v1 = chain.CPoint( i ) - chain.CPoint( prev );
1088 VECTOR2I v2 = chain.CPoint( next ) - chain.CPoint( i );
1089
1090 int64_t cross = (int64_t) v1.x * v2.y - (int64_t) v1.y * v2.x;
1091
1092 if( cross < -1000 )
1093 concaveCount++;
1094 }
1095
1096 if( concaveCount > 5 )
1097 {
1098 BOOST_TEST_MESSAGE( wxString::Format( "Oval teardrop has %d concave vertices",
1099 concaveCount ) );
1100 foundBadTeardrop = true;
1101 }
1102 }
1103
1104 BOOST_CHECK_MESSAGE( teardropCount > 0, "Expected at least one teardrop zone" );
1105
1106 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
1107 "Found teardrop with excessive concave vertices on oval pad, "
1108 "indicating curve is not tangent to semicircular end" );
1109}
1110
1111
1120{
1121 KI_TEST::LoadBoard( m_settingsManager, "large_circle_teardrop", m_board );
1122
1123 // Set up tool manager for teardrop generation
1124 TOOL_MANAGER toolMgr;
1125 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, nullptr );
1126
1127 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
1128 toolMgr.RegisterTool( dummyTool );
1129
1130 // Generate teardrops
1131 BOARD_COMMIT commit( dummyTool );
1132 TEARDROP_MANAGER teardropMgr( m_board.get(), &toolMgr );
1133 teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true );
1134
1135 if( !commit.Empty() )
1136 commit.Push( _( "Add teardrops" ), SKIP_UNDO | SKIP_SET_DIRTY );
1137
1138 // Find the pad and its teardrop
1139 PAD* largePad = nullptr;
1140
1141 for( FOOTPRINT* fp : m_board->Footprints() )
1142 {
1143 for( PAD* pad : fp->Pads() )
1144 {
1145 if( pad->GetShape( F_Cu ) == PAD_SHAPE::CIRCLE )
1146 {
1147 largePad = pad;
1148 break;
1149 }
1150 }
1151 }
1152
1153 BOOST_REQUIRE_MESSAGE( largePad != nullptr, "Expected a circular pad in test board" );
1154
1155 int padRadius = largePad->GetSize( F_Cu ).x / 2;
1156 VECTOR2I padCenter = largePad->GetPosition();
1157
1158 // Find teardrop zones
1159 int teardropCount = 0;
1160 bool foundBadTeardrop = false;
1161
1162 for( ZONE* zone : m_board->Zones() )
1163 {
1164 if( !zone->IsTeardropArea() )
1165 continue;
1166
1167 teardropCount++;
1168
1169 const SHAPE_POLY_SET* outline = zone->Outline();
1170
1171 if( !outline || outline->OutlineCount() == 0 )
1172 continue;
1173
1174 const SHAPE_LINE_CHAIN& chain = outline->Outline( 0 );
1175
1176 // Check for excessive concave vertices
1177 int concaveCount = 0;
1178
1179 for( int i = 0; i < chain.PointCount(); i++ )
1180 {
1181 int prev = ( i == 0 ) ? chain.PointCount() - 1 : i - 1;
1182 int next = ( i + 1 ) % chain.PointCount();
1183
1184 VECTOR2I v1 = chain.CPoint( i ) - chain.CPoint( prev );
1185 VECTOR2I v2 = chain.CPoint( next ) - chain.CPoint( i );
1186
1187 int64_t cross = (int64_t) v1.x * v2.y - (int64_t) v1.y * v2.x;
1188
1189 if( cross < -1000 )
1190 concaveCount++;
1191 }
1192
1193 if( concaveCount > 5 )
1194 {
1195 BOOST_TEST_MESSAGE( wxString::Format( "Large circle teardrop has %d concave vertices",
1196 concaveCount ) );
1197 foundBadTeardrop = true;
1198 }
1199
1200 // Also verify that the teardrop anchor points near the pad are approximately
1201 // on the circle edge (within tolerance)
1202 int maxError = m_board->GetDesignSettings().m_MaxError;
1203
1204 for( int i = 0; i < chain.PointCount(); i++ )
1205 {
1206 VECTOR2I pt = chain.CPoint( i );
1207 double dist = ( pt - padCenter ).EuclideanNorm();
1208
1209 // Points that are close to the circle should be approximately on it
1210 if( dist > padRadius * 0.5 && dist < padRadius * 1.5 )
1211 {
1212 double deviation = std::abs( dist - padRadius );
1213
1214 // Allow some tolerance for polygon approximation
1215 if( deviation > maxError * 5 && deviation < padRadius * 0.2 )
1216 {
1217 BOOST_TEST_MESSAGE( wxString::Format(
1218 "Teardrop point at distance %.2f from pad center (radius %.2f), "
1219 "deviation %.2f exceeds tolerance",
1220 dist / 1000.0, padRadius / 1000.0, deviation / 1000.0 ) );
1221 }
1222 }
1223 }
1224 }
1225
1226 BOOST_CHECK_MESSAGE( teardropCount > 0, "Expected at least one teardrop zone" );
1227
1228 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
1229 "Found teardrop with excessive concave vertices on large circle, "
1230 "indicating anchor points may not be on circle edge" );
1231}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Container for design settings for a BOARD object.
std::shared_ptr< DRC_ENGINE > m_DRCEngine
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
bool Empty() const
Definition commit.h:137
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:167
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
const KIID m_Uuid
Definition eda_item.h:522
Definition kiid.h:49
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
const wxString & GetNumber() const
Definition pad.h:137
VECTOR2I GetPosition() const override
Definition pad.h:209
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition pad.h:264
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
double Area(bool aAbsolute=true) const
Return the area of this chain.
Represent a set of closed polygons.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
double Area()
Return the area of this poly set.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
TEARDROP_MANAGER manage and build teardrop areas A teardrop area is a polygonal area (a copper ZONE) ...
Definition teardrop.h:95
void UpdateTeardrops(BOARD_COMMIT &aCommit, const std::vector< BOARD_ITEM * > *dirtyPadsAndVias, const std::set< PCB_TRACK * > *dirtyTracks, bool aForceFullUpdate=false)
Update teardrops on a list of items.
Definition teardrop.cpp:230
Master controller class:
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
Handle a list of polygons defining a copper zone.
Definition zone.h:73
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:606
double GetFilledArea()
This area is cached from the most recent call to CalculateFilledArea().
Definition zone.h:265
SHAPE_POLY_SET * Outline()
Definition zone.h:340
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition zone.h:597
double CalculateFilledArea()
Compute the area currently occupied by the zone fill.
Definition zone.cpp:1579
@ DRCE_CLEARANCE
Definition drc_item.h:44
@ DRCE_COPPER_SLIVER
Definition drc_item.h:93
#define _(s)
bool m_ZoneFillIterativeRefill
Enable iterative zone filling to handle isolated islands in higher priority zones.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_Cu
Definition layer_ids.h:64
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
void FillZones(BOARD *m_board)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
CITER next(CITER it)
Definition ptree.cpp:124
@ RPT_SEVERITY_ERROR
#define SKIP_SET_DIRTY
Definition sch_commit.h:42
#define SKIP_UNDO
Definition sch_commit.h:40
std::unique_ptr< BOARD > m_board
SETTINGS_MANAGER m_settingsManager
VECTOR3I v1(5, 5, 5)
const SHAPE_LINE_CHAIN chain
int clearance
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2I v2(1, 0)
static const std::vector< wxString > RegressionZoneFillTests_tests
int delta
static const std::vector< std::pair< wxString, int > > RegressionTeardropFill_tests
BOOST_DATA_TEST_CASE_F(ZONE_FILL_TEST_FIXTURE, RegressionZoneFillTests, boost::unit_test::data::make(RegressionZoneFillTests_tests), relPath)
static const std::vector< wxString > RegressionSliverZoneFillTests_tests
BOOST_FIXTURE_TEST_CASE(BasicZoneFills, ZONE_FILL_TEST_FIXTURE)
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695