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 <pad.h>
32#include <pcb_track.h>
33#include <footprint.h>
34#include <zone.h>
35#include <drc/drc_item.h>
38#include <advanced_config.h>
40#include <teardrop/teardrop.h>
41
42
51
52
53int delta = KiROUND( 0.006 * pcbIUScale.IU_PER_MM );
54
55
57{
58 KI_TEST::LoadBoard( m_settingsManager, "zone_filler", m_board );
59
60 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
61
62 KI_TEST::FillZones( m_board.get() );
63
64 // Now that the zones are filled we're going to increase the size of -some- pads and
65 // tracks so that they generate DRC errors. The test then makes sure that those errors
66 // are generated, and that the other pads and tracks do -not- generate errors.
67
68 for( PAD* pad : m_board->Footprints()[0]->Pads() )
69 {
70 if( pad->GetNumber() == "2" || pad->GetNumber() == "4" || pad->GetNumber() == "6" )
71 {
72 pad->SetSize( PADSTACK::ALL_LAYERS,
73 pad->GetSize( PADSTACK::ALL_LAYERS ) + VECTOR2I( delta, delta ) );
74 }
75 }
76
77 int ii = 0;
78 KIID arc8;
79 KIID arc12;
80
81 for( PCB_TRACK* track : m_board->Tracks() )
82 {
83 if( track->Type() == PCB_ARC_T )
84 {
85 ii++;
86
87 if( ii == 8 )
88 {
89 arc8 = track->m_Uuid;
90 track->SetWidth( track->GetWidth() + delta + delta );
91 }
92 else if( ii == 12 )
93 {
94 arc12 = track->m_Uuid;
95 track->Move( VECTOR2I( -delta, -delta ) );
96 }
97 }
98 }
99
100 bool foundPad2Error = false;
101 bool foundPad4Error = false;
102 bool foundPad6Error = false;
103 bool foundArc8Error = false;
104 bool foundArc12Error = false;
105 bool foundOtherError = false;
106
107 bds.m_DRCEngine->InitEngine( wxFileName() ); // Just to be sure to be sure
108
110 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
111 const std::function<void( PCB_MARKER* )>& aPathGenerator )
112 {
113 if( aItem->GetErrorCode() == DRCE_CLEARANCE )
114 {
115 BOARD_ITEM* item_a = m_board->ResolveItem( aItem->GetMainItemID() );
116 PAD* pad_a = dynamic_cast<PAD*>( item_a );
117 PCB_TRACK* trk_a = dynamic_cast<PCB_TRACK*>( item_a );
118
119 BOARD_ITEM* item_b = m_board->ResolveItem( aItem->GetAuxItemID() );
120 PAD* pad_b = dynamic_cast<PAD*>( item_b );
121 PCB_TRACK* trk_b = dynamic_cast<PCB_TRACK*>( item_b );
122
123 if( pad_a && pad_a->GetNumber() == "2" ) foundPad2Error = true;
124 else if( pad_a && pad_a->GetNumber() == "4" ) foundPad4Error = true;
125 else if( pad_a && pad_a->GetNumber() == "6" ) foundPad6Error = true;
126 else if( pad_b && pad_b->GetNumber() == "2" ) foundPad2Error = true;
127 else if( pad_b && pad_b->GetNumber() == "4" ) foundPad4Error = true;
128 else if( pad_b && pad_b->GetNumber() == "6" ) foundPad6Error = true;
129 else if( trk_a && trk_a->m_Uuid == arc8 ) foundArc8Error = true;
130 else if( trk_a && trk_a->m_Uuid == arc12 ) foundArc12Error = true;
131 else if( trk_b && trk_b->m_Uuid == arc8 ) foundArc8Error = true;
132 else if( trk_b && trk_b->m_Uuid == arc12 ) foundArc12Error = true;
133 else foundOtherError = true;
134
135 }
136 } );
137
138 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
139
140 BOOST_CHECK_EQUAL( foundPad2Error, true );
141 BOOST_CHECK_EQUAL( foundPad4Error, true );
142 BOOST_CHECK_EQUAL( foundPad6Error, true );
143 BOOST_CHECK_EQUAL( foundArc8Error, true );
144 BOOST_CHECK_EQUAL( foundArc12Error, true );
145 BOOST_CHECK_EQUAL( foundOtherError, false );
146}
147
148
150{
151 KI_TEST::LoadBoard( m_settingsManager, "notched_zones", m_board );
152
153 // Older algorithms had trouble where the filleted zones intersected and left notches.
154 // See:
155 // https://gitlab.com/kicad/code/kicad/-/issues/2737
156 // https://gitlab.com/kicad/code/kicad/-/issues/2752
157 SHAPE_POLY_SET frontCopper;
158
159 KI_TEST::FillZones( m_board.get() );
160
161 frontCopper = SHAPE_POLY_SET();
162
163 for( ZONE* zone : m_board->Zones() )
164 {
165 if( zone->GetLayerSet().Contains( F_Cu ) )
166 {
167 frontCopper.BooleanAdd( *zone->GetFilledPolysList( F_Cu ) );
168 }
169 }
170
171 BOOST_CHECK_EQUAL( frontCopper.OutlineCount(), 2 );
172}
173
174
175static const std::vector<wxString> RegressionZoneFillTests_tests = {
176 "issue18",
177 "issue2568",
178 "issue3812",
179 "issue5102",
180 "issue5313",
181 "issue5320",
182 "issue5567",
183 "issue5830",
184 "issue6039",
185 "issue6260",
186 "issue6284",
187 "issue7086",
188 "issue14294", // Bad Clipper2 fill
189 "fill_bad" // Missing zone clearance expansion
190};
191
192
194 boost::unit_test::data::make( RegressionZoneFillTests_tests ), relPath )
195{
196 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
197
198 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
199
200 KI_TEST::FillZones( m_board.get() );
201
202 std::vector<DRC_ITEM> violations;
203
205 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
206 const std::function<void( PCB_MARKER* )>& aPathGenerator )
207 {
208 if( aItem->GetErrorCode() == DRCE_CLEARANCE )
209 violations.push_back( *aItem );
210 } );
211
212 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
213
214 if( violations.empty() )
215 {
216 BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
217 BOOST_TEST_MESSAGE( wxString::Format( "Zone fill regression: %s passed", relPath ) );
218 }
219 else
220 {
221 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCH );
222
223 std::map<KIID, EDA_ITEM*> itemMap;
224 m_board->FillItemMap( itemMap );
225
226 for( const DRC_ITEM& item : violations )
227 BOOST_TEST_MESSAGE( item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR, itemMap ) );
228
229 BOOST_ERROR( wxString::Format( "Zone fill regression: %s failed", relPath ) );
230 }
231}
232
233
234static const std::vector<wxString> RegressionSliverZoneFillTests_tests = {
235 "issue16182" // Slivers
236};
237
238
239BOOST_DATA_TEST_CASE_F( ZONE_FILL_TEST_FIXTURE, RegressionSliverZoneFillTests,
240 boost::unit_test::data::make( RegressionSliverZoneFillTests_tests ),
241 relPath )
242{
243 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
244
245 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
246
247 KI_TEST::FillZones( m_board.get() );
248
249 std::vector<DRC_ITEM> violations;
250
252 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
253 const std::function<void( PCB_MARKER* )>& aPathGenerator )
254 {
255 if( aItem->GetErrorCode() == DRCE_COPPER_SLIVER )
256 violations.push_back( *aItem );
257 } );
258
259 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
260
261 if( violations.empty() )
262 {
263 BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
264 BOOST_TEST_MESSAGE( wxString::Format( "Zone fill copper sliver regression: %s passed", relPath ) );
265 }
266 else
267 {
268 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCH );
269
270 std::map<KIID, EDA_ITEM*> itemMap;
271 m_board->FillItemMap( itemMap );
272
273 for( const DRC_ITEM& item : violations )
274 BOOST_TEST_MESSAGE( item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR, itemMap ) );
275
276 BOOST_ERROR( wxString::Format( "Zone fill copper sliver regression: %s failed", relPath ) );
277 }
278}
279
280
281static const std::vector<std::pair<wxString,int>> RegressionTeardropFill_tests = {
282 { "teardrop_issue_JPC2", 5 }, // Arcs with teardrops connecting to pads
283};
284
285
287 boost::unit_test::data::make( RegressionTeardropFill_tests ), test )
288{
289 const wxString& relPath = test.first;
290 const int count = test.second;
291
292 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
293
294 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
295
296 KI_TEST::FillZones( m_board.get() );
297
298 int zoneCount = 0;
299
300 for( ZONE* zone : m_board->Zones() )
301 {
302 if( zone->IsTeardropArea() )
303 zoneCount++;
304 }
305
306 BOOST_CHECK_MESSAGE( zoneCount == count, "Expected " << count << " teardrop zones in "
307 << relPath << ", found "
308 << zoneCount );
309}
310
311
313{
314
315 std::vector<wxString> tests = { { "issue19956/issue19956" } // Arcs with teardrops connecting to pads
316 };
317
318 for( const wxString& relPath : tests )
319 {
320 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
321 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
322 KI_TEST::FillZones( m_board.get() );
323
324 for( ZONE* zone : m_board->Zones() )
325 {
326 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
327 {
328 std::shared_ptr<SHAPE> a_shape( zone->GetEffectiveShape( layer ) );
329
330 for( PAD* pad : m_board->GetPads() )
331 {
332 std::shared_ptr<SHAPE> pad_shape( pad->GetEffectiveShape( layer ) );
333 int clearance = pad_shape->GetClearance( a_shape.get() );
334 BOOST_CHECK_MESSAGE( pad->GetNetCode() == zone->GetNetCode() || clearance != 0,
335 wxString::Format( "Pad %s from Footprint %s has net code %s and "
336 "is connected to zone with net code %s",
337 pad->GetNumber(),
338 pad->GetParentFootprint()->GetReferenceAsString(),
339 pad->GetNetname(),
340 zone->GetNetname() ) );
341 }
342 }
343 }
344 }
345}
346
347
362BOOST_FIXTURE_TEST_CASE( RegressionZonePriorityIsolatedIslands, ZONE_FILL_TEST_FIXTURE )
363{
364 // Enable iterative refill to fix issue 21746
365 ADVANCED_CFG& cfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
366 bool originalIterativeRefill = cfg.m_ZoneFillIterativeRefill;
367 cfg.m_ZoneFillIterativeRefill = true;
368
369 // Restore config at end of scope to avoid polluting other tests
370 struct ScopeGuard { bool& ref; bool orig; ~ScopeGuard() { ref = orig; } } guard{ cfg.m_ZoneFillIterativeRefill, originalIterativeRefill };
371
372 KI_TEST::LoadBoard( m_settingsManager, "issue21746/issue21746", m_board );
373
374 KI_TEST::FillZones( m_board.get() );
375
376 // Find the GND zone
377 ZONE* gndZone = nullptr;
378
379 for( ZONE* zone : m_board->Zones() )
380 {
381 if( zone->GetNetname() == "GND" )
382 {
383 gndZone = zone;
384 break;
385 }
386 }
387
388 BOOST_REQUIRE_MESSAGE( gndZone != nullptr, "GND zone not found in test board" );
389
390 // Calculate board outline area
391 SHAPE_POLY_SET boardOutline;
392 bool hasOutline = m_board->GetBoardPolygonOutlines( boardOutline, true );
393 BOOST_REQUIRE_MESSAGE( hasOutline, "Board outline not found" );
394
395 double boardArea = 0.0;
396
397 for( int i = 0; i < boardOutline.OutlineCount(); i++ )
398 boardArea += boardOutline.Outline( i ).Area();
399
400 // Get GND zone filled area
401 gndZone->CalculateFilledArea();
402 double gndFilledArea = gndZone->GetFilledArea();
403
404 // The GND zone should fill at least 25% of the board area
405 // With the bug, it fills almost nothing because VDD knocks it out
406 double fillRatio = gndFilledArea / boardArea;
407
408 BOOST_TEST_MESSAGE( wxString::Format( "Board area: %.2f sq mm, GND filled area: %.2f sq mm, "
409 "Fill ratio: %.1f%%",
410 boardArea / 1e6, gndFilledArea / 1e6,
411 fillRatio * 100.0 ) );
412
413 BOOST_CHECK_MESSAGE( fillRatio >= 0.25,
414 wxString::Format( "GND zone fill ratio %.1f%% is less than expected 25%%. "
415 "This indicates issue 21746 - lower priority zones not "
416 "filling areas where higher priority isolated islands "
417 "were removed.",
418 fillRatio * 100.0 ) );
419}
420
421
435BOOST_FIXTURE_TEST_CASE( RegressionViaFlashingUnreachableZone, ZONE_FILL_TEST_FIXTURE )
436{
437 KI_TEST::LoadBoard( m_settingsManager, "issue22010/issue22010", m_board );
438
439 KI_TEST::FillZones( m_board.get() );
440
441 // Find vias with zone_layer_connections set for In1.Cu or In2.Cu
442 // After filling, vias that the zone doesn't actually reach should NOT be flashed
443 int viasWithUnreachableFlashing = 0;
444 int totalConditionalVias = 0;
445
446 PCB_LAYER_ID in1Cu = m_board->GetLayerID( wxT( "In1.Cu" ) );
447 PCB_LAYER_ID in2Cu = m_board->GetLayerID( wxT( "In2.Cu" ) );
448
449 for( PCB_TRACK* track : m_board->Tracks() )
450 {
451 if( track->Type() != PCB_VIA_T )
452 continue;
453
454 PCB_VIA* via = static_cast<PCB_VIA*>( track );
455
456 if( !via->GetRemoveUnconnected() )
457 continue;
458
459 totalConditionalVias++;
460
461 // Check if via is flashed on In1.Cu or In2.Cu
462 bool flashedOnIn1 = via->FlashLayer( in1Cu );
463 bool flashedOnIn2 = via->FlashLayer( in2Cu );
464
465 if( !flashedOnIn1 && !flashedOnIn2 )
466 continue;
467
468 VECTOR2I viaCenter = via->GetPosition();
469 int holeRadius = via->GetDrillValue() / 2;
470
471 // Check if any zone fill actually reaches this via
472 bool zoneReachesVia = false;
473
474 for( ZONE* zone : m_board->Zones() )
475 {
476 if( zone->GetIsRuleArea() )
477 continue;
478
479 if( zone->GetNetCode() != via->GetNetCode() )
480 continue;
481
482 for( PCB_LAYER_ID layer : { in1Cu, in2Cu } )
483 {
484 if( !zone->IsOnLayer( layer ) )
485 continue;
486
487 if( !zone->HasFilledPolysForLayer( layer ) )
488 continue;
489
490 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
491
492 if( fill->Contains( viaCenter, -1, holeRadius ) )
493 {
494 zoneReachesVia = true;
495 break;
496 }
497 }
498
499 if( zoneReachesVia )
500 break;
501 }
502
503 // If via is flashed but zone doesn't reach it, that's the bug
504 if( !zoneReachesVia && ( flashedOnIn1 || flashedOnIn2 ) )
505 viasWithUnreachableFlashing++;
506 }
507
508 BOOST_TEST_MESSAGE( wxString::Format( "Total conditional vias: %d, Vias with unreachable "
509 "flashing: %d", totalConditionalVias,
510 viasWithUnreachableFlashing ) );
511
512 BOOST_CHECK_MESSAGE( viasWithUnreachableFlashing == 0,
513 wxString::Format( "Found %d vias flashed on zone layers where the zone "
514 "fill doesn't actually reach them. This indicates "
515 "issue 22010 is not fixed.",
516 viasWithUnreachableFlashing ) );
517}
518
519
533{
534 KI_TEST::LoadBoard( m_settingsManager, "issue12964/issue12964", m_board );
535
536 KI_TEST::FillZones( m_board.get() );
537
538 int viasShortingZones = 0;
539 int totalConditionalVias = 0;
540
541 for( PCB_TRACK* track : m_board->Tracks() )
542 {
543 if( track->Type() != PCB_VIA_T )
544 continue;
545
546 PCB_VIA* via = static_cast<PCB_VIA*>( track );
547
548 if( !via->GetRemoveUnconnected() )
549 continue;
550
551 totalConditionalVias++;
552
553 VECTOR2I viaCenter = via->GetPosition();
554
555 for( ZONE* zone : m_board->Zones() )
556 {
557 if( zone->GetIsRuleArea() )
558 continue;
559
560 if( zone->GetNetCode() == via->GetNetCode() )
561 continue;
562
563 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
564 {
565 if( !via->FlashLayer( layer ) )
566 continue;
567
568 if( !zone->HasFilledPolysForLayer( layer ) )
569 continue;
570
571 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
572 int viaRadius = via->GetWidth( layer ) / 2;
573
574 if( fill->Contains( viaCenter, -1, viaRadius ) )
575 {
576 BOOST_TEST_MESSAGE( wxString::Format(
577 "Via at (%d, %d) on net %s is flashing on layer %s where zone "
578 "net %s is filled - this creates a short!",
579 viaCenter.x, viaCenter.y, via->GetNetname(),
580 m_board->GetLayerName( layer ), zone->GetNetname() ) );
581 viasShortingZones++;
582 }
583 }
584 }
585 }
586
587 BOOST_TEST_MESSAGE( wxString::Format( "Total conditional vias: %d, Vias shorting zones: %d",
588 totalConditionalVias, viasShortingZones ) );
589
590 BOOST_CHECK_MESSAGE( viasShortingZones == 0,
591 wxString::Format( "Found %d vias flashed on layers where they short to "
592 "zones with different nets. This indicates issue 12964 "
593 "is not fixed.",
594 viasShortingZones ) );
595}
596
597
610BOOST_FIXTURE_TEST_CASE( HatchZoneThermalConnectivity, ZONE_FILL_TEST_FIXTURE )
611{
612 KI_TEST::LoadBoard( m_settingsManager, "hatch_thermal_connectivity/hatch_thermal_connectivity",
613 m_board );
614
615 KI_TEST::FillZones( m_board.get() );
616
617 m_board->BuildConnectivity();
618
619 int unconnectedCount = m_board->GetConnectivity()->GetUnconnectedCount( false );
620
621 BOOST_CHECK_MESSAGE( unconnectedCount == 0,
622 wxString::Format( "Found %d unconnected items after zone fill. "
623 "Hatch zone thermal reliefs should maintain connectivity "
624 "even with large hatch gaps.",
625 unconnectedCount ) );
626}
627
628
645BOOST_FIXTURE_TEST_CASE( RegressionShallowArcZoneFill, ZONE_FILL_TEST_FIXTURE )
646{
647 KI_TEST::LoadBoard( m_settingsManager, "issue22475/issue22475", m_board );
648
649 PCB_LAYER_ID in1Cu = m_board->GetLayerID( wxT( "In1.Cu" ) );
650
651 ZONE* gndZone = nullptr;
652
653 for( ZONE* zone : m_board->Zones() )
654 {
655 if( zone->GetNetname() == "GND" && zone->IsOnLayer( in1Cu ) )
656 {
657 gndZone = zone;
658 break;
659 }
660 }
661
662 BOOST_REQUIRE_MESSAGE( gndZone != nullptr, "GND zone on In1.Cu not found in test board" );
663
664 if( !gndZone )
665 return;
666
667 KI_TEST::FillZones( m_board.get() );
668
669 BOOST_REQUIRE_MESSAGE( gndZone->HasFilledPolysForLayer( in1Cu ),
670 "GND zone has no fill on In1.Cu" );
671
672 const std::shared_ptr<SHAPE_POLY_SET>& fill = gndZone->GetFilledPolysList( in1Cu );
673
674 // The zone fill should produce a single contiguous outline. Multiple outlines
675 // indicate disconnected fill areas caused by malformed clearance holes.
676 BOOST_CHECK_EQUAL( fill->OutlineCount(), 1 );
677
678 double zoneOutlineArea = gndZone->Outline()->Area();
679
680 BOOST_REQUIRE_MESSAGE( zoneOutlineArea > 0.0, "Zone outline area must be positive" );
681
682 double fillArea = 0.0;
683
684 for( int i = 0; i < fill->OutlineCount(); i++ )
685 fillArea += std::abs( fill->Outline( i ).Area() );
686
687 double fillRatio = fillArea / zoneOutlineArea;
688
689 // The zone should be mostly filled. A low fill ratio indicates excessive voids
690 // from malformed clearance holes around shallow arcs.
691 BOOST_CHECK_GE( fillRatio, 0.90 );
692}
693
694
707BOOST_FIXTURE_TEST_CASE( RegressionIterativeRefillRespectsKeepouts, ZONE_FILL_TEST_FIXTURE )
708{
709 // Enable iterative refill
710 ADVANCED_CFG& cfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
711 bool originalIterativeRefill = cfg.m_ZoneFillIterativeRefill;
712 cfg.m_ZoneFillIterativeRefill = true;
713
714 struct ScopeGuard { bool& ref; bool orig; ~ScopeGuard() { ref = orig; } }
715 guard{ cfg.m_ZoneFillIterativeRefill, originalIterativeRefill };
716
717 KI_TEST::LoadBoard( m_settingsManager, "issue22809/issue22809", m_board );
718
719 KI_TEST::FillZones( m_board.get() );
720
721 // Find all zone keepouts
722 std::vector<ZONE*> keepouts;
723
724 for( ZONE* zone : m_board->Zones() )
725 {
726 if( zone->GetIsRuleArea() && zone->GetDoNotAllowZoneFills() )
727 keepouts.push_back( zone );
728 }
729
730 BOOST_REQUIRE_MESSAGE( !keepouts.empty(), "No zone keepouts found in test board" );
731
732 // For each keepout, check that no zone fill exists inside it
733 int violationCount = 0;
734
735 for( ZONE* keepout : keepouts )
736 {
737 for( PCB_LAYER_ID layer : keepout->GetLayerSet().Seq() )
738 {
739 SHAPE_POLY_SET keepoutOutline( *keepout->Outline() );
740 keepoutOutline.ClearArcs();
741
742 for( ZONE* zone : m_board->Zones() )
743 {
744 if( zone->GetIsRuleArea() )
745 continue;
746
747 if( !zone->IsOnLayer( layer ) )
748 continue;
749
750 if( !zone->HasFilledPolysForLayer( layer ) )
751 continue;
752
753 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
754
755 // Check if any fill intersects the keepout
756 SHAPE_POLY_SET intersection = *fill;
757 intersection.BooleanIntersection( keepoutOutline );
758
759 if( intersection.OutlineCount() > 0 )
760 {
761 double intersectionArea = 0;
762
763 for( int i = 0; i < intersection.OutlineCount(); i++ )
764 intersectionArea += std::abs( intersection.Outline( i ).Area() );
765
766 // Allow for small numerical errors (less than 1 square mm)
767 if( intersectionArea > 1e6 )
768 {
769 BOOST_TEST_MESSAGE( wxString::Format(
770 "Zone %s fill on layer %s overlaps keepout by %.2f sq mm",
771 zone->GetNetname(),
772 m_board->GetLayerName( layer ),
773 intersectionArea / 1e6 ) );
774 violationCount++;
775 }
776 }
777 }
778 }
779 }
780
781 BOOST_CHECK_MESSAGE( violationCount == 0,
782 wxString::Format( "Found %d zone fills overlapping keepout areas. "
783 "This indicates issue 22809 - iterative refiller "
784 "ignores zone keepouts.", violationCount ) );
785}
786
787
801BOOST_FIXTURE_TEST_CASE( RegressionTHPadInnerLayerFlashing, ZONE_FILL_TEST_FIXTURE )
802{
803 KI_TEST::LoadBoard( m_settingsManager, "issue22826/issue22826", m_board );
804
805 KI_TEST::FillZones( m_board.get() );
806
807 PCB_LAYER_ID in2Cu = m_board->GetLayerID( wxT( "In2.Cu" ) );
808 int padsWithMissingFlashing = 0;
809 int totalConditionalPads = 0;
810
811 for( FOOTPRINT* footprint : m_board->Footprints() )
812 {
813 for( PAD* pad : footprint->Pads() )
814 {
815 if( !pad->GetRemoveUnconnected() )
816 continue;
817
818 if( !pad->HasHole() )
819 continue;
820
821 if( pad->GetNetname() != "VBUS_DUT" && pad->GetNetname() != "VBUS_DBG" )
822 continue;
823
824 totalConditionalPads++;
825
826 // Check if the pad should flash on In2.Cu
827 bool shouldFlash = false;
828
829 for( ZONE* zone : m_board->Zones() )
830 {
831 if( zone->GetIsRuleArea() )
832 continue;
833
834 if( zone->GetNetCode() != pad->GetNetCode() )
835 continue;
836
837 if( !zone->IsOnLayer( in2Cu ) )
838 continue;
839
840 if( zone->Outline()->Contains( pad->GetPosition() ) )
841 {
842 shouldFlash = true;
843 break;
844 }
845 }
846
847 if( shouldFlash && !pad->FlashLayer( in2Cu ) )
848 {
849 BOOST_TEST_MESSAGE( wxString::Format(
850 "Pad %s at (%d, %d) on net %s is inside zone but not flashing on In2.Cu",
851 pad->GetNumber(), pad->GetPosition().x, pad->GetPosition().y,
852 pad->GetNetname() ) );
853 padsWithMissingFlashing++;
854 }
855 }
856 }
857
858 BOOST_TEST_MESSAGE( wxString::Format( "Total conditional pads: %d, Pads with missing "
859 "flashing: %d", totalConditionalPads,
860 padsWithMissingFlashing ) );
861
862 BOOST_CHECK_MESSAGE( padsWithMissingFlashing == 0,
863 wxString::Format( "Found %d TH pads that should flash on inner layers "
864 "but don't. This indicates issue 22826 is not fixed.",
865 padsWithMissingFlashing ) );
866}
867
868
877BOOST_FIXTURE_TEST_CASE( RegressionRoundRectTeardropGeometry, ZONE_FILL_TEST_FIXTURE )
878{
879 KI_TEST::LoadBoard( m_settingsManager, "issue19405_roundrect_teardrop", m_board );
880
881 // Set up tool manager for teardrop generation
882 TOOL_MANAGER toolMgr;
883 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, nullptr );
884
885 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
886 toolMgr.RegisterTool( dummyTool );
887
888 // Generate teardrops
889 BOARD_COMMIT commit( dummyTool );
890 TEARDROP_MANAGER teardropMgr( m_board.get(), &toolMgr );
891 teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true );
892
893 if( !commit.Empty() )
894 commit.Push( _( "Add teardrops" ), SKIP_UNDO | SKIP_SET_DIRTY );
895
896 // Find teardrop zones
897 int teardropCount = 0;
898 bool foundBadTeardrop = false;
899
900 for( ZONE* zone : m_board->Zones() )
901 {
902 if( !zone->IsTeardropArea() )
903 continue;
904
905 teardropCount++;
906
907 // Get the teardrop outline
908 const SHAPE_POLY_SET* outline = zone->Outline();
909
910 if( !outline || outline->OutlineCount() == 0 )
911 continue;
912
913 const SHAPE_LINE_CHAIN& chain = outline->Outline( 0 );
914
915 // Check that the teardrop polygon is convex or at least doesn't have
916 // any sharp concave angles that would indicate intersection with the pad corner.
917 // A well-formed teardrop should have all turns in the same direction
918 // (or very close to it) except at the pad anchor points.
919 int concaveCount = 0;
920
921 for( int i = 0; i < chain.PointCount(); i++ )
922 {
923 int prev = ( i == 0 ) ? chain.PointCount() - 1 : i - 1;
924 int next = ( i + 1 ) % chain.PointCount();
925
926 VECTOR2I v1 = chain.CPoint( i ) - chain.CPoint( prev );
927 VECTOR2I v2 = chain.CPoint( next ) - chain.CPoint( i );
928
929 // Cross product gives handedness of turn
930 int64_t cross = (int64_t) v1.x * v2.y - (int64_t) v1.y * v2.x;
931
932 // Count significant concave turns (negative cross product for CCW polygons)
933 // Small values are numerical noise
934 if( cross < -1000 )
935 concaveCount++;
936 }
937
938 // A teardrop should have at most 2-3 concave points (at the pad anchor points)
939 // Many concave points indicate the curve is intersecting the pad corner
940 if( concaveCount > 5 )
941 {
942 BOOST_TEST_MESSAGE( wxString::Format( "Teardrop has %d concave vertices, "
943 "indicating possible corner intersection",
944 concaveCount ) );
945 foundBadTeardrop = true;
946 }
947 }
948
949 BOOST_CHECK_MESSAGE( teardropCount > 0, "Expected at least one teardrop zone" );
950
951 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
952 "Found teardrop with excessive concave vertices, indicating "
953 "issue 19405 - teardrop curve intersecting rounded rectangle corner" );
954}
955
956
965{
966 KI_TEST::LoadBoard( m_settingsManager, "oval_teardrop", m_board );
967
968 // Set up tool manager for teardrop generation
969 TOOL_MANAGER toolMgr;
970 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, nullptr );
971
972 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
973 toolMgr.RegisterTool( dummyTool );
974
975 // Generate teardrops
976 BOARD_COMMIT commit( dummyTool );
977 TEARDROP_MANAGER teardropMgr( m_board.get(), &toolMgr );
978 teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true );
979
980 if( !commit.Empty() )
981 commit.Push( _( "Add teardrops" ), SKIP_UNDO | SKIP_SET_DIRTY );
982
983 // Find teardrop zones
984 int teardropCount = 0;
985 bool foundBadTeardrop = false;
986
987 for( ZONE* zone : m_board->Zones() )
988 {
989 if( !zone->IsTeardropArea() )
990 continue;
991
992 teardropCount++;
993
994 const SHAPE_POLY_SET* outline = zone->Outline();
995
996 if( !outline || outline->OutlineCount() == 0 )
997 continue;
998
999 const SHAPE_LINE_CHAIN& chain = outline->Outline( 0 );
1000
1001 // Check for excessive concave vertices that would indicate the teardrop curve
1002 // is not tangent to the oval's semicircular end
1003 int concaveCount = 0;
1004
1005 for( int i = 0; i < chain.PointCount(); i++ )
1006 {
1007 int prev = ( i == 0 ) ? chain.PointCount() - 1 : i - 1;
1008 int next = ( i + 1 ) % chain.PointCount();
1009
1010 VECTOR2I v1 = chain.CPoint( i ) - chain.CPoint( prev );
1011 VECTOR2I v2 = chain.CPoint( next ) - chain.CPoint( i );
1012
1013 int64_t cross = (int64_t) v1.x * v2.y - (int64_t) v1.y * v2.x;
1014
1015 if( cross < -1000 )
1016 concaveCount++;
1017 }
1018
1019 if( concaveCount > 5 )
1020 {
1021 BOOST_TEST_MESSAGE( wxString::Format( "Oval teardrop has %d concave vertices",
1022 concaveCount ) );
1023 foundBadTeardrop = true;
1024 }
1025 }
1026
1027 BOOST_CHECK_MESSAGE( teardropCount > 0, "Expected at least one teardrop zone" );
1028
1029 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
1030 "Found teardrop with excessive concave vertices on oval pad, "
1031 "indicating curve is not tangent to semicircular end" );
1032}
1033
1034
1043{
1044 KI_TEST::LoadBoard( m_settingsManager, "large_circle_teardrop", m_board );
1045
1046 // Set up tool manager for teardrop generation
1047 TOOL_MANAGER toolMgr;
1048 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, nullptr );
1049
1050 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
1051 toolMgr.RegisterTool( dummyTool );
1052
1053 // Generate teardrops
1054 BOARD_COMMIT commit( dummyTool );
1055 TEARDROP_MANAGER teardropMgr( m_board.get(), &toolMgr );
1056 teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true );
1057
1058 if( !commit.Empty() )
1059 commit.Push( _( "Add teardrops" ), SKIP_UNDO | SKIP_SET_DIRTY );
1060
1061 // Find the pad and its teardrop
1062 PAD* largePad = nullptr;
1063
1064 for( FOOTPRINT* fp : m_board->Footprints() )
1065 {
1066 for( PAD* pad : fp->Pads() )
1067 {
1068 if( pad->GetShape( F_Cu ) == PAD_SHAPE::CIRCLE )
1069 {
1070 largePad = pad;
1071 break;
1072 }
1073 }
1074 }
1075
1076 BOOST_REQUIRE_MESSAGE( largePad != nullptr, "Expected a circular pad in test board" );
1077
1078 int padRadius = largePad->GetSize( F_Cu ).x / 2;
1079 VECTOR2I padCenter = largePad->GetPosition();
1080
1081 // Find teardrop zones
1082 int teardropCount = 0;
1083 bool foundBadTeardrop = false;
1084
1085 for( ZONE* zone : m_board->Zones() )
1086 {
1087 if( !zone->IsTeardropArea() )
1088 continue;
1089
1090 teardropCount++;
1091
1092 const SHAPE_POLY_SET* outline = zone->Outline();
1093
1094 if( !outline || outline->OutlineCount() == 0 )
1095 continue;
1096
1097 const SHAPE_LINE_CHAIN& chain = outline->Outline( 0 );
1098
1099 // Check for excessive concave vertices
1100 int concaveCount = 0;
1101
1102 for( int i = 0; i < chain.PointCount(); i++ )
1103 {
1104 int prev = ( i == 0 ) ? chain.PointCount() - 1 : i - 1;
1105 int next = ( i + 1 ) % chain.PointCount();
1106
1107 VECTOR2I v1 = chain.CPoint( i ) - chain.CPoint( prev );
1108 VECTOR2I v2 = chain.CPoint( next ) - chain.CPoint( i );
1109
1110 int64_t cross = (int64_t) v1.x * v2.y - (int64_t) v1.y * v2.x;
1111
1112 if( cross < -1000 )
1113 concaveCount++;
1114 }
1115
1116 if( concaveCount > 5 )
1117 {
1118 BOOST_TEST_MESSAGE( wxString::Format( "Large circle teardrop has %d concave vertices",
1119 concaveCount ) );
1120 foundBadTeardrop = true;
1121 }
1122
1123 // Also verify that the teardrop anchor points near the pad are approximately
1124 // on the circle edge (within tolerance)
1125 int maxError = m_board->GetDesignSettings().m_MaxError;
1126
1127 for( int i = 0; i < chain.PointCount(); i++ )
1128 {
1129 VECTOR2I pt = chain.CPoint( i );
1130 double dist = ( pt - padCenter ).EuclideanNorm();
1131
1132 // Points that are close to the circle should be approximately on it
1133 if( dist > padRadius * 0.5 && dist < padRadius * 1.5 )
1134 {
1135 double deviation = std::abs( dist - padRadius );
1136
1137 // Allow some tolerance for polygon approximation
1138 if( deviation > maxError * 5 && deviation < padRadius * 0.2 )
1139 {
1140 BOOST_TEST_MESSAGE( wxString::Format(
1141 "Teardrop point at distance %.2f from pad center (radius %.2f), "
1142 "deviation %.2f exceeds tolerance",
1143 dist / 1000.0, padRadius / 1000.0, deviation / 1000.0 ) );
1144 }
1145 }
1146 }
1147 }
1148
1149 BOOST_CHECK_MESSAGE( teardropCount > 0, "Expected at least one teardrop zone" );
1150
1151 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
1152 "Found teardrop with excessive concave vertices on large circle, "
1153 "indicating anchor points may not be on circle edge" );
1154}
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:83
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:168
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
const KIID m_Uuid
Definition eda_item.h:521
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:74
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:607
double GetFilledArea()
This area is cached from the most recent call to CalculateFilledArea().
Definition zone.h:266
SHAPE_POLY_SET * Outline()
Definition zone.h:341
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition zone.h:598
double CalculateFilledArea()
Compute the area currently occupied by the zone fill.
Definition zone.cpp:1574
@ 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